From 422191968956a6a41b4d821f0ecfde589f651c11 Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Fri, 25 Feb 2022 07:37:12 -0600 Subject: [PATCH 1/5] Allow config to have multiple remotes. This will enable development teams to test emails from a system like mailcatcher while also having the email delivered to the indented mailbox. --- config.go | 65 +++++++++-------------------------- go.mod | 1 + go.sum | 7 ++++ main.go | 63 ++++++++++++++-------------------- remotes.go | 74 ++++++++++++++++++++++++++++++++++++++++ remotes_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ smtp.go | 29 ++++++++-------- smtprelay.ini | 22 ++++++------ 8 files changed, 239 insertions(+), 112 deletions(-) create mode 100644 remotes.go create mode 100644 remotes_test.go diff --git a/config.go b/config.go index bcbf2c0..9623c89 100644 --- a/config.go +++ b/config.go @@ -2,8 +2,8 @@ package main import ( "flag" + "fmt" "net" - "net/smtp" "regexp" "strings" "time" @@ -45,13 +45,8 @@ var ( allowedRecipients *regexp.Regexp allowedUsers = flag.String("allowed_users", "", "Path to file with valid users/passwords") command = flag.String("command", "", "Path to pipe command") - remoteHost = flag.String("remote_host", "", "Outgoing SMTP server") - remoteSkipVerify = flag.Bool("remote_skip_verify", false, "Ignore invalid remote certificates") - 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") + remotesStr = flag.String("remotes", "", "Outgoing SMTP servers") + remotes = []*Remote{} versionInfo = flag.Bool("version", false, "Show version information") ) @@ -103,46 +98,18 @@ func setupAllowedPatterns() { } } -func setupRemoteAuth() { - logger := log.WithField("remote_auth", *remoteAuthStr) +func setupRemotes() { + logger := log.WithField("remotes", *remotesStr) - // Remote auth disabled? - if *remoteAuthStr == "" || *remoteAuthStr == "none" { - if *remoteUser != "" { - logger.Fatal("remote_user given but not used") + if *remotesStr != "" { + for _, remoteURL := range strings.Split(*remotesStr, " ") { + r, err := ParseRemote(remoteURL) + 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 setupLogger() - if *remoteHost == "" && *command == "" { - log.Warn("no remote_host or command set; mail will not be forwarded!") + if *remotesStr == "" && *command == "" { + log.Warn("no remotes or command set; mail will not be forwarded!") } setupAllowedNetworks() setupAllowedPatterns() - setupRemoteAuth() + setupRemotes() setupListeners() setupTimeouts() } diff --git a/go.mod b/go.mod index 8716c35..c48c1df 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/chrj/smtpd v0.3.1 github.com/google/uuid v1.3.0 github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.7.0 github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad ) diff --git a/go.sum b/go.sum index 0530500..8709160 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/chrj/smtpd v0.3.1 h1:kogHFkbFdKaoH3bgZkqNC9uVtKYOFfM3uV3rroBdooE= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 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/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/go.mod h1:irMhzlTz8+fVFj6CH2AN2i+WI5S6wWFtK3MBCIxIpyI= 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/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= +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= diff --git a/main.go b/main.go index 4547432..1a815e2 100644 --- a/main.go +++ b/main.go @@ -161,11 +161,10 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { "from": env.Sender, "to": env.Recipients, "peer": peerIP, - "host": *remoteHost, "uuid": generateUUID(), }) - if *remoteHost == "" && *command == "" { + if *remotesStr == "" && *command == "" { logger.Warning("no remote_host or command set; discarding mail") return nil } @@ -192,50 +191,40 @@ func mailHandler(peer smtpd.Peer, env smtpd.Envelope) error { cmdLogger.Info("pipe command successful: " + stdout.String()) } - if *remoteHost == "" { - return nil - } + for _, remote := range remotes { + 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 == "" { - sender = env.Sender - } else { - sender = *remoteSender - } + logger.WithFields(logrus.Fields{ + "err_code": err.Code, + "err_msg": err.Msg, + }).Error("delivery failed") + default: + smtpError = smtpd.Error{Code: 554, Message: "Forwarding failed"} - err := SendMail( - *remoteHost, - remoteAuth, - sender, - env.Recipients, - env.Data, - ) - if err != nil { - var smtpError smtpd.Error + logger.WithError(err). + Error("delivery failed") + } - 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"} - - logger.WithError(err). - Error("delivery failed") + return smtpError } - return smtpError + logger.Debug("delivery successful") } - logger.Debug("delivery successful") return nil } diff --git a/remotes.go b/remotes.go new file mode 100644 index 0000000..07ae8d3 --- /dev/null +++ b/remotes.go @@ -0,0 +1,74 @@ +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&...] +// +// 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 + } + + hostname, port := u.Hostname(), u.Port() + + if u.Scheme == "smtp" && port == "" { + port = "25" + } else if u.Scheme == "smtps" && port == "" { + port = "465" + } + + 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 +} diff --git a/remotes_test.go b/remotes_test.go new file mode 100644 index 0000000..f01aefd --- /dev/null +++ b/remotes_test.go @@ -0,0 +1,90 @@ +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.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{ + SkipVerify: false, + Auth: nil, + Hostname: "email.com", + Port: "25", + Addr: "email.com:25", + Sender: "", + }, "smtp://email.com") + + AssertRemoteUrlEquals(t, &Remote{ + SkipVerify: true, + Auth: nil, + Hostname: "email.com", + Port: "25", + Addr: "email.com:25", + Sender: "", + }, "smtp://email.com?skipVerify") + + AssertRemoteUrlEquals(t, &Remote{ + 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{ + 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{ + 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{ + 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{ + SkipVerify: true, + Auth: LoginAuth("user", "pass"), + Hostname: "email.com", + Port: "8425", + Addr: "email.com:8425", + Sender: "sender@website.com", + }, "smtp://user:pass@email.com:8425/sender@website.com?auth=login&skipVerify") +} diff --git a/smtp.go b/smtp.go index a32f286..e5bdd69 100644 --- a/smtp.go +++ b/smtp.go @@ -322,7 +322,11 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests // attachments (see the mime/multipart package), or other mail // functionality. Higher-level packages exist outside of the standard // 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 { return err } @@ -331,22 +335,19 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er return err } } - host, port, err := net.SplitHostPort(addr) - if err != nil { - return err - } var c *Client - if port == "465" || port == "smtps" { + var err error + if r.Scheme == "smtps" { config := &tls.Config{ - ServerName: host, - InsecureSkipVerify: *remoteSkipVerify, + ServerName: r.Hostname, + InsecureSkipVerify: r.SkipVerify, } - conn, err := tls.Dial("tcp", addr, config) + conn, err := tls.Dial("tcp", r.Addr, config) if err != nil { return err } defer conn.Close() - c, err = NewClient(conn, host) + c, err = NewClient(conn, r.Hostname) if err != nil { return err } @@ -354,7 +355,7 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er return err } } else { - c, err = Dial(addr) + c, err = Dial(r.Addr) if err != nil { 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 { config := &tls.Config{ ServerName: c.serverName, - InsecureSkipVerify: *remoteSkipVerify, + InsecureSkipVerify: r.SkipVerify, } if testHookStartTLS != nil { testHookStartTLS(config) @@ -375,11 +376,11 @@ func SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) er } } } - if a != nil && c.ext != nil { + if r.Auth != nil && c.ext != nil { if _, ok := c.ext["AUTH"]; !ok { 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 } } diff --git a/smtprelay.ini b/smtprelay.ini index be83633..0e55fd1 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -87,27 +87,25 @@ ; If not set, mails are discarded. ; GMail -;remote_host = smtp.gmail.com:587 +;remotes = smtp://user:pass@smtp.gmail.com:587 ; Mailgun.org -;remote_host = smtp.mailgun.org:587 +;remotes = smtp://user:pass@smtp.mailgun.org:587 ; Mailjet.com -;remote_host = in-v3.mailjet.com:587 +;remotes = smtp://user:pass@in-v3.mailjet.com:587 ; Ignore remote host certificates -;remote_skip_verify = false +;remotes = smtp://user:pass@server:2525?skipVerify -; Authentication credentials on outgoing SMTP server -;remote_user = -;remote_pass = - -; Authentication method on outgoing SMTP server -; (none, plain, login) -;remote_auth = none +; Login Authentication method on outgoing SMTP server +;remotes = smtp://user:pass@server:2525?auth=login ; Sender e-mail address on outgoing SMTP server -;remote_sender = +;remotes = smtp://user:pass@server:2525/overridden@email.com?auth=login + +; Multiple remotes +;remotes = smtp://127.0.0.1:1025 smtp://user:pass@smtp.mailgun.org:587 ; Pipe messages to external command ;command = /usr/local/bin/script From cf927508dd514474cd558927583620500fe20cfd Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Thu, 21 Apr 2022 11:28:16 -0500 Subject: [PATCH 2/5] Improve sample config to be more accurate --- smtprelay.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/smtprelay.ini b/smtprelay.ini index 0e55fd1..8546560 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -87,16 +87,16 @@ ; If not set, mails are discarded. ; GMail -;remotes = smtp://user:pass@smtp.gmail.com:587 +;remotes = starttls://user:pass@smtp.gmail.com:587 ; Mailgun.org -;remotes = smtp://user:pass@smtp.mailgun.org:587 +;remotes = starttls://user:pass@smtp.mailgun.org:587 ; Mailjet.com -;remotes = smtp://user:pass@in-v3.mailjet.com:587 +;remotes = starttls://user:pass@in-v3.mailjet.com:587 ; Ignore remote host certificates -;remotes = smtp://user:pass@server:2525?skipVerify +;remotes = starttls://user:pass@server:587?skipVerify ; Login Authentication method on outgoing SMTP server ;remotes = smtp://user:pass@server:2525?auth=login @@ -104,8 +104,8 @@ ; Sender e-mail address on outgoing SMTP server ;remotes = smtp://user:pass@server:2525/overridden@email.com?auth=login -; Multiple remotes -;remotes = smtp://127.0.0.1:1025 smtp://user:pass@smtp.mailgun.org:587 +; Multiple remotes, space delimited +;remotes = smtp://127.0.0.1:1025 starttls://user:pass@smtp.mailgun.org:587 ; Pipe messages to external command ;command = /usr/local/bin/script From b7f3701502009fb5c82338e7555a8d943715ac44 Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Thu, 21 Apr 2022 11:28:48 -0500 Subject: [PATCH 3/5] Add missing default port for starttls and validate scheme to ensure support --- remotes.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/remotes.go b/remotes.go index 07ae8d3..b05d825 100644 --- a/remotes.go +++ b/remotes.go @@ -20,6 +20,7 @@ type Remote struct { // // 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. @@ -31,12 +32,21 @@ func ParseRemote(remoteURL string) (*Remote, error) { 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 u.Scheme == "smtp" && port == "" { - port = "25" - } else if u.Scheme == "smtps" && port == "" { - port = "465" + if port == "" { + switch u.Scheme { + case "smtp": + port = "25" + case "smtps": + port = "465" + case "starttls": + port = "587" + } } q := u.Query() From 32d0206af0e752d7d68db460ca9e089af15dc59a Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Thu, 21 Apr 2022 11:29:15 -0500 Subject: [PATCH 4/5] throw an error if the remote is configured for starttls and the server does not have the extension --- smtp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smtp.go b/smtp.go index e5bdd69..14ab8c1 100644 --- a/smtp.go +++ b/smtp.go @@ -374,6 +374,8 @@ func SendMail(r *Remote, from string, to []string, msg []byte) error { if err = c.StartTLS(config); err != nil { return err } + } else if r.Scheme == "starttls" { + return errors.New("starttls: server does not support extension, check remote scheme") } } if r.Auth != nil && c.ext != nil { From 875264837ee8d1a807d96f4e12c881e46f292798 Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Thu, 21 Apr 2022 11:39:41 -0500 Subject: [PATCH 5/5] add test coverage for scheme validation --- remotes_test.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/remotes_test.go b/remotes_test.go index f01aefd..74ca960 100644 --- a/remotes_test.go +++ b/remotes_test.go @@ -11,6 +11,7 @@ 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) @@ -26,6 +27,7 @@ func AssertRemoteUrlEquals(t *testing.T, expected *Remote, remotUrl string) { func TestValidRemoteUrls(t *testing.T) { AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtp", SkipVerify: false, Auth: nil, Hostname: "email.com", @@ -35,6 +37,7 @@ func TestValidRemoteUrls(t *testing.T) { }, "smtp://email.com") AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtp", SkipVerify: true, Auth: nil, Hostname: "email.com", @@ -44,6 +47,7 @@ func TestValidRemoteUrls(t *testing.T) { }, "smtp://email.com?skipVerify") AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtp", SkipVerify: false, Auth: smtp.PlainAuth("", "user", "pass", ""), Hostname: "email.com", @@ -53,6 +57,7 @@ func TestValidRemoteUrls(t *testing.T) { }, "smtp://user:pass@email.com") AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtp", SkipVerify: false, Auth: LoginAuth("user", "pass"), Hostname: "email.com", @@ -62,6 +67,7 @@ func TestValidRemoteUrls(t *testing.T) { }, "smtp://user:pass@email.com?auth=login") AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtp", SkipVerify: false, Auth: LoginAuth("user", "pass"), Hostname: "email.com", @@ -71,6 +77,7 @@ func TestValidRemoteUrls(t *testing.T) { }, "smtp://user:pass@email.com/sender@website.com?auth=login") AssertRemoteUrlEquals(t, &Remote{ + Scheme: "smtps", SkipVerify: false, Auth: LoginAuth("user", "pass"), Hostname: "email.com", @@ -80,11 +87,28 @@ func TestValidRemoteUrls(t *testing.T) { }, "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", - }, "smtp://user:pass@email.com:8425/sender@website.com?auth=login&skipVerify") + }, "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") }