Refactor to migrate from sirupsen/logrus to rs/zerolog and integrate DeRuina/timberjack for logfile rotation

Fixes #228
This commit is contained in:
Bernhard Froehlich
2025-08-03 13:25:52 +02:00
committed by Bernhard Fröhlich
parent 1cb95f0265
commit 17421a539e
5 changed files with 207 additions and 156 deletions

View File

@@ -12,7 +12,6 @@ import (
"time" "time"
"github.com/peterbourgon/ff/v3" "github.com/peterbourgon/ff/v3"
"github.com/sirupsen/logrus"
) )
var ( var (
@@ -70,18 +69,19 @@ func setupAllowedNetworks() {
for _, netstr := range splitstr(*allowedNetsStr, ' ') { for _, netstr := range splitstr(*allowedNetsStr, ' ') {
baseIP, allowedNet, err := net.ParseCIDR(netstr) baseIP, allowedNet, err := net.ParseCIDR(netstr)
if err != nil { if err != nil {
log.WithField("netstr", netstr). log.Fatal().
WithError(err). Str("netstr", netstr).
Fatal("Invalid CIDR notation in allowed_nets") Err(err).
Msg("Invalid CIDR notation in allowed_nets")
} }
// Reject any network specification where any host bits are set, // Reject any network specification where any host bits are set,
// meaning the address refers to a host and not a network. // meaning the address refers to a host and not a network.
if !allowedNet.IP.Equal(baseIP) { if !allowedNet.IP.Equal(baseIP) {
log.WithFields(logrus.Fields{ log.Fatal().
"given_net": netstr, Str("given_net", netstr).
"proper_net": allowedNet, Str("proper_net", allowedNet.String()).
}).Fatal("Invalid network in allowed_nets (host bits set)") Msg("Invalid network in allowed_nets (host bits set)")
} }
allowedNets = append(allowedNets, allowedNet) allowedNets = append(allowedNets, allowedNet)
@@ -94,30 +94,32 @@ func setupAllowedPatterns() {
if *allowedSenderStr != "" { if *allowedSenderStr != "" {
allowedSender, err = regexp.Compile(*allowedSenderStr) allowedSender, err = regexp.Compile(*allowedSenderStr)
if err != nil { if err != nil {
log.WithField("allowed_sender", *allowedSenderStr). log.Fatal().
WithError(err). Str("allowed_sender", *allowedSenderStr).
Fatal("allowed_sender pattern invalid") Err(err).
Msg("allowed_sender pattern invalid")
} }
} }
if *allowedRecipStr != "" { if *allowedRecipStr != "" {
allowedRecipients, err = regexp.Compile(*allowedRecipStr) allowedRecipients, err = regexp.Compile(*allowedRecipStr)
if err != nil { if err != nil {
log.WithField("allowed_recipients", *allowedRecipStr). log.Fatal().
WithError(err). Str("allowed_recipients", *allowedRecipStr).
Fatal("allowed_recipients pattern invalid") Err(err).
Msg("allowed_recipients pattern invalid")
} }
} }
} }
func setupRemotes() { func setupRemotes() {
logger := log.WithField("remotes", *remotesStr) logger := log.With().Str("remotes", *remotesStr).Logger()
if *remotesStr != "" { if *remotesStr != "" {
for _, remoteURL := range strings.Split(*remotesStr, " ") { for _, remoteURL := range strings.Split(*remotesStr, " ") {
r, err := ParseRemote(remoteURL) r, err := ParseRemote(remoteURL)
if err != nil { if err != nil {
logger.Fatal(fmt.Sprintf("error parsing url: '%s': %v", remoteURL, err)) logger.Fatal().Msg(fmt.Sprintf("error parsing url: '%s': %v", remoteURL, err))
} }
remotes = append(remotes, r) remotes = append(remotes, r)
@@ -148,8 +150,9 @@ func setupListeners() {
pa := splitProto(listenAddr) pa := splitProto(listenAddr)
if localAuthRequired() && pa.protocol == "" { if localAuthRequired() && pa.protocol == "" {
log.WithField("address", pa.address). log.Fatal().
Fatal("Local authentication (via allowed_users file) " + Str("address", pa.address).
Msg("Local authentication (via allowed_users file) " +
"not allowed with non-TLS listener") "not allowed with non-TLS listener")
} }
@@ -162,35 +165,41 @@ func setupTimeouts() {
readTimeout, err = time.ParseDuration(*readTimeoutStr) readTimeout, err = time.ParseDuration(*readTimeoutStr)
if err != nil { if err != nil {
log.WithField("read_timeout", *readTimeoutStr). log.Fatal().
WithError(err). Str("read_timeout", *readTimeoutStr).
Fatal("read_timeout duration string invalid") Err(err).
Msg("read_timeout duration string invalid")
} }
if readTimeout.Seconds() < 1 { if readTimeout.Seconds() < 1 {
log.WithField("read_timeout", *readTimeoutStr). log.Fatal().
Fatal("read_timeout less than one second") Str("read_timeout", *readTimeoutStr).
Msg("read_timeout less than one second")
} }
writeTimeout, err = time.ParseDuration(*writeTimeoutStr) writeTimeout, err = time.ParseDuration(*writeTimeoutStr)
if err != nil { if err != nil {
log.WithField("write_timeout", *writeTimeoutStr). log.Fatal().
WithError(err). Str("write_timeout", *writeTimeoutStr).
Fatal("write_timeout duration string invalid") Err(err).
Msg("write_timeout duration string invalid")
} }
if writeTimeout.Seconds() < 1 { if writeTimeout.Seconds() < 1 {
log.WithField("write_timeout", *writeTimeoutStr). log.Fatal().
Fatal("write_timeout less than one second") Str("write_timeout", *writeTimeoutStr).
Msg("write_timeout less than one second")
} }
dataTimeout, err = time.ParseDuration(*dataTimeoutStr) dataTimeout, err = time.ParseDuration(*dataTimeoutStr)
if err != nil { if err != nil {
log.WithField("data_timeout", *dataTimeoutStr). log.Fatal().
WithError(err). Str("data_timeout", *dataTimeoutStr).
Fatal("data_timeout duration string invalid") Err(err).
Msg("data_timeout duration string invalid")
} }
if dataTimeout.Seconds() < 1 { if dataTimeout.Seconds() < 1 {
log.WithField("data_timeout", *dataTimeoutStr). log.Fatal().
Fatal("data_timeout less than one second") Str("data_timeout", *dataTimeoutStr).
Msg("data_timeout less than one second")
} }
} }
@@ -226,7 +235,7 @@ func ConfigLoad() {
} }
if *remotesStr == "" && *command == "" { if *remotesStr == "" && *command == "" {
log.Warn("no remotes or command set; mail will not be forwarded!") log.Warn().Msg("no remotes or command set; mail will not be forwarded!")
} }
setupAllowedNetworks() setupAllowedNetworks()

8
go.mod
View File

@@ -1,19 +1,19 @@
module github.com/decke/smtprelay module github.com/decke/smtprelay
require ( require (
github.com/DeRuina/timberjack v1.3.3
github.com/chrj/smtpd v0.3.1 github.com/chrj/smtpd v0.3.1
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/sirupsen/logrus v1.9.3 github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.40.0 golang.org/x/crypto v0.40.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
go 1.24.3 go 1.24.3

34
go.sum
View File

@@ -1,27 +1,29 @@
github.com/DeRuina/timberjack v1.3.3 h1:R+3oEvJP1wd8iYkGGsnq9e6pHGAXFqizPKReEMj0jvQ=
github.com/DeRuina/timberjack v1.3.3/go.mod h1:pbNOvT1AIUxBqiH/ytL9nNsKrHUrDGJdz7T23y79RRU=
github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE= github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE=
github.com/chrj/smtpd v0.3.1/go.mod h1:JtABvV/LzvLmEIzy0NyDnrfMGOMd8wy5frAokwf6J9Q= github.com/chrj/smtpd v0.3.1/go.mod h1:JtABvV/LzvLmEIzy0NyDnrfMGOMd8wy5frAokwf6J9Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -2,59 +2,84 @@ package main
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"strings"
"time" "time"
"github.com/sirupsen/logrus" "github.com/DeRuina/timberjack"
"github.com/rs/zerolog"
) )
var ( var (
log *logrus.Logger rotator *timberjack.Logger
log *zerolog.Logger
) )
func setupLogger() { func setupLogger() {
log = logrus.New() zerolog.TimeFieldFormat = time.RFC3339
// Handle logfile // Handle logfile
var writer io.Writer
if *logFile == "" { if *logFile == "" {
log.SetOutput(os.Stderr) writer = os.Stderr
} else { } else {
writer, err := os.OpenFile(*logFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) rotator = &timberjack.Logger{
if err != nil { Filename: *logFile,
fmt.Printf("cannot open log file: %s\n", err) MaxSize: 10, // megabytes before rotation
os.Exit(1) MaxBackups: 3,
MaxAge: 30, // days
Compress: true,
BackupTimeFormat: "20060102150405",
} }
writer = rotator
log.SetOutput(writer)
} }
// Handle log_format // Handle log_format
switch *logFormat { switch *logFormat {
case "json": case "json":
log.SetFormatter(&logrus.JSONFormatter{ // zerolog default is JSON
TimestampFormat: time.RFC3339Nano,
DisableHTMLEscape: true,
})
case "plain": case "plain":
log.SetFormatter(&logrus.TextFormatter{ writer = zerolog.ConsoleWriter{
DisableTimestamp: true, Out: writer,
}) NoColor: true,
TimeFormat: "",
FormatTimestamp: func(i interface{}) string {
return "" // avoid default time
},
}
case "", "default": case "", "default":
log.SetFormatter(&logrus.TextFormatter{ writer = zerolog.ConsoleWriter{
FullTimestamp: true, Out: writer,
}) NoColor: true,
TimeFormat: time.RFC3339,
}
case "pretty":
writer = zerolog.ConsoleWriter{
Out: writer,
TimeFormat: time.RFC3339Nano,
}
default: default:
fmt.Fprintf(os.Stderr, "Invalid log_format: %s\n", *logFormat) fmt.Fprintf(os.Stderr, "Invalid log_format: %s\n", *logFormat)
os.Exit(1) os.Exit(1)
} }
// Handle log_level l := zerolog.New(writer).With().Timestamp().Logger()
level, err := logrus.ParseLevel(*logLevel) log = &l
if err != nil {
level = logrus.InfoLevel
log.WithField("given_level", *logLevel). // Handle log_level
Warn("could not parse log level, defaulting to 'info'") level, err := zerolog.ParseLevel(strings.ToLower(*logLevel))
if err != nil {
level = zerolog.InfoLevel
log.Warn().Str("given_level", *logLevel).
Msg("could not parse log level, defaulting to 'info'")
}
zerolog.SetGlobalLevel(level)
}
// Call this on shutdown if you want to close the rotator and stop timers cleanly
func closeLogger() {
if rotator != nil {
rotator.Close()
} }
log.SetLevel(level)
} }

165
main.go
View File

@@ -14,7 +14,6 @@ import (
"github.com/chrj/smtpd" "github.com/chrj/smtpd"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus"
) )
func connectionChecker(peer smtpd.Peer) error { func connectionChecker(peer smtpd.Peer) error {
@@ -32,9 +31,9 @@ func connectionChecker(peer smtpd.Peer) error {
} }
} }
log.WithFields(logrus.Fields{ log.Warn().
"ip": peerIP, Str("ip", peerIP.String()).
}).Warn("Connection refused from address outside of allowed_nets") Msg("Connection refused from address outside of allowed_nets")
return smtpd.Error{Code: 421, Message: "Denied"} return smtpd.Error{Code: 421, Message: "Denied"}
} }
@@ -87,19 +86,21 @@ func senderChecker(peer smtpd.Peer, addr string) error {
user, err := AuthFetch(peer.Username) user, err := AuthFetch(peer.Username)
if err != nil { if err != nil {
// Shouldn't happen: authChecker already validated username+password // Shouldn't happen: authChecker already validated username+password
log.WithFields(logrus.Fields{ log.Warn().
"peer": peer.Addr, Str("peer", peer.Addr.String()).
"username": peer.Username, Str("username", peer.Username).
}).WithError(err).Warn("could not fetch auth user") Err(err).
Msg("could not fetch auth user")
return smtpd.Error{Code: 451, Message: "Bad sender address"} return smtpd.Error{Code: 451, Message: "Bad sender address"}
} }
if !addrAllowed(addr, user.allowedAddresses) { if !addrAllowed(addr, user.allowedAddresses) {
log.WithFields(logrus.Fields{ log.Warn().
"peer": peer.Addr, Str("peer", peer.Addr.String()).
"username": peer.Username, Str("username", peer.Username).
"sender_address": addr, Str("sender_address", addr).
}).Warn("sender address not allowed for authenticated user") Err(err).
Msg("sender address not allowed for authenticated user")
return smtpd.Error{Code: 451, Message: "Bad sender address"} return smtpd.Error{Code: 451, Message: "Bad sender address"}
} }
} }
@@ -114,10 +115,10 @@ func senderChecker(peer smtpd.Peer, addr string) error {
return nil return nil
} }
log.WithFields(logrus.Fields{ log.Warn().
"sender_address": addr, Str("sender_address", addr).
"peer": peer.Addr, Str("peer", peer.Addr.String()).
}).Warn("sender address not allowed by allowed_sender pattern") Msg("sender address not allowed by allowed_sender pattern")
return smtpd.Error{Code: 451, Message: "Bad sender address"} return smtpd.Error{Code: 451, Message: "Bad sender address"}
} }
@@ -132,20 +133,21 @@ func recipientChecker(peer smtpd.Peer, addr string) error {
return nil return nil
} }
log.WithFields(logrus.Fields{ log.Warn().
"peer": peer.Addr, Str("peer", peer.Addr.String()).
"recipient_address": addr, Str("recipient_address", addr).
}).Warn("recipient address not allowed by allowed_recipients pattern") Msg("recipient address not allowed by allowed_recipients pattern")
return smtpd.Error{Code: 451, Message: "Bad recipient address"} return smtpd.Error{Code: 451, Message: "Bad recipient address"}
} }
func authChecker(peer smtpd.Peer, username string, password string) error { func authChecker(peer smtpd.Peer, username string, password string) error {
err := AuthCheckPassword(username, password) err := AuthCheckPassword(username, password)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.Warn().
"peer": peer.Addr, Str("peer", peer.Addr.String()).
"username": username, Str("username", username).
}).WithError(err).Warn("auth error") Err(err).
Msg("auth error")
return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"} return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"}
} }
return nil return nil
@@ -157,12 +159,12 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
peerIP = addr.IP.String() peerIP = addr.IP.String()
} }
logger := log.WithFields(logrus.Fields{ logger := log.With().
"from": env.Sender, Str("from", env.Sender).
"to": env.Recipients, Strs("to", env.Recipients).
"peer": peerIP, Str("peer", peerIP).
"uuid": generateUUID(), Str("uuid", generateUUID()).
}) Logger()
var envRemotes []*Remote var envRemotes []*Remote
@@ -177,14 +179,14 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
} }
if len(envRemotes) == 0 && *command == "" { if len(envRemotes) == 0 && *command == "" {
logger.Warning("no remote_host or command set; discarding mail") logger.Warn().Msg("no remote_host or command set; discarding mail")
return smtpd.Error{Code: 554, Message: "There are no appropriate remote_host or command"} return smtpd.Error{Code: 554, Message: "There are no appropriate remote_host or command"}
} }
env.AddReceivedLine(peer) env.AddReceivedLine(peer)
if *command != "" { if *command != "" {
cmdLogger := logger.WithField("command", *command) cmdLogger := logger.With().Str("command", *command).Logger()
var stdout bytes.Buffer var stdout bytes.Buffer
var stderr bytes.Buffer var stderr bytes.Buffer
@@ -205,16 +207,16 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
cmdLogger.WithError(err).Error(stderr.String()) cmdLogger.Error().Err(err).Msg(stderr.String())
return smtpd.Error{Code: 554, Message: "External command failed"} return smtpd.Error{Code: 554, Message: "External command failed"}
} }
cmdLogger.Info("pipe command successful: " + stdout.String()) cmdLogger.Info().Msg("pipe command successful: " + stdout.String())
} }
for _, remote := range envRemotes { for _, remote := range envRemotes {
logger = logger.WithField("host", remote.Addr) logger = logger.With().Str("host", remote.Addr).Logger()
logger.Info("delivering mail from peer using smarthost") logger.Info().Msg("delivering mail from peer using smarthost")
err := SendMail( err := SendMail(
remote, remote,
@@ -229,21 +231,22 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
case *textproto.Error: case *textproto.Error:
smtpError = smtpd.Error{Code: err.Code, Message: err.Msg} smtpError = smtpd.Error{Code: err.Code, Message: err.Msg}
logger.WithFields(logrus.Fields{ logger.Error().
"err_code": err.Code, Int("err_code", err.Code).
"err_msg": err.Msg, Str("err_msg", err.Msg).
}).Error("delivery failed") Msg("delivery failed")
default: default:
smtpError = smtpd.Error{Code: 421, Message: "Forwarding failed"} smtpError = smtpd.Error{Code: 421, Message: "Forwarding failed"}
logger.WithError(err). logger.Error().
Error("delivery failed") Err(err).
Msg("delivery failed")
} }
return smtpError return smtpError
} }
logger.Debug("delivery successful") logger.Debug().Msg("delivery successful")
} }
return nil return nil
@@ -253,8 +256,9 @@ func generateUUID() string {
uniqueID, err := uuid.NewRandom() uniqueID, err := uuid.NewRandom()
if err != nil { if err != nil {
log.WithError(err). log.Error().
Error("could not generate UUIDv4") Err(err).
Msg("could not generate UUIDv4")
return "" return ""
} }
@@ -280,16 +284,17 @@ func getTLSConfig() *tls.Config {
} }
if *localCert == "" || *localKey == "" { if *localCert == "" || *localKey == "" {
log.WithFields(logrus.Fields{ log.Fatal().
"cert_file": *localCert, Str("cert_file", *localCert).
"key_file": *localKey, Str("key_file", *localKey).
}).Fatal("TLS certificate/key file not defined in config") Msg("TLS certificate/key file not defined in config")
} }
cert, err := tls.LoadX509KeyPair(*localCert, *localKey) cert, err := tls.LoadX509KeyPair(*localCert, *localKey)
if err != nil { if err != nil {
log.WithField("error", err). log.Fatal().
Fatal("cannot load X509 keypair") Err(err).
Msg("cannot load X509 keypair")
} }
return &tls.Config{ return &tls.Config{
@@ -303,16 +308,18 @@ func getTLSConfig() *tls.Config {
func main() { func main() {
ConfigLoad() ConfigLoad()
log.WithField("version", appVersion). log.Debug().
Debug("starting smtprelay") Str("version", appVersion).
Msg("starting smtprelay")
// Load allowed users file // Load allowed users file
if localAuthRequired() { if localAuthRequired() {
err := AuthLoadFile(*allowedUsers) err := AuthLoadFile(*allowedUsers)
if err != nil { if err != nil {
log.WithField("file", *allowedUsers). log.Fatal().
WithError(err). Str("file", *allowedUsers).
Fatal("cannot load allowed users file") Err(err).
Msg("cannot load allowed users file")
} }
} }
@@ -320,7 +327,7 @@ func main() {
// Create a server for each desired listen address // Create a server for each desired listen address
for _, listen := range listenAddrs { for _, listen := range listenAddrs {
logger := log.WithField("address", listen.address) logger := log.With().Str("address", listen.address).Logger()
server := &smtpd.Server{ server := &smtpd.Server{
Hostname: *hostName, Hostname: *hostName,
@@ -346,29 +353,32 @@ func main() {
switch listen.protocol { switch listen.protocol {
case "": case "":
logger.Info("listening on address") logger.Info().Msg("listening on address")
lsnr, err = net.Listen("tcp", listen.address) lsnr, err = net.Listen("tcp", listen.address)
case "starttls": case "starttls":
server.TLSConfig = getTLSConfig() server.TLSConfig = getTLSConfig()
server.ForceTLS = *localForceTLS server.ForceTLS = *localForceTLS
logger.Info("listening on address (STARTTLS)") logger.Info().Msg("listening on address (STARTTLS)")
lsnr, err = net.Listen("tcp", listen.address) lsnr, err = net.Listen("tcp", listen.address)
case "tls": case "tls":
server.TLSConfig = getTLSConfig() server.TLSConfig = getTLSConfig()
logger.Info("listening on address (TLS)") logger.Info().Msg("listening on address (TLS)")
lsnr, err = tls.Listen("tcp", listen.address, server.TLSConfig) lsnr, err = tls.Listen("tcp", listen.address, server.TLSConfig)
default: default:
logger.WithField("protocol", listen.protocol). logger.Fatal().
Fatal("unknown protocol in listen address") Str("protocol", listen.protocol).
Msg("unknown protocol in listen address")
} }
if err != nil { if err != nil {
logger.WithError(err).Fatal("error starting listener") logger.Fatal().
Err(err).
Msg("error starting listener")
} }
servers = append(servers, server) servers = append(servers, server)
@@ -381,27 +391,31 @@ func main() {
// First close the listeners // First close the listeners
for _, server := range servers { for _, server := range servers {
logger := log.WithField("address", server.Address()) logger := log.With().Str("address", server.Address().String()).Logger()
logger.Debug("Shutting down server") logger.Debug().Msg("Shutting down server")
err := server.Shutdown(false) err := server.Shutdown(false)
if err != nil { if err != nil {
logger.WithError(err). logger.Warn().
Warning("Shutdown failed") Err(err).
Msg("Shutdown failed")
} }
} }
// Then wait for the clients to exit // Then wait for the clients to exit
for _, server := range servers { for _, server := range servers {
logger := log.WithField("address", server.Address()) logger := log.With().Str("address", server.Address().String()).Logger()
logger.Debug("Waiting for server") logger.Debug().Msg("Waiting for server")
err := server.Wait() err := server.Wait()
if err != nil { if err != nil {
logger.WithError(err). logger.Warn().
Warning("Wait failed") Err(err).
Msg("Wait failed")
} }
} }
log.Debug("done") log.Debug().Msg("done")
closeLogger()
} }
func handleSignals() { func handleSignals() {
@@ -410,6 +424,7 @@ func handleSignals() {
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
sig := <-sigs sig := <-sigs
log.WithField("signal", sig). log.Info().
Info("shutting down in response to received signal") Str("signal", sig.String()).
Msg("shutting down in response to received signal")
} }