Files
crockery/vendor/github.com/emersion/go-imap/server/server.go
2018-03-05 12:19:04 +00:00

427 lines
10 KiB
Go

// Package server provides an IMAP server.
package server
import (
"crypto/tls"
"errors"
"io"
"log"
"net"
"os"
"sync"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/backend"
"github.com/emersion/go-imap/responses"
"github.com/emersion/go-sasl"
)
// The minimum autologout duration defined in RFC 3501 section 5.4.
const MinAutoLogout = 30 * time.Minute
// A command handler.
type Handler interface {
imap.Parser
// Handle this command for a given connection.
//
// By default, after this function has returned a status response is sent. To
// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
Handle(conn Conn) error
}
// A connection upgrader. If a Handler is also an Upgrader, the connection will
// be upgraded after the Handler succeeds.
//
// This should only be used by libraries implementing an IMAP extension (e.g.
// COMPRESS).
type Upgrader interface {
// Upgrade the connection. This method should call conn.Upgrade().
Upgrade(conn Conn) error
}
// A function that creates handlers.
type HandlerFactory func() Handler
// A function that creates SASL servers.
type SaslServerFactory func(conn Conn) sasl.Server
// An IMAP extension.
type Extension interface {
// Get capabilities provided by this extension for a given connection.
Capabilities(c Conn) []string
// Get the command handler factory for the provided command name.
Command(name string) HandlerFactory
}
// An extension that provides additional features to each connection.
type ConnExtension interface {
Extension
// This function will be called when a client connects to the server. It can
// be used to add new features to the default Conn interface by implementing
// new methods.
NewConn(c Conn) Conn
}
type errStatusResp struct {
resp *imap.StatusResp
}
func (err *errStatusResp) Error() string {
return ""
}
// ErrStatusResp can be returned by a Handler to replace the default status
// response. The response tag must be empty.
//
// To disable the default status response, use ErrNoStatusResp instead.
func ErrStatusResp(res *imap.StatusResp) error {
return &errStatusResp{res}
}
// ErrNoStatusResp can be returned by a Handler to prevent the default status
// response from being sent.
func ErrNoStatusResp() error {
return &errStatusResp{nil}
}
// An IMAP server.
type Server struct {
locker sync.Mutex
listeners map[net.Listener]struct{}
conns map[Conn]struct{}
commands map[string]HandlerFactory
auths map[string]SaslServerFactory
extensions []Extension
// TCP address to listen on.
Addr string
// This server's TLS configuration.
TLSConfig *tls.Config
// This server's backend.
Backend backend.Backend
// Backend updates that will be sent to connected clients.
Updates <-chan interface{}
// Automatically logout clients after a duration. To do not logout users
// automatically, set this to zero. The duration MUST be at least
// MinAutoLogout (as stated in RFC 3501 section 5.4).
AutoLogout time.Duration
// Allow authentication over unencrypted connections.
AllowInsecureAuth bool
// An io.Writer to which all network activity will be mirrored.
Debug io.Writer
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog imap.Logger
// The maximum literal size, in bytes. Literals exceeding this size will be
// rejected. A value of zero disables the limit (this is the default).
MaxLiteralSize uint32
}
// Create a new IMAP server from an existing listener.
func New(bkd backend.Backend) *Server {
s := &Server{
listeners: make(map[net.Listener]struct{}),
conns: make(map[Conn]struct{}),
Backend: bkd,
ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags),
}
s.auths = map[string]SaslServerFactory{
sasl.Plain: func(conn Conn) sasl.Server {
return sasl.NewPlainServer(func(identity, username, password string) error {
if identity != "" && identity != username {
return errors.New("Identities not supported")
}
user, err := bkd.Login(username, password)
if err != nil {
return err
}
ctx := conn.Context()
ctx.State = imap.AuthenticatedState
ctx.User = user
return nil
})
},
}
s.commands = map[string]HandlerFactory{
imap.Noop: func() Handler { return &Noop{} },
imap.Capability: func() Handler { return &Capability{} },
imap.Logout: func() Handler { return &Logout{} },
imap.StartTLS: func() Handler { return &StartTLS{} },
imap.Login: func() Handler { return &Login{} },
imap.Authenticate: func() Handler { return &Authenticate{} },
imap.Select: func() Handler { return &Select{} },
imap.Examine: func() Handler {
hdlr := &Select{}
hdlr.ReadOnly = true
return hdlr
},
imap.Create: func() Handler { return &Create{} },
imap.Delete: func() Handler { return &Delete{} },
imap.Rename: func() Handler { return &Rename{} },
imap.Subscribe: func() Handler { return &Subscribe{} },
imap.Unsubscribe: func() Handler { return &Unsubscribe{} },
imap.List: func() Handler { return &List{} },
imap.Lsub: func() Handler {
hdlr := &List{}
hdlr.Subscribed = true
return hdlr
},
imap.Status: func() Handler { return &Status{} },
imap.Append: func() Handler { return &Append{} },
imap.Check: func() Handler { return &Check{} },
imap.Close: func() Handler { return &Close{} },
imap.Expunge: func() Handler { return &Expunge{} },
imap.Search: func() Handler { return &Search{} },
imap.Fetch: func() Handler { return &Fetch{} },
imap.Store: func() Handler { return &Store{} },
imap.Copy: func() Handler { return &Copy{} },
imap.Uid: func() Handler { return &Uid{} },
}
return s
}
// Serve accepts incoming connections on the Listener l.
func (s *Server) Serve(l net.Listener) error {
s.locker.Lock()
s.listeners[l] = struct{}{}
s.locker.Unlock()
defer func() {
s.locker.Lock()
defer s.locker.Unlock()
l.Close()
delete(s.listeners, l)
}()
go s.listenUpdates()
for {
c, err := l.Accept()
if err != nil {
return err
}
var conn Conn = newConn(s, c)
for _, ext := range s.extensions {
if ext, ok := ext.(ConnExtension); ok {
conn = ext.NewConn(conn)
}
}
go s.serveConn(conn)
}
}
// ListenAndServe listens on the TCP network address s.Addr and then calls Serve
// to handle requests on incoming connections.
//
// If s.Addr is blank, ":imap" is used.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if addr == "" {
addr = ":imap"
}
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return s.Serve(l)
}
// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
// Serve to handle requests on incoming TLS connections.
//
// If s.Addr is blank, ":imaps" is used.
func (s *Server) ListenAndServeTLS() error {
addr := s.Addr
if addr == "" {
addr = ":imaps"
}
l, err := tls.Listen("tcp", addr, s.TLSConfig)
if err != nil {
return err
}
return s.Serve(l)
}
func (s *Server) serveConn(conn Conn) error {
s.locker.Lock()
s.conns[conn] = struct{}{}
s.locker.Unlock()
defer func() {
s.locker.Lock()
defer s.locker.Unlock()
conn.Close()
delete(s.conns, conn)
}()
return conn.serve()
}
// Get a command handler factory for the provided command name.
func (s *Server) Command(name string) HandlerFactory {
// Extensions can override builtin commands
for _, ext := range s.extensions {
if h := ext.Command(name); h != nil {
return h
}
}
return s.commands[name]
}
func (s *Server) listenUpdates() (err error) {
updater, ok := s.Backend.(backend.Updater)
if !ok {
return
}
s.Updates = updater.Updates()
for {
item := <-s.Updates
var (
update *backend.Update
res imap.WriterTo
)
switch item := item.(type) {
case *backend.StatusUpdate:
update = &item.Update
res = item.StatusResp
case *backend.MailboxUpdate:
update = &item.Update
res = &responses.Select{Mailbox: item.MailboxStatus}
case *backend.MessageUpdate:
update = &item.Update
ch := make(chan *imap.Message, 1)
ch <- item.Message
close(ch)
res = &responses.Fetch{Messages: ch}
case *backend.ExpungeUpdate:
update = &item.Update
ch := make(chan uint32, 1)
ch <- item.SeqNum
close(ch)
res = &responses.Expunge{SeqNums: ch}
default:
s.ErrorLog.Printf("unhandled update: %T\n", item)
}
if update == nil || res == nil {
continue
}
sends := make(chan struct{})
wait := 0
s.locker.Lock()
for conn := range s.conns {
ctx := conn.Context()
if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) {
continue
}
if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) {
continue
}
if *conn.silent() {
// If silent is set, do not send message updates
if _, ok := res.(*responses.Fetch); ok {
continue
}
}
conn := conn // Copy conn to a local variable
go func() {
done := make(chan struct{})
conn.Context().Responses <- &response{
response: res,
done: done,
}
<-done
sends <- struct{}{}
}()
wait++
}
s.locker.Unlock()
if wait > 0 {
go func() {
for done := 0; done < wait; done++ {
<-sends
}
close(sends)
backend.DoneUpdate(update)
}()
} else {
backend.DoneUpdate(update)
}
}
}
// ForEachConn iterates through all opened connections.
func (s *Server) ForEachConn(f func(Conn)) {
s.locker.Lock()
defer s.locker.Unlock()
for conn := range s.conns {
f(conn)
}
}
// Stops listening and closes all current connections.
func (s *Server) Close() error {
s.locker.Lock()
defer s.locker.Unlock()
for l := range s.listeners {
l.Close()
}
for conn := range s.conns {
conn.Close()
}
return nil
}
// Enable some IMAP extensions on this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) Enable(extensions ...Extension) {
s.extensions = append(s.extensions, extensions...)
}
// Enable an authentication mechanism on this server.
//
// This function should not be called directly, it must only be used by
// libraries implementing extensions of the IMAP protocol.
func (s *Server) EnableAuth(name string, f SaslServerFactory) {
s.auths[name] = f
}