-
-
Notifications
You must be signed in to change notification settings - Fork 103
Description
Summary
The AUTH LOGIN challenge strings in SMTP class contain trailing null bytes (\x00), which causes strict SMTP clients to abort authentication.
# aiosmtpd/smtp.py lines 23-24
AuthLoginUsernameChallenge = "User Name\x00"
AuthLoginPasswordChallenge = "Password\x00"This produces base64-encoded challenges with the null byte included:
VXNlciBOYW1lAA==(User Name\x00) instead ofVXNlciBOYW1l(User Name)UGFzc3dvcmQA(Password\x00) instead ofUGFzc3dvcmQ=(Password)
Affected clients
Go net/smtp (used by Prometheus Alertmanager, and other Go-based tools) — its loginAuth.Next() does an exact string match on the decoded challenge:
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, fmt.Errorf("unexpected server challenge: %s", fromServer)
}The trailing \x00 causes "User Name\x00" != "Username:" (and would fail even without the null byte since Go expects Username: / Password:), so the client aborts AUTH with *.
Observed behavior:
<< challenge: b'334 VXNlciBOYW1lAA=='
client aborted AUTH with '*'
<< b'501 5.7.0 Auth aborted'
Alertmanager error:
err="*email.loginAuth auth: unexpected server challenge"
Two separate issues
-
Null bytes —
\x00at the end of both challenge strings should not be there. Even clients that acceptUser Nameas a challenge will fail becauseUser Name\x00is not a valid prompt string. -
Challenge text — Go's
net/smtpexpectsUsername:/Password:(RFC 4616 / common convention). While RFC 4954 doesn't strictly define the challenge text for AUTH LOGIN,Username:/Password:is the most widely compatible choice (used by Postfix, Microsoft Exchange, etc.).
Workaround
Subclass SMTP and override the challenge class attributes:
from aiosmtpd.smtp import SMTP
from aiosmtpd.controller import Controller
class FixedSMTP(SMTP):
AuthLoginUsernameChallenge = "Username:"
AuthLoginPasswordChallenge = "Password:"
class FixedController(Controller):
def factory(self):
return FixedSMTP(self.handler, **self.SMTP_kwargs)Suggested fix
In aiosmtpd/smtp.py, remove the null bytes and use the most compatible challenge strings:
AuthLoginUsernameChallenge = "Username:"
AuthLoginPasswordChallenge = "Password:"Environment
- aiosmtpd version: 1.4.6
- Python: 3.11, 3.13
- Failing client: Go net/smtp (Alertmanager v0.27+), curl