aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2019-11-13 06:24:07 +0100
committerGuilhem Moulin <guilhem@fripost.org>2019-11-13 06:24:07 +0100
commitc6bbdd8aa697b2d42be1ac0839189da32d437a8f (patch)
treecd1b8d19d99557fbebe33344d0d23f6ac826da57
parent87d947df1b837514a0f1efa3bc36b58088bd2564 (diff)
parentdac4ab1c9306bf2035bc1547d2ed27ab09850120 (diff)
Merge branch 'master' into debian
-rw-r--r--Changelog21
-rw-r--r--Makefile2
-rwxr-xr-xinterimap23
-rw-r--r--interimap.sample2
-rw-r--r--lib/Net/IMAP/InterIMAP.pm142
-rwxr-xr-xpullimap10
-rw-r--r--tests/00-db-exclusive/local.conf5
-rw-r--r--tests/00-db-exclusive/remote.conf5
-rw-r--r--tests/00-db-exclusive/run25
l---------tests/00-db-migration-0-to-1-delim-mismatch/before.sql1
-rw-r--r--tests/00-db-migration-0-to-1-delim-mismatch/local.conf6
-rw-r--r--tests/00-db-migration-0-to-1-delim-mismatch/remote.conf6
-rw-r--r--tests/00-db-migration-0-to-1-delim-mismatch/run8
-rw-r--r--tests/00-db-migration-0-to-1-foreign-key-violation/local.conf6
-rw-r--r--tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf6
-rw-r--r--tests/00-db-migration-0-to-1-foreign-key-violation/run23
-rw-r--r--tests/00-db-migration-0-to-1/local.conf6
-rw-r--r--tests/00-db-migration-0-to-1/remote.conf6
-rw-r--r--tests/01-rename-exists-db/local.conf6
-rw-r--r--tests/01-rename-exists-db/remote.conf6
-rw-r--r--tests/01-rename-exists-local/local.conf6
-rw-r--r--tests/01-rename-exists-local/remote.conf6
-rw-r--r--tests/01-rename-exists-remote/local.conf6
-rw-r--r--tests/01-rename-exists-remote/remote.conf6
-rw-r--r--tests/01-rename/local.conf6
-rw-r--r--tests/01-rename/remote.conf6
-rw-r--r--tests/02-delete/local.conf6
-rw-r--r--tests/02-delete/remote.conf6
-rw-r--r--tests/03-sync-mailbox-list-partial/interimap.conf1
-rw-r--r--tests/03-sync-mailbox-list-partial/local.conf6
-rw-r--r--tests/03-sync-mailbox-list-partial/remote.conf6
-rw-r--r--tests/03-sync-mailbox-list-partial/run57
-rw-r--r--tests/03-sync-mailbox-list-ref/local.conf6
-rw-r--r--tests/03-sync-mailbox-list-ref/remote.conf6
-rw-r--r--tests/03-sync-mailbox-list-ref/run28
-rw-r--r--tests/03-sync-mailbox-list/local.conf6
-rw-r--r--tests/03-sync-mailbox-list/remote.conf6
-rw-r--r--tests/03-sync-mailbox-list/run73
-rw-r--r--tests/04-resume/local.conf6
-rw-r--r--tests/04-resume/remote.conf6
-rw-r--r--tests/05-repair/local.conf6
-rw-r--r--tests/05-repair/remote.conf6
-rw-r--r--tests/06-largeint/local.conf5
-rw-r--r--tests/06-largeint/remote.conf5
-rw-r--r--tests/07-sync-live-multi/remote.conf6
-rw-r--r--tests/07-sync-live-multi/remote2.conf6
-rw-r--r--tests/07-sync-live-multi/remote3.conf6
-rw-r--r--tests/07-sync-live/local.conf6
-rw-r--r--tests/07-sync-live/remote.conf6
-rw-r--r--tests/auth-login/interimap.remote5
-rw-r--r--tests/auth-login/remote.conf2
-rw-r--r--tests/auth-login/t12
l---------tests/auth-logindisabled/interimap.remote1
-rw-r--r--tests/auth-logindisabled/remote.conf4
-rw-r--r--tests/auth-logindisabled/t16
-rw-r--r--tests/auth-noplaintext/interimap.remote3
l---------tests/auth-noplaintext/remote.conf1
-rw-r--r--tests/auth-noplaintext/t15
l---------tests/auth-sasl-plain-no-ir/interimap.remote1
-rw-r--r--tests/auth-sasl-plain-no-ir/remote.conf2
-rw-r--r--tests/auth-sasl-plain-no-ir/t26
-rw-r--r--tests/auth-sasl-plain/interimap.remote4
-rw-r--r--tests/auth-sasl-plain/remote.conf1
-rw-r--r--tests/auth-sasl-plain/t12
l---------tests/compress/interimap.remote1
l---------tests/compress/remote.conf1
-rw-r--r--tests/compress/t19
-rw-r--r--tests/condstore/t50
-rw-r--r--tests/db-exclusive-lock/t16
-rw-r--r--tests/db-migration-0-1-foreign-key-violation/t21
-rw-r--r--tests/db-no-create--watch/t6
l---------tests/db-upgrade-0-1-delim-mismatch/before.sql1
-rw-r--r--tests/db-upgrade-0-1-delim-mismatch/local.conf3
-rw-r--r--tests/db-upgrade-0-1-delim-mismatch/remote.conf3
-rw-r--r--tests/db-upgrade-0-1-delim-mismatch/t7
-rw-r--r--tests/db-upgrade-0-1/after.sql (renamed from tests/00-db-migration-0-to-1/after.sql)0
-rw-r--r--tests/db-upgrade-0-1/before.sql (renamed from tests/00-db-migration-0-to-1/before.sql)0
-rw-r--r--tests/db-upgrade-0-1/local.conf3
l---------tests/db-upgrade-0-1/remote.conf1
-rw-r--r--tests/db-upgrade-0-1/t (renamed from tests/00-db-migration-0-to-1/run)11
-rw-r--r--tests/delete/local.conf3
-rw-r--r--tests/delete/remote.conf3
-rw-r--r--tests/delete/t (renamed from tests/02-delete/run)64
-rw-r--r--tests/ignore-mailbox/interimap.conf1
-rw-r--r--tests/ignore-mailbox/local.conf3
-rw-r--r--tests/ignore-mailbox/remote.conf3
-rw-r--r--tests/ignore-mailbox/t62
-rw-r--r--tests/largeint/t (renamed from tests/06-largeint/run)13
-rw-r--r--tests/list60
-rw-r--r--tests/list-mailbox/interimap.conf1
-rw-r--r--tests/list-mailbox/local.conf3
-rw-r--r--tests/list-mailbox/remote.conf3
-rw-r--r--tests/list-mailbox/t57
-rw-r--r--tests/list-reference/interimap.local1
-rw-r--r--tests/list-reference/interimap.remote1
-rw-r--r--tests/list-reference/local.conf3
-rw-r--r--tests/list-reference/remote.conf3
-rw-r--r--tests/list-reference/t47
-rw-r--r--tests/list-select-opts/interimap.conf1
-rw-r--r--tests/list-select-opts/local.conf3
-rw-r--r--tests/list-select-opts/remote.conf3
-rw-r--r--tests/list-select-opts/t56
l---------tests/pullimap/interimap.remote1
-rw-r--r--tests/pullimap/local.conf1
-rw-r--r--tests/pullimap/pullimap.conf1
l---------tests/pullimap/remote.conf1
-rw-r--r--tests/pullimap/t96
-rw-r--r--tests/rename-exists-db/local.conf3
-rw-r--r--tests/rename-exists-db/remote.conf3
-rw-r--r--tests/rename-exists-db/t (renamed from tests/01-rename-exists-db/run)6
-rw-r--r--tests/rename-exists-local/local.conf3
-rw-r--r--tests/rename-exists-local/remote.conf3
-rw-r--r--tests/rename-exists-local/t (renamed from tests/01-rename-exists-local/run)6
-rw-r--r--tests/rename-exists-remote/local.conf3
-rw-r--r--tests/rename-exists-remote/remote.conf3
-rw-r--r--tests/rename-exists-remote/t (renamed from tests/01-rename-exists-remote/run)6
-rw-r--r--tests/rename-inferiors/local.conf3
-rw-r--r--tests/rename-inferiors/remote.conf3
-rw-r--r--tests/rename-inferiors/t (renamed from tests/01-rename/run)50
-rw-r--r--tests/rename-simple/t61
-rw-r--r--tests/repair/local.conf3
-rw-r--r--tests/repair/remote.conf3
-rw-r--r--tests/repair/t (renamed from tests/05-repair/run)32
-rw-r--r--tests/resume/local.conf3
-rw-r--r--tests/resume/remote.conf3
-rw-r--r--tests/resume/t (renamed from tests/04-resume/run)12
-rwxr-xr-xtests/run288
-rwxr-xr-xtests/run-all65
-rw-r--r--tests/snippets/dovecot/dhparams.pem8
-rw-r--r--tests/snippets/dovecot/dovecot.key5
-rw-r--r--tests/snippets/dovecot/dovecot.pem11
-rw-r--r--tests/snippets/dovecot/imapd.conf19
-rw-r--r--tests/snippets/dovecot/interimap-required-capabilities.conf3
-rw-r--r--tests/snippets/dovecot/lmtpd.conf7
-rw-r--r--tests/snippets/dovecot/ssl.conf4
l---------tests/split-set/interimap.remote1
l---------tests/split-set/remote.conf1
-rw-r--r--tests/split-set/t43
-rw-r--r--tests/starttls-logindisabled/interimap.remote4
-rw-r--r--tests/starttls-logindisabled/remote.conf5
-rw-r--r--tests/starttls-logindisabled/t19
-rw-r--r--tests/starttls/interimap.remote4
-rw-r--r--tests/starttls/remote.conf2
-rw-r--r--tests/starttls/t27
l---------tests/sync-live-crippled/interimap.remote1
l---------tests/sync-live-crippled/local.conf1
-rw-r--r--tests/sync-live-crippled/remote.conf6
l---------tests/sync-live-crippled/t1
-rw-r--r--tests/sync-live-multi/interimap1.local1
-rw-r--r--tests/sync-live-multi/interimap2.local1
-rw-r--r--tests/sync-live-multi/interimap3.local1
-rw-r--r--tests/sync-live-multi/local.conf (renamed from tests/07-sync-live-multi/local.conf)0
-rw-r--r--tests/sync-live-multi/remote1.conf3
-rw-r--r--tests/sync-live-multi/remote2.conf3
-rw-r--r--tests/sync-live-multi/remote3.conf3
-rw-r--r--tests/sync-live-multi/remotes1
-rw-r--r--tests/sync-live-multi/t (renamed from tests/07-sync-live-multi/run)95
l---------tests/sync-live-tls/interimap.remote1
l---------tests/sync-live-tls/local.conf1
-rw-r--r--tests/sync-live-tls/remote.conf6
l---------tests/sync-live-tls/t1
-rw-r--r--tests/sync-live/local.conf3
-rw-r--r--tests/sync-live/remote.conf3
-rw-r--r--tests/sync-live/t (renamed from tests/07-sync-live/run)42
-rw-r--r--tests/sync-mailbox-list/local.conf3
-rw-r--r--tests/sync-mailbox-list/remote.conf3
-rw-r--r--tests/sync-mailbox-list/t93
l---------tests/tls-pin-fingerprint/interimap.remote1
l---------tests/tls-pin-fingerprint/remote.conf1
-rw-r--r--tests/tls-pin-fingerprint/t33
l---------tests/tls-protocols/interimap.remote1
l---------tests/tls-protocols/remote.conf1
-rw-r--r--tests/tls-protocols/t39
-rw-r--r--tests/tls-verify-peer/interimap.remote2
l---------tests/tls-verify-peer/remote.conf1
-rw-r--r--tests/tls-verify-peer/t80
-rw-r--r--tests/tls/interimap.remote3
-rw-r--r--tests/tls/remote.conf2
-rw-r--r--tests/tls/t14
179 files changed, 1800 insertions, 740 deletions
diff --git a/Changelog b/Changelog
index 48481dd..f0c9e50 100644
--- a/Changelog
+++ b/Changelog
@@ -50,6 +50,7 @@ interimap (0.5) upstream;
full.
+ interimap: new option 'log-prefix' to control the prefix of each log
entry, depending on the component name and relevant mailbox.
+ + interimap: raise SELECT sample range size from 64 to 256 bytes.
- libinterimap: bugfix: hierarchy delimiters in LIST responses were
returned as an escaped quoted special, like "\\", not as a single
character (backslash in this case).
@@ -84,6 +85,26 @@ interimap (0.5) upstream;
POSIX.1-2017, sec. 2.6.1.)
- libinterimap: don't panic() when inflate() reports the end of the
compression stream is reached.
+ - libinterimap: the 'compress' boolean wasn't honored.
+ - libinterimap: fix STARTTLS directive, broken since 0.2.
+ - libinterimap: push_flag_updates(): the UNCHANGEDSINCE test from
+ the CONDSTORE extension was incorrectly placed after the flag list in
+ UID STORE commands.
+ - libinterimap: push_flag_updates(): ignore UIDs for which no untagged
+ FETCH response was received.
+ - libinterimap: push_flag_updates(): don't ignores received updates (by
+ another client) to a superset of the desigred flag list.
+ - libinterimap: avoid sending large UID EXPUNGE|FETCH|STORE commands as
+ they might exceed the server's max acceptable command size; these
+ commands are now split into multiple (sequential) commands when their
+ set representation exceeds 4096 bytes in size. Performance could be
+ improved by pipelining but given the scope of this software
+ (synchronization) it's unlikely to make any difference in practice.
+ This is a also a workaround for a bug in Dovecot 2.3.4:
+ https://dovecot.org/pipermail/dovecot/2019-November/117522.html
+ - interimap: for the reason explained above, limit number of messages
+ to 128 per APPEND command (only on servers advertizing MULTIAPPEND,
+ for other servers the number remains 1).
-- Guilhem Moulin <guilhem@fripost.org> Fri, 10 May 2019 00:58:14 +0200
diff --git a/Makefile b/Makefile
index 4fc759f..fdfb717 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ $(MANUALS): %: %.md
pandoc -s -f json -t man+smart -o "$@"
test:
- @for t in tests/*; do if [ -f "$$t/run" ]; then ./tests/run "$$t" || exit 1; fi; done
+ @./tests/run-all
HTML_ROOTDIR ?= ./doc
CSS ?= /usr/share/javascript/bootstrap/css/bootstrap.min.css
diff --git a/interimap b/interimap
index 87c3a64..386492e 100755
--- a/interimap
+++ b/interimap
@@ -396,7 +396,7 @@ fail(undef, "Local and remote namespaces are neither both flat nor both hierarch
fail(undef, "Local and remote hierachy delimiters differ ",
"(local ", print_delimiter($IMAP->{local}->{delimiter}), ", ",
"remote ", print_delimiter($IMAP->{remote}->{delimiter}), "), ",
- "refusing to update \`mailboxes\` table.")
+ "refusing to update table \`mailboxes\`.")
if defined $IMAP->{local}->{delimiter} and defined $IMAP->{remote}->{delimiter}
# we failed earlier if only one of them was NIL
and $IMAP->{local}->{delimiter} ne $IMAP->{remote}->{delimiter};
@@ -418,7 +418,7 @@ fail(undef, "Local and remote namespaces are neither both flat nor both hierarch
$DBH->do("DROP TABLE mailboxes");
$DBH->do("ALTER TABLE _tmp${DATABASE_VERSION}_mailboxes RENAME TO mailboxes");
}
- fail("database", "Broken referential integrity! Refusing to commit changes.")
+ fail("database", "Broken referential integrity! Refusing to commit changes.")
if defined $DBH->selectrow_arrayref("PRAGMA foreign_key_check");
SCHEMA_DONE:
$DBH->do("PRAGMA user_version = $DATABASE_VERSION");
@@ -530,6 +530,7 @@ if (defined $COMMAND and $COMMAND eq 'delete') {
qw/mapping local remote mailboxes/
if @ARGV and $CONFIG{target}->{database};
foreach my $mailbox (@ARGV) {
+ fail(undef, "INBOX can't be deleted") if uc($mailbox) eq "INBOX"; # RFC 3501 sec. 6.3.4
my $idx = db_get_mailbox_idx($mailbox);
# delete $mailbox on servers where $mailbox exists. note that
@@ -636,6 +637,10 @@ sub sync_mailbox_list() {
state $sth_subscribe = $DBH->prepare(q{
UPDATE mailboxes SET subscribed = ? WHERE idx = ?
});
+ state $ignore_mailbox = do {
+ my $re = $CONF->{_}->{"ignore-mailbox"};
+ defined $re ? qr/$re/ : undef
+ };
foreach my $name (qw/local remote/) {
foreach my $mbx (keys %{$IMAP->{$name}->{mailboxes}}) {
@@ -645,8 +650,7 @@ sub sync_mailbox_list() {
# exclude ignored mailboxes (taken from the default config as it doesn't
# make sense to ignore mailboxes from one side but not the other
- next if !@ARGV and defined $CONF->{_}->{"ignore-mailbox"}
- and $mbx =~ /$CONF->{_}->{"ignore-mailbox"}/o;
+ next if !@ARGV and defined $ignore_mailbox and $mbx =~ $ignore_mailbox;
$mailboxes{$mbx} = 1;
}
}
@@ -789,7 +793,7 @@ sub delete_mapping($$) {
# we let the server know that the messages have been EXPUNGEd [RFC7162,
# section 3.2.5.2].
# The UID set is the largest set of higest UIDs with at most 1024 UIDs,
-# of length (once compacted) at most 64.
+# of length (once compacted) at most 256.
# The reason why we sample with the highest UIDs is that lowest UIDs are
# less likely to be deleted.
sub sample($$) {
@@ -810,13 +814,14 @@ sub sample($$) {
$uids = ($min == $max ? $min : "$min:$max")
.(defined $uids ? ','.$uids : '');
$min = $max = $k;
- if (length($uids) > 64) {
+ if (length($uids) > 256) {
$sth->finish(); # done with the statement
last;
}
}
}
- if (!defined $uids or length($uids) <= 64) {
+ if (!defined $uids or length($uids) <= 256) {
+ # exceed max size by at most 22 bytes ("$MIN:$MAX,")
$n += $max - $min + 1;
$uids = ($min == $max ? $min : "$min:$max")
. (defined $uids ? ','.$uids : '');
@@ -1161,8 +1166,8 @@ sub callback_new_message($$$$;$$$) {
}
else {
# use MULTIAPPEND (RFC 3502)
- # proceed by 1MiB batches to save roundtrips without blowing up the memory
- if (@$buff and $$bufflen + $length > 1048576) {
+ # proceed by batches of 128/1MiB to save roundtrips without blowing up the memory
+ if ($#$buff >= 127 or (@$buff and $$bufflen + $length > 1048576)) {
@UIDs = callback_new_message_flush($idx, $mailbox, $name, @$buff);
@$buff = ();
$$bufflen = 0;
diff --git a/interimap.sample b/interimap.sample
index b0f4b95..2a7b8de 100644
--- a/interimap.sample
+++ b/interimap.sample
@@ -10,7 +10,7 @@ ignore-mailbox = ^virtual(?:\x00|$)
[local]
type = tunnel
-command = exec doveadm exec imap
+command = doveadm exec imap
null-stderr = YES
[remote]
diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm
index bb27009..02ae65f 100644
--- a/lib/Net/IMAP/InterIMAP.pm
+++ b/lib/Net/IMAP/InterIMAP.pm
@@ -17,7 +17,7 @@
#----------------------------------------------------------------------
package Net::IMAP::InterIMAP v0.0.5;
-use v5.10.0;
+use v5.20.0;
use warnings;
use strict;
@@ -36,7 +36,7 @@ BEGIN {
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();
- our @EXPORT_OK = qw/xdg_basedir read_config compact_set $IMAP_text $IMAP_cond
+ our @EXPORT_OK = qw/xdg_basedir read_config compact_set
slurp is_dirty has_new_mails/;
}
@@ -61,7 +61,7 @@ my %OPTIONS = (
auth => qr/\A($RE_ATOM_CHAR+(?: $RE_ATOM_CHAR+)*)\z/,
command => qr/\A(\P{Control}+)\z/,
'null-stderr' => qr/\A(YES|NO)\z/i,
- compress => qr/\A($RE_ATOM_CHAR+(?: $RE_ATOM_CHAR+)*)\z/,
+ compress => qr/\A(YES|NO)\z/i,
SSL_protocols => qr/\A(!?$RE_SSL_PROTO(?: !?$RE_SSL_PROTO)*)\z/,
SSL_fingerprint => qr/\A((?:[A-Za-z0-9]+\$)?\p{AHex}+)\z/,
SSL_cipherlist => qr/\A(\P{Control}+)\z/,
@@ -203,6 +203,21 @@ sub compact_list(@) {
return $set;
}
+# with_set($set, $cmd)
+# Split long commands over multiple subsets to avoid exceeding the server limit
+sub with_set($&) {
+ my ($set, $cmd) = @_;
+ my $max_length = 4096;
+ for (my $length = length($set); $length > $max_length;) {
+ my $l = rindex($set, ',', $max_length);
+ die unless $l > 0; # sanity check
+ $cmd->(substr($set, 0, $l));
+ $set = substr($set, ++$l);
+ $length -= $l;
+ }
+ return $cmd->($set);
+}
+
# in_set($x, $set)
# Return true if the UID or sequence number $x belongs to the set $set.
@@ -215,7 +230,7 @@ sub in_set($$) {
return 1 if $x == $1;
}
elsif ($r eq '*' or $r eq '*:*') {
- warn "Assuming $x belongs to set $set! (Dunno what \"*\" means.)";
+ warn "Assuming $x belongs to set $set! (Dunno what \"*\" means.)";
return 1;
}
elsif ($r =~ /\A([0-9]+):\*\z/ or $r =~ /\A\*:([0-9]+)\z/) {
@@ -398,7 +413,8 @@ sub new($%) {
if ($self->{type} eq 'imap' and $self->{STARTTLS}) { # RFC 2595 section 5.1
$self->fail("Server did not advertise STARTTLS capability.")
unless grep {$_ eq 'STARTTLS'} @caps;
- $self->_start_ssl($self->{S}) if $self->{type} eq 'imaps';
+ $self->_send('STARTTLS');
+ $self->_start_ssl($self->{S});
# refresh the previous CAPABILITY list since the previous one could have been spoofed
delete $self->{_CAPABILITIES};
@@ -840,9 +856,10 @@ sub remove_message($@) {
$self->fail("Server did not advertise UIDPLUS (RFC 4315) capability.")
unless $self->_capable('UIDPLUS');
- my $set = compact_set(@set);
- $self->_send("UID STORE $set +FLAGS.SILENT (\\Deleted)");
- $self->_send("UID EXPUNGE $set"); # RFC 4315 UIDPLUS
+ with_set(compact_set(@set), sub($) {
+ $self->_send("UID STORE $_[0] +FLAGS.SILENT (\\Deleted)");
+ $self->_send("UID EXPUNGE $_[0]"); # RFC 4315 UIDPLUS
+ });
my %vanished = map {$_ => 1} @{$self->{_VANISHED}};
@@ -959,7 +976,9 @@ sub append($$@) {
# optional $callback.
sub fetch($$$;&) {
my ($self, $set, $flags, $callback) = @_;
- $self->_send("UID FETCH $set $flags", $callback);
+ return with_set($set, sub($) {
+ $self->_send("UID FETCH $_[0] $flags", $callback);
+ });
}
@@ -1196,16 +1215,15 @@ sub pull_updates($;$) {
my $mailbox = $self->{_SELECTED} // $self->panic();
my $pcache = $self->{_PCACHE}->{$mailbox};
- my %modified;
$self->_send("UID FETCH 1:".($pcache->{UIDNEXT}-1)." (MODSEQ FLAGS)")
if $full and ($pcache->{UIDNEXT} // 1) > 1;
- my @missing;
+ my %modified;
while (%{$self->{_MODIFIED}}) {
+ my @missing;
while (my ($uid,$v) = each %{$self->{_MODIFIED}}) {
- # don't filter on the fly (during FETCH responses) because
- # FLAG updates can arrive while processing pull_new_messages
- # for instance
+ # don't filter on the fly (during FETCH responses) because FLAG updates
+ # can arrive while processing pull_new_messages() for instance
if (defined $v->[1] and $v->[0] > 0) { # setting the MODSEQ to 0 forces a FETCH
next unless $uid < ($pcache->{UIDNEXT} // 1) # out of bounds
and ($full or $v->[0] > ($pcache->{HIGHESTMODSEQ} // 0)); # already seen
@@ -1215,8 +1233,11 @@ sub pull_updates($;$) {
}
}
$self->{_MODIFIED} = {};
- $self->_send("UID FETCH ".compact_set(@missing)." (MODSEQ FLAGS)") if @missing;
- @missing = ();
+ # non-empty @missing indicates a discouraged (but allowed) CONDSTORE server behavior,
+ # cf. RFC 7162 sec. 3.1.3 ex. 8 and the comment in push_flag_updates() below
+ with_set(compact_set(@missing), sub($) {
+ $self->_send("UID FETCH $_[0] (MODSEQ FLAGS)")
+ }) if @missing;
}
# do that afterwards since the UID FETCH command above can produce VANISHED responses
@@ -1278,7 +1299,7 @@ sub pull_new_messages($$&@) {
$range .= "$since:4294967295";
$UIDNEXT = $cache->{UIDNEXT} // $self->panic(); # sanity check
- $self->_send("UID FETCH $range ($attrs)", sub($) {
+ $self->fetch($range, "($attrs)", sub($) {
my $mail = shift;
$UIDNEXT = $mail->{UID} + 1 if $UIDNEXT <= $mail->{UID};
$callback->($mail) if defined $callback;
@@ -1306,57 +1327,48 @@ sub push_flag_updates($$@) {
my $mailbox = $self->{_SELECTED} // $self->panic();
my $modseq = $self->{_PCACHE}->{$mailbox}->{HIGHESTMODSEQ} // $self->panic();
- my $command = "UID STORE ".compact_set(@set)." FLAGS.SILENT ($flags) (UNCHANGEDSINCE $modseq)";
-
- my %listed;
- $self->_send($command, sub($){ $listed{shift->{UID}}++; });
my %failed;
- if ($IMAP_text =~ /\A\Q$IMAP_cond\E \[MODIFIED ([0-9,:]+)\] $RE_TEXT_CHAR+\z/) {
- foreach (split /,/, $1) {
- if (/\A([0-9]+)\z/) {
- $failed{$1} = 1;
- }
- elsif (/\A([0-9]+):([0-9]+)\z/) {
- my ($min, $max) = $1 < $2 ? ($1,$2) : ($2,$1);
- $failed{$_} = 1 foreach ($min .. $max);
- }
- else {
- $self->panic($_);
+ with_set(compact_set(@set), sub($) {
+ $self->_send("UID STORE $_[0] (UNCHANGEDSINCE $modseq) FLAGS.SILENT ($flags)");
+ if ($IMAP_text =~ /\A\Q$IMAP_cond\E \[MODIFIED ([0-9,:]+)\] $RE_TEXT_CHAR+\z/) {
+ foreach (split /,/, $1) {
+ if (/\A([0-9]+)\z/) {
+ $failed{$1} = 1;
+ } elsif (/\A([0-9]+):([0-9]+)\z/) {
+ my ($min, $max) = $1 < $2 ? ($1,$2) : ($2,$1);
+ $failed{$_} = 1 foreach ($min .. $max);
+ } else {
+ $self->panic($_);
+ }
}
}
- }
+ });
my @ok;
foreach my $uid (@set) {
+ my $modified = $self->{_MODIFIED};
if ($failed{$uid}) {
- # $uid was listed in the MODIFIED response code
- $self->{_MODIFIED}->{$uid} //= [ 0, undef ]; # will be downloaded again in pull_updates
- delete $self->{_MODIFIED}->{$uid} if
- # got a FLAG update for $uid; ignore it if it's $flags
- defined $self->{_MODIFIED}->{$uid}->[1] and
- $self->{_MODIFIED}->{$uid}->[1] eq $flags;
- }
- else {
- # $uid wasn't listed in the MODIFIED response code
- next unless defined $self->{_MODIFIED}->{$uid}; # already stored
- $self->panic() unless defined $listed{$uid} and $listed{$uid} > 0; # sanity check
- if ($listed{$uid} == 1) {
- # ignore succesful update
- delete $self->{_MODIFIED}->{$uid};
+ # $uid was listed in the MODIFIED response code from RFC 7162; will FETCH
+ # again in pull_updates(); per RFC 7162 sec. 3.1.3 $modified->{$uid} might not
+ # be defined ("nice" servers send an untagged FETCH response, cf. example 10,
+ # but they might omit it - allowed but discouraged CONDSTORE server behavior -
+ # cf. example 8)
+ $modified->{$uid} //= [ 0, undef ];
+ } elsif (defined (my $m = $modified->{$uid})) {
+ # received an untagged FETCH response, remove from the list of pending changes
+ # if the flag list was up to date (either implicitely or explicitely)
+ if (!defined $m->[1] or $m->[1] eq $flags) {
+ delete $modified->{$uid};
+ push @ok, $uid;
}
- elsif ($self->{_MODIFIED}->{$uid}->[1] and $self->{_MODIFIED}->{$uid}->[1] eq $flags) {
- # got multiple FETCH responses for $uid, the last one with $flags
- delete $self->{_MODIFIED}->{$uid};
- }
- push @ok, $uid;
}
}
unless ($self->{quiet}) {
$self->log("Updated flags ($flags) for UID ".compact_set(@ok)) if @ok;
$self->log("Couldn't update flags ($flags) for UID ".compact_set(keys %failed).', '.
- "trying again later") if %failed;
+ "will try again later") if %failed;
}
return keys %failed;
}
@@ -1369,8 +1381,9 @@ sub push_flag_updates($$@) {
sub silent_store($$$@) {
my $self = shift;
my $set = shift;
- my $mod = shift;
- $self->_send("UID STORE $set ${mod}FLAGS.SILENT (".join(' ', @_).")");
+ my $subcmd = shift . "FLAGS.SILENT";
+ my $flags = join(' ', @_);
+ with_set($set, sub($) { $self->_send("UID STORE $_[0] $subcmd ($flags)") });
}
@@ -1383,7 +1396,7 @@ sub expunge($$) {
$self->fail("Server did not advertise UIDPLUS (RFC 4315) capability.")
unless $self->_capable('UIDPLUS');
- $self->_send("UID EXPUNGE $set");
+ with_set($set, sub($) { $self->_send("UID EXPUNGE $_[0]") });
}
@@ -1408,10 +1421,10 @@ sub _ssl_error($$@) {
# RFC 3986 appendix A
my $RE_IPv4 = do {
my $dec = qr/[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]/;
- qr/$dec(?:\.$dec){3}/o };
+ qr/$dec(?:\.$dec){3}/ };
my $RE_IPv6 = do {
my $h16 = qr/[0-9A-Fa-f]{1,4}/;
- my $ls32 = qr/$h16:$h16|$RE_IPv4/o;
+ my $ls32 = qr/$h16:$h16|$RE_IPv4/;
qr/ (?: $h16 : ){6} $ls32
| :: (?: $h16 : ){5} $ls32
| (?: $h16 )? :: (?: $h16 : ){4} $ls32
@@ -1421,7 +1434,7 @@ my $RE_IPv6 = do {
| (?: (?: $h16 : ){0,4} $h16 )? :: $ls32
| (?: (?: $h16 : ){0,5} $h16 )? :: $h16
| (?: (?: $h16 : ){0,6} $h16 )? ::
- /xo };
+ /x };
# Opens a TCP socket to the given $host and $port.
@@ -1429,11 +1442,10 @@ sub _tcp_connect($$$) {
my ($self, $host, $port) = @_;
my %hints = (socktype => SOCK_STREAM, protocol => IPPROTO_TCP);
- if ($host =~ qr/\A$RE_IPv4\z/o) {
+ if ($host =~ qr/\A$RE_IPv4\z/) {
$hints{family} = AF_INET;
$hints{flags} |= AI_NUMERICHOST;
- }
- elsif ($host =~ qr/\A\[($RE_IPv6)\]\z/o) {
+ } elsif ($host =~ qr/\A\[($RE_IPv6)\]\z/) {
$host = $1;
$hints{family} = AF_INET6;
$hints{flags} |= AI_NUMERICHOST;
@@ -1611,7 +1623,7 @@ sub _ssl_verify($$$) {
my $pkey = Net::SSLeay::X509_get_X509_PUBKEY($cert);
unless (defined $pkey and Net::SSLeay::EVP_Digest($pkey, $type) eq $digest) {
- $self->warn("Fingerprint doesn't match! MiTM in action?");
+ $self->warn("Fingerprint doesn't match! MiTM in action?");
$ok = 0;
}
}
@@ -2356,7 +2368,7 @@ sub _resp($$;&$$) {
# /!\ No bookkeeping since there is no internal cache mapping sequence numbers to UIDs
if ($self->_enabled('QRESYNC')) {
$self->panic("$1 <= $cache->{EXISTS}") if $1 <= $cache->{EXISTS}; # sanity check
- $self->fail("RFC 7162 violation! Got an EXPUNGE response with QRESYNC enabled.");
+ $self->fail("RFC 7162 violation! Got an EXPUNGE response with QRESYNC enabled.");
}
# the new message was expunged before it was synced
$self->{_NEW} = 0 if $self->{_NEW} == 1 and $cache->{EXISTS} == $1;
@@ -2407,7 +2419,7 @@ sub _resp($$;&$$) {
/\A \((\\?$RE_ATOM_CHAR+ [0-9]+(?: \\?$RE_ATOM_CHAR+ [0-9]+)*)?\)\z/ or $self->panic($_);
my %status = split / /, $1;
$mailbox = 'INBOX' if uc $mailbox eq 'INBOX'; # INBOX is case-insensitive
- $self->panic("RFC 5465 violation! Missing HIGHESTMODSEQ data item in STATUS response")
+ $self->panic("RFC 5465 violation! Missing HIGHESTMODSEQ data item in STATUS response")
if $self->_enabled('QRESYNC') and !defined $status{HIGHESTMODSEQ} and defined $cmd and
($cmd eq 'NOTIFY' or $cmd eq 'slurp');
$self->_update_cache_for($mailbox, %status);
diff --git a/pullimap b/pullimap
index dcbe59b..1dc4b9e 100755
--- a/pullimap
+++ b/pullimap
@@ -18,10 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#----------------------------------------------------------------------
+use v5.20.2;
use strict;
use warnings;
-use v5.20.2;
our $VERSION = '0.4';
my $NAME = 'pullimap';
@@ -29,7 +29,7 @@ use Errno 'EINTR';
use Fcntl qw/O_CREAT O_RDWR O_DSYNC F_SETLK F_WRLCK SEEK_SET F_GETFD F_SETFD FD_CLOEXEC/;
use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version/;
use List::Util 'first';
-use Socket qw/PF_INET PF_INET6 SOCK_STREAM/;
+use Socket qw/PF_INET PF_INET6 SOCK_STREAM IPPROTO_TCP/;
use lib 'lib';
use Net::IMAP::InterIMAP 0.0.5 qw/xdg_basedir read_config compact_set/;
@@ -146,8 +146,7 @@ sub sendmail($$) {
: $fam == PF_INET6 ? Socket::pack_sockaddr_in6($port, $addr)
: die;
- my $proto = getprotobyname("tcp") // die;
- socket($SMTP, $fam, SOCK_STREAM, $proto) or die "socket: $!";
+ socket($SMTP, $fam, SOCK_STREAM, IPPROTO_TCP) or die "socket: $!";
until (connect($SMTP, $sockaddr)) {
next if $! == EINTR; # try again if connect(2) was interrupted by a signal
die "connect: $!";
@@ -286,6 +285,7 @@ my $ATTRS = "ENVELOPE INTERNALDATE";
$ATTRS .= " BODY.PEEK[]" unless $CONFIG{'no-delivery'};
my $RE_ATOM = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
+my $DOT_STRING = qr/\A$RE_ATOM(?:\.$RE_ATOM)*\z/;
sub pull_callback($$) {
my ($uids, $mail) = @_;
return unless exists $mail->{RFC822} or $CONFIG{'no-delivery'}; # not for us
@@ -294,7 +294,7 @@ sub pull_callback($$) {
my $e = $mail->{ENVELOPE}->[3];
my $sender = '';
if (defined $e and defined (my $l = $e->[0]->[2]) and defined (my $d = $e->[0]->[3])) {
- if ($l =~ /\A$RE_ATOM(?:\.$RE_ATOM)*\z/o) {
+ if ($l =~ $DOT_STRING) {
$sender = $l.'@'.$d;
} elsif ($l =~ /\A[\x20-\x7E]*\z/) {
# quote the local part if not Dot-string (RFC 5321)
diff --git a/tests/00-db-exclusive/local.conf b/tests/00-db-exclusive/local.conf
deleted file mode 100644
index 9c838fd..0000000
--- a/tests/00-db-exclusive/local.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace inbox {
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-exclusive/remote.conf b/tests/00-db-exclusive/remote.conf
deleted file mode 100644
index 9c838fd..0000000
--- a/tests/00-db-exclusive/remote.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace inbox {
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-exclusive/run b/tests/00-db-exclusive/run
deleted file mode 100644
index 1528b3b..0000000
--- a/tests/00-db-exclusive/run
+++ /dev/null
@@ -1,25 +0,0 @@
-# verify that database isn't created in --watch mode
-! interimap --watch=60
-xgrep -E "^DBI connect\(.*\) failed: unable to open database file at " <"$STDERR"
-
-# now create database
-interimap
-
-# start a background process
-interimap --watch=60 & pid=$!
-cleanup() {
- # kill interimap process and its children
- pkill -P "$pid" -TERM || true
- kill -TERM "$pid" || true
- wait
-}
-trap cleanup EXIT INT TERM
-
-sleep .05 # wait a short while so we have time to lock the database (ugly and racy...)
-# verify that subsequent runs fail as we can't acquire the exclusive lock
-! interimap
-
-# line 177 is `$DBH->do("PRAGMA locking_mode = EXCLUSIVE");`
-xgrep -Fx "DBD::SQLite::db do failed: database is locked at ./interimap line 177." <"$STDERR"
-
-# vim: set filetype=sh :
diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/before.sql b/tests/00-db-migration-0-to-1-delim-mismatch/before.sql
deleted file mode 120000
index 0abb9bf..0000000
--- a/tests/00-db-migration-0-to-1-delim-mismatch/before.sql
+++ /dev/null
@@ -1 +0,0 @@
-../00-db-migration-0-to-1/before.sql \ No newline at end of file
diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/local.conf b/tests/00-db-migration-0-to-1-delim-mismatch/local.conf
deleted file mode 100644
index 08438cb..0000000
--- a/tests/00-db-migration-0-to-1-delim-mismatch/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\""
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf b/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf
deleted file mode 100644
index cc6781d..0000000
--- a/tests/00-db-migration-0-to-1-delim-mismatch/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ^
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-migration-0-to-1-delim-mismatch/run b/tests/00-db-migration-0-to-1-delim-mismatch/run
deleted file mode 100644
index 434c678..0000000
--- a/tests/00-db-migration-0-to-1-delim-mismatch/run
+++ /dev/null
@@ -1,8 +0,0 @@
-# import an existing non-migrated database
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql"
-! interimap
-
-# may happen if the server(s) software or its configuration changed
-xgrep -Fx 'ERROR: Local and remote hierachy delimiters differ (local "\"", remote "^"), refusing to update `mailboxes` table.' <"$STDERR"
-
-# vim: set filetype=sh :
diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf b/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/00-db-migration-0-to-1-foreign-key-violation/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf b/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-migration-0-to-1-foreign-key-violation/run b/tests/00-db-migration-0-to-1-foreign-key-violation/run
deleted file mode 100644
index f2d12a9..0000000
--- a/tests/00-db-migration-0-to-1-foreign-key-violation/run
+++ /dev/null
@@ -1,23 +0,0 @@
-# create new schema and add INBOX
-interimap
-xgrep "^Creating new schema in database file " <"$STDERR"
-xgrep -Fx "database: Created mailbox INBOX" <"$STDERR"
-
-# empty table `mailboxes` and revert its schema to version 0
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF
- PRAGMA foreign_keys = OFF;
- PRAGMA user_version = 0;
- DROP TABLE mailboxes;
- CREATE TABLE mailboxes (
- idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE,
- subscribed BOOLEAN NOT NULL
- );
-EOF
-
-# check that migration fails due to broken referential integrity
-! interimap
-xgrep -Fx "Upgrading database version from 0" <"$STDERR"
-xgrep -Fx "database: ERROR: Broken referential integrity! Refusing to commit changes." <"$STDERR"
-
-# vim: set filetype=sh :
diff --git a/tests/00-db-migration-0-to-1/local.conf b/tests/00-db-migration-0-to-1/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/00-db-migration-0-to-1/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/00-db-migration-0-to-1/remote.conf b/tests/00-db-migration-0-to-1/remote.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/00-db-migration-0-to-1/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-db/local.conf b/tests/01-rename-exists-db/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/01-rename-exists-db/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-db/remote.conf b/tests/01-rename-exists-db/remote.conf
deleted file mode 100644
index 61e3d0d..0000000
--- a/tests/01-rename-exists-db/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\\"
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-local/local.conf b/tests/01-rename-exists-local/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/01-rename-exists-local/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-local/remote.conf b/tests/01-rename-exists-local/remote.conf
deleted file mode 100644
index 61e3d0d..0000000
--- a/tests/01-rename-exists-local/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\\"
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-remote/local.conf b/tests/01-rename-exists-remote/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/01-rename-exists-remote/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename-exists-remote/remote.conf b/tests/01-rename-exists-remote/remote.conf
deleted file mode 100644
index 61e3d0d..0000000
--- a/tests/01-rename-exists-remote/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\\"
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename/local.conf b/tests/01-rename/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/01-rename/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/01-rename/remote.conf b/tests/01-rename/remote.conf
deleted file mode 100644
index cc6781d..0000000
--- a/tests/01-rename/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ^
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/02-delete/local.conf b/tests/02-delete/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/02-delete/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/02-delete/remote.conf b/tests/02-delete/remote.conf
deleted file mode 100644
index cc6781d..0000000
--- a/tests/02-delete/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ^
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list-partial/interimap.conf b/tests/03-sync-mailbox-list-partial/interimap.conf
deleted file mode 100644
index 4970867..0000000
--- a/tests/03-sync-mailbox-list-partial/interimap.conf
+++ /dev/null
@@ -1 +0,0 @@
-list-mailbox = *
diff --git a/tests/03-sync-mailbox-list-partial/local.conf b/tests/03-sync-mailbox-list-partial/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/03-sync-mailbox-list-partial/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list-partial/remote.conf b/tests/03-sync-mailbox-list-partial/remote.conf
deleted file mode 100644
index 352cdd4..0000000
--- a/tests/03-sync-mailbox-list-partial/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ~
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list-partial/run b/tests/03-sync-mailbox-list-partial/run
deleted file mode 100644
index 449115d..0000000
--- a/tests/03-sync-mailbox-list-partial/run
+++ /dev/null
@@ -1,57 +0,0 @@
-# try a bunch of invalid 'list-mailbox' values:
-# empty string, missing space between values, unterminated string
-for v in '""' '"f o o""bar"' '"f o o" "bar" "baz\" x'; do
- sed -ri "s/^(list-mailbox\\s*=\\s*).*/\\1${v//\\/\\\\}/" "$XDG_CONFIG_HOME/interimap/config"
- ! interimap
- xgrep -xF "Invalid value for list-mailbox: $v" <"$STDERR"
-done
-
-# create some mailboxes
-doveadm -u "local" mailbox create "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "bad"
-for m in "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "bad" "INBOX"; do
- sample_message | deliver -u "local" -- -m "$m"
-done
-
-# restrict 'list-mailbox' to the above minus "bad"
-sed -ri 's/^(list-mailbox\s*=\s*).*/\1foo "foo bar" "f\\\\\\"o\\x21o.*" "f\\0o\\0o"/' \
- "$XDG_CONFIG_HOME/interimap/config"
-
-# run partial sync
-interimap
-check_mailbox_list "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "INBOX" "f\\\"o!o" "f" "f.o"
-check_mailboxes_status "foo" "foo bar" "f\\\"o!o.bar" "f.o.o"
-
-# check that "bad" isn't in the remote imap server
-! doveadm -u "remote" mailbox status uidvalidity "bad"
-
-# check that "bad" and "INBOX" aren't in the database
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
- SELECT COUNT(*)
- FROM mailboxes
- WHERE mailbox = x'$(printf "%s" "bad" | xxd -ps)'
- OR mailbox = x'$(printf "%s" "INBOX" | xxd -ps)'
-EOF
-[ $(< "$TMPDIR/count") -eq 0 ]
-
-
-# run partial sync
-doveadm -u "remote" mailbox create "f\\\"o!o~baz" "f\\\"o!o~bad"
-for m in "f\\\"o!o~baz" "f\\\"o!o~bad"; do
- sample_message | deliver -u "remote" -- -m "$m"
-done
-interimap "f\\\"o!o.baz"
-
-check_mailbox_list "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "INBOX" "f\\\"o!o" "f" "f.o" "f\\\"o!o.baz"
-check_mailboxes_status "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" "f\\\"o!o.baz"
-
-# check that "bad", "f\\\"o!o.bad" and "INBOX" aren't in the database
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
- SELECT COUNT(*)
- FROM mailboxes
- WHERE mailbox = x'$(printf "%s" "bad" | xxd -ps)'
- OR mailbox = x'$(printf "%s" "INBOX" | xxd -ps)'
- OR mailbox = x'$(printf "%s\\0%s" "f\\\"o!o" "bad" | xxd -ps)'
-EOF
-[ $(< "$TMPDIR/count") -eq 0 ]
-
-# vim: set filetype=sh :
diff --git a/tests/03-sync-mailbox-list-ref/local.conf b/tests/03-sync-mailbox-list-ref/local.conf
deleted file mode 100644
index 6eccf43..0000000
--- a/tests/03-sync-mailbox-list-ref/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = /
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list-ref/remote.conf b/tests/03-sync-mailbox-list-ref/remote.conf
deleted file mode 100644
index 61e3d0d..0000000
--- a/tests/03-sync-mailbox-list-ref/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\\"
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list-ref/run b/tests/03-sync-mailbox-list-ref/run
deleted file mode 100644
index 3ead25d..0000000
--- a/tests/03-sync-mailbox-list-ref/run
+++ /dev/null
@@ -1,28 +0,0 @@
-# Note: implementation-dependent as the reference name is not a level of
-# mailbox hierarchy nor ends with the hierarchy delimiter
-sed -ri 's#^\[local\]$#&\nlist-reference = foo#; s#^\[remote\]$#&\nlist-reference = bar#' \
- "$XDG_CONFIG_HOME/interimap/config"
-
-# create a bunch of mailboxes in and out the respective list # references
-doveadm -u "local" mailbox create "foo" "foobar" "foo/bar/baz" "foo/baz" "bar"
-doveadm -u "remote" mailbox create "foo"
-
-# deliver somemessages to these mailboxes
-for m in "foo" "foobar" "foo/bar/baz" "foo/baz" "bar"; do
- sample_message | deliver -u "local" -- -m "$m"
-done
-sample_message | deliver -u "remote" -- -m "foo"
-
-interimap
-
-# check that the mailbox lists match
-diff -u --label="local/mailboxes" --label="remote/mailboxes" \
- <( doveadm -u "local" mailbox list | sed -n "s/^foo//p" | sort ) \
- <( doveadm -u "remote" mailbox list | sed -n "s/^bar//p" | tr '\\' '/' | sort )
-
-for m in "" "bar" "/bar/baz" "/baz"; do
- blob="x'$(printf "%s" "$m" | tr "/" "\\0" | xxd -c256 -ps)'"
- check_mailbox_status2 "$blob" "foo$m" "remote" "bar${m//\//\\}"
-done
-
-# vim: set filetype=sh :
diff --git a/tests/03-sync-mailbox-list/local.conf b/tests/03-sync-mailbox-list/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/03-sync-mailbox-list/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list/remote.conf b/tests/03-sync-mailbox-list/remote.conf
deleted file mode 100644
index 352cdd4..0000000
--- a/tests/03-sync-mailbox-list/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ~
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/03-sync-mailbox-list/run b/tests/03-sync-mailbox-list/run
deleted file mode 100644
index b506204..0000000
--- a/tests/03-sync-mailbox-list/run
+++ /dev/null
@@ -1,73 +0,0 @@
-# pre-create some mailboxes and susbscribe to some
-# foo: present on both, subscribed to both
-# bar: present on both, subscribed to local only
-# baz: present on both, subscribed to remote only
-# foo.bar: present on local only
-# foo.baz: present on remote only
-doveadm -u "local" mailbox create "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]"
-doveadm -u "local" mailbox subscribe "foo" "bar"
-doveadm -u "remote" mailbox create "foo" "bar" "baz" "foo~baz" "foo]bar"
-doveadm -u "remote" mailbox subscribe "foo" "baz"
-
-interimap
-xgrep -Fx "local: Subscribe to baz" <"$STDERR"
-xgrep -Fx "remote: Subscribe to bar" <"$STDERR"
-xgrep -Fx "local: Created mailbox foo.baz" <"$STDERR"
-xgrep -Fx "remote: Created mailbox foo~bar" <"$STDERR"
-
-# check syncing
-check_mailbox_list
-check_mailboxes_status "foo" "bar" "baz" "foo.bar" "foo.baz" "INBOX" "fo!o [b*a%r]" "foo]bar"
-check_mailbox_list -s
-
-
-# delete a mailbox one server and verify that synchronization fails as it's still in the database
-doveadm -u "remote" mailbox delete "foo~baz"
-! interimap
-xgrep -Fx 'database: ERROR: Mailbox foo.baz exists. Run `interimap --target=database --delete foo.baz` to delete.' <"$STDERR"
-interimap --target="database" --delete "foo.baz"
-xgrep -Fx 'database: Removed mailbox foo.baz' <"$STDERR"
-interimap # create again
-xgrep -Fx 'database: Created mailbox foo.baz' <"$STDERR"
-xgrep -Fx 'remote: Created mailbox foo~baz' <"$STDERR"
-
-doveadm -u "local" mailbox delete "foo.bar"
-! interimap
-xgrep -Fx 'database: ERROR: Mailbox foo.bar exists. Run `interimap --target=database --delete foo.bar` to delete.' <"$STDERR"
-interimap --target="database" --delete "foo.bar"
-xgrep -Fx 'database: Removed mailbox foo.bar' <"$STDERR"
-interimap
-xgrep -Fx 'database: Created mailbox foo.bar' <"$STDERR"
-xgrep -Fx 'local: Created mailbox foo.bar' <"$STDERR"
-
-check_mailbox_list
-check_mailboxes_status "foo" "bar" "baz" "foo.bar" "foo.baz" "INBOX" "fo!o [b*a%r]" "foo]bar"
-check_mailbox_list -s
-
-
-# (un)subscribe from some mailboxes, including a non-existent one
-doveadm -u "local" mailbox unsubscribe "foo"
-doveadm -u "remote" mailbox unsubscribe "bar"
-doveadm -u "local" mailbox subscribe "foo.bar" "foo.nonexistent" "foo.baz"
-doveadm -u "remote" mailbox subscribe "foo~bar" "bar~nonexistent"
-
-interimap
-xgrep -Fx 'remote: Unsubscribe to foo' <"$STDERR"
-xgrep -Fx 'local: Unsubscribe to bar' <"$STDERR"
-xgrep -Fx 'remote: Subscribe to foo~baz' <"$STDERR"
-check_mailbox_list
-check_mailbox_list -s $(doveadm -u "local" mailbox list) # exclude "foo.nonexistent" and "bar~nonexistent"
-
-# check that "baz", "foo.bar" and "foo.baz" are the only subscribed mailboxes
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
- SELECT COUNT(*)
- FROM mailboxes
- WHERE subscribed <> (mailbox IN (
- x'$(printf "%s" "baz" | xxd -ps)',
- x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)',
- x'$(printf "%s\\0%s" "foo" "baz" | xxd -ps)'
- ))
-EOF
-[ $(< "$TMPDIR/count") -eq 0 ]
-
-# vim: set filetype=sh :
diff --git a/tests/04-resume/local.conf b/tests/04-resume/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/04-resume/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/04-resume/remote.conf b/tests/04-resume/remote.conf
deleted file mode 100644
index 352cdd4..0000000
--- a/tests/04-resume/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ~
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/05-repair/local.conf b/tests/05-repair/local.conf
deleted file mode 100644
index 93497d9..0000000
--- a/tests/05-repair/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/05-repair/remote.conf b/tests/05-repair/remote.conf
deleted file mode 100644
index 352cdd4..0000000
--- a/tests/05-repair/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ~
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/06-largeint/local.conf b/tests/06-largeint/local.conf
deleted file mode 100644
index 9c838fd..0000000
--- a/tests/06-largeint/local.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace inbox {
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/06-largeint/remote.conf b/tests/06-largeint/remote.conf
deleted file mode 100644
index 9c838fd..0000000
--- a/tests/06-largeint/remote.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace inbox {
- location = maildir:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/07-sync-live-multi/remote.conf b/tests/07-sync-live-multi/remote.conf
deleted file mode 100644
index 3267182..0000000
--- a/tests/07-sync-live-multi/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ^
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/07-sync-live-multi/remote2.conf b/tests/07-sync-live-multi/remote2.conf
deleted file mode 100644
index 062429e..0000000
--- a/tests/07-sync-live-multi/remote2.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "\\"
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/07-sync-live-multi/remote3.conf b/tests/07-sync-live-multi/remote3.conf
deleted file mode 100644
index a4b9b1c..0000000
--- a/tests/07-sync-live-multi/remote3.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = "?"
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/07-sync-live/local.conf b/tests/07-sync-live/local.conf
deleted file mode 100644
index 1333540..0000000
--- a/tests/07-sync-live/local.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = .
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/07-sync-live/remote.conf b/tests/07-sync-live/remote.conf
deleted file mode 100644
index 3267182..0000000
--- a/tests/07-sync-live/remote.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace inbox {
- separator = ^
- location = dbox:~/inbox:LAYOUT=index
- inbox = yes
- list = yes
-}
diff --git a/tests/auth-login/interimap.remote b/tests/auth-login/interimap.remote
new file mode 100644
index 0000000..b7d67bf
--- /dev/null
+++ b/tests/auth-login/interimap.remote
@@ -0,0 +1,5 @@
+type = imap
+host = localhost
+port = 10143
+STARTTLS = NO
+auth = login
diff --git a/tests/auth-login/remote.conf b/tests/auth-login/remote.conf
new file mode 100644
index 0000000..4ab127a
--- /dev/null
+++ b/tests/auth-login/remote.conf
@@ -0,0 +1,2 @@
+!include conf.d/imapd.conf
+auth_mechanisms = plain login
diff --git a/tests/auth-login/t b/tests/auth-login/t
new file mode 100644
index 0000000..7fd83d5
--- /dev/null
+++ b/tests/auth-login/t
@@ -0,0 +1,12 @@
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+# check that credentials aren't leaked to the debug output
+interimap --debug || error
+grep -Fx "remote: C: xxx LOGIN [REDACTED]" <"$STDERR" || error
+
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :
diff --git a/tests/auth-logindisabled/interimap.remote b/tests/auth-logindisabled/interimap.remote
new file mode 120000
index 0000000..a4ea3f3
--- /dev/null
+++ b/tests/auth-logindisabled/interimap.remote
@@ -0,0 +1 @@
+../auth-sasl-plain/interimap.remote \ No newline at end of file
diff --git a/tests/auth-logindisabled/remote.conf b/tests/auth-logindisabled/remote.conf
new file mode 100644
index 0000000..1f02afe
--- /dev/null
+++ b/tests/auth-logindisabled/remote.conf
@@ -0,0 +1,4 @@
+!include conf.d/imapd.conf
+
+# trick dovecot into treating local connections as insecure
+imap_capability = +LOGINDISABLED
diff --git a/tests/auth-logindisabled/t b/tests/auth-logindisabled/t
new file mode 100644
index 0000000..0bcd0d6
--- /dev/null
+++ b/tests/auth-logindisabled/t
@@ -0,0 +1,16 @@
+! interimap --debug || error
+
+# double check the presence of 'LOGINDISABLED' in the preauth capability list
+grep -oE -m1 '^remote: S: \* OK \[CAPABILITY IMAP4rev1( [^]]*)? AUTH=[^]]*\]' <"$STDERR" >"$TMPDIR/capability"
+
+sed -ri 's/^remote: S: \* OK \[CAPABILITY (.*)\]$/\1/' "$TMPDIR/capability"
+tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
+grep -Fx "IMAP4rev1" <"$TMPDIR/capabilities" || error
+grep -Fx "LOGINDISABLED" <"$TMPDIR/capabilities" || error
+! grep -Fx "STARTTLS" <"$TMPDIR/capabilities" || error # otherwise we'd try to upgrade the connectionn
+
+# make sure we didn't send any credentials
+grep -Fx "remote: ERROR: Logins are disabled." <"$STDERR" || error
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/auth-noplaintext/interimap.remote b/tests/auth-noplaintext/interimap.remote
new file mode 100644
index 0000000..60567e2
--- /dev/null
+++ b/tests/auth-noplaintext/interimap.remote
@@ -0,0 +1,3 @@
+type = imap
+host = localhost
+port = 10143
diff --git a/tests/auth-noplaintext/remote.conf b/tests/auth-noplaintext/remote.conf
new file mode 120000
index 0000000..dbbb908
--- /dev/null
+++ b/tests/auth-noplaintext/remote.conf
@@ -0,0 +1 @@
+../auth-sasl-plain/remote.conf \ No newline at end of file
diff --git a/tests/auth-noplaintext/t b/tests/auth-noplaintext/t
new file mode 100644
index 0000000..11d7d4d
--- /dev/null
+++ b/tests/auth-noplaintext/t
@@ -0,0 +1,15 @@
+! interimap --debug || error
+
+# double check the presence of 'STARTTLS' in the preauth capability list
+grep -oE -m1 '^remote: S: \* OK \[CAPABILITY IMAP4rev1( [^]]*)? AUTH=[^]]*\]' <"$STDERR" >"$TMPDIR/capability"
+
+sed -ri 's/^remote: S: \* OK \[CAPABILITY (.*)\]$/\1/' "$TMPDIR/capability"
+tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
+ grep -Fx "IMAP4rev1" <"$TMPDIR/capabilities" || error
+! grep -Fx "STARTTLS" <"$TMPDIR/capabilities" || error
+
+# make sure we didn't send any credentials
+grep -Fx "remote: ERROR: Server did not advertise STARTTLS capability." <"$STDERR" || error
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/auth-sasl-plain-no-ir/interimap.remote b/tests/auth-sasl-plain-no-ir/interimap.remote
new file mode 120000
index 0000000..a4ea3f3
--- /dev/null
+++ b/tests/auth-sasl-plain-no-ir/interimap.remote
@@ -0,0 +1 @@
+../auth-sasl-plain/interimap.remote \ No newline at end of file
diff --git a/tests/auth-sasl-plain-no-ir/remote.conf b/tests/auth-sasl-plain-no-ir/remote.conf
new file mode 100644
index 0000000..dae9545
--- /dev/null
+++ b/tests/auth-sasl-plain-no-ir/remote.conf
@@ -0,0 +1,2 @@
+!include conf.d/imapd.conf
+!include conf.d/interimap-required-capabilities.conf
diff --git a/tests/auth-sasl-plain-no-ir/t b/tests/auth-sasl-plain-no-ir/t
new file mode 100644
index 0000000..17aa9e6
--- /dev/null
+++ b/tests/auth-sasl-plain-no-ir/t
@@ -0,0 +1,26 @@
+n=1 # at least one message to send remotely
+sample_message | deliver -u "local"
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ [ "$u" = "remote" ] || n=$(( n+1 ))
+ sample_message | deliver -u "$u"
+done
+
+# check that credentials aren't leaked to the debug output
+interimap --debug || error
+grep -Fx "remote: C: xxx AUTHENTICATE PLAIN [REDACTED]" <"$STDERR" || error
+
+# make sure we didn't use SASL-IR
+grep -oE -m1 '^remote: S: \* OK \[CAPABILITY IMAP4rev1( [^]]*)? AUTH=[^]]*\]' <"$STDERR" >"$TMPDIR/capability"
+
+sed -ri 's/^remote: S: \* OK \[CAPABILITY (.*)\]$/\1/' "$TMPDIR/capability"
+tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
+ grep -Fx "IMAP4rev1" <"$TMPDIR/capabilities" || error
+! grep -Fx "SASL-IR" <"$TMPDIR/capabilities" || error
+
+# make sure all literals were synchronizing (and that we didn't use MULTIAPPEND)
+xcgrep "$n" -E "^remote(\(INBOX\))?: C: [0-9]+ APPEND INBOX .* \{[0-9]+\}$" <"$STDERR"
+
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :
diff --git a/tests/auth-sasl-plain/interimap.remote b/tests/auth-sasl-plain/interimap.remote
new file mode 100644
index 0000000..9c0a623
--- /dev/null
+++ b/tests/auth-sasl-plain/interimap.remote
@@ -0,0 +1,4 @@
+type = imap
+host = localhost
+port = 10143
+STARTTLS = NO
diff --git a/tests/auth-sasl-plain/remote.conf b/tests/auth-sasl-plain/remote.conf
new file mode 100644
index 0000000..3ccbd42
--- /dev/null
+++ b/tests/auth-sasl-plain/remote.conf
@@ -0,0 +1 @@
+!include conf.d/imapd.conf
diff --git a/tests/auth-sasl-plain/t b/tests/auth-sasl-plain/t
new file mode 100644
index 0000000..68f71a9
--- /dev/null
+++ b/tests/auth-sasl-plain/t
@@ -0,0 +1,12 @@
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+# check that credentials aren't leaked to the debug output
+interimap --debug || error
+grep -Fx "remote: C: xxx AUTHENTICATE PLAIN [REDACTED]" <"$STDERR" || error
+
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :
diff --git a/tests/compress/interimap.remote b/tests/compress/interimap.remote
new file mode 120000
index 0000000..a4ea3f3
--- /dev/null
+++ b/tests/compress/interimap.remote
@@ -0,0 +1 @@
+../auth-sasl-plain/interimap.remote \ No newline at end of file
diff --git a/tests/compress/remote.conf b/tests/compress/remote.conf
new file mode 120000
index 0000000..dbbb908
--- /dev/null
+++ b/tests/compress/remote.conf
@@ -0,0 +1 @@
+../auth-sasl-plain/remote.conf \ No newline at end of file
diff --git a/tests/compress/t b/tests/compress/t
new file mode 100644
index 0000000..5625761
--- /dev/null
+++ b/tests/compress/t
@@ -0,0 +1,19 @@
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+# compression enabled by default
+interimap --debug || error
+grep -Fx "remote: C: 000001 COMPRESS DEFLATE" <"$STDERR" || error
+grep -E "^remote: S: 000001 OK( |$)" <"$STDERR" || error
+
+check_mailbox_status "INBOX"
+
+
+# can be disabled
+echo "compress = no" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || error
+! grep -E "^remote: C: [^[:blank:]]+ COMPRESS DEFLATE$" <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/condstore/t b/tests/condstore/t
new file mode 100644
index 0000000..d4da50f
--- /dev/null
+++ b/tests/condstore/t
@@ -0,0 +1,50 @@
+TIMEOUT=60
+N=4096
+
+# test CONDSTORE/QRESYNC (behavior) in UID STORE commands, in particular
+# the UNCHANGEDSINCE test: populate, keep assiging keywords at random,
+# and make sure interimap is able to reconciliate the changes
+
+# populate (with dummy messages to speed things up) only one server
+# before initializing interimap, so UIDs concide with sequence numbers
+# and are identical on both servers
+for ((i = 0; i < N; i++)); do
+ deliver -u "local" <<< .
+done
+
+interimap_init
+
+# assign a set of 16 tags; not more because in order to maximize the
+# likelyhood of conflicts we want UID STORE commands to use large sets
+declare -a FLAGS=(0 1 2 3 4 5 6 7 8 9 a b c d e f)
+
+# start a long-lived interimap process
+interimap --watch=1 & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+timer=$(( $(date +%s) + TIMEOUT ))
+while [ $(date +%s) -le $timer ]; do
+ a="$(shuf -n1 -e "add" "remove" "replace")"
+ u="$(shuf -n1 -e "local" "remote")"
+ f="$(shuf -n1 -e "${FLAGS[@]}")"
+ seqs="$(shuf -n$((N/8)) -i1-$N)" # trigger changes on 1/8 of all messages
+ doveadm -u "$u" flags "$a" "$f" mailbox "INBOX" "${seqs//$'\n'/,}"
+ sleep "0.0$(shuf -n1 -i10-99)" # 10 to 99ms
+done
+sleep 2
+
+ptree_abort $PID
+trap - EXIT INT TERM
+
+# make sure the list of uids for a given tag match
+flagged_uids() {
+ local u="$1" f="$2"
+ doveadm -u "$u" search mailbox "INBOX" keyword "$f" | cut -d" " -f2 | sort -n
+}
+for f in "${FLAGS[@]}"; do
+ diff --label="local/$f" --label="remote/$f" -u -- \
+ <(flagged_uids "local" "$f") <(flagged_uids "remote" "$f") ||
+ error "UID list differs for keyword '$f'"
+done
+
+# vim: set filetype=sh :
diff --git a/tests/db-exclusive-lock/t b/tests/db-exclusive-lock/t
new file mode 100644
index 0000000..88172c9
--- /dev/null
+++ b/tests/db-exclusive-lock/t
@@ -0,0 +1,16 @@
+interimap_init
+
+# start a background process
+interimap --watch=60 &
+trap "ptree_abort $!" EXIT INT TERM
+
+# wait a short while so we have time to lock the database (ugly and racy...)
+sleep .5
+
+# subsequent runs fail as we can't acquire the exclusive lock
+! interimap || error
+
+grep -Fx "DBD::SQLite::db do failed: database is locked at ./interimap line 177." <"$STDERR" \
+ || error "Is \$DBH->do(\"PRAGMA locking_mode = EXCLUSIVE\"); at line 177?"
+
+# vim: set filetype=sh :
diff --git a/tests/db-migration-0-1-foreign-key-violation/t b/tests/db-migration-0-1-foreign-key-violation/t
new file mode 100644
index 0000000..35e5be5
--- /dev/null
+++ b/tests/db-migration-0-1-foreign-key-violation/t
@@ -0,0 +1,21 @@
+interimap_init
+grep -Fx "database: Created mailbox INBOX" <"$STDERR" || error "INBOX missing from DB"
+
+# empty table `mailboxes` and revert its schema to version 0
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF
+ PRAGMA foreign_keys = OFF;
+ PRAGMA user_version = 0;
+ DROP TABLE mailboxes;
+ CREATE TABLE mailboxes (
+ idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE,
+ subscribed BOOLEAN NOT NULL
+ );
+EOF
+
+# now migration must fail due to broken referential integrity
+! interimap || error
+grep -Fx "Upgrading database version from 0" <"$STDERR" || error "DB upgrade not attempted"
+grep -Fx "database: ERROR: Broken referential integrity! Refusing to commit changes." <"$STDERR" || error "DB upgrade successful despite broken refint"
+
+# vim: set filetype=sh :
diff --git a/tests/db-no-create--watch/t b/tests/db-no-create--watch/t
new file mode 100644
index 0000000..89f1e3e
--- /dev/null
+++ b/tests/db-no-create--watch/t
@@ -0,0 +1,6 @@
+! interimap --watch=60 || error
+
+grep -Ex "DBI connect\(.*\) failed: unable to open database file at \./interimap line 173\." <"$STDERR" || error
+test \! -e "$XDG_DATA_HOME/interimap/remote.db" || error
+
+# vim: set filetype=sh :
diff --git a/tests/db-upgrade-0-1-delim-mismatch/before.sql b/tests/db-upgrade-0-1-delim-mismatch/before.sql
new file mode 120000
index 0000000..6c31715
--- /dev/null
+++ b/tests/db-upgrade-0-1-delim-mismatch/before.sql
@@ -0,0 +1 @@
+../db-upgrade-0-1/before.sql \ No newline at end of file
diff --git a/tests/db-upgrade-0-1-delim-mismatch/local.conf b/tests/db-upgrade-0-1-delim-mismatch/local.conf
new file mode 100644
index 0000000..900c73f
--- /dev/null
+++ b/tests/db-upgrade-0-1-delim-mismatch/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\""
+}
diff --git a/tests/db-upgrade-0-1-delim-mismatch/remote.conf b/tests/db-upgrade-0-1-delim-mismatch/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/db-upgrade-0-1-delim-mismatch/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/db-upgrade-0-1-delim-mismatch/t b/tests/db-upgrade-0-1-delim-mismatch/t
new file mode 100644
index 0000000..d133437
--- /dev/null
+++ b/tests/db-upgrade-0-1-delim-mismatch/t
@@ -0,0 +1,7 @@
+# import an existing non-migrated database
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" || error "Couldn't import DB"
+! interimap || error
+
+grep -Fx 'ERROR: Local and remote hierachy delimiters differ (local "\"", remote "^"), refusing to update table `mailboxes`.' <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/00-db-migration-0-to-1/after.sql b/tests/db-upgrade-0-1/after.sql
index 18b0ad7..18b0ad7 100644
--- a/tests/00-db-migration-0-to-1/after.sql
+++ b/tests/db-upgrade-0-1/after.sql
diff --git a/tests/00-db-migration-0-to-1/before.sql b/tests/db-upgrade-0-1/before.sql
index 333a1dc..333a1dc 100644
--- a/tests/00-db-migration-0-to-1/before.sql
+++ b/tests/db-upgrade-0-1/before.sql
diff --git a/tests/db-upgrade-0-1/local.conf b/tests/db-upgrade-0-1/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/db-upgrade-0-1/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/db-upgrade-0-1/remote.conf b/tests/db-upgrade-0-1/remote.conf
new file mode 120000
index 0000000..b798ff5
--- /dev/null
+++ b/tests/db-upgrade-0-1/remote.conf
@@ -0,0 +1 @@
+local.conf \ No newline at end of file
diff --git a/tests/00-db-migration-0-to-1/run b/tests/db-upgrade-0-1/t
index 757fe04..088008e 100644
--- a/tests/00-db-migration-0-to-1/run
+++ b/tests/db-upgrade-0-1/t
@@ -1,14 +1,14 @@
-# create some mailboxes
+# create the mailboxes from the database
doveadm -u "local" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
doveadm -u "remote" mailbox create "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
# import an existing non-migrated database
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql"
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <"$TESTDIR/before.sql" || error "Couldn't import DB"
# migrate
-interimap
+interimap || error "Couldn't upgrade DB"
-xgrep -Fx "Upgrading database version from 0" <"$STDERR"
+grep -Fx "Upgrading database version from 0" <"$STDERR" || error "Couldn't upgrade DB"
check_mailboxes_status "a.b1.c1" "a.b1.c2" "a.b2.c" "a2"
# verify that the new schema is as expected
@@ -28,6 +28,7 @@ EOF
# XXX need 'user_version' PRAGMA in the dump for future migrations
# http://sqlite.1065341.n5.nabble.com/dump-command-and-user-version-td101228.html
diff -u --label="a/dump.sql" --label="b/dump.sql" \
- "$TMPDIR/dump-expected.sql" "$TMPDIR/dump.sql"
+ "$TMPDIR/dump-expected.sql" "$TMPDIR/dump.sql" \
+ || error "DB dumps differ"
# vim: set filetype=sh :
diff --git a/tests/delete/local.conf b/tests/delete/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/delete/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/delete/remote.conf b/tests/delete/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/delete/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/02-delete/run b/tests/delete/t
index f63c52c..c38d4d3 100644
--- a/tests/02-delete/run
+++ b/tests/delete/t
@@ -4,15 +4,16 @@ for m in "foo.bar" "foo.bar.baz" "INBOX"; do
sample_message | deliver -u "local" -- -m "$m"
done
-interimap
+interimap_init
check_mailbox_list
check_mailboxes_status "foo.bar" "foo.bar.baz" "INBOX"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF
.dump
EOF
-# delete non-existent mailbox is a no-op
-interimap --target="local,remote" --target="database" --delete "nonexistent"
+
+step_start "nonexistent source (no-op)"
+interimap --target="local,remote" --target="database" --delete "nonexistent" || error
check_mailbox_list
check_mailboxes_status "foo.bar" "foo.bar.baz" "INBOX"
@@ -20,13 +21,22 @@ sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
EOF
diff -u --label="a/dump.sql" --label="b/dump.sql" \
- "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql"
+ "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" || error "SQL dumps differ"
+step_done
+
# foo.bar will become \NoSelect in local, per RFC 3501: "It is permitted
# to delete a name that has inferior hierarchical names and does not
# have the \Noselect mailbox name attribute. In this case, all messages
# in that mailbox are removed, and the name will acquire the \Noselect
# mailbox name attribute."
+step_start "mailbox with inferiors"
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes
+ WHERE mailbox != x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)'
+ ORDER BY idx
+EOF
+
interimap --target="local" --delete "foo.bar"
check_mailbox_list
@@ -35,33 +45,51 @@ check_mailboxes_status "foo.bar.baz" "INBOX"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
EOF
-diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql"
+diff -u --label="a/dump.sql" --label="b/dump.sql" \
+ "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" || error "SQL dumps differ"
! doveadm -u "local" mailbox status uidvalidity "foo.bar" # gone
doveadm -u "remote" mailbox status uidvalidity "foo^bar"
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF
- SELECT idx, mailbox
- FROM mailboxes
- WHERE mailbox != x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)'
-EOF
-
-
# now delete from the remote server and the database
interimap --delete "foo.bar"
! doveadm -u "local" mailbox status uidvalidity "foo.bar"
! doveadm -u "remote" mailbox status uidvalidity "foo^bar"
-sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF
- SELECT idx, mailbox
- FROM mailboxes
- WHERE mailbox != x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)'
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes ORDER BY idx
EOF
-diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \
- "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv"
+diff -u --label="a/mailboxes.sql" --label="b/mailboxes.sql" \
+ "$TMPDIR/mailboxes.sql" "$TMPDIR/mailboxes2.sql" || error "SQL dumps differ"
check_mailbox_list
check_mailboxes_status "foo.bar.baz" "INBOX"
+step_done
+
+
+step_start "INBOX (fail)"
+! interimap --delete "InBoX" || error "deleted INBOX"
+grep -Fx "ERROR: INBOX can't be deleted" <"$STDERR" || error
+
+check_mailbox_list
+check_mailboxes_status "foo.bar.baz" "INBOX"
+step_done
+
+
+step_start "\\Noinferiors mailbox"
+interimap --delete "foo.bar.baz"
+
+! doveadm -u "local" mailbox status uidvalidity "foo.bar.baz"
+! doveadm -u "remote" mailbox status uidvalidity "foo^bar^baz"
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
+ SELECT COUNT(*) FROM mailboxes
+EOF
+[ "$(< "$TMPDIR/count" )" -eq 1 ] || error "Not only INBOX left?"
+
+check_mailbox_list
+check_mailboxes_status "INBOX"
+step_done
# vim: set filetype=sh :
diff --git a/tests/ignore-mailbox/interimap.conf b/tests/ignore-mailbox/interimap.conf
new file mode 100644
index 0000000..6168958
--- /dev/null
+++ b/tests/ignore-mailbox/interimap.conf
@@ -0,0 +1 @@
+ignore-mailbox = ^virtual(?:\x00|$)
diff --git a/tests/ignore-mailbox/local.conf b/tests/ignore-mailbox/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/ignore-mailbox/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/ignore-mailbox/remote.conf b/tests/ignore-mailbox/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/ignore-mailbox/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/ignore-mailbox/t b/tests/ignore-mailbox/t
new file mode 100644
index 0000000..f90227c
--- /dev/null
+++ b/tests/ignore-mailbox/t
@@ -0,0 +1,62 @@
+doveadm -u "local" mailbox create "foo" -- "-virtual"
+doveadm -u "remote" mailbox create "bar" -- "virtual-"
+
+interimap_init
+check_mailbox_list
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes ORDER BY idx
+EOF
+
+for ((i = 0; i < 16; i++)); do
+ u="$(shuf -n1 -e "local" "remote")" # choose target at random
+ m="$(shuf -n1 -e -- "INBOX" "foo" "bar")"
+ sample_message | deliver -u "$u" -- -m "$m"
+done
+
+# create new mailboxes matching 'ignore-mailbox'
+doveadm -u "local" mailbox create "virtual" "virtual.foo"
+doveadm -u "remote" mailbox create "virtual^bar"
+for n in $(seq 1 "$(shuf -n1 -i1-8)"); do
+ sample_message | deliver -u "local" -- -m "virtual"
+ sample_message | deliver -u "local" -- -m "virtual.foo"
+done
+for n in $(seq 1 "$(shuf -n1 -i1-8)"); do
+ sample_message | deliver -u "remote" -- -m "virtual^bar"
+done
+
+
+# no new mailbox should be created
+interimap || error
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes ORDER BY idx
+EOF
+diff -u --label="a/mailboxes.sql" --label="b/mailboxes.sql" \
+ "$TMPDIR/mailboxes.sql" "$TMPDIR/mailboxes2.sql" || error "SQL dumps differ"
+
+check_mailboxes_status "INBOX" "foo" "bar"
+
+# double check the unsubscribed mailboxes weren't copied
+! doveadm -u "remote" mailbox status uidvalidity "virtual" || error
+! doveadm -u "remote" mailbox status uidvalidity "virtual^foo" || error
+! doveadm -u "local" mailbox status uidvalidity "virtual.bar" || error
+
+
+# ignored mailboxes are created when passed to the command line
+interimap "virtual" "virtual.bar" || error
+grep -Fx "database: Created mailbox virtual" <"$STDERR" || error
+grep -Fx "database: Created mailbox virtual.bar" <"$STDERR" || error
+grep -Fx "local: Created mailbox virtual.bar" <"$STDERR" || error
+grep -Fx "remote: Created mailbox virtual" <"$STDERR" || error
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes
+ WHERE mailbox != x'$(printf "virtual" | xxd -ps)'
+ AND mailbox != x'$(printf "%s\\0%s" "virtual" "foo" | xxd -ps)'
+ ORDER BY idx
+EOF
+
+check_mailboxes_status "virtual" "virtual.bar" || error
+
+# vim: set filetype=sh :
diff --git a/tests/06-largeint/run b/tests/largeint/t
index b08bcfa..b0877d5 100644
--- a/tests/06-largeint/run
+++ b/tests/largeint/t
@@ -12,13 +12,14 @@ doveadm -u "remote" mailbox update --uid-validity 2147483647 "bar" # 2^31-1
doveadm -u "remote" mailbox update --uid-validity 1 "baz" #
run() {
- local u m
- for u in local remote; do
- for m in "INBOX" "foo" "bar" "baz"; do
- sample_message | deliver -u "$u" -- -m "$m"
- done
+ local u m i
+ for ((i = 0; i < 64; i++)); do
+ u="$(shuf -n1 -e "local" "remote")" # choose target at random
+ m="$(shuf -n1 -e -- "INBOX" "foo" "bar" "baz")"
+ sample_message | deliver -u "$u" -- -m "$m"
done
- interimap
+ interimap || error
+ check_mailbox_list
check_mailbox_status "INBOX" "foo" "bar" "baz"
}
run
diff --git a/tests/list b/tests/list
new file mode 100644
index 0000000..52417c1
--- /dev/null
+++ b/tests/list
@@ -0,0 +1,60 @@
+db-no-create--watch `interimap --watch` refuses to create the database
+db-exclusive-lock mutually exclusive DB access
+
+. DB schema upgrade (v0 -> v1)
+ db-upgrade-0-1 migrate
+ # may happen if the server(s) software or its configuration changed
+ db-upgrade-0-1-delim-mismatch abort on hierarchy delimiter mismatch
+ # foreign key checking was broken until v0.5
+ db-migration-0-1-foreign-key-violation abort on foreign key contraint violation
+
+. Mailbox deletion
+ ... delete
+
+. Mailbox renaming
+ rename-exists-db abort if target exists in the DB
+ rename-exists-local abort if target exists locally
+ rename-exists-remote abort if target exists remotely
+ ... rename-simple
+ ... rename-inferiors
+
+# try values beyond the signed integer limit
+largeint Large UIDVALIDITY/UIDNEXT/HIGHESTMODSEQ values
+
+. Mailbox synchronization
+ ... sync-mailbox-list
+ list-reference list-reference
+ list-mailbox list-mailbox = foo "foo bar" "f\\\"o\x21o.*" "f\0o\0o"
+ list-select-opts list-select-opts = SUBSCRIBED
+ ignore-mailbox ignore-mailbox = ^virtual(?:\x00|$)
+
+resume Resume when aborted
+repair --repair
+
+. Authentication
+ auth-sasl-plain AUTHENTICATE (SASL PLAIN)
+ auth-sasl-plain-no-ir AUTHENTICATE (SASL PLAIN, no SASL-IR)
+ auth-login LOGIN
+ auth-logindisabled LOGINDISABLED
+ auth-noplaintext abort when STARTTLS is not offered
+
+compress COMPRESS=DEFLATE
+condstore CONDSTORE
+split-set Split large sets to avoid extra-long command lines
+
+. SSL/TLS
+ starttls-logindisabled LOGINDISABLED STARTTLS
+ starttls STARTTLS
+ tls SSL/TLS handshake
+ ... tls-verify-peer
+ tls-pin-fingerprint pubkey fingerprint pinning
+ tls-protocols force TLS protocol versions
+
+. Live synchronization (60s)
+ sync-live local/remote simulation
+ sync-live-crippled local/remote simulation (crippled remote)
+ sync-live-tls local/remote simulation (TLS remote)
+ sync-live-multi local/remote1+remote2+remote3 simulation (3 local namespaces)
+
+. pullimap
+ ... pullimap
diff --git a/tests/list-mailbox/interimap.conf b/tests/list-mailbox/interimap.conf
new file mode 100644
index 0000000..6702107
--- /dev/null
+++ b/tests/list-mailbox/interimap.conf
@@ -0,0 +1 @@
+list-mailbox = foo "foo bar" "f\\\"o\x21o.*" "f\0o\0o"
diff --git a/tests/list-mailbox/local.conf b/tests/list-mailbox/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/list-mailbox/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/list-mailbox/remote.conf b/tests/list-mailbox/remote.conf
new file mode 100644
index 0000000..2d6b9e0
--- /dev/null
+++ b/tests/list-mailbox/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ?
+}
diff --git a/tests/list-mailbox/t b/tests/list-mailbox/t
new file mode 100644
index 0000000..e905537
--- /dev/null
+++ b/tests/list-mailbox/t
@@ -0,0 +1,57 @@
+# create and populate some mailboxes locally
+declare -a MAILBOXES=( "foo" "foo bar" "f\\\"o!o.bar" "f.o.o" )
+doveadm -u "local" mailbox create -- "${MAILBOXES[@]}" "foobad" "baz" "INBOX"
+for ((i = 0; i < 32; i++)); do
+ m="$(shuf -n1 -e -- "${MAILBOXES[@]}" "foobad" "baz" "INBOX")"
+ sample_message | deliver -u "local" -- -m "$m"
+done
+
+interimap_init
+for m in "${MAILBOXES[@]}"; do
+ grep -Fx "remote: Created mailbox ${m//./?}" <"$STDERR" || error "${m//./?}"
+ grep -Fx "database: Created mailbox $m" <"$STDERR" || error
+done
+
+# also check inferiors in the list, but exclude "foobad" and "baz"
+check_mailbox_list "${MAILBOXES[@]}" "INBOX" "f\\\"o!o" "f" "f.o"
+check_mailboxes_status "${MAILBOXES[@]}" || error
+
+# double check that "foobad" and "baz" weren't created
+! doveadm -u "remote" mailbox status uidvalidity "foobad" || error
+! doveadm -u "remote" mailbox status uidvalidity "baz" || error
+
+# check that "foobad" and "INBOX" aren't in the database
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
+ SELECT COUNT(*)
+ FROM mailboxes
+ WHERE mailbox = x'$(printf "%s" "foobad" | xxd -u -ps)'
+ OR mailbox = x'$(printf "%s" "INBOX" | xxd -u -ps)'
+ OR mailbox = x'$(printf "%s" "baz" | xxd -u -ps)'
+EOF
+[ $(< "$TMPDIR/count") -eq 0 ] || error
+
+
+# mailbox given on the command line overrides list-mailbox
+sample_message | deliver -u "local" -- -m "foobad"
+sample_message | deliver -u "local" -- -m "foo"
+interimap "foobad" || error
+! grep -F "remote(foo): Added 1 UID(s)" <"$STDERR" || error
+check_mailbox_list "foobad"
+check_mailbox_status "foobad"
+
+interimap "foo" || error
+grep -F "remote(foo): Added 1 UID(s)" <"$STDERR" || error
+check_mailbox_status "foo"
+! check_mailbox_list "baz"
+
+
+# finally, try a bunch of invalid 'list-mailbox' values to test the parser:
+# empty string, missing space between values, unterminated string
+for v in '""' '"f o o""bar"' '"f o o" "bar" "baz\" x'; do
+ sed -ri "s/^(list-mailbox\\s*=\\s*).*/\\1${v//\\/\\\\}/" \
+ "$XDG_CONFIG_HOME/interimap/config"
+ ! interimap || error
+ grep -xF "Invalid value for list-mailbox: $v" <"$STDERR"
+done
+
+# vim: set filetype=sh :
diff --git a/tests/list-reference/interimap.local b/tests/list-reference/interimap.local
new file mode 100644
index 0000000..a013813
--- /dev/null
+++ b/tests/list-reference/interimap.local
@@ -0,0 +1 @@
+list-reference = foo/
diff --git a/tests/list-reference/interimap.remote b/tests/list-reference/interimap.remote
new file mode 100644
index 0000000..f34119c
--- /dev/null
+++ b/tests/list-reference/interimap.remote
@@ -0,0 +1 @@
+list-reference = bar\
diff --git a/tests/list-reference/local.conf b/tests/list-reference/local.conf
new file mode 100644
index 0000000..93e4860
--- /dev/null
+++ b/tests/list-reference/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = /
+}
diff --git a/tests/list-reference/remote.conf b/tests/list-reference/remote.conf
new file mode 100644
index 0000000..9657e89
--- /dev/null
+++ b/tests/list-reference/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\\"
+}
diff --git a/tests/list-reference/t b/tests/list-reference/t
new file mode 100644
index 0000000..a2cc9c7
--- /dev/null
+++ b/tests/list-reference/t
@@ -0,0 +1,47 @@
+# create and populate some mailboxes in and out the respective list references
+doveadm -u "local" mailbox create "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" "bar/baz"
+doveadm -u "remote" mailbox create "foo" "foobaz" "foo/baz" "foo\\baz" "bar\\baz" "bar\\!"
+
+populate() {
+ local i
+ for ((i = 0; i < 32; i++)); do
+ m="$(shuf -n1 -e -- "foo" "foobar" "foo/bar/baz" "foo/baz" "bar" "bar/baz")"
+ sample_message | deliver -u "local" -- -m "$m"
+
+ m="$(shuf -n1 -e -- "foo" "foobar" "foo/baz" "foo\\baz" "bar\\baz" "bar\\!")"
+ sample_message | deliver -u "remote" -- -m "$m"
+ done
+}
+populate
+
+interimap_init
+grep -Fx "database: Created mailbox bar/baz" <"$STDERR" || error
+grep -Fx "database: Created mailbox baz" <"$STDERR" || error
+grep -Fx "database: Created mailbox !" <"$STDERR" || error
+grep -Fx "local: Created mailbox foo/!" <"$STDERR" || error
+grep -Fx "remote: Created mailbox bar\\bar\\baz" <"$STDERR" || error
+
+verify() {
+ # check that the mailbox lists match
+ diff -u --label="local/mailboxes" --label="remote/mailboxes" \
+ <( doveadm -u "local" mailbox list | sed -n 's,^foo/,,p' | sort ) \
+ <( doveadm -u "remote" mailbox list | sed -n 's,^bar\\,,p' | tr '\\' '/' | sort ) \
+ || error "mailbox lists differ"
+
+ for m in "bar/baz" "baz" "!"; do
+ blob="x'$(printf "%s" "$m" | tr "/" "\\0" | xxd -c256 -u -ps)'"
+ check_mailbox_status2 "$blob" "foo/$m" "remote" "bar\\${m//\//\\}"
+ done
+}
+verify
+
+# add more messages and re-check
+populate
+interimap || error
+verify
+
+# double check that mailboxes outside references weren't created
+! doveadm -u "local" mailbox status uidvalidity "foobaz" || error
+! doveadm -u "remote" mailbox status uidvalidity "foobar" || error
+
+# vim: set filetype=sh :
diff --git a/tests/list-select-opts/interimap.conf b/tests/list-select-opts/interimap.conf
new file mode 100644
index 0000000..2fa632d
--- /dev/null
+++ b/tests/list-select-opts/interimap.conf
@@ -0,0 +1 @@
+list-select-opts = SUBSCRIBED
diff --git a/tests/list-select-opts/local.conf b/tests/list-select-opts/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/list-select-opts/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/list-select-opts/remote.conf b/tests/list-select-opts/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/list-select-opts/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/list-select-opts/t b/tests/list-select-opts/t
new file mode 100644
index 0000000..98acb43
--- /dev/null
+++ b/tests/list-select-opts/t
@@ -0,0 +1,56 @@
+doveadm -u "local" mailbox create -s "INBOX" "foo.bar"
+doveadm -u "remote" mailbox create -s "INBOX" "bar"
+
+interimap_init
+check_mailbox_list
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes ORDER BY idx
+EOF
+
+for ((i = 0; i < 16; i++)); do
+ u="$(shuf -n1 -e "local" "remote")" # choose target at random
+ m="$(shuf -n1 -e -- "INBOX" "foo.bar" "bar")"
+ sample_message | deliver -u "$u" -- -m "$m"
+done
+
+# create new unsubscribed mailboxes
+doveadm -u "local" mailbox create "foo"
+doveadm -u "remote" mailbox create "baz"
+
+for ((i = 0; i < 8; i++)); do
+ u="$(shuf -n1 -e "local" "remote")" # choose target at random
+ [ u="local" ] && m="foo" || m="baz"
+ sample_message | deliver -u "$u" -- -m "$m"
+done
+
+
+# no new mailbox should be created
+interimap || error
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.sql" <<-EOF
+ SELECT idx, mailbox FROM mailboxes ORDER BY idx
+EOF
+diff -u --label="a/mailboxes.sql" --label="b/mailboxes.sql" \
+ "$TMPDIR/mailboxes.sql" "$TMPDIR/mailboxes2.sql" || error "SQL dumps differ"
+
+check_mailboxes_status "INBOX" "foo.bar" "bar"
+
+# double check the unsubscribed mailboxes weren't copied
+! doveadm -u "remote" mailbox status uidvalidity "foo" || error
+! doveadm -u "local" mailbox status uidvalidity "baz" || error
+
+# reconcile when subcribed
+doveadm -u "local" mailbox subscribe "foo"
+doveadm -u "remote" mailbox subscribe "baz"
+
+interimap || error
+grep -Fx "database: Created mailbox foo" <"$STDERR" || error
+grep -Fx "database: Created mailbox baz" <"$STDERR" || error
+grep -Fx "local: Created mailbox baz" <"$STDERR" || error
+grep -Fx "remote: Created mailbox foo" <"$STDERR" || error
+
+check_mailbox_list
+check_mailboxes_status "INBOX" "foo" "foo.bar" "bar" "baz"
+
+# vim: set filetype=sh :
diff --git a/tests/pullimap/interimap.remote b/tests/pullimap/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/pullimap/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/pullimap/local.conf b/tests/pullimap/local.conf
new file mode 100644
index 0000000..b67641f
--- /dev/null
+++ b/tests/pullimap/local.conf
@@ -0,0 +1 @@
+!include conf.d/lmtpd.conf
diff --git a/tests/pullimap/pullimap.conf b/tests/pullimap/pullimap.conf
new file mode 100644
index 0000000..3f6c2e1
--- /dev/null
+++ b/tests/pullimap/pullimap.conf
@@ -0,0 +1 @@
+deliver-method = lmtp:[127.0.0.1]:10024
diff --git a/tests/pullimap/remote.conf b/tests/pullimap/remote.conf
new file mode 120000
index 0000000..6029749
--- /dev/null
+++ b/tests/pullimap/remote.conf
@@ -0,0 +1 @@
+../tls/remote.conf \ No newline at end of file
diff --git a/tests/pullimap/t b/tests/pullimap/t
new file mode 100644
index 0000000..7ae0c5f
--- /dev/null
+++ b/tests/pullimap/t
@@ -0,0 +1,96 @@
+MAILBOX="INBOX"
+TIMEOUT=60
+N=2048
+
+step_start "\`pullimap --idle\` refuses to create the state file"
+! pullimap --idle "remote" || error
+step_done
+
+# make sure remote UIDs are 11-bytes long
+doveadm -u "remote" mailbox update --min-next-uid 1000000000 "$MAILBOX"
+
+# compare mailboxes; can't compare the RFC 3501 TEXT as LMTP adds a
+# Received: header.
+# TODO unset lmtp_add_received_header once avaisable in Sid:
+# https://doc.dovecot.org/settings/dovecot_core_settings/#lmtp-add-received-header
+list_mails_sha256() {
+ local u="$1" guid uid
+ while read guid uid; do
+ doveadm -u "$u" -f "flow" fetch body mailbox-guid "$guid" uid "$uid" \
+ | sed "1s/body=//" | sha256sum
+ done < <(doveadm -u "$u" search mailbox "$MAILBOX") | sort -f
+}
+check() {
+ diff -u --label="local/mails" --label="remote/mails" \
+ <( list_mails_sha256 "local" ) \
+ <( list_mails_sha256 "remote" ) \
+ || error "mailboxes differ"
+}
+
+
+# Add some messages and sync
+step_start "Fetching messages"
+for ((i = 0; i < 32; i++)); do
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+done
+
+pullimap "remote" || error
+check
+
+# same thing, but with some missing messages
+for ((i = 0; i < N; i+=2)); do
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+ deliver -u "remote" -- -m "$MAILBOX" </dev/null # even seqnum
+done
+for ((i = 0; i < N; i+=2)); do
+ # expunge every other message
+ doveadm -u "remote" expunge mailbox "$MAILBOX" $((N-i+32))
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+done
+
+pullimap "remote" || error
+check
+
+# count unseen remote messages
+doveadm -u "remote" search mailbox "$MAILBOX" unseen >"$TMPDIR/unseen"
+[ ! -s "$TMPDIR/unseen" ] || error "\\Unseen messages left"
+step_done
+
+
+step_start "--idle (${TIMEOUT}s)"
+
+pullimap --idle "remote" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+timer=$(( $(date +%s) + TIMEOUT ))
+while [ $(date +%s) -le $timer ]; do
+ n="$(shuf -n1 -i1-5)"
+ for (( i=0; i < n; i++)); do
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+ done
+
+ s=$(shuf -n1 -i1-1500)
+ [ $s -ge 1000 ] && s="$(printf "1.%03d" $((s-1000)))" || s="$(printf "0.%03d" $s)"
+ sleep "$s"
+done
+
+sleep 2
+ptree_abort $PID
+trap - EXIT INT TERM
+
+check
+step_done
+
+
+step_start "Purging"
+echo "purge-after = 0" >>"$XDG_CONFIG_HOME/pullimap/config"
+for ((i = 0; i < 32; i++)); do
+ sample_message | deliver -u "remote" -- -m "$MAILBOX"
+done
+pullimap "remote"
+
+doveadm -u "remote" search mailbox "$MAILBOX" all >"$TMPDIR/messages"
+[ ! -s "$TMPDIR/messages" ] || error "messages left"
+step_done
+
+# vim: set filetype=sh :
diff --git a/tests/rename-exists-db/local.conf b/tests/rename-exists-db/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/rename-exists-db/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/rename-exists-db/remote.conf b/tests/rename-exists-db/remote.conf
new file mode 100644
index 0000000..9657e89
--- /dev/null
+++ b/tests/rename-exists-db/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\\"
+}
diff --git a/tests/01-rename-exists-db/run b/tests/rename-exists-db/t
index aad7c44..cb6cfcd 100644
--- a/tests/01-rename-exists-db/run
+++ b/tests/rename-exists-db/t
@@ -1,14 +1,14 @@
doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o"
-interimap
+interimap_init
check_mailbox_list
# delete a mailbox on both servers but leave it in the database, then try to use it as target for --rename
doveadm -u "local" mailbox delete "t.o"
doveadm -u "remote" mailbox delete "t\\o"
-! interimap --rename "root.from" "t.o"
-xgrep -Fx 'database: ERROR: Mailbox t.o exists. Run `interimap --target=database --delete t.o` to delete.' <"$STDERR"
+! interimap --rename "root.from" "t.o" || error
+grep -Fx 'database: ERROR: Mailbox t.o exists. Run `interimap --target=database --delete t.o` to delete.' <"$STDERR" || error
# vim: set filetype=sh :
diff --git a/tests/rename-exists-local/local.conf b/tests/rename-exists-local/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/rename-exists-local/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/rename-exists-local/remote.conf b/tests/rename-exists-local/remote.conf
new file mode 100644
index 0000000..9657e89
--- /dev/null
+++ b/tests/rename-exists-local/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\\"
+}
diff --git a/tests/01-rename-exists-local/run b/tests/rename-exists-local/t
index d82a0a4..190f49a 100644
--- a/tests/01-rename-exists-local/run
+++ b/tests/rename-exists-local/t
@@ -1,13 +1,13 @@
doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
doveadm -u "remote" mailbox create "root\\from" "root\\from\\child"
-interimap
+interimap_init
check_mailbox_list
# delete a mailbox on the remote server, then try to use it as target for --rename
doveadm -u "remote" mailbox delete "t\\o"
-! interimap --rename "root.from" "t.o"
-xgrep -Fx 'local: ERROR: Mailbox t.o exists. Run `interimap --target=local --delete t.o` to delete.' <"$STDERR"
+! interimap --rename "root.from" "t.o" || error
+grep -Fx 'local: ERROR: Mailbox t.o exists. Run `interimap --target=local --delete t.o` to delete.' <"$STDERR" || error
# vim: set filetype=sh :
diff --git a/tests/rename-exists-remote/local.conf b/tests/rename-exists-remote/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/rename-exists-remote/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/rename-exists-remote/remote.conf b/tests/rename-exists-remote/remote.conf
new file mode 100644
index 0000000..9657e89
--- /dev/null
+++ b/tests/rename-exists-remote/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\\"
+}
diff --git a/tests/01-rename-exists-remote/run b/tests/rename-exists-remote/t
index 28af1fc..be16a12 100644
--- a/tests/01-rename-exists-remote/run
+++ b/tests/rename-exists-remote/t
@@ -1,13 +1,13 @@
doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o"
doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" "t\\o"
-interimap
+interimap_init
check_mailbox_list
# delete a mailbox on the local server, then try to use it as target for --rename
doveadm -u "local" mailbox delete "t.o"
-! interimap --rename "root.from" "t.o"
-xgrep -Fx 'remote: ERROR: Mailbox t\o exists. Run `interimap --target=remote --delete t.o` to delete.' <"$STDERR"
+! interimap --rename "root.from" "t.o" || error
+grep -Fx 'remote: ERROR: Mailbox t\o exists. Run `interimap --target=remote --delete t.o` to delete.' <"$STDERR" || remote
# vim: set filetype=sh :
diff --git a/tests/rename-inferiors/local.conf b/tests/rename-inferiors/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/rename-inferiors/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/rename-inferiors/remote.conf b/tests/rename-inferiors/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/rename-inferiors/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/01-rename/run b/tests/rename-inferiors/t
index 6541c5c..9267e6f 100644
--- a/tests/01-rename/run
+++ b/tests/rename-inferiors/t
@@ -8,7 +8,7 @@ for m in "root^sibbling" "root^sibbling^grandchild" "root2" "INBOX"; do
sample_message | deliver -u "remote" -- -m "$m"
done
-interimap
+interimap_init
check_mailboxes_status "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" \
"root.sibbling" "root.sibbling.grandchild" "root2" "INBOX"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF
@@ -18,26 +18,31 @@ sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF
ORDER BY idx
EOF
-# renaming a non-existent mailbox doesn't yield an error
-interimap --rename "nonexistent" "nonexistent2"
+step_start "non-existent source (no-op)"
+interimap --rename "nonexistent" "root" || error "Renamed non-existent mailbox?"
+check_mailbox_list
+step_done
+
+step_start "\\NonExistent target (fail)"
+! interimap --rename "root2" "root" || error "Didn't abort on ALREADYEXISTS"
+grep -E "^local: ERROR: Couldn't rename mailbox root2: NO \[ALREADYEXISTS\] " <"$STDERR"
check_mailbox_list
+step_done
-# renaming to an existing name yields an error
-! interimap --rename "root2" "root"
-xgrep -E "^local: ERROR: Couldn't rename mailbox root2: NO \[ALREADYEXISTS\] .*" <"$STDERR"
# rename 'root.from' to 'from.root', including inferiors
+step_start "existing source with inferiors"
interimap --rename "root.from" "from.root"
-xgrep -Fx 'local: Renamed mailbox root.from to from.root' <"$STDERR"
-xgrep -Fx 'remote: Renamed mailbox root^from to from^root' <"$STDERR"
-xgrep -Fx 'database: Renamed mailbox root.from to from.root' <"$STDERR"
+grep -Fx 'local: Renamed mailbox root.from to from.root' <"$STDERR"
+grep -Fx 'remote: Renamed mailbox root^from to from^root' <"$STDERR"
+grep -Fx 'database: Renamed mailbox root.from to from.root' <"$STDERR"
check_mailbox_list
check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \
"root.sibbling" "root.sibbling.grandchild" "root2" "INBOX"
before="$(printf "%s\\0%s" "root" "from" | xxd -u -ps)"
-after="$(printf "%s\\0%s" "from" "root" | xxd -ps)"
+after="$(printf "%s\\0%s" "from" "root" | xxd -u -ps)"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF
.mode csv
SELECT idx,
@@ -50,21 +55,24 @@ sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF
ORDER BY idx
EOF
diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \
- "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv"
+ "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv" \
+ || error "Mailbox list differs"
+step_done
-# Try to rename \NonExistent root and check that its children move
+# rename \NonExistent root and check that its children move
+step_start "\\NonExistent source with inferiors"
interimap --rename "root" "newroot"
-xgrep -Fq 'local: Renamed mailbox root to newroot' <"$STDERR"
-xgrep -Fq 'remote: Renamed mailbox root to newroot' <"$STDERR"
-xgrep -Fq 'database: Renamed mailbox root to newroot' <"$STDERR"
+grep -Fq 'local: Renamed mailbox root to newroot' <"$STDERR"
+grep -Fq 'remote: Renamed mailbox root to newroot' <"$STDERR"
+grep -Fq 'database: Renamed mailbox root to newroot' <"$STDERR"
check_mailbox_list
check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \
"newroot.sibbling" "newroot.sibbling.grandchild" "root2" "INBOX"
before2="$(printf "%s" "root" | xxd -u -ps)"
-after2="$(printf "%s" "newroot" | xxd -ps)"
+after2="$(printf "%s" "newroot" | xxd -u -ps)"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes3.csv" <<-EOF
.mode csv
SELECT idx,
@@ -79,6 +87,14 @@ sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes3.csv" <<-EOF
ORDER BY idx
EOF
diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \
- "$TMPDIR/mailboxes2.csv" "$TMPDIR/mailboxes3.csv"
+ "$TMPDIR/mailboxes2.csv" "$TMPDIR/mailboxes3.csv" \
+ || error "Mailbox list differs"
+step_done
+
+
+interimap
+check_mailbox_list
+check_mailboxes_status "from.root" "from.root.child" "from.root.child2" "from.root.child.grandchild" \
+ "newroot.sibbling" "newroot.sibbling.grandchild" "root2" "INBOX"
# vim: set filetype=sh :
diff --git a/tests/rename-simple/t b/tests/rename-simple/t
new file mode 100644
index 0000000..6ebee9a
--- /dev/null
+++ b/tests/rename-simple/t
@@ -0,0 +1,61 @@
+doveadm -u "local" mailbox create "foo"
+
+sample_message | deliver -u "local" -- -m "INBOX"
+sample_message | deliver -u "remote" -- -m "INBOX"
+sample_message | deliver -u "remote" -- -m "foo"
+
+interimap_init
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes.csv" <<-EOF
+ .mode csv
+ SELECT idx, hex(mailbox)
+ FROM mailboxes
+ ORDER BY idx
+EOF
+
+step_start "non-existent source (no-op)"
+interimap --rename "nonexistent" "bar" || error "Rename non-existent mailbox?"
+check_mailbox_list
+step_done
+
+step_start "existing target (fail)"
+! interimap --rename "nonexistent" "foo" || error "Overwrote target?"
+grep -Fx "local: ERROR: Mailbox foo exists. Run \`interimap --target=local --delete foo\` to delete." <"$STDERR" || error
+check_mailbox_list
+step_done
+
+step_start "INBOX"
+interimap --rename "INBOX" "baz" || error
+check_mailbox_list
+step_done
+
+step_start "\\Noinferiors mailbox"
+interimap --rename "foo" "bar" || error
+check_mailbox_list
+
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF
+ .mode csv
+ SELECT idx,
+ CASE
+ WHEN mailbox = x'$(printf "baz" | xxd -u -ps)'
+ THEN '$(printf "%s" "INBOX" | xxd -u -ps)'
+ WHEN mailbox = x'$(printf "bar" | xxd -u -ps)'
+ THEN '$(printf "%s" "foo" | xxd -u -ps)'
+ ELSE hex(mailbox)
+ END
+ FROM mailboxes
+ ORDER BY idx
+EOF
+diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \
+ "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv" \
+ || error "Mailbox list differs"
+step_done
+
+interimap
+# recreated after renaming
+grep -Fx "database: Created mailbox INBOX" <"$STDERR"
+
+check_mailbox_list
+check_mailboxes_status "INBOX" "bar" "baz"
+
+# vim: set filetype=sh :
diff --git a/tests/repair/local.conf b/tests/repair/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/repair/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/repair/remote.conf b/tests/repair/remote.conf
new file mode 100644
index 0000000..1cbbc07
--- /dev/null
+++ b/tests/repair/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ~
+}
diff --git a/tests/05-repair/run b/tests/repair/t
index 66f9ce9..6b205ea 100644
--- a/tests/05-repair/run
+++ b/tests/repair/t
@@ -9,7 +9,7 @@ for ((i = 0; i < 64; i++)); do
sample_message | deliver -u "remote" -- -m "baz"
done
-interimap
+interimap_init
check_mailbox_list
check_mailboxes_status "foo.bar" "baz" "INBOX"
@@ -22,7 +22,7 @@ doveadm -u "remote" expunge mailbox "foo~bar" 4,5,7,10
doveadm -u "local" flags add "\\Answered" mailbox "foo.bar" 2,3,5:7,10
doveadm -u "remote" flags add "\\Seen" mailbox "foo~bar" 4,5,7
-# spoof HIGHESTMODSEQ value in the database, to make it look that we recorded the new changes already
+# spoof HIGHESTMODSEQ value in the database to make it look that we recorded the new changes already
spoof() {
local k="$1" v m hex="$(printf "%s\\0%s" "foo" "bar" | xxd -ps)"
shift
@@ -46,7 +46,7 @@ doveadm -u "remote" mailbox status "all" "foo~bar" >"$TMPDIR/foo-bar.status.remo
# verify that without --repair interimap does nothing due to the spoofed HIGHESTMODSEQ values
-interimap "foo.bar"
+interimap "foo.bar" || error
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
@@ -58,14 +58,14 @@ diff -u --label="a/foo_bar.local" --label="a/foo_bar.local" "$TMPDIR/foo-bar.s
diff -u --label="a/foo_bar.remote" --label="a/foo_bar.remote" "$TMPDIR/foo-bar.status.remote" "$TMPDIR/foo-bar.status2.remote"
-# deliver more messages and spoof UIDNEXT, on one side only
+# deliver more messages and spoof UIDNEXT *on one side only*
sample_message | deliver -u "local" -- -m "foo.bar"
sample_message | deliver -u "remote" -- -m "foo~bar"
spoof UIDNEXT "local"
spoof HIGHESTMODSEQ "local" "remote"
# now repair
-interimap --repair "baz" "foo.bar"
+interimap --repair "baz" "foo.bar" || error
# 6 updates with \Answered (luid 4,8,11:13,16), 2 of which (luid 12,13) vanished from remote
# 3 updates with \Seen (ruid 6,8,10), 1 of which (uid 10) vanished from remote
@@ -82,26 +82,26 @@ xcgrep 5 -E '^local\(foo\.bar\): WARNING: UID [0-9]+ disappeared. Redownloading
# 6-1 (luid 2 <-> ruid 10 is gone from both)
xcgrep 3 -E '^remote\(foo~bar\): WARNING: UID [0-9]+ disappeared. Redownloading local UID [0-9]+\.$' <"$STDERR"
-xgrep -E '^local\(baz\): Removed 24 UID\(s\) ' <"$STDERR"
-xgrep -E '^remote\(baz\): Removed 5 UID\(s\) ' <"$STDERR"
+grep -E '^local\(baz\): Removed 24 UID\(s\) ' <"$STDERR" || error
+grep -E '^remote\(baz\): Removed 5 UID\(s\) ' <"$STDERR" || error
-# pining UIDs here is not very robust...
-xgrep -E '^local\(foo\.bar\): Updated flags \(\\Answered \\Seen\) for UID 16$' <"$STDERR"
-xgrep -E '^local\(foo\.bar\): Updated flags \(\\Seen\) for UID 14$' <"$STDERR"
-xgrep -E '^remote\(foo~bar\): Updated flags \(\\Answered \\Seen\) for UID 8$' <"$STDERR"
-xgrep -E '^remote\(foo~bar\): Updated flags \(\\Answered\) for UID 3,12,16$' <"$STDERR"
+# hardcoding UIDs here is not very robust...
+grep -E '^local\(foo\.bar\): Updated flags \(\\Answered \\Seen\) for UID 16$' <"$STDERR" || error
+grep -E '^local\(foo\.bar\): Updated flags \(\\Seen\) for UID 14$' <"$STDERR" || error
+grep -E '^remote\(foo~bar\): Updated flags \(\\Answered \\Seen\) for UID 8$' <"$STDERR" || error
+grep -E '^remote\(foo~bar\): Updated flags \(\\Answered\) for UID 3,12,16$' <"$STDERR" || error
# luid 17
xcgrep 1 -E '^remote\(foo~bar\): WARNING: No match for modified local UID [0-9]+. Redownloading\.' <"$STDERR"
-xgrep -E '^local\(foo\.bar\): Added 5 UID\(s\) ' <"$STDERR"
-xgrep -E '^remote\(foo~bar\): Added 4 UID\(s\) ' <"$STDERR"
-xgrep -E '^local\(foo\.bar\): Added 1 UID\(s\) ' <"$STDERR" # the new message
+grep -E '^local\(foo\.bar\): Added 5 UID\(s\) ' <"$STDERR" || error
+grep -E '^remote\(foo~bar\): Added 4 UID\(s\) ' <"$STDERR" || error
+grep -E '^local\(foo\.bar\): Added 1 UID\(s\) ' <"$STDERR" || error # the new message
check_mailbox_list
check_mailboxes_status "baz" "foo.bar"
-interimap
+interimap || error
check_mailboxes_status "baz" "foo.bar" "INBOX"
# vim: set filetype=sh :
diff --git a/tests/resume/local.conf b/tests/resume/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/resume/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/resume/remote.conf b/tests/resume/remote.conf
new file mode 100644
index 0000000..1cbbc07
--- /dev/null
+++ b/tests/resume/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ~
+}
diff --git a/tests/04-resume/run b/tests/resume/t
index 22d66bc..cb0208c 100644
--- a/tests/04-resume/run
+++ b/tests/resume/t
@@ -5,7 +5,7 @@ for ((i = 0; i < 8; i++)); do
sample_message | deliver -u "local" -- -m "foo.bar"
sample_message | deliver -u "local" -- -m "INBOX"
done
-interimap
+interimap_init
check_mailbox_list
check_mailboxes_status "foo" "foo.bar" "baz" "INBOX"
@@ -36,9 +36,9 @@ EOF
doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local"
doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote"
-! interimap
-xgrep -Fx "Resuming interrupted sync for foo" <"$STDERR"
-xgrep -Fx "local(foo): ERROR: UIDVALIDITY changed! ($uidvalidity2 != $uidvalidity) Need to invalidate the UID cache." <"$STDERR"
+! interimap || error
+grep -Fx "Resuming interrupted sync for foo" <"$STDERR"
+grep -Fx "local(foo): ERROR: UIDVALIDITY changed! ($uidvalidity2 != $uidvalidity) Need to invalidate the UID cache." <"$STDERR"
sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF
.dump
@@ -70,8 +70,8 @@ sample_message | deliver -u "remote" -- -m "foo~bar"
sample_message | deliver -u "local" -- -m "baz"
interimap "foo.bar" "InBoX" "baz" # ignore "foo"
-xgrep -Fx "Resuming interrupted sync for foo.bar" <"$STDERR"
-xgrep -Fx "Resuming interrupted sync for INBOX" <"$STDERR"
+grep -Fx "Resuming interrupted sync for foo.bar" <"$STDERR"
+grep -Fx "Resuming interrupted sync for INBOX" <"$STDERR"
check_mailbox_list
check_mailboxes_status "foo.bar" "INBOX" "baz" # ignore "foo"
diff --git a/tests/run b/tests/run
index ee11757..30d20f9 100755
--- a/tests/run
+++ b/tests/run
@@ -22,27 +22,39 @@ set -ue
PATH=/usr/bin:/bin
export PATH
-if [ $# -ne 1 ]; then
- printf "Usage: %s TESTNAME\\n" "$0" >&2
+if [ $# -eq 0 ] || [ $# -gt 2 ]; then
+ printf "Usage: %s TESTFILE [TESTNAME]\\n" "$0" >&2
exit 1
fi
-TEST="${1%/}"
-TEST="${TEST##*/}"
-NAME="${TEST#[0-9]*-}"
-TESTDIR="$(dirname -- "$0")/$TEST"
+BASEDIR="$(dirname -- "$0")"
+TESTDIR="$BASEDIR/$1"
+TESTNAME="${2-$1}"
if [ ! -d "$TESTDIR" ]; then
printf "ERROR: Not a directory: %s\\n" "$TESTDIR" >&2
exit 1
fi
-ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$NAME.XXXXXXXXXX")"
-trap 'rm -rf -- "$ROOTDIR"' EXIT INT TERM
+ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$1.XXXXXXXXXX")"
+declare -a DOVECOT_SERVER=()
+trap cleanup EXIT INT TERM
+cleanup() {
+ local pid c
+ for c in "${DOVECOT_SERVER[@]}"; do
+ if [ ! -f "$c" ] || ! env -i PATH="/usr/bin:/bin" doveadm -c "$c" stop; then
+ pid="$(< "${c%/*}/run/master.pid")"
+ kill -TERM "$pid" || printf "kill(1) exited with status %d\\n" "$?" >&2
+ fi
+ done
+ rm -rf -- "$ROOTDIR"
+}
-STDOUT="$ROOTDIR/stdout"
-STDERR="$ROOTDIR/stderr"
+_STDOUT="$ROOTDIR/stdout"
+_STDERR="$ROOTDIR/stderr"
TMPDIR="$ROOTDIR/tmp"
+STDERR="$(mktemp --tmpdir="$ROOTDIR" "stderr.XXXXXXXXXX")"
mkdir -- "$TMPDIR" "$ROOTDIR/home"
+declare -a REMOTES=()
# Set environment for the given user
environ_set() {
@@ -60,65 +72,151 @@ environ_set() {
# Prepare the test harness
prepare() {
declare -a ENVIRON=()
- local src cfg target u home
+ local src cfg target u home n proto
+ if [ -f "$TESTDIR/remotes" ] || [ -L "$TESTDIR/remotes" ]; then
+ for cfg in $(seq 1 "$(< "$TESTDIR/remotes")"); do
+ REMOTES+=( "remote$cfg" )
+ done
+ else
+ REMOTES+=( "remote" )
+ fi
# copy dovecot config
- for src in "$TESTDIR/local.conf" "$TESTDIR"/remote*.conf; do
- [ -r "$src" ] || continue
- u="${src#"$TESTDIR/"}"
- u="${u%.conf}"
- home="$ROOTDIR/home/$u"
+ for u in "local" "${REMOTES[@]}"; do
+ home="$ROOTDIR/$u/home"
export "HOME_$u"="$home"
- mkdir -pm0755 -- "$home/.local/bin"
- mkdir -pm0700 -- "$home/.config/dovecot"
- cat >"$home/.config/dovecot/config" <<-EOF
- log_path = /dev/null
- mail_home = $ROOTDIR/home/%u
+ environ_set "$u"
+
+ mkdir -pm0700 -- "$home/.dovecot"
+ cat >"$home/.dovecot/config" <<-EOF
+ log_path = $HOME_local/mail.log
+ mail_home = $home
+ mail_location = dbox:~/inbox:LAYOUT=index
mailbox_list_index = yes
ssl = no
+ listen = 127.0.0.1, ::1
+ namespace inbox {
+ inbox = yes
+ }
EOF
- cat >>"$home/.config/dovecot/config" <"$src"
- environ_set "$u"
+ if [ -f "$TESTDIR/$u.conf" ] || [ -L "$TESTDIR/$u.conf" ]; then
+ cat >>"$home/.dovecot/config" <"$TESTDIR/$u.conf"
+ fi
+ cp -aT -- "$BASEDIR/snippets/dovecot" "$home/.dovecot/conf.d"
+
+ proto="$(env -i "${ENVIRON[@]}" doveconf -c "$home/.dovecot/config" -h protocols)"
+ if [ -n "$proto" ]; then
+ cat >>"$home/.dovecot/config" <<-EOF
+ # https://wiki.dovecot.org/HowTo/Rootless
+ base_dir = $home/.dovecot/run
+ default_internal_user = $(id -un)
+ default_internal_group = $(id -gn)
+ default_login_user = $(id -un)
+
+ service anvil {
+ chroot =
+ }
+ service imap-login {
+ chroot =
+ }
+ service stats {
+ chroot =
+ }
+
+ passdb {
+ args = scheme=PLAIN username_format=%u $home/.dovecot/users
+ driver = passwd-file
+ }
+ userdb {
+ args = username_format=%u $home/.dovecot/users
+ driver = passwd-file
+ }
+ EOF
+
+ env -i PATH="/usr/bin:/bin" /usr/sbin/dovecot -c "$home/.dovecot/config"
+ DOVECOT_SERVER+=( "$home/.dovecot/config" )
+ printf "%s:%s:::::\\n" "$u" "$(xxd -l16 -p </dev/urandom)" >"$home/.dovecot/users"
+ fi
+
+ mkdir -pm0755 -- "$home/.local/bin"
cat >"$home/.local/bin/doveadm" <<-EOF
#!/bin/sh
exec env -i ${ENVIRON[@]@Q} \\
- doveadm -c ${home@Q}/.config/dovecot/config "\$@"
+ doveadm -c ${home@Q}/.dovecot/config "\$@"
EOF
chmod +x -- "$home/.local/bin/doveadm"
done
- # copy interimap config
- mkdir -pm0700 -- "$HOME_local/.local/share/interimap"
- mkdir -pm0700 -- "$HOME_local/.config/interimap"
- for cfg in "$TESTDIR"/remote*.conf; do
- cfg="${cfg#"$TESTDIR/remote"}"
- cfg="${cfg%.conf}"
- u="remote$cfg"
+ # copy interimap and pullimap configuration
+ mkdir -pm0700 -- "$HOME_local/.local/share/interimap" "$HOME_local/.local/share/pullimap"
+ mkdir -pm0700 -- "$HOME_local/.config/interimap" "$HOME_local/.config/pullimap"
+ echo "deliver-rcpt = local" >>"$HOME_local/.config/pullimap/config"
+ for u in "${REMOTES[@]}"; do
+ n="${u#remote}"
eval home="\$HOME_$u"
- if [ -f "$TESTDIR/interimap.conf" ]; then
- cat <"$TESTDIR/interimap.conf" >>"$HOME_local/.config/interimap/config$cfg"
- fi
- cat >>"$HOME_local/.config/interimap/config$cfg" <<-EOF
+
+ cat >>"$HOME_local/.config/interimap/config$n" <<-EOF
database = $u.db
-
+ #logfile = $HOME_local/interimap$n.log
+ EOF
+ if [ -f "$TESTDIR/interimap$n.conf" ] || [ -L "$TESTDIR/interimap$n.conf" ]; then
+ cat <"$TESTDIR/interimap$n.conf" >>"$HOME_local/.config/interimap/config$n"
+ fi
+ if [ -f "$TESTDIR/pullimap.conf" ] || [ -L "$TESTDIR/pullimap.conf" ]; then
+ cat <"$TESTDIR/pullimap.conf" >>"$HOME_local/.config/pullimap/config"
+ fi
+
+ cat >>"$HOME_local/.config/interimap/config$n" <<-EOF
+
[local]
type = tunnel
command = exec ${HOME_local@Q}/.local/bin/doveadm exec imap
null-stderr = NO
-
- [remote]
- type = tunnel
- command = exec ${home@Q}/.local/bin/doveadm exec imap
- null-stderr = NO
EOF
+ if [ -f "$TESTDIR/interimap$n.local" ] || [ -L "$TESTDIR/interimap$n.local" ]; then
+ cat <"$TESTDIR/interimap$n.local" >>"$HOME_local/.config/interimap/config$n"
+ fi
+
+ if [ -s "$home/.dovecot/users" ]; then
+ cat <<-EOF
+ username = $u
+ password = $(awk -F: -vu="$u" '$1 == u {print $2}' <"$home/.dovecot/users")
+ EOF
+ else
+ cat <<-EOF
+ type = tunnel
+ command = exec ${home@Q}/.local/bin/doveadm exec imap
+ null-stderr = NO
+ EOF
+ fi >"$HOME_local/.$u.conf"
+ if [ -f "$TESTDIR/interimap$n.remote" ] || [ -L "$TESTDIR/interimap$n.remote" ]; then
+ cat <"$TESTDIR/interimap$n.remote" >>"$HOME_local/.$u.conf"
+ fi
+
+ { printf "\\n[remote]\\n"; cat <"$HOME_local/.$u.conf"; } >>"$HOME_local/.config/interimap/config$n"
+ { printf "\\n[%s]\\n" "$u"; cat <"$HOME_local/.$u.conf"; } >>"$HOME_local/.config/pullimap/config"
done
}
prepare
# Wrappers for interimap(1) and doveadm(1)
-interimap() {
- declare -a ENVIRON=()
+interimap() { _interimap_cmd "interimap" "$@"; }
+pullimap() { _interimap_cmd "pullimap" "$@"; }
+_interimap_cmd() {
+ declare -a ENVIRON=() r=0
+ local script="$1"
+ shift
environ_set "local"
- env -i "${ENVIRON[@]}" perl -I./lib -T ./interimap "$@"
+ env -i "${ENVIRON[@]}" perl -I./lib -T "./$script" "$@" 2> >(tee "$STDERR" >&2)
+}
+interimap_init() {
+ local u="${1-remote}"
+ local db="$XDG_DATA_HOME/interimap/$u.db"
+ local cfg="config${u#remote}"
+
+ test \! -e "$db" || error "Database already exists" 1
+ interimap --config "$cfg" || error "Couldn't initialize interimap" 1
+ test -f "$db" || error "Database is still missing" 1
+ grep -Fx "Creating new schema in database file $db" <"$STDERR" || error "DB wasn't created" 1
}
doveadm() {
if [ $# -le 2 ] || [ "$1" != "-u" ]; then
@@ -137,8 +235,10 @@ sqlite3() {
# Sample (random) message
sample_message() {
local date="$(date +"%s.%N")"
+ # also try non-conventional addresses for pullimap
+ local sender="$(shuf -n1 -e "sender" "first.last" "foo-bar" \"\" "\"x\\\" #&\\\\y\"" )"
cat <<-EOF
- From: <sender@example.net>
+ From: <$sender@example.net>
To: <recipient@example.net>
Date: $(date -R -d@"$date")
Message-ID: <$date@example.net>
@@ -150,7 +250,7 @@ sample_message() {
# Wrapper for dovecot-lda(1)
deliver() {
- local -a argv
+ declare -a argv
while [ $# -gt 0 ] && [ "$1" != "--" ]; do
argv+=( "$1" )
shift
@@ -167,36 +267,40 @@ dump_test_result() {
local above="<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
local src u home
declare -a ENVIRON=()
- for src in "$TESTDIR/local.conf" "$TESTDIR"/remote*.conf; do
- u="${src#"$TESTDIR/"}"
- u="${u%.conf}"
+ for u in "local" "${REMOTES[@]}"; do
environ_set "$u"
eval home="\$HOME_$u"
printf "%s dovecot configuration:\\n%s\\n" "$u" "$below"
- env -i "${ENVIRON[@]}" doveconf -c "$home/.config/dovecot/config" -n
+ env -i "${ENVIRON[@]}" doveconf -c "$home/.dovecot/config" -n
+ printf "%s\\n\\n" "$above"
+ done
+
+ for u in "${REMOTES[@]}"; do
+ printf "interimap configuration (local <-> $u):\\n%s\\n" "$below"
+ cat <"$HOME_local/.config/interimap/config${u#remote}"
printf "%s\\n\\n" "$above"
done
- printf "(local) interimap configuration:\\n%s\\n" "$below"
- cat <"$HOME_local/.config/interimap/config"
+ printf "mail.log:\\n%s\\n" "$below"
+ cat -- "$HOME_local/mail.log" 2>/dev/null || true
printf "%s\\n\\n" "$above"
- printf "standard output was:\\n%s\\n" "$below"
- cat <"$STDOUT"
+ printf "standard output:\\n%s\\n" "$below"
+ cat <"$_STDOUT"
printf "%s\\n\\n" "$above"
- printf "standard error was:\\n%s\\n" "$below"
- cat <"$STDERR"
+ printf "standard error:\\n%s\\n" "$below"
+ cat <"$_STDERR"
printf "%s\\n\\n" "$above"
}
# Check mailbox consistency between the local/remote server and interimap's database
check_mailbox_status() {
local mailbox="$1" lns="inbox" lsep lprefix rns="inbox" rsep rprefix
- lsep="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/separator")"
- lprefix="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/prefix")"
- rsep="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/separator")"
- rprefix="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/prefix")"
+ lsep="$(doveconf -c "$HOME_local/.dovecot/config" -h "namespace/$lns/separator")"
+ lprefix="$(doveconf -c "$HOME_local/.dovecot/config" -h "namespace/$lns/prefix")"
+ rsep="$(doveconf -c "$HOME_remote/.dovecot/config" -h "namespace/$lns/separator")"
+ rprefix="$(doveconf -c "$HOME_remote/.dovecot/config" -h "namespace/$lns/prefix")"
local blob="x'$(printf "%s" "$mailbox" | tr "$lsep" "\\0" | xxd -c256 -ps)'"
local rmailbox="$(printf "%s" "$mailbox" | tr "$lsep" "$rsep")"
@@ -225,7 +329,7 @@ check_mailbox_status2() {
WHERE mailbox = $blob
EOF
)
- check_mailbox_status_values "local" "$lmailbox" $lUIDVALIDITY $lUIDNEXT $lHIGHESTMODSEQ $MESSAGES
+ check_mailbox_status_values "local" "$lmailbox" $lUIDVALIDITY $lUIDNEXT $lHIGHESTMODSEQ $MESSAGES
check_mailbox_status_values "$u" "$rmailbox" $rUIDVALIDITY $rUIDNEXT $rHIGHESTMODSEQ $MESSAGES
local a b
@@ -268,10 +372,10 @@ check_mailboxes_status() {
# Check mailbox list constency between the local and remote servers
check_mailbox_list() {
local m i lns="inbox" lsep lprefix rns="inbox" rsep rprefix sub=
- lsep="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/separator")"
- lprefix="$(doveconf -c "$HOME_local/.config/dovecot/config" -h "namespace/$lns/prefix")"
- rsep="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/separator")"
- rprefix="$(doveconf -c "$HOME_remote/.config/dovecot/config" -h "namespace/$lns/prefix")"
+ lsep="$(doveconf -c "$HOME_local/.dovecot/config" -h "namespace/$lns/separator")"
+ lprefix="$(doveconf -c "$HOME_local/.dovecot/config" -h "namespace/$lns/prefix")"
+ rsep="$(doveconf -c "$HOME_remote/.dovecot/config" -h "namespace/$lns/separator")"
+ rprefix="$(doveconf -c "$HOME_remote/.dovecot/config" -h "namespace/$lns/prefix")"
if [ $# -gt 0 ] && [ "$1" = "-s" ]; then
sub="-s"
shift
@@ -304,38 +408,54 @@ check_mailbox_list() {
<( printf "%s" "${lmailboxes[*]}" | sort ) <( printf "%s" "${rmailboxes[*]}" | sort )
}
-# Wrappers for grep(1) and `grep -C`
-xgrep() {
- if ! grep -q "$@"; then
- printf "\`grep %s\` failed on line %d\\n" "${*@Q}" ${BASH_LINENO[0]} >&2
- exit 1
- fi
-}
+# Wrapper for `grep -c`
xcgrep() {
local m="$1" n
shift
if ! n="$(grep -c "$@")" || [ $m -ne $n ]; then
- printf "\`grep -c %s\` failed on line %d: %d != %d\\n" "${*@Q}" ${BASH_LINENO[0]} "$m" "$n" >&2
- exit 1
+ error "\`grep -c ${*@Q}\` failed ($m != $n)" 1
fi
}
+error() {
+ local err="${1+": $1"}" i=${2-0}
+ printf "ERROR$err on file %s line %d\\n" "${BASH_SOURCE[i+1]}" ${BASH_LINENO[i]} >&2
+ exit 1
+}
+ptree_abort() {
+ local pid
+ for pid in "$@"; do
+ # kill a process and its children
+ pkill -TERM -P "$pid" || printf "pkill(1) exited with status %d\\n" "$?" >&2
+ kill -TERM "$pid" || printf "kill(1) exited with status %d\\n" "$?" >&2
+ done
+ wait
+}
+step_start() { printf "%s%s..." "${INDENT-}" "$1" >&3; }
+step_done() { passed >&3; }
+failed() {
+ [ -t 1 ] && printf " \\x1B[1;31mFAILED\\x1B[0m\\n" || echo " FAILED"
+}
+passed() {
+ [ -t 1 ] && printf " \\x1B[1;32mPASSED\\x1B[0m\\n" || echo " PASSED"
+}
# Run test in a sub-shell
declare -a ENVIRON=()
environ_set "local"
-export TMPDIR TESTDIR STDOUT STDERR "${ENVIRON[@]}"
-export -f environ_set doveadm interimap sqlite3 sample_message deliver
+export TMPDIR TESTDIR STDERR "${ENVIRON[@]}"
+export -f environ_set doveadm interimap interimap_init pullimap _interimap_cmd
+export -f sqlite3 sample_message deliver ptree_abort step_start step_done passed
export -f check_mailbox_status check_mailbox_status_values check_mailbox_status2
-export -f check_mailboxes_status check_mailbox_list xgrep xcgrep
-printf "%s..." "$TEST"
-if ! bash -ue "$TESTDIR/run" >"$STDOUT" 2>"$STDERR"; then
- echo " FAILED"
- dump_test_result
+export -f check_mailboxes_status check_mailbox_list xcgrep error
+[ "$TESTNAME" = "..." ] || printf "%s%s..." "${INDENT-}" "$TESTNAME"
+if ! bash -ue "$TESTDIR/t" 3>&1 >"$_STDOUT" 2>"$_STDERR"; then
+ failed
+ [ "${QUIET-n}" = "y" ] || dump_test_result
exit 1
else
- echo " OK"
- if grep -Paq "\\x00" -- "$STDOUT" "$STDERR"; then
- printf "\\tWarn: binary output (outstanding \\0)!\\n"
+ [ "$TESTNAME" = "..." ] || passed
+ if grep -Paq "\\x00" -- "$_STDOUT" "$_STDERR"; then
+ printf "\\tWARN: binary output (outstanding \\0)!\\n"
fi
exit 0
fi
diff --git a/tests/run-all b/tests/run-all
new file mode 100755
index 0000000..1eca50c
--- /dev/null
+++ b/tests/run-all
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+#----------------------------------------------------------------------
+# Test suite for InterIMAP
+# Copyright © 2019 Guilhem Moulin <guilhem@fripost.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#----------------------------------------------------------------------
+
+set -ue
+PATH=/usr/bin:/bin
+export PATH
+
+BASEDIR="$(dirname -- "$0")"
+RUN="$BASEDIR/run"
+
+failed=0
+
+while IFS="" read -r x; do
+ if [[ "$x" =~ ^([[:blank:]]*)([^[:blank:]#]+)[[:blank:]]+(.*)$ ]]; then
+ indent="${BASH_REMATCH[1]}"
+ t="${BASH_REMATCH[2]}"
+ desc="${BASH_REMATCH[3]}"
+
+ if [ "$t" = "." ]; then
+ printf "%s%s:\\n" "$indent" "$desc"
+ continue
+ elif [ "$t" = "..." ]; then
+ t="$desc"
+ desc="..."
+ fi
+ elif [[ "$x" =~ ^([[:blank:]]*)([^[:blank:]#]+)$ ]]; then
+ indent="${BASH_REMATCH[1]}"
+ t="${BASH_REMATCH[2]}"
+ unset desc
+ else
+ continue
+ fi
+
+ if [ ! -d "$BASEDIR/$t" ]; then
+ printf "WARN: %s does doesn't exist, skipping\\n" "$t" >&2
+ continue
+ fi
+
+ INDENT="$indent" "$RUN" "$t" ${desc+"$desc"} || failed=$(( failed+1 ))
+done <"$BASEDIR/list"
+
+if [ $failed -eq 0 ]; then
+ printf "All tests passed.\\n"
+ exit 0
+else
+ printf "%d test(s) failed.\\n" $failed
+ exit 1
+fi
diff --git a/tests/snippets/dovecot/dhparams.pem b/tests/snippets/dovecot/dhparams.pem
new file mode 100644
index 0000000..7734d2a
--- /dev/null
+++ b/tests/snippets/dovecot/dhparams.pem
@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA0J1dU8erRgIk4bMCBMLezjx32pcQpXrdNgl04dxZVxnJ5Ik2gGhA
+uQRbbZhAlHNHtFtp9s4TdQ3Ddrv9SuWXYul8U5BWbcxs4nOtwFU8912SfiuVr/kc
+4ok2zQ1hdMODtaqWS2ZKBmwcuk4QM6e7fMEAkuZX+Dtf2u8bG5G9B7OL5LggYtrP
+cFVNQDtfhs64D+sUKJLWkgeg5NH6nbf+0Gs5a8v3/urHKvoxdVScGmKzF+LsFsBm
+ycQjYeVtA9gLr41mo80rrFysUQqZtNkbdkaXOIA2r9JGTYex1l/XaediR8J94ck9
+dwAe2ubRqWcPjmoLJYQIPKiCbvXuJAd0wwIBAg==
+-----END DH PARAMETERS-----
diff --git a/tests/snippets/dovecot/dovecot.key b/tests/snippets/dovecot/dovecot.key
new file mode 100644
index 0000000..95c9846
--- /dev/null
+++ b/tests/snippets/dovecot/dovecot.key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIGkqkKq69zVeF17S3y2U2HkQWh8z9M/xeblCztkKIfzJoAoGCCqGSM49
+AwEHoUQDQgAE1LLppulKw8KjINrDhOjEd0NTax5iDCds+vpA2PwsvvtGoprNAjQM
+zX+40u30N3CE0r591txqohSBQ/X+nvG2ug==
+-----END EC PRIVATE KEY-----
diff --git a/tests/snippets/dovecot/dovecot.pem b/tests/snippets/dovecot/dovecot.pem
new file mode 100644
index 0000000..7e53d90
--- /dev/null
+++ b/tests/snippets/dovecot/dovecot.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBkzCCATmgAwIBAgIUQ+3hBMsPJcl59xDDujDDfexurOswCgYIKoZIzj0EAwIw
+HzEdMBsGA1UEAwwUSW50ZXJJTUFQIHRlc3Qgc3VpdGUwHhcNMTkxMTEwMTM1NDAw
+WhcNMjkxMTA3MTM1NDAwWjAfMR0wGwYDVQQDDBRJbnRlcklNQVAgdGVzdCBzdWl0
+ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNSy6abpSsPCoyDaw4ToxHdDU2se
+YgwnbPr6QNj8LL77RqKazQI0DM1/uNLt9DdwhNK+fdbcaqIUgUP1/p7xtrqjUzBR
+MB0GA1UdDgQWBBRlh8nSwyX+VlhwuhV7RKYwvKLyDzAfBgNVHSMEGDAWgBRlh8nS
+wyX+VlhwuhV7RKYwvKLyDzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA
+MEUCIQDK8xPPHTbYW5JnZ1Siy8ChZ6GOu2sRwQu7OgtGYGZRSQIgFKn1oAhnq2Oi
+aIPqxjvBPMsK/sjrdI/rNsr2XgaulU4=
+-----END CERTIFICATE-----
diff --git a/tests/snippets/dovecot/imapd.conf b/tests/snippets/dovecot/imapd.conf
new file mode 100644
index 0000000..2b26451
--- /dev/null
+++ b/tests/snippets/dovecot/imapd.conf
@@ -0,0 +1,19 @@
+protocols = $protocols imap
+
+mail_plugins = $mail_plugins zlib
+protocol imap {
+ mail_plugins = $mail_plugins imap_zlib
+}
+
+service imap-login {
+ inet_listener imap {
+ port = 10143
+ }
+ inet_listener imaps {
+ port = 10993
+ ssl = yes
+ }
+}
+
+# we should avoid sending command lines that are too long
+imap_max_line_length = 8192
diff --git a/tests/snippets/dovecot/interimap-required-capabilities.conf b/tests/snippets/dovecot/interimap-required-capabilities.conf
new file mode 100644
index 0000000..10dd8e1
--- /dev/null
+++ b/tests/snippets/dovecot/interimap-required-capabilities.conf
@@ -0,0 +1,3 @@
+# strict minimum of IMAP capabilities required for interimap to work
+# (in particular, no LITERAL+, MULTIAPPEND, COMPRESS=DEFLATE, SASL-IR)
+imap_capability = IMAP4rev1 ENABLE UIDPLUS LIST-EXTENDED QRESYNC LIST-STATUS
diff --git a/tests/snippets/dovecot/lmtpd.conf b/tests/snippets/dovecot/lmtpd.conf
new file mode 100644
index 0000000..6aa8365
--- /dev/null
+++ b/tests/snippets/dovecot/lmtpd.conf
@@ -0,0 +1,7 @@
+protocols = $protocols lmtp
+
+service lmtp {
+ inet_listener lmtp {
+ port = 10024
+ }
+}
diff --git a/tests/snippets/dovecot/ssl.conf b/tests/snippets/dovecot/ssl.conf
new file mode 100644
index 0000000..240f24b
--- /dev/null
+++ b/tests/snippets/dovecot/ssl.conf
@@ -0,0 +1,4 @@
+ssl = required
+ssl_cert = <dovecot.pem
+ssl_key = <dovecot.key
+ssl_dh = <dhparams.pem
diff --git a/tests/split-set/interimap.remote b/tests/split-set/interimap.remote
new file mode 120000
index 0000000..a4ea3f3
--- /dev/null
+++ b/tests/split-set/interimap.remote
@@ -0,0 +1 @@
+../auth-sasl-plain/interimap.remote \ No newline at end of file
diff --git a/tests/split-set/remote.conf b/tests/split-set/remote.conf
new file mode 120000
index 0000000..dbbb908
--- /dev/null
+++ b/tests/split-set/remote.conf
@@ -0,0 +1 @@
+../auth-sasl-plain/remote.conf \ No newline at end of file
diff --git a/tests/split-set/t b/tests/split-set/t
new file mode 100644
index 0000000..5e8ea52
--- /dev/null
+++ b/tests/split-set/t
@@ -0,0 +1,43 @@
+N=2048
+
+# XXX with COMPRESS=DEFLATE dovecot-imapd 2.3.4 hangs when the command
+# line exceeds 'imap_max_line_length' (or 8192, whichever is smaller)
+# bytes, instead of returning a tagged BAD response.
+# https://dovecot.org/pipermail/dovecot/2019-November/117522.html
+
+# set UIDNEXT to 10^9 so all uids are 10 chars long, otherwise we'd need
+# to add many more messages to obtain large sets
+doveadm -u "local" mailbox update --min-next-uid 1000000000 "INBOX"
+doveadm -u "remote" mailbox update --min-next-uid 1000000000 "INBOX"
+
+for ((i = 0; i < N; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+interimap_init
+check_mailbox_status "INBOX"
+
+# mark every other message as \Seen on the local server
+for ((i = 0; i < N; i+=2)); do
+ doveadm -u "local" flags add "\\Seen" mailbox "INBOX" $((N-i))
+done
+
+# send the changes to the remote; this results into an UID STORE set
+# representation of size 11*N/2-1, which exceeds $imap_max_line_length
+interimap
+check_mailbox_status "INBOX"
+
+# now expunge every other message on the remote server; this results
+# into large UID STORE and UID EXPUNGE set representation
+for ((i = 0; i < N; i+=2)); do
+ doveadm -u "local" expunge mailbox "INBOX" $((N-i))
+ # add some more messages
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+interimap || error
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :
diff --git a/tests/starttls-logindisabled/interimap.remote b/tests/starttls-logindisabled/interimap.remote
new file mode 100644
index 0000000..5d7571d
--- /dev/null
+++ b/tests/starttls-logindisabled/interimap.remote
@@ -0,0 +1,4 @@
+type = imap
+host = 127.0.0.1
+port = 10143
+SSL_verify = no
diff --git a/tests/starttls-logindisabled/remote.conf b/tests/starttls-logindisabled/remote.conf
new file mode 100644
index 0000000..be2d51e
--- /dev/null
+++ b/tests/starttls-logindisabled/remote.conf
@@ -0,0 +1,5 @@
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
+
+# trick dovecot into treating local connections as insecure
+imap_capability = +LOGINDISABLED
diff --git a/tests/starttls-logindisabled/t b/tests/starttls-logindisabled/t
new file mode 100644
index 0000000..0ac7465
--- /dev/null
+++ b/tests/starttls-logindisabled/t
@@ -0,0 +1,19 @@
+interimap --debug || true
+
+# double check the presence of 'LOGINDISABLED' and 'STARTTLS' in the preauth capability list
+grep -oE -m1 '^remote: S: \* OK \[CAPABILITY IMAP4rev1( [^]]*)? AUTH=[^]]*\]' <"$STDERR" >"$TMPDIR/capability"
+
+sed -ri 's/^remote: S: \* OK \[CAPABILITY (.*)\]$/\1/' "$TMPDIR/capability"
+tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
+grep -Fx "IMAP4rev1" <"$TMPDIR/capabilities" || error
+grep -Fx "LOGINDISABLED" <"$TMPDIR/capabilities" || error
+
+# make sure we upgraded the connection and check the capability again
+grep -Fx "STARTTLS" <"$TMPDIR/capabilities" || error
+grep -Fx "remote: C: 000000 STARTTLS" <"$STDERR" || error
+grep -Fx "remote: C: 000001 CAPABILITY" <"$STDERR" || error
+
+# can't go further as the capability string still has the manually
+# enforced 'LOGINDISABLED'
+
+# vim: set filetype=sh :
diff --git a/tests/starttls/interimap.remote b/tests/starttls/interimap.remote
new file mode 100644
index 0000000..5d7571d
--- /dev/null
+++ b/tests/starttls/interimap.remote
@@ -0,0 +1,4 @@
+type = imap
+host = 127.0.0.1
+port = 10143
+SSL_verify = no
diff --git a/tests/starttls/remote.conf b/tests/starttls/remote.conf
new file mode 100644
index 0000000..3d07ea9
--- /dev/null
+++ b/tests/starttls/remote.conf
@@ -0,0 +1,2 @@
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
diff --git a/tests/starttls/t b/tests/starttls/t
new file mode 100644
index 0000000..99a39c2
--- /dev/null
+++ b/tests/starttls/t
@@ -0,0 +1,27 @@
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+interimap --debug || error
+
+# double check the presence of 'STARTTLS' in the preauth capability list
+grep -oE -m1 '^remote: S: \* OK \[CAPABILITY IMAP4rev1( [^]]*)? AUTH=[^]]*\]' <"$STDERR" >"$TMPDIR/capability"
+
+sed -ri 's/^remote: S: \* OK \[CAPABILITY (.*)\]$/\1/' "$TMPDIR/capability"
+tr " " "\\n" <"$TMPDIR/capability" >"$TMPDIR/capabilities"
+grep -Fx "IMAP4rev1" <"$TMPDIR/capabilities" || error
+grep -Fx "STARTTLS" <"$TMPDIR/capabilities" || error
+
+# make sure we upgraded the connection and check the capability again
+grep -Fx "remote: C: 000000 STARTTLS" <"$STDERR" || error
+grep -Fx "remote: C: 000001 CAPABILITY" <"$STDERR" || error
+
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$35944e3bd3300d3ac310bb497a32cc1eef6931482a587ddbc95343740cdf1323" <"$STDERR" || error
+grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+grep "^remote: SSL cipher: " <"$STDERR" || error
+
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :
diff --git a/tests/sync-live-crippled/interimap.remote b/tests/sync-live-crippled/interimap.remote
new file mode 120000
index 0000000..a4ea3f3
--- /dev/null
+++ b/tests/sync-live-crippled/interimap.remote
@@ -0,0 +1 @@
+../auth-sasl-plain/interimap.remote \ No newline at end of file
diff --git a/tests/sync-live-crippled/local.conf b/tests/sync-live-crippled/local.conf
new file mode 120000
index 0000000..ad27dd1
--- /dev/null
+++ b/tests/sync-live-crippled/local.conf
@@ -0,0 +1 @@
+../sync-live/local.conf \ No newline at end of file
diff --git a/tests/sync-live-crippled/remote.conf b/tests/sync-live-crippled/remote.conf
new file mode 100644
index 0000000..ee22c5f
--- /dev/null
+++ b/tests/sync-live-crippled/remote.conf
@@ -0,0 +1,6 @@
+namespace inbox {
+ separator = ^
+}
+
+!include conf.d/imapd.conf
+!include conf.d/interimap-required-capabilities.conf
diff --git a/tests/sync-live-crippled/t b/tests/sync-live-crippled/t
new file mode 120000
index 0000000..189360e
--- /dev/null
+++ b/tests/sync-live-crippled/t
@@ -0,0 +1 @@
+../sync-live/t \ No newline at end of file
diff --git a/tests/sync-live-multi/interimap1.local b/tests/sync-live-multi/interimap1.local
new file mode 100644
index 0000000..a013813
--- /dev/null
+++ b/tests/sync-live-multi/interimap1.local
@@ -0,0 +1 @@
+list-reference = foo/
diff --git a/tests/sync-live-multi/interimap2.local b/tests/sync-live-multi/interimap2.local
new file mode 100644
index 0000000..8f10633
--- /dev/null
+++ b/tests/sync-live-multi/interimap2.local
@@ -0,0 +1 @@
+list-reference = bar/
diff --git a/tests/sync-live-multi/interimap3.local b/tests/sync-live-multi/interimap3.local
new file mode 100644
index 0000000..31dee55
--- /dev/null
+++ b/tests/sync-live-multi/interimap3.local
@@ -0,0 +1 @@
+list-reference = baz/
diff --git a/tests/07-sync-live-multi/local.conf b/tests/sync-live-multi/local.conf
index baae39d..baae39d 100644
--- a/tests/07-sync-live-multi/local.conf
+++ b/tests/sync-live-multi/local.conf
diff --git a/tests/sync-live-multi/remote1.conf b/tests/sync-live-multi/remote1.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/sync-live-multi/remote1.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/sync-live-multi/remote2.conf b/tests/sync-live-multi/remote2.conf
new file mode 100644
index 0000000..9657e89
--- /dev/null
+++ b/tests/sync-live-multi/remote2.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "\\"
+}
diff --git a/tests/sync-live-multi/remote3.conf b/tests/sync-live-multi/remote3.conf
new file mode 100644
index 0000000..0b6aafd
--- /dev/null
+++ b/tests/sync-live-multi/remote3.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = "?"
+}
diff --git a/tests/sync-live-multi/remotes b/tests/sync-live-multi/remotes
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/tests/sync-live-multi/remotes
@@ -0,0 +1 @@
+3
diff --git a/tests/07-sync-live-multi/run b/tests/sync-live-multi/t
index 15a27fd..9b129ec 100644
--- a/tests/07-sync-live-multi/run
+++ b/tests/sync-live-multi/t
@@ -1,72 +1,59 @@
-# add references to each interimap instance
-sed -ri 's#^\[local\]$#&\nlist-reference = foo/#' "$XDG_CONFIG_HOME/interimap/config"
-sed -ri 's#^\[local\]$#&\nlist-reference = bar/#' "$XDG_CONFIG_HOME/interimap/config2"
-sed -ri 's#^\[local\]$#&\nlist-reference = baz/#' "$XDG_CONFIG_HOME/interimap/config3"
-
-# create databases
-interimap --config="config"
-interimap --config="config2"
-interimap --config="config3"
-
-# start long-lived interimap processes
-interimap --config="config" --watch=1 & pid=$!
-interimap --config="config2" --watch=1 & pid2=$!
-interimap --config="config3" --watch=1 & pid3=$!
-
-abort() {
- # kill interimap process and its children
- pkill -P "$pid" -TERM || true
- kill -TERM "$pid" || true
- pkill -P "$pid2" -TERM || true
- kill -TERM "$pid2" || true
- pkill -P "$pid3" -TERM || true
- kill -TERM "$pid3" || true
- wait
-}
-trap abort EXIT INT TERM
-
+TIMEOUT=60
# mailbox list (as seen on local) and alphabet
-declare -a mailboxes=( "INBOX" ) alphabet=()
+declare -a MAILBOXES=( "INBOX" ) ALPHABET=()
str="#+-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
for ((i=0; i < ${#str}; i++)); do
- alphabet[i]="${str:i:1}"
+ ALPHABET[i]="${str:i:1}"
done
-declare -a targets=( "local" "remote" "remote2" "remote3" )
+declare -a TARGETS=( "local" "remote1" "remote2" "remote3" )
+
+
+# create databases
+interimap_init "remote1"
+interimap_init "remote2"
+interimap_init "remote3"
+
+# start long-lived interimap processes
+declare -a PID=()
+trap 'ptree_abort ${PID[@]}' EXIT INT TERM
+interimap --config="config1" --watch=1 & PID+=( $! )
+interimap --config="config2" --watch=1 & PID+=( $! )
+interimap --config="config3" --watch=1 & PID+=( $! )
-timer=$(( $(date +%s) + 30 ))
+timer=$(( $(date +%s) + TIMEOUT ))
while [ $(date +%s) -le $timer ]; do
# create new mailbox with 10% probability
if [ $(shuf -n1 -i0-9) -eq 0 ]; then
- u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random
+ u="$(shuf -n1 -e -- "${TARGETS[@]}")" # choose target at random
case "$u" in
local) ns="$(shuf -n1 -e "foo/" "bar/" "baz/")";;
- remote) ns="foo/";;
+ remote1) ns="foo/";;
remote2) ns="bar/";;
remote3) ns="baz/";;
- *) echo "Uh?" >&2; exit 1;;
+ *) error "Uh?";;
esac
m=
d=$(shuf -n1 -i1-3) # random depth
for (( i=0; i < d; i++)); do
l=$(shuf -n1 -i1-16)
- m="${m:+$m/}$(shuf -n "$l" -e -- "${alphabet[@]}" | tr -d '\n')"
+ m="${m:+$m/}$(shuf -n "$l" -e -- "${ALPHABET[@]}" | tr -d '\n')"
done
- mailboxes+=( "$ns$m" )
+ MAILBOXES+=( "$ns$m" )
case "$u" in
local) m="$ns$m";;
- remote) m="${m//\//^}";;
+ remote1) m="${m//\//^}";;
remote2) m="${m//\//\\}";;
remote3) m="${m//\//\?}";;
- *) echo "Uh?" >&2; exit 1;;
+ *) error "Uh?";;
esac
doveadm -u "$u" mailbox create -- "$m"
fi
# EXPUNGE some messages
- u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random
+ u="$(shuf -n1 -e -- "${TARGETS[@]}")" # choose target at random
n="$(shuf -n1 -i0-3)"
while read guid uid; do
doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid"
@@ -74,7 +61,7 @@ while [ $(date +%s) -le $timer ]; do
# mark some existing messages as read (toggle \Seen flag as unlike other
# flags it's easier to query and check_mailboxes_status checks it)
- u="$(shuf -n1 -e -- "${targets[@]}")" # choose target at random
+ u="$(shuf -n1 -e -- "${TARGETS[@]}")" # choose target at random
n="$(shuf -n1 -i0-9)"
while read guid uid; do
a="$(shuf -n1 -e add remove replace)"
@@ -83,14 +70,14 @@ while [ $(date +%s) -le $timer ]; do
# select at random a mailbox where to deliver some messages
u="$(shuf -n1 -e "local" "remote")" # choose target at random
- m="$(shuf -n1 -e -- "${mailboxes[@]}")"
+ m="$(shuf -n1 -e -- "${MAILBOXES[@]}")"
if [ "$u" = "remote" ]; then
case "$m" in
- foo/*) u="remote"; m="${m#foo/}"; m="${m//\//^}";;
+ foo/*) u="remote1"; m="${m#foo/}"; m="${m//\//^}";;
bar/*) u="remote2"; m="${m#bar/}"; m="${m//\//\\}";;
baz/*) u="remote3"; m="${m#baz/}"; m="${m//\//\?}";;
- INBOX) u="$(shuf -n1 -e "remote" "remote2" "remote3")";;
- *) echo "Uh? $m" >&2; exit 1;;
+ INBOX) u="$(shuf -n1 -e "remote1" "remote2" "remote3")";;
+ *) error "Uh? $m";;
esac
fi
@@ -100,8 +87,10 @@ while [ $(date +%s) -le $timer ]; do
sample_message | deliver -u "$u" -- -m "$m"
done
- # sleep a little bit
- sleep "$(printf "0.%03d" "$(shuf -n1 -i1-999)")"
+ # sleep a little bit (sometimes beyond --watch timer, sometimes not)
+ s=$(shuf -n1 -i1-1500)
+ [ $s -ge 1000 ] && s="$(printf "1.%03d" $((s-1000)))" || s="$(printf "0.%03d" $s)"
+ sleep "$s"
done
# wait a little longer so interimap has time to run loop() again and
@@ -109,13 +98,13 @@ done
# started above
sleep 2
-abort
+ptree_abort ${PID[@]}
trap - EXIT INT TERM
# check that the mailbox lists match
-diff -u --label="local/mailboxes" --label="remote/mailboxes" \
- <( doveadm -u "local" mailbox list | sed -n "s,^foo/,,p" | sort ) \
- <( doveadm -u "remote" mailbox list | tr '^' '/' | sort )
+diff -u --label="local/mailboxes" --label="remote1/mailboxes" \
+ <( doveadm -u "local" mailbox list | sed -n "s,^foo/,,p" | sort ) \
+ <( doveadm -u "remote1" mailbox list | tr '^' '/' | sort )
diff -u --label="local/mailboxes" --label="remote2/mailboxes" \
<( doveadm -u "local" mailbox list | sed -n "s,^bar/,,p" | sort ) \
<( doveadm -u "remote2" mailbox list | tr '\\' '/' | sort )
@@ -123,13 +112,13 @@ diff -u --label="local/mailboxes" --label="remote3/mailboxes" \
<( doveadm -u "local" mailbox list | sed -n "s,^baz/,,p" | sort ) \
<( doveadm -u "remote3" mailbox list | tr '?' '/' | sort )
-for m in "${mailboxes[@]}"; do
+for m in "${MAILBOXES[@]}"; do
case "$m" in
- foo/*) u="remote"; mb="${m#foo/}"; mr="${mb//\//^}";;
+ foo/*) u="remote1"; mb="${m#foo/}"; mr="${mb//\//^}";;
bar/*) u="remote2"; mb="${m#bar/}"; mr="${mb//\//\\}";;
baz/*) u="remote3"; mb="${m#baz/}"; mr="${mb//\//\?}";;
INBOX) continue;;
- *) echo "Uh? $m" >&2; exit 1;;
+ *) error "Uh? $m";;
esac
blob="x'$(printf "%s" "$mb" | tr "/" "\\0" | xxd -c256 -ps)'"
check_mailbox_status2 "$blob" "$m" "$u" "$mr"
diff --git a/tests/sync-live-tls/interimap.remote b/tests/sync-live-tls/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/sync-live-tls/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/sync-live-tls/local.conf b/tests/sync-live-tls/local.conf
new file mode 120000
index 0000000..ad27dd1
--- /dev/null
+++ b/tests/sync-live-tls/local.conf
@@ -0,0 +1 @@
+../sync-live/local.conf \ No newline at end of file
diff --git a/tests/sync-live-tls/remote.conf b/tests/sync-live-tls/remote.conf
new file mode 100644
index 0000000..b2af2d2
--- /dev/null
+++ b/tests/sync-live-tls/remote.conf
@@ -0,0 +1,6 @@
+namespace inbox {
+ separator = ^
+}
+
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
diff --git a/tests/sync-live-tls/t b/tests/sync-live-tls/t
new file mode 120000
index 0000000..189360e
--- /dev/null
+++ b/tests/sync-live-tls/t
@@ -0,0 +1 @@
+../sync-live/t \ No newline at end of file
diff --git a/tests/sync-live/local.conf b/tests/sync-live/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/sync-live/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/sync-live/remote.conf b/tests/sync-live/remote.conf
new file mode 100644
index 0000000..2d08a24
--- /dev/null
+++ b/tests/sync-live/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ^
+}
diff --git a/tests/07-sync-live/run b/tests/sync-live/t
index 04d8247..19d1e08 100644
--- a/tests/07-sync-live/run
+++ b/tests/sync-live/t
@@ -1,25 +1,19 @@
-# create database
-interimap
-
-# start a long-lived interimap process
-interimap --watch=1 & pid=$!
-
-abort() {
- # kill interimap process and its children
- pkill -P "$pid" -TERM || true
- kill -TERM "$pid" || true
- wait
-}
-trap abort EXIT INT TERM
+TIMEOUT=60
# mailbox list and alphabet (exclude &, / and ~, which dovecot treats specially)
-declare -a mailboxes=( "INBOX" ) alphabet=()
+declare -a MAILBOXES=( "INBOX" ) ALPHABET=()
str="!\"#\$'()+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]_\`abcdefghijklmnopqrstuvwxyz{|}"
for ((i=0; i < ${#str}; i++)); do
- alphabet[i]="${str:i:1}"
+ ALPHABET[i]="${str:i:1}"
done
-timer=$(( $(date +%s) + 30 ))
+interimap_init
+
+# start a long-lived interimap process
+interimap --watch=1 & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+timer=$(( $(date +%s) + TIMEOUT ))
while [ $(date +%s) -le $timer ]; do
# create new mailbox with 10% probability
if [ $(shuf -n1 -i0-9) -eq 0 ]; then
@@ -27,9 +21,9 @@ while [ $(date +%s) -le $timer ]; do
d=$(shuf -n1 -i1-3) # random depth
for (( i=0; i < d; i++)); do
l=$(shuf -n1 -i1-16)
- m="${m:+$m.}$(shuf -n "$l" -e -- "${alphabet[@]}" | tr -d '\n')"
+ m="${m:+$m.}$(shuf -n "$l" -e -- "${ALPHABET[@]}" | tr -d '\n')"
done
- mailboxes+=( "$m" )
+ MAILBOXES+=( "$m" )
u="$(shuf -n1 -e "local" "remote")" # choose target at random
[ "$u" = "local" ] || m="${m//./^}"
doveadm -u "$u" mailbox create -- "$m"
@@ -53,7 +47,7 @@ while [ $(date +%s) -le $timer ]; do
# select at random a mailbox where to deliver some messages
u="$(shuf -n1 -e "local" "remote")" # choose target at random
- m="$(shuf -n1 -e -- "${mailboxes[@]}")"
+ m="$(shuf -n1 -e -- "${MAILBOXES[@]}")"
[ "$u" = "local" ] || m="${m//./^}"
# deliver between 1 and 5 messages to the chosen mailbox
@@ -62,8 +56,10 @@ while [ $(date +%s) -le $timer ]; do
sample_message | deliver -u "$u" -- -m "$m"
done
- # sleep a little bit
- sleep "$(printf "0.%03d" "$(shuf -n1 -i1-999)")"
+ # sleep a little bit (sometimes beyond --watch timer, sometimes not)
+ s=$(shuf -n1 -i1-1500)
+ [ $s -ge 1000 ] && s="$(printf "1.%03d" $((s-1000)))" || s="$(printf "0.%03d" $s)"
+ sleep "$s"
done
# wait a little longer so interimap has time to run loop() again and
@@ -71,10 +67,10 @@ done
# above
sleep 2
-abort
+ptree_abort $PID
trap - EXIT INT TERM
check_mailbox_list
-check_mailboxes_status "${mailboxes[@]}"
+check_mailboxes_status "${MAILBOXES[@]}"
# vim: set filetype=sh :
diff --git a/tests/sync-mailbox-list/local.conf b/tests/sync-mailbox-list/local.conf
new file mode 100644
index 0000000..b56cc70
--- /dev/null
+++ b/tests/sync-mailbox-list/local.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = .
+}
diff --git a/tests/sync-mailbox-list/remote.conf b/tests/sync-mailbox-list/remote.conf
new file mode 100644
index 0000000..1cbbc07
--- /dev/null
+++ b/tests/sync-mailbox-list/remote.conf
@@ -0,0 +1,3 @@
+namespace inbox {
+ separator = ~
+}
diff --git a/tests/sync-mailbox-list/t b/tests/sync-mailbox-list/t
new file mode 100644
index 0000000..ea80fbf
--- /dev/null
+++ b/tests/sync-mailbox-list/t
@@ -0,0 +1,93 @@
+# pre-create some mailboxes and susbscribe to some
+# foo: present on both, subscribed to both
+# bar: present on both, subscribed to local only
+# baz: present on both, subscribed to remote only
+# foo.bar: present on local only
+# foo.baz: present on remote only
+doveadm -u "local" mailbox create "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]"
+doveadm -u "local" mailbox subscribe "foo" "bar"
+doveadm -u "remote" mailbox create "foo" "bar" "baz" "foo~baz" "foo]bar"
+doveadm -u "remote" mailbox subscribe "foo" "baz"
+
+populate() {
+ local i
+ for ((i = 0; i < 32; i++)); do
+ m="$(shuf -n1 -e -- "foo" "bar" "baz" "foo.bar" "fo!o [b*a%r]")"
+ sample_message | deliver -u "local" -- -m "$m"
+
+ m="$(shuf -n1 -e -- "foo" "bar" "baz" "foo~baz" "foo]bar")"
+ sample_message | deliver -u "remote" -- -m "$m"
+ done
+}
+verify() {
+ check_mailbox_list || error
+ check_mailboxes_status "foo" "bar" "baz" "foo.bar" "foo.baz" "INBOX" "fo!o [b*a%r]" "foo]bar"
+}
+populate
+
+step_start "pre-subscribtions"
+interimap_init
+grep -Fx "local: Subscribe to baz" <"$STDERR" || error
+grep -Fx "remote: Subscribe to bar" <"$STDERR" || error
+grep -Fx "local: Created mailbox foo.baz" <"$STDERR" || error
+grep -Fx "remote: Created mailbox foo~bar" <"$STDERR" || error
+step_done
+
+# ensure the mailbox list is synchronized
+step_start "mailbox list and content"
+verify
+check_mailbox_list -s
+step_done
+
+
+# delete a mailbox on one server and verify that synchronization fails as it's still in the database
+step_start "aborts if present in database"
+for u in "local" "remote"; do
+ [ "$u" = "local" ] && { m="foo.bar"; m2="$m"; } || { m="foo.baz"; m2="foo~baz"; }
+
+ doveadm -u "$u" mailbox delete "$m2"
+ ! interimap || error
+ grep -Fx "database: ERROR: Mailbox $m exists. Run \`interimap --target=database --delete $m\` to delete." <"$STDERR"
+
+ interimap --target="database" --delete "$m" || error
+ grep -Fx "database: Removed mailbox $m" <"$STDERR" || error
+
+ interimap || error # create again
+ grep -Fx "database: Created mailbox $m" <"$STDERR" || error
+ grep -Fx "$u: Created mailbox $m2" <"$STDERR" || error
+done
+verify
+check_mailbox_list -s
+step_done
+
+
+
+# (un)subscribe from some mailboxes, including a non-existent one
+step_start "new (un)subscribtions"
+doveadm -u "local" mailbox unsubscribe "foo"
+doveadm -u "remote" mailbox unsubscribe "bar"
+doveadm -u "local" mailbox subscribe "foo.bar" "foo.nonexistent" "foo.baz"
+doveadm -u "remote" mailbox subscribe "foo~bar" "bar~nonexistent"
+populate
+
+interimap
+grep -Fx "remote: Unsubscribe to foo" <"$STDERR"
+grep -Fx "local: Unsubscribe to bar" <"$STDERR"
+grep -Fx "remote: Subscribe to foo~baz" <"$STDERR"
+verify
+check_mailbox_list -s $(doveadm -u "local" mailbox list) # exclude "foo.nonexistent" and "bar~nonexistent"
+
+# check that "baz", "foo.bar" and "foo.baz" are the only subscribed mailboxes
+sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF
+ SELECT COUNT(*)
+ FROM mailboxes
+ WHERE subscribed <> (mailbox IN (
+ x'$(printf "%s" "baz" | xxd -ps)',
+ x'$(printf "%s\\0%s" "foo" "bar" | xxd -ps)',
+ x'$(printf "%s\\0%s" "foo" "baz" | xxd -ps)'
+ ))
+EOF
+[ $(< "$TMPDIR/count") -eq 0 ] || error
+step_done
+
+# vim: set filetype=sh :
diff --git a/tests/tls-pin-fingerprint/interimap.remote b/tests/tls-pin-fingerprint/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/tls-pin-fingerprint/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/tls-pin-fingerprint/remote.conf b/tests/tls-pin-fingerprint/remote.conf
new file mode 120000
index 0000000..6029749
--- /dev/null
+++ b/tests/tls-pin-fingerprint/remote.conf
@@ -0,0 +1 @@
+../tls/remote.conf \ No newline at end of file
diff --git a/tests/tls-pin-fingerprint/t b/tests/tls-pin-fingerprint/t
new file mode 100644
index 0000000..1b84390
--- /dev/null
+++ b/tests/tls-pin-fingerprint/t
@@ -0,0 +1,33 @@
+# backup config
+install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
+with_remote_config() {
+ install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ cat >>"$XDG_CONFIG_HOME/interimap/config"
+}
+
+# pinned valid fingerprint
+with_remote_config <<-EOF
+ SSL_fingerprint = sha256\$e8fc8d03ffe75e03897136a2f1c5647bf8c36be7136a6883a732a8c4961c1614
+EOF
+
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+interimap_init
+check_mailbox_status "INBOX"
+
+
+# and now an invalid one
+with_remote_config <<-EOF
+ SSL_fingerprint = sha256\$deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
+EOF
+! interimap --debug || error
+
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+# make sure we didn't send any credentials
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/tls-protocols/interimap.remote b/tests/tls-protocols/interimap.remote
new file mode 120000
index 0000000..daf3741
--- /dev/null
+++ b/tests/tls-protocols/interimap.remote
@@ -0,0 +1 @@
+../tls/interimap.remote \ No newline at end of file
diff --git a/tests/tls-protocols/remote.conf b/tests/tls-protocols/remote.conf
new file mode 120000
index 0000000..6029749
--- /dev/null
+++ b/tests/tls-protocols/remote.conf
@@ -0,0 +1 @@
+../tls/remote.conf \ No newline at end of file
diff --git a/tests/tls-protocols/t b/tests/tls-protocols/t
new file mode 100644
index 0000000..f34a95b
--- /dev/null
+++ b/tests/tls-protocols/t
@@ -0,0 +1,39 @@
+# backup config
+install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
+with_remote_tls_protocols() {
+ install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ printf "SSL_protocols = %s\\n" "$*" >>"$XDG_CONFIG_HOME/interimap/config"
+}
+
+# default
+interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+
+# also disable TLSv1.2
+with_remote_tls_protocols "!SSLv2" "!SSLv3" "!TLSv1" "!TLSv1.1" "!TLSv1.2"
+interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1, TLSv1.2" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.3 " <"$STDERR" || error
+
+# force TLSv1.2
+with_remote_tls_protocols "TLSv1.2"
+interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1, TLSv1.3" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv1\.2 " <"$STDERR" || error
+
+# force TLSv1 to TLSv1.2
+with_remote_tls_protocols "TLSv1" "TLSv1.1" "TLSv1.2"
+interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1.3" <"$STDERR" || error
+grep -E "^remote: SSL protocol: TLSv(1\.[12])? " <"$STDERR" || error
+
+# force SSLv2 and SSLv3, fails as it's disabled server side
+with_remote_tls_protocols "SSLv2" "SSLv3"
+! interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: TLSv1, TLSv1.1, TLSv1.2, TLSv1.3" <"$STDERR" || error
+grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+# make sure we didn't send any credentials
+! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+
+# vim: set filetype=sh :
diff --git a/tests/tls-verify-peer/interimap.remote b/tests/tls-verify-peer/interimap.remote
new file mode 100644
index 0000000..b02fcd0
--- /dev/null
+++ b/tests/tls-verify-peer/interimap.remote
@@ -0,0 +1,2 @@
+host = ::1
+port = 10993
diff --git a/tests/tls-verify-peer/remote.conf b/tests/tls-verify-peer/remote.conf
new file mode 120000
index 0000000..6029749
--- /dev/null
+++ b/tests/tls-verify-peer/remote.conf
@@ -0,0 +1 @@
+../tls/remote.conf \ No newline at end of file
diff --git a/tests/tls-verify-peer/t b/tests/tls-verify-peer/t
new file mode 100644
index 0000000..d84328a
--- /dev/null
+++ b/tests/tls-verify-peer/t
@@ -0,0 +1,80 @@
+CERT=~/.dovecot/conf.d/dovecot.pem
+
+unverified_peer() {
+ ! interimap --debug || error
+
+ grep -Fx "remote: ERROR: Can't initiate TLS/SSL handshake" <"$STDERR" || error
+ sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify"
+ [ -s "$TMPDIR/preverify" ] || error
+ ! grep -Fvx "preverify=0" <"$TMPDIR/preverify" || error
+
+ # make sure we didn't send any credentials
+ ! grep -E "^remote: C: .* (AUTHENTICATE|LOGIN) " <"$STDERR" || error
+}
+verified_peer() {
+ local i u
+ for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+ done
+ interimap --debug || error
+
+ sed -nr "s/remote: \[[0-9]+\] (preverify=[0-9]+)$/\1/p" <"$STDERR" >"$TMPDIR/preverify"
+ [ -s "$TMPDIR/preverify" ] || error
+ ! grep -Fvx "preverify=1" <"$TMPDIR/preverify" || error
+
+ grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+ grep "^remote: SSL cipher: " <"$STDERR" || error
+
+ check_mailbox_status "INBOX"
+}
+
+# backup config
+install -m0600 "$XDG_CONFIG_HOME/interimap/config" "$XDG_CONFIG_HOME/interimap/config~"
+with_remote_config() {
+ install -m0600 "$XDG_CONFIG_HOME/interimap/config~" "$XDG_CONFIG_HOME/interimap/config"
+ cat >>"$XDG_CONFIG_HOME/interimap/config"
+}
+
+step_start "peer verification enabled by default"
+unverified_peer
+step_done
+
+step_start "peer verification result honored when pinned pubkey matches"
+pkey_sha256="$(openssl x509 -pubkey <"$CERT" | openssl pkey -pubin -outform DER \
+ | openssl dgst -sha256 | sed -rn "/^.*=\\s*/ {s///p;q}")"
+with_remote_config <<-EOF
+ SSL_fingerprint = sha256\$$pkey_sha256
+EOF
+unverified_peer
+! grep -Fx "remote: WARNING: Fingerprint doesn't match! MiTM in action?" <"$STDERR" || error
+step_done
+
+
+step_start "SSL_CAfile"
+if [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+ # the self-signed cert should not be in there
+ with_remote_config <<<"SSL_CAfile = /etc/ssl/certs/ca-certificates.crt"
+ unverified_peer
+fi
+with_remote_config <<<"SSL_CAfile = $CERT"
+verified_peer
+step_done
+
+
+step_start "SSL_CApath"
+if [ -d "/etc/ssl/certs" ]; then
+ # the self-signed cert should not be in there
+ with_remote_config <<<"SSL_CApath = /etc/ssl/certs"
+ unverified_peer
+fi
+
+capath=$(mktemp --tmpdir="$TMPDIR" --directory capath.XXXXXX)
+cp -t"$capath" "$CERT"
+c_rehash "$capath"
+
+with_remote_config <<<"SSL_CApath = $capath"
+verified_peer
+step_done
+
+# vim: set filetype=sh :
diff --git a/tests/tls/interimap.remote b/tests/tls/interimap.remote
new file mode 100644
index 0000000..2c0e37e
--- /dev/null
+++ b/tests/tls/interimap.remote
@@ -0,0 +1,3 @@
+host = ::1
+port = 10993
+SSL_verify = no
diff --git a/tests/tls/remote.conf b/tests/tls/remote.conf
new file mode 100644
index 0000000..3d07ea9
--- /dev/null
+++ b/tests/tls/remote.conf
@@ -0,0 +1,2 @@
+!include conf.d/imapd.conf
+!include conf.d/ssl.conf
diff --git a/tests/tls/t b/tests/tls/t
new file mode 100644
index 0000000..dd6d955
--- /dev/null
+++ b/tests/tls/t
@@ -0,0 +1,14 @@
+for ((i = 0; i < 32; i++)); do
+ u="$(shuf -n1 -e "local" "remote")"
+ sample_message | deliver -u "$u"
+done
+
+interimap --debug || error
+grep -Fx "remote: Disabling SSL protocols: SSLv3, TLSv1, TLSv1.1" <"$STDERR" || error
+grep -Fx "remote: Peer certificate fingerprint: sha256\$35944e3bd3300d3ac310bb497a32cc1eef6931482a587ddbc95343740cdf1323" <"$STDERR" || error
+grep "^remote: SSL protocol: TLSv1\.[23] " <"$STDERR" || error
+grep "^remote: SSL cipher: " <"$STDERR" || error
+
+check_mailbox_status "INBOX"
+
+# vim: set filetype=sh :