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