From 1514e13bb86d4714550107efa58c947b1bfc39c6 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Thu, 6 Jan 2022 00:43:45 +0000
Subject: [PATCH] Add option to convert CRLF to LF line endings for sendmail
 (#18075)

It appears that several versions of sendmail require that the mail is sent to them with
LF line endings instead of CRLF endings - which of course they will then convert back
to CRLF line endings to comply with the SMTP standard.

This PR adds another setting SENDMAIL_CONVERT_CRLF which will pass the message writer
through a filter. This will filter out and convert CRLFs to LFs before writing them
out to sendmail.

Fix #18024

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 custom/conf/app.example.ini                          |  3 +++
 .../content/doc/advanced/config-cheat-sheet.en-us.md |  1 +
 modules/setting/mailer.go                            | 12 +++++++-----
 services/mailer/mailer.go                            | 11 +++++++++--
 4 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index d860923500..17343aaea1 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1494,6 +1494,9 @@ PATH =
 ;;
 ;; Timeout for Sendmail
 ;SENDMAIL_TIMEOUT = 5m
+;;
+;; convert \r\n to \n for Sendmail
+;SENDMAIL_CONVERT_CRLF = true
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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 02a61fe445..247ea935ee 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -667,6 +667,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
    command or full path).
 - `SENDMAIL_ARGS`: **_empty_**: Specify any extra sendmail arguments.
 - `SENDMAIL_TIMEOUT`: **5m**: default timeout for sending email through sendmail
+- `SENDMAIL_CONVERT_CRLF`: **true**: Most versions of sendmail prefer LF line endings rather than CRLF line endings. Set this to false if your version of sendmail requires CRLF line endings.
 - `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
 
 ## Cache (`cache`)
diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go
index 1bcd63a914..d7713f3b80 100644
--- a/modules/setting/mailer.go
+++ b/modules/setting/mailer.go
@@ -37,9 +37,10 @@ type Mailer struct {
 	IsTLSEnabled      bool
 
 	// Sendmail sender
-	SendmailPath    string
-	SendmailArgs    []string
-	SendmailTimeout time.Duration
+	SendmailPath        string
+	SendmailArgs        []string
+	SendmailTimeout     time.Duration
+	SendmailConvertCRLF bool
 }
 
 var (
@@ -71,8 +72,9 @@ func newMailService() {
 		IsTLSEnabled:   sec.Key("IS_TLS_ENABLED").MustBool(),
 		SubjectPrefix:  sec.Key("SUBJECT_PREFIX").MustString(""),
 
-		SendmailPath:    sec.Key("SENDMAIL_PATH").MustString("sendmail"),
-		SendmailTimeout: sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
+		SendmailPath:        sec.Key("SENDMAIL_PATH").MustString("sendmail"),
+		SendmailTimeout:     sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute),
+		SendmailConvertCRLF: sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true),
 	}
 	MailService.From = sec.Key("FROM").MustString(MailService.User)
 	MailService.EnvelopeFrom = sec.Key("ENVELOPE_FROM").MustString("")
diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go
index eac2b15c3c..e5e6272f10 100644
--- a/services/mailer/mailer.go
+++ b/services/mailer/mailer.go
@@ -290,13 +290,20 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
 		return err
 	}
 
-	_, err = msg.WriteTo(pipe)
+	if setting.MailService.SendmailConvertCRLF {
+		buf := &strings.Builder{}
+		_, err = msg.WriteTo(buf)
+		if err == nil {
+			_, err = strings.NewReplacer("\r\n", "\n").WriteString(pipe, buf.String())
+		}
+	} else {
+		_, 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 {