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

@ -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")

View 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 =
;; 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 =
;;
;; Disable HELO operation when hostnames are different.
;DISABLE_HELO =
;; 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 =
;;
;; Custom hostname for HELO operation, if no value is provided, one is retrieved from system.
;; 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
;;

View 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`)

View 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
Protocol string
SMTPAddr string
SMTPPort string
User, Passwd string
DisableHelo bool
EnableHelo bool
HeloHostname string
SkipVerify bool
UseCertificate bool
CertFile, KeyFile string
IsTLSEnabled bool
ForceTrustServerCert bool
UseClientCert bool
ClientCertFile string
ClientKeyFile string
// Sendmail sender
SendmailPath string
@ -56,18 +59,18 @@ 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(),
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(),
DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
EnableHelo: sec.Key("ENABLE_HELO").MustBool(true),
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(),
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"),
@ -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
// 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.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
}
// 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") {
if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") {
MailService.SendAsPlainText = !sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)
}
// 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"
}
}
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.")
}
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
}

View 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

View 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)

View 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,

View 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)
}

View 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

View 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:

View 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)"`

View 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

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)
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,
}
if opts.UseCertificate {
cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
conn, err := net.Dial(network, address)
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
if opts.Protocol == "smtp+startls" {
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return fmt.Errorf("StartTLS: %v", err)
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 {

View file

@ -173,8 +173,12 @@
{{.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>