]> git.ipfire.org Git - thirdparty/dovecot/core.git/log
thirdparty/dovecot/core.git
8 hours agoimap/pop3-login: Tolerate SASL continuations "+" without trailing space main
Marco Bettini [Fri, 24 Jul 2026 15:47:31 +0000 (15:47 +0000)] 
imap/pop3-login: Tolerate SASL continuations "+" without trailing space

5 hours agolib-master: Fix kick reason when KICK-USER-SIGNAL command arrives late
Timo Sirainen [Wed, 29 Jul 2026 06:45:29 +0000 (06:45 +0000)] 
lib-master: Fix kick reason when KICK-USER-SIGNAL command arrives late

anvil kicks processes with client_limit=1 by connecting to the process's
master-admin socket, writing the KICK-USER-SIGNAL command and sending
SIGTERM. The command is normally read by the SIGTERM handler itself,
which accept()s the connection. If the process's ioloop happens to
accept the connection first, master_admin_client_initial_read() waits
for the command for 100 ms with SIGTERM blocked.

If the command wasn't received within those 100 ms, the kick was lost:
the SIGTERM handler's accept() failed with EAGAIN, because the
connection had already been accepted, and last_kick_signal_user was
still unset. The process then shut down as if it wasn't kicked at all,
logging "Killed with signal 15" and disconnecting the client with
"Server shutting down" instead of "User kicked".

The same happened even when the command was already readable: the
delayed SIGTERM handler stops the ioloop, which stops running the rest
of the io callbacks in the current ioloop iteration, so the master-admin
connection's input was never handled.

Fix this by waiting a while for the pending master-admin commands in the
delayed SIGTERM handler before deciding that the process wasn't kicked,
and by doing the username match already in
master_service_set_last_kick_signal_user() when the SIGTERM was handled
before the command was received.

5 hours agolib-master: Add MASTER_ADMIN_CLIENT_WAIT_MSECS
Timo Sirainen [Wed, 29 Jul 2026 06:45:22 +0000 (06:45 +0000)] 
lib-master: Add MASTER_ADMIN_CLIENT_WAIT_MSECS

Replaces the hardcoded master-admin connection command wait timeout.

5 hours agolib-master: Fix parsing master-admin-client minor version in SIGTERM handler
Timo Sirainen [Wed, 29 Jul 2026 06:40:25 +0000 (06:40 +0000)] 
lib-master: Fix parsing master-admin-client minor version in SIGTERM handler

16 hours agologin-proxy: Log admin kick reason when exiting due to a kicked proxy
Timo Sirainen [Sun, 5 Jul 2026 17:06:42 +0000 (17:06 +0000)] 
login-proxy: Log admin kick reason when exiting due to a kicked proxy

In high-security mode a kicked proxy connection terminates the login
process via SIGTERM, and the proxy is freed in login_proxy_deinit() with
the generic "Process shutting down" reason. Use "Kicked by admin" instead
when the process was killed by a user kick, matching the reason logged by
the admin-socket kick path.

16 hours agologin-proxy: Fix doveadm kick for proxied connections in high-security mode
Timo Sirainen [Sun, 5 Jul 2026 17:06:15 +0000 (17:06 +0000)] 
login-proxy: Fix doveadm kick for proxied connections in high-security mode

When imap-login runs with client_limit=1 (restart_request_count=1, the
community-edition default), a detached proxy connection is kicked via
SIGTERM + KICK-USER-SIGNAL. The master service signal handler matches the
kick against current_user, which login processes never set, so the signal
was treated as a mismatch and ignored: doveadm reported the connection
kicked while the client stayed connected.

Set current_user to the proxied user while proxying with client_limit=1,
so the kick is recognized and terminates the connection. With
client_limit>1 the kick uses the admin socket and current_user stays unset.

16 hours agolib: ostream-file - Avoid memcpy(NULL) assert with max_buffer_size=0
Markus Valentin [Wed, 29 Jul 2026 00:52:50 +0000 (00:52 +0000)] 
lib: ostream-file - Avoid memcpy(NULL) assert with max_buffer_size=0

o_stream_set_max_buffer_size(output, 0) on a stream that has never
buffered anything leaves fstream->buffer NULL, because
o_stream_grow_buffer() caps the allocation to max_buffer_size. If a
write to the fd could then not be fully flushed, o_stream_add() did a
zero-length memcpy() into the NULL buffer:

  Panic: file lib.h: line 75 (i_memcpy): assertion failed: (dest != NULL && src != NULL)

This is not triggered by any of the current max_buffer_size=0 callers.

o_stream_add() also already returned the correct 0 here and the
memcpy() copied nothing, so this only became fatal with the
assert-crashing memcpy() macro in 38dbeec767ad10021020e3833b1dbc3d04442f85.

16 hours agolib: test-ostream-multiplex - Add STREAM format backpressure and buffering tests
Timo Sirainen [Tue, 14 Jul 2026 05:41:06 +0000 (05:41 +0000)] 
lib: test-ostream-multiplex - Add STREAM format backpressure and buffering tests

