diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2019-11-07 20:54:08 +0100 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2019-11-07 20:54:08 +0100 |
commit | a8c3a40c6cb3d05115c0213243edff52ba5f3dcf (patch) | |
tree | 21546e3a77eac185c614989b4c2535face1f17c2 | |
parent | 590bf57446967d897ee8327c8b2df57b77f4744e (diff) | |
parent | a4a371234215a7705f304875cc8af067bf3142af (diff) |
Merge branch 'master' into debian
-rw-r--r-- | Changelog | 23 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | doc/build.md | 18 | ||||
-rw-r--r-- | doc/development.md | 18 | ||||
-rw-r--r-- | doc/index.md | 4 | ||||
-rw-r--r-- | doc/interimap.1.md | 55 | ||||
-rw-r--r-- | doc/pullimap.1.md | 9 | ||||
-rw-r--r-- | doc/template.html | 41 | ||||
-rwxr-xr-x | interimap | 124 | ||||
-rw-r--r-- | interimap.sample | 11 | ||||
-rw-r--r-- | lib/Net/IMAP/InterIMAP.pm | 109 | ||||
-rwxr-xr-x | pullimap | 7 | ||||
-rw-r--r-- | tests/01-rename-exists-db/run | 2 | ||||
-rw-r--r-- | tests/01-rename-exists-local/run | 2 | ||||
-rw-r--r-- | tests/01-rename-exists-remote/run | 2 | ||||
-rw-r--r-- | tests/03-sync-mailbox-list/run | 4 | ||||
-rw-r--r-- | tests/05-repair/run | 6 |
17 files changed, 278 insertions, 161 deletions
@@ -1,5 +1,13 @@ interimap (0.5) upstream; + Breaking changes: + * interimap: when matching mailbox names against the 'ignore-mailbox' + pattern, the hierarchy delimiter is substituted with a null character + before hand. For instance one should now use '^virtual(?:\x00|$)' to + exclude the mailbox named 'virtual' as well as its descendants + (regardless of the hierarchy delimiter in use). + + Other changes: * interimap: the space-speparated list of names and/or patterns in 'list-mailbox' can now contain C-style escape sequences (backslash and hexadecimal escape). @@ -36,6 +44,12 @@ interimap (0.5) upstream; is run following Perl's `exec` semantics: it is passed to `/bin/sh -c` when it contains shell metacharacters; and split into words and passed to execvp(3) otherwise. + + interimap, pullimap: redact AUTHENTICATE and LOGIN commands in + --debug mode in order to avoid inadvertently receiving credentials in + bug reports. --debug can be set twice to spell out these commands in + full. + + interimap: new option 'log-prefix' to control the prefix of each log + entry, depending on the component name and relevant mailbox. - libinterimap: bugfix: hierarchy delimiters in LIST responses were returned as an escaped quoted special, like "\\", not as a single character (backslash in this case). @@ -61,6 +75,15 @@ interimap (0.5) upstream; the 'foreign_keys' PRAGMA during a transaction is a documented no-op). - interimap: fix handling of mod-sequence values greater or equal than 2 << 63. + - libinterimap: use directories relative to $HOME for the XDG + environment variables default values. Previously getpwuid() was + called to determine the user's home directory, while the XDG + specification explicitely mentions $HOME. Conveniently our docs + always mentioned ~/, which on POSIX-compliant systems expands to the + value of the variable HOME. (Cf. Shell and Utilities volume of + POSIX.1-2017, sec. 2.6.1.) + - libinterimap: don't panic() when inflate() reports the end of the + compression stream is reached. -- Guilhem Moulin <guilhem@fripost.org> Fri, 10 May 2019 00:58:14 +0200 @@ -46,8 +46,12 @@ html: $(HTML_FILES) $(HTML_ROOTDIR)/%.html: ./doc/%.md $(HTML_TEMPLATE) mtime="$$(git --no-pager log -1 --pretty="format:%ct" -- "$<" 2>/dev/null)"; \ [ -n "$$mtime" ] || mtime="$$(date +%s -r "$<")"; \ + [ "$<" = "doc/index.md" ] && parent="" || parent="./index.html"; \ pandoc -sp -f markdown -t html+smart --css=$(CSS) --template=$(HTML_TEMPLATE) \ --variable=date:"$$(LC_TIME=C date +"Last modified on %a, %d %b %Y at %T %z" -d @"$$mtime")" \ + --variable=keywords:"interimap" \ + --variable=lang:"en" \ + --variable=parent:"$$parent" \ --output="$@" -- "$<" doc: manual html diff --git a/doc/build.md b/doc/build.md index 38d1bfb..5c362f1 100644 --- a/doc/build.md +++ b/doc/build.md @@ -1,5 +1,5 @@ % Build instructions -% Guilhem Moulin <guilhem@fripost.org> +% [Guilhem Moulin](mailto:guilhem@fripost.org) On Debian 9 (codename *Stretch*) and later, installing [`interimap`(1)] is a single command away: @@ -33,9 +33,9 @@ On Debian GNU/Linux systems, the dependencies can be installed with the following command: $ apt install libconfig-tiny-perl \ - libdbi-perl \ - libdbd-sqlite3-perl \ - libnet-ssleay-perl + libdbi-perl \ + libdbd-sqlite3-perl \ + libnet-ssleay-perl Additional packages are required in order to run the test suite: @@ -68,8 +68,8 @@ the `CSS` environment variable (the value of which defaults to For instance, use $ CSS="https://guilhem.org/static/css/bootstrap.min.css" \ - HTML_ROOTDIR="$XDG_RUNTIME_DIR/interimap" \ - make html + HTML_ROOTDIR="$XDG_RUNTIME_DIR/interimap" \ + make html to generate the HTML documentation under directory `$XDG_RUNTIME_DIR/interimap` (which needs to exist) using a remote CSS file. @@ -78,12 +78,12 @@ The `doc` target generates all documentation, manpages as well as HTML pages. -Build custom Debian package -=========================== +Build custom Debian packages +============================ Debian GNU/Linux users can also use [`gbp`(1)] from [`git-buildpackage`](https://tracker.debian.org/pkg/git-buildpackage) in -order to build their own package: +order to build their own packages: $ git checkout debian $ gbp buildpackage diff --git a/doc/development.md b/doc/development.md index 406207a..49e8d74 100644 --- a/doc/development.md +++ b/doc/development.md @@ -1,5 +1,5 @@ -% Test environment setup for [`interimap`(1)] and [`pullimap`(1)] -% Guilhem Moulin <guilhem@fripost.org> +% Test environment setup +% [Guilhem Moulin](mailto:guilhem@fripost.org) Introduction ============ @@ -83,7 +83,7 @@ pre-authenticated [IMAP4rev1] in the test environment for username `testuser`, list mailboxes, and exit, run: $ env -i PATH="/usr/bin:/bin" USER="testuser" \ - doveadm -c "$BASEDIR/dovecot.conf" exec imap + doveadm -c "$BASEDIR/dovecot.conf" exec imap * PREAUTH [CAPABILITY IMAP4rev1 …] Logged in as testuser a LIST "" "*" * LIST (\HasNoChildren) "." INBOX @@ -98,10 +98,10 @@ the latter to create a mailbox `foo`, add a sample message to it, and finally mark it as `\Seen`. $ env -i PATH="/usr/bin:/bin" USER="testuser" \ - doveadm -c "$BASEDIR/dovecot.conf" mailbox create "foo" + doveadm -c "$BASEDIR/dovecot.conf" mailbox create "foo" <!-- --> $ env -i PATH="/usr/bin:/bin" USER="testuser" HOME="$BASEDIR/testuser" \ - doveadm -c "$BASEDIR/dovecot.conf" exec dovecot-lda -e -m "foo" <<-EOF + doveadm -c "$BASEDIR/dovecot.conf" exec dovecot-lda -e -m "foo" <<-EOF From: <sender@example.net> To: <recipient@example.net> Subject: Hello world! @@ -112,7 +112,7 @@ finally mark it as `\Seen`. EOF <!-- --> $ env -i PATH="/usr/bin:/bin" USER="testuser" \ - doveadm -c "$BASEDIR/dovecot.conf" flags add "\\Seen" mailbox "foo" "*" + doveadm -c "$BASEDIR/dovecot.conf" flags add "\\Seen" mailbox "foo" "*" Normally [`dovecot-lda`(1)](https://wiki.dovecot.org/LDA) tries to do a userdb lookup in order to determine the user's home directory. Since we @@ -155,7 +155,7 @@ You can now run [`interimap`(1)] with `--watch` set, here to one second to observe synchronisation steps early. $ env -i PATH="$PATH" perl -I./lib -T ./interimap --config="$BASEDIR/interimap.conf" \ - --watch=1 --debug + --watch=1 --debug Use instructions from the [previous section][Mail storage access] (substituting `testuser` with `local` or `remote`) in order to simulate @@ -179,12 +179,12 @@ Create a [`pullimap`(1)] configuration file with as section `[foo]`. Run [`pullimap`(1)] without `--idle` in order to create the state file. $ env -i PATH="$PATH" perl -I./lib -T ./pullimap --config="$BASEDIR/pullimap.conf" \ - --no-delivery foo + --no-delivery foo You can now run [`pullimap`(1)] with `--idle` set. $ env -i PATH="$PATH" perl -I./lib -T ./pullimap --config="$BASEDIR/pullimap.conf" \ - --no-delivery --idle --debug foo + --no-delivery --idle --debug foo Use instructions from the [previous section][Mail storage access] in order to simulate activity on the “remote” server (in the relevant diff --git a/doc/index.md b/doc/index.md index 12de956..a403a3e 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,5 +1,5 @@ -% [`interimap`(1)] and [`pullimap`(1)] documentation -% Guilhem Moulin <guilhem@fripost.org> +% [`interimap`(1)] and [`pullimap`(1)] +% [Guilhem Moulin](mailto:guilhem@fripost.org) Manuals (HTML versions) ----------------------- diff --git a/doc/interimap.1.md b/doc/interimap.1.md index 387850a..ee92668 100644 --- a/doc/interimap.1.md +++ b/doc/interimap.1.md @@ -85,7 +85,10 @@ However if some extra argument are provided on the command line, `interimap` ignores these options and synchronizes the given *MAILBOX*es instead. Note that each *MAILBOX* is taken “as is”; in particular, it must be [UTF-7 encoded][RFC 2152], unquoted, and the list -wildcards ‘\*’ and ‘%’ are passed verbatim to the IMAP server. +wildcards ‘\*’ and ‘%’ are passed verbatim to the IMAP server. If the +local and remote hierarchy delimiter differ, then within the *MAILBOX* +names the *local* delimiter should be used (it is transparently +substituted for remote commands and responses). If the synchronization was interrupted during a previous run while some messages were being replicated (but before the `UIDNEXT` or @@ -175,10 +178,11 @@ Options `--debug` -: Turn on debug mode. Debug messages are written to the given *logfile*. - Note that this include all IMAP traffic (except literals). - Depending on the chosen authentication mechanism, this might include - authentication credentials. +: Turn on debug mode. Debug messages, which includes all IMAP traffic + besides literals, are written to the given *logfile*. The `LOGIN` + and `AUTHENTICATE` commands are however redacted (in order to avoid + disclosing authentication credentials) unless the `--debug` flag is + set multiple times. `-h`, `--help` @@ -245,7 +249,13 @@ Valid options are: Two wildcards are available, and passed verbatim to the IMAP server: a ‘\*’ character matches zero or more characters, while a ‘%’ character matches zero or more characters up to the hierarchy - delimiter. + delimiter. Hardcoding the hierarchy delimiter in this setting is + not advised because the server might silently change it at some + point. A null character should be used instead. For instance, if + *list-mailbox* is set `"foo\x00bar"` then, assuming the hierarchy + delimiter is ‘/’, only the mailbox named `foo/bar` is considered for + synchronization. + This option is only available in the default section. (The default pattern, `*`, matches all visible mailboxes on the server.) @@ -266,7 +276,10 @@ Valid options are: : An optional Perl Compatible Regular Expressions ([PCRE]) covering mailboxes to exclude: any ([UTF-7 encoded][RFC 2152] and unquoted) mailbox listed in the initial `LIST` responses is ignored if it - matches the given expression. + matches the given expression after trimming the reference names and + substituting the hierarchy delimiter with the null character. For + instance, specifying `^virtual(?:\x00|$)` excludes the mailbox named + “virtual” as well as its descendants. Note that the *MAILBOX*es given as command-line arguments bypass the check and are always considered for synchronization. This option is only available in the default section. @@ -277,6 +290,19 @@ Valid options are: default these messages are written to the error output.) This option is only available in the default section. +*log-prefix* + +: A `printf`(3)-like format string to use as prefix for each log + message. Interpreted sequences are `%n` and `%m`, expanding + respectively to the component name (*local*/*remote*) and to the + name of the mailbox relevant for the log entry. Conditions on a + specifier `%X` can be obtained with `%?X?then?` or `%?X?then&else?`, + which expands to *then* if the `%X` specifier expands to a non-empty + string, and to *else* (or the empty string if there is no else + condition) if it doesn't. Literal `%` characters need to be escaped + as `%%`, while `&`, `?` and `\` characters need to be `\`-escaped. + (Default: `%?n?%?m?%n(%m)&%n?: ?`.) + *type* : One of `imap`, `imaps` or `tunnel`. @@ -422,9 +448,10 @@ Known bugs and limitations * Using `interimap` on two identical servers with a non-existent or empty *database* will duplicate each message due to the absence of - local ↔ remote UID association. Hence one needs to manually empty - the mail store on one end when migrating to `interimap` from another - synchronisation solution. + local ↔ remote UID association. (Should they arise, an external tool + such as [`doveadm-deduplicate`(1)] can be used to weed them out.) + Hence one needs to manually empty the mail store on one end when + migrating to `interimap` from another synchronization solution. * `interimap` is single threaded and doesn't use IMAP command pipelining. Synchronization could be boosted up by sending @@ -439,6 +466,13 @@ Known bugs and limitations was deleted while another one (which is replicated again) was added to the other mailbox in the meantime. + * Because the [IMAP protocol][RFC 3501] doesn't provide a way for + clients to determine whether a disappeared mailbox was deleted or + renamed, `interimap` aborts when a known mailbox disappeared from one + server but not the other. The `--delete` (resp. `rename`) command + should be used instead to delete (resp. rename) the mailbox on both + servers as well as within `interimap`'s internal database. + * `PLAIN` and `LOGIN` are the only authentication mechanisms currently supported. @@ -524,3 +558,4 @@ Standards [PCRE]: https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions [`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html [`verify`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/verify.html +[`doveadm-deduplicate`(1)]: https://wiki.dovecot.org/Tools/Doveadm/Deduplicate diff --git a/doc/pullimap.1.md b/doc/pullimap.1.md index 1b2e509..d40ece8 100644 --- a/doc/pullimap.1.md +++ b/doc/pullimap.1.md @@ -57,10 +57,11 @@ Options `--debug` -: Turn on debug mode. Debug messages are written to the error output. - Note that this include all IMAP traffic (except literals). - Depending on the chosen authentication mechanism, this might include - authentication credentials. +: Turn on debug mode. Debug messages, which includes all IMAP traffic + besides literals, are written to the given *logfile*. The `LOGIN` + and `AUTHENTICATE` commands are however redacted (in order to avoid + disclosing authentication credentials) unless the `--debug` flag is + set multiple times. `-h`, `--help` diff --git a/doc/template.html b/doc/template.html index e17f0e3..2cd7cc9 100644 --- a/doc/template.html +++ b/doc/template.html @@ -15,12 +15,25 @@ $if(keywords)$ $endif$ <title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title> <style type="text/css"> - code{white-space: pre-wrap;} - span.smallcaps{font-variant: small-caps;} - span.underline{text-decoration: underline;} - div.column{display: inline-block; vertical-align: top; width: 50%;} + code{white-space: pre-wrap;} + span.smallcaps{font-variant: small-caps;} + span.underline{text-decoration: underline;} + div.column{display: inline-block; vertical-align: top; width: 50%;} + pre{tab-size: 4; -moz-tab-size: 4;} + @media only screen and (min-width: 600px) { + .parent { + float: right; + margin-left: 1em; + } + .content p { + text-align: justify; + } + } + @media(max-width: 1440px) { .container{ max-width: 1080px; } } + @media(max-width: 1280px) { .container{ max-width: 960px; } } + @media(max-width: 1024px) { .container{ max-width: 768px; } } $if(quotes)$ - q { quotes: "“" "”" "‘" "’"; } + q { quotes: "“" "”" "‘" "’"; } $endif$ </style> $if(highlighting-css)$ @@ -31,11 +44,6 @@ $endif$ $for(css)$ <link rel="stylesheet" href="$css$" /> $endfor$ - <style type="text/css"> - @media(max-width: 1440px) { .container{ max-width: 1080px; } } - @media(max-width: 1280px) { .container{ max-width: 960px; } } - @media(max-width: 1024px) { .container{ max-width: 768px; } } - </style> $if(math)$ $math$ $endif$ @@ -48,21 +56,26 @@ $endfor$ $for(include-before)$ $include-before$ $endfor$ -<div class="container text-justify"> +<div class="container"> <div class="content"> $if(title)$ - <div class="page-header"><h1>$title$</h1></div> + <div class="page-header"> +$if(parent)$ + <div class=parent><a href="$parent$"><span class="glyphicon glyphicon-circle-arrow-up" aria-hidden="true"></span> Parent</a></div> +$endif$ + <h1 style="">$title$</h1> + </div> $endif$ $body$ - </div> +</div> <footer> <hr/> <div class="row"> <div class="col-md-8 text-muted"> $if(author)$ - <a href="https://git.guilhem.org/interimap/plain/COPYING">©</a> + <a href="https://git.guilhem.org/interimap/plain/COPYING">Copyright</a> © $for(author)$$author$$sep$, $endfor$ $endif$ </div> @@ -57,7 +57,7 @@ sub usage(;$) { } my @COMMANDS = qw/repair delete rename/; -usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q target=s@ debug help|h watch:i notify/, @COMMANDS); +usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q target=s@ debug+ help|h watch:i notify/, @COMMANDS); usage(0) if $CONFIG{help}; my $COMMAND = do { my @command = grep {exists $CONFIG{$_}} @COMMANDS; @@ -79,32 +79,32 @@ my $CONF = do { , [qw/_ local remote/] , database => qr/\A(\P{Control}+)\z/ , logfile => qr/\A(\/\P{Control}+)\z/ + , 'log-prefix' => qr/\A(\P{Control}*)\z/ , 'list-reference' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]*)\z/ , 'list-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ , 'list-select-opts' => qr/\A([\x20\x21\x23\x24\x26\x27\x2B-\x5B\x5E-\x7A\x7C-\x7E]*)\z/ , 'ignore-mailbox' => qr/\A([\x01-\x09\x0B\x0C\x0E-\x7F]+)\z/ ); }; -my ($DBFILE, $LOGGER_FD, %LIST); +my ($DBFILE, %LOGGER_CONF, %LIST); { - $DBFILE = $CONF->{_}->{database} if defined $CONF->{_}; + $CONF->{_} //= {}; + $DBFILE = $CONF->{_}->{database}; $DBFILE //= $CONF->{remote}->{host}.'.db' if defined $CONF->{remote}; $DBFILE //= $CONF->{local}->{host}. '.db' if defined $CONF->{local}; die "Missing option database" unless defined $DBFILE; $DBFILE = xdg_basedir( XDG_DATA_HOME => ".local/share", $NAME, $DBFILE ); - if (defined $CONF->{_} and defined $CONF->{_}->{logfile}) { + $LOGGER_CONF{'logger-prefix'} = $CONF->{_}->{'log-prefix'} // "%?n?%?m?%n(%m)&%n?: ?"; + if (defined (my $l = $CONF->{_}->{logfile})) { require 'POSIX.pm'; require 'Time/HiRes.pm'; - open $LOGGER_FD, '>>', $CONF->{_}->{logfile} - or die "Can't open $CONF->{_}->{logfile}: $!\n"; - $LOGGER_FD->autoflush(1); - my $flags = fcntl($LOGGER_FD, F_GETFD, 0) or die "fcntl F_GETFD: $!"; - fcntl($LOGGER_FD, F_SETFD, $flags | FD_CLOEXEC) or die "fcntl F_SETFD: $!"; - } - elsif ($CONFIG{debug}) { - $LOGGER_FD = \*STDERR; + open my $fd, '>>', $l or die "Can't open $l: $!\n"; + $fd->autoflush(1); + my $flags = fcntl($fd, F_GETFD, 0) or die "fcntl F_GETFD: $!"; + fcntl($fd, F_SETFD, $flags | FD_CLOEXEC) or die "fcntl F_SETFD: $!"; + $LOGGER_CONF{'logger-fd'} = $fd; } $LIST{mailbox} = [@ARGV]; @@ -149,7 +149,7 @@ my ($IMAP, $lIMAP, $rIMAP); sub cleanup() { undef $_ foreach grep defined, ($IMAP, $lIMAP, $rIMAP); logger(undef, "Cleaning up...") if $CONFIG{debug}; - close $LOGGER_FD if defined $LOGGER_FD; + $LOGGER_CONF{'logger-fd'}->close() if defined $LOGGER_CONF{'logger-fd'}; $DBH->disconnect() if defined $DBH; } $SIG{INT} = sub { msg(undef, $!); cleanup(); exit 1; }; @@ -180,31 +180,25 @@ $SIG{TERM} = sub { cleanup(); exit 0; }; } sub msg($@) { + my %h = ( %LOGGER_CONF, name => shift ); + return Net::IMAP::InterIMAP::log(\%h, @_); +} +sub msg2($$@) { my $name = shift; - return unless @_; - logger($name, @_) if defined $LOGGER_FD and defined $LOGGER_FD->fileno - and $LOGGER_FD->fileno != fileno STDERR; - my $prefix = defined $name ? "$name: " : ''; - print STDERR $prefix, @_, "\n"; + my $mailbox = mbx_name($name => shift); + my %h = ( %LOGGER_CONF, name => $name, mailbox => $mailbox ); + return Net::IMAP::InterIMAP::log(\%h, @_); } sub logger($@) { - my $name = shift; - return unless @_ and defined $LOGGER_FD; - my $prefix = ''; - if (defined $LOGGER_FD and defined $LOGGER_FD->fileno - and $LOGGER_FD->fileno != fileno STDERR) { - my ($s, $us) = Time::HiRes::gettimeofday(); - $prefix = POSIX::strftime("%b %e %H:%M:%S", localtime($s)).".$us "; - } - $prefix .= "$name: " if defined $name; - $LOGGER_FD->say($prefix, @_); + my %h = ( %LOGGER_CONF, name => shift ); + return Net::IMAP::InterIMAP::logger(\%h, @_); } sub fail($@) { my $name = shift; msg($name, "ERROR: ", @_); exit 1; } -logger(undef, ">>> $NAME $VERSION"); +logger(undef, ">>> $NAME $VERSION") if $CONFIG{debug}; ############################################################################# @@ -215,12 +209,12 @@ foreach my $name (qw/local remote/) { $config{$_} = $CONFIG{$_} foreach grep {defined $CONFIG{$_}} qw/quiet debug/; $config{enable} = 'QRESYNC'; $config{name} = $name; - $config{'logger-fd'} = $LOGGER_FD if defined $LOGGER_FD; + $config{$_} = $LOGGER_CONF{$_} foreach keys %LOGGER_CONF; $config{'compress'} //= ($name eq 'local' ? 0 : 1); $config{keepalive} = 1 if $CONFIG{watch} and $config{type} ne 'tunnel'; - $IMAP->{$name} = { client => Net::IMAP::InterIMAP::->new(%config) }; - my $client = $IMAP->{$name}->{client}; + my $client = Net::IMAP::InterIMAP::->new(%config); + $IMAP->{$name} = { client => $client }; die "Non $_-capable IMAP server.\n" foreach $client->incapable(qw/LIST-EXTENDED UIDPLUS/); die "Non LIST-STATUS-capable IMAP server.\n" if !$CONFIG{notify} and $client->incapable('LIST-STATUS'); @@ -280,7 +274,7 @@ sub list_mailboxes($) { # INBOX exists in a namespace of its own, so it may have a different separator. # All other mailboxes MUST have the same separator though, per 3501 sec. 7.2.2 - # and https://www.imapwiki.org/ClientImplementation/MailboxList#Hierarchy_separators + # and https://imapwiki.org/ClientImplementation/MailboxList#Hierarchy_separators # (We assume all list-mailbox arguments given live in the same namespace. Otherwise # the user needs to start multiple interimap instances.) delete $delims->{INBOX}; @@ -446,7 +440,7 @@ sub db_create_mailbox($$) { $sth->bind_param(1, $mailbox, SQL_BLOB); $sth->bind_param(2, $subscribed, SQL_BOOLEAN); my $r = $sth->execute(); - msg("database", fmt("Created mailbox %d", $mailbox)); + msg("database", "Created mailbox ", mbx_pretty($mailbox)); return $r; } @@ -473,6 +467,7 @@ sub mbx_name($$) { } return defined $name ? ($CONF->{$name}->{"list-reference"} . $mailbox) : $mailbox; } +sub mbx_pretty($) { return mbx_name(undef, $_[0]); } # Transform mailbox name from local/remote IMAP server to the internal representation # (with \0 as hierarchy delimiters and without reference prefix). Return undef if @@ -552,7 +547,7 @@ if (defined $COMMAND and $COMMAND eq 'delete') { $sth->execute(); } $DBH->commit(); - msg("database", fmt("Removed mailbox %d", $mailbox)); + msg("database", "Removed mailbox ", mbx_pretty($mailbox)); } } exit 0; @@ -578,11 +573,14 @@ elsif (defined $COMMAND and $COMMAND eq 'rename') { foreach my $name (qw/local remote/) { my $mbx = mbx_name($name, $to); next unless $CONFIG{target}->{$name} and mbx_exists($name, $mbx); - fail($name, fmt("Mailbox %s exists. Run `$NAME --target=$name --delete %d` to delete.", $mbx, $to)); + fail($name, "Mailbox $mbx exists. Run `$NAME --target=$name --delete ", + mbx_pretty($to), "` to delete."); } # ensure the target name doesn't already exist in the database - fail("database", fmt("Mailbox %d exists. Run `$NAME --target=database --delete %d` to delete.", $to, $to)) + my $to_pretty = mbx_pretty($to); + fail("database", "Mailbox $to_pretty exists. Run `$NAME --target=database ", + "--delete $to_pretty` to delete.") if $CONFIG{target}->{database} and defined db_get_mailbox_idx($to); @@ -623,7 +621,8 @@ elsif (defined $COMMAND and $COMMAND eq 'rename') { $r += $sth_rename_children->execute(); $DBH->commit(); - msg("database", fmt("Renamed mailbox %d to %d", $from, $to)) if $r > 0; + msg("database", "Renamed mailbox ", mbx_pretty($from), " to ", + mbx_pretty($to)) if $r > 0; } exit 0; } @@ -702,7 +701,8 @@ sub sync_mailbox_list() { } elsif ($lExists or $rExists) { # $mailbox is on one server only - fail("database", fmt("Mailbox %d exists. Run `$NAME --target=database --delete %d` to delete.", $mailbox, $mailbox)) + my $str = mbx_pretty($mailbox); + fail("database", "Mailbox $str exists. Run `$NAME --target=database --delete $str` to delete.") if defined $idx; my ($name1, $name2, $mbx1, $mbx2) = $lExists ? ("local", "remote", $lMailbox, $rMailbox) : ("remote", "local", $rMailbox, $lMailbox); @@ -732,8 +732,7 @@ sub download_missing($$$@) { my @set = @_; my @uids; - my ($target, $f) = $source eq 'local' ? ('remote', '%l') : ('local', '%r'); - my $prefix = fmt("%s($f)", $source, $mailbox) unless $CONFIG{quiet}; + my $target = $source eq 'local' ? 'remote' : 'local'; my ($buff, $bufflen) = ([], 0); undef $buff if ($target eq 'local' ? $lIMAP : $rIMAP)->incapable('MULTIAPPEND'); @@ -746,7 +745,7 @@ sub download_missing($$$@) { my $from = first { defined $_ and @$_ } @{$mail->{ENVELOPE}}[2,3,4]; $from = (defined $from and defined $from->[0]->[2] and defined $from->[0]->[3]) ? $from->[0]->[2].'@'.$from->[0]->[3] : ''; - msg($prefix, "UID $mail->{UID} from <$from> ($mail->{INTERNALDATE})"); + msg2($source => $mailbox, "UID $mail->{UID} from <$from> ($mail->{INTERNALDATE})"); } callback_new_message($idx, $mailbox, $source, $mail, \@uids, $buff, \$bufflen) }); @@ -761,9 +760,9 @@ sub flag_conflict($$$$$) { my %flags = map {$_ => 1} (split(/ /, $lFlags), split(/ /, $rFlags)); my $flags = join ' ', sort(keys %flags); - msg(undef, fmt("WARNING: Conflicting flag update in %d for local UID $lUID (%s) ". - "and remote UID $rUID (%s). Setting both to the union (%s).", - $mailbox, $lFlags, $rFlags, $flags)); + msg(undef, "WARNING: Conflicting flag update in ", mbx_pretty($mailbox), + " for local UID $lUID ($lFlags) and remote UID $rUID ($rFlags).", + " Setting both to the union ($flags)."); return $flags } @@ -913,7 +912,8 @@ sub repair($) { } else { # conflict - msg(undef, fmt("WARNING: Missed flag update in %d for (lUID,rUID) = ($lUID,$rUID). Repairing.", $mailbox)) + msg(undef, "WARNING: Missed flag update in ", mbx_pretty($mailbox), + " for (lUID,rUID) = ($lUID,$rUID). Repairing.") if $lModSeq <= $cache->{lHIGHESTMODSEQ} and $rModSeq <= $cache->{rHIGHESTMODSEQ}; # set both $lUID and $rUID to the union of $lFlags and $rFlags my $flags = flag_conflict($mailbox, $lUID => $lFlags, $rUID => $rFlags); @@ -925,7 +925,8 @@ sub repair($) { } elsif (!defined $lModified->{$lUID} and !defined $rModified->{$rUID}) { push @delete_mapping, $lUID; - msg(undef, fmt("WARNING: Pair (lUID,rUID) = ($lUID,$rUID) vanished from %d. Repairing.", $mailbox)) + msg(undef, "WARNING: Pair (lUID,rUID) = ($lUID,$rUID) vanished from ", + mbx_pretty($mailbox), ". Repairing.") unless $lVanished{$lUID} and $rVanished{$rUID}; } elsif (!defined $lModified->{$lUID}) { @@ -933,7 +934,7 @@ sub repair($) { if ($lVanished{$lUID}) { push @rToRemove, $rUID; } else { - msg(fmt("local(%l)", $mailbox), "WARNING: UID $lUID disappeared. Downloading remote UID $rUID again."); + msg2(local => $mailbox, "WARNING: UID $lUID disappeared. Redownloading remote UID $rUID."); push @rMissing, $rUID; } } @@ -942,7 +943,7 @@ sub repair($) { if ($rVanished{$rUID}) { push @lToRemove, $lUID; } else { - msg(fmt("remote(%r)",$mailbox), "WARNING: UID $rUID disappeared. Downloading local UID $lUID again."); + msg2(remote => $mailbox, "WARNING: UID $rUID disappeared. Redownloading local UID $lUID."); push @lMissing, $lUID; } } @@ -972,17 +973,17 @@ sub repair($) { # Process UID found in IMAP but not in the mapping table. my @lDunno = keys %lVanished; my @rDunno = keys %rVanished; - msg(fmt("remote(%r)",$mailbox), "WARNING: No match for ".($#lDunno+1)." vanished local UID(s) " + msg2(remote => $mailbox, "WARNING: No match for ".($#lDunno+1)." vanished local UID(s) " .compact_set(@lDunno).". Ignoring.") if @lDunno; - msg(fmt("local(%l)",$mailbox), "WARNING: No match for ".($#rDunno+1)." vanished remote UID(s) " + msg2(local => $mailbox, "WARNING: No match for ".($#rDunno+1)." vanished remote UID(s) " .compact_set(@rDunno).". Ignoring.") if @rDunno; foreach my $lUID (keys %$lModified) { - msg(fmt("remote(%r)",$mailbox), "WARNING: No match for modified local UID $lUID. Downloading again."); + msg2(remote => $mailbox, "WARNING: No match for modified local UID $lUID. Redownloading."); push @lMissing, $lUID; } foreach my $rUID (keys %$rModified) { - msg(fmt("local(%l)",$mailbox), "WARNING: No match for modified remote UID $rUID. Downloading again."); + msg2(local => $mailbox, "WARNING: No match for modified remote UID $rUID. Redownloading."); push @rMissing, $rUID; } @@ -1062,9 +1063,9 @@ sub sync_known_messages($$) { } } - msg(fmt("remote(%r)",$mailbox), "WARNING: No match for ".($#lDunno+1)." vanished local UID(s) " + msg2(remote => $mailbox, "WARNING: No match for ".($#lDunno+1)." vanished local UID(s) " .compact_set(@lDunno).". Ignoring.") if @lDunno; - msg(fmt("local(%l)",$mailbox), "WARNING: No match for ".($#rDunno+1)." vanished remote UID(s) " + msg2(local => $mailbox, "WARNING: No match for ".($#rDunno+1)." vanished remote UID(s) " .compact_set(@rDunno).". Ignoring.") if @rDunno; $lIMAP->remove_message(@lToRemove) if @lToRemove; @@ -1097,7 +1098,7 @@ sub sync_known_messages($$) { my ($rUID) = $sth_get_remote_uid->fetchrow_array(); die if defined $sth_get_remote_uid->fetch(); # safety check if (!defined $rUID) { - msg(fmt("remote(%r)",$mailbox), "WARNING: No match for modified local UID $lUID. Try '--repair'."); + msg2(remote => $mailbox, "WARNING: No match for modified local UID $lUID. Try '--repair'."); } elsif (defined (my $rFlags = $rModified->{$rUID})) { unless ($lFlags eq $rFlags) { my $flags = flag_conflict($mailbox, $lUID => $lFlags, $rUID => $rFlags); @@ -1118,7 +1119,7 @@ sub sync_known_messages($$) { my ($lUID) = $sth_get_local_uid->fetchrow_array(); die if defined $sth_get_local_uid->fetch(); # safety check if (!defined $lUID) { - msg(fmt("local(%l)",$mailbox), "WARNING: No match for modified remote UID $rUID. Try '--repair'."); + msg2(local => $mailbox, "WARNING: No match for modified remote UID $rUID. Try '--repair'."); } elsif (!exists $lModified->{$lUID}) { # conflicts are taken care of above $lToUpdate{$rFlags} //= []; @@ -1150,8 +1151,7 @@ sub callback_new_message($$$$;$$$) { my $length = length ${$mail->{RFC822}}; if ($length == 0) { - my $prefix = $name eq "local" ? "local(%l)" : "remote(%r)"; - msg(fmt($prefix, $mailbox), "WARNING: Ignoring new 0-length message (UID $mail->{UID})"); + msg2($name => $mailbox, "WARNING: Ignoring new 0-length message (UID $mail->{UID})"); return; } @@ -1161,7 +1161,7 @@ sub callback_new_message($$$$;$$$) { } else { # use MULTIAPPEND (RFC 3502) - # proceed by batches of 1MB to save roundtrips without blowing up the memory + # proceed by 1MiB batches to save roundtrips without blowing up the memory if (@$buff and $$bufflen + $length > 1048576) { @UIDs = callback_new_message_flush($idx, $mailbox, $name, @$buff); @$buff = (); @@ -1191,7 +1191,7 @@ sub callback_new_message_flush($$$@) { }); my ($lUIDs, $rUIDs) = $name eq 'local' ? (\@sUID,\@tUID) : (\@tUID,\@sUID); for (my $k=0; $k<=$#messages; $k++) { - logger(undef, fmt("Adding mapping (lUID,rUID) = ($lUIDs->[$k],$rUIDs->[$k]) for %d", $mailbox)) + logger(undef, "Adding mapping (lUID,rUID) = ($lUIDs->[$k],$rUIDs->[$k]) for ", mbx_pretty($mailbox)) if $CONFIG{debug}; $sth->bind_param(1, $idx, SQL_INTEGER); $sth->bind_param(2, $lUIDs->[$k], SQL_INTEGER); @@ -1321,7 +1321,7 @@ sub db_get_cache_by_idx($) { next unless grep { $_ eq $row->[1] } @MAILBOXES; # skip ignored mailboxes ($IDX, $MAILBOX) = @$row; - msg(undef, fmt("Resuming interrupted sync for %d", $MAILBOX)); + msg(undef, "Resuming interrupted sync for ", mbx_pretty($MAILBOX)); my $cache = db_get_cache_by_idx($IDX) // die; # safety check my ($lMailbox, $rMailbox) = map {mbx_name($_, $MAILBOX)} qw/local remote/; diff --git a/interimap.sample b/interimap.sample index f771e54..b0f4b95 100644 --- a/interimap.sample +++ b/interimap.sample @@ -1,11 +1,16 @@ #database = imap.example.org.db -#list-mailbox = "*" + +# only consider subscribed mailboxes list-select-opts = SUBSCRIBED -ignore-mailbox = ^virtual/ +#list-mailbox = "*" + +# ignore the mailbox named 'virtual' and its descendants +ignore-mailbox = ^virtual(?:\x00|$) + [local] type = tunnel -command = /usr/lib/dovecot/imap +command = exec doveadm exec imap null-stderr = YES [remote] diff --git a/lib/Net/IMAP/InterIMAP.pm b/lib/Net/IMAP/InterIMAP.pm index 19895c4..bb27009 100644 --- a/lib/Net/IMAP/InterIMAP.pm +++ b/lib/Net/IMAP/InterIMAP.pm @@ -1,6 +1,6 @@ #---------------------------------------------------------------------- # A minimal IMAP4 client for QRESYNC-capable servers -# Copyright © 2015-2018 Guilhem Moulin <guilhem@fripost.org> +# Copyright © 2015-2019 Guilhem Moulin <guilhem@fripost.org> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,10 +17,11 @@ #---------------------------------------------------------------------- package Net::IMAP::InterIMAP v0.0.5; +use v5.10.0; use warnings; use strict; -use Compress::Raw::Zlib qw/Z_OK Z_FULL_FLUSH Z_SYNC_FLUSH MAX_WBITS/; +use Compress::Raw::Zlib qw/Z_OK Z_STREAM_END Z_FULL_FLUSH Z_SYNC_FLUSH MAX_WBITS/; use Config::Tiny (); use Errno qw/EEXIST EINTR/; use Net::SSLeay 1.73 (); @@ -92,11 +93,9 @@ sub xdg_basedir($$$$) { return $path if $path =~ /\A\//; my $basedir = $ENV{$xdg_variable}; - unless (defined $basedir) { - my @getent = getpwuid($>); - $basedir = $getent[7] ."/". $default; - } + $basedir = ($ENV{HOME} // "") ."/". $default unless defined $basedir; die "No such directory: ", $basedir unless -d $basedir; + $basedir .= "/".$subdir; $basedir =~ /\A(\/\p{Print}+)\z/ or die "Insecure $basedir"; $basedir = $1; @@ -282,7 +281,8 @@ our $IMAP_text; # # - 'name': An optional instance name to include in log messages. # -# - 'logger-fd': An optional filehandle to use for debug output. +# - 'logger-fd': An optional filehandle to use for debug output +# (default: STDERR). # # - 'keepalive': Whether to enable sending of keep-alive messages. # (type=imap or type=imaps). @@ -291,6 +291,7 @@ sub new($%) { my $class = shift; my $self = { @_ }; bless $self, $class; + require 'Time/HiRes.pm' if defined $self->{'logger-fd'}; # the IMAP state: one of 'UNAUTH', 'AUTH', 'SELECTED' or 'LOGOUT' # (cf RFC 3501 section 3) @@ -380,11 +381,6 @@ sub new($%) { # are considered. $self->{_MODIFIED} = {}; - if (defined $self->{'logger-fd'} and defined $self->{'logger-fd'}->fileno - and $self->{'logger-fd'}->fileno != fileno STDERR) { - require 'Time/HiRes.pm'; - } - # wait for the greeting my $x = $self->_getline(); $x =~ s/\A\* (OK|PREAUTH) // or $self->panic($x); @@ -409,8 +405,8 @@ sub new($%) { @caps = $self->capabilities(); } - my @mechs = ('LOGIN', grep defined, map { /^AUTH=(.+)/i ? $1 : undef } @caps); - my $mech = (grep defined, map {my $m = $_; (grep {$m eq $_} @mechs) ? $m : undef} + my @mechs = ('LOGIN', grep defined, map { /^AUTH=(.+)/i ? uc($1) : undef } @caps); + my $mech = (grep defined, map {my $m = uc($_); (grep {$m eq $_} @mechs) ? $m : undef} split(/ /, $self->{auth}))[0]; $self->fail("Failed to choose an authentication mechanism") unless defined $mech; $self->fail("Logins are disabled.") if ($mech eq 'LOGIN' or $mech eq 'PLAIN') and @@ -438,8 +434,21 @@ sub new($%) { $self->fail("Unsupported authentication mechanism: $mech"); } + my $dbg; delete $self->{password}; # no need to remember passwords + if (($self->{debug} // 0) == 1) { + $dbg = $self->{debug}--; + my $cmd = $command =~ /\A(LOGIN) / ? $1 + : $command =~ /\A(AUTHENTICATE \S+)(?: .*)?\z/ ? $1 + : $self->panic(); + $self->logger('C: xxx ', $cmd, ' [REDACTED]'); + } $self->_send($command, $callback); + if (defined $dbg) { + $self->logger('S: xxx ', $IMAP_text); + $self->{debug} = $dbg; + } + unless ($IMAP_text =~ /\A\Q$IMAP_cond\E \[CAPABILITY /) { # refresh the CAPABILITY list since the previous one had only pre-login capabilities delete $self->{_CAPABILITIES}; @@ -509,6 +518,7 @@ sub stats($) { # Destroy a Net::IMAP::InterIMAP object. sub DESTROY($) { + local($., $@, $!, $^E, $?); my $self = shift; $self->{_STATE} = 'LOGOUT'; @@ -527,32 +537,55 @@ sub DESTROY($) { # $self->log($message, [...]) # $self->logger($message, [...]) -# Log a $message. The latter method is used to log in the 'logger-fd', and -# add timestamps. +# Log a $message. The latter method is used to log in the 'logger-fd' +# (and adds timestamps). sub log($@) { my $self = shift; return unless @_; - $self->logger(@_) if defined $self->{'logger-fd'} and defined $self->{'logger-fd'}->fileno - and $self->{'logger-fd'}->fileno != fileno STDERR; - my $prefix = $self->{name} // ''; - $prefix .= "($self->{_SELECTED})" if $self->{_STATE} eq 'SELECTED'; - $prefix .= ': ' unless $prefix eq ''; - print STDERR $prefix, @_, "\n"; + my $prefix = _logger_prefix($self); + if (defined (my $fd = $self->{'logger-fd'})) { + say $fd _date(), " ", $prefix, @_; + } + say STDERR $prefix, @_; } sub logger($@) { my $self = shift; - return unless @_ and defined $self->{'logger-fd'}; - my $prefix = ''; - if (defined $self->{'logger-fd'}->fileno and defined $self->{'logger-fd'}->fileno - and $self->{'logger-fd'}->fileno != fileno STDERR) { - my ($s, $us) = Time::HiRes::gettimeofday(); - $prefix = POSIX::strftime("%b %e %H:%M:%S", localtime($s)).".$us"; - $prefix .= ' ' if defined $self->{name} or $self->{_STATE} eq 'SELECTED'; + return unless @_; + my $prefix = _logger_prefix($self); + if (defined (my $fd = $self->{'logger-fd'})) { + say $fd _date(), " ", $prefix, @_; + } else { + say STDERR $prefix, @_; } - $prefix .= $self->{name} if defined $self->{name}; - $prefix .= "($self->{_SELECTED})" if $self->{_STATE} eq 'SELECTED'; - $prefix .= ': ' unless $prefix eq ''; - $self->{'logger-fd'}->say($prefix, @_); +} +sub _date() { + my ($s, $us) = Time::HiRes::gettimeofday(); + my $t = POSIX::strftime("%b %e %H:%M:%S", localtime($s)); + return "$t.$us"; # millisecond precision +} + +# $self->_logger_prefix() +# Format a prefix for logging with printf(3)-like sequences: +# %n: the object name +# %m: mailbox, either explicit named or selected +sub _logger_prefix($) { + my $self = shift; + my $format = $self->{'logger-prefix'} // return ""; + + my %seq = ( "%" => "%", m => $self->{mailbox}, n => $self->{name} ); + $seq{m} //= $self->{_SELECTED} // die + if defined $self->{_STATE} and $self->{_STATE} eq 'SELECTED'; + + do {} while + # rewrite conditionals (loop because of nesting) + $format =~ s#%\? ([[:alpha:]]) \? + ( (?: (?> (?: [^%&?\\] | %[^?] | \\[&?\\] )+ ) | (?R) )* ) + (?: \& ( (?: (?> (?: [^%&?\\] | %[^?] | \\[&?\\] )+ ) | (?R) )*) )? + \?# ($seq{$1} // "") ne "" ? $2 : ($3 // "") #agex; + + $format =~ s#\\([&?\\])#$1#g; # unescape remaining '&', '?' and '\' + $format =~ s#%([%mn])# $seq{$1} #ge; + return $format; } @@ -1678,6 +1711,7 @@ sub _start_ssl($$) { } @$self{qw/_SSL _SSL_CTX/} = ($ssl, $ctx); + undef $self; # the verify callback has reference to $self, free it now } @@ -1710,8 +1744,9 @@ sub _getline($;$) { $self->{_OUTRAWCOUNT} += $n; if (defined (my $i = $self->{_Z_INFLATE})) { - $i->inflate($buf, $self->{_OUTBUF}) == Z_OK or - $self->panic("Inflation failed: ", $i->msg()); + my $r = $i->inflate($buf, $self->{_OUTBUF}); + $self->panic("Inflation failed: $r ", $i->msg()) + unless $r == Z_OK or $r == Z_STREAM_END; } else { $self->{_OUTBUF} = $buf; @@ -1828,8 +1863,8 @@ sub _cmd_extend($$) { $self->_cmd_extend_($args); } else { - # server supports LITERAL+: flush the command before each - # literal + # server doesn't supports LITERAL+: flush the command before + # each literal my ($offset, $litlen) = (0, 0); while ( (my $idx = index($$args, "\n", $offset+$litlen)) >= 0 ) { my $line = substr($$args, $offset, $idx+1-$offset); @@ -2,7 +2,7 @@ #---------------------------------------------------------------------- # Pull mails from an IMAP mailbox and deliver them to an SMTP session -# Copyright © 2016-2018 Guilhem Moulin <guilhem@fripost.org> +# Copyright © 2016-2019 Guilhem Moulin <guilhem@fripost.org> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -52,7 +52,7 @@ sub usage(;$) { exit $rv; } -usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q debug help|h idle:i no-delivery/); +usage(1) unless GetOptions(\%CONFIG, qw/config=s quiet|q debug+ help|h idle:i no-delivery/); usage(0) if $CONFIG{help}; usage(1) unless $#ARGV == 0 and $ARGV[0] ne '_'; @@ -233,7 +233,8 @@ sub smtp_send(@) { my $IMAP = do { my %config = (%$CONF, %CONFIG{qw/quiet debug/}, name => $ARGV[0]); $config{keepalive} = 1 if defined $CONFIG{idle}; - $config{'logger-fd'} = \*STDERR if $CONFIG{debug}; + $config{'logger-prefix'} = "%?n?%?m?%n(%m)&%n?: ?"; + delete $config{mailbox}; # use SELECTed mailbox in log messages Net::IMAP::InterIMAP::->new( %config ); }; diff --git a/tests/01-rename-exists-db/run b/tests/01-rename-exists-db/run index 29cb075..aad7c44 100644 --- a/tests/01-rename-exists-db/run +++ b/tests/01-rename-exists-db/run @@ -9,6 +9,6 @@ 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" +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/run b/tests/01-rename-exists-local/run index 17d8fcc..d82a0a4 100644 --- a/tests/01-rename-exists-local/run +++ b/tests/01-rename-exists-local/run @@ -8,6 +8,6 @@ check_mailbox_list 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" +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/run b/tests/01-rename-exists-remote/run index c867a77..28af1fc 100644 --- a/tests/01-rename-exists-remote/run +++ b/tests/01-rename-exists-remote/run @@ -8,6 +8,6 @@ check_mailbox_list 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" +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/03-sync-mailbox-list/run b/tests/03-sync-mailbox-list/run index e9fda06..b506204 100644 --- a/tests/03-sync-mailbox-list/run +++ b/tests/03-sync-mailbox-list/run @@ -24,7 +24,7 @@ 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" +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 @@ -33,7 +33,7 @@ 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" +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 diff --git a/tests/05-repair/run b/tests/05-repair/run index 15553e0..66f9ce9 100644 --- a/tests/05-repair/run +++ b/tests/05-repair/run @@ -77,10 +77,10 @@ xcgrep 5 '^WARNING: Conflicting flag update in foo\.bar ' <"$STDERR" 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\. Downloading remote UID [0-9]+ again\.$' <"$STDERR" +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\. Downloading local UID [0-9]+ again\.$' <"$STDERR" +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" @@ -92,7 +92,7 @@ xgrep -E '^remote\(foo~bar\): Updated flags \(\\Answered \\Seen\) for UID 8$' < 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]+\. Downloading again\.' <"$STDERR" +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" |