test / test (push) Canceled after 0s
The runtime base is debian:trixie-slim, the build stage golang:1.26-trixie:
Postfix 3.10.13 with upstream's 2026 security fixes up to the August
batch, libmilter 8.18.1, OpenSSL 3.5. Every citation of another program in
code, scripts and documents was re-read against the trixie sources
(dev/upstream; bookworm's kept in dev/upstream-bookworm) and updated;
development.md's cited-package table holds the trixie versions.
What the re-reading changed in behaviour:
- logtail: reject lines with sasl_method/sasl_username/sasl_sender
(smtpd_check.c:1019-1026); cleanup's "removed (canceled)" forgets a
port-25 id (cleanup_api.c:363-364).
- internal/postfix/retry.go reads postconf's stdout only: since 3.9 a
deprecated-parameter warning on stderr made one value too many and the
panel fell back to the compiled-in retry policy.
- logrotate-loop.sh: logrotate 3.22 exits 1 when it ignores a config;
the loop refuses on that output whatever the status.
- postfix-config.sh: cyrus_sasl_config_path set explicitly (bookworm's
07_sasl_config.diff is gone); maillog_file_permissions=0640; mynetworks
explicit; Dockerfile: SYNC_CHROOT= in /etc/default/postfix,
opendkim-tools dropped, openssl named, systemd-standalone-sysusers so
cron does not bring the whole of systemd.
- The selfpost group is 10998, outside the system range 100-999: in
trixie a package took 998 and the build failed (owner's decision:
a conflict-free id, the distribution's own ids untouched). The
entrypoint hands /data back by name when the user or the group differs.
Guide, backup refusal text, help (design commit 68ec8b2), e2e.
- smtpd_hide_client_session=yes on smtps and submission (owner's
decision): Received: names no client, TLS, login or protocol; port 25
keeps the full header. e2e checks both.
- test/upstream/opendkim-probe.sh makes keys with openssl genrsa.
Tests: on the server, make e2e exit 0 (674 s, the image built on trixie),
go vet + go test 27 packages ok. CHANGELOG [Unreleased], guide,
architecture.md, security.md, roadmap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
147 lines
3.4 KiB
Go
147 lines
3.4 KiB
Go
package postfix
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ParseTime reads a Postfix time value as Postfix 3.10.13 reads it:
|
|
// src/global/conv_time.c, conv_time(), called by get_mail_conf_time() in
|
|
// src/global/mail_conf_time.c. One number - strtol(), so leading blanks and a
|
|
// "+" are taken, a negative number or one past an int is invalid - followed
|
|
// by one unit, w d h m or s in lower case; with no unit, defUnit, which
|
|
// Postfix takes from the parameter's default value (get_def_time_unit(): "5d"
|
|
// gives d, "300s" s, "0h" h). Whatever follows the unit is ignored, so "1h7m"
|
|
// is one hour (assumptions B-PQ10, B-PQ11). A string with no digits is read as
|
|
// 0 of the unit it starts with, as strtol() leaves it there.
|
|
func ParseTime(s string, defUnit byte) (time.Duration, error) {
|
|
if s == "" {
|
|
return 0, fmt.Errorf("postfix: empty time value")
|
|
}
|
|
i := 0
|
|
for i < len(s) && isCSpace(s[i]) {
|
|
i++
|
|
}
|
|
neg := false
|
|
if i < len(s) && (s[i] == '+' || s[i] == '-') {
|
|
neg = s[i] == '-'
|
|
i++
|
|
}
|
|
start := i
|
|
var n int64
|
|
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
|
n = n*10 + int64(s[i]-'0')
|
|
if n > math.MaxInt32 {
|
|
return 0, fmt.Errorf("postfix: time value %q out of range", s)
|
|
}
|
|
i++
|
|
}
|
|
end := i
|
|
if i == start {
|
|
// strtol() converted nothing: the end is the start of the string.
|
|
end, n, neg = 0, 0, false
|
|
}
|
|
if neg && n > 0 {
|
|
return 0, fmt.Errorf("postfix: negative time value %q", s)
|
|
}
|
|
unit := defUnit
|
|
if end < len(s) {
|
|
unit = s[end]
|
|
}
|
|
var per int64
|
|
switch unit {
|
|
case 'w':
|
|
per = 7 * 24 * 3600
|
|
case 'd':
|
|
per = 24 * 3600
|
|
case 'h':
|
|
per = 3600
|
|
case 'm':
|
|
per = 60
|
|
case 's':
|
|
per = 1
|
|
default:
|
|
return 0, fmt.Errorf("postfix: invalid time value %q", s)
|
|
}
|
|
// conv_time(): "if (intval < INT_MAX / WEEK)" and the like.
|
|
if per > 1 && n >= math.MaxInt32/per {
|
|
return 0, fmt.Errorf("postfix: time value %q out of range", s)
|
|
}
|
|
return time.Duration(n*per) * time.Second, nil
|
|
}
|
|
|
|
// isCSpace is C's isspace() in the "C" locale, which strtol() skips.
|
|
func isCSpace(c byte) bool {
|
|
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'
|
|
}
|
|
|
|
// FormatDuration renders a Postfix interval the way the Mail queue card and
|
|
// delivery history share it, so the two cannot drift. Exact day/hour/minute/
|
|
// second values stay exact (`5 minutes`, `5 days`, `1 hour`); a remainder that
|
|
// is rounded to the nearest minute is marked `about` (`about 1 hour 7 minutes`
|
|
// for the stock 4000s backoff cap).
|
|
func FormatDuration(d time.Duration) string {
|
|
if d < 0 {
|
|
d = -d
|
|
}
|
|
sec := int64(d / time.Second)
|
|
if sec == 0 {
|
|
return "0 seconds"
|
|
}
|
|
|
|
days := sec / 86400
|
|
rem := sec % 86400
|
|
hours := rem / 3600
|
|
rem %= 3600
|
|
minutes := rem / 60
|
|
seconds := rem % 60
|
|
|
|
about := false
|
|
if days > 0 || hours > 0 {
|
|
if seconds >= 30 {
|
|
minutes++
|
|
about = true
|
|
} else if seconds > 0 {
|
|
about = true
|
|
}
|
|
seconds = 0
|
|
if minutes >= 60 {
|
|
hours++
|
|
minutes = 0
|
|
}
|
|
if hours >= 24 {
|
|
days++
|
|
hours = 0
|
|
}
|
|
}
|
|
|
|
parts := make([]string, 0, 4)
|
|
if days > 0 {
|
|
parts = append(parts, counted(days, "day"))
|
|
}
|
|
if hours > 0 {
|
|
parts = append(parts, counted(hours, "hour"))
|
|
}
|
|
if minutes > 0 {
|
|
parts = append(parts, counted(minutes, "minute"))
|
|
}
|
|
if seconds > 0 {
|
|
parts = append(parts, counted(seconds, "second"))
|
|
}
|
|
s := strings.Join(parts, " ")
|
|
if about {
|
|
return "about " + s
|
|
}
|
|
return s
|
|
}
|
|
|
|
func counted(n int64, unit string) string {
|
|
if n == 1 {
|
|
return "1 " + unit
|
|
}
|
|
return strconv.FormatInt(n, 10) + " " + unit + "s"
|
|
}
|