Cover the recent ostream-multiplex STREAM fixes and some previously
untested paths:

- STREAM must write through a max_buffer_size==0 parent instead of
  wedging the data in the channel buffer.
- A single sendv larger than IO_BLOCK_SIZE to such a parent must make
  forward progress rather than repeatedly buffering nothing and
  busy-looping.
- A congested socket must apply backpressure so o_stream_send_istream()
  stops and waits, instead of buffering the whole body into the parent
  without bound. A regression spins forever, so this runs in a
  subprocess whose watchdog kills the hung child and turns the hang into
  a clean failure.
- A max_buffer_size==0 PACKET parent must fail its assert loudly.
- The STREAM_CONTINUE format had no coverage at all.

32 hours agolib-mail: message-parser - Check both dashes of the epilogue boundary
Timo Sirainen [Wed, 29 Jul 2026 01:00:24 +0000 (01:00 +0000)] 
lib-mail: message-parser - Check both dashes of the epilogue boundary

preparsed_parse_epilogue_boundary() failed to verify the second dash's
existence when epilogue starts with "\r\n--".

32 hours agolib-mail: message-parser - Fix skipping the epilogue boundary line
Timo Sirainen [Wed, 29 Jul 2026 01:00:24 +0000 (01:00 +0000)] 
lib-mail: message-parser - Fix skipping the epilogue boundary line

preparsed_parse_epilogue_boundary() looks for the end of the close
delimiter line with memchr(). When it isn't found the code asks for more
data, but stops asking once it holds BOUNDARY_END_MAX_LEN bytes, the
input buffer is full, or no more data is coming. It then fell through
to:

ctx->skip = cur - data + 1;

with cur == NULL. That is undefined behavior, and in practice yields a
skip of roughly 2^64 - (address of data). The skip is applied by the
next message_parser_read_more(), whose i_stream_skip() then seeks the
stream far past any real offset. The walk ends at EOF with
broken_reason unset, so message_parser_deinit_from_parts() reports
success for a truncated parse. If the multipart's parent had a further
sibling, preparsed_parse_next_header_init() would instead see the
stream past that sibling's cached physical_pos.

The forward parser doesn't stop looking for the line's end:
parse_next_body_skip_boundary_line() consumes whole blocks until it
finds the newline. Do the same here, in a new
preparsed_parse_epilogue_skip_boundary_line() state, so replaying a
cached tree returns the same blocks the forward parser returns for the
same input. Give up only if the line doesn't end within the part, which
is the pre-existing "Epilogue boundary end not at expected position"
check.

This wasn't reachable with the current callers: the epilogue is only
parsed with MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS, and the
from-parts callers (index-mail-headers, message-search) do not set it.

Add tests for the ways the line's end can be missing: a close delimiter
with no trailing newline that ends exactly at the multipart's end, a
stream truncated inside the close delimiter line, and a line whose
newline is further away than the input buffer can reach -- the last one
comparing the returned blocks against a forward parse of the same input.

32 hours agolib-mail: message-part-serialize - Enforce message part offset consistency
Timo Sirainen [Mon, 27 Jul 2026 11:25:20 +0000 (11:25 +0000)] 
lib-mail: message-part-serialize - Enforce message part offset consistency

The message_part tree is serialized to the cache and later deserialized
and walked by message_parser_init_from_parts(). That walk requires the
parts to be laid out in order with non-overlapping siblings, but neither
end enforced it fully:

 - message_part_deserialize() advanced its expected-position check only
   for parts that have children, never past a leaf body. Overlapping
   leaf siblings therefore passed validation and could later crash the
   from-parts walk. Advance the position past leaf bodies too so such a
   tree is rejected as corrupted (leading to a re-parse).

   This also makes the existing "child part location exceeds our size"
   check effective for child lists ending in a leaf: previously the
   position was still at the parent's body start when the recursion
   returned, so a last child overrunning its parent's body was accepted.

 - message_part_serialize() only checked that non-composite parts have
   no children. Its input is freshly parsed in-memory data, so an
   inconsistent tree is a parser/programming bug, not corrupted input:
   assert the same ordering the deserializer requires. This catches a
   bad producer at write time rather than as a confusing read-side
   failure, and guarantees anything serialized will deserialize.

The position is only used for validation and is never stored into the
returned parts, so trees that still deserialize are unchanged.

Add tests for the deserializer rejecting overlapping leaf siblings and
for the serializer asserting on them.

32 hours agolib-mail: message-parser - Handle inconsistent cached parts in from-parts
Timo Sirainen [Mon, 27 Jul 2026 11:25:11 +0000 (11:25 +0000)] 
lib-mail: message-parser - Handle inconsistent cached parts in from-parts

