From 8709ba9de6ff422045bb004b6c742f84c9057c7f Mon Sep 17 00:00:00 2001 From: Ian Kent Date: Tue, 22 Apr 2014 21:19:48 +0100 Subject: [PATCH] Add basic AUTH support (RFC4954) --- mailhog/smtp/session.go | 41 ++++++++++++++++++++++++++++++++++++++--- main_test.go | 5 ++++- mime_test.go | 5 ++++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/mailhog/smtp/session.go b/mailhog/smtp/session.go index cdd58b6..0f4f182 100644 --- a/mailhog/smtp/session.go +++ b/mailhog/smtp/session.go @@ -19,10 +19,13 @@ type Session struct { state int message *data.SMTPMessage mongo *storage.MongoDB + isTLS bool } const ( ESTABLISH = iota + AUTH + AUTH2 MAIL RCPT DATA @@ -32,7 +35,7 @@ const ( // TODO replace ".." lines with . in data func StartSession(conn *net.TCPConn, conf *mailhog.Config, mongo *storage.MongoDB) { - conv := &Session{conn, "", conf, ESTABLISH, &data.SMTPMessage{}, mongo} + conv := &Session{conn, "", conf, ESTABLISH, &data.SMTPMessage{}, mongo, false} conv.log("Starting session") conv.Write("220", conv.conf.Hostname + " ESMTP Go-MailHog") conv.Read() @@ -145,13 +148,45 @@ func (c *Session) Process(line string) { c.log("Got EHLO command, switching to MAIL state") c.state = MAIL c.message.Helo = args - c.Write("250", "Hello " + args, "PIPELINING") + c.Write("250", "Hello " + args, "PIPELINING", "AUTH EXTERNAL CRAM-MD5 LOGIN PLAIN") default: c.log("Got unknown command for ESTABLISH state: '%s'", command) c.Write("500", "Unrecognised command") } - case c.state == MAIL: + case c.state == AUTH: + c.log("Got authentication response: '%s', switching to MAIL state", args) + c.state = MAIL + c.Write("235", "Authentication successful") + case c.state == AUTH2: + c.log("Got LOGIN authentication response: '%s', switching to AUTH state", args) + c.state = AUTH + c.Write("334", "VXNlcm5hbWU6") + case c.state == MAIL: // TODO rename/split state switch command { + case "AUTH": + c.log("Got AUTH command, staying in MAIL state") + switch { + case strings.HasPrefix(args, "PLAIN "): + c.log("Got PLAIN authentication: %s", strings.TrimPrefix(args, "PLAIN ")) + c.Write("235", "Authentication successful") + case args == "LOGIN": + c.log("Got LOGIN authentication, switching to AUTH state") + c.state = AUTH + c.Write("334", "VXNlcm5hbWU6") + case args == "PLAIN": + c.log("Got PLAIN authentication (no args), switching to AUTH state") + c.state = AUTH + c.Write("334", "") + case args == "CRAM-MD5": + c.log("Got CRAM-MD5 authentication, switching to AUTH state") + c.state = AUTH + c.Write("334", "PDQxOTI5NDIzNDEuMTI4Mjg0NzJAc291cmNlZm91ci5hbmRyZXcuY211LmVkdT4=") + case args == "EXTERNAL ": + c.log("Got EXTERNAL authentication: %s", strings.TrimPrefix(args, "EXTERNAL ")) + c.Write("235", "Authentication successful") + default: + c.Write("504", "Unsupported authentication mechanism") + } case "MAIL": c.log("Got MAIL command, switching to RCPT state") r, _ := regexp.Compile("From:<([^>]+)>") diff --git a/main_test.go b/main_test.go index ed68088..3ac4cd3 100644 --- a/main_test.go +++ b/main_test.go @@ -34,7 +34,10 @@ func TestBasicHappyPath(t *testing.T) { assert.Equal(t, string(buf[0:n]), "250-Hello localhost\n") n, err = conn.Read(buf) assert.Nil(t, err) - assert.Equal(t, string(buf[0:n]), "250 PIPELINING\n") + assert.Equal(t, string(buf[0:n]), "250-PIPELINING\n") + n, err = conn.Read(buf) + assert.Nil(t, err) + assert.Equal(t, string(buf[0:n]), "250 AUTH EXTERNAL CRAM-MD5 LOGIN PLAIN\n") // Send MAIL _, err = conn.Write([]byte("MAIL From:\r\n")) diff --git a/mime_test.go b/mime_test.go index ce86e4a..0ef8e55 100644 --- a/mime_test.go +++ b/mime_test.go @@ -34,7 +34,10 @@ func TestBasicMIMEHappyPath(t *testing.T) { assert.Equal(t, string(buf[0:n]), "250-Hello localhost\n") n, err = conn.Read(buf) assert.Nil(t, err) - assert.Equal(t, string(buf[0:n]), "250 PIPELINING\n") + assert.Equal(t, string(buf[0:n]), "250-PIPELINING\n") + n, err = conn.Read(buf) + assert.Nil(t, err) + assert.Equal(t, string(buf[0:n]), "250 AUTH EXTERNAL CRAM-MD5 LOGIN PLAIN\n") // Send MAIL _, err = conn.Write([]byte("MAIL From:\r\n"))