Compare commits

..

No commits in common. "filter" and "v0.4" have entirely different histories.
filter ... v0.4

56 changed files with 2984 additions and 17178 deletions

32
.gitignore vendored
View file

@ -1,32 +0,0 @@
/.autoconf_trace
/ChangeLog
/INSTALL
/autom4te.cache/
/aclocal.m4
/autodefs.h
/autodefs.h.in
/autodefs.h.in~
/build-stamp
/compile
/config.cache
/config.guess
/config.log
/config.status
/config.sub
/configure
/configure.lineno
/configure-stamp
/cov-int/
/depcomp
/install-sh
/isync.spec
/isync-*.tar.gz
/isync-*.tar.gz.asc
/missing
/patch-stamp
/stamp-h
/stamp-h.in
/stamp-h1
Makefile
Makefile.in

18
AUTHORS
View file

@ -1,17 +1 @@
Oswald Buddenhagen <ossi@users.sf.net>
* Contributor, current maintainer
Theodore Ts'o <tytso@mit.edu>
* Contributor, Debian package co-maintainer
Nicolas Boullis <nboullis@debian.org>
* Debian package maintainer and minor upstream contributions
Michael Elkins <me@mutt.org>
* Original author
Send questions and bug reports to the isync-devel@lists.sourceforge.net
mailing list.
_DON'T_ report bugs to Michael, not even in a CC: - he is not actively
involved in isync development any more.
Michael R. Elkins <me@mutt.org>

158
ChangeLog Normal file
View file

