]> git.ipfire.org Git - thirdparty/dovecot/core.git/log
thirdparty/dovecot/core.git
3 weeks agoplugins/welcome: Move settings to welcome-settings.c
alexandre.roux [Thu, 11 Dec 2025 10:30:12 +0000 (19:30 +0900)] 
plugins/welcome: Move settings to welcome-settings.c

3 weeks agoanvil: admin-client - Log sent admin commands at debug level
Timo Sirainen [Thu, 9 Jul 2026 12:54:24 +0000 (12:54 +0000)] 
anvil: admin-client - Log sent admin commands at debug level

3 weeks agoanvil: admin-client - Fix busy loop on disconnected admin socket
Timo Sirainen [Thu, 9 Jul 2026 11:51:02 +0000 (11:51 +0000)] 
anvil: admin-client - Fix busy loop on disconnected admin socket

The connection destroy callback failed pending commands but never removed
the connection's io. The connection framework's connection_closed() calls
v.destroy() expecting the connection to be torn down, but the actual
io_remove() happens only in connection_deinit() via admin_client_unref()
when refcount drops to 0. Since the pool or kick target still holds a
reference, connection_deinit() was never reached.

The io therefore stayed registered on the dead fd and kept firing, calling
destroy repeatedly for the same connection and spinning anvil at ~90% CPU.

Disconnect the connection in the destroy callback so the io is removed and
conn.disconnected is set, matching admin_client_send_cmd()'s reconnect check.

3 weeks agoimap: imap-state - Add unit tests for parser helpers
Timo Sirainen [Thu, 2 Jul 2026 12:01:49 +0000 (12:01 +0000)] 
imap: imap-state - Add unit tests for parser helpers

Test the seq-range/string import helpers (round-trip, truncation,
overflow rejection) and the compress/enabled-feature/searchres
state-block import handlers.

3 weeks agoimap: imap-state - Expose parser helpers via imap-state-private.h
Timo Sirainen [Thu, 2 Jul 2026 12:01:37 +0000 (12:01 +0000)] 
imap: imap-state - Expose parser helpers via imap-state-private.h

Add imap-state-private.h that declares the seq-range, string and
state-block import/export helpers, and make them non-static so they can
be unit tested. Rename all exposed functions to use the imap_state_
prefix for consistency.

3 weeks agoimap: imap-state - Add assert to import_string()
Timo Sirainen [Sat, 16 May 2026 11:52:55 +0000 (11:52 +0000)] 
imap: imap-state - Add assert to import_string()

3 weeks agoimap: imap-state - Reject overflowing UID seq-range on import()
Timo Sirainen [Sat, 16 May 2026 11:52:38 +0000 (11:52 +0000)] 
imap: imap-state - Reject overflowing UID seq-range on import()

3 weeks agoimap: imap-state - Reject duplicate SEARCHRES state block
Timo Sirainen [Sat, 16 May 2026 11:52:23 +0000 (11:52 +0000)] 
imap: imap-state - Reject duplicate SEARCHRES state block

Avoids a memory leak in case of corrupted state containing duplicate
SEARCHRES block.

3 weeks agoimap: imap-state - Change import()s to return enum imap_state_result and move skip_r...
Timo Sirainen [Sat, 16 May 2026 11:51:59 +0000 (11:51 +0000)] 
imap: imap-state - Change import()s to return enum imap_state_result and move skip_r to parameter

This makes the error handling clearer.

3 weeks agoimap: imap-state - import_state_mailbox_open() - Fix caller's error handling
Timo Sirainen [Sun, 17 May 2026 01:12:06 +0000 (01:12 +0000)] 
imap: imap-state - import_state_mailbox_open() - Fix caller's error handling

3 weeks agoimap: imap-state - Centralize IMAP_STATE_INCONSISTENT handling
Timo Sirainen [Sun, 17 May 2026 01:53:37 +0000 (01:53 +0000)] 
imap: imap-state - Centralize IMAP_STATE_INCONSISTENT handling

3 weeks agoimap: imap-state - Close master client connection if unhibernated mailbox is deleted
Timo Sirainen [Sun, 17 May 2026 01:58:16 +0000 (01:58 +0000)] 
imap: imap-state - Close master client connection if unhibernated mailbox is deleted

3 weeks agolib-sasl: Improve fuzz-sasl-authentication.dict
Aki Tuomi [Thu, 9 Jul 2026 05:42:54 +0000 (05:42 +0000)] 
lib-sasl: Improve fuzz-sasl-authentication.dict

3 weeks agolib-http: Add fuzzer dicts for new fuzzers
Aki Tuomi [Thu, 9 Jul 2026 05:22:10 +0000 (05:22 +0000)] 
lib-http: Add fuzzer dicts for new fuzzers

3 weeks agologin-common: Fix incorrect variable assignment in real_remote_ip
Manuel Mausz [Thu, 2 Jul 2026 13:28:07 +0000 (13:28 +0000)] 
login-common: Fix incorrect variable assignment in real_remote_ip

Correct real_remote_ip to look up client->real_remote_ip.

Signed-off-by: Michael M Slusarz <slusarz@open-xchange.com>
3 weeks agologin-common: Improve logging for proxy connection failures
Michael M Slusarz [Fri, 15 May 2026 03:26:01 +0000 (21:26 -0600)] 
login-common: Improve logging for proxy connection failures

When a proxy connection to a destination fails, the Info log
line stating this now includes the destination to which the
connection failed.

3 weeks agodoveadm: Use STREAM multiplex format at protocol v1.4
Timo Sirainen [Wed, 8 Jul 2026 07:12:39 +0000 (07:12 +0000)] 
doveadm: Use STREAM multiplex format at protocol v1.4

The doveadm server->client output used the deprecated PACKET multiplex
format. Bump the protocol to v1.4 and use the STREAM format when the client
is new enough to understand it (minor version >=
DOVEADM_PROTOCOL_MIN_VERSION_MULTIPLEX_STREAM); older clients still get
PACKET. The client istream side auto-detects the format, so no client change
is needed beyond advertising the newer version.

3 weeks agoanvil: Use STREAM multiplex format instead of the deprecated PACKET
Timo Sirainen [Wed, 8 Jul 2026 07:12:39 +0000 (07:12 +0000)] 
anvil: Use STREAM multiplex format instead of the deprecated PACKET

Both ends of an anvil connection are internal Dovecot processes from the
same build, so the format can be switched without protocol negotiation (the
istream side auto-detects the format anyway). Move off the deprecated PACKET
format to STREAM.