message_parser_init_from_parts() walks a cached message_part tree using
its cached physical positions and sizes. If the tree is internally
inconsistent -- a sibling part recorded as starting before the previous
part ends -- preparsed_parse_next_header_init() finds the stream already
past the part's cached physical_pos and i_assert-panics:

  Panic: ... preparsed_parse_next_header_init: assertion failed:
  (ctx->part->physical_pos >= ctx->input->v_offset)

The sibling body_init and epilogue_init paths already treat the same
condition as a broken message. Do the same here: set broken_reason and
return -1 instead of asserting, so a corrupt cache degrades to a
re-parse instead of crashing the process.

Add a test that parses tricky multipart messages at every input buffer
size and checks the resulting tree offsets never overlap and always
re-parse, plus a test that a manually overlapped tree fails the
from-parts walk gracefully.

33 hours agolib: test-iostream-pump - Fix duplicated test name
Timo Sirainen [Tue, 28 Jul 2026 23:44:38 +0000 (23:44 +0000)] 
lib: test-iostream-pump - Fix duplicated test name

test_iostream_pump_failure_end_read() printed itself as "failure mid-read",
which is what test_iostream_pump_failure_mid_read() is already called. This
made it ambiguous which of the two tests a CI failure belonged to.

33 hours agolib: test-iostream-pump - Fix flakiness on stalled machines
Timo Sirainen [Tue, 28 Jul 2026 23:44:01 +0000 (23:44 +0000)] 
lib: test-iostream-pump - Fix flakiness on stalled machines

The tests used a 3000 ms ioloop timeout as a watchdog for the pump to
finish. The ioloop compensates for time moving forwards only while it is
waiting, not while timeout and IO callbacks are running. When the wall
clock jumps forwards during that window, or the process doesn't get
scheduled, every timeout that became due is called in the same run, so the
watchdog fires before the pump has had the chance to make any progress.

Use a 30 second watchdog instead, so that only a machine that is stalled
that long can trigger it. Keep alarm() a bit above the watchdog, so that it
still only triggers when the ioloop itself is stuck.

33 hours agolib: test-lib-signals - Fix flakiness on stalled machines
Timo Sirainen [Tue, 28 Jul 2026 23:43:48 +0000 (23:43 +0000)] 
lib: test-lib-signals - Fix flakiness on stalled machines

The tests used a 400 ms (or 100 ms) ioloop timeout as a watchdog for a
delayed signal handler that was expected to be called much sooner. The
ioloop compensates for time moving forwards only while it is waiting, not
while timeout and IO callbacks are running. When the wall clock jumps
forwards during that window, or the process doesn't get scheduled, every
timeout that became due is called in the same run in the order it expired.
The watchdog then runs immediately after the timeout that sent the signal,
and the test fails before the ioloop ever gets to call the delayed signal
handler.

Use a 30 second watchdog instead, so that only a machine that is stalled
that long can trigger it. The timeouts whose expiry is the expected outcome
of the test are left as they were.

37 hours agoconfig: Fix doveconf hiding secrets
Aki Tuomi [Mon, 1 Jun 2026 11:15:31 +0000 (11:15 +0000)] 
config: Fix doveconf hiding secrets

Secrets should be properly hidden when asking for doveconf and doveconf
section/

37 hours agoconfig: Implement heredoc support
Aki Tuomi [Fri, 1 May 2026 19:41:50 +0000 (22:41 +0300)] 
config: Implement heredoc support

Adds <<MARKER heredoc syntax to the Dovecot config file format,
allowing multi-line values to be set without escaping:

  key = <<EOD
  line one
  line two
  EOD

The marker is arbitrary (any C identifier) and is preserved through
the binary config format so doveconf reproduces the exact marker in
its output (e.g. <<FOO → doveconf outputs <<FOO).

Storage: values set via heredoc are stored with prefix byte
CONFIG_VALUE_PREFIX_HEREDOC (4) followed by the marker, a newline,
and the value. SET_FILE settings use HEREDOC|FILE_INLINE (12) to
preserve the marker while flagging the content as inline.

The "inline:" assignment form (ssl_ca = inline:cert) is still
accepted for backward compatibility but is now stored as
CONFIG_VALUE_PREFIX_FILE_INLINE (8). doveconf always outputs
SET_FILE inline content using heredoc syntax; the "inline:" prefix
is no longer emitted. The "inline:" is still accepted, and not
deprecated from passdb/userdb result.

37 hours agoconfig: Replace config_value_prefix #defines with enum
Aki Tuomi [Fri, 1 May 2026 19:41:38 +0000 (22:41 +0300)] 
config: Replace config_value_prefix #defines with enum

Replaces the three CONFIG_VALUE_PREFIX_* character constants with a
proper enum config_value_prefix using powers-of-two values so that
prefix flags can be combined with bitwise OR.

Also adds CONFIG_VALUE_PREFIX_FILE_INLINE (8) for SET_FILE inline
content, allowing it to be stored directly without the previous
\001\n two-byte hack. The HEREDOC|FILE_INLINE (12) combination
preserves both the heredoc marker and the inline-content flag.

