From a896ab284750e1ae4b86b83dd74afb293cd6a3b4 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 14 Mar 2021 12:20:48 -0400 Subject: [PATCH 1/5] Move compilation of allowed_sender to ConfigLoad() This has several benefits: - Configuration errors are caught at startup rather than upon a connection - senderChecker() has less work to do for each connection --- config.go | 19 ++++++++++++++++++- main.go | 16 +++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/config.go b/config.go index 36f32e5..5a29ba5 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package main import ( "flag" "net" + "regexp" "github.com/vharitonsky/iniflags" "github.com/sirupsen/logrus" @@ -25,7 +26,8 @@ var ( localForceTLS = flag.Bool("local_forcetls", false, "Force STARTTLS (needs local_cert and local_key)") allowedNetsStr = flag.String("allowed_nets", "127.0.0.0/8 ::1/128", "Networks allowed to send mails") allowedNets = []*net.IPNet{} - allowedSender = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses") + allowedSenderStr = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses") + allowedSender *regexp.Regexp allowedRecipients = flag.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses") allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords") remoteHost = flag.String("remote_host", "", "Outgoing SMTP server") @@ -59,6 +61,20 @@ func setupAllowedNetworks() { } } +func setupAllowedSender() { + if (*allowedSenderStr == "") { + return + } + + var err error + allowedSender, err = regexp.Compile(*allowedSenderStr) + if err != nil { + log.WithField("allowed_sender", *allowedSenderStr). + WithError(err). + Fatal("allowed_sender pattern invalid") + } +} + func ConfigLoad() { iniflags.Parse() @@ -70,4 +86,5 @@ func ConfigLoad() { } setupAllowedNetworks() + setupAllowedSender() } diff --git a/main.go b/main.go index cbd5993..c0e8b64 100644 --- a/main.go +++ b/main.go @@ -103,26 +103,20 @@ func senderChecker(peer smtpd.Peer, addr string) error { } } - if *allowedSender == "" { + if allowedSender == nil { + // Any sender is permitted return nil } - re, err := regexp.Compile(*allowedSender) - if err != nil { - log.WithFields(logrus.Fields{ - "allowed_sender": *allowedSender, - }).WithError(err).Warn("allowed_sender pattern invalid") - return smtpd.Error{Code: 451, Message: "Bad sender address"} - } - - if re.MatchString(addr) { + if allowedSender.MatchString(addr) { + // Permitted by regex return nil } log.WithFields(logrus.Fields{ "sender_address": addr, "peer": peer.Addr, - }).Warn("Sender address not allowed by allowed_sender pattern") + }).Warn("sender address not allowed by allowed_sender pattern") return smtpd.Error{Code: 451, Message: "Bad sender address"} } From 7c0ba340252b2e5777e9deb3fd78653b7b0a1442 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 14 Mar 2021 12:26:40 -0400 Subject: [PATCH 2/5] Move compilation of allowed_recipients to ConfigLoad() This has several benefits: - Configuration errors are caught at startup rather than upon a connection - recipientChecker() has less work to do for each connection --- config.go | 31 ++++++++++++++++++++----------- main.go | 15 ++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/config.go b/config.go index 5a29ba5..a1bdc53 100644 --- a/config.go +++ b/config.go @@ -28,7 +28,8 @@ var ( allowedNets = []*net.IPNet{} allowedSenderStr = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses") allowedSender *regexp.Regexp - allowedRecipients = flag.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses") + allowedRecipStr = flag.String("allowed_recipients", "", "Regular expression for valid TO EMail addresses") + allowedRecipients *regexp.Regexp allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords") remoteHost = flag.String("remote_host", "", "Outgoing SMTP server") remoteUser = flag.String("remote_user", "", "Username for authentication on outgoing SMTP server") @@ -61,17 +62,25 @@ func setupAllowedNetworks() { } } -func setupAllowedSender() { - if (*allowedSenderStr == "") { - return +func setupAllowedPatterns() { + var err error + + if (*allowedSenderStr != "") { + allowedSender, err = regexp.Compile(*allowedSenderStr) + if err != nil { + log.WithField("allowed_sender", *allowedSenderStr). + WithError(err). + Fatal("allowed_sender pattern invalid") + } } - var err error - allowedSender, err = regexp.Compile(*allowedSenderStr) - if err != nil { - log.WithField("allowed_sender", *allowedSenderStr). - WithError(err). - Fatal("allowed_sender pattern invalid") + if (*allowedRecipStr != "") { + allowedRecipients, err = regexp.Compile(*allowedRecipStr) + if err != nil { + log.WithField("allowed_recipients", *allowedRecipStr). + WithError(err). + Fatal("allowed_recipients pattern invalid") + } } } @@ -86,5 +95,5 @@ func ConfigLoad() { } setupAllowedNetworks() - setupAllowedSender() + setupAllowedPatterns() } diff --git a/main.go b/main.go index c0e8b64..5c960ef 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "net/smtp" "net/textproto" "os" - "regexp" "strings" "time" @@ -121,19 +120,13 @@ func senderChecker(peer smtpd.Peer, addr string) error { } func recipientChecker(peer smtpd.Peer, addr string) error { - if *allowedRecipients == "" { + if allowedRecipients == nil { + // Any recipient is permitted return nil } - re, err := regexp.Compile(*allowedRecipients) - if err != nil { - log.WithFields(logrus.Fields{ - "allowed_recipients": *allowedRecipients, - }).WithError(err).Warn("allowed_recipients pattern invalid") - return smtpd.Error{Code: 451, Message: "Bad recipient address"} - } - - if re.MatchString(addr) { + if allowedRecipients.MatchString(addr) { + // Permitted by regex return nil } From 76ef135d33371a1ad594bccbea684699b3a782c6 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 14 Mar 2021 12:36:34 -0400 Subject: [PATCH 3/5] Clarify allowed_sender/allowed_recipient empty string behavior --- smtprelay.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smtprelay.ini b/smtprelay.ini index 0b97399..d0f3d4e 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -35,10 +35,12 @@ ;allowed_nets = 127.0.0.0/8 ::1/128 ; Regular expression for valid FROM EMail addresses +; If set to "", then any sender is permitted. ; Example: ^(.*)@localhost.localdomain$ ;allowed_sender = ; Regular expression for valid TO EMail addresses +; If set to "", then any recipient is permitted. ; Example: ^(.*)@localhost.localdomain$ ;allowed_recipients = From 22ef0c2ee6261145176b964ffa38d70c5c1bcd02 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Sun, 14 Mar 2021 14:06:21 -0400 Subject: [PATCH 4/5] Move SMTP auth setup to ConfigLoad() This has several benefits: - Configuration errors are caught at startup rather than upon a connection - mailHandler() has less work to do for each connection Rather than relying on remote_user and remote_pass to control whether authentication is used, introduce an explicit "none" type for remote_auth, and make that the default. (This is effectively the same default behavior since remote_user and remote_pass default to empty.) Also, we are in a better position to more thoroughly check for configuration errors or mismatches: - If remote_auth is given, remote_user and remote_pass are required. - If remote_auth is given, remote_host is also required (because it makes no sense to say we're going to authenticate if we have no server to which to authenticate.) - If remote_user or remote_pass are given, remote_auth cannot be "none". --- config.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- main.go | 17 +---------------- smtprelay.ini | 4 ++-- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/config.go b/config.go index a1bdc53..fdc7394 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "flag" "net" "regexp" + "net/smtp" "github.com/vharitonsky/iniflags" "github.com/sirupsen/logrus" @@ -34,7 +35,8 @@ var ( remoteHost = flag.String("remote_host", "", "Outgoing SMTP server") remoteUser = flag.String("remote_user", "", "Username for authentication on outgoing SMTP server") remotePass = flag.String("remote_pass", "", "Password for authentication on outgoing SMTP server") - remoteAuth = flag.String("remote_auth", "plain", "Auth method on outgoing SMTP server (plain, login)") + remoteAuthStr = flag.String("remote_auth", "none", "Auth method on outgoing SMTP server (none, plain, login)") + remoteAuth smtp.Auth remoteSender = flag.String("remote_sender", "", "Sender e-mail address on outgoing SMTP server") versionInfo = flag.Bool("version", false, "Show version information") ) @@ -84,6 +86,51 @@ func setupAllowedPatterns() { } } + +func setupRemoteAuth() { + logger := log.WithField("remote_auth", *remoteAuthStr) + + // Remote auth disabled? + switch *remoteAuthStr { + case "", "none": + if *remoteUser != "" { + logger.Fatal("remote_user given but not used") + } + if *remotePass != "" { + logger.Fatal("remote_pass given but not used") + } + + // No auth; use empty default + return + } + + // We need a username, password, and remote host + if *remoteUser == "" { + logger.Fatal("remote_user required but empty") + } + if *remotePass == "" { + logger.Fatal("remote_pass required but empty") + } + if *remoteHost == "" { + logger.Fatal("remote_auth without remote_host is pointless") + } + + host, _, err := net.SplitHostPort(*remoteHost) + if err != nil { + logger.WithField("remote_host", *remoteHost). + Fatal("Invalid remote_host") + } + + switch *remoteAuthStr { + case "plain": + remoteAuth = smtp.PlainAuth("", *remoteUser, *remotePass, host) + case "login": + remoteAuth = LoginAuth(*remoteUser, *remotePass) + default: + logger.Fatal("Invalid remote_auth type") + } +} + func ConfigLoad() { iniflags.Parse() @@ -96,4 +143,5 @@ func ConfigLoad() { setupAllowedNetworks() setupAllowedPatterns() + setupRemoteAuth() } diff --git a/main.go b/main.go index 5c960ef..556647d 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "fmt" "net" - "net/smtp" "net/textproto" "os" "strings" @@ -170,20 +169,6 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { logger.Info("delivering mail from peer using smarthost") - var auth smtp.Auth - host, _, _ := net.SplitHostPort(*remoteHost) - - if *remoteUser != "" && *remotePass != "" { - switch *remoteAuth { - case "plain": - auth = smtp.PlainAuth("", *remoteUser, *remotePass, host) - case "login": - auth = LoginAuth(*remoteUser, *remotePass) - default: - return smtpd.Error{Code: 530, Message: "Authentication method not supported"} - } - } - env.AddReceivedLine(peer) var sender string @@ -196,7 +181,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { err := SendMail( *remoteHost, - auth, + remoteAuth, sender, env.Recipients, env.Data, diff --git a/smtprelay.ini b/smtprelay.ini index d0f3d4e..b47f103 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -73,8 +73,8 @@ ;remote_pass = ; Authentication method on outgoing SMTP server -; (plain, login) -;remote_auth = plain +; (none, plain, login) +;remote_auth = none ; Sender e-mail address on outgoing SMTP server ;remote_sender = From 3debf4127d364764789eae1bee9564080219a789 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Wed, 31 Mar 2021 17:18:13 -0400 Subject: [PATCH 5/5] Adjust remote auth disabled check syntax --- config.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.go b/config.go index fdc7394..bdcb54e 100644 --- a/config.go +++ b/config.go @@ -91,8 +91,7 @@ func setupRemoteAuth() { logger := log.WithField("remote_auth", *remoteAuthStr) // Remote auth disabled? - switch *remoteAuthStr { - case "", "none": + if *remoteAuthStr == "" || *remoteAuthStr == "none" { if *remoteUser != "" { logger.Fatal("remote_user given but not used") }