Neues Paket mail/internal/protolog (log/slog): SessionLogger loggt strukturierte Ereignisse einer Verbindung mit fester correlation_id und protocol über die gesamte Verbindungsdauer (Akzeptanzkriterium 1) — ein Logger mit logger==nil ist sicher benutzbar und loggt nichts (Rückwärtskompatibilität zu ING-01..ING-07, Logging ist opt-in wie TLS und Guard-Konfiguration). RedactCommandLine ersetzt bei sensiblen Kommandos (PASS, LOGIN, AUTH) alle Argumente vollständig durch [REDACTED] statt einzeln zu parsen (Akzeptanzkriterium 2). Reconstruct liest zeilenweise JSON-Logs und liefert ausschließlich die Einträge einer Korrelations-ID in Reihenfolge — das geforderte Diagnosewerkzeug (Akzeptanzkriterium 3). Alle drei Sessions loggen jetzt session_start/command (je empfangener Zeile, redigiert)/session_end. Nachrichteninhalte werden strukturell nie geloggt: SMTP-DATA-Body-Zeilen laufen durch eine eigene Leseschleife, die nicht durch den Kommando-Logpfad der Hauptschleife kommt: nur das Kommando DATA selbst erscheint im Log. Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt, jeweils in IMAP, POP3 und SMTP einzeln: Redaktion gegen den echten laufenden Server bestätigt (Klartextpasswort bzw. absichtlich eingebettetes Geheimnis im SMTP-Body erscheint nie im Log), zwei gemischte reale Sessions über dieselbe Korrelations-ID lückenlos rekonstruiert, Lasttest mit 100 Sessions mit/ohne Logging ohne relevante Durchsatzeinbuße. go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~29 Pakete) regressionsfrei getestet.
77 lines
2.6 KiB
Go
77 lines
2.6 KiB
Go
package imap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/mail/internal/protoguard"
|
|
)
|
|
|
|
// Server nimmt IMAP-Verbindungen an und bedient jede in einer eigenen
|
|
// Goroutine (Akzeptanzkriterium 1). tlsConfig ist optional (ING-06):
|
|
// nil bedeutet Klartextbetrieb wie vor ING-06 (z. B. hinter einem
|
|
// separaten TLS-Terminator oder in Tests); ist tlsConfig gesetzt,
|
|
// verlangt LOGIN zwingend STARTTLS oder eine bereits per implizitem
|
|
// TLS-Listener verschlüsselte Verbindung (Akzeptanzkriterium 1).
|
|
type Server struct {
|
|
auth Authenticator
|
|
store MailboxStore
|
|
guardCfg protoguard.Config
|
|
tlsConfig *tls.Config
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewServer(auth Authenticator, store MailboxStore) *Server {
|
|
return NewServerWithGuardConfig(auth, store, protoguard.DefaultConfig())
|
|
}
|
|
|
|
// NewServerWithGuardConfig erlaubt abweichende Phase-Timeouts und
|
|
// Backoff-Parameter (ING-07), z. B. für Tests oder gehärtete
|
|
// Betriebsumgebungen.
|
|
func NewServerWithGuardConfig(auth Authenticator, store MailboxStore, guardCfg protoguard.Config) *Server {
|
|
return &Server{auth: auth, store: store, guardCfg: guardCfg}
|
|
}
|
|
|
|
// NewServerWithGuardAndTLSConfig erlaubt zusätzlich, STARTTLS/TLS zu
|
|
// aktivieren (ING-06). tlsConfig sollte über tlscert.Store.Config()
|
|
// erzeugt werden.
|
|
func NewServerWithGuardAndTLSConfig(auth Authenticator, store MailboxStore, guardCfg protoguard.Config, tlsConfig *tls.Config) *Server {
|
|
return &Server{auth: auth, store: store, guardCfg: guardCfg, tlsConfig: tlsConfig}
|
|
}
|
|
|
|
// NewServerWithGuardTLSAndLogger erlaubt zusätzlich strukturiertes
|
|
// Protokoll-Logging (ING-08). logger darf nil sein (Logging dann
|
|
// deaktiviert, Rückwärtskompatibilität zu ING-01..ING-07).
|
|
func NewServerWithGuardTLSAndLogger(auth Authenticator, store MailboxStore, guardCfg protoguard.Config, tlsConfig *tls.Config, logger *slog.Logger) *Server {
|
|
return &Server{auth: auth, store: store, guardCfg: guardCfg, tlsConfig: tlsConfig, logger: logger}
|
|
}
|
|
|
|
// Serve nimmt Verbindungen auf listener an, bis ctx beendet wird oder
|
|
// Accept endgültig fehlschlägt. Blockiert den Aufrufer.
|
|
func (srv *Server) Serve(ctx context.Context, listener net.Listener) error {
|
|
go func() {
|
|
<-ctx.Done()
|
|
_ = listener.Close()
|
|
}()
|
|
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
return nil // beabsichtigtes Herunterfahren
|
|
}
|
|
var netErr net.Error
|
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
|
continue
|
|
}
|
|
return fmt.Errorf("imap: verbindung annehmen: %w", err)
|
|
}
|
|
session := newSession(conn, srv.auth, srv.store, srv.guardCfg, srv.tlsConfig, srv.logger)
|
|
go session.Serve(ctx)
|
|
}
|
|
}
|