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
 | 
			
		||||
; Specify an alternative sendmail binary
 | 
			
		||||
SENDMAIL_PATH = sendmail
 | 
			
		||||
; Specify any extra sendmail arguments
 | 
			
		||||
SENDMAIL_ARGS =
 | 
			
		||||
 | 
			
		||||
[cache]
 | 
			
		||||
; 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
 | 
			
		||||
 | 
			
		||||
	args := []string{"-F", from, "-i"}
 | 
			
		||||
	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...)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,7 @@ import (
 | 
			
		|||
	"github.com/go-macaron/session"
 | 
			
		||||
	_ "github.com/go-macaron/session/redis" // redis plugin for store session
 | 
			
		||||
	"github.com/go-xorm/core"
 | 
			
		||||
	"github.com/kballard/go-shellquote"
 | 
			
		||||
	"gopkg.in/ini.v1"
 | 
			
		||||
	"strk.kbt.io/projects/go/libravatar"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1326,6 +1327,7 @@ type Mailer struct {
 | 
			
		|||
	// Sendmail sender
 | 
			
		||||
	UseSendmail  bool
 | 
			
		||||
	SendmailPath string
 | 
			
		||||
	SendmailArgs []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -1372,6 +1374,13 @@ func newMailService() {
 | 
			
		|||
	MailService.FromName = parsed.Name
 | 
			
		||||
	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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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",
 | 
			
		||||
			"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=",
 | 
			
		||||
			"path": "github.com/keybase/go-crypto/brainpool",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue