Neues Paket mail/internal/smtp: SMTP-Server für eingehende Mails, von Grund auf implementiert, analog zu mail/internal/imap und mail/internal/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 wird schrittweise aufgebaut und validiert (503 bei übersprungenen Schritten, 553 bei ungültiger Absender-/Empfängeradresse), Nachrichtengröße wird während DATA laufend gegen eine konfigurierbare Höchstgröße geprüft (552 bei Überschreitung, Sink bekommt die Nachricht nicht). Dot-Stuffing beim Empfang korrekt rückgängig gemacht. Neues Paket mail/internal/mailer: Mailer-Komponente für ausgehende Nachrichten. headerWriter ist die einzige Stelle, an der Header geschrieben werden — jeder Feldwert wird hart gegen CR/LF/Steuerzeichen geprüft, bevor er in die Nachricht geschrieben wird. Behebt den bekannten archivmail-Fehler (Header-Injection durch Stringkonkatenation ohne CRLF-Prüfung, siehe known-issues-archivmail.md #1). Sender.Send überträgt per echtem net/smtp-Client (Standardbibliothek) — keine Zugangsdaten im Code, Zieladresse kommt vom Aufrufer. Alle drei Pflichtprüfungen mit echten Nachweisen durchgeführt: CRLF-/Steuerzeichen-Injection in Betreff und Anzeigenamen schlägt fehl (vier Testfälle), Ende-zu-Ende-Header-Integritätstest über echten SMTP-Dialog (Mailpit/MailHog nicht installierbar auf diesem Rechner — Ersatz durch den in dieser Kachel gebauten echten SMTP-Server, kein Mock, im Prüfprotokoll begründet), Lasttest mit 50 gleichzeitigen Verbindungen ohne Goroutine-/Verbindungsleck. go build/go vet/golangci-lint clean, gesamtes Mail-Modul (~26 Pakete) regressionsfrei getestet.
29 lines
717 B
Go
29 lines
717 B
Go
package smtp
|
|
|
|
// State ist einer der vier SMTP-Sitzungszustände dieser Implementierung
|
|
// (RFC 5321 §3.3), Akzeptanzkriterium 1: Envelope wird schrittweise vor
|
|
// der DATA-Annahme aufgebaut und geprüft.
|
|
type State int
|
|
|
|
const (
|
|
Greeting State = iota // vor HELO/EHLO
|
|
Ready // nach HELO/EHLO, bereit für MAIL FROM
|
|
MailFromSet // nach gültigem MAIL FROM, wartet auf RCPT TO
|
|
RcptToSet // mind. ein gültiges RCPT TO, DATA erlaubt
|
|
)
|
|
|
|
func (s State) String() string {
|
|
switch s {
|
|
case Greeting:
|
|
return "GREETING"
|
|
case Ready:
|
|
return "READY"
|
|
case MailFromSet:
|
|
return "MAIL FROM SET"
|
|
case RcptToSet:
|
|
return "RCPT TO SET"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|