aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2019-11-07 20:54:08 +0100
committerGuilhem Moulin <guilhem@fripost.org>2019-11-07 20:54:08 +0100
commita8c3a40c6cb3d05115c0213243edff52ba5f3dcf (patch)
tree21546e3a77eac185c614989b4c2535face1f17c2
parent590bf57446967d897ee8327c8b2df57b77f4744e (diff)
parenta4a371234215a7705f304875cc8af067bf3142af (diff)
Merge branch 'master' into debian
-rw-r--r--Changelog23
-rw-r--r--Makefile4
-rw-r--r--doc/build.md18
-rw-r--r--doc/development.md18
-rw-r--r--doc/index.md4
-rw-r--r--doc/interimap.1.md55
-rw-r--r--doc/pullimap.1.md9
-rw-r--r--doc/template.html41
-rwxr-xr-xinterimap124
-rw-r--r--interimap.sample11
-rw-r--r--lib/Net/IMAP/InterIMAP.pm109
-rwxr-xr-xpullimap7
-rw-r--r--tests/01-rename-exists-db/run2
-rw-r--r--tests/01-rename-exists-local/run2
-rw-r--r--tests/01-rename-exists-remote/run2
-rw-r--r--tests/03-sync-mailbox-list/run4
-rw-r--r--tests/05-repair/run6
17 files changed, 278 insertions, 161 deletions
diff --git a/Changelog b/Changelog
index 4cc66ba..48481dd 100644
--- a/Changelog
+++ b/Changelog
@@ -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
diff --git a/Makefile b/Makefile
index fda0fe0..4fc759f 100644
--- a/Makefile
+++ b/Makefile
@@ -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">&copy;</a>
+ <a href="https://git.guilhem.org/interimap/plain/COPYING">Copyright</a> &copy;
$for(author)$$author$$sep$, $endfor$
$endif$
</div>
diff --git a/interimap b/interimap
index 7054f88..87c3a64 100755
--- a/interimap
+++ b/interimap
@@ -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);
diff --git a/pullimap b/pullimap
index e1c96e8..dcbe59b 100755
--- a/pullimap
+++ b/pullimap
@@ -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"