Files
postmoogle/smtp/manager.go
2024-02-21 09:46:46 +02:00

218 lines
4.8 KiB
Go

package smtp
import (
"context"
"crypto/tls"
"net"
"sync"
"time"
"github.com/emersion/go-smtp"
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"gitlab.com/etke.cc/go/fswatcher"
"maunium.net/go/mautrix/id"
"gitlab.com/etke.cc/postmoogle/email"
)
type Config struct {
Domains []string
Port string
TLSCerts []string
TLSKeys []string
TLSPort string
TLSRequired bool
Logger *zerolog.Logger
MaxSize int
Bot matrixbot
Callers []Caller
Relay *RelayConfig
}
type TLSConfig struct {
Listener *Listener
Config *tls.Config
Certs []string
Keys []string
Port string
Mu sync.Mutex
}
type RelayConfig struct {
Host string
Port string
Usename string
Password string
}
type Manager struct {
log *zerolog.Logger
bot matrixbot
fsw *fswatcher.Watcher
smtp *smtp.Server
errs chan error
port string
tls TLSConfig
}
type matrixbot interface {
AllowAuth(context.Context, string, string) (id.RoomID, bool)
IsGreylisted(context.Context, net.Addr) bool
IsBanned(context.Context, net.Addr) bool
IsTrusted(net.Addr) bool
BanAuto(context.Context, net.Addr)
BanAuth(context.Context, net.Addr)
GetMapping(context.Context, string) (id.RoomID, bool)
GetIFOptions(context.Context, id.RoomID) email.IncomingFilteringOptions
IncomingEmail(context.Context, *email.Email) error
GetDKIMprivkey(context.Context) string
}
// Caller is Sendmail caller
type Caller interface {
SetSendmail(func(string, string, string) error)
}
// NewManager creates new SMTP server manager
func NewManager(cfg *Config) *Manager {
mailsrv := &mailServer{
log: cfg.Logger,
bot: cfg.Bot,
domains: cfg.Domains,
sender: newClient(cfg.Relay, cfg.Logger),
}
for _, caller := range cfg.Callers {
caller.SetSendmail(mailsrv.sender.Send)
}
s := smtp.NewServer(mailsrv)
s.ErrorLog = loggerWrapper{func(s string, i ...any) {
cfg.Logger.Warn().Msgf(s, i...)
}}
s.ReadTimeout = 10 * time.Second
s.WriteTimeout = 10 * time.Second
s.MaxMessageBytes = int64(cfg.MaxSize * 1024 * 1024)
s.AllowInsecureAuth = !cfg.TLSRequired
s.EnableREQUIRETLS = cfg.TLSRequired
s.EnableSMTPUTF8 = true
// set domain in greeting only in single-domain mode
if len(cfg.Domains) == 1 {
s.Domain = cfg.Domains[0]
}
loglevel := cfg.Logger.GetLevel()
if loglevel == zerolog.InfoLevel || loglevel == zerolog.DebugLevel || loglevel == zerolog.TraceLevel {
s.Debug = loggerWriter{func(s string) { cfg.Logger.Info().Msg(s) }}
}
fsw, err := fswatcher.New(append(cfg.TLSCerts, cfg.TLSKeys...), 0)
if err != nil {
cfg.Logger.Error().Err(err).Msg("cannot start FS watcher")
}
m := &Manager{
smtp: s,
bot: cfg.Bot,
log: cfg.Logger,
fsw: fsw,
port: cfg.Port,
tls: TLSConfig{
Certs: cfg.TLSCerts,
Keys: cfg.TLSKeys,
Port: cfg.TLSPort,
},
}
m.tls.Mu.Lock()
m.loadTLSConfig()
m.tls.Mu.Unlock()
if m.fsw != nil {
go m.fsw.Start(func(_ fsnotify.Event) {
m.tls.Mu.Lock()
defer m.tls.Mu.Unlock()
ok := m.loadTLSConfig()
if ok {
m.tls.Listener.SetTLSConfig(m.tls.Config)
}
})
}
return m
}
// Start SMTP server
func (m *Manager) Start() error {
m.errs = make(chan error, 1)
go m.listen(m.port, nil)
if m.tls.Config != nil {
go m.listen(m.tls.Port, m.tls.Config)
}
return <-m.errs
}
// Stop SMTP server
func (m *Manager) Stop() {
err := m.fsw.Stop()
if err != nil {
m.log.Error().Err(err).Msg("cannot stop filesystem watcher properly")
}
err = m.smtp.Close()
if err != nil {
m.log.Error().Err(err).Msg("cannot stop SMTP server properly")
}
m.log.Info().Msg("SMTP server has been stopped")
}
func (m *Manager) listen(port string, tlsConfig *tls.Config) {
lwrapper, err := NewListener(port, tlsConfig, m.bot.IsBanned, m.log)
if err != nil {
m.log.Error().Err(err).Str("port", port).Msg("cannot start listener")
m.errs <- err
return
}
if tlsConfig != nil {
m.tls.Listener = lwrapper
}
m.log.Info().Str("port", port).Msg("Starting SMTP server")
err = m.smtp.Serve(lwrapper)
if err != nil {
m.log.Error().Str("port", port).Err(err).Msg("cannot start SMTP server")
m.errs <- err
close(m.errs)
}
}
// loadTLSConfig returns true if certs were loaded and false if not
func (m *Manager) loadTLSConfig() bool {
m.log.Info().Msg("(re)loading TLS config")
if len(m.tls.Certs) == 0 || len(m.tls.Keys) == 0 {
m.log.Warn().Msg("SSL certificates are not provided")
return false
}
certificates := make([]tls.Certificate, 0, len(m.tls.Certs))
for i, path := range m.tls.Certs {
tlsCert, err := tls.LoadX509KeyPair(path, m.tls.Keys[i])
if err != nil {
m.log.Error().Err(err).Msg("cannot load SSL certificate")
continue
}
certificates = append(certificates, tlsCert)
}
if len(certificates) == 0 {
return false
}
m.tls.Config = &tls.Config{Certificates: certificates} //nolint:gosec // it's email, even that config is too strict sometimes
m.smtp.TLSConfig = m.tls.Config
return true
}