5 days agoglobal: Drop TIME_T_SIGNED define
Aki Tuomi [Fri, 24 Jul 2026 10:22:14 +0000 (10:22 +0000)] 
global: Drop TIME_T_SIGNED define

5 days agom4: Require signed time_t
Aki Tuomi [Tue, 14 Jul 2026 07:12:02 +0000 (07:12 +0000)] 
m4: Require signed time_t

An unsigned time_t is not forbidden by POSIX, but it is essentially
nonexistent on POSIX systems. Requiring a signed time_t simplifies
overflow checks in time arithmetic.

5 days agolib: ioloop - Use time_max_safe_value() for infinite-wait sentinel
Aki Tuomi [Sat, 11 Jul 2026 18:53:40 +0000 (18:53 +0000)] 
lib: ioloop - Use time_max_safe_value() for infinite-wait sentinel

Reuse the shared bound instead of duplicating the TIME_T_MAX_BITS
computation. Widens the sentinel from 2^(TIME_T_MAX_BITS-1)-1 to
2^TIME_T_MAX_BITS-1, matching time_max_safe_value()'s bound.

5 days agolib-imap: Use time helpers for date arithmetic overflow checks
Aki Tuomi [Sat, 11 Jul 2026 17:54:16 +0000 (17:54 +0000)] 
lib-imap: Use time helpers for date arithmetic overflow checks

tm_is_too_large() now shares its max-time_t bound via
time_max_safe_value() instead of duplicating the #if TIME_T_MAX_BITS
block. imap_parse_datetime() and imap_to_datetime_tz() use
time_sub_secs()/time_add_secs() instead of unchecked time_t
arithmetic when applying the timezone offset.

5 days agolib-http: Use time_add_secs() for Retry-After parsing
Aki Tuomi [Sat, 11 Jul 2026 16:18:05 +0000 (19:18 +0300)] 
lib-http: Use time_add_secs() for Retry-After parsing

Found by fuzzers. Fixes overflow when parsing too large
retry-fater values.

5 days agolib: Add time_add_secs()/time_sub_secs() overflow-safe helpers
Aki Tuomi [Sat, 11 Jul 2026 17:53:00 +0000 (17:53 +0000)] 
lib: Add time_add_secs()/time_sub_secs() overflow-safe helpers

Adding/subtracting seconds to/from a time_t without an overflow check
is a recurring bug source (e.g. Retry-After parsing). Add shared
helpers that reject out-of-range results instead of invoking UB.

5 days agolib-http: Document why client response parser is lenient
Aki Tuomi [Mon, 13 Jul 2026 21:09:14 +0000 (21:09 +0000)] 
lib-http: Document why client response parser is lenient

Record why http_response_parser_init() is intentionally not passed
STRICT here: the client parses responses from arbitrary upstream
servers, some of which send legitimately non-compliant framing, and
the response is consumed internally rather than re-emitted
downstream, so the response-splitting precondition that STRICT
guards against doesn't apply to this connection's use.

No behavior change.

5 days agolib/uri-util: Fix out-of-bounds reads on unterminated buffers
Aki Tuomi [Mon, 13 Jul 2026 21:09:07 +0000 (21:09 +0000)] 
lib/uri-util: Fix out-of-bounds reads on unterminated buffers

uri_parse_ip_literal() scanned for the closing ']' with memchr()
starting at cur+1 but passing length end-cur, one byte too many.
uri_parse_pct_encoded_data() dereferenced both bytes after a '%'
before checking either against pend.

Both are masked when parsing a '\0'-terminated string (the extra
read lands on the terminator), which covers every caller currently
in tree, but uri_parser_init_data() is public API that allows a
bounded, non-terminated buffer, so a real out-of-bounds read is
possible via that path.

Add a regression test using uri_check_data() with an exact-size,
non-terminated heap buffer so valgrind can catch a reintroduced
overread; a '\0'-terminated test table would pass unchanged whether
or not the bug is present.

5 days agolib-http: Reject bare LF as line terminator in strict mode
Aki Tuomi [Mon, 13 Jul 2026 21:08:55 +0000 (21:08 +0000)] 
lib-http: Reject bare LF as line terminator in strict mode

Every CR-handling state in the header parser, request-line parser
and chunked transfer parser treated CR as optional, so a lone LF
always terminated the line even with STRICT set. This let an
untrusted peer choose between CRLF and LF at each line boundary,
which is a request-smuggling-relevant desync risk against an
upstream that requires full CRLF.

Add a strict flag to the chunked transfer istream (previously it had
no strict/lenient distinction at all) and thread it from the message
parser, so server-side (request) parsing rejects bare LF while
client-side (response) parsing stays lenient, matching the existing
header parser split.

12 days agologin-common: Add unit test for 4d6e5744bb5 bugfix
Michael M Slusarz [Fri, 10 Jul 2026 19:41:07 +0000 (13:41 -0600)] 
login-common: Add unit test for 4d6e5744bb5 bugfix

