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

@ -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.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", "MAILER_TYPE", "mailer", "PROTOCOL")
if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") {
if sec.Key("MAILER_TYPE").String() == "sendmail" {
MailService.Protocol = "sendmail"
}
}
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", "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") && !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
}