3 weeks agolib: ostream-multiplex - Mark the PACKET format deprecated
Timo Sirainen [Wed, 8 Jul 2026 07:12:39 +0000 (07:12 +0000)] 
lib: ostream-multiplex - Mark the PACKET format deprecated

The original packet-based framing requires the parent ostream to buffer a
whole packet and cannot stream to a parent with max_buffer_size==0, so it
does not work with senders that stream directly to the socket. New code
should use the STREAM format; PACKET is kept only for backwards
compatibility and is being phased out.

3 weeks agolib: ostream-multiplex - Apply backpressure in the STREAM format
Timo Sirainen [Wed, 8 Jul 2026 09:53:37 +0000 (09:53 +0000)] 
lib: ostream-multiplex - Apply backpressure in the STREAM format

o_stream_multiplex_ochannel_sendv() always bumped the available space up
to IO_BLOCK_SIZE when the channel buffer was small, to batch tiny writes.
This ignored whether the parent could actually accept the data. A parent
with max_buffer_size==0 (as imap-fetch sets while streaming a body
straight to the socket) always reports avail==0, so the bump made the
channel keep accepting data unconditionally. Since o_stream_multiplex_sendv()
no longer gates the STREAM format on parent space, the data was buffered
into the parent without bound and writev() was retried on the full socket
in a tight loop - o_stream_send_istream() never got a 0 return to make it
stop and wait for the socket to become writable.

Only apply the IO_BLOCK_SIZE floor while the parent has no un-flushed
backlog, i.e. the socket is keeping up. Once the parent has buffered data
the socket is congested and the real avail==0 is honored, so the channel
sendv returns 0 and backpressure propagates to the caller.

3 weeks agolib: ostream-multiplex - Don't gate STREAM flushing on a max_buffer_size=0 parent
Timo Sirainen [Wed, 8 Jul 2026 06:37:03 +0000 (06:37 +0000)] 
lib: ostream-multiplex - Don't gate STREAM flushing on a max_buffer_size=0 parent

o_stream_multiplex_sendv() skipped sending a channel's buffered data unless
the parent stream reported at least 6 bytes of available space (room for a
frame header). A parent with max_buffer_size==0 always reports
get_buffer_avail_size()==0, so this gate blocks every send to such a parent.

The gate is only correct for the PACKET format, which writes a whole header
and its data with a single sendv and can't cope with a partial parent write.
The STREAM format sends incrementally through pending_buf and already handles
partial parent writes, so it must attempt the send regardless; a full or
streaming parent then applies backpressure naturally through short writes.

imap-fetch sets the output's max_buffer_size to 0 around the send_istream()
that streams a body, so that it goes straight to the socket instead of being
buffered. When the output is an ostream-multiplex channel - as with the
imap-login proxy that multiplexes the backend connection - that 0 reaches the
multiplex parent and the gate stops STREAM from writing through it while it is
set. Under the login-proxy relay this wedged the connection (observed as
either a CPU busy-loop or an idle stall) and the client timed out.

Apply the available-space gate only for the PACKET format. Also assert that a
gated PACKET parent can actually buffer a header, so an unsupported
max_buffer_size==0 PACKET parent fails loudly instead of hanging.

3 weeks agolib-auth-client: Use master_service_ioloop_run_interruptible()
Timo Sirainen [Wed, 10 Jun 2026 22:27:09 +0000 (22:27 +0000)] 
lib-auth-client: Use master_service_ioloop_run_interruptible()

auth_master_request_wait() and auth_master_wait() both hand-rolled the same
kill-poll pattern: a 100ms timeout whose handler checked
master_service_is_killed() and stopped the ioloop. Replace both with
master_service_ioloop_run_interruptible().

3 weeks agolib-master: Add master_service_ioloop_run_interruptible()
Timo Sirainen [Wed, 10 Jun 2026 22:06:32 +0000 (22:06 +0000)] 
lib-master: Add master_service_ioloop_run_interruptible()

This is a helper that runs a nested ioloop while polling
master_service_is_killed(), stopping the ioloop when the process is killed.

3 weeks agolib-http: Add http response parser fuzzer
Timo Sirainen [Sun, 5 Jul 2026 06:34:45 +0000 (06:34 +0000)] 
lib-http: Add http response parser fuzzer

3 weeks agolib-http: Add http request parser fuzzer
Arthur Chan [Tue, 9 Jun 2026 15:32:31 +0000 (16:32 +0100)] 
lib-http: Add http request parser fuzzer

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
3 weeks agoauth: Pass resolved credentials scheme via lookup_credentials callback
Marco Bettini [Tue, 7 Jul 2026 10:24:30 +0000 (10:24 +0000)] 
auth: Pass resolved credentials scheme via lookup_credentials callback

passdb_get_credentials() mutated request->wanted_credentials_scheme to
report the resolved scheme, letting it leak into the next passdb
during a multi-passdb continue lookup and wrongly reject a different
scheme there as SCHEME_NOT_AVAILABLE. Pass the resolved scheme as a
callback parameter instead of mutating shared request state.
wanted_credentials_scheme stays immutable.

3 weeks agoauth: Add scheme parameter to lookup_credentials_callback_t
Marco Bettini [Tue, 7 Jul 2026 10:20:31 +0000 (10:20 +0000)] 
auth: Add scheme parameter to lookup_credentials_callback_t

3 weeks agolib-var-expand: Add iso8601 filter
Timo Sirainen [Sun, 28 Jun 2026 11:56:08 +0000 (11:56 +0000)] 
lib-var-expand: Add iso8601 filter

