From fc2097fc335cdebfda8b6d1b72334135074beb55 Mon Sep 17 00:00:00 2001 From: Ian Kent Date: Sat, 19 Apr 2014 12:19:17 +0100 Subject: [PATCH] More commands, better connection handling/logging --- mailhog/config.go | 13 ++++++ mailhog/smtp/smtp.go | 95 +++++++++++++++++++++++++++++--------------- main.go | 36 ++++------------- 3 files changed, 83 insertions(+), 61 deletions(-) create mode 100644 mailhog/config.go diff --git a/mailhog/config.go b/mailhog/config.go new file mode 100644 index 0000000..f1410e9 --- /dev/null +++ b/mailhog/config.go @@ -0,0 +1,13 @@ +package mailhog + +func DefaultConfig() *Config { + return &Config{ + BindAddr: "0.0.0.0:1025", + Hostname: "mailhog.example", + } +} + +type Config struct { + BindAddr string + Hostname string +} diff --git a/mailhog/smtp/smtp.go b/mailhog/smtp/smtp.go index dbb83a8..b96ac1f 100644 --- a/mailhog/smtp/smtp.go +++ b/mailhog/smtp/smtp.go @@ -6,13 +6,23 @@ 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 @@ -20,19 +30,20 @@ type Message struct { Helo string } -func StartSession(conn *net.TCPConn) { - conv := &Session{conn, ""} - log.Printf("Starting session with %s", conn.RemoteAddr()) - conv.Begin() +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]", message}, " ") - args = append([]interface{}{c.conn.RemoteAddr()}, args...) +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() { +func (c *Session) Read() { buf := make([]byte, 1024) n, err := c.conn.Read(buf) @@ -45,26 +56,29 @@ func (c Session) Read() { return } - text := string(buf) - c.log("Received %d bytes: %s\n", n, text) + text := string(buf[0:n]) + c.log("Received %d bytes: '%s'\n", n, text) c.line += text c.Parse() } -func (c Session) Parse() { +func (c *Session) Parse() { for strings.Contains(c.line, "\n") { parts := strings.SplitN(c.line, "\n", 2) - c.line = parts[1] - c.log("Parsing string: %s", parts[0]) + 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) { +func (c *Session) Write(code string, text ...string) { if len(text) == 1 { c.conn.Write([]byte(code + " " + text[0] + "\n")) return @@ -75,28 +89,43 @@ func (c Session) Write(code string, text ...string) { c.conn.Write([]byte(code + " " + text[len(text)] + "\n")) } -func (c Session) Process(line string) { +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 words[0] { - case "HELO": - c.log("Got HELO command") - c.Write("250", "HELO " + "my.hostname") - case "EHLO": - c.log("Got EHLO command") - c.Write("250", "HELO " + "my.hostname") - default: - c.log("Got unknown command: '%s'", words[0]) + 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) + } } } - -func (c Session) Begin() { - _, err := c.conn.Write([]byte("220 Go-MailHog\n")) - if err != nil { - c.log("Failed writing to socket: %s\n", err) - return - } - c.Read() -} diff --git a/main.go b/main.go index c5f11d7..42e2937 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,11 @@ import ( "flag" "log" "net" + "github.com/ian-kent/MailHog/mailhog" "github.com/ian-kent/MailHog/mailhog/smtp" ) -var conf = map[string]string { - "BIND_ADDRESS": "0.0.0.0:1025", - "HOSTNAME": "mailhog.example", -} +var conf *mailhog.Config func config() { var listen, hostname string @@ -20,14 +18,16 @@ func config() { flag.Parse() - conf["BIND_ADDRESS"] = listen - conf["HOSTNAME"] = hostname + conf = &mailhog.Config{ + BindAddr: listen, + Hostname: hostname, + } } func main() { config() - ln := listen(conf["BIND_ADDRESS"]) + ln := listen(conf.BindAddr) defer ln.Close() for { @@ -38,7 +38,7 @@ func main() { } defer conn.Close() - go smtp.StartSession(conn.(*net.TCPConn)) + go smtp.StartSession(conn.(*net.TCPConn), conf) } } @@ -50,23 +50,3 @@ func listen(bind string) (*net.TCPListener) { } return ln.(*net.TCPListener) } - -func accept(conn net.Conn) { - buf := make([]byte, 1024) - n, err := conn.Read(buf) - - if err != nil { - log.Printf("Error reading from socket: %s", err) - return - } - - log.Printf("Received %s bytes: %s\n", n, string(buf)) - - _, err = conn.Write(buf) - if err != nil { - log.Printf("Error writing to socket: %s\n", err) - return - } - - log.Printf("Reply sent\n") -}