IMAP-Server-Grundgerüst: TCP-Listener, Command-Parser, Session- Zustandsmaschine (Not Authenticated/Authenticated/Selected), Grundbefehle CAPABILITY/LOGIN/SELECT/FETCH/LOGOUT. - parser.go: Tag+Kommando+Argumente (Atome, zitierte Zeichenketten), keine IMAP-Literalsyntax (kleinste Lösung). - response.go: sanitizeResponseText entfernt eingebettete CR/LF vor jeder Antwortzeile — bekannten archivmail-Fehler (Header-/Zeilen-Injection durch Stringkonkatenation ohne CRLF-Prüfung) strukturell vermieden. - session.go/commands.go: strikte Zustandsprüfung je Kommando, verbotene Übergänge und fehlerhafte Zeilen liefern BAD/NO statt Verbindungsabbruch. maxCommandLineBytes begrenzt Pufferwachstum defensiv. - server.go: TCP-Accept-Schleife, eine Goroutine je Verbindung. - Authenticator/MailboxStore als schmale Schnittstellen — echte Benutzerverwaltungs-/Postfach-Anbindung ist Sache von IMP-01 u. a. Prüfungen (alle real durchgeführt, siehe mail/docs/ING-01-PRUEFPROTOKOLL.md): 1. Manuelle Session mit Pythons imaplib gegen den echten laufenden Server: alle Grundbefehle real beantwortet, ungültiges SELECT liefert real NO ohne Verbindungsabbruch. 2. TestSession_StateTransitionsAndForbiddenTransitions: alle drei Zustandsübergänge und deren verbotene Übergänge real über TCP geprüft. 3. TestServer_50ParallelSessionsNoLeak: 50 reale parallele Sessions, 0 Fehler. Kein Umbau: alle bestehenden Pakete unverändert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
48 lines
1.2 KiB
Go
48 lines
1.2 KiB
Go
package imap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
)
|
|
|
|
// Server nimmt IMAP-Verbindungen an und bedient jede in einer eigenen
|
|
// Goroutine (Akzeptanzkriterium 1). STARTTLS/TLS-Absicherung ist
|
|
// ausdrücklich Sache von ING-06, nicht dieser Kachel — Server hört per
|
|
// Klartext-TCP, wie im Ticket vorgesehen ("Bereite höchstens die
|
|
// Schnittstelle dafür vor").
|
|
type Server struct {
|
|
auth Authenticator
|
|
store MailboxStore
|
|
}
|
|
|
|
func NewServer(auth Authenticator, store MailboxStore) *Server {
|
|
return &Server{auth: auth, store: store}
|
|
}
|
|
|
|
// 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)
|
|
go session.Serve(ctx)
|
|
}
|
|
}
|