Skip to content

AUTH LOGIN challenges contain trailing null byte, breaking Go/Alertmanager clients #566

@ykalyna-ts

Description

@ykalyna-ts

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 of VXNlciBOYW1l (User Name)
  • UGFzc3dvcmQA (Password\x00) instead of UGFzc3dvcmQ= (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

  1. Null bytes\x00 at the end of both challenge strings should not be there. Even clients that accept User Name as a challenge will fail because User Name\x00 is not a valid prompt string.

  2. Challenge text — Go's net/smtp expects Username: / 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions