Commit Diff


commit - a96b2723a3021df065f3860ba0115f2bfea97764
commit + 845c36149fc11758db2f7d64937a70de2a56ed67
blob - d52e35c2e1f4e621ef4078d51583e7add8cb3d06
blob + 7f147c923fac828c5211a4b66dfef861d9307ab8
--- README.md
+++ README.md
@@ -1,24 +1,24 @@
 # OpenIMAPD
 
-A from-scratch IMAP4rev2 ([RFC 9051](https://www.rfc-editor.org/rfc/rfc9051)) server for OpenBSD, written in traditional C in the privilege-separated tradition of `smtpd(8)`, `httpd(8)`, and `ntpd(8)`. No third-party IMAP library, no borrowed protocol engine.
+A from-scratch IMAP4rev2 ([RFC 9051](https://www.rfc-editor.org/rfc/rfc9051)) server for OpenBSD, written in traditional C in the privilege-separated tradition of `smtpd(8)`, `httpd(8)`, and `ntpd(8)`. No third-party IMAP library.
 
-**Status:** pre-release, version 0.1.1. Actively developed. Not yet a port — see [Getting the source](#getting-the-source) below for the repository.
+**Status:** pre-release, version 0.1.1 Actively developed. Not a port. See [Getting the source](#getting-the-source) below for the repository.
 
 ## What it is
 
 - **Privilege-separated**, `smtpd`-style: a root *parent* process reads configuration and binds the listening sockets; unprivileged *listener* and *auth* children handle the network and credential checks; a *store* child is forked per authenticated session, chroots into the mail spool, and drops privileges to that session's own user before ever touching a message. `pledge(2)`, `unveil(2)`, and `chroot(2)` enforce these boundaries, not just convention.
-- **Storage**: stock maildir format (`tmp/`/`new/`/`cur/`, atomic delivery via `rename(2)`) — readable with `ls` and `grep`, and natively understood by `smtpd(8)`'s own `maildir` delivery action. IMAP's extra bookkeeping (UIDs, UIDVALIDITY, per-message mod-sequences, keywords) lives in a small, `flock(2)`-guarded, line-oriented index file per mailbox — plain colon-delimited text, not a database.
+- **Storage**: stock maildir format (`tmp/`/`new/`/`cur/`, atomic delivery via `rename(2)`), readable with `ls` and `grep`, and natively understood by `smtpd(8)`'s own `maildir` delivery action. IMAP's extra bookkeeping (UIDs, UIDVALIDITY, per-message mod-sequences, keywords) lives in a small, `flock(2)`-guarded, line-oriented index file per mailbox, plain colon-delimited text, not a database.
 - **Transport**: STARTTLS on port 143 and implicit TLS on port 993 ([RFC 8314](https://www.rfc-editor.org/rfc/rfc8314)), via `libtls`. `AUTH=PLAIN` only, refused before TLS is established.
 
 ## Protocol coverage
 
 `CAPABILITY`, `STARTTLS`, `AUTHENTICATE`, `ID`, `ENABLE`, `SELECT`, `EXAMINE`, `CREATE`, `DELETE`, `RENAME`, `LIST`, `LSUB`, `NAMESPACE`, `STATUS`, `FETCH` (including `ENVELOPE`, `BODYSTRUCTURE`, and MIME-part-addressed `BODY[<part>]`/`BODY.PEEK[<part>]`), `STORE`, `SEARCH`, `APPEND`, `COPY`, `MOVE`, `EXPUNGE`, `UNSELECT`, `CLOSE`, the `UID`-prefixed form of every command that supports it, `IDLE` with real cross-session push, and the [RFC 7162](https://www.rfc-editor.org/rfc/rfc7162) `CONDSTORE`/`QRESYNC` extensions.
 
-`SUBSCRIBE`, `UNSUBSCRIBE`, and ACL/shared-mailbox support are deliberately out of scope, not unfinished — matched against real client behavior and left out on the same "every extra command is attack surface" principle `smtpd(8)` uses to justify skipping `VRFY`/`EXPN`. Full protocol-scope reasoning and other caveats (flat per-user namespace, `IDLE` push triggers, `EXAMINE` read-only enforcement, etc.) are documented in `imapd(8)`'s CAVEATS section — that man page is the authoritative reference, this file is just an overview.
+`SUBSCRIBE`, `UNSUBSCRIBE`, and ACL/shared-mailbox support are deliberately left out.
 
 ## Requirements
 
-OpenBSD only. This depends on `<imsg.h>`, `pledge(2)`, `unveil(2)`, and libutil's `imsgbuf_*` API, none of which exist outside OpenBSD, so it will not build on any other host. Developed and tested against OpenBSD 8.0. Links against libevent, libtls/libssl/libcrypto, and libutil — all base-system libraries (see `src/Makefile`).
+OpenBSD only. This depends on `<imsg.h>`, `pledge(2)`, `unveil(2)`, and libutil's `imsgbuf_*` API, none of which exist outside OpenBSD. Developed and tested against OpenBSD 8.0. Links against libevent, libtls/libssl/libcrypto, and libutil — all base-system libraries (see `src/Makefile`).
 
 ## Building and installing
 
@@ -30,7 +30,7 @@ doas make install
 
 Installs the daemon to `/usr/local/sbin/imapd`, man pages to `/usr/local/man/man8`, the `imapduser` account-provisioning tool alongside the daemon, and a sample config to `/usr/local/share/examples/imapd/imapd.conf`.
 
-The `rc.d(8)` script is not installed automatically — `install(1)`, not `cp(1)`, matters here so the installed copy is executable regardless of the source tree's own permission bits:
+The `rc.d(8)` script is not installed automatically. `install(1)`, not `cp(1)`, so the installed copy is executable regardless of the source tree's own permission bits:
 
 ```
 doas install -o root -g wheel -m 555 src/rc.d/imapd /etc/rc.d/imapd
@@ -38,7 +38,7 @@ doas install -o root -g wheel -m 555 src/rc.d/imapd /e
 
 ## Configuring
 
-Copy the sample config into place with restrictive permissions — imapd refuses to start against a config that's group- or world-writable, *or* world-readable:
+Copy the sample config into place with restrictive permissions. imapd refuses to start against a config that's group- or world-writable, *or* world-readable:
 
 ```
 doas install -o root -g wheel -m 600 \
@@ -68,9 +68,9 @@ doas rcctl start imapd
 
 Beyond the deliberate protocol-scope decisions covered in `imapd(8)`'s CAVEATS:
 
-- If the listener or auth process exits unexpectedly after startup, it is not automatically restarted — a deliberate choice, not an oversight: neither `smtpd(8)` nor `httpd(8)` auto-restarts their own equivalent core processes either. Recovery is `rcctl restart imapd`. See `imapd(8)`.
+- If the listener or auth process exits unexpectedly after startup, it is not automatically restarted. Recovery is `rcctl restart imapd`. See `imapd(8)`.
 
-`SIGHUP` reloads `spool`, `attachment max`, and the TLS certificate/key without dropping connected sessions, matching `httpd(8)`'s own documented reload behavior — `listen on` and `credentials` changes still require a restart. See `imapd(8)`.
+`SIGHUP` reloads `spool`, `attachment max`, and the TLS certificate/key without dropping connected sessions, `listen on` and `credentials` changes still require a restart. See `imapd(8)`.
 
 IPv6 is supported (`listen on ::` or `listen on *` for dual-stack) but not the default — see `imapd(8)`'s `listen on` directive.
 
blob - bfef11cc5d838831f3115309fc760b76d835c501
blob + 3011ab2b68aa384025c3db089fbdca6bfd0a28ad
--- contrib/imapd-teardown
+++ contrib/imapd-teardown
@@ -16,65 +16,17 @@
 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
-# imapd-teardown -- completely remove an installed imapd, to test
+# imapd-teardown, completely remove an installed imapd, to test
 # repeated from-scratch installs.
 #
-# This is a dev-tree-only tool (unlike contrib/imapduser, it is NOT
-# installed by src/Makefile's afterinstall: target -- there's no real
-# operational reason a production box would ever want to "uninstall
-# itself," so it stays contrib/-only, run straight out of the source
-# tree). It exists because a fresh-install pass (task #228) found
-# five real gaps that only showed up by actually attempting an install
-# on a genuinely clean system -- this script is what makes "genuinely
-# clean system" repeatable without needing an actual fresh box every
-# time.
-#
-# Scope, by design:
-#
-#   - Everything needed to make the NEXT install genuinely fresh --
-#     the daemon binary, its own admin tool (imapduser), both man
-#     pages, the installed sample config, the rc.d script, boot-time
-#     relink artifacts, the running config file, the credentials file
-#     (and its directory), the TLS certificate and key, and the
-#     _imapd/_imapauth system accounts -- is removed.
-#
-#   - The mail spool (real mail data, not install-time cruft) is left
-#     alone unless -M is given explicitly, and -M has its own separate
-#     type-the-path confirmation that -y does not bypass. Revoking an
-#     install and destroying mail are different-consequence decisions,
-#     same reasoning as imapduser's -a/-d split.
-#
-#   - smtpd.conf's shd_userbase table (a different daemon's config) is
-#     never touched. If you also want smtpd to stop trying to deliver
-#     into a wiped spool, that's a separate, deliberate edit to that
-#     table.
-#
 # Usage:
 #   doas ./imapd-teardown [-y] [-M] [-c credentials-dir] [-f config-file]
 #       [-s spool-root] [-T tls-cert] [-K tls-key]
 #
 #   -y  skip the general confirmation prompt (still stops to ask
-#       separately if -M is also given -- see above)
+#       separately if -M is also given)
 #   -M  also remove the mail spool
 #
-# -c/-f/-s/-T/-K override the v1 defaults documented in imapd.8, in
-# case this premio install (or some future one) ever diverges from
-# them; BINDIR/MANDIR are not overridable here since they're a build-
-# time Makefile decision (BINDIR=/usr/local/sbin, MANDIR=/usr/local/
-# man/man in src/Makefile), not a runtime config one.
-#
-# Stops and disables the running service via rcctl(8) before removing
-# anything -- per rcctl(8) itself, "when a package daemon is disabled,
-# it is removed from pkg_scripts and its variables are removed if
-# any" (man.openbsd.org/rcctl.8): the real mechanism for cleanly
-# reversing what "rcctl enable imapd" did, rather than hand-editing
-# rc.conf.local's pkg_scripts line the way an earlier pass in this
-# project's history had to hand-edit smtpd.conf.
-#
-# Safe to re-run: every removal is conditional on the target actually
-# existing, so a partial or already-torn-down install just reports
-# nothing left to do for whatever's already gone.
-
 set -e
 
 BINDIR=/usr/local/sbin
blob - 4a3837a3ad12eadd36f78dce37c7223f721d5187
blob + b2ac6c85f01332165cfe0472ac57af93f72ca3de
--- contrib/imapduser
+++ contrib/imapduser
@@ -18,75 +18,19 @@
 #
 # imapduser -- add or delete an imapd mailbox account.
 #
-# Renamed and given a real -a/-d mode split from its previous identity
-# as "newimapuser", a contrib/-only dev script with add-only behavior.
-# It's now an actual installed part of the package (${PREFIX}/sbin/
-# imapduser, via src/Makefile's afterinstall: target), because there's
-# real OpenBSD ports precedent for exactly this shape of tool: a
-# daemon that keeps its own bespoke, non-system credentials store
-# needs its own installed admin tool to manage it, since useradd(8)/
-# userdel(8)/vipw(8) don't apply to a store that isn't /etc/passwd.
-# Confirmed directly against OpenBSD's own cyrus-sasl2 port PLIST
-# (github.com/openbsd/ports, security/cyrus-sasl2/pkg/PLIST, read
-# directly rather than assumed): it installs "@bin sbin/saslpasswd2"
-# and "@man man/man8/saslpasswd2.8" for precisely this reason --
-# managing sasldb2, SASL's own bespoke secrets store.
-#
-# imapd's credentials file is deliberately self-contained the same
-# way: auth.c's cred_lookup() reads "username:passwordhash:uid:gid:
-# maildir" lines straight out of it and never calls getpwnam(3) for an
-# IMAP end user (see auth.c's own header comment -- that decision is
-# specifically about end users, not about auth's own _imapauth service
-# identity).
-# store.c's per-session store child then drops privileges directly to
-# that uid/gid (IMSG_STORE_INIT handling) before chroot(2)-ing into
-# spool_root and chdir(2)-ing into "/maildir" -- chdir(2) is NOT
-# tolerant of a missing directory (it fatal()s), so the maildir itself
-# has to already exist, owned by that uid/gid, before the first login;
-# store.c only ever mkdir(2)s tmp/new/cur *inside* it on demand.
-#
-# This script exists purely to keep those three things (a credentials
-# line, the on-disk maildir's ownership, and the uid/gid tying them
-# together) consistent with each other -- and, in -d mode, to remove
-# the credentials line safely without disturbing the mail data it
-# pointed at. It does NOT create or remove a real system account: no
-# useradd(8)/userdel(8)/adduser(8), nothing written to /etc/passwd or
-# /etc/group. That's the whole point of the design this script is
-# automating.
-#
 # Usage:
 #   imapduser -a [-c credentials-file] [-s spool-root] [-u uid] [-g gid] username
 #   imapduser -d [-c credentials-file] username
 #
-# -a adds a new mailbox account: creates its maildir (owned by the
-# given, or automatically picked, uid:gid) and appends a credentials-
-# file line, prompting for a password via encrypt(1) (OpenBSD base,
-# man.openbsd.org/encrypt.1), which produces the Blowfish hash
-# crypt_checkpass(3) -- and so auth.c's auth_verify() -- expects.
+# -a adds a new mailbox account.
 #
-# -d removes a mailbox account's credentials-file line ONLY. It
-# deliberately does NOT touch the account's maildir or any mail data
-# in it: revoking login ability and destroying mail are two very
-# different decisions with very different blast radii, and this tool
-# doesn't conflate them. The maildir's path is printed on success so
-# the operator can remove it by hand if that's actually what's wanted.
+# -d removes a mailbox account's credentials-file line ONLY.
 #
-# Exactly one of -a or -d is required. -c/-s default to imapd.8's own
-# documented v1 defaults (/etc/imapd/credentials, /var/mail/imapd). In
-# -a mode, if -u/-g are omitted, the same free id is picked
-# automatically and used for both (matching the uid==gid==1000 pattern
-# already used for the real "dhw" account on premio) -- see
-# next_free_id() below for exactly how "free" is decided. -s/-u/-g are
-# ignored in -d mode (nothing left to size or own once the credentials
-# line is gone).
+# Exactly one of -a or -d is required.
 #
 # Must be run as root (or via doas/su): -a chown(8)s a maildir to an
 # arbitrary uid/gid and both modes write to a file that should stay
-# root-owned, same spirit as parse.y's check_file_secrecy() check on
-# imapd.conf itself (that check isn't applied to the credentials file
-# by auth.c today, but keeping it root-owned/non-world-readable is
-# still the obviously correct posture for a file full of password
-# hashes).
+# root-owned.
 
 set -e
 
@@ -136,25 +80,6 @@ if [ "$(id -u)" -ne 0 ]; then
 	exit 1
 fi
 
-# Canonical ownership/mode for $CRED_FILE, applied any time this script
-# creates or rewrites it (fresh creation in -a mode, or after removing
-# a line in -d mode). Real bug found on a genuinely fresh install:
-# auth.c's auth_main() chroot()s into this file's directory and drops
-# privileges to the fixed _imapauth daemon account BEFORE ever opening
-# this file -- cred_lookup()'s fopen() happens later, per auth request,
-# already running as _imapauth. A root:wheel/0600 file is therefore
-# unreadable by the dropped-privilege process no matter what: auth
-# logs "fopen credentials: Permission denied" and every AUTHENTICATE
-# fails, surfacing to a real IMAP client as a generic "invalid
-# credentials" rather than anything that points at the actual cause.
-# Fixed by owning the file root:_imapauth (group-readable by the one
-# daemon account that ever needs to read it, not group- or world-
-# writable, and not readable by any *other* unprivileged account
-# either) instead of root:wheel. If the _imapauth group doesn't exist
-# yet (daemon accounts not provisioned before this script ran), chgrp
-# fails silently below and the warning tells the operator exactly what
-# to fix and why, rather than leaving a working-looking credentials
-# file that auth can never actually read.
 set_cred_perms() {
 	chown root:wheel "$CRED_FILE" 2>/dev/null || true
 	if ! chgrp _imapauth "$CRED_FILE" 2>/dev/null; then
@@ -169,11 +94,6 @@ set_cred_perms() {
 	chmod 640 "$CRED_FILE"
 }
 
-# True (exit 0) if $1 is already claimed by /etc/passwd's or /etc/group's
-# id column, or by either the uid or gid column of an existing
-# credentials-file line -- checked jointly (not per-namespace) so the
-# same free number is always safe to hand out as *both* a uid and a gid
-# at once, which is what happens below when neither -u nor -g was given.
 id_in_use() {
 	_id=$1
 	awk -F: -v id="$_id" '$3 == id { f=1 } END { exit !f }' /etc/passwd && return 0
@@ -193,13 +113,7 @@ next_free_id() {
 
 do_add() {
 	if [ ! -e "$CRED_FILE" ]; then
-		# Real gap found on a genuinely fresh install (no leftover
-		# /etc/imapd/ from an earlier pass): this used to assume
-		# CRED_FILE's parent directory already existed, and failed
-		# with a bare "No such file or directory" from the shell
-		# redirection below if it didn't -- true for a truly fresh
-		# box, not just a hypothetical. mkdir -p is a no-op if the
-		# directory is already there, so this is safe either way.
+
 		CRED_DIR=${CRED_FILE%/*}
 		if [ "$CRED_DIR" != "$CRED_FILE" ] && [ ! -d "$CRED_DIR" ]; then
 			mkdir -p "$CRED_DIR"
@@ -230,12 +144,6 @@ do_add() {
 	*[!0-9]*|"") echo "${0##*/}: invalid gid: $NEWGID" 1>&2; exit 1 ;;
 	esac
 
-	# Credentials-file maildir field is just the bare directory name
-	# under spool_root (store.c's own log line for a real session shows
-	# this exactly: "maildir dhw", not a full or nested path) -- see
-	# store.c's unveil_path construction ("/" + init.maildir) for why
-	# store.c itself requires this to resolve as a single path
-	# component relative to its chroot.
 	MAILDIR=$USERNAME
 	MAILDIR_PATH=$SPOOL_ROOT/$MAILDIR
 
@@ -272,8 +180,7 @@ do_delete() {
 		exit 1
 	fi
 
-	# Captured only to tell the operator where the mail data is -- this
-	# tool never touches it itself, see the usage comment up top for why.
+	# Captured only to tell the operator where the mail data is.
 	MAILDIR_FIELD=$(awk -F: -v u="$USERNAME" '$1 == u { print $5; exit }' "$CRED_FILE")
 
 	TMP_CRED_FILE=$(mktemp "${CRED_FILE}.XXXXXXXX") || {
blob - ac69fe628610f731e8f0c4d4be75f349932e41d4
blob + c805bc504412d3fbd4b2ce04c460968abe84b808
--- contrib/imapduser.8
+++ contrib/imapduser.8
@@ -1,7 +1,6 @@
 .\" $OpenIMAPD$
 .\"
-.\" Written for the OpenIMAPD project. Public domain / no rights reserved,
-.\" matching the project's ports-oriented, OpenBSD-base-inclusion goal.
+.\" Written for the OpenIMAPD project. Public domain / no rights reserved.
 .\"
 .Dd $Mdocdate: August 19 2026 $
 .Dt IMAPDUSER 8
@@ -171,18 +170,4 @@ Default spool root; see
 .Xr imapd 8
 .Sh HISTORY
 .Nm
-was written for the OpenIMAPD project.
-It replaces an earlier, add-only, contrib/-only script named
-.Nm newimapuser ,
-renamed and split into
-.Fl a Ns / Ns Fl d .
-.Nm
-is now installed rather than left as a dev-tree-only script, following
-the same real precedent as OpenBSD's own
-.Sy cyrus-sasl2
-port, which installs
-.Xr saslpasswd2 8
-to
-.Pa ${PREFIX}/sbin
-for the same reason: managing a daemon's own bespoke, non-system
-credentials store.
+was written for the OpenIMAPD project.
\ No newline at end of file
blob - a2453cb647b3f6b3ce336c5fa0ada94d5d63bfd8
blob + b470a59c6c9e616124c7179097afd260205e1236
--- src/Makefile
+++ src/Makefile
@@ -4,35 +4,8 @@
 # build on a non-OpenBSD host: it depends on <imsg.h>, pledge(2), unveil(2),
 # and libutil's imsgbuf_* API, none of which exist outside OpenBSD.
 
-# Renamed from "openimap" to "imapd" to match OpenBSD's own naming
-# convention for its "Open*" projects: the installed daemon drops the
-# "Open" prefix and just goes by "<protocol>d". Confirmed against
-# OpenBSD's own innovations page (openbsd.org/innovations.html):
-# OpenNTPD ships ntpd, OpenSMTPD ships smtpd, OpenBGPD ships bgpd,
-# OpenIKED ships iked -- and in each case the "D" is already part of
-# the *project* name itself, not added at the daemon-naming step.
-# OpenSSH is the outlier (no "D"), and only because it ships a whole
-# toolkit -- ssh/scp/sftp/ssh-keygen/etc -- not one daemon. This
-# project fits the single-daemon shape, so the project itself was
-# renamed OpenIMAP -> OpenIMAPD to match.
 PROG=		imapd
 
-# listener.c and store.c were each a single file through the end of
-# 0.1 (10,619 and 7,768 lines respectively) -- far larger than any file
-# in smtpd (24 files, largest 3,104 lines) or httpd (largest 2,029
-# lines), the two base-system daemons this project otherwise follows
-# most closely. Split by responsibility, matching that precedent: the
-# listener process's own source is now listener.c (core: accept loop,
-# session I/O, line dispatch) + auth_cmd.c + mailbox_cmd.c + append_cmd.c
-# + fetch_cmd.c + search_cmd.c + store_cmd.c + store_ipc.c, sharing
-# struct session and cross-file prototypes via listener.h (not installed,
-# not part of the wire protocol -- see that file). The store process's
-# own source is now store.c (core: imsg dispatch loop) + index.c + mime.c
-# + envelope.c + mbox_fetch.c + mbox_search.c + mbox_store.c +
-# mbox_manage.c + mbox_copy.c, sharing struct mbox_index and cross-file
-# prototypes via store_internal.h the same way. Pure move, no behavior
-# change -- see each new file's own header comment for exactly which
-# commands/responsibility it holds.
 SRCS=		main.c parent.c log.c imsgev.c parse.y \
 		listener.c auth_cmd.c mailbox_cmd.c append_cmd.c fetch_cmd.c \
 		search_cmd.c store_cmd.c store_ipc.c \
@@ -40,83 +13,24 @@ SRCS=		main.c parent.c log.c imsgev.c parse.y \
 		store.c index.c mime.c envelope.c mbox_fetch.c mbox_search.c \
 		mbox_store.c mbox_manage.c mbox_copy.c
 
-# imapd isn't in OpenBSD base and has no ports-framework Makefile of its
-# own (no bsd.port.mk, no PREFIX), so BINDIR/MANDIR must be set explicitly:
-# neither bsd.own.mk nor bsd.prog.mk defaults BINDIR (confirmed by grepping
-# both -- share/mk/bsd.own.mk and share/mk/bsd.prog.mk -- neither contains
-# a "BINDIR?=" line; base daemons that aren't building via bsd.port.mk set
-# it themselves per-Makefile, e.g. smtpd's own Makefile: BINDIR=/usr/sbin).
-# /usr/local is "where to install things in general" for locally-
-# administered, non-base software per share/man/man7/ports.7 (PREFIX
-# description) -- so /usr/local/sbin + /usr/local/man/man match the same
-# convention ports use for daemons, without requiring the ports framework.
 BINDIR=		/usr/local/sbin
 MANDIR=		/usr/local/man/man
 
-# RELINK: a shell command bsd.prog.mk uses to smoke-test a from-source
-# relink of ${PROG} at "make install" time. Building this triggers bsd.
-# prog.mk's documented re-link-kit mechanism (share/mk/bsd.prog.mk,
-# confirmed against the live upstream file): install produces ${PROG}.tar
-# (containing ${OBJS} + a generated install.sh that recompiles from those
-# objects in random link order, runs this RELINK command against the
-# result, then installs it) and drops it at
-# /usr/share/relink/${BINDIR}/${PROG}/${PROG}.tar. Matches smtpd's own
-# precedent verbatim (RELINK= "./${PROG} -V > /dev/null", confirmed
-# against smtpd's live Makefile) -- "-V" prints the version and exits 0
-# with no side effects (see main.c), so this just proves the relinked
-# binary starts and runs correctly before anything overwrites the
-# installed copy.
-#
-# NOTE: unlike base's libc/libcrypto/ld.so/sshd, nothing on this system
-# automatically *consumes* this tarball at boot -- /etc/rc's reorder_libs()
-# has a hardcoded allowlist that doesn't include imapd (confirmed by
-# reading etc/rc directly) and won't be patched to add it (fragile against
-# base upgrades). rc.d/imapd's own rc_pre() hook is the consumer
-# instead -- see that script for the actual relink-at-service-start logic.
-# NOTE: deliberately no embedded quotes around this value -- bsd.prog.mk's
-# own recipe for the RELINK feature already does "echo \"${RELINK}\" >> $@"
-# when generating install.sh, so wrapping this value in its own literal
-# quotes (matching smtpd's Makefile precedent verbatim) causes a doubled-
-# quote collision: the generated line becomes
-# echo ""./${PROG} -V > /dev/null"" >> install.sh, which the shell parses
-# as two stacked redirects (> /dev/null, then >> install.sh) rather than
-# literal text -- the later redirect wins for the same fd, so "> /dev/null"
-# silently drops and install.sh ends up with a bare "./${PROG} -V" instead
-# of the intended output-suppressed form. Confirmed by reading the actual
-# generated line in a real "make install" transcript on test hardware: "-V"'s
-# stdout leaked into rc.d/imapd's rc_pre() relink log instead of being
-# discarded. Harmless (doesn't affect correctness -- install.sh's
-# "set -o errexit" still aborts on real failures either way), but not the
-# intended behavior, so leaving the quotes off here instead.
 RELINK=		./${PROG} -V > /dev/null
 
 # bsd.prog.mk's built-in .y suffix rule runs yacc(1) on parse.y and
-# compiles the result -- no extra machinery needed here, matching every
-# other base-system daemon that ships a parse.y (ripd, smtpd, httpd,
-# ntpd, etc. all just list it in SRCS the same way). -y (POSIX-mode
-# output naming, y.tab.c/y.tab.h) is yacc(1)'s default on OpenBSD, so no
-# YFLAGS override is needed either.
+# compiles the result.
 
 # imsg_init(3): imsgbuf_init/imsgbuf_read/imsgbuf_write/imsg_get/
 # imsg_compose live in libutil on OpenBSD.
 LDADD=		-lutil
 DPADD=		${LIBUTIL}
 
-# event_init/event_set/event_add/event_del/event_dispatch (<event.h>,
-# used throughout listener.c/parent.c/auth.c/store.c's event loops) live
-# in libevent on OpenBSD, which -- like libtls below -- is base-system
-# but not linked in automatically. Confirmed against httpd's own
-# Makefile: LDADD=-levent -ltls -lssl -lcrypto -lutil.
+# event_init/event_set/event_add/event_del/event_dispatch.
 LDADD+=		-levent
 DPADD+=		${LIBEVENT}
 
-# TLS (STARTTLS on 143, implicit TLS on 993 per RFC 8314): listener.c now
-# actually terminates TLS via libtls (tls_server/tls_configure/
-# tls_accept_socket/tls_handshake/tls_read/tls_write/tls_close), sourced
-# against src/lib/libtls/tls.h and httpd's server_tls_init()/server_tls_
-# handshake(). -ltls pulls in libssl/libcrypto itself on OpenBSD, but
-# both are listed explicitly anyway, matching how httpd's own Makefile
-# links it.
+# TLS (STARTTLS on 143, implicit TLS on 993 per RFC 8314).
 LDADD+=		-ltls -lssl -lcrypto
 DPADD+=		${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
 
@@ -133,16 +47,7 @@ CFLAGS+=	-Wsign-compare -Wcast-qual -Wcast-align
 DEBUG=		-g
 
 # Sample imapd.conf, installed read-only at /usr/local/share/examples/
-# imapd/imapd.conf -- matching the real OpenBSD ports convention for
-# sample configs (ports(7), the @sample PLIST keyword: a port installs
-# its sample under ${PREFIX}/share/examples/${PKGNAME}/ and pkg_add(1)
-# copies it into place on first install). Confirmed by reading share/
-# mk/bsd.prog.mk directly rather than guessed: /etc/examples/ itself is
-# NOT an option here -- that mechanism is base-only, populated by base's
-# own etc/Makefile during a release build, with no hook for locally-
-# installed software at all. Using the ports-convention path now, even
-# before this project has an actual port, means the eventual port's
-# PLIST can just reference this same path rather than needing rework.
+# imapd/imapd.conf.
 #
 # afterinstall: is bsd.prog.mk's own documented extension point for
 # exactly this (".if !target(afterinstall)" guards its default no-op,
@@ -160,17 +65,6 @@ afterinstall:
 
 # imapduser: the account-provisioning tool for imapd's own bespoke
 # credentials store (see contrib/imapduser's own header comment and
-# imapduser.8). It isn't compiled -- it's a shell script -- so it can't
-# be a second bsd.prog.mk PROG (that machinery only supports one per
-# Makefile); installed here via the same afterinstall: hook as the
-# sample config above instead, same ${BINDIR}/${MANDIR} destinations
-# and 555/444 modes bsd.prog.mk itself would use for PROG/MAN. Matches
-# real OpenBSD ports precedent for a daemon shipping its own bespoke-
-# credentials-store admin tool as an installed binary rather than a
-# dev-tree-only script: cyrus-sasl2's port PLIST installs saslpasswd2
-# to ${PREFIX}/sbin with its own man page for exactly the same reason
-# (confirmed by reading that PLIST directly: github.com/openbsd/ports,
-# security/cyrus-sasl2/pkg/PLIST -- "@bin sbin/saslpasswd2" / "@man
-# man/man8/saslpasswd2.8").
+# imapduser.8).
 
 .include <bsd.prog.mk>
blob - 0af51913734a344c0d44415b736ff248d1c5fd40
blob + 3e52fad9d3e1bff8f2aa3c844b32c0036673d4cb
--- src/append_cmd.c
+++ src/append_cmd.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * append_cmd.c -- APPEND: literal-driven message upload, and its
+ * append_cmd.c, APPEND: literal-driven message upload, and its
  * asynchronous IMSG_MBOX_APPENDED completion handling.
  */
 
@@ -89,7 +89,7 @@ parse_date_time(const char *s, int64_t *out)
 	return (0);
 }
 
-/* Result struct for parse_append_args() -- avoids an unwieldy number of out-parameters. */
+/* Result struct for parse_append_args(), avoids an unwieldy number of out-parameters. */
 struct append_parsed {
 	char		mailbox[MBOX_NAME_MAX];
 	uint32_t	sysflags;
@@ -249,9 +249,9 @@ parse_append_args(char *args, struct append_parsed *ou
 		out->litlen = (uint64_t)litlen;
 
 		if (out->litnonsync && out->litlen > 4096) {
-			/* RFC 9051 SS4.3: non-sync literals capped at 4096 octets -- BAD, not cmd_append()'s NO size cap. */
+			/* RFC 9051 SS4.3: non-sync literals capped at 4096 octets, BAD, not cmd_append()'s NO size cap. */
 			*errmsg = "non-synchronizing literal exceeds RFC "
-			    "9051 SS4.3's 4096-octet limit -- use a "
+			    "9051 SS4.3's 4096-octet limit, use a "
 			    "synchronizing literal instead";
 			return (-1);
 		}
@@ -284,15 +284,15 @@ cmd_append(struct session *s, const char *tag, char *a
 	}
 
 	if (parsed.litlen > APPEND_LITERAL_MAX) {
-		/* RFC 5530 LIMIT code -- matches APPEND_LITERAL_MAX's situation precisely. */
+		/* RFC 5530 LIMIT code, matches APPEND_LITERAL_MAX's situation precisely. */
 		session_reply(s, tag, "NO",
-		    "[LIMIT] message too large for this server (v1 size "
-		    "limit -- see APPEND_LITERAL_MAX)");
+		    "[LIMIT] message too large for this server "
+		    "limit, see APPEND_LITERAL_MAX)");
 		return (1);
 	}
 
 	if (s->store_iev == NULL) {
-		/* Same store_iev invariant as cmd_select()/cmd_fetch() -- ST_AUTH requires it already wired. */
+		/* Same store_iev invariant as cmd_select()/cmd_fetch(), ST_AUTH requires it already wired. */
 		log_warnx("session %u: APPEND with no store channel wired",
 		    s->id);
 		session_reply(s, tag, "NO", "[SERVERBUG] internal error");
@@ -352,7 +352,7 @@ session_finish_append(struct session *s)
 	    sizeof(req.mailbox) ||
 	    strlcpy(req.keywords, s->append_keywords, sizeof(req.keywords)) >=
 	    sizeof(req.keywords)) {
-		log_warnx("session %u: APPEND mailbox/keywords truncated -- "
+		log_warnx("session %u: APPEND mailbox/keywords truncated, "
 		    "can't happen (both already bounded when first stored)",
 		    s->id);
 		session_reply(s, s->pending_tag, "NO", "[SERVERBUG] internal error");
@@ -416,7 +416,7 @@ session_handle_mbox_appended(struct session *s,
 	if (res->error != MBOX_OP_OK) {
 		if (res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX)
 			session_reply(s, s->pending_tag, "NO",
-			    "[TRYCREATE] no such mailbox");	/* SS6.3.12: reports why, not a promise CREATE would help (v1 has none) */
+			    "[TRYCREATE] no such mailbox");	/* SS6.3.12: reports why, not a promise CREATE would help */
 		else
 			session_reply(s, s->pending_tag, "NO",
 			    "APPEND failed");
@@ -431,7 +431,7 @@ session_handle_mbox_appended(struct session *s,
 		appended_to_selected =
 		    (strcmp(s->append_mailbox, s->selected_mailbox) == 0);
 
-	/* RFC 9051 SS6.3.13: APPEND always adds one message -- unlike EXPUNGE/CLOSE, no res->count gate needed. */
+	/* RFC 9051 SS6.3.13: APPEND always adds one message, unlike EXPUNGE/CLOSE, no res->count gate needed. */
 	session_notify_idle_peers(s);
 
 	if (s->append_prev_state == SESSION_SELECTED && appended_to_selected) {
blob - 03591a478c2cae7027e77e94fad9b6e7472667f3
blob + 374c9f5dc398eca7c5d2b03b65e9d36cc15cf558
--- src/auth.c
+++ src/auth.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* auth.c -- credential verification process: AUTHENTICATE PLAIN against the flat cred file. */
+/* auth.c, credential verification process: AUTHENTICATE PLAIN against the flat cred file. */
 
 #include <sys/types.h>
 
@@ -34,14 +34,14 @@
 
 struct cred_entry {
 	char	username[AUTH_USERNAME_MAX];
-	char	passwordhash[128];	/* bcrypt "$2b$NN$..." -- generous */
+	char	passwordhash[128];	/* bcrypt "$2b$NN$...", generous */
 	uid_t	uid;
 	gid_t	gid;
 	char	maildir[AUTH_MAILDIR_MAX];
 };
 
 static struct imsgev	 iev_listener;
-static struct imsgev	 iev_parent;	/* fd 3, alive for the process's lifetime -- task #321 */
+static struct imsgev	 iev_parent;	/* fd 3, alive for the process's lifetime */
 static char		 cred_file_basename[256];
 
 static int	 cred_lookup(const char *, const char *username,
@@ -119,20 +119,12 @@ auth_main(void)
 	event_init();
 	imsgev_init(&iev_listener, peer_fd, auth_dispatch, NULL);
 
-	/* task #321: reuses fd 3's populated ibuf3 -- a fresh imsgbuf_init()
-	 * would drop buffered bytes. Kept alive post-boot (unlike before)
-	 * so successful logins can IMSG_AUTH_CRED parent directly; mirrors
-	 * listener.c's own boot -> iev_parent conversion. */
 	imsgev_init_from_ibuf(&iev_parent, &ibuf3, auth_dispatch_parent, NULL);
 
 	/* unveil() path is relative to the chroot above: "/" + basename. */
 	{
 		char unveil_path[512];
 
-		/* cred_file_basename[256] is already strlcpy(3)-truncation-
-		 * checked above; "/" + up to 255 bytes can never approach
-		 * this 512-byte buffer, so snprintf(3) here can't truncate --
-		 * the return value is explicitly discarded, not overlooked. */
 		(void)snprintf(unveil_path, sizeof(unveil_path), "/%s",
 		    cred_file_basename);
 		if (unveil(unveil_path, "r") == -1)
@@ -188,7 +180,7 @@ auth_dispatch(int fd, short event, void *arg)
 				log_warnx("bad IMSG_AUTH_REQUEST");
 				break;
 			}
-			/* imsg_get_data() guarantees size, not NUL termination -- force it */
+			/* imsg_get_data() guarantees size, not NUL termination, force it */
 			req.username[sizeof(req.username) - 1] = '\0';
 			req.password[sizeof(req.password) - 1] = '\0';
 			memset(&res, 0, sizeof(res));
@@ -203,10 +195,6 @@ auth_dispatch(int fd, short event, void *arg)
 				log_warn("imsg_compose IMSG_AUTH_RESULT");
 			imsgev_add(iev);
 
-			/* task #321: parent, not listener, is who actually
-			 * spawns the store child -- send it uid/gid/maildir
-			 * directly rather than trusting listener to relay
-			 * what we just told it. */
 			if (res.ok) {
 				struct imsg_auth_cred	 cred;
 
@@ -215,7 +203,7 @@ auth_dispatch(int fd, short event, void *arg)
 				cred.uid = res.uid;
 				cred.gid = res.gid;
 				/* cred.maildir and res.maildir are both sized
-				 * AUTH_MAILDIR_MAX -- truncation is structurally
+				 * AUTH_MAILDIR_MAX, truncation is structurally
 				 * impossible, so the return value is discarded
 				 * deliberately, same as imsg_store_init's own
 				 * maildir field elsewhere. */
@@ -241,7 +229,7 @@ auth_dispatch(int fd, short event, void *arg)
 	(void)fd;
 }
 
-/* task #321: parent never sends auth anything post-boot, so this exists to flush queued IMSG_AUTH_CRED writes and notice if parent's end closes */
+/* parent never sends auth anything post-boot, so this exists to flush queued IMSG_AUTH_CRED writes and notice if parent's end closes */
 static void
 auth_dispatch_parent(int fd, short event, void *arg)
 {
@@ -303,7 +291,7 @@ auth_verify(struct imsg_auth_request *req, struct imsg
 	explicit_bzero(&ce, sizeof(ce));
 }
 
-/* linear scan of "username:passwordhash:uid:gid:maildir" lines -- fine for v1's small cred files */
+/* linear scan of "username:passwordhash:uid:gid:maildir" lines */
 static int
 cred_lookup(const char *path, const char *username, struct cred_entry *out)
 {
blob - 4a48404e352ed638dd3f5cf8012c9b7d2326dd79
blob + fa4f85eeb4c4c347226d0d001e2580ec44713e2e
--- src/auth_cmd.c
+++ src/auth_cmd.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * auth_cmd.c -- CAPABILITY/NOOP/LOGOUT/ID/LOGIN/STARTTLS/
+ * auth_cmd.c, CAPABILITY/NOOP/LOGOUT/ID/LOGIN/STARTTLS/
  * AUTHENTICATE/ENABLE: command-any and command-nonauth handlers that
  * don't need an established mailbox session.
  */
@@ -44,11 +44,7 @@
 #include "listener.h"
 
 /*
- * RFC 9051 SS6.1.1 capability strings, selected by session->tls_active;
- * CONDSTORE/QRESYNC (RFC 7162) advertised regardless of TLS. LOGINDISABLED
- * is advertised in both strings -- cmd_login() below refuses LOGIN
- * unconditionally, pre- and post-TLS, so both capability strings should
- * say so rather than implying LOGIN might work once TLS is up.
+ * RFC 9051 SS6.1.1 capability strings, selected by session->tls_active.
  */
 #define CAPABILITY_PRE_TLS	"IMAP4rev2 STARTTLS LOGINDISABLED ID CONDSTORE QRESYNC"
 #define CAPABILITY_POST_TLS	"IMAP4rev2 AUTH=PLAIN LOGINDISABLED ID CONDSTORE QRESYNC"
@@ -57,7 +53,7 @@
 int
 cmd_capability(struct session *s, const char *tag, char *args)
 {
-	(void)args;	/* RFC 9051: "Arguments: none" -- extra args ignored, not rejected */
+	(void)args;	/* RFC 9051: "Arguments: none", extra args ignored, not rejected */
 
 	session_untagged(s, s->tls_active ?
 	    "CAPABILITY " CAPABILITY_POST_TLS : "CAPABILITY " CAPABILITY_PRE_TLS);
@@ -102,15 +98,7 @@ cmd_id(struct session *s, const char *tag, char *args)
 
 /*
  * LOGIN is permanently disabled, matching LOGINDISABLED in both
- * CAPABILITY strings above. AUTHENTICATE PLAIN (SASL) is the only
- * supported credential path -- one fewer parser/credential-handling
- * code path than supporting both, per this project's smaller-feature-
- * set-is-smaller-attack-surface design principle. (A full LOGIN
- * implementation was written and real-hardware-verified during Canary
- * Mail interop testing, since Canary sends LOGIN, never AUTHENTICATE
- * PLAIN; every other client tested -- Apple Mail, Airmail -- uses
- * AUTHENTICATE PLAIN without issue, so LOGIN was re-disabled rather
- * than kept enabled solely for one client's benefit.)
+ * CAPABILITY strings above.
  */
 int
 cmd_login(struct session *s, const char *tag, char *args)
@@ -175,17 +163,6 @@ sasl_plain_finish(struct session *s, const char *tag, 
 		}
 	}
 
-	/*
-	 * rawlen==0 (the allow_empty_equals "=" case just above) would end
-	 * up here anyway -- memchr(3) can't find a NUL in a zero-length
-	 * buffer -- but reading raw's still-uninitialized stack contents
-	 * through memchr(3) to get there is needless even though every
-	 * real memchr(3) treats n==0 as a no-op that never dereferences
-	 * the pointer. Short-circuit it explicitly instead of relying on
-	 * that (cppcheck flagged this as a genuine uninitialized-variable
-	 * read, which is technically correct about raw's contents even
-	 * though the call itself is harmless).
-	 */
 	if (rawlen == 0) {
 		session_reply(s, tag, "BAD", "malformed SASL PLAIN message");
 		explicit_bzero(raw, sizeof(raw));
@@ -210,7 +187,7 @@ sasl_plain_finish(struct session *s, const char *tag, 
 	passwd = nul + 1;
 	passwdlen = (size_t)rawlen - off - authcidlen - 1;
 
-	/* RFC 4616 SS2: empty prep result SHALL fail verification -- NO not BAD, framing is fine */
+	/* RFC 4616 SS2: empty prep result SHALL fail verification, NO not BAD, framing is fine */
 	if (authcidlen == 0 || passwdlen == 0) {
 		session_reply(s, tag, "NO", "[AUTHENTICATIONFAILED] authentication failed");
 		explicit_bzero(raw, sizeof(raw));
@@ -309,7 +286,7 @@ cmd_authenticate(struct session *s, const char *tag, c
 		return (1);
 	}
 
-	/* v1 only implements PLAIN, matching CAPABILITY_POST_TLS */
+	/* only implements PLAIN, matching CAPABILITY_POST_TLS */
 	if (strcasecmp(mech, "PLAIN") != 0) {
 		session_reply(s, tag, "NO",
 		    "authentication mechanism not available");
@@ -377,9 +354,7 @@ cmd_enable(struct session *s, const char *tag, char *a
 	/*
 	 * buf[64] can never truncate here: the only strings ever appended
 	 * are these two fixed literals plus one separator space, 18 bytes
-	 * total in the worst case ("QRESYNC CONDSTORE") -- but the return
-	 * value is still explicitly discarded rather than silently ignored,
-	 * per this project's check-every-return-value standard.
+	 * total in the worst case ("QRESYNC CONDSTORE").
 	 */
 	buf[0] = '\0';
 	if (newly_qresync)
blob - 8c03107f9c2ffdf3e003d2b7297d2dbb48146bb3
blob + 51f65325e114d9eab73fcbd8cff51ff4da44736c
--- src/envelope.c
+++ src/envelope.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* envelope.c -- the ENVELOPE and BODYSTRUCTURE FETCH response builders. */
+/* envelope.c, the ENVELOPE and BODYSTRUCTURE FETCH response builders. */
 
 #include <sys/types.h>
 #include <sys/file.h>
@@ -131,7 +131,7 @@ envbuf_append_one_address(char *buf, size_t bufsize, s
 			}
 		}
 		if (gt >= toklen)
-			return (-1);	/* unmatched '<' -- malformed, skip */
+			return (-1);	/* unmatched '<', malformed, skip */
 
 		{
 			const char	*disp = tok;
@@ -469,7 +469,7 @@ build_envelope(const char *basename, char **buf_out, u
 
 fail:
 	log_warnx("session %u: message %s: formatted ENVELOPE exceeds "
-	    "ENVELOPE_MAX -- ENVELOPE skipped", session_id, basename);
+	    "ENVELOPE_MAX, ENVELOPE skipped", session_id, basename);
 	free(hdrbuf);
 	return (-1);
 }
@@ -681,5 +681,3 @@ build_bodystructure(const char *basename, char **buf_o
 	*len_out = (uint32_t)outlen;
 	return (0);
 }
-
-/* Parses RFC 9051 SS6.4.5.1 section-part (e.g. "3.1") into 1-based part numbers; re-validates untrusted input. -1 on bad syntax or > maxpath. */
blob - 075fcd37dae4bde752279995e76c977001f0e82a
blob + c30aafc310af2638e430368765831fed1d627e96
--- src/fetch_cmd.c
+++ src/fetch_cmd.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * fetch_cmd.c -- FETCH: attribute/section-spec parsing and
+ * fetch_cmd.c, FETCH: attribute/section-spec parsing and
  * response building.
  */
 
@@ -335,7 +335,7 @@ parse_fetch_atts(char *spec, uint32_t *attrs_out, int 
 		} else if (strcasecmp(tok, "RFC822.SIZE") == 0) {
 			attrs |= MBOX_FETCH_RFC822_SIZE;
 		} else if (strcasecmp(tok, "MODSEQ") == 0) {
-			attrs |= MBOX_FETCH_MODSEQ;	/* RFC 7162 SS3.1.4.2 -- also CONDSTORE-enabling, cmd_fetch() checks this bit */
+			attrs |= MBOX_FETCH_MODSEQ;	/* RFC 7162 SS3.1.4.2, also CONDSTORE-enabling, cmd_fetch() checks this bit */
 		} else if (strcasecmp(tok, "BODY.PEEK[HEADER]") == 0) {
 			attrs |= MBOX_FETCH_BODY_HEADER;	/* the one exact-match BODY[...]; plain BODY[HEADER] would need \Seen, unimplemented */
 		} else if (strncasecmp(tok, "BODY.PEEK[", strlen("BODY.PEEK[")) ==
@@ -350,7 +350,7 @@ parse_fetch_atts(char *spec, uint32_t *attrs_out, int 
 
 			close = strchr(bracket_start, ']');
 			if (close == NULL) {
-				degraded = 1;	/* not well-bracketed -- same lenient skip as other unsupported forms */
+				degraded = 1;	/* not well-bracketed, same lenient skip as other unsupported forms */
 				continue;
 			}
 			if ((size_t)(close - bracket_start) >= sizeof(inner)) {
@@ -397,7 +397,7 @@ parse_fetch_atts(char *spec, uint32_t *attrs_out, int 
 				return (-1);
 			}
 			if (attrs & MBOX_FETCH_HEADER_FIELDS)
-				continue; /* already captured one -- ignore any further duplicates */
+				continue; /* already captured one, ignore any further duplicates */
 
 			if (toklen - strlen("BODY.PEEK[") - 1 >=
 			    sizeof(inner)) {
@@ -443,7 +443,7 @@ parse_fetch_atts(char *spec, uint32_t *attrs_out, int 
 
 	if (attrs == 0) {
 		/* every requested item was unsupported, e.g. BODY[<part>] or RFC822(.HEADER/.TEXT) alone */
-		*errmsg = "cannot fetch that message content yet -- "
+		*errmsg = "cannot fetch that message content yet, "
 		    "supported: FLAGS/UID/INTERNALDATE/RFC822.SIZE/MODSEQ/"
 		    "ENVELOPE/(BODY|BODYSTRUCTURE)/BODY.PEEK[...]";
 		return (-2);
@@ -468,7 +468,7 @@ const char *fetch_month_names[12] = {
 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 };
 
-/* RFC 9051 SS9 date-time; always formats in UTC "+0000" -- ts carries no tz info and a chroot'd store child has no tzdata */
+/* RFC 9051 SS9 date-time; always formats in UTC "+0000", ts carries no tz info and a chroot'd store child has no tzdata */
 void
 format_internaldate(int64_t ts, char *out, size_t outsize)
 {
@@ -479,7 +479,7 @@ format_internaldate(int64_t ts, char *out, size_t outs
 		if (strlcpy(out, "01-Jan-1970 00:00:00 +0000", outsize) >=
 		    outsize)
 			log_warnx("format_internaldate: fallback string "
-			    "truncated -- caller's buffer too small");
+			    "truncated, caller's buffer too small");
 		return;
 	}
 
@@ -777,7 +777,7 @@ parse_fetch_modifiers(char *modspec, struct imsg_mbox_
 	return (0);
 }
 
-/* RFC 9051 SS6.4.5 fetch + RFC 4466/7162 modifier list; plain BODY[...] and BODY[<part>] are a deliberate v1 scope cut */
+/* RFC 9051 SS6.4.5 fetch + RFC 4466/7162 modifier list; plain BODY[...] and BODY[<part>] are a deliberate scope cut */
 int
 cmd_fetch(struct session *s, const char *tag, char *args)
 {
@@ -824,7 +824,7 @@ fetch_dispatch(struct session *s, const char *tag, cha
 
 	if (strchr(seqtok, ',') != NULL) {
 		session_reply(s, tag, "BAD",
-		    "comma-separated sequence sets not supported in v1 -- "
+		    "comma-separated sequence sets not supported "
 		    "issue separate FETCH commands");
 		return (1);
 	}
@@ -853,7 +853,7 @@ fetch_dispatch(struct session *s, const char *tag, cha
 		log_debug("session %u: %s: one or more unsupported message "
 		    "data items silently dropped (plain BODY[...]/BODY[<part>]"
 		    "/BODY.PEEK[<part>] with MESSAGE/RFC822|GLOBAL or MULTIPART"
-		    " nested numbering/RFC822[.HEADER/.TEXT]) -- answering "
+		    " nested numbering/RFC822[.HEADER/.TEXT]), answering "
 		    "with whatever was recognized", s->id, cmdname);
 
 	memset(&req, 0, sizeof(req));
@@ -873,7 +873,7 @@ fetch_dispatch(struct session *s, const char *tag, cha
 	req.partial_start = partial_start;
 	req.partial_count = partial_count;
 
-	/* verbatim client-typed label never crosses to store.c -- stashed here for session_send_fetch_response() to echo */
+	/* verbatim client-typed label never crosses to store.c, stashed here for session_send_fetch_response() to echo */
 	if (attrs & MBOX_FETCH_BODY_HEADER) {
 		if (strlcpy(s->pending_header_label, "HEADER",
 		    sizeof(s->pending_header_label)) >=
@@ -943,7 +943,7 @@ fetch_dispatch(struct session *s, const char *tag, cha
 		req.attrs |= MBOX_FETCH_UID;
 
 	if (s->store_iev == NULL) {
-		/* same invariant check as cmd_select() -- ST_SELECTED requires store_iev already wired */
+		/* same invariant check as cmd_select(), ST_SELECTED requires store_iev already wired */
 		log_warnx("session %u: %s with no store channel wired",
 		    s->id, cmdname);
 		session_reply(s, tag, "NO", "[SERVERBUG] internal error");
blob - 2ce9b6536055d078f1333f243a394898dd6243aa
blob + f0352b50e26676e1d971d79906d320fb43b27b6a
--- src/imapd.8
+++ src/imapd.8
@@ -377,7 +377,7 @@ by default; these, along with the listen address, are 
 .Pa /etc/imapd.conf
 (or the file named by
 .Fl f )
-at startup, falling back to those v1 defaults for any
+at startup, falling back to defaults for any
 .Ic listen
 directive the file omits.
 IPv6 and dual-stack binding are available but not the default; see the
@@ -419,122 +419,4 @@ This implementation is under active development.
 .Li UNSUBSCRIBE ,
 and shared or multi-user mailboxes
 .Pq no Li ACL support
-are deliberate scope decisions, not gaps awaiting implementation:
-matched against real Apple Mail client behavior, both features exist
-primarily to manage large numbers of shared or public mailboxes on a
-multi-user server, a scenario this single-user, no-shared-mailbox
-implementation does not have.
-This mirrors
-.Xr smtpd 8 Ns 's
-own precedent of omitting
-.Li VRFY Ns / Ns Li EXPN
-despite RFC recommendation, on the same reasoning: every additional
-command is attack surface, and neither of these two currently serves
-this implementation's actual usage.
-Revisit if a real, concrete need for either ever materializes.
-Mailboxes form a single flat namespace per user: there is no nested
-hierarchy, and
-.Li CREATE
-refuses a name containing the
-.Ql /
-hierarchy-delimiter character rather than creating intermediate
-levels.
-.Li CREATE
-and
-.Li DELETE
-of
-.Li INBOX
-itself are refused
-.Pq Li CANNOT ,
-as is
-.Li RENAME
-of
-.Li INBOX
-as the source
-.Pq permitted by RFC 9051 but explicitly sanctioned there as a
-server-side refusal ; renaming
-.Em to
-.Li INBOX
-is unreachable, since that name always already exists.
-.Li COPY
-and
-.Li MOVE
-may target any real, existing mailbox, not only the one currently
-selected; a destination naming a mailbox that does not exist is
-refused with
-.Li TRYCREATE ,
-and a syntactically invalid destination name is refused outright
-.Pq Li BAD
-with no attempt to look it up.
-.Li DELETE
-of a mailbox that does not exist, and
-.Li RENAME
-with a source that does not exist, are refused with
-.Pq Li NONEXISTENT ;
-.Li CREATE
-of a mailbox that already exists, and
-.Li RENAME
-to a destination that already exists, are refused with
-.Pq Li ALREADYEXISTS
-(RFC 5530).
-If a mailbox is renamed or deleted while a
-.Em different
-session
-.Pq same user
-has it selected, that other session's own notion of what it has
-selected can go stale until its next
-.Li SELECT ,
-.Li EXAMINE ,
-.Li CLOSE ,
-or
-.Li UNSELECT ;
-no unsolicited notification of the rename or deletion is sent to it.
-.Pp
-Plain, non-PEEK
-.Li BODY
-section forms, which implicitly set the
-.Li \eSeen
-flag, are not implemented for any content item.
-BODYSTRUCTURE and part-addressed
-.Li BODY.PEEK
-fetches do not address into a MULTIPART container's own combined
-content, or into MESSAGE/RFC822 or MESSAGE/GLOBAL nested part
-numbering.
-A mailbox selected via
-.Li EXAMINE
-refuses
-.Li STORE ,
-.Li EXPUNGE ,
-and
-.Li MOVE
-with a tagged
-.Li NO
-.Pq Li CANNOT
-response;
-.Li CLOSE
-on such a mailbox instead succeeds without removing anything, per
-RFC 9051.
-.Li APPEND
-is not restricted by the currently selected mailbox's read-only state.
-.Pp
-.Li IDLE
-pushes unsolicited
-.Li EXISTS
-and
-.Li EXPUNGE
-responses only; unsolicited
-.Li FETCH
-on flag change (e.g. another session's
-.Li STORE )
-is not sent.
-Pushes are triggered only by mutations made through
-.Nm
-itself
-.Pq Li APPEND , Li EXPUNGE , Li "UID EXPUNGE" , a read-write Li CLOSE , and Li MOVE ;
-mail delivered directly into a mailbox's maildir by an external MTA or
-LDA is not detected until some other command against that session
-happens to trigger a refresh.
-.Pp
-It has been built and run against a real OpenBSD 8.0 system,
-including real Apple Mail client traffic exercising most of the
-commands listed above as implemented.
+are deliberately left out.
blob - c74a9983b8e5823e65048a325460d25d23be1b18
blob + 990a13c9f6bb93fc272d9a2c6c0f30821042744d
--- src/imapd.conf.example
+++ src/imapd.conf.example
@@ -1,13 +1,13 @@
 #
-# imapd.conf example -- see imapd(8) for the full directive list.
+# imapd.conf example, see imapd(8) for the full directive list.
 #
 # This file is installed read-only at /usr/local/share/examples/imapd/
 # imapd.conf (matching the OpenBSD ports convention for sample configs,
-# see ports(7) and the @sample PLIST keyword) -- it is NOT read by imapd
+# see ports(7) and the @sample PLIST keyword), it is NOT read by imapd
 # itself. To use it, copy it to /etc/imapd.conf and edit as needed.
 #
 # imapd.conf must be owned by root (or the user running imapd) and must
-# NOT be group- or world-writable -- nor world-readable, which trips
+# NOT be group- or world-writable, nor world-readable, which trips
 # people up more often, since it's easy to forget: check_file_secrecy()
 # (parse.y) rejects a world-readable file too, not just a writable one.
 # A plain "cp" or "tee" leaves the copy at the default umask (typically
@@ -28,9 +28,9 @@ listen on 0.0.0.0 port 143
 listen on 0.0.0.0 tls port 993
 
 # The address may also be "::" (all IPv6 interfaces) or "*" (both IPv4 and
-# IPv6 -- binds two sockets per listener, one of each family, matching
+# IPv6, binds two sockets per listener, one of each family, matching
 # httpd.conf(5)'s own "*" convention), or a single literal IPv4/IPv6
-# address. Hostnames are not accepted -- see imapd(8) for why. For example,
+# address. Hostnames are not accepted, see imapd(8) for why. For example,
 # to listen on both address families:
 #listen on * port 143
 #listen on * tls port 993
@@ -40,7 +40,7 @@ listen on 0.0.0.0 tls port 993
 #spool "/var/mail/imapd"
 
 # Credentials file: one line per user, "username:passwordhash:uid:gid:
-# maildir" -- see imapduser(8) and imapd(8) FILES. Defaults to
+# maildir", see imapduser(8) and imapd(8) FILES. Defaults to
 # /etc/imapd/credentials.
 #credentials "/etc/imapd/credentials"
 
@@ -51,10 +51,10 @@ listen on 0.0.0.0 tls port 993
 #tls key "/etc/ssl/private/imapd.key"
 
 # Largest message imapd will read from disk while deriving BODYSTRUCTURE
-# or a MIME part-addressed BODY[<part>] fetch -- see imapd(8). Must be
+# or a MIME part-addressed BODY[<part>] fetch, see imapd(8). Must be
 # between 12000 and 1073741824 (1 GiB) bytes. Defaults to 41943040
 # (40 MiB, sized off Gmail's documented attachment limit plus base64
-# encoding overhead -- see the BODYSTRUCTURE_READ_DEFAULT comment in
+# encoding overhead, see the BODYSTRUCTURE_READ_DEFAULT comment in
 # imapd.h for the full rationale). Uncomment and adjust if your mail
 # routinely carries larger attachments than that.
 attachment max 41943040
blob - 88870413145be405657cc25906d29343f8a98aea
blob + 7ebe46f03770374d5b2bb7631a129c716bc566cb
--- src/imapd.h
+++ src/imapd.h
@@ -15,34 +15,7 @@
  */
 
 /*
- * Shared definitions for all four imapd(8) process roles: parent,
- * listener, auth, store -- the process split, imsg message catalog,
- * pledge strings, and the fork-per-session store mechanism are all
- * defined here.
- *
- * struct/enum names below still say "openimap" in places (struct
- * openimap_config, enum openimap_proc_type) even after the imapd(8)
- * rename -- those are internal identifiers with no user-visible effect
- * (nothing an admin or a client ever sees), touching hundreds of call
- * sites across every .c file for a purely cosmetic change, so they were
- * deliberately left alone.
- *
- * "OpenIMAP" (the project/brand name) was itself later renamed to
- * "OpenIMAPD" -- a correction, not a reversal, of the daemon-rename
- * reasoning above. The original assumption was that OpenSSH/OpenNTPD/
- * OpenSMTPD all "keep Open, daemon drops it," so OpenIMAP should stay
- * OpenIMAP the same way. Checked directly against OpenBSD's own
- * innovations page (openbsd.org/innovations.html) rather than assumed
- * further: OpenNTPD, OpenSMTPD, OpenBGPD, and OpenIKED all carry the
- * "D" in the *project* name itself (ships ntpd/smtpd/bgpd/iked) --
- * OpenSSH is the one exception, and only because it ships a whole
- * toolkit (ssh/scp/sftp/ssh-keygen/...), not a single daemon. This
- * project is shaped like the single-daemon case, so "OpenIMAP" was
- * the wrong analogy. Only
- * the installed daemon's own identity (PROG, man page, rc.d script,
- * default file paths), this header's own filename, and its include
- * guard/version macro (below) changed as part of that *earlier*
- * daemon rename -- unaffected by this later project-name correction.
+ * Shared definitions for all four imapd(8) process roles
  */
 
 #ifndef IMAPD_H
@@ -56,13 +29,6 @@
 #include <imsg.h>
 #include <stdint.h>
 
-/*
- * No formal release process yet (this project has never run "make install"
- * before task #196's rc.d/RELINK work) -- this exists mainly so "-V" (see
- * main.c) has something concrete to print, and so the RELINK smoke-test
- * command has stable, greppable output if that's ever wanted. Bump by hand
- * until something better (git describe, etc.) is worth wiring in.
- */
 #define IMAPD_VERSION	"0.1.1"
 
 /*
@@ -76,9 +42,7 @@ enum openimap_proc_type {
 };
 
 /*
- * imsg message catalog. IMSG_SETUP_PEER / IMSG_SETUP_DONE are the
- * boot-time handshake, also reused for the per-session store peer-wiring
- * handshake after IMSG_STORE_INIT.
+ * imsg message catalog.
  */
 enum imsg_type {
 	IMSG_NONE,
@@ -89,11 +53,7 @@ enum imsg_type {
 
 	/* parent -> listener, at boot */
 	IMSG_LISTENER_SOCKET_CLEARTEXT,	/* one bound, listening fd for the
-					 * cleartext/STARTTLS port -- sent
-					 * once per resolved address (1
-					 * normally, 2 for "listen on *",
-					 * dual-stack -- see struct imsg_
-					 * listener_init's n_cleartext_addrs) */
+					 * cleartext/STARTTLS port */
 	IMSG_LISTENER_SOCKET_TLS,	/* same, for the implicit-TLS port */
 	IMSG_TLS_CERT,
 	IMSG_TLS_KEY,
@@ -106,15 +66,10 @@ enum imsg_type {
 	IMSG_AUTH_REQUEST,
 	IMSG_AUTH_RESULT,
 
-	/* auth -> parent, per successful login (task #321: parent's
-	 * privilege decision is sourced directly from auth, keyed by
-	 * session_id, instead of being relayed through the network-facing
-	 * listener process) */
+	/* auth -> parent, per successful login */
 	IMSG_AUTH_CRED,
 
-	/* per-session store spawn: auth's IMSG_AUTH_CRED triggers the spawn
-	 * in parent; IMSG_STORE_FORK is now parent -> listener only, used
-	 * solely as a failure notification when the spawn couldn't proceed */
+	/* per-session store spawn */
 	IMSG_STORE_FORK,
 	IMSG_STORE_INIT,
 	IMSG_STORE_PEER,
@@ -127,38 +82,15 @@ enum imsg_type {
 	IMSG_MBOX_FETCH,
 	IMSG_MBOX_FETCH_META,
 	IMSG_MBOX_FETCH_HEADER,	/* raw BODY.PEEK[HEADER] bytes for one
-					 * message (store -> listener), sent
-					 * immediately before that message's own
-					 * IMSG_MBOX_FETCH_META -- see struct
-					 * imsg_mbox_fetch_header's comment for
-					 * why this ordering is a contract, not
-					 * a convention */
+					 * message (store -> listener) */
 	IMSG_MBOX_FETCH_BODY,	/* raw BODY.PEEK[] / BODY.PEEK[TEXT] bytes for
-					 * one message (store -> listener), same
-					 * "sent immediately before that message's
-					 * IMSG_MBOX_FETCH_META" contract as
-					 * IMSG_MBOX_FETCH_HEADER above -- see
-					 * struct imsg_mbox_fetch_body's comment */
+					 * one message (store -> listener) */
 	IMSG_MBOX_FETCH_ENVELOPE,	/* pre-formatted ENVELOPE parenthesized-
 					 * list text for one message (store ->
-					 * listener), same "sent immediately
-					 * before that message's IMSG_MBOX_
-					 * FETCH_META" contract as IMSG_MBOX_
-					 * FETCH_HEADER above, but -- unlike that
-					 * one -- carrying already-formatted
-					 * response text, not raw message bytes;
-					 * see struct imsg_mbox_fetch_envelope's
-					 * comment */
+					 * listener)*/
 	IMSG_MBOX_FETCH_BODYSTRUCTURE,	/* pre-formatted BODYSTRUCTURE
 					 * parenthesized-list text for one
-					 * message (store -> listener), same
-					 * "sent immediately before that
-					 * message's IMSG_MBOX_FETCH_META"
-					 * contract and same "already-formatted
-					 * response text, not raw bytes" shape
-					 * as IMSG_MBOX_FETCH_ENVELOPE above;
-					 * see struct imsg_mbox_fetch_
-					 * bodystructure's comment */
+					 * message (store -> listener) */
 	IMSG_MBOX_STORE,
 	IMSG_MBOX_APPEND,
 	IMSG_MBOX_APPENDED,
@@ -179,12 +111,7 @@ enum imsg_type {
 	IMSG_MBOX_UNSOLICITED,
 
 	/*
-	 * RFC 7162 (CONDSTORE/QRESYNC) additions -- see this header's
-	 * imsg_mbox_select/imsg_mbox_store comments below for the wire shape
-	 * each carries. Both are streamed store -> listener, the same
-	 * "zero or more of these, then one terminal IMSG_MBOX_SELECTED/
-	 * IMSG_MBOX_RESULT" pattern IMSG_MBOX_FETCH_META/IMSG_MBOX_EXPUNGED/
-	 * IMSG_MBOX_SEARCH_MATCH already establish.
+	 * RFC 7162 (CONDSTORE/QRESYNC) additions
 	 */
 	IMSG_MBOX_SELECT_VANISHED,	/* one vanished UID during a QRESYNC
 					 * SELECT resync (store -> listener,
@@ -194,13 +121,7 @@ enum imsg_type {
 					 * listener, before IMSG_MBOX_RESULT) */
 
 	/*
-	 * RFC 9051 SS6.3.13 (IDLE) additions. No payload on the request --
-	 * "refresh this session's view of its already-selected mailbox" is
-	 * fully determined by which store child the request arrives on, same
-	 * as IMSG_STORE_SHUTDOWN needing none. The reply is the same
-	 * "zero or more streamed items, then one terminal reply" shape as
-	 * IMSG_MBOX_FETCH_META/IMSG_MBOX_SELECT_VANISHED above -- see struct
-	 * imsg_mbox_idle_uid/imsg_mbox_idle_refreshed comments below.
+	 * RFC 9051 SS6.3.13 (IDLE) additions.
 	 */
 	IMSG_MBOX_IDLE_REFRESH,		/* listener -> store, no payload */
 	IMSG_MBOX_IDLE_UID,		/* one currently-existing UID, in
@@ -209,50 +130,14 @@ enum imsg_type {
 	IMSG_MBOX_IDLE_REFRESHED,	/* terminal reply (store -> listener) */
 
 	/*
-	 * RFC 9051 SS6.3.4-SS6.3.6 (CREATE/DELETE/RENAME) and SS6.3.9 (LIST),
-	 * this pass -- flat (non-nested) multi-mailbox support, mailboxes as
-	 * sibling subdirectories of the session's own per-user maildir root.
-	 * IMSG_MBOX_CREATE/IMSG_MBOX_DELETE/IMSG_MBOX_RENAME and
-	 * IMSG_MBOX_LIST itself were already reserved in this enum from an
-	 * earlier skeleton pass (store_dispatch()'s "TODO: none of these
-	 * payload shapes are designed yet" case) -- only their payload
-	 * structs and one new streaming-item type for LIST are added here.
-	 * CREATE/DELETE/RENAME all reply with the existing, already-generic
-	 * struct imsg_mbox_result (only its "ok" field is meaningful for
-	 * these three -- count/highestmodseq stay 0), the same reuse
-	 * CLOSE already gets by riding EXPUNGE's reply shape. LIST follows
-	 * the "stream zero or more items, then one terminal reply" pattern
-	 * IMSG_MBOX_IDLE_UID/IMSG_MBOX_IDLE_REFRESHED above (and IMSG_MBOX_
-	 * FETCH_META, IMSG_MBOX_SELECT_VANISHED, ...) already establish --
-	 * its terminal reply also reuses struct imsg_mbox_result, with
-	 * "count" now meaningful (number of IMSG_MBOX_LIST_ITEM messages
-	 * that preceded it), matching that field's existing doc comment
-	 * ("equivalent, for a future op").
+	 * RFC 9051 SS6.3.4-SS6.3.6 (CREATE/DELETE/RENAME) and SS6.3.9 (LIST).
 	 */
 	IMSG_MBOX_LIST_ITEM		/* one mailbox name (store -> listener),
-					 * before the terminal IMSG_MBOX_RESULT
-					 * -- INBOX itself is never included:
-					 * listener.c already special-cases
-					 * INBOX into every LIST response
-					 * locally (RFC 9051 SS6.3.9: "The
-					 * special name INBOX is included in
-					 * the output from LIST... if INBOX is
-					 * supported by this server for this
-					 * user", true unconditionally in v1),
-					 * so store.c only needs to report the
-					 * *named* mailboxes it actually finds
-					 * on disk */
+					 * before the terminal IMSG_MBOX_RESULT */
 };
 
 /*
- * Standard privsep imsg-over-event(3) wrapper. This struct's fields and
- * imsgev.c's functions are this project's own implementation, not copied
- * from a specific quoted definition -- but the name "imsgev"/"struct
- * imsgev" for an imsgbuf+event(3) wrapper matches Eric Faurot's
- * usr.sbin/ldapd/imsgev.c in OpenBSD base closely enough (not a generic
- * name) that his copyright is carried in imsgev.c's own header as
- * attribution for that naming/conceptual lineage, even though the actual
- * function signatures and dispatch design here differ from his.
+ * privsep imsg-over-event(3) wrapper.
  */
 struct imsgev {
 	struct imsgbuf	 ibuf;
@@ -262,88 +147,33 @@ struct imsgev {
 	short		 events;
 };
 
-/*
- * Config, as read from imapd.conf by parent via config_load() (parse.y
- * -- a real yacc-based grammar as of this pass, covering exactly these
- * eight fields: "listen on <addr> [tls] port <port>" x2, "spool <path>",
- * "credentials <path>", "tls certificate <path>", "tls key <path>",
- * "attachment max <bytes>"). See parse.y's header comment for the
- * grammar's full design and sourcing.
- */
-/*
- * Max number of sockets bind_listen_socket() (parent.c) ever binds for a
- * single "listen on <addr>" line: 1 for a literal IPv4 or IPv6 address, or
- * 2 for the "*" wildcard, which binds one IPv4-any and one IPv6-any socket
- * -- OpenBSD's IPv6 sockets are always IPv6-only (ip6(4): "With OpenBSD
- * IPv6 sockets are always IPv6-only, so the socket option is read-only"),
- * so unlike Linux there is no single dual-mapped socket to bind instead;
- * this matches how smtpd's own host_v4()/host_v6()/host_dns() (src/
- * usr.sbin/smtpd/parse.y, read directly this pass) build one struct
- * listener per resolved address/family rather than one shared socket.
- * "*" itself matches httpd.conf(5)'s own documented address semantics
- * (man.openbsd.org/httpd.conf.5): "'*' ... listen on all IPv4 and IPv6
- * addresses ... '0.0.0.0' means to listen on all IPv4 addresses and '::'
- * all IPv6 addresses."
- */
 #define LISTENER_MAX_ADDRS	2
 
 struct openimap_config {
 	char	 listen_addr[64];	/* "0.0.0.0" (default), "::", a literal
 					 * IPv4/IPv6 address, or "*" for both
-					 * -- see LISTENER_MAX_ADDRS above */
+					 *, see LISTENER_MAX_ADDRS above */
 	uint16_t port_cleartext;	/* 143, STARTTLS */
 	uint16_t port_implicit_tls;	/* 993, RFC 8314 */
 	char	 spool_root[1024];	/* mail spool root, store's chroot */
-	char	 cred_file[1024];	/* auth's credential file -- one
+	char	 cred_file[1024];	/* auth's credential file, one
 					 * line per user, format
 					 * username:passwordhash:uid:gid:
 					 * maildir */
 	char	 tls_cert_file[1024];
 	char	 tls_key_file[1024];
-	uint32_t bodystructure_read_max; /* "attachment max" directive --
-					 * see BODYSTRUCTURE_READ_DEFAULT
-					 * below for the default value and
-					 * full rationale; store children get
-					 * their own copy of this via
-					 * struct imsg_store_init, since they
-					 * never read imapd.conf themselves. */
+	uint32_t bodystructure_read_max; /* "attachment max" directive */
 };
 
 /*
- * imsg payload wire structs. The design doc's imsg catalog describes
- * these only in prose ("mechanism, decoded username, decoded password" /
- * "ok/fail, mailbox identifier on success", etc.) -- these fixed-size
- * structs are this implementation's concrete choice, not something the
- * design doc itself specifies. Fixed-size, no length-prefixed strings,
- * for v1 simplicity; revisit if that turns out to be too small anywhere.
+ * imsg payload wire structs.
  */
 #define AUTH_USERNAME_MAX	64
 #define AUTH_PASSWORD_MAX	128
 #define AUTH_MAILDIR_MAX	256
 
 /*
- * Boot-time config-delivery payloads, closing the gap flagged in an
- * earlier pass: listener/auth don't read imapd.conf themselves (kept
- * off their rpath/unveil surface deliberately, per each role's own
- * pledge(2) string below), but nothing ever specified how they'd get
- * their slice of it otherwise. Modeled directly on
- * IMSG_STORE_INIT's existing precedent: parent, which alone reads the
- * real config, hands over only the fields that role actually needs, not
- * the whole struct openimap_config.
- *
- * auth's is not optional polish -- auth_main() derives its chroot
- * directory from cred_file, so without this message it would chroot
- * into the dirname of an empty string. listener's used to be only a
- * startup log line, on the theory that the listening fds themselves are
- * always fd-passed directly, never rebuilt from listen_addr/ports -- that
- * stopped being true the moment dual-stack ("listen on *") support was
- * added: listener now needs n_cleartext_addrs/n_tls_addrs from this
- * message to know how many IMSG_LISTENER_SOCKET_CLEARTEXT/_TLS messages
- * to expect (1 each normally, 2 each for "*") before its boot-time drain
- * loop can know it has received all of them. This message must therefore
- * arrive before listener can finish that loop, though not necessarily
- * before the socket fds themselves -- see listener.c's listener_main()
- * for how the loop tolerates any arrival order.
+ * Boot-time config-delivery payloads.
  */
 struct imsg_listener_init {
 	char		listen_addr[64];
@@ -373,18 +203,6 @@ struct imsg_auth_result {
 	char		maildir[AUTH_MAILDIR_MAX];
 };
 
-/*
- * task #321: sent auth -> parent directly, over auth's own existing
- * channel (the one IMSG_AUTH_INIT already arrives on), immediately after
- * a successful login. This is the sole source parent uses to spawn a
- * session's store child -- see parent_handle_store_fork() in parent.c.
- * Previously this data (uid/gid/maildir, resolved by auth.c from the
- * credential file) was relayed to parent by listener.c instead, meaning
- * parent's privilege decision ultimately trusted a value repeated by the
- * network-facing process rather than the process that actually verified
- * the login. See docs/SECURITY-PATCHES.md's F1 writeup for the original
- * finding and the recommended complete fix this implements.
- */
 struct imsg_auth_cred {
 	uint32_t	session_id;
 	uid_t		uid;
@@ -392,12 +210,6 @@ struct imsg_auth_cred {
 	char		maildir[AUTH_MAILDIR_MAX];
 };
 
-/*
- * task #321: parent -> listener only now, a failure notification when
- * parent couldn't spawn (or lost) a session's store child. No longer
- * carries uid/gid/maildir -- those arrive on IMSG_AUTH_CRED above -- so
- * a failure reply is just session_id, always zeroed for the rest.
- */
 #define STORE_MAILDIR_MAX	AUTH_MAILDIR_MAX
 
 struct imsg_store_fork {
@@ -408,87 +220,29 @@ struct imsg_store_init {
 	uint32_t	session_id;
 	uid_t		uid;
 	gid_t		gid;
-	char		spool_root[1024];	/* store needs this to chroot()
-						 * -- store children don't read
-						 * imapd.conf themselves (see
-						 * main.c's NOTE on why), so
-						 * parent has to hand it over
-						 * explicitly here rather than
-						 * store already having it. */
+	char		spool_root[1024];	/* store needs this to chroot() */
 	char		maildir[STORE_MAILDIR_MAX]; /* THIS session's own
 						 * mailbox subdirectory, relative
-						 * to spool_root above -- distinct
-						 * from spool_root itself, which
-						 * is shared by every store child
-						 * regardless of user. store.c
-						 * scopes its unveil(2) to this
-						 * path specifically (not the
-						 * whole chroot), and every
-						 * mailbox file it opens is
-						 * relative to it. */
+						 * to spool_root above */
 	uint32_t	bodystructure_read_max; /* copied from struct
 						 * openimap_config's field of the
-						 * same name -- see
-						 * BODYSTRUCTURE_READ_DEFAULT's
-						 * comment for what this gates.
-						 * Same "parent read the config,
-						 * child gets only what it needs"
-						 * pattern as spool_root/maildir
-						 * above. */
+						 * same name */
 };
 
 /*
  * IMSG_MBOX_SELECT (listener -> store) / IMSG_MBOX_SELECTED (store ->
- * listener): the first IMSG_MBOX_* pair to actually get a wire payload
- * shape -- the rest of the family (FETCH/STORE/APPEND/...) is still exactly
- * as undesigned as store.c's own header comment says. v1 is single-mailbox
- * (INBOX only, and the still-unresolved hierarchy-separator question is
- * flagged in listener.c's cmd_namespace()) -- readonly distinguishes
- * EXAMINE (RFC 9051 SS6.3.3)
- * from SELECT, wired up in listener.c's select_or_examine(). store.c still
- * does nothing different for readonly than for a normal SELECT: v1's single
- * mailbox returns the identical EXISTS/UIDVALIDITY/UIDNEXT/highestmodseq
- * either way, so read-only enforcement (refusing STORE/EXPUNGE/MOVE,
- * short-circuiting CLOSE) lives entirely in listener.c via s->mbox_readonly
- * -- a pure session-local invariant that doesn't need store.c's
- * involvement to check.
+ * listener).
  */
 #define MBOX_NAME_MAX	256
 
 /*
  * Shared specific-error type for the store-process operation-result imsg
  * structs (imsg_mbox_selected, imsg_mbox_status_result, imsg_mbox_result,
- * imsg_mbox_appended) -- replaces the old "int ok" plus a bolted-on "int
- * no_such_mailbox" that some of these structs used to maintain
- * independently for the exact same concept. MBOX_ERR_UNSET is deliberately
- * the zero value, not MBOX_OP_OK: every producer memset()s its result
- * struct to 0 before filling it in, so a codepath that forgets to set this
- * field ends up reporting failure, not silent success.
+ * imsg_mbox_appended).
  *
  * MBOX_OP_ERR_NO_SUCH_MAILBOX and MBOX_OP_ERR_ALREADY_EXISTS are
  * client-visible via RFC 5530 SS3's NONEXISTENT and ALREADYEXISTS codes
- * respectively (confirmed directly against the RFC text, not assumed):
- * NONEXISTENT's own worked example is a RENAME failing because the
- * source doesn't exist, and ALREADYEXISTS's is a CREATE/RENAME target
- * that already exists -- both apply directly to CREATE/DELETE/RENAME's
- * not-found and already-exists failure modes (added 2026-08-27, task
- * #317), correcting an earlier, unverified claim in this comment that no
- * RFC 5530 code fit those cases. NO_SUCH_MAILBOX is also COPY/MOVE/
- * APPEND's TRYCREATE trigger (RFC 9051 SS6.4.7/SS6.4.8/SS6.3.12). Every
- * other failure cause (mkdir/stat/flock/index races, truncated internal
- * buffers, etc.) still collapses to a plain "<CMD> failed" NO via
- * MBOX_OP_ERR_GENERIC -- those genuinely don't fit any RFC 5530 code.
- *
- * Defined here, ahead of imsg_mbox_select(ed)/imsg_mbox_status_result
- * below, rather than down by imsg_mbox_result where it was first added --
- * a straight compile (caught by the 2026-08-27 -Wcast-qual/-Wcast-align
- * addition finally forcing a real, non-sandboxed syntax check of this
- * header) showed the enum being referenced by struct fields hundreds of
- * lines before its old definition, which every C compiler rejects as an
- * incomplete type. This project's enum-error-type pass (task #307-309)
- * had never actually been compile-checked end to end before now --
- * task #310 ("compile-check + real-hardware verify") was still open
- * when this was found.
+ * respectively.
  */
 enum mbox_op_error {
 	MBOX_ERR_UNSET = 0,
@@ -498,111 +252,51 @@ enum mbox_op_error {
 	MBOX_OP_ERR_ALREADY_EXISTS,
 };
 
-/*
- * QRESYNC select-param additions (RFC 7162 SS3.2.5): `"QRESYNC" SP "("
- * uidvalidity SP mod-sequence-value [SP known-uids [SP seq-match-data]]
- * ")"`. v1 scope, sourced against this project's own established pattern
- * of accepting exactly one sequence-set range (never a comma-separated
- * list) everywhere a sequence-set appears (FETCH/STORE/SEARCH's UID
- * ranges) -- known-uids gets the same restriction here. seq-match-data is
- * parsed and syntax-validated by listener.c but never sent down this wire
- * at all: this implementation's chosen QRESYNC state model (see the
- * imsg_mbox_select_vanished comment below) never uses it to narrow
- * anything, so there's nothing for store.c to do with it -- explicitly
- * sanctioned by RFC 7162 SS5.2 ("A client providing message sequence
- * match data can reduce the scope as above. In the case where there have
- * been no expunges, the server can ignore this data").
- */
+/* QRESYNC select-param (RFC 7162 SS3.2.5). */
 struct imsg_mbox_select {
 	char		mailbox[MBOX_NAME_MAX];
 	int		readonly;	/* 1 = EXAMINE, 0 = SELECT */
 
-	int		qresync;	/* 1 if a QRESYNC select-param was
-					 * given and passed listener.c's
-					 * "ENABLE QRESYNC already issued"
-					 * gate (RFC 7162 SS3.2.5) */
+	int		qresync;	/* 1 if QRESYNC was requested and
+					 * enabled (RFC 7162 SS3.2.5) */
 	uint32_t	qresync_uidvalidity; /* client's last-known
-					 * UIDVALIDITY -- store.c ignores the
-					 * rest of the qresync_* fields below
-					 * if this doesn't match the mailbox's
-					 * actual current UIDVALIDITY (SS3.2.5:
-					 * "the server MUST ignore the
-					 * remaining parameters and behave as
-					 * if no dynamic message data
-					 * changed") */
+					 * UIDVALIDITY; a mismatch means the
+					 * rest of qresync_* is ignored */
 	uint64_t	qresync_modseq;	/* client's last-known mailbox
 					 * mod-sequence */
-	int		qresync_has_uids; /* 0 => client omitted known-uids;
-					 * SS3.2.5.1: "the server acts as if
-					 * the client has specified
-					 * '1:<maxuid>'" -- store.c resolves
-					 * that default itself, since it's the
-					 * one that knows UIDNEXT */
-	uint32_t	qresync_uid_lo;	/* known-uids range, v1's usual
-					 * single-range restriction (no comma
-					 * lists) -- ignored if
+	int		qresync_has_uids; /* 0 = client omitted known-uids;
+					 * store.c then defaults to the full
+					 * UID range (SS3.2.5.1) */
+	uint32_t	qresync_uid_lo;	/* known-uids range, single range only
+					 * (no comma lists); ignored if
 					 * !qresync_has_uids */
 	uint32_t	qresync_uid_hi;
 };
 
 struct imsg_mbox_selected {
-	enum mbox_op_error error;	/* MBOX_OP_ERR_GENERIC covers both "no
-				 * such mailbox" and an index I/O failure --
-				 * session_handle_mbox_selected() replies
-				 * "[NONEXISTENT] no such mailbox" (RFC 5530)
-				 * unconditionally on any failure here, so
-				 * there's no NO_SUCH_MAILBOX/other distinction
-				 * for this struct to carry, unlike COPY/MOVE/
-				 * APPEND's TRYCREATE case */
+	enum mbox_op_error error;	/* MBOX_OP_ERR_GENERIC covers "no
+					 * such mailbox" and I/O failure alike
+					 *, always reported as
+					 * [NONEXISTENT] */
 	uint32_t	exists;		/* RFC 9051 SS7.4.1 EXISTS */
 	uint32_t	uidvalidity;	/* RFC 9051 SS2.3.1.1 */
 	uint32_t	uidnext;	/* RFC 9051 SS2.3.1.1 */
 
-	/*
-	 * RFC 7162 SS3.1.2.1: highest mod-sequence of all messages in the
-	 * mailbox. Always populated (v1's index format now tracks a
-	 * per-mailbox mod-sequence counter unconditionally -- see store.c's
-	 * struct mbox_index comment), whether or not this particular
-	 * session has issued a CONDSTORE-enabling command yet -- listener.c
-	 * is the one that decides whether to actually surface it to the
-	 * client via the HIGHESTMODSEQ OK response code, and also caches it
-	 * in s->mbox_highestmodseq for the "CONDSTORE enabled later, mailbox
-	 * already selected" unsolicited-HIGHESTMODSEQ case (RFC 7162 SS3.1:
-	 * "A first CONDSTORE enabling command executed in the session with a
-	 * mailbox selected MUST cause the server to return HIGHESTMODSEQ").
-	 * Since v1's only mailbox always supports persistent mod-sequence
-	 * storage, the NOMODSEQ response code (SS3.1.2.2) is simply
-	 * unreachable in this implementation -- not emitted anywhere.
-	 */
+	/* RFC 7162 SS3.1.2.1: mailbox's highest mod-sequence, always
+	 * populated; listener.c decides whether to surface it via the
+	 * HIGHESTMODSEQ response code. */
 	uint64_t	highestmodseq;
 
-	/*
-	 * Before this struct, store.c streams (in this order, per RFC 7162
-	 * SS3.2.6's "VANISHED (EARLIER) responses MUST be returned before
-	 * any FETCH responses" ordering rule, which this implementation
-	 * also applies to the QRESYNC-SELECT resync case by the same
-	 * reasoning): zero or more IMSG_MBOX_SELECT_VANISHED (uid_lo/
-	 * uid_hi range), then zero or more IMSG_MBOX_FETCH_META (with
-	 * .modseq set, for messages
-	 * in the requested known-uids range whose current mod-sequence is
-	 * greater than qresync_modseq) -- only when req->qresync was set
-	 * and the UIDVALIDITY check passed. See imsg_mbox_select_vanished's
-	 * comment for why the vanished set ignores qresync_modseq entirely
-	 * (this implementation's chosen minimal QRESYNC state model).
-	 */
+	/* store.c streams, before this struct: zero or more
+	 * IMSG_MBOX_SELECT_VANISHED, then zero or more IMSG_MBOX_FETCH_META
+	 * (modseq set), per RFC 7162 SS3.2.6's VANISHED-before-FETCH
+	 * ordering, only when qresync was requested and UIDVALIDITY
+	 * matched. */
 };
 
-/*
- * RFC 9051 SS6.3.11 status-att-val values, plus RFC 7162 SS3.1.7's
- * HIGHESTMODSEQ addition (`status-att =/ "HIGHESTMODSEQ"`). Request-parsing
- * order only -- listener.c's response formatting uses its own fixed
- * canonical order (MESSAGES, UIDNEXT, UIDVALIDITY, UNSEEN, DELETED, SIZE,
- * HIGHESTMODSEQ), directly sourced from the RFC's own worked example
- * (SS6.3.11: "C: A042 STATUS blurdybloop (UIDNEXT MESSAGES)" answered
- * "S: * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)" -- the server
- * reordered the client's own request order), not from this bitmask's bit
- * order.
- */
+/* RFC 9051 SS6.3.11 status-att-val bits, plus RFC 7162 SS3.1.7's
+ * HIGHESTMODSEQ. Request-parsing order only, listener.c's response uses
+ * its own fixed order, not this bitmask's. */
 #define STATUS_ATT_MESSAGES		(1U << 0)
 #define STATUS_ATT_UIDNEXT		(1U << 1)
 #define STATUS_ATT_UIDVALIDITY		(1U << 2)
@@ -610,89 +304,42 @@ struct imsg_mbox_selected {
 #define STATUS_ATT_DELETED		(1U << 4)
 #define STATUS_ATT_SIZE			(1U << 5)
 #define STATUS_ATT_HIGHESTMODSEQ	(1U << 6)
-#define STATUS_ATT_RECENT		(1U << 7)	/* IMAP4rev2 (RFC 9051
-							 * SS2.3.2) formally
-							 * dropped \Recent and
-							 * RECENT from STATUS's
-							 * status-att grammar,
-							 * but real clients
-							 * (Canary Mail seen
-							 * requesting it
-							 * directly against
-							 * this server) still
-							 * ask for it out of
-							 * IMAP4rev1 habit --
-							 * always answered "0"
-							 * (no \Recent tracking
-							 * exists in this
-							 * server) rather than
-							 * failing the whole
-							 * STATUS command with
-							 * BAD over one legacy
-							 * attribute name. */
+#define STATUS_ATT_RECENT		(1U << 7)	/* IMAP4rev2 dropped
+							 * \Recent/RECENT, but
+							 * some clients still
+							 * ask, always
+							 * answered "0" rather
+							 * than BAD */
 
 /*
  * IMSG_MBOX_STATUS (listener -> store) / IMSG_MBOX_STATUS_RESULT (store ->
- * listener): RFC 9051 SS6.3.11 STATUS command. Single-request/single-
- * combined-reply pair, same shape as IMSG_MBOX_SELECT/IMSG_MBOX_SELECTED --
- * STATUS's response is one aggregated line, not per-message data, so
- * (unlike FETCH/STORE/SEARCH/EXPUNGE) there's no streamed-then-terminal
- * shape here.
+ * listener): RFC 9051 SS6.3.11 STATUS, one combined reply, no per-message
+ * streaming.
  *
- * mailbox field added for RFC 9051 SS6.3.4-SS6.3.6/SS6.3.9 (flat multi-
- * mailbox support): this comment previously argued a mailbox-name field
- * was unnecessary, "v1 has
- * exactly one mailbox (INBOX) and no CREATE" -- no longer true. SS6.3.11
- * itself requires STATUS to target *any* named mailbox independent of
- * whatever this session currently has selected ("asks the server to
- * return... status of a mailbox... without... opening a mailbox"), so
- * store.c's handle_mbox_status() can't just answer for whatever cwd
- * happens to be (that would incorrectly reflect the *selected* mailbox,
- * conflating two independent concepts). It resolves this field the same
- * way handle_mbox_append() resolves its own independent destination:
- * temporarily visiting the target via select_mailbox_dir() and restoring
- * whatever was selected before, rather than leaving the session's actual
- * selection state changed by a STATUS call.
+ * mailbox targets any named mailbox independent of what's selected
+ * (SS6.3.11); store.c visits it via select_mailbox_dir() and restores the
+ * prior selection afterward.
  */
 struct imsg_mbox_status {
 	char		mailbox[MBOX_NAME_MAX];
-	uint32_t	attrs;	/* STATUS_ATT_* bitmask -- MESSAGES/UIDNEXT/
-				 * UIDVALIDITY/HIGHESTMODSEQ are always computed
-				 * by store.c regardless of this mask (all four
-				 * are free reads from the index header, same
-				 * "always compute, listener decides whether to
-				 * print" precedent as imsg_mbox_selected's own
-				 * fields); UNSEEN/DELETED/SIZE are the
-				 * deliberate exception -- store.c only runs the
-				 * per-message locate_message_file() scan their
-				 * computation requires when at least one of
-				 * the three is set here, per RFC 9051
-				 * SS6.3.11's own warning: "the STATUS command
-				 * SIZE...can take a significant amount of
-				 * time...clients should use STATUS SIZE
-				 * cautiously". Once that scan does run, all
-				 * three are computed together regardless of
-				 * which subset was actually requested --
-				 * locate_message_file() already returns both
-				 * the flag suffix and the size in one call, so
-				 * there's no marginal cost to computing all
-				 * three vs. one. */
+	uint32_t	attrs;	/* STATUS_ATT_* bitmask. MESSAGES/UIDNEXT/
+				 * UIDVALIDITY/HIGHESTMODSEQ are always
+				 * computed (free reads); UNSEEN/DELETED/SIZE
+				 * only trigger a per-message scan if
+				 * requested (RFC 9051 SS6.3.11 warns SIZE can
+				 * be slow) */
 };
 
 struct imsg_mbox_status_result {
-	enum mbox_op_error error;	/* MBOX_OP_ERR_GENERIC -- bad mailbox
-				 * name or the open/flock/index_load sequence
-				 * itself failed, same causes handle_mbox_
-				 * select() can hit; session_handle_mbox_
-				 * status_result() replies "[NONEXISTENT] no
-				 * such mailbox" unconditionally on any
-				 * failure, so no further distinction needed */
+	enum mbox_op_error error;	/* MBOX_OP_ERR_GENERIC only --
+					 * always reported as
+					 * [NONEXISTENT] */
 	uint32_t	messages;	/* STATUS_ATT_MESSAGES */
 	uint32_t	uidnext;	/* STATUS_ATT_UIDNEXT */
 	uint32_t	uidvalidity;	/* STATUS_ATT_UIDVALIDITY */
 	uint64_t	highestmodseq;	/* STATUS_ATT_HIGHESTMODSEQ, RFC 7162
 					 * SS3.1.7 */
-	uint32_t	unseen;		/* STATUS_ATT_UNSEEN -- 0 if not
+	uint32_t	unseen;		/* STATUS_ATT_UNSEEN, 0 if not
 					 * requested, see imsg_mbox_status.attrs
 					 * comment */
 	uint32_t	deleted;	/* STATUS_ATT_DELETED, same as above */
@@ -701,42 +348,16 @@ struct imsg_mbox_status_result {
 
 /*
  * IMSG_MBOX_SELECT_VANISHED (store -> listener, zero or more, before the
- * terminal IMSG_MBOX_SELECTED or IMSG_MBOX_RESULT): one *range*
- * [uid_lo, uid_hi] (inclusive) of UIDs no longer present in the mailbox,
- * drawn from the range being resolved. Despite the name (kept from when
- * this only existed for QRESYNC SELECT resync), also reused as of this
- * pass for RFC 7162 SS3.2.6's VANISHED UID FETCH modifier -- same "report
- * gaps in a UID range" computation, same wire shape, just a different
- * range source (a UID FETCH's own seq_lo/seq_hi instead of QRESYNC's
- * known-uids) and a different terminal message (IMSG_MBOX_RESULT, since
- * a UID FETCH isn't a SELECT). listener.c tells the two apart by s->state
- * (SESSION_SELECTING vs SESSION_FETCHING) when it arrives.
+ * terminal IMSG_MBOX_SELECTED or IMSG_MBOX_RESULT): one range [uid_lo,
+ * uid_hi] of UIDs no longer present. Used both for QRESYNC SELECT resync
+ * and RFC 7162 SS3.2.6's VANISHED UID FETCH modifier; listener.c tells
+ * them apart by s->state.
  *
- * A range, not a single UID: store.c computes these by walking its
- * *present*-message list once (bounded by mailbox size, this file's usual
- * "personal use, modest mailbox size" scale) and reporting the gaps
- * between consecutive present UIDs -- never by iterating the requested
- * UID range one number at a time, which a client could set arbitrarily
- * large (e.g. "known-uids 1:4000000000" against a five-message mailbox)
- * independent of real mailbox size. Since UIDs are assigned strictly
- * sequentially and never reused (RFC 9051 SS2.3.1.1), every UID gap in
- * the present-message list genuinely was assigned-then-expunged at some
- * point, so this is exactly the vanished set, computed in O(mailbox
- * size) rather than O(requested range size).
- *
- * Deliberately reports every such range regardless of qresync_modseq --
- * this implementation adopts RFC 7162 SS5.1's explicitly-sanctioned
- * minimal-state QRESYNC model ("a server implementation that doesn't
- * remember mod-sequences associated with expunged messages can be
- * considered compliant... Such implementations return all expunged
- * messages specified in the UID set... every time, without paying
- * attention to the specified CHANGEDSINCE mod-sequence"), rather than
- * persisting a queue of <UID set, mod-sequence> expunge history (SS5.3's
- * "Additional State Required" option) -- v1's index format has no place
- * to put that history, and the RFC treats the simpler behavior as fully
- * compliant, just less bandwidth-optimal than a server that remembers
- * more. listener.c formats these ranges directly into the VANISHED
- * (EARLIER) response's known-uids list.
+ * Computed in O(mailbox size) by walking the present-message list once
+ * and reporting gaps, not by iterating the (client-controlled) requested
+ * range. Ignores qresync_modseq entirely. This implementation uses RFC
+ * 7162 SS5.1's minimal-state model rather than persisting expunge
+ * history (SS5.3).
  */
 struct imsg_mbox_select_vanished {
 	uint32_t	uid_lo;
@@ -745,468 +366,156 @@ struct imsg_mbox_select_vanished {
 
 /*
  * IMSG_MBOX_FETCH (listener -> store) / IMSG_MBOX_FETCH_META (store ->
- * listener, one per matching message, sent in ascending sequence-number
- * order) / IMSG_MBOX_RESULT (store -> listener, exactly once, after the
- * last IMSG_MBOX_FETCH_META -- "no more responses coming for this FETCH,
- * safe to send the tagged OK/NO").
- *
- * v1 FETCH scope: message METADATA (FLAGS, UID, INTERNALDATE, RFC822.SIZE),
- * plus, across five successive real-client-testing passes, six content
- * items -- BODY.PEEK[HEADER] (see MBOX_FETCH_BODY_HEADER / IMSG_
- * MBOX_FETCH_HEADER below); BODY.PEEK[] and BODY.PEEK[TEXT] (see MBOX_
- * FETCH_BODY_WHOLE/MBOX_FETCH_BODY_TEXT / IMSG_MBOX_FETCH_BODY below);
- * BODY.PEEK[HEADER.FIELDS (...)]/BODY.PEEK[HEADER.FIELDS.NOT (...)] (see
- * MBOX_FETCH_HEADER_FIELDS below, which reuses IMSG_MBOX_FETCH_HEADER
- * wholesale rather than adding a fifth imsg type) -- all four raw-byte
- * extraction/filtering with no MIME awareness; ENVELOPE (see MBOX_
- * FETCH_ENVELOPE / IMSG_MBOX_FETCH_ENVELOPE below), the first content item
- * that's a *parsed*, structured response rather than raw or filtered
- * message bytes; and BODYSTRUCTURE/bare BODY (see MBOX_FETCH_BODYSTRUCTURE
- * / IMSG_MBOX_FETCH_BODYSTRUCTURE below), the first content item that
- * requires real MIME parsing -- deliberately scoped out of the same pass
- * that added ENVELOPE (user choice, via AskUserQuestion, to land ENVELOPE
- * first and take up BODYSTRUCTURE as its own, later pass), then
- * implemented as full recursive multipart parsing bounded by depth/part-
- * count caps (a second AskUserQuestion, favoring correctness for the very
- * common nested multipart/mixed(multipart/alternative(...), attachment)
- * shape over a simpler single-level or single-part-only implementation),
- * with RFC 9051's optional extension data (body MD5/disposition/language/
- * location) omitted entirely -- see MBOX_FETCH_BODYSTRUCTURE's own comment
- * for the full scoping story. A seventh content item, BODY[<section-part>]/
- * BODY.PEEK[<section-part>] (see MBOX_FETCH_BODY_PART below), followed as
- * real-hardware testing of BODYSTRUCTURE surfaced the natural next gap: a
- * client that knows (via BODYSTRUCTURE) a message has an attachment still
- * had no way to actually retrieve that attachment's bytes -- BODYSTRUCTURE
- * only ever describes the part tree, never returns part content. Bundled
- * into the same pass: <<partial>> byte-range support (SS6.4.5's
- * "<start.count>" suffix), applying uniformly to whole/TEXT/section-part
- * BODY[...] fetches -- added after real Apple Mail traffic was observed
- * issuing "BODY.PEEK[TEXT]<0.16384>", which this server silently failed to
- * handle at all (no "<...>" parsing existed anywhere in listener.c before
- * this pass). Everything else content-related -- BODY[]/BODY[TEXT]/
- * BODY[HEADER.FIELDS...]/BODY[<section-part>] without .PEEK (the \Seen-
- * setting side effect, still not implemented for any content item, this
- * one included -- same scoping as every .PEEK-only item above) and part
- * addressing into a MULTIPART container or a MESSAGE/RFC822|GLOBAL part's
- * own nested numbering (matching BODYSTRUCTURE's own established message/
- * rfc822 scope cut) -- remains deliberately out of this pass. See
- * listener.c's cmd_fetch() comment for the full scoping reasoning.
- * Also v1-scoped: exactly one
- * sequence-set range per request (a single number, "a:b", or "*" at either
- * end) -- listener.c rejects a comma-separated sequence-set before ever
- * sending this message, rather than silently fetching only the first
- * sub-range.
+ * listener, one per matching message, ascending sequence order) /
+ * IMSG_MBOX_RESULT (store -> listener, once, after the last META).
  */
-#define MBOX_FLAGS_MAX	256	/* generous -- the five standard flags plus a
-				 * handful of keywords comfortably fits;
-				 * truncated (not rejected) if a message
-				 * somehow has more, same truncate-rather-
-				 * than-overflow style as listener.c's
-				 * session_reply() */
+#define MBOX_FLAGS_MAX	256	/* generous; truncated (not rejected) if
+				 * exceeded */
 
 #define MBOX_FETCH_FLAGS		(1U << 0)
 #define MBOX_FETCH_UID			(1U << 1)
 #define MBOX_FETCH_INTERNALDATE	(1U << 2)
 #define MBOX_FETCH_RFC822_SIZE		(1U << 3)
-#define MBOX_FETCH_MODSEQ		(1U << 4) /* RFC 7162 SS3.1.4.2 MODSEQ
-					 * fetch-att -- set whenever the client
-					 * named MODSEQ explicitly, used
-					 * CHANGEDSINCE (which "implicitly adds
-					 * the MODSEQ FETCH message data item",
-					 * SS3.1.4.1), or (this implementation's
-					 * simplifying choice, see cmd_fetch()'s
-					 * comment) the session already has
-					 * CONDSTORE enabled at all */
-#define MBOX_FETCH_BODY_HEADER		(1U << 5) /* BODY.PEEK[HEADER] only --
-					 * RFC 9051 SS6.4.5: "the [RFC5322]
-					 * header of the message" for the
-					 * HEADER section-msgtext specifier, i.e.
-					 * the raw, unparsed header block, not a
-					 * structured ENVELOPE. Deliberately not
-					 * set for plain BODY[HEADER] (without
-					 * .PEEK) -- that variant "implicitly
-					 * sets the \Seen flag" per the same
-					 * section, and this pass doesn't
-					 * implement that side effect (would need
-					 * the same flag-rename + modseq-bump
-					 * machinery STORE already has, plus
-					 * reflecting the change back in this
-					 * FETCH's own response -- scoped
-					 * out). listener.c's
-					 * parse_fetch_atts() only recognizes the
-					 * exact token "BODY.PEEK[HEADER]"; plain
-					 * BODY[HEADER] still degrades like every
-					 * other unsupported BODY[...] variant. */
-#define MBOX_FETCH_BODY_WHOLE		(1U << 6) /* BODY.PEEK[] only -- RFC 9051
-					 * SS6.4.5: "If BODY[] is specified
-					 * (the section specification is
-					 * omitted), the FETCH is requesting the
-					 * [RFC5322] expression of the entire
-					 * message." Raw bytes, header and body
-					 * together, no MIME parsing -- same
-					 * .PEEK-only, exact-token-match scoping
-					 * as MBOX_FETCH_BODY_HEADER above, for
-					 * the same \Seen-side-effect reason. */
-#define MBOX_FETCH_BODY_TEXT		(1U << 7) /* BODY.PEEK[TEXT] only -- SS6.4.5.1:
-					 * "The TEXT part specifier refers to
-					 * the text body of the message,
-					 * omitting the [RFC5322] header."
-					 * store.c's read_message_body() finds
-					 * the same header/body blank-line
-					 * separator read_message_header()
-					 * already scans for, just returns
-					 * everything after it instead of
-					 * everything through it. If a client
-					 * requests both MBOX_FETCH_BODY_WHOLE
-					 * and MBOX_FETCH_BODY_TEXT in the same
-					 * FETCH (legal per SS6.4.5, unseen from
-					 * any real client so far), store.c
-					 * answers WHOLE and silently drops
-					 * TEXT -- one IMSG_MBOX_FETCH_BODY per
-					 * message keeps the wire protocol
-					 * symmetric with IMSG_MBOX_FETCH_HEADER
-					 * rather than needing an array; see
-					 * struct imsg_mbox_fetch_body's is_text
-					 * field and handle_mbox_fetch()'s
-					 * comment. */
+#define MBOX_FETCH_MODSEQ		(1U << 4) /* RFC 7162 SS3.1.4.2 --
+					 * set if MODSEQ was named,
+					 * CHANGEDSINCE was used, or
+					 * CONDSTORE is already enabled */
+#define MBOX_FETCH_BODY_HEADER		(1U << 5) /* BODY.PEEK[HEADER] only
+					 * (SS6.4.5): raw header block, not
+					 * ENVELOPE. Plain BODY[HEADER] is
+					 * unimplemented (\Seen side effect). */
+#define MBOX_FETCH_BODY_WHOLE		(1U << 6) /* BODY.PEEK[] only
+					 * (SS6.4.5): raw header+body, no MIME
+					 * parsing. */
+#define MBOX_FETCH_BODY_TEXT		(1U << 7) /* BODY.PEEK[TEXT] only
+					 * (SS6.4.5.1): body after the
+					 * header/body blank line. If both
+					 * WHOLE and TEXT are requested,
+					 * store.c answers WHOLE only. */
 #define MBOX_FETCH_HEADER_FIELDS	(1U << 8) /* BODY.PEEK[HEADER.FIELDS
-					 * (name ...)] or BODY.PEEK[HEADER.
-					 * FIELDS.NOT (name ...)] -- SS6.4.5.1.
-					 * Reuses IMSG_MBOX_FETCH_HEADER/struct
-					 * imsg_mbox_fetch_header wholesale (see
-					 * that struct's comment): from store.c's
-					 * and listener.c's wire-protocol point of
-					 * view this is just "header-region bytes,
-					 * possibly filtered", the same shape as
-					 * plain BODY.PEEK[HEADER], just produced
-					 * by store.c's read_message_header_
-					 * fields() instead of read_message_
-					 * header(). req->header_fields_not and
-					 * req->header_fields (below) carry the
-					 * NOT flag and the space-joined field-
-					 * name list; the exact client-typed
-					 * label text ("HEADER.FIELDS (DATE
-					 * FROM)", etc.) never crosses the imsg
-					 * boundary at all -- listener.c already
-					 * has it from parsing the client's own
-					 * command line, and echoes it back
-					 * verbatim in the FETCH response rather
-					 * than reconstructing it (see listener.c's
-					 * parse_header_fields_att() and struct
-					 * session's pending_header_label
-					 * comment). If a client requests both
-					 * plain BODY.PEEK[HEADER] and a HEADER.
-					 * FIELDS variant in the same FETCH (legal
-					 * per SS6.4.5, unseen from any real
-					 * client so far), listener.c's parse_
-					 * fetch_atts() has HEADER win and drops
-					 * HEADER_FIELDS -- same "more general
-					 * variant wins" precedent as MBOX_FETCH_
-					 * BODY_WHOLE vs. MBOX_FETCH_BODY_TEXT. */
-#define MBOX_FETCH_ENVELOPE		(1U << 9) /* RFC 9051 SS7.5.2 ENVELOPE --
-						 * "computed by the server by
-						 * parsing the [RFC5322] header
-						 * into the component parts,
-						 * defaulting various fields as
-						 * necessary." Unlike every MBOX_
-						 * FETCH_BODY_* item above, this is
-						 * a *parsed*, structured response
-						 * (date/subject/address lists),
-						 * built entirely by store.c's
-						 * build_envelope() -- see struct
-						 * imsg_mbox_fetch_envelope below
-						 * for why the wire payload is
-						 * already-formatted response text
-						 * rather than raw bytes. No .PEEK
-						 * variant exists for ENVELOPE (SS6.4.5's
-						 * fetch-att grammar has no "ENVELOPE.
-						 * PEEK" production) and it has no
-						 * \Seen-setting side effect to avoid
-						 * in the first place, unlike the BODY[...]
-						 * family -- so, unlike MBOX_FETCH_BODY_*,
-						 * this bit is set directly from the
-						 * bare "ENVELOPE" token. */
+					 * [.NOT] (names)] (SS6.4.5.1); reuses
+					 * IMSG_MBOX_FETCH_HEADER. req->
+					 * header_fields_not/header_fields
+					 * carry the NOT flag and field list;
+					 * listener.c echoes the client's own
+					 * label text back verbatim. Plain
+					 * BODY.PEEK[HEADER] wins if both are
+					 * requested. */
+#define MBOX_FETCH_ENVELOPE		(1U << 9) /* RFC 9051 SS7.5.2 --
+						 * parsed response via store.c's
+						 * build_envelope(). No .PEEK
+						 * variant, no \Seen side
+						 * effect. */
 #define MBOX_FETCH_BODYSTRUCTURE	(1U << 10) /* RFC 9051 SS7.5.2
-						 * BODYSTRUCTURE, and its non-
-						 * extensible sibling "BODY"
-						 * (SS9's fetch-att: `"BODY"
-						 * ["STRUCTURE"]` -- bare "BODY",
-						 * no brackets, is a synonym for
-						 * BODYSTRUCTURE-without-
-						 * extension-data, distinct from
-						 * "BODY[section]", which is
-						 * content). This implementation
-						 * never emits extension data
-						 * (body MD5/disposition/
-						 * language/location) even for
-						 * BODYSTRUCTURE -- RFC 9051 SS7.5.2
-						 * says extension data "can be
-						 * returned" with BODYSTRUCTURE,
-						 * not that it MUST be, so BODY
-						 * and BODYSTRUCTURE produce
-						 * byte-identical output in this
-						 * server, both setting this one
-						 * bit. Like ENVELOPE, this is a
-						 * *parsed* response -- here,
-						 * recursive MIME structure
-						 * parsing via store.c's build_
-						 * bodystructure()/build_body_
-						 * structure() (see struct imsg_
-						 * mbox_fetch_bodystructure's
-						 * comment) -- not raw or
-						 * filtered bytes, and has no
-						 * .PEEK variant or \Seen side
-						 * effect, so (like MBOX_FETCH_
-						 * ENVELOPE, unlike MBOX_FETCH_
-						 * BODY_*) this bit is set
-						 * directly from the bare token. */
+						 * BODYSTRUCTURE and its synonym
+						 * bare "BODY", extension data
+						 * (MD5/disposition/language/
+						 * location) is never emitted,
+						 * so both produce identical
+						 * output. Recursive MIME
+						 * parsing via store.c's
+						 * build_bodystructure(). */
 #define MBOX_FETCH_BODY_PART		(1U << 11) /* BODY.PEEK[<section-part>]
-						 * only -- same .PEEK-only
-						 * scoping as every other
-						 * MBOX_FETCH_BODY_* bit (plain
-						 * BODY[<section-part>], which
-						 * would implicitly set \Seen,
-						 * is not implemented, same as
-						 * plain BODY[]/BODY[TEXT]/
-						 * BODY[HEADER...] already
-						 * aren't). SS6.4.5.1's numeric-only
-						 * section-part grammar
-						 * (`section-part = nz-number
-						 * *("." nz-number)`, e.g. "2" or
-						 * "3.1"), addressing one specific
-						 * leaf MIME part's raw (still
-						 * transfer-encoded -- SS6.4.5's
-						 * BODY[] never decodes Content-
-						 * Transfer-Encoding, that's
-						 * BINARY[]'s job, out of scope
-						 * here same as BODYSTRUCTURE's own
-						 * extension-data cut) body bytes.
-						 * Distinct bit from MBOX_FETCH_
-						 * BODY_WHOLE/_TEXT since it needs
-						 * an extra parameter (req->
-						 * section_part below) those don't.
-						 * Deliberately v1-scoped to leaf
-						 * parts only: a section-part
-						 * naming a MULTIPART container
-						 * itself, or reaching into a
-						 * MESSAGE/RFC822 or MESSAGE/GLOBAL
-						 * part's own nested numbering
-						 * (SS6.4.5.1: "also has nested
-						 * part numbers, referring to
-						 * parts of the MESSAGE part's
-						 * body") is "not found" for this
-						 * item -- matching BODYSTRUCTURE's
-						 * own established message/rfc822
-						 * scope cut (store.c's build_
-						 * body_structure() already refuses
-						 * to describe such a message's
-						 * structure at all, so this
-						 * implementation was never going
-						 * to be able to name a part inside
-						 * one). The non-numeric part
-						 * specifiers (HEADER, HEADER.
-						 * FIELDS[.NOT], MIME, TEXT)
-						 * standing alone are already
-						 * MBOX_FETCH_BODY_HEADER/_TEXT/
-						 * HEADER_FIELDS; this bit is only
-						 * for the purely-numeric form. */
+						 * only (SS6.4.5.1, numeric
+						 * section-part). Leaf parts
+						 * only, a MULTIPART container
+						 * or nested MESSAGE/RFC822|
+						 * GLOBAL numbering is "not
+						 * found", matching
+						 * BODYSTRUCTURE's own scope
+						 * cut. Needs req->section_part,
+						 * hence a separate bit from
+						 * WHOLE/TEXT. */
 
 /*
- * Cap on the dotted-numeric section-part string (imapd.h's own
- * MBOX_FETCH_BODY_PART comment) a BODY[<n>]/BODY.PEEK[<n>] fetch-att
- * carries from listener.c to store.c (struct imsg_mbox_fetch's
- * section_part below). Sized for MIME_MAX_DEPTH (10) levels of
- * MIME_MAX_PARTS (64, i.e. up to 2 digits per level) numbers plus
- * separating dots: 10*2 + 9 = 29 characters worst case: 40 leaves
- * comfortable headroom without being large enough to matter for the
- * imsg-size arithmetic every other MAX constant in this file cares
- * about. listener.c's tokenizer rejects (BAD) a section-part that
- * would exceed this rather than truncate it, same precedent as every
- * other MAX constant here.
+ * Cap on the dotted-numeric section-part string (struct imsg_mbox_fetch's
+ * section_part below). Sized for MIME_MAX_DEPTH (10) levels x 2 digits
+ * plus dots: 29 worst case; 40 leaves headroom. listener.c rejects (BAD)
+ * an oversized section-part rather than truncating it.
  */
 #define SECTION_PART_MAX	40
 
 /*
- * Cap on the raw header bytes IMSG_MBOX_FETCH_HEADER can carry, for the same
- * reason APPEND_LITERAL_MAX exists in listener.c: struct imsg_mbox_fetch_
- * header's fixed fields plus this many trailing bytes need to fit under
- * MAX_IMSGSIZE (16384, imsg.h) alongside the imsg header itself. Real-world
- * RFC 5322 headers are essentially always well under 8192 bytes even with a
- * long Received:/DKIM-Signature: chain; a header that somehow exceeds this
- * is treated as "not found" for BODY.PEEK[HEADER] purposes (that one
- * message's header is silently omitted, same as a message missing on disk
- * -- see handle_mbox_fetch()'s existing "indexed but missing on disk"
- * skip), not truncated, matching APPEND_LITERAL_MAX's own "reject rather
- * than silently do something the client didn't ask for" precedent.
+ * Cap on raw header bytes IMSG_MBOX_FETCH_HEADER can carry (must fit
+ * under imsg's MAX_IMSGSIZE, 16384). Real RFC 5322 headers are well
+ * under 8192; an oversized header is "not found" for BODY.PEEK[HEADER],
+ * not truncated.
  */
 #define FETCH_HEADER_MAX	8192
 
 /*
- * Cap on a whole message's size, both when APPEND writes one and when
- * BODY.PEEK[]/BODY.PEEK[TEXT] read one back out. Originally listener.c-
- * local (only APPEND needed it); moved here when store.c's read_message_
- * body() needed the identical number, so the two enforcement points share
- * one symbol instead of two independently-maintained constants that could
- * silently drift apart. See listener.c's own comment at this symbol's
- * former definition site for the full MAX_IMSGSIZE arithmetic (12000
- * leaves headroom under imsg's 16384-byte ceiling alongside either
- * struct's own fixed fields and the imsg header itself). A message larger
- * than this needs real fd-passing, not implemented this pass; rejected
- * with a plain NO/omitted from the FETCH response (RFC 9051 defines no
- * response code for a size cap) rather than truncating.
+ * Cap on a whole message's size, for both APPEND and BODY.PEEK[]/
+ * BODY.PEEK[TEXT] (shared so the two enforcement points can't drift).
+ * 12000 leaves headroom under imsg's 16384-byte MAX_IMSGSIZE. Larger
+ * messages need real fd-passing, not implemented; rejected rather than
+ * truncated.
  */
 #define APPEND_LITERAL_MAX	12000
 
 /*
- * Cap on the bytes any single BODY[<section>]/BODY.PEEK[<section>]
- * response (whole message, TEXT-only, or a numeric section-part) can
- * carry on one IMSG_MBOX_FETCH_BODY, reusing APPEND_LITERAL_MAX's own
- * value and "comfortable headroom under imsg's MAX_IMSGSIZE" reasoning
- * rather than a new constant, since it's the same underlying limit
- * (one struct imsg_mbox_fetch_body's fixed fields plus this many
- * trailing bytes have to fit under MAX_IMSGSIZE alongside the imsg
- * header itself). Before this pass, only whole/TEXT fetches existed
- * and this cap was simply "the message is too big, fail the whole
- * item" (APPEND_LITERAL_MAX's original framing). Section-part fetches
- * change the picture: MIME parts (attachments especially) routinely
- * exceed this on their own -- that's the entire reason BODYSTRUCTURE_
- * READ_MAX exists as a separate, much larger cap on what store.c is
- * willing to *read* off disk. A client fetching a large part without
- * a <<partial>> range still can't get more than this many bytes back
- * in one response (treated as "not found" for that item, same reject-
- * not-truncate precedent as everywhere else) -- real clients handle
- * this by re-fetching in <<partial>> ranged chunks instead (confirmed
- * against real Apple Mail traffic this pass, which already issues
- * BODY.PEEK[TEXT]<0.16384>-style ranged fetches unprompted). A
- * <<partial>> request's own requested count is silently clamped down
- * to this cap rather than rejected outright if it's larger -- RFC 9051
- * SS6.4.5's BODY[]<<partial>> semantics already require truncating a
- * range that runs past the end of the available text, so a server-
- * side response-size cap truncating a too-large *count* the same way
- * (returning fewer bytes than asked, letting the client re-fetch the
- * remainder at a later origin octet) is consistent with that existing
- * "truncate the count, don't fail the fetch" spirit, not a new kind of
- * behavior this cap invents.
+ * Cap on bytes any single BODY[<section>]/BODY.PEEK[<section>] response
+ * can carry on one IMSG_MBOX_FETCH_BODY; reuses APPEND_LITERAL_MAX's
+ * value and MAX_IMSGSIZE-headroom reasoning. Real clients (confirmed:
+ * Apple Mail) re-fetch large parts via <<partial>> ranges rather than
+ * expecting a whole part in one response. A <<partial>> count larger
+ * than this is clamped down, per RFC 9051 SS6.4.5's own truncate-past-
+ * end-of-text precedent, rather than rejected.
  */
 #define FETCH_PART_MAX		APPEND_LITERAL_MAX
 
 /*
  * Cap on the space-joined header-field-name list a BODY.PEEK[HEADER.
- * FIELDS (...)]/BODY.PEEK[HEADER.FIELDS.NOT (...)] fetch-att carries from
- * listener.c to store.c (struct imsg_mbox_fetch's header_fields below).
- * 256 bytes comfortably covers any realistic request (RFC 9051 SS6.4.5's
- * own example asks for two: "DATE FROM"; even a dozen longish field names
- * like "Content-Type"/"Message-Id" fit easily) -- listener.c's parse_
- * header_fields_att() rejects (BAD) a field-name list that would exceed
- * this rather than truncate it, same "reject rather than silently do
- * something the client didn't ask for" precedent as every other MAX
- * constant in this file.
+ * FIELDS[.NOT] (...)] fetch-att carries to store.c. 256 bytes covers
+ * any realistic request; listener.c rejects (BAD) an oversized list
+ * rather than truncating it.
  */
 #define HEADER_FIELDS_MAX	256
 
 /*
- * Cap on the fully-formatted ENVELOPE parenthesized-list text store.c's
- * build_envelope() can carry on one IMSG_MBOX_FETCH_ENVELOPE (struct imsg_
- * mbox_fetch_envelope below). Sized off the same reasoning as FETCH_HEADER_
- * MAX (8192): every field in an envelope is extracted from a header that
- * itself can't exceed FETCH_HEADER_MAX, and while IMAP quoted-string
- * escaping (backslash/double-quote doubling) plus the address-structure
- * parenthesization overhead can inflate the formatted size somewhat versus
- * the raw header, a header dense enough with backslashes/quotes/addresses
- * to actually approach 8192 bytes of *formatted* envelope text from a
- * header that's itself under 8192 bytes is already an extreme case --
- * matching FETCH_HEADER_MAX's own "essentially always well under" framing.
- * A message whose formatted envelope somehow exceeds this is treated as
- * "not found" for ENVELOPE purposes (same reject-rather-than-truncate
- * precedent as FETCH_HEADER_MAX/APPEND_LITERAL_MAX), not truncated.
+ * Cap on the fully-formatted ENVELOPE text store.c's build_envelope()
+ * can carry on one IMSG_MBOX_FETCH_ENVELOPE. Sized off FETCH_HEADER_MAX
+ * (8192), since every envelope field comes from a header bounded by
+ * that same cap. An oversized envelope is "not found", not truncated.
  */
 #define ENVELOPE_MAX	8192
 
 /*
- * Caps on store.c's recursive BODYSTRUCTURE builder (build_body_structure(),
- * store.c), bounding both the work it does and the size of what it can ever
- * produce -- a message is entirely attacker/sender-controlled data (MIME
- * part count and multipart nesting depth are both just numbers the message's
- * own headers claim), so both need a hard ceiling rather than trusting
- * whatever a message says about its own structure. MIME_MAX_DEPTH (10) is
- * generous for any real mail this personal-use server will see -- deeply
- * nested multipart is already unusual beyond 2-3 levels (e.g. multipart/
- * mixed containing a multipart/alternative) -- while still bounding
- * recursion (and hence worst-case stack use) at a small, fixed number.
- * MIME_MAX_PARTS (64) similarly bounds total part count across the whole
- * recursive walk (a running counter threaded through every recursive call,
- * not a per-multipart-parent limit), bounding both output size and total
- * work independent of depth. Exceeding either cap is treated as "not found"
- * for this message's BODYSTRUCTURE (reject, not silently truncate the part
- * tree into something that no longer accurately describes the message) --
- * same precedent as every other MAX constant in this header.
+ * Caps on store.c's recursive BODYSTRUCTURE builder (build_body_
+ * structure()): a message's own headers claim its own part count and
+ * nesting depth, so both need a hard ceiling rather than trusting them.
+ * Exceeding either is "not found" for that message's BODYSTRUCTURE
+ * rather than a truncated part tree.
  */
 #define MIME_MAX_DEPTH	10
 #define MIME_MAX_PARTS	64
 
 /*
- * Cap on the fully-formatted BODYSTRUCTURE parenthesized-list text store.c's
- * build_bodystructure() can carry on one IMSG_MBOX_FETCH_BODYSTRUCTURE
- * (struct imsg_mbox_fetch_bodystructure below). Unlike ENVELOPE_MAX, this
- * isn't derived from a single header's own size cap -- a BODYSTRUCTURE's
- * size instead scales with MIME_MAX_PARTS (each part contributing its own
- * type/subtype/parameter-list/encoding/octet-count fields) and MIME_MAX_
- * DEPTH (each nesting level adding its own wrapping parens and multipart
- * subtype). 12000 mirrors APPEND_LITERAL_MAX's own reasoning: comfortable
- * headroom under imsg(3)'s MAX_IMSGSIZE (16384) alongside this struct's own
- * fixed fields and the imsg header itself, generous enough that MIME_MAX_
- * PARTS/MIME_MAX_DEPTH -- not this byte cap -- are expected to be the
- * limiting factor in practice for any real message. Same reject-rather-
- * than-truncate handling as every other MAX constant here if somehow
- * exceeded anyway.
+ * Cap on the fully-formatted BODYSTRUCTURE text store.c's
+ * build_bodystructure() can carry on one IMSG_MBOX_FETCH_BODYSTRUCTURE.
+ * 12000 mirrors APPEND_LITERAL_MAX's MAX_IMSGSIZE-headroom reasoning;
+ * MIME_MAX_PARTS/MIME_MAX_DEPTH, not this byte cap, are expected to be
+ * the limiting factor in practice.
  */
 #define BODYSTRUCTURE_MAX	12000
 
 /*
- * Cap on the raw on-disk bytes store.c's build_bodystructure() (via read_
- * message_body(), store.c) will read into memory while deriving a
- * message's MIME structure. Deliberately independent from APPEND_LITERAL_
- * MAX/FETCH_BODY_MAX: that constant's "no on-disk message can legally
- * exceed this" reasoning only holds for messages that arrived through
- * this server's own APPEND command, not for mail delivered by an
- * external MTA into the spool directly, which this server doesn't
- * control the size of at all. Real-hardware testing (an Apple Mail
- * message with a small image attachment) confirmed this in practice --
- * base64-encoded attachment data routinely pushes even a modest image
- * past 12000 bytes, so sharing that cap made BODYSTRUCTURE fail on
- * essentially any real attachment-bearing mail.
+ * Cap on raw on-disk bytes build_bodystructure() (via read_message_
+ * body()) will read while deriving a message's MIME structure.
+ * Independent from APPEND_LITERAL_MAX: mail delivered by an external
+ * MTA isn't size-bounded by this server's own APPEND cap, and
+ * real-hardware testing showed base64 attachments routinely exceed it.
+ * The read is never sent back over the wire whole (only the derived,
+ * already-bounded structure summary is), so it can afford to be large.
  *
- * build_bodystructure() only *derives* a small, MIME_MAX_PARTS/MIME_MAX_
- * DEPTH/BODYSTRUCTURE_MAX-bounded structure summary from these bytes --
- * it never sends the raw bytes themselves back to the client over the
- * wire -- so, unlike BODY.PEEK[]/BODY.PEEK[TEXT] (still capped at
- * APPEND_LITERAL_MAX, since those responses do carry the raw bytes
- * whole on a single imsg and are therefore still bound by imsg's own
- * MAX_IMSGSIZE ceiling), this read can afford to be much larger.
+ * 41943040 (40 MiB) is sized off Gmail's documented 25MB attachment
+ * limit (support.google.com/mail/answer/6584) plus base64 overhead, not
+ * a protocol requirement. Exceeding it is "not found" for that
+ * message's BODYSTRUCTURE, not truncated.
  *
- * 41943040 (40 MiB) is sized off Gmail's own documented 25MB attachment
- * limit (https://support.google.com/mail/answer/6584), the most common
- * real-world ceiling a personal mailbox is likely to receive mail under,
- * plus headroom for base64's ~37% encoding overhead (a 25MB attachment
- * becomes roughly 34MB once base64-encoded and wrapped in MIME headers)
- * -- not a hard protocol requirement, just a generous, cited real-world
- * bound rather than an arbitrary guess. Exceeding it is "not found" for
- * this message's BODYSTRUCTURE (reject, not truncate -- same precedent
- * as every other MAX constant in this header), same as any other reason
- * this message's structure can't be produced.
- *
- * As of this pass, this value is operator-configurable via imapd.conf's
- * "attachment max <bytes>" directive (parse.y), since the whole reason
- * this cap is sized off Gmail's own attachment limit rather than derived
- * from any protocol constant is that it's inherently a judgment call
- * about the operator's own expected mail, not a fixed property of the
- * implementation -- see parse.y's grammar rule for the directive and
- * struct openimap_config's bodystructure_read_max field. This macro
- * changed meaning accordingly: no code reads it directly anymore (store.c
- * uses the runtime value received via IMSG_STORE_INIT instead); it now
- * exists solely as config_load()'s default when the directive is absent
- * from imapd.conf, so an empty/absent config file still gets the same
- * behavior this implementation always had.
+ * Operator-configurable via imapd.conf's "attachment max <bytes>"
+ * directive (parse.y, struct openimap_config's bodystructure_read_max).
+ * store.c uses the runtime value from IMSG_STORE_INIT; this macro now
+ * only serves as config_load()'s default when the directive is absent.
  */
 #define BODYSTRUCTURE_READ_DEFAULT	41943040
 
@@ -1216,108 +525,52 @@ struct imsg_mbox_fetch {
 	uint32_t	seq_hi;		/* 1-based, inclusive; ignored if
 					 * hi_is_star */
 	int		lo_is_star;
-	int		hi_is_star;	/* "*" is resolved by store against
-					 * its own live message count at
-					 * fetch time, not against listener's
-					 * -- possibly stale -- count from the
-					 * last SELECT reply (mail could have
-					 * arrived since) */
+	int		hi_is_star;	/* "*" resolves against store's live
+					 * message count, not listener's
+					 * possibly-stale SELECT-time count */
 	uint32_t	attrs;		/* bitmask of MBOX_FETCH_* above */
 
-	/*
-	 * RFC 7162 SS3.1.4.1 CHANGEDSINCE fetch-modifier: "The information
-	 * described by message data items is only returned for messages
-	 * that have a mod-sequence bigger than <mod-sequence>." has_
-	 * changedsince distinguishes "not specified" from a legal value of
-	 * 0, the same has_/value pairing this header already uses for
-	 * APPEND's optional date-time (see imsg_mbox_append's append_has_
-	 * date, in listener.c's struct session).
-	 */
+	/* RFC 7162 SS3.1.4.1 CHANGEDSINCE; has_changedsince distinguishes
+	 * "not specified" from a legal value of 0. */
 	int		has_changedsince;
 	uint64_t	changedsince;
 
-	/*
-	 * RFC 9051 SS6.4.9 (UID command): "the numbers in the sequence-set
-	 * argument are unique identifiers instead of message sequence
-	 * numbers" for a UID FETCH -- by_uid tells store.c to resolve seq_lo/
-	 * seq_hi (and "*") against UID space rather than 1-based index
-	 * position. listener.c separately forces MBOX_FETCH_UID into attrs
-	 * whenever by_uid is set (SS6.4.9: "server implementations MUST
-	 * implicitly include the UID message data item as part of any FETCH
-	 * response caused by a UID command"), so store.c itself needs no
-	 * special-casing for *that* part -- meta.uid is already unconditionally
-	 * populated regardless (see imsg_mbox_fetch_meta below).
+	/* by_uid: RFC 9051 SS6.4.9 UID FETCH, resolve seq_lo/seq_hi
+	 * against UID space. listener.c separately forces MBOX_FETCH_UID
+	 * into attrs whenever this is set.
 	 *
-	 * want_vanished is RFC 7162 SS3.2.6's VANISHED UID FETCH modifier
-	 * (only legal alongside CHANGEDSINCE, and only on UID FETCH --
-	 * listener.c's parse_fetch_modifiers()/fetch_dispatch() enforce both
-	 * restrictions before this ever reaches store.c). Per this
-	 * implementation's RFC 7162 SS5.1 minimal-state QRESYNC decision
-	 * (see imsg_mbox_select_vanished below), store.c doesn't track
-	 * expunge-event history, so it can't actually filter by CHANGEDSINCE
-	 * here either -- it reports every UID in [seq_lo, seq_hi] that isn't
-	 * currently present, unconditionally, which SS3.2.6's own note
-	 * explicitly sanctions ("A server that receives a mod-sequence
-	 * smaller than <minmodseq> ... MUST behave as if it was requested to
-	 * report all expunged messages from the provided UID set parameter" --
-	 * this implementation always behaves that way, having no memory of
-	 * <minmodseq> at all).
+	 * want_vanished: RFC 7162 SS3.2.6 VANISHED modifier (only legal
+	 * with CHANGEDSINCE, only on UID FETCH; listener.c enforces both).
+	 * Per this implementation's minimal-state QRESYNC model, store.c
+	 * reports every UID in range that isn't present, unconditionally
+	 *, explicitly sanctioned by SS3.2.6.
 	 */
 	int		by_uid;
 	int		want_vanished;
 
-	/*
-	 * BODY.PEEK[HEADER.FIELDS (...)]/BODY.PEEK[HEADER.FIELDS.NOT (...)]
-	 * (see MBOX_FETCH_HEADER_FIELDS above): header_fields_not is 0 for
-	 * HEADER.FIELDS (include only the listed names), 1 for HEADER.
-	 * FIELDS.NOT (exclude the listed names). header_fields is the
-	 * requested field-name list, space-joined, exactly as the client
-	 * typed each name (matching is ASCII-range case-insensitive per
-	 * SS6.4.5.1, done by store.c's read_message_header_fields() --
-	 * this field is not itself normalized to any particular case).
-	 * Both are only meaningful alongside attrs & MBOX_FETCH_HEADER_
-	 * FIELDS; listener.c's parse_header_fields_att() has already fully
-	 * validated the header-list grammar before either field is ever
-	 * populated, so store.c can assume header_fields is well-formed
-	 * (non-empty, space-separated, no embedded quotes -- see that
-	 * function's comment for why a bare-atom-only field name is this
-	 * implementation's own scope cut).
+	/* BODY.PEEK[HEADER.FIELDS[.NOT] (...)] (MBOX_FETCH_HEADER_FIELDS):
+	 * header_fields_not is 0 for FIELDS (include), 1 for FIELDS.NOT
+	 * (exclude). header_fields is the space-joined field-name list,
+	 * exactly as typed (matching is ASCII case-insensitive, done by
+	 * store.c). listener.c has already validated the grammar before
+	 * either field is populated.
 	 */
 	int		header_fields_not;
 	char		header_fields[HEADER_FIELDS_MAX];
 
-	/*
-	 * BODY[<section-part>]/BODY.PEEK[<section-part>] (MBOX_FETCH_BODY_
-	 * PART above): section_part is the client-typed dotted-numeric part
-	 * path verbatim (e.g. "3.1"), non-empty only alongside attrs &
-	 * MBOX_FETCH_BODY_PART -- listener.c's tokenizer has already
-	 * validated it's 1*(digit) *("." 1*digit) with no leading zeros
-	 * before this is ever populated, so store.c can assume it parses
-	 * cleanly into a path of nz-numbers. Single shared field, same "one
-	 * instance assumed per FETCH command" simplification as header_
-	 * fields above -- a client requesting two different section-parts
-	 * in one FETCH (legal per SS6.4.5.1, unseen from any real client so
-	 * far) only gets the first one honored, matching that same
-	 * precedent rather than restructuring this struct into an array for
-	 * a case no real client actually does.
+	/* BODY[<section-part>]/BODY.PEEK[<section-part>]
+	 * (MBOX_FETCH_BODY_PART): section_part is the client-typed
+	 * dotted-numeric path (e.g. "3.1"), pre-validated by listener.c's
+	 * tokenizer. Single shared field, a client requesting two
+	 * section-parts in one FETCH only gets the first honored.
 	 *
-	 * has_partial/partial_start/partial_count carry a <<partial>> range
-	 * (SS6.4.5's "<start.count>" suffix, e.g. "BODY.PEEK[3.1]<0.65536>"
-	 * or "BODY.PEEK[TEXT]<0.16384>" -- the latter is real, observed
-	 * Apple Mail traffic this project's own real-hardware testing
-	 * turned up, previously silently unhandled since no "<...>" parsing
-	 * existed anywhere in listener.c at all before this pass). Applies
-	 * uniformly to whichever BODY[...]/BODY.PEEK[...] variant attrs
-	 * selects (whole, TEXT, or section_part) -- store.c slices the
-	 * already-extracted content to [partial_start, partial_start +
-	 * partial_count) before ever composing the response imsg, clamped
-	 * to FETCH_PART_MAX and to the content's own actual length (RFC
-	 * 9051 SS6.4.5: "If the starting octet is beyond the end of the
-	 * text, an empty string is returned... Any partial fetch that
-	 * attempts to read beyond the end of the text is truncated as
-	 * appropriate"). has_partial distinguishes "no range requested"
-	 * from a legal partial_start of 0, same has_/value pairing this
-	 * header already uses for CHANGEDSINCE above.
+	 * has_partial/partial_start/partial_count carry a <<partial>>
+	 * range (SS6.4.5), applying uniformly to whole/TEXT/section_part.
+	 * store.c slices the extracted content to
+	 * [partial_start, partial_start + partial_count), clamped to
+	 * FETCH_PART_MAX and the content's actual length (SS6.4.5's
+	 * truncate-past-end-of-text rule). has_partial distinguishes
+	 * "no range" from a legal partial_start of 0.
 	 */
 	char		section_part[SECTION_PART_MAX];
 	int		has_partial;
@@ -1330,290 +583,168 @@ struct imsg_mbox_fetch_meta {
 	uint32_t	uid;
 	uint64_t	size;		/* on-disk file size, octets --
 					 * RFC822.SIZE (RFC 9051 SS2.3.4).
-					 * F13 fix: 64-bit so a message larger
-					 * than 4 GiB reports a correct size. */
+					 * 64-bit so a message over 4 GiB
+					 * reports correctly (F13 fix). */
 	int64_t		internaldate;	/* Unix timestamp, parsed from the
-					 * maildir basename's own leading
-					 * delivery-time field -- see store.c's
-					 * handle_mbox_fetch() comment for why
-					 * that's used instead of the file's
-					 * mtime */
+					 * maildir basename's delivery-time
+					 * field, not the file's mtime */
 	char		flags[MBOX_FLAGS_MAX]; /* space-separated IMAP flag
-					 * names, e.g. "\Seen \Flagged foo" --
-					 * pre-formatted by store.c, the only
-					 * side that has both the maildir
-					 * flag-suffix letters and the index's
-					 * keyword list */
-	uint64_t	modseq;		/* RFC 7162 per-message mod-sequence --
-					 * always populated by store.c (cheap:
-					 * it's already parsed the index line),
-					 * same "always compute, let listener.c
-					 * decide whether to print it" split as
-					 * every other struct imsg_mbox_fetch_
-					 * meta field; shared by FETCH, STORE's
-					 * FETCH echo, and QRESYNC SELECT
-					 * resync's FETCH-with-UID responses,
-					 * all three of which reuse this struct
-					 * (see imsg_mbox_selected's comment) */
+					 * names, e.g. "\Seen \Flagged foo",
+					 * pre-formatted by store.c */
+	uint64_t	modseq;		/* RFC 7162 per-message mod-sequence,
+					 * always populated; shared by FETCH,
+					 * STORE's FETCH echo, and QRESYNC
+					 * resync's FETCH-with-UID responses */
 };
 
 /*
  * IMSG_MBOX_FETCH_HEADER: sent by store.c immediately before the
- * IMSG_MBOX_FETCH_META for the same message, if and only if req->attrs &
- * MBOX_FETCH_BODY_HEADER -- listener.c relies on this exact ordering
- * (rather than matching on seqno/uid) to fold the header bytes into the
- * same untagged "* N FETCH (...)" response line as the message's other
- * requested data items, per RFC 9051's own SS6.4.5/SS7.5.2 worked examples,
- * which always show every FETCH data item for a message on one response
- * line, not spread across several. This mirrors imsg_mbox_append's
- * "fixed struct, then trailing variable-length bytes on the same imsg"
- * shape (listener.c's session_finish_append(): malloc(sizeof(struct) +
- * len), memcpy both pieces in, one imsg_compose() call) -- just sent in
- * the opposite direction (store -> listener) and read back the same way
- * imsg_mbox_append already is (store.c's handle_mbox_append():
- * imsg_get_buf() for the fixed struct, then imsg_get_len()/imsg_get_buf()
- * again for whatever trailing bytes remain).
+ * IMSG_MBOX_FETCH_META for the same message, iff req->attrs &
+ * MBOX_FETCH_BODY_HEADER, listener.c relies on this exact ordering to
+ * fold the header bytes into the same "* N FETCH (...)" response line.
+ * Same "fixed struct + trailing variable-length bytes on one imsg" shape
+ * as imsg_mbox_append.
  */
 struct imsg_mbox_fetch_header {
-	uint32_t	seqno;		/* 1-based -- matches the seqno on the
-					 * IMSG_MBOX_FETCH_META that follows */
+	uint32_t	seqno;		/* 1-based, matches the following
+					 * IMSG_MBOX_FETCH_META */
 	uint32_t	uid;
-	int		found;		/* 0 if locate_message_file() (well,
-					 * store.c's own open_message_file(),
-					 * see its comment for why this is a
-					 * separate lookup rather than a shared
-					 * one) couldn't find the message file,
-					 * or its header exceeded
-					 * FETCH_HEADER_MAX -- listener.c omits
-					 * BODY[HEADER] from this one message's
+	int		found;		/* 0 if the message file couldn't be
+					 * found or its header exceeded
+					 * FETCH_HEADER_MAX; listener.c omits
+					 * BODY[HEADER] from this message's
 					 * response rather than failing the
-					 * whole FETCH, same as a metadata
-					 * lookup failure already does. hdrlen
-					 * and the trailing bytes are only
-					 * meaningful when found is 1. */
+					 * whole FETCH. hdrlen and the trailing
+					 * bytes are only meaningful if 1. */
 	uint32_t	hdrlen;		/* length of the trailing raw header
-					 * bytes on this same imsg, capped at
+					 * bytes on this imsg, capped at
 					 * FETCH_HEADER_MAX */
 };
 
 /*
  * IMSG_MBOX_FETCH_BODY: sent by store.c immediately before the
- * IMSG_MBOX_FETCH_META for the same message, if and only if req->attrs &
- * (MBOX_FETCH_BODY_WHOLE | MBOX_FETCH_BODY_TEXT) -- same ordering contract,
- * same "fixed struct + trailing variable-length bytes on one imsg" shape,
- * as struct imsg_mbox_fetch_header above (see that struct's comment); this
- * is the same wire idiom used a third time, not a new one.
+ * IMSG_MBOX_FETCH_META for the same message, iff req->attrs &
+ * (MBOX_FETCH_BODY_WHOLE | MBOX_FETCH_BODY_TEXT). Same ordering contract
+ * and wire shape as imsg_mbox_fetch_header above.
  */
 struct imsg_mbox_fetch_body {
-	uint32_t	seqno;		/* 1-based -- matches the seqno on the
-					 * IMSG_MBOX_FETCH_META that follows */
+	uint32_t	seqno;		/* 1-based, matches the following
+					 * IMSG_MBOX_FETCH_META */
 	uint32_t	uid;
-	int		found;		/* 0 if open_message_file() couldn't find
-					 * the message file, the file exceeded
-					 * APPEND_LITERAL_MAX (listener.c), the
-					 * content contained a NUL byte, or (for
-					 * is_text only) no header/body separator
-					 * could be found within the file --
-					 * listener.c omits BODY[]/BODY[TEXT] from
-					 * this one message's response rather than
-					 * failing the whole FETCH, same precedent
-					 * as imsg_mbox_fetch_header's found field.
-					 * bodylen and the trailing bytes are only
-					 * meaningful when found is 1. */
-	int		is_text;	/* 0 -- these are BODY.PEEK[] bytes (whole
-					 * message, header and body together); 1 --
-					 * these are BODY.PEEK[TEXT] bytes (body
-					 * only, header omitted). Set by store.c
-					 * based on which of MBOX_FETCH_BODY_WHOLE/
-					 * MBOX_FETCH_BODY_TEXT req->attrs actually
-					 * had set (WHOLE wins if a client somehow
-					 * requested both, see MBOX_FETCH_BODY_
-					 * TEXT's comment) -- listener.c uses this
-					 * to label the literal "BODY[]" vs
-					 * "BODY[TEXT]" correctly rather than
-					 * re-deriving it from its own fetch_attrs,
-					 * which could have both bits set. */
-	uint32_t	bodylen;	/* length of the trailing raw body bytes on
-					 * this same imsg, capped at
-					 * APPEND_LITERAL_MAX (listener.c) -- same
-					 * cap APPEND itself already enforces when
-					 * writing a message, reused here rather
-					 * than inventing a second constant, since
-					 * no message on disk can legally exceed it
-					 * in the first place */
+	int		found;		/* 0 if the message file couldn't be
+					 * found, it exceeded
+					 * APPEND_LITERAL_MAX, it contained a
+					 * NUL byte, or (is_text only) no
+					 * header/body separator was found.
+					 * bodylen and the trailing bytes are
+					 * only meaningful if 1. */
+	int		is_text;	/* 0 = BODY.PEEK[] bytes (whole
+					 * message); 1 = BODY.PEEK[TEXT] bytes
+					 * (body only). Set from which of
+					 * MBOX_FETCH_BODY_WHOLE/_TEXT was
+					 * requested (WHOLE wins if both). */
+	uint32_t	bodylen;	/* length of the trailing raw body
+					 * bytes on this imsg, capped at
+					 * APPEND_LITERAL_MAX */
 };
 
 /*
  * IMSG_MBOX_FETCH_ENVELOPE: sent by store.c immediately before the
- * IMSG_MBOX_FETCH_META for the same message, if and only if req->attrs &
- * MBOX_FETCH_ENVELOPE -- same ordering contract as struct imsg_mbox_fetch_
- * header/imsg_mbox_fetch_body above. Unlike those two, whose trailing bytes
- * are raw message content that listener.c wraps in a `{n}` literal, the
- * trailing bytes here are the *complete*, already-formatted RFC 9051
- * SS7.5.2 envelope parenthesized-list text -- e.g. `("Wed, 17 Jul 1996
- * 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry
- * Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray"
- * "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu"))
- * ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes"
- * "CNRI.Reston.VA.US")("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL
- * "<B27397-0100000@cac.washington.edu>")`, RFC 9051's own SS7.5.2 worked
- * example -- fully quoted/escaped and ready to splice verbatim into the
- * FETCH response right after the literal string "ENVELOPE ". store.c does
- * all the RFC 5322 header parsing, field unfolding, and address-list
- * decomposition (build_envelope(), store.c) -- that's where the raw header
- * bytes already live and where BODY.PEEK[HEADER.FIELDS...]'s folding-aware
- * parsing already lives, so this keeps parsing logic in one place rather
- * than splitting RFC 5322 semantics across both processes; listener.c's job
- * stays purely wire framing, same division of labor as every other FETCH
- * content item this project has implemented. No literal-block wrapping
- * needed (unlike BODY[HEADER]/BODY[]/BODY[TEXT]): envelope fields are short
- * quoted strings built by build_envelope()'s own IMAP-quoted-string escaper,
- * never raw message bytes, so they're never CRLF-bearing and always fit
- * directly on the response line.
+ * IMSG_MBOX_FETCH_META for the same message, iff req->attrs &
+ * MBOX_FETCH_ENVELOPE. Unlike fetch_header/fetch_body, the trailing bytes
+ * are the complete, already-formatted RFC 9051 SS7.5.2 envelope
+ * parenthesized-list text, ready to splice verbatim after "ENVELOPE " in
+ * the FETCH response. store.c's build_envelope() does all RFC 5322
+ * parsing and address-list decomposition; listener.c does pure wire
+ * framing. No literal-block wrapping needed, envelope fields are short
+ * quoted strings, never raw message bytes, so never CRLF-bearing.
  */
 struct imsg_mbox_fetch_envelope {
-	uint32_t	seqno;		/* 1-based -- matches the seqno on the
-					 * IMSG_MBOX_FETCH_META that follows */
+	uint32_t	seqno;		/* 1-based, matches the following
+					 * IMSG_MBOX_FETCH_META */
 	uint32_t	uid;
-	int		found;		/* 0 if open_message_file()/read_message_
-					 * header() couldn't find or read the
-					 * message's header, or the formatted
-					 * envelope text exceeded ENVELOPE_MAX --
-					 * listener.c omits ENVELOPE from this one
-					 * message's response rather than failing
-					 * the whole FETCH, same precedent as
-					 * imsg_mbox_fetch_header/imsg_mbox_fetch_
-					 * body's own found fields. envlen and the
-					 * trailing bytes are only meaningful when
-					 * found is 1. */
-	uint32_t	envlen;		/* length of the trailing, already-
-					 * formatted envelope text on this same
-					 * imsg, capped at ENVELOPE_MAX */
+	int		found;		/* 0 if the message's header couldn't
+					 * be found/read, or the formatted
+					 * envelope exceeded ENVELOPE_MAX.
+					 * envlen and the trailing bytes are
+					 * only meaningful if 1. */
+	uint32_t	envlen;		/* length of the trailing formatted
+					 * envelope text, capped at
+					 * ENVELOPE_MAX */
 };
 
 /*
  * IMSG_MBOX_FETCH_BODYSTRUCTURE: sent by store.c immediately before the
- * IMSG_MBOX_FETCH_META for the same message, if and only if req->attrs &
- * MBOX_FETCH_BODYSTRUCTURE -- same ordering contract and same "already-
- * formatted response text, not raw bytes" shape as struct imsg_mbox_fetch_
- * envelope above (see that struct's comment). The trailing bytes are the
- * *complete* RFC 9051 SS7.5.2 BODYSTRUCTURE parenthesized-list text, e.g.
- * `("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 2279 48)` for a
- * simple message (RFC 9051's own SS7.5.2 example), or, for a multipart
- * message, a nested structure like `(("TEXT" "PLAIN" ("CHARSET" "US-ASCII")
- * NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME"
- * "cc.diff") "<...>" "Compiler diff" "BASE64" 4554 73) "MIXED")` (also RFC
- * 9051's own example) -- built entirely by store.c's build_bodystructure()/
- * build_body_structure(), which do all the MIME parsing (Content-Type/
- * Content-Transfer-Encoding/Content-ID/Content-Description extraction,
- * multipart boundary splitting, recursion into sub-parts bounded by MIME_
- * MAX_DEPTH/MIME_MAX_PARTS) -- same "all the semantic parsing lives in one
- * process, listener.c does pure wire framing" division of labor as
- * envelope. This implementation deliberately never emits RFC 9051's
- * optional extension data (body MD5/disposition/language/location -- see
- * MBOX_FETCH_BODYSTRUCTURE's own comment for why), so a BODYSTRUCTURE fetch
- * and a bare BODY fetch (the explicitly non-extensible form) produce
- * identical text in this server. No literal-block wrapping needed, same
- * reasoning as envelope (build_body_structure()'s own IMAP-quoted-string
- * escaping, reused from envbuf_append_nstring(), guarantees no raw CRLF).
+ * IMSG_MBOX_FETCH_META for the same message, iff req->attrs &
+ * MBOX_FETCH_BODYSTRUCTURE. Same "already-formatted response text"
+ * shape as imsg_mbox_fetch_envelope. The trailing bytes are the complete
+ * RFC 9051 SS7.5.2 BODYSTRUCTURE parenthesized-list text, built by
+ * store.c's build_bodystructure()/build_body_structure() (Content-Type/
+ * CTE/Content-ID/Content-Description extraction, multipart boundary
+ * splitting, recursion bounded by MIME_MAX_DEPTH/MIME_MAX_PARTS).
+ * Extension data (MD5/disposition/language/location) is never emitted,
+ * so BODYSTRUCTURE and bare BODY produce identical text. No
+ * literal-block wrapping needed, same reasoning as envelope.
  */
 struct imsg_mbox_fetch_bodystructure {
-	uint32_t	seqno;		/* 1-based -- matches the seqno on the
-					 * IMSG_MBOX_FETCH_META that follows */
+	uint32_t	seqno;		/* 1-based, matches the following
+					 * IMSG_MBOX_FETCH_META */
 	uint32_t	uid;
-	int		found;		/* 0 if the message's raw bytes couldn't
-					 * be read, MIME_MAX_DEPTH/MIME_MAX_PARTS
-					 * was exceeded, the message contains a
-					 * message/rfc822 or message/global part
-					 * (see MBOX_FETCH_BODYSTRUCTURE's comment
-					 * for why those are scoped out), or the
-					 * formatted text exceeded BODYSTRUCTURE_
-					 * MAX -- listener.c omits BODYSTRUCTURE
-					 * from this one message's response rather
-					 * than failing the whole FETCH, same
-					 * precedent as every other content item's
-					 * own found field. bslen and the trailing
-					 * bytes are only meaningful when found is
-					 * 1. */
-	uint32_t	bslen;		/* length of the trailing, already-
-					 * formatted BODYSTRUCTURE text on this
-					 * same imsg, capped at BODYSTRUCTURE_MAX */
+	int		found;		/* 0 if the message's raw bytes
+					 * couldn't be read, MIME_MAX_DEPTH/
+					 * MIME_MAX_PARTS was exceeded, the
+					 * message contains a message/rfc822
+					 * or message/global part, or the
+					 * formatted text exceeded
+					 * BODYSTRUCTURE_MAX. bslen and the
+					 * trailing bytes are only meaningful
+					 * if 1. */
+	uint32_t	bslen;		/* length of the trailing formatted
+					 * BODYSTRUCTURE text, capped at
+					 * BODYSTRUCTURE_MAX */
 };
 
 /* enum mbox_op_error is defined earlier in this file, above
- * imsg_mbox_select -- moved there because every struct using it,
- * including this one, needs the complete type in scope, and
- * imsg_mbox_selected/imsg_mbox_status_result appear before this point.
- * See that definition's own comment for the full rationale. */
+ * imsg_mbox_select, since several structs before this point need the
+ * complete type in scope. */
 
-/* Generic per-operation completion signal -- FETCH is the first user,
- * but the name (and the enum's own placement of IMSG_MBOX_RESULT after
- * every other IMSG_MBOX_* type) suggests it's meant to close out
- * STORE/APPEND/etc. too once those exist. */
+/* Generic per-operation completion signal; FETCH is the first user, but
+ * intended for STORE/APPEND/etc. too. */
 struct imsg_mbox_result {
 	enum mbox_op_error error;
 	uint32_t	count;	/* number of IMSG_MBOX_FETCH_META (or
 				 * equivalent, for a future op) messages that
 				 * preceded this one */
 
-	/*
-	 * RFC 7162: the mailbox's HIGHESTMODSEQ after this operation.
-	 * Always populated for STORE/EXPUNGE (the two operations that can
-	 * change it); left 0 for a plain FETCH, which never mutates
-	 * anything. listener.c is the one that decides whether/how to
-	 * surface it: EXPUNGE's tagged OK MAY include it (SS3.2.7, "If at
-	 * least one message got expunged and QRESYNC was enabled, the
-	 * server MUST send" it -- this implementation does so whenever
-	 * CONDSTORE is enabled at all, a safe superset, see cmd_expunge()'s
-	 * comment), STORE's tagged OK/NO doesn't need to (SS3.1.3's own
-	 * examples show it appearing on some responses and not others --
-	 * "presumably because this was the first CONDSTORE enabling
-	 * command", i.e. it's the same "first enabling command" case
-	 * covered by session_condstore_enable(), not something every STORE
-	 * repeats), and CLOSE's tagged OK MUST NOT include it (SS3.2.8,
-	 * explicit "MUST NOT ... as this might cause loss of
-	 * synchronization on the client" -- cmd_close()'s existing
-	 * was_close branch just never reads this field).
+	/* RFC 7162: mailbox HIGHESTMODSEQ after this operation. Always
+	 * populated for STORE/EXPUNGE; 0 for plain FETCH. listener.c
+	 * decides whether to surface it: EXPUNGE's tagged OK includes it
+	 * whenever CONDSTORE is enabled (SS3.2.7); STORE's tagged OK only
+	 * on the first CONDSTORE-enabling command (SS3.1.3); CLOSE's
+	 * tagged OK MUST NOT include it (SS3.2.8).
 	 */
 	uint64_t	highestmodseq;
 
-	/*
-	 * RFC 9051 SS7.1's COPYUID response code: "the UIDVALIDITY of the
-	 * destination mailbox". Populated only for COPY/MOVE (see imapd.h's
-	 * imsg_mbox_copy comment) -- v1 has no CREATE, so the destination is
-	 * always the same mailbox as the source (the currently selected
-	 * mailbox), making this identical to that mailbox's own UIDVALIDITY;
-	 * left 0 for FETCH/STORE/EXPUNGE/SEARCH, which have no COPYUID to
-	 * report. Same "populated for the operations that need it, 0
-	 * otherwise, listener.c decides what to do with it" split as
-	 * highestmodseq above.
+	/* RFC 9051 SS7.1 COPYUID response code: destination mailbox's
+	 * UIDVALIDITY. Populated only for COPY/MOVE; 0 for
+	 * FETCH/STORE/EXPUNGE/SEARCH.
 	 */
 	uint32_t	uidvalidity;
 };
 
 /*
- * IMSG_MBOX_STORE (listener -> store): RFC 9051 SS6.4.6 STORE command --
- * `store = "STORE" SP sequence-set SP store-att-flags`, `store-att-flags =
- * (["+" / "-"] "FLAGS" [".SILENT"]) SP (flag-list / (flag *(SP flag)))`.
- * Replies reuse IMSG_MBOX_FETCH_META (one per modified message, sent only
- * if !silent) and the terminal IMSG_MBOX_RESULT -- SS6.4.6 itself says
- * STORE's only response is "untagged responses: FETCH", the exact same
- * shape FETCH already produces ("* <seqno> FETCH (FLAGS (...))"), so
- * there was no reason to invent a second reply pair.
+ * IMSG_MBOX_STORE (listener -> store): RFC 9051 SS6.4.6 STORE. Replies
+ * reuse IMSG_MBOX_FETCH_META (one per modified message, only if
+ * !silent) and the terminal IMSG_MBOX_RESULT, SS6.4.6's only response
+ * is an untagged FETCH, the same shape FETCH itself produces.
  *
- * System flags are a fixed 5-bit set (v1 supports exactly the five RFC
- * 9051 SS2.3.2 system flags: \Answered \Flagged \Deleted \Seen \Draft --
- * \Recent is explicitly excluded from the `flag` ABNF production itself,
- * and any other "\"-prefixed token is a `flag-extension` this server
- * doesn't define, so listener.c rejects both before ever building this
- * struct). Keywords (arbitrary non-"\" atoms, SS2.3.2's `flag-keyword`)
- * are carried separately as a comma-separated list -- matching the
- * index's own on-disk keyword delimiter so store.c can merge them
- * directly without a format conversion; listener.c
- * rejects any client-supplied keyword containing ':' or ',' (both valid
- * in IMAP's `atom` grammar, but the index format has no escaping
- * mechanism for its own field/record delimiters -- see cmd_store_cmd()'s
- * comment) rather than silently corrupting the index.
+ * System flags are the fixed 5-bit RFC 9051 SS2.3.2 set (\Answered
+ * \Flagged \Deleted \Seen \Draft; \Recent and any flag-extension are
+ * rejected by listener.c before this struct is built). Keywords are
+ * carried as a comma-separated list matching the index's own
+ * delimiter; listener.c rejects a keyword containing ':' or ',' (legal
+ * IMAP atoms, but the index format has no escaping for its own
+ * delimiters).
  */
 #define MBOX_FLAG_ANSWERED	(1U << 0)
 #define MBOX_FLAG_FLAGGED	(1U << 1)
@@ -1621,9 +752,9 @@ struct imsg_mbox_result {
 #define MBOX_FLAG_SEEN		(1U << 3)
 #define MBOX_FLAG_DRAFT		(1U << 4)
 
-#define MBOX_STORE_SET		0	/* FLAGS -- replace outright */
-#define MBOX_STORE_ADD		1	/* +FLAGS -- union in */
-#define MBOX_STORE_REMOVE	2	/* -FLAGS -- subtract out */
+#define MBOX_STORE_SET		0	/* FLAGS, replace outright */
+#define MBOX_STORE_ADD		1	/* +FLAGS, union in */
+#define MBOX_STORE_REMOVE	2	/* -FLAGS, subtract out */
 
 struct imsg_mbox_store {
 	uint32_t	seq_lo;
@@ -1631,36 +762,23 @@ struct imsg_mbox_store {
 	int		lo_is_star;
 	int		hi_is_star;
 	int		mode;		/* MBOX_STORE_* above */
-	int		silent;		/* 1 if the ".SILENT" suffix was given
-					 * -- suppress the untagged FETCH
-					 * response per message */
+	int		silent;		/* 1 = ".SILENT", suppress the
+					 * untagged FETCH per message */
 	uint32_t	sysflags;	/* MBOX_FLAG_* bitmask named in this
-					 * STORE (the flags being set/added/
-					 * removed, not the message's
-					 * resulting flags -- store.c computes
-					 * that) */
+					 * STORE (flags being set/added/
+					 * removed, not the resulting flags) */
 	char		keywords[MBOX_FLAGS_MAX]; /* comma-separated keyword
 					 * atoms named in this STORE, "" if
 					 * none */
 
-	/*
-	 * RFC 7162 SS3.1.3 UNCHANGEDSINCE store-modifier. has_unchangedsince
-	 * distinguishes "not specified" from the legal value 0 (SS3.1.3
-	 * Example 8: "Use of UNCHANGEDSINCE with a modification sequence of
-	 * 0 always fails if the metadata item exists" -- a deliberate,
-	 * always-fails conditional test, not the same as omitting the
-	 * modifier entirely).
+	/* RFC 7162 SS3.1.3 UNCHANGEDSINCE; has_unchangedsince distinguishes
+	 * "not specified" from the legal (always-fails) value 0.
 	 */
 	int		has_unchangedsince;
 	uint64_t	unchangedsince;
 
-	/*
-	 * RFC 9051 SS6.4.9: same UID-vs-sequence-number resolution switch as
-	 * imsg_mbox_fetch's by_uid, for UID STORE. meta.uid (in the shared
-	 * imsg_mbox_fetch_meta STORE echo) is already unconditionally
-	 * populated regardless of this flag -- only listener.c's decision to
-	 * *print* it changes based on whether the in-flight command was UID
-	 * STORE (see struct session's cmd_by_uid in listener.c).
+	/* RFC 9051 SS6.4.9: UID-vs-sequence-number switch for UID STORE,
+	 * same as imsg_mbox_fetch's by_uid.
 	 */
 	int		by_uid;
 };
@@ -1668,13 +786,9 @@ struct imsg_mbox_store {
 /*
  * IMSG_MBOX_STORE_MODIFIED (store -> listener, zero or more, only when
  * req->has_unchangedsince, before the terminal IMSG_MBOX_RESULT): one
- * message whose mod-sequence exceeded the UNCHANGEDSINCE value, so the
- * requested STORE operation was *not* performed for it (RFC 7162 SS3.1.3:
- * "the message number (or unique identifier in the case of the UID STORE
- * command) is added to the list of messages that failed the UNCHANGEDSINCE
- * test"). listener.c range-compacts these into the tagged response's
- * MODIFIED response code, same compaction helper SEARCH/VANISHED already
- * use.
+ * message whose mod-sequence exceeded UNCHANGEDSINCE, so the STORE was
+ * not performed for it (RFC 7162 SS3.1.3). listener.c range-compacts
+ * these into the tagged response's MODIFIED response code.
  */
 struct imsg_mbox_store_modified {
 	uint32_t	seqno;
@@ -1683,47 +797,27 @@ struct imsg_mbox_store_modified {
 
 /*
  * IMSG_MBOX_EXPUNGE (listener -> store) / IMSG_MBOX_EXPUNGED (store ->
- * listener, one per removed message, streamed in the order store.c
- * actually removes them) / IMSG_MBOX_RESULT (store -> listener, exactly
- * once, terminal -- same generic completion signal FETCH/STORE already
- * use).
+ * listener, one per removed message, in removal order) / IMSG_MBOX_RESULT
+ * (store -> listener, terminal).
  *
- * RFC 9051 SS6.4.3: EXPUNGE "permanently removes all messages that have
- * the \Deleted flag set from the currently selected mailbox," sending one
- * untagged EXPUNGE response per removed message before the tagged OK.
- * SS7.5.1 (the EXPUNGE response itself): "The message sequence number for
- * each successive message in the mailbox is immediately decremented by 1"
- * -- so which sequence number gets reported for each removal depends on
- * removal order; this server removes lowest-numbered first ("a 'lower to
- * higher' server," SS7.5.1's own term), matching SS6.4.3's own worked
- * example exactly (message 3, then 3 again, then 5, then 8, for original
- * positions 3/4/7/11) -- see store.c's handle_mbox_expunge() for the
- * compaction algorithm that produces those numbers.
+ * RFC 9051 SS6.4.3 EXPUNGE removes all \Deleted messages, one untagged
+ * EXPUNGE per removal before the tagged OK. Per SS7.5.1's "sequence
+ * number immediately decremented by 1" rule, this server removes
+ * lowest-numbered first (a "lower to higher" server), matching SS6.4.3's
+ * own worked example; see store.c's handle_mbox_expunge() for the
+ * compaction algorithm.
  *
- * silent exists so CLOSE (RFC 9051 SS6.4.1: "permanently removes all
- * messages that have the \Deleted flag set ... No untagged EXPUNGE
- * responses are sent") can reuse this exact request/reply pair instead of
- * inventing a second one -- the same ".SILENT"-suffix pattern STORE
- * already uses for the same "same operation, client doesn't want the
- * per-message notifications" reason.
+ * silent lets CLOSE (SS6.4.1: no untagged EXPUNGE responses) reuse this
+ * request/reply pair, same ".SILENT" pattern as STORE.
  */
 struct imsg_mbox_expunge {
 	int		silent;	/* 1 for CLOSE, 0 for a real EXPUNGE command */
 
-	/*
-	 * RFC 9051 SS6.4.9's second UID command form: "the UID command takes
-	 * an EXPUNGE command with an extra parameter that specifies a
-	 * sequence set of UIDs to operate on... permanently removes all
-	 * messages that have both the \Deleted flag set and a UID that is
-	 * included in the specified sequence set... If a message either does
-	 * not have the \Deleted flag set or has a UID that is not included in
-	 * the specified sequence set, it is not affected." by_uid is never
-	 * set together with silent=1 -- CLOSE has no UID-restricted form
-	 * (there is no "UID CLOSE"), only plain EXPUNGE does. seq_lo/seq_hi/
-	 * lo_is_star/hi_is_star mirror imsg_mbox_fetch's own naming exactly
-	 * (same seq-range/UID-range resolution convention throughout this
-	 * header); meaningless when !by_uid, since a plain EXPUNGE takes no
-	 * arguments at all (RFC 9051 SS6.4.3: "Arguments: none").
+	/* RFC 9051 SS6.4.9's UID EXPUNGE form: only \Deleted messages whose
+	 * UID is in [seq_lo, seq_hi] are removed. Never set together with
+	 * silent=1 (no "UID CLOSE"). seq_lo/seq_hi/lo_is_star/hi_is_star
+	 * mirror imsg_mbox_fetch's naming; meaningless when !by_uid (plain
+	 * EXPUNGE takes no arguments).
 	 */
 	int		by_uid;
 	uint32_t	seq_lo;
@@ -1733,45 +827,29 @@ struct imsg_mbox_expunge {
 };
 
 struct imsg_mbox_expunged {
-	uint32_t	seqno;	/* the message's sequence number at the moment
-				 * of removal, per SS7.5.1's "immediately
-				 * decremented" rule -- NOT its UID, and NOT
-				 * its pre-EXPUNGE sequence number */
-	uint32_t	uid;	/* RFC 7162 addition: the same message's UID,
-				 * needed once QRESYNC is enabled -- SS3.2.10.2
-				 * replaces the untagged EXPUNGE (seqno-based)
-				 * response with VANISHED (UID-based) in that
-				 * case, and listener.c has no other way to
-				 * learn the removed message's UID (the index
-				 * line is already gone by the time this imsg
-				 * is sent -- see store.c's handle_mbox_
-				 * expunge()) */
+	uint32_t	seqno;	/* sequence number at the moment of removal
+				 * (SS7.5.1's "immediately decremented"
+				 * rule), not the UID, not the
+				 * pre-EXPUNGE sequence number */
+	uint32_t	uid;	/* RFC 7162: same message's UID, needed once
+				 * QRESYNC is enabled (SS3.2.10.2 replaces
+				 * EXPUNGE with VANISHED in that case); the
+				 * index line is already gone by the time
+				 * this is sent, so there's no other way to
+				 * learn it */
 };
 
 /*
- * IMSG_MBOX_COPY (listener -> store, RFC 9051 SS6.4.7 COPY) / IMSG_MBOX_MOVE
- * (listener -> store, SS6.4.8 MOVE) share this exact request shape -- same
- * "one struct, two message types, differ only in which store.c handler
- * fires" pattern imsg_mbox_expunge already established for EXPUNGE/CLOSE
- * (distinguished there by .silent; here by which IMSG_MBOX_* type arrived).
+ * IMSG_MBOX_COPY (RFC 9051 SS6.4.7) / IMSG_MBOX_MOVE (SS6.4.8) share this
+ * request shape, distinguished by which IMSG_MBOX_* type arrived (same
+ * pattern as EXPUNGE/CLOSE's .silent).
  *
- * destname: the destination mailbox, resolved and validated by listener.c's
- * copy_move_dispatch() exactly the way cmd_rename()'s oldname/newname
- * already are -- mailbox_name_is_inbox()/mailbox_name_valid() first, with
- * zero store round trip for a syntactically invalid name. Before flat
- * multi-mailbox support (RFC 9051 SS6.3.4-SS6.3.6) this struct had no
- * destination field at all: v1 had
- * no CREATE, so the destination was *always* the same mailbox as the
- * source (the currently selected mailbox, itself always INBOX) --
- * explicitly RFC-sanctioned even then (SS6.4.8: "moving a message to the
- * currently selected mailbox... is allowed when copying the message to the
- * currently selected mailbox is allowed"). Now that named mailboxes are
- * real, a genuinely different destination is real too; store.c's handle_
- * mbox_copy()/handle_mbox_move() still fast-path the "destname names the
- * mailbox already selected" case as a single-index operation identical to
- * that original v1 code, and only take the new two-index cross-mailbox
- * path (see those functions' own header comments, including the lock-
- * ordering discussion) when it genuinely differs.
+ * destname is validated by listener.c's copy_move_dispatch() the same
+ * way cmd_rename()'s oldname/newname are. store.c's handle_mbox_copy()/
+ * handle_mbox_move() fast-path destname == the already-selected mailbox
+ * as a single-index operation, and only take the cross-mailbox two-index
+ * path (see those functions' own comments, including lock ordering) when
+ * it genuinely differs.
  */
 struct imsg_mbox_copy {
 	int		by_uid;
@@ -1784,29 +862,17 @@ struct imsg_mbox_copy {
 
 /*
  * IMSG_MBOX_COPY_MAPPING (store -> listener, zero or more, before the
- * terminal IMSG_MBOX_RESULT): one message's COPYUID mapping -- RFC 9051
- * SS7.1's COPYUID response code carries "a UID set containing the UIDs of
- * the message(s) in the source mailbox that were copied... followed by
- * another UID set containing the UIDs assigned... in the destination
- * mailbox... in the order the message(s) was copied." Streamed one pair
- * per message, ascending, rather than pre-compacted into ranges here --
- * matching this header's own established "store streams raw values,
- * listener compacts into ranges at formatting time" split (format_seq_
- * list() already does exactly this for ESEARCH/MODIFIED); listener.c
- * accumulates src_uid/dest_uid into two parallel growable arrays and
- * range-compacts each independently once the terminal reply arrives.
+ * terminal IMSG_MBOX_RESULT): one message's COPYUID mapping (RFC 9051
+ * SS7.1). Streamed one pair per message, ascending; listener.c
+ * accumulates src_uid/dest_uid into parallel arrays and range-compacts
+ * each once the terminal reply arrives, same "store streams raw values,
+ * listener compacts" split as ESEARCH/MODIFIED.
  *
  * Used for both COPY and MOVE. For MOVE, listener.c also buffers each
- * IMSG_MBOX_EXPUNGED that arrives during the same round trip (rather than
- * writing it to the client immediately, which is what happens for a real
- * EXPUNGE/CLOSE) and flushes both buffers, in order, only once the
- * terminal reply arrives -- COPYUID first, then EXPUNGE/VANISHED -- per
- * SS6.4.8: "servers are also REQUIRED to send the COPYUID response code in
- * an untagged OK before sending EXPUNGE". This mirrors session_handle_
- * mbox_selected()'s existing vanished_ranges/qresync_fetches dual-buffer-
- * then-flush-in-fixed-order pattern for a QRESYNC SELECT resync -- same
- * underlying problem (control final wire order across two streamed
- * sub-types that arrive interleaved with other traffic), same solution.
+ * IMSG_MBOX_EXPUNGED from the same round trip and flushes both buffers
+ * in order. COPYUID first, then EXPUNGE/VANISHED, per SS6.4.8's
+ * requirement to send COPYUID before EXPUNGE. Mirrors session_handle_
+ * mbox_selected()'s dual-buffer-then-flush pattern for QRESYNC resync.
  */
 struct imsg_mbox_copy_mapping {
 	uint32_t	src_uid;
@@ -1815,146 +881,70 @@ struct imsg_mbox_copy_mapping {
 
 /*
  * IMSG_MBOX_APPEND (listener -> store) / IMSG_MBOX_APPENDED (store ->
- * listener, exactly once). APPEND handles exactly one message per
- * request in v1 (no [MULTIAPPEND]), so unlike FETCH/STORE/EXPUNGE there's
- * no per-message streaming reply -- one request, one reply.
+ * listener, exactly once).
  *
- * RFC 9051 SS6.3.12: `append = "APPEND" SP mailbox [SP flag-list] [SP
- * date-time] SP literal`. The literal -- the message body itself -- is
- * carried as variable-length trailing data appended directly after this
- * fixed struct within the SAME imsg, not as a separate message and not
- * fd-passed. Verified directly against the real imsg.c/imsg-buffer.c this
- * session: imsg_get_data() requires an *exact* length match (its own
- * source: `if (ibuf_size(imsg->buf) != len) { errno = EBADMSG; return
- * (-1); }`), so it cannot be used to read a fixed header out of a longer
- * imsg. imsg_get_buf() is the sequential-read alternative (no length
- * check, just `ibuf_get()`, which advances the ibuf's internal read
- * position); imsg_get_len() reflects bytes *remaining*, not total size,
- * because it calls ibuf_size(), which is literally `wpos - rpos`. So
- * store.c's handle_mbox_append() reads this struct with imsg_get_buf(),
- * then treats whatever imsg_get_len() reports afterward as the message
- * body length and reads that with a second imsg_get_buf() call.
+ * RFC 9051 SS6.3.12 append literal (the message body) is carried as
+ * variable-length trailing data on the SAME imsg after this fixed
+ * struct, not fd-passed: store.c's handle_mbox_append() reads the
+ * struct with imsg_get_buf(), then reads whatever imsg_get_len()
+ * reports afterward as the body (imsg_get_data() can't be used here,
+ * it requires an exact length match against the whole imsg).
  *
- * This one-message-one-imsg design only works because the message is
- * capped at APPEND_LITERAL_MAX (listener.c) to fit comfortably under
- * MAX_IMSGSIZE (16384, imsg.h) alongside this struct's own ~550 bytes --
- * see APPEND_LITERAL_MAX's comment in listener.c for the exact arithmetic
- * and the imsg_create() source check ("datalen += IMSG_HEADER_SIZE; if
- * (datalen > imsgbuf->maxsize) ... return NULL") it's based on. A message
- * larger than that needs real fd-passing -- the same mechanism BODY[]
- * FETCH still needs and doesn't have -- not implemented this pass;
- * listener.c rejects an oversized literal announcement with a plain NO
- * (RFC 9051 defines no response code for a size cap) before ever reading
- * it, rather than truncating or crashing.
+ * Only works because the message is capped at APPEND_LITERAL_MAX to
+ * fit under imsg's MAX_IMSGSIZE (16384) alongside this struct. A larger
+ * message needs real fd-passing, not implemented; listener.c rejects an
+ * oversized literal announcement with a plain NO before reading it.
  */
 struct imsg_mbox_append {
 	char		mailbox[MBOX_NAME_MAX];
-	uint32_t	sysflags;	/* MBOX_FLAG_* bitmask -- an omitted or
-					 * empty "()" flag-list both mean 0,
-					 * per SS6.3.12: "otherwise the flag
-					 * list of the resulting message is
-					 * set to 'empty' by default" */
+	uint32_t	sysflags;	/* MBOX_FLAG_* bitmask; an omitted or
+					 * empty flag-list means 0 (SS6.3.12) */
 	char		keywords[MBOX_FLAGS_MAX]; /* comma-separated, same
 					 * convention as imsg_mbox_store's */
-	int		has_date;	/* 0 -- SS6.3.12: "otherwise the
-					 * internal date... is set to the
-					 * current date and time" -- store.c
-					 * uses time(NULL) at delivery time
-					 * in that case */
+	int		has_date;	/* 0 = use current time at delivery
+					 * (SS6.3.12) */
 	int64_t		date;		/* Unix timestamp; meaningful only if
 					 * has_date */
 	uint32_t	msglen;		/* length of the trailing message
-					 * bytes -- redundant with what
-					 * imsg_get_len() reports after the
-					 * header is read, kept anyway as an
-					 * explicit value store.c cross-checks
-					 * against that, rather than trusting
-					 * a single source for something this
-					 * consequential (a mismatch likely
-					 * means a build-time struct-layout
-					 * skew between listener and store,
-					 * or a truncated imsg). */
+					 * bytes; redundant with imsg_get_len()
+					 * after the header is read, kept as
+					 * an explicit cross-check against
+					 * struct-layout skew or truncation */
 };
 
 struct imsg_mbox_appended {
-	enum mbox_op_error error;	/* MBOX_OP_ERR_NO_SUCH_MAILBOX
-					 * distinguishes "not INBOX" --
-					 * listener.c must send the tagged NO
-					 * with a "[TRYCREATE]" prefix per
-					 * SS6.3.12 -- from any other failure
-					 * (MBOX_OP_ERR_GENERIC: plain NO, no
-					 * response code) */
+	enum mbox_op_error error;	/* MBOX_OP_ERR_NO_SUCH_MAILBOX, "not
+					 * INBOX", tagged NO gets [TRYCREATE]
+					 * per SS6.3.12; MBOX_OP_ERR_GENERIC, 
+					 * plain NO */
 	uint32_t	uidvalidity;
-	uint32_t	uid;		/* the appended message's own UID --
-					 * together with uidvalidity, this is
-					 * SS7.1's APPENDUID response code */
+	uint32_t	uid;		/* appended message's UID; with
+					 * uidvalidity, this is SS7.1's
+					 * APPENDUID response code */
 	uint32_t	exists;		/* mailbox's new total message count,
-					 * so listener.c can send SS6.3.12's
-					 * "SHOULD notify the client
-					 * immediately via an untagged EXISTS
-					 * response" -- sent only if this
-					 * session currently has the mailbox
-					 * selected; see cmd_append()'s and
-					 * session_handle_mbox_appended()'s
-					 * comments */
+					 * for SS6.3.12's untagged EXISTS
+					 * notification, sent only if this
+					 * session has the mailbox selected */
 };
 
 /*
  * IMSG_MBOX_SEARCH (listener -> store) / IMSG_MBOX_SEARCH_MATCH (store ->
- * listener, one per matching message, streamed in ascending sequence
- * order -- same per-message streaming shape as IMSG_MBOX_FETCH_META and
- * IMSG_MBOX_EXPUNGED) / IMSG_MBOX_RESULT (store -> listener, exactly
- * once, terminal -- the same generic completion signal FETCH/STORE/
- * EXPUNGE/APPEND already reuse; `count` is the number of matches
- * streamed, which listener.c uses directly as COUNT if requested).
+ * listener, one per match, ascending sequence order) / IMSG_MBOX_RESULT
+ * (store -> listener, terminal; count is the number of matches, used
+ * directly as COUNT if requested).
  *
- * RFC 9051 SS6.4.4 SEARCH's `search-key` grammar nests arbitrarily (NOT
- * wraps one key, OR takes two, a parenthesized list ANDs N of them), so
- * the parsed criteria can't be a single fixed-size struct the way
- * STORE's flag-list or FETCH's fetch-att bitmask can. Instead,
- * listener.c's parse_search_key()/parse_search_key_list() compile the
- * whole search-program into a flat postfix (reverse Polish) array of
- * struct search_node, sent as variable-length trailing data on this
- * imsg after a small fixed header -- the same imsg_get_buf()/imsg_get_
- * len() wire technique IMSG_MBOX_APPEND already established and had
- * verified against the real imsg-buffer.c this project (see that
- * struct's own comment above): the header's nnodes times sizeof(struct
- * search_node) tells store_dispatch() exactly how many trailing bytes
- * to expect. SEARCH_PROGRAM_MAX_NODES bounds both the array's wire size
- * (comfortably under imsg's MAX_IMSGSIZE even with every node using its
- * full fixed-size keyword field) and store.c's postfix-evaluation stack
- * depth -- generous for a hand-typed v1 query, not a hard IMAP-mandated
- * ceiling; listener.c rejects a query that compiles to more nodes than
- * this with a plain BAD ("search criteria too complex"), the same "cap
- * and refuse cleanly" choice APPEND makes for an oversized message.
- *
- * v1 scope, matching this project's smaller-feature-set design
- * philosophy: search keys that need actual message content or headers
- * -- BCC/BODY/CC/FROM/HEADER/SENTBEFORE/
- * SENTON/SENTSINCE/SUBJECT/TEXT/TO -- are rejected by listener.c's
- * parser with a specific NO before this imsg is ever built, the same
- * "recognized, can't do it right now" category FETCH's BODY[] rejection
- * already uses; store.c never sees a SEARCH_OP_* for any of them because
- * none exists. The "UID SEARCH" command-level wrapper (RFC 9051
- * SS6.4.9's generic UID prefix, which would make ESEARCH's data refer to
- * UIDs instead of sequence numbers) is also out of scope this pass --
- * IMSG_MBOX_SEARCH_MATCH always carries both seqno and uid, but
- * listener.c's v1 ESEARCH formatter only ever uses seqno. The "UID
- * <sequence-set>" *search key* (filtering by UID range, one of many
- * possible criteria within an ordinary sequence-number-space SEARCH) is
- * unrelated to that command-level wrapper and is fully supported
- * (SEARCH_OP_UIDSET) -- SS6.4.4's own example list includes it as a
- * plain search key, not as a UID-command variant.
- *
- * Exactly one sequence-set range per SEQSET/UIDSET node (no internal
- * comma-separated list) -- the same v1 restriction already established
- * for FETCH/STORE's own sequence-set argument (see imsg_mbox_fetch's
- * comment above); listener.c rejects a comma inside a SEARCH sequence-
- * set token before ever building a node for it.
+ * RFC 9051 SS6.4.4 SEARCH's search-key grammar nests arbitrarily, so it
+ * can't be a single fixed-size struct. listener.c's parse_search_key()/
+ * parse_search_key_list() compile the whole search-program into a flat
+ * postfix array of struct search_node, sent as variable-length trailing
+ * data after a small fixed header, same imsg_get_buf()/imsg_get_len()
+ * technique as IMSG_MBOX_APPEND. SEARCH_PROGRAM_MAX_NODES bounds both
+ * wire size and evaluation stack depth; an oversized query gets a plain
+ * BAD.
  */
 #define SEARCH_PROGRAM_MAX_NODES	100
 #define SEARCH_KEYWORD_MAX		64	/* one flag-keyword atom, not
-						 * a list -- MBOX_FLAGS_MAX is
+						 * a list, MBOX_FLAGS_MAX is
 						 * sized for STORE's comma-
 						 * joined keyword *list* and
 						 * would be the wrong constant
@@ -1986,38 +976,23 @@ struct imsg_mbox_appended {
 #define SEARCH_OP_AND		20	/* postfix binary combinator */
 #define SEARCH_OP_OR		21	/* postfix binary combinator */
 #define SEARCH_OP_NOT		22	/* postfix unary combinator */
-#define SEARCH_OP_MODSEQ	23	/* RFC 7162 SS3.1.5 -- operand: num
-					 * (mod-sequence-valzer threshold,
-					 * message matches if its own
-					 * mod-sequence is >= this). The
+#define SEARCH_OP_MODSEQ	23	/* RFC 7162 SS3.1.5, operand: num,
+					 * matches if the message's own
+					 * mod-sequence is >= this. The
 					 * optional <entry-name>/<entry-type-
-					 * req> prefix (SS3.1.5: "If the server
-					 * doesn't store separate mod-sequences
-					 * for different metadata items, it
-					 * MUST ignore <entry-name> and <entry-
-					 * type-req>") is parsed by listener.c's
-					 * parse_search_key() for syntax only
-					 * and never reaches this struct -- v1's
-					 * per-message mod-sequence (see store.c's
-					 * struct mbox_index comment) is exactly
-					 * the "doesn't store separate mod-
-					 * sequences per metadata item" case the
-					 * RFC anticipates, so there's nothing
-					 * for store.c to narrow by */
+					 * req> prefix is parsed by
+					 * listener.c for syntax only and
+					 * never reaches this struct */
 
 struct search_node {
 	int		op;		/* SEARCH_OP_* above */
 	int64_t		num;		/* BEFORE/ON/SINCE/LARGER/SMALLER/MODSEQ
 					 * operand */
-	uint32_t	seq_lo;		/* SEQSET/UIDSET operand -- see
-					 * imsg_mbox_fetch's seq_lo/seq_hi/
-					 * lo_is_star/hi_is_star comment for
-					 * the "*" resolution convention this
-					 * mirrors; store.c resolves it here
-					 * against its own live idx.nlines
-					 * (SEQSET) or highest in-use UID
-					 * (UIDSET) up front, once, before
-					 * scanning messages */
+	uint32_t	seq_lo;		/* SEQSET/UIDSET operand, same "*"
+					 * convention as imsg_mbox_fetch;
+					 * store.c resolves it against live
+					 * idx.nlines (SEQSET) or highest
+					 * in-use UID (UIDSET) up front */
 	uint32_t	seq_hi;
 	int		lo_is_star;
 	int		hi_is_star;
@@ -2033,54 +1008,29 @@ struct imsg_mbox_search {
 struct imsg_mbox_search_match {
 	uint32_t	seqno;
 	uint32_t	uid;
-	uint64_t	modseq;		/* RFC 7162 SS3.1.6: "If a client
-					 * specifies a MODSEQ criterion in a
-					 * SEARCH ... command and the server
-					 * returns a non-empty SEARCH result,
-					 * the server MUST also append ... the
-					 * highest mod-sequence for all messages
-					 * being returned." Always populated
-					 * (cheap, store.c already has it
-					 * per-message) -- listener.c only
-					 * tracks the running max across
-					 * matches when the client's search
-					 * program actually used SEARCH_OP_
-					 * MODSEQ, same "store always computes,
-					 * listener decides whether to use it"
-					 * split as imsg_mbox_fetch_meta.modseq */
+	uint64_t	modseq;		/* RFC 7162 SS3.1.6: highest
+					 * mod-sequence among returned
+					 * matches, required whenever the
+					 * client used a MODSEQ criterion.
+					 * Always populated (cheap);
+					 * listener.c tracks the running max
+					 * only when SEARCH_OP_MODSEQ was
+					 * actually used. */
 };
 
 /*
  * RFC 9051 SS6.3.13 (IDLE). IMSG_MBOX_IDLE_REFRESH (listener -> store, no
- * payload) / IMSG_MBOX_IDLE_UID (store -> listener, one per currently-
- * existing message, in ascending UID order) / IMSG_MBOX_IDLE_REFRESHED
- * (store -> listener, exactly once, terminal).
+ * payload) / IMSG_MBOX_IDLE_UID (store -> listener, one per existing
+ * message, ascending UID order) / IMSG_MBOX_IDLE_REFRESHED (store ->
+ * listener, terminal).
  *
- * Used two ways by listener.c: once, synchronously after "+ idling" is
- * sent, purely to seed s->idle_known_uids with a baseline (no diffing or
- * pushing yet, since there's nothing to diff against the first time); and
- * again, any time session_notify_idle_peers() (triggered by another same-
- * uid session's successful EXPUNGE/UID EXPUNGE/CLOSE-with-removal/APPEND/
- * MOVE) asks an idling session to recheck its mailbox. Both cases reuse
- * the identical request/reply shape -- only what listener.c does with the
- * result differs. store.c doesn't need to know or care which case this
- * is; it just reports current, authoritative state, via the same refresh_
- * index() helper handle_mbox_select() itself uses (including that
- * function's new/ directory scan for undiscovered message files), so an
- * idle-refresh is exactly as fresh as a fresh SELECT would be -- including
- * picking up mail placed directly in new/ by something other than this
- * daemon's own APPEND, if an idle-refresh happens to run after it landed.
- *
- * v1 scope decision (confirmed with the user): EXISTS and EXPUNGE only.
- * RFC 9051 SS6.3.13 says the server is merely "free to" send EXISTS/
- * EXPUNGE/FETCH while idling, none of the three is mandatory -- pushing
- * unsolicited FETCH (e.g. for a flag changed by another session) is
- * deferred to a follow-up pass, since it needs the same per-message
- * mod-sequence diffing this server already has for QRESYNC, just re-
- * triggered the same way, and RFC 9051 doesn't require it. That's why
- * this reply carries only the UID list (enough for listener.c to compute
- * seqno-based EXPUNGE lines and an EXISTS count) and not per-message
- * flags/modseq.
+ * Used two ways by listener.c: once, synchronously after "+ idling", to
+ * seed s->idle_known_uids with a baseline; and again whenever
+ * session_notify_idle_peers() (triggered by another session's
+ * EXPUNGE/APPEND/MOVE) asks an idling session to recheck. Both reuse the
+ * same shape; store.c just reports current state via the same
+ * refresh_index() helper handle_mbox_select() uses, so an idle-refresh
+ * is as fresh as a fresh SELECT.
  */
 struct imsg_mbox_idle_uid {
 	uint32_t	uid;
@@ -2095,13 +1045,13 @@ struct imsg_mbox_idle_refreshed {
 };
 
 /*
- * RFC 9051 SS6.3.4/SS6.3.5 (CREATE/DELETE) and SS6.3.9 (LIST), this pass --
+ * RFC 9051 SS6.3.4/SS6.3.5 (CREATE/DELETE) and SS6.3.9 (LIST), this pass, 
  * flat, non-nested mailboxes as sibling subdirectories of the
  * session's own per-user maildir root; CREATE/DELETE/RENAME all reply with
  * the existing struct imsg_mbox_result, only "ok" meaningful). listener.c
  * has already validated the name syntactically (non-empty, not "INBOX",
  * no hierarchy-delimiter character, within MBOX_NAME_MAX) before either of
- * these is ever sent -- store.c re-validates independently rather than
+ * these is ever sent, store.c re-validates independently rather than
  * trusting that, the same defense-in-depth every other mailbox-name-
  * carrying imsg in this file already gets across the privsep boundary.
  */
@@ -2115,10 +1065,9 @@ struct imsg_mbox_delete {
 
 /*
  * RFC 9051 SS6.3.6 (RENAME). oldname/newname, not "mailbox"/"destination",
- * to avoid any confusion with imsg_mbox_copy's identically-purposed but
- * differently-named destination field -- RENAME's two names are peers
- * (both mailbox names in the same flat namespace), not a source-message-
- * range-plus-destination-mailbox pairing the way COPY/MOVE's is.
+ * to avoid confusion with imsg_mbox_copy's destination field, RENAME's
+ * two names are peers in the same flat namespace, not a source-range-
+ * plus-destination pairing like COPY/MOVE.
  */
 struct imsg_mbox_rename {
 	char		oldname[MBOX_NAME_MAX];
@@ -2126,15 +1075,10 @@ struct imsg_mbox_rename {
 };
 
 /*
- * RFC 9051 SS6.3.9 (LIST). One IMSG_MBOX_LIST_ITEM per real, on-disk
- * mailbox subdirectory store.c finds under the session's maildir root
- * (INBOX itself excluded -- see the IMSG_MBOX_LIST_ITEM enum comment
- * above), in no particular guaranteed order; listener.c does its own
- * wildcard matching (list_pattern_match()) against each name plus the
- * literal "INBOX" it already handles locally. Terminal reply reuses struct
- * imsg_mbox_result ("count" = number of names streamed, "ok" = 0 only on a
- * real I/O error opening the maildir root itself, not on finding zero
- * mailboxes -- an account with no named mailboxes yet is not a failure).
+ * RFC 9051 SS6.3.9 (LIST). One IMSG_MBOX_LIST_ITEM per on-disk mailbox
+ * subdirectory (INBOX excluded), unordered; listener.c does its own
+ * wildcard matching. Terminal reply reuses imsg_mbox_result ("ok" = 0
+ * only on a real I/O error, not on finding zero mailboxes).
  */
 struct imsg_mbox_list_item {
 	char		mailbox[MBOX_NAME_MAX];
@@ -2148,39 +1092,24 @@ int		 config_load(const char *, struct openimap_config
 int		 cmdline_symset(char *);
 
 /* parent.c */
-/*
- * First argument is the config file path, threaded through from main.c's
- * "conffile" local (default "/etc/imapd.conf" or -f's argument) -- added
- * for SIGHUP reload support: parent.c's sighup_handler() needs to re-run
- * config_load() against the exact same path main() originally used, and
- * had no way to reach it before (main()'s "conffile" was a local, never
- * passed down; parent.c only ever received the already-parsed struct).
+/* Config file path, threaded from main.c's conffile local, so
+ * sighup_handler() can re-run config_load() against the same path.
  */
 __dead void	 parent_main(const char *, int, char *[],
 		    struct openimap_config *);
 
-/*
- * listener.c / auth.c / store.c take no struct openimap_config * --
- * none of the three re-exec'd child roles read imapd.conf, and as of
- * this pass none of them receive it as a stub parameter either (an
- * earlier draft did, unused/misleadingly for listener and store, and
- * actively wrong for auth -- see the imsg_listener_init/imsg_auth_init
- * comment above). Each gets exactly the config it needs over its fd-3
- * channel from parent instead: IMSG_LISTENER_INIT, IMSG_AUTH_INIT,
- * IMSG_STORE_INIT respectively.
+/* listener.c / auth.c / store.c take no struct openimap_config *, each
+ * gets exactly the config it needs over its fd-3 channel instead:
+ * IMSG_LISTENER_INIT, IMSG_AUTH_INIT, IMSG_STORE_INIT respectively.
  */
 __dead void	 listener_main(void);
 __dead void	 auth_main(void);
 __dead void	 store_main(void);
 
-/*
- * imsg helpers shared by all roles -- see each role's setup_proc(). The
- * "handler" passed to imsgev_init() is the real libevent callback: it's
- * expected to do its own imsgbuf_read()/imsg_get() loop, matching
- * parent.c's parent_dispatch_child() as the reference shape. This mirrors
- * the imsg_event_add()-style pattern common to OpenBSD privsep daemons
- * (relayd, httpd) -- not itself quoted from any uploaded source file this
- * session, flagged as a standard-pattern choice, not a sourced one.
+/* imsg helpers shared by all roles. The "handler" passed to
+ * imsgev_init() is the libevent callback, expected to run its own
+ * imsgbuf_read()/imsg_get() loop (parent_dispatch_child() is the
+ * reference shape).
  */
 void		 imsgev_init(struct imsgev *, int,
 		    void (*)(int, short, void *), void *);
@@ -2188,15 +1117,9 @@ void		 imsgev_init_from_ibuf(struct imsgev *, const st
 		    void (*)(int, short, void *), void *);
 void		 imsgev_add(struct imsgev *);
 
-/*
- * Boot-time setup-loop helpers, sourced against smtpd.c's setup_proc()
- * shape: a freshly exec'd child blocks reading fd 3 for zero or more
- * IMSG_SETUP_PEER messages, then an IMSG_SETUP_DONE, and acks. v1's
- * boot-time wiring is 1:1 (listener gets exactly one peer, auth gets
- * exactly one) so setup_recv_one_peer() covers both; store's later,
- * per-session peer wiring happens post-boot, through the normal event
- * loop, not through these blocking helpers -- see store.c.
- */
+/* Boot-time setup-loop helpers: a freshly exec'd child blocks reading
+ * fd 3 for zero or more IMSG_SETUP_PEER messages, then IMSG_SETUP_DONE,
+ * and acks. */
 int		 setup_recv_one_peer(struct imsgbuf *);
 void		 setup_recv_done_and_ack(struct imsgbuf *);
 
blob - 55cbf3e19a7d339b4fb4f479c379a596688464fb
blob + 556721d06f0b054a849cf2b37d31b5a2697799e4
--- src/imsgev.c
+++ src/imsgev.c
@@ -4,7 +4,7 @@
  *
  * This file's name and its "struct imsgev" wrapper-around-imsgbuf+
  * event(3) concept match Eric Faurot's imsgev.c in OpenBSD's ldapd
- * (usr.sbin/ldapd/imsgev.c) -- not a generic/obvious name, so his
+ * (usr.sbin/ldapd/imsgev.c), not a generic/obvious name, so his
  * copyright is carried forward here even though this file's actual
  * function signatures and dispatch design (a caller-supplied raw
  * libevent handler, vs. ldapd's callback+needfd model) were written
@@ -24,7 +24,7 @@
  */
 
 /*
- * imsgev.c -- shared wrapper around imsgbuf + event(3), used by
+ * imsgev.c, shared wrapper around imsgbuf + event(3), used by
  * parent.c, listener.c, auth.c, and store.c.
  */
 
blob - 2cb78dfc0e89505b92cf682de96fbeced91d8781
blob + 6d496e3ee37c7d87346833e35d43c6e96ce2e0d4
--- src/index.c
+++ src/index.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* index.c -- the maildir index file format: load/save/append, QRESYNC resync, and vanished-UID tracking. */
+/* index.c, the maildir index file format: load/save/append, QRESYNC resync, and vanished-UID tracking. */
 
 #include <sys/types.h>
 #include <sys/file.h>
@@ -200,7 +200,7 @@ index_parse_line(const char *line, struct index_rec *r
 			return (-1);
 		}
 	} else {
-		/* no MODSEQ field -- pre-CONDSTORE line (struct index_rec backward-compatibility) */
+		/* no MODSEQ field, pre-CONDSTORE line (struct index_rec backward-compatibility) */
 		if (strlcpy(rec->keywords, p, sizeof(rec->keywords)) >=
 		    sizeof(rec->keywords)) {
 			log_warnx("session %u: keywords too long in index "
@@ -228,7 +228,7 @@ index_max_uid(struct mbox_index *idx)
 	errno = 0;
 	v = (uint32_t)strtoul(line, &ep, 10);
 	if (*ep != ':')
-		return (0);	/* corrupt last line -- treat as "no UIDs in use" rather than guessing */
+		return (0);	/* corrupt last line, treat as "no UIDs in use" rather than guessing */
 	return (v);
 }
 
@@ -281,7 +281,7 @@ send_vanished_range(const struct mbox_index *idx, uint
 	}
 }
 
-/* Linear scan for a basename already in the index; O(n) per lookup, fine for v1's modest-mailbox-size scope. */
+/* Linear scan for a basename already in the index; O(n) per lookup, fine for modest-mailbox-size scope. */
 int
 index_has_basename(struct mbox_index *idx, const char *basename)
 {
@@ -435,15 +435,15 @@ qresync_send_resync(const struct imsg_mbox_select *req
 		uid_hi = idx->uidnext - 1;
 	}
 	if (uid_hi < uid_lo)
-		return;		/* empty requested range -- nothing to do */
+		return;		/* empty requested range, nothing to do */
 
-	/* single forward pass; want tracks the lowest UID not yet accounted for -- a gap before it means vanished */
+	/* single forward pass; want tracks the lowest UID not yet accounted for, a gap before it means vanished */
 	want = uid_lo;
 	for (i = 0; i < idx->nlines && want <= uid_hi; i++) {
 		struct index_rec	rec;
 
 		if (index_parse_line(idx->lines[i], &rec) == -1)
-			continue;	/* corrupt line, already logged -- not reported either way */
+			continue;	/* corrupt line, already logged, not reported either way */
 		if (rec.uid < want)
 			continue;	/* below the requested range, or already accounted for */
 		if (rec.uid > uid_hi)
@@ -511,15 +511,15 @@ refresh_index(struct mbox_index *idx, int fd)
 	dp = opendir("new");
 	if (dp == NULL) {
 		if (errno == ENOENT)
-			return (0);	/* no new/ yet on a never-used mailbox -- not an error */
+			return (0);	/* no new/ yet on a never-used mailbox, not an error */
 		log_warn("session %u: opendir new", session_id);
 		index_free(idx);
 		return (-1);
 	}
 	while ((de = readdir(dp)) != NULL) {
 		if (de->d_name[0] == '.')
-			continue;	/* ".", "..", and dotfiles -- maildir delivery never creates the latter */
-		/* never index a filename with ':' or newline -- would corrupt the index line format */
+			continue;	/* ".", "..", and dotfiles, maildir delivery never creates the latter */
+		/* never index a filename with ':' or newline, would corrupt the index line format */
 		if (strpbrk(de->d_name, ":\r\n") != NULL) {
 			log_warnx("session %u: skipping new/ file with unsafe "
 			    "name (contains ':' or newline): %s", session_id,
blob - b418cd4079c0ac53d846b137d7487da81c37c0d0
blob + c4aa587b33bd334d3a01ebbe554cc1e058b1c0f4
--- src/listener.c
+++ src/listener.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* listener.c -- protocol/network process: client sockets, IMAP dispatch, TLS. */
+/* listener.c, protocol/network process: client sockets, IMAP dispatch, TLS. */
 
 #include <sys/types.h>
 #include <sys/queue.h>
@@ -58,7 +58,7 @@ static struct event	 ev_accept_cleartext[LISTENER_MAX_
 static struct event	 ev_accept_tls[LISTENER_MAX_ADDRS];
 
 static struct tls_config	*listener_tls_config;
-struct tls		*listener_tls_ctx;	/* NULL if TLS setup failed -- degrades to no-TLS, not fatal */
+struct tls		*listener_tls_ctx;	/* NULL if TLS setup failed, degrades to no-TLS, not fatal */
 
 /* Matches parent.c's send_tls_certs() read buffer size. */
 #define TLS_CERT_MAX	8192
@@ -250,19 +250,19 @@ listener_main(void)
 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
 		fatal("cannot drop privileges to _imapd");
 
-	/* Failure here isn't fatal -- degrades to no-TLS, checked via listener_tls_ctx == NULL below. */
+	/* Failure here isn't fatal, degrades to no-TLS, checked via listener_tls_ctx == NULL below. */
 	if (cert_len == 0 || key_len == 0) {
-		log_warnx("listener: no TLS cert/key received -- TLS "
+		log_warnx("listener: no TLS cert/key received, TLS "
 		    "disabled for this run");
 	} else if ((listener_tls_config = tls_config_new()) == NULL) {
-		log_warnx("listener: tls_config_new failed -- TLS disabled");
+		log_warnx("listener: tls_config_new failed, TLS disabled");
 	} else if ((listener_tls_ctx = tls_server()) == NULL) {
-		log_warnx("listener: tls_server failed -- TLS disabled");
+		log_warnx("listener: tls_server failed, TLS disabled");
 		tls_config_free(listener_tls_config);
 		listener_tls_config = NULL;
 	} else if (tls_config_set_ciphers(listener_tls_config, "secure")
 	    != 0) {
-		log_warnx("listener: tls_config_set_ciphers: %s -- "
+		log_warnx("listener: tls_config_set_ciphers: %s, "
 		    "TLS disabled", tls_config_error(listener_tls_config));
 		tls_free(listener_tls_ctx);
 		tls_config_free(listener_tls_config);
@@ -271,7 +271,7 @@ listener_main(void)
 	} else if (tls_config_set_keypair_mem(listener_tls_config,
 	    (const uint8_t *)cert_buf, cert_len,
 	    (const uint8_t *)key_buf, key_len) != 0) {
-		log_warnx("listener: tls_config_set_keypair_mem: %s -- "
+		log_warnx("listener: tls_config_set_keypair_mem: %s, "
 		    "TLS disabled", tls_config_error(listener_tls_config));
 		tls_free(listener_tls_ctx);
 		tls_config_free(listener_tls_config);
@@ -279,7 +279,7 @@ listener_main(void)
 		listener_tls_config = NULL;
 	} else if (tls_configure(listener_tls_ctx, listener_tls_config)
 	    != 0) {
-		log_warnx("listener: tls_configure: %s -- TLS disabled",
+		log_warnx("listener: tls_configure: %s, TLS disabled",
 		    tls_error(listener_tls_ctx));
 		tls_free(listener_tls_ctx);
 		tls_config_free(listener_tls_config);
@@ -295,7 +295,7 @@ listener_main(void)
 
 	imsgev_init(&iev_auth, peer_fd, listener_dispatch_auth, NULL);
 
-	/* Reuses fd 3's populated ibuf3 -- a fresh imsgbuf_init() would drop buffered bytes. */
+	/* Reuses fd 3's populated ibuf3, a fresh imsgbuf_init() would drop buffered bytes. */
 	imsgev_init_from_ibuf(&iev_parent, &ibuf3, listener_dispatch_parent,
 	    NULL);
 
@@ -352,7 +352,7 @@ listener_accept(int fd, short event, void *arg)
 	if (s->implicit_tls) {
 		if (listener_tls_ctx == NULL) {
 			log_warnx("session %u: implicit-TLS port, but TLS "
-			    "isn't configured -- closing", s->id);
+			    "isn't configured, closing", s->id);
 			session_teardown(s);
 			return;
 		}
@@ -528,7 +528,7 @@ session_dispatch_client(int fd, short event, void *arg
 			if (s->inbuflen < 2)
 				break;	/* trailing CRLF hasn't arrived yet */
 			if (s->inbuf[0] != '\r' || s->inbuf[1] != '\n') {
-				/* no reliable resync point -- give up */
+				/* no reliable resync point, give up */
 				log_warnx("session %u: expected CRLF after "
 				    "literal data, closing", s->id);
 				session_teardown(s);
@@ -574,9 +574,9 @@ session_dispatch_client(int fd, short event, void *arg
 			alive = session_handle_line(s, s->inbuf);
 		}
 		if (alive == 0)
-			return;	/* s was torn down (LOGOUT) -- do not touch */
+			return;	/* s was torn down (LOGOUT), do not touch */
 
-		/* cmd_starttls() zeroes inbuflen to discard pipelined plaintext -- clamp to avoid underflow. */
+		/* cmd_starttls() zeroes inbuflen to discard pipelined plaintext, clamp to avoid underflow. */
 		if (consumed > s->inbuflen)
 			consumed = s->inbuflen;
 
@@ -585,18 +585,18 @@ session_dispatch_client(int fd, short event, void *arg
 	}
 
 	if (s->inbuflen == sizeof(s->inbuf)) {
-		/* Buffer full, no CRLF -- matches the spirit of RFC 9051 SS7.1.3's example text. */
+		/* Buffer full, no CRLF, matches the spirit of RFC 9051 SS7.1.3's example text. */
 		static const char bad[] = "* BAD command line too long\r\n";
 
 		log_warnx("session %u: command line too long, closing",
 		    s->id);
-		/* was a raw write(2) -- wrong on a TLS session, bytes would land unencrypted on the wire */
+		/* was a raw write(2), wrong on a TLS session, bytes would land unencrypted on the wire */
 		session_write(s, bad, sizeof(bad) - 1);
 		session_teardown(s);
 	}
 }
 
-/* Blocking write(2)/tls_write(): v1 simplification (short fixed responses); never tears s down on failure. */
+/* Blocking write(2)/tls_write(): never tears s down on failure. */
 #define SESSION_WRITE_POLL_TIMEOUT_MS	5000
 
 void
@@ -710,14 +710,7 @@ session_reply(struct session *s, const char *tag, cons
 		return;
 	if ((size_t)len >= sizeof(buf)) {
 		/*
-		 * text can legitimately exceed this buffer (e.g. COPYUID's
-		 * sequence-set text, up to ~4KB) -- snprintf(3) already
-		 * truncated safely, but a bare truncation drops the trailing
-		 * CRLF, and this project's clients are strictly line-based
-		 * (RFC 9051 SS2.2.1). A response missing its terminator would
-		 * merge with whatever comes next, corrupting every later
-		 * response on this connection. Force the last two bytes back
-		 * to CRLF rather than send a non-terminated line.
+		 * Force the last two bytes back to CRLF rather than send a non-terminated line.
 		 */
 		buf[sizeof(buf) - 3] = '\r';
 		buf[sizeof(buf) - 2] = '\n';
@@ -806,7 +799,7 @@ parse_command_line(char *line, char **tag, char **name
 	return (0);
 }
 
-/* True only for the post-auth async-round-trip states; pre-auth states (AUTHENTICATING/STORE_PENDING) deliberately excluded -- see SESSION_CMD_QUEUE_MAX's comment. */
+/* True only for the post-auth async-round-trip states; pre-auth states (AUTHENTICATING/STORE_PENDING) deliberately excluded, see SESSION_CMD_QUEUE_MAX's comment. */
 static int
 session_is_busy(const struct session *s)
 {
@@ -829,7 +822,7 @@ session_is_busy(const struct session *s)
 	}
 }
 
-/* Appends a pipelined line to s->cmd_queue; returns 0 on a full queue or strdup(3) failure -- caller must teardown. */
+/* Appends a pipelined line to s->cmd_queue; returns 0 on a full queue or strdup(3) failure, caller must teardown. */
 static int
 session_enqueue_cmd(struct session *s, const char *line)
 {
@@ -868,7 +861,7 @@ session_dequeue_next(struct session *s)
 	return (1);
 }
 
-/* Returns 1 if the session is still alive, 0 if torn down (LOGOUT) -- caller must not touch *s* if 0. */
+/* Returns 1 if the session is still alive, 0 if torn down (LOGOUT), caller must not touch *s* if 0. */
 int
 session_handle_line(struct session *s, char *line)
 {
@@ -882,7 +875,7 @@ session_handle_line(struct session *s, char *line)
 		return (1);
 	}
 
-	/* Checked once here, not per strlcpy(3) site -- an overlong tag must not be echoed back truncated. */
+	/* Checked once here, not per strlcpy(3) site, an overlong tag must not be echoed back truncated. */
 	if (strlen(tag) >= IMAP_TAG_MAX) {
 		session_reply(s, "*", "BAD", "Tag too long");
 		return (1);
@@ -901,7 +894,7 @@ session_handle_line(struct session *s, char *line)
 		return (1);
 	}
 	if (!(imap_cmds[i].states & (1U << s->state))) {
-		/* RFC 9051 SS3: BAD or NO for wrong-state command -- BAD chosen here. */
+		/* RFC 9051 SS3: BAD or NO for wrong-state command, BAD chosen here. */
 		session_reply(s, tag, "BAD",
 		    "Command not permitted in this state");
 		return (1);
@@ -959,11 +952,7 @@ listener_dispatch_auth(int fd, short event, void *arg)
 				    "[AUTHENTICATIONFAILED] authentication failed");
 				break;
 			}
-			/* task #321: parent spawns the store child off auth's
-			 * own direct IMSG_AUTH_CRED now, not a request relayed
-			 * from here -- this just tracks state while we wait
-			 * for parent's IMSG_SETUP_PEER (success) or
-			 * IMSG_STORE_FORK (failure). */
+			
 			s->uid = res.uid;	/* session_notify_idle_peers() groups by this */
 			s->state = SESSION_STORE_PENDING;
 			break;
@@ -990,25 +979,25 @@ listener_reload_tls(const char *cert_buf, size_t cert_
 	struct tls_config	*old_config;
 
 	if (cert_len == 0 || key_len == 0) {
-		log_warnx("listener: SIGHUP reload: empty cert or key -- "
+		log_warnx("listener: SIGHUP reload: empty cert or key, "
 		    "keeping previous TLS configuration");
 		return;
 	}
 
 	if ((new_config = tls_config_new()) == NULL) {
-		log_warnx("listener: SIGHUP reload: tls_config_new failed -- "
+		log_warnx("listener: SIGHUP reload: tls_config_new failed, "
 		    "keeping previous TLS configuration");
 		return;
 	}
 	if ((new_ctx = tls_server()) == NULL) {
-		log_warnx("listener: SIGHUP reload: tls_server failed -- "
+		log_warnx("listener: SIGHUP reload: tls_server failed, "
 		    "keeping previous TLS configuration");
 		tls_config_free(new_config);
 		return;
 	}
 	if (tls_config_set_ciphers(new_config, "secure") != 0) {
 		log_warnx("listener: SIGHUP reload: tls_config_set_ciphers: "
-		    "%s -- keeping previous TLS configuration",
+		    "%s, keeping previous TLS configuration",
 		    tls_config_error(new_config));
 		tls_free(new_ctx);
 		tls_config_free(new_config);
@@ -1017,14 +1006,14 @@ listener_reload_tls(const char *cert_buf, size_t cert_
 	if (tls_config_set_keypair_mem(new_config, (const uint8_t *)cert_buf,
 	    cert_len, (const uint8_t *)key_buf, key_len) != 0) {
 		log_warnx("listener: SIGHUP reload: tls_config_set_keypair_"
-		    "mem: %s -- keeping previous TLS configuration",
+		    "mem: %s, keeping previous TLS configuration",
 		    tls_config_error(new_config));
 		tls_free(new_ctx);
 		tls_config_free(new_config);
 		return;
 	}
 	if (tls_configure(new_ctx, new_config) != 0) {
-		log_warnx("listener: SIGHUP reload: tls_configure: %s -- "
+		log_warnx("listener: SIGHUP reload: tls_configure: %s, "
 		    "keeping previous TLS configuration", tls_error(new_ctx));
 		tls_free(new_ctx);
 		tls_config_free(new_config);
@@ -1229,7 +1218,7 @@ session_teardown(struct session *s)
 		close(s->client_fd);
 	}
 
-	/* All NULL-safe -- can still be set on a mid-stream teardown (FETCH/SEARCH/etc. in flight). */
+	/* All NULL-safe, can still be set on a mid-stream teardown (FETCH/SEARCH/etc. in flight). */
 	free(s->literal_buf);
 	free(s->search_matches);
 	free(s->vanished_ranges);
blob - 22356e4c4e6c46c8052d0c9f57aa0dff3df46b29
blob + d646dbe0c9832601d042c2b1c7c8a0123ace2182
--- src/listener.h
+++ src/listener.h
@@ -15,17 +15,9 @@
  */
 
 /*
- * listener.h -- internal, listener-process-only shared declarations.
- *
- * Not installed, not part of imapd's public wire protocol (that's
- * imapd.h) -- this exists solely so listener.c's core (the accept loop
- * and session dispatch machinery) and the eight files split out of what
- * used to be one 10,619-line listener.c (auth_cmd.c, mailbox_cmd.c,
- * append_cmd.c, fetch_cmd.c, search_cmd.c, store_cmd.c, store_ipc.c, and
- * listener.c itself) can all see struct session, enum session_state,
- * and each other's entry points. Nothing in here crosses process
- * boundaries or gets seen by auth.c/store.c/parent.c -- those live
- * entirely behind imapd.h's imsg wire structs instead.
+ * listener.h, internal, listener-process-only shared declarations, so
+ * listener.c's split files can all see struct session, enum
+ * session_state, and each other's entry points.
  */
 
 #ifndef LISTENER_H
@@ -41,177 +33,106 @@
 enum session_state {
 	SESSION_NOT_AUTH,
 	SESSION_AUTHENTICATING,	/* IMSG_AUTH_REQUEST sent, awaiting reply */
-	SESSION_STORE_PENDING,	/* auth succeeded; awaiting parent's store-child handshake (task #321) */
+	SESSION_STORE_PENDING,	/* auth succeeded; awaiting parent's
+				 * store-child handshake */
 	SESSION_AUTHENTICATED,
-	SESSION_SELECTING,	/* IMSG_MBOX_SELECT sent, awaiting reply --
-				 * see cmd_select()/session_store_dispatch() */
+	SESSION_SELECTING,	/* IMSG_MBOX_SELECT sent, awaiting reply */
 	SESSION_SELECTED,
 	SESSION_FETCHING,	/* IMSG_MBOX_FETCH sent, awaiting the
 				 * IMSG_MBOX_FETCH_META stream + terminal
-				 * IMSG_MBOX_RESULT -- see cmd_fetch()/
-				 * session_handle_mbox_result() */
-	SESSION_STORING,	/* IMSG_MBOX_STORE sent, awaiting the same
-				 * IMSG_MBOX_FETCH_META stream + terminal
-				 * IMSG_MBOX_RESULT shape SESSION_FETCHING
-				 * uses -- see cmd_store_cmd()'s comment on
-				 * why STORE reuses FETCH's reply types */
-	SESSION_EXPUNGING,	/* IMSG_MBOX_EXPUNGE sent (by EXPUNGE itself,
-				 * or by CLOSE with silent=1), awaiting the
-				 * IMSG_MBOX_EXPUNGED stream + terminal
-				 * IMSG_MBOX_RESULT -- see cmd_expunge()/
-				 * cmd_close()/session_handle_mbox_result() */
+				 * IMSG_MBOX_RESULT */
+	SESSION_STORING,	/* IMSG_MBOX_STORE sent; reuses FETCH's
+				 * reply shape (see cmd_store_cmd()) */
+	SESSION_EXPUNGING,	/* IMSG_MBOX_EXPUNGE sent (EXPUNGE, or CLOSE
+				 * with silent=1), awaiting IMSG_MBOX_EXPUNGED
+				 * stream + terminal IMSG_MBOX_RESULT */
 	SESSION_APPENDING,	/* IMSG_MBOX_APPEND sent, awaiting the single
-				 * terminal IMSG_MBOX_APPENDED reply -- see
-				 * cmd_append()/session_finish_append()/
-				 * session_handle_mbox_appended(). Distinct
-				 * from the *client-literal-read* phase that
-				 * precedes it (s->literal_pending -- see that
-				 * field's comment): this state only covers
-				 * the store round trip, once the full
-				 * message has already been read off the
-				 * wire. */
+				 * terminal IMSG_MBOX_APPENDED reply. Distinct
+				 * from the client-literal-read phase
+				 * (s->literal_pending) that precedes it --
+				 * this covers only the store round trip. */
 	SESSION_SEARCHING,	/* IMSG_MBOX_SEARCH sent, awaiting the
 				 * IMSG_MBOX_SEARCH_MATCH stream + terminal
-				 * IMSG_MBOX_RESULT -- see cmd_search()/
-				 * session_finish_search(). Same per-match-
-				 * stream-then-terminal-result reply shape as
-				 * SESSION_FETCHING/STORING/EXPUNGING, so it's
-				 * handled by the same session_handle_mbox_
-				 * result() entry point, which branches to
-				 * session_finish_search() first. */
+				 * IMSG_MBOX_RESULT; handled by
+				 * session_handle_mbox_result(), which
+				 * branches to session_finish_search(). */
 	SESSION_STATUSING,	/* IMSG_MBOX_STATUS sent, awaiting the single
-				 * terminal IMSG_MBOX_STATUS_RESULT reply --
-				 * see cmd_status()/session_handle_mbox_
-				 * status_result(). Same single-request/single-
-				 * reply shape as SESSION_SELECTING, but
-				 * (unlike SELECT) never changes s->state's
-				 * SELECTED-ness -- STATUS "does not change the
-				 * currently selected mailbox" (RFC 9051
-				 * SS6.3.11) -- so s->status_prev_state records
-				 * whichever ST_AUTH state was current when
-				 * cmd_status() was called, for session_handle_
-				 * mbox_status_result() to restore. */
+				 * terminal IMSG_MBOX_STATUS_RESULT reply.
+				 * Never changes s->state's SELECTED-ness
+				 * (RFC 9051 SS6.3.11); s->status_prev_state
+				 * records the state to restore. */
 	SESSION_COPYING,	/* IMSG_MBOX_COPY or IMSG_MBOX_MOVE sent
 				 * (s->cmd_is_move says which), awaiting the
 				 * IMSG_MBOX_COPY_MAPPING stream (plus, for a
 				 * MOVE, an interleaved IMSG_MBOX_EXPUNGED
-				 * stream -- see s->move_expunged's comment) +
-				 * terminal IMSG_MBOX_RESULT -- see cmd_copy()/
-				 * cmd_move()/session_finish_copy_or_move().
-				 * Same per-message-stream-then-terminal-result
-				 * shape as SESSION_FETCHING/STORING/EXPUNGING/
-				 * SEARCHING, so it's handled by the same
-				 * session_handle_mbox_result() entry point,
-				 * which branches to session_finish_copy_or_
-				 * move() first, the same way it already
-				 * branches to session_finish_search(). */
+				 * stream) + terminal IMSG_MBOX_RESULT;
+				 * branches to
+				 * session_finish_copy_or_move(). */
 
 	/*
 	 * RFC 9051 SS6.3.4-SS6.3.6/SS6.3.9 additions (flat multi-mailbox
-	 * support). All four are
-	 * command-auth (valid in Authenticated or Selected state) and never
-	 * change s->state's SELECTED-ness -- same "record whichever ST_AUTH
-	 * state was current before, restore it after" pattern SESSION_
-	 * STATUSING's s->status_prev_state already established, reused here
-	 * as s->mbox_op_prev_state (one shared field: only one of these four
-	 * can ever be in flight for a given session at once, so there's no
-	 * need for four separate fields the way SESSION_APPENDING has its
-	 * own append_prev_state).
+	 * support). All four are command-auth and never change s->state's
+	 * SELECTED-ness; s->mbox_op_prev_state records the state to
+	 * restore (one shared field, only one of these four is ever in
+	 * flight per session).
 	 */
-	SESSION_CREATING,	/* IMSG_MBOX_CREATE sent, awaiting the single
-				 * terminal IMSG_MBOX_RESULT reply -- see
-				 * cmd_create()/session_finish_mbox_op(). */
-	SESSION_DELETING,	/* IMSG_MBOX_DELETE sent, same single-terminal-
-				 * reply shape as SESSION_CREATING -- see
-				 * cmd_delete()/session_finish_mbox_op(). */
-	SESSION_RENAMING,	/* IMSG_MBOX_RENAME sent, same single-terminal-
-				 * reply shape as SESSION_CREATING -- see
-				 * cmd_rename()/session_finish_mbox_op(). */
+	SESSION_CREATING,	/* IMSG_MBOX_CREATE sent, single terminal
+				 * IMSG_MBOX_RESULT reply */
+	SESSION_DELETING,	/* IMSG_MBOX_DELETE sent, same shape as
+				 * SESSION_CREATING */
+	SESSION_RENAMING,	/* IMSG_MBOX_RENAME sent, same shape as
+				 * SESSION_CREATING */
 	SESSION_LISTING		/* IMSG_MBOX_LIST sent, awaiting the
 				 * IMSG_MBOX_LIST_ITEM stream + terminal
-				 * IMSG_MBOX_RESULT -- see list_dispatch()/
-				 * session_finish_list(). Same per-item-stream-
-				 * then-terminal-result shape as SESSION_
-				 * SEARCHING/COPYING, so it's handled by the
-				 * same session_handle_mbox_result() entry
-				 * point too, branching to session_finish_
-				 * list() first. */
+				 * IMSG_MBOX_RESULT; branches to
+				 * session_finish_list(). */
 };
 
 /*
- * Generous line-length cap for the raw CRLF-delimited read buffer below.
- * RFC 9051 doesn't mandate a specific limit -- SS7.1.3's "* BAD Command
- * line too long" is example text, not a normative value -- but any real
- * server needs one to bound memory for a client that never sends CRLF.
- * Revisit once IMAP literals ({n}-prefixed octet counts, SS4.3) are
- * implemented: those need a different, larger mechanism than a flat line
- * buffer, since a literal's byte count is part of the command syntax
- * itself, not just "a longer line".
+ * Line-length cap for the raw CRLF-delimited read buffer below. RFC 9051
+ * doesn't mandate a specific limit, but any real server needs one to
+ * bound memory for a client that never sends CRLF.
  */
 #define SESSION_INBUF_MAX	8192
 
 /*
- * Generous bound for a client-chosen tag we need to remember across an
- * async IMSG_AUTH_REQUEST/IMSG_AUTH_RESULT (and, on success, IMSG_STORE_
- * FORK/IMSG_SETUP_PEER) round trip -- RFC 9051 SS9 doesn't specify a tag
- * length limit (`tag = 1*<any ASTRING-CHAR except "+">`), so this is the
- * same kind of deliberate, flagged simplification as session_reply()'s
- * 512-byte response buffer: truncated via strlcpy() rather than rejected,
- * matching this file's existing truncate-rather-than-overflow style.
+ * Bound for a client-chosen tag remembered across an async IMSG_AUTH_
+ * REQUEST/IMSG_AUTH_RESULT round trip. RFC 9051 SS9 specifies no tag
+ * length limit; truncated via strlcpy() rather than rejected.
  */
 #define IMAP_TAG_MAX	64
 
 /*
- * Bound on how many complete command lines a session may have pipelined
- * ahead of the one currently awaiting an async store round trip (RFC 9051
- * SS5.5 permits a client to send further commands before a prior one's
- * tagged response arrives, as long as the server processes them in order --
- * see session_enqueue_cmd()/session_dequeue_next() in listener.c). Deep
- * enough for any real client (the iOS Mail bug this was written for only
- * ever overlapped two: LIST then SELECT); a session that queues past this
- * is either buggy or hostile, so it's disconnected rather than given
- * unbounded memory -- same philosophy as SESSION_INBUF_MAX/IMAP_TAG_MAX
- * above. Deliberately does NOT apply to the pre-authentication states
- * (SESSION_AUTHENTICATING/STORE_PENDING) -- see session_is_busy()'s
- * comment -- so an unauthenticated client gains no new memory-allocation
- * surface from this queue at all.
+ * Bound on pipelined command lines ahead of the one awaiting an async
+ * store round trip (RFC 9051 SS5.5 permits pipelining as long as the
+ * server processes them in order). A session that queues past this is
+ * disconnected rather than given unbounded memory. Does NOT apply to
+ * the pre-authentication states (see session_is_busy()).
  */
 #define SESSION_CMD_QUEUE_MAX	8
 
 /*
- * Cap on the verbatim label text (e.g. "HEADER.FIELDS (DATE FROM)" or
- * "HEADER.FIELDS.NOT (X-SPAM-STATUS)") stashed on struct session's
- * pending_header_label and echoed back into the FETCH response's
- * "BODY[<label>]" -- see that field's own comment for the full
- * reasoning. Defined up here (rather than down near parse_fetch_atts(),
- * where it's actually used) because struct session, just below,
- * declares a fixed array of this size -- needs to be visible before that
- * point. Never crosses the imsg boundary (listener.c has this text
- * straight from the client's own command line), so this is a
- * listener.c-local constant, not a shared imapd.h one like HEADER_
- * FIELDS_MAX. Sized generously above HEADER_FIELDS_MAX (256, imapd.h)
- * to cover the "HEADER.FIELDS.NOT (...)" wrapper text around whatever
- * field-name list already fits under that cap.
+ * Cap on the verbatim label text (e.g. "HEADER.FIELDS (DATE FROM)")
+ * stashed on struct session's pending_header_label and echoed back as
+ * "BODY[<label>]". Listener-local, since this text never crosses the
+ * imsg boundary. Sized above HEADER_FIELDS_MAX (256, imapd.h) to cover
+ * the wrapper text around the field-name list.
  */
 #define HEADER_FIELDS_LABEL_MAX	288
 
 /*
- * RFC 9051 SS6.4.4's SEARCH result options this server understands as
- * ESEARCH return items (SS7.3.4) -- SAVE (the "$" search result
- * variable, SS6.4.4.1) is recognized but rejected with a flagged NO; see
- * parse_search_return_opts()'s comment for why it's out of scope this
- * pass. Shared here (not file-local to search_cmd.c, where they're
- * mostly used) because store_cmd.c's session_finish_search() also tests
- * these bits when assembling the ESEARCH response.
+ * RFC 9051 SS6.4.4 SEARCH result options understood as ESEARCH return
+ * items (SS7.3.4). SAVE ("$", SS6.4.4.1) is recognized but rejected
+ * with a flagged NO. Shared here since store_cmd.c's
+ * session_finish_search() also tests these bits.
  */
 #define SEARCH_RETURN_MIN	(1U << 0)
 #define SEARCH_RETURN_MAX	(1U << 1)
 #define SEARCH_RETURN_ALL	(1U << 2)
 #define SEARCH_RETURN_COUNT	(1U << 3)
 
-/* One RFC 7162 VANISHED (EARLIER) range -- see struct session's vanished_
- * ranges comment and format_range_list(). Named (not anonymous) so format_
- * range_list() can take it as a parameter type. */
+/* One RFC 7162 VANISHED (EARLIER) range. Named so format_range_list()
+ * can take it as a parameter type. */
 struct vanished_range {
 	uint32_t	lo;
 	uint32_t	hi;
@@ -223,505 +144,273 @@ struct session {
 	struct event		 client_ev;
 	enum session_state	 state;
 	int			 implicit_tls;	/* accepted on port 993, per
-						 * RFC 8314 -- vs. port 143,
+						 * RFC 8314, vs. port 143,
 						 * where TLS only starts after
 						 * STARTTLS */
 	struct imsgev		*store_iev;	/* NULL until STORE_PENDING
 						 * resolves */
 	int			 client_ev_added; /* client_ev is only safe to
 						   * event_del() once this is
-						   * set -- see listener_accept()'s
+						   * set, see listener_accept()'s
 						   * implicit-TLS early-teardown
-						   * path, which never reaches
-						   * the event_set()/event_add()
-						   * call below it. */
+						   * path */
 	int			 tls_active;	/* 1 once a real TLS handshake
-						 * (implicit-TLS accept or
-						 * STARTTLS) has completed --
-						 * threaded through CAPABILITY's
-						 * state-appropriate response and
-						 * AUTHENTICATE's TLS gate. */
-	struct tls		*tls_ctx;	/* NULL until tls_accept_socket()
-						 * -- non-NULL during handshake
-						 * too, not just once tls_active */
+						 * (implicit-TLS or STARTTLS)
+						 * has completed; gates
+						 * CAPABILITY/AUTHENTICATE */
+	struct tls		*tls_ctx;	/* NULL until tls_accept_socket();
+						 * non-NULL during handshake too */
 	int			 pending_greeting; /* implicit-TLS only: the
-						 * greeting can't be sent until
-						 * after the handshake completes
-						 * (RFC 8314 -- no protocol bytes
-						 * before TLS), so listener_accept()
-						 * defers it and session_tls_
-						 * handshake() sends it on success */
+						 * greeting is deferred until
+						 * the handshake completes (RFC
+						 * 8314 forbids protocol bytes
+						 * before TLS) */
 	char			 inbuf[SESSION_INBUF_MAX];
 	size_t			 inbuflen;	/* bytes of unparsed input
 						 * currently in inbuf */
 	int			 auth_cont;	/* 1 while the next raw client
-						 * line is a SASL continuation-
-						 * response (base64 or "*"), not
-						 * a fresh tagged command -- set
-						 * by cmd_authenticate() when the
-						 * client sends bare "AUTHENTICATE
-						 * PLAIN" with no inline initial
-						 * response, cleared by session_
-						 * handle_auth_continuation() as
-						 * soon as that line arrives. */
+						 * line is a SASL continuation
+						 * response, not a fresh tagged
+						 * command; set by
+						 * cmd_authenticate(), cleared
+						 * by session_handle_auth_
+						 * continuation() */
 	int			 idling;	/* 1 while the next raw client
-						 * line is IDLE's "DONE"
-						 * continuation, not a fresh
-						 * tagged command -- set by
-						 * cmd_idle(), cleared by
-						 * session_handle_idle_
-						 * continuation() (RFC 9051
-						 * SS6.3.13). Same shape as
-						 * auth_cont above, checked
-						 * second in session_dispatch_
-						 * client()'s read loop. */
+						 * line is IDLE's "DONE" (RFC
+						 * 9051 SS6.3.13); same shape as
+						 * auth_cont, checked second in
+						 * the read loop */
 	char			*cmd_queue[SESSION_CMD_QUEUE_MAX]; /* malloc(3)'d
-						 * command lines the client
-						 * pipelined while session_is_
-						 * busy() was true -- e.g. iOS
-						 * Mail sending SELECT right
-						 * behind LIST, before LIST's
-						 * tagged OK arrives (RFC 9051
-						 * SS5.5 permits this). Entries
-						 * 0..cmd_queue_n-1 are valid,
-						 * always compacted (no holes);
-						 * session_enqueue_cmd() appends,
-						 * session_dequeue_next() pops
-						 * the front and shifts. Never
-						 * touched for auth_cont/idling
-						 * continuation lines -- those
-						 * bypass this queue entirely,
-						 * same as they bypass session_
-						 * handle_line()'s tag/name/args
-						 * parser. */
+						 * command lines pipelined while
+						 * session_is_busy() (RFC 9051
+						 * SS5.5). Entries 0..cmd_queue_n-1
+						 * are valid, always compacted;
+						 * session_enqueue_cmd()/
+						 * session_dequeue_next() manage
+						 * it. Bypassed entirely for
+						 * auth_cont/idling continuation
+						 * lines. */
 	uint32_t		 cmd_queue_n;	/* count of valid entries in
 						 * cmd_queue, 0..SESSION_CMD_
 						 * QUEUE_MAX */
-	uid_t			 uid;		/* set once, in session_request_
-						 * store(), from the same imsg_
-						 * auth_result the store-fork
-						 * request itself uses -- exists
-						 * purely so session_notify_
-						 * idle_peers() can find this
-						 * session's *other* concurrent
-						 * sessions (same user, v1 has
-						 * no shared mailboxes so that's
-						 * the only case that can ever
-						 * matter) without a second round
-						 * trip through auth just to ask
-						 * "whose session is this." */
+	uid_t			 uid;		/* set once from the auth
+						 * result; lets session_notify_
+						 * idle_peers() find this
+						 * session's other concurrent
+						 * sessions (same user) without
+						 * a second auth round trip */
 	char			 pending_tag[IMAP_TAG_MAX]; /* copy of whichever
 						 * command's tag is waiting on an
-						 * async imsg round trip right now
-						 * (AUTHENTICATE -> auth, or SELECT
-						 * -> store) -- tag itself is a
-						 * pointer into inbuf, which gets
-						 * overwritten by the next read()
-						 * long before IMSG_AUTH_RESULT (or
-						 * the later IMSG_SETUP_PEER/
-						 * IMSG_STORE_FORK store-spawn
-						 * reply, or IMSG_MBOX_SELECTED)
-						 * comes back, so it has to be
-						 * saved out. Only ever one such
-						 * round trip in flight at a time
-						 * per session (SESSION_
-						 * AUTHENTICATING/STORE_PENDING/
-						 * SELECTING/FETCHING are all
-						 * mutually exclusive states), so
+						 * async imsg round trip, the
+						 * tag itself points into inbuf,
+						 * which gets overwritten by the
+						 * next read() before the reply
+						 * arrives. Only one round trip is
+						 * ever in flight per session, so
 						 * one field is enough. */
 	uint32_t		 fetch_attrs;	/* MBOX_FETCH_* bitmask for
-						 * the FETCH currently in flight
-						 * (SESSION_FETCHING) -- needed
-						 * because struct imsg_mbox_
-						 * fetch_meta's fields (uid,
-						 * size, ...) are always
-						 * populated by store regardless
-						 * of what was actually
-						 * requested, so session_send_
-						 * fetch_response() needs to
-						 * know which ones to print. */
+						 * the in-flight FETCH, needed
+						 * because imsg_mbox_fetch_meta's
+						 * fields are always populated by
+						 * store regardless of what was
+						 * requested */
 	char			*pending_header_buf; /* malloc(3)'d raw header
 						 * bytes from the most recent
 						 * IMSG_MBOX_FETCH_HEADER, held
-						 * here until the very next
-						 * IMSG_MBOX_FETCH_META (same
-						 * message, always sent
-						 * immediately after -- see that
-						 * struct's comment in
-						 * imapd.h) folds it into one
-						 * FETCH response line; freed and
-						 * NULL'd there. Never allocated
-						 * for more than one message at a
-						 * time -- store.c's ordering
-						 * contract means this is never
-						 * still set when the next
-						 * IMSG_MBOX_FETCH_HEADER
-						 * arrives, but session_teardown()
-						 * frees it defensively anyway in
-						 * case a FETCH is torn down
-						 * mid-stream. */
+						 * until the following
+						 * IMSG_MBOX_FETCH_META folds it
+						 * into one FETCH response line;
+						 * freed and NULL'd there.
+						 * session_teardown() also frees
+						 * it defensively. */
 	uint32_t		 pending_header_len; /* valid only alongside
 						 * pending_header_buf != NULL */
-	int			 pending_header_found; /* 0 -- this message's
-						 * header couldn't be read; 1
-						 * -- pending_header_buf/_len are
-						 * valid. Distinguishes "BODY[HEADER]
-						 * was requested but this message
-						 * has none to give" from "BODY[HEADER]
-						 * wasn't requested at all", the
-						 * latter never populating this or
-						 * pending_header_buf in the first
-						 * place. */
+	int			 pending_header_found; /* 0 = this message's
+						 * header couldn't be read; 1 =
+						 * pending_header_buf/_len valid.
+						 * Distinguishes "requested but
+						 * none to give" from "not
+						 * requested". */
 	char			 pending_header_label[HEADER_FIELDS_LABEL_MAX];
-						/* verbatim client-typed section-spec
-						 * text -- "HEADER" for plain BODY.PEEK
-						 * [HEADER], or e.g. "HEADER.FIELDS
-						 * (DATE FROM)" for that variant --
-						 * echoed back as-is in the FETCH
-						 * response's "BODY[<label>]" rather than
-						 * reconstructed, since RFC 9051 doesn't
-						 * mandate an exact echo format and
-						 * verbatim is simplest and trivially
-						 * correct. Set once in fetch_dispatch(),
-						 * read by session_send_fetch_response()
-						 * -- never needs its own free()/reset
-						 * since it's a fixed-size buffer, not a
-						 * malloc(3)'d pointer like pending_
-						 * header_buf. */
-	char			*pending_body_buf; /* same stash-until-
-						 * the-next-IMSG_MBOX_FETCH_META
-						 * shape as pending_header_buf above,
-						 * just for IMSG_MBOX_FETCH_BODY
-						 * (BODY.PEEK[]/BODY.PEEK[TEXT])
-						 * instead -- see struct imsg_mbox_
-						 * fetch_body's comment in
-						 * imapd.h. Freed and NULL'd by
+						/* verbatim client-typed section
+						 * spec ("HEADER", or "HEADER.FIELDS
+						 * (DATE FROM)"), echoed as-is in
+						 * "BODY[<label>]" rather than
+						 * reconstructed. Set once in
+						 * fetch_dispatch(), read by
 						 * session_send_fetch_response();
-						 * session_teardown() frees it
-						 * defensively too, same reasoning as
-						 * pending_header_buf. */
+						 * fixed-size, no free needed. */
+	char			*pending_body_buf; /* same stash-until-next-
+						 * IMSG_MBOX_FETCH_META shape as
+						 * pending_header_buf, for
+						 * IMSG_MBOX_FETCH_BODY
+						 * (BODY.PEEK[]/[TEXT]) */
 	uint32_t		 pending_body_len; /* valid only alongside
 						 * pending_body_buf != NULL */
 	int			 pending_body_found; /* same found/not-found
 						 * distinction as pending_header_
-						 * found, for BODY.PEEK[]/
-						 * BODY.PEEK[TEXT] */
+						 * found */
 	char			 pending_body_label[SECTION_PART_MAX];
-						/* verbatim section text for the
-						 * "BODY[<label>]" response --
-						 * "" for BODY.PEEK[] (whole
-						 * message), "TEXT" for BODY.PEEK
-						 * [TEXT], or the client-typed
-						 * dotted-numeric path (e.g. "3.1")
-						 * for BODY.PEEK[<section-part>] --
-						 * same "precompute the full
-						 * verbatim label at dispatch time"
-						 * idiom as pending_header_label,
-						 * unifying what used to be a
-						 * pending_body_is_text boolean
-						 * (adequate when only WHOLE/TEXT
-						 * existed, not once a third,
-						 * client-supplied-text variant --
-						 * PART -- joined them). Set once in
-						 * fetch_dispatch() from whichever of
-						 * MBOX_FETCH_BODY_WHOLE/_TEXT/_PART
-						 * attrs selects (same WHOLE > TEXT >
-						 * PART precedence store.c's
-						 * handle_mbox_fetch() applies --
-						 * see that function's comment),
-						 * read by session_send_fetch_
-						 * response(). */
+						/* verbatim section text for
+						 * "BODY[<label>]", "" for
+						 * whole message, "TEXT", or a
+						 * dotted-numeric part path.
+						 * Set once in fetch_dispatch()
+						 * from whichever of WHOLE/TEXT/
+						 * PART attrs selects (same
+						 * precedence as store.c's
+						 * handle_mbox_fetch()). */
 	int			 pending_body_has_partial; /* 1 if the
-						 * client's own BODY.PEEK[...]
-						 * token carried a <<start.count>>
-						 * suffix (RFC 9051 SS6.4.5) for
-						 * whichever of WHOLE/TEXT/PART was
-						 * actually selected -- see struct
-						 * imsg_mbox_fetch's has_partial
-						 * comment in imapd.h. Only
-						 * pending_body_partial_origin (the
-						 * *requested* start octet, not
-						 * store.c's own bodylen -- SS6.4.5:
-						 * "The origin octet facility MUST
-						 * NOT be used ... unless the client
-						 * specifically requested it", and
-						 * the response echoes only the
-						 * origin, never the count) is echoed
-						 * back; set alongside pending_body_
-						 * label in fetch_dispatch(). */
+						 * client's BODY.PEEK[...] token
+						 * carried a <<start.count>>
+						 * suffix (RFC 9051 SS6.4.5). Only
+						 * the origin is echoed back, never
+						 * the count. */
 	uint32_t		 pending_body_partial_origin;
-	char			*pending_envelope_buf; /* same stash-
-						 * until-the-next-IMSG_MBOX_
-						 * FETCH_META shape as pending_
-						 * header_buf/pending_body_buf
-						 * above, for IMSG_MBOX_FETCH_
-						 * ENVELOPE -- see struct imsg_
-						 * mbox_fetch_envelope's comment
-						 * in imapd.h. Unlike those
-						 * two, holds already-formatted
-						 * "(...)" envelope response
-						 * text, not raw message bytes,
-						 * so session_send_fetch_
+	char			*pending_envelope_buf; /* same stash-until-
+						 * next shape as pending_header_buf,
+						 * for IMSG_MBOX_FETCH_ENVELOPE.
+						 * Holds already-formatted "(...)"
+						 * text, so session_send_fetch_
 						 * response() writes it directly
 						 * rather than wrapping it in a
-						 * `{n}` literal. Freed and
-						 * NULL'd by session_send_fetch_
-						 * response(); session_teardown()
-						 * frees it defensively too, same
-						 * reasoning as pending_header_buf. */
+						 * literal. */
 	uint32_t		 pending_envelope_len; /* valid only
 						 * alongside pending_envelope_buf
 						 * != NULL */
 	int			 pending_envelope_found; /* same found/
 						 * not-found distinction as
-						 * pending_header_found, for
-						 * ENVELOPE */
-	char			*pending_bodystructure_buf; /* same stash-
-						 * until-the-next-IMSG_MBOX_
-						 * FETCH_META shape, same
-						 * already-formatted-text shape,
-						 * as pending_envelope_buf above,
-						 * for IMSG_MBOX_FETCH_
-						 * BODYSTRUCTURE -- see struct
-						 * imsg_mbox_fetch_bodystructure's
-						 * comment in imapd.h. */
+						 * pending_header_found */
+	char			*pending_bodystructure_buf; /* same shape as
+						 * pending_envelope_buf, for
+						 * IMSG_MBOX_FETCH_BODYSTRUCTURE */
 	uint32_t		 pending_bodystructure_len; /* valid only
 						 * alongside pending_
 						 * bodystructure_buf != NULL */
 	int			 pending_bodystructure_found; /* same found/
 						 * not-found distinction as
-						 * pending_header_found, for
-						 * BODYSTRUCTURE */
-	char			 pending_bodystructure_label[16]; /*
-						 * "BODY" or "BODYSTRUCTURE" --
-						 * RFC 9051 SS9's msg-att-static:
-						 * `"BODY" ["STRUCTURE"] SP body`
-						 * means the response label
-						 * itself echoes whichever bare
-						 * token the client used (both
-						 * set the same MBOX_FETCH_
-						 * BODYSTRUCTURE bit and produce
-						 * identical body text in this
-						 * implementation -- see that
-						 * bit's imapd.h comment --
-						 * but the label still has to
-						 * match what was asked for).
-						 * Set once in fetch_dispatch(),
-						 * read by session_send_fetch_
-						 * response() -- same fixed-
-						 * buffer, no-free-needed shape
-						 * as pending_header_label. */
+						 * pending_header_found */
+	char			 pending_bodystructure_label[16]; /* "BODY"
+						 * or "BODYSTRUCTURE", both set
+						 * the same bit and produce
+						 * identical text, but the
+						 * response label must match what
+						 * was asked for */
 	int			 close_after_expunge; /* 1 if the in-flight
 						 * SESSION_EXPUNGING round trip
 						 * was started by CLOSE, not a
-						 * real EXPUNGE command -- both
-						 * send the identical IMSG_MBOX_
-						 * EXPUNGE (CLOSE with silent=1)
-						 * and get back the identical
-						 * IMSG_MBOX_RESULT, so this is
-						 * the only way session_handle_
-						 * mbox_result() can tell which
-						 * tagged completion text/next
-						 * state applies: CLOSE goes to
-						 * SESSION_AUTHENTICATED, a real
-						 * EXPUNGE goes back to SESSION_
-						 * SELECTED. */
+						 * real EXPUNGE, both send the
+						 * identical imsg pair, so this is
+						 * the only way to tell which next
+						 * state applies: CLOSE ->
+						 * SESSION_AUTHENTICATED, EXPUNGE
+						 * -> SESSION_SELECTED */
 	int			 literal_pending; /* 1 while the next bytes off
 						 * the wire are a client
 						 * literal's raw octets (RFC 9051
 						 * SS4.3), not a CRLF-delimited
-						 * line -- set by cmd_append()
-						 * when it finds a trailing
-						 * "{n}"/"{n+}" on the APPEND
-						 * command line, cleared once
-						 * literal_buf_len reaches
-						 * literal_len. Checked at the
-						 * very top of session_dispatch_
-						 * client()'s read loop, *before*
-						 * the normal CRLF search -- so
-						 * unlike every async-imsg-wait
-						 * state above, this needs no
-						 * ST_* dispatch-table exclusion:
-						 * while it's set, session_
+						 * line, set by cmd_append() on
+						 * a trailing "{n}"/"{n+}",
+						 * cleared once literal_buf_len
+						 * reaches literal_len. Checked at
+						 * the top of session_dispatch_
+						 * client()'s read loop, before the
+						 * CRLF search, so no ST_* dispatch
+						 * exclusion is needed: session_
 						 * handle_line() is never reached
-						 * at all, regardless of s->state,
-						 * so no new complete "tag SP
-						 * command CRLF" can ever be
-						 * mis-parsed out of literal
-						 * bytes. */
+						 * while it's set. */
 	char			*literal_buf;	/* malloc(3)'d accumulator for
 						 * the literal currently being
 						 * read, literal_len bytes, freed
-						 * once handed off to session_
-						 * finish_append() (or on early
-						 * teardown -- see session_
-						 * teardown()) */
+						 * once handed to session_finish_
+						 * append() (or on early teardown) */
 	uint64_t		 literal_len;	/* total announced literal size
-						 * (what "{n}" both this and
-						 * literal_remaining start from) */
-	uint64_t		 literal_remaining; /* bytes of the literal still
-						 * needed -- literal_len minus
-						 * however much has landed in
-						 * literal_buf so far, across
-						 * possibly many reads */
+						 * ("{n}"), what literal_remaining
+						 * starts from */
+	uint64_t		 literal_remaining; /* bytes of the literal
+						 * still needed */
 	char			 append_mailbox[MBOX_NAME_MAX]; /* APPEND's
 						 * arguments, parsed by
-						 * cmd_append() *before* the
-						 * literal is read (mailbox/
-						 * flags/date all precede the
-						 * literal in RFC 9051 SS6.3.12's
-						 * grammar) and held here across
-						 * the literal-read phase until
-						 * session_finish_append() needs
-						 * them to build IMSG_MBOX_
-						 * APPEND */
+						 * cmd_append() before the literal
+						 * is read, held here until
+						 * session_finish_append() builds
+						 * IMSG_MBOX_APPEND */
 	uint32_t		 append_sysflags;
 	char			 append_keywords[MBOX_FLAGS_MAX];
 	int			 append_has_date;
 	int64_t			 append_date;
-	enum session_state	 append_prev_state; /* SESSION_AUTHENTICATED or
-						 * SESSION_SELECTED -- whichever
+	enum session_state	 append_prev_state; /* SESSION_AUTHENTICATED
+						 * or SESSION_SELECTED, whichever
 						 * s->state was when cmd_append()
-						 * was first called (APPEND is
-						 * valid in either, RFC 9051
-						 * command-auth), so session_
-						 * handle_mbox_appended() knows
-						 * which one to restore once
-						 * IMSG_MBOX_APPENDED arrives --
-						 * unlike FETCH/STORE/EXPUNGE,
-						 * which are only ever dispatched
-						 * from (and so only ever return
-						 * to) SESSION_SELECTED, APPEND's
-						 * "go back to" state isn't
-						 * fixed. */
+						 * was called, so session_handle_
+						 * mbox_appended() knows which to
+						 * restore, unlike FETCH/STORE/
+						 * EXPUNGE, APPEND's return state
+						 * isn't fixed */
 	uint32_t		 status_attrs;	/* STATUS_ATT_* bitmask for the
-						 * STATUS currently in flight
-						 * (SESSION_STATUSING) -- which
-						 * status-att values to include
-						 * in the response, in listener.c's
-						 * fixed canonical order; store.c
+						 * in-flight STATUS, which
+						 * values to include, in
+						 * listener.c's fixed order; store.c
 						 * always computes MESSAGES/
 						 * UIDNEXT/UIDVALIDITY/
-						 * HIGHESTMODSEQ regardless (see
-						 * imapd.h's imsg_mbox_status
-						 * comment), same "store always
-						 * computes, listener decides
-						 * whether to print" split as
-						 * s->fetch_attrs. */
-	char			 status_mailbox[MBOX_NAME_MAX]; /* the mailbox
-						 * name STATUS was asked about
-						 * (RFC 9051 SS6.3.4-SS6.3.6's
-						 * flat multi-mailbox support --
-						 * before that, STATUS could only
-						 * ever mean INBOX, so this field
-						 * didn't need to exist) --
-						 * stashed here so session_
-						 * handle_mbox_status_result() can
-						 * echo it back in the untagged
-						 * "* STATUS <mailbox> (...)"
-						 * response; store.c's own reply
-						 * (struct imsg_mbox_status_
-						 * result) has no mailbox field of
-						 * its own to read it back from,
-						 * since listener.c already knows
-						 * what it asked. */
-	enum session_state	 status_prev_state; /* SESSION_AUTHENTICATED or
-						 * SESSION_SELECTED -- same
-						 * "STATUS is command-auth, valid
-						 * in either state" reasoning as
-						 * s->append_prev_state above, and
-						 * for the identical reason:
-						 * session_handle_mbox_status_
-						 * result() needs to know which
-						 * one to restore. */
-	enum session_state	 mbox_op_prev_state; /* SESSION_AUTHENTICATED or
-						 * SESSION_SELECTED -- same
-						 * "record whichever ST_AUTH
-						 * state was current before,
-						 * restore it after" pattern as
-						 * s->status_prev_state just
-						 * above, shared across all four
-						 * of SESSION_CREATING/DELETING/
-						 * RENAMING/LISTING (see that enum
-						 * block's own comment) since only
-						 * one of the four can ever be in
-						 * flight at once for a given
-						 * session. session_finish_mbox_
-						 * op()/session_finish_list() both
-						 * restore it. */
+						 * HIGHESTMODSEQ regardless */
+	char			 status_mailbox[MBOX_NAME_MAX]; /* mailbox
+						 * name STATUS was asked about,
+						 * stashed so session_handle_
+						 * mbox_status_result() can echo
+						 * it in the untagged response --
+						 * store.c's reply has no mailbox
+						 * field of its own */
+	enum session_state	 status_prev_state; /* SESSION_AUTHENTICATED
+						 * or SESSION_SELECTED, same
+						 * reasoning as append_prev_state */
+	enum session_state	 mbox_op_prev_state; /* SESSION_AUTHENTICATED
+						 * or SESSION_SELECTED, shared
+						 * across SESSION_CREATING/
+						 * DELETING/RENAMING/LISTING
+						 * (only one in flight at a time).
+						 * session_finish_mbox_op()/
+						 * session_finish_list() restore
+						 * it. */
 	char			 list_pattern[2 * MBOX_NAME_MAX]; /* canonical
-						 * LIST/LSUB pattern (reference
-						 * concatenated with the mailbox
-						 * pattern by list_dispatch(),
-						 * same construction the old
-						 * synchronous 'canon' local
-						 * used) stashed across the
-						 * SESSION_LISTING round trip so
-						 * session_handle_mbox_list_
-						 * item() can test each IMSG_
-						 * MBOX_LIST_ITEM name against it
-						 * as it streams in, one at a
-						 * time -- unlike s->search_
-						 * matches, nothing needs to be
-						 * accumulated first, since each
-						 * match can be turned into its
-						 * untagged response
-						 * immediately. */
-	int			 list_is_lsub;	/* 1 if the in-flight SESSION_
-						 * LISTING round trip was started
-						 * by LSUB rather than LIST --
-						 * picks the untagged response
-						 * keyword and the tagged
-						 * completion text, same is_lsub
-						 * parameter list_dispatch()
-						 * already threads through
-						 * synchronously today. */
+						 * LIST/LSUB pattern (reference +
+						 * mailbox pattern, concatenated
+						 * by list_dispatch()), tested
+						 * against each IMSG_MBOX_LIST_
+						 * ITEM name as it streams in --
+						 * unlike search_matches, nothing
+						 * needs accumulating first, since
+						 * each match becomes its
+						 * untagged response immediately */
+	int			 list_is_lsub;	/* 1 if the in-flight LISTING
+						 * round trip was started by LSUB
+						 * rather than LIST, picks the
+						 * untagged keyword and tagged
+						 * completion text */
 	char			 rename_oldname[MBOX_NAME_MAX]; /* RENAME's
-						 * two arguments, stashed by
-						 * cmd_rename() across the
-						 * SESSION_RENAMING round trip
-						 * purely so session_finish_
-						 * mbox_op() can compare
-						 * rename_oldname against s->
-						 * selected_mailbox on success --
-						 * if this session had the
-						 * just-renamed mailbox itself
-						 * selected, its own s->selected_
-						 * mailbox needs to follow along
-						 * to rename_newname, the same
-						 * way store.c's handle_mbox_
-						 * rename() already makes its own
-						 * cwd/current_mailbox_dir follow
-						 * (see that function's own real-
-						 * hardware-bug comment). Neither
-						 * name is otherwise needed here
-						 * -- req.oldname/req.newname
-						 * already made the trip to
-						 * store.c in the imsg itself. */
+						 * arguments, stashed so
+						 * session_finish_mbox_op() can
+						 * compare oldname against s->
+						 * selected_mailbox: if this
+						 * session had the renamed
+						 * mailbox selected, it needs to
+						 * follow along to newname */
 	char			 rename_newname[MBOX_NAME_MAX];
 	uint32_t		 search_return_opts; /* SEARCH_RETURN_* bitmask
-						 * for the SEARCH currently in
-						 * flight (SESSION_SEARCHING) --
-						 * which of MIN/MAX/ALL/COUNT
-						 * session_finish_search() should
+						 * for the in-flight SEARCH --
+						 * which of MIN/MAX/ALL/COUNT to
 						 * include in the ESEARCH
-						 * response; store.c never needs
-						 * this, since it only computes
-						 * *which* messages match, not
-						 * how the client wants that
-						 * reported. */
+						 * response; store.c doesn't need
+						 * this, it only computes matches */
 	uint32_t		*search_matches; /* malloc(3)'d/realloc(3)'d,
-						 * growable -- ascending sequence
-						 * numbers streamed in via
-						 * IMSG_MBOX_SEARCH_MATCH, kept
-						 * in full (not range-compacted
-						 * until formatting time) so
-						 * MIN/MAX/COUNT/ALL can all be
-						 * derived from one array. Freed
-						 * by session_finish_search() once
-						 * the ESEARCH response is sent,
-						 * or by session_teardown() on
-						 * early disconnect. */
+						 * growable, ascending sequence
+						 * numbers streamed via
+						 * IMSG_MBOX_SEARCH_MATCH, kept in
+						 * full (not range-compacted until
+						 * formatting) so MIN/MAX/COUNT/ALL
+						 * can all derive from one array.
+						 * Freed by session_finish_search()
+						 * or session_teardown(). */
 	uint32_t		 search_nmatches; /* entries actually in
 						 * search_matches */
 	uint32_t		 search_matches_cap; /* allocated capacity of
@@ -729,142 +418,83 @@ struct session {
 						 * nmatches */
 	int			 search_alloc_failed; /* 1 if a realloc(3) in
 						 * session_handle_mbox_search_
-						 * match() ever failed for this
-						 * SEARCH -- makes session_
-						 * finish_search() send a NO
-						 * instead of a silently
-						 * incomplete ESEARCH response;
-						 * see that function's comment on
-						 * why "some matches dropped" is
-						 * treated as a hard failure, not
-						 * a best-effort partial result. */
-	int			 search_used_modseq; /* 1 if the SEARCH program
-							 * currently in flight
-							 * contained a MODSEQ
-							 * search-key -- RFC 7162
-							 * SS3.1.5/SS3.1.8: makes
-							 * this SEARCH a CONDSTORE-
-							 * enabling command, and
-							 * makes session_finish_
-							 * search() append "(MODSEQ
-							 * n)" using the running
-							 * max below. */
+						 * match() ever failed, makes
+						 * session_finish_search() send
+						 * a NO instead of a silently
+						 * incomplete ESEARCH response */
+	int			 search_used_modseq; /* 1 if the in-flight
+						 * SEARCH program contained a
+						 * MODSEQ search-key (RFC 7162
+						 * SS3.1.5/SS3.1.8: a CONDSTORE-
+						 * enabling command); makes
+						 * session_finish_search() append
+						 * "(MODSEQ n)" using the running
+						 * max below */
 	uint64_t		 search_max_modseq; /* highest modseq seen
-							 * across all matches of
-							 * the SEARCH currently in
-							 * flight, tracked by
-							 * session_handle_mbox_
-							 * search_match() --
-							 * store.c always sends
-							 * modseq per match (cheap,
-							 * see imapd.h's struct
-							 * imsg_mbox_search_match
-							 * comment), so this is
-							 * just a running max, not
-							 * conditional on search_
-							 * used_modseq itself. */
+						 * across matches of the
+						 * in-flight SEARCH; store.c
+						 * always sends modseq per match
+						 * (cheap), so this is just a
+						 * running max */
 
-	/*
-	 * RFC 7162 (CONDSTORE/QRESYNC) session state, added this pass.
-	 * condstore_enabled/qresync_enabled are both sticky for the whole
-	 * connection once set (SS3.1: "Once a client issues a CONDSTORE
-	 * enabling command, it has announced itself... until the connection
-	 * is closed"; SS3.2.3 says the same for QRESYNC) -- never cleared
-	 * back to 0 anywhere in this file. qresync_enabled implies
-	 * condstore_enabled is also 1 (SS3.2.3: "the presence of the
-	 * 'QRESYNC' capability implies support for the CONDSTORE IMAP
-	 * extension"), enforced at every place qresync_enabled gets set.
+	/* RFC 7162 (CONDSTORE/QRESYNC) session state. condstore_enabled/
+	 * qresync_enabled are sticky for the whole connection once set
+	 * (SS3.1, SS3.2.3), never cleared. qresync_enabled implies
+	 * condstore_enabled (SS3.2.3).
 	 */
 	int			 condstore_enabled;
 	int			 qresync_enabled;
 
-	/*
-	 * Best-known HIGHESTMODSEQ of the currently selected mailbox --
-	 * cached from every IMSG_MBOX_SELECTED/IMSG_MBOX_RESULT reply that
-	 * carries one (store.c always populates it; see imapd.h's
-	 * imsg_mbox_selected/imsg_mbox_result comments), regardless of
-	 * whether this session is CONDSTORE-aware yet. Exists specifically
-	 * for session_condstore_enable()'s "CONDSTORE enabling command
-	 * issued while a mailbox is already selected" case (RFC 7162 SS3.1:
-	 * the server MUST emit an unsolicited HIGHESTMODSEQ OK response at
-	 * that moment) -- without a cached value there would be nothing to
-	 * report without an extra store round trip just for this edge case.
+	/* Best-known HIGHESTMODSEQ of the currently selected mailbox,
+	 * cached from every reply that carries one, regardless of whether
+	 * this session is CONDSTORE-aware yet. Exists for session_
+	 * condstore_enable()'s "enabling command issued while a mailbox
+	 * is already selected" case (RFC 7162 SS3.1 requires an
+	 * unsolicited HIGHESTMODSEQ OK at that moment).
 	 */
 	uint64_t		 mbox_highestmodseq;
 
 	/*
-	 * RFC 9051 SS6.3.3: whether the currently selected mailbox was
-	 * opened via EXAMINE (1) rather than SELECT (0) -- meaningful only
-	 * while s->state == SESSION_SELECTED (and, transiently, SESSION_
-	 * SELECTING/SESSION_EXPUNGING while a select_or_examine()/session_
-	 * request_expunge() round trip is in flight). Set synchronously by
-	 * select_or_examine() before the IMSG_MBOX_SELECT round trip even
-	 * starts -- like s->state = SESSION_SELECTING itself, this doesn't
-	 * need to wait for store.c's reply, since it's derived purely from
-	 * which command the client typed, not from anything store.c knows.
-	 * Consulted by session_handle_mbox_selected() (READ-ONLY/READ-WRITE
-	 * response code, PERMANENTFLAGS) and by every command that would otherwise
-	 * mutate the selected mailbox's permanent state -- store_do(),
-	 * session_request_expunge(), copy_move_dispatch()'s is_move case --
-	 * to refuse (or, for CLOSE specifically, silently no-op) per SS6.3.3
-	 * ("No changes to the permanent state of the mailbox... are
-	 * permitted") and SS6.4.1's CLOSE exception.
+	 * RFC 9051 SS6.3.3: whether the selected mailbox was opened via
+	 * EXAMINE (1) rather than SELECT (0); meaningful while s->state ==
+	 * SESSION_SELECTED (and transiently during the SELECT/EXPUNGE
+	 * round trip). Set synchronously by select_or_examine() before the
+	 * round trip starts, since it's derived from the client's command,
+	 * not store.c's reply. Consulted for READ-ONLY/READ-WRITE/
+	 * PERMANENTFLAGS and by every command that would mutate the
+	 * mailbox's permanent state, to refuse per SS6.3.3 (with SS6.4.1's
+	 * CLOSE exception).
 	 */
 	int			 mbox_readonly;
 	char			 selected_mailbox[MBOX_NAME_MAX]; /* which
-					 * mailbox SESSION_SELECTED actually
-					 * refers to -- didn't need to exist
-					 * before RFC 9051 SS6.3.4-SS6.3.6's flat
-					 * multi-mailbox support, since
-					 * "selected" and "INBOX is selected"
-					 * were the same fact. Written
-					 * optimistically by select_or_examine()
-					 * at the same point s->state moves to
-					 * SESSION_SELECTING -- safe even though
-					 * the SELECT/EXAMINE might still fail,
-					 * because every reader of this field
-					 * also gates on s->state ==
-					 * SESSION_SELECTED first, and a failed
+					 * mailbox SESSION_SELECTED refers to.
+					 * Written optimistically by select_
+					 * or_examine() when s->state moves to
+					 * SESSION_SELECTING, safe because
+					 * every reader also gates on s->state
+					 * == SESSION_SELECTED, and a failed
 					 * SELECT/EXAMINE never reaches that
-					 * state (RFC 9051 SS6.3.2: "the session
-					 * is in authenticated state" on
-					 * failure, deselected same as before
-					 * the attempt). Consulted so far by
-					 * session_handle_mbox_appended()'s own
-					 * "is the mailbox APPEND just targeted
-					 * the one actually selected right now"
-					 * check -- see that function's
-					 * comment. */
+					 * state. Consulted by session_handle_
+					 * mbox_appended()'s "did APPEND
+					 * target the selected mailbox" check. */
 
 	/*
-	 * RFC 9051 SS6.3.13 (IDLE) v1 scope: EXISTS and EXPUNGE only (see
-	 * imapd.h's imsg_mbox_idle_uid comment for the sourcing/reasoning
-	 * behind not also pushing unsolicited FETCH). idle_known_uids is this
-	 * session's own cached, ordered snapshot of "which UIDs existed as of
-	 * the last time this session's view was authoritative" -- populated
-	 * from an IMSG_MBOX_IDLE_REFRESH round trip, either the baseline one
-	 * cmd_idle() triggers right after sending "+ idling" (idle_baseline_
-	 * valid is 0 until that first one lands, so session_handle_idle_
-	 * refreshed() knows not to diff against garbage), or a later one
-	 * triggered by session_notify_idle_peers() when some *other* session
-	 * belonging to the same uid successfully mutates the mailbox.
-	 * Diffing this array against a freshly streamed IMSG_MBOX_IDLE_UID
-	 * list is what lets session_handle_idle_refreshed() compute correct
-	 * seqno-based "* N EXPUNGE" lines for whatever UIDs dropped out --
-	 * same "position in the shrinking list, removed in ascending order"
-	 * algorithm handle_mbox_expunge() already needs on the store side,
-	 * just recomputed here in listener.c from two UID arrays instead of
-	 * from the index file directly.
+	 * RFC 9051 SS6.3.13 (IDLE) EXISTS and EXPUNGE only.
+	 * idle_known_uids is this session's cached, ordered snapshot of
+	 * which UIDs existed as of the last authoritative view, populated
+	 * from an IMSG_MBOX_IDLE_REFRESH round trip (the baseline one after
+	 * "+ idling", or a later one from session_notify_idle_peers() when
+	 * another session with the same uid mutates the mailbox).
+	 * idle_baseline_valid gates against diffing before the first one
+	 * lands. Diffing this array against a freshly streamed
+	 * IMSG_MBOX_IDLE_UID list lets session_handle_idle_refreshed()
+	 * compute correct seqno-based EXPUNGE lines for dropped UIDs.
 	 *
-	 * idle_refresh_pending/idle_refresh_again exist because a refresh is
-	 * itself an async imsg round trip: if a second trigger arrives while
-	 * one is already in flight for this session (e.g. two sibling
-	 * sessions both mutate in quick succession), there's no way to ask
-	 * store.c to hurry up or to safely start a second overlapping
-	 * request on the same store_iev channel -- idle_refresh_again just
-	 * remembers to immediately re-request once the in-flight one's
-	 * IMSG_MBOX_IDLE_REFRESHED reply lands, rather than either blocking
-	 * or silently dropping the second trigger.
+	 * idle_refresh_pending/idle_refresh_again exist because a refresh
+	 * is itself an async round trip: if a second trigger arrives while
+	 * one is in flight, idle_refresh_again remembers to immediately
+	 * re-request once the in-flight reply lands, rather than blocking
+	 * or dropping the second trigger.
 	 */
 	uint32_t		*idle_known_uids;
 	uint32_t		 idle_known_nuids;
@@ -873,168 +503,112 @@ struct session {
 	int			 idle_refresh_pending;
 	int			 idle_refresh_again;
 
-	/*
-	 * Accumulates the freshly streamed IMSG_MBOX_IDLE_UID list for an
-	 * in-flight refresh -- deliberately a *separate* array from idle_
-	 * known_uids above, not built in place over it, since session_
-	 * handle_idle_refreshed() needs both the old and the new list at
-	 * once to diff them. Same "held back until the terminal reply"
-	 * shape as s->vanished_ranges/s->qresync_fetches during a QRESYNC
-	 * SELECT resync.
+	/* Accumulates the freshly streamed IMSG_MBOX_IDLE_UID list for an
+	 * in-flight refresh, a separate array from idle_known_uids,
+	 * since session_handle_idle_refreshed() needs both old and new
+	 * lists at once to diff them.
 	 */
 	uint32_t		*idle_incoming_uids;
 	uint32_t		 idle_incoming_n;
 	uint32_t		 idle_incoming_cap;
 
 	/*
-	 * Accumulates VANISHED (EARLIER) ranges streamed in via zero or more
+	 * Accumulates VANISHED (EARLIER) ranges streamed via zero or more
 	 * IMSG_MBOX_SELECT_VANISHED during an in-flight QRESYNC SELECT
-	 * resync (s->state == SESSION_SELECTING) -- store.c already reports
-	 * these pre-compacted into ranges (see imsg_mbox_select_vanished's
-	 * comment in imapd.h), so this array just collects them in
-	 * arrival order (already ascending -- store.c streams its single
-	 * forward pass in increasing-UID order) for session_handle_mbox_
-	 * selected() to join into one combined "* VANISHED (EARLIER) ..."
-	 * line once IMSG_MBOX_SELECTED arrives -- RFC 7162 SS3.2.6 requires
-	 * every VANISHED (EARLIER) response to precede any FETCH response
-	 * in the same resync, which is only guaranteed by holding all of it
-	 * (and the FETCH data below) until the terminal reply, not by
-	 * relaying each piece to the client as it streams in.
+	 * resync. Already pre-compacted and ascending, collected here so
+	 * session_handle_mbox_selected() can join them into one combined
+	 * response line once IMSG_MBOX_SELECTED arrives. RFC 7162 SS3.2.6
+	 * requires VANISHED (EARLIER) to precede any FETCH in the same
+	 * resync, guaranteed only by holding everything until the terminal
+	 * reply.
 	 */
 	struct vanished_range	*vanished_ranges;
 	uint32_t		 vanished_nranges;
 	uint32_t		 vanished_cap;
 
-	/*
-	 * Accumulates the QRESYNC resync's per-message FETCH-with-UID-and-
-	 * MODSEQ data, streamed in via IMSG_MBOX_FETCH_META while s->state
-	 * == SESSION_SELECTING (see session_store_dispatch()'s IMSG_MBOX_
-	 * FETCH_META case) -- held back for the same "VANISHED must precede
-	 * FETCH" ordering reason vanished_ranges is. Holds full struct
-	 * imsg_mbox_fetch_meta copies (not just seqno/uid, unlike search_
-	 * matches) because session_send_qresync_fetch_response() needs
-	 * FLAGS too -- heavier per-entry than search_matches, but v1-scale
-	 * resyncs are small (this project's usual "personal use, modest
-	 * mailbox size" tolerance).
+	/* Accumulates the QRESYNC resync's per-message FETCH-with-UID-and-
+	 * MODSEQ data, held back for the same VANISHED-before-FETCH
+	 * ordering reason as vanished_ranges. Holds full struct
+	 * imsg_mbox_fetch_meta copies (not just seqno/uid) since
+	 * session_send_qresync_fetch_response() needs FLAGS too.
 	 */
 	struct imsg_mbox_fetch_meta *qresync_fetches;
 	uint32_t		 qresync_nfetches;
 	uint32_t		 qresync_fetches_cap;
 
 	/*
-	 * COPY/MOVE (RFC 9051 SS6.4.7/SS6.4.8), SESSION_COPYING. Two parallel
-	 * growable arrays accumulating each IMSG_MBOX_COPY_MAPPING streamed
-	 * in during the round trip -- src_uid/dest_uid pairs, already
-	 * ascending (store.c streams its single forward pass in increasing
-	 * order, same guarantee vanished_ranges above relies on), range-
-	 * compacted independently via format_seq_list() into COPYUID's two
-	 * UID sets once the terminal reply arrives. cmd_is_move distinguishes
-	 * which of COPY/MOVE is in flight (same session-state-plus-flag
-	 * pattern as s->close_after_expunge for EXPUNGE/CLOSE) -- both share
-	 * SESSION_COPYING since their in-flight bookkeeping is otherwise
-	 * identical, differing only in the terminal formatting (see session_
-	 * finish_copy_or_move()). move_expunged buffers each IMSG_MBOX_
-	 * EXPUNGED that arrives during a MOVE specifically (rather than
-	 * writing it to the client immediately, the way a real EXPUNGE/CLOSE
-	 * does) -- SS6.4.8 requires COPYUID to precede any EXPUNGE/VANISHED
-	 * for the same operation, which (like vanished_ranges/qresync_fetches
-	 * above) is only guaranteed by holding everything until the terminal
-	 * reply, not by relaying each piece as it streams in.
+	 * COPY/MOVE (RFC 9051 SS6.4.7/SS6.4.8), SESSION_COPYING. Two
+	 * parallel growable arrays accumulate each IMSG_MBOX_COPY_MAPPING
+	 * (src_uid/dest_uid, already ascending), range-compacted into
+	 * COPYUID's two UID sets once the terminal reply arrives.
+	 * cmd_is_move distinguishes COPY from MOVE (both share
+	 * SESSION_COPYING, differing only in terminal formatting).
+	 * move_expunged buffers each IMSG_MBOX_EXPUNGED from a MOVE rather
+	 * than writing it immediately, since SS6.4.8 requires COPYUID to
+	 * precede any EXPUNGE/VANISHED for the same operation.
 	 */
 	uint32_t		*copy_src_uids;
 	uint32_t		*copy_dest_uids;
 	uint32_t		 copy_n;
 	uint32_t		 copy_cap;
-	int			 copy_alloc_failed; /* same "don't silently drop
-						 * a mapping" reasoning as
+	int			 copy_alloc_failed; /* same reasoning as
 						 * s->search_alloc_failed */
 	int			 cmd_is_move;
 	struct imsg_mbox_expunged *move_expunged;
 	uint32_t		 move_expunged_n;
 	uint32_t		 move_expunged_cap;
 
-	/*
-	 * Accumulates messages that failed a STORE's UNCHANGEDSINCE test,
-	 * streamed in via zero or more IMSG_MBOX_STORE_MODIFIED while
-	 * s->state == SESSION_STORING, for session_handle_mbox_result() to
-	 * range-compact (format_seq_list(), same helper SEARCH/ESEARCH
-	 * already use) into the tagged response's MODIFIED response code
-	 * (RFC 7162 SS3.1.3). Holds sequence numbers for a plain STORE, UIDs
-	 * for a UID STORE (SS3.1.3: "the message number (or unique
-	 * identifier in the case of the UID STORE command)") -- a single
-	 * array, not a parallel uid array, since exactly one of the two
-	 * values is ever meaningful for a given STORE; see session_handle_
-	 * store_modified()'s use of s->cmd_by_uid to pick which.
+	/* Accumulates messages that failed a STORE's UNCHANGEDSINCE test
+	 * (IMSG_MBOX_STORE_MODIFIED), range-compacted by session_handle_
+	 * mbox_result() into the tagged response's MODIFIED code (RFC 7162
+	 * SS3.1.3). Holds sequence numbers for a plain STORE, UIDs for a
+	 * UID STORE, a single array, since only one is ever meaningful
+	 * per STORE (s->cmd_by_uid picks which).
 	 */
 	uint32_t		*store_modified;
 	uint32_t		 store_modified_n;
 	uint32_t		 store_modified_cap;
 
 	/*
-	 * RFC 9051 SS6.4.9 (UID command), added this pass: 1 if the async
-	 * operation currently in flight (SESSION_FETCHING/STORING/SEARCHING/
-	 * EXPUNGING) was dispatched via UID <cmd> rather than the bare
-	 * command. Every entry point that starts one of those four states
-	 * sets this explicitly (fetch_dispatch()/store_do()/search_dispatch()/
-	 * session_request_expunge()), so a stale value from a previous,
-	 * different-mode command can never leak into the next one -- there is
-	 * no "clear it back to 0" step anywhere else, matching this struct's
-	 * existing condstore_enabled/qresync_enabled precedent of "every
-	 * setter is unconditional, so no separate reset is needed."
+	 * RFC 9051 SS6.4.9 (UID command): 1 if the in-flight async
+	 * operation (FETCHING/STORING/SEARCHING/EXPUNGING) was dispatched
+	 * via UID <cmd> rather than the bare command. Every entry point
+	 * that starts one of those states sets this explicitly, so no
+	 * separate reset is needed.
 	 *
-	 * Consulted by: session_send_store_fetch_response() (UID STORE's
-	 * FETCH echo must include UID, SS6.4.9's "MUST implicitly include
-	 * the UID message data item"), session_handle_mbox_search_match()/
-	 * session_finish_search() (UID SEARCH reports UIDs instead of
-	 * sequence numbers, plus the "UID" ESEARCH correlator token),
-	 * session_handle_store_modified() (UID STORE's MODIFIED response
-	 * code lists UIDs, not sequence numbers -- RFC 7162 SS3.1.3: "the
-	 * message number (or unique identifier in the case of the UID STORE
-	 * command)"), and session_handle_mbox_result() (every one of the four
-	 * commands' tagged completion text becomes "UID <CMD> completed" --
-	 * RFC 9051's own SS6.4.9 example shows "UID FETCH completed"
-	 * verbatim; UID EXPUNGE's own worked example shows "UID EXPUNGE
-	 * completed"; UID STORE/UID SEARCH aren't shown as literal worked
-	 * examples but follow the same "UID <cmd> completed" pattern by
-	 * direct symmetry with those two, not fabricated). Plain FETCH
-	 * doesn't need to consult this at all -- forcing MBOX_FETCH_UID into
-	 * s->fetch_attrs at dispatch time (see fetch_dispatch()) already
-	 * makes session_send_fetch_response()'s existing attrs-driven UID
-	 * printing do the right thing with no further changes there.
+	 * Consulted by session_send_store_fetch_response() (UID STORE's
+	 * FETCH echo must include UID), session_handle_mbox_search_match()/
+	 * session_finish_search() (UID SEARCH reports UIDs, plus the "UID"
+	 * ESEARCH correlator), session_handle_store_modified() (MODIFIED
+	 * lists UIDs for UID STORE), and session_handle_mbox_result()
+	 * (tagged completion text becomes "UID <CMD> completed"). Plain
+	 * FETCH doesn't need this, forcing MBOX_FETCH_UID into
+	 * s->fetch_attrs at dispatch time already handles it.
 	 */
 	int			 cmd_by_uid;
 
 	TAILQ_ENTRY(session)	 entry;
 };
 
-/*
- * Named (not anonymous) so the type matches between this extern
- * declaration and listener.c's actual definition -- TAILQ_HEAD(, session)
- * with an empty name expands to a distinct anonymous struct at each use,
- * which the original single-file listener.c got away with (only one use
- * existed at all) but which is a hard redefinition error once that one
- * use is split across a header and a .c file.
+/* Named (not anonymous) so the type matches between this extern
+ * declaration and listener.c's definition, an anonymous
+ * TAILQ_HEAD(, session) would be a redefinition error once split across
+ * a header and a .c file.
  */
 TAILQ_HEAD(session_list, session);
 
 /*
  * Globals shared across the files this process's source is split into
- * (definitions live in listener.c's core; see that file for why each
- * exists).
+ * (definitions live in listener.c's core).
  */
 extern struct session_list	 sessions;
 extern struct imsgev		 iev_auth;
 extern struct imsgev		 iev_parent;
 extern struct tls		*listener_tls_ctx;
 
-/*
- * Cross-file entry points. This is the same set of forward declarations
- * listener.c carried as `static` prototypes before the file was split
- * into listener.c (core) + auth_cmd.c + mailbox_cmd.c + append_cmd.c +
- * fetch_cmd.c + search_cmd.c + store_cmd.c + store_ipc.c -- moved here
- * verbatim (minus `static`, which no longer applies once callers and
- * callees can live in different translation units) rather than
- * redesigned, so this split is a pure move with no behavior change.
+/* Cross-file entry points: forward declarations for listener.c (core) +
+ * auth_cmd.c + mailbox_cmd.c + append_cmd.c + fetch_cmd.c + search_cmd.c
+ * + store_cmd.c + store_ipc.c.
  */
  void	 listener_accept(int, short, void *);
  void	 listener_dispatch_auth(int, short, void *);
@@ -1060,16 +634,9 @@ extern struct tls		*listener_tls_ctx;
  int	 session_finish_append(struct session *);
  void	 session_handle_mbox_appended(struct session *,
 		    const struct imsg_mbox_appended *);
-/*
- * Real, initialized definition lives in fetch_cmd.c alongside
- * format_internaldate() itself; append_cmd.c's parse_date_time() and
- * search_cmd.c's parse_search_date() both reuse it for the reverse
- * (name-to-index) direction. The original single-file listener.c got
- * away with a same-file tentative "const char *fetch_month_names[12];"
- * forward declaration ahead of its real, initialized definition further
- * down -- valid C, but only within one translation unit; split across
- * files, that trick doesn't reach across them, so this needs to be a
- * real extern instead.
+/* Definition lives in fetch_cmd.c alongside format_internaldate();
+ * append_cmd.c's parse_date_time() and search_cmd.c's parse_search_date()
+ * reuse it for the reverse (name-to-index) direction.
  */
 extern const char	*fetch_month_names[12];
  void	 format_internaldate(int64_t, char *, size_t);
@@ -1188,17 +755,11 @@ struct search_parse_ctx;
  int	 cmd_starttls(struct session *, const char *, char *);
  int	 cmd_authenticate(struct session *, const char *, char *);
 
-/* command-auth (RFC 9051 SS6.3 -- valid in Authenticated or Selected
- * state): ENABLE is real (see cmd_enable()'s comment for why that's a
- * complete implementation, not a stub, for v1). Everything else here
- * needs a mailbox layer that doesn't exist yet -- store.c's IMSG_MBOX_*
- * family is entirely unimplemented (see store.c's own header comment),
- * and its wire payload shapes aren't designed, so these all reply a
- * generic NO via stub_not_implemented() rather than doing anything real.
- * They're in the dispatch table anyway, correctly gated by session
- * state, because "recognized command, can't do it right now" (NO) and
- * "not a real IMAP command at all" (BAD, session_handle_line()'s unknown-
- * command fallback) are different, both spec-meaningful responses. */
+/* command-auth (RFC 9051 SS6.3, valid in Authenticated or Selected
+ * state). stub_not_implemented() remains for any future command added
+ * to the dispatch table before its handler is real, "recognized,
+ * can't do it right now" (NO) is spec-meaningful, distinct from
+ * "unknown command" (BAD). */
  int	 stub_not_implemented(struct session *, const char *,
 		    const char *);
  int	 cmd_enable(struct session *, const char *, char *);
@@ -1216,11 +777,7 @@ struct search_parse_ctx;
  int	 cmd_append(struct session *, const char *, char *);
  int	 cmd_idle(struct session *, const char *, char *);
 
-/* command-select (RFC 9051 SS6.4 -- valid only in Selected state).
- * SESSION_SELECTED is currently unreachable (SELECT itself is a stub
- * above), so none of these can be dispatched to yet in practice -- listed
- * for the same "correctly gated, honestly stubbed" reason as command-auth
- * above. */
+/* command-select (RFC 9051 SS6.4, valid only in Selected state). */
  int	 cmd_close(struct session *, const char *, char *);
  int	 cmd_unselect(struct session *, const char *, char *);
  int	 cmd_expunge(struct session *, const char *, char *);
blob - 4df72d97e7332fa0c20a4bd7bfd9e9ae4b99ce13
blob + faf6403faeed92cc39bf3f8af63c7b7d54a31e53
--- src/log.c
+++ src/log.c
@@ -10,7 +10,7 @@
  * credit him in their own log.c). This file's implementation details
  * (log_procname storage, log_init()'s parameters) are this project's own,
  * but the API shape and name are that same shared idiom, so his copyright
- * is carried forward here too -- same rationale as parse.y's copyright
+ * is carried forward here too, same rationale as parse.y's copyright
  * chain.
  *
  * Permission to use, copy, modify, and distribute this software for any
blob - 0e32858221d95f544b0de3cc47589ed40fdcbd5a
blob + e9c855b6203bc27068a064fdffffcec766212edb
--- src/log.h
+++ src/log.h
@@ -3,7 +3,7 @@
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
  *
  * This header's call signatures were deliberately matched to smtpd's own
- * log.h (see the comment below) -- that log_init()/fatal()/fatalx()/
+ * log.h (see the comment below), that log_init()/fatal()/fatalx()/
  * log_warn()/log_debug() API is the same shared daemon-logging idiom
  * Henning Brauer wrote and that smtpd, bgpd, and most other privsep
  * daemons in OpenBSD base carry his copyright for. Same rationale as
@@ -24,11 +24,7 @@
 
 /*
  * Minimal logging, matching the call signatures actually observed in the
- * uploaded smtpd.c this session (fatal("pledge"), fatalx("chdir"),
- * log_debug("setup_done: %s[%d] done", p->name, p->pid),
- * log_procinit(proc_title(smtpd_process))) -- those call sites are real,
- * quoted material; this implementation of them is this project's own, not
- * copied from smtpd's actual log.c (not among the uploaded files).
+ * uploaded smtpd.c this session.
  */
 
 #ifndef OPENIMAP_LOG_H
blob - 057cc76fd1e66b7c12523384292371c4cdd5bba9
blob + 1b18e20d63c7e39c3f55c43dedf0da5e1ef134c8
--- src/mailbox_cmd.c
+++ src/mailbox_cmd.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* mailbox_cmd.c -- SELECT/EXAMINE/CREATE/DELETE/RENAME/SUBSCRIBE/LIST/NAMESPACE/STATUS handlers. */
+/* mailbox_cmd.c, SELECT/EXAMINE/CREATE/DELETE/RENAME/SUBSCRIBE/LIST/NAMESPACE/STATUS handlers. */
 
 #include <sys/types.h>
 #include <sys/queue.h>
@@ -106,7 +106,7 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele
 		p++;
 	}
 	if (strchr(tok, ',') != NULL) {
-		*errmsg = "comma-separated known-uids not supported in v1";
+		*errmsg = "comma-separated known-uids not supported";
 		return (-1);
 	}
 	{
@@ -115,7 +115,7 @@ parse_qresync_group(char *inner, struct imsg_mbox_sele
 
 		if (parse_seq_range(tok, &lo, &hi, &lo_star, &hi_star) == -1 ||
 		    lo_star || hi_star) {
-			*errmsg = "invalid known-uids -- '*' is not allowed "
+			*errmsg = "invalid known-uids, '*' is not allowed "
 			    "here (RFC 7162 SS3.2.5.1)";
 			return (-1);
 		}
@@ -298,7 +298,7 @@ select_or_examine(struct session *s, const char *tag, 
 	}
 
 	if (s->store_iev == NULL) {
-		/* should already be wired here -- ST_AUTH requires SESSION_AUTHENTICATED/SELECTED */
+		/* should already be wired here, ST_AUTH requires SESSION_AUTHENTICATED/SELECTED */
 		log_warnx("session %u: %s with no store channel wired",
 		    s->id, cmdname);
 		session_reply(s, tag, "NO", "[SERVERBUG] internal error");
@@ -602,7 +602,7 @@ cmd_unsubscribe(struct session *s, const char *tag, ch
 	return stub_not_implemented(s, tag, "UNSUBSCRIBE");
 }
 
-/* RFC 9051 SS6.3.9 wildcards, collapsed to "zero or more of anything" -- v1's flat namespace has no delimiter */
+/* RFC 9051 SS6.3.9 wildcards, collapsed to "zero or more of anything", flat namespace has no delimiter */
 int
 list_pattern_match(const char *pat, const char *name, int ci)
 {
@@ -712,7 +712,7 @@ list_dispatch(struct session *s, const char *tag, char
 		p++;
 
 	if (*p == '(') {
-		/* SS6.3.9 condition 1: first word after the command starts with "(" -- list-select-opts */
+		/* SS6.3.9 condition 1: first word after the command starts with "(", list-select-opts */
 		snprintf(text, sizeof(text),
 		    "extended %s selection options not supported", cmdname);
 		session_reply(s, tag, "NO", text);
@@ -729,7 +729,7 @@ list_dispatch(struct session *s, const char *tag, char
 		p++;
 
 	if (*p == '(') {
-		/* SS6.3.9 condition 2: second word starts with "(" -- parenthesized `patterns` form */
+		/* SS6.3.9 condition 2: second word starts with "(", parenthesized `patterns` form */
 		snprintf(text, sizeof(text),
 		    "extended %s mailbox-pattern lists not supported",
 		    cmdname);
@@ -745,14 +745,14 @@ list_dispatch(struct session *s, const char *tag, char
 	while (*p == ' ')
 		p++;
 	if (*p != '\0') {
-		/* SS6.3.9 condition 3: more than 2 parameters -- trailing list-return-opts */
+		/* SS6.3.9 condition 3: more than 2 parameters, trailing list-return-opts */
 		snprintf(text, sizeof(text),
 		    "extended %s return options not supported", cmdname);
 		session_reply(s, tag, "NO", text);
 		return (1);
 	}
 
-	/* RFC 9051 SS6.3.9: empty pattern requests the delimiter/root; v1 has no hierarchy, always empty root */
+	/* RFC 9051 SS6.3.9: empty pattern requests the delimiter/root; always empty root */
 	if (pattern[0] == '\0') {
 		snprintf(text, sizeof(text), "%s (\\Noselect) \"/\" \"\"", kw);
 		session_untagged(s, text);
@@ -777,7 +777,7 @@ list_dispatch(struct session *s, const char *tag, char
 
 	/* SS6.3.9: unaccepted pattern MUST be silently ignored; INBOX answered synchronously, no store round trip */
 	if (list_pattern_match(canon, "INBOX", 1)) {
-		/* "()" -- SS7.3.1 makes every attribute optional; INBOX has no children and is selectable */
+		/* "()", SS7.3.1 makes every attribute optional; INBOX has no children and is selectable */
 		snprintf(text, sizeof(text), "%s () \"/\" INBOX", kw);
 		session_untagged(s, text);
 	}
@@ -947,5 +947,3 @@ cmd_status(struct session *s, const char *tag, char *a
 
 	return (1);
 }
-
-/* v1's whole-message-in-one-imsg design caps literal size to fit MAX_IMSGSIZE (16384); also read-side cap */
blob - 3296ea513e7104ad1d7c66bbd2ab5a8277a1bacf
blob + e023cd32c16c4ccb8bd55c98e948d355ee0047e7
--- src/main.c
+++ src/main.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* main.c -- imapd(8) entry point: parses argv, dispatches to the role named by "-x". */
+/* main.c, imapd(8) entry point: parses argv, dispatches to the role named by "-x". */
 
 #include <sys/types.h>
 
blob - a84ea914205d6836057a543fd1d357b47cb41d6f
blob + ccd5b0e54b123e49096895d2a72789ba83ac746e
--- src/mbox_copy.c
+++ src/mbox_copy.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mbox_copy.c -- COPY and MOVE request handling, both
+ * mbox_copy.c, COPY and MOVE request handling, both
  * same-mailbox and cross-mailbox.
  */
 
@@ -105,7 +105,7 @@ stage_copy_messages(struct mbox_index *idx, struct ims
 		if (locate_message_file(rec.basename, &size, suffix,
 		    sizeof(suffix)) == -1) {
 			log_warnx("session %u: COPY: message %s indexed but "
-			    "missing on disk -- failing whole COPY (partial "
+			    "missing on disk, failing whole COPY (partial "
 			    "copy not permitted, RFC 9051 SS6.4.7)",
 			    session_id, rec.basename);
 			goto fail;
@@ -114,13 +114,13 @@ stage_copy_messages(struct mbox_index *idx, struct ims
 		/* refuse before allocating if this message or the running total exceeds the staging limits */
 		if ((uint64_t)size > COPY_STAGE_MSG_MAX) {
 			log_warnx("session %u: COPY: message %s is %lld bytes, "
-			    "over the per-message staging limit -- failing COPY",
+			    "over the per-message staging limit, failing COPY",
 			    session_id, rec.basename, (long long)size);
 			goto fail;
 		}
 		if ((uint64_t)size > COPY_STAGE_TOTAL_MAX - staged_total) {
 			log_warnx("session %u: COPY: staged data would exceed the "
-			    "total staging limit -- failing COPY", session_id);
+			    "total staging limit, failing COPY", session_id);
 			goto fail;
 		}
 		staged_total += (uint64_t)size;
@@ -130,7 +130,7 @@ stage_copy_messages(struct mbox_index *idx, struct ims
 		if (strlcpy(cs.keywords, rec.keywords, sizeof(cs.keywords)) >=
 		    sizeof(cs.keywords)) {
 			log_warnx("session %u: COPY: keywords too long for "
-			    "%s -- failing COPY", session_id, rec.basename);
+			    "%s, failing COPY", session_id, rec.basename);
 			goto fail;
 		}
 		lp = strstr(suffix, "2,");
@@ -357,7 +357,7 @@ handle_mbox_copy(struct imsg_mbox_copy *req, struct im
 
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
-		log_warnx("session %u: COPY: current_mailbox_dir truncated -- "
+		log_warnx("session %u: COPY: current_mailbox_dir truncated, "
 		    "can't happen (same-size buffers)", session_id);
 		ok = 0;
 		goto done;
@@ -404,7 +404,7 @@ handle_mbox_copy(struct imsg_mbox_copy *req, struct im
 			    strlcpy(second, desttarget, sizeof(second)) >=
 			    sizeof(second)) {
 				log_warnx("session %u: COPY: mailbox name "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "buffers)", session_id);
 				ok = 0;
 				goto done;
@@ -416,7 +416,7 @@ handle_mbox_copy(struct imsg_mbox_copy *req, struct im
 			    strlcpy(second, saved, sizeof(second)) >=
 			    sizeof(second)) {
 				log_warnx("session %u: COPY: mailbox name "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "buffers)", session_id);
 				ok = 0;
 				goto done;
@@ -483,7 +483,7 @@ handle_mbox_copy(struct imsg_mbox_copy *req, struct im
 		srcidx = first_is_dest ? &idx_b : &idx_a;
 		destidx = first_is_dest ? &idx_a : &idx_b;
 
-		/* Back to source -- staging below reads relative to cwd. */
+		/* Back to source, staging below reads relative to cwd. */
 		if (select_mailbox_dir(saved) == -1) {
 			log_warnx("session %u: COPY: couldn't return to %s "
 			    "to stage messages", session_id, saved);
@@ -549,7 +549,7 @@ done:
 	imsgev_add(iev);
 }
 
-/* MOVE within same mailbox: range membership below must test "in" (read pos), not "out" (compaction write index) -- "out" under-consumed the range. */
+/* MOVE within same mailbox: range membership below must test "in" (read pos), not "out" (compaction write index), "out" under-consumed the range. */
 
 static int
 move_same_mailbox(struct imsg_mbox_copy *req, struct mbox_index *idx,
@@ -629,7 +629,7 @@ move_same_mailbox(struct imsg_mbox_copy *req, struct m
 		    sizeof(moved[nmoved].keywords)) >=
 		    sizeof(moved[nmoved].keywords)) {
 			log_warnx("session %u: MOVE: basename/keywords too "
-			    "long for %s -- failing MOVE", session_id,
+			    "long for %s, failing MOVE", session_id,
 			    rec.basename);
 			free(idx->lines[in]);
 			ok = 0;
@@ -745,7 +745,7 @@ move_cross_mailbox(struct imsg_mbox_copy *req, struct 
 		goto cleanup;
 	}
 
-	/* destination commit is durable -- now remove originals from source; cwd is at destination, return to saved dir first */
+	/* destination commit is durable, now remove originals from source; cwd is at destination, return to saved dir first */
 	if (select_mailbox_dir(saved) == -1) {
 		log_warnx("session %u: MOVE: committed to destination but "
 		    "couldn't return to source %s to remove the originals "
@@ -809,7 +809,7 @@ move_cross_mailbox(struct imsg_mbox_copy *req, struct 
 
 	if (index_save(srcidx) == -1) {
 		log_warnx("session %u: MOVE: destination commit succeeded "
-		    "but saving the source's compacted index failed -- "
+		    "but saving the source's compacted index failed, "
 		    "message(s) may remain duplicated", session_id);
 		ok = 0;
 		goto cleanup;
@@ -846,7 +846,7 @@ handle_mbox_move(struct imsg_mbox_copy *req, struct im
 
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
-		log_warnx("session %u: MOVE: current_mailbox_dir truncated -- "
+		log_warnx("session %u: MOVE: current_mailbox_dir truncated, "
 		    "can't happen (same-size buffers)", session_id);
 		ok = 0;
 		goto done;
@@ -893,7 +893,7 @@ handle_mbox_move(struct imsg_mbox_copy *req, struct im
 			    strlcpy(second, desttarget, sizeof(second)) >=
 			    sizeof(second)) {
 				log_warnx("session %u: MOVE: mailbox name "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "buffers)", session_id);
 				ok = 0;
 				goto done;
@@ -905,7 +905,7 @@ handle_mbox_move(struct imsg_mbox_copy *req, struct im
 			    strlcpy(second, saved, sizeof(second)) >=
 			    sizeof(second)) {
 				log_warnx("session %u: MOVE: mailbox name "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "buffers)", session_id);
 				ok = 0;
 				goto done;
@@ -1021,4 +1021,3 @@ done:
 		    session_id);
 	imsgev_add(iev);
 }
-
blob - 335ff535eb3abdf54b70601be59012780c46af82
blob + d861857885440699c0eaa29c21ea45c9d67a66e4
--- src/mbox_fetch.c
+++ src/mbox_fetch.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mbox_fetch.c -- FETCH and STATUS request handling.
+ * mbox_fetch.c, FETCH and STATUS request handling.
  */
 
 #include <sys/types.h>
@@ -84,7 +84,7 @@ handle_mbox_fetch(struct imsg_mbox_fetch *req, struct 
 	if (!req->by_uid && hi > (uint32_t)idx.nlines)
 		hi = (uint32_t)idx.nlines;
 
-	/* RFC 7162 SS3.2.6: VANISHED (EARLIER) MUST precede FETCH responses -- guaranteed by send order here */
+	/* RFC 7162 SS3.2.6: VANISHED (EARLIER) MUST precede FETCH responses, guaranteed by send order here */
 	if (req->by_uid && req->want_vanished)
 		send_vanished_range(&idx, lo, hi, iev);
 
@@ -123,7 +123,7 @@ handle_mbox_fetch(struct imsg_mbox_fetch *req, struct 
 			if (locate_message_file(rec.basename, &size, suffix,
 			    sizeof(suffix)) == -1) {
 				log_warnx("session %u: message %s (uid %u) "
-				    "indexed but missing on disk -- skipped",
+				    "indexed but missing on disk, skipped",
 				    session_id, rec.basename, meta.uid);
 				continue;
 			}
@@ -520,7 +520,7 @@ handle_mbox_status(struct imsg_mbox_status *req, struc
 			if (locate_message_file(rec.basename, &size, suffix,
 			    sizeof(suffix)) == -1) {
 				log_warnx("session %u: message %s indexed but "
-				    "missing on disk -- skipped for STATUS "
+				    "missing on disk, skipped for STATUS "
 				    "UNSEEN/DELETED/SIZE", session_id,
 				    rec.basename);
 				continue;
@@ -544,7 +544,7 @@ send:
 	/* restore whatever this session had selected before STATUS (no-op if already there) */
 	if (switched && select_mailbox_dir(saved) == -1)
 		log_warnx("session %u: STATUS: failed to restore selection "
-		    "to %s -- session may be left in an inconsistent state",
+		    "to %s, session may be left in an inconsistent state",
 		    session_id, saved[0] != '\0' ? saved : "INBOX");
 	if (imsg_compose(&iev->ibuf, IMSG_MBOX_STATUS_RESULT, 0, 0, -1, &reply,
 	    sizeof(reply)) == -1)
blob - 3509890981573d844a3f5185b7e774a973ac2410
blob + d8b7cd3d2492401da8e5323e7f7f54ab57647d1e
--- src/mbox_manage.c
+++ src/mbox_manage.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mbox_manage.c -- CREATE/DELETE/RENAME/LIST/APPEND/SELECT
+ * mbox_manage.c, CREATE/DELETE/RENAME/LIST/APPEND/SELECT
  * request handling.
  */
 
@@ -150,11 +150,11 @@ handle_mbox_create(struct imsg_mbox_create *req, struc
 		goto send;
 	}
 
-	/* mkdir/ensure_maildir_dirs() below need cwd at maildir root, not wherever a SELECTed mailbox left it -- visit root first */
+	/* mkdir/ensure_maildir_dirs() below need cwd at maildir root, not wherever a SELECTed mailbox left it, visit root first */
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
 		log_warnx("session %u: CREATE %s: current_mailbox_dir "
-		    "truncated -- can't happen (same-size buffers)",
+		    "truncated, can't happen (same-size buffers)",
 		    session_id, req->mailbox);
 		result.error = MBOX_OP_ERR_GENERIC;
 		goto send;
@@ -250,7 +250,7 @@ remove_maildir_subtree(const char *prefix)
 				continue;
 			}
 			if (!S_ISREG(st.st_mode)) {
-				log_warnx("session %u: DELETE: refusing -- "
+				log_warnx("session %u: DELETE: refusing, "
 				    "%s is not a regular file", session_id,
 				    entpath);
 				ok = 0;
@@ -286,7 +286,7 @@ remove_maildir_subtree(const char *prefix)
 	return (0);
 }
 
-/* IMSG_MBOX_DELETE (RFC 9051 SS6.3.5); no check for "is this session's own SELECTed mailbox" -- leaves store child at root until next SELECT resolves it */
+/* IMSG_MBOX_DELETE (RFC 9051 SS6.3.5); no check for "is this session's own SELECTed mailbox", leaves store child at root until next SELECT resolves it */
 
 void
 handle_mbox_delete(struct imsg_mbox_delete *req, struct imsgev *iev)
@@ -311,7 +311,7 @@ handle_mbox_delete(struct imsg_mbox_delete *req, struc
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
 		log_warnx("session %u: DELETE %s: current_mailbox_dir "
-		    "truncated -- can't happen (same-size buffers)",
+		    "truncated, can't happen (same-size buffers)",
 		    session_id, req->mailbox);
 		result.error = MBOX_OP_ERR_GENERIC;
 		goto send;
@@ -387,7 +387,7 @@ handle_mbox_rename(struct imsg_mbox_rename *req, struc
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
 		log_warnx("session %u: RENAME %s -> %s: current_mailbox_dir "
-		    "truncated -- can't happen (same-size buffers)",
+		    "truncated, can't happen (same-size buffers)",
 		    session_id, req->oldname, req->newname);
 		result.error = MBOX_OP_ERR_GENERIC;
 		goto send;
@@ -499,7 +499,7 @@ handle_mbox_list(struct imsgev *iev)
 		memset(&item, 0, sizeof(item));
 		if (strlcpy(item.mailbox, de->d_name, sizeof(item.mailbox)) >=
 		    sizeof(item.mailbox)) {
-			log_warnx("session %u: LIST: %s truncated -- can't "
+			log_warnx("session %u: LIST: %s truncated, can't "
 			    "happen (mailbox_name_valid() already bounds it "
 			    "under MBOX_NAME_MAX)", session_id, de->d_name);
 			continue;
@@ -541,7 +541,7 @@ append_hostname(void)
 			if (strlcpy(hostbuf, "imapd", sizeof(hostbuf)) >=
 			    sizeof(hostbuf))
 				log_warnx("session %u: gethostname fallback "
-				    "truncated -- can't happen", session_id);
+				    "truncated, can't happen", session_id);
 		}
 		resolved = 1;
 	}
@@ -571,7 +571,7 @@ handle_mbox_append(struct imsg_mbox_append *req, const
 	if (strlcpy(saved, current_mailbox_dir, sizeof(saved)) >=
 	    sizeof(saved)) {
 		log_warnx("session %u: APPEND %s: current_mailbox_dir "
-		    "truncated -- can't happen (same-size buffers)",
+		    "truncated, can't happen (same-size buffers)",
 		    session_id, req->mailbox);
 		reply.error = MBOX_OP_ERR_NO_SUCH_MAILBOX;
 		goto done_reply;
@@ -700,7 +700,7 @@ handle_mbox_append(struct imsg_mbox_append *req, const
 	locked = 0;
 	index_free(&idx);
 
-	/* index committed -- now the rename; index-then-rename is the safer order to fail partway through */
+	/* index committed, now the rename; index-then-rename is the safer order to fail partway through */
 	sysflags_to_letters(req->sysflags, letters, sizeof(letters));
 	if (snprintf(curpath, sizeof(curpath), "cur/%s:2,%s", basename,
 	    letters) >= (int)sizeof(curpath)) {
@@ -738,5 +738,3 @@ done_reply:
 		    session_id);
 	imsgev_add(iev);
 }
-
-/* resolves a mailbox name to select_mailbox_dir()'s expected "target" string; checks syntax only, not existence */
blob - 300c2c9bd0fed6acd4ee626f7994d3596fb66784
blob + 16d378de99112160ecb1631e514644ca190af35a
--- src/mbox_search.c
+++ src/mbox_search.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mbox_search.c -- SEARCH key evaluation.
+ * mbox_search.c, SEARCH key evaluation.
  */
 
 #include <sys/types.h>
@@ -48,7 +48,7 @@ kw_list_contains(const char *list, const char *kw)
 
 	if (strlcpy(tmp, list, sizeof(tmp)) >= sizeof(tmp)) {
 		log_warnx("session %u: kw_list_contains: keyword list "
-		    "truncated -- can't happen (both same-size "
+		    "truncated, can't happen (both same-size "
 		    "MBOX_FLAGS_MAX buffers); treating as not-found",
 		    session_id);
 		return (0);
@@ -76,7 +76,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 	if (mode == MBOX_STORE_REMOVE) {
 		if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) {
 			log_warnx("session %u: merge_keywords: old_kws "
-			    "truncated -- can't happen (same-size "
+			    "truncated, can't happen (same-size "
 			    "MBOX_FLAGS_MAX buffers); refusing to guess at "
 			    "the keyword set", session_id);
 			return;
@@ -97,7 +97,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 			}
 			if (strlcat(out, tok, outsize) >= outsize) {
 				log_warnx("session %u: merge_keywords: out "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "MBOX_FLAGS_MAX buffers)", session_id);
 				return;
 			}
@@ -109,7 +109,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 	if (mode == MBOX_STORE_ADD) {
 		if (strlcpy(tmp, old_kws, sizeof(tmp)) >= sizeof(tmp)) {
 			log_warnx("session %u: merge_keywords: old_kws "
-			    "truncated -- can't happen (same-size "
+			    "truncated, can't happen (same-size "
 			    "MBOX_FLAGS_MAX buffers); refusing to guess at "
 			    "the keyword set", session_id);
 			return;
@@ -128,7 +128,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 			}
 			if (strlcat(out, tok, outsize) >= outsize) {
 				log_warnx("session %u: merge_keywords: out "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "MBOX_FLAGS_MAX buffers)", session_id);
 				return;
 			}
@@ -138,7 +138,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 
 	/* SET starts from nothing; ADD continues from old_kws already built above; either way append new_kws not already present */
 	if (strlcpy(tmp, new_kws, sizeof(tmp)) >= sizeof(tmp)) {
-		log_warnx("session %u: merge_keywords: new_kws truncated -- "
+		log_warnx("session %u: merge_keywords: new_kws truncated, "
 		    "can't happen (same-size MBOX_FLAGS_MAX buffers); "
 		    "refusing to guess at the keyword set", session_id);
 		return;
@@ -150,7 +150,7 @@ merge_keywords(int mode, const char *old_kws, const ch
 		if (!first) {
 			if (strlcat(out, ",", outsize) >= outsize) {
 				log_warnx("session %u: merge_keywords: out "
-				    "truncated -- can't happen (same-size "
+				    "truncated, can't happen (same-size "
 				    "MBOX_FLAGS_MAX buffers)", session_id);
 				return;
 			}
@@ -340,7 +340,7 @@ handle_mbox_search(struct imsg_mbox_search *req, struc
 		if (locate_message_file(rec.basename, &size, suffix,
 		    sizeof(suffix)) == -1) {
 			log_warnx("session %u: message %s (uid %u) indexed "
-			    "but missing on disk -- skipped", session_id,
+			    "but missing on disk, skipped", session_id,
 			    rec.basename, m.uid);
 			continue;
 		}
blob - d87a5cd657f34a7b8e207398875e4bd8e2f99e1a
blob + 831abb312f39b67c75e6dd453dca123db64ede3f
--- src/mbox_store.c
+++ src/mbox_store.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mbox_store.c -- STORE and EXPUNGE request handling.
+ * mbox_store.c, STORE and EXPUNGE request handling.
  */
 
 #include <sys/types.h>
@@ -131,7 +131,7 @@ handle_mbox_store(struct imsg_mbox_store *req, struct 
 		if (locate_message_file(rec.basename, &size, suffix,
 		    sizeof(suffix)) == -1) {
 			log_warnx("session %u: message %s (uid %u) indexed "
-			    "but missing on disk -- skipped", session_id,
+			    "but missing on disk, skipped", session_id,
 			    rec.basename, rec.uid);
 			continue;
 		}
@@ -303,7 +303,7 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, str
 		if (locate_message_file(rec.basename, &size, suffix,
 		    sizeof(suffix)) == -1) {
 			log_warnx("session %u: message %s indexed but missing "
-			    "on disk -- kept in index, not counted as "
+			    "on disk, kept in index, not counted as "
 			    "expunged", session_id, rec.basename);
 			idx.lines[out++] = idx.lines[in];
 			continue;
@@ -325,7 +325,7 @@ handle_mbox_expunge(struct imsg_mbox_expunge *req, str
 		if (snprintf(path, sizeof(path), "%s/%s%s",
 		    suffix[0] == '\0' ? "new" : "cur", rec.basename, suffix) >=
 		    (int)sizeof(path)) {
-			log_warnx("session %u: path too long for %s -- kept "
+			log_warnx("session %u: path too long for %s, kept "
 			    "in index", session_id, rec.basename);
 			idx.lines[out++] = idx.lines[in];
 			continue;
blob - aee52ad2f5fc396dbbc89817817034937985e94e
blob + a14d666a57d963e0630fd248e8b19416d7b92a64
--- src/mime.c
+++ src/mime.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * mime.c -- header and MIME parsing shared across FETCH,
+ * mime.c, header and MIME parsing shared across FETCH,
  * ENVELOPE, and BODYSTRUCTURE: content-type, multipart splitting, and
  * per-part location.
  */
@@ -86,7 +86,7 @@ locate_message_file(const char *basename, off_t *size_
 				if (strlcpy(suffix_out, de->d_name + baselen,
 				    suffix_out_size) >= suffix_out_size) {
 					log_warnx("session %u: %s: flag "
-					    "suffix truncated -- refusing to "
+					    "suffix truncated, refusing to "
 					    "report a possibly-wrong flag "
 					    "set", session_id, de->d_name);
 				} else {
@@ -177,7 +177,7 @@ read_message_header(const char *basename, char **buf_o
 	for (i = 0; i + 1 < (size_t)total; i++) {
 		if (readbuf[i] == '\0') {
 			log_warnx("session %u: message %s has a NUL byte in "
-			    "its header -- BODY.PEEK[HEADER] skipped",
+			    "its header, BODY.PEEK[HEADER] skipped",
 			    session_id, basename);
 			return (-1);
 		}
@@ -195,7 +195,7 @@ read_message_header(const char *basename, char **buf_o
 
 	if (sepindex == -1 || sepindex > FETCH_HEADER_MAX) {
 		log_warnx("session %u: message %s: no header/body separator "
-		    "found within %d bytes -- BODY.PEEK[HEADER] skipped",
+		    "found within %d bytes, BODY.PEEK[HEADER] skipped",
 		    session_id, basename, FETCH_HEADER_MAX);
 		return (-1);
 	}
@@ -252,7 +252,7 @@ read_message_body(const char *basename, int text_only,
 	close(fd);
 
 	if (total > (ssize_t)maxlen) {
-		log_warnx("session %u: message %s exceeds %zu bytes -- "
+		log_warnx("session %u: message %s exceeds %zu bytes, "
 		    "%s skipped", session_id, basename, maxlen, label);
 		free(readbuf);
 		return (-1);
@@ -260,7 +260,7 @@ read_message_body(const char *basename, int text_only,
 
 	for (i = 0; i < (size_t)total; i++) {
 		if (readbuf[i] == '\0') {
-			log_warnx("session %u: message %s has a NUL byte -- "
+			log_warnx("session %u: message %s has a NUL byte, "
 			    "%s skipped", session_id, basename, label);
 			free(readbuf);
 			return (-1);
@@ -282,7 +282,7 @@ read_message_body(const char *basename, int text_only,
 		}
 		if (sepindex == -1) {
 			log_warnx("session %u: message %s: no header/body "
-			    "separator found -- %s skipped",
+			    "separator found, %s skipped",
 			    session_id, basename, label);
 			free(readbuf);
 			return (-1);
@@ -318,7 +318,7 @@ header_field_name_matches(const char *name, size_t nam
 
 	if (strlcpy(listcopy, list, sizeof(listcopy)) >= sizeof(listcopy)) {
 		log_warnx("session %u: header_field_name_matches: list "
-		    "truncated -- can't happen (list bounded by same-size "
+		    "truncated, can't happen (list bounded by same-size "
 		    "HEADER_FIELDS_MAX buffer upstream); treating as "
 		    "not-found", session_id);
 		return (0);
@@ -350,7 +350,7 @@ read_message_header_fields(const char *basename, const
 	if (read_message_header(basename, &hdrbuf, &hdrlen) == -1)
 		return (-1);
 
-	/* filtered output can never exceed the unfiltered header's size -- every byte copied below comes verbatim from hdrbuf */
+	/* filtered output can never exceed the unfiltered header's size, every byte copied below comes verbatim from hdrbuf */
 	if ((out = malloc(hdrlen)) == NULL) {
 		log_warn("session %u: malloc HEADER.FIELDS buffer (%s)",
 		    session_id, basename);
@@ -445,19 +445,19 @@ extract_header_field(const char *hdr, size_t hdrlen, c
 		while (i < hdrlen && hdr[i] != '\n')
 			i++;
 		if (i >= hdrlen)
-			break;	/* malformed -- no trailing blank line found */
+			break;	/* malformed, no trailing blank line found */
 		line_end = (i > field_start && hdr[i - 1] == '\r') ? i - 1 : i;
 		off = i + 1;
 
 		if (line_end == field_start)
-			break;	/* blank line -- end of header, not found */
+			break;	/* blank line, end of header, not found */
 
 		for (k = field_start; k < line_end; k++) {
 			if (hdr[k] == ':')
 				break;
 		}
 
-		/* consume obs-fold lines regardless of name match -- off must land past them to stay positioned for the next field */
+		/* consume obs-fold lines regardless of name match, off must land past them to stay positioned for the next field */
 		while (off < hdrlen && (hdr[off] == ' ' || hdr[off] == '\t')) {
 			size_t	 j = off;
 
@@ -637,7 +637,7 @@ parse_content_type(const char *hdr, size_t hdrlen, cha
 		    strlcpy(subtype_out, "PLAIN", subtypesize) >=
 		    subtypesize) {
 			log_warnx("session %u: parse_content_type: default "
-			    "type/subtype truncated -- caller-supplied "
+			    "type/subtype truncated, caller-supplied "
 			    "buffer too small for \"TEXT\"/\"PLAIN\"",
 			    session_id);
 			return (-1);
@@ -725,14 +725,14 @@ split_multipart(const char *body, size_t bodylen, cons
 	int	 closed = 0;
 
 	if (strlcpy(needle, "--", sizeof(needle)) >= sizeof(needle)) {
-		log_warnx("session %u: split_multipart: \"--\" truncated -- "
+		log_warnx("session %u: split_multipart: \"--\" truncated, "
 		    "can't happen (2 bytes into a 73-byte buffer)",
 		    session_id);
 		return (-1);
 	}
 	needlelen = strlcat(needle, boundary, sizeof(needle));
 	if (needlelen >= sizeof(needle))
-		return (-1);	/* boundary too long -- non-conformant */
+		return (-1);	/* boundary too long, non-conformant */
 
 	*nparts_out = 0;
 
@@ -761,7 +761,7 @@ split_multipart(const char *body, size_t bodylen, cons
 			after++;
 		if (after < bodylen && body[after] != '\r' &&
 		    body[after] != '\n') {
-			pos++;	/* not followed by CRLF/LF/EOF -- coincidental match inside content, not a delimiter */
+			pos++;	/* not followed by CRLF/LF/EOF, coincidental match inside content, not a delimiter */
 			continue;
 		}
 
@@ -791,7 +791,7 @@ split_multipart(const char *body, size_t bodylen, cons
 		else if (after < bodylen && body[after] == '\n')
 			after += 1;
 		else
-			break;	/* delimiter runs to end of body with no CRLF -- no body-part can follow */
+			break;	/* delimiter runs to end of body with no CRLF, no body-part can follow */
 
 		if (n >= maxparts)
 			return (-1);
@@ -886,7 +886,7 @@ find_mime_part(int depth, const char *hdr, size_t hdrl
 		return (-1);
 
 	if (pathlen == 1) {
-		/* path consumed; must be a genuine leaf (not MULTIPART/MESSAGE-RFC822|GLOBAL) -- no "combined children" concept exists */
+		/* path consumed; must be a genuine leaf (not MULTIPART/MESSAGE-RFC822|GLOBAL), no "combined children" concept exists */
 		char	 ctype[64], csub[64], cparams[600];
 		char	 cboundary[70 + 1];
 		int	 chb;
@@ -1048,13 +1048,13 @@ build_flags_string(const char *maildir_suffix, const c
 			continue;
 		if (!first && strlcat(out, " ", outsize) >= outsize) {
 			log_warnx("session %u: build_flags_string: out "
-			    "truncated -- caller-supplied buffer too small "
+			    "truncated, caller-supplied buffer too small "
 			    "for the flag list; stopping early", session_id);
 			return;
 		}
 		if (strlcat(out, stdflags[i].flag, outsize) >= outsize) {
 			log_warnx("session %u: build_flags_string: out "
-			    "truncated -- caller-supplied buffer too small "
+			    "truncated, caller-supplied buffer too small "
 			    "for the flag list; stopping early", session_id);
 			return;
 		}
@@ -1067,7 +1067,7 @@ build_flags_string(const char *maildir_suffix, const c
 
 		if (strlcpy(kwbuf, keywords, sizeof(kwbuf)) >= sizeof(kwbuf)) {
 			log_warnx("session %u: build_flags_string: keywords "
-			    "too long for kwbuf -- omitting keyword flags "
+			    "too long for kwbuf, omitting keyword flags "
 			    "rather than guessing at a truncated list",
 			    session_id);
 			return;
@@ -1076,14 +1076,14 @@ build_flags_string(const char *maildir_suffix, const c
 		    kw = strtok_r(NULL, ",", &save)) {
 			if (!first && strlcat(out, " ", outsize) >= outsize) {
 				log_warnx("session %u: build_flags_string: "
-				    "out truncated -- caller-supplied "
+				    "out truncated, caller-supplied "
 				    "buffer too small for the flag list; "
 				    "stopping early", session_id);
 				return;
 			}
 			if (strlcat(out, kw, outsize) >= outsize) {
 				log_warnx("session %u: build_flags_string: "
-				    "out truncated -- caller-supplied "
+				    "out truncated, caller-supplied "
 				    "buffer too small for the flag list; "
 				    "stopping early", session_id);
 				return;
@@ -1106,4 +1106,3 @@ parse_maildir_timestamp(const char *basename)
 		return ((int64_t)time(NULL));
 	return ((int64_t)ts);
 }
-
blob - d7ddcce302acb1f06be1b9c92ea56a678fbe9360
blob + 7d38934e0e41ad7eaf36f5e19be868d6c5f63f6a
--- src/parent.c
+++ src/parent.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* parent.c -- privileged supervisor: process management, SETUP_PEER/SETUP_DONE handshake, privilege drop. */
+/* parent.c, privileged supervisor: process management, SETUP_PEER/SETUP_DONE handshake, privilege drop. */
 
 #include <sys/types.h>
 #include <sys/queue.h>
@@ -99,7 +99,7 @@ static void	 sigterm_handler(int, short, void *);
 static void	 sigchld_handler(int, short, void *);
 static void	 reap_child(pid_t, int);
 
-/* config_load() -- the full imapd.conf grammar -- lives in parse.y */
+/* config_load(), the full imapd.conf grammar, lives in parse.y */
 __dead void
 parent_main(const char *conffile, int argc, char *argv[],
     struct openimap_config *conf)
@@ -118,7 +118,7 @@ parent_main(const char *conffile, int argc, char *argv
 	if (realpath(argv[0], progpath) == NULL)
 		fatal("realpath");
 
-	/* bind(2) on <1024 needs root -- done here, before any privdrop. */
+	/* bind(2) on <1024 needs root, done here, before any privdrop. */
 	n_cleartext = bind_listen_socket(conf->listen_addr,
 	    conf->port_cleartext, cleartext_fds);
 	n_tls = bind_listen_socket(conf->listen_addr,
@@ -126,11 +126,11 @@ parent_main(const char *conffile, int argc, char *argv
 
 	event_init();
 
-	/* boot-time children: listener and auth only -- store is spawned per-session, see parent_handle_store_fork() */
+	/* boot-time children: listener and auth only, store is spawned per-session, see parent_handle_store_fork() */
 	fork_child(PROC_LISTENER, &iev_listener, parent_dispatch_child);
 	fork_child(PROC_AUTH, &iev_auth, parent_dispatch_child);
 
-	/* IMSG_AUTH_INIT before the peer handshake -- auth_main() derives its chroot dir from cred_file */
+	/* IMSG_AUTH_INIT before the peer handshake, auth_main() derives its chroot dir from cred_file */
 	send_auth_init(iev_auth, conf);
 
 	/* IMSG_SETUP_PEER / IMSG_SETUP_DONE, sourced from smtpd.c's setup_peers()/setup_done(); listener<->auth only */
@@ -154,7 +154,7 @@ parent_main(const char *conffile, int argc, char *argv
 	signal_add(&ev_sighup, NULL);
 	signal_add(&ev_sigterm, NULL);
 	signal_add(&ev_sigchld, NULL);
-	/* SIGPIPE is ignored process-wide in main.c, before fork_child() -- too late here to reach listener/auth, which are already spawned above */
+	/* SIGPIPE is ignored process-wide in main.c, before fork_child(), too late here to reach listener/auth, which are already spawned above */
 
 	/* proc/exec/sendfd stay for the process lifetime, not narrowed post-boot, since store is fork-per-session */
 #ifdef __OpenBSD__
@@ -197,17 +197,8 @@ fork_child(enum openimap_proc_type type, struct imsgev
 		 * char *const argv[] for historical reasons predating C
 		 * const-correctness, and never writes through argv's
 		 * pointers, so this cast is the standard, unavoidable idiom
-		 * for building an exec() argv array. Routed through
-		 * uintptr_t, not a direct (char *) cast, matching the real
-		 * precedent for this exact situation (a const process-title
-		 * string spliced into an execvp() argv[]) in OpenBSD base's
-		 * own privsep proc.c, shared by relayd/vmd/iked/snmpd (e.g.
-		 * usr.sbin/relayd/proc.c's "nargv[proc_i] =
-		 * (char *)(uintptr_t)p->p_title;") -- confirmed directly
-		 * against that source, not assumed. Verified with gcc that
-		 * the uintptr_t hop (unlike a direct (char *) cast) does not
-		 * trigger -Wcast-qual, since the diagnostic only tracks
-		 * qualifier loss across a direct pointer-to-pointer cast. */
+		 * for building an exec() argv array.
+		 */
 		nargv[n++] = (char *)(uintptr_t)log_procname(type);
 		for (i = 1; saved_argv[i] != NULL && n < 13; i++) {
 			/* skip a pre-existing -x <role> from our own argv, everything else passes through */
@@ -231,7 +222,7 @@ fork_child(enum openimap_proc_type type, struct imsgev
 		fatal("calloc");
 	c->pid = pid;
 	c->type = type;
-	/* NULL here, not "c" -- handlers expect arg == &iev; imsgev_init()'s own fallback self-references &c->iev */
+	/* NULL here, not "c", handlers expect arg == &iev; imsgev_init()'s own fallback self-references &c->iev */
 	imsgev_init(&c->iev, sp[0], handler, NULL);
 	TAILQ_INSERT_TAIL(&children, c, entry);
 
@@ -290,7 +281,7 @@ setup_done_send(struct imsgev *iev)
 	imsg_free(&imsg);
 }
 
-/* dispatch for boot-time listener/auth channels post-boot; task #321: IMSG_AUTH_CRED arrives here from auth, triggering a store spawn directly (no longer relayed via listener's old IMSG_STORE_FORK request) */
+/* dispatch for boot-time listener/auth channels post-boot; IMSG_AUTH_CRED arrives here from auth, triggering a store spawn directly (no longer relayed via listener's old IMSG_STORE_FORK request) */
 static void
 parent_dispatch_child(int fd, short event, void *arg)
 {
@@ -298,7 +289,7 @@ parent_dispatch_child(int fd, short event, void *arg)
 	struct imsg	 imsg;
 	ssize_t		 n;
 
-	/* EV_WRITE: imsg_compose() only queues -- imsgbuf_write() puts bytes on the wire */
+	/* EV_WRITE: imsg_compose() only queues, imsgbuf_write() puts bytes on the wire */
 	if (event & EV_WRITE) {
 		if (imsgbuf_write(&iev->ibuf) == -1)
 			fatal("imsgbuf_write");
@@ -308,7 +299,7 @@ parent_dispatch_child(int fd, short event, void *arg)
 		if ((n = imsgbuf_read(&iev->ibuf)) == -1)
 			fatal("imsgbuf_read");
 		if (n == 0) {
-			/* child's end closed -- reaped via SIGCHLD */
+			/* child's end closed, reaped via SIGCHLD */
 			event_del(&iev->ev);
 			return;
 		}
@@ -367,7 +358,7 @@ maildir_path_is_safe(const char *p)
 	return (1);
 }
 
-/* task #321: per-session store spawn triggered directly by auth's IMSG_AUTH_CRED; mirrors fork_child() but with a timeout, not fatal() */
+/* per-session store spawn triggered directly by auth's IMSG_AUTH_CRED; mirrors fork_child() but with a timeout, not fatal() */
 static void
 parent_handle_store_fork(uint32_t session_id, uid_t uid, gid_t gid,
     const char *maildir)
@@ -383,14 +374,6 @@ parent_handle_store_fork(uint32_t session_id, uid_t ui
 	struct store_child	*it;
 	unsigned int		 nchildren = 0;
 
-	/*
-	 * task #321: uid/gid/maildir now arrive directly from auth, the
-	 * process that actually resolved them from the credentials file --
-	 * not relayed through the network-facing listener. These checks stay
-	 * as defense in depth regardless: they guard against a misconfigured
-	 * credentials file (e.g. a "root:...:0:0:/" line) or a future bug in
-	 * auth, not just a hostile sender.
-	 */
 	if (uid == 0 || gid == 0) {
 		log_warnx("refusing store spawn: privileged uid=%u gid=%u "
 		    "for session %u", (unsigned)uid, (unsigned)gid,
@@ -403,7 +386,6 @@ parent_handle_store_fork(uint32_t session_id, uid_t ui
 		goto fail;
 	}
 
-	/* F5 fix: cap concurrent store children. */
 	TAILQ_FOREACH(it, &store_children, entry)
 		nchildren++;
 	if (nchildren >= STORE_CHILD_MAX) {
@@ -449,7 +431,7 @@ parent_handle_store_fork(uint32_t session_id, uid_t ui
 
 	close(pair[1]);
 
-	/* allocated once, in place -- sc->iev is never copied afterward (same reason as struct store_child) */
+	/* allocated once, in place, sc->iev is never copied afterward (same reason as struct store_child) */
 	sc = calloc(1, sizeof(*sc));
 	if (sc == NULL)
 		fatal("calloc");
@@ -459,7 +441,7 @@ parent_handle_store_fork(uint32_t session_id, uid_t ui
 	sc->listener_iev = iev_listener;
 	imsgev_init(&sc->iev, pair[0], store_child_dispatch, sc);
 
-	/* IMSG_STORE_INIT: the one message a store child needs that boot-time children don't -- runtime privilege target */
+	/* IMSG_STORE_INIT: the one message a store child needs that boot-time children don't, runtime privilege target */
 	init_payload.session_id = session_id;
 	init_payload.uid = uid;
 	init_payload.gid = gid;
@@ -467,15 +449,15 @@ parent_handle_store_fork(uint32_t session_id, uid_t ui
 	    sizeof(init_payload.spool_root)) >=
 	    sizeof(init_payload.spool_root)) {
 		log_warnx("session %u: spool_root truncated spawning store "
-		    "child -- refusing to wire a session to a possibly "
+		    "child, refusing to wire a session to a possibly "
 		    "wrong spool root", session_id);
 		goto fail_kill;
 	}
-	/* task #321: maildir came directly from auth's IMSG_AUTH_CRED, passed through verbatim */
+
 	if (strlcpy(init_payload.maildir, maildir,
 	    sizeof(init_payload.maildir)) >= sizeof(init_payload.maildir)) {
 		log_warnx("session %u: maildir truncated spawning store "
-		    "child -- refusing to wire a session to a possibly "
+		    "child, refusing to wire a session to a possibly "
 		    "wrong mailbox", session_id);
 		goto fail_kill;
 	}
@@ -527,7 +509,7 @@ store_child_dispatch(int fd, short event, void *arg)
 	struct imsg		 imsg;
 	ssize_t			 n;
 
-	/* EV_WRITE: same as parent_dispatch_child() -- imsg_compose() only queues */
+	/* EV_WRITE: same as parent_dispatch_child(), imsg_compose() only queues */
 	if (event & EV_WRITE) {
 		if (imsgbuf_write(&sc->iev.ibuf) == -1) {
 			store_child_fail(sc);
@@ -561,7 +543,7 @@ store_child_dispatch(int fd, short event, void *arg)
 		    imsg_get_type(&imsg), sc->session_id);
 		imsg_free(&imsg);
 	}
-	imsgev_add(&sc->iev);	/* re-arm -- see this function's header comment */
+	imsgev_add(&sc->iev);	/* re-arm, see this function's header comment */
 	(void)fd;
 }
 
@@ -606,7 +588,7 @@ store_child_fail(struct store_child *sc)
 	store_child_teardown(sc, 0);
 }
 
-/* binds, sets SO_REUSEADDR, and listen(2)s one socket -- the common tail end of every bind_listen_socket() case */
+/* binds, sets SO_REUSEADDR, and listen(2)s one socket, the common tail end of every bind_listen_socket() case */
 static int
 bind_one(int family, const struct sockaddr *sa, socklen_t salen,
     uint16_t port)
@@ -710,7 +692,7 @@ send_tls_certs(struct imsgev *iev, struct openimap_con
 	size_t		 n;
 	struct stat	 st;
 
-	/* cert is public -- existence/readability is all that matters */
+	/* cert is public, existence/readability is all that matters */
 	n = 0;
 	if ((fp = fopen(conf->tls_cert_file, "r")) == NULL) {
 		log_warn("fopen %s", conf->tls_cert_file);
@@ -732,11 +714,11 @@ send_tls_certs(struct imsgev *iev, struct openimap_con
 		log_warn("fstat %s", conf->tls_key_file);
 		fclose(fp);
 	} else if (st.st_uid != 0) {
-		log_warnx("%s: not owned by uid 0 -- refusing to load "
+		log_warnx("%s: not owned by uid 0, refusing to load "
 		    "(TLS will be disabled)", conf->tls_key_file);
 		fclose(fp);
 	} else if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~0740) {
-		log_warnx("%s: insecure permissions -- must be at most "
+		log_warnx("%s: insecure permissions, must be at most "
 		    "rwxr----- (TLS will be disabled)", conf->tls_key_file);
 		fclose(fp);
 	} else {
@@ -764,7 +746,7 @@ send_listener_init(struct imsgev *iev, struct openimap
 	memset(&init, 0, sizeof(init));
 	if (strlcpy(init.listen_addr, conf->listen_addr,
 	    sizeof(init.listen_addr)) >= sizeof(init.listen_addr))
-		fatalx("listen_addr truncated sending IMSG_LISTENER_INIT -- "
+		fatalx("listen_addr truncated sending IMSG_LISTENER_INIT, "
 		    "config value too long for the wire struct's field");
 	init.port_cleartext = conf->port_cleartext;
 	init.port_implicit_tls = conf->port_implicit_tls;
@@ -778,7 +760,7 @@ send_listener_init(struct imsgev *iev, struct openimap
 		fatal("imsgbuf_flush");
 }
 
-/* IMSG_AUTH_INIT: auth's slice of config -- just cred_file, to derive its chroot dir and unveil() path */
+/* IMSG_AUTH_INIT: auth's slice of config, just cred_file, to derive its chroot dir and unveil() path */
 static void
 send_auth_init(struct imsgev *iev, struct openimap_config *conf)
 {
@@ -787,7 +769,7 @@ send_auth_init(struct imsgev *iev, struct openimap_con
 	memset(&init, 0, sizeof(init));
 	if (strlcpy(init.cred_file, conf->cred_file, sizeof(init.cred_file))
 	    >= sizeof(init.cred_file))
-		fatalx("cred_file truncated sending IMSG_AUTH_INIT -- config "
+		fatalx("cred_file truncated sending IMSG_AUTH_INIT, config "
 		    "value too long for the wire struct's field");
 
 	if (imsg_compose(&iev->ibuf, IMSG_AUTH_INIT, 0, 0, -1,
@@ -809,7 +791,7 @@ sighup_handler(int fd, short event, void *arg)
 
 	memset(&newconf, 0, sizeof(newconf));
 	if (config_load(conf_path, &newconf) == -1) {
-		log_warnx("SIGHUP: %s: reload failed -- keeping the "
+		log_warnx("SIGHUP: %s: reload failed, keeping the "
 		    "already-running configuration", conf_path);
 		return;
 	}
@@ -818,14 +800,14 @@ sighup_handler(int fd, short event, void *arg)
 	    newconf.port_cleartext != gconf->port_cleartext ||
 	    newconf.port_implicit_tls != gconf->port_implicit_tls) {
 		log_warnx("SIGHUP: %s: \"listen on\" changed but listening "
-		    "sockets cannot be rebound without a restart -- still "
+		    "sockets cannot be rebound without a restart, still "
 		    "serving %s:%u / %s:%u", conf_path, gconf->listen_addr,
 		    gconf->port_cleartext, gconf->listen_addr,
 		    gconf->port_implicit_tls);
 	}
 	if (strcmp(newconf.cred_file, gconf->cred_file) != 0) {
 		log_warnx("SIGHUP: %s: \"credentials\" changed but auth is "
-		    "already chrooted for the old path -- a restart is "
+		    "already chrooted for the old path, a restart is "
 		    "required for this to take effect", conf_path);
 	}
 
@@ -834,12 +816,12 @@ sighup_handler(int fd, short event, void *arg)
 	    strlen(newconf.tls_cert_file) >= sizeof(gconf->tls_cert_file) ||
 	    strlen(newconf.tls_key_file) >= sizeof(gconf->tls_key_file)) {
 		log_warnx("SIGHUP: %s: reloaded value too long for gconf's "
-		    "field -- keeping the already-running configuration",
+		    "field, keeping the already-running configuration",
 		    conf_path);
 		return;
 	}
 
-	/* Safe to adopt immediately -- see this function's header comment. */
+	/* Safe to adopt immediately, see this function's header comment. */
 	(void)strlcpy(gconf->spool_root, newconf.spool_root,
 	    sizeof(gconf->spool_root));
 	gconf->bodystructure_read_max = newconf.bodystructure_read_max;
@@ -899,18 +881,18 @@ reap_child(pid_t pid, int status)
 			    "(already-authenticated sessions are unaffected)";
 
 			if (WIFSIGNALED(status))
-				log_warnx("%s[%d] terminated by signal %d -- "
+				log_warnx("%s[%d] terminated by signal %d, "
 				    "%s; run \"rcctl restart imapd\" to "
 				    "recover", log_procname(c->type), pid,
 				    WTERMSIG(status), what);
 			else if (WIFEXITED(status))
 				log_warnx("%s[%d] exited unexpectedly, "
-				    "status %d -- %s; run \"rcctl restart "
+				    "status %d, %s; run \"rcctl restart "
 				    "imapd\" to recover",
 				    log_procname(c->type), pid,
 				    WEXITSTATUS(status), what);
 			else
-				log_warnx("%s[%d] exited unexpectedly -- %s; "
+				log_warnx("%s[%d] exited unexpectedly, %s; "
 				    "run \"rcctl restart imapd\" to recover",
 				    log_procname(c->type), pid, what);
 			TAILQ_REMOVE(&children, c, entry);
blob - 9846dc4ba7e91776deb0293561504738804eddee
blob + 3833ac0b7d22c93dcfaa39efaa5c0ea8d2b8894f
--- src/parse.y
+++ src/parse.y
@@ -26,50 +26,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/*
- * parse.y -- imapd.conf grammar.
- *
- * config_load() (the only function this file exposes -- see imapd.h's
- * prototype) replaces parent.c's old stub of the same name, which only
- * ever filled in v1 defaults and ignored the config file path entirely.
- *
- * Structure and most of the machinery below (the pushfile()/popfile()
- * file stack for "include", the lgetc()/lungetc()/findeol() character-
- * pushback pair, the hand-written yylex() built on top of them, the
- * symset()/symget()/cmdline_symset() $macro/-D variable table, and
- * check_file_secrecy()) are this project's own rewrite of the same
- * pattern used throughout the OpenBSD base system's own daemons -- sourced
- * directly against ripd's parse.y (src/usr.sbin/ripd/parse.y, read in
- * full this pass) for the generic boilerplate, and against smtpd's and
- * httpd's parse.y (src/usr.sbin/smtpd/parse.y, src/usr.sbin/httpd/
- * parse.y) for the "include" grammar rule and the "listen on <addr>
- * [tls] port <port>" grammar shape respectively. None of these daemons'
- * domain-specific grammar (RIP redistribution rules, SMTP rulesets,
- * HTTP server blocks) is reused here -- only the parser skeleton and the
- * listen-line shape. This project's own grammar covers exactly the eight
- * fields in struct openimap_config (imapd.h): the two listeners,
- * spool_root, cred_file, tls_cert_file, tls_key_file, and (added this
- * pass) bodystructure_read_max via the "attachment max <bytes>"
- * directive -- see that grammar rule's own comment below for why this
- * one field gets range-validated where the others don't.
- *
- * Deliberately NOT following ripd/smtpd's convention in one place: those
- * daemons' top-level parse function allocates and returns their own
- * config struct. config_load()'s signature is already fixed by imapd.h
- * (a caller-supplied struct openimap_config * to fill in, matching the
- * existing prototype main.c already calls) and by the man page already
- * documenting it, so this file fills that struct in place instead of
- * allocating its own.
- *
- * Full-parity scope decision (AskUserQuestion, this pass): rather than a
- * trimmed-down parser with just the core grammar, this includes the same
- * $macro / -D command-line variable support and "include" file support
- * every OpenBSD base daemon's config grammar has, even though a flat
- * seven-field config has only modest use for either -- matching upstream
- * convention closely was judged more valuable than the modest size
- * savings a trimmed version would give, particularly given this project's
- * long-term (if distant) OpenBSD base-inclusion aspiration.
- */
+/* parse.y, imapd.conf grammar. */
 
 %{
 #include <sys/types.h>
@@ -129,17 +86,6 @@ char		*symget(const char *);
 static struct openimap_config	*conf;
 static int			 errors = 0;
 
-/*
- * Tracks whether a "listen on ..." line for each of the two listeners has
- * already been seen this parse, both to reject a second one for the same
- * listener and to detect a second listener whose address doesn't match
- * the first -- struct openimap_config has only one listen_addr shared by
- * both ports (bind_listen_socket() is called once per port against that
- * same field, parent.c), so two "listen on" lines naming different
- * addresses would silently mean "the second one wins" without this check,
- * which is far more likely to be a config mistake than an intentional
- * per-listener address split this implementation doesn't actually support.
- */
 static int			 have_cleartext = 0;
 static int			 have_tls_listen = 0;
 
@@ -217,17 +163,7 @@ main		: LISTEN ON STRING opttls PORT NUMBER	{
 				YYERROR;
 			}
 
-			/*
-			 * "*" (dual-stack, both IPv4-any and IPv6-any) or a
-			 * literal IPv4/IPv6 address only -- no getaddrinfo(3)
-			 * hostname resolution, a deliberate scope decision
-			 * for this pass (see LISTENER_MAX_ADDRS's comment in
-			 * imapd.h for the full reasoning). Checked here, at
-			 * parse time, so a bad address is rejected with a
-			 * clear config-file error instead of surfacing later
-			 * as parent.c's bind_listen_socket() fatal()ing at
-			 * startup.
-			 */
+			/* "*" dual-stack, both IPv4-any and IPv6-any */
 			if (strcmp($3, "*") != 0) {
 				struct in_addr	 ina;
 				struct in6_addr	 ina6;
@@ -246,7 +182,7 @@ main		: LISTEN ON STRING opttls PORT NUMBER	{
 				if (strcmp(conf->listen_addr, $3) != 0) {
 					yyerror("listen address \"%s\" does "
 					    "not match earlier \"listen on "
-					    "%s\" -- imapd binds both "
+					    "%s\", imapd binds both "
 					    "listeners to the same address",
 					    $3, conf->listen_addr);
 					free($3);
@@ -322,25 +258,7 @@ main		: LISTEN ON STRING opttls PORT NUMBER	{
 		}
 		| ATTACHMENT MAX NUMBER	{
 			/*
-			 * Range-validated, unlike the other directives above:
-			 * this value flows straight into a per-FETCH malloc()
-			 * in store.c's read_message_body() (readbuf_size =
-			 * maxlen + 1), once per session-child process, so an
-			 * operator typo here (an extra zero, a value meant as
-			 * megabytes typed as bytes) has a real memory-exhaustion
-			 * consequence that a bad path string or port number
-			 * doesn't. Lower bound matches BODYSTRUCTURE_MAX
-			 * (imapd.h) as a floor -- below that, no message could
-			 * ever produce a usable BODYSTRUCTURE anyway, so a
-			 * smaller value is certainly a mistake, not a real
-			 * intended restriction. Upper bound (1 GiB) is a
-			 * generous, round sanity ceiling, not a protocol limit
-			 * -- personal mail attachments this large are not a
-			 * real-world case this implementation targets (see
-			 * BODYSTRUCTURE_READ_DEFAULT's own Gmail-sourced
-			 * reasoning), and rejecting it here surfaces the
-			 * mistake at config-parse time rather than as a
-			 * confusing malloc failure or slow FETCH later.
+			 * Range-validated
 			 */
 			if ($3 < BODYSTRUCTURE_MAX || $3 > 1073741824) {
 				yyerror("attachment max out of range "
@@ -631,19 +549,7 @@ nodigits:
 	x != ','))
 
 	/*
-	 * '*' added alongside the pre-existing ':' (there for bare IPv6
-	 * literals like "::") so the dual-stack "listen on *" wildcard from
-	 * the IPv6 pass works unquoted, matching every other address form
-	 * documented in imapd.conf.example -- found as a real bug, not
-	 * designed in up front: an unquoted "listen on * port 143" line
-	 * failed with a bare "syntax error" on real hardware before
-	 * this fix, since '*' alone satisfied none of the original starting
-	 * characters here and fell through to being returned as a raw,
-	 * unexpected single-character token instead of ever entering this
-	 * bareword-accumulation loop. Quoting it ("listen on \"*\" ...")
-	 * already worked, since the quoted-string branch above this one
-	 * doesn't consult allowed_in_string() at all -- this fix is only
-	 * about making the unquoted form work too.
+	 * '*' added alongside the pre-existing ':'
 	 */
 	if (isalnum(c) || c == ':' || c == '_' || c == '*') {
 		do {
@@ -671,16 +577,7 @@ nodigits:
 
 /*
  * Same root-owned-or-current-user, not-group-or-world-writable check this
- * project already applies to the TLS private key file (parent.c's
- * send_tls_certs(), added when that boot-deadlock/permission-check pass
- * closed a real gap there) -- applied here to the top-level config file
- * itself, since it can name the credentials file path and TLS key path,
- * matching every base-system daemon's own parse.y (ripd's is the direct
- * source for this function, byte-for-byte apart from the log_warn/
- * log_warnx call signatures matching this project's own log.h instead of
- * ripd's warn(3)/warnx(3)-based logit() wrappers). Not applied to
- * "include"d files (pushfile()'s second argument is 0 for those) --
- * matching smtpd's own convention of only checking the top-level file.
+ * project already applies to the TLS private key file
  */
 static int
 check_file_secrecy(int fd, const char *fname)
@@ -750,17 +647,7 @@ popfile(void)
 }
 
 /*
- * config_load(): imapd.h's public entry point (replaces parent.c's old
- * config_load() stub of the same name/signature -- see this file's header
- * comment). Fills xconf with the v1 defaults first (same values the old
- * stub always used unconditionally), then parses path over those defaults
- * -- so a config file that sets only, say, "spool" leaves every other
- * field at its documented v1 default rather than requiring a client to
- * spell out all seven fields every time. Returns 0 on success (xconf
- * fully populated) or -1 (a parse error occurred; xconf's contents are
- * unspecified) -- same "reject rather than run with a half-parsed config"
- * precedent as every other MAX-constant/parse-failure case in this
- * codebase, left to the caller (main.c) to treat as fatal.
+ * config_load(): imapd.h's public entry point.
  */
 int
 config_load(const char *path, struct openimap_config *xconf)
@@ -855,11 +742,7 @@ symset(const char *nam, const char *val, int persist)
 }
 
 /*
- * cmdline_symset(): "-D name=value" command-line macro definitions,
- * matching ripd/smtpd's own "-D" flag exactly -- called directly from
- * main.c's getopt(3) loop, before config_load() runs, so these persist
- * (the "1" argument to symset()) across the parse: a "-D" definition
- * always wins over the same name defined inside the config file itself.
+ * cmdline_symset(): "-D name=value" command-line macro definitions.
  */
 int
 cmdline_symset(char *s)
blob - bbbf2d8cb4c55ae27eed5cb70cec9c3ff817bb28
blob + b3a1b82e5edd90490b6a9157af573b2455daa5eb
--- src/rc.d/imapd
+++ src/rc.d/imapd
@@ -2,22 +2,7 @@
 #
 # $OpenIMAPD$
 #
-# rc.d(8) service script for imapd(8). imapd does not detach from
-# its controlling terminal or otherwise background itself (see
-# imapd.8) -- rc_bg=YES tells rc.subr to run it under "set -o monitor"
-# instead (own process group, avoids SIGHUP at boot), which is rc.subr's
-# documented idiom for exactly this kind of foreground-only daemon. No
-# rc_start override is needed: the default "rc_exec ${daemon}
-# ${daemon_flags}" is sufficient with rc_bg=YES alone -- confirmed against
-# ypbind's real, shipped rc.d script (/etc/rc.d/ypbind), which uses the
-# identical rc_bg=YES-with-default-rc_start pattern for the same stated
-# reason ("avoid SIGHUP at boot").
-#
-# rc_stop needs no override either: parent.c's sigterm_handler already
-# cascades SIGTERM to every child (listener, auth, each per-session store
-# child) on receipt of a single SIGTERM to the root parent -- exactly what
-# rc.subr's default rc_stop() sends (pkill -TERM against the tracked
-# process). See parent.c for that cascade logic.
+# rc.d(8) service script for imapd(8).
 
 daemon="/usr/local/sbin/imapd"
 
@@ -25,35 +10,6 @@ daemon="/usr/local/sbin/imapd"
 
 rc_bg=YES
 
-# --- boot-time relink consumer -------------------------------------------
-#
-# "make install" (see ../Makefile's RELINK=) drops a re-link kit at
-# ${_relink_tar} if the Makefile's RELINK variable is set, per bsd.prog.
-# mk's documented re-link-kit mechanism. Nothing on this system consumes
-# it automatically the way base does for libc/libcrypto/ld.so/sshd:
-# /etc/rc's reorder_libs() has a hardcoded allowlist (confirmed by reading
-# /etc/rc directly) that doesn't and won't include imapd -- extending
-# it would mean patching base /etc/rc, which gets clobbered on every base
-# upgrade. This hook is the substitute consumer.
-#
-# It runs every time this service is (re)started -- at boot via
-# pkg_scripts, or by hand via "rcctl restart imapd" -- checks for a
-# pending tarball, and if present, extracts it and runs the install.sh
-# that bsd.prog.mk generated inside it. That install.sh recompiles ${PROG}
-# from the tarball's own object files in a freshly randomized link order,
-# smoke-tests the result via the Makefile's RELINK command ("imapd -V",
-# see main.c -- prints a version string and exits 0 with no other side
-# effects), and only then installs the result over the currently running
-# binary.
-#
-# Failure here is deliberately non-fatal to starting the service: if the
-# relink or its smoke test fails, install.sh's own "install -c -s ..."
-# step never runs, so the binary already at ${daemon} (from the last
-# successful relink, or a plain "make install") is untouched. We log a
-# warning, leave the tarball in place for investigation, and fall through
-# to starting that known-good binary rather than taking the mail server
-# down over a relink hiccup -- KARL's own kernel relink (reorder_kernel.sh)
-# follows the same non-fatal-on-failure philosophy for the same reason.
 _relink_tar="/usr/share/relink/usr/local/sbin/imapd/imapd.tar"
 
 rc_pre() {
blob - ad9e1ac1f13afb0e026401cb2f682807957c83e1
blob + 21cf4cd6b50c99435f432d649865f16d0c19b57b
--- src/search_cmd.c
+++ src/search_cmd.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * search_cmd.c -- SEARCH: key parsing, evaluation dispatch, and
+ * search_cmd.c, SEARCH: key parsing, evaluation dispatch, and
  * the async IMSG_MBOX_SEARCH_MATCH/IMSG_MBOX_RESULT completion path.
  */
 
@@ -70,7 +70,7 @@ parse_search_date(const char *s, int64_t *out)
 	tm.tm_mday = day;
 	tm.tm_mon = i;
 	tm.tm_year = year - 1900;
-	/* tm_hour/tm_min/tm_sec already 0 from memset -- UTC midnight */
+	/* tm_hour/tm_min/tm_sec already 0 from memset, UTC midnight */
 
 	if ((t = timegm(&tm)) == (time_t)-1)
 		return (-1);
@@ -101,7 +101,7 @@ search_push(struct search_parse_ctx *ctx, const struct
 	return (0);
 }
 
-/* reads one sequence-set token, rejects an internal comma (v1's single-range restriction), via parse_seq_range() */
+/* reads one sequence-set token, rejects an internal comma, via parse_seq_range() */
 static int
 parse_search_seqset_token(char **pp, uint32_t *lo, uint32_t *hi,
     int *lo_star, int *hi_star, const char **errmsg)
@@ -130,7 +130,7 @@ parse_search_seqset_token(char **pp, uint32_t *lo, uin
 
 		if (strchr(tok, ',') != NULL) {
 			*errmsg = "comma-separated sequence sets not "
-			    "supported in v1 -- issue separate SEARCH "
+			    "supported, issue separate SEARCH "
 			    "commands";
 			return (-1);
 		}
@@ -632,8 +632,8 @@ parse_search_return_opts(char **pp, uint32_t *opts_out
 		else if (strcasecmp(word, "COUNT") == 0)
 			*opts_out |= SEARCH_RETURN_COUNT;
 		else if (strcasecmp(word, "SAVE") == 0) {
-			*errmsg = "SEARCH RETURN (SAVE) -- the \"$\" search "
-			    "result variable -- is not supported in this "
+			*errmsg = "SEARCH RETURN (SAVE), the \"$\" search "
+			    "result variable, is not supported in this "
 			    "pass";
 			return (-2);
 		} else {
@@ -915,13 +915,7 @@ session_finish_search(struct session *s, struct imsg_m
 		    (unsigned long long)s->search_max_modseq);
 
 	/*
-	 * len holds snprintf(3)'s "would-be" length, which can run past
-	 * sizeof(buf) (e.g. a huge ALL sequence-set plus COUNT/MODSEQ
-	 * tails pushes it over even though format_seq_list() itself was
-	 * bounded). The two explicit buf[len++] writes below need two
-	 * free slots, so reserve two bytes, not one -- "- 1" here was an
-	 * off-by-one that let buf[sizeof(buf)] get written, one byte past
-	 * the end of this stack array.
+	 * len holds snprintf(3)'s "would-be" length, which can run past sizeof(buf).
 	 */
 	if (len >= sizeof(buf) - 1)
 		len = sizeof(buf) - 2;
blob - b0c7909da499fde858f7d85ef79e114888e3e7dc
blob + 913c52e22655c40876409ed6770e1b31da4e9cef
--- src/store.c
+++ src/store.c
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* store.c -- mailbox-store process: one per session, privilege-dropped, handles IMSG_MBOX_*. */
+/* store.c, mailbox-store process: one per session, privilege-dropped, handles IMSG_MBOX_*. */
 
 #include <sys/types.h>
 #include <sys/file.h>
@@ -146,7 +146,7 @@ mailbox_name_valid(const char *name)
 	for (i = 0; i < len; i++) {
 		unsigned char	c = (unsigned char)name[i];
 
-		/* RFC 9051 SS5.1.1: "/" is the hierarchy delimiter; v1 is flat, so it's never addressable */
+		/* RFC 9051 SS5.1.1: "/" is the hierarchy delimiter */
 		if (c == '/')
 			return (0);
 		/* SS5.1 point 2: MAY refuse CTL/non-graphic names; taking that MAY for ASCII C0/DEL */
@@ -154,7 +154,7 @@ mailbox_name_valid(const char *name)
 			return (0);
 	}
 
-	/* "tmp"/"new"/"cur" are INBOX's own maildir internals -- refusing them here prevents cross-mailbox corruption */
+	/* "tmp"/"new"/"cur" are INBOX's own maildir internals, refusing them here prevents cross-mailbox corruption */
 	if (strcmp(name, "tmp") == 0 || strcmp(name, "new") == 0 ||
 	    strcmp(name, "cur") == 0)
 		return (0);
@@ -246,7 +246,7 @@ store_dispatch(int fd, short event, void *arg)
 		if ((n = imsgbuf_read(&iev->ibuf)) == -1)
 			fatal("imsgbuf_read");
 		if (n == 0) {
-			/* listener's end closed -- treat like IMSG_STORE_SHUTDOWN: nothing left to serve */
+			/* listener's end closed, treat like IMSG_STORE_SHUTDOWN: nothing left to serve */
 			store_shutdown();
 			/* NOTREACHED */
 		}
@@ -307,7 +307,7 @@ store_dispatch(int fd, short event, void *arg)
 			break;
 		}
 		case IMSG_MBOX_IDLE_REFRESH:
-			/* no request payload -- see imapd.h's imsg_mbox_idle_uid comment */
+			/* no request payload, see imapd.h's imsg_mbox_idle_uid comment */
 			handle_mbox_idle_refresh(iev);
 			break;
 		case IMSG_MBOX_APPEND: {
blob - b93a07a57b01c11ccb66e38b1ab919530c1e1f65
blob + 90e9426c1da3331eb9f0a61f8a8db04c5a1f0a38
--- src/store_cmd.c
+++ src/store_cmd.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * store_cmd.c -- STORE/EXPUNGE/CLOSE/UNSELECT/IDLE/COPY/MOVE/UID:
+ * store_cmd.c, STORE/EXPUNGE/CLOSE/UNSELECT/IDLE/COPY/MOVE/UID:
  * command-select handlers plus their async completion paths.
  */
 
@@ -61,7 +61,7 @@ cmd_idle(struct session *s, const char *tag, char *arg
 	return (1);
 }
 
-/* RFC 9051 SS6.4.1: CLOSE removes \Deleted with no untagged EXPUNGE -- via session_request_expunge(silent=1). */
+/* RFC 9051 SS6.4.1: CLOSE removes \Deleted with no untagged EXPUNGE, via session_request_expunge(silent=1). */
 int
 cmd_close(struct session *s, const char *tag, char *args)
 {
@@ -69,7 +69,7 @@ cmd_close(struct session *s, const char *tag, char *ar
 	return session_request_expunge(s, tag, 1, 0, 0, 0, 0, 0);
 }
 
-/* RFC 9051 SS6.4.2: like CLOSE but removes nothing -- a purely local state change. */
+/* RFC 9051 SS6.4.2: like CLOSE but removes nothing, a purely local state change. */
 int
 cmd_unselect(struct session *s, const char *tag, char *args)
 {
@@ -131,12 +131,12 @@ parse_store_flags(char *flagspec, uint32_t *sysflags_o
 			else if (strcasecmp(tok, "\\Draft") == 0)
 				sysflags |= MBOX_FLAG_DRAFT;
 			else if (strcasecmp(tok, "\\Recent") == 0) {
-				*errmsg = "\\Recent cannot be set -- RFC 9051 "
+				*errmsg = "\\Recent cannot be set, RFC 9051 "
 				    "deprecates it and excludes it from the "
 				    "flag grammar entirely";
 				return (-2);
 			} else {
-				*errmsg = "unsupported system flag -- v1 only "
+				*errmsg = "unsupported system flag"
 				    "supports \\Answered/\\Flagged/\\Deleted/"
 				    "\\Seen/\\Draft";
 				return (-2);
@@ -145,7 +145,7 @@ parse_store_flags(char *flagspec, uint32_t *sysflags_o
 		}
 
 		if (strchr(tok, ':') != NULL || strchr(tok, ',') != NULL) {
-			*errmsg = "keyword contains ':' or ',' -- not "
+			*errmsg = "keyword contains ':' or ',', not "
 			    "representable in this server's index format";
 			return (-2);
 		}
@@ -303,7 +303,7 @@ store_do(struct session *s, const char *tag, char *arg
 
 	if (strchr(seqtok, ',') != NULL) {
 		session_reply(s, tag, "BAD",
-		    "comma-separated sequence sets not supported in v1 -- "
+		    "comma-separated sequence sets not supported"
 		    "issue separate STORE commands");
 		return (1);
 	}
@@ -352,7 +352,7 @@ store_do(struct session *s, const char *tag, char *arg
 	}
 
 	if (s->mbox_readonly) {
-		/* RFC 9051 SS6.3.3: no changes on EXAMINE'd mailbox (RFC 5530 CANNOT) -- same check as session_request_expunge(). */
+		/* RFC 9051 SS6.3.3: no changes on EXAMINE'd mailbox (RFC 5530 CANNOT), same check as session_request_expunge(). */
 		session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
 		    "(selected via EXAMINE)");
 		return (1);
@@ -366,7 +366,7 @@ store_do(struct session *s, const char *tag, char *arg
 		return (1);
 	}
 
-	/* req already memset(3)'d and has_unchangedsince set earlier -- not re-zeroed here, to avoid wiping it out. */
+	/* req already memset(3)'d and has_unchangedsince set earlier, not re-zeroed here, to avoid wiping it out. */
 	req.seq_lo = lo;
 	req.seq_hi = hi;
 	req.lo_is_star = lo_star;
@@ -439,7 +439,7 @@ copy_move_dispatch(struct session *s, const char *tag,
 
 	if (strchr(seqtok, ',') != NULL) {
 		session_reply(s, tag, "BAD",
-		    "comma-separated sequence sets not supported in v1 -- "
+		    "comma-separated sequence sets not supported"
 		    "issue separate COPY/MOVE commands");
 		return (1);
 	}
@@ -464,7 +464,7 @@ copy_move_dispatch(struct session *s, const char *tag,
 	}
 
 	if (is_move && s->mbox_readonly) {
-		/* MOVE removes source messages (SS6.4.8) -- a change SS6.3.3 prohibits when EXAMINE'd; COPY is unaffected. */
+		/* MOVE removes source messages (SS6.4.8), a change SS6.3.3 prohibits when EXAMINE'd; COPY is unaffected. */
 		session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
 		    "(selected via EXAMINE)");
 		return (1);
@@ -572,7 +572,7 @@ session_send_expunge_response(struct session *s,
 	session_untagged(s, buf);
 }
 
-/* RFC 7162 SS3.2.6: one VANISHED (EARLIER) range, written immediately -- unlike QRESYNC SELECT's buffered resync. */
+/* RFC 7162 SS3.2.6: one VANISHED (EARLIER) range, written immediately, unlike QRESYNC SELECT's buffered resync. */
 void
 session_handle_fetch_vanished(struct session *s,
     const struct imsg_mbox_select_vanished *v)
@@ -598,12 +598,12 @@ session_request_expunge(struct session *s, const char 
 
 	if (s->mbox_readonly) {
 		if (is_close) {
-			/* RFC 9051 SS6.4.1: EXAMINE'd -- skip the round trip, just deselect and reply OK, no error. */
+			/* RFC 9051 SS6.4.1: EXAMINE'd, skip the round trip, just deselect and reply OK, no error. */
 			s->state = SESSION_AUTHENTICATED;
 			session_reply(s, tag, "OK", "CLOSE completed");
 			return (1);
 		}
-		/* Unlike CLOSE, plain/UID EXPUNGE has no read-only exception -- SS6.3.3 applies directly (RFC 5530 CANNOT). */
+		/* Unlike CLOSE, plain/UID EXPUNGE has no read-only exception, SS6.3.3 applies directly (RFC 5530 CANNOT). */
 		session_reply(s, tag, "NO", "[CANNOT] Mailbox is read-only "
 		    "(selected via EXAMINE)");
 		return (1);
@@ -656,7 +656,7 @@ uid_expunge_dispatch(struct session *s, const char *ta
 	}
 	if (strchr(args, ',') != NULL) {
 		session_reply(s, tag, "BAD",
-		    "comma-separated sequence sets not supported in v1 -- "
+		    "comma-separated sequence sets not supported"
 		    "issue separate UID EXPUNGE commands");
 		return (1);
 	}
@@ -796,7 +796,7 @@ session_finish_copy_or_move(struct session *s, const s
 	if (res->error != MBOX_OP_OK || s->copy_alloc_failed) {
 		char	text[48];
 
-		/* RFC 9051 SS6.4.7/SS6.4.8: destname valid but not found -- TRYCREATE, mirroring imsg_mbox_appended's field. */
+		/* RFC 9051 SS6.4.7/SS6.4.8: destname valid but not found, TRYCREATE, mirroring imsg_mbox_appended's field. */
 		if (!s->copy_alloc_failed &&
 		    res->error == MBOX_OP_ERR_NO_SUCH_MAILBOX)
 			snprintf(text, sizeof(text), "[TRYCREATE] no such "
blob - 14b7c31bb86a1912212a2074a0f5bb4d66a11915
blob + db2690adc00fb3bbeeb0b7eceb072944d0994ccf
--- src/store_internal.h
+++ src/store_internal.h
@@ -15,16 +15,7 @@
  */
 
 /*
- * store_internal.h -- internal, store-process-only shared declarations.
- *
- * Not installed, not part of imapd's public wire protocol (that's
- * imapd.h) -- this exists solely so store.c's core (the imsg dispatch
- * loop) and the eight files split out of what used to be one 7,768-line
- * store.c (index.c, mime.c, envelope.c, mbox_fetch.c, mbox_search.c,
- * mbox_store.c, mbox_manage.c, mbox_copy.c, and store.c itself) can all
- * see struct mbox_index/struct index_rec and each other's entry points.
- * Nothing in here crosses process boundaries -- that's imapd.h's job,
- * behind the imsg wire structs instead.
+ * store_internal.h, internal, store-process-only shared declarations.
  */
 
 #ifndef STORE_INTERNAL_H
@@ -40,41 +31,26 @@ struct mbox_index {
 	uint64_t	  highestmodseq; /* RFC 7162 SS3.1: per-mailbox highest
 					 * mod-sequence, persisted as the
 					 * index header's third field (see
-					 * index_load()/index_save()). v1's
-					 * only mailbox always supports this
-					 * (there is no on-disk format that
-					 * predates it in a real deployment of
-					 * this not-yet-released server), so
-					 * the NOMODSEQ response code
-					 * (RFC 7162 SS3.1.2.2) is simply
-					 * unreachable in this implementation. */
+					 * index_load()/index_save()). Always
+					 * supported, so NOMODSEQ (RFC 7162
+					 * SS3.1.2.2) is unreachable here. */
 	char		**lines;	/* raw "UID:basename:keywords:MODSEQ"
 					 * lines, no trailing newline, one
-					 * malloc(3) each -- the MODSEQ field
-					 * is this pass's addition; see
+					 * malloc(3) each; see
 					 * index_parse_line() */
 	size_t		  nlines;
 	size_t		  cap;
 };
 
 /*
- * One index message line, parsed. Introduced this pass (RFC 7162) to stop
- * handle_mbox_fetch()/handle_mbox_store()/handle_mbox_expunge() from each
- * hand-rolling their own "UID:basename:keywords" strchr() chain -- adding
- * a fourth colon-delimited field (MODSEQ) to every one of those independently
- * would have tripled the size of this change and the chance of one of them
- * drifting out of sync with the on-disk format. basename/keywords are
- * fixed-size copies (same 512-byte bound index_load()'s own STORE_INDEX_
- * LINE_MAX line buffer already implies is generous for either field), not
- * pointers into the original line -- callers are free to mutate or discard
- * the line string after parsing.
+ * One index message line, parsed, so handle_mbox_fetch()/handle_mbox_
+ * store()/handle_mbox_expunge() don't each hand-roll their own
+ * "UID:basename:keywords:MODSEQ" strchr() chain. Basename/keywords are
+ * fixed-size copies, not pointers into the original line, callers may
+ * mutate or discard the line string after parsing.
  *
- * Backward compatibility: the MODSEQ field is new this pass. A line with
- * only three colon-delimited fields (written by a pre-CONDSTORE build of
- * this server) parses successfully with modseq defaulted to 1 -- this
- * server has never had a real deployment to be compatible *with*, so this
- * is a defensive nicety, not a migration guarantee this project is making
- * any promise about.
+ * A line with only three colon-delimited fields (pre-CONDSTORE) parses
+ * successfully with modseq defaulted to 1.
  */
 struct index_rec {
 	uint32_t	uid;
@@ -85,10 +61,9 @@ struct index_rec {
 
 /*
  * One message staged by stage_copy_messages() (mbox_copy.c) for
- * handle_mbox_copy()/handle_mbox_move() -- read fully into memory from
- * the source mailbox, not yet written anywhere. Shared here (not local
- * to mbox_copy.c) because commit_copy_messages() and move_cross_mailbox()
- * take it as a parameter type too.
+ * handle_mbox_copy()/handle_mbox_move(). Read fully into memory from
+ * the source mailbox, not yet written. Shared here since commit_copy_
+ * messages() and move_cross_mailbox() also take it as a parameter type.
  */
 struct copy_staged {
 	uint32_t	src_uid;
@@ -100,77 +75,33 @@ struct copy_staged {
 	size_t		bodylen;
 };
 
-/*
- * F6 fix: bound how much message data COPY/MOVE holds in memory at once.
- * stage_copy_messages() reads every message in the range fully into RAM
- * before commit (required for RFC 9051 6.4.7 all-or-nothing COPY), so an
- * unbounded range of large, externally-delivered messages could exhaust
- * the store child's address space. Exceeding either limit fails the whole
- * COPY/MOVE cleanly (no partial copy). Tunable for larger deployments.
- */
 #define COPY_STAGE_MSG_MAX	((uint64_t)64 * 1024 * 1024)	/* per message */
 #define COPY_STAGE_TOTAL_MAX	((uint64_t)512 * 1024 * 1024)	/* per operation */
 
-/*
- * Globals shared across the files this process's source is split into
- * (definitions live in store.c's core; see that file for why each
- * exists).
+/* Globals shared across the files this process's source is split into
+ * (definitions live in store.c's core).
  */
 extern uint32_t	 session_id;
-extern uint32_t	 append_counter;
-extern char	 current_mailbox_dir[MBOX_NAME_MAX];
+extern uint32_t	 append_counter; /* per-store-child monotonic counter
+				 * feeding the maildir basename uniquer
+				 * (see handle_mbox_append()); shared with
+				 * handle_mbox_copy() so a copy's minted
+				 * basename can't collide with an APPEND's */
+extern char	 current_mailbox_dir[MBOX_NAME_MAX]; /* named mailbox (if
+				 * any) this store child's cwd is chdir'd
+				 * into, relative to the maildir root; ""
+				 * means INBOX/nothing selected.
+				 * handle_mbox_select() is the only writer */
 
-/*
- * Cross-file entry points. Same set of forward declarations store.c
- * carried as `static` prototypes before the file was split into store.c
- * (core) + index.c + mime.c + envelope.c + mbox_fetch.c + mbox_search.c
- * + mbox_store.c + mbox_manage.c + mbox_copy.c -- moved here verbatim
- * (minus `static`) rather than redesigned, so this split is a pure move
- * with no behavior change.
+/* Cross-file entry points: forward declarations for store.c (core) +
+ * index.c + mime.c + envelope.c + mbox_fetch.c + mbox_search.c +
+ * mbox_store.c + mbox_manage.c + mbox_copy.c.
  */
-extern uint32_t	 bodystructure_read_max; /* from IMSG_STORE_INIT --
-					 * see imapd.h's struct imsg_store_init
-					 * comment and BODYSTRUCTURE_READ_
-					 * DEFAULT's comment for what this
-					 * gates and where the operator-
-					 * configured value comes from. Missing
-					 * `extern` here once meant every file
-					 * that included this header got its
-					 * own tentative definition -- harmless
-					 * within a single translation unit
-					 * (which is how the original,
-					 * unsplit store.c got away with the
-					 * same bare declaration), but nine
-					 * separate definitions once split
-					 * across files, caught as a real
-					 * duplicate-symbol link error. */
+extern uint32_t	 bodystructure_read_max; /* from IMSG_STORE_INIT; see
+					 * imapd.h's imsg_store_init and
+					 * BODYSTRUCTURE_READ_DEFAULT
+					 * comments */
 
-/*
- * Per-store-child monotonic counter feeding the maildir basename uniquer
- * (see handle_mbox_append()'s header comment for the full `<timestamp>.
- * <pid>_<counter>.<hostname>` scheme). Was originally a function-local
- * static inside handle_mbox_append() alone; hoisted to file scope this
- * pass so handle_mbox_copy() -- COPY's own message-duplication path, which
- * mints a brand new basename per copied message the same way APPEND does
- * -- shares the same monotonic sequence rather than starting its own at 0,
- * which could otherwise collide with an APPEND's basename minted in the
- * same second by the same pid.
- */
-
-/*
- * RFC 9051 SS6.3.4-SS6.3.6/SS6.3.9 addition (flat multi-mailbox support):
- * which named mailbox (if any) this store child's cwd is currently
- * chdir'd into,
- * relative to the session's own maildir root. Empty string means "at the
- * root" -- i.e. INBOX, or nothing selected yet -- the same state every
- * store child has always implicitly been in before this pass, when INBOX
- * was the only mailbox that could ever exist. handle_mbox_select() is the
- * only function that ever changes this; every other IMSG_MBOX_* handler
- * (FETCH/STORE/EXPUNGE/IDLE-refresh/QRESYNC/...) keeps using bare
- * relative paths exactly as before, unmodified, since cwd is always
- * "wherever the currently selected mailbox is" by construction.
- */
-
 void	 store_dispatch(int, short, void *);
 void	 store_shutdown(void);
 void	 handle_mbox_select(struct imsg_mbox_select *, struct imsgev *);
@@ -239,29 +170,19 @@ int	 extract_mime_part(const char *, const int *, int,
 /*
  * The maildir+index format: stock maildir (tmp/new/cur) for message
  * bodies, plus one small line-oriented text index per mailbox holding
- * UIDVALIDITY/UIDNEXT and the UID<->basename(<->keywords) map. v1 is
- * INBOX-only, and that index lives directly in
- * the maildir root store.c is now chdir'd into -- a sibling of tmp/new/
- * cur, not inside any of them. Name is plain and `ls`-visible on
- * purpose, matching this project's own stated preference for
- * boring/inspectable formats over hidden dotfiles.
+ * UIDVALIDITY/UIDNEXT and the UID<->basename(<->keywords) map. The index
+ * lives directly in that mailbox's maildir root, a sibling of tmp/new/
+ * cur. Name is plain and `ls`-visible on purpose.
  */
 #define STORE_INDEX_NAME	"imapd.index"
 #define STORE_INDEX_TMP_NAME	"imapd.index.tmp"
-#define STORE_INDEX_LINE_MAX	1024	/* matches auth.c's cred_lookup()
-					 * line-buffer precedent for the same
-					 * kind of small, personal-scope flat
-					 * text file */
+#define STORE_INDEX_LINE_MAX	1024
 
-/*
- * In-memory copy of one mailbox's index while a single IMSG_MBOX_SELECT
- * (or, later, any other mutating mbox op) is being handled -- built fresh
- * from the on-disk file, mutated, and rewritten in full per the design
- * doc's resolved "whole file rewritten to a temp file and rename(2)'d
- * over on every mutation" rule, then discarded. Never kept around between
- * imsg messages.
+/* In-memory copy of one mailbox's index while a single mutating mbox op
+ * is handled. Built fresh from the on-disk file, mutated, and
+ * rewritten in full (temp file + rename(2)) on every mutation, then
+ * discarded. Never kept around between imsg messages.
  */
-
 int	 index_load(int, struct mbox_index *);
 int	 index_has_basename(struct mbox_index *, const char *);
 int	 index_append(struct mbox_index *, uint32_t, const char *);
@@ -278,9 +199,7 @@ void	 qresync_send_resync(const struct imsg_mbox_selec
 
 /*
  * The remaining six are single-purpose helpers that happen to be called
- * from more than one of the split-out files (unlike the bulk of each
- * file's own static helpers, which stayed file-local) -- see each
- * definition's own comment for why.
+ * from more than one of the split-out files.
  */
 const char	*append_hostname(void);
 int		 ensure_maildir_dirs(const char *);
blob - 17b76e30c7c676291164a0bd047b0c49a090866d
blob + 4e312adf98b142d52cacc81428dddf30a46cd113
--- src/store_ipc.c
+++ src/store_ipc.c
@@ -15,7 +15,7 @@
  */
 
 /*
- * store_ipc.c -- listener-side dispatch of the store process's async imsg
+ * store_ipc.c, listener-side dispatch of the store process's async imsg
  * protocol: session_handle_mbox_*()/session_finish_*() reply handlers.
  */
 
@@ -149,7 +149,7 @@ session_store_dispatch(int fd, short event, void *arg)
 			struct imsg_mbox_fetch_body	 bodyhdr;
 			size_t				 bodylen;
 
-			/* Same technique as IMSG_MBOX_FETCH_HEADER just above -- see that case's comment. */
+			/* Same technique as IMSG_MBOX_FETCH_HEADER just above, see that case's comment. */
 			if (imsg_get_buf(&imsg, &bodyhdr, sizeof(bodyhdr)) ==
 			    -1) {
 				log_warnx("bad IMSG_MBOX_FETCH_BODY (header)");
@@ -159,7 +159,7 @@ session_store_dispatch(int fd, short event, void *arg)
 			s->pending_body_buf = NULL;
 			s->pending_body_len = 0;
 			s->pending_body_found = 0;
-			/* pending_body_label/has_partial/partial_origin are NOT reset here -- set once per FETCH in fetch_dispatch(). */
+			/* pending_body_label/has_partial/partial_origin are NOT reset here, set once per FETCH in fetch_dispatch(). */
 
 			bodylen = imsg_get_len(&imsg);
 			if (!bodyhdr.found)
@@ -196,7 +196,7 @@ session_store_dispatch(int fd, short event, void *arg)
 			struct imsg_mbox_fetch_envelope	 envhdr;
 			size_t					 envlen;
 
-			/* Same technique as IMSG_MBOX_FETCH_HEADER/IMSG_MBOX_FETCH_BODY -- see IMSG_MBOX_FETCH_HEADER's comment. */
+			/* Same technique as IMSG_MBOX_FETCH_HEADER/IMSG_MBOX_FETCH_BODY, see IMSG_MBOX_FETCH_HEADER's comment. */
 			if (imsg_get_buf(&imsg, &envhdr, sizeof(envhdr)) ==
 			    -1) {
 				log_warnx("bad IMSG_MBOX_FETCH_ENVELOPE (header)");
@@ -241,7 +241,7 @@ session_store_dispatch(int fd, short event, void *arg)
 			struct imsg_mbox_fetch_bodystructure	 bshdr;
 			size_t					 bslen;
 
-			/* Same technique as IMSG_MBOX_FETCH_HEADER/IMSG_MBOX_FETCH_ENVELOPE -- see IMSG_MBOX_FETCH_HEADER's comment. */
+			/* Same technique as IMSG_MBOX_FETCH_HEADER/IMSG_MBOX_FETCH_ENVELOPE, see IMSG_MBOX_FETCH_HEADER's comment. */
 			if (imsg_get_buf(&imsg, &bshdr, sizeof(bshdr)) ==
 			    -1) {
 				log_warnx("bad IMSG_MBOX_FETCH_BODYSTRUCTURE "
@@ -437,9 +437,9 @@ session_store_dispatch(int fd, short event, void *arg)
 	imsgev_add(s->store_iev);	/* re-arm for the next round of IMSG_MBOX_* replies */
 	(void)fd;
 
-	/* If the reply just processed above was the terminal one, s->state is idle again -- drain anything pipelined behind it. */
+	/* If the reply just processed above was the terminal one, s->state is idle again, drain anything pipelined behind it. */
 	if (!session_dequeue_next(s))
-		return;	/* s was torn down by a queued LOGOUT -- do not touch */
+		return;	/* s was torn down by a queued LOGOUT, do not touch */
 }
 
 /* Finishes SELECT (SS6.3.2); flushes QRESYNC resync in RFC order: VANISHED (EARLIER), then FETCH, then tagged OK. */
@@ -504,7 +504,7 @@ session_handle_mbox_selected(struct session *s, const 
 		size_t	vlen;
 		int	flen;
 
-		/* session_untagged()'s 512-byte buffer is too small here -- same bypass session_finish_search() uses for ESEARCH. */
+		/* session_untagged()'s 512-byte buffer is too small here, same bypass session_finish_search() uses for ESEARCH. */
 		vlen = format_range_list(vbuf, sizeof(vbuf),
 		    s->vanished_ranges, s->vanished_nranges, &truncated);
 		if (truncated)
@@ -555,13 +555,13 @@ session_handle_mbox_status_result(struct session *s,
 	s->state = s->status_prev_state;
 
 	if (res->error != MBOX_OP_OK) {
-		/* Valid-but-missing name or store.c's open/flock/index_load failure -- can't tell apart, so NONEXISTENT covers both. */
+		/* Valid-but-missing name or store.c's open/flock/index_load failure, can't tell apart, so NONEXISTENT covers both. */
 		session_reply(s, s->pending_tag, "NO",
 		    "[NONEXISTENT] no such mailbox");
 		return;
 	}
 
-	/* Quoted, not bare -- mailbox names can contain spaces; no backslash-escaping, same gap parse_list_token() has. */
+	/* Quoted, not bare, mailbox names can contain spaces; no backslash-escaping, same gap parse_list_token() has. */
 	len = (size_t)snprintf(buf, sizeof(buf), "STATUS \"%s\" (",
 	    s->status_mailbox);
 
@@ -578,7 +578,7 @@ session_handle_mbox_status_result(struct session *s,
 	if (s->status_attrs & STATUS_ATT_MESSAGES)
 		STATUS_APPEND("MESSAGES %u", res->messages);
 	if (s->status_attrs & STATUS_ATT_RECENT)
-		/* IMAP4rev2 dropped \Recent (RFC 9051 SS2.3.2) -- this
+		/* IMAP4rev2 dropped \Recent (RFC 9051 SS2.3.2), this
 		 * server tracks no such state, so always answer 0; placed
 		 * here to mirror IMAP4rev1's conventional MESSAGES/RECENT
 		 * ordering, though RFC 9051 response order is unspecified
@@ -639,7 +639,7 @@ session_finish_mbox_op(struct session *s, const struct
 		    "[ALREADYEXISTS] mailbox already exists");
 		return;
 	default:
-		/* mkdir/stat/rename(2) I/O error or truncated buffer -- no RFC 5530 code fits, plain NO. */
+		/* mkdir/stat/rename(2) I/O error or truncated buffer, no RFC 5530 code fits, plain NO. */
 		snprintf(text, sizeof(text), "%s failed", cmdname);
 		session_reply(s, s->pending_tag, "NO", text);
 		return;
@@ -651,7 +651,7 @@ session_finish_mbox_op(struct session *s, const struct
 	    strlcpy(s->selected_mailbox, s->rename_newname,
 	    sizeof(s->selected_mailbox)) >= sizeof(s->selected_mailbox))
 		log_warnx("session %u: selected_mailbox truncated after "
-		    "RENAME -- can't happen (both same size)", s->id);
+		    "RENAME, can't happen (both same size)", s->id);
 
 	snprintf(text, sizeof(text), "%s completed", cmdname);
 	session_reply(s, s->pending_tag, "OK", text);
@@ -668,12 +668,12 @@ session_handle_mbox_list_item(struct session *s,
 	if (!list_pattern_match(s->list_pattern, item->mailbox, 0))
 		return;
 
-	/* Quoted, not bare -- same reasoning as session_handle_mbox_status_result(); "()" = no attributes (SS7.3.1). */
+	/* Quoted, not bare, same reasoning as session_handle_mbox_status_result(); "()" = no attributes (SS7.3.1). */
 	snprintf(buf, sizeof(buf), "%s () \"/\" \"%s\"", kw, item->mailbox);
 	session_untagged(s, buf);
 }
 
-/* Terminal LIST/LSUB reply; a non-OK error means a real I/O error only -- mismatches already produce no item, silently. */
+/* Terminal LIST/LSUB reply; a non-OK error means a real I/O error only, mismatches already produce no item, silently. */
 void
 session_finish_list(struct session *s, const struct imsg_mbox_result *res)
 {
@@ -824,7 +824,7 @@ session_push_idle_expunges(struct session *s, const ui
 	}
 }
 
-/* Terminal IDLE_REFRESH reply; push gated on s->idling && SELECTED -- client may've sent DONE/a new SELECT meanwhile. */
+/* Terminal IDLE_REFRESH reply; push gated on s->idling && SELECTED, client may've sent DONE/a new SELECT meanwhile. */
 void
 session_handle_idle_refreshed(struct session *s,
     const struct imsg_mbox_idle_refreshed *res)
@@ -886,7 +886,7 @@ session_request_idle_refresh(struct session *s)
 		return;
 	}
 	if (s->store_iev == NULL)
-		return;		/* no store child wired -- nothing to ask */
+		return;		/* no store child wired, nothing to ask */
 
 	s->idle_refresh_pending = 1;
 	if (imsg_compose(&s->store_iev->ibuf, IMSG_MBOX_IDLE_REFRESH, 0, 0,
@@ -896,7 +896,7 @@ session_request_idle_refresh(struct session *s)
 	imsgev_add(s->store_iev);
 }
 
-/* Notifies same-uid idling sessions after a UID-changing op (not plain STORE); no mailbox filter -- harmless over-notify. */
+/* Notifies same-uid idling sessions after a UID-changing op (not plain STORE); no mailbox filter, harmless over-notify. */
 void
 session_notify_idle_peers(const struct session *s)
 {
@@ -943,7 +943,7 @@ session_handle_mbox_result(struct session *s, struct i
 	}
 	if (s->state == SESSION_CREATING || s->state == SESSION_DELETING ||
 	    s->state == SESSION_RENAMING) {
-		/* RFC 9051 SS6.3.4-SS6.3.6: same peel-off pattern as SEARCH/COPY -- reply text doesn't fit this function's cmdname table. */
+		/* RFC 9051 SS6.3.4-SS6.3.6: same peel-off pattern as SEARCH/COPY, reply text doesn't fit this function's cmdname table. */
 		session_finish_mbox_op(s, res);
 		return;
 	}
@@ -953,7 +953,7 @@ session_handle_mbox_result(struct session *s, struct i
 		return;
 	}
 
-	/* RFC 9051 SS6.4.9: becomes "UID <CMD>" when s->cmd_by_uid is set -- CLOSE is the exception (no "UID CLOSE"). */
+	/* RFC 9051 SS6.4.9: becomes "UID <CMD>" when s->cmd_by_uid is set, CLOSE is the exception (no "UID CLOSE"). */
 	if (s->state == SESSION_STORING) {
 		cmdname = s->cmd_by_uid ? "UID STORE" : "STORE";
 		was_storing = 1;
@@ -976,7 +976,7 @@ session_handle_mbox_result(struct session *s, struct i
 	s->state = was_close ? SESSION_AUTHENTICATED : SESSION_SELECTED;
 
 	if (res->error != MBOX_OP_OK) {
-		/* v1's only failure mode is an index I/O error; RFC 9051 has no specific code for it, so a plain NO is honest. */
+		/* only failure mode is an index I/O error; RFC 9051 has no specific code for it, so a plain NO is honest. */
 		char	text[32];
 
 		free(s->store_modified);
@@ -989,7 +989,7 @@ session_handle_mbox_result(struct session *s, struct i
 		return;
 	}
 
-	/* RFC 7162 SS3.1.3: a failed-conditional STORE gets MODIFIED on its tagged OK; v1 never needs the tagged-NO form. */
+	/* RFC 7162 SS3.1.3: a failed-conditional STORE gets MODIFIED on its tagged OK */
 	if (was_storing && s->store_modified_n > 0) {
 		char	rbuf[2048];
 		char	text[2048 + 32];
@@ -1003,11 +1003,7 @@ session_handle_mbox_result(struct session *s, struct i
 		/*
 		 * text's ~32-byte margin over rbuf's 2048 is enough for the
 		 * wrapper words but not always for wrapper + a near-max
-		 * rbuf + "UID STORE" together (worst case ~2106 bytes) --
-		 * session_reply() itself can no longer mangle the line if
-		 * this truncates (it preserves the trailing CRLF), but the
-		 * MODIFIED list could still lose its tail silently. Check
-		 * and log it, same as rbuf's own truncation just above.
+		 * rbuf + "UID STORE" together.
 		 */
 		if (snprintf(text, sizeof(text), "[MODIFIED %s] Conditional "
 		    "%s failed for some messages", rbuf, cmdname) >=
@@ -1027,11 +1023,11 @@ session_handle_mbox_result(struct session *s, struct i
 	s->store_modified_n = 0;
 	s->store_modified_cap = 0;
 
-	/* RFC 9051 SS6.3.13: gated on was_expunging not res->count (CLOSE's count is always 0) -- harmless extra refresh. */
+	/* RFC 9051 SS6.3.13: gated on was_expunging not res->count (CLOSE's count is always 0), harmless extra refresh. */
 	if (was_expunging)
 		session_notify_idle_peers(s);
 
-	/* RFC 7162 SS3.2.7: real EXPUNGE (not CLOSE -- SS3.2.8 forbids it) with count>0 gets HIGHESTMODSEQ, once CONDSTORE-aware. */
+	/* RFC 7162 SS3.2.7: real EXPUNGE (not CLOSE, SS3.2.8 forbids it) with count>0 gets HIGHESTMODSEQ, once CONDSTORE-aware. */
 	if (was_expunging && !was_close && s->condstore_enabled &&
 	    res->count > 0) {
 		char	text[64];
@@ -1050,4 +1046,3 @@ session_handle_mbox_result(struct session *s, struct i
 		session_reply(s, s->pending_tag, "OK", text);
 	}
 }
-