]> source.dussan.org Git - gitea.git/commitdiff
Rework mailer settings (#18982)
authorClar Fon <15850505+clarfonthey@users.noreply.github.com>
Tue, 2 Aug 2022 05:24:18 +0000 (01:24 -0400)
committerGitHub <noreply@github.com>
Tue, 2 Aug 2022 05:24:18 +0000 (13:24 +0800)
* `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>
14 files changed:
cmd/admin.go
custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
modules/setting/mailer.go
options/locale/locale_en-US.ini
routers/install/install.go
routers/web/admin/auths.go
services/auth/source/smtp/auth.go
services/auth/source/smtp/source.go
services/auth/source/smtp/source_authenticate.go
services/forms/auth_form.go
services/forms/user_form.go
services/mailer/mailer.go
templates/install.tmpl

index 3375435749b053e8f37a628d307ebdb524e08fb4..6c2a8626c41a479342e31754b93d6bc3983340b3 100644 (file)
@@ -414,9 +414,9 @@ var (
                        Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
                },
                cli.StringFlag{
-                       Name:  "host",
+                       Name:  "addr",
                        Value: "",
-                       Usage: "SMTP Host",
+                       Usage: "SMTP Addr",
                },
                cli.IntFlag{
                        Name:  "port",
@@ -956,8 +956,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
                }
                conf.Auth = c.String("auth-type")
        }
-       if c.IsSet("host") {
-               conf.Host = c.String("host")
+       if c.IsSet("addr") {
+               conf.Addr = c.String("addr")
        }
        if c.IsSet("port") {
                conf.Port = c.Int("port")
index ac0c9e9c8670b9ba118a98abad014be8046cdfd3..367553f1fa44bf9d76b1a473f6ae4539b365ed43 100644 (file)
@@ -1503,30 +1503,42 @@ ROUTER = console
 ;; Prefix displayed before subject in mail
 ;SUBJECT_PREFIX =
 ;;
-;; Mail server
-;; Gmail: smtp.gmail.com:587
-;; QQ: smtp.qq.com:465
-;; As per RFC 8314 using Implicit TLS/SMTPS on port 465 (if supported) is recommended,
-;; otherwise STARTTLS on port 587 should be used.
-;HOST =
-;;
-;; Disable HELO operation when hostnames are different.
-;DISABLE_HELO =
-;;
-;; Custom hostname for HELO operation, if no value is provided, one is retrieved from system.
+;; Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy".
+;; - sendmail: use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems.
+;; - dummy: send email messages to the log as a testing phase.
+;; If your provider does not explicitly say which protocol it uses but does provide a port,
+;; you can set SMTP_PORT instead and this will be inferred.
+;; (Before 1.18, this was controlled via MAILER_TYPE and IS_TLS_ENABLED.)
+;PROTOCOL =
+;;
+;; Mail server address, e.g. smtp.gmail.com.
+;; For smtp+unix, this should be a path to a unix socket instead.
+;; (Before 1.18, this was combined with SMTP_PORT as HOST.)
+;SMTP_ADDR =
+;;
+;; Mail server port. Common ports are:
+;;   25:  insecure SMTP
+;;   465: SMTP Secure
+;;   587: StartTLS
+;; If no protocol is specified, it will be inferred by this setting.
+;; (Before 1.18, this was combined with SMTP_ADDR as HOST.)
+;SMTP_PORT =
+;;
+;; Enable HELO operation. Defaults to true.
+;ENABLE_HELO = true
+;;
+;; Custom hostname for HELO operation.
+;; If no value is provided, one is retrieved from system.
 ;HELO_HOSTNAME =
 ;;
-;; Whether or not to skip verification of certificates; `true` to disable verification. This option is unsafe. Consider adding the certificate to the system trust store instead.
-;SKIP_VERIFY = false
+;; If set to `true`, completely ignores server certificate validation errors.
+;; This option is unsafe. Consider adding the certificate to the system trust store instead.
+;FORCE_TRUST_SERVER_CERT = false
 ;;
-;; Use client certificate
-;USE_CERTIFICATE = false
-;CERT_FILE = custom/mailer/cert.pem
-;KEY_FILE = custom/mailer/key.pem
-;;
-;; Should SMTP connect with TLS, (if port ends with 465 TLS will always be used.)
-;; If this is false but STARTTLS is supported the connection will be upgraded to TLS opportunistically.
-;IS_TLS_ENABLED = false
+;; Use client certificate in connection.
+;USE_CLIENT_CERT = false
+;CLIENT_CERT_FILE = custom/mailer/cert.pem
+;CLIENT_KEY_FILE = custom/mailer/key.pem
 ;;
 ;; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
 ;FROM =
@@ -1534,19 +1546,15 @@ ROUTER = console
 ;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
 ;ENVELOPE_FROM =
 ;;
-;; Mailer user name and password
-;; Please Note: Authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via STARTTLS) or `HOST=localhost`.
+;; Mailer user name and password, if required by provider.
 ;USER =
 ;;
 ;; Use PASSWD = `your password` for quoting if you use special characters in the password.
 ;PASSWD =
 ;;
-;; Send mails as plain text
+;; Send mails only in plain text, without HTML alternative
 ;SEND_AS_PLAIN_TEXT = false
 ;;
-;; Set Mailer Type (either SMTP, sendmail or dummy to just send to the log)
-;MAILER_TYPE = smtp
-;;
 ;; Specify an alternative sendmail binary
 ;SENDMAIL_PATH = sendmail
 ;;
index e4e7ad7b1986391a698bd5f99e06b9132d3315a8..c6a4d989a6f61a7c37a6ce68b7d55df93a8ee937 100644 (file)
@@ -647,41 +647,35 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
 ## Mailer (`mailer`)
 
 - `ENABLED`: **false**: Enable to use a mail service.
-- `DISABLE_HELO`: **\<empty\>**: Disable HELO operation.
-- `HELO_HOSTNAME`: **\<empty\>**: Custom hostname for HELO operation.
-- `HOST`: **\<empty\>**: SMTP mail host address and port (example: smtp.gitea.io:587).
-  - As per RFC 8314, if supported, Implicit TLS/SMTPS on port 465 is recommended, otherwise opportunistic TLS via STARTTLS on port 587 should be used.
-- `IS_TLS_ENABLED` :  **false** : Forcibly use TLS to connect even if not on a default SMTPS port.
-  - Note, if the port ends with `465` Implicit TLS/SMTPS/SMTP over TLS will be used despite this setting.
-  - Otherwise if `IS_TLS_ENABLED=false` and the server supports `STARTTLS` this will be used. Thus if `STARTTLS` is preferred you should set `IS_TLS_ENABLED=false`.
-- `FROM`: **\<empty\>**: Mail from address, RFC 5322. This can be just an email address, or
-   the "Name" \<email@example.com\> format.
-- `ENVELOPE_FROM`: **\<empty\>**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address.
+- `PROTOCOL`: **\<empty\>**: Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._
+  - SMTP family, if your provider does not explicitly say which protocol it uses but does provide a port, you can set SMTP_PORT instead and this will be inferred.
+  - **sendmail** Use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems.
+  - **dummy** Send email messages to the log as a testing phase.
+  - Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`, `FROM`, `SUBJECT_PREFIX` and `SENDMAIL_PATH`.
+  - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`.
+- `SMTP_ADDR`: **\<empty\>**: Mail server address. e.g. smtp.gmail.com. For smtp+unix, this should be a path to a unix socket instead. _Before 1.18, this was combined with `SMTP_PORT` under the name `HOST`._
+- `SMTP_PORT`: **\<empty\>**: Mail server port. If no protocol is specified, it will be inferred by this setting. Common ports are listed below. _Before 1.18, this was combined with `SMTP_ADDR` under the name `HOST`._
+  - 25:  insecure SMTP
+  - 465: SMTP Secure
+  - 587: StartTLS
+- `USE_CLIENT_CERT`: **false**: Use client certificate for TLS/SSL.
+- `CLIENT_CERT_FILE`: **custom/mailer/cert.pem**: Client certificate file.
+- `CLIENT_KEY_FILE`: **custom/mailer/key.pem**: Client key file.
+- `FORCE_TRUST_SERVER_CERT`: **false**: If set to `true`, completely ignores server certificate validation errors. This option is unsafe. Consider adding the certificate to the system trust store instead.
 - `USER`: **\<empty\>**: Username of mailing user (usually the sender's e-mail address).
 - `PASSWD`: **\<empty\>**: Password of mailing user.  Use \`your password\` for quoting if you use special characters in the password.
-  - Please note: authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via `STARTTLS`) or `HOST=localhost`. See [Email Setup]({{< relref "doc/usage/email-setup.en-us.md" >}}) for more information.
-- `SEND_AS_PLAIN_TEXT`: **false**: Send mails as plain text.
-- `SKIP_VERIFY`: **false**: Whether or not to skip verification of certificates; `true` to disable verification.
-  - **Warning:** This option is unsafe. Consider adding the certificate to the system trust store instead.
-  - **Note:** Gitea only supports SMTP with STARTTLS.
-- `USE_CERTIFICATE`: **false**: Use client certificate.
-- `CERT_FILE`: **custom/mailer/cert.pem**
-- `KEY_FILE`: **custom/mailer/key.pem**
+  - Please note: authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via `STARTTLS`) or SMTP host is localhost. See [Email Setup]({{< relref "doc/usage/email-setup.en-us.md" >}}) for more information.
+- `ENABLE_HELO`: **true**: Enable HELO operation.
+- `HELO_HOSTNAME`: **(retrieved from system)**: HELO hostname.
+- `FROM`: **\<empty\>**: Mail from address, RFC 5322. This can be just an email address, or the "Name" \<email@example.com\> format.
+- `ENVELOPE_FROM`: **\<empty\>**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address.
 - `SUBJECT_PREFIX`: **\<empty\>**: Prefix to be placed before e-mail subject lines.
-- `MAILER_TYPE`: **smtp**: \[smtp, sendmail, dummy\]
-  - **smtp** Use SMTP to send mail
-  - **sendmail** Use the operating system's `sendmail` command instead of SMTP.
-   This is common on Linux systems.
-  - **dummy** Send email messages to the log as a testing phase.
-  - Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`,
-     `FROM`, `SUBJECT_PREFIX` and `SENDMAIL_PATH`.
-  - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`.
-- `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be
-   command or full path).
-- `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`)
+- `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path).
+- `SENDMAIL_ARGS`: **\<empty\>**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`)
 - `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail
 - `SENDMAIL_CONVERT_CRLF`: **true**: Most versions of sendmail prefer LF line endings rather than CRLF line endings. Set this to false if your version of sendmail requires CRLF line endings.
 - `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
