215 lines
4.6 KiB
Go
215 lines
4.6 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(string, string) (id.RoomID, bool)
|
|
IsGreylisted(net.Addr) bool
|
|
IsBanned(net.Addr) bool
|
|
IsTrusted(net.Addr) bool
|
|
Ban(net.Addr)
|
|
GetMapping(string) (id.RoomID, bool)
|
|
GetIFOptions(id.RoomID) email.IncomingFilteringOptions
|
|
IncomingEmail(context.Context, *email.Email) error
|
|
GetDKIMprivkey() 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 ...interface{}) { cfg.Logger.Error().Msgf(s, i...) }}
|
|
s.ReadTimeout = 10 * time.Second
|
|
s.WriteTimeout = 10 * time.Second
|
|
s.MaxMessageBytes = 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}
|
|
m.smtp.TLSConfig = m.tls.Config
|
|
return true
|
|
}
|