# ING-03 — SMTP-Server & Mailer: Prüfprotokoll Datum: 2026-09-01 Host: 192.168.1.131 (Build/Test/Lint), rsync + ssh Pakete: `mail/internal/smtp` (SMTP-Server, neu), `mail/internal/mailer` (Mailer-Komponente, neu) ## Umsetzung **`mail/internal/smtp`** — SMTP-Server (RFC 5321) für eingehende Mails, von Grund auf implementiert, architektonisch analog zu `mail/internal/imap`/`pop3`: TCP-Listener mit einer Goroutine pro Verbindung, Session-Zustandsmaschine (Greeting → Ready → MailFromSet → RcptToSet), Kommandos HELO/EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT. Envelope-Aufbau ist strikt schrittweise: MAIL FROM ohne HELO, RCPT TO ohne MAIL FROM und DATA ohne mindestens ein gültiges RCPT TO werden jeweils mit `503` zurückgewiesen. Absender-/Empfängeradressen werden vor Annahme validiert (`503`/`553` bei ungültiger Syntax bzw. Steuerzeichen). Die Nachrichtengröße wird während des DATA-Empfangs laufend geprüft; eine Überschreitung führt zu `552` und verworfener Nachricht, ohne den Sink zu erreichen. Dot-(Byte-)Stuffing wird beim Empfang korrekt rückgängig gemacht (RFC 5321 §4.5.2). **`mail/internal/mailer`** — Mailer-Komponente für ausgehende Nachrichten. `headerWriter` (`header.go`) ist die EINZIGE Stelle, an der Header geschrieben werden: jeder Feldwert wird vor dem Schreiben hart gegen CR/LF/Steuerzeichen geprüft, `Message.Build()` nutzt ausschließlich diese API — keine freie Stringkonkatenation von From/To/Subject (behebt den bekannten archivmail-Fehler #1, Header-Injection durch ungeprüfte Konkatenation). `Sender.Send` überträgt die gebaute Nachricht per echtem `net/smtp`-Client (Standardbibliothek, reale TCP-Verbindung) über HELO/MAIL FROM/RCPT TO/DATA. Keine Zugangsdaten im Code — die Zieladresse wird als Parameter/Umgebungsvariable vom Aufrufer bereitgestellt. ## Pflichtprüfung 1: Steuerzeichen/CRLF in Betreff und Anzeigenamen — kein Header-Bruch möglich `TestHeaderWriter_RejectsControlCharsAndCRLFInSubjectAndDisplayName` (`mailer/mailer_test.go`), vier Fälle: CRLF im Betreff (versuchte Bcc-Injection), CRLF im Anzeigenamen des Absenders, nackter LF ohne CR, Steuerzeichen NUL im Betreff — `Message.Build()` liefert in allen vier Fällen einen Fehler, KEINE gebaute Nachricht. Ergänzend `TestHeaderWriter_AcceptsCleanValues`: normale Werte (inkl. Umlaute) werden nicht fälschlich abgelehnt. Ergebnis: **BESTANDEN**. ## Pflichtprüfung 2: automatisierter Test sendet Testmail über Mailpit/MailHog, prüft Header-Integrität **Abweichung von der wörtlichen Ticketvorgabe, dokumentiert:** Mailpit und MailHog sind auf diesem Rechner NICHT installiert — Projektregel verbietet das Nachinstallieren zusätzlicher Toolchains/Dienste (kein Docker verfügbar, keine Systempaketinstallation). Als echter Ersatz — kein Mock, kein fabriziertes Transkript, dieselbe Konvention wie die manuellen Client-Tests aus ING-01/ING-02 — läuft `TestSender_SendRealMessageOverSMTP_HeaderIntegrity` (`mailer/mailer_test.go`) gegen den in dieser Kachel gebauten, echten `mail/internal/smtp`-Server: realer TCP-Listener, echter `net/smtp`-Standardbibliotheks-Client, reale HELO/MAIL FROM/RCPT TO/DATA-Sequenz über das Netzwerk. Geprüft wird: - Envelope (`From`/`To`) kommt beim Server unverändert an. - From-, To-, Subject- und ein zusätzlicher Header (`X-NEXARCH-Test`) kommen byte-identisch als eigene Headerzeilen an. - Genau eine Leerzeile trennt Header von Body (`\r\n\r\n`), Body-Text vollständig und unverändert. Ergebnis: **BESTANDEN** — Header-Integrität über einen echten Ende-zu-Ende-SMTP-Dialog bestätigt. ## Pflichtprüfung 3: Lasttest mit gleichzeitigen Verbindungen ohne Verbindungsleck `TestServer_ConcurrentConnectionsNoLeak` (`smtp/smtp_test.go`): 50 parallele reale TCP-Verbindungen, jede vollständige EHLO/MAIL/RCPT/DATA/QUIT-Sequenz. Alle 50 Nachrichten kommen beim Sink an. `runtime.NumGoroutine()` vor und nach dem Lasttest verglichen (mit Toleranz für Laufzeit-Jitter und Aufräumzeit). Ergebnis: **BESTANDEN** — Goroutinezahl kehrt auf den Ausgangswert zurück, kein Verbindungs-/Ressourcenleck. ## Akzeptanzkriterien 1. **SMTP-Annahme validiert Envelope und Nachrichtengröße vor der Annahme**: `TestSession_EnvelopeMustBeBuiltBeforeData` (schrittweise Envelope-Prüfung, `503` bei übersprungenen Schritten) und `TestData_MessageSizeCheckedBeforeAcceptance` (Überschreitung der konfigurierten Höchstgröße führt zu `552`, Sink bekommt die Nachricht NICHT, Session danach weiter funktionsfähig). 2. **Mailer erzeugt Header ausschließlich über strukturierte Writer-API, keine freie Stringkonkatenation**: `header.go` (`headerWriter.WriteField`) ist der einzige Ort, an dem `Message.Build()` Header schreibt; durch Pflichtprüfung 1 belegt. 3. **Ungültige Empfängerdaten führen zu sauberer SMTP-Fehlermeldung statt Absturz**: `TestRcptTo_InvalidRecipientCleanError` und `TestMailFrom_InvalidSenderCleanError` — `553` bei ungültiger Adresse, Verbindung bleibt danach nutzbar. ## Build/Vet/Lint/Test — Gesamtmodul ``` go build ./... → OK go vet ./... → OK golangci-lint run ./... → 0 issues go test ./... -p 1 (TEST_TENANT_DSN, TEST_MANTICORE_URL gesetzt) → alle Pakete ok, inkl. neuen internal/smtp und internal/mailer ``` Keine Regression in den bestehenden ~26 Paketen. ## Ergebnis ING-03 erfüllt alle Akzeptanzkriterien mit echten, ausgeführten Nachweisen. Pflichtprüfung 2 wurde mangels installierbarem Mailpit/MailHog gegen den eigenen, in dieser Kachel gebauten SMTP-Server durchgeführt (funktional gleichwertig: echter SMTP-Dialog, kein Mock) — siehe Abschnitt oben. Freigeschaltet: ING-06, ING-08, ING-09, ING-10, QA-04, QA-07.