1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2026-01-27 20:45:56 +00:00

courier: Rename Procmail to MDA

This patch renames courier.Procmail to courier.MDA, to make it more
obvious that the functionality is not tied to that particular MDA.

It's just for readability, there are no functional changes.
This commit is contained in:
Alberto Bertogli
2020-09-17 02:45:48 +01:00
parent 1cc7b9a864
commit 025cb2d96a
4 changed files with 21 additions and 22 deletions

104
internal/courier/mda.go Normal file
View File

@@ -0,0 +1,104 @@
package courier
import (
"bytes"
"context"
"fmt"
"os/exec"
"strings"
"syscall"
"time"
"unicode"
"blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/trace"
)
var (
errTimeout = fmt.Errorf("operation timed out")
)
// MDA delivers local mail by executing a local binary, like procmail or
// maildrop. It works with any binary that:
// - Receives the email to deliver via stdin.
// - Exits with code EX_TEMPFAIL (75) for transient issues.
type MDA struct {
Binary string // Path to the binary.
Args []string // Arguments to pass.
Timeout time.Duration // Timeout for each invocation.
}
// Deliver an email. On failures, returns an error, and whether or not it is
// permanent.
func (p *MDA) Deliver(from string, to string, data []byte) (error, bool) {
tr := trace.New("Courier.MDA", to)
defer tr.Finish()
// Sanitize, just in case.
from = sanitizeForMDA(from)
to = sanitizeForMDA(to)
tr.Debugf("%s -> %s", from, to)
// Prepare the command, replacing the necessary arguments.
replacer := strings.NewReplacer(
"%from%", from,
"%from_user%", envelope.UserOf(from),
"%from_domain%", envelope.DomainOf(from),
"%to%", to,
"%to_user%", envelope.UserOf(to),
"%to_domain%", envelope.DomainOf(to),
)
args := []string{}
for _, a := range p.Args {
args = append(args, replacer.Replace(a))
}
tr.Debugf("%s %q", p.Binary, args)
ctx, cancel := context.WithTimeout(context.Background(), p.Timeout)
defer cancel()
cmd := exec.CommandContext(ctx, p.Binary, args...)
cmd.Stdin = bytes.NewReader(data)
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return tr.Error(errTimeout), false
}
if err != nil {
// Determine if the error is permanent or not.
// Default to permanent, but error code 75 is transient by general
// convention (/usr/include/sysexits.h), and commonly relied upon.
permanent := true
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
permanent = status.ExitStatus() != 75
}
}
err = tr.Errorf("MDA delivery failed: %v - %q", err, string(output))
return err, permanent
}
tr.Debugf("delivered")
return nil, false
}
// sanitizeForMDA 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 sanitizeForMDA(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)
}