Add an iso8601 filter that formats a UNIX timestamp as an ISO 8601 /
RFC 3339 date-time string, e.g. 2025-06-08T10:40:00Z. The tz parameter
selects the timezone: utc (alias gmt, the default), which uses the 'Z'
suffix, or local, which uses a +HH:MM / -HH:MM offset. This produces
RFC 3339 compliant output, which the strftime-based date filter cannot
(strftime's %z lacks the colon and cannot emit 'Z').

Reuses iso8601_date_create_tm() and utc_offset() from lib.

3 weeks agolib-var-expand: Add date filter
Timo Sirainen [Sun, 28 Jun 2026 08:24:49 +0000 (08:24 +0000)] 
lib-var-expand: Add date filter

Add a date filter that formats a UNIX timestamp using strftime(3). The
input may be a plain seconds value or the "<seconds>.<nanoseconds>" form
from the time:unix provider; any fractional part is ignored. The tz
parameter selects the timezone used to break the timestamp into calendar
fields: utc (alias gmt, the default) or local.

3 weeks agolib-var-expand: Add from_epoch filter
Timo Sirainen [Sun, 28 Jun 2026 08:23:45 +0000 (08:23 +0000)] 
lib-var-expand: Add from_epoch filter

Add a from_epoch filter, the inverse of epoch: it converts an integer
UNIX timestamp expressed in s/ms/us/ns into the canonical
"<seconds>.<nanoseconds>" form. This is useful for feeding millisecond
or nanosecond timestamps from external input into the date filter.

3 weeks agolib-var-expand: Add epoch filter
Timo Sirainen [Sun, 28 Jun 2026 08:22:56 +0000 (08:22 +0000)] 
lib-var-expand: Add epoch filter

Add an epoch filter that converts a UNIX timestamp into an integer in
the requested unit (s, ms, us or ns). The input may be a plain seconds
value or the "<seconds>.<nanoseconds>" form returned by the time:unix
provider. The output never contains a decimal point.

3 weeks agolib-var-expand: Add time:unix provider
Timo Sirainen [Sun, 28 Jun 2026 08:21:40 +0000 (08:21 +0000)] 
lib-var-expand: Add time:unix provider

Add a new %{time:unix} provider that returns the current time as a
"<seconds>.<nanoseconds>" UNIX timestamp. This is intended as the input
for the epoch and date filters.

3 weeks agolast-login: Add support for updating multiple dictionary fields
Michael M Slusarz [Tue, 9 Jun 2026 14:33:32 +0000 (08:33 -0600)] 
last-login: Add support for updating multiple dictionary fields

Extend the last-login plugin to support a new 'last_login_dict_fields'
setting. This allows users to specify a list of key/value pairs (with
variable expansion support) to be updated during the login process.

3 weeks agolast-login: Move timestamp generation to static helper function
Michael M Slusarz [Mon, 6 Jul 2026 16:44:43 +0000 (10:44 -0600)] 
last-login: Move timestamp generation to static helper function

3 weeks agolib-dict-extra: Increase dict client request timeout to 65s
Timo Sirainen [Mon, 6 Jul 2026 21:14:06 +0000 (21:14 +0000)] 
lib-dict-extra: Increase dict client request timeout to 65s

The dict client request timeout was 30 seconds, which is lower than the
backend SQL/Cassandra request timeout default of 60 seconds
(SQL_QUERY_TIMEOUT_SECS). A slow backend query would therefore hit the
dict client timeout first, logging a "Dict server timeout" warning and
forcing a reconnect, instead of letting the backend abort the query with
a proper error.

Raise the timeout to 65 seconds so it sits above the backend timeouts.

3 weeks agolib-storage: Fix crash when reverting a failed mailbox deletion
Timo Sirainen [Fri, 3 Jul 2026 13:04:53 +0000 (13:04 +0000)] 
lib-storage: Fix crash when reverting a failed mailbox deletion

If a backend's delete_box fails after the mailbox was marked deleted,
mailbox_delete_real() reverts the mark by calling mailbox_mark_index_deleted()
which reopens the mailbox. That open can fail before the index is opened,
leaving box->index NULL, and mailbox_try_undelete() then dereferenced it in
mail_index_get_modification_time(), crashing.

Return FALSE from mailbox_try_undelete() when box->index is NULL - there is
nothing to undelete.

3 weeks agofts: Don't run FTS optimize on forced resync
Timo Sirainen [Fri, 3 Jul 2026 11:33:31 +0000 (11:33 +0000)] 
fts: Don't run FTS optimize on forced resync

This was an obsolete design decision, which is only making force-resyncs
slower and can cause them to fail. FTS optimization has nothing to do with
index rebuilds.

3 weeks agolib-storage: Fix NFC subscription rewrite for mUTF7 storage names
Timo Sirainen [Mon, 6 Jul 2026 16:19:16 +0000 (16:19 +0000)] 
lib-storage: Fix NFC subscription rewrite for mUTF7 storage names

mailbox_list_subscriptions_refresh() normalized the raw subscription file
entry, which is a storage name (e.g. mUTF7). With mUTF7 the non-NFC
characters are hidden inside the encoding and the storage name looks like
plain ASCII, so uni_utf8_to_nfc() was a no-op and the subscription was
never rewritten to NFC form.

The mailbox directory itself does get renamed to NFC during listing (that
code normalizes the decoded vname), leaving the subscription pointing at a
non-existent non-NFC name. This triggered the NFC rename fallback on every
iteration: repeated 'Failed to rename mailbox for NFC normalization:
Mailbox doesn't exist' errors and a bogus GUID-suffixed subscription entry.

Normalize the decoded vname instead and convert the result back to a
storage name, matching what mailbox listing does.

Forgotten in a80f875790bdce6dc59f3aca237b7d6833dcf8e9

4 weeks agolib-smtp: test-smtp-client-errors: Fix flaky connection refused test
Timo Sirainen [Thu, 2 Jul 2026 21:32:15 +0000 (21:32 +0000)] 
lib-smtp: test-smtp-client-errors: Fix flaky connection refused test

4 weeks agolib-smtp: test-smtp-submit: Fix flaky connection refused test
Timo Sirainen [Thu, 2 Jul 2026 21:32:15 +0000 (21:32 +0000)] 
lib-smtp: test-smtp-submit: Fix flaky connection refused test

4 weeks agolib-auth-client: test-auth-master: Fix flaky connection refused test
Timo Sirainen [Thu, 2 Jul 2026 21:32:15 +0000 (21:32 +0000)] 
lib-auth-client: test-auth-master: Fix flaky connection refused test

4 weeks agolib-auth-client: test-auth-client: Fix flaky connection refused test
Timo Sirainen [Thu, 2 Jul 2026 21:32:15 +0000 (21:32 +0000)] 
lib-auth-client: test-auth-client: Fix flaky connection refused test

4 weeks agolib-login: test-login-server-auth: Fix flaky connection refused test
Timo Sirainen [Thu, 2 Jul 2026 21:32:15 +0000 (21:32 +0000)] 
lib-login: test-login-server-auth: Fix flaky connection refused test

4 weeks agoimapc: Fix COPY checking expunged state of wrong mailbox
Timo Sirainen [Fri, 3 Jul 2026 16:01:08 +0000 (16:01 +0000)] 
imapc: Fix COPY checking expunged state of wrong mailbox

imapc_copy() checked imapc_is_mail_expunged() against the destination
mailbox (ctx->mbox) while passing a source UID. The check looks up the
UID in the given mailbox's delayed_expunged_uids / delayed_sync_trans,
i.e. in that mailbox's own UID space, so it must be given the source
mailbox (ctx->src_mbox).

Because of this, a source mail whose EXPUNGE had already been received
but not yet applied to the msgmap was not detected. The message map
still contained the UID, so the copy proceeded and a UID COPY was sent
for an already-expunged mail. The remote server then silently skipped
it (UID COPY ignores non-existent UIDs rather than failing), returning
fewer COPYUIDs than mails requested. That mismatch tripped the
all-or-nothing saved_uids assertion in
mailbox_transaction_commit_get_changes() and crashed imap. This showed
up as random failures in the obox-tests/imapc-shared-folder test.

Check the source mailbox so such mails are detected up front and the
copy is aborted with MAIL_ERROR_EXPUNGED before any UID COPY is sent.

4 weeks agolib-sql: Make sqlite busy_timeout configurable
Timo Sirainen [Thu, 2 Jul 2026 08:24:51 +0000 (08:24 +0000)] 
lib-sql: Make sqlite busy_timeout configurable

Add sqlite_busy_timeout setting (default 1s, unchanged from the previous
hardcoded value). Under WAL only one writer runs at a time, so concurrent
writers can still hit SQLITE_BUSY; a configurable timeout lets deployments
with slow storage widen the retry window.

4 weeks agolib-imap: imap-bodystructure - Don't read past args in message/rfc822 parsing
usama khan [Thu, 2 Jul 2026 15:27:58 +0000 (20:57 +0530)] 
lib-imap: imap-bodystructure - Don't read past args in message/rfc822 parsing

The message/rfc822 branch of imap_bodystructure_parse_args_int()
dereferences the child bodystructure at args[1] before it validates the
envelope at args[0]. A BODYSTRUCTURE truncated right after the size field,
e.g. ("message" "rfc822" NIL NIL NIL "7bit" 0), leaves args pointing at
the argument list's IMAP_ARG_EOL terminator, so reading args[1] indexes one
struct imap_arg past the end of the EOL-terminated array.

Bail out when args[0] is IMAP_ARG_EOL before indexing args[1], matching the
ordering already used by the sibling imap_parse_bodystructure_args(). This is
reachable via imapc from an upstream server's FETCH BODYSTRUCTURE reply.

4 weeks agolib-ldap: Improve request timeout error when connection setup fails
Timo Sirainen [Thu, 2 Jul 2026 15:00:03 +0000 (15:00 +0000)] 
lib-ldap: Improve request timeout error when connection setup fails

When an LDAP request times out while the connection has not yet reached a
usable (bound) state, it timed out while still connecting rather than while
waiting for a server reply. Report that, and when TLS is in use also point at
the certificate.

This matters for OpenLDAP built against GnuTLS: that backend does not report a
handshake or certificate verification failure back to lib-ldap at all, so such
a failure previously surfaced only as a generic "Aborting LDAP request after
timeout".

4 weeks agolib-ldap: Use a conn local variable in ldap_connection_abort_request()
Timo Sirainen [Thu, 2 Jul 2026 14:59:48 +0000 (14:59 +0000)] 
lib-ldap: Use a conn local variable in ldap_connection_abort_request()

Avoid repeating req->conn->. No functional change.

4 weeks agoCOPYING: Simplify and standardize copyright handling
Michael M Slusarz [Tue, 30 Jun 2026 21:00:58 +0000 (15:00 -0600)] 
COPYING: Simplify and standardize copyright handling

4 weeks agoAdd missing copyright headers
Michael M Slusarz [Mon, 29 Jun 2026 23:10:48 +0000 (17:10 -0600)] 
Add missing copyright headers

4 weeks agolib-smtp: smtp-server-recipient - Assert cmd is set in get_reply
Timo Sirainen [Fri, 26 Jun 2026 05:55:38 +0000 (05:55 +0000)] 
lib-smtp: smtp-server-recipient - Assert cmd is set in get_reply

smtp_server_recipient_get_reply() dereferences rcpt->cmd (which is NULLed
in _approved() and re-set by _data_command()) relying on the DATA
lifecycle invariant. The sibling _is_replied()/_replyv() helpers already
assert rcpt->cmd != NULL; add the same assertion here so the invariant is
explicit and a violation fails loudly rather than dereferencing NULL.

4 weeks agolib-imap: imap-parser - Make list_add_ghost_eol iterative
Timo Sirainen [Fri, 26 Jun 2026 05:54:18 +0000 (05:54 +0000)] 
lib-imap: imap-parser - Make list_add_ghost_eol iterative

list_add_ghost_eol() recursed once per list nesting level when finishing
a line that left an open list. Pre-auth parsers cap the list count at 1
so this is not pre-auth reachable, but convert it to an iterative walk
over the parent chain as defense in depth against C-stack growth from a
deeply nested list.

4 weeks agolib-otp: otp-parse - Make word-count invariant explicit
Timo Sirainen [Fri, 26 Jun 2026 05:53:46 +0000 (05:53 +0000)] 
lib-otp: otp-parse - Make word-count invariant explicit

otp_read_words() appends decoded words into a fixed buffer_t backed by
bits[OTP_WORDS_NUMBER]. The loop condition and the trailing add_word()
guard already keep the count within capacity, but assert it explicitly
in add_word() so any future change that lets count reach the capacity is
caught rather than silently relying on buffer_t's own overflow panic.

4 weeks agolib-oauth2: oauth2-jwt - Percent-encode bare '.'/'..' identifiers
Timo Sirainen [Fri, 26 Jun 2026 05:53:15 +0000 (05:53 +0000)] 
lib-oauth2: oauth2-jwt - Percent-encode bare '.'/'..' identifiers

escape_identifier() only escaped '/' and '%', so a bare '.' or '..' in
the attacker-controlled azp/kid passed through unchanged into the
shared/<azp>/<alg>/<kid> dict key. The shipped flat-file dict treats the
key literally, but a path-mapping dict backend could be made to traverse
(CVE-2021-29157 class). Percent-encode a whole-segment '.'/'..' so it
cannot act as a relative path component.

4 weeks agolib-oauth2: oauth2-jwt - Guard against empty JWT body segment
Timo Sirainen [Fri, 26 Jun 2026 05:52:33 +0000 (05:52 +0000)] 
lib-oauth2: oauth2-jwt - Guard against empty JWT body segment

The JWT header segment is rejected when it decodes to an empty buffer,
but the body segment was passed straight to oauth2_json_tree_build()
without the same check. Add the symmetric body->used == 0 guard so the
two segments are handled consistently. The 'empty body and signature'
case in test-oauth2-jwt already exercises this path.

4 weeks agoimap: imap-search - Detect overflow in SEARCH RETURN PARTIAL range
Timo Sirainen [Fri, 26 Jun 2026 05:51:07 +0000 (05:51 +0000)] 
imap: imap-search - Detect overflow in SEARCH RETURN PARTIAL range

imap_partial_range_parse() accumulated the partial range bounds into
uint32_t by hand with no overflow check, so a value such as
99999999999 wrapped to an unrelated number. The wrapped values are only
used as a result-index comparison window and echoed back, so this is a
correctness issue rather than a memory-safety one, but it should still
be rejected. Parse with str_parse_uint32(), which fails on overflow.

4 weeks agosubmission-login: submission-proxy - Handle multi-line backend AUTH reply
Timo Sirainen [Fri, 26 Jun 2026 05:50:10 +0000 (05:50 +0000)] 
submission-login: submission-proxy - Handle multi-line backend AUTH reply

In the SUBMISSION_PROXY_AUTHENTICATE state a non-334 reply line that is
not an invalid line ran i_assert(proxy_reply == NULL) and then created
proxy_reply, returning early (return 0) for a non-final line without
clearing it. proxy_reply is only reset on connection reset, so a backend
answering the proxied AUTH with a multi-line reply (e.g. a two-line
'535-...' / '535 ...' rejection) re-entered this state with proxy_reply
already set and tripped the assert, i_panic()ing the submission-login
process - a deterministic DoS triggerable by a malicious/compromised
backend.

Create proxy_reply only on the first line and append each subsequent
line's text to it.

4 weeks agosubmission-login: submission-proxy - Don't read past end of short reply
Timo Sirainen [Fri, 26 Jun 2026 05:49:57 +0000 (05:49 +0000)] 
submission-login: submission-proxy - Don't read past end of short reply

submission_proxy_parse_line() inspected line[3] (twice) before checking
the reply length. A backend reply line shorter than 4 bytes made these
reads run past the string's NUL terminator - a small out-of-bounds read
reachable from a malicious/compromised proxied backend.

Parse the 3-digit status code first with str_parse_uint() and use the
returned end pointer as the separator position, which is always within
the string (at worst the NUL terminator).

4 weeks agolib-mail: istream-binary-converter - Avoid unbounded recursion
Timo Sirainen [Fri, 26 Jun 2026 05:47:29 +0000 (05:47 +0000)] 
lib-mail: istream-binary-converter - Avoid unbounded recursion

i_stream_binary_converter_read() tail-recursed into itself whenever a
parsed block produced no stream output (new_size == old_size), e.g. a
header line buffered into hdr_buf without advancing istream.pos. A MIME
part with many short folded header lines could drive thousands of
recursion frames before the 32 kB cap flushes. Production -O2 usually
tail-call-optimizes this away, but -O0/-O1/sanitizer builds do not.

Move the per-block work into i_stream_binary_converter_read_block() and
drive it from a loop in i_stream_binary_converter_read(), so a block that
produces no output reads the next block instead of recursing.

4 weeks agolib-smtp: smtp-reply-parser - Bounds-check enhanced code input
Timo Sirainen [Fri, 26 Jun 2026 05:42:10 +0000 (05:42 +0000)] 
lib-smtp: smtp-reply-parser - Bounds-check enhanced code input

smtp_reply_parse_enhanced_code() read text[0] and text[1] before any
length check. For an empty string text[1] is one byte past the NUL
terminator, a 1-byte out-of-bounds read. The function is exported, so a
future caller could reach it with a short string even though the current
callers happen to guard the length themselves.

4 weeks agolib-mail: rfc822-parser - Decode punycode from non-NUL-terminated input
Timo Sirainen [Mon, 29 Jun 2026 08:36:31 +0000 (08:36 +0000)] 
lib-mail: rfc822-parser - Decode punycode from non-NUL-terminated input

rfc822_decode_punycode() took a const char * but already received a
length, yet still relied on NUL termination: strchr()/str_begins()
scanned for a NUL, forcing rfc822_parse_domain() to t_strndup() a
NUL-terminated copy of the label run first.

Take const unsigned char * and treat input as exactly len bytes: bound
the "xn--" prefix test by the label length instead of str_begins(). This
lets the caller pass str_data() directly and drop the t_strndup() copy.

4 weeks agolib-mail: rfc822-parser - Don't copy trailing NUL into decoded domain
Timo Sirainen [Mon, 29 Jun 2026 08:35:22 +0000 (08:35 +0000)] 
lib-mail: rfc822-parser - Don't copy trailing NUL into decoded domain

For a domain label with no trailing '.', rfc822_decode_punycode()
computed delim == end (the NUL terminator of the t_strndup()'d buffer)
and then copied delim - pos + 1 bytes, i.e. one byte too many, appending
a stray NUL to the decoded domain. This left a NUL inside addr.domain,
confusing strlen()-based consumers.

Copy only the label bytes (delim - pos) and append the '.' separator
explicitly, and bound the delimiter search with memchr() so it cannot
scan past the given length.

4 weeks agolib-mail: rfc822-parser - Remove unreachable post-loop append
Timo Sirainen [Mon, 29 Jun 2026 09:16:42 +0000 (09:16 +0000)] 
lib-mail: rfc822-parser - Remove unreachable post-loop append

The post-loop "if (pos < end)" append was dead code: the loop only exits
once pos has advanced past end (pos = delim + 1, with delim <= end), so
the condition is never true. Drop it; no behaviour change.

4 weeks agolib-mail: rfc822-parser - Remove unused decoded buffer
Timo Sirainen [Mon, 29 Jun 2026 09:16:34 +0000 (09:16 +0000)] 
lib-mail: rfc822-parser - Remove unused decoded buffer

The 'decoded' string buffer was allocated and truncated but never read
from. Drop it; no behaviour change.

4 weeks agolib: punycode - Fix meaningless bounds assertion
Timo Sirainen [Fri, 26 Jun 2026 05:39:57 +0000 (05:39 +0000)] 
lib: punycode - Fix meaningless bounds assertion

i_assert(out < sizeof(label)) compared an item count against the size of
the ARRAY() wrapper struct (~16 bytes), and 'out' is always 0 at this
point (it is only assigned from array_count() afterwards). The check was
effectively '0 < 16' and never meaningful.

Assert the actual invariant instead: the number of basic code points
appended never exceeds the input length.

4 weeks agolib: punycode - Reject invalid Unicode code points in decoder
Timo Sirainen [Fri, 26 Jun 2026 05:39:33 +0000 (05:39 +0000)] 
lib: punycode - Reject invalid Unicode code points in decoder

punycode_decode() reconstructs code points into an unsigned int and
feeds them to uni_ucs4_to_utf8(), which i_assert()s that each value is a
valid Unicode scalar. The decode loop only guarded against integer
wraparound and a lower bound, so a crafted label could decode to a
surrogate (0xD800..0xDFFF) or a value above U+10FFFF and trip the
assert, i_panic()ing the process.

With experimental mail UTF-8 enabled this path is reachable from
untrusted address domains (xn-- labels) via rfc822_decode_punycode(),
making it a remote content DoS.

Reject such code points with -1 before inserting them; callers already
treat a negative return as 'consider it as data', so it fails safe.

4 weeks agologin-common: Reject proxy credentials with ASCII control characters
Marco Bettini [Wed, 27 May 2026 09:11:53 +0000 (09:11 +0000)] 
login-common: Reject proxy credentials with ASCII control characters

POP3 sends proxy credentials unencoded in USER/PASS, where control
characters could inject protocol commands. Such characters are never
valid in credentials anyway, since PRECIS (RFC 8264/65) disallows
them.

4 weeks agolib: Add RFC 5234 abnf definitions
Marco Bettini [Mon, 18 May 2026 13:05:14 +0000 (13:05 +0000)] 
lib: Add RFC 5234 abnf definitions

4 weeks agolib-ldap: Fall back to system default CA paths for TLS
Marco Bettini [Mon, 29 Jun 2026 13:09:47 +0000 (13:09 +0000)] 
lib-ldap: Fall back to system default CA paths for TLS

OpenLDAP built against GnuTLS does not load the system trust store on
its own (unlike its OpenSSL backend), so without this the TLS handshake
has no trust anchors and fails. The existing handle settings are
checked via ldap_get_option() so an inherited ldap.conf CA is not
overridden.

Broken by: 333ac4c837cf (lib-ldap: Create a new TLS context for all LDAP TLS connections)

4 weeks agolib-ssl-iostream: Add ssl_iostream_get_default_ca_paths()
Marco Bettini [Mon, 29 Jun 2026 13:09:40 +0000 (13:09 +0000)] 
lib-ssl-iostream: Add ssl_iostream_get_default_ca_paths()

4 weeks agoauth: Wrap var_expand_program_execute() for auth_request var expansion
Marco Bettini [Wed, 10 Jun 2026 13:02:59 +0000 (13:02 +0000)] 
auth: Wrap var_expand_program_execute() for auth_request var expansion

The var_expand providers expect params.context to be a struct
auth_request_var_expand_ctx and dereference ctx->auth_request, but the
passwd-file backends erroneously passed raw auth_request as params.context,
giving garbage %{passdb:...} and %{userdb:...} lookups and potential crashes.

Add auth_request_var_expand_program_execute(), which builds the context
internally and enforces it to be of the proper type.

Broken since d0b4a58cb9 ("auth: Use new var_expand").

4 weeks agolib-settings: Treat empty built-in default as "no default" for non-emptyable settings
Timo Sirainen [Wed, 1 Jul 2026 08:40:40 +0000 (08:40 +0000)] 
lib-settings: Treat empty built-in default as "no default" for non-emptyable settings

A built-in default (setting_parser_info.default_settings) with an empty value
for a setting that cannot be empty (e.g. a number or time) previously failed
with an "Invalid ..." error when applied directly via lib-settings. Treat it
instead as "no default" and leave the value to be inherited from a less
specific filter.

This lets a named filter keep a default-settings entry - so settings-history
can override the value for old config versions - without forcing that value for
current versions. The config process already added this in
2b98d64515f5c679b5b9d884c63f19dc4c73aa46, but lib-settings was forgotten.

Fixes Pigeonhole fuzzer to work again.

4 weeks agolib-dns-client: dns-lookup - Fix use-after-free in dns_client_destroy()
Timo Sirainen [Mon, 29 Jun 2026 15:47:36 +0000 (15:47 +0000)] 
lib-dns-client: dns-lookup - Fix use-after-free in dns_client_destroy()

dns_client_destroy() calls dns_client_disconnect() to abort pending
lookups. For one-shot clients (deinit_client_at_free=TRUE) this frees the
last lookup via dns_lookup_free(), which calls dns_client_deinit() and
frees the client (and the embedded connection). dns_client_destroy() then
kept using the freed client.

Clear deinit_client_at_free while aborting the lookups so the client is
not freed from under us, tear down the connection, and only then perform
the deinit if it was requested.

Broken by 389100925af49121a109d3e849ed5beecc2d2cdb

5 weeks agolib-storage: Fix memory leak when deinitializing mailbox list
Timo Sirainen [Mon, 29 Jun 2026 07:05:11 +0000 (10:05 +0300)] 
lib-storage: Fix memory leak when deinitializing mailbox list

Broken by c2aa2d2223cceab5ece02fb472f13a2b7ee4b52a

5 weeks agolib-settings: Expand %variables in strlist and boollist keys
Timo Sirainen [Sun, 7 Jun 2026 14:32:57 +0000 (17:32 +0300)] 
lib-settings: Expand %variables in strlist and boollist keys

Until now only strlist/boollist values were %variable-expanded by
settings_get(); the keys were always used literally. Expand the keys as
well, using the same rules as the values:

 - Keys coming from the configuration are trusted and are always
   expanded.
 - Override keys are expanded only for SETTINGS_OVERRIDE_TYPE_DEFAULT,
   exactly like override values. Keys from -o parameters
   (SETTINGS_OVERRIDE_TYPE_CLI_PARAM) and userdb
   (SETTINGS_OVERRIDE_TYPE_USERDB) are kept literal.

The expansion is done before settings_parse_list_has_key(), so list key
deduplication and overriding operate on the final key.

5 weeks agolib-dns-client: dns-lookup - Fix handling of disconnect initated by dns-client service
Stephan Bosch [Fri, 24 Apr 2026 02:13:53 +0000 (04:13 +0200)] 
lib-dns-client: dns-lookup - Fix handling of disconnect initated by dns-client service

5 weeks agolib-var-expand: Add escape filter
Aki Tuomi [Sun, 7 Jun 2026 11:37:51 +0000 (11:37 +0000)] 
lib-var-expand: Add escape filter

Runs the currently set escape filter for lhs. Errors out if
no escape function is set. Intended to be used with safe.

5 weeks agolib-var-expand: safe filter must be last in the filter chain
Aki Tuomi [Sun, 7 Jun 2026 11:25:31 +0000 (11:25 +0000)] 
lib-var-expand: safe filter must be last in the filter chain

5 weeks agoauth: oauth2 mech - snapshot worker-supplied fields before credential lookup
Timo Sirainen [Fri, 12 Jun 2026 22:07:00 +0000 (22:07 +0000)] 
auth: oauth2 mech - snapshot worker-supplied fields before credential lookup

On the auth worker path (oauth2_use_worker_with_mech=yes) the mech applies
the fields returned by the worker with auth_request_set_fields() and then
runs auth_request_lookup_credentials(). If that lookup reaches a passdb
returning PASSDB_RESULT_SCHEME_NOT_AVAILABLE, the passdb rollback wipes
fields.userdb_reply (and extra_fields), dropping the userdb_* fields the
worker just supplied. Snapshot them right after set_fields so the rollback
keeps them. The in-process path is handled in db_oauth2_add_extra_fields().

5 weeks agoauth: db-oauth2 - snapshot userdb_reply after extra-field set
Timo Sirainen [Fri, 12 Jun 2026 21:59:31 +0000 (21:59 +0000)] 
auth: db-oauth2 - snapshot userdb_reply after extra-field set

On the SASL OAUTHBEARER path, auth_request_lookup_credentials reaches
the oauth2 passdb's PASSDB_RESULT_SCHEME_NOT_AVAILABLE branch, whose
rollback wipes fields.userdb_reply - including the userdb_* entries
db_oauth2_add_extra_fields just set. Snapshot userdb_reply right after
the field-set loop so the rollback keeps them. extra_fields is already
snapshotted by the caller; only userdb_reply needs explicit
protection.

5 weeks agolib-storage: mailbox list index - Use hash table to storage name lookups
Timo Sirainen [Sat, 6 Dec 2025 11:23:05 +0000 (13:23 +0200)] 
lib-storage: mailbox list index - Use hash table to storage name lookups

5 weeks agolib-storage: mailbox_list_index_node_get_path() - Rename str parameter to storage_name
Timo Sirainen [Thu, 30 Apr 2026 17:53:36 +0000 (17:53 +0000)] 
lib-storage: mailbox_list_index_node_get_path() - Rename str parameter to storage_name

The function emits a full storage name (escaped, joined by hierarchy
separators), not a vname. Rename for clarity.

5 weeks agolib-storage: mailbox_list_get_escaped_mailbox_name() - Match storage_name escape...
Timo Sirainen [Thu, 30 Apr 2026 17:53:17 +0000 (17:53 +0000)] 
lib-storage: mailbox_list_get_escaped_mailbox_name() - Match storage_name escape rules

The function previously escaped only the storage_escape_char and the
hierarchy separator, while mailbox_list_default_get_storage_name() applies
mailbox_list_escape_name_params() rules: leading '~' on the first part,
literal '/', and a dirstart maildir_name. Names round-tripping through the
mailbox list index lookup hash relied on the two encoders producing
identical output, so any name containing one of those characters could
not be looked up by its storage name.

Use mailbox_list_escape_name_params_to_str() so both producers share the
same escape logic. node->parent == NULL marks the leading hierarchy part
for the leading-'~' rule.

5 weeks agolib-storage: mailbox_list_escape_name_params() - Add _to_str() variant
Timo Sirainen [Thu, 30 Apr 2026 17:52:24 +0000 (17:52 +0000)] 
lib-storage: mailbox_list_escape_name_params() - Add _to_str() variant

Allows callers to append the escaped name into an existing string_t
without an intermediate t_str_new() allocation. Existing function
becomes a thin wrapper around the new helper.

5 weeks agoconfig: doveconf - Hide flat global settings overridden by a named filter
Timo Sirainen [Tue, 9 Jun 2026 07:50:00 +0000 (07:50 +0000)] 
config: doveconf - Hide flat global settings overridden by a named filter

doveconf -a printed both the flat global setting (e.g. cassandra_hosts)
and its grouped override (cassandra { hosts }). The flat global is only
the default value and is confusing when a named filter overrides the same
setting, because the flat default and the grouped override are shown side
by side in different scopes.

Hide the flat global from the full human-readable output when a top-level
named filter overrides the same prefixed setting. Settings that are not
overridden are still shown as flat globals, and explicitly querying a
single setting still shows it.

5 weeks agoglobal: Remove set_value_unknown checks that are now unnecessary
Timo Sirainen [Wed, 3 Jun 2026 09:11:33 +0000 (12:11 +0300)] 
global: Remove set_value_unknown checks that are now unnecessary

5 weeks agoconfig: Skip checking at startup settings structs that contain %{variables}
Timo Sirainen [Wed, 3 Jun 2026 09:09:59 +0000 (12:09 +0300)] 
config: Skip checking at startup settings structs that contain %{variables}

This fixes failures at startup when some settings check_func()s weren't
checking for set_value_unknown.

5 weeks agoconfig: settings-history: Prefer exact filter-path default over setting-name default
Timo Sirainen [Tue, 23 Jun 2026 17:51:53 +0000 (17:51 +0000)] 
config: settings-history: Prefer exact filter-path default over setting-name default

old_settings_default() matched on either the setting name or the full filter
path and returned whichever entry came first in the version-sorted history
array. When both a generic and a filter-specific entry existed for the same
version, the winner was arbitrary. Always prefer the more specific
filter-path match.

5 weeks agoconfig: Treat empty built-in default as "no default" for non-emptyable settings
Timo Sirainen [Tue, 23 Jun 2026 17:51:53 +0000 (17:51 +0000)] 
config: Treat empty built-in default as "no default" for non-emptyable settings

A built-in default with an empty value for a setting that cannot be empty
(e.g. a number or time) previously failed with an "Invalid ..." error. Treat
it instead as "no default" and leave the value to be inherited from a less
specific filter.

This lets a named filter keep a default-settings entry - so settings-history
can override the value for old config versions - without forcing that value
for current versions.

5 weeks agolib-master: haproxy - Bound v2 want against HAPROXY_READ_SIZE not rbuf_full
Aki Tuomi [Fri, 12 Jun 2026 10:46:54 +0000 (10:46 +0000)] 
lib-master: haproxy - Bound v2 want against HAPROXY_READ_SIZE not rbuf_full

5 weeks agolib-smtp: xclient - Reject invalid HELO parameter with 501
Aki Tuomi [Fri, 12 Jun 2026 06:09:16 +0000 (06:09 +0000)] 
lib-smtp: xclient - Reject invalid HELO parameter with 501

The XCLIENT spec states that invalid attribute values must result in
a 501 syntax error.  All other attributes (ADDR, PORT, PROTO, ...) already
return 501 on bad input; invalid HELO= was silently ignored instead.

5 weeks agologin-common: client-common-auth - Use overflow-safe auth response bound
Aki Tuomi [Fri, 5 Jun 2026 11:31:38 +0000 (11:31 +0000)] 
login-common: client-common-auth - Use overflow-safe auth response bound

client_auth_read_line() bounded the accumulated SASL response with
str_len(auth_response) + i > LOGIN_MAX_AUTH_BUF_SIZE, where both operands
are size_t. With the shipped LOGIN_MAX_INBUF_SIZE the sum can't approach
SIZE_MAX, so this can't wrap in practice, but the additive form would be
the load-bearing bound if the inbuf size were ever raised. Rewrite it in
overflow-safe form so the bound holds independently of the inbuf size.

5 weeks agolib-imap: imap-parser - Track quoted-string escape with a bool
Aki Tuomi [Fri, 5 Jun 2026 11:31:38 +0000 (11:31 +0000)] 
lib-imap: imap-parser - Track quoted-string escape with a bool

parser->str_first_escape held the offset of the first '\' escape in a
quoted string in a signed int, but that offset was only ever tested for
>= 0 - it was never used as a position. Storing a size_t offset into a
signed int would truncate/negate for an offset past INT_MAX, which can't
happen under the login parsers' 8 KiB line cap but is fragile for parser
instances configured with a much larger max_line_size.

Replace it with a plain bool str_have_escape, which is all the code
actually needs and removes the signedness concern entirely.

Add a quoted-string test covering both the escaped and non-escaped paths
to confirm unescaping still works.

5 weeks agolib-http: http-header-parser - Reject obs-fold in strict mode
Aki Tuomi [Fri, 5 Jun 2026 11:31:37 +0000 (11:31 +0000)] 
lib-http: http-header-parser - Reject obs-fold in strict mode

http_header_parse() accepted obsolete line folding (obs-fold: CRLF
followed by SP/HTAB) unconditionally, replacing the CRLF with a single
space and folding the continuation into the previous field value. RFC
7230, Section 3.2.4 says a server that receives obs-fold in a request
MUST reject it with 400 (Bad Request), because differing obs-fold
handling between Dovecot and an upstream HTTP agent is a request
desynchronization / smuggling primitive.

The HTTP server parses requests with HTTP_HEADER_PARSE_FLAG_STRICT
(http-server-connection.c), so gate the rejection on that flag. Lenient
parsing (HTTP response parsing, where we accommodate bad servers) keeps
folding as before.

Add a strict-mode obs-fold case to the invalid header tests; the existing
lenient folding test continues to pass.

5 weeks agolib-imap: imap-arg - Don't read past an empty list in imap_arg_get_list_full()
Aki Tuomi [Fri, 5 Jun 2026 11:31:37 +0000 (11:31 +0000)] 
lib-imap: imap-arg - Don't read past an empty list in imap_arg_get_list_full()

When the backing array of an IMAP_ARG_LIST is empty - holding no elements
at all, not even the temporary IMAP_ARG_EOL that imap-parser appends when
it stops early - array_get() returns count 0. The code then fell through
to the early-stop branch and read (*list_r)[0], indexing one element past
the zero-length array (an out-of-bounds read, or an assertion failure on
the bogus type).

Treat count == 0 as an empty list instead. Add a regression test that
calls imap_arg_get_list_full() on a list arg with an empty array.

5 weeks agolib: punycode - Don't scan past len in punycode_decode()
Aki Tuomi [Wed, 10 Jun 2026 11:39:32 +0000 (11:39 +0000)] 
lib: punycode - Don't scan past len in punycode_decode()

punycode_decode(input, len, ...) located the delimiter with
strrchr(input, '-'), which scans the whole NUL-terminated string rather
than just the first len bytes. rfc822_decode_punycode() calls it with
input pointing into a longer dot-atom buffer and len equal to a single
label's length, so a '-' in a later label lies past len: strrchr() then
returned a pointer >= end and the i_assert(delim < end) aborted the
process (reachable via a crafted IDN address when experimental mail
UTF-8 is enabled). It was also an out-of-bounds read for any
non-NUL-terminated caller.

Use i_memrchr() to search only the first len bytes, which also makes
the previous delim<end assertion unnecessary.

5 weeks agolib: punycode - Use const unsigned char* input in punycode_decode()
Aki Tuomi [Wed, 10 Jun 2026 12:02:42 +0000 (12:02 +0000)] 
lib: punycode - Use const unsigned char* input in punycode_decode()

5 weeks agolib: Add i_memrchr() helper
Aki Tuomi [Wed, 10 Jun 2026 11:39:19 +0000 (11:39 +0000)] 
lib: Add i_memrchr() helper

memrchr() is not in POSIX. Wrap it with a portable fallback that
scans backwards through the first size bytes of data, returning a
pointer to the last occurrence of c or NULL if not found.

5 weeks agolib-index: mail-index-map - Fix out-of-bounds read on empty keyword name area
Aki Tuomi [Fri, 5 Jun 2026 11:31:37 +0000 (11:31 +0000)] 
lib-index: mail-index-map - Fix out-of-bounds read on empty keyword name area

mail_index_map_parse_keywords() computes

name_area_end_offset = (kw_hdr + ext->hdr_size) - name

as an unsigned int. When the on-disk keywords extension header has no name
area - e.g. keywords_count==0 with hdr_size equal to just the
mail_index_keyword_header - name_area_end_offset is 0 (the preceding bound
check uses '>' so this passes). The code then evaluated

name[name_area_end_offset-1]

i.e. name[(unsigned)-1] == name[0xFFFFFFFF], a ~4 GB out-of-bounds read,
on a header read straight from dovecot.index.

Skip the terminating-NUL check when the name area is empty; a keyword
header with zero names is a valid (empty) keyword set.

5 weeks agolib-smtp: smtp-syntax - Don't leave a dangling domain pointer on parse error
Aki Tuomi [Fri, 5 Jun 2026 11:31:37 +0000 (11:31 +0000)] 
lib-smtp: smtp-syntax - Don't leave a dangling domain pointer on parse error

Parse into a local variable so the transient data-stack pointer never lands in
the persistent proxy_data struct.