fix: support SMTP STARTTLS mode and NTLM auth (#5426)

* fix: support SMTP STARTTLS mode and NTLM auth

Add explicit SMTP STARTTLS configuration for 587-style connections and keep SSL/TLS as the implicit TLS mode.

Prefer PLAIN when advertised, keep LOGIN compatibility, and add NTLM as a fallback for Exchange SMTP servers that require it after STARTTLS.

* fix: respect explicit SMTP encryption mode

* fix: preserve SMTP TLS compatibility
This commit is contained in:
Benson Yan
2026-06-24 12:45:39 +08:00
committed by GitHub
parent 9fc9c8f1e3
commit 2f23a66733
27 changed files with 959 additions and 52 deletions
+72 -40
View File
@@ -27,10 +27,52 @@ func shouldUseSMTPLoginAuth() bool {
}
func getSMTPAuth() smtp.Auth {
if shouldUseSMTPLoginAuth() {
return LoginAuth(SMTPAccount, SMTPToken)
return AutoSMTPAuth(SMTPAccount, SMTPToken)
}
func shouldAuthenticateSMTP() bool {
return SMTPAccount != "" && SMTPToken != ""
}
func smtpTLSConfig() *tls.Config {
return &tls.Config{
ServerName: SMTPServer,
InsecureSkipVerify: SMTPInsecureSkipVerify, // #nosec G402 -- admin-controlled SMTP compatibility option.
}
return smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
}
func newSMTPClient(addr string) (*smtp.Client, error) {
if SMTPSSLEnabled || (SMTPPort == 465 && !SMTPStartTLSEnabled) {
conn, err := tls.Dial("tcp", addr, smtpTLSConfig())
if err != nil {
return nil, err
}
client, err := smtp.NewClient(conn, SMTPServer)
if err != nil {
_ = conn.Close()
return nil, err
}
return client, nil
}
client, err := smtp.Dial(addr)
if err != nil {
return nil, err
}
if SMTPStartTLSEnabled {
startTLSSupported, _ := client.Extension("STARTTLS")
if !startTLSSupported {
_ = client.Close()
return nil, fmt.Errorf("SMTP server does not support STARTTLS")
}
if err := client.StartTLS(smtpTLSConfig()); err != nil {
_ = client.Close()
return nil, err
}
}
return client, nil
}
func SendEmail(subject string, receiver string, content string) error {
@@ -56,47 +98,37 @@ func SendEmail(subject string, receiver string, content string) error {
addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
to := strings.Split(receiver, ";")
var err error
if SMTPPort == 465 || SMTPSSLEnabled {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: SMTPServer,
}
conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTPServer, SMTPPort), tlsConfig)
if err != nil {
return err
}
client, err := smtp.NewClient(conn, SMTPServer)
if err != nil {
return err
}
defer client.Close()
client, err := newSMTPClient(addr)
if err != nil {
return err
}
defer client.Close()
if shouldAuthenticateSMTP() {
if err = client.Auth(auth); err != nil {
return err
}
if err = client.Mail(SMTPFrom); err != nil {
return err
}
receiverEmails := strings.Split(receiver, ";")
for _, receiver := range receiverEmails {
if err = client.Rcpt(receiver); err != nil {
return err
}
}
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write(mail)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
} else {
err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
}
if err = client.Mail(SMTPFrom); err != nil {
return err
}
for _, receiver := range to {
if err = client.Rcpt(receiver); err != nil {
return err
}
}
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write(mail)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
err = client.Quit()
if err != nil {
SysError(fmt.Sprintf("failed to send email to %s: %v", receiver, err))
}