1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00
Files
go-chasquid-smtp/internal/courier/procmail.go
Alberto Bertogli ff103c18c3 courier: Let the users configure the mail delivery agent
This patch adds configuration options for the MDA binary and command line
arguments, and changes the (soon to be renamed) procmail courier to make use
of them.
2016-07-16 12:33:51 +01:00

102 lines
2.4 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.
// TODO: Make these a part of the courier instance itself? Why do they
// have to be global?
MailDeliveryAgentBin = "procmail"
MailDeliveryAgentArgs = []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 MailDeliveryAgentArgs {
args = append(args, strings.Replace(a, "%user%", user, -1))
}
cmd := exec.Command(MailDeliveryAgentBin, 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)
}