Repository navigation
fix: support SMTP STARTTLS mode and NTLM auth #5426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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. | ||||||||||||||||||||||||
|
Comment on lines
+37
to
+40
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Inspect declared Go/toolchain files and TLS configs to confirm explicit MinVersion coverage.
set -euo pipefail
printf 'Declared Go/toolchain files:\n'
fd -t f -HI 'go\.mod|go\.work|\.go-version|\.tool-versions|Dockerfile' . \
-x sh -c 'printf "\n--- %s ---\n" "$1"; sed -n "1,80p" "$1"' sh {}
printf '\nTLS configs in Go sources:\n'
rg -nP 'tls\.Config\s*\{' -C2 --type goRepository: QuantumNous/new-api Length of output: 7063 🏁 Script executed: # Find all tls.Config instantiations to assess scope
rg -nP 'tls\.Config\s*\{' --type go -A3Repository: QuantumNous/new-api Length of output: 811 🌐 Web query:
💡 Result: In Go 1.25, the default minimum TLS version is TLS 1.2 [1][2]. This default applies to both TLS clients and servers when the Citations:
🌐 Web query:
💡 Result: As of June 2026, the industry standard and security best practice for SMTP is to support and prefer TLS 1.2 and TLS 1.3, while disabling all older, insecure versions [1][2][3]. Minimum TLS Version Requirements The IETF has deprecated TLS 1.0 and 1.1 due to known security vulnerabilities [1][3]. Consequently, the current minimum recommendation is TLS 1.2 [4][5]. Organizations are strongly advised to transition to TLS 1.2 or TLS 1.3 for all email submission, access, and relay traffic [1][6][3]. Major Provider Standards Major email service providers, including Microsoft (Office 365) and Google (Gmail), have moved to enforce these standards [7][8]. - Microsoft 365: Has deprecated TLS 1.0 and 1.1, requiring TLS 1.2 or higher for inbound SMTP connections [7][8]. - Google (Gmail): Requires TLS 1.2+ for bulk senders and strongly encourages it for all connections to ensure deliverability and security [8][9]. - General Industry: Using versions older than TLS 1.2 is widely considered obsolete and is a common cause of delivery failures and security warnings [1][2][8]. Implementation Recommendations To maintain secure and reliable mail flow, administrators should: 1. Configure MTAs to support only TLS 1.2 and 1.3 [1][2][3]. 2. Explicitly disable support for SSL 2.0, 3.0, TLS 1.0, and TLS 1.1 [2][3]. 3. Implement modern security protocols such as MTA-STS (Mail Transfer Agent Strict Transport Security) to enforce TLS requirements and protect against downgrade attacks [2][10]. 4. Where DNSSEC is available, deploy DANE (DNS-based Authentication of Named Entities) to provide further verification of MX certificates [2]. Citations:
Set an explicit minimum TLS version to prevent environment variable downgrades. Although Go 1.25.1 defaults to TLS 1.2, explicitly setting Proposed fix func smtpTLSConfig() *tls.Config {
return &tls.Config{
ServerName: SMTPServer,
+ MinVersion: tls.VersionTLS12,
InsecureSkipVerify: SMTPInsecureSkipVerify, // `#nosec` G402 -- admin-controlled SMTP compatibility option.
}
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.43.0)[warning] 37-40: MinVersion (missing-ssl-minversion-go) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||
| return smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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 { | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if err = client.Mail(SMTPFrom); err != nil { | ||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| for _, receiver := range to { | ||||||||||||||||||||||||
| if err = client.Rcpt(receiver); err != nil { | ||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| err = smtp.SendMail(addr, auth, SMTPFrom, to, mail) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| 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)) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "errors" | ||
| "net/smtp" | ||
| "strings" | ||
|
|
||
| ntlmssp "github.com/Azure/go-ntlmssp" | ||
| ) | ||
|
|
||
| type smtpAutoAuth struct { | ||
| username string | ||
| password string | ||
| mech string | ||
| } | ||
|
|
||
| func AutoSMTPAuth(username, password string) smtp.Auth { | ||
| return &smtpAutoAuth{username: username, password: password} | ||
| } | ||
|
|
||
| func (a *smtpAutoAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { | ||
| useLoginAuth := SMTPForceAuthLogin | ||
| if !useLoginAuth && shouldUseSMTPLoginAuth() { | ||
| useLoginAuth = !(server != nil && len(server.Auth) == 1 && smtpServerSupportsAuth(server, "NTLM")) | ||
| } | ||
| if useLoginAuth { | ||
| a.mech = "LOGIN" | ||
| return "LOGIN", []byte{}, nil | ||
| } | ||
|
|
||
| switch { | ||
| case smtpServerSupportsAuth(server, "PLAIN"): | ||
| a.mech = "PLAIN" | ||
| return "PLAIN", []byte("\x00" + a.username + "\x00" + a.password), nil | ||
| case smtpServerSupportsAuth(server, "LOGIN"): | ||
| a.mech = "LOGIN" | ||
| return "LOGIN", []byte{}, nil | ||
| case smtpServerSupportsAuth(server, "NTLM"): | ||
| a.mech = "NTLM" | ||
| negotiateMessage, err := ntlmssp.NewNegotiateMessage("", "") | ||
| if err != nil { | ||
| return "", nil, err | ||
| } | ||
| return "NTLM", negotiateMessage, nil | ||
| default: | ||
| a.mech = "PLAIN" | ||
| return "PLAIN", []byte("\x00" + a.username + "\x00" + a.password), nil | ||
| } | ||
| } | ||
|
|
||
| func (a *smtpAutoAuth) Next(fromServer []byte, more bool) ([]byte, error) { | ||
| if !more { | ||
| return nil, nil | ||
| } | ||
|
|
||
| switch a.mech { | ||
| case "LOGIN": | ||
| switch string(fromServer) { | ||
| case "Username:": | ||
| return []byte(a.username), nil | ||
| case "Password:": | ||
| return []byte(a.password), nil | ||
| default: | ||
| return nil, errors.New("unknown SMTP AUTH LOGIN challenge") | ||
| } | ||
| case "NTLM": | ||
| return ntlmssp.NewAuthenticateMessage(fromServer, a.username, a.password, nil) | ||
| default: | ||
| return nil, errors.New("unexpected SMTP auth challenge") | ||
| } | ||
| } | ||
|
|
||
| func smtpServerSupportsAuth(server *smtp.ServerInfo, mechanism string) bool { | ||
| if server == nil { | ||
| return false | ||
| } | ||
| for _, auth := range server.Auth { | ||
| if strings.EqualFold(auth, mechanism) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.