use postmoogle as general purpose SMTP server and allow other apps or scripts to send emails through it

This commit is contained in:
Aine
2022-09-22 18:21:17 +03:00
parent c9c871287d
commit 070a6ffc76
11 changed files with 108 additions and 20 deletions

View File

@@ -2,10 +2,13 @@ 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
@@ -13,11 +16,15 @@ type msa struct {
log *logger.Logger
domain string
bot Bot
mta utils.MTA
}
func (m *msa) newSession() *msasession {
func (m *msa) newSession(from string, local bool) *msasession {
return &msasession{
ctx: sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()),
mta: m.mta,
from: from,
local: local,
log: m.log,
bot: m.bot,
domain: m.domain,
@@ -25,9 +32,18 @@ func (m *msa) newSession() *msasession {
}
func (m *msa) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
return nil, smtp.ErrAuthUnsupported
if !utils.AddressValid(username) {
return nil, errors.New("please, provide email address")
}
mailbox := utils.Mailbox(username)
if !m.bot.AllowAuth(mailbox, 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(), nil
return m.newSession("", true), nil
}

View File

@@ -2,6 +2,7 @@ package smtp
import (
"context"
"errors"
"io"
"github.com/emersion/go-smtp"
@@ -15,32 +16,41 @@ import (
type msasession struct {
log *logger.Logger
bot Bot
mta utils.MTA
domain string
ctx context.Context
to string
from string
ctx context.Context
local bool
to string
from string
}
func (s *msasession) Mail(from string, opts smtp.MailOptions) error {
sentry.GetHubFromContext(s.ctx).Scope().SetTag("from", from)
s.from = from
s.log.Debug("mail from %s, options: %+v", from, opts)
if !utils.AddressValid(from) {
return errors.New("please, provide email address")
}
if s.local {
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)
if utils.Hostname(to) != s.domain {
s.log.Debug("wrong domain of %s", to)
return smtp.ErrAuthRequired
}
if s.local {
if utils.Hostname(to) != s.domain {
s.log.Debug("wrong domain of %s", to)
return smtp.ErrAuthRequired
}
_, ok := s.bot.GetMapping(utils.Mailbox(to))
if !ok {
s.log.Debug("mapping for %s not found", to)
return smtp.ErrAuthRequired
_, ok := s.bot.GetMapping(utils.Mailbox(to))
if !ok {
s.log.Debug("mapping for %s not found", to)
return smtp.ErrAuthRequired
}
}
s.to = to
@@ -80,7 +90,7 @@ func (s *msasession) Data(r io.Reader) error {
eml.HTML,
files)
return s.bot.Send2Matrix(s.ctx, email)
return s.bot.Send2Matrix(s.ctx, email, s.local)
}
func (s *msasession) Reset() {}

View File

@@ -17,8 +17,9 @@ import (
// Bot interface to send emails into matrix
type Bot interface {
AllowAuth(string, string) bool
GetMapping(string) (id.RoomID, bool)
Send2Matrix(ctx context.Context, email *utils.Email) error
Send2Matrix(ctx context.Context, email *utils.Email, local bool) error
SetMTA(mta utils.MTA)
}

View File

@@ -40,6 +40,7 @@ func NewServer(cfg *Config) *Server {
sender := NewMTA(cfg.LogLevel)
receiver := &msa{
log: log,
mta: sender,
bot: cfg.Bot,
domain: cfg.Domain,
}
@@ -51,6 +52,7 @@ func NewServer(cfg *Config) *Server {
s.WriteTimeout = 10 * time.Second
s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024
s.EnableREQUIRETLS = cfg.TLSRequired
s.AllowInsecureAuth = !cfg.TLSRequired
if log.GetLevel() == "DEBUG" || log.GetLevel() == "TRACE" {
s.Debug = os.Stdout
}