// Package imap provides an IMAP4rev2 server backed by the encrypted SQLite store. package imap import ( "context" "crypto/tls" "fmt" "log" "net" "time" "github.com/emersion/go-imap/v2" "github.com/emersion/go-imap/v2/imapserver" "ghb.freebede.com/nahakubuilder/mailgosend/internal/auth" "ghb.freebede.com/nahakubuilder/mailgosend/internal/config" appCrypto "ghb.freebede.com/nahakubuilder/mailgosend/internal/crypto" "ghb.freebede.com/nahakubuilder/mailgosend/internal/db" ) // Deps groups dependencies needed by the IMAP server. type Deps struct { DB *db.DB Crypt *appCrypto.Crypto Brute *auth.BruteGuard Cfg *config.Config } // NewServer creates an IMAP4rev2 server for port 143 (STARTTLS). func NewServer(d *Deps, tlsCfg *tls.Config) *imapserver.Server { return imapserver.New(&imapserver.Options{ NewSession: func(c *imapserver.Conn) (imapserver.Session, *imapserver.GreetingData, error) { clientIP := connRemoteIP(c) if d.Brute != nil && d.Cfg.BruteMaxTries > 0 { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) if banned, err := d.Brute.IsBanned(ctx, clientIP); err == nil && banned { cancel() return nil, nil, fmt.Errorf("connection refused") } cancel() } return &IMAPSession{deps: d, clientIP: clientIP}, &imapserver.GreetingData{}, nil }, Caps: imap.CapSet{ imap.CapIMAP4rev2: {}, imap.CapIMAP4rev1: {}, }, TLSConfig: tlsCfg, InsecureAuth: tlsCfg == nil, }) } // ListenAndServe listens on addr (port 143 / STARTTLS). func ListenAndServe(s *imapserver.Server, addr, name string) error { ln, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("%s listen %s: %w", name, addr, err) } log.Printf("[%s] listening on %s", name, addr) return s.Serve(ln) } // ListenAndServeTLS listens with implicit TLS (port 993 / IMAPS). func ListenAndServeTLS(s *imapserver.Server, addr, name string) error { log.Printf("[%s] listening on %s (TLS)", name, addr) return s.ListenAndServeTLS(addr) } // connRemoteIP returns the client IP from an imapserver.Conn. func connRemoteIP(c *imapserver.Conn) string { if c == nil { return "" } nc := c.NetConn() if nc == nil { return "" } host, _, _ := net.SplitHostPort(nc.RemoteAddr().String()) return host }