From 37ebe331178e2b7d225a31f64463aef5448d4970 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 8 Nov 2019 05:27:36 +0100 Subject: libinterimap: honor compress={Yes/No}. --- Changelog | 1 + lib/Net/IMAP/InterIMAP.pm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index 48481dd..5b43c6f 100644 --- a/Changelog +++ b/Changelog @@ -84,6 +84,7 @@ 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. -- Guilhem Moulin Fri, 10 May 2019 00:58:14 +0200 diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index bb27009..3d5bdcf 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -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/, -- cgit v1.2.3 From d08ee23dccf56af292a9616986e58cc39386e3fb Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Fri, 8 Nov 2019 06:53:19 +0100 Subject: Remove deprecated/buggy 'o' regexp modifier. --- interimap | 7 +++++-- lib/Net/IMAP/InterIMAP.pm | 11 +++++------ pullimap | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/interimap b/interimap index 87c3a64..f170ded 100755 --- a/interimap +++ b/interimap @@ -636,6 +636,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 +649,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; } } diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 3d5bdcf..77c1b14 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1408,10 +1408,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 +1421,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 +1429,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; diff --git a/pullimap b/pullimap index dcbe59b..f983fe1 100755 --- a/pullimap +++ b/pullimap @@ -286,6 +286,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 +295,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) -- cgit v1.2.3 From b9da6cc7ecf71026d1023dc3354b820c7518426e Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sat, 9 Nov 2019 03:10:45 +0100 Subject: interimap.sample: remove leading `exec`. The value is passed to `/bin/sh -c` if it contains shell metacharacters; otherwise it is split into words and the resulting list is passed to execvp(3). --- interimap.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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] -- cgit v1.2.3 From 67440844c422ee30b31df9a46a7f99ac0e833add Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sat, 9 Nov 2019 03:13:45 +0100 Subject: Refactor and improve test suite. --- Makefile | 2 +- interimap | 5 +- tests/00-db-exclusive/local.conf | 5 - tests/00-db-exclusive/remote.conf | 5 - tests/00-db-exclusive/run | 25 --- .../before.sql | 1 - .../local.conf | 6 - .../remote.conf | 6 - tests/00-db-migration-0-to-1-delim-mismatch/run | 8 - .../local.conf | 6 - .../remote.conf | 6 - .../run | 23 --- tests/00-db-migration-0-to-1/after.sql | 14 -- tests/00-db-migration-0-to-1/before.sql | 14 -- tests/00-db-migration-0-to-1/local.conf | 6 - tests/00-db-migration-0-to-1/remote.conf | 6 - tests/00-db-migration-0-to-1/run | 33 ---- tests/01-rename-exists-db/local.conf | 6 - tests/01-rename-exists-db/remote.conf | 6 - tests/01-rename-exists-db/run | 14 -- tests/01-rename-exists-local/local.conf | 6 - tests/01-rename-exists-local/remote.conf | 6 - tests/01-rename-exists-local/run | 13 -- tests/01-rename-exists-remote/local.conf | 6 - tests/01-rename-exists-remote/remote.conf | 6 - tests/01-rename-exists-remote/run | 13 -- tests/01-rename/local.conf | 6 - tests/01-rename/remote.conf | 6 - tests/01-rename/run | 84 ---------- tests/02-delete/local.conf | 6 - tests/02-delete/remote.conf | 6 - tests/02-delete/run | 67 -------- tests/03-sync-mailbox-list-partial/interimap.conf | 1 - tests/03-sync-mailbox-list-partial/local.conf | 6 - tests/03-sync-mailbox-list-partial/remote.conf | 6 - tests/03-sync-mailbox-list-partial/run | 57 ------- tests/03-sync-mailbox-list-ref/local.conf | 6 - tests/03-sync-mailbox-list-ref/remote.conf | 6 - tests/03-sync-mailbox-list-ref/run | 28 ---- tests/03-sync-mailbox-list/local.conf | 6 - tests/03-sync-mailbox-list/remote.conf | 6 - tests/03-sync-mailbox-list/run | 73 --------- tests/04-resume/local.conf | 6 - tests/04-resume/remote.conf | 6 - tests/04-resume/run | 98 ------------ tests/05-repair/local.conf | 6 - tests/05-repair/remote.conf | 6 - tests/05-repair/run | 107 ------------- tests/06-largeint/local.conf | 5 - tests/06-largeint/remote.conf | 5 - tests/06-largeint/run | 38 ----- tests/07-sync-live-multi/local.conf | 30 ---- tests/07-sync-live-multi/remote.conf | 6 - tests/07-sync-live-multi/remote2.conf | 6 - tests/07-sync-live-multi/remote3.conf | 6 - tests/07-sync-live-multi/run | 138 ---------------- tests/07-sync-live/local.conf | 6 - tests/07-sync-live/remote.conf | 6 - tests/07-sync-live/run | 80 ---------- tests/db-exclusive-lock/t | 16 ++ tests/db-migration-0-1-foreign-key-violation/t | 21 +++ tests/db-no-create--watch/t | 6 + tests/db-upgrade-0-1-delim-mismatch/before.sql | 1 + tests/db-upgrade-0-1-delim-mismatch/local.conf | 3 + tests/db-upgrade-0-1-delim-mismatch/remote.conf | 3 + tests/db-upgrade-0-1-delim-mismatch/t | 7 + tests/db-upgrade-0-1/after.sql | 14 ++ tests/db-upgrade-0-1/before.sql | 14 ++ tests/db-upgrade-0-1/local.conf | 3 + tests/db-upgrade-0-1/remote.conf | 1 + tests/db-upgrade-0-1/t | 34 ++++ tests/delete/local.conf | 3 + tests/delete/remote.conf | 3 + tests/delete/t | 95 +++++++++++ tests/ignore-mailbox/interimap.conf | 1 + tests/ignore-mailbox/local.conf | 3 + tests/ignore-mailbox/remote.conf | 3 + tests/ignore-mailbox/t | 62 ++++++++ tests/largeint/t | 39 +++++ tests/list | 37 +++++ tests/list-mailbox/interimap.conf | 1 + tests/list-mailbox/local.conf | 3 + tests/list-mailbox/remote.conf | 3 + tests/list-mailbox/t | 57 +++++++ tests/list-reference/interimap.local | 1 + tests/list-reference/interimap.remote | 1 + tests/list-reference/local.conf | 3 + tests/list-reference/remote.conf | 3 + tests/list-reference/t | 47 ++++++ tests/list-select-opts/interimap.conf | 1 + tests/list-select-opts/local.conf | 3 + tests/list-select-opts/remote.conf | 3 + tests/list-select-opts/t | 56 +++++++ tests/rename-exists-db/local.conf | 3 + tests/rename-exists-db/remote.conf | 3 + tests/rename-exists-db/t | 14 ++ tests/rename-exists-local/local.conf | 3 + tests/rename-exists-local/remote.conf | 3 + tests/rename-exists-local/t | 13 ++ tests/rename-exists-remote/local.conf | 3 + tests/rename-exists-remote/remote.conf | 3 + tests/rename-exists-remote/t | 13 ++ tests/rename-inferiors/local.conf | 3 + tests/rename-inferiors/remote.conf | 3 + tests/rename-inferiors/t | 100 ++++++++++++ tests/rename-simple/t | 61 ++++++++ tests/repair/local.conf | 3 + tests/repair/remote.conf | 3 + tests/repair/t | 107 +++++++++++++ tests/resume/local.conf | 3 + tests/resume/remote.conf | 3 + tests/resume/t | 98 ++++++++++++ tests/run | 173 ++++++++++++++------- tests/run-all | 65 ++++++++ tests/sync-live-crippled/local.conf | 1 + tests/sync-live-crippled/remote.conf | 6 + tests/sync-live-crippled/t | 1 + tests/sync-live-multi/interimap1.local | 1 + tests/sync-live-multi/interimap2.local | 1 + tests/sync-live-multi/interimap3.local | 1 + tests/sync-live-multi/local.conf | 30 ++++ tests/sync-live-multi/remote1.conf | 3 + tests/sync-live-multi/remote2.conf | 3 + tests/sync-live-multi/remote3.conf | 3 + tests/sync-live-multi/remotes | 1 + tests/sync-live-multi/t | 127 +++++++++++++++ tests/sync-live/local.conf | 3 + tests/sync-live/remote.conf | 3 + tests/sync-live/t | 76 +++++++++ tests/sync-mailbox-list/local.conf | 3 + tests/sync-mailbox-list/remote.conf | 3 + tests/sync-mailbox-list/t | 93 +++++++++++ 132 files changed, 1534 insertions(+), 1228 deletions(-) delete mode 100644 tests/00-db-exclusive/local.conf delete mode 100644 tests/00-db-exclusive/remote.conf delete mode 100644 tests/00-db-exclusive/run delete mode 120000 tests/00-db-migration-0-to-1-delim-mismatch/before.sql delete mode 100644 tests/00-db-migration-0-to-1-delim-mismatch/local.conf delete mode 100644 tests/00-db-migration-0-to-1-delim-mismatch/remote.conf delete mode 100644 tests/00-db-migration-0-to-1-delim-mismatch/run delete mode 100644 tests/00-db-migration-0-to-1-foreign-key-violation/local.conf delete mode 100644 tests/00-db-migration-0-to-1-foreign-key-violation/remote.conf delete mode 100644 tests/00-db-migration-0-to-1-foreign-key-violation/run delete mode 100644 tests/00-db-migration-0-to-1/after.sql delete mode 100644 tests/00-db-migration-0-to-1/before.sql delete mode 100644 tests/00-db-migration-0-to-1/local.conf delete mode 100644 tests/00-db-migration-0-to-1/remote.conf delete mode 100644 tests/00-db-migration-0-to-1/run delete mode 100644 tests/01-rename-exists-db/local.conf delete mode 100644 tests/01-rename-exists-db/remote.conf delete mode 100644 tests/01-rename-exists-db/run delete mode 100644 tests/01-rename-exists-local/local.conf delete mode 100644 tests/01-rename-exists-local/remote.conf delete mode 100644 tests/01-rename-exists-local/run delete mode 100644 tests/01-rename-exists-remote/local.conf delete mode 100644 tests/01-rename-exists-remote/remote.conf delete mode 100644 tests/01-rename-exists-remote/run delete mode 100644 tests/01-rename/local.conf delete mode 100644 tests/01-rename/remote.conf delete mode 100644 tests/01-rename/run delete mode 100644 tests/02-delete/local.conf delete mode 100644 tests/02-delete/remote.conf delete mode 100644 tests/02-delete/run delete mode 100644 tests/03-sync-mailbox-list-partial/interimap.conf delete mode 100644 tests/03-sync-mailbox-list-partial/local.conf delete mode 100644 tests/03-sync-mailbox-list-partial/remote.conf delete mode 100644 tests/03-sync-mailbox-list-partial/run delete mode 100644 tests/03-sync-mailbox-list-ref/local.conf delete mode 100644 tests/03-sync-mailbox-list-ref/remote.conf delete mode 100644 tests/03-sync-mailbox-list-ref/run delete mode 100644 tests/03-sync-mailbox-list/local.conf delete mode 100644 tests/03-sync-mailbox-list/remote.conf delete mode 100644 tests/03-sync-mailbox-list/run delete mode 100644 tests/04-resume/local.conf delete mode 100644 tests/04-resume/remote.conf delete mode 100644 tests/04-resume/run delete mode 100644 tests/05-repair/local.conf delete mode 100644 tests/05-repair/remote.conf delete mode 100644 tests/05-repair/run delete mode 100644 tests/06-largeint/local.conf delete mode 100644 tests/06-largeint/remote.conf delete mode 100644 tests/06-largeint/run delete mode 100644 tests/07-sync-live-multi/local.conf delete mode 100644 tests/07-sync-live-multi/remote.conf delete mode 100644 tests/07-sync-live-multi/remote2.conf delete mode 100644 tests/07-sync-live-multi/remote3.conf delete mode 100644 tests/07-sync-live-multi/run delete mode 100644 tests/07-sync-live/local.conf delete mode 100644 tests/07-sync-live/remote.conf delete mode 100644 tests/07-sync-live/run create mode 100644 tests/db-exclusive-lock/t create mode 100644 tests/db-migration-0-1-foreign-key-violation/t create mode 100644 tests/db-no-create--watch/t create mode 120000 tests/db-upgrade-0-1-delim-mismatch/before.sql create mode 100644 tests/db-upgrade-0-1-delim-mismatch/local.conf create mode 100644 tests/db-upgrade-0-1-delim-mismatch/remote.conf create mode 100644 tests/db-upgrade-0-1-delim-mismatch/t create mode 100644 tests/db-upgrade-0-1/after.sql create mode 100644 tests/db-upgrade-0-1/before.sql create mode 100644 tests/db-upgrade-0-1/local.conf create mode 120000 tests/db-upgrade-0-1/remote.conf create mode 100644 tests/db-upgrade-0-1/t create mode 100644 tests/delete/local.conf create mode 100644 tests/delete/remote.conf create mode 100644 tests/delete/t create mode 100644 tests/ignore-mailbox/interimap.conf create mode 100644 tests/ignore-mailbox/local.conf create mode 100644 tests/ignore-mailbox/remote.conf create mode 100644 tests/ignore-mailbox/t create mode 100644 tests/largeint/t create mode 100644 tests/list create mode 100644 tests/list-mailbox/interimap.conf create mode 100644 tests/list-mailbox/local.conf create mode 100644 tests/list-mailbox/remote.conf create mode 100644 tests/list-mailbox/t create mode 100644 tests/list-reference/interimap.local create mode 100644 tests/list-reference/interimap.remote create mode 100644 tests/list-reference/local.conf create mode 100644 tests/list-reference/remote.conf create mode 100644 tests/list-reference/t create mode 100644 tests/list-select-opts/interimap.conf create mode 100644 tests/list-select-opts/local.conf create mode 100644 tests/list-select-opts/remote.conf create mode 100644 tests/list-select-opts/t create mode 100644 tests/rename-exists-db/local.conf create mode 100644 tests/rename-exists-db/remote.conf create mode 100644 tests/rename-exists-db/t create mode 100644 tests/rename-exists-local/local.conf create mode 100644 tests/rename-exists-local/remote.conf create mode 100644 tests/rename-exists-local/t create mode 100644 tests/rename-exists-remote/local.conf create mode 100644 tests/rename-exists-remote/remote.conf create mode 100644 tests/rename-exists-remote/t create mode 100644 tests/rename-inferiors/local.conf create mode 100644 tests/rename-inferiors/remote.conf create mode 100644 tests/rename-inferiors/t create mode 100644 tests/rename-simple/t create mode 100644 tests/repair/local.conf create mode 100644 tests/repair/remote.conf create mode 100644 tests/repair/t create mode 100644 tests/resume/local.conf create mode 100644 tests/resume/remote.conf create mode 100644 tests/resume/t create mode 100755 tests/run-all create mode 120000 tests/sync-live-crippled/local.conf create mode 100644 tests/sync-live-crippled/remote.conf create mode 120000 tests/sync-live-crippled/t create mode 100644 tests/sync-live-multi/interimap1.local create mode 100644 tests/sync-live-multi/interimap2.local create mode 100644 tests/sync-live-multi/interimap3.local create mode 100644 tests/sync-live-multi/local.conf create mode 100644 tests/sync-live-multi/remote1.conf create mode 100644 tests/sync-live-multi/remote2.conf create mode 100644 tests/sync-live-multi/remote3.conf create mode 100644 tests/sync-live-multi/remotes create mode 100644 tests/sync-live-multi/t create mode 100644 tests/sync-live/local.conf create mode 100644 tests/sync-live/remote.conf create mode 100644 tests/sync-live/t create mode 100644 tests/sync-mailbox-list/local.conf create mode 100644 tests/sync-mailbox-list/remote.conf create mode 100644 tests/sync-mailbox-list/t diff --git a/Makefile b/Makefile index 4fc759f..fdfb717 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ $(MANUALS): %: %.md pandoc -s -f json -t man+smart -o "$@" test: - @for t in tests/*; do if [ -f "$$t/run" ]; then ./tests/run "$$t" || exit 1; fi; done + @./tests/run-all HTML_ROOTDIR ?= ./doc CSS ?= /usr/share/javascript/bootstrap/css/bootstrap.min.css diff --git a/interimap b/interimap index f170ded..a409c65 100755 --- a/interimap +++ b/interimap @@ -396,7 +396,7 @@ fail(undef, "Local and remote namespaces are neither both flat nor both hierarch fail(undef, "Local and remote hierachy delimiters differ ", "(local ", print_delimiter($IMAP->{local}->{delimiter}), ", ", "remote ", print_delimiter($IMAP->{remote}->{delimiter}), "), ", - "refusing to update \`mailboxes\` table.") + "refusing to update table \`mailboxes\`.") if defined $IMAP->{local}->{delimiter} and defined $IMAP->{remote}->{delimiter} # we failed earlier if only one of them was NIL and $IMAP->{local}->{delimiter} ne $IMAP->{remote}->{delimiter}; @@ -418,7 +418,7 @@ fail(undef, "Local and remote namespaces are neither both flat nor both hierarch $DBH->do("DROP TABLE mailboxes"); $DBH->do("ALTER TABLE _tmp${DATABASE_VERSION}_mailboxes RENAME TO mailboxes"); } - fail("database", "Broken referential integrity! Refusing to commit changes.") + fail("database", "Broken referential integrity! Refusing to commit changes.") if defined $DBH->selectrow_arrayref("PRAGMA foreign_key_check"); SCHEMA_DONE: $DBH->do("PRAGMA user_version = $DATABASE_VERSION"); @@ -530,6 +530,7 @@ if (defined $COMMAND and $COMMAND eq 'delete') { qw/mapping local remote mailboxes/ if @ARGV and $CONFIG{target}->{database}; foreach my $mailbox (@ARGV) { + fail(undef, "INBOX can't be deleted") if uc($mailbox) eq "INBOX"; # RFC 3501 sec. 6.3.4 my $idx = db_get_mailbox_idx($mailbox); # delete $mailbox on servers where $mailbox exists. note that 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/after.sql b/tests/00-db-migration-0-to-1/after.sql deleted file mode 100644 index 18b0ad7..0000000 --- a/tests/00-db-migration-0-to-1/after.sql +++ /dev/null @@ -1,14 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); -CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); -CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); -CREATE TABLE IF NOT EXISTS "mailboxes" (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox BLOB COLLATE BINARY NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); -INSERT INTO mailboxes VALUES(1,X'61006231006332',0); -INSERT INTO mailboxes VALUES(2,X'61006231006331',0); -INSERT INTO mailboxes VALUES(3,X'494e424f58',0); -INSERT INTO mailboxes VALUES(4,X'6132',0); -INSERT INTO mailboxes VALUES(5,X'610062320063',0); -DELETE FROM sqlite_sequence; -INSERT INTO sqlite_sequence VALUES('mailboxes',5); -COMMIT; diff --git a/tests/00-db-migration-0-to-1/before.sql b/tests/00-db-migration-0-to-1/before.sql deleted file mode 100644 index 333a1dc..0000000 --- a/tests/00-db-migration-0-to-1/before.sql +++ /dev/null @@ -1,14 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE mailboxes (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); -INSERT INTO mailboxes VALUES(1,'a.b1.c2',0); -INSERT INTO mailboxes VALUES(2,'a.b1.c1',0); -INSERT INTO mailboxes VALUES(3,'INBOX',0); -INSERT INTO mailboxes VALUES(4,'a2',0); -INSERT INTO mailboxes VALUES(5,'a.b2.c',0); -CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); -CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); -CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); -DELETE FROM sqlite_sequence; -INSERT INTO sqlite_sequence VALUES('mailboxes',5); -COMMIT; 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/00-db-migration-0-to-1/run b/tests/00-db-migration-0-to-1/run deleted file mode 100644 index 757fe04..0000000 --- a/tests/00-db-migration-0-to-1/run +++ /dev/null @@ -1,33 +0,0 @@ -# create some mailboxes -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" - -# migrate -interimap - -xgrep -Fx "Upgrading database version from 0" <"$STDERR" -check_mailboxes_status "a.b1.c1" "a.b1.c2" "a.b2.c" "a2" - -# verify that the new schema is as expected -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF - DELETE FROM local; - DELETE FROM remote; - .dump -EOF - -# re-import and dump the expected dump to work around SQLite format -# differences across versions -sqlite3 "$XDG_DATA_HOME/interimap/remote2.db" <"$TESTDIR/after.sql" -sqlite3 "$XDG_DATA_HOME/interimap/remote2.db" >"$TMPDIR/dump-expected.sql" <<-EOF - .dump -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" - -# vim: set filetype=sh : 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-db/run b/tests/01-rename-exists-db/run deleted file mode 100644 index aad7c44..0000000 --- a/tests/01-rename-exists-db/run +++ /dev/null @@ -1,14 +0,0 @@ -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 -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" - -# vim: set filetype=sh : 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-local/run b/tests/01-rename-exists-local/run deleted file mode 100644 index d82a0a4..0000000 --- a/tests/01-rename-exists-local/run +++ /dev/null @@ -1,13 +0,0 @@ -doveadm -u "local" mailbox create "root.from" "root.from.child" "t.o" -doveadm -u "remote" mailbox create "root\\from" "root\\from\\child" - -interimap -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" - -# vim: set filetype=sh : 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-exists-remote/run b/tests/01-rename-exists-remote/run deleted file mode 100644 index 28af1fc..0000000 --- a/tests/01-rename-exists-remote/run +++ /dev/null @@ -1,13 +0,0 @@ -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 -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" - -# vim: set filetype=sh : 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/01-rename/run b/tests/01-rename/run deleted file mode 100644 index 6541c5c..0000000 --- a/tests/01-rename/run +++ /dev/null @@ -1,84 +0,0 @@ -doveadm -u "local" mailbox create "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" -doveadm -u "remote" mailbox create "root^sibbling" "root^sibbling^grandchild" "root2" - -for m in "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" "INBOX"; do - sample_message | deliver -u "local" -- -m "$m" -done -for m in "root^sibbling" "root^sibbling^grandchild" "root2" "INBOX"; do - sample_message | deliver -u "remote" -- -m "$m" -done - -interimap -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 - .mode csv - SELECT idx, hex(mailbox) - FROM mailboxes - ORDER BY idx -EOF - -# renaming a non-existent mailbox doesn't yield an error -interimap --rename "nonexistent" "nonexistent2" -check_mailbox_list - -# 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 -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" - -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)" -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF - .mode csv - SELECT idx, - CASE - WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' - THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) - 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" - - -# Try to rename \NonExistent root and check that its children move -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" - -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)" -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes3.csv" <<-EOF - .mode csv - SELECT idx, - CASE - WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' - THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) - WHEN hex(mailbox) LIKE '${after2}00%' - THEN '$before2' || SUBSTR(hex(mailbox), $((${#after2}+1))) - ELSE hex(mailbox) - END - FROM mailboxes - ORDER BY idx -EOF -diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \ - "$TMPDIR/mailboxes2.csv" "$TMPDIR/mailboxes3.csv" - -# vim: set filetype=sh : 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/02-delete/run b/tests/02-delete/run deleted file mode 100644 index f63c52c..0000000 --- a/tests/02-delete/run +++ /dev/null @@ -1,67 +0,0 @@ -doveadm -u "local" mailbox create "foo.bar" "foo.bar.baz" - -for m in "foo.bar" "foo.bar.baz" "INBOX"; do - sample_message | deliver -u "local" -- -m "$m" -done - -interimap -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" - -check_mailbox_list -check_mailboxes_status "foo.bar" "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" - -# 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." -interimap --target="local" --delete "foo.bar" - -check_mailbox_list -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" - -! 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)' -EOF -diff -u --label="a/mailboxes.csv" --label="b/mailboxes.csv" \ - "$TMPDIR/mailboxes.csv" "$TMPDIR/mailboxes2.csv" - -check_mailbox_list -check_mailboxes_status "foo.bar.baz" "INBOX" - -# vim: set filetype=sh : 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/04-resume/run b/tests/04-resume/run deleted file mode 100644 index 22d66bc..0000000 --- a/tests/04-resume/run +++ /dev/null @@ -1,98 +0,0 @@ -# create and populate a bunch of mailboxes -doveadm -u "local" mailbox create "foo" "foo.bar" "baz" -for ((i = 0; i < 8; i++)); do - sample_message | deliver -u "local" -- -m "foo" - sample_message | deliver -u "local" -- -m "foo.bar" - sample_message | deliver -u "local" -- -m "INBOX" -done -interimap -check_mailbox_list -check_mailboxes_status "foo" "foo.bar" "baz" "INBOX" - -# spoof UIDNEXT in the database -set_uidnext() { - local imap="$1" mailbox="$2" uidnext="$3" - sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF - UPDATE $imap - SET UIDNEXT = $uidnext - WHERE idx = ( - SELECT idx - FROM mailboxes - WHERE mailbox = x'$mailbox' - ); - EOF -} - -# spoof "foo"'s UIDVALIDITY and UIDNEXT values -uidvalidity="$(doveadm -u "local" -f flow mailbox status uidvalidity "foo" | sed 's/.*=//')" -[ $uidvalidity -eq 4294967295 ] && uidvalidity2=1 || uidvalidity2=$((uidvalidity+1)) -doveadm -u "local" mailbox update --uid-validity "$uidvalidity2" "foo" -set_uidnext "local" "$(printf "%s" "foo" | xxd -ps)" 1 - -# verify that interimap chokes on the UIDVALIDITY change without doing any changes -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF - .dump -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" - -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF - .dump -EOF -doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local2" -doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote2" - -diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump2.sql" "$TMPDIR/dump.sql" -diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.local" "$TMPDIR/foo.local2" -diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.remote" "$TMPDIR/foo.remote2" - - -# spoof UIDNEXT values for INBOX (local+remote) and foo.bar (remote) -set_uidnext "local" "$(printf "%s" "INBOX" | xxd -ps)" 2 -set_uidnext "remote" "$(printf "%s" "INBOX" | xxd -ps)" 2 -set_uidnext "remote" "$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" 0 - -# set some flags and remove some messages for UIDs >2 -doveadm -u "local" flags add "\\Seen" mailbox "INBOX" 6,7 -doveadm -u "remote" flags add "\\Deleted" mailbox "INBOX" 6,8 - -doveadm -u "local" expunge mailbox "INBOX" 4,5 -doveadm -u "remote" expunge mailbox "INBOX" 3,4 -doveadm -u "remote" expunge mailbox "foo~bar" 5 - -# add new messages -sample_message | deliver -u "local" -- -m "foo.bar" -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" -check_mailbox_list -check_mailboxes_status "foo.bar" "INBOX" "baz" # ignore "foo" - - -# count entries in the mapping table -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF - SELECT COUNT(*) - FROM mapping NATURAL JOIN mailboxes - WHERE mailbox != x'$(printf "%s" "foo" | xxd -ps)' - GROUP BY idx - ORDER BY mailbox; -EOF - -# count messages: -# INBOX: 8-2-1 = 5 -# baz: 1 -# foo.bar: 8-1+1+1 = 9 -diff -u --label="a/count" --label="b/count" "$TMPDIR/count" - <<-EOF - 5 - 1 - 9 -EOF - -# vim: set filetype=sh : 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/05-repair/run b/tests/05-repair/run deleted file mode 100644 index 66f9ce9..0000000 --- a/tests/05-repair/run +++ /dev/null @@ -1,107 +0,0 @@ -# create some mailboxes and populate them -doveadm -u "local" mailbox create "foo.bar" -doveadm -u "remote" mailbox create "foo~bar" "baz" -for ((i = 0; i < 8; i++)); do - sample_message | deliver -u "local" -- -m "foo.bar" - sample_message | deliver -u "remote" -- -m "foo~bar" -done -for ((i = 0; i < 64; i++)); do - sample_message | deliver -u "remote" -- -m "baz" -done - -interimap -check_mailbox_list -check_mailboxes_status "foo.bar" "baz" "INBOX" - -# make more changes (flag updates, new massages, deletions) -sample_message | deliver -u "remote" -- -m "INBOX" -doveadm -u "local" expunge mailbox "baz" 1:10 -doveadm -u "remote" expunge mailbox "baz" "$(seq -s"," 1 2 32),$(seq -s"," 40 2 64)" -doveadm -u "local" expunge mailbox "foo.bar" 2,3,5:7,10 -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() { - local k="$1" v m hex="$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" - shift - while [ $# -gt 0 ]; do - [ "$1" = "local" ] && m="foo.bar" || m="$(printf "%s" "foo.bar" | tr "." "~")" - v="$(doveadm -u "$1" -f flow mailbox status "${k,,[A-Z]}" "$m" | sed 's/.*=//')" - sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF - UPDATE \`$1\` SET $k = $v - WHERE idx = (SELECT idx FROM mailboxes WHERE mailbox = x'$hex'); - EOF - shift - done -} - -spoof HIGHESTMODSEQ "local" "remote" -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF - .dump -EOF -doveadm -u "local" mailbox status "all" "foo.bar" >"$TMPDIR/foo-bar.status.local" -doveadm -u "remote" mailbox status "all" "foo~bar" >"$TMPDIR/foo-bar.status.remote" - - -# verify that without --repair interimap does nothing due to the spoofed HIGHESTMODSEQ values -interimap "foo.bar" - -sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF - .dump -EOF -doveadm -u "local" mailbox status all "foo.bar" >"$TMPDIR/foo-bar.status2.local" -doveadm -u "remote" mailbox status all "foo~bar" >"$TMPDIR/foo-bar.status2.remote" -diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" -diff -u --label="a/foo_bar.local" --label="a/foo_bar.local" "$TMPDIR/foo-bar.status.local" "$TMPDIR/foo-bar.status2.local" -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 -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" - -# 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 -# luid 16 <-> ruid 8 has both \Answered and \Seen -xcgrep 5 '^WARNING: Missed flag update in foo\.bar for ' <"$STDERR" -xcgrep 5 '^WARNING: Conflicting flag update in foo\.bar ' <"$STDERR" - -# luid 2 <-> ruid 10 -xcgrep 1 -E '^WARNING: Pair \(lUID,rUID\) = \([0-9]+,[0-9]+\) vanished from foo\.bar\. Repairing\.$' <"$STDERR" - -# 6-1 (luid 2 <-> ruid 10 is gone from both) -xcgrep 5 -E '^local\(foo\.bar\): WARNING: UID [0-9]+ disappeared. Redownloading remote UID [0-9]+\.$' <"$STDERR" - -# 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" - -# 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" - -# 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 - -check_mailbox_list -check_mailboxes_status "baz" "foo.bar" - -interimap -check_mailboxes_status "baz" "foo.bar" "INBOX" - -# vim: set filetype=sh : 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/06-largeint/run b/tests/06-largeint/run deleted file mode 100644 index b08bcfa..0000000 --- a/tests/06-largeint/run +++ /dev/null @@ -1,38 +0,0 @@ -doveadm -u "local" mailbox create "foo" "bar" "baz" -doveadm -u "remote" mailbox create "foo" "bar" "baz" - -doveadm -u "local" mailbox update --uid-validity 1 "INBOX" -doveadm -u "local" mailbox update --uid-validity 2147483647 "foo" # 2^31-1 -doveadm -u "local" mailbox update --uid-validity 2147483648 "bar" # 2^31 -doveadm -u "local" mailbox update --uid-validity 4294967295 "baz" # 2^32-1 - -doveadm -u "remote" mailbox update --uid-validity 4294967295 "INBOX" # 2^32-1 -doveadm -u "remote" mailbox update --uid-validity 2147483648 "foo" # 2^31 -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 - done - interimap - check_mailbox_status "INBOX" "foo" "bar" "baz" -} -run - -# raise UIDNEXT AND HIGHESTMODSEQ close to the max values (resp. 2^32-1 och 2^63-1) -doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "INBOX" # 2^31-1, 2^63-1 -doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "foo" # 2^31-1, 2^63-1 -doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 -doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "baz" # 2^31, 2^63 - -doveadm -u "remote" mailbox update --min-next-uid 4294967168 --min-highest-modseq 18446744073709551488 "INBOX" # 2^32-128, 2^64-128 -doveadm -u "remote" mailbox update --min-next-uid 2147483776 --min-highest-modseq 9223372036854775936 "foo" # 2^31+128, 2^63+128 -doveadm -u "remote" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 - -run - -# vim: set filetype=sh : diff --git a/tests/07-sync-live-multi/local.conf b/tests/07-sync-live-multi/local.conf deleted file mode 100644 index baae39d..0000000 --- a/tests/07-sync-live-multi/local.conf +++ /dev/null @@ -1,30 +0,0 @@ -namespace inbox { - separator = / - location = dbox:~/inbox:LAYOUT=index - inbox = yes - list = yes -} - -namespace foo { - separator = / - prefix = foo/ - location = dbox:~/foo:LAYOUT=index - inbox = no - list = yes -} - -namespace bar { - separator = / - prefix = bar/ - location = dbox:~/bar:LAYOUT=index - inbox = no - list = yes -} - -namespace baz { - separator = / - prefix = baz/ - location = dbox:~/baz:LAYOUT=index - inbox = no - 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-multi/run b/tests/07-sync-live-multi/run deleted file mode 100644 index 15a27fd..0000000 --- a/tests/07-sync-live-multi/run +++ /dev/null @@ -1,138 +0,0 @@ -# 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 - - -# mailbox list (as seen on local) and alphabet -declare -a mailboxes=( "INBOX" ) alphabet=() -str="#+-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" -for ((i=0; i < ${#str}; i++)); do - alphabet[i]="${str:i:1}" -done - -declare -a targets=( "local" "remote" "remote2" "remote3" ) - -timer=$(( $(date +%s) + 30 )) -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 - case "$u" in - local) ns="$(shuf -n1 -e "foo/" "bar/" "baz/")";; - remote) ns="foo/";; - remote2) ns="bar/";; - remote3) ns="baz/";; - *) echo "Uh?" >&2; exit 1;; - 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')" - done - mailboxes+=( "$ns$m" ) - case "$u" in - local) m="$ns$m";; - remote) m="${m//\//^}";; - remote2) m="${m//\//\\}";; - remote3) m="${m//\//\?}";; - *) echo "Uh?" >&2; exit 1;; - esac - doveadm -u "$u" mailbox create -- "$m" - fi - - # EXPUNGE some messages - 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" - done < <(doveadm -u "$u" search all | shuf -n "$n") - - # 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 - n="$(shuf -n1 -i0-9)" - while read guid uid; do - a="$(shuf -n1 -e add remove replace)" - doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" - done < <(doveadm -u "$u" search all | shuf -n "$n") - - # 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[@]}")" - if [ "$u" = "remote" ]; then - case "$m" in - foo/*) u="remote"; 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;; - esac - fi - - # deliver between 1 and 5 messages to the chosen mailbox - n="$(shuf -n1 -i1-5)" - for (( i=0; i < n; i++)); do - sample_message | deliver -u "$u" -- -m "$m" - done - - # sleep a little bit - sleep "$(printf "0.%03d" "$(shuf -n1 -i1-999)")" -done - -# wait a little longer so interimap has time to run loop() again and -# synchronize outstanding changes, then terminate the processes we -# started above -sleep 2 - -abort -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="remote2/mailboxes" \ - <( doveadm -u "local" mailbox list | sed -n "s,^bar/,,p" | sort ) \ - <( doveadm -u "remote2" mailbox list | tr '\\' '/' | sort ) -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 - case "$m" in - foo/*) u="remote"; 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;; - esac - blob="x'$(printf "%s" "$mb" | tr "/" "\\0" | xxd -c256 -ps)'" - check_mailbox_status2 "$blob" "$m" "$u" "$mr" -done - -# vim: set filetype=sh : 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/07-sync-live/run b/tests/07-sync-live/run deleted file mode 100644 index 04d8247..0000000 --- a/tests/07-sync-live/run +++ /dev/null @@ -1,80 +0,0 @@ -# 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 - -# mailbox list and alphabet (exclude &, / and ~, which dovecot treats specially) -declare -a mailboxes=( "INBOX" ) alphabet=() -str="!\"#\$'()+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]_\`abcdefghijklmnopqrstuvwxyz{|}" -for ((i=0; i < ${#str}; i++)); do - alphabet[i]="${str:i:1}" -done - -timer=$(( $(date +%s) + 30 )) -while [ $(date +%s) -le $timer ]; do - # create new mailbox with 10% probability - if [ $(shuf -n1 -i0-9) -eq 0 ]; then - 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')" - done - mailboxes+=( "$m" ) - u="$(shuf -n1 -e "local" "remote")" # choose target at random - [ "$u" = "local" ] || m="${m//./^}" - doveadm -u "$u" mailbox create -- "$m" - fi - - # EXPUNGE some messages - u="$(shuf -n1 -e "local" "remote")" # choose target at random - n="$(shuf -n1 -i0-3)" - while read guid uid; do - doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid" - done < <(doveadm -u "$u" search all | shuf -n "$n") - - # 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 "local" "remote")" # choose target at random - n="$(shuf -n1 -i0-9)" - while read guid uid; do - a="$(shuf -n1 -e add remove replace)" - doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" - done < <(doveadm -u "$u" search all | shuf -n "$n") - - # 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[@]}")" - [ "$u" = "local" ] || m="${m//./^}" - - # deliver between 1 and 5 messages to the chosen mailbox - n="$(shuf -n1 -i1-5)" - for (( i=0; i < n; i++)); do - sample_message | deliver -u "$u" -- -m "$m" - done - - # sleep a little bit - sleep "$(printf "0.%03d" "$(shuf -n1 -i1-999)")" -done - -# wait a little longer so interimap has time to run loop() again and -# synchronize outstanding changes, then terminate the process we started -# above -sleep 2 - -abort -trap - EXIT INT TERM - -check_mailbox_list -check_mailboxes_status "${mailboxes[@]}" - -# 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/db-upgrade-0-1/after.sql b/tests/db-upgrade-0-1/after.sql new file mode 100644 index 0000000..18b0ad7 --- /dev/null +++ b/tests/db-upgrade-0-1/after.sql @@ -0,0 +1,14 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); +CREATE TABLE IF NOT EXISTS "mailboxes" (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox BLOB COLLATE BINARY NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); +INSERT INTO mailboxes VALUES(1,X'61006231006332',0); +INSERT INTO mailboxes VALUES(2,X'61006231006331',0); +INSERT INTO mailboxes VALUES(3,X'494e424f58',0); +INSERT INTO mailboxes VALUES(4,X'6132',0); +INSERT INTO mailboxes VALUES(5,X'610062320063',0); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('mailboxes',5); +COMMIT; diff --git a/tests/db-upgrade-0-1/before.sql b/tests/db-upgrade-0-1/before.sql new file mode 100644 index 0000000..333a1dc --- /dev/null +++ b/tests/db-upgrade-0-1/before.sql @@ -0,0 +1,14 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE mailboxes (idx INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, mailbox TEXT NOT NULL CHECK (mailbox != '') UNIQUE, subscribed BOOLEAN NOT NULL); +INSERT INTO mailboxes VALUES(1,'a.b1.c2',0); +INSERT INTO mailboxes VALUES(2,'a.b1.c1',0); +INSERT INTO mailboxes VALUES(3,'INBOX',0); +INSERT INTO mailboxes VALUES(4,'a2',0); +INSERT INTO mailboxes VALUES(5,'a.b2.c',0); +CREATE TABLE local (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE remote (idx INTEGER NOT NULL PRIMARY KEY REFERENCES mailboxes(idx), UIDVALIDITY UNSIGNED INT NOT NULL CHECK (UIDVALIDITY > 0), UIDNEXT UNSIGNED INT NOT NULL, HIGHESTMODSEQ UNSIGNED BIGINT NOT NULL); +CREATE TABLE mapping (idx INTEGER NOT NULL REFERENCES mailboxes(idx), lUID UNSIGNED INT NOT NULL CHECK (lUID > 0), rUID UNSIGNED INT NOT NULL CHECK (rUID > 0), PRIMARY KEY (idx,lUID), UNIQUE (idx,rUID)); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('mailboxes',5); +COMMIT; 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/db-upgrade-0-1/t b/tests/db-upgrade-0-1/t new file mode 100644 index 0000000..088008e --- /dev/null +++ b/tests/db-upgrade-0-1/t @@ -0,0 +1,34 @@ +# 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" || error "Couldn't import DB" + +# migrate +interimap || error "Couldn't upgrade DB" + +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 +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + DELETE FROM local; + DELETE FROM remote; + .dump +EOF + +# re-import and dump the expected dump to work around SQLite format +# differences across versions +sqlite3 "$XDG_DATA_HOME/interimap/remote2.db" <"$TESTDIR/after.sql" +sqlite3 "$XDG_DATA_HOME/interimap/remote2.db" >"$TMPDIR/dump-expected.sql" <<-EOF + .dump +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" \ + || 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/delete/t b/tests/delete/t new file mode 100644 index 0000000..c38d4d3 --- /dev/null +++ b/tests/delete/t @@ -0,0 +1,95 @@ +doveadm -u "local" mailbox create "foo.bar" "foo.bar.baz" + +for m in "foo.bar" "foo.bar.baz" "INBOX"; do + sample_message | deliver -u "local" -- -m "$m" +done + +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 + + +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" +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" || 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 +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" || error "SQL dumps differ" + +! doveadm -u "local" mailbox status uidvalidity "foo.bar" # gone + doveadm -u "remote" mailbox status uidvalidity "foo^bar" + +# 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.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_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/largeint/t b/tests/largeint/t new file mode 100644 index 0000000..b0877d5 --- /dev/null +++ b/tests/largeint/t @@ -0,0 +1,39 @@ +doveadm -u "local" mailbox create "foo" "bar" "baz" +doveadm -u "remote" mailbox create "foo" "bar" "baz" + +doveadm -u "local" mailbox update --uid-validity 1 "INBOX" +doveadm -u "local" mailbox update --uid-validity 2147483647 "foo" # 2^31-1 +doveadm -u "local" mailbox update --uid-validity 2147483648 "bar" # 2^31 +doveadm -u "local" mailbox update --uid-validity 4294967295 "baz" # 2^32-1 + +doveadm -u "remote" mailbox update --uid-validity 4294967295 "INBOX" # 2^32-1 +doveadm -u "remote" mailbox update --uid-validity 2147483648 "foo" # 2^31 +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 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 || error + check_mailbox_list + check_mailbox_status "INBOX" "foo" "bar" "baz" +} +run + +# raise UIDNEXT AND HIGHESTMODSEQ close to the max values (resp. 2^32-1 och 2^63-1) +doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "INBOX" # 2^31-1, 2^63-1 +doveadm -u "local" mailbox update --min-next-uid 2147483647 --min-highest-modseq 9223372036854775807 "foo" # 2^31-1, 2^63-1 +doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 +doveadm -u "local" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "baz" # 2^31, 2^63 + +doveadm -u "remote" mailbox update --min-next-uid 4294967168 --min-highest-modseq 18446744073709551488 "INBOX" # 2^32-128, 2^64-128 +doveadm -u "remote" mailbox update --min-next-uid 2147483776 --min-highest-modseq 9223372036854775936 "foo" # 2^31+128, 2^63+128 +doveadm -u "remote" mailbox update --min-next-uid 2147483648 --min-highest-modseq 9223372036854775808 "bar" # 2^31, 2^63 + +run + +# vim: set filetype=sh : diff --git a/tests/list b/tests/list new file mode 100644 index 0000000..21aa3f4 --- /dev/null +++ b/tests/list @@ -0,0 +1,37 @@ +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 + +. Live synchronization (60s) + sync-live local/remote simulation + sync-live-crippled local/remote simulation (crippled remote) + sync-live-multi local/remote1+remote2+remote3 simulation (3 local namespaces) 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/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/rename-exists-db/t b/tests/rename-exists-db/t new file mode 100644 index 0000000..cb6cfcd --- /dev/null +++ b/tests/rename-exists-db/t @@ -0,0 +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_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" || 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/rename-exists-local/t b/tests/rename-exists-local/t new file mode 100644 index 0000000..190f49a --- /dev/null +++ b/tests/rename-exists-local/t @@ -0,0 +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_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" || 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/rename-exists-remote/t b/tests/rename-exists-remote/t new file mode 100644 index 0000000..be16a12 --- /dev/null +++ b/tests/rename-exists-remote/t @@ -0,0 +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_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" || 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/rename-inferiors/t b/tests/rename-inferiors/t new file mode 100644 index 0000000..9267e6f --- /dev/null +++ b/tests/rename-inferiors/t @@ -0,0 +1,100 @@ +doveadm -u "local" mailbox create "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" +doveadm -u "remote" mailbox create "root^sibbling" "root^sibbling^grandchild" "root2" + +for m in "root.from" "root.from.child" "root.from.child2" "root.from.child.grandchild" "INBOX"; do + sample_message | deliver -u "local" -- -m "$m" +done +for m in "root^sibbling" "root^sibbling^grandchild" "root2" "INBOX"; do + sample_message | deliver -u "remote" -- -m "$m" +done + +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 + .mode csv + SELECT idx, hex(mailbox) + FROM mailboxes + ORDER BY idx +EOF + +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 + + +# rename 'root.from' to 'from.root', including inferiors +step_start "existing source with inferiors" +interimap --rename "root.from" "from.root" +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 -u -ps)" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes2.csv" <<-EOF + .mode csv + SELECT idx, + CASE + WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' + THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) + 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 + + +# rename \NonExistent root and check that its children move +step_start "\\NonExistent source with inferiors" +interimap --rename "root" "newroot" +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 -u -ps)" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/mailboxes3.csv" <<-EOF + .mode csv + SELECT idx, + CASE + WHEN mailbox = x'$after' OR hex(mailbox) LIKE '${after}00%' + THEN '$before' || SUBSTR(hex(mailbox), $((${#after}+1))) + WHEN hex(mailbox) LIKE '${after2}00%' + THEN '$before2' || SUBSTR(hex(mailbox), $((${#after2}+1))) + ELSE hex(mailbox) + END + FROM mailboxes + ORDER BY idx +EOF +diff -u --label="a/mailboxes.csv" --label="b/mailboxes.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/repair/t b/tests/repair/t new file mode 100644 index 0000000..6b205ea --- /dev/null +++ b/tests/repair/t @@ -0,0 +1,107 @@ +# create some mailboxes and populate them +doveadm -u "local" mailbox create "foo.bar" +doveadm -u "remote" mailbox create "foo~bar" "baz" +for ((i = 0; i < 8; i++)); do + sample_message | deliver -u "local" -- -m "foo.bar" + sample_message | deliver -u "remote" -- -m "foo~bar" +done +for ((i = 0; i < 64; i++)); do + sample_message | deliver -u "remote" -- -m "baz" +done + +interimap_init +check_mailbox_list +check_mailboxes_status "foo.bar" "baz" "INBOX" + +# make more changes (flag updates, new massages, deletions) +sample_message | deliver -u "remote" -- -m "INBOX" +doveadm -u "local" expunge mailbox "baz" 1:10 +doveadm -u "remote" expunge mailbox "baz" "$(seq -s"," 1 2 32),$(seq -s"," 40 2 64)" +doveadm -u "local" expunge mailbox "foo.bar" 2,3,5:7,10 +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() { + local k="$1" v m hex="$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" + shift + while [ $# -gt 0 ]; do + [ "$1" = "local" ] && m="foo.bar" || m="$(printf "%s" "foo.bar" | tr "." "~")" + v="$(doveadm -u "$1" -f flow mailbox status "${k,,[A-Z]}" "$m" | sed 's/.*=//')" + sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF + UPDATE \`$1\` SET $k = $v + WHERE idx = (SELECT idx FROM mailboxes WHERE mailbox = x'$hex'); + EOF + shift + done +} + +spoof HIGHESTMODSEQ "local" "remote" +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status "all" "foo.bar" >"$TMPDIR/foo-bar.status.local" +doveadm -u "remote" mailbox status "all" "foo~bar" >"$TMPDIR/foo-bar.status.remote" + + +# verify that without --repair interimap does nothing due to the spoofed HIGHESTMODSEQ values +interimap "foo.bar" || error + +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump2.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status all "foo.bar" >"$TMPDIR/foo-bar.status2.local" +doveadm -u "remote" mailbox status all "foo~bar" >"$TMPDIR/foo-bar.status2.remote" +diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump.sql" "$TMPDIR/dump2.sql" +diff -u --label="a/foo_bar.local" --label="a/foo_bar.local" "$TMPDIR/foo-bar.status.local" "$TMPDIR/foo-bar.status2.local" +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* +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" || 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 +# luid 16 <-> ruid 8 has both \Answered and \Seen +xcgrep 5 '^WARNING: Missed flag update in foo\.bar for ' <"$STDERR" +xcgrep 5 '^WARNING: Conflicting flag update in foo\.bar ' <"$STDERR" + +# luid 2 <-> ruid 10 +xcgrep 1 -E '^WARNING: Pair \(lUID,rUID\) = \([0-9]+,[0-9]+\) vanished from foo\.bar\. Repairing\.$' <"$STDERR" + +# 6-1 (luid 2 <-> ruid 10 is gone from both) +xcgrep 5 -E '^local\(foo\.bar\): WARNING: UID [0-9]+ disappeared. Redownloading remote UID [0-9]+\.$' <"$STDERR" + +# 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" + +grep -E '^local\(baz\): Removed 24 UID\(s\) ' <"$STDERR" || error +grep -E '^remote\(baz\): Removed 5 UID\(s\) ' <"$STDERR" || error + +# 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" + +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 || 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/resume/t b/tests/resume/t new file mode 100644 index 0000000..cb0208c --- /dev/null +++ b/tests/resume/t @@ -0,0 +1,98 @@ +# create and populate a bunch of mailboxes +doveadm -u "local" mailbox create "foo" "foo.bar" "baz" +for ((i = 0; i < 8; i++)); do + sample_message | deliver -u "local" -- -m "foo" + sample_message | deliver -u "local" -- -m "foo.bar" + sample_message | deliver -u "local" -- -m "INBOX" +done +interimap_init +check_mailbox_list +check_mailboxes_status "foo" "foo.bar" "baz" "INBOX" + +# spoof UIDNEXT in the database +set_uidnext() { + local imap="$1" mailbox="$2" uidnext="$3" + sqlite3 "$XDG_DATA_HOME/interimap/remote.db" <<-EOF + UPDATE $imap + SET UIDNEXT = $uidnext + WHERE idx = ( + SELECT idx + FROM mailboxes + WHERE mailbox = x'$mailbox' + ); + EOF +} + +# spoof "foo"'s UIDVALIDITY and UIDNEXT values +uidvalidity="$(doveadm -u "local" -f flow mailbox status uidvalidity "foo" | sed 's/.*=//')" +[ $uidvalidity -eq 4294967295 ] && uidvalidity2=1 || uidvalidity2=$((uidvalidity+1)) +doveadm -u "local" mailbox update --uid-validity "$uidvalidity2" "foo" +set_uidnext "local" "$(printf "%s" "foo" | xxd -ps)" 1 + +# verify that interimap chokes on the UIDVALIDITY change without doing any changes +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/dump.sql" <<-EOF + .dump +EOF +doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local" +doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote" + +! 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 +EOF +doveadm -u "local" mailbox status "all" "foo" >"$TMPDIR/foo.local2" +doveadm -u "remote" mailbox status "all" "foo" >"$TMPDIR/foo.remote2" + +diff -u --label="a/dump.sql" --label="b/dump.sql" "$TMPDIR/dump2.sql" "$TMPDIR/dump.sql" +diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.local" "$TMPDIR/foo.local2" +diff -u --label="a/foo.local" --label="b/foo.remote" "$TMPDIR/foo.remote" "$TMPDIR/foo.remote2" + + +# spoof UIDNEXT values for INBOX (local+remote) and foo.bar (remote) +set_uidnext "local" "$(printf "%s" "INBOX" | xxd -ps)" 2 +set_uidnext "remote" "$(printf "%s" "INBOX" | xxd -ps)" 2 +set_uidnext "remote" "$(printf "%s\\0%s" "foo" "bar" | xxd -ps)" 0 + +# set some flags and remove some messages for UIDs >2 +doveadm -u "local" flags add "\\Seen" mailbox "INBOX" 6,7 +doveadm -u "remote" flags add "\\Deleted" mailbox "INBOX" 6,8 + +doveadm -u "local" expunge mailbox "INBOX" 4,5 +doveadm -u "remote" expunge mailbox "INBOX" 3,4 +doveadm -u "remote" expunge mailbox "foo~bar" 5 + +# add new messages +sample_message | deliver -u "local" -- -m "foo.bar" +sample_message | deliver -u "remote" -- -m "foo~bar" +sample_message | deliver -u "local" -- -m "baz" + +interimap "foo.bar" "InBoX" "baz" # ignore "foo" +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" + + +# count entries in the mapping table +sqlite3 "$XDG_DATA_HOME/interimap/remote.db" >"$TMPDIR/count" <<-EOF + SELECT COUNT(*) + FROM mapping NATURAL JOIN mailboxes + WHERE mailbox != x'$(printf "%s" "foo" | xxd -ps)' + GROUP BY idx + ORDER BY mailbox; +EOF + +# count messages: +# INBOX: 8-2-1 = 5 +# baz: 1 +# foo.bar: 8-1+1+1 = 9 +diff -u --label="a/count" --label="b/count" "$TMPDIR/count" - <<-EOF + 5 + 1 + 9 +EOF + +# vim: set filetype=sh : diff --git a/tests/run b/tests/run index ee11757..cb52518 100755 --- a/tests/run +++ b/tests/run @@ -22,27 +22,27 @@ 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" +TESTDIR="$(dirname -- "$0")/$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")" +ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$1.XXXXXXXXXX")" trap 'rm -rf -- "$ROOTDIR"' EXIT INT TERM -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,24 +60,35 @@ environ_set() { # Prepare the test harness prepare() { declare -a ENVIRON=() - local src cfg target u home + local src cfg target u home n capability + if [ -f "$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 + log_path = $HOME_local/mail.log + mail_home = $home + mail_location = dbox:~/inbox:LAYOUT=index mailbox_list_index = yes ssl = no + namespace inbox { + inbox = yes + } EOF - cat >>"$home/.config/dovecot/config" <"$src" + if [ -f "$TESTDIR/$u.conf" ]; then + cat >>"$home/.config/dovecot/config" <"$TESTDIR/$u.conf" + fi + environ_set "$u" + mkdir -pm0755 -- "$home/.local/bin" cat >"$home/.local/bin/doveadm" <<-EOF #!/bin/sh exec env -i ${ENVIRON[@]@Q} \\ @@ -89,36 +100,62 @@ prepare() { # 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" + 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" ]; then + cat <"$TESTDIR/interimap$n.conf" >>"$HOME_local/.config/interimap/config$n" + 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 + EOF + if [ -f "$TESTDIR/interimap$n.local" ]; then + cat <"$TESTDIR/interimap$n.local" >>"$HOME_local/.config/interimap/config$n" + fi + + # `doveadm exec imap` ignores 'imap_capability' from doveconf/config + capability="$(doveconf -c "$home/.config/dovecot/config" -h imap_capability)" + cat >>"$HOME_local/.config/interimap/config$n" <<-EOF [remote] type = tunnel - command = exec ${home@Q}/.local/bin/doveadm exec imap + command = exec ${home@Q}/.local/bin/doveadm exec imap ${capability:+-oimap_capability=${capability@Q}} null-stderr = NO EOF + if [ -f "$TESTDIR/interimap$n.remote" ]; then + cat <"$TESTDIR/interimap$n.remote" >>"$HOME_local/.config/interimap/config$n" + fi done } prepare # Wrappers for interimap(1) and doveadm(1) interimap() { - declare -a ENVIRON=() + declare -a ENVIRON=() r=0 environ_set "local" - env -i "${ENVIRON[@]}" perl -I./lib -T ./interimap "$@" + env -i "${ENVIRON[@]}" perl -I./lib -T ./interimap "$@" 2>"$STDERR" || r=$? + cat "$STDERR" >&2 + return $r +} +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 @@ -167,9 +204,7 @@ 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" @@ -177,16 +212,18 @@ dump_test_result() { printf "%s\\n\\n" "$above" done - printf "(local) interimap configuration:\\n%s\\n" "$below" - cat <"$HOME_local/.config/interimap/config" - printf "%s\\n\\n" "$above" + 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 "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" } @@ -225,7 +262,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 @@ -304,38 +341,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 TMPDIR TESTDIR STDERR "${ENVIRON[@]}" export -f environ_set doveadm interimap sqlite3 sample_message deliver +export -f interimap_init 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 +# +# 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 . +#---------------------------------------------------------------------- + +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/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..76c08e0 --- /dev/null +++ b/tests/sync-live-crippled/remote.conf @@ -0,0 +1,6 @@ +namespace inbox { + separator = ^ +} + +# strict minimum of IMAP capabilities required for interimap to work +imap_capability = IMAP4rev1 ENABLE UIDPLUS LIST-EXTENDED QRESYNC LIST-STATUS 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/sync-live-multi/local.conf b/tests/sync-live-multi/local.conf new file mode 100644 index 0000000..baae39d --- /dev/null +++ b/tests/sync-live-multi/local.conf @@ -0,0 +1,30 @@ +namespace inbox { + separator = / + location = dbox:~/inbox:LAYOUT=index + inbox = yes + list = yes +} + +namespace foo { + separator = / + prefix = foo/ + location = dbox:~/foo:LAYOUT=index + inbox = no + list = yes +} + +namespace bar { + separator = / + prefix = bar/ + location = dbox:~/bar:LAYOUT=index + inbox = no + list = yes +} + +namespace baz { + separator = / + prefix = baz/ + location = dbox:~/baz:LAYOUT=index + inbox = no + list = yes +} 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/sync-live-multi/t b/tests/sync-live-multi/t new file mode 100644 index 0000000..9b129ec --- /dev/null +++ b/tests/sync-live-multi/t @@ -0,0 +1,127 @@ +TIMEOUT=60 + +# mailbox list (as seen on local) and alphabet +declare -a MAILBOXES=( "INBOX" ) ALPHABET=() +str="#+-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" +for ((i=0; i < ${#str}; i++)); do + ALPHABET[i]="${str:i:1}" +done + +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) + 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 + case "$u" in + local) ns="$(shuf -n1 -e "foo/" "bar/" "baz/")";; + remote1) ns="foo/";; + remote2) ns="bar/";; + remote3) ns="baz/";; + *) 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')" + done + MAILBOXES+=( "$ns$m" ) + case "$u" in + local) m="$ns$m";; + remote1) m="${m//\//^}";; + remote2) m="${m//\//\\}";; + remote3) m="${m//\//\?}";; + *) error "Uh?";; + esac + doveadm -u "$u" mailbox create -- "$m" + fi + + # EXPUNGE some messages + 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" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # 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 + n="$(shuf -n1 -i0-9)" + while read guid uid; do + a="$(shuf -n1 -e add remove replace)" + doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # 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[@]}")" + if [ "$u" = "remote" ]; then + case "$m" in + 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 "remote1" "remote2" "remote3")";; + *) error "Uh? $m";; + esac + fi + + # deliver between 1 and 5 messages to the chosen mailbox + n="$(shuf -n1 -i1-5)" + for (( i=0; i < n; i++)); do + sample_message | deliver -u "$u" -- -m "$m" + done + + # 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 +# synchronize outstanding changes, then terminate the processes we +# started above +sleep 2 + +ptree_abort ${PID[@]} +trap - EXIT INT TERM + +# check that the mailbox lists match +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 ) +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 + case "$m" in + 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;; + *) error "Uh? $m";; + esac + blob="x'$(printf "%s" "$mb" | tr "/" "\\0" | xxd -c256 -ps)'" + check_mailbox_status2 "$blob" "$m" "$u" "$mr" +done + +# vim: set filetype=sh : 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/sync-live/t b/tests/sync-live/t new file mode 100644 index 0000000..19d1e08 --- /dev/null +++ b/tests/sync-live/t @@ -0,0 +1,76 @@ +TIMEOUT=60 + +# mailbox list and alphabet (exclude &, / and ~, which dovecot treats specially) +declare -a MAILBOXES=( "INBOX" ) ALPHABET=() +str="!\"#\$'()+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]_\`abcdefghijklmnopqrstuvwxyz{|}" +for ((i=0; i < ${#str}; i++)); do + ALPHABET[i]="${str:i:1}" +done + +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 + 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')" + done + MAILBOXES+=( "$m" ) + u="$(shuf -n1 -e "local" "remote")" # choose target at random + [ "$u" = "local" ] || m="${m//./^}" + doveadm -u "$u" mailbox create -- "$m" + fi + + # EXPUNGE some messages + u="$(shuf -n1 -e "local" "remote")" # choose target at random + n="$(shuf -n1 -i0-3)" + while read guid uid; do + doveadm -u "$u" expunge mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # 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 "local" "remote")" # choose target at random + n="$(shuf -n1 -i0-9)" + while read guid uid; do + a="$(shuf -n1 -e add remove replace)" + doveadm -u "$u" flags "$a" "\\Seen" mailbox-guid "$guid" uid "$uid" + done < <(doveadm -u "$u" search all | shuf -n "$n") + + # 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[@]}")" + [ "$u" = "local" ] || m="${m//./^}" + + # deliver between 1 and 5 messages to the chosen mailbox + n="$(shuf -n1 -i1-5)" + for (( i=0; i < n; i++)); do + sample_message | deliver -u "$u" -- -m "$m" + done + + # 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 +# synchronize outstanding changes, then terminate the process we started +# above +sleep 2 + +ptree_abort $PID +trap - EXIT INT TERM + +check_mailbox_list +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 : -- cgit v1.2.3 From 78522acced782587b3768f3fb57f2f25cb905754 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 10 Nov 2019 03:18:26 +0100 Subject: Test suite: add new tests for authentication. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can't be done with `doveadm exec imap`, so the IMAPd needs to bind to TCP port 10143 on the loopback interface. Also, no longer pass ‘imap_capability’ Dovecot setting explicitely to `doveadm exec imap`; changed tests/sync-live-crippled to use type=imap instead of type=tunnel. --- tests/auth-login/interimap.remote | 5 + tests/auth-login/remote.conf | 2 + tests/auth-login/t | 12 +++ tests/auth-logindisabled/interimap.remote | 1 + tests/auth-logindisabled/remote.conf | 4 + tests/auth-logindisabled/t | 16 +++ tests/auth-noplaintext/interimap.remote | 3 + tests/auth-noplaintext/remote.conf | 1 + tests/auth-noplaintext/t | 15 +++ tests/auth-sasl-plain-no-ir/interimap.remote | 1 + tests/auth-sasl-plain-no-ir/remote.conf | 2 + tests/auth-sasl-plain-no-ir/t | 26 +++++ tests/auth-sasl-plain/interimap.remote | 4 + tests/auth-sasl-plain/remote.conf | 1 + tests/auth-sasl-plain/t | 12 +++ tests/list | 7 ++ tests/run | 109 ++++++++++++++++----- tests/snippets/dovecot/imapd.conf | 12 +++ .../dovecot/interimap-required-capabilities.conf | 3 + tests/sync-live-crippled/interimap.remote | 1 + tests/sync-live-crippled/remote.conf | 4 +- 21 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 tests/auth-login/interimap.remote create mode 100644 tests/auth-login/remote.conf create mode 100644 tests/auth-login/t create mode 120000 tests/auth-logindisabled/interimap.remote create mode 100644 tests/auth-logindisabled/remote.conf create mode 100644 tests/auth-logindisabled/t create mode 100644 tests/auth-noplaintext/interimap.remote create mode 120000 tests/auth-noplaintext/remote.conf create mode 100644 tests/auth-noplaintext/t create mode 120000 tests/auth-sasl-plain-no-ir/interimap.remote create mode 100644 tests/auth-sasl-plain-no-ir/remote.conf create mode 100644 tests/auth-sasl-plain-no-ir/t create mode 100644 tests/auth-sasl-plain/interimap.remote create mode 100644 tests/auth-sasl-plain/remote.conf create mode 100644 tests/auth-sasl-plain/t create mode 100644 tests/snippets/dovecot/imapd.conf create mode 100644 tests/snippets/dovecot/interimap-required-capabilities.conf create mode 120000 tests/sync-live-crippled/interimap.remote 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/list b/tests/list index 21aa3f4..86034ef 100644 --- a/tests/list +++ b/tests/list @@ -31,6 +31,13 @@ largeint Large UIDVALIDITY/UIDNEXT/HIGHESTMODSEQ values 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 + . Live synchronization (60s) sync-live local/remote simulation sync-live-crippled local/remote simulation (crippled remote) diff --git a/tests/run b/tests/run index cb52518..bff9c18 100755 --- a/tests/run +++ b/tests/run @@ -27,7 +27,8 @@ if [ $# -eq 0 ] || [ $# -gt 2 ]; then exit 1 fi -TESTDIR="$(dirname -- "$0")/$1" +BASEDIR="$(dirname -- "$0")" +TESTDIR="$BASEDIR/$1" TESTNAME="${2-$1}" if [ ! -d "$TESTDIR" ]; then printf "ERROR: Not a directory: %s\\n" "$TESTDIR" >&2 @@ -35,7 +36,18 @@ if [ ! -d "$TESTDIR" ]; then fi ROOTDIR="$(mktemp --tmpdir="${TMPDIR:-/dev/shm}" --directory "$1.XXXXXXXXXX")" -trap 'rm -rf -- "$ROOTDIR"' EXIT INT TERM +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" @@ -60,7 +72,7 @@ environ_set() { # Prepare the test harness prepare() { declare -a ENVIRON=() - local src cfg target u home n capability + local src cfg target u home n proto if [ -f "$TESTDIR/remotes" ]; then for cfg in $(seq 1 "$(< "$TESTDIR/remotes")"); do REMOTES+=( "remote$cfg" ) @@ -72,27 +84,64 @@ prepare() { for u in "local" "${REMOTES[@]}"; do home="$ROOTDIR/$u/home" export "HOME_$u"="$home" - mkdir -pm0700 -- "$home/.config/dovecot" - cat >"$home/.config/dovecot/config" <<-EOF + 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 if [ -f "$TESTDIR/$u.conf" ]; then - cat >>"$home/.config/dovecot/config" <"$TESTDIR/$u.conf" + 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 "$home/.dovecot/users" fi - environ_set "$u" 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 @@ -123,15 +172,19 @@ prepare() { cat <"$TESTDIR/interimap$n.local" >>"$HOME_local/.config/interimap/config$n" fi - # `doveadm exec imap` ignores 'imap_capability' from doveconf/config - capability="$(doveconf -c "$home/.config/dovecot/config" -h imap_capability)" - cat >>"$HOME_local/.config/interimap/config$n" <<-EOF - - [remote] - type = tunnel - command = exec ${home@Q}/.local/bin/doveadm exec imap ${capability:+-oimap_capability=${capability@Q}} - null-stderr = NO - EOF + printf "\\n[remote]\\n" >>"$HOME_local/.config/interimap/config$n" + 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/.config/interimap/config$n" if [ -f "$TESTDIR/interimap$n.remote" ]; then cat <"$TESTDIR/interimap$n.remote" >>"$HOME_local/.config/interimap/config$n" fi @@ -208,7 +261,7 @@ dump_test_result() { 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 @@ -218,6 +271,10 @@ dump_test_result() { printf "%s\\n\\n" "$above" done + printf "mail.log:\\n%s\\n" "$below" + cat -- "$HOME_local/mail.log" 2>/dev/null || true + printf "%s\\n\\n" "$above" + printf "standard output:\\n%s\\n" "$below" cat <"$_STDOUT" printf "%s\\n\\n" "$above" @@ -230,10 +287,10 @@ dump_test_result() { # 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")" @@ -305,10 +362,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 diff --git a/tests/snippets/dovecot/imapd.conf b/tests/snippets/dovecot/imapd.conf new file mode 100644 index 0000000..c9926ce --- /dev/null +++ b/tests/snippets/dovecot/imapd.conf @@ -0,0 +1,12 @@ +protocols = $protocols imap + +mail_plugins = $mail_plugins zlib +protocol imap { + mail_plugins = $mail_plugins imap_zlib +} + +service imap-login { + inet_listener imap { + port = 10143 + } +} 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/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/remote.conf b/tests/sync-live-crippled/remote.conf index 76c08e0..ee22c5f 100644 --- a/tests/sync-live-crippled/remote.conf +++ b/tests/sync-live-crippled/remote.conf @@ -2,5 +2,5 @@ namespace inbox { separator = ^ } -# strict minimum of IMAP capabilities required for interimap to work -imap_capability = IMAP4rev1 ENABLE UIDPLUS LIST-EXTENDED QRESYNC LIST-STATUS +!include conf.d/imapd.conf +!include conf.d/interimap-required-capabilities.conf -- cgit v1.2.3 From b7514eeac609a7e99c66031f853f695bb82c990a Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 10 Nov 2019 18:11:05 +0100 Subject: Test suite: don't treat broken symlinks as missing. `test -f` deferences paths so fails on broken symlinks, yielding an incorrect test environment and perhaps even a false negative. --- tests/run | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/run b/tests/run index bff9c18..2903938 100755 --- a/tests/run +++ b/tests/run @@ -73,7 +73,7 @@ environ_set() { prepare() { declare -a ENVIRON=() local src cfg target u home n proto - if [ -f "$TESTDIR/remotes" ]; then + if [ -f "$TESTDIR/remotes" ] || [ -L "$TESTDIR/remotes" ]; then for cfg in $(seq 1 "$(< "$TESTDIR/remotes")"); do REMOTES+=( "remote$cfg" ) done @@ -98,7 +98,7 @@ prepare() { inbox = yes } EOF - if [ -f "$TESTDIR/$u.conf" ]; then + 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" @@ -157,7 +157,7 @@ prepare() { database = $u.db #logfile = $HOME_local/interimap$n.log EOF - if [ -f "$TESTDIR/interimap$n.conf" ]; then + if [ -f "$TESTDIR/interimap$n.conf" ] || [ -L "$TESTDIR/interimap$n.conf" ]; then cat <"$TESTDIR/interimap$n.conf" >>"$HOME_local/.config/interimap/config$n" fi @@ -168,7 +168,7 @@ prepare() { command = exec ${HOME_local@Q}/.local/bin/doveadm exec imap null-stderr = NO EOF - if [ -f "$TESTDIR/interimap$n.local" ]; then + if [ -f "$TESTDIR/interimap$n.local" ] || [ -L "$TESTDIR/interimap$n.local" ]; then cat <"$TESTDIR/interimap$n.local" >>"$HOME_local/.config/interimap/config$n" fi @@ -185,7 +185,7 @@ prepare() { null-stderr = NO EOF fi >>"$HOME_local/.config/interimap/config$n" - if [ -f "$TESTDIR/interimap$n.remote" ]; then + if [ -f "$TESTDIR/interimap$n.remote" ] || [ -L "$TESTDIR/interimap$n.remote" ]; then cat <"$TESTDIR/interimap$n.remote" >>"$HOME_local/.config/interimap/config$n" fi done -- cgit v1.2.3 From a7c364bf90a4593cfbc7911b1b7536dc66b1c879 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 10 Nov 2019 05:39:41 +0100 Subject: Test suite: add new tests for SSL/TLS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SSL connections are accepted on TCP port 10993. Also, fix STARTTLS directive, broken since fba1c36… --- Changelog | 1 + lib/Net/IMAP/InterIMAP.pm | 11 ++-- tests/list | 9 +++ tests/snippets/dovecot/dhparams.pem | 8 +++ tests/snippets/dovecot/dovecot.key | 5 ++ tests/snippets/dovecot/dovecot.pem | 11 ++++ tests/snippets/dovecot/imapd.conf | 4 ++ tests/snippets/dovecot/ssl.conf | 4 ++ tests/starttls-logindisabled/interimap.remote | 4 ++ tests/starttls-logindisabled/remote.conf | 5 ++ tests/starttls-logindisabled/t | 19 +++++++ tests/starttls/interimap.remote | 4 ++ tests/starttls/remote.conf | 2 + tests/starttls/t | 27 +++++++++ tests/sync-live-tls/interimap.remote | 1 + tests/sync-live-tls/local.conf | 1 + tests/sync-live-tls/remote.conf | 6 ++ tests/sync-live-tls/t | 1 + tests/tls-pin-fingerprint/interimap.remote | 1 + tests/tls-pin-fingerprint/remote.conf | 1 + tests/tls-pin-fingerprint/t | 33 +++++++++++ tests/tls-protocols/interimap.remote | 1 + tests/tls-protocols/remote.conf | 1 + tests/tls-protocols/t | 39 +++++++++++++ tests/tls-verify-peer/interimap.remote | 2 + tests/tls-verify-peer/remote.conf | 1 + tests/tls-verify-peer/t | 80 +++++++++++++++++++++++++++ tests/tls/interimap.remote | 3 + tests/tls/remote.conf | 2 + tests/tls/t | 14 +++++ 30 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 tests/snippets/dovecot/dhparams.pem create mode 100644 tests/snippets/dovecot/dovecot.key create mode 100644 tests/snippets/dovecot/dovecot.pem create mode 100644 tests/snippets/dovecot/ssl.conf create mode 100644 tests/starttls-logindisabled/interimap.remote create mode 100644 tests/starttls-logindisabled/remote.conf create mode 100644 tests/starttls-logindisabled/t create mode 100644 tests/starttls/interimap.remote create mode 100644 tests/starttls/remote.conf create mode 100644 tests/starttls/t create mode 120000 tests/sync-live-tls/interimap.remote create mode 120000 tests/sync-live-tls/local.conf create mode 100644 tests/sync-live-tls/remote.conf create mode 120000 tests/sync-live-tls/t create mode 120000 tests/tls-pin-fingerprint/interimap.remote create mode 120000 tests/tls-pin-fingerprint/remote.conf create mode 100644 tests/tls-pin-fingerprint/t create mode 120000 tests/tls-protocols/interimap.remote create mode 120000 tests/tls-protocols/remote.conf create mode 100644 tests/tls-protocols/t create mode 100644 tests/tls-verify-peer/interimap.remote create mode 120000 tests/tls-verify-peer/remote.conf create mode 100644 tests/tls-verify-peer/t create mode 100644 tests/tls/interimap.remote create mode 100644 tests/tls/remote.conf create mode 100644 tests/tls/t diff --git a/Changelog b/Changelog index 5b43c6f..c08de80 100644 --- a/Changelog +++ b/Changelog @@ -85,6 +85,7 @@ interimap (0.5) upstream; - 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. -- Guilhem Moulin Fri, 10 May 2019 00:58:14 +0200 diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 77c1b14..b4d8bec 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -215,7 +215,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 +398,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}; @@ -1610,7 +1611,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; } } @@ -2355,7 +2356,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; @@ -2406,7 +2407,7 @@ sub _resp($$;&$$) { /\A \((\\?$RE_ATOM_CHAR+ [0-9]+(?: \\?$RE_ATOM_CHAR+ [0-9]+)*)?\)\z/ or $self->panic($_); my %status = split / /, $1; $mailbox = 'INBOX' if uc $mailbox eq 'INBOX'; # INBOX is case-insensitive - $self->panic("RFC 5465 violation! Missing HIGHESTMODSEQ data item in STATUS response") + $self->panic("RFC 5465 violation! Missing HIGHESTMODSEQ data item in STATUS response") if $self->_enabled('QRESYNC') and !defined $status{HIGHESTMODSEQ} and defined $cmd and ($cmd eq 'NOTIFY' or $cmd eq 'slurp'); $self->_update_cache_for($mailbox, %status); diff --git a/tests/list b/tests/list index 86034ef..bc8d144 100644 --- a/tests/list +++ b/tests/list @@ -38,7 +38,16 @@ repair --repair auth-logindisabled LOGINDISABLED auth-noplaintext abort when STARTTLS is not offered +. 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) 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 index c9926ce..18c60f8 100644 --- a/tests/snippets/dovecot/imapd.conf +++ b/tests/snippets/dovecot/imapd.conf @@ -9,4 +9,8 @@ service imap-login { inet_listener imap { port = 10143 } + inet_listener imaps { + port = 10993 + ssl = yes + } } 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 = "$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-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/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 : -- cgit v1.2.3 From 7bf1b61dcf4eacc4e9d7a23fbe3050495990f8d9 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 10 Nov 2019 20:17:45 +0100 Subject: Test suite: add new test for COMPRESS=DEFLATE. An imapd is required as `doveadm exec imap` won't offer COMPRESS=DEFLATE in its capability list. --- tests/compress/interimap.remote | 1 + tests/compress/remote.conf | 1 + tests/compress/t | 19 +++++++++++++++++++ tests/list | 2 ++ 4 files changed, 23 insertions(+) create mode 120000 tests/compress/interimap.remote create mode 120000 tests/compress/remote.conf create mode 100644 tests/compress/t 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/list b/tests/list index bc8d144..9ce0c06 100644 --- a/tests/list +++ b/tests/list @@ -38,6 +38,8 @@ repair --repair auth-logindisabled LOGINDISABLED auth-noplaintext abort when STARTTLS is not offered +compress COMPRESS=DEFLATE + . SSL/TLS starttls-logindisabled LOGINDISABLED STARTTLS starttls STARTTLS -- cgit v1.2.3 From 23046d58204e636880ff4412e52799e0c06065b4 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 11 Nov 2019 00:20:26 +0100 Subject: Bump minimum Perl for Net::IMAP::InterIMAP to v5.20. We're using s///r which was introduced in 5.14, and hash slices which were introduced in 5.20. --- lib/Net/IMAP/InterIMAP.pm | 2 +- pullimap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index b4d8bec..c25df27 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; diff --git a/pullimap b/pullimap index f983fe1..3d1a0ec 100755 --- a/pullimap +++ b/pullimap @@ -18,10 +18,10 @@ # along with this program. If not, see . #---------------------------------------------------------------------- +use v5.20.2; use strict; use warnings; -use v5.20.2; our $VERSION = '0.4'; my $NAME = 'pullimap'; -- cgit v1.2.3 From c3bf5d306ff1396d6117774316afd998f6e9874a Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 11 Nov 2019 16:43:40 +0100 Subject: interimap: raise SELECT sample set size from 64 to 256 bytes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A n-bytes set covers at least ⌊(n+1)/11⌋ UIDs (UIDs are at most 10 bytes of size), hence 23 UIDs for 256 bytes long sets. However we exceed it by another range, so in the worst case (if the the higher UIDs are sparse) we'll sample ⌊(n+1)/11+1⌋ UIDs: 1000000000,1000000002,1000000004,…,1000000046 This was 6 UIDs for n=64 which is a tad low; this is now raised to 24 UIDs. The actual set size returned by sample() is of max size n+22 bytes (extra "$UID1:$UID2," where $UID1 and $UID2 are both ≥10⁹). --- Changelog | 1 + interimap | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index c08de80..f90c28c 100644 --- a/Changelog +++ b/Changelog @@ -50,6 +50,7 @@ interimap (0.5) upstream; full. + interimap: new option 'log-prefix' to control the prefix of each log entry, depending on the component name and relevant mailbox. + + interimap: raise SELECT sample range size from 64 to 256 bytes. - libinterimap: bugfix: hierarchy delimiters in LIST responses were returned as an escaped quoted special, like "\\", not as a single character (backslash in this case). diff --git a/interimap b/interimap index a409c65..9a1df0b 100755 --- a/interimap +++ b/interimap @@ -793,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($$) { @@ -814,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 : ''); -- cgit v1.2.3 From 3aa5593af18bd4925235d1820fd0fe7c646843aa Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 12 Nov 2019 01:39:29 +0100 Subject: Net::IMAP::InterIMAP::push_flag_updates() bugfixes. The UNCHANGEDSINCE test from the CONDSTORE extension was incorrectly placed after the flag list in UID STORE commands. In practice this meant the server didn't add the MODIFIED code when needed. The server won't send an untagged FETCH command (and won't increase the message's MODSEQ) if no change was made to the flag list. A panic() was incorrectly triggered in that case. When the flag list was set (by another client) to a superset of the UID STORE command currently processed, the extra flags were not synchronized. Cf. RFC 7162 sec. 3.1.3 ex. 10. --- Changelog | 7 ++++++ lib/Net/IMAP/InterIMAP.pm | 58 ++++++++++++++++++++--------------------------- tests/condstore/t | 50 ++++++++++++++++++++++++++++++++++++++++ tests/list | 1 + 4 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 tests/condstore/t diff --git a/Changelog b/Changelog index f90c28c..763a7a1 100644 --- a/Changelog +++ b/Changelog @@ -87,6 +87,13 @@ interimap (0.5) upstream; 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. -- Guilhem Moulin Fri, 10 May 2019 00:58:14 +0200 diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index c25df27..a838dd0 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1197,16 +1197,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 @@ -1216,8 +1215,9 @@ sub pull_updates($;$) { } } $self->{_MODIFIED} = {}; + # 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 $self->_send("UID FETCH ".compact_set(@missing)." (MODSEQ FLAGS)") if @missing; - @missing = (); } # do that afterwards since the UID FETCH command above can produce VANISHED responses @@ -1307,22 +1307,18 @@ 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 $command = "UID STORE ".compact_set(@set)." (UNCHANGEDSINCE $modseq) FLAGS.SILENT ($flags)"; my %failed; + $self->_send($command); 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/) { + } elsif (/\A([0-9]+):([0-9]+)\z/) { my ($min, $max) = $1 < $2 ? ($1,$2) : ($2,$1); $failed{$_} = 1 foreach ($min .. $max); - } - else { + } else { $self->panic($_); } } @@ -1330,34 +1326,28 @@ sub push_flag_updates($$@) { 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}; - } - 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}; + # $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; } - 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; } 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/list b/tests/list index 9ce0c06..8bb4478 100644 --- a/tests/list +++ b/tests/list @@ -39,6 +39,7 @@ repair --repair auth-noplaintext abort when STARTTLS is not offered compress COMPRESS=DEFLATE +condstore CONDSTORE . SSL/TLS starttls-logindisabled LOGINDISABLED STARTTLS -- cgit v1.2.3 From ccf90182d04c064bd9327c5e7067ed4b9dc32f41 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 12 Nov 2019 05:04:45 +0100 Subject: Net::IMAP::InterIMAP: Don't export $IMAP_text and $IMAP_cond. --- lib/Net/IMAP/InterIMAP.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index a838dd0..e595060 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -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/; } -- cgit v1.2.3 From 0a2558aabfefd6800fe74c24e5aff2b0d47cc5e2 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 11 Nov 2019 00:39:09 +0100 Subject: Avoid sending large UID EXPUNGE|FETCH|STORE and APPEND commands. UID EXPUNGE|FETCH|STORE commands are now split into multiple (sequential) commands when their set representation exceeds 4096 bytes in size. Without splitting logic set representations could grow arbitrarily large, and exceed the server's maximum command size. This adds roundtrips which could be eliminated by pipelining, but it's unlikely to make any difference in typical synchronization work. While set representations seem to remain small in practice, they might grow significantly if many non-contiguous UIDs were flagged and/or expunged, and later synchronized at once. Furthermore, for MULTIAPPEND-capable servers, the number of messages is limited to 128 per APPEND command (also subject to a combined literal size of 1MiB like before). These numbers are currently not configurable. They're intentionally lower than Dovecot's default maximum command size (64k) in order to avoid a deadlock situation after sending 8k-long commands under COMPRESS=DEFLATE: https://dovecot.org/pipermail/dovecot/2019-November/117522.html . --- Changelog | 11 +++++++ interimap | 4 +-- lib/Net/IMAP/InterIMAP.pm | 64 ++++++++++++++++++++++++++------------- tests/list | 1 + tests/snippets/dovecot/imapd.conf | 3 ++ tests/split-set/interimap.remote | 1 + tests/split-set/remote.conf | 1 + tests/split-set/t | 43 ++++++++++++++++++++++++++ 8 files changed, 105 insertions(+), 23 deletions(-) create mode 120000 tests/split-set/interimap.remote create mode 120000 tests/split-set/remote.conf create mode 100644 tests/split-set/t diff --git a/Changelog b/Changelog index 763a7a1..f0c9e50 100644 --- a/Changelog +++ b/Changelog @@ -94,6 +94,17 @@ interimap (0.5) upstream; 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 Fri, 10 May 2019 00:58:14 +0200 diff --git a/interimap b/interimap index 9a1df0b..386492e 100755 --- a/interimap +++ b/interimap @@ -1166,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/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index e595060..02ae65f 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -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. @@ -841,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}}; @@ -960,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); + }); } @@ -1217,7 +1235,9 @@ sub pull_updates($;$) { $self->{_MODIFIED} = {}; # 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 - $self->_send("UID FETCH ".compact_set(@missing)." (MODSEQ FLAGS)") if @missing; + 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 @@ -1279,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; @@ -1307,22 +1327,23 @@ 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)." (UNCHANGEDSINCE $modseq) FLAGS.SILENT ($flags)"; my %failed; - $self->_send($command); - 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) { @@ -1360,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)") }); } @@ -1374,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]") }); } diff --git a/tests/list b/tests/list index 8bb4478..a18cb29 100644 --- a/tests/list +++ b/tests/list @@ -40,6 +40,7 @@ repair --repair compress COMPRESS=DEFLATE condstore CONDSTORE +split-set Split large sets to avoid extra-long command lines . SSL/TLS starttls-logindisabled LOGINDISABLED STARTTLS diff --git a/tests/snippets/dovecot/imapd.conf b/tests/snippets/dovecot/imapd.conf index 18c60f8..2b26451 100644 --- a/tests/snippets/dovecot/imapd.conf +++ b/tests/snippets/dovecot/imapd.conf @@ -14,3 +14,6 @@ service imap-login { ssl = yes } } + +# we should avoid sending command lines that are too long +imap_max_line_length = 8192 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 : -- cgit v1.2.3 From dac4ab1c9306bf2035bc1547d2ed27ab09850120 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 13 Nov 2019 04:16:48 +0100 Subject: Test suite: add new test for pullimap(1). This adds a dependency on Dovecot's LMTPd, which will bind to to TCP port 10024 on the loopback interface. --- pullimap | 5 +- tests/list | 3 ++ tests/pullimap/interimap.remote | 1 + tests/pullimap/local.conf | 1 + tests/pullimap/pullimap.conf | 1 + tests/pullimap/remote.conf | 1 + tests/pullimap/t | 96 +++++++++++++++++++++++++++++++++++++++ tests/run | 38 ++++++++++------ tests/snippets/dovecot/lmtpd.conf | 7 +++ 9 files changed, 136 insertions(+), 17 deletions(-) create mode 120000 tests/pullimap/interimap.remote create mode 100644 tests/pullimap/local.conf create mode 100644 tests/pullimap/pullimap.conf create mode 120000 tests/pullimap/remote.conf create mode 100644 tests/pullimap/t create mode 100644 tests/snippets/dovecot/lmtpd.conf diff --git a/pullimap b/pullimap index 3d1a0ec..1dc4b9e 100755 --- a/pullimap +++ b/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: $!"; diff --git a/tests/list b/tests/list index a18cb29..52417c1 100644 --- a/tests/list +++ b/tests/list @@ -55,3 +55,6 @@ split-set Split large sets to avoid extra-long command lines 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/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" "$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/run b/tests/run index 2903938..30d20f9 100755 --- a/tests/run +++ b/tests/run @@ -146,9 +146,10 @@ prepare() { chmod +x -- "$home/.local/bin/doveadm" done - # copy interimap config - mkdir -pm0700 -- "$HOME_local/.local/share/interimap" - mkdir -pm0700 -- "$HOME_local/.config/interimap" + # 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" @@ -160,6 +161,9 @@ prepare() { 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 @@ -172,7 +176,6 @@ prepare() { cat <"$TESTDIR/interimap$n.local" >>"$HOME_local/.config/interimap/config$n" fi - printf "\\n[remote]\\n" >>"$HOME_local/.config/interimap/config$n" if [ -s "$home/.dovecot/users" ]; then cat <<-EOF username = $u @@ -184,21 +187,26 @@ prepare() { command = exec ${home@Q}/.local/bin/doveadm exec imap null-stderr = NO EOF - fi >>"$HOME_local/.config/interimap/config$n" + fi >"$HOME_local/.$u.conf" if [ -f "$TESTDIR/interimap$n.remote" ] || [ -L "$TESTDIR/interimap$n.remote" ]; then - cat <"$TESTDIR/interimap$n.remote" >>"$HOME_local/.config/interimap/config$n" + 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() { +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 "$@" 2>"$STDERR" || r=$? - cat "$STDERR" >&2 - return $r + env -i "${ENVIRON[@]}" perl -I./lib -T "./$script" "$@" 2> >(tee "$STDERR" >&2) } interimap_init() { local u="${1-remote}" @@ -227,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: + From: <$sender@example.net> To: Date: $(date -R -d@"$date") Message-ID: <$date@example.net> @@ -240,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 @@ -433,8 +443,8 @@ passed() { declare -a ENVIRON=() environ_set "local" export TMPDIR TESTDIR STDERR "${ENVIRON[@]}" -export -f environ_set doveadm interimap sqlite3 sample_message deliver -export -f interimap_init ptree_abort step_start step_done passed +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 xcgrep error [ "$TESTNAME" = "..." ] || printf "%s%s..." "${INDENT-}" "$TESTNAME" 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 + } +} -- cgit v1.2.3