Merge branch 'multidomain' into 'main'
initial, rought, not-user-friendly support for multi-domain setup See merge request etke.cc/postmoogle!36
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
bot/bot.go
16
bot/bot.go
@@ -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 {
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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]))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
10
smtp/msa.go
10
smtp/msa.go
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user