Commit graph

4 commits

Author SHA1 Message Date
veg
7c9b13051d Make 'nothing archived' a distinct exit (3) and fsync the dir on publish
Two further hardening findings:

- Destroy-on-success was unsafe: the script exited 0 for empty/no-archive
  lists too, so the README's &&-gated loop could remove a list that
  produced no backup artifact. Give the no-file outcomes a distinct exit
  code 3 ("nothing to archive" — not an error). The destroy loop now
  removes only on exit 0 (archive written) + a successful member dump,
  and leaves exit-3 lists intact unless ALLOW_NO_ARCHIVE=1. Safe by
  default: an unexpected "no archive" can no longer authorise a delete.

- Publish wasn't crash-durable: the handoff fsynced file contents but not
  the parent directory, so the rename's directory entry could be lost to
  a crash after success was reported and the list removed. fsync the
  destination directory after os.replace(), before returning success.

Verified with stubbed mailman-web: NONE/EMPTY -> exit 3 (no file),
FILE -> exit 0 (0600, content intact, source temp unlinked), bad
address -> exit 2. sh -n + shellcheck clean, embedded python compiles.
2026-05-31 12:28:09 +00:00
veg
7b7fcde84a Close root-side TOCTOU in the export handoff
The prior handoff checked the /var/tmp
temp file by name (-L/-f) and then reopened it by name with cp: a
process holding the web uid could swap the path for a symlink in that
window and make root copy an arbitrary root-readable file into $out.

Replace the check-then-reopen with a single root-side python3 step that
opens the source ONCE with O_NOFOLLOW (a swapped symlink makes the open
fail), verifies it is a regular file via the open fd, copies from that
fd into a fresh root-owned mkstemp temp in the destination dir, fsyncs,
and atomically os.replace()s it into place. The path is never
re-resolved by name, so the TOCTOU is closed rather than narrowed.

Paths are passed as argv (heredoc single-quoted), so nothing is
interpolated into the snippet. Verified: regular file publishes 0600
with src removed and no leftover temp; symlink source is rejected
(ELOOP) with dest untouched; existing backups are atomically replaced.
2026-05-31 12:28:09 +00:00
veg
c2b304e4a9 Harden mm3-hk-export: safe handoff, no-cred policy lookup, gated destroy
Address three hardening findings:

- Gate the README backup-and-destroy loop so `mailman-wrapper remove`
  only runs after both the export and member dump succeed; a failed
  backup now leaves the list intact instead of falling through to an
  irreversible delete. Empty/no-archive lists still pass (exit 0 by
  design), so only real failures block removal.

- Replace the curl/REST archive_policy lookup with `mailman-wrapper
  shell`, reading archive_policy straight from core's DB via
  getUtility(IListManager). Eliminates REST credential handling
  entirely (no secret in argv / /proc), drops the curl dependency, and
  removes the @->. list-id conversion.

- Make the final publish a safe privilege handoff: reject non-regular /
  symlinked temp files, copy the web-user mkstemp output into a
  root-owned temp file on the destination filesystem, chmod 600, then
  atomically rename into place. Fixes inherited web-user ownership and
  the cross-filesystem mv that could leave a partial or clobbered
  backup.
2026-05-31 12:24:58 +00:00
veg
f7a9714e93 Add mm3-hk-export: HyperKitty archive -> mbox exporter
Scriptable equivalent of HyperKitty's web Download button for
backup-and-destroy loops. Runs the export through Debian's mailman-web
wrapper (HyperKitty lives in the web DB, separate from core), reproducing
the web view's Email.as_bytes() output.

- Handles the three archive states without writing partial/empty files:
  populated (writes mbox), row-but-empty, and no row at all.
- For a missing row, best-effort core REST lookup distinguishes
  archive_policy=never from 'nothing archived yet'.
- -z/--gzip matches the web .mbox.gz (deterministic header, mtime=0).
- Privilege-safe: web user writes a 0600 mkstemp file, root moves it.
- Address validated before interpolation into the embedded Python.
2026-05-31 12:24:58 +00:00