package smtp import ( "bytes" "context" "strings" ) func (s *Session) handleHelo(verb, arg string) bool { if strings.TrimSpace(arg) == "" { return s.reply(501, verb+" requires a domain/address") == nil } // HELO/EHLO setzt den Envelope zurück, falls bereits einer im // Aufbau war (RFC 5321 §4.1.1.1). s.from = "" s.to = nil s.state = Ready if verb == "EHLO" { return s.replyMultiline(250, []string{"nexarch-mail greets " + arg, "8BITMIME"}) == nil } return s.reply(250, "nexarch-mail greets "+arg) == nil } // handleMailFrom ist Teil des Envelope-Aufbaus (Akzeptanzkriterium 1): // die Absenderadresse wird vor der Annahme validiert. func (s *Session) handleMailFrom(arg string) bool { if s.state == Greeting { return s.reply(503, "send HELO/EHLO first") == nil } addr, err := parseMailAddressArg(arg, "FROM") if err != nil { return s.reply(501, "invalid MAIL FROM syntax") == nil } if err := validateAddress(addr); err != nil { // Akzeptanzkriterium 3: ungültige Absenderdaten -> saubere // SMTP-Fehlermeldung statt Absturz oder Verbindungsabbruch. return s.reply(553, "invalid sender address") == nil } s.from = addr s.to = nil s.state = MailFromSet return s.reply(250, "OK") == nil } // handleRcptTo ist Teil des Envelope-Aufbaus (Akzeptanzkriterium 1): // jede Empfängeradresse wird vor der Annahme validiert; mehrere RCPT TO // sind erlaubt. func (s *Session) handleRcptTo(arg string) bool { if s.state != MailFromSet && s.state != RcptToSet { return s.reply(503, "send MAIL FROM first") == nil } addr, err := parseMailAddressArg(arg, "TO") if err != nil { return s.reply(501, "invalid RCPT TO syntax") == nil } if err := validateAddress(addr); err != nil { // Akzeptanzkriterium 3: ungültige Empfängerdaten -> saubere // SMTP-Fehlermeldung statt Absturz oder Verbindungsabbruch. return s.reply(553, "invalid recipient address") == nil } s.to = append(s.to, addr) s.state = RcptToSet return s.reply(250, "OK") == nil } func (s *Session) handleRset() bool { s.from = "" s.to = nil if s.state != Greeting { s.state = Ready } return s.reply(250, "OK") == nil } // handleData verlangt einen vollständig aufgebauten und validierten // Envelope (Akzeptanzkriterium 1: Envelope UND Nachrichtengröße werden // vor der Annahme geprüft) und liest die dot-gestuffte Nachricht bis zur // Abschlusszeile ".". func (s *Session) handleData(ctx context.Context) bool { if s.state != RcptToSet { return s.reply(503, "send MAIL FROM/RCPT TO first") == nil } if err := s.reply(354, "Start mail input; end with ."); err != nil { return false } var buf bytes.Buffer for { line, err := s.readLine() if err != nil { return false } if line == "." { break } // Byte-Stuffing rückgängig machen (RFC 5321 §4.5.2): eine Zeile, // die mit "." beginnt, verliert genau diesen ersten Punkt. line = strings.TrimPrefix(line, ".") buf.WriteString(line) buf.WriteString("\r\n") if int64(buf.Len()) > s.maxMessageBytes { // Akzeptanzkriterium 1: Nachrichtengröße wird VOR der // endgültigen Annahme geprüft — sauberer Fehlercode statt // unbegrenztem Pufferwachstum. _ = s.drainUntilDot() s.from = "" s.to = nil s.state = Ready return s.reply(552, "message size exceeds fixed maximum message size") == nil } } envelope := Envelope{From: s.from, To: s.to} raw := buf.Bytes() s.from = "" s.to = nil s.state = Ready if s.sink != nil { if err := s.sink.Accept(ctx, envelope, raw); err != nil { return s.reply(451, "unable to accept message, try again later") == nil } } return s.reply(250, "OK: message accepted") == nil }