2014-03-19 16:27:27 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// 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"
2014-03-19 16:27:27 +04:00
"strings"
2015-09-17 08:54:12 +03:00
"time"
2016-05-30 11:32:01 +03:00
"github.com/jaytaylor/html2text"
2015-09-17 08:54:12 +03:00
"gopkg.in/gomail.v2"
2015-08-20 14:12:55 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2014-03-19 16:27:27 +04:00
)
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.
2016-05-30 11:32:01 +03:00
func NewMessageFrom ( to [ ] string , from , subject , htmlBody string ) * Message {
2016-07-15 19:36:39 +03:00
log . Trace ( "NewMessageFrom (htmlBody):\n%s" , htmlBody )
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
body , err := html2text . FromString ( htmlBody )
2016-05-30 11:18:49 +03:00
if err != nil {
2016-05-30 11:32:01 +03:00
log . Error ( 4 , "html2text.FromString: %v" , err )
msg . SetBody ( "text/html" , htmlBody )
2016-05-30 11:18:49 +03:00
} else {
msg . SetBody ( "text/plain" , body )
2016-05-30 11:50:20 +03:00
if setting . MailService . EnableHTMLAlternative {
msg . AddAlternative ( "text/html" , htmlBody )
}
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
}
2015-08-20 14:12:55 +03:00
// SMTP AUTH LOGIN Auth Handler
2015-08-20 08:56:25 +03:00
func LoginAuth ( username , password string ) smtp . Auth {
return & loginAuth { username , password }
}
func ( a * loginAuth ) Start ( server * smtp . ServerInfo ) ( string , [ ] byte , error ) {
return "LOGIN" , [ ] byte { } , nil
}
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
}
2015-09-17 08:54:12 +03:00
type Sender struct {
2014-03-19 16:27:27 +04:00
}
2015-09-17 08:54:12 +03:00
func ( s * Sender ) Send ( from string , to [ ] string , msg io . WriterTo ) error {
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
2014-12-18 14:34:30 +03:00
// If not using SMTPS, alway 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 ( )
}
2015-09-17 08:54:12 +03:00
func processMailQueue ( ) {
sender := & Sender { }
2014-03-19 16:27:27 +04:00
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 )
if err := gomail . Send ( sender , msg . Message ) ; err != nil {
2016-07-15 19:36:39 +03:00
log . Error ( 3 , "Fail 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
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
mailQueue = make ( chan * Message , setting . MailService . QueueLength )
go processMailQueue ( )
2014-03-19 16:27:27 +04:00
}
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
} ( )
}