Rework mailer settings (#18982)

* `PROTOCOL`: can be smtp, smtps, smtp+startls, smtp+unix, sendmail, dummy
* `SMTP_ADDR`: domain for SMTP, or path to unix socket
* `SMTP_PORT`: port for SMTP; defaults to 25 for `smtp`, 465 for `smtps`, and 587 for `smtp+startls`
* `ENABLE_HELO`, `HELO_HOSTNAME`: reverse `DISABLE_HELO` to `ENABLE_HELO`; default to false + system hostname
* `FORCE_TRUST_SERVER_CERT`: replace the unclear `SKIP_VERIFY`
* `CLIENT_CERT_FILE`, `CLIENT_KEY_FILE`, `USE_CLIENT_CERT`: clarify client certificates here

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
Clar Fon 2022-08-02 01:24:18 -04:00 committed by GitHub
parent ae3b88bef3
commit 036dd8a788
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 300 additions and 158 deletions

View file

@ -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)
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)
}
conn, err := net.Dial(network, address)
if err != nil {
return err
}
tlsconfig := &tls.Config{
InsecureSkipVerify: opts.SkipVerify,
ServerName: host,
}
if opts.UseCertificate {
cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
if err != nil {
return err
}
tlsconfig.Certificates = []tls.Certificate{cert}
}
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
if err != nil {
return err
return fmt.Errorf("failed to establish network connection to SMTP server: %v", err)
}
defer conn.Close()
isSecureConn := opts.IsTLSEnabled || (strings.HasSuffix(port, "465"))
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if isSecureConn {
var tlsconfig *tls.Config
if opts.Protocol == "smtps" || opts.Protocol == "smtp+startls" {
tlsconfig = &tls.Config{
InsecureSkipVerify: opts.ForceTrustServerCert,
ServerName: opts.SMTPAddr,
}
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}
}
}
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") {
@ -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()
@ -338,13 +355,13 @@ func NewContext() {
return
}
switch setting.MailService.MailerType {
case "smtp":
Sender = &smtpSender{}
switch setting.MailService.Protocol {
case "sendmail":
Sender = &sendmailSender{}
case "dummy":
Sender = &dummySender{}
default:
Sender = &smtpSender{}
}
mailQueue = queue.CreateQueue("mail", func(data ...queue.Data) []queue.Data {