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"
"os"
"strings"
"sync"
)
type AliasMap map[string]string
var (
aliasesMutex sync.RWMutex
)
func AliasLoadFile(file string) (AliasMap, error) {
aliasMap := make(AliasMap)
count := 0
@@ -50,3 +55,18 @@ func AliasLoadFile(file string) (AliasMap, error) {
}
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")
allowedRecipStr = flagset.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses")
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")
remotesStr = flagset.String("remotes", "", "Outgoing SMTP servers")
strictSender = flagset.Bool("strict_sender", false, "Use only SMTP servers with Sender matches to From")
@@ -68,12 +68,12 @@ func localAuthRequired() bool {
}
func setupAliases() {
if *aliasesFile != "" {
if *aliasFile != "" {
aliases := make(AliasMap)
aliases, err := AliasLoadFile(*aliasesFile)
aliases, err := AliasLoadFile(*aliasFile)
if err != nil {
log.Fatal().
Str("file", *aliasesFile).
Str("file", *aliasFile).
Err(err).
Msg("cannot load aliases file")
}

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/decke/smtprelay
require (
github.com/DeRuina/timberjack v1.3.9
github.com/chrj/smtpd v0.3.1
github.com/fsnotify/fsnotify v1.9.0
github.com/google/uuid v1.6.0
github.com/peterbourgon/ff/v3 v3.4.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/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
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"
"github.com/chrj/smtpd"
"github.com/fsnotify/fsnotify"
"github.com/google/uuid"
)
@@ -160,6 +161,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
}
// Check for aliases
aliasesMutex.RLock()
for i, recipient := range env.Recipients {
if alias, exists := aliasesList[recipient]; exists {
env.Recipients[i] = alias
@@ -169,6 +171,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
Msg("Recipient address aliased")
}
}
aliasesMutex.RUnlock()
logger := log.With().
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() {
ConfigLoad()
@@ -315,6 +383,9 @@ func main() {
}
}
// Start watching alias file for changes
watchAliasFile()
var servers []*smtpd.Server
// Create a server for each desired listen address