From ab341c697d4def20fc6aee17484b05ab7d14bee5 Mon Sep 17 00:00:00 2001 From: Bernhard Froehlich Date: Sat, 29 Dec 2018 12:24:32 +0000 Subject: [PATCH] Code refactoring and rename package --- README.md | 2 +- auth.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ config.go | 33 ++++++++++++++++++++++++ main.go | 76 +++++++++---------------------------------------------- smtp.go | 2 +- 5 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 auth.go create mode 100644 config.go diff --git a/README.md b/README.md index efbbefc..087cf80 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,5 @@ produces mail. * Authentication support with file (LOGIN, PLAIN) * Enforce encryption for authentication * Forwards all mail to a smarthost (GMail, MailGun or any other SMTP server) -* Small codebase (smtp-proxy ~300 LoC, chrj/smtpd ~1200 LoC) +* Small codebase * IPv6 support diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..219448e --- /dev/null +++ b/auth.go @@ -0,0 +1,68 @@ +package smtpproxy + +import ( + "bufio" + "errors" + "os" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +var ( + filename string +) + +func AuthLoadFile(file string) error { + f, err := os.Open(file) + if err != nil { + return err + } + f.Close() + + filename = file + return nil +} + +func AuthReady() bool { + return (filename == "") +} + +func AuthFetch(username string) (string, string, error) { + if ! AuthReady() { + return "", "", errors.New("Authentication file not specified. Call LoadFile() first") + } + + file, err := os.Open(filename) + if err != nil { + return "", "", err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + parts := strings.Fields(scanner.Text()) + + if len(parts) != 3 { + continue + } + + if strings.ToLower(username) == strings.ToLower(parts[0]) { + return parts[1], parts[2], nil + } + } + + return "", "", errors.New("User not found") +} + +func AuthCheckPassword(username string, secret string) error { + hash, _, err := AuthFetch(username) + if err != nil { + return err + } + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(secret)) == nil { + return nil + } + return errors.New("Password invalid") +} + diff --git a/config.go b/config.go new file mode 100644 index 0000000..3ed5b10 --- /dev/null +++ b/config.go @@ -0,0 +1,33 @@ +package smtpproxy + +import ( + "flag" + + "github.com/vharitonsky/iniflags" +) + +const ( + VERSION = "1.1.1-dev" +) + +var ( + logFile = flag.String("logfile", "/var/log/smtpd-proxy.log", "Path to logfile") + hostName = flag.String("hostname", "localhost.localdomain", "Server hostname") + welcomeMsg = flag.String("welcome_msg", "", "Welcome message for SMTP session") + listen = flag.String("listen", "127.0.0.1:25 [::1]:25", "Address and port to listen for incoming SMTP") + localCert = flag.String("local_cert", "", "SSL certificate for STARTTLS/TLS") + localKey = flag.String("local_key", "", "SSL private key for STARTTLS/TLS") + localForceTLS = flag.Bool("local_forcetls", false, "Force STARTTLS (needs local_cert and local_key)") + allowedNets = flag.String("allowed_nets", "127.0.0.1/8 ::1/128", "Networks allowed to send mails") + allowedSender = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail adresses") + allowedRecipients = flag.String("allowed_recipients", "", "Regular expression for valid TO EMail adresses") + allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords") + remoteHost = flag.String("remote_host", "smtp.gmail.com:587", "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") + versionInfo= flag.Bool("version", false, "Show version information") +) + +func ConfigLoad() { + iniflags.Parse() +} diff --git a/main.go b/main.go index 1469b27..2063856 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,7 @@ -package main +package smtpproxy import ( - "bufio" "crypto/tls" - "flag" "fmt" "io" "log" @@ -15,30 +13,6 @@ import ( "time" "github.com/chrj/smtpd" - "github.com/vharitonsky/iniflags" - "golang.org/x/crypto/bcrypt" -) - -const ( - VERSION = "1.1.1-dev" -) - -var ( - logFile = flag.String("logfile", "/var/log/smtpd-proxy.log", "Path to logfile") - hostName = flag.String("hostname", "localhost.localdomain", "Server hostname") - welcomeMsg = flag.String("welcome_msg", "", "Welcome message for SMTP session") - listen = flag.String("listen", "127.0.0.1:25 [::1]:25", "Address and port to listen for incoming SMTP") - localCert = flag.String("local_cert", "", "SSL certificate for STARTTLS/TLS") - localKey = flag.String("local_key", "", "SSL private key for STARTTLS/TLS") - localForceTLS = flag.Bool("local_forcetls", false, "Force STARTTLS (needs local_cert and local_key)") - allowedNets = flag.String("allowed_nets", "127.0.0.1/8 ::1/128", "Networks allowed to send mails") - allowedSender = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail adresses") - allowedRecipients = flag.String("allowed_recipients", "", "Regular expression for valid TO EMail adresses") - allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords") - remoteHost = flag.String("remote_host", "smtp.gmail.com:587", "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") - versionInfo= flag.Bool("version", false, "Show version information") ) func connectionChecker(peer smtpd.Peer) error { @@ -65,26 +39,13 @@ func connectionChecker(peer smtpd.Peer) error { func senderChecker(peer smtpd.Peer, addr string) error { // check sender address from auth file if user is authenticated if *allowedUsers != "" && peer.Username != "" { - file, err := os.Open(*allowedUsers) + _, email, err := AuthFetch(peer.Username) if err != nil { - log.Printf("User file not found %v", err) return smtpd.Error{Code: 451, Message: "Bad sender address"} } - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - parts := strings.Fields(scanner.Text()) - - if len(parts) != 3 { - continue - } - - if peer.Username == parts[0] { - if strings.ToLower(addr) != strings.ToLower(parts[2]) { - return smtpd.Error{Code: 451, Message: "Bad sender address"} - } - } + if strings.ToLower(addr) != strings.ToLower(email) { + return smtpd.Error{Code: 451, Message: "Bad sender address"} } } @@ -124,29 +85,11 @@ func recipientChecker(peer smtpd.Peer, addr string) error { } func authChecker(peer smtpd.Peer, username string, password string) error { - file, err := os.Open(*allowedUsers) + err := AuthCheckPassword(username, password) if err != nil { - log.Printf("User file not found %v", err) return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"} } - defer file.Close() - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - parts := strings.Fields(scanner.Text()) - - if len(parts) != 2 { - continue - } - - if username == parts[0] { - if bcrypt.CompareHashAndPassword([]byte(parts[1]), []byte(password)) == nil { - return nil - } - } - } - - return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"} + return nil } func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { @@ -192,7 +135,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { func main() { - iniflags.Parse() + ConfigLoad() if *versionInfo { fmt.Printf("smtpd-proxy/%s\n", VERSION) @@ -224,6 +167,11 @@ func main() { } if *allowedUsers != "" { + err := AuthLoadFile(*allowedUsers) + if err != nil { + log.Fatalf("Authentication file: %s\n", err) + } + server.Authenticator = authChecker } diff --git a/smtp.go b/smtp.go index c928f5d..99d74d3 100644 --- a/smtp.go +++ b/smtp.go @@ -13,7 +13,7 @@ // Some external packages provide more functionality. See: // // https://godoc.org/?q=smtp -package main +package smtpproxy import ( "crypto/tls"