223 lines
4.9 KiB
Go
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, addr string) error {
|
|
s.mutex.Lock()
|
|
ok := !s.closed
|
|
if ok {
|
|
s.listeners[addr] = 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, addr)
|
|
}
|
|
|
|
// 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, addr)
|
|
}
|
|
|
|
// 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
|
|
}
|