13 days agologin-common: Fix memory leak of proxy_last_host
Markus Valentin [Thu, 16 Jul 2026 11:16:52 +0000 (11:16 +0000)] 
login-common: Fix memory leak of proxy_last_host

Broken in 6335dab970771b13689918637122360ff7021662.

2 weeks agolib-smtp: address - Fix assert on empty broken localpart
Aki Tuomi [Tue, 14 Jul 2026 05:55:27 +0000 (05:55 +0000)] 
lib-smtp: address - Fix assert on empty broken localpart

smtp_address_parse_path_full() with an end pointer and
ALLOW_BAD_LOCALPART could leave the separator search pointer one before
the parse position for an empty address (e.g. a leading space),
underflowing into p_strdup_until() and aborting with an assertion. Clamp
the pointer to the parse position. Add a test covering this case.

2 weeks agosubmission-login: proxy - Fail cleanly on malformed AUTH reply
Aki Tuomi [Tue, 14 Jul 2026 05:52:10 +0000 (05:52 +0000)] 
submission-login: proxy - Fail cleanly on malformed AUTH reply

When the backend answered the proxied AUTH command with a malformed
status line, the AUTHENTICATE state broke out into the shared failure
block, which assumes proxy_reply was already created. That assumption
does not hold for an invalid line, so the login process aborted with an
assertion. Handle the invalid line as a protocol failure and return
immediately instead.

2 weeks agosubmission-login: proxy - Cap AUTH reply continuation lines
Aki Tuomi [Tue, 14 Jul 2026 05:47:11 +0000 (05:47 +0000)] 
submission-login: proxy - Cap AUTH reply continuation lines

A malicious or compromised backend could answer the proxied AUTH command
with an endless multi-line reply, growing proxy_reply without bound until
the login process ran out of memory (only loosely bounded by the login
timeout). Limit the number of continuation lines and fail the proxy
connection with a protocol error when the cap is exceeded.

2 weeks agoplugins/quota-clone: Register flushing quota clone on recalc
Karl Fleischmann [Mon, 13 Jul 2026 13:22:15 +0000 (15:22 +0200)] 
plugins/quota-clone: Register flushing quota clone on recalc

2 weeks agoplugins/quota-clone: Split off quota_clone_changed_user()
Karl Fleischmann [Mon, 13 Jul 2026 12:51:30 +0000 (14:51 +0200)] 
plugins/quota-clone: Split off quota_clone_changed_user()

2 weeks agoplugins/quota: Trigger recalc-callbacks on doveadm quota recalc
Karl Fleischmann [Mon, 13 Jul 2026 13:15:38 +0000 (15:15 +0200)] 
plugins/quota: Trigger recalc-callbacks on doveadm quota recalc

2 weeks agoplugins/quota: Add mechanism to register recalc-callbacks
Karl Fleischmann [Mon, 13 Jul 2026 13:06:29 +0000 (15:06 +0200)] 
plugins/quota: Add mechanism to register recalc-callbacks

These allow a call-site to hook into the quota plugin recalculating the
quota.

2 weeks agolib-mail: test-message-parser - Add LF-only empty preamble test
Aki Tuomi [Tue, 14 Jul 2026 07:28:09 +0000 (07:28 +0000)] 
lib-mail: test-message-parser - Add LF-only empty preamble test

2 weeks agolib-mail: message-parser - Fix out-of-bounds read on empty preamble
nishat-06 [Tue, 14 Jul 2026 07:08:48 +0000 (12:38 +0530)] 
lib-mail: message-parser - Fix out-of-bounds read on empty preamble

2 weeks agoimap: cmd_getmetadata() - Don't panic on GETMETADATA with invalid UTF-8 mailbox pattern
Marco Bettini [Tue, 14 Jul 2026 09:17:03 +0000 (09:17 +0000)] 
imap: cmd_getmetadata() - Don't panic on GETMETADATA with invalid UTF-8 mailbox pattern

2 weeks agolib-storage: mailbox_list_default_get_vname_part() - Sanitise mailbox vname when...
Marco Bettini [Tue, 7 Jul 2026 17:10:46 +0000 (17:10 +0000)] 
lib-storage: mailbox_list_default_get_vname_part() - Sanitise mailbox vname when it can't be escaped

With no escape char set, replace invalid bytes with U+FFFD so callers
don't panic.

2 weeks agolib-storage: mailbox_list_name_escape() - Escape invalid UTF-8 bytes in mailbox vname
Marco Bettini [Tue, 7 Jul 2026 17:09:20 +0000 (17:09 +0000)] 
lib-storage: mailbox_list_name_escape() - Escape invalid UTF-8 bytes in mailbox vname

Escape invalid bytes as <esc><hex> so vnames stay valid UTF-8 even with
an escape char set.

2 weeks agodoveadm-dsync: dsync_mailbox_name_to_parts() - Keep invalid UTF-8 bytes escaped in...
Marco Bettini [Mon, 13 Jul 2026 12:24:09 +0000 (12:24 +0000)] 
doveadm-dsync: dsync_mailbox_name_to_parts() - Keep invalid UTF-8 bytes escaped in mailbox vnames

