aio-libs / aio-libs/aiosmtpd

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

Aberta
#566 0 comentários 0 reações 0 responsáveis Ver no GitHub
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

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.