initial, rought, not-user-friendly support for multi-domain setup

This commit is contained in:
Aine
2022-11-08 18:16:38 +02:00
parent 8954a7801a
commit 15d5afe90f
14 changed files with 70 additions and 45 deletions

View File

@@ -45,7 +45,7 @@ env vars
* **POSTMOOGLE_HOMESERVER** - homeserver url, eg: `https://matrix.example.com` * **POSTMOOGLE_HOMESERVER** - homeserver url, eg: `https://matrix.example.com`
* **POSTMOOGLE_LOGIN** - user login/localpart, eg: `moogle` * **POSTMOOGLE_LOGIN** - user login/localpart, eg: `moogle`
* **POSTMOOGLE_PASSWORD** - user password * **POSTMOOGLE_PASSWORD** - user password
* **POSTMOOGLE_DOMAIN** - SMTP domain to listen for new emails * **POSTMOOGLE_DOMAINS** - space separated list of SMTP domains to listen for new emails. The first domain acts as actual domain, all other as aliases
<details> <details>
<summary>other optional config parameters</summary> <summary>other optional config parameters</summary>

View File

@@ -73,7 +73,14 @@ func (b *Bot) allowSend(actorID id.UserID, targetRoomID id.RoomID) bool {
// AllowAuth check if SMTP login (email) and password are valid // AllowAuth check if SMTP login (email) and password are valid
func (b *Bot) AllowAuth(email, password string) bool { func (b *Bot) AllowAuth(email, password string) bool {
if !strings.HasSuffix(email, "@"+b.domain) { var suffix bool
for _, domain := range b.domains {
if strings.HasSuffix(email, "@"+domain) {
suffix = true
break
}
}
if !suffix {
return false return false
} }

View File

@@ -19,7 +19,7 @@ import (
// Bot represents matrix bot // Bot represents matrix bot
type Bot struct { type Bot struct {
prefix string prefix string
domain string domains []string
allowedUsers []*regexp.Regexp allowedUsers []*regexp.Regexp
allowedAdmins []*regexp.Regexp allowedAdmins []*regexp.Regexp
commands commandList commands commandList
@@ -36,16 +36,16 @@ func New(
lp *linkpearl.Linkpearl, lp *linkpearl.Linkpearl,
log *logger.Logger, log *logger.Logger,
prefix string, prefix string,
domain string, domains []string,
admins []string, admins []string,
) (*Bot, error) { ) (*Bot, error) {
b := &Bot{ b := &Bot{
prefix: prefix, prefix: prefix,
domain: domain, domains: domains,
rooms: sync.Map{}, rooms: sync.Map{},
log: log, log: log,
lp: lp, lp: lp,
mu: map[id.RoomID]*sync.Mutex{}, mu: map[id.RoomID]*sync.Mutex{},
} }
users, err := b.initBotUsers() users, err := b.initBotUsers()
if err != nil { if err != nil {

View File

@@ -262,7 +262,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) {
msg.WriteString(" SOME_INBOX` command.\n") msg.WriteString(" SOME_INBOX` command.\n")
msg.WriteString("You will then be able to send emails to `SOME_INBOX@") msg.WriteString("You will then be able to send emails to `SOME_INBOX@")
msg.WriteString(b.domain) msg.WriteString(b.domains[0])
msg.WriteString("` and have them appear in this room.") msg.WriteString("` and have them appear in this room.")
b.SendNotice(ctx, roomID, msg.String()) b.SendNotice(ctx, roomID, msg.String())
@@ -301,7 +301,7 @@ func (b *Bot) sendHelp(ctx context.Context) {
msg.WriteString(value) msg.WriteString(value)
if cmd.key == roomOptionMailbox { if cmd.key == roomOptionMailbox {
msg.WriteString("@") msg.WriteString("@")
msg.WriteString(b.domain) msg.WriteString(b.domains[0])
} }
msg.WriteString("`)") msg.WriteString("`)")
} }
@@ -358,8 +358,8 @@ func (b *Bot) runSend(ctx context.Context) {
} }
} }
from := mailbox + "@" + b.domain from := mailbox + "@" + b.domains[0]
ID := fmt.Sprintf("<%s@%s>", evt.ID, b.domain) ID := fmt.Sprintf("<%s@%s>", evt.ID, b.domains[0])
for _, to := range tos { for _, to := range tos {
data := utils. data := utils.
NewEmail(ID, "", subject, from, to, body, "", nil). NewEmail(ID, "", subject, from, to, body, "", nil).

View File

@@ -55,7 +55,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
msg.WriteString("* `") msg.WriteString("* `")
msg.WriteString(mailbox) msg.WriteString(mailbox)
msg.WriteString("@") msg.WriteString("@")
msg.WriteString(b.domain) msg.WriteString(b.domains[0])
msg.WriteString("` by ") msg.WriteString("` by ")
msg.WriteString(cfg.Owner()) msg.WriteString(cfg.Owner())
msg.WriteString("\n") msg.WriteString("\n")
@@ -202,5 +202,5 @@ func (b *Bot) runCatchAll(ctx context.Context, commandSlice []string) {
return return
} }
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Catch-all is set to: `%s@%s`.", mailbox, b.domain)) b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Catch-all is set to: `%s@%s`.", mailbox, b.domains[0]))
} }

View File

@@ -58,7 +58,7 @@ func (b *Bot) getOption(ctx context.Context, name string) {
} }
if name == roomOptionMailbox { if name == roomOptionMailbox {
value = value + "@" + b.domain value = value + "@" + b.domains[0]
} }
msg := fmt.Sprintf("`%s` of this room is `%s`\n"+ msg := fmt.Sprintf("`%s` of this room is `%s`\n"+
@@ -85,7 +85,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
if name == roomOptionMailbox { if name == roomOptionMailbox {
existingID, ok := b.getMapping(value) existingID, ok := b.getMapping(value)
if ok && existingID != "" && existingID != evt.RoomID { if ok && existingID != "" && existingID != evt.RoomID {
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s@%s` already taken, kupo", value, b.domain)) b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s@%s` already taken, kupo", value, b.domains[0]))
return return
} }
} }
@@ -114,7 +114,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
b.rooms.Delete(old) b.rooms.Delete(old)
} }
b.rooms.Store(value, evt.RoomID) b.rooms.Store(value, evt.RoomID)
value = fmt.Sprintf("%s@%s", value, b.domain) value = fmt.Sprintf("%s@%s", value, b.domains[0])
} }
err = b.setRoomSettings(evt.RoomID, cfg) err = b.setRoomSettings(evt.RoomID, cfg)

