|
@@ -3,16 +3,17 @@ package main
|
|
|
import (
|
|
|
"crypto/tls"
|
|
|
"fmt"
|
|
|
- "io"
|
|
|
- "log"
|
|
|
"net"
|
|
|
"net/smtp"
|
|
|
+ "net/textproto"
|
|
|
"os"
|
|
|
"regexp"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
"github.com/chrj/smtpd"
|
|
|
+ "github.com/google/uuid"
|
|
|
+ "github.com/sirupsen/logrus"
|
|
|
)
|
|
|
|
|
|
func connectionChecker(peer smtpd.Peer) error {
|
|
@@ -20,6 +21,8 @@ func connectionChecker(peer smtpd.Peer) error {
|
|
|
if addr, ok := peer.Addr.(*net.TCPAddr); ok {
|
|
|
peerIP = net.ParseIP(addr.IP.String())
|
|
|
} else {
|
|
|
+ log.WithField("ip", addr.IP).
|
|
|
+ Warn("failed to parse IP")
|
|
|
return smtpd.Error{Code: 421, Message: "Denied"}
|
|
|
}
|
|
|
|
|
@@ -33,7 +36,9 @@ func connectionChecker(peer smtpd.Peer) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- log.Printf("Connection from peer=[%s] denied: Not in allowed_nets\n", peerIP)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "ip": peerIP,
|
|
|
+ }).Warn("Connection refused from address outside of allowed_nets")
|
|
|
return smtpd.Error{Code: 421, Message: "Denied"}
|
|
|
}
|
|
|
|
|
@@ -86,12 +91,19 @@ func senderChecker(peer smtpd.Peer, addr string) error {
|
|
|
user, err := AuthFetch(peer.Username)
|
|
|
if err != nil {
|
|
|
// Shouldn't happen: authChecker already validated username+password
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "peer": peer.Addr,
|
|
|
+ "username": peer.Username,
|
|
|
+ }).WithError(err).Warn("could not fetch auth user")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad sender address"}
|
|
|
}
|
|
|
|
|
|
if !addrAllowed(addr, user.allowedAddresses) {
|
|
|
- log.Printf("Mail from=<%s> not allowed for authenticated user %s (%v)\n",
|
|
|
- addr, peer.Username, peer.Addr)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "peer": peer.Addr,
|
|
|
+ "username": peer.Username,
|
|
|
+ "sender_address": addr,
|
|
|
+ }).Warn("sender address not allowed for authenticated user")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad sender address"}
|
|
|
}
|
|
|
}
|
|
@@ -102,7 +114,9 @@ func senderChecker(peer smtpd.Peer, addr string) error {
|
|
|
|
|
|
re, err := regexp.Compile(*allowedSender)
|
|
|
if err != nil {
|
|
|
- log.Printf("allowed_sender invalid: %v\n", err)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "allowed_sender": *allowedSender,
|
|
|
+ }).WithError(err).Warn("allowed_sender pattern invalid")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad sender address"}
|
|
|
}
|
|
|
|
|
@@ -110,8 +124,10 @@ func senderChecker(peer smtpd.Peer, addr string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- log.Printf("Mail from=<%s> not allowed by allowed_sender pattern for peer %v\n",
|
|
|
- addr, peer.Addr)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "sender_address": addr,
|
|
|
+ "peer": peer.Addr,
|
|
|
+ }).Warn("Sender address not allowed by allowed_sender pattern")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad sender address"}
|
|
|
}
|
|
|
|
|
@@ -122,7 +138,9 @@ func recipientChecker(peer smtpd.Peer, addr string) error {
|
|
|
|
|
|
re, err := regexp.Compile(*allowedRecipients)
|
|
|
if err != nil {
|
|
|
- log.Printf("allowed_recipients invalid: %v\n", err)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "allowed_recipients": *allowedRecipients,
|
|
|
+ }).WithError(err).Warn("allowed_recipients pattern invalid")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad recipient address"}
|
|
|
}
|
|
|
|
|
@@ -130,15 +148,20 @@ func recipientChecker(peer smtpd.Peer, addr string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- log.Printf("Mail to=<%s> not allowed by allowed_recipients pattern for peer %v\n",
|
|
|
- addr, peer.Addr)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "peer": peer.Addr,
|
|
|
+ "recipient_address": addr,
|
|
|
+ }).Warn("recipient address not allowed by allowed_recipients pattern")
|
|
|
return smtpd.Error{Code: 451, Message: "Bad recipient address"}
|
|
|
}
|
|
|
|
|
|
func authChecker(peer smtpd.Peer, username string, password string) error {
|
|
|
err := AuthCheckPassword(username, password)
|
|
|
if err != nil {
|
|
|
- log.Printf("Auth error for peer %v: %v\n", peer.Addr, err)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "peer": peer.Addr,
|
|
|
+ "username": username,
|
|
|
+ }).WithError(err).Warn("auth error")
|
|
|
return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"}
|
|
|
}
|
|
|
return nil
|
|
@@ -150,8 +173,14 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
|
|
|
peerIP = addr.IP.String()
|
|
|
}
|
|
|
|
|
|
- log.Printf("new mail from=<%s> to=%s peer=[%s]\n", env.Sender,
|
|
|
- env.Recipients, peerIP)
|
|
|
+ logger := log.WithFields(logrus.Fields{
|
|
|
+ "from": env.Sender,
|
|
|
+ "to": env.Recipients,
|
|
|
+ "peer": peerIP,
|
|
|
+ "host": *remoteHost,
|
|
|
+ "uuid": generateUUID(),
|
|
|
+ })
|
|
|
+ logger.Info("delivering mail from peer using smarthost")
|
|
|
|
|
|
var auth smtp.Auth
|
|
|
host, _, _ := net.SplitHostPort(*remoteHost)
|
|
@@ -169,8 +198,6 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
|
|
|
|
|
|
env.AddReceivedLine(peer)
|
|
|
|
|
|
- log.Printf("delivering using smarthost %s\n", *remoteHost)
|
|
|
-
|
|
|
var sender string
|
|
|
|
|
|
if *remoteSender == "" {
|
|
@@ -187,15 +214,44 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
|
|
|
env.Data,
|
|
|
)
|
|
|
if err != nil {
|
|
|
- log.Printf("delivery failed: %v\n", err)
|
|
|
- return smtpd.Error{Code: 554, Message: "Forwarding failed"}
|
|
|
- }
|
|
|
+ var smtpError smtpd.Error
|
|
|
+
|
|
|
+ switch err.(type) {
|
|
|
+ case *textproto.Error:
|
|
|
+ err := err.(*textproto.Error)
|
|
|
+ smtpError = smtpd.Error{Code: err.Code, Message: err.Msg}
|
|
|
+
|
|
|
+ logger.WithFields(logrus.Fields{
|
|
|
+ "err_code": err.Code,
|
|
|
+ "err_msg": err.Msg,
|
|
|
+ }).Error("delivery failed")
|
|
|
+ default:
|
|
|
+ smtpError = smtpd.Error{Code: 554, Message: "Forwarding failed"}
|
|
|
|
|
|
- log.Printf("%s delivery successful\n", env.Recipients)
|
|
|
+ logger.WithError(err).
|
|
|
+ Error("delivery failed")
|
|
|
+ }
|
|
|
+
|
|
|
+ return smtpError
|
|
|
+ }
|
|
|
|
|
|
+ logger.Debug("delivery successful")
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func generateUUID() string {
|
|
|
+ uniqueID, err := uuid.NewRandom()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ log.WithError(err).
|
|
|
+ Error("could not generate UUIDv4")
|
|
|
+
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+
|
|
|
+ return uniqueID.String()
|
|
|
+}
|
|
|
+
|
|
|
func getTLSConfig() *tls.Config {
|
|
|
// Ciphersuites as defined in stock Go but without 3DES and RC4
|
|
|
// https://golang.org/src/crypto/tls/cipher_suites.go
|
|
@@ -214,12 +270,16 @@ func getTLSConfig() *tls.Config {
|
|
|
}
|
|
|
|
|
|
if *localCert == "" || *localKey == "" {
|
|
|
- log.Fatal("TLS certificate/key not defined in config")
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "cert_file": *localCert,
|
|
|
+ "key_file": *localKey,
|
|
|
+ }).Fatal("TLS certificate/key file not defined in config")
|
|
|
}
|
|
|
|
|
|
cert, err := tls.LoadX509KeyPair(*localCert, *localKey)
|
|
|
if err != nil {
|
|
|
- log.Fatal(err)
|
|
|
+ log.WithField("error", err).
|
|
|
+ Fatal("cannot load X509 keypair")
|
|
|
}
|
|
|
|
|
|
return &tls.Config{
|
|
@@ -238,21 +298,16 @@ func main() {
|
|
|
os.Exit(0)
|
|
|
}
|
|
|
|
|
|
- if *logFile != "" {
|
|
|
- f, err := os.OpenFile(*logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
|
|
- if err != nil {
|
|
|
- log.Fatalf("Error opening logfile: %v", err)
|
|
|
- }
|
|
|
- defer f.Close()
|
|
|
-
|
|
|
- log.SetOutput(io.MultiWriter(os.Stdout, f))
|
|
|
- }
|
|
|
+ log.WithField("version", appVersion).
|
|
|
+ Debug("starting smtprelay")
|
|
|
|
|
|
// Load allowed users file
|
|
|
if *allowedUsers != "" {
|
|
|
err := AuthLoadFile(*allowedUsers)
|
|
|
if err != nil {
|
|
|
- log.Fatalf("Authentication file: %s\n", err)
|
|
|
+ log.WithField("file", *allowedUsers).
|
|
|
+ WithError(err).
|
|
|
+ Fatal("cannot load allowed users file")
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -275,7 +330,8 @@ func main() {
|
|
|
var err error
|
|
|
|
|
|
if strings.Index(listenAddr, "://") == -1 {
|
|
|
- log.Printf("Listen on %s ...\n", listenAddr)
|
|
|
+ log.WithField("address", listenAddr).
|
|
|
+ Info("listening on address")
|
|
|
|
|
|
lsnr, err = net.Listen("tcp", listenAddr)
|
|
|
} else if strings.HasPrefix(listenAddr, "starttls://") {
|
|
@@ -284,21 +340,26 @@ func main() {
|
|
|
server.TLSConfig = getTLSConfig()
|
|
|
server.ForceTLS = *localForceTLS
|
|
|
|
|
|
- log.Printf("Listen on %s (STARTTLS) ...\n", listenAddr)
|
|
|
+ log.WithField("address", listenAddr).
|
|
|
+ Info("listening on address (STARTTLS)")
|
|
|
lsnr, err = net.Listen("tcp", listenAddr)
|
|
|
} else if strings.HasPrefix(listenAddr, "tls://") {
|
|
|
listenAddr = strings.TrimPrefix(listenAddr, "tls://")
|
|
|
|
|
|
server.TLSConfig = getTLSConfig()
|
|
|
|
|
|
- log.Printf("Listen on %s (TLS) ...\n", listenAddr)
|
|
|
+ log.WithField("address", listenAddr).
|
|
|
+ Info("listening on address (TLS)")
|
|
|
lsnr, err = tls.Listen("tcp", listenAddr, server.TLSConfig)
|
|
|
} else {
|
|
|
- log.Fatal("Unknown protocol in listen address ", listenAddr)
|
|
|
+ log.WithField("address", listenAddr).
|
|
|
+ Fatal("unknown protocol in listen address")
|
|
|
}
|
|
|
|
|
|
if err != nil {
|
|
|
- log.Fatal(err)
|
|
|
+ log.WithFields(logrus.Fields{
|
|
|
+ "address": listenAddr,
|
|
|
+ }).WithError(err).Fatal("error starting listener")
|
|
|
}
|
|
|
defer lsnr.Close()
|
|
|
|