diff -pruN 1:2.3.16+dfsg1-3/ChangeLog 1:2.3.19.1+dfsg1-2/ChangeLog
--- 1:2.3.16+dfsg1-3/ChangeLog	2021-08-06 09:26:32.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/ChangeLog	2022-06-14 06:55:44.000000000 +0000
@@ -1,228 +1,8013 @@
-2021-08-05 18:25:31 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7e2e900c1a)
+2022-06-13 13:28:22 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (9b53102964)
 
-    NEWS: Add 100% master process CPU item
+    NEWS: Add news for 2.3.19.1
 
 
 M	NEWS
 
-2021-08-05 17:48:42 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9ac07b89c1)
+2022-06-13 09:56:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a04e1b9abf)
+
+    doveadm deduplicate: Allocate memory properly for keys in hash table
+
+    This caused wrong mails to be deleted somewhat randomly. Broken by
+    320844f50cd669b602d30210e2e5216f65d2050f
+
+M	src/doveadm/doveadm-mail-deduplicate.c
+
+2022-05-24 15:52:26 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (38b7195efe)
+
+    lib-compression: istream-decompress - Copy parent stream name
+
+    This is needed, because istream-decompress doesn't actually use the parent 
+    stream as the istream parent.
+
+M	src/lib-compression/istream-decompress.c
+
+2022-05-18 11:31:44 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8a8380a410)
+
+    auth: Fix assert-crash in iterating multiple userdbs
+
+    Broken by 501e17ba6b448ba3c88338596e0e8f99f0693f79
+
+    Fixes: Panic: file userdb-blocking.c: line 125 (userdb_blocking_iter_next):
+    assertion failed: (ctx->conn != NULL)
+
+M	src/auth/auth-master-connection.c
+
+2022-05-04 15:47:03 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (b3ad6004dc)
+
+    NEWS: Update date for 2.3.19 news
+
+
+M	NEWS
+
+2022-05-03 00:27:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f539a7e18c)
+
+    fts: Fix optimizing searches in virtual mailboxes without up-to-date indexes
+
+    This could have caused header searches in virtual mailboxes to not return 
+    all results when fts_enforced!=yes
+
+    Broken by 9705b81fb51b5bdeaba12932a390ced2cc9dcad7
+
+M	src/plugins/fts/fts-storage.c
+
+2022-04-26 09:30:10 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (e55db06e1a)
+
+    NEWS: Add news for 2.3.19
+
+
+M	NEWS
+
+2022-02-01 19:29:46 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (1f1e770332)
+
+    NEWS: Update news for 2.3.18 - final fixups
+
+
+M	NEWS
+
+2022-01-12 11:34:02 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (4ab62982e3)
+
+    NEWS: Update news for 2.3.18
+
+
+M	NEWS
+
+2022-04-25 10:43:45 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (f28361b357)
+
+    lib-lua: dlua-dovecot-http - Support more settings with HTTP client
+
+
+M	src/lib-lua/dlua-dovecot-http.c
+
+2022-04-25 14:27:12 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (c6dd084a6d)
+
+    lib-http: Rename max_auto_retry_delay to max_auto_retry_delay_secs
+
+
+M	src/lib-http/http-client-request.c
+M	src/lib-http/http-client.c
+M	src/lib-http/http-client.h
+
+2022-04-26 09:35:15 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (f3bfa1d5ab)
+
+    configure: Update version
+
+
+M	configure.ac
+
+2022-04-19 11:42:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1c6c3c320d)
+
+    auth: auth_worker_call() - Return void instead of the connection
+
+    The return value is no longer necessary, and it most likely would just be 
+    used wrong.
+
+M	src/auth/auth-worker-server.c
+M	src/auth/auth-worker-server.h
+
+2022-04-19 11:40:52 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (94577c4450)
+
+    auth: Fix crash when user iteration request is queued
+
+    auth_worker_call() returns NULL when the request couldn't be handled 
+    immediately, which would result in NULL pointer dereference later on.
+
+M	src/auth/userdb-blocking.c
+
+2022-04-19 11:39:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f89c9e587e)
+
+    auth: Add connection parameter to auth_worker_callback_t
+
+
+M	src/auth/auth-worker-server.c
+M	src/auth/auth-worker-server.h
+M	src/auth/passdb-blocking.c
+M	src/auth/passdb-cache.c
+M	src/auth/userdb-blocking.c
+
+2022-04-14 13:46:59 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (865fb4f3c1)
+
+    doveadm: Fix hang when flushing a corked print-ostream
+
+    This could have happened at least with doveadm sync/backup command, i.e.
+    causing replication to hang until timeout is reached:
+
+    Error: write(<local>) failed: Timed out after 60 seconds
+
+M	src/doveadm/doveadm-print-server.c
+
+2022-04-08 15:59:59 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0dd36a73e4)
+
+    lib-index: Always write tail offset the same as head offset
+
+    The mail_index_write() must not be called unless this is safe to do. This
+    prevents unnecessarily reading through dovecot.index.log between tail..head
+    offsets, which can be expensive due to modseq calculation.
+
+M	src/lib-index/mail-index-write.c
+
+2022-04-08 15:10:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (aa7d67e6d6)
+
+    lib-index: Optimize modseq scanning by using the modseq header in index
+
+    View is very commonly set to start reading new changes since dovecot.index 
+    was last written. When reading the newer records in dovecot.index.log, the 
+    modseq of each change is needed to be known. However, the initial modseq 
+    calculation was usually done inefficiently by reading the whole 
+    dovecot.index.log from the beginning of the file. This change prevents that
+    by using instead the "modseq" header in dovecot.index to get the initial
+    modseq.
+
+M	src/lib-index/mail-index-map-read.c
+M	src/lib-index/mail-index-modseq.c
+M	src/lib-index/mail-index-modseq.h
+M	src/lib-index/mail-index-private.h
+M	src/lib-index/mail-transaction-log-modseq.c
+
+2022-04-08 13:42:55 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (40e2a21f83)
+
+    lib-index: Handle 0-sized dovecot.index.log record properly
+
+    The previous behavior was to just silently ignore it and have the next write 
+    to the transaction log silently truncate away the trailing garbage.
+
+    Now if the log file isn't locked the issue is still ignored, since it's 
+    possible that this is just a race condition. But if the log is locked, it's
+    handled as corruption.
+
+M	src/lib-index/mail-transaction-log-file.c
+
+2022-04-12 10:12:11 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (36cbfb8e2b)
+
+    lib: test-event-flatten - Use __FILE__ to get correct expected filename
+
+    Fixes out of tree buids
+
+    Broken in 4f752d381c8e19dd07d1e593996d02294668c8b6
+
+M	src/lib/test-event-flatten.c
+
+2022-03-01 10:01:06 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (5c2f3d0b85)
+
+    auth: db-oauth2 - Fix off by one in oauth2 variable handling
+
+    Broken in 9b670175445a75987a713ff899d1a945255b0b5b
+
+M	src/auth/db-oauth2.c
+
+2022-03-21 11:03:03 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (dc40c6dbcf)
+
+    dsync: Properly terminate escape_chars when escaping mailbox names
+
+    Broken by 596c5a52e7e554571285e90063712cb0d37b34eb
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+
+2022-03-02 09:39:30 -0500 Timo Sirainen <timo.sirainen@open-xchange.com> (9e7503d439)
+
+    dsync: Fix hierarchical mailbox name parts individually
+
+    For example with filesystem-based mailbox formats it's not allowed to create
+    "box/../child" mailbox. With previous code dsync just gave up and created 
+    the mailbox name based on its GUID. This is now improved to instead try to 
+    insert '_' character after each hierarchy separator so the newly fixed 
+    mailbox name is "box/_../child".
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+
+2022-03-01 08:37:57 -0500 Timo Sirainen <timo.sirainen@open-xchange.com> (801aae4bd5)
+
+    dsync: Escape and unescape mailbox names as needed
+
+    This should fix various issues with syncing local and remote mailbox names. 
+    It especially avoids changing the escape character to alt_char.
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+M	src/doveadm/dsync/dsync-mailbox-tree.c
+M	src/doveadm/dsync/test-dsync-mailbox-tree-sync.c
+
+2022-03-01 11:51:04 -0500 Timo Sirainen <timo.sirainen@open-xchange.com> (cfd1b0687f)
+
+    dsync: Track whether escape character was added just for dsync
+
+    Will be used by the following commit
+
+M	src/doveadm/doveadm-dsync.c
+M	src/doveadm/dsync/dsync-brain.c
+M	src/doveadm/dsync/dsync-brain.h
+
+2022-03-01 08:36:55 -0500 Timo Sirainen <timo.sirainen@open-xchange.com> (60ae30dd9e)
+
+    dsync: Add clarifying comment about mailbox_log_record.maibox_guid contents
+
+
+M	src/doveadm/dsync/dsync-mailbox-tree-fill.c
+
+2022-03-01 08:06:14 -0500 Timo Sirainen <timo.sirainen@open-xchange.com> (3864ecbfc7)
+
+    dsync: Add escape_char to dsync-mailbox-tree
+
+    This will be used by the following changes.
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+M	src/doveadm/dsync/dsync-brain-private.h
+M	src/doveadm/dsync/dsync-ibc-pipe.c
+M	src/doveadm/dsync/dsync-ibc-private.h
+M	src/doveadm/dsync/dsync-ibc-stream.c
+M	src/doveadm/dsync/dsync-ibc.c
+M	src/doveadm/dsync/dsync-ibc.h
+M	src/doveadm/dsync/dsync-mailbox-tree-private.h
+M	src/doveadm/dsync/dsync-mailbox-tree.c
+M	src/doveadm/dsync/dsync-mailbox-tree.h
+M	src/doveadm/dsync/test-dsync-mailbox-tree-sync.c
+
+2022-03-18 09:41:37 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (f95da1fa2f)
+
+    lib-test: test-subprocess - Free subprocess before forking
+
+    Otherwise it'll leak memory.
+
+    Broken in 34bdfdcbc7e3b374a219732329b6ce6d84a7666e
+
+M	src/lib-test/test-subprocess.c
+
+2021-10-25 12:23:56 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (25ae16cb40)
+
+    lib-lua: Add a minimal interface to lib-http
+
+    Adds the ability to
+    - Creating http clients
+    - Creating http requests
+    - Adding arbitrary headers and payload to the http request
+    - Submitting the request to remote server and getting the response
+
+M	src/lib-lua/Makefile.am
+A	src/lib-lua/dlua-dovecot-http.c
+M	src/lib-lua/dlua-dovecot.c
+M	src/lib-lua/dlua-script-private.h
+
+2022-03-14 10:18:58 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (e5ae6b26e9)
+
+    doveadm-who: Do not parse numbers as IP addresses
+
+    Fixes doveadm kick as well.
+
+    Broken in 381daab1e3b56a0bc94d2191cf62beba0df51af9
+
+M	src/doveadm/doveadm-who.c
+
+2022-03-01 12:06:20 +0100 Markus Valentin <markus.valentin@open-xchange.com> (6026905b7a)
+
+    lib: test-seqset-builder - Add missing seqset_builder_deinit()
+
+    Fixes leaking memory when running tests.
+
+M	src/lib/test-seq-set-builder.c
+
+2022-03-01 13:49:39 +0100 Markus Valentin <markus.valentin@open-xchange.com> (4540884c46)
+
+    imapc: imapc_transaction_save_rollback() - Fix NULL-check for ctx->src_mbox
+
+    Assert that unfinished context implies that ctx->src_mbox is non-NULL. Also
+    check for ctx->src_mbox being non-NULL before deinitializing it.
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2022-02-28 13:15:06 +0100 Markus Valentin <markus.valentin@open-xchange.com> (e5a07bd1c4)
+
+    imapc: imapc_mailbox_msgmap_update() - Set new_message_r
+
+    In case a message has not yet been keep the reply till syncing and do not
+    discard it right away.
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-02-24 15:31:39 +0100 Markus Valentin <markus.valentin@open-xchange.com> (f5fc15b1b1)
+
+    imapc: Delay fetching state after untagged exists reply
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+M	src/lib-storage/index/imapc/imapc-storage.h
+M	src/lib-storage/index/imapc/imapc-sync.c
+
+2021-12-16 09:11:55 +0100 Markus Valentin <markus.valentin@open-xchange.com> (173cc696f8)
+
+    imapc: imapc_copy() - Make sure capabilities are known before copying
+
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2021-11-11 16:17:35 +0100 Markus Valentin <markus.valentin@open-xchange.com> (09bfbb4cb1)
+
+    imapc: Implement rollback for failed copies
+
+
+M	src/lib-storage/index/imapc/imapc-save.c
+M	src/lib-storage/index/imapc/imapc-storage.c
+M	src/lib-storage/index/imapc/imapc-storage.h
+
+2021-11-11 18:31:54 +0100 Markus Valentin <markus.valentin@open-xchange.com> (95baaaf454)
+
+    imapc: imapc_save_copyuid() deduplicate code
+
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2021-11-11 16:18:09 +0100 Markus Valentin <markus.valentin@open-xchange.com> (1e888011d0)
+
+    imapc: Enable bulk copying if remote backend has UIDPLUS capability
+
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2021-11-11 14:52:54 +0100 Markus Valentin <markus.valentin@open-xchange.com> (79b5dc6024)
+
+    imapc: Implement bulk copying for imapc
+
+
+M	src/lib-storage/index/imapc/imapc-save.c
+M	src/lib-storage/index/imapc/imapc-storage.c
+M	src/lib-storage/index/imapc/imapc-storage.h
+
+2021-11-11 16:10:53 +0100 Markus Valentin <markus.valentin@open-xchange.com> (d8ac772071)
+
+    imapc: Extract imapc_copy_simple() from imapc_copy()
+
+    Keep the old one by one copying functionality as simple call. This will be
+    used if the remote backend does not have the UIDPLUS capability.
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2022-01-12 15:09:16 +0100 Markus Valentin <markus.valentin@open-xchange.com> (dd9923ed99)
+
+    lib-storage: index_save_context_free() - Add assertion on non-NULL
+    ctx->dest_mail
+
+
+M	src/lib-storage/index/index-storage.c
+
+2021-12-08 17:17:56 +0100 Markus Valentin <markus.valentin@open-xchange.com> (ed093cb058)
+
+    lib: Add seq-set-builder and tests for it
+
+
+M	src/lib/Makefile.am
+A	src/lib/seq-set-builder.c
+A	src/lib/seq-set-builder.h
+M	src/lib/test-lib.inc
+A	src/lib/test-seq-set-builder.c
+
+2022-02-10 17:37:59 +0100 Marco Bettini <marco.bettini@open-xchange.com> (0b8d22e109)
+
+    lib-storage: imapc_mailbox_close() - Remove unprocessed
+    untagged_fetch_contexts
+
+
+M	src/lib-storage/index/imapc/imapc-storage.c
+
+2022-02-10 16:26:10 +0100 Marco Bettini <marco.bettini@open-xchange.com> (589f8b1eba)
+
+    lib-storage: Remove trainling spaces
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+M	src/lib-storage/index/imapc/imapc-sync.c
+
+2022-02-02 16:58:24 +0100 Markus Valentin <markus.valentin@open-xchange.com> (6b7a40bd5d)
+
+    imapc: imapc_sync_handle_untagged_fetches() - Commit after adding the
+    untagged fetch messages
+
+    The absence of the commit could have caused issues when imapc_sync_finish()
+    was not called as it left mbox->delayed_sync_trans initialized but
+    mbox->delayed_sync_view was NULL.
+
+    Broken by 55a8c2d294bb2f764209c7ce455d258b2b7506f5
+
+M	src/lib-storage/index/imapc/imapc-sync.c
+
+2022-01-18 09:46:32 +0100 Markus Valentin <markus.valentin@open-xchange.com> (4b517a91b4)
+
+    imapc: Start to handle new untagged fetch messages in imapc_sync()
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-01-18 09:22:24 +0100 Markus Valentin <markus.valentin@open-xchange.com> (6d505f1b39)
+
+    imapc: Add imapc_sync_handle_untagged_fetches()
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+M	src/lib-storage/index/imapc/imapc-storage.c
+M	src/lib-storage/index/imapc/imapc-storage.h
+M	src/lib-storage/index/imapc/imapc-sync.c
+
+2022-01-17 15:41:25 +0100 Markus Valentin <markus.valentin@open-xchange.com> (aa26f7a231)
+
+    imapc: imapc_untagged_fetch_handle() - Change return type to bool
+
+    When calling imapc_untagged_fetch_handle() also retrieve new_message_r from
+    it which allows imapc_mailbox_msgmap_update() to determine if the message
+    just handled was already in index or not.
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-01-27 08:06:16 +0100 Markus Valentin <markus.valentin@open-xchange.com> (235f491ffe)
+
+    imapc: imapc_untagged_fetch_update_flags() - Reduce indentation
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-01-27 07:56:32 +0100 Markus Valentin <markus.valentin@open-xchange.com> (ea5eb228c7)
+
+    imapc: Split off imapc_untagged_fetch_update_flags() for handling flag
+    updates
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-01-17 15:00:41 +0100 Markus Valentin <markus.valentin@open-xchange.com> (4656356f1e)
+
+    imapc: Introduce imapc_untagged_fetch_ctx
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+M	src/lib-storage/index/imapc/imapc-storage.h
+
+2022-01-17 13:57:56 +0100 Markus Valentin <markus.valentin@open-xchange.com> (332c4ef1a4)
+
+    imapc: Decouple imapc_untagged_fetch parsing and handling
+
+
+M	src/lib-storage/index/imapc/imapc-mailbox.c
+
+2022-03-09 02:30:12 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a987d6b01f)
+
+    submission-login: Add workaround for SMTP backend that is not Dovecot.
+
+    Adds workaround called "exotic-backend".
+
+M	src/submission-login/client-authenticate.c
+M	src/submission-login/submission-login-settings.c
+M	src/submission-login/submission-login-settings.h
+M	src/submission/submission-settings.c
+
+2022-03-02 15:40:16 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (f703acd4a2)
+
+    auth: auth-cache - Always use translated_username as cache key
+
+    This fixes auth cache when passdb/userdb changes the user field.
+
+M	src/auth/auth-cache.c
+
+2022-03-02 14:57:20 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (020f8d5cff)
+
+    auth: Set translated_user to requested_login_user in master login
+
+    Fixes caching issues with master logins.
+
+M	src/auth/auth-request-fields.c
+
+2021-07-06 14:02:41 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (2e5b63175d)
+
+    auth: Include cache hit/miss information in passdb/userdb lookup end events
+
+
+M	src/auth/auth-request.c
+M	src/auth/auth-request.h
+M	src/auth/passdb-cache.c
+
+2022-02-28 17:12:32 +0100 Marco Bettini <marco.bettini@open-xchange.com> (9a355e8aa7)
+
+    fts: Parse mail header before checking whether to index it
+
+    Fixes an issue where mime parts Content-type wasn't properly set, causing
+    binary mime parts to be fed to the indexes.
+
+    Broken in ddb85f3533842aa7c4e943c10bbd3dcb745c2eae
+
+M	src/plugins/fts/fts-build-mail.c
+
+2022-02-18 18:31:43 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1133504c52)
+
+    dsync: Log reason why mailbox is synced with debug logging
+
+
+M	src/doveadm/dsync/dsync-brain-mailbox.c
+M	src/doveadm/dsync/dsync-brain-mails.c
+M	src/doveadm/dsync/dsync-brain-private.h
+
+2022-02-21 23:07:11 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (8bc0825d97)
+
+    dsync: If modseqs aren't permanent, assume HIGHESTMODSEQ=0
+
+    Otherwise the HIGHESTMODSEQ is just whatever happens to be in the in-memory 
+    view of the index, which most likely isn't the true HIGHESTMODSEQ. Using 0 
+    makes it clear that the HIGHESTMODSEQ isn't valid and can't be used.
+
+M	src/doveadm/dsync/dsync-brain-mailbox.c
+M	src/doveadm/dsync/dsync-mailbox-import.c
+
+2022-02-12 15:07:39 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (9da4834ad0)
+
+    submission-login: submission-proxy - Do not include initial response in AUTH
+    command if it is too long.
+
+
+M	src/submission-login/client.h
+M	src/submission-login/submission-proxy.c
+
+2022-02-21 21:36:55 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (9a16de7aad)
+
+    submission-login: submission-proxy - Rename local variable in
+    proxy_send_login().
+
+
+M	src/submission-login/submission-proxy.c
+
+2022-02-21 21:32:45 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (7a79eba26e)
+
+    lib-smtp: smtp-client - Move SMTP_BASE_LINE_LENGTH_LIMIT definition to
+    smtp-common.h.
+
+
+M	src/lib-smtp/smtp-client-private.h
+M	src/lib-smtp/smtp-common.h
+
+2022-02-21 21:29:23 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (10b0d57967)
+
+    lib-smtp: smtp-client-connection - Rename SMTP_CLIENT_BASE_LINE_LENGTH_LIMIT
+    to SMTP_BASE_LINE_LENGTH_LIMIT.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/smtp-client-private.h
+
+2022-02-23 14:21:02 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (84fe9d12fc)
+
+    lib-smtp: smtp-client-connection - Fix typo in comment.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2022-02-22 14:14:20 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (d5d8b69aa7)
+
+    director: Fix crashes caused by changing host tag
+
+    Fixes: Panic: file director.c: line 1175 (director_move_user): assertion
+    failed: (user->host->tag == host->tag) Panic: file director-request.c: line
+    303 (director_request_continue_real): assertion failed: (user->host->tag ==
+    mail_tag)
+
+M	src/director/mail-host.c
+
+2022-02-22 14:10:02 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (44b7eda458)
+
+    director: Prevent loops where directors keep changing host tags
+
+
+M	src/director/director-connection.c
+
+2022-02-21 12:47:29 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (7a64ec1ef8)
+
+    lib: If log prefix update can't be sent to log, exit with FATAL_LOGERROR
+
+
+M	src/lib/failures.c
+
+2022-02-21 12:39:02 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (1bf3963714)
+
+    lib: Fix losing log prefix or IP change when log process is busy
+
+    The fd for writing to log process is non-blocking. When sending options to
+    log process, it was done with write_full(), which stopped whenever EAGAIN
+    was returned. This error was simply ignored, and the logging code thought
+    that the prefix was successfully changed (or the IP was sent).
+
+    This mainly caused a problem when processes were reused and log process was
+    busy. The prefix update could have failed, and the following logging would
+    be using the previous session's log prefix, i.e. log lines could have been
+    logged for the wrong user/session.
+
+    Broken by 9372e48b702a3af5705785e08fbf47b0e37f2047
+
+M	src/lib/failures.c
+
+2022-02-06 14:20:08 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (3e8befe6b5)
+
+    dict: Fix potential timeout leak at deinit
+
+    The deinit code could still trigger proctitle refresh.
+
+M	src/dict/main.c
+
+2022-02-22 11:23:33 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (17118d5970)
+
+    dict: Make sure proctitle refresh timeout goes to the main ioloop
+
+    In some situations it could have gone to dict_wait() ioloop, causing timeout
+    leaks or crashes.
+
+M	src/dict/main.c
+
+2021-10-25 15:43:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (974e970889)
+
+    lib-storage: Don't use cached message_parts while message is being parsed
+
+    Finish the parsing instead. Otherwise there can be some confusion about 
+    parsed_bodystructure* fields, which indicate that data->parts have the 
+    bodystructure info while in reality data->parts came from cached fields and
+    they have no bodystructure info.
+
+M	src/lib-storage/index/index-mail.c
+
+2021-10-25 12:22:06 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6b6d61e9d1)
+
+    lib-storage: Reset bodystructure parsing state when reseting message_parts
+
+    Fixes: Panic: file message-part-data.c: line 28
+    (message_part_data_is_plain_7bit): assertion failed: (data != NULL)
+
+M	src/lib-storage/index/index-mail-headers.c
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/index-mail.h
+M	src/lib-storage/test-mail.c
+
+2022-02-09 23:12:19 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d31af56e9a)
+
+    dict-sql: Fix assert-crash if trying to rollback an open set/inc transaction
+
+    Fixes: Panic: file dict-sql.c: line 911 (sql_dict_transaction_free):
+    assertion failed: (!array_is_created(&ctx->prev_set))
+
+M	src/lib-dict-backend/dict-sql.c
+
+2022-02-04 04:30:48 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (b743c13533)
+
+    anvil: connect_limit_deinit() - Free ident_pid_hash elements
+
+
+M	src/anvil/connect-limit.c
+
+2022-02-11 09:17:58 +0100 Marco Bettini <marco.bettini@open-xchange.com> (feebfa572a)
+
+    indexer: indexer_client_status_callback() - Fix accessing freed memory
+
+    Broken by a9683d7b3
+
+M	src/indexer/indexer-client.c
+
+2022-02-07 16:04:41 +0100 Marco Bettini <marco.bettini@open-xchange.com> (e65b925b86)
+
+    indexer: Fix memory leak on indexer timeout
+
+
+M	src/indexer/indexer-client.c
+
+2022-02-04 01:28:45 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (ff258ecea5)
+
+    stats: stats_metrics_remove_dynamic() - Free the removed metric
+
+
+M	src/stats/stats-metrics.c
+
+2022-02-04 01:32:18 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (8437a9224f)
+
+    stats: stats_metric_alloc() - Fix indentation on p_new() call
+
+
+M	src/stats/stats-metrics.c
+
+2022-01-31 12:42:46 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (c5ef0ce4b7)
+
+    replication: aggregator - Free replicator_connection content
+
+
+M	src/replication/aggregator/replicator-connection.c
+
+2022-02-01 14:22:31 +0100 Marco Bettini <marco.bettini@open-xchange.com> (12a751615b)
+
+    mail-crypt: Fix for mail being wrongly storing encrypted via LMTP
+
+    If 1st recipient has mail_crypt_save_version=2, and 2nd recipient has
+    mail_crypt_save_version=0, the mail for 2nd recipient is wrongly stored
+    encrypted.
+
+    Same happens if 2nd recipient has mail_crypt disabled
+
+M	src/plugins/mail-crypt/mail-crypt-plugin.c
+
+2020-03-31 13:51:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a707e6907c)
+
+    mail-crypt: Remove "plugin disabled" texts from errors
+
+    The plugin isn't just disabled, the user initialization fails entirely.
+
+M	src/plugins/mail-crypt/mail-crypt-plugin.c
+
+2020-03-31 13:49:28 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4514e72dd7)
+
+    mail-crypt: Fix crash when plugin is already initialized, but not used for
+    another user
+
+    Fixes: Panic: Module context mail_crypt_user_module missing
+
+M	src/plugins/mail-crypt/mail-crypt-plugin.c
+
+2021-04-29 21:09:01 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (41e439f993)
+
+    doveadm deduplicate: Simplify code by using doveadm_mail_iter_deinit_sync()
+
+
+M	src/doveadm/doveadm-mail-deduplicate.c
+
+2021-04-29 21:05:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d60ca6eaac)
+
+    doveadm deduplicate: Optimize by deduplicating in a single transaction
+
+
+M	src/doveadm/doveadm-mail-deduplicate.c
+
+2022-02-14 13:50:02 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (70a924bd84)
+
+    pop3: Fix empty command handling
+
+    If empty command is given, error out instead of trying to create event. This
+    fixes a crash caused by d2ab26be6038bd53b13a3ff18c403d6c192c1d91.
+
+M	src/pop3/pop3-client.c
+
+2022-02-02 17:01:49 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (6b66c7694e)
+
+    lib-master: Fix deinit memory leak with process_shutdown_filter
+
+
+M	src/lib-master/master-service.c
+
+2021-12-17 09:27:07 +0100 Marco Bettini <marco.bettini@open-xchange.com> (3b05ece496)
+
+    plugins/fts: Allow plugins to carry state across calls during search session
+
+
+M	src/plugins/fts/fts-api.h
+M	src/plugins/fts/fts-search.c
+M	src/plugins/fts/fts-storage.h
+
+2022-01-26 16:09:29 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d6f90fc69e)
+
+    auth: ldap: Drop partially saved results before retrying request
+
+    Fixes "LDAP search returned multiple entries" happening after reconnects.
+
+M	src/auth/db-ldap.c
+
+2022-01-26 16:03:27 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c870c58f3f)
+
+    auth: ldap: If any requests were lost, reconnect to LDAP server
+
+
+M	src/auth/db-ldap.c
+
+2022-01-26 16:00:45 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (30325b92b7)
+
+    auth: ldap: If LDAP connection appears to be hanging, abort all old requests
+
+    Retrying the reconnect might fix them, but since this situation isn't 
+    supposed to happen in the first place, it's safer to just remove them 
+    entirely to guarantee that it's not trying to keep retrying them for a long
+    time.
+
+M	src/auth/db-ldap.c
+
+2022-01-26 14:46:55 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (6360d26c8a)
+
+    auth: ldap: Reconnect to server if receiving unknown msgid
+
+    It's unclear why this happens. Is it a bug on server or client side? Either
+    way, this situation doesn't now fix itself automatically, so reconnecting
+    should help.
+
+M	src/auth/db-ldap.c
+
+2022-01-26 14:43:01 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (88d55b93ba)
+
+    auth: ldap: Stop re-sending request after 3 disconnect+reconnects
+
+    This prevents retrying the same LDAP request forever in situations where the 
+    request causes LDAP server to become disconnected. This might fix some real 
+    issues, but it was mainly implemented because testing the following commit 
+    caused infinite looping.
+
+M	src/auth/db-ldap.c
+M	src/auth/db-ldap.h
+
+2022-02-01 14:57:16 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (342c872791)
+
+    lmtp: proxy - Add per-connection counter to session_id
+
+    This way connections to two different backends don't try to use the same 
+    session_id. Add 'P' letter before the counter to clarify that it's the proxy
+    connection counter.
+
+M	src/lmtp/lmtp-proxy.c
+
+2022-02-01 14:40:48 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (84ebee8902)
+
+    lmtp: Simplify/clarify per-recipient session ID
+
+    The session ID is the transaction ID followed by an increasing recipient 
+    count (number of RCPT commands) in the SMTP transaction. Clarify this by 
+    adding 'R' letter before the counter. Also don't add the counter suffix at 
+    all for the first recipient, since most transactions only have a single 
+    recipient.
+
+M	src/lmtp/lmtp-recipient.c
+
+2022-02-01 14:35:05 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (edf91ac779)
+
+    lib-smtp: server-transaction - Simplify/clarify transaction ID
+
+    The transaction ID is the session ID followed by an increasing transaction 
+    count (number of MAIL commands) in the SMTP server connection. Clarify this
+    by adding 'T' letter before the counter. Also don't add the counter suffix
+    at all for the first session, since most sessions only have a single
+    transaction.
+
+M	src/lib-smtp/smtp-server-transaction.c
+
+2022-02-01 14:29:43 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (f26b7d3c13)
+
+    lib-smtp, lmtp: Add brackets to <session-id> in logging
+
+    This makes it clearer where the session-id stops. It's also similar to how
+    it is in mail_log_prefix.
+
+M	src/lib-smtp/smtp-server-transaction.c
+M	src/lmtp/lmtp-proxy.c
+
+2022-02-01 15:03:15 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (7a168bb5b5)
+
+    lmtp: proxy - Use recipient-specific session-id when logging the result
+
+    Instead of transaction ID, which is shared between recipients.
+
+M	src/lmtp/lmtp-proxy.c
+
+2022-02-01 16:00:36 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (d107539524)
+
+    lmtp: struct lmtp_recipient - Change all strings to const
+
+
+M	src/lmtp/lmtp-recipient.h
+
+2022-01-27 21:40:25 +0100 Markus Valentin <markus.valentin@open-xchange.com> (9e151e0fd9)
+
+    lib-storage: LAYOUT=index: Fix accessing freed memory when deleting
+    \Noselect parents
+
+    Broken by f5328d6f7e4a8e460c736fa0336f5766aa58abda
+
+M	src/lib-storage/list/mailbox-list-index-backend.c
+
+2022-01-28 10:54:53 +0100 Markus Valentin <markus.valentin@open-xchange.com> (dc5e01c2b2)
+
+    lib-http: http_server_resource_create() - Remove unnecessary pool allocation
+
+    Fixes leaking the memory pool created in stats_http_resource_add()
+
+M	src/lib-http/http-server-resource.c
+
+2022-01-26 04:00:32 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (4a4a4cf02b)
+
+    lib-smtp: test-smtp-client-errors - Add test for succesful authentication
+    with large initial response.
+
+
+M	src/lib-smtp/test-smtp-client-errors.c
+
+2022-01-26 03:58:49 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (3de3cd5987)
+
+    lib-smtp: smtp-client-connection - Fix authentication with multi-roundtrip
+    SASL mechanisms.
+
+    Before, it would fail with an unexpected reply error.
+
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/smtp-client-private.h
+
+2022-01-24 01:39:19 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (1a09d82bee)
+
+    lib-smtp: test-smtp-client-errors - Add test for successful authentication.
+
+
+M	src/lib-smtp/test-smtp-client-errors.c
+
+2022-01-24 01:41:15 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (20d406b91c)
+
+    lib-smtp: test-smtp-client-errors - Rename "authentication failed" test to
+    "authentication".
+
+
+M	src/lib-smtp/test-smtp-client-errors.c
+
+2022-01-28 03:14:16 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (5ad9c03dc4)
+
+    lib-smtp: smtp-client - Fix maximum line length to include CRLF.
+
+
+M	src/lib-smtp/smtp-client-private.h
+
+2022-01-21 01:08:31 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (c8671f74ee)
+
+    lib-smtp: smtp-client-connection - Do not include initial response in AUTH
+    command if it is too long.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/smtp-client-private.h
+
+2022-01-21 02:26:18 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (02e43414e0)
+
+    lib-smtp: smtp-client-connection - Move auth cleanup to separate function.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2022-01-28 03:17:59 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (76810027a5)
+
+    lib-sasl: dsasl-client - Make dsasl_client_free(&NULL) a no-op.
+
+
+M	src/lib-sasl/dsasl-client.c
+M	src/lib-smtp/smtp-client-connection.c
+M	src/login-common/client-common-auth.c
+M	src/login-common/client-common.c
+
+2022-01-21 02:28:23 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (94c8febccc)
+
+    lib-smtp: smtp-client-connection - Reformat comment.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2022-01-26 19:28:33 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (3e0a147292)
+
+    indexer: Fix memory leak - worker_requests were never freed
+
+    Broken by 141766b24f885259508ae39f2e18811018373bc7
+
+M	src/indexer/indexer.c
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+
+2022-01-25 23:31:34 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (8e0763ac32)
+
+    indexer: Fix memory leak - session IDs were never freed
+
+    Broken by a8dcd4e2332c73087e9b148d34259230a77edb28
+
+M	src/indexer/indexer-queue.c
+
+2021-12-01 12:21:48 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (7b96f6cbf5)
+
+    lib-storage: Fix search query that only contains SEARCH_MAILBOX_GUID
+
+    Fixes assert-crash in virtual mailbox:
+
+    Panic: file virtual-search.c: line 77 (virtual_search_get_records):
+    assertion failed: (result != 0)
+
+M	src/lib-storage/index/index-search-private.h
+M	src/lib-storage/index/index-search.c
+
+2021-12-16 11:49:24 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (a97162ccc6)
+
+    lib-master: Add process_shutdown_filter setting
+
+
+M	src/lib-master/master-service-private.h
+M	src/lib-master/master-service-settings.c
+M	src/lib-master/master-service-settings.h
+M	src/lib-master/master-service.c
+M	src/lib-master/master-service.h
+
+2021-12-16 11:44:38 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (14f8d9c734)
+
+    lib-master: Refactor parsing event filters from settings.
+
+
+M	src/lib-master/master-service-settings.c
+
+2021-12-16 11:38:53 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (623a7b2be8)
+
+    lib-master: Remove check for client->filter == NULL before
+    event_filter_match().
+
+
+M	src/lib-master/stats-client.c
+
+2021-12-16 11:38:08 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (8e99dae094)
+
+    lib: Add check for NULL to event_filter_match().
+
+
+M	src/lib/event-filter.c
+
+2021-12-15 15:19:04 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (181f1456d6)
+
+    lib-master: Whitespace cleanup.
+
+
+M	src/lib-master/master-service-private.h
+M	src/lib-master/master-service.c
+M	src/lib-master/master-service.h
+
+2021-12-01 13:35:12 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (1425e94b93)
+
+    lib-storage: Emit event with process stat in mail_user_deinit().
+
+
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mail-user.h
+
+2021-12-07 16:58:43 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (d8c4013cdc)
+
+    lib: Add event_add_int_non_zero()
+
+
+M	src/lib/lib-event.c
+M	src/lib/lib-event.h
+
+2021-12-01 13:34:22 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (0ba13e8e70)
+
+    lib: Add functionality for acquiring process stat.
+
+
+M	src/lib/Makefile.am
+A	src/lib/process-stat.c
+A	src/lib/process-stat.h
+
+2021-12-21 10:47:54 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (3a25648a1d)
+
+    lib: add timeval_to_usecs()
+
+
+M	src/lib/time-util.h
+
+2021-12-20 16:17:54 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (578c5f4b8c)
+
+    lib: Whitespace cleanup
+
+
+M	src/lib/lib-event.c
+
+2022-01-14 08:07:50 +0100 Markus Valentin <markus.valentin@open-xchange.com> (1e0870724b)
+
+    lib-storage: mailbox-list: index_list_rename_mailbox() - Support NO-NOSELECT
+
+    When NO-NOSELECT is configured delete parent mailboxes which are not 
+    selectable or existant when renaming child mailboxes.
+
+M	src/lib-storage/list/mailbox-list-index-backend.c
+
+2022-01-14 08:04:09 +0100 Markus Valentin <markus.valentin@open-xchange.com> (964fe86c5c)
+
+    lib-storage: mailbox-list: index_list_delete_mailbox() - Support NO-NOSELECT
+
+    When NO-NOSELECT is configured delete parent mailboxes which are not 
+    selectable or not existant when deleting child mailboxes.
+
+M	src/lib-storage/list/mailbox-list-index-backend.c
+
+2022-01-21 16:31:04 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (77239a25c5)
+
+    doveadm fetch/search: Use DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT
+
+    These commands only write output to the client, so it's safe to stop them if
+    the client disconnects.
+
+M	src/doveadm/doveadm-mail-fetch.c
+M	src/doveadm/doveadm-mail-search.c
+
+2022-01-21 16:29:48 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (4eb82acbc3)
+
+    doveadm: doveadm_mail_iter_init() - Add
+    DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT
+
+    When this flag is used, the iteration is stopped if print ostream has 
+    reported an error, i.e. doveadm-client has disconnected.
+
+M	src/doveadm/doveadm-mail-iter.c
+M	src/doveadm/doveadm-mail-iter.h
+
+2022-01-21 16:24:39 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (2b2cdb719b)
+
+    doveadm: doveadm_mail_iter_init() - Change bool parameter to flags
+
+
+M	src/doveadm/doveadm-mail-altmove.c
+M	src/doveadm/doveadm-mail-copymove.c
+M	src/doveadm/doveadm-mail-deduplicate.c
+M	src/doveadm/doveadm-mail-expunge.c
+M	src/doveadm/doveadm-mail-fetch.c
+M	src/doveadm/doveadm-mail-flags.c
+M	src/doveadm/doveadm-mail-import.c
+M	src/doveadm/doveadm-mail-iter.c
+M	src/doveadm/doveadm-mail-iter.h
+M	src/doveadm/doveadm-mail-mailbox-cache.c
+M	src/doveadm/doveadm-mail-rebuild.c
+M	src/doveadm/doveadm-mail-search.c
+
+2022-01-20 14:16:51 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (aacaf65d7f)
+
+    doveadm-server: Fix hang when flushing print output and client disconnects
+
+
+M	src/doveadm/doveadm-print-server.c
+
+2022-01-20 14:13:48 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (8f34eb952d)
+
+    lib: ostream-multiplex - Call flush callbacks also when stream has failed
+
+    It may be important for the flush callbacks to know when ostream has been 
+    closed. This is a partial fix to prevent doveadm-server hanging when it's 
+    printing lots of output and doveadm client disconnects.
+
+M	src/lib/ostream-multiplex.c
+
+2022-01-20 13:34:50 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (d902430e43)
+
+    doveadm: Fix assert-crash when proxying causes print buffer to be flushed
+
+    Fixes: Panic: file ioloop.c: line 865 (io_loop_destroy): assertion failed:
+    (ioloop == current_ioloop)
+
+M	src/doveadm/doveadm-print-server.c
+
+2021-12-22 12:12:51 +0100 Markus Valentin <markus.valentin@open-xchange.com> (d02a022d73)
+
+    imapc: Fix doveadm copy with imapc
+
+    When copying to a mailbox with imapc it does not sync the destination 
+    mailbox when opening. This created "Error: Syncing mailbox '$mailboxname' 
+    failed: Internal error occurred." Prevent this error by checking for the 
+    MAILBOX_FLAG_SAVEONLY flag which is used by doveadm to create the 
+    destination mailbox. If that flag is set ignore that there was no initial 
+    fetching done.
+
+M	src/lib-storage/index/imapc/imapc-mail-fetch.c
+M	src/lib-storage/index/imapc/imapc-sync.c
+
+2022-01-21 12:12:52 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (2088e0917e)
+
+    configure.ac: Set dovecot version to 2.3.100.devel
+
+    This helps with version comparisons.
+
+M	configure.ac
+
+2022-01-14 04:02:09 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (bcdc6ef0ec)
+
+    submission: submission-backend-relay - Make sure QUIT command yields 221
+    when relay connection is closed normally.
+
+    Before, it would sometimes erroneously treat the closing connection as a
+    "connection lost" 421 situation.
+
+M	src/submission/submission-backend-relay.c
+
+2022-01-14 04:01:29 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (9f05ce1758)
+
+    submission: submission-backend-relay - Fix segfault in QUIT command
+    client-side destruction.
+
+
+M	src/submission/submission-backend-relay.c
+
+2020-11-09 02:29:04 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a53380c353)
+
+    lib-smtp: smtp-server-cmd-rset - Stop processing pipeline until RSET is
+    complete.
+
+    A subsequent MAIL command could get reset in the middle otherwise. Before,
+    it only blocked input until a reply was submitted, but the transaction isn't
+    reset until the RSET command is complete (just before actually sending the
+    reply) which can cause issues when the subsequent MAIL command is already
+    being processed.
+
+M	src/lib-smtp/smtp-server-cmd-rset.c
+
+2020-11-09 00:02:51 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (5a7616f93b)
+
+    lib-smtp: smtp-server-cmd-helo - Stop processing pipeline until HELO/EHLO is
+    complete.
+
+    A subsequent MAIL command could get reset in the middle otherwise. Before,
+    it only blocked input until a reply was submitted, but the transaction isn't
+    reset until the EHLO/HELO command is complete (just before actually sending
+    the reply) which can cause issues when the subsequent MAIL command is
+    already being processed.
+
+M	src/lib-smtp/smtp-server-cmd-helo.c
+
+2020-11-08 03:58:06 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (5aeca06ad6)
+
+    lib-smtp: smtp-server-command - Add
+    smtp_server_command_pipeline_block/unblock().
+
+
+M	src/lib-smtp/smtp-server-command.c
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server-private.h
+M	src/lib-smtp/smtp-server.h
+
+2022-01-10 03:54:13 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (d79841aa34)
+
+    lib-smtp: smtp-client-transaction - Plug the command pipeline while
+    transaction is pending.
+
+    This prevents commands submitted after creating the transaction from being 
+    exectuted out-of-order before the transaction's MAIL command.
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2022-01-10 03:53:41 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (ffbf4efea2)
+
+    lib-smtp: smtp-client-transaction - Add
+    smtp_client_command_mail_submit_after().
+
+
+M	src/lib-smtp/smtp-client-command.c
+M	src/lib-smtp/smtp-client-command.h
+
+2022-01-10 01:18:56 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (2ccec52a52)
+
+    lib-smtp: smtp-client-transaction - Prevent recursion for
+    smtp_client_transaction_fail*().
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2022-01-11 02:05:26 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (c40e19df16)
+
+    lib-smtp: Reformat smtp-server-cmd-quit.c.
+
+
+M	src/lib-smtp/smtp-server-cmd-quit.c
+
+2022-01-20 12:42:57 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (6f703021eb)
+
+    lib-storage: Don't delay setting mail event's log prefix
+
+    This caused crashes if the mail event was kept referenced and used for 
+    logging after struct mail was already freed. With the delayed mail event 
+    creation the log prefix shouldn't be much of a performance problem, so just 
+    set the prefix immediately.
+
+    Partially reverts bc68e1c368db746557829f67556f3c72943b7956.
+
+M	src/lib-storage/mail.c
+
+2022-01-19 15:07:58 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (6b6465700f)
+
+    configure: Update version to 2.3.devel
+
+
+M	configure.ac
+
+2021-11-30 12:47:52 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (51bb266887)
+
+    NEWS: Add news for 2.3.17.1
+
+
+M	NEWS
+
+2021-09-28 12:40:29 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (3330f8f631)
+
+    NEWS: Add news for 2.3.17
+
+
+M	NEWS
+
+2022-01-10 19:46:12 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (26c5c2aa2b)
+
+    lib-storage: Remove reason_code=mailbox:search
+
+    Continues the slow per-mail event removals started in 
+    f3c568e3cbc113920bc029e07e56619589c6a26d.
+
+    Reverts 87cd6570a14b3f572fc6000f710df409371a4bcb
+
+M	src/lib-storage/index/index-search.c
+
+2021-12-13 12:54:19 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (c043787771)
+
+    lmtp: lmtp-proxy - Add session ID to backend connection error replies.
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2021-12-13 04:08:32 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (b29d0df2ef)
+
+    submssion: submission-backend-relay - Avoid logging two errors about a
+    connection problem.
+
+    Earlier commit changed lib-smtp to consistently log an error for connection 
+    problems, which causes a second error to occur for submission relay backend. 
+    This is undesirable.
+
+M	src/submission/submission-backend-relay.c
+
+2021-12-07 01:51:45 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (08d650cb81)
+
+    lmtp: lmtp-proxy - Add lmtp_verbose_replies setting.
+
+    It causes the proxy to return errors with full details in replies to the
+    LMTP client.
+
+M	src/lmtp/lmtp-proxy.c
+M	src/lmtp/lmtp-settings.c
+M	src/lmtp/lmtp-settings.h
+
+2021-12-27 11:35:05 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a0cd85f004)
+
+    lmtp: lmtp-proxy - Split off lmtp_proxy_handle_connection_error().
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2022-01-04 21:20:22 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (1d88bdfdf8)
+
+    lmtp: lmtp-proxy - Change proxy connection failure message make more sense.
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2021-12-07 01:58:32 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (11b6ccdfce)
+
+    lib-smtp: smtp-client - Add verbose_user_errors setting.
+
+    It returns the detailed administrator errors as user error in the reply
+    returned to the calling application.
+
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/smtp-client.c
+M	src/lib-smtp/smtp-client.h
+
+2021-12-07 01:51:18 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (40d49a038b)
+
+    lib-smtp: smtp-client-connection - Log administrator connection failures at
+    a single place.
+
+
+M	src/lib-smtp/smtp-client-command.c
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/smtp-client-private.h
+
+2021-12-23 00:59:45 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (f436df8709)
+
+    lib-smtp: smtp-client-connection - Consistently log an error for connection
+    failures.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+M	src/lib-smtp/test-smtp-client-errors.c
+
+2021-12-07 02:44:58 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (5ef51c52ef)
+
+    lib-smtp: smtp-client-connection - Add definition for repeatedly used
+    connection error.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2021-12-27 11:24:36 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (06f838091b)
+
+    lmtp: Reformat lmtp-proxy.c.
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2021-12-07 01:23:13 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (2e82fdb706)
+
+    lib-smtp: Reformat smtp-client-connection.c.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2022-01-05 01:27:48 +0000 Stephan Bosch <stephan.bosch@dovecot.fi> (3575639fc4)
+
+    lib-compression: ostream-zlib - Fix signed vs unsigned comparison.
+
+
+M	src/lib-compression/ostream-zlib.c
+
+2022-01-05 01:25:34 +0000 Stephan Bosch <stephan.bosch@dovecot.fi> (5108d7bddd)
+
+    lib: test-event-flatten - Fix format string specifier for usec time
+
+
+M	src/lib/test-event-flatten.c
+
+2021-12-28 09:37:42 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (f3c568e3cb)
+
+    lib-storage: Remove most reason_code=mail:*
+
+    Event reasons create events internally. This is rather expensive if it's 
+    done for operations that access all emails in a large folder, e.g. SORT. The
+    per-mail reason_codes also didn't seem to be hugely helpful, so best to just
+    remove them.
+
+    Some of the mail:* reasons were left where they are causing mail stream to 
+    be opened anyway. In these cases the extra CPU used by event reasons is 
+    relatively little.
+
+    Reverts ce517f8323fa4a60b230f29712b207139badb3f0
+
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/mail.c
+
+2021-12-20 11:05:39 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (ac4d66e25e)
+
+    maildir: maildir_filename_base_hash() - Disable ubsan integer wrapping
+    checks
+
+
+M	src/lib-storage/index/maildir/maildir-filename.c
+
+2021-12-09 18:06:11 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (ac7bcc31c9)
+
+    lib-fts: Reuse textcat handle between sessions
+
+    textcat initialization is rather CPU intensive. Its configuration is 
+    normally always the same between sessions, so we can keep the latest textcat 
+    handle cached.
+
+M	src/lib-fts/fts-language.c
+
+2021-11-29 17:05:18 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (fd7248b707)
+
+    lib-storage: Remove index_mail_data.seq
+
+    The seq already exists in struct mail, so this unnecessarily duplicated it.
+
+M	src/lib-storage/index/index-mail-headers.c
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/index-mail.h
+
+2021-11-29 16:21:29 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (767b689cfb)
+
+    lib-storage: Minor optimization - Use mail_index_lookup_full() to avoid two
+    index lookups
+
+    mail_index_is_expunged() was already internally doing the full lookup.
+
+M	src/lib-storage/index/index-mail.c
+
+2021-11-29 16:16:30 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (526a0f5b18)
+
+    lib-index: mail_index_lookup_full() - Add expunged_r parameter
+
+
+M	src/lib-index/mail-index-modseq.c
+M	src/lib-index/mail-index-view.c
+M	src/lib-index/mail-index.h
+
+2021-11-29 13:26:25 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (bc68e1c368)
+
+    lib-storage: Optimize setting mail event log prefix
+
+    This was much more important before mail event creation was delayed. In one 
+    installation the t_strdup_printf() call itself took about 4% of the total
+    CPU usage. Now that mail events are delayed, this is likely much less of an
+    issue. Still, this is easy enough of an optimization that might as well do
+    it.
+
+M	src/lib-storage/mail.c
+
+2021-11-29 15:46:04 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (165be3edb6)
+
+    lib-storage: Delay creating mail event until mail_event() is called
+
+    Mails can be accessed a lot. The event handling code takes up a lot of CPU, 
+    but most of the time the created event wasn't actually used for anything.
+
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail-storage.h
+M	src/lib-storage/mail.c
+
+2021-11-29 15:43:29 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (a24e4a14f9)
+
+    lib-storage: Make sure mail event is created before accessing streams or
+    metadata
+
+    This is in preparation for the next commit which delays the mail event 
+    creation. The event duration behaves better if the event is created before 
+    any potentially slow access is done.
+
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail.c
+
+2021-11-29 15:36:58 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (58eef6713e)
+
+    lib-storage: Move struct mail.event to mail_private
+
+
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail-storage.h
+M	src/lib-storage/mail.c
+
+2021-11-29 15:32:28 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (b0596718ab)
+
+    lib-lda, lib-storage: Use mail_event()
+
+
+M	src/lib-lda/mail-send.c
+M	src/lib-storage/mail.c
+
+2021-11-29 15:31:17 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (4b3775baf0)
+
+    lib-storage: Add mail_event()
+
+
+M	src/lib-storage/mail-storage.h
+M	src/lib-storage/mail.c
+
+2021-11-29 18:39:22 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (13fd3c3256)
+
+    lib-storage: Add and use mail_metadata_access_start()
+
+
+M	src/lib-storage/index/maildir/maildir-mail.c
+M	src/lib-storage/index/raw/raw-mail.c
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail.c
+
+2021-11-29 18:32:09 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (988a575c35)
+
+    lib-storage: Add and use mail_stream_access_start()
+
+    This replaces setting mail_stream_accessed directly.
+
+M	src/lib-storage/index/dbox-multi/mdbox-mail.c
+M	src/lib-storage/index/dbox-single/sdbox-mail.c
+M	src/lib-storage/index/imapc/imapc-mail-fetch.c
+M	src/lib-storage/index/imapc/imapc-save.c
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/maildir/maildir-mail.c
+M	src/lib-storage/index/mbox/mbox-mail.c
+M	src/lib-storage/index/pop3c/pop3c-mail.c
+M	src/lib-storage/index/raw/raw-mail.c
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail.c
+
+2021-11-29 18:35:31 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (e06ee029f6)
+
+    lib-storage: raw: get_stream() - Add missing lookup_abort and
+    mail_stream_accessed handling
+
+
+M	src/lib-storage/index/raw/raw-mail.c
+
+2021-11-29 18:34:43 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (2f2e3b3d37)
+
+    pop3c: Add missing lookup_abort and mail_stream_accessed handling
+
+
+M	src/lib-storage/index/pop3c/pop3c-mail.c
+
+2021-11-29 20:44:05 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (5b97f3af04)
+
+    maildir: get_stream() - Add missing lookup_abort and mail_stream_accessed
+    handling
+
+
+M	src/lib-storage/index/maildir/maildir-mail.c
+
+2021-11-29 18:24:30 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (d7b8c40971)
+
+    lib-storage: Rename mail.mail_stream_opened to mail_stream_accessed
+
+    It is set to TRUE even if the mail stream couldn't successfully be opened, 
+    so this describes it better. Also it's now consistent with 
+    mail_metadata_accessed.
+
+M	src/lib-storage/index/dbox-multi/mdbox-mail.c
+M	src/lib-storage/index/dbox-single/sdbox-mail.c
+M	src/lib-storage/index/imapc/imapc-mail-fetch.c
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/index-mailbox-size.c
+M	src/lib-storage/index/index-sort.c
+M	src/lib-storage/index/mbox/mbox-mail.c
+M	src/lib-storage/mail-storage.h
+
+2021-12-03 10:44:10 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (74b94e02e5)
+
+    lib-index: Remove mail_cache_lookup_finished event
+
+    This event was using too much CPU with commands that accessed lots of mails
+    (e.g. IMAP SORT). It also wasn't especially useful.
+
+    Reverts 0d252dccb3013fea4d9a28bd5fafb5ea6e847d0e
+
+M	src/lib-index/mail-cache-lookup.c
+
+2021-12-02 11:35:20 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1fb3dda9ac)
+
+    lib-index: Use ATTR_COLD for error/corruption handling functions
+
+    This could reduce CPU usage due to better branch prediction.
+
+M	src/lib-index/mail-cache-private.h
+M	src/lib-index/mail-cache.h
+M	src/lib-index/mail-index-private.h
+M	src/lib-index/mail-index-strmap.h
+M	src/lib-index/mail-index-sync-private.h
+M	src/lib-index/mail-index.h
+M	src/lib-index/mail-transaction-log-private.h
+M	src/lib-index/mail-transaction-log.h
+
+2021-11-23 15:00:02 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (4a74574d39)
+
+    lib: pool_alloconly_destroy() optimization - Don't clear the last block
+    before free
+
+    The block is going to be freed, so there's no need to clear it.
+    (The clearing still happens if clean_frees=TRUE.)
+
+M	src/lib/mempool-alloconly.c
+
+2021-11-23 14:59:28 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (40666e3bca)
+
+    lib: pool_alloconly_destroy() - Deduplicate code
+
+
+M	src/lib/mempool-alloconly.c
+
+2021-11-23 14:58:39 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (7c9ee911cf)
+
+    lib: Split off pool_alloconly_free_block()
+
+
+M	src/lib/mempool-alloconly.c
+
+2021-11-23 14:56:10 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (333bd2e628)
+
+    lib: Split off pool_alloconly_free_blocks_until_last()
+
+
+M	src/lib/mempool-alloconly.c
+
+2021-11-23 15:17:49 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (0bcc6897de)
+
+    lib: Minor optimization - Avoid zeroing a newly created empty event field
+
+
+M	src/lib/lib-event.c
+
+2021-11-22 17:36:21 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (9fc6965de1)
+
+    lib: Remove copy of event_passthrough from struct event
+
+    There was no need for it. This saves some memory and CPU.
+
+    This change now prevents modifications to the event_passthrough, but nothing 
+    was doing it before either.
+
+M	src/lib/lib-event-private.h
+M	src/lib/lib-event.c
+
+2021-11-22 17:23:28 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (a2ea3df64f)
+
+    lib: Grow initial event pool size to 1024 bytes
+
+    The old 64 bytes size wasn't enough to allocate even the struct event
+    itself.
+
+M	src/lib/lib-event.c
+
+2021-12-15 09:19:40 +0100 Karl Fleischmann <karl.fleischmann@open-xchange.com> (793cae8549)
+
+    login-common: Explicitly null client auth fields on proxy pool unref
+
+    Explicitly setting `client_id` and `forward_fields` to null in the client
+    may reveal use-after-free issues when trying to access these fields on a
+    client proxy.
+
+M	src/login-common/client-common.c
+
+2021-12-10 11:04:06 +0100 Karl Fleischmann <karl.fleischmann@open-xchange.com> (7ad1a92aa5)
+
+    login-common: Use base index for aliases
+
+    Define a base index for aliases in the login variable table. Assign values
+    via offset of that base index. This allows adding more values later without
+    updating any subsequent index.
+
+M	src/login-common/client-common.c
+
+2021-12-10 11:32:37 +0100 Marco Bettini <marco.bettini@open-xchange.com> (ddb85f3533)
+
+    fts: Add headers filters
+
+
+M	src/plugins/fts/fts-api-private.h
+M	src/plugins/fts/fts-api.c
+M	src/plugins/fts/fts-build-mail.c
+
+2021-12-10 11:30:08 +0100 Marco Bettini <marco.bettini@open-xchange.com> (24b66ca063)
+
+    fts: fts-build-mail - Remove stray line
+
+
+M	src/plugins/fts/fts-build-mail.c
+
+2020-11-10 00:11:15 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a92209788d)
+
+    auth: mech-scram - Amend comments.
+
+
+M	src/auth/mech-scram.c
+
+2020-11-10 00:11:11 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (49b5780c29)
+
+    auth: mech-scram - Fix handling of authzid.
+
+    The gs2-header field was not recreated correctly for the final verification. 
+    Fixed by storing the complete gs2-header value instead.
+
+M	src/auth/mech-scram.c
+
+2020-11-10 00:11:07 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a164ea3786)
+
+    auth: mech-scram - Properly size temporary string buffers.
+
+
+M	src/auth/mech-scram.c
+
+2020-11-10 00:11:03 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (1ee7d0d807)
+
+    auth: mech-scram - Restructure message parsing in
+    parse_scram_client_first().
+
+
+M	src/auth/mech-scram.c
+
+2020-11-10 00:10:57 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (6678648cfb)
+
+    auth: Reformat mech-scram.c.
+
+
+M	src/auth/mech-scram.c
+
+2021-08-20 17:43:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (15f25bf7ad)
+
+    lib-storage: Return reason string in mailbox_vfuncs.list_index_has_changed()
+
+    The callers can use it to log why the list index had changed.
+
+M	src/lib-storage/index/index-storage.h
+M	src/lib-storage/index/index-sync.c
+M	src/lib-storage/index/maildir/maildir-sync-index.c
+M	src/lib-storage/index/maildir/maildir-sync.h
+M	src/lib-storage/index/mbox/mbox-sync-list-index.c
+M	src/lib-storage/index/mbox/mbox-sync-private.h
+M	src/lib-storage/list/mailbox-list-index-status.c
+M	src/lib-storage/list/mailbox-list-index.c
+M	src/lib-storage/mail-storage-private.h
+M	src/plugins/virtual/virtual-storage.c
+
+2021-08-20 19:09:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e13768d2ee)
+
+    lib-storage: Don't call mailbox_vfuncs.list_index_has_changed() in data
+    stack frame
+
+    This will be required by the next change.
+
+M	src/lib-storage/list/mailbox-list-index-iter.c
+M	src/lib-storage/list/mailbox-list-index-status.c
+M	src/lib-storage/list/mailbox-list-index.c
+
+2021-08-20 17:26:42 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6fb3e493e2)
+
+    lib-storage: mailbox_list_index_view_open() - Log a debug line why index
+    isn't up-to-date
+
+
+M	src/lib-storage/list/mailbox-list-index.c
+
+2021-08-20 17:20:01 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (49d2d1d9ba)
+
+    lib-storage: mailbox_list_index_status() - Return failure reason string
+
+    Log a debug line if mailbox list index couldn't be used to get the wanted 
+    status information.
+
+M	src/lib-storage/list/mailbox-list-index-backend.c
+M	src/lib-storage/list/mailbox-list-index-notify.c
+M	src/lib-storage/list/mailbox-list-index-status.c
+M	src/lib-storage/list/mailbox-list-index.h
+M	src/lib-storage/list/mailbox-list-notify-tree.c
+
+2021-12-06 11:54:16 +0100 Marco Bettini <marco.bettini@open-xchange.com> (7751299dee)
+
+    auth: userdb-ldap - Avoid early dereferencing
+
+    userdb_ldap_iterate_callback() is still invoked after dereferencing 
+    auth_request in userdb_ldap_iterate_deinit().
+
+    Normally this happens only on teardown, but it may happens also in case of
+    auth disconnecting from auth-worker during iteration.
+    (which shouldn't happen unless the auth process crashes)
+
+M	src/auth/userdb-ldap.c
+
+2021-12-09 17:31:04 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (2302fcd607)
+
+    var-expand-crypt: Remove dead code
+
+    It's correct that dcrypt library shouldn't be deinitialized at plugin 
+    deinit.
+
+M	src/plugins/var-expand-crypt/var-expand-crypt-plugin.c
+
+2021-12-03 17:23:35 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (82be40f65c)
+
+    login-common: Add more fields to proxy_session_finished
+
+    Added disconnect_side, disconnect_reason, idle_secs, bytes_in, bytes_out.
+
+M	src/login-common/login-proxy.c
+
+2021-12-03 17:54:37 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (da379cfc29)
+
+    login-common: Split off proxy disconnection prefix to
+    LOGIN_PROXY_KILL_PREFIX
+
+
+M	src/login-common/login-proxy.c
+
+2021-12-03 17:47:50 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d36139fc22)
+
+    login-common: login_proxy_free_*() - Rename reason to log_msg
+
+    This better describes it, since it's the full log message.
+
+M	src/login-common/login-proxy.c
+
+2021-12-01 14:46:04 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (203147d0fc)
+
+    dsync: Fix -I max-size to actually be the max-size rather than min-size
+
+    The -I max-size parameter was supposed to be used to skip mails that are 
+    larger than max-size. Instead, it skipped mails that were smaller.
+
+M	src/doveadm/dsync/dsync-mailbox-import.c
+
+2021-11-09 13:42:24 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (34f2fdeed5)
+
+    man: Add missing parameters to doveadm-sync
+
+
+M	doc/man/doveadm-sync.1.in
+
+2021-11-30 18:02:14 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (e6a5cb57d1)
+
+    dsync: Remove -D parameter that disables syncing mailbox renames
+
+    It hasn't been necessary for a long time, and its behavior seems to be 
+    broken.
+
+M	src/doveadm/doveadm-dsync.c
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+M	src/doveadm/dsync/dsync-brain-private.h
+M	src/doveadm/dsync/dsync-brain.c
+M	src/doveadm/dsync/dsync-brain.h
+M	src/doveadm/dsync/dsync-ibc-stream.c
+
+2021-11-09 13:41:37 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (96f9651c98)
+
+    dsync: Add back various missing parameters
+
+    Also add them to the usage string.
+
+    Broken by 5676c510bfa9217df05e9b7cb000ae3554d66f22
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-09-22 14:50:50 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (2beae65898)
+
+    fts: Don't overwrite INUSE error if indexing times out
+
+
+M	src/plugins/fts/fts-indexer.c
+M	src/plugins/fts/fts-storage.c
+
+2021-11-11 12:31:49 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (61d9023523)
+
+    lib-storage/index: If mail stream was already opened, do not count it as
+    slow vsize access
+
+    imapc storage driver will open the mail stream in some circumstances during
+    search, so do not consider this as slow vsize.
+
+M	src/lib-storage/index/index-mailbox-size.c
+
+2021-11-16 15:08:47 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (73475e1ea6)
+
+    lib-storage: Split off index_mailbox_vsize_finish_bg()
+
+    Simplifies next commit
+
+M	src/lib-storage/index/index-mailbox-size.c
+
+2021-08-10 12:22:08 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1ee84ba065)
+
+    lib-index: Fix storing cache fields' last_used with 64bit big endian CPUs
+
+
+M	src/lib-index/mail-cache-fields.c
+
+2019-01-17 12:13:38 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (616684a8fe)
+
+    maildir: Fix crash at error handling
+
+    At this point dest_mail is already NULL. Mainly could happen when running 
+    out of disk space.
+
+M	src/lib-storage/index/maildir/maildir-save.c
+
+2021-11-30 09:51:25 +0000 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (ea41bf7db1)
+
+    login-proxy: Don't send proxying start event for reconnects
+
+    Send the event only for the beginning of proxying and add the reconnect
+    counts as a field for _established and _finished events.
+
+M	src/login-common/login-proxy.c
+
+2021-11-26 14:45:47 +0100 Marco Bettini <marco.bettini@open-xchange.com> (2f7254a6af)
+
+    lib: str_parse_intmax() - Replace signed expression with UNSIGNED_MINUS()
+
+    Found by code analysis tool
+
+M	src/lib/strnum.c
+
+2021-11-26 15:48:04 +0100 Marco Bettini <marco.bettini@open-xchange.com> (5297d4862f)
+
+    lib: switch i_rand_limit() - Replace signed expression with UNSIGNED_MINUS()
+
+    Found by code analysis tool
+
+M	src/lib/rand.c
+
+2021-11-26 14:15:07 +0100 Marco Bettini <marco.bettini@open-xchange.com> (4a3ef239b4)
+
+    lib: buffer_truncate_rshift_bits() - Replace signed expression with an
+    unsigned equivalent
+
+    Found by code analysis tool
+
+M	src/lib/buffer.c
+
+2021-11-29 15:05:29 +0100 Marco Bettini <marco.bettini@open-xchange.com> (668bdc6838)
+
+    lib: bits_rotXYY() - Replace signed expression with UNSIGNED_MINUS() macro
+    for unsigned 2's complement
+
+    Found by code analysis tool
+
+M	src/lib/bits.h
+M	src/lib/test-bits.c
+
+2021-11-29 15:00:41 +0100 Marco Bettini <marco.bettini@open-xchange.com> (bf9c2cb8b5)
+
+    bits_is_power_of_two() Replace signed expression with __builtin_popcountl()
+
+    Found by code analysis tool
+
+M	src/lib/bits.h
+M	src/lib/test-bits.c
+
+2021-11-29 14:58:33 +0100 Marco Bettini <marco.bettini@open-xchange.com> (f7cbaac8ff)
+
+    lib: test-bits.c - Removing stray spaces at end of lines
+
+
+M	src/lib/test-bits.c
+
+2021-01-15 17:43:19 +0100 Fabrice Bellet <fabrice@bellet.info> (9211e803f2)
+
+    raw-storage: copy the envelope sender instead of referencing it
+
+
+M	src/lib-storage/index/raw/raw-storage.c
+
+2019-10-14 16:43:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4a58d467b6)
+
+    lib-fs: Add test-fs utility for testing fs drivers
+
+    It performs random read/write/delete/iter operations.
+
+M	src/util/Makefile.am
+A	src/util/test-fs.c
+
+2021-06-18 15:19:48 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (051d39ee45)
+
+    replicator: doveadm replicator replicate -f didn't always start full sync
+
+    The full sync happened only if the dsync queue was already full. If it 
+    wasn't, dsync was called too early before user->force_full_sync was set.
+
+M	src/replication/replicator/replicator-brain.c
+
+2021-07-28 11:52:01 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d0c28bba8b)
+
+    replicator: Move replicator_brain_timeout(()
+
+
+M	src/replication/replicator/replicator-brain.c
+
+2021-04-19 18:48:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (21880fac08)
+
+    lib-storage: Optimize behavior when building THREAD tree with empty cache
+
+    Although it's possible that Date, Subject or received-date isn't actually 
+    needed for all mails, it's much more efficient behavior to add all of these 
+    to cache at the same time if they're not there already. Otherwise the same 
+    mail could be opened and parsed up to 3 times.
+
+M	src/lib-storage/index/index-thread.c
+
+2020-04-02 11:06:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8abebfe9a4)
+
+    dsync: Improve checking if source and destination are the same location
+
+    Prefer checking using the first -n parameter's namespace. Next try prefix=""
+    if it's created and last fallback to inbox=yes namespace.
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-11-22 18:17:52 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (27e371af06)
+
+    dsync: Improve bad namespace configuration related error messages
+
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-11-24 16:01:08 +0100 Marco Bettini <marco.bettini@open-xchange.com> (5519de5749)
+
+    submission: smtp_server_command_execute() Remove check for null pointer
+    confusing coverity
+
+
+M	src/lib-smtp/smtp-server-command.c
+
+2021-11-22 16:04:11 +0000 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (c36969fee5)
+
+    imapc: Do not include the root node in list if it's the namespace prefix
+
+    Otherwise namespace prefix is added to the mailbox tree as a separate node.
+    Fixes duplicated shared namespace root in mailbox list.
+
+M	src/lib-storage/index/imapc/imapc-list.c
+
+2021-11-23 15:56:45 +0100 Marco Bettini <marco.bettini@open-xchange.com> (5cf012bad9)
+
+    acl: acl_mailbox_exists() - Add missing error handling
+
+    Set mailbox storage error to "internal error" when
+    acl_object_get_my_rights() fails (as per contract).
+
+M	src/plugins/acl/acl-mailbox.c
+
+2021-11-23 16:03:07 +0100 Marco Bettini <marco.bettini@open-xchange.com> (a672d83152)
+
+    lib-storage: fail_mailbox_exists() - Return not found as a success instead
+    of an error
+
+    GETMETADATA sometimes responds with “NO [SERVERBUG] BUG: Unknown internal
+    error returned”, instead of “NO Mailbox doesn't exist”, depending on the
+    actual configuration. This can happen with the shared namespace.
+
+M	src/lib-storage/fail-mailbox.c
+
+2021-11-23 17:55:43 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (13ed86bbf3)
+
+    login-common: Default director_username_hash to %Lu like everywhere else
+
+    This setting has many places where the default is set, and this syncs it 
+    with the others.
+
+M	src/login-common/login-settings.c
+
+2021-11-12 11:16:47 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (d36d2c595a)
+
+    lib-storage: mail-duplicate - Fix segfault occurring when user has no
+    configured home directory
+
+    Occurred only when a duplicate DB transaction was created.
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-11-12 10:43:16 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (98f709deac)
+
+    lib-storage: mail-duplicate - Fix panic occurring when user has no
+    configured home directory.
+
+    Panic was:
+
+    Panic: file imem.c: line 65 (i_strconcat): assertion failed: (str1 != NULL)
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-11-11 09:57:07 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (476990fffa)
+
+    stats: Whitespace cleanup.
+
+
+M	src/stats/stats-metrics.c
+
+2021-11-09 14:12:18 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (e41cd77f96)
+
+    doveadm: Fix processing of group-by parameter of doveadm stats add.
+
+
+M	src/doveadm/doveadm-stats.c
+
+2020-09-25 03:48:09 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (6341cfc57a)
+
+    lib-smtp: smtp-client-transaction - Always drop MAIL/RCPT state data before
+    calling callbacks.
+
+    This prevents problems with callbacks destroying the transaction, the 
+    connection, or commands involved in the transaction. This at least fixes a 
+    segmentation fault occurring in the submission service when STARTTLS fails 
+    during login.
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2021-11-08 23:29:35 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (0037fb7bad)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_rcpt_fail_reply(NULL, reply) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:31:07 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (30f0154b78)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_rcpt_abort(NULL) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:29:53 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (53b3b9f55b)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_rcpt_replied(NULL) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:27:39 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f4c5ab773a)
+
+    lib-smtp: smtp-client-transaction - Assert that
+    smtp_client_transaction_rcpt_approved/denied() has non-NULL parameter.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:24:29 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (5e68870131)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_rcpt_free(NULL) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:22:45 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (da73aac321)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_mail_fail_reply(NULL, reply) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:21:06 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (86debae8fe)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_mail_abort(NULL) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:18:56 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (8f2751f53d)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_mail_replied(NULL, reply) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:17:57 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (02079622d1)
+
+    lib-smtp: smtp-client-transaction - Make
+    smtp_client_transaction_mail_free(NULL) a no-op.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 02:45:27 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (c588b55541)
+
+    lib-smtp: smtp-client-command - Drop callback before call in
+    smtp_client_command_input_reply().
+
+
+M	src/lib-smtp/smtp-client-command.c
+
+2020-09-25 02:38:54 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (34e5a418c1)
+
+    lib-smtp: smtp-client-command - Make smtp_client_command_fail_reply(NULL,
+    reply) a no-op.
+
+
+M	src/lib-smtp/smtp-client-command.c
+
+2020-09-25 02:33:33 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (ed211d21b0)
+
+    lib-smtp: smtp-client-command - Make smtp_client_command_abort(NULL) a
+    no-op.
+
+
+M	src/lib-smtp/smtp-client-command.c
+
+2020-09-25 03:00:31 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (15c98d994b)
+
+    lib-smtp: Reformat smtp-client-transaction.c.
+
+
+M	src/lib-smtp/smtp-client-transaction.c
+
+2020-09-25 03:14:16 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (ec3cdb2168)
+
+    lib-smtp: Reformat smtp-client-command.c.
+
+
+M	src/lib-smtp/smtp-client-command.c
+
+2021-10-28 09:19:37 +0200 Marco Bettini <marco.bettini@open-xchange.com> (1863b1c535)
+
+    lib-fts: Don't index inline base64 encoded content
+
+
+M	src/lib-fts/fts-tokenizer-generic.c
+M	src/lib-fts/test-fts-tokenizer.c
+
+2021-11-14 23:27:00 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (771ebaf961)
+
+    lib-smtp: smtp-server-command - Emit smtp_server_command_started event a
+    little later.
+
+    This way cmd_*args fields are available for the event.
+
+M	src/lib-smtp/smtp-server-command.c
+
+2021-10-27 00:48:28 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (6687627b15)
+
+    lib-smtp: smtp-server-command - Add cmd_args and cmd_human_args fields.
+
+    This mirrors the identically named fields for the imap service. For SMTP, 
+    though, cmd_human_args == cmd_args.
+
+M	src/lib-smtp/smtp-server-command.c
+
+2021-07-19 11:53:41 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f0e2299322)
+
+    lib-http: http-client-connection - Fix crash in
+    http_client_connection_server_close().
+
+    The conn->peer member is only not NULL when the connection is associated
+    with a peer object. If it is not, http_client_connection_server_close() will
+    crash. Fixed by doing the peer->client operations only when there is an
+    associated peer.
+
+M	src/lib-http/http-client-connection.c
+
+2020-06-27 17:55:58 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9032716e00)
+
+    lib-http: Reformat http-client-connection.c.
+
+
+M	src/lib-http/http-client-connection.c
+
+2021-10-29 11:42:40 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (f572de72f4)
+
+    lib: Add unit tests for named events only optimization.
+
+
+M	src/lib/event-filter-private.h
+M	src/lib/event-filter.c
+M	src/lib/test-event-filter.c
+
+2021-10-29 14:06:39 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (5a69a67fb8)
+
+    lib: Fix event filtering for unnamed events with optional event name
+
+    For example event filter "event=ev_name OR field1=value1" wouldn't
+    previously match if the event didn't have any name, even if it did have
+    field1=value1.
+
+M	src/lib/event-filter-parser.y
+M	src/lib/event-filter.c
+
+2021-10-22 12:09:24 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (f01aec1f96)
+
+    lib-master: Whitespace cleanup.
+
+
+M	src/lib-master/master-service.c
+
+2021-11-09 14:37:07 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (f083562f92)
+
+    auth: passdb-cache - Preserve cached fields when verifying password with
+    worker
+
+
+M	src/auth/passdb-cache.c
+
+2021-11-05 17:11:49 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (533ef6c62d)
+
+    lib-smtp: smtp-server-command - Hold a command reference while sending
+    replies.
+
+    Fixes segfault at smtp-server-reply.c:652.
+
+M	src/lib-smtp/smtp-server-command.c
+
+2021-11-05 16:53:48 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (2275fc4d51)
+
+    lib-smtp: smtp-server-command - Split off
+    smtp_server_command_send_more_replies().
+
+
+M	src/lib-smtp/smtp-server-command.c
+
+2021-11-05 16:46:15 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (9ae95cb884)
+
+    lib-smtp: smtp-server-command - Split off
+    smtp_server_command_send_replies().
+
+
+M	src/lib-smtp/smtp-server-command.c
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server-private.h
+
+2021-11-08 14:36:57 +0200 sergey.kitov <sergey.kitov@open-xchange.com> (083570cb73)
+
+    plugins/fts: Fix memory leak when searching mail in virtual folder with fts.
+
+
+M	src/plugins/fts/fts-storage.c
+
+2021-11-08 14:04:07 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (5afea22757)
+
+    configure: Fix version macros to work with devel version
+
+
+M	configure.ac
+
+2021-11-08 15:24:30 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (318f566fc4)
+
+    SECURITY.md: Add initial security policy
+
+
+A	SECURITY.md
+
+2021-10-26 16:59:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e188435026)
+
+    lib-fts: Fix address tokenizer to handle large input properly
+
+    Previously it could have used excessive amounts of memory if the input 
+    didn't contain separator characters.
+
+    The fix changes a bit how the address-tokenizer works: Previously large 
+    email addresses were saved as truncated tokens. Now they're skipped entirely
+    by the address tokenizer. Similarly when searching long email addresses
+    they're no longer searched as truncated tokens, but instead simply fed to
+    the parent tokenizer which (likely) searches them in smaller pieces.
+
+    Note that this also sometimes changes the order in which tokens are 
+    returned, e.g. "foo", "example", "foo@example.com", "com" instead of 
+    returning "com" before the email address. This isn't ideal, but fixing it 
+    seems annoyingly complicated and practically it doesn't matter right now.
+
+M	src/lib-fts/fts-tokenizer-address.c
+M	src/lib-fts/test-fts-tokenizer.c
+
+2021-10-26 16:34:25 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2af8437d1d)
+
+    lib-fts: Implement support for parent tokenizer "streaming"
+
+    By default parent tokenizer is further tokenizing the token strings returned 
+    by child tokenizer. When streaming is enabled, the parent tokenizers are 
+    instead tokenizing a stream of data sent by the child tokenizer. This 
+    effectively makes the parent tokenizer return the same tokens as if the 
+    child tokenizer didn't exist (assuming child tokenizer feeds the parent all
+    the same input).
+
+    Arguably this should be the only way tokenizers work, but at least for now 
+    lets keep both ways.
+
+M	src/lib-fts/fts-tokenizer-private.h
+M	src/lib-fts/fts-tokenizer.c
+
+2021-10-26 12:33:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (782d806db2)
+
+    lib-fts: fts-tokenizer - Add comments explaning how it works
+
+
+M	src/lib-fts/fts-tokenizer.c
+
+2021-10-26 11:52:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2e91a13fe9)
+
+    lib-fts: test-fts-tokenizer - Add more uniqueness to the long email address
+
+    This helps at least with debugging problems.
+
+M	src/lib-fts/test-fts-tokenizer.c
+
+2021-09-14 18:21:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (31c24137c2)
+
+    lib: DOVECOT_PREREQ() - Add micro version
+
+    Nowadays APIs can change between micro versions as well.
+
+M	src/lib/macros.h
+
+2021-09-14 18:23:41 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (cb5968f451)
+
+    lib: DOVECOT_PREREQ() - Remove surrounding if checks
+
+    The DOVECOT_VERSION_* macros are expected to always exist.
+
+M	src/lib/macros.h
+
+2021-09-14 18:21:03 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d199125cc5)
+
+    configure: Add DOVECOT_VERSION_MICRO
+
+
+M	configure.ac
+
+2021-11-02 16:06:55 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (e95f985635)
+
+    lib: Fix building with --disable-asserts
+
+    The #endif location was wrong.
+
+M	src/lib/macros.h
+
+2021-01-27 02:20:53 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (17efe7a404)
+
+    submission-login: Implicitly login using EXTERNAL mechanism upon MAIL if
+    enabled.
+
+    This is a workaround for TLS clients that present a valid client
+    certificate, yet don't authenticate explicitly. This is enabled by setting:
+
+    submission_client_workarounds = implicit-auth-external
+
+M	src/submission-login/client-authenticate.c
+M	src/submission-login/client-authenticate.h
+M	src/submission-login/client.c
+M	src/submission-login/submission-login-settings.c
+M	src/submission-login/submission-login-settings.h
+M	src/submission/main.c
+M	src/submission/submission-client.c
+M	src/submission/submission-client.h
+M	src/submission/submission-settings.c
+
+2021-10-14 12:47:23 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (1236352801)
+
+    submission-login: client-authenticate - Split off
+    cmd_auth_set_master_data_prefix().
+
+
+M	src/submission-login/client-authenticate.c
+
+2021-01-27 13:51:03 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (14b6f813e0)
+
+    login-common: sasl-server - Add support for implicit login.
+
+
+M	src/login-common/client-common-auth.c
+M	src/login-common/sasl-server.c
+M	src/login-common/sasl-server.h
+
+2021-01-27 13:43:23 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (d4321dd910)
+
+    login-common: sasl-server - Turn private argument of
+    sasl_server_auth_begin() into flag.
+
+
+M	src/login-common/client-common-auth.c
+M	src/login-common/client-common.h
+M	src/login-common/sasl-server.c
+M	src/login-common/sasl-server.h
+
+2021-01-27 13:39:23 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (d5588935bf)
+
+    lib-master: master-auth - Add support for MAIL_AUTH_REQUEST_FLAG_IMPLICIT
+    flag.
+
+    It signals that this login is implicit; no command reply is expected. This
+    is going to be used by the Submission service to implicitly login a user
+    using the EXTERNAL SASL mechanism when the first MAIL command is issued. In
+    that case sending a reply for the implicit AUTH command would break the
+    protocol and this new flag is used to signal the post-login submission
+    service to not send an initial reply.
+
+M	src/lib-master/master-auth.h
+
+2021-01-27 13:40:43 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a0eeeb6c76)
+
+    lib-smtp: smtp-server - Add support for suppressing the inital greeting
+    reply.
+
+
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server.c
+M	src/lib-smtp/smtp-server.h
+
+2021-01-27 02:22:10 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (eef32e87e4)
+
+    lib-smtp: smtp-server - Make default command functions public.
+
+
+M	src/lib-smtp/smtp-server-private.h
+M	src/lib-smtp/smtp-server.h
+
+2021-01-27 02:21:50 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (6b380d6072)
+
+    lib-smtp: smtp-server-command - Allow overriding commands.
+
+
+M	src/lib-smtp/smtp-server-command.c
+M	src/lib-smtp/smtp-server.h
+
+2021-10-30 00:43:55 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (73b1ed4e82)
+
+    submission-login: client - Explicitly set SMTP server command and auth line
+    limits.
+
+    This way, it will always use the same limits as all other login services.
+
+M	src/submission-login/client.c
+
+2021-10-29 20:41:42 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (056aeb0748)
+
+    lib-smtp: smtp-command-parser - Make parser suitable for input stream with
+    small buffer.
+
+
+M	src/lib-smtp/smtp-command-parser.c
+M	src/lib-smtp/test-smtp-server-errors.c
+
+2021-10-29 23:44:01 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (d982aaadf1)
+
+    lib-smtp: smtp-command - Use size_t for command line limits.
+
+
+M	src/lib-smtp/smtp-command-parser.c
+M	src/lib-smtp/smtp-command.h
+
+2021-10-29 19:37:16 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (12eb20fc0d)
+
+    lib-smtp: test-smtp-server-errors - Add tests for very long AUTH response
+    lines.
+
+
+M	src/lib-smtp/test-smtp-server-errors.c
+
+2021-10-29 19:36:18 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (eb22a35feb)
+
+    lib-smtp: test-smtp-command-parser - Add test for very long AUTH response
+    line.
+
+
+M	src/lib-smtp/test-smtp-command-parser.c
+
+2021-10-29 23:52:05 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (e0fa1375e2)
+
+    lib-smtp: Reformat smtp-command-parser.c.
+
+
+M	src/lib-smtp/smtp-command-parser.c
+
+2021-10-29 23:47:57 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f47cc80a24)
+
+    lib-smtp: Reformat smtp-command-parser.h.
+
+
+M	src/lib-smtp/smtp-command-parser.h
+
+2021-11-01 08:33:49 -0400 Timo Sirainen <timo.sirainen@open-xchange.com> (6fff8d5819)
+
+    master: Use MASTER_SERVICE_FLAG_DISABLE_SSL_SET
+
+    This prevents startup failures if ssl_ca has a large number of certificates.
+
+    Broken by 36ff43f1a9aff8594d08f791e77ea13390fd569f
+
+M	src/master/main.c
+
+2021-11-01 08:33:14 -0400 Timo Sirainen <timo.sirainen@open-xchange.com> (ca2237e067)
+
+    lib-master: Add MASTER_SERVICE_FLAG_DISABLE_SSL_SET
+
+
+M	src/lib-master/master-service-settings.c
+M	src/lib-master/master-service.h
+
+2021-10-27 12:06:10 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (7fc901f370)
+
+    lib-ssl-iostream: Fix assert-crash when OpenSSL returns syscall error
+    without errno
+
+    Incomplete fix in f41874b3dec541478a85275698a91f089f537df2
+
+    Fixes: Panic: file istream-openssl.c: line 51 (i_stream_ssl_read_real):
+    assertion failed: (errno != 0)
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2021-10-14 18:31:19 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (84d3bc8e21)
+
+    lib-storage: Improve mailbox_vfuncs.search_next_update_seq() comment
+
+
+M	src/lib-storage/mail-storage-private.h
+
+2019-08-08 15:35:03 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (d1e1e346a8)
+
+    example-config: Remove outdated object-storage conf.d file
+
+
+D	doc/example-config/conf.d/11-object-storage.conf
+
+2021-10-28 17:07:39 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (0407978a8f)
+
+    dsync: Add back missing -T parameter
+
+    Broken by 5676c510bfa9217df05e9b7cb000ae3554d66f22
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-09-16 11:24:31 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (9705b81fb5)
+
+    fts: Keep track of every backend mailbox fts index status for virtual
+    folders.
+
+
+M	src/plugins/fts/fts-search.c
+M	src/plugins/fts/fts-storage.c
+M	src/plugins/fts/fts-storage.h
+
+2021-10-22 12:07:17 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (392d79bbd2)
+
+    fts: Whitespace cleanup.
+
+
+M	src/plugins/fts/fts-storage.c
+
+2021-10-14 17:33:30 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (356b36fc52)
+
+    lib-storage: Move search matching into
+    mailbox_vfuncs.search_next_match_mail()
+
+
+M	src/lib-storage/fail-mailbox.c
+M	src/lib-storage/index/dbox-multi/mdbox-deleted-storage.c
+M	src/lib-storage/index/dbox-multi/mdbox-storage.c
+M	src/lib-storage/index/dbox-single/sdbox-storage.c
+M	src/lib-storage/index/imapc/imapc-storage.c
+M	src/lib-storage/index/index-search.c
+M	src/lib-storage/index/index-storage.h
+M	src/lib-storage/index/maildir/maildir-storage.c
+M	src/lib-storage/index/mbox/mbox-storage.c
+M	src/lib-storage/index/pop3c/pop3c-storage.c
+M	src/lib-storage/index/raw/raw-storage.c
+M	src/lib-storage/mail-storage-private.h
+M	src/plugins/virtual/virtual-storage.c
+
+2021-01-08 11:21:20 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (547c07bdd3)
+
+    lib: Use data stack frame with IO switch and destroy callbacks
+
+
+M	src/lib/ioloop.c
+
+2021-01-08 11:28:26 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (0a9123d94c)
+
+    lib: Use data stack frame with IO context activate/deactive callbacks
+
+    These were running outside the regular ioloop data stack frames, so if the 
+    callback used any data stack it kept increasing memory usage.
+
+    This fixes excessive memory usage with old_stats plugin when used with 
+    long-running imap sessions. The memory got filled with UPDATE-SESSION 
+    commands.
+
+M	src/lib/ioloop.c
+
+2021-09-29 00:04:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2cea75b7f5)
+
+    lib-storage: When search parses message headers, deinitialize the parsing
+    properly
+
+    index_mail_parse_header() was being called only for the actual headers. This
+    prevented it from being called with hdr=NULL to indicate that the parsing is
+    finished and should be deinitialized. Move the index_mail_parse_header() to
+    be called earlier so it's called also with hdr=NULL.
+
+    Not deinitilizing the parsing could have caused assert-crashes later on in 
+    some situations.
+
+    Fixes: Panic: file index-mail-headers.c: line 667
+    (index_mail_get_raw_headers): assertion failed:
+    (mail->mail.mail.lookup_abort >= MAIL_LOOKUP_ABORT_NOT_IN_CACHE) Panic: file
+    ../../../src/lib/array.h: line 244 (array_idx_i): assertion failed: (idx <
+    array->buffer->used / array->element_size) Panic: file index-mail.c: line
+    1203 (index_mail_parse_body_finish): assertion failed: (!success)
+
+M	src/lib-storage/index/index-search.c
+
+2021-10-21 15:45:56 +0200 Marco Bettini <marco.bettini@open-xchange.com> (be55b9a4e0)
+
+    doveadm: Route help/usage messages on stderr rather than stdout.
+
+
+M	src/doveadm/doveadm.c
+
+2021-09-30 16:19:58 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (dbf7ed69c3)
+
+    login-common: Add proxying events
+
+    - proxy_session_started: Emitted before connecting to remote
+    - proxy_session_established: Emitted after connection to remote is
+    established and user is successfully logged in to the backend.
+    - proxy_session_finished: Emitted when proxying has ended. Either
+    successfully or with error.
+
+M	src/login-common/client-common-auth.c
+M	src/login-common/login-proxy.c
+
+2021-09-30 12:49:31 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (32e57666dc)
+
+    login-common: Change login proxy message to include IP address
+
+    Will be in format "<IP> (<host>)". Host part is optional and not added if
+    proxy target is an IP address.
+
+M	src/login-common/client-common-auth.c
+
+2021-10-05 11:33:06 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (313b96cd9a)
+
+    login-proxy: Add source_port to proxy event after connection is established
+
+
+M	src/login-common/login-proxy.c
+
+2021-10-05 15:17:50 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (ac4089115b)
+
+    login-common: Add more fields to login proxy event
+
+    Adds
+    - source_ip
+    - dest_ip
+    - dest_port
+    - dest_host
+    - master_user
+
+M	src/login-common/login-proxy.c
+
+2021-10-07 22:52:47 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (574ad02a47)
+
+    login-common: Start proxying after client fields are set
+
+    Allows adding more event fields in login_proxy_new().
+
+M	src/login-common/client-common-auth.c
+
+2021-09-30 12:48:09 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (e202252702)
+
+    login-proxy: Add login_proxy_get_ip_str()
+
+    Returns proxy IP address as string.
+
+M	src/login-common/login-proxy.c
+M	src/login-common/login-proxy.h
+
+2021-10-07 10:29:04 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (9ed1cdecee)
+
+    client-common: Add service field to client event
+
+
+M	src/login-common/client-common.c
+
+2021-10-13 14:49:43 +0300 Marco Bettini <marco.bettini@open-xchange.com> (351b424a37)
+
+    imap: list_send_status() - Fixes LIST-EXTENDED doesn't return STATUS for all
+    folders
+
+    Sending LIST .. RETURN (SUBSCRIBED STATUS (...)) did not return STATUS for
+    folders that are not subscribed when they have a child folder that is
+    subscribed as mandated by IMAP RFC
+
+M	src/imap/cmd-list.c
+
+2021-10-13 11:45:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b3f32368aa)
+
+    lib-index: Add mail_index_alloc_cache_find()
+
+
+M	src/lib-index/mail-index-alloc-cache.c
+M	src/lib-index/mail-index-alloc-cache.h
+
+2021-10-07 18:11:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (55338455ae)
+
+    maildir: Fix crash when closing a mailbox that isn't open
+
+    This could have happened at least with virtual plugin.
+
+M	src/lib-storage/index/maildir/maildir-storage.c
+
+2021-10-08 16:32:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2555d374c0)
+
+    master: test-auth-client - Check that authorization ID is as expected
+
+
+M	src/master/test-auth-client.c
+
+2021-10-07 20:18:21 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b314a81a69)
+
+    master: test-auth-client - Fix authenid check to be assert
+
+    It can never be NULL at this point.
+
+M	src/master/test-auth-client.c
+
+2021-10-07 19:59:52 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (859529c802)
+
+    lib-dict: dict-lua - Throw Lua error if dict key is invalid or username is
+    missing
+
+    This prevents assert-crashes in the C code.
+
+M	src/lib-dict/dict-iter-lua.c
+M	src/lib-dict/dict-lua.c
+M	src/lib-dict/dict-lua.h
+M	src/lib-dict/dict-txn-lua.c
+
+2021-10-07 19:54:41 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e26cebc652)
+
+    lib-dict: dict_lookup_async() - Add missing assert to check for key prefix
+    and username
+
+
+M	src/lib-dict/dict.c
+
+2021-10-07 19:42:41 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5ed33ba11b)
+
+    lib-master: Prevent read buffer overflow with invalid haproxy header size
+
+    This could have happened only for connections from haproxy_trusted_networks, 
+    so it's unlikely to cause any real security issues.
+
+M	src/lib-master/master-service-haproxy.c
+
+2021-10-07 19:36:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1ed470aa2d)
+
+    lib-dcrypt: dcrypt_openssl_decrypt_point_password_v1() - Fix crash if pbkdf2
+    generation fails
+
+
+M	src/lib-dcrypt/dcrypt-openssl.c
+
+2021-10-06 15:38:55 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1e12cf4be0)
+
+    imap: Fix handling client initialization error
+
+    It should send "OK Logged in" + BYE, not PREAUTH.
+
+    Broken by 714ff4342e39e309ff184905cd2f714def6177a3
+
+M	src/imap/main.c
+
+2021-10-08 16:02:04 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5a26f6160b)
+
+    imap: Move client_add_input() content to calling functions
+
+
+M	src/imap/main.c
+
+2021-10-08 15:57:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8f5d8d9e13)
+
+    imap: Move IMAPLOGINTAG environment handling
+
+    It can only happen with stdio clients when they don't have CLIENT_INPUT, so
+    it makes more sense to exist in the calling function.
+
+M	src/imap/main.c
+
+2021-10-06 15:43:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (29471bf743)
+
+    imap-login: proxy: Don't forward untagged BYE responses to the client
+
+    It will just cause confusion, especially when connections are retried. It
+    could end up looking like:
+
+    x login user pass
+    * BYE Internal error occurred. Refer to server log for more information.
+    * BYE Internal error occurred. Refer to server log for more information.
+    * BYE Internal error occurred. Refer to server log for more information.
+    * BYE Internal error occurred. Refer to server log for more information. x
+    NO [UNAVAILABLE] Account is temporarily unavailable.
+
+M	src/imap-login/imap-proxy.c
+
+2021-10-08 15:53:21 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f4da884f67)
+
+    lib-storage: mail_storage_service_lookup() - Fix memory leak when returning
+    -2
+
+    This was also visible as event leaks.
+
+M	src/lib-storage/mail-storage-service.c
+
+2021-09-28 20:46:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f29786350e)
+
+    lib-index: Fix potential crash with debug logging when looking up cache
+
+
+M	src/lib-index/mail-cache-lookup.c
+
+2021-09-28 20:45:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a016ac6a3e)
+
+    lib-index: mail_cache_lookup_iter_next() - Add a warning comment about
+    re-reading fields
+
+
+M	src/lib-index/mail-cache-private.h
+
+2021-10-07 15:52:28 +0200 Marco Bettini <marco.bettini@open-xchange.com> (08656f3aad)
+
+    mail-crypt: mail_crypt_load_global_private_key() - Drop unnecessary NULL
+    check
+
+
+M	src/plugins/mail-crypt/mail-crypt-global-key.c
+
+2021-09-28 18:13:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b4217b8d36)
+
+    lib, global: i_stream_create_chain() - Add max_buffer_size
+
+    This makes sure that the istream size can't grow too large and waste memory.
+
+    Previously the istream max_buffer_size was dynamically changed to be the 
+    smallest seen max_buffer_size in chained istreams. This mostly worked, but 
+    sometimes the istream-chain's max_buffer_size was requested before even the 
+    first istream was added to it.
+
+    Having an explicit max_buffer_size avoids all the problems of it being 
+    dynamic, and there's not really any need for it anyway.
+
+M	src/imap/cmd-append.c
+M	src/lib-http/test-http-client-errors.c
+M	src/lib-smtp/smtp-server-cmd-data.c
+M	src/lib-storage/index/pop3c/pop3c-client.c
+M	src/lib/istream-chain.c
+M	src/lib/istream-chain.h
+M	src/lib/test-istream-chain.c
+
+2021-10-07 14:52:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2aa531b4a4)
+
+    virtual: Fix assert-crash when backend mbox mailbox is deleted while virtual
+    mailbox is open
+
+    The code wrongly thought that metadata lookup couldn't fail because it was 
+    already successfully looked up. But the backend storage could still try to 
+    refresh the mailbox to verify whether it still exists or not, and fail if it
+    was deleted.
+
+    This seems to have affected only the mbox mailbox format.
+
+    Broken by 710346bcb884b464c8ed128870fdc1999c13dfd3
+
+M	src/plugins/virtual/virtual-sync.c
+
+2021-10-05 02:03:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5050000f5d)
+
+    lib: istream-concat - Add a comment about explicit snapshot function
+
+
+M	src/lib/istream-concat.c
+
+2021-10-05 01:39:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e830aca4ef)
+
+    lib: test-istream-concat - Unref child istreams immediately
+
+    This doesn't currently make a difference, since istream-concat keeps them 
+    internally referenced. In case this changes and snapshot handling isn't 
+    fixed similarly to istream-chain, the unit test should now fail.
+
+M	src/lib/test-istream-concat.c
+
+2021-10-04 18:25:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9507dcba9f)
+
+    lib: istream-concat - Fix snapshot handling when combining two istreams
+
+    Snapshotting wasn't handled correctly when two (or more) istreams' contents 
+    were combined into the same buffer.
+
+M	src/lib/istream-concat.c
+M	src/lib/test-istream-concat.c
+
+2021-10-05 00:55:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4614c27f68)
+
+    lib: istream-chain - Optimize snapshot handling
+
+
+M	src/lib/istream-chain.c
+
+2021-10-05 01:32:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (539a052672)
+
+    lib: istream-chain - Fix snapshot handling when link istream is destroyed
+
+
+M	src/lib/istream-chain.c
+M	src/lib/istream-private.h
+M	src/lib/istream.c
+M	src/lib/test-istream-chain.c
+
+2021-10-04 18:17:49 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (504c0e6424)
+
+    lib: istream-chain - Fix snapshot handling when combining two istreams
+
+    Snapshotting wasn't handled correctly when two (or more) istreams' contents 
+    were combined into the same buffer.
+
+M	src/lib/istream-chain.c
+M	src/lib/test-istream-chain.c
+
+2021-10-05 01:00:38 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (39642d6b49)
+
+    lib: Add i_stream_memarea_detach()
+
+
+M	src/lib/istream-private.h
+M	src/lib/istream.c
+
+2021-10-04 17:41:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (67324622b6)
+
+    lib: test-istream-chain - Use more unique strings for testing
+
+    DEBUG checks don't work well if the input repeats the same character.
+
+M	src/lib/test-istream-chain.c
+
+2019-09-15 00:08:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (43d7f354c4)
+
+    lib: istream-seekable - Fix crash after write to temp file failed
+
+    Fixes: Panic: file istream-seekable.c: line 230 (read_from_buffer):
+    assertion failed: (*ret_r > 0)
+
+M	src/lib/istream-seekable.c
+
+2019-09-15 00:06:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (66fc9ffd4c)
+
+    lib: istream-seekable - Don't double-close temp file fd on errors
+
+    Closing the fd_input stream already auto-closes the fd.
+
+M	src/lib/istream-seekable.c
+
+2019-09-14 23:53:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (eb15b02121)
+
+    lib: istream-seekable - Fix crash if writing to temp file fails
+
+
+M	src/lib/istream-seekable.c
+M	src/lib/test-istream-seekable.c
+
+2021-09-29 13:13:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7f752d77be)
+
+    lib: i_stream_try_alloc() - Add sanity check asserts
+
+
+M	src/lib/istream.c
+
+2021-10-06 17:37:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (baf3e985c0)
+
+    lib-imap-client: test-imapc-client - Increase connect timeout for most tests
+
+    The 500ms timeout could have been too short when running with valgrind on an 
+    overloaded system. Increase it to 5 seconds, but keep 500ms for the tests 
+    that expect a timeout.
+
+M	src/lib-imap-client/test-imapc-client.c
+
+2021-10-06 05:03:08 -0400 Marco Bettini <marco.bettini@open-xchange.com> (313ce3e709)
+
+    lib-master: master_login_auth_callback() - Add assert to guard for both
+    errormsg and auth_args being NULL
+
+
+M	src/lib-master/master-login.c
+
+2021-10-07 03:55:59 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e7754572e6)
+
+    auth: db-passwd-file - Fix using paths with %% escaped characters but no
+    %variables
+
+    full_key might not have been NULL, causing var_expand() to read past the 
+    array and possibly crash.
+
+M	src/auth/db-passwd-file.c
+
+2021-10-07 03:46:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (687bc05ad0)
+
+    lib: t_get_bytes_available() - Move code inside DEBUG to avoid dead code
+    warning
+
+
+M	src/lib/data-stack.c
+
+2021-10-07 03:44:46 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6e02a7ba64)
+
+    lib-index: mail_index_sync_init_expunge_handlers() - Simplify and remove
+    dead code
+
+
+M	src/lib-index/mail-index-sync-ext.c
+
+2021-10-07 03:37:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1bf116c7a6)
+
+    lib-index: test-mail-index-write - Fix initializing map.hdr_copy_buf
+
+
+M	src/lib-index/test-mail-index-write.c
+
+2021-10-07 03:29:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a08c2213af)
+
+    lib-settings: test-settings-parser - Check settings_parser_check() return
+    value
+
+
+M	src/lib-settings/test-settings-parser.c
+
+2021-10-07 03:28:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a3b12700a6)
+
+    auth: Make sure auth_request_import() doesn't unexpectedly fail
+
+
+M	src/auth/auth-master-connection.c
+
+2021-10-07 03:10:28 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4327b55f45)
+
+    lib: Change i_unreached() to be a function
+
+    This allows overriding the function in a Coverity model, so it can 
+    understand that i_unreached() is intended to be unreachable code.
+
+M	src/lib/failures.c
+M	src/lib/failures.h
+M	src/lib/macros.h
+
+2021-10-07 02:32:26 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (44bd159679)
+
+    lib: base64 - Add asserts to check max_line_len is in valid range
+
+
+M	src/lib/base64.c
+
+2021-10-04 13:32:51 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1f0fad9044)
+
+    ipc: Disable connecting to stats
+
+    This avoids reconnect errors if stats process crashes.
+
+M	src/ipc/main.c
+
+2021-10-01 01:44:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (dec257fb78)
+
+    master: Use relative path for stats_writer_socket_path for chrooted services
+
+    This allows login process to reconnect to stats-writer if it gets 
+    disconnected.
+
+M	src/master/service-process.c
+
+2021-10-01 01:42:43 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f08703dae8)
+
+    stats: Add login/stats-writer socket
+
+
+M	src/stats/stats-settings.c
+
+2021-10-04 02:58:15 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (53b018722b)
+
+    lmtp: lmtp-proxy - Use the per-recipient session ID for the "Saved" message.
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2021-10-04 02:57:25 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0daefe8937)
+
+    lmtp: Move session_id field to generic recipient struct.
+
+
+M	src/lmtp/lmtp-commands.c
+M	src/lmtp/lmtp-local.c
+M	src/lmtp/lmtp-recipient.c
+M	src/lmtp/lmtp-recipient.h
+
+2021-10-02 00:21:40 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (47c7166b91)
+
+    lmtp: lmtp-proxy - Forward session ID towards backend.
+
+
+M	src/lmtp/lmtp-proxy.c
+
+2021-10-02 00:21:06 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (e67d8cdb88)
+
+    lmtp: lmtp-local - Always add RCPT index to session ID for delivery.
+
+    Even when it is 1.
+
+M	src/lmtp/lmtp-local.c
+
+2021-10-02 00:36:09 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (c8423c354b)
+
+    lib-smtp: smtp-server-transaction - Base transaction ID on connection
+    session ID.
+
+    Composed as "<connection session ID>:<trasaction sequence>".
+
+M	src/lib-smtp/smtp-server-private.h
+M	src/lib-smtp/smtp-server-transaction.c
+
+2021-10-02 00:32:05 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (e6451151f5)
+
+    lib-smtp: smtp-server-connection - Manage session ID for the connection.
+
+
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server-private.h
+
+2021-10-02 00:29:29 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (8fb86c24be)
+
+    lib-smtp: smtp-server-cmd-xclient - Parse XCLIENT SESSION field.
+
+
+M	src/lib-smtp/smtp-server-cmd-xclient.c
+M	src/lib-smtp/smtp-server-reply.c
+M	src/submission-login/client.c
+
+2021-10-02 00:16:22 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (8c6d07276b)
+
+    lib-smtp: smtp-client-connection - Send new SESSION field with XCLIENT
+    command.
+
+
+M	src/lib-smtp/smtp-client-connection.c
+
+2021-10-04 02:02:21 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (2d4587d13a)
+
+    lib-smtp: smtp-client - Use smtp_proxy_data_merge() to copy proxy data in
+    smtp_client_init().
+
+
+M	src/lib-smtp/smtp-client.c
+
+2021-10-02 00:15:07 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b1497466f2)
+
+    lib-smtp: smtp-common - Add SESSION field to proxy data.
+
+
+M	src/lib-smtp/smtp-common.c
+M	src/lib-smtp/smtp-common.h
+
+2021-10-02 00:25:50 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (66d9b71ad4)
+
+    lib: connection - Add debug message for when property label (peer address)
+    changes.
+
+
+M	src/lib/connection.c
+
+2021-10-02 00:26:28 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4a84440f53)
+
+    lib: connection - Make connection_update_properties() public.
+
+
+M	src/lib/connection.c
+M	src/lib/connection.h
+
+2021-10-02 00:24:35 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (44819f897c)
+
+    lib: connection - Drop useless newlines.
+
+
+M	src/lib/connection.c
+
+2019-02-21 18:50:10 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (1b13ee113b)
+
+    lib-program-client: test-program-client-local - Use dd instead of head
+
+    `head -c n` is only guaranteed to print n bytes, but it may consume more. 
+    FreeBSD's implementation of head(1) uses buffered stdio, which did just
+    that.
+
+    `dd` consumes exactly the specified number of bytes.
+
+M	src/lib-program-client/test-program-client-local.c
+
+2021-08-10 13:38:39 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1489e6378c)
+
+    login-proxy: Fix potential memory leak if backend login fails
+
+    It also needs an abnormal way of destroying the client for the leak to 
+    happen. For example if the login process is being killed.
+
+M	src/login-common/client-common.c
+
+2021-04-19 17:36:02 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (350600f984)
+
+    lib-test: fuzzer - Disable error handling for output stream towards program.
+
+    Fixes:
+
+    Panic: output stream  is missing error handling
+
+M	src/lib-test/fuzzer.c
+
+2021-04-19 17:34:58 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b41e7198ad)
+
+    lib-test: fuzzer - Provide names for fuzzer streams.
+
+
+M	src/lib-test/fuzzer.c
+
+2021-04-18 12:33:01 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (860e5e3a83)
+
+    lib-smtp: smtp-server-cmd-helo - Fix crash occurring upon pipelined EHLO
+    with invalid domain.
+
+    Failed to check for NULL in domain value for a pipelined EHLO/HELO command.
+
+M	src/lib-smtp/smtp-server-cmd-helo.c
+
+2021-10-06 13:20:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2e06ce6c98)
+
+    lib: Fix data_stack_get_alloc_size() and data_stack_get_used_size()
+
+    It only worked correctly if data stack hadn't been grown. This resulted in
+    wrong numbers in the data_stack_grow event.
+
+M	src/lib/data-stack.c
+
+2021-10-01 15:08:45 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bdf4474ed8)
+
+    virtual: Fix leaking mailboxes if virtual mailbox can't be opened
+
+    Fixes also a crash at deinit: Panic: file mail-user.c: line 232
+    (mail_user_deinit): assertion failed: ((*user)->refcount == 1)
+
+M	src/plugins/virtual/virtual-storage.c
+
+2021-10-04 17:25:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (79ebaf7b14)
+
+    mdbox: Avoid calling container_of() with a NULL pointer
+
+
+M	src/lib-storage/index/dbox-multi/mdbox-save.c
+
+2021-10-04 15:20:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6275be1981)
+
+    sdbox: Avoid calling container_of() with a NULL pointer
+
+
+M	src/lib-storage/index/dbox-single/sdbox-save.c
+
+2021-10-04 14:42:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a037ede37f)
+
+    lib: buffer_free() - Check for buf==NULL before using container_of()
+
+    Helps with static analyzer complaints.
+
+M	src/lib/buffer.c
+
+2021-10-04 14:42:14 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ecaaa728c7)
+
+    lib: lib-event - Assert-crash if attempting to use NULL passthrough event
+
+    Fixes also complaints from static analyzer.
+
+M	src/lib/lib-event.c
+
+2021-10-04 14:22:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bb5048cda0)
+
+    lib: container_of() - Don't check for NULL after all
+
+    Adding the explicit NULL checks for container_of() caused caused static 
+    analyzers to think that NULL could be returned at any time. This caused 
+    unnecessary warnings in various places.
+
+    Reverts b178d0792b6335277f7fa831fd7e5403105abd04
+
+M	src/lib/macros.h
+
+2021-10-04 14:35:03 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5de8349891)
+
+    lib: macros.h - Fix #endif position
+
+
+M	src/lib/macros.h
+
+2021-09-24 16:36:41 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6baff5b2d4)
+
+    lib-index: Add more mail_index_ext_name_is_valid() asserts
+
+
+M	src/lib-index/mail-index-map.c
+M	src/lib-index/mail-index-sync-ext.c
+
+2021-09-24 16:35:39 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f81779845a)
+
+    lib-index: Handle invalid extension header names without assert-crashing
+
+    Fixes: Panic: mail_index_ext_register(...): Invalid name
+
+M	src/lib-index/mail-index-map.c
+
+2021-09-24 16:27:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (05e570be12)
+
+    lib-index: Add and use mail_index_ext_name_is_valid()
+
+    Use it in mail_index_ext_register() instead of the more relaxed 
+    str_sanitize() check.
+
+M	src/lib-index/mail-index-private.h
+M	src/lib-index/mail-index.c
+
+2021-09-24 16:32:26 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4896364107)
+
+    lib-index: Remove unused mail_index_sync_ext_init()
+
+    It doesn't look like this was ever used for anything. The initial commit 
+    used only mail_index_sync_ext_init_new().
+
+M	src/lib-index/mail-index-sync-ext.c
+M	src/lib-index/mail-index-sync-private.h
+
+2021-07-29 11:37:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d7dc02bbb0)
+
+    zlib: Handle empty zlib_save_level the same as if it doesn't exist
+
+
+M	src/plugins/zlib/zlib-plugin.c
+
+2021-07-29 11:29:37 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (42457ff2eb)
+
+    zlib: Fix crash when zlib_save_level is set, but zlib_save is unset
+
+
+M	src/plugins/zlib/zlib-plugin.c
+
+2021-10-04 11:14:04 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (4cd6999dd5)
+
+    driver-pgsql: Fix error leak
+
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-10-04 12:37:11 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1e56bbb41b)
+
+    lib-compression: ostream-zlib - Fix non-blocking gz header write
+
+    Broken by 373dc6a93da1f6a0ad0c80dbb72566c2b3a295f2
+
+M	src/lib-compression/ostream-zlib.c
+
+2021-08-03 20:38:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1efd4a1765)
+
+    lib-storage: mailbox_get_expunge*() - Fix assert-crash on index corruption
+
+    This happened in the very unlikely situation that indexes became corrupted 
+    between two mail_transaction_log_view_set() calls.
+
+    Fixes: Panic: file mailbox-get.c: line 112 (mailbox_get_expunges_init):
+    assertion failed: (ret != 0)
+
+M	src/lib-storage/mailbox-get.c
+
+2021-10-01 15:21:38 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (088783cdaa)
+
+    lib: str_hash() - Ignore integer wrapping with ubsan
+
+    ATTR_NO_SANITIZE_INTEGER was already set for other hash functions, but 
+    forgotten for this one.
+
+    Fixes e.g.: Error: hash.c:529:16: runtime error: unsigned integer overflow:
+    4294967200 + 115 cannot be represented in type ‘unsigned int’
+
+M	src/lib/hash.c
+
+2021-09-28 00:51:03 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3c05dcfa58)
+
+    lib: ostream-wrapper - Make sure o_stream_finish() has stream_errno != 0
+    when -1 is to be returned.
+
+    Fixes:
+
+    Panic: file ostream.c: line 209 (o_stream_flush): assertion failed:
+    (stream->stream_errno != 0)
+
+M	src/lib/ostream-wrapper.c
+
+2021-10-01 10:42:16 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (a1729a72e8)
+
+    driver-cassandra: Fix prepared statement pool leak
+
+
+M	src/lib-sql/driver-cassandra.c
+
+2021-09-30 18:42:08 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (ebd5223c8d)
+
+    stats: Remove metric from stats dump, when removing dynamically.
+
+
+M	src/stats/stats-metrics.c
+
+2020-09-23 23:35:53 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (d16a69f7e2)
+
+    lib-ssl-iostream: istream-openssl - Remove assert() in i_stream_ssl_read().
+
+
+M	src/lib-ssl-iostream/istream-openssl.c
+
+2020-09-23 23:35:30 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (50b7231aab)
+
+    lib-ssl-iostream: iostream-openssl - Allow plain_input buffer to remain
+    filled in openssl_iostream_bio_input().
+
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2020-09-23 22:02:41 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b4d477a50b)
+
+    lib-ssl-iostream: iostream-openssl - Use o_stream_uncork_flush() to uncork
+    the plain output.
+
+    This flushes the stream after uncorking it, fixing I/O hang with nested SSL 
+    layers.
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2021-09-30 00:17:47 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f42c0637ac)
+
+    lib-ssl-iostream: iostream-openssl - Return immediately from
+    openssl_iostream_bio_output() upon error.
+
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2020-09-23 23:58:12 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3200480ecf)
+
+    lib-ssl-iostream: iostream-openssl - Move error handling out of
+    openssl_iostream_bio_output().
+
+    Makes the next commit clearer.
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2021-04-16 13:55:06 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (aa8a39557b)
+
+    lib-ssl-iostream: test-iostream-ssl - Make finishing the ssl streams more
+    robust.
+
+    Fixes:
+
+    Panic: file ostream.c: line 59 (o_stream_close_full): assertion failed:
+    (stream->real_stream->error_handling_disabled)
+
+M	src/lib-ssl-iostream/test-iostream-ssl.c
+
+2021-04-16 18:18:07 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (2816a2525b)
+
+    lib-ssl-iostream: test-iostream-ssl - Properly mark client endpoints.
+
+
+M	src/lib-ssl-iostream/test-iostream-ssl.c
+
+2021-04-16 18:21:01 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (7738cb723b)
+
+    lib-ssl-iostream: test-iostream-ssl - Move small_packets_flush_callback().
+
+
+M	src/lib-ssl-iostream/test-iostream-ssl.c
+
+2021-04-16 18:19:55 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3e53f67a65)
+
+    lib-ssl-iostream: test-iostream-ssl - Move bufsize_flush_callback().
+
+
+M	src/lib-ssl-iostream/test-iostream-ssl.c
+
+2021-04-16 14:52:34 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0f4a532bd1)
+
+    lib-ssl-iostream: ostream-openssl - Call SSL_shutdown() once stream is
+    finished and buffer is empty.
+
+
+M	src/lib-ssl-iostream/ostream-openssl.c
+
+2021-04-16 12:31:13 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (46970690e9)
+
+    lib: istream - Remove try_alloc_limit.
+
+
+M	src/lib/istream-private.h
+M	src/lib/istream.c
+
+2021-04-16 12:27:48 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b44c5504ff)
+
+    lib-ssl-iostream - iostream-openssl - Use i_stream_read_limited().
+
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+
+2021-04-16 12:25:43 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (377f1cbe88)
+
+    lib: istream - Add i_stream_read_limited().
+
+
+M	src/lib/istream-private.h
+M	src/lib/istream.c
+M	src/lib/istream.h
+
+2020-09-27 15:19:05 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (00df102e56)
+
+    lib-ssl-iostream: istream-openssl - Simplify i_stream_ssl_read().
+
+    Avoid using a stack buffer. Just fill the stream to the maximum buffer size.
+
+M	src/lib-ssl-iostream/istream-openssl.c
+
+2021-04-16 18:08:50 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b4a2da442d)
+
+    lib-ssl-iostream: iostream-openssl - Add OPENSSL_IOSTREAM_SYNC_TYPE_NONE.
+
+    This is useful for calling openssl_iostream_handle_error() while avoiding 
+    processing more I/O.
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+M	src/lib-ssl-iostream/iostream-openssl.h
+
+2021-09-24 19:16:43 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8a9d1aa7b6)
+
+    lib-ssl-iostream: Fix error handling if parent iostream fails
+
+    Expand out and remove openssl_iostream_more(). It could have returned errors
+    to two different locations depending on whether the failure came from SSL
+    handshake or parent iostream.
+
+M	src/lib-ssl-iostream/iostream-openssl.c
+M	src/lib-ssl-iostream/iostream-openssl.h
+M	src/lib-ssl-iostream/istream-openssl.c
+M	src/lib-ssl-iostream/ostream-openssl.c
+
+2021-09-24 19:15:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (75a6da0a1e)
+
+    lib-ssl-iostream: ostream-ssl - Add ssl_io helper variable
+
+
+M	src/lib-ssl-iostream/ostream-openssl.c
+
+2021-09-24 18:59:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6289334552)
+
+    lib: o_stream_copy_error_from_parent() - Assert-crash if source stream_errno
+    is 0
+
+    This can help debug situations where error is missing.
+
+M	src/lib/ostream.c
+
+2021-09-24 18:59:21 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8f22f0ab6e)
+
+    global: Call o_stream_copy_error_from_parent() only on errors
+
+
+M	src/lib-http/http-transfer-chunked.c
+M	src/lib-mail/ostream-dot.c
+
+2021-09-24 18:58:02 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (373dc6a93d)
+
+    lib-compression: ostream-zlib - Fix sending partial gz header to parent
+    ostream
+
+    This practically wouldn't happen.
+
+M	src/lib-compression/ostream-zlib.c
+
+2021-09-30 15:38:23 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1545fadb3d)
+
+    lib-http: test-http-client-errors - Revert previous retry changes
+
+    The real bug is found now, so the previous fix attempt isn't useful. Reverts
+    ed1264368a5435c3080871380156978a8951fe26
+
+M	src/lib-http/test-http-client-errors.c
+
+2021-09-30 15:32:24 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (37f4f27114)
+
+    lib-http: test-http-client-errors - Reset USR1 notification signal between
+    tests
+
+    The "connection refused" test already set the signal as being received, 
+    which caused the "connection refused backoff" test to randomly fail since it 
+    thought the signal was immediately received.
+
+M	src/lib-http/test-http-client-errors.c
+
+2021-09-25 00:48:12 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (ddb173aa3d)
+
+    lib: istream-concat - Prevent inheriting SIZE_MAX max buffer size from
+    parent streams.
+
+    Only when all parent streams have SIZE_MAX for max buffer size, the concat
+    stream will follow suit.
+
+M	src/lib/istream-concat.c
+
+2021-09-27 20:35:19 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f5c1baf2cd)
+
+    lib-storage: Add mailbox_list.disable_rebuild_on_corruption
+
+    If set, don't try to rebuild the mailbox list index even if corruption is 
+    detected.
+
+M	src/lib-storage/list/mailbox-list-index.c
+M	src/lib-storage/mailbox-list-private.h
+
+2021-09-24 17:59:06 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (462d9b6ea6)
+
+    lib-dcrypt: Fix istream-decrypt snapshotting
+
+    This is mostly copy&pasted from istream-header-filter.
+
+M	src/lib-dcrypt/istream-decrypt.c
+M	src/lib-dcrypt/test-stream.c
+
+2021-09-23 18:02:17 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (9bf48f34bf)
+
+    lib-storage: hide mail_cache_min_mail_count setting
+
+
+M	src/lib-storage/mail-storage-settings.c
+
+2021-09-26 01:16:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ccb0579b3b)
+
+    master: Avoid leaking master client fds to forked processes
+
+
+M	src/master/master-client.c
+
+2021-09-29 14:58:22 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (231105a825)
+
+    stats: Improve error message for doveadm stats add.
+
+
+M	src/stats/client-reader.c
+
+2021-09-29 14:57:34 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (22bc379024)
+
+    stats: Check if metric already exists when adding dynamically.
+
+
+M	src/stats/stats-metrics.c
+
+2021-09-28 15:35:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1ad80be9a6)
+
+    driver-pglsqlpool: Implement sql_get_flags() reliably
+
+    Try to use one of the already connected databases. If none were found, 
+    create a new database.
+
+M	src/lib-sql/driver-sqlpool.c
+
+2021-09-28 15:32:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a55f18c67f)
+
+    driver-pgsql: Implement sql_get_flags() reliably
+
+    Some of the flags aren't known until server is connected to, so wait for the 
+    connect to finish if necessary before returning the flags.
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-09-28 15:33:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a90663e588)
+
+    driver-sqlpool: Implement sql_wait()
+
+
+M	src/lib-sql/driver-sqlpool.c
+
+2021-09-27 11:59:09 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (8146312f9c)
+
+    driver-cassandra: Add wait() implementation
+
+
+M	src/lib-sql/driver-cassandra.c
+
+2021-09-27 11:58:21 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (8981a97398)
+
+    driver-pgsql: Add wait() implementation
+
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-09-27 11:48:34 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (beca893733)
+
+    dict-sql: Add "sql_dict_wait()" implementation
+
+    lib-sql now has sql_wait().
+
+M	src/lib-dict-backend/dict-sql.c
+
+2021-09-27 11:46:07 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (3e717dd730)
+
+    lib-sql: Add "wait" vfunc
+
+    To be used in backends that support async operations. On deinit dicts should
+    wait for results and then exit.
+
+M	src/lib-sql/sql-api-private.h
+M	src/lib-sql/sql-api.c
+M	src/lib-sql/sql-api.h
+
+2021-09-27 19:43:54 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (d5bf78e2da)
+
+    driver-pgsql: Keep a list of pending query results
+
+    To be used in wait() to determine if there are pending operations.
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-09-22 20:57:06 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a660f544ea)
+
+    dict: Wait for all dicts to finish pending operations at shutdown
+
+    This also changes the dict process to exit cleanly instead of early via 
+    lib_exit().
+
+M	src/dict/dict-init-cache.c
+M	src/dict/dict-init-cache.h
+M	src/dict/main.c
+
+2019-07-11 12:05:37 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d571a13586)
+
+    dict: Unref dict connection immediately when connection is destroyed
+
+    The delayed unrefing is needed when calling from dict command callbacks to 
+    avoid calling dict_deinit() too early. But the connection's destroy() vfunc 
+    can't be called directly from dict command callbacks, so there's no need to
+    add further delays. Especially when this happens at deinit where there is no
+    possibility of calling the delayed callback anymore.
+
+M	src/dict/dict-connection.c
+
+2021-09-27 11:42:07 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (60f20b00b9)
+
+    driver-pgsql: Fix escaped blob prefix
+
+    Escaped strings must be prefixed with double backslash.
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-09-27 11:35:38 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (103a99d531)
+
+    driver-pgsql: Fix assert-crash when using binary fields
+
+    array_idx_modifiable() changed behavior in 
+    1d4e5de8414ed93d1c810b30a91ad83d6d954861 and doesn't allocate space anymore.
+
+    Fixes: Panic: file array.c: line 10 (array_idx_modifiable_i): assertion
+    failed: (idx < array->buffer->used / array->element_size)
+
+M	src/lib-sql/driver-pgsql.c
+
+2021-09-29 18:04:37 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (f318731eac)
+
+    doveadm mail batch: Fix run() return value on errors
+
+    The function always returned 0, even when there was an error. This
+    practically doesn't fix anything, but makes it work the way it was intended.
+    It also makes scan-build happier.
+
+    Broken by fd4360e30b695e596a5081a6080152188a12852a
+
+M	src/doveadm/doveadm-mail-batch.c
+
+2021-09-22 19:34:32 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d10bc56592)
+
+    mdbox: Add assert to help static analyzers
+
+    mdbox_sync_begin() can return 0 with sync_ctx=NULL, but not with the 
+    parameters given here.
+
+M	src/lib-storage/index/dbox-multi/mdbox-save.c
+
+2021-09-22 19:02:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (67c96d89f3)
+
+    lib-smtp: smtp_client_transaction_get_state_name() - Fix return value
+
+    Only the "new" state was returned correctly, while everything else was 
+    wrong. This didn't really cause problems, because it was used only by 
+    imaptest.
+
+M	src/lib-smtp/smtp-client-transaction.c
+M	src/lib-smtp/smtp-client-transaction.h
+
+2021-09-22 18:46:39 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (37ab021b41)
+
+    lib: Ignore COMPILE_ERROR_IF_TRUE() with --enable-static-checker
+
+
+M	src/lib/macros.h
+
+2021-09-28 01:24:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b178d0792b)
+
+    lib: container_of() - Always return NULL if input pointer is NULL
+
+
+M	src/lib/macros.h
+
+2021-09-22 18:40:11 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (71d3f9a4c0)
+
+    lib: container_of() - Avoid uintptr_t
+
+    Using uintptr_t confused Coverity, but char* works fine.
+
+M	src/lib/macros.h
+
+2021-09-29 00:21:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e47c4a3ff7)
+
+    indexer: Abort requests if indexer-worker disconnects unexpectedly
+
+
+M	src/indexer/worker-connection.c
+
+2021-04-19 21:43:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1e14dd0f49)
+
+    lib-storage: Add reason_code=mail:header_fields
+
+
+M	src/lib-storage/index/index-mail-headers.c
+
+2021-04-19 21:38:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (034597d2fe)
+
+    lib-storage: Add more specific mail:* reason_codes in when prefetching
+
+    Added:
+    * mail:attachment_keywords
+    * mail:date
+    * mail:snippet
+    * mail:mime_parts
+    * mail:imap_envelope
+    * mail:imap_bodystructure
+
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/index-mail.h
+
+2021-03-11 23:38:01 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (88c2ee58f1)
+
+    fts: Add reason_code=fts:index and fts:lookup
+
+    Note that fts:index won't be used for email opening events, because the 
+    emails are already opened by the indexer precache searching code due to 
+    wanted_fields.
+
+M	src/plugins/fts/fts-search.c
+M	src/plugins/fts/fts-storage.c
+
+2021-09-09 16:45:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1279e1bd00)
+
+    lib-storage: Add reason_code=mailbox:*
+
+    Added:
+    * mailbox:create
+    * mailbox:update
+    * mailbox:delete
+    * mailbox:rename
+    * mailbox:subscribe
+    * mailbox:unsubscribe
+    * mailbox:attributes_changed
+
+M	src/lib-storage/mail-storage.c
+
+2021-03-17 15:15:00 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (87cd6570a1)
+
+    lib-storage: Add reason_code=mailbox:search
+
+
+M	src/lib-storage/index/index-search.c
+
+2021-03-17 14:49:24 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (858da66efb)
+
+    lib-storage: Add reason_code=mailbox:thread
+
+    This is split into a few different locations in the code.
+
+M	src/lib-storage/index/index-thread-finish.c
+M	src/lib-storage/index/index-thread.c
+
+2021-03-17 17:48:57 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (eedf4a3f22)
+
+    lib-storage: Add reason_code=mailbox:vsize*
+
+     * mailbox:vsize - mailbox vsize is asked
+    * mailbox:vsize_update - mailbox vsize is updated (append/expunge)
+
+M	src/lib-storage/index/index-mailbox-size.c
+
+2021-03-17 17:48:33 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d4b3fcee59)
+
+    lib-storage: Add reason_code=mailbox:sort
+
+
+M	src/lib-storage/index/index-sort.c
+
+2021-03-11 19:43:22 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (ce517f8323)
+
+    lib-storage: Add reason_code=mail:*
+
+     * mail:virtual_size
+    * mail:physical_size
+    * mail:snippet
+    * mail:prefetch
+    * mail:mime_parts
+    * mail:date
+    * mail:received_date
+    * mail:save_date
+    * mail:storage_id
+    * mail:pop3_uidl
+    * mail:pop3_order
+    * mail:guid
+    * mail:refcount
+    * mail:refcount_id
+
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/mail.c
+
+2021-03-11 19:07:10 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (962efef5f7)
+
+    lib-storage: Remove mailbox_set_reason()
+
+    Event reason_codes should be used instead. Keep the "Mailbox opened" debug
+    message mainly because it's often used in CI tests.
+
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail-storage.c
+M	src/lib-storage/mail-storage.h
+
+2021-03-11 19:06:00 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (f1db5fbfb3)
+
+    global: Remove mailbox_set_reason() calls
+
+    These ones that are left are all rather unnecessary.
+
+M	src/lib-imap-storage/imap-metadata.c
+M	src/lib-imap-storage/imap-msgpart-url.c
+M	src/lib-imap-urlauth/imap-urlauth-backend.c
+M	src/lib-lda/mail-deliver.c
+M	src/lib-storage/mail-storage.c
+M	src/lmtp/lmtp-local.c
+M	src/plugins/quota/quota-status.c
+M	src/plugins/virtual/virtual-storage.c
+
+2021-03-17 17:40:46 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d2ab26be60)
+
+    pop3: Add reason_code=pop3:cmd_<command name> to events
+
+
+M	src/pop3/pop3-client.c
+
+2021-03-11 19:04:36 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (641d0bd3bd)
+
+    pop3: Replace mailbox_set_reason() with reason_code=pop3:initialize
+
+
+M	src/pop3/main.c
+M	src/pop3/pop3-client.c
+
+2021-03-11 17:29:59 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (bf1b124e0d)
+
+    virtual: Replace mailbox_set_reason() with reason_code=virtual:config_read
+
+
+M	src/plugins/virtual/virtual-config.c
+
+2021-03-11 17:27:54 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d31e44e66c)
+
+    trash: Replace mailbox_set_reason() with reason_code=trash:clean
+
+
+M	src/plugins/trash/trash-plugin.c
+
+2021-03-09 16:49:12 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (459f9eeeac)
+
+    quota: Replace mailbox_set_reason() with reason_code=quota:recalculate and
+    quota:count
+
+
+M	src/plugins/quota/quota-count.c
+M	src/plugins/quota/quota-dict.c
+M	src/plugins/quota/quota-maildir.c
+
+2021-03-11 17:22:31 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (b0d8db1bb3)
+
+    pop3-migration: Replace mailbox_set_reason() with
+    reason_code=pop3_migration:uidl_sync
+
+
+M	src/plugins/pop3-migration/pop3-migration-plugin.c
+
+2021-03-11 17:03:49 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (d2af1066c4)
+
+    lazy_expunge: Replace mailbox_set_reason() with
+    reason_code=lazy_expunge:expunge
+
+
+M	src/plugins/lazy-expunge/lazy-expunge-plugin.c
+
+2021-03-17 18:23:11 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (bc85909b45)
+
+    lazy-expunge: Split off lazy_expunge_mail_expunge_move()
+
+
+M	src/plugins/lazy-expunge/lazy-expunge-plugin.c
+
+2021-03-11 16:56:32 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (2ec30d083b)
+
+    lib-storage: Replace mailbox_set_reason() with
+    reason_code=storage:autoexpunge and storage:mailbox_list_rebuild
+
+
+M	src/lib-storage/list/mail-storage-list-index-rebuild.c
+M	src/lib-storage/mail-autoexpunge.c
+
+2021-03-11 16:53:35 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (a20d06d4b3)
+
+    mdbox: Replace mailbox_set_reason() with reason_code=mdbox:rebuild
+
+
+M	src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c
+
+2021-03-16 19:02:06 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1a30dc1b43)
+
+    mdbox: Split off mdbox_storage_rebuild_scan_prepare()
+
+
+M	src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c
+
+2021-03-11 15:46:11 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (e3dc046996)
+
+    indexer: Replace mailbox_set_reason() with reason_code=indexer:index_mailbox
+
+
+M	src/indexer/master-connection.c
+
+2021-03-17 17:28:03 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (bb1df67719)
+
+    imap: Add reason_code=imap:fetch_*
+
+    The main reason for these is to allow understanding why mails are being 
+    opened. The possibilities:
+
+     * imap:fetch_body
+    * imap:fetch_header
+    * imap:fetch_header_fields
+    * imap:fetch_bodystructure
+    * imap:fetch_size
+
+M	src/imap/cmd-fetch.c
+M	src/imap/imap-fetch-body.c
+M	src/imap/imap-fetch.h
+
+2021-03-11 15:44:47 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (b41063291e)
+
+    imap: Remove mailbox_set_reason() calls
+
+    They are now unnecessary because of the new reason_code field.
+
+M	src/imap/cmd-create.c
+M	src/imap/cmd-delete.c
+M	src/imap/cmd-getmetadata.c
+M	src/imap/cmd-notify.c
+M	src/imap/cmd-rename.c
+M	src/imap/cmd-resetkey.c
+M	src/imap/cmd-select.c
+M	src/imap/cmd-setmetadata.c
+M	src/imap/cmd-subscribe.c
+M	src/imap/imap-commands-util.c
+M	src/imap/imap-notify.c
+M	src/imap/imap-state.c
+M	src/imap/imap-status.c
+
+2021-03-11 15:44:10 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (61685f7b7a)
+
+    imap: Add reason_code=imap:unhibernate for events during unhibernation.
+
+
+M	src/imap/imap-master-client.c
+
+2021-03-11 15:39:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (4eb305cccc)
+
+    imap: Add reason_code=imap:notify_update for events during NOTIFY updates
+
+
+M	src/imap/imap-notify.c
+
+2021-03-09 16:37:18 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (ad5ef653eb)
+
+    imap: Add and use client_command_context.global_event
+
+    This way all the (potentially large) IMAP command parameters won't be 
+    included in all the events. This change might be reverted in the future if
+    the performance worries go away.
+
+    The global event also contains reason_code=imap:cmd_<command name> field.
+
+M	src/imap/cmd-append.c
+M	src/imap/cmd-create.c
+M	src/imap/cmd-delete.c
+M	src/imap/cmd-getmetadata.c
+M	src/imap/cmd-rename.c
+M	src/imap/cmd-resetkey.c
+M	src/imap/cmd-select.c
+M	src/imap/cmd-setmetadata.c
+M	src/imap/cmd-status.c
+M	src/imap/cmd-subscribe.c
+M	src/imap/imap-client.c
+M	src/imap/imap-client.h
+M	src/imap/imap-commands-util.c
+M	src/imap/imap-commands.c
+
+2021-03-11 15:26:56 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (cfc7b74425)
+
+    doveadm: Remove mailbox_set_reason() calls
+
+    They are now unnecessary because of the new reason_code field.
+
+M	src/doveadm/doveadm-mail-copymove.c
+M	src/doveadm/doveadm-mail-import.c
+M	src/doveadm/doveadm-mail-index.c
+M	src/doveadm/doveadm-mail-iter.c
+M	src/doveadm/doveadm-mail-mailbox-metadata.c
+M	src/doveadm/doveadm-mail-mailbox-status.c
+M	src/doveadm/doveadm-mail-mailbox.c
+M	src/doveadm/doveadm-mail-save.c
+M	src/doveadm/doveadm-mail.c
+
+2021-03-11 15:26:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (fd4360e30b)
+
+    doveadm: Add reason_code=doveadm:cmd_<command name> to events
+
+
+M	src/doveadm/doveadm-mail-batch.c
+M	src/doveadm/doveadm-mail.c
+
+2021-04-20 00:05:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (82f0d7bf81)
+
+    dsync: ioloop context shouldn't deactivate after ioloop has run
+
+    Normally that is wanted, but dsync reuses the same ioloop as the parent 
+    doveadm connection, so after the inner io_loop_run() it's still running in
+    the doveadm connection's io callback.
+
+    Without this all the global events will be popped out, and the following 
+    global event change commit will cause crashes.
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-04-20 00:28:02 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a9ffa98ac7)
+
+    doveadm copy: Don't permanently activate source user's ioloop
+
+
+M	src/doveadm/doveadm-mail-copymove.c
+
+2021-04-20 00:24:39 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (23c1983be1)
+
+    doveadm import: Don't permanently activate source user's ioloop
+
+    After the source user is initialized, the original user's ioloop should be 
+    activated back.
+
+M	src/doveadm/doveadm-mail-import.c
+
+2021-03-17 18:48:37 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (19779828ea)
+
+    submission: Add reason_code=submission:cmd_<name>
+
+
+M	src/submission-login/client.c
+M	src/submission/main.c
+
+2021-03-17 18:48:24 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (ebd660c594)
+
+    lmtp: Add reason_code=lmtp:cmd_<name>
+
+
+M	src/lmtp/main.c
+
+2021-03-17 18:44:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (750f14dae0)
+
+    lib-smtp: Add reason_code=<reason_code_module>:cmd_*
+
+    This requires caller to set smtp_server_settings.reason_code_module. For now
+    only cmd_mail, cmd_rcpt and cmd_data are implemented. The other commands are
+    likely not very useful.
+
+M	src/lib-smtp/smtp-server-cmd-data.c
+M	src/lib-smtp/smtp-server-cmd-mail.c
+M	src/lib-smtp/smtp-server-cmd-rcpt.c
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server-private.h
+M	src/lib-smtp/smtp-server.c
+M	src/lib-smtp/smtp-server.h
+
+2021-04-22 20:27:45 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7b6e14ce5e)
+
+    lib-http: http-client-request - Preserve global events' reason_code in
+    request events
+
+    Since HTTP requests are asynchronous, it's possible that the global events 
+    go away before the HTTP request is finished. This way the reason_code will 
+    be preserved in http_request_finished event.
+
+M	src/lib-http/http-client-request.c
+
+2021-03-11 17:54:18 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (fbcb1dafbf)
+
+    lib: Add event_reason_code() for building reason codes easily
+
+
+M	src/lib/lib-event.c
+M	src/lib/lib-event.h
+M	src/lib/test-lib-event.c
+M	src/lib/test-lib.inc
+
+2021-03-09 16:45:20 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c0a3b73732)
+
+    lib: Add event_reason_begin/end() API
+
+    This can be used to easily add reason_code to all events being emitted 
+    within the begin..end calls. The reasons can also be nested. For example:
+
+    reason = event_reason_begin("reason1");
+    // ... reason2 = event_reason_begin("reason2");
+    // .. event_reason_end(&reason2); event_reason_end(&reason);
+
+    This results in having reason_code=["reason1", "reason2"] for all events 
+    emitted while reason2 exists.
+
+M	src/lib/lib-event.c
+M	src/lib/lib-event.h
+
+2021-04-21 16:55:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2bba7f1f34)
+
+    lib: event_pop_global() - Assert-crash if trying to pop ioloop context's
+    global root event
+
+    This makes it easier to debug bugs.
+
+M	src/lib/ioloop-private.h
+M	src/lib/ioloop.c
+M	src/lib/lib-event.c
+
+2021-03-11 02:19:37 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c633d08bf2)
+
+    lib: Push/pop global event stack automatically when ioloop contexts are
+    switched
+
+
+M	src/lib/ioloop-private.h
+M	src/lib/ioloop.c
+M	src/lib/lib-event.h
+M	src/lib/test-ioloop.c
+
+2021-04-21 17:11:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (75285a3169)
+
+    lib-storage: mail_storage_service_next*() - On failure don't leave user's io
+    context activated
+
+
+M	src/lib-storage/mail-storage-service.c
+
+2021-03-11 02:18:55 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (340e961cfe)
+
+    lib-storage: Remove global event stack tracking
+
+    This will be implemented by ioloop contexts directly in the next commit.
+
+M	src/lib-storage/mail-storage-service.c
+
+2021-03-09 16:33:15 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c822238604)
+
+    lib: Fix global events to actually work
+
+    Also add comments to clarify how exactly it works.
+
+M	src/lib/event-filter.c
+M	src/lib/lib-event.c
+M	src/lib/lib-event.h
+M	src/lib/test-event-filter.c
+
+2021-04-20 00:08:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (16fca42dec)
+
+    lib: event_push_global() - Assert that event is not NULL
+
+
+M	src/lib/lib-event.c
+
+2021-09-09 13:46:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e2995d85c2)
+
+    lib-master: Fix sending events recursively
+
+    The event sending itself may recursively trigger more events (e.g. 
+    data_stack_grow). The previous BEGINs must have been flushed by that time or
+    the recursive events might be pointing to event IDs that haven't even been
+    sent to the stats process yet.
+
+    Fixes: stats: Error: Client sent invalid input for UPDATE: Unknown event ID
+
+M	src/lib-master/stats-client.c
+
+2021-03-09 16:29:49 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (9d93a24824)
+
+    lib-master, stats: Send global events to stats process
+
+
+M	src/lib-master/stats-client.c
+M	src/lib-master/test-event-stats.c
+M	src/stats/client-writer.c
+M	src/stats/test-client-writer.c
+
+2021-03-19 13:13:45 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (0a57bd650e)
+
+    stats: Support group_by for string lists
+
+
+M	src/stats/stats-metrics.c
+
+2021-01-27 13:10:07 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (5f8775fc40)
+
+    stats: Support exporting event string lists
+
+
+M	src/stats/event-exporter-fmt-json.c
+M	src/stats/event-exporter-fmt-tab-text.c
+
+2021-03-19 13:02:54 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (775ddf19ed)
+
+    stats: Split off stats_metric_get_sub_metric()
+
+
+M	src/stats/stats-metrics.c
+
+2021-03-19 13:05:35 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (a1623b5e44)
+
+    stats: Rename stats_metric_get_sub_metric() to
+    stats_metric_find_sub_metric()
+
+
+M	src/stats/stats-metrics.c
+
+2021-03-19 12:59:53 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (81d50807bf)
+
+    stats: Split off stats_metric_group_by_value_label()
+
+
+M	src/stats/stats-metrics.c
+
+2021-03-19 12:52:13 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (add670d598)
+
+    stats: stats_metric_group_by_*() - Add _r suffix to returned value parameter
+
+
+M	src/stats/stats-metrics.c
+
+2020-12-15 09:17:57 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (4f752d381c)
+
+    lib: lib-event - Add support for string lists
+
+    Provide API to create string lists. These are particularly useful if you
+    need to provide list of causes for an event, such as why some mail was
+    opened.
+
+M	src/lib/Makefile.am
+M	src/lib/event-filter.c
+M	src/lib/lib-event.c
+M	src/lib/lib-event.h
+M	src/lib/test-event-filter.c
+M	src/lib/test-event-flatten.c
+A	src/lib/test-lib-event.c
+M	src/lib/test-lib.inc
+M	src/stats/event-exporter-fmt-json.c
+M	src/stats/event-exporter-fmt-tab-text.c
+M	src/stats/stats-metrics.c
+
+2021-08-23 17:21:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9a4d33f893)
+
+    lib: test-event-filter - Improve "override parent fields" test
+
+
+M	src/lib/test-event-filter.c
+
+2021-08-23 17:15:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e670cb1468)
+
+    lib: test-event-filter - Fix parent/child events to actually be parent/child
+
+
+M	src/lib/test-event-filter.c
+
+2021-03-16 19:22:25 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1411aaf4a4)
+
+    lib: Split off event_import_arg()
+
+
+M	src/lib/lib-event.c
+
+2021-03-16 19:19:43 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (bb602382b9)
+
+    lib: Split off event_import_field()
+
+
+M	src/lib/lib-event.c
+
+2021-09-24 18:21:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a12796fd66)
+
+    lib-index: Allow ignoring index corruption checks with --enable-devel-checks
+
+    If DEBUG_IGNORE_INDEX_CORRUPTION environment is set, don't check if index
+    contains internal corruption. This is useful for CI tests that intentionally
+    test corrupted indexes.
+
+M	src/lib-index/mail-index-sync-update.c
+
+2021-09-28 00:28:35 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (c92afe67a1)
+
+    lib-http: http-server: Add request events
+
+    Adds http_server_request_started and http_server_request_finished.
+
+M	src/lib-http/http-server-connection.c
+M	src/lib-http/http-server-private.h
+M	src/lib-http/http-server-request.c
+
+2021-08-26 13:13:55 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (12229f5e61)
+
+    lib-http: http-server-request - Add request_id and status_code fields to
+    event
+
+
+M	src/lib-http/http-server-request.c
+M	src/lib-http/http-server-response.c
+
+2021-09-28 15:35:21 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (e8d9838485)
+
+    stats: Use duplicated metric settings in stats_metrics_add_dynamic().
+
+
+M	src/stats/stats-metrics.c
+
+2021-09-15 14:44:42 +0200 Markus Valentin <markus.valentin@open-xchange.com> (b04cb5ea03)
+
+    imapc: Add MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX storage class flag
+
+    Add SECONDARY_INDEX storage class flag to enable storing shared private 
+    indexes in obox user root bundle.
+
+M	src/lib-storage/index/imapc/imapc-storage.c
+
+2021-09-24 17:33:38 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e399436c15)
+
+    lib-storage: mail_get_binary_stream() - Add comment about having to unref
+    returned istream
+
+
+M	src/lib-storage/mail-storage.h
+
+2021-09-22 15:58:55 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1ce928c75f)
+
+    lib: test-cpu-limit - Remove dead code
+
+    No longer needed after 6d902507c24fca4f64e3e9bf7d79ae5a48281cd8
+
+M	src/lib/test-cpu-limit.c
+
+2021-09-27 11:49:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0a70b02bbb)
+
+    lib: test-strfuncs - Avoid testing p_strndup() with overlong max_chars
+    parameter
+
+    This ended up in memchr() call with n=SIZE_MAX-1, which sometimes doesn't 
+    work right with old glibc versions.
+
+    Fixes: Panic: Trying to allocate 18446744073709551615 bytes
+
+M	src/lib/test-strfuncs.c
+
+2021-09-08 10:39:36 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (211040d836)
+
+    lib-mail: Add test for empty header value
+
+
+M	src/lib-mail/test-message-header-parser.c
+
+2021-09-02 17:10:11 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (d0e0b22b6b)
+
+    lib-mail: Limit header length to 1000 bytes
+
+
+M	src/lib-mail/message-header-parser.c
+M	src/lib-mail/test-message-header-parser.c
+
+2021-09-02 17:12:55 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (44c43133a4)
+
+    lib-mail: If message header has no colon, store it as value only
+
+    If the header is missing :, it is not valid header. Storing it as value only
+    ensures it will be kept by mbox rewrite, but will not be processed as a
+    header.
+
+M	src/lib-mail/message-header-parser.c
+M	src/lib-mail/test-message-header-parser.c
+M	src/plugins/pop3-migration/pop3-migration-plugin.c
+
+2021-09-02 16:46:19 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (ac65213384)
+
+    lib-mail: test-message-header-parser - Add NAME10, 100, 1000 macros for
+    testing
+
+
+M	src/lib-mail/test-message-header-parser.c
+
+2021-09-07 14:37:34 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (0009fd1edf)
+
+    lib: Add i_memspn() and i_memcspn()
+
+    Binary data safe variants of strspn() and strcspn()
+
+M	src/lib/strfuncs.c
+M	src/lib/strfuncs.h
+M	src/lib/test-strfuncs.c
+
+2021-09-16 13:32:37 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (8af2bd8640)
+
+    virtual: Log a debug message why backend mailbox has changed
+
+
+M	src/plugins/virtual/virtual-sync.c
+
+2021-09-24 10:39:43 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (801405d49f)
+
+    lib-storage: mail-duplicate - Restructure mail_duplicate_read_db_file() to
+    make sure fd is closed.
+
+    Found by Coverity.
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-09-24 10:22:28 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (99d129cd50)
+
+    lib-storage: mail-duplicate - Fix segfault occurring upon failure to lock
+    and open DB file.
+
+    Found by Coverity.
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-08-18 16:54:00 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (26284cf6f7)
+
+    indexer-worker: Fix event leak on error handling
+
+
+M	src/indexer/master-connection.c
+
+2021-07-13 04:15:14 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (2d32ee8b54)
+
+    lib-storage: mail-duplicate - Implement separate error code for deadlock.
+
+
+M	src/lib-storage/mail-duplicate.c
+M	src/lib-storage/mail-duplicate.h
+
+2021-09-16 01:49:57 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (dcee43b54c)
+
+    lib-storage: mail-duplicate - Update records from duplicate DB file after
+    acquirement of per-ID lock.
+
+    The process previously holding the per-ID lock may have updated the DB.
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-09-17 11:11:21 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (a27a97f49d)
+
+    lib-storage: mail-duplicate - Move acquirement of dotlock for DB file into
+    mail_duplicate_read().
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-09-20 11:58:12 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9cb56c3b28)
+
+    lib-storage: mail-duplicate - Implement per-ID locking.
+
+
+M	src/lib-storage/mail-duplicate.c
+M	src/lib-storage/mail-duplicate.h
+
+2021-09-17 11:16:39 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9bd8fff3b0)
+
+    lib-storage: mail-duplicate - Allow calling mail_duplicate_read() more than
+    once in a transaction.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-07-13 23:22:52 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b3d183d81d)
+
+    lib-storage: mail-duplicate - Record an entry for both checked and marked
+    IDs.
+
+    Still only write the marked IDs to the file. The in-memory record is needed
+    for the locking introduced in a later commit.
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-07-13 05:03:02 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (711f05f3d1)
+
+    lib-storage: mail-duplicate - Add debug messages for transaction.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-07-13 05:07:39 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4809e7e1cd)
+
+    lib-storage: mail-duplicate - Add debug messages for database.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-07-13 04:40:24 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (70af5522c6)
+
+    lib-storage: mail-duplicate - Add event to transaction object.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-07-13 04:53:11 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4fe7f887a6)
+
+    lib-storage: mail-duplicate - Add event to database object.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-06-08 03:51:10 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (63553ff976)
+
+    lib-storage: mail-duplicate - Restructure API to make it transaction-based.
+
+
+M	src/lib-storage/mail-duplicate.c
+M	src/lib-storage/mail-duplicate.h
+
+2021-07-13 04:03:42 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f786488b23)
+
+    lib-storage: mail-duplicate - Change mail_duplicate_check() return type from
+    bool to enum.
+
+
+M	src/lib-storage/mail-duplicate.c
+M	src/lib-storage/mail-duplicate.h
+
+2021-09-17 11:04:55 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (90400db2d6)
+
+    lib-storage: Reformat mail-duplicate.c.
+
+
+M	src/lib-storage/mail-duplicate.c
+
+2021-08-07 15:43:35 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (8bfdede141)
+
+    lib-storage: mail-user - Add mail_user_get_volatile_dir().
+
+
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mail-user.h
+
+2021-09-20 02:45:23 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4c1cf82d61)
+
+    lib: file-lock - Adjust API to allow EDEADLK to be used by application.
+
+    It always caused a Dovecot panic before when returned from kernel.
+
+M	src/lib/file-lock.c
+M	src/lib/file-lock.h
+
+2021-09-19 13:09:29 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (083439ed73)
+
+    lib: file-lock - Rework API to make it extensible.
+
+
+M	src/doveadm/dsync/dsync-brain.c
+M	src/lib-dict/dict-file.c
+M	src/lib-fs/fs-posix.c
+M	src/lib-index/mail-index-lock.c
+M	src/lib-index/mail-index-strmap.c
+M	src/lib-storage/index/dbox-common/dbox-file.c
+M	src/lib-storage/mail-storage.c
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mailbox-list.c
+M	src/lib/file-create-locked.c
+M	src/lib/file-create-locked.h
+M	src/lib/file-lock.c
+M	src/lib/file-lock.h
+M	src/lib/test-file-create-locked.c
+M	src/plugins/fts-squat/squat-trie.c
+M	src/plugins/fts-squat/squat-uidlist.c
+
+2021-09-20 02:39:08 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (de5e12a160)
+
+    lib: file-lock - Rename file_{wait,try}_lock_error() to
+    file_{wait,try}_lock().
+
+
+M	src/lib-dict/dict-file.c
+M	src/lib-fs/fs-posix.c
+M	src/lib-index/mail-index-lock.c
+M	src/lib-index/mail-index-strmap.c
+M	src/lib-storage/index/dbox-common/dbox-file.c
+M	src/lib/file-create-locked.c
+M	src/lib/file-lock.c
+M	src/lib/file-lock.h
+M	src/plugins/fts-squat/squat-trie.c
+M	src/plugins/fts-squat/squat-uidlist.c
+
+2021-09-20 02:29:55 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (13b9181e32)
+
+    lib: file-lock - Remove file_{wait,try}_lock().
+
+
+M	src/lib/file-lock.c
+M	src/lib/file-lock.h
+
+2021-09-20 02:26:46 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4dbae0e6c1)
+
+    global: Avoid use of file_{wait,try}_lock().
+
+    Use the file_{wait,try}_lock_error() variants instead.
+
+M	src/lib-dict/dict-file.c
+M	src/lib-fs/fs-posix.c
+M	src/lib-index/mail-index-lock.c
+M	src/lib-index/mail-index-strmap.c
+M	src/lib-storage/index/dbox-common/dbox-file.c
+M	src/plugins/fts-squat/squat-trie.c
+M	src/plugins/fts-squat/squat-uidlist.c
+
+2021-09-22 17:36:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ed1264368a)
+
+    lib-http: test-http-client-errors - Allow more relaxed timeouts for connect
+    retry tests
+
+    With max_connect_attempts=3 the connects come at (0ms, 100ms, 300ms). Before
+    the 3rd attempt a timeout at 250ms must have triggered, so there was only
+    50ms time for it to trigger. This wasn't always enough when running with
+    valgrind on an overloaded system. Solve this by increasing 
+    max_connect_attempts=4 so the 4th attempt comes at 700ms, giving the timeout
+    450ms to trigger.
+
+M	src/lib-http/test-http-client-errors.c
+
+2021-09-22 12:14:08 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4f605823a4)
+
+    lib: Add most data_stack_grow event fields before checking if event is
+    wanted
+
+    This allows using e.g. "event=data_stack_grow and alloc_size > 32768" as an
+    event filter.
+
+M	src/lib/data-stack.c
+
+2021-09-21 18:27:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (18e1eaf61e)
+
+    config: Add data stack frame
+
+
+M	src/config/config-request.c
+
+2021-09-21 18:26:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9055758361)
+
+    doveconf: Avoid unnecessary data stack use when writing output
+
+
+M	src/config/doveconf.c
+
+2021-09-21 18:21:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (dab478e5f4)
+
+    master: Create each service in its own data stack frame
+
+
+M	src/master/service.c
+
+2021-09-21 17:52:15 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e873c9aeae)
+
+    doveadm: Call each run() in its own data stack frame
+
+
+M	src/doveadm/doveadm-mail.c
+
+2021-09-21 17:14:44 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7ac69350e3)
+
+    dsync: Add data stack frames to mailbox loops
+
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+
+2021-09-21 17:10:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1b4f8cf6a2)
+
+    dsync: Split off dsync_brain_recv_mailbox_tree_add()
+
+
+M	src/doveadm/dsync/dsync-brain-mailbox-tree.c
+
+2021-09-21 17:50:32 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d83e4f5fd2)
+
+    acl: Code cleanup - Remove pointless while-loop
+
+
+M	src/plugins/acl/acl-shared-storage.c
+
+2021-09-21 17:03:45 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f78c00f1f3)
+
+    acl: acllist rebuild - Move data stack frame to caller's loop
+
+
+M	src/plugins/acl/acl-backend-vfile-acllist.c
+
+2021-09-21 16:14:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e05b6df33a)
+
+    acl: Add data stack frame when iterating mailboxes
+
+    Avoids wasting memory when there are a lot of mailboxes.
+
+M	src/plugins/acl/acl-mailbox-list.c
+
+2021-09-21 17:51:15 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5694b04f70)
+
+    lib-storage: mail_user_unref() - Add data stack frame
+
+    The deinit code paths can sometimes eat up quite a lot of data stack.
+
+M	src/lib-storage/mail-user.c
+
+2021-09-22 12:49:46 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ef84b4e2f6)
+
+    lib-storage: mailbox_create() - Add data stack frames
+
+
+M	src/lib-storage/mail-storage.c
+
+2021-09-22 12:48:18 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0b1dff0b1d)
+
+    lib-storage: Add data stack frames to [service] user initialization
+
+    Initialization steps can use a lot of data stack, so try to free it at 
+    several checkpoints.
+
+M	src/lib-storage/mail-storage-service.c
+
+2021-09-21 17:49:34 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1682263f04)
+
+    lib-storage: Add data stack frame when iterating mailboxes to build GUID
+    cache
+
+
+M	src/lib-storage/mailbox-guid-cache.c
+
+2021-09-21 17:48:33 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8c56e2c025)
+
+    lib-storage: mailbox_verify_*name() - Add data stack frames
+
+    This function can eat up quite a lot of data stack.
+
+M	src/lib-storage/mail-storage.c
+
+2021-09-21 17:47:41 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4908b7c139)
+
+    lib-storage: str_contains_special_use() - Add data stack frame
+
+    This function is called in a loop by namespace_find_special_use().
+
+M	src/lib-storage/mail-storage.c
+
+2021-09-21 16:58:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e22519d85d)
+
+    lib-storage: List index rebuild - Add data stack frames
+
+
+M	src/lib-storage/list/mail-storage-list-index-rebuild.c
+
+2021-09-21 16:57:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (99b9890db0)
+
+    lib-storage: List index rebuild - Split off
+    mail_storage_list_index_find_indexed_mailbox()
+
+
+M	src/lib-storage/list/mail-storage-list-index-rebuild.c
+
+2021-09-21 16:37:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8b7438a3f9)
+
+    lib-storage: Don't use data stack for mailbox list index header update
+
+    There can be a lot of mailboxes, causing excessive data stack usage.
+
+M	src/lib-storage/list/mailbox-list-index-sync.c
+
+2021-09-21 16:42:39 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bb75bc110f)
+
+    lib-index: Don't use data stack for building index header update buffer
+
+    The header update can be large (e.g. dovecot.list.index with many mailboxes)
+    and grow data stack unnecessarily.
+
+M	src/lib-index/mail-index-transaction-export.c
+
+2021-09-21 17:51:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (742053d520)
+
+    lib: module_dir_deinit() - Call each deinit() in its own data stack frame
+
+
+M	src/lib/module-dir.c
+
+2021-09-21 16:15:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f13526d432)
+
+    lib: test-data-stack - Make sure data stack memory usage doesn't leak
+
+
+M	src/lib/test-data-stack.c
+
+2021-09-21 13:38:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6d902507c2)
+
+    lib: test-cpu-limit - Remove checking for CPU usage upper limit
+
+    These tests keep randomly failing on loaded systems. It's more important 
+    anyway to check that the minimum CPU usage is high enough than it is to 
+    check that CPU usage isn't too high.
+
+M	src/lib/test-cpu-limit.c
+
+2021-09-17 16:11:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ba465f3a04)
+
+    lib: Optimize str_tabescape()
+
+
+M	src/lib/strescape.c
+
+2021-09-17 16:10:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bcfc14fae3)
+
+    lib: Optimize p_strsplit_tabescaped()
+
+
+M	src/lib/strescape.c
+
+2021-09-17 16:06:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5615fa62db)
+
+    lib: Optimize t_strsplit_tabescaped()
+
+
+M	src/lib/strescape.c
+
+2021-09-17 16:04:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (09b2f400d2)
+
+    lib: Optimize t_strsplit_tabescaped_inplace()
+
+
+M	src/lib/strescape.c
+
+2021-09-17 16:02:10 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a6b1c0114c)
+
+    lib: Optimize t_strdup*()
+
+    Avoid zeroing the allocated data stack memory just before it's going to be 
+    filled with the duplicated string anyway.
+
+M	src/lib/strfuncs.c
+
+2021-03-12 01:41:35 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (6345e8f021)
+
+    lib: Optimize t_str_tabunescape()
+
+
+M	src/lib/strescape.c
+
+2021-03-12 01:38:13 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (cd63b1eeea)
+
+    lib: Optimize str_tabunescape()
+
+
+M	src/lib/strescape.c
+
+2021-03-12 01:27:30 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (ea636c55e4)
+
+    lib: Optimize str_append_tabescaped()
+
+    Avoid calling strlen() and replace for-loop with strcspn().
+
+M	src/lib/strescape.c
+
+2021-03-12 01:23:04 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (1abeccbeca)
+
+    lib: Optimize str_append_tabescaped_n()
+
+
+M	src/lib/strescape.c
+M	src/lib/test-strescape.c
+
+2021-03-12 01:19:38 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (de0871dedb)
+
+    lib: Optimize p_strndup()
+
+
+M	src/lib/strfuncs.c
+M	src/lib/test-lib.inc
+M	src/lib/test-strfuncs.c
+
+2021-02-11 02:56:11 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (7371c1033e)
+
+    lib: Optimize buffer_append() and buffer_append_c()
+
+
+M	src/lib/buffer.c
+
+2021-02-11 02:44:34 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (34d72ca7f2)
+
+    lib: buffer_create_dynamic_max() - Fix max_size handling
+
+    Never allocate buffer larger than its max_size, since it's just wasted 
+    memory. Also clarify that the allocation can actually go up to max_size+1 
+    because of str_c() NUL byte reservation.
+
+M	src/lib/buffer.c
+M	src/lib/buffer.h
+
+2021-02-11 02:30:12 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (53adac82ea)
+
+    lib: buffer_append_zero() - Avoid unnecessary memset()
+
+
+M	src/lib/buffer.c
+
+2021-02-11 02:21:30 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (93499cd0fd)
+
+    lib: buffer - Add writable_size to simplify checking if buffer needs to be
+    grown
+
+
+M	src/lib/buffer.c
+M	src/lib/buffer.h
+
+2021-09-15 14:14:47 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (4a2d3e3b30)
+
+    lib: buffer - Replace casts with container_of()
+
+
+M	src/lib/buffer.c
+
+2021-08-12 10:02:55 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (c513b9a13d)
+
+    lib-storage: Add data stack frame for mailbox_copy and mailbox_move
+
+
+M	src/lib-storage/mail-storage.c
+
+2021-08-12 09:58:57 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (d1ef929697)
+
+    lib-storage: Add data stack frame for mailbox_rename
+
+
+M	src/lib-storage/mail-storage.c
+
+2021-08-12 09:58:41 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (0e6e64c6c0)
+
+    lib-storage: Add data stack frame for mailbox_delete
+
+
+M	src/lib-storage/mail-storage.c
+
+2021-09-16 19:32:02 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (095089cd8f)
+
+    acl: acl_backend_vfile_has_acl() - Simplify code
+
+
+M	src/plugins/acl/acl-backend-vfile.c
+
+2021-09-16 19:13:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4f765652f0)
+
+    acl: acl_backend_vfile_has_acl() - Open mailbox to check if it exists
+
+    This is a bit more expensive than the previous behavior, but it's done only 
+    when creating or renaming mailboxes which are pretty rare operations.
+
+    This fixes copying parent ACLs with obox when the parent mailbox doesn't 
+    exist in local metacache.
+
+M	src/plugins/acl/acl-backend-vfile.c
+M	src/plugins/acl/acl-backend-vfile.h
+
+2021-09-16 19:26:07 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (3533994fd9)
+
+    acl: acl_backend_vfile_has_acl() - Avoid unnecessary local ACL file check
+
+    The local ACL file can't exist if its mailbox doesn't exist.
+
+M	src/plugins/acl/acl-backend-vfile.c
+
+2021-09-16 19:22:41 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0f8fb686c0)
+
+    acl: acl_backend_vfile_has_acl() - Check first if global ACL exists
+
+    Global ACL can be checked more efficiently. If it exists, there's no need 
+    anymore to access the local mailbox ro ACL files.
+
+M	src/plugins/acl/acl-backend-vfile.c
+
+2021-09-16 18:05:04 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f63bda4db5)
+
+    acl: Consistently determine whether ACL files are in control or mailbox
+    directory
+
+    Add mail_storage_get_acl_list_path_type() that is used for it. This fixes 
+    ACL inheritance with obox.
+
+M	src/plugins/acl/acl-backend-vfile-acllist.c
+M	src/plugins/acl/acl-backend-vfile.c
+M	src/plugins/acl/acl-backend-vfile.h
+
+2021-09-06 00:49:06 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (e1cbc8f64d)
+
+    lib-lua: Add function for restricting global variable definition
+
+    Add "dovecot.restrict_global_variables()" with a boolean argument to
+    mitigate unintended variable assignments and to prevent unnecessarily
+    populating global namespace.
+
+    If enabled, variables can only be defined local to the script but global 
+    functions are still allowed.
+
+M	src/lib-lua/dlua-dovecot.c
+
+2021-09-07 15:03:37 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (460edc6b64)
+
+    lib-lua: Use rawget to check presence of functions in scripts
+
+    With the restricted global variables in next commit, checking for undeclared
+    functions results in error. Use rawget to avoid metamethods.
+
+M	src/lib-lua/dlua-script.c
+
+2021-09-17 12:26:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (dc21a1d7e8)
+
+    lib-lua: Build test-dict-lua only with Lua 5.3+
+
+    Fixes building with Lua 5.1
+
+M	src/lib-lua/Makefile.am
+
+2021-09-14 10:33:50 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (85e65dd58a)
+
+    lib-compression: bench-compress - Ensure we have both istream and ostream
+    constructor
+
+    After 6e5ae5ef0f0f31acd7bde0db53980a708c81eced we have read-only compression 
+    mechanism, that leads to null pointer crash in bench-compress.
+
+M	src/lib-compression/bench-compression.c
+
+2021-09-16 20:12:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4ee8601abb)
+
+    NEWS: Add news for 2.3.16
+
+
+M	NEWS
+
+2021-09-10 15:52:43 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9e3df2cddd)
+
+    lib-lua: Add test-dict-lua
+
+
+M	src/lib-lua/Makefile.am
+A	src/lib-lua/test-dict-lua.c
+
+2021-09-10 15:47:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (39f2166fac)
+
+    dict-lua: Change dict:lookup() to actually return nil if key isn't found
+
+    It was previously returning an empty table, although the comment said it 
+    should have returned nil.
+
+M	src/lib-dict/dict-lua.c
+
+2021-09-10 12:49:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a564b607de)
+
+    lib-dict: dict-lua - Add set_timestamp()
+
+
+M	src/lib-dict/dict-txn-lua.c
+
+2021-09-01 19:33:49 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (53eeb81a6d)
+
+    lib-dict: dict-lua - Add unset()
+
+
+M	src/lib-dict/dict-txn-lua.c
+
+2021-09-07 17:00:07 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (3740bc1679)
+
+    indexer: Remove the concept of a "busy" connection
+
+    All the connections are busy, since they are created for a new request and 
+    they are disconnected when the request is done.
+
+M	src/indexer/indexer.c
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+M	src/indexer/worker-pool.c
+M	src/indexer/worker-pool.h
+
+2021-09-07 16:56:16 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (2f8bdc9372)
+
+    indexer: Handle more requests whenever indexer-worker connection closes
+
+    Previously this was done only when worker process sent a "request finished" 
+    notification. Crashing worker processes could have caused the queue to get 
+    stuck until more requests were added to the queue.
+
+M	src/indexer/indexer.c
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+M	src/indexer/worker-pool.c
+M	src/indexer/worker-pool.h
+
+2021-09-07 16:52:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (61ca1eb1b8)
+
+    indexer: Disconnect from worker after each request
+
+    service_count won't be tracked correctly otherwise.
+
+M	src/indexer/worker-connection.c
+
+2021-09-07 15:47:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9718957b84)
+
+    indexer: Fix tracking indexer-worker's process_limit
+
+    After recent changes, the process_limit was too often thought to be 1.
+
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+M	src/indexer/worker-pool.c
+
+2021-08-31 12:14:15 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4018f44337)
+
+    indexer: Change status callback to take struct indexer_request parameter
+
+
+M	src/indexer/indexer.c
+M	src/indexer/indexer.h
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+
+2021-08-31 12:12:49 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b1c4f2b882)
+
+    indexer: Don't free worker_connection too early
+
+    There's no need for the status callback anymore to free the connection. It
+    will be tracked automatically.
+
+M	src/indexer/indexer.c
+M	src/indexer/worker-connection.c
+M	src/indexer/worker-connection.h
+M	src/indexer/worker-pool.c
+M	src/indexer/worker-pool.h
+
+2021-08-31 12:08:32 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a15eb469aa)
+
+    indexer: Use a separate indexer_queue_callback_t type for indexer-queue
+    callback
+
+
+M	src/indexer/indexer-queue.c
+M	src/indexer/indexer-queue.h
+
+2021-09-14 15:33:50 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (e7a3c48390)
+
+    lib: Remove connect_finished_time field from connection event.
+
+
+M	src/lib/connection.c
+
+2021-09-10 19:00:09 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (da3bc6ea23)
+
+    lib: ENUM_NEGATE() - Disable runtime sizeof() check with STATIC_CHECKER
+
+    This is to avoid "Dangerous variable-length array (VLA) declaration" errors
+    with clang 12 scan-build, which happen because scan-build keeps thinking
+    that the enums can become larger than 2147483647.
+
+M	src/lib/macros.h
+
+2021-08-31 15:44:20 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (4826b08c47)
+
+    doveadm: Flush data to server client asynchronously
+
+
+M	src/doveadm/doveadm-print-server.c
+
+2021-07-13 13:32:09 +0200 Markus Valentin <markus.valentin@open-xchange.com> (d89e8a5c3c)
+
+    virtual: Add storage flag for secondary index
+
+
+M	src/plugins/virtual/virtual-storage.c
+
+2021-07-13 13:28:43 +0200 Markus Valentin <markus.valentin@open-xchange.com> (69ddbe6dd0)
+
+    lib-storage: Add storage_class_flag for secondary index
+
+
+M	src/lib-storage/mail-storage-private.h
+
+2021-09-10 06:47:21 +0200 Bernhard M. Wiedemann <bwiedemann@suse.de> (00eba31f19)
+
+    man: doveadm director flush - The parameter is -F, not -f
+
+
+M	doc/man/doveadm-director.1.in
+
+2021-09-08 09:53:12 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (30eecd1f82)
+
+    doveadm: doveadm-stats - Allocate field_types
+
+    field_types was function local variable that got used outside of function.
+    Change to use datastack allocation instead to keep it valid after leaving
+    function.
+
+    Broken in e9a46e3a6df2edd6cb68a8fc04a5e8e4564a9d5e
+
+M	src/doveadm/doveadm-stats.c
+
+2021-08-31 12:48:38 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (601b5465dc)
+
+    lib-storage: Move (un)deleting debug message to mailbox_mark_index_deleted
+
+
+M	src/lib-storage/list/mail-storage-list-index-rebuild.c
+M	src/lib-storage/mail-storage.c
+
+2021-08-09 11:56:17 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (99efbc7f81)
+
+    lib-storage: mailbox-list - Detect duplicate GUIDs
+
+    This is only done for LAYOUT=INDEX.
+
+M	src/lib-storage/list/mailbox-list-index.c
+
+2021-08-19 17:10:31 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (f30403df4b)
+
+    lib-storage: mailbox-list - Add duplicate pool for duplicate detection
+
+
+M	src/lib-storage/list/mailbox-list-index.c
+
+2021-08-16 14:38:47 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (196a5ed776)
+
+    lib-storage: maildir - Use mail_storage_index_list_rebuild
+
+
+M	src/lib-storage/index/maildir/maildir-storage.c
+M	src/lib-storage/index/maildir/maildir-sync.c
+
+2021-08-20 11:43:27 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (18afa46644)
+
+    lib-storage: maildir - Allow creating existing folder when rebuilding list
+    index
+
+
+M	src/lib-storage/index/maildir/maildir-storage.c
+
+2021-08-09 14:23:13 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (a92541c5b9)
+
+    lib-storage: dbox - Use mailbox_list_index_rebuild
+
+
+M	src/lib-storage/index/dbox-multi/mdbox-storage.c
+M	src/lib-storage/index/dbox-multi/mdbox-sync.c
+M	src/lib-storage/index/dbox-single/sdbox-storage.c
+M	src/lib-storage/index/dbox-single/sdbox-sync.c
+M	src/lib-storage/index/index-storage.c
+
+2021-08-09 15:47:28 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (451883dbb8)
+
+    lib-storage: dbox - Allow creating existing folder when rebuilding list
+    index
+
+
+M	src/lib-storage/index/dbox-common/dbox-storage.c
+M	src/lib-storage/index/index-storage.c
+
+2021-08-13 14:18:21 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (2fc8d7024b)
+
+    lib-storage: Add list index rebuild code
+
+
+M	src/lib-storage/list/Makefile.am
+A	src/lib-storage/list/mail-storage-list-index-rebuild.c
+M	src/lib-storage/mail-storage-private.h
+
+2021-08-27 12:59:59 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (5a75724a2e)
+
+    lib-storage: Populate mailboxes_fs if needed
+
+
+M	src/lib-storage/mail-storage.c
+
+2021-08-20 14:30:46 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (3855ccbf9a)
+
+    lib-storage: mailbox-list - Try to get GUID from mailbox path
+
+
+M	src/lib-storage/list/mailbox-list-index-status.c
+
+2021-08-13 10:29:59 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (a92b1f9316)
+
+    lib-storage: Add fields to mail_storage needed for list rebuild
+
+
+M	src/lib-storage/mail-storage-private.h
+M	src/lib-storage/mail-storage.c
+
+2021-06-30 08:44:33 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (36f96c49ca)
+
+    lib-storage: Fix changing mailbox GUID with LAYOUT=index
+
+    The old path would generate Mail/mailboxes/mailboxes/<guid>
+
+M	src/lib-storage/list/mailbox-list-index-backend.c
+
+2021-06-22 15:59:25 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (c1a57f47a7)
+
+    doveadm-stats: Add doveadm stats add/remove documentation
+
+
+M	doc/man/doveadm-stats.1.in
+
+2021-05-27 11:46:02 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (bc31da4d84)
+
+    doveadm: Add 'stats add' and 'stats remove' commands
+
+
+M	src/doveadm/Makefile.am
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-stats.c
+
+2021-06-09 17:23:28 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (eef75dd719)
+
+    stats: replace hardcoded value of default exporter_include with macro
+
+
+M	src/stats/stats-settings.c
+M	src/stats/stats-settings.h
+
+2021-06-09 17:22:39 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (e9a46e3a6d)
+
+    doveadm: Rewrite doveadm stats dump command to allow reuse code in other
+    commands.
+
+
+M	src/doveadm/doveadm-stats.c
+
+2021-05-10 16:39:21 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (57bb3b90cb)
+
+    lib-master: Accept filter updates from stats by stats-client.
+
+
+M	src/lib-master/stats-client.c
+
+2021-04-20 17:07:36 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (0444b730c2)
+
+    stats: Implement ipc interface for adding and removing metrics
+
+
+M	src/stats/client-reader.c
+
+2021-05-10 16:13:12 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (adbbd40458)
+
+    stats: Implement sending filter updates to connected processes.
+
+
+M	src/stats/client-writer.c
+M	src/stats/client-writer.h
+
+2021-04-20 17:06:58 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (a2a79fb376)
+
+    stats: Add functions for adding or removing metrics
+
+
+M	src/stats/stats-metrics.c
+M	src/stats/stats-metrics.h
+
+2021-04-13 11:49:43 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (50f36bb1d2)
+
+    stats: Expose stats_metric_setting_parser_info
+
+
+M	src/stats/stats-settings.h
+
+2021-04-20 17:06:26 +0300 sergey.kitov <sergey.kitov@open-xchange.com> (fcacfde02a)
+
+    lib: Add function removing queries from event_filter
+
+
+M	src/lib/event-filter.c
+M	src/lib/event-filter.h
+
+2021-09-02 14:31:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a476e16ae3)
+
+    lib-http: test-http-client-errors - Fix random hangs
+
+    Wait for subprocesses to be initialized before starting each test. This
+    should fix random hangs with the test.
+
+M	src/lib-http/test-http-client-errors.c
+
+2021-09-02 15:39:33 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8b883745d6)
+
+    lib-test: test_subprocess_notify_signal_*() - Add signo parameter
+
+    This allows using multiple notification signals.
+
+M	src/lib-http/test-http-client-errors.c
+M	src/lib-test/test-subprocess.c
+M	src/lib-test/test-subprocess.h
+
+2021-08-19 12:50:04 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (5280904733)
+
+    lib-storage: Make sure header parsing is deinitialized after failures
+
+    This should fix all the possible reasons for: Panic: file
+    index-mail-headers.c: line 198 (index_mail_parse_header_init): assertion
+    failed: (!mail->data.header_parser_initialized)
+
+M	src/lib-storage/index/index-mail-headers.c
+M	src/lib-storage/index/index-mail.c
+M	src/lib-storage/index/index-mail.h
+
+2021-08-19 12:32:45 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (0b6a2fb6c1)
+
+    lib-storage: Fix potential assert-crash if filter-stream fails
+
+    If filter-stream isn't read until header (because the parent istream fails), 
+    the header parsing isn't deinitialized. If after the failure the headers are 
+    attempted to be parsed again, there's an assert-crash. Make sure this won't 
+    happen by finishing the filter-istream read, and if that fails then reset
+    the header parsing anyway.
+
+    Destroying the filter_stream may also change the parent istream offset to 
+    change, so this commit adds an extra seek to beginning of the istream when 
+    beginning to parse the mail headers.
+
+    Fixes: Panic: file index-mail-headers.c: line 198
+    (index_mail_parse_header_init): assertion failed:
+    (!mail->data.header_parser_initialized)
+
+M	src/lib-storage/index/index-mail-headers.c
+
+2021-08-19 12:29:36 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (45df8159fe)
+
+    lib-storage: Split off index_mail_filter_stream_destroy()
+
+
+M	src/lib-storage/index/index-mail-headers.c
+
+2021-08-17 14:33:56 +0200 Markus Valentin <markus.valentin@open-xchange.com> (6b7e4d1527)
+
+    imapc: Fix crashing when copying nonexistent mails
+
+    Check the msgmap before attempting to copy an mail which may has been 
+    expunged already. Fixes:
+
+    Panic: file mail-storage.c: line 2385
+    (mailbox_transaction_commit_get_changes): assertion failed: (ret < 0 ||
+    seq_range_count(&changes_r->saved_uids) == save_count ||
+    array_count(&changes_r->saved_uids) == 0)
+
+M	src/lib-storage/index/imapc/imapc-save.c
+
+2021-08-18 14:04:53 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (8e592661d5)
+
+    lib-storage: Fix assert-crash in special partial mail parsing failures
+
+    This happened for example if:
+    - mail_precache() started parsing mail
+    - header was parsed, but mail body parsing failed due to mail size mismatch
+    - vsize parsing doesn't restart header parsing, because header size is
+    already known
+    - body parsing assert-crashes because there is no messsage parser
+    initialized
+
+    Fixes: Panic: file index-mail.c: line 1290 (index_mail_parse_body):
+    assertion failed: (data->parser_ctx != NULL)
+
+M	src/lib-storage/index/index-mail.c
+
+2021-08-12 12:42:40 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (35f7ff1c41)
+
+    lib-oauth2: Add unit test for missing exp field
+
+
+M	src/lib-oauth2/test-oauth2-jwt.c
+
+2021-08-12 12:40:39 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (5bc6fa7571)
+
+    lib-oauth2: Add unit tests for valid tokens
+
+
+M	src/lib-oauth2/test-oauth2-jwt.c
+
+2021-06-08 00:35:13 +0200 s3lph <5564491+s3lph@users.noreply.github.com> (f3bef96857)
+
+    lib-oauth2-jwt: Remove 'nbf < iat' check, as it's not mandated by RFC7519,
+    and not uncommon to predate the nbf field
+
+
+M	src/lib-oauth2/oauth2-jwt.c
+
+2021-08-24 22:58:59 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (8a7fa002d2)
+
+    lib-test: Fix race when subprocess immediately receives signal
+
+    Signal could be received before test_subprocess_is_child=1 is set, causing 
+    the subprocess's signal handler to also attempt to cleanup other 
+    subprocesses.
+
+    This was causing http-test-client-errors unit tests to fail somewhat 
+    randomly, especially when running them only with 1 CPU.
+
+M	src/lib-test/test-subprocess.c
+
+2021-08-24 15:02:44 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (07d66366ab)
+
+    lib: Add test-macros
+
+
+M	src/lib/Makefile.am
+M	src/lib/test-lib.inc
+A	src/lib/test-macros.c
+
+2021-08-23 14:21:02 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a4f9219709)
+
+    lib: Rewrite POINTER_CAST_TO() to avoid new clang warning
+
+    Fixes: warning: performing pointer subtraction with a null pointer has
+    undefined behavior [-Wnull-pointer-subtraction]
+
+M	src/lib/macros.h
+
+2021-08-23 14:20:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9c4077a843)
+
+    dbox: Remove unnecessary variable
+
+    The variable was set and updated, but never read.
+
+M	src/lib-storage/index/dbox-common/dbox-file.c
+
+2021-08-23 14:20:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a11c739b86)
+
+    lib-dcrypt: Remove unnecessary variable
+
+    The variable was set and updated, but never read.
+
+M	src/lib-dcrypt/istream-decrypt.c
+
+2021-08-23 11:46:46 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (bc679231f1)
+
+    doveadm-dsync: Free ssl_iostream_context on connection failure
+
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-04-13 18:25:06 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (415c305d6c)
+
+    lib-smtp: smtp-server-cmd-rcpt - Fix assert crash occurring for pipelined
+    MAIL RCPT MAIL sequence.
+
+    The assertion is wrong in that it assumes that no MAIL commands can be
+    pending once RCPT command is next to reply. The RCPT command does not block
+    the pipeline, so that a subsequent MAIL command can also be pending (but
+    will almost never succeed).
+
+M	src/lib-smtp/smtp-server-cmd-rcpt.c
+M	src/lib-smtp/test-smtp-server-errors.c
+
+2021-04-13 18:25:31 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (23246612f2)
+
+    lib-smtp: smtp-server-cmd-data - Add comment to pipeline state assertion.
+
+
+M	src/lib-smtp/smtp-server-cmd-data.c
+
+2021-08-20 01:19:28 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (741a963c19)
+
+    lib-smtp: smtp-server-cmd-data - Remove useless trans != NULL checks.
+
+
+M	src/lib-smtp/smtp-server-cmd-data.c
+
+2021-08-19 17:06:42 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (285cf9c849)
+
+    dict-sql: Fail early if there's unexpected number of bind arguments for iter
+
+    Otherwise, lib-sql raises a panic.
+
+M	src/lib-dict-backend/dict-sql.c
+
+2021-08-17 15:23:31 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (e34bffaab5)
+
+    lib: istreams - Make sure freeing snapshots can't access freed parent
+    istream memory
+
+    This happened after the recent istream-header-filter snapshot changes.
+
+M	src/lib/istream.c
+
+2021-08-18 19:49:43 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (b401e4136f)
+
+    imap: Fix mailbox leak if MOVE can't open the source mailbox
+
+    Broken by 143b7c2b412ed8f155e812603fda81886bec466e
+
+    Fixes: Panic: file mail-user.c: line 229 (mail_user_deinit): assertion
+    failed: ((*user)->refcount == 1)
+
+M	src/imap/cmd-copy.c
+
+2021-08-18 13:54:39 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (16deb5b8bc)
+
+    lib-lua: Don't include lua_resume_compat() for 5.1
+
+    lua_resume() is not supported in 5.1, so don't try to add a compatibility
+    function that fails compilation.
+
+M	src/lib-lua/dlua-compat.c
+M	src/lib-lua/dlua-compat.h
+
+2021-05-25 19:26:14 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bb1d67a870)
+
+    lib: Remove unused event_filter_add()
+
+
+M	src/lib/event-filter.c
+M	src/lib/event-filter.h
+
+2021-08-16 17:13:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f1bbfcb712)
+
+    lib: test-event-filter - Replace event_filter_add() with
+    event_filter_parse()
+
+
+M	src/lib/test-event-filter.c
+
+2021-05-24 23:57:13 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c4369cbf6a)
+
+    lib: Add comments to event-related code
+
+
+M	src/lib/event-log.c
+M	src/lib/event-log.h
+M	src/lib/lib-event-private.h
+M	src/lib/lib-event.h
+
+2021-05-24 22:26:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d2c0f1374c)
+
+    lib: event_want_level() - Minor code cleanup
+
+    event_want_log_level() internally does both of these checks, so it's enough 
+    to just check its return value.
+
+M	src/lib/event-log.c
+
+2021-08-17 12:12:18 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (730b2a449f)
+
+    lib-fs: Fix fs_stats.copy_count tracking with fs_default_copy()
+
+    The copy_count could have been decreased too many times with async 
+    operations.
+
+M	src/lib-fs/fs-api-private.h
+M	src/lib-fs/fs-api.c
+
+2021-08-16 15:36:37 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b82faee3cb)
+
+    global: Add ATTR_UNSIGNED_WRAPS to fix various ubsan issues
+
+
+M	src/auth/crypt-blowfish.c
+M	src/lib/numpack.c
+M	src/lib/sha3.c
+
+2021-08-05 18:53:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9e80b588f4)
+
+    global: Fix various ubsan issues
+
+
+M	src/lib-mail/istream-header-filter.c
+M	src/lib-mail/test-qp-decoder.c
+M	src/lib-test/test-istream.c
+M	src/lib/test-base64.c
+M	src/lib/test-data-stack.c
+
+2021-08-17 10:59:49 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (05189ecdc7)
+
+    doveadm: Free memory for all loaded mail_plugins at deinit
+
+
+M	src/doveadm/doveadm-mail.c
+
+2021-08-17 10:50:34 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (654d7b2d11)
+
+    doveadm pw -l: Free all memory to avoid memory leak complaints
+
+
+M	src/doveadm/doveadm-pw.c
+
+2021-08-17 09:30:25 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (b997fc957e)
+
+    doveadm batch: Fix memory leak
+
+
+M	src/doveadm/doveadm-mail-batch.c
+
+2021-08-17 11:06:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (46a511412a)
+
+    doveadm: Split off doveadm_mail_cmd_deinit()
+
+
+M	src/doveadm/doveadm-mail.c
+M	src/doveadm/doveadm-mail.h
+
+2021-05-07 21:15:04 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f708052c5d)
+
+    doveadm: Make doveadm_mail_cmd_free() public
+
+
+M	src/doveadm/doveadm-mail.c
+M	src/doveadm/doveadm-mail.h
+
+2021-08-13 11:08:30 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f28482afb8)
+
+    global: Use consistent lua function names
+
+    Change lua-style function names to be consistent with dovecot's style.
+
+M	src/auth/db-lua.c
+M	src/lib-lua/dlua-dovecot.c
+M	src/lib-lua/dlua-error.c
+M	src/lib-lua/dlua-pushstring.c
+M	src/lib-lua/dlua-script-private.h
+M	src/lib-lua/dlua-script.c
+M	src/lib-storage/mail-lua.c
+M	src/lib-storage/mail-storage-lua.c
+M	src/lib-storage/mail-user-lua.c
+M	src/lib-storage/mailbox-lua.c
+M	src/plugins/push-notification/push-notification-driver-lua.c
+
+2021-08-12 19:43:25 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (e14026e708)
+
+    lib-lua: test-lua: Fix the test for lua versions later than 5.3
+
+
+M	src/lib-lua/test-lua.c
+
+2021-08-12 19:40:22 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (72a864b4b0)
+
+    lib-lua: Add lua_resume_compat() and use it in lua versions prior to 5.4
+
+    Starting lua 5.4 "lua_resume()" expects an extra "nresults" argument. Add a 
+    compatibility function to handle this argument in earlier versions.
+
+M	src/lib-lua/dlua-compat.c
+M	src/lib-lua/dlua-compat.h
+M	src/lib-lua/dlua-resume.c
+
+2020-05-24 14:47:18 -0400 Felipe Gasper <felipe@felipegasper.com> (2595113387)
+
+    man: Document command/args destination format to sync/backup.
+
+
+M	doc/man/doveadm-sync.1.in
+
+2021-08-16 14:52:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e14e68cb0b)
+
+    lib-compression: istream-lz4 - Fix handling partial header reads
+
+    Reading assert-crashed if the header was read only partially. Either because
+    the file really was truncated or because parent stream already had fewer
+    bytes buffered.
+
+M	src/lib-compression/istream-lz4.c
+M	src/lib-compression/test-compression.c
+
+2021-08-16 14:51:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d8995452c8)
+
+    lib-compression: istream-lz4 - Remove redundant check
+
+    The loop is reached only if ret is 0, so there's no need to check it again.
+
+M	src/lib-compression/istream-lz4.c
+
+2021-08-16 14:50:10 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (eea3b751de)
+
+    lib-compression: istream-lz4 - Add asserts to make sure parent buffer isn't
+    full
+
+    The parent buffer's max size would have to be tiny for these to happen.
+
+M	src/lib-compression/istream-lz4.c
+
+2021-07-01 13:52:29 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f38100a5fe)
+
+    dict: Use dict-init-cache
+
+    Make use of dict-init-cache for initialization and deinitialization of
+    dicts.
+
+M	src/dict/dict-connection.c
+M	src/dict/main.c
+
+2021-06-25 14:11:52 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (222e8d5b55)
+
+    dict: Add caching mechanism for initializing dicts
+
+    Add a pool for dict instances. Each dict is refcounted and given a grace
+    period of 30 seconds for deletion. If refcount drops to 0 and no new dict 
+    operation uses the instance in that period, it will be freed. A maximum of
+    10 dicts are kept in the cache.
+
+M	src/dict/Makefile.am
+A	src/dict/dict-init-cache.c
+A	src/dict/dict-init-cache.h
+
+2021-08-09 00:35:09 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f5a2a9a6d8)
+
+    dict: Add dict_created and dict_destroyed events
+
+    Inherit from dict.event and emitted at dict initialization/deinit.
+
+M	src/lib-dict/dict.c
+
+2021-08-15 12:45:12 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (fcf23d9919)
+
+    lib-sasl: test-sasl-client.c - Initialize authid of sasl_empty_set
+
+    This fixes the compiler warning:
+
+      test-sasl-client.c:8:1: error: missing initializer for field 'authid'
+     of 'const struct dsasl_client_settings'
+
+M	src/lib-sasl/test-sasl-client.c
+
+2021-08-15 07:12:42 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (0434bd96f7)
+
+    lib-sasl: oauthbearer - Fix memory leak on auth failure
+
+
+M	src/lib-sasl/mech-oauthbearer.c
+
+2021-08-11 09:48:06 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (64ab5d866c)
+
+    lib-sasl: Add unit tests
+
+
+M	src/lib-sasl/Makefile.am
+A	src/lib-sasl/test-sasl-client.c
+
+2021-08-11 09:59:05 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (5071cfe16c)
+
+    lib-sasl: Do not crash if password is NULL
+
+
+M	src/lib-sasl/dsasl-client.c
+
+2021-08-11 09:47:17 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (feba4f0c88)
+
+    lib-sasl: When setting port, parse value, not key.
+
+    Broken in 228f1e8d583
+
+M	src/lib-sasl/mech-oauthbearer.c
+
+2021-08-11 08:49:44 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (902407f7eb)
+
+    auth: Do not forward empty "master" passdb field
+
+
+M	src/auth/auth-request-handler.c
+
+2021-08-11 08:48:29 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (f4cf9d6974)
+
+    login-common: Ignore empty value for "master" passdb extra field
+
+
+M	src/login-common/client-common-auth.c
+
+2021-08-10 12:26:14 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (a0c1fe343e)
+
+    mail-crypt: Add password confirmation for doveadm cryptokey password command
+
+    To prevent setting wrong passwords by accident.
+
+M	src/plugins/mail-crypt/doveadm-mail-crypt.c
+
+2021-08-10 12:23:55 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (13830767a0)
+
+    mail-crypt: Fix -O argument type for doveadm cryptokey password command
+
+    Should be boolean instead of string.
+
+M	src/plugins/mail-crypt/doveadm-mail-crypt.c
+
+2021-04-28 02:01:14 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ddf224808e)
+
+    lib-index: Fix "Extension introduction for unknown id" errors after map is
+    generated
+
+    This happens when:
+    * View is opened
+    * Messages are expunged
+    * View is synced with NOEXPUNGES flag
+    * A new extension is introduced
+    * Index is rotated at least twice
+    * View is again synced with NOEXPUNGES flag
+    * More changes are done to index with the new extension
+    * Once more view is synced with NOEXPUNGES flag
+
+    The last sync will see changes with the new extension ID, but the view's map 
+    doesn't know its ID.
+
+M	src/lib-index/mail-index-sync-ext.c
+M	src/lib-index/test-mail-index.c
+
+2021-06-24 12:58:58 -0400 Josef 'Jeff' Sipek <jeff.sipek@open-xchange.com> (710346bcb8)
+
+    virtual: Expunge old emails if backend box guid changed
+
+    This introduces a new extensible "ext2" header to make it easier to add new 
+    fields in the future. It also allows keeping backwards/forwards 
+    compatibility with the old code, so the virtual index isn't rebuilt on 
+    upgrades or downgrades.
+
+M	src/plugins/virtual/virtual-storage.c
+M	src/plugins/virtual/virtual-storage.h
+M	src/plugins/virtual/virtual-sync.c
+
+2021-07-29 12:38:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5310858bd0)
+
+    virtual: Don't use data stack when building extension header
+
+    Some users may have thousands of mailboxes, which grows the data stack 
+    unnecessarily large.
+
+M	src/plugins/virtual/virtual-sync.c
+
+2021-08-09 13:01:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (97367ef617)
+
+    indexer: Fix crash if client disconnects while it's waiting for command
+    reply
+
+    This happened for example if IMAP SEARCH triggered long fts indexing and the 
+    IMAP client disconnected while waiting for the reply.
+
+    Broken by f62a25849358e40a08a2c47f5bcaa1613a31d076
+
+M	src/indexer/indexer-client.c
+
+2021-05-06 11:58:21 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9f5e723974)
+
+    lib-smtp: smtp-server-cmd-data - Fix global state cleanup upon DATA command
+    destroy.
+
+    Should cleanup global state only when it belongs to the DATA/BDAT command 
+    currently being destroyed.
+
+    Fixes NULL-dereference in i_stream_read() found by OSS-Fuzz.
+
+M	src/lib-smtp/smtp-server-cmd-data.c
+M	src/lib-smtp/test-smtp-server-errors.c
+
+2021-08-05 11:38:26 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (da1d2332bd)
+
+    lib-smtp: test-smtp-server-errors - Perform "Bad pipelined DATA" test with
+    actual pipelining.
+
+
+M	src/lib-smtp/test-smtp-server-errors.c
+
+2021-08-03 19:47:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7f9cfccb54)
+
+    imap, pop3: Prevent reading ssl_ca setting into memory
+
+    Especially with imap there can be a lot of processes and a large ssl_ca 
+    could be wasting a lot of memory. This was already the old behavior before 
+    removing ssl_* settings from lib-storage.
+
+M	src/imap/main.c
+M	src/lib-master/master-service-settings.c
+M	src/lib-master/master-service-settings.h
+M	src/lib-storage/mail-storage-service.c
+M	src/lib-storage/mail-storage-service.h
+M	src/pop3/main.c
+
+2021-08-03 19:46:59 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4ea4a85607)
+
+    config: Add exclude=<name> settings to drop specific settings
+
+
+M	src/config/config-connection.c
+M	src/config/config-request.c
+M	src/config/config-request.h
+M	src/config/doveconf.c
+
+2021-07-29 18:04:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (327bd99f84)
+
+    lib-storage: Remove SSL settings from mail_storage_settings
+
+    They can be accessed via master_service_ssl_settings instead.
+
+M	src/lib-master/master-service-ssl-settings.c
+M	src/lib-storage/mail-storage-settings.c
+M	src/lib-storage/mail-storage-settings.h
+
+2021-07-29 18:02:57 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1e5324b580)
+
+    lib-storage: mail_user_init_ssl_client_settings() - Use
+    master_service_ssl_settings
+
+    This will allow dropping the duplicate SSL settings handling.
+
+M	src/lib-storage/mail-storage-service.c
+M	src/lib-storage/mail-user.c
+
+2021-07-29 17:45:16 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4930b1b883)
+
+    dsync: Get SSL settings via master_service_ssl_settings
+
+
+M	src/doveadm/doveadm-dsync.c
+
+2021-07-29 17:44:43 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6fcaffd1e5)
+
+    lib-storage: Add mail_storage_service_user_get_ssl_settings()
+
+
+M	src/lib-storage/mail-storage-service.c
+M	src/lib-storage/mail-storage-service.h
+
+2021-07-29 17:44:25 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (aa8cb602eb)
+
+    lib-master: Add master_service_ssl_settings_get_from_parser()
+
+
+M	src/lib-master/master-service-ssl-settings.c
+M	src/lib-master/master-service-ssl-settings.h
+
+2021-07-29 17:57:42 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (61a7a9e00e)
+
+    global: Don't zero SSL settings unnecessarily
+
+    mail_user_init_ssl_client_settings() and mail_user_init_fs_settings() will
+    clear them again anyway.
+
+M	src/doveadm/doveadm-settings.c
+M	src/lib-lda/mail-send.c
+M	src/lib-storage/mailbox-list.c
+M	src/lmtp/lmtp-proxy.c
+M	src/plugins/fts-solr/fts-backend-solr-old.c
+M	src/plugins/fts-solr/fts-backend-solr.c
+M	src/plugins/fts/fts-parser-tika.c
+M	src/plugins/push-notification/push-notification-driver-ox.c
+M	src/stats/event-exporter-transport-http-post.c
+M	src/submission/submission-backend-relay.c
+
+2021-07-29 17:58:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c59736a995)
+
+    lib-storage: mail_user_init_fs_settings() - Clarify that ssl settings are
+    fully initialized
+
+
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mail-user.h
+
+2021-07-29 17:57:12 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b8a60edd6a)
+
+    lib-storage: mail_user_init_ssl_client_settings() - Clarify that ssl
+    settings are fully initialized
+
+
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mail-user.h
+
+2021-07-29 15:31:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1b2134e0be)
+
+    doveadm: Free SSL iostream contexts at deinit
+
+    This wasn't really a memory leak, because the contexts are always kept 
+    allocated until deinit anyway.
+
+M	src/doveadm/doveadm-mail-server.c
+
+2021-07-29 16:06:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (48d8cb6b4f)
+
+    lib-ssl-iostream: ssl_iostream_context_unref(NULL) is a no-op
+
+
+M	src/lib-ssl-iostream/iostream-ssl.c
+
+2021-08-07 18:52:09 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c84de300ae)
+
+    man: doveadm-pw - Fix default scheme to be CRYPT / $2y$ bcrypt
+
+
+M	doc/man/doveadm-pw.1.in
+
+2021-07-22 15:31:11 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c5cbbdf177)
+
+    lib-index: Remove mail_index_transaction_get_highest_modseq()
+
+    This isn't actually used anywhere, so there's no need to keep it.
+
+M	src/lib-index/mail-index-transaction-export.c
+M	src/lib-index/mail-index-transaction.c
+M	src/lib-index/mail-index.h
+M	src/lib-index/mail-transaction-log-file.c
+
+2021-07-22 15:28:39 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4ddc389c16)
+
+    lib-index: mail_index_transaction_get_highest_modseq() - Fix handling
+    MAIL_INDEX_MAIL_FLAG_UPDATE_MODSEQ
+
+    MAIL_INDEX_MAIL_FLAG_UPDATE_MODSEQ flag updates didn't calculate the 
+    returned modseq correctly. This function wasn't used outside
+    --with-devel-checks though, but with it this fixes:
+
+    Panic: file mail-index-transaction.c: line 212
+    (mail_index_transaction_commit_real): assertion failed: (t->reset ||
+    expected_highest_modseq == log->head->sync_highest_modseq)
+
+M	src/lib-index/mail-index-transaction-export.c
+
+2021-07-22 14:24:11 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (11c6ffa0f6)
+
+    lib-index: Avoid modseq warnings --with-devel-checks
+
+    Avoids warnings: Requested highest-modseq for transaction, but modseq
+    tracking isn't enabled for the file (this shouldn't happen)
+
+M	src/lib-index/mail-index-transaction.c
+
+2021-07-22 14:29:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a6548a3b73)
+
+    director: Avoid calling timeval_diff_msecs() with too great time difference
+
+    Fixes assert-crash --with-devel-checks: Panic: file time-util.c: line 76
+    (timeval_diff_msecs): assertion failed: (diff <= INT_MAX)
+
+M	src/director/director-connection.c
+
+2021-07-14 10:00:28 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (5245129f06)
+
+    lib: data-stack - Initialize alloc_count / alloc_bytes
+
+    This has only effect with devel checks enabled. Fixes counter values to show
+    sensible data.
+
+M	src/lib/data-stack.c
+
+2021-07-13 13:41:14 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (d74cf4ca2a)
+
+    lib: data-stack - Allow errno changes when sending event
+
+
+M	src/lib/data-stack.c
+
+2021-07-29 22:21:52 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (368a96783e)
+
+    lib-master, global: Remove unnecessary MASTER_SERVICE_FLAG_USE_SSL_SETTINGS
+
+    SSL client settings are now always read.
+
+M	src/auth/main.c
+M	src/doveadm/doveadm.c
+M	src/doveadm/main.c
+M	src/lib-master/master-service.h
+M	src/lmtp/main.c
+M	src/login-common/main.c
+M	src/stats/main.c
+
+2021-07-29 22:20:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (36ff43f1a9)
+
+    lib-master: Use ssl-server settings only when necessary
+
+
+M	src/lib-master/master-service-private.h
+M	src/lib-master/master-service-settings.c
+M	src/lib-master/master-service-ssl-settings.c
+M	src/lib-master/master-service.c
+
+2021-07-29 22:18:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a7fb3cce6a)
+
+    lib-master: Remove unused master_service_is_ssl_module_loaded()
+
+
+M	src/lib-master/master-service-private.h
+M	src/lib-master/master-service.c
+M	src/lib-master/master-service.h
+
+2021-07-29 21:45:18 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c6cea57577)
+
+    lib-master, login-common: Split off master_service_ssl_server_settings
+
+
+M	src/config/settings-get.pl
+M	src/lib-master/master-service-settings.c
+M	src/lib-master/master-service-ssl-settings.c
+M	src/lib-master/master-service-ssl-settings.h
+M	src/lib-master/master-service-ssl.c
+M	src/login-common/client-common.c
+M	src/login-common/client-common.h
+M	src/login-common/login-settings.c
+M	src/login-common/login-settings.h
+M	src/login-common/main.c
+
+2021-07-29 21:43:03 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (79a210c1f7)
+
+    lib-master, global: Split master_service_ssl_settings_to_iostream_set() to
+    client/server functions
+
+
+M	src/auth/auth-policy.c
+M	src/doveadm/doveadm-settings.c
+M	src/lib-master/master-service-ssl-settings.c
+M	src/lib-master/master-service-ssl-settings.h
+M	src/lmtp/lmtp-proxy.c
+M	src/login-common/client-common.c
+M	src/login-common/login-proxy.c
+M	src/login-common/main.c
+M	src/stats/event-exporter-transport-http-post.c
+
+2021-08-05 17:48:42 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (77dd65422c)
 
     master: Avoid creating prefork timeout if process_limit is already reached
 
 
-M	src/master/service-monitor.c
+M	src/master/service-monitor.c
+
+2021-08-05 17:53:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0610336a86)
+
+    master: Avoid high CPU usage when process_min_avail reaches process_limit
+
+    process_min_avail handling always created a 0ms timeout to try to create the 
+    missing processes. This timeout was supposed to stop when it couldn't launch 
+    all the wanted processes, but the check wasn't done right. This ended up 
+    causing the timeout to be called rapidly over and over again.
+
+M	src/master/service-monitor.c
+
+2021-07-22 14:51:05 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (904ee71927)
+
+    lazy_expunge: Add lazy_expunge_exclude setting
+
+    This allows mailboxes to be excluded via configuration.
+
+M	src/plugins/lazy-expunge/lazy-expunge-plugin.c
+
+2021-08-03 17:38:33 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9d02ac2e42)
+
+    fts: Use mailbox-match-plugin API for fts_autoindex_exclude
+
+    This doesn't change the functionality, just deduplicates the code.
+
+M	src/plugins/fts/fts-storage.c
+M	src/plugins/fts/fts-user.c
+M	src/plugins/fts/fts-user.h
+
+2021-08-04 15:50:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (3eb404aa8c)
+
+    fts: Always initialize struct fts_user
+
+    Initializing lib-fts is still optional within it.
+
+M	src/plugins/fts-lucene/fts-lucene-plugin.c
+M	src/plugins/fts-solr/fts-solr-plugin.c
+M	src/plugins/fts/fts-user.c
+M	src/plugins/fts/fts-user.h
+
+2021-07-22 14:20:25 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (27a98a2d3c)
+
+    lib-storage: Add mailbox exclusion plugin API
+
+    Allows mailbox exclusion configuration to be easily added to any plugin.
+
+M	src/lib-storage/Makefile.am
+A	src/lib-storage/mailbox-match-plugin.c
+A	src/lib-storage/mailbox-match-plugin.h
+
+2021-08-04 19:57:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8939c30ce9)
+
+    master: Fix unfinished "time moved backwards" comment
+
+
+M	src/master/main.c
+
+2021-08-04 19:54:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (0768778bb6)
+
+    master: Log a warning also about "time moved forwards"
+
+    This isn't really important to know, but it could help figure out 
+    performance problems if it happens a lot.
+
+M	src/master/main.c
+
+2021-08-04 19:52:14 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (24216a4924)
+
+    lib: ioloop - Handle "time moved forwards" only after 100ms difference
+
+    Previously this was done after even a single microsecond difference, causing
+    it to happen almost constantly. This was causing performance problems when
+    there were many timeouts that had to be updated. Especially master process
+    could have been spending a lot of time unnecessarily here.
+
+    Broken by b258137d0e0618ae792e3606071a1715d26f107b
+
+M	src/lib/ioloop.c
+
+2021-08-04 19:55:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (87bd288902)
+
+    lib: ioloop - Fix 0 timeout with kqueue() and select()
+
+    With these it was waiting for 1 ms instead of 0.
+
+    Broken by fac27f192d8432c45d360025613f7d432271c5bb
+
+M	src/lib/ioloop.c
+
+2021-08-04 17:24:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1f6aaaeb3c)
+
+    fts: Fix internal error when fts_index_timeout is set
+
+    Broken by cf114f90e0ba25c18db846ee582e3a130bd52949
+
+M	src/plugins/fts/fts-indexer.c
+
+2021-08-03 16:11:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b79d3118e5)
+
+    acl: Cast enums explicitly to int in sorting function
+
+    Fixes ubsan complaint: runtime error: unsigned integer overflow: 0 - 4
+    cannot be represented in type 'unsigned int'
+
+M	src/plugins/acl/acl-api.c
+
+2021-06-29 22:45:33 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (bb6efaa417)
+
+    lib-program-client: program-client-remote - Fix signed integer arithmetic.
+
+    Make type cast explicit to gain ubsan approval. Also prevent negative 
+    reserve_mod from having unexpected effect.
+
+M	src/lib-program-client/program-client-remote.c
+
+2021-08-03 11:44:06 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (ac769778d1)
+
+    util: dovecot-sysreport - Fix help to have -o as the short form of --core
+
+
+M	src/util/dovecot-sysreport
+
+2021-08-03 10:56:08 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (6cbeaf165d)
+
+    util: dovecot-sysreport - Use only spaces for indentation
+
+    Stop mixing tabs and spaces.
+
+M	src/util/dovecot-sysreport
+
+2021-07-27 20:13:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (23388cf96e)
+
+    stats: Revert the previous OpenMetrics info type revert
+
+    The OpenMetrics standard does support "info" type. The original Prometheus 
+    format doesn't support it, but our support is for OpenMetrics. They don't 
+    even have any overlapping types that could be used for this, so the only 
+    other possibility would have been to make this configurable.
+
+    Reverts 55a519d18fbbb8435854f1fcf2642b908d6fc074
+
+M	src/stats/stats-service-openmetrics.c
+
+2021-07-28 16:39:59 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8b80c45ab1)
+
+    submission-login: Fix compiling error with some older compilers
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-07-05 00:37:46 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (c096b7fd77)
+
+    submission-login: submission-proxy - Optionally send XCLIENT LOGIN to
+    backend and skip authentication.
+
+    This behavior is enabled by returning proxy_noauth from passdb.
+
+M	src/submission-login/submission-proxy.c
+
+2021-07-19 00:29:23 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f94dd904b8)
+
+    login-common: Add proxy field proxy_noauth.
+
+
+M	src/login-common/client-common-auth.c
+M	src/login-common/client-common.h
+
+2021-07-19 00:24:32 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (bdba22fecb)
+
+    submission-login: submission-proxy - Move
+    submission_proxy_success_reply_sent().
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-05-18 20:18:45 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c5387d7778)
+
+    dict: Use the dict name as the log prefix
+
+    Otherwise if there are multiple dicts it may not be obvious which one is 
+    causing the errors.
+
+M	src/dict/dict-connection.c
+
+2021-07-22 11:26:10 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (55a519d18f)
+
+    stats: Revert dovecot build information to untyped data
+
+    OpenMetrics does not know type 'info', so use 'untyped' instead.
+
+    Broken in ae678116a79fff609cdf4fb1eb7eb3db2975bf1c
+
+M	src/stats/stats-service-openmetrics.c
+
+2021-07-21 18:12:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (bf111f6830)
+
+    lib-storage: mail_cache_*_fields - Check for invalid header names while
+    parsing config
+
+    This way the errors are noticed early on.
+
+M	src/config/settings-get.pl
+M	src/lib-storage/mail-storage-settings.c
+
+2020-04-14 12:24:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (723d129153)
+
+    lib-storage: mail_cache_*_fields - Log an error if hdr.<name> isn't valid
+
+    Mainly verify that it doesn't have accidental UTF-8 characters that aren't 
+    easily visible in text editors.
+
+M	src/lib-storage/index/index-storage.c
+
+2020-04-19 14:03:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ecac22474a)
+
+    lib-mail: Add message_header_name_is_valid()
+
+
+M	src/lib-mail/message-header-parser.c
+M	src/lib-mail/message-header-parser.h
+
+2021-07-22 16:38:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (fafd7ad584)
+
+    login-proxy: Make sure input line isn't freed too early
+
+    proxy_parse_line() could free the proxy's istream, which frees the line 
+    string. With IMAP the line could have been used as part of the error string 
+    passed to login_proxy_failed(), which can free the istream before using the
+    string for logging purposes. This could have resulted in logging a corrupted
+    line or a crash.
+
+    Broken by e3134289529cec16ade44cefd0fd26594ae40e30
+
+M	src/login-common/client-common-auth.c
+
+2021-07-23 14:45:03 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (af3e934e33)
+
+    lib-test: Make sure child processes exit cleanly with SIGTERM
+
+    It's normal behavior that the parent process kills the child process with 
+    SIGTERM. This shouldn't result in the child process dying with SIGTERM, but 
+    a clean exit. This was causing http-test-client-errors unit tests to fail 
+    somewhat randomly, especially when running them only with 1 CPU.
+
+M	src/lib-test/test-subprocess.c
+
+2021-07-21 14:06:55 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (128bcb85a9)
+
+    lib-test: Ensure we send signals to regular PIDs only
+
+
+M	src/lib-test/test-subprocess.c
+
+2021-07-21 14:05:34 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (34bdfdcbc7)
+
+    lib-test: Update subprocess list after forking
+
+    Due to a race condition, we could end up killing PID 0 by accident 
+    sometimes.
+
+M	src/lib-test/test-subprocess.c
+
+2021-07-22 11:23:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (e3e4cd2681)
+
+    lib: random_fill() - Optimize away memmove()
+
+    We just need to track the position of how far the random_next buffer has 
+    been used.
+
+M	src/lib/randgen.c
+
+2021-07-05 00:36:36 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (82d9013dd3)
+
+    submission-login: submission-proxy - Send XCLIENT data in multiple commands
+    if line exceeds 512 bytes.
+
+    When the proxy talks to non-Dovecot software, failures could occur
+    otherwise. Particularly Postfix will fail.
+
+M	src/submission-login/client.h
+M	src/submission-login/submission-proxy.c
+
+2021-06-19 00:31:19 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (12c9ce36a4)
+
+    submission-login: submission-proxy - Send PROTO and HELO XCLIENT fields.
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-06-18 19:32:03 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9127db1a89)
+
+    submission-login: submission-proxy - Properly send EHLO after XCLIENT.
+
+
+M	src/submission-login/client.h
+M	src/submission-login/submission-proxy.c
+
+2021-07-05 23:33:51 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3764af4b42)
+
+    submission-login: submission-proxy - Reorder XCLIENT fields to match
+    lib-smtp client.
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-06-19 00:25:02 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b1e0d63624)
+
+    submission-login: submission-proxy - Handle EHLO reply in a separate
+    function.
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-06-18 23:58:38 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (297f39ba26)
+
+    submission-login: submission-proxy - Move sending STARTTLS to separate
+    function.
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-06-18 19:52:33 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (bbf725af33)
+
+    submission-login: submission-proxy - Avoid sending empty XCLIENT FORWARD
+    field.
+
+
+M	src/submission-login/submission-proxy.c
+
+2021-06-18 19:31:54 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (52516b0566)
+
+    submission: Separately pass XCLIENT HELO value from pre-login to post-login
+    service.
+
+
+M	src/submission-login/client-authenticate.c
+M	src/submission/main.c
+M	src/submission/submission-client.c
+M	src/submission/submission-client.h
+
+2021-06-18 17:02:23 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (019522fc34)
+
+    submission: submission-backend-relay - Use
+    smtp_server_connection_get_proxy_data() for composing client settings.
+
+
+M	src/submission/submission-backend-relay.c
+
+2021-06-18 17:46:07 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (13f5a8132c)
+
+    submission: main - Restructure parsing of login input data.
+
+    Needed for adding additional fields.
+
+M	src/submission/main.c
+
+2021-06-18 16:43:55 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0c90556d3f)
+
+    submission-login: client-authenticate - Restructure composition of
+    master_prefix.
+
+    Needed for later commit.
+
+M	src/submission-login/client-authenticate.c
+
+2021-06-18 19:27:40 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b33f1b875d)
+
+    lib-smtp: smtp-server-connection - Record proxied EHLO domain separately.
+
+    This prevents it from being overriden by a local EHLO command.
+
+M	src/lib-smtp/smtp-server-connection.c
+M	src/lib-smtp/smtp-server-private.h
+
+2021-06-18 19:02:34 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (53cdb6dedd)
+
+    lib-smtp: smtp-server - Make smtp_server_connection_set_proxy_data() public.
+
+
+M	src/lib-smtp/smtp-server-private.h
+M	src/lib-smtp/smtp-server.h
+
+2021-06-18 16:54:51 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (5f5f93e962)
+
+    lib-smtp: smtp-server-connection - Use
+    smtp_server_connection_get_proxy_data() for the conn_proxy_data_updated()
+    callback.
+
+    Removes code duplication.
+
+M	src/lib-smtp/smtp-server-connection.c
+
+2021-06-18 16:52:26 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (26b64133b2)
+
+    lib-smtp: smtp-server-connection - Allow username/ehlo to be set before
+    smtp_server_connection_login().
+
+    This allows setting the proxy data early.
+
+M	src/lib-smtp/smtp-server-connection.c
+
+2021-06-18 19:25:43 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0f55dcb0bd)
+
+    lib-smtp: Reformat smtp-server.h.
+
+
+M	src/lib-smtp/smtp-server.h
+
+2021-06-18 16:31:08 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (85669603b8)
+
+    submission: Reformat submission-client.h.
+
+
+M	src/submission/submission-client.h
+
+2021-06-18 16:30:52 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (cf73c66df0)
+
+    submission: Reformat submission-client.c.
+
+
+M	src/submission/submission-client.c
+
+2021-06-24 00:19:16 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (c8bdb4578c)
+
+    login-common: Add support for recording rawlog of connection between proxy
+    and backend.
+
+
+M	src/login-common/client-common-auth.c
+M	src/login-common/login-proxy.c
+M	src/login-common/login-proxy.h
+M	src/login-common/login-settings.c
+M	src/login-common/login-settings.h
+
+2021-07-21 17:14:26 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (d757b9af43)
+
+    doveadm: Mark fs_cmd_help() with ATTR_NORETURN
+
+    This helps static analyzers understand that the function doesn't return.
+
+M	src/doveadm/doveadm-fs.c
+
+2021-07-21 17:12:05 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (6e7928f5cf)
+
+    doveadm: Avoid passing NULL to memcpy() even though zero bytes are copied
+
+    Makes static analyzers happier.
+
+M	src/doveadm/doveadm.c
+
+2021-07-19 16:38:08 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (567d853885)
+
+    doveadm: Remove dead assignment
+
+
+M	src/doveadm/client-connection-tcp.c
+
+2021-04-22 11:09:49 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (a7146d0939)
+
+    lib: Use 32 byte buffer for getting randomness
+
+    This reduces the number of syscalls when small amount of randomness is 
+    requested, like i_rand*() calls using only 4 bytes at a time.
+
+M	src/lib/randgen.c
+
+2021-06-10 12:16:20 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8d396dda21)
+
+    doveadm: Remove code related to obsolete doveadm_mail_cmd handling
+
+    struct doveadm_mail_cmd couldn't be completely removed since v2 mail 
+    commands are still converted to it.
+
+M	src/doveadm/client-connection-tcp.c
+M	src/doveadm/doveadm-mail-batch.c
+M	src/doveadm/doveadm-mail.c
+M	src/doveadm/doveadm-mail.h
+M	src/doveadm/doveadm.c
+
+2021-06-10 12:06:40 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5676c510bf)
+
+    doveadm sync/backup/dsync-server: Convert to v2 command
+
+
+M	src/doveadm/doveadm-dsync.c
+M	src/doveadm/doveadm-dsync.h
+M	src/doveadm/doveadm-mail.c
+
+2021-06-10 11:51:51 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (dfe41ddaac)
+
+    doveadm batch: Convert to v2 command
+
+
+M	src/doveadm/doveadm-mail-batch.c
+M	src/doveadm/doveadm-mail.c
+M	src/doveadm/doveadm-mail.h
+
+2021-06-10 01:14:14 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1e48423c17)
+
+    doveadm: Remove struct doveadm_cmd and related code
+
+
+M	src/doveadm/client-connection-tcp.c
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm.c
+M	src/doveadm/doveadm.h
+M	src/doveadm/main.c
+
+2021-06-10 01:09:34 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (875a371693)
+
+    doveadm: Remove doveadm_cmd_ver2.old_cmd
+
+
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+
+2021-06-10 01:08:16 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (058f76650a)
+
+    doveadm proxy: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-proxy.c
+
+2021-06-10 01:03:46 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (871adaab1f)
+
+    doveadm oldstats: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-oldstats.c
+
+2021-06-10 00:59:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (3aef610846)
+
+    doveadm stop/reload: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-master.c
+
+2021-06-10 00:58:45 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (59087d1dad)
+
+    doveadm fs: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-fs.c
+
+2021-06-10 00:45:43 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b6e92ea8fb)
+
+    doveadm auth cache flush: Convert auth-server command to v2
+
+
+M	src/doveadm/doveadm-auth-server.c
+M	src/doveadm/doveadm-auth.c
+
+2021-06-10 00:42:38 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (af1d3d37f1)
+
+    doveadm auth: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-auth.c
+
+2021-06-10 00:18:17 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (94734b9359)
+
+    doveadm instance: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-instance.c
+
+2021-06-10 00:05:28 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (fc0b20f702)
+
+    doveadm log: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-log.c
+
+2021-06-10 00:01:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f8290ea73a)
+
+    doveadm replicator: Convert to v2 commands
+
+
+M	src/doveadm/doveadm-replicator.c
+
+2021-06-09 23:48:10 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f1524717c6)
+
+    doveadm config: Convert to v2 command
+
+
+M	src/doveadm/doveadm.c
+
+2021-06-09 23:45:51 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (476dc132cb)
+
+    doveadm exec: Convert to v2 command
+
+
+M	src/doveadm/doveadm.c
+
+2021-06-10 14:23:46 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (938428f914)
+
+    doveadm: Add CMD_FLAG_NO_UNORDERED_OPTIONS
+
+
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+
+2021-06-09 23:44:29 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8b461f612c)
+
+    doveadm: Add CMD_FLAG_NO_OPTIONS
+
+    This allows command to process all parameters, including parameters starting 
+    with "-".
+
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+
+2021-06-09 23:39:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b086c1a88b)
+
+    doveadm: Split off doveadm_cmd_process_options()
+
+
+M	src/doveadm/doveadm-cmd.c
+
+2021-06-09 23:37:19 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (087ec4374b)
+
+    doveadm: Reformat doveadm-cmd.c
+
+
+M	src/doveadm/doveadm-cmd.c
+
+2021-06-09 23:21:27 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9f305a24c5)
+
+    doveadm help: Convert to v2 command
+
+
+M	src/doveadm/doveadm.c
+
+2021-06-09 23:13:30 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7b67742aa7)
+
+    doveadm dump: Convert to v2 command
+
+
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-dump.c
+M	src/doveadm/doveadm.c
+
+2021-06-09 23:10:08 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (4a508ba07a)
+
+    doveadm dump: Change doveadm_cmd_dump.cmd() API to be simpler
+
+
+M	src/doveadm/doveadm-dump-dbox.c
+M	src/doveadm/doveadm-dump-dcrypt-file.c
+M	src/doveadm/doveadm-dump-dcrypt-key.c
+M	src/doveadm/doveadm-dump-index.c
+M	src/doveadm/doveadm-dump-log.c
+M	src/doveadm/doveadm-dump-mailboxlog.c
+M	src/doveadm/doveadm-dump-thread.c
+M	src/doveadm/doveadm-dump.c
+M	src/doveadm/doveadm-dump.h
+M	src/doveadm/doveadm-zlib.c
+M	src/plugins/fts-lucene/doveadm-fts-lucene.c
+M	src/plugins/fts/doveadm-dump-fts-expunge-log.c
+
+2021-06-09 23:02:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ced5df32b5)
+
+    doveadm pw: Convert to v2 command
+
+
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-pw.c
+M	src/doveadm/doveadm.c
+
+2021-06-09 22:55:55 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (cfe3a83df7)
+
+    doveadm mailbox mutf7: Convert to v2 command
+
+
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-mutf7.c
+
+2021-06-09 22:47:35 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (07ba60c061)
+
+    doveadm sis deduplicate/find: Convert to v2 command
+
+
+M	src/doveadm/doveadm-cmd.c
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-sis.c
+
+2021-06-09 22:42:54 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (11439a8366)
+
+    doveadm zlibconnect: Convert to v2 command
+
+
+M	src/doveadm/doveadm-cmd.h
+M	src/doveadm/doveadm-zlib.c
+M	src/doveadm/doveadm.c
+
+2021-06-09 22:39:55 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (580b696a1f)
+
+    doveadm: Code cleanup - Add doveadm_cmdline_commands_ver2[]
+
+
+M	src/doveadm/doveadm.c
+
+2021-06-10 16:56:06 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (3d52021b2d)
+
+    doveadm: Support building ARRAY type mail command parameters
+
+
+M	src/doveadm/doveadm-mail.c
+
+2021-06-10 16:55:04 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5dc7039807)
+
+    doveadm: Fix building IP/INT64 type mail command parameters
+
+
+M	src/doveadm/doveadm-mail.c
+
+2021-06-10 16:51:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ce9ac4a9cc)
+
+    doveadm: Split off doveadm_cmd_parse_arg()
+
+
+M	src/doveadm/doveadm-mail.c
+
+2020-12-03 19:20:32 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (6477f9be35)
+
+    lib: Fix assert-crash when destroying ioloop that has active context
+
+    Normally the ioloop shouldn't have an active context at deinit, but it seems
+    to be possible in some situations. It's not really bad anyway, so just allow
+    it.
+
+    Fixes: Panic: file ioloop.c: line 928 (io_loop_destroy): assertion failed:
+    (ioloop->cur_ctx == NULL)
+
+M	src/lib/ioloop.c
+M	src/lib/test-ioloop.c
+
+2021-07-19 14:56:47 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (eac71a2968)
+
+    lib-mail: Fix memory leak in istream-header-filter
+
+    Broken by 1c1b77dbf9a548aac788efb76973ce2d0fa6c732
+
+M	src/lib-mail/istream-header-filter.c
+
+2021-07-19 13:54:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5bd95c2ba9)
+
+    lib: data-stack - t_try_realloc() - Add missing
+    data_stack_last_buffer_reset() call
+
+    This fixes incorrect "buffer overflow" panics with DEBUG builds.
+
+M	src/lib/data-stack.c
+
+2021-07-19 13:52:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (7dedc453ca)
+
+    lib: data-stack - data_stack_last_buffer_reset(() - Add comments and an
+    assert
+
+    This clarifies how the function is expected to work.
+
+M	src/lib/data-stack.c
+
+2021-07-12 14:26:38 +0200 Markus Valentin <markus.valentin@open-xchange.com> (4fdb040d24)
+
+    lib-storage: Use escaped name length to calculate truncation margin
+
+    This fixes corruption of mailbox names when the storage_name_escape_char has
+    been part of the parent folder name.
+
+    Broken by 5dd81d83d8d9120ed2a74d5bd2aa62622885b49c
+
+M	src/lib-storage/list/mailbox-list-index-iter.c
+
+2021-07-12 13:40:58 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (14ad268aa1)
+
+    lib-lua: Fix LIBDICT_LUA variable usage
+
+    It needs to be appended to, not set again. Broken by 
+    3d0b7e9bb59e3dc41fd5a4d09832eedea7a92933.
+
+M	src/lib-lua/Makefile.am
+
+2021-07-08 12:24:47 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (3d0b7e9bb5)
+
+    lib-lua: Only link libdict_lua if it's available
+
+
+M	src/lib-lua/Makefile.am
+
+2021-07-08 11:10:38 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (b4827bc2f2)
+
+    plugins/fts: Restore fts_indexer_cmd
+
+    It was removed in cf114f90e0ba25c18db846ee582e3a130bd52949 and that broke
+    some FTS plugins.
+
+M	src/plugins/fts/fts-indexer.c
+
+2021-06-22 13:28:34 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (3bac871aea)
+
+    lib-dict: Fix linking when building without Lua
+
+    Linking didn't work on some non-Linux OSes (non-GNU linkers?)
+
+M	src/lib-dict/Makefile.am
+
+2021-06-04 11:16:53 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (b0a354f7a9)
+
+    lib-compression: istream-zstd - Make sure parent stream error isn't
+    overwritten
+
+    This could have happened if the parent istream failed before the zstd header 
+    was read. Practically this didn't happen currently, because the initial 
+    parent stream error was normally already handled by istream-decompress 
+    before istream-zstd was even called.
+
+M	src/lib-compression/istream-zstd.c
+
+2021-06-04 11:16:22 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8e3a7d9a50)
+
+    lib-compression: istream-zlib - Fix parent stream error handling near EOF
+
+    This happened when gz stream (including trailer) was fully read, but the 
+    final check to see if there is a concatenated gz stream afterwards failed 
+    due to parent istream failure. In this case the error was ignored and 
+    istream returned success, truncating any potential concatenated istream 
+    input. This situation was very unlikely to happen.
+
+M	src/lib-compression/istream-zlib.c
+
+2021-05-27 19:34:50 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1c1b77dbf9)
+
+    lib-mail: Fix istream-header-filter snapshotting
+
+
+M	src/lib-mail/istream-header-filter.c
+M	src/lib-mail/test-istream-header-filter.c
+
+2021-06-17 19:55:56 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (25e0619c01)
+
+    lib: Add istream_snapshot.free() to allow more complex snapshots
+
+
+M	src/lib/istream-private.h
+M	src/lib/istream.c
+
+2021-06-15 12:21:22 +0200 Markus Valentin <markus.valentin@open-xchange.com> (f8c0c372a9)
+
+    lib-storage: Make use of MAIL_INDEX_VIEW_SYNC_FLAG_2ND_INDEX
+
+
+M	src/lib-storage/index/index-sync-pvt.c
+
+2021-06-15 12:19:00 +0200 Markus Valentin <markus.valentin@open-xchange.com> (1d9b391105)
+
+    lib-index: Introduce MAIL_INDEX_VIEW_SYNC_FLAG_2ND_INDEX
+
+    This flag is used to make sure secondary views flags-index can work properly
+    without emitting warnings about inconsistency. If an inconsistency is
+    encountered fix it by fully syncing.
+
+M	src/lib-index/mail-index-view-sync.c
+M	src/lib-index/mail-index.h
+
+2021-06-15 08:59:17 +0200 Markus Valentin <markus.valentin@open-xchange.com> (c1f2323e15)
+
+    Revert "lib-storage: Always fix inconsistency when syncing private flags
+    index"
+
+    This reverts commit 986d9cbbecffd836d977b6ad956b04e3ca606677.
+
+    This is reverted because storing flags on private indexes no longer send 
+    untagged replies.
+
+M	src/lib-storage/index/index-sync-private.h
+M	src/lib-storage/index/index-sync-pvt.c
+M	src/lib-storage/index/index-sync.c
+M	src/lib-storage/index/index-transaction.c
+
+2021-07-01 10:46:05 +0200 Markus Valentin <markus.valentin@open-xchange.com> (9183e2b3af)
+
+    acl: Ignore acls in acl_lookup_dict_rebuild_add_backend if ignore_acls is
+    set
 
-2021-08-05 17:53:58 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (07a1c92747)
+    In case an namespace has been configured to ignore ACLs also respect that
+    when acl_lookup_dict_rebuild is called.
 
-    master: Avoid high CPU usage when process_min_avail reaches process_limit
+    Co-Authored-By: Vincent Brillault <vincent.brillault@cern.ch>
 
-    process_min_avail handling always created a 0ms timeout to try to create the 
-    missing processes. This timeout was supposed to stop when it couldn't launch 
-    all the wanted processes, but the check wasn't done right. This ended up 
-    causing the timeout to be called rapidly over and over again.
+M	src/plugins/acl/acl-lookup-dict.c
 
-M	src/master/service-monitor.c
+2021-06-02 00:40:19 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (357ff0b35b)
 
-2021-08-04 17:24:00 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (40ae513d78)
+    lib-smtp: test-smtp-payload - Set all timeouts to CLIENT_PROGRESS_TIMEOUT.
 
-    fts: Fix internal error when fts_index_timeout is set
+    This prevents connect and idle timeouts from triggering earlier than the 
+    progress timeout.
 
-    Broken by cf114f90e0ba25c18db846ee582e3a130bd52949
+M	src/lib-smtp/test-smtp-payload.c
 
-M	src/plugins/fts/fts-indexer.c
+2021-06-22 11:52:02 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (c5edbe6007)
 
-2021-08-04 12:51:19 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5efa307f2b)
+    imap: PREVIEW responses need trailing space
 
-    NEWS: Updates for v2.3.16
+    2.3.15 regression
 
+    Before 2.3.15, there was this same buggy behavior in error cases; 2.3.15 
+    moved that buggy behavior to the success code path
 
-M	NEWS
+    DOP-2463
 
-2021-06-14 12:47:15 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (1c0653a7bd)
+M	src/imap/imap-fetch-body.c
 
-    NEWS: Updates for v2.3.15
+2021-06-14 23:46:48 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (5fbff5f80d)
 
+    doveadm-server: Add log prefix to logs written to client
 
-M	NEWS
 
-2021-05-24 14:03:57 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (63d1dc03d8)
+M	src/doveadm/client-connection-tcp.c
 
-    NEWS: Add news for 2.3.14.1
+2021-06-14 23:43:11 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (8f2033be24)
 
+    doveadm-server: Simplify writing log output to client
 
-M	NEWS
 
-2021-08-03 11:44:06 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (2e0f58a1db)
+M	src/doveadm/client-connection-tcp.c
 
-    util: dovecot-sysreport - Fix help to have -o as the short form of --core
+2021-06-14 20:14:13 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (c4acd0ce0e)
 
+    lib: Fix log prefix in internal handler when log handler has been overridden
 
-M	src/util/dovecot-sysreport
+    Adds back mail_log_prefix to doveadm mail commands when doveadm-server was 
+    accessed via TCP.
 
-2021-08-03 10:56:08 +0300 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (cd85976845)
+    Originally broken by c9dd53f7180a78668cbc1e6eb34d5b1722beccb9
 
-    util: dovecot-sysreport - Use only spaces for indentation
+M	src/lib/failures.c
 
-    Stop mixing tabs and spaces.
+2021-06-04 23:00:21 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (60cbb2959c)
 
-M	src/util/dovecot-sysreport
+    dict-file: Make sure home_dir doesn't change during operations
 
-2021-07-27 20:13:24 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c27d091a0d)
+    file_dict is initialized for a specific user. Keep record of the user's 
+    home_dir and check for all dict operations that the user did not change.
 
-    stats: Revert the previous OpenMetrics info type revert
+M	src/lib-dict/dict-file.c
 
-    The OpenMetrics standard does support "info" type. The original Prometheus 
-    format doesn't support it, but our support is for OpenMetrics. They don't 
-    even have any overlapping types that could be used for this, so the only 
-    other possibility would have been to make this configurable.
+2021-06-02 16:17:22 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (ebc92f1ba7)
 
-    Reverts 55a519d18fbbb8435854f1fcf2642b908d6fc074
+    global: Remove dict_settings.home_dir
 
-M	src/stats/stats-service-openmetrics.c
+    Allows dict to be shared across users. Dict operations use 
+    dict_op_settings.home_dir if set.
 
-2021-07-22 11:26:10 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (9a312ebd21)
+M	src/lib-dict/dict-file.c
+M	src/lib-dict/dict.h
+M	src/lib-storage/index/index-attribute.c
+M	src/plugins/last-login/last-login-plugin.c
+M	src/plugins/notify-status/notify-status-plugin.c
+M	src/plugins/quota-clone/quota-clone-plugin.c
+M	src/plugins/quota/quota-dict.c
+M	src/plugins/quota/quota.c
 
-    stats: Revert dovecot build information to untyped data
+2021-05-26 15:18:20 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (bdcdd37fb0)
 
-    OpenMetrics does not know type 'info', so use 'untyped' instead.
+    dict: Split protocol command arguments in dict_command_input()
 
-    Broken in ae678116a79fff609cdf4fb1eb7eb3db2975bf1c
+    Instead of duplicating code and splitting in each command functions 
+    separately.
 
-M	src/stats/stats-service-openmetrics.c
+M	src/dict/dict-commands.c
 
-2020-12-03 19:20:32 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (214157dd82)
+2021-05-26 01:41:51 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (e222a33dc1)
 
-    lib: Fix assert-crash when destroying ioloop that has active context
+    lib-dict: Make sure username is valid as well when checking key prefix
 
-    Normally the ioloop shouldn't have an active context at deinit, but it seems
-    to be possible in some situations. It's not really bad anyway, so just allow
-    it.
+    If this is a private key, username must be non-empty.
 
-    Fixes: Panic: file ioloop.c: line 928 (io_loop_destroy): assertion failed:
-    (ioloop->cur_ctx == NULL)
+M	src/lib-dict/dict.c
 
-M	src/lib/ioloop.c
-M	src/lib/test-ioloop.c
+2021-05-24 12:54:58 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (4c15dc4ede)
 
-2021-07-12 14:26:38 +0200 Markus Valentin <markus.valentin@open-xchange.com> (53fafc97c3)
+    dict: Remove dict_connection.username and dict_settings.username
 
-    lib-storage: Use escaped name length to calculate truncation margin
+    - dict should be user agnostic. dict operations have username.
+    - same for dict connection
+    - also removes username from dict process' log prefix
 
-    This fixes corruption of mailbox names when the storage_name_escape_char has
-    been part of the parent folder name.
+M	src/auth/db-dict.c
+M	src/auth/db-oauth2.c
+M	src/dict/dict-connection.c
+M	src/dict/dict-connection.h
+M	src/doveadm/doveadm-dict.c
+M	src/lib-dict-backend/test-dict-sql.c
+M	src/lib-dict-extra/dict-fs.c
+M	src/lib-dict-extra/test-dict-fs.c
+M	src/lib-dict/dict.c
+M	src/lib-dict/dict.h
+M	src/lib-dict/test-dict-client.c
+M	src/lib-fs/fs-dict.c
+M	src/lib-oauth2/test-oauth2-jwt.c
+M	src/lib-storage/index/index-attribute.c
+M	src/plugins/acl/acl-lookup-dict.c
+M	src/plugins/last-login/last-login-plugin.c
+M	src/plugins/notify-status/notify-status-plugin.c
+M	src/plugins/quota-clone/quota-clone-plugin.c
+M	src/plugins/quota/quota-dict.c
+M	src/plugins/quota/quota.c
 
-    Broken by 5dd81d83d8d9120ed2a74d5bd2aa62622885b49c
+2021-06-03 19:59:23 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (037442cde7)
 
-M	src/lib-storage/list/mailbox-list-index-iter.c
+    dict: Add username field for command events
 
-2021-07-13 12:01:47 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (639cd6f517)
+    To prepare for connection username removal in next commit. Instead of adding
+    username field for the parent event, add it in command events.
 
-    configure: Set version to 2.3.16
+    Commands can now be run for different users. Having the same username field
+    as the one used in initialization can be wrong.
 
+M	src/dict/dict-commands.c
+M	src/dict/dict-connection.c
 
-M	configure.ac
+2021-05-24 11:47:15 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (a9e6e57a73)
 
-2021-07-12 13:40:58 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (3697bbaf77)
+    dict-client: Do not send username in initial handshake
 
-    lib-lua: Fix LIBDICT_LUA variable usage
+    Dict commands that need username have it included in their arguments now.
 
-    It needs to be appended to, not set again. Broken by 
-    3d0b7e9bb59e3dc41fd5a4d09832eedea7a92933.
+M	src/lib-dict/dict-client.c
 
-M	src/lib-lua/Makefile.am
+2021-05-24 11:58:44 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (2af85d6cbc)
 
-2021-07-08 12:24:47 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (e1b0b343c6)
+    dict-fs: Remove unused fs_dict.username
 
-    lib-lua: Only link libdict_lua if it's available
 
+M	src/lib-dict-extra/dict-fs.c
 
-M	src/lib-lua/Makefile.am
+2021-05-24 11:58:02 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (88636cd5a1)
 
-2021-07-08 11:10:38 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (bd4ec646cc)
+    dict-redis: Remove unused redis_dict.username
 
-    plugins/fts: Restore fts_indexer_cmd
 
-    It was removed in cf114f90e0ba25c18db846ee582e3a130bd52949 and that broke
-    some FTS plugins.
+M	src/lib-dict/dict-redis.c
 
-M	src/plugins/fts/fts-indexer.c
+2021-05-24 11:57:18 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f7da270894)
 
-2021-06-22 13:28:34 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (321c3f31be)
+    dict-memcached-ascii: Remove unused memcached_ascii_dict.username
 
-    lib-dict: Fix linking when building without Lua
 
-    Linking didn't work on some non-Linux OSes (non-GNU linkers?)
+M	src/lib-dict/dict-memcached-ascii.c
 
-M	src/lib-dict/Makefile.am
+2021-05-24 11:56:29 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (e2a74dab49)
 
-2021-06-02 00:40:19 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0e0ead994c)
+    dict-sql: Remove unused sql_dict.username
 
-    lib-smtp: test-smtp-payload - Set all timeouts to CLIENT_PROGRESS_TIMEOUT.
 
-    This prevents connect and idle timeouts from triggering earlier than the 
-    progress timeout.
+M	src/lib-dict-backend/dict-sql-private.h
+M	src/lib-dict-backend/dict-sql.c
 
-M	src/lib-smtp/test-smtp-payload.c
+2021-05-24 11:54:56 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (38ae540aa5)
 
-2021-07-01 10:46:05 +0200 Markus Valentin <markus.valentin@open-xchange.com> (604b879dd7)
+    dict-ldap: Remove unused ldap_dict.username
 
-    acl: Ignore acls in acl_lookup_dict_rebuild_add_backend if ignore_acls is
-    set
 
-    In case an namespace has been configured to ignore ACLs also respect that
-    when acl_lookup_dict_rebuild is called.
+M	src/lib-dict-backend/dict-ldap.c
+
+2021-06-03 21:27:31 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (6c94259f8f)
+
+    dict: Add dict_event_create
+
+    Helper function for dict events. If dict_op_settings has username set, adds 
+    it as a field.
+
+M	src/lib-dict/dict.c
+
+2021-06-03 21:18:03 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (843cb951df)
+
+    dict-client: Send username in commands
+
+    Lookup, iterate_begin, and transaction_begin need username. This bumps dict
+    protocol's major version to 3.
+
+M	src/lib-dict/dict-client.c
+M	src/lib-dict/dict-client.h
+
+2021-06-03 21:16:22 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (bb6c727856)
+
+    dict-redis: Use dict_op_settings to get full key
+
+    Switch from dict.username to dict_op_settings.username. Also, ensure that
+    username is escaped.
+
+M	src/lib-dict/dict-redis.c
+
+2021-06-03 21:14:51 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (3d78c522f7)
+
+    dict-memcached-ascii: Use dict_op_settings to get full key
+
+    Switch from dict.username to dict_op_settings.username.
+
+M	src/lib-dict/dict-memcached-ascii.c
+
+2021-06-03 21:12:27 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f4d43cf907)
+
+    dict-fs: Use dict_op_settings to get full key
+
+    Switch from dict.username to dict_op_settings.username.
+
+M	src/lib-dict-extra/dict-fs.c
+
+2021-06-03 20:58:05 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (6ed788ca21)
+
+    dict: Use dict_op_settings in backends
+
+    - Use dict_op_settings in dict API functions
+    - forward the settings object to backends for dict lookup/iterate
+    - Update backends to use dict_op_settings
+
+M	src/lib-dict-backend/dict-cdb.c
+M	src/lib-dict-backend/dict-ldap.c
+M	src/lib-dict-backend/dict-sql.c
+M	src/lib-dict-extra/dict-fs.c
+M	src/lib-dict/dict-client.c
+M	src/lib-dict/dict-fail.c
+M	src/lib-dict/dict-file.c
+M	src/lib-dict/dict-memcached-ascii.c
+M	src/lib-dict/dict-memcached.c
+M	src/lib-dict/dict-private.h
+M	src/lib-dict/dict-redis.c
+M	src/lib-dict/dict.c
+
+2021-06-03 18:31:57 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (8d69e81674)
+
+    notify-status: Initialize dict_op_settings for dict operations
+
+
+M	src/plugins/notify-status/notify-status-plugin.c
+
+2021-06-03 18:32:50 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (b87665654f)
+
+    last-login: Initialize dict_op_settings for dict operations
+
+
+M	src/plugins/last-login/last-login-plugin.c
+
+2021-06-03 18:30:17 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (88f5f42541)
+
+    quota: Initialize dict_op_settings for dict operations
+
+
+M	src/plugins/quota-clone/quota-clone-plugin.c
+M	src/plugins/quota/quota-dict.c
+M	src/plugins/quota/quota.c
+
+2021-06-03 19:32:26 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (42052d71f3)
+
+    dict: Initialize dict_op_settings for dict operations
+
+
+M	src/dict/dict-commands.c
+
+2021-06-03 18:33:21 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (b70da6c60b)
+
+    acl: Initialize dict_op_settings for dict lookup
 
-    Co-Authored-By: Vincent Brillault <vincent.brillault@cern.ch>
 
 M	src/plugins/acl/acl-lookup-dict.c
 
-2021-06-15 12:21:22 +0200 Markus Valentin <markus.valentin@open-xchange.com> (b9a96d3f07)
+2021-06-03 18:34:26 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (737e107ef7)
 
-    lib-storage: Make use of MAIL_INDEX_VIEW_SYNC_FLAG_2ND_INDEX
+    lib-oauth2: Initialize dict_op_settings for dict operations
 
 
-M	src/lib-storage/index/index-sync-pvt.c
+M	src/lib-oauth2/oauth2-jwt.c
+M	src/lib-oauth2/test-oauth2-jwt.c
 
-2021-06-15 12:19:00 +0200 Markus Valentin <markus.valentin@open-xchange.com> (b05e4d4497)
+2021-06-03 18:41:14 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (683c314667)
 
-    lib-index: Introduce MAIL_INDEX_VIEW_SYNC_FLAG_2ND_INDEX
+    lib-dict: lua: Initialize dict_op_settings for dict operations
 
-    This flag is used to make sure secondary views flags-index can work properly
-    without emitting warnings about inconsistency. If an inconsistency is
-    encountered fix it by fully syncing.
+    - Add new mandatory argument for lookup, set, and transaction_begin.
+    - Use the username to initialize dict_op_settings passed to lib-dict API
 
-M	src/lib-index/mail-index-view-sync.c
-M	src/lib-index/mail-index.h
+M	src/lib-dict/dict-iter-lua.c
+M	src/lib-dict/dict-lua.c
+M	src/lib-dict/dict-txn-lua.c
 
-2021-06-15 08:59:17 +0200 Markus Valentin <markus.valentin@open-xchange.com> (e4fb1de76f)
+2021-07-01 20:39:41 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (b3ab76ff1d)
 
-    Revert "lib-storage: Always fix inconsistency when syncing private flags
-    index"
+    lib-dict-extra: test-dict-fs: Initialize dict_op_settings for dict
+    operations
 
-    This reverts commit 986d9cbbecffd836d977b6ad956b04e3ca606677.
 
-    This is reverted because storing flags on private indexes no longer send 
-    untagged replies.
+M	src/lib-dict-extra/test-dict-fs.c
 
-M	src/lib-storage/index/index-sync-private.h
-M	src/lib-storage/index/index-sync-pvt.c
-M	src/lib-storage/index/index-sync.c
-M	src/lib-storage/index/index-transaction.c
+2021-06-03 18:39:39 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (b150ee039c)
 
-2021-06-22 11:52:02 -0600 Michael M Slusarz <michael.slusarz@open-xchange.com> (954db37d6b)
+    lib-dict-backend: test-dict-sql: Initialize dict_op_settings for dict
+    operations
 
-    imap: PREVIEW responses need trailing space
 
-    2.3.15 regression
+M	src/lib-dict-backend/test-dict-sql.c
 
-    Before 2.3.15, there was this same buggy behavior in error cases; 2.3.15 
-    moved that buggy behavior to the success code path
+2021-06-03 18:38:50 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (051d8cbe36)
 
-    DOP-2463
+    lib-dict: test-dict-client: Initialize dict_op_settings for dict operations
 
-M	src/imap/imap-fetch-body.c
 
-2021-06-25 14:52:11 +0200 Markus Valentin <markus.valentin@open-xchange.com> (ab3757e44e)
+M	src/lib-dict/test-dict-client.c
+
+2021-06-03 18:36:04 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (7fce91db18)
+
+    lib-storage: Initialize dict_op_settings for dict operations
+
+
+M	src/lib-storage/index/index-attribute.c
+
+2021-06-03 18:35:28 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f2083c0b5c)
+
+    lib-fs: Initialize dict_op_settings for dict operations
+
+
+M	src/lib-fs/fs-dict.c
+
+2021-06-03 18:13:31 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (11046766af)
+
+    auth: userdb-dict: Initialize dict_op_settings for dict iterate
+
+
+M	src/auth/userdb-dict.c
+
+2021-06-03 18:11:47 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (09e89ca08a)
+
+    auth: db-dict: Initialize dict_op_settings for dict lookup
+
+
+M	src/auth/db-dict.c
+
+2021-06-03 12:53:08 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (8ec65b7e0c)
+
+    doveadm dict: Initialize & use dict_op_settings for dict commands
+
+    Sets the username in cmd_dict_init_full and use the settings struct for dict 
+    operations.
+
+M	src/doveadm/doveadm-dict.c
+
+2021-06-03 00:06:44 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (64719ad090)
+
+    lib-storage: Add mail_user.dict_op_set and mail_user_get_dict_op_settings()
+
+    Used to initialize or obtain dict_op_settings for the user.
+
+M	src/lib-storage/mail-user.c
+M	src/lib-storage/mail-user.h
+
+2021-06-04 15:39:05 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (aceb659c98)
+
+    lib-storage: mail-user: Clarify mail_user_get_home() usage string
+
+    Explicitly comment about returned string lifetime.
+
+M	src/lib-storage/mail-user.h
+
+2021-05-18 17:52:55 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (5b579636a4)
+
+    dict: Optionally accept username in lookup/iterate/begin dict protocol
+    commands
+
+    Only an optional field, don't actually use it.
+
+M	src/dict/dict-commands.c
+
+2021-05-28 11:57:39 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (f976f4ac89)
+
+    dict-file: Remove file_dict_iterate_path struct
+
+    No need for the struct since support for multi-path iterate is removed. So
+    path and path length can be merged with file_dict_iterate_context.
+
+M	src/lib-dict/dict-file.c
+
+2021-05-26 13:47:50 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (32e8bd5b5a)
+
+    dict backends: Remove multi-path handling code in dict iteration
+
+    With mutli-path iteration support being dropped from dict protocol, these 
+    can be cleaned up.
+
+M	src/lib-dict-backend/dict-cdb.c
+M	src/lib-dict-backend/dict-sql.c
+M	src/lib-dict-extra/dict-fs.c
+M	src/lib-dict/dict-client.c
+M	src/lib-dict/dict-fail.c
+M	src/lib-dict/dict-file.c
+M	src/lib-dict/dict-private.h
+M	src/lib-dict/dict.c
+
+2021-05-26 12:15:06 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (cbc81b4471)
+
+    dict: Drop support for multi-path iteration in dict protocol
+
+    Remove dict_iterate_init_multiple() since it is not used anywhere. Also drop
+    support in dict protocol to read multiple paths.
+
+M	src/dict/dict-commands.c
+M	src/lib-dict/dict.c
+M	src/lib-dict/dict.h
+
+2021-05-18 01:50:03 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (ecc4985151)
+
+    lib-dict: Add dict_op_settings_dup() and dict_op_settings_private_free()
+
+    - dict_op_settings_dup() creates a copy of dict_op_settings into a new
+     struct dict_dop_settings_private.
+    - dict_op_settings_private_free() frees the memory for copied object.
+
+M	src/lib-dict/dict-private.h
+M	src/lib-dict/dict.c
+
+2021-05-03 15:09:49 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (4aeba6af64)
+
+    lib-dict: Add dict_op_settings parameter for dict_iterate_init()
+
+    Only adds the parameter, doesn't use it.
+
+M	src/auth/userdb-dict.c
+M	src/doveadm/doveadm-dict.c
+M	src/lib-dict-backend/test-dict-sql.c
+M	src/lib-dict/dict-iter-lua.c
+M	src/lib-dict/dict.c
+M	src/lib-dict/dict.h
+M	src/lib-dict/test-dict-client.c
+M	src/lib-fs/fs-dict.c
+M	src/lib-storage/index/index-attribute.c
+M	src/plugins/acl/acl-lookup-dict.c
+
+2021-05-03 15:09:01 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (8c0a16627e)
+
+    lib-dict: Add dict_op_settings parameter for dict_transaction_begin()
+
+    Only adds the parameter, doesn't use it.
+
+M	src/dict/dict-commands.c
+M	src/doveadm/doveadm-dict.c
+M	src/lib-dict-backend/test-dict-sql.c
+M	src/lib-dict-extra/test-dict-fs.c
+M	src/lib-dict/dict-txn-lua.c
+M	src/lib-dict/dict.c
+M	src/lib-dict/dict.h
+M	src/lib-dict/test-dict-client.c
+M	src/lib-fs/fs-dict.c
+M	src/lib-oauth2/test-oauth2-jwt.c
+M	src/lib-storage/index/index-attribute.c
+M	src/plugins/acl/acl-lookup-dict.c
+M	src/plugins/last-login/last-login-plugin.c
+M	src/plugins/notify-status/notify-status-plugin.c
+M	src/plugins/quota-clone/quota-clone-plugin.c
+M	src/plugins/quota/quota-dict.c
+M	src/plugins/quota/quota.c
+
+2021-05-03 13:42:56 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (c840870249)
+
+    lib-dict: Add dict_op_settings
+
+    Used for dict operations. Currently username & home_dir are used. Also,
+    change dict_lookup() to accept a parameter of this type but don't actually
+    use it.
+
+M	src/auth/db-dict.c
+M	src/dict/dict-commands.c
+M	src/doveadm/doveadm-dict.c
+M	src/lib-dict-backend/test-dict-sql.c
+M	src/lib-dict-extra/test-dict-fs.c
+M	src/lib-dict/dict-client.c
+M	src/lib-dict/dict-lua.c
+M	src/lib-dict/dict.c
+M	src/lib-dict/dict.h
+M	src/lib-dict/test-dict-client.c
+M	src/lib-fs/fs-dict.c
+M	src/lib-oauth2/oauth2-jwt.c
+M	src/lib-storage/index/index-attribute.c
+M	src/plugins/quota/quota-dict.c
+
+2021-05-03 12:15:54 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (53fa6616c5)
+
+    lib-dict: Remove dict_settings.value_type
+
+    It was originally added to dict-db and no other backend uses it. With
+    dict-db removed, there is no need to keep it.
+
+M	src/auth/db-oauth2.c
+M	src/dict/dict-connection.c
+M	src/lib-dict/dict-client.c
+M	src/lib-dict/dict.h
+M	src/lib-oauth2/test-oauth2-jwt.c
+
+2021-05-12 12:54:30 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (08fee75254)
+
+    dict-client: Escape username and dict uri in handshake HELLO message
+
+
+M	src/lib-dict/dict-client.c
+
+2021-04-30 14:42:46 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (d5acb577c9)
+
+    lib-dict-backen: Drop unused dict-db
+
+
+M	src/lib-dict-backend/Makefile.am
+D	src/lib-dict-backend/dict-db.c
+
+2021-06-24 12:56:05 -0400 Josef 'Jeff' Sipek <jeff.sipek@open-xchange.com> (5a7c7869c7)
+
+    virtual: Expunge old emails if backend box uidvalidity changed
+
+
+M	src/plugins/virtual/virtual-storage.h
+M	src/plugins/virtual/virtual-sync.c
+
+2021-06-14 23:28:48 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (9ea5d36197)
+
+    doveadm-server: Don't return two error lines if a command fails
+
+    This normally didn't affect doveadm client usage, but it did break reply 
+    matching when multiple usernames were handled by a single command (e.g. 
+    wildcard users).
+
+M	src/doveadm/client-connection-tcp.c
+
+2020-12-10 10:44:42 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (e4ab282d56)
+
+    lib-http: test-http-client-errors - Don't rely on sleeps
+
+    Use the notification signal API instead. Also it's only one test (group) 
+    that needs to do this.
+
+M	src/lib-http/test-http-client-errors.c
+
+2021-06-18 14:19:36 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (ec04bc903e)
+
+    lib-test: Add notification signal send/wait API
+
+    This allows a simple way for child/parent processes to communicate when 
+    they're ready. SIGHUP signal is reserved for this.
+
+M	src/lib-test/test-subprocess.c
+M	src/lib-test/test-subprocess.h
+
+2020-12-10 10:54:25 +0200 Timo Sirainen <timo.sirainen@open-xchange.com> (6192e2a196)
+
+    doveadm batch: Fix assert-crash that happened when it was attempted to be
+    used
+
+    Fixes: Panic: file mail-storage.c: line 1067 (mailbox_set_reason): assertion
+    failed: (reason != NULL)
+
+M	src/doveadm/doveadm-mail-batch.c
+
+2021-06-25 14:52:11 +0200 Markus Valentin <markus.valentin@open-xchange.com> (d2d021efe0)
 
     lib-fs: metawrap - Fix handling empty file
 
@@ -237,13 +8022,27 @@ M	src/imap/imap-fetch-body.c
 
 M	src/lib-fs/fs-metawrap.c
 
-2021-06-25 14:28:57 +0200 Markus Valentin <markus.valentin@open-xchange.com> (8bd4e7fe80)
+2021-06-25 14:28:57 +0200 Markus Valentin <markus.valentin@open-xchange.com> (cab617276a)
 
     lib-fs: fs-metawrap - Expect fs_stat to return size 0 for empty file
 
 
 M	src/lib-fs/test-fs-metawrap.c
 
+2021-06-14 12:47:15 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (f0256e3d60)
+
+    NEWS: Updates for v2.3.15
+
+
+M	NEWS
+
+2021-05-24 14:03:57 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (7ad5753566)
+
+    NEWS: Add news for 2.3.14.1
+
+
+M	NEWS
+
 2021-05-22 00:16:38 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (bcdbf445a9)
 
     lib-smtp: smtp-server-connection - Fix STARTTLS command injection
diff -pruN 1:2.3.16+dfsg1-3/config.h.in 1:2.3.19.1+dfsg1-2/config.h.in
--- 1:2.3.16+dfsg1-3/config.h.in	2021-08-06 09:26:02.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/config.h.in	2022-06-14 06:55:14.000000000 +0000
@@ -72,6 +72,9 @@
 /* Dovecot major version */
 #undef DOVECOT_VERSION_MAJOR
 
+/* Dovecot micro version */
+#undef DOVECOT_VERSION_MICRO
+
 /* Dovecot minor version */
 #undef DOVECOT_VERSION_MINOR
 
diff -pruN 1:2.3.16+dfsg1-3/configure 1:2.3.19.1+dfsg1-2/configure
--- 1:2.3.16+dfsg1-3/configure	2021-08-06 09:26:02.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/configure	2022-06-14 06:55:14.000000000 +0000
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Dovecot 2.3.16.
+# Generated by GNU Autoconf 2.69 for Dovecot 2.3.19.1.
 #
 # Report bugs to <dovecot@dovecot.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='Dovecot'
 PACKAGE_TARNAME='dovecot'
-PACKAGE_VERSION='2.3.16'
-PACKAGE_STRING='Dovecot 2.3.16'
+PACKAGE_VERSION='2.3.19.1'
+PACKAGE_STRING='Dovecot 2.3.19.1'
 PACKAGE_BUGREPORT='dovecot@dovecot.org'
 PACKAGE_URL=''
 
@@ -1577,7 +1577,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures Dovecot 2.3.16 to adapt to many kinds of systems.
+\`configure' configures Dovecot 2.3.19.1 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1648,7 +1648,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of Dovecot 2.3.16:";;
+     short | recursive ) echo "Configuration of Dovecot 2.3.19.1:";;
    esac
   cat <<\_ACEOF
 
@@ -1873,7 +1873,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-Dovecot configure 2.3.16
+Dovecot configure 2.3.19.1
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2646,7 +2646,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by Dovecot $as_me 2.3.16, which was
+It was created by Dovecot $as_me 2.3.19.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2996,7 +2996,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 cat >>confdefs.h <<_ACEOF
-#define DOVECOT_ABI_VERSION "2.3.ABIv16($PACKAGE_VERSION)"
+#define DOVECOT_ABI_VERSION "2.3.ABIv19($PACKAGE_VERSION)"
 _ACEOF
 
 
@@ -3518,7 +3518,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='dovecot'
- VERSION='2.3.16'
+ VERSION='2.3.19.1'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -19634,6 +19634,55 @@ $as_echo "#define DOVECOT_VERSION_MAJOR
 $as_echo "#define DOVECOT_VERSION_MINOR 3" >>confdefs.h
 
 
+$as_echo "#define DOVECOT_VERSION_MICRO 19" >>confdefs.h
+
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+
+#if DOVECOT_VERSION_MICRO > 0
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+
+$as_echo "#define DOVECOT_VERSION_MICRO 0" >>confdefs.h
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+
+#if DOVECOT_VERSION_MAJOR > 0 && DOVECOT_VERSION_MINOR > 0
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  as_fn_error $? "Version macros broken" "$LINENO" 5
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
 for ac_header in strings.h stdint.h unistd.h dirent.h malloc.h \
   sys/uio.h sys/sysmacros.h sys/resource.h sys/select.h libgen.h \
   sys/quota.h sys/fs/ufs_quota.h ufs/ufs/quota.h jfs/quota.h \
@@ -31133,7 +31182,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wri
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by Dovecot $as_me 2.3.16, which was
+This file was extended by Dovecot $as_me 2.3.19.1, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -31199,7 +31248,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-Dovecot config.status 2.3.16
+Dovecot config.status 2.3.19.1
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -pruN 1:2.3.16+dfsg1-3/configure.ac 1:2.3.19.1+dfsg1-2/configure.ac
--- 1:2.3.16+dfsg1-3/configure.ac	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/configure.ac	2022-06-14 06:55:03.000000000 +0000
@@ -2,8 +2,8 @@ AC_PREREQ([2.59])
 
 # Be sure to update ABI version also if anything changes that might require
 # recompiling plugins. Most importantly that means if any structs are changed.
-AC_INIT([Dovecot],[2.3.16],[dovecot@dovecot.org])
-AC_DEFINE_UNQUOTED([DOVECOT_ABI_VERSION], "2.3.ABIv16($PACKAGE_VERSION)", [Dovecot ABI version])
+AC_INIT([Dovecot],[2.3.19.1],[dovecot@dovecot.org])
+AC_DEFINE_UNQUOTED([DOVECOT_ABI_VERSION], "2.3.ABIv19($PACKAGE_VERSION)", [Dovecot ABI version])
 
 AC_CONFIG_SRCDIR([src])
 AC_CONFIG_MACRO_DIR([m4])
@@ -293,6 +293,19 @@ AC_DEFINE_UNQUOTED(DOVECOT_VERSION, "$PA
 
 AC_DEFINE([DOVECOT_VERSION_MAJOR], regexp(AC_PACKAGE_VERSION, [^\([0-9]+\)\.\([0-9]+\)], [\1]), [Dovecot major version])
 AC_DEFINE([DOVECOT_VERSION_MINOR], regexp(AC_PACKAGE_VERSION, [^\([0-9]+\)\.\([0-9]+\)], [\2]), [Dovecot minor version])
+AC_DEFINE([DOVECOT_VERSION_MICRO], regexp(AC_PACKAGE_VERSION, [^\([0-9]+\)\.\([0-9]+\).\([0-9]+\)], [\3]), [Dovecot micro version])
+
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+],[
+#if DOVECOT_VERSION_MICRO > 0
+#endif
+])], [], AC_DEFINE([DOVECOT_VERSION_MICRO], [0], [Dovecot micro version]))
+
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+],[
+#if DOVECOT_VERSION_MAJOR > 0 && DOVECOT_VERSION_MINOR > 0
+#endif
+])], [], AC_MSG_ERROR([Version macros broken]))
 
 AC_CHECK_HEADERS(strings.h stdint.h unistd.h dirent.h malloc.h \
   sys/uio.h sys/sysmacros.h sys/resource.h sys/select.h libgen.h \
diff -pruN 1:2.3.16+dfsg1-3/debian/changelog 1:2.3.19.1+dfsg1-2/debian/changelog
--- 1:2.3.16+dfsg1-3/debian/changelog	2021-09-16 15:41:27.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/changelog	2022-07-30 02:58:28.000000000 +0000
@@ -1,3 +1,72 @@
+dovecot (1:2.3.19.1+dfsg1-2) unstable; urgency=medium
+
+  [ Christian Göttsche ]
+  * [281fb2c] d/patches: cherry-pick fix for CVE-2022-30550 (Closes: #1016351)
+  * [9c58e71] d/patches: fix uninitialized read in doveadm-oldstats
+  * [a76a24d] d/control: bump to standards version 4.6.1 (no further changes)
+  * [4aaaa8b] Update Lintian overrides
+
+ -- Noah Meyerhans <noahm@debian.org>  Fri, 29 Jul 2022 19:58:28 -0700
+
+dovecot (1:2.3.19.1+dfsg1-1) unstable; urgency=medium
+
+  [ Christian Göttsche ]
+  * [e40f93f] d/patches: avoid usage of PATH_MAX not available on hurd
+  * [19e00cd] d/rules: enable backtrace generation
+  * [5bf1c43] d/patches: debug flaky unit test
+
+  [ Noah Meyerhans ]
+  * [b73422f] New upstream version 2.3.19.1+dfsg1
+  * [c88bfc0] Update changelog for 1:2.3.19.1+dfsg1-1 release
+  * [ca59548] Update lintian overrides
+  * [d6406c2] d/copyright: update declarations for current maintainers
+
+ -- Noah Meyerhans <noahm@debian.org>  Wed, 22 Jun 2022 09:27:01 -0700
+
+dovecot (1:2.3.19+dfsg1-1) unstable; urgency=medium
+
+  [ Christian Göttsche ]
+  * [0d29e45] d/rules: enable LTO via DEB_BUILD_MAINT_OPTIONS instead of custom flags
+  * [560cceb] d/source/lintian-overrides: update very-long-line-length-in-source-file overrides
+  * [b99d09e] d/copyright: update years
+  * [9ee8271] d/dovecot-core.prerm: drop as superseded by debhelper
+  * [907f85c] d/maintscripts: update
+  * [2b38240] d/dovecot-core.postinst: drop support for version skips
+  * [dcb76d1] d/dovecot-core.postinst: only link certs if existent (Closes: #1009872)
+  * [d223bbd] d/patches: add patch to support openssl 3.0 (Closes: #996273)
+
+  [ Noah Meyerhans ]
+  * [9f3175e] New upstream version 2.3.19+dfsg1
+
+ -- Noah Meyerhans <noahm@debian.org>  Sun, 05 Jun 2022 18:29:18 +0000
+
+dovecot (1:2.3.18+dfsg1-1) unstable; urgency=medium
+
+  [ Noah Meyerhans ]
+  * [36966c8] New upstream version 2.3.18+dfsg1
+  * [042bda4] Refresh patches for 1:2.3.18+dfsg1-1
+
+ -- "Noah Meyerhans" <noahm@debian.org>  Thu, 10 Feb 2022 20:05:50 +0000
+
+dovecot (1:2.3.17.1+dfsg1-1) unstable; urgency=medium
+
+  [ Christian Göttsche ]
+  * [40b0010] New upstream version 2.3.17+dfsg1
+  * [3c377e0] New upstream version 2.3.17.1+dfsg1
+  * [e2f1ce2] d/patches: rebase and drop upstream applied ones
+  * [533b7ad] d/control: bump to standards version 4.6.0 (no further changes)
+  * [02ed6cf] debian: reduce Lintian issues
+  * [bb3ae48] d/salsa-ci.yml: skip cross build and do not fail on Lintian
+    warnings
+  * [bcda7e4] d/control: build against Lua 5.4
+  * [9eed0dd] d/control: enable libunwind support on available archs
+  * [1990699] d/patches: cherry-pick memory leak commit
+  * [426df46] d/patches: cherry-pick imapsieve fix
+  * [e3d0747] d/patches: add patch for LTO by avoiding unaligned access
+    (Closes: #997513)
+
+ -- Noah Meyerhans <noahm@debian.org>  Tue, 14 Dec 2021 09:24:23 -0800
+
 dovecot (1:2.3.16+dfsg1-3) unstable; urgency=medium
 
   * [7b858b6] Fix FTBFS on mips(64)el.  Stacktrace generation on these
diff -pruN 1:2.3.16+dfsg1-3/debian/control 1:2.3.19.1+dfsg1-2/debian/control
--- 1:2.3.16+dfsg1-3/debian/control	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/control	2022-07-30 02:58:28.000000000 +0000
@@ -18,7 +18,7 @@ Build-Depends: debhelper-compat (= 13),
                libexttextcat-dev,
                libicu-dev,
                libldap2-dev,
-               liblua5.3-dev,
+               liblua5.4-dev,
                liblz4-dev,
                liblzma-dev,
                libpam0g-dev,
@@ -29,12 +29,13 @@ Build-Depends: debhelper-compat (= 13),
                libssl-dev,
                libstemmer-dev,
                libsystemd-dev [linux-any],
+               libunwind-dev [amd64 arm64 armel armhf hppa i386 ia64 mips mips64 mips64el mipsel powerpc powerpcspe ppc64 ppc64el sh4],
                libwrap0-dev,
                libzstd-dev,
                lsb-release,
                pkg-config,
                zlib1g-dev
-Standards-Version: 4.5.1
+Standards-Version: 4.6.1
 Rules-Requires-Root: binary-targets
 Homepage: https://dovecot.org/
 Vcs-Git: https://salsa.debian.org/debian/dovecot.git
diff -pruN 1:2.3.16+dfsg1-3/debian/copyright 1:2.3.19.1+dfsg1-2/debian/copyright
--- 1:2.3.16+dfsg1-3/debian/copyright	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/copyright	2022-07-30 02:58:28.000000000 +0000
@@ -8,13 +8,13 @@ Source: https://github.com/dovecot/core
 Files-Excluded-pigeonhole: doc/rfc
 
 Files: *
-Copyright: (c) 2001-2018 Dovecot authors
+Copyright: (c) 2001-2022 Dovecot authors
 Comment: see AUTHORS
 License: LGPL-2.1
 
 Files: src/lib/*
        src/lib-sql/*
-Copyright: (c) 2001-2018 Dovecot authors
+Copyright: (c) 2001-2022 Dovecot authors
 Comment: see AUTHORS
 License: MIT
 
@@ -105,11 +105,13 @@ Copyright: (c) 2006-2016 Jaldhar H. Vyas
            (c) 2011-2013 Micah Anderson <micah@debian.org>
            (c) 2014-2015 Jelmer Vernooij <jelmer@debian.org>
            (c) 2016-2018 Apollon Oikonomopoulos <apoikos@debian.org>
+	   (c) 2020-2022 Noah Meyerhans <noahm@debian.org>
+	   (c) 2020-2022 Christian Göttsche <cgzones@googlemail.com>
 License: GPL-2+
 
 Files: pigeonhole/*
-Copyright: (c) 2002-2018 Stephan Bosch <stephan@rename-it.nl>
-           (c) 2002-2018 Dovecot authors
+Copyright: (c) 2002-2021 Stephan Bosch <stephan@rename-it.nl>
+           (c) 2002-2021 Dovecot authors
 Comment: Source obtained from https://pigeonhole.dovecot.org/
 License: LGPL-2.1
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-core.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-core.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-core.lintian-overrides	2021-09-16 15:41:27.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-core.lintian-overrides	2022-07-30 02:58:28.000000000 +0000
@@ -1,115 +1,74 @@
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/auth
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/config
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/director
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/gdbhelper
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/libdovecot-login.so.0.0.0
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/modules/lib10_quota_plugin.so
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/modules/lib20_replication_plugin.so
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/quota-status
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/script
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/script-login
-dovecot-core: package-contains-empty-directory usr/share/dovecot/protocols.d/
-dovecot-core: package-contains-empty-directory usr/lib/dovecot/modules/dict/
-dovecot-core: spelling-error-in-readme-debian dovenull dovenull (duplicate word) dovenull
-dovecot-core: library-not-linked-against-libc usr/lib/dovecot/modules/old-stats/libold_stats_mail.so
-dovecot-core: library-not-linked-against-libc usr/lib/dovecot/modules/old-stats/libstats_auth.so
+# hardening flags are set and blhc succeeds; might need some investigation
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/auth]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/config]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/director]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/gdbhelper]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/libdovecot-login.so.0.0.0]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/libdovecot-storage.so.0.0.0]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/modules/lib10_quota_plugin.so]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/modules/lib20_fts_plugin.so]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/modules/lib20_replication_plugin.so]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/modules/lib99_welcome_plugin.so]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/quota-status]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/script-login]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/script]
+dovecot-core: hardening-no-fortify-functions [usr/lib/dovecot/xml2text]
+dovecot-core: package-contains-empty-directory [usr/share/dovecot/protocols.d/]
+dovecot-core: package-contains-empty-directory [usr/lib/dovecot/modules/dict/]
+dovecot-core: spelling-error-in-readme-debian dovenull dovenull (duplicate word) dovenull [usr/share/doc/dovecot-core/README.Debian]
+dovecot-core: library-not-linked-against-libc [usr/lib/dovecot/modules/old-stats/libold_stats_mail.so]
+dovecot-core: library-not-linked-against-libc [usr/lib/dovecot/modules/old-stats/libstats_auth.so]
 dovecot-core [armel]: library-not-linked-against-libc usr/lib/dovecot/modules/lib20_listescape_plugin.so
 # ignore internal libraries without dependency information
-dovecot-core: shared-library-lacks-prerequisites usr/lib/dovecot/modules/lib20_listescape_plugin.so
-dovecot-core: shared-library-lacks-prerequisites usr/lib/dovecot/modules/settings/libpigeonhole_settings.so
-# this is a nasty hack, hopefully to be removed at some point (see #696382)
-dovecot-core: uses-dpkg-database-directly postinst
-# hardening flags are set and blhc succeeds; might need some investigation
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/libdovecot-storage.so.0.0.0
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/modules/lib20_fts_plugin.so
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/modules/lib99_welcome_plugin.so
-dovecot-core: hardening-no-fortify-functions usr/lib/dovecot/xml2text
+dovecot-core: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/lib20_listescape_plugin.so]
+dovecot-core: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/settings/libpigeonhole_settings.so]
 # ignore extra man pages
-dovecot-core: spare-manual-page usr/share/man/man1/deliver.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-acl.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-altmove.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-auth.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-backup.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-batch.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-config.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-copy.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-deduplicate.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-director.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-dump.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-exec.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-expunge.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-fetch.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-flags.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-force-resync.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-fs.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-fts.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-help.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-import.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-index.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-instance.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-kick.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-log.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-mailbox-cryptokey.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-mailbox.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-move.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-penalty.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-proxy.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-purge.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-pw.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-quota.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-rebuild.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-reload.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-replicator.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-save.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-search.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-sieve.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-stats.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-stop.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-sync.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-user.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/doveadm-who.1.gz
-dovecot-core: spare-manual-page usr/share/man/man1/dovecot-lda.1.gz
+dovecot-core: spare-manual-page [usr/share/man/man1/deliver.1.gz]
+# keep all man pages in section 1 for now
+dovecot-core: manual-page-for-system-command [usr/sbin/dovecot]
 # ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/aggregator
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/anvil
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/auth
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/checkpassword-reply
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/config
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/dict
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/director
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/dns-client
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/doveadm-server
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/dovecot-lda
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/gdbhelper
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/health-check.sh
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/indexer
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/indexer-worker
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/ipc
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/log
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/maildirlock
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/old-stats
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/quota-status
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/rawlog
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/replicator
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/script
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/script-login
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/stats
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/tcpwrap
-dovecot-core: executable-in-usr-lib usr/lib/dovecot/xml2text
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/aggregator]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/anvil]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/auth]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/checkpassword-reply]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/config]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dict]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/director]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dns-client]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/doveadm-server]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/dovecot-lda]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/gdbhelper]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/health-check.sh]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/indexer]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/indexer-worker]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/ipc]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/log]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/maildirlock]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/old-stats]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/quota-status]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/rawlog]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/replicator]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/script]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/script-login]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/stats]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/tcpwrap]
+dovecot-core: executable-in-usr-lib [usr/lib/dovecot/xml2text]
 # ignore equal stub files
 dovecot-core: duplicate-files usr/share/doc/dovecot-core/wiki/Pigeonhole.ManageSieve.Install.txt usr/share/doc/dovecot-core/wiki/Plugins.Stats.txt
 # ignore stopwords txt files not in /usr/share/doc
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_da.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_de.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_en.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_es.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_fi.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_fr.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_it.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_nl.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_no.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_pt.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_ro.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_ru.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_sv.txt
-dovecot-core: package-contains-documentation-outside-usr-share-doc usr/share/dovecot/stopwords/stopwords_tr.txt
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_da.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_de.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_en.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_es.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_fi.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_fr.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_it.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_nl.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_no.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_pt.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_ro.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_ru.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_sv.txt]
+dovecot-core: package-contains-documentation-outside-usr-share-doc [usr/share/dovecot/stopwords/stopwords_tr.txt]
+# ulimit -c might be specified but is supported by bash and dash
+dovecot-core: bash-term-in-posix-shell '| ulimit' [etc/init.d/dovecot:43]
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-core.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-core.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-core.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-core.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -42,20 +42,20 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-core /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-core "/etc/dovecot/$conffile"
     if [ "$conffile" != "dovecot.conf" ] && [ -f "/etc/dovecot/$conffile" ] &&
-		[ `echo $conffile | cut -b -7` != "conf.d/" ]; then
-      chmod 0640 /etc/dovecot/$conffile
-      chgrp dovecot /etc/dovecot/$conffile
+		[ "$(echo "$conffile" | cut -b -7)" != "conf.d/" ]; then
+      chmod 0640 "/etc/dovecot/$conffile"
+      chgrp dovecot "/etc/dovecot/$conffile"
     fi
   done
 
   for oldconffile in $OLD_CONFFILES ; do
-    if [ -e /etc/dovecot/$oldconffile ]; then
+    if [ -e "/etc/dovecot/$oldconffile" ]; then
       echo "Configuration file '/etc/dovecot/$oldconffile' is obsolete. Please remove."
-      ucf --purge /etc/dovecot/$conffile
-      ucfr --purge dovecot-core /etc/dovecot/$conffile
+      ucf --purge "/etc/dovecot/$conffile"
+      ucfr --purge dovecot-core "/etc/dovecot/$conffile"
     fi
   done
 
@@ -67,21 +67,15 @@ if [ "$1" = "configure" ]; then
   # SSL configuration
   # Use the ssl-cert-snakeoil certificate in the following cases:
   # - On new installations
-  # - On upgrades from versions that did not enable SSL by default
-  if [ -z "$2" ] || dpkg --compare-versions "$2" lt "1:2.2.31-1~"; then
+  if [ -z "$2" ]; then
     if [ ! -e /etc/dovecot/private/dovecot.key ] && \
-       [ ! -e /etc/dovecot/private/dovecot.pem ]; then
+       [ ! -e /etc/dovecot/private/dovecot.pem ] && \
+       [ -e /etc/ssl/certs/ssl-cert-snakeoil.pem ] && \
+       [ -e /etc/ssl/private/ssl-cert-snakeoil.key ]; then
       ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/dovecot/private/dovecot.pem
       ln -s /etc/ssl/private/ssl-cert-snakeoil.key /etc/dovecot/private/dovecot.key
     fi
   fi
-
-  # Remove the old dovecot-common.postrm (if it's still around for some
-  # reason), as purging dovecot-common will remove /etc/dovecot/dovecot.conf.
-  # See #696382.
-  if [ -f "/var/lib/dpkg/info/dovecot-common.postrm" ]; then
-    rm -f /var/lib/dpkg/info/dovecot-common.postrm
-  fi
 fi
 
 if [ "$1" = "triggered" ]; then
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-core.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-core.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-core.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-core.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,28 +2,30 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-    for conffile in `ucfq --with-colons dovecot-core | cut -d: -f1`; do
+    for conffile in $(ucfq --with-colons dovecot-core | cut -d: -f1); do
         # we mimic dpkg as closely as possible, so we remove configuration
         # files with dpkg backup extensions too:
         ### Some of the following is from Tore Anderson:
         for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-            rm -f $conffile$ext
+            rm -f "$conffile$ext"
         done
         # remove the configuration file itself
-        rm -f $conffile
+        rm -f "$conffile"
         # and finally clear it out from the ucf database
         if which ucf >/dev/null; then
-            ucf --purge $conffile
+            ucf --purge "$conffile"
         fi
         if which ucfr >/dev/null; then
-            ucfr --purge dovecot-core $conffile
+            ucfr --purge dovecot-core "$conffile"
         fi
     done
 
     userdel dovecot || true;
     userdel dovenull || true;
 
-    if [ -d /var/run/dovecot ]; then rm -rf /var/run/dovecot; fi
+    if [ -d /run/dovecot ]; then
+        rm -rf /run/dovecot
+    fi
 
     # Remove dovecot.pem and dovecot.key only if they are symlinks; otherwise
     # we might remove CA-issued certificates that are difficult and/or
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-core.prerm 1:2.3.19.1+dfsg1-2/debian/dovecot-core.prerm
--- 1:2.3.16+dfsg1-3/debian/dovecot-core.prerm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-core.prerm	1970-01-01 00:00:00.000000000 +0000
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-set -e
-
-if [ -d /run/systemd/system ]; then
-	deb-systemd-invoke stop dovecot.socket || true
-fi
-
-#DEBHELPER#
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-imapd.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-imapd.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.lintian-overrides	1970-01-01 00:00:00.000000000 +0000
@@ -1,9 +0,0 @@
-# hardening flags are set and blhc succeeds; might need some investigation
-dovecot-imapd: hardening-no-fortify-functions usr/lib/dovecot/imap
-# ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap-hibernate
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap-login
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap-urlauth
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap-urlauth-login
-dovecot-imapd: executable-in-usr-lib usr/lib/dovecot/imap-urlauth-worker
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-imapd.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-imapd.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -9,8 +9,8 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-imapd /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-imapd "/etc/dovecot/$conffile"
   done
 
   echo 'protocols = $protocols imap' > /usr/share/dovecot/protocols.d/imapd.protocol
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-imapd.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-imapd.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-imapd.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-imapd | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-imapd | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-imapd $conffile
+			ucfr --purge dovecot-imapd "$conffile"
 		fi
 	done
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-ldap.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-ldap.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-ldap.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-ldap.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -10,11 +10,11 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-ldap /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-ldap "/etc/dovecot/$conffile"
     if [ "$conffile" != "dovecot.conf" ] && [ -f "/etc/dovecot/$conffile" ] &&
-		[ `echo $conffile | cut -b -7` != "conf.d/" ]; then
-      chmod 0600 /etc/dovecot/$conffile
+		[ "$(echo "$conffile" | cut -b -7)" != "conf.d/" ]; then
+      chmod 0600 "/etc/dovecot/$conffile"
     fi
   done
 fi
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-ldap.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-ldap.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-ldap.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-ldap.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -10,16 +10,16 @@ if [ "$1" = "purge" ] ; then
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-ldap $conffile
+			ucfr --purge dovecot-ldap "$conffile"
 		fi
 	done
 fi
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.lintian-overrides	1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-# ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-lmtpd: executable-in-usr-lib usr/lib/dovecot/lmtp
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -9,8 +9,8 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-lmtpd /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-lmtpd "/etc/dovecot/$conffile"
   done
 
   echo 'protocols = $protocols lmtp' > /usr/share/dovecot/protocols.d/lmtpd.protocol
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-lmtpd.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-lmtpd.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-lmtpd | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-lmtpd | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-lmtpd $conffile
+			ucfr --purge dovecot-lmtpd "$conffile"
 		fi
 	done
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.lintian-overrides	2022-07-30 02:58:28.000000000 +0000
@@ -1,7 +1 @@
-dovecot-managesieved: hardening-no-fortify-functions usr/lib/dovecot/modules/settings/libmanagesieve_login_settings.so
-# ignore internal library without dependency information
-dovecot-managesieved: shared-library-lacks-prerequisites usr/lib/dovecot/modules/settings/libmanagesieve_settings.so
-# ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-managesieved: executable-in-usr-lib usr/lib/dovecot/managesieve
-dovecot-managesieved: executable-in-usr-lib usr/lib/dovecot/managesieve-login
-dovecot-managesieved [armel]: library-not-linked-against-libc usr/lib/dovecot/modules/settings/libmanagesieve_settings.so
+dovecot-managesieved: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/settings/libmanagesieve_settings.so]
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -9,8 +9,8 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-managesieved /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-managesieved "/etc/dovecot/$conffile"
   done
 
   echo 'protocols = $protocols sieve' > /usr/share/dovecot/protocols.d/managesieved.protocol
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-managesieved.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-managesieved.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-managesieved | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-managesieved | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-managesieved $conffile
+			ucfr --purge dovecot-managesieved "$conffile"
 		fi
 	done
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.lintian-overrides	1970-01-01 00:00:00.000000000 +0000
@@ -1,3 +0,0 @@
-# ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-pop3d: executable-in-usr-lib usr/lib/dovecot/pop3
-dovecot-pop3d: executable-in-usr-lib usr/lib/dovecot/pop3-login
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -9,8 +9,8 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-pop3d /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-pop3d "/etc/dovecot/$conffile"
   done
 
   echo 'protocols = $protocols pop3' > /usr/share/dovecot/protocols.d/pop3d.protocol
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-pop3d.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-pop3d.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-pop3d | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-pop3d | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-pop3d $conffile
+			ucfr --purge dovecot-pop3d "$conffile"
 		fi
 	done
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-sieve.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-sieve.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.lintian-overrides	2022-07-30 02:58:28.000000000 +0000
@@ -1,5 +1,4 @@
 # ignore internal library without dependency information
-dovecot-sieve: shared-library-lacks-prerequisites usr/lib/dovecot/modules/sieve/lib90_sieve_imapsieve_plugin.so
-# ignore extra man page
-dovecot-sieve: spare-manual-page usr/share/man/man1/sieved.1.gz
-dovecot-sieve [armel]: library-not-linked-against-libc usr/lib/dovecot/modules/sieve/lib90_sieve_imapsieve_plugin.so
+dovecot-sieve: shared-library-lacks-prerequisites [usr/lib/dovecot/modules/sieve/lib90_sieve_imapsieve_plugin.so]
+# alias for sieve-dump
+dovecot-sieve: spare-manual-page [usr/share/man/man1/sieved.1.gz]
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-sieve.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-sieve.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -10,8 +10,8 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
-    ucfr dovecot-sieve /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
+    ucfr dovecot-sieve "/etc/dovecot/$conffile"
   done
 fi
 
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-sieve.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-sieve.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-sieve.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-sieve | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-sieve | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-sieve $conffile
+			ucfr --purge dovecot-sieve "$conffile"
 		fi
 	done
 fi
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.lintian-overrides 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.lintian-overrides	1970-01-01 00:00:00.000000000 +0000
@@ -1,3 +0,0 @@
-# ignore executable-in-usr-lib, maybe move to libexec at some point
-dovecot-submissiond: executable-in-usr-lib usr/lib/dovecot/submission
-dovecot-submissiond: executable-in-usr-lib usr/lib/dovecot/submission-login
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.postinst 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.postinst
--- 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.postinst	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.postinst	2022-07-30 02:58:28.000000000 +0000
@@ -9,9 +9,9 @@ if [ "$1" = "configure" ]; then
     # Tell ucf that the file in /usr/share/dovecot is the latest
     # maintainer version, and let it handle how to manage the real
     # configuration file in /etc/dovecot.
-    ucf --three-way /usr/share/dovecot/$conffile /etc/dovecot/$conffile
+    ucf --three-way "/usr/share/dovecot/$conffile" "/etc/dovecot/$conffile"
     # Use --force to allow hijacking the file from dovecot-submission
-    ucfr --force dovecot-submissiond /etc/dovecot/$conffile
+    ucfr --force dovecot-submissiond "/etc/dovecot/$conffile"
   done
 
   if [ -f /usr/share/dovecot/protocols.d/submission.protocol ]; then
diff -pruN 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.postrm 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.postrm
--- 1:2.3.16+dfsg1-3/debian/dovecot-submissiond.postrm	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/dovecot-submissiond.postrm	2022-07-30 02:58:28.000000000 +0000
@@ -2,21 +2,21 @@
 set -e
 
 if [ "$1" = "purge" ] ; then
-	for conffile in `ucfq --with-colons dovecot-submissiond | cut -d: -f1`; do
+	for conffile in $(ucfq --with-colons dovecot-submissiond | cut -d: -f1); do
 		# we mimic dpkg as closely as possible, so we remove configuration
 		# files with dpkg backup extensions too:
 		### Some of the following is from Tore Anderson:
 		for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist .ucf-new .ucf-old .ucf-dist;  do
-			rm -f $conffile$ext
+			rm -f "$conffile$ext"
 		done
 		# remove the configuration file itself
-		rm -f $conffile
+		rm -f "$conffile"
 		# and finally clear it out from the ucf database
 		if which ucf >/dev/null; then
-			ucf --purge $conffile
+			ucf --purge "$conffile"
 		fi
 		if which ucfr >/dev/null; then
-			ucfr --purge dovecot-submissiond $conffile
+			ucfr --purge dovecot-submissiond "$conffile"
 		fi
 	done
 
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/auth-Add-a-comment-about-updating-userdb_find.patch 1:2.3.19.1+dfsg1-2/debian/patches/auth-Add-a-comment-about-updating-userdb_find.patch
--- 1:2.3.16+dfsg1-3/debian/patches/auth-Add-a-comment-about-updating-userdb_find.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/auth-Add-a-comment-about-updating-userdb_find.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,22 @@
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 16 May 2022 14:58:45 +0200
+Subject: auth: Add a comment about updating userdb_find()
+
+---
+ src/auth/userdb.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/auth/userdb.c b/src/auth/userdb.c
+index 21751f9..c71a479 100644
+--- a/src/auth/userdb.c
++++ b/src/auth/userdb.c
+@@ -158,7 +158,8 @@ userdb_preinit(pool_t pool, const struct auth_userdb_settings *set)
+ 	userdb->id = ++auth_userdb_id;
+ 	userdb->iface = iface;
+ 	userdb->args = p_strdup(pool, set->args);
+-
++	/* NOTE: if anything else than driver & args are added here,
++	   userdb_find() also needs to be updated. */
+ 	array_push_back(&userdb_modules, &userdb);
+ 	return userdb;
+ }
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch 1:2.3.19.1+dfsg1-2/debian/patches/auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch
--- 1:2.3.16+dfsg1-3/debian/patches/auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,130 @@
+From: Timo Sirainen <timo.sirainen@open-xchange.com>
+Date: Mon, 9 May 2022 15:23:33 +0300
+Subject: auth: Fix handling passdbs with identical driver/args but different
+ mechanisms/username_filter
+
+The passdb was wrongly deduplicated in this situation, causing wrong
+mechanisms or username_filter setting to be used. This would be a rather
+unlikely configuration though.
+
+Fixed by moving mechanisms and username_filter from struct passdb_module
+to struct auth_passdb, which is where they should have been in the first
+place.
+---
+ src/auth/auth-request.c |  6 +++---
+ src/auth/auth.c         | 18 ++++++++++++++++++
+ src/auth/auth.h         |  5 +++++
+ src/auth/passdb.c       | 15 ++-------------
+ src/auth/passdb.h       |  4 ----
+ 5 files changed, 28 insertions(+), 20 deletions(-)
+
+diff --git a/src/auth/auth-request.c b/src/auth/auth-request.c
+index ee89e75..cd44cd4 100644
+--- a/src/auth/auth-request.c
++++ b/src/auth/auth-request.c
+@@ -553,8 +553,8 @@ auth_request_want_skip_passdb(struct auth_request *request,
+ 			      struct auth_passdb *passdb)
+ {
+ 	/* if mechanism is not supported, skip */
+-	const char *const *mechs = passdb->passdb->mechanisms;
+-	const char *const *username_filter = passdb->passdb->username_filter;
++	const char *const *mechs = passdb->mechanisms;
++	const char *const *username_filter = passdb->username_filter;
+ 	const char *username;
+ 
+ 	username = request->fields.user;
+@@ -567,7 +567,7 @@ auth_request_want_skip_passdb(struct auth_request *request,
+ 		return TRUE;
+ 	}
+ 
+-	if (passdb->passdb->username_filter != NULL &&
++	if (passdb->username_filter != NULL &&
+ 	    !auth_request_username_accepted(username_filter, username)) {
+ 		auth_request_log_debug(request,
+ 				       request->mech != NULL ? AUTH_SUBSYS_MECH
+diff --git a/src/auth/auth.c b/src/auth/auth.c
+index 845c43c..a5a4c81 100644
+--- a/src/auth/auth.c
++++ b/src/auth/auth.c
+@@ -93,6 +93,24 @@ auth_passdb_preinit(struct auth *auth, const struct auth_passdb_settings *set,
+ 	auth_passdb->override_fields_tmpl =
+ 		passdb_template_build(auth->pool, set->override_fields);
+ 
++	if (*set->mechanisms == '\0') {
++		auth_passdb->mechanisms = NULL;
++	} else if (strcasecmp(set->mechanisms, "none") == 0) {
++		auth_passdb->mechanisms = (const char *const[]){ NULL };
++	} else {
++		auth_passdb->mechanisms =
++			(const char *const *)p_strsplit_spaces(auth->pool,
++				set->mechanisms, " ,");
++	}
++
++	if (*set->username_filter == '\0') {
++		auth_passdb->username_filter = NULL;
++	} else {
++		auth_passdb->username_filter =
++			(const char *const *)p_strsplit_spaces(auth->pool,
++				set->username_filter, " ,");
++	}
++
+ 	/* for backwards compatibility: */
+ 	if (set->pass)
+ 		auth_passdb->result_success = AUTH_DB_RULE_CONTINUE;
+diff --git a/src/auth/auth.h b/src/auth/auth.h
+index 3ca5a9b..6208e4d 100644
+--- a/src/auth/auth.h
++++ b/src/auth/auth.h
+@@ -41,6 +41,11 @@ struct auth_passdb {
+ 	struct passdb_template *default_fields_tmpl;
+ 	struct passdb_template *override_fields_tmpl;
+ 
++	/* Supported authentication mechanisms, NULL is all, {NULL} is none */
++	const char *const *mechanisms;
++	/* Username filter, NULL is no filter */
++	const char *const *username_filter;
++
+ 	enum auth_passdb_skip skip;
+ 	enum auth_db_rule result_success;
+ 	enum auth_db_rule result_failure;
+diff --git a/src/auth/passdb.c b/src/auth/passdb.c
+index 9bc2b87..d3c61cc 100644
+--- a/src/auth/passdb.c
++++ b/src/auth/passdb.c
+@@ -224,19 +224,8 @@ passdb_preinit(pool_t pool, const struct auth_passdb_settings *set)
+ 	passdb->id = ++auth_passdb_id;
+ 	passdb->iface = *iface;
+ 	passdb->args = p_strdup(pool, set->args);
+-	if (*set->mechanisms == '\0') {
+-		passdb->mechanisms = NULL;
+-	} else if (strcasecmp(set->mechanisms, "none") == 0) {
+-		passdb->mechanisms = (const char *const[]){NULL};
+-	} else {
+-		passdb->mechanisms = (const char* const*)p_strsplit_spaces(pool, set->mechanisms, " ,");
+-	}
+-
+-	if (*set->username_filter == '\0') {
+-		passdb->username_filter = NULL;
+-	} else {
+-		passdb->username_filter = (const char* const*)p_strsplit_spaces(pool, set->username_filter, " ,");
+-	}
++	/* NOTE: if anything else than driver & args are added here,
++	   passdb_find() also needs to be updated. */
+ 	array_push_back(&passdb_modules, &passdb);
+ 	return passdb;
+ }
+diff --git a/src/auth/passdb.h b/src/auth/passdb.h
+index b405aa7..8f50050 100644
+--- a/src/auth/passdb.h
++++ b/src/auth/passdb.h
+@@ -63,10 +63,6 @@ struct passdb_module {
+ 	/* Default password scheme for this module.
+ 	   If default_cache_key is set, must not be NULL. */
+ 	const char *default_pass_scheme;
+-	/* Supported authentication mechanisms, NULL is all, [NULL] is none*/
+-	const char *const *mechanisms;
+-	/* Username filter, NULL is no filter */
+-	const char *const *username_filter;
+ 
+ 	/* If blocking is set to TRUE, use child processes to access
+ 	   this passdb. */
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Avoid-usage-of-PATH_MAX-not-available-on-hurd.patch 1:2.3.19.1+dfsg1-2/debian/patches/Avoid-usage-of-PATH_MAX-not-available-on-hurd.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Avoid-usage-of-PATH_MAX-not-available-on-hurd.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Avoid-usage-of-PATH_MAX-not-available-on-hurd.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,21 @@
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Mon, 6 Jun 2022 15:29:38 +0200
+Subject: Avoid usage of PATH_MAX not available on hurd
+
+---
+ src/lib/test-net.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/lib/test-net.c b/src/lib/test-net.c
+index fb19d5b..28398fe 100644
+--- a/src/lib/test-net.c
++++ b/src/lib/test-net.c
+@@ -145,7 +145,7 @@ static void test_net_unix_long_paths(void)
+ 
+ 	test_begin("net_*_unix() - long paths");
+ 
+-	char path[PATH_MAX];
++	char path[4096];
+ 	memset(path, 'x', sizeof(path)-1);
+ 	path[sizeof(path)-1] = '\0';
+ 
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Correct-misspellings.patch 1:2.3.19.1+dfsg1-2/debian/patches/Correct-misspellings.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Correct-misspellings.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Correct-misspellings.patch	2022-07-30 02:58:28.000000000 +0000
@@ -14,11 +14,11 @@ Found by Lintian
  src/lib-storage/index/index-sync-pvt.c        | 2 +-
  8 files changed, 10 insertions(+), 10 deletions(-)
 
-diff --git a/ChangeLog b/ChangeLog
-index a737cb3..e0de15f 100644
---- a/ChangeLog
-+++ b/ChangeLog
-@@ -63898,7 +63898,7 @@ M	src/lib-storage/index/index-mail.c
+Index: dovecot/ChangeLog
+===================================================================
+--- dovecot.orig/ChangeLog
++++ dovecot/ChangeLog
+@@ -70625,7 +70625,7 @@ M	src/lib-storage/index/index-mail.c
      lib-index: Add mail_cache_close_mail() to smartly drop cached data with
      INDEX=MEMORY
  
@@ -27,7 +27,7 @@ index a737cb3..e0de15f 100644
      just drop mails have have been marked closed with mail_cache_close_mail(). 
      If that's not enough, continue deleting forcibly until the buffer is below 
      256 kB.
-@@ -83798,7 +83798,7 @@ M	src/lib-storage/index/dbox-single/sdbox-storage.c
+@@ -90525,7 +90525,7 @@ M	src/lib-storage/index/dbox-single/sdbo
  
  2016-09-21 20:50:24 +0300 Timo Sirainen <timo.sirainen@dovecot.fi> (2e5e2047af)
  
@@ -36,7 +36,7 @@ index a737cb3..e0de15f 100644
  
      Broken by c8eb8314a, which moved adding num_waiting_connections earlier. 
      After that it was never 0 at the check time.
-@@ -100740,7 +100740,7 @@ M	src/login-common/ssl-proxy-openssl.c
+@@ -107467,7 +107467,7 @@ M	src/login-common/ssl-proxy-openssl.c
  
  2015-12-07 11:29:29 +0200 Timo Sirainen <tss@iki.fi> (975c2cdc1b)
  
@@ -45,11 +45,11 @@ index a737cb3..e0de15f 100644
      commands in a single process. This means commands run with doveadm batch or
      multiple commands in a single doveadm-server connection.
  
-diff --git a/NEWS b/NEWS
-index d02850a..3b04c98 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1802,7 +1802,7 @@ v2.2.26 2016-10-27  Timo Sirainen <tss@iki.fi>
+Index: dovecot/NEWS
+===================================================================
+--- dovecot.orig/NEWS
++++ dovecot/NEWS
+@@ -2010,7 +2010,7 @@ v2.2.26 2016-10-27  Timo Sirainen <tss@i
  	- zlib, IMAP BINARY: Fixed internal caching when accessing multiple
  	  newly created mails. They all had UID=0 and the next mail could have
  	  wrongly used the previously cached mail.
@@ -58,11 +58,11 @@ index d02850a..3b04c98 100644
  	- auth_stats=yes: Don't update num_logins, since it doubles them when
  	  using with mail stats.
  	- quota count: Fixed deadlocks when updating vsize header.
-diff --git a/pigeonhole/src/lib-sieve/sieve-address.c b/pigeonhole/src/lib-sieve/sieve-address.c
-index f9456f8..c0d982a 100644
---- a/pigeonhole/src/lib-sieve/sieve-address.c
-+++ b/pigeonhole/src/lib-sieve/sieve-address.c
-@@ -449,7 +449,7 @@ parse_mailbox_address(struct sieve_message_address_parser *ctx,
+Index: dovecot/pigeonhole/src/lib-sieve/sieve-address.c
+===================================================================
+--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-address.c
++++ dovecot/pigeonhole/src/lib-sieve/sieve-address.c
+@@ -449,7 +449,7 @@ parse_mailbox_address(struct sieve_messa
  	if (ctx->parser.data != ctx->parser.end) {
  		if (*ctx->parser.data == ',') {
  			sieve_address_error(
@@ -71,11 +71,11 @@ index f9456f8..c0d982a 100644
  		} else {
  			sieve_address_error(
  				ctx, "address ends in invalid characters");
-diff --git a/src/director/director-connection.c b/src/director/director-connection.c
-index 72211bd..14a1c5d 100644
---- a/src/director/director-connection.c
-+++ b/src/director/director-connection.c
-@@ -893,7 +893,7 @@ static bool director_cmd_director(struct director_connection *conn,
+Index: dovecot/src/director/director-connection.c
+===================================================================
+--- dovecot.orig/src/director/director-connection.c
++++ dovecot/src/director/director-connection.c
+@@ -896,7 +896,7 @@ static bool director_cmd_director(struct
  
  		/* already have this. just reset its last_network_failure
  		   timestamp, since it might be up now, but only if this
@@ -84,11 +84,11 @@ index 72211bd..14a1c5d 100644
  		   timestamp could cause us to rapidly keep trying to connect
  		   to it) */
  		if (conn->handshake_received)
-diff --git a/src/lib-index/mail-index-transaction-finish.c b/src/lib-index/mail-index-transaction-finish.c
-index 360e597..d39cbe2 100644
---- a/src/lib-index/mail-index-transaction-finish.c
-+++ b/src/lib-index/mail-index-transaction-finish.c
-@@ -53,7 +53,7 @@ ext_reset_update_atomic(struct mail_index_transaction *t,
+Index: dovecot/src/lib-index/mail-index-transaction-finish.c
+===================================================================
+--- dovecot.orig/src/lib-index/mail-index-transaction-finish.c
++++ dovecot/src/lib-index/mail-index-transaction-finish.c
+@@ -53,7 +53,7 @@ ext_reset_update_atomic(struct mail_inde
  
  	array_idx_set(&t->ext_reset_ids, ext_id, &reset_id);
  
@@ -97,11 +97,11 @@ index 360e597..d39cbe2 100644
  	if (array_is_created(&t->ext_resets)) {
  		reset = array_idx_modifiable(&t->ext_resets, ext_id);
  		if (reset->new_reset_id == (uint32_t)-1)
-diff --git a/src/lib-index/mail-index.h b/src/lib-index/mail-index.h
-index 7f34eb5..06e2e6e 100644
---- a/src/lib-index/mail-index.h
-+++ b/src/lib-index/mail-index.h
-@@ -384,7 +384,7 @@ void mail_index_set_lock_method(struct mail_index *index,
+Index: dovecot/src/lib-index/mail-index.h
+===================================================================
+--- dovecot.orig/src/lib-index/mail-index.h
++++ dovecot/src/lib-index/mail-index.h
+@@ -384,7 +384,7 @@ void mail_index_set_lock_method(struct m
     use the default. */
  void mail_index_set_optimization_settings(struct mail_index *index,
  	const struct mail_index_optimization_settings *set);
@@ -110,11 +110,11 @@ index 7f34eb5..06e2e6e 100644
     extension header data immediately to it. */
  void mail_index_set_ext_init_data(struct mail_index *index, uint32_t ext_id,
  				  const void *data, size_t size);
-diff --git a/src/lib-index/mail-transaction-log-file.c b/src/lib-index/mail-transaction-log-file.c
-index 3e66d7e..807cb0c 100644
---- a/src/lib-index/mail-transaction-log-file.c
-+++ b/src/lib-index/mail-transaction-log-file.c
-@@ -760,7 +760,7 @@ mail_transaction_log_file_create2(struct mail_transaction_log_file *file,
+Index: dovecot/src/lib-index/mail-transaction-log-file.c
+===================================================================
+--- dovecot.orig/src/lib-index/mail-transaction-log-file.c
++++ dovecot/src/lib-index/mail-transaction-log-file.c
+@@ -760,7 +760,7 @@ mail_transaction_log_file_create2(struct
  		return -1;
  
  	if (reset) {
@@ -123,11 +123,11 @@ index 3e66d7e..807cb0c 100644
  		   indexes we'll probably want to keep uidvalidity and in such
  		   cases we really don't want to shrink modseqs. */
  		file->hdr.prev_file_seq = 0;
-diff --git a/src/lib-storage/index/index-sync-pvt.c b/src/lib-storage/index/index-sync-pvt.c
-index 92eb4e8..358eb1a 100644
---- a/src/lib-storage/index/index-sync-pvt.c
-+++ b/src/lib-storage/index/index-sync-pvt.c
-@@ -185,7 +185,7 @@ index_mailbox_sync_pvt_index(struct index_mailbox_sync_pvt_context *ctx,
+Index: dovecot/src/lib-storage/index/index-sync-pvt.c
+===================================================================
+--- dovecot.orig/src/lib-storage/index/index-sync-pvt.c
++++ dovecot/src/lib-storage/index/index-sync-pvt.c
+@@ -185,7 +185,7 @@ index_mailbox_sync_pvt_index(struct inde
  	} else {
  		/* mailbox created/recreated */
  		reset = TRUE;
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Debug-flaky-unit-test.patch 1:2.3.19.1+dfsg1-2/debian/patches/Debug-flaky-unit-test.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Debug-flaky-unit-test.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Debug-flaky-unit-test.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,65 @@
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Mon, 6 Jun 2022 17:24:19 +0200
+Subject: Debug flaky unit test
+
+See #1007744
+---
+ src/lib-smtp/test-smtp-server-errors.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/src/lib-smtp/test-smtp-server-errors.c b/src/lib-smtp/test-smtp-server-errors.c
+index d3e528c..5240d30 100644
+--- a/src/lib-smtp/test-smtp-server-errors.c
++++ b/src/lib-smtp/test-smtp-server-errors.c
+@@ -3712,13 +3712,14 @@ static void server_connection_accept(void *context ATTR_UNUSED)
+ 
+ static void test_server_timeout(void *context ATTR_UNUSED)
+ {
+-	i_fatal("Server timed out");
++	i_fatal("Server timed out [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ }
+ 
+ static void test_server_run(const struct smtp_server_settings *smtp_set)
+ {
+ 	struct timeout *to;
+ 
++	i_debug("Adding timeout to server [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 	to = timeout_add(SERVER_MAX_TIMEOUT_MSECS,
+ 			 test_server_timeout, NULL);
+ 
+@@ -3729,8 +3730,7 @@ static void test_server_run(const struct smtp_server_settings *smtp_set)
+ 
+ 	io_loop_run(ioloop);
+ 
+-	if (debug)
+-		i_debug("Server finished");
++	i_debug("Server finished [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 
+ 	/* close server socket */
+ 	io_remove(&io_listen);
+@@ -3770,9 +3770,12 @@ static int test_run_client(struct test_client_data *data)
+ 	/* wait a little for server setup */
+ 	i_sleep_msecs(100);
+ 
++	i_debug("test_run_client: pre create [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 	ioloop = io_loop_create();
++	i_debug("test_run_client: post create [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 	data->client_test(data->index);
+ 	io_loop_destroy(&ioloop);
++	i_debug("test_run_client: post destroy [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 
+ 	if (debug)
+ 		i_debug("Terminated");
+@@ -3794,9 +3797,12 @@ test_run_server(const struct smtp_server_settings *server_set,
+ 	i_zero(&server_callbacks);
+ 
+ 	server_pending = client_tests_count;
++	i_debug("test_run_server: pre create [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 	ioloop = io_loop_create();
++	i_debug("test_run_server: post create [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 	server_test(server_set);
+ 	io_loop_destroy(&ioloop);
++	i_debug("test_run_server: post destroy create [current_ioloop=%p ioloop=%p]", current_ioloop, ioloop);
+ 
+ 	if (debug)
+ 		i_debug("Terminated");
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/doveadm-director.1-drop-acute-accent.patch 1:2.3.19.1+dfsg1-2/debian/patches/doveadm-director.1-drop-acute-accent.patch
--- 1:2.3.16+dfsg1-3/debian/patches/doveadm-director.1-drop-acute-accent.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/doveadm-director.1-drop-acute-accent.patch	2022-07-30 02:58:28.000000000 +0000
@@ -16,7 +16,7 @@ Found by Lintian:
  1 file changed, 3 insertions(+), 3 deletions(-)
 
 diff --git a/doc/man/doveadm-director.1.in b/doc/man/doveadm-director.1.in
-index c430a3d..069241e 100644
+index 2a2c688..4b1855f 100644
 --- a/doc/man/doveadm-director.1.in
 +++ b/doc/man/doveadm-director.1.in
 @@ -108,7 +108,7 @@ hosts. All the existing connections will be kicked. If
@@ -29,8 +29,8 @@ index c430a3d..069241e 100644
  parameter specifies how many users can be moved concurrently.
  The default is 100.
 @@ -116,7 +116,7 @@ The default is 100.
- If
- .B \-f
+ If the
+ .B \-F
  parameter is used, the user associations are simply dropped. Existing
 -connections won\'t be kicked and flush scripts aren\'t run.
 +connections won't be kicked and flush scripts aren't run.
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/dovecot_name.patch 1:2.3.19.1+dfsg1-2/debian/patches/dovecot_name.patch
--- 1:2.3.16+dfsg1-3/debian/patches/dovecot_name.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/dovecot_name.patch	2022-07-30 02:58:28.000000000 +0000
@@ -18,10 +18,10 @@ Last-Update: 2020-05-23
  src/submission/submission-settings.c | 2 +-
  6 files changed, 13 insertions(+), 8 deletions(-)
 
-diff --git a/configure.ac b/configure.ac
-index 96d1b8c..3479e29 100644
---- a/configure.ac
-+++ b/configure.ac
+Index: dovecot/configure.ac
+===================================================================
+--- dovecot.orig/configure.ac
++++ dovecot/configure.ac
 @@ -287,7 +287,12 @@ AM_ICONV
  # SIZE_MAX is missing without this
  CXXFLAGS="$CXXFLAGS -D__STDC_LIMIT_MACROS"
@@ -36,11 +36,11 @@ index 96d1b8c..3479e29 100644
  AC_DEFINE_UNQUOTED(DOVECOT_STRING, "$PACKAGE_STRING", [Dovecot string])
  AC_DEFINE_UNQUOTED(DOVECOT_VERSION, "$PACKAGE_VERSION", [Dovecot version])
  
-diff --git a/src/config/all-settings.c b/src/config/all-settings.c
-index fc16f83..7724c66 100644
---- a/src/config/all-settings.c
-+++ b/src/config/all-settings.c
-@@ -2315,7 +2315,7 @@ static const struct submission_settings submission_default_settings = {
+Index: dovecot/src/config/all-settings.c
+===================================================================
+--- dovecot.orig/src/config/all-settings.c
++++ dovecot/src/config/all-settings.c
+@@ -2320,7 +2320,7 @@ static const struct submission_settings
  
  	.hostname = "",
  
@@ -49,7 +49,7 @@ index fc16f83..7724c66 100644
  	.login_trusted_networks = "",
  
  	.recipient_delimiter = "+",
-@@ -4111,7 +4111,7 @@ static const struct setting_define login_setting_defines[] = {
+@@ -4182,7 +4182,7 @@ static const struct setting_define login
  static const struct login_settings login_default_settings = {
  	.login_trusted_networks = "",
  	.login_source_ips = "",
@@ -58,7 +58,7 @@ index fc16f83..7724c66 100644
  	.login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c session=<%{session}>",
  	.login_log_format = "%$: %s",
  	.login_access_sockets = "",
-@@ -4322,7 +4322,7 @@ static const struct lmtp_settings lmtp_default_settings = {
+@@ -4396,7 +4396,7 @@ static const struct lmtp_settings lmtp_d
  
  	.lmtp_client_workarounds = "",
  
@@ -67,11 +67,11 @@ index fc16f83..7724c66 100644
  	.login_trusted_networks = "",
  
  	.mail_plugins = "",
-diff --git a/src/lib-smtp/smtp-server.c b/src/lib-smtp/smtp-server.c
-index f48b60a..3f8cee4 100644
---- a/src/lib-smtp/smtp-server.c
-+++ b/src/lib-smtp/smtp-server.c
-@@ -47,7 +47,7 @@ struct smtp_server *smtp_server_init(const struct smtp_server_settings *set)
+Index: dovecot/src/lib-smtp/smtp-server.c
+===================================================================
+--- dovecot.orig/src/lib-smtp/smtp-server.c
++++ dovecot/src/lib-smtp/smtp-server.c
+@@ -49,7 +49,7 @@ struct smtp_server *smtp_server_init(con
  	if (set->login_greeting != NULL && *set->login_greeting != '\0')
  		server->set.login_greeting = p_strdup(pool, set->login_greeting);
  	else
@@ -80,11 +80,11 @@ index f48b60a..3f8cee4 100644
  	if (set->capabilities == 0) {
  		server->set.capabilities = SMTP_SERVER_DEFAULT_CAPABILITIES;
  	} else  {
-diff --git a/src/lmtp/lmtp-settings.c b/src/lmtp/lmtp-settings.c
-index 5cec2be..8e018f7 100644
---- a/src/lmtp/lmtp-settings.c
-+++ b/src/lmtp/lmtp-settings.c
-@@ -91,7 +91,7 @@ static const struct lmtp_settings lmtp_default_settings = {
+Index: dovecot/src/lmtp/lmtp-settings.c
+===================================================================
+--- dovecot.orig/src/lmtp/lmtp-settings.c
++++ dovecot/src/lmtp/lmtp-settings.c
+@@ -93,7 +93,7 @@ static const struct lmtp_settings lmtp_d
  
  	.lmtp_client_workarounds = "",
  
@@ -93,11 +93,11 @@ index 5cec2be..8e018f7 100644
  	.login_trusted_networks = "",
  
  	.mail_plugins = "",
-diff --git a/src/login-common/login-settings.c b/src/login-common/login-settings.c
-index b1e0b06..f133717 100644
---- a/src/login-common/login-settings.c
-+++ b/src/login-common/login-settings.c
-@@ -50,7 +50,7 @@ static const struct setting_define login_setting_defines[] = {
+Index: dovecot/src/login-common/login-settings.c
+===================================================================
+--- dovecot.orig/src/login-common/login-settings.c
++++ dovecot/src/login-common/login-settings.c
+@@ -51,7 +51,7 @@ static const struct setting_define login
  static const struct login_settings login_default_settings = {
  	.login_trusted_networks = "",
  	.login_source_ips = "",
@@ -106,11 +106,11 @@ index b1e0b06..f133717 100644
  	.login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c session=<%{session}>",
  	.login_log_format = "%$: %s",
  	.login_access_sockets = "",
-diff --git a/src/submission/submission-settings.c b/src/submission/submission-settings.c
-index c4dd3db..5b3e00d 100644
---- a/src/submission/submission-settings.c
-+++ b/src/submission/submission-settings.c
-@@ -103,7 +103,7 @@ static const struct submission_settings submission_default_settings = {
+Index: dovecot/src/submission/submission-settings.c
+===================================================================
+--- dovecot.orig/src/submission/submission-settings.c
++++ dovecot/src/submission/submission-settings.c
+@@ -103,7 +103,7 @@ static const struct submission_settings
  
  	.hostname = "",
  
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Fix-uninitialized-read-in-doveadm-oldstats.patch 1:2.3.19.1+dfsg1-2/debian/patches/Fix-uninitialized-read-in-doveadm-oldstats.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Fix-uninitialized-read-in-doveadm-oldstats.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Fix-uninitialized-read-in-doveadm-oldstats.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,33 @@
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Sat, 23 Jul 2022 16:38:23 +0200
+Subject: Fix uninitialized read in doveadm-oldstats
+
+The third argument to doveadm_cmd_param_bool() is only set on a return
+value of TRUE.
+Since disk_input_field and disk_output_field should be set if the value
+of show-disk-io is specified and specified to true, fix the condition.
+
+    doveadm-oldstats.c: In function 'cmd_stats_top':
+    doveadm-oldstats.c:551:63: warning: 'b' may be used uninitialized [-Wmaybe-uninitialized]
+      551 |         if (!doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) {
+          |                                                               ^
+    doveadm-oldstats.c:545:14: note: 'b' was declared here
+      545 |         bool b;
+          |              ^
+---
+ src/doveadm/doveadm-oldstats.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/doveadm/doveadm-oldstats.c b/src/doveadm/doveadm-oldstats.c
+index 4be575e..ff6dcf5 100644
+--- a/src/doveadm/doveadm-oldstats.c
++++ b/src/doveadm/doveadm-oldstats.c
+@@ -548,7 +548,7 @@ static void cmd_stats_top(struct doveadm_cmd_context *cctx)
+ 		path = t_strconcat(doveadm_settings->base_dir,
+ 				   "/old-stats", NULL);
+ 	}
+-	if (!doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) {
++	if (doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) {
+ 		disk_input_field = "read_bytes";
+ 		disk_output_field = "write_bytes";
+ 	}
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Improve-cross-compile-support.patch 1:2.3.19.1+dfsg1-2/debian/patches/Improve-cross-compile-support.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Improve-cross-compile-support.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Improve-cross-compile-support.patch	2022-07-30 02:58:28.000000000 +0000
@@ -100,10 +100,10 @@ index 98e6f93..425bcdd 100644
 +  ])
  ])
 diff --git a/src/lib-lua/Makefile.am b/src/lib-lua/Makefile.am
-index bbf727a..f69bb83 100644
+index 20ce311..7d44e89 100644
 --- a/src/lib-lua/Makefile.am
 +++ b/src/lib-lua/Makefile.am
-@@ -20,9 +20,7 @@ LIBDICT_LUA += ../lib-dict/libdict_lua.la
+@@ -24,9 +24,7 @@ test_programs += test-dict-lua
  endif
  
  # Note: the only things this lib should depend on are libdovecot and lua.
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-1.patch 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-1.patch
--- 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-1.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-1.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,59 +0,0 @@
-From 4640109c9c7a58a7d42931ee7e829c4f98f3b5a7 Mon Sep 17 00:00:00 2001
-From: Timo Sirainen <timo.sirainen@open-xchange.com>
-Date: Tue, 31 Aug 2021 12:08:32 +0300
-Subject: [PATCH 1/3] indexer: Use a separate indexer_queue_callback_t type for
- indexer-queue callback
-
----
- src/indexer/indexer-queue.c | 4 ++--
- src/indexer/indexer-queue.h | 4 +++-
- 2 files changed, 5 insertions(+), 3 deletions(-)
-
-diff --git a/src/indexer/indexer-queue.c b/src/indexer/indexer-queue.c
-index bc7718791f..e0239a351a 100644
---- a/src/indexer/indexer-queue.c
-+++ b/src/indexer/indexer-queue.c
-@@ -7,7 +7,7 @@
- #include "indexer-queue.h"
- 
- struct indexer_queue {
--	indexer_status_callback_t *callback;
-+	indexer_queue_callback_t *callback;
- 	void (*listen_callback)(struct indexer_queue *);
- 
- 	/* username+mailbox -> indexer_request */
-@@ -29,7 +29,7 @@ static int indexer_request_cmp(const struct indexer_request *r1,
- }
- 
- struct indexer_queue *
--indexer_queue_init(indexer_status_callback_t *callback)
-+indexer_queue_init(indexer_queue_callback_t *callback)
- {
- 	struct indexer_queue *queue;
- 	
-diff --git a/src/indexer/indexer-queue.h b/src/indexer/indexer-queue.h
-index fe56829faa..a9d5b9d1e7 100644
---- a/src/indexer/indexer-queue.h
-+++ b/src/indexer/indexer-queue.h
-@@ -3,6 +3,8 @@
- 
- #include "indexer.h"
- 
-+typedef void indexer_queue_callback_t(int status, void *context);
-+
- struct indexer_request {
- 	struct indexer_request *prev, *next;
- 
-@@ -30,7 +32,7 @@ struct indexer_request {
- 	ARRAY(void *) contexts;
- };
- 
--struct indexer_queue *indexer_queue_init(indexer_status_callback_t *callback);
-+struct indexer_queue *indexer_queue_init(indexer_queue_callback_t *callback);
- void indexer_queue_deinit(struct indexer_queue **queue);
- 
- /* The callback is called whenever a new request is added to the queue. */
--- 
-GitLab
-
-
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-2.patch 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-2.patch
--- 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-2.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-2.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,133 +0,0 @@
-From dd4eb3dc08a8fd3dc5f1afff30e2d7389c5840a8 Mon Sep 17 00:00:00 2001
-From: Timo Sirainen <timo.sirainen@open-xchange.com>
-Date: Tue, 31 Aug 2021 12:12:49 +0300
-Subject: [PATCH 2/3] indexer: Don't free worker_connection too early
-
-There's no need for the status callback anymore to free the connection.
-It will be tracked automatically.
----
- src/indexer/indexer.c           |  2 --
- src/indexer/worker-connection.c | 17 +----------------
- src/indexer/worker-connection.h |  2 +-
- src/indexer/worker-pool.c       |  8 +-------
- src/indexer/worker-pool.h       |  2 --
- 5 files changed, 3 insertions(+), 28 deletions(-)
-
-diff --git a/src/indexer/indexer.c b/src/indexer/indexer.c
-index 508c185512..9a7af6e28c 100644
---- a/src/indexer/indexer.c
-+++ b/src/indexer/indexer.c
-@@ -108,8 +108,6 @@ static void worker_status_callback(int percentage, void *context)
- 
- 	indexer_queue_request_finish(queue, &request,
- 				     percentage == 100);
--	if (worker_pool != NULL) /* not in deinit */
--		worker_pool_release_connection(worker_pool, conn);
- 
- 	/* if this was the last request for the connection, we can send more
- 	   through it. delay it a bit, since we may be coming here from
-diff --git a/src/indexer/worker-connection.c b/src/indexer/worker-connection.c
-index 90f536d2b2..a9b9ae464c 100644
---- a/src/indexer/worker-connection.c
-+++ b/src/indexer/worker-connection.c
-@@ -25,8 +25,6 @@
- struct worker_connection {
- 	struct connection conn;
- 
--	int refcount;
--
- 	indexer_status_callback_t *callback;
- 
- 	char *request_username;
-@@ -44,7 +42,7 @@ static void worker_connection_call_callback(struct worker_connection *worker,
- 		worker->request = NULL;
- }
- 
--static void worker_connection_destroy(struct connection *conn)
-+void worker_connection_destroy(struct connection *conn)
- {
- 	struct worker_connection *worker =
- 		container_of(conn, struct worker_connection, conn);
-@@ -52,18 +50,6 @@ static void worker_connection_destroy(struct connection *conn)
- 	worker->request = NULL;
- 	i_free_and_null(worker->request_username);
- 	connection_deinit(conn);
--}
--
--void worker_connection_unref(struct connection **_conn)
--{
--	struct connection *conn = *_conn;
--	struct worker_connection *worker =
--		container_of(conn, struct worker_connection, conn);
--
--	i_assert(worker->refcount > 0);
--	if (--worker->refcount > 0)
--		return;
--	worker_connection_destroy(conn);
- 	i_free(conn);
- }
- 
-@@ -220,7 +206,6 @@ worker_connection_create(const char *socket_path,
- 	struct worker_connection *conn;
- 
- 	conn = i_new(struct worker_connection, 1);
--	conn->refcount = 1;
- 	conn->callback = callback;
- 	connection_init_client_unix(list, &conn->conn, socket_path);
- 
-diff --git a/src/indexer/worker-connection.h b/src/indexer/worker-connection.h
-index 58fd050660..f8d6c39d1a 100644
---- a/src/indexer/worker-connection.h
-+++ b/src/indexer/worker-connection.h
-@@ -10,7 +10,7 @@ struct connection *
- worker_connection_create(const char *socket_path,
- 			 indexer_status_callback_t *callback,
- 			 struct connection_list *list);
--void worker_connection_unref(struct connection **_conn);
-+void worker_connection_destroy(struct connection *conn);
- 
- struct connection_list *worker_connection_list_create(void);
- 
-diff --git a/src/indexer/worker-pool.c b/src/indexer/worker-pool.c
-index 6367265885..16f8feb6e3 100644
---- a/src/indexer/worker-pool.c
-+++ b/src/indexer/worker-pool.c
-@@ -60,7 +60,7 @@ static int worker_pool_add_connection(struct worker_pool *pool,
- 	conn = worker_connection_create(pool->socket_path, pool->callback,
- 					pool->connection_list);
- 	if (connection_client_connect(conn) < 0) {
--		worker_connection_unref(&conn);
-+		worker_connection_destroy(conn);
- 		return -1;
- 	}
- 
-@@ -99,12 +99,6 @@ bool worker_pool_get_connection(struct worker_pool *pool,
- 	return TRUE;
- }
- 
--void worker_pool_release_connection(struct worker_pool *pool ATTR_UNUSED,
--				    struct connection *conn)
--{
--	worker_connection_unref(&conn);
--}
--
- struct connection *
- worker_pool_find_username_connection(struct worker_pool *pool,
- 				     const char *username)
-diff --git a/src/indexer/worker-pool.h b/src/indexer/worker-pool.h
-index 057d67f37c..3f114d2024 100644
---- a/src/indexer/worker-pool.h
-+++ b/src/indexer/worker-pool.h
-@@ -13,8 +13,6 @@ bool worker_pool_have_busy_connections(struct worker_pool *pool);
- 
- bool worker_pool_get_connection(struct worker_pool *pool,
- 				struct connection **conn_r);
--void worker_pool_release_connection(struct worker_pool *pool,
--				    struct connection *conn);
- 
- struct connection *
- worker_pool_find_username_connection(struct worker_pool *pool,
--- 
-GitLab
-
-
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-3.patch 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-3.patch
--- 1:2.3.16+dfsg1-3/debian/patches/indexer-crash/indexer-crash-3.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/indexer-crash/indexer-crash-3.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,91 +0,0 @@
-From 6c047bd28caaa4d203a3f2ec43f199c83ec792f3 Mon Sep 17 00:00:00 2001
-From: Timo Sirainen <timo.sirainen@open-xchange.com>
-Date: Tue, 31 Aug 2021 12:14:15 +0300
-Subject: [PATCH 3/3] indexer: Change status callback to take struct
- indexer_request parameter
-
----
- src/indexer/indexer.c           |  6 ++----
- src/indexer/indexer.h           |  5 ++++-
- src/indexer/worker-connection.c | 10 +---------
- src/indexer/worker-connection.h |  3 ---
- 4 files changed, 7 insertions(+), 17 deletions(-)
-
-diff --git a/src/indexer/indexer.c b/src/indexer/indexer.c
-index 9a7af6e28c..219af539f4 100644
---- a/src/indexer/indexer.c
-+++ b/src/indexer/indexer.c
-@@ -95,11 +95,9 @@ static void queue_listen_callback(struct indexer_queue *queue)
- 	queue_try_send_more(queue);
- }
- 
--static void worker_status_callback(int percentage, void *context)
-+static void
-+worker_status_callback(int percentage, struct indexer_request *request)
- {
--	struct connection *conn = context;
--	struct indexer_request *request = worker_connection_get_request(conn);
--
- 	if (percentage >= 0 && percentage < 100) {
- 		indexer_queue_request_status(queue, request,
- 					     percentage);
-diff --git a/src/indexer/indexer.h b/src/indexer/indexer.h
-index 417a433cd9..2234f2db70 100644
---- a/src/indexer/indexer.h
-+++ b/src/indexer/indexer.h
-@@ -1,8 +1,11 @@
- #ifndef INDEXER_H
- #define INDEXER_H
- 
-+struct indexer_request;
-+
- /* percentage: -1 = failed, 0..99 = indexing in progress, 100 = done */
--typedef void indexer_status_callback_t(int percentage, void *context);
-+typedef void
-+indexer_status_callback_t(int percentage, struct indexer_request *request);
- 
- void indexer_refresh_proctitle(void);
- 
-diff --git a/src/indexer/worker-connection.c b/src/indexer/worker-connection.c
-index a9b9ae464c..d50655f0a3 100644
---- a/src/indexer/worker-connection.c
-+++ b/src/indexer/worker-connection.c
-@@ -37,7 +37,7 @@ static void worker_connection_call_callback(struct worker_connection *worker,
- 					    int percentage)
- {
- 	if (worker->request != NULL)
--		worker->callback(percentage, &worker->conn);
-+		worker->callback(percentage, worker->request);
- 	if (percentage < 0 || percentage == 100)
- 		worker->request = NULL;
- }
-@@ -168,14 +168,6 @@ const char *worker_connection_get_username(struct connection *conn)
- 	return worker->request_username;
- }
- 
--struct indexer_request *
--worker_connection_get_request(struct connection *conn)
--{
--	struct worker_connection *worker =
--		container_of(conn, struct worker_connection, conn);
--	return worker->request;
--}
--
- static const struct connection_vfuncs worker_connection_vfuncs = {
- 	.destroy = worker_connection_destroy,
- 	.input_args = worker_connection_input_args,
-diff --git a/src/indexer/worker-connection.h b/src/indexer/worker-connection.h
-index f8d6c39d1a..c307a63fd5 100644
---- a/src/indexer/worker-connection.h
-+++ b/src/indexer/worker-connection.h
-@@ -35,7 +35,4 @@ bool worker_connection_is_busy(struct connection *conn);
-    or NULL if there are none. */
- const char *worker_connection_get_username(struct connection *conn);
- 
--struct indexer_request *
--worker_connection_get_request(struct connection *conn);
--
- #endif
--- 
-GitLab
-
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/indexer-Fix-crash-if-client-disconnects-while-it-s-w.patch 1:2.3.19.1+dfsg1-2/debian/patches/indexer-Fix-crash-if-client-disconnects-while-it-s-w.patch
--- 1:2.3.16+dfsg1-3/debian/patches/indexer-Fix-crash-if-client-disconnects-while-it-s-w.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/indexer-Fix-crash-if-client-disconnects-while-it-s-w.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,30 +0,0 @@
-From 97367ef61784a364ccf615cb816be6492329050d Mon Sep 17 00:00:00 2001
-From: Timo Sirainen <timo.sirainen@open-xchange.com>
-Date: Mon, 9 Aug 2021 13:01:12 +0300
-Subject: [PATCH] indexer: Fix crash if client disconnects while it's waiting
- for command reply
-
-This happened for example if IMAP SEARCH triggered long fts indexing and the
-IMAP client disconnected while waiting for the reply.
-
-Broken by f62a25849358e40a08a2c47f5bcaa1613a31d076
----
- src/indexer/indexer-client.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/indexer/indexer-client.c b/src/indexer/indexer-client.c
-index c5aac8a3da..ab4710778a 100644
---- a/src/indexer/indexer-client.c
-+++ b/src/indexer/indexer-client.c
-@@ -139,7 +139,7 @@ void indexer_client_status_callback(int percentage, void *context)
- 	struct indexer_client_request *ctx = context;
- 
- 	/* we are in deinit already, or the client has disconnected */
--	if (ctx->client == NULL)
-+	if (ctx->client->conn.output == NULL)
- 		return;
- 
- 	T_BEGIN {
--- 
-2.30.2
-
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/mail-cache-bigendian-1.patch 1:2.3.19.1+dfsg1-2/debian/patches/mail-cache-bigendian-1.patch
--- 1:2.3.16+dfsg1-3/debian/patches/mail-cache-bigendian-1.patch	2021-09-10 23:11:27.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/mail-cache-bigendian-1.patch	1970-01-01 00:00:00.000000000 +0000
@@ -1,56 +0,0 @@
-From: Timo Sirainen <timo.sirainen@open-xchange.com>
-Date: Tue, 10 Aug 2021 12:22:08 +0300
-Subject: lib-index: Fix storing cache fields' last_used with 64bit big endian
- CPUs
-
-Origin: https://www.mail-archive.com/dovecot@dovecot.org/msg84308.html
----
- src/lib-index/mail-cache-fields.c | 21 +++++++++++++++------
- 1 file changed, 15 insertions(+), 6 deletions(-)
-
-diff --git a/src/lib-index/mail-cache-fields.c b/src/lib-index/mail-cache-fields.c
-index e929fb5..429e0d2 100644
---- a/src/lib-index/mail-cache-fields.c
-+++ b/src/lib-index/mail-cache-fields.c
-@@ -524,6 +524,19 @@ static void copy_to_buf_byte(struct mail_cache *cache, buffer_t *dest,
- 	}
- }
- 
-+static void
-+copy_to_buf_last_used(struct mail_cache *cache, buffer_t *dest, bool add_new)
-+{
-+	size_t offset = offsetof(struct mail_cache_field, last_used);
-+#if defined(WORDS_BIGENDIAN) && SIZEOF_VOID_P == 8
-+	/* 64bit time_t with big endian CPUs: copy the last 32 bits instead of
-+	   the first 32 bits (that are always 0). The 32 bits are enough until
-+	   year 2106, so we're not in a hurry to use 64 bits on disk. */
-+	offset += sizeof(uint32_t);
-+#endif
-+	copy_to_buf(cache, dest, add_new, offset, sizeof(uint32_t));
-+}
-+
- static int mail_cache_header_fields_update_locked(struct mail_cache *cache)
- {
- 	buffer_t *buffer;
-@@ -536,9 +549,7 @@ static int mail_cache_header_fields_update_locked(struct mail_cache *cache)
- 
- 	buffer = t_buffer_create(256);
- 
--	copy_to_buf(cache, buffer, FALSE,
--		    offsetof(struct mail_cache_field, last_used),
--		    sizeof(uint32_t));
-+	copy_to_buf_last_used(cache, buffer, FALSE);
- 	ret = mail_cache_write(cache, buffer->data, buffer->used,
- 			       offset + MAIL_CACHE_FIELD_LAST_USED());
- 	if (ret == 0) {
-@@ -599,9 +610,7 @@ void mail_cache_header_fields_get(struct mail_cache *cache, buffer_t *dest)
- 	buffer_append(dest, &hdr, sizeof(hdr));
- 
- 	/* we have to keep the field order for the existing fields. */
--	copy_to_buf(cache, dest, TRUE,
--		    offsetof(struct mail_cache_field, last_used),
--		    sizeof(uint32_t));
-+	copy_to_buf_last_used(cache, dest, TRUE);
- 	copy_to_buf(cache, dest, TRUE,
- 		    offsetof(struct mail_cache_field, field_size),
- 		    sizeof(uint32_t));
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/mboxlocking.patch 1:2.3.19.1+dfsg1-2/debian/patches/mboxlocking.patch
--- 1:2.3.16+dfsg1-3/debian/patches/mboxlocking.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/mboxlocking.patch	2022-07-30 02:58:28.000000000 +0000
@@ -33,10 +33,10 @@ index a69224f..b47235f 100644
  # Maximum time to wait for lock (all of them) before aborting.
  #mbox_lock_timeout = 5 mins
 diff --git a/src/config/all-settings.c b/src/config/all-settings.c
-index 2df662b..fc16f83 100644
+index 391da6d..231100f 100644
 --- a/src/config/all-settings.c
 +++ b/src/config/all-settings.c
-@@ -1252,7 +1252,7 @@ static const struct setting_define mbox_setting_defines[] = {
+@@ -1239,7 +1239,7 @@ static const struct setting_define mbox_setting_defines[] = {
  };
  static const struct mbox_settings mbox_default_settings = {
  	.mbox_read_locks = "fcntl",
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/md4-md5-disable-optimization-causing-unaligned-access.patch 1:2.3.19.1+dfsg1-2/debian/patches/md4-md5-disable-optimization-causing-unaligned-access.patch
--- 1:2.3.16+dfsg1-3/debian/patches/md4-md5-disable-optimization-causing-unaligned-access.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/md4-md5-disable-optimization-causing-unaligned-access.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,51 @@
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Tue, 7 Dec 2021 13:01:10 +0100
+Subject: md4/md5: disable optimization causing unaligned access
+
+This restores unit tests with GCC 11 and LTO.
+
+    md5.c:92:23: warning: cast from 'const unsigned char *' to 'const uint32_t *' (aka 'const unsigned int *') increases required alignment from 1 to 4 [-Wcast-align]
+                    STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
+                    ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
+    md5.c:51:4: note: expanded from macro 'SET'
+            (*(const uint32_t *)&ptr[(n) * 4])
+              ^
+    md5.c:37:29: note: expanded from macro 'STEP'
+            (a) += f((b), (c), (d)) + (x) + (t); \
+                                       ^
+
+    md5.c:92:3: runtime error: load of misaligned address 0x61900000008b for type 'const uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment
+    0x61900000008b: note: pointer points here
+     41  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41
+                  ^
+---
+ src/lib/md4.c | 2 +-
+ src/lib/md5.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/lib/md4.c b/src/lib/md4.c
+index 06e3231..798292a 100644
+--- a/src/lib/md4.c
++++ b/src/lib/md4.c
+@@ -42,7 +42,7 @@
+  * memory accesses is just an optimization.  Nothing will break if it
+  * doesn't work.
+  */
+-#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
++#if 0 //defined(__i386__) || defined(__x86_64__) || defined(__vax__)
+ /* uint_fast32_t might be 64 bit, and thus may read 4 more bytes
+  * beyond the end of the buffer. So only read precisely 32 bits
+  */
+diff --git a/src/lib/md5.c b/src/lib/md5.c
+index 6b5da6c..c605639 100644
+--- a/src/lib/md5.c
++++ b/src/lib/md5.c
+@@ -46,7 +46,7 @@
+  * memory accesses is just an optimization.  Nothing will break if it
+  * doesn't work.
+  */
+-#if defined(__i386__) || defined(__x86_64__) || defined(__vax__)
++#if 0 //defined(__i386__) || defined(__x86_64__) || defined(__vax__)
+ #define SET(n) \
+ 	(*(const uint32_t *)&ptr[(n) * 4])
+ #define GET(n) \
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/series 1:2.3.19.1+dfsg1-2/debian/patches/series
--- 1:2.3.16+dfsg1-3/debian/patches/series	2021-09-10 23:11:27.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/series	2022-07-30 02:58:28.000000000 +0000
@@ -13,8 +13,10 @@ doveadm-director.1-drop-acute-accent.pat
 Fix-32bit-sign-comparisons.patch
 Improve-cross-compile-support.patch
 Silence-LTO-related-compiler-warning.patch
-indexer-crash/indexer-crash-1.patch
-indexer-crash/indexer-crash-2.patch
-indexer-crash/indexer-crash-3.patch
-indexer-Fix-crash-if-client-disconnects-while-it-s-w.patch
-mail-cache-bigendian-1.patch
+md4-md5-disable-optimization-causing-unaligned-access.patch
+Support-openssl-3.0.patch
+Debug-flaky-unit-test.patch
+Avoid-usage-of-PATH_MAX-not-available-on-hurd.patch
+auth-Fix-handling-passdbs-with-identical-driver-args-but-.patch
+auth-Add-a-comment-about-updating-userdb_find.patch
+Fix-uninitialized-read-in-doveadm-oldstats.patch
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/skip-rfc-subdir.patch 1:2.3.19.1+dfsg1-2/debian/patches/skip-rfc-subdir.patch
--- 1:2.3.16+dfsg1-3/debian/patches/skip-rfc-subdir.patch	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/skip-rfc-subdir.patch	2022-07-30 02:58:28.000000000 +0000
@@ -10,7 +10,7 @@ Forwarded: no (Debian-specific)
  3 files changed, 3 deletions(-)
 
 diff --git a/pigeonhole/configure.ac b/pigeonhole/configure.ac
-index b1ac729..2ba55dc 100644
+index 227746a..38671ed 100644
 --- a/pigeonhole/configure.ac
 +++ b/pigeonhole/configure.ac
 @@ -176,7 +176,6 @@ doc/Makefile
@@ -34,10 +34,10 @@ index 5131ece..258c135 100644
  	locations \
  	plugins
 diff --git a/pigeonhole/doc/Makefile.in b/pigeonhole/doc/Makefile.in
-index 00d1ff3..18517bc 100644
+index bf2b89c..a849848 100644
 --- a/pigeonhole/doc/Makefile.in
 +++ b/pigeonhole/doc/Makefile.in
-@@ -399,7 +399,6 @@ top_srcdir = @top_srcdir@
+@@ -404,7 +404,6 @@ top_srcdir = @top_srcdir@
  SUBDIRS = \
  	man \
  	example-config \
diff -pruN 1:2.3.16+dfsg1-3/debian/patches/Support-openssl-3.0.patch 1:2.3.19.1+dfsg1-2/debian/patches/Support-openssl-3.0.patch
--- 1:2.3.16+dfsg1-3/debian/patches/Support-openssl-3.0.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/patches/Support-openssl-3.0.patch	2022-07-30 02:58:28.000000000 +0000
@@ -0,0 +1,45 @@
+From: =?utf-8?q?Christian_G=C3=B6ttsche?= <cgzones@googlemail.com>
+Date: Wed, 11 May 2022 20:27:53 +0200
+Author: Michal Hlavinka
+Origin: https://bugzilla.redhat.com/show_bug.cgi?id=1962035
+Subject: Support openssl 3.0
+
+---
+ src/lib-dcrypt/dcrypt-openssl.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+diff --git a/src/lib-dcrypt/dcrypt-openssl.c b/src/lib-dcrypt/dcrypt-openssl.c
+index 1cbe352..5570d62 100644
+--- a/src/lib-dcrypt/dcrypt-openssl.c
++++ b/src/lib-dcrypt/dcrypt-openssl.c
+@@ -73,10 +73,30 @@
+   2<tab>key algo oid<tab>1<tab>symmetric algo name<tab>salt<tab>hash algo<tab>rounds<tab>E(RSA = i2d_PrivateKey, EC=Private Point)<tab>key id
+ **/
+ 
++#if OPENSSL_VERSION_MAJOR == 3
++static EC_KEY *EVP_PKEY_get0_EC_KEYv3(EVP_PKEY *key)
++{
++  EC_KEY *eck = EVP_PKEY_get1_EC_KEY(key);
++  EVP_PKEY_set1_EC_KEY(key, eck);
++  EC_KEY_free(eck);
++  return eck;
++}
++
++static EC_KEY *EVP_PKEY_get1_EC_KEYv3(EVP_PKEY *key)
++{
++  EC_KEY *eck = EVP_PKEY_get1_EC_KEY(key);
++  EVP_PKEY_set1_EC_KEY(key, eck);
++  return eck;
++}
++
++#define EVP_PKEY_get0_EC_KEY EVP_PKEY_get0_EC_KEYv3 
++#define EVP_PKEY_get1_EC_KEY EVP_PKEY_get1_EC_KEYv3
++#else
+ #ifndef HAVE_EVP_PKEY_get0
+ #define EVP_PKEY_get0_EC_KEY(x) x->pkey.ec
+ #define EVP_PKEY_get0_RSA(x) x->pkey.rsa
+ #endif
++#endif
+ 
+ #ifndef HAVE_OBJ_LENGTH
+ #define OBJ_length(o) ((o)->length)
diff -pruN 1:2.3.16+dfsg1-3/debian/rules 1:2.3.19.1+dfsg1-2/debian/rules
--- 1:2.3.16+dfsg1-3/debian/rules	2021-09-16 15:41:27.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/rules	2022-07-30 02:58:28.000000000 +0000
@@ -6,7 +6,7 @@
 SHELL=/bin/bash -O extglob
 PIGEONHOLE_DIR=$(CURDIR)/pigeonhole
 
-export DEB_BUILD_MAINT_OPTIONS=hardening=+all
+export DEB_BUILD_MAINT_OPTIONS=hardening=+all optimize=+lto
 
 # LP: 1636781 - strip incompatible default linker option
 ifeq ($(shell dpkg-vendor --derives-from Ubuntu && echo yes),yes)
@@ -20,14 +20,11 @@ DOV_DEB_LDFLAGS=
 # Ensure that stacktrace generation works:
 DOV_DEB_LDFLAGS += -rdynamic
 include /usr/share/dpkg/architecture.mk
-ifneq ($(filter armel armhf mips64el mipsel, $(DEB_HOST_ARCH)),)
+ifneq ($(filter armel armhf hppa mips64el mipsel riscv64 sparc64, $(DEB_HOST_ARCH)),)
   DOV_DEB_CFLAGS += -funwind-tables
+  DOV_DEB_CXXFLAGS += -funwind-tables
 endif
 
-DOV_DEB_CFLAGS += -flto
-DOV_DEB_CXXFLAGS += -flto
-DOV_DEB_LDFLAGS += -flto
-
 export DEB_CFLAGS_MAINT_APPEND = $(DOV_DEB_CFLAGS)
 export DEB_CXXFLAGS_MAINT_APPEND = $(DOV_DEB_CXXFLAGS)
 export DEB_LDFLAGS_MAINT_APPEND = $(DOV_DEB_LDFLAGS)
diff -pruN 1:2.3.16+dfsg1-3/debian/salsa-ci.yml 1:2.3.19.1+dfsg1-2/debian/salsa-ci.yml
--- 1:2.3.16+dfsg1-3/debian/salsa-ci.yml	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/salsa-ci.yml	2022-07-30 02:58:28.000000000 +0000
@@ -4,5 +4,5 @@ include:
 
 # The test suite does not pass reprotest
 variables:
-  SALSA_CI_LINTIAN_FAIL_WARNING: 1
   SALSA_CI_DISABLE_REPROTEST: 1
+  SALSA_CI_DISABLE_CROSSBUILD_ARM64: 1
diff -pruN 1:2.3.16+dfsg1-3/debian/source/lintian-overrides 1:2.3.19.1+dfsg1-2/debian/source/lintian-overrides
--- 1:2.3.16+dfsg1-3/debian/source/lintian-overrides	2021-09-02 20:48:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/debian/source/lintian-overrides	2022-07-30 02:58:28.000000000 +0000
@@ -1,2 +1,10 @@
-# The corresponding binary is Debian only
-dovecot source: maintainer-manual-page debian/maildirmake.dovecot.1
+# ignore long source lines
+dovecot source: very-long-line-length-in-source-file 2059 > 512 [pigeonhole/configure:13685]
+dovecot source: very-long-line-length-in-source-file 2367 > 512 [src/lib-fts/stopwords/stopwords_malformed.txt:105]
+dovecot source: very-long-line-length-in-source-file 3400 > 512 [pigeonhole/tests/execute/errors/cpu-limit.sieve:4]
+dovecot source: very-long-line-length-in-source-file 513 > 512 [src/plugins/mail-crypt/test-mail-global-key.c:24]
+dovecot source: very-long-line-length-in-source-file 541 > 512 [dovecot-config.in.in:31]
+dovecot source: very-long-line-length-in-source-file 621 > 512 [src/lib-fts/udhr_fra.txt:17]
+dovecot source: very-long-line-length-in-source-file 623 > 512 [src/lib/test-hmac.c:*]
+dovecot source: very-long-line-length-in-source-file 739 > 512 [pigeonhole/m4/libtool.m4:6621]
+dovecot source: very-long-line-length-in-source-file 842 > 512 [src/lib-imap/test-imap-bodystructure.c:225]
diff -pruN 1:2.3.16+dfsg1-3/doc/man/doveadm-director.1.in 1:2.3.19.1+dfsg1-2/doc/man/doveadm-director.1.in
--- 1:2.3.16+dfsg1-3/doc/man/doveadm-director.1.in	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/doc/man/doveadm-director.1.in	2022-06-14 06:55:03.000000000 +0000
@@ -94,7 +94,7 @@ dumped state.
 .SS director flush
 .B doveadm director flush
 [\fB\-a\fP \fIdirector_socket_path\fP]
-[\fB\-f\fP]
+[\fB\-F\fP]
 [\fB\-\-max\-parallel\fP \fIn\fP]
 \fIhost\fP|\fBall\fP
 .PP
@@ -113,8 +113,8 @@ load spike, all the users aren\'t moved
 parameter specifies how many users can be moved concurrently.
 The default is 100.
 .PP
-If
-.B \-f
+If the
+.B \-F
 parameter is used, the user associations are simply dropped. Existing
 connections won\'t be kicked and flush scripts aren\'t run.
 .\"-------------------------------------
diff -pruN 1:2.3.16+dfsg1-3/doc/man/doveadm-pw.1.in 1:2.3.19.1+dfsg1-2/doc/man/doveadm-pw.1.in
--- 1:2.3.16+dfsg1-3/doc/man/doveadm-pw.1.in	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/doc/man/doveadm-pw.1.in	2022-06-14 06:55:03.000000000 +0000
@@ -99,8 +99,8 @@ The password
 .I scheme
 which should be used to generate the hashed password.
 By default the
-.BI CRAM\-MD5\  scheme
-will be used.
+.BI CRYPT\  scheme
+will be used (with the $2y$ bcrypt format).
 It is also possible to append an encoding suffix to the
 .IR scheme .
 Supported encoding suffixes are:
diff -pruN 1:2.3.16+dfsg1-3/doc/man/doveadm-stats.1.in 1:2.3.19.1+dfsg1-2/doc/man/doveadm-stats.1.in
--- 1:2.3.16+dfsg1-3/doc/man/doveadm-stats.1.in	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/doc/man/doveadm-stats.1.in	2022-06-14 06:55:03.000000000 +0000
@@ -9,6 +9,10 @@ doveadm\-stats \- Inspect or reset stats
 .BR doveadm\ stats\ top " [ " \-s\ <stats\ socket\ path> " ] " "[ " -b " ]" " [ " sort field> " ]"
 .PP
 .BR doveadm\ stats\ reset " [ " \-s\ <stats\ socket\ path> " ] "
+.PP
+.BR doveadm\ stats\ add " [ " \-\-description\ <string> " ] [ " \-\-exporter\ <name> " [ " \-\-exporter-include\ <field> " ]] [ " \-\-fields\ <fields> " ] [ " \-\-group_by\ <fields> " ] " <name>\ <filter>
+.PP
+.BR doveadm\ stats\ remove\ <name>
 .\"------------------------------------------------------------------------
 .SH DESCRIPTION
 .B doveadm stats dump
@@ -19,6 +23,12 @@ is used to monitor statistics
 .PP
 .B doveadm stats reset
 is used to reset statistics
+.PP
+.B doveadm stats add
+is used to add metrics to statistics
+.PP
+.B doveadm stats remove
+is used to remove metrics from statistics
 .\"------------------------------------------------------------------------
 .SH OPTIONS
 Command specific
diff -pruN 1:2.3.16+dfsg1-3/doc/man/doveadm-sync.1.in 1:2.3.19.1+dfsg1-2/doc/man/doveadm-sync.1.in
--- 1:2.3.16+dfsg1-3/doc/man/doveadm-sync.1.in	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/doc/man/doveadm-sync.1.in	2022-06-14 06:55:03.000000000 +0000
@@ -11,15 +11,18 @@ doveadm\-backup \- Dovecot\(aqs one\-way
 [\fB\-S\fP \fIsocket_path\fP]
 .RB [ \-1fPRU ]
 [\fB\-l\fP \fIsecs\fP]
-[\fB\-r\fP \fIrawlog_path\fP]
+[\fB\-r\fP \fIrawlog path\fP]
 [\fB\-m\fP \fImailbox\fP]
-[\fB\-g\fP \fImailbox_guid\fP]
+[\fB\-g\fP \fImailbox guid\fP]
 [\fB\-n\fP \fInamespace\fP|\fB\-N\fP]
 [\fB\-x\fP \fIexclude\fP]
+[\fB\-a\fP \fIall mailbox\fP]
 [\fB\-s\fP \fIstate\fP]
 [\fB\-T\fP \fIsecs\fP]
 [\fB\-t\fP \fIstart date\fP]
 [\fB\-e\fP \fIend date\fP]
+[\fB\-O\fP \fIsync flag\fP]
+[\fB\-I\fP \fImax size\fP]
 \fB\-d\fP|\fIdestination\fP
 .\"-------------------------------------
 .PP
@@ -28,15 +31,18 @@ doveadm\-backup \- Dovecot\(aqs one\-way
 [\fB\-S\fP \fIsocket_path\fP]
 .RB [ \-fPRU ]
 [\fB\-l\fP \fIsecs\fP]
-[\fB\-r\fP \fIrawlog_path\fP]
+[\fB\-r\fP \fIrawlog path\fP]
 [\fB\-m\fP \fImailbox\fP]
-[\fB\-g\fP \fImailbox_guid\fP]
+[\fB\-g\fP \fImailbox guid\fP]
 [\fB\-n\fP \fInamespace\fP|\fB\-N\fP]
 [\fB\-x\fP \fIexclude\fP]
+[\fB\-a\fP \fIall mailbox\fP]
 [\fB\-s\fP \fIstate\fP]
 [\fB\-T\fP \fIsecs\fP]
 [\fB\-t\fP \fIstart date\fP]
 [\fB\-e\fP \fIend date\fP]
+[\fB\-O\fP \fIsync flag\fP]
+[\fB\-I\fP \fImax size\fP]
 \fB\-d\fP|\fIdestination\fP
 .\"------------------------------------------------------------------------
 .SH DESCRIPTION
@@ -177,13 +183,17 @@ which automatically runs dsync whenever
 .PP
 Command specific
 .IR options :
+@INCLUDE:option-A@
+.\"-------------------------------------
+@INCLUDE:option-F-file@
+.\"-------------------------------------
 .TP
 .B \-1
 Do one\-way synchronization instead of two\-way synchronization.
 .\"-------------------------------------
-@INCLUDE:option-A@
-.\"-------------------------------------
-@INCLUDE:option-F-file@
+.TP
+.B \-f
+Do full synchronization.
 .\"-------------------------------------
 .TP
 .B \-N
@@ -247,6 +257,30 @@ Synchronize only the specified namespace
 This parameter can be used multiple times.
 .\"-------------------------------------
 .TP
+.BI \-a \ all\ mailbox
+Name for the "All mails" virtual mailbox. If specified, mails are attempted to
+be copied from this mailbox instead of being saved separately. This may
+reduce the total disk space usage as well as disk IO.
+.\"-------------------------------------
+.TP
+.BI \-t \ start\ date
+Skip any mails whose received-timestamp is older than the specified time.
+.\"-------------------------------------
+.TP
+.BI \-e \ end\ date
+Skip any mails whose received-timestamp is newer than the specified time.
+.\"-------------------------------------
+.TP
+.BI \-O \ sync\ flag
+Sync only mails that have the specified flag.
+If the flag name begins with \(dq\fB\-\fP\(dq, sync all mails except the ones
+with the specified flag.
+.\"-------------------------------------
+.TP
+.BI \-I \ max\ size
+Skip any mails larger than the specified size.
+.\"-------------------------------------
+.TP
 .BI \-r \ rawlog_path
 Running dsync remotely, write the remote input/output traffic to the
 specified log file.
@@ -263,6 +297,7 @@ The new state is always printed to stand
 .BI \-x \ mailbox_mask
 Exclude the specified mailbox name/mask.
 The mask may contain \(dq\fB?\fP\(dq and \(dq\fB*\fP\(dq wildcards.
+The mask can also be a special-use name (e.g. \\Trash).
 This parameter can be used multiple times.
 .\"------------------------------------------------------------------------
 .SH ARGUMENTS
@@ -295,6 +330,10 @@ The default port is specified by
 .TP
 .BI tcps: host[:port]
 This is the same as tcp, but with SSL.
+.TP
+.BI command\ [arg1\ [,\ arg2,\ ...]]
+Runs a local command that connects its standard input & output
+to a dsync server.
 .RE
 .\"------------------------------------------------------------------------
 .SH "EXIT STATUS"
diff -pruN 1:2.3.16+dfsg1-3/dovecot-version.h 1:2.3.19.1+dfsg1-2/dovecot-version.h
--- 1:2.3.16+dfsg1-3/dovecot-version.h	2021-08-06 09:26:31.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/dovecot-version.h	2022-06-14 06:55:42.000000000 +0000
@@ -1,7 +1,7 @@
 #ifndef DOVECOT_VERSION_H
 #define DOVECOT_VERSION_H
 
-#define DOVECOT_REVISION "7e2e900c1a"
+#define DOVECOT_REVISION "9b53102964"
 #define DOVECOT_VERSION_FULL VERSION" ("DOVECOT_REVISION")"
 #define DOVECOT_BUILD_INFO DOVECOT_VERSION_FULL
 
diff -pruN 1:2.3.16+dfsg1-3/NEWS 1:2.3.19.1+dfsg1-2/NEWS
--- 1:2.3.16+dfsg1-3/NEWS	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/NEWS	2022-06-14 06:55:03.000000000 +0000
@@ -1,3 +1,302 @@
+v2.3.19.1 2022-06-14  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	- doveadm deduplicate: Non-duplicate mails were deleted.
+	  v2.3.19 regression.
+	- auth: Crash would occur when iterating multiple backends.
+	  Fixes: Panic: file userdb-blocking.c:
+	  line 125 (userdb_blocking_iter_next): assertion failed: (ctx->conn != NULL)
+
+v2.3.19 2022-05-10  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	+ Added mail_user_session_finished event, which is emitted when the mail
+	  user session is finished (e.g. imap, pop3, lmtp). It also includes
+	  fields with some process statistics information.
+	  See https://doc.dovecot.org/admin_manual/list_of_events/ for more
+	  information.
+	+ Added process_shutdown_filter setting. When an event matches the filter,
+	  the process will be shutdown after the current connection(s) have
+	  finished. This is intended to reduce memory usage of long-running imap
+	  processes that keep a lot of memory allocated instead of freeing it to
+	  the OS.
+	+ auth: Add cache hit indicator to auth passdb/userdb finished events.
+	  See https://doc.dovecot.org/admin_manual/list_of_events/ for more
+	  information.
+	+ doveadm deduplicate: Performance is improved significantly.
+	+ imapc: COPY commands were sent one mail at a time to the remote IMAP
+	  server. Now the copying is buffered, so multiple mails can be copied
+	  with a single COPY command.
+	+ lib-lua: Add a Lua interface to Dovecot's HTTP client library. See
+	  https://doc.dovecot.org/admin_manual/lua/ for more information.
+	- auth: Cache lookup would use incorrect cache key after username change.
+	- auth: Improve handling unexpected LDAP connection errors/hangs.
+	  Try to fix up these cases by reconnecting to the LDAP server and
+	  aborting LDAP requests earlier.
+	- auth: Process crashed if userdb iteration was attempted while auth-workers
+	  were already full handling auth requests.
+	- auth: db-oauth2: Using %{oauth2:name} variables caused unnecessary
+	  introspection requests.
+	- dict: Timeouts may have been leaked at deinit.
+	- director: Ring may have become unstable if a backend's tag was changed.
+	  It could also have caused director process to crash.
+	- doveadm kick: Numeric parameter was treated as IP address.
+	- doveadm: Proxying can panic when flushing print output. Fixes
+	  Panic: file ioloop.c: line 865 (io_loop_destroy): assertion failed:
+	  (ioloop == current_ioloop).
+	- doveadm sync: BROKENCHAR was wrongly changed to '_' character when
+	  migrating mailboxes. This was set by default to %, so any mailbox
+	  names containing % characters were modified to "_25".
+	- imapc: Copying or moving mails with doveadm to an imapc mailbox could
+	  have produced "Error: Syncing mailbox '[...]' failed" Errors. The
+	  operation itself succeeded but attempting to sync the destination
+	  mailbox failed.
+	- imapc: Prevent index log synchronization errors when two or more imapc
+	  sessions are adding messages to the same mailbox index files, i.e.
+	  INDEX=MEMORY is not used.
+	- indexer: Process was slowly leaking memory for each indexing request.
+	- lib-fts: fts header filters caused binary content to be sent to the
+	  indexer with non-default configuration.
+	- doveadm-server: Process could hang in some situations when printing
+	  output to TCP client, e.g. when printing doveadm sync state.
+	- lib-index: dovecot.index.log files were often read and parsed entirely,
+	  rather than only the parts that were actually necessary. This mainly
+	  increased CPU usage.
+	- lmtp-proxy: Session ID forwarding would cause same session IDs being
+	  used when delivering same mail to multiple backends.
+	- log: Log prefix update may have been lost if log process was busy.
+	  This could have caused log prefixes to be empty or in some cases
+	  reused between sessions, i.e. log lines could have been logged for the
+	  wrong user/session.
+	- mail_crypt: Plugin crashes if it's loaded only for some users. Fixes
+	  Panic: Module context mail_crypt_user_module missing.
+	- mail_crypt: When LMTP was delivering mails to both recipients with mail
+	  encryption enabled and not enabled, the non-encrypted recipients may
+	  have gotten mails encrypted anyway. This happened when the first
+	  recipient was encrypted (mail_crypt_save_version=2) and the 2nd
+	  recipient was not encrypted (mail_crypt_save_version=0).
+	- pop3: Session would crash if empty line was sent.
+	- stats: HTTP server leaked memory.
+	- submission-login: Long credentials, such as OAUTH2 tokens, were refused
+	  during SASL interactive due to submission server applying line length
+	  limits.
+	- submission-login: When proxying to remote host, authentication was not
+	  using interactive SASL when logging in using long credentials such as
+	  OAUTH2 tokens. This caused authentication to fail due to line length
+	  constraints in SMTP protocol.
+	- submission: Terminating the client connection with QUIT command after
+	  mail transaction is started with MAIL command and before it is
+	  finished with DATA/BDAT can cause a segfault crash.
+	- virtual: doveadm search queries with mailbox-guid as the only parameter
+	  crashes: Panic: file virtual-search.c: line 77 (virtual_search_get_records):
+	  assertion failed: (result != 0)
+
+v2.3.18 2022-02-03  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	* Removed mail_cache_lookup_finished event. This event wasn't especially
+	  useful, but it increased CPU usage significantly.
+	* fts: Don't index inline base64 encoded content in FTS indexes using
+	  the generic tokenizer. This reduces the FTS index sizes by removing
+	  input that is very unlikely to be searched for. See
+	  https://doc.dovecot.org/configuration_manual/fts/tokenization for
+	  details on how base64 is detected. Only applies when using libfts.
+	* lmtp: Session IDs are now preserved through proxied connections, so
+	  LMTP sessions can be tracked. This slightly changes the LMTP session
+	  ID format by appending ":Tn" (transaction), ":Pn" (proxy connection)
+	  and ":Rn" (recipient) counters after the session ID prefix.
+	+ Events now have "reason_code" field, which can provide a list of
+	  reasons why the event is happening. See
+	  https://doc.dovecot.org/admin_manual/event_reasons/
+	+ New events are added. See https://doc.dovecot.org/admin_manual/list_of_events/
+	+ fts: Added fts_header_excludes and fts_header_includes settings to
+	  specify which headers to index. See
+	  https://doc.dovecot.org/settings/plugin/fts-plugin#plugin-fts-setting-fts-header-excludes
+	  for configuration details.
+	+ fts: Initialize the textcat language detection library only once per
+	  process. This can reduce CPU usage if fts_languages setting has multiple
+	  languages listed and service indexer-worker { service_count } isn't 1.
+	  Only applies when using libfts.
+	+ lib-storage: Reduced CPU usage significantly for some operations that
+	  accessed lots of emails (e.g. fetching all flags in a folder, SORT, ...)
+	+ lib: DOVECOT_PREREQ() - Add micro version which enables compiling
+	  external plugins against different versions of Dovecot.
+	+ lmtp: Added new lmtp_verbose_replies setting that makes errors sent to
+	  the LMTP client much more verbose with details about why exactly
+	  backend proxy connections or commands are failing.
+	+ submission: Support implicit SASL EXTERNAL with
+	  submission_client_workarounds=implicit-auth-external. This allows
+	  automatically logging in when SSL client certificate is present.
+	- *-login: Statistics were disabled if stats process connection was lost.
+	- auth: Authentication master user login fails with SCRAM-* SASL mechanisms.
+	- auth: With auth_cache_verify_password_with_worker=yes, passdb extra
+	  fields in the auth cache got lost.
+	- doveadm: Fixed crash if zlib_save_level setting was specified,
+	  but zlib_save was unset. v2.3.15 regression.
+	- doveadm: Proxying can panic when flushing print output. v2.3.17
+	  regression. Fixes:
+	  Panic: file ioloop.c: line 865 (io_loop_destroy): assertion failed:
+	  (ioloop == current_ioloop)
+	- doveadm: stats add --group-by parameter didn't work.
+	- fts: Using email-address fts tokenizer could result in excessive memory
+	  usage with garbage email input. This could cause the indexer-worker
+	  processes to fail due to reaching the VSZ memory size limit.
+	  Only applies when using libfts.
+	- imap: A SEARCH command timing out while fts returns indexes may timeout
+	  returning "NO [SERVERBUG]", while it should return "NO [INUSE]" instead.
+	- imap: LIST-EXTENDED doesn't return STATUS for all folders. Sending
+	  LIST .. RETURN (SUBSCRIBED STATUS (...)) did not return STATUS for
+	  folders that are not subscribed when they have a child folder that is
+	  subscribed as mandated by IMAP RFCs.
+	- imapc: Mailbox vsize calculation crashed with
+	  Panic: file index-mailbox-size.c: line 344 (index_mailbox_vsize_hdr_add_missing):
+	  assertion failed: (mails_left > 0)
+	- indexer: If indexer-worker crashes, the request it was processing gets
+	  stuck in the indexer process. This stops indexing for the folder until
+	  indexer process is restarted. v2.3.14 regression.
+	- indexer: Process was slowly leaking memory for each indexing request.
+	- lib-event: Unnamed events were wrongly filtered out for event/metric
+	  filters like "event=abc OR something_independent_of_event_name".
+	- lib-index: 64-bit big endian CPUs handle last_used field in
+	  dovecot.index.cache wrong.
+	- lib-ssl-iostream: Fix buggy OpenSSL error handling without assert-crashing.
+	  If there is no error available, log it as an error instead of crashing.
+	  The previous fix for this in v2.3.11 was incomplete. Fixes
+	  Panic: file istream-openssl.c: line 51 (i_stream_ssl_read_real):
+	  assertion failed: (errno != 0)
+	- lmtp: Out-of-memory issues can happen when proxying large messages to
+	  LMTP backend servers that accept the message data too slow.
+	- master: HAProxy header parsing has read buffer overflow if provided
+	  header size is invalid. This happens only if inet_listener
+	  { haproxy=yes } is configured and only if the remote IP address is in
+	  haproxy_trusted_networks.
+	- old_stats: Plugin kept increasing memory usage, which became
+	  noticeable with long-running imap sessions.
+	- stats: Dynamically adding same metric multiple times causes multiple stats.
+	- submission-login: Authentication does not accept OAUTH2 token (or
+	  other very long credentials) because it considers the line to be too long.
+	- submission-login: Process can crash if HELO is pipelined with an
+	  invalid domain.
+	- submission-proxy: Don't use SASL-IR if it would make the AUTH command
+	  line longer than 512 bytes.
+	- submission: Service would crash if relay server authentication failed.
+	- virtual: FTS search in a virtual folder could crash if there are
+	  duplicate mailbox GUIDs. This mainly happened when user had both INBOX
+	  and INBOX/INBOX folders and the namespace prefix was INBOX/. Fixes
+	  Panic: file hash.c: line 252 (hash_table_insert_node):
+	  assertion failed: (opcode == HASH_TABLE_OP_UPDATE)
+	- virtual: If mailbox opening fails, the backend mailbox is leaked and
+	  process crashes when client disconnects. Fixes
+	  Panic: file mail-user.c: line 232 (mail_user_deinit):
+	  assertion failed: ((*user)->refcount == 1)
+	- virtual: Searching headers in virtual folders didn't always use
+	  full-text search indexes, if fts_enforced=no or body.
+
+v2.3.17.1 2021-12-07  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	- dsync: Add back accidentically removed parameters.
+	- lib-ssl-iostream: Fix assert-crash when OpenSSL returned syscall error
+	  without errno.
+	- master: Dovecot failed to start if ssl_ca was too large.
+
+v2.3.17 2021-10-28  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	* Dovecot now logs a warning if time seems to jump forward at least
+	  100 milliseconds.
+	* dict: Lines logged by the dict process now contain the dict name as
+	  the prefix.
+	* lib-index: mail_cache_fields, mail_always_cache_fields and
+	  mail_never_cache_fields now verifies that the listed header names are
+	  valid. Especially the UTF8 "–" character has sometimes been wrongly
+	  used instead of the ASCII "-".
+	+ *-login: Added login_proxy_rawlog_dir setting to capture
+	  rawlogs between proxy and backend.
+	+ dict: The server process now keeps the last 10 idle dict backends
+	  cached for maximum of 30 seconds. Practically this acts as a
+	  connection pool for dict-redis and dict-ldap. Note that this doesn't
+	  affect dict-sql, because it already had its own internal cache.
+	+ doveadm: New stats add/remove commands added to support changing the
+	  metrics configuration on runtime.
+	+ lazy_expunge: Added lazy_expunge_exclude settings to disable
+	  lazy_expunge for specific folders. \Special-use flags can be used as
+	  folder names.
+	+ lib-lua: Added a new helper function dovecot.restrict_global_variables()
+	  to disable or enable defining new global variables.
+	- LAYOUT=index List index rebuild was missing.
+	- LAYOUT=index: Duplicate GUIDs were not detected.
+	- acl: When using acl_ignore_namespace Dovecot attempted to access or
+	  create dovecot-acl-list even when the namespace should have been
+	  ignored. For virtual namespaces this could have yielded errors about
+	  "Read-only file system" or "Permission denied".
+	- auth: Setting the "master" passdb field to empty value would
+	  cause proxying to fail with an authentication error.
+	  Now an empty "master" field is ignored.
+	- doveadm-server: Duplicate error lines were sent for failed commands.
+	  This didn't normally cause visible problems, except when using
+	  wildcards in usernames or -A parameter to go through multiple users.
+	- doveadm-server: Logs written by doveadm-server were often missing log
+	  prefixes, especially mail_log_prefix for mail commands. Logs sent to
+	  doveadm TCP client were also missing log prefixes.
+	- doveadm: v2.3 regression: batch command always crashes.
+	- doveadm: v2.3.11 regression: Commands failed if ssl_cert or
+	  ssl_key files weren't readable by the user running doveadm, even
+	  though doveadm didn't actually use these settings
+	- imap-hibernate: Process may crash at deinit:
+	  Panic: file ioloop.c: line 928 (io_loop_destroy): assertion failed:
+	  (ioloop->cur_ctx == NULL).
+	- imap: Using imap_fetch_failure=no-after can cause assert-crash
+	  with some IMAP commands if reading the mail fails (e.g. wrong cached
+	  mail size). Fixes:
+	  Panic: file index-mail-headers.c: line 198 (index_mail_parse_header_init):
+	  assertion failed: (!mail->data.header_parser_initialized)
+	- imap: v2.3.10 regression: When using INDEXPVT to enable private
+	  \Seen flags (for shared or public namespaces) the STORE command did
+	  not send untagged replies for the \Seen flag changes.
+	- imap: v2.3.15 regression: If PREVIEW/SNIPPET is not the final FETCH
+	  option in the command, the IMAP FETCH response is broken.
+	- imap: v2.3.15 regression: MOVE command leaks mailbox if it can't be
+	  opened and crashes at deinit:
+	  Panic: file mail-user.c: line 229 (mail_user_deinit): assertion failed:
+	  ((*user)->refcount == 1).
+	- imapc: Copying nonexistent mail via imapc could have crashed. Fixes:
+	  Panic: file mail-storage.c: line 2385 (mailbox_transaction_commit_get_changes):
+	  assertion failed: (ret < 0 || seq_range_count(&changes_r->saved_uids) == save_count ||
+	  array_count(&changes_r->saved_uids) == 0).
+	- indexer: v2.3.15 regression: Process crashes if indexer-client
+	  disconnects while it's waiting for command reply. This happened for
+	  example if IMAP SEARCH triggered long fts indexing and the IMAP
+	  client disconnected while waiting for the reply.
+	- indexer: v2.3.15 regression: Process may have crashed in some situations.
+	- indexer: v2.3.15 regression: indexer-worker processes may not have
+	  reached the process_limit in some situations, possibly even using just
+	  one indexer-worker process even though there were many indexing
+	  requests queued.
+	- lib-compression: Reading lz4 compressed mdbox mails may crash. Fixes:
+	  Panic: file istream.c: line 345 (i_stream_read_memarea):
+	  assertion failed: (!stream->blocking).
+	- lib-compression: bench-compress crashes due to xz being read-only.
+	- lib-lua: Fix linking libdict_lua for non-GNU linkers when Lua support
+	  is disabled.
+	- lib-mail: There was no limit on how large an email header name could be.
+	  Processable header names are now limited to 1000 bytes.
+	- lib-oauth2: Dovecot disallowed JWT tokens if their validity time was
+	  older than token creation time (nbf < iat).
+	- lib-storage: Reduce memory footprint of certain storage operations.
+	- lib-storage: When listing mailboxes with storage name escape
+	  characters (^ or .) as part of the mailbox name, the listing could
+	  show corrupted mailbox names. Due to an issue in handling escaped
+	  parent folders, the listing of other mailbox names would become
+	  corrupted by prepending parts of the previously listed mailboxes
+	  parent folder as prefix to the actual mailbox names. The corruption
+	  can occur when using LAYOUT=INDEX and maildir or obox, or when using
+	  the listescape plugin.
+	- mail-crypt: Fix "-O" argument for "doveadm mailbox cryptokey password"
+	  command to be a boolean, and not expect a string.
+	- submission-login: Add support for not authenticating to next hop in
+	  submission proxying.
+	- submission-login: EHLO was not sent again after XCLIENT when doing
+	  submission proxying.
+	- virtual: Mailboxes do not correctly detect underlying mailboxes
+	  getting re-created even though they have a different UIDVALIDITY or
+	  GUID.
+
 v2.3.16 2021-08-06  Timo Sirainen <timo.sirainen@open-xchange.com>
 
 	* Any unexpected exit() will now result in a core dump. This can
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/ChangeLog 1:2.3.19.1+dfsg1-2/pigeonhole/ChangeLog
--- 1:2.3.16+dfsg1-3/pigeonhole/ChangeLog	2021-08-06 09:27:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/ChangeLog	2022-06-14 06:56:10.000000000 +0000
@@ -1,30 +1,534 @@
-2021-08-04 13:06:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (09c29328)
+2022-05-04 15:04:01 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (4eae2f79)
+
+    NEWS: Update date for 2.3.19 news
+
+
+M	NEWS
+
+2022-04-26 09:45:26 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (d0e82ef9)
+
+    NEWS: Add news for 0.5.19
+
+
+M	NEWS
+
+2022-01-12 12:54:05 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (d9023d0c)
+
+    NEWS: Add news for 0.5.18
+
+
+M	NEWS
+
+2022-04-26 09:34:49 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (963ed291)
+
+    configure: Update version
+
+
+M	configure.ac
+
+2022-02-05 17:14:16 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (7927a415)
+
+    managesieve-login: managesieve_proxy_parse_auth_reply() - Free input istream
+
+
+M	src/managesieve-login/managesieve-proxy.c
+
+2022-01-21 12:31:43 +0200 Martti Rannanjärvi <martti.rannanjarvi@open-xchange.com> (ede85010)
+
+    configure.ac: Set pigeonhole version to 0.5.100
+
+    This is to match the version 2.3.100 used in core.
+
+M	configure.ac
+
+2022-01-19 19:48:48 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (aba35b74)
+
+    configure.ac: Fix version to 2.3 dovecot
+
+
+M	configure.ac
+
+2022-01-19 17:14:03 +0100 Timo Sirainen <timo.sirainen@open-xchange.com> (ca2cb5fb)
+
+    configure: Update version to 0.5.devel
+
+
+M	configure.ac
+
+2021-11-30 12:50:17 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (c0951f32)
+
+    NEWS: Add news for 0.5.17.1
+
+
+M	NEWS
+
+2021-09-28 12:57:33 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (8bf12bb7)
+
+    NEWS: Add news for 0.5.17
+
+
+M	NEWS
+
+2021-12-03 09:05:16 +0200 Aki Tuomi <aki.tuomi@open-xchange.com> (df7907ae)
+
+    lib-sieve-tool: Sieve tools do not need SSL settings
+
+    Fixes argument list too long error.
+
+M	src/lib-sieve-tool/sieve-tool.c
+
+2019-10-06 19:36:32 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (e8af1162)
+
+    lib-sieve: sieve-stringlist - Assert that index for the index stringlist is
+    not 0.
+
+
+M	src/lib-sieve/sieve-stringlist.c
+
+2019-10-06 20:42:54 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (8c6a3928)
+
+    lib-sieve: index extension: Do not accept :index tag with zero value.
+
+
+M	src/lib-sieve/plugins/index/tag-index.c
+M	tests/extensions/index/errors.svtest
+M	tests/extensions/index/errors/syntax.sieve
+
+2019-10-06 20:59:43 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (a46bb1a9)
+
+    lib-sieve: plugins: index: Reformat tag-index.c.
+
+
+M	src/lib-sieve/plugins/index/tag-index.c
+
+2021-10-22 21:54:43 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3e2a8df4)
+
+    managesieve-login: Pass forward_ passdb args using XCLIENT command.
+
+
+M	src/managesieve-login/managesieve-proxy.c
+
+2021-10-22 21:51:08 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (a869b1a8)
+
+    managesieve-login: Add FORWARD for XCLIENT.
+
+    This allows passing passdb variables. They are prefixed with forward_ when 
+    imported to extra fields.
+
+M	src/managesieve-login/client.c
+
+2021-11-03 23:49:09 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a1b4cea5)
+
+    managesieve-login: managesieve-proxy - Fix pipelining beyond AUTHENTICATE.
+
+    Upon login success, the next line was erroneously skipped.
+
+M	src/managesieve-login/managesieve-proxy.c
+
+2021-06-22 17:06:58 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b5e69c08)
+
+    managesieve-login: Reformat managesieve-proxy.c.
+
+
+M	src/managesieve-login/managesieve-proxy.c
+
+2021-06-22 16:21:20 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (a38436fc)
+
+    managesieve-login: Reformat client.h.
+
+
+M	src/managesieve-login/client.h
+
+2021-06-22 16:18:49 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (d92ad7b1)
+
+    managesieve-login: Reformat client.c.
+
+
+M	src/managesieve-login/client.c
+
+2021-11-04 10:46:57 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (cbc1c489)
+
+    lib-sieve: util: edit-mail - Fix returning buffer-full condition when data
+    was read.
+
+    Fixes:
+
+    Panic: file istream-crlf.c: line 24 (i_stream_crlf_read_common): assertion
+    failed: (ret != -2) Panic: file istream.h: line 228 (i_stream_read_more):
+    assertion failed: (ret != -2)
+    (various other similar panics are possible)
+
+M	src/lib-sieve/util/edit-mail.c
+M	src/lib-sieve/util/test-edit-mail.c
+
+2020-08-26 00:38:15 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b7b4d24d)
+
+    lib-sieve: util: Reformat test-edit-mail.c.
+
+
+M	src/lib-sieve/util/test-edit-mail.c
+
+2020-08-26 00:00:04 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (ecc70828)
+
+    lib-sieve: util: Reformat edit-mail.h.
+
+
+M	src/lib-sieve/util/edit-mail.h
+
+2020-08-25 23:55:28 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (a4a22309)
+
+    lib-sieve: util: Reformat edit-mail.c.
+
+
+M	src/lib-sieve/util/edit-mail.c
+
+2021-11-02 09:54:08 -0400 Timo Sirainen <timo.sirainen@open-xchange.com> (29750ba5)
+
+    managesieve: Use MASTER_SERVICE_FLAG_DISABLE_SSL_SET when dumping capability
+
+    This prevents startup failures if ssl_ca has a large number of certificates.
+
+M	src/managesieve/main.c
+
+2021-10-28 00:40:58 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0b5e7b1a)
+
+    lib-sieve: sieve-result - Fix panic occurring upon temp fail after committed
+    keep-equivalent action.
+
+    The determination of whether a keep-equivalent action was already executed
+    is performed in sieve_result_implicit_keep_execute(), which can also be
+    called from sieve_result_implicit_keep_finalize(). But there, the
+    keep-equivalent status was checked before any call to
+    sieve_result_implicit_keep_execute(), which would thus yield the wrong
+    result.
+
+    This change also makes the associated debug message more specific (otherwise
+    two identical debug messages are logged) and an assert is added that makes
+    sure the keep-equivalent action is actually finalized when the implicit keep
+    was supposed to be finalized.
+
+    Panic was:
+
+    Panic: file sieve-result.c: line 1706 (sieve_result_implicit_keep_finalize):
+    assertion failed: (aexec_keep->state ==
+    SIEVE_ACTION_EXECUTION_STATE_EXECUTED)
+
+M	src/lib-sieve/sieve-result.c
+
+2021-10-28 00:45:15 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (d1c44652)
+
+    lib-sieve: sieve-result - Move finalize debug message for implicit keep.
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-09-20 22:09:48 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (27ab897f)
+
+    plugins: imapsieve: imap-sieve-storage - Make sure mail_set_uid() is never
+    called with uid=0.
+
+
+M	src/plugins/imapsieve/imap-sieve-storage.c
+
+2021-08-04 13:06:23 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (c6d3cabf)
 
     NEWS: Add news for 0.5.16
 
 
 M	NEWS
 
-2021-06-11 11:58:05 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (91dd721d)
+2021-06-11 11:58:05 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (216e41c2)
 
     NEWS: Update release date
 
 
 M	NEWS
 
-2021-04-22 15:20:32 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (0943d44b)
+2021-04-22 15:20:32 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (ab56f331)
 
     NEWS: Add news for 0.5.15
 
 
 M	NEWS
 
-2021-07-13 11:58:54 +0300 Aki Tuomi <aki.tuomi@open-xchange.com> (4fdcf3e6)
+2021-09-17 11:40:41 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (837bd100)
 
-    configure: Update version to 0.5.16
+    lib-sieve: plugins: duplicate: ext-duplicate-common - Add debug messages for
+    action finish stage.
 
 
-M	configure.ac
+M	src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
+
+2021-06-08 03:50:53 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (5e66dfca)
+
+    lib-sieve: Adjust to changes in mail-duplicate API.
+
+
+M	src/lib-sieve/cmd-redirect.c
+M	src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
+M	src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
+M	src/lib-sieve/plugins/duplicate/tst-duplicate.c
+M	src/lib-sieve/plugins/vacation/cmd-vacation.c
+M	src/lib-sieve/sieve-actions.c
+M	src/lib-sieve/sieve-actions.h
+M	src/lib-sieve/sieve-execute.c
+M	src/lib-sieve/sieve-execute.h
+M	src/lib-sieve/sieve-result.c
+M	src/lib-sieve/sieve-result.h
+M	src/lib-sieve/sieve-types.h
+M	src/lib-sieve/sieve.c
+M	src/plugins/imap-filter-sieve/imap-filter-sieve.c
+M	src/plugins/imapsieve/imap-sieve.c
+M	src/plugins/lda-sieve/lda-sieve-plugin.c
+M	src/sieve-tools/sieve-test.c
+M	src/testsuite/testsuite-script.c
+
+2021-09-19 13:18:24 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (0f2c9569)
+
+    lib-sieve: Adjust to changes in Dovecot file-lock API.
+
+
+M	src/lib-sieve/sieve-binary-file.c
+
+2021-09-16 20:04:06 +0300 Timo Sirainen <timo.sirainen@open-xchange.com> (86170b0f)
+
+    m4: dovecot.m4 - Sync with core
+
+
+M	m4/dovecot.m4
+
+2021-07-21 00:07:21 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (79e51ecf)
+
+    lib-sieve: Disable CPU time limit by default when Sieve is executed in mail
+    storage.
+
+
+M	src/lib-sieve/sieve-settings.c
+M	src/lib-sieve/sieve.c
+
+2021-08-18 13:27:50 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4596d399)
+
+    lib-sieve: sieve-result - Indicate in sieve_result_transaction_finalize()
+    debug message whether actions were committed.
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 13:26:26 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (65d771c1)
+
+    lib-sieve: sieve-result - Indicate in sieve_result_transaction_execute()
+    debug message whether actions were executed.
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:43:05 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (dbf5b62b)
+
+    lib-sieve: sieve-result - Add debug messages for temp failure handling.
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 13:29:41 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (81bd53d6)
+
+    lib-sieve: sieve-result - Assert that implicit keep is executed in
+    sieve_result_implicit_keep_finalize().
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:54:25 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (293f0027)
+
+    lib-sieve: sieve-result - Fix resource leak occurring when implicit keep is
+    executed before temporary failure at commit.
+
+    In the commit phase the implicit keep was never finalized, meaning that it
+    was not rolled back and thus not cleaned up properly. This leads to a memory
+    leak and a mailbox reference leak. This in turn causes an assert crash at
+    the end of delivery when the mail user is destroyed.
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:56:32 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (f2b81cc6)
+
+    lib-sieve: sieve-result - Fix handling of resource limit status after
+    implicit keep commit.
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:46:18 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (92b4b06d)
+
+    lib-sieve: sieve-result - Skip implicit keep in execution stage upon temp
+    failure.
+
+    It will be executed in the commit phase if necessary; don't do it early; it
+    will only be rolled back.
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:26:32 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (c84e6e5d)
+
+    lib-sieve: sieve-result - Move temp failure status checks into
+    sieve_result_implicit_keep_finalize().
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 02:07:01 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (10e347e3)
+
+    lib-sieve: sieve-result - Move temp failure status checks into
+    sieve_result_implicit_keep_execute().
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 01:40:57 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (291f2fdb)
+
+    lib-sieve: sieve-result - Remove success parameter from
+    sieve_result_implicit_keep_finalize().
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-18 01:44:23 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (54e020c1)
+
+    lib-sieve: sieve-result - Remove success parameter from
+    sieve_result_implicit_keep_execute().
+
+
+M	src/lib-sieve/sieve-result.c
+
+2021-08-17 19:09:13 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9f300239)
+
+    lib-sieve: sieve-interpreter - Fix field mixup in debug message.
+
+
+M	src/lib-sieve/sieve-interpreter.c
+
+2021-08-19 02:46:22 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (4c0acd83)
+
+    lib-sieve: sieve-binary-debug - Properly handle line decrements.
+
+
+M	src/lib-sieve/sieve-binary-debug.c
+
+2021-08-19 02:20:37 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (2cb70c69)
+
+    lib-sieve: plugins: include: ext-include-common - Use ENUM_NEGATE() macro
+    where necessary.
+
+
+M	src/lib-sieve/plugins/include/ext-include-common.c
+
+2021-08-19 01:30:17 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9222ce45)
+
+    lib-sieve: sieve-ast - Use sieve_number_t for number values.
+
+
+M	src/lib-sieve/sieve-ast.c
+M	src/lib-sieve/sieve-ast.h
+
+2021-08-19 01:29:48 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (efdb74cf)
+
+    lib-sieve: sieve-lexer - Use sieve_number_t for number values.
+
+
+M	src/lib-sieve/sieve-lexer.h
+
+2021-08-19 01:26:25 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (ee9f9be3)
+
+    lib-sieve: Add SIEVE_PRI_NUMBER definition.
+
+
+M	src/lib-sieve/sieve-common.h
+
+2020-10-12 21:21:12 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (37dfe0d8)
+
+    lib-sieve: plugins: include: Reformat ext-include-common.c.
+
+
+M	src/lib-sieve/plugins/include/ext-include-common.c
+
+2020-03-09 23:55:33 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (031967f5)
+
+    lib-sieve: Reformat sieve-ast.h.
+
+
+M	src/lib-sieve/sieve-ast.h
+
+2020-03-09 23:48:48 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (06213060)
+
+    lib-sieve: Reformat sieve-ast.c.
+
+
+M	src/lib-sieve/sieve-ast.c
+
+2021-08-12 22:26:41 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (d6e0a9e3)
+
+    lib-sieve: cmd-redirect - Avoid cancelling implicit keep upon mail loop
+    detection.
+
+
+M	src/lib-sieve/cmd-redirect.c
+
+2021-08-13 00:36:58 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (87df7e30)
+
+    lib-sieve: cmd-redirect - Move dupeid to action exection transaction struct.
+
+
+M	src/lib-sieve/cmd-redirect.c
+
+2021-08-13 00:26:20 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (b25a511f)
+
+    lib-sieve: cmd-redirect - Move msg_id to action exection transaction struct.
+
+
+M	src/lib-sieve/cmd-redirect.c
+
+2021-08-13 00:08:25 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (19335d87)
+
+    lib-sieve: Remove spurious whitespace in cmd-redirect.c.
+
+
+M	src/lib-sieve/cmd-redirect.c
+
+2021-08-10 17:31:30 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (9e4160d6)
+
+    lib-sieve: sieve-result - Fix omission of action finalization happening upon
+    result execution failure.
+
+    This occurred because the code that checks for a deferred keep action in 
+    sieve_result_implicit_keep_execute() could end up "finding" an action that 
+    wasn't actually a deferred keep. That action was then erroneously promoted
+    to the FINALIZED state, which meant that commit/rollback was never executed.
+
+M	src/lib-sieve/sieve-result.c
+
+2021-06-30 01:36:33 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (3f66b725)
+
+    lib-sieve: plugins: enotify: mailto: uri-mailto - Change _is_qchar() to
+    accept char parameter.
+
+    It is used like that everywhere and ubsan complains about passing char to 
+    unsigned char parameter.
+
+M	src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
+
+2020-03-26 17:21:07 +0100 Stephan Bosch <stephan.bosch@open-xchange.com> (a679bce4)
+
+    lib-sieve: plugins: enotify: mailto: Reformat uri-mailto.c.
+
+
+M	src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
+
+2021-05-10 16:23:45 +0100 Siavash Tavakoli <siavash.tavakoli@open-xchange.com> (0aa68128)
+
+    sieve-dict: Adjust to core dict API changes
+
+
+M	src/lib-sieve/storage/dict/sieve-dict-script.c
+M	src/lib-sieve/storage/dict/sieve-dict-storage.c
 
 2021-06-02 23:19:46 +0200 Stephan Bosch <stephan.bosch@open-xchange.com> (7090e625)
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/configure 1:2.3.19.1+dfsg1-2/pigeonhole/configure
--- 1:2.3.16+dfsg1-3/pigeonhole/configure	2021-08-06 09:26:52.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/configure	2022-06-14 06:56:03.000000000 +0000
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Pigeonhole 0.5.16.
+# Generated by GNU Autoconf 2.69 for Pigeonhole 0.5.19.
 #
 # Report bugs to <dovecot@dovecot.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='Pigeonhole'
 PACKAGE_TARNAME='dovecot-2.3-pigeonhole'
-PACKAGE_VERSION='0.5.16'
-PACKAGE_STRING='Pigeonhole 0.5.16'
+PACKAGE_VERSION='0.5.19'
+PACKAGE_STRING='Pigeonhole 0.5.19'
 PACKAGE_BUGREPORT='dovecot@dovecot.org'
 PACKAGE_URL=''
 
@@ -655,6 +655,11 @@ DOVECOT_PLUGIN_DEPS_FALSE
 DOVECOT_PLUGIN_DEPS_TRUE
 DOVECOT_INSTALLED_FALSE
 DOVECOT_INSTALLED_TRUE
+LIBDOVECOT_LUA_DEPS
+LIBDOVECOT_LUA
+DOVECOT_LUA_CFLAGS
+DOVECOT_LUA_LIBS
+LIBDOVECOT_LUA_INCLUDE
 LIBDOVECOT_LIBFTS_INCLUDE
 LIBDOVECOT_ACL_INCLUDE
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE
@@ -1417,7 +1422,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures Pigeonhole 0.5.16 to adapt to many kinds of systems.
+\`configure' configures Pigeonhole 0.5.19 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1489,7 +1494,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of Pigeonhole 0.5.16:";;
+     short | recursive ) echo "Configuration of Pigeonhole 0.5.19:";;
    esac
   cat <<\_ACEOF
 
@@ -1618,7 +1623,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-Pigeonhole configure 0.5.16
+Pigeonhole configure 0.5.19
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1987,7 +1992,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by Pigeonhole $as_me 0.5.16, which was
+It was created by Pigeonhole $as_me 0.5.19, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2337,7 +2342,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 cat >>confdefs.h <<_ACEOF
-#define PIGEONHOLE_ABI_VERSION "0.5.ABIv16($PACKAGE_VERSION)"
+#define PIGEONHOLE_ABI_VERSION "0.5.ABIv19($PACKAGE_VERSION)"
 _ACEOF
 
 
@@ -2877,7 +2882,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='dovecot-2.3-pigeonhole'
- VERSION='0.5.16'
+ VERSION='0.5.19'
 
 
 # Some tools Automake needs.
@@ -13127,6 +13132,9 @@ fi
 
 
 
+
+
+
 	 if test "$DOVECOT_INSTALLED" = "yes"; then
   DOVECOT_INSTALLED_TRUE=
   DOVECOT_INSTALLED_FALSE='#'
@@ -14235,7 +14243,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wri
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by Pigeonhole $as_me 0.5.16, which was
+This file was extended by Pigeonhole $as_me 0.5.19, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -14301,7 +14309,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-Pigeonhole config.status 0.5.16
+Pigeonhole config.status 0.5.19
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/configure.ac 1:2.3.19.1+dfsg1-2/pigeonhole/configure.ac
--- 1:2.3.16+dfsg1-3/pigeonhole/configure.ac	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/configure.ac	2022-06-14 06:55:57.000000000 +0000
@@ -2,8 +2,8 @@ AC_PREREQ([2.59])
 
 # Be sure to update ABI version also if anything changes that might require
 # recompiling plugins. Most importantly that means if any structs are changed.
-AC_INIT([Pigeonhole], [0.5.16], [dovecot@dovecot.org], [dovecot-2.3-pigeonhole])
-AC_DEFINE_UNQUOTED([PIGEONHOLE_ABI_VERSION], "0.5.ABIv16($PACKAGE_VERSION)", [Pigeonhole ABI version])
+AC_INIT([Pigeonhole], [0.5.19], [dovecot@dovecot.org], [dovecot-2.3-pigeonhole])
+AC_DEFINE_UNQUOTED([PIGEONHOLE_ABI_VERSION], "0.5.ABIv19($PACKAGE_VERSION)", [Pigeonhole ABI version])
 
 AC_CONFIG_AUX_DIR([.])
 AC_CONFIG_SRCDIR([src])
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/example-config/conf.d/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/example-config/conf.d/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/example-config/conf.d/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/example-config/conf.d/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -179,6 +179,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -224,6 +226,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/example-config/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/example-config/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/example-config/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/example-config/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -239,6 +239,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -284,6 +286,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/extensions/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/extensions/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/extensions/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/extensions/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -179,6 +179,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -224,6 +226,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/locations/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/locations/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/locations/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/locations/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -179,6 +179,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -224,6 +226,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -239,6 +239,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -284,6 +286,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/man/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/man/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/man/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/man/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -181,6 +181,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -226,6 +228,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/doc/plugins/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/doc/plugins/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/doc/plugins/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/doc/plugins/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -179,6 +179,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -224,6 +226,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/m4/dovecot.m4 1:2.3.19.1+dfsg1-2/pigeonhole/m4/dovecot.m4
--- 1:2.3.16+dfsg1-3/pigeonhole/m4/dovecot.m4	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/m4/dovecot.m4	2022-06-14 06:55:57.000000000 +0000
@@ -6,7 +6,7 @@ dnl This file is free software; the auth
 dnl unlimited permission to copy and/or distribute it, with or without
 dnl modifications, as long as this notice is preserved.
 
-# serial 32
+# serial 34
 
 dnl
 dnl Check for support for D_FORTIFY_SOURCE=2
@@ -430,7 +430,8 @@ AC_DEFUN([DC_DOVECOT],[
 	AX_SUBST_L([LIBDOVECOT_DEPS], [LIBDOVECOT_LOGIN_DEPS], [LIBDOVECOT_SQL_DEPS], [LIBDOVECOT_SSL_DEPS], [LIBDOVECOT_COMPRESS_DEPS], [LIBDOVECOT_LDA_DEPS], [LIBDOVECOT_STORAGE_DEPS], [LIBDOVECOT_DSYNC_DEPS], [LIBDOVECOT_LIBFTS_DEPS])
 	AX_SUBST_L([LIBDOVECOT_INCLUDE], [LIBDOVECOT_LDA_INCLUDE], [LIBDOVECOT_AUTH_INCLUDE], [LIBDOVECOT_DOVEADM_INCLUDE], [LIBDOVECOT_SERVICE_INCLUDE], [LIBDOVECOT_STORAGE_INCLUDE], [LIBDOVECOT_LOGIN_INCLUDE], [LIBDOVECOT_SQL_INCLUDE])
 	AX_SUBST_L([LIBDOVECOT_IMAP_LOGIN_INCLUDE], [LIBDOVECOT_CONFIG_INCLUDE], [LIBDOVECOT_IMAP_INCLUDE], [LIBDOVECOT_POP3_INCLUDE], [LIBDOVECOT_SUBMISSION_INCLUDE], [LIBDOVECOT_LMTP_INCLUDE], [LIBDOVECOT_DSYNC_INCLUDE], [LIBDOVECOT_IMAPC_INCLUDE], [LIBDOVECOT_FTS_INCLUDE])
-	AX_SUBST_L([LIBDOVECOT_NOTIFY_INCLUDE], [LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE], [LIBDOVECOT_ACL_INCLUDE], [LIBDOVECOT_LIBFTS_INCLUDE])
+	AX_SUBST_L([LIBDOVECOT_NOTIFY_INCLUDE], [LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE], [LIBDOVECOT_ACL_INCLUDE], [LIBDOVECOT_LIBFTS_INCLUDE], [LIBDOVECOT_LUA_INCLUDE])
+	AX_SUBST_L([DOVECOT_LUA_LIBS], [DOVECOT_LUA_CFLAGS], [LIBDOVECOT_LUA], [LIBDOVECOT_LUA_DEPS])
 
 	AM_CONDITIONAL(DOVECOT_INSTALLED, test "$DOVECOT_INSTALLED" = "yes")
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/Makefile.in	2022-06-14 06:56:03.000000000 +0000
@@ -267,6 +267,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -312,6 +314,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/NEWS 1:2.3.19.1+dfsg1-2/pigeonhole/NEWS
--- 1:2.3.16+dfsg1-3/pigeonhole/NEWS	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/NEWS	2022-06-14 06:55:57.000000000 +0000
@@ -1,3 +1,51 @@
+v0.5.19  2022-05-10  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	* No changes - release done to keep version numbers synced.
+
+v0.5.18  2022-02-03  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	- duplicate: Users without a home directory can crash with Sieve when
+	  using duplicate database. v2.3.17 regression.
+	- imapsieve: When mail was expunged when processing imapsieve events, a
+	  crash could occur. Fixes Panic: file mail-index-map.c:
+	  line 558 (mail_index_map_lookup_seq_range): assertion failed: (first_uid > 0)
+	- managesieve-login: Proxy didn't support forwarding the forward_* passdb fields.
+	- redirect: Sieve would crash if redirect after keep-equivalent action failed.
+	- sieve: Interpreter crashes when the Sieve index extension is used with
+	  index zero.
+	- vnd.dovecot.filter: Envelope sender string may become corrupted when
+	  Sieve scripts are using vnd.dovecot.filter. This could end up
+	  corrupting mbox's From line and return wrong envelope sender string in
+	  Sieve tests.
+
+v0.5.17.1 2021-12-07  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	- managesieve: Dovecot failed to start if ssl_ca was too large.
+	- lib-sieve-tool: Binaries failed to run if ssl_ca was too large.
+
+v0.5.17 2021-10-28  Aki Tuomi <aki.tuomi@open-xchange.com>
+
+	- duplicate: The Sieve duplicate test is prone to false negatives when
+	  the user receives many e-mails concurrently, meaning that duplicate
+	  deliveries can still occur.
+	- fileinto: v2.3.16 regression: Sieve delivery crashes if mail is
+	  delivered to non-existing and existing folder.
+	- imap-filter-sieve: v2.3.15 regression: The CPU limits on Sieve
+	  execution are too easily exceeded in IMAP context (the IMAPSieve and
+	  FILTER=SIEVE capabilities). Changed the default to unlimited CPU time
+	  for IMAP context, since similar excessive resource usage can be caused
+	  by other means as well. The CPU limits on Sieve scripts executed at
+	  LDA/LMTP delivery are still enforced by default.
+	- redirect:  The Sieve redirect action has protections against users
+	  triggering mail loops. Unfortunately, the detection of a redirect mail
+	  loop sometimes causes the message to get lost if no other Sieve action
+	  is applied that delivers the message somewhere else.
+	- redirect: v2.3.16 regression: With certain Sieve scripts if redirect
+	  fails due to temporary failure, the lmtp process may crash after the
+	  delivery. Fixes:
+	  Panic: file mail-user.c: line 229 (mail_user_deinit):
+	  assertion failed: ((*user)->refcount == 1).
+
 v0.5.16 2021-08-06  Timo Sirainen <timo.sirainen@open-xchange.com>
 
 	* .dovecot.sieve.log file now includes year in the header.
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/pigeonhole-version.h 1:2.3.19.1+dfsg1-2/pigeonhole/pigeonhole-version.h
--- 1:2.3.16+dfsg1-3/pigeonhole/pigeonhole-version.h	2021-08-06 09:27:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/pigeonhole-version.h	2022-06-14 06:56:10.000000000 +0000
@@ -1,6 +1,6 @@
 #ifndef PIGEONHOLE_VERSION_H
 #define PIGEONHOLE_VERSION_H
 
-#define PIGEONHOLE_VERSION_FULL PIGEONHOLE_VERSION" (09c29328)"
+#define PIGEONHOLE_VERSION_FULL PIGEONHOLE_VERSION" (4eae2f79)"
 
 #endif /* PIGEONHOLE_VERSION_H */
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-managesieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-managesieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-managesieve/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-managesieve/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/cmd-redirect.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/cmd-redirect.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/cmd-redirect.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/cmd-redirect.c	2022-06-14 06:55:57.000000000 +0000
@@ -87,6 +87,9 @@ act_redirect_check_duplicate(const struc
 static void
 act_redirect_print(const struct sieve_action *action,
 		   const struct sieve_result_print_env *rpenv, bool *keep);
+
+static int
+act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context);
 static int
 act_redirect_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
 		    bool *keep);
@@ -99,6 +102,7 @@ const struct sieve_action_def act_redire
 	.equals = act_redirect_equals,
 	.check_duplicate = act_redirect_check_duplicate,
 	.print = act_redirect_print,
+	.start = act_redirect_start,
 	.execute = act_redirect_execute,
 	.commit = act_redirect_commit,
 };
@@ -155,8 +159,6 @@ cmd_redirect_validate(struct sieve_valid
 	return TRUE;
 }
 
-
-
 /*
  * Code generation
  */
@@ -254,6 +256,13 @@ cmd_redirect_operation_execute(const str
  * Action implementation
  */
 
+struct act_redirect_transaction {
+	const char *msg_id, *new_msg_id;
+	const char *dupeid;
+
+	bool skip_redirect:1;
+};
+
 static bool
 act_redirect_equals(const struct sieve_script_env *senv ATTR_UNUSED,
 		    const struct sieve_action *act1,
@@ -455,6 +464,8 @@ act_redirect_get_duplicate_id(struct act
 	else
 		recipient = sieve_get_user_email(eenv->svinst);
 
+	pool_t pool = sieve_result_pool(aenv->result);
+
 	/* Base the duplicate ID on:
 	   - the message id
 	   - the recipient running this Sieve script
@@ -463,8 +474,8 @@ act_redirect_get_duplicate_id(struct act
 		   the original message
 	   - if the message came through a mailing list: the mailinglist ID
 	 */
-	*dupeid_r = t_strdup_printf(
-		"%s-%s-%s-%s-%s", msg_id,
+	*dupeid_r = p_strdup_printf(
+		pool, "%s-%s-%s-%s-%s", msg_id,
 		(recipient != NULL ? smtp_address_encode(recipient) : ""),
 		smtp_address_encode(ctx->to_address),
 		(resent_id != NULL ? resent_id : ""),
@@ -522,32 +533,33 @@ act_redirect_check_loop_header(const str
 }
 
 static int
-act_redirect_execute(const struct sieve_action_exec_env *aenv ATTR_UNUSED,
-		     void *tr_context ATTR_UNUSED, bool *keep)
+act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context)
 {
-	/* Cancel implicit keep */
-	*keep = FALSE;
+	struct act_redirect_transaction *trans;
+	pool_t pool = sieve_result_pool(aenv->result);
+
+	/* Create transaction context */
+	trans = p_new(pool, struct act_redirect_transaction, 1);
+	*tr_context = trans;
 
 	return SIEVE_EXEC_OK;
 }
 
 static int
-act_redirect_commit(const struct sieve_action_exec_env *aenv,
-		    void *tr_context ATTR_UNUSED)
+act_redirect_execute(const struct sieve_action_exec_env *aenv,
+		     void *tr_context, bool *keep)
 {
 	const struct sieve_action *action = aenv->action;
 	const struct sieve_execute_env *eenv = aenv->exec_env;
 	struct sieve_instance *svinst = eenv->svinst;
 	struct act_redirect_context *ctx =
 		(struct act_redirect_context *)action->context;
+	struct act_redirect_transaction *trans = tr_context;
 	struct sieve_message_context *msgctx = aenv->msgctx;
 	struct mail *mail = (action->mail != NULL ?
 			     action->mail : sieve_message_get_mail(msgctx));
 	const struct sieve_message_data *msgdata = eenv->msgdata;
-	const struct sieve_script_env *senv = eenv->scriptenv;
-	const char *msg_id = msgdata->id, *new_msg_id = NULL;
-	const char *dupeid = NULL;
-	bool loop_detected = FALSE;
+	bool duplicate, loop_detected = FALSE;
 	int ret;
 
 	/*
@@ -555,20 +567,38 @@ act_redirect_commit(const struct sieve_a
 	 */
 
 	/* Create Message-ID for the message if it has none */
-	if (msg_id == NULL)
-		msg_id = new_msg_id = sieve_message_get_new_id(eenv->svinst);
+	trans->msg_id = msgdata->id;
+	if (trans->msg_id == NULL) {
+		pool_t pool = sieve_result_pool(aenv->result);
+		trans->msg_id = trans->new_msg_id =
+			p_strdup(pool, sieve_message_get_new_id(svinst));
+	}
 
 	/* Create ID for duplicate database lookup */
-	ret = act_redirect_get_duplicate_id(ctx, aenv, msg_id, &dupeid);
+	ret = act_redirect_get_duplicate_id(ctx, aenv, trans->msg_id,
+					    &trans->dupeid);
 	if (ret != SIEVE_EXEC_OK)
 		return ret;
-	i_assert(dupeid != NULL);
+	i_assert(trans->dupeid != NULL);
 
 	/* Check whether we've seen this message before */
-	if (sieve_action_duplicate_check(senv, dupeid, strlen(dupeid))) {
+	ret = sieve_action_duplicate_check(aenv, trans->dupeid,
+					   strlen(trans->dupeid),
+					   &duplicate);
+	if (ret < SIEVE_EXEC_OK) {
+		sieve_result_critical(
+			aenv, "failed to check for duplicate forward",
+			"failed to check for duplicate forward to <%s>%s",
+			smtp_address_encode(ctx->to_address),
+			(ret == SIEVE_EXEC_TEMP_FAILURE ?
+			 " (temporaty failure)" : ""));
+		return ret;
+	}
+	if (duplicate) {
 		sieve_result_global_log(
 			aenv, "discarded duplicate forward to <%s>",
 			smtp_address_encode(ctx->to_address));
+		trans->skip_redirect = TRUE;
 		return SIEVE_EXEC_OK;
 	}
 
@@ -582,18 +612,43 @@ act_redirect_commit(const struct sieve_a
 			aenv, "not forwarding message to <%s>: "
 			"the `x-sieve-redirected-from' header indicates a mail loop",
 			smtp_address_encode(ctx->to_address));
+		trans->skip_redirect = TRUE;
 		return SIEVE_EXEC_OK;
 	}
 
+	/* Cancel implicit keep */
+	*keep = FALSE;
+
+	return SIEVE_EXEC_OK;
+}
+
+static int
+act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
+{
+	const struct sieve_action *action = aenv->action;
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+	struct sieve_instance *svinst = eenv->svinst;
+	struct act_redirect_context *ctx =
+		(struct act_redirect_context *)action->context;
+	struct sieve_message_context *msgctx = aenv->msgctx;
+	struct mail *mail = (action->mail != NULL ?
+			     action->mail : sieve_message_get_mail(msgctx));
+	struct act_redirect_transaction *trans = tr_context;
+	int ret;
+
+	if (trans->skip_redirect)
+		return SIEVE_EXEC_OK;
+
 	/*
 	 * Try to forward the message
 	 */
 
-	ret = act_redirect_send(aenv, mail, ctx, new_msg_id);
+	ret = act_redirect_send(aenv, mail, ctx, trans->new_msg_id);
 	if (ret == SIEVE_EXEC_OK) {
 		/* Mark this message id as forwarded to the specified
 		   destination */
-		sieve_action_duplicate_mark(senv, dupeid, strlen(dupeid),
+		sieve_action_duplicate_mark(
+			aenv, trans->dupeid, strlen(trans->dupeid),
 			ioloop_time + svinst->redirect_duplicate_period);
 
 		eenv->exec_status->significant_action_executed = TRUE;
@@ -615,5 +670,3 @@ act_redirect_commit(const struct sieve_a
 
 	return ret;
 }
-
-
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -361,6 +361,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -406,6 +408,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/body/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/body/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/body/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/body/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/comparator-i-ascii-numeric/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -201,6 +201,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -246,6 +248,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/copy/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -230,6 +230,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -275,6 +277,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/date/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/date/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/date/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/date/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.c	2022-06-14 06:55:57.000000000 +0000
@@ -109,18 +109,22 @@ act_duplicate_mark_finish(const struct s
 			  void *tr_context ATTR_UNUSED, int status)
 {
 	const struct sieve_execute_env *eenv = aenv->exec_env;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct act_duplicate_mark_data *data =
 		(struct act_duplicate_mark_data *)aenv->action->context;
 
-	if (status != SIEVE_EXEC_OK)
+	if (status != SIEVE_EXEC_OK) {
+		e_debug(aenv->event, "Not marking duplicate (status=%s)",
+			sieve_execution_exitcode_to_str(status));
 		return;
+	}
+
+	e_debug(aenv->event, "Marking duplicate");
 
 	/* Message was handled successfully, so track duplicate for this
 	 * message.
 	 */
 	eenv->exec_status->significant_action_executed = TRUE;
-	sieve_action_duplicate_mark(senv, data->hash, sizeof(data->hash),
+	sieve_action_duplicate_mark(aenv, data->hash, sizeof(data->hash),
 				    ioloop_time + data->period);
 }
 
@@ -168,11 +172,10 @@ ext_duplicate_hash(string_t *handle, con
 
 int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 			const char *value, size_t value_len,
-			sieve_number_t period, bool last)
+			sieve_number_t period, bool last, bool *duplicate_r)
 {
 	const struct sieve_execute_env *eenv = renv->exec_env;
 	const struct sieve_extension *this_ext = renv->oprtn->ext;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct ext_duplicate_context *rctx;
 	bool duplicate = FALSE;
 	pool_t msg_pool = NULL, result_pool = NULL;
@@ -180,16 +183,19 @@ int ext_duplicate_check(const struct sie
 	struct ext_duplicate_hash *hash_record = NULL;
 	struct ext_duplicate_handle *handle_record = NULL;
 	struct act_duplicate_mark_data *act;
+	int ret;
 
-	if (!sieve_action_duplicate_check_available(senv)) {
+	*duplicate_r = FALSE;
+
+	if (!sieve_execute_duplicate_check_available(eenv)) {
 		sieve_runtime_warning(
 			renv, NULL, "duplicate test: "
 			"duplicate checking not available in this context");
-		return 0;
+		return SIEVE_EXEC_OK;
 	}
 
 	if (value == NULL)
-		return 0;
+		return SIEVE_EXEC_OK;
 
 	/* Create hash */
 	ext_duplicate_hash(handle, value, value_len, last, hash);
@@ -221,7 +227,9 @@ int ext_duplicate_check(const struct sie
 				(handle == NULL ? NULL : str_c(handle));
 			if (null_strcmp(rhandle->handle, handle_str) == 0 &&
 			    rhandle->last == last)
-				return (rhandle->duplicate ? 1 : 0);
+				return (rhandle->duplicate ?
+				        SIEVE_DUPLICATE_CHECK_RESULT_EXISTS :
+					SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND);
 		}
 	}
 
@@ -234,15 +242,25 @@ int ext_duplicate_check(const struct sie
 	act->last = last;
 
 	/* Check duplicate */
-	duplicate = sieve_action_duplicate_check(senv, hash, sizeof(hash));
-	if (!duplicate && last) {
+	ret = sieve_execute_duplicate_check(eenv, hash, sizeof(hash),
+					    &duplicate);
+	if (ret >= SIEVE_EXEC_OK && !duplicate && last) {
 		unsigned char no_last_hash[MD5_RESULTLEN];
 
 		/* Check for entry without :last */
 		ext_duplicate_hash(handle, value, value_len,
 				   FALSE, no_last_hash);
-		sieve_action_duplicate_check(senv, no_last_hash,
-					     sizeof(no_last_hash));
+		ret = sieve_execute_duplicate_check(
+			eenv, no_last_hash, sizeof(no_last_hash),
+			&duplicate);
+	}
+	if (ret < SIEVE_EXEC_OK) {
+		sieve_runtime_critical(
+			renv, NULL, "failed to check for duplicate",
+			"failed to check for duplicate%s",
+			(ret == SIEVE_EXEC_TEMP_FAILURE ?
+			 " (temporary failure)" : ""));
+		return ret;
 	}
 
 	/* We may only mark the message as duplicate when Sieve script executes
@@ -253,7 +271,7 @@ int ext_duplicate_check(const struct sie
 		if (sieve_result_add_action(renv, NULL, NULL,
 					    &act_duplicate_mark,
 					    NULL, (void *) act, 0, FALSE) < 0)
-			return -1;
+			return SIEVE_EXEC_FAILURE;
 	}
 
 	/* Cache result */
@@ -273,6 +291,8 @@ int ext_duplicate_check(const struct sie
 	handle_record->last = last;
 	handle_record->duplicate = duplicate;
 
-	return ( duplicate ? 1 : 0 );
+	*duplicate_r = duplicate;
+
+	return SIEVE_EXEC_OK;
 }
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/ext-duplicate-common.h	2022-06-14 06:55:57.000000000 +0000
@@ -36,6 +36,6 @@ extern const struct sieve_operation_def
 
 int ext_duplicate_check(const struct sieve_runtime_env *renv, string_t *handle,
 			const char *value, size_t value_len,
-			sieve_number_t period, bool last);
+			sieve_number_t period, bool last, bool *duplicate_r);
 
 #endif
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -207,6 +207,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -252,6 +254,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c	2022-06-14 06:55:57.000000000 +0000
@@ -428,10 +428,10 @@ tst_duplicate_operation_execute(const st
 	if (val == NULL) {
 		duplicate = FALSE;
 	} else {
-		if ((ret = ext_duplicate_check(renv, handle, val, val_len,
-					       seconds, last)) < 0)
-			return SIEVE_EXEC_FAILURE;
-		duplicate = (ret > 0);
+		ret = ext_duplicate_check(renv, handle, val, val_len,
+					  seconds, last, &duplicate);
+		if (ret < SIEVE_EXEC_OK)
+			return ret;
 	}
 
 	/* Trace */
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/editheader/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -208,6 +208,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -253,6 +255,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/mailto/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c	2022-06-14 06:55:57.000000000 +0000
@@ -2,10 +2,10 @@
  */
 
 /* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and
- *   draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification
- *   when it matures. This requires modifications to the address parser (no
- *   whitespace allowed within the address itself) and UTF-8 support will be
- *   required in the URL.
+          draft-duerst-mailto-bis-05.txt. Should fully migrate to new
+          specification when it matures. This requires modifications to the
+          address parser (no whitespace allowed within the address itself) and
+          UTF-8 support will be required in the URL.
  */
 
 #include "lib.h"
@@ -71,37 +71,39 @@ uri_mailto_log(struct uri_mailto_parser
  * Reserved and unique headers
  */
 
-static inline bool uri_mailto_header_is_reserved
-(struct uri_mailto_parser *parser, const char *field_name)
+static inline bool
+uri_mailto_header_is_reserved(struct uri_mailto_parser *parser,
+			      const char *field_name)
 {
 	const char **hdr = parser->reserved_headers;
 
-	if ( hdr == NULL ) return FALSE;
+	if (hdr == NULL)
+		return FALSE;
 
 	/* Check whether it is reserved */
-	while ( *hdr != NULL ) {
-		if ( strcasecmp(field_name, *hdr) == 0 )
+	while (*hdr != NULL) {
+		if (strcasecmp(field_name, *hdr) == 0)
 			return TRUE;
 		hdr++;
 	}
-
 	return FALSE;
 }
 
-static inline bool uri_mailto_header_is_unique
-(struct uri_mailto_parser *parser, const char *field_name)
+static inline bool
+uri_mailto_header_is_unique(struct uri_mailto_parser *parser,
+			    const char *field_name)
 {
 	const char **hdr = parser->unique_headers;
 
-	if ( hdr == NULL ) return FALSE;
+	if (hdr == NULL)
+		return FALSE;
 
 	/* Check whether it is supposed to be unique */
-	while ( *hdr != NULL ) {
-		if ( strcasecmp(field_name, *hdr) == 0 )
+	while (*hdr != NULL) {
+		if (strcasecmp(field_name, *hdr) == 0)
 			return TRUE;
 		hdr++;
 	}
-
 	return FALSE;
 }
 
@@ -132,25 +134,22 @@ static const char _qchar_lookup[256] = {
 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // F0
 };
 
-static inline bool _is_qchar(unsigned char c)
+static inline bool _is_qchar(char c)
 {
-	return ((_qchar_lookup[c] & 0x01) != 0);
+	return ((_qchar_lookup[(unsigned char)c] & 0x01) != 0);
 }
 
 static inline int _decode_hex_digit(unsigned char digit)
 {
-	switch ( digit ) {
+	switch (digit) {
 	case '0': case '1': case '2': case '3': case '4':
 	case '5': case '6': case '7': case '8': case '9':
 		return (int) digit - '0';
-
 	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
 		return (int) digit - 'a' + 0x0a;
-
 	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
 		return (int) digit - 'A' + 0x0A;
 	}
-
 	return -1;
 }
 
@@ -158,22 +157,22 @@ static bool _parse_hex_value(const char
 {
 	int value, digit;
 
-	if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
+	if ((digit = _decode_hex_digit((unsigned char)**in)) < 0)
 		return FALSE;
 
 	value = digit << 4;
 	(*in)++;
 
-	if ( (digit=_decode_hex_digit((unsigned char) **in)) < 0 )
+	if ((digit = _decode_hex_digit((unsigned char)**in)) < 0)
 		return FALSE;
 
 	value |= digit;
 	(*in)++;
 
-	if ( value == 0 )
+	if (value == 0)
 		return FALSE;
 
-	*out = (char) value;
+	*out = (char)value;
 	return TRUE;
 }
 
@@ -181,8 +180,9 @@ static bool _parse_hex_value(const char
  * URI recipient parsing
  */
 
-static bool uri_mailto_add_valid_recipient
-(struct uri_mailto_parser *parser, string_t *recipient, bool cc)
+static bool
+uri_mailto_add_valid_recipient(struct uri_mailto_parser *parser,
+			       string_t *recipient, bool cc)
 {
 	struct uri_mailto *uri = parser->uri;
 	struct uri_mailto_recipient *new_recipient;
@@ -192,35 +192,41 @@ static bool uri_mailto_add_valid_recipie
 	const struct smtp_address *address;
 
 	/* Verify recipient */
-	if ( (address=sieve_address_parse_str
-		(recipient, &error)) == NULL ) {
+	if ((address = sieve_address_parse_str(recipient, &error)) == NULL) {
 		uri_mailto_error(parser, "invalid recipient '%s': %s",
-			str_sanitize(str_c(recipient), 80), error);
+				 str_sanitize(str_c(recipient), 80), error);
 		return FALSE;
 	}
 
 	/* Add recipient to the uri */
-	if ( uri != NULL ) {
+	if (uri != NULL) {
 		/* Get current recipients */
 		rcpts = array_get_modifiable(&uri->recipients, &count);
 
 		/* Enforce limits */
-		if ( parser->max_recipients > 0 && (int)count >= parser->max_recipients ) {
-			if ( (int)count == parser->max_recipients) {
-				uri_mailto_warning(parser,
+		if (parser->max_recipients > 0 &&
+		    (int)count >= parser->max_recipients) {
+			if ((int)count == parser->max_recipients) {
+				uri_mailto_warning(
+					parser,
 					"more than the maximum %u recipients specified; "
-					"rest is discarded", parser->max_recipients);
+					"rest is discarded",
+					parser->max_recipients);
 			}
 			return TRUE;
 		}
 
 		/* Check for duplicate first */
-		for ( i = 0; i < count; i++ ) {
-			if ( smtp_address_equals(rcpts[i].address, address) ) {
-				/* Upgrade existing Cc: recipient to a To: recipient if possible */
-				rcpts[i].carbon_copy = ( rcpts[i].carbon_copy && cc );
-
-				uri_mailto_warning(parser, "ignored duplicate recipient '%s'",
+		for (i = 0; i < count; i++) {
+			if (smtp_address_equals(rcpts[i].address, address)) {
+				/* Upgrade existing Cc: recipient to a To:
+				   recipient if possible */
+				rcpts[i].carbon_copy =
+					(rcpts[i].carbon_copy && cc);
+
+				uri_mailto_warning(
+					parser,
+					"ignored duplicate recipient '%s'",
 					str_sanitize(str_c(recipient), 80));
 				return TRUE;
 			}
@@ -230,50 +236,55 @@ static bool uri_mailto_add_valid_recipie
 		new_recipient = array_append_space(&uri->recipients);
 		new_recipient->carbon_copy = cc;
 		new_recipient->full = p_strdup(parser->pool, str_c(recipient));
-		new_recipient->address = smtp_address_clone(parser->pool, address);
+		new_recipient->address =
+			smtp_address_clone(parser->pool, address);
 	}
 
 	return TRUE;
 }
 
-static bool uri_mailto_parse_recipients
-(struct uri_mailto_parser *parser, const char **uri_p)
+static bool
+uri_mailto_parse_recipients(struct uri_mailto_parser *parser,
+			    const char **uri_p)
 {
 	string_t *to = t_str_new(128);
 	const char *p = *uri_p;
 
-	if ( *p == '\0' || *p == '?' )
+	if (*p == '\0' || *p == '?')
 		return TRUE;
 
-	while ( *p != '\0' && *p != '?' ) {
-		if ( *p == '%' ) {
+	while (*p != '\0' && *p != '?') {
+		if (*p == '%') {
 			/* % encoded character */
 			char ch;
 
 			p++;
 
 			/* Parse 2-digit hex value */
-			if ( !_parse_hex_value(&p, &ch) ) {
+			if (!_parse_hex_value(&p, &ch)) {
 				uri_mailto_error(parser, "invalid %% encoding");
 				return FALSE;
 			}
 
 			/* Check for delimiter */
-			if ( ch == ',' ) {
+			if (ch == ',') {
 				/* Verify and add recipient */
-				if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
+				if (!uri_mailto_add_valid_recipient(
+					parser, to, FALSE))
 					return FALSE;
 
 				/* Reset for next recipient */
 				str_truncate(to, 0);
-			}	else {
+			} else {
 				/* Content character */
 				str_append_c(to, ch);
 			}
 		} else {
-			if ( *p == ':' || *p == ';' || *p == ',' || !_is_qchar(*p) ) {
-				uri_mailto_error
-					(parser, "invalid character '%c' in 'to' part", *p);
+			if (*p == ':' || *p == ';' || *p == ',' ||
+			    !_is_qchar(*p)) {
+				uri_mailto_error(
+					parser,
+					"invalid character '%c' in 'to' part", *p);
 				return FALSE;
 			}
 
@@ -283,27 +294,28 @@ static bool uri_mailto_parse_recipients
 		}
 	}
 
-	i_assert( *p == '\0' || *p == '?' );
+	i_assert(*p == '\0' || *p == '?');
 
 	/* Verify and add recipient */
-	if ( !uri_mailto_add_valid_recipient(parser, to, FALSE) )
+	if (!uri_mailto_add_valid_recipient(parser, to, FALSE))
 		return FALSE;
 
 	*uri_p = p;
 	return TRUE;
 }
 
-static bool uri_mailto_parse_header_recipients
-(struct uri_mailto_parser *parser, string_t *rcpt_header, bool cc)
+static bool
+uri_mailto_parse_header_recipients(struct uri_mailto_parser *parser,
+				   string_t *rcpt_header, bool cc)
 {
 	string_t *to = t_str_new(128);
-	const char *p = (const char *) str_data(rcpt_header);
+	const char *p = (const char *)str_data(rcpt_header);
 	const char *pend = p + str_len(rcpt_header);
 
-	while ( p < pend ) {
-		if ( *p == ',' ) {
+	while (p < pend) {
+		if (*p == ',') {
 			/* Verify and add recipient */
-			if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
+			if (!uri_mailto_add_valid_recipient(parser, to, cc))
 				return FALSE;
 
 			/* Reset for next recipient */
@@ -316,7 +328,7 @@ static bool uri_mailto_parse_header_reci
 	}
 
 	/* Verify and add recipient */
-	if ( !uri_mailto_add_valid_recipient(parser, to, cc) )
+	if (!uri_mailto_add_valid_recipient(parser, to, cc))
 		return FALSE;
 
 	return TRUE;
@@ -324,80 +336,90 @@ static bool uri_mailto_parse_header_reci
 
 /* URI header parsing */
 
-static bool uri_mailto_header_is_duplicate
-(struct uri_mailto_parser *parser, const char *field_name)
+static bool
+uri_mailto_header_is_duplicate(struct uri_mailto_parser *parser,
+			       const char *field_name)
 {
 	struct uri_mailto *uri = parser->uri;
 
-	if ( uri == NULL ) return FALSE;
+	if (uri == NULL)
+		return FALSE;
 
-	if ( uri_mailto_header_is_unique(parser, field_name) ) {
+	if (uri_mailto_header_is_unique(parser, field_name)) {
 		const struct uri_mailto_header_field *hdrs;
 		unsigned int count, i;
 
 		hdrs = array_get(&uri->headers, &count);
-		for ( i = 0; i < count; i++ ) {
-			if ( strcasecmp(hdrs[i].name, field_name) == 0 )
+		for (i = 0; i < count; i++) {
+			if (strcasecmp(hdrs[i].name, field_name) == 0)
 				return TRUE;
 		}
 	}
-
 	return FALSE;
 }
 
-static bool uri_mailto_parse_headers
-(struct uri_mailto_parser *parser, const char **uri_p)
+static bool
+uri_mailto_parse_headers(struct uri_mailto_parser *parser, const char **uri_p)
 {
 	struct uri_mailto *uri = parser->uri;
 	unsigned int header_count = 0;
 	string_t *field = t_str_new(128);
 	const char *p = *uri_p;
 
-	while ( *p != '\0' ) {
+	while (*p != '\0') {
 		enum {
 			_HNAME_IGNORED,
 			_HNAME_GENERIC,
 			_HNAME_TO,
 			_HNAME_CC,
 			_HNAME_SUBJECT,
-			_HNAME_BODY
+			_HNAME_BODY,
 		} hname_type = _HNAME_GENERIC;
 		struct uri_mailto_header_field *hdrf = NULL;
 		const char *field_name;
 
 		/* Parse field name */
-		while ( *p != '\0' && *p != '=' ) {
+		while (*p != '\0' && *p != '=') {
 			char ch = *p;
 			p++;
 
-			if ( ch == '%' ) {
+			if (ch == '%') {
 				/* Encoded, parse 2-digit hex value */
-				if ( !_parse_hex_value(&p, &ch) ) {
-					uri_mailto_error(parser, "invalid %% encoding");
+				if (!_parse_hex_value(&p, &ch)) {
+					uri_mailto_error(parser,
+							 "invalid %% encoding");
 					return FALSE;
 				}
-			} else if ( ch != '=' && !_is_qchar(ch) ) {
-				uri_mailto_error
-					(parser, "invalid character '%c' in header field name part", ch);
+			} else if (ch != '=' && !_is_qchar(ch)) {
+				uri_mailto_error(
+					parser,
+					"invalid character '%c' in header field name part",
+					ch);
 				return FALSE;
 			}
 
 			str_append_c(field, ch);
 		}
-		if ( *p != '\0' ) p++;
+		if (*p != '\0')
+			p++;
 
 		/* Verify field name */
-		if ( !rfc2822_header_field_name_verify(str_c(field), str_len(field)) ) {
+		if (!rfc2822_header_field_name_verify(str_c(field),
+						      str_len(field))) {
 			uri_mailto_error(parser, "invalid header field name");
 			return FALSE;
 		}
 
-		if ( parser->max_headers > -1 &&
-			(int)header_count >= parser->max_headers ) {
-			/* Refuse to accept more headers than allowed by policy */
-			if ( (int)header_count == parser->max_headers ) {
-				uri_mailto_warning(parser, "more than the maximum %u headers specified; "
-					"rest is discarded", parser->max_headers);
+		if (parser->max_headers > -1 &&
+		    (int)header_count >= parser->max_headers) {
+			/* Refuse to accept more headers than allowed by policy
+			 */
+			if ((int)header_count == parser->max_headers) {
+				uri_mailto_warning(
+					parser,
+					"more than the maximum %u headers specified; "
+					"rest is discarded",
+					parser->max_headers);
 			}
 
 			hname_type = _HNAME_IGNORED;
@@ -405,21 +427,22 @@ static bool uri_mailto_parse_headers
 			/* Add new header field to array and assign its name */
 
 			field_name = str_c(field);
-			if ( strcasecmp(field_name, "to") == 0 )
+			if (strcasecmp(field_name, "to") == 0)
 				hname_type = _HNAME_TO;
-			else if ( strcasecmp(field_name, "cc") == 0 )
+			else if (strcasecmp(field_name, "cc") == 0)
 				hname_type = _HNAME_CC;
-			else if ( strcasecmp(field_name, "subject") == 0 )
+			else if (strcasecmp(field_name, "subject") == 0)
 				hname_type = _HNAME_SUBJECT;
-			else if ( strcasecmp(field_name, "body") == 0 )
+			else if (strcasecmp(field_name, "body") == 0)
 				hname_type = _HNAME_BODY;
-			else if ( !uri_mailto_header_is_reserved(parser, field_name) ) {
-				if ( uri != NULL ) {
-					if ( !uri_mailto_header_is_duplicate(parser, field_name) ) {
+			else if (!uri_mailto_header_is_reserved(parser, field_name)) {
+				if (uri != NULL) {
+					if (!uri_mailto_header_is_duplicate(parser, field_name)) {
 						hdrf = array_append_space(&uri->headers);
 						hdrf->name = p_strdup(parser->pool, field_name);
 					} else {
-						uri_mailto_warning(parser,
+						uri_mailto_warning(
+							parser,
 							"ignored duplicate for unique header field '%s'",
 							str_sanitize(field_name, 32));
 						hname_type = _HNAME_IGNORED;
@@ -428,7 +451,9 @@ static bool uri_mailto_parse_headers
 					hname_type = _HNAME_IGNORED;
 				}
 			} else {
-				uri_mailto_warning(parser, "ignored reserved header field '%s'",
+				uri_mailto_warning(
+					parser,
+					"ignored reserved header field '%s'",
 					str_sanitize(field_name, 32));
 				hname_type = _HNAME_IGNORED;
 			}
@@ -440,71 +465,85 @@ static bool uri_mailto_parse_headers
 		str_truncate(field, 0);
 
 		/* Parse field body */
-		while ( *p != '\0' && *p != '&' ) {
+		while (*p != '\0' && *p != '&') {
 			char ch = *p;
 			p++;
 
-			if ( ch == '%' ) {
+			if (ch == '%') {
 				/* Encoded, parse 2-digit hex value */
-				if ( !_parse_hex_value(&p, &ch) ) {
-					uri_mailto_error(parser, "invalid %% encoding");
+				if (!_parse_hex_value(&p, &ch)) {
+					uri_mailto_error(parser,
+							 "invalid %% encoding");
 					return FALSE;
 				}
-			} else if ( ch != '=' && !_is_qchar(ch) ) {
-				uri_mailto_error
-					(parser, "invalid character '%c' in header field value part", ch);
+			} else if (ch != '=' && !_is_qchar(ch)) {
+				uri_mailto_error(
+					parser,
+					"invalid character '%c' in header field value part",
+					ch);
 				return FALSE;
 			}
 			str_append_c(field, ch);
 		}
-		if ( *p != '\0' ) p++;
+		if (*p != '\0')
+			p++;
 
 		/* Verify field body */
-		if ( hname_type == _HNAME_BODY ) {
+		if (hname_type == _HNAME_BODY) {
 			// FIXME: verify body ...
 		} else {
-			if ( !rfc2822_header_field_body_verify
-				(str_c(field), str_len(field), FALSE, FALSE) ) {
-				uri_mailto_error(parser, "invalid header field body");
+			if (!rfc2822_header_field_body_verify(
+				str_c(field), str_len(field), FALSE, FALSE)) {
+				uri_mailto_error(parser,
+						 "invalid header field body");
 				return FALSE;
 			}
 		}
 
 		/* Assign field body */
 
-		switch ( hname_type ) {
+		switch (hname_type) {
 		case _HNAME_IGNORED:
 			break;
 		case _HNAME_TO:
 			/* Gracefully allow duplicate To fields */
-			if ( !uri_mailto_parse_header_recipients(parser, field, FALSE) )
+			if (!uri_mailto_parse_header_recipients(
+				parser, field, FALSE))
 				return FALSE;
 			break;
 		case _HNAME_CC:
 			/* Gracefully allow duplicate Cc fields */
-			if ( !uri_mailto_parse_header_recipients(parser, field, TRUE) )
+			if (!uri_mailto_parse_header_recipients(
+				parser, field, TRUE))
 				return FALSE;
 			break;
 		case _HNAME_SUBJECT:
-			/* Igore duplicate subject field */
-			if ( uri != NULL ) {
-				if ( uri->subject == NULL )
-					uri->subject = p_strdup(parser->pool, str_c(field));
-				else
-					uri_mailto_warning(parser, "ignored duplicate subject field");
+			/* Ignore duplicate subject field */
+			if (uri != NULL) {
+				if (uri->subject == NULL) {
+					uri->subject =
+						p_strdup(parser->pool, str_c(field));
+				} else {
+					uri_mailto_warning(
+						parser,
+						"ignored duplicate subject field");
+				}
 			}
 			break;
 		case _HNAME_BODY:
-			/* Igore duplicate body field */
-			if ( uri != NULL ) {
-				if ( uri->body == NULL )
-					uri->body = p_strdup(parser->pool, str_c(field));
-				else
-					uri_mailto_warning(parser, "ignored duplicate body field");
+			/* Ignore duplicate body field */
+			if (uri != NULL) {
+				if (uri->body == NULL) {
+					uri->body = p_strdup(
+						parser->pool, str_c(field));
+				} else {
+					uri_mailto_warning(
+						parser, "ignored duplicate body field");
+				}
 			}
 			break;
 		case _HNAME_GENERIC:
-			if ( uri != NULL && hdrf != NULL )
+			if (uri != NULL && hdrf != NULL)
 				hdrf->body = p_strdup(parser->pool, str_c(field));
 			break;
 		}
@@ -514,14 +553,15 @@ static bool uri_mailto_parse_headers
 	}
 
 	/* Skip '&' */
-	if ( *p != '\0' ) p++;
+	if (*p != '\0')
+		p++;
 
 	*uri_p = p;
 	return TRUE;
 }
 
-static bool uri_mailto_parse_uri
-(struct uri_mailto_parser *parser, const char *uri_body)
+static bool
+uri_mailto_parse_uri(struct uri_mailto_parser *parser, const char *uri_body)
 {
 	const char *p = uri_body;
 
@@ -547,22 +587,22 @@ static bool uri_mailto_parse_uri
 	/* First extract to-part by searching for '?' and decoding % items
 	 */
 
-	if ( !uri_mailto_parse_recipients(parser, &p) )
+	if (!uri_mailto_parse_recipients(parser, &p))
 		return FALSE;
 
-	if ( *p == '\0' )
+	if (*p == '\0')
 		return TRUE;
-	i_assert( *p == '?' );
+	i_assert(*p == '?');
 	p++;
 
 	/* Extract hfield items */
 
-	while ( *p != '\0' ) {
-		/* Extract hfield item by searching for '&' and decoding '%' items */
-		if ( !uri_mailto_parse_headers(parser, &p) )
+	while (*p != '\0') {
+		/* Extract hfield item by searching for '&' and decoding '%'
+		   items */
+		if (!uri_mailto_parse_headers(parser, &p))
 			return FALSE;
 	}
-
 	return TRUE;
 }
 
@@ -570,10 +610,10 @@ static bool uri_mailto_parse_uri
  * Validation
  */
 
-bool uri_mailto_validate
-(const char *uri_body, const char **reserved_headers,
-	const char **unique_headers, int max_recipients, int max_headers,
-	const struct uri_mailto_log *log)
+bool uri_mailto_validate(const char *uri_body,
+			 const char **reserved_headers,
+			 const char **unique_headers, int max_recipients,
+			 int max_headers, const struct uri_mailto_log *log)
 {
 	struct uri_mailto_parser parser;
 
@@ -585,7 +625,7 @@ bool uri_mailto_validate
 	parser.unique_headers = unique_headers;
 
 	/* If no errors are reported, we don't need to record any data */
-	if ( log != NULL ) {
+	if (log != NULL) {
 		parser.pool = pool_datastack_create();
 
 		parser.uri = p_new(parser.pool, struct uri_mailto, 1);
@@ -593,14 +633,16 @@ bool uri_mailto_validate
 		p_array_init(&parser.uri->headers, parser.pool, max_headers);
 	}
 
-	if ( !uri_mailto_parse_uri(&parser, uri_body) )
+	if (!uri_mailto_parse_uri(&parser, uri_body))
 		return FALSE;
 
-	if ( log != NULL ) {
-		if ( array_count(&parser.uri->recipients) == 0 )
-			uri_mailto_warning(&parser, "notification URI specifies no recipients");
+	if (log != NULL) {
+		if (array_count(&parser.uri->recipients) == 0) {
+			uri_mailto_warning(
+				&parser,
+				"notification URI specifies no recipients");
+		}
 	}
-
 	return TRUE;
 }
 
@@ -608,10 +650,11 @@ bool uri_mailto_validate
  * Parsing
  */
 
-struct uri_mailto *uri_mailto_parse
-(const char *uri_body, pool_t pool, const char **reserved_headers,
-	const char **unique_headers, int max_recipients, int max_headers,
-	const struct uri_mailto_log *log)
+struct uri_mailto *
+uri_mailto_parse(const char *uri_body, pool_t pool,
+		 const char **reserved_headers, const char **unique_headers,
+		 int max_recipients, int max_headers,
+		 const struct uri_mailto_log *log)
 {
 	struct uri_mailto_parser parser;
 
@@ -626,18 +669,15 @@ struct uri_mailto *uri_mailto_parse
 	p_array_init(&parser.uri->recipients, pool, max_recipients);
 	p_array_init(&parser.uri->headers, pool, max_headers);
 
-	if ( !uri_mailto_parse_uri(&parser, uri_body) )
+	if (!uri_mailto_parse_uri(&parser, uri_body))
 		return NULL;
 
-	if ( log != NULL ) {
-		if ( array_count(&parser.uri->recipients) == 0 )
-			uri_mailto_warning(&parser, "notification URI specifies no recipients");
+	if (log != NULL) {
+		if (array_count(&parser.uri->recipients) == 0) {
+			uri_mailto_warning(
+				&parser,
+				"notification URI specifies no recipients");
+		}
 	}
-
 	return parser.uri;
 }
-
-
-
-
-
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/enotify/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -282,6 +282,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -327,6 +329,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/environment/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -235,6 +235,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -280,6 +282,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/ihave/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -208,6 +208,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -253,6 +255,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/imap4flags/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -239,6 +239,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -284,6 +286,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/include/ext-include-common.c	2022-06-14 06:55:57.000000000 +0000
@@ -38,7 +38,7 @@ struct ext_include_generator_context {
 
 static inline struct ext_include_generator_context *
 ext_include_get_generator_context(const struct sieve_extension *ext_this,
-				 struct sieve_generator *gentr);
+				  struct sieve_generator *gentr);
 
 /* Interpreter context */
 
@@ -102,10 +102,10 @@ bool ext_include_load(const struct sieve
 
 	if (sieve_setting_get_uint_value(
 		svinst, "sieve_include_max_nesting_depth", &uint_setting))
-		ctx->max_nesting_depth = (unsigned int) uint_setting;
+		ctx->max_nesting_depth = (unsigned int)uint_setting;
 	if (sieve_setting_get_uint_value(
 		svinst, "sieve_include_max_includes", &uint_setting))
-		ctx->max_includes = (unsigned int) uint_setting;
+		ctx->max_includes = (unsigned int)uint_setting;
 
 	/* Extension dependencies */
 	ctx->var_ext = sieve_ext_variables_get_extension(ext->svinst);
@@ -530,9 +530,9 @@ int ext_include_generate_include(
 	if (included != NULL) {
 		/* Yes, only update flags */
 		if ((flags & EXT_INCLUDE_FLAG_OPTIONAL) == 0)
-			included->flags &= ~EXT_INCLUDE_FLAG_OPTIONAL;
+			included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_OPTIONAL);
 		if ((flags & EXT_INCLUDE_FLAG_ONCE) == 0)
-			included->flags &= ~EXT_INCLUDE_FLAG_ONCE;
+			included->flags &= ENUM_NEGATE(EXT_INCLUDE_FLAG_ONCE);
 	} else 	{
 		const char *script_name = sieve_script_name(script);
 		enum sieve_compile_flags cpflags = cgenv->flags;
@@ -583,10 +583,12 @@ int ext_include_generate_include(
 			(void)ext_include_create_ast_context(
 				this_ext, ast, cmd->ast_node->ast);
 
-			if (location == EXT_INCLUDE_LOCATION_GLOBAL)
-				cpflags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
-			else
+			if (location == EXT_INCLUDE_LOCATION_GLOBAL) {
+				cpflags &=
+					ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+			} else {
 				cpflags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			}
 
 			/* Validate */
 			if (!sieve_validate(ast, ehandler, cpflags, NULL)) {
@@ -606,7 +608,7 @@ int ext_include_generate_include(
 		 	subgentr = sieve_generator_create(ast, ehandler, cpflags);
 			ext_include_initialize_generator_context(
 				cmd->ext, subgentr, ctx, script);
-	
+
 			if (sieve_generator_run(subgentr, &inc_block) == NULL) {
 				sieve_command_generate_error(
 					gentr, cmd,
@@ -621,7 +623,7 @@ int ext_include_generate_include(
 			sieve_ast_unref(&ast);
 		}
 	}
-	
+
 	if (result > 0)
 		*included_r = included;
 	return result;
@@ -737,8 +739,10 @@ int ext_include_execute_include(const st
 
 			if (included->location != EXT_INCLUDE_LOCATION_GLOBAL)
 				eenv_new.flags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
-			else
-				eenv_new.flags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
+			else {
+				eenv_new.flags &=
+					ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+			}
 
 			/* Create interpreter for top-level included script
 			   (first sub-interpreter)
@@ -808,8 +812,10 @@ int ext_include_execute_include(const st
 
 							if (curctx->include->location != EXT_INCLUDE_LOCATION_GLOBAL)
 								eenv_new.flags |= SIEVE_EXECUTE_FLAG_NOGLOBAL;
-							else
-								eenv_new.flags &= ~SIEVE_EXECUTE_FLAG_NOGLOBAL;
+							else {
+								eenv_new.flags &=
+									ENUM_NEGATE(SIEVE_EXECUTE_FLAG_NOGLOBAL);
+							}
 
 							/* Create sub-interpreter */
 							subinterp = sieve_interpreter_create_for_block(
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/include/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/include/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/include/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/include/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -211,6 +211,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -256,6 +258,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/index/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/index/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/index/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/index/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -204,6 +204,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -249,6 +251,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/index/tag-index.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/index/tag-index.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/index/tag-index.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/index/tag-index.c	2022-06-14 06:55:57.000000000 +0000
@@ -22,12 +22,13 @@
  * Tagged argument
  */
 
-static bool tag_index_validate
-	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
-		struct sieve_command *cmd);
-static bool tag_index_generate
-	(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
-    struct sieve_command *context);
+static bool
+tag_index_validate(struct sieve_validator *valdtr,
+		   struct sieve_ast_argument **arg, struct sieve_command *cmd);
+static bool
+tag_index_generate(const struct sieve_codegen_env *cgenv,
+		   struct sieve_ast_argument *arg,
+		   struct sieve_command *context);
 
 const struct sieve_argument_def index_tag = {
 	.identifier = "index",
@@ -35,9 +36,9 @@ const struct sieve_argument_def index_ta
 	.generate = tag_index_generate
 };
 
-static bool tag_last_validate
-	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
-		struct sieve_command *cmd);
+static bool
+tag_last_validate(struct sieve_validator *valdtr,
+		  struct sieve_ast_argument **arg, struct sieve_command *cmd);
 
 const struct sieve_argument_def last_tag = {
 	.identifier = "last",
@@ -48,17 +49,18 @@ const struct sieve_argument_def last_tag
  * Header override
  */
 
-static bool svmo_index_dump_context
-	(const struct sieve_message_override *svmo,
-		const struct sieve_dumptime_env *denv, sieve_size_t *address);
-static int svmo_index_read_context
-	(const struct sieve_message_override *svmo,
-		const struct sieve_runtime_env *renv, sieve_size_t *address,
-		void **ho_context);
-static int svmo_index_header_override
-	(const struct sieve_message_override *svmo,
-		const struct sieve_runtime_env *renv,
-		bool mime_decode, struct sieve_stringlist **headers);
+static bool
+svmo_index_dump_context(const struct sieve_message_override *svmo,
+			const struct sieve_dumptime_env *denv,
+			sieve_size_t *address);
+static int
+svmo_index_read_context(const struct sieve_message_override *svmo,
+			const struct sieve_runtime_env *renv,
+			sieve_size_t *address, void **ho_context);
+static int
+svmo_index_header_override(const struct sieve_message_override *svmo,
+			   const struct sieve_runtime_env *renv,
+			   bool mime_decode, struct sieve_stringlist **headers);
 
 const struct sieve_message_override_def index_header_override = {
 	SIEVE_OBJECT("index", &index_operand, 0),
@@ -95,9 +97,9 @@ struct tag_index_data {
  * Tag validation
  */
 
-static bool tag_index_validate
-(struct sieve_validator *valdtr ATTR_UNUSED,
-	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+static bool
+tag_index_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+		   struct sieve_ast_argument **arg, struct sieve_command *cmd)
 {
 	struct sieve_ast_argument *tag = *arg;
 	struct tag_index_data *data;
@@ -108,10 +110,9 @@ static bool tag_index_validate
 	/* Check syntax:
 	 *   ":index" <fieldno: number>
 	 */
-	if ( !sieve_validate_tag_parameter
-		(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE) ) {
+	if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+					  SAAT_NUMBER, FALSE))
 		return FALSE;
-	}
 
 	if (tag->argument->data == NULL) {
 		data = p_new(sieve_command_pool(cmd), struct tag_index_data, 1);
@@ -121,25 +122,34 @@ static bool tag_index_validate
 	}
 
 	data->fieldno = sieve_ast_argument_number(*arg);
+	if (data->fieldno == 0) {
+		sieve_argument_validate_error(valdtr, *arg,
+			"the :index tag for the %s %s cannot be zero",
+			sieve_command_identifier(cmd),
+			sieve_command_type_name(cmd));
+		return FALSE;
+	}
 
 	/* Detach parameter */
 	*arg = sieve_ast_arguments_detach(*arg,1);
 	return TRUE;
 }
 
-static bool tag_last_validate
-(struct sieve_validator *valdtr ATTR_UNUSED,
-	struct sieve_ast_argument **arg, struct sieve_command *cmd)
+static bool
+tag_last_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+		  struct sieve_ast_argument **arg, struct sieve_command *cmd)
 {
 	struct sieve_ast_argument *index_arg;
 	struct tag_index_data *data;
 
 	index_arg = sieve_command_find_argument(cmd, &index_tag);
 	if (index_arg == NULL) {
-		sieve_argument_validate_error(valdtr, *arg,
+		sieve_argument_validate_error(
+			valdtr, *arg,
 			"the :last tag for the %s %s cannot be specified "
 			"without the :index tag",
-			sieve_command_identifier(cmd), sieve_command_type_name(cmd));
+			sieve_command_identifier(cmd),
+			sieve_command_type_name(cmd));
 		return FALSE;
 	}
 
@@ -153,7 +163,7 @@ static bool tag_last_validate
 	data->last = TRUE;
 
 	/* Detach */
-	*arg = sieve_ast_arguments_detach(*arg,1);
+	*arg = sieve_ast_arguments_detach(*arg, 1);
 	return TRUE;
 }
 
@@ -161,24 +171,22 @@ static bool tag_last_validate
  * Code generation
  */
 
-static bool tag_index_generate
-(const struct sieve_codegen_env *cgenv, struct sieve_ast_argument *arg,
-	struct sieve_command *cmd ATTR_UNUSED)
+static bool
+tag_index_generate(const struct sieve_codegen_env *cgenv,
+		   struct sieve_ast_argument *arg,
+		   struct sieve_command *cmd ATTR_UNUSED)
 {
 	struct tag_index_data *data =
 		(struct tag_index_data *)arg->argument->data;
 
-	if ( sieve_ast_argument_type(arg) != SAAT_TAG )
+	if (sieve_ast_argument_type(arg) != SAAT_TAG)
 		return FALSE;
 
-	sieve_opr_message_override_emit
-		(cgenv->sblock, arg->argument->ext, &index_header_override);
-
-	(void)sieve_binary_emit_integer
-		(cgenv->sblock, data->fieldno);
-	(void)sieve_binary_emit_byte
-		(cgenv->sblock, ( data->last ? 1 : 0 ));
+	sieve_opr_message_override_emit(cgenv->sblock, arg->argument->ext,
+					&index_header_override);
 
+	(void)sieve_binary_emit_integer	(cgenv->sblock, data->fieldno);
+	(void)sieve_binary_emit_byte(cgenv->sblock, (data->last ? 1 : 0));
 	return TRUE;
 }
 
@@ -195,20 +203,21 @@ struct svmo_index_context {
 
 /* Context coding */
 
-static bool svmo_index_dump_context
-(const struct sieve_message_override *svmo ATTR_UNUSED,
-	const struct sieve_dumptime_env *denv, sieve_size_t *address)
+static bool
+svmo_index_dump_context(const struct sieve_message_override *svmo ATTR_UNUSED,
+			const struct sieve_dumptime_env *denv,
+			sieve_size_t *address)
 {
 	sieve_number_t fieldno = 0;
 	unsigned int last;
 
-	if ( !sieve_binary_read_integer(denv->sblock, address, &fieldno) )
+	if (!sieve_binary_read_integer(denv->sblock, address, &fieldno) ||
+	    fieldno == 0)
 		return FALSE;
 
-	sieve_code_dumpf(denv, "fieldno: %llu",
-		(unsigned long long) fieldno);
+	sieve_code_dumpf(denv, "fieldno: %llu", (unsigned long long) fieldno);
 
-	if ( !sieve_binary_read_byte(denv->sblock, address, &last) )
+	if (!sieve_binary_read_byte(denv->sblock, address, &last))
 		return FALSE;
 
 	if (last > 0)
@@ -216,22 +225,25 @@ static bool svmo_index_dump_context
 	return TRUE;
 }
 
-static int svmo_index_read_context
-(const struct sieve_message_override *svmo ATTR_UNUSED,
-	const struct sieve_runtime_env *renv, sieve_size_t *address,
-	void **ho_context)
+static int
+svmo_index_read_context(const struct sieve_message_override *svmo ATTR_UNUSED,
+			const struct sieve_runtime_env *renv,
+			sieve_size_t *address, void **ho_context)
 {
 	pool_t pool = sieve_result_pool(renv->result);
 	struct svmo_index_context *ctx;
 	sieve_number_t fieldno;
 	unsigned int last = 0;
 
-	if ( !sieve_binary_read_integer(renv->sblock, address, &fieldno) ) {
+	if (!sieve_binary_read_integer(renv->sblock, address, &fieldno)) {
 		sieve_runtime_trace_error(renv, "fieldno: invalid number");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
-
-	if ( !sieve_binary_read_byte(renv->sblock, address, &last) ) {
+	if (fieldno == 0) {
+		sieve_runtime_trace_error(renv, "fieldno: index is zero");
+		return SIEVE_EXEC_BIN_CORRUPT;
+	}
+	if (!sieve_binary_read_byte(renv->sblock, address, &last)) {
 		sieve_runtime_trace_error(renv, "last: invalid byte");
 		return SIEVE_EXEC_BIN_CORRUPT;
 	}
@@ -241,27 +253,26 @@ static int svmo_index_read_context
 	ctx->last = (last == 0 ? FALSE : TRUE);
 
 	*ho_context = (void *) ctx;
-
 	return SIEVE_EXEC_OK;
 }
 
 /* Override */
 
-static int svmo_index_header_override
-(const struct sieve_message_override *svmo,
-	const struct sieve_runtime_env *renv,
-	bool mime_decode ATTR_UNUSED,
-	struct sieve_stringlist **headers)
+static int
+svmo_index_header_override(const struct sieve_message_override *svmo,
+			   const struct sieve_runtime_env *renv,
+			   bool mime_decode ATTR_UNUSED,
+			   struct sieve_stringlist **headers)
 {
 	struct svmo_index_context *ctx =
 		(struct svmo_index_context *)svmo->context;
 
-	sieve_runtime_trace(renv, SIEVE_TRLVL_MATCHING,
+	sieve_runtime_trace(
+		renv, SIEVE_TRLVL_MATCHING,
 		"header index override: only returning index %d%s",
-		ctx->fieldno, ( ctx->last ? " (from last)" : "" ));
+		ctx->fieldno, (ctx->last ? " (from last)" : ""));
 
-	*headers = sieve_index_stringlist_create(renv, *headers,
-		(int)ctx->fieldno * ( ctx->last ? -1 : 1 ));
+	*headers = sieve_index_stringlist_create(
+		renv, *headers, (int)ctx->fieldno * (ctx->last ? -1 : 1));
 	return SIEVE_EXEC_OK;
 }
-
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/mailbox/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -236,6 +236,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -281,6 +283,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -209,6 +209,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -254,6 +256,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/metadata/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -212,6 +212,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -257,6 +259,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/mime/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -212,6 +212,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -257,6 +259,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/notify/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -206,6 +206,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -251,6 +253,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/regex/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -204,6 +204,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -249,6 +251,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/relational/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -206,6 +206,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -251,6 +253,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/spamvirustest/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -207,6 +207,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -252,6 +254,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/special-use/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -208,6 +208,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -253,6 +255,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/subaddress/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -200,6 +200,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -245,6 +247,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vacation/cmd-vacation.c	2022-06-14 06:55:57.000000000 +0000
@@ -1233,7 +1233,6 @@ act_vacation_commit(const struct sieve_a
 	struct sieve_instance *svinst = eenv->svinst;
 	const struct ext_vacation_config *config =
 		(const struct ext_vacation_config *)ext->context;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct act_vacation_context *ctx =
 		(struct act_vacation_context *)action->context;
 	unsigned char dupl_hash[MD5_RESULTLEN];
@@ -1302,12 +1301,23 @@ act_vacation_commit(const struct sieve_a
 	}
 
 	/* Did whe respond to this user before? */
-	if (sieve_action_duplicate_check_available(senv)) {
+	if (sieve_action_duplicate_check_available(aenv)) {
+		bool duplicate;
+
 		act_vacation_hash(ctx, smtp_address_encode(sender), dupl_hash);
 
-		if (sieve_action_duplicate_check(senv, dupl_hash,
-						 sizeof(dupl_hash)))
-		{
+		ret = sieve_action_duplicate_check(aenv, dupl_hash,
+						   sizeof(dupl_hash),
+						   &duplicate);
+		if (ret < SIEVE_EXEC_OK) {
+			sieve_result_critical(
+				aenv, "failed to check for duplicate vacation response",
+				"failed to check for duplicate vacation response%s",
+				(ret == SIEVE_EXEC_TEMP_FAILURE ?
+				 " (temporaty failure)" : ""));
+			return ret;
+		}
+		if (duplicate) {
 			sieve_result_global_log(
 				aenv,
 				"discarded duplicate vacation response to <%s>",
@@ -1554,7 +1564,7 @@ act_vacation_commit(const struct sieve_a
 
 		/* Mark as replied */
 		if (seconds > 0) {
-			sieve_action_duplicate_mark(senv, dupl_hash,
+			sieve_action_duplicate_mark(aenv, dupl_hash,
 						    sizeof(dupl_hash),
 						    ioloop_time + seconds);
 		}
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vacation/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -208,6 +208,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -253,6 +255,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/variables/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -245,6 +245,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -290,6 +292,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/debug/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -204,6 +204,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -249,6 +251,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/environment/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -206,6 +206,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -251,6 +253,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -209,6 +209,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -254,6 +256,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/plugins/vnd.dovecot/report/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -207,6 +207,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -252,6 +254,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-actions.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-actions.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-actions.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-actions.c	2022-06-14 06:55:57.000000000 +0000
@@ -927,39 +927,6 @@ int sieve_act_redirect_add_to_result(con
  * Action utility functions
  */
 
-/* Checking for duplicates */
-
-bool sieve_action_duplicate_check_available(
-	const struct sieve_script_env *senv)
-{
-	return (senv->duplicate_check != NULL && senv->duplicate_mark != NULL);
-}
-
-bool sieve_action_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size)
-{
-	if (senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
-		return FALSE;
-
-	return senv->duplicate_check(senv, id, id_size);
-}
-
-void sieve_action_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time)
-{
-	if (senv->duplicate_check == NULL || senv->duplicate_mark == NULL)
-		return;
-
-	senv->duplicate_mark(senv, id, id_size, time);
-}
-
-void sieve_action_duplicate_flush(const struct sieve_script_env *senv)
-{
-	if (senv->duplicate_flush == NULL)
-		return;
-	senv->duplicate_flush(senv);
-}
-
 /* Rejecting the mail */
 
 static int
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-actions.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-actions.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-actions.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-actions.h	2022-06-14 06:55:57.000000000 +0000
@@ -260,18 +260,40 @@ int sieve_act_redirect_add_to_result(con
 				     const struct smtp_address *to_address);
 
 /*
- * Action utility functions
+ * Checking for duplicates
  */
 
-/* Checking for duplicates */
+static inline bool
+sieve_action_duplicate_check_available(const struct sieve_action_exec_env *aenv)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+
+	return sieve_execute_duplicate_check_available(eenv);
+}
+
+static inline int
+sieve_action_duplicate_check(const struct sieve_action_exec_env *aenv,
+			     const void *id, size_t id_size,
+			     bool *duplicate_r)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
+
+	return sieve_execute_duplicate_check(eenv, id, id_size,
+					     duplicate_r);
+}
+
+static inline void
+sieve_action_duplicate_mark(const struct sieve_action_exec_env *aenv,
+			    const void *id, size_t id_size, time_t time)
+{
+	const struct sieve_execute_env *eenv = aenv->exec_env;
 
-bool sieve_action_duplicate_check_available(
-	const struct sieve_script_env *senv);
-bool sieve_action_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size);
-void sieve_action_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time);
-void sieve_action_duplicate_flush(const struct sieve_script_env *senv);
+	return sieve_execute_duplicate_mark(eenv, id, id_size, time);
+}
+
+/*
+ * Action utility functions
+ */
 
 /* Rejecting mail */
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-ast.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-ast.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-ast.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-ast.c	2022-06-14 06:55:57.000000000 +0000
@@ -18,9 +18,9 @@
  * Forward declarations
  */
 
-static struct sieve_ast_node *sieve_ast_node_create
-	(struct sieve_ast *ast, struct sieve_ast_node *parent,
-		enum sieve_ast_type type, unsigned int source_line);
+static struct sieve_ast_node *
+sieve_ast_node_create(struct sieve_ast *ast, struct sieve_ast_node *parent,
+		      enum sieve_ast_type type, unsigned int source_line);
 
 /*
  * Types
@@ -54,8 +54,7 @@ struct sieve_ast {
 	ARRAY(struct sieve_ast_extension_reg) extensions;
 };
 
-struct sieve_ast *sieve_ast_create
-(struct sieve_script *script)
+struct sieve_ast *sieve_ast_create(struct sieve_script *script)
 {
 	pool_t pool;
 	struct sieve_ast *ast;
@@ -100,10 +99,10 @@ void sieve_ast_unref(struct sieve_ast **
 
 	/* Signal registered extensions that the AST is being destroyed */
 	extrs = array_get(&(*ast)->extensions, &ext_count);
-	for ( i = 0; i < ext_count; i++ ) {
-		if ( extrs[i].ast_ext != NULL &&
-			extrs[i].ast_ext->free != NULL )
-			extrs[i].ast_ext->free(extrs[i].ext, *ast, extrs[i].context);
+	for (i = 0; i < ext_count; i++) {
+		if (extrs[i].ast_ext != NULL &&	extrs[i].ast_ext->free != NULL)
+			extrs[i].ast_ext->free(extrs[i].ext, *ast,
+					       extrs[i].context);
 	}
 
 	/* Destroy AST */
@@ -131,27 +130,26 @@ struct sieve_script *sieve_ast_script(st
  * Extension support
  */
 
-void sieve_ast_extension_link
-(struct sieve_ast *ast, const struct sieve_extension *ext,
-	bool required)
+void sieve_ast_extension_link(struct sieve_ast *ast,
+			      const struct sieve_extension *ext, bool required)
 {
 	unsigned int i, ext_count;
 	const struct sieve_extension *const *extensions;
 	struct sieve_ast_extension_reg *reg;
 
-	if ( ext->id < 0 ) return;
+	if (ext->id < 0)
+		return;
 
 	/* Initialize registration */
-	reg = array_idx_get_space(&ast->extensions,
-		(unsigned int) ext->id);
+	reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
 	i_assert(reg->ext == NULL || reg->ext == ext);
 	reg->ext = ext;
 	reg->required = reg->required || required;
 
 	/* Prevent duplicates */
 	extensions = array_get(&ast->linked_extensions, &ext_count);
-	for ( i = 0; i < ext_count; i++ ) {
-		if ( extensions[i] == ext )
+	for (i = 0; i < ext_count; i++) {
+		if (extensions[i] == ext)
 			return;
 	}
 
@@ -159,48 +157,52 @@ void sieve_ast_extension_link
 	array_append(&ast->linked_extensions, &ext, 1);
 }
 
-const struct sieve_extension * const *sieve_ast_extensions_get
-(struct sieve_ast *ast, unsigned int *count_r)
+const struct sieve_extension * const *
+sieve_ast_extensions_get(struct sieve_ast *ast, unsigned int *count_r)
 {
 	return array_get(&ast->linked_extensions, count_r);
 }
 
-void sieve_ast_extension_register
-(struct sieve_ast *ast, const struct sieve_extension *ext,
-	const struct sieve_ast_extension *ast_ext, void *context)
+void sieve_ast_extension_register(struct sieve_ast *ast,
+				  const struct sieve_extension *ext,
+				  const struct sieve_ast_extension *ast_ext,
+				  void *context)
 {
 	struct sieve_ast_extension_reg *reg;
 
-	if ( ext->id < 0 ) return;
+	if (ext->id < 0)
+		return;
 
 	/* Initialize registration */
-	reg = array_idx_get_space(&ast->extensions, (unsigned int) ext->id);
+	reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
 	i_assert(reg->ext == NULL || reg->ext == ext);
 	reg->ext = ext;
 	reg->ast_ext = ast_ext;
 	reg->context = context;
 }
 
-void sieve_ast_extension_set_context
-(struct sieve_ast *ast, const struct sieve_extension *ext, void *context)
+void sieve_ast_extension_set_context(struct sieve_ast *ast,
+				     const struct sieve_extension *ext,
+				     void *context)
 {
 	struct sieve_ast_extension_reg *reg;
 
-	if ( ext->id < 0 ) return;
+	if (ext->id < 0)
+		return;
 
-	reg = array_idx_get_space(&ast->extensions, (unsigned int) ext->id);
+	reg = array_idx_get_space(&ast->extensions, (unsigned int)ext->id);
 	reg->context = context;
 }
 
-void *sieve_ast_extension_get_context
-(struct sieve_ast *ast, const struct sieve_extension *ext)
+void *sieve_ast_extension_get_context(struct sieve_ast *ast,
+				      const struct sieve_extension *ext)
 {
 	const struct sieve_ast_extension_reg *reg;
 
-	if  ( ext->id < 0 || ext->id >= (int) array_count(&ast->extensions) )
+	if  (ext->id < 0 || ext->id >= (int)array_count(&ast->extensions))
 		return NULL;
 
-	reg = array_idx(&ast->extensions, (unsigned int) ext->id);
+	reg = array_idx(&ast->extensions, (unsigned int)ext->id);
 
 	return reg->context;
 }
@@ -210,8 +212,8 @@ bool sieve_ast_extension_is_required
 {
 	const struct sieve_ast_extension_reg *reg;
 
-	i_assert( ext->id >= 0 &&
-		ext->id < (int) array_count(&ast->extensions) );
+	i_assert(ext->id >= 0 &&
+		 ext->id < (int)array_count(&ast->extensions));
 
 	reg = array_idx(&ast->extensions, (unsigned int)ext->id);
 	return reg->required;
@@ -222,22 +224,22 @@ bool sieve_ast_extension_is_required
  */
 
 /* Very simplistic linked list implementation
- * FIXME: Move to separate file
+   FIXME: Merge with core
  */
 #define __LIST_CREATE(pool, type) { \
 		type *list = p_new(pool, type, 1); \
 		list->head = NULL; \
 		list->tail = NULL; \
-		list->len = 0;		\
+		list->len = 0; \
 		return list; \
 	}
 
 #define __LIST_ADD(list, node) { \
-		if ( list->len + 1 < list->len ) \
+		if (list->len + 1 < list->len) \
 			return FALSE; \
 		\
 		node->next = NULL; \
-		if ( list->head == NULL ) { \
+		if (list->head == NULL) { \
 			node->prev = NULL; \
 			list->head = node; \
 			list->tail = node; \
@@ -252,11 +254,11 @@ bool sieve_ast_extension_is_required
 	}
 
 #define __LIST_INSERT(list, before, node) { \
-		if ( list->len + 1 < list->len ) \
+		if (list->len + 1 < list->len) \
 			return FALSE; \
 		\
 		node->next = before; \
-		if ( list->head == before ) { \
+		if (list->head == before) { \
 			node->prev = NULL; \
 			list->head = node; \
 		} else { \
@@ -273,12 +275,13 @@ bool sieve_ast_extension_is_required
 #define __LIST_JOIN(list, node_type, items) { \
 		node_type *node; \
 		\
-		if ( list->len + items->len < list->len ) \
+		if (list->len + items->len < list->len) \
 			return FALSE; \
 		\
-		if ( items->len == 0 ) return TRUE; \
+		if (items->len == 0) \
+			return TRUE; \
 		\
-		if ( list->head == NULL ) { \
+		if (list->head == NULL) { \
 			list->head = items->head; \
 			list->tail = items->tail; \
 		} else { \
@@ -289,7 +292,7 @@ bool sieve_ast_extension_is_required
 		list->len += items->len; \
 		\
 		node = items->head; \
-		while ( node != NULL ) { \
+		while (node != NULL) { \
 			node->list = list; \
 			node = node->next; \
 		} \
@@ -304,19 +307,19 @@ bool sieve_ast_extension_is_required
 		\
 		left = count - 1; \
 		last = first; \
-		while ( left > 0 && last->next != NULL ) { \
+		while (left > 0 && last->next != NULL) { \
 			left--; \
 			last = last->next; \
 		} \
 		\
-		if ( first->list->head == first ) \
+		if (first->list->head == first) \
 			first->list->head = last->next; \
-		if ( first->list->tail == last ) \
+		if (first->list->tail == last) \
 			first->list->tail = first->prev; \
 		\
-		if ( first->prev != NULL ) \
+		if (first->prev != NULL) \
 			first->prev->next = last->next;	\
-		if ( last->next != NULL ) \
+		if (last->next != NULL) \
 			last->next->prev = first->prev; \
 		\
 		first->list->len -= count - left; \
@@ -330,15 +333,16 @@ bool sieve_ast_extension_is_required
 
 /* List of AST nodes */
 
-static struct sieve_ast_list *sieve_ast_list_create(pool_t pool)
+static struct sieve_ast_list *
+sieve_ast_list_create(pool_t pool)
 	__LIST_CREATE(pool, struct sieve_ast_list)
 
-static bool sieve_ast_list_add
-(struct sieve_ast_list *list, struct sieve_ast_node *node)
+static bool
+sieve_ast_list_add(struct sieve_ast_list *list, struct sieve_ast_node *node)
 	__LIST_ADD(list, node)
 
-static struct sieve_ast_node *sieve_ast_list_detach
-(struct sieve_ast_node *first, unsigned int count)
+static struct sieve_ast_node *
+sieve_ast_list_detach(struct sieve_ast_node *first, unsigned int count)
 	__LIST_DETACH(first, struct sieve_ast_node, count)
 
 /* List of argument AST nodes */
@@ -346,35 +350,37 @@ static struct sieve_ast_node *sieve_ast_
 struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool)
 	__LIST_CREATE(pool, struct sieve_ast_arg_list)
 
-bool sieve_ast_arg_list_add
-(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument)
+bool sieve_ast_arg_list_add(struct sieve_ast_arg_list *list,
+			    struct sieve_ast_argument *argument)
 	__LIST_ADD(list, argument)
 
-bool sieve_ast_arg_list_insert
-(struct sieve_ast_arg_list *list, struct sieve_ast_argument *before,
-	struct sieve_ast_argument *argument)
+bool sieve_ast_arg_list_insert(struct sieve_ast_arg_list *list,
+			       struct sieve_ast_argument *before,
+			       struct sieve_ast_argument *argument)
 	__LIST_INSERT(list, before, argument)
 
-static bool sieve_ast_arg_list_join
-(struct sieve_ast_arg_list *list, struct sieve_ast_arg_list *items)
+static bool
+sieve_ast_arg_list_join(struct sieve_ast_arg_list *list,
+			struct sieve_ast_arg_list *items)
 	__LIST_JOIN(list, struct sieve_ast_argument, items)
 
-static struct sieve_ast_argument *sieve_ast_arg_list_detach
-(struct sieve_ast_argument *first, const unsigned int count)
+static struct sieve_ast_argument *
+sieve_ast_arg_list_detach(struct sieve_ast_argument *first,
+			  const unsigned int count)
 	__LIST_DETACH(first, struct sieve_ast_argument, count)
 
-void sieve_ast_arg_list_substitute
-(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument,
-	struct sieve_ast_argument *replacement)
+void sieve_ast_arg_list_substitute(struct sieve_ast_arg_list *list,
+				   struct sieve_ast_argument *argument,
+				   struct sieve_ast_argument *replacement)
 {
-	if ( list->head == argument )
+	if (list->head == argument)
 		list->head = replacement;
-	if ( list->tail == argument )
+	if (list->tail == argument)
 		list->tail = replacement;
 
-	if ( argument->prev != NULL )
+	if (argument->prev != NULL)
 		argument->prev->next = replacement;
-	if ( argument->next != NULL )
+	if (argument->next != NULL)
 		argument->next->prev = replacement;
 
 	replacement->prev = argument->prev;
@@ -389,11 +395,12 @@ void sieve_ast_arg_list_substitute
  * AST node
  */
 
-static struct sieve_ast_node *sieve_ast_node_create
-(struct sieve_ast *ast, struct sieve_ast_node *parent, enum sieve_ast_type type,
-	unsigned int source_line)
+static struct sieve_ast_node *
+sieve_ast_node_create(struct sieve_ast *ast, struct sieve_ast_node *parent,
+		      enum sieve_ast_type type, unsigned int source_line)
 {
-	struct sieve_ast_node *node = p_new(ast->pool, struct sieve_ast_node, 1);
+	struct sieve_ast_node *node =
+		p_new(ast->pool, struct sieve_ast_node, 1);
 
 	node->ast = ast;
 	node->parent = parent;
@@ -414,11 +421,12 @@ static struct sieve_ast_node *sieve_ast_
 	return node;
 }
 
-static bool sieve_ast_node_add_command
-(struct sieve_ast_node *node, struct sieve_ast_node *command)
+static bool
+sieve_ast_node_add_command(struct sieve_ast_node *node,
+			   struct sieve_ast_node *command)
 {
-	i_assert( command->type == SAT_COMMAND &&
-		(node->type == SAT_ROOT || node->type == SAT_COMMAND) );
+	i_assert(command->type == SAT_COMMAND &&
+		 (node->type == SAT_ROOT || node->type == SAT_COMMAND));
 
 	if (node->commands == NULL)
 		node->commands = sieve_ast_list_create(node->ast->pool);
@@ -426,11 +434,12 @@ static bool sieve_ast_node_add_command
 	return sieve_ast_list_add(node->commands, command);
 }
 
-static bool sieve_ast_node_add_test
-(struct sieve_ast_node *node, struct sieve_ast_node *test)
+static bool
+sieve_ast_node_add_test(struct sieve_ast_node *node,
+			struct sieve_ast_node *test)
 {
-	i_assert( test->type == SAT_TEST &&
-		(node->type == SAT_TEST || node->type == SAT_COMMAND) );
+	i_assert(test->type == SAT_TEST &&
+		 (node->type == SAT_TEST || node->type == SAT_COMMAND));
 
 	if (node->tests == NULL)
 		node->tests = sieve_ast_list_create(node->ast->pool);
@@ -438,10 +447,11 @@ static bool sieve_ast_node_add_test
 	return sieve_ast_list_add(node->tests, test);
 }
 
-static bool sieve_ast_node_add_argument
-(struct sieve_ast_node *node, struct sieve_ast_argument *argument)
+static bool
+sieve_ast_node_add_argument(struct sieve_ast_node *node,
+			    struct sieve_ast_argument *argument)
 {
-	i_assert( node->type == SAT_TEST || node->type == SAT_COMMAND );
+	i_assert(node->type == SAT_TEST || node->type == SAT_COMMAND);
 
 	if (node->arguments == NULL)
 		node->arguments = sieve_ast_arg_list_create(node->ast->pool);
@@ -449,23 +459,24 @@ static bool sieve_ast_node_add_argument
 	return sieve_ast_arg_list_add(node->arguments, argument);
 }
 
-struct sieve_ast_node *sieve_ast_node_detach
-(struct sieve_ast_node *first)
+struct sieve_ast_node *sieve_ast_node_detach(struct sieve_ast_node *first)
 {
 	return sieve_ast_list_detach(first, 1);
 }
 
-const char *sieve_ast_type_name
-(enum sieve_ast_type ast_type)
+const char *sieve_ast_type_name(enum sieve_ast_type ast_type)
 {
-	switch ( ast_type ) {
-
-	case SAT_NONE: return "none";
-	case SAT_ROOT: return "ast root node";
-	case SAT_COMMAND: return "command";
-	case SAT_TEST: return "test";
-
-	default: return "??AST NODE??";
+	switch (ast_type) {
+	case SAT_NONE:
+		return "none";
+	case SAT_ROOT:
+		return "ast root node";
+	case SAT_COMMAND:
+		return "command";
+	case SAT_TEST:
+		return "test";
+	default:
+		return "??AST NODE??";
 	}
 }
 
@@ -473,8 +484,8 @@ const char *sieve_ast_type_name
  * Argument AST node
  */
 
-struct sieve_ast_argument *sieve_ast_argument_create
-(struct sieve_ast *ast, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_create(struct sieve_ast *ast, unsigned int source_line)
 {
 	struct sieve_ast_argument *arg =
 		p_new(ast->pool, struct sieve_ast_argument, 1);
@@ -491,17 +502,19 @@ struct sieve_ast_argument *sieve_ast_arg
 	return arg;
 }
 
-static void sieve_ast_argument_substitute
-(struct sieve_ast_argument *argument, struct sieve_ast_argument *replacement)
+static void
+sieve_ast_argument_substitute(struct sieve_ast_argument *argument,
+			      struct sieve_ast_argument *replacement)
 {
 	sieve_ast_arg_list_substitute(argument->list, argument, replacement);
 }
 
-struct sieve_ast_argument *sieve_ast_argument_string_create_raw
-(struct sieve_ast *ast, string_t *str, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_string_create_raw(struct sieve_ast *ast, string_t *str,
+				     unsigned int source_line)
 {
-	struct sieve_ast_argument *argument = sieve_ast_argument_create
-		(ast, source_line);
+	struct sieve_ast_argument *argument =
+		sieve_ast_argument_create(ast, source_line);
 
 	argument->type = SAAT_STRING;
 	argument->_value.str = str;
@@ -509,8 +522,9 @@ struct sieve_ast_argument *sieve_ast_arg
 	return argument;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_string_create
-(struct sieve_ast_node *node, const string_t *str, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_string_create(struct sieve_ast_node *node,
+				 const string_t *str, unsigned int source_line)
 {
 	struct sieve_ast_argument *argument;
 	string_t *newstr;
@@ -522,8 +536,8 @@ struct sieve_ast_argument *sieve_ast_arg
 	str_append_str(newstr, str);
 
 	/* Create string argument */
-	argument = sieve_ast_argument_string_create_raw
-		(node->ast, newstr, source_line);
+	argument = sieve_ast_argument_string_create_raw(
+		node->ast, newstr, source_line);
 
 	/* Add argument to command/test node */
 	sieve_ast_node_add_argument(node, argument);
@@ -531,8 +545,9 @@ struct sieve_ast_argument *sieve_ast_arg
 	return argument;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_cstring_create
-(struct sieve_ast_node *node, const char *str, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_cstring_create(struct sieve_ast_node *node, const char *str,
+				  unsigned int source_line)
 {
 	struct sieve_ast_argument *argument;
 	string_t *newstr;
@@ -544,8 +559,8 @@ struct sieve_ast_argument *sieve_ast_arg
 	str_append(newstr, str);
 
 	/* Create string argument */
-	argument = sieve_ast_argument_string_create_raw
-		(node->ast, newstr, source_line);
+	argument = sieve_ast_argument_string_create_raw(
+		node->ast, newstr, source_line);
 
 	/* Add argument to command/test node */
 	sieve_ast_node_add_argument(node, argument);
@@ -553,31 +568,32 @@ struct sieve_ast_argument *sieve_ast_arg
 	return argument;
 }
 
-void sieve_ast_argument_string_set
-(struct sieve_ast_argument *argument, string_t *newstr)
+void sieve_ast_argument_string_set(struct sieve_ast_argument *argument,
+				   string_t *newstr)
 {
-	i_assert( argument->type == SAAT_STRING);
+	i_assert(argument->type == SAAT_STRING);
 	argument->_value.str = newstr;
 }
 
-void sieve_ast_argument_string_setc
-(struct sieve_ast_argument *argument, const char *newstr)
+void sieve_ast_argument_string_setc(struct sieve_ast_argument *argument,
+				    const char *newstr)
 {
-	i_assert( argument->type == SAAT_STRING);
+	i_assert(argument->type == SAAT_STRING);
 
 	str_truncate(argument->_value.str, 0);
 	str_append(argument->_value.str, newstr);
 }
 
-void sieve_ast_argument_number_substitute
-(struct sieve_ast_argument *argument, unsigned int number)
+void sieve_ast_argument_number_substitute(struct sieve_ast_argument *argument,
+					  sieve_number_t number)
 {
 	argument->type = SAAT_NUMBER;
 	argument->_value.number = number;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_stringlist_create
-(struct sieve_ast_node *node, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_create(struct sieve_ast_node *node,
+				     unsigned int source_line)
 {
 	struct sieve_ast_argument *argument =
 		sieve_ast_argument_create(node->ast, source_line);
@@ -590,8 +606,9 @@ struct sieve_ast_argument *sieve_ast_arg
 	return argument;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_stringlist_substitute
-(struct sieve_ast_node *node, struct sieve_ast_argument *arg)
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_substitute(struct sieve_ast_node *node,
+					 struct sieve_ast_argument *arg)
 {
 	struct sieve_ast_argument *argument =
 		sieve_ast_argument_create(node->ast, arg->source_line);
@@ -604,31 +621,39 @@ struct sieve_ast_argument *sieve_ast_arg
 	return argument;
 }
 
-static inline bool _sieve_ast_stringlist_add_item
-(struct sieve_ast_argument *list, struct sieve_ast_argument *item)
-{
-	i_assert( list->type == SAAT_STRING_LIST );
-
-	if ( list->_value.strlist == NULL )
-		list->_value.strlist = sieve_ast_arg_list_create(list->ast->pool);
+static inline bool
+_sieve_ast_stringlist_add_item(struct sieve_ast_argument *list,
+			       struct sieve_ast_argument *item)
+{
+	i_assert(list->type == SAAT_STRING_LIST);
+
+	if (list->_value.strlist == NULL) {
+		list->_value.strlist =
+			sieve_ast_arg_list_create(list->ast->pool);
+	}
 
 	return sieve_ast_arg_list_add(list->_value.strlist, item);
 }
 
-static bool sieve_ast_stringlist_add_stringlist
-(struct sieve_ast_argument *list, struct sieve_ast_argument *items)
-{
-	i_assert( list->type == SAAT_STRING_LIST );
-	i_assert( items->type == SAAT_STRING_LIST );
-
-	if ( list->_value.strlist == NULL )
-		list->_value.strlist = sieve_ast_arg_list_create(list->ast->pool);
+static bool
+sieve_ast_stringlist_add_stringlist(struct sieve_ast_argument *list,
+				    struct sieve_ast_argument *items)
+{
+	i_assert(list->type == SAAT_STRING_LIST);
+	i_assert(items->type == SAAT_STRING_LIST);
+
+	if (list->_value.strlist == NULL) {
+		list->_value.strlist =
+			sieve_ast_arg_list_create(list->ast->pool);
+	}
 
-	return sieve_ast_arg_list_join(list->_value.strlist, items->_value.strlist);
+	return sieve_ast_arg_list_join(list->_value.strlist,
+				       items->_value.strlist);
 }
 
-static bool _sieve_ast_stringlist_add_str
-(struct sieve_ast_argument *list, string_t *str, unsigned int source_line)
+static bool
+_sieve_ast_stringlist_add_str(struct sieve_ast_argument *list, string_t *str,
+			      unsigned int source_line)
 {
 	struct sieve_ast_argument *stritem;
 
@@ -639,8 +664,8 @@ static bool _sieve_ast_stringlist_add_st
 	return _sieve_ast_stringlist_add_item(list, stritem);
 }
 
-bool sieve_ast_stringlist_add
-(struct sieve_ast_argument *list, const string_t *str, unsigned int source_line)
+bool sieve_ast_stringlist_add(struct sieve_ast_argument *list,
+			      const string_t *str, unsigned int source_line)
 {
 	string_t *copied_str = str_new(list->ast->pool, str_len(str));
 	str_append_str(copied_str, str);
@@ -648,8 +673,8 @@ bool sieve_ast_stringlist_add
 	return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
 }
 
-bool sieve_ast_stringlist_add_strc
-(struct sieve_ast_argument *list, const char *str, unsigned int source_line)
+bool sieve_ast_stringlist_add_strc(struct sieve_ast_argument *list,
+				   const char *str, unsigned int source_line)
 {
 	string_t *copied_str = str_new(list->ast->pool, strlen(str));
 	str_append(copied_str, str);
@@ -657,8 +682,9 @@ bool sieve_ast_stringlist_add_strc
 	return _sieve_ast_stringlist_add_str(list, copied_str, source_line);
 }
 
-struct sieve_ast_argument *sieve_ast_argument_tag_create
-(struct sieve_ast_node *node, const char *tag, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_tag_create(struct sieve_ast_node *node, const char *tag,
+			      unsigned int source_line)
 {
 	struct sieve_ast_argument *argument =
 		sieve_ast_argument_create(node->ast, source_line);
@@ -666,14 +692,14 @@ struct sieve_ast_argument *sieve_ast_arg
 	argument->type = SAAT_TAG;
 	argument->_value.tag = p_strdup(node->ast->pool, tag);
 
-	if ( !sieve_ast_node_add_argument(node, argument) )
+	if (!sieve_ast_node_add_argument(node, argument))
 		return NULL;
-
 	return argument;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_tag_insert
-(struct sieve_ast_argument *before, const char *tag, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_tag_insert(struct sieve_ast_argument *before,
+			      const char *tag, unsigned int source_line)
 {
 	struct sieve_ast_argument *argument =
 		sieve_ast_argument_create(before->ast, source_line);
@@ -681,95 +707,94 @@ struct sieve_ast_argument *sieve_ast_arg
 	argument->type = SAAT_TAG;
 	argument->_value.tag = p_strdup(before->ast->pool, tag);
 
-	if ( !sieve_ast_arg_list_insert(before->list, before, argument) )
+	if (!sieve_ast_arg_list_insert(before->list, before, argument))
 		return NULL;
-
 	return argument;
 }
 
-struct sieve_ast_argument *sieve_ast_argument_number_create
-(struct sieve_ast_node *node, unsigned int number, unsigned int source_line)
+struct sieve_ast_argument *
+sieve_ast_argument_number_create(struct sieve_ast_node *node,
+				 sieve_number_t number,
+				 unsigned int source_line)
 {
-
 	struct sieve_ast_argument *argument =
 		sieve_ast_argument_create(node->ast, source_line);
 
 	argument->type = SAAT_NUMBER;
 	argument->_value.number = number;
 
-	if ( !sieve_ast_node_add_argument(node, argument) )
+	if (!sieve_ast_node_add_argument(node, argument))
 		return NULL;
-
 	return argument;
 }
 
-void sieve_ast_argument_number_set
-(struct sieve_ast_argument *argument, unsigned int newnum)
+void sieve_ast_argument_number_set(struct sieve_ast_argument *argument,
+				   sieve_number_t newnum)
 {
-	i_assert( argument->type == SAAT_NUMBER );
+	i_assert(argument->type == SAAT_NUMBER);
 	argument->_value.number = newnum;
 }
 
-
-struct sieve_ast_argument *sieve_ast_arguments_detach
-(struct sieve_ast_argument *first, unsigned int count)
+struct sieve_ast_argument *
+sieve_ast_arguments_detach(struct sieve_ast_argument *first,
+			   unsigned int count)
 {
 	return sieve_ast_arg_list_detach(first, count);
 }
 
-bool sieve_ast_argument_attach
-(struct sieve_ast_node *node, struct sieve_ast_argument *argument)
+bool sieve_ast_argument_attach(struct sieve_ast_node *node,
+			       struct sieve_ast_argument *argument)
 {
 	return sieve_ast_node_add_argument(node, argument);
 }
 
-const char *sieve_ast_argument_type_name
-(enum sieve_ast_argument_type arg_type)
+const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type)
 {
-	switch ( arg_type ) {
-
-	case SAAT_NONE: return "none";
-	case SAAT_STRING_LIST: return "a string list";
-	case SAAT_STRING: return "a string";
-	case SAAT_NUMBER: return "a number";
-	case SAAT_TAG: return "a tag";
-
-	default: return "??ARGUMENT??";
+	switch (arg_type) {
+	case SAAT_NONE:
+		return "none";
+	case SAAT_STRING_LIST:
+		return "a string list";
+	case SAAT_STRING:
+		return "a string";
+	case SAAT_NUMBER:
+		return "a number";
+	case SAAT_TAG:
+		return "a tag";
+	default:
+		return "??ARGUMENT??";
 	}
 }
 
 /* Test AST node */
 
-struct sieve_ast_node *sieve_ast_test_create
-(struct sieve_ast_node *parent, const char *identifier,
-	unsigned int source_line)
+struct sieve_ast_node *
+sieve_ast_test_create(struct sieve_ast_node *parent, const char *identifier,
+		      unsigned int source_line)
 {
-	struct sieve_ast_node *test = sieve_ast_node_create
-		(parent->ast, parent, SAT_TEST, source_line);
+	struct sieve_ast_node *test = sieve_ast_node_create(
+		parent->ast, parent, SAT_TEST, source_line);
 
 	test->identifier = p_strdup(parent->ast->pool, identifier);
 
-	if ( !sieve_ast_node_add_test(parent, test) )
+	if (!sieve_ast_node_add_test(parent, test))
 		return NULL;
-
 	return test;
 }
 
 /* Command AST node */
 
-struct sieve_ast_node *sieve_ast_command_create
-(struct sieve_ast_node *parent, const char *identifier,
-	unsigned int source_line)
+struct sieve_ast_node *
+sieve_ast_command_create(struct sieve_ast_node *parent, const char *identifier,
+			 unsigned int source_line)
 {
-
-	struct sieve_ast_node *command = sieve_ast_node_create
-		(parent->ast, parent, SAT_COMMAND, source_line);
+	struct sieve_ast_node *command = sieve_ast_node_create(
+		parent->ast, parent, SAT_COMMAND, source_line);
 
 	command->identifier = p_strdup(parent->ast->pool, identifier);
 
-	if ( !sieve_ast_node_add_command(parent, command) )
+	if (!sieve_ast_node_add_command(parent, command))
 		return NULL;
-
 	return command;
 }
 
@@ -777,22 +802,21 @@ struct sieve_ast_node *sieve_ast_command
  * Utility
  */
 
-int sieve_ast_stringlist_map
-(struct sieve_ast_argument **listitem, void *context,
+int sieve_ast_stringlist_map(
+	struct sieve_ast_argument **listitem, void *context,
 	int (*map_function)(void *context, struct sieve_ast_argument *arg))
 {
-	if ( sieve_ast_argument_type(*listitem) == SAAT_STRING ) {
+	if (sieve_ast_argument_type(*listitem) == SAAT_STRING) {
 		/* Single string */
 		return map_function(context, *listitem);
-	} else if ( sieve_ast_argument_type(*listitem) == SAAT_STRING_LIST ) {
+	} else if (sieve_ast_argument_type(*listitem) == SAAT_STRING_LIST) {
 		int ret = 0;
 
 		/* String list */
 		*listitem = sieve_ast_strlist_first(*listitem);
 
-		while ( *listitem != NULL ) {
-
-			if ( (ret=map_function(context, *listitem)) <= 0 )
+		while (*listitem != NULL) {
+			if ((ret = map_function(context, *listitem)) <= 0)
 				return ret;
 
 			*listitem = sieve_ast_strlist_next(*listitem);
@@ -805,8 +829,9 @@ int sieve_ast_stringlist_map
 	return -1;
 }
 
-struct sieve_ast_argument *sieve_ast_stringlist_join
-(struct sieve_ast_argument *list, struct sieve_ast_argument *items)
+struct sieve_ast_argument *
+sieve_ast_stringlist_join(struct sieve_ast_argument *list,
+			  struct sieve_ast_argument *items)
 {
 	enum sieve_ast_argument_type list_type, items_type;
 	struct sieve_ast_argument *newlist;
@@ -814,92 +839,78 @@ struct sieve_ast_argument *sieve_ast_str
 	list_type = sieve_ast_argument_type(list);
 	items_type = sieve_ast_argument_type(items);
 
-	switch ( list_type ) {
-
+	switch (list_type) {
 	case SAAT_STRING:
-		switch ( items_type ) {
-
+		switch (items_type) {
 		case SAAT_STRING:
-			newlist =
-				sieve_ast_argument_create(list->ast, list->source_line);
+			newlist = sieve_ast_argument_create(
+				list->ast, list->source_line);
 			newlist->type = SAAT_STRING_LIST;
 			newlist->_value.strlist = NULL;
 
 			sieve_ast_argument_substitute(list, newlist);
 			sieve_ast_arguments_detach(items, 1);
 
-			if ( !_sieve_ast_stringlist_add_item(newlist, list) ||
-				!_sieve_ast_stringlist_add_item(newlist, items) ) {
+			if (!_sieve_ast_stringlist_add_item(newlist, list) ||
+			    !_sieve_ast_stringlist_add_item(newlist, items))
 				return NULL;
-			}
-
 			return newlist;
-
 		case SAAT_STRING_LIST:
-			/* Adding stringlist to string; make them swith places and add one to the
-			 * other.
+			/* Adding stringlist to string; make them swith places
+			   and add one to the other.
 			 */
 			sieve_ast_arguments_detach(items, 1);
 			sieve_ast_argument_substitute(list, items);
-			if ( !_sieve_ast_stringlist_add_item(items, list) )
+			if (!_sieve_ast_stringlist_add_item(items, list))
 				return NULL;
-
 			return list;
-
 		default:
 			i_unreached();
 		}
 		break;
-
 	case SAAT_STRING_LIST:
-		switch ( items_type ) {
-
+		switch (items_type) {
 		case SAAT_STRING:
 			/* Adding string to stringlist; straightforward add */
 			sieve_ast_arguments_detach(items, 1);
-			if ( !_sieve_ast_stringlist_add_item(list, items) )
+			if (!_sieve_ast_stringlist_add_item(list, items))
 				return NULL;
-
 			return list;
-
 		case SAAT_STRING_LIST:
-			/* Adding stringlist to stringlist; perform actual join */
+			/* Adding stringlist to stringlist; perform actual join
+			 */
 			sieve_ast_arguments_detach(items, 1);
-			if ( !sieve_ast_stringlist_add_stringlist(list, items) )
+			if (!sieve_ast_stringlist_add_stringlist(list, items))
 				return NULL;
-
 			return list;
-
 		default:
 			i_unreached();
 		}
-
 		break;
 	default:
 		i_unreached();
 	}
-
 	return NULL;
 }
 
-
 /* Debug */
 
 /* Unparsing, currently implemented using plain printf()s */
 
 static void sieve_ast_unparse_string(const string_t *strval)
 {
-	char *str = t_strdup_noconst(str_c((string_t *) strval));
+	char *str = t_strdup_noconst(str_c((string_t *)strval));
 
-	if ( strchr(str, '\n') != NULL && str[strlen(str)-1] == '\n' ) {
-		/* Print it as a multi-line string and do required dotstuffing */
+	if (strchr(str, '\n') != NULL && str[strlen(str)-1] == '\n') {
+		/* Print it as a multi-line string and do required dotstuffing
+		 */
 		char *spos = str;
 		char *epos = strchr(str, '\n');
 		printf("text:\n");
 
-		while ( epos != NULL ) {
+		while (epos != NULL) {
 			*epos = '\0';
-			if ( *spos == '.' )
+			if (*spos == '.')
 				printf(".");
 
 			printf("%s\n", spos);
@@ -907,8 +918,8 @@ static void sieve_ast_unparse_string(con
 			spos = epos+1;
 			epos = strchr(spos, '\n');
 		}
-		if ( *spos == '.' )
-				printf(".");
+		if (*spos == '.')
+			printf(".");
 
 		printf("%s\n.\n", spos);
 	} else {
@@ -917,7 +928,7 @@ static void sieve_ast_unparse_string(con
 		char *epos = strchr(str, '"');
 		printf("\"");
 
-		while ( epos != NULL ) {
+		while (epos != NULL) {
 			*epos = '\0';
 			printf("%s\\\"", spos);
 
@@ -929,33 +940,35 @@ static void sieve_ast_unparse_string(con
 	}
 }
 
-static void sieve_ast_unparse_argument
-	(struct sieve_ast_argument *argument, int level);
+static void
+sieve_ast_unparse_argument(struct sieve_ast_argument *argument, int level);
 
-static void sieve_ast_unparse_stringlist
-(struct sieve_ast_argument *strlist, int level)
+static void
+sieve_ast_unparse_stringlist(struct sieve_ast_argument *strlist, int level)
 {
 	struct sieve_ast_argument *stritem;
 
-	if ( sieve_ast_strlist_count(strlist) > 1 ) {
+	if (sieve_ast_strlist_count(strlist) > 1) {
 		int i;
 
 		printf("[\n");
 
 		/* Create indent */
-		for ( i = 0; i < level+2; i++ )
+		for (i = 0; i < level+2; i++)
 			printf("  ");
 
 		stritem = sieve_ast_strlist_first(strlist);
-		if ( stritem != NULL ) {
-			sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+		if (stritem != NULL) {
+			sieve_ast_unparse_string(
+				sieve_ast_strlist_str(stritem));
 
 			stritem = sieve_ast_strlist_next(stritem);
-			while ( stritem != NULL ) {
+			while (stritem != NULL) {
 				printf(",\n");
-				for ( i = 0; i < level+2; i++ )
+				for (i = 0; i < level+2; i++)
 					printf("  ");
-				sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+				sieve_ast_unparse_string(
+					sieve_ast_strlist_str(stritem));
 				stritem = sieve_ast_strlist_next(stritem);
 			}
 		}
@@ -963,15 +976,17 @@ static void sieve_ast_unparse_stringlist
 		printf(" ]");
 	} else {
 		stritem = sieve_ast_strlist_first(strlist);
-		if ( stritem != NULL )
-			sieve_ast_unparse_string(sieve_ast_strlist_str(stritem));
+		if (stritem != NULL) {
+			sieve_ast_unparse_string(
+				sieve_ast_strlist_str(stritem));
+		}
 	}
 }
 
-static void sieve_ast_unparse_argument
-(struct sieve_ast_argument *argument, int level)
+static void
+sieve_ast_unparse_argument(struct sieve_ast_argument *argument, int level)
 {
-	switch ( argument->type ) {
+	switch (argument->type) {
 	case SAAT_STRING:
 		sieve_ast_unparse_string(sieve_ast_argument_str(argument));
 		break;
@@ -979,7 +994,8 @@ static void sieve_ast_unparse_argument
 		sieve_ast_unparse_stringlist(argument, level+1);
 		break;
 	case SAAT_NUMBER:
-		printf("%d", sieve_ast_argument_number(argument));
+		printf("%"SIEVE_PRI_NUMBER,
+		       sieve_ast_argument_number(argument));
 		break;
 	case SAAT_TAG:
 		printf(":%s", sieve_ast_argument_tag(argument));
@@ -990,52 +1006,49 @@ static void sieve_ast_unparse_argument
 	}
 }
 
-static void sieve_ast_unparse_test
-	(struct sieve_ast_node *node, int level);
+static void sieve_ast_unparse_test(struct sieve_ast_node *node, int level);
 
-static void sieve_ast_unparse_tests
-(struct sieve_ast_node *node, int level)
+static void sieve_ast_unparse_tests(struct sieve_ast_node *node, int level)
 {
 	struct sieve_ast_node *test;
 
-	if ( sieve_ast_test_count(node) > 1 ) {
+	if (sieve_ast_test_count(node) > 1) {
 		int i;
 
 		printf(" (\n");
 
 		/* Create indent */
-		for ( i = 0; i < level+2; i++ )
+		for (i = 0; i < level+2; i++)
 			printf("  ");
 
 		test = sieve_ast_test_first(node);
 		sieve_ast_unparse_test(test, level+1);
 
 		test = sieve_ast_test_next(test);
-		while ( test != NULL ) {
+		while (test != NULL) {
 			printf(", \n");
-			for ( i = 0; i < level+2; i++ )
+			for (i = 0; i < level+2; i++)
 				printf("  ");
 			sieve_ast_unparse_test(test, level+1);
-		  test = sieve_ast_test_next(test);
-	  }
+			test = sieve_ast_test_next(test);
+		}
 
 		printf(" )");
 	} else {
 		test = sieve_ast_test_first(node);
-		if ( test != NULL )
+		if (test != NULL)
 			sieve_ast_unparse_test(test, level);
 	}
 }
 
-static void sieve_ast_unparse_test
-(struct sieve_ast_node *node, int level)
+static void sieve_ast_unparse_test(struct sieve_ast_node *node, int level)
 {
 	struct sieve_ast_argument *argument;
 
 	printf(" %s", node->identifier);
 
 	argument = sieve_ast_argument_first(node);
-	while ( argument != NULL ) {
+	while (argument != NULL) {
 		printf(" ");
 		sieve_ast_unparse_argument(argument, level);
 		argument = sieve_ast_argument_next(argument);
@@ -1044,8 +1057,7 @@ static void sieve_ast_unparse_test
 	sieve_ast_unparse_tests(node, level);
 }
 
-static void sieve_ast_unparse_command
-(struct sieve_ast_node *node, int level)
+static void sieve_ast_unparse_command(struct sieve_ast_node *node, int level)
 {
 	struct sieve_ast_node *command;
 	struct sieve_ast_argument *argument;
@@ -1053,13 +1065,13 @@ static void sieve_ast_unparse_command
 	int i;
 
 	/* Create indent */
-	for ( i = 0; i < level; i++ )
+	for (i = 0; i < level; i++)
 		printf("  ");
 
 	printf("%s", node->identifier);
 
 	argument = sieve_ast_argument_first(node);
-	while ( argument != NULL ) {
+	while (argument != NULL) {
 		printf(" ");
 		sieve_ast_unparse_argument(argument, level);
 		argument = sieve_ast_argument_next(argument);
@@ -1068,15 +1080,15 @@ static void sieve_ast_unparse_command
 	sieve_ast_unparse_tests(node, level);
 
 	command = sieve_ast_command_first(node);
-	if ( command != NULL ) {
+	if (command != NULL) {
 		printf(" {\n");
 
-		while ( command != NULL) {
+		while (command != NULL) {
 			sieve_ast_unparse_command(command, level+1);
 			command = sieve_ast_command_next(command);
 		}
 
-		for ( i = 0; i < level; i++ )
+		for (i = 0; i < level; i++)
 			printf("  ");
 		printf("}\n");
 	} else
@@ -1091,11 +1103,9 @@ void sieve_ast_unparse(struct sieve_ast
 
 	T_BEGIN {
 		command = sieve_ast_command_first(sieve_ast_root(ast));
-		while ( command != NULL ) {
+		while (command != NULL) {
 			sieve_ast_unparse_command(command, 0);
 			command = sieve_ast_command_next(command);
 		}
 	} T_END;
 }
-
-
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-ast.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-ast.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-ast.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-ast.h	2022-06-14 06:55:57.000000000 +0000
@@ -40,8 +40,8 @@
 */
 
 /* IMPORTANT NOTICE: Do not decorate the AST with objects other than those
- * allocated on the ast's pool or static const objects. Otherwise it is possible
- * that pointers in the tree become dangling which is highly undesirable.
+   allocated on the ast's pool or static const objects. Otherwise it is possible
+   that pointers in the tree become dangling which is highly undesirable.
  */
 
 /*
@@ -93,7 +93,7 @@ struct sieve_ast_argument {
 		string_t *str;
 		struct sieve_ast_arg_list *strlist;
 		const char *tag;
-		unsigned int number;
+		sieve_number_t number;
 	} _value;
 
 	unsigned int source_line;
@@ -180,25 +180,27 @@ struct sieve_ast_extension {
 	const struct sieve_extension_def *ext;
 
 	void (*free)(const struct sieve_extension *ext, struct sieve_ast *ast,
-		void *context);
+		     void *context);
 };
 
-void sieve_ast_extension_link
-	(struct sieve_ast *ast, const struct sieve_extension *ext,
-		bool required);
-const struct sieve_extension * const *sieve_ast_extensions_get
-	(struct sieve_ast *ast, unsigned int *count_r);
-
-void sieve_ast_extension_register
-	(struct sieve_ast *ast, const struct sieve_extension *ext,
-		const struct sieve_ast_extension *ast_ext, void *context);
-void sieve_ast_extension_set_context
-	(struct sieve_ast *ast, const struct sieve_extension *ext, void *context);
-void *sieve_ast_extension_get_context
-	(struct sieve_ast *ast, const struct sieve_extension *ext);
+void sieve_ast_extension_link(struct sieve_ast *ast,
+			      const struct sieve_extension *ext,
+			      bool required);
+const struct sieve_extension * const *
+sieve_ast_extensions_get(struct sieve_ast *ast, unsigned int *count_r);
+
+void sieve_ast_extension_register(struct sieve_ast *ast,
+				  const struct sieve_extension *ext,
+				  const struct sieve_ast_extension *ast_ext,
+				  void *context);
+void sieve_ast_extension_set_context(struct sieve_ast *ast,
+				     const struct sieve_extension *ext,
+				     void *context);
+void *sieve_ast_extension_get_context(struct sieve_ast *ast,
+				      const struct sieve_extension *ext);
 
-bool sieve_ast_extension_is_required
-	(struct sieve_ast *ast, const struct sieve_extension *ext);
+bool sieve_ast_extension_is_required(struct sieve_ast *ast,
+				     const struct sieve_extension *ext);
 
 /*
  * AST node manipulation
@@ -206,89 +208,98 @@ bool sieve_ast_extension_is_required
 
 /* Command nodes */
 
-struct sieve_ast_node *sieve_ast_test_create
-	(struct sieve_ast_node *parent, const char *identifier,
-		unsigned int source_line);
-struct sieve_ast_node *sieve_ast_command_create
-	(struct sieve_ast_node *parent, const char *identifier,
-		unsigned int source_line);
+struct sieve_ast_node *
+sieve_ast_test_create(struct sieve_ast_node *parent, const char *identifier,
+		      unsigned int source_line);
+struct sieve_ast_node *
+sieve_ast_command_create(struct sieve_ast_node *parent, const char *identifier,
+			 unsigned int source_line);
 
-struct sieve_ast_node *sieve_ast_node_detach
-	(struct sieve_ast_node *first);
+struct sieve_ast_node *
+sieve_ast_node_detach(struct sieve_ast_node *first);
 
 const char *sieve_ast_type_name(enum sieve_ast_type ast_type);
 
 /* Argument nodes */
 
-struct sieve_ast_argument *sieve_ast_argument_create
-	(struct sieve_ast *ast, unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_create(struct sieve_ast *ast, unsigned int source_line);
 
 struct sieve_ast_arg_list *sieve_ast_arg_list_create(pool_t pool);
-bool sieve_ast_arg_list_add
-	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument);
-bool sieve_ast_arg_list_insert
-	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *before,
-		struct sieve_ast_argument *argument);
-void sieve_ast_arg_list_substitute
-	(struct sieve_ast_arg_list *list, struct sieve_ast_argument *argument,
-		struct sieve_ast_argument *replacement);
-
-struct sieve_ast_argument *sieve_ast_argument_string_create_raw
-	(struct sieve_ast *ast, string_t *str, unsigned int source_line);
-struct sieve_ast_argument *sieve_ast_argument_string_create
-	(struct sieve_ast_node *node, const string_t *str, unsigned int source_line);
-struct sieve_ast_argument *sieve_ast_argument_cstring_create
-	(struct sieve_ast_node *node, const char *str, unsigned int source_line);
-
-struct sieve_ast_argument *sieve_ast_argument_tag_create
-	(struct sieve_ast_node *node, const char *tag, unsigned int source_line);
-
-struct sieve_ast_argument *sieve_ast_argument_number_create
-	(struct sieve_ast_node *node, unsigned int number, unsigned int source_line);
-
-void sieve_ast_argument_string_set
-	(struct sieve_ast_argument *argument, string_t *newstr);
-void sieve_ast_argument_string_setc
-	(struct sieve_ast_argument *argument, const char *newstr);
-
-void sieve_ast_argument_number_set
-	(struct sieve_ast_argument *argument, unsigned int newnum);
-void sieve_ast_argument_number_substitute
-	(struct sieve_ast_argument *argument, unsigned int number);
-
-struct sieve_ast_argument *sieve_ast_argument_tag_insert
-(struct sieve_ast_argument *before, const char *tag, unsigned int source_line);
-
-struct sieve_ast_argument *sieve_ast_argument_stringlist_create
-	(struct sieve_ast_node *node, unsigned int source_line);
-struct sieve_ast_argument *sieve_ast_argument_stringlist_substitute
-	(struct sieve_ast_node *node, struct sieve_ast_argument *arg);
-
-struct sieve_ast_argument *sieve_ast_arguments_detach
-	(struct sieve_ast_argument *first, unsigned int count);
-bool sieve_ast_argument_attach
-	(struct sieve_ast_node *node, struct sieve_ast_argument *argument);
+bool sieve_ast_arg_list_add(struct sieve_ast_arg_list *list,
+			    struct sieve_ast_argument *argument);
+bool sieve_ast_arg_list_insert(struct sieve_ast_arg_list *list,
+			       struct sieve_ast_argument *before,
+			       struct sieve_ast_argument *argument);
+void sieve_ast_arg_list_substitute(struct sieve_ast_arg_list *list,
+				   struct sieve_ast_argument *argument,
+				   struct sieve_ast_argument *replacement);
+
+struct sieve_ast_argument *
+sieve_ast_argument_string_create_raw(struct sieve_ast *ast, string_t *str,
+				     unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_string_create(struct sieve_ast_node *node,
+				 const string_t *str, unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_cstring_create(struct sieve_ast_node *node, const char *str,
+				  unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_create(struct sieve_ast_node *node, const char *tag,
+			      unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_number_create(struct sieve_ast_node *node,
+				 sieve_number_t number,
+				 unsigned int source_line);
+
+void sieve_ast_argument_string_set(struct sieve_ast_argument *argument,
+				   string_t *newstr);
+void sieve_ast_argument_string_setc(struct sieve_ast_argument *argument,
+				    const char *newstr);
+
+void sieve_ast_argument_number_set(struct sieve_ast_argument *argument,
+				   sieve_number_t newnum);
+void sieve_ast_argument_number_substitute(struct sieve_ast_argument *argument,
+					  sieve_number_t number);
+
+struct sieve_ast_argument *
+sieve_ast_argument_tag_insert(struct sieve_ast_argument *before,
+			      const char *tag, unsigned int source_line);
+
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_create(struct sieve_ast_node *node,
+				     unsigned int source_line);
+struct sieve_ast_argument *
+sieve_ast_argument_stringlist_substitute(struct sieve_ast_node *node,
+					 struct sieve_ast_argument *arg);
+
+struct sieve_ast_argument *
+sieve_ast_arguments_detach(struct sieve_ast_argument *first,
+			   unsigned int count);
+bool sieve_ast_argument_attach(struct sieve_ast_node *node,
+			       struct sieve_ast_argument *argument);
 
 const char *sieve_ast_argument_type_name(enum sieve_ast_argument_type arg_type);
 #define sieve_ast_argument_name(argument) \
 	sieve_ast_argument_type_name((argument)->type)
 
-bool sieve_ast_stringlist_add
-	(struct sieve_ast_argument *list, const string_t *str,
-		unsigned int source_line);
-bool sieve_ast_stringlist_add_strc
-	(struct sieve_ast_argument *list, const char *str,
-		unsigned int source_line);
+bool sieve_ast_stringlist_add(struct sieve_ast_argument *list,
+			      const string_t *str, unsigned int source_line);
+bool sieve_ast_stringlist_add_strc(struct sieve_ast_argument *list,
+				   const char *str, unsigned int source_line);
 
 /*
  * Utility
  */
 
-int sieve_ast_stringlist_map
-	(struct sieve_ast_argument **listitem, void *context,
-		int (*map_function)(void *context, struct sieve_ast_argument *arg));
-struct sieve_ast_argument *sieve_ast_stringlist_join
-	(struct sieve_ast_argument *list, struct sieve_ast_argument *items);
+int sieve_ast_stringlist_map(
+	struct sieve_ast_argument **listitem, void *context,
+	int (*map_function)(void *context, struct sieve_ast_argument *arg));
+struct sieve_ast_argument *
+sieve_ast_stringlist_join(struct sieve_ast_argument *list,
+			  struct sieve_ast_argument *items);
 
 /*
  * AST access macros
@@ -326,8 +337,8 @@ struct sieve_ast_argument *sieve_ast_str
 
 /* Compare the identifier of the previous command */
 #define sieve_ast_prev_cmd_is(cmd, id) \
-	( (cmd)->prev == NULL ? FALSE : \
-		strncasecmp((cmd)->prev->identifier, id, sizeof(id)-1) == 0 )
+	((cmd)->prev == NULL ? FALSE : \
+	 strncasecmp((cmd)->prev->identifier, id, sizeof(id)-1) == 0)
 
 /* AST test macros */
 #define sieve_ast_test_count(node) __AST_NODE_LIST_COUNT(node, tests)
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-binary-debug.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-binary-debug.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-binary-debug.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-binary-debug.c	2022-06-14 06:55:57.000000000 +0000
@@ -40,8 +40,8 @@ struct sieve_binary_debug_writer {
 	struct sieve_binary_block *sblock;
 
 	sieve_size_t address;
-	unsigned long int line;
-	unsigned long int column;
+	unsigned int line;
+	unsigned int column;
 };
 
 struct sieve_binary_debug_writer *
@@ -66,13 +66,18 @@ void sieve_binary_debug_emit(struct siev
 			     sieve_size_t code_address, unsigned int code_line,
 			     unsigned int code_column)
 {
+	i_assert(code_address >= dwriter->address);
+
 	struct sieve_binary_block *sblock = dwriter->sblock;
 	sieve_size_t address_inc = code_address - dwriter->address;
-	unsigned int line_inc = code_line - dwriter->line;
+	int line_inc = (code_line > dwriter->line ?
+			(int)(code_line - dwriter->line) :
+			-(int)(dwriter->line - code_line));
 	unsigned int sp_opcode = 0;
 
 	/* Check for applicability of special opcode */
-	if ((LINPROG_LINE_BASE + LINPROG_LINE_RANGE - 1) >= line_inc) {
+	if (line_inc > 0 &&
+	    (LINPROG_LINE_BASE + LINPROG_LINE_RANGE - 1) >= line_inc) {
 		sp_opcode = LINPROG_OP_SPECIAL_BASE +
 			(line_inc - LINPROG_LINE_BASE) +
 			(LINPROG_LINE_RANGE * address_inc);
@@ -83,10 +88,11 @@ void sieve_binary_debug_emit(struct siev
 
 	/* Update line and address */
 	if (sp_opcode == 0) {
-		if (line_inc > 0) {
+		if (line_inc != 0) {
 			(void)sieve_binary_emit_byte(sblock,
 						     LINPROG_OP_ADVANCE_LINE);
-			(void)sieve_binary_emit_unsigned(sblock, line_inc);
+			(void)sieve_binary_emit_unsigned(
+				sblock, (unsigned int)line_inc);
 		}
 
 		if (address_inc > 0) {
@@ -120,9 +126,9 @@ struct sieve_binary_debug_reader {
 	struct sieve_binary_block *sblock;
 
 	sieve_size_t address, last_address;
-	unsigned long int line, last_line;
+	unsigned int line, last_line;
 
-	unsigned long int column;
+	unsigned int column;
 
 	sieve_size_t state;
 };
@@ -159,7 +165,7 @@ sieve_binary_debug_read_line(struct siev
 {
 	size_t linprog_size;
 	sieve_size_t address;
-	unsigned long int line;
+	unsigned int line;
 
 	if (code_address < dreader->last_address)
 		sieve_binary_debug_reader_reset(dreader);
@@ -183,6 +189,7 @@ sieve_binary_debug_read_line(struct siev
 	while (dreader->state < linprog_size) {
 		unsigned int opcode;
 		unsigned int value;
+		int line_inc;
 
 		if (sieve_binary_read_byte(dreader->sblock,
 					   &dreader->state, &opcode)) {
@@ -224,8 +231,11 @@ sieve_binary_debug_read_line(struct siev
 					sieve_binary_debug_reader_reset(dreader);
 					return 0;
 				}
-				debug_printf("        : + %d\n", value);
-				line += value;
+				line_inc = (int)value;
+				debug_printf("        : + %d\n", line_inc);
+				line = (line_inc > 0 ?
+					line + (unsigned int)line_inc :
+					line - (unsigned int)-line_inc);
 				break;
 			case LINPROG_OP_SET_COLUMN:
 				debug_printf("%08llx: SET_COL\n",
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-binary-file.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-binary-file.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-binary-file.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-binary-file.c	2022-06-14 06:55:57.000000000 +0000
@@ -985,11 +985,16 @@ sieve_binary_file_do_update_resource_usa
 {
 	struct sieve_binary_header *header = &sbin->header;
 	struct file_lock *lock;
+	const char *error;
 	int ret;
 
-	ret = file_wait_lock(fd, sbin->path, F_WRLCK, FILE_LOCK_METHOD_FCNTL,
-			     SIEVE_BINARY_FILE_LOCK_TIMEOUT, &lock);
+	struct file_lock_settings lock_set = {
+		.lock_method = FILE_LOCK_METHOD_FCNTL,
+	};
+	ret = file_wait_lock(fd, sbin->path, F_WRLCK, &lock_set,
+			     SIEVE_BINARY_FILE_LOCK_TIMEOUT, &lock, &error);
 	if (ret <= 0) {
+		e_error(sbin->event, "%s", error);
 		*error_r = SIEVE_ERROR_TEMP_FAILURE;
 		return -1;
 	}
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve.c	2022-06-14 06:55:57.000000000 +0000
@@ -671,6 +671,7 @@ int sieve_execute(struct sieve_binary *s
 	/* Cleanup */
 	if (result != NULL)
 		sieve_result_unref(&result);
+	sieve_execute_finish(&eenv, ret);
 	sieve_execute_deinit(&eenv);
 	pool_unref(&pool);
 
@@ -934,6 +935,8 @@ int sieve_multiscript_finish(struct siev
 		sieve_execution_exitcode_to_str(status),
 		(mscript->keep ? "yes" : "no"));
 
+	sieve_execute_finish(&mscript->exec_env, status);
+
 	/* Cleanup */
 	sieve_multiscript_destroy(&mscript);
 
@@ -1285,6 +1288,8 @@ bool sieve_resource_usage_is_excessive(
 	const struct sieve_resource_usage *rusage)
 {
 	i_assert(svinst->max_cpu_time_secs <= (UINT_MAX / 1000));
+	if (svinst->max_cpu_time_secs == 0)
+		return FALSE;
 	return (rusage->cpu_time_msecs > (svinst->max_cpu_time_secs * 1000));
 }
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-common.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-common.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-common.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-common.h	2022-06-14 06:55:57.000000000 +0000
@@ -16,6 +16,7 @@ typedef uint32_t sieve_offset_t;
 typedef uint64_t sieve_number_t;
 
 #define SIEVE_MAX_NUMBER ((sieve_number_t)-1)
+#define SIEVE_PRI_NUMBER PRIu64
 
 /*
  * Forward declarations
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-execute.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-execute.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-execute.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-execute.c	2022-06-14 06:55:57.000000000 +0000
@@ -5,11 +5,34 @@
 
 #include "sieve-execute.h"
 
+struct sieve_execute_state {
+	void *dup_trans;
+};
+
 struct event_category event_category_sieve_execute = {
 	.parent = &event_category_sieve,
 	.name = "sieve-execute",
 };
 
+static struct sieve_execute_state *
+sieve_execute_state_create(struct sieve_execute_env *eenv)
+{
+	return p_new(eenv->pool, struct sieve_execute_state, 1);
+}
+
+static void
+sieve_execute_state_free(struct sieve_execute_state **_estate,
+			 struct sieve_execute_env *eenv)
+{
+	struct sieve_execute_state *estate = *_estate;
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	*_estate = NULL;
+
+	if (senv->duplicate_transaction_rollback != NULL)
+		senv->duplicate_transaction_rollback(&estate->dup_trans);
+}
+
 void sieve_execute_init(struct sieve_execute_env *eenv,
 			struct sieve_instance *svinst, pool_t pool,
 			const struct sieve_message_data *msgdata,
@@ -35,6 +58,8 @@ void sieve_execute_init(struct sieve_exe
 			smtp_address_encode(msgdata->envelope.rcpt_to));
 	}
 
+	eenv->state = sieve_execute_state_create(eenv);
+
 	eenv->exec_status = senv->exec_status;
 	if (eenv->exec_status == NULL)
 		eenv->exec_status = p_new(pool, struct sieve_exec_status, 1);
@@ -42,9 +67,96 @@ void sieve_execute_init(struct sieve_exe
 		i_zero(eenv->exec_status);
 }
 
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	if (status == SIEVE_EXEC_OK) {
+		if (senv->duplicate_transaction_commit != NULL) {
+			senv->duplicate_transaction_commit(
+				&eenv->state->dup_trans);
+		}
+	} else {
+		if (senv->duplicate_transaction_rollback != NULL) {
+			senv->duplicate_transaction_rollback(
+				&eenv->state->dup_trans);
+		}
+	}
+}
+
 void sieve_execute_deinit(struct sieve_execute_env *eenv)
 {
+	sieve_execute_state_free(&eenv->state, eenv);
 	event_unref(&eenv->event);
 	pool_unref(&eenv->pool);
 }
 
+/*
+ * Checking for duplicates
+ */
+
+static void *
+sieve_execute_get_dup_transaction(const struct sieve_execute_env *eenv)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	if (senv->duplicate_transaction_begin == NULL)
+		return NULL;
+	if (eenv->state->dup_trans == NULL) {
+		eenv->state->dup_trans =
+			senv->duplicate_transaction_begin(senv);
+	}
+	return eenv->state->dup_trans;
+}
+
+bool sieve_execute_duplicate_check_available(
+	const struct sieve_execute_env *eenv)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+
+	return (senv->duplicate_transaction_begin != NULL);
+}
+
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size,
+				  bool *duplicate_r)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+	void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+	int ret;
+
+	*duplicate_r = FALSE;
+
+	if (senv->duplicate_check == NULL)
+		return SIEVE_EXEC_OK;
+
+	e_debug(eenv->svinst->event, "Check duplicate ID");
+
+	ret = senv->duplicate_check(dup_trans, senv, id, id_size);
+	switch (ret) {
+	case SIEVE_DUPLICATE_CHECK_RESULT_EXISTS:
+		*duplicate_r = TRUE;
+		break;
+	case SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		break;
+	case SIEVE_DUPLICATE_CHECK_RESULT_FAILURE:
+		return SIEVE_EXEC_FAILURE;
+	case SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE:
+		return SIEVE_EXEC_TEMP_FAILURE;
+	}
+	return SIEVE_EXEC_OK;
+}
+
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size, time_t time)
+{
+	const struct sieve_script_env *senv = eenv->scriptenv;
+	void *dup_trans = sieve_execute_get_dup_transaction(eenv);
+
+	if (senv->duplicate_mark == NULL)
+		return;
+
+	e_debug(eenv->svinst->event, "Mark ID as duplicate");
+
+	senv->duplicate_mark(dup_trans, senv, id, id_size, time);
+}
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-execute.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-execute.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-execute.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-execute.h	2022-06-14 06:55:57.000000000 +0000
@@ -3,6 +3,8 @@
 
 #include "sieve-common.h"
 
+struct sieve_execute_state;
+
 struct sieve_execute_env {
 	struct sieve_instance *svinst;
 	pool_t pool;
@@ -13,6 +15,7 @@ struct sieve_execute_env {
 	const struct sieve_message_data *msgdata;
 	const struct sieve_script_env *scriptenv;
 
+	struct sieve_execute_state *state;
 	struct sieve_exec_status *exec_status;
 };
 
@@ -21,6 +24,19 @@ void sieve_execute_init(struct sieve_exe
 			const struct sieve_message_data *msgdata,
 			const struct sieve_script_env *senv,
 			enum sieve_execute_flags flags);
+void sieve_execute_finish(struct sieve_execute_env *eenv, int status);
 void sieve_execute_deinit(struct sieve_execute_env *eenv);
 
+/*
+ * Checking for duplicates
+ */
+
+bool sieve_execute_duplicate_check_available(
+	const struct sieve_execute_env *eenv);
+int sieve_execute_duplicate_check(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size,
+				  bool *duplicate_r);
+void sieve_execute_duplicate_mark(const struct sieve_execute_env *eenv,
+				  const void *id, size_t id_size, time_t time);
+
 #endif
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-interpreter.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-interpreter.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-interpreter.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-interpreter.c	2022-06-14 06:55:57.000000000 +0000
@@ -1003,8 +1003,8 @@ int sieve_interpreter_continue(struct si
 		}
 		e_debug(e->event(), "Finished running script `%s' "
 			"(status=%s, resource usage: %s)",
-			sieve_execution_exitcode_to_str(ret),
 			sieve_binary_source(interp->runenv.sbin),
+			sieve_execution_exitcode_to_str(ret),
 			sieve_resource_usage_get_summary(&interp->rusage));
 		interp->running = FALSE;
 	}
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-lexer.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-lexer.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-lexer.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-lexer.h	2022-06-14 06:55:57.000000000 +0000
@@ -50,7 +50,7 @@ struct sieve_lexer {
 
 	enum sieve_token_type token_type;
 	string_t *token_str_value;
-	int token_int_value;
+	sieve_number_t token_int_value;
 
 	int token_line;
 };
@@ -94,7 +94,8 @@ sieve_lexer_token_ident(const struct sie
 	return str_c(lexer->token_str_value);
 }
 
-static inline int sieve_lexer_token_int(const struct sieve_lexer *lexer)
+static inline sieve_number_t
+sieve_lexer_token_int(const struct sieve_lexer *lexer)
 {
 	i_assert(lexer->token_type == STT_NUMBER);
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-result.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-result.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-result.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-result.c	2022-06-14 06:55:57.000000000 +0000
@@ -930,10 +930,10 @@ struct sieve_result_execution {
 	struct sieve_action_execution *keep_equiv_action;
 	int keep_status;
 
-	bool dup_flushed:1;
 	bool keep_success:1;
 	bool keep_explicit:1;
 	bool keep_implicit:1;
+	bool keep_finalizing:1;
 	bool seen_delivery:1;
 	bool executed:1;
 	bool executed_delivery:1;
@@ -1075,9 +1075,6 @@ static int
 sieve_result_action_start(struct sieve_result_execution *rexec,
 			  struct sieve_action_execution *aexec)
 {
-	const struct sieve_action_exec_env *aenv = &rexec->action_env;
-	const struct sieve_execute_env *eenv = aenv->exec_env;
-	const struct sieve_script_env *senv = eenv->scriptenv;
 	struct sieve_result_action *rac = aexec->action;
 	struct sieve_action *act = &rac->action;
 	int status = SIEVE_EXEC_OK;
@@ -1092,12 +1089,6 @@ sieve_result_action_start(struct sieve_r
 	if (act->def == NULL)
 		return status;
 
-	if ((act->def->flags & SIEVE_ACTFLAG_MAIL_STORAGE) != 0 &&
-	    !rexec->dup_flushed) {
-		sieve_action_duplicate_flush(senv);
-		rexec->dup_flushed = TRUE;
-	}
-
 	if (act->def->start != NULL) {
 		sieve_action_execution_pre(rexec, aexec);
 		status = act->def->start(&rexec->action_env,
@@ -1475,8 +1466,7 @@ void sieve_result_execution_destroy(stru
 }
 
 static void
-sieve_result_implicit_keep_execute(struct sieve_result_execution *rexec,
-				   bool success)
+sieve_result_implicit_keep_execute(struct sieve_result_execution *rexec)
 {
 	const struct sieve_action_exec_env *aenv = &rexec->action_env;
 	struct sieve_result *result = aenv->result;
@@ -1486,6 +1476,33 @@ sieve_result_implicit_keep_execute(struc
 	struct sieve_action_execution *aexec_keep = &rexec->keep;
 	struct sieve_result_action *ract_keep = &rexec->keep_action;
 	struct sieve_action *act_keep = &ract_keep->action;
+	bool success = FALSE;
+
+	switch (rexec->status) {
+	case SIEVE_EXEC_OK:
+		success = TRUE;
+		break;
+	case SIEVE_EXEC_TEMP_FAILURE:
+	case SIEVE_EXEC_RESOURCE_LIMIT:
+		if (rexec->committed) {
+			e_debug(rexec->event,
+				"Temporary failure occurred (status=%s), "
+				"but other actions were already committed: "
+				"execute failure implicit keep",
+				sieve_execution_exitcode_to_str(rexec->status));
+			break;
+		}
+		if (rexec->keep_finalizing)
+			break;
+
+		e_debug(rexec->event,
+			"Skip implicit keep for temporary failure "
+			"(state=execute, status=%s)",
+			sieve_execution_exitcode_to_str(rexec->status));
+		return;
+	default:
+		break;
+	}
 
 	if (rexec->keep_equiv_action != NULL) {
 		e_debug(rexec->event, "No implicit keep needed "
@@ -1539,10 +1556,13 @@ sieve_result_implicit_keep_execute(struc
 
 	/* Scan for deferred keep */
 	aexec = rexec->actions_tail;
-	while (aexec != NULL &&
-	       aexec->state >= SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+	while (aexec != NULL) {
 		struct sieve_result_action *rac = aexec->action;
 
+		if (aexec->state < SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+			aexec = NULL;
+			break;
+		}
 		if (rac->action.keep && rac->action.def == NULL)
 			break;
 		aexec = aexec->prev;
@@ -1576,8 +1596,8 @@ sieve_result_implicit_keep_execute(struc
 		}
 	}
 
-	e_debug(rexec->event, "Execute implicit keep (failure=%s)",
-		(!success ? "yes" : "no"));
+	e_debug(rexec->event, "Execute implicit keep (status=%s)",
+		sieve_execution_exitcode_to_str(rexec->status));
 
 	/* Initialize side effects */
 	sieve_action_execution_add_side_effects(rexec, aexec_keep, ract_keep);
@@ -1600,8 +1620,7 @@ sieve_result_implicit_keep_execute(struc
 }
 
 static int
-sieve_result_implicit_keep_finalize(struct sieve_result_execution *rexec,
-				    bool success)
+sieve_result_implicit_keep_finalize(struct sieve_result_execution *rexec)
 {
 	const struct sieve_action_exec_env *aenv = &rexec->action_env;
 	const struct sieve_execute_env *eenv = aenv->exec_env;
@@ -1609,28 +1628,53 @@ sieve_result_implicit_keep_finalize(stru
 	struct sieve_result_action *ract_keep = &rexec->keep_action;
 	struct sieve_action *act_keep = &ract_keep->action;
 	int commit_status = SIEVE_EXEC_OK;
+	bool success = FALSE, temp_failure = FALSE;
 
-	if (rexec->keep_equiv_action != NULL) {
-		struct sieve_action_execution *ke_aexec =
-			rexec->keep_equiv_action;
+	switch (rexec->status) {
+	case SIEVE_EXEC_OK:
+		success = TRUE;
+		break;
+	case SIEVE_EXEC_TEMP_FAILURE:
+	case SIEVE_EXEC_RESOURCE_LIMIT:
+		if (rexec->committed) {
+			e_debug(rexec->event,
+				"Temporary failure occurred (status=%s), "
+				"but other actions were already committed: "
+				"commit failure implicit keep",
+				sieve_execution_exitcode_to_str(rexec->status));
+			break;
+		}
 
-		e_debug(rexec->event, "No implicit keep needed "
-			"(equivalent %s action already executed)",
-			sieve_action_name(&ke_aexec->action->action));
-		return ke_aexec->status;
+		if (aexec_keep->state !=
+		    SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+			e_debug(rexec->event,
+				"Skip implicit keep for temporary failure "
+				"(state=commit, status=%s)",
+				sieve_execution_exitcode_to_str(rexec->status));
+			return rexec->status;
+		}
+		/* Roll back for temporary failure when no other action
+		   is committed. */
+		commit_status = rexec->status;
+		temp_failure = TRUE;
+		break;
+	default:
+		break;
 	}
+
 	if ((eenv->flags & SIEVE_EXECUTE_FLAG_DEFER_KEEP) != 0) {
 		e_debug(rexec->event, "Execution of implicit keep is deferred");
 		return rexec->keep_status;
 	}
 
-	e_debug(rexec->event, "Finalize implicit keep (failure=%s)",
-		(!success ? "yes" : "no"));
+	rexec->keep_finalizing = TRUE;
 
 	/* Start keep if necessary */
-	if (act_keep->def == NULL ||
-	    aexec_keep->state != SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
-		sieve_result_implicit_keep_execute(rexec, success);
+	if (temp_failure) {
+		rexec->keep_status = rexec->status;
+	} else if (act_keep->def == NULL ||
+		   aexec_keep->state != SIEVE_ACTION_EXECUTION_STATE_EXECUTED) {
+		sieve_result_implicit_keep_execute(rexec);
 	/* Switch to failure keep if necessary. */
 	} else if (rexec->keep_success && !success){
 		e_debug(rexec->event, "Switch to failure implicit keep");
@@ -1642,11 +1686,29 @@ sieve_result_implicit_keep_finalize(stru
 		i_zero(aexec_keep);
 
 		/* Start failure keep action. */
-		sieve_result_implicit_keep_execute(rexec, success);
+		sieve_result_implicit_keep_execute(rexec);
 	}
 	if (act_keep->def == NULL)
 		return rexec->keep_status;
 
+	if (rexec->keep_equiv_action != NULL) {
+		struct sieve_action_execution *ke_aexec =
+			rexec->keep_equiv_action;
+
+		i_assert(ke_aexec->state >=
+			 SIEVE_ACTION_EXECUTION_STATE_FINALIZED);
+
+		e_debug(rexec->event, "No implicit keep needed "
+			"(equivalent %s action already finalized)",
+			sieve_action_name(&ke_aexec->action->action));
+		return ke_aexec->status;
+	}
+
+	e_debug(rexec->event, "Finalize implicit keep (status=%s)",
+		sieve_execution_exitcode_to_str(rexec->status));
+
+	i_assert(aexec_keep->state == SIEVE_ACTION_EXECUTION_STATE_EXECUTED);
+
 	/* Finalize keep action */
 	rexec->keep_status = sieve_result_action_commit_or_rollback(
 		rexec, aexec_keep, rexec->keep_status, &commit_status);
@@ -1685,7 +1747,6 @@ static int sieve_result_transaction_star
 
 	e_debug(rexec->event, "Starting execution of actions");
 
-	rexec->dup_flushed = FALSE;
 	aexec = rexec->actions_head;
 	while (status == SIEVE_EXEC_OK && aexec != NULL) {
 		status = sieve_result_action_start(rexec, aexec);
@@ -1728,10 +1789,11 @@ sieve_result_transaction_execute(struct
 	}
 
 	e_debug(rexec->event, "Finished executing actions "
-		"(status=%s, keep=%s)",
+		"(status=%s, keep=%s, executed=%s)",
 		sieve_execution_exitcode_to_str(status),
 		(rexec->keep_explicit ? "explicit" :
-		 (rexec->keep_implicit ? "implicit" : "none")));
+		 (rexec->keep_implicit ? "implicit" : "none")),
+		(rexec->executed ? "yes" : "no"));
 	return status;
 }
 
@@ -1790,10 +1852,11 @@ sieve_result_transaction_commit_or_rollb
 	}
 
 	e_debug(rexec->event, "Finished finalizing actions "
-		"(status=%s, keep=%s)",
+		"(status=%s, keep=%s, committed=%s)",
 		sieve_execution_exitcode_to_str(status),
 		(rexec->keep_explicit ? "explicit" :
-		 (rexec->keep_implicit ? "implicit" : "none")));
+		 (rexec->keep_implicit ? "implicit" : "none")),
+		(rexec->committed ? "yes" : "no"));
 
 	return commit_status;
 }
@@ -1920,19 +1983,11 @@ int sieve_result_execute(struct sieve_re
 		return rexec->status;
 	}
 
-	/* Execute implicit keep if necessary */
-
-	if (rexec->executed ||
-	    (rexec->status != SIEVE_EXEC_TEMP_FAILURE &&
-	     rexec->status != SIEVE_EXEC_RESOURCE_LIMIT)) {
-		/* Execute implicit keep if the transaction failed or when the
-		   implicit keep was not canceled during transaction.
-		 */
-		if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit) {
-			sieve_result_implicit_keep_execute(
-				rexec, (rexec->status == SIEVE_EXEC_OK));
-		}
-	}
+	/* Execute implicit keep if the transaction failed or when the
+	   implicit keep was not canceled during transaction.
+	 */
+	if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit)
+		sieve_result_implicit_keep_execute(rexec);
 
 	/* Transaction commit/rollback */
 
@@ -1942,33 +1997,30 @@ int sieve_result_execute(struct sieve_re
 	/* Commit implicit keep if necessary */
 
 	result_status = rexec->status;
-	if (rexec->committed ||
-	    (rexec->status != SIEVE_EXEC_TEMP_FAILURE &&
-	     rexec->status != SIEVE_EXEC_RESOURCE_LIMIT)) {
-		/* Commit implicit keep if the transaction failed or when the
-		   implicit keep was not canceled during transaction.
-		 */
-		if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit) {
-			ret = sieve_result_implicit_keep_finalize(
-				rexec, (rexec->status == SIEVE_EXEC_OK));
-			switch (ret) {
-			case SIEVE_EXEC_OK:
-				if (result_status == SIEVE_EXEC_TEMP_FAILURE)
-					result_status = SIEVE_EXEC_FAILURE;
+
+	/* Commit implicit keep if the transaction failed or when the
+	   implicit keep was not canceled during transaction.
+	 */
+	if (rexec->status != SIEVE_EXEC_OK || rexec->keep_implicit) {
+		ret = sieve_result_implicit_keep_finalize(rexec);
+		switch (ret) {
+		case SIEVE_EXEC_OK:
+			if (result_status == SIEVE_EXEC_TEMP_FAILURE)
+				result_status = SIEVE_EXEC_FAILURE;
+			break;
+		case SIEVE_EXEC_TEMP_FAILURE:
+		case SIEVE_EXEC_RESOURCE_LIMIT:
+			if (!rexec->committed) {
+				result_status = ret;
 				break;
-			case SIEVE_EXEC_TEMP_FAILURE:
-				if (!rexec->committed) {
-					result_status = ret;
-					break;
-				}
-				/* fall through */
-			default:
-				result_status = SIEVE_EXEC_KEEP_FAILED;
 			}
+			/* fall through */
+		default:
+			result_status = SIEVE_EXEC_KEEP_FAILED;
 		}
-		if (rexec->status == SIEVE_EXEC_OK)
-			rexec->status = result_status;
 	}
+	if (rexec->status == SIEVE_EXEC_OK)
+		rexec->status = result_status;
 
 	/* Finish execution */
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-result.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-result.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-result.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-result.h	2022-06-14 06:55:57.000000000 +0000
@@ -110,6 +110,9 @@ struct sieve_result_execution *
 sieve_result_execution_create(struct sieve_result *result, pool_t pool);
 void sieve_result_execution_destroy(struct sieve_result_execution **_rexec);
 
+void *sieve_result_execution_get_dup_transaction(
+	struct sieve_result_execution *rexec);
+
 int sieve_result_execute(struct sieve_result_execution *rexec, int status,
 			 bool commit, struct sieve_error_handler *ehandler,
 			 bool *keep_r);
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-settings.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-settings.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-settings.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-settings.c	2022-06-14 06:55:57.000000000 +0000
@@ -216,7 +216,9 @@ void sieve_settings_load(struct sieve_in
 					 &uint_setting))
 		svinst->max_redirects = (unsigned int)uint_setting;
 
-	svinst->max_cpu_time_secs = SIEVE_DEFAULT_MAX_CPU_TIME_SECS;
+	svinst->max_cpu_time_secs =
+		(svinst->env_location == SIEVE_ENV_LOCATION_MS ?
+		 0 : SIEVE_DEFAULT_MAX_CPU_TIME_SECS);
 	if (sieve_setting_get_duration_value(svinst, "sieve_max_cpu_time",
 					     &period)) {
 		if (period > (UINT_MAX / 1000))
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-stringlist.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-stringlist.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-stringlist.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-stringlist.c	2022-06-14 06:55:57.000000000 +0000
@@ -218,6 +218,7 @@ static int sieve_index_stringlist_next_i
 		index = strlist->index;
 	}
 
+	i_assert(index > 0);
 	while ( index > 0 ) {
 		if ( (ret=sieve_stringlist_next_item(strlist->source, str_r)) <= 0 ) {
 			if (ret < 0)
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-types.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-types.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/sieve-types.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/sieve-types.h	2022-06-14 06:55:57.000000000 +0000
@@ -189,6 +189,17 @@ struct sieve_trace_config {
 };
 
 /*
+ * Duplicate checking
+ */
+
+enum sieve_duplicate_check_result {
+	SIEVE_DUPLICATE_CHECK_RESULT_EXISTS = 1,
+	SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND = 0,
+	SIEVE_DUPLICATE_CHECK_RESULT_FAILURE = -1,
+	SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE = -2,
+};
+
+/*
  * Script environment
  *
  * - Environment for currently executing script
@@ -229,13 +240,17 @@ struct sieve_script_env {
 			const char **error_r);
 
 	/* Interface for marking and checking duplicates */
-	bool (*duplicate_check)
-		(const struct sieve_script_env *senv, const void *id, size_t id_size);
-	void (*duplicate_mark)
-		(const struct sieve_script_env *senv, const void *id, size_t id_size,
-			time_t time);
-	void (*duplicate_flush)
-		(const struct sieve_script_env *senv);
+	void *(*duplicate_transaction_begin)(
+		const struct sieve_script_env *senv);
+	void (*duplicate_transaction_commit)(void **_dup_trans);
+	void (*duplicate_transaction_rollback)(void **_dup_trans);
+
+	enum sieve_duplicate_check_result
+	(*duplicate_check)(void *dup_trans, const struct sieve_script_env *senv,
+			   const void *id, size_t id_size);
+	void (*duplicate_mark)(void *dup_trans,
+			       const struct sieve_script_env *senv,
+			       const void *id, size_t id_size, time_t time);
 
 	/* Interface for rejecting mail */
 	int (*reject_mail)(const struct sieve_script_env *senv,
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/data/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/data/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/data/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/data/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -205,6 +205,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -250,6 +252,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-script.c	2022-06-14 06:55:57.000000000 +0000
@@ -81,8 +81,11 @@ static int sieve_dict_script_open
 	path = t_strconcat
 		(DICT_SIEVE_NAME_PATH, dict_escape_string(name), NULL);
 
+	struct dict_op_settings set = {
+		.username = dstorage->username,
+	};
 	ret = dict_lookup
-		(dscript->dict, script->pool, path, &data_id, &error);
+		(dscript->dict, &set, script->pool, path, &data_id, &error);
 	if ( ret <= 0 ) {
 		if ( ret < 0 ) {
 			sieve_script_set_critical(script,
@@ -109,6 +112,8 @@ static int sieve_dict_script_get_stream
 {
 	struct sieve_dict_script *dscript =
 		(struct sieve_dict_script *)script;
+	struct sieve_dict_storage *dstorage =
+		(struct sieve_dict_storage *)script->storage;
 	const char *path, *name = script->name, *data, *error;
 	int ret;
 
@@ -118,8 +123,11 @@ static int sieve_dict_script_get_stream
 	path = t_strconcat
 		(DICT_SIEVE_DATA_PATH, dict_escape_string(dscript->data_id), NULL);
 
+	struct dict_op_settings set = {
+		.username = dstorage->username,
+	};
 	ret = dict_lookup
-		(dscript->dict, dscript->data_pool, path, &data, &error);
+		(dscript->dict, &set, dscript->data_pool, path, &data, &error);
 	if ( ret <= 0 ) {
 		if ( ret < 0 ) {
 			sieve_script_set_critical(script,
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/dict/sieve-dict-storage.c	2022-06-14 06:55:57.000000000 +0000
@@ -93,7 +93,6 @@ int sieve_dict_storage_get_dict
 
 	if ( dstorage->dict == NULL ) {
 		i_zero(&dict_set);
-		dict_set.username = dstorage->username;
 		dict_set.base_dir = svinst->base_dir;
 		ret = dict_init(dstorage->uri, &dict_set, &dstorage->dict, &error);
 		if ( ret < 0 ) {
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/file/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/file/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/file/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/file/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -212,6 +212,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -257,6 +259,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/ldap/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -261,6 +261,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -306,6 +308,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/storage/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/storage/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -209,6 +209,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -254,6 +256,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/edit-mail.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/edit-mail.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/edit-mail.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/edit-mail.c	2022-06-14 06:55:57.000000000 +0000
@@ -35,8 +35,8 @@ static struct mail_vfuncs edit_mail_vfun
 struct edit_mail_istream;
 struct istream *edit_mail_istream_create(struct edit_mail *edmail);
 
-static struct _header_index *edit_mail_header_clone
-	(struct edit_mail *edmail, struct _header *header);
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header);
 
 /*
  * Raw storage
@@ -47,10 +47,12 @@ static unsigned int edit_mail_refcount =
 
 static struct mail_user *edit_mail_raw_storage_get(struct mail_user *mail_user)
 {
-	if ( edit_mail_user == NULL ) {
-		void **sets = master_service_settings_get_others(master_service);
+	if (edit_mail_user == NULL) {
+		void **sets =
+			master_service_settings_get_others(master_service);
 
-		edit_mail_user = raw_storage_create_from_set(mail_user->set_info, sets[0]);
+		edit_mail_user = raw_storage_create_from_set(
+			mail_user->set_info, sets[0]);
 	}
 
 	edit_mail_refcount++;
@@ -62,7 +64,7 @@ static void edit_mail_raw_storage_drop(v
 {
 	i_assert(edit_mail_refcount > 0);
 
-	if ( --edit_mail_refcount != 0)
+	if (--edit_mail_refcount != 0)
 		return;
 
 	mail_user_unref(&edit_mail_user);
@@ -130,8 +132,8 @@ static inline void _header_ref(struct _h
 
 static inline void _header_unref(struct _header *header)
 {
-	i_assert( header->refcount > 0 );
-	if ( --header->refcount != 0 )
+	i_assert(header->refcount > 0);
+	if (--header->refcount != 0)
 		return;
 
 	i_free(header->name);
@@ -145,7 +147,7 @@ static inline struct _header_field *_hea
 	hfield = i_new(struct _header_field, 1);
 	hfield->refcount = 1;
 	hfield->header = header;
-	if ( header != NULL )
+	if (header != NULL)
 		_header_ref(header);
 
 	return hfield;
@@ -158,16 +160,16 @@ static inline void _header_field_ref(str
 
 static inline void _header_field_unref(struct _header_field *hfield)
 {
-	i_assert( hfield->refcount > 0 );
-	if ( --hfield->refcount != 0 )
+	i_assert(hfield->refcount > 0);
+	if (--hfield->refcount != 0)
 		return;
 
-	if ( hfield->header != NULL )
+	if (hfield->header != NULL)
 		_header_unref(hfield->header);
 
-	if ( hfield->data != NULL )
+	if (hfield->data != NULL)
 		i_free(hfield->data);
-	if ( hfield->utf8_value != NULL )
+	if (hfield->utf8_value != NULL)
 		i_free(hfield->utf8_value);
 	i_free(hfield);
 }
@@ -215,16 +217,15 @@ struct edit_mail *edit_mail_wrap(struct
 	uoff_t size_diff;
 	pool_t pool;
 
-	if ( mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0 ) {
+	if (mail_get_stream(mail, &hdr_size, &body_size, &wrapped_stream) < 0)
 		return NULL;
-	}
 
 	/* Create dummy raw mailbox for our wrapper */
 
 	raw_mail_user = edit_mail_raw_storage_get(mail->box->storage->user);
 
-	if ( raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
-		"editor@example.com", &raw_box) < 0 ) {
+	if (raw_mailbox_alloc_stream(raw_mail_user, wrapped_stream, (time_t)-1,
+				     "editor@example.com", &raw_box) < 0) {
 		i_error("edit-mail: failed to open raw box: %s",
 			mailbox_get_last_internal_error(raw_box, NULL));
 		mailbox_free(&raw_box);
@@ -248,10 +249,11 @@ struct edit_mail *edit_mail_wrap(struct
 	edmail->wrapped_stream = wrapped_stream;
 	i_stream_ref(edmail->wrapped_stream);
 
-	/* Determine whether we should use CRLF or LF for the physical message */
-	size_diff = (hdr_size.virtual_size + body_size.virtual_size) -
-		(hdr_size.physical_size + body_size.physical_size);
-	if ( size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2 )
+	/* Determine whether we should use CRLF or LF for the physical message
+	 */
+	size_diff = ((hdr_size.virtual_size + body_size.virtual_size) -
+		     (hdr_size.physical_size + body_size.physical_size));
+	if (size_diff == 0 || size_diff <= (hdr_size.lines + body_size.lines)/2)
 		edmail->crlf = edmail->eoh_crlf = TRUE;
 
 	array_create(&edmail->mail.module_contexts, pool, sizeof(void *), 5);
@@ -272,9 +274,8 @@ struct edit_mail *edit_mail_snapshot(str
 	struct edit_mail *edmail_new;
 	pool_t pool;
 
-	if ( !edmail->snapshot_modified ) {
+	if (!edmail->snapshot_modified)
 		return edmail;
-	}
 
 	pool = pool_alloconly_create("edit_mail", 1024);
 	edmail_new = p_new(pool, struct edit_mail, 1);
@@ -294,7 +295,8 @@ struct edit_mail *edit_mail_snapshot(str
 	edmail_new->crlf = edmail->crlf;
 	edmail_new->eoh_crlf = edmail->eoh_crlf;
 
-	array_create(&edmail_new->mail.module_contexts, pool, sizeof(void *), 5);
+	array_create(&edmail_new->mail.module_contexts, pool,
+		     sizeof(void *), 5);
 
 	edmail_new->mail.v = edit_mail_vfuncs;
 	edmail_new->mail.mail.seq = 1;
@@ -305,31 +307,33 @@ struct edit_mail *edit_mail_snapshot(str
 
 	edmail_new->stream = NULL;
 
-	if ( edmail->modified ) {
+	if (edmail->modified) {
 		field_idx = edmail->header_fields_head;
-		while ( field_idx != NULL ) {
+		while (field_idx != NULL) {
 			struct _header_field_index *next = field_idx->next;
 
 			field_idx_new = i_new(struct _header_field_index, 1);
 
-			field_idx_new->header =
-				edit_mail_header_clone(edmail_new, field_idx->header->header);
+			field_idx_new->header = edit_mail_header_clone(
+				edmail_new, field_idx->header->header);
 
 			field_idx_new->field = field_idx->field;
 			_header_field_ref(field_idx_new->field);
 
-			DLLIST2_APPEND
-				(&edmail_new->header_fields_head, &edmail_new->header_fields_tail,
-					field_idx_new);
+			DLLIST2_APPEND(&edmail_new->header_fields_head,
+				       &edmail_new->header_fields_tail,
+				       field_idx_new);
 
 			field_idx_new->header->count++;
-			if ( field_idx->header->first == field_idx )
+			if (field_idx->header->first == field_idx)
 				field_idx_new->header->first = field_idx_new;
-			if ( field_idx->header->last == field_idx )
+			if (field_idx->header->last == field_idx)
 				field_idx_new->header->last = field_idx_new;
 
-			if ( field_idx == edmail->header_fields_appended )
-				edmail_new->header_fields_appended = field_idx_new;
+			if (field_idx == edmail->header_fields_appended) {
+				edmail_new->header_fields_appended =
+					field_idx_new;
+			}
 
 			field_idx = next;
 		}
@@ -338,9 +342,7 @@ struct edit_mail *edit_mail_snapshot(str
 	}
 
 	edmail_new->headers_parsed = edmail->headers_parsed;
-
 	edmail_new->parent = edmail;
-	//edmail->refcount++;
 
 	return edmail_new;
 }
@@ -353,7 +355,7 @@ void edit_mail_reset(struct edit_mail *e
 	i_stream_unref(&edmail->stream);
 
 	field_idx = edmail->header_fields_head;
-	while ( field_idx != NULL ) {
+	while (field_idx != NULL) {
 		struct _header_field_index *next = field_idx->next;
 
 		_header_field_unref(field_idx->field);
@@ -363,7 +365,7 @@ void edit_mail_reset(struct edit_mail *e
 	}
 
 	header_idx = edmail->headers_head;
-	while ( header_idx != NULL ) {
+	while (header_idx != NULL) {
 		struct _header_index *next = header_idx->next;
 
 		_header_unref(header_idx->header);
@@ -379,8 +381,8 @@ void edit_mail_unwrap(struct edit_mail *
 {
 	struct edit_mail *parent;
 
-	i_assert( (*edmail)->refcount > 0 );
-	if ( --(*edmail)->refcount != 0 )
+	i_assert((*edmail)->refcount > 0);
+	if (--(*edmail)->refcount != 0)
 		return;
 
 	edit_mail_reset(*edmail);
@@ -388,7 +390,7 @@ void edit_mail_unwrap(struct edit_mail *
 
 	parent = (*edmail)->parent;
 
-	if ( parent == NULL ) {
+	if (parent == NULL) {
 		mailbox_transaction_rollback(&(*edmail)->mail.mail.transaction);
 		mailbox_free(&(*edmail)->mail.mail.box);
 		edit_mail_raw_storage_drop();
@@ -397,14 +399,14 @@ void edit_mail_unwrap(struct edit_mail *
 	pool_unref(&(*edmail)->mail.pool);
 	*edmail = NULL;
 
-	if ( parent != NULL )
+	if (parent != NULL)
 		edit_mail_unwrap(&parent);
 }
 
 struct mail *edit_mail_get_mail(struct edit_mail *edmail)
 {
 	/* Return wrapped mail when nothing is modified yet */
-	if ( !edmail->modified )
+	if (!edmail->modified)
 		return &edmail->wrapped->mail;
 
 	return &edmail->mail.mail;
@@ -423,29 +425,27 @@ static inline void edit_mail_modify(stru
 
 /* Header modification */
 
-static inline char *_header_value_unfold
-(const char *value)
+static inline char *_header_value_unfold(const char *value)
 {
 	string_t *out;
 	unsigned int i;
 
-	for ( i = 0; value[i] != '\0'; i++ ) {
+	for (i = 0; value[i] != '\0'; i++) {
 		if (value[i] == '\r' || value[i] == '\n')
 			break;
 	}
-	if ( value[i] == '\0' ) {
+	if (value[i] == '\0')
 		return i_strdup(value);
-	}
 
 	out = t_str_new(i + strlen(value+i) + 10);
 	str_append_data(out, value, i);
-	for ( ; value[i] != '\0'; i++ ) {
+	for (; value[i] != '\0'; i++) {
 		if (value[i] == '\n') {
 			i++;
 			if (value[i] == '\0')
 				break;
 
-			switch ( value[i] ) {
+			switch (value[i]) {
 			case ' ': 
 				str_append_c(out, ' ');
 				break;
@@ -462,14 +462,14 @@ static inline char *_header_value_unfold
 	return i_strndup(str_c(out), str_len(out));
 }
 
-static struct _header_index *edit_mail_header_find
-(struct edit_mail *edmail, const char *field_name)
+static struct _header_index *
+edit_mail_header_find(struct edit_mail *edmail, const char *field_name)
 {
 	struct _header_index *header_idx;
 
 	header_idx = edmail->headers_head;
-	while ( header_idx != NULL ) {
-		if ( strcasecmp(header_idx->header->name, field_name) == 0 )
+	while (header_idx != NULL) {
+		if (strcasecmp(header_idx->header->name, field_name) == 0)
 			return header_idx;
 
 		header_idx = header_idx->next;
@@ -478,29 +478,31 @@ static struct _header_index *edit_mail_h
 	return NULL;
 }
 
-static struct _header_index *edit_mail_header_create
-(struct edit_mail *edmail, const char *field_name)
+static struct _header_index *
+edit_mail_header_create(struct edit_mail *edmail, const char *field_name)
 {
 	struct _header_index *header_idx;
 
-	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+	header_idx = edit_mail_header_find(edmail, field_name);
+	if (header_idx == NULL) {
 		header_idx = i_new(struct _header_index, 1);
 		header_idx->header = _header_create(field_name);
 
-		DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail, header_idx);
+		DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+			       header_idx);
 	}
 
 	return header_idx;
 }
 
-static struct _header_index *edit_mail_header_clone
-(struct edit_mail *edmail, struct _header *header)
+static struct _header_index *
+edit_mail_header_clone(struct edit_mail *edmail, struct _header *header)
 {
 	struct _header_index *header_idx;
 
 	header_idx = edmail->headers_head;
-	while ( header_idx != NULL ) {
-		if ( header_idx->header == header )
+	while (header_idx != NULL) {
+		if (header_idx->header == header)
 			return header_idx;
 
 		header_idx = header_idx->next;
@@ -509,14 +511,15 @@ static struct _header_index *edit_mail_h
 	header_idx = i_new(struct _header_index, 1);
 	header_idx->header = header;
 	_header_ref(header);
-	DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail, header_idx);
+	DLLIST2_APPEND(&edmail->headers_head, &edmail->headers_tail,
+		       header_idx);
 
 	return header_idx;
 }
 
 static struct _header_field_index *
-edit_mail_header_field_create
-(struct edit_mail *edmail, const char *field_name, const char *value)
+edit_mail_header_field_create(struct edit_mail *edmail, const char *field_name,
+			      const char *value)
 {
 	struct _header_index *header_idx;
 	struct _header *header;
@@ -542,13 +545,15 @@ edit_mail_header_field_create
 
 		message_header_encode(value, enc_value);
 
-		lines = rfc2822_header_append
-			(data, field_name, str_c(enc_value), edmail->crlf, &field->body_offset);
+		lines = rfc2822_header_append(data, field_name,
+					      str_c(enc_value), edmail->crlf,
+					      &field->body_offset);
 
 		/* Copy to new field */
 		field->data = i_strndup(str_data(data), str_len(data));
 		field->size = str_len(data);
-		field->virtual_size = ( edmail->crlf ? field->size : field->size + lines );
+		field->virtual_size = (edmail->crlf ?
+				       field->size : field->size + lines);
 		field->lines = lines;
 	} T_END;
 
@@ -558,70 +563,73 @@ edit_mail_header_field_create
 	return field_idx;
 }
 
-static void edit_mail_header_field_delete
-(struct edit_mail *edmail, struct _header_field_index *field_idx,
-	bool update_index)
+static void
+edit_mail_header_field_delete(struct edit_mail *edmail,
+			      struct _header_field_index *field_idx,
+			      bool update_index)
 {
 	struct _header_index *header_idx = field_idx->header;
 	struct _header_field *field = field_idx->field;
 
-	i_assert( header_idx != NULL );
+	i_assert(header_idx != NULL);
 
 	edmail->hdr_size.physical_size -= field->size;
 	edmail->hdr_size.virtual_size -= field->virtual_size;
 	edmail->hdr_size.lines -= field->lines;
 
 	header_idx->count--;
-	if ( update_index ) {
-		if ( header_idx->count == 0 ) {
-			DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+	if (update_index) {
+		if (header_idx->count == 0) {
+			DLLIST2_REMOVE(&edmail->headers_head,
+				       &edmail->headers_tail, header_idx);
 			_header_unref(header_idx->header);
 			i_free(header_idx);
-		} else if ( header_idx->first == field_idx ) {
-			struct _header_field_index *hfield = header_idx->first->next;
+		} else if (header_idx->first == field_idx) {
+			struct _header_field_index *hfield =
+				header_idx->first->next;
 
-			while ( hfield != NULL && hfield->header != header_idx ) {
+			while (hfield != NULL && hfield->header != header_idx)
 				hfield = hfield->next;
-			}
 
-			i_assert( hfield != NULL );
+			i_assert(hfield != NULL);
 			header_idx->first = hfield;
-		} else if ( header_idx->last == field_idx ) {
-			struct _header_field_index *hfield = header_idx->last->prev;
+		} else if (header_idx->last == field_idx) {
+			struct _header_field_index *hfield =
+				header_idx->last->prev;
 
-			while ( hfield != NULL && hfield->header != header_idx ) {
+			while (hfield != NULL && hfield->header != header_idx)
 				hfield = hfield->prev;
-			}
 
-			i_assert( hfield != NULL );
+			i_assert(hfield != NULL);
 			header_idx->last = hfield;
 		}
 	}
 
-	DLLIST2_REMOVE
-		(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+	DLLIST2_REMOVE(&edmail->header_fields_head, &edmail->header_fields_tail,
+		       field_idx);
 	_header_field_unref(field_idx->field);
 	i_free(field_idx);
 }
 
 static struct _header_field_index *
-edit_mail_header_field_replace
-(struct edit_mail *edmail, struct _header_field_index *field_idx,
-	const char *newname, const char *newvalue, bool update_index)
+edit_mail_header_field_replace(struct edit_mail *edmail,
+			       struct _header_field_index *field_idx,
+			       const char *newname, const char *newvalue,
+			       bool update_index)
 {
 	struct _header_field_index *field_idx_new;
 	struct _header_index *header_idx = field_idx->header, *header_idx_new;
 	struct _header_field *field = field_idx->field, *field_new;
 
-	i_assert( header_idx != NULL );
-	i_assert( newname != NULL || newvalue != NULL );
+	i_assert(header_idx != NULL);
+	i_assert(newname != NULL || newvalue != NULL);
 
-	if ( newname == NULL )
+	if (newname == NULL)
 		newname = header_idx->header->name;
-	if ( newvalue == NULL )
+	if (newvalue == NULL)
 		newvalue = field_idx->field->utf8_value;
-	field_idx_new = edit_mail_header_field_create
-		(edmail, newname, newvalue);
+	field_idx_new = edit_mail_header_field_create(
+		edmail, newname, newvalue);
 	field_new = field_idx_new->field;
 	header_idx_new = field_idx_new->header;
 
@@ -636,16 +644,16 @@ edit_mail_header_field_replace
 	/* Replace header field index */
 	field_idx_new->prev = field_idx->prev;
 	field_idx_new->next = field_idx->next;
-	if ( field_idx->prev != NULL )
+	if (field_idx->prev != NULL)
 		field_idx->prev->next = field_idx_new;
-	if ( field_idx->next != NULL )
+	if (field_idx->next != NULL)
 		field_idx->next->prev = field_idx_new;
 	if (edmail->header_fields_head == field_idx)
 		edmail->header_fields_head = field_idx_new;
 	if (edmail->header_fields_tail == field_idx)
 		edmail->header_fields_tail = field_idx_new;
 
-	if ( header_idx_new == header_idx ) {
+	if (header_idx_new == header_idx) {
 		if (header_idx->first == field_idx)
 			header_idx->first = field_idx_new;
 		if (header_idx->last == field_idx)
@@ -654,47 +662,51 @@ edit_mail_header_field_replace
 		header_idx->count--;
 		header_idx_new->count++;
 
-		if ( update_index ) {
-			if ( header_idx->count == 0 ) {
-				DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+		if (update_index) {
+			if (header_idx->count == 0) {
+				DLLIST2_REMOVE(&edmail->headers_head,
+					       &edmail->headers_tail,
+					       header_idx);
 				_header_unref(header_idx->header);
 				i_free(header_idx);
-			} else if ( header_idx->first == field_idx ) {
-				struct _header_field_index *hfield = header_idx->first->next;
+			} else if (header_idx->first == field_idx) {
+				struct _header_field_index *hfield =
+					header_idx->first->next;
 
-				while ( hfield != NULL && hfield->header != header_idx ) {
+				while (hfield != NULL &&
+				       hfield->header != header_idx)
 					hfield = hfield->next;
-				}
 
-				i_assert( hfield != NULL );
+				i_assert(hfield != NULL);
 				header_idx->first = hfield;
-			} else if ( header_idx->last == field_idx ) {
-				struct _header_field_index *hfield = header_idx->last->prev;
+			} else if (header_idx->last == field_idx) {
+				struct _header_field_index *hfield =
+					header_idx->last->prev;
 
-				while ( hfield != NULL && hfield->header != header_idx ) {
+				while (hfield != NULL &&
+				       hfield->header != header_idx)
 					hfield = hfield->prev;
-				}
 
-				i_assert( hfield != NULL );
+				i_assert(hfield != NULL);
 				header_idx->last = hfield;
 			}
-			if ( header_idx_new->count > 0 ) {
+			if (header_idx_new->count > 0) {
 				struct _header_field_index *hfield;
 
 				hfield = edmail->header_fields_head;
-				while ( hfield != NULL && hfield->header != header_idx_new ) {
+				while (hfield != NULL &&
+				       hfield->header != header_idx_new)
 					hfield = hfield->next;
-				}
 
-				i_assert( hfield != NULL );
+				i_assert(hfield != NULL);
 				header_idx_new->first = hfield;
 
 				hfield = edmail->header_fields_tail;
-				while ( hfield != NULL && hfield->header != header_idx_new ) {
+				while (hfield != NULL &&
+				       hfield->header != header_idx_new)
 					hfield = hfield->prev;
-				}
 
-				i_assert( hfield != NULL );
+				i_assert(hfield != NULL);
 				header_idx_new->last = hfield;
 			}
 		}
@@ -705,21 +717,20 @@ edit_mail_header_field_replace
 	return field_idx_new;
 }
 
-static inline char *_header_decode
-(const unsigned char *hdr_data, size_t hdr_data_len)
+static inline char *
+_header_decode(const unsigned char *hdr_data, size_t hdr_data_len)
 {
 	string_t *str = t_str_new(512);
 
 	/* hdr_data is already unfolded */
 
 	/* Decode MIME encoded-words. */
-	message_header_decode_utf8
-		((const unsigned char *)hdr_data, hdr_data_len, str, NULL);
+	message_header_decode_utf8((const unsigned char *)hdr_data,
+				   hdr_data_len, str, NULL);
 	return i_strdup(str_c(str));
 }
 
-static int edit_mail_headers_parse
-(struct edit_mail *edmail)
+static int edit_mail_headers_parse(struct edit_mail *edmail)
 {
 	struct message_header_parser_ctx *hparser;
 	enum message_header_parser_flags hparser_flags =
@@ -733,50 +744,57 @@ static int edit_mail_headers_parse
 	unsigned int lines = 0;
 	int ret;
 
-	if ( edmail->headers_parsed ) return 1;
+	if (edmail->headers_parsed)
+		return 1;
 
 	i_stream_seek(edmail->wrapped_stream, 0);
-	hparser = message_parse_header_init
-		(edmail->wrapped_stream, NULL, hparser_flags);
+	hparser = message_parse_header_init(edmail->wrapped_stream, NULL,
+					    hparser_flags);
 
 	T_BEGIN {
 		hdr_data = t_str_new(1024);
-		while ( (ret=message_parse_header_next(hparser, &hdr)) > 0 ) {
+		while ((ret = message_parse_header_next(hparser, &hdr)) > 0) {
 			struct _header_field_index *field_idx_new;
 			struct _header_field *field;
 
-			if ( hdr->eoh ) {
+			if (hdr->eoh) {
 				/* Record whether header ends in CRLF or LF */
 				edmail->eoh_crlf = hdr->crlf_newline;
 			}
 
-			if ( hdr == NULL || hdr->eoh ) break;
+			if (hdr == NULL || hdr->eoh)
+				break;
 
-			/* We deny the existence of any `Content-Length:' header. This header is
-			 * non-standard and it can wreak havok when the message is modified.
+			/* We deny the existence of any `Content-Length:'
+			   header. This header is non-standard and it can wreak
+			   havok when the message is modified.
 			 */
-			if ( strcasecmp(hdr->name, "Content-Length" ) == 0 )
+			if (strcasecmp(hdr->name, "Content-Length" ) == 0)
 				continue;
 
-			if ( hdr->continued ) {
+			if (hdr->continued) {
 				/* Continued line of folded header */
-				buffer_append(hdr_data, hdr->value, hdr->value_len);
+				buffer_append(hdr_data, hdr->value,
+					      hdr->value_len);
 			} else {
 				/* First line of header */
 				offset = hdr->name_offset;
 				body_offset = hdr->name_len + hdr->middle_len;
 				str_truncate(hdr_data, 0);
-				buffer_append(hdr_data, hdr->name, hdr->name_len);
-				buffer_append(hdr_data, hdr->middle, hdr->middle_len);
-				buffer_append(hdr_data, hdr->value, hdr->value_len);
+				buffer_append(hdr_data, hdr->name,
+					      hdr->name_len);
+				buffer_append(hdr_data, hdr->middle,
+					      hdr->middle_len);
+				buffer_append(hdr_data, hdr->value,
+					      hdr->value_len);
 				lines = 0;
 				vsize_diff = 0;
 			}
 
-			if ( !hdr->no_newline ) {
+			if (!hdr->no_newline) {
 				lines++;
 
-				if ( hdr->crlf_newline ) {
+				if (hdr->crlf_newline) {
 					buffer_append(hdr_data, "\r\n", 2);
 				} else {
 					buffer_append(hdr_data, "\n", 1);
@@ -784,7 +802,7 @@ static int edit_mail_headers_parse
 				}
 			}
 
-			if ( hdr->continues ) {
+			if (hdr->continues) {
 				hdr->use_full_value = TRUE;
 				continue;
 			}
@@ -796,16 +814,19 @@ static int edit_mail_headers_parse
 			header_idx = edit_mail_header_create(edmail, hdr->name);
 			header_idx->count++;
 			field_idx_new->header = header_idx;
-			field_idx_new->field = field = _header_field_create(header_idx->header);
+			field_idx_new->field = field =
+				_header_field_create(header_idx->header);
 
-			i_assert( body_offset > 0 );
+			i_assert(body_offset > 0);
 			field->body_offset = body_offset;
 
-			field->utf8_value = _header_decode(hdr->full_value, hdr->full_value_len);
+			field->utf8_value = _header_decode(hdr->full_value,
+							   hdr->full_value_len);
 
 			field->size = str_len(hdr_data);
 			field->virtual_size = field->size + vsize_diff;
-			field->data = i_strndup(str_data(hdr_data), field->size);
+			field->data = i_strndup(str_data(hdr_data),
+						field->size);
 			field->offset = offset;
 			field->lines = lines;
 
@@ -819,16 +840,16 @@ static int edit_mail_headers_parse
 
 	message_parse_header_deinit(&hparser);
 
-	/* blocking i/o required */
-	i_assert( ret != 0 );
+	/* Blocking i/o required */
+	i_assert(ret != 0);
 
-	if ( ret < 0 && edmail->wrapped_stream->stream_errno != 0 ) {
+	if (ret < 0 && edmail->wrapped_stream->stream_errno != 0) {
 		/* Error; clean up */
 		i_error("read(%s) failed: %s",
 			i_stream_get_name(edmail->wrapped_stream),
 			i_stream_get_error(edmail->wrapped_stream));
 		current = head;
-		while ( current != NULL ) {
+		while (current != NULL) {
 			struct _header_field_index *next = current->next;
 
 			_header_field_unref(current->field);
@@ -841,9 +862,10 @@ static int edit_mail_headers_parse
 	}
 
 	/* Insert header field index items in main list */
-	if ( head != NULL && tail != NULL ) {
-		if ( edmail->header_fields_appended != NULL ) {
-			if ( edmail->header_fields_head != edmail->header_fields_appended ) {
+	if (head != NULL && tail != NULL) {
+		if (edmail->header_fields_appended != NULL) {
+			if (edmail->header_fields_head !=
+			    edmail->header_fields_appended) {
 				edmail->header_fields_appended->prev->next = head;
 				head->prev = edmail->header_fields_appended->prev;
 			} else {
@@ -852,7 +874,7 @@ static int edit_mail_headers_parse
 
 			tail->next = edmail->header_fields_appended;
 			edmail->header_fields_appended->prev = tail;
-		} else if ( edmail->header_fields_tail != NULL ) {
+		} else if (edmail->header_fields_tail != NULL) {
 			edmail->header_fields_tail->next = head;
 			head->prev = edmail->header_fields_tail;
 			edmail->header_fields_tail = tail;
@@ -864,8 +886,8 @@ static int edit_mail_headers_parse
 
 	/* Rebuild header index */
 	current = edmail->header_fields_head;
-	while ( current != NULL ) {
-		if ( current->header->first == NULL )
+	while (current != NULL) {
+		if (current->header->first == NULL)
 			current->header->first = current;
 		current->header->last = current;
 
@@ -884,9 +906,8 @@ static int edit_mail_headers_parse
 	return 1;
 }
 
-void edit_mail_header_add
-(struct edit_mail *edmail, const char *field_name, const char *value,
-	bool last)
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+			  const char *value, bool last)
 {
 	struct _header_index *header_idx;
 	struct _header_field_index *field_idx;
@@ -899,16 +920,16 @@ void edit_mail_header_add
 	field = field_idx->field;
 
 	/* Add it to the header field index */
-	if ( last ) {
-		DLLIST2_APPEND
-			(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+	if (last) {
+		DLLIST2_APPEND(&edmail->header_fields_head,
+			       &edmail->header_fields_tail, field_idx);
 
 		header_idx->last = field_idx;
-		if ( header_idx->first == NULL )
+		if (header_idx->first == NULL)
 			header_idx->first = field_idx;
 
-		if ( !edmail->headers_parsed )  {
-			if ( edmail->header_fields_appended == NULL ) {
+		if (!edmail->headers_parsed)  {
+			if (edmail->header_fields_appended == NULL) {
 				/* Record beginning of appended headers */
 				edmail->header_fields_appended = field_idx;
 			}
@@ -918,11 +939,11 @@ void edit_mail_header_add
 			edmail->appended_hdr_size.lines += field->lines;
 		}
 	} else {
-		DLLIST2_PREPEND
-			(&edmail->header_fields_head, &edmail->header_fields_tail, field_idx);
+		DLLIST2_PREPEND(&edmail->header_fields_head,
+				&edmail->header_fields_tail, field_idx);
 
 		header_idx->first = field_idx;
-		if ( header_idx->last == NULL )
+		if (header_idx->last == NULL)
 			header_idx->last = field_idx;
 	}
 
@@ -933,8 +954,8 @@ void edit_mail_header_add
 	edmail->hdr_size.lines += field->lines;
 }
 
-int edit_mail_header_delete
-(struct edit_mail *edmail, const char *field_name, int index)
+int edit_mail_header_delete(struct edit_mail *edmail, const char *field_name,
+			    int index)
 {
 	struct _header_index *header_idx;
 	struct _header_field_index *field_idx;
@@ -942,11 +963,12 @@ int edit_mail_header_delete
 	int ret = 0;
 
 	/* Make sure headers are parsed */
-	if ( edit_mail_headers_parse(edmail) <= 0 )
+	if (edit_mail_headers_parse(edmail) <= 0)
 		return -1;
 
 	/* Find the header entry */
-	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+	header_idx = edit_mail_header_find(edmail, field_name);
+	if (header_idx == NULL) {
 		/* Not found */
 		return 0;
 	}
@@ -955,46 +977,51 @@ int edit_mail_header_delete
 	edit_mail_modify(edmail);
 
 	/* Iterate through all header fields and remove those that match */
-	field_idx = ( index >= 0 ? header_idx->first : header_idx->last );
-	while ( field_idx != NULL ) {
+	field_idx = (index >= 0 ? header_idx->first : header_idx->last);
+	while (field_idx != NULL) {
 		struct _header_field_index *next =
-			( index >= 0 ? field_idx->next : field_idx->prev );
+			(index >= 0 ? field_idx->next : field_idx->prev);
 
-		if ( field_idx->field->header == header_idx->header ) {
+		if (field_idx->field->header == header_idx->header) {
 			bool final;
 
-			if ( index >= 0 ) {
+			if (index >= 0) {
 				pos++;
-				final = ( header_idx->last == field_idx );
+				final = (header_idx->last == field_idx);
 			} else {
 				pos--;
-				final = ( header_idx->first == field_idx );
+				final = (header_idx->first == field_idx);
 			}
 
-			if ( index == 0 || index == pos ) {
-				if ( header_idx->first == field_idx ) header_idx->first = NULL;
-				if ( header_idx->last == field_idx ) header_idx->last = NULL;
-				edit_mail_header_field_delete(edmail, field_idx, FALSE);
+			if (index == 0 || index == pos) {
+				if (header_idx->first == field_idx)
+					header_idx->first = NULL;
+				if (header_idx->last == field_idx)
+					header_idx->last = NULL;
+				edit_mail_header_field_delete(
+					edmail, field_idx, FALSE);
 				ret++;
 			}
 
-			if ( final || (index != 0 && index == pos) )
+			if (final || (index != 0 && index == pos))
 				break;
 		}
 
 		field_idx = next;
 	}
 
-	if ( index == 0 || header_idx->count == 0 ) {
-		DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+	if (index == 0 || header_idx->count == 0) {
+		DLLIST2_REMOVE(&edmail->headers_head,
+			       &edmail->headers_tail, header_idx);
 		_header_unref(header_idx->header);
 		i_free(header_idx);
-	} else if ( header_idx->first == NULL || header_idx->last == NULL ) {
-		struct _header_field_index *current = edmail->header_fields_head;
-
-		while ( current != NULL ) {
-			if ( current->header == header_idx ) {
-				if ( header_idx->first == NULL )
+	} else if (header_idx->first == NULL || header_idx->last == NULL) {
+		struct _header_field_index *current =
+			edmail->header_fields_head;
+
+		while (current != NULL) {
+			if (current->header == header_idx) {
+				if (header_idx->first == NULL)
 					header_idx->first = current;
 				header_idx->last = current;
 			}
@@ -1005,9 +1032,9 @@ int edit_mail_header_delete
 	return ret;
 }
 
-int edit_mail_header_replace
-(struct edit_mail *edmail, const char *field_name, int index,
-	const char *newname, const char *newvalue)
+int edit_mail_header_replace(struct edit_mail *edmail,
+			     const char *field_name, int index,
+			     const char *newname, const char *newvalue)
 {
 	struct _header_index *header_idx, *header_idx_new;
 	struct _header_field_index *field_idx, *field_idx_new;
@@ -1015,11 +1042,12 @@ int edit_mail_header_replace
 	int ret = 0;
 
 	/* Make sure headers are parsed */
-	if ( edit_mail_headers_parse(edmail) <= 0 )
+	if (edit_mail_headers_parse(edmail) <= 0)
 		return -1;
 
 	/* Find the header entry */
-	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ) {
+	header_idx = edit_mail_header_find(edmail, field_name);
+	if (header_idx == NULL) {
 		/* Not found */
 		return 0;
 	}
@@ -1028,32 +1056,35 @@ int edit_mail_header_replace
 	edit_mail_modify(edmail);
 
 	/* Iterate through all header fields and replace those that match */
-	field_idx = ( index >= 0 ? header_idx->first : header_idx->last );
+	field_idx = (index >= 0 ? header_idx->first : header_idx->last);
 	field_idx_new = NULL;
-	while ( field_idx != NULL ) {
+	while (field_idx != NULL) {
 		struct _header_field_index *next =
-			( index >= 0 ? field_idx->next : field_idx->prev );
+			(index >= 0 ? field_idx->next : field_idx->prev);
 
-		if ( field_idx->field->header == header_idx->header ) {
+		if (field_idx->field->header == header_idx->header) {
 			bool final;
 
-			if ( index >= 0 ) {
+			if (index >= 0) {
 				pos++;
-				final = ( header_idx->last == field_idx );
+				final = (header_idx->last == field_idx);
 			} else {
 				pos--;
-				final = ( header_idx->first == field_idx );
+				final = (header_idx->first == field_idx);
 			}
 
-			if ( index == 0 || index == pos ) {
-				if ( header_idx->first == field_idx ) header_idx->first = NULL;
-				if ( header_idx->last == field_idx ) header_idx->last = NULL;
-				field_idx_new = edit_mail_header_field_replace
-					(edmail, field_idx, newname, newvalue, FALSE);
+			if (index == 0 || index == pos) {
+				if (header_idx->first == field_idx)
+					header_idx->first = NULL;
+				if (header_idx->last == field_idx)
+					header_idx->last = NULL;
+				field_idx_new = edit_mail_header_field_replace(
+					edmail, field_idx, newname, newvalue,
+					FALSE);
 				ret++;
 			}
 
-			if ( final || (index != 0 && index == pos) )
+			if (final || (index != 0 && index == pos))
 				break;
 		}
 
@@ -1061,16 +1092,18 @@ int edit_mail_header_replace
 	}
 
 	/* Update old header index */
-	if ( header_idx->count == 0 ) {
-		DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail, header_idx);
+	if (header_idx->count == 0) {
+		DLLIST2_REMOVE(&edmail->headers_head, &edmail->headers_tail,
+			       header_idx);
 		_header_unref(header_idx->header);
 		i_free(header_idx);
-	} else if ( header_idx->first == NULL || header_idx->last == NULL ) {
-		struct _header_field_index *current = edmail->header_fields_head;
-
-		while ( current != NULL ) {
-			if ( current->header == header_idx ) {
-				if ( header_idx->first == NULL )
+	} else if (header_idx->first == NULL || header_idx->last == NULL) {
+		struct _header_field_index *current =
+			edmail->header_fields_head;
+
+		while (current != NULL) {
+			if (current->header == header_idx) {
+				if (header_idx->first == NULL)
 					header_idx->first = current;
 				header_idx->last = current;
 			}
@@ -1078,14 +1111,15 @@ int edit_mail_header_replace
 		}
 	}
 
-	/* Update new header index */	
-	if ( field_idx_new != NULL ) {
-		struct _header_field_index *current = edmail->header_fields_head;
-	
-		header_idx_new = field_idx_new->header;	
-		while ( current != NULL ) {
-			if ( current->header == header_idx_new ) {
-				if ( header_idx_new->first == NULL )
+	/* Update new header index */
+	if (field_idx_new != NULL) {
+		struct _header_field_index *current =
+			edmail->header_fields_head;
+
+		header_idx_new = field_idx_new->header;
+		while (current != NULL) {
+			if (current->header == header_idx_new) {
+				if (header_idx_new->first == NULL)
 					header_idx_new->first = current;
 				header_idx_new->last = current;
 			}
@@ -1105,36 +1139,36 @@ struct edit_mail_header_iter
 	bool reverse:1;
 };
 
-int edit_mail_headers_iterate_init
-(struct edit_mail *edmail, const char *field_name, bool reverse,
-	struct edit_mail_header_iter **edhiter_r)
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+				   const char *field_name, bool reverse,
+				   struct edit_mail_header_iter **edhiter_r)
 {
 	struct edit_mail_header_iter *edhiter;
 	struct _header_index *header_idx = NULL;
 	struct _header_field_index *current = NULL;
 
 	/* Make sure headers are parsed */
-	if ( edit_mail_headers_parse(edmail) <= 0 ) {
+	if (edit_mail_headers_parse(edmail) <= 0) {
 		/* Failure */
 		return -1;
 	}
 
 	header_idx = edit_mail_header_find(edmail, field_name);
 
-	if ( field_name != NULL && header_idx == NULL ) {
+	if (field_name != NULL && header_idx == NULL) {
 		current = NULL;
-	} else if ( !reverse ) {
-		current =
-			( header_idx != NULL ? header_idx->first : edmail->header_fields_head );
+	} else if (!reverse) {
+		current = (header_idx != NULL ?
+			   header_idx->first : edmail->header_fields_head);
 	} else {
-		current =
-			( header_idx != NULL ? header_idx->last : edmail->header_fields_tail );
-		if ( current->header == NULL )
+		current = (header_idx != NULL ?
+			   header_idx->last : edmail->header_fields_tail);
+		if (current->header == NULL)
 			current = current->prev;
 	}
 
-	if ( current ==  NULL )
-		return 0; 
+	if (current ==  NULL)
+		return 0;
 
  	edhiter = i_new(struct edit_mail_header_iter, 1);
 	edhiter->mail = edmail;
@@ -1146,51 +1180,51 @@ int edit_mail_headers_iterate_init
 	return 1;
 }
 
-void edit_mail_headers_iterate_deinit
-(struct edit_mail_header_iter **edhiter)
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter)
 {
 	i_free(*edhiter);
 	*edhiter = NULL;
 }
 
-void edit_mail_headers_iterate_get
-(struct edit_mail_header_iter *edhiter, const char **value_r)
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+				   const char **value_r)
 {
 	const char *raw;
 	int i;
 
-	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+	i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
 
 	raw = edhiter->current->field->utf8_value;
-	for ( i = strlen(raw)-1; i >= 0; i-- ) {
-		if ( raw[i] != ' ' && raw[i] != '\t' ) break;
+	for (i = strlen(raw)-1; i >= 0; i--) {
+		if (raw[i] != ' ' && raw[i] != '\t')
+			break;
 	}
 
 	*value_r = t_strndup(raw, i+1);
 }
 
-bool edit_mail_headers_iterate_next
-(struct edit_mail_header_iter *edhiter)
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter)
 {
-	if ( edhiter->current == NULL )
+	if (edhiter->current == NULL)
 		return FALSE;
 
 	do {
-		edhiter->current = 
-			( !edhiter->reverse ? edhiter->current->next : edhiter->current->prev );
-	} while ( edhiter->current != NULL && edhiter->current->header != NULL &&
-		edhiter->header != NULL && edhiter->current->header != edhiter->header );
+		edhiter->current = (!edhiter->reverse ?
+				    edhiter->current->next :
+				    edhiter->current->prev );
+	} while (edhiter->current != NULL && edhiter->current->header != NULL &&
+		 edhiter->header != NULL &&
+		 edhiter->current->header != edhiter->header);
 
-	return ( edhiter->current != NULL && edhiter->current->header != NULL);
+	return (edhiter->current != NULL && edhiter->current->header != NULL);
 }
 
-bool edit_mail_headers_iterate_remove
-(struct edit_mail_header_iter *edhiter)
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter)
 {
 	struct _header_field_index *field_idx;
 	bool next;
 
-	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+	i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
 
 	edit_mail_modify(edhiter->mail);
 
@@ -1201,21 +1235,21 @@ bool edit_mail_headers_iterate_remove
 	return next;
 }
 
-bool edit_mail_headers_iterate_replace
-(struct edit_mail_header_iter *edhiter,
-	const char *newname, const char *newvalue)
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+				       const char *newname,
+				       const char *newvalue)
 {
 	struct _header_field_index *field_idx;
 	bool next;
 
-	i_assert( edhiter->current != NULL && edhiter->current->header != NULL);
+	i_assert(edhiter->current != NULL && edhiter->current->header != NULL);
 
 	edit_mail_modify(edhiter->mail);
 
 	field_idx = edhiter->current;
 	next = edit_mail_headers_iterate_next(edhiter);
-	edit_mail_header_field_replace
-		(edhiter->mail, field_idx, newname, newvalue, TRUE);
+	edit_mail_header_field_replace(edhiter->mail, field_idx,
+				       newname, newvalue, TRUE);
 
 	return next;
 }
@@ -1244,15 +1278,15 @@ static void edit_mail_free(struct mail *
 	edit_mail_unwrap(&edmail);
 }
 
-static void edit_mail_set_seq
-(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
-	bool saving ATTR_UNUSED)
+static void
+edit_mail_set_seq(struct mail *mail ATTR_UNUSED, uint32_t seq ATTR_UNUSED,
+		  bool saving ATTR_UNUSED)
 {
 	i_panic("edit_mail_set_seq() not implemented");
 }
 
-static bool ATTR_NORETURN edit_mail_set_uid
-(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
+static bool ATTR_NORETURN
+edit_mail_set_uid(struct mail *mail ATTR_UNUSED, uint32_t uid ATTR_UNUSED)
 {
 	i_panic("edit_mail_set_uid() not implemented");
 }
@@ -1264,8 +1298,9 @@ static void edit_mail_set_uid_cache_upda
 	edmail->wrapped->v.set_uid_cache_updates(&edmail->wrapped->mail, set);
 }
 
-static void edit_mail_add_temp_wanted_fields
-(struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
+static void
+edit_mail_add_temp_wanted_fields(
+	struct mail *mail ATTR_UNUSED, enum mail_fetch_field fields ATTR_UNUSED,
 	struct mailbox_header_lookup_ctx *headers ATTR_UNUSED)
 {
   /* Nothing */
@@ -1285,8 +1320,8 @@ static const char *const *edit_mail_get_
 	return edmail->wrapped->v.get_keywords(&edmail->wrapped->mail);
 }
 
-static const ARRAY_TYPE(keyword_indexes) *edit_mail_get_keyword_indexes
-(struct mail *mail)
+static const ARRAY_TYPE(keyword_indexes) *
+edit_mail_get_keyword_indexes(struct mail *mail)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
@@ -1307,27 +1342,28 @@ static uint64_t edit_mail_get_pvt_modseq
 	return edmail->wrapped->v.get_pvt_modseq(&edmail->wrapped->mail);
 }
 
-static int edit_mail_get_parts
-(struct mail *mail, struct message_part **parts_r)
+static int edit_mail_get_parts(struct mail *mail, struct message_part **parts_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
 	return edmail->wrapped->v.get_parts(&edmail->wrapped->mail, parts_r);
 }
 
-static int edit_mail_get_date
-(struct mail *mail, time_t *date_r, int *timezone_r)
+static int
+edit_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	return edmail->wrapped->v.get_date(&edmail->wrapped->mail, date_r, timezone_r);
+	return edmail->wrapped->v.get_date(&edmail->wrapped->mail,
+					   date_r, timezone_r);
 }
 
 static int edit_mail_get_received_date(struct mail *mail, time_t *date_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail, date_r);
+	return edmail->wrapped->v.get_received_date(&edmail->wrapped->mail,
+						    date_r);
 }
 
 static int edit_mail_get_save_date(struct mail *mail, time_t *date_r)
@@ -1341,17 +1377,18 @@ static int edit_mail_get_virtual_size(st
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	if ( !edmail->headers_parsed ) {
-		*size_r = edmail->wrapped_hdr_size.virtual_size +
-			edmail->wrapped_body_size.virtual_size;
+	if (!edmail->headers_parsed) {
+		*size_r = (edmail->wrapped_hdr_size.virtual_size +
+			   edmail->wrapped_body_size.virtual_size);
 
-		if ( !edmail->modified )
+		if (!edmail->modified)
 			return 0;
 	} else {
 		*size_r = edmail->wrapped_body_size.virtual_size + 2;
 	}
 
-	*size_r += edmail->hdr_size.virtual_size + edmail->body_size.virtual_size;
+	*size_r += (edmail->hdr_size.virtual_size +
+		    edmail->body_size.virtual_size);
 	return 0;
 }
 
@@ -1360,24 +1397,25 @@ static int edit_mail_get_physical_size(s
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
 	*size_r = 0;
-	if ( !edmail->headers_parsed ) {
-		*size_r = edmail->wrapped_hdr_size.physical_size +
-			edmail->wrapped_body_size.physical_size;
+	if (!edmail->headers_parsed) {
+		*size_r = (edmail->wrapped_hdr_size.physical_size +
+			   edmail->wrapped_body_size.physical_size);
 
-		if ( !edmail->modified )
+		if (!edmail->modified)
 			return 0;
 	} else {
-		*size_r = edmail->wrapped_body_size.physical_size +
-			( edmail->eoh_crlf ? 2 : 1 );
+		*size_r = (edmail->wrapped_body_size.physical_size +
+			   (edmail->eoh_crlf ? 2 : 1));
 	}
 
-	*size_r += edmail->hdr_size.physical_size + edmail->body_size.physical_size;
+	*size_r += (edmail->hdr_size.physical_size +
+		    edmail->body_size.physical_size);
 	return 0;
 }
 
-static int edit_mail_get_first_header
-(struct mail *mail, const char *field_name, bool decode_to_utf8,
-	const char **value_r)
+static int
+edit_mail_get_first_header(struct mail *mail, const char *field_name,
+			   bool decode_to_utf8, const char **value_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 	struct _header_index *header_idx;
@@ -1385,20 +1423,21 @@ static int edit_mail_get_first_header
 	int ret;
 
 	/* Check whether mail headers were modified at all */
-	if ( !edmail->modified || edmail->headers_head == NULL ) {
+	if (!edmail->modified || edmail->headers_head == NULL) {
 		/* Unmodified */
-		return edmail->wrapped->v.get_first_header
-			(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+		return edmail->wrapped->v.get_first_header(
+			&edmail->wrapped->mail, field_name, decode_to_utf8,
+			value_r);
 	}
 
 	/* Try to find modified header */
-	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ||
-		header_idx->count == 0 ) {
-
-		if ( !edmail->headers_parsed ) {
+	header_idx = edit_mail_header_find(edmail, field_name);
+	if (header_idx == NULL || header_idx->count == 0 ) {
+		if (!edmail->headers_parsed) {
 			/* No new header */
-			return edmail->wrapped->v.get_first_header
-				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+			return edmail->wrapped->v.get_first_header(
+				&edmail->wrapped->mail, field_name,
+				decode_to_utf8, value_r);
 		}
 
 		*value_r = NULL;
@@ -1406,7 +1445,7 @@ static int edit_mail_get_first_header
 	}
 
 	/* Get the first occurrence */
-	if ( edmail->header_fields_appended == NULL ) {
+	if (edmail->header_fields_appended == NULL) {
 		/* There are no appended headers, so first is found directly */
 		field = header_idx->first->field;
 	} else {
@@ -1414,21 +1453,23 @@ static int edit_mail_get_first_header
 
 		/* Scan prepended headers */
 		field_idx = edmail->header_fields_head;
-		while ( field_idx != NULL ) {
-			if ( field_idx->header == header_idx )
+		while (field_idx != NULL) {
+			if (field_idx->header == header_idx)
 				break;
 
-			if ( field_idx == edmail->header_fields_appended ) {
+			if (field_idx == edmail->header_fields_appended) {
 				field_idx = NULL;
 				break;
 			}
 			field_idx = field_idx->next;
 		}
 
-		if ( field_idx == NULL ) {
+		if (field_idx == NULL) {
 			/* Check original message */
-			if ( (ret=edmail->wrapped->v.get_first_header
-				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r)) != 0 )
+			ret = edmail->wrapped->v.get_first_header(
+				&edmail->wrapped->mail, field_name,
+				decode_to_utf8, value_r);
+			if (ret != 0)
 				return ret;
 
 			/* Use first (apparently appended) header */
@@ -1438,16 +1479,16 @@ static int edit_mail_get_first_header
 		}
 	}
 
-	if ( decode_to_utf8 )
+	if (decode_to_utf8)
 		*value_r = field->utf8_value;
 	else
-		*value_r = (const char *) (field->data + field->body_offset);
+		*value_r = (const char *)(field->data + field->body_offset);
 	return 1;
 }
 
-static int edit_mail_get_headers
-(struct mail *mail, const char *field_name, bool decode_to_utf8,
-	const char *const **value_r)
+static int
+edit_mail_get_headers(struct mail *mail, const char *field_name,
+		      bool decode_to_utf8, const char *const **value_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 	struct _header_index *header_idx;
@@ -1455,18 +1496,20 @@ static int edit_mail_get_headers
 	const char *const *headers;
 	ARRAY(const char *) header_values;
 
-	if ( !edmail->modified || edmail->headers_head == NULL ) {
+	if (!edmail->modified || edmail->headers_head == NULL) {
 		/* Unmodified */
-		return edmail->wrapped->v.get_headers
-			(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+		return edmail->wrapped->v.get_headers(
+			&edmail->wrapped->mail, field_name, decode_to_utf8,
+			value_r);
 	}
 
-	if ( (header_idx=edit_mail_header_find(edmail, field_name)) == NULL ||
-		header_idx->count == 0 ) {
-		if ( !edmail->headers_parsed ) {
+	header_idx = edit_mail_header_find(edmail, field_name);
+	if (header_idx == NULL || header_idx->count == 0 ) {
+		if (!edmail->headers_parsed) {
 			/* No new header */
-			return edmail->wrapped->v.get_headers
-				(&edmail->wrapped->mail, field_name, decode_to_utf8, value_r);
+			return edmail->wrapped->v.get_headers(
+				&edmail->wrapped->mail, field_name,
+				decode_to_utf8, value_r);
 		}
 
 		p_array_init(&header_values, edmail->mail.pool, 1);
@@ -1479,40 +1522,41 @@ static int edit_mail_get_headers
 
 	/* Read original headers too if message headers are not parsed */
 	headers = NULL;
-	if ( !edmail->headers_parsed && edmail->wrapped->v.get_headers
-			(&edmail->wrapped->mail, field_name, decode_to_utf8, &headers) < 0 ) {
+	if (!edmail->headers_parsed &&
+	    edmail->wrapped->v.get_headers(&edmail->wrapped->mail, field_name,
+					   decode_to_utf8, &headers) < 0)
 		return -1;
-	}
 
 	/* Fill result array */
 	p_array_init(&header_values, edmail->mail.pool, 32);
 	field_idx = header_idx->first;
-	while ( field_idx != NULL ) {
-
-		/* If current field is the first appended one, we need to add original
-		 * headers first.
+	while (field_idx != NULL) {
+		/* If current field is the first appended one, we need to add
+		   original headers first.
 		 */
-		if ( field_idx == edmail->header_fields_appended && headers != NULL ) {
-			while ( *headers != NULL ) {
+		if (field_idx == edmail->header_fields_appended &&
+		    headers != NULL) {
+			while (*headers != NULL) {
 				array_append(&header_values, headers, 1);
-
 				headers++;
 			}
 		}
 
 		/* Add modified header to the list */
-		if ( field_idx->field->header == header_idx->header ) {
+		if (field_idx->field->header == header_idx->header) {
 			struct _header_field *field = field_idx->field;
 
 			const char *value;
-			if ( decode_to_utf8 )
+			if (decode_to_utf8)
 				value = field->utf8_value;
-			else
-				value = (const char *)(field->data + field->body_offset);
+			else {
+				value = (const char *)(field->data +
+						       field->body_offset);
+			}
 
 			array_append(&header_values, &value, 1);
 
-			if ( field_idx == header_idx->last )
+			if (field_idx == header_idx->last)
 				break;
 		}
 
@@ -1520,10 +1564,9 @@ static int edit_mail_get_headers
 	}
 
 	/* Add original headers if necessary */
-	if ( headers != NULL ) {
-		while ( *headers != NULL ) {
+	if (headers != NULL) {
+		while (*headers != NULL) {
 			array_append(&header_values, headers, 1);
-
 			headers++;
 		}
 	}
@@ -1533,8 +1576,9 @@ static int edit_mail_get_headers
 	return 1;
 }
 
-static int ATTR_NORETURN edit_mail_get_header_stream
-(struct mail *mail ATTR_UNUSED,
+static int ATTR_NORETURN
+edit_mail_get_header_stream(
+	struct mail *mail ATTR_UNUSED,
 	struct mailbox_header_lookup_ctx *headers ATTR_UNUSED,
 	struct istream **stream_r ATTR_UNUSED)
 {
@@ -1542,26 +1586,25 @@ static int ATTR_NORETURN edit_mail_get_h
 	i_panic("edit_mail_get_header_stream() not implemented");
 }
 
-static int edit_mail_get_stream
-(struct mail *mail, bool get_body ATTR_UNUSED, struct message_size *hdr_size,
-	struct message_size *body_size, struct istream **stream_r)
+static int
+edit_mail_get_stream(struct mail *mail, bool get_body ATTR_UNUSED,
+		     struct message_size *hdr_size,
+		     struct message_size *body_size, struct istream **stream_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	if ( edmail->stream == NULL ) {
+	if (edmail->stream == NULL)
 		edmail->stream = edit_mail_istream_create(edmail);
-	}
 
-	if ( hdr_size != NULL ) {
+	if (hdr_size != NULL) {
 		*hdr_size = edmail->wrapped_hdr_size;
 		hdr_size->physical_size += edmail->hdr_size.physical_size;
 		hdr_size->virtual_size += edmail->hdr_size.virtual_size;
 		hdr_size->lines += edmail->hdr_size.lines;
 	}
 
-	if ( body_size != NULL ) {
+	if (body_size != NULL)
 		*body_size = edmail->wrapped_body_size;
-	}
 
 	*stream_r = edmail->stream;
 	i_stream_seek(edmail->stream, 0);
@@ -1569,12 +1612,13 @@ static int edit_mail_get_stream
 	return 0;
 }
 
-static int edit_mail_get_special
-(struct mail *mail, enum mail_fetch_field field, const char **value_r)
+static int
+edit_mail_get_special(struct mail *mail, enum mail_fetch_field field,
+		      const char **value_r)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	if ( edmail->modified ) {
+	if (edmail->modified) {
 		/* Block certain fields when modified */
 
 		switch (field) {
@@ -1591,7 +1635,8 @@ static int edit_mail_get_special
 		}
 	}
 
-	return edmail->wrapped->v.get_special(&edmail->wrapped->mail, field, value_r);
+	return edmail->wrapped->v.get_special(&edmail->wrapped->mail,
+					      field, value_r);
 }
 
 static int
@@ -1603,22 +1648,24 @@ edit_mail_get_backend_mail(struct mail *
 	return 0;
 }
 
-static void edit_mail_update_flags
-(struct mail *mail, enum modify_type modify_type, enum mail_flags flags)
+static void
+edit_mail_update_flags(struct mail *mail, enum modify_type modify_type,
+		       enum mail_flags flags)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	edmail->wrapped->v.update_flags(&edmail->wrapped->mail, modify_type, flags);
+	edmail->wrapped->v.update_flags(&edmail->wrapped->mail,
+					modify_type, flags);
 }
 
-static void edit_mail_update_keywords
-(struct mail *mail, enum modify_type modify_type,
-	struct mail_keywords *keywords)
+static void
+edit_mail_update_keywords(struct mail *mail, enum modify_type modify_type,
+			  struct mail_keywords *keywords)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	edmail->wrapped->v.update_keywords
-		(&edmail->wrapped->mail, modify_type, keywords);
+	edmail->wrapped->v.update_keywords(&edmail->wrapped->mail,
+					   modify_type, keywords);
 }
 
 static void edit_mail_update_modseq(struct mail *mail, uint64_t min_modseq)
@@ -1628,19 +1675,23 @@ static void edit_mail_update_modseq(stru
 	edmail->wrapped->v.update_modseq(&edmail->wrapped->mail, min_modseq);
 }
 
-static void edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
+static void
+edit_mail_update_pvt_modseq(struct mail *mail, uint64_t min_pvt_modseq)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail, min_pvt_modseq);
+	edmail->wrapped->v.update_pvt_modseq(&edmail->wrapped->mail,
+					     min_pvt_modseq);
 }
 
 static void edit_mail_update_pop3_uidl(struct mail *mail, const char *uidl)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	if ( edmail->wrapped->v.update_pop3_uidl != NULL )
-		edmail->wrapped->v.update_pop3_uidl(&edmail->wrapped->mail, uidl);
+	if (edmail->wrapped->v.update_pop3_uidl != NULL) {
+		edmail->wrapped->v.update_pop3_uidl(
+			&edmail->wrapped->mail, uidl);
+	}
 }
 
 static void edit_mail_expunge(struct mail *mail ATTR_UNUSED)
@@ -1648,14 +1699,14 @@ static void edit_mail_expunge(struct mai
 	/* NOOP */
 }
 
-static void edit_mail_set_cache_corrupted
-(struct mail *mail, enum mail_fetch_field field,
-	const char *reason)
+static void
+edit_mail_set_cache_corrupted(struct mail *mail, enum mail_fetch_field field,
+			      const char *reason)
 {
 	struct edit_mail *edmail = (struct edit_mail *)mail;
 
-	edmail->wrapped->v.set_cache_corrupted
-		(&edmail->wrapped->mail, field, reason);
+	edmail->wrapped->v.set_cache_corrupted(&edmail->wrapped->mail,
+					       field, reason);
 }
 
 static struct mail_vfuncs edit_mail_vfuncs = {
@@ -1723,9 +1774,9 @@ static void edit_mail_istream_destroy(st
 	pool_unref(&edstream->pool);
 }
 
-static ssize_t merge_from_parent
-(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
-	uoff_t parent_end_v_offset, uoff_t copy_v_offset)
+static ssize_t
+merge_from_parent(struct edit_mail_istream *edstream, uoff_t parent_v_offset,
+		  uoff_t parent_end_v_offset, uoff_t copy_v_offset)
 {
 	struct istream_private *stream = &edstream->istream;
 	uoff_t v_offset, append_v_offset;
@@ -1767,29 +1818,30 @@ static ssize_t merge_from_parent
 	if (pos > cur_pos)
 		ret = 0;
 	else do {
-		/* Use normal read here, since parent data can be returned directly
-		   to caller. */
+		/* Use normal read here, since parent data can be returned
+		   directly to caller. */
 		ret = i_stream_read(stream->parent);
 
 		stream->istream.stream_errno = stream->parent->stream_errno;
 		stream->istream.eof = stream->parent->eof;
 		edstream->eof = stream->parent->eof;
 		data = i_stream_get_data(stream->parent, &pos);
-		/* check again, in case the parent stream had been seeked
+		/* Check again, in case the parent stream had been seeked
 		   backwards and the previous read() didn't get us far
 		   enough. */
 	} while (pos <= cur_pos && ret > 0);
 
 	/* Don't read beyond parent end offset */
 	if (parent_end_v_offset != (uoff_t)-1) {
-		parent_bytes_left = (size_t)(parent_end_v_offset - parent_v_offset);
+		parent_bytes_left = (size_t)(parent_end_v_offset -
+					     parent_v_offset);
 		if (pos >= parent_bytes_left) {
 			pos = parent_bytes_left;
 		}
 	}
 
 	if (v_offset < copy_v_offset || ret == -2 ||
-		(parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
+	    (parent_buffer && (append_v_offset + 1) >= parent_end_v_offset)) {
 		/* Merging with our local buffer; copying data from parent */
 		if (pos > 0) {
 			size_t avail;
@@ -1815,8 +1867,8 @@ static ssize_t merge_from_parent
 		}
 	} else {
 		/* Just passing buffers from parent; no copying */
-		ret = (pos > cur_pos ? (ssize_t)(pos - cur_pos) :
-			(ret == 0 ? 0 : -1));
+		ret = (pos > cur_pos ?
+		       (ssize_t)(pos - cur_pos) : (ret == 0 ? 0 : -1));
 		stream->buffer = data;
 		stream->pos = pos;
 		stream->skip = 0;
@@ -1846,14 +1898,15 @@ static ssize_t merge_modified_headers(st
 
 	/* Add modified headers to buffer */
 	written = 0;
-	while ( edstream->cur_header != NULL) {
+	while (edstream->cur_header != NULL) {
 		size_t wsize;
 
 		/* Determine what part of the header was already buffered */
 		append_v_offset = v_offset + (stream->pos - stream->skip);
 		i_assert(append_v_offset >= edstream->cur_header_v_offset);
 		if (append_v_offset >= edstream->cur_header_v_offset)
-			appended = (size_t)(append_v_offset - edstream->cur_header_v_offset);
+			appended = (size_t)(append_v_offset -
+					    edstream->cur_header_v_offset);
 		else
 			appended = 0;
 		i_assert(appended <= edstream->cur_header->field->size);
@@ -1862,13 +1915,17 @@ static ssize_t merge_modified_headers(st
 		size = edstream->cur_header->field->size - appended;
 		if (size > 0) {
 			/* Determine how much we can write */
-			if (!i_stream_try_alloc(stream, size, &avail))
-				return -2;
+			if (!i_stream_try_alloc(stream, size, &avail)) {
+				if (written == 0)
+					return -2;
+				break;
+			}
 			wsize = (size >= avail ? avail : size);
 
 			/* Write (part of) the header to buffer */
 			memcpy(stream->w_buffer + stream->pos,
-				edstream->cur_header->field->data + appended, wsize);
+			       edstream->cur_header->field->data + appended,
+			       wsize);
 			stream->pos += wsize;
 			stream->buffer = stream->w_buffer;
 			written += wsize;
@@ -1884,9 +1941,10 @@ static ssize_t merge_modified_headers(st
 			edstream->cur_header->field->size;
 		edstream->cur_header = edstream->cur_header->next;
 
-		/* Stop at end of prepended headers if original header is left unparsed */
-		if ( !edmail->headers_parsed
-			&& edstream->cur_header == edmail->header_fields_appended )
+		/* Stop at end of prepended headers if original header is left
+		   unparsed */
+		if (!edmail->headers_parsed &&
+		    edstream->cur_header == edmail->header_fields_appended)
 			edstream->cur_header = NULL;
 	}
 
@@ -1922,53 +1980,58 @@ static ssize_t edit_mail_istream_read(st
 
 	/* Merge prepended headers */
 	if (!edstream->parent_buffer) {
-		if ( (ret=merge_modified_headers(edstream)) != 0 )
+		ret = merge_modified_headers(edstream);
+		if (ret != 0)
 			return ret;
 	}
 	v_offset = stream->istream.v_offset;
 	append_v_offset = v_offset + (stream->pos - stream->skip);
 
-	if ( !edmail->headers_parsed &&
-		edmail->header_fields_appended != NULL &&
-		!edstream->header_read) {
+	if (!edmail->headers_parsed && edmail->header_fields_appended != NULL &&
+	    !edstream->header_read) {
 		/* Output headers from original stream */
 
 		/* Size of the prepended header */
 		i_assert(edmail->hdr_size.physical_size >=
-			edmail->appended_hdr_size.physical_size);
-		prep_hdr_size = edmail->hdr_size.physical_size -
-			edmail->appended_hdr_size.physical_size;
+			 edmail->appended_hdr_size.physical_size);
+		prep_hdr_size = (edmail->hdr_size.physical_size -
+				 edmail->appended_hdr_size.physical_size);
 
-		/* Offset of header end or appended header
-		 * Any final CR is dealt with later
+		/* Calculate offset of header end or appended header. Any final
+		   CR is dealt with later.
 		 */
-		hdr_size = prep_hdr_size + edmail->wrapped_hdr_size.physical_size;
+		hdr_size = (prep_hdr_size +
+			    edmail->wrapped_hdr_size.physical_size);
 		i_assert(hdr_size > 0);
-		if ( append_v_offset <= hdr_size - 1 &&
-			edmail->wrapped_hdr_size.physical_size > 0) {
-
+		if (append_v_offset <= (hdr_size - 1) &&
+		    edmail->wrapped_hdr_size.physical_size > 0) {
 			parent_v_offset = stream->parent_start_offset;
-			parent_end_v_offset = stream->parent_start_offset +
-				edmail->wrapped_hdr_size.physical_size - 1;
+			parent_end_v_offset =
+				(stream->parent_start_offset +
+				 edmail->wrapped_hdr_size.physical_size - 1);
 			copy_v_offset = prep_hdr_size;
 
-			if ( (ret=merge_from_parent(edstream, parent_v_offset,
-				parent_end_v_offset, copy_v_offset)) < 0 )
+			ret = merge_from_parent(edstream, parent_v_offset,
+						parent_end_v_offset,
+						copy_v_offset);
+			if (ret < 0)
 				return ret;
-			append_v_offset = v_offset + (stream->pos - stream->skip);
+			append_v_offset = (v_offset +
+					   (stream->pos - stream->skip));
 			i_assert(append_v_offset <= hdr_size - 1);
 
-			if ( append_v_offset == hdr_size - 1 ) {
+			if (append_v_offset == hdr_size - 1) {
 				/* Strip final CR too when it is present */
-				if ( stream->buffer != NULL &&
-					stream->buffer[stream->pos-1] == '\r' ) {
+				if (stream->buffer != NULL &&
+				    stream->buffer[stream->pos-1] == '\r') {
 					stream->pos--;
 					append_v_offset--;
 					ret--;
 				}
 
 				i_assert(ret >= 0);
-				edstream->cur_header = edmail->header_fields_appended;
+				edstream->cur_header =
+					edmail->header_fields_appended;
 				edstream->cur_header_v_offset = append_v_offset;
 				if (!edstream->parent_buffer)
 					edstream->header_read = TRUE;
@@ -1981,34 +2044,35 @@ static ssize_t edit_mail_istream_read(st
 		}
 
 		/* Merge appended headers */
-		if ( (ret=merge_modified_headers(edstream)) != 0 )
+		ret = merge_modified_headers(edstream);
+		if (ret != 0)
 			return ret;
 	}
 
 	/* Header does not come from original mail at all */
-	if ( edmail->headers_parsed ) {
-		parent_v_offset = stream->parent_start_offset +
-			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
+	if (edmail->headers_parsed) {
+		parent_v_offset = (stream->parent_start_offset +
+				   edmail->wrapped_hdr_size.physical_size -
+				   (edmail->eoh_crlf ? 2 : 1));
 		copy_v_offset = edmail->hdr_size.physical_size;
-
-	/* Header comes partially from original mail and headers are added between
-	   header and body.
-	 */
+	/* Header comes partially from original mail and headers are added
+	   between header and body. */
 	} else if (edmail->header_fields_appended != NULL) {
-		parent_v_offset = stream->parent_start_offset +
-			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
-		copy_v_offset = edmail->hdr_size.physical_size +
-			edmail->wrapped_hdr_size.physical_size - ( edmail->eoh_crlf ? 2 : 1);
-
-	/* Header comes partially from original mail, but headers are only prepended.
-	 */
+		parent_v_offset = (stream->parent_start_offset +
+				   edmail->wrapped_hdr_size.physical_size -
+				   (edmail->eoh_crlf ? 2 : 1));
+		copy_v_offset = (edmail->hdr_size.physical_size +
+				 edmail->wrapped_hdr_size.physical_size -
+				 (edmail->eoh_crlf ? 2 : 1));
+	/* Header comes partially from original mail, but headers are only
+	   prepended. */
 	} else {
 		parent_v_offset = stream->parent_start_offset;
 		copy_v_offset = edmail->hdr_size.physical_size;
 	}
 
-	return merge_from_parent(edstream,
-		parent_v_offset, (uoff_t)-1, copy_v_offset);
+	return merge_from_parent(edstream, parent_v_offset, (uoff_t)-1,
+				 copy_v_offset);
 }
 
 static void
@@ -2023,8 +2087,9 @@ stream_reset_to(struct edit_mail_istream
 	i_stream_seek(edstream->istream.parent, 0);
 }
 
-static void edit_mail_istream_seek
-(struct istream_private *stream, uoff_t v_offset, bool mark ATTR_UNUSED)
+static void
+edit_mail_istream_seek(struct istream_private *stream, uoff_t v_offset,
+		       bool mark ATTR_UNUSED)
 {
 	struct edit_mail_istream *edstream =
 		(struct edit_mail_istream *)stream;
@@ -2037,35 +2102,36 @@ static void edit_mail_istream_seek
 	edstream->cur_header_v_offset = 0;
 
 	/* The beginning */
-	if ( v_offset == 0 ) {
+	if (v_offset == 0) {
 		stream_reset_to(edstream, 0);
 
-		if ( edmail->header_fields_head != edmail->header_fields_appended )
+		if (edmail->header_fields_head !=
+		    edmail->header_fields_appended)
 			edstream->cur_header = edmail->header_fields_head;
 		return;
 	}
 
 	/* Inside (prepended) headers */
-	if ( edmail->headers_parsed ) {
+	if (edmail->headers_parsed) {
 		offset = edmail->hdr_size.physical_size;
 	} else {
-		offset = edmail->hdr_size.physical_size -
-			edmail->appended_hdr_size.physical_size;
+		offset = (edmail->hdr_size.physical_size -
+			  edmail->appended_hdr_size.physical_size);
 	}
 
-	if ( v_offset < offset ) {
+	if (v_offset < offset) {
 		stream_reset_to(edstream, v_offset);
 
 		/* Find the header */
 		cur_header = edmail->header_fields_head;
-		i_assert( cur_header != NULL &&
-			cur_header != edmail->header_fields_appended );
+		i_assert(cur_header != NULL &&
+			 cur_header != edmail->header_fields_appended);
 		edstream->cur_header_v_offset = 0;
 		offset = cur_header->field->size;
-		while ( v_offset > offset ) {
+		while (v_offset > offset) {
 			cur_header = cur_header->next;
-			i_assert( cur_header != NULL &&
-				cur_header != edmail->header_fields_appended );
+			i_assert(cur_header != NULL &&
+				 cur_header != edmail->header_fields_appended);
 
 			edstream->cur_header_v_offset = offset;
 			offset += cur_header->field->size;
@@ -2075,12 +2141,12 @@ static void edit_mail_istream_seek
 		return;
 	}
 
-	if ( !edmail->headers_parsed ) {
+	if (!edmail->headers_parsed) {
 		/* Inside original header */
-		offset = edmail->hdr_size.physical_size -
-			edmail->appended_hdr_size.physical_size +
-			edmail->wrapped_hdr_size.physical_size;
-		if ( v_offset < offset ) {
+		offset = (edmail->hdr_size.physical_size -
+			  edmail->appended_hdr_size.physical_size +
+			  edmail->wrapped_hdr_size.physical_size);
+		if (v_offset < offset) {
 			stream_reset_to(edstream, v_offset);
 			return;
 		}
@@ -2088,21 +2154,21 @@ static void edit_mail_istream_seek
 		edstream->header_read = TRUE;
 
 		/* Inside appended header */
-		offset = edmail->hdr_size.physical_size +
-			edmail->wrapped_hdr_size.physical_size;
-		if ( v_offset < offset ) {
+		offset = (edmail->hdr_size.physical_size +
+			  edmail->wrapped_hdr_size.physical_size);
+		if (v_offset < offset) {
 			stream_reset_to(edstream, v_offset);
 
 			offset -= edmail->appended_hdr_size.physical_size;
 
 			cur_header = edmail->header_fields_appended;
-			i_assert( cur_header != NULL );
+			i_assert(cur_header != NULL);
 			edstream->cur_header_v_offset = offset;
 			offset += cur_header->field->size;
 
-			while ( v_offset > offset ) {
+			while (v_offset > offset) {
 				cur_header = cur_header->next;
-				i_assert( cur_header != NULL );
+				i_assert(cur_header != NULL);
 
 				edstream->cur_header_v_offset = offset;
 				offset += cur_header->field->size;
@@ -2139,31 +2205,32 @@ edit_mail_istream_stat(struct istream_pr
 	if (st->st_size == -1 || !exact)
 		return 0;
 
-	if ( !edmail->headers_parsed ) {
-		if ( !edmail->modified )
+	if (!edmail->headers_parsed) {
+		if (!edmail->modified)
 			return 0;
 	} else {
-		stream->statbuf.st_size = edmail->wrapped_body_size.physical_size +
-			( edmail->eoh_crlf ? 2 : 1 );
+		stream->statbuf.st_size =
+			(edmail->wrapped_body_size.physical_size +
+			 (edmail->eoh_crlf ? 2 : 1));
 	}
 
-	stream->statbuf.st_size += edmail->hdr_size.physical_size +
-		edmail->body_size.physical_size;
+	stream->statbuf.st_size += (edmail->hdr_size.physical_size +
+				    edmail->body_size.physical_size);
 	return 0;
 }
 
-struct istream *edit_mail_istream_create
-(struct edit_mail *edmail)
+struct istream *edit_mail_istream_create(struct edit_mail *edmail)
 {
 	struct edit_mail_istream *edstream;
 	struct istream *wrapped = edmail->wrapped_stream;
 
 	edstream = i_new(struct edit_mail_istream, 1);
 	edstream->pool = pool_alloconly_create(MEMPOOL_GROWING
-					      "edit mail stream", 4096);
+					       "edit mail stream", 4096);
 	edstream->mail = edmail;
 
-	edstream->istream.max_buffer_size = wrapped->real_stream->max_buffer_size;
+	edstream->istream.max_buffer_size =
+		wrapped->real_stream->max_buffer_size;
 
 	edstream->istream.iostream.destroy = edit_mail_istream_destroy;
 	edstream->istream.read = edit_mail_istream_read;
@@ -2175,11 +2242,10 @@ struct istream *edit_mail_istream_create
 	edstream->istream.istream.blocking = wrapped->blocking;
 	edstream->istream.istream.seekable = wrapped->seekable;
 
-	if ( edmail->header_fields_head != edmail->header_fields_appended )
+	if (edmail->header_fields_head != edmail->header_fields_appended)
 		edstream->cur_header = edmail->header_fields_head;
 
 	i_stream_seek(wrapped, 0);
 
 	return i_stream_create(&edstream->istream, wrapped, -1, 0);
 }
-
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/edit-mail.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/edit-mail.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/edit-mail.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/edit-mail.h	2022-06-14 06:55:57.000000000 +0000
@@ -17,37 +17,31 @@ struct mail *edit_mail_get_mail(struct e
 
 /* Simple API */
 
-void edit_mail_header_add
-	(struct edit_mail *edmail, const char *field_name, const char *value,
-		bool last);
-int edit_mail_header_delete
-	(struct edit_mail *edmail, const char *field_name, int index);
-int edit_mail_header_replace
-	(struct edit_mail *edmail, const char *field_name, int index,
-		const char *newname, const char *newvalue);
+void edit_mail_header_add(struct edit_mail *edmail, const char *field_name,
+			  const char *value, bool last);
+int edit_mail_header_delete(struct edit_mail *edmail,
+			    const char *field_name, int index);
+int edit_mail_header_replace(struct edit_mail *edmail,
+			     const char *field_name, int index,
+			     const char *newname, const char *newvalue);
 
 /* Iterator */
 
 struct edit_mail_header_iter;
 
-int edit_mail_headers_iterate_init
-	(struct edit_mail *edmail, const char *field_name, bool reverse,
-		struct edit_mail_header_iter **edhiter_r);
-void edit_mail_headers_iterate_deinit
-	(struct edit_mail_header_iter **edhiter);
-
-void edit_mail_headers_iterate_get
-	(struct edit_mail_header_iter *edhiter, const char **value_r);
-
-bool edit_mail_headers_iterate_next
-	(struct edit_mail_header_iter *edhiter);
-
-bool edit_mail_headers_iterate_remove
-	(struct edit_mail_header_iter *edhiter);
-bool edit_mail_headers_iterate_replace
-	(struct edit_mail_header_iter *edhiter,
-		const char *newname, const char *newvalue);
-
-
+int edit_mail_headers_iterate_init(struct edit_mail *edmail,
+				   const char *field_name, bool reverse,
+				   struct edit_mail_header_iter **edhiter_r);
+void edit_mail_headers_iterate_deinit(struct edit_mail_header_iter **edhiter);
+
+void edit_mail_headers_iterate_get(struct edit_mail_header_iter *edhiter,
+				   const char **value_r);
+
+bool edit_mail_headers_iterate_next(struct edit_mail_header_iter *edhiter);
+
+bool edit_mail_headers_iterate_remove(struct edit_mail_header_iter *edhiter);
+bool edit_mail_headers_iterate_replace(struct edit_mail_header_iter *edhiter,
+				       const char *newname,
+				       const char *newvalue);
 
 #endif
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -245,6 +245,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -290,6 +292,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/test-edit-mail.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/test-edit-mail.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve/util/test-edit-mail.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve/util/test-edit-mail.c	2022-06-14 06:55:57.000000000 +0000
@@ -63,10 +63,11 @@ static int test_init_mail_user(void)
 		.debug = TRUE,
 	};
 
-	mail_storage_service = mail_storage_service_init(master_service, NULL,
-		MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS |
-		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
-		MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS);
+	mail_storage_service = mail_storage_service_init(
+		master_service, NULL,
+		(MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS |
+		 MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
+		 MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS));
 
 	if (mail_storage_service_lookup(mail_storage_service, &input,
 					&test_service_user, &error) < 0)
@@ -186,7 +187,7 @@ static void test_edit_mail_concatenated(
 	test_begin("edit-mail - concatenated");
 	test_init();
 
-	/* compose the message */
+	/* Compose the message */
 
 	inputs[0] = i_stream_create_from_data(msg_part1, strlen(msg_part1));
 	inputs[1] = i_stream_create_from_data(msg_part2, strlen(msg_part2));
@@ -203,7 +204,7 @@ static void test_edit_mail_concatenated(
 
 	rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
 
-	/* add headers */
+	/* Add headers */
 
 	edmail = edit_mail_wrap(rawmail->mail);
 
@@ -212,22 +213,22 @@ static void test_edit_mail_concatenated(
 
 	mail = edit_mail_get_mail(edmail);
 
-	/* evaluate modified header */
+	/* Evaluate modified header */
 
 	test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
 	test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
 
 	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
-					  &value) > 0);
+					       &value) > 0);
 	test_assert(strcmp(value, "NO") == 0);
 	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
-					  &value) > 0);
+					       &value) > 0);
 	test_assert(strcmp(value, "NONE") == 0);
 
 	test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
-					  &value) > 0);
+					       &value) > 0);
 
-	/* prepare tests */
+	/* Prepare tests */
 
 	if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
 		i_fatal("Failed to open mail stream: %s",
@@ -237,7 +238,7 @@ static void test_edit_mail_concatenated(
 	buffer = buffer_create_dynamic(default_pool, 1024);
 	expected = t_str_new(1024);
 
-	/* added */
+	/* Added */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
@@ -252,10 +253,9 @@ static void test_edit_mail_concatenated(
 	str_append(expected, msg_part3);
 	str_append(expected, msg_part4);
 
-	test_out("added",
-		 strcmp(str_c(buffer), str_c(expected)) == 0);
+	test_out("added", strcmp(str_c(buffer), str_c(expected)) == 0);
 
-	/* added, slow */
+	/* Added, slow */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
@@ -269,18 +269,17 @@ static void test_edit_mail_concatenated(
 	str_append(expected, msg_part3);
 	str_append(expected, msg_part4);
 
-	test_out("added, slow",
-		 strcmp(str_c(buffer), str_c(expected)) == 0);
+	test_out("added, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
 
-	/* added, filtered */
+	/* Added, filtered */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
 
-	input_filt = i_stream_create_header_filter(input_mail,
-		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
-		N_ELEMENTS(hide_headers), *null_header_filter_callback,
-		(void *)NULL);
+	input_filt = i_stream_create_header_filter(
+		input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+		hide_headers, N_ELEMENTS(hide_headers),
+		*null_header_filter_callback, (void *)NULL);
 	input = i_stream_create_lf(input_filt);
 	i_stream_unref(&input_filt);
 
@@ -299,15 +298,15 @@ static void test_edit_mail_concatenated(
 
 	i_stream_unref(&input);
 
-	/* added, filtered, slow */
+	/* Added, filtered, slow */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
 
-	input_filt = i_stream_create_header_filter(input_mail,
-		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
-		N_ELEMENTS(hide_headers), *null_header_filter_callback,
-		(void *)NULL);
+	input_filt = i_stream_create_header_filter(
+		input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+		hide_headers, N_ELEMENTS(hide_headers),
+		*null_header_filter_callback, (void *)NULL);
 	input = i_stream_create_lf(input_filt);
 	i_stream_unref(&input_filt);
 
@@ -326,26 +325,26 @@ static void test_edit_mail_concatenated(
 
 	i_stream_unref(&input);
 
-	/* delete header */
+	/* Delete header */
 
 	edit_mail_header_delete(edmail, "Delivered-To", 0);
 
-	/* evaluate modified header */
+	/* Evaluate modified header */
 
 	test_assert(mail_get_first_header_utf8(mail, "Subject", &value) > 0);
 	test_assert(strcmp(value, "Sieve editheader breaks with LMTP") == 0);
 
 	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Flag",
-					  &value) > 0);
+					       &value) > 0);
 	test_assert(strcmp(value, "NO") == 0);
 	test_assert(mail_get_first_header_utf8(mail, "X-Filter-Junk-Type",
-					  &value) > 0);
+					       &value) > 0);
 	test_assert(strcmp(value, "NONE") == 0);
 
 	test_assert(mail_get_first_header_utf8(mail, "Delivered-To",
-					  &value) == 0);
+					       &value) == 0);
 
-	/* deleted */
+	/* Deleted */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
@@ -359,10 +358,9 @@ static void test_edit_mail_concatenated(
 	str_append(expected, msg_part2);
 	str_append(expected, msg_part4);
 
-	test_out("deleted",
-		 strcmp(str_c(buffer), str_c(expected)) == 0);
+	test_out("deleted", strcmp(str_c(buffer), str_c(expected)) == 0);
 
-	/* deleted, slow */
+	/* Deleted, slow */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
@@ -375,18 +373,17 @@ static void test_edit_mail_concatenated(
 	str_append(expected, msg_part2);
 	str_append(expected, msg_part4);
 
-	test_out("deleted, slow",
-		 strcmp(str_c(buffer), str_c(expected)) == 0);
+	test_out("deleted, slow", strcmp(str_c(buffer), str_c(expected)) == 0);
 
-	/* deleted, filtered */
+	/* Deleted, filtered */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
 
-	input_filt = i_stream_create_header_filter(input_mail,
-		HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, hide_headers,
-		N_ELEMENTS(hide_headers), *null_header_filter_callback,
-		(void *)NULL);
+	input_filt = i_stream_create_header_filter(
+		input_mail, (HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR),
+		hide_headers, N_ELEMENTS(hide_headers),
+		*null_header_filter_callback, (void *)NULL);
 	input = i_stream_create_lf(input_filt);
 	i_stream_unref(&input_filt);
 
@@ -404,7 +401,7 @@ static void test_edit_mail_concatenated(
 
 	i_stream_unref(&input);
 
-	/* deleted, filtered, slow */
+	/* Deleted, filtered, slow */
 
 	i_stream_seek(input_mail, 0);
 	buffer_set_used_size(buffer, 0);
@@ -724,8 +721,7 @@ static void test_edit_mail_big_header(vo
 
 	/* evaluate modified header */
 
-	test_assert(mail_get_first_header_utf8(mail, "X-B",
-					  &value) == 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-B", &value) == 0);
 
 	/* deleted */
 
@@ -744,11 +740,81 @@ static void test_edit_mail_big_header(vo
 	test_end();
 }
 
+static void test_edit_mail_small_buffer(void)
+{
+	static const char *message =
+		"X-A: AAAA\n"
+		"X-B: BBBB\n"
+		"\n"
+		"Frop!\n";
+	struct istream *input_msg, *input_mail;
+	buffer_t *buffer;
+	struct mail_raw *rawmail;
+	struct edit_mail *edmail;
+	struct mail *mail;
+	const char *value;
+	unsigned int i;
+
+	test_begin("edit-mail - small buffer");
+	test_init();
+
+	/* compose the message */
+
+	input_msg = i_stream_create_from_data(message, strlen(message));
+	i_stream_set_max_buffer_size(input_msg, 16);
+
+	rawmail = mail_raw_open_stream(test_raw_mail_user, input_msg);
+
+	edmail = edit_mail_wrap(rawmail->mail);
+
+	/* add headers */
+
+	for (i = 0; i < 16; i++) {
+		edit_mail_header_add(edmail, "X-F", "FF", FALSE);
+		edit_mail_header_add(edmail, "X-L", "LL", TRUE);
+	}
+	
+	mail = edit_mail_get_mail(edmail);
+
+	/* prepare tests */
+
+	if (mail_get_stream(mail, NULL, NULL, &input_mail) < 0) {
+		i_fatal("Failed to open mail stream: %s",
+			mailbox_get_last_internal_error(mail->box, NULL));
+	}
+
+	buffer = buffer_create_dynamic(default_pool, 1024);
+
+	/* evaluate modified header */
+
+	test_assert(mail_get_first_header_utf8(mail, "X-F", &value) > 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-A", &value) > 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-B", &value) > 0);
+	test_assert(mail_get_first_header_utf8(mail, "X-L", &value) > 0);
+
+	/* check stream read */
+
+	i_stream_seek(input_mail, 0);
+	buffer_set_used_size(buffer, 0);
+
+	test_stream_data(input_mail, buffer);
+
+	/* clean up */
+
+	buffer_free(&buffer);
+	edit_mail_unwrap(&edmail);
+	mail_raw_close(&rawmail);
+	i_stream_unref(&input_msg);
+	test_deinit();
+	test_end();
+}
+
 int main(int argc, char *argv[])
 {
 	static void (*test_functions[])(void) = {
 		test_edit_mail_concatenated,
 		test_edit_mail_big_header,
+		test_edit_mail_small_buffer,
 		NULL
 	};
 	const enum master_service_flags service_flags =
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve-tool/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve-tool/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve-tool/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve-tool/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -202,6 +202,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -247,6 +249,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve-tool/sieve-tool.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve-tool/sieve-tool.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/lib-sieve-tool/sieve-tool.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/lib-sieve-tool/sieve-tool.c	2022-06-14 06:55:57.000000000 +0000
@@ -138,7 +138,8 @@ sieve_tool_init(const char *name, int *a
 	enum master_service_flags service_flags =
 		MASTER_SERVICE_FLAG_STANDALONE |
 		MASTER_SERVICE_FLAG_DONT_SEND_STATS |
-		MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME;
+		MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME |
+		MASTER_SERVICE_FLAG_DISABLE_SSL_SET;
 
 	if (no_config)
 		service_flags |= MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS;
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/Makefile.in	2021-08-06 09:26:53.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/Makefile.in	2022-06-14 06:56:04.000000000 +0000
@@ -210,6 +210,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -255,6 +257,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve/main.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve/main.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve/main.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve/main.c	2022-06-14 06:55:57.000000000 +0000
@@ -313,8 +313,10 @@ int main(int argc, char *argv[])
 	} else {
 		service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
 	}
-	if (getenv("DUMP_CAPABILITY") != NULL)
-		service_flags |= MASTER_SERVICE_FLAG_DONT_SEND_STATS;
+	if (getenv("DUMP_CAPABILITY") != NULL) {
+		service_flags |= MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+			MASTER_SERVICE_FLAG_DISABLE_SSL_SET;
+	}
 
 	master_service = master_service_init("managesieve", service_flags,
 					     &argc, &argv, "t:u:");
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -276,6 +276,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -321,6 +323,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/client.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/client.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/client.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/client.c	2022-06-14 06:55:57.000000000 +0000
@@ -9,6 +9,7 @@
 #include "safe-memset.h"
 #include "str.h"
 #include "strescape.h"
+#include "base64.h"
 #include "master-service.h"
 #include "master-auth.h"
 #include "auth-client.h"
@@ -22,14 +23,13 @@
 #include "managesieve-login-settings.h"
 #include "managesieve-proxy.h"
 
-
 /* Disconnect client when it sends too many bad commands */
 #define CLIENT_MAX_BAD_COMMANDS 3
 
 struct managesieve_command {
 	const char *name;
-	int (*func)
-		(struct managesieve_client *client, const struct managesieve_arg *args);
+	int (*func)(struct managesieve_client *client,
+		    const struct managesieve_arg *args);
 	int preparsed_args;
 };
 
@@ -55,39 +55,46 @@ bool client_skip_line(struct managesieve
 static void client_send_capabilities(struct client *client)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 	const char *saslcap;
 
 	T_BEGIN {
 		saslcap = client_authenticate_get_capabilities(client);
 
 		/* Default capabilities */
-		client_send_raw(client, t_strconcat("\"IMPLEMENTATION\" \"",
-			msieve_client->set->managesieve_implementation_string, "\"\r\n", NULL));
-		client_send_raw(client, t_strconcat("\"SIEVE\" \"",
-			msieve_client->set->managesieve_sieve_capability, "\"\r\n", NULL));
-		if ( msieve_client->set->managesieve_notify_capability != NULL )
-			client_send_raw(client, t_strconcat("\"NOTIFY\" \"",
-				msieve_client->set->managesieve_notify_capability, "\"\r\n", NULL));
-		client_send_raw
-			(client, t_strconcat("\"SASL\" \"", saslcap, "\"\r\n", NULL));
+		client_send_raw(client, t_strconcat(
+			"\"IMPLEMENTATION\" \"",
+			msieve_client->set->managesieve_implementation_string,
+			"\"\r\n", NULL));
+		client_send_raw(client, t_strconcat(
+			"\"SIEVE\" \"",
+			msieve_client->set->managesieve_sieve_capability,
+			"\"\r\n", NULL));
+		if (msieve_client->set->managesieve_notify_capability != NULL) {
+			client_send_raw(client, t_strconcat(
+				"\"NOTIFY\" \"",
+				msieve_client->set->managesieve_notify_capability,
+				"\"\r\n", NULL));
+		}
+		client_send_raw(client, t_strconcat("\"SASL\" \"", saslcap,
+						    "\"\r\n", NULL));
 
 		/* STARTTLS */
 		if (login_ssl_initialized && !client->tls)
-			client_send_raw(client, "\"STARTTLS\"\r\n" );
+			client_send_raw(client, "\"STARTTLS\"\r\n");
 
 		/* Protocol version */
 		client_send_raw(client, "\"VERSION\" \"1.0\"\r\n");
 
 		/* XCLIENT */
-	    if (client->trusted)
-        	client_send_raw(client, "\"XCLIENT\"\r\n");
+		if (client->trusted)
+			client_send_raw(client, "\"XCLIENT\"\r\n");
 	} T_END;
 }
 
-static int cmd_capability
-(struct managesieve_client *client,
-	const struct managesieve_arg *args ATTR_UNUSED)
+static int
+cmd_capability(struct managesieve_client *client,
+	       const struct managesieve_arg *args ATTR_UNUSED)
 {
 	o_stream_cork(client->common.output);
 
@@ -99,39 +106,37 @@ static int cmd_capability
 	return 1;
 }
 
-static int cmd_starttls
-(struct managesieve_client *client,
-	const struct managesieve_arg *args ATTR_UNUSED)
+static int
+cmd_starttls(struct managesieve_client *client,
+	     const struct managesieve_arg *args ATTR_UNUSED)
 {
 	client_cmd_starttls(&client->common);
 	return 1;
 }
 
-static void managesieve_client_notify_starttls
-(struct client *client, bool success, const char *text)
+static void
+managesieve_client_notify_starttls(struct client *client, bool success,
+				   const char *text)
 {
-	if ( success )
+	if (success)
 		client_send_ok(client, text);
 	else
 		client_send_no(client, text);
 }
 
-static int cmd_noop
-(struct managesieve_client *client,
-	const struct managesieve_arg *args)
+static int
+cmd_noop(struct managesieve_client *client, const struct managesieve_arg *args)
 {
 	const char *text;
 	string_t *resp_code;
 
-	if ( MANAGESIEVE_ARG_IS_EOL(&args[0]) ) {
+	if (MANAGESIEVE_ARG_IS_EOL(&args[0])) {
 		client_send_ok(&client->common, "NOOP Completed");
 		return 1;
 	}
-
-	if ( !MANAGESIEVE_ARG_IS_EOL(&args[1]) )
+	if (!MANAGESIEVE_ARG_IS_EOL(&args[1]))
 		return -1;
-
-	if ( !managesieve_arg_get_string(&args[0], &text) ) {
+	if (!managesieve_arg_get_string(&args[0], &text)) {
 		client_send_no(&client->common, "Invalid echo tag.");
 		return 1;
 	}
@@ -144,49 +149,72 @@ static int cmd_noop
 	return 1;
 }
 
-static int cmd_logout
-(struct managesieve_client *client,
-	const struct managesieve_arg *args ATTR_UNUSED)
+static int
+cmd_logout(struct managesieve_client *client,
+	   const struct managesieve_arg *args ATTR_UNUSED)
 {
 	client_send_ok(&client->common, "Logout completed.");
 	client_destroy(&client->common, CLIENT_UNAUTHENTICATED_LOGOUT_MSG);
 	return 1;
 }
 
-static int cmd_xclient
-(struct managesieve_client *client,
-	const struct managesieve_arg *args)
+static int
+cmd_xclient_parse_forward(struct managesieve_client *client, const char *value)
+{
+	size_t value_len = strlen(value);
+
+	if (client->common.forward_fields != NULL)
+		str_truncate(client->common.forward_fields, 0);
+	else {
+		client->common.forward_fields =	str_new(
+			client->common.preproxy_pool,
+			MAX_BASE64_DECODED_SIZE(value_len));
+	}
+
+	if (base64_decode(value, value_len, NULL,
+			  client->common.forward_fields) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int
+cmd_xclient(struct managesieve_client *client,
+	    const struct managesieve_arg *args)
 {
 	const char *arg;
 	bool args_ok = TRUE;
 
-	if ( !client->common.trusted ) {
-		client_send_no(&client->common,
-			"You are not from trusted IP");
+	if (!client->common.trusted) {
+		client_send_no(&client->common, "You are not from trusted IP");
 		return 1;
 	}
 	while (!MANAGESIEVE_ARG_IS_EOL(&args[0]) &&
 		managesieve_arg_get_atom(&args[0], &arg)) {
-		if ( strncasecmp(arg, "ADDR=", 5) == 0 ) {
+		if (strncasecmp(arg, "ADDR=", 5) == 0) {
 			if (net_addr2ip(arg + 5, &client->common.ip) < 0)
 				args_ok = FALSE;
-		} else if ( strncasecmp(arg, "PORT=", 5) == 0 ) {
-			if (net_str2port(arg + 5, &client->common.remote_port) < 0)
+		} else if (strncasecmp(arg, "FORWARD=", 8)  == 0) {
+			if (cmd_xclient_parse_forward(client, arg + 8) < 0)
+				args_ok = FALSE;
+		} else if (strncasecmp(arg, "PORT=", 5) == 0) {
+			if (net_str2port(arg + 5,
+					 &client->common.remote_port) < 0)
 				args_ok = FALSE;
-		} else if ( strncasecmp(arg, "SESSION=", 8) == 0 ) {
+		} else if (strncasecmp(arg, "SESSION=", 8) == 0) {
 			const char *value = arg + 8;
 
 			if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) {
 				client->common.session_id =
 					p_strdup(client->common.pool, value);
 			}
-		} else if ( strncasecmp(arg, "TTL=", 4 ) == 0) {
+		} else if (strncasecmp(arg, "TTL=", 4)  == 0) {
 			if (str_to_uint(arg + 4, &client->common.proxy_ttl) < 0)
 				args_ok = FALSE;
 		}
 		args++;
 	}
-	if ( !args_ok || !MANAGESIEVE_ARG_IS_EOL(&args[0]))
+	if (!args_ok || !MANAGESIEVE_ARG_IS_EOL(&args[0]))
 		return -1;
 
 	client_send_ok(&client->common, "Updated");
@@ -208,13 +236,13 @@ static bool client_handle_input(struct m
 	i_assert(!client->common.authenticating);
 
 	if (client->cmd_finished) {
-		/* clear the previous command from memory */
+		/* Clear the previous command from memory */
 		client->cmd_name = NULL;
 		client->cmd_parsed_args = FALSE;
 		client->cmd = NULL;
 		managesieve_parser_reset(client->parser);
 
-		/* remove \r\n */
+		/* Remove \r\n */
 		if (client->skip_line) {
 			if (!client_skip_line(client))
 				return FALSE;
@@ -230,17 +258,17 @@ static bool client_handle_input(struct m
 
 		client->cmd_name = managesieve_parser_read_word(client->parser);
 		if (client->cmd_name == NULL)
-			return FALSE; /* need more data */
+			return FALSE; /* Need more data */
 
 		cmd_name = t_str_ucase(client->cmd_name);
 		cmd = commands;
-		while ( cmd->name != NULL ) {
-			if ( strcmp(cmd->name, cmd_name) == 0 )
+		while (cmd->name != NULL) {
+			if (strcmp(cmd->name, cmd_name) == 0)
 				break;
 			cmd++;
 		}
 
-		if ( cmd->name != NULL )
+		if (cmd->name != NULL)
 			client->cmd = cmd;
 		else
 			client->skip_line = TRUE;
@@ -258,45 +286,48 @@ static bool managesieve_client_input_nex
 	bool fatal;
 
 	if (client->cmd == NULL) {
-		/* unknown command */
+		/* Unknown command */
 		ret = -1;
-	} else if ( !client->cmd_parsed_args ) {
+	} else if (!client->cmd_parsed_args) {
 		unsigned int arg_count =
-			( client->cmd->preparsed_args > 0 ? client->cmd->preparsed_args : 0 );
-		switch (managesieve_parser_read_args(client->parser, arg_count, 0, &args)) {
+			(client->cmd->preparsed_args > 0 ?
+			 client->cmd->preparsed_args : 0);
+
+		switch (managesieve_parser_read_args(client->parser, arg_count,
+						     0, &args)) {
 		case -1:
-			/* error */
-			msg = managesieve_parser_get_error(client->parser, &fatal);
+			/* Error */
+			msg = managesieve_parser_get_error(client->parser,
+							   &fatal);
 			if (fatal) {
 				client_send_bye(&client->common, msg);
 				client_destroy(&client->common, msg);
 				return FALSE;
 			}
-
 			client_send_no(&client->common, msg);
 			client->cmd_finished = TRUE;
 			client->skip_line = TRUE;
 			return TRUE;
 		case -2:
-			/* not enough data */
+			/* Not enough data */
 			return FALSE;
 		}
 		i_assert(args != NULL);
 
-		if (arg_count == 0 ) {
-			/* we read the entire line - skip over the CRLF */
+		if (arg_count == 0) {
+			/* We read the entire line - skip over the CRLF */
 			if (!client_skip_line(client))
 				i_unreached();
 		} else {
-			/* get rid of it later */
+			/* Get rid of it later */
 			client->skip_line = TRUE;
 		}
 
 		client->cmd_parsed_args = TRUE;
 
 		if (client->cmd->preparsed_args == -1) {
-			/* check absence of arguments */
-			if ( args[0].type != MANAGESIEVE_ARG_EOL )
+			/* Check absence of arguments */
+			if (args[0].type != MANAGESIEVE_ARG_EOL)
 				ret = -1;
 		}
 	}
@@ -325,20 +356,20 @@ static bool managesieve_client_input_nex
 static void managesieve_client_input(struct client *client)
 {
 	struct managesieve_client *managesieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	if (!client_read(client))
 		return;
 
 	client_ref(client);
-
 	o_stream_cork(managesieve_client->common.output);
 	for (;;) {
 		if (!auth_client_is_connected(auth_client)) {
-			/* we're not currently connected to auth process -
+			/* We're not currently connected to auth process -
 			   don't allow any commands */
-			/* FIXME: Can't do untagged responses with managesieve. Any other ways?
-			client_send_ok(client, AUTH_SERVER_WAITING_MSG);
+			/* FIXME: Can't do untagged responses with managesieve.
+			   Any other ways?
+			   client_send_ok(client, AUTH_SERVER_WAITING_MSG);
 			*/
 			timeout_remove(&client->to_auth_waiting);
 
@@ -361,22 +392,21 @@ static struct client *managesieve_client
 	return &msieve_client->common;
 }
 
-static void managesieve_client_create
-(struct client *client, void **other_sets)
+static void managesieve_client_create(struct client *client, void **other_sets)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	msieve_client->set = other_sets[0];
-	msieve_client->parser = managesieve_parser_create
-		(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
+	msieve_client->parser = managesieve_parser_create(
+		msieve_client->common.input, MAX_MANAGESIEVE_LINE);
 	client->io = io_add(client->fd, IO_READ, client_input, client);
 }
 
 static void managesieve_client_destroy(struct client *client)
 {
 	struct managesieve_client *managesieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	managesieve_parser_destroy(&managesieve_client->parser);
 }
@@ -384,7 +414,7 @@ static void managesieve_client_destroy(s
 static void managesieve_client_notify_auth_ready(struct client *client)
 {
 	/* Cork the stream to send the capability data as a single tcp frame
-	 *   Some naive clients break if we don't.
+	   Some naive clients break if we don't.
 	 */
 	o_stream_cork(client->output);
 
@@ -400,17 +430,17 @@ static void managesieve_client_notify_au
 static void managesieve_client_starttls(struct client *client)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	managesieve_parser_destroy(&msieve_client->parser);
-	msieve_client->parser = managesieve_parser_create
-		(msieve_client->common.input, MAX_MANAGESIEVE_LINE);
+	msieve_client->parser = managesieve_parser_create(
+		msieve_client->common.input, MAX_MANAGESIEVE_LINE);
 
 	/* CRLF is lost from buffer when streams are reopened. */
 	msieve_client->skip_line = FALSE;
 
 	/* Cork the stream to send the capability data as a single tcp frame
-	 *   Some naive clients break if we don't.
+	   Some naive clients break if we don't.
 	 */
 	o_stream_cork(client->output);
 
@@ -421,9 +451,8 @@ static void managesieve_client_starttls(
 }
 
 static void
-client_send_reply_raw(struct client *client,
-				   const char *prefix, const char *resp_code,
-				   const char *text)
+client_send_reply_raw(struct client *client, const char *prefix,
+		      const char *resp_code, const char *text)
 {
 	T_BEGIN {
 		string_t *line = t_str_new(256);
@@ -436,7 +465,7 @@ client_send_reply_raw(struct client *cli
 			str_append_c(line, ')');
 		}
 
-		if ( text != NULL )	{
+		if (text != NULL) {
 			str_append_c(line, ' ');
 			managesieve_quote_append_string(line, text, TRUE);
 		}
@@ -447,9 +476,9 @@ client_send_reply_raw(struct client *cli
 	} T_END;
 }
 
-void client_send_reply_code
-(struct client *client, enum managesieve_cmd_reply reply, const char *resp_code,
-	const char *text)
+void client_send_reply_code(struct client *client,
+			    enum managesieve_cmd_reply reply,
+			    const char *resp_code, const char *text)
 {
 	const char *prefix = "NO";
 
@@ -467,22 +496,23 @@ void client_send_reply_code
 	client_send_reply_raw(client, prefix, resp_code, text);
 }
 
-void client_send_reply
-(struct client *client, enum managesieve_cmd_reply reply, const char *text)
+void client_send_reply(struct client *client, enum managesieve_cmd_reply reply,
+		       const char *text)
 {
 	client_send_reply_code(client, reply, NULL, text);
 }
 
 static void
-managesieve_client_notify_disconnect
-(struct client *client, enum client_disconnect_reason reason, const char *text)
-{
-	if ( reason == CLIENT_DISCONNECT_SYSTEM_SHUTDOWN ) {
-		client_send_reply_code
-			(client, MANAGESIEVE_CMD_REPLY_BYE, "TRYLATER", text);
+managesieve_client_notify_disconnect(struct client *client,
+				     enum client_disconnect_reason reason,
+				     const char *text)
+{
+	if (reason == CLIENT_DISCONNECT_SYSTEM_SHUTDOWN) {
+		client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE,
+				       "TRYLATER", text);
 	} else {
-		client_send_reply_code
-			(client, MANAGESIEVE_CMD_REPLY_BYE, NULL, text);
+		client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE,
+				       NULL, text);
 	}
 }
 
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/client.h 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/client.h
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/client.h	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/client.h	2022-06-14 06:55:57.000000000 +0000
@@ -49,12 +49,12 @@ enum managesieve_cmd_reply {
 	MANAGESIEVE_CMD_REPLY_BYE
 };
 
-void client_send_reply(struct client *client,
-				   enum managesieve_cmd_reply reply, const char *text);
+void client_send_reply(struct client *client, enum managesieve_cmd_reply reply,
+		       const char *text);
 
 void client_send_reply_code(struct client *client,
-				   enum managesieve_cmd_reply reply, const char *resp_code,
-				   const char *text);
+			    enum managesieve_cmd_reply reply,
+			    const char *resp_code, const char *text);
 
 #define client_send_ok(client, text) \
 	client_send_reply(client, MANAGESIEVE_CMD_REPLY_OK, text)
@@ -64,11 +64,13 @@ void client_send_reply_code(struct clien
 	client_send_reply(client, MANAGESIEVE_CMD_REPLY_BYE, text)
 
 #define client_send_okresp(client, resp_code, text) \
-	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_OK, resp_code, text)
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_OK, \
+			       resp_code, text)
 #define client_send_noresp(client, resp_code, text) \
-	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO, resp_code, text)
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO, \
+			       resp_code, text)
 #define client_send_byeresp(client, resp_code, text) \
-	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE, resp_code, text)
-
+	client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE, \
+			       resp_code, text)
 
 #endif
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -257,6 +257,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -302,6 +304,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/managesieve-proxy.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/managesieve-proxy.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/managesieve-login/managesieve-proxy.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/managesieve-login/managesieve-proxy.c	2022-06-14 06:55:57.000000000 +0000
@@ -8,6 +8,7 @@
 #include "ostream.h"
 #include "str.h"
 #include "str-sanitize.h"
+#include "strescape.h"
 #include "safe-memset.h"
 #include "buffer.h"
 #include "base64.h"
@@ -31,20 +32,48 @@ static const char *managesieve_proxy_sta
 	"none", "tls-start", "tls-ready", "xclient", "auth"
 };
 
-static void proxy_write_xclient
-(struct managesieve_client *client, string_t *str)
+static string_t *
+proxy_compose_xclient_forward(struct managesieve_client *client)
 {
-	str_printfa(str,
-		"XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u\r\n",
-		net_ip2addr(&client->common.ip),
-		client->common.remote_port,
-		client_get_session_id(&client->common),
-		client->common.proxy_ttl - 1);
+	const char *const *arg;
+	string_t *str;
+
+	if (*client->common.auth_passdb_args == NULL)
+		return NULL;
+
+	str = t_str_new(128);
+	for (arg = client->common.auth_passdb_args; *arg != NULL; arg++) {
+		if (strncasecmp(*arg, "forward_", 8) == 0) {
+			if (str_len(str) > 0)
+				str_append_c(str, '\t');
+			str_append_tabescaped(str, (*arg)+8);
+		}
+	}
+	if (str_len(str) == 0)
+		return NULL;
+
+	return str;
 }
 
-static void proxy_write_auth_data
-(const unsigned char *data, unsigned int data_len,
-	string_t *str)
+static void
+proxy_write_xclient(struct managesieve_client *client, string_t *str)
+{
+	string_t *fwd = proxy_compose_xclient_forward(client);
+
+	str_printfa(str, "XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u",
+		    net_ip2addr(&client->common.ip), client->common.remote_port,
+		    client_get_session_id(&client->common),
+		    client->common.proxy_ttl - 1);
+	if (fwd != NULL) {
+		str_append(str, " FORWARD=");
+		base64_encode(str_data(fwd), str_len(fwd), str);
+	}
+	str_append(str, "\r\n");
+}
+
+static void
+proxy_write_auth_data(const unsigned char *data, unsigned int data_len,
+		      string_t *str)
 {
 	if (data_len == 0)
 		str_append(str, "\"\"");
@@ -55,8 +84,8 @@ static void proxy_write_auth_data
 	}
 }
 
-static int proxy_write_auth
-(struct managesieve_client *client, string_t *str)
+static int
+proxy_write_auth(struct managesieve_client *client, string_t *str)
 {
 	struct dsasl_client_settings sasl_set;
 	const unsigned char *output;
@@ -65,9 +94,9 @@ static int proxy_write_auth
 
 	i_assert(client->common.proxy_ttl > 1);
 
-	if ( !client->proxy_sasl ) {
-		/* Prevent sending credentials to a server that has login disabled;
-		   i.e., due to the lack of TLS */
+	if (!client->proxy_sasl) {
+		/* Prevent sending credentials to a server that has login
+		   disabled; i.e., due to the lack of TLS */
 		login_proxy_failed(client->common.login_proxy,
 			login_proxy_get_event(client->common.login_proxy),
 			LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG,
@@ -80,8 +109,9 @@ static int proxy_write_auth
 
 	i_assert(client->common.proxy_sasl_client == NULL);
 	i_zero(&sasl_set);
-	sasl_set.authid = client->common.proxy_master_user != NULL ?
-		client->common.proxy_master_user : client->common.proxy_user;
+	sasl_set.authid = (client->common.proxy_master_user != NULL ?
+			   client->common.proxy_master_user :
+			   client->common.proxy_user);
 	sasl_set.authzid = client->common.proxy_user;
 	sasl_set.password = client->common.proxy_password;
 	client->common.proxy_sasl_client =
@@ -108,9 +138,9 @@ static int proxy_write_auth
 	return 0;
 }
 
-static int proxy_input_auth_challenge
-(struct managesieve_client *client, const char *line,
-	const char **challenge_r)
+static int
+proxy_input_auth_challenge(struct managesieve_client *client, const char *line,
+			   const char **challenge_r)
 {
 	struct istream *input;
 	struct managesieve_parser *parser;
@@ -122,8 +152,8 @@ static int proxy_input_auth_challenge
 	i_assert(client->common.proxy_sasl_client != NULL);
 	*challenge_r = NULL;
 
-	/* Build an input stream for the managesieve parser
-	 *  FIXME: Ugly, see proxy_input_capability().
+	/* Build an input stream for the managesieve parser.
+	   FIXME: Ugly, see proxy_input_capability().
 	 */
 	line = t_strconcat(line, "\r\n", NULL);
 	input = i_stream_create_from_data(line, strlen(line));
@@ -133,8 +163,9 @@ static int proxy_input_auth_challenge
 	(void)i_stream_read(input);
 	ret = managesieve_parser_read_args(parser, 1, 0, &args);
 
-	if ( ret >= 0 ) {
-		if ( ret > 0 && managesieve_arg_get_string(&args[0], &challenge) ) {
+	if (ret >= 0) {
+		if (ret > 0 &&
+		    managesieve_arg_get_string(&args[0], &challenge)) {
 			*challenge_r = t_strdup(challenge);
 		} else {
 			const char *reason = t_strdup_printf(
@@ -146,18 +177,19 @@ static int proxy_input_auth_challenge
 			fatal = TRUE;
 		}
 
-	} else if ( ret == -2 ) {
+	} else if (ret == -2) {
 		/* Parser needs more data (not possible on mem stream) */
 		i_unreached();
 
 	} else {
-		const char *error_str = managesieve_parser_get_error(parser, &fatal);
-		error_str = (error_str != NULL ? error_str : "unknown (bug)" );
+		const char *error_str =
+			managesieve_parser_get_error(parser, &fatal);
+		error_str = (error_str != NULL ? error_str : "unknown (bug)");
 
 		/* Do not accept faulty server */
 		const char *reason = t_strdup_printf(
-			"Protocol parse error(%d) int SASL challenge line: %s (line=`%s')",
-			ret, error_str, line);
+			"Protocol parse error(%d) int SASL challenge line: %s "
+			"(line=`%s')", ret, error_str, line);
 		login_proxy_failed(client->common.login_proxy,
 			login_proxy_get_event(client->common.login_proxy),
 			LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
@@ -166,17 +198,18 @@ static int proxy_input_auth_challenge
 
 
 	/* Cleanup parser */
-  managesieve_parser_destroy(&parser);
+	managesieve_parser_destroy(&parser);
 	i_stream_destroy(&input);
 
 	/* Time to exit if greeting was not accepted */
-	if ( fatal ) return -1;
+	if (fatal)
+		return -1;
 	return 0;
 }
 
-static int proxy_write_auth_response
-(struct managesieve_client *client,
-	const char *challenge, string_t *str)
+static int
+proxy_write_auth_response(struct managesieve_client *client,
+			  const char *challenge, string_t *str)
 {
 	const unsigned char *data;
 	size_t data_len;
@@ -212,21 +245,21 @@ static int proxy_write_auth_response
 	return 0;
 }
 
-static managesieve_response_t proxy_read_response
-(const struct managesieve_arg *args)
+static managesieve_response_t
+proxy_read_response(const struct managesieve_arg *args)
 {
 	const char *response;
 
-	if ( managesieve_arg_get_atom(&args[0], &response) ) {
-		if ( strcasecmp(response, "OK") == 0 ) {
+	if (managesieve_arg_get_atom(&args[0], &response)) {
+		if (strcasecmp(response, "OK") == 0) {
 			/* Received OK response; greeting is finished */
 			return MANAGESIEVE_RESPONSE_OK;
 
-		} else if ( strcasecmp(response, "NO") == 0 ) {
+		} else if (strcasecmp(response, "NO") == 0) {
 			/* Received OK response; greeting is finished */
 			return MANAGESIEVE_RESPONSE_NO;
 
-		} else if ( strcasecmp(response, "BYE") == 0 ) {
+		} else if (strcasecmp(response, "BYE") == 0) {
 			/* Received OK response; greeting is finished */
 			return MANAGESIEVE_RESPONSE_BYE;
 		}
@@ -234,9 +267,9 @@ static managesieve_response_t proxy_read
 	return MANAGESIEVE_RESPONSE_NONE;
 }
 
-static int proxy_input_capability
-(struct managesieve_client *client, const char *line,
-	managesieve_response_t *resp_r)
+static int
+proxy_input_capability(struct managesieve_client *client, const char *line,
+		       managesieve_response_t *resp_r)
 {
 	struct istream *input;
 	struct managesieve_parser *parser;
@@ -248,10 +281,11 @@ static int proxy_input_capability
 	*resp_r = MANAGESIEVE_RESPONSE_NONE;
 
 	/* Build an input stream for the managesieve parser
-	 *  FIXME: It would be nice if the line-wise parsing could be
-	 *    substituded by something similar to the command line interpreter.
-	 *    However, the current login_proxy structure does not make streams
-	 *    known until inside proxy_input handler.
+
+	   FIXME: It would be nice if the line-wise parsing could be substituded
+	          by something similar to the command line interpreter. However,
+	          the current login_proxy structure does not make streams known
+		  until inside proxy_input handler.
 	 */
 	line = t_strconcat(line, "\r\n", NULL);
 	input = i_stream_create_from_data(line, strlen(line));
@@ -259,13 +293,14 @@ static int proxy_input_capability
 	managesieve_parser_reset(parser);
 
 	/* Parse input
-	 *  FIXME: Theoretically the OK response could include a
-	 *   response code which could be rejected by the parser.
+
+	   FIXME: Theoretically the OK response could include a response code
+	          which could be rejected by the parser.
 	 */
 	(void)i_stream_read(input);
 	ret = managesieve_parser_read_args(parser, 2, 0, &args);
 
-	if ( ret == 0 ) {
+	if (ret == 0) {
 		const char *reason = t_strdup_printf(
 			"Remote returned with invalid capability/greeting line: %s",
 			str_sanitize(line,160));
@@ -273,11 +308,11 @@ static int proxy_input_capability
 			login_proxy_get_event(client->common.login_proxy),
 			LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
 		fatal = TRUE;
-	} else if ( ret > 0 ) {
-		if ( args[0].type == MANAGESIEVE_ARG_ATOM ) {
+	} else if (ret > 0) {
+		if (args[0].type == MANAGESIEVE_ARG_ATOM) {
 			*resp_r = proxy_read_response(args);
 
-			if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) {
+			if (*resp_r == MANAGESIEVE_RESPONSE_NONE) {
 				const char *reason = t_strdup_printf(
 					"Remote sent invalid response: %s",
 					str_sanitize(line,160));
@@ -288,17 +323,18 @@ static int proxy_input_capability
 
 				fatal = TRUE;
 			}
-		} else if ( managesieve_arg_get_string(&args[0], &capability) ) {
-			if ( strcasecmp(capability, "SASL") == 0 ) {
+		} else if (managesieve_arg_get_string(&args[0], &capability)) {
+			if (strcasecmp(capability, "SASL") == 0) {
 				const char *sasl_mechs;
 
 				/* Check whether the server supports the SASL mechanism
-				 * we are going to use (currently only PLAIN supported).
+				   we are going to use (currently only PLAIN supported).
 				 */
-				if ( ret == 2 && managesieve_arg_get_string(&args[1], &sasl_mechs) ) {
+				if (ret == 2 &&
+				    managesieve_arg_get_string(&args[1], &sasl_mechs)) {
 					const char *const *mechs = t_strsplit(sasl_mechs, " ");
 
-					if ( *mechs != NULL ) {
+					if (*mechs != NULL) {
 						/* At least one SASL mechanism is supported */
 						client->proxy_sasl = TRUE;
 					}
@@ -311,9 +347,9 @@ static int proxy_input_capability
 					fatal = TRUE;
 				}
 
-			} else if ( strcasecmp(capability, "STARTTLS") == 0 ) {
+			} else if (strcasecmp(capability, "STARTTLS") == 0) {
 				client->proxy_starttls = TRUE;
-			} else if ( strcasecmp(capability, "XCLIENT") == 0 ) {
+			} else if (strcasecmp(capability, "XCLIENT") == 0) {
 				client->proxy_xclient = TRUE;
 			}
 
@@ -328,18 +364,19 @@ static int proxy_input_capability
 			fatal = TRUE;
 		}
 
-	} else if ( ret == -2 ) {
+	} else if (ret == -2) {
 		/* Parser needs more data (not possible on mem stream) */
 		i_unreached();
 
 	} else {
-		const char *error_str = managesieve_parser_get_error(parser, &fatal);
-		error_str = (error_str != NULL ? error_str : "unknown (bug)" );
+		const char *error_str =
+			managesieve_parser_get_error(parser, &fatal);
+		error_str = (error_str != NULL ? error_str : "unknown (bug)");
 
 		/* Do not accept faulty server */
 		const char *reason = t_strdup_printf(
-			"Protocol parse error(%d) in capability/greeting line: %s (line=`%s')",
-			ret, error_str, line);
+			"Protocol parse error(%d) in capability/greeting line: %s "
+			"(line=`%s')", ret, error_str, line);
 		login_proxy_failed(client->common.login_proxy,
 			login_proxy_get_event(client->common.login_proxy),
 			LOGIN_PROXY_FAILURE_TYPE_PROTOCOL, reason);
@@ -351,10 +388,12 @@ static int proxy_input_capability
 	i_stream_destroy(&input);
 
 	/* Time to exit if greeting was not accepted */
-	if ( fatal ) return -1;
+	if (fatal)
+		return -1;
 
 	/* Wait until greeting is received completely */
-	if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) return 1;
+	if (*resp_r == MANAGESIEVE_RESPONSE_NONE)
+		return 1;
 
 	return 0;
 }
@@ -394,7 +433,7 @@ managesieve_proxy_parse_auth_reply(const
 		}
 	}
 
-	/* parse the string */
+	/* Parse the string */
 	input = i_stream_create_from_data(line, strlen(line));
 	parser = managesieve_parser_create(input, (size_t)-1);
 	(void)i_stream_read(input);
@@ -402,12 +441,13 @@ managesieve_proxy_parse_auth_reply(const
 	if (ret == 1 && managesieve_arg_get_string(&args[0], &reason))
 		*reason_r = t_strdup(reason);
 	managesieve_parser_destroy(&parser);
+	i_stream_destroy(&input);
 }
 
 int managesieve_proxy_parse_line(struct client *client, const char *line)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 	struct ostream *output;
 	enum login_proxy_ssl_flags ssl_flags;
 	managesieve_response_t response = MANAGESIEVE_RESPONSE_NONE;
@@ -417,14 +457,13 @@ int managesieve_proxy_parse_line(struct
 	i_assert(!client->destroyed);
 
 	output = login_proxy_get_ostream(client->login_proxy);
-	switch ( msieve_client->proxy_state ) {
+	switch (msieve_client->proxy_state) {
 	case MSIEVE_PROXY_STATE_NONE:
-		if ( (ret=proxy_input_capability
-			(msieve_client, line, &response)) < 0 )
+		ret = proxy_input_capability(msieve_client, line, &response);
+		if (ret < 0)
 			return -1;
-
-		if ( ret == 0 ) {
-			if ( response != MANAGESIEVE_RESPONSE_OK ) {
+		if (ret == 0) {
+			if (response != MANAGESIEVE_RESPONSE_OK) {
 				login_proxy_failed(client->login_proxy,
 					login_proxy_get_event(client->login_proxy),
 					LOGIN_PROXY_FAILURE_TYPE_PROTOCOL,
@@ -436,7 +475,7 @@ int managesieve_proxy_parse_line(struct
 
 			ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
 			if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
-				if ( !msieve_client->proxy_starttls ) {
+				if (!msieve_client->proxy_starttls) {
 					login_proxy_failed(client->login_proxy,
 						login_proxy_get_event(client->login_proxy),
 						LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG,
@@ -446,13 +485,11 @@ int managesieve_proxy_parse_line(struct
 
 				str_append(command, "STARTTLS\r\n");
 				msieve_client->proxy_state = MSIEVE_PROXY_STATE_TLS_START;
-
 			} else if (msieve_client->proxy_xclient) {
 				proxy_write_xclient(msieve_client, command);
 				msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
-
 			} else {
-				if ( proxy_write_auth(msieve_client, command) < 0 )
+				if (proxy_write_auth(msieve_client, command) < 0)
 					return -1;
 				msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
 			}
@@ -460,13 +497,11 @@ int managesieve_proxy_parse_line(struct
 			o_stream_nsend(output, str_data(command), str_len(command));
 		}
 		return 0;
-
 	case MSIEVE_PROXY_STATE_TLS_START:
-		if ( strncasecmp(line, "OK", 2) == 0 &&
-			( strlen(line) == 2 || line[2] == ' ' ) ) {
-
+		if (strncasecmp(line, "OK", 2) == 0 &&
+		    (strlen(line) == 2 || line[2] == ' ')) {
 			/* STARTTLS successful, begin TLS negotiation. */
-			if ( login_proxy_starttls(client->login_proxy) < 0 )
+			if (login_proxy_starttls(client->login_proxy) < 0)
 				return -1;
 
 			msieve_client->proxy_sasl = FALSE;
@@ -480,13 +515,12 @@ int managesieve_proxy_parse_line(struct
 			LOGIN_PROXY_FAILURE_TYPE_REMOTE,
 			"Remote refused STARTTLS command");
 		return -1;
-
 	case MSIEVE_PROXY_STATE_TLS_READY:
-		if ( (ret=proxy_input_capability(msieve_client, line, &response)) < 0 )
+		ret = proxy_input_capability(msieve_client, line, &response);
+		if (ret < 0)
 			return -1;
-
-		if ( ret == 0 ) {
-			if ( response != MANAGESIEVE_RESPONSE_OK ) {
+		if (ret == 0) {
+			if (response != MANAGESIEVE_RESPONSE_OK) {
 				/* STARTTLS failed */
 				const char *reason = t_strdup_printf(
 					"Remote STARTTLS failed: %s",
@@ -498,25 +532,22 @@ int managesieve_proxy_parse_line(struct
 			}
 
 			command = t_str_new(128);
-			if ( msieve_client->proxy_xclient ) {
+			if (msieve_client->proxy_xclient) {
 				proxy_write_xclient(msieve_client, command);
 				msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
-
 			} else {
-				if ( proxy_write_auth(msieve_client, command) < 0 )
+				if (proxy_write_auth(msieve_client, command) < 0)
 					return -1;
 				msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
 			}
 			o_stream_nsend(output, str_data(command), str_len(command));
 		}
 		return 0;
-
 	case MSIEVE_PROXY_STATE_XCLIENT:
-		if ( strncasecmp(line, "OK", 2) == 0 &&
-			( strlen(line) == 2 || line[2] == ' ' ) ) {
-
+		if (strncasecmp(line, "OK", 2) == 0 &&
+		    (strlen(line) == 2 || line[2] == ' ')) {
 			command = t_str_new(128);
-			if ( proxy_write_auth(msieve_client, command) < 0 )
+			if (proxy_write_auth(msieve_client, command) < 0)
 				return -1;
 			o_stream_nsend(output, str_data(command), str_len(command));
 			msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
@@ -530,49 +561,52 @@ int managesieve_proxy_parse_line(struct
 			login_proxy_get_event(client->login_proxy),
 			LOGIN_PROXY_FAILURE_TYPE_REMOTE, reason);
 		return -1;
-
 	case MSIEVE_PROXY_STATE_AUTH:
 		/* Challenge? */
-		if ( *line == '"' ) {
+		if (*line == '"') {
 			const char *challenge;
 
-			if ( proxy_input_auth_challenge
-				(msieve_client, line, &challenge) < 0 )
+			if (proxy_input_auth_challenge(msieve_client, line,
+						       &challenge) < 0)
 				return -1;
 			command = t_str_new(128);
-			if ( proxy_write_auth_response
-				(msieve_client, challenge, command) < 0 )
+			if (proxy_write_auth_response(msieve_client, challenge,
+						      command) < 0)
 				return -1;
-			o_stream_nsend(output, str_data(command), str_len(command));
+			o_stream_nsend(output, str_data(command),
+				       str_len(command));
 			return 0;
 		}
 
 		/* Check login status */
-		if ( strncasecmp(line, "OK", 2) == 0 &&
-			(strlen(line) == 2 || line[2] == ' ') ) {
+		if (strncasecmp(line, "OK", 2) == 0 &&
+		    (strlen(line) == 2 || line[2] == ' ')) {
 			string_t *str = t_str_new(128);
 
 			/* Login successful */
 
-			/* FIXME: some SASL mechanisms cause a capability response to be sent */
+			/* FIXME: Some SASL mechanisms cause a capability
+			          response to be sent.
+			 */
 
 			/* Send this line to client. */
-			str_append(str, line );
+			str_append(str, line);
 			str_append(str, "\r\n");
-			o_stream_nsend(client->output, str_data(str), str_len(str));
+			o_stream_nsend(client->output, str_data(str),
+				       str_len(str));
 
-			(void)client_skip_line(msieve_client);
 			client_proxy_finish_destroy_client(client);
 			return 1;
 		}
 
 		/* Authentication failed */
 		bool try_later;
-		(void)managesieve_proxy_parse_auth_reply(line, &reason, &try_later);
+		(void)managesieve_proxy_parse_auth_reply(line, &reason,
+							 &try_later);
 
 		/* Login failed. Send our own failure reply so client can't
-		 * figure out if user exists or not just by looking at the
-		 * reply string.
+		   figure out if user exists or not just by looking at the reply
+		   string.
 		 */
 		enum login_proxy_failure_type failure_type;
 		if (try_later)
@@ -586,7 +620,6 @@ int managesieve_proxy_parse_line(struct
 			login_proxy_get_event(client->login_proxy),
 			failure_type, reason);
 		return -1;
-
 	default:
 		/* Not supposed to happen */
 		break;
@@ -599,7 +632,7 @@ int managesieve_proxy_parse_line(struct
 void managesieve_proxy_reset(struct client *client)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	msieve_client->proxy_starttls = FALSE;
 	msieve_client->proxy_sasl = FALSE;
@@ -647,7 +680,7 @@ void managesieve_proxy_failed(struct cli
 const char *managesieve_proxy_get_state(struct client *client)
 {
 	struct managesieve_client *msieve_client =
-		(struct managesieve_client *) client;
+		(struct managesieve_client *)client;
 
 	return managesieve_proxy_state_names[msieve_client->proxy_state];
 }
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/doveadm-sieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/doveadm-sieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/doveadm-sieve/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/doveadm-sieve/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -249,6 +249,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -294,6 +296,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imap-filter-sieve/imap-filter-sieve.c	2022-06-14 06:55:57.000000000 +0000
@@ -627,38 +627,66 @@ imap_filter_sieve_smtp_finish(const stru
  * Duplicate checking
  */
 
-static bool
-imap_filter_sieve_duplicate_check(const struct sieve_script_env *senv,
-				  const void *id, size_t id_size)
+static void *
+imap_filter_sieve_duplicate_transaction_begin(
+	const struct sieve_script_env *senv)
 {
 	struct imap_filter_sieve_context *sctx = senv->script_context;
 	struct imap_filter_sieve_user *ifsuser =
 		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
 
-	return mail_duplicate_check(ifsuser->dup_db,
-		id, id_size, senv->user->username);
+	return mail_duplicate_transaction_begin(ifsuser->dup_db);
 }
 
-static void
-imap_filter_sieve_duplicate_mark(const struct sieve_script_env *senv,
-				 const void *id, size_t id_size, time_t time)
+static void imap_filter_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct imap_filter_sieve_context *sctx = senv->script_context;
-	struct imap_filter_sieve_user *ifsuser =
-		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+
+	mail_duplicate_transaction_commit(&dup_trans);
+}
+
+static void imap_filter_sieve_duplicate_transaction_rollback(void **_dup_trans)
+{
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
 
-	mail_duplicate_mark(ifsuser->dup_db,
-		id, id_size, senv->user->username, time);
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+imap_filter_sieve_duplicate_check(void *_dup_trans,
+				  const struct sieve_script_env *senv,
+				  const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
 }
 
 static void
-imap_filter_sieve_duplicate_flush(const struct sieve_script_env *senv)
+imap_filter_sieve_duplicate_mark(void *_dup_trans,
+				 const struct sieve_script_env *senv,
+				 const void *id, size_t id_size, time_t time)
 {
-	struct imap_filter_sieve_context *sctx = senv->script_context;
-	struct imap_filter_sieve_user *ifsuser =
-		IMAP_FILTER_SIEVE_USER_CONTEXT_REQUIRE(sctx->user);
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(ifsuser->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -943,9 +971,14 @@ int imap_sieve_filter_run_init(struct im
 	scriptenv->smtp_send = imap_filter_sieve_smtp_send;
 	scriptenv->smtp_abort = imap_filter_sieve_smtp_abort;
 	scriptenv->smtp_finish = imap_filter_sieve_smtp_finish;
+	scriptenv->duplicate_transaction_begin =
+		imap_filter_sieve_duplicate_transaction_begin;
+	scriptenv->duplicate_transaction_commit =
+		imap_filter_sieve_duplicate_transaction_commit;
+	scriptenv->duplicate_transaction_rollback =
+		imap_filter_sieve_duplicate_transaction_rollback;
 	scriptenv->duplicate_mark = imap_filter_sieve_duplicate_mark;
 	scriptenv->duplicate_check = imap_filter_sieve_duplicate_check;
-	scriptenv->duplicate_flush = imap_filter_sieve_duplicate_flush;
 	scriptenv->script_context = sctx;
 	return 0;
 }
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imap-filter-sieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imap-filter-sieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imap-filter-sieve/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imap-filter-sieve/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -241,6 +241,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -286,6 +288,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/imap-sieve.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/imap-sieve.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/imap-sieve.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/imap-sieve.c	2022-06-14 06:55:57.000000000 +0000
@@ -219,32 +219,60 @@ imap_sieve_smtp_finish(const struct siev
  * Duplicate checking
  */
 
-static bool
-imap_sieve_duplicate_check(const struct sieve_script_env *senv, const void *id,
-			   size_t id_size)
+static void *
+imap_sieve_duplicate_transaction_begin(const struct sieve_script_env *senv)
 {
 	struct imap_sieve_context *isctx = senv->script_context;
 
-	return mail_duplicate_check(isctx->isieve->dup_db, id, id_size,
-				    senv->user->username);
+	return mail_duplicate_transaction_begin(isctx->isieve->dup_db);
 }
 
-static void
-imap_sieve_duplicate_mark(const struct sieve_script_env *senv, const void *id,
-			  size_t id_size, time_t time)
+static void imap_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct imap_sieve_context *isctx = senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
 
-	mail_duplicate_mark(isctx->isieve->dup_db, id, id_size,
-			    senv->user->username, time);
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_commit(&dup_trans);
+}
+
+static void imap_sieve_duplicate_transaction_rollback(void **_dup_trans)
+{
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+imap_sieve_duplicate_check(void *_dup_trans,
+			   const struct sieve_script_env *senv,
+			   const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
 }
 
 static void
-imap_sieve_duplicate_flush(const struct sieve_script_env *senv)
+imap_sieve_duplicate_mark(void *_dup_trans, const struct sieve_script_env *senv,
+			  const void *id, size_t id_size, time_t time)
 {
-	struct imap_sieve_context *isctx = senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(isctx->isieve->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -884,9 +912,14 @@ int imap_sieve_run_mail(struct imap_siev
 			scriptenv.smtp_send = imap_sieve_smtp_send;
 			scriptenv.smtp_abort = imap_sieve_smtp_abort;
 			scriptenv.smtp_finish = imap_sieve_smtp_finish;
+			scriptenv.duplicate_transaction_begin =
+				imap_sieve_duplicate_transaction_begin;
+			scriptenv.duplicate_transaction_commit =
+				imap_sieve_duplicate_transaction_commit;
+			scriptenv.duplicate_transaction_rollback =
+				imap_sieve_duplicate_transaction_rollback;
 			scriptenv.duplicate_mark = imap_sieve_duplicate_mark;
 			scriptenv.duplicate_check = imap_sieve_duplicate_check;
-			scriptenv.duplicate_flush = imap_sieve_duplicate_flush;
 			scriptenv.result_amend_log_message =
 				imap_sieve_result_amend_log_message;
 			scriptenv.trace_log = trace_log;
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/imap-sieve-storage.c	2022-06-14 06:55:57.000000000 +0000
@@ -581,6 +581,7 @@ imap_sieve_mailbox_run_copy_source(
 		return;
 
 	i_assert(ismt->src_mail_trans->box == src_box);
+	i_assert(mevent->src_mail_uid > 0);
 
 	if (*src_mail == NULL)
 		*src_mail = mail_alloc(ismt->src_mail_trans, 0, NULL);
@@ -741,11 +742,18 @@ imap_sieve_mailbox_transaction_run(
 		bool fatal;
 
 		/* Determine UID for saved message */
-		if (mevent->dest_mail_uid > 0 ||
-			!seq_range_array_iter_nth(&siter, mevent->save_seq, &uid))
+		if (mevent->dest_mail_uid > 0)
 			uid = mevent->dest_mail_uid;
+		else if (!seq_range_array_iter_nth(&siter, mevent->save_seq,
+						   &uid)) {
+			/* already gone for some reason */
+			imap_sieve_mailbox_debug(
+				sbox, "Message for Sieve event gone");
+			continue;
+		}
 
 		/* Select event message */
+		i_assert(uid > 0);
 		if (!mail_set_uid(mail, uid) || mail->expunged) {
 			/* already gone for some reason */
 			imap_sieve_mailbox_debug(sbox,
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/imapsieve/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/imapsieve/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -256,6 +256,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -301,6 +303,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/lda-sieve/lda-sieve-plugin.c	2022-06-14 06:55:57.000000000 +0000
@@ -148,34 +148,60 @@ lda_sieve_reject_mail(const struct sieve
  * Duplicate checking
  */
 
-static bool
-lda_sieve_duplicate_check(const struct sieve_script_env *senv,
-			  const void *id, size_t id_size)
+static void *
+lda_sieve_duplicate_transaction_begin(const struct sieve_script_env *senv)
 {
 	struct mail_deliver_context *dctx =
 		(struct mail_deliver_context *)senv->script_context;
 
-	return mail_duplicate_check(dctx->dup_db, id, id_size,
-				    senv->user->username);
+	return mail_duplicate_transaction_begin(dctx->dup_db);
 }
 
-static void
-lda_sieve_duplicate_mark(const struct sieve_script_env *senv, const void *id,
-			 size_t id_size, time_t time)
+static void lda_sieve_duplicate_transaction_commit(void **_dup_trans)
 {
-	struct mail_deliver_context *dctx =
-		(struct mail_deliver_context *)senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
 
-	mail_duplicate_mark(dctx->dup_db,
-		id, id_size, senv->user->username, time);
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_commit(&dup_trans);
 }
 
-static void lda_sieve_duplicate_flush(const struct sieve_script_env *senv)
+static void lda_sieve_duplicate_transaction_rollback(void **_dup_trans)
 {
-	struct mail_deliver_context *dctx =
-		(struct mail_deliver_context *)senv->script_context;
+	struct mail_duplicate_transaction *dup_trans = *_dup_trans;
+
+	*_dup_trans = NULL;
+	mail_duplicate_transaction_rollback(&dup_trans);
+}
+
+static enum sieve_duplicate_check_result
+lda_sieve_duplicate_check(void *_dup_trans, const struct sieve_script_env *senv,
+			  const void *id, size_t id_size)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
+
+	switch (mail_duplicate_check(dup_trans, id, id_size,
+				     senv->user->username)) {
+	case MAIL_DUPLICATE_CHECK_RESULT_EXISTS:
+		return SIEVE_DUPLICATE_CHECK_RESULT_EXISTS;
+	case MAIL_DUPLICATE_CHECK_RESULT_NOT_FOUND:
+		return SIEVE_DUPLICATE_CHECK_RESULT_NOT_FOUND;
+	case MAIL_DUPLICATE_CHECK_RESULT_DEADLOCK:
+	case MAIL_DUPLICATE_CHECK_RESULT_LOCK_TIMEOUT:
+		return SIEVE_DUPLICATE_CHECK_RESULT_TEMP_FAILURE;
+	case MAIL_DUPLICATE_CHECK_RESULT_IO_ERROR:
+	case MAIL_DUPLICATE_CHECK_RESULT_TOO_MANY_LOCKS:
+		break;
+	}
+	return SIEVE_DUPLICATE_CHECK_RESULT_FAILURE;
+}
+
+static void
+lda_sieve_duplicate_mark(void *_dup_trans, const struct sieve_script_env *senv,
+			 const void *id, size_t id_size, time_t time)
+{
+	struct mail_duplicate_transaction *dup_trans = _dup_trans;
 
-	mail_duplicate_db_flush(dctx->dup_db);
+	mail_duplicate_mark(dup_trans, id, id_size, senv->user->username, time);
 }
 
 /*
@@ -984,9 +1010,14 @@ lda_sieve_execute(struct lda_sieve_run_c
 	scriptenv.smtp_send = lda_sieve_smtp_send;
 	scriptenv.smtp_abort = lda_sieve_smtp_abort;
 	scriptenv.smtp_finish = lda_sieve_smtp_finish;
+	scriptenv.duplicate_transaction_begin =
+		lda_sieve_duplicate_transaction_begin;
+	scriptenv.duplicate_transaction_commit =
+		lda_sieve_duplicate_transaction_commit;
+	scriptenv.duplicate_transaction_rollback =
+		lda_sieve_duplicate_transaction_rollback;
 	scriptenv.duplicate_mark = lda_sieve_duplicate_mark;
 	scriptenv.duplicate_check = lda_sieve_duplicate_check;
-	scriptenv.duplicate_flush = lda_sieve_duplicate_flush;
 	scriptenv.reject_mail = lda_sieve_reject_mail;
 	scriptenv.result_amend_log_message = lda_sieve_result_amend_log_message;
 	scriptenv.script_context = (void *) mdctx;
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/lda-sieve/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/lda-sieve/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/lda-sieve/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/lda-sieve/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -235,6 +235,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -280,6 +282,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -209,6 +209,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -254,6 +256,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/settings/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/settings/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/settings/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/settings/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -232,6 +232,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -277,6 +279,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/sieve-extprograms/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/sieve-extprograms/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/plugins/sieve-extprograms/Makefile.in	2021-08-06 09:26:54.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/plugins/sieve-extprograms/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -244,6 +244,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -289,6 +291,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/sieve-tools/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/sieve-tools/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/sieve-tools/Makefile.in	2021-08-06 09:26:55.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/sieve-tools/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -230,6 +230,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -275,6 +277,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/sieve-tools/sieve-test.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/sieve-tools/sieve-test.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/sieve-tools/sieve-test.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/sieve-tools/sieve-test.c	2022-06-14 06:55:57.000000000 +0000
@@ -107,17 +107,34 @@ sieve_smtp_finish(const struct sieve_scr
  * Dummy duplicate check implementation
  */
 
-static bool
-duplicate_check(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
-		size_t id_size ATTR_UNUSED)
+static void *
+duplicate_transaction_begin(const struct sieve_script_env *senv ATTR_UNUSED)
+{
+	return NULL;
+}
+
+static void duplicate_transaction_commit(void **_dup_trans ATTR_UNUSED)
+{
+}
+
+static void duplicate_transaction_rollback(void **_dup_trans ATTR_UNUSED)
+{
+}
+
+static int
+duplicate_check(void *_dup_trans ATTR_UNUSED,
+		const struct sieve_script_env *senv,
+		const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED)
 {
 	i_info("checked duplicate for user %s.\n", senv->user->username);
 	return 0;
 }
 
 static void
-duplicate_mark(const struct sieve_script_env *senv, const void *id ATTR_UNUSED,
-	       size_t id_size ATTR_UNUSED, time_t time ATTR_UNUSED)
+duplicate_mark(void *_dup_trans ATTR_UNUSED,
+	       const struct sieve_script_env *senv,
+	       const void *id ATTR_UNUSED, size_t id_size ATTR_UNUSED,
+	       time_t time ATTR_UNUSED)
 {
 	i_info("marked duplicate for user %s.\n", senv->user->username);
 }
@@ -342,6 +359,12 @@ int main(int argc, char **argv)
 		scriptenv.smtp_send = sieve_smtp_send;
 		scriptenv.smtp_abort = sieve_smtp_abort;
 		scriptenv.smtp_finish = sieve_smtp_finish;
+		scriptenv.duplicate_transaction_begin =
+			duplicate_transaction_begin;
+		scriptenv.duplicate_transaction_commit =
+			duplicate_transaction_commit;
+		scriptenv.duplicate_transaction_rollback =
+			duplicate_transaction_rollback;
 		scriptenv.duplicate_mark = duplicate_mark;
 		scriptenv.duplicate_check = duplicate_check;
 		scriptenv.result_amend_log_message = result_amend_log_message;
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/testsuite/Makefile.in 1:2.3.19.1+dfsg1-2/pigeonhole/src/testsuite/Makefile.in
--- 1:2.3.16+dfsg1-3/pigeonhole/src/testsuite/Makefile.in	2021-08-06 09:26:55.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/testsuite/Makefile.in	2022-06-14 06:56:05.000000000 +0000
@@ -247,6 +247,8 @@ DOVECOT_CFLAGS = @DOVECOT_CFLAGS@
 DOVECOT_COMPRESS_LIBS = @DOVECOT_COMPRESS_LIBS@
 DOVECOT_INSTALLED = @DOVECOT_INSTALLED@
 DOVECOT_LIBS = @DOVECOT_LIBS@
+DOVECOT_LUA_CFLAGS = @DOVECOT_LUA_CFLAGS@
+DOVECOT_LUA_LIBS = @DOVECOT_LUA_LIBS@
 DOVECOT_SQL_LIBS = @DOVECOT_SQL_LIBS@
 DOVECOT_SSL_LIBS = @DOVECOT_SSL_LIBS@
 DSYMUTIL = @DSYMUTIL@
@@ -292,6 +294,9 @@ LIBDOVECOT_LMTP_INCLUDE = @LIBDOVECOT_LM
 LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
 LIBDOVECOT_LOGIN_DEPS = @LIBDOVECOT_LOGIN_DEPS@
 LIBDOVECOT_LOGIN_INCLUDE = @LIBDOVECOT_LOGIN_INCLUDE@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_LUA_INCLUDE = @LIBDOVECOT_LUA_INCLUDE@
 LIBDOVECOT_NOTIFY_INCLUDE = @LIBDOVECOT_NOTIFY_INCLUDE@
 LIBDOVECOT_POP3_INCLUDE = @LIBDOVECOT_POP3_INCLUDE@
 LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE = @LIBDOVECOT_PUSH_NOTIFICATION_INCLUDE@
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/src/testsuite/testsuite-script.c 1:2.3.19.1+dfsg1-2/pigeonhole/src/testsuite/testsuite-script.c
--- 1:2.3.16+dfsg1-3/pigeonhole/src/testsuite/testsuite-script.c	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/src/testsuite/testsuite-script.c	2022-06-14 06:55:57.000000000 +0000
@@ -147,8 +147,9 @@ bool testsuite_script_run(const struct s
 	}
 
 	ret = sieve_interpreter_run(interp, result);
-
 	sieve_interpreter_free(&interp);
+
+	sieve_execute_finish(&exec_env, ret);
 	sieve_execute_deinit(&exec_env);
 
 	return (ret > 0 ||
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/tests/extensions/index/errors/syntax.sieve 1:2.3.19.1+dfsg1-2/pigeonhole/tests/extensions/index/errors/syntax.sieve
--- 1:2.3.16+dfsg1-3/pigeonhole/tests/extensions/index/errors/syntax.sieve	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/tests/extensions/index/errors/syntax.sieve	2022-06-14 06:55:57.000000000 +0000
@@ -18,3 +18,9 @@ if header :index "frop" "to" "ok" {}
 
 # 4: last without index
 if header :last "to" "ok" {}
+
+# 5: index 0
+if header :index 0 "to" "ok" {}
+
+# 6: index 0 last
+if header :index 0 :last "to" "ok" {}
diff -pruN 1:2.3.16+dfsg1-3/pigeonhole/tests/extensions/index/errors.svtest 1:2.3.19.1+dfsg1-2/pigeonhole/tests/extensions/index/errors.svtest
--- 1:2.3.16+dfsg1-3/pigeonhole/tests/extensions/index/errors.svtest	2021-08-06 09:26:46.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/pigeonhole/tests/extensions/index/errors.svtest	2022-06-14 06:55:57.000000000 +0000
@@ -12,7 +12,7 @@ test "Invalid Syntax" {
                 test_fail "compile should have failed";
         }
 
-        if not test_error :count "eq" :comparator "i;ascii-numeric" "5" {
+        if not test_error :count "eq" :comparator "i;ascii-numeric" "7" {
                 test_fail "wrong number of errors reported";
         }
 }
diff -pruN 1:2.3.16+dfsg1-3/src/anvil/connect-limit.c 1:2.3.19.1+dfsg1-2/src/anvil/connect-limit.c
--- 1:2.3.16+dfsg1-3/src/anvil/connect-limit.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/anvil/connect-limit.c	2022-06-14 06:55:03.000000000 +0000
@@ -21,6 +21,9 @@ struct connect_limit {
 	HASH_TABLE(struct ident_pid *, struct ident_pid *) ident_pid_hash;
 };
 
+static void
+connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident);
+
 static unsigned int ident_pid_hash(const struct ident_pid *i)
 {
 	return str_hash(i->ident) ^ i->pid;
@@ -50,6 +53,17 @@ struct connect_limit *connect_limit_init
 void connect_limit_deinit(struct connect_limit **_limit)
 {
 	struct connect_limit *limit = *_limit;
+	struct hash_iterate_context *iter;
+	struct ident_pid *i, *value;
+
+	iter = hash_table_iterate_init(limit->ident_pid_hash);
+	while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) {
+		hash_table_remove(limit->ident_pid_hash, i);
+		for (; i->refcount > 0; i->refcount--)
+			connect_limit_ident_hash_unref(limit, i->ident);
+		i_free(i);
+	}
+	hash_table_iterate_deinit(&iter);
 
 	*_limit = NULL;
 	hash_table_destroy(&limit->ident_hash);
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-cache.c 1:2.3.19.1+dfsg1-2/src/auth/auth-cache.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-cache.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-cache.c	2022-06-14 06:55:03.000000000 +0000
@@ -384,7 +384,7 @@ auth_cache_lookup(struct auth_cache *cac
 	*expired_r = FALSE;
 	*neg_expired_r = FALSE;
 
-	key = auth_request_expand_cache_key(request, key, request->fields.user);
+	key = auth_request_expand_cache_key(request, key, request->fields.translated_username);
 	node = hash_table_lookup(cache->hash, key);
 	if (node == NULL) {
 		cache->miss_count++;
@@ -421,7 +421,6 @@ void auth_cache_insert(struct auth_cache
 {
         struct auth_cache_node *node;
 	size_t data_size, alloc_size, key_len, value_len = strlen(value);
-	const char *cache_username;
 	char *hash_key;
 
 	if (*value == '\0' && cache->neg_ttl_secs == 0) {
@@ -429,15 +428,7 @@ void auth_cache_insert(struct auth_cache
 		return;
 	}
 
-	/* store into cache using the translated username, except if we're doing
-	   a master user login */
-	cache_username = request->fields.user;
-	if (request->fields.translated_username != NULL &&
-	    request->fields.requested_login_user == NULL &&
-	    request->fields.master_user == NULL)
-		cache_username = request->fields.translated_username;
-
-	key = auth_request_expand_cache_key(request, key, cache_username);
+	key = auth_request_expand_cache_key(request, key, request->fields.translated_username);
 	key_len = strlen(key);
 
 	data_size = key_len + 1 + value_len + 1;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-master-connection.c 1:2.3.19.1+dfsg1-2/src/auth/auth-master-connection.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-master-connection.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-master-connection.c	2022-06-14 06:55:03.000000000 +0000
@@ -514,7 +514,6 @@ static void master_input_list_callback(c
 		ctx->auth_request->userdb = userdb;
 		ctx->iter = userdb_blocking_iter_init(ctx->auth_request,
 					master_input_list_callback, ctx);
-		userdb_blocking_iter_next(ctx->iter);
 		return;
 	}
 
@@ -608,7 +607,8 @@ master_input_list(struct auth_master_con
 	if (auth_request->fields.user == NULL)
 		auth_request_set_username_forced(auth_request, "");
 	if (auth_request->fields.service == NULL) {
-		auth_request_import(auth_request, "service", "");
+		if (!auth_request_import(auth_request, "service", ""))
+			i_unreached();
 		i_assert(auth_request->fields.service != NULL);
 	}
 
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-policy.c 1:2.3.19.1+dfsg1-2/src/auth/auth-policy.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-policy.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-policy.c	2022-06-14 06:55:03.000000000 +0000
@@ -170,9 +170,8 @@ void auth_policy_init(void)
 	if (global_auth_settings->debug)
 		http_client_set.debug = 1;
 
-	master_service_ssl_settings_to_iostream_set(master_ssl_set, pool_datastack_create(),
-						    MASTER_SERVICE_SSL_SETTINGS_TYPE_CLIENT,
-						    &ssl_set);
+	master_service_ssl_client_settings_to_iostream_set(master_ssl_set,
+		pool_datastack_create(), &ssl_set);
 	http_client_set.ssl = &ssl_set;
 	http_client_set.event_parent = auth_event;
 	http_client = http_client_init(&http_client_set);
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-request.c 1:2.3.19.1+dfsg1-2/src/auth/auth-request.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-request.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-request.c	2022-06-14 06:55:03.000000000 +0000
@@ -606,6 +606,21 @@ auth_request_want_skip_userdb(struct aut
 	i_unreached();
 }
 
+static const char *
+auth_request_cache_result_to_str(enum auth_request_cache_result result)
+{
+	switch(result) {
+	case AUTH_REQUEST_CACHE_NONE:
+		return "none";
+	case AUTH_REQUEST_CACHE_HIT:
+		return "hit";
+	case AUTH_REQUEST_CACHE_MISS:
+		return "miss";
+	default:
+		i_unreached();
+	}
+}
+
 void auth_request_passdb_lookup_begin(struct auth_request *request)
 {
 	struct event *event;
@@ -614,6 +629,8 @@ void auth_request_passdb_lookup_begin(st
 	i_assert(request->passdb != NULL);
 	i_assert(!request->userdb_lookup);
 
+	request->passdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+
 	name = (request->passdb->set->name[0] != '\0' ?
 		request->passdb->set->name :
 		request->passdb->passdb->iface.name);
@@ -647,6 +664,9 @@ void auth_request_passdb_lookup_end(stru
 		event_create_passthrough(event)->
 		set_name("auth_passdb_request_finished")->
 		add_str("result", passdb_result_to_string(result));
+	if (request->passdb_cache_result != AUTH_REQUEST_CACHE_NONE &&
+	    request->set->cache_ttl != 0 && request->set->cache_size != 0)
+		e->add_str("cache", auth_request_cache_result_to_str(request->passdb_cache_result));
 	e_debug(e->event(), "Finished passdb lookup");
 	event_unref(&event);
 	array_pop_back(&request->authdb_event);
@@ -660,6 +680,8 @@ void auth_request_userdb_lookup_begin(st
 	i_assert(request->userdb != NULL);
 	i_assert(request->userdb_lookup);
 
+	request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE;
+
 	name = (request->userdb->set->name[0] != '\0' ?
 		request->userdb->set->name :
 		request->userdb->userdb->iface->name);
@@ -693,6 +715,9 @@ void auth_request_userdb_lookup_end(stru
 		event_create_passthrough(event)->
 		set_name("auth_userdb_request_finished")->
 		add_str("result", userdb_result_to_string(result));
+	if (request->userdb_cache_result != AUTH_REQUEST_CACHE_NONE &&
+	    request->set->cache_ttl != 0 && request->set->cache_size != 0)
+		e->add_str("cache", auth_request_cache_result_to_str(request->userdb_cache_result));
 	e_debug(e->event(), "Finished userdb lookup");
 	event_unref(&event);
 	array_pop_back(&request->authdb_event);
@@ -1253,11 +1278,14 @@ void auth_request_lookup_credentials_pol
 		if (passdb_cache_lookup_credentials(request, cache_key,
 						    &cache_cred, &cache_scheme,
 						    &result, FALSE)) {
+			request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT;
 			passdb_handle_credentials(
 				result, cache_cred, cache_scheme,
 				auth_request_lookup_credentials_finish,
 				request);
 			return;
+		} else {
+			request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS;
 		}
 	}
 
@@ -1360,6 +1388,7 @@ static bool auth_request_lookup_user_cac
 				  &expired, &neg_expired);
 	if (value == NULL || (expired && !use_expired)) {
 		stats->auth_cache_miss_count++;
+		request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS;
 		e_debug(request->event,
 			value == NULL ? "%suserdb cache miss" :
 			"%suserdb cache expired",
@@ -1367,6 +1396,7 @@ static bool auth_request_lookup_user_cac
 		return FALSE;
 	}
 	stats->auth_cache_hit_count++;
+	request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT;
 	e_debug(request->event,
 		"%suserdb cache hit: %s",
 		auth_request_get_log_prefix_db(request), value);
@@ -1506,7 +1536,7 @@ void auth_request_userdb_callback(enum u
 	if (request->userdb_lookup_tempfailed) {
 		/* no caching */
 	} else if (result != USERDB_RESULT_INTERNAL_FAILURE) {
-		if (!request->userdb_result_from_cache)
+		if (request->userdb_cache_result != AUTH_REQUEST_CACHE_HIT)
 			auth_request_userdb_save_cache(request, result);
 	} else if (passdb_cache != NULL && userdb->cache_key != NULL) {
 		/* lookup failed. if we're looking here only because the
@@ -1534,7 +1564,7 @@ void auth_request_lookup_user(struct aut
 	request->private_callback.userdb = callback;
 	request->user_changed_by_lookup = FALSE;
 	request->userdb_lookup = TRUE;
-	request->userdb_result_from_cache = FALSE;
+	request->userdb_cache_result = AUTH_REQUEST_CACHE_NONE;
 	if (request->fields.userdb_reply == NULL)
 		auth_request_init_userdb_reply(request, TRUE);
 	else {
@@ -1560,9 +1590,11 @@ void auth_request_lookup_user(struct aut
 
 		if (auth_request_lookup_user_cache(request, cache_key,
 						   &result, FALSE)) {
-			request->userdb_result_from_cache = TRUE;
+			request->userdb_cache_result = AUTH_REQUEST_CACHE_HIT;
 			auth_request_userdb_callback(result, request);
 			return;
+		} else {
+			request->userdb_cache_result = AUTH_REQUEST_CACHE_MISS;
 		}
 	}
 
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-request-fields.c 1:2.3.19.1+dfsg1-2/src/auth/auth-request-fields.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-request-fields.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-request-fields.c	2022-06-14 06:55:03.000000000 +0000
@@ -475,6 +475,9 @@ void auth_request_master_user_login_fini
 
 	auth_request_set_username_forced(request,
 					 request->fields.requested_login_user);
+	request->fields.translated_username = request->fields.requested_login_user;
+	event_add_str(request->event, "translated_user",
+		      request->fields.translated_username);
 	request->fields.requested_login_user = NULL;
 	event_field_clear(request->event, "login_user");
 }
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-request.h 1:2.3.19.1+dfsg1-2/src/auth/auth-request.h
--- 1:2.3.16+dfsg1-3/src/auth/auth-request.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-request.h	2022-06-14 06:55:03.000000000 +0000
@@ -34,6 +34,12 @@ enum auth_request_secured {
 	AUTH_REQUEST_SECURED_TLS,
 };
 
+enum auth_request_cache_result {
+	AUTH_REQUEST_CACHE_NONE,
+	AUTH_REQUEST_CACHE_MISS,
+	AUTH_REQUEST_CACHE_HIT,
+};
+
 /* All auth request fields are exported to auth-worker process. */
 struct auth_request_fields {
         /* user contains the user who is being authenticated.
@@ -160,6 +166,9 @@ struct auth_request {
 
 	void *context;
 
+	enum auth_request_cache_result passdb_cache_result;
+	enum auth_request_cache_result userdb_cache_result;
+
 	/* this is a lookup on auth socket (not login socket).
 	   skip any proxying stuff if enabled. */
 	bool auth_only:1;
@@ -202,8 +211,6 @@ struct auth_request {
 	/* userdb_* fields have been set by the passdb lookup, userdb prefetch
 	   will work. */
 	bool userdb_prefetch_set:1;
-	/* userdb lookup's results are from cache */
-	bool userdb_result_from_cache:1;
 	bool stats_sent:1;
 	bool policy_refusal:1;
 	bool policy_processed:1;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-request-handler.c 1:2.3.19.1+dfsg1-2/src/auth/auth-request-handler.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-request-handler.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-request-handler.c	2022-06-14 06:55:03.000000000 +0000
@@ -204,7 +204,8 @@ auth_str_append_extra_fields(struct auth
 					      request->mech_password);
 		}
 		if (fields->master_user != NULL &&
-		    !auth_fields_exists(fields->extra_fields, "master")) {
+		    !auth_fields_exists(fields->extra_fields, "master") &&
+		    *fields->master_user != '\0') {
 			/* the master username needs to be forwarded */
 			auth_str_add_keyvalue(dest, "master",
 					      fields->master_user);
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-worker-server.c 1:2.3.19.1+dfsg1-2/src/auth/auth-worker-server.c
--- 1:2.3.16+dfsg1-3/src/auth/auth-worker-server.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-worker-server.c	2022-06-14 06:55:03.000000000 +0000
@@ -99,7 +99,7 @@ static bool auth_worker_request_send(str
 			"Aborting auth request that was queued for %d secs, "
 			"%d left in queue",
 			age_secs, aqueue_count(worker_request_queue));
-		request->callback(t_strdup_printf(
+		request->callback(conn, t_strdup_printf(
 			"FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE),
 			request->context);
 		return FALSE;
@@ -243,7 +243,7 @@ static void auth_worker_destroy(struct a
 		e_error(conn->event, "Aborted %s request for %s: %s",
 			t_strcut(conn->request->data, '\t'),
 			conn->request->username, reason);
-		conn->request->callback(t_strdup_printf(
+		conn->request->callback(conn, t_strdup_printf(
 				"FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE),
 				conn->request->context);
 	}
@@ -304,7 +304,7 @@ static bool auth_worker_request_handle(s
 		idle_count++;
 	}
 
-	if (!request->callback(line, request->context) && conn->io != NULL) {
+	if (!request->callback(conn, line, request->context) && conn->io != NULL) {
 		conn->timeout_pending_resume = FALSE;
 		timeout_remove(&conn->to);
 		io_remove(&conn->io);
@@ -442,9 +442,8 @@ static void worker_input_resume(struct a
 	worker_input(conn);
 }
 
-struct auth_worker_connection *
-auth_worker_call(pool_t pool, const char *username, const char *data,
-		 auth_worker_callback_t *callback, void *context)
+void auth_worker_call(pool_t pool, const char *username, const char *data,
+		      auth_worker_callback_t *callback, void *context)
 {
 	struct auth_worker_connection *conn;
 	struct auth_worker_request *request;
@@ -474,7 +473,6 @@ auth_worker_call(pool_t pool, const char
 		/* reached the limit, queue the request */
 		aqueue_append(worker_request_queue, &request);
 	}
-	return conn;
 }
 
 void auth_worker_server_resume_input(struct auth_worker_connection *conn)
diff -pruN 1:2.3.16+dfsg1-3/src/auth/auth-worker-server.h 1:2.3.19.1+dfsg1-2/src/auth/auth-worker-server.h
--- 1:2.3.16+dfsg1-3/src/auth/auth-worker-server.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/auth-worker-server.h	2022-06-14 06:55:03.000000000 +0000
@@ -3,12 +3,13 @@
 
 struct auth_request;
 struct auth_stream_reply;
+struct auth_worker_connection;
 
-typedef bool auth_worker_callback_t(const char *reply, void *context);
+typedef bool auth_worker_callback_t(struct auth_worker_connection *conn,
+				    const char *reply, void *context);
 
-struct auth_worker_connection * ATTR_NOWARN_UNUSED_RESULT
-auth_worker_call(pool_t pool, const char *username, const char *data,
-		 auth_worker_callback_t *callback, void *context);
+void auth_worker_call(pool_t pool, const char *username, const char *data,
+		      auth_worker_callback_t *callback, void *context);
 void auth_worker_server_resume_input(struct auth_worker_connection *conn);
 
 void auth_worker_server_init(void);
diff -pruN 1:2.3.16+dfsg1-3/src/auth/crypt-blowfish.c 1:2.3.19.1+dfsg1-2/src/auth/crypt-blowfish.c
--- 1:2.3.16+dfsg1-3/src/auth/crypt-blowfish.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/crypt-blowfish.c	2022-06-14 06:55:03.000000000 +0000
@@ -437,7 +437,8 @@ static void BF_encode(char *dst, const B
 	} while (sptr < end);
 }
 
-static void BF_swap(BF_word *x, int count)
+static void ATTR_UNSIGNED_WRAPS
+BF_swap(BF_word *x, int count)
 {
 	static int endianness_check = 1;
 	char *is_little_endian = (char *)&endianness_check;
@@ -534,7 +535,8 @@ static void BF_swap(BF_word *x, int coun
 		*(ptr - 1) = R; \
 	} while (ptr < &data.ctx.S[3][0xFF]);
 
-static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
+static void ATTR_UNSIGNED_WRAPS
+BF_set_key(const char *key, BF_key expanded, BF_key initial,
     unsigned char flags)
 {
 	const char *ptr = key;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-dict.c 1:2.3.19.1+dfsg1-2/src/auth/db-dict.c
--- 1:2.3.16+dfsg1-3/src/auth/db-dict.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-dict.c	2022-06-14 06:55:03.000000000 +0000
@@ -309,7 +309,6 @@ struct dict_connection *db_dict_init(con
 		i_fatal("dict %s: Empty uri setting", config_path);
 
 	i_zero(&dict_set);
-	dict_set.username = "";
 	dict_set.base_dir = global_auth_settings->base_dir;
 	dict_set.event_parent = auth_event;
 	if (dict_init(conn->set.uri, &dict_set, &conn->dict, &error) < 0)
@@ -409,13 +408,17 @@ static int db_dict_iter_lookup_key_value
 	path = t_str_new(128);
 	str_append(path, DICT_PATH_SHARED);
 
+	struct dict_op_settings set = {
+		.username = iter->auth_request->fields.user,
+	};
+
 	array_foreach_modifiable(&iter->keys, key) {
 		if (!key->used)
 			continue;
 
 		str_truncate(path, strlen(DICT_PATH_SHARED));
 		str_append(path, key->key->key);
-		ret = dict_lookup(iter->conn->dict, iter->pool,
+		ret = dict_lookup(iter->conn->dict, &set, iter->pool,
 				  str_c(path), &key->value, &error);
 		if (ret > 0) {
 			e_debug(authdb_event(iter->auth_request),
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-ldap.c 1:2.3.19.1+dfsg1-2/src/auth/db-ldap.c
--- 1:2.3.16+dfsg1-3/src/auth/db-ldap.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-ldap.c	2022-06-14 06:55:03.000000000 +0000
@@ -49,6 +49,8 @@
 #  define LDAP_OPT_SUCCESS LDAP_SUCCESS
 #endif
 
+#define DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT 3
+
 static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= ";
 
 struct db_ldap_result {
@@ -172,6 +174,11 @@ db_ldap_result_iterate_init_full(struct
 				 struct ldap_request_search *ldap_request,
 				 LDAPMessage *res, bool skip_null_values,
 				 bool iter_dn_values);
+static bool db_ldap_abort_requests(struct ldap_connection *conn,
+				   unsigned int max_count,
+				   unsigned int timeout_secs,
+				   bool error, const char *reason);
+static void db_ldap_request_free(struct ldap_request *request);
 
 static int deref2str(const char *str, int *ref_r)
 {
@@ -399,18 +406,28 @@ static bool db_ldap_request_queue_next(s
 		break;
 	}
 
-	switch (request->type) {
-	case LDAP_REQUEST_TYPE_BIND:
-		ret = db_ldap_request_bind(conn, request);
-		break;
-	case LDAP_REQUEST_TYPE_SEARCH:
-		ret = db_ldap_request_search(conn, request);
-		break;
+	if (request->send_count >= DB_LDAP_REQUEST_MAX_ATTEMPT_COUNT) {
+		/* Enough many times retried. Server just keeps disconnecting
+		   whenever attempting to send the request. */
+		ret = 0;
+	} else {
+		/* clear away any partial results saved before reconnecting */
+		db_ldap_request_free(request);
+
+		switch (request->type) {
+		case LDAP_REQUEST_TYPE_BIND:
+			ret = db_ldap_request_bind(conn, request);
+			break;
+		case LDAP_REQUEST_TYPE_SEARCH:
+			ret = db_ldap_request_search(conn, request);
+			break;
+		}
 	}
 
 	if (ret > 0) {
 		/* success */
 		i_assert(request->msgid != -1);
+		request->send_count++;
 		conn->pending_count++;
 		return TRUE;
 	} else if (ret < 0) {
@@ -425,7 +442,7 @@ static bool db_ldap_request_queue_next(s
 }
 
 static void
-db_ldap_check_hanging(struct ldap_connection *conn, struct ldap_request *request)
+db_ldap_check_hanging(struct ldap_connection *conn)
 {
 	struct ldap_request *first_request;
 	unsigned int count;
@@ -439,8 +456,8 @@ db_ldap_check_hanging(struct ldap_connec
 				       aqueue_idx(conn->request_queue, 0));
 	secs_diff = ioloop_time - first_request->create_time;
 	if (secs_diff > DB_LDAP_REQUEST_LOST_TIMEOUT_SECS) {
-		e_error(authdb_event(request->auth_request),
-			"Connection appears to be hanging, reconnecting");
+		db_ldap_abort_requests(conn, UINT_MAX, 0, TRUE,
+				       "LDAP connection appears to be hanging");
 		ldap_conn_reconnect(conn);
 	}
 }
@@ -453,7 +470,7 @@ void db_ldap_request(struct ldap_connect
 	request->msgid = -1;
 	request->create_time = ioloop_time;
 
-	db_ldap_check_hanging(conn, request);
+	db_ldap_check_hanging(conn);
 
 	aqueue_append(conn->request_queue, &request);
 	(void)db_ldap_request_queue_next(conn);
@@ -496,13 +513,14 @@ static void db_ldap_default_bind_finishe
 	}
 }
 
-static void db_ldap_abort_requests(struct ldap_connection *conn,
+static bool db_ldap_abort_requests(struct ldap_connection *conn,
 				   unsigned int max_count,
 				   unsigned int timeout_secs,
 				   bool error, const char *reason)
 {
 	struct ldap_request *request;
 	time_t diff;
+	bool aborts = FALSE;
 
 	while (aqueue_count(conn->request_queue) > 0 && max_count > 0) {
 		request = array_idx_elem(&conn->request_array,
@@ -528,7 +546,9 @@ static void db_ldap_abort_requests(struc
 		}
 		request->callback(conn, request, NULL);
 		max_count--;
+		aborts = TRUE;
 	}
+	return aborts;
 }
 
 static struct ldap_request *
@@ -841,9 +861,10 @@ db_ldap_handle_request_result(struct lda
 
 	if (idx > 0) {
 		/* see if there are timed out requests */
-		db_ldap_abort_requests(conn, idx,
-				       DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
-				       TRUE, "Request lost");
+		if (db_ldap_abort_requests(conn, idx,
+					   DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
+					   TRUE, "Request lost"))
+			ldap_conn_reconnect(conn);
 	}
 	return TRUE;
 }
@@ -876,7 +897,8 @@ db_ldap_request_free(struct ldap_request
 				if (named_res->result != NULL)
 					db_ldap_result_unref(&named_res->result);
 			}
-			array_clear(&srequest->named_results);
+			array_free(&srequest->named_results);
+			srequest->name_idx = 0;
 		}
 	}
 }
@@ -898,6 +920,7 @@ db_ldap_handle_result(struct ldap_connec
 	request = db_ldap_find_request(conn, msgid, &idx);
 	if (request == NULL) {
 		e_error(conn->event, "Reply with unknown msgid %d", msgid);
+		ldap_conn_reconnect(conn);
 		return;
 	}
 	/* request is allocated from auth_request's pool */
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-ldap.h 1:2.3.19.1+dfsg1-2/src/auth/db-ldap.h
--- 1:2.3.16+dfsg1-3/src/auth/db-ldap.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-ldap.h	2022-06-14 06:55:03.000000000 +0000
@@ -104,6 +104,11 @@ struct ldap_request {
 	/* timestamp when request was created */
 	time_t create_time;
 
+	/* Number of times this request has been sent to LDAP server. This
+	   increases when LDAP gets disconnected and reconnect send the request
+	   again. */
+	unsigned int send_count;
+
 	bool failed:1;
 	/* This is to prevent double logging the result */
 	bool result_logged:1;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-lua.c 1:2.3.19.1+dfsg1-2/src/auth/db-lua.c
--- 1:2.3.16+dfsg1-3/src/auth/db-lua.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-lua.c	2022-06-14 06:55:03.000000000 +0000
@@ -303,14 +303,14 @@ static void auth_lua_push_auth_request(l
 	lua_pushboolean(L, req->fields.skip_password_check);
 	lua_setfield(L, -2, "skip_password_check");
 
-#undef LUA_TABLE_SETBOOL
-#define LUA_TABLE_SETBOOL(field) \
+#undef LUA_TABLE_SET_BOOL
+#define LUA_TABLE_SET_BOOL(field) \
 	lua_pushboolean(L, req->field); \
 	lua_setfield(L, -2, #field);
 
-	LUA_TABLE_SETBOOL(passdbs_seen_user_unknown);
-	LUA_TABLE_SETBOOL(passdbs_seen_internal_failure);
-	LUA_TABLE_SETBOOL(userdbs_seen_internal_failure);
+	LUA_TABLE_SET_BOOL(passdbs_seen_user_unknown);
+	LUA_TABLE_SET_BOOL(passdbs_seen_internal_failure);
+	LUA_TABLE_SET_BOOL(userdbs_seen_internal_failure);
 }
 
 static struct auth_request *
@@ -370,12 +370,12 @@ static luaL_Reg auth_lua_dovecot_auth_me
 
 static void auth_lua_dovecot_auth_register(lua_State *L)
 {
-	dlua_getdovecot(L);
+	dlua_get_dovecot(L);
 	/* Create new table for holding values */
 	lua_newtable(L);
 
 	/* register constants */
-	dlua_setmembers(L, auth_lua_dovecot_auth_values, -1);
+	dlua_set_members(L, auth_lua_dovecot_auth_values, -1);
 
 	/* push new metatable to stack */
 	luaL_newmetatable(L, AUTH_LUA_DOVECOT_AUTH);
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-oauth2.c 1:2.3.19.1+dfsg1-2/src/auth/db-oauth2.c
--- 1:2.3.16+dfsg1-3/src/auth/db-oauth2.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-oauth2.c	2022-06-14 06:55:03.000000000 +0000
@@ -294,9 +294,7 @@ struct db_oauth2 *db_oauth2_init(const c
 
 	if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL) {
 		struct dict_settings dict_set = {
-			.username = "",
 			.base_dir = global_auth_settings->base_dir,
-			.value_type = DICT_DATA_TYPE_STRING,
 			.event_parent = auth_event,
 		};
 		if (dict_init(db->set.local_validation_key_dict, &dict_set,
@@ -397,7 +395,7 @@ db_oauth2_have_all_fields(struct db_oaut
 				ptr = ptr+idx;
 				field = t_strndup(ptr,size);
 				if (str_begins(field, "oauth2:") &&
-				    !auth_fields_exists(req->fields, ptr+8))
+				    !auth_fields_exists(req->fields, ptr+7))
 					return FALSE;
 				ptr = ptr+size;
 			}
diff -pruN 1:2.3.16+dfsg1-3/src/auth/db-passwd-file.c 1:2.3.19.1+dfsg1-2/src/auth/db-passwd-file.c
--- 1:2.3.16+dfsg1-3/src/auth/db-passwd-file.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/db-passwd-file.c	2022-06-14 06:55:03.000000000 +0000
@@ -350,11 +350,12 @@ db_passwd_file_init(const char *path, bo
 
 	if (percents && !db->vars) {
 		/* just extra escaped % chars. remove them. */
-		struct var_expand_table empty_table[1];
+		struct var_expand_table empty_table[1] = {
+			{ .key = '\0' },
+		};
 		string_t *dest;
 		const char *error;
 
-		empty_table[0].key = '\0';
 		dest = t_str_new(256);
 		if (var_expand(dest, path, empty_table, &error) <= 0)
 			i_unreached();
diff -pruN 1:2.3.16+dfsg1-3/src/auth/main.c 1:2.3.19.1+dfsg1-2/src/auth/main.c
--- 1:2.3.16+dfsg1-3/src/auth/main.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/main.c	2022-06-14 06:55:03.000000000 +0000
@@ -370,7 +370,6 @@ int main(int argc, char *argv[])
 {
 	int c;
 	enum master_service_flags service_flags =
-		MASTER_SERVICE_FLAG_USE_SSL_SETTINGS |
 		MASTER_SERVICE_FLAG_NO_SSL_INIT;
 
 	master_service = master_service_init("auth", service_flags, &argc, &argv, "w");
diff -pruN 1:2.3.16+dfsg1-3/src/auth/mech-scram.c 1:2.3.19.1+dfsg1-2/src/auth/mech-scram.c
--- 1:2.3.16+dfsg1-3/src/auth/mech-scram.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/mech-scram.c	2022-06-14 06:55:03.000000000 +0000
@@ -1,4 +1,3 @@
-
 /*
  * SCRAM-SHA-1 SASL authentication, see RFC-5802
  *
@@ -40,7 +39,7 @@ struct scram_auth_request {
 	const char *snonce;
 
 	/* received: */
-	const char *gs2_cbind_flag;
+	const char *gs2_header;
 	const char *cnonce;
 	const char *client_first_message_bare;
 	const char *client_final_message_without_proof;
@@ -51,13 +50,28 @@ struct scram_auth_request {
 	unsigned char *server_key;
 };
 
-static const char *get_scram_server_first(struct scram_auth_request *request,
-					  int iter, const char *salt)
+static const char *
+get_scram_server_first(struct scram_auth_request *request,
+		       int iter, const char *salt)
 {
 	unsigned char snonce[SCRAM_SERVER_NONCE_LEN+1];
 	string_t *str;
 	size_t i;
 
+	/* RFC 5802, Section 7:
+
+	   server-first-message =
+	                     [reserved-mext ","] nonce "," salt ","
+	                     iteration-count ["," extensions]
+
+	   nonce           = "r=" c-nonce [s-nonce]
+
+	   salt            = "s=" base64
+
+	   iteration-count = "i=" posit-number
+	                     ;; A positive number.
+	 */
+
 	random_fill(snonce, sizeof(snonce)-1);
 
 	/* make sure snonce is printable and does not contain ',' */
@@ -69,7 +83,8 @@ static const char *get_scram_server_firs
 	snonce[sizeof(snonce)-1] = '\0';
 	request->snonce = p_strndup(request->pool, snonce, sizeof(snonce));
 
-	str = t_str_new(sizeof(snonce));
+	str = t_str_new(32 + strlen(request->cnonce) + sizeof(snonce) +
+			strlen(salt));
 	str_printfa(str, "r=%s%s,s=%s,i=%d", request->cnonce, request->snonce,
 		    salt, iter);
 	return str_c(str);
@@ -83,6 +98,13 @@ static const char *get_scram_server_fina
 	unsigned char server_signature[hmethod->digest_size];
 	string_t *str;
 
+	/* RFC 5802, Section 3:
+
+	   AuthMessage     := client-first-message-bare + "," +
+	                      server-first-message + "," +
+	                      client-final-message-without-proof
+	   ServerSignature := HMAC(ServerKey, AuthMessage)
+	 */
 	auth_message = t_strconcat(request->client_first_message_bare, ",",
 			request->server_first_message, ",",
 			request->client_final_message_without_proof, NULL);
@@ -91,7 +113,16 @@ static const char *get_scram_server_fina
 	hmac_update(&ctx, auth_message, strlen(auth_message));
 	hmac_final(&ctx, server_signature);
 
-	str = t_str_new(MAX_BASE64_ENCODED_SIZE(sizeof(server_signature)));
+	/* RFC 5802, Section 7:
+
+	   server-final-message = (server-error / verifier)
+	                     ["," extensions]
+
+	   verifier        = "v=" base64
+	                     ;; base-64 encoded ServerSignature.
+
+	 */
+	str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(sizeof(server_signature)));
 	str_append(str, "v=");
 	base64_encode(server_signature, sizeof(server_signature), str);
 
@@ -102,6 +133,14 @@ static const char *scram_unescape_userna
 {
 	string_t *out;
 
+	/* RFC 5802, Section 5.1:
+
+	   The characters ',' or '=' in usernames are sent as '=2C' and '=3D'
+	   respectively.  If the server receives a username that contains '='
+	   not followed by either '2C' or '3D', then the server MUST fail the
+	   authentication.
+	 */
+
 	out = t_str_new(64);
 	for (; *in != '\0'; in++) {
 		i_assert(in[0] != ','); /* strsplit should have caught this */
@@ -121,58 +160,79 @@ static const char *scram_unescape_userna
 	return str_c(out);
 }
 
-static bool parse_scram_client_first(struct scram_auth_request *request,
-				     const unsigned char *data, size_t size,
-				     const char **error_r)
+static bool
+parse_scram_client_first(struct scram_auth_request *request,
+			 const unsigned char *data, size_t size,
+			 const char **error_r)
 {
-	const char *const *fields, *login_username = NULL;
-	const char *gs2_cbind_flag, *authzid, *username, *nonce;
+	const char *login_username = NULL;
+	const char *data_cstr, *p;
+	const char *gs2_header, *gs2_cbind_flag, *authzid;
+	const char *cfm_bare, *username, *nonce;
+	const char *const *fields;
 
-	fields = t_strsplit(t_strndup(data, size), ",");
-	if (str_array_length(fields) < 4) {
-		*error_r = "Invalid initial client message";
-		return FALSE;
-	}
-	gs2_cbind_flag = fields[0];
-	authzid = fields[1];
-	username = fields[2];
-	nonce = fields[3];
+	data_cstr = gs2_header = t_strndup(data, size);
 
-	/* Order of fields is fixed:
+	/* RFC 5802, Section 7:
 
 	   client-first-message = gs2-header client-first-message-bare
 	   gs2-header      = gs2-cbind-flag "," [ authzid ] ","
-	   gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
 
 	   client-first-message-bare = [reserved-mext ","]
-                                       username "," nonce ["," extensions]
-	   reserved-mext   = "m=" 1*(value-char)
-
-	   username        = "n=" saslname
-	   nonce           = "r=" c-nonce [s-nonce]
+	                     username "," nonce ["," extensions]
 
 	   extensions      = attr-val *("," attr-val)
-			       ;; All extensions are optional,
-			       ;; i.e., unrecognized attributes
-			       ;; not defined in this document
-			       ;; MUST be ignored.
+	                     ;; All extensions are optional,
+	                     ;; i.e., unrecognized attributes
+	                     ;; not defined in this document
+	                     ;; MUST be ignored.
 	   attr-val        = ALPHA "=" value
+	 */
+	p = strchr(data_cstr, ',');
+	if (p == NULL) {
+		*error_r = "Invalid initial client message: "
+			"Missing first ',' in GS2 header";
+		return FALSE;
+	}
+	gs2_cbind_flag = t_strdup_until(data_cstr, p);
+	data_cstr = p + 1;
+
+	p = strchr(data_cstr, ',');
+	if (p == NULL) {
+		*error_r = "Invalid initial client message: "
+			"Missing second ',' in GS2 header";
+		return FALSE;
+	}
+	authzid = t_strdup_until(data_cstr, p);
+	gs2_header = t_strdup_until(gs2_header, p + 1);
+	cfm_bare = p + 1;
+
+	fields = t_strsplit(cfm_bare, ",");
+	if (str_array_length(fields) < 2) {
+		*error_r = "Invalid initial client message: "
+			"Missing nonce field";
+		return FALSE;
+	}
+	username = fields[0];
+	nonce = fields[1];
 
-	   */
+	/* gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
+	 */
 	switch (gs2_cbind_flag[0]) {
 	case 'p':
 		*error_r = "Channel binding not supported";
 		return FALSE;
 	case 'y':
 	case 'n':
-		request->gs2_cbind_flag =
-			p_strdup(request->pool, gs2_cbind_flag);
 		break;
 	default:
 		*error_r = "Invalid GS2 header";
 		return FALSE;
 	}
 
+	/* authzid         = "a=" saslname
+	                     ;; Protocol specific.
+	 */
 	if (authzid[0] == '\0')
 		;
 	else if (authzid[0] == 'a' && authzid[1] == '=') {
@@ -187,10 +247,15 @@ static bool parse_scram_client_first(str
 		*error_r = "Invalid authzid field";
 		return FALSE;
 	}
+
+	/* reserved-mext   = "m=" 1*(value-char)
+	 */
 	if (username[0] == 'm') {
 		*error_r = "Mandatory extension(s) not supported";
 		return FALSE;
 	}
+	/* username        = "n=" saslname
+	 */
 	if (username[0] == 'n' && username[1] == '=') {
 		/* Unescape username */
 		username = scram_unescape_username(username + 2);
@@ -211,6 +276,7 @@ static bool parse_scram_client_first(str
 			return FALSE;
 	}
 
+	/* nonce           = "r=" c-nonce [s-nonce] */
 	if (nonce[0] == 'r' && nonce[1] == '=')
 		request->cnonce = p_strdup(request->pool, nonce+2);
 	else {
@@ -218,10 +284,8 @@ static bool parse_scram_client_first(str
 		return FALSE;
 	}
 
-	/* This works only without channel binding support,
-	   otherwise the GS2 header doesn't have a fixed length */
-	request->client_first_message_bare =
-		p_strndup(request->pool, data + 3, size - 3);
+	request->gs2_header = p_strdup(request->pool, gs2_header);
+	request->client_first_message_bare = p_strdup(request->pool, cfm_bare);
 	return TRUE;
 }
 
@@ -235,6 +299,13 @@ static bool verify_credentials(struct sc
 	unsigned char stored_key[hmethod->digest_size];
 	size_t i;
 
+	/* RFC 5802, Section 3:
+
+	   AuthMessage     := client-first-message-bare + "," +
+	                      server-first-message + "," +
+	                      client-final-message-without-proof
+	   ClientSignature := HMAC(StoredKey, AuthMessage)
+	 */
 	auth_message = t_strconcat(request->client_first_message_bare, ",",
 			request->server_first_message, ",",
 			request->client_final_message_without_proof, NULL);
@@ -243,22 +314,26 @@ static bool verify_credentials(struct sc
 	hmac_update(&ctx, auth_message, strlen(auth_message));
 	hmac_final(&ctx, client_signature);
 
+	/* ClientProof     := ClientKey XOR ClientSignature */
 	const unsigned char *proof_data = request->proof->data;
 	for (i = 0; i < sizeof(client_signature); i++)
 		client_key[i] = proof_data[i] ^ client_signature[i];
 
+	/* StoredKey       := H(ClientKey) */
 	hash_method_get_digest(hmethod, client_key, sizeof(client_key),
 			       stored_key);
 
 	safe_memset(client_key, 0, sizeof(client_key));
 	safe_memset(client_signature, 0, sizeof(client_signature));
 
-	return mem_equals_timing_safe(stored_key, request->stored_key, sizeof(stored_key));
+	return mem_equals_timing_safe(stored_key, request->stored_key,
+				      sizeof(stored_key));
 }
 
-static void credentials_callback(enum passdb_result result,
-				 const unsigned char *credentials, size_t size,
-				 struct auth_request *auth_request)
+static void
+credentials_callback(enum passdb_result result,
+		     const unsigned char *credentials, size_t size,
+		     struct auth_request *auth_request)
 {
 	struct scram_auth_request *request =
 		(struct scram_auth_request *)auth_request;
@@ -294,15 +369,24 @@ static void credentials_callback(enum pa
 	}
 }
 
-static bool parse_scram_client_final(struct scram_auth_request *request,
-				     const unsigned char *data, size_t size,
-				     const char **error_r)
+static bool
+parse_scram_client_final(struct scram_auth_request *request,
+			 const unsigned char *data, size_t size,
+			 const char **error_r)
 {
 	const struct hash_method *hmethod = request->hash_method;
 	const char **fields, *cbind_input, *nonce_str;
 	unsigned int field_count;
 	string_t *str;
 
+	/* RFC 5802, Section 7:
+
+	   client-final-message-without-proof =
+	                     channel-binding "," nonce [","
+	                     extensions]
+	   client-final-message =
+	                     client-final-message-without-proof "," proof
+	 */
 	fields = t_strsplit(t_strndup(data, size), ",");
 	field_count = str_array_length(fields);
 	if (field_count < 3) {
@@ -310,8 +394,17 @@ static bool parse_scram_client_final(str
 		return FALSE;
 	}
 
-	cbind_input = t_strconcat(request->gs2_cbind_flag, ",,", NULL);
-	str = t_str_new(MAX_BASE64_ENCODED_SIZE(strlen(cbind_input)));
+	/* channel-binding = "c=" base64
+	                     ;; base64 encoding of cbind-input.
+
+	   cbind-data      = 1*OCTET
+	   cbind-input     = gs2-header [ cbind-data ]
+	                     ;; cbind-data MUST be present for
+	                     ;; gs2-cbind-flag of "p" and MUST be absent
+	                     ;; for "y" or "n".
+	 */
+	cbind_input = request->gs2_header;
+	str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(strlen(cbind_input)));
 	str_append(str, "c=");
 	base64_encode(cbind_input, strlen(cbind_input), str);
 
@@ -320,12 +413,19 @@ static bool parse_scram_client_final(str
 		return FALSE;
 	}
 
+	/* nonce           = "r=" c-nonce [s-nonce]
+	                     ;; Second part provided by server.
+	   c-nonce         = printable
+	   s-nonce         = printable
+	 */
 	nonce_str = t_strconcat("r=", request->cnonce, request->snonce, NULL);
 	if (strcmp(fields[1], nonce_str) != 0) {
 		*error_r = "Wrong nonce";
 		return FALSE;
 	}
 
+	/* proof           = "p=" base64
+	 */
 	if (fields[field_count-1][0] == 'p') {
 		size_t len = strlen(&fields[field_count-1][2]);
 
diff -pruN 1:2.3.16+dfsg1-3/src/auth/passdb-blocking.c 1:2.3.19.1+dfsg1-2/src/auth/passdb-blocking.c
--- 1:2.3.16+dfsg1-3/src/auth/passdb-blocking.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/passdb-blocking.c	2022-06-14 06:55:03.000000000 +0000
@@ -74,7 +74,8 @@ passdb_blocking_auth_worker_reply_parse(
 }
 
 static bool
-verify_plain_callback(const char *reply, void *context)
+verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+		      const char *reply, void *context)
 {
 	struct auth_request *request = context;
 	enum passdb_result result;
@@ -100,7 +101,9 @@ void passdb_blocking_verify_plain(struct
 			 verify_plain_callback, request);
 }
 
-static bool lookup_credentials_callback(const char *reply, void *context)
+static bool
+lookup_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+			    const char *reply, void *context)
 {
 	struct auth_request *request = context;
 	enum passdb_result result;
@@ -141,7 +144,8 @@ void passdb_blocking_lookup_credentials(
 }
 
 static bool
-set_credentials_callback(const char *reply, void *context)
+set_credentials_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+			 const char *reply, void *context)
 {
 	struct auth_request *request = context;
 	bool success;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/passdb-cache.c 1:2.3.19.1+dfsg1-2/src/auth/passdb-cache.c
--- 1:2.3.16+dfsg1-3/src/auth/passdb-cache.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/passdb-cache.c	2022-06-14 06:55:03.000000000 +0000
@@ -36,6 +36,8 @@ passdb_cache_lookup(struct auth_request
 	const char *value;
 	bool expired;
 
+	request->passdb_cache_result = AUTH_REQUEST_CACHE_MISS;
+
 	/* value = password \t ... */
 	value = auth_cache_lookup(passdb_cache, request, key, node_r,
 				  &expired, neg_expired_r);
@@ -48,17 +50,22 @@ passdb_cache_lookup(struct auth_request
 	}
 	stats->auth_cache_hit_count++;
 	passdb_cache_log_hit(request, value);
+	request->passdb_cache_result = AUTH_REQUEST_CACHE_HIT;
 
 	*value_r = value;
 	return TRUE;
 }
 
-static bool passdb_cache_verify_plain_callback(const char *reply, void *context)
+static bool
+passdb_cache_verify_plain_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+				   const char *reply, void *context)
 {
 	struct auth_request *request = context;
 	enum passdb_result result;
 
 	result = passdb_blocking_auth_worker_reply_parse(request, reply);
+	if (result != PASSDB_RESULT_OK)
+		auth_fields_rollback(request->fields.extra_fields);
 	auth_request_verify_plain_callback_finish(result, request);
 	auth_request_unref(&request);
 	return TRUE;
@@ -110,6 +117,10 @@ bool passdb_cache_verify_plain(struct au
 		e_debug(authdb_event(request), "cache: "
 			"validating password on worker");
 		auth_request_ref(request);
+		/* Save the extra fields already here, and take a snapshot.
+		   If verification fails, roll back fields. */
+		auth_request_set_fields(request, list + 1, NULL);
+		auth_fields_snapshot(request->fields.extra_fields);
 		auth_worker_call(request->pool, request->fields.user, str_c(str),
 				 passdb_cache_verify_plain_callback, request);
 		return TRUE;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/userdb-blocking.c 1:2.3.19.1+dfsg1-2/src/auth/userdb-blocking.c
--- 1:2.3.16+dfsg1-3/src/auth/userdb-blocking.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/userdb-blocking.c	2022-06-14 06:55:03.000000000 +0000
@@ -14,7 +14,8 @@ struct blocking_userdb_iterate_context {
 	bool destroyed;
 };
 
-static bool user_callback(const char *reply, void *context)
+static bool user_callback(struct auth_worker_connection *conn ATTR_UNUSED,
+			  const char *reply, void *context)
 {
 	struct auth_request *request = context;
 	enum userdb_result result;
@@ -70,10 +71,13 @@ void userdb_blocking_lookup(struct auth_
 			 str_c(str), user_callback, request);
 }
 
-static bool iter_callback(const char *reply, void *context)
+static bool iter_callback(struct auth_worker_connection *conn,
+			  const char *reply, void *context)
 {
 	struct blocking_userdb_iterate_context *ctx = context;
 
+	ctx->conn = conn;
+
 	if (str_begins(reply, "*\t")) {
 		if (ctx->destroyed)
 			return TRUE;
@@ -107,8 +111,8 @@ userdb_blocking_iter_init(struct auth_re
 	ctx->ctx.context = context;
 
 	auth_request_ref(request);
-	ctx->conn = auth_worker_call(request->pool, "*",
-				     str_c(str), iter_callback, ctx);
+	auth_worker_call(request->pool, "*",
+			 str_c(str), iter_callback, ctx);
 	return &ctx->ctx;
 }
 
@@ -117,6 +121,8 @@ void userdb_blocking_iter_next(struct us
 	struct blocking_userdb_iterate_context *ctx =
 		(struct blocking_userdb_iterate_context *)_ctx;
 
+	i_assert(ctx->conn != NULL);
+
 	ctx->next = TRUE;
 	auth_worker_server_resume_input(ctx->conn);
 }
@@ -132,6 +138,7 @@ int userdb_blocking_iter_deinit(struct u
 	/* iter_callback() may still be called */
 	ctx->destroyed = TRUE;
 
-	auth_worker_server_resume_input(ctx->conn);
+	if (ctx->conn != NULL)
+		auth_worker_server_resume_input(ctx->conn);
 	return ret;
 }
diff -pruN 1:2.3.16+dfsg1-3/src/auth/userdb-dict.c 1:2.3.19.1+dfsg1-2/src/auth/userdb-dict.c
--- 1:2.3.16+dfsg1-3/src/auth/userdb-dict.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/userdb-dict.c	2022-06-14 06:55:03.000000000 +0000
@@ -118,7 +118,10 @@ userdb_dict_iterate_init(struct auth_req
 	ctx->key_prefix = p_strdup(auth_request->pool, str_c(path));
 	ctx->key_prefix_len = strlen(ctx->key_prefix);
 
-	ctx->iter = dict_iterate_init(module->conn->dict, ctx->key_prefix, 0);
+	struct dict_op_settings set = {
+		.username = auth_request->fields.user,
+	};
+	ctx->iter = dict_iterate_init(module->conn->dict, &set, ctx->key_prefix, 0);
 	e_debug(authdb_event(auth_request),
 		"iterate: prefix=%s", ctx->key_prefix);
 	return &ctx->ctx;
diff -pruN 1:2.3.16+dfsg1-3/src/auth/userdb-ldap.c 1:2.3.19.1+dfsg1-2/src/auth/userdb-ldap.c
--- 1:2.3.16+dfsg1-3/src/auth/userdb-ldap.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/auth/userdb-ldap.c	2022-06-14 06:55:03.000000000 +0000
@@ -36,7 +36,7 @@ struct ldap_userdb_iterate_context {
 	struct userdb_iter_ldap_request request;
 	pool_t pool;
 	struct ldap_connection *conn;
-	bool continued, in_callback;
+	bool continued, in_callback, deinitialized;
 };
 
 static void
@@ -166,10 +166,15 @@ static void userdb_ldap_iterate_callback
 	if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
 		if (res == NULL)
 			ctx->ctx.failed = TRUE;
-		ctx->ctx.callback(NULL, ctx->ctx.context);
+		if (!ctx->deinitialized)
+			ctx->ctx.callback(NULL, ctx->ctx.context);
+		auth_request_unref(&request->auth_request);
 		return;
 	}
 
+	if (ctx->deinitialized)
+		return;
+
 	/* the iteration can take a while. reset the request's create time so
 	   it won't be aborted while it's still running */
 	request->create_time = ioloop_time;
@@ -267,7 +272,7 @@ static int userdb_ldap_iterate_deinit(st
 	int ret = _ctx->failed ? -1 : 0;
 
 	db_ldap_enable_input(ctx->conn, TRUE);
-	auth_request_unref(&ctx->request.request.request.auth_request);
+	ctx->deinitialized = TRUE;
 	return ret;
 }
 
diff -pruN 1:2.3.16+dfsg1-3/src/config/all-settings.c 1:2.3.19.1+dfsg1-2/src/config/all-settings.c
--- 1:2.3.16+dfsg1-3/src/config/all-settings.c	2021-08-06 09:26:38.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/all-settings.c	2022-06-14 06:55:49.000000000 +0000
@@ -17,6 +17,7 @@
 #include "unichar.h"
 #include "hash-method.h"
 #include "settings-parser.h"
+#include "message-header-parser.h"
 #include "all-settings.h"
 #include <stddef.h>
 #include <unistd.h>
@@ -82,17 +83,6 @@ struct mail_storage_settings {
 	const char *hostname;
 	const char *recipient_delimiter;
 
-	const char *ssl_client_ca_file;
-	const char *ssl_client_ca_dir;
-	const char *ssl_client_cert;
-	const char *ssl_client_key;
-	const char *ssl_cipher_list;
-	const char *ssl_cipher_suites;
-	const char *ssl_curve_list;
-	const char *ssl_min_protocol;
-	const char *ssl_crypto_device;
-	bool ssl_client_require_valid_cert;
-	bool verbose_ssl;
 	const char *mail_attachment_detection_options;
 
 	enum file_lock_method parsed_lock_method;
@@ -361,19 +351,14 @@ struct service_settings {
 ARRAY_DEFINE_TYPE(service_settings, struct service_settings *);
 /* ../../src/lib-master/master-service-ssl-settings.h */
 extern const struct setting_parser_info master_service_ssl_setting_parser_info;
+extern const struct setting_parser_info master_service_ssl_server_setting_parser_info;
 struct master_service_ssl_settings {
 	const char *ssl;
 	const char *ssl_ca;
-	const char *ssl_cert;
-	const char *ssl_alt_cert;
-	const char *ssl_key;
-	const char *ssl_alt_key;
-	const char *ssl_key_password;
 	const char *ssl_client_ca_file;
 	const char *ssl_client_ca_dir;
 	const char *ssl_client_cert;
 	const char *ssl_client_key;
-	const char *ssl_dh;
 	const char *ssl_cipher_list;
 	const char *ssl_cipher_suites;
 	const char *ssl_curve_list;
@@ -394,6 +379,14 @@ struct master_service_ssl_settings {
 		bool tickets;
 	} parsed_opts;
 };
+struct master_service_ssl_server_settings {
+	const char *ssl_cert;
+	const char *ssl_alt_cert;
+	const char *ssl_key;
+	const char *ssl_alt_key;
+	const char *ssl_key_password;
+	const char *ssl_dh;
+};
 /* ../../src/lib-master/master-service-settings.h */
 extern const struct setting_parser_info master_service_setting_parser_info;
 struct master_service_settings {
@@ -408,6 +401,7 @@ struct master_service_settings {
 	const char *log_timestamp;
 	const char *log_debug;
 	const char *log_core_filter;
+	const char *process_shutdown_filter;
 	const char *syslog_facility;
 	const char *import_environment;
 	const char *stats_writer_socket_path;
@@ -457,6 +451,25 @@ struct dict_ldap_settings {
 extern const struct setting_parser_info mailbox_setting_parser_info;
 extern const struct setting_parser_info mail_namespace_setting_parser_info;
 /* <settings checks> */
+static bool mail_cache_fields_parse(const char *key, const char *value,
+				    const char **error_r)
+{
+	const char *const *arr;
+
+	for (arr = t_strsplit_spaces(value, " ,"); *arr != NULL; arr++) {
+		const char *name = *arr;
+
+		if (strncasecmp(name, "hdr.", 4) == 0 &&
+		    !message_header_name_is_valid(name+4)) {
+			*error_r = t_strdup_printf(
+				"Invalid %s: %s is not a valid header name",
+				key, name);
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
 static bool mail_storage_settings_check(void *_set, pool_t pool,
 					const char **error_r)
 {
@@ -553,15 +566,6 @@ static bool mail_storage_settings_check(
 		return FALSE;
 	}
 	hash_format_deinit_free(&format);
-#ifndef CONFIG_BINARY
-	if (*set->ssl_client_ca_dir != '\0' &&
-	    access(set->ssl_client_ca_dir, X_OK) < 0) {
-		*error_r = t_strdup_printf(
-			"ssl_client_ca_dir: access(%s) failed: %m",
-			set->ssl_client_ca_dir);
-		return FALSE;
-	}
-#endif
 
 	// FIXME: check set->mail_server_admin syntax (RFC 5464, Section 6.2.2)
 
@@ -603,6 +607,15 @@ static bool mail_storage_settings_check(
 		set->parsed_mail_attachment_content_type_filter = array_front(&content_types);
 	}
 
+	if (!mail_cache_fields_parse("mail_cache_fields",
+				     set->mail_cache_fields, error_r))
+		return FALSE;
+	if (!mail_cache_fields_parse("mail_always_cache_fields",
+				     set->mail_always_cache_fields, error_r))
+		return FALSE;
+	if (!mail_cache_fields_parse("mail_never_cache_fields",
+				     set->mail_never_cache_fields, error_r))
+		return FALSE;
 	return TRUE;
 }
 
@@ -820,10 +833,10 @@ static const struct setting_define mail_
 	DEF(STR, mail_never_cache_fields),
 	DEF(STR, mail_server_comment),
 	DEF(STR, mail_server_admin),
-	DEF(UINT, mail_cache_min_mail_count),
 	DEF(TIME_HIDDEN, mail_cache_unaccessed_field_drop),
 	DEF(SIZE_HIDDEN, mail_cache_record_max_size),
 	DEF(SIZE_HIDDEN, mail_cache_max_size),
+	DEF(UINT_HIDDEN, mail_cache_min_mail_count),
 	DEF(SIZE_HIDDEN, mail_cache_purge_min_size),
 	DEF(UINT_HIDDEN, mail_cache_purge_delete_percentage),
 	DEF(UINT_HIDDEN, mail_cache_purge_continued_percentage),
@@ -859,18 +872,6 @@ static const struct setting_define mail_
 	DEF(STR, hostname),
 	DEF(STR, recipient_delimiter),
 
-	DEF(STR, ssl_client_ca_file),
-	DEF(STR, ssl_client_ca_dir),
-	DEF(STR, ssl_client_cert),
-	DEF(STR, ssl_client_key),
-	DEF(STR, ssl_cipher_list),
-	DEF(STR, ssl_cipher_suites),
-	DEF(STR, ssl_curve_list),
-	DEF(STR, ssl_min_protocol),
-	DEF(STR, ssl_crypto_device),
-	DEF(BOOL, ssl_client_require_valid_cert),
-	DEF(BOOL, verbose_ssl),
-
 	SETTING_DEFINE_LIST_END
 };
 const struct mail_storage_settings mail_storage_default_settings = {
@@ -925,19 +926,6 @@ const struct mail_storage_settings mail_
 
 	.hostname = "",
 	.recipient_delimiter = "+",
-
-	/* Keep synced with master-service-ssl-settings */
-	.ssl_client_ca_file = "",
-	.ssl_client_ca_dir = "",
-	.ssl_client_cert = "",
-	.ssl_client_key = "",
-	.ssl_cipher_list = "ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH",
-	.ssl_cipher_suites = "", /* Use TLS library provided value */
-	.ssl_curve_list = "",
-	.ssl_min_protocol = "TLSv1.2",
-	.ssl_crypto_device = "",
-	.ssl_client_require_valid_cert = TRUE,
-	.verbose_ssl = FALSE,
 };
 const struct setting_parser_info mail_storage_setting_parser_info = {
 	.module_name = "mail",
@@ -1600,15 +1588,25 @@ struct submission_settings {
 };
 /* ../../src/submission-login/submission-login-settings.h */
 extern const struct setting_parser_info *submission_login_setting_roots[];
+/* <settings checks> */
+enum submission_login_client_workarounds {
+	SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL	= BIT(0),
+	SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND		= BIT(1),
+};
+/* </settings checks> */
 struct submission_login_settings {
 	const char *hostname;
 
 	/* submission: */
 	uoff_t submission_max_mail_size;
+	const char *submission_client_workarounds;
 	const char *submission_backend_capabilities;
+
+	enum submission_login_client_workarounds parsed_workarounds;
 };
 /* ../../src/stats/stats-settings.h */
 extern const struct setting_parser_info stats_setting_parser_info;
+extern const struct setting_parser_info stats_metric_setting_parser_info;
 /* <settings checks> */
 /*
  * We allow a selection of a timestamp format.
@@ -1688,6 +1686,8 @@ struct stats_metric_settings_group_by {
 	struct stats_metric_settings_bucket_range *ranges;
 };
 /* </settings checks> */
+#define STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE \
+	"name hostname timestamps categories fields"
 struct stats_exporter_settings {
 	const char *name;
 	const char *transport;
@@ -1835,6 +1835,7 @@ struct login_settings {
 	unsigned int login_proxy_timeout;
 	unsigned int login_proxy_max_reconnects;
 	unsigned int login_proxy_max_disconnect_delay;
+	const char *login_proxy_rawlog_dir;
 	const char *director_username_hash;
 
 	bool auth_ssl_require_client_cert;
@@ -1870,6 +1871,7 @@ struct lmtp_settings {
 	bool lmtp_save_to_detail_mailbox;
 	bool lmtp_rcpt_check_quota;
 	bool lmtp_add_received_header;
+	bool lmtp_verbose_replies;
 	unsigned int lmtp_user_concurrency_limit;
 	const char *lmtp_hdr_delivery_address;
 	const char *lmtp_rawlog_dir;
@@ -2185,12 +2187,18 @@ struct submission_client_workaround_list
 	enum submission_client_workarounds num;
 };
 
+/* These definitions need to be kept in sync with equivalent definitions present
+   in src/submission-login/submission-login-settings.c. Workarounds that are not
+   relevant to the submission service are defined as 0 here to prevent "Unknown
+   workaround" errors below. */
 static const struct submission_client_workaround_list
 submission_client_workaround_list[] = {
 	{ "whitespace-before-path",
 	  SUBMISSION_WORKAROUND_WHITESPACE_BEFORE_PATH },
 	{ "mailbox-for-path",
 	  SUBMISSION_WORKAROUND_MAILBOX_FOR_PATH },
+	{ "implicit-auth-external", 0 },
+	{ "exotic-backend", 0 },
 	{ NULL, 0 }
 };
 
@@ -2379,6 +2387,71 @@ static buffer_t submission_login_inet_li
 };
 
 /* </settings checks> */
+/* <settings checks> */
+struct submission_login_client_workaround_list {
+	const char *name;
+	enum submission_login_client_workarounds num;
+};
+
+/* These definitions need to be kept in sync with equivalent definitions present
+   in src/submission/submission-settings.c. Workarounds that are not relevant
+   to the submission-login service are defined as 0 here to prevent "Unknown
+   workaround" errors below. */
+static const struct submission_login_client_workaround_list
+submission_login_client_workaround_list[] = {
+	{ "whitespace-before-path", 0},
+	{ "mailbox-for-path", 0 },
+	{ "implicit-auth-external",
+	  SUBMISSION_LOGIN_WORKAROUND_IMPLICIT_AUTH_EXTERNAL },
+	{ "exotic-backend",
+	  SUBMISSION_LOGIN_WORKAROUND_EXOTIC_BACKEND },
+	{ NULL, 0 }
+};
+
+static int
+submission_login_settings_parse_workarounds(
+	struct submission_login_settings *set, const char **error_r)
+{
+	enum submission_login_client_workarounds client_workarounds = 0;
+        const struct submission_login_client_workaround_list *list;
+	const char *const *str;
+
+        str = t_strsplit_spaces(set->submission_client_workarounds, " ,");
+	for (; *str != NULL; str++) {
+		list = submission_login_client_workaround_list;
+		for (; list->name != NULL; list++) {
+			if (strcasecmp(*str, list->name) == 0) {
+				client_workarounds |= list->num;
+				break;
+			}
+		}
+		if (list->name == NULL) {
+			*error_r = t_strdup_printf(
+				"submission_client_workarounds: "
+				"Unknown workaround: %s", *str);
+			return -1;
+		}
+	}
+	set->parsed_workarounds = client_workarounds;
+	return 0;
+}
+
+static bool
+submission_login_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+				const char **error_r)
+{
+	struct submission_login_settings *set = _set;
+
+	if (submission_login_settings_parse_workarounds(set, error_r) < 0)
+		return FALSE;
+
+#ifndef CONFIG_BINARY
+	if (*set->hostname == '\0')
+		set->hostname = p_strdup(pool, my_hostdomain());
+#endif
+	return TRUE;
+}
+/* </settings checks> */
 struct service_settings submission_login_service_settings = {
 	.name = "submission-login",
 	.protocol = "submission",
@@ -2411,6 +2484,7 @@ static const struct setting_define submi
 	DEF(STR, hostname),
 
 	DEF(SIZE, submission_max_mail_size),
+	DEF(STR, submission_client_workarounds),
 	DEF(STR, submission_backend_capabilities),
 
 	SETTING_DEFINE_LIST_END
@@ -2419,6 +2493,7 @@ static const struct submission_login_set
 	.hostname = "",
 
 	.submission_max_mail_size = 0,
+	.submission_client_workarounds = "",
 	.submission_backend_capabilities = NULL
 };
 static const struct setting_parser_info *submission_login_setting_dependencies[] = {
@@ -2434,9 +2509,7 @@ const struct setting_parser_info submiss
 	.struct_size = sizeof(struct submission_login_settings),
 	.parent_offset = SIZE_MAX,
 
-#ifndef CONFIG_BINARY
 	.check_func = submission_login_settings_check,
-#endif
 	.dependencies = submission_login_setting_dependencies
 };
 const struct setting_parser_info *submission_login_setting_roots[] = {
@@ -2455,10 +2528,12 @@ extern const struct setting_parser_info
 static struct file_listener_settings stats_unix_listeners_array[] = {
 	{ "stats-reader", 0600, "", "" },
 	{ "stats-writer", 0660, "", "$default_internal_group" },
+	{ "login/stats-writer", 0600, "$default_login_user", "" },
 };
 static struct file_listener_settings *stats_unix_listeners[] = {
 	&stats_unix_listeners_array[0],
 	&stats_unix_listeners_array[1],
+	&stats_unix_listeners_array[2],
 };
 static buffer_t stats_unix_listeners_buf = {
 	{ { stats_unix_listeners, sizeof(stats_unix_listeners) } }
@@ -2898,7 +2973,7 @@ static const struct stats_metric_setting
 	.filter = "",
 	.exporter = "",
 	.group_by = "",
-	.exporter_include = "name hostname timestamps categories fields",
+	.exporter_include = STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE,
 	.description = "",
 };
 const struct setting_parser_info stats_metric_setting_parser_info = {
@@ -4094,6 +4169,7 @@ static const struct setting_define login
 	DEF(TIME_MSECS, login_proxy_timeout),
 	DEF(UINT, login_proxy_max_reconnects),
 	DEF(TIME, login_proxy_max_disconnect_delay),
+	DEF(STR, login_proxy_rawlog_dir),
 	DEF(STR, director_username_hash),
 
 	DEF(BOOL, auth_ssl_require_client_cert),
@@ -4121,7 +4197,8 @@ static const struct login_settings login
 	.login_proxy_timeout = 30*1000,
 	.login_proxy_max_reconnects = 3,
 	.login_proxy_max_disconnect_delay = 0,
-	.director_username_hash = "%u",
+	.login_proxy_rawlog_dir = "",
+	.director_username_hash = "%Lu",
 
 	.auth_ssl_require_client_cert = FALSE,
 	.auth_ssl_username_from_cert = FALSE,
@@ -4295,6 +4372,7 @@ static const struct setting_define lmtp_
 	DEF(BOOL, lmtp_save_to_detail_mailbox),
 	DEF(BOOL, lmtp_rcpt_check_quota),
 	DEF(BOOL, lmtp_add_received_header),
+	DEF(BOOL, lmtp_verbose_replies),
 	DEF(UINT, lmtp_user_concurrency_limit),
 	DEF(ENUM, lmtp_hdr_delivery_address),
 	DEF(STR_VARS, lmtp_rawlog_dir),
@@ -4315,6 +4393,7 @@ static const struct lmtp_settings lmtp_d
 	.lmtp_save_to_detail_mailbox = FALSE,
 	.lmtp_rcpt_check_quota = FALSE,
 	.lmtp_add_received_header = TRUE,
+	.lmtp_verbose_replies = FALSE,
 	.lmtp_user_concurrency_limit = 0,
 	.lmtp_hdr_delivery_address = "final:none:original",
 	.lmtp_rawlog_dir = "",
@@ -5993,6 +6072,7 @@ buffer_t config_all_services_buf = {
 const struct setting_parser_info *all_default_roots[] = {
 	&master_service_setting_parser_info,
 	&master_service_ssl_setting_parser_info,
+	&master_service_ssl_server_setting_parser_info,
 	&smtp_submit_setting_parser_info,
 	&aggregator_setting_parser_info, 
 	&auth_setting_parser_info, 
diff -pruN 1:2.3.16+dfsg1-3/src/config/config-connection.c 1:2.3.19.1+dfsg1-2/src/config/config-connection.c
--- 1:2.3.16+dfsg1-3/src/config/config-connection.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/config-connection.c	2022-06-14 06:55:03.000000000 +0000
@@ -72,10 +72,12 @@ static int config_connection_request(str
 	struct config_filter filter;
 	const char *path, *error, *module, *const *wanted_modules;
 	ARRAY(const char *) modules;
+	ARRAY(const char *) exclude_settings;
 	bool is_master = FALSE;
 
 	/* [<args>] */
 	t_array_init(&modules, 4);
+	t_array_init(&exclude_settings, 4);
 	i_zero(&filter);
 	for (; *args != NULL; args++) {
 		if (str_begins(*args, "service="))
@@ -85,6 +87,9 @@ static int config_connection_request(str
 			if (strcmp(module, "master") == 0)
 				is_master = TRUE;
 			array_push_back(&modules, &module);
+		} else if (str_begins(*args, "exclude=")) {
+			const char *value = *args + 8;
+			array_push_back(&exclude_settings, &value);
 		} else if (str_begins(*args, "lname="))
 			filter.local_name = *args + 6;
 		else if (str_begins(*args, "lip=")) {
@@ -104,6 +109,7 @@ static int config_connection_request(str
 	array_append_zero(&modules);
 	wanted_modules = array_count(&modules) == 1 ? NULL :
 		array_front(&modules);
+	array_append_zero(&exclude_settings);
 
 	if (is_master) {
 		/* master reads configuration only when reloading settings */
@@ -118,7 +124,10 @@ static int config_connection_request(str
 
 	o_stream_cork(conn->output);
 
-	ctx = config_export_init(wanted_modules, CONFIG_DUMP_SCOPE_SET, 0,
+	ctx = config_export_init(wanted_modules,
+				 array_count(&exclude_settings) == 1 ? NULL :
+				 array_front(&exclude_settings),
+				 CONFIG_DUMP_SCOPE_SET, 0,
 				 config_request_output, conn->output);
 	config_export_by_filter(ctx, &filter);
 	config_export_get_output(ctx, &output);
diff -pruN 1:2.3.16+dfsg1-3/src/config/config-request.c 1:2.3.19.1+dfsg1-2/src/config/config-request.c
--- 1:2.3.16+dfsg1-3/src/config/config-request.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/config-request.c	2022-06-14 06:55:03.000000000 +0000
@@ -23,6 +23,7 @@ struct config_export_context {
 	void *context;
 
 	const char *const *modules;
+	const char *const *exclude_settings;
 	enum config_dump_flags flags;
 	const struct config_module_parser *parsers;
 	struct config_module_parser *dup_parsers;
@@ -206,9 +207,9 @@ setting_export_section_name(string_t *st
 	if (*name == NULL || **name == '\0') {
 		/* no name, this one isn't unique. use the index. */
 		str_printfa(str, "%u", idx);
-	} else {
+	} else T_BEGIN {
 		str_append(str, settings_section_escape(*name));
-	}
+	} T_END;
 }
 
 static void
@@ -227,6 +228,10 @@ settings_export(struct config_export_con
 	bool dump, dump_default = FALSE;
 
 	for (def = info->defines; def->key != NULL; def++) {
+		if (ctx->exclude_settings != NULL &&
+		    str_array_find(ctx->exclude_settings, def->key))
+			continue;
+
 		value = CONST_PTR_OFFSET(set, def->offset);
 		default_value = info->defaults == NULL ? NULL :
 			CONST_PTR_OFFSET(info->defaults, def->offset);
@@ -373,7 +378,9 @@ settings_export(struct config_export_con
 }
 
 struct config_export_context *
-config_export_init(const char *const *modules, enum config_dump_scope scope,
+config_export_init(const char *const *modules,
+		   const char *const *exclude_settings,
+		   enum config_dump_scope scope,
 		   enum config_dump_flags flags,
 		   config_request_callback_t *callback, void *context)
 {
@@ -385,6 +392,8 @@ config_export_init(const char *const *mo
 	ctx->pool = pool;
 
 	ctx->modules = modules == NULL ? NULL : p_strarray_dup(pool, modules);
+	ctx->exclude_settings = exclude_settings == NULL ? NULL :
+		p_strarray_dup(pool, exclude_settings);
 	ctx->flags = flags;
 	ctx->callback = callback;
 	ctx->context = context;
diff -pruN 1:2.3.16+dfsg1-3/src/config/config-request.h 1:2.3.19.1+dfsg1-2/src/config/config-request.h
--- 1:2.3.16+dfsg1-3/src/config/config-request.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/config-request.h	2022-06-14 06:55:03.000000000 +0000
@@ -42,7 +42,9 @@ bool config_export_type(string_t *str, c
 			enum setting_type type, bool dump_default,
 			bool *dump_r) ATTR_NULL(3);
 struct config_export_context *
-config_export_init(const char *const *modules, enum config_dump_scope scope,
+config_export_init(const char *const *modules,
+		   const char *const *exclude_settings,
+		   enum config_dump_scope scope,
 		   enum config_dump_flags flags,
 		   config_request_callback_t *callback, void *context)
 	ATTR_NULL(1, 5);
diff -pruN 1:2.3.16+dfsg1-3/src/config/doveconf.c 1:2.3.19.1+dfsg1-2/src/config/doveconf.c
--- 1:2.3.16+dfsg1-3/src/config/doveconf.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/doveconf.c	2022-06-14 06:55:03.000000000 +0000
@@ -77,8 +77,8 @@ config_request_get_strings(const char *k
 	case CONFIG_KEY_UNIQUE_KEY:
 		p = strrchr(key, '/');
 		i_assert(p != NULL);
-		value = p_strdup_printf(ctx->pool, "%s/"UNIQUE_KEY_SUFFIX"%s=%s",
-					t_strdup_until(key, p), p + 1, value);
+		value = p_strdup_printf(ctx->pool, "%.*s/"UNIQUE_KEY_SUFFIX"%s=%s",
+					(int)(p - key), key, p + 1, value);
 		break;
 	case CONFIG_KEY_ERROR:
 		value = p_strdup(ctx->pool, value);
@@ -154,7 +154,7 @@ config_dump_human_init(const char *const
 		flags |= CONFIG_DUMP_FLAG_CHECK_SETTINGS;
 	if (in_section)
 		flags |= CONFIG_DUMP_FLAG_IN_SECTION;
-	ctx->export_ctx = config_export_init(modules, scope, flags,
+	ctx->export_ctx = config_export_init(modules, NULL, scope, flags,
 					     config_request_get_strings, ctx);
 	return ctx;
 }
@@ -998,7 +998,7 @@ int main(int argc, char *argv[])
 	if (simple_output) {
 		struct config_export_context *ctx;
 
-		ctx = config_export_init(wanted_modules, scope,
+		ctx = config_export_init(wanted_modules, NULL, scope,
 					 CONFIG_DUMP_FLAG_CHECK_SETTINGS,
 					 config_request_simple_stdout,
 					 setting_name_filters);
@@ -1032,7 +1032,7 @@ int main(int argc, char *argv[])
 	} else {
 		struct config_export_context *ctx;
 
-		ctx = config_export_init(wanted_modules, CONFIG_DUMP_SCOPE_SET,
+		ctx = config_export_init(wanted_modules, NULL, CONFIG_DUMP_SCOPE_SET,
 					 CONFIG_DUMP_FLAG_CHECK_SETTINGS,
 					 config_request_putenv, NULL);
 		config_export_by_filter(ctx, &filter);
diff -pruN 1:2.3.16+dfsg1-3/src/config/settings-get.pl 1:2.3.19.1+dfsg1-2/src/config/settings-get.pl
--- 1:2.3.16+dfsg1-3/src/config/settings-get.pl	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/config/settings-get.pl	2022-06-14 06:55:03.000000000 +0000
@@ -20,6 +20,7 @@ print '#include "net.h"'."\n";
 print '#include "unichar.h"'."\n";
 print '#include "hash-method.h"'."\n";
 print '#include "settings-parser.h"'."\n";
+print '#include "message-header-parser.h"'."\n";
 print '#include "all-settings.h"'."\n";
 print '#include <stddef.h>'."\n";
 print '#include <unistd.h>'."\n";
@@ -147,6 +148,7 @@ print "};\n";
 print "const struct setting_parser_info *all_default_roots[] = {\n";
 print "\t&master_service_setting_parser_info,\n";
 print "\t&master_service_ssl_setting_parser_info,\n";
+print "\t&master_service_ssl_server_setting_parser_info,\n";
 print "\t&smtp_submit_setting_parser_info,\n";
 foreach my $name (sort(keys %parsers)) {
   my $module = $parsers{$name};
diff -pruN 1:2.3.16+dfsg1-3/src/dict/dict-commands.c 1:2.3.19.1+dfsg1-2/src/dict/dict-commands.c
--- 1:2.3.16+dfsg1-3/src/dict/dict-commands.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/dict-commands.c	2022-06-14 06:55:03.000000000 +0000
@@ -7,6 +7,7 @@
 #include "strescape.h"
 #include "stats-dist.h"
 #include "time-util.h"
+#include "dict-private.h"
 #include "dict-client.h"
 #include "dict-settings.h"
 #include "dict-connection.h"
@@ -19,7 +20,7 @@
 
 struct dict_cmd_func {
 	enum dict_protocol_cmd cmd;
-	int (*func)(struct dict_connection_cmd *cmd, const char *line);
+	int (*func)(struct dict_connection_cmd *cmd, const char *const *args);
 };
 
 struct dict_connection_cmd {
@@ -219,12 +220,24 @@ cmd_lookup_callback(const struct dict_lo
 	dict_connection_cmd_try_flush(&cmd);
 }
 
-static int cmd_lookup(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_lookup(struct dict_connection_cmd *cmd, const char *const *args)
 {
-	/* <key> */
+	const char *username;
+
+	if (str_array_length(args) < 1) {
+		e_error(cmd->event, "LOOKUP: broken input");
+		return -1;
+	}
+	username = args[1];
+
+	/* <key> [<username>] */
 	dict_connection_cmd_async(cmd);
-	event_add_str(cmd->event, "key", line);
-	dict_lookup_async(cmd->conn->dict, line, cmd_lookup_callback, cmd);
+	event_add_str(cmd->event, "key", args[0]);
+	event_add_str(cmd->event, "user", username);
+	const struct dict_op_settings set = {
+		.username = username,
+	};
+	dict_lookup_async(cmd->conn->dict, &set, args[0], cmd_lookup_callback, cmd);
 	return 1;
 }
 
@@ -340,13 +353,12 @@ static void cmd_iterate_callback(struct
 	dict_connection_unref_safe(conn);
 }
 
-static int cmd_iterate(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_iterate(struct dict_connection_cmd *cmd, const char *const *args)
 {
-	const char *const *args;
+	const char *username;
 	unsigned int flags;
 	uint64_t max_rows;
 
-	args = t_strsplit_tabescaped(line);
 	if (str_array_length(args) < 3 ||
 	    str_to_uint(args[0], &flags) < 0 ||
 	    str_to_uint64(args[1], &max_rows) < 0) {
@@ -354,11 +366,17 @@ static int cmd_iterate(struct dict_conne
 		return -1;
 	}
 	dict_connection_cmd_async(cmd);
+	username = args[3];
 
-	/* <flags> <max_rows> <path> */
+	const struct dict_op_settings set = {
+		.username = username,
+	};
+
+	/* <flags> <max_rows> <path> [<username>] */
 	flags |= DICT_ITERATE_FLAG_ASYNC;
 	event_add_str(cmd->event, "key", args[2]);
-	cmd->iter = dict_iterate_init_multiple(cmd->conn->dict, args+2, flags);
+	event_add_str(cmd->event, "user", username);
+	cmd->iter = dict_iterate_init(cmd->conn->dict, &set, args[2], flags);
 	cmd->iter_flags = flags;
 	if (max_rows > 0)
 		dict_iterate_set_limit(cmd->iter, max_rows);
@@ -401,13 +419,21 @@ dict_connection_transaction_array_remove
 	i_unreached();
 }
 
-static int cmd_begin(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_begin(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
 	unsigned int id;
+	const char *username;
+
+	if (str_array_length(args) < 1) {
+		e_error(cmd->event, "BEGIN: broken input");
+		return -1;
+	}
+	username = args[1];
 
-	if (str_to_uint(line, &id) < 0) {
-		e_error(cmd->event, "Invalid transaction ID %s", line);
+	/* <id> [<username>] */
+	if (str_to_uint(args[0], &id) < 0) {
+		e_error(cmd->event, "Invalid transaction ID %s", args[0]);
 		return -1;
 	}
 	if (dict_connection_transaction_lookup(cmd->conn, id) != NULL) {
@@ -418,23 +444,25 @@ static int cmd_begin(struct dict_connect
 	if (!array_is_created(&cmd->conn->transactions))
 		i_array_init(&cmd->conn->transactions, 4);
 
-	/* <id> */
+	struct dict_op_settings set = {
+		.username = username,
+	};
 	trans = array_append_space(&cmd->conn->transactions);
 	trans->id = id;
 	trans->conn = cmd->conn;
-	trans->ctx = dict_transaction_begin(cmd->conn->dict);
+	trans->ctx = dict_transaction_begin(cmd->conn->dict, &set);
 	return 0;
 }
 
 static int
 dict_connection_transaction_lookup_parse(struct dict_connection *conn,
-					 const char *line,
+					 const char *id_str,
 					 struct dict_connection_transaction **trans_r)
 {
 	unsigned int id;
 
-	if (str_to_uint(line, &id) < 0) {
-		e_error(conn->conn.event, "Invalid transaction ID %s", line);
+	if (str_to_uint(id_str, &id) < 0) {
+		e_error(conn->conn.event, "Invalid transaction ID %s", id_str);
 		return -1;
 	}
 	*trans_r = dict_connection_transaction_lookup(conn, id);
@@ -507,13 +535,14 @@ static void cmd_commit_callback_async(co
 }
 
 static int
-cmd_commit(struct dict_connection_cmd *cmd, const char *line)
+cmd_commit(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
 
-	if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
+	if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
 		return -1;
 	cmd->trans_id = trans->id;
+	event_add_str(cmd->event, "user", trans->ctx->set.username);
 
 	dict_connection_cmd_async(cmd);
 	dict_transaction_commit_async(&trans->ctx, cmd_commit_callback, cmd);
@@ -521,38 +550,39 @@ cmd_commit(struct dict_connection_cmd *c
 }
 
 static int
-cmd_commit_async(struct dict_connection_cmd *cmd, const char *line)
+cmd_commit_async(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
 
-	if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
+	if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
 		return -1;
 	cmd->trans_id = trans->id;
+	event_add_str(cmd->event, "user", trans->ctx->set.username);
 
 	dict_connection_cmd_async(cmd);
 	dict_transaction_commit_async(&trans->ctx, cmd_commit_callback_async, cmd);
 	return 1;
 }
 
-static int cmd_rollback(struct dict_connection_cmd *cmd, const char *line)
+static int
+cmd_rollback(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
 
-	if (dict_connection_transaction_lookup_parse(cmd->conn, line, &trans) < 0)
+	if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
 		return -1;
 
+	event_add_str(cmd->event, "user", trans->ctx->set.username);
 	dict_transaction_rollback(&trans->ctx);
 	dict_connection_transaction_array_remove(cmd->conn, trans->id);
 	return 0;
 }
 
-static int cmd_set(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_set(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
-	const char *const *args;
 
 	/* <id> <key> <value> */
-	args = t_strsplit_tabescaped(line);
 	if (str_array_length(args) != 3) {
 		e_error(cmd->event, "SET: broken input");
 		return -1;
@@ -560,17 +590,16 @@ static int cmd_set(struct dict_connectio
 
 	if (dict_connection_transaction_lookup_parse(cmd->conn, args[0], &trans) < 0)
 		return -1;
+	event_add_str(cmd->event, "user", trans->ctx->set.username);
         dict_set(trans->ctx, args[1], args[2]);
 	return 0;
 }
 
-static int cmd_unset(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_unset(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
-	const char *const *args;
 
 	/* <id> <key> */
-	args = t_strsplit_tabescaped(line);
 	if (str_array_length(args) != 2) {
 		e_error(cmd->event, "UNSET: broken input");
 		return -1;
@@ -582,14 +611,13 @@ static int cmd_unset(struct dict_connect
 	return 0;
 }
 
-static int cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *line)
+static int
+cmd_atomic_inc(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
-	const char *const *args;
 	long long diff;
 
 	/* <id> <key> <diff> */
-	args = t_strsplit_tabescaped(line);
 	if (str_array_length(args) != 3 ||
 	    str_to_llong(args[2], &diff) < 0) {
 		e_error(cmd->event, "ATOMIC_INC: broken input");
@@ -603,15 +631,13 @@ static int cmd_atomic_inc(struct dict_co
 	return 0;
 }
 
-static int cmd_timestamp(struct dict_connection_cmd *cmd, const char *line)
+static int cmd_timestamp(struct dict_connection_cmd *cmd, const char *const *args)
 {
 	struct dict_connection_transaction *trans;
-	const char *const *args;
 	long long tv_sec;
 	unsigned int tv_nsec;
 
 	/* <id> <secs> <nsecs> */
-	args = t_strsplit_tabescaped(line);
 	if (str_array_length(args) != 3 ||
 	    str_to_llong(args[1], &tv_sec) < 0 ||
 	    str_to_uint(args[2], &tv_nsec) < 0) {
@@ -661,6 +687,7 @@ int dict_command_input(struct dict_conne
 	const struct dict_cmd_func *cmd_func;
 	struct dict_connection_cmd *cmd;
 	int ret;
+	const char *const *args;
 
 	cmd_func = dict_command_find((enum dict_protocol_cmd)*line);
 	if (cmd_func == NULL) {
@@ -675,7 +702,9 @@ int dict_command_input(struct dict_conne
 	cmd->start_timeval = ioloop_timeval;
 	array_push_back(&conn->cmds, &cmd);
 	dict_connection_ref(conn);
-	if ((ret = cmd_func->func(cmd, line + 1)) <= 0) {
+
+	args = t_strsplit_tabescaped(line + 1);
+	if ((ret = cmd_func->func(cmd, args)) <= 0) {
 		dict_connection_cmd_remove(cmd);
 		return ret;
 	}
diff -pruN 1:2.3.16+dfsg1-3/src/dict/dict-connection.c 1:2.3.19.1+dfsg1-2/src/dict/dict-connection.c
--- 1:2.3.16+dfsg1-3/src/dict/dict-connection.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/dict-connection.c	2022-06-14 06:55:03.000000000 +0000
@@ -12,6 +12,7 @@
 #include "dict-settings.h"
 #include "dict-commands.h"
 #include "dict-connection.h"
+#include "dict-init-cache.h"
 
 #include <unistd.h>
 
@@ -49,7 +50,6 @@ static int dict_connection_handshake_arg
 		return -1;
 
 	conn->value_type = (enum dict_data_type)value_type_num;
-	conn->username = i_strdup(args[3]);
 	conn->name = i_strdup(args[4]);
 
 	/* try initialize the given dict */
@@ -88,17 +88,15 @@ static int dict_connection_dict_init(str
 			conn->name);
 		return -1;
 	}
+	event_set_append_log_prefix(conn->conn.event,
+				    t_strdup_printf("%s: ", conn->name));
 	event_add_str(conn->conn.event, "dict_name", conn->name);
-	if (conn->username[0] != '\0')
-		event_add_str(conn->conn.event, "user", conn->username);
 	uri = strlist[i+1];
 
 	i_zero(&dict_set);
-	dict_set.value_type = conn->value_type;
-	dict_set.username = conn->username;
 	dict_set.base_dir = dict_settings->base_dir;
 	dict_set.event_parent = conn->conn.event;
-	if (dict_init(uri, &dict_set, &conn->dict, &error) < 0) {
+	if (dict_init_cache_get(conn->name, uri, &dict_set, &conn->dict, &error) < 0) {
 		/* dictionary initialization failed */
 		e_error(conn->conn.event, "Failed to initialize dictionary '%s': %s",
 			conn->name, error);
@@ -165,7 +163,7 @@ bool dict_connection_unref(struct dict_c
 	}
 
 	if (conn->dict != NULL)
-		dict_deinit(&conn->dict);
+		dict_init_cache_unref(&conn->dict);
 
 	if (array_is_created(&conn->transactions))
 		array_free(&conn->transactions);
@@ -175,7 +173,6 @@ bool dict_connection_unref(struct dict_c
 	connection_deinit(&conn->conn);
 
 	i_free(conn->name);
-	i_free(conn->username);
 	i_free(conn);
 
 	master_service_client_connection_destroyed(master_service);
@@ -243,7 +240,7 @@ static void dict_connection_destroy(stru
 	dict_connection_cmds_output_more(conn);
 
 	io_remove(&conn->conn.io);
-	dict_connection_unref_safe(conn);
+	dict_connection_unref(conn);
 }
 
 unsigned int dict_connections_current_count(void)
diff -pruN 1:2.3.16+dfsg1-3/src/dict/dict-connection.h 1:2.3.19.1+dfsg1-2/src/dict/dict-connection.h
--- 1:2.3.16+dfsg1-3/src/dict/dict-connection.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/dict-connection.h	2022-06-14 06:55:03.000000000 +0000
@@ -16,7 +16,6 @@ struct dict_connection {
 	struct dict_server *server;
 	int refcount;
 
-	char *username;
 	char *name;
 	struct dict *dict;
 	enum dict_data_type value_type;
diff -pruN 1:2.3.16+dfsg1-3/src/dict/dict-init-cache.c 1:2.3.19.1+dfsg1-2/src/dict/dict-init-cache.c
--- 1:2.3.16+dfsg1-3/src/dict/dict-init-cache.c	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/dict-init-cache.c	2022-06-14 06:55:03.000000000 +0000
@@ -0,0 +1,164 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dict.h"
+#include "dict-private.h"
+#include "dict-init-cache.h"
+#include "llist.h"
+
+/* How many seconds to keep dict opened for reuse after it's been closed */
+#define DICT_CACHE_TIMEOUT_SECS 30
+/* How many closed dicts to keep */
+#define DICT_CACHE_MAX_COUNT 10
+
+struct dict_init_cache_list {
+	struct dict_init_cache_list *prev, *next;
+
+	struct dict *dict;
+	char *dict_name;
+	int refcount;
+
+	time_t destroy_time;
+};
+
+static struct dict_init_cache_list *dicts = NULL;
+static struct timeout *to_dict = NULL;
+
+static struct dict_init_cache_list *
+dict_init_cache_add(const char *dict_name, struct dict *dict)
+{
+	struct dict_init_cache_list *list;
+
+	list = i_new(struct dict_init_cache_list, 1);
+	list->refcount = 1;
+	list->dict = dict;
+	list->dict_name = i_strdup(dict_name);
+
+	DLLIST_PREPEND(&dicts, list);
+
+	return list;
+}
+
+static void dict_init_cache_list_free(struct dict_init_cache_list *list)
+{
+	i_assert(list->refcount == 0);
+
+	DLLIST_REMOVE(&dicts, list);
+	dict_deinit(&list->dict);
+	i_free(list->dict_name);
+	i_free(list);
+}
+
+static struct dict_init_cache_list *dict_init_cache_find(const char *dict_name)
+{
+	struct dict_init_cache_list *listp = dicts, *next = NULL, *match = NULL;
+	unsigned int ref0_count = 0;
+
+	while (listp != NULL) {
+                next = listp->next;
+		if (match != NULL) {
+			/* already found the dict. we're just going through
+			   the rest of them to drop 0 refcounts */
+		} else if (strcmp(dict_name, listp->dict_name) == 0)
+			match = listp;
+
+		if (listp->refcount == 0 && listp != match) {
+			if (listp->destroy_time <= ioloop_time ||
+			    ref0_count >= DICT_CACHE_MAX_COUNT - 1)
+				dict_init_cache_list_free(listp);
+			else
+				ref0_count++;
+		}
+                listp = next;
+	}
+	return match;
+}
+
+int dict_init_cache_get(const char *dict_name, const char *uri,
+			const struct dict_settings *set,
+			struct dict **dict_r, const char **error_r)
+{
+	struct dict_init_cache_list *match;
+	int ret = 0;
+
+	match = dict_init_cache_find(dict_name);
+	if (match == NULL) {
+		if (dict_init(uri, set, dict_r, error_r) < 0)
+			return -1;
+		match = dict_init_cache_add(dict_name, *dict_r);
+	} else {
+		match->refcount++;
+		*dict_r = match->dict;
+	}
+	i_assert(match->dict != NULL);
+	return ret;
+}
+
+static void destroy_unrefed(void)
+{
+	struct dict_init_cache_list *listp, *next = NULL;
+	bool seen_ref0 = FALSE;
+
+	for (listp = dicts; listp != NULL; listp = next) {
+		next = listp->next;
+
+		i_assert(listp->refcount >= 0);
+		if (listp->refcount > 0)
+			;
+		else if (listp->destroy_time <= ioloop_time)
+			dict_init_cache_list_free(listp);
+		else
+			seen_ref0 = TRUE;
+	}
+
+	if (!seen_ref0 && to_dict != NULL)
+		timeout_remove(&to_dict);
+}
+
+static void dict_removal_timeout(void *context ATTR_UNUSED)
+{
+	destroy_unrefed();
+}
+
+void dict_init_cache_unref(struct dict **_dict)
+{
+	struct dict *dict = *_dict;
+	struct dict_init_cache_list *listp;
+
+	if (dict == NULL)
+		return;
+
+	*_dict = NULL;
+	for (listp = dicts; listp != NULL; listp = listp->next) {
+		if (listp->dict == dict)
+			break;
+	}
+
+	i_assert(listp != NULL && listp->dict == dict);
+	i_assert(listp->refcount > 0);
+
+	listp->refcount--;
+	listp->destroy_time = ioloop_time + DICT_CACHE_TIMEOUT_SECS;
+
+	if (to_dict == NULL) {
+		to_dict = timeout_add_to(io_loop_get_root(),
+					 DICT_CACHE_TIMEOUT_SECS*1000/2,
+					 dict_removal_timeout, NULL);
+	}
+}
+
+void dict_init_cache_wait_all(void)
+{
+	struct dict_init_cache_list *listp;
+
+	for (listp = dicts; listp != NULL; listp = listp->next)
+		dict_wait(listp->dict);
+}
+
+void dict_init_cache_destroy_all(void)
+{
+	timeout_remove(&to_dict);
+	while (dicts != NULL)
+		dict_init_cache_list_free(dicts);
+}
diff -pruN 1:2.3.16+dfsg1-3/src/dict/dict-init-cache.h 1:2.3.19.1+dfsg1-2/src/dict/dict-init-cache.h
--- 1:2.3.16+dfsg1-3/src/dict/dict-init-cache.h	1970-01-01 00:00:00.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/dict-init-cache.h	2022-06-14 06:55:03.000000000 +0000
@@ -0,0 +1,12 @@
+#ifndef DICT_INIT_CACHE_H
+#define DICT_INIT_CACHE_H
+
+int dict_init_cache_get(const char *dict_name, const char *uri,
+			const struct dict_settings *set,
+			struct dict **dict_r, const char **error_r);
+void dict_init_cache_unref(struct dict **dict);
+
+void dict_init_cache_wait_all(void);
+void dict_init_cache_destroy_all(void);
+
+#endif
diff -pruN 1:2.3.16+dfsg1-3/src/dict/main.c 1:2.3.19.1+dfsg1-2/src/dict/main.c
--- 1:2.3.16+dfsg1-3/src/dict/main.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/main.c	2022-06-14 06:55:03.000000000 +0000
@@ -17,6 +17,7 @@
 #include "dict-commands.h"
 #include "dict-connection.h"
 #include "dict-settings.h"
+#include "dict-init-cache.h"
 #include "main.h"
 
 #include <math.h>
@@ -24,6 +25,7 @@
 static struct module *modules;
 static struct timeout *to_proctitle;
 static bool proctitle_updated;
+static struct ioloop *main_ioloop;
 
 static void
 add_stats_string(string_t *str, struct stats_dist *stats, const char *name)
@@ -68,7 +70,7 @@ void dict_proctitle_update_later(void)
 		return;
 
 	if (to_proctitle == NULL)
-		to_proctitle = timeout_add(1000, dict_proctitle_update, NULL);
+		to_proctitle = timeout_add_to(main_ioloop, 1000, dict_proctitle_update, NULL);
 	proctitle_updated = TRUE;
 }
 
@@ -128,18 +130,19 @@ static void main_init(void)
 
 static void main_deinit(void)
 {
-	/* FIXME: we're not able to do a clean deinit currently without
-	   larger changes. */
-	lib_exit(0);
-	timeout_remove(&to_proctitle);
-
+	/* wait for all dict operations to finish */
+	dict_init_cache_wait_all();
+	/* connections should no longer have any extra refcounts */
 	dict_connections_destroy_all();
+	dict_init_cache_destroy_all();
+
 	dict_drivers_unregister_all();
 	dict_commands_deinit();
 
 	module_dir_unload(&modules);
 
 	sql_drivers_deinit();
+	timeout_remove(&to_proctitle);
 }
 
 int main(int argc, char *argv[])
@@ -164,10 +167,12 @@ int main(int argc, char *argv[])
 	main_preinit();
 	master_service_set_die_callback(master_service, dict_die);
 
+	main_ioloop = current_ioloop;
 	main_init();
 	master_service_init_finish(master_service);
 	master_service_run(master_service, client_connected);
 
+	/* clean up cached dicts */
 	main_deinit();
 	master_service_deinit(&master_service);
         return 0;
diff -pruN 1:2.3.16+dfsg1-3/src/dict/Makefile.am 1:2.3.19.1+dfsg1-2/src/dict/Makefile.am
--- 1:2.3.16+dfsg1-3/src/dict/Makefile.am	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/Makefile.am	2022-06-14 06:55:03.000000000 +0000
@@ -32,10 +32,12 @@ dict_SOURCES = \
 	dict-connection.c \
 	dict-commands.c \
 	dict-settings.c \
+	dict-init-cache.c \
 	main.c
 
 noinst_HEADERS = \
 	dict-connection.h \
 	dict-commands.h \
 	dict-settings.h \
+	dict-init-cache.h \
 	main.h
diff -pruN 1:2.3.16+dfsg1-3/src/dict/Makefile.in 1:2.3.19.1+dfsg1-2/src/dict/Makefile.in
--- 1:2.3.16+dfsg1-3/src/dict/Makefile.in	2021-08-06 09:26:04.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/dict/Makefile.in	2022-06-14 06:55:16.000000000 +0000
@@ -152,7 +152,8 @@ CONFIG_CLEAN_VPATH_FILES =
 am__installdirs = "$(DESTDIR)$(pkglibexecdir)"
 PROGRAMS = $(pkglibexec_PROGRAMS)
 am_dict_OBJECTS = dict-connection.$(OBJEXT) dict-commands.$(OBJEXT) \
-	dict-settings.$(OBJEXT) main.$(OBJEXT)
+	dict-settings.$(OBJEXT) dict-init-cache.$(OBJEXT) \
+	main.$(OBJEXT)
 dict_OBJECTS = $(am_dict_OBJECTS)
 am__DEPENDENCIES_1 =
 am__DEPENDENCIES_2 = ../lib-dict-backend/libdict_backend.la \
@@ -180,8 +181,8 @@ DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top
 depcomp = $(SHELL) $(top_srcdir)/depcomp
 am__maybe_remake_depfiles = depfiles
 am__depfiles_remade = ./$(DEPDIR)/dict-commands.Po \
-	./$(DEPDIR)/dict-connection.Po ./$(DEPDIR)/dict-settings.Po \
-	./$(DEPDIR)/main.Po
+	./$(DEPDIR)/dict-connection.Po ./$(DEPDIR)/dict-init-cache.Po \
+	./$(DEPDIR)/dict-settings.Po ./$(DEPDIR)/main.Po
 am__mv = mv -f
 COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
 	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -478,12 +479,14 @@ dict_SOURCES = \
 	dict-connection.c \
 	dict-commands.c \
 	dict-settings.c \
+	dict-init-cache.c \
 	main.c
 
 noinst_HEADERS = \
 	dict-connection.h \
 	dict-commands.h \
 	dict-settings.h \
+	dict-init-cache.h \
 	main.h
 
 all: all-am
@@ -581,6 +584,7 @@ distclean-compile:
 
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-commands.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-connection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-init-cache.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dict-settings.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
 
@@ -747,6 +751,7 @@ clean-am: clean-generic clean-libtool cl
 distclean: distclean-am
 		-rm -f ./$(DEPDIR)/dict-commands.Po
 	-rm -f ./$(DEPDIR)/dict-connection.Po
+	-rm -f ./$(DEPDIR)/dict-init-cache.Po
 	-rm -f ./$(DEPDIR)/dict-settings.Po
 	-rm -f ./$(DEPDIR)/main.Po
 	-rm -f Makefile
@@ -796,6 +801,7 @@ installcheck-am:
 maintainer-clean: maintainer-clean-am
 		-rm -f ./$(DEPDIR)/dict-commands.Po
 	-rm -f ./$(DEPDIR)/dict-connection.Po
+	-rm -f ./$(DEPDIR)/dict-init-cache.Po
 	-rm -f ./$(DEPDIR)/dict-settings.Po
 	-rm -f ./$(DEPDIR)/main.Po
 	-rm -f Makefile
diff -pruN 1:2.3.16+dfsg1-3/src/director/director-connection.c 1:2.3.19.1+dfsg1-2/src/director/director-connection.c
--- 1:2.3.16+dfsg1-3/src/director/director-connection.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/director/director-connection.c	2022-06-14 06:55:03.000000000 +0000
@@ -188,9 +188,6 @@ director_cmd_error(struct director_conne
 static void
 director_connection_append_stats(struct director_connection *conn, string_t *str)
 {
-	int input_msecs = timeval_diff_msecs(&ioloop_timeval, &conn->last_input);
-	int output_msecs = timeval_diff_msecs(&ioloop_timeval, &conn->last_output);
-	int connected_msecs = timeval_diff_msecs(&ioloop_timeval, &conn->connected_time);
 	struct rusage usage;
 
 	str_printfa(str, "bytes in=%"PRIuUOFF_T", bytes out=%"PRIuUOFF_T,
@@ -202,14 +199,20 @@ director_connection_append_stats(struct
 			    conn->handshake_users_sent);
 	}
 	if (conn->last_input.tv_sec > 0) {
+		int input_msecs = timeval_diff_msecs(&ioloop_timeval,
+						     &conn->last_input);
 		str_printfa(str, ", last input %u.%03u s ago",
 			    input_msecs/1000, input_msecs%1000);
 	}
 	if (conn->last_output.tv_sec > 0) {
+		int output_msecs = timeval_diff_msecs(&ioloop_timeval,
+						      &conn->last_output);
 		str_printfa(str, ", last output %u.%03u s ago",
 			    output_msecs/1000, output_msecs%1000);
 	}
 	if (conn->connected) {
+		int connected_msecs = timeval_diff_msecs(&ioloop_timeval,
+							 &conn->connected_time);
 		str_printfa(str, ", connected %u.%03u s ago",
 			    connected_msecs/1000, connected_msecs%1000);
 	}
@@ -1114,7 +1117,7 @@ director_cmd_host_int(struct director_co
 	struct ip_addr ip;
 	const char *tag = "", *host_tag, *hostname = NULL;
 	unsigned int arg_count, vhost_count;
-	bool update, down = FALSE;
+	bool update, down = FALSE, tag_changed = FALSE;
 	time_t last_updown_change = 0;
 
 	arg_count = str_array_length(args);
@@ -1153,18 +1156,11 @@ director_cmd_host_int(struct director_co
 					      hostname, &ip, tag);
 		update = TRUE;
 	} else {
+		host_tag = mail_host_get_tag(host);
+		tag_changed = strcmp(tag, host_tag) != 0;
 		update = host->vhost_count != vhost_count ||
-			host->down != down;
+			host->down != down || tag_changed;
 
-		host_tag = mail_host_get_tag(host);
-		if (strcmp(tag, host_tag) != 0) {
-			e_error(conn->event,
-				"Host %s changed tag from '%s' to '%s'",
-				host->ip_str,
-				host_tag, tag);
-			mail_host_set_tag(host, tag);
-			update = TRUE;
-		}
 		if (update && host->desynced) {
 			string_t *str = t_str_new(128);
 
@@ -1210,6 +1206,13 @@ director_cmd_host_int(struct director_co
 	if (update) {
 		const char *log_prefix = t_strdup_printf("director(%s): ",
 							 conn->name);
+		if (tag_changed) {
+			e_error(conn->event,
+				"Host %s changed tag from '%s' to '%s'",
+				host->ip_str,
+				mail_host_get_tag(host), tag);
+			mail_host_set_tag(host, tag);
+		}
 		mail_host_set_down(host, down, last_updown_change, log_prefix);
 		mail_host_set_vhost_count(host, vhost_count, log_prefix);
 		director_update_host(conn->dir, src_host, dir_host, host);
diff -pruN 1:2.3.16+dfsg1-3/src/director/mail-host.c 1:2.3.19.1+dfsg1-2/src/director/mail-host.c
--- 1:2.3.16+dfsg1-3/src/director/mail-host.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/director/mail-host.c	2022-06-14 06:55:03.000000000 +0000
@@ -330,6 +330,11 @@ void mail_host_set_tag(struct mail_host
 {
 	i_assert(tag_name != NULL);
 
+	/* If the host already has users, forget all of them. Otherwise state
+	   becomes inconsistent, since tag->users won't match
+	   user->host->tag. */
+	user_directory_remove_host(host->tag->users, host);
+
 	host->tag = mail_tag_get(host->list, tag_name);
 	host->list->vhosts_unsorted = TRUE;
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/client-connection-tcp.c 1:2.3.19.1+dfsg1-2/src/doveadm/client-connection-tcp.c
--- 1:2.3.16+dfsg1-3/src/doveadm/client-connection-tcp.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/client-connection-tcp.c	2022-06-14 06:55:03.000000000 +0000
@@ -70,7 +70,7 @@ doveadm_server_log_handler(const struct
 		struct ioloop *prev_ioloop = current_ioloop;
 		struct ostream *log_out = conn->log_out;
 		char c;
-		const char *ptr, *start;
+		const char *ptr;
 		bool corked;
 		va_list va;
 
@@ -83,26 +83,32 @@ doveadm_server_log_handler(const struct
 		if (conn->ioloop != NULL)
 			io_loop_set_current(conn->ioloop);
 
+		const char *log_prefix =
+			ctx->log_prefix != NULL ? ctx->log_prefix :
+			i_get_failure_prefix();
+		size_t log_prefix_len = strlen(log_prefix);
 		c = doveadm_log_type_to_char(ctx->type);
 		corked = o_stream_is_corked(log_out);
 
 		va_copy(va, args);
-		string_t *str = t_str_new(128);
-		str_vprintfa(str, format, va);
+		const char *str = t_strdup_vprintf(format, va);
 		va_end(va);
 
-		start = str_c(str);
 		if (!corked)
 			o_stream_cork(log_out);
-		while((ptr = strchr(start, '\n'))!=NULL) {
-			o_stream_nsend(log_out, &c, 1);
-			o_stream_nsend(log_out, start, ptr-start+1);
-			str_delete(str, 0, ptr-start+1);
-		}
-		if (str->used > 0) {
+		for (;;) {
+			ptr = strchr(str, '\n');
+			size_t len = ptr == NULL ? strlen(str) :
+				(size_t)(ptr - str);
+
 			o_stream_nsend(log_out, &c, 1);
-			o_stream_nsend(log_out, str->data, str->used);
+			o_stream_nsend(log_out, log_prefix, log_prefix_len);
+			o_stream_nsend(log_out, str, len);
 			o_stream_nsend(log_out, "\n", 1);
+
+			if (ptr == NULL)
+				break;
+			str = ptr+1;
 		}
 		o_stream_uncork(log_out);
 		if (corked)
@@ -183,157 +189,19 @@ doveadm_cmd_server_run_ver2(struct clien
 	doveadm_cmd_server_post(conn, cctx->cmd->name);
 }
 
-static void
-doveadm_cmd_server_run(struct client_connection_tcp *conn,
-		       int argc, const char *const argv[],
-		       const struct doveadm_cmd *cmd)
-{
-	i_getopt_reset();
-	cmd->cmd(argc, (char **)argv);
-	doveadm_cmd_server_post(conn, cmd->name);
-}
-
-static int
-doveadm_mail_cmd_server_parse(const struct doveadm_mail_cmd *cmd,
-			      const struct doveadm_settings *set,
-			      int argc, const char *const argv[],
-			      struct doveadm_cmd_context *cctx,
-			      struct doveadm_mail_cmd_context **mctx_r)
-{
-	struct doveadm_mail_cmd_context *mctx;
-	const char *getopt_args;
-	bool add_username_header = FALSE;
-	int c;
-
-	mctx = doveadm_mail_cmd_init(cmd, set);
-	mctx->cctx = cctx;
-	mctx->full_args = argv+1;
-	mctx->proxying = TRUE;
-	mctx->service_flags |=
-		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT |
-		MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
-	if (doveadm_debug)
-		mctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_DEBUG;
-
-	i_getopt_reset();
-	getopt_args = t_strconcat("AF:S:u:", mctx->getopt_args, NULL);
-	while ((c = getopt(argc, (char **)argv, getopt_args)) > 0) {
-		switch (c) {
-		case 'A':
-		case 'F':
-			add_username_header = TRUE;
-			break;
-		case 'S':
-			/* ignore */
-			break;
-		case 'u':
-			if (strchr(optarg, '*') != NULL ||
-			    strchr(optarg, '?') != NULL)
-				add_username_header = TRUE;
-			break;
-		default:
-			if ((mctx->v.parse_arg == NULL ||
-			     !mctx->v.parse_arg(mctx, c))) {
-				i_error("doveadm %s: "
-					"Client sent unknown parameter: %c",
-					cmd->name, c);
-				mctx->v.deinit(mctx);
-				pool_unref(&mctx->pool);
-				return -1;
-			}
-		}
-	}
-
-	if (argv[optind] != NULL && cmd->usage_args == NULL) {
-		i_error("doveadm %s: Client sent unknown parameter: %s",
-			cmd->name, argv[optind]);
-		mctx->v.deinit(mctx);
-		pool_unref(&mctx->pool);
-		return -1;
-	}
-	mctx->args = argv+optind;
-
-	if (cctx->username != NULL) {
-		if (strchr(cctx->username, '*') != NULL ||
-		    strchr(cctx->username, '?') != NULL) {
-			add_username_header = TRUE;
-		}
-	}
-
-	if (doveadm_print_is_initialized() && add_username_header) {
-		doveadm_print_header("username", "Username",
-				     DOVEADM_PRINT_HEADER_FLAG_STICKY |
-				     DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
-		doveadm_print_sticky("username", cctx->username);
-	}
-	*mctx_r = mctx;
-	return 0;
-}
-
-static void
-doveadm_mail_cmd_server_run(struct client_connection_tcp *conn,
-			    struct doveadm_mail_cmd_context *mctx)
-{
-	const char *error;
-	int ret;
-
-	o_stream_cork(conn->output);
-
-	if (mctx->v.preinit != NULL)
-		mctx->v.preinit(mctx);
-
-	ret = doveadm_mail_single_user(mctx, &error);
-	doveadm_mail_server_flush();
-	mctx->v.deinit(mctx);
-	doveadm_print_flush();
-	mail_storage_service_deinit(&mctx->storage_service);
-
-	if (ret < 0) {
-		i_error("%s: %s", mctx->cmd->name, error);
-		o_stream_nsend(conn->output, "\n-\n", 3);
-	} else if (ret == 0) {
-		o_stream_nsend_str(conn->output, "\n-NOUSER\n");
-	} else if (mctx->exit_code == DOVEADM_EX_NOREPLICATE) {
-		o_stream_nsend_str(conn->output, "\n-NOREPLICATE\n");
-	} else if (mctx->exit_code != 0) {
-		/* maybe not an error, but not a full success either */
-		o_stream_nsend_str(conn->output,
-				   t_strdup_printf("\n-%u\n", mctx->exit_code));
-	} else {
-		o_stream_nsend(conn->output, "\n+\n", 3);
-	}
-	o_stream_uncork(conn->output);
-	pool_unref(&mctx->pool);
-}
-
 static int doveadm_cmd_handle(struct client_connection_tcp *conn,
 			      const char *cmd_name,
 			      int argc, const char *const argv[],
 			      struct doveadm_cmd_context *cctx)
 {
 	struct ioloop *prev_ioloop = current_ioloop;
-	const struct doveadm_cmd *cmd = NULL;
-	const struct doveadm_mail_cmd *mail_cmd;
-	struct doveadm_mail_cmd_context *mctx = NULL;
 	const struct doveadm_cmd_ver2 *cmd_ver2;
 
 	if ((cmd_ver2 = doveadm_cmd_find_with_args_ver2(cmd_name, &argc, &argv)) == NULL) {
-		mail_cmd = doveadm_mail_cmd_find(cmd_name);
-		if (mail_cmd == NULL) {
-			cmd = doveadm_cmd_find_with_args(cmd_name, &argc, &argv);
-			if (cmd == NULL) {
-				i_error("doveadm: Client sent unknown command: %s", cmd_name);
-				return -1;
-			}
-		} else {
-			if (doveadm_mail_cmd_server_parse(mail_cmd, conn->conn.set,
-							  argc, argv,
-							  cctx, &mctx) < 0)
-				return -1;
-		}
-	} else {
-		cctx->cmd = cmd_ver2;
+		i_error("doveadm: Client sent unknown command: %s", cmd_name);
+		return -1;
 	}
+	cctx->cmd = cmd_ver2;
 
 	/* some commands will want to call io_loop_run(), but we're already
 	   running one and we can't call the original one recursively, so
@@ -343,14 +211,7 @@ static int doveadm_cmd_handle(struct cli
 	if (conn->log_out != NULL)
 		o_stream_switch_ioloop(conn->log_out);
 
-	if (cmd_ver2 != NULL)
-		doveadm_cmd_server_run_ver2(conn, argc, argv, cctx);
-	else if (cmd != NULL)
-		doveadm_cmd_server_run(conn, argc, argv, cmd);
-	else {
-		i_assert(mctx != NULL);
-		doveadm_mail_cmd_server_run(conn, mctx);
-	}
+	doveadm_cmd_server_run_ver2(conn, argc, argv, cctx);
 
 	o_stream_switch_ioloop_to(conn->output, prev_ioloop);
 	if (conn->log_out != NULL)
@@ -360,7 +221,10 @@ static int doveadm_cmd_handle(struct cli
 	/* clear all headers */
 	doveadm_print_deinit();
 	doveadm_print_init(DOVEADM_PRINT_TYPE_SERVER);
-	return doveadm_exit_code == 0 ? 0 : -1;
+
+	/* We already sent the success/failure reply to the client. Return 0
+	   so caller never adds another failure reply. */
+	return 0;
 }
 
 static bool client_handle_command(struct client_connection_tcp *conn,
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-auth.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-auth.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-auth.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-auth.c	2022-06-14 06:55:03.000000000 +0000
@@ -46,7 +46,7 @@ struct authtest_input {
 
 };
 
-static void auth_cmd_help(doveadm_command_t *cmd);
+static void auth_cmd_help(struct doveadm_cmd_context *cctx);
 
 static struct auth_master_connection *
 doveadm_get_auth_master_conn(const char *auth_socket_path)
@@ -233,7 +233,8 @@ cmd_auth_input(const char *auth_socket_p
 	auth_client_deinit(&client);
 }
 
-static void auth_user_info_parse(struct auth_user_info *info, const char *arg)
+static void
+auth_user_info_parse_arg(struct auth_user_info *info, const char *arg)
 {
 	if (str_begins(arg, "service="))
 		info->service = arg + 8;
@@ -290,9 +291,16 @@ static void auth_user_info_parse(struct
 }
 
 static void
+auth_user_info_parse(struct auth_user_info *info, const char *const *args)
+{
+	for (unsigned int i = 0; args[i] != NULL; i++)
+		auth_user_info_parse_arg(info, args[i]);
+}
+
+static void
 cmd_user_list(struct auth_master_connection *conn,
 	      const struct authtest_input *input,
-	      char *const *users)
+	      const char *const *users)
 {
 	struct auth_master_user_list_ctx *ctx;
 	const char *username, *user_mask = "*";
@@ -314,31 +322,21 @@ cmd_user_list(struct auth_master_connect
 		i_fatal("user listing failed");
 }
 
-static void cmd_auth_cache_flush(int argc, char *argv[])
+static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx)
 {
-	const char *master_socket_path = NULL;
+	const char *master_socket_path;
 	struct auth_master_connection *conn;
+	const char *const *users = NULL;
 	unsigned int count;
-	int c;
 
-	while ((c = getopt(argc, argv, "a:")) > 0) {
-		switch (c) {
-		case 'a':
-			master_socket_path = optarg;
-			break;
-		default:
-			auth_cmd_help(cmd_auth_cache_flush);
-		}
-	}
-	argv += optind;
-
-	if (master_socket_path == NULL) {
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &master_socket_path)) {
 		master_socket_path = t_strconcat(doveadm_settings->base_dir,
 						 "/auth-master", NULL);
 	}
+	(void)doveadm_cmd_param_array(cctx, "user", &users);
 
 	conn = doveadm_get_auth_master_conn(master_socket_path);
-	if (auth_master_cache_flush(conn, (void *)argv, &count) < 0) {
+	if (auth_master_cache_flush(conn, users, &count) < 0) {
 		i_error("Cache flush failed");
 		doveadm_exit_code = EX_TEMPFAIL;
 	} else {
@@ -354,37 +352,22 @@ static void authtest_input_init(struct a
 	input->info.debug = doveadm_settings->auth_debug;
 }
 
-static void cmd_auth_test(int argc, char *argv[])
+static void cmd_auth_test(struct doveadm_cmd_context *cctx)
 {
 	const char *auth_socket_path = NULL;
+	const char *const *auth_info;
 	struct authtest_input input;
-	int c;
 
 	authtest_input_init(&input);
-	while ((c = getopt(argc, argv, "a:M:x:")) > 0) {
-		switch (c) {
-		case 'a':
-			auth_socket_path = optarg;
-			break;
-		case 'M':
-			input.master_user = optarg;
-			break;
-		case 'x':
-			auth_user_info_parse(&input.info, optarg);
-			break;
-		default:
-			auth_cmd_help(cmd_auth_test);
-		}
-	}
-
-	if (optind == argc)
-		auth_cmd_help(cmd_auth_test);
-
-	input.username = argv[optind++];
-	input.password = argv[optind] != NULL ? argv[optind++] :
-		t_askpass("Password: ");
-	if (argv[optind] != NULL)
-			i_fatal("Unexpected parameter: %s", argv[optind]);
+	(void)doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path);
+	(void)doveadm_cmd_param_str(cctx, "master-user", &input.master_user);
+	if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info))
+		auth_user_info_parse(&input.info, auth_info);
+
+	if (!doveadm_cmd_param_str(cctx, "user", &input.username))
+		auth_cmd_help(cctx);
+	if (!doveadm_cmd_param_str(cctx, "password", &input.password))
+		input.password = t_askpass("Password: ");
 	cmd_auth_input(auth_socket_path, &input);
 	if (!input.success)
 		doveadm_exit_code = EX_NOPERM;
@@ -439,46 +422,35 @@ cmd_auth_master_input(const char *auth_m
 	master_login_auth_deinit(&master_auth);
 }
 
-static void cmd_auth_login(int argc, char *argv[])
+static void cmd_auth_login(struct doveadm_cmd_context *cctx)
 {
 	const char *auth_login_socket_path, *auth_master_socket_path;
+	const char *const *auth_info;
 	struct auth_client *auth_client;
 	struct authtest_input input;
-	int c;
 
 	authtest_input_init(&input);
-	auth_login_socket_path = t_strconcat(doveadm_settings->base_dir,
-					     "/auth-login", NULL);
-	auth_master_socket_path = t_strconcat(doveadm_settings->base_dir,
-					      "/auth-master", NULL);
-	while ((c = getopt(argc, argv, "a:m:M:x:")) > 0) {
-		switch (c) {
-		case 'a':
-			auth_login_socket_path = optarg;
-			break;
-		case 'm':
-			auth_master_socket_path = optarg;
-			break;
-		case 'M':
-			input.master_user = optarg;
-			break;
-		case 'x':
-			auth_user_info_parse(&input.info, optarg);
-			break;
-		default:
-			auth_cmd_help(cmd_auth_login);
-		}
-	}
-
-	if (optind == argc)
-		auth_cmd_help(cmd_auth_login);
+	if (!doveadm_cmd_param_str(cctx, "auth-login-socket-path",
+				   &auth_login_socket_path)) {
+		auth_login_socket_path =
+			t_strconcat(doveadm_settings->base_dir,
+				    "/auth-login", NULL);
+	}
+	if (!doveadm_cmd_param_str(cctx, "auth-master-socket-path",
+				   &auth_master_socket_path)) {
+		auth_master_socket_path =
+			t_strconcat(doveadm_settings->base_dir,
+				    "/auth-master", NULL);
+	}
+	(void)doveadm_cmd_param_str(cctx, "master-user", &input.master_user);
+	if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info))
+		auth_user_info_parse(&input.info, auth_info);
+	if (!doveadm_cmd_param_str(cctx, "user", &input.username))
+		auth_cmd_help(cctx);
+	if (!doveadm_cmd_param_str(cctx, "password", &input.password))
+		input.password = t_askpass("Password: ");
 
 	input.pool = pool_alloconly_create("auth login", 256);
-	input.username = argv[optind++];
-	input.password = argv[optind] != NULL ? argv[optind++] :
-		t_askpass("Password: ");
-	if (argv[optind] != NULL)
-			i_fatal("Unexpected parameter: %s", argv[optind]);
 	/* authenticate */
 	auth_client = auth_client_init(auth_login_socket_path, getpid(), FALSE);
 	auth_client_connect(auth_client);
@@ -495,37 +467,28 @@ static void cmd_auth_login(int argc, cha
 	pool_unref(&input.pool);
 }
 
-static void cmd_auth_lookup(int argc, char *argv[])
+static void cmd_auth_lookup(struct doveadm_cmd_context *cctx)
 {
-	const char *auth_socket_path = doveadm_settings->auth_socket_path;
+	const char *auth_socket_path;
 	struct auth_master_connection *conn;
 	struct authtest_input input;
 	const char *show_field = NULL;
+	const char *const *auth_info, *const *users;
 	bool first = TRUE;
-	int c, ret;
+	int ret;
 
 	authtest_input_init(&input);
-	while ((c = getopt(argc, argv, "a:f:x:")) > 0) {
-		switch (c) {
-		case 'a':
-			auth_socket_path = optarg;
-			break;
-		case 'f':
-			show_field = optarg;
-			break;
-		case 'x':
-			auth_user_info_parse(&input.info, optarg);
-			break;
-		default:
-			auth_cmd_help(cmd_auth_lookup);
-		}
-	}
-
-	if (optind == argc)
-		auth_cmd_help(cmd_auth_lookup);
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path))
+		auth_socket_path = doveadm_settings->auth_socket_path;
+	(void)doveadm_cmd_param_str(cctx, "field", &show_field);
+	if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info))
+		auth_user_info_parse(&input.info, auth_info);
+	if (!doveadm_cmd_param_array(cctx, "user", &users))
+		auth_cmd_help(cctx);
 
 	conn = doveadm_get_auth_master_conn(auth_socket_path);
-	while ((input.username = argv[optind++]) != NULL) {
+	for (unsigned int i = 0; users[i] != NULL; i++) {
+		input.username = users[i];
 		if (first)
 			first = FALSE;
 		else
@@ -650,39 +613,28 @@ cmd_user_mail_input(struct mail_storage_
 	return 1;
 }
 
-static void cmd_user(int argc, char *argv[])
+static void cmd_user(struct doveadm_cmd_context *cctx)
 {
-	const char *auth_socket_path = doveadm_settings->auth_socket_path;
+	const char *auth_socket_path;
 	struct auth_master_connection *conn;
 	struct authtest_input input;
 	const char *show_field = NULL, *expand_field = NULL;
+	const char *const *user_masks, *const *auth_info;
 	struct mail_storage_service_ctx *storage_service = NULL;
 	unsigned int i;
 	bool have_wildcards, userdb_only = FALSE, first = TRUE;
-	int c, ret;
+	int ret;
 
 	authtest_input_init(&input);
-	while ((c = getopt(argc, argv, "a:e:f:ux:")) > 0) {
-		switch (c) {
-		case 'a':
-			auth_socket_path = optarg;
-			break;
-		case 'e':
-			expand_field = optarg;
-			break;
-		case 'f':
-			show_field = optarg;
-			break;
-		case 'u':
-			userdb_only = TRUE;
-			break;
-		case 'x':
-			auth_user_info_parse(&input.info, optarg);
-			break;
-		default:
-			auth_cmd_help(cmd_user);
-		}
-	}
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &auth_socket_path))
+		auth_socket_path = doveadm_settings->auth_socket_path;
+	(void)doveadm_cmd_param_str(cctx, "field", &show_field);
+	(void)doveadm_cmd_param_str(cctx, "expand-field", &expand_field);
+	(void)doveadm_cmd_param_bool(cctx, "userdb-only", &userdb_only);
+	if (doveadm_cmd_param_array(cctx, "auth-info", &auth_info))
+		auth_user_info_parse(&input.info, auth_info);
+	if (!doveadm_cmd_param_array(cctx, "user-mask", &user_masks))
+		auth_cmd_help(cctx);
 
 	if (expand_field != NULL && userdb_only) {
 		i_error("-e can't be used with -u");
@@ -695,22 +647,19 @@ static void cmd_user(int argc, char *arg
 		return;
 	}
 
-	if (optind == argc)
-		auth_cmd_help(cmd_user);
-
 	conn = doveadm_get_auth_master_conn(auth_socket_path);
 
 	have_wildcards = FALSE;
-	for (i = optind; argv[i] != NULL; i++) {
-		if (strchr(argv[i], '*') != NULL ||
-		    strchr(argv[i], '?') != NULL) {
+	for (i = 0; user_masks[i] != NULL; i++) {
+		if (strchr(user_masks[i], '*') != NULL ||
+		    strchr(user_masks[i], '?') != NULL) {
 			have_wildcards = TRUE;
 			break;
 		}
 	}
 
 	if (have_wildcards) {
-		cmd_user_list(conn, &input, argv + optind);
+		cmd_user_list(conn, &input, user_masks);
 		auth_master_deinit(&conn);
 		return;
 	}
@@ -732,7 +681,8 @@ static void cmd_user(int argc, char *arg
 		}
 	}
 
-	while ((input.username = argv[optind++]) != NULL) {
+	for (i = 0; user_masks[i] != NULL; i++) {
+		input.username = user_masks[i];
 		if (first)
 			first = FALSE;
 		else
@@ -756,26 +706,74 @@ static void cmd_user(int argc, char *arg
 		auth_master_deinit(&conn);
 }
 
-struct doveadm_cmd doveadm_cmd_auth[] = {
-	{ cmd_auth_test, "auth test",
-	  "[-a <auth socket path>] [-x <auth info>] [-M <master user>] <user> [<password>]" },
-	{ cmd_auth_login, "auth login",
-	  "[-a <auth-login socket path>] [-m <auth-master socket path>] [-x <auth info>] [-M <master user>] <user> [<password>]" },
-	{ cmd_auth_lookup, "auth lookup",
-	  "[-a <userdb socket path>] [-x <auth info>] [-f field] <user> [...]" },
-	{ cmd_auth_cache_flush, "auth cache flush",
-	  "[-a <master socket path>] [<user> [...]]" },
-	{ cmd_user, "user",
-	  "[-a <userdb socket path>] [-x <auth info>] [-f field] [-e <value>] [-u] <user mask> [...]" }
+struct doveadm_cmd_ver2 doveadm_cmd_auth[] = {
+{
+	.cmd = cmd_auth_test,
+	.name = "auth test",
+	.usage = "[-a <auth socket path>] [-x <auth info>] [-M <master user>] <user> [<password>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0)
+DOVEADM_CMD_PARAM('M', "master-user", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "password", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.cmd = cmd_auth_login,
+	.name = "auth login",
+	.usage = "[-a <auth-login socket path>] [-m <auth-master socket path>] [-x <auth info>] [-M <master user>] <user> [<password>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "auth-login-socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('m', "auth-master-socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0)
+DOVEADM_CMD_PARAM('M', "master-user", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "password", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.cmd = cmd_auth_lookup,
+	.name = "auth lookup",
+	.usage = "[-a <userdb socket path>] [-x <auth info>] [-f field] <user> [<user> [...]]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0)
+DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.cmd = cmd_auth_cache_flush,
+	.name = "auth cache flush",
+	.usage = "[-a <master socket path>] [<user> [...]]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.cmd = cmd_user,
+	.name = "user",
+	.usage = "[-a <userdb socket path>] [-x <auth info>] [-f field] [-e <value>] [-u] <user mask> [<user mask> [...]]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('x', "auth-info", CMD_PARAM_ARRAY, 0)
+DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('e', "expand-field", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('u', "userdb-only", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+}
 };
 
-static void auth_cmd_help(doveadm_command_t *cmd)
+static void auth_cmd_help(struct doveadm_cmd_context *cctx)
 {
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++) {
-		if (doveadm_cmd_auth[i].cmd == cmd)
-			help(&doveadm_cmd_auth[i]);
+		if (doveadm_cmd_auth[i].cmd == cctx->cmd->cmd)
+			help_ver2(&doveadm_cmd_auth[i]);
 	}
 	i_unreached();
 }
@@ -785,5 +783,5 @@ void doveadm_register_auth_commands(void
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_auth); i++)
-		doveadm_register_cmd(&doveadm_cmd_auth[i]);
+		doveadm_cmd_register_ver2(&doveadm_cmd_auth[i]);
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-auth-server.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-auth-server.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-auth-server.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-auth-server.c	2022-06-14 06:55:03.000000000 +0000
@@ -202,32 +202,21 @@ cmd_user_list(struct auth_master_connect
 	o_stream_nsend_str(doveadm_print_ostream, "]}");
 }
 
-static void cmd_auth_cache_flush(int argc, char *argv[])
+static void cmd_auth_cache_flush(struct doveadm_cmd_context *cctx)
 {
-	const char *master_socket_path = NULL;
+	const char *master_socket_path, *const *users;
 	struct auth_master_connection *conn;
 	unsigned int count;
-	int c;
 
-	while ((c = getopt(argc, argv, "a:")) > 0) {
-		switch (c) {
-		case 'a':
-			master_socket_path = optarg;
-			break;
-		default:
-			doveadm_exit_code = EX_USAGE;
-			return;
-		}
-	}
-	argv += optind;
-
-	if (master_socket_path == NULL) {
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &master_socket_path)) {
 		master_socket_path = t_strconcat(doveadm_settings->base_dir,
 						 "/auth-master", NULL);
 	}
+	if (!doveadm_cmd_param_array(cctx, "user", &users))
+		i_fatal("Missing user parameter");
 
 	conn = doveadm_get_auth_master_conn(master_socket_path);
-	if (auth_master_cache_flush(conn, (void *)argv, &count) < 0) {
+	if (auth_master_cache_flush(conn, users, &count) < 0) {
 		i_error("Cache flush failed");
 		doveadm_exit_code = EX_TEMPFAIL;
 	} else {
@@ -495,7 +484,7 @@ static
 struct doveadm_cmd_ver2 doveadm_cmd_auth_server[] = {
 {
 	.name = "auth cache flush",
-	.old_cmd = cmd_auth_cache_flush,
+	.cmd = cmd_auth_cache_flush,
 	.usage = "[-a <master socket path>] [<user> [...]]",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm.c	2022-06-14 06:55:03.000000000 +0000
@@ -111,145 +111,142 @@ doveadm_usage_compress_lines(FILE *out,
 }
 
 static void ATTR_NORETURN
-usage_to(FILE *out, const char *prefix)
+usage_prefix(const char *prefix)
 {
 	const struct doveadm_cmd_ver2 *cmd2;
-	const struct doveadm_cmd *cmd;
 	string_t *str = t_str_new(1024);
 
-	fprintf(out, "usage: doveadm [-Dv] [-f <formatter>] ");
+	fprintf(stderr, "usage: doveadm [-Dv] [-f <formatter>] ");
 	if (*prefix != '\0')
-		fprintf(out, "%s ", prefix);
-	fprintf(out, "<command> [<args>]\n");
+		fprintf(stderr, "%s ", prefix);
+	fprintf(stderr, "<command> [<args>]\n");
 
-	array_foreach(&doveadm_cmds, cmd)
-		str_printfa(str, "%s\t%s\n", cmd->name, cmd->short_usage);
 	array_foreach(&doveadm_cmds_ver2, cmd2)
 		str_printfa(str, "%s\t%s\n", cmd2->name, cmd2->usage);
 
-	doveadm_mail_usage(str);
-	doveadm_usage_compress_lines(out, str_c(str), prefix);
+	doveadm_usage_compress_lines(stderr, str_c(str), prefix);
 
 	lib_exit(EX_USAGE);
 }
 
 void usage(void)
 {
-	usage_to(stderr, "");
-}
-
-static void ATTR_NORETURN
-help_to(const struct doveadm_cmd *cmd, FILE *out)
-{
-	fprintf(out, "doveadm %s %s\n", cmd->name, cmd->short_usage);
-	lib_exit(EX_USAGE);
-}
-
-void help(const struct doveadm_cmd *cmd)
-{
-	help_to(cmd, stdout);
-}
-
-static void ATTR_NORETURN
-help_to_ver2(const struct doveadm_cmd_ver2 *cmd, FILE *out)
-{
-	fprintf(out, "doveadm %s %s\n", cmd->name, cmd->usage);
-	lib_exit(EX_USAGE);
+	usage_prefix("");
 }
 
 void help_ver2(const struct doveadm_cmd_ver2 *cmd)
 {
-	help_to_ver2(cmd, stdout);
+	fprintf(stderr, "doveadm %s %s\n", cmd->name, cmd->usage);
+	lib_exit(EX_USAGE);
 }
 
-static void cmd_help(int argc ATTR_UNUSED, char *argv[])
+static void cmd_help(struct doveadm_cmd_context *cctx)
 {
-	const char *man_argv[3];
+	const char *cmd, *man_argv[3];
 
-	if (argv[1] == NULL)
-		usage_to(stdout, "");
+	if (!doveadm_cmd_param_str(cctx, "cmd", &cmd))
+		usage_prefix("");
 
 	env_put("MANPATH", MANDIR);
 	man_argv[0] = "man";
-	man_argv[1] = t_strconcat("doveadm-", argv[1], NULL);
+	man_argv[1] = t_strconcat("doveadm-", cmd, NULL);
 	man_argv[2] = NULL;
 	execvp_const(man_argv[0], man_argv);
 }
 
-static struct doveadm_cmd doveadm_cmd_help = {
-	cmd_help, "help", "<cmd>"
+static struct doveadm_cmd_ver2 doveadm_cmd_help = {
+	.name = "help",
+	.cmd = cmd_help,
+	.usage = "[<cmd>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "cmd", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
 
-static void cmd_config(int argc ATTR_UNUSED, char *argv[])
+static void cmd_config(struct doveadm_cmd_context *cctx)
 {
+	const char *const *args, **argv;
+
+	if (!doveadm_cmd_param_array(cctx, "args", &args))
+		args = NULL;
+
 	env_put(MASTER_CONFIG_FILE_ENV,
 		master_service_get_config_path(master_service));
+
+	unsigned int len = str_array_length(args);
+	argv = t_new(const char *, len + 2);
 	argv[0] = BINDIR"/doveconf";
-	(void)execv(argv[0], argv);
-	i_fatal("execv(%s) failed: %m", argv[0]);
+	if (len > 0) {
+		i_assert(args != NULL);
+		memcpy(argv+1, args, len * sizeof(args[0]));
+	}
+	execv_const(argv[0], argv);
 }
 
-static struct doveadm_cmd doveadm_cmd_config = {
-	cmd_config, "config", "[doveconf parameters]"
+static struct doveadm_cmd_ver2 doveadm_cmd_config = {
+	.name = "config",
+	.cmd = cmd_config,
+	.usage = "[doveconf parameters]",
+	.flags = CMD_FLAG_NO_OPTIONS,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
 
-static void cmd_exec(int argc ATTR_UNUSED, char *argv[]);
-static struct doveadm_cmd doveadm_cmd_exec = {
-	cmd_exec, "exec", "<binary> [binary parameters]"
+static void cmd_exec(struct doveadm_cmd_context *cctx);
+static struct doveadm_cmd_ver2 doveadm_cmd_exec = {
+	.name = "exec",
+	.cmd = cmd_exec,
+	.usage = "<binary> [binary parameters]",
+	.flags = CMD_FLAG_NO_OPTIONS,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "binary", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
 
-static void cmd_exec(int argc ATTR_UNUSED, char *argv[])
+static void cmd_exec(struct doveadm_cmd_context *cctx)
 {
-	const char *path, *binary = argv[1];
+	const char *path, *binary, *const *args, **argv;
 
-	if (binary == NULL)
-		help(&doveadm_cmd_exec);
+	if (!doveadm_cmd_param_str(cctx, "binary", &binary))
+		help_ver2(&doveadm_cmd_exec);
+	if (!doveadm_cmd_param_array(cctx, "args", &args))
+		args = NULL;
 
 	path = t_strdup_printf("%s/%s", doveadm_settings->libexec_dir, binary);
-	argv++;
-	argv[0] = t_strdup_noconst(path);
-	(void)execv(argv[0], argv);
-	i_fatal("execv(%s) failed: %m", argv[0]);
-}
-
-static bool doveadm_try_run(const char *cmd_name, int argc,
-			    const char *const argv[])
-{
-	const struct doveadm_cmd *cmd;
 
-	cmd = doveadm_cmd_find_with_args(cmd_name, &argc, &argv);
-	if (cmd == NULL)
-		return FALSE;
-	cmd->cmd(argc, (char **)argv);
-	return TRUE;
+	unsigned int len = str_array_length(args);
+	argv = t_new(const char *, len + 2);
+	argv[0] = path;
+	if (len > 0) {
+		i_assert(args != NULL);
+		memcpy(argv+1, args, len * sizeof(args[0]));
+	}
+	execv_const(argv[0], argv);
 }
 
 static bool doveadm_has_subcommands(const char *cmd_name)
 {
 	const struct doveadm_cmd_ver2 *cmd2;
-	const struct doveadm_cmd *cmd;
 	size_t len = strlen(cmd_name);
 
-	array_foreach(&doveadm_cmds, cmd) {
-		if (strncmp(cmd->name, cmd_name, len) == 0 &&
-		    cmd->name[len] == ' ')
-			return TRUE;
-	}
 	array_foreach(&doveadm_cmds_ver2, cmd2) {
 		if (strncmp(cmd2->name, cmd_name, len) == 0 &&
 		    cmd2->name[len] == ' ')
 			return TRUE;
 	}
-	return doveadm_mail_has_subcommands(cmd_name);
+	return FALSE;
 }
 
-static struct doveadm_cmd *doveadm_cmdline_commands[] = {
-	&doveadm_cmd_help,
+static struct doveadm_cmd_ver2 *doveadm_cmdline_commands_ver2[] = {
 	&doveadm_cmd_config,
-	&doveadm_cmd_exec,
 	&doveadm_cmd_dump,
+	&doveadm_cmd_exec,
+	&doveadm_cmd_help,
+	&doveadm_cmd_oldstats_top_ver2,
 	&doveadm_cmd_pw,
-	&doveadm_cmd_zlibconnect
+	&doveadm_cmd_zlibconnect,
 };
 
 int main(int argc, char *argv[])
@@ -257,7 +254,6 @@ int main(int argc, char *argv[])
 	enum master_service_flags service_flags =
 		MASTER_SERVICE_FLAG_STANDALONE |
 		MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN |
-		MASTER_SERVICE_FLAG_USE_SSL_SETTINGS |
 		MASTER_SERVICE_FLAG_NO_SSL_INIT |
 		MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME;
 	struct doveadm_cmd_context cctx;
@@ -306,10 +302,9 @@ int main(int argc, char *argv[])
 
 	doveadm_settings_init();
 	doveadm_cmds_init();
-	for (i = 0; i < N_ELEMENTS(doveadm_cmdline_commands); i++)
-		doveadm_register_cmd(doveadm_cmdline_commands[i]);
+	for (i = 0; i < N_ELEMENTS(doveadm_cmdline_commands_ver2); i++)
+		doveadm_cmd_register_ver2(doveadm_cmdline_commands_ver2[i]);
 	doveadm_register_auth_commands();
-	doveadm_cmd_register_ver2(&doveadm_cmd_oldstats_top_ver2);
 
 	if (cmd_name != NULL && (quick_init ||
 				 strcmp(cmd_name, "config") == 0 ||
@@ -345,7 +340,7 @@ int main(int argc, char *argv[])
 
 		if (cmd_name == NULL) {
 			/* show usage after registering all plugins */
-			usage_to(stdout, "");
+			usage_prefix("");
 		}
 	}
 
@@ -363,11 +358,9 @@ int main(int argc, char *argv[])
 	   the env pointer */
 	cctx.username = getenv("USER");
 
-	if (!doveadm_cmd_try_run_ver2(cmd_name, argc, (const char**)argv, &cctx) &&
-	    !doveadm_try_run(cmd_name, argc, (const char **)argv) &&
-	    !doveadm_mail_try_run(cmd_name, argc, argv)) {
+	if (!doveadm_cmd_try_run_ver2(cmd_name, argc, (const char**)argv, &cctx)) {
 		if (doveadm_has_subcommands(cmd_name))
-			usage_to(stdout, cmd_name);
+			usage_prefix(cmd_name);
 		if (doveadm_has_unloaded_plugin(cmd_name)) {
 			i_fatal("Unknown command '%s', but plugin %s exists. "
 				"Try to set mail_plugins=%s",
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-cmd.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-cmd.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-cmd.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-cmd.c	2022-06-14 06:55:03.000000000 +0000
@@ -12,19 +12,18 @@
 #include <unistd.h>
 #include <getopt.h>
 
-static struct doveadm_cmd *doveadm_commands[] = {
-	&doveadm_cmd_mailbox_mutf7,
-	&doveadm_cmd_sis_deduplicate,
-	&doveadm_cmd_sis_find,
-};
-
 static struct doveadm_cmd_ver2 *doveadm_commands_ver2[] = {
+	&doveadm_cmd_mailbox_mutf7,
 	&doveadm_cmd_service_stop_ver2,
 	&doveadm_cmd_service_status_ver2,
+	&doveadm_cmd_sis_deduplicate,
+	&doveadm_cmd_sis_find,
 	&doveadm_cmd_process_status_ver2,
 	&doveadm_cmd_stop_ver2,
 	&doveadm_cmd_reload_ver2,
 	&doveadm_cmd_stats_dump_ver2,
+	&doveadm_cmd_stats_add_ver2,
+	&doveadm_cmd_stats_remove_ver2,
 	&doveadm_cmd_oldstats_dump_ver2,
 	&doveadm_cmd_oldstats_reset_ver2,
 	&doveadm_cmd_penalty_ver2,
@@ -47,7 +46,6 @@ static const struct exit_code_str {
 	{ DOVEADM_EX_NOTFOUND, "NOTFOUND" }
 };
 
-ARRAY_TYPE(doveadm_cmd) doveadm_cmds;
 ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2;
 ARRAY_DEFINE_TYPE(getopt_option_array, struct option);
 
@@ -71,18 +69,11 @@ int doveadm_str_to_exit_code(const char
 	return DOVEADM_EX_UNKNOWN;
 }
 
-void doveadm_register_cmd(const struct doveadm_cmd *cmd)
-{
-	array_push_back(&doveadm_cmds, cmd);
-}
-
 void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd)
 {
 	if (cmd->cmd == NULL) {
 		if (cmd->mail_cmd != NULL)
 			cmd->cmd = doveadm_cmd_ver2_to_mail_cmd_wrapper;
-		else if (cmd->old_cmd != NULL)
-			cmd->cmd = doveadm_cmd_ver2_to_cmd_wrapper;
 		else i_unreached();
 	}
 	array_push_back(&doveadm_cmds_ver2, cmd);
@@ -93,7 +84,7 @@ const struct doveadm_cmd_ver2 *doveadm_c
 	const struct doveadm_cmd_ver2 *cmd;
 
 	array_foreach(&doveadm_cmds_ver2, cmd) {
-		if (strcmp(cmd_name, cmd->name)==0)
+		if (strcmp(cmd_name, cmd->name) == 0)
 			return cmd;
 	}
 	return NULL;
@@ -107,8 +98,9 @@ doveadm_cmd_find_with_args_ver2(const ch
 	const struct doveadm_cmd_ver2 *cmd;
 	const char *cptr;
 
-	for(i=0;i<*argc;i++) {
-		if (strcmp((*argv)[i],cmd_name)==0) break;
+	for (i = 0; i < *argc; i++) {
+		if (strcmp((*argv)[i], cmd_name) == 0)
+			break;
 	}
 
 	i_assert(i != *argc);
@@ -117,16 +109,20 @@ doveadm_cmd_find_with_args_ver2(const ch
 		cptr = cmd->name;
 		/* cannot reuse i here because this needs be
 		   done more than once */
-		for (k=0; *cptr != '\0' && i+k < *argc; k++) {
-			size_t alen = strlen((*argv)[i+k]);
+		for (k = 0; *cptr != '\0' && i + k < *argc; k++) {
+			size_t alen = strlen((*argv)[i + k]);
 			/* make sure we don't overstep */
-			if (strlen(cptr) < alen) break;
+			if (strlen(cptr) < alen)
+				break;
 			/* did not match */
-			if (strncmp(cptr, (*argv)[i+k], alen) != 0) break;
+			if (strncmp(cptr, (*argv)[i+k], alen) != 0)
+				break;
 			/* do not accept abbreviations */
-			if (cptr[alen] != ' ' && cptr[alen] != '\0') break;
+			if (cptr[alen] != ' ' && cptr[alen] != '\0')
+				break;
 			cptr += alen;
-			if (*cptr != '\0') cptr++; /* consume space */
+			if (*cptr != '\0')
+				cptr++; /* consume space */
 		}
 		/* name was fully consumed */
 		if (*cptr == '\0') {
@@ -141,74 +137,12 @@ doveadm_cmd_find_with_args_ver2(const ch
 	return NULL;
 }
 
-static bool
-doveadm_cmd_find_multi_word(const char *cmdname, int *_argc,
-			    const char *const *_argv[])
-{
-	int argc = *_argc;
-	const char *const *argv = *_argv;
-	size_t len;
-
-	if (argc < 2)
-		return FALSE;
-
-	len = strlen(argv[1]);
-	if (!str_begins(cmdname, argv[1]))
-		return FALSE;
-
-	argc--; argv++;
-	if (cmdname[len] == ' ') {
-		/* more args */
-		if (!doveadm_cmd_find_multi_word(cmdname + len + 1,
-						 &argc, &argv))
-			return FALSE;
-	} else {
-		if (cmdname[len] != '\0')
-			return FALSE;
-	}
-
-	*_argc = argc;
-	*_argv = argv;
-	return TRUE;
-}
-
-const struct doveadm_cmd *
-doveadm_cmd_find_with_args(const char *cmd_name, int *argc,
-			   const char *const *argv[])
-{
-	const struct doveadm_cmd *cmd;
-	size_t cmd_name_len;
-
-	i_assert(*argc > 0);
-
-	cmd_name_len = strlen(cmd_name);
-	array_foreach(&doveadm_cmds, cmd) {
-		if (strcmp(cmd->name, cmd_name) == 0)
-			return cmd;
-
-		/* see if it matches a multi-word command */
-		if (strncmp(cmd->name, cmd_name, cmd_name_len) == 0 &&
-		    cmd->name[cmd_name_len] == ' ') {
-			const char *subcmd_name = cmd->name + cmd_name_len + 1;
-
-			if (doveadm_cmd_find_multi_word(subcmd_name,
-							argc, argv))
-				return cmd;
-		}
-	}
-	return NULL;
-}
-
 void doveadm_cmds_init(void)
 {
 	unsigned int i;
 
-	i_array_init(&doveadm_cmds, 32);
 	i_array_init(&doveadm_cmds_ver2, 2);
 
-	for (i = 0; i < N_ELEMENTS(doveadm_commands); i++)
-		doveadm_register_cmd(doveadm_commands[i]);
-
 	for (i = 0; i < N_ELEMENTS(doveadm_commands_ver2); i++)
 		doveadm_cmd_register_ver2(doveadm_commands_ver2[i]);
 
@@ -223,18 +157,18 @@ void doveadm_cmds_init(void)
 
 void doveadm_cmds_deinit(void)
 {
-	array_free(&doveadm_cmds);
 	array_free(&doveadm_cmds_ver2);
 }
 
-static const struct doveadm_cmd_param*
+static const struct doveadm_cmd_param *
 doveadm_cmd_param_get(const struct doveadm_cmd_context *cctx,
 		      const char *name)
 {
 	i_assert(cctx != NULL);
 	i_assert(cctx->argv != NULL);
 	for(int i = 0; i < cctx->argc; i++) {
-		if (strcmp(cctx->argv[i].name, name) == 0 && cctx->argv[i].value_set)
+		if (strcmp(cctx->argv[i].name, name) == 0 &&
+		    cctx->argv[i].value_set)
 			return &cctx->argv[i];
 	}
 	return NULL;
@@ -244,7 +178,8 @@ bool doveadm_cmd_param_bool(const struct
 			    const char *name, bool *value_r)
 {
 	const struct doveadm_cmd_param *param;
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 
 	if (param->type == CMD_PARAM_BOOL) {
 		*value_r = param->value.v_bool;
@@ -257,7 +192,8 @@ bool doveadm_cmd_param_int64(const struc
 			     const char *name, int64_t *value_r)
 {
 	const struct doveadm_cmd_param *param;
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 
 	if (param->type == CMD_PARAM_INT64) {
 		*value_r = param->value.v_int64;
@@ -270,7 +206,8 @@ bool doveadm_cmd_param_str(const struct
 			   const char *name, const char **value_r)
 {
 	const struct doveadm_cmd_param *param;
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 
 	if (param->type == CMD_PARAM_STR) {
 		*value_r = param->value.v_string;
@@ -283,7 +220,8 @@ bool doveadm_cmd_param_ip(const struct d
 			  const char *name, struct ip_addr *value_r)
 {
 	const struct doveadm_cmd_param *param;
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 
 	if (param->type == CMD_PARAM_IP) {
 		memcpy(value_r, &param->value.v_ip, sizeof(struct ip_addr));
@@ -298,7 +236,8 @@ bool doveadm_cmd_param_array(const struc
 	const struct doveadm_cmd_param *param;
 	unsigned int count;
 
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 	if (param->type == CMD_PARAM_ARRAY) {
 		*value_r = array_get(&param->value.v_array, &count);
 		/* doveadm_cmd_params_null_terminate_arrays() should have been
@@ -313,7 +252,8 @@ bool doveadm_cmd_param_istream(const str
 			       const char *name, struct istream **value_r)
 {
 	const struct doveadm_cmd_param *param;
-	if ((param = doveadm_cmd_param_get(cctx, name))==NULL) return FALSE;
+	if ((param = doveadm_cmd_param_get(cctx, name)) == NULL)
+		return FALSE;
 
 	if (param->type == CMD_PARAM_ISTREAM) {
 		*value_r = param->value.v_istream;
@@ -334,7 +274,8 @@ void doveadm_cmd_params_clean(ARRAY_TYPE
 	array_clear(pargv);
 }
 
-void doveadm_cmd_params_null_terminate_arrays(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv)
+void doveadm_cmd_params_null_terminate_arrays(
+	ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv)
 {
 	struct doveadm_cmd_param *param;
 
@@ -348,86 +289,11 @@ void doveadm_cmd_params_null_terminate_a
 }
 
 static void
-doveadm_cmd_params_to_argv(const char *name, int pargc, const struct doveadm_cmd_param* params,
-	ARRAY_TYPE(const_string) *argv)
-{
-	bool array_add_opt;
-	int i;
-	const char * const * cptr;
-	i_assert(array_count(argv) == 0);
-	array_push_back(argv, &name);
-
-	ARRAY_TYPE(const_string) pargv;
-	t_array_init(&pargv, 8);
-
-	for(i=0;i<pargc;i++) {
-		const char *optarg = NULL;
-		ARRAY_TYPE(const_string) *target = argv;
-		if ((params[i].flags & CMD_PARAM_FLAG_POSITIONAL) != 0)
-			target = &pargv;
-		/* istreams are special */
-		i_assert(params[i].type != CMD_PARAM_ISTREAM);
-		if (params[i].value_set) {
-			array_add_opt = FALSE;
-			if (params[i].short_opt != '\0') {
-				if (params[i].type == CMD_PARAM_ARRAY) {
-					array_add_opt = TRUE;
-				} else {
-					optarg = t_strdup_printf("-%c", params[i].short_opt);
-					array_push_back(argv, &optarg);
-				}
-			}
-			/* CMD_PARAM_BOOL is implicitly handled above */
-			if (params[i].type == CMD_PARAM_STR) {
-				array_push_back(target,
-						&params[i].value.v_string);
-			} else if (params[i].type == CMD_PARAM_INT64) {
-				const char *tmp = t_strdup_printf("%lld",
-					(long long)params[i].value.v_int64);
-				array_push_back(target, &tmp);
-			} else if (params[i].type == CMD_PARAM_IP) {
-				const char *tmp = net_ip2addr(&params[i].value.v_ip);
-				array_push_back(target, &tmp);
-			} else if (params[i].type == CMD_PARAM_ARRAY) {
-				array_foreach(&params[i].value.v_array, cptr) {
-					if (array_add_opt)
-						array_push_back(argv, &optarg);
-					array_push_back(target, cptr);
-				}
-			}
-		}
-	}
-
-	if (array_count(&pargv) > 0) {
-		const char *dashdash = "--";
-		array_push_back(argv, &dashdash);
-		array_append_array(argv, &pargv);
-	}
-	array_append_zero(argv);
-}
-
-void
-doveadm_cmd_ver2_to_cmd_wrapper(struct doveadm_cmd_context *cctx)
-{
-	unsigned int pargc;
-	const char **pargv;
-
-	i_assert(cctx->cmd->old_cmd != NULL);
-
-	ARRAY_TYPE(const_string) nargv;
-	t_array_init(&nargv, 8);
-	doveadm_cmd_params_to_argv(cctx->cmd->name, cctx->argc, cctx->argv, &nargv);
-	pargv = array_get_modifiable(&nargv, &pargc);
-	i_getopt_reset();
-	cctx->cmd->old_cmd(pargc-1, (char**)pargv);
-}
-
-static void
 doveadm_build_options(const struct doveadm_cmd_param par[],
-		string_t *shortopts,
-		ARRAY_TYPE(getopt_option_array) *longopts)
+		      string_t *shortopts,
+		      ARRAY_TYPE(getopt_option_array) *longopts)
 {
-	for(size_t i=0; par[i].name != NULL; i++) {
+	for (size_t i = 0; par[i].name != NULL; i++) {
 		struct option longopt;
 
 		i_zero(&longopt);
@@ -445,22 +311,22 @@ doveadm_build_options(const struct dovea
 	array_append_zero(longopts);
 }
 
-static void doveadm_fill_param(struct doveadm_cmd_param *param,
-	const char *value, pool_t pool)
+static void
+doveadm_fill_param(struct doveadm_cmd_param *param,
+		   const char *value, pool_t pool)
 {
 	param->value_set = TRUE;
-	switch(param->type) {
+	switch (param->type) {
 	case CMD_PARAM_BOOL:
-		param->value.v_bool = TRUE; break;
+		param->value.v_bool = TRUE;
+		break;
 	case CMD_PARAM_INT64:
-		if (str_to_int64(value, &param->value.v_int64) != 0) {
+		if (str_to_int64(value, &param->value.v_int64) != 0)
 			param->value_set = FALSE;
-		}
 		break;
 	case CMD_PARAM_IP:
-		if (net_addr2ip(value, &param->value.v_ip) != 0) {
+		if (net_addr2ip(value, &param->value.v_ip) != 0)
 			param->value_set = FALSE;
-		}
 		break;
 	case CMD_PARAM_STR:
 		param->value.v_string = p_strdup(pool, value);
@@ -473,12 +339,12 @@ static void doveadm_fill_param(struct do
 		break;
 	case CMD_PARAM_ISTREAM: {
 		struct istream *is;
-		if (strcmp(value,"-") == 0) {
+		if (strcmp(value,"-") == 0)
 			is = i_stream_create_fd(STDIN_FILENO, IO_BLOCK_SIZE);
-		} else {
+		else
 			is = i_stream_create_file(value, IO_BLOCK_SIZE);
-		}
 		param->value.v_istream = is;
+		break;
 	}
 	}
 }
@@ -499,62 +365,86 @@ bool doveadm_cmd_try_run_ver2(const char
 	return TRUE;
 }
 
-int doveadm_cmd_run_ver2(int argc, const char *const argv[],
-			 struct doveadm_cmd_context *cctx)
+static int
+doveadm_cmd_process_options(int argc, const char *const argv[],
+			    struct doveadm_cmd_context *cctx, pool_t pool,
+			    ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv)
 {
 	struct doveadm_cmd_param *param;
-	ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv;
 	ARRAY_TYPE(getopt_option_array) opts;
-	unsigned int pargc;
-	int c,li;
-	pool_t pool = pool_datastack_create();
 	string_t *optbuf = str_new(pool, 64);
 
 	p_array_init(&opts, pool, 4);
 
 	// build parameters
+	if ((cctx->cmd->flags & CMD_FLAG_NO_UNORDERED_OPTIONS) != 0)
+		str_append_c(optbuf, '+');
 	doveadm_build_options(cctx->cmd->parameters, optbuf, &opts);
 
-	p_array_init(&pargv, pool, 20);
-
-	for(pargc=0;cctx->cmd->parameters[pargc].name != NULL;pargc++) {
-		param = array_append_space(&pargv);
-		memcpy(param, &cctx->cmd->parameters[pargc], sizeof(struct doveadm_cmd_param));
+	unsigned int pargc;
+	for (pargc = 0; cctx->cmd->parameters[pargc].name != NULL; pargc++) {
+		param = array_append_space(pargv);
+		memcpy(param, &cctx->cmd->parameters[pargc],
+		       sizeof(struct doveadm_cmd_param));
 		param->value_set = FALSE;
 	}
 	i_assert(pargc == array_count(&opts)-1); /* opts is NULL-terminated */
 
-	while((c = getopt_long(argc, (char*const*)argv, str_c(optbuf), array_front(&opts), &li)) > -1) {
-		switch(c) {
+	if ((cctx->cmd->flags & CMD_FLAG_NO_OPTIONS) != 0) {
+		/* process -parameters as if they were regular parameters */
+		optind = 1;
+		return 0;
+	}
+
+	int c, li;
+	while ((c = getopt_long(argc, (char *const *)argv, str_c(optbuf),
+				array_front(&opts), &li)) > -1) {
+		switch (c) {
 		case 0:
-			for(unsigned int i = 0; i < array_count(&pargv); i++) {
-				const struct option *opt = array_idx(&opts,li);
-				param = array_idx_modifiable(&pargv,i);
+			for (unsigned int i = 0; i < array_count(pargv); i++) {
+				const struct option *opt = array_idx(&opts, li);
+				param = array_idx_modifiable(pargv, i);
 				if (opt->name == param->name)
 					doveadm_fill_param(param, optarg, pool);
 			}
 			break;
 		case '?':
 		case ':':
-			doveadm_cmd_params_clean(&pargv);
+			doveadm_cmd_params_clean(pargv);
 			return -1;
 		default:
 			// hunt the option
-			for(unsigned int i = 0; i < pargc; i++) {
-				const struct option *longopt = array_idx(&opts,i);
+			for (unsigned int i = 0; i < pargc; i++) {
+				const struct option *longopt =
+					array_idx(&opts, i);
 				if (longopt->val == c)
-					doveadm_fill_param(array_idx_modifiable(&pargv,i), optarg, pool);
+					doveadm_fill_param(array_idx_modifiable(pargv, i),
+							   optarg, pool);
 			}
 		}
 	}
+	return 0;
+}
+
+int doveadm_cmd_run_ver2(int argc, const char *const argv[],
+			 struct doveadm_cmd_context *cctx)
+{
+	ARRAY_TYPE(doveadm_cmd_param_arr_t) pargv;
+	unsigned int pargc;
+	pool_t pool = pool_datastack_create();
+
+	p_array_init(&pargv, pool, 20);
+	if (doveadm_cmd_process_options(argc, argv, cctx, pool, &pargv) < 0)
+		return -1;
 
 	/* process positional arguments */
-	for(;optind<argc;optind++) {
+	for (; optind < argc; optind++) {
 		struct doveadm_cmd_param *ptr;
 		bool found = FALSE;
 		array_foreach_modifiable(&pargv, ptr) {
 			if ((ptr->flags & CMD_PARAM_FLAG_POSITIONAL) != 0 &&
-			    (ptr->value_set == FALSE || ptr->type == CMD_PARAM_ARRAY)) {
+			    (ptr->value_set == FALSE ||
+			     ptr->type == CMD_PARAM_ARRAY)) {
 				doveadm_fill_param(ptr, argv[optind], pool);
 				found = TRUE;
 				break;
@@ -562,7 +452,7 @@ int doveadm_cmd_run_ver2(int argc, const
 		}
 		if (!found) {
 			i_error("Extraneous arguments found: %s",
-				t_strarray_join(argv+optind, " "));
+				t_strarray_join(argv + optind, " "));
 			doveadm_cmd_params_clean(&pargv);
 			return -1;
 		}
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-cmd.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-cmd.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-cmd.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-cmd.h	2022-06-14 06:55:03.000000000 +0000
@@ -32,6 +32,12 @@ typedef enum {
 	CMD_FLAG_NONE			= 0x0,
 	CMD_FLAG_HIDDEN			= 0x1,
 	CMD_FLAG_NO_PRINT		= 0x2,
+	/* Don't parse any -options for the command. */
+	CMD_FLAG_NO_OPTIONS		= 0x4,
+	/* Prevent GNU getopt() from finding options after the first
+	   non-option is seen (e.g. "-1 arg -2" would parse -1 but not -2
+	   as option). */
+	CMD_FLAG_NO_UNORDERED_OPTIONS	= 0x8,
 } doveadm_cmd_flag_t;
 
 struct doveadm_cmd_param {
@@ -53,15 +59,8 @@ ARRAY_DEFINE_TYPE(doveadm_cmd_param_arr_
 
 typedef void doveadm_command_ver2_t(struct doveadm_cmd_context *cctx);
 
-struct doveadm_cmd {
-	doveadm_command_t *cmd;
-	const char *name;
-	const char *short_usage;
-};
-
 struct doveadm_cmd_ver2 {
 	doveadm_command_ver2_t *cmd;
-	doveadm_command_t *old_cmd;
 	struct doveadm_mail_cmd_context *(*mail_cmd)(void);
 	const char *name;
 	const char *usage;
@@ -84,25 +83,9 @@ struct doveadm_cmd_context {
 	struct ostream *output;
 };
 
-ARRAY_DEFINE_TYPE(doveadm_cmd, struct doveadm_cmd);
-extern ARRAY_TYPE(doveadm_cmd) doveadm_cmds;
-
 ARRAY_DEFINE_TYPE(doveadm_cmd_ver2, struct doveadm_cmd_ver2);
 extern ARRAY_TYPE(doveadm_cmd_ver2) doveadm_cmds_ver2;
 
-extern struct doveadm_cmd doveadm_cmd_dump;
-extern struct doveadm_cmd doveadm_cmd_pw;
-extern struct doveadm_cmd doveadm_cmd_mailbox_mutf7;
-extern struct doveadm_cmd doveadm_cmd_sis_deduplicate;
-extern struct doveadm_cmd doveadm_cmd_sis_find;
-extern struct doveadm_cmd doveadm_cmd_zlibconnect;
-
-void doveadm_register_cmd(const struct doveadm_cmd *cmd);
-
-const struct doveadm_cmd *
-doveadm_cmd_find_with_args(const char *cmd_name, int *argc,
-			   const char *const *argv[]);
-
 void doveadm_register_auth_commands(void);
 void doveadm_register_auth_server_commands(void);
 void doveadm_register_director_commands(void);
@@ -117,7 +100,6 @@ void doveadm_register_fs_commands(void);
 void doveadm_cmds_init(void);
 void doveadm_cmds_deinit(void);
 
-void doveadm_cmd_ver2_to_cmd_wrapper(struct doveadm_cmd_context *cctx);
 void doveadm_cmd_ver2_to_mail_cmd_wrapper(struct doveadm_cmd_context *cctx);
 
 void doveadm_cmd_register_ver2(struct doveadm_cmd_ver2 *cmd);
@@ -149,17 +131,25 @@ bool doveadm_cmd_param_istream(const str
 void doveadm_cmd_params_clean(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv);
 void doveadm_cmd_params_null_terminate_arrays(ARRAY_TYPE(doveadm_cmd_param_arr_t) *pargv);
 
+extern struct doveadm_cmd_ver2 doveadm_cmd_dump;
 extern struct doveadm_cmd_ver2 doveadm_cmd_service_stop_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_service_status_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_process_status_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_stop_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_reload_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2;
+extern struct doveadm_cmd_ver2 doveadm_cmd_stats_add_ver2;
+extern struct doveadm_cmd_ver2 doveadm_cmd_stats_remove_ver2;
+extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_mutf7;
 extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_reset_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_dump_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_oldstats_top_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_penalty_ver2;
+extern struct doveadm_cmd_ver2 doveadm_cmd_pw;
 extern struct doveadm_cmd_ver2 doveadm_cmd_kick_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_who_ver2;
+extern struct doveadm_cmd_ver2 doveadm_cmd_sis_deduplicate;
+extern struct doveadm_cmd_ver2 doveadm_cmd_sis_find;
+extern struct doveadm_cmd_ver2 doveadm_cmd_zlibconnect;
 
 #endif
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dict.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dict.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dict.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dict.c	2022-06-14 06:55:03.000000000 +0000
@@ -11,12 +11,13 @@
 static int
 cmd_dict_init_full(struct doveadm_cmd_context *cctx,
 		   doveadm_command_ver2_t *cmd ATTR_UNUSED, enum dict_iterate_flags *iter_flags,
-		   struct dict **dict_r)
+		   struct dict **dict_r, struct dict_op_settings *dopset_r)
 {
 	struct dict_settings dict_set;
 	struct dict *dict;
 	bool set = FALSE;
 	const char *dict_uri, *error, *key, *username = "";
+	i_zero(dopset_r);
 
 	if (doveadm_cmd_param_bool(cctx, "exact", &set) && set)
 		*iter_flags |= DICT_ITERATE_FLAG_EXACT_KEY;
@@ -25,6 +26,7 @@ cmd_dict_init_full(struct doveadm_cmd_co
 	if (doveadm_cmd_param_bool(cctx, "no-value", &set) && set)
 		*iter_flags |= DICT_ITERATE_FLAG_NO_VALUE;
 	(void)doveadm_cmd_param_str(cctx, "user", &username);
+	dopset_r->username = username;
 
 	if (!doveadm_cmd_param_str(cctx, "dict-uri", &dict_uri)) {
 		i_error("dictionary URI must be specified");
@@ -52,7 +54,6 @@ cmd_dict_init_full(struct doveadm_cmd_co
 
 	dict_drivers_register_builtin();
 	i_zero(&dict_set);
-	dict_set.username = username;
 	dict_set.base_dir = doveadm_settings->base_dir;
 	if (dict_init(dict_uri, &dict_set, &dict, &error) < 0) {
 		i_error("dict_init(%s) failed: %s", dict_uri, error);
@@ -65,10 +66,11 @@ cmd_dict_init_full(struct doveadm_cmd_co
 
 static int
 cmd_dict_init(struct doveadm_cmd_context *cctx,
-	      doveadm_command_ver2_t *cmd, struct dict **dict_r)
+	      doveadm_command_ver2_t *cmd, struct dict **dict_r,
+	      struct dict_op_settings *set_r)
 {
 	enum dict_iterate_flags iter_flags = 0;
-	return cmd_dict_init_full(cctx, cmd, &iter_flags, dict_r);
+	return cmd_dict_init_full(cctx, cmd, &iter_flags, dict_r, set_r);
 }
 
 struct doveadm_dict_ctx {
@@ -92,6 +94,7 @@ static void cmd_dict_get(struct doveadm_
 	struct doveadm_dict_ctx ctx;
 	struct dict *dict;
 	const char *key;
+	struct dict_op_settings set;
 
 	if (!doveadm_cmd_param_str(cctx, "key", &key)) {
 		i_error("dict-get: Missing key");
@@ -99,7 +102,7 @@ static void cmd_dict_get(struct doveadm_
 		return;
 	}
 
-	if (cmd_dict_init(cctx, cmd_dict_get, &dict) < 0)
+	if (cmd_dict_init(cctx, cmd_dict_get, &dict, &set) < 0)
 		return;
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
@@ -108,7 +111,7 @@ static void cmd_dict_get(struct doveadm_
 	i_zero(&ctx);
 	ctx.pool = pool_alloconly_create("doveadm dict lookup", 512);
 	ctx.ret = -2;
-	dict_lookup_async(dict, key, dict_lookup_callback, &ctx);
+	dict_lookup_async(dict, &set, key, dict_lookup_callback, &ctx);
 	while (ctx.ret == -2)
 		dict_wait(dict);
 	if (ctx.ret < 0) {
@@ -135,6 +138,7 @@ static void cmd_dict_set(struct doveadm_
 	struct dict_transaction_context *trans;
 	const char *error;
 	const char *key, *value = "";
+	struct dict_op_settings set;
 
 	if (!doveadm_cmd_param_str(cctx, "key", &key) ||
 	    !doveadm_cmd_param_str(cctx, "value", &value)) {
@@ -143,10 +147,10 @@ static void cmd_dict_set(struct doveadm_
 		return;
 	}
 
-	if (cmd_dict_init(cctx, cmd_dict_set, &dict) < 0)
+	if (cmd_dict_init(cctx, cmd_dict_set, &dict, &set) < 0)
 		return;
 
-	trans = dict_transaction_begin(dict);
+	trans = dict_transaction_begin(dict, &set);
 	dict_set(trans, key, value);
 	if (dict_transaction_commit(&trans, &error) <= 0) {
 		i_error("dict_transaction_commit() failed: %s", error);
@@ -161,6 +165,7 @@ static void cmd_dict_unset(struct dovead
 	struct dict_transaction_context *trans;
 	const char *error;
 	const char *key;
+	struct dict_op_settings set;
 
 	if (!doveadm_cmd_param_str(cctx, "key", &key)) {
 		i_error("dict unset: Missing key");
@@ -168,10 +173,10 @@ static void cmd_dict_unset(struct dovead
 		return;
 	}
 
-	if (cmd_dict_init(cctx, cmd_dict_unset, &dict) < 0)
+	if (cmd_dict_init(cctx, cmd_dict_unset, &dict, &set) < 0)
 		return;
 
-	trans = dict_transaction_begin(dict);
+	trans = dict_transaction_begin(dict, &set);
 	dict_unset(trans, key);
 	if (dict_transaction_commit(&trans, &error) <= 0) {
 		i_error("dict_transaction_commit() failed: %s", error);
@@ -188,6 +193,7 @@ static void cmd_dict_inc(struct doveadm_
 	const char *key;
 	int64_t diff;
 	int ret;
+	struct dict_op_settings set;
 
 	if (!doveadm_cmd_param_str(cctx, "key", &key) ||
 	    !doveadm_cmd_param_int64(cctx, "difference", &diff)) {
@@ -196,10 +202,10 @@ static void cmd_dict_inc(struct doveadm_
 		return;
 	}
 
-	if (cmd_dict_init(cctx, cmd_dict_inc, &dict) < 0)
+	if (cmd_dict_init(cctx, cmd_dict_inc, &dict, &set) < 0)
 		return;
 
-	trans = dict_transaction_begin(dict);
+	trans = dict_transaction_begin(dict, &set);
 	dict_atomic_inc(trans, key, diff);
 	ret = dict_transaction_commit(&trans, &error);
 	if (ret < 0) {
@@ -219,6 +225,7 @@ static void cmd_dict_iter(struct doveadm
 	enum dict_iterate_flags iter_flags = 0;
 	const char *prefix, *key, *const *values, *error;
 	bool header_printed = FALSE;
+	struct dict_op_settings set;
 
 	if (!doveadm_cmd_param_str(cctx, "prefix", &prefix)) {
 		i_error("dict-iter: Missing prefix");
@@ -226,7 +233,7 @@ static void cmd_dict_iter(struct doveadm
 		return;
 	}
 
-	if (cmd_dict_init_full(cctx, cmd_dict_iter, &iter_flags, &dict) < 0)
+	if (cmd_dict_init_full(cctx, cmd_dict_iter, &iter_flags, &dict, &set) < 0)
 		return;
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TAB);
@@ -234,7 +241,7 @@ static void cmd_dict_iter(struct doveadm
 	if ((iter_flags & DICT_ITERATE_FLAG_NO_VALUE) == 0)
 		doveadm_print_header_simple("value");
 
-	iter = dict_iterate_init(dict, prefix, iter_flags);
+	iter = dict_iterate_init(dict, &set, prefix, iter_flags);
 	while (dict_iterate_values(iter, &key, &values)) {
 		unsigned int values_count = str_array_length(values);
 		if (!header_printed) {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dsync.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dsync.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dsync.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dsync.c	2022-06-14 06:55:03.000000000 +0000
@@ -105,7 +105,6 @@ struct dsync_cmd_context {
 	bool reverse_backup:1;
 	bool remote_user_prefix:1;
 	bool no_mail_sync:1;
-	bool no_mailbox_renames:1;
 	bool local_location_from_arg:1;
 	bool replicator_notify:1;
 	bool exited:1;
@@ -219,11 +218,16 @@ mirror_get_remote_cmd_line(const char *c
 	if (legacy_dsync) {
 		/* we're executing dsync */
 		p = "server";
+	} else if (i > 0 && strcmp(argv[i-1], "dsync-server") == 0) {
+		/* Caller already specified dsync-server in parameters.
+		   This is a common misconfiguration, so just allow it. */
+		p = NULL;
 	} else {
 		/* we're executing doveadm */
 		p = "dsync-server";
 	}
-	array_push_back(&cmd_args, &p);
+	if (p != NULL)
+		array_push_back(&cmd_args, &p);
 	array_append_zero(&cmd_args);
 	*cmd_args_r = array_front(&cmd_args);
 }
@@ -330,29 +334,68 @@ static void doveadm_user_init_dsync(stru
 
 	user->dsyncing = TRUE;
 	for (ns = user->namespaces; ns != NULL; ns = ns->next) {
+		struct dsync_mailbox_list *dlist =
+			p_new(ns->list->pool, struct dsync_mailbox_list, 1);
+		MODULE_CONTEXT_SET(ns->list, dsync_mailbox_list_module, dlist);
+
 		if (ns->list->set.vname_escape_char == '\0') {
 			ns->list->set.vname_escape_char =
 				ns_sep != DSYNC_LIST_VNAME_ESCAPE_CHAR ?
 				DSYNC_LIST_VNAME_ESCAPE_CHAR :
 				DSYNC_LIST_VNAME_ALT_ESCAPE_CHAR;
+		} else {
+			dlist->have_orig_escape_char = TRUE;
 		}
 	}
 }
 
-static bool paths_are_equal(struct mail_user *user1, struct mail_user *user2,
-			    enum mailbox_list_path_type type)
+static bool
+paths_are_equal(struct mail_namespace *ns1, struct mail_namespace *ns2,
+		enum mailbox_list_path_type type)
 {
 	const char *path1, *path2;
 
-	i_assert(user1->namespaces != NULL);
-	i_assert(user2->namespaces != NULL);
-
-	return mailbox_list_get_root_path(user1->namespaces->list, type, &path1) &&
-		mailbox_list_get_root_path(user2->namespaces->list, type, &path2) &&
+	return mailbox_list_get_root_path(ns1->list, type, &path1) &&
+		mailbox_list_get_root_path(ns2->list, type, &path2) &&
 		strcmp(path1, path2) == 0;
 }
 
 static int
+get_dsync_verify_namespace(struct dsync_cmd_context *ctx,
+			   struct mail_user *user, struct mail_namespace **ns_r)
+{
+	struct mail_namespace *ns;
+
+	/* Use the first -n namespace if given */
+	if (array_count(&ctx->namespace_prefixes) > 0) {
+		const char *prefix =
+			array_idx_elem(&ctx->namespace_prefixes, 0);
+		ns = mail_namespace_find(user->namespaces, prefix);
+		if (ns == NULL) {
+			i_error("Namespace not found: '%s'", prefix);
+			ctx->ctx.exit_code = DOVEADM_EX_NOTFOUND;
+			return -1;
+		}
+		*ns_r = ns;
+		return 0;
+	}
+
+	/* Prefer prefix="" namespace over inbox=yes namespace. Either it uses
+	   the global mail_location, which is good, or it might have
+	   overwritten location in case of e.g. using subscriptions file for
+	   all namespaces. This isn't necessarily obvious, so lets make it
+	   clearer by failing if it happens. */
+	if ((user->namespaces->flags & NAMESPACE_FLAG_UNUSABLE) == 0) {
+		*ns_r = user->namespaces;
+		i_assert((*ns_r)->prefix_len == 0);
+	} else {
+		/* fallback to inbox=yes */
+		*ns_r = mail_namespace_find_inbox(user->namespaces);
+	}
+	return 0;
+}
+
+static int
 cmd_dsync_run_local(struct dsync_cmd_context *ctx, struct mail_user *user,
 		    struct dsync_brain *brain, struct dsync_ibc *ibc2,
 		    const char **changes_during_sync_r,
@@ -360,6 +403,7 @@ cmd_dsync_run_local(struct dsync_cmd_con
 {
 	struct dsync_brain *brain2;
 	struct mail_user *user2;
+	struct mail_namespace *ns, *ns2;
 	struct setting_parser_context *set_parser;
 	const char *location, *error;
 	bool brain1_running, brain2_running, changed1, changed2;
@@ -392,21 +436,25 @@ cmd_dsync_run_local(struct dsync_cmd_con
 	}
 	doveadm_user_init_dsync(user2);
 
-	if (mail_namespaces_get_root_sep(user->namespaces) !=
-	    mail_namespaces_get_root_sep(user2->namespaces)) {
-		i_error("Mail locations must use the same "
-			"virtual mailbox hierarchy separator "
-			"(specify separator for the default namespace)");
+	if (get_dsync_verify_namespace(ctx, user, &ns) < 0 ||
+	    get_dsync_verify_namespace(ctx, user2, &ns2) < 0)
+		return -1;
+	if (mail_namespace_get_sep(ns) != mail_namespace_get_sep(ns2)) {
+		i_error("Mail locations must use the same hierarchy separator "
+			"(specify namespace prefix=\"%s\" "
+			"{ separator } explicitly)", ns->prefix);
 		ctx->ctx.exit_code = EX_CONFIG;
 		mail_user_deinit(&user2);
 		return -1;
 	}
-	if (paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_MAILBOX) &&
-	    paths_are_equal(user, user2, MAILBOX_LIST_PATH_TYPE_INDEX)) {
+	if (paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_MAILBOX) &&
+	    paths_are_equal(ns, ns2, MAILBOX_LIST_PATH_TYPE_INDEX)) {
 		i_error("Both source and destination mail_location "
-			"points to same directory: %s",
+			"points to same directory: %s (namespace "
+			"prefix=\"%s\" { location } is set explicitly?)",
 			mailbox_list_get_root_forced(user->namespaces->list,
-						     MAILBOX_LIST_PATH_TYPE_MAILBOX));
+						     MAILBOX_LIST_PATH_TYPE_MAILBOX),
+			ns->prefix);
 		ctx->ctx.exit_code = EX_CONFIG;
 		mail_user_deinit(&user2);
 		return -1;
@@ -457,6 +505,9 @@ static void cmd_dsync_wait_remote(struct
 	io_loop_run(current_ioloop);
 	timeout_remove(&to);
 
+	/* io_loop_run() deactivates the context - put it back */
+	mail_storage_service_io_activate_user(ctx->ctx.cur_service_user);
+
 	if (!ctx->exited) {
 		i_error("Remote command process isn't dying, killing it");
 		if (kill(ctx->remote_pid, SIGKILL) < 0 && errno != ESRCH) {
@@ -664,8 +715,6 @@ cmd_dsync_run(struct doveadm_mail_cmd_co
 		brain_flags |= DSYNC_BRAIN_FLAG_SYNC_VISIBLE_NAMESPACES;
 	if (ctx->purge_remote)
 		brain_flags |= DSYNC_BRAIN_FLAG_PURGE_REMOTE;
-	if (ctx->no_mailbox_renames)
-		brain_flags |= DSYNC_BRAIN_FLAG_NO_MAILBOX_RENAMES;
 
 	if (ctx->backup) {
 		if (ctx->reverse_backup)
@@ -699,6 +748,8 @@ cmd_dsync_run(struct doveadm_mail_cmd_co
 		/* fall through */
 	case DSYNC_RUN_TYPE_STREAM:
 		cmd_dsync_run_remote(user);
+		/* io_loop_run() deactivates the context - put it back */
+		mail_storage_service_io_activate_user(ctx->ctx.cur_service_user);
 		break;
 	}
 
@@ -807,17 +858,18 @@ static void dsync_connected_callback(int
 }
 
 static int dsync_init_ssl_ctx(struct dsync_cmd_context *ctx,
-			      const struct mail_storage_settings *mail_set,
+			      const struct master_service_ssl_settings *ssl_set,
 			      const char **error_r)
 {
-	struct ssl_iostream_settings ssl_set;
+	struct ssl_iostream_settings ssl_ctx_set;
 
 	if (ctx->ssl_ctx != NULL)
 		return 0;
 
-	mail_storage_settings_init_ssl_client_settings(mail_set, &ssl_set);
-
-	return ssl_iostream_client_context_cache_get(&ssl_set, &ctx->ssl_ctx, error_r);
+	master_service_ssl_client_settings_to_iostream_set(ssl_set,
+		pool_datastack_create(), &ssl_ctx_set);
+	return ssl_iostream_client_context_cache_get(&ssl_ctx_set,
+						     &ctx->ssl_ctx, error_r);
 }
 
 static void dsync_server_run_command(struct dsync_cmd_context *ctx,
@@ -845,7 +897,7 @@ static void dsync_server_run_command(str
 
 static int
 dsync_connect_tcp(struct dsync_cmd_context *ctx,
-		  const struct mail_storage_settings *mail_set,
+		  const struct master_service_ssl_settings *ssl_set,
 		  const char *target, bool ssl, const char **error_r)
 {
 	struct doveadm_server *server;
@@ -859,7 +911,7 @@ dsync_connect_tcp(struct dsync_cmd_conte
 	server->hostname = p == NULL ? server->name :
 		p_strdup_until(ctx->ctx.pool, server->name, p);
 	if (ssl) {
-		if (dsync_init_ssl_ctx(ctx, mail_set, &error) < 0) {
+		if (dsync_init_ssl_ctx(ctx, ssl_set, &error) < 0) {
 			*error_r = t_strdup_printf(
 				"Couldn't initialize SSL context: %s", error);
 			return -1;
@@ -897,6 +949,7 @@ dsync_connect_tcp(struct dsync_cmd_conte
 	io_loop_destroy(&ioloop);
 
 	if (ctx->error != NULL) {
+		ssl_iostream_context_unref(&ctx->ssl_ctx);
 		*error_r = ctx->error;
 		ctx->error = NULL;
 		return -1;
@@ -907,7 +960,7 @@ dsync_connect_tcp(struct dsync_cmd_conte
 
 static int
 parse_location(struct dsync_cmd_context *ctx,
-	       const struct mail_storage_settings *mail_set,
+	       const struct master_service_ssl_settings *ssl_set,
 	       const char *location,
 	       const char *const **remote_cmd_args_r, const char **error_r)
 {
@@ -916,13 +969,13 @@ parse_location(struct dsync_cmd_context
 	if (str_begins(location, "tcp:")) {
 		/* TCP connection to remote dsync */
 		ctx->remote_name = location+4;
-		return dsync_connect_tcp(ctx, mail_set, ctx->remote_name,
+		return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name,
 					 FALSE, error_r);
 	}
 	if (str_begins(location, "tcps:")) {
 		/* TCP+SSL connection to remote dsync */
 		ctx->remote_name = location+5;
-		return dsync_connect_tcp(ctx, mail_set, ctx->remote_name,
+		return dsync_connect_tcp(ctx, ssl_set, ctx->remote_name,
 					 TRUE, error_r);
 	}
 
@@ -952,11 +1005,11 @@ static int cmd_dsync_prerun(struct dovea
 	struct doveadm_cmd_context *cctx = _ctx->cctx;
 	const char *const *remote_cmd_args = NULL;
 	const struct mail_user_settings *user_set;
-	const struct mail_storage_settings *mail_set;
+	const struct master_service_ssl_settings *ssl_set;
 	const char *username = "";
 
 	user_set = mail_storage_service_user_get_set(service_user)[0];
-	mail_set = mail_storage_service_user_get_mail_set(service_user);
+	ssl_set = mail_storage_service_user_get_ssl_settings(service_user);
 
 	ctx->fd_in = -1;
 	ctx->fd_out = -1;
@@ -989,7 +1042,7 @@ static int cmd_dsync_prerun(struct dovea
 	}
 
 	if (remote_cmd_args == NULL && ctx->local_location != NULL) {
-		if (parse_location(ctx, mail_set, ctx->local_location,
+		if (parse_location(ctx, ssl_set, ctx->local_location,
 				   &remote_cmd_args, error_r) < 0)
 			return -1;
 	}
@@ -1049,9 +1102,6 @@ cmd_mailbox_dsync_parse_arg(struct dovea
 	case 'd':
 		ctx->default_replica_location = TRUE;
 		break;
-	case 'D':
-		ctx->no_mailbox_renames = TRUE;
-		break;
 	case 'E':
 		/* dsync wrapper detection flag */
 		legacy_dsync = TRUE;
@@ -1232,6 +1282,8 @@ cmd_dsync_server_run(struct doveadm_mail
 				       doveadm_settings->dsync_alt_char[0]);
 
 	io_loop_run(current_ioloop);
+	/* io_loop_run() deactivates the context - put it back */
+	mail_storage_service_io_activate_user(ctx->ctx.cur_service_user);
 
 	if (ctx->replicator_notify) {
 		state_str = t_str_new(128);
@@ -1298,16 +1350,69 @@ static struct doveadm_mail_cmd_context *
 	return &ctx->ctx;
 }
 
-struct doveadm_mail_cmd cmd_dsync_mirror = {
-	cmd_dsync_alloc, "sync",
-	"[-1fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] [-t <start date>] -d|<dest>"
+#define DSYNC_COMMON_PARAMS \
+DOVEADM_CMD_MAIL_COMMON \
+DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('P', "purge-remote", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('R', "reverse-sync", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('l', "lock-timeout", CMD_PARAM_INT64, 0) \
+DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('m', "mailbox", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('g', "mailbox-guid", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('n', "namespace", CMD_PARAM_ARRAY, 0) \
+DOVEADM_CMD_PARAM('N', "all-namespaces", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('x', "exclude-mailbox", CMD_PARAM_ARRAY, 0) \
+DOVEADM_CMD_PARAM('a', "all-mailbox", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('s', "state", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('t', "sync-since-time", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('e', "sync-until-time", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('O', "sync-flags", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('I', "sync-max-size", CMD_PARAM_STR, 0) \
+DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0) \
+DOVEADM_CMD_PARAM('d', "default-destination", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0) \
+DOVEADM_CMD_PARAM('\0', "destination", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+
+#define DSYNC_COMMON_USAGE \
+	"[-l <secs>] [-r <rawlog path>] " \
+	"[-m <mailbox>] [-g <mailbox guid>] [-n <namespace> | -N] " \
+	"[-x <exclude>] [-a <all mailbox>] [-s <state>] [-T <secs>] " \
+	"[-t <start date>] [-e <end date>] [-O <sync flag>] [-I <max size>] " \
+	"-d|<dest>"
+
+struct doveadm_cmd_ver2 doveadm_cmd_dsync_mirror = {
+	.mail_cmd = cmd_dsync_alloc,
+	.name = "sync",
+	.usage = "[-1fPRU] "DSYNC_COMMON_USAGE,
+	.flags = CMD_FLAG_NO_UNORDERED_OPTIONS,
+DOVEADM_CMD_PARAMS_START
+DSYNC_COMMON_PARAMS
+DOVEADM_CMD_PARAM('1', "oneway-sync", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAMS_END
 };
-struct doveadm_mail_cmd cmd_dsync_backup = {
-	cmd_dsync_backup_alloc, "backup",
-	"[-fPRU] [-l <secs>] [-r <rawlog path>] [-m <mailbox>] [-g <mailbox_guid>] [-n <namespace> | -N] [-x <exclude>] [-s <state>] [-t <start date>] -d|<dest>"
+struct doveadm_cmd_ver2 doveadm_cmd_dsync_backup = {
+	.mail_cmd = cmd_dsync_backup_alloc,
+	.name = "backup",
+	.usage = "[-fPRU] "DSYNC_COMMON_USAGE,
+	.flags = CMD_FLAG_NO_UNORDERED_OPTIONS,
+DOVEADM_CMD_PARAMS_START
+DSYNC_COMMON_PARAMS
+DOVEADM_CMD_PARAMS_END
 };
-struct doveadm_mail_cmd cmd_dsync_server = {
-	cmd_dsync_server_alloc, "dsync-server", &doveadm_mail_cmd_hide
+struct doveadm_cmd_ver2 doveadm_cmd_dsync_server = {
+	.mail_cmd = cmd_dsync_server_alloc,
+	.name = "dsync-server",
+	.usage = "[-E] [-r <rawlog path>] [-T <timeout secs>] [-U]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('E', "legacy-dsync", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('r', "rawlog", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('T', "timeout", CMD_PARAM_INT64, 0)
+DOVEADM_CMD_PARAM('U', "replicator-notify", CMD_PARAM_BOOL, 0)
+/* previously dsync-server could have been added twice to the parameters */
+DOVEADM_CMD_PARAM('\0', "ignore-arg", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
 
 void doveadm_dsync_main(int *_argc, char **_argv[])
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dsync.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dsync.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dsync.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dsync.h	2022-06-14 06:55:03.000000000 +0000
@@ -1,9 +1,9 @@
 #ifndef DOVEADM_DSYNC_H
 #define DOVEADM_DSYNC_H
 
-extern struct doveadm_mail_cmd cmd_dsync_mirror;
-extern struct doveadm_mail_cmd cmd_dsync_backup;
-extern struct doveadm_mail_cmd cmd_dsync_server;
+extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_mirror;
+extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_backup;
+extern struct doveadm_cmd_ver2 doveadm_cmd_dsync_server;
 
 void doveadm_dsync_main(int *_argc, char **_argv[]);
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump.c	2022-06-14 06:55:03.000000000 +0000
@@ -41,49 +41,46 @@ dump_find_test(const char *path)
 	return NULL;
 }
 
-static void cmd_dump(int argc, char *argv[])
+static void cmd_dump(struct doveadm_cmd_context *cctx)
 {
 	const struct doveadm_cmd_dump *dump;
-	const char *type = NULL;
-	int c;
+	const char *path, *type = NULL, *const *args = NULL;
+	const char *no_args = NULL;
 
-	while ((c = getopt(argc, argv, "t:")) > 0) {
-		switch (c) {
-		case 't':
-			type = optarg;
-			break;
-		default:
-			help(&doveadm_cmd_dump);
-		}
-	}
-	if (optind == argc)
-		help(&doveadm_cmd_dump);
-
-	optind--;
-	argc -= optind;
-	argv += optind;
+	if (!doveadm_cmd_param_str(cctx, "path", &path))
+		help_ver2(&doveadm_cmd_dump);
+	(void)doveadm_cmd_param_str(cctx, "type", &type);
+	(void)doveadm_cmd_param_array(cctx, "args", &args);
 
-	dump = type != NULL ? dump_find_name(type) : dump_find_test(argv[1]);
+	dump = type != NULL ? dump_find_name(type) : dump_find_test(path);
 	if (dump == NULL) {
 		if (type != NULL) {
 			print_dump_types();
 			i_fatal_status(EX_USAGE, "Unknown type: %s", type);
 		} else {
 			i_fatal_status(EX_DATAERR,
-				"Can't autodetect file type: %s", argv[1]);
+				"Can't autodetect file type: %s", path);
 		}
 	} else {
 		if (type == NULL)
 			printf("Detected file type: %s\n", dump->name);
 	}
-	dump->cmd(argc, argv);
+	dump->cmd(path, args != NULL ? args : &no_args);
 }
 
-struct doveadm_cmd doveadm_cmd_dump = {
-	cmd_dump, "dump", "[-t <type>] <path>"
+struct doveadm_cmd_ver2 doveadm_cmd_dump = {
+	.name = "dump",
+	.cmd = cmd_dump,
+	.usage = "[-t <type>] <path> [<type-specific args>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('t', "type", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "path", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
 
-static void cmd_dump_multiplex(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_multiplex(const char *path, const char *const *args ATTR_UNUSED)
 {
 	const unsigned int channels_count = 256;
 	struct istream *file_input, *channels[channels_count];
@@ -91,7 +88,7 @@ static void cmd_dump_multiplex(int argc
 	size_t size;
 	unsigned int i;
 
-	file_input = i_stream_create_file(argv[1], IO_BLOCK_SIZE);
+	file_input = i_stream_create_file(path, IO_BLOCK_SIZE);
 	/* A bit kludgy: istream-multiplex returns 0 if a wrong channel is
 	   being read from. This causes a panic with blocking istreams.
 	   Work around this by assuming that the file istream isn't blocking. */
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dbox.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dbox.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dbox.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dbox.c	2022-06-14 06:55:03.000000000 +0000
@@ -189,19 +189,19 @@ static bool dump_msg(struct istream *inp
 	return TRUE;
 }
 
-static void cmd_dump_dbox(int argc ATTR_UNUSED, char *argv[])
+static void cmd_dump_dbox(const char *path, const char *const *args ATTR_UNUSED)
 {
 	struct istream *input;
 	int fd;
 	unsigned int hdr_size;
 	bool ret;
 
-	fd = open(argv[1], O_RDONLY);
+	fd = open(path, O_RDONLY);
 	if (fd < 0)
-		i_fatal("open(%s) failed: %m", argv[1]);
+		i_fatal("open(%s) failed: %m", path);
 
 	input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
-	i_stream_set_name(input, argv[1]);
+	i_stream_set_name(input, path);
 	hdr_size = dump_file_hdr(input);
 	do {
 		printf("\n");
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dcrypt-file.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dcrypt-file.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dcrypt-file.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dcrypt-file.c	2022-06-14 06:55:03.000000000 +0000
@@ -77,12 +77,13 @@ static bool test_dump_dcrypt_file(const
 	return ret;
 }
 
-static void cmd_dump_dcrypt_file(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_dcrypt_file(const char *path, const char *const *args ATTR_UNUSED)
 {
 	const char *error = NULL;
 	if (!dcrypt_initialize("openssl", NULL, &error))
 		i_fatal("dcrypt_initialize failed: %s", error);
-	(void)dcrypt_file_dump_metadata(argv[1], TRUE);
+	(void)dcrypt_file_dump_metadata(path, TRUE);
 }
 
 struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_file = {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dcrypt-key.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dcrypt-key.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-dcrypt-key.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-dcrypt-key.c	2022-06-14 06:55:03.000000000 +0000
@@ -199,12 +199,13 @@ static bool test_dump_dcrypt_key(const c
 	return ret;
 }
 
-static void cmd_dump_dcrypt_key(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_dcrypt_key(const char *path, const char *const *args ATTR_UNUSED)
 {
 	const char *error = NULL;
 	if (!dcrypt_initialize("openssl", NULL, &error))
 		i_fatal("dcrypt_initialize: %s", error);
-	(void)dcrypt_key_dump_metadata(argv[1], TRUE);
+	(void)dcrypt_key_dump_metadata(path, TRUE);
 }
 
 struct doveadm_cmd_dump doveadm_cmd_dump_dcrypt_key = {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump.h	2022-06-14 06:55:03.000000000 +0000
@@ -6,7 +6,7 @@
 struct doveadm_cmd_dump {
 	const char *name;
 	bool (*test)(const char *path);
-	doveadm_command_t *cmd;
+	void (*cmd)(const char *path, const char *const *args);
 };
 
 extern struct doveadm_cmd_dump doveadm_cmd_dump_dbox;
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-index.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-index.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-index.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-index.c	2022-06-14 06:55:03.000000000 +0000
@@ -764,20 +764,21 @@ static struct mail_index *path_open_inde
 		return mail_index_alloc(NULL, ".", path);
 }
 
-static void cmd_dump_index(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_index(const char *path, const char *const *args)
 {
 	struct mail_index *index;
 	struct mail_index_view *view;
 	struct mail_cache_view *cache_view;
 	unsigned int seq, uid = 0;
 
-	index = path_open_index(argv[1]);
+	index = path_open_index(path);
 	if (index == NULL ||
 	    mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY) <= 0)
-		i_fatal("Couldn't open index %s", argv[1]);
-	if (argv[2] != NULL) {
-		if (str_to_uint(argv[2], &uid) < 0)
-			i_fatal("Invalid uid number %s", argv[2]);
+		i_fatal("Couldn't open index %s", path);
+	if (args[0] != NULL) {
+		if (str_to_uint(args[0], &uid) < 0)
+			i_fatal("Invalid uid number %s", args[0]);
 	}
 
 	view = mail_index_view_open(index);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-log.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-log.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-log.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-log.c	2022-06-14 06:55:03.000000000 +0000
@@ -518,14 +518,14 @@ static int dump_record(struct istream *i
 	return 1;
 }
 
-static void cmd_dump_log(int argc ATTR_UNUSED, char *argv[])
+static void cmd_dump_log(const char *path, const char *const *args ATTR_UNUSED)
 {
 	struct istream *input;
 	uint64_t modseq;
 	unsigned int version;
 	int ret;
 
-	input = i_stream_create_file(argv[1], SIZE_MAX);
+	input = i_stream_create_file(path, SIZE_MAX);
 	dump_hdr(input, &modseq, &version);
 	do {
 		T_BEGIN {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-mailboxlog.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-mailboxlog.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-mailboxlog.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-mailboxlog.c	2022-06-14 06:55:03.000000000 +0000
@@ -57,13 +57,14 @@ static int dump_record(int fd)
 	return 1;
 }
 
-static void cmd_dump_mailboxlog(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_mailboxlog(const char *path, const char *const *args ATTR_UNUSED)
 {
 	int fd, ret;
 
-	fd = open(argv[1], O_RDONLY);
+	fd = open(path, O_RDONLY);
 	if (fd < 0)
-		i_fatal("open(%s) failed: %m", argv[1]);
+		i_fatal("open(%s) failed: %m", path);
 
 	do {
 		T_BEGIN {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-thread.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-thread.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-dump-thread.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-dump-thread.c	2022-06-14 06:55:03.000000000 +0000
@@ -91,7 +91,8 @@ static int dump_block(const uint8_t *dat
 	return p - data;
 }
 
-static void cmd_dump_thread(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_thread(const char *path, const char *const *args ATTR_UNUSED)
 {
 	unsigned int pos;
 	const void *map, *end;
@@ -99,12 +100,12 @@ static void cmd_dump_thread(int argc ATT
 	uint32_t uid;
 	int fd, ret;
 
-	fd = open(argv[1], O_RDONLY);
+	fd = open(path, O_RDONLY);
 	if (fd < 0)
-		i_fatal("open(%s) failed: %m", argv[1]);
+		i_fatal("open(%s) failed: %m", path);
 
 	if (fstat(fd, &st) < 0)
-		i_fatal("fstat(%s) failed: %m", argv[1]);
+		i_fatal("fstat(%s) failed: %m", path);
 	max_likely_index = (st.st_size / 8) * 2;
 
 	map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-fs.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-fs.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-fs.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-fs.c	2022-06-14 06:55:03.000000000 +0000
@@ -16,32 +16,20 @@
 #include <stdio.h>
 #include <unistd.h>
 
-static void fs_cmd_help(doveadm_command_t *cmd);
-static void cmd_fs_delete(int argc, char *argv[]);
-
-static void cmd_fs_getopt(int *argc, char **argv[])
-{
-	 if (getopt(*argc, *argv, "") == '?')
-		i_fatal("fs_init: Add -- if you have - in arguments");
-	*argc -= optind;
-	*argv += optind;
-}
+static void fs_cmd_help(struct doveadm_cmd_context *cctx) ATTR_NORETURN;
+static void cmd_fs_delete(struct doveadm_cmd_context *cctx);
 
 static struct fs *
-cmd_fs_init(int *argc, char **argv[], int own_arg_count, doveadm_command_t *cmd)
+cmd_fs_init(struct doveadm_cmd_context *cctx)
 {
 	struct ssl_iostream_settings ssl_set;
 	struct fs_settings fs_set;
 	struct fs *fs;
-	const char *error;
+	const char *fs_driver, *fs_args, *error;
 
-	if (own_arg_count > 0) {
-		if (*argc != 2 + own_arg_count)
-			fs_cmd_help(cmd);
-	} else {
-		if (*argc <= 2)
-			fs_cmd_help(cmd);
-	}
+	if (!doveadm_cmd_param_str(cctx, "fs-driver", &fs_driver) ||
+	    !doveadm_cmd_param_str(cctx, "fs-args", &fs_args))
+		fs_cmd_help(cctx);
 
 	doveadm_get_ssl_settings(&ssl_set, pool_datastack_create());
 	ssl_set.verbose = doveadm_debug;
@@ -51,19 +39,17 @@ cmd_fs_init(int *argc, char **argv[], in
 	fs_set.base_dir = doveadm_settings->base_dir;
 	fs_set.debug = doveadm_debug;
 
-	if (fs_init((*argv)[0], (*argv)[1], &fs_set, &fs, &error) < 0)
+	if (fs_init(fs_driver, fs_args, &fs_set, &fs, &error) < 0)
 		i_fatal("fs_init() failed: %s", error);
-
-	*argc += 2;
-	*argv += 2;
 	return fs;
 }
 
-static void cmd_fs_get(int argc, char *argv[])
+static void cmd_fs_get(struct doveadm_cmd_context *cctx)
 {
 	struct fs *fs;
 	struct fs_file *file;
 	struct istream *input;
+	const char *path;
 	const unsigned char *data;
 	size_t size;
 	ssize_t ret;
@@ -71,10 +57,11 @@ static void cmd_fs_get(int argc, char *a
 	doveadm_print_init(DOVEADM_PRINT_TYPE_PAGER);
 	doveadm_print_header("content", "content", DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
 
-	cmd_fs_getopt(&argc, &argv);
-	fs = cmd_fs_init(&argc, &argv, 1, cmd_fs_get);
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "path", &path))
+		fs_cmd_help(cctx);
 
-	file = fs_file_init(fs, argv[0], FS_OPEN_MODE_READONLY);
+	file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
 	input = fs_read_stream(file, IO_BLOCK_SIZE);
 	while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
 		doveadm_print_stream(data, size);
@@ -96,33 +83,25 @@ static void cmd_fs_get(int argc, char *a
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_put(int argc, char *argv[])
+static void cmd_fs_put(struct doveadm_cmd_context *cctx)
 {
 	struct fs *fs;
 	enum fs_properties props;
-	const char *src_path, *dest_path;
+	const char *hash_str, *src_path, *dest_path;
 	struct fs_file *file;
 	struct istream *input;
 	struct ostream *output;
 	buffer_t *hash = NULL;
-	int c;
 
-	while ((c = getopt(argc, argv, "h:")) > 0) {
-		switch (c) {
-		case 'h':
-			hash = t_buffer_create(32);
-			if (hex_to_binary(optarg, hash) < 0)
-				i_fatal("Invalid -h parameter: Hash not in hex");
-			break;
-		default:
-			fs_cmd_help(cmd_fs_put);
-		}
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "input-path", &src_path) ||
+	    !doveadm_cmd_param_str(cctx, "path", &dest_path))
+		fs_cmd_help(cctx);
+	if (doveadm_cmd_param_str(cctx, "hash", &hash_str)) {
+		hash = t_buffer_create(32);
+		if (hex_to_binary(optarg, hash) < 0)
+			i_fatal("Invalid -h parameter: Hash not in hex");
 	}
-	argc -= optind; argv += optind;
-
-	fs = cmd_fs_init(&argc, &argv, 2, cmd_fs_put);
-	src_path = argv[0];
-	dest_path = argv[1];
 
 	file = fs_file_init(fs, dest_path, FS_OPEN_MODE_REPLACE);
 	props = fs_get_properties(fs);
@@ -153,16 +132,16 @@ static void cmd_fs_put(int argc, char *a
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_copy(int argc, char *argv[])
+static void cmd_fs_copy(struct doveadm_cmd_context *cctx)
 {
 	struct fs *fs;
 	struct fs_file *src_file, *dest_file;
 	const char *src_path, *dest_path;
 
-	cmd_fs_getopt(&argc, &argv);
-	fs = cmd_fs_init(&argc, &argv, 2, cmd_fs_copy);
-	src_path = argv[0];
-	dest_path = argv[1];
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "source-path", &src_path) ||
+	    !doveadm_cmd_param_str(cctx, "destination-path", &dest_path))
+		fs_cmd_help(cctx);
 
 	src_file = fs_file_init(fs, src_path, FS_OPEN_MODE_READONLY);
 	dest_file = fs_file_init(fs, dest_path, FS_OPEN_MODE_REPLACE);
@@ -181,16 +160,18 @@ static void cmd_fs_copy(int argc, char *
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_stat(int argc, char *argv[])
+static void cmd_fs_stat(struct doveadm_cmd_context *cctx)
 {
 	struct fs *fs;
 	struct fs_file *file;
 	struct stat st;
+	const char *path;
 
-	cmd_fs_getopt(&argc, &argv);
-	fs = cmd_fs_init(&argc, &argv, 1, cmd_fs_stat);
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "path", &path))
+		fs_cmd_help(cctx);
 
-	file = fs_file_init(fs, argv[0], FS_OPEN_MODE_READONLY);
+	file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED);
 	doveadm_print_formatted_set_format("%{path} size=%{size}");
@@ -213,17 +194,19 @@ static void cmd_fs_stat(int argc, char *
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_metadata(int argc, char *argv[])
+static void cmd_fs_metadata(struct doveadm_cmd_context *cctx)
 {
 	struct fs *fs;
 	struct fs_file *file;
 	const struct fs_metadata *m;
 	const ARRAY_TYPE(fs_metadata) *metadata;
+	const char *path;
 
-	cmd_fs_getopt(&argc, &argv);
-	fs = cmd_fs_init(&argc, &argv, 1, cmd_fs_metadata);
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "path", &path))
+		fs_cmd_help(cctx);
 
-	file = fs_file_init(fs, argv[0], FS_OPEN_MODE_READONLY);
+	file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY);
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED);
 	doveadm_print_formatted_set_format("%{key}=%{value}\n");
@@ -413,26 +396,34 @@ static void cmd_fs_delete_recursive_path
 }
 
 static void
-cmd_fs_delete_recursive(int argc, char *argv[], unsigned int async_count)
+cmd_fs_delete_recursive(struct doveadm_cmd_context *cctx,
+			unsigned int async_count)
 {
 	struct fs *fs;
+	const char *const *paths;
 	unsigned int i;
 
-	fs = cmd_fs_init(&argc, &argv, 0, cmd_fs_delete);
-	for (i = 0; argv[i] != NULL; i++)
-		cmd_fs_delete_recursive_path(fs, argv[i], async_count);
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_array(cctx, "path", &paths))
+		fs_cmd_help(cctx);
+
+	for (i = 0; paths[i] != NULL; i++)
+		cmd_fs_delete_recursive_path(fs, paths[i], async_count);
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_delete_paths(int argc, char *argv[],
+static void cmd_fs_delete_paths(struct doveadm_cmd_context *cctx,
 				unsigned int async_count)
 {
 	struct fs *fs;
 	struct fs_delete_ctx ctx;
+	const char *const *paths;
 	unsigned int i;
 	int ret;
 
-	fs = cmd_fs_init(&argc, &argv, 0, cmd_fs_delete);
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_array(cctx, "path", &paths))
+		fs_cmd_help(cctx);
 
 	i_zero(&ctx);
 	ctx.fs = fs;
@@ -440,9 +431,9 @@ static void cmd_fs_delete_paths(int argc
 	ctx.files_count = I_MAX(async_count, 1);
 	ctx.files = t_new(struct fs_file *, ctx.files_count);
 
-	for (i = 0; argv[i] != NULL; i++) {
+	for (i = 0; paths[i] != NULL; i++) {
 		T_BEGIN {
-			ret = doveadm_fs_delete_async_fname(&ctx, argv[i]);
+			ret = doveadm_fs_delete_async_fname(&ctx, paths[i]);
 		} T_END;
 		if (ret < 0)
 			break;
@@ -451,87 +442,66 @@ static void cmd_fs_delete_paths(int argc
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_delete(int argc, char *argv[])
+static void cmd_fs_delete(struct doveadm_cmd_context *cctx)
 {
 	bool recursive = FALSE;
-	unsigned int async_count = 0;
-	int c;
+	int64_t async_count = 0;
 
-	while ((c = getopt(argc, argv, "Rn:")) > 0) {
-		switch (c) {
-		case 'R':
-			recursive = TRUE;
-			break;
-		case 'n':
-			if (str_to_uint(optarg, &async_count) < 0)
-				i_fatal("Invalid -n parameter: %s", optarg);
-			break;
-		default:
-			fs_cmd_help(cmd_fs_delete);
-		}
-	}
-	argc -= optind; argv += optind;
+	(void)doveadm_cmd_param_bool(cctx, "recursive", &recursive);
+	(void)doveadm_cmd_param_int64(cctx, "max-parallel", &async_count);
 
 	if (recursive)
-		cmd_fs_delete_recursive(argc, argv, async_count);
+		cmd_fs_delete_recursive(cctx, async_count);
 	else
-		cmd_fs_delete_paths(argc, argv, async_count);
+		cmd_fs_delete_paths(cctx, async_count);
 }
 
-static void cmd_fs_iter_full(int argc, char *argv[], enum fs_iter_flags flags,
-			     doveadm_command_t *cmd)
+static void cmd_fs_iter_full(struct doveadm_cmd_context *cctx,
+			     enum fs_iter_flags flags)
 {
 	struct fs *fs;
 	struct fs_iter *iter;
-	const char *fname, *error;
-	int c;
+	const char *path, *fname, *error;
+	bool b;
 
-	while ((c = getopt(argc, argv, "CO")) > 0) {
-		switch (c) {
-		case 'C':
-			flags |= FS_ITER_FLAG_NOCACHE;
-			break;
-		case 'O':
-			flags |= FS_ITER_FLAG_OBJECTIDS;
-			break;
-		default:
-			fs_cmd_help(cmd);
-		}
-	}
-	argc -= optind; argv += optind;
-
-	fs = cmd_fs_init(&argc, &argv, 1, cmd);
+	if (doveadm_cmd_param_bool(cctx, "no-cache", &b) && b)
+		flags |= FS_ITER_FLAG_NOCACHE;
+	if (doveadm_cmd_param_bool(cctx, "object-ids", &b) && b)
+		flags |= FS_ITER_FLAG_OBJECTIDS;
+
+	fs = cmd_fs_init(cctx);
+	if (!doveadm_cmd_param_str(cctx, "path", &path))
+		fs_cmd_help(cctx);
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_FORMATTED);
 	doveadm_print_formatted_set_format("%{path}\n");
 	doveadm_print_header_simple("path");
 
-	iter = fs_iter_init(fs, argv[0], flags);
+	iter = fs_iter_init(fs, path, flags);
 	while ((fname = fs_iter_next(iter)) != NULL) {
 		doveadm_print(fname);
 	}
 	if (fs_iter_deinit(&iter, &error) < 0) {
-		i_error("fs_iter_deinit(%s) failed: %s",
-			argv[0], error);
+		i_error("fs_iter_deinit(%s) failed: %s", path, error);
 		doveadm_exit_code = EX_TEMPFAIL;
 	}
 	fs_deinit(&fs);
 }
 
-static void cmd_fs_iter(int argc, char *argv[])
+static void cmd_fs_iter(struct doveadm_cmd_context *cctx)
 {
-	cmd_fs_iter_full(argc, argv, 0, cmd_fs_iter);
+	cmd_fs_iter_full(cctx, 0);
 }
 
-static void cmd_fs_iter_dirs(int argc, char *argv[])
+static void cmd_fs_iter_dirs(struct doveadm_cmd_context *cctx)
 {
-	cmd_fs_iter_full(argc, argv, FS_ITER_FLAG_DIRS, cmd_fs_iter_dirs);
+	cmd_fs_iter_full(cctx, FS_ITER_FLAG_DIRS);
 }
 
 struct doveadm_cmd_ver2 doveadm_cmd_fs[] = {
 {
 	.name = "fs get",
-	.old_cmd = cmd_fs_get,
+	.cmd = cmd_fs_get,
 	.usage = "<fs-driver> <fs-args> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
@@ -541,7 +511,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs put",
-	.old_cmd = cmd_fs_put,
+	.cmd = cmd_fs_put,
 	.usage = "[-h <hash>] <fs-driver> <fs-args> <input path> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('h', "hash", CMD_PARAM_STR, 0)
@@ -553,7 +523,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs copy",
-	.old_cmd = cmd_fs_copy,
+	.cmd = cmd_fs_copy,
 	.usage = "<fs-driver> <fs-args> <source path> <dest path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
@@ -564,7 +534,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs stat",
-	.old_cmd = cmd_fs_stat,
+	.cmd = cmd_fs_stat,
 	.usage = "<fs-driver> <fs-args> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
@@ -574,7 +544,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs metadata",
-	.old_cmd = cmd_fs_metadata,
+	.cmd = cmd_fs_metadata,
 	.usage = "<fs-driver> <fs-args> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
@@ -584,7 +554,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs delete",
-	.old_cmd = cmd_fs_delete,
+	.cmd = cmd_fs_delete,
 	.usage = "[-R] [-n <count>] <fs-driver> <fs-args> <path> [<path> ...]",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('R', "recursive", CMD_PARAM_BOOL, 0)
@@ -596,7 +566,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs iter",
-	.old_cmd = cmd_fs_iter,
+	.cmd = cmd_fs_iter,
 	.usage = "[--no-cache] [--object-ids] <fs-driver> <fs-args> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('C', "no-cache", CMD_PARAM_BOOL, 0)
@@ -608,7 +578,7 @@ DOVEADM_CMD_PARAMS_END
 },
 {
 	.name = "fs iter-dirs",
-	.old_cmd = cmd_fs_iter_dirs,
+	.cmd = cmd_fs_iter_dirs,
 	.usage = "<fs-driver> <fs-args> <path>",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('\0', "fs-driver", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
@@ -618,12 +588,12 @@ DOVEADM_CMD_PARAMS_END
 }
 };
 
-static void fs_cmd_help(doveadm_command_t *cmd)
+static void fs_cmd_help(struct doveadm_cmd_context *cctx)
 {
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_fs); i++) {
-		if (doveadm_cmd_fs[i].old_cmd == cmd)
+		if (doveadm_cmd_fs[i].cmd == cctx->cmd->cmd)
 			help_ver2(&doveadm_cmd_fs[i]);
 	}
 	i_unreached();
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm.h	2022-06-14 06:55:03.000000000 +0000
@@ -25,7 +25,6 @@ extern bool doveadm_verbose_proctitle;
 extern int doveadm_exit_code;
 
 void usage(void) ATTR_NORETURN;
-void help(const struct doveadm_cmd *cmd) ATTR_NORETURN;
 void help_ver2(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN;
 void doveadm_master_send_signal(int signo);
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-instance.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-instance.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-instance.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-instance.c	2022-06-14 06:55:03.000000000 +0000
@@ -11,9 +11,9 @@
 #include <fcntl.h>
 #include <signal.h>
 
-extern struct doveadm_cmd doveadm_cmd_instance[];
+extern struct doveadm_cmd_ver2 doveadm_cmd_instance[];
 
-static void instance_cmd_help(doveadm_command_t *cmd) ATTR_NORETURN;
+static void instance_cmd_help(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN;
 
 static bool pid_file_read(const char *path)
 {
@@ -44,25 +44,17 @@ static bool pid_file_read(const char *pa
 	return found;
 }
 
-static void cmd_instance_list(int argc, char *argv[])
+static void cmd_instance_list(struct doveadm_cmd_context *cctx)
 {
 	struct master_instance_list *list;
 	struct master_instance_list_iter *iter;
 	const struct master_instance *inst;
 	const char *instance_path, *pidfile_path;
 	bool show_config = FALSE;
-	int c;
+	const char *name = NULL;
 
-	while ((c = getopt(argc, argv, "c")) > 0) {
-		switch (c) {
-		case 'c':
-			show_config = TRUE;
-			break;
-		default:
-			help(&doveadm_cmd_instance[0]);
-		}
-	}
-	argv += optind;
+	(void)doveadm_cmd_param_bool(cctx, "show-config", &show_config);
+	(void)doveadm_cmd_param_str(cctx, "name", &name);
 
 	if (!show_config) {
 		doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
@@ -77,7 +69,7 @@ static void cmd_instance_list(int argc,
 	list = master_instance_list_init(instance_path);
 	iter = master_instance_list_iterate_init(list);
 	while ((inst = master_instance_iterate_list_next(iter)) != NULL) {
-		if (argv[0] != NULL && strcmp(argv[0], inst->name) != 0)
+		if (name != NULL && strcmp(name, inst->name) != 0)
 			continue;
 
 		if (show_config) {
@@ -98,21 +90,21 @@ static void cmd_instance_list(int argc,
 	master_instance_list_deinit(&list);
 }
 
-static void cmd_instance_remove(int argc, char *argv[])
+static void cmd_instance_remove(struct doveadm_cmd_context *cctx)
 {
 	struct master_instance_list *list;
 	const struct master_instance *inst;
-	const char *base_dir, *instance_path;
+	const char *base_dir, *instance_path, *name;
 	int ret;
 
-	if (argc != 2)
-		instance_cmd_help(cmd_instance_remove);
+	if (!doveadm_cmd_param_str(cctx, "name", &name))
+		instance_cmd_help(cctx->cmd);
 
 	instance_path = t_strconcat(service_set->state_dir,
 				    "/"MASTER_INSTANCE_FNAME, NULL);
 	list = master_instance_list_init(instance_path);
-	inst = master_instance_list_find_by_name(list, argv[1]);
-	base_dir = inst != NULL ? inst->base_dir : argv[1];
+	inst = master_instance_list_find_by_name(list, name);
+	base_dir = inst != NULL ? inst->base_dir : name;
 	if ((ret = master_instance_list_remove(list, base_dir)) < 0) {
 		i_error("Failed to remove instance");
 		doveadm_exit_code = EX_TEMPFAIL;
@@ -123,18 +115,33 @@ static void cmd_instance_remove(int argc
 	master_instance_list_deinit(&list);
 }
 
-struct doveadm_cmd doveadm_cmd_instance[] = {
-	{ cmd_instance_list, "instance list", "[-c] [<name>]" },
-	{ cmd_instance_remove, "instance remove", "<name> | <base dir>" }
+struct doveadm_cmd_ver2 doveadm_cmd_instance[] = {
+{
+	.name = "instance list",
+	.cmd = cmd_instance_list,
+	.usage = "[-c] [<name>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('c', "show-config", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "instance remove",
+	.cmd = cmd_instance_remove,
+	.usage = "<name> | <base dir>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+}
 };
 
-static void instance_cmd_help(doveadm_command_t *cmd)
+static void instance_cmd_help(const struct doveadm_cmd_ver2 *cmd)
 {
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++) {
-		if (doveadm_cmd_instance[i].cmd == cmd)
-			help(&doveadm_cmd_instance[i]);
+		if (doveadm_cmd_instance[i].cmd == cmd->cmd)
+			help_ver2(&doveadm_cmd_instance[i]);
 	}
 	i_unreached();
 }
@@ -144,5 +151,5 @@ void doveadm_register_instance_commands(
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++)
-		doveadm_register_cmd(&doveadm_cmd_instance[i]);
+		doveadm_cmd_register_ver2(&doveadm_cmd_instance[i]);
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-log.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-log.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-log.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-log.c	2022-06-14 06:55:03.000000000 +0000
@@ -24,10 +24,8 @@
 #define LOG_ERRORS_FNAME "log-errors"
 #define LOG_TIMESTAMP_FORMAT "%b %d %H:%M:%S"
 
-extern struct doveadm_cmd doveadm_cmd_log[];
-
 static void ATTR_NULL(2)
-cmd_log_test(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+cmd_log_test(struct doveadm_cmd_context *cctx ATTR_UNUSED)
 {
 	struct failure_context ctx;
 	unsigned int i;
@@ -49,7 +47,7 @@ cmd_log_test(int argc ATTR_UNUSED, char
 	}
 }
 
-static void cmd_log_reopen(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+static void cmd_log_reopen(struct doveadm_cmd_context *cctx ATTR_UNUSED)
 {
 	doveadm_master_send_signal(SIGUSR1);
 }
@@ -192,13 +190,14 @@ static void cmd_log_find_syslog_messages
 }
 
 static void
-cmd_log_find_syslog(struct log_find_context *ctx, int argc, char *argv[])
+cmd_log_find_syslog(struct log_find_context *ctx,
+		    struct doveadm_cmd_context *cctx)
 {
 	const char *log_dir;
 	struct stat st;
 
-	if (argc > 1)
-		log_dir = argv[1];
+	if (doveadm_cmd_param_str(cctx, "log-dir", &log_dir))
+		;
 	else if (stat("/var/log", &st) == 0 && S_ISDIR(st.st_mode))
 		log_dir = "/var/log";
 	else if (stat("/var/adm", &st) == 0 && S_ISDIR(st.st_mode))
@@ -208,14 +207,14 @@ cmd_log_find_syslog(struct log_find_cont
 
 	printf("Looking for log files from %s\n", log_dir);
 	cmd_log_find_syslog_files(ctx, log_dir);
-	cmd_log_test(0, NULL);
+	cmd_log_test(cctx);
 
 	/* give syslog some time to write the messages to files */
 	sleep(1);
 	cmd_log_find_syslog_messages(ctx);
 }
 
-static void cmd_log_find(int argc, char *argv[])
+static void cmd_log_find(struct doveadm_cmd_context *cctx)
 {
 	const struct master_service_settings *set;
 	const char *log_file_path;
@@ -256,7 +255,7 @@ static void cmd_log_find(int argc, char
 	    strcmp(set->info_log_path, "syslog") == 0 ||
 	    strcmp(set->debug_log_path, "syslog") == 0) {
 		/* at least some logs were logged via syslog */
-		cmd_log_find_syslog(&ctx, argc, argv);
+		cmd_log_find_syslog(&ctx, cctx);
 	}
 
 	/* print them */
@@ -325,26 +324,16 @@ static void cmd_log_error_write(const ch
 	}
 }
 
-static void cmd_log_errors(int argc, char *argv[])
+static void cmd_log_errors(struct doveadm_cmd_context *cctx)
 {
 	struct istream *input;
 	const char *path, *line, *const *args;
 	time_t min_timestamp = 0;
-	int c, fd;
+	int64_t since_int64;
+	int fd;
 
-	while ((c = getopt(argc, argv, "s:")) > 0) {
-		switch (c) {
-		case 's':
-			if (str_to_time(optarg, &min_timestamp) < 0)
-				i_fatal("Invalid timestamp: %s", optarg);
-			break;
-		default:
-			help(&doveadm_cmd_log[3]);
-		}
-	}
-	argv += optind - 1;
-	if (argv[1] != NULL)
-		help(&doveadm_cmd_log[3]);
+	if (doveadm_cmd_param_int64(cctx, "since", &since_int64))
+		min_timestamp = since_int64;
 
 	path = t_strconcat(doveadm_settings->base_dir,
 			   "/"LOG_ERRORS_FNAME, NULL);
@@ -375,27 +364,43 @@ static void cmd_log_errors(int argc, cha
 	i_stream_destroy(&input);
 }
 
-struct doveadm_cmd doveadm_cmd_log[] = {
-	{ cmd_log_test, "log test", "" },
-	{ cmd_log_reopen, "log reopen", "" },
-	{ cmd_log_find, "log find", "[<dir>]" },
-};
-
-struct doveadm_cmd_ver2 doveadm_cmd_log_errors_ver2 = {
+struct doveadm_cmd_ver2 doveadm_cmd_log[] = {
+{
+	.name = "log test",
+	.cmd = cmd_log_test,
+	.usage = "",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "log reopen",
+	.cmd = cmd_log_reopen,
+	.usage = "",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "log find",
+	.cmd = cmd_log_find,
+	.usage = "[<dir>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "log-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
 	.name = "log errors",
 	.usage = "[-s <min_timestamp>]",
-	.old_cmd = cmd_log_errors,
+	.cmd = cmd_log_errors,
 DOVEADM_CMD_PARAMS_START
-DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('s', "since", CMD_PARAM_INT64, 0)
 DOVEADM_CMD_PARAMS_END
+}
 };
 
 void doveadm_register_log_commands(void)
 {
 	unsigned int i;
 
-	doveadm_cmd_register_ver2(&doveadm_cmd_log_errors_ver2);
-
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_log); i++)
-		doveadm_register_cmd(&doveadm_cmd_log[i]);
+		doveadm_cmd_register_ver2(&doveadm_cmd_log[i]);
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-altmove.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-altmove.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-altmove.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-altmove.c	2022-06-14 06:55:03.000000000 +0000
@@ -24,7 +24,7 @@ cmd_altmove_box(struct doveadm_mail_cmd_
 	enum modify_type modify_type =
 		!reverse ? MODIFY_ADD : MODIFY_REMOVE;
 
-	if (doveadm_mail_iter_init(ctx, info, search_args, 0, NULL, FALSE,
+	if (doveadm_mail_iter_init(ctx, info, search_args, 0, NULL, 0,
 				   &iter) < 0)
 		return -1;
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-batch.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-batch.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-batch.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-batch.c	2022-06-14 06:55:03.000000000 +0000
@@ -38,10 +38,15 @@ static int cmd_batch_run(struct doveadm_
 
 	array_foreach_elem(&ctx->commands, cmd) {
 		cmd->cur_mail_user = user;
-		if (cmd->v.run(cmd, user) < 0) {
+		const char *reason_code =
+			event_reason_code_prefix("doveadm", "cmd_",
+						 cmd->cmd->name);
+		struct event_reason *reason = event_reason_begin(reason_code);
+		ret = cmd->v.run(cmd, user);
+		event_reason_end(&reason);
+		if (ret < 0) {
 			i_assert(cmd->exit_code != 0);
 			_ctx->exit_code = cmd->exit_code;
-			ret = -1;
 			break;
 		}
 		cmd->cur_mail_user = NULL;
@@ -55,26 +60,21 @@ cmd_batch_add(struct batch_cmd_context *
 {
 	struct doveadm_mail_cmd_context *subctx;
 	const struct doveadm_cmd_ver2 *cmd_ver2;
-	struct doveadm_mail_cmd tmpcmd;
 	const struct doveadm_mail_cmd *cmd;
 	const char *getopt_args;
 	int c;
 
 	cmd_ver2 = doveadm_cmd_find_with_args_ver2(argv[0], &argc, &argv);
-
 	if (cmd_ver2 == NULL)
-		cmd = doveadm_mail_cmd_find_from_argv(argv[0], &argc, &argv);
-	else {
-		i_zero(&tmpcmd);
-		tmpcmd.usage_args = cmd_ver2->usage;
-		tmpcmd.name = cmd_ver2->name;
-		tmpcmd.alloc = cmd_ver2->mail_cmd;
-		cmd = &tmpcmd;
-	}
-
-	if (cmd == NULL)
 		i_fatal_status(EX_USAGE, "doveadm batch: '%s' mail command doesn't exist", argv[0]);
 
+	struct doveadm_mail_cmd *dyncmd =
+		p_new(batchctx->ctx.pool, struct doveadm_mail_cmd, 1);
+	dyncmd->usage_args = cmd_ver2->usage;
+	dyncmd->name = cmd_ver2->name;
+	dyncmd->alloc = cmd_ver2->mail_cmd;
+	cmd = dyncmd;
+
 	subctx = doveadm_mail_cmd_init(cmd, doveadm_settings);
 	subctx->full_args = argv + 1;
 	subctx->service_flags |= batchctx->ctx.service_flags;
@@ -154,8 +154,8 @@ static void cmd_batch_deinit(struct dove
 	struct doveadm_mail_cmd_context *cmd;
 
 	array_foreach_elem(&ctx->commands, cmd) {
-		if (cmd->v.deinit != NULL)
-			cmd->v.deinit(cmd);
+		doveadm_mail_cmd_deinit(cmd);
+		doveadm_mail_cmd_free(cmd);
 	}
 }
 
@@ -173,6 +173,14 @@ static struct doveadm_mail_cmd_context *
 	return &ctx->ctx;
 }
 
-struct doveadm_mail_cmd cmd_batch = {
-	cmd_batch_alloc, "batch", "<sep> <cmd1> [<sep> <cmd2> [..]]"
+struct doveadm_cmd_ver2 doveadm_cmd_batch = {
+	.name = "batch",
+	.mail_cmd = cmd_batch_alloc,
+	.usage = "<sep> <cmd1> [<sep> <cmd2> [..]]",
+	.flags = CMD_FLAG_NO_UNORDERED_OPTIONS,
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_MAIL_COMMON
+DOVEADM_CMD_PARAM('\0', "separator", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "args", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail.c	2022-06-14 06:55:03.000000000 +0000
@@ -37,7 +37,6 @@ struct force_resync_cmd_context {
 	bool fsck;
 };
 
-ARRAY_TYPE(doveadm_mail_cmd) doveadm_mail_cmds;
 void (*hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx);
 struct doveadm_mail_cmd_module_register
 	doveadm_mail_cmd_module_register = { 0 };
@@ -299,7 +298,6 @@ static int cmd_force_resync_box(struct d
 		flags |= MAILBOX_FLAG_FSCK;
 
 	box = mailbox_alloc(info->ns->list, info->vname, flags);
-	mailbox_set_reason(box, _ctx->cmd->name);
 	if (mailbox_open(box) < 0) {
 		i_error("Opening mailbox %s failed: %s", info->vname,
 			mailbox_get_last_internal_error(box, NULL));
@@ -459,10 +457,18 @@ doveadm_mail_next_user(struct doveadm_ma
 		return ret;
 	}
 
-	if (ctx->v.run(ctx, ctx->cur_mail_user) < 0) {
-		i_assert(ctx->exit_code != 0);
-	}
+	struct event_reason *reason =
+		event_reason_begin(event_reason_code_prefix("doveadm", "cmd_",
+							    ctx->cmd->name));
+	T_BEGIN {
+		if (ctx->v.run(ctx, ctx->cur_mail_user) < 0) {
+			i_assert(ctx->exit_code != 0);
+		}
+	} T_END;
 	mail_user_deinit(&ctx->cur_mail_user);
+	/* user deinit may still do some work, so finish the reason after it */
+	event_reason_end(&reason);
+
 	mail_storage_service_user_unref(&ctx->cur_service_user);
 	return 1;
 }
@@ -663,10 +669,8 @@ doveadm_mail_cmd_exec(struct doveadm_mai
 		ctx->service_flags |= MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP;
 		doveadm_mail_all_users(ctx, wildcard_user);
 	}
-	if (ctx->search_args != NULL)
-		mail_search_args_unref(&ctx->search_args);
 	doveadm_mail_server_flush();
-	ctx->v.deinit(ctx);
+	doveadm_mail_cmd_deinit(ctx);
 	doveadm_print_flush();
 
 	/* service deinit unloads mail plugins, so do it late */
@@ -676,181 +680,18 @@ doveadm_mail_cmd_exec(struct doveadm_mai
 		doveadm_exit_code = ctx->exit_code;
 }
 
-static void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx)
-{
-	i_stream_unref(&ctx->users_list_input);
-	i_stream_unref(&ctx->cmd_input);
-	pool_unref(&ctx->pool);
-}
-
-static void
-doveadm_mail_cmd(const struct doveadm_mail_cmd *cmd, int argc, char *argv[])
-{
-	struct doveadm_cmd_context cctx;
-	struct doveadm_mail_cmd_context *ctx;
-	const char *getopt_args, *wildcard_user;
-	int c;
-
-	i_zero(&cctx);
-	cctx.conn_type = DOVEADM_CONNECTION_TYPE_CLI;
-	cctx.username = getenv("USER");
-
-	ctx = doveadm_mail_cmdline_init(cmd);
-	ctx->cctx = &cctx;
-	ctx->full_args = (const void *)(argv + 1);
-
-	getopt_args = "AF:S:u:";
-	/* keep context's getopt_args first in case it contains '+' */
-	if (ctx->getopt_args != NULL)
-		getopt_args = t_strconcat(ctx->getopt_args, getopt_args, NULL);
-	i_assert(master_getopt_str_is_valid(getopt_args));
-
-	wildcard_user = NULL;
-	while ((c = getopt(argc, argv, getopt_args)) > 0) {
-		switch (c) {
-		case 'A':
-			ctx->iterate_all_users = TRUE;
-			break;
-		case 'S':
-			doveadm_settings->doveadm_socket_path = optarg;
-			if (doveadm_settings->doveadm_worker_count == 0)
-				doveadm_settings->doveadm_worker_count = 1;
-			break;
-		case 'u':
-			ctx->service_flags |=
-				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
-			cctx.username = optarg;
-			if (strchr(cctx.username, '*') != NULL ||
-			    strchr(cctx.username, '?') != NULL) {
-				wildcard_user = cctx.username;
-				cctx.username = NULL;
-			}
-			break;
-		case 'F':
-			ctx->service_flags |=
-				MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP;
-			wildcard_user = "*";
-			ctx->users_list_input =
-				i_stream_create_file(optarg, 1024);
-			break;
-		default:
-			if (ctx->v.parse_arg == NULL ||
-			    !ctx->v.parse_arg(ctx, c))
-				doveadm_mail_help(cmd);
-		}
-	}
-	argv += optind;
-	if (argv[0] != NULL && cmd->usage_args == NULL) {
-		i_fatal_status(EX_USAGE, "doveadm %s: Unknown parameter: %s",
-			       cmd->name, argv[0]);
-	}
-	ctx->args = (const void *)argv;
-	doveadm_mail_cmd_exec(ctx, wildcard_user);
-	doveadm_mail_cmd_free(ctx);
-}
-
-static bool
-doveadm_mail_cmd_try_find_multi_word(const struct doveadm_mail_cmd *cmd,
-				     const char *cmdname, int *argc,
-				     const char *const **argv)
+void doveadm_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx)
 {
-	size_t len;
-
-	if (*argc < 2)
-		return FALSE;
-	*argc -= 1;
-	*argv += 1;
-
-	len = strlen((*argv)[0]);
-	if (!str_begins(cmdname, (*argv)[0]))
-		return FALSE;
-
-	if (cmdname[len] == ' ') {
-		/* more args */
-		return doveadm_mail_cmd_try_find_multi_word(cmd, cmdname + len + 1,
-							    argc, argv);
-	}
-	if (cmdname[len] != '\0')
-		return FALSE;
-
-	/* match */
-	return TRUE;
-}
-
-const struct doveadm_mail_cmd *
-doveadm_mail_cmd_find_from_argv(const char *cmd_name, int *argc,
-				const char *const **argv)
-{
-	const struct doveadm_mail_cmd *cmd;
-	size_t cmd_name_len;
-	const char *const *orig_argv;
-	int orig_argc;
-
-	i_assert(*argc > 0);
-
-	cmd_name_len = strlen(cmd_name);
-	array_foreach(&doveadm_mail_cmds, cmd) {
-		if (strcmp(cmd->name, cmd_name) == 0)
-			return cmd;
-
-		/* see if it matches a multi-word command */
-		if (strncmp(cmd->name, cmd_name, cmd_name_len) == 0 &&
-		    cmd->name[cmd_name_len] == ' ') {
-			const char *subcmd = cmd->name + cmd_name_len + 1;
-
-			orig_argc = *argc;
-			orig_argv = *argv;
-			if (doveadm_mail_cmd_try_find_multi_word(cmd, subcmd,
-								 argc, argv))
-				return cmd;
-			*argc = orig_argc;
-			*argv = orig_argv;
-		}
-	}
-
-	return NULL;
-}
-
-bool doveadm_mail_try_run(const char *cmd_name, int argc, char *argv[])
-{
-	const struct doveadm_mail_cmd *cmd;
-
-	cmd = doveadm_mail_cmd_find_from_argv(cmd_name, &argc, (void *)&argv);
-	if (cmd == NULL)
-		return FALSE;
-	doveadm_mail_cmd(cmd, argc, argv);
-	return TRUE;
-}
-
-void doveadm_mail_register_cmd(const struct doveadm_mail_cmd *cmd)
-{
-	/* for now we'll just assume that cmd will be permanently in memory */
-	array_push_back(&doveadm_mail_cmds, cmd);
-}
-
-const struct doveadm_mail_cmd *doveadm_mail_cmd_find(const char *cmd_name)
-{
-	const struct doveadm_mail_cmd *cmd;
-
-	array_foreach(&doveadm_mail_cmds, cmd) {
-		if (strcmp(cmd->name, cmd_name) == 0)
-			return cmd;
-	}
-	return NULL;
+	ctx->v.deinit(ctx);
+	if (ctx->search_args != NULL)
+		mail_search_args_unref(&ctx->search_args);
 }
 
-void doveadm_mail_usage(string_t *out)
+void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx)
 {
-	const struct doveadm_mail_cmd *cmd;
-
-	array_foreach(&doveadm_mail_cmds, cmd) {
-		if (cmd->usage_args == &doveadm_mail_cmd_hide)
-			continue;
-		str_printfa(out, "%s\t"DOVEADM_CMD_MAIL_USAGE_PREFIX, cmd->name);
-		if (cmd->usage_args != NULL)
-			str_append(out, cmd->usage_args);
-		str_append_c(out, '\n');
-	}
+	i_stream_unref(&ctx->users_list_input);
+	i_stream_unref(&ctx->cmd_input);
+	pool_unref(&ctx->pool);
 }
 
 void doveadm_mail_help(const struct doveadm_mail_cmd *cmd)
@@ -863,28 +704,10 @@ void doveadm_mail_help(const struct dove
 void doveadm_mail_try_help_name(const char *cmd_name)
 {
 	const struct doveadm_cmd_ver2 *cmd2;
-	const struct doveadm_mail_cmd *cmd;
 
 	cmd2 = doveadm_cmd_find_ver2(cmd_name);
 	if (cmd2 != NULL)
 		help_ver2(cmd2);
-
-	cmd = doveadm_mail_cmd_find(cmd_name);
-	if (cmd != NULL)
-		doveadm_mail_help(cmd);
-}
-
-bool doveadm_mail_has_subcommands(const char *cmd_name)
-{
-	const struct doveadm_mail_cmd *cmd;
-	size_t len = strlen(cmd_name);
-
-	array_foreach(&doveadm_mail_cmds, cmd) {
-		if (strncmp(cmd->name, cmd_name, len) == 0 &&
-		    cmd->name[len] == ' ')
-			return TRUE;
-	}
-	return FALSE;
 }
 
 void doveadm_mail_help_name(const char *cmd_name)
@@ -913,15 +736,11 @@ DOVEADM_CMD_MAIL_COMMON
 DOVEADM_CMD_PARAMS_END
 };
 
-
-static struct doveadm_mail_cmd *mail_commands[] = {
-	&cmd_batch,
-	&cmd_dsync_backup,
-	&cmd_dsync_mirror,
-	&cmd_dsync_server
-};
-
 static struct doveadm_cmd_ver2 *mail_commands_ver2[] = {
+	&doveadm_cmd_batch,
+	&doveadm_cmd_dsync_backup,
+	&doveadm_cmd_dsync_mirror,
+	&doveadm_cmd_dsync_server,
 	&doveadm_cmd_mailbox_metadata_set_ver2,
 	&doveadm_cmd_mailbox_metadata_unset_ver2,
 	&doveadm_cmd_mailbox_metadata_get_ver2,
@@ -960,10 +779,6 @@ void doveadm_mail_init(void)
 {
 	unsigned int i;
 
-	i_array_init(&doveadm_mail_cmds, 32);
-	for (i = 0; i < N_ELEMENTS(mail_commands); i++)
-		doveadm_mail_register_cmd(mail_commands[i]);
-
 	for (i = 0; i < N_ELEMENTS(mail_commands_ver2); i++)
 		doveadm_cmd_register_ver2(mail_commands_ver2[i]);
 }
@@ -992,7 +807,53 @@ void doveadm_mail_init_finish(void)
 void doveadm_mail_deinit(void)
 {
 	mail_storage_deinit();
-	array_free(&doveadm_mail_cmds);
+	module_dir_unload(&mail_storage_service_modules);
+}
+
+static int doveadm_cmd_parse_arg(struct doveadm_mail_cmd_context *mctx,
+				 const struct doveadm_cmd_param *arg,
+				 ARRAY_TYPE(const_string) *full_args)
+{
+	const char *short_opt_str =
+		p_strdup_printf(mctx->pool, "-%c", arg->short_opt);
+	const char *arg_value = NULL;
+
+	switch (arg->type) {
+	case CMD_PARAM_BOOL:
+		break;
+	case CMD_PARAM_INT64:
+		arg_value = dec2str(arg->value.v_int64);
+		break;
+	case CMD_PARAM_IP:
+		arg_value = net_ip2addr(&arg->value.v_ip);
+		break;
+	case CMD_PARAM_STR:
+		arg_value = arg->value.v_string;
+		break;
+	case CMD_PARAM_ARRAY: {
+		const char *str;
+
+		array_foreach_elem(&arg->value.v_array, str) {
+			optarg = (char *)str;
+			if (!mctx->v.parse_arg(mctx, arg->short_opt))
+				return -1;
+			array_push_back(full_args, &short_opt_str);
+			array_push_back(full_args, &str);
+		}
+		return 0;
+	}
+	default:
+		i_panic("Cannot convert parameter %s to short opt", arg->name);
+	}
+
+	optarg = (char *)arg_value;
+	if (!mctx->v.parse_arg(mctx, arg->short_opt))
+		return -1;
+
+	array_push_back(full_args, &short_opt_str);
+	if (arg_value != NULL)
+		array_push_back(full_args, &arg_value);
+	return 0;
 }
 
 void
@@ -1093,37 +954,11 @@ doveadm_cmd_ver2_to_mail_cmd_wrapper(str
 		/* Keep all named special parameters above this line */
 
 		} else if (mctx->v.parse_arg != NULL && arg->short_opt != '\0') {
-			const char *short_opt_str = p_strdup_printf(
-				mctx->pool, "-%c", arg->short_opt);
-
-			switch(arg->type) {
-			case CMD_PARAM_BOOL:
-				optarg = NULL;
-				break;
-			case CMD_PARAM_INT64:
-				optarg = (char*)dec2str(arg->value.v_int64);
-				break;
-			case CMD_PARAM_IP:
-				optarg = (char*)net_ip2addr(&arg->value.v_ip);
-				break;
-			case CMD_PARAM_STR:
-				optarg = (char*)arg->value.v_string;
-				break;
-			default:
-				i_panic("Cannot convert parameter %s to short opt",
-					arg->name);
-			}
-			if (!mctx->v.parse_arg(mctx, arg->short_opt)) {
+			if (doveadm_cmd_parse_arg(mctx, arg, &full_args) < 0) {
 				i_error("Invalid parameter %c", arg->short_opt);
 				doveadm_mail_cmd_free(mctx);
 				doveadm_exit_code = EX_USAGE;
-				return;
 			}
-
-			array_push_back(&full_args, &short_opt_str);
-			if (arg->type == CMD_PARAM_STR)
-				array_push_back(&full_args,
-						&arg->value.v_string);
 		} else if ((arg->flags & CMD_PARAM_FLAG_POSITIONAL) != 0) {
 			/* feed this into pargv */
 			if (arg->type == CMD_PARAM_ARRAY)
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-copymove.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-copymove.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-copymove.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-copymove.c	2022-06-14 06:55:03.000000000 +0000
@@ -32,7 +32,7 @@ cmd_copy_box(struct copy_cmd_context *ct
 	int ret = 0, ret2;
 
 	if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, 0,
-				   NULL, FALSE, &iter) < 0)
+				   NULL, 0, &iter) < 0)
 		return -1;
 
 	/* use a separately committed transaction for each mailbox.
@@ -83,11 +83,14 @@ cmd_copy_alloc_source_user(struct copy_c
 	input = ctx->ctx.storage_service_input;
 	input.username = ctx->source_username;
 
+	mail_storage_service_io_deactivate_user(ctx->ctx.cur_service_user);
 	if (mail_storage_service_lookup_next(ctx->ctx.storage_service, &input,
 					     &ctx->source_service_user,
 					     &ctx->source_user,
 					     &error) < 0)
 		i_fatal("Couldn't lookup user %s: %s", input.username, error);
+	mail_storage_service_io_deactivate_user(ctx->source_service_user);
+	mail_storage_service_io_activate_user(ctx->ctx.cur_service_user);
 }
 
 static int
@@ -109,7 +112,6 @@ cmd_copy_run(struct doveadm_mail_cmd_con
 
 	ns = mail_namespace_find(user->namespaces, ctx->destname);
 	destbox = mailbox_alloc(ns->list, ctx->destname, MAILBOX_FLAG_SAVEONLY);
-	mailbox_set_reason(destbox, _ctx->cmd->name);
 	if (mailbox_open(destbox) < 0) {
 		i_error("Can't open mailbox '%s': %s", ctx->destname,
 			mailbox_get_last_internal_error(destbox, NULL));
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-deduplicate.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-deduplicate.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-deduplicate.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-deduplicate.c	2022-06-14 06:55:03.000000000 +0000
@@ -8,63 +8,11 @@
 #include "doveadm-mail-iter.h"
 #include "doveadm-mail.h"
 
-struct uidlist {
-	struct uidlist *next;
-	uint32_t uid;
-};
-
 struct deduplicate_cmd_context {
 	struct doveadm_mail_cmd_context ctx;
 	bool by_msgid;
 };
 
-static int cmd_deduplicate_uidlist(struct doveadm_mail_cmd_context *_ctx,
-				   struct mailbox *box, struct uidlist *uidlist)
-{
-	struct mailbox_transaction_context *trans;
-	struct mail_search_context *search_ctx;
-	struct mail_search_args *search_args;
-	struct mail_search_arg *arg;
-	struct mail *mail;
-	ARRAY_TYPE(seq_range) uids;
-	int ret = 0;
-
-	/* the uidlist is reversed with oldest mails at the end.
-	   we'll delete everything but the oldest mail. */
-	if (uidlist->next == NULL)
-		return 0;
-
-	t_array_init(&uids, 8);
-	for (; uidlist->next != NULL; uidlist = uidlist->next)
-		seq_range_array_add(&uids, uidlist->uid);
-
-	search_args = mail_search_build_init();
-	arg = mail_search_build_add(search_args, SEARCH_UIDSET);
-	arg->value.seqset = uids;
-
-	trans = mailbox_transaction_begin(box, _ctx->transaction_flags, __func__);
-	search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
-	mail_search_args_unref(&search_args);
-
-	while (mailbox_search_next(search_ctx, &mail))
-		mail_expunge(mail);
-	if (mailbox_search_deinit(&search_ctx) < 0) {
-		i_error("Searching mailbox '%s' failed: %s",
-			mailbox_get_vname(box),
-			mailbox_get_last_internal_error(box, NULL));
-		doveadm_mail_failed_mailbox(_ctx, box);
-		ret = -1;
-	}
-	if (mailbox_transaction_commit(&trans) < 0) {
-		i_error("Committing mailbox '%s' transaction failed: %s",
-			mailbox_get_vname(box),
-			mailbox_get_last_internal_error(box, NULL));
-		doveadm_mail_failed_mailbox(_ctx, box);
-		ret = -1;
-	}
-	return ret;
-}
-
 static int
 cmd_deduplicate_box(struct doveadm_mail_cmd_context *_ctx,
 		    const struct mailbox_info *info,
@@ -73,16 +21,14 @@ cmd_deduplicate_box(struct doveadm_mail_
 	struct deduplicate_cmd_context *ctx =
 		(struct deduplicate_cmd_context *)_ctx;
 	struct doveadm_mail_iter *iter;
-	struct mailbox *box;
 	struct mail *mail;
 	enum mail_error error;
 	pool_t pool;
-	HASH_TABLE(const char *, struct uidlist *) hash;
+	HASH_TABLE(const char *, void *) hash;
 	const char *key, *errstr;
-	struct uidlist *value;
 	int ret = 0;
 
-	if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, FALSE,
+	if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, 0,
 				   &iter) < 0)
 		return -1;
 
@@ -113,46 +59,20 @@ cmd_deduplicate_box(struct doveadm_mail_
 			}
 		}
 		if (key != NULL && *key != '\0') {
-			value = p_new(pool, struct uidlist, 1);
-			value->uid = mail->uid;
-			value->next = hash_table_lookup(hash, key);
-
-			if (value->next == NULL) {
+			if (hash_table_lookup(hash, key) != NULL)
+				mail_expunge(mail);
+			else {
 				key = p_strdup(pool, key);
-				hash_table_insert(hash, key, value);
-			} else {
-				hash_table_update(hash, key, value);
+				hash_table_insert(hash, key, POINTER_CAST(1));
 			}
 		}
 	}
 
-	if (doveadm_mail_iter_deinit_keep_box(&iter, &box) < 0)
+	if (doveadm_mail_iter_deinit_sync(&iter) < 0)
 		ret = -1;
 
-	if (ret == 0) {
-		struct hash_iterate_context *iter;
-
-		iter = hash_table_iterate_init(hash);
-		while (hash_table_iterate(iter, hash, &key, &value)) {
-			T_BEGIN {
-				if (cmd_deduplicate_uidlist(_ctx, box, value) < 0)
-					ret = -1;
-			} T_END;
-		}
-		hash_table_iterate_deinit(&iter);
-	}
-
 	hash_table_destroy(&hash);
 	pool_unref(&pool);
-
-	if (mailbox_sync(box, 0) < 0) {
-		i_error("Syncing mailbox '%s' failed: %s",
-			mailbox_get_vname(box),
-			mailbox_get_last_internal_error(box, NULL));
-		doveadm_mail_failed_mailbox(_ctx, box);
-		ret = -1;
-	}
-	mailbox_free(&box);
 	return ret;
 }
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-expunge.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-expunge.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-expunge.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-expunge.c	2022-06-14 06:55:03.000000000 +0000
@@ -26,7 +26,7 @@ cmd_expunge_box(struct doveadm_mail_cmd_
 	enum mail_error error;
 	int ret = 0;
 
-	if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, FALSE,
+	if (doveadm_mail_iter_init(_ctx, info, search_args, 0, NULL, 0,
 				   &iter) < 0)
 		return -1;
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-fetch.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-fetch.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-fetch.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-fetch.c	2022-06-14 06:55:03.000000000 +0000
@@ -609,7 +609,7 @@ cmd_fetch_box(struct fetch_cmd_context *
 	if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args,
 				   ctx->wanted_fields,
 				   array_front(&ctx->header_fields),
-				   FALSE,
+				   DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT,
 				   &iter) < 0)
 		return -1;
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-flags.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-flags.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-flags.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-flags.c	2022-06-14 06:55:03.000000000 +0000
@@ -26,7 +26,7 @@ cmd_flags_run_box(struct flags_cmd_conte
 	struct mail_keywords *kw = NULL;
 
 	if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args,
-				   0, NULL, FALSE, &iter) < 0)
+				   0, NULL, 0, &iter) < 0)
 		return -1;
 	box = doveadm_mail_iter_get_mailbox(iter);
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail.h	2022-06-14 06:55:03.000000000 +0000
@@ -105,9 +105,7 @@ struct doveadm_mail_cmd {
 	const char *name;
 	const char *usage_args;
 };
-ARRAY_DEFINE_TYPE(doveadm_mail_cmd, struct doveadm_mail_cmd);
 
-extern ARRAY_TYPE(doveadm_mail_cmd) doveadm_mail_cmds;
 extern void (*hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx);
 extern struct doveadm_mail_cmd_module_register doveadm_mail_cmd_module_register;
 extern char doveadm_mail_cmd_hide;
@@ -115,26 +113,19 @@ extern char doveadm_mail_cmd_hide;
 bool doveadm_is_killed(void);
 int doveadm_killed_signo(void);
 
-bool doveadm_mail_try_run(const char *cmd_name, int argc, char *argv[]);
-void doveadm_mail_register_cmd(const struct doveadm_mail_cmd *cmd);
-const struct doveadm_mail_cmd *doveadm_mail_cmd_find(const char *cmd_name);
-
-void doveadm_mail_usage(string_t *out);
 void doveadm_mail_help(const struct doveadm_mail_cmd *cmd) ATTR_NORETURN;
 void doveadm_mail_help_name(const char *cmd_name) ATTR_NORETURN;
 void doveadm_mail_try_help_name(const char *cmd_name);
-bool doveadm_mail_has_subcommands(const char *cmd_name);
 
 void doveadm_mail_init(void);
 void doveadm_mail_init_finish(void);
 void doveadm_mail_deinit(void);
 
-const struct doveadm_mail_cmd *
-doveadm_mail_cmd_find_from_argv(const char *cmd_name, int *argc,
-				const char *const **argv);
 struct doveadm_mail_cmd_context *
 doveadm_mail_cmd_init(const struct doveadm_mail_cmd *cmd,
 		      const struct doveadm_settings *set);
+void doveadm_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx);
+void doveadm_mail_cmd_free(struct doveadm_mail_cmd_context *ctx);
 int doveadm_mail_single_user(struct doveadm_mail_cmd_context *ctx,
 			     const char **error_r);
 int doveadm_mail_server_user(struct doveadm_mail_cmd_context *ctx,
@@ -170,8 +161,7 @@ void doveadm_mail_failed_mailbox(struct
 void doveadm_mail_failed_list(struct doveadm_mail_cmd_context *ctx,
 			      struct mailbox_list *list);
 
-extern struct doveadm_mail_cmd cmd_batch;
-
+extern struct doveadm_cmd_ver2 doveadm_cmd_batch;
 extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_set_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_unset_ver2;
 extern struct doveadm_cmd_ver2 doveadm_cmd_mailbox_metadata_get_ver2;
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-import.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-import.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-import.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-import.c	2022-06-14 06:55:03.000000000 +0000
@@ -63,7 +63,6 @@ dest_mailbox_open_or_create(struct impor
 	}
 
 	box = mailbox_alloc(ns->list, name, MAILBOX_FLAG_SAVEONLY);
-	mailbox_set_reason(box, ctx->ctx.cmd->name);
 	if (mailbox_create(box, NULL, FALSE) < 0) {
 		errstr = mailbox_get_last_internal_error(box, &error);
 		if (error != MAIL_ERROR_EXISTS) {
@@ -136,7 +135,8 @@ cmd_import_box(struct import_cmd_context
 	struct mail *mail;
 	int ret = 0;
 
-	if (doveadm_mail_iter_init(&ctx->ctx, info, search_args, 0, NULL, TRUE,
+	if (doveadm_mail_iter_init(&ctx->ctx, info, search_args, 0, NULL,
+				   DOVEADM_MAIL_ITER_FLAG_READONLY,
 				   &iter) < 0)
 		return -1;
 
@@ -171,6 +171,7 @@ static void cmd_import_init_source_user(
 			 ctx->src_username :
 			 dest_user->username;
 
+	mail_storage_service_io_deactivate_user(ctx->ctx.cur_service_user);
 	input.flags_override_add = MAIL_STORAGE_SERVICE_FLAG_NO_NAMESPACES |
 		MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS;
 	if (mail_storage_service_lookup_next(ctx->ctx.storage_service, &input,
@@ -180,7 +181,9 @@ static void cmd_import_init_source_user(
 		i_fatal("Import namespace initialization failed: %s", error);
 
 	ctx->src_user = user;
+	mail_storage_service_io_deactivate_user(service_user);
 	mail_storage_service_user_unref(&service_user);
+	mail_storage_service_io_activate_user(ctx->ctx.cur_service_user);
 }
 
 static int
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-index.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-index.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-index.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-index.c	2022-06-14 06:55:03.000000000 +0000
@@ -114,7 +114,6 @@ cmd_index_box(struct index_cmd_context *
 
 	box = mailbox_alloc(info->ns->list, info->vname,
 			    MAILBOX_FLAG_IGNORE_ACLS);
-	mailbox_set_reason(box, ctx->ctx.cmd->name);
 	if (ctx->max_recent_msgs != 0) {
 		/* index only if there aren't too many recent messages.
 		   don't bother syncing the mailbox, that alone can take a
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-iter.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-iter.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-iter.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-iter.c	2022-06-14 06:55:03.000000000 +0000
@@ -1,15 +1,18 @@
 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "ostream.h"
 #include "mail-storage.h"
 #include "mail-namespace.h"
 #include "mail-search.h"
+#include "doveadm-print.h"
 #include "doveadm-mail.h"
 #include "doveadm-mail-iter.h"
 
 struct doveadm_mail_iter {
 	struct doveadm_mail_cmd_context *ctx;
 	struct mail_search_args *search_args;
+	enum doveadm_mail_iter_flags flags;
 
 	struct mailbox *box;
 	struct mailbox_transaction_context *t;
@@ -22,7 +25,7 @@ int doveadm_mail_iter_init(struct dovead
 			   struct mail_search_args *search_args,
 			   enum mail_fetch_field wanted_fields,
 			   const char *const *wanted_headers,
-			   bool readonly,
+			   enum doveadm_mail_iter_flags flags,
 			   struct doveadm_mail_iter **iter_r)
 {
 	struct doveadm_mail_iter *iter;
@@ -31,13 +34,14 @@ int doveadm_mail_iter_init(struct dovead
 	enum mail_error error;
 
 	enum mailbox_flags readonly_flag =
-		readonly ? MAILBOX_FLAG_READONLY : 0;
+		(flags & DOVEADM_MAIL_ITER_FLAG_READONLY) != 0 ?
+		MAILBOX_FLAG_READONLY : 0;
 
 	iter = i_new(struct doveadm_mail_iter, 1);
 	iter->ctx = ctx;
+	iter->flags = flags;
 	iter->box = mailbox_alloc(info->ns->list, info->vname,
 				  MAILBOX_FLAG_IGNORE_ACLS | readonly_flag);
-	mailbox_set_reason(iter->box, ctx->cmd->name);
 	iter->search_args = search_args;
 
 	if (mailbox_sync(iter->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
@@ -158,6 +162,11 @@ bool doveadm_mail_iter_next(struct dovea
 		iter->killed = TRUE;
 		return FALSE;
 	}
+	if ((iter->flags & DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT) != 0 &&
+	    doveadm_print_ostream->stream_errno != 0) {
+		iter->killed = TRUE;
+		return FALSE;
+	}
 	return mailbox_search_next(iter->search_ctx, mail_r);
 }
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-iter.h 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-iter.h
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-iter.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-iter.h	2022-06-14 06:55:03.000000000 +0000
@@ -3,6 +3,13 @@
 
 #include "mailbox-list-iter.h"
 
+enum doveadm_mail_iter_flags {
+	/* Open the mailbox with MAILBOX_FLAG_READONLY */
+	DOVEADM_MAIL_ITER_FLAG_READONLY = BIT(0),
+	/* Stop the iteration if client is detected to be disconnected. */
+	DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT = BIT(1),
+};
+
 struct doveadm_mail_iter;
 struct doveadm_mail_cmd_context;
 
@@ -11,7 +18,7 @@ int doveadm_mail_iter_init(struct dovead
 			   struct mail_search_args *search_args,
 			   enum mail_fetch_field wanted_fields,
 			   const char *const *wanted_headers,
-			   bool readonly,
+			   enum doveadm_mail_iter_flags flags,
 			   struct doveadm_mail_iter **iter_r) ATTR_NULL(6);
 int doveadm_mail_iter_deinit(struct doveadm_mail_iter **iter);
 int doveadm_mail_iter_deinit_sync(struct doveadm_mail_iter **iter);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox.c	2022-06-14 06:55:03.000000000 +0000
@@ -241,7 +241,6 @@ cmd_mailbox_create_run(struct doveadm_ma
 		}
 
 		box = mailbox_alloc(ns->list, name, 0);
-		mailbox_set_reason(box, _ctx->cmd->name);
 		if (mailbox_create(box, &ctx->update, directory) < 0) {
 			i_error("Can't create mailbox %s: %s", name,
 				mailbox_get_last_internal_error(box, NULL));
@@ -370,7 +369,6 @@ cmd_mailbox_delete_run(struct doveadm_ma
 	array_foreach_elem(mailboxes, name) {
 		ns = mail_namespace_find(user->namespaces, name);
 		box = mailbox_alloc(ns->list, name, mailbox_flags);
-		mailbox_set_reason(box, _ctx->cmd->name);
 		storage = mailbox_get_storage(box);
 		ret2 = ctx->require_empty ? mailbox_delete_empty(box) :
 			mailbox_delete(box);
@@ -463,8 +461,6 @@ cmd_mailbox_rename_run(struct doveadm_ma
 	newns = mail_namespace_find(user->namespaces, newname);
 	oldbox = mailbox_alloc(oldns->list, oldname, 0);
 	newbox = mailbox_alloc(newns->list, newname, 0);
-	mailbox_set_reason(oldbox, _ctx->cmd->name);
-	mailbox_set_reason(newbox, _ctx->cmd->name);
 	if (mailbox_rename(oldbox, newbox) < 0) {
 		i_error("Can't rename mailbox %s to %s: %s", oldname, newname,
 			mailbox_get_last_internal_error(oldbox, NULL));
@@ -527,7 +523,6 @@ cmd_mailbox_subscribe_run(struct doveadm
 	array_foreach_elem(&ctx->mailboxes, name) {
 		ns = mail_namespace_find(user->namespaces, name);
 		box = mailbox_alloc(ns->list, name, 0);
-		mailbox_set_reason(box, _ctx->cmd->name);
 		if (mailbox_set_subscribed(box, ctx->ctx.subscriptions) < 0) {
 			i_error("Can't %s mailbox %s: %s", name,
 				ctx->ctx.subscriptions ? "subscribe to" :
@@ -655,7 +650,6 @@ int cmd_mailbox_update_run(struct dovead
 
 	ns = mail_namespace_find(user->namespaces, ctx->mailbox);
 	box = mailbox_alloc(ns->list, ctx->mailbox, 0);
-	mailbox_set_reason(box, _ctx->cmd->name);
 
 	if ((ret = mailbox_update(box, &(ctx->update))) != 0) {
 		i_error("Cannot update %s: %s",
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-cache.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-cache.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-cache.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-cache.c	2022-06-14 06:55:03.000000000 +0000
@@ -250,7 +250,7 @@ static int cmd_mailbox_cache_remove_box(
 	int ret = 0, count = 0;
 
 	if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args,
-				   0, NULL, FALSE, &iter) < 0)
+				   0, NULL, 0, &iter) < 0)
 		return -1;
 
 	box = doveadm_mail_iter_get_mailbox(iter);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-metadata.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-metadata.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-metadata.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-metadata.c	2022-06-14 06:55:03.000000000 +0000
@@ -66,7 +66,6 @@ cmd_mailbox_metadata_get_mailbox(struct
 		*box_r = mailbox_alloc((*ns_r)->list, mctx->mailbox,
 				       MAILBOX_FLAG_ATTRIBUTE_SESSION);
 	}
-	mailbox_set_reason(*box_r, mctx->ctx.cmd->name);
 
 	if (op == DOVEADM_METADATA_OP_SET &&
 	    mailbox_open(*box_r) < 0) {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-status.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-status.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-mailbox-status.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-mailbox-status.c	2022-06-14 06:55:03.000000000 +0000
@@ -136,7 +136,6 @@ status_mailbox(struct status_cmd_context
 	struct mailbox_metadata metadata;
 
 	box = doveadm_mailbox_find(ctx->ctx.cur_mail_user, info->vname);
-	mailbox_set_reason(box, ctx->ctx.cmd->name);
 	if (mailbox_get_status(box, ctx->status_items, &status) < 0 ||
 	    mailbox_get_metadata(box, ctx->metadata_items, &metadata) < 0) {
 		i_error("Mailbox %s: Failed to lookup mailbox status: %s",
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-rebuild.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-rebuild.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-rebuild.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-rebuild.c	2022-06-14 06:55:03.000000000 +0000
@@ -18,7 +18,7 @@ cmd_rebuild_attachment_box(struct dovead
 
 	if (doveadm_mail_iter_init(ctx, info, ctx->search_args,
 				   MAIL_FETCH_IMAP_BODYSTRUCTURE|
-				   MAIL_FETCH_MESSAGE_PARTS, NULL, FALSE,
+				   MAIL_FETCH_MESSAGE_PARTS, NULL, 0,
 				   &iter) < 0)
 		return -1;
 
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-save.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-save.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-save.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-save.c	2022-06-14 06:55:03.000000000 +0000
@@ -91,7 +91,6 @@ cmd_save_run(struct doveadm_mail_cmd_con
 
 	ns = mail_namespace_find(user->namespaces, ctx->mailbox);
 	box = mailbox_alloc(ns->list, ctx->mailbox, MAILBOX_FLAG_SAVEONLY);
-	mailbox_set_reason(box, _ctx->cmd->name);
 	ret = cmd_save_to_mailbox(ctx, box, _ctx->cmd_input);
 	mailbox_free(&box);
 	return ret;
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-search.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-search.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-search.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-search.c	2022-06-14 06:55:03.000000000 +0000
@@ -20,7 +20,8 @@ cmd_search_box(struct doveadm_mail_cmd_c
 	const char *guid_str;
 	int ret = 0;
 
-	if (doveadm_mail_iter_init(ctx, info, ctx->search_args, 0, NULL, FALSE,
+	if (doveadm_mail_iter_init(ctx, info, ctx->search_args, 0, NULL,
+				   DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT,
 				   &iter) < 0)
 		return -1;
 	box = doveadm_mail_iter_get_mailbox(iter);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-server.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-server.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mail-server.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mail-server.c	2022-06-14 06:55:03.000000000 +0000
@@ -7,6 +7,7 @@
 #include "strescape.h"
 #include "ioloop.h"
 #include "master-service.h"
+#include "iostream-ssl.h"
 #include "auth-master.h"
 #include "mail-storage.h"
 #include "mail-storage-service.h"
@@ -373,6 +374,7 @@ static void doveadm_servers_destroy_all_
 			conn = *connp;
 			server_connection_destroy(&conn);
 		}
+		ssl_iostream_context_unref(&server->ssl_ctx);
 	}
 	hash_table_iterate_deinit(&iter);
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-master.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-master.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-master.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-master.c	2022-06-14 06:55:03.000000000 +0000
@@ -81,12 +81,12 @@ void doveadm_master_send_signal(int sign
 	}
 }
 
-static void cmd_stop(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+static void cmd_stop(struct doveadm_cmd_context *cctx ATTR_UNUSED)
 {
 	doveadm_master_send_signal(SIGTERM);
 }
 
-static void cmd_reload(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+static void cmd_reload(struct doveadm_cmd_context *cctx ATTR_UNUSED)
 {
 	doveadm_master_send_signal(SIGHUP);
 }
@@ -254,7 +254,7 @@ static void cmd_process_status(struct do
 }
 
 struct doveadm_cmd_ver2 doveadm_cmd_stop_ver2 = {
-	.old_cmd = cmd_stop,
+	.cmd = cmd_stop,
 	.name = "stop",
 	.usage = "",
 DOVEADM_CMD_PARAMS_START
@@ -262,7 +262,7 @@ DOVEADM_CMD_PARAMS_END
 };
 
 struct doveadm_cmd_ver2 doveadm_cmd_reload_ver2 = {
-        .old_cmd = cmd_reload,
+        .cmd = cmd_reload,
         .name = "reload",
         .usage = "",
 DOVEADM_CMD_PARAMS_START
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mutf7.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mutf7.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-mutf7.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-mutf7.c	2022-06-14 06:55:03.000000000 +0000
@@ -8,44 +8,35 @@
 #include <stdio.h>
 #include <unistd.h>
 
-static void cmd_mailbox_mutf7(int argc, char *argv[])
+static void cmd_mailbox_mutf7(struct doveadm_cmd_context *cctx)
 {
 	string_t *str;
-	bool from_utf8;
+	const char *const *names;
+	bool from_utf8, to_utf8;
 	unsigned int i;
-	int c;
 
-	from_utf8 = TRUE;
-	while ((c = getopt(argc, argv, "78")) > 0) {
-		switch (c) {
-		case '7':
-			from_utf8 = FALSE;
-			break;
-		case '8':
+	if (!doveadm_cmd_param_array(cctx, "name", &names))
+		help_ver2(&doveadm_cmd_mailbox_mutf7);
+	if (!doveadm_cmd_param_bool(cctx, "from-utf8", &from_utf8)) {
+		if (!doveadm_cmd_param_bool(cctx, "to-utf8", &to_utf8))
 			from_utf8 = TRUE;
-			break;
-		default:
-			help(&doveadm_cmd_mailbox_mutf7);
-		}
+		else
+			from_utf8 = !to_utf8;
 	}
-	argv += optind;
-
-	if (argv[0] == NULL)
-		help(&doveadm_cmd_mailbox_mutf7);
 
 	str = t_str_new(128);
-	for (i = 0; argv[i] != NULL; i++) {
+	for (i = 0; names[i] != NULL; i++) {
 		str_truncate(str, 0);
 		if (from_utf8) {
-			if (imap_utf8_to_utf7(argv[i], str) < 0) {
+			if (imap_utf8_to_utf7(names[i], str) < 0) {
 				i_error("Mailbox name not valid UTF-8: %s",
-					argv[i]);
+					names[i]);
 				doveadm_exit_code = EX_DATAERR;
 			}
 		} else {
-			if (imap_utf7_to_utf8(argv[i], str) < 0) {
+			if (imap_utf7_to_utf8(names[i], str) < 0) {
 				i_error("Mailbox name not valid mUTF-7: %s",
-					argv[i]);
+					names[i]);
 				doveadm_exit_code = EX_DATAERR;
 			}
 		}
@@ -53,7 +44,13 @@ static void cmd_mailbox_mutf7(int argc,
 	}
 }
 
-struct doveadm_cmd doveadm_cmd_mailbox_mutf7 = {
-	cmd_mailbox_mutf7, "mailbox mutf7",
-	"[-7|-8] <name> [...]"
+struct doveadm_cmd_ver2 doveadm_cmd_mailbox_mutf7 = {
+	.name = "mailbox mutf7",
+	.cmd = cmd_mailbox_mutf7,
+	.usage = "[-7|-8] <name> [...]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('7', "to-utf8", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('8', "from-utf8", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-oldstats.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-oldstats.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-oldstats.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-oldstats.c	2022-06-14 06:55:03.000000000 +0000
@@ -493,7 +493,7 @@ static void stats_top(const char *path,
 	i_close_fd(&ctx.fd);
 }
 
-static void stats_reset(const char *path, const char **items ATTR_UNUSED)
+static void stats_reset(const char *path)
 {
 	const char **ptr ATTR_UNUSED;
 	int fd,ret;
@@ -539,60 +539,36 @@ static void stats_reset(const char *path
 	i_close_fd(&fd);
 }
 
-static void cmd_stats_top(int argc, char *argv[])
+static void cmd_stats_top(struct doveadm_cmd_context *cctx)
 {
 	const char *path, *sort_type;
-	int c;
+	bool b;
 
-	path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL);
-
-	while ((c = getopt(argc, argv, "bs:")) > 0) {
-		switch (c) {
-		case 'b':
-			disk_input_field = "read_bytes";
-			disk_output_field = "write_bytes";
-			break;
-		case 's':
-			path = optarg;
-			break;
-		default:
-			help_ver2(&doveadm_cmd_oldstats_top_ver2);
-		}
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) {
+		path = t_strconcat(doveadm_settings->base_dir,
+				   "/old-stats", NULL);
+	}
+	if (!doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) {
+		disk_input_field = "read_bytes";
+		disk_output_field = "write_bytes";
 	}
-	argv += optind - 1;
-	if (argv[1] == NULL)
+	if (!doveadm_cmd_param_str(cctx, "sort-field", &sort_type))
 		sort_type = "disk";
-	else if (argv[2] != NULL)
-		help_ver2(&doveadm_cmd_oldstats_top_ver2);
-	else
-		sort_type = argv[1];
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
 	stats_top(path, sort_type);
 }
 
-static void cmd_stats_reset(int argc, char *argv[])
+static void cmd_stats_reset(struct doveadm_cmd_context *cctx)
 {
 	const char *path;
-	int c;
 
-	path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL);
-	while((c = getopt(argc, argv, "s:")) > 0) {
-		switch (c) {
-		case 's':
-			path = optarg;
-			break;
-		default:
-			help_ver2(&doveadm_cmd_oldstats_reset_ver2);
-		}
-	}
-	argv += optind - 1;
-	/* items is now argv */
-/*	if (optind >= argc) {
-		i_fatal("missing item(s) to reset");
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) {
+		path = t_strconcat(doveadm_settings->base_dir,
+				   "/old-stats", NULL);
 	}
-*/
-	stats_reset(path, (const char**)argv);
+
+	stats_reset(path);
 }
 
 struct doveadm_cmd_ver2 doveadm_cmd_oldstats_dump_ver2 = {
@@ -607,19 +583,19 @@ DOVEADM_CMD_PARAMS_END
 };
 
 struct doveadm_cmd_ver2 doveadm_cmd_oldstats_top_ver2 = {
-	.old_cmd = cmd_stats_top,
+	.cmd = cmd_stats_top,
 	.name = "oldstats top",
 	.usage = "[-s <stats socket path>] [-b] [<sort field>]",
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0)
 DOVEADM_CMD_PARAM('b', "show-disk-io", CMD_PARAM_BOOL, 0)
-DOVEADM_CMD_PARAM('\0', "field", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "sort-field", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
 DOVEADM_CMD_PARAMS_END
 };
 
 
 struct doveadm_cmd_ver2 doveadm_cmd_oldstats_reset_ver2 = {
-	.old_cmd = cmd_stats_reset,
+	.cmd = cmd_stats_reset,
 	.name = "oldstats reset",
 	.usage = "[-s <stats socket path>]",
 DOVEADM_CMD_PARAMS_START
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-print-server.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-print-server.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-print-server.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-print-server.c	2022-06-14 06:55:03.000000000 +0000
@@ -9,6 +9,8 @@
 #include "doveadm-print-private.h"
 #include "client-connection.h"
 
+#define DOVEADM_PRINT_FLUSH_TIMEOUT_SECS 60
+
 struct doveadm_print_server_context {
 	unsigned int header_idx, header_count;
 
@@ -61,11 +63,48 @@ doveadm_print_server_print_stream(const
 		doveadm_print_server_flush();
 }
 
+static int flush_callback(struct doveadm_print_server_context *ctx ATTR_UNUSED)
+{
+
+	int ret;
+	/* Keep flushing until everything is sent */
+	if ((ret = o_stream_flush(doveadm_print_ostream)) != 0)
+		io_loop_stop(current_ioloop);
+	return ret;
+}
+
+static void handle_flush_timeout(struct doveadm_print_server_context *ctx ATTR_UNUSED)
+{
+	io_loop_stop(current_ioloop);
+	o_stream_close(doveadm_print_ostream);
+	i_error("write(%s) failed: Timed out after %u seconds",
+		o_stream_get_name(doveadm_print_ostream),
+		DOVEADM_PRINT_FLUSH_TIMEOUT_SECS);
+}
+
 static void doveadm_print_server_flush(void)
 {
 	o_stream_nsend(doveadm_print_ostream,
 		       str_data(ctx.str), str_len(ctx.str));
 	str_truncate(ctx.str, 0);
+	o_stream_uncork(doveadm_print_ostream);
+
+	if (o_stream_get_buffer_used_size(doveadm_print_ostream) < IO_BLOCK_SIZE ||
+	    doveadm_print_ostream->stream_errno != 0)
+		return;
+	/* Wait until buffer is flushed to avoid it growing too large */
+	struct ioloop *prev_loop = current_ioloop;
+	struct ioloop *loop = io_loop_create();
+	/* Ensure we don't get stuck here forever */
+	struct timeout *to =
+		timeout_add(DOVEADM_PRINT_FLUSH_TIMEOUT_SECS*1000, handle_flush_timeout, &ctx);
+	o_stream_switch_ioloop_to(doveadm_print_ostream, loop);
+	o_stream_set_flush_callback(doveadm_print_ostream, flush_callback, &ctx);
+	io_loop_run(loop);
+	timeout_remove(&to);
+	o_stream_unset_flush_callback(doveadm_print_ostream);
+	o_stream_switch_ioloop_to(doveadm_print_ostream, prev_loop);
+	io_loop_destroy(&loop);
 }
 
 struct doveadm_print_vfuncs doveadm_print_server_vfuncs = {
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-proxy.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-proxy.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-proxy.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-proxy.c	2022-06-14 06:55:03.000000000 +0000
@@ -19,34 +19,21 @@ struct proxy_context {
 
 extern struct doveadm_cmd_ver2 doveadm_cmd_proxy[];
 
-static void proxy_cmd_help(doveadm_command_t *cmd) ATTR_NORETURN;
+static void proxy_cmd_help(struct doveadm_cmd_context *cctx) ATTR_NORETURN;
 
 static struct proxy_context *
-cmd_proxy_init(int argc, char *argv[], const char *getopt_args,
-	       doveadm_command_t *cmd)
+cmd_proxy_init(struct doveadm_cmd_context *cctx)
 {
 	struct proxy_context *ctx;
 	const char *socket_path;
-	int c;
 
 	ctx = t_new(struct proxy_context, 1);
-	socket_path = t_strconcat(doveadm_settings->base_dir, "/ipc", NULL);
-
-	while ((c = getopt(argc, argv, getopt_args)) > 0) {
-		switch (c) {
-		case 'a':
-			socket_path = optarg;
-			break;
-		case 'f':
-			ctx->username_field = optarg;
-			break;
-		case 'h':
-			ctx->kick_hosts = optarg;
-			break;
-		default:
-			proxy_cmd_help(cmd);
-		}
+	if (!doveadm_cmd_param_str(cctx, "socket-path", &socket_path)) {
+		socket_path = t_strconcat(doveadm_settings->base_dir,
+					  "/ipc", NULL);
 	}
+	(void)doveadm_cmd_param_str(cctx, "passdb-field", &ctx->username_field);
+	(void)doveadm_cmd_param_str(cctx, "host", &ctx->kick_hosts);
 	ctx->ipc = ipc_client_init(socket_path);
 	return ctx;
 }
@@ -109,12 +96,12 @@ static void cmd_proxy_list_callback(enum
 	io_loop_stop(current_ioloop);
 }
 
-static void cmd_proxy_list(int argc, char *argv[])
+static void cmd_proxy_list(struct doveadm_cmd_context *cctx)
 {
 	struct proxy_context *ctx;
 	bool seen_header = FALSE;
 
-	ctx = cmd_proxy_init(argc, argv, "a:", cmd_proxy_list);
+	ctx = cmd_proxy_init(cctx);
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
 
@@ -145,15 +132,16 @@ static void cmd_proxy_kick_callback(enum
 	io_loop_stop(current_ioloop);
 }
 
-static void cmd_proxy_kick(int argc, char *argv[])
+static void cmd_proxy_kick(struct doveadm_cmd_context *cctx)
 {
 	struct proxy_context *ctx;
+	const char *const *users = NULL;
 	string_t *cmd;
 
-	ctx = cmd_proxy_init(argc, argv, "a:f:h:", cmd_proxy_kick);
-
-	if (argv[optind] == NULL && ctx->kick_hosts == NULL) {
-		proxy_cmd_help(cmd_proxy_kick);
+	ctx = cmd_proxy_init(cctx);
+	(void)doveadm_cmd_param_array(cctx, "user", &users);
+	if (users == NULL && ctx->kick_hosts == NULL) {
+		proxy_cmd_help(cctx);
 		return;
 	}
 
@@ -173,9 +161,11 @@ static void cmd_proxy_kick(int argc, cha
 		str_append(cmd, "KICK-ALT\t");
 		str_append_tabescaped(cmd, ctx->username_field);
 	}
-	for (; argv[optind] != NULL; optind++) {
-		str_append_c(cmd, '\t');
-		str_append_tabescaped(cmd, argv[optind]);
+	if (users != NULL) {
+		for (unsigned int i = 0; users[i] != NULL; i++) {
+			str_append_c(cmd, '\t');
+			str_append_tabescaped(cmd, users[i]);
+		}
 	}
 	ipc_client_cmd(ctx->ipc, str_c(cmd), cmd_proxy_kick_callback, NULL);
 	io_loop_run(current_ioloop);
@@ -186,7 +176,7 @@ struct doveadm_cmd_ver2 doveadm_cmd_prox
 {
 	.name = "proxy list",
 	.usage = "[-a <ipc socket path>]",
-	.old_cmd = cmd_proxy_list,
+	.cmd = cmd_proxy_list,
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
 DOVEADM_CMD_PARAMS_END
@@ -194,7 +184,7 @@ DOVEADM_CMD_PARAMS_END
 {
 	.name = "proxy kick",
 	.usage = "[-a <ipc socket path>] [-f <passdb field>] [-h <host> [...] | <user> [...]]",
-	.old_cmd = cmd_proxy_kick,
+	.cmd = cmd_proxy_kick,
 DOVEADM_CMD_PARAMS_START
 DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
 DOVEADM_CMD_PARAM('f', "passdb-field", CMD_PARAM_STR, 0)
@@ -204,12 +194,12 @@ DOVEADM_CMD_PARAMS_END
 }
 };
 
-static void proxy_cmd_help(doveadm_command_t *cmd)
+static void proxy_cmd_help(struct doveadm_cmd_context *cctx)
 {
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_proxy); i++) {
-		if (doveadm_cmd_proxy[i].old_cmd == cmd)
+		if (doveadm_cmd_proxy[i].cmd == cctx->cmd->cmd)
 			help_ver2(&doveadm_cmd_proxy[i]);
 	}
 	i_unreached();
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-pw.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-pw.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-pw.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-pw.c	2022-06-14 06:55:03.000000000 +0000
@@ -17,14 +17,14 @@
 
 static struct module *modules = NULL;
 
-static void cmd_pw(int argc, char *argv[])
+static void cmd_pw(struct doveadm_cmd_context *cctx)
 {
 	const char *hash = NULL;
 	const char *scheme = NULL;
 	const char *plaintext = NULL;
 	const char *test_hash = NULL;
 	bool list_schemes = FALSE, reverse_verify = FALSE;
-	int c;
+	int64_t rounds_int64;
 	struct module_dir_load_settings mod_set;
 	struct password_generate_params gen_params;
 	i_zero(&gen_params);
@@ -40,36 +40,18 @@ static void cmd_pw(int argc, char *argv[
 	modules = module_dir_load_missing(modules, AUTH_MODULE_DIR, NULL, &mod_set);
 	module_dir_init(modules);
 
-	while ((c = getopt(argc, argv, "lp:r:s:t:u:V")) > 0) {
-		switch (c) {
-		case 'l':
-			list_schemes = 1;
-			break;
-		case 'p':
-			plaintext = optarg;
-			break;
-		case 'r':
-			if (str_to_uint(optarg, &gen_params.rounds) < 0)
-				i_fatal("Invalid number of rounds: %s", optarg);
-			break;
-		case 's':
-			scheme = optarg;
-			break;
-		case 't':
-			test_hash = optarg;
-			reverse_verify = TRUE;
-			break;
-		case 'u':
-			gen_params.user = optarg;
-			break;
-		case 'V':
-			reverse_verify = TRUE;
-			break;
-		case '?':
-		default:
-			help(&doveadm_cmd_pw);
-		}
+	(void)doveadm_cmd_param_bool(cctx, "list", &list_schemes);
+	(void)doveadm_cmd_param_str(cctx, "plaintext", &plaintext);
+	if (doveadm_cmd_param_int64(cctx, "rounds", &rounds_int64)) {
+		if (rounds_int64 > UINT_MAX)
+			i_fatal("Invalid number of rounds: %"PRId64, rounds_int64);
+		gen_params.rounds = rounds_int64;
 	}
+	(void)doveadm_cmd_param_str(cctx, "scheme", &scheme);
+	if (doveadm_cmd_param_str(cctx, "test-hash", &test_hash))
+		reverse_verify = TRUE;
+	(void)doveadm_cmd_param_str(cctx, "user", &gen_params.user);
+	(void)doveadm_cmd_param_bool(cctx, "reverse-verify", &reverse_verify);
 
 	if (list_schemes) {
 		ARRAY_TYPE(password_scheme_p) arr;
@@ -81,12 +63,11 @@ static void cmd_pw(int argc, char *argv[
 		for (i = 0; i < count; i++)
 			printf("%s ", schemes[i]->name);
 		printf("\n");
-		lib_exit(0);
+		module_dir_unload(&modules);
+		password_schemes_deinit();
+		return;
 	}
 
-	if (argc != optind)
-		help(&doveadm_cmd_pw);
-
 	scheme = scheme == NULL ? DEFAULT_SCHEME : t_str_ucase(scheme);
 
 	if (test_hash != NULL && plaintext == NULL)
@@ -138,7 +119,17 @@ static void cmd_pw(int argc, char *argv[
 	password_schemes_deinit();
 }
 
-struct doveadm_cmd doveadm_cmd_pw = {
-	cmd_pw, "pw",
-	"[-l] [-p plaintext] [-r rounds] [-s scheme] [-t hash] [-u user] [-V]"
+struct doveadm_cmd_ver2 doveadm_cmd_pw = {
+	.name = "pw",
+	.cmd = cmd_pw,
+	.usage = "[-l] [-p plaintext] [-r rounds] [-s scheme] [-t hash] [-u user] [-V]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('l', "list", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('p', "plaintext", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('r', "rounds", CMD_PARAM_INT64, 0)
+DOVEADM_CMD_PARAM('s', "scheme", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('t', "test-hash", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('u', "user", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('V', "reverse-verify", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAMS_END
 };
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-replicator.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-replicator.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-replicator.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-replicator.c	2022-06-14 06:55:03.000000000 +0000
@@ -17,13 +17,14 @@
 struct replicator_context {
 	const char *socket_path;
 	const char *priority;
+	const char *user_mask, *username;
 	struct istream *input;
 	bool full_sync;
 };
 
-extern struct doveadm_cmd doveadm_cmd_replicator[];
+extern struct doveadm_cmd_ver2 doveadm_cmd_replicator[];
 
-static void replicator_cmd_help(doveadm_command_t *cmd) ATTR_NORETURN;
+static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd) ATTR_NORETURN;
 
 static void
 replicator_send(struct replicator_context *ctx, const char *data)
@@ -73,33 +74,20 @@ static void replicator_disconnect(struct
 }
 
 static struct replicator_context *
-cmd_replicator_init(int *argc, char **argv[], const char *getopt_args,
-		    doveadm_command_t *cmd)
+cmd_replicator_init(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
-	int c;
 
 	ctx = t_new(struct replicator_context, 1);
 	ctx->socket_path = t_strconcat(doveadm_settings->base_dir,
 				       "/replicator-doveadm", NULL);
 
-	while ((c = getopt(*argc, *argv, getopt_args)) > 0) {
-		switch (c) {
-		case 'a':
-			ctx->socket_path = optarg;
-			break;
-		case 'f':
-			ctx->full_sync = TRUE;
-			break;
-		case 'p':
-			ctx->priority = optarg;
-			break;
-		default:
-			replicator_cmd_help(cmd);
-		}
-	}
-	*argc -= optind-1;
-	*argv += optind-1;
+	(void)doveadm_cmd_param_str(cctx, "socket-path", &ctx->socket_path);
+	(void)doveadm_cmd_param_bool(cctx, "full-sync", &ctx->full_sync);
+	(void)doveadm_cmd_param_str(cctx, "priority", &ctx->priority);
+	(void)doveadm_cmd_param_str(cctx, "user-mask", &ctx->user_mask);
+	(void)doveadm_cmd_param_str(cctx, "user", &ctx->username);
+
 	replicator_connect(ctx);
 	return ctx;
 }
@@ -139,16 +127,15 @@ static void cmd_replicator_status_overvi
 	replicator_disconnect(ctx);
 }
 
-static void cmd_replicator_status(int argc, char *argv[])
+static void cmd_replicator_status(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
 	const char *line, *const *args;
 	time_t last_fast, last_full, last_success;
 
-	ctx = cmd_replicator_init(&argc, &argv, "a:", cmd_replicator_status);
-
-	if (argv[1] == NULL) {
-		cmd_replicator_status_overview(ctx);
+	ctx = cmd_replicator_init(cctx);
+	if (ctx->user_mask == NULL) {
+	        cmd_replicator_status_overview(ctx);
 		return;
 	}
 
@@ -162,7 +149,7 @@ static void cmd_replicator_status(int ar
 	doveadm_print_header_simple("failed");
 
 	replicator_send(ctx, t_strdup_printf("STATUS\t%s\n",
-					     str_tabescape(argv[1])));
+					     str_tabescape(ctx->user_mask)));
 	while ((line = i_stream_read_next_line(ctx->input)) != NULL) {
 		if (*line == '\0')
 			break;
@@ -188,13 +175,13 @@ static void cmd_replicator_status(int ar
 	replicator_disconnect(ctx);
 }
 
-static void cmd_replicator_dsync_status(int argc, char *argv[])
+static void cmd_replicator_dsync_status(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
 	const char *line;
 	unsigned int i;
 
-	ctx = cmd_replicator_init(&argc, &argv, "a:", cmd_replicator_dsync_status);
+	ctx = cmd_replicator_init(cctx);
 
 	doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
 	doveadm_print_header("username", "username",
@@ -221,16 +208,15 @@ static void cmd_replicator_dsync_status(
 	replicator_disconnect(ctx);
 }
 
-static void cmd_replicator_replicate(int argc, char *argv[])
+static void cmd_replicator_replicate(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
 	string_t *str;
 	const char *line;
 
-	if (argv[1] == NULL)
-		replicator_cmd_help(cmd_replicator_replicate);
-
-	ctx = cmd_replicator_init(&argc, &argv, "a:fp:", cmd_replicator_replicate);
+	ctx = cmd_replicator_init(cctx);
+	if (ctx->user_mask == NULL)
+		replicator_cmd_help(cctx->cmd);
 
 	str = t_str_new(128);
 	str_append(str, "REPLICATE\t");
@@ -242,7 +228,7 @@ static void cmd_replicator_replicate(int
 	if (ctx->full_sync)
 		str_append_c(str, 'f');
 	str_append_c(str, '\t');
-	str_append_tabescaped(str, argv[1]);
+	str_append_tabescaped(str, ctx->user_mask);
 	str_append_c(str, '\n');
 	replicator_send(ctx, str_c(str));
 
@@ -263,20 +249,19 @@ static void cmd_replicator_replicate(int
 	replicator_disconnect(ctx);
 }
 
-static void cmd_replicator_add(int argc, char *argv[])
+static void cmd_replicator_add(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
 	string_t *str;
 	const char *line;
 
-	if (argv[1] == NULL)
-		replicator_cmd_help(cmd_replicator_add);
-
-	ctx = cmd_replicator_init(&argc, &argv, "a:", cmd_replicator_add);
+	ctx = cmd_replicator_init(cctx);
+	if (ctx->user_mask == NULL)
+		replicator_cmd_help(cctx->cmd);
 
 	str = t_str_new(128);
 	str_append(str, "ADD\t");
-	str_append_tabescaped(str, argv[1]);
+	str_append_tabescaped(str, ctx->user_mask);
 	str_append_c(str, '\n');
 	replicator_send(ctx, str_c(str));
 
@@ -291,20 +276,19 @@ static void cmd_replicator_add(int argc,
 	replicator_disconnect(ctx);
 }
 
-static void cmd_replicator_remove(int argc, char *argv[])
+static void cmd_replicator_remove(struct doveadm_cmd_context *cctx)
 {
 	struct replicator_context *ctx;
 	string_t *str;
 	const char *line;
 
-	if (argv[1] == NULL)
-		replicator_cmd_help(cmd_replicator_remove);
-
-	ctx = cmd_replicator_init(&argc, &argv, "a:", cmd_replicator_remove);
+	ctx = cmd_replicator_init(cctx);
+	if (ctx->username == NULL)
+		replicator_cmd_help(cctx->cmd);
 
 	str = t_str_new(128);
 	str_append(str, "REMOVE\t");
-	str_append_tabescaped(str, argv[1]);
+	str_append_tabescaped(str, ctx->username);
 	str_append_c(str, '\n');
 	replicator_send(ctx, str_c(str));
 
@@ -319,26 +303,62 @@ static void cmd_replicator_remove(int ar
 	replicator_disconnect(ctx);
 }
 
-struct doveadm_cmd doveadm_cmd_replicator[] = {
-	{ cmd_replicator_status, "replicator status",
-	  "[-a <replicator socket path>] [<user mask>]" },
-	{ cmd_replicator_dsync_status, "replicator dsync-status",
-	  "[-a <replicator socket path>]" },
-	{ cmd_replicator_replicate, "replicator replicate",
-	  "[-a <replicator socket path>] [-f] [-p <priority>] <user mask>" },
-	{ cmd_replicator_add, "replicator add",
-	  "[-a <replicator socket path>] <user mask>" },
-	{ cmd_replicator_remove, "replicator remove",
-	  "[-a <replicator socket path>] <username>" },
+struct doveadm_cmd_ver2 doveadm_cmd_replicator[] = {
+{
+	.name = "replicator status",
+	.cmd = cmd_replicator_status,
+	.usage = "[-a <replicator socket path>] [<user mask>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "replicator dsync-status",
+	.cmd = cmd_replicator_dsync_status,
+	.usage = "[-a <replicator socket path>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "replicator replicate",
+	.cmd = cmd_replicator_replicate,
+	.usage = "[-a <replicator socket path>] [-f] [-p <priority>] <user mask>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('f', "full-sync", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('p', "priority", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "replicator add",
+	.cmd = cmd_replicator_add,
+	.usage = "[-a <replicator socket path>] <user mask>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user-mask", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
+{
+	.name = "replicator remove",
+	.cmd = cmd_replicator_remove,
+	.usage = "[-a <replicator socket path>] <username>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('a', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+},
 };
 
-static void replicator_cmd_help(doveadm_command_t *cmd)
+static void replicator_cmd_help(const struct doveadm_cmd_ver2 *cmd)
 {
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++) {
-		if (doveadm_cmd_replicator[i].cmd == cmd)
-			help(&doveadm_cmd_replicator[i]);
+		if (doveadm_cmd_replicator[i].cmd == cmd->cmd)
+			help_ver2(&doveadm_cmd_replicator[i]);
 	}
 	i_unreached();
 }
@@ -348,5 +368,5 @@ void doveadm_register_replicator_command
 	unsigned int i;
 
 	for (i = 0; i < N_ELEMENTS(doveadm_cmd_replicator); i++)
-		doveadm_register_cmd(&doveadm_cmd_replicator[i]);
+		doveadm_cmd_register_ver2(&doveadm_cmd_replicator[i]);
 }
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-settings.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-settings.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-settings.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-settings.c	2022-06-14 06:55:03.000000000 +0000
@@ -212,10 +212,8 @@ const struct master_service_ssl_settings
 
 void doveadm_get_ssl_settings(struct ssl_iostream_settings *set_r, pool_t pool)
 {
-	i_zero(set_r);
-	master_service_ssl_settings_to_iostream_set(doveadm_ssl_set, pool,
-						    MASTER_SERVICE_SSL_SETTINGS_TYPE_CLIENT,
-						    set_r);
+	master_service_ssl_client_settings_to_iostream_set(doveadm_ssl_set,
+							   pool, set_r);
 }
 
 void doveadm_settings_expand(struct doveadm_settings *set, pool_t pool)
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-sis.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-sis.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-sis.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-sis.c	2022-06-14 06:55:03.000000000 +0000
@@ -206,7 +206,7 @@ static int sis_try_deduplicate(const cha
 	return hardlink_replace(path, hashes_path, st.st_ino) < 0 ? -1 : 0;
 }
 
-static void cmd_sis_deduplicate(int argc, char *argv[])
+static void cmd_sis_deduplicate(struct doveadm_cmd_context *cctx)
 {
 	const char *rootdir, *queuedir;
 	DIR *dir;
@@ -216,14 +216,12 @@ static void cmd_sis_deduplicate(int argc
 	size_t dir_len;
 	int ret;
 
-	if (argc < 3)
-		help(&doveadm_cmd_sis_deduplicate);
+	if (!doveadm_cmd_param_str(cctx, "root-dir", &rootdir) ||
+	    !doveadm_cmd_param_str(cctx, "queue-dir", &queuedir))
+		help_ver2(&doveadm_cmd_sis_deduplicate);
 
 	/* go through the filenames in the queue dir and see if
 	   we can deduplicate them. */
-	rootdir = argv[1];
-	queuedir = argv[2];
-
 	if (stat(rootdir, &st) < 0)
 		i_fatal("stat(%s) failed: %m", rootdir);
 
@@ -264,7 +262,7 @@ static void cmd_sis_deduplicate(int argc
 		i_error("closedir(%s) failed: %m", queuedir);
 }
 
-static void cmd_sis_find(int argc, char *argv[])
+static void cmd_sis_find(struct doveadm_cmd_context *cctx)
 {
 	const char *rootdir, *path, *hash;
 	DIR *dir;
@@ -273,16 +271,16 @@ static void cmd_sis_find(int argc, char
 	string_t *str;
 	size_t dir_len, hash_len;
 
-	if (argc < 3 || strlen(argv[2]) < 4)
-		help(&doveadm_cmd_sis_find);
+	if (!doveadm_cmd_param_str(cctx, "root-dir", &rootdir) ||
+	    !doveadm_cmd_param_str(cctx, "hash", &hash) ||
+	    strlen(hash) < 4)
+		help_ver2(&doveadm_cmd_sis_find);
 
-	rootdir = argv[1];
 	if (stat(rootdir, &st) < 0) {
 		if (errno == ENOENT)
 			i_fatal("Attachment dir doesn't exist: %s", rootdir);
 		i_fatal("stat(%s) failed: %m", rootdir);
 	}
-	hash = argv[2];
 	hash_len = strlen(hash);
 
 	path = sis_get_dir(rootdir, hash);
@@ -312,9 +310,21 @@ static void cmd_sis_find(int argc, char
 		i_error("closedir(%s) failed: %m", path);
 }
 
-struct doveadm_cmd doveadm_cmd_sis_deduplicate = {
-	cmd_sis_deduplicate, "sis deduplicate", "<root dir> <queue dir>"
+struct doveadm_cmd_ver2 doveadm_cmd_sis_deduplicate = {
+	.name = "sis deduplicate",
+	.cmd = cmd_sis_deduplicate,
+	.usage = "<root dir> <queue dir>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "root-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "queue-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
-struct doveadm_cmd doveadm_cmd_sis_find = {
-	cmd_sis_find, "sis find", "<root dir> <hash>"
+struct doveadm_cmd_ver2 doveadm_cmd_sis_find = {
+	.name = "sis find",
+	.cmd = cmd_sis_find,
+	.usage = "<root dir> <hash>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "root-dir", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "hash", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-stats.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-stats.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-stats.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-stats.c	2022-06-14 06:55:03.000000000 +0000
@@ -9,17 +9,119 @@
 #include "master-service.h"
 #include "doveadm.h"
 #include "doveadm-print.h"
+#include "stats-settings.h"
 
 #include <math.h>
 
 #define DOVEADM_DUMP_DEFAULT_FIELDS \
 	"count sum min max avg median stddev %95"
 
+#define ADD_NAME_PARAM "name"
+#define ADD_DESCR_PARAM "description"
+#define ADD_FIELDS_PARAM "fields"
+#define ADD_GROUPBY_PARAM "group-by"
+#define ADD_FILTER_PARAM "filter"
+#define ADD_EXPORTER_PARAM "exporter"
+#define ADD_EXPORTERINCL_PARAM "exporter-include"
+
 enum doveadm_dump_field_type {
 	DOVEADM_DUMP_FIELD_TYPE_PASSTHROUGH = 0,
 	DOVEADM_DUMP_FIELD_TYPE_STDDEV,
 };
 
+struct stats_cmd_context {
+	string_t *cmd;
+	struct doveadm_cmd_context *cctx;
+	struct istream *input;
+	const char *path;
+	void *data;
+};
+
+struct dump_data {
+	const char **fields;
+	unsigned int field_count;
+	enum doveadm_dump_field_type *field_types;
+};
+
+struct stats_cmd_vfuncs {
+	int (*build_cmd)(struct stats_cmd_context *ctx, const char **error_r);
+	void (*process_response)(struct stats_cmd_context *ctx);
+};
+
+static int build_stats_dump_cmd(struct stats_cmd_context *ctx, const char **error_r);
+static int build_stats_add_cmd(struct stats_cmd_context *ctx, const char **error_r);
+static int build_stats_remove_cmd(struct stats_cmd_context *ctx, const char **error_r);
+
+static void stats_dump_process_response(struct stats_cmd_context *ctx);
+static void stats_modify_process_response(struct stats_cmd_context *ctx);
+
+static void stats_send_cmd(struct stats_cmd_context *ctx);
+
+static struct stats_cmd_vfuncs dump_vfuncs = {
+	.build_cmd = build_stats_dump_cmd,
+	.process_response = stats_dump_process_response
+};
+
+static struct stats_cmd_vfuncs add_vfuncs = {
+	.build_cmd = build_stats_add_cmd,
+	.process_response = stats_modify_process_response
+};
+
+static struct stats_cmd_vfuncs remove_vfuncs = {
+	.build_cmd = build_stats_remove_cmd,
+	.process_response = stats_modify_process_response
+};
+
+static string_t *init_stats_cmd(void)
+{
+	string_t *cmd = t_str_new(128);
+	str_append(cmd, "VERSION\tstats-reader-client\t2\t0\n");
+	return cmd;
+}
+
+static void stats_exec_cmd(struct doveadm_cmd_context *cctx,
+			   struct stats_cmd_vfuncs *vfuncs)
+{
+	struct stats_cmd_context ctx;
+	const char *build_cmd_error;
+	ctx.cctx = cctx;
+	if (vfuncs->build_cmd(&ctx, &build_cmd_error) < 0) {
+		i_error("%s", build_cmd_error);
+		return;
+	}
+	stats_send_cmd(&ctx);
+	vfuncs->process_response(&ctx);
+	i_stream_destroy(&ctx.input);
+}
+
+static void handle_disconnection(struct stats_cmd_context *ctx)
+{
+	i_error("read(%s) failed: %s", ctx->path,
+		i_stream_get_disconnect_reason(ctx->input));
+}
+
+static void stats_send_cmd(struct stats_cmd_context *ctx)
+{
+	int fd;
+	const char *line;
+	if (!doveadm_cmd_param_str(ctx->cctx, "socket-path", &ctx->path))
+		ctx->path = t_strconcat(doveadm_settings->base_dir,
+					"/stats-reader", NULL);
+
+	fd = doveadm_connect(ctx->path);
+	net_set_nonblock(fd, FALSE);
+	if (write_full(fd, str_data(ctx->cmd), str_len(ctx->cmd)) < 0)
+		i_fatal("write(%s) failed %m", ctx->path);
+	ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+
+	if ((line = i_stream_read_next_line(ctx->input)) == NULL)
+		i_fatal("%s: Failed to read VERSION line", ctx->path);
+	else if (!version_string_verify(line, "stats-reader-server", 2)) {
+		i_fatal_status(EX_PROTOCOL,
+			"%s is not a compatible stats-reader socket", ctx->path);
+	}
+}
+
 static void dump_timing(const char *const **args,
 			const enum doveadm_dump_field_type field_types[],
 			unsigned int fields_count)
@@ -45,47 +147,54 @@ static void dump_timing(const char *cons
 	*args += args_count;
 }
 
-static void stats_dump(const char *path, const char *const *fields, bool reset)
+static int build_stats_dump_cmd(struct stats_cmd_context *ctx,
+				const char **error_r ATTR_UNUSED)
 {
-	struct istream *input;
-	string_t *cmd = t_str_new(128);
-	unsigned int i, fields_count = str_array_length(fields);
-	enum doveadm_dump_field_type field_types[fields_count];
-	char *line;
-	int fd;
+	bool reset;
+	struct dump_data *data = t_new(struct dump_data, 1);
+	const char *fields_raw;
+	const char **fields;
+	if (!doveadm_cmd_param_bool(ctx->cctx, "reset", &reset))
+		reset = FALSE;
+	if (!doveadm_cmd_param_str(ctx->cctx, "fields", &fields_raw))
+		fields_raw = DOVEADM_DUMP_DEFAULT_FIELDS;
 
-	fd = doveadm_connect(path);
-	net_set_nonblock(fd, FALSE);
-	str_append(cmd, "VERSION\tstats-reader-client\t2\t0\n");
-	str_append(cmd, reset ? "DUMP-RESET" : "DUMP");
-	i_zero(&field_types);
-	for (i = 0; i < fields_count; i++) {
-		str_append_c(cmd, '\t');
+	fields = t_strsplit_spaces(fields_raw, ", ");
+	data->fields = fields;
+	data->field_count = str_array_length(fields);
+	data->field_types =
+		t_malloc0(sizeof(enum doveadm_dump_field_type) * data->field_count);
+	ctx->data = data;
+	ctx->cmd = init_stats_cmd();
+	str_append(ctx->cmd, reset ? "DUMP-RESET" : "DUMP");
+	unsigned int i;
+	for (i = 0; i < data->field_count; i++) {
+		str_append_c(ctx->cmd, '\t');
 		if (strcmp(fields[i], "stddev") == 0) {
-			field_types[i] = DOVEADM_DUMP_FIELD_TYPE_STDDEV;
-			str_append(cmd, "variance");
+			data->field_types[i] = DOVEADM_DUMP_FIELD_TYPE_STDDEV;
+			str_append(ctx->cmd, "variance");
 		} else {
-			str_append_tabescaped(cmd, fields[i]);
+			str_append_tabescaped(ctx->cmd, fields[i]);
 		}
 	}
-	str_append_c(cmd, '\n');
-	if (write_full(fd, str_data(cmd), str_len(cmd)) < 0)
-		i_fatal("write(%s) failed: %m", path);
-
-	input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
-	if ((line = i_stream_read_next_line(input)) == NULL)
-		i_fatal("%s: Failed to read VERSION line", path);
-	else if (!version_string_verify(line, "stats-reader-server", 2)) {
-		i_fatal_status(EX_PROTOCOL,
-			"%s is not a compatible stats-reader socket", path);
-	}
+	str_append_c(ctx->cmd, '\n');
+	return 0;
+}
+
+static void stats_dump_process_response(struct stats_cmd_context *ctx)
+{
+	unsigned int i;
+	char *line;
+	struct dump_data *data = ctx->data;
 
+	doveadm_print_init(DOVEADM_PRINT_TYPE_TAB);
 	doveadm_print_header_simple("metric_name");
 	doveadm_print_header_simple("field");
-	for (i = 0; i < fields_count; i++)
-		doveadm_print_header(fields[i], fields[i], DOVEADM_PRINT_HEADER_FLAG_NUMBER);
+	for (i = 0; i < data->field_count; i++)
+		doveadm_print_header(data->fields[i], data->fields[i],
+				     DOVEADM_PRINT_HEADER_FLAG_NUMBER);
 
-	while ((line = i_stream_read_next_line(input)) != NULL) {
+	while ((line = i_stream_read_next_line(ctx->input)) != NULL) {
 		if (line[0] == '\0')
 			break;
 		T_BEGIN {
@@ -95,37 +204,104 @@ static void stats_dump(const char *path,
 			const char *metric_name = args[0];
 			doveadm_print(metric_name); args++;
 			doveadm_print("duration");
-			dump_timing(&args, field_types, fields_count);
+			dump_timing(&args, data->field_types, data->field_count);
 			while (*args != NULL) {
 				doveadm_print(metric_name);
 				doveadm_print(*args); args++;
-				dump_timing(&args, field_types, fields_count);
+				dump_timing(&args, data->field_types, data->field_count);
 			}
 		} T_END;
 	}
+	if (line == NULL)
+		handle_disconnection(ctx);
+}
+
+static int build_stats_add_cmd(struct stats_cmd_context *ctx,
+			       const char **error_r)
+{
+	unsigned int i;
+	const char *parameter;
+	struct {
+		const char *name;
+		const char *default_val;
+	} params[] = {
+		{ ADD_NAME_PARAM, "" },
+		{ ADD_DESCR_PARAM, "" },
+		{ ADD_FIELDS_PARAM, "" },
+		{ ADD_GROUPBY_PARAM, "" },
+		{ ADD_FILTER_PARAM, "" },
+		{ ADD_EXPORTER_PARAM, "" },
+		/* Default exporter-include is to be modified
+		   together with stats-settings */
+		{ ADD_EXPORTERINCL_PARAM,
+		  STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE },
+	};
+
+	ctx->cmd = init_stats_cmd();
+	str_append(ctx->cmd, "METRICS-ADD");
+
+	for (i = 0; i < N_ELEMENTS(params); i++) {
+		if (!doveadm_cmd_param_str(ctx->cctx, params[i].name, &parameter))
+			parameter = params[i].default_val;
+		if (parameter[0] == '\0' &&
+		    (strcmp(params[i].name, "name") == 0 ||
+		     strcmp(params[i].name, "filter") == 0)) {
+			*error_r =
+				t_strdup_printf("stats add: missing %s parameter",
+						params[i].name);
+			return -1;
+		}
+		str_append_c(ctx->cmd, '\t');
+		str_append_tabescaped(ctx->cmd, parameter);
+	}
 
-	if (input->stream_errno != 0)
-		i_fatal("read(%s) failed: %s", path, i_stream_get_error(input));
-	i_stream_destroy(&input);
+	str_append_c(ctx->cmd, '\n');
+	return 0;
 }
 
-static void
-doveadm_cmd_stats_dump(struct doveadm_cmd_context *cctx)
+static void stats_modify_process_response(struct stats_cmd_context *ctx)
 {
-	const char *path, *fields;
-	bool reset;
+	const char *line = i_stream_read_next_line(ctx->input);
+	if (line == NULL) {
+		handle_disconnection(ctx);
+		return;
+	}
+	if (line[0] == '-')
+		i_error("%s", ++line);
+	else if (line[0] != '+')
+		i_error("Invalid response: %s", line);
+}
 
-	if (!doveadm_cmd_param_str(cctx, "socket-path", &path))
-		path = t_strconcat(doveadm_settings->base_dir, "/stats-reader", NULL);
-	if (!doveadm_cmd_param_bool(cctx, "reset", &reset))
-		reset = FALSE;
+static int build_stats_remove_cmd(struct stats_cmd_context *ctx,
+				  const char **error_r)
+{
+	const char *name;
 
-	if (!doveadm_cmd_param_str(cctx, "fields", &fields))
-		fields = DOVEADM_DUMP_DEFAULT_FIELDS;
+	ctx->cmd = init_stats_cmd();
+	str_append(ctx->cmd, "METRICS-REMOVE\t");
 
-	doveadm_print_init(DOVEADM_PRINT_TYPE_TAB);
-	stats_dump(path, t_strsplit_spaces(fields, ", "), reset);
-	return;
+	if (!doveadm_cmd_param_str(ctx->cctx, "name", &name)) {
+		*error_r = "stats remove: missing name parameter";
+		return -1;
+	}
+	str_append_tabescaped(ctx->cmd, name);
+	str_append_c(ctx->cmd, '\n');
+	return 0;
+}
+
+static void doveadm_cmd_stats_dump(struct doveadm_cmd_context *cctx)
+{
+	stats_exec_cmd(cctx, &dump_vfuncs);
+}
+
+static void doveadm_cmd_stats_add(struct doveadm_cmd_context *cctx)
+{
+	stats_exec_cmd(cctx, &add_vfuncs);
+}
+
+static void doveadm_cmd_stats_remove(struct doveadm_cmd_context *cctx)
+{
+	stats_exec_cmd(cctx, &remove_vfuncs);
 }
 
 struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2 = {
@@ -138,3 +314,30 @@ DOVEADM_CMD_PARAM('r', "reset", CMD_PARA
 DOVEADM_CMD_PARAM('f', "fields", CMD_PARAM_STR, 0)
 DOVEADM_CMD_PARAMS_END
 };
+
+struct doveadm_cmd_ver2 doveadm_cmd_stats_add_ver2 = {
+	.cmd = doveadm_cmd_stats_add,
+	.name = "stats add",
+	.usage = "[--"ADD_DESCR_PARAM" <string>] "
+	"[--"ADD_EXPORTER_PARAM" <name> [--"ADD_EXPORTERINCL_PARAM" <fields>]] "
+	"[--"ADD_FIELDS_PARAM" <fields>] "
+	"[--"ADD_GROUPBY_PARAM" <fields>] <name> <filter>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', ADD_NAME_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', ADD_FILTER_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', ADD_EXPORTER_PARAM, CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', ADD_EXPORTERINCL_PARAM, CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', ADD_DESCR_PARAM, CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', ADD_FIELDS_PARAM, CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', ADD_GROUPBY_PARAM, CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_stats_remove_ver2 = {
+	.cmd = doveadm_cmd_stats_remove,
+	.name = "stats remove",
+	.usage = "<name>",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-who.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-who.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-who.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-who.c	2022-06-14 06:55:03.000000000 +0000
@@ -152,7 +152,8 @@ int who_parse_args(struct who_context *c
 	unsigned int i, net_bits;
 
 	for (i = 0; masks[i] != NULL; i++) {
-		if (net_parse_range(masks[i], &net_ip, &net_bits) == 0) {
+		if (!str_is_numeric(masks[i], '\0') &&
+		    net_parse_range(masks[i], &net_ip, &net_bits) == 0) {
 			if (ctx->filter.net_bits != 0) {
 				i_error("Multiple network masks not supported");
 				doveadm_exit_code = EX_USAGE;
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/doveadm-zlib.c 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-zlib.c
--- 1:2.3.16+dfsg1-3/src/doveadm/doveadm-zlib.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/doveadm-zlib.c	2022-06-14 06:55:03.000000000 +0000
@@ -42,7 +42,8 @@ static bool test_dump_imapzlib(const cha
 }
 
 #ifdef HAVE_ZLIB
-static void cmd_dump_imapzlib(int argc ATTR_UNUSED, char *argv[])
+static void
+cmd_dump_imapzlib(const char *path, const char *const *args ATTR_UNUSED)
 {
 	struct istream *input, *input2;
 	const unsigned char *data;
@@ -50,9 +51,9 @@ static void cmd_dump_imapzlib(int argc A
 	const char *line;
 	int fd;
 
-	fd = open(argv[1], O_RDONLY);
+	fd = open(path, O_RDONLY);
 	if (fd < 0)
-		i_fatal("open(%s) failed: %m", argv[1]);
+		i_fatal("open(%s) failed: %m", path);
 	input = i_stream_create_fd_autoclose(&fd, 1024*32);
 	while ((line = i_stream_read_next_line(input)) != NULL) {
 		/* skip tag */
@@ -75,10 +76,8 @@ static void cmd_dump_imapzlib(int argc A
 			break;
 		i_stream_skip(input2, size);
 	}
-	if (input2->stream_errno != 0) {
-		i_error("read(%s) failed: %s",
-			argv[1], i_stream_get_error(input));
-	}
+	if (input2->stream_errno != 0)
+		i_error("read(%s) failed: %s", path, i_stream_get_error(input));
 	i_stream_unref(&input2);
 	fflush(stdout);
 }
@@ -220,26 +219,32 @@ static void server_input(struct client *
 	i_stream_skip(client->input, size);
 }
 
-static void cmd_zlibconnect(int argc ATTR_UNUSED, char *argv[])
+static void cmd_zlibconnect(struct doveadm_cmd_context *cctx)
 {
 	struct client client;
+	const char *host;
 	struct ip_addr *ips;
 	unsigned int ips_count;
+	int64_t port_int64;
 	in_port_t port = 143;
 	int fd, ret;
 
-	if (argv[1] == NULL ||
-	    (argv[2] != NULL && net_str2port(argv[2], &port) < 0))
-		help(&doveadm_cmd_zlibconnect);
+	if (!doveadm_cmd_param_str(cctx, "host", &host))
+		help_ver2(&doveadm_cmd_zlibconnect);
+	if (doveadm_cmd_param_int64(cctx, "port", &port_int64)) {
+		if (port_int64 == 0 || port_int64 > 65535)
+			i_fatal("Invalid port: %"PRId64, port_int64);
+		port = (in_port_t)port_int64;
+	}
 
-	ret = net_gethostbyname(argv[1], &ips, &ips_count);
+	ret = net_gethostbyname(host, &ips, &ips_count);
 	if (ret != 0) {
-		i_fatal("Host %s lookup failed: %s", argv[1],
+		i_fatal("Host %s lookup failed: %s", host,
 			net_gethosterror(ret));
 	}
 
 	if ((fd = net_connect_ip(&ips[0], port, NULL)) == -1)
-		i_fatal("connect(%s, %u) failed: %m", argv[1], port);
+		i_fatal("connect(%s, %u) failed: %m", host, port);
 
 	i_info("Connected to %s port %u.", net_ip2addr(&ips[0]), port);
 
@@ -262,12 +267,14 @@ static void cmd_zlibconnect(int argc ATT
 		i_fatal("close() failed: %m");
 }
 #else
-static void cmd_dump_imapzlib(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+static void
+cmd_dump_imapzlib(const char *path ATTR_UNUSED,
+		  const char *const *args ATTR_UNUSED)
 {
 	i_fatal("Dovecot compiled without zlib support");
 }
 
-static void cmd_zlibconnect(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+static void cmd_zlibconnect(struct doveadm_cmd_context *cctx ATTR_UNUSED)
 {
 	i_fatal("Dovecot compiled without zlib support");
 }
@@ -279,8 +286,12 @@ struct doveadm_cmd_dump doveadm_cmd_dump
 	cmd_dump_imapzlib
 };
 
-struct doveadm_cmd doveadm_cmd_zlibconnect = {
-	cmd_zlibconnect,
-	"zlibconnect",
-	"<host> [<port>]"
+struct doveadm_cmd_ver2 doveadm_cmd_zlibconnect = {
+	.name = "zlibconnect",
+	.cmd = cmd_zlibconnect,
+	.usage = "<host> [<port>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('\0', "host", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "port", CMD_PARAM_INT64, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
 };
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain.c 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain.c
--- 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain.c	2022-06-14 06:55:03.000000000 +0000
@@ -40,6 +40,9 @@ static const char *dsync_state_names[] =
 	"done"
 };
 
+struct dsync_mailbox_list_module dsync_mailbox_list_module =
+	MODULE_CONTEXT_INIT(&mailbox_list_module_register);
+
 static void dsync_brain_mailbox_states_dump(struct dsync_brain *brain);
 
 static const char *
@@ -160,8 +163,6 @@ dsync_brain_set_flags(struct dsync_brain
 		(flags & DSYNC_BRAIN_FLAG_NO_BACKUP_OVERWRITE) != 0;
 	brain->no_mail_prefetch =
 		(flags & DSYNC_BRAIN_FLAG_NO_MAIL_PREFETCH) != 0;
-	brain->no_mailbox_renames =
-		(flags & DSYNC_BRAIN_FLAG_NO_MAILBOX_RENAMES) != 0;
 	brain->no_notify = (flags & DSYNC_BRAIN_FLAG_NO_NOTIFY) != 0;
 	brain->empty_hdr_workaround = (flags & DSYNC_BRAIN_FLAG_EMPTY_HDR_WORKAROUND) != 0;
 }
@@ -402,7 +403,9 @@ dsync_brain_lock(struct dsync_brain *bra
 {
 	const struct file_create_settings lock_set = {
 		.lock_timeout_secs = brain->lock_timeout,
-		.lock_method = FILE_LOCK_METHOD_FCNTL,
+		.lock_settings = {
+			.lock_method = FILE_LOCK_METHOD_FCNTL,
+		},
 	};
 	const char *home, *error, *local_hostname = my_hostdomain();
 	bool created;
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain.h 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain.h
--- 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain.h	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain.h	2022-06-14 06:55:03.000000000 +0000
@@ -1,8 +1,10 @@
 #ifndef DSYNC_BRAIN_H
 #define DSYNC_BRAIN_H
 
+#include "module-context.h"
 #include "guid.h"
 #include "mail-error.h"
+#include "mailbox-list-private.h"
 
 struct mail_namespace;
 struct mail_user;
@@ -28,9 +30,6 @@ enum dsync_brain_flags {
 	   only with pipe ibc. It's useful if most of the mails can be copied
 	   directly within filesystem without having to read them. */
 	DSYNC_BRAIN_FLAG_NO_MAIL_PREFETCH	= 0x100,
-	/* Disable mailbox renaming logic. This is just a kludge that should
-	   be removed once the renaming logic has no more bugs.. */
-	DSYNC_BRAIN_FLAG_NO_MAILBOX_RENAMES	= 0x200,
 	/* Add MAILBOX_TRANSACTION_FLAG_NO_NOTIFY to transactions. */
 	DSYNC_BRAIN_FLAG_NO_NOTIFY		= 0x400,
 	/* Workaround missing Date/Message-ID headers */
@@ -88,6 +87,15 @@ struct dsync_brain_settings {
 	const char *state;
 };
 
+#define DSYNC_LIST_CONTEXT(obj) \
+	MODULE_CONTEXT(obj, dsync_mailbox_list_module)
+struct dsync_mailbox_list {
+	union mailbox_list_module_context module_ctx;
+	bool have_orig_escape_char;
+};
+extern MODULE_CONTEXT_DEFINE(dsync_mailbox_list_module,
+			     &mailbox_list_module_register);
+
 struct dsync_brain *
 dsync_brain_master_init(struct mail_user *user, struct dsync_ibc *ibc,
 			enum dsync_brain_sync_type sync_type,
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain-mailbox.c 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain-mailbox.c
--- 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain-mailbox.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain-mailbox.c	2022-06-14 06:55:03.000000000 +0000
@@ -276,6 +276,8 @@ int dsync_brain_sync_mailbox_open(struct
 	mailbox_get_open_status(brain->box, STATUS_UIDNEXT |
 				STATUS_HIGHESTMODSEQ |
 				STATUS_HIGHESTPVTMODSEQ, &status);
+	if (status.nonpermanent_modseqs)
+		status.highest_modseq = 0;
 	if (ret == 0) {
 		if (pvt_too_old) {
 			desync_reason = t_strdup_printf(
@@ -426,6 +428,8 @@ static int dsync_box_get(struct mailbox
 		*error_r = error;
 		return -1;
 	}
+	if (status.nonpermanent_modseqs)
+		status.highest_modseq = 0;
 
 	i_assert(status.uidvalidity != 0 || status.messages == 0);
 
@@ -596,18 +600,46 @@ void dsync_brain_master_send_mailbox(str
 
 bool dsync_boxes_need_sync(struct dsync_brain *brain,
 			   const struct dsync_mailbox *box1,
-			   const struct dsync_mailbox *box2)
+			   const struct dsync_mailbox *box2,
+			   const char **reason_r)
 {
 	if (brain->no_mail_sync)
 		return FALSE;
-	if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_CHANGED)
+	if (brain->sync_type != DSYNC_BRAIN_SYNC_TYPE_CHANGED) {
+		*reason_r = "Full sync";
 		return TRUE;
-	return box1->highest_modseq != box2->highest_modseq ||
-		box1->highest_pvt_modseq != box2->highest_pvt_modseq ||
-		box1->messages_count != box2->messages_count ||
-		box1->uid_next != box2->uid_next ||
-		box1->uid_validity != box2->uid_validity ||
-		box1->first_recent_uid != box2->first_recent_uid;
+	}
+	if (box1->uid_validity != box2->uid_validity)
+		*reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u",
+			box1->uid_validity, box2->uid_validity);
+	else if (box1->uid_next != box2->uid_next)
+		*reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u",
+			box1->uid_next, box2->uid_next);
+	else if (box1->messages_count != box2->messages_count)
+		*reason_r = t_strdup_printf("Message count changed: %u -> %u",
+			box1->messages_count, box2->messages_count);
+	else if (box1->highest_modseq != box2->highest_modseq) {
+		*reason_r = t_strdup_printf("HIGHESTMODSEQ changed %"
+					    PRIu64" -> %"PRIu64,
+					    box1->highest_modseq,
+					    box2->highest_modseq);
+		if (box1->highest_modseq == 0 ||
+		    box2->highest_modseq == 0) {
+			*reason_r = t_strdup_printf(
+				"%s (Permanent MODSEQs aren't supported)",
+				*reason_r);
+		}
+	} else if (box1->highest_pvt_modseq != box2->highest_pvt_modseq)
+		*reason_r = t_strdup_printf("Private HIGHESTMODSEQ changed %"
+					    PRIu64" -> %"PRIu64,
+					    box1->highest_pvt_modseq,
+					    box2->highest_pvt_modseq);
+	else if (box1->first_recent_uid != box2->first_recent_uid)
+		*reason_r = t_strdup_printf("First RECENT UID changed: %u -> %u",
+			box1->first_recent_uid, box2->first_recent_uid);
+	else
+		return FALSE;
+	return TRUE;
 }
 
 static int
@@ -772,7 +804,7 @@ bool dsync_brain_slave_recv_mailbox(stru
 	struct dsync_mailbox local_dsync_box;
 	struct mailbox *box;
 	struct file_lock *lock;
-	const char *errstr, *resync_reason;
+	const char *errstr, *resync_reason, *reason;
 	enum mail_error error;
 	int ret;
 	bool resync;
@@ -854,7 +886,7 @@ bool dsync_brain_slave_recv_mailbox(stru
 	resync = !dsync_brain_mailbox_update_pre(brain, box, &local_dsync_box,
 						 dsync_box, &resync_reason);
 
-	if (!dsync_boxes_need_sync(brain, &local_dsync_box, dsync_box)) {
+	if (!dsync_boxes_need_sync(brain, &local_dsync_box, dsync_box, &reason)) {
 		/* no fields appear to have changed, skip this mailbox */
 		if (brain->debug) {
 			i_debug("brain %c: Skipping unchanged mailbox %s",
@@ -866,6 +898,11 @@ bool dsync_brain_slave_recv_mailbox(stru
 		mailbox_free(&box);
 		return TRUE;
 	}
+	if (brain->debug) {
+		i_debug("brain %c: Syncing mailbox %s: %s",
+			brain->master_brain ? 'M' : 'S',
+			guid_128_to_string(dsync_box->mailbox_guid), reason);
+	}
 
 	/* start export/import */
 	dsync_brain_sync_mailbox_init(brain, box, lock, &local_dsync_box, FALSE);
diff -pruN 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain-mailbox-tree.c 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain-mailbox-tree.c
--- 1:2.3.16+dfsg1-3/src/doveadm/dsync/dsync-brain-mailbox-tree.c	2021-08-06 09:25:51.000000000 +0000
+++ 1:2.3.19.1+dfsg1-2/src/doveadm/dsync/dsync-brain-mailbox-tree.c	2022-06-14 06:55:03.000000000 +0000
@@ -13,23 +13,31 @@
 static void dsync_brain_check_namespaces(struct dsync_brain *brain)
 {
 	struct mail_namespace *ns, *first_ns = NULL;
-	char sep;
+	char sep, escape_char;
 
 	i_assert(brain->hierarchy_sep == '\0');
+	i_assert(brain->escape_char == '\0');
 
 	for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
 		if (!dsync_brain_want_namespace(brain, ns))
 			continue;
 
 		sep = mail_namespace_get_sep(ns);
+		escape_char = mailbox_list_get_settings(ns->list)->vname_escape_char;
 		if (first_ns == NULL) {
 			brain->hierarchy_sep = sep;
+			brain->escape_char = escape_char;
 			first_ns = ns;
 		} else if (brain->hierarchy_sep != sep) {
 			i_fatal("Synced namespaces have conflicting separators "
 				"('%c' for prefix=\"%s\", '%c' for prefix=\"%s\")",
 				brain->hierarchy_sep, first_ns->prefix,
 				sep, ns->prefix);
+		} else if (brain->escape_char != escape_char) {
+			i_fatal("Synced namespaces have conflicting escape chars "
+				"('%c' for prefix=\"%s\", '%c' for prefix=\"%s\")",
+				brain->escape_char, first_ns->prefix,
+				escape_char, ns->prefix);
 		}
 	}
 	if (brain->hierarchy_sep != '\0')
@@ -47,10 +55,12 @@ void dsync_brain_mailbox_trees_init(stru
 	dsync_brain_check_namespaces(brain);
 
 	brain->local_mailbox_tree =
-		dsync_mailbox_tree_init(brain->hierarchy_sep, brain->alt_char);
+		dsync_mailbox_tree_init(brain->hierarchy_sep,
+					brain->escape_char, brain->alt_char);
 	/* we'll convert remote mailbox names to use our own separator */
 	brain->remote_mailbox_tree =
-		dsync_mailbox_tree_init(brain->hierarchy_sep, brain->alt_char);
+		dsync_mailbox_tree_init(brain->hierarchy_sep,
+					brain->escape_char, brain->alt_char);
 
 	/* fill the local mailbox tree */
 	for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
@@ -74,15 +84,24 @@ void dsync_brain_mailbox_trees_init(stru
 		dsync_mailbox_tree_iter_init(brain->local_mailbox_tree);
 }
 
+static const char *const *
+dsync_brain_mailbox_to_parts(struct dsync_brain *brain, const char *name)
+{
+	char sep[] = { brain->hierarchy_sep, '\0' };
+	char **parts = p_strsplit(unsafe_data_stack_pool, name, sep);
+	for (unsigned int i = 0; parts[i] != NULL; i++) {
+		mailbox_list_name_unescape((const char **)&parts[i],
+					   brain->escape_char);
+	}
+	return (const char *const *)parts;
+}
 
 void dsync_brain_send_mailbox_tree(struct dsync_brain *brain)
 {
 	struct dsync_mailbox_node *node;
 	enum dsync_ibc_send_ret ret;
 	const char *full_name;
-	char sep[2];
 
-	sep[0] = brain->hierarchy_sep; sep[1] = '\0';
 	while (dsync_mailbox_tree_iter_next(brain->local_tree_iter,
 					    &full_name, &node)) {
 		if (node->ns == NULL) {
@@ -105,19 +124,7 @@ void dsync_brain_send_mailbox_tree(struc
 					dsync_mailbox_node_to_string(node));
 			}
 
-			/* Avoid sending out mailbox names with escape
-			   characters. Especially when dsync is used for
-			   migration, we don't want to end up having invalid
-			   mUTF7 mailbox names locally. Also, remote might not
-			   even be configured to use the same escape
-			   character. */
-			if (node->ns != NULL) {
-				i_assert(brain->alt_char != '\0');
-				full_name = t_str_replace(full_name,
-					node->ns->list->set.vname_escape_char,
-					brain->alt_char);
-			}
-			parts = t_strsplit(full_name, sep);
+			parts = dsync_brain_mailbox_to_parts(brain, full_name);
 			ret = dsync_ibc_send_mailbox_tree_node(brain->ibc,
 							       parts, node);
 		} T_END;
@@ -138,7 +145,8 @@ void dsync_brain_send_mailbox_tree_delet
 	deletes = dsync_mailbox_tree_get_deletes(brain->local_mailbox_tree,
 						 &count);
 	dsync_ibc_send_mailbox_deletes(brain->ibc, deletes, count,
-				       brain->hierarchy_sep);
+				       brain->hierarchy_sep,
+				       brain->escape_char);
 
 	brain->state = DSYNC_STATE_RECV_MAILBOX_TREE;
 }
@@ -210,12 +218,70 @@ dsync_is_valid_name(struct mail_namespac
 	return ret;
 }
 
+static bool
+dsync_is_valid_name_until(struct mail_namespace *ns, string_t *vname_full,
+			  unsigned int end_pos)
+{
+	const char *vname;
+	if (end_pos == str_len(vname_full))
+		vname = str_c(vname_full);
+	else
+		vname = t_strndup(str_c(vname_full), end_pos);
+	return dsync_is_valid_name(ns, vname);
+}
+
+static bool
+dsync_fix_mailbox_name_until(struct mail_namespace *ns, string_t *vname_full,
+			     char alt_char, unsigned int start_pos,
+			     unsigned int *_end_pos)
+{
+	unsigned int end_pos = *_end_pos;
+	unsigned int i;
+
+	if (dsync_is_valid_name_until(ns, vname_full, end_pos))
+		return TRUE;
+
+	/* 1) change any real separators to alt separators (this
+	   wouldn't be necessary with listescape, but don't bother
+	   detecting it) */
+	char list_sep = mailbox_list_get_hierarchy_sep(ns->list);
+	char ns_sep = mail_namespace_get_sep(ns);
+	if (list_sep != ns_sep) {
+		char *v = str_c_modifiable(vname_full);
+		for (i = start_pos; i < end_pos; i++) {
+			if (v[i] == list_sep)
+				v[i] = alt_char;
+		}
+		if (dsync_is_valid_name_until(ns, vname_full, end_pos))
+			return TRUE;
+	}
+
+	/* 2) '/' characters aren't valid without listescape */
+	if (ns_sep != '/' && list_sep != '/') {
+		char *v = str_c_modifiable(vname_full);
+		for (i = start_pos; i < end_pos; i++) {
+			if (v[i] == '/')
+				v[i] = alt_char;
+		}
+		if (dsync_is_valid_name_until(ns, vname_full, end_pos))
+			return TRUE;
+	}
+
+	/* 3) probably some reserved name (e.g. dbox-Mails or ..) */
+	str_insert(vname_full, start_pos, "_"); end_pos++; *_end_pos += 1;
+	if (dsync_is_valid_name_until(ns, vname_full, end_pos))
+		return TRUE;
+
+	return FALSE;
+}
+
 static void
 dsync_fix_mailbox_name(struct mail_namespace *ns, string_t *vname_str,
 		       char alt_char)
 {
 	const char *old_vname;
-	char *vname, list_sep = mailbox_list_get_hierarchy_sep(ns->list);
+	char *vname;
+	char ns_sep = mail_namespace_get_sep(ns);
 	guid_128_t guid;
 	unsigned int i, start_pos;
 
@@ -242,36 +308,30 @@ dsync_fix_mailbox_name(struct mail_names
 	if (dsync_is_valid_name(ns, vname))
 		return;
 
-	/* 1) change any real separators to alt separators (this wouldn't
-	   be necessary with listescape, but don't bother detecting it) */
-	if (list_sep != mail_namespace_get_sep(ns)) {
-		for (i = start_pos; vname[i] != '\0'; i++) {
-			if (vname[i] == list_sep)
-				vname[i] = alt_char;
-		}
-		if (dsync_is_valid_name(ns, vname))
-			return;
-	}
-	/* 2) '/' characters aren't valid without listescape */
-	if (mail_namespace_get_sep(ns) != '/' && list_sep != '/') {
-		for (i = start_pos; vname[i] != '\0'; i++) {
-			if (vname[i] == '/')
-				vname[i] = alt_char;
+	/* Check/fix each hierarchical name separately */
+	const char *p;
+	do {
+		i_assert(start_pos <= str_len(vname_str));
+		p = strchr(str_c(vname_str) + start_pos, ns_sep);
+		unsigned int end_pos;
+		if (p == NULL)
+			end_pos = str_len(vname_str);
+		else
+			end_pos = p - str_c(vname_str);
+
+		if (!dsync_fix_mailbox_name_until(ns, vname_str, alt_char,
+						  start_pos, &end_pos)) {
+			/* Couldn't fix it. Name is too long? Just give up and
+			   generate a unique name. */
+			guid_128_generate(guid);
+			str_truncate(vname_str, 0);
+			str_append(vname_str, ns->prefix);
+			str_append(vname_str, guid_128_to_string(guid));
+			i_assert(dsync_is_valid_name(ns, str_c(vname_str)));
+			break;
 		}
-		if (dsync_is_valid_name(ns, vname))
-			return;
-	}
-	/* 3) probably some reserved name (e.g. d