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"
2014-10-10 02:08:07 +04:00
"net"
2014-12-20 00:06:03 +03:00
"net/mail"
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-08-20 14:12:55 +03:00
2014-03-19 16:27:27 +04:00
"github.com/gogits/gogs/modules/log"
2014-05-26 04:11:25 +04:00
"github.com/gogits/gogs/modules/setting"
2014-03-19 16:27:27 +04: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
}
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
}
2014-03-19 16:27:27 +04:00
type Message struct {
To [ ] string
From string
Subject string
2015-09-07 13:28:56 +03:00
ReplyTo string
2014-03-19 16:27:27 +04:00
Body string
Type string
Massive bool
Info string
}
// create mail content
func ( m Message ) Content ( ) string {
// set mail type
contentType := "text/plain; charset=UTF-8"
if m . Type == "html" {
contentType = "text/html; charset=UTF-8"
}
// create mail content
2015-09-07 13:28:56 +03:00
content := "From: " + m . From + "\r\nReply-To: " + m . ReplyTo + "\r\nSubject: " + m . Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m . Body
2014-03-19 16:27:27 +04:00
return content
}
2014-03-20 05:05:48 +04:00
var mailQueue chan * Message
2014-03-21 09:48:10 +04:00
func NewMailerContext ( ) {
2014-12-31 13:37:29 +03:00
mailQueue = make ( chan * Message , setting . Cfg . Section ( "mailer" ) . Key ( "SEND_BUFFER_LEN" ) . MustInt ( 10 ) )
2014-03-20 05:05:48 +04:00
go processMailQueue ( )
}
func processMailQueue ( ) {
for {
select {
case msg := <- mailQueue :
num , err := Send ( msg )
2015-09-16 22:04:54 +03:00
tos := strings . Join ( msg . To , ", " )
2014-03-20 05:05:48 +04:00
info := ""
if err != nil {
if len ( msg . Info ) > 0 {
info = ", info: " + msg . Info
}
2014-07-26 08:24:27 +04:00
log . Error ( 4 , fmt . Sprintf ( "Async sent email %d succeed, not send emails: %s%s err: %s" , num , tos , info , err ) )
2014-03-20 10:25:21 +04:00
} else {
log . Trace ( fmt . Sprintf ( "Async sent email %d succeed, sent emails: %s%s" , num , tos , info ) )
2014-03-20 05:05:48 +04:00
}
}
}
}
2014-10-10 02:08:07 +04:00
// sendMail allows mail with self-signed certificates.
2014-12-19 23:48:21 +03:00
func sendMail ( settings * setting . Mailer , recipients [ ] string , msgContent [ ] byte ) error {
2014-12-18 14:34:30 +03:00
host , port , err := net . SplitHostPort ( settings . 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 {
InsecureSkipVerify : settings . SkipVerify ,
2014-10-10 02:08:07 +04:00
ServerName : host ,
}
2014-12-18 14:34:30 +03:00
2015-02-13 10:33:55 +03:00
if settings . UseCertificate {
cert , err := tls . LoadX509KeyPair ( settings . CertFile , settings . KeyFile )
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 {
2014-10-10 02:08:07 +04:00
return err
}
2015-07-03 09:08:18 +03:00
if ! setting . MailService . DisableHelo {
hostname := setting . MailService . HeloHostname
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 {
return err
}
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 {
return err
}
}
2014-12-19 08:24:17 +03:00
canAuth , options := client . Extension ( "AUTH" )
2014-12-18 14:34:30 +03:00
2014-12-19 08:24:17 +03:00
if canAuth && len ( settings . 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" ) {
2014-12-18 14:58:18 +03:00
auth = smtp . CRAMMD5Auth ( settings . User , settings . Passwd )
2014-12-18 15:15:13 +03:00
} else if strings . Contains ( options , "PLAIN" ) {
auth = smtp . PlainAuth ( "" , settings . User , settings . 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-08-20 08:56:25 +03:00
auth = LoginAuth ( settings . User , settings . Passwd )
2014-12-18 14:58:18 +03:00
}
if auth != nil {
if err = client . Auth ( auth ) ; err != nil {
return err
}
2014-10-10 02:08:07 +04:00
}
}
2014-12-20 00:06:03 +03:00
if fromAddress , err := mail . ParseAddress ( settings . From ) ; err != nil {
2014-10-10 02:08:07 +04:00
return err
2014-12-20 00:06:03 +03:00
} else {
if err = client . Mail ( fromAddress . Address ) ; err != nil {
return err
}
2014-10-10 02:08:07 +04:00
}
for _ , rec := range recipients {
if err = client . Rcpt ( rec ) ; err != nil {
return err
}
}
w , err := client . Data ( )
if err != nil {
return err
}
if _ , err = w . Write ( [ ] byte ( msgContent ) ) ; err != nil {
return err
}
if err = w . Close ( ) ; err != nil {
return err
}
return client . Quit ( )
}
2014-03-19 16:27:27 +04:00
// Direct Send mail message
2014-03-20 05:05:48 +04:00
func Send ( msg * Message ) ( int , error ) {
2015-09-16 22:04:54 +03:00
log . Trace ( "Sending mails to: %s" , strings . Join ( msg . To , ", " ) )
2014-03-19 16:27:27 +04:00
// get message body
content := msg . Content ( )
if len ( msg . To ) == 0 {
return 0 , fmt . Errorf ( "empty receive emails" )
2014-05-15 22:46:04 +04:00
} else if len ( msg . Body ) == 0 {
2014-03-19 16:27:27 +04:00
return 0 , fmt . Errorf ( "empty email body" )
}
if msg . Massive {
// send mail to multiple emails one by one
num := 0
for _ , to := range msg . To {
body := [ ] byte ( "To: " + to + "\r\n" + content )
2014-12-19 23:48:21 +03:00
err := sendMail ( setting . MailService , [ ] string { to } , body )
2014-03-19 16:27:27 +04:00
if err != nil {
return num , err
}
num ++
}
return num , nil
} else {
2015-09-16 22:04:54 +03:00
body := [ ] byte ( "To: " + strings . Join ( msg . To , "," ) + "\r\n" + content )
2014-03-19 16:27:27 +04:00
// send to multiple emails in one message
2014-12-19 23:48:21 +03:00
err := sendMail ( setting . MailService , msg . To , body )
2014-03-19 16:27:27 +04:00
if err != nil {
return 0 , err
} else {
return 1 , nil
}
}
}
// Async Send mail message
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
} ( )
}
// Create html mail message
func NewHtmlMessage ( To [ ] string , From , Subject , Body string ) Message {
return Message {
To : To ,
2015-09-07 13:25:15 +03:00
From : setting . MailService . From ,
2015-09-07 13:28:56 +03:00
ReplyTo : From ,
2014-03-19 16:27:27 +04:00
Subject : Subject ,
Body : Body ,
Type : "html" ,
}
}