Files
go-imap/imapserver/server.go
2025-12-08 06:42:29 +02:00

223 lines
4.9 KiB
Go

// Package imapserver implements an IMAP server.
package imapserver
import (
"crypto/tls"
"errors"
"fmt"
"io"
"log"
"net"
"sync"
"time"
"github.com/emersion/go-imap/v2"
)
var errClosed = errors.New("imapserver: server closed")
// Logger is a facility to log error messages.
type Logger interface {
Printf(format string, args ...interface{})
}
// Options contains server options.
//
// The only required field is NewSession.
type Options struct {
// NewSession is called when a client connects.
NewSession func(*Conn) (Session, *GreetingData, error)
// Supported capabilities. If nil, only IMAP4rev1 is advertised. This set
// must contain at least IMAP4rev1 or IMAP4rev2.
//
// The following capabilities are part of IMAP4rev2 and need to be
// explicitly enabled by IMAP4rev1-only servers:
//
// - NAMESPACE
// - UIDPLUS
// - ESEARCH
// - LIST-EXTENDED
// - LIST-STATUS
// - MOVE
// - STATUS=SIZE
Caps imap.CapSet
// Logger is a logger to print error messages. If nil, log.Default is used.
Logger Logger
// TLSConfig is a TLS configuration for STARTTLS. If nil, STARTTLS is
// disabled.
TLSConfig *tls.Config
// InsecureAuth allows clients to authenticate without TLS. In this mode,
// the server is susceptible to man-in-the-middle attacks.
InsecureAuth bool
// Raw ingress and egress data will be written to this writer, if any.
// Note, this may include sensitive information such as credentials used
// during authentication.
DebugWriter io.Writer
}
func (options *Options) wrapReadWriter(rw io.ReadWriter) io.ReadWriter {
if options.DebugWriter == nil {
return rw
}
return struct {
io.Reader
io.Writer
}{
Reader: io.TeeReader(rw, options.DebugWriter),
Writer: io.MultiWriter(rw, options.DebugWriter),
}
}
func (options *Options) caps() imap.CapSet {
if options.Caps != nil {
return options.Caps
}
return imap.CapSet{imap.CapIMAP4rev1: {}}
}
// Server is an IMAP server.
type Server struct {
options Options
listenerWaitGroup sync.WaitGroup
mutex sync.Mutex
listeners map[net.Listener]struct{}
conns map[*Conn]struct{}
closed bool
}
// New creates a new server.
func New(options *Options) *Server {
if caps := options.caps(); !caps.Has(imap.CapIMAP4rev2) && !caps.Has(imap.CapIMAP4rev1) {
panic("imapserver: at least IMAP4rev1 must be supported")
}
return &Server{
options: *options,
listeners: make(map[net.Listener]struct{}),
conns: make(map[*Conn]struct{}),
}
}
func (s *Server) logger() Logger {
if s.options.Logger == nil {
return log.Default()
}
return s.options.Logger
}
// Serve accepts incoming connections on the listener ln.
func (s *Server) Serve(ln net.Listener) error {
s.mutex.Lock()
ok := !s.closed
if ok {
s.listeners[ln] = struct{}{}
}
s.mutex.Unlock()
if !ok {
return errClosed
}
defer func() {
s.mutex.Lock()
delete(s.listeners, ln)
s.mutex.Unlock()
}()
s.listenerWaitGroup.Add(1)
defer s.listenerWaitGroup.Done()
var delay time.Duration
for {
conn, err := ln.Accept()
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if delay == 0 {
delay = 5 * time.Millisecond
} else {
delay *= 2
}
if max := 1 * time.Second; delay > max {
delay = max
}
s.logger().Printf("accept error (retrying in %v): %v", delay, err)
time.Sleep(delay)
continue
} else if errors.Is(err, net.ErrClosed) {
return nil
} else if err != nil {
return fmt.Errorf("accept error: %w", err)
}
delay = 0
go newConn(conn, s).serve()
}
}
// ListenAndServe listens on the TCP network address addr and then calls Serve.
//
// If addr is empty, ":143" is used.
func (s *Server) ListenAndServe(addr string) error {
if addr == "" {
addr = ":143"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Serve(ln)
}
// ListenAndServeTLS listens on the TCP network address addr and then calls
// Serve to handle incoming TLS connections.
//
// The TLS configuration set in Options.TLSConfig is used. If addr is empty,
// ":993" is used.
func (s *Server) ListenAndServeTLS(addr string) error {
if addr == "" {
addr = ":993"
}
ln, err := tls.Listen("tcp", addr, s.options.TLSConfig)
if err != nil {
return err
}
return s.Serve(ln)
}
// Close immediately closes all active listeners and connections.
//
// Close returns any error returned from closing the server's underlying
// listeners.
//
// Once Close has been called on a server, it may not be reused; future calls
// to methods such as Serve will return an error.
func (s *Server) Close() error {
var err error
s.mutex.Lock()
ok := !s.closed
if ok {
s.closed = true
for l := range s.listeners {
if closeErr := l.Close(); closeErr != nil && err == nil {
err = closeErr
}
}
}
s.mutex.Unlock()
if !ok {
return errClosed
}
s.listenerWaitGroup.Wait()
s.mutex.Lock()
for c := range s.conns {
c.mutex.Lock()
c.conn.Close()
c.mutex.Unlock()
}
s.mutex.Unlock()
return err
}