diff options
179 files changed, 1800 insertions, 740 deletions
@@ -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 @@ -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 @@ -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); @@ -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" @@ -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 : |