View File

@@ -112,7 +112,7 @@ func (b *Bot) Send2Matrix(ctx context.Context, email *utils.Email, incoming bool
} }
if !incoming { if !incoming {
email.MessageID = fmt.Sprintf("<%s@%s>", eventID, b.domain) email.MessageID = fmt.Sprintf("<%s@%s>", eventID, b.domains[0])
return b.mta.Send(email.From, email.To, email.Compose(b.getBotSettings().DKIMPrivateKey())) return b.mta.Send(email.From, email.To, email.Compose(b.getBotSettings().DKIMPrivateKey()))
} }
return nil return nil
@@ -167,7 +167,7 @@ func (b *Bot) Send2Email(ctx context.Context, to, subject, body string) error {
if mailbox == "" { if mailbox == "" {
return fmt.Errorf("mailbox not configured, kupo") return fmt.Errorf("mailbox not configured, kupo")
} }
from := mailbox + "@" + b.domain from := mailbox + "@" + b.domains[0]
pTo, pInReplyTo, pSubject := b.getParentEmail(evt) pTo, pInReplyTo, pSubject := b.getParentEmail(evt)
inReplyTo = pInReplyTo inReplyTo = pInReplyTo
if pTo != "" && to == "" { if pTo != "" && to == "" {
@@ -189,7 +189,7 @@ func (b *Bot) Send2Email(ctx context.Context, to, subject, body string) error {
} }
} }
ID := evt.ID.String()[1:] + "@" + b.domain ID := evt.ID.String()[1:] + "@" + b.domains[0]
data := utils. data := utils.
NewEmail(ID, inReplyTo, subject, from, to, body, "", nil). NewEmail(ID, inReplyTo, subject, from, to, body, "", nil).
Compose(b.getBotSettings().DKIMPrivateKey()) Compose(b.getBotSettings().DKIMPrivateKey())

View File

