From e4fc0f5d15444a12991a3c1e370b451aee175708 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Wed, 29 Apr 2020 23:15:36 -0400 Subject: [PATCH] Handle initial responses for AUTH PLAIN. See RFC 4954, Section 4. --- smtp/conn.go | 35 ++++++++++++++++++++++++----------- smtp/conn_test.go | 41 +++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/smtp/conn.go b/smtp/conn.go index 11995b1..f1c195d 100644 --- a/smtp/conn.go +++ b/smtp/conn.go @@ -97,7 +97,12 @@ func AcceptConnection(netConn net.Conn, server Server, log zap.Logger) { return } - conn.log.Info("ReadLine()", zap.String("line", conn.line)) + lineForLog := conn.line + const authPlain = "AUTH PLAIN " + if strings.HasPrefix(conn.line, authPlain) { + lineForLog = authPlain + "[redacted]" + } + conn.log.Info("ReadLine()", zap.String("line", lineForLog)) var cmd string if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil { @@ -250,9 +255,9 @@ func (conn *connection) doAUTH() { return } - var cmd, authType string - _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &authType) - if err != nil { + var cmd, authType, authString string + n, err := fmt.Sscanf(conn.line, "%s %s %s", &cmd, &authType, &authString) + if n < 2 { conn.reply(ReplyBadSyntax) return } @@ -262,18 +267,26 @@ func (conn *connection) doAUTH() { return } + // If only 2 tokens were scanned, then an initial response was not provided. + if n == 2 && conn.line[len(conn.line)-1] != ' ' { + conn.reply(ReplyBadSyntax) + return + } + conn.log.Info("doAUTH()") - conn.writeReply(334, " ") + if authString == "" { + conn.writeReply(334, " ") - authLine, err := conn.tp.ReadLine() - if err != nil { - conn.log.Error("failed to read auth line", zap.Error(err)) - conn.reply(ReplyBadSyntax) - return + authString, err = conn.tp.ReadLine() + if err != nil { + conn.log.Error("failed to read auth line", zap.Error(err)) + conn.reply(ReplyBadSyntax) + return + } } - authBytes, err := base64.StdEncoding.DecodeString(authLine) + authBytes, err := base64.StdEncoding.DecodeString(authString) if err != nil { conn.reply(ReplyBadSyntax) return diff --git a/smtp/conn_test.go b/smtp/conn_test.go index 27dd63f..fd4fef3 100644 --- a/smtp/conn_test.go +++ b/smtp/conn_test.go @@ -117,13 +117,15 @@ type requestResponse struct { func runTableTest(t testing.TB, conn *textproto.Conn, seq []requestResponse) { for i, rr := range seq { - t.Logf("%s case %d", _fl(1), i) ok(t, conn.PrintfLine(rr.request)) if rr.handler != nil { rr.handler(t, conn) } else { readCodeLine(t, conn, rr.responseCode) } + if t.Failed() { + t.Logf("%s case %d", _fl(1), i) + } } } @@ -396,19 +398,38 @@ func TestAuth(t *testing.T) { runTableTest(t, conn, []requestResponse{ {"AUTH", 501, nil}, {"AUTH OAUTHBEARER", 504, nil}, - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN", 501, nil}, // Bad syntax, missing space. + {"AUTH PLAIN ", 334, nil}, {b64enc("abc\x00def\x00ghf"), 535, nil}, - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN ", 334, nil}, {b64enc("\x00"), 501, nil}, - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN ", 334, nil}, {"this isn't base 64", 501, nil}, - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN ", 334, nil}, {b64enc("-authz-\x00-authc-\x00goats"), 250, nil}, - {"AUTH PLAIN", 503, nil}, // already authenticated + {"AUTH PLAIN ", 503, nil}, // Already authenticated. {"NOOP", 250, nil}, }) } +func TestAuthNoInitialResponse(t *testing.T) { + l := runServer(t, &testServer{ + tlsConfig: getTLSConfig(t), + userAuth: &userAuth{ + authz: "", + authc: "user", + passwd: "longpassword", + }, + }) + defer l.Close() + + conn := setupTLSClient(t, l.Addr()) + + runTableTest(t, conn, []requestResponse{ + {"AUTH PLAIN " + b64enc("\x00user\x00longpassword"), 250, nil}, + }) +} + func TestRelayRequiresAuth(t *testing.T) { l := runServer(t, &testServer{ domain: "example.com", @@ -426,7 +447,7 @@ func TestRelayRequiresAuth(t *testing.T) { runTableTest(t, conn, []requestResponse{ {"MAIL FROM:", 550, nil}, {"MAIL FROM:", 550, nil}, - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN ", 334, nil}, {b64enc("\x00mailbox@example.com\x00test"), 250, nil}, {"MAIL FROM:", 250, nil}, }) @@ -445,7 +466,7 @@ func setupRelayTest(t *testing.T) (server *testServer, l net.Listener, conn *tex l = runServer(t, server) conn = setupTLSClient(t, l.Addr()) runTableTest(t, conn, []requestResponse{ - {"AUTH PLAIN", 334, nil}, + {"AUTH PLAIN ", 334, nil}, {b64enc("\x00mailbox@example.com\x00test"), 250, nil}, }) return @@ -509,7 +530,7 @@ func TestSendAsRelay(t *testing.T) { }) if len(server.relayed) != 1 { - t.Errorf("Expected 1 relayed message, got %d", len(server.relayed)) + t.Fatalf("Expected 1 relayed message, got %d", len(server.relayed)) } replaced := "source@example.com" @@ -560,7 +581,7 @@ func TestSendMultipleRelay(t *testing.T) { }) if len(server.relayed) != 1 { - t.Errorf("Expected 1 relayed message, got %d", len(server.relayed)) + t.Fatalf("Expected 1 relayed message, got %d", len(server.relayed)) } replaced := "source@example.com" -- 2.22.5