add optional YYYY/MM archive partitioning via control/archivepartition
Large mailing lists accumulate thousands of files in a single archive/
directory, degrading filesystem performance. When control/archivepartition
exists, new archives are written to archive/YYYY/MM/N and mlmmj-maintd
automatically migrates existing flat archives based on file mtime.
Reading always tries flat layout first then falls back to scanning
subdirectories, so archives work regardless of layout.
generate RFC 2919/2369 List-* and Precedence headers natively
Mailing list messages without List-Id/Precedence headers cause vacation
autoreplies to trigger bounces leading to unsubscriptions, prevent mail
clients like Delta Chat from detecting list messages, and hurt
deliverability with major providers.
Generate List-Id, List-Post, List-Help, List-Subscribe, List-Unsubscribe,
and Precedence headers by default in do_all_the_voodoo_here(). List-Owner
is included only when control/owner exists. All headers can be disabled
via control/nolistheaders.
mlmmj-make-ml: use full email address for default owner
The default owner "postmaster" lacks a domain part, causing
mlmmj-send to reject it with "No @ in address". Use the FQDN
already collected earlier in the script to build a proper
postmaster@FQDN default.
send_mail: add X-Forwarded-To and X-Signed-Recipient headers
Add per-recipient headers to improve deliverability and support DARA
(draft ARC replay-resistant authentication):
- X-Forwarded-To: helps Gmail recognize legitimate forwarding
- X-Signed-Recipient: used in ARC signatures to prove the message
was intended for a specific recipient
Both are enabled independently via control files (xforwardedto and dara).
Like addtohdr, these are incompatible with VERP since they require
per-recipient header injection.
Wulf Coulmann [Fri, 6 Mar 2026 08:40:23 +0000 (09:40 +0100)]
Update exim4 configuration hints
- add debug output to router and transport
- lookup changes according exim4 >= 4.94 taind behaver (Errors like "Tainted filename for search")
- add router condition for setups with more than one domain (you need uniq local_parts over all your lists, but we do not want to match the transport on local_part@wrong.domain)
hint
- VERP config is not changed/updated, may not work
mlmmj-process: clean up queue file on second do_all_the_voodoo_here failure
The owner-forwarding path re-opens donemailname with O_TRUNC and calls
do_all_the_voodoo_here a second time. If this call fails, the truncated
queue file is left behind. Add unlink(donemailname) and free(donemailname)
to match the cleanup already done at the first call site.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use close_text() instead of free() when cleaning up txt in error paths.
The txt parameter is a fully initialized text structure from open_text(),
so it needs proper cleanup via close_text() to free all internal resources.
Also add missing close_text() call when prepstdreply_to() fails.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
do_all_the_voodoo_here: fix memory leaks and NULL dereference
Fix two pre-existing issues:
1. Memory leak: unfolded variable from tll_pop_front() was never freed
in the header processing loop. Add free(unfolded) at loop end and
before continue statements.
2. NULL dereference: posteraddr can be NULL if From: header is missing
or malformed. Pass empty string to process_headers_fd() when NULL
to avoid undefined behavior in xasprintf().
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
do_all_the_voodoo_here: fix FILE* leaks in error paths
Four error paths return without closing the FILE* f:
- process_headers_fd() failure in MIME header case
- process_headers_fd() failure in !hdrsadded case
- dprintf() failure when writing header terminator
- dumpfd2fd() failure when dumping mail body
Add fclose(f) before each return -1.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
listcontrol: fix resource leaks in CTRL_GET error path
When send_single_mail() fails, the code returns without freeing
bounceaddr, archivefilename, or closing mail.fp. Also add cleanup on
the success path which is missing the same resources.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix missing fdopen() NULL checks across multiple files
If fdopen() fails (e.g., due to memory pressure), passing NULL to
subsequent stdio operations (getline, fclose, scan_headers, etc.)
causes undefined behavior, typically segmentation faults.
Add NULL checks after fdopen() calls in multiple files:
subscriberfuncs.c:
- find_subscriber(): return false on fdopen failure
- autosubscribe_sender(): return early on fdopen failure
mlmmj-send.c:
- send_mail_many_fd(): return -1 on fdopen failure
- main(): exit with failure on fdopen failure
mlmmj-process.c:
- is_moderator(): exit with failure on fdopen failure
listcontrol.c:
- listcontrol(): return -1 on fdopen failure, freeing resources
prepstdreply.c:
- init_file_lines_fd(): return NULL on fdopen failure
- get_msgid_line(): return id on fdopen failure (graceful degradation)
- open_text_fd(): return NULL on fdopen failure
do_all_the_voodoo_here.c:
- do_all_the_voodoo_here(): return -1 on fdopen or dup failure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The dsnparseaddr() function has three resource leaks:
1. The file handle 'f' is never closed after parsing the DSN mail
2. The 'boundary' string is allocated but never freed
3. The 'emails' list is populated but never freed on exit
Free all three resources before all return paths.
Fixes: a1f1fbc8 ("mlmmj-bounce: make sure mlmmj-bounce is never called directly") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mlmmj: fix multiple resource leaks in send_probe()
The send_probe() function has several resource leaks:
1. bfd (bounce directory file descriptor) is opened but never closed
2. fd (probe file descriptor) is opened but never closed on success
3. mail.fp is not closed on send_single_mail() failure path
4. queuefilename is allocated by prepstdreply() but never freed
5. myaddr and from are not freed on send_single_mail() failure path
Clean up all resources properly on both success and failure
paths.
Fixes: 958d3143 ("probes: fix a regression causing the probes not to sent the bounce numbers") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
do_all_the_voodoo_here: fix memory leak of fromemails and posteraddr
The function has two related memory leaks:
1. fromemails strlist is populated by find_email_adr() but never freed.
The list goes out of scope when the if block ends, but the allocated
strings remain unreachable.
2. posteraddr is set to tll_front(fromemails), which is just a pointer
to the first element. This creates a dependency on the leaked list.
Fix:
- Copy the string using xstrdup() so posteraddr owns its memory
- Add tll_length() check before accessing tll_front() to avoid NULL pointer dereference (can occur with empty or malformed From header)
- Free the fromemails list immediately after extracting the value
- Free posteraddr on all return paths (error and success)
Fixes: 59181abe ("customheaders allow substitution on variable $posteraddr$") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
prepstdreply: fix resource leaks on prepstdreply_to() failure
When prepstdreply_to() returns false, the prepstdreply() function
returns NULL without:
1. Freeing the allocated retstr (queue filename)
2. Closing the opened outfd file descriptor
Add the cleanup.
Fixes: 660caa29 ("prepstdreply: split the function to make it more testable") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The verp variable is allocated via ctrlvalue() or xstrdup().
We free it in posfix case before it's assigned but we
forgot to free it when it's assigned NULL, so it will leak.
Free verp each time before it's assigned NULL.
Fixes: 4e53dbc8 ("VERP support") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mlmmj-process: move owner file open inside conditional
The openat() call for the "owner" control file is executed
unconditionally for all recipextra values, but it's only needed
when recipextra equals "owner". Move the openat() and xasprintf()
calls inside the conditional block where they are actually used.
Fixes: ac9e3eef ("mlmmj-process: pass the owner via file descriptor to mlmmj-send") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mlmmj-send: only unlink subfilename when it was a real path
When resending failed mails (case '3'), subfilename can be either:
- A numeric string like "5" representing an inherited file descriptor
- An actual file path
The strtoim() call sets errp=NULL when subfilename parses as a number,
and errp!=NULL when it's a path that needs to be opened.
Only call unlink() when errp!=NULL, i.e., when we opened a real file.
Otherwise unlink("5") would try to delete a file literally named "5"
in the current directory.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mlmmj-process: clean up queue file on do_all_the_voodoo_here failure
When do_all_the_voodoo_here() fails, the partially written queue file
is left behind. Add unlink() to match the cleanup done in the earlier
rawmailfd open failure path.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When send_mail() fails for the only (or last) recipient, the address
gets popped into the 'addr' parameter, leaving 'addrs' empty. The
early return was checking only if addrs was empty, ignoring addr.
Effect: when sending to a single recipient fails, that recipient is
not saved to the requeue file and is lost entirely. The return value
of 0 (false) propagates back as "success", causing the caller to
delete the queue file as if sending succeeded.
Fix by only returning early when there is nothing to save (both addrs
empty AND addr NULL). This ensures failed recipients are properly
requeued for retry.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
listcontrol: replace __attribute__((fallthrough)) with comment
The __attribute__((fallthrough)) statement was added in GCC 7. Older
versions treat it as an empty declaration and emit a warning. Use a
/* fallthrough */ comment instead, which GCC (-Wimplicit-fallthrough)
and Clang both recognise as an intentional fallthrough annotation.
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org> Assisted-by: claude-opus-4-6
Identifiers with double-underscore prefixes are reserved for the C
implementation. Older glibc headers (bits/stat.h) use __unused as a
struct field name, so the mlmmj macro expansion produces invalid syntax
when that header is included transitively via fcntl.h.
Rename to MLMMJ_UNUSED to avoid the conflict and stay out of the
reserved namespace.
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org> Assisted-by: claude-opus-4-6
mlmmj-process: fix NULL deref in owner mail reprocessing path
When mail is addressed to listname+owner, mlmmj-process calls
do_all_the_voodoo_here() a second time to strip envelope headers
before forwarding to the list owner. This second call reuses the
same allheaders list that was already populated by the first call.
Inside do_all_the_voodoo_here(), scan_headers() appends the new
mail's headers to the passed-in allhdrs list, while allunfoldeds
is a fresh local list containing only the headers from the current
scan. The main loop then iterates all entries in allhdrs (which now
includes headers from both calls) while popping from allunfoldeds
(which only has entries from the second scan). Once the unfolded
entries are exhausted, tll_pop_front() on the empty list dereferences
NULL and crashes.
Fix this by passing a separate local strlist to the second call so
that allhdrs and allunfoldeds stay in sync.
Signed-off-by: Konstantin Ryabitsev <konstantin@linuxfoundation.org> Assisted-by: claude-opus-4-6
Set MALLOC_CHECK_=3 in test environment to detect memory corruption
issues like double-free and buffer overflows. When detected, glibc
will print a diagnostic and abort the program.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>