upgrade deps; rewrite smtp session

This commit is contained in:
Aine
2024-02-19 22:55:14 +02:00
parent 10213cc7d7
commit a01720da00
277 changed files with 106832 additions and 7641 deletions

View File

@@ -1,6 +1,7 @@
package smtp
import (
"context"
"crypto/tls"
"errors"
"io"
@@ -13,7 +14,9 @@ import (
"github.com/emersion/go-sasl"
)
var errTCPAndLMTP = errors.New("smtp: cannot start LMTP server listening on a TCP socket")
var (
ErrServerClosed = errors.New("smtp: server already closed")
)
// A function that creates SASL servers.
type SaslServerFactory func(conn *Conn) sasl.Server
@@ -26,20 +29,20 @@ type Logger interface {
// A SMTP server.
type Server struct {
// The type of network, "tcp" or "unix".
Network string
// TCP or Unix address to listen on.
Addr string
// The server TLS configuration.
TLSConfig *tls.Config
// Enable LMTP mode, as defined in RFC 2033. LMTP mode cannot be used with a
// TCP listener.
// Enable LMTP mode, as defined in RFC 2033.
LMTP bool
Domain string
MaxRecipients int
MaxMessageBytes int
MaxMessageBytes int64
MaxLineLength int
AllowInsecureAuth bool
Strict bool
Debug io.Writer
ErrorLog Logger
ReadTimeout time.Duration
@@ -57,6 +60,10 @@ type Server struct {
// Should be used only if backend supports it.
EnableBINARYMIME bool
// Advertise DSN (RFC 3461) capability.
// Should be used only if backend supports it.
EnableDSN bool
// If set, the AUTH command will not be advertised and authentication
// attempts will be rejected. This setting overrides AllowInsecureAuth.
AuthDisabled bool
@@ -64,6 +71,8 @@ type Server struct {
// The server backend.
Backend Backend
wg sync.WaitGroup
caps []string
auths map[string]SaslServerFactory
done chan struct{}
@@ -87,17 +96,15 @@ func NewServer(be Backend) *Server {
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")
return errors.New("identities not supported")
}
state := conn.State()
session, err := be.Login(&state, username, password)
if err != nil {
return err
sess := conn.Session()
if sess == nil {
panic("No session when AUTH is called")
}
conn.SetSession(session)
return nil
return sess.AuthPlain(username, password)
})
},
},
@@ -111,6 +118,8 @@ func (s *Server) Serve(l net.Listener) error {
s.listeners = append(s.listeners, l)
s.locker.Unlock()
var tempDelay time.Duration // how long to sleep on accept failure
for {
c, err := l.Accept()
if err != nil {
@@ -119,11 +128,32 @@ func (s *Server) Serve(l net.Listener) error {
// we called Close()
return nil
default:
return err
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
s.ErrorLog.Printf("accept error: %s; retrying in %s", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
go s.handleConn(newConn(c, s))
s.wg.Add(1)
go func() {
defer s.wg.Done()
err := s.handleConn(newConn(c, s))
if err != nil {
s.ErrorLog.Printf("handler error: %s", err)
}
}()
}
}
@@ -140,10 +170,22 @@ func (s *Server) handleConn(c *Conn) error {
s.locker.Unlock()
}()
if tlsConn, ok := c.conn.(*tls.Conn); ok {
if d := s.ReadTimeout; d != 0 {
c.conn.SetReadDeadline(time.Now().Add(d))
}
if d := s.WriteTimeout; d != 0 {
c.conn.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.Handshake(); err != nil {
return err
}
}
c.greet()
for {
line, err := c.ReadLine()
line, err := c.readLine()
if err == nil {
cmd, arg, err := parseCmd(line)
if err != nil {
@@ -153,34 +195,41 @@ func (s *Server) handleConn(c *Conn) error {
c.handle(cmd, arg)
} else {
if err == io.EOF {
if err == io.EOF || errors.Is(err, net.ErrClosed) {
return nil
}
if err == ErrTooLongLine {
c.WriteResponse(500, EnhancedCode{5, 4, 0}, "Too long line, closing connection")
c.writeResponse(500, EnhancedCode{5, 4, 0}, "Too long line, closing connection")
return nil
}
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
c.WriteResponse(221, EnhancedCode{2, 4, 2}, "Idle timeout, bye bye")
c.writeResponse(421, EnhancedCode{4, 4, 2}, "Idle timeout, bye bye")
return nil
}
c.WriteResponse(221, EnhancedCode{2, 4, 0}, "Connection error, sorry")
c.writeResponse(421, EnhancedCode{4, 4, 0}, "Connection error, sorry")
return err
}
}
}
func (s *Server) network() string {
if s.Network != "" {
return s.Network
}
if s.LMTP {
return "unix"
}
return "tcp"
}
// ListenAndServe listens on the network address s.Addr and then calls Serve
// to handle requests on incoming connections.
//
// If s.Addr is blank and LMTP is disabled, ":smtp" is used.
func (s *Server) ListenAndServe() error {
network := "tcp"
if s.LMTP {
network = "unix"
}
network := s.network()
addr := s.Addr
if !s.LMTP && addr == "" {
@@ -198,18 +247,16 @@ func (s *Server) ListenAndServe() error {
// 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, ":smtps" is used.
// If s.Addr is blank and LMTP is disabled, ":smtps" is used.
func (s *Server) ListenAndServeTLS() error {
if s.LMTP {
return errTCPAndLMTP
}
network := s.network()
addr := s.Addr
if addr == "" {
if !s.LMTP && addr == "" {
addr = ":smtps"
}
l, err := tls.Listen("tcp", addr, s.TLSConfig)
l, err := tls.Listen(network, addr, s.TLSConfig)
if err != nil {
return err
}
@@ -224,19 +271,19 @@ func (s *Server) ListenAndServeTLS() error {
func (s *Server) Close() error {
select {
case <-s.done:
return errors.New("smtp: server already closed")
return ErrServerClosed
default:
close(s.done)
}
var err error
s.locker.Lock()
for _, l := range s.listeners {
if lerr := l.Close(); lerr != nil && err == nil {
err = lerr
}
}
s.locker.Lock()
for conn := range s.conns {
conn.Close()
}
@@ -245,6 +292,44 @@ func (s *Server) Close() error {
return err
}
// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners and then waiting indefinitely for connections to return to
// idle and then shut down.
// If the provided context expires before the shutdown is complete,
// Shutdown returns the context's error, otherwise it returns any
// error returned from closing the Server's underlying Listener(s).
func (s *Server) Shutdown(ctx context.Context) error {
select {
case <-s.done:
return ErrServerClosed
default:
close(s.done)
}
var err error
s.locker.Lock()
for _, l := range s.listeners {
if lerr := l.Close(); lerr != nil && err == nil {
err = lerr
}
}
s.locker.Unlock()
connDone := make(chan struct{})
go func() {
defer close(connDone)
s.wg.Wait()
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-connDone:
return err
}
}
// EnableAuth enables an authentication mechanism on this server.
//
// This function should not be called directly, it must only be used by
@@ -252,12 +337,3 @@ func (s *Server) Close() error {
func (s *Server) EnableAuth(name string, f SaslServerFactory) {
s.auths[name] = f
}
// 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)
}
}