Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for any network + improve logs & errors #4

Merged
merged 1 commit into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"flag"
"log"
"os"

"github.com/vharitonsky/iniflags"
)
Expand All @@ -11,19 +13,23 @@ const (
)

var (
logFile = flag.String("logfile", "/var/log/smtprelay.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 addresses")
logFile = flag.String("logfile", "/var/log/smtprelay.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)")
// set allowed_nets to "" to allow any Networks (i.e disable network check)
allowedNets = flag.String("allowed_nets", "127.0.0.1/8 ::1/128", "Networks allowed to send mails, use \"\" to disable")
// set "" to allow any sender (i.e disable sender check)
allowedSender = flag.String("allowed_sender", "", "Regular expression for valid FROM EMail addresses")
// set "" to allow any recipients (i.e disable recipients check)
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", "smtp.gmail.com:587", "Outgoing SMTP server")
remoteUser = flag.String("remote_user", "", "Username for authentication on outgoing SMTP server")
// set "" to allow any user (i.e disable users check)
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")
// REMOTE_PASS env var can also be used to set remotePass
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)")
Expand All @@ -32,5 +38,17 @@ var (
)

func ConfigLoad() {
log.Printf("loading config...")
iniflags.Parse()

// if remotePass is not set, try reading it from env var
if *remotePass == "" {
log.Printf("remote_pass not set, try REMOTE_PASS env var")
*remotePass = os.Getenv("REMOTE_PASS")
if *remotePass != "" {
log.Printf("found data in REMOTE_PASS env var")
} else {
log.Printf("no data found in REMOTE_PASS env var")
}
}
}
53 changes: 31 additions & 22 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ import (
)

func connectionChecker(peer smtpd.Peer) error {
if *allowedSender == "" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this have been *allowedNets?

This will be fixed in upstream decke/smtprelay#18.

// disable network check, and allow any peer
return nil
}

var peerIP net.IP
if addr, ok := peer.Addr.(*net.TCPAddr); ok {
peerIP = net.ParseIP(addr.IP.String())
} else {
return smtpd.Error{Code: 421, Message: "Denied"}
log.Printf("Denied - failed to parseIP")
return smtpd.Error{Code: 421, Message: "Denied - failed to parse IP"}
}

nets := strings.Split(*allowedNets, " ")
Expand All @@ -33,55 +39,62 @@ func connectionChecker(peer smtpd.Peer) error {
}
}

return smtpd.Error{Code: 421, Message: "Denied"}
log.Printf("IP out of allowed network range, peerIP:[%s]", peerIP)
return smtpd.Error{Code: 421, Message: "Denied - IP out of allowed network range"}
}

func senderChecker(peer smtpd.Peer, addr string) error {
if *allowedSender == "" {
// disable sender check, allow anyone to send mail
return nil
}
Comment on lines +47 to +50

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the motivation for this was more correctly handled in upstream decke/smtprelay#7.


// check sender address from auth file if user is authenticated
if *allowedUsers != "" && peer.Username != "" {
_, email, err := AuthFetch(peer.Username)
if err != nil {
return smtpd.Error{Code: 451, Message: "Bad sender address"}
log.Printf("sender address not allowed")
return smtpd.Error{Code: 451, Message: "sender address not allowed"}
}

if strings.ToLower(addr) != strings.ToLower(email) {
return smtpd.Error{Code: 451, Message: "Bad sender address"}
log.Printf("sender address not allowed")
return smtpd.Error{Code: 451, Message: "sender address not allowed"}
}
}

if *allowedSender == "" {
return nil
}

re, err := regexp.Compile(*allowedSender)
if err != nil {
log.Printf("allowed_sender invalid: %v\n", err)
return smtpd.Error{Code: 451, Message: "Bad sender address"}
return smtpd.Error{Code: 451, Message: "sender address not allowed"}
}

if re.MatchString(addr) {
return nil
}

return smtpd.Error{Code: 451, Message: "Bad sender address"}
log.Printf("sender address not allowed")
return smtpd.Error{Code: 451, Message: "sender address not allowed"}
}

func recipientChecker(peer smtpd.Peer, addr string) error {
if *allowedRecipients == "" {
// allow any recipient, disable recipient check
return nil
}

re, err := regexp.Compile(*allowedRecipients)
if err != nil {
log.Printf("allowed_recipients invalid: %v\n", err)
return smtpd.Error{Code: 451, Message: "Bad recipient address"}
return smtpd.Error{Code: 451, Message: "Invalid recipient address"}
}

if re.MatchString(addr) {
return nil
}

return smtpd.Error{Code: 451, Message: "Bad recipient address"}
log.Printf("Invalid recipient address, addr: %s", addr)
return smtpd.Error{Code: 451, Message: "Invalid recipient address"}
}

func authChecker(peer smtpd.Peer, username string, password string) error {
Expand All @@ -105,15 +118,6 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
var auth smtp.Auth
host, _, _ := net.SplitHostPort(*remoteHost)

// if remotePass is not set, try reading it from env var
if *remotePass == "" {
log.Printf("attempting to read remote pass from REMOTE_PASS env var")
*remotePass = os.Getenv("REMOTE_PASS")
if *remotePass != "" {
log.Printf("found data in REMOTE_PASS env var")
}
}

if *remoteUser != "" && *remotePass != "" {
switch *remoteAuth {
case "plain":
Expand Down Expand Up @@ -155,7 +159,7 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
}

func main() {
// Ciphersuites as defined in stock Go but without 3DES and RC4
// Cipher suites as defined in stock Go but without 3DES and RC4
// https://golang.org/src/crypto/tls/cipher_suites.go
var tlsCipherSuites = []uint16{
tls.TLS_AES_128_GCM_SHA256,
Expand Down Expand Up @@ -187,6 +191,9 @@ func main() {
os.Exit(0)
}

// print version on start
log.Printf("starting smtprelay, version: %s\n", VERSION)

if *logFile != "" {
f, err := os.OpenFile(*logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
Expand All @@ -202,6 +209,7 @@ func main() {
for i := range listeners {
listener := listeners[i]

// TODO: expose smtpd config options (timeouts, message size, and recipients)
server := &smtpd.Server{
Hostname: *hostName,
WelcomeMessage: *welcomeMsg,
Expand Down Expand Up @@ -284,6 +292,7 @@ func main() {
}
}

// TODO: handle SIGTERM and gracefully shutdown
for true {
time.Sleep(time.Minute)
}
Expand Down