2014-03-19 16:27:27 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2017-06-07 04:14:31 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2014-03-19 16:27:27 +04:00
package mailer
import (
2019-02-03 05:06:52 +03:00
"bytes"
2022-08-28 12:43:25 +03:00
"context"
2014-10-10 02:08:07 +04:00
"crypto/tls"
2014-03-19 16:27:27 +04:00
"fmt"
2021-12-08 10:34:23 +03:00
"hash/fnv"
2015-09-17 08:54:12 +03:00
"io"
2014-10-10 02:08:07 +04:00
"net"
2014-03-19 16:27:27 +04:00
"net/smtp"
2015-02-19 10:47:05 +03:00
"os"
2016-12-25 16:55:22 +03:00
"os/exec"
2014-03-19 16:27:27 +04:00
"strings"
2015-09-17 08:54:12 +03:00
"time"
2017-09-11 09:33:28 +03:00
"code.gitea.io/gitea/modules/base"
2020-01-16 20:55:36 +03:00
"code.gitea.io/gitea/modules/graceful"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2020-05-03 02:04:31 +03:00
"code.gitea.io/gitea/modules/process"
2020-01-16 20:55:36 +03:00
"code.gitea.io/gitea/modules/queue"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2022-08-08 21:04:28 +03:00
"code.gitea.io/gitea/modules/templates"
2017-06-07 04:14:31 +03:00
2023-05-03 00:40:46 +03:00
ntlmssp "github.com/Azure/go-ntlmssp"
2017-06-07 04:14:31 +03:00
"github.com/jaytaylor/html2text"
"gopkg.in/gomail.v2"
2014-03-19 16:27:27 +04:00
)
2016-11-25 04:44:04 +03:00
// Message mail body and log info
2015-09-17 08:54:12 +03:00
type Message struct {
2020-01-16 20:55:36 +03:00
Info string // Message information for log purpose.
FromAddress string
FromDisplayName string
2023-01-22 17:23:52 +03:00
To string // Use only one recipient to prevent leaking of addresses
2023-01-14 18:57:10 +03:00
ReplyTo string
2020-01-16 20:55:36 +03:00
Subject string
Date time . Time
Body string
Headers map [ string ] [ ] string
2015-09-17 08:54:12 +03:00
}
2020-01-16 20:55:36 +03:00
// ToMessage converts a Message to gomail.Message
func ( m * Message ) ToMessage ( ) * gomail . Message {
2015-09-17 08:54:12 +03:00
msg := gomail . NewMessage ( )
2020-01-16 20:55:36 +03:00
msg . SetAddressHeader ( "From" , m . FromAddress , m . FromDisplayName )
2023-01-22 17:23:52 +03:00
msg . SetHeader ( "To" , m . To )
2023-01-14 18:57:10 +03:00
if m . ReplyTo != "" {
msg . SetHeader ( "Reply-To" , m . ReplyTo )
}
2020-01-16 20:55:36 +03:00
for header := range m . Headers {
msg . SetHeader ( header , m . Headers [ header ] ... )
}
2019-04-17 07:56:40 +03:00
if len ( setting . MailService . SubjectPrefix ) > 0 {
2020-01-16 20:55:36 +03:00
msg . SetHeader ( "Subject" , setting . MailService . SubjectPrefix + " " + m . Subject )
2019-04-17 07:56:40 +03:00
} else {
2020-01-16 20:55:36 +03:00
msg . SetHeader ( "Subject" , m . Subject )
2019-04-17 07:56:40 +03:00
}
2020-01-16 20:55:36 +03:00
msg . SetDateHeader ( "Date" , m . Date )
2019-04-02 18:45:54 +03:00
msg . SetHeader ( "X-Auto-Response-Suppress" , "All" )
2016-05-30 11:32:01 +03:00
2020-01-16 20:55:36 +03:00
plainBody , err := html2text . FromString ( m . Body )
2017-06-07 04:14:31 +03:00
if err != nil || setting . MailService . SendAsPlainText {
2020-01-16 20:55:36 +03:00
if strings . Contains ( base . TruncateString ( m . Body , 100 ) , "<html>" ) {
2017-06-07 04:14:31 +03:00
log . Warn ( "Mail contains HTML but configured to send as plain text." )
2016-05-30 11:50:20 +03:00
}
2017-06-07 04:14:31 +03:00
msg . SetBody ( "text/plain" , plainBody )
} else {
msg . SetBody ( "text/plain" , plainBody )
2020-01-16 20:55:36 +03:00
msg . AddAlternative ( "text/html" , m . Body )
2016-05-30 11:18:49 +03:00
}
2021-12-08 10:34:23 +03:00
if len ( msg . GetHeader ( "Message-ID" ) ) == 0 {
msg . SetHeader ( "Message-ID" , m . generateAutoMessageID ( ) )
}
2020-01-16 20:55:36 +03:00
return msg
}
// SetHeader adds additional headers to a message
func ( m * Message ) SetHeader ( field string , value ... string ) {
m . Headers [ field ] = value
}
2021-12-08 10:34:23 +03:00
func ( m * Message ) generateAutoMessageID ( ) string {
dateMs := m . Date . UnixNano ( ) / 1e6
h := fnv . New64 ( )
if len ( m . To ) > 0 {
2023-01-22 17:23:52 +03:00
_ , _ = h . Write ( [ ] byte ( m . To ) )
2021-12-08 10:34:23 +03:00
}
_ , _ = h . Write ( [ ] byte ( m . Subject ) )
_ , _ = h . Write ( [ ] byte ( m . Body ) )
return fmt . Sprintf ( "<autogen-%d-%016x@%s>" , dateMs , h . Sum64 ( ) , setting . Domain )
}
2020-01-16 20:55:36 +03:00
// NewMessageFrom creates new mail message object with custom From header.
2023-01-22 17:23:52 +03:00
func NewMessageFrom ( to , fromDisplayName , fromAddress , subject , body string ) * Message {
2020-01-16 20:55:36 +03:00
log . Trace ( "NewMessageFrom (body):\n%s" , body )
2015-09-17 08:54:12 +03:00
return & Message {
2020-01-16 20:55:36 +03:00
FromAddress : fromAddress ,
FromDisplayName : fromDisplayName ,
To : to ,
Subject : subject ,
Date : time . Now ( ) ,
Body : body ,
Headers : map [ string ] [ ] string { } ,
2015-09-17 08:54:12 +03:00
}
}
// NewMessage creates new mail message object with default From header.
2023-01-22 17:23:52 +03:00
func NewMessage ( to , subject , body string ) * Message {
2017-09-21 07:29:45 +03:00
return NewMessageFrom ( to , setting . MailService . FromName , setting . MailService . FromEmail , subject , body )
2015-09-17 08:54:12 +03:00
}
2015-08-20 08:56:25 +03:00
type loginAuth struct {
2015-08-20 14:12:55 +03:00
username , password string
2015-08-20 08:56:25 +03:00
}
2016-11-25 04:44:04 +03:00
// LoginAuth SMTP AUTH LOGIN Auth Handler
2015-08-20 08:56:25 +03:00
func LoginAuth ( username , password string ) smtp . Auth {
return & loginAuth { username , password }
}
2016-11-25 04:44:04 +03:00
// Start start SMTP login auth
2015-08-20 08:56:25 +03:00
func ( a * loginAuth ) Start ( server * smtp . ServerInfo ) ( string , [ ] byte , error ) {
return "LOGIN" , [ ] byte { } , nil
}
2016-11-25 04:44:04 +03:00
// Next next step of SMTP login auth
2015-08-20 08:56:25 +03:00
func ( a * loginAuth ) Next ( fromServer [ ] byte , more bool ) ( [ ] byte , error ) {
if more {
switch string ( fromServer ) {
case "Username:" :
return [ ] byte ( a . username ) , nil
case "Password:" :
return [ ] byte ( a . password ) , nil
default :
2017-06-18 03:30:04 +03:00
return nil , fmt . Errorf ( "unknown fromServer: %s" , string ( fromServer ) )
2015-08-20 08:56:25 +03:00
}
}
return nil , nil
}
2023-05-03 00:40:46 +03:00
type ntlmAuth struct {
username , password , domain string
domainNeeded bool
}
// NtlmAuth SMTP AUTH NTLM Auth Handler
func NtlmAuth ( username , password string ) smtp . Auth {
user , domain , domainNeeded := ntlmssp . GetDomain ( username )
return & ntlmAuth { user , password , domain , domainNeeded }
}
// Start starts SMTP NTLM Auth
func ( a * ntlmAuth ) Start ( server * smtp . ServerInfo ) ( string , [ ] byte , error ) {
negotiateMessage , err := ntlmssp . NewNegotiateMessage ( a . domain , "" )
return "NTLM" , negotiateMessage , err
}
// Next next step of SMTP ntlm auth
func ( a * ntlmAuth ) Next ( fromServer [ ] byte , more bool ) ( [ ] byte , error ) {
if more {
if len ( fromServer ) == 0 {
return nil , fmt . Errorf ( "ntlm ChallengeMessage is empty" )
}
authenticateMessage , err := ntlmssp . ProcessChallenge ( fromServer , a . username , a . password , a . domainNeeded )
return authenticateMessage , err
}
return nil , nil
}
2016-12-25 16:55:22 +03:00
// Sender SMTP mail sender
2022-01-20 20:46:10 +03:00
type smtpSender struct { }
2014-03-19 16:27:27 +04:00
2016-11-25 04:44:04 +03:00
// Send send email
2016-12-25 16:55:22 +03:00
func ( s * smtpSender ) Send ( from string , to [ ] string , msg io . WriterTo ) error {
2015-09-17 08:54:12 +03:00
opts := setting . MailService
2014-03-20 05:05:48 +04:00
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>
2022-08-02 08:24:18 +03:00
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 )
2014-10-10 02:08:07 +04:00
}
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>
2022-08-02 08:24:18 +03:00
conn , err := net . Dial ( network , address )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to establish network connection to SMTP server: %w" , err )
2014-10-10 02:08:07 +04:00
}
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>
2022-08-02 08:24:18 +03:00
defer conn . Close ( )
2014-12-18 14:34:30 +03:00
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>
2022-08-02 08:24:18 +03:00
var tlsconfig * tls . Config
2022-11-27 13:08:40 +03:00
if opts . Protocol == "smtps" || opts . Protocol == "smtp+starttls" {
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>
2022-08-02 08:24:18 +03:00
tlsconfig = & tls . Config {
InsecureSkipVerify : opts . ForceTrustServerCert ,
ServerName : opts . SMTPAddr ,
2015-02-13 10:33:55 +03:00
}
2014-12-18 14:34:30 +03:00
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>
2022-08-02 08:24:18 +03:00
if opts . UseClientCert {
cert , err := tls . LoadX509KeyPair ( opts . ClientCertFile , opts . ClientKeyFile )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "could not load SMTP client certificate: %w" , err )
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>
2022-08-02 08:24:18 +03:00
}
tlsconfig . Certificates = [ ] tls . Certificate { cert }
}
2014-12-18 14:34:30 +03:00
}
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>
2022-08-02 08:24:18 +03:00
if opts . Protocol == "smtps" {
2014-12-18 14:34:30 +03:00
conn = tls . Client ( conn , tlsconfig )
}
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>
2022-08-02 08:24:18 +03:00
host := "localhost"
if opts . Protocol == "smtp+unix" {
host = opts . SMTPAddr
}
2014-12-19 08:24:17 +03:00
client , err := smtp . NewClient ( conn , host )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "could not initiate SMTP session: %w" , err )
2014-10-10 02:08:07 +04:00
}
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>
2022-08-02 08:24:18 +03:00
if opts . EnableHelo {
2016-08-29 10:10:21 +03:00
hostname := opts . HeloHostname
2015-07-03 09:08:18 +03:00
if len ( hostname ) == 0 {
hostname , err = os . Hostname ( )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "could not retrieve system hostname: %w" , err )
2015-07-03 09:08:18 +03:00
}
}
2015-02-20 10:12:27 +03:00
2015-07-03 09:08:18 +03:00
if err = client . Hello ( hostname ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to issue HELO command: %w" , err )
2015-07-03 09:08:18 +03:00
}
2015-02-20 10:12:27 +03:00
}
2015-02-19 10:47:05 +03:00
2022-11-27 13:08:40 +03:00
if opts . Protocol == "smtp+starttls" {
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>
2022-08-02 08:24:18 +03:00
hasStartTLS , _ := client . Extension ( "STARTTLS" )
if hasStartTLS {
if err = client . StartTLS ( tlsconfig ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to start TLS connection: %w" , err )
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>
2022-08-02 08:24:18 +03:00
}
} else {
log . Warn ( "StartTLS requested, but SMTP server does not support it; falling back to regular SMTP" )
2014-12-18 14:34:30 +03:00
}
}
2014-12-19 08:24:17 +03:00
canAuth , options := client . Extension ( "AUTH" )
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>
2022-08-02 08:24:18 +03:00
if len ( opts . User ) > 0 {
if ! canAuth {
return fmt . Errorf ( "SMTP server does not support AUTH, but credentials provided" )
}
2014-12-18 14:58:18 +03:00
var auth smtp . Auth
2014-12-18 15:15:13 +03:00
if strings . Contains ( options , "CRAM-MD5" ) {
2015-09-17 08:54:12 +03:00
auth = smtp . CRAMMD5Auth ( opts . User , opts . Passwd )
2014-12-18 15:15:13 +03:00
} else if strings . Contains ( options , "PLAIN" ) {
2015-09-17 08:54:12 +03:00
auth = smtp . PlainAuth ( "" , opts . User , opts . Passwd , host )
2015-08-20 08:56:25 +03:00
} else if strings . Contains ( options , "LOGIN" ) {
2015-08-20 14:12:55 +03:00
// Patch for AUTH LOGIN
2015-09-17 08:54:12 +03:00
auth = LoginAuth ( opts . User , opts . Passwd )
2023-05-03 00:40:46 +03:00
} else if strings . Contains ( options , "NTLM" ) {
auth = NtlmAuth ( opts . User , opts . Passwd )
2014-12-18 14:58:18 +03:00
}
if auth != nil {
if err = client . Auth ( auth ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to authenticate SMTP: %w" , err )
2014-12-18 14:58:18 +03:00
}
2014-10-10 02:08:07 +04:00
}
}
2021-11-19 18:35:20 +03:00
if opts . OverrideEnvelopeFrom {
if err = client . Mail ( opts . EnvelopeFrom ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to issue MAIL command: %w" , err )
2021-11-19 18:35:20 +03:00
}
} else {
if err = client . Mail ( from ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to issue MAIL command: %w" , err )
2021-11-19 18:35:20 +03:00
}
2014-10-10 02:08:07 +04:00
}
2015-09-17 08:54:12 +03:00
for _ , rec := range to {
2014-10-10 02:08:07 +04:00
if err = client . Rcpt ( rec ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to issue RCPT command: %w" , err )
2014-10-10 02:08:07 +04:00
}
}
w , err := client . Data ( )
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "failed to issue DATA command: %w" , err )
2015-09-17 08:54:12 +03:00
} else if _ , err = msg . WriteTo ( w ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "SMTP write failed: %w" , err )
2015-09-17 08:54:12 +03:00
} else if err = w . Close ( ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "SMTP close failed: %w" , err )
2014-10-10 02:08:07 +04:00
}
return client . Quit ( )
}
2016-12-25 16:55:22 +03:00
// Sender sendmail mail sender
2022-01-20 20:46:10 +03:00
type sendmailSender struct { }
2016-12-25 16:55:22 +03:00
// Send send email
func ( s * sendmailSender ) Send ( from string , to [ ] string , msg io . WriterTo ) error {
var err error
var closeError error
var waitError error
2021-11-19 18:35:20 +03:00
envelopeFrom := from
if setting . MailService . OverrideEnvelopeFrom {
envelopeFrom = setting . MailService . EnvelopeFrom
}
args := [ ] string { "-f" , envelopeFrom , "-i" }
2017-10-25 22:27:25 +03:00
args = append ( args , setting . MailService . SendmailArgs ... )
2016-12-25 16:55:22 +03:00
args = append ( args , to ... )
log . Trace ( "Sending with: %s %v" , setting . MailService . SendmailPath , args )
2020-05-03 02:04:31 +03:00
desc := fmt . Sprintf ( "SendMail: %s %v" , setting . MailService . SendmailPath , args )
2021-11-30 23:06:32 +03:00
ctx , _ , finished := process . GetManager ( ) . AddContextTimeout ( graceful . GetManager ( ) . HammerContext ( ) , setting . MailService . SendmailTimeout , desc )
defer finished ( )
2020-05-03 02:04:31 +03:00
cmd := exec . CommandContext ( ctx , setting . MailService . SendmailPath , args ... )
2016-12-25 16:55:22 +03:00
pipe , err := cmd . StdinPipe ( )
if err != nil {
return err
}
2022-06-03 17:36:18 +03:00
process . SetSysProcAttribute ( cmd )
2016-12-25 16:55:22 +03:00
if err = cmd . Start ( ) ; err != nil {
2021-11-30 23:06:32 +03:00
_ = pipe . Close ( )
2016-12-25 16:55:22 +03:00
return err
}
2022-01-06 03:43:45 +03:00
if setting . MailService . SendmailConvertCRLF {
buf := & strings . Builder { }
_ , err = msg . WriteTo ( buf )
if err == nil {
_ , err = strings . NewReplacer ( "\r\n" , "\n" ) . WriteString ( pipe , buf . String ( ) )
}
} else {
_ , err = msg . WriteTo ( pipe )
}
2014-03-19 16:27:27 +04:00
2016-12-25 16:55:22 +03:00
// we MUST close the pipe or sendmail will hang waiting for more of the message
// Also we should wait on our sendmail command even if something fails
closeError = pipe . Close ( )
2016-12-30 10:26:05 +03:00
waitError = cmd . Wait ( )
2016-12-25 16:55:22 +03:00
if err != nil {
return err
} else if closeError != nil {
return closeError
} else {
return waitError
}
}
2019-02-03 05:06:52 +03:00
// Sender sendmail mail sender
2022-01-20 20:46:10 +03:00
type dummySender struct { }
2019-02-03 05:06:52 +03:00
// Send send email
func ( s * dummySender ) Send ( from string , to [ ] string , msg io . WriterTo ) error {
buf := bytes . Buffer { }
if _ , err := msg . WriteTo ( & buf ) ; err != nil {
return err
}
log . Info ( "Mail From: %s To: %v Body: %s" , from , to , buf . String ( ) )
return nil
}
2020-01-16 20:55:36 +03:00
var mailQueue queue . Queue
2015-09-17 08:54:12 +03:00
2016-12-25 16:55:22 +03:00
// Sender sender for sending mail synchronously
var Sender gomail . Sender
2016-11-25 04:44:04 +03:00
// NewContext start mail queue service
2022-08-28 12:43:25 +03:00
func NewContext ( ctx context . Context ) {
2016-02-21 01:32:34 +03:00
// Need to check if mailQueue is nil because in during reinstall (user had installed
2021-07-08 14:38:13 +03:00
// before but switched install lock off), this function will be called again
2016-02-21 01:32:34 +03:00
// while mail queue is already processing tasks, and produces a race condition.
if setting . MailService == nil || mailQueue != nil {
2015-09-17 08:54:12 +03:00
return
2014-03-19 16:27:27 +04:00
}
2015-09-17 08:54:12 +03:00
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>
2022-08-02 08:24:18 +03:00
switch setting . MailService . Protocol {
2019-02-03 05:06:52 +03:00
case "sendmail" :
Sender = & sendmailSender { }
case "dummy" :
Sender = & dummySender { }
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>
2022-08-02 08:24:18 +03:00
default :
Sender = & smtpSender { }
2016-12-25 16:55:22 +03:00
}
2022-01-23 00:22:14 +03:00
mailQueue = queue . CreateQueue ( "mail" , func ( data ... queue . Data ) [ ] queue . Data {
2020-01-16 20:55:36 +03:00
for _ , datum := range data {
msg := datum . ( * Message )
gomailMsg := msg . ToMessage ( )
log . Trace ( "New e-mail sending request %s: %s" , gomailMsg . GetHeader ( "To" ) , msg . Info )
if err := gomail . Send ( Sender , gomailMsg ) ; err != nil {
log . Error ( "Failed to send emails %s: %s - %v" , gomailMsg . GetHeader ( "To" ) , msg . Info , err )
} else {
log . Trace ( "E-mails sent %s: %s" , gomailMsg . GetHeader ( "To" ) , msg . Info )
}
}
2022-01-23 00:22:14 +03:00
return nil
2020-01-16 20:55:36 +03:00
} , & Message { } )
go graceful . GetManager ( ) . RunWithShutdownFns ( mailQueue . Run )
2022-08-08 21:04:28 +03:00
2022-08-28 12:43:25 +03:00
subjectTemplates , bodyTemplates = templates . Mailer ( ctx )
2014-03-19 16:27:27 +04:00
}
2019-11-18 11:08:20 +03:00
// SendAsync send mail asynchronously
2014-03-20 05:05:48 +04:00
func SendAsync ( msg * Message ) {
2021-04-02 13:25:13 +03:00
SendAsyncs ( [ ] * Message { msg } )
2014-03-19 16:27:27 +04:00
}
2019-11-18 11:08:20 +03:00
// SendAsyncs send mails asynchronously
func SendAsyncs ( msgs [ ] * Message ) {
2021-04-02 13:25:13 +03:00
if setting . MailService == nil {
log . Error ( "Mailer: SendAsyncs is being invoked but mail service hasn't been initialized" )
return
}
2019-11-18 11:08:20 +03:00
go func ( ) {
for _ , msg := range msgs {
2020-01-16 20:55:36 +03:00
_ = mailQueue . Push ( msg )
2019-11-18 11:08:20 +03:00
}
} ( )
}