@ -0,0 +1,158 @@
2000-12-31 Michael Elkins <me@sigipe.org>
* sync.c:
display how many messages were fetched from the server
* imap.c:
fixed compilation error with no libssl support ("lorenzo martignoni"
<lorenzo.martignoni@technologist.com>)
2000-12-28 Michael Elkins <me@sigipe.org>
* main.c:
fixed config parser to accept arbitrary whitespace
2000-12-27 Michael Elkins <me@sigipe.org>
* imap.c:
use imap_close to terminate a connection in imap_open()
* imap.c, isync.1, isync.h, maildir.c, main.c:
allow leading whitespace in config files
now possible to sync multiple mailboxes by specifying multiple aliases on
the command line. IMAP connections are reused if possible.
don't initialize ssl unless we are going to use it.
2000-12-23 Michael Elkins <me@sigipe.org>
* imap.c, isync.h:
don't use NAMESPACE unless the server supports it
* Makefile.am, README, cram.c, imap.c, isync.h:
added CRAM-MD5 authentication support.
parse server capability string to determine if STARTTLS is available
2000-12-22 Michael Elkins <me@sigipe.org>
* README, imap.c, isync.1, isync.h, main.c:
isync-brokenservers.diff (Jeremy Katz <katzj@linuxpower.org>)
adds support for disabling NAMESPACE, and disable various flavors of TLS/SSL
for use with some broken IMAP servers.
* imap.c, sync.c:
prompt user if they wish to continue if the server's X.509 certificate can't
be verified.
sync_mailbox should consider uid == 0 to be "unknown"
* main.c, sync.c:
fixed sync_mailbox() to correctly write new messages to the local maildir
box (Thomas Roessler <roessler@does-not-exist.org>)
* main.c: set default MaxSize to 0 (unlimited)
invert test for password being set after getpass() call (Magnus Jonsson
<bigfoot@acc.umu.se>)
* ChangeLog, NEWS, configure.in, imap.c, isync.1, isync.h, maildir.c, main.c, sample.isyncrc, sync.c:
added MaxSize configuration variable
fixed --fast to work robustly without relying on the \Recent flag in
messages
2000-12-21 Michael Elkins <me@sigipe.org>
* imap.c, isync.h, maildir.c, sync.c:
RFC822.PEEK is obsolete in RFC2060. Use BODY.PEEK[] instead, which does
the same thing
keep track of the uidvalidity so isync can detect if the mailbox on the
server has changed since the last sync.
* NEWS: updated NEWS for 0.3 release
* Makefile.am, isync.spec:
added support for building RPMS
* Makefile.am, isync.1:
added target for creating html version of the man page
documented the imaps: prefix to the Host command
* imap.c, sync.c:
can't assume flag order when fetching a message. just search for the
first `{' to find the message size.
* isync.1, sync.c:
added BUGS section to manpage detailing the fact that we break the
maildir(5) spec by parsing the filename
change message delivery to use the method described in maildir(5)
* configure.in, main.c, sync.c:
use getpass() to get the user's password
unlink the temp file if we are unable to fetch a new message from the
server.
update version to 0.3
* isync.1: fixed typo in man page for --verbose option
* Makefile.am, README, TODO, imap.c, isync.h, list.c:
added generic IMAP list parser and rewrote imap_exec() to handle
arbitrary data instead of hardcoded
* Makefile.am, README, configure.in, main.c:
fixes to compile cleanly under Solaris 2.7
* configure.in, imap.c, isync.1, isync.h, main.c:
added OpenSSL support
* ChangeLog, configure.in, main.c:
config options were not case insensitive
* imap.c, isync.h, maildir.c, main.c, sync.c:
don't fetch deleted messages when expunging
display number of messages that are to be deleted
flags for \Recent messages were not properly fetched
local messages with updated flags were not corrected renamed
2000-12-20 Michael Elkins <me@sigipe.org>
* ChangeLog, Makefile.am:
updated ChangeLog
added log: rule in Makefile.am
* configure: forgot to remove configure script
* INSTALL, Makefile.in, aclocal.m4, autogen.sh, install-sh, missing, mkinstalldirs:
added autogen.sh to regenerate the build environment
* COPYING, INSTALL, install-sh, missing, mkinstalldirs:
added missing files
* isync.1, sample.isyncrc: New file.
* isync.1, sample.isyncrc:
initial import
* TODO, configure, imap.c, maildir.c, sync.c:
New file.
* TODO, configure, imap.c, maildir.c, sync.c:
initial import
* AUTHORS, ChangeLog, INSTALL, Makefile.am, Makefile.in, NEWS, README, aclocal.m4, configure.in, isync.h, main.c:
New file.
* AUTHORS, ChangeLog, INSTALL, Makefile.am, Makefile.in, NEWS, README, aclocal.m4, configure.in, isync.h, main.c:
initial import

View file

@ -1,41 +0,0 @@
#FROM debian:bookworm-20231030-slim
FROM debian:bullseye-20220801
# need to add
# removed
# libsasl2-modules \
# ca-certificates \
# version pinning is being handled in our from line
# hadolint ignore=DL3008
RUN true && \
apt-get update && \
groupadd --gid 1000 user && \
useradd -m --home-dir /home/user --shell /bin/sh --uid 1000 --gid 1000 user && \
apt-get install -y --no-install-recommends \
libsasl2-2 \
libsasl2-dev \
perl \
libdatetime-format-dateparse-perl \
autoconf \
automake \
zlib1g-dev \
libdb-dev \
libsasl2-dev \
libssl-dev \
gcc \
make \
git \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
true
# Built with
# apt install build-essential dh-autoreconf git
# apt install libssl-dev
# apt install zlib1g-dev
# apt install libsasl2-dev
WORKDIR /home/user
USER user

View file

@ -1,80 +1,16 @@
SUBDIRS = src
bin_SCRIPTS = mbsync-get-cert
EXTRA_DIST = debian isync.spec $(bin_SCRIPTS)
LOG_PL = \
use POSIX qw(strftime); \
use Date::Parse; \
use Text::Wrap; \
$$Text::Wrap::columns = 72; \
while (defined($$_ = <>)) { \
/^commit / or die "commit missing: $$_"; \
<> =~ /^log size (\d+)$$/ or die "wrong size"; \
$$len = $$1; \
read(STDIN, $$log, $$len) == $$len or die "unexpected EOF"; \
$$log =~ s/^Author: ([^>]+>)\nDate: (\d{4}-\d\d-\d\d \d\d:\d\d:\d\d [-+]\d{4})\n(.*)$$/$$3/s or die "unexpected log format"; \
$$author = $$1; $$date = str2time($$2); \
scalar(<>); \
@files = (); \
$$pfx = ""; \
while (defined($$l = <>) and $$l ne "\n") { \
chomp $$l; \
next if ($$l =~ m,^(ChangeLog$$|NEWS$$|TODO$$|debian/),); \
if (!@files) { \
$$pfx = $$l; \
$$pfx =~ s,/?[^/]+$$,,; \
} else { \
while (length($$pfx)) { \
$$l =~ m,^\Q$$pfx/\E, and last; \
$$pfx =~ s,/?[^/]+$$,,; \
} \
} \
push @files, $$l; \
} \
next if (!@files); \
print strftime("%F %H:%M", gmtime($$date))." ".$$author."\n\n"; \
if (@files > 1 and ($$len = length($$pfx))) { \
@efiles = (); \
for $$f (@files) { push @efiles, substr($$f, $$len + 1); } \
$$fstr = $$pfx."/: "; \
} else { \
@efiles = @files; \
$$fstr = ""; \
} \
print wrap("\t* ", "\t ", $$fstr.join(", ", @efiles).":")."\n"; \
$$log =~ s, +$$,,gm; \
$$log =~ s,^ ,\t,gm; \
print $$log."\n"; \
}
$(srcdir)/.git/index:
$(srcdir)/ChangeLog: $(srcdir)/.git/index
$(MAKE) log
bin_PROGRAMS=isync
isync_SOURCES=main.c imap.c sync.c maildir.c isync.h list.c cram.c
man_MANS=isync.1
EXTRA_DIST=sample.isyncrc $(man_MANS)
CPPFLAGS=$(RPM_OPT_FLAGS)
log:
@test -z "$(srcdir)" || cd $(srcdir) && \
( ! test -d .git || \
git log --pretty=medium --date=iso --log-size --name-only --no-merges | \
perl -e '$(LOG_PL)' > ChangeLog )
rcs2log -h sigipe.org | sed 's;/home/cvs/isync/;;g' > ChangeLog
cov-scan: clean
/opt/cov-analysis-*/bin/cov-build --dir cov-int $(MAKE)
tar cavf isync-cov.tar.xz cov-int
isync.html: isync.1
groff -Thtml -man isync.1 > isync.html
deb:
CFLAGS= INSTALL= dpkg-buildpackage -b --no-sign
dist-hook:
find $(distdir)/debian \( -name .#\*# -o -type l \) -print0 | xargs -0r rm -rf
-cd $(distdir)/debian && test -f .gitignore && rm -rf `cut -c2- .gitignore` .gitignore
dist-sign: dist
gpg -b -a $(PACKAGE)-$(VERSION).tar.gz
rpm: dist
CFLAGS="-O2 -mtune=core2" rpmbuild --clean -ta $(PACKAGE)-$(VERSION).tar.gz
rpm-ia32: dist
CFLAGS="-O2 -m32 -march=i686" rpmbuild --target i686-unknown-linux --clean -ta $(PACKAGE)-$(VERSION).tar.gz
doc_DATA = README TODO NEWS ChangeLog AUTHORS
rpm:
make dist
cp isync-$(VERSION).tar.gz /usr/src/RPM/SOURCES
rpm -ba --target=i586 --clean isync.spec

162
NEWS
View file

@ -1,165 +1,3 @@
[1.4.0]
The 'isync' compatibility wrapper was removed.
Added support for disabling TLS v1.3 - adjust SSLVersions if you set it.
Removed support for obsolete/insecure SSL v3.
The IMAP '$Forwarded' / Maildir 'P' (passed) flag is supported now.
Support for configuring a TLS cipher string was added.
IMAP mailbox subscriptions are supported now.
The IMAP user query can be scripted now.
Added built-in support for macOS Keychain.
Messages excluded by MaxSize will now result in placeholders.
The use of Master/Slave terminology has been deprecated.
[1.3.0]
Network timeout handling has been added.
Support for proper Maildir++ and a Maildir sub-folder naming style
without extra dots have been added.
Support for TLS client certificates was added.
Support for recovering from baseless UID validity changes was added.
The get-cert script was renamed to mbsync-get-cert.
[1.2.0]
The 'isync' compatibility wrapper is now deprecated.
An IMAP Path/NAMESPACE rooted in INBOX won't be handled specially any more.
This means that some Patterns may need adjustment.
The default output is a lot less verbose now.
The meanings of the -V and -D options changed significantly.
The SSL/TLS configuration has been re-designed.
SSL is now explicitly enabled or disabled - "use SSL if available" is gone.
Notice: Tunnels are assumed to be secure and thus default to no SSL.
Support for SASL (flexible authentication) has been added.
Support for Windows file systems has been added.
Support for compressed data transfer has been added.
Folder deletions can be propagated now.
[1.1.0]
Support for hierarchical mailboxes in Patterns.
Full support for IMAP pipelining (streaming, parallelization) added.
This is considerably faster especially with high-latency networks.
Faster and hopefully more reliable support for IMAP servers without the
UIDPLUS extension (e.g., M$ Exchange).
More automatic handling of SSL certificates.
IPv6 support.
IMAP password query can be scripted.
Message arrival dates can be propagated.
Data safety in case of system crashes was improved.
MaxMessages was made vastly more useful.
[1.0.0]
Essentially a rewrite. Synchronization state storage concept, configuration
and command line changed entirely.
But you needn't to worry about the upgrade, as a fully automated migration
path is provided, even for users of isync 0.7 and below.
Still, you should re-read the manual to be able to take full advantage of the
new features:
The supported mailbox types can be freely paired.
A possible application of this is using a local IMAP server to access
mailboxes that are not natively supported yet.
Message deletions (expunges) are now propagated both ways, so there is no need
for using mutt with maildir_trash any more.
Additional trash options added.
`OneToOne' replaced by something more flexible.
Partial support for IMAP pipelining (streaming, parallelization) added.
Makes flag change propagation much faster - this affects every message that
becomes Seen/Read.
[0.9]
Added Tunnel directive to allow the user to specify a shell command to run
to set up an IMAP connection in place of a TCP socket (eg., to run over
an SSH session).
Added PREAUTH support (useful mostly in conjunction with Tunnel).
Messages marked deleted are not uploaded when we are going to expunge.
Locally generated messages are not re-fetched after uploading even if the
UIDPLUS extension is not supported by the server.
Added `OneToOne' configuration option: ignore any Mailbox specifications
and instead pick up all mailboxes from the local MailDir and remote Folder
and map them 1:1 onto each other according to their names.
-C now creates both local and remote boxes; -L and -R create only local/remote.
--quiet is now really quiet.
[0.8]
!!! IMPORTANT !!!
In order to fix the problem where messages copied from one mailbox to
another were not uploaded to the new mailbox, the way Isync stores the UID
for each message needed to be changed. As a result, you _MUST_ delete all
the messages in the local maildir box before using this version. Otherwise
it will upload every message to the server thinking its a new mail.
[0.7]
Added `MaxMessages' configuration option to allow tracking of only the most
recently added message in the local mailbox.
Added --create (-C) command line option to force creation of the local
maildir-style mailbox if it doesn't already exist.
[0.6]
Added `Delete' configuration option to correspond to the -d command line
option.
Added -a (--all) command line option to synchronize all mailboxes.
[0.5]
Updated SSL support.
Added CRAM authentication support.
Added MailDir configuration option to specify the default location of local
mailboxes when relative paths are used.
Added support for uploading local messages to the IMAP server.
Added CopyDeletedTo configuration option to cause isync to move deleted
messages to a particular mailbox on the server when they are expunged.
[0.4]
Added MaxSize configuration option to limit downloading of new messages from

82
README
View file

@ -4,75 +4,49 @@
| \__ \ |_| | | | | (__
|_|___/\__, |_| |_|\___|
|___/
isync/mbsync - free (GPL) mailbox synchronization program
http://isync.sf.net/
isync - IMAP4 to maildir mailbox synchronization program
http://www.sigpipe.org/isync/
See AUTHORS for contact information.
Author: Michael Elkins <me@mutt.org>
``mbsync'' is a command line application which synchronizes mailboxes;
currently Maildir and IMAP4 mailboxes are supported. New messages, message
deletions and flag changes can be propagated both ways.
``mbsync'' is suitable for use in IMAP-disconnected mode.
``isync'' is a command line application which synchronizes a local
maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in
IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailbox can be
maintained, and all flags are synchronized.
Synchronization is based on unique message identifiers (UIDs), so
no identification conflicts can occur (unlike with some other mail
synchronizers).
Synchronization state is kept in one local text file per mailbox pair;
these files are protected against concurrent ``mbsync'' processes.
Mailboxes can be safely modified while ``mbsync'' operates.
Multiple replicas of each mailbox can be maintained.
* Features:
isync is the project name, while mbsync is the current executable name; this
change was necessary because of massive changes in the user interface.
* Features
* Fine-grained selection of synchronization operations to perform
* Synchronizes single mailboxes or entire mailbox collections
* Partial mirrors possible: keep only the latest messages locally
* Trash functionality: backup messages before removing them
* IMAP features:
* Supports TLS/SSL via imaps: (port 993) and STARTTLS
* Supports SASL for authentication
* Pipelining for maximum speed
* Fast mode for fetching new mail only
* Supports imaps: (port 993) TLS/SSL connections
* Supports STARTTLS (RFC2595) for confidentiality
* Supports NAMESPACE (RFC2342)
* Supports CRAM-MD5 (RFC2095) for authentication
* Compatibility
isync should work fairly well with any IMAP4 compliant server;
servers that support the UIDPLUS and LITERAL+ extensions are most
efficient.
``isync'' has been tested with the following IMAP servers:
Courier 1.4.3 is known to be buggy, version 1.7.3 works fine.
M$ Exchange (2013 at least) needs DisableExtension MOVE to be compatible
with the Trash functionality.
* Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0
* Courier-IMAP 1.2.3
* WU-IMAP 2000
* Platforms
At some point, ``isync'' has successfully run on:
Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3.
``isync'' has successfully be compiled under:
* Linux 2.2.18
* Solaris 2.7
* OpenBSD 2.8
* Requirements
perl v5.14+
Berkeley DB 4.1+ (optional)
OpenSSL for TLS/SSL support (optional)
Cyrus SASL (optional)
zlib (optional)
OpenSSL for TLS/SSL support (optional)
The build from git also requires:
* INSTALLING
GNU autotools (autoconf & automake)
perl module Date::Parse (libtimedate-perl on Debian, perl-TimeDate on
Fedora and Suse)
./configure
make install
* Installation
* HELP
./autogen.sh (only when building from git)
./configure
make
sudo make install
* Help
Please see the man page for complete documentation.
Please see the man page for complete documentation.

87
TODO
View file

@ -1,86 +1,3 @@
f{,data}sync() usage could be optimized by batching the calls.
add upload support to mirror local msgs on the server
make SSL (connect) timeouts produce a bit more than "Unidentified socket error".
automatically resume upon transient errors, e.g. "connection reset by peer"
or timeout after some data was already transmitted.
possibly also try to handle Exchange's "glitches" somehow.
add support for IMAP UTF-7 (for internationalized mailbox names).
uidvalidity lock timeout handling would be a good idea.
should complain when multiple Channels match the same folders.
propagate folder deletions even when the folders are non-empty.
- verify that "most" of the folders in the Channel are still there.
- refuse to delete unpropagated messages when trashing on the remote side.
- refuse to delete far side if it has unpropagated messages. symmetry?
add message expiration based on arrival date (message date would be too
unreliable). MaxAge; probably mutually exclusive to MaxMessages.
add alternative treatments of expired messages. ExpiredMessageMode: Prune
(delete messages like now), Keep (just don't sync) and Archive (move to
separate folder - ArchiveSuffix, default .archive).
add support for event notification callbacks.
it would be also possible to report more differentiated exit codes, but
that seems too limiting in the general case.
make it possible to have different mailbox names for far and near side in
Patterns.
- use far:near for the pattern
- for quoting, use more colons: the longest sequence of colons is the
separator
- this makes Groups mostly useless, as they are mostly a workaround for this
function being missing so far
- this is needed for move detection, which would work only within one Channel
add regexp-based mailbox path rewriting to the drivers. user would provide
expressions for both directions. every transformation would be immediately
verified with the inverse transform. PathDelimiter and Flatten would become
special cases of this.
add daemon mode. primary goal: keep imap password in memory.
also: idling mode.
parallel fetching of multiple mailboxes.
TLS session resumption becomes interesting then as well.
imap_set_flags(): group commands for efficiency, don't call back until
imap_commit().
add streaming from fetching to storing.
handle custom flags (keywords).
make use of IMAP CONDSTORE extension (rfc4551; CHANGEDSINCE FETCH Modifier);
make use of IMAP QRESYNC extension (rfc5162) to avoid SEARCH to find vanished
messages.
use MULTIAPPEND and FETCH with multiple messages.
dummy messages resulting from MaxSize should contain a dump of the original
message's MIME structure and its (reasonably sized) text parts.
don't SELECT boxes unless really needed; in particular not for appending,
and in write-only mode not before changes are made.
problem: UIDVALIDITY change detection is delayed, significantly complicating
matters.
some error messages are unhelpful in non-verbose mode due to missing context.
possibly use ^[[1m to highlight error messages.
consider alternative approach to trashing: instead of the current trash-before-
expunge done by mbsync, let MUAs do the trashing (as modern ones typically do).
mbsync wouldn't do any trashing by itself, but should track the moves for
optimization. additionally, there should be a mode to move trashed messages to
the remote store. TrashMode Internal|External, AbsorbRemoteTrash.
a yet different approach to trashing is treating the trash like a normal mailbox.
however, this implies a huge working set.
consider optional use of messages-id (and X-GM-MSGID):
- detection of message moves between folders
- recovery from loss of sync state, migration from other tools
add support for syncing with other: and shared: via NAMESPACE

View file

@ -1,35 +0,0 @@
# Add --enable-maintainer-mode option to configure.
# From Jim Meyering
# Change it to enable maintainer mode by default by Nicolas Boullis.
# Copyright 1996, 1998, 2000, 2001, 2002 Free Software Foundation, Inc.
# Copyright 2004 Nicolas Boullis.
# 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 2, 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
AC_DEFUN([AM_MAINTAINER_MODE],
[AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles])
dnl maintainer-mode is enabled by default
AC_ARG_ENABLE(maintainer-mode,
[ --disable-maintainer-mode disable make rules and dependencies not useful
(and sometimes confusing) to the casual installer],
USE_MAINTAINER_MODE=$enableval,
USE_MAINTAINER_MODE=yes)
AC_MSG_RESULT([$USE_MAINTAINER_MODE])
AM_CONDITIONAL(MAINTAINER_MODE, [test $USE_MAINTAINER_MODE = yes])
MAINT=$MAINTAINER_MODE_TRUE
AC_SUBST(MAINT)dnl
]
)

View file

@ -1,4 +1,15 @@
#! /bin/sh
set -e -v
make -f Makefile.am log
autoreconf -f -i
#!/bin/sh
# $Id$
aclocal
if test $? -ne 0; then
exit
fi
automake --add-missing
if test $? -ne 0; then
exit
fi
autoconf
if test $? -ne 0; then
exit
fi
./configure $@

3
build
View file

@ -1,3 +0,0 @@
#!/bin/sh
# Note we assume this is run with podman. Take -u 0:0 out if using docker
docker run -u 0:0 -it --rm -v $(pwd):/make -w /make isync-build make

View file

@ -1,280 +0,0 @@
AC_INIT([isync], [1.4.4])
AC_CONFIG_HEADERS([autodefs.h])
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE
AM_MAINTAINER_MODE
AC_PROG_CC
if test "$GCC" = yes; then
warnings="
-Wall -Wextra
-Wshadow
-Wcast-qual
-Wformat=2 -Wformat-signedness -Wformat-nonliteral
-Wstrict-prototypes
-Wno-overlength-strings
"
CFLAGS="$CFLAGS -pipe -std=c11 -pedantic $(echo $warnings)"
fi
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
void fkt(void)
{
int a = 42; // c99 comment
for (int i = 0; i < a; i++) {} // declaration inside for()
int b; // declaration after code
}
// c11 anonymous structs/unions
struct base {
int a;
};
union deriv {
struct base gen;
struct {
int a;
int b;
};
};
])], , [AC_MSG_ERROR([compiler does not support required C11 features])])
CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
AC_CHECK_PROG(PERL, perl, perl)
if test "x$PERL" = "x"; then
AC_MSG_ERROR([perl not found])
fi
need_perl=5.14
AC_CACHE_CHECK([whether perl is recent enough], ob_cv_perl_ver, [
if $PERL -e "use v$need_perl;" 2> /dev/null; then
ob_cv_perl_ver=yes
else
ob_cv_perl_ver=no
fi
])
if test "x$ob_cv_perl_ver" = "xno"; then
AC_MSG_ERROR([perl is too old, need v$need_perl])
fi
AC_CACHE_CHECK([whether strftime supports %z], ob_cv_strftime_z,
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <time.h>
#include <string.h>
int main(void)
{
time_t t = 0;
char buf[32];
strftime(buf, sizeof(buf), "%z", localtime(&t));
return !(buf[0] == '+' || buf[0] == '-');
}
]])], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])])
if test "x$ob_cv_strftime_z" = x"no"; then
AC_MSG_ERROR([libc lacks necessary feature])
fi
AC_CHECK_HEADERS(poll.h sys/select.h)
AC_CHECK_FUNCS(vasprintf strnlen memrchr timegm)
AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"])
AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"])
AC_SUBST(SOCK_LIBS)
have_ipv6=true
sav_LIBS=$LIBS
LIBS="$LIBS $SOCK_LIBS"
AC_CHECK_FUNCS(getaddrinfo inet_ntop, , [have_ipv6=false])
LIBS=$sav_LIBS
if $have_ipv6; then
AC_DEFINE(HAVE_IPV6, 1, [if your libc has IPv6 support])
fi
have_ssl_paths=
AC_ARG_WITH(ssl,
AS_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]),
[ob_cv_with_ssl=$withval])
if test "x$ob_cv_with_ssl" != xno; then
case $ob_cv_with_ssl in
""|yes)
dnl Detect the pkg-config tool, as it may have extra info about the openssl
dnl installation we can use. I *believe* this is what we are expected to do
dnl on really recent Redhat Linux hosts.
PKG_PROG_PKG_CONFIG
if test "x$PKG_CONFIG" != "x" ; then
AC_MSG_CHECKING([OpenSSL presence with pkg-config])
if $PKG_CONFIG --exists openssl; then
SSL_LIBS=`$PKG_CONFIG --libs-only-l openssl`
SSL_LDFLAGS=`$PKG_CONFIG --libs-only-L openssl`
SSL_CPPFLAGS=`$PKG_CONFIG --cflags-only-I openssl`
have_ssl_paths=yes
AC_MSG_RESULT([found])
else
AC_MSG_RESULT([not found])
fi
fi
;;
*)
SSL_LDFLAGS=-L$ob_cv_with_ssl/lib$libsuff
SSL_CPPFLAGS=-I$ob_cv_with_ssl/include
;;
esac
if test -z "$have_ssl_paths"; then
sav_LDFLAGS=$LDFLAGS
LDFLAGS="$LDFLAGS $SSL_LDFLAGS"
AC_CHECK_LIB(dl, dlopen, [LIBDL=-ldl])
AC_CHECK_LIB(crypto, X509_cmp, [LIBCRYPTO=-lcrypto])
AC_CHECK_LIB(ssl, SSL_connect,
[SSL_LIBS="-lssl $LIBCRYPTO $LIBDL" have_ssl_paths=yes])
LDFLAGS=$sav_LDFLAGS
fi
sav_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS"
AC_CHECK_HEADER(openssl/ssl.h, , [have_ssl_paths=])
CPPFLAGS=$sav_CPPFLAGS
if test -z "$have_ssl_paths"; then
if test -n "$ob_cv_with_ssl"; then
AC_MSG_ERROR([OpenSSL libs and/or includes were not found where specified])
fi
else
AC_DEFINE(HAVE_LIBSSL, 1, [if you have the OpenSSL libraries])
CPPFLAGS="$CPPFLAGS $SSL_CPPFLAGS"
LDFLAGS="$LDFLAGS $SSL_LDFLAGS"
fi
fi
AC_SUBST(SSL_LIBS)
have_sasl_paths=
AC_ARG_WITH(sasl,
AS_HELP_STRING([--with-sasl[=PATH]], [where to look for SASL [detect]]),
[ob_cv_with_sasl=$withval])
if test "x$ob_cv_with_sasl" != xno; then
case $ob_cv_with_sasl in
""|yes)
dnl FIXME: Try various possible paths here...
;;
*)
SASL_LDFLAGS=-L$ob_cv_with_sasl/lib$libsuff
SASL_CPPFLAGS=-I$ob_cv_with_sasl/include
;;
esac
if test -z "$have_sasl_paths"; then
sav_LDFLAGS=$LDFLAGS
LDFLAGS="$LDFLAGS $SASL_LDFLAGS"
AC_CHECK_LIB(sasl2, sasl_client_init,
[SASL_LIBS="-lsasl2" have_sasl_paths=yes])
LDFLAGS=$sav_LDFLAGS
fi
sav_CPPFLAGS=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $SASL_CPPFLAGS"
AC_CHECK_HEADER(sasl/sasl.h, , [have_sasl_paths=])
CPPFLAGS=$sav_CPPFLAGS
if test -z "$have_sasl_paths"; then
if test -n "$ob_cv_with_sasl"; then
AC_MSG_ERROR([SASL libs and/or includes were not found where specified])
fi
else
AC_DEFINE(HAVE_LIBSASL, 1, [if you have the SASL libraries])
CPPFLAGS="$CPPFLAGS $SASL_CPPFLAGS"
LDFLAGS="$LDFLAGS $SASL_LDFLAGS"
fi
fi
AC_SUBST(SASL_LIBS)
AC_CACHE_CHECK([for Berkeley DB >= 4.1], ac_cv_berkdb4,
[ac_cv_berkdb4=no
sav_LIBS=$LIBS
LIBS="$LIBS -ldb"
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[#include <db.h>],
[DB *db;
db_create(&db, 0, 0);
db->truncate(db, 0, 0, 0);
db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0);
])], [ac_cv_berkdb4=yes], [])
LIBS=$sav_LIBS
])
if test "x$ac_cv_berkdb4" = xyes; then
AC_SUBST([DB_LIBS], ["-ldb"])
AC_DEFINE(USE_DB, 1, [if Berkeley DB should be used])
fi
have_zlib=
AC_ARG_WITH(zlib,
AS_HELP_STRING([--with-zlib], [use zlib [detect]]),
[ob_cv_with_zlib=$withval])
if test "x$ob_cv_with_zlib" != xno; then
AC_CHECK_LIB([z], [deflate],
[AC_CHECK_HEADER(zlib.h,
[have_zlib=1
AC_SUBST([Z_LIBS], ["-lz"])
AC_DEFINE([HAVE_LIBZ], 1, [if you have the zlib library])]
)]
)
fi
AM_CONDITIONAL(with_mdconvert, test "x$ac_cv_berkdb4" = xyes)
case $target_os in
darwin*)
darwin=yes
;;
*)
darwin=no
;;
esac
AC_ARG_WITH(
macos-keychain,
[AS_HELP_STRING([--with-macos-keychain], [Support macOS keychain])],
[have_macos_keychain=$withval],
[have_macos_keychain=$darwin])
if test "x$have_macos_keychain" != xno; then
if test $darwin = no; then
AC_MSG_ERROR([Cannot use macOS Keychain outside macOS.])
fi
have_macos_keychain=yes
AC_DEFINE(HAVE_MACOS_KEYCHAIN, 1, [Define to 1 if you have the macOS Keychain Services API.])
AC_SUBST(KEYCHAIN_LIBS, ["-Wl,-framework,Security,-framework,CoreFoundation"])
fi
AC_CONFIG_FILES([Makefile src/Makefile isync.spec])
AC_OUTPUT
AC_MSG_RESULT()
if test -n "$have_ssl_paths"; then
AC_MSG_RESULT([Using SSL])
else
AC_MSG_RESULT([Not using SSL])
fi
if test -n "$have_sasl_paths"; then
AC_MSG_RESULT([Using SASL])
else
AC_MSG_RESULT([Not using SASL])
fi
if test -n "$have_zlib"; then
AC_MSG_RESULT([Using zlib])
else
AC_MSG_RESULT([Not using zlib])
fi
if test "x$ac_cv_berkdb4" = xyes; then
AC_MSG_RESULT([Using Berkeley DB])
else
AC_MSG_RESULT([Not using Berkeley DB])
fi
if test $darwin = yes; then
if test "x$have_macos_keychain" = xyes; then
AC_MSG_RESULT([Using macOS Keychain])
else
AC_MSG_RESULT([Not using macOS Keychain])
fi
fi
AC_MSG_RESULT()

13
configure.in Normal file
View file

@ -0,0 +1,13 @@
AC_INIT(isync.h)
AM_INIT_AUTOMAKE(isync,0.4)
AM_PROG_CC_STDC
if test $CC = gcc; then
CFLAGS="$CFLAGS -pipe"
fi
AC_CHECK_FUNCS(getopt_long)
AC_CHECK_LIB(socket,socket)
AC_CHECK_LIB(nsl,inet_ntoa)
AC_CHECK_LIB(crypto,ERR_error_string)
AC_CHECK_LIB(ssl,SSL_library_init)
CFLAGS="$CFLAGS -W -Wall -pedantic -Wmissing-prototypes -Wmissing-declarations"
AC_OUTPUT(Makefile)

85
cram.c Normal file
View file

@ -0,0 +1,85 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <assert.h>
#include "isync.h"
#if HAVE_LIBSSL
#include <openssl/hmac.h>
#define ENCODED_SIZE(n) (4*((n+2)/3))
static char
hexchar (unsigned int b)
{
if (b < 10)
return '0' + b;
return 'a' + (b - 10);
}
char *
cram (const char *challenge, const char *user, const char *pass)
{
HMAC_CTX hmac;
char hash[16];
char hex[33];
int i;
unsigned int hashlen = sizeof (hash);
char buf[256];
int len = strlen (challenge);
char *response = calloc (1, 1 + len);
char *final;
/* response will always be smaller than challenge because we are
* decoding.
*/
len = EVP_DecodeBlock ((unsigned char *) response, (unsigned char *) challenge, strlen (challenge));
// printf ("CRAM-MD5 challege is %s\n", response);
HMAC_Init (&hmac, (unsigned char *) pass, strlen (pass), EVP_md5 ());
HMAC_Update (&hmac, (unsigned char *) response, strlen(response));
HMAC_Final (&hmac, (unsigned char *) hash, &hashlen);
assert (hashlen == sizeof (hash));
free (response);
hex[32] = 0;
for (i = 0; i < 16; i++)
{
hex[2 * i] = hexchar ((hash[i] >> 4) & 0xf);
hex[2 * i + 1] = hexchar (hash[i] & 0xf);
}
snprintf (buf, sizeof (buf), "%s %s", user, hex);
// printf ("Response: %s\n", buf);
len = strlen (buf);
len = ENCODED_SIZE (len) + 1;
final = malloc (len);
final[len - 1] = 0;
assert (EVP_EncodeBlock ((unsigned char *) final, (unsigned char *) buf, strlen (buf)) == len - 1);
return final;
}
#endif

7
debian/.gitignore vendored
View file

@ -1,7 +0,0 @@
/.debhelper
/autoreconf.after
/autoreconf.before
/files
/isync
/isync.debhelper.log
/isync.substvars

13
debian/README.Debian vendored
View file

@ -1,13 +0,0 @@
A note from isync's web site:
isync can be integrated into Mutt fairly easily with a few hooks:
folder-hook ~A bind index $ <sync-mailbox>
folder-hook +maildir 'macro index $ "<sync-mailbox>!mbsync the_channel:maildir\n"'
where the_channel is the Channel used to sync this mailbox, and maildir is the
name of the local mailbox itself. This works well so long as you are not
modifying the IMAP mailbox outside of Mutt. However, if you are using another
mail program simultaneously, Mutt will have the wrong idea of the local mailbox
flags and messages will start disappearing from its index display (don't worry,
they are still on disk).

349
debian/changelog vendored
View file

@ -1,349 +0,0 @@
isync (1.2.3-0) unstable; urgency=low
* Upload to unstable (with urgency=low)
-- Oswald Buddenhagen <ossi@users.sf.net> Sun, 01 Oct 2017 12:12:12 +0000
isync (1.2.1-2) unstable; urgency=low
* Upload to unstable (with urgency=low)
* Don't call uupdate after uscan
* Import patch to fix build with OpenSSL 1.1 (Closes: #828357)
* Bump Standards-Version to 3.9.8 (no changes needed)
* Add pkg-config to Build-Depends
* Update Vcs-* URLs
* Fix spelling-error-in-binary
-- Alessandro Ghedini <ghedo@debian.org> Sat, 19 Nov 2016 17:14:42 +0000
isync (1.2.1-1) experimental; urgency=medium
[ Evgeni Golov ]
* New upstream release.
* Explicitly Build-Depend on zlib1g-dev
-- Alessandro Ghedini <ghedo@debian.org> Sat, 09 Jan 2016 12:56:39 +0000
isync (1.2.0-1) experimental; urgency=medium
* New upstream release
- Only show sync progress by default (Closes: #765052)
* Enable libsasl support
-- Alessandro Ghedini <ghedo@debian.org> Mon, 06 Apr 2015 13:42:24 +0200
isync (1.1.2-1) unstable; urgency=medium
* New upstream release
* Bump Standards-Version to 3.9.6 (no changes needed)
-- Alessandro Ghedini <ghedo@debian.org> Sun, 01 Feb 2015 20:42:25 +0100
isync (1.1.1-1) unstable; urgency=medium
* New upstream release
- Don't lie about the default of User (Closes: #744389)
- Don't forget to reset message counts when skipping scan (Closes: #744259)
- Rework maildir store mapping (Closes: #737708)
* Drop 01_fix-manpages.patch (merged upstream)
* Drop 02_fix-empty-folder-sync.patch (merged upstream)
-- Alessandro Ghedini <ghedo@debian.org> Tue, 03 Jun 2014 21:00:44 +0200
isync (1.1.0-2) unstable; urgency=medium
* Drop 02_fix-duplicate-changelog.patch
(rm the file after installation instead)
* Update 01_fix-manpages.patch
* Add 02_fix-empty-folder-sync.patch (Closes: #738873)
-- Alessandro Ghedini <ghedo@debian.org> Fri, 14 Feb 2014 20:41:49 +0100
isync (1.1.0-1) unstable; urgency=low
* New upstream release (Closes: #674403)
- Fix overlapping memcpy (Closes: #650373)
- Fix segfault while syncing mailboxes (Closes: #411120)
- Fix segfault when invoked with arguments without configuration
(Closes: #727239)
* Bump debhelper compat level, update Build-Depends
* Switch to short-form dh rules, remove useless files
* Switch to 3.0 (quilt) source format
* Remove empty patches/ directory
* Drop local source modifications
* Update short/long descriptions
* Add 01_fix-manpages.patch to fix manpage errors and typos
* Add Homepage field
* Update copyright file to Copyright-Format 1.0
* Add Vcs-* fields
* Add 02_fix-duplicate-changelog.patch to avoid duplicate changelog install
* Add myself to Uploaders
* Bump Standards-Version to 3.9.5 (no changes needed)
* Use dh-autoreconf instead of autotools-dev
-- Alessandro Ghedini <ghedo@debian.org> Sun, 12 Jan 2014 16:35:52 +0100
isync (1.0.4-2.2) unstable; urgency=low
* Non-maintainer upload.
* Apply upstream patch for CVE-2013-0289.
Fix incorrect server's SSL x509.v3 certificate validation when
performing IMAP synchronization. (Closes: #701052)
-- Salvatore Bonaccorso <carnil@debian.org> Sun, 24 Feb 2013 09:27:55 +0100
isync (1.0.4-2.1) unstable; urgency=low
* Non-maintainer upload.
* Drop debconf note that deals with a pre-Etch transition.
Closes: #492194
-- Christian Perrier <bubulle@debian.org> Sat, 25 Oct 2008 08:40:52 +0200
isync (1.0.4-2) unstable; urgency=low
* Change the libdb4.4-dev build-dependency to libdb-dev. Thanks Luk for
pointing this. (Closes: #499165)
-- Nicolas Boullis <nboullis@debian.org> Wed, 17 Sep 2008 23:58:58 +0200
isync (1.0.4-1) unstable; urgency=low
* The second "thanks Christian" release.
* New upstream release.
- Accept empty "* SEARCH" response. (Closes: #413336)
- Quote user name in generated config. (Closes: #456783)
* Explain the isync->mbsync change in the package description.
(Closes: #430648)
* Fix the debian/watch file that lacked the version and action fields.
* Disable the upstream "deb-clean" stuff in the top-level Makefile, as
in breaks cleaning the build directory.
* Bump Standards-Version to 3.7.3. (No change required.)
-- Nicolas Boullis <nboullis@debian.org> Sat, 03 May 2008 01:42:55 +0200
isync (1.0.3-3.1) unstable; urgency=low
* Non-maintainer upload to fix pending l10n issues.
* Debconf translations:
- Portuguese. Closes: #418283
- Italian. Closes: #418246
- Dutch. Closes: #422244
- Spanish. Closes: #426184
- Finnish. Closes: #468214
- Galician. Closes: #470529
* [Lintian] Do not include debian revision in the build dependency for
libssl-dev
* [Lintian] No longer ignore errors from "make distclean"
-- Christian Perrier <bubulle@debian.org> Wed, 12 Mar 2008 07:24:01 +0100
isync (1.0.3-3) unstable; urgency=low
* The "thanks Christian" release.
* Update German debconf templates translation. Thanks to Erik Schanze
(for the translation) and Christian Perrier (for forwarding the
translation). (Closes: #407615)
-- Nicolas Boullis <nboullis@debian.org> Mon, 5 Feb 2007 00:17:15 +0100
isync (1.0.3-2.1) unstable; urgency=low
* Non-maintainer upload with maintainer's permission
* Debconf templates translations:
- French updated by me
- Brazilian Portuguese translation added
- Czech translation added. Closes: #403473
- Russian translation added. Closes: #403510
- Vietnamese translation added
- Norwegian Bokmål translation added. Closes: #403523
-- Christian Perrier <bubulle@debian.org> Sun, 17 Dec 2006 15:31:04 +0100
isync (1.0.3-2) unstable; urgency=low
* Back to unstable, with permission from Steve Langasek. (Message-ID:
<20061121015225.GF28035@borges.dodds.net>)
* Rewrite the debconf note, thanks to the debian-l10n-english team (and
especially MJ Ray).
* Also add some information about the new version into NEWS.Debian.
* Remove the information about the need to set the T (trashed) flag from
README.Debian.
* Also install the isyncrc.sample sample configuration file.
* Bump Standards-Version to 3.7.2. (No change required.)
-- Nicolas Boullis <nboullis@debian.org> Tue, 5 Dec 2006 00:34:54 +0100
isync (1.0.3-1) experimental; urgency=low
* New upstream release. (Closes: #315423)
- Isync now supports breaking and linking threads. (Closes: #177280)
- It also supports unflagging messages. (Closes: #111286)
- IMAP commands are sent asynchronously. (Closes: #226222)
* Kill the old debconf question about upgrades from pre-0.8 versions.
* Use the (now obsolete) swedish and portugese translations anyway.
(Closes: #337771, #378891)
* New debconf note that warns about upgrades from pre-1.0 versions.
* Add a build dependency on po-debconf.
-- Nicolas Boullis <nboullis@debian.org> Sun, 19 Nov 2006 15:04:31 +0100
isync (0.9.2-4) unstable; urgency=low
* Add Czech debconf translation, thanks to Martin Šín. (Closes: #317571)
* Build with the newest libssl-dev.
* Load the debconf library in postinst to ensure that everything works
as expected, thanks to lintian for noticing the problem and to
Josselin Mouette for pointing to the right doc.
* Fix a bashism in the config script, thanks to lintian.
* Update the postal address of the FSF in the copyright file.
* Bump Standards-Version to 3.6.2. (No change required.)
-- Nicolas Boullis <nboullis@debian.org> Mon, 10 Oct 2005 01:37:50 +0200
isync (0.9.2-3) unstable; urgency=low
* Bump build-dependency from libdb4.0-dev to libdb4.2-dev, thanks to
Andreas Jochens. (Closes: #280268)
-- Nicolas Boullis <nboullis@debian.org> Tue, 9 Nov 2004 18:21:12 +0100
isync (0.9.2-2) unstable; urgency=low
* Add german debconf templates translation, thanks to Erik Schanze.
(Closes: #267675)
-- Nicolas Boullis <nboullis@debian.org> Tue, 24 Aug 2004 00:32:32 +0200
isync (0.9.2-1) unstable; urgency=low
* New upstream release.
- Password prompt now includes the mailbox/server. (Closes: #92893)
* Backported from CVS:
- A few prinf converted to info (disabled with -q).
- A few other printf converted to warn (disabled with -q -q) to be
able to disable the warning when SSL is not available.
(Closes: #228086)
- Update the manpage accordingly (about -q).
- Improve the manpage (about using isync with mutt).
* Add Theodore Y. Ts'o as a co-maintainter.
-- Nicolas Boullis <nboullis@debian.org> Tue, 13 Apr 2004 02:12:42 +0200
isync (0.9.1-4) unstable; urgency=low
* The "Why do I keep adding such stupid bugs?" release.
* Remove the extra parenthesis that caused UID FETCH syntax errors,
thanks to Niels den Otter for pointing the bug and giving the
solution. (Closes: #224803)
* Use configure's --build and --host options to prevent wrong
optimizations (such as building for sparc64 rather than for sparc).
-- Nicolas Boullis <nboullis@debian.org> Wed, 7 Jan 2004 01:06:53 +0100
isync (0.9.1-3) unstable; urgency=low
* Do not segfault when using both tunneled end non-tunneled connections,
thanks to Nik A. Melchior for reporting and for his patch.
(Closes: #220667)
* Save uid of messages when interrupted, thanks to Theodore Y. Ts'o for
reporting and for his patch. (Closes: #220346)
* Do not get the sizes of the messages if unneeded (if MaxSize=0).
-- Nicolas Boullis <nboullis@debian.org> Thu, 18 Dec 2003 00:55:04 +0100
isync (0.9.1-2) unstable; urgency=low
* Add french debconf templates translation, thanks to Christian
Perrier. (Closes: #218118)
-- Nicolas Boullis <nboullis@debian.org> Mon, 3 Nov 2003 18:50:56 +0100
isync (0.9.1-1) unstable; urgency=low
* New maintainer. (Closes: #180050)
* New upstream release.
- With the new option -R, isync is now able to create non-existent
remote mailboxes. (Closes: #170388)
* Update debian/copyright to match the current copyright:
- Add Oswald Buddenhagen as copyright owner.
- Add special exception for OpenSSL.
* Add support for noopt in $DEB_BUILD_OPTIONS in debian/rules.
* Switch to po-debconf.
* Remove sample.isyncrc from debian/docs: no need to have it both as a
doc and as an example.
* Move package from section non-US/main (?) to mail. (Closes: #154216)
* Update versionned build-dependency on debhelper to >= 4.1.16.
* Bump Standards-Version to 3.6.1. (No change required.)
-- Nicolas Boullis <nboullis@debian.org> Tue, 14 Oct 2003 22:02:20 +0200
isync (0.8-4) unstable; urgency=low
* Orphaned the package, as I no longer use it.
-- Joey Hess <joeyh@debian.org> Thu, 6 Feb 2003 15:46:38 -0500
isync (0.8-3) unstable; urgency=low
* New upstream maintainer; updated copyright file web site address, and
watch file. NB: new upstream has not made any new releases yet.
-- Joey Hess <joeyh@debian.org> Sat, 1 Feb 2003 16:03:49 -0500
isync (0.8-2) unstable; urgency=low
* Only reset debconf question if user chooses to abort upgrade.
Closes: #167363
* Don't open lock files O_EXCL. As seen in upstream cvs.
* Description and build-deps updates.
* Added README.Debian with notes on mutt integration.
-- Joey Hess <joeyh@debian.org> Fri, 1 Nov 2002 18:02:44 -0500
isync (0.8-1) unstable; urgency=low
* New upstream release. Closes: #134080
**WARNING**
You need to remove all the messages in your local folder if you were
previously using another version of isync or else you will end up with
duplicate messages on your IMAP server.
* Has better support for uploading locally added messages. Closes: #120272
* Added a debconf queston with some info about this that lets you abort the
upgrade.
* Added NEWS.Debian with same info.
* New maintainer.
* Removed upstream debianization stuff.
* Updated copyright file.
* Updated to current policy throughout.
* Added uscan watch file.
* Updated build-deps.
* Now that isync needs berkeley databases, go with db4, so I don't have to
transition from db3 later.
* Fix fd leak (forgot to close tmp dir in maildir). Closes: #150762
-- Joey Hess <joeyh@debian.org> Tue, 29 Oct 2002 17:02:14 -0500
isync (0.7-1) unstable; urgency=low
* New upstream version (Closes: #121312, #92051).
* Rumors say this might fix bugs #102255 and #120272,
but I have no test setup right now, so I'm leaving them open.
* Updated standards-version.
-- Tommi Virtanen <tv@debian.org> Sat, 5 Jan 2002 16:13:35 +0200
isync (0.5-1) unstable; urgency=low
* New upstream version (Closes: #98642).
* Install sample.isyncrc too (Closes: #90464).
-- Tommi Virtanen <tv@debian.org> Sat, 23 Jun 2001 01:19:07 +0300
isync (0.4-1) unstable; urgency=low
* Initial Release.
-- Tommi Virtanen <tv@debian.org> Sat, 10 Mar 2001 18:43:35 +0200

1
debian/compat vendored
View file

@ -1 +0,0 @@
9

38
debian/control vendored
View file

@ -1,38 +0,0 @@
Source: isync
Section: mail
Priority: optional
Maintainer: Nicolas Boullis <nboullis@debian.org>
Uploaders: Theodore Y. Ts'o <tytso@mit.edu>,
Alessandro Ghedini <ghedo@debian.org>
Standards-Version: 3.9.8
Build-Depends: debhelper (>= 9),
dh-autoreconf,
libdb-dev,
libsasl2-dev,
libssl-dev,
pkg-config,
zlib1g-dev
Vcs-Git: https://anonscm.debian.org/git/collab-maint/isync.git
Vcs-Browser: https://anonscm.debian.org/gitweb/?p=collab-maint/isync.git
Homepage: http://isync.sourceforge.net/
Package: isync
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Suggests: mutt
Description: IMAP and MailDir mailbox synchronizer
mbsync/isync is a command line application which synchronizes mailboxes;
currently Maildir and IMAP4 mailboxes are supported. New messages, message
deletions and flag changes can be propagated both ways. isync is suitable
for use in IMAP-disconnected mode.
.
Features:
* Fine-grained selection of synchronization operations to perform
* Synchronizes single mailboxes or entire mailbox collections
* Partial mirrors possible: keep only the latest messages locally
* Trash functionality: backup messages before removing them
IMAP features:
* Security: supports TLS/SSL via imaps: (port 993) and STARTTLS; SASL
for authentication
* Supports NAMESPACE for simplified configuration
* Pipelining for maximum speed

33
debian/copyright vendored
View file

@ -1,33 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: isync
Source: http://isync.sourceforge.net
Files: *
Copyright: 2000-2002, Michael R. Elkins <me@mutt.org>
2002-2017, Oswald Buddenhagen <ossi@users.sf.net>
2004, Theodore Y. Ts'o <tytso@mit.edu>
License: GPL-2+
Files: debian/*
Copyright: 2013, Alessandro Ghedini <ghedo@debian.org>
License: GPL-2+
License: GPL-2+
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 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
As a special exception, mbsync may be linked with the OpenSSL library,
despite that library's more restrictive license.
.
On Debian systems, the complete text of the GNU General Public License version
2 can be found in "/usr/share/common-licenses/GPL-2".

8
debian/rules vendored
View file

@ -1,8 +0,0 @@
#!/usr/bin/make -f
%:
dh $@ --with=autoreconf
override_dh_auto_install:
dh_auto_install
$(RM) $(CURDIR)/debian/isync/usr/share/doc/isync/ChangeLog

View file

@ -1 +0,0 @@
3.0 (quilt)

2
debian/watch vendored
View file

@ -1,2 +0,0 @@
version=3
http://sf.net/isync/ isync-(.*)\.tar\.gz

905
imap.c Normal file
View file

@ -0,0 +1,905 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#if HAVE_LIBSSL
#include <openssl/err.h>
#endif
#include "isync.h"
const char *Flags[] = {
"\\Seen",
"\\Answered",
"\\Deleted",
"\\Flagged",
"\\Recent",
"\\Draft"
};
void
free_message (message_t * msg)
{
message_t *tmp;
while (msg)
{
tmp = msg;
msg = msg->next;
if (tmp->file)
free (tmp->file);
free (tmp);
}
}
#if HAVE_LIBSSL
#define MAX_DEPTH 1
SSL_CTX *SSLContext = 0;
/* this gets called when a certificate is to be verified */
static int
verify_cert (SSL * ssl)
{
X509 *cert;
int err;
char buf[256];
int ret = -1;
BIO *bio;
cert = SSL_get_peer_certificate (ssl);
if (!cert)
{
puts ("Error, no server certificate");
return -1;
}
err = SSL_get_verify_result (ssl);
if (err == X509_V_OK)
return 0;
printf ("Error, can't verify certificate: %s (%d)\n",
X509_verify_cert_error_string (err), err);
X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf));
printf ("\nSubject: %s\n", buf);
X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf));
printf ("Issuer: %s\n", buf);
bio = BIO_new (BIO_s_mem ());
ASN1_TIME_print (bio, X509_get_notBefore (cert));
memset (buf, 0, sizeof (buf));
BIO_read (bio, buf, sizeof (buf) - 1);
printf ("Valid from: %s\n", buf);
ASN1_TIME_print (bio, X509_get_notAfter (cert));
memset (buf, 0, sizeof (buf));
BIO_read (bio, buf, sizeof (buf) - 1);
BIO_free (bio);
printf (" to: %s\n", buf);
printf
("\n*** WARNING *** There is no way to verify this certificate. It is\n"
" possible that a hostile attacker has replaced the\n"
" server certificate. Continue at your own risk!\n");
printf ("\nAccept this certificate anyway? [no]: ");
fflush (stdout);
if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y'))
{
ret = 0;
puts ("\n*** Fine, but don't say I didn't warn you!\n");
}
return ret;
}
static int
init_ssl (config_t * conf)
{
if (!conf->cert_file)
{
puts ("Error, CertificateFile not defined");
return -1;
}
SSL_library_init ();
SSL_load_error_strings ();
SSLContext = SSL_CTX_new (SSLv23_client_method ());
if (access (conf->cert_file, F_OK))
{
if (errno != ENOENT)
{
perror ("access");
return -1;
}
puts
("*** Warning, CertificateFile doesn't exist, can't verify server certificates");
}
else
if (!SSL_CTX_load_verify_locations
(SSLContext, conf->cert_file, NULL))
{
printf ("Error, SSL_CTX_load_verify_locations: %s\n",
ERR_error_string (ERR_get_error (), 0));
return -1;
}
if (!conf->use_sslv2)
SSL_CTX_set_options (SSLContext, SSL_OP_NO_SSLv2);
if (!conf->use_sslv3)
SSL_CTX_set_options (SSLContext, SSL_OP_NO_SSLv3);
if (!conf->use_tlsv1)
SSL_CTX_set_options (SSLContext, SSL_OP_NO_TLSv1);
/* we check the result of the verification after SSL_connect() */
SSL_CTX_set_verify (SSLContext, SSL_VERIFY_NONE, 0);
return 0;
}
#endif /* HAVE_LIBSSL */
static int
socket_read (Socket_t * sock, char *buf, size_t len)
{
#if HAVE_LIBSSL
if (sock->use_ssl)
return SSL_read (sock->ssl, buf, len);
#endif
return read (sock->fd, buf, len);
}
static int
socket_write (Socket_t * sock, char *buf, size_t len)
{
#if HAVE_LIBSSL
if (sock->use_ssl)
return SSL_write (sock->ssl, buf, len);
#endif
return write (sock->fd, buf, len);
}
/* simple line buffering */
static int
buffer_gets (buffer_t * b, char **s)
{
int n;
int start = b->offset;
*s = b->buf + start;
for (;;)
{
/* make sure we have enough data to read the \r\n sequence */
if (b->offset + 1 >= b->bytes)
{
if (start != 0)
{
/* shift down used bytes */
*s = b->buf;
assert (start <= b->bytes);
n = b->bytes - start;
if (n)
memmove (b->buf, b->buf + start, n);
b->offset -= start;
b->bytes = n;
start = 0;
}
n =
socket_read (b->sock, b->buf + b->bytes,
sizeof (b->buf) - b->bytes);
if (n <= 0)
{
if (n == -1)
perror ("read");
else
puts ("EOF");
return -1;
}
b->bytes += n;
}
if (b->buf[b->offset] == '\r')
{
assert (b->offset + 1 < b->bytes);
if (b->buf[b->offset + 1] == '\n')
{
b->buf[b->offset] = 0; /* terminate the string */
b->offset += 2; /* next line */
// assert (strchr (*s, '\r') == 0);
return 0;
}
}
b->offset++;
}
/* not reached */
}
static int
parse_fetch (imap_t * imap, list_t * list)
{
list_t *tmp;
unsigned int uid = 0;
unsigned int mask = 0;
unsigned int size = 0;
message_t *cur;
if (!is_list (list))
return -1;
for (tmp = list->child; tmp; tmp = tmp->next)
{
if (is_atom (tmp))
{
if (!strcmp ("UID", tmp->val))
{
tmp = tmp->next;
if (is_atom (tmp))
{
uid = atoi (tmp->val);
if (uid < imap->minuid)
{
/* already saw this message */
return 0;
}
else if (uid > imap->maxuid)
imap->maxuid = uid;
}
else
puts ("Error, unable to parse UID");
}
else if (!strcmp ("FLAGS", tmp->val))
{
tmp = tmp->next;
if (is_list (tmp))
{
list_t *flags = tmp->child;
for (; flags; flags = flags->next)
{
if (is_atom (flags))
{
if (!strcmp ("\\Seen", flags->val))
mask |= D_SEEN;
else if (!strcmp ("\\Flagged", flags->val))
mask |= D_FLAGGED;
else if (!strcmp ("\\Deleted", flags->val))
mask |= D_DELETED;
else if (!strcmp ("\\Answered", flags->val))
mask |= D_ANSWERED;
else if (!strcmp ("\\Draft", flags->val))
mask |= D_DRAFT;
else if (!strcmp ("\\Recent", flags->val))
mask |= D_RECENT;
else
printf ("Warning, unknown flag %s\n",
flags->val);
}
else
puts ("Error, unable to parse FLAGS list");
}
}
else
puts ("Error, unable to parse FLAGS");
}
else if (!strcmp ("RFC822.SIZE", tmp->val))
{
tmp = tmp->next;
if (is_atom (tmp))
size = atol (tmp->val);
}
}
}
cur = calloc (1, sizeof (message_t));
cur->next = imap->msgs;
imap->msgs = cur;
if (mask & D_DELETED)
imap->deleted++;
cur->uid = uid;
cur->flags = mask;
cur->size = size;
return 0;
}
static void
parse_response_code (imap_t * imap, char *s)
{
char *arg;
if (*s != '[')
return; /* no response code */
s++;
arg = next_arg (&s);
if (!strcmp ("UIDVALIDITY", arg))
{
arg = next_arg (&s);
imap->uidvalidity = atol (arg);
}
else if (!strcmp ("ALERT", arg))
{
/* RFC2060 says that these messages MUST be displayed
* to the user
*/
fputs ("***ALERT*** ", stdout);
puts (s);
}
}
static int
imap_exec (imap_t * imap, const char *fmt, ...)
{
va_list ap;
char tmp[256];
char buf[256];
char *cmd;
char *arg;
char *arg1;
va_start (ap, fmt);
vsnprintf (tmp, sizeof (tmp), fmt, ap);
va_end (ap);
snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp);
if (Verbose)
fputs (buf, stdout);
socket_write (imap->sock, buf, strlen (buf));
for (;;)
{
if (buffer_gets (imap->buf, &cmd))
return -1;
if (Verbose)
puts (cmd);
arg = next_arg (&cmd);
if (*arg == '*')
{
arg = next_arg (&cmd);
if (!arg)
{
puts ("Error, unable to parse untagged command");
return -1;
}
if (!strcmp ("NAMESPACE", arg))
{
imap->ns_personal = parse_list (cmd, &cmd);
imap->ns_other = parse_list (cmd, &cmd);
imap->ns_shared = parse_list (cmd, 0);
}
else if (!strcmp ("OK", arg) || !strcmp ("BAD", arg) ||
!strcmp ("NO", arg) || !strcmp ("PREAUTH", arg) ||
!strcmp ("BYE", arg))
{
parse_response_code (imap, cmd);
}
else if (!strcmp ("CAPABILITY", arg))
{
#if HAVE_LIBSSL
while ((arg = next_arg (&cmd)))
{
if (!strcmp ("STARTTLS", arg))
imap->have_starttls = 1;
else if (!strcmp ("AUTH=CRAM-MD5", arg))
imap->have_cram = 1;
else if (!strcmp ("NAMESPACE", arg))
imap->have_namespace = 1;
}
#endif
}
else if ((arg1 = next_arg (&cmd)))
{
if (!strcmp ("EXISTS", arg1))
imap->count = atoi (arg);
else if (!strcmp ("RECENT", arg1))
imap->recent = atoi (arg);
else if (!strcmp ("FETCH", arg1))
{
list_t *list;
list = parse_list (cmd, 0);
if (parse_fetch (imap, list))
{
free_list (list);
return -1;
}
free_list (list);
}
}
else
{
puts ("Error, unable to parse untagged command");
return -1;
}
}
#if HAVE_LIBSSL
else if (*arg == '+')
{
char *resp;
if (!imap->cram)
{
puts ("Error, not doing CRAM-MD5 authentication");
return -1;
}
resp = cram (cmd, imap->box->user, imap->box->pass);
socket_write (imap->sock, resp, strlen (resp));
if (Verbose)
puts (resp);
socket_write (imap->sock, "\r\n", 2);
free (resp);
imap->cram = 0;
}
#endif
else if ((size_t) atol (arg) != Tag)
{
puts ("wrong tag");
return -1;
}
else
{
arg = next_arg (&cmd);
parse_response_code (imap, cmd);
if (!strcmp ("OK", arg))
return 0;
return -1;
}
}
/* not reached */
}
/* `box' is the config info for the maildrop to sync. `minuid' is the
* minimum UID to consider. in normal mode this will be 1, but in --fast
* mode we only fetch messages newer than the last one seen in the local
* mailbox.
*/
imap_t *
imap_open (config_t * box, unsigned int minuid, imap_t * imap)
{
int ret;
int s;
struct sockaddr_in sin;
struct hostent *he;
char *ns_prefix = "";
int reuse = 0;
#if HAVE_LIBSSL
int use_ssl = 0;
#endif
if (imap)
{
/* determine whether or not we can reuse the existing session */
if (strcmp (box->host, imap->box->host) ||
strcmp (box->user, imap->box->user) ||
box->port != imap->box->port
#if HAVE_LIBSSL
/* ensure that security requirements are met */
|| (box->require_ssl ^ imap->box->require_ssl)
|| (box->require_cram ^ imap->box->require_cram)
#endif
)
{
/* can't reuse */
imap_close (imap);
imap = 0;
}
else
{
reuse = 1;
/* reset mailbox-specific state info */
imap->recent = 0;
imap->deleted = 0;
imap->count = 0;
imap->maxuid = 0;
free_message (imap->msgs);
imap->msgs = 0;
}
}
if (!imap)
{
imap = calloc (1, sizeof (imap_t));
imap->sock = calloc (1, sizeof (Socket_t));
imap->buf = calloc (1, sizeof (buffer_t));
imap->buf->sock = imap->sock;
}
imap->box = box;
imap->minuid = minuid;
if (!reuse)
{
/* open connection to IMAP server */
memset (&sin, 0, sizeof (sin));
sin.sin_port = htons (box->port);
sin.sin_family = AF_INET;
printf ("Resolving %s... ", box->host);
fflush (stdout);
he = gethostbyname (box->host);
if (!he)
{
perror ("gethostbyname");
return 0;
}
puts ("ok");
sin.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
s = socket (PF_INET, SOCK_STREAM, 0);
printf ("Connecting to %s:%hu... ", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port));
fflush (stdout);
if (connect (s, (struct sockaddr *) &sin, sizeof (sin)))
{
perror ("connect");
exit (1);
}
puts ("ok");
imap->sock->fd = s;
}
do
{
/* if we are reusing the existing connection, we can skip the
* authentication steps.
*/
if (!reuse)
{
#if HAVE_LIBSSL
if (!box->use_imaps)
{
/* let's see what this puppy can do... */
if ((ret = imap_exec (imap, "CAPABILITY")))
break;
/* always try to select SSL support if available */
if (imap->have_starttls)
{
if ((ret = imap_exec (imap, "STARTTLS")))
break;
use_ssl = 1;
}
if (!use_ssl)
{
if (box->require_ssl)
{
puts ("Error, SSL support not available");
ret = -1;
break;
}
else
puts ("Warning, SSL support not available");
}
}
else
use_ssl = 1;
if (use_ssl)
{
/* initialize SSL */
if (init_ssl (box))
{
ret = -1;
break;
}
imap->sock->ssl = SSL_new (SSLContext);
SSL_set_fd (imap->sock->ssl, imap->sock->fd);
ret = SSL_connect (imap->sock->ssl);
if (ret <= 0)
{
ret = SSL_get_error (imap->sock->ssl, ret);
printf ("Error, SSL_connect: %s\n",
ERR_error_string (ret, 0));
ret = -1;
break;
}
/* verify the server certificate */
if ((ret = verify_cert (imap->sock->ssl)))
break;
imap->sock->use_ssl = 1;
puts ("SSL support enabled");
if (box->use_imaps)
if ((ret = imap_exec (imap, "CAPABILITY")))
break;
}
#else
if ((ret = imap_exec (imap, "CAPABILITY")))
break;
#endif
puts ("Logging in...");
#if HAVE_LIBSSL
if (imap->have_cram)
{
puts ("Authenticating with CRAM-MD5");
imap->cram = 1;
if ((ret = imap_exec (imap, "AUTHENTICATE CRAM-MD5")))
break;
}
else if (imap->box->require_cram)
{
puts
("Error, CRAM-MD5 authentication is not supported by server");
ret = -1;
break;
}
else
#endif
{
#if HAVE_LIBSSL
if (!use_ssl)
#endif
puts
("*** Warning *** Password is being sent in the clear");
if (
(ret =
imap_exec (imap, "LOGIN \"%s\" \"%s\"", box->user,
box->pass)))
break;
}
/* get NAMESPACE info */
if (box->use_namespace && imap->have_namespace)
{
if ((ret = imap_exec (imap, "NAMESPACE")))
break;
}
} /* !reuse */
/* XXX for now assume personal namespace */
if (imap->box->use_namespace && is_list (imap->ns_personal) &&
is_list (imap->ns_personal->child) &&
is_atom (imap->ns_personal->child->child))
{
ns_prefix = imap->ns_personal->child->child->val;
}
fputs ("Selecting mailbox... ", stdout);
fflush (stdout);
if ((ret = imap_exec (imap, "SELECT %s%s", ns_prefix, box->box)))
break;
printf ("%d messages, %d recent\n", imap->count, imap->recent);
puts ("Reading IMAP mailbox index");
if (imap->count > 0)
{
if ((ret = imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)",
imap->minuid)))
break;
}
}
while (0);
if (ret)
{
imap_close (imap);
imap = 0;
}
return imap;
}
void
imap_close (imap_t * imap)
{
puts ("Closing IMAP connection");
imap_exec (imap, "LOGOUT");
close (imap->sock->fd);
free (imap->sock);
free (imap->buf);
free_message (imap->msgs);
memset (imap, 0xff, sizeof (imap_t));
free (imap);
}
/* write a buffer stripping all \r bytes */
static int
write_strip (int fd, char *buf, size_t len)
{
size_t start = 0;
size_t end = 0;
while (start < len)
{
while (end < len && buf[end] != '\r')
end++;
write (fd, buf + start, end - start);
end++;
start = end;
}
return 0;
}
static void
send_server (Socket_t * sock, const char *fmt, ...)
{
char buf[128];
char cmd[128];
va_list ap;
va_start (ap, fmt);
vsnprintf (buf, sizeof (buf), fmt, ap);
va_end (ap);
snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf);
socket_write (sock, cmd, strlen (cmd));
if (Verbose)
fputs (cmd, stdout);
}
int
imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
{
char *cmd;
char *arg;
size_t bytes;
size_t n;
char buf[1024];
send_server (imap->sock, "UID FETCH %d BODY.PEEK[]", uid);
for (;;)
{
if (buffer_gets (imap->buf, &cmd))
return -1;
if (Verbose)
puts (cmd);
if (*cmd == '*')
{
/* need to figure out how long the message is
* * <msgno> FETCH (RFC822 {<size>}
*/
next_arg (&cmd); /* * */
next_arg (&cmd); /* <msgno> */
next_arg (&cmd); /* FETCH */
while ((arg = next_arg (&cmd)) && *arg != '{')
;
if (!arg)
{
puts ("parse error getting size");
return -1;
}
bytes = strtol (arg + 1, 0, 10);
// printf ("receiving %d byte message\n", bytes);
/* dump whats left over in the input buffer */
n = imap->buf->bytes - imap->buf->offset;
if (n > bytes)
{
/* the entire message fit in the buffer */
n = bytes;
}
/* ick. we have to strip out the \r\n line endings, so
* i can't just dump the raw bytes to disk.
*/
write_strip (fd, imap->buf->buf + imap->buf->offset, n);
bytes -= n;
// printf ("wrote %d buffered bytes\n", n);
/* mark that we used part of the buffer */
imap->buf->offset += n;
/* now read the rest of the message */
while (bytes > 0)
{
n = bytes;
if (n > sizeof (buf))
n = sizeof (buf);
n = socket_read (imap->sock, buf, n);
if (n > 0)
{
// printf("imap_fetch_message:%d:read %d bytes\n", __LINE__, n);
write_strip (fd, buf, n);
bytes -= n;
}
else
{
if (n == (size_t) - 1)
perror ("read");
else
puts ("EOF");
return -1;
}
}
// puts ("finished fetching msg");
buffer_gets (imap->buf, &cmd);
if (Verbose)
puts (cmd); /* last part of line */
}
else
{
arg = next_arg (&cmd);
if (!arg || (size_t) atoi (arg) != Tag)
{
puts ("wrong tag");
return -1;
}
arg = next_arg (&cmd);
if (!strcmp ("OK", arg))
return 0;
return -1;
}
}
/* not reached */
}
/* add flags to existing flags */
int
imap_set_flags (imap_t * imap, unsigned int uid, unsigned int flags)
{
char buf[256];
int i;
buf[0] = 0;
for (i = 0; i < D_MAX; i++)
{
if (flags & (1 << i))
snprintf (buf + strlen (buf),
sizeof (buf) - strlen (buf), "%s%s",
(buf[0] != 0) ? " " : "", Flags[i]);
}
return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf);
}
int
imap_expunge (imap_t * imap)
{
return imap_exec (imap, "EXPUNGE");
}