+- `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative.
 
 ## Cache (`cache`)
 
index 8a26f8b0c49f86386e61b2a5fb51c1bcb3dec723..d6f1dae0f7156ae0777293a4a8a14e61ce04c106 100644 (file)
@@ -5,7 +5,9 @@
 package setting
 
 import (
+       "net"
        "net/mail"
+       "strings"
        "time"
 
        "code.gitea.io/gitea/modules/log"
@@ -23,18 +25,19 @@ type Mailer struct {
        FromName             string
        FromEmail            string
        SendAsPlainText      bool
-       MailerType           string
        SubjectPrefix        string
 
        // SMTP sender
-       Host              string
-       User, Passwd      string
-       DisableHelo       bool
-       HeloHostname      string
-       SkipVerify        bool
-       UseCertificate    bool
-       CertFile, KeyFile string
-       IsTLSEnabled      bool
+       Protocol             string
+       SMTPAddr             string
+       SMTPPort             string
+       User, Passwd         string
+       EnableHelo           bool
+       HeloHostname         string
+       ForceTrustServerCert bool
+       UseClientCert        bool
+       ClientCertFile       string
+       ClientKeyFile        string
 
        // Sendmail sender
        SendmailPath        string
@@ -56,19 +59,19 @@ func newMailService() {
        MailService = &Mailer{
                Name:            sec.Key("NAME").MustString(AppName),
                SendAsPlainText: sec.Key("SEND_AS_PLAIN_TEXT").MustBool(false),
-               MailerType:      sec.Key("MAILER_TYPE").In("", []string{"smtp", "sendmail", "dummy"}),
-
-               Host:           sec.Key("HOST").String(),
-               User:           sec.Key("USER").String(),
-               Passwd:         sec.Key("PASSWD").String(),
-               DisableHelo:    sec.Key("DISABLE_HELO").MustBool(),
-               HeloHostname:   sec.Key("HELO_HOSTNAME").String(),
-               SkipVerify:     sec.Key("SKIP_VERIFY").MustBool(),
-               UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
-               CertFile:       sec.Key("CERT_FILE").String(),
-               KeyFile:        sec.Key("KEY_FILE").String(),
-               IsTLSEnabled:   sec.Key("IS_TLS_ENABLED").MustBool(),
-               SubjectPrefix:  sec.Key("SUBJECT_PREFIX").MustString(""),
+
+               Protocol:             sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy"}),
+               SMTPAddr:             sec.Key("SMTP_ADDR").String(),
+               SMTPPort:             sec.Key("SMTP_PORT").String(),
+               User:                 sec.Key("USER").String(),
+               Passwd:               sec.Key("PASSWD").String(),
+               EnableHelo:           sec.Key("ENABLE_HELO").MustBool(true),
+               HeloHostname:         sec.Key("HELO_HOSTNAME").String(),
+               ForceTrustServerCert: sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false),
+               UseClientCert:        sec.Key("USE_CLIENT_CERT").MustBool(false),
+               ClientCertFile:       sec.Key("CLIENT_CERT_FILE").String(),
+               ClientKeyFile:        sec.Key("CLIENT_KEY_FILE").String(),
+               SubjectPrefix:        sec.Key("SUBJECT_PREFIX").MustString(""),
 
                SendmailPath:        sec.Key("SENDMAIL_PATH").MustString("sendmail"),
                SendmailTimeout:     sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
@@ -77,26 +80,123 @@ func newMailService() {
        MailService.From = sec.Key("FROM").MustString(MailService.User)
        MailService.EnvelopeFrom = sec.Key("ENVELOPE_FROM").MustString("")
 
-       // FIXME: DEPRECATED to be removed in v1.18.0
-       deprecatedSetting("mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT")
-       if sec.HasKey("ENABLE_HTML_ALTERNATIVE") {
-               MailService.SendAsPlainText = !sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "MAILER_TYPE", "mailer", "PROTOCOL")
+       if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
+               if sec.Key("MAILER_TYPE").String() == "sendmail" {
+                       MailService.Protocol = "sendmail"
+               }
        }
 
-       // FIXME: DEPRECATED to be removed in v1.18.0
-       deprecatedSetting("mailer", "USE_SENDMAIL", "mailer", "MAILER_TYPE")
-       if sec.HasKey("USE_SENDMAIL") {
-               if MailService.MailerType == "" && sec.Key("USE_SENDMAIL").MustBool(false) {
-                       MailService.MailerType = "sendmail"
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "HOST", "mailer", "SMTP_ADDR")
+       if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
+               givenHost := sec.Key("HOST").String()
+               addr, port, err := net.SplitHostPort(givenHost)
+               if err != nil {
+                       log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
                }
+               MailService.SMTPAddr = addr
+               MailService.SMTPPort = port
        }
 
-       parsed, err := mail.ParseAddress(MailService.From)
-       if err != nil {
-               log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
+       if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") {
+               if sec.Key("IS_TLS_ENABLED").MustBool() {
+                       MailService.Protocol = "smtps"
+               } else {
+                       MailService.Protocol = "smtp+startls"
+               }
+       }
+
+       if MailService.SMTPPort == "" {
+               switch MailService.Protocol {
+               case "smtp":
+                       MailService.SMTPPort = "25"
+               case "smtps":
+                       MailService.SMTPPort = "465"
+               case "smtp+startls":
+                       MailService.SMTPPort = "587"
+               }
+       }
+
+       if MailService.Protocol == "" {
+               if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
+                       MailService.Protocol = "smtp+unix"
+               } else {
+                       switch MailService.SMTPPort {
+                       case "25":
+                               MailService.Protocol = "smtp"
+                       case "465":
+                               MailService.Protocol = "smtps"
+                       case "587":
+                               MailService.Protocol = "smtp+startls"
+                       default:
+                               log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
+                               MailService.Protocol = "smtps"
+                       }
+               }
+       }
+
+       // we want to warn if users use SMTP on a non-local IP;
+       // we might as well take the opportunity to check that it has an IP at all
+       ips := tryResolveAddr(MailService.SMTPAddr)
+       if MailService.Protocol == "smtp" {
+               for _, ip := range ips {
+                       if !ip.IsLoopback() {
+                               log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
+                               break
+                       }
+               }
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO")
+       if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") {
+               MailService.EnableHelo = !sec.Key("DISABLE_HELO").MustBool()
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT")
+       if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") {
+               MailService.ForceTrustServerCert = sec.Key("SKIP_VERIFY").MustBool()
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT")
+       if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") {
+               MailService.UseClientCert = sec.Key("USE_CLIENT_CERT").MustBool()
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE")
+       if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") {
+               MailService.ClientCertFile = sec.Key("CERT_FILE").String()
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE")
+       if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") {
+               MailService.ClientKeyFile = sec.Key("KEY_FILE").String()
+       }
+
+       // FIXME: DEPRECATED to be removed in v1.19.0
+       deprecatedSetting("mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT")
+       if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
+               MailService.SendAsPlainText = !sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)
+       }
+
+       if MailService.From != "" {
+               parsed, err := mail.ParseAddress(MailService.From)
+               if err != nil {
+                       log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
+               }
+               MailService.FromName = parsed.Name
+               MailService.FromEmail = parsed.Address
+       } else {
+               log.Error("no mailer.FROM provided, email system may not work.")
        }
-       MailService.FromName = parsed.Name
-       MailService.FromEmail = parsed.Address
 
        switch MailService.EnvelopeFrom {
        case "":
@@ -105,7 +205,7 @@ func newMailService() {
                MailService.EnvelopeFrom = ""
                MailService.OverrideEnvelopeFrom = true
        default:
-               parsed, err = mail.ParseAddress(MailService.EnvelopeFrom)
+               parsed, err := mail.ParseAddress(MailService.EnvelopeFrom)
                if err != nil {
                        log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err)
                }
@@ -113,11 +213,8 @@ func newMailService() {
                MailService.EnvelopeFrom = parsed.Address
        }
 
-       if MailService.MailerType == "" {
-               MailService.MailerType = "smtp"
-       }
-
-       if MailService.MailerType == "sendmail" {
+       if MailService.Protocol == "sendmail" {
+               var err error
                MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
                if err != nil {
                        log.Error("Failed to parse Sendmail args: %s with error %v", CustomConf, err)
@@ -148,3 +245,21 @@ func newNotifyMailService() {
        Service.EnableNotifyMail = true
        log.Info("Notify Mail Service Enabled")
 }
+
+func tryResolveAddr(addr string) []net.IP {
+       if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
+               addr = addr[1 : len(addr)-1]
+       }
+       ip := net.ParseIP(addr)
+       if ip != nil {
+               ips := make([]net.IP, 1)
+               ips[0] = ip
+               return ips
+       }
+       ips, err := net.LookupIP(addr)
+       if err != nil {
+               log.Warn("could not look up mailer.SMTP_ADDR: %v", err)
+               return make([]net.IP, 0)
+       }
+       return ips
+}
index aad10ce87b1b209abbf52f00411eac1a82b80206..a774bf92fff427212ba67b44e3d8f36bd3fcd4d4 100644 (file)
@@ -179,7 +179,8 @@ log_root_path_helper = Log files will be written to this directory.
 
 optional_title = Optional Settings
 email_title = Email Settings
-smtp_host = SMTP Host
+smtp_addr = SMTP Host
+smtp_port = SMTP Port
 smtp_from = Send Email As
 smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format.
 mailer_user = SMTP Username
index 27c3509fdec5134d433f765942f6b81eb49597a0..8060414a1115ac50bb5f55d8bd3a2bb92be2bbfb 100644 (file)
@@ -133,7 +133,8 @@ func Install(ctx *context.Context) {
 
        // E-mail service settings
        if setting.MailService != nil {
-               form.SMTPHost = setting.MailService.Host
+               form.SMTPAddr = setting.MailService.SMTPAddr
+               form.SMTPPort = setting.MailService.SMTPPort
                form.SMTPFrom = setting.MailService.From
                form.SMTPUser = setting.MailService.User
                form.SMTPPasswd = setting.MailService.Passwd
@@ -421,9 +422,10 @@ func SubmitInstall(ctx *context.Context) {
                cfg.Section("server").Key("LFS_START_SERVER").SetValue("false")
        }
 
-       if len(strings.TrimSpace(form.SMTPHost)) > 0 {
+       if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
                cfg.Section("mailer").Key("ENABLED").SetValue("true")
-               cfg.Section("mailer").Key("HOST").SetValue(form.SMTPHost)
+               cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
+               cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
                cfg.Section("mailer").Key("FROM").SetValue(form.SMTPFrom)
                cfg.Section("mailer").Key("USER").SetValue(form.SMTPUser)
                cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd)
index 7ea8a52809e60f35e0901ead4f716fb9ec9e8a1a..b79b317555966e912e93c62e807078cdfc81cde1 100644 (file)
@@ -159,7 +159,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
 func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
        return &smtp.Source{
                Auth:           form.SMTPAuth,
-               Host:           form.SMTPHost,
+               Addr:           form.SMTPAddr,
                Port:           form.SMTPPort,
                AllowedDomains: form.AllowedDomains,
                ForceSMTPS:     form.ForceSMTPS,
index 8d0cbb11cdc9a389c4c8303e029eaa66fb78b549..a9e4b0e5f4454d38e93d0f6a80221fa62768461e 100644 (file)
@@ -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)
        }
index 5e69f912da35bee109b7ca27a732a7a6166dac85..b2286d42a0ff7ad13d95571e5cbee976705b9eea 100644 (file)
@@ -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
index dff24d494ee0f6543c8c06854cfb44ba5babfa4c..63fd3e55110b7eb5ef036c3c9529a7f3e6ab4c02 100644 (file)
@@ -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:
index 7e7c75675299be552a9d2a9f83e92e2b6592561b..9064be2cca38e14f666ddbd6bb9016624574c317 100644 (file)
@@ -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)"`
index 405b4a9a490f2a20f273186346ba096b1e2b0ab4..c8f2b02d8c80930d649e3594b5ff4a63a657374e 100644 (file)
@@ -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
index f4bc2ddc630cd43fb202b0098b5d5d94d77dc8fb..c86c54c748c59b76a783e2d2673cdc807d75ec78 100644 (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)
-       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 {
index 8f87a9e0d64c916c17bf2791126b0a10b7d55b91..36f58218d4638de5bdb0f3416941206280f0044d 100644 (file)
                                                        {{.locale.Tr "install.email_title"}}
                                                </summary>
                                                <div class="inline field">
-                                                       <label for="smtp_host">{{.locale.Tr "install.smtp_host"}}</label>
-                                                       <input id="smtp_host" name="smtp_host" value="{{.smtp_host}}">
+                                                       <label for="smtp_addr">{{.locale.Tr "install.smtp_addr"}}</label>
+                                                       <input id="smtp_addr" name="smtp_addr" value="{{.smtp_addr}}">
+                                               </div>
+                                               <div class="inline field">
+                                                       <label for="smtp_port">{{.locale.Tr "install.smtp_port"}}</label>
+                                                       <input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
                                                </div>
                                                <div class="inline field {{if .Err_SMTPFrom}}error{{end}}">
                                                        <label for="smtp_from">{{.locale.Tr "install.smtp_from"}}</label>