// Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package setting import ( "net" "net/mail" "strings" "time" "code.gitea.io/gitea/modules/log" shellquote "github.com/kballard/go-shellquote" ) // Mailer represents mail service. type Mailer struct { // Mailer Name string From string EnvelopeFrom string OverrideEnvelopeFrom bool `ini:"-"` FromName string FromEmail string SendAsPlainText bool SubjectPrefix string // SMTP sender 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 SendmailArgs []string SendmailTimeout time.Duration SendmailConvertCRLF bool } // MailService the global mailer var MailService *Mailer func newMailService() { sec := Cfg.Section("mailer") // Check mailer setting. if !sec.Key("ENABLED").MustBool() { return } MailService = &Mailer{ Name: sec.Key("NAME").MustString(AppName), SendAsPlainText: sec.Key("SEND_AS_PLAIN_TEXT").MustBool(false), 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), SendmailConvertCRLF: sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true), } MailService.From = sec.Key("FROM").MustString(MailService.User) MailService.EnvelopeFrom = sec.Key("ENVELOPE_FROM").MustString("") // 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") && !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.") } switch MailService.EnvelopeFrom { case "": MailService.OverrideEnvelopeFrom = false case "<>": MailService.EnvelopeFrom = "" MailService.OverrideEnvelopeFrom = true default: parsed, err := mail.ParseAddress(MailService.EnvelopeFrom) if err != nil { log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err) } MailService.OverrideEnvelopeFrom = true MailService.EnvelopeFrom = parsed.Address } 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) } } log.Info("Mail Service Enabled") } func newRegisterMailService() { if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() { return } else if MailService == nil { log.Warn("Register Mail Service: Mail Service is not enabled") return } Service.RegisterEmailConfirm = true log.Info("Register Mail Service Enabled") } func newNotifyMailService() { if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() { return } else if MailService == nil { log.Warn("Notify Mail Service: Mail Service is not enabled") return } 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 }