code.gitea.io/gitea@v1.19.3/modules/setting/mailer.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package setting 5 6 import ( 7 "net" 8 "net/mail" 9 "strings" 10 "time" 11 12 "code.gitea.io/gitea/modules/log" 13 14 shellquote "github.com/kballard/go-shellquote" 15 ) 16 17 // Mailer represents mail service. 18 type Mailer struct { 19 // Mailer 20 Name string `ini:"NAME"` 21 From string `ini:"FROM"` 22 EnvelopeFrom string `ini:"ENVELOPE_FROM"` 23 OverrideEnvelopeFrom bool `ini:"-"` 24 FromName string `ini:"-"` 25 FromEmail string `ini:"-"` 26 SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` 27 SubjectPrefix string `ini:"SUBJECT_PREFIX"` 28 29 // SMTP sender 30 Protocol string `ini:"PROTOCOL"` 31 SMTPAddr string `ini:"SMTP_ADDR"` 32 SMTPPort string `ini:"SMTP_PORT"` 33 User string `ini:"USER"` 34 Passwd string `ini:"PASSWD"` 35 EnableHelo bool `ini:"ENABLE_HELO"` 36 HeloHostname string `ini:"HELO_HOSTNAME"` 37 ForceTrustServerCert bool `ini:"FORCE_TRUST_SERVER_CERT"` 38 UseClientCert bool `ini:"USE_CLIENT_CERT"` 39 ClientCertFile string `ini:"CLIENT_CERT_FILE"` 40 ClientKeyFile string `ini:"CLIENT_KEY_FILE"` 41 42 // Sendmail sender 43 SendmailPath string `ini:"SENDMAIL_PATH"` 44 SendmailArgs []string `ini:"-"` 45 SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"` 46 SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"` 47 } 48 49 // MailService the global mailer 50 var MailService *Mailer 51 52 func loadMailsFrom(rootCfg ConfigProvider) { 53 loadMailerFrom(rootCfg) 54 loadRegisterMailFrom(rootCfg) 55 loadNotifyMailFrom(rootCfg) 56 loadIncomingEmailFrom(rootCfg) 57 } 58 59 func loadMailerFrom(rootCfg ConfigProvider) { 60 sec := rootCfg.Section("mailer") 61 // Check mailer setting. 62 if !sec.Key("ENABLED").MustBool() { 63 return 64 } 65 66 // Handle Deprecations and map on to new configuration 67 // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version 68 // if these are removed, the warning will not be shown 69 deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0") 70 if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") { 71 if sec.Key("MAILER_TYPE").String() == "sendmail" { 72 sec.Key("PROTOCOL").MustString("sendmail") 73 } 74 } 75 76 deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0") 77 if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") { 78 givenHost := sec.Key("HOST").String() 79 addr, port, err := net.SplitHostPort(givenHost) 80 if err != nil && strings.Contains(err.Error(), "missing port in address") { 81 addr = givenHost 82 } else if err != nil { 83 log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err) 84 } 85 if addr == "" { 86 addr = "127.0.0.1" 87 } 88 sec.Key("SMTP_ADDR").MustString(addr) 89 sec.Key("SMTP_PORT").MustString(port) 90 } 91 92 deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0") 93 if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") { 94 if sec.Key("IS_TLS_ENABLED").MustBool() { 95 sec.Key("PROTOCOL").MustString("smtps") 96 } else { 97 sec.Key("PROTOCOL").MustString("smtp+starttls") 98 } 99 } 100 101 deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0") 102 if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") { 103 sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool()) 104 } 105 106 deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0") 107 if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") { 108 sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool()) 109 } 110 111 deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0") 112 if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") { 113 sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool()) 114 } 115 116 deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0") 117 if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") { 118 sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String()) 119 } 120 121 deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0") 122 if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") { 123 sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String()) 124 } 125 126 deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0") 127 if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") { 128 sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)) 129 } 130 131 if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" { 132 log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0") 133 sec.Key("PROTOCOL").SetValue("smtp+starttls") 134 } 135 136 // Set default values & validate 137 sec.Key("NAME").MustString(AppName) 138 sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"}) 139 sec.Key("ENABLE_HELO").MustBool(true) 140 sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false) 141 sec.Key("USE_CLIENT_CERT").MustBool(false) 142 sec.Key("SENDMAIL_PATH").MustString("sendmail") 143 sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute) 144 sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true) 145 sec.Key("FROM").MustString(sec.Key("USER").String()) 146 147 // Now map the values on to the MailService 148 MailService = &Mailer{} 149 if err := sec.MapTo(MailService); err != nil { 150 log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err) 151 } 152 153 // Infer SMTPPort if not set 154 if MailService.SMTPPort == "" { 155 switch MailService.Protocol { 156 case "smtp": 157 MailService.SMTPPort = "25" 158 case "smtps": 159 MailService.SMTPPort = "465" 160 case "smtp+starttls": 161 MailService.SMTPPort = "587" 162 } 163 } 164 165 // Infer Protocol 166 if MailService.Protocol == "" { 167 if strings.ContainsAny(MailService.SMTPAddr, "/\\") { 168 MailService.Protocol = "smtp+unix" 169 } else { 170 switch MailService.SMTPPort { 171 case "25": 172 MailService.Protocol = "smtp" 173 case "465": 174 MailService.Protocol = "smtps" 175 case "587": 176 MailService.Protocol = "smtp+starttls" 177 default: 178 log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort) 179 MailService.Protocol = "smtps" 180 if MailService.SMTPPort == "" { 181 MailService.SMTPPort = "465" 182 } 183 } 184 } 185 } 186 187 // we want to warn if users use SMTP on a non-local IP; 188 // we might as well take the opportunity to check that it has an IP at all 189 // This check is not needed for sendmail 190 switch MailService.Protocol { 191 case "sendmail": 192 var err error 193 MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String()) 194 if err != nil { 195 log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err) 196 } 197 case "smtp", "smtps", "smtp+starttls", "smtp+unix": 198 ips := tryResolveAddr(MailService.SMTPAddr) 199 if MailService.Protocol == "smtp" { 200 for _, ip := range ips { 201 if !ip.IsLoopback() { 202 log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended") 203 break 204 } 205 } 206 } 207 case "dummy": // just mention and do nothing 208 } 209 210 if MailService.From != "" { 211 parsed, err := mail.ParseAddress(MailService.From) 212 if err != nil { 213 log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err) 214 } 215 MailService.FromName = parsed.Name 216 MailService.FromEmail = parsed.Address 217 } else { 218 log.Error("no mailer.FROM provided, email system may not work.") 219 } 220 221 switch MailService.EnvelopeFrom { 222 case "": 223 MailService.OverrideEnvelopeFrom = false 224 case "<>": 225 MailService.EnvelopeFrom = "" 226 MailService.OverrideEnvelopeFrom = true 227 default: 228 parsed, err := mail.ParseAddress(MailService.EnvelopeFrom) 229 if err != nil { 230 log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err) 231 } 232 MailService.OverrideEnvelopeFrom = true 233 MailService.EnvelopeFrom = parsed.Address 234 } 235 236 log.Info("Mail Service Enabled") 237 } 238 239 func loadRegisterMailFrom(rootCfg ConfigProvider) { 240 if !rootCfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() { 241 return 242 } else if MailService == nil { 243 log.Warn("Register Mail Service: Mail Service is not enabled") 244 return 245 } 246 Service.RegisterEmailConfirm = true 247 log.Info("Register Mail Service Enabled") 248 } 249 250 func loadNotifyMailFrom(rootCfg ConfigProvider) { 251 if !rootCfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() { 252 return 253 } else if MailService == nil { 254 log.Warn("Notify Mail Service: Mail Service is not enabled") 255 return 256 } 257 Service.EnableNotifyMail = true 258 log.Info("Notify Mail Service Enabled") 259 } 260 261 func tryResolveAddr(addr string) []net.IP { 262 if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") { 263 addr = addr[1 : len(addr)-1] 264 } 265 ip := net.ParseIP(addr) 266 if ip != nil { 267 ips := make([]net.IP, 1) 268 ips[0] = ip 269 return ips 270 } 271 ips, err := net.LookupIP(addr) 272 if err != nil { 273 log.Warn("could not look up mailer.SMTP_ADDR: %v", err) 274 return make([]net.IP, 0) 275 } 276 return ips 277 }