MailHog/mailhog/smtp/smtp.go

131 lines
2.7 KiB
Go

package smtp
// http://www.rfc-editor.org/rfc/rfc5321.txt
import (
"log"
"net"
"strings"
"github.com/ian-kent/MailHog/mailhog"
)
type Session struct {
conn *net.TCPConn
line string
conf *mailhog.Config
state int
}
const (
ESTABLISH = iota
MAIL
RCPT
DATA
)
type Message struct {
From string
To string
Data []byte
Helo string
}
func StartSession(conn *net.TCPConn, conf *mailhog.Config) {
conv := &Session{conn, "", conf, ESTABLISH}
conv.log("Starting session")
conv.Write("220", "Go-MailHog")
conv.Read()
}
func (c *Session) log(message string, args ...interface{}) {
message = strings.Join([]string{"[%s, %d]", message}, " ")
args = append([]interface{}{c.conn.RemoteAddr(), c.state}, args...)
log.Printf(message, args...)
}
func (c *Session) Read() {
buf := make([]byte, 1024)
n, err := c.conn.Read(buf)
if n == 0 {
c.log("Connection closed by remote host\n")
return
}
if err != nil {
c.log("Error reading from socket: %s\n", err)
return
}
text := string(buf[0:n])
c.log("Received %d bytes: '%s'\n", n, text)
c.line += text
c.Parse()
}
func (c *Session) Parse() {
for strings.Contains(c.line, "\n") {
parts := strings.SplitN(c.line, "\n", 2)
if len(parts) == 2 {
c.line = parts[1]
} else {
c.line = ""
}
c.Process(strings.Trim(parts[0], "\r\n"))
}
c.Read()
}
func (c *Session) Write(code string, text ...string) {
if len(text) == 1 {
c.conn.Write([]byte(code + " " + text[0] + "\n"))
return
}
for i := 0; i < len(text) - 2; i++ {
c.conn.Write([]byte(code + "-" + text[i] + "\n"))
}
c.conn.Write([]byte(code + " " + text[len(text)] + "\n"))
}
func (c *Session) Process(line string) {
c.log("Processing line: %s", line)
words := strings.Split(line, " ")
command := words[0]
c.log("In state %d, got command '%s'", c.state, command)
switch {
case command == "RSET":
c.log("Got RSET command, switching to ESTABLISH state")
c.state = ESTABLISH
c.Write("250", "OK")
case command == "NOOP":
c.log("Got NOOP command")
c.Write("250", "OK")
case command == "QUIT":
c.log("Got QUIT command")
c.Write("221", "OK")
case c.state == ESTABLISH:
switch command {
case "HELO":
c.log("Got HELO command, switching to MAIL state")
c.state = MAIL
c.Write("250", "HELO " + c.conf.Hostname)
case "EHLO":
c.log("Got EHLO command, switching to MAIL state")
c.state = MAIL
c.Write("250", "EHLO " + c.conf.Hostname)
default:
c.log("Got unknown command for ESTABLISH state: '%s'", command)
}
case c.state == MAIL:
switch command {
case "MAIL":
c.log("Got MAIL command")
default:
c.log("Got unknown command for MAIL state: '%s'", command)
}
}
}