264
isync.1 Normal file
View file

@ -0,0 +1,264 @@
.ig
\" isync - IMAP4 to maildir mailbox synchronizer
\" Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
\"
\" This program is free software; you can redistribute it and/or modify
\" it under the terms of the GNU General Public License as published by
\" the Free Software Foundation; either version 2 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, write to the Free Software
\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
..
.TH isync 1 "2000 Dec 27"
..
.SH NAME
isync - synchronize IMAP4 and maildir mailboxes
..
.SH SYNOPSIS
.B isync
[
.I options...
]
.I mailbox
[
.I mailbox ...
]
..
.SH DESCRIPTION
.B isync
is a command line application which synchronizes a local maildir-style
mailbox with a remote IMAP4 mailbox, suitable for use in IMAP-disconnected
mode. Multiple copies of the remote IMAP4 mailbox can be maintained, and
all flags are synchronized.
..
.SH OPTIONS
.TP
\fB-c\fR, \fB--config\fR \fIfile\fR
Read configuration from
.I file
By default, configuration is read from ~/.isyncrc if it exists.
.TP
.B -d, --delete
Causes
.B isync
to delete messages from the local maildir mailbox which do not exist on the
IMAP server. By default,
.I dead
messages are
.B not
deleted.
.TP
.B -e, --expunge
Causes
.B isync
to permanently remove all messages marked for deletion in both the local
maildir mailbox and the remote IMAP mailbox. By default, messages are
.B not
expunged.
.TP
.B -f, --fast
Causes
.B isync
to skip the step of synchronzing message flags between the local maildir
mailbox and the IMAP mailbox. Only new messages existing on the server will
be fetched into the local mailbox.
.TP
.B -h, --help
Displays a summary of command line options
.TP
\fB-p\fR, \fB--port\fR \fIport\fR
Specifies the port on the IMAP server to connect to (default: 143)
.TP
\fB-r\fR, \fB--remote\fR \fIbox\fR
Specifies the name of the remote IMAP mailbox to synchronize with
(Default: INBOX)
.TP
\fB-s\fR, \fB--host\fR \fB[\fRimaps:\fB]\fR\fIhost\fR
.P
Specifies the hostname of the IMAP server
.TP
\fB-u\fR, \fB--user\fR \fIuser\fR
Specifies the login name to access the IMAP server (default: $USER)
.TP
.B -v, --version
Displays
.B isync
version information
.TP
.B -V, --verbose
Enables
.I verbose
mode, which displays the IMAP4 network traffic.
..
.SH CONFIGURATION
.B isync
reads
.I ~/.isyncrc
to load default configuration data. Each line of the configuration file
consists of a command. The following commands are understood:
.TP
\fBMailbox\fR \fIpath\fR
Defines a local maildir mailbox. All configuration commands following this
line, up until the next
.I Mailbox
command, apply to this mailbox only.
..
.TP
\fBHost\fR \fB[\fRimaps:\fB]\fR\fIname\fR
Defines the DNS name or IP address of the IMAP server. If the hostname is
prefixed with
.I imaps:
the connection is assumed to be a SSL connection to port 993 (though you can
change this by placing a
.B Port
command
.B after
the
.B Host
command. Note that some servers support SSL on the default port 143.
.B isync
will always attempt to use SSL if available.
..
.TP
\fBPort\fR \fIport\fR
Defines the TCP port number on the IMAP server to use (Default: 143)
..
.TP
\fBBox\fR \fImailbox\fR
Defines the name of the remote IMAP mailbox associated with the local
maildir mailbox (Default: INBOX)
..
.TP
\fBUser\fR \fIusername\fR
Defines the login name on the IMAP server (Default: current user)
..
.TP
\fBPass\fR \fIpassword\fR
Defines the password for
.I username
on the IMAP server. Note that this option is
.B NOT
required. If no password is specified in the configuration file,
.B isync
will prompt you for it.
..
.TP
\fBAlias\fR \fIstring\fR
Defines an alias for the mailbox which can be used as a shortcut on the
command line.
..
.TP
\fBMaxSize\fR \fIbytes\fR
Sets a threshold for the maximum message size (in bytes) for which
.B isync
should fetch from the server. This is useful for weeding out messages with
large attachments. If
.I bytes
is 0, the maximum file size is
.B unlimited.
..
.TP
\fBUseNamespace\fR \fIyes|no\fR
Selects whether
.B isync
should select mailboxes using the namespace given by the NAMESPACE command.
This is useful with broken IMAP servers. (Default:
.I yes
)
..
.TP
\fBRequireCRAM\fR \fIyes|no\fR
If set to
.I yes
,
.B isync
will require that the server accept CRAM-MD5 intead of PLAIN to authenticate
the user.
..
.TP
\fBRequireSSL\fR \fIyes|no\fR
.B isync
will abort the connection if a TLS/SSL session to the IMAP
server can not be established. (Default:
.I yes
)
..
.TP
\fBCertificateFile\fR \fIpath\fR
File containing X.509 CA certificates used to verify server identities.
..
.TP
\fBUseSSLv2\fR \fIyes|no\fR
Should
.B isync
use SSLv2 for communication with the IMAP server over SSL? (Default:
.I yes
)
..
.TP
\fBUseSSLv3\fR \fIyes|no\fR
Should
.B isync
use SSLv3 for communication with the IMAP server over SSL? (Default:
.I yes
)
..
.TP
\fBUseTLSv1\fR \fIyes|no\fR
Should
.B isync
use TLSv1 for communication with the IMAP server over SSL? (Default:
.I yes
)
..
.P
Configuration commands that appear prior to the first
.B Mailbox
command are considered to be
.I global
options which are used as defaults when those specific options are not
specifically set for a defined Mailbox. For example, if you use the same
login name for several IMAP servers, you can put a
.B User
command before the first
.B Mailbox
command, and then leave out the
.B User
command in the sections for each mailbox.
.B isync
will then use the global value by default.
..
.SH FILES
.TP
.B ~/.isyncrc
Default configuration file
..
.SH BUGS
maildir(5) states that readers should not attempt to parse the filename of a
a message other than the :info field. However, since
.B isync
relies on using the message UIDs that info must be inserted into the
filename in a way which will be interoperable with existing readers. So
the UID is placed in the filename of the messages in the local maildir
mailbox rather than the :info field.
.P
When synchronizing multiple mailboxes on the same IMAP server, it is not
possible to select different SSL options for each mailbox. Only the options
from the first mailbox are applied since the SSL session is reused.
.SH SEE ALSO
mutt(1), maildir(5)
.P
Up to date information on
.B isync
can be found at
http://www.sigpipe.org/isync/.
..
.SH AUTHOR
Written by Michael R. Elkins <me@mutt.org>.

182
isync.h Normal file
View file

@ -0,0 +1,182 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdarg.h>
#if HAVE_LIBSSL
#include <openssl/ssl.h>
#endif
typedef struct
{
int fd;
#if HAVE_LIBSSL
SSL *ssl;
unsigned int use_ssl:1;
#endif
} Socket_t;
typedef struct
{
Socket_t *sock;
char buf[1024];
int bytes;
int offset;
}
buffer_t;
typedef struct config config_t;
typedef struct mailbox mailbox_t;
typedef struct message message_t;
struct config
{
char *path;
char *host;
int port;
char *user;
char *pass;
char *box;
char *alias;
unsigned int max_size;
config_t *next;
#if HAVE_LIBSSL
char *cert_file;
unsigned int use_imaps:1;
unsigned int require_ssl:1;
unsigned int use_sslv2:1;
unsigned int use_sslv3:1;
unsigned int use_tlsv1:1;
unsigned int require_cram:1;
#endif
unsigned int use_namespace:1;
};
/* struct representing local mailbox file */
struct mailbox
{
char *path;
message_t *msgs;
unsigned int deleted; /* # of deleted messages */
unsigned int uidvalidity;
unsigned int maxuid; /* largest uid we know about */
unsigned int changed:1;
unsigned int maxuidchanged:1;
};
/* message dispositions */
#define D_SEEN (1<<0)
#define D_ANSWERED (1<<1)
#define D_DELETED (1<<2)
#define D_FLAGGED (1<<3)
#define D_RECENT (1<<4)
#define D_DRAFT (1<<5)
#define D_MAX 6
struct message
{
char *file;
unsigned int uid;
unsigned int flags;
unsigned int size;
message_t *next;
unsigned int processed:1; /* message has already been evaluated */
unsigned int new:1; /* message is in the new/ subdir */
unsigned int changed:1; /* flags changed */
unsigned int dead:1; /* message doesn't exist on the server */
};
/* struct used for parsing IMAP lists */
typedef struct _list list_t;
#define NIL (void*)0x1
#define LIST (void*)0x2
struct _list {
char *val;
list_t *next;
list_t *child;
};
/* imap connection info */
typedef struct
{
Socket_t *sock;
unsigned int count; /* # of msgs */
unsigned int recent; /* # of recent messages */
buffer_t *buf; /* input buffer for reading server output */
message_t *msgs; /* list of messages on the server */
config_t *box; /* mailbox to open */
unsigned int deleted; /* # of deleted messages */
unsigned int uidvalidity;
unsigned int maxuid;
unsigned int minuid;
/* NAMESPACE info */
list_t *ns_personal;
list_t *ns_other;
list_t *ns_shared;
unsigned int have_namespace:1;
#if HAVE_LIBSSL
unsigned int have_cram:1;
unsigned int have_starttls:1;
unsigned int cram:1;
#endif
}
imap_t;
/* flags for sync_mailbox */
#define SYNC_DELETE (1<<0) /* delete local that don't exist on server */
#define SYNC_EXPUNGE (1<<1) /* don't fetch deleted messages */
extern config_t global;
extern unsigned int Tag;
extern char Hostname[256];
extern int Verbose;
#if HAVE_LIBSSL
extern SSL_CTX *SSLContext;
char *cram (const char *, const char *, const char *);
#endif
char *next_arg (char **);
int sync_mailbox (mailbox_t *, imap_t *, int, unsigned int);
void imap_close (imap_t *);
int imap_fetch_message (imap_t *, unsigned int, int);
int imap_set_flags (imap_t *, unsigned int, unsigned int);
int imap_expunge (imap_t *);
imap_t *imap_open (config_t *, unsigned int, imap_t *);
mailbox_t *maildir_open (const char *, int fast);
int maildir_expunge (mailbox_t *, int);
int maildir_sync (mailbox_t *);
int maildir_set_uidvalidity (mailbox_t *, unsigned int uidvalidity);
void maildir_close (mailbox_t *);
message_t * find_msg (message_t * list, unsigned int uid);
void free_message (message_t *);
/* parse an IMAP list construct */
list_t * parse_list (char *s, char **end);
int is_atom (list_t *list);
int is_list (list_t *list);
int is_nil (list_t *list);
void free_list (list_t *list);

View file

@ -1,35 +1,33 @@
Summary: Utility to synchronize IMAP mailboxes with local maildir folders
Name: isync
Version: @VERSION@
Version: 0.4
Release: 1
License: GPL
Copyright: GPL
Group: Applications/Internet
Source: @PACKAGE@-@VERSION@.tar.gz
URL: http://@PACKAGE@.sf.net/
Packager: Oswald Buddenhagen <ossi@users.sf.net>
Source: http://www.sigpipe.org/isync/isync-0.4.tar.gz
URL: http://www.sigpipe.org/isync/
Packager: Michael Elkins <me@mutt.org>
BuildRoot: /var/tmp/%{name}-buildroot
%description
isync is a command line utility which synchronizes mailboxes; currently
Maildir and IMAP4 mailboxes are supported.
New messages, message deletions and flag changes can be propagated both ways.
It is useful for working in disconnected mode, such as on a laptop or with a
non-permanent internet collection (dIMAP).
isync is a command line utility for synchronizing a remote IMAP mailbox with a
local maildir-style mailbox. This is useful for working in disconnected mode,
such as on a laptop. Modifications made locally and remotely are synchronized
so that no message status flags are lost.
%prep
%setup
%build
%configure
./configure --prefix=/usr
make RPM_OPT_FLAGS="$RPM_OPT_FLAGS"
%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
rm -rf $RPM_BUILD_ROOT%{_docdir}/%{name}
%clean
rm -rf $RPM_BUILD_ROOT
%files
%doc AUTHORS COPYING NEWS README TODO ChangeLog src/mbsyncrc.sample
%{_bindir}/*
%{_mandir}/man1/*
%doc COPYING README TODO ChangeLog
/usr/bin/isync
/usr/man/man1/isync.1.bz2

30
isyncrc.sample Normal file
View file

@ -0,0 +1,30 @@
# Global configuration section
# Values here are used as defaults for any following Mailbox section that
# doesn't specify it.
# my default username, if different from the local username
User me
#Port 143
#Box INBOX
# don't download messages larger than 200K bytes
MaxSize 200000
###
### work mailbox
###
Mailbox /home/me/Mail/work
Host work.host.com
Pass xxxxxxxx
# define a shortcut so I can just use "isync work" from the command line
Alias work
###
### personal mailbox
###
Mailbox /home/me/Mail/personal
Host host.play.com
# use a non-default port for this connection
Port 6789
Alias personal

175
list.c Normal file
View file

@ -0,0 +1,175 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "isync.h"
static char *
skip_string (char *s)
{
while (*s && *s != '"')
s++;
return s;
}
list_t *
parse_list (char *s, char **end)
{
int level = 1;
list_t *cur;
list_t **list;
char *b;
cur = calloc (1, sizeof (list_t));
while (isspace ((unsigned char) *s))
s++;
if (*s == '(')
{
/* start of list. find the end of the list */
s++;
b = s; /* save beginning */
cur->val = LIST;
while (*s)
{
if (*s == '(')
{
level++;
}
else if (*s == ')')
{
level--;
if (level == 0)
break;
}
else if (*s == '"')
{
s = skip_string (s + 1);
if (!*s)
{
/* parse error */
free (cur);
return NULL;
}
}
s++;
}
if (level != 0)
{
free (cur); /* parse error */
return NULL;
}
*s++ = 0;
list = &cur->child;
while (*b)
{
*list = parse_list (b, &b);
if (*list == NULL)
{
/* parse error */
free (cur);
return NULL;
}
while (*list)
list = &(*list)->next;
}
}
else if (*s == '"')
{
/* quoted string */
s++;
cur->val = s;
s = skip_string (s);
if (!*s)
{
/* parse error */
free (cur);
return NULL;
}
*s++ = 0;
cur->val = strdup (cur->val);
}
else
{
/* atom */
cur->val = s;
while (*s && !isspace ((unsigned char) *s))
s++;
if (*s)
*s++ = 0;
if (strcmp ("NIL", cur->val))
cur->val = strdup (cur->val);
else
cur->val = NIL;
}
if (end)
*end = s;
return cur;
}
int
is_atom (list_t * list)
{
return (list && list->val && list->val != NIL && list->val != LIST);
}
int
is_list (list_t * list)
{
return (list && list->val == LIST);
}
int
is_nil (list_t * list)
{
return (list && list->val == NIL);
}
void
free_list (list_t * list)
{
list_t *tmp;
while (list)
{
tmp = list;
list = list->next;
if (is_list (list))
free_list (tmp->child);
else if (is_atom (tmp))
free (tmp->val);
free (tmp);
}
}
#if TEST
int
main (int argc, char **argv)
{
char buf[256];
list_t *list;
strcpy (buf,
"((compound list) atom NIL \"string with a (\" (another list))");
list = parse_list (buf, 0);
}
#endif

421
maildir.c Normal file
View file

@ -0,0 +1,421 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include "isync.h"
static int
do_lock (int fd, int flag)
{
struct flock lck;
struct stat sb;
if (fstat (fd, &sb))
{
perror ("fstat");
return -1;
}
memset (&lck, 0, sizeof (lck));
lck.l_type = flag;
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = sb.st_size;
if (fcntl (fd, F_SETLK, &lck))
{
perror ("fcntl");
close (fd);
return -1;
}
return 0;
}
/* 2,<flags> */
static void
parse_info (message_t * m, char *s)
{
if (*s == '2' && *(s + 1) == ',')
{
s += 2;
while (*s)
{
if (*s == 'F')
m->flags |= D_FLAGGED;
else if (*s == 'R')
m->flags |= D_ANSWERED;
else if (*s == 'T')
m->flags |= D_DELETED;
else if (*s == 'S')
m->flags |= D_SEEN;
s++;
}
}
}
static unsigned int
read_uid (const char *path, const char *file)
{
char full[_POSIX_PATH_MAX];
int fd;
int ret;
int len;
char buf[64];
unsigned int uid = 0;
snprintf (full, sizeof (full), "%s/%s", path, file);
fd = open (full, O_RDONLY);
if (fd == -1)
{
if (errno != ENOENT)
{
perror ("open");
return -1;
}
return 0; /* doesn't exist */
}
ret = do_lock (fd, F_RDLCK);
if (!ret)
{
len = read (fd, buf, sizeof (buf) - 1);
if (len == -1)
ret = -1;
else
{
buf[len] = 0;
uid = atol (buf);
}
}
ret |= do_lock (fd, F_UNLCK);
close (fd);
return ret ? ret : uid;
}
/* open a maildir mailbox. if `fast' is nonzero, we just check to make
* sure its a valid mailbox and don't actually parse it. any IMAP messages
* with the \Recent flag set are guaranteed not to be in the mailbox yet,
* so we can save a lot of time when the user just wants to fetch new messages
* without syncing the flags.
*/
mailbox_t *
maildir_open (const char *path, int fast)
{
char buf[_POSIX_PATH_MAX];
DIR *d;
struct dirent *e;
message_t **cur;
message_t *p;
mailbox_t *m;
char *s;
int count = 0;
/* check to make sure this looks like a valid maildir box */
snprintf (buf, sizeof (buf), "%s/new", path);
if (access (buf, F_OK))
{
perror ("access");
return 0;
}
snprintf (buf, sizeof (buf), "%s/cur", path);
if (access (buf, F_OK))
{
perror ("access");
return 0;
}
m = calloc (1, sizeof (mailbox_t));
m->path = strdup (path);
/* check for the uidvalidity value */
m->uidvalidity = read_uid (path, "isyncuidvalidity");
if (m->uidvalidity == (unsigned int) -1)
{
free (m->path);
free (m);
return NULL;
}
/* load the current maxuid */
if ((m->maxuid = read_uid (path, "isyncmaxuid")) == (unsigned int) -1)
{
free (m->path);
free (m);
return NULL;
}
if (fast)
return m;
cur = &m->msgs;
for (; count < 2; count++)
{
/* read the msgs from the new subdir */
snprintf (buf, sizeof (buf), "%s/%s", path,
(count == 0) ? "new" : "cur");
d = opendir (buf);
if (!d)
{
free (m->path);
free (m);
perror ("opendir");
return 0;
}
while ((e = readdir (d)))
{
if (*e->d_name == '.')
continue; /* skip dot-files */
*cur = calloc (1, sizeof (message_t));
p = *cur;
p->file = strdup (e->d_name);
p->uid = -1;
p->flags = (count == 1) ? D_SEEN : 0;
p->new = (count == 0);
/* filename format is something like:
* <unique-prefix>.UID<n>:2,<flags>
* This is completely non-standard, but in order for mail
* clients to understand the flags, we have to use the
* standard :info as described by the qmail spec
*/
s = strstr (p->file, "UID");
if (!s)
puts ("Warning, no uid for message");
else
{
p->uid = strtol (s + 3, &s, 10);
if (p->uid > m->maxuid)
{
m->maxuid = p->uid;
m->maxuidchanged = 1;
}
if (*s && *s != ':')
{
puts ("warning, unable to parse uid");
p->uid = -1; /* reset */
}
}
s = strchr (p->file, ':');
if (s)
parse_info (p, s + 1);
if (p->flags & D_DELETED)
m->deleted++;
cur = &p->next;
}
closedir (d);
}
return m;
}
/* permanently remove messages from a maildir mailbox. if `dead' is nonzero,
* we only remove the messags marked dead.
*/
int
maildir_expunge (mailbox_t * mbox, int dead)
{
message_t **cur = &mbox->msgs;
message_t *tmp;
char path[_POSIX_PATH_MAX];
while (*cur)
{
if ((dead == 0 && (*cur)->flags & D_DELETED) ||
(dead && (*cur)->dead))
{
tmp = *cur;
*cur = (*cur)->next;
snprintf (path, sizeof (path), "%s/%s/%s",
mbox->path, tmp->new ? "new" : "cur", tmp->file);
if (unlink (path))
perror ("unlink");
free (tmp->file);
free (tmp);
}
else
cur = &(*cur)->next;
}
return 0;
}
static int
update_maxuid (mailbox_t * mbox)
{
int fd;
char buf[64];
size_t len;
unsigned int uid;
char path[_POSIX_PATH_MAX];
int ret = 0;
snprintf (path, sizeof (path), "%s/isyncmaxuid", mbox->path);
fd = open (path, O_RDWR | O_CREAT, 0600);
if (fd == -1)
{
perror ("open");
return -1;
}
/* lock the file */
if (do_lock (fd, F_WRLCK))
{
close (fd);
return -1;
}
/* read the file again just to make sure it wasn't updated while
* we were doing something else
*/
len = read (fd, buf, sizeof (buf) - 1);
buf[len] = 0;
uid = atol (buf);
if (uid > mbox->maxuid)
{
puts ("Error, maxuid is now higher (fatal)");
ret = -1;
}
if (!ret)
{
/* rewind */
lseek (fd, 0, SEEK_SET);
/* write out the file */
snprintf (buf, sizeof (buf), "%u\n", mbox->maxuid);
len = write (fd, buf, strlen (buf));
if (len == (size_t) - 1)
{
perror ("write");
ret = -1;
}
else
{
ret = ftruncate (fd, len);
if (ret)
perror ("ftruncate");
}
}
ret |= do_lock (fd, F_UNLCK);
ret |= close (fd);
return ret;
}
int
maildir_sync (mailbox_t * mbox)
{
message_t *cur = mbox->msgs;
char path[_POSIX_PATH_MAX];
char oldpath[_POSIX_PATH_MAX];
char *p;
int ret = 0;
if (mbox->changed)
{
for (; cur; cur = cur->next)
{
if (cur->changed)
{
/* generate old path */
snprintf (oldpath, sizeof (oldpath), "%s/%s/%s",
mbox->path, cur->new ? "new" : "cur", cur->file);
/* truncate old flags (if present) */
p = strchr (cur->file, ':');
if (p)
*p = 0;
/* generate new path */
snprintf (path, sizeof (path), "%s/%s/%s:2,%s%s%s%s",
mbox->path, (cur->flags & D_SEEN) ? "cur" : "new",
cur->file, (cur->flags & D_FLAGGED) ? "F" : "",
(cur->flags & D_ANSWERED) ? "R" : "",
(cur->flags & D_SEEN) ? "S" : "",
(cur->flags & D_DELETED) ? "T" : "");
if (rename (oldpath, path))
perror ("rename");
}
}
}
if (mbox->maxuidchanged)
ret = update_maxuid (mbox);
return ret;
}
int
maildir_set_uidvalidity (mailbox_t * mbox, unsigned int uidvalidity)
{
char path[_POSIX_PATH_MAX];
char buf[16];
int fd;
int ret;
snprintf (path, sizeof (path), "%s/isyncuidvalidity", mbox->path);
fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd == -1)
{
perror ("open");
return -1;
}
snprintf (buf, sizeof (buf), "%u\n", uidvalidity);
ret = write (fd, buf, strlen (buf));
if (ret == -1)
perror ("write");
else if ((size_t) ret != strlen (buf))
ret = -1;
else
ret = 0;
if (close (fd))
{
perror ("close");
ret = -1;
}
if (ret)
if (unlink (path))
perror ("unlink");
return (ret);
}
void
maildir_close (mailbox_t * mbox)
{
free (mbox->path);
free_message (mbox->msgs);
memset (mbox, 0xff, sizeof (mailbox_t));
free (mbox);
}

476
main.c Normal file
View file

