AUTH LOGIN challenges contain trailing null byte, breaking Go/Alertmanager clients
- Linguagem predominante
- Python
- Estrelas
- 373
- Forks
- 105
- Merge médio
- 4min
- PRs com merge (30d)
- 2
Descrição
## Summary
The `AUTH LOGIN` challenge strings in `SMTP` class contain trailing null bytes (`\x00`), which causes strict SMTP clients to abort authentication.
```python
# 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:
```go
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:
```python
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:
```python
AuthLoginUsernameChallenge = "Username:"
AuthLoginPasswordChallenge = "Password:"
```
## Environment
- aiosmtpd version: 1.4.6
- Python: 3.11, 3.13
- Failing client: Go net/smtp (Alertmanager v0.27+), curl
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.