diff --git a/mail/cyrus-imapd34/Makefile b/mail/cyrus-imapd34/Makefile index 35a8252929ee..8b82bfdc0088 100644 --- a/mail/cyrus-imapd34/Makefile +++ b/mail/cyrus-imapd34/Makefile @@ -1,6 +1,6 @@ PORTNAME= cyrus-imapd -PORTVERSION= 3.4.7 -PORTREVISION= 1 +PORTVERSION= 3.4.8 +PORTREVISION= 0 CATEGORIES= mail MASTER_SITES= https://github.com/cyrusimap/cyrus-imapd/releases/download/${PORTNAME}-${PORTVERSION}/ PKGNAMESUFFIX= ${CYRUS_IMAPD_VER} @@ -20,8 +20,6 @@ http_PKGNAMESUFFIX= ${CYRUS_IMAPD_VER}-http CYRUS_IMAPD_VER= 34 -EXTRA_PATCHES= ${FILESDIR}/v34-CVE-2024-34055.patch:-p1 - LIB_DEPENDS= libsasl2.so:security/cyrus-sasl2 \ libicuuc.so:devel/icu \ libjansson.so:devel/jansson \ diff --git a/mail/cyrus-imapd34/distinfo b/mail/cyrus-imapd34/distinfo index 986ce4ee823f..9e3efd0bb118 100644 --- a/mail/cyrus-imapd34/distinfo +++ b/mail/cyrus-imapd34/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1710506943 -SHA256 (cyrus-imapd-3.4.7.tar.gz) = 8be5abdc8392de9e217a6c4c0b24132d16cf9f68e42c071ec9d4b9fdca44da38 -SIZE (cyrus-imapd-3.4.7.tar.gz) = 13411396 +TIMESTAMP = 1717583784 +SHA256 (cyrus-imapd-3.4.8.tar.gz) = 791258fae0bbfe6d39101a287910ec37368f454982d473b2ff93ab3ea91bf55a +SIZE (cyrus-imapd-3.4.8.tar.gz) = 13428824 diff --git a/mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch b/mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch deleted file mode 100644 index c1719ea49b28..000000000000 --- a/mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch +++ /dev/null @@ -1,5815 +0,0 @@ -From b6682068bf8c754a87f98ee59d2616d48ed756c7 Mon Sep 17 00:00:00 2001 -From: Robert Stepanek -Date: Wed, 3 Jan 2024 09:51:36 +0100 -Subject: [PATCH 01/22] SearchFuzzy.pm: do not use non-standard XSNIPPETS - command - -The XSNIPPETS and XCONVMULTISTANDARD commands in Cyrus got -deprecated, so don't keep our test using it. - -Signed-off-by: Robert Stepanek ---- - cassandane/Cassandane/Cyrus/SearchFuzzy.pm | 344 +++++++++------------ - 1 file changed, 146 insertions(+), 198 deletions(-) - -diff --git a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -index 1ac00dc49..dd1a369bd 100644 ---- a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -+++ b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -@@ -43,6 +43,8 @@ use warnings; - use Cwd qw(abs_path); - use DateTime; - use Data::Dumper; -+use MIME::Base64 qw(encode_base64); -+use Encode qw(decode encode); - - use lib '.'; - use base qw(Cassandane::Cyrus::TestCase); -@@ -50,10 +52,19 @@ use Cassandane::Util::Log; - - sub new - { -+ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); -- $config->set(conversations => 'on'); -- return $class->SUPER::new({ config => $config }, @args); -+ $config->set( -+ conversations => 'on', -+ httpallowcompress => 'no', -+ httpmodules => 'jmap', -+ ); -+ return $class->SUPER::new({ -+ config => $config, -+ jmap => 1, -+ services => [ 'imap', 'http' ] -+ }, @args); - } - - sub set_up -@@ -134,6 +145,55 @@ sub create_testmessages - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - } - -+sub get_snippets -+{ -+ # Previous versions of this test module used XSNIPPETS to -+ # assert snippets but this command got removed from Cyrus. -+ # Use JMAP instead. -+ -+ my ($self, $folder, $uids, $filter) = @_; -+ -+ my $imap = $self->{store}->get_client(); -+ my $jmap = $self->{jmap}; -+ -+ $self->assert_not_null($jmap); -+ -+ $imap->select($folder); -+ my $res = $imap->fetch($uids, ['emailid']); -+ my %emailIdToImapUid = map { $res->{$_}{emailid}[0] => $_ } keys %$res; -+ -+ $res = $jmap->CallMethods([ -+ ['SearchSnippet/get', { -+ filter => $filter, -+ emailIds => [ keys %emailIdToImapUid ], -+ }, 'R1'], -+ ]); -+ -+ my @snippets; -+ foreach (@{$res->[0][1]{list}}) { -+ if ($_->{subject}) { -+ push(@snippets, [ -+ 0, -+ $emailIdToImapUid{$_->{emailId}}, -+ 'SUBJECT', -+ $_->{subject}, -+ ]); -+ } -+ if ($_->{preview}) { -+ push(@snippets, [ -+ 0, -+ $emailIdToImapUid{$_->{emailId}}, -+ 'BODY', -+ $_->{preview}, -+ ]); -+ } -+ } -+ -+ return { -+ snippets => [ sort { $a->[1] <=> $b->[1] } @snippets ], -+ }; -+} -+ - sub test_copy_messages - :needs_search_xapian - { -@@ -151,12 +211,13 @@ sub test_copy_messages - } - - sub test_stem_verbs -- :min_version_3_0 :needs_search_xapian -+ :min_version_3_0 :needs_search_xapian :JMAPExtensions - { - my ($self) = @_; - $self->create_testmessages(); - - my $talk = $self->{store}->get_client(); -+ $self->assert_not_null($self->{jmap}); - - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; -@@ -175,11 +236,8 @@ sub test_stem_verbs - $r = $talk->search('fuzzy', ['subject', { Quote => "runs" }]) || die; - $self->assert_num_equals(3, scalar @$r); - -- xlog $self, 'XSNIPPETS for FUZZY subject "runs"'; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'subject', { Quote => 'runs' }] -- ) || die; -+ xlog $self, 'Get snippets for FUZZY subject "runs"'; -+ $r = $self->get_snippets('INBOX', $uids, { subject => 'runs' }); - $self->assert_num_equals(3, scalar @{$r->{snippets}}); - } - -@@ -250,12 +308,8 @@ sub test_snippet_wildcard - $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - -- xlog $self, "XSNIPPETS for $term"; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => "$term*" }] -- ) || die; -- xlog $self, Dumper($r); -+ xlog $self, "Get snippets for $term"; -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => "$term*" }); - $self->assert_num_equals(2, scalar @{$r->{snippets}}); - } - -@@ -358,13 +412,17 @@ sub test_normalize_snippets - my ($self) = @_; - - # Set up test message with funny characters -- my $body = "foo gären советской diĝir naïve léger"; -- my @terms = split / /, $body; -+use utf8; -+ my @terms = ( "gären", "советской", "diĝir", "naïve", "léger" ); -+no utf8; -+ my $body = encode_base64(encode('UTF-8', join(' ', @terms))); -+ $body =~ s/\r?\n/\r\n/gs; - - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("1", %params) || die; - -@@ -380,24 +438,20 @@ sub test_normalize_snippets - - # Assert that diacritics are matched and returned - foreach my $term (@terms) { -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); - } - - # Assert that search without diacritics matches - if ($self->{skipdiacrit}) { - my $term = "naive"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "naïve"), -1); -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => $term }); -+use utf8; -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "naïve"), -1); -+no utf8; - } -+ - } - - sub test_skipdiacrit -@@ -499,38 +553,23 @@ sub test_snippets_termcover - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); -- my $want = "favourite cereal"; -+ my $want = "favourite cereal"; - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', 'favourite', -- 'fuzzy', 'text', 'cereal', -- 'fuzzy', 'text', { Quote => 'bogus gnarly' } -- ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { -+ operator => 'AND', -+ conditions => [{ -+ text => 'favourite', -+ }, { -+ text => 'cereal', -+ }, { -+ text => '"bogus gnarly"' -+ }], -+ }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', 'favourite cereal' -- ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); -- -- # Regression - a phrase is treated as a loose term -- $r = $talk->xsnippets( [ [ 'INBOX', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', { Quote => 'favourite nope cereal' }, -- 'fuzzy', 'text', { Quote => 'bogus gnarly' } -- ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); -- -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', { Quote => 'favourite cereal' } -- ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { -+ text => 'favourite cereal', -+ }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); - } - -@@ -542,18 +581,28 @@ sub test_cjk_words - - xlog $self, "Generate and index test messages."; - -+use utf8; - my $body = "明末時已經有香港地方的概念"; -+no utf8; -+ $body = encode_base64(encode('UTF-8', $body)); -+ $body =~ s/\r?\n/\r\n/gs; - my %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("1", %params) || die; - - # Splits into the words: "み, 円, 月額, 申込 -+use utf8; - $body = "申込み!月額円"; -+no utf8; -+ $body = encode_base64(encode('UTF-8', $body)); -+ $body =~ s/\r?\n/\r\n/gs; - %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("2", %params) || die; - -@@ -569,50 +618,45 @@ sub test_cjk_words - - my $term; - # Search for a two-character CJK word -+use utf8; - $term = "已經"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); - - # Search for the CJK words 明末 and 時, note that the - # word order is reversed to the original message -+use utf8; - $term = "時明末"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); - - # Search for the partial CJK word 月 -+use utf8; - $term = "月"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); - - # Search for the interleaved, partial CJK word 額申 -+use utf8; - $term = "額申"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); - - # Search for three of four words: "み, 月額, 申込", - # in different order than the original. -+use utf8; - $term = "月額み申込"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); - } - -@@ -805,86 +849,6 @@ sub test_xattachmentname - } - - --sub test_xapianv2 -- :min_version_3_0 :needs_search_xapian --{ -- my ($self) = @_; -- -- my $talk = $self->{store}->get_client(); -- -- # This is a smallish regression test to check if we break something -- # obvious by moving Xapian indexing from folder:uid to message guids. -- # -- # Apart from the tests in this module, at least also the following -- # imodules are relevant: Metadata for SORT, Thread for THREAD. -- -- xlog $self, "Generate message"; -- my $r = $self->make_message("I run", body => "Run, Forrest! Run!" ) || die; -- my $uid = $r->{attrs}->{uid}; -- -- xlog $self, "Copy message into INBOX"; -- $talk->copy($uid, "INBOX"); -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(2, scalar @{$r->{sort}[0]} - 1); -- $self->assert_num_equals(1, scalar @{$r->{sort}}); -- -- xlog $self, "Create target mailbox"; -- $talk->create("INBOX.target"); -- -- xlog $self, "Copy message into INBOX.target"; -- $talk->copy($uid, "INBOX.target"); -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(3, scalar @{$r->{sort}[0]} - 1); -- $self->assert_num_equals(1, scalar @{$r->{sort}}); -- -- xlog $self, "Generate message"; -- $self->make_message("You run", body => "A running joke" ) || die; -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(2, scalar @{$r->{sort}}); -- -- xlog $self, "SEARCH FUZZY"; -- $r = $talk->search( -- "charset", "utf-8", "fuzzy", "text", "run", -- ) || die; -- $self->assert_num_equals(3, scalar @$r); -- -- xlog $self, "Select INBOX"; -- $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- my $uids = $talk->search('1:*', 'NOT', 'DELETED'); -- -- xlog $self, "XSNIPPETS"; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'body', 'run'], -- ) || die; -- $self->assert_num_equals(3, scalar @{$r->{snippets}}); --} -- - sub test_snippets_escapehtml - :min_version_3_0 :needs_search_xapian - { -@@ -914,21 +878,15 @@ sub test_snippets_escapehtml - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - my %m; - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', 'test1' ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test1' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert_str_equals("Test1 body with the same tag as snippets", $m{body}); -- $self->assert_str_equals("Test1 subject with an unescaped & in it", $m{subject}); -- -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', 'test2' ] -- ) || die; -+ $self->assert_str_equals("Test1 body with the same tag as snippets", $m{body}); -+ $self->assert_str_equals("Test1 subject with an unescaped & in it", $m{subject}); - -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test2' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert_str_equals("Test2 body with a <tag/>, although it's plain text", $m{body}); -- $self->assert_str_equals("Test2 subject with a <tag> in it", $m{subject}); -+ $self->assert_str_equals("Test2 body with a <tag/>, although it's plain text", $m{body}); -+ $self->assert_str_equals("Test2 subject with a <tag> in it", $m{subject}); - } - - sub test_search_exactmatch -@@ -963,13 +921,10 @@ sub test_search_exactmatch - $self->assert_num_equals(1, scalar @$uids); - - my %m; -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'body', $query ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { body => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert(index($m{body}, "some text") != -1); -- $self->assert(index($m{body}, "some long text") == -1); -+ $self->assert(index($m{body}, "some text") != -1); -+ $self->assert(index($m{body}, "some long text") == -1); - } - - sub test_search_subjectsnippet -@@ -1004,10 +959,7 @@ sub test_search_subjectsnippet - $self->assert_num_equals(1, scalar @$uids); - - my %m; -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', $query ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { text => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert_matches(qr/^\[plumbing\]/, $m{subject}); - } -@@ -1317,11 +1269,10 @@ sub test_detect_language - $self->assert_deep_equals([1], $uids); - - my $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'body', 'atmet' ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe atmeten.')); -+ $r = $self->get_snippets('INBOX', $uids, { body => 'atmet' }); -+use utf8; -+ $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe atmeten.')); -+no utf8; - } - - sub test_detect_language_subject -@@ -1377,12 +1328,9 @@ sub test_detect_language_subject - $self->assert_deep_equals([1], $uids); - - my $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'subject', 'Landschaft' ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { subject => 'Landschaft' }); - $self->assert_str_equals( -- 'A subject with the German word Landschaften', -+ 'A subject with the German word Landschaften', - $r->{snippets}[0][3] - ); - } --- -2.39.2 - - -From 00aafb0fd51aaac1badc3370a250605cff4313b0 Mon Sep 17 00:00:00 2001 -From: Bron Gondwana -Date: Fri, 20 Nov 2020 11:24:58 +1100 -Subject: [PATCH 02/22] imapd: maxsize for appends - ---- - imap/imapd.c | 4 ++++ - lib/imapoptions | 4 ++++ - 2 files changed, 8 insertions(+) - -diff --git a/imap/imapd.c b/imap/imapd.c -index a617ff80c..48055ccce 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3829,6 +3829,8 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - const char *parseerr = NULL, *url = NULL; - struct appendstage *curstage; - mbentry_t *mbentry = NULL; -+ size_t maxsize = config_getint(IMAPOPT_APPEND_MAXSIZE) * 1024; -+ if (!maxsize) maxsize = UINT32_MAX; - - memset(&appendstate, 0, sizeof(struct appendstate)); - -@@ -4004,12 +4006,14 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - size = 0; - r = append_catenate(curstage->f, cur_name, &size, - &(curstage->binary), &parseerr, &url); -+ if (!r && size > maxsize) r = IMAP_MESSAGE_TOO_LARGE; - if (r) goto done; - } - else { - /* Read size from literal */ - r = getliteralsize(arg.s, c, &size, &(curstage->binary), &parseerr); - if (!r && size == 0) r = IMAP_ZERO_LENGTH_LITERAL; -+ if (!r && size > maxsize) r = IMAP_MESSAGE_TOO_LARGE; - if (r) goto done; - - /* Copy message to stage */ -diff --git a/lib/imapoptions b/lib/imapoptions -index 5cb8ef7b8..786b288fe 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -296,6 +296,10 @@ Blank lines and lines beginning with ``#'' are ignored. - but might be useful in the meantime for supporting old clients that - do not implement the RFC 5464 IMAP METADATA extension. */ - -+{ "append_maxsize", 0, INT, "3.3.2" } -+/* The size in kilobytes of the largest message that can be appended -+ via IMAP. If zero, no limit (i.e UINT32_MAX) */ -+ - { "aps_topic", NULL, STRING, "3.0.0" } - /* Topic for Apple Push Service registration. */ - { "aps_topic_caldav", NULL, STRING, "3.0.0" } --- -2.39.2 - - -From 02f158782578d4d99e0915c317ffe9d339180cca Mon Sep 17 00:00:00 2001 -From: Bron Gondwana -Date: Fri, 20 Nov 2020 12:54:58 +1100 -Subject: [PATCH 03/22] imapd: push the maxsize down into each parser to avoid - spooling - ---- - imap/imap_proxy.c | 7 ++++++- - imap/imap_proxy.h | 2 +- - imap/imapd.c | 42 ++++++++++++++++++------------------------ - imap/index.c | 7 ++++++- - imap/index.h | 2 +- - 5 files changed, 32 insertions(+), 28 deletions(-) - -diff --git a/imap/imap_proxy.c b/imap/imap_proxy.c -index fb585e680..2dac80455 100644 ---- a/imap/imap_proxy.c -+++ b/imap/imap_proxy.c -@@ -1207,7 +1207,7 @@ void proxy_copy(const char *tag, char *sequence, char *name, int myrights, - /* xxx end of separate proxy-only code */ - - int proxy_catenate_url(struct backend *s, struct imapurl *url, FILE *f, -- unsigned long *size, const char **parseerr) -+ size_t maxsize, unsigned long *size, const char **parseerr) - { - char mytag[128]; - int c, r = 0, found = 0; -@@ -1309,6 +1309,11 @@ int proxy_catenate_url(struct backend *s, struct imapurl *url, FILE *f, - if (c == '}') c = prot_getc(s->in); - if (c == '\r') c = prot_getc(s->in); - if (c != '\n') c = EOF; -+ if (sz > maxsize) { -+ r = IMAP_MESSAGE_TOO_LARGE; -+ eatline(s->in, c); -+ goto next_resp; -+ } - } - else if (c == 'n' || c == 'N') { - c = chomp(s->in, "il"); -diff --git a/imap/imap_proxy.h b/imap/imap_proxy.h -index aa2170960..89cb02002 100644 ---- a/imap/imap_proxy.h -+++ b/imap/imap_proxy.h -@@ -86,7 +86,7 @@ void proxy_copy(const char *tag, char *sequence, char *name, int myrights, - int usinguid, struct backend *s); - - int proxy_catenate_url(struct backend *s, struct imapurl *url, FILE *f, -- unsigned long *size, const char **parseerr); -+ size_t maxsize, unsigned long *size, const char **parseerr); - - int annotate_fetch_proxy(const char *server, const char *mbox_pat, - const strarray_t *entry_pat, -diff --git a/imap/imapd.c b/imap/imapd.c -index 48055ccce..2e55a6285 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3534,7 +3534,7 @@ static int isokflag(char *s, int *isseen) - } - } - --static int getliteralsize(const char *p, int c, -+static int getliteralsize(const char *p, int c, size_t maxsize, - unsigned *size, int *binary, const char **parseerr) - - { -@@ -3573,6 +3573,9 @@ static int getliteralsize(const char *p, int c, - return IMAP_PROTOCOL_ERROR; - } - -+ if (num > maxsize) -+ return IMAP_MESSAGE_TOO_LARGE; -+ - if (!isnowait) { - /* Tell client to send the message */ - prot_printf(imapd_out, "+ go ahead\r\n"); -@@ -3584,7 +3587,7 @@ static int getliteralsize(const char *p, int c, - return 0; - } - --static int catenate_text(FILE *f, unsigned *totalsize, int *binary, -+static int catenate_text(FILE *f, size_t maxsize, unsigned *totalsize, int *binary, - const char **parseerr) - { - int c; -@@ -3597,11 +3600,9 @@ static int catenate_text(FILE *f, unsigned *totalsize, int *binary, - c = getword(imapd_in, &arg); - - /* Read size from literal */ -- r = getliteralsize(arg.s, c, &size, binary, parseerr); -+ r = getliteralsize(arg.s, c, maxsize - *totalsize, &size, binary, parseerr); - if (r) return r; - -- if (*totalsize > UINT_MAX - size) r = IMAP_MESSAGE_TOO_LARGE; -- - /* Catenate message part to stage */ - while (size) { - n = prot_read(imapd_in, buf, size > 4096 ? 4096 : size); -@@ -3629,7 +3630,7 @@ static int catenate_text(FILE *f, unsigned *totalsize, int *binary, - } - - static int catenate_url(const char *s, const char *cur_name, FILE *f, -- unsigned *totalsize, const char **parseerr) -+ size_t maxsize, unsigned *totalsize, const char **parseerr) - { - struct imapurl url; - struct index_state *state; -@@ -3668,11 +3669,8 @@ static int catenate_url(const char *s, const char *cur_name, FILE *f, - proxy_userid, &backend_cached, - &backend_current, &backend_inbox, imapd_in); - if (be) { -- r = proxy_catenate_url(be, &url, f, &size, parseerr); -- if (*totalsize > UINT_MAX - size) -- r = IMAP_MESSAGE_TOO_LARGE; -- else -- *totalsize += size; -+ r = proxy_catenate_url(be, &url, f, maxsize - *totalsize, &size, parseerr); -+ *totalsize += size; - } - else - r = IMAP_SERVER_UNAVAILABLE; -@@ -3727,14 +3725,12 @@ static int catenate_url(const char *s, const char *cur_name, FILE *f, - struct protstream *s = prot_new(fileno(f), 1); - - r = index_urlfetch(state, msgno, 0, url.section, -- url.start_octet, url.octet_count, s, &size); -+ url.start_octet, url.octet_count, s, -+ maxsize - *totalsize, &size); - if (r == IMAP_BADURL) - *parseerr = "No such message part"; - else if (!r) { -- if (*totalsize > UINT_MAX - size) -- r = IMAP_MESSAGE_TOO_LARGE; -- else -- *totalsize += size; -+ *totalsize += size; - } - - prot_flush(s); -@@ -3751,7 +3747,7 @@ static int catenate_url(const char *s, const char *cur_name, FILE *f, - return r; - } - --static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize, -+static int append_catenate(FILE *f, const char *cur_name, size_t maxsize, unsigned *totalsize, - int *binary, const char **parseerr, const char **url) - { - int c, r = 0; -@@ -3765,7 +3761,7 @@ static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize, - } - - if (!strcasecmp(arg.s, "TEXT")) { -- int r1 = catenate_text(f, totalsize, binary, parseerr); -+ int r1 = catenate_text(f, maxsize, totalsize, binary, parseerr); - if (r1) return r1; - - /* if we see a SP, we're trying to catenate more than one part */ -@@ -3781,7 +3777,7 @@ static int append_catenate(FILE *f, const char *cur_name, unsigned *totalsize, - } - - if (!r) { -- r = catenate_url(arg.s, cur_name, f, totalsize, parseerr); -+ r = catenate_url(arg.s, cur_name, f, maxsize, totalsize, parseerr); - if (r) { - *url = arg.s; - return r; -@@ -4004,16 +4000,14 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - - /* Catenate the message part(s) to stage */ - size = 0; -- r = append_catenate(curstage->f, cur_name, &size, -+ r = append_catenate(curstage->f, cur_name, maxsize, &size, - &(curstage->binary), &parseerr, &url); -- if (!r && size > maxsize) r = IMAP_MESSAGE_TOO_LARGE; - if (r) goto done; - } - else { - /* Read size from literal */ -- r = getliteralsize(arg.s, c, &size, &(curstage->binary), &parseerr); -+ r = getliteralsize(arg.s, c, maxsize, &size, &(curstage->binary), &parseerr); - if (!r && size == 0) r = IMAP_ZERO_LENGTH_LITERAL; -- if (!r && size > maxsize) r = IMAP_MESSAGE_TOO_LARGE; - if (r) goto done; - - /* Copy message to stage */ -@@ -14010,7 +14004,7 @@ static void cmd_urlfetch(char *tag) - } else { - r = index_urlfetch(state, msgno, params, url.section, - url.start_octet, url.octet_count, -- imapd_out, NULL); -+ imapd_out, UINT32_MAX, NULL); - } - - err: -diff --git a/imap/index.c b/imap/index.c -index ef537aa55..35ca866aa 100644 ---- a/imap/index.c -+++ b/imap/index.c -@@ -4550,7 +4550,7 @@ static int index_fetchreply(struct index_state *state, uint32_t msgno, - EXPORTED int index_urlfetch(struct index_state *state, uint32_t msgno, - unsigned params, const char *section, - unsigned long start_octet, unsigned long octet_count, -- struct protstream *pout, unsigned long *outsize) -+ struct protstream *pout, size_t maxsize, unsigned long *outsize) - { - /* dumbass eM_Client sends this: - * A4 APPEND "INBOX.Junk Mail" () "14-Jul-2013 17:01:02 +0000" -@@ -4723,6 +4723,11 @@ EXPORTED int index_urlfetch(struct index_state *state, uint32_t msgno, - n = size - start_octet; - } - -+ if (n > maxsize) { -+ r = IMAP_MESSAGE_TOO_LARGE; -+ goto done; -+ } -+ - if (outsize) { - /* Return size (CATENATE) */ - *outsize = n; -diff --git a/imap/index.h b/imap/index.h -index 196607f3f..bf8006d9b 100644 ---- a/imap/index.h -+++ b/imap/index.h -@@ -303,7 +303,7 @@ extern struct seqset *index_vanished(struct index_state *state, - extern int index_urlfetch(struct index_state *state, uint32_t msgno, - unsigned params, const char *section, - unsigned long start_octet, unsigned long octet_count, -- struct protstream *pout, unsigned long *size); -+ struct protstream *pout, size_t maxsize, unsigned long *size); - extern char *index_get_msgid(struct index_state *state, uint32_t msgno); - extern struct nntp_overview *index_overview(struct index_state *state, - uint32_t msgno); --- -2.39.2 - - -From 133a11ebfd9e3f659da3081d8e7c9f416c8ead3b Mon Sep 17 00:00:00 2001 -From: Bron Gondwana -Date: Tue, 1 Dec 2020 08:11:31 +1100 -Subject: [PATCH 04/22] use maxmessagesize rather than our own config option - ---- - imap/imapd.c | 2 +- - lib/imapoptions | 4 ---- - 2 files changed, 1 insertion(+), 5 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index 2e55a6285..d9a9dd776 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3825,7 +3825,7 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - const char *parseerr = NULL, *url = NULL; - struct appendstage *curstage; - mbentry_t *mbentry = NULL; -- size_t maxsize = config_getint(IMAPOPT_APPEND_MAXSIZE) * 1024; -+ size_t maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE) * 1024; - if (!maxsize) maxsize = UINT32_MAX; - - memset(&appendstate, 0, sizeof(struct appendstate)); -diff --git a/lib/imapoptions b/lib/imapoptions -index 786b288fe..5cb8ef7b8 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -296,10 +296,6 @@ Blank lines and lines beginning with ``#'' are ignored. - but might be useful in the meantime for supporting old clients that - do not implement the RFC 5464 IMAP METADATA extension. */ - --{ "append_maxsize", 0, INT, "3.3.2" } --/* The size in kilobytes of the largest message that can be appended -- via IMAP. If zero, no limit (i.e UINT32_MAX) */ -- - { "aps_topic", NULL, STRING, "3.0.0" } - /* Topic for Apple Push Service registration. */ - { "aps_topic_caldav", NULL, STRING, "3.0.0" } --- -2.39.2 - - -From ddc431769b61eef06550da624c1c99a2fd620dbb Mon Sep 17 00:00:00 2001 -From: ellie timoney -Date: Wed, 27 Mar 2024 11:31:58 +1100 -Subject: [PATCH 05/22] imapd: read maxmsgsize once at startup - -Based on: -40793dfde8c96797d86f80e9f461bea61bca3bc9 imapd.c: Advertise APPENDLIMIT= capability - -but without introducing the APPENDLIMIT= capability ---- - imap/imapd.c | 6 ++++-- - 1 file changed, 4 insertions(+), 2 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index d9a9dd776..e7cf600c7 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -135,6 +135,7 @@ static int imaps = 0; - static sasl_ssf_t extprops_ssf = 0; - static int nosaslpasswdcheck = 0; - static int apns_enabled = 0; -+static size_t maxsize = 0; - - /* PROXY STUFF */ - /* we want a list of our outgoing connections here and which one we're -@@ -908,6 +909,9 @@ int service_init(int argc, char **argv, char **envp) - - prometheus_increment(CYRUS_IMAP_READY_LISTENERS); - -+ maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE) * 1024; -+ if (!maxsize) maxsize = UINT32_MAX; -+ - return 0; - } - -@@ -3825,8 +3829,6 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - const char *parseerr = NULL, *url = NULL; - struct appendstage *curstage; - mbentry_t *mbentry = NULL; -- size_t maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE) * 1024; -- if (!maxsize) maxsize = UINT32_MAX; - - memset(&appendstate, 0, sizeof(struct appendstate)); - --- -2.39.2 - - -From a32fe042bc503a36393e7d888b26b6c1759cf6b0 Mon Sep 17 00:00:00 2001 -From: Matthew Horsfall -Date: Wed, 15 Jun 2022 14:57:02 -0400 -Subject: [PATCH 06/22] imap/imapd.c: IMAPOPT_MAXMESSAGESIZE is bytes, not - kilobytes - -I think this was a mistake added in bf28aa3fb6 when replacing -IMAPOPT_APPEND_MAXSIZE. - -Signed-off-by: Matthew Horsfall ---- - imap/imapd.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index e7cf600c7..ce8c6f675 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -909,7 +909,7 @@ int service_init(int argc, char **argv, char **envp) - - prometheus_increment(CYRUS_IMAP_READY_LISTENERS); - -- maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE) * 1024; -+ maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE); - if (!maxsize) maxsize = UINT32_MAX; - - return 0; --- -2.39.2 - - -From 2139228a5f43371258e1c460d0db509dcae7a4aa Mon Sep 17 00:00:00 2001 -From: ellie timoney -Date: Wed, 20 Jan 2021 13:25:16 +1100 -Subject: [PATCH 07/22] tools/config2header: allow "UNRELEASED" as imapoptions - version string - ---- - tools/config2header | 26 ++++++++++++++++++++++++-- - 1 file changed, 24 insertions(+), 2 deletions(-) - -diff --git a/tools/config2header b/tools/config2header -index 98418e147..4393e06e0 100755 ---- a/tools/config2header -+++ b/tools/config2header -@@ -128,6 +128,7 @@ EXPORTED struct imapopt_s imapopts[] = - EOF - ; - -+my $__warned_unreleased = 0; - sub parse_last_modified - { - my ($version) = @_; -@@ -141,6 +142,23 @@ sub parse_last_modified - - return sprintf "0x%2.2X%2.2X%2.2X00", $maj, $min, $rev; - } -+ elsif ($version eq 'UNRELEASED') { -+ if (not $__warned_unreleased) { -+ # This warning is to remind the release manager to replace -+ # "UNRELEASED" strings in lib/imapoptions with the version -+ # number that is about to be released. -+ # If you're not building a release, ignore it. :) -+ my $w = join q{ }, -+ "$0:", -+ -t STDERR ? "\033[33;1mwarning:\033[0m" : 'warning:', -+ 'build contains UNRELEASED config options'; -+ print STDERR "$w\n"; -+ -+ $__warned_unreleased ++; -+ } -+ -+ return "0xFFFFFFFF"; -+ } - else { - die "unparseable version: $version"; - } -@@ -301,15 +319,19 @@ while () { - # option is deprecated - if ($6 =~ m| - ,\s* # comma and optional whitespace -- (\"[^,]+\") # $1: 'deprecated since' version string -+ \"([^,]+)\" # $1: 'deprecated since' version string - \s* # optional whitespace - ( # $2: (unused) - ,\s* # comma and optional whitespace - \"(.+)\" # $3: 'in favour of' option name - )? - |x) { -- $depver = $1; -+ $depver = qq{"$1"}; - $newopt = $3 if $3; -+ -+ # we don't use the parsed value here, but we do still want to -+ # detect and report if "UNRELEASED" is seen -+ (undef) = parse_last_modified($1); - } else { - #chomp; - #print "rejected '$6'\n"; --- -2.39.2 - - -From 75533e89b6fa79695b6f2cc0aec28add82660419 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 7 Feb 2024 14:00:00 -0500 -Subject: [PATCH 08/22] imapd.c: UIDVALIDITY should be uint32_t and parse it as - such - ---- - imap/imapd.c | 10 +++------- - imap/index.h | 2 +- - 2 files changed, 4 insertions(+), 8 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index ce8c6f675..f3ccf2006 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -4256,15 +4256,11 @@ static void cmd_select(char *tag, char *cmd, char *name) - } - else if ((client_capa & CAPA_QRESYNC) && - !strcmp(arg.s, "QRESYNC")) { -- char *p; -- - if (c != ' ') goto badqresync; - c = prot_getc(imapd_in); - if (c != '(') goto badqresync; -- c = getastring(imapd_in, imapd_out, &arg); -- v->uidvalidity = strtoul(arg.s, &p, 10); -- if (*p || !v->uidvalidity || v->uidvalidity == ULONG_MAX) goto badqresync; -- if (c != ' ') goto badqresync; -+ c = getuint32(imapd_in, &v->uidvalidity); -+ if (c != ' ' || !v->uidvalidity) goto badqresync; - c = getmodseq(imapd_in, &v->modseq); - if (c == EOF) goto badqresync; - if (c == ' ') { -@@ -4404,7 +4400,7 @@ static void cmd_select(char *tag, char *cmd, char *name) - prot_printf(backend_current->out, "%s %s {" SIZE_T_FMT "+}\r\n%s", - tag, cmd, strlen(name), name); - if (v->uidvalidity) { -- prot_printf(backend_current->out, " (QRESYNC (%lu " MODSEQ_FMT, -+ prot_printf(backend_current->out, " (QRESYNC (%u " MODSEQ_FMT, - v->uidvalidity, v->modseq); - if (v->sequence) { - prot_printf(backend_current->out, " %s", v->sequence); -diff --git a/imap/index.h b/imap/index.h -index bf8006d9b..5530ed61a 100644 ---- a/imap/index.h -+++ b/imap/index.h -@@ -72,7 +72,7 @@ extern unsigned client_capa; - struct message; - - struct vanished_params { -- unsigned long uidvalidity; -+ uint32_t uidvalidity; - modseq_t modseq; - const char *match_seq; - const char *match_uid; --- -2.39.2 - - -From 9b6bc78da02d04a5fc639fd557c49922066409ab Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 08:13:05 -0500 -Subject: [PATCH 09/22] imapd.c: consolidate ID field-value parse error - response - ---- - imap/imapd.c | 17 +++++------------ - 1 file changed, 5 insertions(+), 12 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index f3ccf2006..7bbb99740 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3085,19 +3085,12 @@ static void cmd_id(char *tag) - - /* get field name */ - c = getstring(imapd_in, imapd_out, &field); -- if (c != ' ') { -+ if (c != ' ' || -+ /* get field value */ -+ (c = getnstring(imapd_in, imapd_out, &arg)) == EOF || -+ (c != ' ' && c != ')')) { - prot_printf(imapd_out, -- "%s BAD Invalid/missing field name in Id\r\n", -- tag); -- eatline(imapd_in, c); -- return; -- } -- -- /* get field value */ -- c = getnstring(imapd_in, imapd_out, &arg); -- if (c != ' ' && c != ')') { -- prot_printf(imapd_out, -- "%s BAD Invalid/missing value in Id\r\n", -+ "%s BAD Invalid field-value pair in Id\r\n", - tag); - eatline(imapd_in, c); - return; --- -2.39.2 - - -From 64521529535738a933041e5b4c41a454df65b8dc Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 7 Feb 2024 14:12:41 -0500 -Subject: [PATCH 10/22] imapd.c: response code in fatal() string MUST - immediately follow "* BYE" - ---- - imap/imapd.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index 7bbb99740..c3b9b42ea 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -1182,7 +1182,8 @@ EXPORTED void fatal(const char *s, int code) - } - recurse_code = code; - if (imapd_out) { -- prot_printf(imapd_out, "* BYE Fatal error: %s\r\n", s); -+ prot_printf(imapd_out, "* BYE %s%s\r\n", -+ *s == '[' /* resp-text-code */ ? "" : "Fatal error: ", s); - prot_flush(imapd_out); - } - if (stages.count) { --- -2.39.2 - - -From afd1e5f4ceb98b6caf0ee01b83f61468ccb1ca96 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 23 Feb 2024 11:00:19 -0500 -Subject: [PATCH 11/22] imapparse.c: include [TOOBIG] response code for - oversized word/qstring - ---- - imap/imapparse.c | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/imap/imapparse.c b/imap/imapparse.c -index b2852a357..e8e6f1b94 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -74,7 +74,7 @@ EXPORTED int getword(struct protstream *in, struct buf *buf) - } - buf_putc(buf, c); - if (config_maxword && buf_len(buf) > config_maxword) { -- fatal("word too long", EX_IOERR); -+ fatal("[TOOBIG] Word too long", EX_IOERR); - } - } - } -@@ -138,7 +138,7 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - } - buf_putc(buf, c); - if (config_maxquoted && buf_len(buf) > config_maxquoted) { -- fatal("quoted value too long", EX_IOERR); -+ fatal("[TOOBIG] Quoted value too long", EX_IOERR); - } - } - -@@ -212,6 +212,9 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - return c; - } - buf_putc(buf, c); -+ if (config_maxword && buf_len(buf) > config_maxword) { -+ fatal("[TOOBIG] Word too long", EX_IOERR); -+ } - c = prot_getc(pin); - } - /* never gets here */ --- -2.39.2 - - -From 23d153f65745bba51c70a92644cf0d5ea286539f Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 13:31:11 -0500 -Subject: [PATCH 12/22] imapparse.c: fatal() when a client violates LITERAL- - limit - ---- - imap/imap_err.et | 3 +++ - imap/imapparse.c | 7 +++++-- - 2 files changed, 8 insertions(+), 2 deletions(-) - -diff --git a/imap/imap_err.et b/imap/imap_err.et -index 8d6ca361e..eab15f0b1 100644 ---- a/imap/imap_err.et -+++ b/imap/imap_err.et -@@ -65,6 +65,9 @@ ec IMAP_QUOTA_EXCEEDED, - ec IMAP_MESSAGE_TOO_LARGE, - "Message size exceeds fixed limit" - -+ec IMAP_LITERAL_MINUS_TOO_LARGE, -+ "[TOOBIG] Non-synchronizing literal size exceeds 4K" -+ - ec IMAP_USERFLAG_EXHAUSTED, - "Too many user flags in mailbox" - -diff --git a/imap/imapparse.c b/imap/imapparse.c -index e8e6f1b94..80b29354c 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -153,8 +153,11 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - buf_reset(buf); - c = getint32(pin, &len); - if (c == '+') { -- // LITERAL- says maximum size is 4096! -- if (lminus && len > 4096) return EOF; -+ /* LITERAL- says maximum size is 4096! */ -+ if (lminus && len > 4096) { -+ /* Fail per RFC 7888, Section 4, choice 2 */ -+ fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); -+ } - isnowait++; - c = prot_getc(pin); - } --- -2.39.2 - - -From f4827451e59bc04169ab462c3805f72e9dd134c4 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Mon, 12 Feb 2024 10:54:03 -0500 -Subject: [PATCH 13/22] Cleanup and document the use of prot_setisclient() - -Only IMAP(-like) clients send LITERAL+ syntax ---- - backup/backupd.c | 3 +-- - backup/lcb.c | 1 - - backup/lcb_read.c | 2 -- - backup/lcb_verify.c | 2 -- - cunit/getxstring.testc | 4 ---- - imap/append.c | 1 - - imap/backend.c | 2 -- - imap/cyr_dbtool.c | 1 - - imap/dlist.c | 5 ++++- - imap/imapd.c | 4 ++++ - imap/imapparse.c | 5 +++-- - imap/message.c | 3 --- - imap/mupdate.c | 3 +++ - imap/sync_server.c | 3 +-- - imap/sync_support.c | 4 ---- - lib/prot.h | 2 +- - 16 files changed, 17 insertions(+), 28 deletions(-) - -diff --git a/backup/backupd.c b/backup/backupd.c -index e34c8ab3a..275711bb3 100644 ---- a/backup/backupd.c -+++ b/backup/backupd.c -@@ -229,9 +229,8 @@ EXPORTED int service_main(int argc __attribute__((unused)), - backupd_in = prot_new(0, 0); - backupd_out = prot_new(1, 1); - -- /* Force use of LITERAL+ so we don't need two way communications */ -+ /* Allow use of LITERAL+ */ - prot_setisclient(backupd_in, 1); -- prot_setisclient(backupd_out, 1); - - /* Find out name of client host */ - backupd_clienthost = get_clienthost(0, &localip, &remoteip); -diff --git a/backup/lcb.c b/backup/lcb.c -index 53bf8dc21..8f0de3b8f 100644 ---- a/backup/lcb.c -+++ b/backup/lcb.c -@@ -595,7 +595,6 @@ EXPORTED int backup_reindex(const char *name, - fprintf(out, "\nfound chunk at offset " OFF_T_FMT "\n\n", member_offset); - - struct protstream *member = prot_readcb(_prot_fill_cb, gzuc); -- prot_setisclient(member, 1); /* don't sync literals */ - - // FIXME stricter timestamp sequence checks - time_t member_start_ts = -1; -diff --git a/backup/lcb_read.c b/backup/lcb_read.c -index 201b59696..cc9410242 100644 ---- a/backup/lcb_read.c -+++ b/backup/lcb_read.c -@@ -113,7 +113,6 @@ EXPORTED int backup_read_message_data(struct backup *backup, - if (r) return r; - - struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc); -- prot_setisclient(ps, 1); /* don't sync literals */ - r = parse_backup_line(ps, NULL, NULL, &dl); - prot_free(ps); - -@@ -203,7 +202,6 @@ EXPORTED int backup_prepare_message_upload(struct backup *backup, - if (!r) { - struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc); - int c; -- prot_setisclient(ps, 1); /* don't sync literals */ - c = parse_backup_line(ps, NULL, NULL, &dl); - prot_free(ps); - ps = NULL; -diff --git a/backup/lcb_verify.c b/backup/lcb_verify.c -index 45a08bb66..a59984471 100644 ---- a/backup/lcb_verify.c -+++ b/backup/lcb_verify.c -@@ -228,7 +228,6 @@ static int _verify_message_cb(const struct backup_message *message, void *rock) - if (r) return r; - - struct protstream *ps = prot_readcb(_prot_fill_cb, vmrock->gzuc); -- prot_setisclient(ps, 1); /* don't sync literals */ - r = parse_backup_line(ps, NULL, NULL, &dl); - - if (r == EOF) { -@@ -527,7 +526,6 @@ static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk - goto done; - } - struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc); -- prot_setisclient(ps, 1); /* don't sync literals */ - - struct buf cmd = BUF_INITIALIZER; - while (1) { -diff --git a/cunit/getxstring.testc b/cunit/getxstring.testc -index 3de9b8569..5946c5676 100644 ---- a/cunit/getxstring.testc -+++ b/cunit/getxstring.testc -@@ -72,9 +72,6 @@ static int tear_down(void) - - /* - * Run a single testcase. -- * -- * Note: prot_setisclient() turns off off literal synchronising so -- * we don't have to futz around with testing that. - */ - #define _TESTCASE_PRE(fut, input, retval, consumed) \ - do { \ -@@ -83,7 +80,6 @@ static int tear_down(void) - int c; \ - p = prot_readmap(input, sizeof(input)-1); \ - CU_ASSERT_PTR_NOT_NULL_FATAL(p); \ -- prot_setisclient(p, 1); \ - c = fut(p, NULL, &b); \ - CU_ASSERT_EQUAL(c, retval); \ - if (consumed >= 0) { \ -diff --git a/imap/append.c b/imap/append.c -index 55eb140b0..81526b09b 100644 ---- a/imap/append.c -+++ b/imap/append.c -@@ -436,7 +436,6 @@ static int callout_receive_reply(const char *callout, - } - - p = prot_new(fd, /*write*/0); -- prot_setisclient(p, 1); - - /* read and parse the reply as a dlist */ - c = dlist_parse(results, /*parsekeys*/0, /*isbackup*/0, p); -diff --git a/imap/backend.c b/imap/backend.c -index 08429c915..4d4af461e 100644 ---- a/imap/backend.c -+++ b/imap/backend.c -@@ -955,7 +955,6 @@ EXPORTED struct backend *backend_connect_pipe(int infd, int outfd, - ret->prot = prot; - - /* use literal+ to send literals */ -- prot_setisclient(ret->in, 1); - prot_setisclient(ret->out, 1); - - /* Start TLS if required */ -@@ -1153,7 +1152,6 @@ EXPORTED struct backend *backend_connect(struct backend *ret_backend, const char - ret->prot = prot; - - /* use literal+ to send literals */ -- prot_setisclient(ret->in, 1); - prot_setisclient(ret->out, 1); - - /* Start TLS if required */ -diff --git a/imap/cyr_dbtool.c b/imap/cyr_dbtool.c -index 56cb4dd92..3f750149c 100644 ---- a/imap/cyr_dbtool.c -+++ b/imap/cyr_dbtool.c -@@ -155,7 +155,6 @@ static void batch_commands(struct db *db) - int r = 0; - - prot_setisclient(in, 1); -- prot_setisclient(out, 1); - - while (1) { - buf_reset(&cmd); -diff --git a/imap/dlist.c b/imap/dlist.c -index 8a3a975b4..5d2782356 100644 ---- a/imap/dlist.c -+++ b/imap/dlist.c -@@ -1167,7 +1167,10 @@ EXPORTED int dlist_parsemap(struct dlist **dlp, int parsekey, int isbackup, - struct dlist *dl = NULL; - - stream = prot_readmap(base, len); -- prot_setisclient(stream, 1); /* don't sync literals */ -+ -+ /* Allow LITERAL+ - this is silly, but required to parse personal CALDATA */ -+ prot_setisclient(stream, 1); -+ - c = dlist_parse(&dl, parsekey, isbackup, stream); - prot_free(stream); - -diff --git a/imap/imapd.c b/imap/imapd.c -index c3b9b42ea..abf0e7153 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -953,6 +953,10 @@ int service_main(int argc __attribute__((unused)), - - imapd_in = prot_new(0, 0); - imapd_out = prot_new(1, 1); -+ -+ /* Allow LITERAL+ */ -+ prot_setisclient(imapd_in, 1); -+ - protgroup_insert(protin, imapd_in); - - /* Find out name of client host */ -diff --git a/imap/imapparse.c b/imap/imapparse.c -index 80b29354c..14e6be226 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -149,10 +149,11 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - } - - /* Literal */ -- isnowait = pin->isclient; -+ isnowait = !pin->isclient; - buf_reset(buf); - c = getint32(pin, &len); -- if (c == '+') { -+ -+ if (pin->isclient && c == '+') { - /* LITERAL- says maximum size is 4096! */ - if (lminus && len > 4096) { - /* Fail per RFC 7888, Section 4, choice 2 */ -diff --git a/imap/message.c b/imap/message.c -index 30e2003d1..b9ccb1b0d 100644 ---- a/imap/message.c -+++ b/imap/message.c -@@ -3371,7 +3371,6 @@ EXPORTED void message_read_bodystructure(const struct index_record *record, stru - /* Read envelope response from cache */ - strm = prot_readmap(cacheitem_base(record, CACHE_ENVELOPE), - cacheitem_size(record, CACHE_ENVELOPE)); -- prot_setisclient(strm, 1); /* no-sync literals */ - - message_read_envelope(strm, *body); - prot_free(strm); -@@ -3379,7 +3378,6 @@ EXPORTED void message_read_bodystructure(const struct index_record *record, stru - /* Read bodystructure response from cache */ - strm = prot_readmap(cacheitem_base(record, CACHE_BODYSTRUCTURE), - cacheitem_size(record, CACHE_BODYSTRUCTURE)); -- prot_setisclient(strm, 1); /* no-sync literals */ - - message_read_body(strm, *body, NULL); - prot_free(strm); -@@ -4646,7 +4644,6 @@ static int message_parse_cbodystructure(message_t *m) - cacheitem_size(&m->record, CACHE_BODYSTRUCTURE)); - if (!prot) - return IMAP_MAILBOX_BADFORMAT; -- prot_setisclient(prot, 1); /* don't crash parsing literals */ - - m->body = xzmalloc(sizeof(struct body)); - r = parse_bodystructure_part(prot, m->body, NULL); -diff --git a/imap/mupdate.c b/imap/mupdate.c -index eef0f4b83..f6087e019 100644 ---- a/imap/mupdate.c -+++ b/imap/mupdate.c -@@ -249,6 +249,9 @@ static struct conn *conn_new(int fd) - C->pin = prot_new(C->fd, 0); - C->pout = prot_new(C->fd, 1); - -+ /* Allow LITERAL+ */ -+ prot_setisclient(C->pin, 1); -+ - prot_setflushonread(C->pin, C->pout); - prot_settimeout(C->pin, 180*60); - -diff --git a/imap/sync_server.c b/imap/sync_server.c -index 27f219636..f834cac5c 100644 ---- a/imap/sync_server.c -+++ b/imap/sync_server.c -@@ -316,9 +316,8 @@ int service_main(int argc __attribute__((unused)), - sync_in = prot_new(0, 0); - sync_out = prot_new(1, 1); - -- /* Force use of LITERAL+ so we don't need two way communications */ -+ /* Allow LITERAL+ */ - prot_setisclient(sync_in, 1); -- prot_setisclient(sync_out, 1); - - /* Find out name of client host */ - sync_clienthost = get_clienthost(0, &localip, &remoteip); -diff --git a/imap/sync_support.c b/imap/sync_support.c -index e7fe3cbdb..16595d50c 100644 ---- a/imap/sync_support.c -+++ b/imap/sync_support.c -@@ -7516,10 +7516,6 @@ connected: - if (timeout < 3) timeout = 3; - prot_settimeout(backend->in, timeout); - -- /* Force use of LITERAL+ so we don't need two way communications */ -- prot_setisclient(backend->in, 1); -- prot_setisclient(backend->out, 1); -- - sync_cs->backend = backend; - - return 0; -diff --git a/lib/prot.h b/lib/prot.h -index 98af5d160..89b0b0a2a 100644 ---- a/lib/prot.h -+++ b/lib/prot.h -@@ -133,7 +133,7 @@ struct protstream { - int can_unget; - int bytes_in; - int bytes_out; -- int isclient; -+ int isclient; /* read/write IMAP LITERAL+ */ - - /* Events */ - prot_readcallback_t *readcallback_proc; --- -2.39.2 - - -From 05a832afb53643944b49497ab658251366ce3828 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 22:48:58 -0400 -Subject: [PATCH 14/22] imapd.c: LITERAL- also applies to APPEND - -imap_err.et: add IMAP_MESSAGE_TOOBIG error message ---- - imap/imap_err.et | 4 ++++ - imap/imapd.c | 12 ++++++++++++ - 2 files changed, 16 insertions(+) - -diff --git a/imap/imap_err.et b/imap/imap_err.et -index eab15f0b1..77297a405 100644 ---- a/imap/imap_err.et -+++ b/imap/imap_err.et -@@ -65,6 +65,10 @@ ec IMAP_QUOTA_EXCEEDED, - ec IMAP_MESSAGE_TOO_LARGE, - "Message size exceeds fixed limit" - -+# Same as IMAP_MESSAGE_TOO_LARGE, but with TOOBIG response code -+ec IMAP_MESSAGE_TOOBIG, -+ "[TOOBIG] Message size exceeds fixed limit" -+ - ec IMAP_LITERAL_MINUS_TOO_LARGE, - "[TOOBIG] Non-synchronizing literal size exceeds 4K" - -diff --git a/imap/imapd.c b/imap/imapd.c -index abf0e7153..9ebd11d09 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3542,6 +3542,9 @@ static int getliteralsize(const char *p, int c, size_t maxsize, - { - int isnowait = 0; - uint32_t num; -+ static int lminus = -1; -+ -+ if (lminus == -1) lminus = config_getswitch(IMAPOPT_LITERALMINUS); - - /* Check for literal8 */ - if (*p == '~') { -@@ -3562,6 +3565,15 @@ static int getliteralsize(const char *p, int c, size_t maxsize, - } - - if (*p == '+') { -+ /* LITERAL- says maximum size is 4096! */ -+ if (lminus && num > 4096) { -+ /* Fail per RFC 7888, Section 4, choice 2 */ -+ fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); -+ } -+ if (num > maxsize) { -+ /* Fail per RFC 7888, Section 4, choice 2 */ -+ fatal(error_message(IMAP_MESSAGE_TOOBIG), EX_IOERR); -+ } - isnowait++; - p++; - } --- -2.39.2 - - -From e5e874efe32e3afc90469c493f3a114e9bc30a54 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 14:52:22 -0500 -Subject: [PATCH 15/22] imapd.c: remove XSNIPPETS and XCONV* commands - ---- - cassandane/Cassandane/Cyrus/Conversations.pm | 71 -- - imap/imapd.c | 997 ------------------- - 2 files changed, 1068 deletions(-) - -diff --git a/cassandane/Cassandane/Cyrus/Conversations.pm b/cassandane/Cassandane/Cyrus/Conversations.pm -index acafb3f74..e857f6d52 100755 ---- a/cassandane/Cassandane/Cyrus/Conversations.pm -+++ b/cassandane/Cassandane/Cyrus/Conversations.pm -@@ -706,77 +706,6 @@ sub bogus_test_replication_clash - $self->check_messages(\%exp, store => $replica_store); - } - --sub test_xconvfetch -- :min_version_3_0 --{ -- my ($self) = @_; -- my $store = $self->{store}; -- -- # check IMAP server has the XCONVERSATIONS capability -- $self->assert($store->get_client()->capability()->{xconversations}); -- -- xlog $self, "generating messages"; -- my $generator = Cassandane::ThreadedGenerator->new(); -- $store->write_begin(); -- while (my $msg = $generator->generate()) -- { -- $store->write_message($msg); -- } -- $store->write_end(); -- -- xlog $self, "reading the whole folder again to discover CIDs etc"; -- my %cids; -- my %uids; -- $store->read_begin(); -- while (my $msg = $store->read_message()) -- { -- my $uid = $msg->get_attribute('uid'); -- my $cid = $msg->get_attribute('cid'); -- my $threadid = $msg->get_header('X-Cassandane-Thread'); -- if (defined $cids{$cid}) -- { -- $self->assert_num_equals($threadid, $cids{$cid}); -- } -- else -- { -- $cids{$cid} = $threadid; -- xlog $self, "Found CID $cid"; -- } -- $self->assert_null($uids{$uid}); -- $uids{$uid} = 1; -- } -- $store->read_end(); -- -- xlog $self, "Using XCONVFETCH on each conversation"; -- foreach my $cid (keys %cids) -- { -- xlog $self, "XCONVFETCHing CID $cid"; -- -- my $result = $store->xconvfetch_begin($cid); -- $self->assert_not_null($result->{xconvmeta}); -- $self->assert_num_equals(1, scalar keys %{$result->{xconvmeta}}); -- $self->assert_not_null($result->{xconvmeta}->{$cid}); -- $self->assert_not_null($result->{xconvmeta}->{$cid}->{modseq}); -- while (my $msg = $store->xconvfetch_message()) -- { -- my $muid = $msg->get_attribute('uid'); -- my $mcid = $msg->get_attribute('cid'); -- my $threadid = $msg->get_header('X-Cassandane-Thread'); -- $self->assert_str_equals($cid, $mcid); -- $self->assert_num_equals($cids{$cid}, $threadid); -- $self->assert_num_equals(1, $uids{$muid}); -- $uids{$muid} |= 2; -- } -- $store->xconvfetch_end(); -- } -- -- xlog $self, "checking that all the UIDs in the folder were XCONVFETCHed"; -- foreach my $uid (keys %uids) -- { -- $self->assert_num_equals(3, $uids{$uid}); -- } --} -- - # - # Test APPEND of a new composed draft message to the Drafts folder by - # the Fastmail webui, which sets the X-ME-Message-ID header to thread -diff --git a/imap/imapd.c b/imap/imapd.c -index 9ebd11d09..67e864d1a 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -438,14 +438,6 @@ static void cmd_idle(char* tag); - - static void cmd_starttls(char *tag, int imaps); - --static void cmd_xconvsort(char *tag, int updates); --static void cmd_xconvmultisort(char *tag); --static void cmd_xconvmeta(const char *tag); --static void cmd_xconvfetch(const char *tag); --static int do_xconvfetch(struct dlist *cidlist, -- modseq_t ifchangedsince, -- struct fetchargs *fetchargs); --static void cmd_xsnippets(char *tag); - static void cmd_xstats(char *tag); - - static void cmd_xapplepushservice(const char *tag, -@@ -502,12 +494,8 @@ static int parse_metadata_store_data(const char *tag, - static int getlistselopts(char *tag, struct listargs *args); - static int getlistretopts(char *tag, struct listargs *args); - --static int get_snippetargs(struct snippetargs **sap); --static void free_snippetargs(struct snippetargs **sap); - static int getsortcriteria(char *tag, struct sortcrit **sortcrit); - static int getdatetime(time_t *date); --static int parse_windowargs(const char *tag, struct windowargs **, int); --static void free_windowargs(struct windowargs *wa); - - static void appendfieldlist(struct fieldlist **l, char *section, - strarray_t *fields, char *trail, -@@ -2273,32 +2261,6 @@ static void cmdloop(void) - - prometheus_increment(CYRUS_IMAP_XBACKUP_TOTAL); - } -- else if (!strcmp(cmd.s, "Xconvfetch")) { -- cmd_xconvfetch(tag.s); -- -- /* XXX prometheus_increment(CYRUS_IMAP_XCONVFETCH_TOTAL); */ -- } -- else if (!strcmp(cmd.s, "Xconvmultisort")) { -- if (c != ' ') goto missingargs; -- if (!imapd_index && !backend_current) goto nomailbox; -- cmd_xconvmultisort(tag.s); -- -- /* XXX prometheus_increment(CYRUS_IMAP_XCONVMULTISORT_TOTAL); */ -- } -- else if (!strcmp(cmd.s, "Xconvsort")) { -- if (c != ' ') goto missingargs; -- if (!imapd_index && !backend_current) goto nomailbox; -- cmd_xconvsort(tag.s, 0); -- -- /* XXX prometheus_increment(CYRUS_IMAP_XCONVSORT_TOTAL); */ -- } -- else if (!strcmp(cmd.s, "Xconvupdates")) { -- if (c != ' ') goto missingargs; -- if (!imapd_index && !backend_current) goto nomailbox; -- cmd_xconvsort(tag.s, 1); -- -- /* XXX prometheus_increment(CYRUS_IMAP_XCONVUPDATES_TOTAL); */ -- } - else if (!strcmp(cmd.s, "Xfer")) { - if (readonly) goto noreadonly; - int havepartition = 0; -@@ -2324,9 +2286,6 @@ static void cmdloop(void) - (havepartition ? arg3.s : NULL)); - /* XXX prometheus_increment(CYRUS_IMAP_XFER_TOTAL); */ - } -- else if (!strcmp(cmd.s, "Xconvmeta")) { -- cmd_xconvmeta(tag.s); -- } - else if (!strcmp(cmd.s, "Xlist")) { - struct listargs listargs; - -@@ -2359,13 +2318,6 @@ static void cmdloop(void) - cmd_xrunannotator(tag.s, arg1.s, usinguid); - /* XXX prometheus_increment(CYRUS_IMAP_XRUNANNOTATOR_TOTAL); */ - } -- else if (!strcmp(cmd.s, "Xsnippets")) { -- if (c != ' ') goto missingargs; -- if (!imapd_index && !backend_current) goto nomailbox; -- cmd_xsnippets(tag.s); -- -- /* XXX prometheus_increment(CYRUS_IMAP_XSNIPPETS_TOTAL); */ -- } - else if (!strcmp(cmd.s, "Xstats")) { - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_xstats(tag.s); -@@ -5165,8 +5117,6 @@ badannotation: - } - if (config_getswitch(IMAPOPT_CONVERSATIONS) - && (fa->fetchitems & (FETCH_MAILBOXIDS|FETCH_MAILBOXES))) { -- // annoyingly, this codepath COULD be called from xconv* commands, but it never is, -- // in reality, so it's safe leaving this as shared - int r = conversations_open_user(imapd_userid, 0/*shared*/, &fa->convstate); - if (r) { - syslog(LOG_WARNING, "error opening conversations for %s: %s", -@@ -5254,136 +5204,6 @@ static void cmd_fetch(char *tag, char *sequence, int usinguid) - fetchargs_fini(&fetchargs); - } - --static void do_one_xconvmeta(struct conversations_state *state, -- conversation_id_t cid, -- conversation_t *conv, -- struct dlist *itemlist) --{ -- struct dlist *item = dlist_newpklist(NULL, ""); -- struct dlist *fl; -- -- assert(conv); -- assert(itemlist); -- -- for (fl = itemlist->head; fl; fl = fl->next) { -- const char *key = dlist_cstring(fl); -- -- /* xxx - parse to a fetchitems? */ -- if (!strcasecmp(key, "MODSEQ")) -- dlist_setnum64(item, "MODSEQ", conv->modseq); -- else if (!strcasecmp(key, "EXISTS")) -- dlist_setnum32(item, "EXISTS", conv->exists); -- else if (!strcasecmp(key, "UNSEEN")) -- dlist_setnum32(item, "UNSEEN", conv->unseen); -- else if (!strcasecmp(key, "SIZE")) -- dlist_setnum32(item, "SIZE", conv->size); -- else if (!strcasecmp(key, "COUNT")) { -- struct dlist *flist = dlist_newlist(item, "COUNT"); -- fl = fl->next; -- if (dlist_isatomlist(fl)) { -- struct dlist *tmp; -- for (tmp = fl->head; tmp; tmp = tmp->next) { -- const char *lookup = dlist_cstring(tmp); -- int i = strarray_find_case(state->counted_flags, lookup, 0); -- if (i >= 0) { -- dlist_setflag(flist, "FLAG", lookup); -- dlist_setnum32(flist, "COUNT", conv->counts[i]); -- } -- } -- } -- } -- else if (!strcasecmp(key, "SENDERS")) { -- conv_sender_t *sender; -- struct dlist *slist = dlist_newlist(item, "SENDERS"); -- for (sender = conv->senders; sender; sender = sender->next) { -- struct dlist *sli = dlist_newlist(slist, ""); -- dlist_setatom(sli, "NAME", sender->name); -- dlist_setatom(sli, "ROUTE", sender->route); -- dlist_setatom(sli, "MAILBOX", sender->mailbox); -- dlist_setatom(sli, "DOMAIN", sender->domain); -- } -- } -- /* XXX - maybe rename FOLDERCOUNTS or something? */ -- else if (!strcasecmp(key, "FOLDEREXISTS")) { -- struct dlist *flist = dlist_newlist(item, "FOLDEREXISTS"); -- conv_folder_t *folder; -- fl = fl->next; -- if (dlist_isatomlist(fl)) { -- struct dlist *tmp; -- for (tmp = fl->head; tmp; tmp = tmp->next) { -- const char *extname = dlist_cstring(tmp); -- char *intname = mboxname_from_external(extname, &imapd_namespace, imapd_userid); -- folder = conversation_find_folder(state, conv, intname); -- free(intname); -- dlist_setatom(flist, "MBOXNAME", extname); -- /* ok if it's not there */ -- dlist_setnum32(flist, "EXISTS", folder ? folder->exists : 0); -- } -- } -- } -- else if (!strcasecmp(key, "FOLDERUNSEEN")) { -- struct dlist *flist = dlist_newlist(item, "FOLDERUNSEEN"); -- conv_folder_t *folder; -- fl = fl->next; -- if (dlist_isatomlist(fl)) { -- struct dlist *tmp; -- for (tmp = fl->head; tmp; tmp = tmp->next) { -- const char *extname = dlist_cstring(tmp); -- char *intname = mboxname_from_external(extname, &imapd_namespace, imapd_userid); -- folder = conversation_find_folder(state, conv, intname); -- free(intname); -- dlist_setatom(flist, "MBOXNAME", extname); -- /* ok if it's not there */ -- dlist_setnum32(flist, "UNSEEN", folder ? folder->unseen : 0); -- } -- } -- } -- else { -- dlist_setatom(item, key, NULL); /* add a NIL response */ -- } -- } -- -- prot_printf(imapd_out, "* XCONVMETA %s ", conversation_id_encode(cid)); -- dlist_print(item, 0, imapd_out); -- prot_printf(imapd_out, "\r\n"); -- -- dlist_free(&item); --} -- --static void do_xconvmeta(const char *tag, -- struct conversations_state *state, -- struct dlist *cidlist, -- struct dlist *itemlist) --{ -- conversation_id_t cid; -- struct dlist *dl; -- int r; -- -- for (dl = cidlist->head; dl; dl = dl->next) { -- const char *cidstr = dlist_cstring(dl); -- conversation_t *conv = NULL; -- -- if (!conversation_id_decode(&cid, cidstr) || !cid) { -- prot_printf(imapd_out, "%s BAD Invalid CID %s\r\n", tag, cidstr); -- return; -- } -- -- r = conversation_load(state, cid, &conv); -- if (r) { -- prot_printf(imapd_out, "%s BAD Failed to read %s\r\n", tag, cidstr); -- conversation_free(conv); -- return; -- } -- -- if (conv && conv->exists) -- do_one_xconvmeta(state, cid, conv, itemlist); -- -- conversation_free(conv); -- } -- -- prot_printf(imapd_out, "%s OK Completed\r\n", tag); --} -- - static int do_xbackup(const char *channel, - const ptrarray_t *list) - { -@@ -5527,261 +5347,6 @@ done: - } - } - --/* -- * Parse and perform a XCONVMETA command. -- */ --void cmd_xconvmeta(const char *tag) --{ -- int r; -- int c = ' '; -- struct conversations_state *state = NULL; -- struct dlist *cidlist = NULL; -- struct dlist *itemlist = NULL; -- -- if (backend_current) { -- /* remote mailbox */ -- prot_printf(backend_current->out, "%s XCONVMETA ", tag); -- if (!pipe_command(backend_current, 65536)) { -- pipe_including_tag(backend_current, tag, 0); -- } -- return; -- } -- -- if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { -- prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); -- eatline(imapd_in, c); -- goto done; -- } -- -- c = dlist_parse_asatomlist(&cidlist, 0, imapd_in); -- if (c != ' ') { -- prot_printf(imapd_out, "%s BAD Failed to parse CID list\r\n", tag); -- eatline(imapd_in, c); -- goto done; -- } -- -- c = dlist_parse_asatomlist(&itemlist, 0, imapd_in); -- if (!IS_EOL(c, imapd_in)) { -- prot_printf(imapd_out, "%s BAD Failed to parse item list\r\n", tag); -- eatline(imapd_in, c); -- goto done; -- } -- -- // this one is OK, xconvmeta doesn't do an expunge -- r = conversations_open_user(imapd_userid, 1/*shared*/, &state); -- if (r) { -- prot_printf(imapd_out, "%s BAD failed to open db: %s\r\n", -- tag, error_message(r)); -- goto done; -- } -- -- do_xconvmeta(tag, state, cidlist, itemlist); -- -- done: -- -- dlist_free(&itemlist); -- dlist_free(&cidlist); -- conversations_commit(&state); --} -- --/* -- * Parse and perform a XCONVFETCH command. -- */ --void cmd_xconvfetch(const char *tag) --{ -- int c = ' '; -- struct fetchargs fetchargs; -- int r; -- clock_t start = clock(); -- modseq_t ifchangedsince = 0; -- char mytime[100]; -- struct dlist *cidlist = NULL; -- struct dlist *item; -- -- if (backend_current) { -- /* remote mailbox */ -- prot_printf(backend_current->out, "%s XCONVFETCH ", tag); -- if (!pipe_command(backend_current, 65536)) { -- pipe_including_tag(backend_current, tag, 0); -- } -- return; -- } -- -- if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { -- prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); -- eatline(imapd_in, c); -- return; -- } -- -- /* local mailbox */ -- memset(&fetchargs, 0, sizeof(struct fetchargs)); -- -- c = dlist_parse_asatomlist(&cidlist, 0, imapd_in); -- if (c != ' ') -- goto syntax_error; -- -- /* check CIDs */ -- for (item = cidlist->head; item; item = item->next) { -- if (!dlist_ishex64(item)) { -- prot_printf(imapd_out, "%s BAD Invalid CID\r\n", tag); -- eatline(imapd_in, c); -- goto freeargs; -- } -- } -- -- c = getmodseq(imapd_in, &ifchangedsince); -- if (c != ' ') -- goto syntax_error; -- -- r = parse_fetch_args(tag, "Xconvfetch", 0, &fetchargs); -- if (r) -- goto freeargs; -- fetchargs.fetchitems |= (FETCH_UIDVALIDITY|FETCH_FOLDER); -- fetchargs.namespace = &imapd_namespace; -- fetchargs.userid = imapd_userid; -- -- r = do_xconvfetch(cidlist, ifchangedsince, &fetchargs); -- -- snprintf(mytime, sizeof(mytime), "%2.3f", -- (clock() - start) / (double) CLOCKS_PER_SEC); -- -- if (r) { -- prot_printf(imapd_out, "%s NO %s (%s sec)\r\n", tag, -- error_message(r), mytime); -- } else { -- prot_printf(imapd_out, "%s OK Completed (%s sec)\r\n", -- tag, mytime); -- } -- --freeargs: -- dlist_free(&cidlist); -- fetchargs_fini(&fetchargs); -- return; -- --syntax_error: -- prot_printf(imapd_out, "%s BAD Syntax error\r\n", tag); -- eatline(imapd_in, c); -- dlist_free(&cidlist); -- fetchargs_fini(&fetchargs); --} -- --static int xconvfetch_lookup(struct conversations_state *statep, -- conversation_id_t cid, -- modseq_t ifchangedsince, -- hash_table *wanted_cids, -- strarray_t *folder_list) --{ -- const char *key = conversation_id_encode(cid); -- conversation_t *conv = NULL; -- conv_folder_t *folder; -- int r; -- -- r = conversation_load(statep, cid, &conv); -- if (r) return r; -- -- if (!conv) -- goto out; -- -- if (!conv->exists) -- goto out; -- -- /* output the metadata for this conversation */ -- { -- struct dlist *dl = dlist_newlist(NULL, ""); -- dlist_setatom(dl, "", "MODSEQ"); -- do_one_xconvmeta(statep, cid, conv, dl); -- dlist_free(&dl); -- } -- -- if (ifchangedsince >= conv->modseq) -- goto out; -- -- hash_insert(key, (void *)1, wanted_cids); -- -- for (folder = conv->folders; folder; folder = folder->next) { -- /* no contents */ -- if (!folder->exists) -- continue; -- -- /* finally, something worth looking at */ -- strarray_add(folder_list, strarray_nth(statep->folder_names, folder->number)); -- } -- --out: -- conversation_free(conv); -- return 0; --} -- --static int do_xconvfetch(struct dlist *cidlist, -- modseq_t ifchangedsince, -- struct fetchargs *fetchargs) --{ -- struct conversations_state *state = NULL; -- int r = 0; -- struct index_state *index_state = NULL; -- struct dlist *dl; -- hash_table wanted_cids = HASH_TABLE_INITIALIZER; -- strarray_t folder_list = STRARRAY_INITIALIZER; -- struct index_init init; -- int i; -- -- // this one expunges each mailbox it enters, so we need to lock exclusively -- r = conversations_open_user(imapd_userid, 0/*shared*/, &state); -- if (r) goto out; -- -- construct_hash_table(&wanted_cids, 1024, 0); -- -- for (dl = cidlist->head; dl; dl = dl->next) { -- r = xconvfetch_lookup(state, dlist_num(dl), ifchangedsince, -- &wanted_cids, &folder_list); -- if (r) goto out; -- } -- -- /* unchanged, woot */ -- if (!folder_list.count) -- goto out; -- -- fetchargs->cidhash = &wanted_cids; -- -- memset(&init, 0, sizeof(struct index_init)); -- init.userid = imapd_userid; -- init.authstate = imapd_authstate; -- init.out = imapd_out; -- -- for (i = 0; i < folder_list.count; i++) { -- const char *mboxname = folder_list.data[i]; -- -- r = index_open(mboxname, &init, &index_state); -- if (r == IMAP_MAILBOX_NONEXISTENT) -- continue; -- if (r) -- goto out; -- -- index_checkflags(index_state, 0, 0); -- -- /* make sure \Deleted messages are expunged. Will also lock the -- * mailbox state and read any new information */ -- r = index_expunge(index_state, NULL, 1); -- -- if (!r) -- index_fetchresponses(index_state, NULL, /*usinguid*/1, -- fetchargs, NULL); -- -- index_close(&index_state); -- -- if (r) goto out; -- } -- -- r = 0; -- --out: -- index_close(&index_state); -- conversations_commit(&state); -- free_hash_table(&wanted_cids, NULL); -- strarray_fini(&folder_list); -- return r; --} -- - #undef PARSE_PARTIAL /* cleanup */ - - /* -@@ -6176,314 +5741,6 @@ error: - freesearchargs(searchargs); - } - --/* -- * Perform a XCONVSORT or XCONVUPDATES command -- */ --void cmd_xconvsort(char *tag, int updates) --{ -- int c; -- struct sortcrit *sortcrit = NULL; -- struct searchargs *searchargs = NULL; -- struct windowargs *windowargs = NULL; -- struct index_init init; -- struct index_state *oldstate = NULL; -- struct conversations_state *cstate = NULL; -- clock_t start = clock(); -- char mytime[100]; -- int r; -- -- if (backend_current) { -- /* remote mailbox */ -- const char *cmd = "Xconvsort"; -- -- prot_printf(backend_current->out, "%s %s ", tag, cmd); -- if (!pipe_command(backend_current, 65536)) { -- pipe_including_tag(backend_current, tag, 0); -- } -- return; -- } -- assert(imapd_index); -- -- if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { -- prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); -- eatline(imapd_in, ' '); -- return; -- } -- -- c = getsortcriteria(tag, &sortcrit); -- if (c == EOF) goto error; -- -- if (c != ' ') { -- prot_printf(imapd_out, "%s BAD Missing window args in XConvSort\r\n", -- tag); -- goto error; -- } -- -- c = parse_windowargs(tag, &windowargs, updates); -- if (c != ' ') -- goto error; -- -- /* open the conversations state first - we don't care if it fails, -- * because that probably just means it's already open */ -- // this codepath might expunge, so we can't open shared -- conversations_open_mbox(index_mboxname(imapd_index), 0/*shared*/, &cstate); -- -- if (updates) { -- /* in XCONVUPDATES, need to force a re-read from scratch into -- * a new index, because we ask for deleted messages */ -- -- oldstate = imapd_index; -- imapd_index = NULL; -- -- memset(&init, 0, sizeof(struct index_init)); -- init.userid = imapd_userid; -- init.authstate = imapd_authstate; -- init.out = imapd_out; -- init.want_expunged = 1; -- -- r = index_open(index_mboxname(oldstate), &init, &imapd_index); -- if (r) { -- prot_printf(imapd_out, "%s NO %s\r\n", tag, -- error_message(r)); -- goto error; -- } -- -- index_checkflags(imapd_index, 0, 0); -- } -- -- /* need index loaded to even parse searchargs! */ -- searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, -- &imapd_namespace, imapd_userid, imapd_authstate, -- imapd_userisadmin || imapd_userisproxyadmin); -- c = get_search_program(imapd_in, imapd_out, searchargs); -- if (c == EOF) goto error; -- -- if (!IS_EOL(c, imapd_in)) { -- prot_printf(imapd_out, -- "%s BAD Unexpected extra arguments to Xconvsort\r\n", tag); -- goto error; -- } -- -- if (updates) -- r = index_convupdates(imapd_index, sortcrit, searchargs, windowargs); -- else -- r = index_convsort(imapd_index, sortcrit, searchargs, windowargs); -- -- if (oldstate) { -- index_close(&imapd_index); -- imapd_index = oldstate; -- } -- -- if (r < 0) { -- prot_printf(imapd_out, "%s NO %s\r\n", tag, -- error_message(r)); -- goto error; -- } -- -- snprintf(mytime, sizeof(mytime), "%2.3f", -- (clock() - start) / (double) CLOCKS_PER_SEC); -- if (CONFIG_TIMING_VERBOSE) { -- char *s = sortcrit_as_string(sortcrit); -- syslog(LOG_DEBUG, "XCONVSORT (%s) processing time %s sec", -- s, mytime); -- free(s); -- } -- prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, -- error_message(IMAP_OK_COMPLETED), mytime); -- --out: -- if (cstate) conversations_commit(&cstate); -- freesortcrit(sortcrit); -- freesearchargs(searchargs); -- free_windowargs(windowargs); -- return; -- --error: -- if (cstate) conversations_commit(&cstate); -- if (oldstate) { -- if (imapd_index) index_close(&imapd_index); -- imapd_index = oldstate; -- } -- eatline(imapd_in, (c == EOF ? ' ' : c)); -- goto out; --} -- --/* -- * Perform a XCONVMULTISORT command. This is like XCONVSORT but returns -- * search results from multiple folders. It still requires a selected -- * mailbox, for two reasons: -- * -- * a) it's a useful shorthand for choosing what the current -- * conversations scope is, and -- * -- * b) the code to parse a search program currently relies on a selected -- * mailbox. -- * -- * Unlike ESEARCH it doesn't take folder names for scope, instead the -- * search scope is implicitly the current conversation scope. This is -- * implemented more or less by accident because both the Sphinx index -- * and the conversations database are hardcoded to be per-user. -- */ --static void cmd_xconvmultisort(char *tag) --{ -- int c; -- struct sortcrit *sortcrit = NULL; -- struct searchargs *searchargs = NULL; -- struct windowargs *windowargs = NULL; -- struct conversations_state *cstate = NULL; -- clock_t start = clock(); -- char mytime[100]; -- int r; -- -- if (backend_current) { -- /* remote mailbox */ -- const char *cmd = "Xconvmultisort"; -- -- prot_printf(backend_current->out, "%s %s ", tag, cmd); -- if (!pipe_command(backend_current, 65536)) { -- pipe_including_tag(backend_current, tag, 0); -- } -- return; -- } -- assert(imapd_index); -- -- if (!config_getswitch(IMAPOPT_CONVERSATIONS)) { -- prot_printf(imapd_out, "%s BAD Unrecognized command\r\n", tag); -- eatline(imapd_in, ' '); -- return; -- } -- -- c = getsortcriteria(tag, &sortcrit); -- if (c == EOF) goto error; -- -- if (c != ' ') { -- prot_printf(imapd_out, "%s BAD Missing window args in XConvMultiSort\r\n", -- tag); -- goto error; -- } -- -- c = parse_windowargs(tag, &windowargs, /*updates*/0); -- if (c != ' ') -- goto error; -- -- /* open the conversations state first - we don't care if it fails, -- * because that probably just means it's already open */ -- // this codepath might expunge, so we can't open shared -- conversations_open_mbox(index_mboxname(imapd_index), 0/*shared*/, &cstate); -- -- /* need index loaded to even parse searchargs! */ -- searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, -- &imapd_namespace, imapd_userid, imapd_authstate, -- imapd_userisadmin || imapd_userisproxyadmin); -- c = get_search_program(imapd_in, imapd_out, searchargs); -- if (c == EOF) goto error; -- -- if (!IS_EOL(c, imapd_in)) { -- prot_printf(imapd_out, -- "%s BAD Unexpected extra arguments to XconvMultiSort\r\n", tag); -- goto error; -- } -- -- r = index_convmultisort(imapd_index, sortcrit, searchargs, windowargs); -- -- if (r < 0) { -- prot_printf(imapd_out, "%s NO %s\r\n", tag, -- error_message(r)); -- goto error; -- } -- -- snprintf(mytime, sizeof(mytime), "%2.3f", -- (clock() - start) / (double) CLOCKS_PER_SEC); -- if (CONFIG_TIMING_VERBOSE) { -- char *s = sortcrit_as_string(sortcrit); -- syslog(LOG_DEBUG, "XCONVMULTISORT (%s) processing time %s sec", -- s, mytime); -- free(s); -- } -- prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, -- error_message(IMAP_OK_COMPLETED), mytime); -- --out: -- if (cstate) conversations_commit(&cstate); -- freesortcrit(sortcrit); -- freesearchargs(searchargs); -- free_windowargs(windowargs); -- return; -- --error: -- if (cstate) conversations_commit(&cstate); -- eatline(imapd_in, (c == EOF ? ' ' : c)); -- goto out; --} -- --static void cmd_xsnippets(char *tag) --{ -- int c; -- struct searchargs *searchargs = NULL; -- struct snippetargs *snippetargs = NULL; -- clock_t start = clock(); -- char mytime[100]; -- int r; -- -- if (backend_current) { -- /* remote mailbox */ -- const char *cmd = "Xsnippets"; -- -- prot_printf(backend_current->out, "%s %s ", tag, cmd); -- if (!pipe_command(backend_current, 65536)) { -- pipe_including_tag(backend_current, tag, 0); -- } -- return; -- } -- assert(imapd_index); -- -- c = get_snippetargs(&snippetargs); -- if (c == EOF) { -- prot_printf(imapd_out, "%s BAD Syntax error in snippet arguments\r\n", tag); -- goto error; -- } -- if (c != ' ') { -- prot_printf(imapd_out, -- "%s BAD Unexpected arguments in Xsnippets\r\n", tag); -- goto error; -- } -- -- /* need index loaded to even parse searchargs! */ -- searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, -- &imapd_namespace, imapd_userid, imapd_authstate, -- imapd_userisadmin || imapd_userisproxyadmin); -- c = get_search_program(imapd_in, imapd_out, searchargs); -- if (c == EOF) goto error; -- -- if (!IS_EOL(c, imapd_in)) { -- prot_printf(imapd_out, -- "%s BAD Unexpected extra arguments to Xsnippets\r\n", tag); -- goto error; -- } -- -- r = index_snippets(imapd_index, snippetargs, searchargs); -- -- if (r < 0) { -- prot_printf(imapd_out, "%s NO %s\r\n", tag, -- error_message(r)); -- goto error; -- } -- -- snprintf(mytime, sizeof(mytime), "%2.3f", -- (clock() - start) / (double) CLOCKS_PER_SEC); -- prot_printf(imapd_out, "%s OK %s (in %s secs)\r\n", tag, -- error_message(IMAP_OK_COMPLETED), mytime); -- --out: -- freesearchargs(searchargs); -- free_snippetargs(&snippetargs); -- return; -- --error: -- eatline(imapd_in, (c == EOF ? ' ' : c)); -- goto out; --} -- - static void cmd_xstats(char *tag) - { - int metric; -@@ -10800,81 +10057,6 @@ out_noprint: - if (uids) seqset_free(uids); - } - --static void free_snippetargs(struct snippetargs **sap) --{ -- while (*sap) { -- struct snippetargs *sa = *sap; -- *sap = sa->next; -- free(sa->mboxname); -- free(sa->uids.data); -- free(sa); -- } --} -- --static int get_snippetargs(struct snippetargs **sap) --{ -- int c; -- struct snippetargs **prevp = sap; -- struct snippetargs *sa = NULL; -- struct buf arg = BUF_INITIALIZER; -- uint32_t uid; -- char *intname = NULL; -- -- c = prot_getc(imapd_in); -- if (c != '(') goto syntax_error; -- -- for (;;) { -- c = prot_getc(imapd_in); -- if (c == ')') break; -- if (c != '(') goto syntax_error; -- -- c = getastring(imapd_in, imapd_out, &arg); -- if (c != ' ') goto syntax_error; -- -- intname = mboxname_from_external(buf_cstring(&arg), &imapd_namespace, imapd_userid); -- -- /* allocate a new snippetargs */ -- sa = xzmalloc(sizeof(struct snippetargs)); -- sa->mboxname = xstrdup(intname); -- /* append to the list */ -- *prevp = sa; -- prevp = &sa->next; -- -- c = getuint32(imapd_in, &sa->uidvalidity); -- if (c != ' ') goto syntax_error; -- -- c = prot_getc(imapd_in); -- if (c != '(') break; -- for (;;) { -- c = getuint32(imapd_in, &uid); -- if (c != ' ' && c != ')') goto syntax_error; -- if (sa->uids.count + 1 > sa->uids.alloc) { -- sa->uids.alloc += 64; -- sa->uids.data = xrealloc(sa->uids.data, -- sizeof(uint32_t) * sa->uids.alloc); -- } -- sa->uids.data[sa->uids.count++] = uid; -- if (c == ')') break; -- } -- -- c = prot_getc(imapd_in); -- if (c != ')') goto syntax_error; -- } -- -- c = prot_getc(imapd_in); -- if (c != ' ') goto syntax_error; -- --out: -- free(intname); -- buf_free(&arg); -- return c; -- --syntax_error: -- free_snippetargs(sap); -- c = EOF; -- goto out; --} -- - static void cmd_dump(char *tag, char *name, int uid_start) - { - int r = 0; -@@ -12329,185 +11511,6 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - return EOF; - } - --static int parse_windowargs(const char *tag, -- struct windowargs **wa, -- int updates) --{ -- struct windowargs windowargs; -- struct buf arg = BUF_INITIALIZER; -- struct buf ext_folder = BUF_INITIALIZER; -- int c; -- -- memset(&windowargs, 0, sizeof(windowargs)); -- -- c = prot_getc(imapd_in); -- if (c == EOF) -- goto out; -- if (c != '(') { -- /* no window args at all */ -- prot_ungetc(c, imapd_in); -- goto out; -- } -- -- for (;;) -- { -- c = prot_getc(imapd_in); -- if (c == EOF) -- goto out; -- if (c == ')') -- break; /* end of window args */ -- -- prot_ungetc(c, imapd_in); -- c = getword(imapd_in, &arg); -- if (!arg.len) -- goto syntax_error; -- -- if (!strcasecmp(arg.s, "CONVERSATIONS")) -- windowargs.conversations = 1; -- else if (!strcasecmp(arg.s, "POSITION")) { -- if (updates) -- goto syntax_error; -- if (c != ' ') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (c != '(') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.position); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.limit); -- if (c != ')') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (windowargs.position == 0) -- goto syntax_error; -- } -- else if (!strcasecmp(arg.s, "ANCHOR")) { -- if (updates) -- goto syntax_error; -- if (c != ' ') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (c != '(') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.anchor); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.offset); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.limit); -- if (c != ')') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (windowargs.anchor == 0) -- goto syntax_error; -- } -- else if (!strcasecmp(arg.s, "MULTIANCHOR")) { -- if (updates) -- goto syntax_error; -- if (c != ' ') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (c != '(') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.anchor); -- if (c != ' ') -- goto syntax_error; -- c = getastring(imapd_in, imapd_out, &ext_folder); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.offset); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.limit); -- if (c != ')') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (windowargs.anchor == 0) -- goto syntax_error; -- } -- else if (!strcasecmp(arg.s, "CHANGEDSINCE")) { -- if (!updates) -- goto syntax_error; -- windowargs.changedsince = 1; -- if (c != ' ') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (c != '(') -- goto syntax_error; -- c = getmodseq(imapd_in, &windowargs.modseq); -- if (c != ' ') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.uidnext); -- if (c != ')') -- goto syntax_error; -- c = prot_getc(imapd_in); -- } else if (!strcasecmp(arg.s, "UPTO")) { -- if (!updates) -- goto syntax_error; -- if (c != ' ') -- goto syntax_error; -- c = prot_getc(imapd_in); -- if (c != '(') -- goto syntax_error; -- c = getuint32(imapd_in, &windowargs.upto); -- if (c != ')') -- goto syntax_error; -- c = prot_getc(imapd_in); -- -- if (windowargs.upto == 0) -- goto syntax_error; -- } -- else -- goto syntax_error; -- -- if (c == ')') -- break; -- if (c != ' ') -- goto syntax_error; -- } -- -- c = prot_getc(imapd_in); -- if (c != ' ') -- goto syntax_error; -- --out: -- /* these two are mutually exclusive */ -- if (windowargs.anchor && windowargs.position) -- goto syntax_error; -- /* changedsince is mandatory for XCONVUPDATES -- * and illegal for XCONVSORT */ -- if (!!updates != windowargs.changedsince) -- goto syntax_error; -- -- if (ext_folder.len) { -- windowargs.anchorfolder = mboxname_from_external(buf_cstring(&ext_folder), -- &imapd_namespace, -- imapd_userid); -- } -- -- *wa = xmemdup(&windowargs, sizeof(windowargs)); -- buf_free(&ext_folder); -- buf_free(&arg); -- return c; -- --syntax_error: -- free(windowargs.anchorfolder); -- buf_free(&ext_folder); -- prot_printf(imapd_out, "%s BAD Syntax error in window arguments\r\n", tag); -- if (c != EOF) prot_ungetc(c, imapd_in); -- return EOF; --} -- --static void free_windowargs(struct windowargs *wa) --{ -- if (!wa) -- return; -- free(wa->anchorfolder); -- free(wa); --} -- - /* - * Parse LIST selection options. - * The command has been parsed up to and including the opening '('. --- -2.39.2 - - -From 9901ee2eae0d2c99ecb4e7057d7e3802fb5b64e4 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Mon, 26 Feb 2024 10:11:15 -0500 -Subject: [PATCH 16/22] imapd.c: add 'maxliteral' option - ---- - changes/next/imap_literal_limits | 19 +++ - imap/imap_err.et | 3 + - imap/imapd.c | 214 ++++++++++++++++++++++--------- - imap/imapparse.c | 69 +++++----- - lib/imapoptions | 12 +- - lib/libconfig.c | 3 + - lib/libconfig.h | 1 + - 7 files changed, 229 insertions(+), 92 deletions(-) - create mode 100644 changes/next/imap_literal_limits - -diff --git a/changes/next/imap_literal_limits b/changes/next/imap_literal_limits -new file mode 100644 -index 000000000..c7fc35bbc ---- /dev/null -+++ b/changes/next/imap_literal_limits -@@ -0,0 +1,19 @@ -+Description: -+ -+Adds a config option to limit the size of a single literal allowed -+by the IMAP parser. Also properly applies LITERAL- to IMAP APPEND. -+ -+ -+Config changes: -+ -+New 'maxliteral' option. -+ -+ -+Upgrade instructions: -+ -+None. -+ -+ -+GitHub issue: -+ -+None. -diff --git a/imap/imap_err.et b/imap/imap_err.et -index 77297a405..e309c1203 100644 ---- a/imap/imap_err.et -+++ b/imap/imap_err.et -@@ -69,6 +69,9 @@ ec IMAP_MESSAGE_TOO_LARGE, - ec IMAP_MESSAGE_TOOBIG, - "[TOOBIG] Message size exceeds fixed limit" - -+ec IMAP_LITERAL_TOO_LARGE, -+ "[TOOBIG] Literal size exceeds fixed limit" -+ - ec IMAP_LITERAL_MINUS_TOO_LARGE, - "[TOOBIG] Non-synchronizing literal size exceeds 4K" - -diff --git a/imap/imapd.c b/imap/imapd.c -index 67e864d1a..28d0f299d 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -1428,7 +1428,7 @@ static void cmdloop(void) - if (c == '\r') goto missingargs; - if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence; - c = getastring(imapd_in, imapd_out, &arg2); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - - cmd_copy(tag.s, arg1.s, arg2.s, usinguid, /*ismove*/0); -@@ -1441,7 +1441,7 @@ static void cmdloop(void) - - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (c == ' ') { - c = parsecreateargs(&extargs); - if (c == EOF) goto badpartition; -@@ -1468,7 +1468,7 @@ static void cmdloop(void) - if (readonly) goto noreadonly; - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_delete(tag.s, arg1.s, 0, 0); - -@@ -1480,7 +1480,7 @@ static void cmdloop(void) - c = getastring(imapd_in, imapd_out, &arg1); - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg2); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_setacl(tag.s, arg1.s, arg2.s, NULL); - -@@ -1525,7 +1525,7 @@ static void cmdloop(void) - else if (!strcmp(cmd.s, "Examine")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - prot_ungetc(c, imapd_in); - - cmd_select(tag.s, cmd.s, arg1.s); -@@ -1556,7 +1556,7 @@ static void cmdloop(void) - if (!strcmp(cmd.s, "Getacl")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_getacl(tag.s, arg1.s); - -@@ -1581,7 +1581,7 @@ static void cmdloop(void) - else if (!strcmp(cmd.s, "Getquota")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_getquota(tag.s, arg1.s); - -@@ -1590,7 +1590,7 @@ static void cmdloop(void) - else if (!strcmp(cmd.s, "Getquotaroot")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_getquotaroot(tag.s, arg1.s); - -@@ -1715,7 +1715,7 @@ static void cmdloop(void) - - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (c == ' ') { - c = parsecreateargs(&extargs); - if (c == EOF) goto badpartition; -@@ -1731,7 +1731,7 @@ static void cmdloop(void) - /* delete a mailbox locally only */ - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_delete(tag.s, arg1.s, 1, 1); - -@@ -1744,7 +1744,7 @@ static void cmdloop(void) - if (!strcmp(cmd.s, "Myrights")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_myrights(tag.s, arg1.s); - -@@ -1754,7 +1754,7 @@ static void cmdloop(void) - if (readonly) goto noreadonly; - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if(c == EOF) goto missingargs; -+ if(c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_mupdatepush(tag.s, arg1.s); - -@@ -1770,7 +1770,7 @@ static void cmdloop(void) - if (c == '\r') goto missingargs; - if (c != ' ' || !imparse_issequence(arg1.s)) goto badsequence; - c = getastring(imapd_in, imapd_out, &arg2); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - - cmd_copy(tag.s, arg1.s, arg2.s, usinguid, /*ismove*/1); -@@ -1805,7 +1805,7 @@ static void cmdloop(void) - c = getastring(imapd_in, imapd_out, &arg1); - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg2); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (c == ' ') { - havepartition = 1; - c = getword(imapd_in, &arg3); -@@ -1878,7 +1878,7 @@ static void cmdloop(void) - if (c == ' ') { - have_mbox = 1; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (c == ' ') { - have_mech = 1; - c = getword(imapd_in, &arg2); -@@ -1950,7 +1950,7 @@ static void cmdloop(void) - else if (!strcmp(cmd.s, "Select")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - prot_ungetc(c, imapd_in); - - cmd_select(tag.s, cmd.s, arg1.s); -@@ -1976,7 +1976,7 @@ static void cmdloop(void) - havenamespace = 1; - c = getastring(imapd_in, imapd_out, &arg2); - } -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - if (havenamespace) { - cmd_changesub(tag.s, arg1.s, arg2.s, 1); -@@ -1994,7 +1994,7 @@ static void cmdloop(void) - c = getastring(imapd_in, imapd_out, &arg2); - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg3); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_setacl(tag.s, arg1.s, arg2.s, arg3.s); - -@@ -2195,7 +2195,7 @@ static void cmdloop(void) - havenamespace = 1; - c = getastring(imapd_in, imapd_out, &arg2); - } -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - if (havenamespace) { - cmd_changesub(tag.s, arg1.s, arg2.s, 0); -@@ -2342,7 +2342,7 @@ static void cmdloop(void) - else if (!strcmp(cmd.s, "Xmeid")) { - if (c != ' ') goto missingargs; - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto missingargs; -+ if (c <= EOF) goto missingargs; - if (!IS_EOL(c, imapd_in)) goto extraargs; - cmd_xmeid(tag.s, arg1.s); - } -@@ -2354,7 +2354,7 @@ static void cmdloop(void) - - do { - c = getastring(imapd_in, imapd_out, &arg1); -- if (c == EOF) goto aps_missingargs; -+ if (c <= EOF) goto aps_missingargs; - - if (!strcmp(arg1.s, "mailboxes")) { - c = prot_getc(imapd_in); -@@ -2366,7 +2366,7 @@ static void cmdloop(void) - prot_ungetc(c, imapd_in); - do { - c = getastring(imapd_in, imapd_out, &arg2); -- if (c == EOF) break; -+ if (c <= EOF) break; - strarray_push(&applepushserviceargs.mailboxes, arg2.s); - } while (c == ' '); - } -@@ -2447,6 +2447,8 @@ static void cmdloop(void) - strarray_fini(&applepushserviceargs.mailboxes); - - missingargs: -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; -+ - prot_printf(imapd_out, - "%s BAD Missing required argument to %s\r\n", tag.s, cmd.s); - eatline(imapd_in, c); -@@ -2459,11 +2461,18 @@ static void cmdloop(void) - strarray_fini(&applepushserviceargs.mailboxes); - - extraargs: -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; -+ - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to %s\r\n", tag.s, cmd.s); - eatline(imapd_in, c); - continue; - -+ maxliteral: -+ prot_printf(imapd_out, "%s NO %s in %s\r\n", -+ tag.s, error_message(IMAP_LITERAL_TOO_LARGE), cmd.s); -+ continue; -+ - badsequence: - prot_printf(imapd_out, - "%s BAD Invalid sequence in %s\r\n", tag.s, cmd.s); -@@ -2630,10 +2639,14 @@ static void cmd_login(char *tag, char *user) - - if (!IS_EOL(c, imapd_in)) { - buf_free(&passwdbuf); -- prot_printf(imapd_out, -- "%s BAD Unexpected extra arguments to LOGIN\r\n", -- tag); -- eatline(imapd_in, c); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in LOGIN\r\n", tag, error_message(c)); -+ } else { -+ prot_printf(imapd_out, -+ "%s BAD Unexpected extra arguments to LOGIN\r\n", -+ tag); -+ eatline(imapd_in, c); -+ } - return; - } - -@@ -3046,10 +3059,16 @@ static void cmd_id(char *tag) - /* get field value */ - (c = getnstring(imapd_in, imapd_out, &arg)) == EOF || - (c != ' ' && c != ')')) { -- prot_printf(imapd_out, -- "%s BAD Invalid field-value pair in Id\r\n", -- tag); -- eatline(imapd_in, c); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Id\r\n", -+ tag, error_message(c)); -+ } -+ else { -+ prot_printf(imapd_out, -+ "%s BAD Invalid field-value pair in Id\r\n", -+ tag); -+ eatline(imapd_in, c); -+ } - return; - } - -@@ -3737,6 +3756,7 @@ static int append_catenate(FILE *f, const char *cur_name, size_t maxsize, unsign - } - else if (!strcasecmp(arg.s, "URL")) { - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c != ' ' && c != ')') { - *parseerr = "Missing URL in Append command"; - return IMAP_PROTOCOL_ERROR; -@@ -3935,8 +3955,7 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - c = parse_annotate_store_data(tag, - /*permessage_flag*/1, - &curstage->annotations); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto cleanup; - } - qdiffs[QUOTA_ANNOTSTORAGE] += sizeentryatts(curstage->annotations); -@@ -4061,6 +4080,8 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - - if (r == IMAP_PROTOCOL_ERROR && parseerr) { - prot_printf(imapd_out, "%s BAD %s\r\n", tag, parseerr); -+ } else if (r == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s\r\n", tag, error_message(r)); - } else if (r == IMAP_BADURL) { - prot_printf(imapd_out, "%s NO [BADURL \"%s\"] %s\r\n", - tag, url, parseerr); -@@ -4603,8 +4624,7 @@ static int parse_fetch_args(const char *tag, const char *cmd, - /*permessage_flag*/1, - &fa->entries, - &fa->attribs); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeargs; - } - if (c != ')') { -@@ -4717,6 +4737,11 @@ badannotation: - } - do { - c = getastring(imapd_in, imapd_out, &fieldname); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in %s %s\r\n", -+ tag, error_message(c), cmd, fetchatt.s); -+ goto freeargs; -+ } - for (p = fieldname.s; *p; p++) { - if (*p <= ' ' || *p & 0x80 || *p == ':') break; - } -@@ -4947,6 +4972,11 @@ badannotation: - } - do { - c = getastring(imapd_in, imapd_out, &fieldname); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in %s %s\r\n", -+ tag, error_message(c), cmd, fetchatt.s); -+ goto freeargs; -+ } - for (p = fieldname.s; *p; p++) { - if (*p <= ' ' || *p & 0x80 || *p == ':') break; - } -@@ -5462,8 +5492,7 @@ static void cmd_store(char *tag, char *sequence, int usinguid) - - c = parse_annotate_store_data(tag, /*permessage_flag*/1, - &storeargs.entryatts); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeflags; - } - storeargs.namespace = &imapd_namespace; -@@ -5637,6 +5666,12 @@ static void cmd_search(char *tag, int usinguid) - return; - } - -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Search\r\n", tag, error_message(c)); -+ freesearchargs(searchargs); -+ return; -+ } -+ - if (!IS_EOL(c, imapd_in)) { - prot_printf(imapd_out, "%s BAD Unexpected extra arguments to Search\r\n", tag); - eatline(imapd_in, c); -@@ -5812,16 +5847,19 @@ static void cmd_thread(char *tag, int usinguid) - c = get_search_program(imapd_in, imapd_out, searchargs); - if (c == EOF) { - eatline(imapd_in, ' '); -- freesearchargs(searchargs); -- return; -+ goto done; -+ } -+ -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Thread\r\n", tag, error_message(c)); -+ goto done; - } - - if (!IS_EOL(c, imapd_in)) { - prot_printf(imapd_out, - "%s BAD Unexpected extra arguments to Thread\r\n", tag); - eatline(imapd_in, c); -- freesearchargs(searchargs); -- return; -+ goto done; - } - - n = index_thread(imapd_index, alg, searchargs, usinguid); -@@ -5830,6 +5868,7 @@ static void cmd_thread(char *tag, int usinguid) - prot_printf(imapd_out, "%s OK %s (%d msgs in %s secs)\r\n", tag, - error_message(IMAP_OK_COMPLETED), n, mytime); - -+ done: - freesearchargs(searchargs); - return; - } -@@ -7333,8 +7372,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - if (c == '(') { - listargs->cmd = LIST_CMD_EXTENDED; - c = getlistselopts(tag, listargs); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - return; - } - } -@@ -7346,6 +7384,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - - /* Read in reference name */ - c = getastring(imapd_in, imapd_out, &reference); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF && !*reference.s) { - prot_printf(imapd_out, - "%s BAD Missing required argument to List: reference name\r\n", -@@ -7368,6 +7407,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - listargs->cmd = LIST_CMD_EXTENDED; - for (;;) { - c = getastring(imapd_in, imapd_out, &buf); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (*buf.s) - strarray_append(&listargs->pat, buf.s); - if (c != ' ') break; -@@ -7383,6 +7423,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - else { - prot_ungetc(c, imapd_in); - c = getastring(imapd_in, imapd_out, &buf); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing required argument to List: mailbox pattern\r\n", -@@ -7397,8 +7438,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - if (c == ' ') { - listargs->cmd = LIST_CMD_EXTENDED; - c = getlistretopts(tag, listargs); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeargs; - } - } -@@ -7417,6 +7457,10 @@ static void getlistargs(char *tag, struct listargs *listargs) - - return; - -+ maxliteral: -+ prot_printf(imapd_out, "%s NO %s in List\r\n", -+ tag, error_message(IMAP_LITERAL_TOO_LARGE)); -+ - freeargs: - strarray_fini(&listargs->pat); - strarray_fini(&listargs->metaitems); -@@ -8816,6 +8860,7 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation entry\r\n", tag); -@@ -8843,6 +8888,7 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation entry\r\n", tag); -@@ -8865,6 +8911,7 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation attribute(s)\r\n", tag); -@@ -8892,6 +8939,7 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation attribute\r\n", tag); -@@ -8904,8 +8952,13 @@ static int parse_annotate_fetch_data(const char *tag, - return c; - - baddata: -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; -+ -+ maxliteral: -+ prot_printf(imapd_out, "%s NO %s in annotation entry\r\n", -+ tag, error_message(IMAP_LITERAL_TOO_LARGE)); -+ return IMAP_LITERAL_TOO_LARGE; - } - - /* -@@ -8936,6 +8989,7 @@ static int parse_metadata_string_or_list(const char *tag, - /* entry list */ - do { - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing metadata entry\r\n", tag); -@@ -8962,6 +9016,7 @@ static int parse_metadata_string_or_list(const char *tag, - /* single entry -- add it to the list */ - prot_ungetc(c, imapd_in); - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing metadata entry\r\n", tag); -@@ -8980,8 +9035,13 @@ static int parse_metadata_string_or_list(const char *tag, - if (c == ' ' || c == '\r' || c == ')') return c; - - baddata: -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; -+ -+ maxliteral: -+ prot_printf(imapd_out, "%s NO %s in metadata entry\r\n", -+ tag, error_message(IMAP_LITERAL_TOO_LARGE)); -+ return IMAP_LITERAL_TOO_LARGE; - } - - /* -@@ -9033,6 +9093,7 @@ static int parse_annotate_store_data(const char *tag, - c = getastring(imapd_in, imapd_out, &entry); - else - c = getqstring(imapd_in, imapd_out, &entry); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation entry\r\n", tag); -@@ -9053,6 +9114,7 @@ static int parse_annotate_store_data(const char *tag, - c = getastring(imapd_in, imapd_out, &attrib); - else - c = getqstring(imapd_in, imapd_out, &attrib); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation attribute\r\n", tag); -@@ -9066,6 +9128,7 @@ static int parse_annotate_store_data(const char *tag, - goto baddata; - } - c = getbnstring(imapd_in, imapd_out, &value); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing annotation value\r\n", tag); -@@ -9107,8 +9170,14 @@ static int parse_annotate_store_data(const char *tag, - - baddata: - if (attvalues) freeattvalues(attvalues); -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; -+ -+ maxliteral: -+ if (attvalues) freeattvalues(attvalues); -+ prot_printf(imapd_out, "%s NO %s in annotation entry\r\n", -+ tag, error_message(IMAP_LITERAL_TOO_LARGE)); -+ return IMAP_LITERAL_TOO_LARGE; - } - - /* -@@ -9141,6 +9210,7 @@ static int parse_metadata_store_data(const char *tag, - do { - /* get entry */ - c = getastring(imapd_in, imapd_out, &entry); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c != ' ') { - prot_printf(imapd_out, - "%s BAD Missing metadata entry\r\n", tag); -@@ -9153,6 +9223,7 @@ static int parse_metadata_store_data(const char *tag, - - /* get value */ - c = getbnstring(imapd_in, imapd_out, &value); -+ if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, - "%s BAD Missing metadata value\r\n", tag); -@@ -9193,7 +9264,7 @@ static int parse_metadata_store_data(const char *tag, - - if (c != ')') { - prot_printf(imapd_out, -- "%s BAD Missing close paren in annotation entry list \r\n", -+ "%s BAD Missing close paren in metadata entry list \r\n", - tag); - goto baddata; - } -@@ -9204,8 +9275,14 @@ static int parse_metadata_store_data(const char *tag, - - baddata: - if (attvalues) freeattvalues(attvalues); -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; -+ -+ maxliteral: -+ if (attvalues) freeattvalues(attvalues); -+ prot_printf(imapd_out, "%s NO %s in metadata entry\r\n", -+ tag, error_message(IMAP_LITERAL_TOO_LARGE)); -+ return IMAP_LITERAL_TOO_LARGE; - } - - static void getannotation_response(const char *mboxname, -@@ -9384,8 +9461,7 @@ static void cmd_getannotation(const char *tag, char *mboxpat) - annotate_state_t *astate = NULL; - - c = parse_annotate_fetch_data(tag, /*permessage_flag*/0, &entries, &attribs); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeargs; - } - -@@ -9619,8 +9695,10 @@ static void cmd_getmetadata(const char *tag) - while (nlists < 3) - { - c = parse_metadata_string_or_list(tag, &lists[nlists], &is_list[nlists]); -+ if (c <= EOF) goto freeargs; -+ - nlists++; -- if (c == '\r' || c == EOF) -+ if (c == '\r') - break; - } - -@@ -9777,8 +9855,7 @@ static void cmd_setannotation(const char *tag, char *mboxpat) - annotate_state_t *astate = NULL; - - c = parse_annotate_store_data(tag, 0, &entryatts); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeargs; - } - -@@ -9845,8 +9922,7 @@ static void cmd_setmetadata(const char *tag, char *mboxpat) - annotate_state_t *astate = NULL; - - c = parse_metadata_store_data(tag, &entryatts); -- if (c == EOF) { -- eatline(imapd_in, c); -+ if (c <= EOF) { - goto freeargs; - } - -@@ -9954,6 +10030,10 @@ static void cmd_xwarmup(const char *tag) - /* parse arguments: expect '('')' */ - - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Xwarmup\r\n", tag, error_message(c)); -+ goto out_noprint; -+ } - if (c != ' ') { - syntax_error: - prot_printf(imapd_out, "%s BAD syntax error in %s\r\n", tag, cmd); -@@ -11423,9 +11503,11 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - (*sortcrit)[n].key = SORT_ANNOTATION; - if (c != ' ') goto missingarg; - c = getastring(imapd_in, imapd_out, &criteria); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c != ' ') goto missingarg; - (*sortcrit)[n].args.annot.entry = xstrdup(criteria.s); - c = getastring(imapd_in, imapd_out, &criteria); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c == EOF) goto missingarg; - if (!strcmp(criteria.s, "value.shared")) - userid = ""; -@@ -11443,6 +11525,7 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - (*sortcrit)[n].key = SORT_HASFLAG; - if (c != ' ') goto missingarg; - c = getastring(imapd_in, imapd_out, &criteria); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c == EOF) goto missingarg; - (*sortcrit)[n].args.flag.name = xstrdup(criteria.s); - } -@@ -11456,6 +11539,7 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - (*sortcrit)[n].key = SORT_HASCONVFLAG; - if (c != ' ') goto missingarg; - c = getastring(imapd_in, imapd_out, &criteria); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c == EOF) goto missingarg; - (*sortcrit)[n].args.flag.name = xstrdup(criteria.s); - } -@@ -11561,10 +11645,10 @@ static int getlistselopts(char *tag, struct listargs *args) - - strarray_t options = STRARRAY_INITIALIZER; - c = parse_metadata_string_or_list(tag, &options, NULL); -+ if (c <= EOF) return c; - parse_getmetadata_options(&options, &opts); - args->metaopts = opts; - strarray_fini(&options); -- if (c == EOF) return EOF; - } else { - prot_printf(imapd_out, - "%s BAD Invalid List selection option \"%s\"\r\n", -@@ -11592,7 +11676,7 @@ static int getlistselopts(char *tag, struct listargs *args) - return prot_getc(imapd_in); - - bad: -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; - } - -@@ -11662,7 +11746,7 @@ static int getlistretopts(char *tag, struct listargs *args) - args->ret |= LIST_RET_METADATA; - /* outputs the error for us */ - c = parse_metadata_string_or_list(tag, &args->metaitems, NULL); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - } - else { - prot_printf(imapd_out, -@@ -11683,7 +11767,7 @@ static int getlistretopts(char *tag, struct listargs *args) - return prot_getc(imapd_in); - - bad: -- if (c != EOF) prot_ungetc(c, imapd_in); -+ eatline(imapd_in, c); - return EOF; - } - -@@ -12847,6 +12931,11 @@ static void cmd_urlfetch(char *tag) - else prot_ungetc(c, imapd_in); - - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Urlfetch\r\n", -+ tag, error_message(c)); -+ return; -+ } - (void)prot_putc(' ', imapd_out); - prot_printstring(imapd_out, arg.s); - -@@ -13079,6 +13168,11 @@ static void cmd_genurlauth(char *tag) - char *intname = NULL; - - c = getastring(imapd_in, imapd_out, &arg1); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Genurlauth\r\n", -+ tag, error_message(c)); -+ return; -+ } - if (c != ' ') { - prot_printf(imapd_out, - "%s BAD Missing required argument to Genurlauth\r\n", -diff --git a/imap/imapparse.c b/imap/imapparse.c -index 14e6be226..ddf5c2756 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -159,6 +159,10 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - /* Fail per RFC 7888, Section 4, choice 2 */ - fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); - } -+ if (config_maxliteral && len >= 0 && (unsigned) len > config_maxliteral) { -+ /* Fail per RFC 7888, Section 4, choice 2 */ -+ fatal(error_message(IMAP_LITERAL_TOO_LARGE), EX_IOERR); -+ } - isnowait++; - c = prot_getc(pin); - } -@@ -181,6 +185,10 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - } - - if (!isnowait) { -+ if (config_maxliteral && len >= 0 && (unsigned) len > config_maxliteral) { -+ return IMAP_LITERAL_TOO_LARGE; -+ } -+ - prot_printf(pout, "+ go ahead\r\n"); - prot_flush(pout); - } -@@ -689,7 +697,7 @@ static int get_search_annotation(struct protstream *pin, - - /* parse the value */ - c = getbnstring(pin, pout, &value); -- if (c == EOF) -+ if (c <= EOF) - goto bad; - - sa = xzmalloc(sizeof(*sa)); -@@ -710,6 +718,7 @@ bad: - buf_free(&attrib); - buf_free(&value); - -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c != EOF) prot_ungetc(c, pin); - return EOF; - } -@@ -842,7 +851,7 @@ static int get_search_criterion(struct protstream *pin, - do { - c = get_search_criterion(pin, pout, e, base); - } while (c == ' '); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - if (c != ')') { - prot_printf(pout, "%s BAD Missing required close paren in Search command\r\n", - base->tag); -@@ -878,7 +887,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "annotation")) { /* RFC 5257 */ - struct searchannot *annot = NULL; - c = get_search_annotation(pin, pout, base, c, &annot); -- if (c == EOF) -+ if (c <= EOF) - goto badcri; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("annotation"); -@@ -899,23 +908,15 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "bcc")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "body")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } -- else if (!strcmp(criteria.s, "fuzzy")) { -- if (c != ' ') goto missingarg; -- base->fuzzy_depth++; -- c = get_search_criterion(pin, pout, parent, base); -- base->fuzzy_depth--; -- if (c == EOF) return EOF; -- break; -- } - else goto badcri; - break; - -@@ -923,7 +924,7 @@ static int get_search_criterion(struct protstream *pin, - if (!strcmp(criteria.s, "cc")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (hasconv && !strcmp(criteria.s, "convflag")) { /* nonstandard */ -@@ -986,7 +987,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "deliveredto")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else goto badcri; -@@ -996,7 +997,7 @@ static int get_search_criterion(struct protstream *pin, - if (!strcmp(criteria.s, "emailid")) { /* draft-gondwana-imap-uniqueid */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - bytestring_match(parent, arg.s, criteria.s, base); - } - else goto badcri; -@@ -1009,7 +1010,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "folder")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find("folder"); - e->value.s = mboxname_from_external(arg.s, base->namespace, base->userid); -@@ -1017,7 +1018,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "from")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "fuzzy")) { /* RFC 6203 */ -@@ -1025,7 +1026,7 @@ static int get_search_criterion(struct protstream *pin, - base->fuzzy_depth++; - c = get_search_criterion(pin, pout, parent, base); - base->fuzzy_depth--; -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - } - else goto badcri; - break; -@@ -1036,7 +1037,7 @@ static int get_search_criterion(struct protstream *pin, - c = getastring(pin, pout, &arg); - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg2); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - - e = search_expr_new(parent, SEOP_MATCH); - e->attr = search_attr_find_field(arg.s); -@@ -1098,7 +1099,7 @@ static int get_search_criterion(struct protstream *pin, - if (c != ' ') goto missingarg; - e = search_expr_new(parent, SEOP_NOT); - c = get_search_criterion(pin, pout, e, base); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - } - else if (!strcmp(criteria.s, "new")) { /* RFC 3501 */ - e = search_expr_new(parent, SEOP_AND); -@@ -1113,10 +1114,10 @@ static int get_search_criterion(struct protstream *pin, - if (c != ' ') goto missingarg; - e = search_expr_new(parent, SEOP_OR); - c = get_search_criterion(pin, pout, e, base); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - if (c != ' ') goto missingarg; - c = get_search_criterion(pin, pout, e, base); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - } - else if (!strcmp(criteria.s, "old")) { /* RFC 3501 */ - indexflag_match(parent, MESSAGE_RECENT, /*not*/1); -@@ -1235,6 +1236,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "spamabove")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_GE); - e->attr = search_attr_find("spamscore"); -@@ -1243,6 +1245,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "spambelow")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c == EOF) goto badnumber; - e = search_expr_new(parent, SEOP_LT); - e->attr = search_attr_find("spamscore"); -@@ -1251,7 +1254,7 @@ static int get_search_criterion(struct protstream *pin, - else if (!strcmp(criteria.s, "subject")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else goto badcri; -@@ -1261,19 +1264,19 @@ static int get_search_criterion(struct protstream *pin, - if (!strcmp(criteria.s, "to")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "text")) { /* RFC 3501 */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, criteria.s, base); - } - else if (!strcmp(criteria.s, "threadid")) { /* draft-gondwana-imap-uniqueid */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - bytestring_match(parent, arg.s, criteria.s, base); - } - else goto badcri; -@@ -1323,25 +1326,25 @@ static int get_search_criterion(struct protstream *pin, - if (!strcmp(criteria.s, "xattachmentname")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, "attachmentname", base); - } - else if (!strcmp(criteria.s, "xattachmentbody")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, "attachmentbody", base); - } - else if (!strcmp(criteria.s, "xlistid")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, "listid", base); - } - else if (!strcmp(criteria.s, "xcontenttype")) { /* nonstandard */ - if (c != ' ') goto missingarg; - c = getastring(pin, pout, &arg); -- if (c == EOF) goto missingarg; -+ if (c <= EOF) goto missingarg; - string_match(parent, arg.s, "contenttype", base); - } - else goto badcri; -@@ -1372,6 +1375,8 @@ static int get_search_criterion(struct protstream *pin, - - default: - badcri: -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; -+ - prot_printf(pout, "%s BAD Invalid Search criteria\r\n", base->tag); - if (c != EOF) prot_ungetc(c, pin); - return EOF; -@@ -1384,6 +1389,8 @@ static int get_search_criterion(struct protstream *pin, - return c; - - missingarg: -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; -+ - prot_printf(pout, "%s BAD Missing required argument to Search %s\r\n", - base->tag, criteria.s); - if (c != EOF) prot_ungetc(c, pin); -diff --git a/lib/imapoptions b/lib/imapoptions -index 5cb8ef7b8..833245069 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -1571,11 +1571,21 @@ Blank lines and lines beginning with ``#'' are ignored. - messages larger than \fImaxmessagesize\fR bytes. If set to 0, this - will allow messages of any size (the default). */ - -+{ "maxliteral", 131072, INT, "UNRELEASED" } -+/* Maximum size in bytes of a single literal allowed by the IMAP parser. -+.PP -+ Literals used for message [part] data in APPEND are only limited by -+ the 'maxmessagesize' option. -+.PP -+ If the 'literalminus' option is enabled, non-synchonizing literals -+ will be limited to the lesser of 4K and either 'maxliteral' or -+ 'maxmessagesize', depending on the use-case. */ -+ - { "maxquoted", 131072, INT, "2.3.17" } - /* Maximum size of a single quoted string for the parser. Default 128k */ - - { "maxword", 131072, INT, "2.3.17" } --/* Maximum size of a single word for the parser. Default 128k */ -+/* Maximum size of a single word allowed by the IMAP parser. Default 128k */ - - { "mboxkey_db", "twoskip", STRINGLIST("skiplist", "twoskip", "zeroskip"), "3.1.6" } - /* The cyrusdb backend to use for mailbox keys. */ -diff --git a/lib/libconfig.c b/lib/libconfig.c -index 860c34863..de9591b7d 100644 ---- a/lib/libconfig.c -+++ b/lib/libconfig.c -@@ -84,6 +84,7 @@ EXPORTED int config_auditlog; - EXPORTED int config_iolog; - EXPORTED unsigned config_maxword; - EXPORTED unsigned config_maxquoted; -+EXPORTED unsigned config_maxliteral; - EXPORTED int config_qosmarking; - EXPORTED int config_debug; - -@@ -473,6 +474,7 @@ EXPORTED void config_reset(void) - config_defdomain = NULL; - config_auditlog = 0; - config_serverinfo = 0; -+ config_maxliteral = 0; - config_maxquoted = 0; - config_maxword = 0; - config_qosmarking = 0; -@@ -659,6 +661,7 @@ EXPORTED void config_read(const char *alt_config, const int config_need_data) - config_serverinfo = config_getenum(IMAPOPT_SERVERINFO); - - /* set some limits */ -+ config_maxliteral = config_getint(IMAPOPT_MAXLITERAL); - config_maxquoted = config_getint(IMAPOPT_MAXQUOTED); - config_maxword = config_getint(IMAPOPT_MAXWORD); - -diff --git a/lib/libconfig.h b/lib/libconfig.h -index dd9eee2e3..8c8fed54a 100644 ---- a/lib/libconfig.h -+++ b/lib/libconfig.h -@@ -89,6 +89,7 @@ extern enum enum_value config_virtdomains; - extern enum enum_value config_mupdate_config; - extern int config_auditlog; - extern int config_iolog; -+extern unsigned config_maxliteral; - extern unsigned config_maxquoted; - extern unsigned config_maxword; - extern int config_qosmarking; --- -2.39.2 - - -From e32406ce63ce69161a37d59507e978b3ae6710fb Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 21 Feb 2024 11:18:52 -0500 -Subject: [PATCH 17/22] prot.h: change bytes_in/out to uint64_t (for - long-running imapd) - ---- - backup/lcb.c | 4 ++-- - backup/lcb_compact.c | 4 ++-- - backup/lcb_verify.c | 8 ++++---- - imap/httpd.c | 14 ++++++++------ - imap/imapd.c | 18 ++++++++++-------- - imap/pop3d.c | 18 ++++++++++-------- - lib/prot.h | 8 ++++---- - 7 files changed, 40 insertions(+), 34 deletions(-) - -diff --git a/backup/lcb.c b/backup/lcb.c -index 8f0de3b8f..cb3c4a595 100644 ---- a/backup/lcb.c -+++ b/backup/lcb.c -@@ -609,11 +609,11 @@ EXPORTED int backup_reindex(const char *name, - const char *error = prot_error(member); - if (error && 0 != strcmp(error, PROT_EOF_STRING)) { - syslog(LOG_ERR, -- "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n", -+ "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s\n", - name, member_offset, prot_bytes_in(member), error); - - if (out) -- fprintf(out, "error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n", -+ fprintf(out, "error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s\n", - member_offset, prot_bytes_in(member), error); - - r = IMAP_IOERROR; -diff --git a/backup/lcb_compact.c b/backup/lcb_compact.c -index 6a6cb5282..3f8693ef2 100644 ---- a/backup/lcb_compact.c -+++ b/backup/lcb_compact.c -@@ -521,11 +521,11 @@ EXPORTED int backup_compact(const char *name, - const char *error = prot_error(in); - if (error && 0 != strcmp(error, PROT_EOF_STRING)) { - syslog(LOG_ERR, -- "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n", -+ "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s\n", - name, chunk->offset, prot_bytes_in(in), error); - - if (out) -- fprintf(out, "error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n", -+ fprintf(out, "error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s\n", - chunk->offset, prot_bytes_in(in), error); - - /* chunk is corrupt, discard the rest of it and get on with -diff --git a/backup/lcb_verify.c b/backup/lcb_verify.c -index a59984471..1fbd7bca5 100644 ---- a/backup/lcb_verify.c -+++ b/backup/lcb_verify.c -@@ -234,10 +234,10 @@ static int _verify_message_cb(const struct backup_message *message, void *rock) - const char *error = prot_error(ps); - if (error && 0 != strcmp(error, PROT_EOF_STRING)) { - syslog(LOG_ERR, -- "%s: error reading message %i at offset " OFF_T_FMT ", byte %i: %s", -+ "%s: error reading message %i at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - __func__, message->id, message->offset, prot_bytes_in(ps), error); - if (out) -- fprintf(out, "error reading message %i at offset " OFF_T_FMT ", byte %i: %s", -+ fprintf(out, "error reading message %i at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - message->id, message->offset, prot_bytes_in(ps), error); - } - prot_free(ps); -@@ -539,10 +539,10 @@ static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk - const char *error = prot_error(ps); - if (error && 0 != strcmp(error, PROT_EOF_STRING)) { - syslog(LOG_ERR, -- "%s: error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s", -+ "%s: error reading chunk %i data at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - __func__, chunk->id, chunk->offset, prot_bytes_in(ps), error); - if (out) -- fprintf(out, "error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s", -+ fprintf(out, "error reading chunk %i data at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - chunk->id, chunk->offset, prot_bytes_in(ps), error); - r = EOF; - } -diff --git a/imap/httpd.c b/imap/httpd.c -index 069038a95..851bcc643 100644 ---- a/imap/httpd.c -+++ b/imap/httpd.c -@@ -584,8 +584,8 @@ struct namespace_t *http_namespaces[] = { - static void httpd_reset(struct http_connection *conn) - { - int i; -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - /* Do any namespace specific cleanup */ - for (i = 0; http_namespaces[i]; i++) { -@@ -627,7 +627,8 @@ static void httpd_reset(struct http_connection *conn) - - if (config_auditlog) { - syslog(LOG_NOTICE, -- "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -+ "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", - session_id(), bytes_in, bytes_out); - } - -@@ -998,8 +999,8 @@ void usage(void) - void shut_down(int code) - { - int i; -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - in_shutdown = 1; - -@@ -1058,7 +1059,8 @@ void shut_down(int code) - - if (config_auditlog) - syslog(LOG_NOTICE, -- "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -+ "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", - session_id(), bytes_in, bytes_out); - - #ifdef HAVE_SSL -diff --git a/imap/imapd.c b/imap/imapd.c -index 28d0f299d..0a6574c19 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -729,8 +729,8 @@ static int mlookup(const char *tag, const char *ext_name, - static void imapd_reset(void) - { - int i; -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - proc_cleanup(); - -@@ -777,8 +777,9 @@ static void imapd_reset(void) - } - - if (config_auditlog) -- syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -- session_id(), bytes_in, bytes_out); -+ syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", -+ session_id(), bytes_in, bytes_out); - - imapd_in = imapd_out = NULL; - -@@ -1072,8 +1073,8 @@ void shut_down(int code) __attribute__((noreturn)); - void shut_down(int code) - { - int i; -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - in_shutdown = 1; - -@@ -1138,8 +1139,9 @@ void shut_down(int code) - : CYRUS_IMAP_SHUTDOWN_TOTAL_STATUS_OK); - - if (config_auditlog) -- syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -- session_id(), bytes_in, bytes_out); -+ syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", -+ session_id(), bytes_in, bytes_out); - - if (protin) protgroup_free(protin); - -diff --git a/imap/pop3d.c b/imap/pop3d.c -index 167e5c75e..349f4bf92 100644 ---- a/imap/pop3d.c -+++ b/imap/pop3d.c -@@ -325,8 +325,8 @@ static struct sasl_callback mysasl_cb[] = { - - static void popd_reset(void) - { -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - proc_cleanup(); - -@@ -361,8 +361,9 @@ static void popd_reset(void) - } - - if (config_auditlog) -- syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -- session_id(), bytes_in, bytes_out); -+ syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", -+ session_id(), bytes_in, bytes_out); - - popd_in = popd_out = NULL; - -@@ -598,8 +599,8 @@ static void usage(void) - */ - void shut_down(int code) - { -- int bytes_in = 0; -- int bytes_out = 0; -+ uint64_t bytes_in = 0; -+ uint64_t bytes_out = 0; - - in_shutdown = 1; - -@@ -644,8 +645,9 @@ void shut_down(int code) - } - - if (config_auditlog) -- syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s> bytes_in=<%d> bytes_out=<%d>", -- session_id(), bytes_in, bytes_out); -+ syslog(LOG_NOTICE, "auditlog: traffic sessionid=<%s>" -+ " bytes_in=<%" PRIu64 "> bytes_out=<%" PRIu64 ">", -+ session_id(), bytes_in, bytes_out); - - #ifdef HAVE_SSL - tls_shutdown_serverengine(); -diff --git a/lib/prot.h b/lib/prot.h -index 89b0b0a2a..94b22fad8 100644 ---- a/lib/prot.h -+++ b/lib/prot.h -@@ -131,8 +131,8 @@ struct protstream { - struct buf *writetobuf; - - int can_unget; -- int bytes_in; -- int bytes_out; -+ uint64_t bytes_in; -+ uint64_t bytes_out; - int isclient; /* read/write IMAP LITERAL+ */ - - /* Events */ -@@ -224,8 +224,8 @@ extern int prot_free(struct protstream *s); - extern int prot_setlog(struct protstream *s, int fd); - - /* Get traffic counts */ --extern int prot_bytes_in(struct protstream *s); --extern int prot_bytes_out(struct protstream *s); -+extern uint64_t prot_bytes_in(struct protstream *s); -+extern uint64_t prot_bytes_out(struct protstream *s); - #define prot_bytes_in(s) ((s)->bytes_in) - #define prot_bytes_out(s) ((s)->bytes_out) - --- -2.39.2 - - -From 0c8af18b9ad12bd59556f033e25ba8f2828bc969 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 21 Feb 2024 11:35:44 -0500 -Subject: [PATCH 18/22] imapd.c: rename 'maxsize' to 'maxmsgsize' - ---- - imap/imapd.c | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index 0a6574c19..5ef0ce778 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -135,7 +135,7 @@ static int imaps = 0; - static sasl_ssf_t extprops_ssf = 0; - static int nosaslpasswdcheck = 0; - static int apns_enabled = 0; --static size_t maxsize = 0; -+static size_t maxmsgsize = 0; - - /* PROXY STUFF */ - /* we want a list of our outgoing connections here and which one we're -@@ -898,8 +898,8 @@ int service_init(int argc, char **argv, char **envp) - - prometheus_increment(CYRUS_IMAP_READY_LISTENERS); - -- maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE); -- if (!maxsize) maxsize = UINT32_MAX; -+ maxmsgsize = config_getint(IMAPOPT_MAXMESSAGESIZE); -+ if (!maxmsgsize) maxmsgsize = UINT32_MAX; - - return 0; - } -@@ -3985,13 +3985,13 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - - /* Catenate the message part(s) to stage */ - size = 0; -- r = append_catenate(curstage->f, cur_name, maxsize, &size, -+ r = append_catenate(curstage->f, cur_name, maxmsgsize, &size, - &(curstage->binary), &parseerr, &url); - if (r) goto done; - } - else { - /* Read size from literal */ -- r = getliteralsize(arg.s, c, maxsize, &size, &(curstage->binary), &parseerr); -+ r = getliteralsize(arg.s, c, maxmsgsize, &size, &(curstage->binary), &parseerr); - if (!r && size == 0) r = IMAP_ZERO_LENGTH_LITERAL; - if (r) goto done; - --- -2.39.2 - - -From 046bf9a6ec7516cb728d9ef003029fc853c2c02f Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 23 Feb 2024 15:08:42 -0500 -Subject: [PATCH 19/22] imapd.c: limit the total size of IMAP command arguments - -Only concerned with commands that can have an unlimited -number of arguments. ---- - changes/next/imap_literal_limits | 7 ++- - imap/imap_err.et | 3 + - imap/imapd.c | 97 +++++++++++++++++++++++++++++--- - imap/imapd.h | 1 + - imap/imapparse.c | 10 ++++ - lib/imapoptions | 5 ++ - 6 files changed, 112 insertions(+), 11 deletions(-) - -diff --git a/changes/next/imap_literal_limits b/changes/next/imap_literal_limits -index c7fc35bbc..f1ea34a0b 100644 ---- a/changes/next/imap_literal_limits -+++ b/changes/next/imap_literal_limits -@@ -1,12 +1,13 @@ - Description: - --Adds a config option to limit the size of a single literal allowed --by the IMAP parser. Also properly applies LITERAL- to IMAP APPEND. -+Adds config options to limit the size of a single literal allowed -+by the IMAP parser and to limit the total size of IMAP command arguments. -+Also properly applies LITERAL- to IMAP APPEND. - - - Config changes: - --New 'maxliteral' option. -+New 'maxliteral' and 'maxargssize' options. - - - Upgrade instructions: -diff --git a/imap/imap_err.et b/imap/imap_err.et -index e309c1203..29ba44953 100644 ---- a/imap/imap_err.et -+++ b/imap/imap_err.et -@@ -69,6 +69,9 @@ ec IMAP_MESSAGE_TOO_LARGE, - ec IMAP_MESSAGE_TOOBIG, - "[TOOBIG] Message size exceeds fixed limit" - -+ec IMAP_ARGS_TOO_LARGE, -+ "[TOOBIG] Command arguments total size exceeds fixed limit" -+ - ec IMAP_LITERAL_TOO_LARGE, - "[TOOBIG] Literal size exceeds fixed limit" - -diff --git a/imap/imapd.c b/imap/imapd.c -index 5ef0ce778..e9451d35e 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -136,6 +136,8 @@ static sasl_ssf_t extprops_ssf = 0; - static int nosaslpasswdcheck = 0; - static int apns_enabled = 0; - static size_t maxmsgsize = 0; -+static int64_t maxargssize = 0; -+static uint64_t maxargssize_mark = 0; - - /* PROXY STUFF */ - /* we want a list of our outgoing connections here and which one we're -@@ -901,6 +903,9 @@ int service_init(int argc, char **argv, char **envp) - maxmsgsize = config_getint(IMAPOPT_MAXMESSAGESIZE); - if (!maxmsgsize) maxmsgsize = UINT32_MAX; - -+ maxargssize = config_getint(IMAPOPT_MAXARGSSIZE); -+ if (maxargssize <= 0) maxargssize = UINT32_MAX; -+ - return 0; - } - -@@ -1345,6 +1350,9 @@ static void cmdloop(void) - allowed when not logged in */ - if (!imapd_userid && !strchr("AELNCIS", cmd.s[0])) goto nologin; - -+ /* Set limit on the total number of bytes allowed for arguments */ -+ maxargssize_mark = prot_bytes_in(imapd_in) + maxargssize; -+ - /* Start command timer */ - cmdtime_starttimer(); - -@@ -3902,6 +3910,9 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - curstage = xzmalloc(sizeof(*curstage)); - ptrarray_push(&stages, curstage); - -+ /* Set limit on the total number of bytes allowed for mailbox+append-opts */ -+ maxargssize_mark = prot_bytes_in(imapd_in) + (maxargssize - strlen(name)); -+ - /* now parsing "append-opts" in the ABNF */ - - /* Parse flags */ -@@ -3910,6 +3921,8 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - strarray_init(&curstage->flags); - do { - c = getword(imapd_in, &arg); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (!curstage->flags.count && !arg.s[0] && c == ')') break; /* empty list */ - if (!isokflag(arg.s, &sync_seen)) { - parseerr = "Invalid flag in Append command"; -@@ -4017,15 +4030,23 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - } - - done: -- if (r) { -- eatline(imapd_in, c); -- } else { -+ switch (r) { -+ case IMAP_ZERO_LENGTH_LITERAL: -+ case IMAP_MESSAGE_TOO_LARGE: -+ break; -+ -+ case 0: - /* we should be looking at the end of the line */ -- if (!IS_EOL(c, imapd_in)) { -- parseerr = "junk after literal"; -- r = IMAP_PROTOCOL_ERROR; -- eatline(imapd_in, c); -- } -+ if (IS_EOL(c, imapd_in)) break; -+ -+ parseerr = "junk after literal"; -+ r = IMAP_PROTOCOL_ERROR; -+ -+ GCC_FALLTHROUGH -+ -+ default: -+ eatline(imapd_in, c); -+ break; - } - - /* Append from the stage(s) */ -@@ -4235,6 +4256,9 @@ static void cmd_select(char *tag, char *cmd, char *name) - c = getword(imapd_in, &arg); - if (arg.s[0] == '\0') goto badlist; - for (;;) { -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - ucase(arg.s); - if (!strcmp(arg.s, "CONDSTORE")) { - client_capa |= CAPA_CONDSTORE; -@@ -4609,6 +4633,9 @@ static int parse_fetch_args(const char *tag, const char *cmd, - c = getword(imapd_in, &fetchatt); - } - for (;;) { -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - ucase(fetchatt.s); - switch (fetchatt.s[0]) { - case 'A': -@@ -4739,6 +4766,8 @@ badannotation: - } - do { - c = getastring(imapd_in, imapd_out, &fieldname); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) { - prot_printf(imapd_out, "%s NO %s in %s %s\r\n", - tag, error_message(c), cmd, fetchatt.s); -@@ -5073,6 +5102,9 @@ badannotation: - } - do { - c = getword(imapd_in, &fetchatt); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - ucase(fetchatt.s); - if (!strcmp(fetchatt.s, "CHANGEDSINCE")) { - if (c != ' ') { -@@ -5419,6 +5451,9 @@ static void cmd_store(char *tag, char *sequence, int usinguid) - - do { - c = getword(imapd_in, &storemod); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - ucase(storemod.s); - if (!strcmp(storemod.s, "UNCHANGEDSINCE")) { - if (c != ' ') { -@@ -5511,6 +5546,8 @@ static void cmd_store(char *tag, char *sequence, int usinguid) - - for (;;) { - c = getword(imapd_in, &flagname); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == '(' && !flagname.s[0] && !flagsparsed && !inlist) { - inlist = 1; - continue; -@@ -5639,6 +5676,8 @@ static void cmd_search(char *tag, int usinguid) - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); - -+ searchargs->maxargssize_mark = maxargssize_mark; -+ - /* Set FUZZY search according to config and quirks */ - static const char *annot = IMAP_ANNOT_NS "search-fuzzy-always"; - char *inbox = mboxname_user_mbox(imapd_userid, NULL); -@@ -5730,6 +5769,9 @@ static void cmd_sort(char *tag, int usinguid) - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); -+ -+ searchargs->maxargssize_mark = maxargssize_mark; -+ - if (imapd_id.quirks & QUIRK_SEARCHFUZZY) - searchargs->fuzzy_depth++; - -@@ -5846,6 +5888,9 @@ static void cmd_thread(char *tag, int usinguid) - searchargs = new_searchargs(tag, GETSEARCH_CHARSET_FIRST, - &imapd_namespace, imapd_userid, imapd_authstate, - imapd_userisadmin || imapd_userisproxyadmin); -+ -+ searchargs->maxargssize_mark = maxargssize_mark; -+ - c = get_search_program(imapd_in, imapd_out, searchargs); - if (c == EOF) { - eatline(imapd_in, ' '); -@@ -7409,6 +7454,8 @@ static void getlistargs(char *tag, struct listargs *listargs) - listargs->cmd = LIST_CMD_EXTENDED; - for (;;) { - c = getastring(imapd_in, imapd_out, &buf); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (*buf.s) - strarray_append(&listargs->pat, buf.s); -@@ -8252,6 +8299,9 @@ void cmd_setquota(const char *tag, const char *quotaroot) - newquotas[res] = limit; - if (c == ')') break; - else if (c != ' ') goto badlist; -+ -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - } - c = prot_getc(imapd_in); - if (!IS_EOL(c, imapd_in)) { -@@ -8404,6 +8454,9 @@ static int parse_statusitems(unsigned *statusitemsp, const char **errstr) - c = getword(imapd_in, &arg); - if (arg.s[0] == '\0') goto bad; - for (;;) { -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - lcase(arg.s); - if (!strcmp(arg.s, "messages")) { - statusitems |= STATUS_MESSAGES; -@@ -8786,6 +8839,9 @@ static int parsecreateargs(struct dlist **extargs) - /* new style RFC 4466 arguments */ - do { - c = getword(imapd_in, &arg); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - name = ucase(arg.s); - if (c != ' ') goto fail; - c = prot_getc(imapd_in); -@@ -8794,6 +8850,9 @@ static int parsecreateargs(struct dlist **extargs) - sub = dlist_newlist(res, name); - do { - c = getword(imapd_in, &val); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - dlist_setatom(sub, name, val.s); - } while (c == ' '); - if (c != ')') goto fail; -@@ -8862,6 +8921,8 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -8913,6 +8974,8 @@ static int parse_annotate_fetch_data(const char *tag, - c = getastring(imapd_in, imapd_out, &arg); - else - c = getqstring(imapd_in, imapd_out, &arg); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -8991,6 +9054,8 @@ static int parse_metadata_string_or_list(const char *tag, - /* entry list */ - do { - c = getastring(imapd_in, imapd_out, &arg); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -9095,6 +9160,8 @@ static int parse_annotate_store_data(const char *tag, - c = getastring(imapd_in, imapd_out, &entry); - else - c = getqstring(imapd_in, imapd_out, &entry); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -9137,6 +9204,9 @@ static int parse_annotate_store_data(const char *tag, - goto baddata; - } - -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - /* add the attrib-value pair to the list */ - appendattvalue(&attvalues, attrib.s, &value); - -@@ -9212,6 +9282,8 @@ static int parse_metadata_store_data(const char *tag, - do { - /* get entry */ - c = getastring(imapd_in, imapd_out, &entry); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c != ' ') { - prot_printf(imapd_out, -@@ -9225,6 +9297,8 @@ static int parse_metadata_store_data(const char *tag, - - /* get value */ - c = getbnstring(imapd_in, imapd_out, &value); -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -11466,6 +11540,9 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - nsort = 0; - n = 0; - for (;;) { -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ - if (n >= nsort - 1) { /* leave room for implicit criterion */ - /* (Re)allocate an array for sort criteria */ - nsort += SORTGROWSIZE; -@@ -11615,6 +11692,8 @@ static int getlistselopts(char *tag, struct listargs *args) - for (;;) { - c = getword(imapd_in, &buf); - -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (!*buf.s) { - prot_printf(imapd_out, - "%s BAD Invalid syntax in List command\r\n", -@@ -11719,6 +11798,8 @@ static int getlistretopts(char *tag, struct listargs *args) - for (;;) { - c = getword(imapd_in, &buf); - -+ if (prot_bytes_in(imapd_in) > maxargssize_mark) -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); - if (!*buf.s) { - prot_printf(imapd_out, - "%s BAD Invalid syntax in List command\r\n", tag); -diff --git a/imap/imapd.h b/imap/imapd.h -index a6724af89..e6537dd95 100644 ---- a/imap/imapd.h -+++ b/imap/imapd.h -@@ -222,6 +222,7 @@ struct searchargs { - int state; - /* used only during parsing */ - int fuzzy_depth; -+ uint64_t maxargssize_mark; - - /* For ESEARCH & XCONVMULTISORT */ - const char *tag; -diff --git a/imap/imapparse.c b/imap/imapparse.c -index ddf5c2756..1fdb8b312 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -634,6 +634,11 @@ EXPORTED int get_search_return_opts(struct protstream *pin, - goto bad; - } - -+ if (searchargs->maxargssize_mark && -+ prot_bytes_in(pin) > searchargs->maxargssize_mark) { -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ } -+ - } while (c == ' '); - - /* RFC 4731: -@@ -1382,6 +1387,11 @@ static int get_search_criterion(struct protstream *pin, - return EOF; - } - -+ if (base->maxargssize_mark && -+ prot_bytes_in(pin) > base->maxargssize_mark) { -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ } -+ - if (!keep_charset) - base->state &= ~GETSEARCH_CHARSET_KEYWORD; - base->state &= ~GETSEARCH_RETURN; -diff --git a/lib/imapoptions b/lib/imapoptions -index 833245069..75950e5c4 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -1566,6 +1566,11 @@ Blank lines and lines beginning with ``#'' are ignored. - /* Maximum number of logged in sessions allowed per user, - zero means no limit */ - -+{ "maxargssize", 0, INT, "UNRELEASED" } -+/* Maximum total size of arguments to an IMAP command that will be -+ accepted by Cyrus. -+ Commands with arguments that exceed this limit will be rejected. -+ - { "maxmessagesize", 0, INT, "2.3.17" } - /* Maximum incoming LMTP message size. If non-zero, lmtpd will reject - messages larger than \fImaxmessagesize\fR bytes. If set to 0, this --- -2.39.2 - - -From 8d72de770eb354e52a659a1264809f773ff8fcf1 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Tue, 12 Mar 2024 23:16:30 -0400 -Subject: [PATCH 20/22] Add IMAPLimits.pm - ---- - cassandane/Cassandane/Cyrus/IMAPLimits.pm | 518 ++++++++++++++++++++++ - cassandane/Cassandane/IMAPMessageStore.pm | 8 +- - 2 files changed, 523 insertions(+), 3 deletions(-) - create mode 100644 cassandane/Cassandane/Cyrus/IMAPLimits.pm - -diff --git a/cassandane/Cassandane/Cyrus/IMAPLimits.pm b/cassandane/Cassandane/Cyrus/IMAPLimits.pm -new file mode 100644 -index 000000000..2275c5cf7 ---- /dev/null -+++ b/cassandane/Cassandane/Cyrus/IMAPLimits.pm -@@ -0,0 +1,518 @@ -+#!/usr/bin/perl -+# -+# Copyright (c) 2011-2023 FastMail Pty Ltd. All rights reserved. -+# -+# Redistribution and use in source and binary forms, with or without -+# modification, are permitted provided that the following conditions -+# are met: -+# -+# 1. Redistributions of source code must retain the above copyright -+# notice, this list of conditions and the following disclaimer. -+# -+# 2. Redistributions in binary form must reproduce the above copyright -+# notice, this list of conditions and the following disclaimer in -+# the documentation and/or other materials provided with the -+# distribution. -+# -+# 3. The name "Fastmail Pty Ltd" must not be used to -+# endorse or promote products derived from this software without -+# prior written permission. For permission or any legal -+# details, please contact -+# FastMail Pty Ltd -+# PO Box 234 -+# Collins St West 8007 -+# Victoria -+# Australia -+# -+# 4. Redistributions of any form whatsoever must retain the following -+# acknowledgment: -+# "This product includes software developed by Fastmail Pty. Ltd." -+# -+# FASTMAIL PTY LTD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO -+# EVENT SHALL OPERA SOFTWARE AUSTRALIA BE LIABLE FOR ANY SPECIAL, INDIRECT -+# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -+# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -+# OF THIS SOFTWARE. -+# -+ -+package Cassandane::Cyrus::IMAPLimits; -+use strict; -+use warnings; -+use Mail::JMAPTalk 0.13; -+use Data::Dumper; -+ -+use lib '.'; -+use base qw(Cassandane::Cyrus::TestCase); -+use Cassandane::Util::Log; -+ -+my $email = < -+ -+Body -+EOF -+ -+$email =~ s/\r?\n/\r\n/gs; -+ -+my $toobig_email = $email . "X" x 100; -+ -+sub assert_bye_toobig -+{ -+ my ($self, $store) = @_; -+ -+ $store = $self->{store} if (!defined $store); -+ -+ # We want to override Mail::IMAPTalk's builtin handling of the BYE -+ # untagged response, as it will 'die' immediately without parsing -+ # the remainder of the line and especially without picking out the -+ # [TOOBIG] response code that we want to see. -+ my $got_toobig = 0; -+ my $handlers = -+ { -+ bye => sub -+ { -+ my (undef, $resp) = @_; -+ $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -+ } -+ }; -+ -+ # Check that we got a BYE [TOOBIG] response -+ $store->idle_response($handlers, 1); -+ $self->assert_num_equals(1, $got_toobig); -+} -+ -+sub assert_cmd_bye_toobig -+{ -+ my $self = shift; -+ my $cmd = shift; -+ -+ my $talk = $self->{store}->get_client(); -+ $talk->enable('qresync'); # IMAPTalk requires lower-case -+ $talk->select('INBOX'); -+ -+ $talk->_send_cmd($cmd, @_); -+ $self->assert_bye_toobig(); -+} -+ -+sub assert_cmd_no_toobig -+{ -+ my $self = shift; -+ my $talk = shift; -+ my $cmd = shift; -+ -+ my $got_toobig = 0; -+ my $handlers = -+ { -+ 'no' => sub -+ { -+ # Pick out the [TOOBIG] response code -+ my (undef, $resp) = @_; -+ $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -+ } -+ }; -+ -+ $talk->_imap_cmd($cmd, 0, $handlers, @_); -+ -+ # Check that we got a NO [TOOBIG] response -+ $self->assert_str_equals('no', $talk->get_last_completion_response()); -+ $self->assert_num_equals(1, $got_toobig); -+} -+ -+sub new -+{ -+ my $class = shift; -+ -+ my $config = Cassandane::Config->default()->clone(); -+ $config->set(maxword => 25); -+ $config->set(maxquoted => 25); -+ $config->set(maxliteral => 25); -+ $config->set(literalminus => 1); -+ $config->set(maxargssize => 45); -+ $config->set(maxmessagesize => 100); -+ $config->set(event_groups => "message mailbox applepushservice"); -+ $config->set(aps_topic => "mail"); -+ -+ return $class->SUPER::new({ -+ adminstore => 1, -+ config => $config, -+ services => ['imap'], -+ }, @_); -+} -+ -+sub set_up -+{ -+ my ($self) = @_; -+ $self->SUPER::set_up(); -+} -+ -+sub tear_down -+{ -+ my ($self) = @_; -+ $self->SUPER::tear_down(); -+} -+ -+sub test_maxword -+{ -+ my ($self) = @_; -+ -+ # Oversized command name -+ $self->assert_cmd_bye_toobig("X" x 26); -+} -+ -+sub test_maxword_astring -+{ -+ my ($self) = @_; -+ -+ # Oversized mailbox name -+ $self->assert_cmd_bye_toobig('SELECT', "X" x 26); -+} -+ -+sub test_maxquoted -+{ -+ my ($self) = @_; -+ -+ # Oversized mailbox name -+ $self->assert_cmd_bye_toobig('SELECT', { Quote => "X" x 26 }); -+} -+ -+sub test_maxliteral_nosync -+{ -+ my ($self) = @_; -+ -+ my $talk = $self->{store}->get_client(); -+ # Do this by brute force until we have IMAPTalk v4.06+ -+ $talk->_imap_socket_out($talk->{CmdId}++ . " SELECT {26+}\015\012"); -+ $self->assert_bye_toobig(); -+} -+ -+sub test_maxliteral_sync -+{ -+ my ($self) = @_; -+ -+ # Unlike oversized non-sync literals which fatal() in one central location, -+ # oversized sync literals fail with a NO response in multiple places, -+ # so we test as many of those places as possible. -+ # Having said that, arguments parsed in cmdloop() or in get_search_criterion() -+ # are mostly handled centrally. -+ -+ # Authenticated State -+ -+ # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) -+ my $talk = $self->{store}->get_client(NoLiteralPlus => 1); -+ -+ $self->assert_cmd_no_toobig($talk, 'SELECT', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'ID', -+ [ { Literal => "X" x 26 } ]); -+ -+ $self->assert_cmd_no_toobig($talk, 'ID', -+ [ { Quote => 'foo' }, { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'LIST', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'LIST', -+ { Quote => '' }, { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'LISTRIGHTS', -+ 'INBOX', { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETACL', -+ 'INBOX', 'anyone', { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'GETMETADATA', -+ 'INBOX', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'GETMETADATA', -+ 'INBOX', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETMETADATA', -+ 'INBOX', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETMETADATA', -+ 'INBOX', [ '/comment', { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', -+ 'FOO', { Literal => "X" x 26 }); -+ -+ # Selected State -+ $talk->select('INBOX'); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'ANNOTATION', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'BODY[HEADER.FIELDS', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'RFC822.HEADER.LINES', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', -+ [ { Quote => '/comment' }, -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', -+ [ { Quote => '/comment' }, -+ [ { Quote => 'value' }, -+ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'HEADER', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'HEADER', 'SUBJECT', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', '/comment', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', '/comment', -+ 'value', { Literal => "X" x 26 } ); -+} -+ -+sub test_maxargssize_append_flags -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('APPEND', 'INBOX', -+ [ "X" x 25, "X" x 25 ], { Literal => $email } ); -+} -+ -+sub test_maxargssize_append_annot -+{ -+ my ($self) = @_; -+ -+ # Use MULTIAPPEND, fail the second -+ $self->assert_cmd_bye_toobig('APPEND', 'INBOX', -+ { Literal => $email }, -+ 'ANNOTATION', -+ [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ], -+ { Literal => $email } ); -+} -+ -+sub test_maxargssize_create -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('CREATE', "X" x 25, [ "X" x 25 ] ); -+} -+ -+sub test_maxargssize_create_ext -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('CREATE', -+ "X" x 5, [ "X" x 5, [ "X" x 25, "X" x 25 ] ] ); -+} -+ -+sub test_maxargssize_fetch -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('FETCH', '1', -+ [ 'BODY', 'ENVELOPE', 'FLAGS', -+ 'INTERNALDATE', 'RFC822.SIZE' ]); -+} -+ -+sub test_maxargssize_fetch_annot -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('FETCH', '1', -+ [ 'ANNOTATION', -+ [ [ "X" x 25, "X" x 25 ] ], "X" x 5 ] ); -+} -+ -+sub test_maxargssize_fetch_annot2 -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('FETCH', '1', -+ [ 'ANNOTATION', -+ [ "X" x 5, [ "X" x 25, "X" x 25 ] ] ] ); -+} -+ -+sub test_maxargssize_fetch_headers -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('FETCH', '1', -+ [ 'BODY[HEADER.FIELDS', [ "X" x 25, "X" x 25 ] ] ); -+} -+ -+sub test_maxargssize_getmetadata -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('GETMETADATA', 'INBOX', [ "X" x 25, "X" x 25 ] ); -+} -+ -+sub test_maxargssize_list_multi -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('LIST', { Quote => '' }, [ "X" x 25, "X" x 25 ]); -+} -+ -+sub test_maxargssize_list_select -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('LIST', -+ [ 'SUBSCRIBED', 'REMOTE', -+ 'RECURSIVEMATCH', 'SPECIAL-USE' ], -+ { Quote => '' }, '*'); -+} -+ -+sub test_maxargssize_list_return -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('LIST', -+ { Quote => '' }, '*', 'RETURN', -+ [ 'SUBSCRIBED', 'CHILDREN', -+ 'MYRIGHTS', 'SPECIAL-USE' ] ); -+} -+ -+sub test_maxargssize_search -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('SEARCH', -+ 'TEXT', "X" x 25, 'TEXT', { Quote => "X" x 25 } ); -+} -+ -+sub test_maxargssize_select -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('SELECT', 'INBOX', -+ [ 'QRESYNC', [ '1234567890', '1234567890' ], -+ 'ANNOTATE' ] ); -+} -+ -+sub test_maxargssize_setmetadata -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', -+ [ "X" x 25, { Quote => "X" x 25 } ] ); -+} -+ -+sub test_maxargssize_setmetadata2 -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('SETMETADATA', 'INBOX', -+ [ '/shared', { Quote => "X" x 25 }, -+ '/shared', { Quote => "X" x 25 } ] ); -+} -+ -+sub test_maxargssize_setquota -+{ -+ my ($self) = @_; -+ -+ my $store = $self->{adminstore}; -+ my $talk = $store->get_client(); -+ -+ $talk->_send_cmd('SETQUOTA', 'user.cassandane', -+ [ 'STORAGE', '1234567890', -+ 'MESSAGE', '1234567890', -+ 'MAILBOX', '1234567890' ] ); -+ $self->assert_bye_toobig($store); -+} -+ -+sub test_maxargssize_sort -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('SORT', -+ [ 'ARRIVAL', 'CC', 'DATE', -+ 'FROM', 'REVERSE', 'SIZE', 'TO' ], -+ 'UTF-8', 'ALL'); -+} -+ -+sub test_maxargssize_status -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('STATUS', 'INBOX', -+ [ 'MESSAGES', 'UIDNEXT', -+ 'UIDVALIDITY', 'UNSEEN', 'SIZE' ] ); -+} -+ -+sub test_maxargssize_store_annot -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('STORE', '1', 'ANNOTATION', -+ [ "X" x 25, [ 'VALUE', { Quote => "X" x 25 } ] ] ); -+} -+ -+sub test_maxargssize_store_annot2 -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('STORE', '1', 'ANNOTATION', -+ [ "X" x 5, [ 'VALUE', { Quote => "X" x 25 } ], -+ "X" x 5, [ 'VALUE', { Quote => "X" x 25 } ] ] ); -+} -+ -+sub test_append_zero -+{ -+ my ($self) = @_; -+ -+ my $talk = $self->{store}->get_client(); -+ $talk->_imap_cmd('APPEND', 0, '', 'INBOX', { Literal => '' } ); -+ $self->assert_str_equals('no', $talk->get_last_completion_response()); -+} -+ -+sub test_maxmessagesize_sync_literal -+{ -+ my ($self) = @_; -+ -+ # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) -+ my $talk = $self->{store}->get_client(NoLiteralPlus => 1); -+ -+ $self->assert_cmd_no_toobig($talk, 'APPEND', -+ 'INBOX', { Literal => $toobig_email } ); -+} -+ -+sub test_maxmessagesize_nosync_literal -+{ -+ my ($self) = @_; -+ -+ my $talk = $self->{store}->get_client(); -+ # Do this by brute force until we have IMAPTalk v4.06+ -+ $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {101+}\015\012"); -+ $self->assert_bye_toobig(); -+} -+ -+sub test_literal_minus -+{ -+ my ($self) = @_; -+ -+ my $talk = $self->{store}->get_client(); -+ $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {4097+}\015\012"); -+ $self->assert_bye_toobig(); -+} -+ -+1; -diff --git a/cassandane/Cassandane/IMAPMessageStore.pm b/cassandane/Cassandane/IMAPMessageStore.pm -index 338d1c5f3..959a9fabc 100644 ---- a/cassandane/Cassandane/IMAPMessageStore.pm -+++ b/cassandane/Cassandane/IMAPMessageStore.pm -@@ -83,7 +83,7 @@ sub new - - sub connect - { -- my ($self) = @_; -+ my ($self, %params) = @_; - - # if already successfully connected, do nothing - return -@@ -115,6 +115,7 @@ sub connect - Pedantic => 1, - PreserveINBOX => 1, - Uid => 0, -+ NoLiteralPlus => delete $params{NoLiteralPlus} || 0, - ) - or die "Cannot connect to '$self->{host}:$self->{port}': $@"; - } -@@ -129,6 +130,7 @@ sub connect - Pedantic => 1, - PreserveINBOX => 1, - Uid => 0, -+ NoLiteralPlus => delete $params{NoLiteralPlus} || 0, - ) - or die "Cannot connect to server: $@"; - } -@@ -323,9 +325,9 @@ sub remove - - sub get_client - { -- my ($self) = @_; -+ my ($self, %params) = @_; - -- $self->connect(); -+ $self->connect(%params); - return $self->{client}; - } - --- -2.39.2 - - -From 280151cceff96ce5eddd4e71255ce73f80fb1565 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 23:49:46 -0400 -Subject: [PATCH 21/22] imapd.c: also emit a NO [TOOBIG] response for oversized - no-sync APPEND - ---- - cassandane/Cassandane/Cyrus/IMAPLimits.pm | 46 ++++++++++++++--------- - imap/imapd.c | 23 ++++++++---- - 2 files changed, 44 insertions(+), 25 deletions(-) - -diff --git a/cassandane/Cassandane/Cyrus/IMAPLimits.pm b/cassandane/Cassandane/Cyrus/IMAPLimits.pm -index 2275c5cf7..52c1c8117 100644 ---- a/cassandane/Cassandane/Cyrus/IMAPLimits.pm -+++ b/cassandane/Cassandane/Cyrus/IMAPLimits.pm -@@ -59,6 +59,7 @@ $email =~ s/\r?\n/\r\n/gs; - - my $toobig_email = $email . "X" x 100; - -+# Check that we got an untagged BYE [TOOBIG] response - sub assert_bye_toobig - { - my ($self, $store) = @_; -@@ -79,11 +80,11 @@ sub assert_bye_toobig - } - }; - -- # Check that we got a BYE [TOOBIG] response - $store->idle_response($handlers, 1); - $self->assert_num_equals(1, $got_toobig); - } - -+# Send a command and expect an untagged BYE [TOOBIG] response - sub assert_cmd_bye_toobig - { - my $self = shift; -@@ -97,28 +98,37 @@ sub assert_cmd_bye_toobig - $self->assert_bye_toobig(); - } - -+# Check that we got a tagged NO [TOOBIG] response -+sub assert_no_toobig -+{ -+ my ($self, $talk) = @_; -+ -+ my $got_toobig = 0; -+ my $handlers = -+ { -+ 'no' => sub -+ { -+ my (undef, $resp) = @_; -+ $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -+ } -+ }; -+ -+ eval { -+ $talk->_parse_response($handlers); -+ }; -+ -+ $self->assert_num_equals(1, $got_toobig); -+} -+ -+# Send a command and expect a tagged NO [TOOBIG] response - sub assert_cmd_no_toobig - { - my $self = shift; - my $talk = shift; - my $cmd = shift; - -- my $got_toobig = 0; -- my $handlers = -- { -- 'no' => sub -- { -- # Pick out the [TOOBIG] response code -- my (undef, $resp) = @_; -- $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -- } -- }; -- -- $talk->_imap_cmd($cmd, 0, $handlers, @_); -- -- # Check that we got a NO [TOOBIG] response -- $self->assert_str_equals('no', $talk->get_last_completion_response()); -- $self->assert_num_equals(1, $got_toobig); -+ $talk->_send_cmd($cmd, @_); -+ $self->assert_no_toobig($talk); - } - - sub new -@@ -503,6 +513,7 @@ sub test_maxmessagesize_nosync_literal - my $talk = $self->{store}->get_client(); - # Do this by brute force until we have IMAPTalk v4.06+ - $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {101+}\015\012"); -+ $self->assert_no_toobig($talk); - $self->assert_bye_toobig(); - } - -@@ -512,6 +523,7 @@ sub test_literal_minus - - my $talk = $self->{store}->get_client(); - $talk->_imap_socket_out($talk->{CmdId}++ . " APPEND INBOX {4097+}\015\012"); -+ $self->assert_no_toobig($talk); - $self->assert_bye_toobig(); - } - -diff --git a/imap/imapd.c b/imap/imapd.c -index e9451d35e..000fedb22 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3517,7 +3517,7 @@ static int isokflag(char *s, int *isseen) - } - } - --static int getliteralsize(const char *p, int c, size_t maxsize, -+static int getliteralsize(const char *tag, const char *p, int c, size_t maxsize, - unsigned *size, int *binary, const char **parseerr) - - { -@@ -3549,10 +3549,14 @@ static int getliteralsize(const char *p, int c, size_t maxsize, - /* LITERAL- says maximum size is 4096! */ - if (lminus && num > 4096) { - /* Fail per RFC 7888, Section 4, choice 2 */ -+ prot_printf(imapd_out, "%s NO %s\r\n", tag, -+ error_message(IMAP_LITERAL_MINUS_TOO_LARGE)); - fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); - } - if (num > maxsize) { - /* Fail per RFC 7888, Section 4, choice 2 */ -+ prot_printf(imapd_out, "%s NO %s\r\n", tag, -+ error_message(IMAP_MESSAGE_TOOBIG)); - fatal(error_message(IMAP_MESSAGE_TOOBIG), EX_IOERR); - } - isnowait++; -@@ -3582,8 +3586,8 @@ static int getliteralsize(const char *p, int c, size_t maxsize, - return 0; - } - --static int catenate_text(FILE *f, size_t maxsize, unsigned *totalsize, int *binary, -- const char **parseerr) -+static int catenate_text(const char *tag, FILE *f, size_t maxsize, -+ unsigned *totalsize, int *binary, const char **parseerr) - { - int c; - static struct buf arg; -@@ -3595,7 +3599,8 @@ static int catenate_text(FILE *f, size_t maxsize, unsigned *totalsize, int *bina - c = getword(imapd_in, &arg); - - /* Read size from literal */ -- r = getliteralsize(arg.s, c, maxsize - *totalsize, &size, binary, parseerr); -+ r = getliteralsize(tag, arg.s, c, maxsize - *totalsize, -+ &size, binary, parseerr); - if (r) return r; - - /* Catenate message part to stage */ -@@ -3742,7 +3747,8 @@ static int catenate_url(const char *s, const char *cur_name, FILE *f, - return r; - } - --static int append_catenate(FILE *f, const char *cur_name, size_t maxsize, unsigned *totalsize, -+static int append_catenate(const char *tag, FILE *f, const char *cur_name, -+ size_t maxsize, unsigned *totalsize, - int *binary, const char **parseerr, const char **url) - { - int c, r = 0; -@@ -3756,7 +3762,7 @@ static int append_catenate(FILE *f, const char *cur_name, size_t maxsize, unsign - } - - if (!strcasecmp(arg.s, "TEXT")) { -- int r1 = catenate_text(f, maxsize, totalsize, binary, parseerr); -+ int r1 = catenate_text(tag, f, maxsize, totalsize, binary, parseerr); - if (r1) return r1; - - /* if we see a SP, we're trying to catenate more than one part */ -@@ -3998,13 +4004,14 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - - /* Catenate the message part(s) to stage */ - size = 0; -- r = append_catenate(curstage->f, cur_name, maxmsgsize, &size, -+ r = append_catenate(tag, curstage->f, cur_name, maxmsgsize, &size, - &(curstage->binary), &parseerr, &url); - if (r) goto done; - } - else { - /* Read size from literal */ -- r = getliteralsize(arg.s, c, maxmsgsize, &size, &(curstage->binary), &parseerr); -+ r = getliteralsize(tag, arg.s, c, maxmsgsize, -+ &size, &(curstage->binary), &parseerr); - if (!r && size == 0) r = IMAP_ZERO_LENGTH_LITERAL; - if (r) goto done; - --- -2.39.2 - - -From b21941fc79f81208cac4f8a2b32aa4ff80e4cc88 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 23:55:13 -0400 -Subject: [PATCH 22/22] imapd.c, imapparse.c: call fatal(EX_PROTOCOL) when - client exceeds a limit - ---- - cunit/parse.testc | 12 ++++++------ - imap/imapd.c | 48 +++++++++++++++++++++++------------------------ - imap/imapparse.c | 22 +++++++++++----------- - 3 files changed, 41 insertions(+), 41 deletions(-) - -diff --git a/cunit/parse.testc b/cunit/parse.testc -index 5a97f9b73..1786706cb 100644 ---- a/cunit/parse.testc -+++ b/cunit/parse.testc -@@ -119,7 +119,7 @@ static void test_getint32(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getint32, int32_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getint32, int32_t, STR4, &c, &val, &bytes_in); -@@ -188,7 +188,7 @@ static void test_getsint32(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getsint32, int32_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getsint32, int32_t, STR4, &c, &val, &bytes_in); -@@ -255,7 +255,7 @@ static void test_getuint32(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getuint32, uint32_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getuint32, uint32_t, STR4, &c, &val, &bytes_in); -@@ -322,7 +322,7 @@ static void test_getint64(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getint64, int64_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getint64, int64_t, STR4, &c, &val, &bytes_in); -@@ -391,7 +391,7 @@ static void test_getsint64(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getsint64, int64_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getsint64, int64_t, STR4, &c, &val, &bytes_in); -@@ -458,7 +458,7 @@ static void test_getuint64(void) - /* test a string with too many digits */ - CU_EXPECT_CYRFATAL_BEGIN; - wrap_int_parser(getuint64, uint64_t, STR3, &c, &val, NULL); -- CU_EXPECT_CYRFATAL_END(EX_IOERR, "num too big"); -+ CU_EXPECT_CYRFATAL_END(EX_PROTOCOL, "num too big"); - - /* test a valid value with a different terminator */ - wrap_int_parser(getuint64, uint64_t, STR4, &c, &val, &bytes_in); -diff --git a/imap/imapd.c b/imap/imapd.c -index 000fedb22..ee6519033 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3551,13 +3551,13 @@ static int getliteralsize(const char *tag, const char *p, int c, size_t maxsize, - /* Fail per RFC 7888, Section 4, choice 2 */ - prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(IMAP_LITERAL_MINUS_TOO_LARGE)); -- fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_PROTOCOL); - } - if (num > maxsize) { - /* Fail per RFC 7888, Section 4, choice 2 */ - prot_printf(imapd_out, "%s NO %s\r\n", tag, - error_message(IMAP_MESSAGE_TOOBIG)); -- fatal(error_message(IMAP_MESSAGE_TOOBIG), EX_IOERR); -+ fatal(error_message(IMAP_MESSAGE_TOOBIG), EX_PROTOCOL); - } - isnowait++; - p++; -@@ -3928,7 +3928,7 @@ static void cmd_append(char *tag, char *name, const char *cur_name) - do { - c = getword(imapd_in, &arg); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (!curstage->flags.count && !arg.s[0] && c == ')') break; /* empty list */ - if (!isokflag(arg.s, &sync_seen)) { - parseerr = "Invalid flag in Append command"; -@@ -4264,7 +4264,7 @@ static void cmd_select(char *tag, char *cmd, char *name) - if (arg.s[0] == '\0') goto badlist; - for (;;) { - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - ucase(arg.s); - if (!strcmp(arg.s, "CONDSTORE")) { -@@ -4641,7 +4641,7 @@ static int parse_fetch_args(const char *tag, const char *cmd, - } - for (;;) { - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - ucase(fetchatt.s); - switch (fetchatt.s[0]) { -@@ -4774,7 +4774,7 @@ badannotation: - do { - c = getastring(imapd_in, imapd_out, &fieldname); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) { - prot_printf(imapd_out, "%s NO %s in %s %s\r\n", - tag, error_message(c), cmd, fetchatt.s); -@@ -5110,7 +5110,7 @@ badannotation: - do { - c = getword(imapd_in, &fetchatt); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - ucase(fetchatt.s); - if (!strcmp(fetchatt.s, "CHANGEDSINCE")) { -@@ -5459,7 +5459,7 @@ static void cmd_store(char *tag, char *sequence, int usinguid) - do { - c = getword(imapd_in, &storemod); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - ucase(storemod.s); - if (!strcmp(storemod.s, "UNCHANGEDSINCE")) { -@@ -5554,7 +5554,7 @@ static void cmd_store(char *tag, char *sequence, int usinguid) - for (;;) { - c = getword(imapd_in, &flagname); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == '(' && !flagname.s[0] && !flagsparsed && !inlist) { - inlist = 1; - continue; -@@ -7462,7 +7462,7 @@ static void getlistargs(char *tag, struct listargs *listargs) - for (;;) { - c = getastring(imapd_in, imapd_out, &buf); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (*buf.s) - strarray_append(&listargs->pat, buf.s); -@@ -8308,7 +8308,7 @@ void cmd_setquota(const char *tag, const char *quotaroot) - else if (c != ' ') goto badlist; - - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - } - c = prot_getc(imapd_in); - if (!IS_EOL(c, imapd_in)) { -@@ -8462,7 +8462,7 @@ static int parse_statusitems(unsigned *statusitemsp, const char **errstr) - if (arg.s[0] == '\0') goto bad; - for (;;) { - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - lcase(arg.s); - if (!strcmp(arg.s, "messages")) { -@@ -8847,7 +8847,7 @@ static int parsecreateargs(struct dlist **extargs) - do { - c = getword(imapd_in, &arg); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - name = ucase(arg.s); - if (c != ' ') goto fail; -@@ -8858,7 +8858,7 @@ static int parsecreateargs(struct dlist **extargs) - do { - c = getword(imapd_in, &val); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - dlist_setatom(sub, name, val.s); - } while (c == ' '); -@@ -8929,7 +8929,7 @@ static int parse_annotate_fetch_data(const char *tag, - else - c = getqstring(imapd_in, imapd_out, &arg); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -8982,7 +8982,7 @@ static int parse_annotate_fetch_data(const char *tag, - else - c = getqstring(imapd_in, imapd_out, &arg); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -9062,7 +9062,7 @@ static int parse_metadata_string_or_list(const char *tag, - do { - c = getastring(imapd_in, imapd_out, &arg); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -9168,7 +9168,7 @@ static int parse_annotate_store_data(const char *tag, - else - c = getqstring(imapd_in, imapd_out, &entry); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -9212,7 +9212,7 @@ static int parse_annotate_store_data(const char *tag, - } - - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - /* add the attrib-value pair to the list */ - appendattvalue(&attvalues, attrib.s, &value); -@@ -9290,7 +9290,7 @@ static int parse_metadata_store_data(const char *tag, - /* get entry */ - c = getastring(imapd_in, imapd_out, &entry); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c != ' ') { - prot_printf(imapd_out, -@@ -9305,7 +9305,7 @@ static int parse_metadata_store_data(const char *tag, - /* get value */ - c = getbnstring(imapd_in, imapd_out, &value); - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (c == IMAP_LITERAL_TOO_LARGE) goto maxliteral; - if (c == EOF) { - prot_printf(imapd_out, -@@ -11548,7 +11548,7 @@ static int getsortcriteria(char *tag, struct sortcrit **sortcrit) - n = 0; - for (;;) { - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - - if (n >= nsort - 1) { /* leave room for implicit criterion */ - /* (Re)allocate an array for sort criteria */ -@@ -11700,7 +11700,7 @@ static int getlistselopts(char *tag, struct listargs *args) - c = getword(imapd_in, &buf); - - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (!*buf.s) { - prot_printf(imapd_out, - "%s BAD Invalid syntax in List command\r\n", -@@ -11806,7 +11806,7 @@ static int getlistretopts(char *tag, struct listargs *args) - c = getword(imapd_in, &buf); - - if (prot_bytes_in(imapd_in) > maxargssize_mark) -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - if (!*buf.s) { - prot_printf(imapd_out, - "%s BAD Invalid syntax in List command\r\n", tag); -diff --git a/imap/imapparse.c b/imap/imapparse.c -index 1fdb8b312..5646f8812 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -74,7 +74,7 @@ EXPORTED int getword(struct protstream *in, struct buf *buf) - } - buf_putc(buf, c); - if (config_maxword && buf_len(buf) > config_maxword) { -- fatal("[TOOBIG] Word too long", EX_IOERR); -+ fatal("[TOOBIG] Word too long", EX_PROTOCOL); - } - } - } -@@ -138,7 +138,7 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - } - buf_putc(buf, c); - if (config_maxquoted && buf_len(buf) > config_maxquoted) { -- fatal("[TOOBIG] Quoted value too long", EX_IOERR); -+ fatal("[TOOBIG] Quoted value too long", EX_PROTOCOL); - } - } - -@@ -157,11 +157,11 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - /* LITERAL- says maximum size is 4096! */ - if (lminus && len > 4096) { - /* Fail per RFC 7888, Section 4, choice 2 */ -- fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_LITERAL_MINUS_TOO_LARGE), EX_PROTOCOL); - } - if (config_maxliteral && len >= 0 && (unsigned) len > config_maxliteral) { - /* Fail per RFC 7888, Section 4, choice 2 */ -- fatal(error_message(IMAP_LITERAL_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_LITERAL_TOO_LARGE), EX_PROTOCOL); - } - isnowait++; - c = prot_getc(pin); -@@ -225,7 +225,7 @@ EXPORTED int getxstring(struct protstream *pin, struct protstream *pout, - } - buf_putc(buf, c); - if (config_maxword && buf_len(buf) > config_maxword) { -- fatal("[TOOBIG] Word too long", EX_IOERR); -+ fatal("[TOOBIG] Word too long", EX_PROTOCOL); - } - c = prot_getc(pin); - } -@@ -284,7 +284,7 @@ EXPORTED int getint32(struct protstream *pin, int32_t *num) - /* INT_MAX == 2147483647 */ - while ((c = prot_getc(pin)) != EOF && cyrus_isdigit(c)) { - if (result > 214748364 || (result == 214748364 && (c > '7'))) -- fatal("num too big", EX_IOERR); -+ fatal("num too big", EX_PROTOCOL); - result = result * 10 + c - '0'; - gotchar = 1; - } -@@ -337,7 +337,7 @@ EXPORTED int getuint32(struct protstream *pin, uint32_t *num) - /* UINT_MAX == 4294967295U */ - while ((c = prot_getc(pin)) != EOF && cyrus_isdigit(c)) { - if (result > 429496729 || (result == 429496729 && (c > '5'))) -- fatal("num too big", EX_IOERR); -+ fatal("num too big", EX_PROTOCOL); - result = result * 10 + c - '0'; - gotchar = 1; - } -@@ -361,7 +361,7 @@ EXPORTED int getint64(struct protstream *pin, int64_t *num) - /* LLONG_MAX == 9223372036854775807LL */ - while ((c = prot_getc(pin)) != EOF && cyrus_isdigit(c)) { - if (result > 922337203685477580LL || (result == 922337203685477580LL && (c > '7'))) -- fatal("num too big", EX_IOERR); -+ fatal("num too big", EX_PROTOCOL); - result = result * 10 + c - '0'; - gotchar = 1; - } -@@ -414,7 +414,7 @@ EXPORTED int getuint64(struct protstream *pin, uint64_t *num) - /* ULLONG_MAX == 18446744073709551615ULL */ - while ((c = prot_getc(pin)) != EOF && cyrus_isdigit(c)) { - if (result > 1844674407370955161ULL || (result == 1844674407370955161ULL && (c > '5'))) -- fatal("num too big", EX_IOERR); -+ fatal("num too big", EX_PROTOCOL); - result = result * 10 + c - '0'; - gotchar = 1; - } -@@ -636,7 +636,7 @@ EXPORTED int get_search_return_opts(struct protstream *pin, - - if (searchargs->maxargssize_mark && - prot_bytes_in(pin) > searchargs->maxargssize_mark) { -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - } - - } while (c == ' '); -@@ -1389,7 +1389,7 @@ static int get_search_criterion(struct protstream *pin, - - if (base->maxargssize_mark && - prot_bytes_in(pin) > base->maxargssize_mark) { -- fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_IOERR); -+ fatal(error_message(IMAP_ARGS_TOO_LARGE), EX_PROTOCOL); - } - - if (!keep_charset) --- -2.39.2 -