* 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 configuredtags/v1.21.12.1
| @@ -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") | |||
| } | |||
| @@ -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", | |||
| "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", | |||