Browse Source

Implement sendmail (#355)

* Implemented sendmail. This piggybacks on existing configuration to keep the change simple

* Changed privicy of new sendSMTP and sendSendmail functions

* Fixed Lint errors

* Seperated SMTP and sendmail into their own senders

* Making new structs private as they should not be used externally now

* Added sendmail setting to ini file

* Minor code cleanup
Philip Couling 3 years ago
parent
commit
d4924d45d6
4 changed files with 73 additions and 10 deletions
  1. 4 0
      conf/app.ini
  2. 1 1
      models/mail.go
  3. 54 6
      modules/mailer/mailer.go
  4. 14 3
      modules/setting/setting.go

+ 4 - 0
conf/app.ini

@@ -226,6 +226,10 @@ USER =
226 226
 PASSWD =
227 227
 ; Use text/html as alternative format of content
228 228
 ENABLE_HTML_ALTERNATIVE = false
229
+; Enable sendmail (override SMTP)
230
+USE_SENDMAIL = false
231
+; Specifiy an alternative sendmail binary
232
+SENDMAIL_PATH = sendmail
229 233
 
230 234
 [cache]
231 235
 ; Either "memory", "redis", or "memcache", default is "memory"

+ 1 - 1
models/mail.go

@@ -40,7 +40,7 @@ func InitMailRender(tmpls *template.Template) {
40 40
 
41 41
 // SendTestMail sends a test mail
42 42
 func SendTestMail(email string) error {
43
-	return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
43
+	return gomail.Send(mailer.Sender, mailer.NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
44 44
 }
45 45
 
46 46
 // SendUserMail sends a mail to the user

+ 54 - 6
modules/mailer/mailer.go

@@ -11,6 +11,7 @@ import (
11 11
 	"net"
12 12
 	"net/smtp"
13 13
 	"os"
14
+	"os/exec"
14 15
 	"strings"
15 16
 	"time"
16 17
 
@@ -87,12 +88,12 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
87 88
 	return nil, nil
88 89
 }
89 90
 
90
-// Sender mail sender
91
-type Sender struct {
91
+// Sender SMTP mail sender
92
+type smtpSender struct {
92 93
 }
93 94
 
94 95
 // Send send email
95
-func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
96
+func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
96 97
 	opts := setting.MailService
97 98
 
98 99
 	host, port, err := net.SplitHostPort(opts.Host)
@@ -195,14 +196,51 @@ func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
195 196
 	return client.Quit()
196 197
 }
197 198
 
198
-func processMailQueue() {
199
-	sender := &Sender{}
199
+// Sender sendmail mail sender
200
+type sendmailSender struct {
201
+}
202
+
203
+// Send send email
204
+func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
205
+	var err error
206
+	var closeError error
207
+	var waitError error
208
+
209
+	args := []string{"-F", from, "-i"}
210
+	args = append(args, to...)
211
+	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
212
+	cmd := exec.Command(setting.MailService.SendmailPath, args...)
213
+	pipe, err := cmd.StdinPipe()
214
+
215
+	if err != nil {
216
+		return err
217
+	}
218
+
219
+	if err = cmd.Start(); err != nil {
220
+		return err
221
+	}
222
+
223
+	_,err = msg.WriteTo(pipe)
200 224
 
225
+	// we MUST close the pipe or sendmail will hang waiting for more of the message
226
+	// Also we should wait on our sendmail command even if something fails
227
+	closeError = pipe.Close()
228
+	waitError =  cmd.Wait()
229
+	if err != nil {
230
+		return err
231
+	} else if closeError != nil {
232
+		return closeError
233
+	} else {
234
+		return waitError
235
+	}
236
+}
237
+
238
+func processMailQueue() {
201 239
 	for {
202 240
 		select {
203 241
 		case msg := <-mailQueue:
204 242
 			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
205
-			if err := gomail.Send(sender, msg.Message); err != nil {
243
+			if err := gomail.Send(Sender, msg.Message); err != nil {
206 244
 				log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
207 245
 			} else {
208 246
 				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
@@ -213,6 +251,9 @@ func processMailQueue() {
213 251
 
214 252
 var mailQueue chan *Message
215 253
 
254
+// Sender sender for sending mail synchronously
255
+var Sender gomail.Sender
256
+
216 257
 // NewContext start mail queue service
217 258
 func NewContext() {
218 259
 	// Need to check if mailQueue is nil because in during reinstall (user had installed
@@ -222,6 +263,13 @@ func NewContext() {
222 263
 		return
223 264
 	}
224 265
 
266
+
267
+	if setting.MailService.UseSendmail {
268
+		Sender = &sendmailSender{}
269
+	} else {
270
+		Sender = &smtpSender{}
271
+	}
272
+
225 273
 	mailQueue = make(chan *Message, setting.MailService.QueueLength)
226 274
 	go processMailQueue()
227 275
 }

+ 14 - 3
modules/setting/setting.go

@@ -858,18 +858,25 @@ func newSessionService() {
858 858
 
859 859
 // Mailer represents mail service.
860 860
 type Mailer struct {
861
+    // Mailer
861 862
 	QueueLength           int
862 863
 	Name                  string
863
-	Host                  string
864 864
 	From                  string
865 865
 	FromEmail             string
866
+	EnableHTMLAlternative bool
867
+
868
+	// SMTP sender
869
+	Host                  string
866 870
 	User, Passwd          string
867 871
 	DisableHelo           bool
868 872
 	HeloHostname          string
869 873
 	SkipVerify            bool
870 874
 	UseCertificate        bool
871 875
 	CertFile, KeyFile     string
872
-	EnableHTMLAlternative bool
876
+
877
+	// Sendmail sender
878
+	UseSendmail           bool
879
+	SendmailPath          string
873 880
 }
874 881
 
875 882
 var (
@@ -887,6 +894,8 @@ func newMailService() {
887 894
 	MailService = &Mailer{
888 895
 		QueueLength:           sec.Key("SEND_BUFFER_LEN").MustInt(100),
889 896
 		Name:                  sec.Key("NAME").MustString(AppName),
897
+		EnableHTMLAlternative: sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(),
898
+
890 899
 		Host:                  sec.Key("HOST").String(),
891 900
 		User:                  sec.Key("USER").String(),
892 901
 		Passwd:                sec.Key("PASSWD").String(),
@@ -896,7 +905,9 @@ func newMailService() {
896 905
 		UseCertificate:        sec.Key("USE_CERTIFICATE").MustBool(),
897 906
 		CertFile:              sec.Key("CERT_FILE").String(),
898 907
 		KeyFile:               sec.Key("KEY_FILE").String(),
899
-		EnableHTMLAlternative: sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(),
908
+
909
+		UseSendmail:           sec.Key("USE_SENDMAIL").MustBool(),
910
+		SendmailPath:          sec.Key("SENDMAIL_PATH").MustString("sendmail"),
900 911
 	}
901 912
 	MailService.From = sec.Key("FROM").MustString(MailService.User)
902 913