diff --git a/server/listen/pop3_server/action.go b/server/listen/pop3_server/action.go index 9bb1c4f4..b4c52f30 100644 --- a/server/listen/pop3_server/action.go +++ b/server/listen/pop3_server/action.go @@ -79,12 +79,6 @@ func (a action) User(session *gopop.Session, username string) error { session.Ctx = tc } log.WithContext(session.Ctx).Debug("POP3 USER received") - - infos := strings.Split(username, "@") - if len(infos) > 1 { - username = infos[0] - } - log.WithContext(session.Ctx).Debug("POP3 user name normalized") session.User = username diff --git a/server/listen/pop3_server/action_test.go b/server/listen/pop3_server/action_test.go index 5c7dbc5d..2ff87962 100644 --- a/server/listen/pop3_server/action_test.go +++ b/server/listen/pop3_server/action_test.go @@ -62,3 +62,13 @@ func Test_email(t *testing.T) { fmt.Printf("%s", b.String()) } + +func TestActionUserPreservesFullEmailIdentifier(t *testing.T) { + session := &gopop.Session{} + if err := (action{}).User(session, "Alice@Example.com"); err != nil { + t.Fatal(err) + } + if session.User != "Alice@Example.com" { + t.Fatalf("POP3 user identifier = %q, want full email identifier", session.User) + } +} diff --git a/server/listen/smtp_server/action.go b/server/listen/smtp_server/action.go index 1800defa..1b59dd68 100644 --- a/server/listen/smtp_server/action.go +++ b/server/listen/smtp_server/action.go @@ -62,7 +62,6 @@ func (s *Session) Auth(mech string) (sasl.Server, error) { } func (s *Session) AuthPlain(username, pwd string) error { - username = auth.NormalizeAccount(username) user, err := auth.AuthenticateProtocol("smtp", s.RemoteAddress.String(), username, pwd) if err != nil { log.WithContext(s.Ctx).Info("SMTP authentication failed") diff --git a/server/services/auth/auth_test.go b/server/services/auth/auth_test.go index 0f1374f2..aac61c13 100644 --- a/server/services/auth/auth_test.go +++ b/server/services/auth/auth_test.go @@ -106,3 +106,46 @@ func TestHasAuthRequiresMailboxOwnershipForAdministrators(t *testing.T) { t.Fatal("administrator bypassed mailbox ownership") } } + +func TestProtocolAuthenticationAcceptsConfiguredFullEmailIdentifier(t *testing.T) { + oldConfig := config.Instance + oldDB := db.Instance + config.Instance = &config.Config{ + DbType: config.DBTypeSQLite, + DbDSN: filepath.Join(t.TempDir(), "full-email-auth.db"), + Domain: "example.com", + Domains: []string{"example.com"}, + } + if err := db.Init(""); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if db.Instance != nil && db.Instance != oldDB { + _ = db.Instance.Close() + } + db.Instance = oldDB + config.Instance = oldConfig + }) + + user := models.User{Account: "alice", Name: "Alice"} + if err := SetPassword(&user, "CorrectP12"); err != nil { + t.Fatal(err) + } + if _, err := db.Instance.Insert(&user); err != nil { + t.Fatal(err) + } + + for _, protocol := range []string{"smtp", "imap", "pop3"} { + got, err := AuthenticateProtocol(protocol, "192.0.2.10:12345", " Alice@Example.com ", "CorrectP12") + if err != nil { + t.Fatalf("%s full email authentication failed: %v", protocol, err) + } + if got == nil || got.Account != "alice" { + t.Fatalf("%s authenticated user = %+v", protocol, got) + } + } + + if _, err := AuthenticateProtocol("imap", "192.0.2.10:12345", "alice@other.example", "CorrectP12"); err == nil { + t.Fatal("unconfigured email domain should be rejected") + } +} diff --git a/server/services/auth/protocol_limiter.go b/server/services/auth/protocol_limiter.go index 4c2c7c90..4cc05e5d 100644 --- a/server/services/auth/protocol_limiter.go +++ b/server/services/auth/protocol_limiter.go @@ -42,7 +42,10 @@ var ( ) func AuthenticateProtocol(protocol string, remoteAddress string, account string, plain string) (*models.User, error) { - normalizedAccount := strings.ToLower(NormalizeAccount(account)) + normalizedAccount := strings.ToLower(strings.TrimSpace(account)) + if resolvedAccount, err := NormalizeIdentifier(account); err == nil { + normalizedAccount = resolvedAccount + } normalizedRemote := protocolRemoteHost(remoteAddress) pairKey := hashedProtocolLoginKey("pair", protocol, normalizedRemote, normalizedAccount) accountKey := hashedProtocolLoginKey("account", normalizedAccount) @@ -51,7 +54,7 @@ func AuthenticateProtocol(protocol string, remoteAddress string, account string, if protocolPairAttempts.blocked(pairKey) || protocolAccountAttempts.blocked(accountKey) || protocolRemoteAttempts.blocked(remoteKey) { return nil, errors.New("invalid credentials") } - user, err := Authenticate(account, plain) + user, err := AuthenticateIdentifier(account, plain) if err != nil { protocolPairAttempts.recordFailure(pairKey) protocolAccountAttempts.recordFailure(accountKey)