89 changes: 53 additions & 36 deletions services/mailer/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,65 +147,82 @@ type smtpSender struct{}
func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
opts := setting.MailService

host, port, err := net.SplitHostPort(opts.Host)
if err != nil {
return err
var network string
var address string
if opts.Protocol == "smtp+unix" {
network = "unix"
address = opts.SMTPAddr
} else {
network = "tcp"
address = net.JoinHostPort(opts.SMTPAddr, opts.SMTPPort)
}

tlsconfig := &tls.Config{
InsecureSkipVerify: opts.SkipVerify,
ServerName: host,
conn, err := net.Dial(network, address)
if err != nil {
return fmt.Errorf("failed to establish network connection to SMTP server: %v", err)
}
defer conn.Close()

if opts.UseCertificate {
cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
if err != nil {
return err
var tlsconfig *tls.Config
if opts.Protocol == "smtps" || opts.Protocol == "smtp+startls" {
tlsconfig = &tls.Config{
InsecureSkipVerify: opts.ForceTrustServerCert,
ServerName: opts.SMTPAddr,
}
tlsconfig.Certificates = []tls.Certificate{cert}
}

conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
if err != nil {
return err
if opts.UseClientCert {
cert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientKeyFile)
if err != nil {
return fmt.Errorf("could not load SMTP client certificate: %v", err)
}
tlsconfig.Certificates = []tls.Certificate{cert}
}
}
defer conn.Close()

isSecureConn := opts.IsTLSEnabled || (strings.HasSuffix(port, "465"))
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if isSecureConn {
if opts.Protocol == "smtps" {
conn = tls.Client(conn, tlsconfig)
}

host := "localhost"
if opts.Protocol == "smtp+unix" {
host = opts.SMTPAddr
}
client, err := smtp.NewClient(conn, host)
if err != nil {
return fmt.Errorf("NewClient: %v", err)
return fmt.Errorf("could not initiate SMTP session: %v", err)
}

if !opts.DisableHelo {
if opts.EnableHelo {
hostname := opts.HeloHostname
if len(hostname) == 0 {
hostname, err = os.Hostname()
if err != nil {
return err
return fmt.Errorf("could not retrieve system hostname: %v", err)
}
}

if err = client.Hello(hostname); err != nil {
return fmt.Errorf("Hello: %v", err)
return fmt.Errorf("failed to issue HELO command: %v", err)
}
}

// If not using SMTPS, always use STARTTLS if available
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return fmt.Errorf("StartTLS: %v", err)
if opts.Protocol == "smtp+startls" {
hasStartTLS, _ := client.Extension("STARTTLS")
if hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return fmt.Errorf("failed to start TLS connection: %v", err)
}
} else {
log.Warn("StartTLS requested, but SMTP server does not support it; falling back to regular SMTP")
}
}

canAuth, options := client.Extension("AUTH")
if canAuth && len(opts.User) > 0 {
if len(opts.User) > 0 {
if !canAuth {
return fmt.Errorf("SMTP server does not support AUTH, but credentials provided")
}

var auth smtp.Auth

if strings.Contains(options, "CRAM-MD5") {
Expand All @@ -219,34 +236,34 @@ func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {

if auth != nil {
if err = client.Auth(auth); err != nil {
return fmt.Errorf("Auth: %v", err)
return fmt.Errorf("failed to authenticate SMTP: %v", err)
}
}
}

if opts.OverrideEnvelopeFrom {
if err = client.Mail(opts.EnvelopeFrom); err != nil {
return fmt.Errorf("Mail: %v", err)
return fmt.Errorf("failed to issue MAIL command: %v", err)
}
} else {
if err = client.Mail(from); err != nil {
return fmt.Errorf("Mail: %v", err)
return fmt.Errorf("failed to issue MAIL command: %v", err)
}
}

for _, rec := range to {
if err = client.Rcpt(rec); err != nil {
return fmt.Errorf("Rcpt: %v", err)
return fmt.Errorf("failed to issue RCPT command: %v", err)
}
}

w, err := client.Data()
if err != nil {
return fmt.Errorf("Data: %v", err)
return fmt.Errorf("failed to issue DATA command: %v", err)
} else if _, err = msg.WriteTo(w); err != nil {
return fmt.Errorf("WriteTo: %v", err)
return fmt.Errorf("SMTP write failed: %v", err)
} else if err = w.Close(); err != nil {
return fmt.Errorf("Close: %v", err)
return fmt.Errorf("SMTP close failed: %v", err)
}

return client.Quit()
Expand Down