1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-18 14:47:03 +00:00
Files
go-chasquid-smtp/internal/courier/procmail.go
Alberto Bertogli e32ba1ee86 courier: Make procmail's sanitize be more unicode friendly
Sanitize only lets some ascii characters go through, which is very unfriendly
to non-ascii usernames.

This patch changes it to be more inclusive, and filter out only selected
characters that may be problematic for the subprocesses, specially considering
UNIX shell environments. It's not meant to catch all possible problems, just
help prevent some common ones.
2016-07-16 12:33:34 +01:00

100 lines
2.2 KiB
Go

package courier
import (
"bytes"
"fmt"
"os/exec"
"strings"
"time"
"unicode"
"blitiri.com.ar/go/chasquid/internal/trace"
)
var (
// Location of the procmail binary, and arguments to use.
// The string "%user%" will be replaced with the local user.
procmailBin = "procmail"
procmailArgs = []string{"-d", "%user%"}
// Give procmail 1m to deliver mail.
procmailTimeout = 1 * time.Minute
)
var (
timeoutError = fmt.Errorf("Operation timed out")
)
// Procmail delivers local mail via procmail.
type Procmail struct {
}
func (p *Procmail) Deliver(from string, to string, data []byte) error {
tr := trace.New("Procmail", "Deliver")
defer tr.Finish()
// Get the user, and sanitize to be extra paranoid.
user := sanitizeForProcmail(userOf(to))
tr.LazyPrintf("%s -> %s (%s)", from, user, to)
// Prepare the command, replacing the necessary arguments.
args := []string{}
for _, a := range procmailArgs {
args = append(args, strings.Replace(a, "%user%", user, -1))
}
cmd := exec.Command(procmailBin, args...)
cmdStdin, err := cmd.StdinPipe()
if err != nil {
return tr.Errorf("StdinPipe: %v", err)
}
output := &bytes.Buffer{}
cmd.Stdout = output
cmd.Stderr = output
err = cmd.Start()
if err != nil {
return tr.Errorf("Error starting procmail: %v", err)
}
_, err = bytes.NewBuffer(data).WriteTo(cmdStdin)
if err != nil {
return tr.Errorf("Error sending data to procmail: %v", err)
}
cmdStdin.Close()
timer := time.AfterFunc(procmailTimeout, func() {
cmd.Process.Kill()
})
err = cmd.Wait()
timedOut := !timer.Stop()
if timedOut {
return tr.Error(timeoutError)
}
if err != nil {
return tr.Errorf("Procmail failed: %v - %q", err, output.String())
}
return nil
}
// sanitizeForProcmail cleans the string, removing characters that could be
// problematic considering we will run an external command.
//
// The server does not rely on this to do substitution or proper filtering,
// that's done at a different layer; this is just for defense in depth.
func sanitizeForProcmail(s string) string {
valid := func(r rune) rune {
switch {
case unicode.IsSpace(r), unicode.IsControl(r),
strings.ContainsRune("/;\"'\\|*&$%()[]{}`!", r):
return rune(-1)
default:
return r
}
}
return strings.Map(valid, s)
}