git: 51303a9aa6d5 - main - mail/cyrus-imapd3[468]: vulnerability fix.
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 05 Jun 2024 10:20:29 UTC
The branch main has been updated by ume: URL: https://cgit.FreeBSD.org/ports/commit/?id=51303a9aa6d52fa38602579485f09d2d73fc39a0 commit 51303a9aa6d52fa38602579485f09d2d73fc39a0 Author: Hajimu UMEMOTO <ume@FreeBSD.org> AuthorDate: 2024-06-05 10:15:09 +0000 Commit: Hajimu UMEMOTO <ume@FreeBSD.org> CommitDate: 2024-06-05 10:19:49 +0000 mail/cyrus-imapd3[468]: vulnerability fix. Security: CVE-2024-34055 --- mail/cyrus-imapd34/Makefile | 4 +- mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch | 5815 +++++++++++++++++++++ mail/cyrus-imapd36/Makefile | 4 +- mail/cyrus-imapd36/files/v36-CVE-2024-34055.patch | 5348 +++++++++++++++++++ mail/cyrus-imapd38/Makefile | 4 +- mail/cyrus-imapd38/files/v38-CVE-2024-34055.patch | 5402 +++++++++++++++++++ 6 files changed, 16574 insertions(+), 3 deletions(-) diff --git a/mail/cyrus-imapd34/Makefile b/mail/cyrus-imapd34/Makefile index d5bedaead1d9..35a8252929ee 100644 --- a/mail/cyrus-imapd34/Makefile +++ b/mail/cyrus-imapd34/Makefile @@ -1,6 +1,6 @@ PORTNAME= cyrus-imapd PORTVERSION= 3.4.7 -PORTREVISION= 0 +PORTREVISION= 1 CATEGORIES= mail MASTER_SITES= https://github.com/cyrusimap/cyrus-imapd/releases/download/${PORTNAME}-${PORTVERSION}/ PKGNAMESUFFIX= ${CYRUS_IMAPD_VER} @@ -20,6 +20,8 @@ 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/files/v34-CVE-2024-34055.patch b/mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch new file mode 100644 index 000000000000..c1719ea49b28 --- /dev/null +++ b/mail/cyrus-imapd34/files/v34-CVE-2024-34055.patch @@ -0,0 +1,5815 @@ +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 & 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 & 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 <tag/>, although it's plain text", $m{body}); +- $self->assert_str_equals("<b>Test2</b> subject with a <tag> in it", $m{subject}); ++ $self->assert_str_equals("<mark>Test2</mark> body with a <tag/>, although it's plain text", $m{body}); ++ $self->assert_str_equals("<mark>Test2</mark> subject with a <tag> in it", $m{subject}); + } + + sub test_search_exactmatch +@@ -963,13 +921,10 @@ sub test_search_exactmatch + $self->assert_num_equals(1, scalar @$uids); + + my %m; +- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], +- 'utf-8', [ 'fuzzy', 'body', $query ] +- ) || die; +- ++ $r = $self->get_snippets('INBOX', $uids, { body => $query }); + %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; +- $self->assert(index($m{body}, "<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 *** 15682 LINES SKIPPED ***