From 4f9d59be17de6714b33e59d89a64c175aaed9381 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Sun, 3 May 2020 00:04:31 +0100
Subject: [PATCH] Sendmail should create a process on the gitea system and have
 a default timeout (#11256)

* Make sure that sendmail processes register with the process manager
* Provide a timeout for these (initially of 5 minutes)
* Add configurable value and tie in to documentation
* Tie in to the admin config page.

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 custom/conf/app.ini.sample                         |  2 ++
 .../doc/advanced/config-cheat-sheet.en-us.md       |  1 +
 modules/setting/mailer.go                          |  9 ++++++---
 options/locale/locale_en-US.ini                    |  1 +
 services/mailer/mailer.go                          | 14 +++++++++++++-
 templates/admin/config.tmpl                        |  2 ++
 6 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample
index 8900a5834..0c29932d9 100644
--- a/custom/conf/app.ini.sample
+++ b/custom/conf/app.ini.sample
@@ -649,6 +649,8 @@ MAILER_TYPE = smtp
 SENDMAIL_PATH = sendmail
 ; Specify any extra sendmail arguments
 SENDMAIL_ARGS =
+; Timeout for Sendmail
+SENDMAIL_TIMEOUT = 5m
 
 [cache]
 ; if the cache enabled
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index 000b65f5a..f9bc05acb 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -410,6 +410,7 @@ set name for unique queues. Individual queues will default to
    - Enabling dummy will ignore all settings except `ENABLED`, `SUBJECT_PREFIX` and `FROM`.
 - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be
    command or full path).
+- `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail
 - ``IS_TLS_ENABLED`` :  **false** : Decide if SMTP connections should use TLS.
 
 ## Cache (`cache`)
diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go
index c692e0fe1..a2228e938 100644
--- a/modules/setting/mailer.go
+++ b/modules/setting/mailer.go
@@ -6,6 +6,7 @@ package setting
 
 import (
 	"net/mail"
+	"time"
 
 	"code.gitea.io/gitea/modules/log"
 
@@ -35,8 +36,9 @@ type Mailer struct {
 	IsTLSEnabled      bool
 
 	// Sendmail sender
-	SendmailPath string
-	SendmailArgs []string
+	SendmailPath    string
+	SendmailArgs    []string
+	SendmailTimeout time.Duration
 }
 
 var (
@@ -69,7 +71,8 @@ func newMailService() {
 		IsTLSEnabled:   sec.Key("IS_TLS_ENABLED").MustBool(),
 		SubjectPrefix:  sec.Key("SUBJECT_PREFIX").MustString(""),
 
-		SendmailPath: sec.Key("SENDMAIL_PATH").MustString("sendmail"),
+		SendmailPath:    sec.Key("SENDMAIL_PATH").MustString("sendmail"),
+		SendmailTimeout: sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
 	}
 	MailService.From = sec.Key("FROM").MustString(MailService.User)
 
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index a6c5d4197..fe6857352 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2119,6 +2119,7 @@ config.mailer_user = User
 config.mailer_use_sendmail = Use Sendmail
 config.mailer_sendmail_path = Sendmail Path
 config.mailer_sendmail_args = Extra Arguments to Sendmail
+config.mailer_sendmail_timeout = Sendmail Timeout
 config.send_test_mail = Send Testing Email
 config.test_mail_failed = Failed to send a testing email to '%s': %v
 config.test_mail_sent = A testing email has been sent to '%s'.
diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go
index 9ff172910..2e7beffa1 100644
--- a/services/mailer/mailer.go
+++ b/services/mailer/mailer.go
@@ -7,6 +7,7 @@ package mailer
 
 import (
 	"bytes"
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io"
@@ -20,6 +21,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/queue"
 	"code.gitea.io/gitea/modules/setting"
 
@@ -244,7 +246,14 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
 	args = append(args, setting.MailService.SendmailArgs...)
 	args = append(args, to...)
 	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
-	cmd := exec.Command(setting.MailService.SendmailPath, args...)
+
+	pm := process.GetManager()
+	desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args)
+
+	ctx, cancel := context.WithTimeout(graceful.GetManager().HammerContext(), setting.MailService.SendmailTimeout)
+	defer cancel()
+
+	cmd := exec.CommandContext(ctx, setting.MailService.SendmailPath, args...)
 	pipe, err := cmd.StdinPipe()
 
 	if err != nil {
@@ -255,12 +264,15 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
 		return err
 	}
 
+	pid := pm.Add(desc, cancel)
+
 	_, 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()
+	pm.Remove(pid)
 	if err != nil {
 		return err
 	} else if closeError != nil {
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 27a47505b..1b553230b 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -228,6 +228,8 @@
 						<dd>{{.Mailer.SendmailPath}}</dd>
 						<dt>{{.i18n.Tr "admin.config.mailer_sendmail_args"}}</dt>
 						<dd>{{.Mailer.SendmailArgs}}</dd>
+						<dt>{{.i18n.Tr "admin.config.mailer_sendmail_timeout"}}</dt>
+						<dd>{{.Mailer.SendmailTimeout}} {{.i18n.Tr "tool.raw_seconds"}}</dd>
 					{{end}}
 					<dt>{{.i18n.Tr "admin.config.mailer_user"}}</dt>
 					<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd><br>