mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-01-05 17:37:03 +00:00
Distinguish between permanent and transient errors
This patch makes the queue and couriers distinguish between permanent and transient errors when delivering mail to individual recipients. Pipe delivery errors are always permanent. Procmail delivery errors are almost always permanent, except if the command exited with code 75, which is an indication of transient. SMTP delivery errors are almost always transient, except if the DNS resolution for the domain failed.
This commit is contained in:
@@ -282,15 +282,17 @@ func (item *Item) SendLoop(q *Queue) {
|
||||
to := rcpt.Address
|
||||
tr.LazyPrintf("%s sending", to)
|
||||
|
||||
err := item.deliver(q, rcpt)
|
||||
err, permanent := item.deliver(q, rcpt)
|
||||
|
||||
if err != nil {
|
||||
// TODO: Local deliveries should not be retried, if they
|
||||
// fail due to the user not existing.
|
||||
// -> we need to know the users.
|
||||
// Or maybe we can just not care?
|
||||
tr.LazyPrintf("error: %v", err)
|
||||
glog.Infof("%s -> %q fail: %v", item.ID, to, err)
|
||||
if permanent {
|
||||
tr.LazyPrintf("permanent error: %v", err)
|
||||
glog.Infof("%s -> %q permanent fail: %v", item.ID, to, err)
|
||||
status = Recipient_FAILED
|
||||
} else {
|
||||
tr.LazyPrintf("error: %v", err)
|
||||
glog.Infof("%s -> %q fail: %v", item.ID, to, err)
|
||||
}
|
||||
} else {
|
||||
tr.LazyPrintf("%s successful", to)
|
||||
glog.Infof("%s -> %q sent", item.ID, to)
|
||||
@@ -322,9 +324,9 @@ func (item *Item) SendLoop(q *Queue) {
|
||||
}
|
||||
|
||||
if pending == 0 {
|
||||
// Successfully sent to all recipients.
|
||||
tr.LazyPrintf("all successful")
|
||||
glog.Infof("%s all successful", item.ID)
|
||||
// Completed to all recipients (some may not have succeeded).
|
||||
tr.LazyPrintf("all done")
|
||||
glog.Infof("%s all done", item.ID)
|
||||
|
||||
q.Remove(item.ID)
|
||||
return
|
||||
@@ -343,17 +345,20 @@ func (item *Item) SendLoop(q *Queue) {
|
||||
// remove item from the queue, and remove from disk.
|
||||
}
|
||||
|
||||
func (item *Item) deliver(q *Queue, rcpt *Recipient) error {
|
||||
// deliver the item to the given recipient, using the couriers from the queue.
|
||||
// Return an error (if any), and whether it is permanent or not.
|
||||
func (item *Item) deliver(q *Queue, rcpt *Recipient) (err error, permanent bool) {
|
||||
if rcpt.Type == Recipient_PIPE {
|
||||
c := strings.Fields(rcpt.Address)
|
||||
if len(c) == 0 {
|
||||
return fmt.Errorf("empty pipe")
|
||||
return fmt.Errorf("empty pipe"), true
|
||||
}
|
||||
ctx, _ := context.WithDeadline(context.Background(),
|
||||
time.Now().Add(30*time.Second))
|
||||
cmd := exec.CommandContext(ctx, c[0], c[1:]...)
|
||||
cmd.Stdin = bytes.NewReader(item.Data)
|
||||
return cmd.Run()
|
||||
return cmd.Run(), true
|
||||
|
||||
} else {
|
||||
if envelope.DomainIn(rcpt.Address, q.localDomains) {
|
||||
return q.localC.Deliver(item.From, rcpt.Address, item.Data)
|
||||
|
||||
@@ -25,9 +25,9 @@ type deliverRequest struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (cc *ChanCourier) Deliver(from string, to string, data []byte) error {
|
||||
func (cc *ChanCourier) Deliver(from string, to string, data []byte) (error, bool) {
|
||||
cc.requests <- deliverRequest{from, to, data}
|
||||
return <-cc.results
|
||||
return <-cc.results, false
|
||||
}
|
||||
func newChanCourier() *ChanCourier {
|
||||
return &ChanCourier{
|
||||
@@ -43,12 +43,12 @@ type TestCourier struct {
|
||||
reqFor map[string]*deliverRequest
|
||||
}
|
||||
|
||||
func (tc *TestCourier) Deliver(from string, to string, data []byte) error {
|
||||
func (tc *TestCourier) Deliver(from string, to string, data []byte) (error, bool) {
|
||||
defer tc.wg.Done()
|
||||
dr := &deliverRequest{from, to, data}
|
||||
tc.requests = append(tc.requests, dr)
|
||||
tc.reqFor[to] = dr
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func newTestCourier() *TestCourier {
|
||||
@@ -138,14 +138,14 @@ func TestFullQueue(t *testing.T) {
|
||||
q.Remove(id)
|
||||
}
|
||||
|
||||
// Dumb courier, for when we don't care for the results.
|
||||
// Dumb courier, for when we just want to return directly.
|
||||
type DumbCourier struct{}
|
||||
|
||||
func (c DumbCourier) Deliver(from string, to string, data []byte) error {
|
||||
return nil
|
||||
func (c DumbCourier) Deliver(from string, to string, data []byte) (error, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var dumbCourier DumbCourier
|
||||
var dumbCourier = DumbCourier{}
|
||||
|
||||
func TestAliases(t *testing.T) {
|
||||
q := New("/tmp/queue_test", set.NewString("loco"), aliases.NewResolver(),
|
||||
@@ -198,13 +198,7 @@ func TestPipes(t *testing.T) {
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := item.deliver(q, item.Rcpt[0]); err != nil {
|
||||
if err, _ := item.deliver(q, item.Rcpt[0]); err != nil {
|
||||
t.Errorf("pipe delivery failed: %v", err)
|
||||
}
|
||||
|
||||
// Make the command "false", should fail.
|
||||
item.Rcpt[0].Address = "false"
|
||||
if err := item.deliver(q, item.Rcpt[0]); err == nil {
|
||||
t.Errorf("pipe delivery worked, expected failure")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user