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.
2014-03-19 16:27:27 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package mailer
import (
2014-10-10 02:08:07 +04:00
"crypto/tls"
2014-03-19 16:27:27 +04:00
"fmt"
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"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
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 {
Info string // Message information for log purpose.
* gomail . Message
}
// NewMessageFrom creates new mail message object with custom From header.
2017-06-07 04:14:31 +03:00
func NewMessageFrom ( to [ ] string , from , subject , body string ) * Message {
log . Trace ( "NewMessageFrom (body):\n%s" , body )
2016-07-15 19:36:39 +03:00
2015-09-17 08:54:12 +03:00
msg := gomail . NewMessage ( )
msg . SetHeader ( "From" , from )
msg . SetHeader ( "To" , to ... )
msg . SetHeader ( "Subject" , subject )
msg . SetDateHeader ( "Date" , time . Now ( ) )
2016-05-30 11:32:01 +03:00
2017-06-07 04:14:31 +03:00
plainBody , err := html2text . FromString ( body )
if err != nil || setting . MailService . SendAsPlainText {
if strings . Contains ( body [ : 100 ] , "<html>" ) {
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 )
msg . AddAlternative ( "text/html" , body )
2016-05-30 11:18:49 +03:00
}
2015-09-17 08:54:12 +03:00
return & Message {
Message : msg ,
}
}
// NewMessage creates new mail message object with default From header.
func NewMessage ( to [ ] string , subject , body string ) * Message {
return NewMessageFrom ( to , setting . MailService . From , subject , body )
}
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 :
2015-08-20 14:12:55 +03:00
return nil , fmt . Errorf ( "unknwon fromServer: %s" , string ( fromServer ) )
2015-08-20 08:56:25 +03:00
}
}
return nil , nil
}
2016-12-25 16:55:22 +03:00
// Sender SMTP mail sender
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
2015-09-17 08:54:12 +03:00
host , port , err := net . SplitHostPort ( opts . Host )
2014-10-10 02:08:07 +04:00
if err != nil {
return err
}
2014-12-18 14:34:30 +03:00
tlsconfig := & tls . Config {
2015-09-17 08:54:12 +03:00
InsecureSkipVerify : opts . SkipVerify ,
2014-10-10 02:08:07 +04:00
ServerName : host ,
}
2014-12-18 14:34:30 +03:00
2015-09-17 08:54:12 +03:00
if opts . UseCertificate {
cert , err := tls . LoadX509KeyPair ( opts . CertFile , opts . KeyFile )
2015-02-13 10:33:55 +03:00
if err != nil {
return err
}
tlsconfig . Certificates = [ ] tls . Certificate { cert }
2014-10-10 02:08:07 +04:00
}
2014-12-18 14:34:30 +03:00
2014-12-19 08:24:17 +03:00
conn , err := net . Dial ( "tcp" , net . JoinHostPort ( host , port ) )
if err != nil {
2014-12-18 14:34:30 +03:00
return err
}
defer conn . Close ( )
2014-12-19 08:24:17 +03:00
isSecureConn := false
2014-12-18 14:34:30 +03:00
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if strings . HasSuffix ( port , "465" ) {
conn = tls . Client ( conn , tlsconfig )
2014-12-19 08:24:17 +03:00
isSecureConn = true
2014-12-18 14:34:30 +03:00
}
2014-12-19 08:24:17 +03:00
client , err := smtp . NewClient ( conn , host )
if err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "NewClient: %v" , err )
2014-10-10 02:08:07 +04:00
}
2016-08-29 10:10:21 +03:00
if ! opts . DisableHelo {
hostname := opts . HeloHostname
2015-07-03 09:08:18 +03:00
if len ( hostname ) == 0 {
hostname , err = os . Hostname ( )
if err != nil {
return err
}
}
2015-02-20 10:12:27 +03:00
2015-07-03 09:08:18 +03:00
if err = client . Hello ( hostname ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Hello: %v" , 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
2017-03-15 03:52:01 +03:00
// If not using SMTPS, always use STARTTLS if available
2014-12-19 08:24:17 +03:00
hasStartTLS , _ := client . Extension ( "STARTTLS" )
if ! isSecureConn && hasStartTLS {
2014-12-18 14:34:30 +03:00
if err = client . StartTLS ( tlsconfig ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "StartTLS: %v" , err )
2014-12-18 14:34:30 +03:00
}
}
2014-12-19 08:24:17 +03:00
canAuth , options := client . Extension ( "AUTH" )
2015-09-17 08:54:12 +03:00
if canAuth && len ( opts . User ) > 0 {
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 )
2014-12-18 14:58:18 +03:00
}
if auth != nil {
if err = client . Auth ( auth ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Auth: %v" , err )
2014-12-18 14:58:18 +03:00
}
2014-10-10 02:08:07 +04:00
}
}
2015-09-17 08:54:12 +03:00
if err = client . Mail ( from ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Mail: %v" , err )
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 {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Rcpt: %v" , err )
2014-10-10 02:08:07 +04:00
}
}
w , err := client . Data ( )
if err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Data: %v" , err )
2015-09-17 08:54:12 +03:00
} else if _ , err = msg . WriteTo ( w ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "WriteTo: %v" , err )
2015-09-17 08:54:12 +03:00
} else if err = w . Close ( ) ; err != nil {
2015-12-19 10:44:34 +03:00
return fmt . Errorf ( "Close: %v" , err )
2014-10-10 02:08:07 +04:00
}
return client . Quit ( )
}
2016-12-25 16:55:22 +03:00
// Sender sendmail mail sender
type sendmailSender struct {
}
// Send send email
func ( s * sendmailSender ) Send ( from string , to [ ] string , msg io . WriterTo ) error {
var err error
var closeError error
var waitError error
args := [ ] string { "-F" , from , "-i" }
args = append ( args , to ... )
log . Trace ( "Sending with: %s %v" , setting . MailService . SendmailPath , args )
cmd := exec . Command ( setting . MailService . SendmailPath , args ... )
pipe , err := cmd . StdinPipe ( )
if err != nil {
return err
}
if err = cmd . Start ( ) ; err != nil {
return err
}
2016-12-30 10:26:05 +03:00
_ , 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
}
}
func processMailQueue ( ) {
2015-09-17 08:54:12 +03:00
for {
select {
case msg := <- mailQueue :
log . Trace ( "New e-mail sending request %s: %s" , msg . GetHeader ( "To" ) , msg . Info )
2016-12-25 16:55:22 +03:00
if err := gomail . Send ( Sender , msg . Message ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Error ( 3 , "Failed to send emails %s: %s - %v" , msg . GetHeader ( "To" ) , msg . Info , err )
2015-09-17 08:54:12 +03:00
} else {
log . Trace ( "E-mails sent %s: %s" , msg . GetHeader ( "To" ) , msg . Info )
2014-03-19 16:27:27 +04:00
}
}
2015-09-17 08:54:12 +03:00
}
}
2014-03-19 16:27:27 +04:00
2015-09-17 08:54:12 +03:00
var mailQueue chan * Message
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
2015-09-17 08:54:12 +03:00
func NewContext ( ) {
2016-02-21 01:32:34 +03:00
// Need to check if mailQueue is nil because in during reinstall (user had installed
// before but swithed install lock off), this function will be called again
// 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
2016-12-25 16:55:22 +03:00
if setting . MailService . UseSendmail {
Sender = & sendmailSender { }
} else {
Sender = & smtpSender { }
}
2015-09-17 08:54:12 +03:00
mailQueue = make ( chan * Message , setting . MailService . QueueLength )
go processMailQueue ( )
2014-03-19 16:27:27 +04:00
}
2016-11-25 04:44:04 +03:00
// SendAsync send mail asynchronous
2014-03-20 05:05:48 +04:00
func SendAsync ( msg * Message ) {
2014-03-19 16:27:27 +04:00
go func ( ) {
2014-03-20 05:05:48 +04:00
mailQueue <- msg
2014-03-19 16:27:27 +04:00
} ( )
}