mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-01-08 17:51:57 +00:00
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.
This commit is contained in:
93
internal/courier/procmail.go
Normal file
93
internal/courier/procmail.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user