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