diff --git a/mail/cyrus-imapd38/Makefile b/mail/cyrus-imapd38/Makefile index 9bbb720228d3..4154e29c5afa 100644 --- a/mail/cyrus-imapd38/Makefile +++ b/mail/cyrus-imapd38/Makefile @@ -1,268 +1,266 @@ PORTNAME= cyrus-imapd -PORTVERSION= 3.8.2 -PORTREVISION= 1 +PORTVERSION= 3.8.3 +PORTREVISION= 0 CATEGORIES= mail MASTER_SITES= https://github.com/cyrusimap/cyrus-imapd/releases/download/${PORTNAME}-${DISTVERSION}/ PKGNAMESUFFIX= ${CYRUS_IMAPD_VER} MAINTAINER= ume@FreeBSD.org COMMENT= Cyrus mail server, supporting POP3 and IMAP4 protocols ${COMMENT_${FLAVOR}} WWW= https://www.cyrusimap.org/ COMMENT_http= (with HTTP) LICENSE= BSD4CLAUSE LICENSE_FILE= ${WRKSRC}/COPYING BROKEN_riscv64= fails to build: lib/chartable.c: Error 1 FLAVORS= basic http http_PKGNAMESUFFIX= ${CYRUS_IMAPD_VER}-http CYRUS_IMAPD_VER= 38 -EXTRA_PATCHES= ${FILESDIR}/v38-CVE-2024-34055.patch:-p1 - LIB_DEPENDS= libsasl2.so:security/cyrus-sasl2 \ libicuuc.so:devel/icu \ libjansson.so:devel/jansson \ libuuid.so:misc/e2fsprogs-libuuid \ libical.so:devel/libical CONFLICTS_INSTALL= cyrus-imapd2? cyrus-imapd3[0-57-] cyrus-imapd3[0-57-]-http USES= compiler:c11 cpe gmake libtool perl5 pkgconfig ssl USE_RC_SUBR= imapd CYRUS_PREFIX= ${PREFIX}/cyrus GNU_CONFIGURE= yes CONFIGURE_ARGS= --libexecdir=${CYRUS_PREFIX}/libexec \ --sbindir=${CYRUS_PREFIX}/sbin \ --sysconfdir=${PREFIX}/etc \ --with-cyrus-user=${CYRUS_USER} \ --with-sasl=${LOCALBASE} \ --with-com_err \ --with-openssl=${OPENSSLBASE} \ --with-perl=${PERL} CONFIGURE_ENV+= LIBS="-L${LOCALBASE}/lib" CPPFLAGS+= -I${LOCALBASE}/include MAKE_JOBS_UNSAFE= yes USES+= shebangfix SHEBANG_FILES= imap/cyr_cd.sh imap/promdatagen tools/config2header \ tools/masssievec tools/mkimap tools/translatesieve \ perl/sieve/scripts/*.pl CPE_VENDOR= cmu CPE_PRODUCT= cyrus_imap_server OPTIONS_DEFINE= AUTOCREATE BACKUP CLAMAV CLD2 HTTP IDLED LDAP MURDER \ MYSQL NNTP PCRE2 PGSQL REPLICATION SQLITE SQUAT SRS \ XAPIAN DOCS OPTIONS_DEFAULT= AUTOCREATE IDLED READLINE_GNU SQLITE SQUAT SRS .if ${FLAVOR:U} == http OPTIONS_DEFAULT+= HTTP .endif OPTIONS_SUB= yes AUTOCREATE_DESC= Enable autocreate support AUTOCREATE_CONFIGURE_ENABLE= autocreate BACKUP_DESC= Enable backup support (experimental) BACKUP_CONFIGURE_ENABLE=backup CLAMAV_DESC= Use ClamAV CLAMAV_CONFIGURE_WITH= clamav CLAMAV_LIB_DEPENDS= libclamav.so:security/clamav CLD2_DESC= Use CLD2 CLD2_CONFIGURE_WITH= cld2 CLD2_CONFIGURE_ENV= CLD2_CFLAGS="-I${LOCALBASE}/include" \ CLD2_LIBS="-L${LOCALBASE}/lib -lcld2" CLD2_LIB_DEPENDS= libcld2.so:devel/cld2 HTTP_DESC= Enable HTTP support HTTP_IMPLIES= SQLITE HTTP_CONFIGURE_ENABLE= http HTTP_LIB_DEPENDS= libnghttp2.so:www/libnghttp2 \ libshp.so:devel/shapelib \ libbrotlidec.so:archivers/brotli \ libwslay_shared.so:www/wslay \ libzstd.so:archivers/zstd HTTP_CONFIGURE_ENV= WSLAY_CFLAGS="-I${LOCALBASE}/include" \ WSLAY_LIBS="-L${LOCALBASE}/lib -lwslay_shared" # Need additional patch to opendkim #HTTP_LIB_DEPENDS+= libopendkim.so:mail/opendkim #HTTP_CPPFLAGS+= -I${LOCALBASE}/include/opendkim HTTP_USES= gnome HTTP_USE= GNOME=libxml2 IDLED_DESC= Enable IMAP idled support IDLED_CONFIGURE_ENABLE= idled LDAP_DESC= Enable LDAP support (experimental) LDAP_USES= ldap LDAP_CONFIGURE_ON= --with-ldap=${LOCALBASE} LDAP_CONFIGURE_OFF= --without-ldap MURDER_DESC= Enable IMAP Murder support MURDER_CONFIGURE_ENABLE=murder MURDER_MAKE_ENV= PTHREAD_LIBS="-lpthread" MYSQL_USES= mysql MYSQL_CONFIGURE_WITH= mysql NNTP_DESC= Enable NNTP support NNTP_CONFIGURE_ENABLE= nntp PCRE2_DESC= Use PCRE2 rather than PCRE PCRE2_LIB_DEPENDS= libpcre2-posix.so:devel/pcre2 PCRE2_LIB_DEPENDS_OFF= libpcre.so:devel/pcre PCRE2_CONFIGURE_ON= --disable-pcre PCRE2_CONFIGURE_OFF= --disable-pcre2 PGSQL_USES= pgsql PGSQL_CONFIGURE_ON= --with-pgsql=${LOCALBASE} PGSQL_CONFIGURE_OFF= --without-pgsql REPLICATION_DESC= Enable replication (experimental) REPLICATION_CONFIGURE_ENABLE=replication SRS_DESC= Enable Sender Rewriting Scheme support SRS_CONFIGURE_ENABLE= srs SRS_LIB_DEPENDS= libsrs2.so:mail/libsrs2 SQLITE_USES= sqlite SQLITE_CONFIGURE_ON= --with-sqlite=${LOCALBASE} SQLITE_CONFIGURE_OFF= --without-sqlite SQLITE_BROKEN_OFF= SQLITE is required SQUAT_DESC= Enable Squat support SQUAT_CONFIGURE_OFF= --disable-squat XAPIAN_DESC= Enable Xapian support XAPIAN_CONFIGURE_ENABLE=xapian XAPIAN_LIB_DEPENDS= libxapian.so:databases/xapian-core XAPIAN_BUILD_DEPENDS= rsync:net/rsync XAPIAN_RUN_DEPENDS= ${XAPIAN_BUILD_DEPENDS} OPTIONS_RADIO= GSSAPI READLINE OPTIONS_RADIO_GSSAPI= GSSAPI_HEIMDAL GSSAPI_MIT .if exists(/usr/lib/libkrb5.a) OPTIONS_RADIO_GSSAPI+= GSSAPI_BASE OPTIONS_DEFAULT+= GSSAPI_BASE .endif GSSAPI_BASE_USES= gssapi GSSAPI_BASE_CONFIGURE_ON= --enable-gssapi="${GSSAPIBASEDIR}" \ --with-gss_impl=heimdal GSSAPI_HEIMDAL_USES= gssapi:heimdal,flags GSSAPI_HEIMDAL_CONFIGURE_ON= --enable-gssapi="${GSSAPIBASEDIR}" \ --with-gss_impl=heimdal GSSAPI_MIT_USES= gssapi:mit GSSAPI_MIT_CONFIGURE_ON= --enable-gssapi="${GSSAPIBASEDIR}" \ --with-gss_impl=mit OPTIONS_RADIO_READLINE= READLINE_GNU READLINE_PERL READLINE_GNU_DESC= Use Term::Readline::GNU for cyradm READLINE_GNU_RUN_DEPENDS= p5-Term-ReadLine-Gnu>=0:devel/p5-Term-ReadLine-Gnu READLINE_PERL_DESC= Use Term::Readline::Perl for cyradm READLINE_PERL_RUN_DEPENDS= p5-Term-ReadLine-Perl>=0:devel/p5-Term-ReadLine-Perl MANDIRS= ${CYRUS_PREFIX}/man PORTDOCS= * SUB_FILES= pkg-message pkg-install pkg-deinstall cyrus-imapd-man.conf SUB_LIST= CYRUS_USER=${CYRUS_USER} CYRUS_GROUP=${CYRUS_GROUP} CYRUS_USER?= cyrus CYRUS_GROUP?= cyrus MAN_MAN1= httptest imtest installsieve lmtptest mupdatetest nntptest \ pop3test sieveshell sivtest smtptest synctest MAN_MAN3= imclient MAN_MAN5= cyrus.conf imapd.conf krb.equiv CYRUS_MAN8= arbitron backupd chk_cyrus ctl_backups ctl_conversationsdb \ ctl_cyrusdb ctl_deliver ctl_mboxlist cvt_cyrusdb \ cvt_xlist_specialuse cyr_backup cyr_buildinfo cyr_dbtool \ cyr_deny cyr_df cyr_expire cyr_info cyr_ls cyr_synclog \ cyr_userseen cyr_virusscan cyradm cyrdump deliver fud idled \ imapd ipurge lmtpd lmtpproxyd master mbexamine mbpath mbtool \ notifyd pop3d pop3proxyd promstatsd proxyd ptdump ptexpire \ ptloader quota reconstruct relocate_by_id restore sievec \ sieved smmapd timsieved tls_prune unexpunge CYRUS_PERL_MAN1=cyradm CYRUS_PERL_MAN3=Cyrus::Annotator::Daemon Cyrus::Annotator::Message \ Cyrus::IMAP Cyrus::IMAP::Admin Cyrus::IMAP::Shell \ Cyrus::SIEVE::managesieve INSTALL_TARGET= install-strip REINPLACE_ARGS= -i '' .include .if !${PORT_OPTIONS:MGSSAPI_BASE} && !${PORT_OPTIONS:MGSSAPI_HEIMDAL} && \ !${PORT_OPTIONS:MGSSAPI_MIT} CONFIGURE_ARGS+=--disable-gssapi .endif .if ${PORT_OPTIONS:MHTTP} CYRUS_MAN8+= ctl_zoneinfo httpd MAN_MAN1+= dav_reconstruct .endif .if ${PORT_OPTIONS:MNNTP} CYRUS_MAN8+= fetchnews nntpd .endif .if ${PORT_OPTIONS:MMURDER} CYRUS_MAN8+= mupdate .endif .if ${PORT_OPTIONS:MREPLICATION} CYRUS_MAN8+= sync_client sync_reset sync_server .endif .if ${PORT_OPTIONS:MSQUAT} || ${PORT_OPTIONS:MXAPIAN} CYRUS_MAN8+= squatter PLIST_SUB+= SQUATTER="" .else PLIST_SUB+= SQUATTER="@comment " .endif post-patch: @${REINPLACE_CMD} -e "s|/etc/|${PREFIX}/etc/|" \ -e "s|%%CYRUS_USER%%|${CYRUS_USER}|g" \ -e "s|%%CYRUS_GROUP%%|${CYRUS_GROUP}|g" \ ${WRKSRC}/tools/mkimap .for f in masssievec translatesieve @${REINPLACE_CMD} -e "s|/etc/|${PREFIX}/etc/|g" \ -e "s|/usr/sieve|/var/imap/sieve|g" \ ${WRKSRC}/tools/${f} .endfor @${REINPLACE_CMD} \ -e 's|$$(libdir)/\(pkgconfig\)|${PREFIX}/libdata/\1|g' \ -e 's|$$(mandir)/\(man[8]\)|${PREFIX}/cyrus/man/\1|g' \ ${WRKSRC}/Makefile.in post-install: ${STRIP_CMD} ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/Cyrus/IMAP/IMAP.so ${STRIP_CMD} ${STAGEDIR}${PREFIX}/${SITE_ARCH_REL}/auto/Cyrus/SIEVE/managesieve/managesieve.so ${MKDIR} ${STAGEDIR}${EXAMPLESDIR} ${INSTALL_DATA} ${FILESDIR}/imapd.conf \ ${STAGEDIR}${EXAMPLESDIR} ${SED} -e 's,/run/cyrus/socket,/var/imap/socket,' \ ${WRKSRC}/doc/examples/cyrus_conf/normal.conf \ > ${STAGEDIR}${EXAMPLESDIR}/cyrus.conf .if !${PORT_OPTIONS:MHTTP} ${REINPLACE_CMD} -e 's/^\( http\)/#\1/' \ ${STAGEDIR}${EXAMPLESDIR}/cyrus.conf .endif .for f in mkimap masssievec translatesieve ${INSTALL_SCRIPT} ${WRKSRC}/tools/${f} \ ${STAGEDIR}${CYRUS_PREFIX}/sbin/${f} .endfor ${INSTALL_DATA} ${WRKDIR}/cyrus-imapd-man.conf \ ${STAGEDIR}${PREFIX}/etc/man.d/cyrus-imapd.conf .for s in 1 3 5 . for m in ${MAN_MAN${s}} @${ECHO_CMD} share/man/man${s}/${m}.${s}.gz >> ${TMPPLIST} . endfor .endfor .for s in 1 3 . for m in ${CYRUS_PERL_MAN${s}} @${ECHO_CMD} ${SITE_MAN${s}}/${m}.${s}.gz >> ${TMPPLIST} . endfor .endfor .for m in ${CYRUS_MAN8} @${ECHO_CMD} ${CYRUS_PREFIX}/man/man8/${m}.8.gz >> ${TMPPLIST} .endfor post-install-DOCS-on: ${MKDIR} ${STAGEDIR}${DOCSDIR} cd ${WRKSRC}/doc && ${COPYTREE_SHARE} . ${STAGEDIR}${DOCSDIR} \ "! ( -path */html/_sources* -o -name .buildinfo )" ${RM} -r ${STAGEDIR}${DOCSDIR}/rst ${STAGEDIR}${DOCSDIR}/source .include diff --git a/mail/cyrus-imapd38/distinfo b/mail/cyrus-imapd38/distinfo index b24dd575b039..f6b51abc78f6 100644 --- a/mail/cyrus-imapd38/distinfo +++ b/mail/cyrus-imapd38/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1710499223 -SHA256 (cyrus-imapd-3.8.2.tar.gz) = 67b17217e061096ed53dbb9005c33182d03deaf4e15025aea029fe6a62a81ff6 -SIZE (cyrus-imapd-3.8.2.tar.gz) = 14394338 +TIMESTAMP = 1717583501 +SHA256 (cyrus-imapd-3.8.3.tar.gz) = 95fc1a8c5355ac42691aea1d7226a5553ba024e4dcc9d4e22020a30033311e35 +SIZE (cyrus-imapd-3.8.3.tar.gz) = 14450246 diff --git a/mail/cyrus-imapd38/files/v38-CVE-2024-34055.patch b/mail/cyrus-imapd38/files/v38-CVE-2024-34055.patch deleted file mode 100644 index 0b36c7510e06..000000000000 --- a/mail/cyrus-imapd38/files/v38-CVE-2024-34055.patch +++ /dev/null @@ -1,5402 +0,0 @@ -From 6dc5cbcf9b72fa7e437e4bbcce1c7024f386d454 Mon Sep 17 00:00:00 2001 -From: Robert Stepanek -Date: Wed, 3 Jan 2024 09:51:36 +0100 -Subject: [PATCH 01/16] SearchFuzzy.pm: do not use non-standard XSNIPPETS - command - -The XSNIPPETS and XCONVMULTISTANDARD commands in Cyrus got -deprecated, so don't keep our test using it. - -Signed-off-by: Robert Stepanek ---- - cassandane/Cassandane/Cyrus/SearchFuzzy.pm | 344 +++++++++------------ - 1 file changed, 146 insertions(+), 198 deletions(-) - -diff --git a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -index e40564153..56d39ef25 100644 ---- a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -+++ b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm -@@ -43,6 +43,8 @@ use warnings; - use Cwd qw(abs_path); - use DateTime; - use Data::Dumper; -+use MIME::Base64 qw(encode_base64); -+use Encode qw(decode encode); - - use lib '.'; - use base qw(Cassandane::Cyrus::TestCase); -@@ -50,10 +52,19 @@ use Cassandane::Util::Log; - - sub new - { -+ - my ($class, @args) = @_; - my $config = Cassandane::Config->default()->clone(); -- $config->set(conversations => 'on'); -- return $class->SUPER::new({ config => $config }, @args); -+ $config->set( -+ conversations => 'on', -+ httpallowcompress => 'no', -+ httpmodules => 'jmap', -+ ); -+ return $class->SUPER::new({ -+ config => $config, -+ jmap => 1, -+ services => [ 'imap', 'http' ] -+ }, @args); - } - - sub set_up -@@ -134,6 +145,55 @@ sub create_testmessages - $self->{instance}->run_command({cyrus => 1}, 'squatter'); - } - -+sub get_snippets -+{ -+ # Previous versions of this test module used XSNIPPETS to -+ # assert snippets but this command got removed from Cyrus. -+ # Use JMAP instead. -+ -+ my ($self, $folder, $uids, $filter) = @_; -+ -+ my $imap = $self->{store}->get_client(); -+ my $jmap = $self->{jmap}; -+ -+ $self->assert_not_null($jmap); -+ -+ $imap->select($folder); -+ my $res = $imap->fetch($uids, ['emailid']); -+ my %emailIdToImapUid = map { $res->{$_}{emailid}[0] => $_ } keys %$res; -+ -+ $res = $jmap->CallMethods([ -+ ['SearchSnippet/get', { -+ filter => $filter, -+ emailIds => [ keys %emailIdToImapUid ], -+ }, 'R1'], -+ ]); -+ -+ my @snippets; -+ foreach (@{$res->[0][1]{list}}) { -+ if ($_->{subject}) { -+ push(@snippets, [ -+ 0, -+ $emailIdToImapUid{$_->{emailId}}, -+ 'SUBJECT', -+ $_->{subject}, -+ ]); -+ } -+ if ($_->{preview}) { -+ push(@snippets, [ -+ 0, -+ $emailIdToImapUid{$_->{emailId}}, -+ 'BODY', -+ $_->{preview}, -+ ]); -+ } -+ } -+ -+ return { -+ snippets => [ sort { $a->[1] <=> $b->[1] } @snippets ], -+ }; -+} -+ - sub test_copy_messages - :needs_search_xapian - { -@@ -151,12 +211,13 @@ sub test_copy_messages - } - - sub test_stem_verbs -- :min_version_3_0 :needs_search_xapian -+ :min_version_3_0 :needs_search_xapian :JMAPExtensions - { - my ($self) = @_; - $self->create_testmessages(); - - my $talk = $self->{store}->get_client(); -+ $self->assert_not_null($self->{jmap}); - - xlog $self, "Select INBOX"; - my $r = $talk->select("INBOX") || die; -@@ -175,11 +236,8 @@ sub test_stem_verbs - $r = $talk->search('fuzzy', ['subject', { Quote => "runs" }]) || die; - $self->assert_num_equals(3, scalar @$r); - -- xlog $self, 'XSNIPPETS for FUZZY subject "runs"'; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'subject', { Quote => 'runs' }] -- ) || die; -+ xlog $self, 'Get snippets for FUZZY subject "runs"'; -+ $r = $self->get_snippets('INBOX', $uids, { subject => 'runs' }); - $self->assert_num_equals(3, scalar @{$r->{snippets}}); - } - -@@ -250,12 +308,8 @@ sub test_snippet_wildcard - $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - -- xlog $self, "XSNIPPETS for $term"; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => "$term*" }] -- ) || die; -- xlog $self, Dumper($r); -+ xlog $self, "Get snippets for $term"; -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => "$term*" }); - $self->assert_num_equals(2, scalar @{$r->{snippets}}); - } - -@@ -358,13 +412,17 @@ sub test_normalize_snippets - my ($self) = @_; - - # Set up test message with funny characters -- my $body = "foo gären советской diĝir naïve léger"; -- my @terms = split / /, $body; -+use utf8; -+ my @terms = ( "gären", "советской", "diĝir", "naïve", "léger" ); -+no utf8; -+ my $body = encode_base64(encode('UTF-8', join(' ', @terms))); -+ $body =~ s/\r?\n/\r\n/gs; - - xlog $self, "Generate and index test messages."; - my %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("1", %params) || die; - -@@ -380,24 +438,20 @@ sub test_normalize_snippets - - # Assert that diacritics are matched and returned - foreach my $term (@terms) { -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); - } - - # Assert that search without diacritics matches - if ($self->{skipdiacrit}) { - my $term = "naive"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "naïve"), -1); -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => $term }); -+use utf8; -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "naïve"), -1); -+no utf8; - } -+ - } - - sub test_skipdiacrit -@@ -499,38 +553,23 @@ sub test_snippets_termcover - my $r = $talk->select("INBOX") || die; - my $uidvalidity = $talk->get_response_code('uidvalidity'); - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); -- my $want = "favourite cereal"; -+ my $want = "favourite cereal"; - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', 'favourite', -- 'fuzzy', 'text', 'cereal', -- 'fuzzy', 'text', { Quote => 'bogus gnarly' } -- ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { -+ operator => 'AND', -+ conditions => [{ -+ text => 'favourite', -+ }, { -+ text => 'cereal', -+ }, { -+ text => '"bogus gnarly"' -+ }], -+ }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', 'favourite cereal' -- ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); -- -- # Regression - a phrase is treated as a loose term -- $r = $talk->xsnippets( [ [ 'INBOX', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', { Quote => 'favourite nope cereal' }, -- 'fuzzy', 'text', { Quote => 'bogus gnarly' } -- ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); -- -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ -- 'fuzzy', 'text', { Quote => 'favourite cereal' } -- ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { -+ text => 'favourite cereal', -+ }); - $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], $want)); - } - -@@ -542,18 +581,28 @@ sub test_cjk_words - - xlog $self, "Generate and index test messages."; - -+use utf8; - my $body = "明末時已經有香港地方的概念"; -+no utf8; -+ $body = encode_base64(encode('UTF-8', $body)); -+ $body =~ s/\r?\n/\r\n/gs; - my %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("1", %params) || die; - - # Splits into the words: "み, 円, 月額, 申込 -+use utf8; - $body = "申込み!月額円"; -+no utf8; -+ $body = encode_base64(encode('UTF-8', $body)); -+ $body =~ s/\r?\n/\r\n/gs; - %params = ( - mime_charset => "utf-8", -- body => $body -+ mime_encoding => 'base64', -+ body => $body, - ); - $self->make_message("2", %params) || die; - -@@ -569,50 +618,45 @@ sub test_cjk_words - - my $term; - # Search for a two-character CJK word -+use utf8; - $term = "已經"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -- $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); -+ $self->assert_num_not_equals(index($r->{snippets}[0][3], "$term"), -1); - - # Search for the CJK words 明末 and 時, note that the - # word order is reversed to the original message -+use utf8; - $term = "時明末"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); - - # Search for the partial CJK word 月 -+use utf8; - $term = "月"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); - - # Search for the interleaved, partial CJK word 額申 -+use utf8; - $term = "額申"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 0); - - # Search for three of four words: "み, 月額, 申込", - # in different order than the original. -+use utf8; - $term = "月額み申込"; -- xlog $self, "XSNIPPETS for FUZZY text \"$term\""; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'text', { Quote => $term }] -- ) || die; -+no utf8; -+ xlog $self, "Get snippets for FUZZY text \"$term\""; -+ $r = $self->get_snippets('INBOX', $uids, { text => $term }); - $self->assert_num_equals(scalar @{$r->{snippets}}, 1); - } - -@@ -805,86 +849,6 @@ sub test_xattachmentname - } - - --sub test_xapianv2 -- :min_version_3_0 :needs_search_xapian --{ -- my ($self) = @_; -- -- my $talk = $self->{store}->get_client(); -- -- # This is a smallish regression test to check if we break something -- # obvious by moving Xapian indexing from folder:uid to message guids. -- # -- # Apart from the tests in this module, at least also the following -- # imodules are relevant: Metadata for SORT, Thread for THREAD. -- -- xlog $self, "Generate message"; -- my $r = $self->make_message("I run", body => "Run, Forrest! Run!" ) || die; -- my $uid = $r->{attrs}->{uid}; -- -- xlog $self, "Copy message into INBOX"; -- $talk->copy($uid, "INBOX"); -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(2, scalar @{$r->{sort}[0]} - 1); -- $self->assert_num_equals(1, scalar @{$r->{sort}}); -- -- xlog $self, "Create target mailbox"; -- $talk->create("INBOX.target"); -- -- xlog $self, "Copy message into INBOX.target"; -- $talk->copy($uid, "INBOX.target"); -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(3, scalar @{$r->{sort}[0]} - 1); -- $self->assert_num_equals(1, scalar @{$r->{sort}}); -- -- xlog $self, "Generate message"; -- $self->make_message("You run", body => "A running joke" ) || die; -- -- xlog $self, "Run squatter"; -- $self->{instance}->run_command({cyrus => 1}, 'squatter'); -- -- $r = $talk->xconvmultisort( -- [ qw(reverse arrival) ], -- [ 'conversations', position => [1,10] ], -- 'utf-8', 'fuzzy', 'text', "run", -- ); -- $self->assert_num_equals(2, scalar @{$r->{sort}}); -- -- xlog $self, "SEARCH FUZZY"; -- $r = $talk->search( -- "charset", "utf-8", "fuzzy", "text", "run", -- ) || die; -- $self->assert_num_equals(3, scalar @$r); -- -- xlog $self, "Select INBOX"; -- $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- my $uids = $talk->search('1:*', 'NOT', 'DELETED'); -- -- xlog $self, "XSNIPPETS"; -- $r = $talk->xsnippets( -- [['INBOX', $uidvalidity, $uids]], 'utf-8', -- ['fuzzy', 'body', 'run'], -- ) || die; -- $self->assert_num_equals(3, scalar @{$r->{snippets}}); --} -- - sub test_snippets_escapehtml - :min_version_3_0 :needs_search_xapian - { -@@ -914,21 +878,15 @@ sub test_snippets_escapehtml - my $uids = $talk->search('1:*', 'NOT', 'DELETED'); - my %m; - -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', 'test1' ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test1' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert_str_equals("Test1 body with the same tag as snippets", $m{body}); -- $self->assert_str_equals("Test1 subject with an unescaped & in it", $m{subject}); -- -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', 'test2' ] -- ) || die; -+ $self->assert_str_equals("Test1 body with the same tag as snippets", $m{body}); -+ $self->assert_str_equals("Test1 subject with an unescaped & in it", $m{subject}); - -+ $r = $self->get_snippets('INBOX', $uids, { 'text' => 'test2' }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert_str_equals("Test2 body with a <tag/>, although it's plain text", $m{body}); -- $self->assert_str_equals("Test2 subject with a <tag> in it", $m{subject}); -+ $self->assert_str_equals("Test2 body with a <tag/>, although it's plain text", $m{body}); -+ $self->assert_str_equals("Test2 subject with a <tag> in it", $m{subject}); - } - - sub test_search_exactmatch -@@ -963,13 +921,10 @@ sub test_search_exactmatch - $self->assert_num_equals(1, scalar @$uids); - - my %m; -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'body', $query ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { body => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; -- $self->assert(index($m{body}, "some text") != -1); -- $self->assert(index($m{body}, "some long text") == -1); -+ $self->assert(index($m{body}, "some text") != -1); -+ $self->assert(index($m{body}, "some long text") == -1); - } - - sub test_search_subjectsnippet -@@ -1004,10 +959,7 @@ sub test_search_subjectsnippet - $self->assert_num_equals(1, scalar @$uids); - - my %m; -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'text', $query ] -- ) || die; -- -+ $r = $self->get_snippets('INBOX', $uids, { text => $query }); - %m = map { lc($_->[2]) => $_->[3] } @{ $r->{snippets} }; - $self->assert_matches(qr/^\[plumbing\]/, $m{subject}); - } -@@ -1317,11 +1269,10 @@ sub test_detect_language - $self->assert_deep_equals([1], $uids); - - my $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'body', 'atmet' ] -- ) || die; -- $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe atmeten.')); -+ $r = $self->get_snippets('INBOX', $uids, { body => 'atmet' }); -+use utf8; -+ $self->assert_num_not_equals(-1, index($r->{snippets}[0][3], ' Höhe atmeten.')); -+no utf8; - } - - sub test_detect_language_subject -@@ -1377,12 +1328,9 @@ sub test_detect_language_subject - $self->assert_deep_equals([1], $uids); - - my $r = $talk->select("INBOX") || die; -- my $uidvalidity = $talk->get_response_code('uidvalidity'); -- $r = $talk->xsnippets( [ [ 'inbox', $uidvalidity, $uids ] ], -- 'utf-8', [ 'fuzzy', 'subject', 'Landschaft' ] -- ) || die; -+ $r = $self->get_snippets('INBOX', $uids, { subject => 'Landschaft' }); - $self->assert_str_equals( -- 'A subject with the German word Landschaften', -+ 'A subject with the German word Landschaften', - $r->{snippets}[0][3] - ); - } --- -2.39.2 - - -From d1b5eb32a3df564a2f79cc5e35230a344a632fc6 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 7 Feb 2024 14:00:00 -0500 -Subject: [PATCH 02/16] 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 991040241..46ac2df86 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -4302,15 +4302,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 == ' ') { -@@ -4450,7 +4446,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 54705d056..3178ebd04 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 ece8be8fc41d8faf8540fe5cf066cd47a09226b6 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 08:13:05 -0500 -Subject: [PATCH 03/16] 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 46ac2df86..fc8bd935a 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3123,19 +3123,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 8cd3fb31a672ed45cacb260605ac111d40ac8e69 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 7 Feb 2024 14:12:41 -0500 -Subject: [PATCH 04/16] 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 fc8bd935a..9f1ca2fe1 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -1190,7 +1190,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 6fc15e3b83cd0c2b71ed483330e2afa5497691ad Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 23 Feb 2024 11:00:19 -0500 -Subject: [PATCH 05/16] 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 5dc987a6e..edeb84215 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 42154ac9984811198f7de21d785e344d6191dce1 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 13:31:11 -0500 -Subject: [PATCH 06/16] 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 a98ec0e1b..072078f94 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 edeb84215..4499264ff 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 13fa309e77264ffbea6a0fc5efa41bbb5f1b7be5 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Mon, 12 Feb 2024 10:54:03 -0500 -Subject: [PATCH 07/16] 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 04715a561..d0ba24cf9 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 dbba85ca7..3f68b1aaa 100644 ---- a/backup/lcb.c -+++ b/backup/lcb.c -@@ -606,7 +606,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 f597c97c5..f2342d69d 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 a8dac204c..ff2758552 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) { -@@ -528,7 +527,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 f5a5989a3..12efe86aa 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 { \ -@@ -84,7 +81,6 @@ static int tear_down(void) - long long _consumed = (consumed); \ - 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 bfd30003a..69f3daf99 100644 ---- a/imap/append.c -+++ b/imap/append.c -@@ -438,7 +438,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, /*isarchive*/0, /*isbackup*/0, p); -diff --git a/imap/backend.c b/imap/backend.c -index 4cc7bc56b..0bbb65ed0 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 42e21e8a9..3534e9c92 100644 ---- a/imap/cyr_dbtool.c -+++ b/imap/cyr_dbtool.c -@@ -156,7 +156,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 1e38d1bfb..fd068f071 100644 ---- a/imap/dlist.c -+++ b/imap/dlist.c -@@ -1235,7 +1235,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, /*isarchive*/ 0, isbackup, stream); - prot_free(stream); - -diff --git a/imap/imapd.c b/imap/imapd.c -index 9f1ca2fe1..97b7732e5 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -959,6 +959,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 4499264ff..5da6af1b8 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 16985728a..0744cb1d0 100644 ---- a/imap/message.c -+++ b/imap/message.c -@@ -3419,7 +3419,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); -@@ -3427,7 +3426,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); -@@ -4792,7 +4790,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 65b78d9a4..6466200a6 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 b9917e87b..0febff288 100644 ---- a/imap/sync_server.c -+++ b/imap/sync_server.c -@@ -329,9 +329,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 99eb70dad..03ad16bee 100644 ---- a/imap/sync_support.c -+++ b/imap/sync_support.c -@@ -8094,10 +8094,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); -- - 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 6663e8e534d0395c797cf74d051f7d0ca3d62621 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 22:48:58 -0400 -Subject: [PATCH 08/16] 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 072078f94..d90e30a4b 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 97b7732e5..a301b554e 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3582,6 +3582,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 == '~') { -@@ -3602,6 +3605,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 986079279f972842602694a02f7d19dbd5228645 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 9 Feb 2024 14:52:22 -0500 -Subject: [PATCH 09/16] imapd.c: remove XSNIPPETS and XCONV* commands - ---- - cassandane/Cassandane/Cyrus/Conversations.pm | 71 -- - imap/imapd.c | 1006 ------------------ - 2 files changed, 1077 deletions(-) - -diff --git a/cassandane/Cassandane/Cyrus/Conversations.pm b/cassandane/Cassandane/Cyrus/Conversations.pm -index 8bdf8aa72..98369cb5c 100755 ---- a/cassandane/Cassandane/Cyrus/Conversations.pm -+++ b/cassandane/Cassandane/Cyrus/Conversations.pm -@@ -708,77 +708,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 a301b554e..0ea16979a 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -443,14 +443,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, -@@ -507,12 +499,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, -@@ -2298,32 +2286,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; -@@ -2349,9 +2311,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; - -@@ -2384,13 +2343,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); -@@ -5209,8 +5161,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", -@@ -5298,136 +5248,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) - { -@@ -5571,270 +5391,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->folders, 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; -- mbentry_t *mbentry = NULL; -- -- if (state->folders_byname) mboxname = folder_list.data[i]; -- else { -- mboxlist_lookup_by_uniqueid(folder_list.data[i], &mbentry, NULL); -- if (!mbentry) continue; -- mboxname = mbentry->name; -- } -- -- r = index_open(mboxname, &init, &index_state); -- mboxlist_entry_free(&mbentry); -- 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 */ - - /* -@@ -6417,314 +5973,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; -@@ -11124,81 +10372,6 @@ out_noprint: - 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; -@@ -12694,185 +11867,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 c34f94d56bc0c872b3ad2616fdf6cd346cf2d69e Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Mon, 26 Feb 2024 10:11:15 -0500 -Subject: [PATCH 10/16] imapd.c: add 'maxliteral' option - ---- - changes/next/imap_literal_limits | 19 +++ - imap/imap_err.et | 3 + - imap/imapd.c | 213 ++++++++++++++++++++++--------- - imap/imapparse.c | 73 ++++++----- - lib/imapoptions | 22 +++- - lib/libconfig.c | 7 + - lib/libconfig.h | 1 + - 7 files changed, 244 insertions(+), 94 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 d90e30a4b..5768f49d1 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 0ea16979a..000b9416d 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -1441,7 +1441,7 @@ static void cmdloop(void) - if (c != ' ' || (strcmp("$", arg1.s) && !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); -@@ -1454,7 +1454,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; -@@ -1481,7 +1481,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); - -@@ -1493,7 +1493,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); - -@@ -1545,7 +1545,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); -@@ -1578,7 +1578,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); - -@@ -1603,7 +1603,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); - -@@ -1612,7 +1612,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); - -@@ -1737,7 +1737,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; -@@ -1753,7 +1753,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); - -@@ -1766,7 +1766,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); - -@@ -1776,7 +1776,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); - -@@ -1794,7 +1794,7 @@ static void cmdloop(void) - if (c != ' ' || (strcmp("$", arg1.s) && !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); -@@ -1829,7 +1829,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); -@@ -1902,7 +1902,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); -@@ -1976,7 +1976,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); -@@ -2001,7 +2001,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); -@@ -2019,7 +2019,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); - -@@ -2220,7 +2220,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); -@@ -2367,7 +2367,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); - } -@@ -2379,7 +2379,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); -@@ -2391,7 +2391,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 == ' '); - } -@@ -2477,6 +2477,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); -@@ -2489,11 +2491,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); -@@ -2666,10 +2675,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; - } - -@@ -3084,10 +3097,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; - } - -@@ -3777,6 +3796,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; -@@ -3975,8 +3995,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); -@@ -4103,6 +4122,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); -@@ -4647,8 +4668,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 != ')') { -@@ -4761,6 +4781,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; - } -@@ -4991,6 +5016,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; - } -@@ -5506,8 +5536,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; -@@ -5748,6 +5777,11 @@ static void cmd_search(char *tag, char *cmd) - return; - } - -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Search\r\n", tag, error_message(c)); -+ goto done; -+ } -+ - if (!IS_EOL(c, imapd_in)) { - prot_printf(imapd_out, "%s BAD Unexpected extra arguments to Search\r\n", tag); - eatline(imapd_in, c); -@@ -6044,16 +6078,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); -@@ -6062,6 +6099,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; - } -@@ -7635,8 +7673,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; - } - } -@@ -7648,6 +7685,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", -@@ -7670,6 +7708,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; -@@ -7685,6 +7724,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", -@@ -7699,8 +7739,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; - } - } -@@ -7719,6 +7758,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); -@@ -9125,6 +9168,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); -@@ -9152,6 +9196,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); -@@ -9174,6 +9219,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); -@@ -9201,6 +9247,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); -@@ -9213,8 +9260,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; - } - - /* -@@ -9245,6 +9297,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); -@@ -9271,6 +9324,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); -@@ -9289,8 +9343,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; - } - - /* -@@ -9342,6 +9401,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); -@@ -9362,6 +9422,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); -@@ -9375,6 +9436,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); -@@ -9416,8 +9478,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; - } - - /* -@@ -9450,6 +9518,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); -@@ -9462,6 +9531,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); -@@ -9502,7 +9572,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; - } -@@ -9513,8 +9583,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, -@@ -9699,8 +9775,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; - } - -@@ -9934,8 +10009,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; - } - -@@ -10092,8 +10169,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; - } - -@@ -10160,8 +10236,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; - } - -@@ -10269,6 +10344,10 @@ static void cmd_xwarmup(const char *tag) - /* parse arguments: expect '('')' */ - - c = getastring(imapd_in, imapd_out, &arg); -+ if (c == IMAP_LITERAL_TOO_LARGE) { -+ prot_printf(imapd_out, "%s NO %s in Xwarmup\r\n", tag, error_message(c)); -+ goto out_noprint; -+ } - if (c != ' ') { - syntax_error: - prot_printf(imapd_out, "%s BAD syntax error in %s\r\n", tag, cmd); -@@ -11779,9 +11858,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 = ""; -@@ -11799,6 +11880,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); - } -@@ -11812,6 +11894,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); - } -@@ -11917,10 +12000,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", -@@ -11948,7 +12031,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; - } - -@@ -12018,7 +12101,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, -@@ -12039,7 +12122,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; - } - -@@ -13232,6 +13315,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); - -@@ -13464,6 +13552,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 5da6af1b8..17e0804ca 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); - } -@@ -628,6 +636,7 @@ EXPORTED int get_search_source_mboxes(struct protstream *pin, - - bad: - buf_free(&extname); -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c != EOF) prot_ungetc(c, pin); - return EOF; - } -@@ -691,6 +700,7 @@ EXPORTED int get_search_source_opts(struct protstream *pin, - - } while (c == ' '); - -+ if (c == IMAP_LITERAL_TOO_LARGE) return c; - if (c != ')') { - prot_printf(pout, - "%s BAD Missing close parenthesis in Search\r\n", -@@ -818,7 +828,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)); -@@ -839,6 +849,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; - } -@@ -971,7 +982,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); -@@ -1022,7 +1033,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"); -@@ -1043,23 +1054,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; - -@@ -1067,7 +1070,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 */ -@@ -1130,7 +1133,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; -@@ -1140,7 +1143,7 @@ static int get_search_criterion(struct protstream *pin, - if (!strcmp(criteria.s, "emailid")) { /* RFC 8474 */ - 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; -@@ -1153,7 +1156,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); -@@ -1161,7 +1164,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 */ -@@ -1169,7 +1172,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; -@@ -1180,7 +1183,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); -@@ -1197,7 +1200,7 @@ static int get_search_criterion(struct protstream *pin, - if ((base->state & GETSEARCH_SOURCE) && - !strcmp(criteria.s, "in")) { /* RFC 7377 */ - c = get_search_source_opts(pin, pout, base); -- if (c == EOF) return EOF; -+ if (c <= EOF) return c; - keep_charset = 1; - } - else goto badcri; -@@ -1252,7 +1255,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); -@@ -1267,10 +1270,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); -@@ -1389,6 +1392,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"); -@@ -1397,6 +1401,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"); -@@ -1405,7 +1410,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; -@@ -1415,19 +1420,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")) { /* RFC 8474 */ - 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; -@@ -1478,25 +1483,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; -@@ -1527,6 +1532,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; -@@ -1542,6 +1549,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 175604340..8810639b5 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -1717,13 +1717,31 @@ Blank lines and lines beginning with ``#'' are ignored. - .PP - If no unit is specified, bytes is assumed. */ - -+{ "maxliteral", "128K", BYTESIZE, "UNRELEASED" } -+/* Maximum size of a single literal allowed by the IMAP parser. -+.PP -+ If set to 0, a large internally-defined limit will be applied. -+.PP -+ If no unit is specified, bytes is assumed. -+.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", "128K", BYTESIZE, "3.8.0" } --/* Maximum size of a single quoted string for the parser. -+/* Maximum size of a single quoted string allowed by the IMAP parser. -+.PP -+ If set to 0, a large internally-defined limit will be applied. - .PP - If no unit is specified, bytes is assumed. */ - - { "maxword", "128K", BYTESIZE, "3.8.0" } --/* Maximum size of a single word for the parser. -+/* Maximum size of a single word allowed by the IMAP parser. -+.PP -+ If set to 0, a large internally-defined limit will be applied. - .PP - If no unit is specified, bytes is assumed. */ - -diff --git a/lib/libconfig.c b/lib/libconfig.c -index df75c1b99..4582988df 100644 ---- a/lib/libconfig.c -+++ b/lib/libconfig.c -@@ -86,6 +86,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; - -@@ -599,6 +600,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; -@@ -808,6 +810,11 @@ EXPORTED void config_read(const char *alt_config, const int config_need_data) - tok_fini(&tok); - - /* set some limits */ -+ i64val = config_getbytesize(IMAPOPT_MAXLITERAL, 'B'); -+ if (i64val <= 0 || i64val > BYTESIZE_UNLIMITED) { -+ i64val = BYTESIZE_UNLIMITED; -+ } -+ config_maxliteral = i64val; - i64val = config_getbytesize(IMAPOPT_MAXQUOTED, 'B'); - if (i64val <= 0 || i64val > BYTESIZE_UNLIMITED) { - i64val = BYTESIZE_UNLIMITED; -diff --git a/lib/libconfig.h b/lib/libconfig.h -index 08de0845b..7796ba511 100644 ---- a/lib/libconfig.h -+++ b/lib/libconfig.h -@@ -97,6 +97,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 5feff906c92200734da7cd654240bf45ab30d0a1 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 21 Feb 2024 11:18:52 -0500 -Subject: [PATCH 11/16] 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 3f68b1aaa..e1eefab89 100644 ---- a/backup/lcb.c -+++ b/backup/lcb.c -@@ -620,11 +620,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", -+ "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - 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 d398a06c0..b1616bd8f 100644 ---- a/backup/lcb_compact.c -+++ b/backup/lcb_compact.c -@@ -528,11 +528,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", -+ "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %" PRIu64 ": %s", - 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 ff2758552..347b9d37d 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); -@@ -540,10 +540,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 f5aae429f..ef9c314e9 100644 ---- a/imap/httpd.c -+++ b/imap/httpd.c -@@ -680,8 +680,8 @@ EXPORTED int http1_resp_body_chunk(struct transaction_t *txn, - 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; - - /* run any delayed actions */ - libcyrus_run_delayed(); -@@ -723,7 +723,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); - } - -@@ -1105,8 +1106,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; - -@@ -1175,7 +1176,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); - - saslprops_free(&saslprops); -diff --git a/imap/imapd.c b/imap/imapd.c -index 000b9416d..5e28e17fc 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -734,8 +734,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; - - /* run delayed commands first before closing anything */ - libcyrus_run_delayed(); -@@ -783,8 +783,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; - -@@ -1078,8 +1079,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; - -@@ -1146,8 +1147,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 ee5a6fad8..3db7e2315 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; - -@@ -646,8 +647,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 ed73edb806a442133eaf439ac1844c95ba450752 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Wed, 21 Feb 2024 11:35:44 -0500 -Subject: [PATCH 12/16] imapd.c: rename 'maxsize' to 'maxmsgsize' - ---- - imap/imapd.c | 12 ++++++------ - 1 file changed, 6 insertions(+), 6 deletions(-) - -diff --git a/imap/imapd.c b/imap/imapd.c -index 5e28e17fc..b883fcfd7 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 int64_t maxsize = 0; -+static int64_t maxmsgsize = 0; - - /* PROXY STUFF */ - /* we want a list of our outgoing connections here and which one we're -@@ -904,8 +904,8 @@ int service_init(int argc, char **argv, char **envp) - - prometheus_increment(CYRUS_IMAP_READY_LISTENERS); - -- maxsize = config_getbytesize(IMAPOPT_MAXMESSAGESIZE, 'B'); -- if (maxsize <= 0) maxsize = BYTESIZE_UNLIMITED; -+ maxmsgsize = config_getbytesize(IMAPOPT_MAXMESSAGESIZE, 'B'); -+ if (maxmsgsize <= 0) maxmsgsize = BYTESIZE_UNLIMITED; - - return 0; - } -@@ -3505,7 +3505,7 @@ static void capa_response(int flags) - prot_printf(imapd_out, " IDLE"); - } - -- prot_printf(imapd_out, " APPENDLIMIT=%" PRIi64, maxsize); -+ prot_printf(imapd_out, " APPENDLIMIT=%" PRIi64, maxmsgsize); - } - - /* -@@ -4025,13 +4025,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 4a321af3d3babe22853cd0421bff12cfe264f4cd Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Fri, 23 Feb 2024 15:08:42 -0500 -Subject: [PATCH 13/16] 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 | 9 +++ - 6 files changed, 116 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 5768f49d1..d1391199d 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 b883fcfd7..fb5dace4a 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 int64_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 -@@ -907,6 +909,9 @@ int service_init(int argc, char **argv, char **envp) - maxmsgsize = config_getbytesize(IMAPOPT_MAXMESSAGESIZE, 'B'); - if (maxmsgsize <= 0) maxmsgsize = BYTESIZE_UNLIMITED; - -+ maxargssize = config_getbytesize(IMAPOPT_MAXARGSSIZE, 'B'); -+ if (maxargssize <= 0) maxargssize = BYTESIZE_UNLIMITED; -+ - return 0; - } - -@@ -1356,6 +1361,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(); - -@@ -3942,6 +3950,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 */ -@@ -3950,6 +3961,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"; -@@ -4057,15 +4070,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) */ -@@ -4281,6 +4302,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; -@@ -4653,6 +4677,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': -@@ -4783,6 +4810,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); -@@ -5117,6 +5146,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 != ' ') { -@@ -5463,6 +5495,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 != ' ') { -@@ -5555,6 +5590,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; -@@ -5750,6 +5787,8 @@ static void cmd_search(char *tag, char *cmd) - &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); -@@ -5961,6 +6000,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++; - -@@ -6077,6 +6119,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, ' '); -@@ -7710,6 +7755,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); -@@ -8559,6 +8606,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)) { -@@ -8711,6 +8761,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; -@@ -9094,6 +9147,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); -@@ -9102,6 +9158,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; -@@ -9170,6 +9229,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, -@@ -9221,6 +9282,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, -@@ -9299,6 +9362,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, -@@ -9403,6 +9468,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, -@@ -9445,6 +9512,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); - -@@ -9520,6 +9590,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, -@@ -9533,6 +9605,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, -@@ -11821,6 +11895,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; -@@ -11970,6 +12047,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", -@@ -12074,6 +12153,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 dd1559a95..d8fe76016 100644 ---- a/imap/imapd.h -+++ b/imap/imapd.h -@@ -234,6 +234,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 17e0804ca..2dceaed94 100644 ---- a/imap/imapparse.c -+++ b/imap/imapparse.c -@@ -764,6 +764,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 == ' '); - - if (!(searchargs->returnopts & ~(SEARCH_RETURN_SAVE|SEARCH_RETURN_RELEVANCY))) { -@@ -1539,6 +1544,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; - if (base->state & GETSEARCH_SOURCE) -diff --git a/lib/imapoptions b/lib/imapoptions -index 8810639b5..731962510 100644 ---- a/lib/imapoptions -+++ b/lib/imapoptions -@@ -1708,6 +1708,15 @@ Blank lines and lines beginning with ``#'' are ignored. - /* Maximum number of logged in sessions allowed per user, - zero means no limit */ - -+{ "maxargssize", "0", BYTESIZE, "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. -+.PP -+ If set to 0 (the default), a large internally-defined limit will be applied. -+.PP -+ If no unit is specified, bytes is assumed. */ -+ - { "maxmessagesize", "0", BYTESIZE, "3.8.0" } - /* Maximum size of messages that will be accepted by Cyrus. This affects LMTP - deliveries, IMAP appends, DAV uploads, etc. Messages larger than this will --- -2.39.2 - - -From 57bf6856838765020f74d4c649c002f7525fccb5 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Tue, 12 Mar 2024 23:16:30 -0400 -Subject: [PATCH 14/16] Add IMAPLimits.pm - ---- - cassandane/Cassandane/Cyrus/IMAPLimits.pm | 529 ++++++++++++++++++++++ - cassandane/Cassandane/IMAPMessageStore.pm | 8 +- - 2 files changed, 534 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..9576d1d88 ---- /dev/null -+++ b/cassandane/Cassandane/Cyrus/IMAPLimits.pm -@@ -0,0 +1,529 @@ -+#!/usr/bin/perl -+# -+# Copyright (c) 2011-2023 FastMail Pty Ltd. All rights reserved. -+# -+# Redistribution and use in source and binary forms, with or without -+# modification, are permitted provided that the following conditions -+# are met: -+# -+# 1. Redistributions of source code must retain the above copyright -+# notice, this list of conditions and the following disclaimer. -+# -+# 2. Redistributions in binary form must reproduce the above copyright -+# notice, this list of conditions and the following disclaimer in -+# the documentation and/or other materials provided with the -+# distribution. -+# -+# 3. The name "Fastmail Pty Ltd" must not be used to -+# endorse or promote products derived from this software without -+# prior written permission. For permission or any legal -+# details, please contact -+# FastMail Pty Ltd -+# PO Box 234 -+# Collins St West 8007 -+# Victoria -+# Australia -+# -+# 4. Redistributions of any form whatsoever must retain the following -+# acknowledgment: -+# "This product includes software developed by Fastmail Pty. Ltd." -+# -+# FASTMAIL PTY LTD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO -+# EVENT SHALL OPERA SOFTWARE AUSTRALIA BE LIABLE FOR ANY SPECIAL, INDIRECT -+# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -+# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -+# OF THIS SOFTWARE. -+# -+ -+package Cassandane::Cyrus::IMAPLimits; -+use strict; -+use warnings; -+use Mail::JMAPTalk 0.13; -+use Data::Dumper; -+ -+use lib '.'; -+use base qw(Cassandane::Cyrus::TestCase); -+use Cassandane::Util::Log; -+ -+my $email = < -+ -+Body -+EOF -+ -+$email =~ s/\r?\n/\r\n/gs; -+ -+my $toobig_email = $email . "X" x 100; -+ -+sub assert_bye_toobig -+{ -+ my ($self, $store) = @_; -+ -+ $store = $self->{store} if (!defined $store); -+ -+ # We want to override Mail::IMAPTalk's builtin handling of the BYE -+ # untagged response, as it will 'die' immediately without parsing -+ # the remainder of the line and especially without picking out the -+ # [TOOBIG] response code that we want to see. -+ my $got_toobig = 0; -+ my $handlers = -+ { -+ bye => sub -+ { -+ my (undef, $resp) = @_; -+ $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -+ } -+ }; -+ -+ # Check that we got a BYE [TOOBIG] response -+ $store->idle_response($handlers, 1); -+ $self->assert_num_equals(1, $got_toobig); -+} -+ -+sub assert_cmd_bye_toobig -+{ -+ my $self = shift; -+ my $cmd = shift; -+ -+ my $talk = $self->{store}->get_client(); -+ $talk->enable('qresync'); # IMAPTalk requires lower-case -+ $talk->select('INBOX'); -+ -+ $talk->_send_cmd($cmd, @_); -+ $self->assert_bye_toobig(); -+} -+ -+sub assert_cmd_no_toobig -+{ -+ my $self = shift; -+ my $talk = shift; -+ my $cmd = shift; -+ -+ my $got_toobig = 0; -+ my $handlers = -+ { -+ 'no' => sub -+ { -+ # Pick out the [TOOBIG] response code -+ my (undef, $resp) = @_; -+ $got_toobig = 1 if (uc($resp->[0]) eq '[TOOBIG]'); -+ } -+ }; -+ -+ $talk->_imap_cmd($cmd, 0, $handlers, @_); -+ -+ # Check that we got a NO [TOOBIG] response -+ $self->assert_str_equals('no', $talk->get_last_completion_response()); -+ $self->assert_num_equals(1, $got_toobig); -+} -+ -+sub new -+{ -+ my $class = shift; -+ -+ my $config = Cassandane::Config->default()->clone(); -+ $config->set(maxword => 25); -+ $config->set(maxquoted => 25); -+ $config->set(maxliteral => 25); -+ $config->set(literalminus => 1); -+ $config->set(maxargssize => 45); -+ $config->set(maxmessagesize => 100); -+ $config->set(event_groups => "message mailbox applepushservice"); -+ $config->set(aps_topic => "mail"); -+ -+ return $class->SUPER::new({ -+ adminstore => 1, -+ config => $config, -+ services => ['imap'], -+ }, @_); -+} -+ -+sub set_up -+{ -+ my ($self) = @_; -+ $self->SUPER::set_up(); -+} -+ -+sub tear_down -+{ -+ my ($self) = @_; -+ $self->SUPER::tear_down(); -+} -+ -+sub test_maxword -+{ -+ my ($self) = @_; -+ -+ # Oversized command name -+ $self->assert_cmd_bye_toobig("X" x 26); -+} -+ -+sub test_maxword_astring -+{ -+ my ($self) = @_; -+ -+ # Oversized mailbox name -+ $self->assert_cmd_bye_toobig('SELECT', "X" x 26); -+} -+ -+sub test_maxquoted -+{ -+ my ($self) = @_; -+ -+ # Oversized mailbox name -+ $self->assert_cmd_bye_toobig('SELECT', { Quote => "X" x 26 }); -+} -+ -+sub test_maxliteral_nosync -+{ -+ my ($self) = @_; -+ -+ my $talk = $self->{store}->get_client(); -+ # Do this by brute force until we have IMAPTalk v4.06+ -+ $talk->_imap_socket_out($talk->{CmdId}++ . " SELECT {26+}\015\012"); -+ $self->assert_bye_toobig(); -+} -+ -+sub test_maxliteral_sync -+{ -+ my ($self) = @_; -+ -+ # Unlike oversized non-sync literals which fatal() in one central location, -+ # oversized sync literals fail with a NO response in multiple places, -+ # so we test as many of those places as possible. -+ # Having said that, arguments parsed in cmdloop() or in get_search_criterion() -+ # are mostly handled centrally. -+ -+ # Authenticated State -+ -+ # Synchronizing literals are the default in IMAPTalk v4.05 (and earlier) -+ my $talk = $self->{store}->get_client(NoLiteralPlus => 1); -+ -+ $self->assert_cmd_no_toobig($talk, 'SELECT', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'ID', -+ [ { Literal => "X" x 26 } ]); -+ -+ $self->assert_cmd_no_toobig($talk, 'ID', -+ [ { Quote => 'foo' }, { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'LIST', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'LIST', -+ { Quote => '' }, { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'LISTRIGHTS', -+ 'INBOX', { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETACL', -+ 'INBOX', 'anyone', { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'GETMETADATA', -+ 'INBOX', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'GETMETADATA', -+ 'INBOX', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETMETADATA', -+ 'INBOX', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SETMETADATA', -+ 'INBOX', [ '/comment', { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', -+ { Literal => "X" x 26 }); -+ -+ $self->assert_cmd_no_toobig($talk, 'XAPPLEPUSHSERVICE', -+ 'FOO', { Literal => "X" x 26 }); -+ -+ # Selected State -+ $talk->select('INBOX'); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'ANNOTATION', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'BODY[HEADER.FIELDS', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'FETCH', -+ '1', [ 'RFC822.HEADER.LINES', -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', [ { Literal => "X" x 26 } ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', -+ [ { Quote => '/comment' }, -+ [ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'STORE', -+ '1', 'ANNOTATION', -+ [ { Quote => '/comment' }, -+ [ { Quote => 'value' }, -+ { Literal => "X" x 26 } ] ] ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'HEADER', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'HEADER', 'SUBJECT', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', '/comment', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'SEARCH', -+ 'ANNOTATION', '/comment', -+ 'value', { Literal => "X" x 26 } ); -+ -+ $self->assert_cmd_no_toobig($talk, 'ESEARCH', -+ 'IN', [ 'MAILBOXES', { 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_multisearch -+{ -+ my ($self) = @_; -+ -+ $self->assert_cmd_bye_toobig('ESEARCH', -+ 'IN', [ 'MAILBOXES', [ "X" x 25, "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 9536206ea6750fe50bd974f65129b18b2a32ec6e Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 23:49:46 -0400 -Subject: [PATCH 15/16] 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 9576d1d88..32d069653 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 -@@ -514,6 +524,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(); - } - -@@ -523,6 +534,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 fb5dace4a..afd58bd66 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3557,7 +3557,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) - - { -@@ -3589,10 +3589,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++; -@@ -3622,8 +3626,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; -@@ -3635,7 +3639,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 */ -@@ -3782,7 +3787,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; -@@ -3796,7 +3802,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 */ -@@ -4038,13 +4044,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 d2f960b07cd32ba10cf4cd828e5dd98dea1bfdc2 Mon Sep 17 00:00:00 2001 -From: Ken Murchison -Date: Thu, 21 Mar 2024 23:55:13 -0400 -Subject: [PATCH 16/16] 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 afd58bd66..58457601a 100644 ---- a/imap/imapd.c -+++ b/imap/imapd.c -@@ -3591,13 +3591,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++; -@@ -3968,7 +3968,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"; -@@ -4310,7 +4310,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")) { -@@ -4685,7 +4685,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]) { -@@ -4818,7 +4818,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); -@@ -5154,7 +5154,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")) { -@@ -5503,7 +5503,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")) { -@@ -5598,7 +5598,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; -@@ -7763,7 +7763,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); -@@ -8615,7 +8615,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)) { -@@ -8769,7 +8769,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")) { -@@ -9155,7 +9155,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; -@@ -9166,7 +9166,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 == ' '); -@@ -9237,7 +9237,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, -@@ -9290,7 +9290,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, -@@ -9370,7 +9370,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, -@@ -9476,7 +9476,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, -@@ -9520,7 +9520,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); -@@ -9598,7 +9598,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, -@@ -9613,7 +9613,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, -@@ -11903,7 +11903,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 */ -@@ -12055,7 +12055,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", -@@ -12161,7 +12161,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 2dceaed94..010921089 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; - } -@@ -766,7 +766,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 == ' '); -@@ -1546,7 +1546,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 -