Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:dovecot
User: [email protected]
Usertags: pu
[ Reason ]
I've prepared a dovecot upload for trixie to ship several bug fixes
backported from newer upstream releases. In all cases, the fixes are
present in versions included in sid and forky. In all cases, the bug
submitters have confirmed that the fix resolves the issue as expected.
Note that this change would supersede the dovecot/1:2.4.1+dfsg1-6+deb13u7
currently in the security archive and s-p-u.
[ Impact ]
Several user-reported bugs will remain unfixed.
[ Tests ]
Upstream's test suite and autopkgtest all runs successfully. The bug
submitters have performed manual validation to confirm that the bugs are
indeed fixed.
Additionally, the upload includes a backported autopkgtest to validate
gssapi authentication to increase coverage. While none of the bugfixes
directly impact gssapi authentication, the additional coverage helps protect
against future regressions.
[ Risks ]
Two of the three changes were simple cherry-picks from upstream. However,
the fix for #1151242 does not apply cleanly because of changes to
surrounding code in the interim. Consequently I had to reimplement the
equivalent change against trixie's code. Diverging from upstream always
brings some additional risk, but because the change is entirely structural
and does not introduce any long-term divergence, I don't think this will be
a problem.
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
[ Changes ]
dovecot (1:2.4.1+dfsg1-6+deb13u8) trixie; urgency=medium
.
* [28e353e] dovecot-sieve: sievec fails to resolve :global/:before/:after
includes
(Closes: #1151164)
* [f644578] fix dovecot-imapd: IMAP parser error on APPEND with UTF8=ACCEPT
and LITERAL+
(Closes: #1151389)
* [cff13fe] config: Fix delaying errors for SET_FILE type (Closes: #1151242)
* [638e901] backport gssapi autopkgtest
* [c95ad64] salsa-ci: disable uscan test
diff -Nru dovecot-2.4.1+dfsg1/debian/changelog
dovecot-2.4.1+dfsg1/debian/changelog
--- dovecot-2.4.1+dfsg1/debian/changelog 2026-09-16 15:06:35.000000000
-0400
+++ dovecot-2.4.1+dfsg1/debian/changelog 2026-10-30 13:21:04.000000000
-0400
@@ -1,3 +1,15 @@
+dovecot (1:2.4.1+dfsg1-6+deb13u8) trixie; urgency=medium
+
+ * [28e353e] dovecot-sieve: sievec fails to resolve :global/:before/:after
includes
+ (Closes: #1151164)
+ * [f644578] fix dovecot-imapd: IMAP parser error on APPEND with UTF8=ACCEPT
and LITERAL+
+ (Closes: #1151389)
+ * [cff13fe] config: Fix delaying errors for SET_FILE type (Closes: #1151242)
+ * [638e901] backport gssapi autopkgtest
+ * [c95ad64] salsa-ci: disable uscan test
+
+ -- Noah Meyerhans <[email protected]> Fri, 30 Oct 2026 13:21:04 -0400
+
dovecot (1:2.4.1+dfsg1-6+deb13u7) trixie-security; urgency=medium
* Import upstream fixes for multiple security issues. (Closes: #1146018)
diff -Nru
dovecot-2.4.1+dfsg1/debian/patches/0001-config-Fix-delaying-errors-for-SET_FILE-type.patch
dovecot-2.4.1+dfsg1/debian/patches/0001-config-Fix-delaying-errors-for-SET_FILE-type.patch
---
dovecot-2.4.1+dfsg1/debian/patches/0001-config-Fix-delaying-errors-for-SET_FILE-type.patch
1969-12-31 19:00:00.000000000 -0500
+++
dovecot-2.4.1+dfsg1/debian/patches/0001-config-Fix-delaying-errors-for-SET_FILE-type.patch
2026-10-26 11:26:48.000000000 -0400
@@ -0,0 +1,51 @@
+From 62c597370c3900a4c6c99be1f39fbcb64cd9c393 Mon Sep 17 00:00:00 2001
+From: Noah Meyerhans <[email protected]>
+Date: Thu, 22 Oct 2026 17:23:38 -0400
+Subject: [PATCH] config: Fix delaying errors for SET_FILE type
+
+The error delaying requires access to the full setting name, but the
+previous code might have used autoprefixed short setting name.
+
+Based on 8799b7ef6842c56ad2966f2fe978439f89b0e740 from upstream
+
+Closes: #1151242
+---
+ src/config/config-parser.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/config/config-parser.c b/src/config/config-parser.c
+index f9920da621..a17b93084f 100644
+--- a/src/config/config-parser.c
++++ b/src/config/config-parser.c
+@@ -667,8 +667,8 @@ static int config_apply_filter_array(struct
config_parser_context *ctx,
+ }
+
+ static int config_apply_file(struct config_parser_context *ctx,
+- const struct config_line *line,
+- const char *path, const char **output_r)
++ const char *key, const char *path,
++ const char **output_r)
+ {
+ const char *full_path, *error;
+
+@@ -689,7 +689,7 @@ static int config_apply_file(struct config_parser_context
*ctx,
+ path = full_path;
+ if (settings_parse_read_file(full_path, path, ctx->pool, output_r,
&error) < 0) {
+ ctx->error = p_strdup(ctx->pool, error);
+- if (config_apply_error(ctx, line->key) < 0)
++ if (config_apply_error(ctx, key) < 0)
+ return -1;
+ /* delayed error */
+ *output_r = "";
+@@ -768,7 +768,7 @@ config_apply_exact_line(struct config_parser_context *ctx,
+ p_strconcat(ctx->pool, "\n",
inline_value, NULL);
+ break;
+ }
+- if (config_apply_file(ctx, line, value,
++ if (config_apply_file(ctx, key, value,
+
&l->settings[config_key->define_idx].str) < 0)
+ return -1;
+ break;
+--
+2.47.3
+
diff -Nru
dovecot-2.4.1+dfsg1/debian/patches/dovecot_imapd_IMAP_parser_error_on_APPEND_with_UTF_ACCEPT_and_LITERAL.patch
dovecot-2.4.1+dfsg1/debian/patches/dovecot_imapd_IMAP_parser_error_on_APPEND_with_UTF_ACCEPT_and_LITERAL.patch
---
dovecot-2.4.1+dfsg1/debian/patches/dovecot_imapd_IMAP_parser_error_on_APPEND_with_UTF_ACCEPT_and_LITERAL.patch
1969-12-31 19:00:00.000000000 -0500
+++
dovecot-2.4.1+dfsg1/debian/patches/dovecot_imapd_IMAP_parser_error_on_APPEND_with_UTF_ACCEPT_and_LITERAL.patch
2026-10-26 11:26:48.000000000 -0400
@@ -0,0 +1,493 @@
+Description: imap: Implement UTF8=ACCEPT for APPEND command
+Origin:
https://github.com/dovecot/core/compare/6c706fe3b962dfece59edc7b1f439084484ac213%5E...f12df6e0eed634aa4fe92c225b54ef5ee965b35c.patch
+Bug-Debian: https://bugs.debian.org/1151389
+
+---
+ src/lib-imap/test-imap-parser.c | 51 +++++++++++++++++++++++++++++++++
+ 1 file changed, 51 insertions(+)
+
+Index: trixie/src/lib-imap/test-imap-parser.c
+===================================================================
+--- trixie.orig/src/lib-imap/test-imap-parser.c
++++ trixie/src/lib-imap/test-imap-parser.c
+@@ -205,6 +205,56 @@ static void test_imap_parser_read_tag_cm
+ test_end();
+ }
+
++#define TEST_CASE(str, lit, flags, ret) \
++ { (str), sizeof((str)), (lit), (flags), (ret) }
++
++static void test_imap_parser_read_literal(void)
++{
++ test_begin("imap_parser_read_literal");
++
++ const struct {
++ const char *input;
++ size_t len;
++ uoff_t lit_len;
++ enum imap_parser_flags flags;
++ int ret;
++ } tests[] = {
++ TEST_CASE("{0}\n\n", 0, 0, 1),
++ TEST_CASE("{0+}\n\n", 0, 0, 1),
++ TEST_CASE("{0}\n", 0, 0, -1),
++ TEST_CASE("{-0}\n", 0, 0, -1),
++ TEST_CASE("{5}\nHello\n", 5, 0, 1),
++ TEST_CASE("{5+}\nHello\n\n", 5, 0, 1),
++ TEST_CASE("~{5}\nHello\n", 5, IMAP_PARSE_FLAG_LITERAL8, 1),
++ TEST_CASE("~{5+}\nHello\n\n", 5, IMAP_PARSE_FLAG_LITERAL8, 1),
++ TEST_CASE("{6+}\nhell\0o\n", 6, 0, 1),
++ TEST_CASE("~{6+}\nhell\xc3\xa5\n", 6, IMAP_PARSE_FLAG_LITERAL8,
1),
++ TEST_CASE("{6+}\nhello", 0, 0, -2),
++ TEST_CASE("{6+}\nhello\n", 0, 0, -1),
++ };
++
++ for (size_t i = 0; i < N_ELEMENTS(tests); i++) {
++ struct istream *input =
++ test_istream_create_data(tests[i].input, tests[i].len);
++ test_assert(i_stream_read(input) > 0);
++ struct imap_parser *parser =
++ imap_parser_create(input, NULL, 1024, NULL);
++ const struct imap_arg *args;
++ int ret = imap_parser_read_args(parser, 0, tests[i].flags,
&args);
++ if (ret != tests[i].ret) {
++ if (tests[i].ret < 0)
++ test_failed(t_strdup_printf("%zu: Unexpected
success %d", i, ret));
++ else
++ test_failed(t_strdup_printf("%zu: Unexpected
failure %d", i, ret));
++ } else if (ret >= 0) {
++ test_assert_cmp_idx(args[0].str_len, ==,
tests[i].lit_len, i);
++ }
++ imap_parser_unref(&parser);
++ i_stream_destroy(&input);
++ }
++ test_end();
++}
++
+ int main(void)
+ {
+ static void (*const test_functions[])(void) = {
+@@ -212,6 +262,7 @@ int main(void)
+ test_imap_parser_partial_list,
+ test_imap_parser_list_limit,
+ test_imap_parser_read_tag_cmd,
++ test_imap_parser_read_literal,
+ NULL
+ };
+ return test_run(test_functions);
+Index: trixie/src/imap/cmd-append.c
+===================================================================
+--- trixie.orig/src/imap/cmd-append.c
++++ trixie/src/imap/cmd-append.c
+@@ -49,9 +49,11 @@ struct cmd_append_context {
+ bool replace:1;
+ bool message_input:1;
+ bool binary_input:1;
++ bool utf8_input:1;
+ bool catenate:1;
+ bool cmd_args_set:1;
+ bool failed:1;
++ bool utf8_accept:1;
+ };
+
+ static void cmd_append_finish(struct cmd_append_context *ctx);
+@@ -289,6 +291,22 @@ static void cmd_append_catenate_text(str
+ }
+ }
+
++static void
++cmd_append_catenate_arg_text(struct client_command_context *cmd,
++ const struct imap_arg *args, bool *nonsync_r)
++{
++ struct cmd_append_context *ctx = cmd->context;
++
++ if (args->literal8 && !ctx->binary_input && !ctx->failed) {
++ client_send_tagline(cmd,
++ "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
++ "Binary input allowed only when the first part is
binary.");
++ ctx->failed = TRUE;
++ }
++ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
++ cmd_append_catenate_text(cmd);
++}
++
+ static int
+ cmd_append_catenate(struct client_command_context *cmd,
+ const struct imap_arg *args, bool *nonsync_r)
+@@ -322,15 +340,21 @@ cmd_append_catenate(struct client_comman
+ invalid_arg = TRUE;
+ break;
+ }
+- if (args->literal8 && !ctx->binary_input &&
+- !ctx->failed) {
+- client_send_tagline(cmd,
+- "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
+- "Binary input allowed only when the
first part is binary.");
+- ctx->failed = TRUE;
++ ctx->utf8_input = FALSE;
++ cmd_append_catenate_arg_text(cmd, args, nonsync_r);
++ return 1;
++ } else if (ctx->utf8_accept && strcasecmp(catpart, "UTF8") ==
0) {
++ const struct imap_arg *list_args = NULL;
++
++ args++;
++ if (!imap_arg_get_list(args, &list_args) ||
++ !list_args[0].literal8 ||
++ !imap_arg_get_literal_size(list_args,
&ctx->literal_size)) {
++ invalid_arg = TRUE;
++ break;
+ }
+- *nonsync_r = args->type ==
IMAP_ARG_LITERAL_SIZE_NONSYNC;
+- cmd_append_catenate_text(cmd);
++ ctx->utf8_input = TRUE;
++ cmd_append_catenate_arg_text(cmd, list_args, nonsync_r);
+ return 1;
+ } else {
+ break;
+@@ -376,6 +400,17 @@ static bool catenate_args_can_stop(struc
+ while (args->type != IMAP_ARG_EOL) {
+ if (imap_arg_atom_equals(args, "TEXT"))
+ return TRUE;
++ if (ctx->utf8_accept && imap_arg_atom_equals(args, "UTF8")) {
++ const struct imap_arg *list_args;
++
++ if (!imap_arg_get_list(++args, &list_args))
++ return FALSE;
++ if (args->type == IMAP_ARG_LITERAL_SIZE ||
++ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
++ return TRUE;
++ args++;
++ continue;
++ }
+ if (!imap_arg_atom_equals(args, "URL")) {
+ /* error - handle it later */
+ return TRUE;
+@@ -399,13 +434,61 @@ static bool catenate_args_can_stop(struc
+ return TRUE;
+ }
+
+-static bool cmd_append_continue_catenate(struct client_command_context *cmd)
++static void cmd_append_handle_parse_error(struct client_command_context *cmd)
+ {
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+- const struct imap_arg *args;
+ const char *client_error;
+ enum imap_parser_error parse_error;
++
++ client_error = imap_parser_get_error(ctx->save_parser, &parse_error);
++ switch (parse_error) {
++ case IMAP_PARSE_ERROR_NONE:
++ i_unreached();
++ case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
++ client_send_line(client, t_strconcat("* BYE ",
++ (client->set->imap_literal_minus ? "[TOOBIG] " : ""),
++ client_error, NULL));
++ client_disconnect(client, client_error);
++ break;
++ default:
++ if (!ctx->failed)
++ client_send_command_error(cmd, client_error);
++ }
++ client->input_skip_line = TRUE;
++}
++
++static int
++cmd_append_finish_list(struct client_command_context *cmd)
++{
++ struct cmd_append_context *ctx = cmd->context;
++ const struct imap_arg *args;
++ int ret;
++
++ ret = imap_parser_read_args(ctx->save_parser, 0,
++ IMAP_PARSE_FLAG_LITERAL_SIZE |
++ IMAP_PARSE_FLAG_LITERAL8 |
++ IMAP_PARSE_FLAG_INSIDE_LIST, &args);
++ if (ret == -1) {
++ cmd_append_handle_parse_error(cmd);
++ return -1;
++ }
++ if (ret < 0) {
++ /* need more data */
++ return 0;
++ }
++
++ if (!IMAP_ARG_IS_EOL(&args[0])) {
++ client_send_command_error(cmd, "Invalid arguments.");
++ return -1;
++ }
++ return 1;
++}
++
++static bool cmd_append_continue_catenate(struct client_command_context *cmd)
++{
++ struct cmd_append_context *ctx = cmd->context;
++ const struct imap_arg *args;
+ bool nonsync = FALSE;
+ int ret;
+
+@@ -415,6 +498,17 @@ static bool cmd_append_continue_catenate
+ return TRUE;
+ }
+
++ if (ctx->utf8_input) {
++ ret = cmd_append_finish_list(cmd);
++ if (ret == 0)
++ return FALSE;
++ if (ret < 0) {
++ cmd_append_finish(ctx);
++ return TRUE;
++ }
++ ctx->utf8_input = FALSE;
++ }
++
+ /* we're parsing inside CATENATE (..) list after handling a TEXT part.
+ it's fine that this would need to fully fit into input buffer
+ (although clients attempting to DoS could simply insert an extra
+@@ -426,22 +520,7 @@ static bool cmd_append_continue_catenate
+ IMAP_PARSE_FLAG_INSIDE_LIST, &args);
+ } while (ret > 0 && !catenate_args_can_stop(ctx, args));
+ if (ret == -1) {
+- client_error = imap_parser_get_error(ctx->save_parser,
+- &parse_error);
+- switch (parse_error) {
+- case IMAP_PARSE_ERROR_NONE:
+- i_unreached();
+- case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+- client_send_line(client, t_strconcat("* BYE ",
+- (client->set->imap_literal_minus ? "[TOOBIG] "
: ""),
+- client_error, NULL));
+- client_disconnect(client, client_error);
+- break;
+- default:
+- if (!ctx->failed)
+- client_send_command_error(cmd, client_error);
+- }
+- client->input_skip_line = TRUE;
++ cmd_append_handle_parse_error(cmd);
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+@@ -482,6 +561,67 @@ static bool cmd_append_continue_catenate
+ }
+
+ static int
++cmd_append_start_catenate(struct cmd_append_context *ctx,
++ const struct imap_arg **args,
++ const struct imap_arg **cat_list_r)
++{
++ const struct imap_arg *list_args;
++
++ if (!imap_arg_atom_equals(*args, "CATENATE"))
++ return 0;
++ if (!imap_arg_get_list(++(*args), &list_args))
++ return -1;
++
++ ctx->catenate = TRUE;
++
++ /* We'll do BINARY conversion only if the CATENATE's first
++ part is a literal8. If it doesn't and a literal8 is seen
++ later we'll abort the append with UNKNOWN-CTE. */
++ if (ctx->utf8_accept && imap_arg_atom_equals(&list_args[0], "UTF8")) {
++ const struct imap_arg *tmp_args;
++ if (!imap_arg_get_list(&list_args[1], &tmp_args))
++ return -1;
++ if (!tmp_args[0].literal8)
++ return -1;
++ ctx->utf8_input = TRUE;
++ ctx->binary_input = TRUE;
++ } else {
++ ctx->utf8_input = FALSE;
++ ctx->binary_input =
++ imap_arg_atom_equals(&list_args[0], "TEXT") &&
++ list_args[1].literal8;
++ }
++ *cat_list_r = list_args;
++ return 1;
++}
++
++static int
++cmd_append_start_utf8(struct cmd_append_context *ctx,
++ const struct imap_arg **args,
++ bool *nonsync_r)
++{
++ struct client *client = ctx->client;
++ const struct imap_arg *list_args;
++
++ if (!ctx->utf8_accept)
++ return 0;
++
++ if (!imap_arg_atom_equals(*args, "UTF8"))
++ return 0;
++ if (!imap_arg_get_list(++(*args), &list_args))
++ return -1;
++ if (!imap_arg_get_literal_size(list_args, &ctx->literal_size))
++ return -1;
++ if (!list_args[0].literal8)
++ return -1;
++ *nonsync_r = list_args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
++ ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size);
++ ctx->utf8_input = TRUE;
++ ctx->binary_input = TRUE;
++ return 1;
++}
++
++static int
+ cmd_append_handle_args(struct client_command_context *cmd,
+ const struct imap_arg *args, bool *nonsync_r)
+ {
+@@ -517,27 +657,22 @@ cmd_append_handle_args(struct client_com
+ args++;
+ }
+
+- /* <message literal> | CATENATE (..) */
++ /* <message literal> | CATENATE (..) | UTF8 (..) */
+ valid = FALSE;
+ *nonsync_r = FALSE;
+ ctx->catenate = FALSE;
+ if (imap_arg_get_literal_size(args, &ctx->literal_size)) {
+ *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
+ ctx->binary_input = args->literal8;
++ ctx->litinput = i_stream_create_limit(client->input,
ctx->literal_size);
++ ctx->utf8_input = FALSE;
++ ctx->litinput = i_stream_create_limit(client->input,
ctx->literal_size);
+ valid = TRUE;
+- } else if (!imap_arg_atom_equals(args, "CATENATE")) {
+- /* invalid */
+- } else if (!imap_arg_get_list(++args, &cat_list)) {
+- /* invalid */
+ } else {
+- valid = TRUE;
+- ctx->catenate = TRUE;
+- /* We'll do BINARY conversion only if the CATENATE's first
+- part is a literal8. If it doesn't and a literal8 is seen
+- later we'll abort the append with UNKNOWN-CTE. */
+- ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT")
&&
+- cat_list[1].literal8;
+-
++ ret = cmd_append_start_catenate(ctx, &args, &cat_list);
++ if (ret == 0)
++ ret = cmd_append_start_utf8(ctx, &args, nonsync_r);
++ valid = (ret > 0);
+ }
+ if (!IMAP_ARG_IS_EOL(&args[1]))
+ valid = FALSE;
+@@ -585,7 +720,7 @@ cmd_append_handle_args(struct client_com
+ timezone_offset = 0;
+ }
+
+- if (cat_list != NULL) {
++ if (ctx->catenate) {
+ ctx->cat_msg_size = 0;
+ ctx->input = i_stream_create_chain(&ctx->catchain,
+ IO_BLOCK_SIZE);
+@@ -606,7 +741,7 @@ cmd_append_handle_args(struct client_com
+ MULTIAPPEND here and adding more messages, it is
+ technically valid so we'll continue parsing.. */
+ }
+- ctx->litinput = i_stream_create_limit(client->input,
ctx->literal_size);
++ i_assert(ctx->litinput != NULL);
+ ctx->input = ctx->litinput;
+ i_stream_ref(ctx->input);
+ }
+@@ -639,7 +774,7 @@ cmd_append_handle_args(struct client_com
+ mailbox_keywords_unref(&keywords);
+ ctx->count++;
+
+- if (cat_list == NULL) {
++ if (!ctx->catenate) {
+ /* normal APPEND */
+ return 1;
+ } else if (cat_list->type == IMAP_ARG_EOL) {
+@@ -757,13 +892,14 @@ static bool cmd_append_args_can_stop(str
+ const struct imap_arg *args,
+ bool *last_literal_r)
+ {
+- const struct imap_arg *cat_list;
++ const struct imap_arg *list;
+
+ *last_literal_r = FALSE;
+ if (args->type == IMAP_ARG_EOL)
+ return TRUE;
+
+- /* [(flags)] ["internal date"] <message literal> | CATENATE (..) */
++ /* [(flags)] ["internal date"]
++ <message literal> | CATENATE (..) | UTF8 (..) */
+ if (args->type == IMAP_ARG_LIST)
+ args++;
+ if (args->type == IMAP_ARG_STRING)
+@@ -773,8 +909,16 @@ static bool cmd_append_args_can_stop(str
+ args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
+ return TRUE;
+ if (imap_arg_atom_equals(args, "CATENATE") &&
+- imap_arg_get_list(&args[1], &cat_list)) {
+- if (catenate_args_can_stop(ctx, cat_list))
++ imap_arg_get_list(&args[1], &list)) {
++ if (catenate_args_can_stop(ctx, list))
++ return TRUE;
++ *last_literal_r = TRUE;
++ }
++ if (ctx->utf8_accept &&
++ imap_arg_atom_equals(args, "UTF8") &&
++ imap_arg_get_list(&args[1], &list)) {
++ if (list->type == IMAP_ARG_LITERAL_SIZE ||
++ list->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
+ return TRUE;
+ *last_literal_r = TRUE;
+ }
+@@ -786,12 +930,21 @@ static bool cmd_append_parse_new_msg(str
+ struct client *client = cmd->client;
+ struct cmd_append_context *ctx = cmd->context;
+ const struct imap_arg *args;
+- const char *client_error;
+- enum imap_parser_error parse_error;
+ unsigned int arg_min_count;
+ bool nonsync, last_literal;
+ int ret;
+
++ if (ctx->utf8_input) {
++ ret = cmd_append_finish_list(cmd);
++ if (ret == 0)
++ return FALSE;
++ if (ret < 0) {
++ cmd_append_finish(ctx);
++ return TRUE;
++ }
++ ctx->utf8_input = FALSE;
++ }
++
+ /* this function gets called 1) after parsing APPEND <mailbox> and
+ 2) with MULTIAPPEND extension after already saving one or more
+ mails. */
+@@ -821,21 +974,7 @@ static bool cmd_append_parse_new_msg(str
+ } while (ret >= (int)arg_min_count &&
+ !cmd_append_args_can_stop(ctx, args, &last_literal));
+ if (ret == -1) {
+- if (!ctx->failed) {
+- client_error = imap_parser_get_error(ctx->save_parser,
&parse_error);
+- switch (parse_error) {
+- case IMAP_PARSE_ERROR_NONE:
+- i_unreached();
+- case IMAP_PARSE_ERROR_LITERAL_TOO_BIG:
+- client_send_line(client, t_strconcat("* BYE ",
+- (client->set->imap_literal_minus ?
"[TOOBIG] " : ""),
+- client_error, NULL));
+- client_disconnect(client, client_error);
+- break;
+- default:
+- client_send_command_error(cmd, client_error);
+- }
+- }
++ cmd_append_handle_parse_error(cmd);
+ cmd_append_finish(ctx);
+ return TRUE;
+ }
+@@ -1028,6 +1167,8 @@ static bool cmd_append_full(struct clien
+ ctx->client = client;
+ ctx->replace = replace;
+ ctx->started = ioloop_time;
++ ctx->utf8_accept = (client_enabled_mailbox_features(cmd->client) &
++ MAILBOX_FEATURE_UTF8ACCEPT) != 0;
+ if (client_open_save_dest_box(cmd, mailbox, &ctx->box) < 0)
+ ctx->failed = TRUE;
+ else {
diff -Nru
dovecot-2.4.1+dfsg1/debian/patches/lib_sieve_tool_sieve_tool_Do_not_override_all_sieve_script.patch
dovecot-2.4.1+dfsg1/debian/patches/lib_sieve_tool_sieve_tool_Do_not_override_all_sieve_script.patch
---
dovecot-2.4.1+dfsg1/debian/patches/lib_sieve_tool_sieve_tool_Do_not_override_all_sieve_script.patch
1969-12-31 19:00:00.000000000 -0500
+++
dovecot-2.4.1+dfsg1/debian/patches/lib_sieve_tool_sieve_tool_Do_not_override_all_sieve_script.patch
2026-10-26 11:26:48.000000000 -0400
@@ -0,0 +1,23 @@
+From c80a52d59a92c139cca481c9af718a2fbfa0ae78 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Eero=20H=C3=A4kkinen?= <[email protected]>
+Date: Thu, 11 Sep 2025 20:51:57 +0300
+Subject: [PATCH] lib-sieve-tool: sieve-tool - Do not override all sieve_script
+ settings
+
+---
+ src/lib-sieve-tool/sieve-tool.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+Index: trixie/pigeonhole/src/lib-sieve-tool/sieve-tool.c
+===================================================================
+--- trixie.orig/pigeonhole/src/lib-sieve-tool/sieve-tool.c
++++ trixie/pigeonhole/src/lib-sieve-tool/sieve-tool.c
+@@ -587,7 +587,7 @@ sieve_tool_script_parse_location(struct
+ settings_instance_find(svinst->event);
+ const char *prefix = t_strdup_printf("sieve_script/%s", storage_name);
+
+- settings_override(set_instance, "sieve_script", storage_name,
++ settings_override(set_instance, "sieve_script+", storage_name,
+ SETTINGS_OVERRIDE_TYPE_2ND_CLI_PARAM);
+ settings_override(set_instance,
+ t_strdup_printf("%s/sieve_script_storage", prefix),
diff -Nru dovecot-2.4.1+dfsg1/debian/patches/series
dovecot-2.4.1+dfsg1/debian/patches/series
--- dovecot-2.4.1+dfsg1/debian/patches/series 2026-09-16 15:06:35.000000000
-0400
+++ dovecot-2.4.1+dfsg1/debian/patches/series 2026-10-26 11:26:48.000000000
-0400
@@ -210,3 +210,6 @@
# CVE-2026-42395
0001-login-common-Fix-crash-when-XCLIENT-FORWARD-base64-c.patch
0002-login-common-Avoid-array_front-panic-on-empty-forwar.patch
+lib_sieve_tool_sieve_tool_Do_not_override_all_sieve_script.patch
+dovecot_imapd_IMAP_parser_error_on_APPEND_with_UTF_ACCEPT_and_LITERAL.patch
+0001-config-Fix-delaying-errors-for-SET_FILE-type.patch
diff -Nru dovecot-2.4.1+dfsg1/debian/salsa-ci.yml
dovecot-2.4.1+dfsg1/debian/salsa-ci.yml
--- dovecot-2.4.1+dfsg1/debian/salsa-ci.yml 2026-09-16 15:06:35.000000000
-0400
+++ dovecot-2.4.1+dfsg1/debian/salsa-ci.yml 2026-10-26 11:37:54.000000000
-0400
@@ -7,3 +7,4 @@
SALSA_CI_DISABLE_REPROTEST: 1
SALSA_CI_DISABLE_CROSSBUILD_ARM64: 1
SALSA_CI_DISABLE_BUILD_PACKAGE_TWICE: 1
+ SALSA_CI_DISABLE_USCAN: 1
diff -Nru dovecot-2.4.1+dfsg1/debian/tests/control
dovecot-2.4.1+dfsg1/debian/tests/control
--- dovecot-2.4.1+dfsg1/debian/tests/control 2026-08-29 09:13:34.000000000
-0400
+++ dovecot-2.4.1+dfsg1/debian/tests/control 2026-10-26 11:34:59.000000000
-0400
@@ -12,3 +12,8 @@
Tests: testmails
Restrictions: needs-root, breaks-testbed
Depends: dovecot-imapd, dovecot-pop3d, lsb-release, python3, python3-passlib
+
+Test-Command: run-parts --report --exit-on-error debian/tests/gssapi
+Restrictions: needs-root, breaks-testbed, allow-stderr
+Depends: dovecot-gssapi, dovecot-imapd, dovecot-pop3d, dovecot-managesieved,
+ sieve-connect, gsasl, python3, krb5-admin-server, python3-gssapi, iproute2
diff -Nru dovecot-2.4.1+dfsg1/debian/tests/gssapi/00_setup_krb5
dovecot-2.4.1+dfsg1/debian/tests/gssapi/00_setup_krb5
--- dovecot-2.4.1+dfsg1/debian/tests/gssapi/00_setup_krb5 1969-12-31
19:00:00.000000000 -0500
+++ dovecot-2.4.1+dfsg1/debian/tests/gssapi/00_setup_krb5 2026-10-26
11:34:59.000000000 -0400
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+
+# Copyright Noah Meyerhans <[email protected]>
+#
+# Adapted from gsasl autopkgtests from
+#
https://salsa.debian.org/debian/gsasl/-/blob/2ead79423c14d21b4ab05f016e8c6583d7afbe15/tests/gsasl-dovecot-gssapi.sh,
+# Copyright (C) 2022-2026 Simon Josefsson
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -e
+set -u
+set -x
+
+# Set up local KDC
+
+export PATH=$PATH:/sbin:/usr/sbin
+
+cat<<EOF > /etc/krb5kdc/kdc.conf
+[kdcdefaults]
+ kdc_ports = 750,88
+
+[realms]
+ KRB5.TEST = {
+ database_name = /var/lib/krb5kdc/principal
+ admin_keytab = FILE:/etc/krb5kdc/kadm5.keytab
+ acl_file = /etc/krb5kdc/kadm5.acl
+ key_stash_file = /etc/krb5kdc/stash
+ kdc_ports = 750,88
+ max_life = 10h 0m 0s
+ max_renewable_life = 7d 0h 0m 0s
+ #master_key_type = aes256-cts
+ #supported_enctypes = aes256-cts:normal aes128-cts:normal
+ default_principal_flags = +preauth
+ }
+
+EOF
+
+cat<<EOF > /etc/krb5.conf
+[libdefaults]
+ default_realm = KRB5.TEST
+
+[domain_realm]
+ .`hostname -d` = KRB5.TEST
+
+[realms]
+ KRB5.TEST = {
+ kdc = `hostname -f`
+ }
+EOF
+
+kdb5_util -P foo create -s
+kadmin.local addprinc -randkey imap/`hostname -f`
+kadmin.local addprinc -pw bar $USER
+kadmin.local ktadd -k /etc/dovecot.keytab imap/`hostname -f`
+chgrp dovecot /etc/dovecot.keytab
+chmod 440 /etc/dovecot.keytab
+
+systemctl restart krb5-admin-server.service krb5-kdc.service
+
+i=0
+while ! (ss -na || netstat -na) | grep 0.0.0.0:88 | grep LISTEN; do
+ i=`expr $i + 1`
+ test "$i" = "10" && exit 1
+ sleep 1
+done
+
+echo bar | kinit $USER
+
+exit 0
diff -Nru dovecot-2.4.1+dfsg1/debian/tests/gssapi/05_setup_dovecot
dovecot-2.4.1+dfsg1/debian/tests/gssapi/05_setup_dovecot
--- dovecot-2.4.1+dfsg1/debian/tests/gssapi/05_setup_dovecot 1969-12-31
19:00:00.000000000 -0500
+++ dovecot-2.4.1+dfsg1/debian/tests/gssapi/05_setup_dovecot 2026-10-26
11:34:59.000000000 -0400
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# Copyright Noah Meyerhans <[email protected]>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+rm -f /etc/dovecot/conf.d/10-auth.conf
+
+cat >"/etc/dovecot/local.conf" <<-EOF
+mail_driver = maildir
+mail_path = ~/Maildir
+mail_inbox_path = ~/Maildir
+
+auth_mechanisms = gssapi
+auth_gssapi_hostname = `hostname -f`
+auth_krb5_keytab = /etc/dovecot.keytab
+
+passdb static {
+ driver = static
+ passdb_static_password = test
+}
+
+userdb static {
+ driver = static
+ fields {
+ uid=nobody
+ gid=nogroup
+ home=/srv/dovecot-dep8/%{user | username}
+ }
+}
+EOF
+
+mkdir -p /srv/dovecot-dep8
+chown nobody:nogroup /srv/dovecot-dep8
+
+systemctl restart dovecot.service
+
+echo "Sending a test message via the LDA"
+/usr/lib/dovecot/dovecot-lda -f "[email protected]" -d dep8 <<EOF
+Return-Path: <[email protected]>
+Message-Id: <[email protected]>
+From: Test User <[email protected]>
+To: dep8 <[email protected]>
+Subject: DEP-8 test
+
+This is just a test
+EOF
+
+echo "Verifying that the email was correctly delivered"
+if [ -z "$(doveadm search -u dep8 header message-id [email protected])"
]; then
+ echo "Message not found"
+ exit 1
+fi
+
+echo "Done"
diff -Nru dovecot-2.4.1+dfsg1/debian/tests/gssapi/10_gsasl
dovecot-2.4.1+dfsg1/debian/tests/gssapi/10_gsasl
--- dovecot-2.4.1+dfsg1/debian/tests/gssapi/10_gsasl 1969-12-31
19:00:00.000000000 -0500
+++ dovecot-2.4.1+dfsg1/debian/tests/gssapi/10_gsasl 2026-10-26
11:34:59.000000000 -0400
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -ex
+
+gsasl -z "" -m GSSAPI -d --no-starttls --imap `hostname -f` | grep -q 'OK
Logged in'
+
+echo PASS: $0
+exit 0
diff -Nru dovecot-2.4.1+dfsg1/debian/tests/gssapi/11_imap
dovecot-2.4.1+dfsg1/debian/tests/gssapi/11_imap
--- dovecot-2.4.1+dfsg1/debian/tests/gssapi/11_imap 1969-12-31
19:00:00.000000000 -0500
+++ dovecot-2.4.1+dfsg1/debian/tests/gssapi/11_imap 2026-10-26
11:34:59.000000000 -0400
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# Copyright Noah Meyerhans <[email protected]>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import imaplib
+import gssapi
+
+from socket import getfqdn
+
+IMAP_HOST = getfqdn()
+SERVICE_NAME = "imap"
+
+imaplib.Debug = 4
+
+def gssapi_authenticate(imap_conn: imaplib.IMAP4, host: str, service_name: str
= "imap"):
+ """
+ Perform SASL GSSAPI authentication against an already-connected
+ imaplib.IMAP4 (or IMAP4_SSL) instance.
+ """
+
+ # Build the target service principal, e.g. "[email protected]"
+ service = gssapi.Name(f"{service_name}@{host}",
gssapi.NameType.hostbased_service)
+
+ # Establish a GSSAPI security context for that service.
+ ctx = gssapi.SecurityContext(name=service, usage="initiate")
+
+ # State shared across callback invocations. IMAP SASL exchange is
+ # multi-step, so imaplib will call our callback repeatedly with
+ # each server challenge until authentication completes.
+ state = {"step": 0}
+
+ def sasl_callback(challenge: bytes) -> bytes:
+ step = state["step"]
+ state["step"] += 1
+
+ if not ctx.complete:
+ # Step 1..N: perform the GSSAPI negotiation.
+ # On the first call, challenge is typically empty.
+ out_token = ctx.step(challenge)
+ if out_token is None:
+ out_token = b""
+ return out_token
+
+ # Context is established: final step handles the security
+ # layer negotiation (RFC 2222 / RFC 4752).
+ # The server sends an encrypted message indicating supported
+ # security layers and max message size; we unwrap it, then
+ # respond with our chosen layer (usually "no security layer",
+ # i.e. byte 1) and, optionally, our username/authorization id.
+ unwrapped = ctx.unwrap(challenge).message
+
+ # unwrapped[0] = bitmask of supported layers
+ # unwrapped[1:4] = max buffer size the server can accept
+ # We select "no security layer" (0x01) and keep buffer size.
+ response = bytes([1]) + unwrapped[1:4]
+
+ # Optionally append the authorization identity (often the
+ # username) to the response, per RFC 4752.
+ # response += authzid.encode("utf-8")
+
+ wrapped = ctx.wrap(response, False).message
+ return wrapped
+
+ # imaplib's authenticate() expects a callable that takes the raw
+ # (already base64-decoded) challenge and returns raw bytes to send
+ # (imaplib handles the base64 encoding/decoding for you).
+ typ, data = imap_conn.authenticate("GSSAPI", sasl_callback)
+
+ if typ != "OK":
+ raise imaplib.IMAP4.error(f"GSSAPI authentication failed: {data}")
+
+ return typ, data
+
+
+def main():
+ imap_conn = imaplib.IMAP4(IMAP_HOST)
+
+ try:
+ typ, data = gssapi_authenticate(imap_conn, IMAP_HOST, SERVICE_NAME)
+ print("Authenticated:", typ, data)
+
+ # Example: list mailboxes after successful auth.
+ typ, mailboxes = imap_conn.list()
+ print(typ, mailboxes)
+
+ # Example: select INBOX and get message count.
+ typ, sel_data = imap_conn.select("INBOX")
+ print("Selected INBOX:", typ, sel_data)
+
+ finally:
+ try:
+ imap_conn.logout()
+ except Exception:
+ pass
+
+
+if __name__ == "__main__":
+ main()