Skip to content

Commit

Permalink
Rework mailer settings (fixes #18901)
Browse files Browse the repository at this point in the history
  • Loading branch information
clarfonthey committed Apr 27, 2022
1 parent af09136 commit d60c438
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 103 deletions.
8 changes: 4 additions & 4 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,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",
Expand Down Expand Up @@ -936,8 +936,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")
Expand Down
52 changes: 29 additions & 23 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1503,39 +1503,45 @@ PATH =
;; 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".
;; 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.
;PROTOCOL =
;
;; Mail server address, e.g. smtp.gmail.com.
;; For smtp+unix, this should be a path to a unix socket instead.
;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.
;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
;;
;; Use client certificate
;USE_CERTIFICATE = false
;CERT_FILE = custom/mailer/cert.pem
;KEY_FILE = custom/mailer/key.pem
;; 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
;;
;; 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 =
;;
;; 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.
Expand Down
156 changes: 127 additions & 29 deletions modules/setting/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package setting

import (
"net"
"net/mail"
"strings"
"time"

"code.gitea.io/gitea/modules/log"
Expand All @@ -27,14 +29,16 @@ type Mailer struct {
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
Expand All @@ -58,17 +62,18 @@ func newMailService() {
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"}),
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),
Expand All @@ -77,12 +82,6 @@ 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") {
Expand All @@ -91,6 +90,109 @@ func newMailService() {
}
}

if MailService.MailerType == "" {
MailService.MailerType = "smtp"
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "HOST", "mailer", "SMTP_ADDR")
if sec.HasKey("HOST") {
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.18.0
deprecatedSetting("mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL")
if sec.HasKey("IS_TLS_ENABLED") {
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.MailerType == "smtp" && 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.Fatal("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = \"%s\"", MailService.SMTPPort)
}
}
}

if MailService.Protocol == "smtp" {
switch MailService.SMTPAddr {
case "localhost":
case "127.0.0.1":
case "::1":
case "[::1]":
// this is a local address, so, we're fine
break
default:
log.Warn("connect via insecure SMTP to non-local address")
}
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO")
if sec.HasKey("DISABLE_HELO") {
MailService.EnableHelo = !sec.Key("DISABLE_HELO").MustBool()
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT")
if sec.HasKey("SKIP_VERIFY") {
MailService.ForceTrustServerCert = sec.Key("SKIP_VERIFY").MustBool()
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT")
if sec.HasKey("USE_CERTIFICATE") {
MailService.UseClientCert = sec.Key("USE_CLIENT_CERT").MustBool()
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE")
if sec.HasKey("CERT_FILE") {
MailService.ClientCertFile = sec.Key("CERT_FILE").String()
}

// FIXME: DEPRECATED to be removed in v1.18.0
deprecatedSetting("mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE")
if sec.HasKey("KEY_FILE") {
MailService.ClientKeyFile = sec.Key("KEY_FILE").String()
}

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

parsed, err := mail.ParseAddress(MailService.From)
if err != nil {
log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
Expand All @@ -113,10 +215,6 @@ func newMailService() {
MailService.EnvelopeFrom = parsed.Address
}

if MailService.MailerType == "" {
MailService.MailerType = "smtp"
}

if MailService.MailerType == "sendmail" {
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
if err != nil {
Expand Down
8 changes: 5 additions & 3 deletions routers/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,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
}
Expand Down Expand Up @@ -418,9 +419,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)
Expand Down
2 changes: 1 addition & 1 deletion routers/web/admin/auths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions services/auth/source/smtp/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion services/auth/source/smtp/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion services/auth/source/smtp/source_authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion services/forms/auth_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)"`
Expand Down
3 changes: 2 additions & 1 deletion services/forms/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit d60c438

Please sign in to comment.