add !pm relay - per-mailbox relay config

This commit is contained in:
Aine
2024-05-02 11:28:37 +03:00
parent 6a63e44bfc
commit ea1533acae
13 changed files with 135 additions and 32 deletions

View File

@@ -3,6 +3,7 @@ package bot
import (
"context"
"fmt"
"net/url"
"regexp"
"sync"
@@ -36,7 +37,7 @@ type Bot struct {
commands commandList
rooms sync.Map
proxies []string
sendmail func(string, string, string) error
sendmail func(string, string, string, *url.URL) error
psdc *psd.Client
cfg *config.Manager
log *zerolog.Logger

View File

@@ -103,6 +103,12 @@ func (b *Bot) initCommands() commandList {
description: "Get or set SMTP password of the room's mailbox",
allowed: b.allowOwner,
},
{
key: config.RoomRelay,
description: "Configure SMTP Relay for that mailbox, format: `smtp://user:pass@host:port`",
sanitizer: utils.SanitizeURL,
allowed: b.allowOwner,
},
{allowed: b.allowOwner, description: "mailbox options"}, // delimiter
{
key: config.RoomAutoreply,
@@ -630,7 +636,7 @@ func (b *Bot) runSendCommand(ctx context.Context, cfg config.Room, tos []string,
b.lp.SendNotice(ctx, evt.RoomID, "email body is empty", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
return
}
queued, err := b.Sendmail(ctx, evt.ID, from, to, data)
queued, err := b.Sendmail(ctx, evt.ID, from, to, data, cfg.Relay())
if queued {
b.log.Warn().Err(err).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, evt.ID, to, eml, cfg)

View File

@@ -51,6 +51,8 @@ func (b *Bot) handleOption(ctx context.Context, cmd []string) {
b.setMailbox(ctx, cmd[1])
case config.RoomPassword:
b.setPassword(ctx)
case config.RoomRelay:
b.setRelay(ctx)
default:
b.setOption(ctx, cmd[0], cmd[1])
}
@@ -152,6 +154,25 @@ func (b *Bot) setPassword(ctx context.Context) {
b.lp.SendNotice(ctx, evt.RoomID, "SMTP password has been set", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
}
func (b *Bot) setRelay(ctx context.Context) {
evt := eventFromContext(ctx)
cfg, err := b.cfg.GetRoom(ctx, evt.RoomID)
if err != nil {
b.Error(ctx, "failed to retrieve settings: %v", err)
return
}
value := b.parseCommand(evt.Content.AsMessage().Body, false)[1] // get original value, without forced lower case
cfg.Set(config.RoomRelay, value)
err = b.cfg.SetRoom(ctx, evt.RoomID, cfg)
if err != nil {
b.Error(ctx, "cannot update settings: %v", err)
return
}
b.lp.SendNotice(ctx, evt.RoomID, "Relay config has been set", linkpearl.RelatesTo(evt.ID, cfg.NoThreads()))
}
func (b *Bot) setOption(ctx context.Context, name, value string) {
cmd := b.commands.get(name)
if cmd != nil && cmd.sanitizer != nil {

View File

@@ -1,8 +1,11 @@
package config
import (
"fmt"
"net/url"
"strings"
"gitlab.com/etke.cc/go/healthchecks/v2"
"gitlab.com/etke.cc/postmoogle/email"
"gitlab.com/etke.cc/postmoogle/utils"
)
@@ -21,6 +24,7 @@ const (
RoomPassword = "password"
RoomSignature = "signature"
RoomAutoreply = "autoreply"
RoomRelay = "relay"
RoomThreadify = "threadify"
RoomStripify = "stripify"
@@ -69,6 +73,20 @@ func (s Room) Active() bool {
return utils.Bool(s.Get(RoomActive))
}
// Relay returns the SMTP Relay configuration in a manner of URL: smtp://user:pass@host:port
func (s Room) Relay() *url.URL {
relay := s.Get(RoomRelay)
if relay == "" {
return nil
}
u, err := url.Parse(relay)
if err != nil {
healthchecks.Global().Fail(strings.NewReader(fmt.Sprintf("cannot parse relay URL %q: %v", relay, err)))
return nil
}
return u
}
func (s Room) Password() string {
return s.Get(RoomPassword)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
@@ -36,7 +37,7 @@ const (
var ErrNoRoom = errors.New("room not found")
// SetSendmail sets mail sending func to the bot
func (b *Bot) SetSendmail(sendmail func(string, string, string) error) {
func (b *Bot) SetSendmail(sendmail func(string, string, string, *url.URL) error) {
b.sendmail = sendmail
b.q.SetSendmail(sendmail)
}
@@ -60,14 +61,14 @@ func (b *Bot) shouldQueue(msg string) bool {
// Sendmail tries to send email immediately, but if it gets 4xx error (greylisting),
// the email will be added to the queue and retried several times after that
func (b *Bot) Sendmail(ctx context.Context, eventID id.EventID, from, to, data string) (bool, error) {
func (b *Bot) Sendmail(ctx context.Context, eventID id.EventID, from, to, data string, relayOverride *url.URL) (bool, error) {
log := b.log.With().Str("from", from).Str("to", to).Str("eventID", eventID.String()).Logger()
log.Info().Msg("attempting to deliver email")
err := b.sendmail(from, to, data)
err := b.sendmail(from, to, data, relayOverride)
if err != nil {
if b.shouldQueue(err.Error()) {
log.Info().Err(err).Msg("email has been added to the queue")
return true, b.q.Add(ctx, eventID.String(), from, to, data)
return true, b.q.Add(ctx, eventID.String(), from, to, data, relayOverride)
}
log.Warn().Err(err).Msg("email delivery failed")
return false, err
@@ -82,6 +83,16 @@ func (b *Bot) GetDKIMprivkey(ctx context.Context) string {
return b.cfg.GetBot(ctx).DKIMPrivateKey()
}
// GetRelayConfig returns relay config for specific room (mailbox) if set
func (b *Bot) GetRelayConfig(ctx context.Context, roomID id.RoomID) *url.URL {
cfg, err := b.cfg.GetRoom(ctx, roomID)
if err != nil {
b.log.Error().Err(err).Str("room_id", roomID.String()).Msg("cannot get room config")
return nil
}
return cfg.Relay()
}
func (b *Bot) getMapping(mailbox string) (id.RoomID, bool) {
v, ok := b.rooms.Load(mailbox)
if !ok {
@@ -277,7 +288,7 @@ func (b *Bot) sendAutoreply(ctx context.Context, roomID id.RoomID, threadID id.E
ctx = newContext(ctx, threadEvt)
recipients := meta.Recipients
for _, to := range recipients {
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data)
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data, cfg.Relay())
if queued {
b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg, "Autoreply has been sent to "+to+" (queued)")
@@ -361,7 +372,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
var queued bool
recipients := meta.Recipients
for _, to := range recipients {
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data)
queued, err = b.Sendmail(ctx, evt.ID, meta.From, to, data, cfg.Relay())
if queued {
b.log.Info().Err(err).Str("from", meta.From).Str("to", to).Msg("email has been queued")
b.saveSentMetadata(ctx, queued, meta.ThreadID, to, eml, cfg)

View File

@@ -2,6 +2,7 @@ package queue
import (
"context"
"net/url"
"github.com/rs/zerolog"
"gitlab.com/etke.cc/linkpearl"
@@ -22,7 +23,7 @@ type Queue struct {
lp *linkpearl.Linkpearl
cfg *config.Manager
log *zerolog.Logger
sendmail func(string, string, string) error
sendmail func(string, string, string, *url.URL) error
}
// New queue
@@ -36,7 +37,7 @@ func New(lp *linkpearl.Linkpearl, cfg *config.Manager, log *zerolog.Logger) *Que
}
// SetSendmail func
func (q *Queue) SetSendmail(function func(string, string, string) error) {
func (q *Queue) SetSendmail(function func(string, string, string, *url.URL) error) {
q.sendmail = function
}

View File

@@ -2,14 +2,20 @@ package queue
import (
"context"
"net/url"
"strconv"
)
// Add to queue
func (q *Queue) Add(ctx context.Context, id, from, to, data string) error {
func (q *Queue) Add(ctx context.Context, id, from, to, data string, relayOverride ...*url.URL) error {
itemkey := acQueueKey + "." + id
relay := ""
if len(relayOverride) > 0 {
relay = relayOverride[0].String()
}
item := map[string]string{
"attempts": "0",
"relay": relay,
"data": data,
"from": from,
"to": to,
@@ -84,7 +90,12 @@ func (q *Queue) try(ctx context.Context, itemkey string, maxRetries int) bool {
return true
}
err = q.sendmail(item["from"], item["to"], item["data"])
var relayOverride *url.URL
if item["relay"] != "" {
relayOverride, _ = url.Parse(item["relay"]) //nolint:errcheck // doesn't matter
}
err = q.sendmail(item["from"], item["to"], item["data"], relayOverride)
if err == nil {
q.log.Info().Str("id", itemkey).Msg("email from queue was delivered")
return true