diff --git a/DOCS.md b/DOCS.md index ee2f7f6..2e923b0 100644 --- a/DOCS.md +++ b/DOCS.md @@ -10,6 +10,7 @@ You can configure the plugin using the following parameters: * **username** - SMTP username * **password** - SMTP password * **skip_verify** - Skip verification of SSL certificates, defaults to `false` +* **starttls** - Enable/Disable STARTTLS * **recipients** - List of recipients to send this mail to (besides the commit author) * **recipients_file** - Filename to load additional recipients from (textfile with one email per line) (besides the commit author) * **recipients_only** - Do not send mails to the commit author, but only to **recipients**, defaults to `false` @@ -127,3 +128,22 @@ steps: password: 12345 + skip_verify: true ``` + +### STARTTLS + +By default, STARTTLS is being used opportunistically meaning, if advertised +by the server, traffic is going to be encrypted. + +You may want to disable STARTTLS, e.g., with faulty and/or internal servers: + +```diff +steps: + - name: notify + image: drillster/drone-email + settings: + from: noreply@github.com + host: smtp.mailgun.org + username: octocat + password: 12345 ++ starttls: false +``` diff --git a/main.go b/main.go index 1210307..18499b4 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,11 @@ func main() { Usage: "skip tls verify", EnvVar: "PLUGIN_SKIP_VERIFY", }, + cli.BoolFlag{ + Name: "starttls", + Usage: "Enable/Disable STARTTLS", + EnvVar: "PLUGIN_STARTTLS", + }, cli.StringFlag{ Name: "recipients.file", Usage: "file to read recipients from", @@ -407,6 +412,7 @@ func run(c *cli.Context) error { Username: c.String("username"), Password: c.String("password"), SkipVerify: c.Bool("skip.verify"), + StartTLS: c.Bool("starttls"), Recipients: c.StringSlice("recipients"), RecipientsFile: c.String("recipients.file"), RecipientsOnly: c.Bool("recipients.only"), diff --git a/plugin.go b/plugin.go index 9057941..35dc85b 100644 --- a/plugin.go +++ b/plugin.go @@ -9,7 +9,7 @@ import ( "github.com/aymerick/douceur/inliner" "github.com/drone/drone-go/template" "github.com/jaytaylor/html2text" - "gopkg.in/gomail.v2" + gomail "gopkg.in/mail.v2" ) type ( @@ -88,6 +88,7 @@ type ( Username string Password string SkipVerify bool + StartTLS bool Recipients []string RecipientsFile string RecipientsOnly bool @@ -147,9 +148,15 @@ func (p Plugin) Exec() error { } else { dialer = gomail.NewDialer(p.Config.Host, p.Config.Port, p.Config.Username, p.Config.Password) } + if p.Config.SkipVerify { dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true} } + + if !p.Config.StartTLS { + dialer.StartTLSPolicy = gomail.NoStartTLS + } + dialer.LocalName = p.Config.ClientHostname closer, err := dialer.Dial() diff --git a/vendor/gopkg.in/gomail.v2/CHANGELOG.md b/vendor/gopkg.in/gomail.v2/CHANGELOG.md deleted file mode 100644 index a797ab4..0000000 --- a/vendor/gopkg.in/gomail.v2/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# Change Log -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -## [2.0.0] - 2015-09-02 - -- Mailer has been removed. It has been replaced by Dialer and Sender. -- `File` type and the `CreateFile` and `OpenFile` functions have been removed. -- `Message.Attach` and `Message.Embed` have a new signature. -- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter` -instead. -- `Message.Export` has been removed. `Message.WriteTo` can be used instead. -- `Message.DelHeader` has been removed. -- The `Bcc` header field is no longer sent. It is far more simpler and -efficient: the same message is sent to all recipients instead of sending a -different email to each Bcc address. -- LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN -authentication mechanism when needed. -- Go 1.2 is now required instead of Go 1.3. No external dependency are used when -using Go 1.5. diff --git a/vendor/gopkg.in/gomail.v2/README.md b/vendor/gopkg.in/gomail.v2/README.md deleted file mode 100644 index b3be9e1..0000000 --- a/vendor/gopkg.in/gomail.v2/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Gomail -[![Build Status](https://travis-ci.org/go-gomail/gomail.svg?branch=v2)](https://travis-ci.org/go-gomail/gomail) [![Code Coverage](http://gocover.io/_badge/gopkg.in/gomail.v2)](http://gocover.io/gopkg.in/gomail.v2) [![Documentation](https://godoc.org/gopkg.in/gomail.v2?status.svg)](https://godoc.org/gopkg.in/gomail.v2) - -## Introduction - -Gomail is a simple and efficient package to send emails. It is well tested and -documented. - -Gomail can only send emails using an SMTP server. But the API is flexible and it -is easy to implement other methods for sending emails using a local Postfix, an -API, etc. - -It is versioned using [gopkg.in](https://gopkg.in) so I promise -there will never be backward incompatible changes within each version. - -It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used. - - -## Features - -Gomail supports: -- Attachments -- Embedded images -- HTML and text templates -- Automatic encoding of special characters -- SSL and TLS -- Sending multiple emails with the same SMTP connection - - -## Documentation - -https://godoc.org/gopkg.in/gomail.v2 - - -## Download - - go get gopkg.in/gomail.v2 - - -## Examples - -See the [examples in the documentation](https://godoc.org/gopkg.in/gomail.v2#example-package). - - -## FAQ - -### x509: certificate signed by unknown authority - -If you get this error it means the certificate used by the SMTP server is not -considered valid by the client running Gomail. As a quick workaround you can -bypass the verification of the server's certificate chain and host name by using -`SetTLSConfig`: - - package main - - import ( - "crypto/tls" - - "gopkg.in/gomail.v2" - ) - - func main() { - d := gomail.NewDialer("smtp.example.com", 587, "user", "123456") - d.TLSConfig = &tls.Config{InsecureSkipVerify: true} - - // Send emails using d. - } - -Note, however, that this is insecure and should not be used in production. - - -## Contribute - -Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for -more info. - - -## Change log - -See [CHANGELOG.md](CHANGELOG.md). - - -## License - -[MIT](LICENSE) - - -## Contact - -You can ask questions on the [Gomail -thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion) -in the Go mailing-list. diff --git a/vendor/gopkg.in/mail.v2/CHANGELOG.md b/vendor/gopkg.in/mail.v2/CHANGELOG.md new file mode 100644 index 0000000..cdd898a --- /dev/null +++ b/vendor/gopkg.in/mail.v2/CHANGELOG.md @@ -0,0 +1,88 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## *Unreleased* + +## [2.3.1] - 2018-11-12 + +### Fixed + +- #39: Reverts addition of Go modules `go.mod` manifest. + +## [2.3.0] - 2018-11-10 + +### Added + +- #12: Adds `SendError` to provide additional info about the cause and index of + a failed attempt to transmit a batch of messages. +- go-gomail#78: Adds new `Message` methods for attaching and embedding + `io.Reader`s: `AttachReader` and `EmbedReader`. + +### Fixed + +- #26: Fixes RFC 1341 compliance by properly capitalizing the + `MIME-Version` header. +- #30: Fixes IO errors being silently dropped in `Message.WriteTo`. + +## [2.2.0] - 2018-03-01 + +### Added + +- #20: Adds `Message.SetBoundary` to allow specifying a custom MIME boundary. +- #22: Adds `Message.SetBodyWriter` to make it easy to use text/template and + html/template for message bodies. Contributed by Quantcast. +- #25: Adds `Dialer.StartTLSPolicy` so that `MandatoryStartTLS` can be required, + or `NoStartTLS` can disable it. Contributed by Quantcast. + +## [2.1.0] - 2017-12-14 + +### Added + +- go-gomail#40: Adds `Dialer.LocalName` field to allow specifying the hostname + sent with SMTP's HELO command. +- go-gomail#47: `Message.SetBody`, `Message.AddAlternative`, and + `Message.AddAlternativeWriter` allow specifying the encoding of message parts. +- `Dialer.Dial`'s returned `SendCloser` automatically redials after a timeout. +- go-gomail#55, go-gomail#56: Adds `Rename` to allow specifying filename + of an attachment. +- go-gomail#100: Exports `NetDialTimeout` to allow setting a custom dialer. +- go-gomail#70: Adds `Dialer.Timeout` field to allow specifying a timeout for + dials, reads, and writes. + +### Changed + +- go-gomail#52: `Dialer.Dial` automatically uses CRAM-MD5 when available. +- `Dialer.Dial` specifies a default timeout of 10 seconds. +- Gomail is forked from to + . + +### Deprecated + +- go-gomail#52: `NewPlainDialer` is deprecated in favor of `NewDialer`. + +### Fixed + +- go-gomail#41, go-gomail#42: Fixes a panic when a `Message` contains a + nil header. +- go-gomail#44: Fixes `AddAlternativeWriter` replacing the message body instead + of adding a body part. +- go-gomail#53: Folds long header lines for RFC 2047 compliance. +- go-gomail#54: Fixes `Message.FormatAddress` when name is blank. + +## [2.0.0] - 2015-09-02 + +- Mailer has been removed. It has been replaced by Dialer and Sender. +- `File` type and the `CreateFile` and `OpenFile` functions have been removed. +- `Message.Attach` and `Message.Embed` have a new signature. +- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter` +instead. +- `Message.Export` has been removed. `Message.WriteTo` can be used instead. +- `Message.DelHeader` has been removed. +- The `Bcc` header field is no longer sent. It is far more simpler and +efficient: the same message is sent to all recipients instead of sending a +different email to each Bcc address. +- LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN +authentication mechanism when needed. +- Go 1.2 is now required instead of Go 1.3. No external dependency are used when +using Go 1.5. diff --git a/vendor/gopkg.in/gomail.v2/CONTRIBUTING.md b/vendor/gopkg.in/mail.v2/CONTRIBUTING.md similarity index 100% rename from vendor/gopkg.in/gomail.v2/CONTRIBUTING.md rename to vendor/gopkg.in/mail.v2/CONTRIBUTING.md diff --git a/vendor/gopkg.in/gomail.v2/LICENSE b/vendor/gopkg.in/mail.v2/LICENSE similarity index 100% rename from vendor/gopkg.in/gomail.v2/LICENSE rename to vendor/gopkg.in/mail.v2/LICENSE diff --git a/vendor/gopkg.in/mail.v2/README.md b/vendor/gopkg.in/mail.v2/README.md new file mode 100644 index 0000000..8cc31b6 --- /dev/null +++ b/vendor/gopkg.in/mail.v2/README.md @@ -0,0 +1,129 @@ +# Gomail +[![Build Status](https://travis-ci.org/go-mail/mail.svg?branch=master)](https://travis-ci.org/go-mail/mail) [![Code Coverage](http://gocover.io/_badge/github.com/go-mail/mail)](http://gocover.io/github.com/go-mail/mail) [![Documentation](https://godoc.org/github.com/go-mail/mail?status.svg)](https://godoc.org/github.com/go-mail/mail) + +This is an actively maintained fork of [Gomail][1] and includes fixes and +improvements for a number of outstanding issues. The current progress is +as follows: + + - [x] Timeouts and retries can be specified outside of the 10 second default. + - [x] Proxying is supported through specifying a custom [NetDialTimeout][2]. + - [ ] Filenames are properly encoded for non-ASCII characters. + - [ ] Email addresses are properly encoded for non-ASCII characters. + - [ ] Embedded files and attachments are tested for their existence. + - [ ] An `io.Reader` can be supplied when embedding and attaching files. + +See [Transitioning Existing Codebases][3] for more information on switching. + +[1]: https://github.com/go-gomail/gomail +[2]: https://godoc.org/gopkg.in/mail.v2#NetDialTimeout +[3]: #transitioning-existing-codebases + +## Introduction + +Gomail is a simple and efficient package to send emails. It is well tested and +documented. + +Gomail can only send emails using an SMTP server. But the API is flexible and it +is easy to implement other methods for sending emails using a local Postfix, an +API, etc. + +It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used. + + +## Features + +Gomail supports: +- Attachments +- Embedded images +- HTML and text templates +- Automatic encoding of special characters +- SSL and TLS +- Sending multiple emails with the same SMTP connection + + +## Documentation + +https://godoc.org/github.com/go-mail/mail + + +## Download + +If you're already using a dependency manager, like [dep][dep], use the following +import path: + +``` +github.com/go-mail/mail +``` + +If you *aren't* using vendoring, `go get` the [Gopkg.in](http://gopkg.in) +import path: + +``` +gopkg.in/mail.v2 +``` + +[dep]: https://github.com/golang/dep#readme + +## Examples + +See the [examples in the documentation](https://godoc.org/github.com/go-mail/mail#example-package). + + +## FAQ + +### x509: certificate signed by unknown authority + +If you get this error it means the certificate used by the SMTP server is not +considered valid by the client running Gomail. As a quick workaround you can +bypass the verification of the server's certificate chain and host name by using +`SetTLSConfig`: + +```go +package main + +import ( + "crypto/tls" + + "gopkg.in/mail.v2" +) + +func main() { + d := mail.NewDialer("smtp.example.com", 587, "user", "123456") + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + // Send emails using d. +} +``` + +Note, however, that this is insecure and should not be used in production. + +### Transitioning Existing Codebases + +If you're already using the original Gomail, switching is as easy as updating +the import line to: + +``` +import gomail "gopkg.in/mail.v2" +``` + +## Contribute + +Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for +more info. + + +## Change log + +See [CHANGELOG.md](CHANGELOG.md). + + +## License + +[MIT](LICENSE) + + +## Support & Contact + +You can ask questions on the [Gomail +thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion) +in the Go mailing-list. diff --git a/vendor/gopkg.in/gomail.v2/auth.go b/vendor/gopkg.in/mail.v2/auth.go similarity index 98% rename from vendor/gopkg.in/gomail.v2/auth.go rename to vendor/gopkg.in/mail.v2/auth.go index d28b83a..b8c0dde 100644 --- a/vendor/gopkg.in/gomail.v2/auth.go +++ b/vendor/gopkg.in/mail.v2/auth.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "bytes" diff --git a/vendor/gopkg.in/gomail.v2/doc.go b/vendor/gopkg.in/mail.v2/doc.go similarity index 57% rename from vendor/gopkg.in/gomail.v2/doc.go rename to vendor/gopkg.in/mail.v2/doc.go index a8f5091..d65bf35 100644 --- a/vendor/gopkg.in/gomail.v2/doc.go +++ b/vendor/gopkg.in/mail.v2/doc.go @@ -1,5 +1,6 @@ // Package gomail provides a simple interface to compose emails and to mail them // efficiently. // -// More info on Github: https://github.com/go-gomail/gomail -package gomail +// More info on Github: https://github.com/go-mail/mail +// +package mail diff --git a/vendor/gopkg.in/mail.v2/errors.go b/vendor/gopkg.in/mail.v2/errors.go new file mode 100644 index 0000000..770da8c --- /dev/null +++ b/vendor/gopkg.in/mail.v2/errors.go @@ -0,0 +1,16 @@ +package mail + +import "fmt" + +// A SendError represents the failure to transmit a Message, detailing the cause +// of the failure and index of the Message within a batch. +type SendError struct { + // Index specifies the index of the Message within a batch. + Index uint + Cause error +} + +func (err *SendError) Error() string { + return fmt.Sprintf("gomail: could not send email %d: %v", + err.Index+1, err.Cause) +} diff --git a/vendor/gopkg.in/gomail.v2/message.go b/vendor/gopkg.in/mail.v2/message.go similarity index 82% rename from vendor/gopkg.in/gomail.v2/message.go rename to vendor/gopkg.in/mail.v2/message.go index 4bffb1e..9f7f7bd 100644 --- a/vendor/gopkg.in/gomail.v2/message.go +++ b/vendor/gopkg.in/mail.v2/message.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "bytes" @@ -18,6 +18,7 @@ type Message struct { encoding Encoding hEncoder mimeEncoder buf bytes.Buffer + boundary string } type header map[string][]string @@ -97,6 +98,11 @@ const ( Unencoded Encoding = "8bit" ) +// SetBoundary sets a custom multipart boundary. +func (m *Message) SetBoundary(boundary string) { + m.boundary = boundary +} + // SetHeader sets a value to the given header field. func (m *Message) SetHeader(field string, value ...string) { m.encodeHeader(value) @@ -183,9 +189,15 @@ func (m *Message) GetHeader(field string) []string { } // SetBody sets the body of the message. It replaces any content previously set -// by SetBody, AddAlternative or AddAlternativeWriter. +// by SetBody, SetBodyWriter, AddAlternative or AddAlternativeWriter. func (m *Message) SetBody(contentType, body string, settings ...PartSetting) { - m.parts = []*part{m.newPart(contentType, newCopier(body), settings)} + m.SetBodyWriter(contentType, newCopier(body), settings...) +} + +// SetBodyWriter sets the body of the message. It can be useful with the +// text/template or html/template packages. +func (m *Message) SetBodyWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) { + m.parts = []*part{m.newPart(contentType, f, settings)} } // AddAlternative adds an alternative part to the message. @@ -226,8 +238,8 @@ func (m *Message) newPart(contentType string, f func(io.Writer) error, settings } // A PartSetting can be used as an argument in Message.SetBody, -// Message.AddAlternative or Message.AddAlternativeWriter to configure the part -// added to a message. +// Message.SetBodyWriter, Message.AddAlternative or Message.AddAlternativeWriter +// to configure the part added to a message. type PartSetting func(*part) // SetPartEncoding sets the encoding of the part added to the message. By @@ -283,8 +295,28 @@ func SetCopyFunc(f func(io.Writer) error) FileSetting { } } -func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file { - f := &file{ +// AttachReader attaches a file using an io.Reader +func (m *Message) AttachReader(name string, r io.Reader, settings ...FileSetting) { + m.attachments = m.appendFile(m.attachments, fileFromReader(name, r), settings) +} + +// Attach attaches the files to the email. +func (m *Message) Attach(filename string, settings ...FileSetting) { + m.attachments = m.appendFile(m.attachments, fileFromFilename(filename), settings) +} + +// EmbedReader embeds the images to the email. +func (m *Message) EmbedReader(name string, r io.Reader, settings ...FileSetting) { + m.embedded = m.appendFile(m.embedded, fileFromReader(name, r), settings) +} + +// Embed embeds the images to the email. +func (m *Message) Embed(filename string, settings ...FileSetting) { + m.embedded = m.appendFile(m.embedded, fileFromFilename(filename), settings) +} + +func fileFromFilename(name string) *file { + return &file{ Name: filepath.Base(name), Header: make(map[string][]string), CopyFunc: func(w io.Writer) error { @@ -299,7 +331,22 @@ func (m *Message) appendFile(list []*file, name string, settings []FileSetting) return h.Close() }, } +} +func fileFromReader(name string, r io.Reader) *file { + return &file{ + Name: filepath.Base(name), + Header: make(map[string][]string), + CopyFunc: func(w io.Writer) error { + if _, err := io.Copy(w, r); err != nil { + return err + } + return nil + }, + } +} + +func (m *Message) appendFile(list []*file, f *file, settings []FileSetting) []*file { for _, s := range settings { s(f) } @@ -310,13 +357,3 @@ func (m *Message) appendFile(list []*file, name string, settings []FileSetting) return append(list, f) } - -// Attach attaches the files to the email. -func (m *Message) Attach(filename string, settings ...FileSetting) { - m.attachments = m.appendFile(m.attachments, filename, settings) -} - -// Embed embeds the images to the email. -func (m *Message) Embed(filename string, settings ...FileSetting) { - m.embedded = m.appendFile(m.embedded, filename, settings) -} diff --git a/vendor/gopkg.in/gomail.v2/mime.go b/vendor/gopkg.in/mail.v2/mime.go similarity index 95% rename from vendor/gopkg.in/gomail.v2/mime.go rename to vendor/gopkg.in/mail.v2/mime.go index 194d4a7..d95ea2e 100644 --- a/vendor/gopkg.in/gomail.v2/mime.go +++ b/vendor/gopkg.in/mail.v2/mime.go @@ -1,6 +1,6 @@ // +build go1.5 -package gomail +package mail import ( "mime" diff --git a/vendor/gopkg.in/gomail.v2/mime_go14.go b/vendor/gopkg.in/mail.v2/mime_go14.go similarity index 96% rename from vendor/gopkg.in/gomail.v2/mime_go14.go rename to vendor/gopkg.in/mail.v2/mime_go14.go index 3dc26aa..bdb605d 100644 --- a/vendor/gopkg.in/gomail.v2/mime_go14.go +++ b/vendor/gopkg.in/mail.v2/mime_go14.go @@ -1,6 +1,6 @@ // +build !go1.5 -package gomail +package mail import "gopkg.in/alexcesaro/quotedprintable.v3" diff --git a/vendor/gopkg.in/gomail.v2/send.go b/vendor/gopkg.in/mail.v2/send.go similarity index 94% rename from vendor/gopkg.in/gomail.v2/send.go rename to vendor/gopkg.in/mail.v2/send.go index 9115ebe..62e67f0 100644 --- a/vendor/gopkg.in/gomail.v2/send.go +++ b/vendor/gopkg.in/mail.v2/send.go @@ -1,10 +1,10 @@ -package gomail +package mail import ( "errors" "fmt" "io" - "net/mail" + stdmail "net/mail" ) // Sender is the interface that wraps the Send method. @@ -36,7 +36,7 @@ func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error { func Send(s Sender, msg ...*Message) error { for i, m := range msg { if err := send(s, m); err != nil { - return fmt.Errorf("gomail: could not send email %d: %v", i+1, err) + return &SendError{Cause: err, Index: uint(i)} } } @@ -108,7 +108,7 @@ func addAddress(list []string, addr string) []string { } func parseAddress(field string) (string, error) { - addr, err := mail.ParseAddress(field) + addr, err := stdmail.ParseAddress(field) if err != nil { return "", fmt.Errorf("gomail: invalid address %q: %v", field, err) } diff --git a/vendor/gopkg.in/gomail.v2/smtp.go b/vendor/gopkg.in/mail.v2/smtp.go similarity index 55% rename from vendor/gopkg.in/gomail.v2/smtp.go rename to vendor/gopkg.in/mail.v2/smtp.go index 2aa49c8..547e04d 100644 --- a/vendor/gopkg.in/gomail.v2/smtp.go +++ b/vendor/gopkg.in/mail.v2/smtp.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "crypto/tls" @@ -27,23 +27,39 @@ type Dialer struct { // most cases since the authentication mechanism should use the STARTTLS // extension instead. SSL bool - // TSLConfig represents the TLS configuration used for the TLS (when the + // TLSConfig represents the TLS configuration used for the TLS (when the // STARTTLS extension is used) or SSL connection. TLSConfig *tls.Config + // StartTLSPolicy represents the TLS security level required to + // communicate with the SMTP server. + // + // This defaults to OpportunisticStartTLS for backwards compatibility, + // but we recommend MandatoryStartTLS for all modern SMTP servers. + // + // This option has no effect if SSL is set to true. + StartTLSPolicy StartTLSPolicy // LocalName is the hostname sent to the SMTP server with the HELO command. // By default, "localhost" is sent. LocalName string + // Timeout to use for read/write operations. Defaults to 10 seconds, can + // be set to 0 to disable timeouts. + Timeout time.Duration + // Whether we should retry mailing if the connection returned an error, + // defaults to true. + RetryFailure bool } // NewDialer returns a new SMTP Dialer. The given parameters are used to connect // to the SMTP server. func NewDialer(host string, port int, username, password string) *Dialer { return &Dialer{ - Host: host, - Port: port, - Username: username, - Password: password, - SSL: port == 465, + Host: host, + Port: port, + Username: username, + Password: password, + SSL: port == 465, + Timeout: 10 * time.Second, + RetryFailure: true, } } @@ -55,10 +71,15 @@ func NewPlainDialer(host string, port int, username, password string) *Dialer { return NewDialer(host, port, username, password) } +// NetDialTimeout specifies the DialTimeout function to establish a connection +// to the SMTP server. This can be used to override dialing in the case that a +// proxy or other special behavior is needed. +var NetDialTimeout = net.DialTimeout + // Dial dials and authenticates to an SMTP server. The returned SendCloser // should be closed when done using it. func (d *Dialer) Dial() (SendCloser, error) { - conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second) + conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), d.Timeout) if err != nil { return nil, err } @@ -72,14 +93,25 @@ func (d *Dialer) Dial() (SendCloser, error) { return nil, err } + if d.Timeout > 0 { + conn.SetDeadline(time.Now().Add(d.Timeout)) + } + if d.LocalName != "" { if err := c.Hello(d.LocalName); err != nil { return nil, err } } - if !d.SSL { - if ok, _ := c.Extension("STARTTLS"); ok { + if !d.SSL && d.StartTLSPolicy != NoStartTLS { + ok, _ := c.Extension("STARTTLS") + if !ok && d.StartTLSPolicy == MandatoryStartTLS { + err := StartTLSUnsupportedError{ + Policy: d.StartTLSPolicy} + return nil, err + } + + if ok { if err := c.StartTLS(d.tlsConfig()); err != nil { c.Close() return nil, err @@ -111,7 +143,7 @@ func (d *Dialer) Dial() (SendCloser, error) { } } - return &smtpSender{c, d}, nil + return &smtpSender{c, conn, d}, nil } func (d *Dialer) tlsConfig() *tls.Config { @@ -121,6 +153,47 @@ func (d *Dialer) tlsConfig() *tls.Config { return d.TLSConfig } +// StartTLSPolicy constants are valid values for Dialer.StartTLSPolicy. +type StartTLSPolicy int + +const ( + // OpportunisticStartTLS means that SMTP transactions are encrypted if + // STARTTLS is supported by the SMTP server. Otherwise, messages are + // sent in the clear. This is the default setting. + OpportunisticStartTLS StartTLSPolicy = iota + // MandatoryStartTLS means that SMTP transactions must be encrypted. + // SMTP transactions are aborted unless STARTTLS is supported by the + // SMTP server. + MandatoryStartTLS + // NoStartTLS means encryption is disabled and messages are sent in the + // clear. + NoStartTLS = -1 +) + +func (policy *StartTLSPolicy) String() string { + switch *policy { + case OpportunisticStartTLS: + return "OpportunisticStartTLS" + case MandatoryStartTLS: + return "MandatoryStartTLS" + case NoStartTLS: + return "NoStartTLS" + default: + return fmt.Sprintf("StartTLSPolicy:%v", *policy) + } +} + +// StartTLSUnsupportedError is returned by Dial when connecting to an SMTP +// server that does not support STARTTLS. +type StartTLSUnsupportedError struct { + Policy StartTLSPolicy +} + +func (e StartTLSUnsupportedError) Error() string { + return "gomail: " + e.Policy.String() + " required, but " + + "SMTP server does not support STARTTLS" +} + func addr(host string, port int) string { return fmt.Sprintf("%s:%d", host, port) } @@ -139,12 +212,29 @@ func (d *Dialer) DialAndSend(m ...*Message) error { type smtpSender struct { smtpClient - d *Dialer + conn net.Conn + d *Dialer +} + +func (c *smtpSender) retryError(err error) bool { + if !c.d.RetryFailure { + return false + } + + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + return true + } + + return err == io.EOF } func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error { + if c.d.Timeout > 0 { + c.conn.SetDeadline(time.Now().Add(c.d.Timeout)) + } + if err := c.Mail(from); err != nil { - if err == io.EOF { + if c.retryError(err) { // This is probably due to a timeout, so reconnect and try again. sc, derr := c.d.Dial() if derr == nil { @@ -154,6 +244,7 @@ func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error { } } } + return err } @@ -182,9 +273,8 @@ func (c *smtpSender) Close() error { // Stubbed out for tests. var ( - netDialTimeout = net.DialTimeout - tlsClient = tls.Client - smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) { + tlsClient = tls.Client + smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) { return smtp.NewClient(conn, host) } ) diff --git a/vendor/gopkg.in/gomail.v2/writeto.go b/vendor/gopkg.in/mail.v2/writeto.go similarity index 93% rename from vendor/gopkg.in/gomail.v2/writeto.go rename to vendor/gopkg.in/mail.v2/writeto.go index 9fb6b86..faf6124 100644 --- a/vendor/gopkg.in/gomail.v2/writeto.go +++ b/vendor/gopkg.in/mail.v2/writeto.go @@ -1,4 +1,4 @@ -package gomail +package mail import ( "encoding/base64" @@ -19,8 +19,8 @@ func (m *Message) WriteTo(w io.Writer) (int64, error) { } func (w *messageWriter) writeMessage(m *Message) { - if _, ok := m.header["Mime-Version"]; !ok { - w.writeString("Mime-Version: 1.0\r\n") + if _, ok := m.header["MIME-Version"]; !ok { + w.writeString("MIME-Version: 1.0\r\n") } if _, ok := m.header["Date"]; !ok { w.writeHeader("Date", m.FormatDate(now())) @@ -28,15 +28,15 @@ func (w *messageWriter) writeMessage(m *Message) { w.writeHeaders(m.header) if m.hasMixedPart() { - w.openMultipart("mixed") + w.openMultipart("mixed", m.boundary) } if m.hasRelatedPart() { - w.openMultipart("related") + w.openMultipart("related", m.boundary) } if m.hasAlternativePart() { - w.openMultipart("alternative") + w.openMultipart("alternative", m.boundary) } for _, part := range m.parts { w.writePart(part, m.charset) @@ -77,8 +77,11 @@ type messageWriter struct { err error } -func (w *messageWriter) openMultipart(mimeType string) { +func (w *messageWriter) openMultipart(mimeType, boundary string) { mw := multipart.NewWriter(w) + if boundary != "" { + mw.SetBoundary(boundary) + } contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary() w.writers[w.depth] = mw @@ -158,7 +161,11 @@ func (w *messageWriter) Write(p []byte) (int, error) { } func (w *messageWriter) writeString(s string) { - n, _ := io.WriteString(w.w, s) + if w.err != nil { // do nothing when in error + return + } + var n int + n, w.err = io.WriteString(w.w, s) w.n += int64(n) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 67be634..01e92ec 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -129,10 +129,10 @@ "revisionTime": "2015-07-16T17:19:45Z" }, { - "checksumSHA1": "iq5WdjScmTU+LfNoerLuwcnXpdM=", - "path": "gopkg.in/gomail.v2", - "revision": "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1", - "revisionTime": "2016-04-11T21:29:32Z" + "checksumSHA1": "SoHkt4HLtGvg9wZIDX6BJTqkDgs=", + "path": "gopkg.in/mail.v2", + "revision": "f59b9b83a4e522098e3d3eb94e6f81850ad6e973", + "revisionTime": "2018-11-12T22:01:18Z" }, { "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=",