@@ -87,7 +87,7 @@ func initBot(cfg *config.Config) {
log.Fatal("cannot initialize matrix bot: %v", err) log.Fatal("cannot initialize matrix bot: %v", err)
} }
mxb, err = bot.New(lp, mxlog, cfg.Prefix, cfg.Domain, cfg.Admins) mxb, err = bot.New(lp, mxlog, cfg.Prefix, cfg.Domains, cfg.Admins)
if err != nil { if err != nil {
// nolint // Fatal = panic, not os.Exit() // nolint // Fatal = panic, not os.Exit()
log.Fatal("cannot start matrix bot: %v", err) log.Fatal("cannot start matrix bot: %v", err)
@@ -97,7 +97,7 @@ func initBot(cfg *config.Config) {
func initSMTP(cfg *config.Config) { func initSMTP(cfg *config.Config) {
smtpserv = smtp.NewServer(&smtp.Config{ smtpserv = smtp.NewServer(&smtp.Config{
Domain: cfg.Domain, Domains: cfg.Domains,
Port: cfg.Port, Port: cfg.Port,
TLSCert: cfg.TLS.Cert, TLSCert: cfg.TLS.Cert,
TLSKey: cfg.TLS.Key, TLSKey: cfg.TLS.Key,

View File

@@ -15,7 +15,7 @@ func New() *Config {
Login: env.String("login", defaultConfig.Login), Login: env.String("login", defaultConfig.Login),
Password: env.String("password", defaultConfig.Password), Password: env.String("password", defaultConfig.Password),
Prefix: env.String("prefix", defaultConfig.Prefix), Prefix: env.String("prefix", defaultConfig.Prefix),
Domain: env.String("domain", defaultConfig.Domain), Domains: migrateDomains("domain", "domains"),
Port: env.String("port", defaultConfig.Port), Port: env.String("port", defaultConfig.Port),
NoEncryption: env.Bool("noencryption"), NoEncryption: env.Bool("noencryption"),
DataSecret: env.String("data.secret", defaultConfig.DataSecret), DataSecret: env.String("data.secret", defaultConfig.DataSecret),
@@ -40,3 +40,13 @@ func New() *Config {
return cfg return cfg
} }
func migrateDomains(oldKey, newKey string) []string {
domains := []string{}
old := env.String(oldKey, "")
if old != "" {
domains = append(domains, old)
}
return append(domains, env.Slice(newKey)...)
}

View File

@@ -2,7 +2,7 @@ package config
var defaultConfig = &Config{ var defaultConfig = &Config{
LogLevel: "INFO", LogLevel: "INFO",
Domain: "localhost", Domains: []string{"localhost"},
Port: "25", Port: "25",
Prefix: "!pm", Prefix: "!pm",
MaxSize: 1024, MaxSize: 1024,

View File

@@ -8,8 +8,8 @@ type Config struct {
Login string Login string
// Password for login/password auth only // Password for login/password auth only
Password string Password string
// Domain for SMTP // Domains for SMTP
Domain string Domains []string
// Port for SMTP // Port for SMTP
Port string Port string
// RoomID of the admin room // RoomID of the admin room

View File

@@ -13,10 +13,10 @@ import (
// msa is mail submission agent, implements smtp.Backend // msa is mail submission agent, implements smtp.Backend
type msa struct { type msa struct {
log *logger.Logger log *logger.Logger
domain string domains []string
bot Bot bot Bot
mta utils.MTA mta utils.MTA
} }
func (m *msa) newSession(from string, incoming bool) *msasession { func (m *msa) newSession(from string, incoming bool) *msasession {
@@ -27,7 +27,7 @@ func (m *msa) newSession(from string, incoming bool) *msasession {
incoming: incoming, incoming: incoming,
log: m.log, log: m.log,
bot: m.bot, bot: m.bot,
domain: m.domain, domains: m.domains,
} }
} }

View File

@@ -19,10 +19,10 @@ import (
// - receiving emails from remote servers, in which case: `incoming = true` // - receiving emails from remote servers, in which case: `incoming = true`
// - sending emails from local users, in which case: `incoming = false` // - sending emails from local users, in which case: `incoming = false`
type msasession struct { type msasession struct {
log *logger.Logger log *logger.Logger
bot Bot bot Bot
mta utils.MTA mta utils.MTA
domain string domains []string
ctx context.Context ctx context.Context
incoming bool incoming bool
@@ -46,8 +46,16 @@ func (s *msasession) Rcpt(to string) error {
sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to) sentry.GetHubFromContext(s.ctx).Scope().SetTag("to", to)
s.to = to s.to = to
//nolint:nestif // TODO
if s.incoming { if s.incoming {
if utils.Hostname(to) != s.domain { 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) s.log.Debug("wrong domain of %s", to)
return smtp.ErrAuthRequired return smtp.ErrAuthRequired
} }

View File

@@ -11,8 +11,8 @@ import (
) )
type Config struct { type Config struct {
Domain string Domains []string
Port string Port string
TLSCert string TLSCert string
TLSKey string TLSKey string
@@ -39,15 +39,15 @@ func NewServer(cfg *Config) *Server {
log := logger.New("smtp/msa.", cfg.LogLevel) log := logger.New("smtp/msa.", cfg.LogLevel)
sender := NewMTA(cfg.LogLevel) sender := NewMTA(cfg.LogLevel)
receiver := &msa{ receiver := &msa{
log: log, log: log,
mta: sender, mta: sender,
bot: cfg.Bot, bot: cfg.Bot,
domain: cfg.Domain, domains: cfg.Domains,
} }
receiver.bot.SetMTA(sender) receiver.bot.SetMTA(sender)
s := smtp.NewServer(receiver) s := smtp.NewServer(receiver)
s.Domain = cfg.Domain s.Domain = cfg.Domains[0]
s.ReadTimeout = 10 * time.Second s.ReadTimeout = 10 * time.Second
s.WriteTimeout = 10 * time.Second s.WriteTimeout = 10 * time.Second
s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024 s.MaxMessageBytes = cfg.MaxSize * 1024 * 1024