@ -0,0 +1,476 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include "isync.h"
#if HAVE_GETOPT_LONG
#define _GNU_SOURCE
#include <getopt.h>
struct option Opts[] = {
{"config", 1, NULL, 'c'},
{"delete", 0, NULL, 'd'},
{"expunge", 0, NULL, 'e'},
{"fast", 0, NULL, 'f'},
{"help", 0, NULL, 'h'},
{"remote", 1, NULL, 'r'},
{"host", 1, NULL, 's'},
{"port", 1, NULL, 'p'},
{"user", 1, NULL, 'u'},
{"version", 0, NULL, 'v'},
{"verbose", 0, NULL, 'V'},
{0, 0, 0, 0}
};
#endif
config_t global;
unsigned int Tag = 0;
static config_t *box = 0;
char Hostname[256];
int Verbose = 0;
static void
version (void)
{
printf ("%s %s\n", PACKAGE, VERSION);
exit (0);
}
static void
usage (void)
{
printf ("%s %s IMAP4 to maildir synchronizer\n", PACKAGE, VERSION);
puts ("Copyright (C) 2000 Michael R. Elkins <me@mutt.org>");
printf ("usage: %s [ flags ] mailbox [mailbox ...]\n", PACKAGE);
puts
(" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)");
puts
(" -d, --delete delete local msgs that don't exist on the server");
puts
(" -e, --expunge expunge deleted messages from the server");
puts (" -f, --fast only fetch new messages");
puts (" -h, --help display this help message");
puts (" -p, --port PORT server IMAP port");
puts (" -r, --remote BOX remote mailbox");
puts (" -s, --host HOST IMAP server address");
puts (" -u, --user USER IMAP user name");
puts (" -v, --version display version");
puts
(" -V, --verbose verbose mode (display network traffic)");
exit (0);
}
/* set defaults from the global configuration section */
static void
config_defaults (config_t * conf)
{
conf->user = global.user;
conf->pass = global.pass;
conf->port = global.port;
conf->box = global.box;
conf->host = global.host;
conf->max_size = global.max_size;
conf->use_namespace = global.use_namespace;
#if HAVE_LIBSSL
conf->require_ssl = global.require_ssl;
conf->use_imaps = global.use_imaps;
conf->cert_file = global.cert_file;
conf->use_sslv2 = global.use_sslv2;
conf->use_sslv3 = global.use_sslv3;
conf->use_tlsv1 = global.use_tlsv1;
#endif
}
static void
load_config (char *where)
{
char path[_POSIX_PATH_MAX];
char buf[1024];
struct passwd *pw;
config_t **cur = &box;
int line = 0;
FILE *fp;
char *p, *cmd, *val;
if (!where)
{
pw = getpwuid (getuid ());
snprintf (path, sizeof (path), "%s/.isyncrc", pw->pw_dir);
where = path;
}
printf ("Reading %s\n", where);
fp = fopen (where, "r");
if (!fp)
{
if (errno != ENOENT)
{
perror ("fopen");
return;
}
}
buf[sizeof buf - 1] = 0;
while ((fgets (buf, sizeof (buf) - 1, fp)))
{
p = buf;
cmd = next_arg (&p);
val = next_arg (&p);
line++;
if (!cmd || *cmd == '#')
continue;
if (!strncasecmp ("mailbox", cmd, 7))
{
if (*cur)
cur = &(*cur)->next;
*cur = calloc (1, sizeof (config_t));
config_defaults (*cur);
(*cur)->path = strdup (val);
}
else if (!strncasecmp ("host", cmd, 4))
{
#if HAVE_LIBSSL
if (!strncasecmp ("imaps:", val, 6))
{
val += 6;
if (*cur)
{
(*cur)->use_imaps = 1;
(*cur)->port = 993;
}
else
{
global.use_imaps = 1;
global.port = 993;
}
}
#endif
if (*cur)
(*cur)->host = strdup (val);
else
global.host = strdup (val);
}
else if (!strncasecmp ("user", cmd, 4))
{
if (*cur)
(*cur)->user = strdup (val);
else
global.user = strdup (val);
}
else if (!strncasecmp ("pass", cmd, 4))
{
if (*cur)
(*cur)->pass = strdup (val);
else
global.pass = strdup (val);
}
else if (!strncasecmp ("port", cmd, 4))
{
if (*cur)
(*cur)->port = atoi (val);
else
global.port = atoi (val);
}
else if (!strncasecmp ("box", cmd, 3))
{
if (*cur)
(*cur)->box = strdup (val);
else
global.box = strdup (val);
}
else if (!strncasecmp ("alias", cmd, 5))
{
if (*cur)
(*cur)->alias = strdup (val);
}
else if (!strncasecmp ("maxsize", cmd, 7))
{
if (*cur)
(*cur)->max_size = atol (val);
else
global.max_size = atol (val);
}
else if (!strncasecmp ("UseNamespace", cmd, 12))
{
if (*cur)
(*cur)->use_namespace = (strcasecmp (val, "yes") == 0);
else
global.use_namespace = (strcasecmp (val, "yes") == 0);
}
#if HAVE_LIBSSL
else if (!strncasecmp ("CertificateFile", cmd, 15))
{
if (*cur)
(*cur)->cert_file = strdup (val);
else
global.cert_file = strdup (val);
}
else if (!strncasecmp ("RequireSSL", cmd, 10))
{
if (*cur)
(*cur)->require_ssl = (strcasecmp (val, "yes") == 0);
else
global.require_ssl = (strcasecmp (val, "yes") == 0);
}
else if (!strncasecmp ("UseSSLv2", cmd, 8))
{
if (*cur)
(*cur)->use_sslv2 = (strcasecmp (val, "yes") == 0);
else
global.use_sslv2 = (strcasecmp (val, "yes") == 0);
}
else if (!strncasecmp ("UseSSLv3", cmd, 8))
{
if (*cur)
(*cur)->use_sslv3 = (strcasecmp (val, "yes") == 0);
else
global.use_sslv3 = (strcasecmp (val, "yes") == 0);
}
else if (!strncasecmp ("UseTLSv1", cmd, 8))
{
if (*cur)
(*cur)->use_tlsv1 = (strcasecmp (val, "yes") == 0);
else
global.use_tlsv1 = (strcasecmp (val, "yes") == 0);
}
else if (!strncasecmp ("RequireCRAM", cmd, 11))
{
if (*cur)
(*cur)->require_cram = (strcasecmp (val, "yes") == 0);
else
global.require_cram = (strcasecmp (val, "yes") == 0);
}
#endif
else if (buf[0])
printf ("%s:%d:unknown command:%s", path, line, cmd);
}
fclose (fp);
}
static config_t *
find_box (const char *s)
{
config_t *p = box;
for (; p; p = p->next)
if (!strcmp (s, p->path) || (p->alias && !strcmp (s, p->alias)))
return p;
return 0;
}
char *
next_arg (char **s)
{
char *ret;
if (!s)
return 0;
if (!*s)
return 0;
while (isspace ((unsigned char) **s))
(*s)++;
if (!**s)
{
*s = 0;
return 0;
}
ret = *s;
while (**s && !isspace ((unsigned char) **s))
(*s)++;
if (**s)
*(*s)++ = 0;
if (!**s)
*s = 0;
return ret;
}
int
main (int argc, char **argv)
{
int i;
config_t *box;
mailbox_t *mail;
imap_t *imap = 0;
int expunge = 0; /* by default, don't delete anything */
int fast = 0;
int delete = 0;
char *config = 0;
struct passwd *pw;
pw = getpwuid (getuid ());
/* defaults */
memset (&global, 0, sizeof (global));
global.port = 143;
global.box = "INBOX";
global.user = strdup (pw->pw_name);
global.max_size = 0;
global.use_namespace = 1;
#if HAVE_LIBSSL
/* this will probably annoy people, but its the best default just in
* case people forget to turn it on
*/
global.require_ssl = 1;
global.use_sslv2 = 1;
global.use_sslv3 = 1;
global.use_tlsv1 = 1;
#endif
#if HAVE_GETOPT_LONG
while ((i = getopt_long (argc, argv, "defhp:u:r:s:vV", Opts, NULL)) != -1)
#else
while ((i = getopt (argc, argv, "defhp:u:r:s:vV")) != -1)
#endif
{
switch (i)
{
case 'c':
config = optarg;
break;
case 'd':
delete = 1;
break;
case 'e':
expunge = 1;
break;
case 'f':
fast = 1;
break;
case 'p':
global.port = atoi (optarg);
break;
case 'r':
global.box = optarg;
break;
case 's':
global.host = optarg;
break;
case 'u':
free (global.user);
global.user = optarg;
break;
case 'V':
Verbose = 1;
break;
case 'v':
version ();
default:
usage ();
}
}
if (!argv[optind])
{
puts ("No box specified");
usage ();
}
gethostname (Hostname, sizeof (Hostname));
load_config (config);
for (; argv[optind]; optind++)
{
box = find_box (argv[optind]);
if (!box)
{
/* if enough info is given on the command line, don't worry if
* the mailbox isn't defined.
*/
if (!global.host)
{
puts ("No such mailbox");
exit (1);
}
global.path = argv[optind];
box = &global;
}
if (!box->pass)
{
char *pass = getpass ("Password:");
if (!pass)
{
puts ("Aborting, no password");
exit (1);
}
box->pass = strdup (pass);
}
printf ("Reading %s\n", box->path);
mail = maildir_open (box->path, fast);
if (!mail)
{
puts ("Unable to load mailbox");
exit (1);
}
imap = imap_open (box, fast ? mail->maxuid + 1 : 1, imap);
if (!imap)
exit (1);
puts ("Synchronizing");
i = delete ? SYNC_DELETE : 0;
i |= expunge ? SYNC_EXPUNGE : 0;
if (sync_mailbox (mail, imap, i, box->max_size))
exit (1);
if (!fast)
{
if (expunge && (imap->deleted || mail->deleted))
{
/* remove messages marked for deletion */
printf ("Expunging %d messages from server\n", imap->deleted);
if (imap_expunge (imap))
exit (1);
printf ("Expunging %d messages from local mailbox\n",
mail->deleted);
if (maildir_expunge (mail, 0))
exit (1);
}
/* remove messages deleted from server. this can safely be an
* `else' clause since dead messages are marked as deleted by
* sync_mailbox.
*/
else if (delete)
maildir_expunge (mail, 1);
/* write changed flags back to the mailbox */
printf ("Committing changes to %s\n", mail->path);
if (maildir_sync (mail))
exit (1);
}
maildir_close (mail);
}
/* gracefully close connection to the IMAP server */
imap_close (imap);
exit (0);
}

View file

@ -1,62 +0,0 @@
#!/bin/sh
#
# This script will extract the necessary certificate from the IMAP server
# It assumes that an attacker isn't trying to spoof you when you connect
# to the IMAP server! You're better off downloading the certificate
# from a trusted source.
#
# Copyright (C) 2003 Theodore Ts'o <tytso@alum.mit.edu>
# 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 2 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
if [ $# != 1 ]; then
echo "Usage: $0 <host>" >&2
exit 1
fi
HOST=$1
seed=`date '+%s'`
try=0
while :; do
TMPDIR=/tmp/get-cert.$$.$seed
mkdir $TMPDIR 2> /dev/null && break
if [ $try = 1000 ]; then
echo "Cannot create temporary directory." >&2
exit 1
fi
try=`expr $try + 1`
seed=`expr \( \( $seed \* 1103515245 \) + 12345 \) % 2147483648`
done
TMPFILE=$TMPDIR/get-cert
ERRFILE=$TMPDIR/get-cert-err
CERTFILE=$TMPDIR/cert
echo QUIT | openssl s_client -connect $HOST:993 -showcerts \
> $TMPFILE 2> $ERRFILE
sed -e '1,/^-----BEGIN CERTIFICATE-----/d' \
-e '/^-----END CERTIFICATE-----/,$d' < $TMPFILE > $CERTFILE
if test -s $CERTFILE ; then
echo -----BEGIN CERTIFICATE-----
cat $CERTFILE
echo -----END CERTIFICATE-----
else
echo "Couldn't retrieve certificate. openssl reported the following errors:"
cat $ERRFILE
fi
rm -r $TMPDIR

8
src/.gitignore vendored
View file

@ -1,8 +0,0 @@
/drv_proxy.inc
/mbsync
/mdconvert
/tst_timers
/tmp/
.deps/
*.o

View file

@ -1,28 +0,0 @@
mbsync_SOURCES = main.c sync.c config.c util.c socket.c driver.c drv_imap.c drv_maildir.c drv_proxy.c
mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS) $(KEYCHAIN_LIBS)
noinst_HEADERS = common.h config.h driver.h sync.h socket.h
drv_proxy.$(OBJEXT): drv_proxy.inc
drv_proxy.inc: $(srcdir)/driver.h $(srcdir)/drv_proxy.c $(srcdir)/drv_proxy_gen.pl
perl $(srcdir)/drv_proxy_gen.pl $(srcdir)/driver.h $(srcdir)/drv_proxy.c drv_proxy.inc
mdconvert_SOURCES = mdconvert.c
mdconvert_LDADD = $(DB_LIBS)
if with_mdconvert
mdconvert_prog = mdconvert
mdconvert_man = mdconvert.1
endif
EXTRA_PROGRAMS = tst_timers
tst_timers_SOURCES = tst_timers.c util.c
bin_PROGRAMS = mbsync $(mdconvert_prog)
man_MANS = mbsync.1 $(mdconvert_man)
exampledir = $(docdir)/examples
example_DATA = mbsyncrc.sample
EXTRA_DIST = drv_proxy_gen.pl run-tests.pl $(example_DATA) $(man_MANS)
CLEANFILES = drv_proxy.inc

View file

@ -1,264 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef COMMON_H
#define COMMON_H
#include <autodefs.h>
#include <sys/types.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
#define as(ar) (sizeof(ar)/sizeof(ar[0]))
#define stringify__(x) #x
#define stringify(x) stringify__(x)
// From https://stackoverflow.com/a/62984543/3685191
#define deparen(x) esc_(ish_ x)
#define esc_(...) esc__(__VA_ARGS__)
#define esc__(...) van_ ## __VA_ARGS__
#define ish_(...) ish_ __VA_ARGS__
#define van_ish_
#define shifted_bit(in, from, to) \
((int)(((uint)(in) / (from > to ? from / to : 1) * (to > from ? to / from : 1)) & to))
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_UNUSED __attribute__((unused))
# define ATTR_NORETURN __attribute__((noreturn))
# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
#else
# define ATTR_UNUSED
# define ATTR_NORETURN
# define ATTR_PRINTFLIKE(fmt,var)
#endif
#if defined(__clang__)
# define DO_PRAGMA__(text) _Pragma(#text)
# define DIAG_PUSH DO_PRAGMA__(clang diagnostic push)
# define DIAG_POP DO_PRAGMA__(clang diagnostic pop)
# define DIAG_DISABLE(text) DO_PRAGMA__(clang diagnostic ignored text)
#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)
# define DO_PRAGMA__(text) _Pragma(#text)
# define DIAG_PUSH DO_PRAGMA__(GCC diagnostic push)
# define DIAG_POP DO_PRAGMA__(GCC diagnostic pop)
# define DIAG_DISABLE(text) DO_PRAGMA__(GCC diagnostic ignored text)
#else
# define DIAG_PUSH
# define DIAG_POP
# define DIAG_DISABLE(text)
#endif
#if __GNUC__ >= 7 || defined(__clang__)
# define FALLTHROUGH __attribute__((fallthrough));
#else
# define FALLTHROUGH
#endif
#ifdef __GNUC__
# define INLINE __inline__
#else
# define INLINE
#endif
#define EXE "mbsync"
/* main.c */
#define DEBUG_CRASH 0x01
#define DEBUG_MAILDIR 0x02
#define DEBUG_NET 0x04
#define DEBUG_NET_ALL 0x08
#define DEBUG_SYNC 0x10
#define DEBUG_MAIN 0x20
#define DEBUG_DRV 0x40
#define DEBUG_DRV_ALL 0x80
#define DEBUG_ALL (0xFF & ~(DEBUG_NET_ALL | DEBUG_DRV_ALL))
#define QUIET 0x100
#define VERYQUIET 0x200
#define PROGRESS 0x400
#define VERBOSE 0x800
#define KEEPJOURNAL 0x1000
#define ZERODELAY 0x2000
#define FORCEASYNC 0x4000
extern int DFlags;
extern int JLimit;
extern int UseFSync;
extern char FieldDelimiter;
extern int Pid;
extern char Hostname[256];
extern const char *Home;
extern uint BufferLimit;
extern int new_total[2], new_done[2];
extern int flags_total[2], flags_done[2];
extern int trash_total[2], trash_done[2];
void stats( void );
/* util.c */
void ATTR_PRINTFLIKE(2, 0) vdebug( int, const char *, va_list va );
void ATTR_PRINTFLIKE(2, 0) vdebugn( int, const char *, va_list va );
void ATTR_PRINTFLIKE(1, 2) info( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) infon( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) progress( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) notice( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) warn( const char *, ... );
void ATTR_PRINTFLIKE(1, 2) error( const char *, ... );
void ATTR_PRINTFLIKE(1, 0) vsys_error( const char *, va_list va );
void ATTR_PRINTFLIKE(1, 2) sys_error( const char *, ... );
void flushn( void );
typedef struct string_list {
struct string_list *next;
char string[1];
} string_list_t;
void add_string_list_n( string_list_t **list, const char *str, uint len );
void add_string_list( string_list_t **list, const char *str );
void free_string_list( string_list_t *list );
#ifndef HAVE_MEMRCHR
void *memrchr( const void *s, int c, size_t n );
#endif
#ifndef HAVE_STRNLEN
size_t strnlen( const char *str, size_t maxlen );
#endif
int starts_with( const char *str, int strl, const char *cmp, uint cmpl );
int starts_with_upper( const char *str, int strl, const char *cmp, uint cmpl );
int equals( const char *str, int strl, const char *cmp, uint cmpl );
#ifndef HAVE_TIMEGM
time_t timegm( struct tm *tm );
#endif
void *nfmalloc( size_t sz );
void *nfcalloc( size_t sz );
void *nfrealloc( void *mem, size_t sz );
char *nfstrndup( const char *str, size_t nchars );
char *nfstrdup( const char *str );
int ATTR_PRINTFLIKE(2, 0) nfvasprintf( char **str, const char *fmt, va_list va );
int ATTR_PRINTFLIKE(2, 3) nfasprintf( char **str, const char *fmt, ... );
int ATTR_PRINTFLIKE(3, 4) nfsnprintf( char *buf, int blen, const char *fmt, ... );
void ATTR_NORETURN oob( void );
void ATTR_NORETURN oom( void );
char *expand_strdup( const char *s );
int map_name( const char *arg, char **result, uint reserve, const char *in, const char *out );
#define DEFINE_ARRAY_TYPE(T) \
typedef struct { \
T *data; \
uint size; \
} T##_array_t; \
typedef union { \
T##_array_t array; \
struct { \
T *data; \
uint size; \
uint alloc; \
}; \
} T##_array_alloc_t; \
static INLINE T *T##_array_append( T##_array_alloc_t *arr ) \
{ \
if (arr->size == arr->alloc) { \
arr->alloc = arr->alloc * 2 + 100; \
arr->data = nfrealloc( arr->data, arr->alloc * sizeof(T) ); \
} \
return &arr->data[arr->size++]; \
}
#define ARRAY_INIT(arr) \
do { (arr)->data = NULL; (arr)->size = (arr)->alloc = 0; } while (0)
#define ARRAY_SQUEEZE(arr) \
do { \
(arr)->data = nfrealloc( (arr)->data, (arr)->size * sizeof((arr)->data[0]) ); \
} while (0)
DEFINE_ARRAY_TYPE(uint)
void sort_uint_array( uint_array_t array );
int find_uint_array( const uint_array_t array, uint value );
void arc4_init( void );
uchar arc4_getbyte( void );
uint bucketsForSize( uint size );
typedef struct list_head {
struct list_head *next, *prev;
} list_head_t;
typedef struct notifier {
struct notifier *next;
void (*cb)( int what, void *aux );
void *aux;
#ifdef HAVE_POLL_H
uint index;
#else
int fd;
short events;
#endif
} notifier_t;
#ifdef HAVE_POLL_H
# include <poll.h>
#else
# define POLLIN 1
# define POLLOUT 4
# define POLLERR 8
#endif
void init_notifier( notifier_t *sn, int fd, void (*cb)( int, void * ), void *aux );
void conf_notifier( notifier_t *sn, short and_events, short or_events );
short notifier_config( notifier_t *sn );
void wipe_notifier( notifier_t *sn );
typedef struct {
list_head_t links;
void (*cb)( void *aux );
void *aux;
time_t timeout;
} wakeup_t;
void init_wakeup( wakeup_t *tmr, void (*cb)( void * ), void *aux );
void conf_wakeup( wakeup_t *tmr, int timeout );
void wipe_wakeup( wakeup_t *tmr );
static INLINE int ATTR_UNUSED pending_wakeup( wakeup_t *tmr ) { return tmr->links.next != NULL; }
void main_loop( void );
#endif

View file

@ -1,536 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2011 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "config.h"
#include "sync.h"
#include <assert.h>
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static store_conf_t *stores;
char *
get_arg( conffile_t *cfile, int required, int *comment )
{
char *ret, *p, *t;
int escaped, quoted;
char c;
p = cfile->rest;
assert( p );
while ((c = *p) && isspace( (uchar)c ))
p++;
if (!c || c == '#') {
if (comment)
*comment = (c == '#');
if (required) {
error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
cfile->err = 1;
}
ret = NULL;
} else {
for (escaped = 0, quoted = 0, ret = t = p; c; c = *p) {
p++;
if (escaped && c >= 32) {
escaped = 0;
*t++ = c;
} else if (c == '\\')
escaped = 1;
else if (c == '"')
quoted ^= 1;
else if (!quoted && isspace( (uchar)c ))
break;
else
*t++ = c;
}
*t = 0;
if (escaped) {
error( "%s:%d: unterminated escape sequence\n", cfile->file, cfile->line );
cfile->err = 1;
ret = NULL;
}
if (quoted) {
error( "%s:%d: missing closing quote\n", cfile->file, cfile->line );
cfile->err = 1;
ret = NULL;
}
}
cfile->rest = p;
return ret;
}
char
parse_bool( conffile_t *cfile )
{
if (!strcasecmp( cfile->val, "yes" ) ||
!strcasecmp( cfile->val, "true" ) ||
!strcasecmp( cfile->val, "on" ) ||
!strcmp( cfile->val, "1" ))
return 1;
if (strcasecmp( cfile->val, "no" ) &&
strcasecmp( cfile->val, "false" ) &&
strcasecmp( cfile->val, "off" ) &&
strcmp( cfile->val, "0" )) {
error( "%s:%d: invalid boolean value '%s'\n",
cfile->file, cfile->line, cfile->val );
cfile->err = 1;
}
return 0;
}
int
parse_int( conffile_t *cfile )
{
char *p;
int ret;
ret = strtol( cfile->val, &p, 10 );
if (*p) {
error( "%s:%d: invalid integer value '%s'\n",
cfile->file, cfile->line, cfile->val );
cfile->err = 1;
return 0;
}
return ret;
}
uint
parse_size( conffile_t *cfile )
{
char *p;
uint ret;
ret = strtoul( cfile->val, &p, 10 );
if (*p == 'k' || *p == 'K')
ret *= 1024, p++;
else if (*p == 'm' || *p == 'M')
ret *= 1024 * 1024, p++;
if (*p == 'b' || *p == 'B')
p++;
if (*p) {
fprintf (stderr, "%s:%d: invalid size '%s'\n",
cfile->file, cfile->line, cfile->val);
cfile->err = 1;
return 0;
}
return ret;
}
static const struct {
int op;
const char *name;
} boxOps[] = {
{ OP_EXPUNGE, "Expunge" },
{ OP_CREATE, "Create" },
{ OP_REMOVE, "Remove" },
};
static int
getopt_helper( conffile_t *cfile, int *cops, channel_conf_t *conf )
{
char *arg;
uint i;
if (!strcasecmp( "Sync", cfile->cmd )) {
arg = cfile->val;
do
if (!strcasecmp( "Push", arg ))
*cops |= XOP_PUSH;
else if (!strcasecmp( "Pull", arg ))
*cops |= XOP_PULL;
else if (!strcasecmp( "ReNew", arg ))
*cops |= OP_RENEW;
else if (!strcasecmp( "New", arg ))
*cops |= OP_NEW;
else if (!strcasecmp( "Delete", arg ))
*cops |= OP_DELETE;
else if (!strcasecmp( "Flags", arg ))
*cops |= OP_FLAGS;
else if (!strcasecmp( "PullReNew", arg ))
conf->ops[N] |= OP_RENEW;
else if (!strcasecmp( "PullNew", arg ))
conf->ops[N] |= OP_NEW;
else if (!strcasecmp( "PullDelete", arg ))
conf->ops[N] |= OP_DELETE;
else if (!strcasecmp( "PullFlags", arg ))
conf->ops[N] |= OP_FLAGS;
else if (!strcasecmp( "PushReNew", arg ))
conf->ops[F] |= OP_RENEW;
else if (!strcasecmp( "PushNew", arg ))
conf->ops[F] |= OP_NEW;
else if (!strcasecmp( "PushDelete", arg ))
conf->ops[F] |= OP_DELETE;
else if (!strcasecmp( "PushFlags", arg ))
conf->ops[F] |= OP_FLAGS;
else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
*cops |= XOP_PULL|XOP_PUSH;
else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg )) {
error( "%s:%d: invalid Sync arg '%s'\n",
cfile->file, cfile->line, arg );
cfile->err = 1;
}
while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[F] |= XOP_HAVE_TYPE;
} else if (!strcasecmp( "SyncState", cfile->cmd ))
conf->sync_state = expand_strdup( cfile->val );
else if (!strcasecmp( "CopyArrivalDate", cfile->cmd ))
conf->use_internal_date = parse_bool( cfile );
else if (!strcasecmp( "MaxMessages", cfile->cmd ))
conf->max_messages = parse_int( cfile );
else if (!strcasecmp( "ExpireUnread", cfile->cmd ))
conf->expire_unread = parse_bool( cfile );
else {
for (i = 0; i < as(boxOps); i++) {
if (!strcasecmp( boxOps[i].name, cfile->cmd )) {
int op = boxOps[i].op;
arg = cfile->val;
do {
if (!strcasecmp( "Both", arg )) {
*cops |= op;
} else if (!strcasecmp( "Far", arg )) {
conf->ops[F] |= op;
} else if (!strcasecmp( "Master", arg )) { // Pre-1.4 legacy
conf->ops[F] |= op;
cfile->ms_warn = 1;
} else if (!strcasecmp( "Near", arg )) {
conf->ops[N] |= op;
} else if (!strcasecmp( "Slave", arg )) { // Pre-1.4 legacy
conf->ops[N] |= op;
cfile->ms_warn = 1;
} else if (strcasecmp( "None", arg )) {
error( "%s:%d: invalid %s arg '%s'\n",
cfile->file, cfile->line, boxOps[i].name, arg );
cfile->err = 1;
}
} while ((arg = get_arg( cfile, ARG_OPTIONAL, NULL )));
conf->ops[F] |= op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE);
return 1;
}
}
return 0;
}
return 1;
}
int
getcline( conffile_t *cfile )
{
char *arg;
int comment;
if (cfile->rest && (arg = get_arg( cfile, ARG_OPTIONAL, NULL ))) {
error( "%s:%d: excess token '%s'\n", cfile->file, cfile->line, arg );
cfile->err = 1;
}
while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
cfile->line++;
cfile->rest = cfile->buf;
if (!(cfile->cmd = get_arg( cfile, ARG_OPTIONAL, &comment ))) {
if (comment)
continue;
return 1;
}
if (!(cfile->val = get_arg( cfile, ARG_REQUIRED, NULL )))
continue;
return 1;
}
return 0;
}
/* XXX - this does not detect None conflicts ... */
int
merge_ops( int cops, int ops[] )
{
int aops, op;
uint i;
aops = ops[F] | ops[N];
if (ops[F] & XOP_HAVE_TYPE) {
if (aops & OP_MASK_TYPE) {
if (aops & cops & OP_MASK_TYPE) {
cfl:
error( "Conflicting Sync args specified.\n" );
return 1;
}
ops[F] |= cops & OP_MASK_TYPE;
ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PULL) {
if (ops[N] & OP_MASK_TYPE)
goto cfl;
ops[N] |= OP_MASK_TYPE;
}
if (cops & XOP_PUSH) {
if (ops[F] & OP_MASK_TYPE)
goto cfl;
ops[F] |= OP_MASK_TYPE;
}
} else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
if (!(cops & OP_MASK_TYPE))
cops |= OP_MASK_TYPE;
else if (!(cops & XOP_MASK_DIR))
cops |= XOP_PULL|XOP_PUSH;
if (cops & XOP_PULL)
ops[N] |= cops & OP_MASK_TYPE;
if (cops & XOP_PUSH)
ops[F] |= cops & OP_MASK_TYPE;
}
}
for (i = 0; i < as(boxOps); i++) {
op = boxOps[i].op;
if (ops[F] & (op * (XOP_HAVE_EXPUNGE / OP_EXPUNGE))) {
if (aops & cops & op) {
error( "Conflicting %s args specified.\n", boxOps[i].name );
return 1;
}
ops[F] |= cops & op;
ops[N] |= cops & op;
}
}
return 0;
}
int
load_config( const char *where )
{
conffile_t cfile;
store_conf_t *store, **storeapp = &stores;
channel_conf_t *channel, **channelapp = &channels;
group_conf_t *group, **groupapp = &groups;
string_list_t *chanlist, **chanlistapp;
char *arg, *p;
uint len, max_size;
int cops, gcops, glob_ok, fn, i;
char path[_POSIX_PATH_MAX];
char buf[1024];
if (!where) {
nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
cfile.file = path;
} else
cfile.file = where;
info( "Reading configuration file %s\n", cfile.file );
if (!(cfile.fp = fopen( cfile.file, "r" ))) {
sys_error( "Cannot open config file '%s'", cfile.file );
return 1;
}
buf[sizeof(buf) - 1] = 0;
cfile.buf = buf;
cfile.bufl = sizeof(buf) - 1;
cfile.line = 0;
cfile.err = 0;
cfile.ms_warn = 0;
cfile.rest = NULL;
gcops = 0;
glob_ok = 1;
global_conf.expire_unread = -1;
reloop:
while (getcline( &cfile )) {
if (!cfile.cmd)
continue;
for (i = 0; i < N_DRIVERS; i++)
if (drivers[i]->parse_store( &cfile, &store )) {
if (store) {
if (!store->max_size)
store->max_size = UINT_MAX;
if (!store->flat_delim)
store->flat_delim = "";
*storeapp = store;
storeapp = &store->next;
*storeapp = NULL;
}
glob_ok = 0;
goto reloop;
}
if (!strcasecmp( "Channel", cfile.cmd ))
{
channel = nfcalloc( sizeof(*channel) );
channel->name = nfstrdup( cfile.val );
channel->max_messages = global_conf.max_messages;
channel->expire_unread = global_conf.expire_unread;
channel->use_internal_date = global_conf.use_internal_date;
cops = 0;
max_size = UINT_MAX;
while (getcline( &cfile ) && cfile.cmd) {
if (!strcasecmp( "MaxSize", cfile.cmd ))
max_size = parse_size( &cfile );
else if (!strcasecmp( "Pattern", cfile.cmd ) ||
!strcasecmp( "Patterns", cfile.cmd ))
{
arg = cfile.val;
do
add_string_list( &channel->patterns, arg );
while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL )));
}
else if (!strcasecmp( "Far", cfile.cmd )) {
fn = F;
goto linkst;
} else if (!strcasecmp( "Master", cfile.cmd )) { // Pre-1.4 legacy
fn = F;
goto olinkst;
} else if (!strcasecmp( "Near", cfile.cmd )) {
fn = N;
goto linkst;
} else if (!strcasecmp( "Slave", cfile.cmd )) { // Pre-1.4 legacy
fn = N;
olinkst:
cfile.ms_warn = 1;
linkst:
if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
error( "%s:%d: malformed mailbox spec\n",
cfile.file, cfile.line );
cfile.err = 1;
continue;
}
*p = 0;
for (store = stores; store; store = store->next)
if (!strcmp( store->name, cfile.val + 1 )) {
channel->stores[fn] = store;
goto stpcom;
}
error( "%s:%d: unknown store '%s'\n",
cfile.file, cfile.line, cfile.val + 1 );
cfile.err = 1;
continue;
stpcom:
if (*++p)
channel->boxes[fn] = nfstrdup( p );
} else if (!getopt_helper( &cfile, &cops, channel )) {
error( "%s:%d: keyword '%s' is not recognized in Channel sections\n",
cfile.file, cfile.line, cfile.cmd );
cfile.err = 1;
}
}
if (!channel->stores[F]) {
error( "channel '%s' refers to no far side store\n", channel->name );
cfile.err = 1;
} else if (!channel->stores[N]) {
error( "channel '%s' refers to no near side store\n", channel->name );
cfile.err = 1;
} else if (merge_ops( cops, channel->ops ))
cfile.err = 1;
else {
if (max_size != UINT_MAX) {
if (!max_size)
max_size = UINT_MAX;
channel->stores[F]->max_size = channel->stores[N]->max_size = max_size;
}
*channelapp = channel;
channelapp = &channel->next;
}
glob_ok = 0;
goto reloop;
}
else if (!strcasecmp( "Group", cfile.cmd ))
{
group = nfmalloc( sizeof(*group) );
group->name = nfstrdup( cfile.val );
*groupapp = group;
groupapp = &group->next;
*groupapp = NULL;
chanlistapp = &group->channels;
*chanlistapp = NULL;
while ((arg = get_arg( &cfile, ARG_OPTIONAL, NULL ))) {
addone:
len = strlen( arg );
chanlist = nfmalloc( sizeof(*chanlist) + len );
memcpy( chanlist->string, arg, len + 1 );
*chanlistapp = chanlist;
chanlistapp = &chanlist->next;
*chanlistapp = NULL;
}
while (getcline( &cfile ) && cfile.cmd) {
if (!strcasecmp( "Channel", cfile.cmd ) ||
!strcasecmp( "Channels", cfile.cmd ))
{
arg = cfile.val;
goto addone;
}
else
{
error( "%s:%d: keyword '%s' is not recognized in Group sections\n",
cfile.file, cfile.line, cfile.cmd );
cfile.err = 1;
}
}
glob_ok = 0;
goto reloop;
}
else if (!strcasecmp( "FSync", cfile.cmd ))
{
UseFSync = parse_bool( &cfile );
}
else if (!strcasecmp( "FieldDelimiter", cfile.cmd ))
{
if (strlen( cfile.val ) != 1) {
error( "%s:%d: Field delimiter must be exactly one character long\n", cfile.file, cfile.line );
cfile.err = 1;
} else {
FieldDelimiter = cfile.val[0];
if (!ispunct( FieldDelimiter )) {
error( "%s:%d: Field delimiter must be a punctuation character\n", cfile.file, cfile.line );
cfile.err = 1;
}
}
}
else if (!strcasecmp( "BufferLimit", cfile.cmd ))
{
BufferLimit = parse_size( &cfile );
if (!BufferLimit) {
error( "%s:%d: BufferLimit cannot be zero\n", cfile.file, cfile.line );
cfile.err = 1;
}
}
else if (!getopt_helper( &cfile, &gcops, &global_conf ))
{
error( "%s:%d: '%s' is not a recognized section-starting or global keyword\n",
cfile.file, cfile.line, cfile.cmd );
cfile.err = 1;
while (getcline( &cfile ))
if (!cfile.cmd)
goto reloop;
break;
}
if (!glob_ok) {
error( "%s:%d: global options may not follow sections\n",
cfile.file, cfile.line );
cfile.err = 1;
}
}
fclose (cfile.fp);
if (cfile.ms_warn)
warn( "Notice: Master/Slave are deprecated; use Far/Near instead.\n" );
cfile.err |= merge_ops( gcops, global_conf.ops );
if (!global_conf.sync_state)
global_conf.sync_state = expand_strdup( "~/." EXE "/" );
return cfile.err;
}

