logtail: deliveryRe anchored at the head of the line (audit M12)

The pattern that writes a row's status matched anywhere in a line: a
port-25 reject line ending helo=<ID: to=<victim@...>, status=sent> parsed
as (ID, victim, sent). parseDelivery now takes the stamp off with
SplitTimestamp and matches `^host program[pid]: <id>: to=<...>, ...
status=`, as queueLineRe and the inbound patterns do.

Tests: the probed HELO line, the same text in a quoted sender on a qmgr
line, the text after an smtpd line's queue id - none parse; a syslog-stamped
line as Postfix 3.7 writes it parses. The existing cases unchanged.

CHANGELOG [Unreleased], audit plan line ticked, roadmap 11/61.
Evidence: go test ./internal/logtail ok but for TestFollowTailsAndRotates
(Windows); the server run of the stage follows before the push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-14 14:22:03 +03:00
co-authored by Claude Opus 5.5
parent ffefa9d6df
commit 63520f1678
5 changed files with 47 additions and 4 deletions
+10 -2
View File
@@ -88,17 +88,25 @@ type RetentionDays func() int
// The "<queue-id>: to=<addr>, …, status=<word>" shape is specific to the
// delivery agents; qmgr/smtpd/cleanup lines do not match.
//
// It is matched at the head of the line - the host name, "program[pid]:",
// then the queue id - after SplitTimestamp has taken the stamp off, like
// queueLineRe and the inbound patterns. Anywhere else the text is a
// stranger's: a port-25 reject line ends with the client's HELO name, which
// once parsed as a delivery result when it read "<id>: to=<…>, status=sent"
// (audit 2026-10-12, M12).
//
// The run before status= is lazy on purpose. Postfix appends the remote
// server's reply verbatim, so a greedy match would take the *last* status= on
// the line — and that one can come from the reply text, which the far end
// controls. A bounce whose reply quoted "status=sent" would then be filed as a
// success. The real field is always the first one after to=<…>.
var deliveryRe = regexp.MustCompile(`\b([0-9A-Za-z]+): to=<([^>]*)>,.*?\bstatus=(\w+)`)
var deliveryRe = regexp.MustCompile(`^\S+ [^\s\[]+\[\d+\]: ([0-9A-Za-z]+): to=<([^>]*)>,.*?\bstatus=(\w+)`)
// parseDelivery extracts (queue-id, recipient, status) from a mail.log line.
// ok is false for lines that are not recognised delivery results.
func parseDelivery(line string) (queueID, recipient, status string, ok bool) {
m := deliveryRe.FindStringSubmatch(line)
_, rest := SplitTimestamp(line)
m := deliveryRe.FindStringSubmatch(rest)
if m == nil {
return "", "", "", false
}