mirror of
https://github.com/decke/smtprelay.git
synced 2025-12-25 07:43:06 -07:00
Merge pull request #63 from markgardner/master
Allow config to have multiple remotes.
This commit is contained in:
65
config.go
65
config.go
@@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/smtp"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -45,13 +45,8 @@ var (
|
|||||||
allowedRecipients *regexp.Regexp
|
allowedRecipients *regexp.Regexp
|
||||||
allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords")
|
allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords")
|
||||||
command = flag.String("command", "", "Path to pipe command")
|
command = flag.String("command", "", "Path to pipe command")
|
||||||
remoteHost = flag.String("remote_host", "", "Outgoing SMTP server")
|
remotesStr = flag.String("remotes", "", "Outgoing SMTP servers")
|
||||||
remoteSkipVerify = flag.Bool("remote_skip_verify", false, "Ignore invalid remote certificates")
|
remotes = []*Remote{}
|
||||||
remoteUser = flag.String("remote_user", "", "Username for authentication on outgoing SMTP server")
|
|
||||||
remotePass = flag.String("remote_pass", "", "Password for authentication on outgoing SMTP server")
|
|
||||||
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")
|
versionInfo = flag.Bool("version", false, "Show version information")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,46 +98,18 @@ func setupAllowedPatterns() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRemoteAuth() {
|
func setupRemotes() {
|
||||||
logger := log.WithField("remote_auth", *remoteAuthStr)
|
logger := log.WithField("remotes", *remotesStr)
|
||||||
|
|
||||||
// Remote auth disabled?
|
if *remotesStr != "" {
|
||||||
if *remoteAuthStr == "" || *remoteAuthStr == "none" {
|
for _, remoteURL := range strings.Split(*remotesStr, " ") {
|
||||||
if *remoteUser != "" {
|
r, err := ParseRemote(remoteURL)
|
||||||
logger.Fatal("remote_user given but not used")
|
if err != nil {
|
||||||
|
logger.Fatal(fmt.Sprintf("error parsing url: '%s': %v", remoteURL, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
remotes = append(remotes, r)
|
||||||
}
|
}
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,13 +188,13 @@ func ConfigLoad() {
|
|||||||
// Set up logging as soon as possible
|
// Set up logging as soon as possible
|
||||||
setupLogger()
|
setupLogger()
|
||||||
|
|
||||||
if *remoteHost == "" && *command == "" {
|
if *remotesStr == "" && *command == "" {
|
||||||
log.Warn("no remote_host or command set; mail will not be forwarded!")
|
log.Warn("no remotes or command set; mail will not be forwarded!")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAllowedNetworks()
|
setupAllowedNetworks()
|
||||||
setupAllowedPatterns()
|
setupAllowedPatterns()
|
||||||
setupRemoteAuth()
|
setupRemotes()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
setupTimeouts()
|
setupTimeouts()
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -4,6 +4,7 @@ require (
|
|||||||
github.com/chrj/smtpd v0.3.1
|
github.com/chrj/smtpd v0.3.1
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de
|
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
)
|
)
|
||||||
|
|||||||
7
go.sum
7
go.sum
@@ -1,5 +1,6 @@
|
|||||||
github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE=
|
github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE=
|
||||||
github.com/chrj/smtpd v0.3.1/go.mod h1:JtABvV/LzvLmEIzy0NyDnrfMGOMd8wy5frAokwf6J9Q=
|
github.com/chrj/smtpd v0.3.1/go.mod h1:JtABvV/LzvLmEIzy0NyDnrfMGOMd8wy5frAokwf6J9Q=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
@@ -8,8 +9,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de h1:fkw+7JkxF3U1GzQoX9h69Wvtvxajo5Rbzy6+YMMzPIg=
|
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=
|
github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de/go.mod h1:irMhzlTz8+fVFj6CH2AN2i+WI5S6wWFtK3MBCIxIpyI=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@@ -21,3 +25,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193
|
|||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
63
main.go
63
main.go
@@ -161,11 +161,10 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
|
|||||||
"from": env.Sender,
|
"from": env.Sender,
|
||||||
"to": env.Recipients,
|
"to": env.Recipients,
|
||||||
"peer": peerIP,
|
"peer": peerIP,
|
||||||
"host": *remoteHost,
|
|
||||||
"uuid": generateUUID(),
|
"uuid": generateUUID(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if *remoteHost == "" && *command == "" {
|
if *remotesStr == "" && *command == "" {
|
||||||
logger.Warning("no remote_host or command set; discarding mail")
|
logger.Warning("no remote_host or command set; discarding mail")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -192,50 +191,40 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error {
|
|||||||
cmdLogger.Info("pipe command successful: " + stdout.String())
|
cmdLogger.Info("pipe command successful: " + stdout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if *remoteHost == "" {
|
for _, remote := range remotes {
|
||||||
return nil
|
logger = logger.WithField("host", remote.Addr)
|
||||||
}
|
logger.Info("delivering mail from peer using smarthost")
|
||||||
|
|
||||||
logger.Info("delivering mail from peer using smarthost")
|
err := SendMail(
|
||||||
|
remote,
|
||||||
|
env.Sender,
|
||||||
|
env.Recipients,
|
||||||
|
env.Data,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
var smtpError smtpd.Error
|
||||||
|
|
||||||
var sender string
|
switch err := err.(type) {
|
||||||
|
case *textproto.Error:
|
||||||
|
smtpError = smtpd.Error{Code: err.Code, Message: err.Msg}
|
||||||
|
|
||||||
if *remoteSender == "" {
|
logger.WithFields(logrus.Fields{
|
||||||
sender = env.Sender
|
"err_code": err.Code,
|
||||||
} else {
|
"err_msg": err.Msg,
|
||||||
sender = *remoteSender
|
}).Error("delivery failed")
|
||||||
}
|
default:
|
||||||
|
smtpError = smtpd.Error{Code: 554, Message: "Forwarding failed"}
|
||||||
|
|
||||||
err := SendMail(
|
logger.WithError(err).
|
||||||
*remoteHost,
|
Error("delivery failed")
|
||||||
remoteAuth,
|
}
|
||||||
sender,
|
|
||||||
env.Recipients,
|
|
||||||
env.Data,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
var smtpError smtpd.Error
|
|
||||||
|
|
||||||
switch err.(type) {
|
return smtpError
|
||||||
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"}
|
|
||||||
|
|
||||||
logger.WithError(err).
|
|
||||||
Error("delivery failed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return smtpError
|
logger.Debug("delivery successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("delivery successful")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
remotes.go
Normal file
84
remotes.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/smtp"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Remote struct {
|
||||||
|
SkipVerify bool
|
||||||
|
Auth smtp.Auth
|
||||||
|
Scheme string
|
||||||
|
Hostname string
|
||||||
|
Port string
|
||||||
|
Addr string
|
||||||
|
Sender string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRemote creates a remote from a given url in the following format:
|
||||||
|
//
|
||||||
|
// smtp://[user[:password]@][netloc][:port][/remote_sender][?param1=value1&...]
|
||||||
|
// smtps://[user[:password]@][netloc][:port][/remote_sender][?param1=value1&...]
|
||||||
|
// starttls://[user[:password]@][netloc][:port][/remote_sender][?param1=value1&...]
|
||||||
|
//
|
||||||
|
// Supported Params:
|
||||||
|
// - skipVerify: can be "true" or empty to prevent ssl verification of remote server's certificate.
|
||||||
|
// - auth: can be "login" to trigger "LOGIN" auth instead of "PLAIN" auth
|
||||||
|
//
|
||||||
|
func ParseRemote(remoteURL string) (*Remote, error) {
|
||||||
|
u, err := url.Parse(remoteURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "smtp" && u.Scheme != "smtps" && u.Scheme != "starttls" {
|
||||||
|
return nil, fmt.Errorf("'%s' is not a supported relay scheme", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, port := u.Hostname(), u.Port()
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "smtp":
|
||||||
|
port = "25"
|
||||||
|
case "smtps":
|
||||||
|
port = "465"
|
||||||
|
case "starttls":
|
||||||
|
port = "587"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
r := &Remote{
|
||||||
|
Scheme: u.Scheme,
|
||||||
|
Hostname: hostname,
|
||||||
|
Port: port,
|
||||||
|
Addr: fmt.Sprintf("%s:%s", hostname, port),
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
pass, _ := u.User.Password()
|
||||||
|
user := u.User.Username()
|
||||||
|
|
||||||
|
if hasAuth, authVal := q.Has("auth"), q.Get("auth"); hasAuth {
|
||||||
|
if authVal != "login" {
|
||||||
|
return nil, fmt.Errorf("Auth must be login or not present, received '%s'", authVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Auth = LoginAuth(user, pass)
|
||||||
|
} else {
|
||||||
|
r.Auth = smtp.PlainAuth("", user, pass, u.Hostname())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasVal, skipVerify := q.Has("skipVerify"), q.Get("skipVerify"); hasVal && skipVerify != "false" {
|
||||||
|
r.SkipVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Path != "" {
|
||||||
|
r.Sender = u.Path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
114
remotes_test.go
Normal file
114
remotes_test.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/smtp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AssertRemoteUrlEquals(t *testing.T, expected *Remote, remotUrl string) {
|
||||||
|
actual, err := ParseRemote(remotUrl)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, actual)
|
||||||
|
assert.Equal(t, expected.Scheme, actual.Scheme, "Scheme %s", remotUrl)
|
||||||
|
assert.Equal(t, expected.Addr, actual.Addr, "Addr %s", remotUrl)
|
||||||
|
assert.Equal(t, expected.Hostname, actual.Hostname, "Hostname %s", remotUrl)
|
||||||
|
assert.Equal(t, expected.Port, actual.Port, "Port %s", remotUrl)
|
||||||
|
assert.Equal(t, expected.Sender, actual.Sender, "Sender %s", remotUrl)
|
||||||
|
assert.Equal(t, expected.SkipVerify, actual.SkipVerify, "SkipVerify %s", remotUrl)
|
||||||
|
|
||||||
|
if expected.Auth != nil || actual.Auth != nil {
|
||||||
|
assert.NotNil(t, expected, "Auth %s", remotUrl)
|
||||||
|
assert.NotNil(t, actual, "Auth %s", remotUrl)
|
||||||
|
assert.IsType(t, expected.Auth, actual.Auth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidRemoteUrls(t *testing.T) {
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtp",
|
||||||
|
SkipVerify: false,
|
||||||
|
Auth: nil,
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "25",
|
||||||
|
Addr: "email.com:25",
|
||||||
|
Sender: "",
|
||||||
|
}, "smtp://email.com")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtp",
|
||||||
|
SkipVerify: true,
|
||||||
|
Auth: nil,
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "25",
|
||||||
|
Addr: "email.com:25",
|
||||||
|
Sender: "",
|
||||||
|
}, "smtp://email.com?skipVerify")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtp",
|
||||||
|
SkipVerify: false,
|
||||||
|
Auth: smtp.PlainAuth("", "user", "pass", ""),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "25",
|
||||||
|
Addr: "email.com:25",
|
||||||
|
Sender: "",
|
||||||
|
}, "smtp://user:pass@email.com")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtp",
|
||||||
|
SkipVerify: false,
|
||||||
|
Auth: LoginAuth("user", "pass"),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "25",
|
||||||
|
Addr: "email.com:25",
|
||||||
|
Sender: "",
|
||||||
|
}, "smtp://user:pass@email.com?auth=login")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtp",
|
||||||
|
SkipVerify: false,
|
||||||
|
Auth: LoginAuth("user", "pass"),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "25",
|
||||||
|
Addr: "email.com:25",
|
||||||
|
Sender: "sender@website.com",
|
||||||
|
}, "smtp://user:pass@email.com/sender@website.com?auth=login")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtps",
|
||||||
|
SkipVerify: false,
|
||||||
|
Auth: LoginAuth("user", "pass"),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "465",
|
||||||
|
Addr: "email.com:465",
|
||||||
|
Sender: "sender@website.com",
|
||||||
|
}, "smtps://user:pass@email.com/sender@website.com?auth=login")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "smtps",
|
||||||
|
SkipVerify: true,
|
||||||
|
Auth: LoginAuth("user", "pass"),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "8425",
|
||||||
|
Addr: "email.com:8425",
|
||||||
|
Sender: "sender@website.com",
|
||||||
|
}, "smtps://user:pass@email.com:8425/sender@website.com?auth=login&skipVerify")
|
||||||
|
|
||||||
|
AssertRemoteUrlEquals(t, &Remote{
|
||||||
|
Scheme: "starttls",
|
||||||
|
SkipVerify: true,
|
||||||
|
Auth: LoginAuth("user", "pass"),
|
||||||
|
Hostname: "email.com",
|
||||||
|
Port: "8425",
|
||||||
|
Addr: "email.com:8425",
|
||||||
|
Sender: "sender@website.com",
|
||||||
|
}, "starttls://user:pass@email.com:8425/sender@website.com?auth=login&skipVerify")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingScheme(t *testing.T) {
|
||||||
|
_, err := ParseRemote("http://user:pass@email.com:8425/sender@website.com")
|
||||||
|
assert.NotNil(t, err, "Err must be present")
|
||||||
|
assert.Equal(t, err.Error(), "'http' is not a supported relay scheme")
|
||||||
|
}
|
||||||
31
smtp.go
31
smtp.go
@@ -322,7 +322,11 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests
|
|||||||
// attachments (see the mime/multipart package), or other mail
|
// attachments (see the mime/multipart package), or other mail
|
||||||
// functionality. Higher-level packages exist outside of the standard
|
// functionality. Higher-level packages exist outside of the standard
|
||||||
// library.
|
// library.
|
||||||
func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
func SendMail(r *Remote, from string, to []string, msg []byte) error {
|
||||||
|
if r.Sender != "" {
|
||||||
|
from = r.Sender
|
||||||
|
}
|
||||||
|
|
||||||
if err := validateLine(from); err != nil {
|
if err := validateLine(from); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -331,22 +335,19 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var c *Client
|
var c *Client
|
||||||
if port == "465" || port == "smtps" {
|
var err error
|
||||||
|
if r.Scheme == "smtps" {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
ServerName: host,
|
ServerName: r.Hostname,
|
||||||
InsecureSkipVerify: *remoteSkipVerify,
|
InsecureSkipVerify: r.SkipVerify,
|
||||||
}
|
}
|
||||||
conn, err := tls.Dial("tcp", addr, config)
|
conn, err := tls.Dial("tcp", r.Addr, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
c, err = NewClient(conn, host)
|
c, err = NewClient(conn, r.Hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -354,7 +355,7 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c, err = Dial(addr)
|
c, err = Dial(r.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -365,7 +366,7 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er
|
|||||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
ServerName: c.serverName,
|
ServerName: c.serverName,
|
||||||
InsecureSkipVerify: *remoteSkipVerify,
|
InsecureSkipVerify: r.SkipVerify,
|
||||||
}
|
}
|
||||||
if testHookStartTLS != nil {
|
if testHookStartTLS != nil {
|
||||||
testHookStartTLS(config)
|
testHookStartTLS(config)
|
||||||
@@ -373,13 +374,15 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er
|
|||||||
if err = c.StartTLS(config); err != nil {
|
if err = c.StartTLS(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if r.Scheme == "starttls" {
|
||||||
|
return errors.New("starttls: server does not support extension, check remote scheme")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if a != nil && c.ext != nil {
|
if r.Auth != nil && c.ext != nil {
|
||||||
if _, ok := c.ext["AUTH"]; !ok {
|
if _, ok := c.ext["AUTH"]; !ok {
|
||||||
return errors.New("smtp: server doesn't support AUTH")
|
return errors.New("smtp: server doesn't support AUTH")
|
||||||
}
|
}
|
||||||
if err = c.Auth(a); err != nil {
|
if err = c.Auth(r.Auth); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,27 +87,25 @@
|
|||||||
; If not set, mails are discarded.
|
; If not set, mails are discarded.
|
||||||
|
|
||||||
; GMail
|
; GMail
|
||||||
;remote_host = smtp.gmail.com:587
|
;remotes = starttls://user:pass@smtp.gmail.com:587
|
||||||
|
|
||||||
; Mailgun.org
|
; Mailgun.org
|
||||||
;remote_host = smtp.mailgun.org:587
|
;remotes = starttls://user:pass@smtp.mailgun.org:587
|
||||||
|
|
||||||
; Mailjet.com
|
; Mailjet.com
|
||||||
;remote_host = in-v3.mailjet.com:587
|
;remotes = starttls://user:pass@in-v3.mailjet.com:587
|
||||||
|
|
||||||
; Ignore remote host certificates
|
; Ignore remote host certificates
|
||||||
;remote_skip_verify = false
|
;remotes = starttls://user:pass@server:587?skipVerify
|
||||||
|
|
||||||
; Authentication credentials on outgoing SMTP server
|
; Login Authentication method on outgoing SMTP server
|
||||||
;remote_user =
|
;remotes = smtp://user:pass@server:2525?auth=login
|
||||||
;remote_pass =
|
|
||||||
|
|
||||||
; Authentication method on outgoing SMTP server
|
|
||||||
; (none, plain, login)
|
|
||||||
;remote_auth = none
|
|
||||||
|
|
||||||
; Sender e-mail address on outgoing SMTP server
|
; Sender e-mail address on outgoing SMTP server
|
||||||
;remote_sender =
|
;remotes = smtp://user:pass@server:2525/overridden@email.com?auth=login
|
||||||
|
|
||||||
|
; Multiple remotes, space delimited
|
||||||
|
;remotes = smtp://127.0.0.1:1025 starttls://user:pass@smtp.mailgun.org:587
|
||||||
|
|
||||||
; Pipe messages to external command
|
; Pipe messages to external command
|
||||||
;command = /usr/local/bin/script
|
;command = /usr/local/bin/script
|
||||||
|
|||||||
Reference in New Issue
Block a user