Add support for extra sendmail arguments (#2731)
* Add support for extra sendmail arguments * Sendmail args to exec.command should be a list * Add go-shellquote package * Use go-shellquote lib for parsing Sendmail args * Only parse if sendmail is configured
This commit is contained in:
		
							parent
							
								
									3af5b67ed0
								
							
						
					
					
						commit
						e86a0bf3fe
					
				
					 9 changed files with 322 additions and 0 deletions
				
			
		
							
								
								
									
										2
									
								
								conf/app.ini
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								conf/app.ini
									
									
									
									
										vendored
									
									
								
							|  | @ -327,6 +327,8 @@ SEND_AS_PLAIN_TEXT = false | ||||||
| USE_SENDMAIL = false | USE_SENDMAIL = false | ||||||
| ; Specify an alternative sendmail binary | ; Specify an alternative sendmail binary | ||||||
| SENDMAIL_PATH = sendmail | SENDMAIL_PATH = sendmail | ||||||
|  | ; Specify any extra sendmail arguments | ||||||
|  | SENDMAIL_ARGS = | ||||||
| 
 | 
 | ||||||
| [cache] | [cache] | ||||||
| ; Either "memory", "redis", or "memcache", default is "memory" | ; Either "memory", "redis", or "memcache", default is "memory" | ||||||
|  |  | ||||||
|  | @ -209,6 +209,7 @@ func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error { | ||||||
| 	var waitError error | 	var waitError error | ||||||
| 
 | 
 | ||||||
| 	args := []string{"-F", from, "-i"} | 	args := []string{"-F", from, "-i"} | ||||||
|  | 	args = append(args, setting.MailService.SendmailArgs...) | ||||||
| 	args = append(args, to...) | 	args = append(args, to...) | ||||||
| 	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) | 	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args) | ||||||
| 	cmd := exec.Command(setting.MailService.SendmailPath, args...) | 	cmd := exec.Command(setting.MailService.SendmailPath, args...) | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ import ( | ||||||
| 	"github.com/go-macaron/session" | 	"github.com/go-macaron/session" | ||||||
| 	_ "github.com/go-macaron/session/redis" // redis plugin for store session
 | 	_ "github.com/go-macaron/session/redis" // redis plugin for store session
 | ||||||
| 	"github.com/go-xorm/core" | 	"github.com/go-xorm/core" | ||||||
|  | 	"github.com/kballard/go-shellquote" | ||||||
| 	"gopkg.in/ini.v1" | 	"gopkg.in/ini.v1" | ||||||
| 	"strk.kbt.io/projects/go/libravatar" | 	"strk.kbt.io/projects/go/libravatar" | ||||||
| ) | ) | ||||||
|  | @ -1326,6 +1327,7 @@ type Mailer struct { | ||||||
| 	// Sendmail sender
 | 	// Sendmail sender
 | ||||||
| 	UseSendmail  bool | 	UseSendmail  bool | ||||||
| 	SendmailPath string | 	SendmailPath string | ||||||
|  | 	SendmailArgs []string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
|  | @ -1372,6 +1374,13 @@ func newMailService() { | ||||||
| 	MailService.FromName = parsed.Name | 	MailService.FromName = parsed.Name | ||||||
| 	MailService.FromEmail = parsed.Address | 	MailService.FromEmail = parsed.Address | ||||||
| 
 | 
 | ||||||
|  | 	if MailService.UseSendmail { | ||||||
|  | 		MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error(4, "Failed to parse Sendmail args: %v", CustomConf, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	log.Info("Mail Service Enabled") | 	log.Info("Mail Service Enabled") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								vendor/github.com/kballard/go-shellquote/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/kballard/go-shellquote/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | Copyright (C) 2014 Kevin Ballard | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining | ||||||
|  | a copy of this software and associated documentation files (the "Software"), | ||||||
|  | to deal in the Software without restriction, including without limitation | ||||||
|  | the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||||||
|  | and/or sell copies of the Software, and to permit persons to whom the | ||||||
|  | Software is furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included | ||||||
|  | in all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||||
|  | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||||||
|  | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||||||
|  | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||||||
|  | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||||||
|  | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE | ||||||
|  | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||||
							
								
								
									
										36
									
								
								vendor/github.com/kballard/go-shellquote/README
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/kballard/go-shellquote/README
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | PACKAGE | ||||||
|  | 
 | ||||||
|  | package shellquote | ||||||
|  |     import "github.com/kballard/go-shellquote" | ||||||
|  | 
 | ||||||
|  |     Shellquote provides utilities for joining/splitting strings using sh's | ||||||
|  |     word-splitting rules. | ||||||
|  | 
 | ||||||
|  | VARIABLES | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  |     UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string") | ||||||
|  |     UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string") | ||||||
|  |     UnterminatedEscapeError      = errors.New("Unterminated backslash-escape") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | FUNCTIONS | ||||||
|  | 
 | ||||||
|  | func Join(args ...string) string | ||||||
|  |     Join quotes each argument and joins them with a space. If passed to | ||||||
|  |     /bin/sh, the resulting string will be split back into the original | ||||||
|  |     arguments. | ||||||
|  | 
 | ||||||
|  | func Split(input string) (words []string, err error) | ||||||
|  |     Split splits a string according to /bin/sh's word-splitting rules. It | ||||||
|  |     supports backslash-escapes, single-quotes, and double-quotes. Notably it | ||||||
|  |     does not support the $'' style of quoting. It also doesn't attempt to | ||||||
|  |     perform any other sort of expansion, including brace expansion, shell | ||||||
|  |     expansion, or pathname expansion. | ||||||
|  | 
 | ||||||
|  |     If the given input has an unterminated quoted string or ends in a | ||||||
|  |     backslash-escape, one of UnterminatedSingleQuoteError, | ||||||
|  |     UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/kballard/go-shellquote/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/kballard/go-shellquote/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | // Shellquote provides utilities for joining/splitting strings using sh's
 | ||||||
|  | // word-splitting rules.
 | ||||||
|  | package shellquote | ||||||
							
								
								
									
										102
									
								
								vendor/github.com/kballard/go-shellquote/quote.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								vendor/github.com/kballard/go-shellquote/quote.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | package shellquote | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Join quotes each argument and joins them with a space.
 | ||||||
|  | // If passed to /bin/sh, the resulting string will be split back into the
 | ||||||
|  | // original arguments.
 | ||||||
|  | func Join(args ...string) string { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	for i, arg := range args { | ||||||
|  | 		if i != 0 { | ||||||
|  | 			buf.WriteByte(' ') | ||||||
|  | 		} | ||||||
|  | 		quote(arg, &buf) | ||||||
|  | 	} | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	specialChars      = "\\'\"`${[|&;<>()*?!" | ||||||
|  | 	extraSpecialChars = " \t\n" | ||||||
|  | 	prefixChars       = "~" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func quote(word string, buf *bytes.Buffer) { | ||||||
|  | 	// We want to try to produce a "nice" output. As such, we will
 | ||||||
|  | 	// backslash-escape most characters, but if we encounter a space, or if we
 | ||||||
|  | 	// encounter an extra-special char (which doesn't work with
 | ||||||
|  | 	// backslash-escaping) we switch over to quoting the whole word. We do this
 | ||||||
|  | 	// with a space because it's typically easier for people to read multi-word
 | ||||||
|  | 	// arguments when quoted with a space rather than with ugly backslashes
 | ||||||
|  | 	// everywhere.
 | ||||||
|  | 	origLen := buf.Len() | ||||||
|  | 
 | ||||||
|  | 	if len(word) == 0 { | ||||||
|  | 		// oops, no content
 | ||||||
|  | 		buf.WriteString("''") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cur, prev := word, word | ||||||
|  | 	atStart := true | ||||||
|  | 	for len(cur) > 0 { | ||||||
|  | 		c, l := utf8.DecodeRuneInString(cur) | ||||||
|  | 		cur = cur[l:] | ||||||
|  | 		if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) { | ||||||
|  | 			// copy the non-special chars up to this point
 | ||||||
|  | 			if len(cur) < len(prev) { | ||||||
|  | 				buf.WriteString(prev[0 : len(prev)-len(cur)-l]) | ||||||
|  | 			} | ||||||
|  | 			buf.WriteByte('\\') | ||||||
|  | 			buf.WriteRune(c) | ||||||
|  | 			prev = cur | ||||||
|  | 		} else if strings.ContainsRune(extraSpecialChars, c) { | ||||||
|  | 			// start over in quote mode
 | ||||||
|  | 			buf.Truncate(origLen) | ||||||
|  | 			goto quote | ||||||
|  | 		} | ||||||
|  | 		atStart = false | ||||||
|  | 	} | ||||||
|  | 	if len(prev) > 0 { | ||||||
|  | 		buf.WriteString(prev) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | 
 | ||||||
|  | quote: | ||||||
|  | 	// quote mode
 | ||||||
|  | 	// Use single-quotes, but if we find a single-quote in the word, we need
 | ||||||
|  | 	// to terminate the string, emit an escaped quote, and start the string up
 | ||||||
|  | 	// again
 | ||||||
|  | 	inQuote := false | ||||||
|  | 	for len(word) > 0 { | ||||||
|  | 		i := strings.IndexRune(word, '\'') | ||||||
|  | 		if i == -1 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if i > 0 { | ||||||
|  | 			if !inQuote { | ||||||
|  | 				buf.WriteByte('\'') | ||||||
|  | 				inQuote = true | ||||||
|  | 			} | ||||||
|  | 			buf.WriteString(word[0:i]) | ||||||
|  | 		} | ||||||
|  | 		word = word[i+1:] | ||||||
|  | 		if inQuote { | ||||||
|  | 			buf.WriteByte('\'') | ||||||
|  | 			inQuote = false | ||||||
|  | 		} | ||||||
|  | 		buf.WriteString("\\'") | ||||||
|  | 	} | ||||||
|  | 	if len(word) > 0 { | ||||||
|  | 		if !inQuote { | ||||||
|  | 			buf.WriteByte('\'') | ||||||
|  | 		} | ||||||
|  | 		buf.WriteString(word) | ||||||
|  | 		buf.WriteByte('\'') | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								vendor/github.com/kballard/go-shellquote/unquote.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								vendor/github.com/kballard/go-shellquote/unquote.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | ||||||
|  | package shellquote | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode/utf8" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string") | ||||||
|  | 	UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string") | ||||||
|  | 	UnterminatedEscapeError      = errors.New("Unterminated backslash-escape") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	splitChars        = " \n\t" | ||||||
|  | 	singleChar        = '\'' | ||||||
|  | 	doubleChar        = '"' | ||||||
|  | 	escapeChar        = '\\' | ||||||
|  | 	doubleEscapeChars = "$`\"\n\\" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Split splits a string according to /bin/sh's word-splitting rules. It
 | ||||||
|  | // supports backslash-escapes, single-quotes, and double-quotes. Notably it does
 | ||||||
|  | // not support the $'' style of quoting. It also doesn't attempt to perform any
 | ||||||
|  | // other sort of expansion, including brace expansion, shell expansion, or
 | ||||||
|  | // pathname expansion.
 | ||||||
|  | //
 | ||||||
|  | // If the given input has an unterminated quoted string or ends in a
 | ||||||
|  | // backslash-escape, one of UnterminatedSingleQuoteError,
 | ||||||
|  | // UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned.
 | ||||||
|  | func Split(input string) (words []string, err error) { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	words = make([]string, 0) | ||||||
|  | 
 | ||||||
|  | 	for len(input) > 0 { | ||||||
|  | 		// skip any splitChars at the start
 | ||||||
|  | 		c, l := utf8.DecodeRuneInString(input) | ||||||
|  | 		if strings.ContainsRune(splitChars, c) { | ||||||
|  | 			input = input[l:] | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var word string | ||||||
|  | 		word, input, err = splitWord(input, &buf) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		words = append(words, word) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) { | ||||||
|  | 	buf.Reset() | ||||||
|  | 
 | ||||||
|  | raw: | ||||||
|  | 	{ | ||||||
|  | 		cur := input | ||||||
|  | 		for len(cur) > 0 { | ||||||
|  | 			c, l := utf8.DecodeRuneInString(cur) | ||||||
|  | 			cur = cur[l:] | ||||||
|  | 			if c == singleChar { | ||||||
|  | 				buf.WriteString(input[0 : len(input)-len(cur)-l]) | ||||||
|  | 				input = cur | ||||||
|  | 				goto single | ||||||
|  | 			} else if c == doubleChar { | ||||||
|  | 				buf.WriteString(input[0 : len(input)-len(cur)-l]) | ||||||
|  | 				input = cur | ||||||
|  | 				goto double | ||||||
|  | 			} else if c == escapeChar { | ||||||
|  | 				buf.WriteString(input[0 : len(input)-len(cur)-l]) | ||||||
|  | 				input = cur | ||||||
|  | 				goto escape | ||||||
|  | 			} else if strings.ContainsRune(splitChars, c) { | ||||||
|  | 				buf.WriteString(input[0 : len(input)-len(cur)-l]) | ||||||
|  | 				return buf.String(), cur, nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if len(input) > 0 { | ||||||
|  | 			buf.WriteString(input) | ||||||
|  | 			input = "" | ||||||
|  | 		} | ||||||
|  | 		goto done | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | escape: | ||||||
|  | 	{ | ||||||
|  | 		if len(input) == 0 { | ||||||
|  | 			return "", "", UnterminatedEscapeError | ||||||
|  | 		} | ||||||
|  | 		c, l := utf8.DecodeRuneInString(input) | ||||||
|  | 		if c == '\n' { | ||||||
|  | 			// a backslash-escaped newline is elided from the output entirely
 | ||||||
|  | 		} else { | ||||||
|  | 			buf.WriteString(input[:l]) | ||||||
|  | 		} | ||||||
|  | 		input = input[l:] | ||||||
|  | 	} | ||||||
|  | 	goto raw | ||||||
|  | 
 | ||||||
|  | single: | ||||||
|  | 	{ | ||||||
|  | 		i := strings.IndexRune(input, singleChar) | ||||||
|  | 		if i == -1 { | ||||||
|  | 			return "", "", UnterminatedSingleQuoteError | ||||||
|  | 		} | ||||||
|  | 		buf.WriteString(input[0:i]) | ||||||
|  | 		input = input[i+1:] | ||||||
|  | 		goto raw | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | double: | ||||||
|  | 	{ | ||||||
|  | 		cur := input | ||||||
|  | 		for len(cur) > 0 { | ||||||
|  | 			c, l := utf8.DecodeRuneInString(cur) | ||||||
|  | 			cur = cur[l:] | ||||||
|  | 			if c == doubleChar { | ||||||
|  | 				buf.WriteString(input[0 : len(input)-len(cur)-l]) | ||||||
|  | 				input = cur | ||||||
|  | 				goto raw | ||||||
|  | 			} else if c == escapeChar { | ||||||
|  | 				// bash only supports certain escapes in double-quoted strings
 | ||||||
|  | 				c2, l2 := utf8.DecodeRuneInString(cur) | ||||||
|  | 				cur = cur[l2:] | ||||||
|  | 				if strings.ContainsRune(doubleEscapeChars, c2) { | ||||||
|  | 					buf.WriteString(input[0 : len(input)-len(cur)-l-l2]) | ||||||
|  | 					if c2 == '\n' { | ||||||
|  | 						// newline is special, skip the backslash entirely
 | ||||||
|  | 					} else { | ||||||
|  | 						buf.WriteRune(c2) | ||||||
|  | 					} | ||||||
|  | 					input = cur | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return "", "", UnterminatedDoubleQuoteError | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | done: | ||||||
|  | 	return buf.String(), input, nil | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							|  | @ -521,6 +521,12 @@ | ||||||
| 			"revision": "b2c7a7da5b2995941048f60146e67702a292e468", | 			"revision": "b2c7a7da5b2995941048f60146e67702a292e468", | ||||||
| 			"revisionTime": "2016-02-12T04:00:40Z" | 			"revisionTime": "2016-02-12T04:00:40Z" | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"checksumSHA1": "+IzngblnBQNh+GmsS2O7jqmzSVQ=", | ||||||
|  | 			"path": "github.com/kballard/go-shellquote", | ||||||
|  | 			"revision": "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2", | ||||||
|  | 			"revisionTime": "2017-06-19T18:30:22Z" | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "VJk3rOWfxEV9Ilig5lgzH1qg8Ss=", | 			"checksumSHA1": "VJk3rOWfxEV9Ilig5lgzH1qg8Ss=", | ||||||
| 			"path": "github.com/keybase/go-crypto/brainpool", | 			"path": "github.com/keybase/go-crypto/brainpool", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue