Commit f2755982 by bergquist

tech: migrates to none deprecated mail lib

gomail is missing a maintainer so we are
switching to an active fork

ref https://github.com/go-gomail/gomail/issues/108

closes #7189
parent 2743e8be
...@@ -27,37 +27,7 @@ ...@@ -27,37 +27,7 @@
[[projects]] [[projects]]
name = "github.com/aws/aws-sdk-go" name = "github.com/aws/aws-sdk-go"
packages = [ packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"]
"aws",
"aws/awserr",
"aws/awsutil",
"aws/client",
"aws/client/metadata",
"aws/corehandlers",
"aws/credentials",
"aws/credentials/ec2rolecreds",
"aws/credentials/endpointcreds",
"aws/credentials/stscreds",
"aws/defaults",
"aws/ec2metadata",
"aws/endpoints",
"aws/request",
"aws/session",
"aws/signer/v4",
"internal/shareddefaults",
"private/protocol",
"private/protocol/ec2query",
"private/protocol/query",
"private/protocol/query/queryutil",
"private/protocol/rest",
"private/protocol/restxml",
"private/protocol/xml/xmlutil",
"service/cloudwatch",
"service/ec2",
"service/ec2/ec2iface",
"service/s3",
"service/sts"
]
revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28" revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
version = "v1.12.67" version = "v1.12.67"
...@@ -105,10 +75,7 @@ ...@@ -105,10 +75,7 @@
[[projects]] [[projects]]
name = "github.com/denisenkom/go-mssqldb" name = "github.com/denisenkom/go-mssqldb"
packages = [ packages = [".","internal/cp"]
".",
"internal/cp"
]
revision = "270bc3860bb94dd3a3ffd047377d746c5e276726" revision = "270bc3860bb94dd3a3ffd047377d746c5e276726"
[[projects]] [[projects]]
...@@ -150,12 +117,7 @@ ...@@ -150,12 +117,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/go-macaron/session" name = "github.com/go-macaron/session"
packages = [ packages = [".","memcache","postgres","redis"]
".",
"memcache",
"postgres",
"redis"
]
revision = "b8e286a0dba8f4999042d6b258daf51b31d08938" revision = "b8e286a0dba8f4999042d6b258daf51b31d08938"
[[projects]] [[projects]]
...@@ -190,13 +152,7 @@ ...@@ -190,13 +152,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = [ packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
]
revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237" revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237"
[[projects]] [[projects]]
...@@ -265,10 +221,7 @@ ...@@ -265,10 +221,7 @@
[[projects]] [[projects]]
name = "github.com/klauspost/compress" name = "github.com/klauspost/compress"
packages = [ packages = ["flate","gzip"]
"flate",
"gzip"
]
revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf" revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf"
version = "v1.2.1" version = "v1.2.1"
...@@ -299,10 +252,7 @@ ...@@ -299,10 +252,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/lib/pq" name = "github.com/lib/pq"
packages = [ packages = [".","oid"]
".",
"oid"
]
revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345" revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
[[projects]] [[projects]]
...@@ -337,11 +287,7 @@ ...@@ -337,11 +287,7 @@
[[projects]] [[projects]]
name = "github.com/opentracing/opentracing-go" name = "github.com/opentracing/opentracing-go"
packages = [ packages = [".","ext","log"]
".",
"ext",
"log"
]
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
version = "v1.0.2" version = "v1.0.2"
...@@ -353,12 +299,7 @@ ...@@ -353,12 +299,7 @@
[[projects]] [[projects]]
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
packages = [ packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"]
"api",
"api/prometheus/v1",
"prometheus",
"prometheus/promhttp"
]
revision = "967789050ba94deca04a5e84cce8ad472ce313c1" revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
version = "v0.9.0-pre1" version = "v0.9.0-pre1"
...@@ -371,22 +312,13 @@ ...@@ -371,22 +312,13 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/prometheus/common" name = "github.com/prometheus/common"
packages = [ packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
]
revision = "89604d197083d4781071d3c65855d24ecfb0a563" revision = "89604d197083d4781071d3c65855d24ecfb0a563"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/prometheus/procfs" name = "github.com/prometheus/procfs"
packages = [ packages = [".","internal/util","nfsd","xfs"]
".",
"internal/util",
"nfsd",
"xfs"
]
revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1" revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1"
[[projects]] [[projects]]
...@@ -403,21 +335,13 @@ ...@@ -403,21 +335,13 @@
[[projects]] [[projects]]
name = "github.com/smartystreets/assertions" name = "github.com/smartystreets/assertions"
packages = [ packages = [".","internal/go-render/render","internal/oglematchers"]
".",
"internal/go-render/render",
"internal/oglematchers"
]
revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea" revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea"
version = "1.8.1" version = "1.8.1"
[[projects]] [[projects]]
name = "github.com/smartystreets/goconvey" name = "github.com/smartystreets/goconvey"
packages = [ packages = ["convey","convey/gotest","convey/reporting"]
"convey",
"convey/gotest",
"convey/reporting"
]
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857" revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
version = "1.6.3" version = "1.6.3"
...@@ -429,21 +353,7 @@ ...@@ -429,21 +353,7 @@
[[projects]] [[projects]]
name = "github.com/uber/jaeger-client-go" name = "github.com/uber/jaeger-client-go"
packages = [ packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"]
".",
"config",
"internal/baggage",
"internal/baggage/remote",
"internal/spanlog",
"log",
"rpcmetrics",
"thrift-gen/agent",
"thrift-gen/baggage",
"thrift-gen/jaeger",
"thrift-gen/sampling",
"thrift-gen/zipkincore",
"utils"
]
revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7" revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7"
version = "v2.11.2" version = "v2.11.2"
...@@ -455,10 +365,7 @@ ...@@ -455,10 +365,7 @@
[[projects]] [[projects]]
name = "github.com/yudai/gojsondiff" name = "github.com/yudai/gojsondiff"
packages = [ packages = [".","formatter"]
".",
"formatter"
]
revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6"
version = "1.0.0" version = "1.0.0"
...@@ -471,37 +378,19 @@ ...@@ -471,37 +378,19 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = [ packages = ["md4","pbkdf2"]
"md4",
"pbkdf2"
]
revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b" revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"]
"context",
"context/ctxhttp",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"lex/httplex",
"trace"
]
revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
packages = [ packages = [".","google","internal","jws","jwt"]
".",
"google",
"internal",
"jws",
"jwt"
]
revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067" revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067"
[[projects]] [[projects]]
...@@ -519,39 +408,12 @@ ...@@ -519,39 +408,12 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/text" name = "golang.org/x/text"
packages = [ packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
[[projects]] [[projects]]
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
packages = [ packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
".",
"cloudsql",
"internal",
"internal/app_identity",
"internal/base",
"internal/datastore",
"internal/log",
"internal/modules",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0" version = "v1.0.0"
...@@ -563,32 +425,7 @@ ...@@ -563,32 +425,7 @@
[[projects]] [[projects]]
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [ packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"]
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"health",
"health/grpc_health_v1",
"internal",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport"
]
revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef" revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef"
version = "v1.9.2" version = "v1.9.2"
...@@ -611,12 +448,6 @@ ...@@ -611,12 +448,6 @@
version = "v1" version = "v1"
[[projects]] [[projects]]
branch = "v2"
name = "gopkg.in/gomail.v2"
packages = ["."]
revision = "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1"
[[projects]]
name = "gopkg.in/ini.v1" name = "gopkg.in/ini.v1"
packages = ["."] packages = ["."]
revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a" revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
...@@ -629,6 +460,12 @@ ...@@ -629,6 +460,12 @@
version = "v1.2.4" version = "v1.2.4"
[[projects]] [[projects]]
branch = "v2"
name = "gopkg.in/mail.v2"
packages = ["."]
revision = "5bc5c8bb07bd8d2803831fbaf8cbd630fcde2c68"
[[projects]]
name = "gopkg.in/redis.v2" name = "gopkg.in/redis.v2"
packages = ["."] packages = ["."]
revision = "e6179049628164864e6e84e973cfb56335748dea" revision = "e6179049628164864e6e84e973cfb56335748dea"
...@@ -643,6 +480,6 @@ ...@@ -643,6 +480,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "8a9e651fb8ea49dfd3c6ddc99bd3242b39e453ea9edd11321da79bd2c865e9d1" inputs-digest = "ad3c71fd3244369c313978e9e7464c7116faee764386439a17de0707a08103aa"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1
...@@ -172,7 +172,7 @@ ignored = [ ...@@ -172,7 +172,7 @@ ignored = [
name = "golang.org/x/sync" name = "golang.org/x/sync"
[[constraint]] [[constraint]]
name = "gopkg.in/gomail.v2" name = "gopkg.in/mail.v2"
branch = "v2" branch = "v2"
[[constraint]] [[constraint]]
......
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"gopkg.in/gomail.v2" gomail "gopkg.in/mail.v2"
) )
var mailQueue chan *Message var mailQueue chan *Message
......
package gomail package mail
import ( import (
"bytes" "bytes"
......
// Package gomail provides a simple interface to compose emails and to mail them // Package gomail provides a simple interface to compose emails and to mail them
// efficiently. // efficiently.
// //
// More info on Github: https://github.com/go-gomail/gomail // More info on Github: https://github.com/go-mail/mail
package gomail //
package mail
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)
}
package gomail package mail
import ( import (
"bytes" "bytes"
...@@ -18,6 +18,7 @@ type Message struct { ...@@ -18,6 +18,7 @@ type Message struct {
encoding Encoding encoding Encoding
hEncoder mimeEncoder hEncoder mimeEncoder
buf bytes.Buffer buf bytes.Buffer
boundary string
} }
type header map[string][]string type header map[string][]string
...@@ -97,6 +98,11 @@ const ( ...@@ -97,6 +98,11 @@ const (
Unencoded Encoding = "8bit" 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. // SetHeader sets a value to the given header field.
func (m *Message) SetHeader(field string, value ...string) { func (m *Message) SetHeader(field string, value ...string) {
m.encodeHeader(value) m.encodeHeader(value)
...@@ -183,9 +189,15 @@ func (m *Message) GetHeader(field string) []string { ...@@ -183,9 +189,15 @@ func (m *Message) GetHeader(field string) []string {
} }
// SetBody sets the body of the message. It replaces any content previously set // 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) { 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. // AddAlternative adds an alternative part to the message.
...@@ -226,8 +238,8 @@ func (m *Message) newPart(contentType string, f func(io.Writer) error, settings ...@@ -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, // A PartSetting can be used as an argument in Message.SetBody,
// Message.AddAlternative or Message.AddAlternativeWriter to configure the part // Message.SetBodyWriter, Message.AddAlternative or Message.AddAlternativeWriter
// added to a message. // to configure the part added to a message.
type PartSetting func(*part) type PartSetting func(*part)
// SetPartEncoding sets the encoding of the part added to the message. By // SetPartEncoding sets the encoding of the part added to the message. By
......
// +build go1.5 // +build go1.5
package gomail package mail
import ( import (
"mime" "mime"
......
// +build !go1.5 // +build !go1.5
package gomail package mail
import "gopkg.in/alexcesaro/quotedprintable.v3" import "gopkg.in/alexcesaro/quotedprintable.v3"
......
package gomail package mail
import ( import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/mail" stdmail "net/mail"
) )
// Sender is the interface that wraps the Send method. // 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 { ...@@ -36,7 +36,7 @@ func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
func Send(s Sender, msg ...*Message) error { func Send(s Sender, msg ...*Message) error {
for i, m := range msg { for i, m := range msg {
if err := send(s, m); err != nil { 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 { ...@@ -108,7 +108,7 @@ func addAddress(list []string, addr string) []string {
} }
func parseAddress(field string) (string, error) { func parseAddress(field string) (string, error) {
addr, err := mail.ParseAddress(field) addr, err := stdmail.ParseAddress(field)
if err != nil { if err != nil {
return "", fmt.Errorf("gomail: invalid address %q: %v", field, err) return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
} }
......
package gomail package mail
import ( import (
"crypto/tls" "crypto/tls"
...@@ -27,23 +27,39 @@ type Dialer struct { ...@@ -27,23 +27,39 @@ type Dialer struct {
// most cases since the authentication mechanism should use the STARTTLS // most cases since the authentication mechanism should use the STARTTLS
// extension instead. // extension instead.
SSL bool 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. // STARTTLS extension is used) or SSL connection.
TLSConfig *tls.Config 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. // LocalName is the hostname sent to the SMTP server with the HELO command.
// By default, "localhost" is sent. // By default, "localhost" is sent.
LocalName string 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 // NewDialer returns a new SMTP Dialer. The given parameters are used to connect
// to the SMTP server. // to the SMTP server.
func NewDialer(host string, port int, username, password string) *Dialer { func NewDialer(host string, port int, username, password string) *Dialer {
return &Dialer{ return &Dialer{
Host: host, Host: host,
Port: port, Port: port,
Username: username, Username: username,
Password: password, Password: password,
SSL: port == 465, SSL: port == 465,
Timeout: 10 * time.Second,
RetryFailure: true,
} }
} }
...@@ -55,10 +71,15 @@ func NewPlainDialer(host string, port int, username, password string) *Dialer { ...@@ -55,10 +71,15 @@ func NewPlainDialer(host string, port int, username, password string) *Dialer {
return NewDialer(host, port, username, password) 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 // Dial dials and authenticates to an SMTP server. The returned SendCloser
// should be closed when done using it. // should be closed when done using it.
func (d *Dialer) Dial() (SendCloser, error) { 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -72,14 +93,25 @@ func (d *Dialer) Dial() (SendCloser, error) { ...@@ -72,14 +93,25 @@ func (d *Dialer) Dial() (SendCloser, error) {
return nil, err return nil, err
} }
if d.Timeout > 0 {
conn.SetDeadline(time.Now().Add(d.Timeout))
}
if d.LocalName != "" { if d.LocalName != "" {
if err := c.Hello(d.LocalName); err != nil { if err := c.Hello(d.LocalName); err != nil {
return nil, err return nil, err
} }
} }
if !d.SSL { if !d.SSL && d.StartTLSPolicy != NoStartTLS {
if ok, _ := c.Extension("STARTTLS"); ok { 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 { if err := c.StartTLS(d.tlsConfig()); err != nil {
c.Close() c.Close()
return nil, err return nil, err
...@@ -111,7 +143,7 @@ func (d *Dialer) Dial() (SendCloser, error) { ...@@ -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 { func (d *Dialer) tlsConfig() *tls.Config {
...@@ -121,6 +153,47 @@ func (d *Dialer) tlsConfig() *tls.Config { ...@@ -121,6 +153,47 @@ func (d *Dialer) tlsConfig() *tls.Config {
return d.TLSConfig 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 { func addr(host string, port int) string {
return fmt.Sprintf("%s:%d", host, port) return fmt.Sprintf("%s:%d", host, port)
} }
...@@ -139,12 +212,29 @@ func (d *Dialer) DialAndSend(m ...*Message) error { ...@@ -139,12 +212,29 @@ func (d *Dialer) DialAndSend(m ...*Message) error {
type smtpSender struct { type smtpSender struct {
smtpClient 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 { 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 := 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. // This is probably due to a timeout, so reconnect and try again.
sc, derr := c.d.Dial() sc, derr := c.d.Dial()
if derr == nil { if derr == nil {
...@@ -154,6 +244,7 @@ func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error { ...@@ -154,6 +244,7 @@ func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
} }
} }
} }
return err return err
} }
...@@ -182,9 +273,8 @@ func (c *smtpSender) Close() error { ...@@ -182,9 +273,8 @@ func (c *smtpSender) Close() error {
// Stubbed out for tests. // Stubbed out for tests.
var ( var (
netDialTimeout = net.DialTimeout tlsClient = tls.Client
tlsClient = tls.Client smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
return smtp.NewClient(conn, host) return smtp.NewClient(conn, host)
} }
) )
......
package gomail package mail
import ( import (
"encoding/base64" "encoding/base64"
...@@ -28,15 +28,15 @@ func (w *messageWriter) writeMessage(m *Message) { ...@@ -28,15 +28,15 @@ func (w *messageWriter) writeMessage(m *Message) {
w.writeHeaders(m.header) w.writeHeaders(m.header)
if m.hasMixedPart() { if m.hasMixedPart() {
w.openMultipart("mixed") w.openMultipart("mixed", m.boundary)
} }
if m.hasRelatedPart() { if m.hasRelatedPart() {
w.openMultipart("related") w.openMultipart("related", m.boundary)
} }
if m.hasAlternativePart() { if m.hasAlternativePart() {
w.openMultipart("alternative") w.openMultipart("alternative", m.boundary)
} }
for _, part := range m.parts { for _, part := range m.parts {
w.writePart(part, m.charset) w.writePart(part, m.charset)
...@@ -77,8 +77,11 @@ type messageWriter struct { ...@@ -77,8 +77,11 @@ type messageWriter struct {
err error err error
} }
func (w *messageWriter) openMultipart(mimeType string) { func (w *messageWriter) openMultipart(mimeType, boundary string) {
mw := multipart.NewWriter(w) mw := multipart.NewWriter(w)
if boundary != "" {
mw.SetBoundary(boundary)
}
contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary() contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
w.writers[w.depth] = mw w.writers[w.depth] = mw
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment