# Local mail protocol fixtures These fixtures are intentionally bound to loopback and use non-standard ports. They are development tools, not production configuration. ## Release and sanitizer harnesses The scripts used for the 0.7 release evidence are kept here rather than as one-off files in `/tmp`: - `test-config-tls-material.sh` generates disposable GnuTLS material and covers CFG-09/CFG-10 generation, permissions, reuse, and unsafe-input cases. - `web-direct-tls-smoke.sh` covers the CFG-08 direct Web TLS 1.2/1.3 listener, security headers, plain-HTTP rejection, and a mismatched private key. - `generate-protocol-test-config.py` creates the disposable local protocol configuration used by sanitizer sessions. - `protocol-smoke.py` delivers through authenticated SMTP submission and reads the message through IMAP/POP over explicit and implicit TLS. - `sanitizer-protocol-session.sh` runs that protocol path under ASan/UBSan/LSan or TSan and validates clean process shutdown. - `asan-protocol-session.sh` and `tsan-protocol-session.sh` preserve the two explicit entry points used by the original release runs. - `sieve-validate-no-lsan.sh` is the narrow helper used by the ASan session; the Sieve validator remains leak-tested directly by CTest. - `cpack-stage-smoke.sh` checks imports and required executables from an extracted CPack staging tree in a private mount namespace. - `debian-source-probe.sh` and `debian-libspf2-package-build.sh` preserve the disposable Debian container probes used before the complete bundle builder was available. - `setup-wizard-defaults.py`, `config-tui-edit.py`, `legacy-config-install.py`, `protocol-diagnostics.py`, and `store-diagnostics.py` preserve the remaining interactive and diagnostic bring-up tests. Destructive live helpers require an explicit opt-in environment variable. - `mail-dns-live-check.py` logs in through the real Web and IMAPS interfaces and verifies that setup produced both an actionable SPF/DKIM/DMARC task and a forwardable administrator message without private-key material. - `dkim-material-check.py` compares a real OpenDKIM-generated RSA-2048 key with Bongo's GnuTLS-derived public value, imports it, checks permissions and DNS output, then verifies byte-identical reuse. - `manager-lifecycle-check.sh` exercises systemd reload/restart/stop/start, one agent crash, and an unclean manager death which leaves a stale PID file. It requires explicit live-test opt-in and restores an active service on exit. - `agent-privilege-check.sh` verifies the live manager/agent process tree, real/effective/saved UID and GID tuples, supplementary groups, and inherited, permitted, effective, and ambient capabilities. - `store-user-layout-check.sh` idempotently provisions disposable Store, Queue, quota, and protocol users, then proves that two freshly accessed accounts have separate Store databases and the complete standard folder layout, including the IMAP `\\Junk` spam collection. - `store-backup-compare.py` compares backups made before and after a restore. It requires byte-identical payloads, paths, GUIDs, flags, IMAP UIDs, custom properties, and calendar-link metadata while ignoring only Store-generated creation/modification timestamps. - `store-protocol-safety-check.py` sends unknown, malformed, oversized, and unauthorised commands to a live Store listener. It verifies that maintenance/debug commands remain manager-only, a user cannot inspect a foreign Store, and the listener remains available after every rejection. - `store-quota-concurrency-check.py` writes oversized contact and calendar fixtures without charging mail quota, then races two Store deliveries against one remaining quota slot. Exactly one delivery must succeed and the other must receive Store status 5220. The complete mapping from the original one-off `/tmp` names to maintained scripts is recorded in `tmp-script-map.md`. Every script which creates credentials requires disposable values through the environment; no test password or private key is stored in Git. For example: ```sh export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' export BONGO_TEST_ROOT=/tmp/bongo-0.7-r1-asan-root/usr export BONGO_SANITIZER=asan unshare --user --map-root-user --net \ ./contrib/testing/sanitizer-protocol-session.sh ``` The live user-layout check deliberately retains its clearly named accounts so later Queue, quota, and protocol matrix rows can reuse them: ```sh export BONGO_ALLOW_LIVE_USER_TEST=1 export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/store-user-layout-check.sh ``` The Queue-to-Store byte-preservation check uses the retained `stqqueue` account. It creates a target-999 diagnostic entry, exports the exact Queue-owned bytes, delivers that entry through Queue's local Store path, and compares the resulting Store object byte-for-byte: ```sh ./contrib/testing/queue-store-byte-check.sh ``` The script deletes the held Queue entry even on failure. It deliberately retains the delivered Store object as release evidence. Set `BONGO_QUEUE_TOOL`, `BONGO_STORE_TOOL`, or `BONGO_TEST_RUN_AS` only when testing a non-standard installation. The Queue disk-space check brackets `QDSPC` with filesystem samples and verifies that the suffixless `queue` configuration's `minimumfreespace` reserve was subtracted without 32-bit truncation: ```sh ./contrib/testing/queue-disk-space-check.sh ``` The Queue failure-safety check retains one isolated message in an 8 MiB tmpfs fixture while it first fills that filesystem below the operative reserve and then overlays the spool with an unreadable directory. It verifies rejection/defer responses, restores all mounts through traps, and compares the retained message byte-for-byte after both recoveries: ```sh ./contrib/testing/queue-failure-safety-check.sh ``` It requires passwordless permission for Bongo service start/stop/restart, `mount`, `umount`, and `stat` on the dedicated test spool. Run it only on a disposable test installation. The Store protocol safety check is non-destructive and requires the two disposable users retained by `store-user-layout-check.sh`: ```sh export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/store-protocol-safety-check.py ``` The password is read only from the environment and is redacted by the Store's command logger. The test deliberately attempts `REINDEX`, `REPAIR`, `RESET`, and `SHUTDOWN` as a normal user; run it only against a build where those commands require manager privileges. The live Store quota check uses the retained `stqquota` account. It reads and restores the account's original limit, removes every fixture document, and requires explicit destructive-test opt-in: ```sh export BONGO_ALLOW_LIVE_USER_TEST=1 export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/store-quota-concurrency-check.py ``` The script invokes `sudo -n bongo-admin user quota` only to set and restore the temporary administrative limit. The password is never passed on a command line. The partial Queue quota check creates two held messages and temporarily leaves space for exactly one of them. It verifies that the first reaches Store, the second remains byte-identical in Queue after Store returns quota exceeded, and the retained message is delivered exactly once after the first is deleted: ```sh export BONGO_ALLOW_LIVE_USER_TEST=1 export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/queue-quota-partial-check.py ``` Every fixture message and Queue entry is removed and the original quota is restored, including after a failed assertion. The normal SMTP companion check submits an authenticated message to the full test mailbox. It verifies that SMTP accepts the durable Queue transaction, Store receives no partial object, the sender receives exactly the standard `5.2.2` DSN, and that ordinary SMTP mail is not retained indefinitely: ```sh export BONGO_ALLOW_LIVE_USER_TEST=1 export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/smtp-quota-dsn-check.py ``` The Collector companion creates a real Queue transaction carrying `MSG_FLAG_COLLECTED_MESSAGE`. It proves that a full Store keeps the sole durable copy in local-delivery queue 6, never hands it to the outgoing SMTP agent, and delivers it exactly once after the quota is released: ```sh export BONGO_ALLOW_LIVE_USER_TEST=1 export BONGO_TEST_PASSWORD='a-disposable-password-of-at-least-12-characters' ./contrib/testing/collector-quota-retry-check.py ``` Set `BONGO_TEST_TRACE=1` to print bounded per-stage timings. On failure, fixtures are normally removed and the original quota is restored. For local diagnosis only, `BONGO_TEST_KEEP_FAILURE=1` preserves the matching Queue entry while still restoring the quota; remove that entry with `bongo-queuetool delete` after inspection. The Debian source probes and package-build commands used by BLD-14/BLD-15 are consolidated in `contrib/debian/build-trixie-bundle.sh`; they are not maintained as duplicate container-only snippets here. ## Cyrus IMAP, POP3, and LMTP Install `net-mail/cyrus-imapd`, then start the disposable fixture as root: ```sh sudo ./contrib/testing/cyrus-fixture.py start ``` It creates no files outside `/tmp/bongo-cyrus-fixture` and listens on: - IMAP: `127.0.0.1:18143` - POP3: `127.0.0.1:18110` - LMTP: `127.0.0.1:18024` The source mailboxes form a multi-account-per-user matrix: | Bongo owner | Cyrus source account | Protocol | Bongo destination | | --- | --- | --- | --- | | `test1` | `test1-pop@cyrus.bongo.test` | POP3 | `INBOX` | | `test1` | `test1-imap@cyrus.bongo.test` | IMAP | `Cyrus IMAP` | | `test2` | `test2-pop@cyrus.bongo.test` | POP3 | `INBOX` | | `test2` | `test2-imap@cyrus.bongo.test` | IMAP | `Cyrus IMAP` | All source accounts use the test-only password `BongoCollector-Test-2026`. Insert a complete RFC 5322 message through Cyrus' real LMTP delivery path: ```sh sudo ./contrib/testing/cyrus-fixture.py inject message.eml ``` Seed three different messages in each source mailbox and exercise Bongo's real Collector POP3 and IMAP transport implementation: ```sh ./contrib/testing/cyrus-fixture.py verify-collector \ --probe ./build/src/agents/collector/collector-remote-transport-probe \ --count 3 ``` The probe lists and fetches every message, checks its fixture marker, deletes it through the selected protocol, and verifies that every Cyrus source mailbox is empty. A live end-to-end run creates all four Bongo external-account records. This proves that accounts, remote UID/UIDL values, retry state and deletion state remain scoped by account ID even when one Bongo user owns multiple sources. All four use `after_import`, so Cyrus deletion happens only after the Bongo Queue has durably accepted and committed the complete message. From that point the normal scanner, rules, quota, and Store delivery pipeline owns it. The fixture can also seed deterministic scanner and quota cases. All ports remain loopback-only and the flood is deliberately bounded: ```sh sudo ./contrib/testing/cyrus-fixture.py seed --profile gtube --count 1 sudo ./contrib/testing/cyrus-fixture.py seed --profile eicar --count 1 sudo ./contrib/testing/cyrus-fixture.py seed --profile flood --count 32 \ --message-size 131072 sudo ./contrib/testing/cyrus-fixture.py seed-suite ``` `gtube` contains SpamAssassin's standard GTUBE string. `eicar` creates the harmless standard EICAR attachment at runtime; the complete signature is kept out of the Git checkout to avoid host antivirus false alarms. `seed-suite` places three normal messages, one GTUBE message, one EICAR message, and 32 128-KiB flood messages in every source mailbox (148 messages total). Set a small quota on disposable Bongo users before collection, then restore it after the test. The test passes only when clean messages are imported, scanner policy is applied, quota cannot be exceeded, and a message rejected before a durable import remains retrievable from Cyrus. The same LMTP listener is the forwarding target for the exact transport-domain mapping below. This accepts `*@cyrus.bongo.test` without making the test domain a local Bongo domain: ```json "lmtp_transports": ["cyrus.bongo.test=127.0.0.1:18024"] ``` Cyrus `lmtpd` is pre-authorized only on this loopback-only fixture listener. That matches a trusted local MTA-to-store LMTP hop and must not be copied to a network-exposed Cyrus listener. The generated short-lived test CA at `/tmp/bongo-cyrus-fixture/tls/ca.crt` is the fixture trust anchor. Set `BONGO_COLLECTOR_CAINFO` to that path for the Collector process during this test; normal installations continue to use the operating-system trust store. The packaged `bongo.service` uses `PrivateTmp=true`, so a live systemd test must make the fixture visible inside that service namespace. A runtime-only drop-in can bind precisely this fixture read-only: ```ini [Service] BindReadOnlyPaths=/tmp/bongo-cyrus-fixture ``` Install it with `systemctl edit --runtime bongo.service`, restart Bongo, and set the Collector `ca_file` to the path above. Do not weaken certificate or host-name verification for the test. After a live collection cycle, show the source-mailbox counts with: ```sh ./contrib/testing/cyrus-fixture.py mailbox-status ``` Stop or remove all fixture state with: ```sh sudo ./contrib/testing/cyrus-fixture.py stop sudo ./contrib/testing/cyrus-fixture.py reset ``` ## Outbound SMTP capture Use `net-mail/xeams-devnullsmtp-bin` from the Bongo Gentoo overlay: ```sh xeams-devnullsmtp -p 2526 -d bongo.test -s /tmp/bongo-devnull-mail ``` Configure `127.0.0.1:2526` as Bongo's relay host before testing any non-local recipient. Xeams DevNullSMTP has no listen-address option, so isolate it with a firewall or network namespace; do not expose it on a production network. ## HAProxy and PROXY v2 `haproxy-bongo-test.cfg` exposes loopback-only high ports and forwards every connection to the corresponding normal Bongo listener with a PROXY v2 header. Before starting it, temporarily enable `proxy_protocol_enabled` and set `proxy_protocol_networks` to `["127.0.0.1/32"]` in the `smtp`, `imap`, `pop3`, and `sieve` documents. Never trust an unbounded network for PROXY headers. Validate and run the fixture in the foreground: ```sh haproxy -c -f contrib/testing/haproxy-bongo-test.cfg haproxy -db -f contrib/testing/haproxy-bongo-test.cfg ``` The test listeners are SMTP `11025`, submission `11587`, submissions `11465`, IMAP `11143`, IMAPS `11993`, POP3 `11110`, POP3S `11995`, and ManageSieve `14190`. Exercise real protocol greetings, capabilities, authentication, and TLS handshakes through these ports, then restore the original Bongo documents.