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
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Shellquote provides utilities for joining/splitting strings using sh's
|
||||||
|
// word-splitting rules.
|
||||||
|
package shellquote
|
|
@ -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('\'')
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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 New Issue