View file

@ -1,51 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "common.h"
typedef struct {
const char *file;
FILE *fp;
char *buf;
int bufl;
int line;
int err;
int ms_warn;
char *cmd, *val, *rest;
} conffile_t;
#define ARG_OPTIONAL 0
#define ARG_REQUIRED 1
char *get_arg( conffile_t *cfile, int required, int *comment );
char parse_bool( conffile_t *cfile );
int parse_int( conffile_t *cfile );
uint parse_size( conffile_t *cfile );
int getcline( conffile_t *cfile );
int merge_ops( int cops, int ops[] );
int load_config( const char *filename );
#endif

View file

@ -1,78 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "driver.h"
#include <stdlib.h>
#include <string.h>
driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
uint
count_generic_messages( message_t *msgs )
{
uint count = 0;
for (; msgs; msgs = msgs->next)
count++;
return count;
}
void
free_generic_messages( message_t *msgs )
{
message_t *tmsg;
for (; msgs; msgs = tmsg) {
tmsg = msgs->next;
free( msgs->msgid );
free( msgs );
}
}
void
parse_generic_store( store_conf_t *store, conffile_t *cfg, const char *type )
{
if (!strcasecmp( "Trash", cfg->cmd )) {
store->trash = nfstrdup( cfg->val );
} else if (!strcasecmp( "TrashRemoteNew", cfg->cmd )) {
store->trash_remote_new = parse_bool( cfg );
} else if (!strcasecmp( "TrashNewOnly", cfg->cmd )) {
store->trash_only_new = parse_bool( cfg );
} else if (!strcasecmp( "MaxSize", cfg->cmd )) {
store->max_size = parse_size( cfg );
} else if (!strcasecmp( "MapInbox", cfg->cmd )) {
store->map_inbox = nfstrdup( cfg->val );
} else if (!strcasecmp( "Flatten", cfg->cmd )) {
const char *p;
for (p = cfg->val; *p; p++) {
if (*p == '/') {
error( "%s:%d: flattened hierarchy delimiter cannot contain the canonical delimiter '/'\n", cfg->file, cfg->line );
cfg->err = 1;
return;
}
}
store->flat_delim = nfstrdup( cfg->val );
} else {
error( "%s:%d: keyword '%s' is not recognized in %s sections\n", cfg->file, cfg->line, cfg->cmd, type );
cfg->err = 1;
}
}

View file

@ -1,302 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef DRIVER_H
#define DRIVER_H
#include "config.h"
typedef struct driver driver_t;
#define FAIL_TEMP 0 /* Retry immediately (also: no error) */
#define FAIL_WAIT 1 /* Retry after some time (if at all) */
#define FAIL_FINAL 2 /* Don't retry until store reconfiguration */
#define STORE_CONF \
struct store_conf *next; \
char *name; \
driver_t *driver; \
const char *flat_delim; \
const char *map_inbox; \
const char *trash; \
uint max_size; /* off_t is overkill */ \
char trash_remote_new, trash_only_new;
typedef struct store_conf {
STORE_CONF
} store_conf_t;
/* For message->flags */
/* Keep the mailbox driver flag definitions in sync: */
/* grep for MAILBOX_DRIVER_FLAG */
/* The order is according to alphabetical maildir flag sort */
#define F_DRAFT (1<<0) /* Draft */
#define F_FLAGGED (1<<1) /* Flagged */
#define F_FORWARDED (1<<2) /* Passed */
#define F_ANSWERED (1<<3) /* Replied */
#define F_SEEN (1<<4) /* Seen */
#define F_DELETED (1<<5) /* Trashed */
#define NUM_FLAGS 6
/* For message->status */
#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
#define M_DEAD (1<<1) /* expunged */
#define M_FLAGS (1<<2) /* flags fetched */
// The following are only for IMAP FETCH response parsing
#define M_DATE (1<<3)
#define M_SIZE (1<<4)
#define M_BODY (1<<5)
#define M_HEADER (1<<6)
#define TUIDL 12
#define MESSAGE(message) \
message *next; \
struct sync_rec *srec; \
char *msgid; /* owned */ \
/* string_list_t *keywords; */ \
uint size; /* zero implies "not fetched" */ \
uint uid; \
uchar flags, status; \
char tuid[TUIDL];
typedef struct message {
MESSAGE(struct message)
} message_t;
// For driver_t->prepare_load_box(), which may amend the passed flags.
// The drivers don't use the first two, but may set them if loading the
// particular range is required to handle some other flag; note that these
// ranges may overlap.
#define OPEN_OLD (1<<0) // Paired messages *in* this store.
#define OPEN_NEW (1<<1) // Messages (possibly) not yet propagated *from* this store.
#define OPEN_FLAGS (1<<2) // Note that fetch_msg() gets the flags regardless.
#define OPEN_NEW_SIZE (1<<4)
#define OPEN_EXPUNGE (1<<5)
#define OPEN_SETFLAGS (1<<6)
#define OPEN_APPEND (1<<7)
#define OPEN_FIND (1<<8)
#define OPEN_OLD_IDS (1<<9)
#define UIDVAL_BAD ((uint)-1)
#define STORE(store) \
store *next; \
driver_t *driver; \
store##_conf *conf; /* foreign */
typedef struct store {
STORE(struct store)
} store_t;
typedef struct {
char *data;
uint len;
time_t date;
uchar flags;
} msg_data_t;
#define DRV_OK 0
/* Message went missing, or mailbox is full, etc. */
#define DRV_MSG_BAD 1
/* Something is wrong with the current mailbox - probably it is somehow inaccessible. */
#define DRV_BOX_BAD 2
/* Failed to connect store. */
#define DRV_STORE_BAD 3
/* The command has been cancel()ed or cancel_store()d. */
#define DRV_CANCELED 4
/* All memory belongs to the driver's user, unless stated otherwise. */
// If the driver is NOT DRV_ASYNC, memory owned by the driver returned
// through callbacks MUST remain valid until a related subsequent command
// is invoked, as the proxy driver may deliver these pointers with delay.
/*
This flag says that the driver CAN store messages with CRLFs,
not that it must. The lack of it OTOH implies that it CANNOT,
and as CRLF is the canonical format, we convert.
*/
#define DRV_CRLF 1
/*
This flag says that the driver will act upon (DFlags & VERBOSE).
*/
#define DRV_VERBOSE 2
/*
This flag says that the driver operates asynchronously.
*/
#define DRV_ASYNC 4
#define LIST_INBOX 1
#define LIST_PATH 2
#define LIST_PATH_MAYBE 4
#define xint uint // For auto-generation of appropriate printf() formats.
struct driver {
/* Return driver capabilities. */
xint (*get_caps)( store_t *ctx );
/* Parse configuration. */
int (*parse_store)( conffile_t *cfg, store_conf_t **storep );
/* Close remaining server connections. All stores must be discarded first. */
void (*cleanup)( void );
/* Allocate a store with the given configuration. This is expected to
* return quickly, and must not fail. */
store_t *(*alloc_store)( store_conf_t *conf, const char *label );
/* When this callback is invoked (at most once per store), the store is fubar;
* call cancel_store() to dispose of it. */
void (*set_bad_callback)( store_t *ctx, void (*cb)( void *aux ), void *aux );
/* Open/connect the store. This may recycle existing server connections. */
void (*connect_store)( store_t *ctx,
void (*cb)( int sts, void *aux ), void *aux );
/* Discard the store. Underlying server connection may be kept alive. */
void (*free_store)( store_t *ctx );
/* Discard the store after a bad_callback. The server connections will be closed.
* Pending commands will have their callbacks synchronously invoked with DRV_CANCELED. */
void (*cancel_store)( store_t *ctx );
/* List the mailboxes in this store. Flags are ORed LIST_* values.
* The returned box list remains owned by the driver. */
void (*list_store)( store_t *ctx, int flags,
void (*cb)( int sts, string_list_t *boxes, void *aux ), void *aux );
/* Invoked before open_box(), this informs the driver which box is to be opened. */
int (*select_box)( store_t *ctx, const char *name );
/* Get the selected box' on-disk path, if applicable, null otherwise. */
const char *(*get_box_path)( store_t *ctx );
/* Create the selected mailbox. */
void (*create_box)( store_t *ctx,
void (*cb)( int sts, void *aux ), void *aux );
/* Open the selected mailbox.
* Note that this should not directly complain about failure to open. */
void (*open_box)( store_t *ctx,
void (*cb)( int sts, uint uidvalidity, void *aux ), void *aux );
/* Return the minimal UID the next stored message will have. */
uint (*get_uidnext)( store_t *ctx );
/* Return the flags that can be stored in the selected mailbox. */
xint (*get_supported_flags)( store_t *ctx );
/* Confirm that the open mailbox is empty. */
int (*confirm_box_empty)( store_t *ctx );
/* Delete the open mailbox. The mailbox is expected to be empty.
* Subfolders of the mailbox are *not* deleted.
* Some artifacts of the mailbox may remain, but they won't be
* recognized as a mailbox any more. */
void (*delete_box)( store_t *ctx,
void (*cb)( int sts, void *aux ), void *aux );
/* Remove the last artifacts of the open mailbox, as far as possible. */
int (*finish_delete_box)( store_t *ctx );
/* Invoked before load_box(), this informs the driver which operations (OP_*)
* will be performed on the mailbox. The driver may extend the set by implicitly
* needed or available operations. Returns this possibly extended set. */
xint (*prepare_load_box)( store_t *ctx, xint opts );
/* Load the message attributes needed to perform the requested operations.
* Consider only messages with UIDs between minuid and maxuid (inclusive)
* and those named in the excs array (smaller than minuid).
* The driver takes ownership of the excs array.
* Messages starting with finduid need to have the TUID populated when OPEN_FIND is set.
* Messages up to pairuid need to have the Message-Id populated when OPEN_OLD_IDS is set.
* Messages up to newuid need to have the size populated when OPEN_OLD_SIZE is set;
* likewise messages above newuid when OPEN_NEW_SIZE is set.
* The returned message list remains owned by the driver. */
void (*load_box)( store_t *ctx, uint minuid, uint maxuid, uint finduid, uint pairuid, uint newuid, uint_array_t excs,
void (*cb)( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux ), void *aux );
/* Fetch the contents and flags of the given message from the current mailbox.
* If minimal is non-zero, fetch only a placeholder for the requested message -
* ideally, this is precisely the header, but it may be more. */
void (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data, int minimal,
void (*cb)( int sts, void *aux ), void *aux );
/* Store the given message to either the current mailbox or the trash folder.
* If the new copy's UID can be immediately determined, return it, otherwise 0. */
void (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash,
void (*cb)( int sts, uint uid, void *aux ), void *aux );
/* Index the messages which have newly appeared in the mailbox, including their
* temporary UID headers. This is needed if store_msg() does not guarantee returning
* a UID; otherwise the driver needs to implement only the OPEN_FIND flag.
* The returned message list remains owned by the driver. */
void (*find_new_msgs)( store_t *ctx, uint newuid,
void (*cb)( int sts, message_t *msgs, void *aux ), void *aux );
/* Add/remove the named flags to/from the given message. The message may be either
* a pre-fetched one (in which case the in-memory representation is updated),
* or it may be identifed by UID only. The operation may be delayed until commit()
* is called. */
void (*set_msg_flags)( store_t *ctx, message_t *msg, uint uid, int add, int del,
void (*cb)( int sts, void *aux ), void *aux );
/* Move the given message from the current mailbox to the trash folder.
* This may expunge the original message immediately, but it needn't to. */
void (*trash_msg)( store_t *ctx, message_t *msg,
void (*cb)( int sts, void *aux ), void *aux );
/* Expunge deleted messages from the current mailbox and close it.
* There is no need to explicitly close a mailbox if no expunge is needed. */
void (*close_box)( store_t *ctx,
void (*cb)( int sts, void *aux ), void *aux );
/* Cancel queued commands which are not in flight yet; they will have their
* callbacks invoked with DRV_CANCELED. Afterwards, wait for the completion of
* the in-flight commands. If the store is canceled before this command completes,
* the callback will *not* be invoked. */
void (*cancel_cmds)( store_t *ctx,
void (*cb)( void *aux ), void *aux );
/* Commit any pending set_msg_flags() commands. */
void (*commit_cmds)( store_t *ctx );
/* Get approximate amount of memory occupied by the driver. */
uint (*get_memory_usage)( store_t *ctx );
/* Get the FAIL_* state of the driver. */
int (*get_fail_state)( store_conf_t *conf );
};
uint count_generic_messages( message_t * );
void free_generic_messages( message_t * );
void parse_generic_store( store_conf_t *store, conffile_t *cfg, const char *type );
store_t *proxy_alloc_store( store_t *real_ctx, const char *label );
#define N_DRIVERS 2
extern driver_t *drivers[N_DRIVERS];
extern driver_t maildir_driver, imap_driver, proxy_driver;
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,447 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2017 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "driver.h"
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
typedef struct gen_cmd gen_cmd_t;
typedef union proxy_store {
store_t gen;
struct {
STORE(union proxy_store)
const char *label; // foreign
uint ref_count;
driver_t *real_driver;
store_t *real_store;
gen_cmd_t *done_cmds, **done_cmds_append;
gen_cmd_t *check_cmds, **check_cmds_append;
wakeup_t wakeup;
void (*bad_callback)( void *aux );
void *bad_callback_aux;
};
} proxy_store_t;
static void ATTR_PRINTFLIKE(1, 2)
debug( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vdebug( DEBUG_DRV, msg, va );
va_end( va );
}
static void ATTR_PRINTFLIKE(1, 2)
debugn( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vdebugn( DEBUG_DRV, msg, va );
va_end( va );
}
/* Keep the mailbox driver flag definitions in sync: */
/* grep for MAILBOX_DRIVER_FLAG */
/* The order is according to alphabetical maildir flag sort */
static const char Flags[] = { 'D', 'F', 'P', 'R', 'S', 'T' };
static char *
proxy_make_flags( uchar flags, char *buf )
{
uint i, d;
for (d = 0, i = 0; i < as(Flags); i++)
if (flags & (1 << i))
buf[d++] = Flags[i];
buf[d] = 0;
return buf;
}
static void
proxy_store_deref( proxy_store_t *ctx )
{
if (!--ctx->ref_count) {
assert( !pending_wakeup( &ctx->wakeup ) );
free( ctx );
}
}
static int curr_tag;
#define GEN_CMD \
uint ref_count; \
int tag; \
proxy_store_t *ctx; \
gen_cmd_t *next; \
void (*queued_cb)( gen_cmd_t *gcmd );
struct gen_cmd {
GEN_CMD
};
#define GEN_STS_CMD \
GEN_CMD \
int sts;
typedef union {
gen_cmd_t gen;
struct {
GEN_STS_CMD
};
} gen_sts_cmd_t;
static gen_cmd_t *
proxy_cmd_new( proxy_store_t *ctx, uint sz )
{
gen_cmd_t *cmd = nfmalloc( sz );
cmd->ref_count = 2;
cmd->tag = ++curr_tag;
cmd->ctx = ctx;
ctx->ref_count++;
return cmd;
}
static void
proxy_cmd_done( gen_cmd_t *cmd )
{
if (!--cmd->ref_count) {
proxy_store_deref( cmd->ctx );
free( cmd );
}
}
static void
proxy_wakeup( void *aux )
{
proxy_store_t *ctx = (proxy_store_t *)aux;
gen_cmd_t *cmd = ctx->done_cmds;
assert( cmd );
if (!(ctx->done_cmds = cmd->next))
ctx->done_cmds_append = &ctx->done_cmds;
else
conf_wakeup( &ctx->wakeup, 0 );
cmd->queued_cb( cmd );
proxy_cmd_done( cmd );
}
static void
proxy_invoke_cb( gen_cmd_t *cmd, void (*cb)( gen_cmd_t * ), int checked, const char *name )
{
if (DFlags & FORCEASYNC) {
debug( "%s[% 2d] Callback queue %s%s\n", cmd->ctx->label, cmd->tag, name, checked ? " (checked)" : "" );
cmd->queued_cb = cb;
cmd->next = NULL;
if (checked) {
*cmd->ctx->check_cmds_append = cmd;
cmd->ctx->check_cmds_append = &cmd->next;
} else {
*cmd->ctx->done_cmds_append = cmd;
cmd->ctx->done_cmds_append = &cmd->next;
conf_wakeup( &cmd->ctx->wakeup, 0 );
}
} else {
cb( cmd );
proxy_cmd_done( cmd );
}
}
static void
proxy_flush_checked_cmds( proxy_store_t *ctx )
{
if (ctx->check_cmds) {
*ctx->done_cmds_append = ctx->check_cmds;
ctx->done_cmds_append = ctx->check_cmds_append;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->check_cmds = NULL;
conf_wakeup( &ctx->wakeup, 0 );
}
}
static void
proxy_cancel_checked_cmds( proxy_store_t *ctx )
{
gen_cmd_t *cmd;
while ((cmd = ctx->check_cmds)) {
if (!(ctx->check_cmds = cmd->next))
ctx->check_cmds_append = &ctx->check_cmds;
((gen_sts_cmd_t *)cmd)->sts = DRV_CANCELED;
cmd->queued_cb( cmd );
}
}
#if 0
//# TEMPLATE GETTER
static @type@proxy_@name@( store_t *gctx )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
@type@rv = ctx->real_driver->@name@( ctx->real_store );
debug( "%sCalled @name@, ret=@fmt@\n", ctx->label, rv );
return rv;
}
//# END
//# TEMPLATE REGULAR
static @type@proxy_@name@( store_t *gctx@decl_args@ )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ );
@print_args@
@type@rv = ctx->real_driver->@name@( ctx->real_store@pass_args@ );
debug( "%sLeave @name@, ret=@fmt@\n", ctx->label, rv );
return rv;
}
//# END
//# TEMPLATE REGULAR_VOID
static @type@proxy_@name@( store_t *gctx@decl_args@ )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
@pre_print_args@
debug( "%sEnter @name@@print_fmt_args@\n", ctx->label@print_pass_args@ );
@print_args@
ctx->real_driver->@name@( ctx->real_store@pass_args@ );
debug( "%sLeave @name@\n", ctx->label );
@action@
}
//# END
//# TEMPLATE CALLBACK
typedef union {
@gen_cmd_t@ gen;
struct {
@GEN_CMD@
@decl_cb_state@
void (*callback)( @decl_cb_args@void *aux );
void *callback_aux;
@decl_state@
};
} @name@_cmd_t;
static void
proxy_do_@name@_cb( gen_cmd_t *gcmd )
{
@name@_cmd_t *cmd = (@name@_cmd_t *)gcmd;
@pre_print_cb_args@
debug( "%s[% 2d] Callback enter @name@@print_fmt_cb_args@\n", cmd->ctx->label, cmd->tag@print_pass_cb_args@ );
@print_cb_args@
cmd->callback( @pass_cb_args@cmd->callback_aux );
debug( "%s[% 2d] Callback leave @name@\n", cmd->ctx->label, cmd->tag );
}
static void
proxy_@name@_cb( @decl_cb_args@void *aux )
{
@name@_cmd_t *cmd = (@name@_cmd_t *)aux;
@save_cb_args@
proxy_invoke_cb( @gen_cmd@, proxy_do_@name@_cb, @checked@, "@name@" );
}
static @type@proxy_@name@( store_t *gctx@decl_args@, void (*cb)( @decl_cb_args@void *aux ), void *aux )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
@name@_cmd_t *cmd = (@name@_cmd_t *)proxy_cmd_new( ctx, sizeof(@name@_cmd_t) );
cmd->callback = cb;
cmd->callback_aux = aux;
@assign_state@
@pre_print_args@
debug( "%s[% 2d] Enter @name@@print_fmt_args@\n", ctx->label, cmd->tag@print_pass_args@ );
@print_args@
ctx->real_driver->@name@( ctx->real_store@pass_args@, proxy_@name@_cb, cmd );
debug( "%s[% 2d] Leave @name@\n", ctx->label, cmd->tag );
proxy_cmd_done( @gen_cmd@ );
}
//# END
//# UNDEFINE list_store_print_fmt_cb_args
//# UNDEFINE list_store_print_pass_cb_args
//# DEFINE list_store_print_cb_args
if (cmd->sts == DRV_OK) {
for (string_list_t *box = cmd->boxes; box; box = box->next)
debug( " %s\n", box->string );
}
//# END
//# DEFINE load_box_pre_print_args
static char ubuf[12];
//# END
//# DEFINE load_box_print_fmt_args , [%u,%s] (find >= %u, paired <= %u, new > %u)
//# DEFINE load_box_print_pass_args , minuid, (maxuid == UINT_MAX) ? "inf" : (nfsnprintf( ubuf, sizeof(ubuf), "%u", maxuid ), ubuf), finduid, pairuid, newuid
//# DEFINE load_box_print_args
if (excs.size) {
debugn( " excs:" );
for (uint t = 0; t < excs.size; t++)
debugn( " %u", excs.data[t] );
debug( "\n" );
}
//# END
//# DEFINE load_box_print_fmt_cb_args , sts=%d, total=%d, recent=%d
//# DEFINE load_box_print_pass_cb_args , cmd->sts, cmd->total_msgs, cmd->recent_msgs
//# DEFINE load_box_print_cb_args
if (cmd->sts == DRV_OK) {
static char fbuf[as(Flags) + 1];
for (message_t *msg = cmd->msgs; msg; msg = msg->next)
debug( " uid=%-5u flags=%-4s size=%-6u tuid=%." stringify(TUIDL) "s\n",
msg->uid, (msg->status & M_FLAGS) ? (proxy_make_flags( msg->flags, fbuf ), fbuf) : "?", msg->size, *msg->tuid ? msg->tuid : "?" );
}
//# END
//# DEFINE find_new_msgs_print_fmt_cb_args , sts=%d
//# DEFINE find_new_msgs_print_pass_cb_args , cmd->sts
//# DEFINE find_new_msgs_print_cb_args
if (cmd->sts == DRV_OK) {
for (message_t *msg = cmd->msgs; msg; msg = msg->next)
debug( " uid=%-5u tuid=%." stringify(TUIDL) "s\n", msg->uid, msg->tuid );
}
//# END
//# DEFINE fetch_msg_decl_state
msg_data_t *data;
//# END
//# DEFINE fetch_msg_assign_state
cmd->data = data;
//# END
//# DEFINE fetch_msg_print_fmt_args , uid=%u, want_flags=%s, want_date=%s
//# DEFINE fetch_msg_print_pass_args , msg->uid, !(msg->status & M_FLAGS) ? "yes" : "no", data->date ? "yes" : "no"
//# DEFINE fetch_msg_pre_print_cb_args
static char fbuf[as(Flags) + 1];
proxy_make_flags( cmd->data->flags, fbuf );
//# END
//# DEFINE fetch_msg_print_fmt_cb_args , flags=%s, date=%lld, size=%u
//# DEFINE fetch_msg_print_pass_cb_args , fbuf, (long long)cmd->data->date, cmd->data->len
//# DEFINE fetch_msg_print_cb_args
if (cmd->sts == DRV_OK && (DFlags & DEBUG_DRV_ALL)) {
printf( "%s=========\n", cmd->ctx->label );
fwrite( cmd->data->data, cmd->data->len, 1, stdout );
printf( "%s=========\n", cmd->ctx->label );
fflush( stdout );
}
//# END
//# DEFINE store_msg_pre_print_args
static char fbuf[as(Flags) + 1];
proxy_make_flags( data->flags, fbuf );
//# END
//# DEFINE store_msg_print_fmt_args , flags=%s, date=%lld, size=%u, to_trash=%s
//# DEFINE store_msg_print_pass_args , fbuf, (long long)data->date, data->len, to_trash ? "yes" : "no"
//# DEFINE store_msg_print_args
if (DFlags & DEBUG_DRV_ALL) {
printf( "%s>>>>>>>>>\n", ctx->label );
fwrite( data->data, data->len, 1, stdout );
printf( "%s>>>>>>>>>\n", ctx->label );
fflush( stdout );
}
//# END
//# DEFINE set_msg_flags_pre_print_args
static char fbuf1[as(Flags) + 1], fbuf2[as(Flags) + 1];
proxy_make_flags( add, fbuf1 );
proxy_make_flags( del, fbuf2 );
//# END
//# DEFINE set_msg_flags_print_fmt_args , uid=%u, add=%s, del=%s
//# DEFINE set_msg_flags_print_pass_args , uid, fbuf1, fbuf2
//# DEFINE set_msg_flags_checked sts == DRV_OK
//# DEFINE trash_msg_print_fmt_args , uid=%u
//# DEFINE trash_msg_print_pass_args , msg->uid
//# DEFINE commit_cmds_print_args
proxy_flush_checked_cmds( ctx );
//# END
//# DEFINE cancel_cmds_print_cb_args
proxy_cancel_checked_cmds( cmd->ctx );
//# END
//# DEFINE free_store_print_args
proxy_cancel_checked_cmds( ctx );
//# END
//# DEFINE free_store_action
proxy_store_deref( ctx );
//# END
//# DEFINE cancel_store_print_args
proxy_cancel_checked_cmds( ctx );
//# END
//# DEFINE cancel_store_action
proxy_store_deref( ctx );
//# END
#endif
//# SPECIAL set_bad_callback
static void
proxy_set_bad_callback( store_t *gctx, void (*cb)( void *aux ), void *aux )
{
proxy_store_t *ctx = (proxy_store_t *)gctx;
ctx->bad_callback = cb;
ctx->bad_callback_aux = aux;
}
static void
proxy_invoke_bad_callback( proxy_store_t *ctx )
{
ctx->ref_count++;
debug( "%sCallback enter bad store\n", ctx->label );
ctx->bad_callback( ctx->bad_callback_aux );
debug( "%sCallback leave bad store\n", ctx->label );
proxy_store_deref( ctx );
}
//# EXCLUDE alloc_store
store_t *
proxy_alloc_store( store_t *real_ctx, const char *label )
{
proxy_store_t *ctx;
ctx = nfcalloc( sizeof(*ctx) );
ctx->driver = &proxy_driver;
ctx->gen.conf = real_ctx->conf;
ctx->ref_count = 1;
ctx->label = label;
ctx->done_cmds_append = &ctx->done_cmds;
ctx->check_cmds_append = &ctx->check_cmds;
ctx->real_driver = real_ctx->driver;
ctx->real_store = real_ctx;
ctx->real_driver->set_bad_callback( ctx->real_store, (void (*)(void *))proxy_invoke_bad_callback, ctx );
init_wakeup( &ctx->wakeup, proxy_wakeup, ctx );
return &ctx->gen;
}
//# EXCLUDE parse_store
//# EXCLUDE cleanup
//# EXCLUDE get_fail_state
#include "drv_proxy.inc"

View file

