From 41461aaa462e21b353fac2ab6a981ee1e96de472 Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Mon, 5 Dec 2016 20:47:05 +0000 Subject: [PATCH 1/7] Implemented sendmail. This piggybacks on existing configuration to keep the change simple --- modules/mailer/mailer.go | 44 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 3dc2c4e53168f..a75a7886b92e7 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -11,6 +11,7 @@ import ( "net" "net/smtp" "os" + "os/exec" "strings" "time" @@ -92,7 +93,7 @@ type Sender struct { } // Send send email -func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { +func (s *Sender) SendSMTP(from string, to []string, msg io.WriterTo) error { opts := setting.MailService host, port, err := net.SplitHostPort(opts.Host) @@ -195,6 +196,47 @@ func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { return client.Quit() } +func (s *Sender) SendSendmail(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...) + cmd := exec.Command(setting.MailService.Host, args...) + pipe, err := cmd.StdinPipe() + + if err != nil { + return err + } + + if err = cmd.Start(); err != nil { + return err + } + + _,err = msg.WriteTo(pipe) + + // 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() + waitError = cmd.Wait() + if err != nil { + return err + } else if closeError != nil { + return closeError + } else { + return waitError + } +} + +func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { + if strings.Contains(setting.MailService.Host, "/") { + return s.SendSendmail(from, to, msg) + } else { + return s.SendSMTP(from, to, msg) + } +} + func processMailQueue() { sender := &Sender{} From 6f3006165d35c0a940cbe4cf94da1fb7c141ee15 Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Mon, 5 Dec 2016 21:19:45 +0000 Subject: [PATCH 2/7] Changed privicy of new sendSMTP and sendSendmail functions --- modules/mailer/mailer.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index a75a7886b92e7..746a41080b8b0 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -93,7 +93,7 @@ type Sender struct { } // Send send email -func (s *Sender) SendSMTP(from string, to []string, msg io.WriterTo) error { +func (s *Sender) sendSMTP(from string, to []string, msg io.WriterTo) error { opts := setting.MailService host, port, err := net.SplitHostPort(opts.Host) @@ -196,7 +196,7 @@ func (s *Sender) SendSMTP(from string, to []string, msg io.WriterTo) error { return client.Quit() } -func (s *Sender) SendSendmail(from string, to []string, msg io.WriterTo) error { +func (s *Sender) sendSendmail(from string, to []string, msg io.WriterTo) error { var err error var closeError error var waitError error @@ -231,9 +231,9 @@ func (s *Sender) SendSendmail(from string, to []string, msg io.WriterTo) error { func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { if strings.Contains(setting.MailService.Host, "/") { - return s.SendSendmail(from, to, msg) + return s.sendSendmail(from, to, msg) } else { - return s.SendSMTP(from, to, msg) + return s.sendSMTP(from, to, msg) } } From fcfedd24ba6d906be915758f1b6629d41f0a67dd Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Tue, 6 Dec 2016 10:06:46 +0000 Subject: [PATCH 3/7] Fixed Lint errors --- modules/mailer/mailer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 746a41080b8b0..88dd2d12c0bd8 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -92,7 +92,6 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { type Sender struct { } -// Send send email func (s *Sender) sendSMTP(from string, to []string, msg io.WriterTo) error { opts := setting.MailService @@ -229,12 +228,12 @@ func (s *Sender) sendSendmail(from string, to []string, msg io.WriterTo) error { } } +// Send send email func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { if strings.Contains(setting.MailService.Host, "/") { return s.sendSendmail(from, to, msg) - } else { - return s.sendSMTP(from, to, msg) } + return s.sendSMTP(from, to, msg) } func processMailQueue() { From 7f2f1ee76880a1ba848b6e324fc8fd6159b2f02f Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Tue, 6 Dec 2016 20:30:23 +0000 Subject: [PATCH 4/7] Seperated SMTP and sendmail into their own senders --- models/mail.go | 2 +- modules/mailer/mailer.go | 38 ++++++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/models/mail.go b/models/mail.go index f89e38e625294..43025f7ff352a 100644 --- a/models/mail.go +++ b/models/mail.go @@ -56,7 +56,7 @@ func InitMailRender(dir, appendDir string, funcMap []template.FuncMap) { // SendTestMail sends a test mail func SendTestMail(email string) error { - return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message) + return gomail.Send(mailer.Sender, mailer.NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message) } // SendUserMail sends a mail to the user diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 88dd2d12c0bd8..7790064d50983 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -88,11 +88,12 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { return nil, nil } -// Sender mail sender -type Sender struct { +// Sender mail sender (SMTP) +type SMTPSender struct { } -func (s *Sender) sendSMTP(from string, to []string, msg io.WriterTo) error { +// Send send email +func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { opts := setting.MailService host, port, err := net.SplitHostPort(opts.Host) @@ -195,7 +196,12 @@ func (s *Sender) sendSMTP(from string, to []string, msg io.WriterTo) error { return client.Quit() } -func (s *Sender) sendSendmail(from string, to []string, msg io.WriterTo) error { +// Sender mail sender (sendmail) +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 @@ -228,17 +234,7 @@ func (s *Sender) sendSendmail(from string, to []string, msg io.WriterTo) error { } } -// Send send email -func (s *Sender) Send(from string, to []string, msg io.WriterTo) error { - if strings.Contains(setting.MailService.Host, "/") { - return s.sendSendmail(from, to, msg) - } - return s.sendSMTP(from, to, msg) -} - -func processMailQueue() { - sender := &Sender{} - +func processMailQueue(sender gomail.Sender) { for { select { case msg := <-mailQueue: @@ -254,6 +250,9 @@ func processMailQueue() { var mailQueue chan *Message +// Sender sender for sending mail synchronously +var Sender gomail.Sender + // NewContext start mail queue service func NewContext() { // Need to check if mailQueue is nil because in during reinstall (user had installed @@ -263,8 +262,15 @@ func NewContext() { return } + + if strings.Contains(setting.MailService.Host, "/") { + Sender = &SendmailSender{} + } else { + Sender = &SMTPSender{} + } + mailQueue = make(chan *Message, setting.MailService.QueueLength) - go processMailQueue() + go processMailQueue(Sender) } // SendAsync send mail asynchronous From 38bf2ec9f0f2d563e0aa55a639987a9459bf87fa Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Tue, 6 Dec 2016 20:37:25 +0000 Subject: [PATCH 5/7] Making new structs private as they should not be used externally now --- modules/mailer/mailer.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 7790064d50983..9ad6828b33bdb 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -88,12 +88,12 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { return nil, nil } -// Sender mail sender (SMTP) -type SMTPSender struct { +// Sender SMTP mail sender +type smtpSender struct { } // Send send email -func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { +func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error { opts := setting.MailService host, port, err := net.SplitHostPort(opts.Host) @@ -196,12 +196,12 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { return client.Quit() } -// Sender mail sender (sendmail) -type SendmailSender struct { +// Sender sendmail mail sender +type sendmailSender struct { } // Send send email -func (s *SendmailSender) Send(from string, to []string, msg io.WriterTo) error { +func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error { var err error var closeError error var waitError error @@ -264,9 +264,9 @@ func NewContext() { if strings.Contains(setting.MailService.Host, "/") { - Sender = &SendmailSender{} + Sender = &sendmailSender{} } else { - Sender = &SMTPSender{} + Sender = &smtpSender{} } mailQueue = make(chan *Message, setting.MailService.QueueLength) From 12221096e6864699971893d318512dbbd95ff25d Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Wed, 7 Dec 2016 18:51:36 +0000 Subject: [PATCH 6/7] Added sendmail setting to ini file --- conf/app.ini | 4 ++++ modules/mailer/mailer.go | 5 +++-- modules/setting/setting.go | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/conf/app.ini b/conf/app.ini index dfc8ae20fce0c..65db42fda648b 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -225,6 +225,10 @@ USER = PASSWD = ; Use text/html as alternative format of content ENABLE_HTML_ALTERNATIVE = false +; Enable sendmail (override SMTP) +USE_SENDMAIL = false +; Specifiy an alternative sendmail binary +SENDMAIL_PATH = sendmail [cache] ; Either "memory", "redis", or "memcache", default is "memory" diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 9ad6828b33bdb..4886deacaa2dd 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -208,7 +208,8 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error { args := []string{"-F", from, "-i"} args = append(args, to...) - cmd := exec.Command(setting.MailService.Host, args...) + log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) + cmd := exec.Command(setting.MailService.SendmailPath, args...) pipe, err := cmd.StdinPipe() if err != nil { @@ -263,7 +264,7 @@ func NewContext() { } - if strings.Contains(setting.MailService.Host, "/") { + if setting.MailService.UseSendmail { Sender = &sendmailSender{} } else { Sender = &smtpSender{} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7e3bfb47ba5c4..b7460b41c69b3 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -738,18 +738,25 @@ func newSessionService() { // Mailer represents mail service. type Mailer struct { + // Mailer QueueLength int Name string - Host string From string FromEmail string + EnableHTMLAlternative bool + + // SMTP sender + Host string User, Passwd string DisableHelo bool HeloHostname string SkipVerify bool UseCertificate bool CertFile, KeyFile string - EnableHTMLAlternative bool + + // Sendmail sender + UseSendmail bool + SendmailPath string } var ( @@ -767,6 +774,8 @@ func newMailService() { MailService = &Mailer{ QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100), Name: sec.Key("NAME").MustString(AppName), + EnableHTMLAlternative: sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(), + Host: sec.Key("HOST").String(), User: sec.Key("USER").String(), Passwd: sec.Key("PASSWD").String(), @@ -776,7 +785,9 @@ func newMailService() { UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(), CertFile: sec.Key("CERT_FILE").String(), KeyFile: sec.Key("KEY_FILE").String(), - EnableHTMLAlternative: sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(), + + UseSendmail: sec.Key("USE_SENDMAIL").MustBool(), + SendmailPath: sec.Key("SENDMAIL_PATH").MustString("sendmail"), } MailService.From = sec.Key("FROM").MustString(MailService.User) From 491d81f12f0664c0de1dd6d0d3b10408a9d77ed2 Mon Sep 17 00:00:00 2001 From: Philip Couling Date: Wed, 7 Dec 2016 19:30:09 +0000 Subject: [PATCH 7/7] Minor code cleanup --- modules/mailer/mailer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/mailer/mailer.go b/modules/mailer/mailer.go index 4886deacaa2dd..3201075f3fa75 100644 --- a/modules/mailer/mailer.go +++ b/modules/mailer/mailer.go @@ -235,12 +235,12 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error { } } -func processMailQueue(sender gomail.Sender) { +func processMailQueue() { 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 { + if err := gomail.Send(Sender, msg.Message); err != nil { log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err) } else { log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info) @@ -271,7 +271,7 @@ func NewContext() { } mailQueue = make(chan *Message, setting.MailService.QueueLength) - go processMailQueue(Sender) + go processMailQueue() } // SendAsync send mail asynchronous