|
| 1 | +# OpenDKIM develop branch - change summary (pre-2.11.0) |
| 2 | + |
| 3 | +This document summarizes the changes merged into the `develop` branch |
| 4 | +during the triage and stabilization effort in May 2026. Over 100 open |
| 5 | +pull requests were reviewed; roughly 80 were merged, closed as |
| 6 | +superseded, or closed with explanation. The open issue count dropped |
| 7 | +from ~125 to ~50. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Security |
| 12 | + |
| 13 | +- **CVE-2020-35766**: Insecure use of predictable path `/tmp/testkeys` |
| 14 | + in key generation tools. (#260, #288) |
| 15 | +- **CVE-2022-48521**: `Authentication-Results` headers were deleted in |
| 16 | + forward order, leaving a window where a crafted message could |
| 17 | + preserve a forged header. Fixed to delete in reverse. (#287) |
| 18 | +- **SubDomains overlapping buffer**: `strlcpy()` was called with |
| 19 | + overlapping source and destination when updating `mctx_domain` during |
| 20 | + the subdomain walk. Undefined behavior; manifests as corrupted `d=` |
| 21 | + tags (invalid signatures) on FreeBSD. Replaced with `memmove()`. (#356) |
| 22 | +- **Header character validity check**: Always-false NULL check on |
| 23 | + `mctx_domain` (a fixed-size array) meant downstream code used a |
| 24 | + potentially empty domain string. (#343, issue #88) |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Correctness fixes |
| 29 | + |
| 30 | +- **DKIM result for ignored signatures**: Ignored signatures were |
| 31 | + reported as `dkim=fail` in Authentication-Results. RFC-correct result |
| 32 | + is `dkim=policy`. (#302, #234, issue #233) |
| 33 | +- **A-R header in sign-only mode**: An `Authentication-Results` header |
| 34 | + was incorrectly added in sign-only mode when the sender domain was |
| 35 | + unresolvable. (#345, issue #130) |
| 36 | +- **A-R / DKIM-Signature header insertion order**: Headers were |
| 37 | + appended rather than prepended, violating the expectation that the |
| 38 | + most-recent results appear first. Now inserted at index 0. (#346, |
| 39 | + issue #24) |
| 40 | +- **AuthservIDWithJobID quoting**: The authserv-id was not quoted when |
| 41 | + `AuthservIDWithJobID` produced a value containing a job ID, producing |
| 42 | + an invalid header. (#308, issue #103) |
| 43 | +- **`On-DNSError` not honoured on key retrieval failure**: DNS errors |
| 44 | + during key lookup were not dispatched to the configured action. (#309, |
| 45 | + issue #176) |
| 46 | +- **libunbound nameserver default**: libunbound ignored |
| 47 | + `/etc/resolv.conf` and used its own root hints, causing DNSSEC |
| 48 | + validation failures in environments with a local validating resolver. |
| 49 | + Now defaults to the system resolver. (#305, issue #181) |
| 50 | +- **NXDOMAIN vs. DNS error**: The stub resolver did not distinguish |
| 51 | + "name does not exist" from "DNS failure", leading to misleading log |
| 52 | + messages and potentially wrong actions. (#312, issue #49) |
| 53 | +- **Multiple DKIM key TXT records**: When multiple TXT records were |
| 54 | + present for a selector, the library used the last one rather than the |
| 55 | + first. (#295) |
| 56 | +- **`l=` body length tag and missing CRLF**: Verification incorrectly |
| 57 | + failed on messages where the body length tag was present but the body |
| 58 | + lacked a trailing CRLF. (#339, issue #45) |
| 59 | +- **`MUSTBESIGNED`/`REQUIREDHDRS` option labels**: Incorrectly labelled |
| 60 | + as "ordered" in documentation and option parsing. (#304, issue #179) |
| 61 | +- **AR header `no-result` parsing**: `Authentication-Results: host; none` |
| 62 | + is valid per RFC 8601 but was rejected by the parser. (#205) |
| 63 | +- **AR header comment delimiter**: Semicolons inside parenthesized |
| 64 | + comments in AR headers confused OpenDMARC's tokenizer. Changed to |
| 65 | + comma. (#136) |
| 66 | +- **DNAME records in DNS responses**: DNS answer packets containing |
| 67 | + DNAME records (whole-zone redirections) caused verification failures. |
| 68 | + DNAME records are now skipped alongside RRSIG records in all DNS |
| 69 | + response parsers. (#353, issue #156) |
| 70 | +- **`RequiredHeaders` error messages**: "not exactly one From/Date |
| 71 | + field" now distinguishes "no From field" from "multiple From fields" |
| 72 | + for easier diagnosis. (#144) |
| 73 | +- **`UserID` supplemental groups**: When an explicit group was given in |
| 74 | + `UserID`, the user's primary group was dropped from the supplemental |
| 75 | + group list. (#301, issue #40) |
| 76 | +- **`KeyFile` silently ignored with `KeyTable`**: If both were |
| 77 | + configured, `KeyFile` was silently ignored with no warning. Now logs a |
| 78 | + warning. (#310, issue #240) |
| 79 | +- **SigningTable consistency check**: DB handle was not correctly |
| 80 | + maintained during the startup SigningTable/KeyTable consistency walk, |
| 81 | + causing spurious failures. (#325, #230, issue #229) |
| 82 | +- **`ldapi://` URI reconstruction**: The socket path was dropped when |
| 83 | + reconstructing an `ldapi://` URI, breaking LDAP over Unix socket. (#314) |
| 84 | +- **`dkim_canon_selecthdrs` assert**: An assertion fired when all |
| 85 | + candidate headers were skipped (e.g. all omitted), causing a crash |
| 86 | + instead of a clean error. (#313, issue #174) |
| 87 | +- **Unsupported signing algorithm handling**: When an unsupported |
| 88 | + algorithm was encountered and skipped, `sig_signalg` was left set to |
| 89 | + `rsa-sha1` for the next signature, causing incorrect algorithm |
| 90 | + assignment. (#334) |
| 91 | +- **`dkim_diffheaders` strlcpy size**: The destination buffer size |
| 92 | + argument to `strlcpy` was wrong, risking truncation. (#291) |
| 93 | +- **`smfi_insheader` stub removed**: A stub for `smfi_insheader()` that |
| 94 | + was required for sendmail 8.12 (released 2001) was still present. |
| 95 | + Removed; libmilter >= sendmail 8.13.0 (2004) is now required. (#333, |
| 96 | + issue #89) |
| 97 | +- **`mctx_domain` NULL check**: Comparing a fixed-size array address to |
| 98 | + NULL always evaluates false; corrected to check for empty string. (#343) |
| 99 | +- **`res_setservers` struct type**: `struct state` should be `struct |
| 100 | + __res_state`; caused compile failure on some platforms. (#283) |
| 101 | +- **`res_nslist` syntax errors**: Syntax errors in |
| 102 | + `dkim_res_nslist()`/`rbl_res_nslist()` that prevented compilation when |
| 103 | + `HAVE_RES_SETSERVERS` was defined. (#298) |
| 104 | +- **`vbr.c` snprintf buffer size**: Two `snprintf()` calls used |
| 105 | + `sizeof(pointer)` (8 bytes) instead of the buffer size, truncating |
| 106 | + error messages. (#244) |
| 107 | +- **`opendkim.c` NULL pointer comparison**: Fixed companion issue in |
| 108 | + `dkimf_add_signrequest`. (#244) |
| 109 | +- **SignHeaders/SkipHeaders regex buffer**: Fixed-size `BUFRSZ` limit on |
| 110 | + the regex buffer caused truncation with long header lists. Now |
| 111 | + dynamically sized. (#341, issue #120) |
| 112 | +- **Minimum signing percentage with empty body**: `Minimum` percentage |
| 113 | + checks incorrectly handled messages with an empty body. (#223, |
| 114 | + issue #222) |
| 115 | +- **Lua `del_header` index**: `odkim.del_header()` used an incorrect |
| 116 | + header index (off-by-one), deleting the wrong header. (#191) |
| 117 | +- **`KeepAuthResults` deleting wrong headers**: Could delete the wrong |
| 118 | + `Authentication-Results` header when multiple were present. (issue #148) |
| 119 | +- **`AuthservID` with job ID producing invalid header**: Quoting fix for |
| 120 | + job-ID-appended authserv-ids. (#308, issue #103) |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Crashes and stability |
| 125 | + |
| 126 | +- **miltertest body replacement stack overflow**: Replacing a message |
| 127 | + body larger than `BUFRSZ` bytes caused a stack overflow in miltertest. |
| 128 | + (#340, issue #66) |
| 129 | +- **memcache `SigningTable` assert**: Using memcache as the `SigningTable` |
| 130 | + backend caused an `assert(0)` crash. (#286) |
| 131 | +- **Use-after-free in `mlfi_close`**: Under `QUERY_CACHE`, a connection |
| 132 | + context could be accessed after being freed. (#280, issue #272) |
| 133 | +- **`dkim_canon_selecthdrs` assert on empty header list**: (#313) |
| 134 | +- **Segfault with empty `RequiredHeaders`**: assert in selecthdrs when |
| 135 | + option was set but produced no headers. (#313, issue #174) |
| 136 | +- **`MultipleSignatures` orphaned signreq entries**: Sign request list |
| 137 | + tail pointer was not maintained, causing use-after-free or missed |
| 138 | + entries with multiple signatures. (#274) |
| 139 | +- **`DKIMF_STATUS_KEYFAIL` undefined**: Missing define caused incorrect |
| 140 | + handling of key failure status. (#329) |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## Memory leaks and resource management |
| 145 | + |
| 146 | +A systematic audit of memory and resource leaks (issue #272) produced |
| 147 | +fixes across multiple code paths: |
| 148 | + |
| 149 | +- Lua global `lg_name` leak in global cleanup (#273) |
| 150 | +- Orphaned signreq list entries with MultipleSignatures (#274) |
| 151 | +- `conf_remardb` not closed in `dkimf_config_free()` (#275) |
| 152 | +- Leak in `dkimf_db_mkarray_base` (#276) |
| 153 | +- GnuTLS hash context leak on aborted messages (#277) |
| 154 | +- Config struct leak on deprecated-setting abort in `dkimf_config_reload` (#278) |
| 155 | +- Leaks on OOM error paths (#279) |
| 156 | +- FD leak from missing `endpwent()` in key safety checks (#284) |
| 157 | +- `dkim_free()` memory leak in ed25519 path (#321) |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## New features |
| 162 | + |
| 163 | +- **`StdoutLog` / `-O` flag**: Log to stdout/stderr instead of syslog, |
| 164 | + for container/Docker deployments. (#323, issue #153) |
| 165 | +- **`CheckSigningTable` option**: Skip the startup SigningTable/KeyTable |
| 166 | + consistency walk, which could be slow with large tables. (#281, #228) |
| 167 | +- **Per-key signing algorithm in KeyTable**: A fourth field in KeyTable |
| 168 | + entries specifies the signing algorithm for that key, enabling |
| 169 | + dual-algorithm signing (RSA + ed25519) from a single config. (#269, |
| 170 | + issue #6) |
| 171 | +- **`opendkim-testkey` ed25519 support**: The key testing tool now |
| 172 | + supports ed25519 keys in addition to RSA. (#299, issue #183) |
| 173 | +- **`opendkim-genkey` ed25519 on OpenSSL 3**: Replaced hardcoded version |
| 174 | + check with a functional test; ed25519 key generation now works on |
| 175 | + OpenSSL 3.x. (#135) |
| 176 | +- **`sd_notify()` systemd readiness**: When built with libsystemd |
| 177 | + (auto-detected), opendkim sends `READY=1` once the milter socket is |
| 178 | + bound. Enables `Type=notify` in the service unit, eliminating the |
| 179 | + race condition where units ordered after opendkim could start before |
| 180 | + it could accept connections. (#352) |
| 181 | +- **`miltertest` `MT_SMTPREPLY` after any callback**: Previously only |
| 182 | + worked after EOM. (#342, issue #95) |
| 183 | +- **Configure arguments in `opendkim -V` output**: `$ac_configure_args` |
| 184 | + is now captured at configure time and printed by `opendkim -V`, |
| 185 | + making it straightforward to reproduce a packaged build from source |
| 186 | + or diagnose support requests involving non-standard builds. (#358, |
| 187 | + issue #357) |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +## Build system and portability |
| 192 | + |
| 193 | +- **`res_ninit()` configure detection**: On non-glibc platforms (FreeBSD, |
| 194 | + etc.), `resolv.h` requires prerequisite headers; the configure check |
| 195 | + was including only `resolv.h`, causing `res_ninit` to go undetected. |
| 196 | + Fixed by moving `AC_HEADER_RESOLV` before the check and adding the |
| 197 | + prerequisite headers to the test program. (#362, #297, issue #203) |
| 198 | + |
| 199 | +- **Lua detection rewrite**: Overhauled `configure.ac` Lua detection to |
| 200 | + use pkg-config where available, with manual fallback. Supports Lua |
| 201 | + 5.1-5.5, respects `LUA_CFLAGS`/`LUA_LIBS` environment variables, |
| 202 | + drops the stale `lua5.1` pkg-config name. (#264, #266, #327, |
| 203 | + issues #111) |
| 204 | +- **Lua 5.5 compatibility**: C API changes in Lua 5.5 (`lua_newstate` |
| 205 | + seed, `lua_pop` placement, writer function signature). (#267, #268, |
| 206 | + #328, issue #265) |
| 207 | +- **OpenSSL 3 EVP API**: Replaced deprecated `SHA1_Init/Update/Final` |
| 208 | + and `SHA256_Init/Update/Final` with the unified `EVP_MD_CTX` API. |
| 209 | + Drops `#ifdef HAVE_SHA256` guards. Compatible with OpenSSL 1.1.1 and |
| 210 | + 3.x. (#351, closes #162) |
| 211 | +- **OpenSSL version check**: Version comparison now ignores patch letter |
| 212 | + and status suffix, fixing false negatives on patch releases like |
| 213 | + `1.1.1n`. (#307, issue #178) |
| 214 | +- **`libssl` detection**: Fixed configure test for OpenSSL 1.1.0+. (#317) |
| 215 | +- **Autoconf minimum version**: Relaxed from 2.71 to 2.69, restoring |
| 216 | + support for Ubuntu 20.04, Debian 11, and RHEL 9 development |
| 217 | + environments. (#306, issue #177) |
| 218 | +- **`--with-milter` explicit path**: When an explicit path was given, |
| 219 | + configure still ran a link test that could override it. (#336, |
| 220 | + issue #85) |
| 221 | +- **`--with-libxml2` removed from INSTALL**: The flag was never |
| 222 | + implemented. (#335, issue #91) |
| 223 | +- **`--with-ldns` removed**: The ldns backend was removed years ago; |
| 224 | + the configure flag was stale. (#311, issue #169) |
| 225 | +- **`smfi_insheader` minimum libmilter version**: Removed backwards |
| 226 | + compat stub for sendmail 8.12. (#333) |
| 227 | +- **K&R function prototype**: `dkimf_base64_encode_file` was using |
| 228 | + pre-ANSI K&R declaration style, removed in C23. (#261) |
| 229 | +- **SASL/LDAP pkg-config**: Improved detection. (#192) |
| 230 | +- **Incompatible pointer type warnings**: Fixed in several places. (#214) |
| 231 | + |
| 232 | +--- |
| 233 | + |
| 234 | +## ed25519 support |
| 235 | + |
| 236 | +- **GnuTLS ed25519 verification**: libopendkim now verifies |
| 237 | + ed25519-sha256 signatures when built with GnuTLS. (#282) |
| 238 | +- **ed25519 key comparison fix**: `dkim_test_key()` used `i2d_PUBKEY_bio` |
| 239 | + output (which includes a 12-byte ASN.1 prefix) to compare against the |
| 240 | + raw ed25519 key from DNS. Fixed to use |
| 241 | + `EVP_PKEY_get_raw_public_key()`. (#299, closes #245) |
| 242 | +- **ed25519 test coverage**: Added unit tests for ed25519 signing and |
| 243 | + verification, multi-signing, and key bit count. (#321, #326) |
| 244 | +- **`dkim_sig_keybits()` for ed25519**: Returned 0; now returns 256. (#321) |
| 245 | + |
| 246 | +--- |
| 247 | + |
| 248 | +## systemd / deployment |
| 249 | + |
| 250 | +- **`network-online.target`**: Service unit now waits for |
| 251 | + `network-online.target` instead of `network.target`, ensuring |
| 252 | + interfaces have addresses before opendkim starts. (#315, issue #141) |
| 253 | +- **`Type=notify`**: With the new `sd_notify()` support, the service |
| 254 | + unit switches from `Type=simple` to `Type=notify`, eliminating the |
| 255 | + startup race condition. (#316, #352) |
| 256 | +- **Hardening directives**: Added `CapabilityBoundingSet`, |
| 257 | + `ProtectSystem=strict`, `PrivateUsers`, `RestrictAddressFamilies`, |
| 258 | + `SystemCallFilter`, `NoExecPaths`/`ExecPaths`, and others. (#316) |
| 259 | +- **`Restart=on-abnormal`**: Service now restarts on unexpected exit. (#316) |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +## Documentation |
| 264 | + |
| 265 | +- **`opendkim.conf(5)` case-insensitivity**: Documented that config |
| 266 | + parameter names are case-insensitive. (#289) |
| 267 | +- **`SignHeaders` documentation**: Clarified semantics, added examples. (#292) |
| 268 | +- **IPv6 `PeerList` inline limitation**: Documented workaround using a |
| 269 | + file for IPv6 CIDR entries. (#318, issue #155) |
| 270 | +- **`SignatureAlgorithm` required for ed25519**: Documented that |
| 271 | + `SignatureAlgorithm` must be explicitly set when using ed25519 keys. |
| 272 | + (#300, issue #107) |
| 273 | +- **Dual-algorithm signing example**: Added KeyTable example and caveats |
| 274 | + for signing with both RSA and ed25519. (#270) |
| 275 | +- **`HowToRelease` modernized**: Updated for GitHub workflow, consistent |
| 276 | + release asset naming. (#338) |
| 277 | +- **Spelling fixes**: Various typos in docs and code comments. (#347) |
| 278 | + |
| 279 | +--- |
| 280 | + |
| 281 | +## Code quality and cleanup |
| 282 | + |
| 283 | +- **`SingleAuthResult` removed**: Deprecated config option removed. (#258) |
| 284 | +- **`conf_refcnt` assert removed**: Defensive assert that fired |
| 285 | + spuriously during normal operation. (#294) |
| 286 | +- **Duplicate block comment marker**: Removed stale `/*` in |
| 287 | + libopendkim. (#293) |
| 288 | +- **`stdio.h` includes**: Added missing includes in libopendkim/util.c |
| 289 | + and libvbr/vbr.c. (#320) |
| 290 | +- **Python 3 Twisted**: Replaced deprecated `defer.returnValue()` with |
| 291 | + plain `return` in contrib/repute. (#237) |
| 292 | + |
| 293 | +--- |
| 294 | + |
| 295 | +## CI and testing |
| 296 | + |
| 297 | +- **GitHub Actions**: Added Linux CI workflow (Ubuntu, OpenSSL 3, Lua |
| 298 | + 5.4) running on push and PR to `develop`. (#332) |
| 299 | +- **`workflow_dispatch`**: Added manual trigger button in Actions UI. (#355) |
| 300 | +- **Parallel test ordering**: Fixed `make -j check` failures due to |
| 301 | + test ordering dependencies. (#296) |
| 302 | +- **Test socket path**: Tests now use `./testkeys` instead of |
| 303 | + `/tmp/testkeys` (CVE-2020-35766 hardening). (#288) |
| 304 | +- **Multi-signing tests**: Added `t-test204` and `t-test205` covering |
| 305 | + multiple simultaneous signatures. (#326) |
| 306 | + |
| 307 | +--- |
| 308 | + |
| 309 | +## Code quality and cleanup (continued) |
| 310 | + |
| 311 | +- **Compiler warning cleanup** (`-Wincompatible-pointer-types`, |
| 312 | + `-Wformat-truncation`): `entry_name` in the `-V` output path declared |
| 313 | + `const char *` to match `dkim_nametable_first`/`next` signatures; |
| 314 | + `snprintf` call sites in `dkim-util.c`, `opendkim.c`, and |
| 315 | + `opendkim-genzone.c` given explicit `%.*s` width bounds or larger |
| 316 | + buffers. (#360, issue #359) |
| 317 | + |
| 318 | +--- |
| 319 | + |
| 320 | +## Still open / needs-testing |
| 321 | + |
| 322 | +- **#337**: Remove `__P()` macro (musl/Alpine portability) - written by |
| 323 | + thegushi as a prerequisite for musl CI coverage. Needs a build test on |
| 324 | + a musl-based system (Alpine Linux). orlitzky has been asked to confirm; |
| 325 | + was out of town, expects to test within a day or two. |
| 326 | + |
| 327 | +- **#151**: Transparent `strlcpy`/`strlcat` via libbsd-overlay - proposed |
| 328 | + by guijan, who build-tested on Alpine Linux and OpenBSD. On hold: making |
| 329 | + libbsd (or a compatible library) a mandatory dependency on non-BSD |
| 330 | + platforms needs broader team discussion before we can accept it. |
| 331 | + |
| 332 | +- **#149**: libunbound UDP socket accumulation under sustained load - |
| 333 | + reported by multiple RHEL 9 / AlmaLinux 9 users (conathan, KIC-8462852, |
| 334 | + sfsumn, juresaht2); pattern is sockets growing unboundedly, requiring |
| 335 | + twice-daily restarts. Reproducer running on AWS EC2 AlmaLinux 9 with |
| 336 | + real friends-and-family mail traffic; no accumulation seen after 3+ hours. May be volume- or configuration-dependent. |
| 337 | + |
| 338 | +- **#354**: Remove or modernize REPUTE/reprrd PHP contrib code - the PHP |
| 339 | + files in `contrib/repute/` and `reprrd/` use the `mysql_*` extension |
| 340 | + removed in PHP 7.0 (2015) and are not functional on any modern PHP |
| 341 | + version. Pending release announcement to surface any remaining users |
| 342 | + before removal. |
0 commit comments