@ -1,198 +0,0 @@
#!/usr/bin/perl
#
# mbsync - mailbox synchronizer
# Copyright (C) 2017 Oswald Buddenhagen <ossi@users.sf.net>
#
# 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 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, mbsync may be linked with the OpenSSL library,
# despite that library's more restrictive license.
#
use strict;
use warnings;
die("Usage: $0 driver.h drv_proxy.c drv_proxy.inc\n")
if ($#ARGV != 2);
my ($in_header, $in_source, $out_source) = @ARGV;
my %templates;
my %defines;
my %excluded;
my %special;
open(my $ins, $in_source) or die("Cannot open $in_source: $!\n");
my $template;
my $define;
my $conts;
while (<$ins>) {
if ($template) {
if (/^\/\/\# END$/) {
$templates{$template} = $conts;
$template = undef;
} else {
$conts .= $_;
}
} elsif ($define) {
if (/^\/\/\# END$/) {
$defines{$define} = $conts;
$define = undef;
} else {
($_ eq "\n") or s/^\t// or die("DEFINE content is not indented: $_");
$conts .= $_;
}
} else {
if (/^\/\/\# TEMPLATE (\w+)$/) {
$template = $1;
$conts = "";
} elsif (/^\/\/\# DEFINE (\w+)$/) {
$define = $1;
$conts = "";
} elsif (/^\/\/\# DEFINE (\w+) (.*)$/) {
$defines{$1} = $2;
} elsif (/^\/\/\# UNDEFINE (\w+)$/) {
$defines{$1} = "";
} elsif (/^\/\/\# EXCLUDE (\w+)$/) {
$excluded{$1} = 1;
} elsif (/^\/\/\# SPECIAL (\w+)$/) {
$special{$1} = 1;
}
}
}
close($ins);
open(my $inh, $in_header) or die("Cannot open $in_header: $!\n");
my $sts = 0;
my $cont = "";
while (<$inh>) {
if ($sts == 0) {
if (/^struct driver \{$/) {
$sts = 1;
}
} elsif ($sts == 1) {
if (/^\};$/) {
$sts = 0;
} else {
$cont .= $_;
}
}
}
close($inh);
$cont =~ s,\n, ,g;
$cont =~ s,/\*.*?\*/, ,g;
$cont =~ s,\h+, ,g;
my @ptypes = map { s,^ ,,r } split(/;/, $cont);
pop @ptypes; # last one is empty
my @cmd_table;
sub make_args($)
{
$_ = shift;
s/(?:^|(?<=, ))(?:const )?\w+ \*?//g;
return $_;
}
sub type_to_format($)
{
$_ = shift;
s/xint /\%\#x/g;
s/uint /\%u/g;
s/int /\%d/g;
s/const char \*/\%s/g;
return $_;
}
sub make_format($)
{
$_ = type_to_format(shift);
s/, (\%\#?.)(\w+)/, $2=$1/g;
return $_;
}
sub indent($$)
{
my ($str, $indent) = @_;
return $str =~ s,^(?=.),$indent,smgr;
}
open(my $outh, ">".$out_source) or die("Cannot create $out_source: $!\n");
for (@ptypes) {
/^([\w* ]+)\(\*(\w+)\)\( (.*) \)$/ or die("Cannot parse prototype '$_'\n");
my ($cmd_type, $cmd_name, $cmd_args) = ($1, $2, $3);
if (defined($excluded{$cmd_name})) {
push @cmd_table, "NULL";
next;
}
push @cmd_table, "proxy_$cmd_name";
next if (defined($special{$cmd_name}));
my %replace;
$replace{'name'} = $cmd_name;
$replace{'type'} = $cmd_type;
$cmd_args =~ s/^store_t \*ctx// or die("Arguments '$cmd_args' don't start with 'store_t *ctx'\n");
if ($cmd_name =~ /^get_/) {
$template = "GETTER";
$replace{'fmt'} = type_to_format($cmd_type);
} else {
if ($cmd_type eq "void " && $cmd_args =~ s/, void \(\*cb\)\( (.*)void \*aux \), void \*aux$//) {
my $cmd_cb_args = $1;
if (length($cmd_cb_args)) {
$replace{'decl_cb_args'} = $cmd_cb_args;
my $r_cmd_cb_args = $cmd_cb_args;
$r_cmd_cb_args =~ s/^int sts, // or die("Callback arguments of $cmd_name don't start with sts.\n");
$replace{'decl_cb_state'} = $r_cmd_cb_args =~ s/, /\;\n/gr;
my $pass_cb_args = make_args($cmd_cb_args);
$replace{'save_cb_args'} = $pass_cb_args =~ s/([^,]+), /cmd->$1 = $1\;\n/gr;
$pass_cb_args =~ s/([^, ]+)/cmd->$1/g;
$replace{'pass_cb_args'} = $pass_cb_args;
$replace{'print_pass_cb_args'} = $pass_cb_args =~ s/(.*), $/, $1/r;
$replace{'print_fmt_cb_args'} = make_format($cmd_cb_args =~ s/(.*), $/, $1/r);
$replace{'gen_cmd_t'} = "gen_sts_cmd_t";
$replace{'GEN_CMD'} = "GEN_STS_CMD\n";
$replace{'gen_cmd'} = "&cmd->gen.gen";
} else {
$replace{'gen_cmd_t'} = "gen_cmd_t";
$replace{'GEN_CMD'} = "GEN_CMD\n";
$replace{'gen_cmd'} = "&cmd->gen";
}
$replace{'checked'} //= '0';
$template = "CALLBACK";
} elsif ($cmd_type eq "void ") {
$template = "REGULAR_VOID";
} else {
$template = "REGULAR";
$replace{'fmt'} = type_to_format($cmd_type);
}
$replace{'decl_args'} = $cmd_args;
$replace{'print_pass_args'} = $replace{'pass_args'} = make_args($cmd_args);
$replace{'print_fmt_args'} = make_format($cmd_args);
}
for (keys %defines) {
$replace{$1} = delete $defines{$_} if (/^${cmd_name}_(.*)$/);
}
my %used;
my $text = $templates{$template};
$text =~ s/^(\h*)\@(\w+)\@\n/$used{$2} = 1; indent($replace{$2} \/\/ "", $1)/smeg;
$text =~ s/\@(\w+)\@/$used{$1} = 1; $replace{$1} \/\/ ""/eg;
print $outh $text."\n";
my @not_used = grep { !defined($used{$_}) } keys %replace;
die("Fatal: unconsumed replacements in $cmd_name: ".join(" ", @not_used)."\n") if (@not_used);
}
die("Fatal: unconsumed DEFINEs: ".join(" ", keys %defines)."\n") if (%defines);
print $outh "struct driver proxy_driver = {\n".join("", map { "\t$_,\n" } @cmd_table)."};\n";
close $outh;

1156
src/main.c

File diff suppressed because it is too large Load diff

View file

@ -1,823 +0,0 @@
.\" mbsync - mailbox synchronizer
.\" Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
.\" Copyright (C) 2002-2004,2011-2015 Oswald Buddenhagen <ossi@users.sf.net>
.\" Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
.\"
.\" 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 2 of the License, or
.\" (at your option) any later version.
.\"
.\" This program is distributed in the hope that it will be useful,
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
.\" GNU General Public License for more details.
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
.\"
.\" As a special exception, mbsync may be linked with the OpenSSL library,
.\" despite that library's more restrictive license.
.
.TH mbsync 1 "2015 Mar 22"
.
.SH NAME
mbsync - synchronize IMAP4 and Maildir mailboxes
.
.SH SYNOPSIS
\fBmbsync\fR [\fIoptions\fR ...] {{\fIchannel\fR[\fB:\fIbox\fR[{\fB,\fR|\fB\\n\fR}...]]|\fIgroup\fR} ...|\fB-a\fR}
.
.SH DESCRIPTION
\fBmbsync\fR is a command line application which synchronizes mailboxes;
currently Maildir and IMAP4 mailboxes are supported.
New messages, message deletions and flag changes can be propagated both ways;
the operation set can be selected in a fine-grained manner.
.br
Synchronization is based on unique message identifiers (UIDs), so no
identification conflicts can occur (unlike with some other mail synchronizers).
OTOH, \fBmbsync\fR is susceptible to UID validity changes (but will recover
just fine if the change is unfounded).
Synchronization state is kept in one local text file per mailbox pair;
these files are protected against concurrent \fBmbsync\fR processes.
Mailboxes can be safely modified while \fBmbsync\fR operates
(see \fBINHERENT PROBLEMS\fR below for a minor exception).
Multiple replicas of each mailbox can be maintained.
.
.SH OPTIONS
.TP
\fB-c\fR, \fB--config\fR \fIfile\fR
Read configuration from \fIfile\fR.
By default, the configuration is read from ~/.mbsyncrc.
.TP
\fB-a\fR, \fB--all\fR
Select all configured channels. Any channel/group specifications on the command
line are ignored.
.TP
\fB-l\fR, \fB--list\fR
Don't synchronize anything, but list all mailboxes in the selected channels
and exit.
.TP
\fB-C\fR[\fBf\fR][\fBn\fR], \fB--create\fR[\fB-far\fR|\fB-near\fR]
Override any \fBCreate\fR options from the config file. See below.
.TP
\fB-R\fR[\fBf\fR][\fBn\fR], \fB--remove\fR[\fB-far\fR|\fB-near\fR]
Override any \fBRemove\fR options from the config file. See below.
.TP
\fB-X\fR[\fBf\fR][\fBn\fR], \fB--expunge\fR[\fB-far\fR|\fB-near\fR]
Override any \fBExpunge\fR options from the config file. See below.
.TP
{\fB-n\fR|\fB-N\fR|\fB-d\fR|\fB-f\fR|\fB-0\fR|\fB-F\fR},\
{\fB--new\fR|\fB--renew\fR|\fB--delete\fR|\fB--flags\fR|\fB--noop\fR|\fB--full\fR}
.TP
\r{\fB-L\fR|\fB-H\fR}[\fBn\fR][\fBN\fR][\fBd\fR][\fBf\fR],\
{\fB--pull\fR|\fB--push\fR}[\fB-new\fR|\fB-renew\fR|\fB-delete\fR|\fB-flags\fR]
Override any \fBSync\fR options from the config file. See below.
.TP
\fB-h\fR, \fB--help\fR
Display a summary of command line options.
.TP
\fB-v\fR, \fB--version\fR
Display version information.
.TP
\fB-V\fR, \fB--verbose\fR
Enable \fIverbose\fR mode, which displays what is currently happening.
.TP
\fB-D\fR[\fBC\fR][\fBd\fR|\fBD\fR][\fBm\fR][\fBM\fR][\fBn\fR|\fBN\fR][\fBs\fR]\fR]\fR,\
\fB--debug\fR[\fB-crash\fR|\fB-driver\fR|\fB-driver-all\fR|\fB-maildir\fR|\fB-main\fR|\fB-net\fR|\fB-net-all\fR|\fB-sync\fR]
Enable debugging categories:
.in +4
\fBC\fR, \fBcrash\fR - use built-in crash handler
.br
\fBd\fR, \fBdriver\fR - print driver calls (metadata only)
.br
\fBD\fR, \fBdriver-all\fR - print driver calls (including messages)
.br
\fBm\fR, \fBmaildir\fR - print maildir debug info
.br
\fBM\fR, \fBmain\fR - print main debug info
.br
\fBn\fR, \fBnet\fR - print network traffic (protocol only)
.br
\fBN\fR, \fBnet-all\fR - print network traffic (including payloads)
.br
\fBs\fR, \fBsync\fR - print synchronization debug info
.in -4
All categories except \fBcrash\fR implictly enable \fIverbose\fR mode.
Without category specification, all categories except net-all are enabled.
.TP
\fB-q\fR, \fB--quiet\fR
Suppress progress counters (this is implicit if stdout is no TTY,
or any debugging categories are enabled) and notices.
If specified twice, suppress warning messages as well.
.
.SH CONFIGURATION
The configuration file is mandatory; \fBmbsync\fR will not run without it.
Lines starting with a hash mark (\fB#\fR) are comments and are ignored entirely.
Configuration items are keywords followed by one or more arguments;
arguments containing spaces must be enclosed in double quotes (\fB"\fR),
and literal double quotes and backslashes (\fB\\\fR) must be backslash-escaped.
All keywords (including those used as arguments) are case-insensitive.
Bash-like home directory expansion using the tilde (\fB~\fR) is supported
in all options which represent local paths.
There are a few global options, the others apply to particular sections.
Sections begin with a section-starting keyword and are terminated by an empty
line or end of file.
Every section defines an object with an identifier unique within that
object class.
.P
There are two basic object classes: Stores and Channels. A Store defines
a collection of mailboxes; basically a folder, either local or remote.
A Channel connects two Stores, describing the way the two are synchronized.
.br
There are two auxiliary object classes: Accounts and Groups. An Account
describes the connection part of network Stores, so server configurations can
be shared between multiple Stores. A Group aggregates multiple Channels to
save typing on the command line.
.P
File system locations (in particular, \fBPath\fR and \fBInbox\fR) use the
Store's internal path separators, which may be slashes, periods, etc., or
even combinations thereof.
.br
Mailbox names, OTOH, always use canonical path separators, which are
Unix-like forward slashes.
.
.SS All Stores
These options can be used in all supported Store types.
.br
In this context, the term "remote" describes the second Store within a Channel,
and not necessarily a remote server.
.br
The special mailbox \fBINBOX\fR exists in every Store; its physical location
in the file system is Store type specific.
.
.TP
\fBPath\fR \fIpath\fR
The location of the Store in the (server's) file system.
If this is no absolute path, the reference point is Store type specific.
This string is prepended to the mailbox names addressed in this Store,
but is not considered part of them; this is important for \fBPatterns\fR
and \fBCreate\fR in the Channels section.
Note that you \fBmust\fR append a slash if you want to specify an entire
directory.
(Default: none)
.
.TP
\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
Messages larger than \fIsize\fR will have only a small placeholder message
propagated into this Store. To propagate the full message, it must be
flagged in either Store; that can be done retroactively, in which case
the \fBReNew\fR operation needs to be executed instead of \fBNew\fR.
This is useful for avoiding downloading messages with large attachments
unless they are actually needed.
Caveat: Setting a size limit on a Store you never read directly (which is
typically the case for servers) is not recommended, as you may never
notice that affected messages were not propagated to it.
.br
\fBK\fR and \fBM\fR can be appended to the size to specify KiBytes resp.
MeBytes instead of bytes. \fBB\fR is accepted but superfluous.
If \fIsize\fR is 0, the maximum message size is \fBunlimited\fR.
(Default: \fI0\fR)
.
.TP
\fBMapInbox\fR \fImailbox\fR
Create a virtual mailbox (relative to \fBPath\fR) which aliases
the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the
Channels section, though with a Maildir near side, you probably want to
place \fBInbox\fR under \fBPath\fR instead.
This virtual mailbox does not support subfolders.
.
.TP
\fBFlatten\fR \fIdelim\fR
Flatten the hierarchy within this Store by substituting the canonical
hierarchy delimiter \fB/\fR with \fIdelim\fR.
This can be useful when the MUA used to access the Store provides
suboptimal handling of hierarchical mailboxes, as is the case with
\fBMutt\fR.
A common choice for the delimiter is \fB.\fR.
.br
Note that flattened sub-folders of the \fBINBOX\fR always end up
under \fBPath\fR, including the "INBOX\fIdelim\fR" prefix.
.
.TP
\fBTrash\fR \fImailbox\fR
Specifies a mailbox (relative to \fBPath\fR) to copy deleted messages to
prior to expunging.
See \fBRECOMMENDATIONS\fR and \fBINHERENT PROBLEMS\fR below.
(Default: none)
.
.TP
\fBTrashNewOnly\fR \fByes\fR|\fBno\fR
When trashing, copy only not yet propagated messages. This makes sense if the
remote Store has a \fBTrash\fR as well (with \fBTrashNewOnly\fR \fBno\fR).
(Default: \fBno\fR)
.
.TP
\fBTrashRemoteNew\fR \fByes\fR|\fBno\fR
When expunging the remote Store, copy not yet propagated messages to this
Store's \fBTrash\fR. When using this, the remote Store does not need an own
\fBTrash\fR at all, yet all messages are archived.
(Default: \fBno\fR)
.
.SS Maildir Stores
The reference point for relative \fBPath\fRs is the current working directory.
.P
As \fBmbsync\fR needs UIDs, but no standardized UID storage scheme exists for
Maildir, \fBmbsync\fR supports two schemes, each with its pros and cons.
.br
The \fBnative\fR scheme is stolen from the latest Maildir patches to \fBc-client\fR
and is therefore compatible with \fBpine\fR. The UID validity is stored in a
file named .uidvalidity; the UIDs are encoded in the file names of the messages.
.br
The \fBalternative\fR scheme is based on the UID mapping used by \fBisync\fR
versions 0.8 and 0.9.x. The invariant parts of the file names of the messages
are used as keys into a Berkeley database named .isyncuidmap.db, which holds
the UID validity as well.
.br
The \fBnative\fR scheme is faster, more space efficient, endianness independent
and "human readable", but will be disrupted if a message is copied from another
mailbox without getting a new file name; this would result in duplicated UIDs
sooner or later, which in turn results in a UID validity change, making
synchronization fail.
The \fBalternative\fR scheme would fail if a MUA changed a message's file name
in a part \fBmbsync\fR considers invariant; this would be interpreted as a
message deletion and a new message, resulting in unnecessary traffic.
.br
\fBMutt\fR is known to work fine with both schemes.
.br
Use \fBmdconvert\fR to convert mailboxes from one scheme to the other.
.
.TP
\fBMaildirStore\fR \fIname\fR
Define the Maildir Store \fIname\fR, opening a section for its parameters.
.
.TP
\fBAltMap\fR \fByes\fR|\fBno\fR
Use the \fBalternative\fR UID storage scheme for mailboxes in this Store.
This does not affect mailboxes that do already have a UID storage scheme;
use \fBmdconvert\fR to change it.
See \fBRECOMMENDATIONS\fR below.
(Default: \fBno\fR)
.
.TP
\fBInbox\fR \fIpath\fR
The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR,
but it is allowed to place the \fBINBOX\fR inside the \fBPath\fR.
(Default: \fI~/Maildir\fR)
.
.TP
\fBInfoDelimiter\fR \fIdelim\fR
The character used to delimit the info field from a message's basename.
The Maildir standard defines this to be the colon, but this is incompatible
with DOS/Windows file systems.
(Default: the value of \fBFieldDelimiter\fR)
.
.TP
\fBSubFolders\fR \fBVerbatim\fR|\fBMaildir++\fR|\fBLegacy\fR
The on-disk folder naming style used for hierarchical mailboxes.
This option has no effect when \fBFlatten\fR is used.
.br
Suppose mailboxes with the canonical paths \fBtop/sub/subsub\fR and
\fBINBOX/sub/subsub\fR, the styles will yield the following on-disk paths:
.br
\fBVerbatim\fR - \fIPath\fB/top/sub/subsub\fR and \fIInbox\fB/sub/subsub\fR
(this is the style you probably want to use)
.br
\fBMaildir++\fR - \fIInbox\fB/.top.sub.subsub\fR and \fIInbox\fB/..sub.subsub\fR
(this style is compatible with Courier and Dovecot - but note that
the mailbox metadata format is \fInot\fR compatible).
Note that attempts to set \fBPath\fR are rejected in this mode.
.br
\fBLegacy\fR - \fIPath\fB/top/.sub/.subsub\fR and \fIInbox\fB/.sub/.subsub\fR
(this is \fBmbsync\fR's historical style)
.br
(Default: unset; will error out when sub-folders are encountered)
.
.SS IMAP4 Accounts
.TP
\fBIMAPAccount\fR \fIname\fR
Define the IMAP4 Account \fIname\fR, opening a section for its parameters.
.
.TP
\fBHost\fR \fIhost\fR
Specify the DNS name or IP address of the IMAP server.
.br
If \fBTunnel\fR is used, this setting is needed only if \fBSSLType\fR is
not \fBNone\fR and \fBCertificateFile\fR is not used,
in which case the host name is used for certificate subject verification.
.
.TP
\fBPort\fR \fIport\fR
Specify the TCP port number of the IMAP server. (Default: 143 for IMAP,
993 for IMAPS)
.br
If \fBTunnel\fR is used, this setting is ignored.
.
.TP
\fBTimeout\fR \fItimeout\fR
Specify the connect and data timeout for the IMAP server in seconds.
Zero means unlimited.
(Default: \fI20\fR)
.
.TP
\fBUser\fR \fIusername\fR
Specify the login name on the IMAP server.
.
.TP
\fBUserCmd\fR [\fB+\fR]\fIcommand\fR
Specify a shell command to obtain a user rather than specifying a
user directly. This allows you to script retrieving user names.
.br
The command must produce exactly one line on stdout; the trailing newline
is optional.
Prepend \fB+\fR to the command to indicate that it produces TTY output
(e.g., a prompt); failure to do so will merely produce messier output.
Remember to backslash-escape double quotes and backslashes embedded into
the command.
.
.TP
\fBPass\fR \fIpassword\fR
Specify the password for \fIusername\fR on the IMAP server.
Note that this option is \fInot\fR required.
If neither a password nor a password command is specified in the
configuration file, \fBmbsync\fR will prompt you for a password.
.
.TP
\fBPassCmd\fR [\fB+\fR]\fIcommand\fR
Specify a shell command to obtain a password rather than specifying a
password directly. This allows you to use password files and agents.
.br
See \fBUserCmd\fR above for details.
.
.TP
\fBUseKeychain\fR \fByes\fR|\fBno\fR
Whether to use the macOS Keychain to obtain the password.
(Default: \fBno\fR)
.IP
The neccessary keychain item can be created this way:
.RS
.IP
.nh
.B security add-internet-password \-r imap \-s
.I Host
.B \-a
.I User
.B \-w
.I password
[
.B \-T
.I /path/to/mbsync
]
.hy
.RE
.
.TP
\fBTunnel\fR \fIcommand\fR
Specify a command to run to establish a connection rather than opening a TCP
socket. This allows you to run an IMAP session over an SSH tunnel, for
example.
.
.TP
\fBAuthMechs\fR \fItype\fR ...
The list of acceptable authentication mechanisms.
In addition to the mechanisms listed in the SASL registry (link below),
the legacy IMAP \fBLOGIN\fR mechanism is known.
The wildcard \fB*\fR represents all mechanisms that are deemed secure
enough for the current \fBSSLType\fR setting.
The actually used mechanism is the most secure choice from the intersection
of this list, the list supplied by the server, and the installed SASL modules.
(Default: \fB*\fR)
.
.TP
\fBSSLType\fR {\fBNone\fR|\fBSTARTTLS\fR|\fBIMAPS\fR}
Select the connection security/encryption method:
.br
\fBNone\fR - no security.
This is the default when \fBTunnel\fR is set, as tunnels are usually secure.
.br
\fBSTARTTLS\fR - security is established via the STARTTLS extension
after connecting the regular IMAP port 143. Most servers support this,
so it is the default (unless a tunnel is used).
.br
\fBIMAPS\fR - security is established by starting SSL/TLS negotiation
right after connecting the secure IMAP port 993.
.
.TP
\fBSSLVersions\fR [\fBSSLv3\fR] [\fBTLSv1\fR] [\fBTLSv1.1\fR] [\fBTLSv1.2\fR] [\fBTLSv1.3\fR]
Select the acceptable SSL/TLS versions.
Use old versions only when the server has problems with newer ones.
(Default: [\fBTLSv1\fR] [\fBTLSv1.1\fR] [\fBTLSv1.2\fR] [\fBTLSv1.3\fR]).
.
.TP
\fBSystemCertificates\fR \fByes\fR|\fBno\fR
Whether the system's default CA (certificate authority) certificate
store should be used to verify certificate trust chains. Disable this
if you want to trust only hand-picked certificates.
(Default: \fByes\fR)
.
.TP
\fBCertificateFile\fR \fIpath\fR
File containing additional X.509 certificates used to verify server
identities.
It may contain two types of certificates:
.RS
.IP Host
These certificates are matched only against the received server certificate
itself.
They are always trusted, regardless of validity.
A typical use case would be forcing acceptance of an expired certificate.
.br
These certificates may be obtained using the \fBmbsync-get-cert\fR tool;
make sure to verify their fingerprints before trusting them, or transfer
them securely from the server's network (if it can be trusted beyond the
server itself).
.IP CA
These certificates are used as trust anchors when building the certificate
chain for the received server certificate.
They are used to supplant or supersede the system's trust store, depending
on the \fBSystemCertificates\fR setting;
it is not necessary and not recommended to specify the system's trust store
itself here.
The trust chains are fully validated.
.RE
.
.TP
\fBClientCertificate\fR \fIpath\fR
File containing a client certificate to send to the server.
\fBClientKey\fR should also be specified.
.br
Note that client certificate verification is usually not required,
so it is unlikely that you need this option.
.
.TP
\fBClientKey\fR \fIpath\fR
File containing the private key corresponding to \fBClientCertificate\fR.
.
.TP
\fBCipherString\fR \fIstring\fR
Specify OpenSSL cipher string for connections secured with TLS up to
version 1.2 (but not 1.3 and above).
The format is described in \fBciphers\fR\|(1).
(Default: empty, which implies system wide policy).
.
.TP
\fBPipelineDepth\fR \fIdepth\fR
Maximum number of IMAP commands which can be simultaneously in flight.
Setting this to \fI1\fR disables pipelining.
This is mostly a debugging option, but may also be used to limit average
bandwidth consumption (GMail may require this if you have a very fast
connection), or to spare flaky servers like M$ Exchange.
(Default: \fIunlimited\fR)
.
.TP
\fBDisableExtension\fR[\fBs\fR] \fIextension\fR ...
Disable the use of specific IMAP extensions.
This can be used to work around bugs in servers
(and possibly \fBmbsync\fR itself).
(Default: empty)
.
.SS IMAP Stores
The reference point for relative \fBPath\fRs is whatever the server likes it
to be; probably the user's $HOME or $HOME/Mail on that server. The location
of \fBINBOX\fR is up to the server as well and is usually irrelevant.
.TP
\fBIMAPStore\fR \fIname\fR
Define the IMAP4 Store \fIname\fR, opening a section for its parameters.
.
.TP
\fBAccount\fR \fIaccount\fR
Specify which IMAP4 Account to use. Instead of defining an Account and
referencing it here, it is also possible to specify all the Account options
directly in the Store's section - this makes sense if an Account is used for
one Store only anyway.
.
.TP
\fBUseNamespace\fR \fByes\fR|\fBno\fR
Selects whether the server's first "personal" NAMESPACE should be prefixed to
mailbox names. Disabling this makes sense for some broken IMAP servers.
This option is meaningless if a \fBPath\fR was specified.
(Default: \fByes\fR)
.
.TP
\fBPathDelimiter\fR \fIdelim\fR
Specify the server's hierarchy delimiter.
(Default: taken from the server's first "personal" NAMESPACE)
.br
Do \fInot\fR abuse this to re-interpret the hierarchy.
Use \fBFlatten\fR instead.
.
.TP
\fBSubscribedOnly\fR \fByes\fR|\fBno\fR
Selects whether to synchronize only mailboxes that are subscribed to on the
IMAP server. In technical terms, if this option is set, \fBmbsync\fR will use
the IMAP command LSUB instead of LIST to look for mailboxes in this Store.
This option make sense only in conjunction with \fBPatterns\fR.
(Default: \fBno\fR)
.
.SS Channels
.TP
\fBChannel\fR \fIname\fR
Define the Channel \fIname\fR, opening a section for its parameters.
.
.TP
{\fBFar\fR|\fBNear\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR]
Specify the far resp. near side Store to be connected by this Channel.
If \fBPatterns\fR are specified, \fImailbox\fR is interpreted as a
prefix which is not matched against the patterns, and which is not
affected by mailbox list overrides.
Otherwise, if \fImailbox\fR is omitted, \fBINBOX\fR is assumed.
.
.TP
\fBPattern\fR[\fBs\fR] [\fB!\fR]\fIpattern\fR ...
Instead of synchronizing only one mailbox pair, synchronize all mailboxes
that match the \fIpattern\fR(s). The mailbox names are the same on the far
and near side. Patterns are IMAP4 patterns, i.e., \fB*\fR matches anything
and \fB%\fR matches anything up to the next hierarchy delimiter. Prepending
\fB!\fR to a pattern makes it an exclusion. Multiple patterns can be specified
(either by supplying multiple arguments or by using \fBPattern\fR multiple
times); later matches take precedence.
.br
Note that \fBINBOX\fR is not matched by wildcards, unless it lives under
\fBPath\fR.
.br
The mailbox list selected by \fBPatterns\fR can be overridden by a mailbox
list in a channel reference (a \fBGroup\fR specification or the command line).
.br
Example: "\fBPatterns\fR\ \fI%\ !Trash\fR"
.
.TP
\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
Analogous to the homonymous option in the Stores section, but applies equally
to Far and Near. Note that this actually modifies the Stores, so take care
not to provide conflicting settings if you use the Stores in multiple Channels.
.
.TP
\fBMaxMessages\fR \fIcount\fR
Sets the maximum number of messages to keep in each near side mailbox.
This is useful for mailboxes where you keep a complete archive on the server,
but want to mirror only the last messages (for instance, for mailing lists).
The messages that were the first to arrive in the mailbox (independently of
the actual date of the message) will be deleted first.
Messages that are flagged (marked as important) and (by default) unread
messages will not be automatically deleted.
If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR
(Default: \fI0\fR).
.
.TP
\fBExpireUnread\fR \fByes\fR|\fBno\fR
Selects whether unread messages should be affected by \fBMaxMessages\fR.
Normally, unread messages are considered important and thus never expired.
This ensures that you never miss new messages even after an extended absence.
However, if your archive contains large amounts of unread messages by design,
treating them as important would practically defeat \fBMaxMessages\fR. In this
case you need to enable this option.
(Default: \fBno\fR).
.
.TP
\fBSync\fR {\fBNone\fR|[\fBPull\fR] [\fBPush\fR] [\fBNew\fR] [\fBReNew\fR] [\fBDelete\fR] [\fBFlags\fR]|\fBAll\fR}
Select the synchronization operation(s) to perform:
.br
\fBPull\fR - propagate changes from far to near side.
.br
\fBPush\fR - propagate changes from near to far side.
.br
\fBNew\fR - propagate newly appeared messages.
.br
\fBReNew\fR - upgrade placeholders to full messages. Useful only with
a configured \fBMaxSize\fR.
.br
\fBDelete\fR - propagate message deletions. This applies only to messages that
are actually gone, i.e., were expunged. The affected messages in the remote
Store are marked as deleted only, i.e., they won't be really deleted until
that Store is expunged.
.br
\fBFlags\fR - propagate flag changes. Note that Deleted/Trashed is a flag as
well; this is particularly interesting if you use \fBmutt\fR with the
maildir_trash option.
.br
\fBAll\fR (\fB--full\fR on the command line) - all of the above.
This is the global default.
.br
\fBNone\fR (\fB--noop\fR on the command line) - don't propagate anything.
Useful if you want to expunge only.
.IP
\fBPull\fR and \fBPush\fR are direction flags, while \fBNew\fR, \fBReNew\fR,
\fBDelete\fR and \fBFlags\fR are type flags. The two flag classes make up a
two-dimensional matrix (a table). Its cells are the individual actions to
perform. There are two styles of asserting the cells:
.br
In the first style, the flags select entire rows/colums in the matrix. Only
the cells which are selected both horizontally and vertically are asserted.
Specifying no flags from a class is like specifying all flags from this class.
For example, "\fBSync\fR\ \fBPull\fR\ \fBNew\fR\ \fBFlags\fR" will propagate
new messages and flag changes from the far side to the near side,
"\fBSync\fR\ \fBNew\fR\ \fBDelete\fR" will propagate message arrivals and
deletions both ways, and "\fBSync\fR\ \fBPush\fR" will propagate all changes
from the near side to the far side.
.br
In the second style, direction flags are concatenated with type flags; every
compound flag immediately asserts a cell in the matrix. In addition to at least
one compound flag, the individual flags can be used as well, but as opposed to
the first style, they immediately assert all cells in their respective
row/column. For example,
"\fBSync\fR\ \fBPullNew\fR\ \fBPullDelete\fR\ \fBPush\fR" will propagate
message arrivals and deletions from the far side to the near side and any
changes from the near side to the far side.
Note that it is not allowed to assert a cell in two ways, e.g.
"\fBSync\fR\ \fBPullNew\fR\ \fBPull\fR" and
"\fBSync\fR\ \fBPullNew\fR\ \fBDelete\fR\ \fBPush\fR" induce error messages.
.
.TP
\fBCreate\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Automatically create missing mailboxes [on the far/near side].
Otherwise print an error message and skip that mailbox pair if a mailbox
and the corresponding sync state does not exist.
(Global default: \fBNone\fR)
.
.TP
\fBRemove\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Propagate mailbox deletions [to the far/near side].
Otherwise print an error message and skip that mailbox pair if a mailbox
does not exist but the corresponding sync state does.
.br
For MailDir mailboxes it is sufficient to delete the cur/ subdirectory to
mark them as deleted. This ensures compatibility with \fBSyncState *\fR.
.br
Note that for safety, non-empty mailboxes are never deleted.
.br
(Global default: \fBNone\fR)
.
.TP
\fBExpunge\fR {\fBNone\fR|\fBFar\fR|\fBNear\fR|\fBBoth\fR}
Permanently remove all messages [on the far/near side] marked for deletion.
See \fBRECOMMENDATIONS\fR below.
(Global default: \fBNone\fR)
.
.TP
\fBCopyArrivalDate\fR {\fByes\fR|\fBno\fR}
Selects whether their arrival time should be propagated together with
the messages.
Enabling this makes sense in order to keep the time stamp based message
sorting intact.
Note that IMAP does not guarantee that the time stamp (termed \fBinternal
date\fR) is actually the arrival time, but it is usually close enough.
(Default: \fBno\fR)
.
.P
\fBSync\fR, \fBCreate\fR, \fBRemove\fR, \fBExpunge\fR,
\fBMaxMessages\fR, and \fBCopyArrivalDate\fR
can be used before any section for a global effect.
The global settings are overridden by Channel-specific options,
which in turn are overridden by command line switches.
.
.TP
\fBSyncState\fR {\fB*\fR|\fIpath\fR}
Set the location of this Channel's synchronization state files.
\fB*\fR means that the state should be saved in a file named .mbsyncstate
in the near side mailbox itself; this has the advantage that you do not need
to handle the state file separately if you delete the mailbox, but it works
only with Maildir mailboxes, obviously.
Otherwise this is interpreted as a string to prepend to the near side mailbox
name to make up a complete path.
.br
This option can be used outside any section for a global effect. In this case
the appended string is made up according to the pattern
\fB:\fIfar-store\fB:\fIfar-box\fB_:\fInear-store\fB:\fInear-box\fR
(see also \fBFieldDelimiter\fR below).
.br
(Global default: \fI~/.mbsync/\fR).
.
.SS Groups
.TP
\fBGroup\fR \fIname\fR [\fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]]] ...
Define the Group \fIname\fR, opening a section for its parameters.
Note that even though Groups have an own namespace, they will "hide" Channels
with the same name on the command line.
.br
One or more Channels can be specified on the same line.
.br
If you supply one or more \fIbox\fRes to a \fIchannel\fR, they will be used
instead of what is specified in the Channel's Patterns.
The same can be done on the command line, except that there newlines can be
used as mailbox name separators as well.
.
.TP
\fBChannel\fR[\fBs\fR] \fIchannel\fR[\fB:\fIbox\fR[\fB,\fR...]] ...
Add the specified channels to the group. This option can be specified multiple
times within a Group.
.
.SS Global Options
.TP
\fBFSync\fR \fByes\fR|\fBno\fR
.br
Selects whether \fBmbsync\fR performs forced flushing, which determines
the level of data safety after system crashes and power outages.
Disabling it is reasonably safe for file systems which are mounted with
data=ordered mode.
Enabling it is a wise choice for file systems mounted with data=writeback,
in particular modern systems like ext4, btrfs and xfs. The performance impact
on older file systems may be disproportionate.
(Default: \fByes\fR)
.
.TP
\fBFieldDelimiter\fR \fIdelim\fR
The character to use to delimit fields in the string appended to a global
\fBSyncState\fR.
\fBmbsync\fR prefers to use the colon, but this is incompatible with
DOS/Windows file systems.
This option is meaningless for \fBSyncState\fR if the latter is \fB*\fR,
obviously. However, it also determines the default of \fBInfoDelimiter\fR.
(Global default: \fI;\fR on Windows, \fI:\fR everywhere else)
.
.TP
\fBBufferLimit\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
The per-Channel, per-direction instantaneous memory usage above which
\fBmbsync\fR will refrain from using more memory. Note that this is no
absolute limit, as even a single message can consume more memory than
this.
(Default: \fI10M\fR)
.
.SH CONSOLE OUTPUT
If \fBmbsync\fR's output is connected to a console, it will print progress
counters by default. The output will look like this:
.P
.in +4
C: 1/2 B: 3/4 F: +13/13 *23/42 #0/0 N: +0/7 *0/0 #0/0
.in -4
.P
This represents the cumulative progress over channels, boxes, and messages
affected on the far and near side, respectively.
The message counts represent added messages, messages with updated flags,
and trashed messages, respectively.
No attempt is made to calculate the totals in advance, so they grow over
time as more information is gathered.
.
.SH RECOMMENDATIONS
Make sure your IMAP server does not auto-expunge deleted messages - it is
slow, and semantically somewhat questionable. Specifically, Gmail needs to
be configured not to do it.
.P
By default, \fBmbsync\fR will not delete any messages - deletions are
propagated by marking the messages as deleted on the remote store.
Once you have verified that your setup works, you will typically want to
set \fBExpunge\fR to \fBBoth\fR, so that deletions become effective.
.P
\fBmbsync\fR's built-in trash functionality relies on \fBmbsync\fR doing
the expunging of deleted messages. This is the case when it propagates
deletions of previously propagated messages, and the trash is on the target
store (typically your IMAP server).
.br
However, when you intend \fBmbsync\fR to trash messages which were not
propagated yet, the MUA must mark the messages as deleted without expunging
them (e.g., \fBMutt\fR's \fBmaildir_trash\fR option). Note that most
messages are propagated a long time before they are deleted, so this is a
corner case you probably do not want to optimize for. This also implies
that the \fBTrashNewOnly\fR and \fBTrashRemoteNew\fR options are typically
not very useful.
.P
If your server supports auto-trashing (as Gmail does), it is probably a
good idea to rely on that instead of \fBmbsync\fR's trash functionality.
If you do that, and intend to synchronize the trash like other mailboxes,
you should not use \fBmbsync\fR's \fBTrash\fR option at all.
.P
Use of the \fBTrash\fR option with M$ Exchange 2013 requires the use of
\fBDisableExtension MOVE\fR due to a server bug.
.P
When using the more efficient default UID mapping scheme, it is important
that the MUA renames files when moving them between Maildir folders.
Mutt always does that, while mu4e needs to be configured to do it:
.br
.in +4
(setq mu4e-change-filenames-when-moving t)
.in -4
.
.SH INHERENT PROBLEMS
Changes done after \fBmbsync\fR has retrieved the message list will not be
synchronised until the next time \fBmbsync\fR is invoked.
.P
Using \fBTrash\fR on IMAP Stores without the UIDPLUS extension (notably,
M$ Exchange up to at least 2010) bears a race condition: messages will be
lost if they are marked as deleted after the message list was retrieved but
before the mailbox is expunged.
There is no risk as long as the IMAP mailbox is accessed by only one client
(including \fBmbsync\fR) at a time.
.
.SH FILES
.TP
.B ~/.mbsyncrc
Default configuration file
.TP
.B ~/.mbsync/
Directory containing synchronization state files
.
.SH SEE ALSO
mdconvert(1), mutt(1), maildir(5)
.P
Up to date information on \fBmbsync\fR can be found at http://isync.sf.net/
.P
SASL mechanisms are listed at
http://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml
.
.SH AUTHORS
Originally written by Michael R. Elkins,
rewritten and currently maintained by Oswald Buddenhagen,
contributions by Theodore Y. Ts'o.

View file

@ -1,97 +0,0 @@
# Global configuration section
# Values here are used as defaults for any following Channel section that
# doesn't specify them.
Expunge None
Create Both
MaildirStore local
Path ~/Mail/
Trash Trash
IMAPStore work
Host work.host.com
User tehuser
Pass xxxxxxxx
# Fetch password from gnome-keyring:
#PassCmd "gnome-keyring-query get mail_pw"
# Fetch password from .netrc:
#PassCmd "sed -n -e 's,^machine work\\.host\\.com login tehuser password \\(.*\\),\\1,p' < $HOME/.netrc"
# Fetch password from a gpg-encrypted file:
#PassCmd "gpg --quiet --for-your-eyes-only --decrypt $HOME/imappassword.gpg"
# Fetch password from pwmd (http://pwmd.sourceforge.net/):
#PassCmd "echo -ne 'GET myIsp\\tpassword' | pwmc datafile"
Channel work
Far :work:
Near :local:work
Expunge Near
Sync PullNew Push
IMAPStore personal
Host host.play.com
Port 6789
RequireSSL no
Channel personal
Far :personal:
Near :local:personal
Expunge Both
MaxMessages 150
MaxSize 200k
IMAPStore remote
Tunnel "ssh -q host.remote.com /usr/sbin/imapd"
Channel remote
Far :remote:
Near :local:remote
Group boxes
Channels work personal remote
IMAPStore st1
Host st1.domain.com
RequireCRAM yes
CertificateFile ~/.st1-certificate.crt
IMAPStore st2
Host imap.another-domain.com
Path non-standard/
RequireSSL no
UseTLSv1 no
Channel rst
Far :st1:somebox
Near :st2:
IMAPAccount server
Host imaps:foo.bar.com
CertificateFile ~/.server-certificate.crt
IMAPStore server
Account server
MapInbox inbox
Trash ~/trash
TrashRemoteNew yes
MaildirStore mirror
Path ~/Maildir/
SubFolders Verbatim
Channel o2o
Far :server:
Near :mirror:
Patterns %
Group partial o2o:inbox,sent-mail,foobar
# INBOX => server, INBOX.foo => server.foo, etc.
Channel inbox
Far :server:INBOX
Near :mirror:server
Patterns *

View file

@ -1,50 +0,0 @@
.ig
\" mdconvert - Maildir mailbox UID storage scheme converter
\" Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
\"
\" 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 2 of the License, or
\" (at your option) any later version.
\"
\" This program is distributed in the hope that it will be useful,
\" but WITHOUT ANY WARRANTY; without even the implied warranty of
\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\" GNU General Public License for more details.
\"
\" You should have received a copy of the GNU General Public License
\" along with this program. If not, see <http://www.gnu.org/licenses/>.
..
.TH mdconvert 1 "2004 Mar 27"
..
.SH NAME
mdconvert - Maildir mailbox UID storage scheme converter
..
.SH SYNOPSIS
\fBmdconvert\fR [\fIoptions\fR ...] \fImailbox\fR ...
..
.SH DESCRIPTION
\fBmdconvert\fR converts Maildir mailboxes between the two UID storage schemes
supported by \fBmbsync\fR. See \fBmbsync\fR's manual page for details on these
schemes.
..
.SH OPTIONS
.TP
\fB-a\fR, \fB--alt\fR
Convert to the \fBalternative\fR (Berkeley DB based) UID storage scheme.
.TP
\fB-n\fR, \fB--native\fR
Convert to the \fBnative\fR (file name based) UID storage scheme.
This is the default.
.TP
\fB-h\fR, \fB--help\fR
Displays a summary of command line options.
.TP
\fB-v\fR, \fB--version\fR
Displays version information.
..
.SH SEE ALSO
mbsync(1)
..
.SH AUTHOR
Written and maintained by Oswald Buddenhagen <ossi@users.sf.net>.

View file

@ -1,284 +0,0 @@
/*
* mdconvert - Maildir UID scheme converter
* Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <autodefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <dirent.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <db.h>
#define EXE "mdconvert"
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_NORETURN __attribute__((noreturn))
# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
#else
# define ATTR_NORETURN
# define ATTR_PRINTFLIKE(fmt,var)
#endif
static void ATTR_NORETURN
oob( void )
{
fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
abort();
}
static void ATTR_PRINTFLIKE(1, 2)
sys_error( const char *msg, ... )
{
va_list va;
char buf[1024];
va_start( va, msg );
if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf))
oob();
va_end( va );
perror( buf );
}
static int ATTR_PRINTFLIKE(3, 4)
nfsnprintf( char *buf, int blen, const char *fmt, ... )
{
int ret;
va_list va;
va_start( va, fmt );
if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
oob();
va_end( va );
return ret;
}
static const char *subdirs[] = { "cur", "new" };
static struct flock lck;
static DBT key, value;
static int
convert( const char *box, int altmap )
{
DB *db;
DIR *d;
struct dirent *e;
const char *u, *ru;
char *p, *s, *dpath, *spath, *dbpath;
int i, n, ret, sfd, dfd, bl, ml, uv[2], uid;
struct stat st;
char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX];
char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX];
if (stat( box, &st ) || !S_ISDIR(st.st_mode)) {
fprintf( stderr, "'%s' is no Maildir mailbox.\n", box );
return 1;
}
nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box );
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box );
if (altmap)
dpath = umpath, spath = uvpath, dbpath = tdpath;
else
spath = umpath, dpath = uvpath, dbpath = umpath;
nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath );
if ((sfd = open( spath, O_RDWR )) < 0) {
if (errno != ENOENT)
sys_error( "Cannot open %s", spath );
return 1;
}
if (fcntl( sfd, F_SETLKW, &lck )) {
sys_error( "Cannot lock %s", spath );
goto sbork;
}
if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) {
sys_error( "Cannot create %s", tdpath );
goto sbork;
}
if (db_create( &db, NULL, 0 )) {
fputs( "Error: db_create() failed\n", stderr );
goto tbork;
}
if ((ret = (db->open)( db, NULL, dbpath, NULL, DB_HASH, altmap ? DB_CREATE|DB_TRUNCATE : 0, 0 ))) {
db->err( db, ret, "Error: db->open(%s)", dbpath );
dbork:
db->close( db, 0 );
tbork:
unlink( tdpath );
close( dfd );
sbork:
close( sfd );
return 1;
}
key.data = (void *)"UIDVALIDITY";
key.size = 11;
if (altmap) {
if ((n = read( sfd, buf, sizeof(buf) - 1 )) <= 0 ||
(buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2))
{
fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box );
goto dbork;
}
value.data = uv;
value.size = sizeof(uv);
if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box );
goto dbork;
}
} else {
if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box );
goto dbork;
}
n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] );
if (write( dfd, buf, n ) != n) {
fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box );
goto dbork;
}
}
again:
for (i = 0; i < 2; i++) {
bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] );
if (!(d = opendir( buf ))) {
sys_error( "Cannot list %s", buf );
goto dbork;
}
while ((e = readdir( d ))) {
if (*e->d_name == '.')
continue;
nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name );
memcpy( buf2, buf, bl );
p = strstr( e->d_name, ",U=" );
if (p)
for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++);
else
u = ru = strchr( e->d_name, ':' );
if (u)
ml = u - e->d_name;
else
ru = "", ml = sizeof(buf);
if (altmap) {
if (!p)
continue;
key.data = e->d_name;
key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name);
uid = atoi( p + 3 );
value.data = &uid;
value.size = sizeof(uid);
if ((ret = db->put( db, NULL, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot write UID for '%s'", box );
goto ebork;
}
nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru );
} else {
s = strpbrk( e->d_name, ",:" );
key.data = e->d_name;
key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name );
if ((ret = db->get( db, NULL, &key, &value, 0 ))) {
if (ret != DB_NOTFOUND) {
db->err( db, ret, "Error: cannot read UID for '%s'", box );
goto ebork;
}
continue;
}
uid = *(int *)value.data;
nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru );
}
if (rename( buf, buf2 )) {
if (errno == ENOENT) {
closedir( d );
goto again;
}
sys_error( "Cannot rename %s to %s", buf, buf2 );
ebork:
closedir( d );
goto dbork;
}
}
closedir( d );
}
db->close( db, 0 );
close( dfd );
if (rename( tdpath, dpath )) {
sys_error( "Cannot rename %s to %s", tdpath, dpath );
close( sfd );
return 1;
}
if (unlink( spath ))
sys_error( "Cannot remove %s", spath );
close( sfd );
return 0;
}
int
main( int argc, char **argv )
{
int oint, ret, altmap = 0;
for (oint = 1; oint < argc; oint++) {
if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) {
puts(
"Usage: " EXE " [-a] mailbox...\n"
" -a, --alt convert to alternative (DB based) UID scheme\n"
" -n, --native convert to native (file name based) UID scheme (default)\n"
" -h, --help show this help message\n"
" -v, --version display version"
);
return 0;
} else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) {
puts( EXE " " VERSION " - Maildir UID scheme converter" );
return 0;
} else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) {
altmap = 1;
} else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) {
altmap = 0;
} else if (!strcmp( argv[oint], "--" )) {
oint++;
break;
} else if (argv[oint][0] == '-') {
fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] );
return 1;
} else
break;
}
if (oint == argc) {
fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" );
return 1;
}
#if SEEK_SET != 0
lck.l_whence = SEEK_SET;
#endif
#if F_WRLCK != 0
lck.l_type = F_WRLCK;
#endif
ret = 0;
for (; oint < argc; oint++)
ret |= convert( argv[oint], altmap );
return ret;
}

View file

@ -1,806 +0,0 @@
#! /usr/bin/perl -w
#
# Copyright (C) 2006,2013 Oswald Buddenhagen <ossi@users.sf.net>
#
# 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 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
use warnings;
use strict;
use Cwd;
use File::Path;
use File::Temp 'tempdir';
my $use_vg = $ENV{USE_VALGRIND};
my $mbsync = getcwd()."/mbsync";
if (!-d "tmp") {
unlink "tmp";
my $tdir = tempdir();
symlink $tdir, "tmp" or die "Cannot symlink temp directory: $!\n";
}
chdir "tmp" or die "Cannot enter temp direcory.\n";
sub show($$$);
sub test($$$$);
################################################################################
# Format of the test defs: [ far, near, state ]
# far/near: [ maxuid, { seq, uid, flags }... ]
# state: [ MaxPulledUid, MaxExpiredFarUid, MaxPushedUid, { muid, suid, flags }... ]
use enum qw(:=1 A..Z);
sub mn($) { chr(64 + shift) }
# generic syncing tests
my @x01 = (
[ 9,
A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
[ 9,
A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "", J, 9, "" ],
[ 8, 0, 0,
1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "" ],
);
my @O01 = ("", "", "");
#show("01", "01", "01");
my @X01 = (
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "", J, 10, "" ],
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "T", J, 9, "", I, 10, "" ],
[ 10, 0, 10,
1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 0, "", 7, 7, "FT", 0, 8, "", 10, 9, "", 9, 10, "" ],
);
test("full", \@x01, \@X01, \@O01);
my @O02 = ("", "", "Expunge Both\n");
#show("01", "02", "02");
my @X02 = (
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", I, 9, "", J, 10, "" ],
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", J, 9, "", I, 10, "" ],
[ 10, 0, 10,
1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 10, 9, "", 9, 10, "" ],
);
test("full + expunge both", \@x01, \@X02, \@O02);
my @O03 = ("", "", "Expunge Near\n");
#show("01", "03", "03");
my @X03 = (
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "", J, 10, "" ],
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", J, 9, "", I, 10, "" ],
[ 10, 0, 10,
1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ],
);
test("full + expunge near side", \@x01, \@X03, \@O03);
my @O04 = ("", "", "Sync Pull\n");
#show("01", "04", "04");
my @X04 = (
[ 9,
A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
[ 10,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "T", J, 9, "", I, 10, "" ],
[ 9, 0, 0,
1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 0, 8, "", 9, 10, "" ],
);
test("pull", \@x01, \@X04, \@O04);
my @O05 = ("", "", "Sync Flags\n");
#show("01", "05", "05");
my @X05 = (
[ 9,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
[ 9,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "", J, 9, "" ],
[ 8, 0, 0,
1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 8, 8, "" ],
);
test("flags", \@x01, \@X05, \@O05);
my @O06 = ("", "", "Sync Delete\n");
#show("01", "06", "06");
my @X06 = (
[ 9,
A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "" ],
[ 9,
A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "T", J, 9, "" ],
[ 8, 0, 0,
1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 0, "", 7, 7, "", 0, 8, "" ],
);
test("deletions", \@x01, \@X06, \@O06);
my @O07 = ("", "", "Sync New\n");
#show("01", "07", "07");
my @X07 = (
[ 10,
A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "", J, 10, "" ],
[ 10,
A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "", J, 9, "", I, 10, "" ],
[ 10, 0, 10,
1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "", 10, 9, "", 9, 10, "" ],
);
test("new", \@x01, \@X07, \@O07);
my @O08 = ("", "", "Sync PushFlags PullDelete\n");
#show("01", "08", "08");
my @X08 = (
[ 9,
A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
[ 9,
A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "T", J, 9, "" ],
[ 8, 0, 0,
1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 0, 8, "" ],
);
test("push flags + pull deletions", \@x01, \@X08, \@O08);
# size restriction tests
my @x10 = (
[ 2,
A, 1, "", B, 2, "*" ],
[ 1,
C, 1, "*" ],
[ 0, 0, 0,
],
);
my @O11 = ("MaxSize 1k\n", "MaxSize 1k\n", "Expunge Near");
#show("10", "11", "11");
my @X11 = (
[ 3,
A, 1, "", B, 2, "*", C, 3, "?" ],
[ 3,
C, 1, "*", A, 2, "", B, 3, "?" ],
[ 3, 0, 3,
3, 1, "<", 1, 2, "", 2, 3, ">" ],
);
test("max size", \@x10, \@X11, \@O11);
my @x22 = (
[ 3,
A, 1, "", B, 2, "*", C, 3, "?" ],
[ 3,
C, 1, "F*", A, 2, "", B, 3, "F?" ],
[ 3, 0, 3,
3, 1, "<", 1, 2, "", 2, 3, ">" ],
);
#show("22", "22", "11");
my @X22 = (
[ 4,
A, 1, "", B, 2, "*", C, 3, "T?", C, 4, "F*" ],
[ 4,
C, 1, "F*", A, 2, "", B, 4, "*" ],
[ 4, 0, 4,
4, 1, "F", 3, 0, "T", 1, 2, "", 2, 4, "" ],
);
test("max size + flagging", \@x22, \@X22, \@O11);
my @x23 = (
[ 2,
A, 1, "", B, 2, "F*" ],
[ 1,
C, 1, "F*" ],
[ 0, 0, 0,
],
);
my @X23 = (
[ 3,
A, 1, "", B, 2, "F*", C, 3, "F*" ],
[ 3,
C, 1, "F*", A, 2, "", B, 3, "F*" ],
[ 3, 0, 3,
3, 1, "F", 1, 2, "", 2, 3, "F" ]
);
test("max size + initial flagging", \@x23, \@X23, \@O11);
my @x24 = (
[ 3,
A, 1, "", B, 2, "*", C, 3, "F*" ],
[ 1,
A, 1, "" ],
[ 3, 0, 1,
1, 1, "", 2, 0, "^", 3, 0, "^" ],
);
my @X24 = (
[ 3,
A, 1, "", B, 2, "*", C, 3, "F*" ],
[ 3,
A, 1, "", B, 2, "?", C, 3, "F*" ],
[ 3, 0, 3,
1, 1, "", 2, 2, ">", 3, 3, "F" ],
);
test("max size (pre-1.4 legacy)", \@x24, \@X24, \@O11);
# expiration tests
my @x30 = (
[ 6,
A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
[ 0,
],
[ 0, 0, 0,
],
);
my @O31 = ("", "", "MaxMessages 3\n");
#show("30", "31", "31");
my @X31 = (
[ 6,
A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
[ 5,
A, 1, "F", B, 2, "", D, 3, "", E, 4, "S", F, 5, "" ],
[ 6, 3, 5,
1, 1, "F", 2, 2, "", 4, 3, "", 5, 4, "S", 6, 5, "" ],
);
test("max messages", \@x30, \@X31, \@O31);
my @O32 = ("", "", "MaxMessages 3\nExpireUnread yes\n");
#show("30", "32", "32");
my @X32 = (
[ 6,
A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
[ 4,
A, 1, "F", D, 2, "", E, 3, "S", F, 4, "" ],
[ 6, 3, 4,
1, 1, "F", 4, 2, "", 5, 3, "S", 6, 4, "" ],
);
test("max messages vs. unread", \@x30, \@X32, \@O32);
my @x50 = (
[ 6,
A, 1, "FS", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
[ 6,
A, 1, "S", B, 2, "ST", D, 4, "", E, 5, "", F, 6, "" ],
[ 6, 3, 0,
1, 1, "FS", 2, 2, "~S", 3, 3, "~S", 4, 4, "", 5, 5, "", 6, 6, "" ],
);
my @O51 = ("", "", "MaxMessages 3\nExpunge Both\n");
#show("50", "51", "51");
my @X51 = (
[ 6,
A, 1, "S", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
[ 6,
B, 2, "FS", D, 4, "", E, 5, "", F, 6, "" ],
[ 6, 3, 6,
2, 2, "FS", 4, 4, "", 5, 5, "", 6, 6, "" ],
);
test("max messages + expunge", \@x50, \@X51, \@O51);
################################################################################
print "OK.\n";
exit 0;
sub qm($)
{
shift;
s/\\/\\\\/g;
s/\"/\\"/g;
s/\"/\\"/g;
s/\n/\\n/g;
return $_;
}
# [ $far, $near, $channel ]
sub writecfg($)
{
my ($sfx) = @_;
open(FILE, ">", ".mbsyncrc") or
die "Cannot open .mbsyncrc.\n";
print FILE
"FSync no
MaildirStore far
Path ./
Inbox ./far
".$$sfx[0]."
MaildirStore near
Path ./
Inbox ./near
".$$sfx[1]."
Channel test
Far :far:
Near :near:
SyncState *
".$$sfx[2];
close FILE;
}
sub killcfg()
{
unlink $_ for (glob("*.log"));
unlink ".mbsyncrc";
}
# $run_async, $mbsync_options, $log_file
# Return: $exit_code, \@mbsync_output
sub runsync($$$)
{
my ($async, $flags, $file) = @_;
my $cmd;
if ($use_vg) {
$cmd = "valgrind -q --error-exitcode=1 ";
} else {
$flags .= " -D";
}
$flags .= " -Ta" if ($async);
$cmd .= "$mbsync -Tz $flags -c .mbsyncrc test";
open FILE, "$cmd 2>&1 |";
my @out = <FILE>;
close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n");
if ($file) {
open FILE, ">$file" or die("Cannot create $file: $!\n");
print FILE @out;
close FILE;
}
return $?, \@out;
}
# $path
# Return: $max_uid, { uid => [ seq, flags ] }
sub readbox($)
{
my $bn = shift;
(-d $bn) or
die "No mailbox '$bn'.\n";
(-d $bn."/tmp" and -d $bn."/new" and -d $bn."/cur") or
die "Invalid mailbox '$bn'.\n";
open(FILE, "<", $bn."/.uidvalidity") or die "Cannot read UID validity of mailbox '$bn'.\n";
my $dummy = <FILE>;
chomp(my $mu = <FILE>);
close FILE;
my %ms = ();
for my $d ("cur", "new") {
opendir(DIR, $bn."/".$d) or next;
for my $f (grep(!/^\.\.?$/, readdir(DIR))) {
my ($uid, $flg, $ph, $num);
if ($f =~ /^\d+\.\d+_\d+\.[-[:alnum:]]+,U=(\d+):2,(.*)$/) {
($uid, $flg) = ($1, $2);
} else {
print STDERR "unrecognided file name '$f' in '$bn'.\n";
exit 1;
}
open(FILE, "<", $bn."/".$d."/".$f) or die "Cannot read message '$f' in '$bn'.\n";
my $sz = 0;
while (<FILE>) {
/^Subject: (\[placeholder\] )?(\d+)$/ && ($ph = defined($1), $num = $2);
$sz += length($_);
}
close FILE;
if (!defined($num)) {
print STDERR "message '$f' in '$bn' has no identifier.\n";
exit 1;
}
@{ $ms{$uid} } = ($num, $flg.($sz>1000?"*":"").($ph?"?":""));
}
}
return $mu, \%ms;
}
# $boxname
# Output:
# [ maxuid,
# serial, uid, "flags", ... ],
sub showbox($)
{
my ($bn) = @_;
my ($mu, $ms) = readbox($bn);
my @bc = ($mu);
for my $uid (sort { $a <=> $b } keys %$ms) {
push @bc, $$ms{$uid}[0], $uid, $$ms{$uid}[1];
}
printbox(\@bc);
}
# $filename
# Output:
# [ maxuid[F], maxxfuid, maxuid[N],
# uid[F], uid[N], "flags", ... ],
sub showstate($)
{
my ($fn) = @_;
if (!open(FILE, "<", $fn)) {
print STDERR " Cannot read sync state $fn: $!\n";
return;
}
chomp(my @ls = <FILE>);
close FILE;
my %hdr;
OUTER: while (1) {
while (@ls) {
$_ = shift(@ls);
last OUTER if (!length($_));
if (!/^([^ ]+) (\d+)$/) {
print STDERR "Malformed sync state header entry: $_\n";
close FILE;
return;
}
$hdr{$1} = $2;
}
print STDERR "Unterminated sync state header.\n";
close FILE;
return;
}
my @T = ($hdr{'MaxPulledUid'} // "missing",
$hdr{'MaxExpiredFarUid'} // "0",
$hdr{'MaxPushedUid'} // "missing");
for (@ls) {
/^(\d+) (\d+) (.*)$/;
push @T, $1, $2, $3;
}
printstate(\@T);
}
# $filename
sub showchan($)
{
my ($fn) = @_;
showbox("far");
showbox("near");
showstate($fn);
}
# $source_state_name, $target_state_name, $configs_name
sub show($$$)
{
my ($sx, $tx, $sfxn) = @_;
my ($sp, $sfx);
eval "\$sp = \\\@x$sx";
eval "\$sfx = \\\@O$sfxn";
mkchan($$sp[0], $$sp[1], $$sp[2]);
print "my \@x$sx = (\n";
showchan("near/.mbsyncstate");
print ");\n";
writecfg($sfx);
runsync(0, "", "");
killcfg();
print "my \@X$tx = (\n";
showchan("near/.mbsyncstate");
print ");\n";
print "test(\"\", \\\@x$sx, \\\@X$tx, \\\@O$sfxn);\n\n";
rmtree "near";
rmtree "far";
}
# $box_name, \@box_state
sub mkbox($$)
{
my ($bn, $bs) = @_;
rmtree($bn);
(mkdir($bn) and mkdir($bn."/tmp") and mkdir($bn."/new") and mkdir($bn."/cur")) or
die "Cannot create mailbox $bn.\n";
open(FILE, ">", $bn."/.uidvalidity") or die "Cannot create UID validity for mailbox $bn.\n";
print FILE "1\n$$bs[0]\n";
close FILE;
for (my $i = 1; $i < @$bs; $i += 3) {
my ($num, $uid, $flg) = ($$bs[$i], $$bs[$i + 1], $$bs[$i + 2]);
my $big = $flg =~ s/\*//;
my $ph = $flg =~ s/\?//;
open(FILE, ">", $bn."/".($flg =~ /S/ ? "cur" : "new")."/0.1_".$num.".local,U=".$uid.":2,".$flg) or
die "Cannot create message ".mn($num)." in mailbox $bn.\n";
print FILE "From: foo\nTo: bar\nDate: Thu, 1 Jan 1970 00:00:00 +0000\nSubject: ".($ph?"[placeholder] ":"").$num."\n\n".(("A"x50)."\n")x($big*30);
close FILE;
}
}
# \@far_state, \@near_state, \@sync_state
sub mkchan($$$)
{
my ($f, $n, $t) = @_;
mkbox("far", $f);
mkbox("near", $n);
open(FILE, ">", "near/.mbsyncstate") or
die "Cannot create sync state.\n";
print FILE "FarUidValidity 1\nMaxPulledUid ".$$t[0]."\n".
"NearUidValidity 1\nMaxExpiredFarUid ".$$t[1]."\nMaxPushedUid ".$$t[2]."\n\n";
for (my $i = 3; $i < @$t; $i += 3) {
print FILE $$t[$i]." ".$$t[$i + 1]." ".$$t[$i + 2]."\n";
}
close FILE;
}
# $box_name, \@box_state
sub ckbox($$)
{
my ($bn, $bs) = @_;
my ($mu, $ms) = readbox($bn);
if ($mu != $$bs[0]) {
print STDERR "MAXUID mismatch for '$bn' (got $mu, wanted $$bs[0]).\n";
return 1;
}
for (my $i = 1; $i < @$bs; $i += 3) {
my ($num, $uid, $flg) = ($$bs[$i], $$bs[$i + 1], $$bs[$i + 2]);
my $m = delete $$ms{$uid};
if (!defined $m) {
print STDERR "No message $bn:$uid.\n";
return 1;
}
if ($$m[0] ne $num) {
print STDERR "Subject mismatch for $bn:$uid.\n";
return 1;
}
if ($$m[1] ne $flg) {
print STDERR "Flag mismatch for $bn:$uid.\n";
return 1;
}
}
if (%$ms) {
print STDERR "Excess messages in '$bn': ".join(", ", sort({ $a <=> $b } keys(%$ms))).".\n";
return 1;
}
return 0;
}
# $state_file, \@sync_state
sub ckstate($$)
{
my ($fn, $t) = @_;
my %hdr;
$hdr{'FarUidValidity'} = "1";
$hdr{'NearUidValidity'} = "1";
$hdr{'MaxPulledUid'} = $$t[0];
$hdr{'MaxPushedUid'} = $$t[2];
$hdr{'MaxExpiredFarUid'} = $$t[1] if ($$t[1] ne 0);
open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n";
chomp(my @ls = <FILE>);
close FILE;
OUTER: while (1) {
while (@ls) {
my $l = shift(@ls);
last OUTER if (!length($l));
if ($l !~ /^([^ ]+) (\d+)$/) {
print STDERR "Malformed sync state header entry: $l\n";
return 1;
}
my $want = delete $hdr{$1};
if (!defined($want)) {
print STDERR "Unexpected sync state header entry: $1\n";
return 1;
}
if ($2 != $want) {
print STDERR "Sync state header entry $1 mismatch: got $2, wanted $want\n";
return 1;
}
}
print STDERR "Unterminated sync state header.\n";
return 1;
}
my @ky = keys %hdr;
if (@ky) {
print STDERR "Keys missing from sync state header: @ky\n";
return 1;
}
my $i = 3;
for my $l (@ls) {
if ($i == @$t) {
print STDERR "Excess sync state entry: '$l'.\n";
return 1;
}
my $xl = $$t[$i]." ".$$t[$i + 1]." ".$$t[$i + 2];
if ($l ne $xl) {
print STDERR "Sync state entry mismatch: '$l' instead of '$xl'.\n";
return 1;
}
$i += 3;
}
if ($i < @$t) {
print STDERR "Missing sync state entry: '".$$t[$i]." ".$$t[$i + 1]." ".$$t[$i + 2]."'.\n";
return 1;
}
return 0;
}
# $state_file, \@chan_state
sub ckchan($$)
{
my ($fn, $cs) = @_;
my $rslt = ckstate($fn, $$cs[2]);
$rslt |= ckbox("far", $$cs[0]);
$rslt |= ckbox("near", $$cs[1]);
return $rslt;
}
# \@box_state
sub printbox($)
{
my ($bs) = @_;
print " [ $$bs[0],\n ";
my $frst = 1;
for (my $i = 1; $i < @$bs; $i += 3) {
if ($frst) {
$frst = 0;
} else {
print ", ";
}
print mn($$bs[$i]).", ".$$bs[$i + 1].", \"".$$bs[$i + 2]."\"";
}
print " ],\n";
}
# \@sync_state
sub printstate($)
{
my ($t) = @_;
print " [ ".$$t[0].", ".$$t[1].", ".$$t[2].",\n ";
my $frst = 1;
for (my $i = 3; $i < @$t; $i += 3) {
if ($frst) {
$frst = 0;
} else {
print ", ";
}
print(($$t[$i] // "??").", ".($$t[$i + 1] // "??").", \"".($$t[$i + 2] // "??")."\"");
}
print " ],\n";
}
# \@chan_state
sub printchan($)
{
my ($cs) = @_;
printbox($$cs[0]);
printbox($$cs[1]);
printstate($$cs[2]);
}
sub readfile($)
{
my ($file) = @_;
open(FILE, $file) or return;
my @nj = <FILE>;
close FILE;
return \@nj;
}
# $run_async, \@source_state, \@target_state, \@channel_configs
sub test_impl($$$$)
{
my ($async, $sx, $tx, $sfx) = @_;
mkchan($$sx[0], $$sx[1], $$sx[2]);
my ($xc, $ret) = runsync($async, "-Tj", "1-initial.log");
if ($xc || ckchan("near/.mbsyncstate.new", $tx)) {
print "Input:\n";
printchan($sx);
print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @$sfx))." ]\n";
if (!$xc) {
print "Expected result:\n";
printchan($tx);
print "Actual result:\n";
showchan("near/.mbsyncstate.new");
}
print "Debug output:\n";
print @$ret;
exit 1;
}
my $nj = readfile("near/.mbsyncstate.journal");
my ($jxc, $jret) = runsync($async, "-0 --no-expunge", "2-replay.log");
if ($jxc || ckstate("near/.mbsyncstate", $$tx[2])) {
print "Journal replay failed.\n";
print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @$sfx))." ], [ \"-0\", \"--no-expunge\" ]\n";
print "Old State:\n";
printstate($$sx[2]);
print "Journal:\n".join("", @$nj)."\n";
if (!$jxc) {
print "Expected New State:\n";
printstate($$tx[2]);
print "New State:\n";
showstate("near/.mbsyncstate");
}
print "Debug output:\n";
print @$jret;
exit 1;
}
my ($ixc, $iret) = runsync($async, "", "3-verify.log");
if ($ixc || ckchan("near/.mbsyncstate", $tx)) {
print "Idempotence verification run failed.\n";
print "Input == Expected result:\n";
printchan($tx);
print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @$sfx))." ]\n";
if (!$ixc) {
print "Actual result:\n";
showchan("near/.mbsyncstate");
}
print "Debug output:\n";
print @$iret;
exit 1;
}
rmtree "near";
rmtree "far";
my $njl = (@$nj - 1) * 2;
for (my $l = 1; $l <= $njl; $l++) {
mkchan($$sx[0], $$sx[1], $$sx[2]);
my ($nxc, $nret) = runsync($async, "-Tj$l", "4-interrupt.log");
if ($nxc != (100 + ($l & 1)) << 8) {
print "Interrupting at step $l/$njl failed.\n";
print "Debug output:\n";
print @$nret;
exit 1;
}
($nxc, $nret) = runsync($async, "-Tj", "5-resume.log");
if ($nxc || ckchan("near/.mbsyncstate.new", $tx)) {
print "Resuming from step $l/$njl failed.\n";
print "Input:\n";
printchan($sx);
print "Options:\n";
print " [ ".join(", ", map('"'.qm($_).'"', @$sfx))." ]\n";
my $nnj = readfile("near/.mbsyncstate.journal");
my $ln = int($l / 2);
print "Journal:\n".join("", @$nnj[0..$ln])."-------\n".join("", @$nnj[($ln + 1)..$#$nnj])."\n";
print "Full journal:\n".join("", @$nj)."\n";
if (!$nxc) {
print "Expected result:\n";
printchan($tx);
print "Actual result:\n";
showchan("near/.mbsyncstate.new");
}
print "Debug output:\n";
print @$nret;
exit 1;
}
rmtree "near";
rmtree "far";
}
}
# $title, \@source_state, \@target_state, \@channel_configs
sub test($$$$)
{
my ($ttl, $sx, $tx, $sfx) = @_;
return 0 if (scalar(@ARGV) && !grep { $_ eq $ttl } @ARGV);
print "Testing: ".$ttl." ...\n";
writecfg($sfx);
test_impl(0, $sx, $tx, $sfx);
test_impl(1, $sx, $tx, $sfx);
killcfg();
}

File diff suppressed because it is too large Load diff

View file

@ -1,155 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef SOCKET_H
#define SOCKET_H
#include "common.h"
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
#ifdef HAVE_LIBSSL
# include <openssl/ssl.h>
# include <openssl/x509.h>
enum {
TLSv1 = 4,
TLSv1_1 = 8,
TLSv1_2 = 16,
TLSv1_3 = 32
};
#endif
typedef struct {
char *tunnel;
char *host;
ushort port;
int timeout;
#ifdef HAVE_LIBSSL
char *cert_file;
char *client_certfile;
char *client_keyfile;
char *cipher_string;
char system_certs;
char ssl_versions;
/* these are actually variables and are leaked at the end */
char ssl_ctx_valid;
STACK_OF(X509) *trusted_certs;
SSL_CTX *SSLContext;
#endif
} server_conf_t;
typedef struct buff_chunk {
struct buff_chunk *next;
uint len;
char data[1];
} buff_chunk_t;
typedef struct {
/* connection */
int fd;
int state;
const server_conf_t *conf; /* needed during connect */
#ifdef HAVE_IPV6
struct addrinfo *addrs, *curr_addr; /* needed during connect */
#else
struct addr_info *addrs, *curr_addr; /* needed during connect */
#endif
char *name;
#ifdef HAVE_LIBSSL
SSL *ssl;
wakeup_t ssl_fake;
#endif
#ifdef HAVE_LIBZ
z_streamp in_z, out_z;
wakeup_t z_fake;
int z_written;
#endif
void (*bad_callback)( void *aux ); /* async fail while sending or listening */
void (*read_callback)( void *aux ); /* data available for reading */
void (*write_callback)( void *aux ); /* all *queued* data was sent */
union {
void (*connect)( int ok, void *aux );
void (*starttls)( int ok, void *aux );
} callbacks;
void *callback_aux;
notifier_t notify;
wakeup_t fd_fake;
wakeup_t fd_timeout;
/* writing */
buff_chunk_t *append_buf; /* accumulating buffer */
buff_chunk_t *write_buf, **write_buf_append; /* buffer head & tail */
#ifdef HAVE_LIBZ
uint append_avail; /* space left in accumulating buffer */
#endif
uint write_offset; /* offset into buffer head */
uint buffer_mem; /* memory currently occupied by buffers in the queue */
/* reading */
uint offset; /* start of filled bytes in buffer */
uint bytes; /* number of filled bytes in buffer */
uint scanoff; /* offset to continue scanning for newline at, relative to 'offset' */
char buf[100000];
#ifdef HAVE_LIBZ
char z_buf[100000];
#endif
} conn_t;
/* call this before doing anything with the socket */
static INLINE void socket_init( conn_t *conn,
const server_conf_t *conf,
void (*bad_callback)( void *aux ),
void (*read_callback)( void *aux ),
void (*write_callback)( void *aux ),
void *aux )
{
conn->conf = conf;
conn->bad_callback = bad_callback;
conn->read_callback = read_callback;
conn->write_callback = write_callback;
conn->callback_aux = aux;
conn->fd = -1;
conn->name = NULL;
conn->write_buf_append = &conn->write_buf;
}
void socket_connect( conn_t *conn, void (*cb)( int ok, void *aux ) );
void socket_start_tls(conn_t *conn, void (*cb)( int ok, void *aux ) );
void socket_start_deflate( conn_t *conn );
void socket_close( conn_t *sock );
void socket_expect_activity( conn_t *sock, int expect );
int socket_read( conn_t *sock, char *buf, uint len ); /* never waits */
char *socket_read_line( conn_t *sock ); /* don't free return value; never waits */
typedef enum { KeepOwn = 0, GiveOwn } ownership_t;
typedef struct {
char *buf;
uint len;
ownership_t takeOwn;
} conn_iovec_t;
void socket_write( conn_t *sock, conn_iovec_t *iov, int iovcnt );
#endif

2467
src/sync.c

File diff suppressed because it is too large Load diff

View file

@ -1,87 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2010-2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#ifndef SYNC_H
#define SYNC_H
#include "driver.h"
#define F 0 // far side
#define N 1 // near side
#define OP_NEW (1<<0)
#define OP_RENEW (1<<1)
#define OP_DELETE (1<<2)
#define OP_FLAGS (1<<3)
#define OP_MASK_TYPE (OP_NEW|OP_RENEW|OP_DELETE|OP_FLAGS) /* asserted in the target ops */
#define OP_EXPUNGE (1<<4)
#define OP_CREATE (1<<5)
#define OP_REMOVE (1<<6)
#define XOP_PUSH (1<<8)
#define XOP_PULL (1<<9)
#define XOP_MASK_DIR (XOP_PUSH|XOP_PULL)
#define XOP_HAVE_TYPE (1<<10)
// The following must all have the same bit shift from the corresponding OP_* flags.
#define XOP_HAVE_EXPUNGE (1<<11)
#define XOP_HAVE_CREATE (1<<12)
#define XOP_HAVE_REMOVE (1<<13)
typedef struct channel_conf {
struct channel_conf *next;
const char *name;
store_conf_t *stores[2];
const char *boxes[2];
char *sync_state;
string_list_t *patterns;
int ops[2];
int max_messages; // For near side only.
signed char expire_unread;
char use_internal_date;
} channel_conf_t;
typedef struct group_conf {
struct group_conf *next;
const char *name;
string_list_t *channels;
} group_conf_t;
extern channel_conf_t global_conf;
extern channel_conf_t *channels;
extern group_conf_t *groups;
extern const char *str_fn[2], *str_hl[2];
#define SYNC_OK 0 /* assumed to be 0 */
#define SYNC_FAIL 1
#define SYNC_BAD(fn) (4<<(fn))
#define SYNC_NOGOOD 16 /* internal */
#define SYNC_CANCELED 32 /* internal */
#define BOX_POSSIBLE -1
#define BOX_ABSENT 0
#define BOX_PRESENT 1
/* All passed pointers must stay alive until cb is called. */
void sync_boxes( store_t *ctx[], const char * const names[], int present[], channel_conf_t *chan,
void (*cb)( int sts, void *aux ), void *aux );
#endif

View file

@ -1,116 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2014 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Just to satisfy the references in util.c */
int DFlags;
const char *Home;
typedef struct {
int id;
int first, other, morph_at, morph_to;
time_t start;
wakeup_t timer;
wakeup_t morph_timer;
} tst_t;
static void
timer_start( tst_t *timer, int to )
{
printf( "starting timer %d, should expire after %d\n", timer->id, to );
time( &timer->start );
conf_wakeup( &timer->timer, to );
}
static void
timed_out( void *aux )
{
tst_t *timer = (tst_t *)aux;
printf( "timer %d expired after %d, repeat %d\n",
timer->id, (int)(time( 0 ) - timer->start), timer->other );
if (timer->other >= 0) {
timer_start( timer, timer->other );
} else {
wipe_wakeup( &timer->timer );
wipe_wakeup( &timer->morph_timer );
free( timer );
}
}
static void
morph_timed_out( void *aux )
{
tst_t *timer = (tst_t *)aux;
printf( "morphing timer %d after %d\n",
timer->id, (int)(time( 0 ) - timer->start) );
timer_start( timer, timer->morph_to );
}
static int nextid;
int
main( int argc, char **argv )
{
int i;
for (i = 1; i < argc; i++) {
char *val = argv[i];
tst_t *timer = nfmalloc( sizeof(*timer) );
init_wakeup( &timer->timer, timed_out, timer );
init_wakeup( &timer->morph_timer, morph_timed_out, timer );
timer->id = ++nextid;
timer->first = strtol( val, &val, 0 );
if (*val == '@') {
timer->other = timer->first;
timer->first = strtol( ++val, &val, 0 );
} else {
timer->other = -1;
}
if (*val == ':') {
timer->morph_to = strtol( ++val, &val, 0 );
if (*val != '@')
goto fail;
timer->morph_at = strtol( ++val, &val, 0 );
} else {
timer->morph_at = -1;
}
if (*val) {
fail:
fprintf( stderr, "Fatal: syntax error in %s, use <timeout>[@<delay>][:<newtimeout>@<delay>]\n", argv[i] );
return 1;
}
timer_start( timer, timer->first );
if (timer->morph_at >= 0) {
printf( "timer %d, should morph after %d\n", timer->id, timer->morph_at );
conf_wakeup( &timer->morph_timer, timer->morph_at );
}
}
main_loop();
return 0;
}

View file

@ -1,904 +0,0 @@
/*
* mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2006,2011,2012 Oswald Buddenhagen <ossi@users.sf.net>
*
* 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "common.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
static int need_nl;
void
flushn( void )
{
if (need_nl) {
putchar( '\n' );
fflush( stdout );
need_nl = 0;
}
}
static void ATTR_PRINTFLIKE(1, 0)
printn( const char *msg, va_list va )
{
if (*msg == '\v')
msg++;
else
flushn();
vprintf( msg, va );
fflush( stdout );
}
void
vdebug( int cat, const char *msg, va_list va )
{
if (DFlags & cat) {
vprintf( msg, va );
fflush( stdout );
need_nl = 0;
}
}
void
vdebugn( int cat, const char *msg, va_list va )
{
if (DFlags & cat) {
vprintf( msg, va );
fflush( stdout );
need_nl = 1;
}
}
void
progress( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vprintf( msg, va );
va_end( va );
fflush( stdout );
need_nl = 1;
}
void
info( const char *msg, ... )
{
va_list va;
if (DFlags & VERBOSE) {
va_start( va, msg );
printn( msg, va );
va_end( va );
need_nl = 0;
}
}
void
infon( const char *msg, ... )
{
va_list va;
if (DFlags & VERBOSE) {
va_start( va, msg );
printn( msg, va );
va_end( va );
need_nl = 1;
}
}
void
notice( const char *msg, ... )
{
va_list va;
if (!(DFlags & QUIET)) {
va_start( va, msg );
printn( msg, va );
va_end( va );
need_nl = 0;
}
}
void
warn( const char *msg, ... )
{
va_list va;
if (!(DFlags & VERYQUIET)) {
flushn();
va_start( va, msg );
vfprintf( stderr, msg, va );
va_end( va );
}
}
void
error( const char *msg, ... )
{
va_list va;
flushn();
va_start( va, msg );
vfprintf( stderr, msg, va );
va_end( va );
}
void
vsys_error( const char *msg, va_list va )
{
char buf[1024];
int errno_bak = errno;
flushn();
if ((uint)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf))
oob();
errno = errno_bak;
perror( buf );
}
void
sys_error( const char *msg, ... )
{
va_list va;
va_start( va, msg );
vsys_error( msg, va );
va_end( va );
}
void
add_string_list_n( string_list_t **list, const char *str, uint len )
{
string_list_t *elem;
elem = nfmalloc( offsetof(string_list_t, string) + len + 1 );
elem->next = *list;
*list = elem;
memcpy( elem->string, str, len );
elem->string[len] = 0;
}
void
add_string_list( string_list_t **list, const char *str )
{
add_string_list_n( list, str, strlen( str ) );
}
void
free_string_list( string_list_t *list )
{
string_list_t *tlist;
for (; list; list = tlist) {
tlist = list->next;
free( list );
}
}
#ifndef HAVE_VASPRINTF
static int
vasprintf( char **strp, const char *fmt, va_list ap )
{
int len;
char tmp[1024];
if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 )))
return -1;
if (len >= (int)sizeof(tmp))
vsprintf( *strp, fmt, ap );
else
memcpy( *strp, tmp, (size_t)len + 1 );
return len;
}
#endif
#ifndef HAVE_MEMRCHR
void *
memrchr( const void *s, int c, size_t n )
{
u_char *b = (u_char *)s, *e = b + n;
while (--e >= b)
if (*e == c)
return (void *)e;
return 0;
}
#endif
#ifndef HAVE_STRNLEN
size_t
strnlen( const char *str, size_t maxlen )
{
const char *estr = memchr( str, 0, maxlen );
return estr ? (size_t)(estr - str) : maxlen;
}
#endif
int
starts_with( const char *str, int strl, const char *cmp, uint cmpl )
{
if (strl < 0)
strl = strnlen( str, cmpl + 1 );
return ((uint)strl >= cmpl) && !memcmp( str, cmp, cmpl );
}
int
starts_with_upper( const char *str, int strl, const char *cmp, uint cmpl )
{
if (strl < 0)
strl = strnlen( str, cmpl + 1 );
if ((uint)strl < cmpl)
return 0;
for (uint i = 0; i < cmpl; i++)
if (str[i] != cmp[i] && toupper( str[i] ) != cmp[i])
return 0;
return 1;
}
int
equals( const char *str, int strl, const char *cmp, uint cmpl )
{
if (strl < 0)
strl = strnlen( str, cmpl + 1 );
return ((uint)strl == cmpl) && !memcmp( str, cmp, cmpl );
}
#ifndef HAVE_TIMEGM
/*
Converts struct tm to time_t, assuming the data in tm is UTC rather
than local timezone.
mktime is similar but assumes struct tm, also known as the
"broken-down" form of time, is in local time zone. timegm
uses mktime to make the conversion understanding that an offset
will be introduced by the local time assumption.
mktime_from_utc then measures the introduced offset by applying
gmtime to the initial result and applying mktime to the resulting
"broken-down" form. The difference between the two mktime results
is the measured offset which is then subtracted from the initial
mktime result to yield a calendar time which is the value returned.
tm_isdst in struct tm is set to 0 to force mktime to introduce a
consistent offset (the non DST offset) since tm and tm+o might be
on opposite sides of a DST change.
Some implementations of mktime return -1 for the nonexistent
localtime hour at the beginning of DST. In this event, use
mktime(tm - 1hr) + 3600.
Schematically
mktime(tm) --> t+o
gmtime(t+o) --> tm+o
mktime(tm+o) --> t+2o
t+o - (t+2o - t+o) = t
Contributed by Roger Beeman <beeman@cisco.com>, with the help of
Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO.
Further improved by Roger with assistance from Edward J. Sabol
based on input by Jamie Zawinski.
*/
static time_t
my_mktime( struct tm *t )
{
time_t tl = mktime( t );
if (tl == -1) {
t->tm_hour--;
tl = mktime( t );
if (tl != -1)
tl += 3600;
}
return tl;
}
time_t
timegm( struct tm *t )
{
time_t tl, tb;
struct tm *tg;
if ((tl = my_mktime( t )) == -1)
return tl;
tg = gmtime( &tl );
tg->tm_isdst = 0;
if ((tb = my_mktime( tg )) == -1)
return tb;
return tl - (tb - tl);
}
#endif
void
oob( void )
{
fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
abort();
}
int
nfsnprintf( char *buf, int blen, const char *fmt, ... )
{
int ret;
va_list va;
va_start( va, fmt );
if (blen <= 0 || (uint)(ret = vsnprintf( buf, (size_t)blen, fmt, va )) >= (uint)blen)
oob();
va_end( va );
return ret;
}
void
oom( void )
{
fputs( "Fatal: Out of memory\n", stderr );
abort();
}
void *
nfmalloc( size_t sz )
{
void *ret;
if (!(ret = malloc( sz )))
oom();
return ret;
}
void *
nfcalloc( size_t sz )
{
void *ret;
if (!(ret = calloc( sz, 1 )))
oom();
return ret;
}
void *
nfrealloc( void *mem, size_t sz )
{
char *ret;
if (!(ret = realloc( mem, sz )) && sz)
oom();
return ret;
}
char *
nfstrndup( const char *str, size_t nchars )
{
char *ret = nfmalloc( nchars + 1 );
memcpy( ret, str, nchars );
ret[nchars] = 0;
return ret;
}
char *
nfstrdup( const char *str )
{
return nfstrndup( str, strlen( str ) );
}
int
nfvasprintf( char **str, const char *fmt, va_list va )
{
int ret = vasprintf( str, fmt, va );
if (ret < 0)
oom();
return ret;
}
int
nfasprintf( char **str, const char *fmt, ... )
{
int ret;
va_list va;
va_start( va, fmt );
ret = nfvasprintf( str, fmt, va );
va_end( va );
return ret;
}
/*
static struct passwd *
cur_user( void )
{
char *p;
struct passwd *pw;
uid_t uid;
uid = getuid();
if ((!(p = getenv("LOGNAME")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) &&
(!(p = getenv("USER")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) &&
!(pw = getpwuid( uid )))
{
fputs ("Cannot determinate current user\n", stderr);
return 0;
}
return pw;
}
*/
char *
expand_strdup( const char *s )
{
struct passwd *pw;
const char *p, *q;
char *r;
if (*s == '~') {
s++;
if (!*s) {
p = NULL;
q = Home;
} else if (*s == '/') {
p = s;
q = Home;
} else {
if ((p = strchr( s, '/' ))) {
r = nfstrndup( s, (size_t)(p - s) );
pw = getpwnam( r );
free( r );
} else
pw = getpwnam( s );
if (!pw)
return NULL;
q = pw->pw_dir;
}
nfasprintf( &r, "%s%s", q, p ? p : "" );
return r;
} else
return nfstrdup( s );
}
/* Return value: 0 = ok, -1 = out found in arg, -2 = in found in arg but no out specified */
int
map_name( const char *arg, char **result, uint reserve, const char *in, const char *out )
{
char *p;
uint i, l, ll, num, inl, outl;
assert( arg );
l = strlen( arg );
assert( in );
inl = strlen( in );
if (!inl) {
copy:
*result = nfmalloc( reserve + l + 1 );
memcpy( *result + reserve, arg, l + 1 );
return 0;
}
assert( out );
outl = strlen( out );
if (equals( in, (int)inl, out, outl ))
goto copy;
for (num = 0, i = 0; i < l; ) {
for (ll = 0; ll < inl; ll++)
if (arg[i + ll] != in[ll])
goto fout;
num++;
i += inl;
continue;
fout:
if (outl) {
for (ll = 0; ll < outl; ll++)
if (arg[i + ll] != out[ll])
goto fnexti;
return -1;
}
fnexti:
i++;
}
if (!num)
goto copy;
if (!outl)
return -2;
*result = nfmalloc( reserve + l + num * (outl - inl) + 1 );
p = *result + reserve;
for (i = 0; i < l; ) {
for (ll = 0; ll < inl; ll++)
if (arg[i + ll] != in[ll])
goto rnexti;
memcpy( p, out, outl );
p += outl;
i += inl;
continue;
rnexti:
*p++ = arg[i++];
}
*p = 0;
return 0;
}
static int
compare_uints( const void *l, const void *r )
{
uint li = *(const uint *)l, ri = *(const uint *)r;
if (li != ri) // Can't subtract, the result might not fit into signed int.
return li > ri ? 1 : -1;
return 0;
}
void
sort_uint_array( uint_array_t array )
{
qsort( array.data, array.size, sizeof(uint), compare_uints );
}
int
find_uint_array( uint_array_t array, uint value )
{
uint bot = 0, top = array.size;
while (bot < top) {
uint i = (bot + top) / 2;
uint elt = array.data[i];
if (elt == value)
return 1;
if (elt < value)
bot = i + 1;
else
top = i;
}
return 0;
}
static struct {
uchar i, j, s[256];
} rs;
void
arc4_init( void )
{
int i, fd;
uchar j, si, dat[128];
if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
error( "Fatal: no random number source available.\n" );
exit( 3 );
}
if (read( fd, dat, 128 ) != 128) {
error( "Fatal: cannot read random number source.\n" );
exit( 3 );
}
close( fd );
for (i = 0; i < 256; i++)
rs.s[i] = (uchar)i;
for (i = j = 0; i < 256; i++) {
si = rs.s[i];
j += si + dat[i & 127];
rs.s[i] = rs.s[j];
rs.s[j] = si;
}
rs.i = rs.j = 0;
for (i = 0; i < 256; i++)
arc4_getbyte();
}
uchar
arc4_getbyte( void )
{
uchar si, sj;
rs.i++;
si = rs.s[rs.i];
rs.j += si;
sj = rs.s[rs.j];
rs.s[rs.i] = sj;
rs.s[rs.j] = si;
return rs.s[(si + sj) & 0xff];
}
static const uchar prime_deltas[] = {
0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 17, 27, 3,
1, 29, 3, 21, 7, 17, 15, 9, 43, 35, 15, 0, 0, 0, 0, 0
};
uint
bucketsForSize( uint size )
{
uint base = 4, bits = 2;
for (;;) {
uint prime = base + prime_deltas[bits];
if (prime >= size)
return prime;
base <<= 1;
bits++;
}
}
static void
list_prepend( list_head_t *head, list_head_t *to )
{
assert( !head->next );
assert( to->next );
assert( to->prev->next == to );
head->next = to;
head->prev = to->prev;
head->prev->next = head;
to->prev = head;
}
static void
list_unlink( list_head_t *head )
{
assert( head->next );
assert( head->next->prev == head);
assert( head->prev->next == head);
head->next->prev = head->prev;
head->prev->next = head->next;
head->next = head->prev = NULL;
}
static notifier_t *notifiers;
static int changed; /* Iterator may be invalid now. */
#ifdef HAVE_POLL_H
static struct pollfd *pollfds;
static uint npolls, rpolls;
#else
# ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
# endif
#endif
void
init_notifier( notifier_t *sn, int fd, void (*cb)( int, void * ), void *aux )
{
#ifdef HAVE_POLL_H
uint idx = npolls++;
if (rpolls < npolls) {
rpolls = npolls;
pollfds = nfrealloc( pollfds, npolls * sizeof(*pollfds) );
}
pollfds[idx].fd = fd;
pollfds[idx].events = 0; /* POLLERR & POLLHUP implicit */
sn->index = idx;
#else
sn->fd = fd;
sn->events = 0;
#endif
sn->cb = cb;
sn->aux = aux;
sn->next = notifiers;
notifiers = sn;
}
void
conf_notifier( notifier_t *sn, short and_events, short or_events )
{
#ifdef HAVE_POLL_H
uint idx = sn->index;
pollfds[idx].events = (pollfds[idx].events & and_events) | or_events;
#else
sn->events = (sn->events & and_events) | or_events;
#endif
}
short
notifier_config( notifier_t *sn )
{
#ifdef HAVE_POLL_H
return pollfds[sn->index].events;
#else
return sn->events;
#endif
}
void
wipe_notifier( notifier_t *sn )
{
notifier_t **snp;
#ifdef HAVE_POLL_H
uint idx;
#endif
for (snp = &notifiers; *snp != sn; snp = &(*snp)->next)
assert( *snp );
*snp = sn->next;
sn->next = NULL;
changed = 1;
#ifdef HAVE_POLL_H
idx = sn->index;
memmove( pollfds + idx, pollfds + idx + 1, (--npolls - idx) * sizeof(*pollfds) );
for (sn = notifiers; sn; sn = sn->next) {
if (sn->index > idx)
sn->index--;
}
#endif
}
static time_t
get_now( void )
{
return time( NULL );
}
static list_head_t timers = { &timers, &timers };
void
init_wakeup( wakeup_t *tmr, void (*cb)( void * ), void *aux )
{
tmr->cb = cb;
tmr->aux = aux;
tmr->links.next = tmr->links.prev = NULL;
}
void
wipe_wakeup( wakeup_t *tmr )
{
if (tmr->links.next)
list_unlink( &tmr->links );
}
void
conf_wakeup( wakeup_t *tmr, int to )
{
list_head_t *head, *succ;
if (to < 0) {
if (tmr->links.next)
list_unlink( &tmr->links );
} else {
time_t timeout = to;
if (!to) {
/* We always prepend null timers, to cluster related events. */
succ = timers.next;
} else {
timeout += get_now();
/* We start at the end in the expectation that the newest timer is likely to fire last
* (which will be true only if all timeouts are equal, but it's an as good guess as any). */
for (succ = &timers; (head = succ->prev) != &timers; succ = head) {
if (head != &tmr->links && timeout > ((wakeup_t *)head)->timeout)
break;
}
assert( head != &tmr->links );
}
tmr->timeout = timeout;
if (succ != &tmr->links) {
if (tmr->links.next)
list_unlink( &tmr->links );
list_prepend( &tmr->links, succ );
}
}
}
static void
event_wait( void )
{
list_head_t *head;
notifier_t *sn;
int m;
#ifdef HAVE_POLL_H
int timeout = -1;
if ((head = timers.next) != &timers) {
wakeup_t *tmr = (wakeup_t *)head;
time_t delta = tmr->timeout;
if (!delta || (delta -= get_now()) <= 0) {
list_unlink( head );
tmr->cb( tmr->aux );
return;
}
timeout = (int)delta * 1000;
}
switch (poll( pollfds, npolls, timeout )) {
case 0:
return;
case -1:
perror( "poll() failed in event loop" );
abort();
default:
break;
}
for (sn = notifiers; sn; sn = sn->next) {
uint n = sn->index;
if ((m = pollfds[n].revents)) {
assert( !(m & POLLNVAL) );
sn->cb( m | shifted_bit( m, POLLHUP, POLLIN ), sn->aux );
if (changed) {
changed = 0;
break;
}
}
}
#else
struct timeval *timeout = 0;
struct timeval to_tv;
fd_set rfds, wfds, efds;
int fd;
if ((head = timers.next) != &timers) {
wakeup_t *tmr = (wakeup_t *)head;
time_t delta = tmr->timeout;
if (!delta || (delta -= get_now()) <= 0) {
list_unlink( head );
tmr->cb( tmr->aux );
return;
}
to_tv.tv_sec = delta;
to_tv.tv_usec = 0;
timeout = &to_tv;
}
FD_ZERO( &rfds );
FD_ZERO( &wfds );
FD_ZERO( &efds );
m = -1;
for (sn = notifiers; sn; sn = sn->next) {
fd = sn->fd;
if (sn->events & POLLIN)
FD_SET( fd, &rfds );
if (sn->events & POLLOUT)
FD_SET( fd, &wfds );
FD_SET( fd, &efds );
if (fd > m)
m = fd;
}
switch (select( m + 1, &rfds, &wfds, &efds, timeout )) {
case 0:
return;
case -1:
perror( "select() failed in event loop" );
abort();
default:
break;
}
for (sn = notifiers; sn; sn = sn->next) {
fd = sn->fd;
m = 0;
if (FD_ISSET( fd, &rfds ))
m |= POLLIN;
if (FD_ISSET( fd, &wfds ))
m |= POLLOUT;
if (FD_ISSET( fd, &efds ))
m |= POLLERR;
if (m) {
sn->cb( m, sn->aux );
if (changed) {
changed = 0;
break;
}
}
}
#endif
}
void
main_loop( void )
{
while (notifiers || timers.next != &timers)
event_wait();
}