2 weeks agolib: Add hex2dec_case()
Marco Bettini [Mon, 13 Jul 2026 12:19:51 +0000 (12:19 +0000)] 
lib: Add hex2dec_case()

2 weeks agoimap: Don't report FETCH output stream errors as storage errors
Markus Valentin [Tue, 14 Jul 2026 06:47:25 +0000 (08:47 +0200)] 
imap: Don't report FETCH output stream errors as storage errors

When no storage error is set and the output stream has failed do not
report "BUG: Unknown internal error".

2 weeks agolib: ostream-multiplex - Fix panic when destroying corked channel 0 after parent...
Markus Valentin [Mon, 13 Jul 2026 13:46:32 +0000 (15:46 +0200)] 
lib: ostream-multiplex - Fix panic when destroying corked channel 0 after parent failed

If the parent ostream fails (e.g. EPIPE when the client disconnects)
while the multiplex has corked it for sending, the uncork at the end of
o_stream_multiplex_sendv() is a no-op on the failed stream, so
parent_corked stays TRUE. Destroying the still-corked channel 0
afterwards tried to transfer the cork back to the parent and panicked.

Fixes:

Panic: file ostream-multiplex.c: line 518 (o_stream_multiplex_try_destroy): assertion failed: (!mstream->parent_corked)

Broken by f368d9e40650ca91ec8a4be1cdb361a0a42c33d9.

2 weeks agodoveadm: fs delete - Fix crash with concurrent async deletes
Timo Sirainen [Fri, 10 Jul 2026 07:08:48 +0000 (07:08 +0000)] 
doveadm: fs delete - Fix crash with concurrent async deletes

When deleting multiple objects asynchronously (doveadm fs delete -n), an
error on one file could leave another file's async lookup still running
when it was deinitialized. The still-pending EAGAIN was masked by the
errored file's ret=-1, and the drain loop in
doveadm_fs_delete_async_finish() was gated on doveadm_exit_code==0, so it
skipped draining once any file failed. Deinitializing a file mid-lookup
tripped the object_id_lookup_state != RUNNING assert in fs-dictmap.

Track pending separately from ret so it's no longer masked, and always
drain pending async operations before deinit, regardless of errors. Also
deinit files that hit a terminal error immediately, so they aren't
retried - otherwise the retry restarts their async lookup and keeps the
drain loop pending forever.

2 weeks agolib: idna - Implement code point context rules
Stephan Bosch [Fri, 4 Apr 2025 02:26:10 +0000 (04:26 +0200)] 
lib: idna - Implement code point context rules

2 weeks agolib: unicode-data - Add data for IDNA code point context rules
Stephan Bosch [Thu, 3 Apr 2025 02:37:59 +0000 (04:37 +0200)] 
lib: unicode-data - Add data for IDNA code point context rules

2 weeks agolib: idna - Implement Bidi rule checks
Stephan Bosch [Fri, 4 Apr 2025 02:20:38 +0000 (04:20 +0200)] 
lib: idna - Implement Bidi rule checks

2 weeks agolib: unicode-data - Add Bidi data needed for IDNA Bidi rules
Stephan Bosch [Fri, 4 Apr 2025 02:06:29 +0000 (04:06 +0200)] 
lib: unicode-data - Add Bidi data needed for IDNA Bidi rules

2 weeks agolib: test-unicode-data - Add support for parsing data from comments
Stephan Bosch [Sun, 23 Nov 2025 07:17:13 +0000 (08:17 +0100)] 
lib: test-unicode-data - Add support for parsing data from comments

2 weeks agolib: idna-punycode - Remove idna_punycode_decode_utf8()
Stephan Bosch [Tue, 7 Jul 2026 14:42:53 +0000 (16:42 +0200)] 
lib: idna-punycode - Remove idna_punycode_decode_utf8()

It is no longer used nor useful.

2 weeks agolib-mail: rfc822-parser - Remove rfc822_decode_punycode()
Stephan Bosch [Tue, 7 Jul 2026 14:40:19 +0000 (16:40 +0200)] 
lib-mail: rfc822-parser - Remove rfc822_decode_punycode()

It is no longer used nor useful.

2 weeks agolib-mail: rfc8222-parser - Use IDNA rather than punycode decoder directly
Stephan Bosch [Tue, 7 Jul 2026 14:38:49 +0000 (16:38 +0200)] 
lib-mail: rfc8222-parser - Use IDNA rather than punycode decoder directly

2 weeks agolib: Implement basic IDNA support
Stephan Bosch [Tue, 7 Jul 2026 17:14:06 +0000 (19:14 +0200)] 
lib: Implement basic IDNA support

2 weeks agolib: test-idna-punycode - Use idna_punycode_decode() directly
Stephan Bosch [Tue, 7 Jul 2026 17:13:20 +0000 (19:13 +0200)] 
lib: test-idna-punycode - Use idna_punycode_decode() directly

