Add per-domain DKIM signing, admin console TLS, log rotation, and resolver timeout scoping

- DKIM signing was hardcoded to one domain; now keyed per-domain off
  the sending address, fixing mail for every domain but the original.
- Admin console (port 1027) is now TLS-wrapped instead of plaintext.
- barantmail.log now rotates by size (config-driven) instead of
  growing unbounded (previously hit 23GB).
- ResolverThreadScope.h: a <setenv> scoping fix for name resolution
  inside a <try> block that previously did not survive its own
  container.
- IMAPArgs.cpp: fixed "UID STORE 1:*" wrongly requiring UID 1 to
  exist, which silently made empty-folder operations delete nothing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brad ArantandClaude Sonnet 5 committed 2026-10-06 17:39:06 +00:00
1 parent 5f3e6ed2e6
commit 407cc2e915
11 files changed
+284 -81

No files matched your search

+40 -7
View File
@@ -73,6 +73,35 @@ namespace mail {
uint32_t uid = resolveNumber(part);
if (uid != 0)
result.push_back(uid);
} else if (uidMode) {
// A UID range's boundaries are just numbers, not required
// to name an existing message's UID - RFC 3501: "A:B"
// means every message whose UID falls in [A,B], not "the
// two messages literally named A and B". resolveNumber()
// above requires an exact existing-UID match via
// folder.find(), which is correct for a bare single-UID
// reference but wrong here: a real, long-lived mailbox
// almost never has a message with UID exactly 1 once
// anything has ever been expunged, so the standard
// "select everything" idiom "UID STORE 1:* +FLAGS
// (\\Deleted)" (used by the admin empty-folder feature)
// resolved fromUid to 0 and silently skipped the whole
// range every time - confirmed live: it reported
// {"ok":true} while deleting nothing from a real
// 538-message Trash folder. Scanning directly by UID
// instead of resolving each boundary to a sequence
// position first sidesteps that entirely.
std::string fromStr = part.substr(0, colon);
std::string toStr = part.substr(colon + 1);
uint32_t fromUid = (fromStr == "*") ? (folder.size() == 0 ? 0 : folder.messages().back().uid)
: (uint32_t)std::strtoul(fromStr.c_str(), nullptr, 10);
uint32_t toUid = (toStr == "*") ? (folder.size() == 0 ? 0 : folder.messages().back().uid)
: (uint32_t)std::strtoul(toStr.c_str(), nullptr, 10);
if (fromUid > toUid)
std::swap(fromUid, toUid);
for (const auto &message : folder.messages())
if (message.uid >= fromUid && message.uid <= toUid)
result.push_back(message.uid);
} else {
std::string fromStr = part.substr(0, colon);
std::string toStr = part.substr(colon + 1);
@@ -82,14 +111,18 @@ namespace mail {
continue;
if (fromUid > toUid)
std::swap(fromUid, toUid);
// Sequence-number range (plain, non-UID STORE/FETCH/etc.) -
// resolveNumber() already turned both ends into concrete
// UIDs regardless of uidMode - walking sequence-number-
// by-sequence-number (not uid-by-uid) from here is
// deliberate: a UID range like "5:9" must include every
// message whose UID falls in that range, not just
// messages whose UID happens to equal one of the
// integers 5-9 (UIDs aren't dense/contiguous once
// anything's been expunged).
// UIDs, and unlike the uidMode branch above, a plain
// sequence number always names a real position
// (1..folder.size()), so this round-trip through
// folder.seq() doesn't have that branch's problem. Walking
// sequence-number-by-sequence-number (not uid-by-uid) is
// still deliberate here: a range like "5:9" must include
// every message whose UID falls in that range, not just
// messages whose UID happens to equal one of the integers
// 5-9 (UIDs aren't dense/contiguous once anything's been
// expunged).
uint32_t fromSeq = folder.seq(fromUid);
uint32_t toSeq = folder.seq(toUid);
if (fromSeq == 0 || toSeq == 0)