203
sync.c Normal file
View file

@ -0,0 +1,203 @@
/* $Id$
*
* isync - IMAP4 to maildir mailbox synchronizer
* Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "isync.h"
static unsigned int MaildirCount = 0;
message_t *
find_msg (message_t * list, unsigned int uid)
{
for (; list; list = list->next)
if (list->uid == uid)
return list;
return 0;
}
int
sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags, unsigned int max_size)
{
message_t *cur;
message_t *tmp;
char path[_POSIX_PATH_MAX];
char newpath[_POSIX_PATH_MAX];
char suffix[_POSIX_PATH_MAX];
char *p;
int fd;
int ret;
int fetched = 0;
if (mbox->uidvalidity > 0)
{
if (mbox->uidvalidity != imap->uidvalidity)
{
/* if the UIDVALIDITY value has changed, it means all our
* local UIDs are invalid, so we can't sync.
*/
puts ("Error, UIDVALIDITY changed on server (fatal)");
return -1;
}
}
else if (maildir_set_uidvalidity (mbox, imap->uidvalidity))
{
puts ("Error, unable to store UIDVALIDITY");
return -1;
}
if (mbox->maxuid == 0 || imap->maxuid > mbox->maxuid)
{
mbox->maxuid = imap->maxuid;
mbox->maxuidchanged = 1;
}
/* if we are --fast mode, the mailbox wont have been loaded, so
* this next step is skipped.
*/
for (cur = mbox->msgs; cur; cur = cur->next)
{
tmp = find_msg (imap->msgs, cur->uid);
if (!tmp)
{
printf ("Warning, uid %d doesn't exist on server\n", cur->uid);
if (flags & SYNC_DELETE)
{
cur->flags |= D_DELETED;
cur->dead = 1;
mbox->deleted++;
}
continue;
}
tmp->processed = 1;
/* check if local flags are different from server flags.
* ignore \Recent and \Draft
*/
if (cur->flags != (tmp->flags & ~(D_RECENT | D_DRAFT)))
{
/* set local flags that don't exist on the server */
if (!(tmp->flags & D_DELETED) && (cur->flags & D_DELETED))
imap->deleted++;
imap_set_flags (imap, cur->uid, cur->flags & ~tmp->flags);
/* update local flags */
if((cur->flags & D_DELETED) == 0 && (tmp->flags & D_DELETED))
mbox->deleted++;
cur->flags |= (tmp->flags & ~(D_RECENT | D_DRAFT));
cur->changed = 1;
mbox->changed = 1;
}
}
fputs ("Fetching new messages", stdout);
fflush (stdout);
for (cur = imap->msgs; cur; cur = cur->next)
{
if (!cur->processed)
{
/* new message on server */
if ((flags & SYNC_EXPUNGE) && (cur->flags & D_DELETED))
{
/* this message has been marked for deletion and
* we are currently expunging a mailbox. don't
* bother downloading this message
*/
continue;
}
if (max_size && cur->size > max_size)
{
printf ("Warning, message skipped because it is too big (%u)\n",
cur->size);
continue;
}
/* construct the flags part of the file name. */
*suffix = 0;
if (cur->flags)
{
snprintf (suffix, sizeof (suffix), ":2,%s%s%s%s",
(cur->flags & D_FLAGGED) ? "F" : "",
(cur->flags & D_ANSWERED) ? "R" : "",
(cur->flags & D_SEEN) ? "S" : "",
(cur->flags & D_DELETED) ? "T" : "");
}
for (;;)
{
/* create new file */
snprintf (path, sizeof (path), "%s/tmp/%s.%ld_%d.%d.UID%d%s",
mbox->path, Hostname, time (0), MaildirCount++,
getpid (), cur->uid, suffix);
if ((fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600)) > 0)
break;
if (errno != EEXIST)
{
perror ("open");
break;
}
sleep (2);
}
if (fd < 0)
continue;
/* give some visual feedback that something is happening */
fputs (".", stdout);
fflush (stdout);
fetched++;
ret = imap_fetch_message (imap, cur->uid, fd);
if (close (fd))
perror ("close");
else if (!ret)
{
p = strrchr (path, '/');
snprintf (newpath, sizeof (newpath), "%s/%s%s", mbox->path,
(cur->flags & D_SEEN) ? "cur" : "new", p);
/* its ok if this fails, the next time we sync the message
* will get pulled down
*/
if (link (path, newpath))
perror ("link");
}
/* always remove the temp file */
unlink (path);
}
}
printf (" %d messages\n", fetched);
return 0;
}