Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework mailer settings #18982

Merged
merged 23 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d60c438
Rework mailer settings (fixes #18901)
clarfonthey Mar 2, 2022
bc7b454
Do proper check if SMTP_ADDR is local IP
clarfonthey May 3, 2022
a4736f8
Reword insecure SMTP warning
clarfonthey May 8, 2022
7e0a30c
Merge branch 'main' into mailer-settings
wxiaoguang Jul 14, 2022
294ddd6
Merge branch 'main' into mailer-settings
wxiaoguang Jul 28, 2022
5f1d299
fix comments to "be removed in v1.19.0"
wxiaoguang Jul 28, 2022
dad722e
only use deprecated key if the new key doesn't exist
wxiaoguang Jul 28, 2022
ae30251
sync document and options
wxiaoguang Jul 28, 2022
8d20702
fix smtp fields in installation page
wxiaoguang Jul 28, 2022
360e32e
fix markdown lint
wxiaoguang Jul 28, 2022
12c00fd
fix i18n
wxiaoguang Jul 28, 2022
4ecb4e8
Update docs/content/doc/advanced/config-cheat-sheet.en-us.md
wxiaoguang Jul 28, 2022
466d6ba
Update custom/conf/app.example.ini
wxiaoguang Jul 28, 2022
4fbdc26
Update config-cheat-sheet.en-us.md
clarfonthey Jul 29, 2022
45df0a6
Update app.example.ini
clarfonthey Jul 29, 2022
be36a99
Update docs to make conversion between settings clearer
clarfonthey Jul 29, 2022
c14ec6c
fix lint errors
wxiaoguang Jul 30, 2022
60cf9a8
Merge branch 'main' into mailer-settings
wxiaoguang Jul 30, 2022
8e0d486
Merge branch 'main' into mailer-settings
lunny Jul 30, 2022
0556044
Merge branch 'main' into mailer-settings
lunny Jul 31, 2022
48d7743
Merge branch 'main' into mailer-settings
wxiaoguang Aug 1, 2022
06792df
Merge branch 'main' into mailer-settings
lunny Aug 1, 2022
8e3f70b
Merge branch 'main' into mailer-settings
wxiaoguang Aug 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 =
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
;
;; Mail server address, e.g. smtp.gmail.com.
;; For smtp+unix, this should be a path to a unix socket instead.
;SMTP_ADDR =
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
;;
;; 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
173 changes: 144 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,108 @@ 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
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
}

// 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("connect via insecure SMTP to non-local address")
clarfonthey marked this conversation as resolved.
Show resolved Hide resolved
break
}
}
}

// 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 +214,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 Expand Up @@ -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
}
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)
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
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
Loading