diff --git a/bot/bot.go b/bot/bot.go index 3ddbbb6..5d62e86 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -12,8 +12,6 @@ import ( "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" - - "gitlab.com/etke.cc/postmoogle/utils" ) // Bot represents matrix bot @@ -24,7 +22,7 @@ type Bot struct { allowedAdmins []*regexp.Regexp commands commandList rooms sync.Map - mta utils.MTA + sendmail func(string, string, string) error log *logger.Logger lp *linkpearl.Linkpearl mu map[id.RoomID]*sync.Mutex diff --git a/bot/command.go b/bot/command.go index 5685318..1872899 100644 --- a/bot/command.go +++ b/bot/command.go @@ -365,7 +365,7 @@ func (b *Bot) runSend(ctx context.Context) { data := utils. NewEmail(ID, "", subject, from, to, body, "", nil). Compose(b.getBotSettings().DKIMPrivateKey()) - err = b.mta.Send(from, to, data) + err = b.sendmail(from, to, data) if err != nil { b.Error(ctx, evt.RoomID, "cannot send email to %s: %v", to, err) } else { diff --git a/bot/email.go b/bot/email.go index 5ed76c7..f5c9259 100644 --- a/bot/email.go +++ b/bot/email.go @@ -26,9 +26,14 @@ const ( eventFromKey = "cc.etke.postmoogle.from" ) -// SetMTA sets mail transfer agent instance to the bot -func (b *Bot) SetMTA(mta utils.MTA) { - b.mta = mta +// SetSendmail sets mail sending func to the bot +func (b *Bot) SetSendmail(sendmail func(string, string, string) error) { + b.sendmail = sendmail +} + +// GetDKIMprivkey returns DKIM private key +func (b *Bot) GetDKIMprivkey() string { + return b.getBotSettings().DKIMPrivateKey() } func (b *Bot) getMapping(mailbox string) (id.RoomID, bool) { @@ -70,9 +75,9 @@ func (b *Bot) GetIFOptions(roomID id.RoomID) utils.IncomingFilteringOptions { return cfg } -// Send email to matrix room -func (b *Bot) Send2Matrix(ctx context.Context, email *utils.Email, incoming bool) error { - roomID, ok := b.GetMapping(email.Mailbox(incoming)) +// IncomingEmail sends incoming email to matrix room +func (b *Bot) IncomingEmail(ctx context.Context, email *utils.Email) error { + roomID, ok := b.GetMapping(email.Mailbox(true)) if !ok { return errors.New("room not found") } @@ -84,10 +89,6 @@ func (b *Bot) Send2Matrix(ctx context.Context, email *utils.Email, incoming bool b.Error(ctx, roomID, "cannot get settings: %v", err) } - if !incoming && cfg.NoSend() { - return errors.New("that mailbox is receive-only") - } - var threadID id.EventID if email.InReplyTo != "" && !cfg.NoThreads() { threadID = b.getThreadID(roomID, email.InReplyTo) @@ -111,10 +112,6 @@ func (b *Bot) Send2Matrix(ctx context.Context, email *utils.Email, incoming bool b.sendFiles(ctx, roomID, email.Files, cfg.NoThreads(), threadID) } - if !incoming { - email.MessageID = fmt.Sprintf("<%s@%s>", eventID, b.domains[0]) - return b.mta.Send(email.From, email.To, email.Compose(b.getBotSettings().DKIMPrivateKey())) - } return nil } @@ -156,7 +153,7 @@ func (b *Bot) getParentEmail(evt *event.Event) (string, string, string) { // Send2Email sends message to email // TODO rewrite to thread replies only -func (b *Bot) Send2Email(ctx context.Context, to, subject, body string) error { +func (b *Bot) SendEmailReply(ctx context.Context, to, subject, body string) error { var inReplyTo string evt := eventFromContext(ctx) cfg, err := b.getRoomSettings(evt.RoomID) @@ -193,7 +190,7 @@ func (b *Bot) Send2Email(ctx context.Context, to, subject, body string) error { data := utils. NewEmail(ID, inReplyTo, subject, from, to, body, "", nil). Compose(b.getBotSettings().DKIMPrivateKey()) - return b.mta.Send(from, to, data) + return b.sendmail(from, to, data) } func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.File, noThreads bool, parentID id.EventID) { diff --git a/cmd/cmd.go b/cmd/cmd.go index 140a43b..6fdd4b9 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -20,9 +20,9 @@ import ( ) var ( - mxb *bot.Bot - smtpserv *smtp.Server - log *logger.Logger + mxb *bot.Bot + smtpm *smtp.Manager + log *logger.Logger ) func main() { @@ -45,7 +45,7 @@ func main() { go startBot(cfg.StatusMsg) - if err := smtpserv.Start(); err != nil { + if err := smtpm.Start(); err != nil { //nolint:gocritic log.Fatal("SMTP server crashed: %v", err) } @@ -96,7 +96,7 @@ func initBot(cfg *config.Config) { } func initSMTP(cfg *config.Config) { - smtpserv = smtp.NewServer(&smtp.Config{ + smtpm = smtp.NewManager(&smtp.Config{ Domains: cfg.Domains, Port: cfg.Port, TLSCert: cfg.TLS.Cert, @@ -132,7 +132,7 @@ func startBot(statusMsg string) { func shutdown() { log.Info("Shutting down...") - smtpserv.Stop() + smtpm.Stop() mxb.Stop() sentry.Flush(5 * time.Second) diff --git a/smtp/manager.go b/smtp/manager.go new file mode 100644 index 0000000..3fc98ed --- /dev/null +++ b/smtp/manager.go @@ -0,0 +1,139 @@ +package smtp + +import ( + "context" + "crypto/tls" + "net" + "os" + "time" + + "github.com/emersion/go-smtp" + "gitlab.com/etke.cc/go/logger" + "maunium.net/go/mautrix/id" + + "gitlab.com/etke.cc/postmoogle/utils" +) + +type Config struct { + Domains []string + Port string + + TLSCert string + TLSKey string + TLSPort string + TLSRequired bool + + LogLevel string + MaxSize int + Bot matrixbot +} + +type Manager struct { + log *logger.Logger + smtp *smtp.Server + errs chan error + + port string + tlsPort string + tlsCfg *tls.Config +} + +type matrixbot interface { + AllowAuth(string, string) bool + GetMapping(string) (id.RoomID, bool) + GetIFOptions(id.RoomID) utils.IncomingFilteringOptions + IncomingEmail(context.Context, *utils.Email) error + SetSendmail(func(string, string, string) error) + GetDKIMprivkey() string +} + +// NewManager creates new SMTP server manager +func NewManager(cfg *Config) *Manager { + log := logger.New("smtp.", cfg.LogLevel) + mailsrv := &mailServer{ + log: log, + bot: cfg.Bot, + domains: cfg.Domains, + } + cfg.Bot.SetSendmail(mailsrv.SendEmail) + + s := smtp.NewServer(mailsrv) + s.Domain = cfg.Domains[0] + 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 + if log.GetLevel() == "DEBUG" || log.GetLevel() == "TRACE" { + s.Debug = os.Stdout + } + + m := &Manager{ + smtp: s, + log: log, + port: cfg.Port, + tlsPort: cfg.TLSPort, + } + m.loadTLSConfig(cfg.TLSCert, cfg.TLSKey) + return m +} + +// Start SMTP server +func (m *Manager) Start() error { + m.errs = make(chan error, 1) + go m.listen(m.port, nil) + if m.tlsCfg != nil { + go m.listen(m.tlsPort, m.tlsCfg) + } + + return <-m.errs +} + +// Stop SMTP server +func (m *Manager) Stop() { + err := m.smtp.Close() + if err != nil { + m.log.Error("cannot stop SMTP server properly: %v", err) + } + m.log.Info("SMTP server has been stopped") +} + +func (m *Manager) listen(port string, tlsCfg *tls.Config) { + var l net.Listener + var err error + if tlsCfg != nil { + l, err = tls.Listen("tcp", ":"+port, tlsCfg) + } else { + l, err = net.Listen("tcp", ":"+port) + } + if err != nil { + m.log.Error("cannot start listener on %s: %v", port, err) + m.errs <- err + return + } + + m.log.Info("Starting SMTP server on port %s", port) + + err = m.smtp.Serve(l) + if err != nil { + m.log.Error("cannot start SMTP server on %s: %v", port, err) + m.errs <- err + close(m.errs) + } +} + +func (m *Manager) loadTLSConfig(cert, key string) { + if cert == "" || key == "" { + m.log.Warn("SSL certificate is not provided") + return + } + + tlsCert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + m.log.Error("cannot load SSL certificate: %v", err) + return + } + m.tlsCfg = &tls.Config{Certificates: []tls.Certificate{tlsCert}} + m.smtp.TLSConfig = m.tlsCfg +} diff --git a/smtp/msa.go b/smtp/msa.go deleted file mode 100644 index 4641425..0000000 --- a/smtp/msa.go +++ /dev/null @@ -1,48 +0,0 @@ -package smtp - -import ( - "context" - "errors" - - "github.com/emersion/go-smtp" - "github.com/getsentry/sentry-go" - "gitlab.com/etke.cc/go/logger" - - "gitlab.com/etke.cc/postmoogle/utils" -) - -// msa is mail submission agent, implements smtp.Backend -type msa struct { - log *logger.Logger - domains []string - bot Bot - mta utils.MTA -} - -func (m *msa) newSession(from string, incoming bool) *msasession { - return &msasession{ - ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()), - mta: m.mta, - from: from, - incoming: incoming, - log: m.log, - bot: m.bot, - domains: m.domains, - } -} - -func (m *msa) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { - if !utils.AddressValid(username) { - return nil, errors.New("please, provide an email address") - } - - if !m.bot.AllowAuth(username, password) { - return nil, errors.New("email or password is invalid") - } - - return m.newSession(username, false), nil -} - -func (m *msa) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { - return m.newSession("", true), nil -} diff --git a/smtp/msasession.go b/smtp/msasession.go deleted file mode 100644 index 4c3c694..0000000 --- a/smtp/msasession.go +++ /dev/null @@ -1,129 +0,0 @@ -package smtp - -import ( - "context" - "errors" - "io" - - "github.com/emersion/go-smtp" - "github.com/getsentry/sentry-go" - "github.com/jhillyerd/enmime" - "gitlab.com/etke.cc/go/logger" - "gitlab.com/etke.cc/go/validator" - - "gitlab.com/etke.cc/postmoogle/utils" -) - -// msasession represents an SMTP-submission session. -// This can be used in 2 directions: -// - receiving emails from remote servers, in which case: `incoming = true` -// - sending emails from local users, in which case: `incoming = false` -type msasession struct { - log *logger.Logger - bot Bot - mta utils.MTA - domains []string - - ctx context.Context - incoming bool - to string - from string -} - -func (s *msasession) Mail(from string, opts smtp.MailOptions) error { - sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from) - if !utils.AddressValid(from) { - return errors.New("please, provide email address") - } - if s.incoming { - s.from = from - s.log.Debug("mail from %s, options: %+v", from, opts) - } - return nil -} - -func (s *msasession) Rcpt(to string) error { - sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) - s.to = to - - //nolint:nestif // TODO - if s.incoming { - var domainok bool - for _, domain := range s.domains { - if utils.Hostname(to) == domain { - domainok = true - break - } - } - if !domainok { - s.log.Debug("wrong domain of %s", to) - return smtp.ErrAuthRequired - } - - roomID, ok := s.bot.GetMapping(utils.Mailbox(to)) - if !ok { - s.log.Debug("mapping for %s not found", to) - return smtp.ErrAuthRequired - } - - validations := s.bot.GetIFOptions(roomID) - if !s.validate(validations) { - return smtp.ErrAuthRequired - } - } - - s.log.Debug("mail to %s", to) - return nil -} - -func (s *msasession) parseAttachments(parts []*enmime.Part) []*utils.File { - files := make([]*utils.File, 0, len(parts)) - for _, attachment := range parts { - for _, err := range attachment.Errors { - s.log.Warn("attachment error: %v", err) - } - file := utils.NewFile(attachment.FileName, attachment.Content) - files = append(files, file) - } - - return files -} - -func (s *msasession) validate(options utils.IncomingFilteringOptions) bool { - enforce := validator.Enforce{ - Email: true, - MX: options.SpamcheckMX(), - SMTP: options.SpamcheckMX(), - } - v := validator.New(options.Spamlist(), enforce, s.to, s.log) - - return v.Email(s.from) -} - -func (s *msasession) Data(r io.Reader) error { - parser := enmime.NewParser() - eml, err := parser.ReadEnvelope(r) - if err != nil { - return err - } - - files := s.parseAttachments(eml.Attachments) - - email := utils.NewEmail( - eml.GetHeader("Message-Id"), - eml.GetHeader("In-Reply-To"), - eml.GetHeader("Subject"), - s.from, - s.to, - eml.Text, - eml.HTML, - files) - - return s.bot.Send2Matrix(s.ctx, email, s.incoming) -} - -func (s *msasession) Reset() {} - -func (s *msasession) Logout() error { - return nil -} diff --git a/smtp/mta.go b/smtp/mta.go deleted file mode 100644 index c219b77..0000000 --- a/smtp/mta.go +++ /dev/null @@ -1,60 +0,0 @@ -package smtp - -import ( - "context" - "io" - "strings" - - "gitlab.com/etke.cc/go/logger" - "gitlab.com/etke.cc/go/trysmtp" - "maunium.net/go/mautrix/id" - - "gitlab.com/etke.cc/postmoogle/utils" -) - -// Bot interface to send emails into matrix -type Bot interface { - AllowAuth(string, string) bool - GetMapping(string) (id.RoomID, bool) - GetIFOptions(id.RoomID) utils.IncomingFilteringOptions - Send2Matrix(ctx context.Context, email *utils.Email, incoming bool) error - SetMTA(mta utils.MTA) -} - -// mta is Mail Transfer Agent -type mta struct { - log *logger.Logger -} - -func NewMTA(loglevel string) utils.MTA { - return &mta{ - log: logger.New("smtp/mta.", loglevel), - } -} - -func (m *mta) Send(from, to, data string) error { - m.log.Debug("Sending email from %s to %s", from, to) - conn, err := trysmtp.Connect(from, to) - if err != nil { - m.log.Error("cannot connect to SMTP server of %s: %v", to, err) - return err - } - defer conn.Close() - - var w io.WriteCloser - w, err = conn.Data() - if err != nil { - m.log.Error("cannot send DATA command: %v", err) - return err - } - defer w.Close() - m.log.Debug("sending DATA:\n%s", data) - _, err = strings.NewReader(data).WriteTo(w) - if err != nil { - m.log.Debug("cannot write DATA: %v", err) - return err - } - - m.log.Debug("email has been sent") - return nil -} diff --git a/smtp/server.go b/smtp/server.go index b386b0d..7355a2b 100644 --- a/smtp/server.go +++ b/smtp/server.go @@ -1,128 +1,86 @@ package smtp import ( - "crypto/tls" - "net" - "os" - "time" + "context" + "errors" + "io" + "strings" "github.com/emersion/go-smtp" + "github.com/getsentry/sentry-go" "gitlab.com/etke.cc/go/logger" + "gitlab.com/etke.cc/go/trysmtp" + + "gitlab.com/etke.cc/postmoogle/utils" ) -type Config struct { - Domains []string - Port string - - TLSCert string - TLSKey string - TLSPort string - TLSRequired bool - - LogLevel string - MaxSize int - Bot Bot +type mailServer struct { + bot matrixbot + log *logger.Logger + domains []string } -type Server struct { - log *logger.Logger - msa *smtp.Server - errs chan error +// Login used for outgoing mail submissions only (when you use postmoogle as smtp server in your scripts) +func (m *mailServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { + if !utils.AddressValid(username) { + return nil, errors.New("please, provide an email address") + } - port string - tlsPort string - tlsCfg *tls.Config + if !m.bot.AllowAuth(username, password) { + return nil, errors.New("email or password is invalid") + } + + return &outgoingSession{ + ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()), + sendmail: m.SendEmail, + privkey: m.bot.GetDKIMprivkey(), + from: username, + log: m.log, + domains: m.domains, + }, nil } -// NewServer creates new SMTP server -func NewServer(cfg *Config) *Server { - log := logger.New("smtp/msa.", cfg.LogLevel) - sender := NewMTA(cfg.LogLevel) - receiver := &msa{ - log: log, - mta: sender, - bot: cfg.Bot, - domains: cfg.Domains, - } - receiver.bot.SetMTA(sender) - - s := smtp.NewServer(receiver) - s.Domain = cfg.Domains[0] - 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 - if log.GetLevel() == "DEBUG" || log.GetLevel() == "TRACE" { - s.Debug = os.Stdout - } - - server := &Server{ - msa: s, - log: log, - port: cfg.Port, - tlsPort: cfg.TLSPort, - } - server.loadTLSConfig(cfg.TLSCert, cfg.TLSKey) - return server +// AnonymousLogin used for incoming mail submissions only +func (m *mailServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { + return &incomingSession{ + ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()), + getRoomID: m.bot.GetMapping, + getFilters: m.bot.GetIFOptions, + receiveEmail: m.ReceiveEmail, + log: m.log, + domains: m.domains, + }, nil } -// Start SMTP server -func (s *Server) Start() error { - s.errs = make(chan error, 1) - go s.listen(s.port, nil) - if s.tlsCfg != nil { - go s.listen(s.tlsPort, s.tlsCfg) - } - - return <-s.errs -} - -// Stop SMTP server -func (s *Server) Stop() { - err := s.msa.Close() +// SendEmail to external mail server +func (m *mailServer) SendEmail(from, to, data string) error { + m.log.Debug("Sending email from %s to %s", from, to) + conn, err := trysmtp.Connect(from, to) if err != nil { - s.log.Error("cannot stop SMTP server properly: %v", err) + m.log.Error("cannot connect to SMTP server of %s: %v", to, err) + return err } - s.log.Info("SMTP server has been stopped") + defer conn.Close() + + var w io.WriteCloser + w, err = conn.Data() + if err != nil { + m.log.Error("cannot send DATA command: %v", err) + return err + } + defer w.Close() + m.log.Debug("sending DATA:\n%s", data) + _, err = strings.NewReader(data).WriteTo(w) + if err != nil { + m.log.Debug("cannot write DATA: %v", err) + return err + } + + m.log.Debug("email has been sent") + return nil } -func (s *Server) listen(port string, tlsCfg *tls.Config) { - var l net.Listener - var err error - if tlsCfg != nil { - l, err = tls.Listen("tcp", ":"+port, tlsCfg) - } else { - l, err = net.Listen("tcp", ":"+port) - } - if err != nil { - s.log.Error("cannot start listener on %s: %v", port, err) - s.errs <- err - return - } - - s.log.Info("Starting SMTP server on port %s", port) - - err = s.msa.Serve(l) - if err != nil { - s.log.Error("cannot start SMTP server on %s: %v", port, err) - s.errs <- err - close(s.errs) - } -} - -func (s *Server) loadTLSConfig(cert, key string) { - if cert == "" || key == "" { - s.log.Warn("SSL certificate is not provided") - return - } - - tlsCert, err := tls.LoadX509KeyPair(cert, key) - if err != nil { - s.log.Error("cannot load SSL certificate: %v", err) - return - } - s.tlsCfg = &tls.Config{Certificates: []tls.Certificate{tlsCert}} - s.msa.TLSConfig = s.tlsCfg +// ReceiveEmail - incoming mail into matrix room +func (m *mailServer) ReceiveEmail(ctx context.Context, email *utils.Email) error { + return m.bot.IncomingEmail(ctx, email) } diff --git a/smtp/session.go b/smtp/session.go new file mode 100644 index 0000000..63650e4 --- /dev/null +++ b/smtp/session.go @@ -0,0 +1,169 @@ +package smtp + +import ( + "context" + "errors" + "io" + + "github.com/emersion/go-smtp" + "github.com/getsentry/sentry-go" + "github.com/jhillyerd/enmime" + "gitlab.com/etke.cc/go/logger" + "gitlab.com/etke.cc/go/validator" + "maunium.net/go/mautrix/id" + + "gitlab.com/etke.cc/postmoogle/utils" +) + +// incomingSession represents an SMTP-submission session receiving emails from remote servers +type incomingSession struct { + log *logger.Logger + getRoomID func(string) (id.RoomID, bool) + getFilters func(id.RoomID) utils.IncomingFilteringOptions + receiveEmail func(context.Context, *utils.Email) error + domains []string + + ctx context.Context + to string + from string +} + +func (s *incomingSession) Mail(from string, opts smtp.MailOptions) error { + sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from) + if !utils.AddressValid(from) { + return errors.New("please, provide email address") + } + s.from = from + s.log.Debug("mail from %s, options: %+v", from, opts) + return nil +} + +func (s *incomingSession) Rcpt(to string) error { + sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) + s.to = to + var domainok bool + for _, domain := range s.domains { + if utils.Hostname(to) == domain { + domainok = true + break + } + } + if !domainok { + s.log.Debug("wrong domain of %s", to) + return smtp.ErrAuthRequired + } + + roomID, ok := s.getRoomID(utils.Mailbox(to)) + if !ok { + s.log.Debug("mapping for %s not found", to) + return smtp.ErrAuthRequired + } + + validations := s.getFilters(roomID) + if !validateEmail(s.from, s.to, s.log, validations) { + return smtp.ErrAuthRequired + } + + s.log.Debug("mail to %s", to) + return nil +} + +func (s *incomingSession) Data(r io.Reader) error { + parser := enmime.NewParser() + eml, err := parser.ReadEnvelope(r) + if err != nil { + return err + } + + files := parseAttachments(eml.Attachments, s.log) + + email := utils.NewEmail( + eml.GetHeader("Message-Id"), + eml.GetHeader("In-Reply-To"), + eml.GetHeader("Subject"), + s.from, + s.to, + eml.Text, + eml.HTML, + files) + + return s.receiveEmail(s.ctx, email) +} +func (s *incomingSession) Reset() {} +func (s *incomingSession) Logout() error { return nil } + +// outgoingSession represents an SMTP-submission session sending emails from external scripts, using postmoogle as SMTP server +type outgoingSession struct { + log *logger.Logger + sendmail func(string, string, string) error + privkey string + domains []string + + ctx context.Context + to string + from string +} + +func (s *outgoingSession) Mail(from string, opts smtp.MailOptions) error { + sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from) + if !utils.AddressValid(from) { + return errors.New("please, provide email address") + } + return nil +} + +func (s *outgoingSession) Rcpt(to string) error { + sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) + s.to = to + + s.log.Debug("mail to %s", to) + return nil +} + +func (s *outgoingSession) Data(r io.Reader) error { + parser := enmime.NewParser() + eml, err := parser.ReadEnvelope(r) + if err != nil { + return err + } + + files := parseAttachments(eml.Attachments, s.log) + + email := utils.NewEmail( + eml.GetHeader("Message-Id"), + eml.GetHeader("In-Reply-To"), + eml.GetHeader("Subject"), + s.from, + s.to, + eml.Text, + eml.HTML, + files) + + return s.sendmail(email.From, email.To, email.Compose(s.privkey)) +} +func (s *outgoingSession) Reset() {} +func (s *outgoingSession) Logout() error { return nil } + +func validateEmail(from, to string, log *logger.Logger, options utils.IncomingFilteringOptions) bool { + enforce := validator.Enforce{ + Email: true, + MX: options.SpamcheckMX(), + SMTP: options.SpamcheckMX(), + } + v := validator.New(options.Spamlist(), enforce, to, log) + + return v.Email(from) +} + +func parseAttachments(parts []*enmime.Part, log *logger.Logger) []*utils.File { + files := make([]*utils.File, 0, len(parts)) + for _, attachment := range parts { + for _, err := range attachment.Errors { + log.Warn("attachment error: %v", err) + } + file := utils.NewFile(attachment.FileName, attachment.Content) + files = append(files, file) + } + + return files +} diff --git a/utils/email.go b/utils/email.go index e099cd1..e7a6247 100644 --- a/utils/email.go +++ b/utils/email.go @@ -14,11 +14,6 @@ import ( "maunium.net/go/mautrix/id" ) -// MTA is mail transfer agent -type MTA interface { - Send(from, to, data string) error -} - // IncomingFilteringOptions for incoming mail type IncomingFilteringOptions interface { SpamcheckSMTP() bool