Rework mailer settings (#18982)
* `PROTOCOL`: can be smtp, smtps, smtp+startls, smtp+unix, sendmail, dummy * `SMTP_ADDR`: domain for SMTP, or path to unix socket * `SMTP_PORT`: port for SMTP; defaults to 25 for `smtp`, 465 for `smtps`, and 587 for `smtp+startls` * `ENABLE_HELO`, `HELO_HOSTNAME`: reverse `DISABLE_HELO` to `ENABLE_HELO`; default to false + system hostname * `FORCE_TRUST_SERVER_CERT`: replace the unclear `SKIP_VERIFY` * `CLIENT_CERT_FILE`, `CLIENT_KEY_FILE`, `USE_CLIENT_CERT`: clarify client certificates here Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		
							parent
							
								
									ae3b88bef3
								
							
						
					
					
						commit
						036dd8a788
					
				| @ -414,9 +414,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", | ||||
| @ -956,8 +956,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") | ||||
|  | ||||
| @ -1503,30 +1503,42 @@ ROUTER = console | ||||
| ;; 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 = | ||||
| ;; Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy". | ||||
| ;; - sendmail: use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems. | ||||
| ;; - dummy: send email messages to the log as a testing phase. | ||||
| ;; 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. | ||||
| ;; (Before 1.18, this was controlled via MAILER_TYPE and IS_TLS_ENABLED.) | ||||
| ;PROTOCOL = | ||||
| ;; | ||||
| ;; Disable HELO operation when hostnames are different. | ||||
| ;DISABLE_HELO = | ||||
| ;; Mail server address, e.g. smtp.gmail.com. | ||||
| ;; For smtp+unix, this should be a path to a unix socket instead. | ||||
| ;; (Before 1.18, this was combined with SMTP_PORT as HOST.) | ||||
| ;SMTP_ADDR = | ||||
| ;; | ||||
| ;; Custom hostname for HELO operation, if no value is provided, one is retrieved from system. | ||||
| ;; 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. | ||||
| ;; (Before 1.18, this was combined with SMTP_ADDR as HOST.) | ||||
| ;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 | ||||
| ;; 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 | ||||
| ;; | ||||
| ;; Use client certificate | ||||
| ;USE_CERTIFICATE = false | ||||
| ;CERT_FILE = custom/mailer/cert.pem | ||||
| ;KEY_FILE = custom/mailer/key.pem | ||||
| ;; | ||||
| ;; 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 = | ||||
| @ -1534,19 +1546,15 @@ ROUTER = console | ||||
| ;; 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. | ||||
| ;PASSWD = | ||||
| ;; | ||||
| ;; Send mails as plain text | ||||
| ;; Send mails only in plain text, without HTML alternative | ||||
| ;SEND_AS_PLAIN_TEXT = false | ||||
| ;; | ||||
| ;; Set Mailer Type (either SMTP, sendmail or dummy to just send to the log) | ||||
| ;MAILER_TYPE = smtp | ||||
| ;; | ||||
| ;; Specify an alternative sendmail binary | ||||
| ;SENDMAIL_PATH = sendmail | ||||
| ;; | ||||
|  | ||||
| @ -647,41 +647,35 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type | ||||
| ## Mailer (`mailer`) | ||||
| 
 | ||||
| - `ENABLED`: **false**: Enable to use a mail service. | ||||
| - `DISABLE_HELO`: **\<empty\>**: Disable HELO operation. | ||||
| - `HELO_HOSTNAME`: **\<empty\>**: Custom hostname for HELO operation. | ||||
| - `HOST`: **\<empty\>**: SMTP mail host address and port (example: smtp.gitea.io:587). | ||||
|   - As per RFC 8314, if supported, Implicit TLS/SMTPS on port 465 is recommended, otherwise opportunistic TLS via STARTTLS on port 587 should be used. | ||||
| - `IS_TLS_ENABLED` :  **false** : Forcibly use TLS to connect even if not on a default SMTPS port. | ||||
|   - Note, if the port ends with `465` Implicit TLS/SMTPS/SMTP over TLS will be used despite this setting. | ||||
|   - Otherwise if `IS_TLS_ENABLED=false` and the server supports `STARTTLS` this will be used. Thus if `STARTTLS` is preferred you should set `IS_TLS_ENABLED=false`. | ||||
| - `FROM`: **\<empty\>**: Mail from address, RFC 5322. This can be just an email address, or | ||||
|    the "Name" \<email@example.com\> format. | ||||
| - `ENVELOPE_FROM`: **\<empty\>**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address. | ||||
| - `PROTOCOL`: **\<empty\>**: Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._ | ||||
|   - SMTP family, 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. | ||||
|   - **sendmail** Use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems. | ||||
|   - **dummy** Send email messages to the log as a testing phase. | ||||
|   - Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`, `FROM`, `SUBJECT_PREFIX` and `SENDMAIL_PATH`. | ||||
|   - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`. | ||||
| - `SMTP_ADDR`: **\<empty\>**: Mail server address. e.g. smtp.gmail.com. For smtp+unix, this should be a path to a unix socket instead. _Before 1.18, this was combined with `SMTP_PORT` under the name `HOST`._ | ||||
| - `SMTP_PORT`: **\<empty\>**: Mail server port. If no protocol is specified, it will be inferred by this setting. Common ports are listed below. _Before 1.18, this was combined with `SMTP_ADDR` under the name `HOST`._ | ||||
|   - 25:  insecure SMTP | ||||
|   - 465: SMTP Secure | ||||
|   - 587: StartTLS | ||||
| - `USE_CLIENT_CERT`: **false**: Use client certificate for TLS/SSL. | ||||
| - `CLIENT_CERT_FILE`: **custom/mailer/cert.pem**: Client certificate file. | ||||
| - `CLIENT_KEY_FILE`: **custom/mailer/key.pem**: Client key file. | ||||
| - `FORCE_TRUST_SERVER_CERT`: **false**: If set to `true`, completely ignores server certificate validation errors. This option is unsafe. Consider adding the certificate to the system trust store instead. | ||||
| - `USER`: **\<empty\>**: Username of mailing user (usually the sender's e-mail address). | ||||
| - `PASSWD`: **\<empty\>**: Password of mailing user.  Use \`your password\` for quoting if you use special characters in the password. | ||||
|   - Please note: authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via `STARTTLS`) or `HOST=localhost`. See [Email Setup]({{< relref "doc/usage/email-setup.en-us.md" >}}) for more information. | ||||
| - `SEND_AS_PLAIN_TEXT`: **false**: Send mails as plain text. | ||||
| - `SKIP_VERIFY`: **false**: Whether or not to skip verification of certificates; `true` to disable verification. | ||||
|   - **Warning:** This option is unsafe. Consider adding the certificate to the system trust store instead. | ||||
|   - **Note:** Gitea only supports SMTP with STARTTLS. | ||||
| - `USE_CERTIFICATE`: **false**: Use client certificate. | ||||
| - `CERT_FILE`: **custom/mailer/cert.pem** | ||||
| - `KEY_FILE`: **custom/mailer/key.pem** | ||||
|   - Please note: authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via `STARTTLS`) or SMTP host is localhost. See [Email Setup]({{< relref "doc/usage/email-setup.en-us.md" >}}) for more information. | ||||
| - `ENABLE_HELO`: **true**: Enable HELO operation. | ||||
| - `HELO_HOSTNAME`: **(retrieved from system)**: HELO hostname. | ||||
| - `FROM`: **\<empty\>**: Mail from address, RFC 5322. This can be just an email address, or the "Name" \<email@example.com\> format. | ||||
| - `ENVELOPE_FROM`: **\<empty\>**: Address set as the From address on the SMTP mail envelope. Set to `<>` to send an empty address. | ||||
| - `SUBJECT_PREFIX`: **\<empty\>**: Prefix to be placed before e-mail subject lines. | ||||
| - `MAILER_TYPE`: **smtp**: \[smtp, sendmail, dummy\] | ||||
|   - **smtp** Use SMTP to send mail | ||||
|   - **sendmail** Use the operating system's `sendmail` command instead of SMTP. | ||||
|    This is common on Linux systems. | ||||
|   - **dummy** Send email messages to the log as a testing phase. | ||||
|   - Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`, | ||||
|      `FROM`, `SUBJECT_PREFIX` and `SENDMAIL_PATH`. | ||||
|   - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`. | ||||
| - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be | ||||
|    command or full path). | ||||
| - `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`) | ||||
| - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be command or full path). | ||||
| - `SENDMAIL_ARGS`: **\<empty\>**: Specify any extra sendmail arguments. (NOTE: you should be aware that email addresses can look like options - if your `sendmail` command takes options you must set the option terminator `--`) | ||||
| - `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail | ||||
| - `SENDMAIL_CONVERT_CRLF`: **true**: Most versions of sendmail prefer LF line endings rather than CRLF line endings. Set this to false if your version of sendmail requires CRLF line endings. | ||||
| - `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]` | ||||
| - `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative. | ||||
| 
 | ||||
| ## Cache (`cache`) | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,9 @@ | ||||
| package setting | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"net/mail" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| @ -23,18 +25,19 @@ type Mailer struct { | ||||
| 	FromName             string | ||||
| 	FromEmail            string | ||||
| 	SendAsPlainText      bool | ||||
| 	MailerType           string | ||||
| 	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 | ||||
| @ -56,19 +59,19 @@ func newMailService() { | ||||
| 	MailService = &Mailer{ | ||||
| 		Name:            sec.Key("NAME").MustString(AppName), | ||||
| 		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", "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), | ||||
| @ -77,26 +80,123 @@ 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") { | ||||
| 		if MailService.MailerType == "" && sec.Key("USE_SENDMAIL").MustBool(false) { | ||||
| 			MailService.MailerType = "sendmail" | ||||
| 	// 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" | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	parsed, err := mail.ParseAddress(MailService.From) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err) | ||||
| 	// 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.") | ||||
| 	} | ||||
| 	MailService.FromName = parsed.Name | ||||
| 	MailService.FromEmail = parsed.Address | ||||
| 
 | ||||
| 	switch MailService.EnvelopeFrom { | ||||
| 	case "": | ||||
| @ -105,7 +205,7 @@ func newMailService() { | ||||
| 		MailService.EnvelopeFrom = "" | ||||
| 		MailService.OverrideEnvelopeFrom = true | ||||
| 	default: | ||||
| 		parsed, err = mail.ParseAddress(MailService.EnvelopeFrom) | ||||
| 		parsed, err := mail.ParseAddress(MailService.EnvelopeFrom) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err) | ||||
| 		} | ||||
| @ -113,11 +213,8 @@ func newMailService() { | ||||
| 		MailService.EnvelopeFrom = parsed.Address | ||||
| 	} | ||||
| 
 | ||||
| 	if MailService.MailerType == "" { | ||||
| 		MailService.MailerType = "smtp" | ||||
| 	} | ||||
| 
 | ||||
| 	if MailService.MailerType == "sendmail" { | ||||
| 	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) | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -179,7 +179,8 @@ log_root_path_helper = Log files will be written to this directory. | ||||
| 
 | ||||
| optional_title = Optional Settings | ||||
| email_title = Email Settings | ||||
| smtp_host = SMTP Host | ||||
| smtp_addr = SMTP Host | ||||
| smtp_port = SMTP Port | ||||
| smtp_from = Send Email As | ||||
| smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format. | ||||
| mailer_user = SMTP Username | ||||
|  | ||||
| @ -133,7 +133,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 | ||||
| 		form.SMTPPasswd = setting.MailService.Passwd | ||||
| @ -421,9 +422,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) | ||||
| 		cfg.Section("mailer").Key("FROM").SetValue(form.SMTPFrom) | ||||
| 		cfg.Section("mailer").Key("USER").SetValue(form.SMTPUser) | ||||
| 		cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd) | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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 | ||||
| 	} | ||||
| @ -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) | ||||
| 	} | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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)"` | ||||
|  | ||||
| @ -40,7 +40,8 @@ type InstallForm struct { | ||||
| 	AppURL       string `binding:"Required"` | ||||
| 	LogRootPath  string `binding:"Required"` | ||||
| 
 | ||||
| 	SMTPHost        string | ||||
| 	SMTPAddr        string | ||||
| 	SMTPPort        string | ||||
| 	SMTPFrom        string | ||||
| 	SMTPUser        string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"` | ||||
| 	SMTPPasswd      string | ||||
|  | ||||
| @ -147,65 +147,82 @@ type smtpSender struct{} | ||||
| func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error { | ||||
| 	opts := setting.MailService | ||||
| 
 | ||||
| 	host, port, err := net.SplitHostPort(opts.Host) | ||||
| 	var network string | ||||
| 	var address string | ||||
| 	if opts.Protocol == "smtp+unix" { | ||||
| 		network = "unix" | ||||
| 		address = opts.SMTPAddr | ||||
| 	} else { | ||||
| 		network = "tcp" | ||||
| 		address = net.JoinHostPort(opts.SMTPAddr, opts.SMTPPort) | ||||
| 	} | ||||
| 
 | ||||
| 	conn, err := net.Dial(network, address) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	tlsconfig := &tls.Config{ | ||||
| 		InsecureSkipVerify: opts.SkipVerify, | ||||
| 		ServerName:         host, | ||||
| 	} | ||||
| 
 | ||||
| 	if opts.UseCertificate { | ||||
| 		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		tlsconfig.Certificates = []tls.Certificate{cert} | ||||
| 	} | ||||
| 
 | ||||
| 	conn, err := net.Dial("tcp", net.JoinHostPort(host, port)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return fmt.Errorf("failed to establish network connection to SMTP server: %v", err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
| 
 | ||||
| 	isSecureConn := opts.IsTLSEnabled || (strings.HasSuffix(port, "465")) | ||||
| 	// Start TLS directly if the port ends with 465 (SMTPS protocol)
 | ||||
| 	if isSecureConn { | ||||
| 	var tlsconfig *tls.Config | ||||
| 	if opts.Protocol == "smtps" || opts.Protocol == "smtp+startls" { | ||||
| 		tlsconfig = &tls.Config{ | ||||
| 			InsecureSkipVerify: opts.ForceTrustServerCert, | ||||
| 			ServerName:         opts.SMTPAddr, | ||||
| 		} | ||||
| 
 | ||||
| 		if opts.UseClientCert { | ||||
| 			cert, err := tls.LoadX509KeyPair(opts.ClientCertFile, opts.ClientKeyFile) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("could not load SMTP client certificate: %v", err) | ||||
| 			} | ||||
| 			tlsconfig.Certificates = []tls.Certificate{cert} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if opts.Protocol == "smtps" { | ||||
| 		conn = tls.Client(conn, tlsconfig) | ||||
| 	} | ||||
| 
 | ||||
| 	host := "localhost" | ||||
| 	if opts.Protocol == "smtp+unix" { | ||||
| 		host = opts.SMTPAddr | ||||
| 	} | ||||
| 	client, err := smtp.NewClient(conn, host) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("NewClient: %v", err) | ||||
| 		return fmt.Errorf("could not initiate SMTP session: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !opts.DisableHelo { | ||||
| 	if opts.EnableHelo { | ||||
| 		hostname := opts.HeloHostname | ||||
| 		if len(hostname) == 0 { | ||||
| 			hostname, err = os.Hostname() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 				return fmt.Errorf("could not retrieve system hostname: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err = client.Hello(hostname); err != nil { | ||||
| 			return fmt.Errorf("Hello: %v", err) | ||||
| 			return fmt.Errorf("failed to issue HELO command: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// If not using SMTPS, always use STARTTLS if available
 | ||||
| 	hasStartTLS, _ := client.Extension("STARTTLS") | ||||
| 	if !isSecureConn && hasStartTLS { | ||||
| 		if err = client.StartTLS(tlsconfig); err != nil { | ||||
| 			return fmt.Errorf("StartTLS: %v", err) | ||||
| 	if opts.Protocol == "smtp+startls" { | ||||
| 		hasStartTLS, _ := client.Extension("STARTTLS") | ||||
| 		if hasStartTLS { | ||||
| 			if err = client.StartTLS(tlsconfig); err != nil { | ||||
| 				return fmt.Errorf("failed to start TLS connection: %v", err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			log.Warn("StartTLS requested, but SMTP server does not support it; falling back to regular SMTP") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	canAuth, options := client.Extension("AUTH") | ||||
| 	if canAuth && len(opts.User) > 0 { | ||||
| 	if len(opts.User) > 0 { | ||||
| 		if !canAuth { | ||||
| 			return fmt.Errorf("SMTP server does not support AUTH, but credentials provided") | ||||
| 		} | ||||
| 
 | ||||
| 		var auth smtp.Auth | ||||
| 
 | ||||
| 		if strings.Contains(options, "CRAM-MD5") { | ||||
| @ -219,34 +236,34 @@ func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error { | ||||
| 
 | ||||
| 		if auth != nil { | ||||
| 			if err = client.Auth(auth); err != nil { | ||||
| 				return fmt.Errorf("Auth: %v", err) | ||||
| 				return fmt.Errorf("failed to authenticate SMTP: %v", err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if opts.OverrideEnvelopeFrom { | ||||
| 		if err = client.Mail(opts.EnvelopeFrom); err != nil { | ||||
| 			return fmt.Errorf("Mail: %v", err) | ||||
| 			return fmt.Errorf("failed to issue MAIL command: %v", err) | ||||
| 		} | ||||
| 	} else { | ||||
| 		if err = client.Mail(from); err != nil { | ||||
| 			return fmt.Errorf("Mail: %v", err) | ||||
| 			return fmt.Errorf("failed to issue MAIL command: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, rec := range to { | ||||
| 		if err = client.Rcpt(rec); err != nil { | ||||
| 			return fmt.Errorf("Rcpt: %v", err) | ||||
| 			return fmt.Errorf("failed to issue RCPT command: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	w, err := client.Data() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Data: %v", err) | ||||
| 		return fmt.Errorf("failed to issue DATA command: %v", err) | ||||
| 	} else if _, err = msg.WriteTo(w); err != nil { | ||||
| 		return fmt.Errorf("WriteTo: %v", err) | ||||
| 		return fmt.Errorf("SMTP write failed: %v", err) | ||||
| 	} else if err = w.Close(); err != nil { | ||||
| 		return fmt.Errorf("Close: %v", err) | ||||
| 		return fmt.Errorf("SMTP close failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return client.Quit() | ||||
| @ -338,13 +355,13 @@ func NewContext() { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	switch setting.MailService.MailerType { | ||||
| 	case "smtp": | ||||
| 		Sender = &smtpSender{} | ||||
| 	switch setting.MailService.Protocol { | ||||
| 	case "sendmail": | ||||
| 		Sender = &sendmailSender{} | ||||
| 	case "dummy": | ||||
| 		Sender = &dummySender{} | ||||
| 	default: | ||||
| 		Sender = &smtpSender{} | ||||
| 	} | ||||
| 
 | ||||
| 	mailQueue = queue.CreateQueue("mail", func(data ...queue.Data) []queue.Data { | ||||
|  | ||||
| @ -173,8 +173,12 @@ | ||||
| 							{{.locale.Tr "install.email_title"}} | ||||
| 						</summary> | ||||
| 						<div class="inline field"> | ||||
| 							<label for="smtp_host">{{.locale.Tr "install.smtp_host"}}</label> | ||||
| 							<input id="smtp_host" name="smtp_host" value="{{.smtp_host}}"> | ||||
| 							<label for="smtp_addr">{{.locale.Tr "install.smtp_addr"}}</label> | ||||
| 							<input id="smtp_addr" name="smtp_addr" value="{{.smtp_addr}}"> | ||||
| 						</div> | ||||
| 						<div class="inline field"> | ||||
| 							<label for="smtp_port">{{.locale.Tr "install.smtp_port"}}</label> | ||||
| 							<input id="smtp_port" name="smtp_port" value="{{.smtp_port}}"> | ||||
| 						</div> | ||||
| 						<div class="inline field {{if .Err_SMTPFrom}}error{{end}}"> | ||||
| 							<label for="smtp_from">{{.locale.Tr "install.smtp_from"}}</label> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user