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 77d547288f Implement couriers
This patch introduces the couriers, which the queue uses to deliver mail.

We have a local courier (using procmail), a remote courier (uses SMTP), and a
router courier that decides which of the two to use based on a list of local
domains.

There are still a few things pending, but they all have their basic
functionality working and tested.
2015-11-13 03:41:06 +00:00

94 lines
2.0 KiB
Go

package courier
import (
"bytes"
"fmt"
"os/exec"
"strings"
"time"
"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, leaving only [a-zA-Z-.].
func sanitizeForProcmail(s string) string {
valid := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z', r >= 'a' && r <= 'z', r == '-', r == '.':
return r
default:
return rune(-1)
}
}
return strings.Map(valid, s)
}