diff options
author | Clar Fon <15850505+clarfonthey@users.noreply.github.com> | 2022-08-02 01:24:18 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-02 13:24:18 +0800 |
commit | 036dd8a788468e7730b29982747cc3cf8829ce86 (patch) | |
tree | 27d7bdd0a616a6f67a4e2140136cfb9c9b80296d /services | |
parent | ae3b88bef36179fb43ddcf2a14b46ca0969d8aad (diff) | |
download | gitea-036dd8a788468e7730b29982747cc3cf8829ce86.tar.gz gitea-036dd8a788468e7730b29982747cc3cf8829ce86.zip |
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>
Diffstat (limited to 'services')
-rw-r--r-- | services/auth/source/smtp/auth.go | 6 | ||||
-rw-r--r-- | services/auth/source/smtp/source.go | 2 | ||||
-rw-r--r-- | services/auth/source/smtp/source_authenticate.go | 2 | ||||
-rw-r--r-- | services/forms/auth_form.go | 2 | ||||
-rw-r--r-- | services/forms/user_form.go | 3 | ||||
-rw-r--r-- | services/mailer/mailer.go | 95 |
6 files changed, 64 insertions, 46 deletions
diff --git a/services/auth/source/smtp/auth.go b/services/auth/source/smtp/auth.go index 8d0cbb11cd..a9e4b0e5f4 100644 --- a/services/auth/source/smtp/auth.go +++ b/services/auth/source/smtp/auth.go @@ -58,10 +58,10 @@ var ErrUnsupportedLoginType = errors.New("Login source is unknown") func Authenticate(a smtp.Auth, source *Source) error { tlsConfig := &tls.Config{ InsecureSkipVerify: source.SkipVerify, - ServerName: source.Host, + ServerName: source.Addr, } - conn, err := net.Dial("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port))) + conn, err := net.Dial("tcp", net.JoinHostPort(source.Addr, strconv.Itoa(source.Port))) if err != nil { return err } @@ -71,7 +71,7 @@ func Authenticate(a smtp.Auth, source *Source) error { conn = tls.Client(conn, tlsConfig) } - client, err := smtp.NewClient(conn, source.Host) + client, err := smtp.NewClient(conn, source.Addr) if err != nil { return fmt.Errorf("failed to create NewClient: %w", err) } diff --git a/services/auth/source/smtp/source.go b/services/auth/source/smtp/source.go index 5e69f912da..b2286d42a0 100644 --- a/services/auth/source/smtp/source.go +++ b/services/auth/source/smtp/source.go @@ -19,7 +19,7 @@ import ( // Source holds configuration for the SMTP login source. type Source struct { Auth string - Host string + Addr string Port int AllowedDomains string `xorm:"TEXT"` ForceSMTPS bool diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index dff24d494e..63fd3e5511 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -32,7 +32,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str var auth smtp.Auth switch source.Auth { case PlainAuthentication: - auth = smtp.PlainAuth("", userName, password, source.Host) + auth = smtp.PlainAuth("", userName, password, source.Addr) case LoginAuthentication: auth = &loginAuthenticator{userName, password} case CRAMMD5Authentication: diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 7e7c756752..9064be2cca 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -45,7 +45,7 @@ type AuthenticationForm struct { IsActive bool IsSyncEnabled bool SMTPAuth string - SMTPHost string + SMTPAddr string SMTPPort int AllowedDomains string SecurityProtocol int `binding:"Range(0,2)"` diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 405b4a9a49..c8f2b02d8c 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -40,7 +40,8 @@ type InstallForm struct { AppURL string `binding:"Required"` LogRootPath string `binding:"Required"` - SMTPHost string + SMTPAddr string + SMTPPort string SMTPFrom string SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"` SMTPPasswd string diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index f4bc2ddc63..c86c54c748 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -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") { @@ -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 { |