feat(mail): ING-09 Rate-Limiting auf Protokollebene für IMAP/POP3/SMTP

Neues Paket mail/internal/ratelimit: Token-Bucket je (Mandant,
Quelle)-Schlüssel. ConfigProvider/StaticConfig liefern Burst/Nachfüllrate
je Mandant mit Fallback auf eine Default-Konfiguration
(Akzeptanzkriterium 2/3: begrenzt, konfigurierbar, je Mandant getrennt).
Limiter.Allow liefert bei Ablehnung eine konkrete positive Wartezeit
statt nur false — Grundlage für die geforderte definierte Ablehnung MIT
Wartezeit-Hinweis statt Verbindungsabbruch (Akzeptanzkriterium 3).

IMAP LOGIN und POP3 PASS begrenzen Anmeldeversuche pro (Mandant,
Quell-IP). SMTP MAIL FROM begrenzt die Annahmerate pro (Mandant,
Absenderadresse+Quell-IP). Bei Überschreitung: POP3 -ERR, IMAP NO, SMTP
451 (temporärer Fehlercode) mit Wartezeit in Sekunden, Verbindung
bleibt in allen drei Fällen offen und weiter nutzbar. loginLimiter/
acceptLimiter sind optional (nil = deaktiviert, Rückwärtskompatibilität
zu ING-01..ING-08) — neue Konstruktoren
NewServerWithGuardTLSLoggerAndRateLimit (IMAP/POP3) und
NewServerWithMaxMessageBytesTLSLoggerAndRateLimit (SMTP). Jeder Server
bekommt eine tenantID, konsistent mit dem in ING-10 etablierten Muster
"ein Server je Mandant".

Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt, jeweils
in IMAP, POP3 und SMTP einzeln: 20 reale Versuche bei Burst=5 -> exakt
5 akzeptiert, 15 mit Wartezeit-Hinweis abgelehnt, keine Verbindung
getrennt; legitime Nutzung unterhalb der Schwelle unbeeinträchtigt;
zwei Mandanten mit unterschiedlichem Burst über denselben Limiter
unabhängig und exakt wirksam (2 vs. 8 akzeptierte Versuche).

go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~31 Pakete)
regressionsfrei getestet — insbesondere QA-07-Lasttests bleiben grün,
da Rate-Limiting standardmäßig deaktiviert ist.
This commit is contained in:
sysops
2026-09-01 12:22:21 +02:00
parent 060b73566b
commit af1709a2bb
15 changed files with 842 additions and 36 deletions
+9
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"context"
"fmt"
"strings"
"gitea.perlbach24.de/scripte/nexarch/mail/internal/tlscert"
@@ -77,6 +78,14 @@ func (s *Session) handleMailFrom(arg string) bool {
// SMTP-Fehlermeldung statt Absturz oder Verbindungsabbruch.
return s.reply(553, "invalid sender address") == nil
}
if s.acceptLimiter != nil {
if ok, retryAfter := s.acceptLimiter.Allow(s.tenantID, addr+"|"+s.sourceAddr()); !ok {
// Akzeptanzkriterium 1/3 (ING-09): definierte, temporäre
// Ablehnung (4xx = "try again later") MIT Wartezeit-Hinweis
// statt Verbindungsabbruch ohne Erklärung.
return s.reply(451, fmt.Sprintf("rate limit exceeded for sender, retry in %.1fs", retryAfter.Seconds())) == nil
}
}
s.from = addr
s.to = nil
s.state = MailFromSet