mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-18 14:47:03 +00:00
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.
100 lines
2.2 KiB
Go
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)
|
|
}
|