aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHajimu UMEMOTO <ume@FreeBSD.org>2024-06-05 12:18:30 +0000
committerHajimu UMEMOTO <ume@FreeBSD.org>2024-06-05 12:21:54 +0000
commit09c8412feede7a96b9fc14df16f8dd8730c61c84 (patch)
tree07582d2ab7f5b472fef94b095fa86e037452dcb2
parentf770ff0c0dcc788d58a44176ff990068d96523aa (diff)
downloadports-09c8412feede7a96b9fc14df16f8dd8730c61c84.tar.gz
ports-09c8412feede7a96b9fc14df16f8dd8730c61c84.zip
mail/cyrus-imapd34: update to 3.4.8
-rw-r--r--mail/cyrus-imapd34/Makefile6
-rw-r--r--mail/cyrus-imapd34/distinfo6
-rw-r--r--mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch5815
3 files changed, 5 insertions, 5822 deletions
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 <rsto@fastmailteam.com>
-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 <rsto@fastmailteam.com>
----
- 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], "<b>$term</b>"), -1);
-+ $r = $self->get_snippets('INBOX', $uids, { text => $term });
-+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "<mark>$term</mark>"), -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], "<b>naïve</b>"), -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], "<mark>naïve</mark>"), -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 = "<b>favourite</b> <b>cereal</b>";
-+ my $want = "<mark>favourite</mark> <mark>cereal</mark>";
-
-- $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], "<b>$term</b>"), -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], "<mark>$term</mark>"), -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("<b>Test1</b> body with the same tag as snippets", $m{body});
-- $self->assert_str_equals("<b>Test1</b> subject with an unescaped &amp; in it", $m{subject});
--
-- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ],
-- 'utf-8', [ 'fuzzy', 'text', 'test2' ]
-- ) || die;
-+ $self->assert_str_equals("<mark>Test1</mark> body with the same tag as snippets", $m{body});
-+ $self->assert_str_equals("<mark>Test1</mark> subject with an unescaped &amp; in it", $m{subject});
-
-+ $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test2' });
- %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} };
-- $self->assert_str_equals("<b>Test2</b> body with a &lt;tag/&gt;, although it's plain text", $m{body});
-- $self->assert_str_equals("<b>Test2</b> subject with a &lt;tag&gt; in it", $m{subject});
-+ $self->assert_str_equals("<mark>Test2</mark> body with a &lt;tag/&gt;, although it's plain text", $m{body});
-+ $self->assert_str_equals("<mark>Test2</mark> subject with a &lt;tag&gt; 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}, "<b>some text</b>") != -1);
-- $self->assert(index($m{body}, "<b>some</b> long <b>text</b>") == -1);
-+ $self->assert(index($m{body}, "<mark>some text</mark>") != -1);
-+ $self->assert(index($m{body}, "<mark>some</mark> long <mark>text</mark>") == -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 <b>atmeten</b>.'));
-+ $r = $self->get_snippets('INBOX', $uids, { body => 'atmet' });
-+use utf8;
-+ $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe <mark>atmeten</mark>.'));
-+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 <b>Landschaften</b>',
-+ 'A subject with the German word <mark>Landschaften</mark>',
- $r->{snippets}[0][3]
- );
- }
---
-2.39.2
-
-
-From 00aafb0fd51aaac1badc3370a250605cff4313b0 Mon Sep 17 00:00:00 2001
-From: Bron Gondwana <brong@fastmail.fm>
-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 <brong@fastmail.fm>
-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 <brong@fastmail.fm>
-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 <ellie@fastmail.com>
-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 <wolfsage@gmail.com>
-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 <wolfsage@gmail.com>
----
- 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 <ellie@fastmail.com>
-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 (<STDIN>) {
- # 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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <mboxname> '('<warmup-items>')' */
-
- 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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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 = <<EOF;
-+Subject: foo
-+Date: bar
-+From: <foobar\@example.com>
-+
-+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 <murch@fastmail.com>
-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 <murch@fastmail.com>
-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
-