)]}'
{
  "log": [
    {
      "commit": "c79cf9aa95dec1292988e4c7a02f469db7d534d4",
      "tree": "a8133c02f5f59b243795d49be3cd70cd527797b2",
      "parents": [
        "929a4937a1dc97e35668cc5fa1e523266080b5ca"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue May 05 16:50:35 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue May 05 16:50:35 2026 -0400"
      },
      "message": "tools: add resign-modern-hash.py for SHA-1 certification sigs\n\nThe certifier-side sibling of rebind-modern-hash.py. Where that tool\nfixes a keyholder\u0027s own UID/subkey self-signatures, this one scans the\nwhole keyring for third-party certifications the operator issued with a\nweak hash algorithm (SHA-1, MD5, RIPEMD-160) and emits the gpg(1)\ncommand sequence to re-sign them with SHA-512.\n\nKey design points:\n\n- Single-shot scan: gpg --with-colons --list-sigs with no FPR reads the\n  entire keyring in one subprocess call.\n- --force-sign-key is mandatory in every emitted command; without it\n  gpg --quick-sign-key silently no-ops when any sig from the issuer\n  already exists on a UID -- exactly the case we are fixing.\n- --default-cert-level takes 0..3 (not the decimal of the 0x10-0x13\n  hex class); the tool preserves the maximum level seen across all\n  weak sigs on a given key.\n- Exportable vs. local sigs are never silently promoted; --include-local\n  is required to include lsign lines in the recipe.\n- Dead primaries (revoked/expired/invalid) are skipped.\n- Keys using DSA, Elgamal, or RSA \u003c 2048 bits are skipped.\n- UIDs whose own self-binding uses a weak hash are excluded from the\n  recipe; re-signing them would be pointless since Sequoia still rejects\n  the UID at the binding level.\n- --batch-file writes a self-contained shell script with all re-sign\n  commands followed by a gpg --armor --export of only the recipe keys,\n  ready to send to the keyring maintainer.\n\nThe test suite (90 tests) covers all major code paths with synthetic\n--with-colons fixtures and a real SHA-1 signing fixture that uses\n--allow-weak-key-signatures, the flag required on modern gpg 2.4.x to\ncreate SHA-1 third-party certifications.\n\nAssisted-by: claude-sonnet-4-6\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "929a4937a1dc97e35668cc5fa1e523266080b5ca",
      "tree": "6568fc7076521082b8f083feb408887080b39a06",
      "parents": [
        "596fd4a2bda509782e95facb2338b40071583bf7"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Fri May 01 12:55:03 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Fri May 01 12:55:03 2026 -0400"
      },
      "message": "tools: improve keyring integration in list management tools\n\nRename remail-keyring-migrate.py to list-keyring-migrate.py to better\nreflect that it operates on individual list directories, not on a\nremail-wide keyring. Update all references in remail-config-migrate.py\nand Documentation/installation.rst.\n\nImprove list-sanity-check.py keyring integration in three ways:\n\n- Match the .asc file format used by list-keyring-migrate.py when\n  writing keys via --import-from-keyring: prefix each file with a\n  human-readable key listing header (matching pgpkeys.git style, with\n  compacted fingerprint lines and no trust-level brackets) followed by\n  a blank line, and use --export-options export-minimal to strip\n  third-party signatures and other non-essential packets.\n\n- When a subscriber\u0027s key is missing from .pgpkeys/ but is present in\n  the operator\u0027s local gpg keyring, annotate the [FAIL] line with a\n  hint to run with --import-from-keyring.\n\n- Add --update-from-keyring to overwrite existing .pgpkeys/\u003cFPR\u003e.asc\n  files with the current version from the local gpg keyring. Useful\n  when a subscriber renews their key, adds a subkey, or extends their\n  expiration date. Both --import-from-keyring and --update-from-keyring\n  may be combined; either writes the same pgpkeys.git-compatible format.\n\nAssisted-by: claude-sonnet-4-6\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "596fd4a2bda509782e95facb2338b40071583bf7",
      "tree": "7a18871d9e219e71bbe3f4d4471f4ffdb0d650de",
      "parents": [
        "fe84769abd867f30605077620945ce93a9a5dad1"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 18:01:50 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 18:01:50 2026 -0400"
      },
      "message": "tools: auto-import missing keys from local gpg keyring\n\nAdding a subscriber to list.yaml and forgetting to also drop their\npublic key into .pgpkeys/\u003cFPR\u003e.asc is the most common failure mode\ncaught by list-sanity-check, especially for admins who already have\nthe keyholder\u0027s public key in their personal gpg keyring (because\nthat\u0027s where they got the fingerprint from in the first place).\n\nAdd an opt-in --import-from-keyring flag that closes that gap. When\nset, before the per-subscriber check loop, the tool walks every\nOpenPGP subscriber whose fingerprint has no matching .pgpkeys/*.asc\nand runs `gpg --export --armor \u003cFPR\u003e`. If gpg has the key, the\nASCII-armored bytes are written to .pgpkeys/\u003cUPPER_FPR\u003e.asc, the\ncert gets added to the in-memory map so the per-subscriber check\nsees it, and the line is annotated `(auto-imported)` for visibility.\nA footer reminds the admin to `git add` and commit the new files.\n\nThe export is validated to actually contain the expected fingerprint\nbefore the .asc is kept -- a misconfigured keyring shouldn\u0027t be able\nto plant a cert under the wrong name. Failures (gpg missing, key\nnot in keyring, write error, mismatched fingerprint, unparsable\noutput) all degrade to \"no auto-import for that subscriber\"; the\nexisting FAIL line is then surfaced as before.\n\nThe flag is explicitly opt-in: writing to the working tree is the\none exception to the script\u0027s read-only contract. Without the flag,\nbehavior is unchanged.\n\nThree new end-to-end tests cover the happy path (import + annotate\n+ footer), the no-flag case (writes nothing), and an unknown\nfingerprint (no-op, still FAILs). All three skip cleanly on hosts\nwithout gpg.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "fe84769abd867f30605077620945ce93a9a5dad1",
      "tree": "e55c9b4ff1db90bd830fbb13e8ceef9c465550b4",
      "parents": [
        "c3dec07c187574265592ad88628cb13dcfaaf81c"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 16:57:15 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 16:57:15 2026 -0400"
      },
      "message": "tools: skip revoked/expired bindings in rebind recipe\n\nA real-world key (Halstead\u0027s, in production use) exposed a bug: it\nhas one revoked subkey with a SHA-1 binding signature plus several\nexpired subkeys, alongside the still-active subkeys. The tool was\nflagging the revoked subkey as [WEAK] and emitting a\n--quick-set-expire line for it, which is wrong on two counts:\n\n  1. gpg won\u0027t actually rebind a revoked subkey (silent no-op or\n     error depending on the path).\n  2. If it did, --quick-set-expire 0 would un-expire it as a side\n     effect -- effectively resurrecting a revoked key.\n\nCapture field 2 of the --with-colons output (the validity letter --\n\u0027r\u0027 \u003d revoked, \u0027e\u0027 \u003d expired, \u0027i\u0027 \u003d invalid per gnupg/doc/DETAILS)\nduring parsing, and skip any item flagged as such from the recipe.\nThe status table still lists them with a [SKIP] tag and the reason\nin parens, so the keyholder sees the tool noticed them and chose\nnot to act.\n\nThree new unit tests cover the parser-level filter using synthetic\ncolon output (constructing a real revoked-subkey cert via gpg is\nmuch fiddlier than the SHA-1 fixture).\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "c3dec07c187574265592ad88628cb13dcfaaf81c",
      "tree": "9db6d3d2447a310553a2db98b79e1ed92912f794",
      "parents": [
        "622fa4aaa527a2b3bd741f41feedffa74b84cd5b"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 15:52:25 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 16:40:26 2026 -0400"
      },
      "message": "tools: add rebind-modern-hash.py for SHA-1 binding sigs\n\nOpenPGP keys generated in the pre-2018 GnuPG era typically bind their\nUIDs and encryption subkeys with SHA-1 self-signatures. Modern\nSequoia-based tools (kremail, sq) reject those bindings under\nStandardPolicy, so list-sanity-check.py reports such addresses as\nhaving no encryption-capable subkey and the keyholder has no obvious\nnext step.\n\nThis new tool reads a public key (--fpr \u003cFPR\u003e, a path, or stdin),\nflags every weakly-bound UID and subkey, and prints a copy-paste gpg\ncommand sequence that re-signs each binding with SHA-512:\n\n  - UIDs:    gpg --cert-digest-algo SHA512 --quick-set-primary-uid\n             \u003cfpr\u003e \u0027\u003cuserid string\u003e\u0027  (one line per weak UID;\n             the LAST line touched ends up flagged primary, so the\n             recipe carries a note to reorder if needed)\n  - Subkeys: gpg --cert-digest-algo SHA512 --quick-set-expire \u003cfpr\u003e\n             \u003cexisting-expiration-or-0\u003e \u003csubkey-fpr\u003e  (preserves\n             the original expiration -- \u00270\u0027 only when the binding\n             had no expiration to begin with)\n\nBoth gpg --quick-* commands are non-interactive, so the recipe is\nclean copy-paste with no /dev/tty ceremony. UID label strings are\npassed through shlex.quote so addresses with shell metachars don\u0027t\nbreak. After running the recipe the keyholder re-runs with --verify\nto confirm everything reads [ OK ], then exports the rebound key\nfor the list admin.\n\nImage / user-attribute (photo ID) packets have no userid string and\ncan\u0027t be addressed by --quick-set-primary-uid; for the (rare) case\nof a weakly-bound photo ID the recipe falls back to a brief\n--edit-key sketch.\n\nImplementation uses only stdlib + a gpg(1) subprocess (no pysequoia\ndependency) -- keyholders running this already have gpg installed,\nand parsing `gpg --with-colons --list-sigs` reaches the same verdict\nas walking the cert with a Python OpenPGP library. File and stdin\ninputs are imported into a throwaway 0o700 GNUPGHOME so the user\u0027s\nreal keyring is never touched; --fpr inspects the real keyring\nread-only.\n\nNo keyserver steps in the recipe -- modern keyserver infra is mostly\nunusable, so the tool tells the keyholder to send the rebound .asc\nto the list admin directly.\n\nTests cover argument handling, the clean-cert path (pysequoia-\ngenerated cert, dev-only test dep), and SHA-1 detection via a real\ngpg-bootstrapped legacy cert (skipped if gpg is missing).\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "622fa4aaa527a2b3bd741f41feedffa74b84cd5b",
      "tree": "e911278ef58e135d115c39c1874b7a193bcb5ee2",
      "parents": [
        "726bb09906f97020cf9a5a074ceeab4cd919a79d"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 15:07:06 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 15:07:06 2026 -0400"
      },
      "message": "tools: drop kremail dependency from list-sanity-check.py\n\nThe original list-sanity-check.py imported from kremail.config /\nkremail.openpgp / kremail.smime, which is fine on the host but\nforces every list admin to install the whole kremail stack on\ntheir workstation just to dry-run a list.yaml change. Pysequoia\nalready pulls in the heavy crypto bits an admin needs, and PyYAML\nis shipped with most distros, so the kremail dependency is the only\none that matters and it\u0027s removable.\n\nReimplement the checks inline using pysequoia plus PyYAML, with\ncryptography lazily imported only when at least one subscriber is\nconfigured for S/MIME. Behaviour is preserved end-to-end: the same\nstatus tags, the same per-subscriber error messages, the same exit\ncodes. The 20 existing regression tests pass unchanged.\n\nKey changes:\n- Replace kremail.config schema validation with an inline\n  Subscriber class that checks the same fields (RE_EMAIL regex\n  copied verbatim, \"Invalid email address\" / \"Missing config entry\"\n  / \"use_smime and use_transport cannot both be set\" all preserved).\n- Replace kremail.openpgp.check_key() with inline _check_openpgp()\n  that reproduces the daemon-side checks: presence of the cert\n  under the configured fingerprint, expiry, packet-level UID match\n  (with weak-binding [WARN] fallback), and an empty-payload\n  encrypt() probe to catch SHA-1-bound encryption subkeys at\n  config-check time.\n- Replace kremail.smime.check_cert() with inline _check_smime()\n  that lazy-imports cryptography. Admins running OpenPGP-only lists\n  no longer need the cryptography package installed.\n- Switch the YAML loader from ruamel.yaml to PyYAML (yaml.safe_load),\n  which is what most distros ship as python3-yaml and what admins\n  are most likely to already have.\n- Add PyYAML to the dev dependency-group so tests find the same\n  parser the tool uses.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "726bb09906f97020cf9a5a074ceeab4cd919a79d",
      "tree": "9655ce3321e945dfb009eff8cdea3ad7639f72a9",
      "parents": [
        "88d5aa211c7213deb676c52768b9f1ea3538eaeb"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:45:48 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:45:48 2026 -0400"
      },
      "message": "tools: add list-sanity-check.py for workstation list validation\n\nList admins normally pre-flight a list.yaml change in their git\ncheckout, but until now the only way to validate it was to push and\nlet the daemon\u0027s kremail-chkconfig run on the host. That trips on\ntrivial mistakes -- pasted-wrong fingerprint, expired subscriber\nkey, broken encryption subkey, missing S/MIME cert -- one round-trip\nlater than necessary, and only the kremail operator (not the list\nadmin) sees the failure.\n\nAdd a single-file workstation tool that reads list.yaml + .pgpkeys/\n+ .certs/ from a list directory and reports a per-subscriber pass/\nfail/skip line plus a summary. The actual checks are delegated to\nthe kremail library so the tool stays in lockstep with daemon\nbehaviour automatically -- including the recent SHA-1 weak-binding\nUID fallback and the encryption-subkey policy probe.\n\nVerifies, per subscriber:\n  - list.yaml schema (address, name, conflicting transport options)\n  - OpenPGP: matching cert in .pgpkeys/, address on the cert (with\n    weak-binding fallback that surfaces a [WARN] hint), expiry,\n    encryption-capable subkey with a policy-valid binding\n  - S/MIME: matching .certs/\u003caddr\u003e.crt, address matches, validity\n    window\n  - Disabled and use_transport accounts are reported as [SKIP]\n    without a key check\n\nOutput uses six-character status tags ([ OK ] / [WARN] / [FAIL] /\n[SKIP]) chosen to align cleanly without padding logic. --quiet\nsuppresses OK and SKIP lines so a CI run only prints actionable\noutput. Exits 0 when nothing failed, 1 on any failure (including\nearly errors like a missing list.yaml or a schema-invalid file).\n\nKey changes:\n- Add tools/list-sanity-check.py.\n- Add tests/test_list_sanity_check.py with 20 cases covering\n  list.yaml schema validation, OpenPGP and S/MIME pass/fail paths,\n  skipped subscribers, --quiet behaviour, and summary-counter\n  correctness. Tests load the tool via importlib (matching the\n  existing test_config_migrate pattern) and call main() with\n  patched argv for fast in-process execution.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "88d5aa211c7213deb676c52768b9f1ea3538eaeb",
      "tree": "d71ff2878969cb7e6c07701d4e51a01d8fc24cd1",
      "parents": [
        "a38406138863a6fb5fccd20f7784fa7bda83dc30"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:32:57 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:32:57 2026 -0400"
      },
      "message": "openpgp: probe encryption-subkey policy at config-check time\n\npysequoia.encrypt() applies StandardPolicy when picking which subkey\nto encrypt to. A subscriber cert whose only encryption-capable subkey\nhas a weak binding signature (typically SHA-1, common on pre-2018\nGnuPG keys whose subkeys have never been rebound) silently fails that\npolicy, and encrypt() raises \"No suitable encryption subkey\" at\ndelivery time. That failure surfaces inside the per-recipient loop in\nmaillist, far from any operator-visible context, and causes the mail\nto be frozen with no actionable hint about which recipient is at\nfault or why.\n\nUnlike the UID case, there is no clean fallback here -- pysequoia\napplies StandardPolicy internally and the high-level encrypt() API\ndoesn\u0027t accept a policy override, so the cert really cannot be used\nwithout rebinding. The best we can do is detect the condition early.\n\nAdd a tiny empty-payload encrypt() probe to _check_key_encryption()\nand call it from both _check_key (the list\u0027s own cert path through\n__init__) and check_key (the per-subscriber path called from\nkremail-chkconfig). On RuntimeError matching the \"No suitable\nencryption subkey\" message, raise KremailOpenPGPKeyException with a\nclear pointer at the keyholder; other RuntimeErrors propagate\nuntouched so unrelated failures aren\u0027t mis-categorised.\n\nKey changes:\n- Add _check_key_encryption() helper using a b\"\" empty-payload probe.\n- Wire into _check_key (private\u003dFalse path only, so the probe runs\n  exactly once per cert) and check_key.\n- Add a _strip_subkeys() test helper that reconstructs a cert with\n  no subkey packets, producing the same \"No suitable encryption\n  subkey\" RuntimeError for testing.\n- Add five regression tests: healthy cert passes, broken cert\n  raises with actionable message including fingerprint and address,\n  per-subscriber check_key wires through, list-cert __init__ path\n  catches the issue at startup, unrelated RuntimeError propagates.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "a38406138863a6fb5fccd20f7784fa7bda83dc30",
      "tree": "26fff982be5372b7d5ab2b797e8d861eacdbdd12",
      "parents": [
        "92097c6f6905af6977b1545eada433fad504e850"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:25:01 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:25:01 2026 -0400"
      },
      "message": "openpgp: accept addresses bound to UIDs with weak self-signatures\n\npysequoia applies its StandardPolicy when exposing cert.user_ids, and\nthat policy rejects SHA-1 self-signatures. Many subscriber keys still\nin active use were created in the GnuPG era when SHA-1 was the default\nself-sig hash (typical for keys generated 2011-2014), so legitimate\nUIDs disappear from the high-level API even though the UID packets are\nright there in the cert. Operators were seeing kremail-chkconfig fail\nwith \"address not in OpenPGP key\" for addresses gpg(1) clearly listed,\nand there was no way to tell from kremail\u0027s output that the UIDs were\nbeing filtered rather than missing.\n\nThe address-matching check in _check_key_addr is a configuration sanity\nguard: it ensures the operator-declared subscriber address actually\nappears somewhere on the cert they pinned by fingerprint. The trust\nanchor for that cert is the configured fingerprint, not the binding\nsignature on any individual UID, so insisting on a policy-valid binding\njust to spell-check the address is overkill.\n\nFall back to a packet-level scan via PacketPile.from_bytes() when the\npolicy-validated UID list doesn\u0027t contain the address. If the address\nmatches one of those weakly-bound UIDs, accept it but print a one-line\nhint asking the keyholder to rebind their UIDs with a modern hash. If\nno UID matches at all, surface the weakly-bound UIDs in the failure\nmessage so the operator can see what the cert actually carries.\n\nKey changes:\n- Add _all_user_ids() helper using pysequoia.packet.PacketPile to\n  enumerate every UID packet, bypassing StandardPolicy filtering.\n- Refactor _check_key_addr to extract address matching into a nested\n  helper and consult the packet-level list as a fallback.\n- Include weakly-bound UIDs in the failure message when no match is\n  found, so operators can see what\u0027s on the cert.\n- Add regression tests covering the weak-binding fallback path with\n  a test wrapper that simulates StandardPolicy filtering.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "92097c6f6905af6977b1545eada433fad504e850",
      "tree": "c8b54305e72d5df1ff439240e0ba852043b06c33",
      "parents": [
        "4fc7090174ac61c1e72f83eefda06cb20515bc54"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:14:30 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 14:14:30 2026 -0400"
      },
      "message": "kremaild: warn when no enabled list claims an incoming mail\n\nprocess_msg returned 1 silently whenever none of the configured lists\nmatched the incoming recipient, with no entry in the warning log. In\npipe mode that surfaces to the operator as a bare exit-1 from\nkremail-pipe and no other output, even with -v: the only log lines come\nfrom cmd_pipe.py\u0027s \"Started\" / \"Stopped\" wrappers, and process_msg\nitself emits nothing on the unclaimed path.\n\nThe most common cause is a domain mismatch between the SMTP envelope\n(what postfix accepts via mydestination) and the To: header (what\nkremail\u0027s listaddrs has registered for the list). Both are valid in\nproduction -- upstream MX/transport rewriting changes envelope-to but\nleaves the header alone -- but a misconfigured deployment that has\npostfix accepting one domain and listaccount declaring another will\nquietly drop every message into the frozen maildir with no breadcrumb.\n\nTrack whether any enabled list claimed the message via get_destination\nand, if not, log_warn the recipient list before returning 1. Exception\npaths and the \"claimed but processing failed\" path are unchanged --\nlog_exception covers the former and exit code 2 distinguishes the\nlatter, so neither needs an extra warning.\n\nTests cover the unclaimed path (warning fires), the disabled-list case\n(disabled lists are skipped, warning still fires), the success path\n(no warning), the claimed-but-failed path (no spurious unclaimed\nwarning, exit 2), and the claimed-but-raised path (exception is\nlogged, no spurious unclaimed warning).\n\nVerification:\n- pytest tests/test_kremaild.py: 11 passed\n- pytest: 168 passed, 12 skipped (full suite, +5 from this commit)\n- ruff check + ruff format --check on the two modified files: clean\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "4fc7090174ac61c1e72f83eefda06cb20515bc54",
      "tree": "12cf3fe4e30f9b7e1d64fbbb281ae6cddfe15416",
      "parents": [
        "8eb9a77372380a8d62a4ae1830b7b4ccba95c213"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 13:54:20 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 30 13:54:20 2026 -0400"
      },
      "message": "openpgp: prefer secret-bearing certs in _load_certs_from_dir\n\nA single .asc file under .pgpkeys/ may contain more than one armored\nblock. Operators sometimes concatenate the transferable secret key with\nthe matching public key in the same file -- in either order -- and\nexpect kremail to load the secret-bearing copy. Cert.split_file does\nyield both blocks as separate Cert objects sharing the same\nfingerprint, but the previous loader unconditionally assigned each one\nto certs[cert.fingerprint], so the last block in the file won. A\nprivate-then-public ordering therefore left certs[fpr] pointing at the\npublic-only copy and the list silently failed at runtime with\n\"No private key found\", because openpgp_crypt._check_key looks at\ncert.has_secret_keys on the loaded entry.\n\nMake the loader prefer the secret-bearing copy when more than one Cert\nshares a fingerprint, regardless of the order they appear in the file.\nThe pub-then-priv ordering happened to work before by luck of last-wins\nand continues to work; the priv-then-pub ordering now also works.\n\nTests gain two regression cases under TestLoadCerts that write a\nconcatenated \u003csecrets\u003e\u003cpublic\u003e and \u003cpublic\u003e\u003csecrets\u003e .asc respectively\nand assert that the cert returned from _load_certs_from_dir has\nhas_secret_keys\u003dTrue. The first case fails on master without the\nloader change; the second passed before but is kept as a guard against\nfuture regressions.\n\nVerification:\n- pytest tests/test_openpgp.py: 27 passed\n- pytest: 163 passed, 12 skipped (full suite)\n- ruff check + ruff format --check on the two modified files: clean\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "8eb9a77372380a8d62a4ae1830b7b4ccba95c213",
      "tree": "3e141a94b9a4fac8566dbb2fa41e5544d97d454c",
      "parents": [
        "7043534c94758e9682e0525d6ec90a58907711c0"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Wed Apr 29 16:37:36 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Wed Apr 29 16:37:36 2026 -0400"
      },
      "message": "When generating .asc keys, give a nice header\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "7043534c94758e9682e0525d6ec90a58907711c0",
      "tree": "37e10692b55de220a3b116f8ef8ee7f7e41c25c8",
      "parents": [
        "c61cb22cb82eb8416747ce2a318c84ebacd90a0e"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Apr 28 14:57:45 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Apr 28 14:57:45 2026 -0400"
      },
      "message": "doc: name base .pgpkeys files by list address\n\nThe OpenPGP and S/MIME loaders index certificates by the cert/account\nfingerprint and address respectively, never by filename, so the previous\n``\u003cbase\u003e/.pgpkeys/\u003cFPR\u003e.asc`` convention was just one of several that\nwould have worked. Switch the documented convention to\n``\u003cbase\u003e/.pgpkeys/\u003clist-addr\u003e.asc`` so the base ``.pgpkeys/`` and\n``.certs/`` directories are symmetric: each holds exactly one\nsecret-bearing file per list, named the same way (by list address) in\nboth places.\n\nThe per-list public ``.pgpkeys/`` (under ``lists/\u003cname\u003e/``) keeps the\n``\u003cFPR\u003e.asc`` convention -- it can hold many subscriber keys, and\nfingerprint naming is the natural choice when a directory is keyed by\n\"one key per fingerprint\" rather than \"one key per list\".\n\nUpdates Documentation/configuration.rst, the man5 page, the\ninstallation note, and the post-run hint in\ntools/remail-keyring-migrate.py (and fixes a stale comment in that\ntool which contradicted its own NOTE by suggesting the secret key be\nappended to the per-list ``.pgpkeys/\u003cFPR\u003e.asc`` rather than the base\nfile).\n\nNo code changes -- the loader doesn\u0027t care about filenames, so this is\ndocumentation only.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "c61cb22cb82eb8416747ce2a318c84ebacd90a0e",
      "tree": "d837fd06df4620be87f0c4489763737f76e7e5ca",
      "parents": [
        "1212fd09ae24b78c24642995136bc41f5d71cffe"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Apr 28 11:41:37 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Apr 28 11:41:37 2026 -0400"
      },
      "message": "openpgp: load list secret keys from a base-level .pgpkeys/\n\nThe per-list .pgpkeys/ directory under lists/\u003cname\u003e/ is normally a\ngit checkout shared with the list admin team (subscriber pubkeys\nland there). Storing the list\u0027s own secret-bearing .asc in the\nsame tree means the secret packets travel through whatever\ninfrastructure mirrors that git repo for admin collaboration --\nwhich is exactly the wrong place for them.\n\nS/MIME already keeps the analogous split: smime_config sets\nglobal_certs \u003d \".certs\" (private list keys at the base config\ndir, top-level) and list_certs \u003d lists/\u003cname\u003e/.certs (public\nsubscriber certs in the per-list git checkout). OpenPGP had only\nthe per-list dir.\n\nMirror the S/MIME shape on the OpenPGP side. Add private_keydir \u003d\n\".pgpkeys\" to openpgp_config -- a relative path that kremail-pipe\nand kremail-daemon both resolve under the base config dir via\ntheir startup os.chdir into the directory containing kremail.yaml\n(see cmd_pipe.py, cmd_daemon.py, cmd_chkcfg.py). openpgp_crypt\nloads keydir first and then dict.update()s private_keydir on top,\nso a secret-bearing .asc at \u003cbase\u003e/.pgpkeys/\u003cFPR\u003e.asc overrides\nthe public-only copy of the same fingerprint that may also live\nin the per-list dir. This keeps backward compatibility with\nexisting deployments where the secret key is still in the per-list\n.pgpkeys/: keydir is loaded first and the existing entry is\nretained when private_keydir is empty.\n\nThe directory tree in Documentation/man5/kremail.config.rst\nalready showed this layout (a top-level .pgpkeys with private\nlist keys, a per-list .pgpkeys with subscriber pubkeys); it was\nthe OpenPGP loader that didn\u0027t match the documented intent.\nThe man page\u0027s prose gets a small rationale note, and\nDocumentation/configuration.rst\u0027s \"Key storage format\" section\nis rewritten to describe the split and the file format expected\nin each location (an ASCII-armored transferable secret key\nunder \u003cbase\u003e/.pgpkeys/, public-only .asc files under the per-list\ndir).\n\nUpdate tools/remail-keyring-migrate.py\u0027s post-run hint and\nDocumentation/installation.rst\u0027s \"Migrating from remail\" note to\ndirect operators at \u003cbase\u003e/.pgpkeys/ for the secret half rather\nthan the per-list .pgpkeys/. The migrate tool itself only ever\nexported public keys, so its on-disk behaviour is unchanged --\njust the printed instructions move.\n\nTests gain a new TestLoadCerts.test_openpgp_crypt_loads_private_keydir\nthat writes one cert to keydir and a different cert to\nprivate_keydir, builds an openpgp_crypt with checkkey\u003dFalse, and\nasserts both fingerprints land in self.certs. The two\nFakeOpenPGPConfig stubs (one in test_openpgp.py, one in\ntest_maillist.py) gain a private_keydir attribute that points at\na non-existent path so _load_certs_from_dir is a no-op for the\nexisting tests, which keeps the override semantic untested in\nisolation but correctly covered as a documented dict.update\ncontract.\n\nVerification:\n- ./ci.sh: clean (ruff format + ruff check + ty + mypy + pyright +\n  pytest 161 passed, 12 skipped) on Python 3.14.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "1212fd09ae24b78c24642995136bc41f5d71cffe",
      "tree": "f8a7eb9e86ee200bf440ed7e4ae90559bd71d6de",
      "parents": [
        "7e6b9dfc9db7deb604a4a43481a815d7e028fbaf"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 16:58:30 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 16:58:30 2026 +0000"
      },
      "message": "config: normalise OpenPGP fingerprint case at account_config load\n\npysequoia\u0027s Cert.fingerprint is lowercase hex, so\n_load_certs_from_dir in openpgp.py keys its certs dict by lowercase\nfingerprint. Configs, however, conventionally write fingerprints in\nuppercase to match gpg --with-colons and keyserver output. The\nresult is that certs.get(account.fingerprint) -- called from\n_check_key, check_key, _do_decrypt, _get_signer, and encrypt --\nsilently misses every subscriber whose YAML fingerprint contains\nhex letters above 9, raising \"No public key found\" before a single\nlist message can be encrypted.\n\nThe existing tests in test_openpgp.py never caught this because\nthey construct accounts with cert.fingerprint passed straight\nthrough, so the case on both sides happens to match.\n\nLowercase the fingerprint in account_config.__init__ right after\nset_defaults runs. pysequoia is the source of truth for the\ncanonical case here -- its output flows into the certs dict\nunchanged, and every internal comparison goes through that dict --\nso normalising on the config side is the minimal touch. The cast\nvia str() is needed because ruamel.yaml parses an all-digit\nfingerprint (e.g. a placeholder of 40 digits in a test fixture)\nas a ScalarInt rather than a string; the cast keeps the\nnormalisation robust against unquoted YAML scalars.\n\nAdd a TestFingerprintCase class to test_openpgp.py covering:\n- uppercase YAML fingerprint -\u003e stored lowercase, equal to\n  pysequoia\u0027s cert.fingerprint\n- mixed-case YAML fingerprint -\u003e stored fully lowercase\n- absent fingerprint (use_transport account) -\u003e stays None\n- end-to-end integration: an account_config built from an uppercase\n  YAML fingerprint resolves to the matching lowercase-keyed cert\n  in the dict that openpgp_crypt._check_key actually queries.\n\nThe integration test is the regression guard. It builds the\naccount through account_config (not the FakeAccount used elsewhere\nin test_openpgp.py), so the case-normalisation step has to run for\nthe lookup to succeed.\n\nVerification:\n- ./ci.sh: clean (ruff format + ruff check + ty + mypy + pyright +\n  pytest 172 passed) on Python 3.14.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "7e6b9dfc9db7deb604a4a43481a815d7e028fbaf",
      "tree": "c6ad9f407e1f1e247fb2940a1977d2241cab3bc3",
      "parents": [
        "cb63854bd798ff01753faabe20092169309bb398"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 16:53:26 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 16:53:26 2026 +0000"
      },
      "message": "tools: add remail-keyring-migrate for legacy GnuPG keyring conversion\n\nremail stored per-list public keys in a GnuPG keyring at\n.gnupg/pubring.kbx; kremail\u0027s pysequoia loader reads individual\nASCII-armored .asc files from .pgpkeys/, keyed by fingerprint\n(see _load_certs_from_dir in src/kremail/openpgp.py). The existing\nremail-config-migrate.py only rewrites YAML and prints a shell\nrecipe for the keyring conversion, leaving operators to script the\ngpg invocations themselves.\n\nAdd tools/remail-keyring-migrate.py to automate the keyring side.\nGiven a list directory containing list.yaml and .gnupg/, it shells\nout to gpg --homedir\u003d.gnupg to enumerate primary-key fingerprints,\nexports each one with export-minimal --armor into\n.pgpkeys/\u003cFPR\u003e.asc, and finally renames .gnupg to .legacy-gnupg so\nthe daemon cannot accidentally fall back to the old keyring on a\nlater run. Pre-flight refuses to clobber an existing\n.legacy-gnupg or a non-empty .pgpkeys.\n\nThe tool cross-checks fingerprints against list.yaml subscribers\nand prints both directions of mismatch: subscribers whose\nfingerprint is absent from the keyring (logged as a WARNING), and\nkeyring entries not referenced by any subscriber (logged as a Note,\nsince these are typically the list\u0027s own keypair or stale\nhistorical material rather than errors). Subscriber records that\nshare a fingerprint -- legitimate when one person subscribes from\nmultiple addresses -- are collapsed into a single .asc and reported\ntogether on the export line.\n\nOnly public keys are exported. If a list directory was the source\nof truth for the list\u0027s own secret key, the tool prints the exact\ngpg invocation needed to append the secret half to the matching\n.pgpkeys/\u003cFPR\u003e.asc, since pysequoia reads both halves from one\nfile.\n\nUpdate Documentation/installation.rst to point operators at the\nnew tool, and replace the shell recipe in\nremail-config-migrate.py\u0027s print_key_migration_hint with a one-line\nloop that drives remail-keyring-migrate over each migrated list\ndirectory.\n\nVerification:\n- ./ci.sh: clean (ruff format + ruff check + ty + mypy + pyright +\n  pytest 168 passed) on Python 3.14.\n- end-to-end run against a copy of the production\n  confounding-cockroach list directory: 41 public keys exported,\n  pysequoia loads all 41 cleanly via _load_certs_from_dir, .gnupg\n  renamed to .legacy-gnupg, no warnings.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "cb63854bd798ff01753faabe20092169309bb398",
      "tree": "9066722a1b5cfc008d8ff8148a62a752a71fb760",
      "parents": [
        "9b9ab2a2477378e8bdc3b4cf9f3484aa59eeeed4"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:52:03 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:52:03 2026 +0000"
      },
      "message": "kremaild: replace pyinotify with inotify_simple\n\npyinotify is unmaintained (last release 2015) and imports stdlib\nasyncore, which was removed in Python 3.12. As a result, the daemon\nentry point was capped at Python 3.11 even though pipe mode and the\ntest suite worked fine on 3.12+.\n\nSwitch to inotify_simple, a small maintained wrapper around the\ninotify_init1/add_watch/read syscalls. The library does not ship\ntype annotations or a py.typed marker, so add it to the existing\nmypy ignore_missing_imports override alongside flufl, ruamel, and\npysequoia.\n\nDrop the pyinotify EventHandler subclass and the AsyncNotifier\ndance. The new pattern is a simple polling loop: install_inotifier\nopens an INotify and watches maildir/new for IN_CREATE | IN_MOVED_TO,\nprocess_events does a bounded read(timeout\u003d1000) and queues each\nevent\u0027s pathname, stop_inotifier closes the fd. The 1 second timeout\nbounds the latency at which the run loop notices should_stop /\nshould_reload flag changes.\n\nSignal handlers no longer call stop_inotifier(). They only set the\nrelevant flag; the bounded read timeout makes the explicit wakeup\nunnecessary and avoids racing with a syscall in flight. The\nAttributeError \"inotifier died\" hack falls out because the new code\nhas deterministic close semantics (close the fd, the next read\neither short-circuits via the None guard or returns OSError which\nthe run loop\u0027s existing exception handler catches).\n\nAdd tests/test_kremaild.py covering the install/stop contract and\nthe event-queueing path for both IN_CREATE and IN_MOVED_TO. The\ntest fixture neutralises siginstall via monkeypatch so the kremaild\nconstructor doesn\u0027t claim pytest\u0027s signal handlers, and exercises\nthe real inotify_simple library against a real on-disk maildir\nunder tmp_path. MOVED_TO is the more important watch flag for\nproduction use because most MTAs deliver to maildir using the\nwrite-tmp-then-rename-into-new atomicity pattern.\n\nUpdate ci-matrix.sh to drop the \"needs Python \u003c3.12\" header note,\nsince the daemon path now imports cleanly across all supported\ninterpreters. installation.rst likewise sheds the pyinotify mention\nin the dependency listing.\n\nVerification:\n- ./ci.sh: clean (ruff format + ruff check + ty + mypy + pyright +\n  pytest 168 passed) on Python 3.14.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "9b9ab2a2477378e8bdc3b4cf9f3484aa59eeeed4",
      "tree": "51d53077044b51c64ad0da0b5e7d692298c9fc3c",
      "parents": [
        "8527e7b06e88d8f270dbcad6ea7a2e71aeb3d485"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:12:51 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:12:51 2026 +0000"
      },
      "message": "Documentation: promote pipe-mode delivery to a peer of the daemon\n\nThe documentation framed kremail as a maildir-watching daemon and\nmentioned pipe mode only as an alternative, but pipe mode is a\nfully supported delivery path: kremail-pipe is shipped as a\nconsole script, reads an RFC 5322 message from stdin, dispatches\nby recipient via the listaccount entries, and signals success or\nfailure via documented exit codes. Operators on hosts where the\nlocal MTA receives list mail directly can integrate kremail\nwithout running a daemon at all.\n\nRestructure installation.rst so daemon mode and pipe mode sit at\nthe same nesting level. Update introduction.rst so it no longer\ndescribes maildir-and-daemon as the only data path.\n\nKey changes:\n- installation.rst: add a \"Delivery modes\" overview section that\n  introduces the two-mode choice and the trade-offs.\n- installation.rst: add a \"Daemon-mode delivery\" section parallel\n  to the existing pipe-mode section, documenting the daemon\u0027s\n  contract (long-running, watches maildir via inotify, SIGHUP to\n  reload), the mail-retrieval setup, and the kremail.service\n  systemd unit shipped at the source tree root.\n- installation.rst: drop the \"By default the daemon is disabled...\"\n  sentence from the General section; that framing implied daemon \u003d\n  default mode.\n- installation.rst: pipe-mode subsections cover MTA configuration\n  (postfix mailbox_command + /etc/aliases pattern), a worked\n  procmail example, and a venv-activating wrapper script.\n- introduction.rst: rewrite \"What it does\" and \"How it works\" so\n  both delivery modes are mentioned, with a pointer to\n  installation.rst for the trade-offs.\n\nNo source code changes; all referenced behaviour was already\nimplemented in cmd_pipe.py and kremaild.py.\n\nVerification:\n- sphinx-build -W -n -b html Documentation Documentation/_build/html:\n  build succeeded, 0 warnings.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "8527e7b06e88d8f270dbcad6ea7a2e71aeb3d485",
      "tree": "18d9cc3fb067875f028004cc0957609d704e3afa",
      "parents": [
        "355ab6d7fbf602b1bf6d1012192464bc73f15696"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:12:19 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Apr 27 15:12:19 2026 +0000"
      },
      "message": "Documentation: extend Sphinx title underlines to match titles\n\nSeven top-level RST titles across the documentation had underline\nrows one character shorter than the title text, which Sphinx\nflagged as \"Title underline too short\" warnings. Extend each\nunderline so the docs build cleanly under sphinx-build -W -n.\n\nPure typographical change; no content modified.\n\nFiles affected:\n- configuration.rst\n- index.rst\n- license-rules.rst\n- man1/kremail-chkcfg.rst\n- man1/kremail-daemon.rst\n- man5/kremail.config.rst\n- operation.rst\n\nVerification:\n- sphinx-build -W -n -b html Documentation Documentation/_build/html:\n  build succeeded, 0 warnings.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "355ab6d7fbf602b1bf6d1012192464bc73f15696",
      "tree": "4f7b07103371b45c3b685f6d1ea20c285c31d8d0",
      "parents": [
        "58f401eda5f174168edeed561b73189c4052a1b8"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 16:17:42 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 16:17:42 2026 +0000"
      },
      "message": "Fix latent bugs surfaced by linter audit; tighten ruff ignores\n\nAudit the ignores in pyproject.toml and classify each as legit or a\ncop-out. Execute the quick wins. The audit also turned up another\nlatent bug of the same shape as the recently-fixed toadmins typo:\nkremaild.py wrote self.log_warn(txt) instead of self.logger.log_warn\nin the list-subscribers reconfiguration error path. The method\ndoesn\u0027t exist on kremaild, so any KremailConfigException raised\nduring ml.config_subscribers() would turn into an AttributeError\nbefore the admin ever saw the original warning. The bug was hidden\nbehind the mypy attr-defined suppression on kremail.kremaild and\nwas only visible once pyright/ty landed.\n\nRework the nine SIM115 \"open without context manager\" sites. Eight\nwere trivial conversions to `with open(...) as f` blocks in\nkremaild, maillist, and tracking. The ninth is the daemon lock\nfile, whose fd must outlive the scope of run(); flagged with a\n# noqa: SIM115 and a comment explaining the deliberate choice.\n\nTighten the type annotations where the audit showed we were hiding\nreal typing issues behind blanket suppressions. In maillist\narchive_mail(), annotate `mbox: mailbox.Maildir | mailbox.mbox` so\nmypy can narrow the two branches; drop the blanket \"assignment\"\ndisable from the kremail.maillist mypy override.\n\nRename three `l` loop variables to `lcfg`. Drop the E402 / S108 /\nSIM115 / E741 entries from the ruff ignore list (E402 and S108 had\nzero current findings; SIM115 and E741 are now clean).\n\nWhat we deliberately kept ignored (see audit notes):\n- UP031 (131 percent-format sites): aesthetic, no safety impact\n- S110 / S112 (silent try-except-pass/continue): each needs a\n  case-by-case judgment call, deferred\n- SIM102/103/108/110: pure readability preferences\n- S324: sha1 used for MIME boundaries, not crypto\n- mypy attr-defined and pyright/ty equivalents for kremail.config\n  and kremail.kremaild: hide the set_defaults dynamic-attribute\n  pattern, which needs a dataclass refactor to remove cleanly\n\nKey changes:\n- kremaild.py: fix self.log_warn -\u003e self.logger.log_warn typo\n- kremaild.py, maillist.py, tracking.py: 8 sites rewritten to use\n  `with open(...)` blocks; 1 site (daemon lock file) gets a noqa\n  with explanation\n- maillist.py: annotate mbox with `Maildir | mbox` union\n- config.py, kremaild.py: rename loop variable `l` to `lcfg` in 3\n  spots\n- pyproject.toml: drop E402, S108, SIM115, E741 from ruff ignore\n  list; drop \"assignment\" from kremail.maillist mypy disable list;\n  tighten comments on the remaining UP031/S110/S112 entries to\n  explain why they\u0027re deferred\n\nVerification:\n- ./ci.sh: clean (ruff format + ruff check + ty + mypy + pyright +\n  pytest 150 passed, 12 skipped)\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "58f401eda5f174168edeed561b73189c4052a1b8",
      "tree": "cba81ee42a279cec41dfe5eb5c2895795c72f94b",
      "parents": [
        "5fba3eb2861be78f2cb8bb65134aaa7cdd7fc31c"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 16:09:55 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 16:09:55 2026 +0000"
      },
      "message": "Add ci.sh / ci-matrix.sh and a wider set of linters\n\nMirror the CI layout from git.kernel.org/pub/scm/utils/b4/b4.git so\nkremail gets the same quality checks and multi-Python coverage.\nci.sh runs the full local CI check (format, lint, three type\ncheckers, tests); ci-matrix.sh walks every interpreter in the\nsupported range and runs an import smoke + pytest against each.\nThe matrix script exists separately because materialising a venv\nper Python version is slow; it\u0027s intended for pre-release runs\nand after changes that can depend on version-specific runtime\nbehaviour.\n\nRestructure the dev tooling: replace [project.optional-dependencies]\nwith a [dependency-groups] dev group (PEP 735), which is uv-native\nand cleanly separates dev tools from runtime dependencies. Add\npyright and ty alongside the existing ruff and mypy.\n\npyright and ty are stricter about flow-sensitive analysis than mypy\nand surfaced a real bug that had been hiding for a while: maillist.py\nassigned `dest.toadmins \u003d True` (plural) in three places, but the\ndestination class only defines `toadmin` (singular) at construction\ntime. The bad assignment silently created a *new* attribute on the\ninstance, so archive_mail() -- which reads the correct `toadmin` at\nL315 -- was getting stale construction-time data. Moderated and\nbounced mail was being archived as if it had gone to subscribers\nrather than admins. Six tests had the same typo and passed by\naccident. Fixed the production path plus all test assertions.\n\nThe type checkers also found a set of latent issues in code paths\nthat mypy tolerates via attr-defined/union-attr suppressions\n(dynamic attribute setting via set_defaults, optional self.openpgp/\nself.smime that is None before the list is enabled, pyinotify and\ncryptography PKCS7 APIs without proper type stubs). Rather than\nrewrite those patterns, mirror the mypy relaxations in the pyright\nand ty config so all three checkers have a consistent view of what\ncounts as a tolerated dynamic pattern.\n\nRun `ruff format` once across the tree to normalize formatting; that\nis why the diff is large despite few semantic changes.\n\nKey changes:\n- Add ci.sh: uv sync --all-extras --all-groups, ruff format --check,\n  ruff check, ty check, mypy ., pyright, pytest --durations\u003d20\n- Add ci-matrix.sh: iterate PYTHONS (default 3.10..3.14), per-version\n  venv via UV_PROJECT_ENVIRONMENT, import smoke + pytest per version,\n  collect failures rather than bail early\n- pyproject.toml: replace [project.optional-dependencies] with\n  [dependency-groups] dev including mypy, pyright, pytest, ruff, ty\n- pyproject.toml: add [tool.pyright] and [tool.ty.src]/[tool.ty.rules]\n  sections with relaxations matching the existing [tool.mypy]\n  overrides; exclude Documentation/ from mypy (Sphinx conf.py)\n- maillist.py: fix dest.toadmin typo (was dest.toadmins in three\n  places); archive routing was silently incorrect for moderated,\n  bounced and autoreply paths\n- tests/test_maillist.py: fix the same dest.toadmin typo in six\n  assertions\n- test_live_data.py: remove dead `wrong_key` computation\n- test_maillist.py: drop stub `or True` guard and replace with a\n  comment; drop unused `list_acc` local\n- test_config_migrate.py: assert spec/loader are not None for the\n  dynamic-import helper so type checkers can follow\n- .gitignore: add .venv-*/ for the per-version matrix venvs\n- Documentation/contribute.rst: use `uv sync --all-groups` and\n  mention ./ci.sh as the canonical way to run the full check\n- Apply `ruff format` across src/, tests/, tools/; formatter\n  normalises string quoting and line wrapping, no semantic change\n\nVerification:\n- ./ci.sh: clean end-to-end (ruff format + ruff check + ty + mypy +\n  pyright + pytest 150 passed, 12 skipped)\n- PYTHONS\u003d\"3.10 3.11 3.14\" ./ci-matrix.sh: all three pass\n\nKnown: kremail-daemon itself still needs Python \u003c3.12 because\npyinotify imports the removed stdlib asyncore. The test suite does\nnot exercise the daemon loop, so ci-matrix.sh passes on 3.12+.\nA ci-matrix.sh header comment documents this.\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "5fba3eb2861be78f2cb8bb65134aaa7cdd7fc31c",
      "tree": "f911e634d9c99db845799ac54aef789e05188605",
      "parents": [
        "adfadbbcb4a7bf957fa50fb2fe920f8b550cf581"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:59:34 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:59:34 2026 +0000"
      },
      "message": "openpgp: clean up gnupg-era leftovers and internal method names\n\nAfter the switch to Sequoia, several references still carried the\n\"GPG\" name and one config attribute (armor) existed only as dead\nflexibility from the python-gnupg era. Replace the GnuPG-specific\nterminology with the protocol name (\"OpenPGP\") in user-facing\nstrings, error messages, and comments. The actual private key files\nthat Outlook produces (msg.gpg etc.) are still matched by those\nliteral names because we have no say over what Outlook writes.\n\nAlso take the opportunity to tighten the internal API of\nopenpgp_crypt. The class previously exposed openpgp_encrypt/\nopenpgp_decrypt_plain/openpgp_decrypt_enveloped/set_sender_info/\ndo_encrypt as public-looking helpers, plus a trivial one-line\nencrypt() wrapper that delegated to openpgp_encrypt. In a class\nalready named openpgp_crypt the \"openpgp_\" prefix on every method\nis redundant; rename the internal helpers to underscore-private\nand merge the trivial wrapper into a single public encrypt().\n\nKey changes:\n- openpgp.py: sender-info prints \"OpenPGP\" instead of \"GPG\";\n  attached public key uses .asc extension (standard ASCII-armored\n  OpenPGP) rather than .gpg (binary in most conventions)\n- openpgp.py: error messages say \"OpenPGP key\" instead of \"GPG key\";\n  parameter renamed gpgcfg -\u003e cfg; \"our keyring\" comment corrected\n  to describe the loaded-certs dict\n- openpgp.py: drop self.config.armor plumbing and hardcode\n  armor\u003dTrue in the one encrypt() call; ASCII armor is mandatory\n  for email PGP (inline and PGP/MIME both require text)\n- openpgp.py: rename internals to underscore-private\n  (_decrypt_plain, _decrypt_enveloped, _set_sender_info,\n  _do_encrypt); merge openpgp_encrypt + trivial encrypt() wrapper\n  into a single public encrypt() method\n- config.py: drop self.armor \u003d True from openpgp_config; no longer\n  referenced anywhere\n- mail.py: \"No GPG/SMIME info available\" -\u003e \"No OpenPGP/S/MIME info\n  available\"; comments mentioning \"GPG\" updated to \"OpenPGP\" or\n  \"PGP\" as appropriate; local variable gpgpl -\u003e pgppl\n- tests/test_openpgp.py: class renamed TestGpgSenderInfo -\u003e\n  TestOpenPGPSenderInfo; assertions updated for the new strings\n\nVerification:\n- pytest: 150 passed, 12 skipped\n- mypy --strict: clean\n- ruff check: no new issues (pre-existing count unchanged)\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "adfadbbcb4a7bf957fa50fb2fe920f8b550cf581",
      "tree": "22bc6a62d9a0b59e287a2719daf73abdc6520e1c",
      "parents": [
        "b4966199f5b4e04cedb9add1ef109c3763f2c3c5"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:52:26 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:52:26 2026 +0000"
      },
      "message": "Rename YAML keys to openpgp, detect legacy configs, add migration tool\n\nComplete the misnomer fix by renaming the user-facing YAML keys that\nstill used \"gpg\" terminology. The top-level \"gpg:\" block becomes\n\"openpgp:\" and the per-account \"gpg_plain:\" option becomes\n\"openpgp_plain:\". This mirrors how \"smime:\" is named for the protocol\nrather than a specific implementation.\n\nRather than silently accepting the legacy key names, the config\nloader rejects any remail-era keys with a clear error message that\npoints the administrator at the new migration tool. This catches\nadmins who skip the migration step and would otherwise run a kremail\ndaemon against a remail config that parses but does not mean what\nthey think it means.\n\ntools/remail-config-migrate.py copies a remail configuration tree to\na new kremail directory, renames remail.yaml to kremail.yaml, and\nrewrites YAML files to the kremail schema: renames \"gpg:\" block,\ndrops the obsolete pgp_backend/gpgbinary/always_trust options, and\nrenames \"gpg_plain:\" in every admins/subscribers/listaccount entry\n(both in the inline main config and in per-list list.yaml files).\nThe tool uses ruamel.yaml to preserve formatting and comments. Key\nmaterial is not automated because remail\u0027s GnuPG keyring model does\nnot map cleanly to per-fingerprint .asc files without input from the\nadmin; the tool prints a copy-pasteable gpg export sequence instead.\n\nKey changes:\n- Rename YAML keys in kremail: \"gpg:\" -\u003e \"openpgp:\",\n  \"gpg_plain:\" -\u003e \"openpgp_plain:\"\n- account_config attribute gpg_plain renamed to openpgp_plain\n- Add check_legacy_remail_keys() helper in config.py, called from\n  main_config, list_config, openpgp_config, and account_config;\n  detects \"gpg\", \"gpg_plain\", \"pgp_backend\", \"gpgbinary\",\n  \"always_trust\" and raises KremailConfigException pointing at the\n  migration tool\n- New tools/remail-config-migrate.py standalone script\n- Update example configs and man5/kremail.config.rst for the new\n  YAML key names\n- Mention the migration tool in installation.rst\n- tests/test_config_migrate.py covers the migration helpers (unit\n  tests), the CLI end-to-end happy path and error cases (subprocess\n  integration tests), and the legacy-config rejection path,\n  including a loop-closing test that runs the migration and then\n  parses the result through main_config\n\nVerification:\n- pytest: 150 passed, 12 skipped (26 new migration tests)\n- mypy --strict: clean\n- ruff check: src/, tools/, and new tests all clean\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "b4966199f5b4e04cedb9add1ef109c3763f2c3c5",
      "tree": "23da99fabdf5decd3b24c556ce3806789a2aaf35",
      "parents": [
        "9209360255bdb57cafa5c220c2860e69b4e9e7ef"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:39:29 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:39:29 2026 +0000"
      },
      "message": "Drop GnuPG backend, rename sequoia module to openpgp\n\nRemove the python-gnupg PGP backend entirely and rename the remaining\nSequoia-based backend from \"sequoia\" to \"openpgp\". The file and class\nnames now reflect the protocol (OpenPGP, RFC 4880) rather than either\na specific implementation library (\"sequoia\") or the GnuPG binary\ncommand (\"gpg\") that used to be involved. This mirrors how the S/MIME\nmodule is named for its protocol rather than the cryptography library.\n\nUser-facing YAML config keys (the \"gpg:\" block, \"gpg_plain:\" option)\nare kept as-is for backward compatibility with existing deployments;\nonly Python-side identifiers are renamed.\n\nKey changes:\n- Delete src/kremail/gpg.py and tests/test_gpg.py\n- Rename src/kremail/sequoia.py -\u003e src/kremail/openpgp.py (git rename)\n- Rename tests/test_sequoia.py -\u003e tests/test_openpgp.py (git rename)\n- Rename Python identifiers:\n  - gpg_crypt -\u003e openpgp_crypt\n  - gpg_sender_info -\u003e openpgp_sender_info\n  - gpg_encrypt/gpg_decrypt_plain/gpg_decrypt_enveloped -\u003e openpgp_*\n  - KremailGPGException -\u003e KremailOpenPGPException\n  - KremailGPGKeyException -\u003e KremailOpenPGPKeyException\n  - gpg_config -\u003e openpgp_config\n  - self.gpg attribute -\u003e self.openpgp (on maillist and list_config)\n  - msg_set_gpg_payload -\u003e msg_set_openpgp_payload\n- Remove pgp_backend config option and the get_pgp_backend() dispatcher\n  from maillist.py; openpgp_crypt is instantiated directly\n- Remove dead config options: gpgbinary, always_trust, and the unused\n  home/keyring attributes from openpgp_config\n- pyproject.toml: drop python-gnupg, promote pysequoia from optional\n  to core dependency, remove the now-empty [sequoia] extras group,\n  clean up obsolete ruff ignores (S603, S607 for gnupg subprocess)\n  and mypy ignore_missing_imports for gnupg\n- Update Documentation: remove PGP backend selection section from\n  configuration.rst, drop python-gnupg from installation.rst, update\n  the directory tree in man5/kremail.config.rst to show .pgpkeys/\n  instead of .gnupg/pubring.kbx\n- Untrack src/*.egg-info directories (already in .gitignore but were\n  tracked from a stray commit)\n\nVerification:\n- pytest: 124 passed, 12 skipped (15 gnupg-specific tests removed)\n- mypy --strict: clean (13 source files)\n- ruff check: src/ clean; 22 pre-existing issues remain in tests/\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "9209360255bdb57cafa5c220c2860e69b4e9e7ef",
      "tree": "725d8b0fbdb195ad59133352920ed18c2d0612a9",
      "parents": [
        "526a8798fdde1dfc5f190284dd42d601ec7fefa8"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:16:55 2026 +0000"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 23 15:21:04 2026 +0000"
      },
      "message": "Rename project from remail to kremail\n\nkremail (pronounced \"Cream Ale\") is a fork of Thomas Gleixner\u0027s remail.\nThe primary motivation for the rename is a complete switch of\ncryptographic backends: from M2Crypto to cryptography for S/MIME (done)\nand from python-gnupg to Sequoia PGP for OpenPGP (in progress). These\nbackend changes meaningfully alter build requirements, deployment, and\nruntime behavior, so a new project name is warranted to avoid confusion\nwith the upstream remail codebase. Git history is preserved via renames\nrather than a fresh copy so the lineage with tglx\u0027s remail remains\nvisible.\n\nKey changes:\n- Rename src/remail/ -\u003e src/kremail/ (14 modules)\n- Rename remaild.py -\u003e kremaild.py; class remaild -\u003e kremaild\n- Rename exception classes: Remail* -\u003e Kremail*\n- Update pyproject.toml: name\u003dkremail, version\u003d2.0-dev, console\n  scripts kremail-daemon/kremail-pipe/kremail-chkcfg\n- Drop top-level shell wrappers; rely on [project.scripts] entries\n- Rename remail.service -\u003e kremail.service, update paths to\n  /home/kremail/kremail/ layout\n- Rename man pages to dashed form to match console scripts:\n  kremail-daemon(1), kremail-pipe(1), kremail-chkcfg(1),\n  kremail.config(5)\n- Rename default config to kremail.yaml (clean break; format remains\n  compatible with remail.yaml)\n- Rename Documentation/remail.svg -\u003e kremail.svg (including the\n  rendered text inside the SVG)\n- Update README.md to describe the fork lineage\n- Refresh .git/CLAUDE.md: describe current backends (cryptography,\n  sequoia) and drop stale notes\n\nVerification:\n- pytest: 139 passed, 12 skipped (live-data tests require external\n  remail-test archive)\n- mypy --strict: clean\n- ruff check: same 24 pre-existing issues; rename did not add new ones\n\nExternal references left as-is:\n- tests/test_live_data.py still uses the external remail-test archive\n  name and addresses (unrelated resource on lore.kernel.org)\n- Documentation/contribute.rst now points at mricon/kremail.git on\n  kernel.org; repo rename on kernel.org still TODO\n\nAssisted-by: claude-opus-4-7\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "526a8798fdde1dfc5f190284dd42d601ec7fefa8",
      "tree": "7cce1753135e9a47830d3338925b63d694c65f19",
      "parents": [
        "4b19c91607a0a8d26a53078b05c3595b1ca0fbc8"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:53:55 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 22:09:04 2026 -0400"
      },
      "message": "Add ruff and mypy --strict linting\n\nWire in ruff for linting and mypy with strict mode for type checking,\nboth configured in pyproject.toml and runnable via uv.\n\nRuff fixes applied:\n- Replace bare except clauses with except Exception\n- Fix type comparisons (type(x) \u003d\u003d str -\u003e isinstance(x, str))\n- Add raise from ex for proper exception chaining\n- Import sorting and old-style class cleanup\n- Revert unsafe SIM118 fix (.keys() needed for accounts_config)\n\nReal bug found: base64.decodestring() was removed in Python 3.9,\nreplaced with base64.decodebytes().\n\nMypy configuration uses strict mode with relaxed untyped checks\nwhile type annotations are added incrementally. Per-module overrides\nsuppress errors for dynamically-typed modules (config, remaild) and\nuntyped external libraries.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "4b19c91607a0a8d26a53078b05c3595b1ca0fbc8",
      "tree": "762da3ef8ec90292dd02afba895d3fb43ca8c556",
      "parents": [
        "3c8d7faef0161059f768d72b9a7ebde8d2598538"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:43:35 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:43:35 2026 -0400"
      },
      "message": "Modernize documentation for Read the Docs\n\nUpdate the Sphinx documentation for modern tooling and to reflect\nthe current state of the project.\n\nKey changes:\n- Switch to sphinx_rtd_theme for Read the Docs compatibility\n- Strip boilerplate from conf.py, keep only what\u0027s needed\n- Clean up index.rst to use a single toctree per section\n- Update installation.rst for new dependencies (cryptography\n  replaces M2Crypto, pysequoia as optional PGP backend)\n- Add PGP backend selection docs to configuration.rst with\n  key storage format for both gnupg and sequoia backends\n- Update contribute.rst with new repo URL and test instructions\n- Fix typos in introduction.rst\n- Add .readthedocs.yaml for RTD builds\n- Add Documentation/requirements.txt for RTD dependencies\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "3c8d7faef0161059f768d72b9a7ebde8d2598538",
      "tree": "b08521b969819bc328e0a175a4d5d54322d2b608",
      "parents": [
        "2dc8d08621e503ba3567ed8abc1344c35a5e7da0"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:39:51 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:39:51 2026 -0400"
      },
      "message": "Add live data integration tests against remail-test archive\n\nAdd tests that validate the sequoia backend against 109 real\nencrypted messages from the remail-test list archive. Tests cover:\n\n- Message classification (PGP/MIME, inline PGP, S/MIME, signed)\n- PGP/MIME decryption: 67/69 messages decrypt successfully (2\n  encrypted to other keys, expected for a real archive)\n- Inline PGP: all 20 text/plain messages handled correctly\n- Non-PGP passthrough: all 12 messages pass through without error\n- Decrypted archive validation: all 74 messages have bodies and\n  required headers\n- Round-trip: encrypt decrypted messages with sequoia, decrypt\n  them back, verify content survives\n\nTests are skipped if the live data directory is not available,\nso they work in CI without the test data.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "2dc8d08621e503ba3567ed8abc1344c35a5e7da0",
      "tree": "3e3c53995a71f483b1098ccd2ffcf6100cc7d0fb",
      "parents": [
        "93dd2ddd6b997f848bed93283c9a29d2b22725c9"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:26:24 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:26:24 2026 -0400"
      },
      "message": "Add maillist corner case tests, fix sequoia exception hierarchy\n\nAdd 27 additional maillist tests covering corner cases:\n- Encryption failure freezes subscriber account\n- Encryption failure for admin does NOT freeze (not a subscriber)\n- disable_subscribers: known, already-disabled, and unknown addresses\n- Autoreply detection: empty Reply-To, noreply address, Microsoft\n  X-Auto-Response-Suppress, empty Return-Path\n- Multiple To: addresses (first match wins)\n- Archive disabled modes (incoming off, plain off, maildir mode)\n- Disabled subscribers skipped during delivery\n- All subscribers disabled still succeeds\n- Moderated list with stranger through full encrypted pipeline\n- Tracking state machine: freeze persists, init new subscribers,\n  frozen stays frozen, disabled clears frozen\n- handle_log: no warnings \u003d no mail, warnings sent to admins\n- From mangling edge cases: @, parentheses, backslash, quotes\n\nThe encryption failure tests uncovered a real bug: the sequoia backend\ndefined its own RemailGPGException class instead of reusing the one\nfrom remail.gpg. This meant maillist.send_encrypted_mail() would not\ncatch sequoia encryption failures, so subscribers would never get\nfrozen on encrypt errors when using the sequoia backend. Fix by\nimporting the exceptions from remail.gpg.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "93dd2ddd6b997f848bed93283c9a29d2b22725c9",
      "tree": "0cae9c1f500d1eaedf91a7039cdb354340d7ec5e",
      "parents": [
        "094050cb6b8015bfa0db702f8d615d84c2cac2f9"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:23:18 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:23:18 2026 -0400"
      },
      "message": "Add maillist tests and fix sequoia plain text handling\n\nAdd comprehensive tests for the maillist module covering:\n- From header mangling (with name, without name, quoting for\n  special characters like commas and dots)\n- Destination routing (post, owner, bounce, unknown addresses)\n- Subscription checks (subscriber, admin, alias, unknown)\n- Moderation (unmoderated, moderated with subscriber/stranger/admin)\n- Subject prefixing\n- Mail archiving (incoming, list, admin)\n- Encryption (GPG and transport-only modes)\n- Decrypt through maillist layer with list-key encrypted messages\n- Bounce/autoreply detection and routing to admins\n- End-to-end mail processing with encrypted messages\n\nThe tests uncovered a bug in the sequoia backend: gpg_decrypt_plain\ndid not handle pysequoia\u0027s \"unexpected EOF\" error when attempting to\ndecrypt non-PGP plain text data. Add \"unexpected eof\" to the list of\nnon-fatal error messages that are treated as unencrypted passthrough.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "094050cb6b8015bfa0db702f8d615d84c2cac2f9",
      "tree": "6abe3bfad409b4f5146d2362104db0bcde4fb6f4",
      "parents": [
        "d57e63d221579fad991e5f159ec6649ee1725d9c"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:19:20 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 18:19:20 2026 -0400"
      },
      "message": "Add pysequoia PGP backend with configurable selection\n\nAdd a pysequoia-based PGP backend as an alternative to the existing\npython-gnupg backend. pysequoia uses the Sequoia PGP library via\nRust FFI, eliminating the need for a gpg binary on the system.\n\nThe backend is selected via the pgp_backend config option:\n- pgp_backend: gnupg (default) — uses python-gnupg with .gnupg keyring\n- pgp_backend: sequoia — uses pysequoia with .pgpkeys directory\n\nThe sequoia backend reads individual .asc key files from the list\u0027s\n.pgpkeys directory. pysequoia is an optional dependency, installed\nvia the [sequoia] extra.\n\nAlso adds comprehensive tests for both PGP backends covering\nsender_info, key validation (missing, expired, wrong UID, strict_uid\ntoggle), and encrypt/decrypt round-trips (enveloped, signed, inline).\n\nKey changes:\n- Add src/remail/sequoia.py with the pysequoia backend\n- Add pgp_backend and keydir to gpg_config\n- Add get_pgp_backend() factory in maillist.py\n- Graceful import: sequoia backend only loaded if pysequoia installed\n- Update .gitignore for modern Python/uv artifacts\n- Add tests/test_gpg.py for the gnupg backend\n- Add tests/test_sequoia.py for the sequoia backend\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "d57e63d221579fad991e5f159ec6649ee1725d9c",
      "tree": "0e8f50fe48e6797b8a10a768f4867a051a337f1a",
      "parents": [
        "4c839d9139d4129ecf3f964861d3bd3d544b458d"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:49:16 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:49:16 2026 -0400"
      },
      "message": "smime: Add comprehensive tests and fix cert extraction\n\nAdd tests for the S/MIME module covering sender_info, helper\nfunctions, certificate validation (valid, expired, not-yet-valid,\nwrong email, missing), initialization, encrypt/decrypt round-trips\n(with and without signing), error handling, and content type\ndetection.\n\nThe encrypt+sign round-trip test uncovered a bug in smime_verify:\nit was trying to parse the whole MIME message as raw PKCS7, which\nfails for multipart/signed messages where the signature is a\ndetached part. Fix it to extract the signature from the correct\nMIME part before parsing for signer certificates.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "4c839d9139d4129ecf3f964861d3bd3d544b458d",
      "tree": "2044e4343094487c6038d2a55ce248c5b2d01aec",
      "parents": [
        "4dd7496ef1f7274107ddd32a864bda71d8d77a61"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:43:22 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:44:45 2026 -0400"
      },
      "message": "Add pytest framework with initial mail module tests\n\nAdd pytest as a test dependency and configure it in pyproject.toml.\nRemove the stale top-level __init__.py that shadowed the src/remail\npackage. Bump requires-python to \u003e\u003d 3.10 to match cryptography\u0027s\nrequirements.\n\nAdd initial tests for the mail module covering:\n- Email address validation\n- Raw email address extraction\n- Header setting and replacement\n- Autoreply detection (RFC 3834, Microsoft, precedence, noreply)\n- HTML stripping from multipart/alternative messages\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "4dd7496ef1f7274107ddd32a864bda71d8d77a61",
      "tree": "5efc3308ed1fcb251dced2bbe6d62322a3a616f1",
      "parents": [
        "8952f156d1fd5c8c6c44c060bc7383f77a064bbd"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:40:16 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:44:45 2026 -0400"
      },
      "message": "smime: Replace M2Crypto with cryptography library\n\nM2Crypto requires swig and fails to build on systems with OpenSSL 3.x\ndue to the removed ENGINE API. Replace it with the well-maintained\ncryptography library which provides equivalent PKCS7 functionality\nand builds cleanly everywhere.\n\nKey changes:\n- Use cryptography\u0027s PKCS7EnvelopeBuilder for S/MIME encryption\n- Use cryptography\u0027s pkcs7_decrypt_smime for S/MIME decryption\n- Use cryptography\u0027s PKCS7SignatureBuilder for S/MIME signing\n- Use cryptography\u0027s x509 module for certificate loading/validation\n- For signed messages, extract signer cert and payload from the MIME\n  structure directly. Cryptographic signature verification is not\n  currently performed as the cryptography library does not yet\n  support PKCS7 verify (pyca/cryptography#6413).\n- Simplify exception handling since cryptography uses standard\n  Python exceptions instead of M2Crypto\u0027s opaque error types\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "8952f156d1fd5c8c6c44c060bc7383f77a064bbd",
      "tree": "2f11550ccccf6ba75c32990d404cfd71f4146ad8",
      "parents": [
        "9434d5732a1f4616797130a27e56527bf8edc128"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:20:35 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:44:45 2026 -0400"
      },
      "message": "Add pyproject.toml and adopt src layout\n\nModernize the project structure with a pyproject.toml for packaging\nmetadata and dependencies. Move the remail package into src/remail\nfor proper src-layout packaging. Refactor the top-level scripts\n(remail_daemon, remail_pipe, remail_chkcfg) into proper modules with\nmain() functions suitable for console_scripts entry points.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "9434d5732a1f4616797130a27e56527bf8edc128",
      "tree": "9de7e8bfecc9810502138c504843ec895cdb2dc1",
      "parents": [
        "3b9c0438b48eee128129c914c66c2ff7033e88e9"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:17:14 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:17:14 2026 -0400"
      },
      "message": "maillist: Gracefully handle expired or broken keys\n\nWhen a GPG key or S/MIME certificate for any mailing list is expired\nor otherwise broken, remail throws a fatal exception during list\ninitialization that kills the entire daemon. This prevents mail\ndelivery to all lists, not just the one with the bad key.\n\nCatch exceptions during smime/gpg initialization and disable only\nthe affected list instead of crashing. The admin is notified via\nlog_exception() so they can fix the keys or disable the list in the\nconfiguration.\n\nCo-Authored-By: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "3b9c0438b48eee128129c914c66c2ff7033e88e9",
      "tree": "59c144dd9065ecb7f17049850d5ac0aed6ff8c5a",
      "parents": [
        "57146cb42a1d909f3da2f3ff8948bcd7a5eab140"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:14:40 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:14:40 2026 -0400"
      },
      "message": "gpg: Add strict_uid option for GPG key UID validation\n\nSubscribers who change organizations may have GPG keys with UIDs that\nno longer match their current email address. Previously this was a\nfatal error that prevented mail delivery.\n\nAdd a per-account strict_uid option (default: true) that controls\nwhether a UID mismatch raises an exception or just prints a warning.\nThe key is still matched by fingerprint, so this only relaxes the\nadditional UID-to-address verification.\n\nTo use, set strict_uid: false on the subscriber account in the list\nconfiguration.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "57146cb42a1d909f3da2f3ff8948bcd7a5eab140",
      "tree": "58c52e8a2b0d7f5f08457c6d1d2be6b96c165743",
      "parents": [
        "904590eb59679ff01d956b2ed91b430f8e1a4279"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:12:41 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:12:41 2026 -0400"
      },
      "message": "gpg: Fix case-insensitive comparison for bare UID addresses\n\nThe _check_key_addr() method lowercases the UID but not the account\naddress when comparing bare addresses (without angle brackets). This\ncauses a mismatch when the config has mixed-case addresses like\n\"User@Example.com\" but the GPG key UID is \"user@example.com\".\n\nLowercase the address in the comparison to match the behavior already\nused for the angle-bracket form.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "904590eb59679ff01d956b2ed91b430f8e1a4279",
      "tree": "f743428cd20cfb18c4c2dccbd85752b8273e1a86",
      "parents": [
        "2646c9569cca8acd390b6dfdc8ac17c80894ce58"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:05:37 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:05:37 2026 -0400"
      },
      "message": "smime: Relax certificate subject matching\n\nThe original check required the S/MIME certificate subject to be\nexactly \"/emailAddress\u003daddr@example.com\", but real-world certificates\noften contain additional fields like CN, O, OU, etc. Use a substring\nmatch instead so certificates with subjects like\n\"/CN\u003dName/O\u003dOrg/emailAddress\u003daddr@example.com\" are accepted.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "2646c9569cca8acd390b6dfdc8ac17c80894ce58",
      "tree": "15cf662407f9d0d7f61bcbdc8b30d60a3654a795",
      "parents": [
        "25900eb7c6633b7eedfd0c4904276a9d583841a7"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:05:19 2026 -0400"
      },
      "committer": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Thu Apr 09 17:05:19 2026 -0400"
      },
      "message": "gpg: Set python-gnupg encoding to UTF-8\n\nThe python-gnupg library defaults to latin-1 encoding, which causes\nfailures when processing GPG keys with non-ASCII characters in their\nUIDs (e.g. accented names, CJK characters). Explicitly set the\nencoding to utf-8 to handle these correctly.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nAssisted-by: claude-opus-4-6\n"
    },
    {
      "commit": "25900eb7c6633b7eedfd0c4904276a9d583841a7",
      "tree": "f5775019067a11fa7840304cd74f8aa87d62c07c",
      "parents": [
        "dc68e97937bf589b31bde1b083c6530c94ec14b4"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:35:11 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: v0.14\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "dc68e97937bf589b31bde1b083c6530c94ec14b4",
      "tree": "55d6157dac314b2142ef4bfc1c494e7bf8b3f45d",
      "parents": [
        "424944ad36e9925a9d3856b7054ca7fcc26f1ea6"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Jun 09 23:17:26 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Make From header mangling less convoluted\n\nDue to the requirement to reencrypt the incoming mail, remail must rewrite\nthe mail headers and create a new email with the \u0027From:\u0027 header being the\nlist address. So remail implemented a scheme to mangle the original senders\nname into the reencrypted mails \u0027From:\u0027 header.\n\nThat fell flat on its nose when there was an incoming mail which had no\nname part and just consisted of the actual email address. Instead of\ndifferentiating between these cases the authors sleep deprived and grump\nladen brain decided to implement \u0027From:\u0027 mangling in the way it is now.\n\nIt converts the original sender mail address \u0027[Name]\n\u003cmailname@sender.domain\u003e\u0027 to:\n\n  \u0027list-name for mailname_at_sender.domain\u0027 \u003clist-name@list.domain\u003e\n\nThis is hard to read and follow. \n\nUse the common format:\n\n  \u0027Name via list-name\u0027 \u003clist-name@list.domain\u003e\n\nExcept for the case where the original \u0027From:\u0027 header is a plain email\naddress without a name. This will mangle it to:\n\n  \u0027mailname at sender.domain via list-name\u0027 \u003clist-name@list.domain\u003e\n\nThe \u0027via list-name\u0027 add on is technically not required but is useful to\nvisually differentiate the name in auto-completion.\n\nSuggested-by: Linus Torvalds \u003ctorvalds@linux-foundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "424944ad36e9925a9d3856b7054ca7fcc26f1ea6",
      "tree": "f19760e9ccda1034a904a4eed603b992a01056d3",
      "parents": [
        "ea4b0b46321ff88bccf30f74cfc3fc540574eff8"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Jun 09 23:12:58 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Add X-Original-From header\n\nThe \u0027From:\u0027 header mangling makes it unnecessarily hard to figure out the\nname and email address of the person who posted to a list.\n\nSave the original sender in the \u0027X-Original-From:\u0027 header.\n\nSuggested-by: Linus Torvalds \u003ctorvalds@linux-foundation.org\u003e\nSuggested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nLink: https://wiki.ietf.org/group/dmarc/XOriginalFrom\n"
    },
    {
      "commit": "ea4b0b46321ff88bccf30f74cfc3fc540574eff8",
      "tree": "ad58555019041dfe6d35fede7753902f3cf9b229",
      "parents": [
        "2760a2d671293cdad53db16fd921796f50773121"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 14:39:10 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Fix closing boundaries\n\nEmailMessage.defects reports a CloseBoundaryNotFoundDefect on the outgoing\nPGP message.\n\nAdd the missing closing boundary after the PGP payload section and fix up the\nclosing boundaries format in msg_strip_.*().\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "2760a2d671293cdad53db16fd921796f50773121",
      "tree": "4eebfa73694afc92d920d6034258d447ff0bbc2f",
      "parents": [
        "759dd19c9e374a9330929259e33e037994776ce2"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 15:45:36 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Check outgoing mails for defects\n\nEmailMessage.defects allows to inspect outgoing mails for defects. If found\nreport them in logging.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "759dd19c9e374a9330929259e33e037994776ce2",
      "tree": "dd16cdc59dd55665ddc3104d0a28f1cbc06708b4",
      "parents": [
        "79aa834870827f802b314b3dd238dbddf25abb69"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 12:11:53 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Simplify send_mail()\n\n1) Use EmailMessage\n\n   EmailMessage with the default policy handles header encoding correctly by\n   default and uses \u0027\\n\u0027 line separators, which works for both as_string() and\n   smtplib.send_message() correctly. The latter flattens the message with the\n   RFC conform \u0027\\r\\n\u0027 line separators unconditionally.\n\n   Aside of that EmailMessage provides defects reporting which is useful to\n   ensure that the outgoing mails are correct. A check for that will be added\n   later\n\n2) Simplify header handling\n\n   Remove all existing headers from the cloned message first and add those\n   which are required either as new headers or copied from the original\n   message which was handed in to send_mail()\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "79aa834870827f802b314b3dd238dbddf25abb69",
      "tree": "c948ac5bb50a0bbf947c6c727cb00639658e628d",
      "parents": [
        "c3b13e47f7fe99f3381726fd11dd317cbcc6622e"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 18:18:53 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Unify send_mail()\n\nNo need for two functions.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "c3b13e47f7fe99f3381726fd11dd317cbcc6622e",
      "tree": "8ca13d0e08b8ff41e99ac74a8ff36ab54cf72f9b",
      "parents": [
        "88f8e48b13810001ca021a396caae6d965a8676a"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 18:23:36 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Simplify msg_set_header()\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "88f8e48b13810001ca021a396caae6d965a8676a",
      "tree": "20fed55323a09e2fa1f20474a41e988358c304bc",
      "parents": [
        "893b9510dbf81f6dc25a1ae1c9161896cf1c7f82"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Jun 16 23:31:07 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Use email.utils.get_addresses()\n\nReplace more historical homebrewn parsing\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "893b9510dbf81f6dc25a1ae1c9161896cf1c7f82",
      "tree": "1d3799fdb0e2d6393697604c55bc19e795ff79c3",
      "parents": [
        "c8ac6b5a084c2c497117a86cb79ece14ddb2b776"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Jun 16 22:43:42 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Use email.utils.parseaddr()\n\nManual parsing is error prone.\n\nSuggested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "c8ac6b5a084c2c497117a86cb79ece14ddb2b776",
      "tree": "ed0cea4bf6743963d62988421bde7d2f75ac8496",
      "parents": [
        "2cee74a08be328e8ace7a47f91369b58e19d6739"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Jun 18 18:09:39 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Use existing policy for pipe\n\nNo need for a new one.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "2cee74a08be328e8ace7a47f91369b58e19d6739",
      "tree": "dd03518b891e0005705cfe292b2a3275ea6f4760",
      "parents": [
        "bab35c35b65a522c6469704533075c2ec99e8cb0"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Jun 16 23:29:09 2023 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Jun 20 23:43:23 2023 +0200"
      },
      "message": "remail: Remove unused functions and debug leftovers\n\nSpring cleaning.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "bab35c35b65a522c6469704533075c2ec99e8cb0",
      "tree": "81eee4fb6d09f242d422ac826573c7d41b688de7",
      "parents": [
        "e2626e1dcadb2a13361d62541615c926e6002315"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 05 01:02:01 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 05 01:03:46 2020 +0100"
      },
      "message": "remail: v13\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "e2626e1dcadb2a13361d62541615c926e6002315",
      "tree": "400b48441ea0ac3be7a7dd10d4be340a3ef3d1c3",
      "parents": [
        "576165a654463caa5c35f92542452a184224ec17"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 05 00:51:22 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 05 01:03:46 2020 +0100"
      },
      "message": "remail: Fix the from mangling\n\nThe comment in mangle_from() says that if there is not a real name then the\nemail address is mangled by replacing @ with _at_. The comment is great\njust the implementation is not doing so.\n\nAdd the replacement functionality.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "576165a654463caa5c35f92542452a184224ec17",
      "tree": "215185ce5643c6fabb749fd70608f330f7355d83",
      "parents": [
        "46f6145ac55ff06521b8537d31d2daf34e3e73ba"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Nov 01 22:18:14 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Nov 01 22:19:22 2020 +0100"
      },
      "message": "remail: v0.12\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "46f6145ac55ff06521b8537d31d2daf34e3e73ba",
      "tree": "a8a1a163e47e86753b67821be7a3dd14e78c0608",
      "parents": [
        "1e490249a7da859fa10941ee8fc658f032c9a73a"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Oct 29 19:26:56 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Nov 01 22:19:22 2020 +0100"
      },
      "message": "remail: Allow optional transport based security\n\nAdd an \"encryption\" option \u0027use_transport\u0027 which does not bother with\nencryption and just relies on transport security.\n\nFor admins this makes sense as none of the admin messages is really\nconfidential.\n\nThis is also a valid option for a subscriber and makes some sense in\nscenarios where the mail provider manages the subscriber key (sic!)\nand does server side decryption. Think twice before using this.\n\nRequested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nTested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nReviewed-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n\n"
    },
    {
      "commit": "1e490249a7da859fa10941ee8fc658f032c9a73a",
      "tree": "62e935c59a8b99bdd00d7972c4b9536f7d7d0165",
      "parents": [
        "05ac7bb6acb1632ee46ba1c419dc43914de69383"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sat Sep 19 01:53:54 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sat Sep 19 01:53:54 2020 +0200"
      },
      "message": "remail: v0.11\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "05ac7bb6acb1632ee46ba1c419dc43914de69383",
      "tree": "507e22d089ae95f9481345a55138b8ac98bee4a1",
      "parents": [
        "3237b7ee67473f046faf7d47fda978d3f971d5a7"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sat Sep 19 01:43:07 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sat Sep 19 01:43:07 2020 +0200"
      },
      "message": "remail/pipe: Add command line option to handle config updates\n\nWhen using pipe mode configuration updates are not handled until the next\nmail is sent to the list. That means that e.g. welcome mails are delayed.\n\nAdd a command line option to allow the invocation of remail_pipe just to\ndeal with configuration updates without trying to process mail from stdin.\n\nThe update handling is serialized against concurrent incoming mail via the\nfolder lock, so no extra serialization is required. Whichever comes first\nhandles it.\n\nReported-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nTested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "3237b7ee67473f046faf7d47fda978d3f971d5a7",
      "tree": "49c70034b104e6520091fdc3ffc562aa99854678",
      "parents": [
        "cf0f7bc5fb181bce51af3fad4f0c34996accb0c2"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Sep 03 22:24:11 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail: v0.10\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "cf0f7bc5fb181bce51af3fad4f0c34996accb0c2",
      "tree": "8095a9376b57cc442d9d2ab7d86c7a8a675444bf",
      "parents": [
        "cfe274374dace70fc718c36fa28bd17a37fda08d"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Sep 02 15:33:48 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail/smime: Store sender information\n\nFor contact points (open list) it\u0027s required to collect the sender\ninformation, email address and if available the S/MIME certificate which is\ncontained in the signature.\n\nSet the encryption method info and if the mail is signed store the S/MIME\ncertificate.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "cfe274374dace70fc718c36fa28bd17a37fda08d",
      "tree": "f56404121a158095b15970f0d42649b109dbc6f1",
      "parents": [
        "3407aa262f3ba02884d7c83006b85ad60a32691a"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Sep 02 15:31:48 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail/gpg: Store sender information\n\nFor contact points (open list) it\u0027s required to collect the sender\ninformation, email address and if available the GPG key which is contained\nin the signature.\n\nSet the encryption method info and if the mail is signed store the GPG key.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "3407aa262f3ba02884d7c83006b85ad60a32691a",
      "tree": "81354bb9d72b2a1514e11417050ddbea33eb2b70",
      "parents": [
        "ea361f973c42a7514cc8c1562f6ffa717867bce6"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Sep 02 15:28:38 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail: Retrieve sender information\n\nFor open lists and especially contact points the \u0027From\u0027 mangling is\nsuboptimal as the senders email address is not contained in the mail\nitself. Due to re-encryption a eventual signature is not longer intact\nwhich means that the GPG key or the S/MIME certificate which are embedded\ninto the signature are not transported either.\n\nAdd infrastructure to collect sender information including key/certificate\nif available and attach it to the mail. The first attachment contains\nsender information and the second one if available contains the key or the\ncertificate.\n\nThe information is only stored when the config switch is enabled and the\nsender is not subscribed to the list.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "ea361f973c42a7514cc8c1562f6ffa717867bce6",
      "tree": "24d9fa9a411121ff71f699d70ed732ae51994134",
      "parents": [
        "43a0b5cb47d96ab5e38dc83e49a479c4249dd211"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Sep 02 14:45:10 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail/smime: Use certificate embedded in signature for verification\n\nAn open list does not have the certificate of senders and for signature\nverification there is no requirement to have the certificate on disk.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "43a0b5cb47d96ab5e38dc83e49a479c4249dd211",
      "tree": "9f71c79201ee662b8f30873a5e479d77e3cff06f",
      "parents": [
        "7d69dec9ae11f8b9606fe31b2fb672b27042bbbc"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Sep 03 22:04:44 2020 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Sep 04 10:32:42 2020 +0200"
      },
      "message": "remail/remaild: Fix failure message\n\nWhile handling an exception in process_msg() the handler triggers another\none by trying to print a undefined variable (mailfile).\n\nHand the mailfile name into the function. Use \u0027pipe input\u0027 for pipe mode.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "7d69dec9ae11f8b9606fe31b2fb672b27042bbbc",
      "tree": "9ddbeaefbfe73b94585e603aae4704933b6ba8fd",
      "parents": [
        "7e4cace1e286e98d2d4d6b90f72671c16a30bbee"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 13:17:15 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 13:17:15 2020 +0100"
      },
      "message": "v0.9\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "7e4cace1e286e98d2d4d6b90f72671c16a30bbee",
      "tree": "a5080aa58f0925b4a6c2a38f4897622781014d33",
      "parents": [
        "76c07f6d490ae01bb5610185f22b546fca9c06ea"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 11:57:42 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 12:10:07 2020 +0100"
      },
      "message": "remail/mail: Handle dwmw2s magic evo plugin\n\ndwmw2\u0027s evolution plugin which seems to workaround ms-exchange oddities has\nanother interesting format not handled by remail yet:\n\nmultipart/mixed with:\n  - empty text/plain section\n  - application/pgp-encrypted\n  - application/octed-stream\n\nCheck for this with at least some sanity checks and morph it into a regular\nmultipart/encrypted message so decrypt can handle it without adding more\nmess to that part.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "76c07f6d490ae01bb5610185f22b546fca9c06ea",
      "tree": "fe41a7fd951de723f2caa92597a5cccb99371685",
      "parents": [
        "4ae83f4389684517659a2ba9d7fe5ee0c954b214"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 11:56:15 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 11 12:10:06 2020 +0100"
      },
      "message": "remail/mail: Add missing encrypted.asc filename\n\nAssuming that there is a limited number of possible filenames is just\nwrong. Add yet another one.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "4ae83f4389684517659a2ba9d7fe5ee0c954b214",
      "tree": "f68b079026fc600e79c85ea6a9f0f450f3819680",
      "parents": [
        "d17b1394e96b97e73cfebf5da394a64106b77b78"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Feb 19 01:14:22 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "remail: v0.8\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "d17b1394e96b97e73cfebf5da394a64106b77b78",
      "tree": "9467e51127b1b671ae462bf2f460ee405ed96617",
      "parents": [
        "8e6e7c2cc58bf8468159310cd824897e46ddfd83"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Mar 02 17:11:39 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "remaild: Also notify on IN_MOVED_TO events\n\nSome MTAs move mails into the maildir after constructing them in some\ntemporary path.\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "8e6e7c2cc58bf8468159310cd824897e46ddfd83",
      "tree": "3cfe04fa8d2798072df2738c68b11feacac39884",
      "parents": [
        "bc2b62bddab69ed42327997b6068738ccb878e16"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Mar 02 17:11:38 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "config: Introduce an enabled flag for S/MIME\n\nThis allows setups where there is no S/MIME. In some scenarios using just\nGPG is fine and S/MIME might even be discouraged. Previously this required\nto provide a dummy S/MIME key just to make remail happy. With this new flag\nthere is no need for that key if S/MIME is not required for the list.\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "bc2b62bddab69ed42327997b6068738ccb878e16",
      "tree": "161401198ebeb5b88956647275ecdcc7a8e62c72",
      "parents": [
        "79d26fccfe9581b8868412a263aa66da568ccefb"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Mar 02 17:11:37 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "remail: Relax the mail validation so new gTLDs have a chance to pass\n\nThese days it is common to see mail addresses from the newer TLDs that do\nnot always fit in two to three chracters. These are not covered by the\nbasic mail address validation regex.\n\nRelax the requirement from two or three characters to just two or more\ncharacters to covers these.\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "79d26fccfe9581b8868412a263aa66da568ccefb",
      "tree": "ead421855e091f30593097fb751741f079424e10",
      "parents": [
        "a031afbb25f6b676182059c9c8cb3b341a0645b1"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Mar 02 17:11:36 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "config: Pass the incorrect mail address instead of the config path\n\nPreviously remail just logged the path of the attribute (e.g.\nbase.lists.demo.listaccount.addr) instead of the actually mail address that\nwasn\u0027t valid.\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "a031afbb25f6b676182059c9c8cb3b341a0645b1",
      "tree": "7825fc947525e01af24e3eb9061fc50709855324",
      "parents": [
        "342d696b2b95cdf8624102f1f28e8911992522ff"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Mar 02 17:11:35 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Mar 04 09:42:35 2020 +0100"
      },
      "message": "config: Throw correct exception when address isn\u0027t valid\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "342d696b2b95cdf8624102f1f28e8911992522ff",
      "tree": "46a90143f36771bbe25445177f8a96785aa86f0f",
      "parents": [
        "fa1e6fc47e52caffbe5dfee3ce1b080c18af7c52"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Feb 20 12:42:50 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Feb 20 12:42:50 2020 +0100"
      },
      "message": "remail/lists: Emit warning text instead of \u0027txt\u0027\n\n\u0027txt\u0027 is pretty useless in syslog and admin reports.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "fa1e6fc47e52caffbe5dfee3ce1b080c18af7c52",
      "tree": "f1a89d12ef258ccc1f8d4a9e4954de4c71de1c50",
      "parents": [
        "fc918a9bef1c7b449080cb064db7b1f3249d793d"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Feb 19 01:14:22 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Feb 19 01:14:51 2020 +0100"
      },
      "message": "remail: v0.7\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "fc918a9bef1c7b449080cb064db7b1f3249d793d",
      "tree": "4d1df9ce6963a621120ca2ecd3f74181d5b2afa2",
      "parents": [
        "9b0ec00c8f3b13ba15ae2af03416c87116f0ffa8"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Feb 18 21:40:36 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Feb 19 01:14:51 2020 +0100"
      },
      "message": "remail/gpg: Use the lists private key for signing\n\nKonstantin reported that outgoing mail from a mailing list is signed with\nthe default private key found in the private keyring.\n\nThat\u0027s caused by just handing boolen True into the \u0027sign\u0027 argument of\ngpg_encrypt() while the documentation clearly says:\n\n  sign (defaults to None)\n    Either the Boolean value True, or the fingerprint of a key which is\n    used to sign the encrypted data. If True is specified, the default key\n    is used for signing. When not specified, the data is not signed.\n\nHand the list account fingerprint in if signing is enabled in the\nconfiguration.\n\nReported-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nTested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nReviewed-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "9b0ec00c8f3b13ba15ae2af03416c87116f0ffa8",
      "tree": "850dc669a03c237dad263c9f689d2eeec6bdaf44",
      "parents": [
        "1920962d97e7a14c57ff308ce08c4adbe3e96952"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Feb 17 20:04:58 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Feb 17 20:04:58 2020 +0100"
      },
      "message": "remail: v0.6\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "1920962d97e7a14c57ff308ce08c4adbe3e96952",
      "tree": "61530e9e111a6328fd9eaae8ef71ce3fe0166859",
      "parents": [
        "6bcb72f32e005f5efb0775994e4edd105721eca8"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Feb 17 19:52:59 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Feb 17 20:04:23 2020 +0100"
      },
      "message": "Documentation: Add mailinglist info\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "6bcb72f32e005f5efb0775994e4edd105721eca8",
      "tree": "8d085a4b0a588d9837a2d9434d3539f137d2c24f",
      "parents": [
        "231d9f0bdb09d53a993fa10093871fe88eac2563"
      ],
      "author": {
        "name": "Andreas Rammhold",
        "email": "andreas@rammhold.de",
        "time": "Mon Feb 17 19:44:05 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Feb 17 19:50:26 2020 +0100"
      },
      "message": "ruamel/config: Use ruamel.yaml instead of yaml\n\nThis removes the (undocumented) dependency on PyYAML and just uses\nruamel.yaml that is already a dependency.\n\nSigned-off-by: Andreas Rammhold \u003candreas@rammhold.de\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "231d9f0bdb09d53a993fa10093871fe88eac2563",
      "tree": "31ba32ecf3e458b2cba4aef4a2ac4fe43fd21c00",
      "parents": [
        "5e8939d9a0c63eae7e8623ba7555b2e4ac379203"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Jan 09 19:52:55 2020 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Jan 09 19:53:44 2020 +0100"
      },
      "message": "remail: v0.5\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "5e8939d9a0c63eae7e8623ba7555b2e4ac379203",
      "tree": "b0d8c68df7539960512ddc73f0196ade8417e348",
      "parents": [
        "18a16b08fb8e72e446a4f7a7cf9b4a92d45ad1cb"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Nov 26 20:27:55 2019 -0500"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Jan 09 19:52:22 2020 +0100"
      },
      "message": "remail: Make sure list configs inherit global gpg/smime\n\nPer-list config dictionaries were always instantiated with default gpg\nconfiguration because global gpg/smime settings were being ignored. This\nchange makes sure that smime and gpg options set on the global level are\ninherited by list-specific configurations.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "18a16b08fb8e72e446a4f7a7cf9b4a92d45ad1cb",
      "tree": "b360ce682d2b671506311b5f7867a98bc7b0e475",
      "parents": [
        "0eecc8bd2dfd0dbbe5740b9bbf09c5d3f2e82ddc"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Nov 26 20:27:54 2019 -0500"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Jan 09 19:52:22 2020 +0100"
      },
      "message": "remail: Fix default list-id value to conform to RFC2919\n\nAccording to RFC2919, List-Id header should be in the form of a hostname\nvalue enclosed inside angle brackets. This change does two things:\n\n1. Fixes the default to be the list address with \"@\" replaced by a \".\"\n2. Allows setting custom list-id values inside remail.yaml\n3. Documents the \"listid\" optional setting in the manpage\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\nLink: https://tools.ietf.org/html/rfc2919\n\n"
    },
    {
      "commit": "0eecc8bd2dfd0dbbe5740b9bbf09c5d3f2e82ddc",
      "tree": "eb38477f6b68ee3d98dcbc67734c7b9bc08ee6f0",
      "parents": [
        "e3a2f4bc1dd624a1e5fa42e0abaf099857f6f4b9"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Tue Nov 26 20:27:53 2019 -0500"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Jan 09 19:52:22 2020 +0100"
      },
      "message": "remail: Add requirements.txt file\n\nUsing a requirements.txt file makes it easier to install dependencies\nvia the pip python package manager:\n\n    pip install -r requirements.txt\n\nThis is particularly handy for testing when running via virtualenv.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "e3a2f4bc1dd624a1e5fa42e0abaf099857f6f4b9",
      "tree": "0a34b325247a4c148aa312a2ca38ffd32afd594b",
      "parents": [
        "d7b8db9b96ec6e8c6a3da6a5364ec5b9937533e0"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 14 15:49:35 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Thu Nov 14 15:49:35 2019 +0100"
      },
      "message": "remail/remaild: Use correct path for frozen mails and update log message\n\nStore the frozen mails in maildir.frozen/new and not in the base\ndirectory. Move the log message to the actual frozen handling function and\nprovide the correct file pathes in the log.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "d7b8db9b96ec6e8c6a3da6a5364ec5b9937533e0",
      "tree": "0d38ec277eb757fd9121700ef8b028cd6491bc65",
      "parents": [
        "8ada1c09b35615d9de41bb1304164f6b4bb7f747"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Nov 12 23:46:12 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Nov 13 00:07:10 2019 +0100"
      },
      "message": "remail/mail: Handle Outlook GPG plugin proper\n\nThe Outlook GPG plugin works in interesting two variants:\n\n  1) msg.asc or msc.gpg provided as a plain attachement\n     without PGP envelope\n\n  2) GpgOL_MIME_structure.txt contains a fully enveloped\n     PGP payload with the proper headers.\n\nOf course everything can be base64 encoded and the number of payload\nsections is variable as well.\n\nImplement the handling for #2 so it can coexist with the existing\nworkaround for #1.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "8ada1c09b35615d9de41bb1304164f6b4bb7f747",
      "tree": "1a0c7816fd852b84d699cd18bc83ed8c297028a9",
      "parents": [
        "dac7c0af8dc95dce85ec8a1086c3ab15b63e2cc4"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Nov 12 23:44:24 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Nov 13 00:07:10 2019 +0100"
      },
      "message": "remail/mail: Sanitize incoming headers\n\nRemove CR/LF leftovers which might be in incoming headers before setting\nthem. Happens when handling the weird GPG Outlook attachments.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "dac7c0af8dc95dce85ec8a1086c3ab15b63e2cc4",
      "tree": "3e5d5cdcdd8b86df4e6a8c853f239167539c3b80",
      "parents": [
        "f8da9d327880e6edd159446424a91c556beb6ffb"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Nov 12 23:42:59 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Wed Nov 13 00:07:10 2019 +0100"
      },
      "message": "remail/pipe: Allow UTF-8 for incoming mails\n\nSimilar to mails in files, this needs to be explicitly enabled.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "f8da9d327880e6edd159446424a91c556beb6ffb",
      "tree": "0a0d5224edf0ddd2b3433afcf6648a9f5e4efb1d",
      "parents": [
        "aa10d8c48a91ebb04544eaa7534f6c9e9a6dc472"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Nov 12 20:50:04 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Tue Nov 12 20:50:04 2019 +0100"
      },
      "message": "remail/mail: Add yet another outlook magic filename\n\nGpgOL_MIME_structure.txt is missing from the outlook GPG repair list.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "aa10d8c48a91ebb04544eaa7534f6c9e9a6dc472",
      "tree": "dcc6c221aa0da4c53a8acc2bf01ca0c21c797759",
      "parents": [
        "1ed9841f76acbb0c63c272acae8690f9407c8660"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Nov 11 22:48:26 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Nov 11 22:52:36 2019 +0100"
      },
      "message": "remail: version 0.4\n\nBump the version number.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "1ed9841f76acbb0c63c272acae8690f9407c8660",
      "tree": "87c34e7d61dfd057fc2b09e6c63925dacb78d94d",
      "parents": [
        "323b8cda64cf52315fa8bfee97fa03b3b8d7a096"
      ],
      "author": {
        "name": "Konstantin Ryabitsev",
        "email": "konstantin@linuxfoundation.org",
        "time": "Mon Nov 11 13:51:30 2019 -0500"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Mon Nov 11 22:49:35 2019 +0100"
      },
      "message": "remail/gpg: Allow specifying path to the gpgbinary to use\n\nWe want to be able to support ECC subkeys, which limits us to gnupg\nversions 2.2 and above. CentOS-7 ships with gnupg-2.0, which cannot be\neasily upgraded to 2.2 due to a slew of potential problems, so we\ninstall the newer version into /opt/gnupg22 and must call it as\n/opt/gnupg22/bin/gpg.\n\nAllow specifying gpg binary path to use instead of the default \"gpg\" in\n$PATH.\n\nSigned-off-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n\n"
    },
    {
      "commit": "323b8cda64cf52315fa8bfee97fa03b3b8d7a096",
      "tree": "3869dd4c8df3876be2f1d755dd57d7ad9c1a4ff9",
      "parents": [
        "266221e151bcc524a0599eef7c55643c6f076d6e"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Nov 01 13:30:25 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Nov 01 13:30:25 2019 +0100"
      },
      "message": "remail: Add remail_pipe script\n\nAdd a handle pipe function to the remailer and a pipe script for handling\nmail in a MTA delivery path.\n\nRequested-by: Konstantin Ryabitsev \u003ckonstantin@linuxfoundation.org\u003e\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "266221e151bcc524a0599eef7c55643c6f076d6e",
      "tree": "6baf741541bde97aad272ab53672ef372c3ddcd5",
      "parents": [
        "2b43bd77898a712fd87c09a368d90d2d0ba09ca0"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Nov 01 13:25:51 2019 +0100"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Fri Nov 01 13:29:19 2019 +0100"
      },
      "message": "Documentation: Rename configuration man page\n\nThe upcoming pipe script will use the same configuration.\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "2b43bd77898a712fd87c09a368d90d2d0ba09ca0",
      "tree": "27baedb6810014b05567f768824b2a775c40145e",
      "parents": [
        "60f6698e525747341d4842dac20b7e84bdfb391d"
      ],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Sep 22 22:48:52 2019 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Sep 22 22:48:52 2019 +0200"
      },
      "message": "remail: Add TODO and a hacky service file\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    },
    {
      "commit": "60f6698e525747341d4842dac20b7e84bdfb391d",
      "tree": "6acd34a3574f930ae817bf8a4d940cda69b33c6e",
      "parents": [],
      "author": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Sep 22 22:38:59 2019 +0200"
      },
      "committer": {
        "name": "Thomas Gleixner",
        "email": "tglx@linutronix.de",
        "time": "Sun Sep 22 22:38:59 2019 +0200"
      },
      "message": "remail: Initial import\n\nSigned-off-by: Thomas Gleixner \u003ctglx@linutronix.de\u003e\n"
    }
  ]
}
