9 Commits

Author SHA1 Message Date
Bernhard Froehlich
92be537b25 Bump version to 1.2.0 2019-01-07 12:03:27 +00:00
Bernhard Froehlich
b9d1663a18 Fixes for new authentication code 2019-01-07 11:52:25 +00:00
Bernhard Froehlich
3a96014c70 Revert package renaming 2018-12-29 13:08:10 +00:00
Bernhard Froehlich
ade7cbca36 Rename to smtprelay 2018-12-29 12:39:56 +00:00
Bernhard Froehlich
ab341c697d Code refactoring and rename package 2018-12-29 12:24:32 +00:00
Bernhard Froehlich
ab850e8765 Use proper hostname instead of "localhost" for outgoing mails 2018-12-28 15:40:43 +00:00
Bernhard Froehlich
a82b0faf96 Check sender email against auth file when user is authenticated 2018-12-28 15:30:55 +00:00
Bernhard Froehlich
76a04a2001 Authentication checker converted to store passwords as bcrypt hashes 2018-12-28 15:18:50 +00:00
Bernhard Froehlich
0df376e54a Bump to 1.1.1-dev 2018-12-26 21:20:51 +00:00
8 changed files with 133 additions and 52 deletions

View File

@@ -1,4 +1,4 @@
# smtp-proxy
# smtprelay
Simple Golang based SMTP relay/proxy server that accepts mail via SMTP
and forwards it directly to another SMTP server.
@@ -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 ~250 LoC, chrj/smtpd ~1200 LoC)
* Small codebase
* IPv6 support

68
auth.go Normal file
View File

@@ -0,0 +1,68 @@
package main
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")
}

33
config.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"flag"
"github.com/vharitonsky/iniflags"
)
const (
VERSION = "1.2.0"
)
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 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()
}

3
go.mod
View File

@@ -1,6 +1,7 @@
module code.bluelife.at/decke/smtp-proxy
module code.bluelife.at/decke/smtprelay
require (
github.com/chrj/smtpd v0.1.2
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
)

2
go.sum
View File

@@ -3,3 +3,5 @@ github.com/chrj/smtpd v0.1.2/go.mod h1:jt4ydELuZmqhn9hn3YpEPV1dY00aOB+Q1nWXnBDFK
github.com/eaigner/dkim v0.0.0-20150301120808-6fe4a7ee9cfb/go.mod h1:FSCIHbrqk7D01Mj8y/jW+NS1uoCerr+ad+IckTHTFf4=
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de h1:fkw+7JkxF3U1GzQoX9h69Wvtvxajo5Rbzy6+YMMzPIg=
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de/go.mod h1:irMhzlTz8+fVFj6CH2AN2i+WI5S6wWFtK3MBCIxIpyI=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

65
main.go
View File

@@ -1,9 +1,7 @@
package main
import (
"bufio"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
@@ -15,29 +13,6 @@ import (
"time"
"github.com/chrj/smtpd"
"github.com/vharitonsky/iniflags"
)
const (
VERSION = "1.1.0"
)
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 {
@@ -62,6 +37,18 @@ 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 != "" {
_, email, err := AuthFetch(peer.Username)
if err != nil {
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"}
}
}
if *allowedSender == "" {
return nil
}
@@ -98,28 +85,13 @@ 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)
log.Printf("Auth error: %v\n", 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] && password == parts[1] {
return nil
}
}
return smtpd.Error{Code: 535, Message: "Authentication credentials invalid"}
}
func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
if *allowedUsers != "" && peer.Username == "" {
@@ -164,10 +136,10 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
func main() {
iniflags.Parse()
ConfigLoad()
if *versionInfo {
fmt.Printf("smtpd-proxy/%s\n", VERSION)
fmt.Printf("smtprelay/%s\n", VERSION)
os.Exit(0)
}
@@ -196,6 +168,11 @@ func main() {
}
if *allowedUsers != "" {
err := AuthLoadFile(*allowedUsers)
if err != nil {
log.Fatalf("Authentication file: %s\n", err)
}
server.Authenticator = authChecker
}

View File

@@ -67,7 +67,7 @@ func NewClient(conn net.Conn, host string) (*Client, error) {
text.Close()
return nil, err
}
c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
c := &Client{Text: text, conn: conn, serverName: host, localName: *hostName}
_, c.tls = conn.(*tls.Conn)
return c, nil
}

View File

@@ -1,7 +1,7 @@
; smtp-proxy configuration
; smtprelay configuration
; Logfile
;logfile = /var/log/smtpd-proxy.log
;logfile = /var/log/smtprelay.log
; Hostname for this SMTP server
;hostname = "localhost.localdomain"
@@ -37,7 +37,7 @@
; File which contains username and password used for
; authentication before they can send mail.
; File format: username password
; File format: username bcrypt-hash email
;allowed_users =
; Relay all mails to this SMTP server