2 weeks agolib: unicode-data - Add IDNA code point data
Stephan Bosch [Mon, 24 Mar 2025 01:36:08 +0000 (02:36 +0100)] 
lib: unicode-data - Add IDNA code point data

2 weeks agolib: idna-punycode - Add idna_punycode_encode()
Stephan Bosch [Fri, 13 Mar 2026 00:45:44 +0000 (01:45 +0100)] 
lib: idna-punycode - Add idna_punycode_encode()

2 weeks agolib: idna-punycode - Rename 'label' variable in idna_punycode_decode() to 'output'
Stephan Bosch [Tue, 7 Jul 2026 14:32:11 +0000 (16:32 +0200)] 
lib: idna-punycode - Rename 'label' variable in idna_punycode_decode() to 'output'

2 weeks agolib: idna-punycode - Split off idna_punycode_decode() to use fixed uint32_t buffers
Stephan Bosch [Fri, 13 Mar 2026 00:35:20 +0000 (01:35 +0100)] 
lib: idna-punycode - Split off idna_punycode_decode() to use fixed uint32_t buffers

2 weeks agolib: idna-punycode - Rename idna_punycode_decode() to idna_punycode_decode_utf8()
Stephan Bosch [Fri, 13 Mar 2026 00:00:12 +0000 (01:00 +0100)] 
lib: idna-punycode - Rename idna_punycode_decode() to idna_punycode_decode_utf8()

2 weeks agolib: idna-punycode - Rename 'len' parameter in idna_punycode_decode() to 'in_len'
Stephan Bosch [Tue, 7 Jul 2026 13:43:45 +0000 (15:43 +0200)] 
lib: idna-punycode - Rename 'len' parameter in idna_punycode_decode() to 'in_len'

2 weeks agolib: idna-punycode - Rename 'input' parameter in idna_punycode_decode() to 'in'
Stephan Bosch [Wed, 11 Mar 2026 21:13:33 +0000 (22:13 +0100)] 
lib: idna-punycode - Rename 'input' parameter in idna_punycode_decode() to 'in'

2 weeks agolib: idna-punycode - Rename 'out' variable in idna_punycode_decode() to 'out_pos'
Stephan Bosch [Wed, 11 Mar 2026 21:00:56 +0000 (22:00 +0100)] 
lib: idna-punycode - Rename 'out' variable in idna_punycode_decode() to 'out_pos'

2 weeks agolib: idna-punycode - Use explicit uint32_t rather than unsigned int
Stephan Bosch [Wed, 11 Mar 2026 20:32:27 +0000 (21:32 +0100)] 
lib: idna-punycode - Use explicit uint32_t rather than unsigned int

2 weeks agolib-mail: test-rfc822-parser - Consolidate domain tests in one test function
Stephan Bosch [Wed, 11 Mar 2026 20:10:27 +0000 (21:10 +0100)] 
lib-mail: test-rfc822-parser - Consolidate domain tests in one test function

2 weeks agolib: Rename punycode to idna-punycode
Stephan Bosch [Wed, 11 Mar 2026 19:41:18 +0000 (20:41 +0100)] 
lib: Rename punycode to idna-punycode

2 weeks ago.gitignore - Add /build-aux/test-driver
Stephan Bosch [Thu, 11 Dec 2025 19:57:56 +0000 (20:57 +0100)] 
.gitignore - Add /build-aux/test-driver

2 weeks agolib: punycode - Add proper attribution for original source code
Stephan Bosch [Tue, 7 Jul 2026 14:04:00 +0000 (16:04 +0200)] 
lib: punycode - Add proper attribution for original source code

2 weeks agolib: Reformat punycode.c
Stephan Bosch [Tue, 7 Jul 2026 13:10:24 +0000 (15:10 +0200)] 
lib: Reformat punycode.c

2 weeks agolib-mail: rfc822-parser - Fix punycode decoding in rfc822_parse_domain()
Stephan Bosch [Wed, 11 Mar 2026 22:15:07 +0000 (23:15 +0100)] 
lib-mail: rfc822-parser - Fix punycode decoding in rfc822_parse_domain()

2 weeks agolib-oauth2: jwt - Add test for many non-singular sibling fields
Aki Tuomi [Mon, 13 Jul 2026 06:09:33 +0000 (06:09 +0000)] 
lib-oauth2: jwt - Add test for many non-singular sibling fields

Covers oauth2_jwt_copy_fields() with many array/object siblings in a
row, exercising the array_append_space() relocation path fixed in
72f87bf4f6.

2 weeks agolib-oauth2: jwt - Avoid use-after-realloc of jwt_node front pointer
rootvector2 [Thu, 28 May 2026 06:34:25 +0000 (12:04 +0530)] 
lib-oauth2: jwt - Avoid use-after-realloc of jwt_node front pointer

