diff --git a/imapserver/authenticate_test.go b/imapserver/authenticate_test.go index fbcf686..f2e0c54 100644 --- a/imapserver/authenticate_test.go +++ b/imapserver/authenticate_test.go @@ -147,7 +147,7 @@ func testAuthenticateSCRAM(t *testing.T, tls bool, method string, h func() hash. tc.transactf("no", "authenticate bogus ") tc.transactf("bad", "authenticate %s not base64...", method) - tc.transactf("bad", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte("bad data"))) + tc.transactf("no", "authenticate %s %s", method, base64.StdEncoding.EncodeToString([]byte("bad data"))) // NFD username, with PRECIS-cleaned password. auth("ok", nil, "mo\u0301x@mox.example", password1) diff --git a/imapserver/server.go b/imapserver/server.go index e247104..48747e3 100644 --- a/imapserver/server.go +++ b/imapserver/server.go @@ -1708,7 +1708,8 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) { c0 := xreadInitial() ss, err := scram.NewServer(h, c0, cs, requireChannelBinding) if err != nil { - xsyntaxErrorf("starting scram: %s", err) + c.log.Infox("scram protocol error", err, slog.Any("remote", c.remoteIP)) + xuserErrorf("scram protocol error: %s", err) } c.log.Debug("scram auth", slog.String("authentication", ss.Authentication)) acc, _, err := store.OpenEmail(c.log, ss.Authentication) @@ -1767,6 +1768,13 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) { authResult = "badcreds" c.log.Info("failed authentication attempt", slog.String("username", ss.Authentication), slog.Any("remote", c.remoteIP)) xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") + } else if errors.Is(err, scram.ErrChannelBindingsDontMatch) { + authResult = "badchanbind" + c.log.Warn("bad channel binding during authentication, potential mitm", slog.String("username", ss.Authentication), slog.Any("remote", c.remoteIP)) + xusercodeErrorf("AUTHENTICATIONFAILED", "channel bindings do not match, potential mitm") + } else if errors.Is(err, scram.ErrInvalidEncoding) { + c.log.Infox("bad scram protocol message", err, slog.String("username", ss.Authentication), slog.Any("remote", c.remoteIP)) + xuserErrorf("bad scram protocol message: %s", err) } xuserErrorf("server final: %w", err) } diff --git a/metrics/auth.go b/metrics/auth.go index 940e366..3014a9d 100644 --- a/metrics/auth.go +++ b/metrics/auth.go @@ -16,7 +16,7 @@ var ( "kind", // submission, imap, webmail, webapi, webaccount, webadmin (formerly httpaccount, httpadmin) "variant", // login, plain, scram-sha-256, scram-sha-1, cram-md5, weblogin, websessionuse, httpbasic. // todo: we currently only use badcreds, but known baduser can be helpful - "result", // ok, baduser, badpassword, badcreds, error, aborted + "result", // ok, baduser, badpassword, badcreds, badchanbind, error, aborted }, ) diff --git a/smtpserver/server.go b/smtpserver/server.go index 5b1ccbb..866cb47 100644 --- a/smtpserver/server.go +++ b/smtpserver/server.go @@ -1186,11 +1186,9 @@ func (c *conn) cmdAuth(p *parser) { addr := norm.NFC.String(t[0]) c.log.Debug("cram-md5 auth", slog.String("address", addr)) acc, _, err := store.OpenEmail(c.log, addr) - if err != nil { - if errors.Is(err, store.ErrUnknownCredentials) { - c.log.Info("failed authentication attempt", slog.String("username", addr), slog.Any("remote", c.remoteIP)) - xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") - } + if err != nil && errors.Is(err, store.ErrUnknownCredentials) { + c.log.Info("failed authentication attempt", slog.String("username", addr), slog.Any("remote", c.remoteIP)) + xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") } xcheckf(err, "looking up address") defer func() { @@ -1271,7 +1269,10 @@ func (c *conn) cmdAuth(p *parser) { } c0 := xreadInitial("") ss, err := scram.NewServer(h, c0, cs, channelBindingRequired) - xcheckf(err, "starting scram") + if err != nil { + c.log.Infox("scram protocol error", err, slog.Any("remote", c.remoteIP)) + xsmtpUserErrorf(smtp.C455BadParams, smtp.SePol7Other0, "scram protocol error: %s", err) + } authc := norm.NFC.String(ss.Authentication) c.log.Debug("scram auth", slog.String("authentication", authc)) acc, _, err := store.OpenEmail(c.log, authc) @@ -1332,6 +1333,13 @@ func (c *conn) cmdAuth(p *parser) { authResult = "badcreds" c.log.Info("failed authentication attempt", slog.String("username", authc), slog.Any("remote", c.remoteIP)) xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad credentials") + } else if errors.Is(err, scram.ErrChannelBindingsDontMatch) { + authResult = "badchanbind" + c.log.Warn("bad channel binding during authentication, potential mitm", slog.String("username", authc), slog.Any("remote", c.remoteIP)) + xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7MsgIntegrity7, "channel bindings do not match, potential mitm") + } else if errors.Is(err, scram.ErrInvalidEncoding) { + c.log.Infox("bad scram protocol message", err, slog.String("username", authc), slog.Any("remote", c.remoteIP)) + xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7Other0, "bad scram protocol message") } xcheckf(err, "server final") }