2
0
forked from drew/smtprelay

feat: dynamic reread of aliases

This commit is contained in:
Aivars Sterns
2025-11-05 14:40:28 +02:00
committed by Bernhard Fröhlich
parent 381a9b334b
commit b164ce1387
5 changed files with 98 additions and 4 deletions

View File

@@ -5,10 +5,15 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"sync"
) )
type AliasMap map[string]string type AliasMap map[string]string
var (
aliasesMutex sync.RWMutex
)
func AliasLoadFile(file string) (AliasMap, error) { func AliasLoadFile(file string) (AliasMap, error) {
aliasMap := make(AliasMap) aliasMap := make(AliasMap)
count := 0 count := 0
@@ -50,3 +55,18 @@ func AliasLoadFile(file string) (AliasMap, error) {
} }
return aliasMap, nil return aliasMap, nil
} }
func LoadAliases(filename string) error {
newAliases, err := AliasLoadFile(filename)
if err != nil {
return err
}
aliasesMutex.Lock()
defer aliasesMutex.Unlock()
// Update the aliases map
aliasesList = newAliases
return nil
}

View File

@@ -42,7 +42,7 @@ var (
allowedSenderStr = flagset.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses") allowedSenderStr = flagset.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses")
allowedRecipStr = flagset.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses") allowedRecipStr = flagset.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses")
allowedUsers = flagset.String("allowed_users", "", "Path to file with valid users/passwords") allowedUsers = flagset.String("allowed_users", "", "Path to file with valid users/passwords")
aliasesFile = flagset.String("aliases_file", "", "Path to aliases file") aliasFile = flagset.String("aliases_file", "", "Path to aliases file")
command = flagset.String("command", "", "Path to pipe command") command = flagset.String("command", "", "Path to pipe command")
remotesStr = flagset.String("remotes", "", "Outgoing SMTP servers") remotesStr = flagset.String("remotes", "", "Outgoing SMTP servers")
strictSender = flagset.Bool("strict_sender", false, "Use only SMTP servers with Sender matches to From") strictSender = flagset.Bool("strict_sender", false, "Use only SMTP servers with Sender matches to From")
@@ -68,12 +68,12 @@ func localAuthRequired() bool {
} }
func setupAliases() { func setupAliases() {
if *aliasesFile != "" { if *aliasFile != "" {
aliases := make(AliasMap) aliases := make(AliasMap)
aliases, err := AliasLoadFile(*aliasesFile) aliases, err := AliasLoadFile(*aliasFile)
if err != nil { if err != nil {
log.Fatal(). log.Fatal().
Str("file", *aliasesFile). Str("file", *aliasFile).
Err(err). Err(err).
Msg("cannot load aliases file") Msg("cannot load aliases file")
} }

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/decke/smtprelay
require ( require (
github.com/DeRuina/timberjack v1.3.9 github.com/DeRuina/timberjack v1.3.9
github.com/chrj/smtpd v0.3.1 github.com/chrj/smtpd v0.3.1
github.com/fsnotify/fsnotify v1.9.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/peterbourgon/ff/v3 v3.4.0 github.com/peterbourgon/ff/v3 v3.4.0
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0

2
go.sum
View File

@@ -7,6 +7,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

71
main.go
View File

@@ -13,6 +13,7 @@ import (
"syscall" "syscall"
"github.com/chrj/smtpd" "github.com/chrj/smtpd"
"github.com/fsnotify/fsnotify"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -160,6 +161,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
} }
// Check for aliases // Check for aliases
aliasesMutex.RLock()
for i, recipient := range env.Recipients { for i, recipient := range env.Recipients {
if alias, exists := aliasesList[recipient]; exists { if alias, exists := aliasesList[recipient]; exists {
env.Recipients[i] = alias env.Recipients[i] = alias
@@ -169,6 +171,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
Msg("Recipient address aliased") Msg("Recipient address aliased")
} }
} }
aliasesMutex.RUnlock()
logger := log.With(). logger := log.With().
Str("from", env.Sender). Str("from", env.Sender).
@@ -297,6 +300,71 @@ func getTLSConfig() *tls.Config {
} }
} }
func watchAliasFile() {
if *aliasFile == "" {
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Error().
Err(err).
Msg("failed to create file watcher for alias file")
return
}
go func() {
defer watcher.Close()
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) {
log.Info().
Str("file", event.Name).
Msg("alias file changed, reloading")
err := LoadAliases(*aliasFile)
if err != nil {
log.Error().
Str("file", *aliasFile).
Err(err).
Msg("failed to reload alias file")
} else {
log.Info().
Int("count", len(aliasesList)).
Msg("alias file reloaded successfully")
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Error().
Err(err).
Msg("file watcher error")
}
}
}()
err = watcher.Add(*aliasFile)
if err != nil {
log.Error().
Str("file", *aliasFile).
Err(err).
Msg("failed to watch alias file")
} else {
log.Info().
Str("file", *aliasFile).
Msg("watching alias file for changes")
}
}
func main() { func main() {
ConfigLoad() ConfigLoad()
@@ -315,6 +383,9 @@ func main() {
} }
} }
// Start watching alias file for changes
watchAliasFile()
var servers []*smtpd.Server var servers []*smtpd.Server
// Create a server for each desired listen address // Create a server for each desired listen address