Handle initial responses for AUTH PLAIN.
authorRobert Sesek <rsesek@bluestatic.org>
Thu, 30 Apr 2020 03:15:36 +0000 (23:15 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Thu, 30 Apr 2020 03:15:36 +0000 (23:15 -0400)
See RFC 4954, Section 4.

smtp/conn.go
smtp/conn_test.go

index 11995b14e4e7bc9cf49297fa03184bcadf723118..f1c195d220b0534df802b00097536d4ba823bb1d 100644 (file)
@@ -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
index 27dd63f60e7737255a9706a7b345515f06cb1e0e..fd4fef3102c6b6273aae7b555b78831039b68c1b 100644 (file)
@@ -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:<apples@example.com>", 550, nil},
                {"MAIL FROM:<mailbox@example.com>", 550, nil},
-               {"AUTH PLAIN", 334, nil},
+               {"AUTH PLAIN ", 334, nil},
                {b64enc("\x00mailbox@example.com\x00test"), 250, nil},
                {"MAIL FROM:<mailbox@example.com>", 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"