oauth2_jwt_copy_fields() captures subroot from array_front(&nodes)
then calls array_append_space(&nodes) inside the inner loop. When the
append cannot extend the buffer in place it relocates it, so subroot
aliases the previous slot and later subroot->prefix / subroot->array
reads operate on the abandoned location. Refresh subroot after the
append.

2 weeks agoconfig: Delay module dlclose() until after lib_deinit()
Aki Tuomi [Mon, 13 Jul 2026 05:27:23 +0000 (05:27 +0000)] 
config: Delay module dlclose() until after lib_deinit()

module_dir_unload() ran deinit() and dlclose()d config modules before
master_service_deinit()/lib_deinit(). If a module (e.g. the
managesieve settings plugin, which pulls in libdovecot-sieve.so and
registers an event category) got dlclose()d, its memory was unmapped
before lib_event_deinit()'s fuzzing-build category cleanup ran,
causing a crash on process exit.

Split module_dir_unload() into module_dir_deinit() (while lib is
still alive) followed by master_service_deinit(), and only then
dlclose() the modules via module_dir_unload().

2 weeks agoimap: Change imap_compress_on_proxy default to yes
Markus Valentin [Thu, 9 Jul 2026 09:15:45 +0000 (09:15 +0000)] 
imap: Change imap_compress_on_proxy default to yes

2 weeks agovalgrind: Add openssl suppressions
Aki Tuomi [Sun, 12 Jul 2026 05:28:25 +0000 (08:28 +0300)] 
valgrind: Add openssl suppressions

2 weeks agolib-ssl-iostream: Don't call OPENSSL_cleanup() for openssl 4.0
Hector Cao [Sat, 11 Jul 2026 08:04:51 +0000 (10:04 +0200)] 
lib-ssl-iostream: Don't call OPENSSL_cleanup() for openssl 4.0

With OpenSSL 4.0 the lib-dcrypt unit tests (test-crypto, test-stream)
segfault at process exit.

dovecot_openssl_common_global_ref() installs custom OpenSSL memory
functions via CRYPTO_set_mem_functions(); their code lives inside the
dynamically loaded OpenSSL/dcrypt module. It also passes
OPENSSL_INIT_NO_ATEXIT so that libcrypto does not register its own
atexit(OPENSSL_cleanup) handler.

dovecot_openssl_common_global_unref() then called OPENSSL_cleanup()
explicitly. On OpenSSL 4.0 this explicit cleanup re-arms an internal
exit-time cleanup that runs after main() returns [1], i.e. after the module
(and therefore the custom memory functions) has been dlclose()d, so
libcrypto calls the now-unmapped free() function and the process crashes
with SIGSEGV.

[1] https://openssl-library.org/post/2026-03-10-remove-atexit/index.html

Signed-off-by: Hector Cao <hector.cao@canonical.com>
2 weeks agolib-http: chunked - Fix UB on chunk-size overflow
Aki Tuomi [Fri, 10 Jul 2026 07:49:32 +0000 (07:49 +0000)] 
lib-http: chunked - Fix UB on chunk-size overflow

Overflow check ran after the shift, so chunk_size <<= 4 itself
overflowed once the top 4 bits were set, triggering UBSan. Check
before shifting instead, same as str_parse_uintmax_hex() in strnum.c.

Add a test case with an oversized chunk-size to catch this.

2 weeks agolib: ostream-multiplex - Fix busy-loop on a single sendv larger than IO_BLOCK_SIZE
Timo Sirainen [Thu, 9 Jul 2026 20:29:29 +0000 (20:29 +0000)] 
lib: ostream-multiplex - Fix busy-loop on a single sendv larger than IO_BLOCK_SIZE

o_stream_multiplex_ochannel_sendv() grows the effective avail to
IO_BLOCK_SIZE when the channel buffer is small and the parent has no
un-flushed data, so a max_buffer_size==0 parent (which always reports
avail==0) can still be written to. But the following "avail < total"
block then re-read avail from the real buffer (0 again) and returned 0,
discarding the grown value. When a single sendv was larger than
IO_BLOCK_SIZE this buffered nothing and returned 0, so the caller made
no forward progress.

With o_stream_send_istream() of an in-memory istream - which returns all
of its data in one read - a field larger than IO_BLOCK_SIZE is sent as
one oversized sendv. The channel kept accepting nothing while the parent
socket stayed writable, so the flush was rescheduled immediately and the
process busy-looped at ~100% CPU.

2 weeks agoplugins/welcome: For Pro edition, add service/welcome/unix_listener defaults
alexandre.roux [Fri, 19 Dec 2025 03:04:44 +0000 (12:04 +0900)] 
plugins/welcome: For Pro edition, add service/welcome/unix_listener defaults

2 weeks agoplugins/welcome: Add service/welcome as default
alexandre.roux [Thu, 11 Dec 2025 10:30:36 +0000 (19:30 +0900)] 
plugins/welcome: Add service/welcome as default

2 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

2 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

2 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.

2 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.

2 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.

2 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()

2 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()

2 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.

2 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.

2 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

2 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

2 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

2 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

2 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.