// Package managesieve implements a RFC 5804 ManageSieve server — the // protocol mail clients (Thunderbird's Sieve plugin, etc.) use to upload and // manage server-side filtering scripts. Every uploaded script is validated // with internal/sieve's parser before being stored, so a syntactically // invalid script is rejected at PUTSCRIPT time rather than silently failing // at delivery time. package managesieve import ( "context" "crypto/tls" "fmt" "log/slog" "net" "sync" "time" "gomail/internal/db" ) const idleTimeout = 10 * time.Minute type Server struct { database *db.DB tlsConf *tls.Config hostname string listener net.Listener wg sync.WaitGroup sessionWG sync.WaitGroup } func NewServer(database *db.DB, tlsConf *tls.Config, hostname string) *Server { return &Server{database: database, tlsConf: tlsConf, hostname: hostname} } func (s *Server) ListenAndServe(ctx context.Context, addr string) error { ln, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("listen %s: %w", addr, err) } s.listener = ln slog.Info("ManageSieve listener started", "addr", addr) s.wg.Add(1) go func() { defer s.wg.Done() s.acceptLoop(ctx, ln) }() <-ctx.Done() return ctx.Err() } func (s *Server) acceptLoop(ctx context.Context, ln net.Listener) { for { conn, err := ln.Accept() if err != nil { select { case <-ctx.Done(): return default: slog.Error("ManageSieve accept error", "err", err) return } } s.sessionWG.Add(1) go func() { defer s.sessionWG.Done() newSession(conn, s).run(ctx) }() } } func (s *Server) Shutdown(gracePeriod time.Duration) { if s.listener != nil { s.listener.Close() } s.wg.Wait() done := make(chan struct{}) go func() { s.sessionWG.Wait() close(done) }() select { case <-done: case <-time.After(gracePeriod): slog.Warn("ManageSieve shutdown grace period expired") } }