commit 130664b622b7169adf35ae4c01177f8b31505321
parent 8d1b26aebe451447ed2ec6fbfb370cd7017db00b
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Sat, 27 Mar 2004 16:07:20 +0000
The Big Rewrite. too many change to list them all.
as opposed to earlier threats, BerkDB was not entirely dropped; i
suppose the isync 0.7 -> 0.8 change had a reason, so i added an
alternative UID storage scheme.
note that BDB 4.0 is not sufficient, as the db->open function changed in
an incompatible way ...
i updated the debian packaging except for a changelog entry.
note that i removed the upgrade blurb, as upstream now has a smooth
upgrade path down to at least isync 0.4.
Diffstat:
49 files changed, 8169 insertions(+), 4716 deletions(-)
diff --git a/.cvsignore b/.cvsignore
@@ -7,12 +7,16 @@ build-stamp
config.h
config.h.in
config.cache
+config.guess
config.log
config.status
+config.sub
configure
configure.lineno
configure-stamp
isync.spec
isync-*.tar.gz
patch-stamp
+stamp-h
+stamp-h.in
stamp-h1
diff --git a/Makefile.am b/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = src
-man_MANS = isync.1
-EXTRA_DIST = debian isyncrc.sample isync.spec $(man_MANS)
+bin_SCRIPTS = get-cert
+EXTRA_DIST = debian isync.spec $(bin_SCRIPTS)
log:
@perl -p -e "s/^(\\S+)\\s+(\\S.+\\S)\\s+(\\S+)\\s*\$$/\$$1:'\$$2 <\$$3>'\\n/" < ../CVSROOT/accounts > .usermap
@@ -16,6 +16,9 @@ deb-clean:
distdir distclean: deb-clean
+dist-hook:
+ find $(distdir)/debian \( -name CVS -o -name .cvsignore -o -name .#\*# -o -type l \) -print0 | xargs -0r rm -rf
+
rpm:
make dist
cp $(PACKAGE)-$(VERSION).tar.gz /usr/src/rpm/SOURCES
diff --git a/NEWS b/NEWS
@@ -1,3 +1,27 @@
+[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
diff --git a/README b/README
@@ -4,52 +4,69 @@
| \__ \ |_| | | | | (__
|_|___/\__, |_| |_|\___|
|___/
-isync - IMAP4 to maildir mailbox synchronization program
+isync/mbsync - free (GPL) mailbox synchronization program
http://isync.sf.net/
-Author: Michael Elkins <me@mutt.org>
-Current maintainer: Oswald Buddenhagen <ossi@users.sf.net>
+See AUTHORS for contact information.
-``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.
+``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.
-* Features:
+Synchronization is based on unique message identifiers (UIDs), so no
+identification conflicts can occur (as opposed to some other mail
+synchronizers).
+Synchronization state is kept in one local text file per mailbox pair;
+multiple replicas of a mailbox can be maintained.
- * 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
+isync is the project name, while mbsync is the current executable name; this
+change was necessary because of massive changes in the user interface. An
+isync executable still exists; it is a compatibility wrapper around mbsync.
+
+* 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 (RFC2595)
+ * Supports CRAM-MD5 (RFC2195) for authentication
+ * Supports NAMESPACE (RFC2342) for simplified configuration
+ * Pipelining for maximum speed (currently only partially implemented)
* Compatibility
- ``isync'' has been tested with the following IMAP servers:
+ isync should work fairly well with any IMAP4 compliant server;
+ particularily efficient with those that support the UIDPLUS and LITERAL+
+ extensions.
- * Microsoft Exchange 2000 IMAP4rev1 server version 6.0.4417.0
- * Courier-IMAP 1.2.3
- * WU-IMAP 2000
- * Domino IMAP4 Server Release 5.0.8
+ Courier 1.4.3 is known to be buggy, version 1.7.3 works fine.
-* Platforms
+ c-client (UW-IMAP, Pine) is mostly fine, but tends to change UIDVALIDITY
+ pretty often when used with unix/mbox mailboxes, making isync refuse
+ synchronization.
+ The "cure" is to simply copy the new UIDVALIDITY from the affected
+ mailbox to mbsync's state file. This is a Bad Hack (TM), but it works -
+ use at your own risk (if the UIDVALIDITY change was genuine, this will
+ delete all messages in the affected mailbox - not that this ever
+ happened to me).
- ``isync'' has successfully been compiled on:
+* Platforms
- * Linux
- * Solaris 2.7
- * OpenBSD 2.8
- * FreeBSD 4.3
+ At some point, ``isync'' has successfully run on:
+ Linux, Solaris 2.7, OpenBSD 2.8, FreeBSD 4.3, Cygwin
* Requirements
- OpenSSL for TLS/SSL support (optional)
+ OpenSSL for TLS/SSL support (optional)
-* INSTALLING
+* Installation
- ./configure
- make install
+ ./configure
+ make install
-* HELP
+* Help
- Please see the man page for complete documentation.
+ Please see the man page for complete documentation.
diff --git a/TODO b/TODO
@@ -1,39 +1,51 @@
-change of UIDVALIDITY shouldn't be considered fatal for the imap connection.
-maybe the error handling needs to be cleaned up in general.
+make SSL certificate validation more automatic.
-don't require maildir_trash. currently MaxMessages gets into the way of
-simply removing it; that is fixable with some shuffling, though.
+add asynchronous operation to remote mailbox drivers. this is actually
+what prevents us from simply using c-client and thus becoming mailsync.
-refactor mailbox support. create proper mailbox drivers; handle imap and
-maildir (and anything else) symmetrically; decouple UID->message mapping
-from sync database - should use the same UID storing schemes as c-client
-(pine, uw-imap) does, at least optionally, i think.
+handle custom flags (keywords).
-add asynchrounous operation to remote mailbox drivers. this is actually
-what prevents us from simply using c-client for the previous point and
-thus simply becoming mailsync.
+fix maildir_{open_store,list} to handle partial names (last char not slash).
-store message flags in sync database, so _un_setting them will be properly
-synced as well.
+add a way to automatically create and sync subfolders.
-handle custom imap flags. currently, isync just fails horribly if it
-encounters some.
+could store TUID even when UIDPLUS is supported. would avoid duplicated
+messages after abort before new UID arrives.
-add options for fine-grained control of syncing operations (--new, --delete &
---flags) and direction (--push & --pull).
+decouple TUID search from append. that's a prerequisite for usable
+MULTIAPPEND, and is generally good for async. should be way faster, too,
+as it saves repeated mailbox rescans with single-file formats.
-add support for syncing with other: and shared: via NAMESPACE
+use MULTIAPPEND and FETCH with multiple messages.
-isync ignores asynchronous notifications (untagged responses), so mail
-arriving during a fetch will not be fetched in the current run any more.
+create dummies describing MIME structure of messages bigger than MaxSize.
+flagging the dummy would fetch the real message. possibly remove --renew.
-add a way to automatically create and sync IMAP subfolders.
+don't SELECT boxes unless really needed; in particular not for appending,
+and in write-only mode not before changes are made.
-make the command line take precedence over the config file.
+possibly request message attributes on a per-message basis from the drivers.
+considerations:
+- record non-existing UID ranges in the sync database, so IMAP FETCHes needn't
+ to exclude anyway non-existing messages explicitly.
+- when detect unborn pairs and orphaned messages being gone? implied by expunge:
+ with trashing, by local driver, or of messages we deleted in this run. the
+ remaining cases could be handled by automatic periodical cleanup passes, an
+ explicit --cleanup action, or be implied by one of the other actions.
+- the benefit of this is questionable, as fine-grained requests will result
+ in sending huge amounts of data, and upstream is often way slower than
+ downstream.
-possibly timestamp mails with remote arrival date.
+maildir: possibly timestamp mails with remote arrival date.
-possibly recover from UIDVALIDITY change by resyncing according to message
-IDs - this is a pretty common condition with uw-imap.
+maybe throw out the ctx->recent stuff - it's used only for one info message.
possibly use ^[[1m to highlight error messages.
+
+consider alternative trash implementation: trash only messages we delete,
+and trash before marking them deleted in the mailbox. downside: all other
+programs have to do the same. and what if the deleted flag is unset?
+
+items out of scope of purely UID based approach:
+- detect message moves between folders
+- recovering from UIDVALIDITY change (uw-imap does this a lot)
diff --git a/configure.in b/configure.in
@@ -1,31 +1,32 @@
AC_INIT(src/isync.h)
AM_CONFIG_HEADER(config.h)
-AM_INIT_AUTOMAKE(isync, 0.9.2)
+AM_INIT_AUTOMAKE(isync, 1.0.0alpha)
AM_MAINTAINER_MODE
AM_PROG_CC_STDC
if test "$GCC" = yes; then
- CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wmissing-prototypes"
+ CFLAGS="$CFLAGS -pipe -W -Wall -Wshadow -Wstrict-prototypes"
fi
-AC_CHECK_FUNCS(getopt_long)
+AC_CHECK_FUNCS(vasprintf)
-AC_CHECK_LIB(socket, socket)
-AC_CHECK_LIB(nsl, inet_ntoa)
+AC_CHECK_LIB(socket, socket, [SOCK_LIBS="-lsocket"])
+AC_CHECK_LIB(nsl, inet_ntoa, [SOCK_LIBS="$SOCK_LIBS -lnsl"])
+AC_SUBST(SOCK_LIBS)
ssl=false
AC_ARG_WITH(ssl,
- [ --with-ssl=DIR yes/no/OpenSSL installation root [detect]],
+ AS_HELP_STRING([--with-ssl=DIR], [yes/no/OpenSSL installation root [detect]]),
[ob_cv_with_ssl=$withval])
if test "x$ob_cv_with_ssl" != xno; then
if test -d "$ob_cv_with_ssl/lib"; then
- CPFLAGS="$CPPFLAGS -I$ob_cv_with_ssl/include"
- LDFLAGS="$LDFLAGS -L$ob_cv_with_ssl/lib"
+ CPFLAGS="$CPPFLAGS -I$ob_cv_with_ssl/include"
+ LDFLAGS="$LDFLAGS -L$ob_cv_with_ssl/lib"
fi
AC_CHECK_LIB(crypto, ERR_error_string, [cryptolib=" -lcrypto"])
AC_CHECK_LIB(ssl, SSL_library_init, [
- LIBS="-lssl$cryptolib $LIBS"
+ SSL_LIBS="-lssl$cryptolib"
AC_DEFINE(HAVE_LIBSSL, 1, [Define if you want SSL support])
ssl=true
],[
@@ -34,19 +35,29 @@ if test "x$ob_cv_with_ssl" != xno; then
fi
])
fi
+AC_SUBST(SSL_LIBS)
-AC_CACHE_CHECK(for db_create in -ldb, ac_cv_db_db_create,
- [ac_cv_db_dbcreate=no
- AC_TRY_LINK([#include <db.h>],
- [db_create();],[ac_cv_db_db_create=yes])])
-if test $ac_cv_db_db_create=yes; then
- LIBS="$LIBS -ldb"
-else
- AC_MSG_ERROR([Berkley DB not found.
-You must install libdb including the respective development files/headers.])
+AC_CACHE_CHECK([for Berkley DB 4.2], ac_cv_berkdb4,
+ [ac_cv_berkdb4=no
+ AC_TRY_LINK([#include <db.h>],
+ [DB *db;
+ db->truncate(db, 0, 0, 0);
+ db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)],
+ [ac_cv_berkdb4=yes])])
+if test "x$ac_cv_berkdb4" = xno; then
+ AC_MSG_ERROR([Berkley DB 4.2 not found.
+You must install libdb4.2 including the respective development files/headers.])
+fi
+
+AC_ARG_ENABLE(compat,
+ AS_HELP_STRING([--disable-compat], [don't include isync compatibility wrapper [no]]),
+ [ob_cv_enable_compat=$enableval])
+if test "x$ob_cv_enable_compat" != xno; then
+ AC_CHECK_FUNCS(getopt_long)
fi
+AM_CONDITIONAL(with_compat, test "x$ob_cv_enable_compat" != xno)
-AC_OUTPUT(Makefile src/Makefile isync.spec)
+AC_OUTPUT(Makefile src/Makefile src/compat/Makefile isync.spec)
if $ssl; then
AC_MSG_RESULT([
diff --git a/debian/NEWS b/debian/NEWS
@@ -1,16 +0,0 @@
-isync (0.8-1) unstable; urgency=low
-
- IMPORTANT upgrade note:
-
- This version includes a change to the way the UID for each message is
- stored in the local mailbox. 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.
-
- A suggested upgrade procedure is to use isync version 0.7 to synchronize
- any local changes in isync-managed mailboxes with your IMAP server, and
- then remove the contents of the local mailboxes, before upgrading to this
- version. Then run isync again to pull down the mail again. You must do
- this manually, the Debian package will not do this for you.
-
- -- Joey Hess <joeyh@debian.org> Tue, 29 Oct 2002 13:50:40 -0500
diff --git a/debian/README.Debian b/debian/README.Debian
@@ -1,22 +0,0 @@
-A note from isync's web site:
-
-To use this command effectively, you need a mail client that sets the T
-(trashed) flag when it deletes a message from a maildir mailbox, instead of
-just removing it altogether. Currently, only Mutt 1.3.27 supports this. Without
-such a client, isync will refetch the locally deleted messages from the server
-since they will never get expunged. Be sure to put
-
-set maildir_trash
-
-in your ~/.muttrc when using Mutt.
-
-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>!isync -e maildir\n"'
-
-where maildir is the name of the local mailbox (or its alias). 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).
diff --git a/debian/config b/debian/config
@@ -1,8 +0,0 @@
-#!/bin/sh
-set -e
-. /usr/share/debconf/confmodule
-if [ "$1" = "configure" -a ! -z "$2" ] && \
- dpkg --compare-versions "$2" lt 0.8; then
- db_input critical isync/upgrade_0.8 || true
- db_go || true
-fi
diff --git a/debian/control b/debian/control
@@ -4,21 +4,26 @@ Priority: optional
Maintainer: Nicolas Boullis <nboullis@debian.org>
Uploaders: Nicolas Boullis <nboullis@debian.org>, Theodore Y. Ts'o <tytso@mit.edu>
Standards-Version: 3.6.1
-Build-Depends: libssl-dev, debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb4.0-dev, dpatch
+Build-Depends: libssl-dev, debhelper (>= 4.1.16), dpkg-dev (>= 1.9.0), libdb4.2-dev, dpatch
Package: isync
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Suggests: mutt
-Description: Synchronize a local maildir with a remote IMAP4 mailbox
- A command line application which synchronizes a local maildir-style
- mailbox with a remote IMAP4 mailbox, suitable for use in disconnected
- mode. Multiple copies of the remote IMAP4 mailbox can be maintained,
- and all flags and messages are synchronized.
+Description: Synchronize Maildir and IMAP4 mailboxes
+ 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.
+ It is useful for working in disconnected mode, such as on a laptop or with a
+ non-permanent internet collection (dIMAP).
.
Features:
- * 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
+ * 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 (RFC2595)
+ * Supports CRAM-MD5 (RFC2195) for authentication
+ * Supports NAMESPACE (RFC2342) for simplified configuration
+ * Pipelining for maximum speed (currently only partially implemented)
diff --git a/debian/patches/00list b/debian/patches/00list
@@ -1 +0,0 @@
-20-cleanup
diff --git a/debian/patches/20-cleanup.dpatch b/debian/patches/20-cleanup.dpatch
@@ -1,146 +0,0 @@
-#! /bin/sh -e
-## 20-cleanup.dpatch by Theodore Ts'o <tytso@mit.edu>
-##
-## DP: Make sure the database store and the imap database is closed
-## DP: if isync is aborted.
-
-[ -f debian/patches/00patch-opts ] && . debian/patches/00patch-opts
-patch_opts="${patch_opts:--f --no-backup-if-mismatch}"
-
-if [ $# -ne 1 ]; then
- echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
- exit 1
-fi
-case "$1" in
- -patch) patch $patch_opts -p1 < $0;;
- -unpatch) patch $patch_opts -p1 -R < $0;;
- *)
- echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
- exit 1;;
-esac
-
-exit 0
-@DPATCH@
-
-Problem description:
-
->> If isync dies in the middle of synchronization, or the network
->> connection breaks while it is synchronizing a mailbox, new messages
->> which are downloaded from the IMAP server do not have their UID saved
->> to the maildir directory. This is REALLY, REALLY BAD, because it
->> means that on the next isync, the downloaded messages are re-uploaded
->> to the imap server, resulting in duplicate messages in the IMAP store.
->>
->> This takes means the network download takes longer, and if the network
->> connection is unrealible, it means it's more likely the the IMAP
->> connection will break, resulting in more duplicate messages being
->> uploaded to the servers. (The first time, 14 messages were uploaded
->> to the server. The second time I re-isynced, 65 messages were
->> uploaded to the server, resulting in some 79 duplicate messages that I
->> had to manually weed out. Grr, grr, grr, grr.)
-
-Problem solution:
-
-Actually, I managed to figure out the solution a while ago, and got
-hung up trying to figure out the right way to submit the patches back
-to upstream (there's no mailing list that I can find; so do you just
-communicate directly with the developers). Anyway, I got busy and I
-never had a chance to send the patches a while ago.
-
-This patch is not the best, but it does seem to work. Perhaps a
-better approach would be to use the more advanced API's available with
-berkdb, so you can actually force a sync to the db/dbm files after
-the mail message has been downloaded. Fundamentally, that's the
-problem. The id has been added to the db file, but the changes don't
-get forced out to disk, so in the case of an abnormal termination of
-the program, the id's never get written to disk.
-
-The patch enclosed below solves the problem by establishing a signal
-handler, which cleans up in the case of the user typing ^C (after the
-network connection has gone away, say because your GSM phone's GPRS
-connection has gotten flakey, for example). However, it doesn't solve
-the problem in case of an abrupt system crash. In order to address
-that problem, the overall program interfaces would have to be changed
-to use the newer berkdb interfaces directly, but that would mean
-dropping compatibility with the ancient dbm interface. Personally, I
-don't think that to be any great loss, but the changes would be much
-more invasive, and would require agreement with the upstream
-maintainer that this is the right way to go.
-
-Also, for bonus points, perhaps there should be an inactivity timer so
-that isync can automatically figure out when the network connection
-has gone away, and can do a clean shutdown and exit automatically,
-instead of requiring the user to type ^C.
-
- - Ted
-
-
-Patched files: src/main.c
-===================================================================
-RCS file: isync-0.9.2/src/RCS/main.c,v
-retrieving revision 1.3
-diff -u -r1.3 isync-0.9.2/src/main.c
---- isync-0.9.2/src/main.c 2004/01/10 01:13:38 1.3
-+++ isync-0.9.2/src/main.c 2004/01/10 01:14:34
-@@ -35,6 +35,7 @@
- #include <string.h>
- #include <ctype.h>
- #include <dirent.h>
-+#include <signal.h>
-
- int Quiet;
-
-@@ -92,6 +93,22 @@
- unsigned int Tag = 0;
- char Hostname[256];
- int Verbose = 0;
-+mailbox_t *CleanupMail = 0;
-+imap_t *CleanupImap = 0;
-+int CleanupValid = 0;
-+
-+static void signal_exit(int sig)
-+{
-+ info("Abort received\n");
-+ if (CleanupValid) {
-+ info("Aborting, cleaning up\n");
-+ if (CleanupMail)
-+ maildir_close (CleanupMail);
-+ if (CleanupImap)
-+ imap_close (CleanupImap);
-+ }
-+ exit (1);
-+}
-
- static void
- version (void)
-@@ -319,6 +336,10 @@
- usage (1);
- }
-
-+ signal(SIGTERM, signal_exit);
-+ signal(SIGHUP, signal_exit);
-+ signal(SIGINT, signal_exit);
-+
- gethostname (Hostname, sizeof (Hostname));
-
- load_config (config, &o2o);
-@@ -410,6 +431,9 @@
- ret = 1;
- break;
- }
-+ CleanupValid = 1;
-+ CleanupMail = mail;
-+ CleanupImap = imap;
-
- info ("Synchronizing\n");
- i = (delete || box->delete) ? SYNC_DELETE : 0;
-@@ -460,6 +484,8 @@
-
- } while (0);
-
-+ CleanupValid = 0;
-+
- /* we never sync the same mailbox twice, so close it now */
- if (mail)
- maildir_close (mail);
-
diff --git a/debian/patches/30-async-imap.dpatch b/debian/patches/30-async-imap.dpatch
@@ -1,458 +0,0 @@
-#!/bin/sh -e
-## 30-aysnc-imap.dpatch by Theodore Y. Ts'o <tytso@mit.edu>
-##
-## DP: Add the beginnings of asynchronous IMAP support. So far, we only
-## DP: support asynchronous flag setting, since that's the easist.
-## DP: Eventually we need to support asynchronous message fetches and
-## DP: uploads.
-
-if [ $# -ne 1 ]; then
- echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
- exit 1
-fi
-
-[ -f debian/patches/00patch-opts ] && . debian/patches/00patch-opts
-patch_opts="${patch_opts:--f --no-backup-if-mismatch}"
-
-case "$1" in
- -patch) patch $patch_opts -p1 < $0;;
- -unpatch) patch $patch_opts -p1 -R < $0;;
- *)
- echo >&2 "`basename $0`: script expects -patch|-unpatch as argument"
- exit 1;;
-esac
-
-exit 0
-
-@DPATCH@
-diff -urNad /usr/projects/isync/SF-cvs/isync/src/imap.c isync/src/imap.c
---- /usr/projects/isync/SF-cvs/isync/src/imap.c 2004-01-15 14:24:40.000000000 -0500
-+++ isync/src/imap.c 2004-01-15 20:36:15.000000000 -0500
-@@ -3,6 +3,7 @@
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 2002-2003 Oswald Buddenhagen <ossi@users.sf.net>
-+ * Copyright (C) 2004 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
-@@ -35,13 +36,33 @@
- #include <string.h>
- #include <ctype.h>
- #include <sys/socket.h>
-+#include <sys/ioctl.h>
- #include <netinet/in.h>
-+#include <netinet/tcp.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #if HAVE_LIBSSL
- # include <openssl/err.h>
- #endif
-
-+struct imap_cmd {
-+ unsigned int tag;
-+ char *cmd;
-+ int flags;
-+ int response;
-+ struct imap_cmd *next;
-+ int (*complete_fn) (imap_t *imap, struct imap_cmd * cmd);
-+};
-+
-+#define IMAP_FLAG_DONE 0x0001
-+
-+static struct imap_cmd *in_progress = NULL;
-+static int num_in_progress = 0;
-+int max_in_progress_high = 50;
-+int max_in_progress_low = 10;
-+
-+static struct imap_cmd *get_cmd_result(imap_t *imap);
-+
- const char *Flags[] = {
- "\\Seen",
- "\\Answered",
-@@ -199,6 +220,22 @@
- return write (sock->fd, buf, len);
- }
-
-+static int
-+socket_pending(Socket_t *sock)
-+{
-+ int num = -1;
-+
-+ if (ioctl(sock->fd, FIONREAD, &num) < 0)
-+ return -1;
-+ if (num > 0)
-+ return num;
-+#if HAVE_LIBSSL
-+ if (sock->use_ssl)
-+ return SSL_pending (sock->ssl);
-+#endif
-+ return 0;
-+}
-+
- static void
- socket_perror (const char *func, Socket_t *sock, int ret)
- {
-@@ -301,16 +338,20 @@
- }
-
- static int
--parse_fetch (imap_t * imap, list_t * list)
-+parse_fetch (imap_t * imap, char *cmd)
- {
-- list_t *tmp;
-+ list_t *tmp, *list;
- unsigned int uid = 0;
- unsigned int mask = 0;
- unsigned int size = 0;
- message_t *cur;
-
-- if (!is_list (list))
-+ list = parse_list (cmd, 0);
-+
-+ if (!is_list (list)) {
-+ free_list(list);
- return -1;
-+ }
-
- for (tmp = list->child; tmp; tmp = tmp->next)
- {
-@@ -325,6 +366,7 @@
- if (uid < imap->minuid)
- {
- /* already saw this message */
-+ free_list(list);
- return 0;
- }
- else if (uid > imap->maxuid)
-@@ -387,6 +429,7 @@
- cur->flags = mask;
- cur->size = size;
-
-+ free_list(list);
- return 0;
- }
-
-@@ -415,39 +458,121 @@
- }
- }
-
--static int
--imap_exec (imap_t * imap, const char *fmt, ...)
-+static void print_imap_command(const char *cmd, FILE *f)
-+{
-+ if (strncmp(cmd, "LOGIN", 5))
-+ fputs(cmd, f);
-+ else
-+ fputs("LOGIN USERNAME PASSWORD", f);
-+}
-+
-+static struct imap_cmd *issue_imap_cmd(imap_t *imap,
-+ const char *fmt, ...)
- {
- va_list ap;
-- char tmp[256];
-- char buf[256];
-- char *cmd;
-- char *arg;
-- char *arg1;
-- config_t *box;
-+ char tmp[1024];
-+ char buf[1024];
-+ struct imap_cmd *cmd;
- int n;
-
-+ cmd = malloc(sizeof(struct imap_cmd));
-+ if (!cmd)
-+ return NULL;
-+
-+ cmd->tag = ++Tag;
-+ cmd->flags = 0;
-+ cmd->response = 0;
-+ cmd->complete_fn = 0;
-+
- va_start (ap, fmt);
- vsnprintf (tmp, sizeof (tmp), fmt, ap);
- va_end (ap);
-
-- snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp);
-+ cmd->cmd = malloc(strlen(tmp)+1);
-+ if (cmd->cmd)
-+ strcpy(cmd->cmd, tmp);
-+
-+ snprintf (buf, sizeof (buf), "%d %s\r\n", cmd->tag, tmp);
- if (Verbose) {
-- printf (">>> %s", buf);
-+ if (num_in_progress)
-+ printf("(%d in progress) ", num_in_progress);
-+ printf(">>> %d ", cmd->tag);
-+ print_imap_command(tmp, stdout);
-+ fputc('\n', stdout);
- fflush (stdout);
- }
- n = socket_write (imap->sock, buf, strlen (buf));
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
-- return -1;
-+ free(cmd);
-+ return NULL;
- }
-+ cmd->next = in_progress;
-+ in_progress = cmd;
-+ num_in_progress++;
-+ if ((num_in_progress > max_in_progress_high) ||
-+ socket_pending(imap->sock)) {
-+ while ((num_in_progress > max_in_progress_low) ||
-+ socket_pending(imap->sock)) {
-+ if (Verbose && socket_pending(imap->sock))
-+ printf("(Socket input pending)\n");
-+ get_cmd_result(imap);
-+ }
-+ }
-+ return cmd;
-+}
-+
-+static struct imap_cmd *find_imap_cmd(unsigned int tag)
-+{
-+ struct imap_cmd *cmd, *prev;
-+
-+ for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) {
-+ if (tag == cmd->tag) {
-+ return cmd;
-+ }
-+ prev = cmd;
-+ }
-+ return NULL;
-+}
-+
-+static void dequeue_imap_cmd(unsigned int tag)
-+{
-+ struct imap_cmd *cmd, *prev;
-+
-+ for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) {
-+ if (tag != cmd->tag) {
-+ prev = cmd;
-+ continue;
-+ }
-+ if (prev)
-+ prev->next = cmd->next;
-+ else
-+ in_progress = cmd->next;
-+ cmd->next = 0;
-+ if (cmd->cmd)
-+ free(cmd->cmd);
-+ cmd->cmd = 0;
-+ free(cmd);
-+ break;
-+ }
-+}
-+
-+static struct imap_cmd *get_cmd_result(imap_t *imap)
-+{
-+ char *cmd;
-+ char *arg;
-+ char *arg1;
-+ config_t *box;
-+ int n;
-+ unsigned int tag;
-+ struct imap_cmd *cmdp;
-
- for (;;)
- {
- next:
- if (buffer_gets (imap->buf, &cmd))
-- return -1;
-+ return NULL;
-
- arg = next_arg (&cmd);
- if (*arg == '*')
-@@ -456,7 +581,7 @@
- if (!arg)
- {
- fprintf (stderr, "IMAP error: unable to parse untagged response\n");
-- return -1;
-+ return NULL;
- }
-
- if (!strcmp ("NAMESPACE", arg))
-@@ -528,23 +653,14 @@
- 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);
-+ if (parse_fetch (imap, cmd))
-+ return NULL;
- }
- }
- else
- {
- fprintf (stderr, "IMAP error: unable to parse untagged response\n");
-- return -1;
-+ return NULL;
- }
- }
- #if HAVE_LIBSSL
-@@ -555,7 +671,7 @@
- if (!imap->cram)
- {
- fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n");
-- return -1;
-+ return NULL;
- }
- resp = cram (cmd, imap->box->user, imap->box->pass);
-
-@@ -568,34 +684,94 @@
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
-- return -1;
-+ return NULL;
- }
- n = socket_write (imap->sock, "\r\n", 2);
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
-- return -1;
-+ return NULL;
- }
- imap->cram = 0;
- }
- #endif
-- else if ((size_t) atol (arg) != Tag)
-- {
-- fprintf (stderr, "IMAP error: wrong tag\n");
-- return -1;
-- }
-- else
-- {
-- arg = next_arg (&cmd);
-- parse_response_code (imap, cmd);
-- if (!strcmp ("OK", arg))
-- return 0;
-- return -1;
-+ else {
-+ tag = (unsigned int) atol (arg);
-+ cmdp = find_imap_cmd(tag);
-+ if (!cmdp) {
-+ fprintf(stderr, "IMAP error: sent unknown tag: %u\n",
-+ tag);
-+ return NULL;
-+ }
-+ arg = next_arg (&cmd);
-+ if (strncmp("OK", arg, 2)) {
-+ if (cmdp->cmd) {
-+ fputc('\'', stderr);
-+ print_imap_command(cmdp->cmd, stderr);
-+ fputc('\'', stderr);
-+ } else
-+ fprintf(stderr, "tag %u", tag);
-+ fprintf(stderr, " returned an error (%s): %s\n",
-+ arg, cmd ? cmd : "");
-+ cmdp->response = -1;
-+ }
-+ parse_response_code (imap, cmd);
-+ num_in_progress--;
-+ cmdp->flags |= IMAP_FLAG_DONE;
-+ if (Verbose)
-+ printf("Tag %u completed with response %d\n",
-+ cmdp->tag, cmdp->response);
-+ return cmdp;
- }
- }
- /* not reached */
- }
-
-+static void flush_imap_cmds(imap_t *imap)
-+{
-+ struct imap_cmd *cmdp;
-+
-+ while (num_in_progress) {
-+ if (in_progress && in_progress->flags & IMAP_FLAG_DONE) {
-+ dequeue_imap_cmd(in_progress->tag);
-+ continue;
-+ }
-+ cmdp = get_cmd_result(imap);
-+ if (!cmdp)
-+ printf("Error trying to flush pending imap cmds\n");
-+ }
-+}
-+
-+static int
-+imap_exec (imap_t * imap, const char *fmt, ...)
-+{
-+ va_list ap;
-+ char tmp[1024];
-+ struct imap_cmd *cmdp, *waitp;
-+ int result;
-+
-+ va_start (ap, fmt);
-+ vsnprintf (tmp, sizeof (tmp), fmt, ap);
-+ va_end (ap);
-+
-+ cmdp = issue_imap_cmd(imap, "%s", tmp);
-+ if (!cmdp)
-+ return -1;
-+
-+ if (cmdp->flags & IMAP_FLAG_DONE)
-+ return cmdp->response;
-+
-+ do {
-+ waitp = get_cmd_result(imap);
-+ } while (waitp->tag != cmdp->tag);
-+
-+ result = cmdp->response;
-+ dequeue_imap_cmd(cmdp->tag);
-+
-+ return cmdp->response;
-+
-+}
-+
- #ifdef HAVE_LIBSSL
- static int
- start_tls (imap_t *imap, config_t * cfg)
-@@ -1039,6 +1215,7 @@
- size_t n;
- char buf[1024];
-
-+ flush_imap_cmds(imap);
- send_server (imap->sock, "UID FETCH %d BODY.PEEK[]", uid);
-
- for (;;)
-@@ -1160,7 +1337,9 @@
- (buf[0] != 0) ? " " : "", Flags[i]);
- }
-
-- return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf);
-+ if (issue_imap_cmd(imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf))
-+ return 0;
-+ return -1;
- }
-
- int
-@@ -1249,6 +1428,7 @@
- strcat (flagstr,") ");
- }
-
-+ flush_imap_cmds(imap);
- send_server (imap->sock, "APPEND %s%s %s{%d}",
- imap->prefix, imap->box->box, flagstr, len + extra);
-
-@@ -1341,6 +1521,7 @@
- }
-
- /* didn't receive an APPENDUID */
-+ flush_imap_cmds(imap);
- send_server (imap->sock,
- "UID SEARCH HEADER X-TUID %08lx%05lx%04x",
- tv.tv_sec, tv.tv_usec, pid);
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
@@ -1 +0,0 @@
-[type: gettext/rfc822deb] templates
diff --git a/debian/po/fr.po b/debian/po/fr.po
@@ -1,74 +0,0 @@
-#
-# Translators, if you are not familiar with the PO format, gettext
-# documentation is worth reading, especially sections dedicated to
-# this format, e.g. by running:
-# info -n '(gettext)PO Files'
-# info -n '(gettext)Header Entry'
-#
-# Some information specific to po-debconf are available at
-# /usr/share/doc/po-debconf/README-trans
-# or http://www.debian.org/intl/l10n/po-debconf/README-trans
-#
-# Developers do not need to manually edit POT or PO files.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: isync 0.9.1-1\n"
-"POT-Creation-Date: 2003-10-14 21:55+0200\n"
-"PO-Revision-Date: 2003-10-27 11:52+0100\n"
-"Last-Translator: Christian Perrier <bubulle@debian.org>\n"
-"Language-Team: French <debian-l10n-french@lists.debian.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=iso-8859-15\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#. Description
-#: ../templates:4
-msgid "Abort isync upgrade"
-msgstr "Interrompre la mise à jour d'isync"
-
-#. Description
-#: ../templates:4
-msgid ""
-"You are upgrading from an older version of isync that stored the UID of each "
-"message in a way that is not compatable with the new version. You need to "
-"remove all the messages in local folders downloaded with the old version of "
-"isync. Otherwise isync will get confused and upload duplicate messages to "
-"the IMAP server."
-msgstr ""
-"Vous mettez isync à jour à partir d'une version qui utilise une méthode de "
-"stockage des identifiants des messages incompatible avec la nouvelle "
-"version. Il faut supprimer des répertoires locaux tous les messages "
-"téléchargés avec l'ancienne version ; sinon, isync fonctionnera "
-"incorrectement et enverra au serveur IMAP des doublons des messages."
-
-#. Description
-#: ../templates:4
-msgid ""
-"A suggested upgrade procedure is to use the isync version 0.7 to synchronize "
-"any local changes in isync-managed mailboxes with your IMAP server (if there "
-"are any local changes to synchronise), and then remove the contents of the "
-"local mailboxes, before upgrading to version 0.8 or above. Then run isync "
-"again to pull down the mail again. You must do this manually; the Debian "
-"package will not do this for you."
-msgstr ""
-"La méthode suggérée pour la mise à jour est la suivante : en utilisant "
-"la version 0.7 d'isync, synchronisez avec le serveur IMAP les éventuelles modifications locales "
-"des boîtes aux lettres gérées par isync ; puis "
-"supprimez le contenu des boîtes aux lettres locales. Ensuite, effectuez la "
-"mise à jour vers une version supérieure ou égale à 0.8. Enfin, utilisez à "
-"nouveau isync pour récupérer les courriels. Vous devez effectuer cette "
-"opération vous-même : le paquet Debian ne la fera pas automatiquement."
-
-#. Description
-#: ../templates:4
-msgid ""
-"If you want, the upgrade of isync can be aborted to let you deal with this "
-"issue. Or you can just suspend the upgrade or switch to a different virtual "
-"console to take care of it. Do not continue past this point before manually "
-"resolving this issue!"
-msgstr ""
-"Si vous le souhaitez, la mise à jour d'isync peut être interrompue pour vous "
-"permettre d'effectuer cette opération. Vous pouvez également basculer vers "
-"une autre console virtuelle pour vous en occuper, puis reprendre la mise à jour. Ne continuez pas "
-"sans faire cette correction."
diff --git a/debian/po/templates.pot b/debian/po/templates.pot
@@ -1,60 +0,0 @@
-#
-# Translators, if you are not familiar with the PO format, gettext
-# documentation is worth reading, especially sections dedicated to
-# this format, e.g. by running:
-# info -n '(gettext)PO Files'
-# info -n '(gettext)Header Entry'
-#
-# Some information specific to po-debconf are available at
-# /usr/share/doc/po-debconf/README-trans
-# or http://www.debian.org/intl/l10n/po-debconf/README-trans
-#
-# Developers do not need to manually edit POT or PO files.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2003-10-14 21:55+0200\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=CHARSET\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#. Description
-#: ../templates:4
-msgid "Abort isync upgrade"
-msgstr ""
-
-#. Description
-#: ../templates:4
-msgid ""
-"You are upgrading from an older version of isync that stored the UID of each "
-"message in a way that is not compatable with the new version. You need to "
-"remove all the messages in local folders downloaded with the old version of "
-"isync. Otherwise isync will get confused and upload duplicate messages to "
-"the IMAP server."
-msgstr ""
-
-#. Description
-#: ../templates:4
-msgid ""
-"A suggested upgrade procedure is to use the isync version 0.7 to synchronize "
-"any local changes in isync-managed mailboxes with your IMAP server (if there "
-"are any local changes to synchronise), and then remove the contents of the "
-"local mailboxes, before upgrading to version 0.8 or above. Then run isync "
-"again to pull down the mail again. You must do this manually; the Debian "
-"package will not do this for you."
-msgstr ""
-
-#. Description
-#: ../templates:4
-msgid ""
-"If you want, the upgrade of isync can be aborted to let you deal with this "
-"issue. Or you can just suspend the upgrade or switch to a different virtual "
-"console to take care of it. Do not continue past this point before manually "
-"resolving this issue!"
-msgstr ""
diff --git a/debian/preinst b/debian/preinst
@@ -1,19 +0,0 @@
-#!/bin/sh
-set -e
-
-if [ "$1" = "upgrade" ] && dpkg --compare-versions "$2" lt "0.8"; then
- # Do not do debconf stuff if debconf is not there.
- # I don't want to have to pre-depend on debconf.
- if [ -e /usr/share/debconf/confmodule ]; then
- . /usr/share/debconf/confmodule
- db_get isync/upgrade_0.8
- if [ "$RET" = true ]; then
- echo "Aborting isync upgrade at your request so you can manually resolve upgrade issue." >&2
- exit 1
- fi
- else
- echo "WARNING: Read NEWS.Debian file about manual upgrade issues from isync 0.7." >&2
- fi
-fi
-
-#DEBHELPER#
diff --git a/debian/rules b/debian/rules
@@ -41,9 +41,8 @@ binary-arch: build install
dh_testroot
dh_installchangelogs ChangeLog
dh_installdocs AUTHORS NEWS README TODO
- dh_installexamples isyncrc.sample
+ dh_installexamples src/mbsyncrc.sample src/compat/isyncrc.sample
dh_installman
- dh_installdebconf
dh_strip
dh_compress
dh_fixperms
diff --git a/debian/templates b/debian/templates
@@ -1,21 +0,0 @@
-Template: isync/upgrade_0.8
-Type: boolean
-Default: false
-_Description: Abort isync upgrade
- You are upgrading from an older version of isync that stored the UID of
- each message in a way that is not compatable with the new version. You
- need to remove all the messages in local folders downloaded with the old
- version of isync. Otherwise isync will get confused and upload duplicate
- messages to the IMAP server.
- .
- A suggested upgrade procedure is to use the isync version 0.7 to
- synchronize any local changes in isync-managed mailboxes with your IMAP
- server (if there are any local changes to synchronise), and then remove
- the contents of the local mailboxes, before upgrading to version 0.8 or
- above. Then run isync again to pull down the mail again. You must do this
- manually; the Debian package will not do this for you.
- .
- If you want, the upgrade of isync can be aborted to let you deal with this
- issue. Or you can just suspend the upgrade or switch to a different
- virtual console to take care of it. Do not continue past this point before
- manually resolving this issue!
diff --git a/isync.1 b/isync.1
@@ -1,344 +0,0 @@
-.ig
-\" isync - IMAP4 to maildir mailbox synchronizer
-\" Copyright (C) 2000-2002 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
-\"
-\" As a special exception, isync may be linked with the OpenSSL library,
-\" despite that library's more restrictive license.
-..
-.TH isync 1 "2002 Dec 22"
-..
-.SH NAME
-isync - synchronize IMAP4 and maildir mailboxes
-..
-.SH SYNOPSIS
-\fBisync\fR [\fIoptions...\fR] \fImailbox\fR [\fImailbox ...\fR]
-.br
-\fBisync\fR [\fIoptions...\fR] \fI-a\fR
-.br
-\fBisync\fR [\fIoptions...\fR] \fI-l\fR
-..
-.SH DESCRIPTION
-\fBisync\fR 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 \fIfile\fR.
-By default, the configuration is read from ~/.isyncrc if it exists.
-.TP
-\fB-1\fR, \fB--one-to-one\fR
-Instead of using the mailbox specifications in ~/.isyncrc, isync will pick up
-all mailboxes from the local directory and remote folder and map them 1:1
-onto each other according to their names.
-.TP
-\fB-I\fR, \fB--inbox\fR \fImailbox\fR
-Exception to the 1:1 mapping created by -1: the special IMAP mailbox \fIINBOX\fR
-is mapped to the local \fImailbox\fR (relative to the maildir).
-.TP
-\fB-a\fR, \fB--all\fR
-Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the
-1:1 mapping).
-.TP
-\fB-l\fR, \fB--list\fR
-Don't synchronize anything, but list all mailboxes and exit.
-.TP
-\fB-L\fR, \fB--create-local\fR
-Automatically create the local maildir-style mailbox if it doesn't already
-exist.
-.TP
-\fB-R\fR, \fB--create-remote\fR
-Automatically create the remote IMAP mailbox if it doesn't already exist.
-.TP
-\fB-C\fR, \fB--create\fR
-Automatically create any mailboxes if they don't already exist.
-This is simply a combination of -L and -R.
-.TP
-\fB-d\fR, \fB--delete\fR
-Causes \fBisync\fR to delete messages from the local maildir mailbox
-which do not exist on the IMAP server. By default, \fIdead\fR messages
-are \fBnot\fR deleted.
-.TP
-\fB-e\fR, \fB--expunge\fR
-Causes \fBisync\fR to permanently remove all messages marked for deletion
-in both the local maildir mailbox and the remote IMAP mailbox. By default,
-\fIdeleted\fR messages are \fBnot\fR expunged.
-.TP
-\fB-f\fR, \fB--fast\fR
-Causes \fBisync\fR 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
-\fB-h\fR, \fB--help\fR
-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-q\fR, \fB--quiet\fR
-Suppress informational messages.
-If specified twice, suppress warning messages as well.
-.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
-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
-\fB-M\fR, \fB--maildir\fR \fIdir\fR
-Specifies the location for your local mailboxes.
-.TP
-\fB-F\fR, \fB--folder\fR \fIfolder\fR/
-Specifies the location for your remote mailboxes.
-.TP
-\fB-v\fR, \fB--version\fR
-Displays \fBisync\fR version information.
-.TP
-\fB-V\fR, \fB--verbose\fR
-Enables \fIverbose\fR mode, which displays the IMAP4 network traffic.
-..
-.SH CONFIGURATION
-\fBisync\fR reads \fI~/.isyncrc\fR 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 \fIMailbox\fR 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 \fIimaps:\fR the connection is assumed to be a SSL connection
-to port 993 (though you can change this by placing a \fBPort\fR command
-\fBafter\fR the \fBHost\fR command. Note that some servers support SSL on
-the default port 143. \fBisync\fR 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 \fIusername\fR on the IMAP server.
-Note that this option is \fBNOT\fR required.
-If no password is specified in the configuration file, \fBisync\fR
-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
-\fBCopyDeletedTo\fR \fImailbox\fR
-Specifies the remote IMAP mailbox to copy deleted messages prior to
-expunging (Default: none).
-..
-.TP
-\fBDelete\fR \fIyes\fR|\fIno\fR
-Specifies whether messages in the local copy of the mailbox which don't
-exist on the server are automatically deleted. (Default: no).
-\fBNOTE:\fR The \fI-d\fR command line option overrides this setting when
-set to \fIno\fR.
-..
-.TP
-\fBExpunge\fR \fIyes\fR|\fIno\fR
-Specifies whether deleted messages are expunged by default (Default: no).
-\fBNOTE:\fR The \fI-e\fR command line option overrides this setting when
-set to \fIno\fR.
-..
-.TP
-\fBMailDir\fR \fIdirectory\fR
-Specifies the location of your local mailboxes if a relative path is
-specified in a \fIMailbox\fR command (Default: \fI~\fR).
-\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
-section (see below).
-..
-.TP
-\fBFolder\fR \fIdirectory\fR/
-Specifies the location of your IMAP mailboxes
-specified in \fIBox\fR commands (Default: \fI""\fR).
-\fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually
-a slash) to this specification.
-\fBNOTE 2:\fR This directive is only meaningful in the \fIglobal\fR
-section (see below).
-..
-.TP
-\fBMaxMessages\fR \fIcount\fR
-Sets the number of messages \fBisync\fR should keep in a 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 (independent of the
-actual date of the message) will automatically be deleted if you
-pass \fBisync\fR the delete (-d, --delete) flag.
-Messages that are flagged (marked as important) will not be automatically
-deleted.
-If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR
-(Default: 0).
-..
-.TP
-\fBMaxSize\fR \fIbytes\fR
-Sets a threshold for the maximum message size (in bytes) for which \fBisync\fR
-should transfer over the wire. This is useful for weeding out messages with
-large attachments. If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR.
-..
-.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
-\fBUseNamespace\fR \fIyes\fR|\fIno\fR
-Selects whether \fBisync\fR should select mailboxes using the namespace given
-by the NAMESPACE command. This is useful with broken IMAP servers. (Default:
-\fIyes\fR)
-..
-.TP
-\fBRequireCRAM\fR \fIyes\fR|\fIno\fR
-If set to \fIyes\fR, \fBisync\fR will require that the server accept CRAM-MD5
-intead of PLAIN to authenticate the user.
-..
-.TP
-\fBRequireSSL\fR \fIyes\fR|\fIno\fR
-\fBisync\fR will abort the connection if a TLS/SSL session to the IMAP
-server can not be established. (Default: \fIyes\fR)
-..
-.TP
-\fBCertificateFile\fR \fIpath\fR
-File containing X.509 CA certificates used to verify server identities.
-..
-.TP
-\fBUseSSLv2\fR \fIyes\fR|\fIno\fR
-Should \fBisync\fR use SSLv2 for communication with the IMAP server over SSL?
-(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
-..
-.TP
-\fBUseSSLv3\fR \fIyes\fR|\fIno\fR
-Should \fBisync\fR use SSLv3 for communication with the IMAP server over SSL?
-(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
-..
-.TP
-\fBUseTLSv1\fR \fIyes\fR|\fIno\fR
-Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL?
-(Default: \fIyes\fR)
-..
-.TP
-\fBOneToOne\fR
-\fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up
-all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map
-them 1:1 onto each other according to their names.
-\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
-section (see below).
-..
-.TP
-\fBInbox\fR \fImailbox\fR
-Exception to the OneToOne mapping: the special IMAP mailbox \fIINBOX\fR
-is mapped to the local \fImailbox\fR (relative to the \fIMailDir\fR).
-\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
-section (see below).
-..
-.P
-Configuration commands that appear prior to the first \fBMailbox\fR
-command are considered to be \fIglobal\fR
-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 \fBUser\fR command before
-the first \fBMailbox\fR command, and then leave out the \fBUser\fR command
-in the sections for each mailbox.
-\fBisync\fR will then use the global value by default.
-..
-.SH MAIL USER AGENT INTERACTION
-To use \fBisync\fR effectively, you need a mail client that sets the T
-(trashed) flag when it deletes a message from a maildir mailbox, instead of
-just removing it altogether. Without such a client, \fBisync\fR will refetch the
-locally deleted messages from the server since they will never get expunged.
-Mutt (starting with version 1.3.27) is known to support this. Be sure to put
-.IP "" 2
-set maildir_trash
-.PP
-in your ~/.muttrc when using Mutt.
-.br
-\fBisync\fR can be integrated into Mutt fairly easily with a few hooks:
-.IP "" 2
-.nf
-folder-hook ~A bind index $ <sync-mailbox>
-.br
-folder-hook +\fImdir\fR 'macro index $ "<sync-mailbox>!isync -e \fImdir\fR\\n"'
-.fi
-.PP
-where \fImdir\fR is the name of the local mailbox (or its \fIalias\fR).
-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).
-..
-.SH FILES
-.TP
-.B ~/.isyncrc
-Default configuration file
-..
-.SH BUGS
-No support for IMAP subfolders.
-.P
-\fBisync\fR does not use NFS-safe locking. It will correctly prevent
-concurrent synchronization of a mailbox on the same host, but not across NFS.
-.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.
-.P
-If new mail arrives in the IMAP mailbox after \fBisync\fR
-has retrieved the initial message list, the new mail will not be fetched
-until the next time \fBisync\fR is invoked.
-.P
-It is currently impossible to unset the \\Flagged attribute of a message
-once it is set. It has to be manually unset everywhere since isync
-doesn't have enough information to know which was the last status of the
-message.
-.P
-The ndbm database created for each mailbox is not portable across different
-architectures. It currently stores the UID in host byte order.
-.P
-The configuration file takes precedence over command line options.
-.br
-Use -c /dev/null to work around.
-.SH SEE ALSO
-mutt(1), maildir(5)
-.P
-Up to date information on \fBisync\fR can be found at http://isync.sf.net/
-..
-.SH AUTHOR
-Written by Michael R. Elkins <me@mutt.org>,
-.br
-maintained by Oswald Buddenhagen <ossi@users.sf.net>.
diff --git a/isync.spec.in b/isync.spec.in
@@ -10,16 +10,16 @@ Packager: Oswald Buddenhagen <ossi@users.sf.net>
BuildRoot: /var/tmp/%{name}-buildroot
%description
-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.
+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).
%prep
%setup
%build
./configure --prefix=/usr
-make RPM_OPT_FLAGS="$RPM_OPT_FLAGS"
%install
make DESTDIR=$RPM_BUILD_ROOT install
@@ -28,6 +28,11 @@ make DESTDIR=$RPM_BUILD_ROOT install
rm -rf $RPM_BUILD_ROOT
%files
-%doc AUTHORS COPYING README TODO ChangeLog isyncrc.sample
+%doc AUTHORS COPYING NEWS README TODO ChangeLog src/mbsyncrc.sample src/compat/isyncrc.sample
/usr/bin/isync
+/usr/bin/mbsync
+/usr/bin/mdconvert
+/usr/bin/get-cert
/usr/man/man1/isync.1.gz
+/usr/man/man1/mbsync.1.gz
+/usr/man/man1/mdconvert.1.gz
diff --git a/src/.cvsignore b/src/.cvsignore
@@ -1,4 +1,5 @@
.deps
Makefile
Makefile.in
-isync
+mbsync
+mdconvert
diff --git a/src/Makefile.am b/src/Makefile.am
@@ -1,5 +1,16 @@
-bin_PROGRAMS = isync
-isync_SOURCES = main.c imap.c sync.c maildir.c list.c cram.c config.c dotlock.c
-noinst_HEADERS = isync.h dotlock.h
-INCLUDES=$(RPM_OPT_FLAGS)
-DISTCLEANFILES = *~
+if with_compat
+compat_dir = compat
+endif
+SUBDIRS = $(compat_dir)
+
+bin_PROGRAMS = mbsync mdconvert
+
+mbsync_SOURCES = main.c sync.c config.c util.c drv_imap.c drv_maildir.c
+mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS)
+noinst_HEADERS = isync.h
+
+mdconvert_SOURCES = mdconvert.c
+mdconvert_LDADD = -ldb
+
+man_MANS = mbsync.1 mdconvert.1
+EXTRA_DIST = mbsyncrc.sample $(man_MANS)
diff --git a/src/.cvsignore b/src/compat/.cvsignore
diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am
@@ -0,0 +1,8 @@
+bin_PROGRAMS = isync
+
+isync_SOURCES = main.c config.c convert.c util.c
+isync_LDADD = -ldb
+noinst_HEADERS = isync.h
+
+man_MANS = isync.1
+EXTRA_DIST = isyncrc.sample $(man_MANS)
diff --git a/src/compat/config.c b/src/compat/config.c
@@ -0,0 +1,443 @@
+/*
+ * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "isync.h"
+
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static char *
+my_strndup( const char *s, size_t nchars )
+{
+ char *r = nfmalloc( sizeof(char) * (nchars + 1) );
+ memcpy( r, s, nchars );
+ r[nchars] = 0;
+ return r;
+}
+
+char *
+expand_strdup( const char *s )
+{
+ struct passwd *pw;
+ const char *p, *q;
+ char *r;
+
+ if (*s == '~') {
+ s++;
+ if (!*s) {
+ p = 0;
+ q = Home;
+ } else if (*s == '/') {
+ p = s + 1;
+ q = Home;
+ } else {
+ if ((p = strchr( s, '/' ))) {
+ r = my_strndup( s, (int)(p - s) );
+ pw = getpwnam( r );
+ free( r );
+ p++;
+ } else
+ pw = getpwnam( s );
+ if (!pw)
+ return 0;
+ q = pw->pw_dir;
+ }
+ nfasprintf( &r, "%s/%s", q, p ? p : "" );
+ return r;
+ } else if (*s != '/' && xmaildir) {
+ nfasprintf( &r, "%s/%s", xmaildir, s );
+ return r;
+ } else
+ return nfstrdup( s );
+}
+
+static int
+is_true( const char *val )
+{
+ return
+ !strcasecmp( val, "yes" ) ||
+ !strcasecmp( val, "true" ) ||
+ !strcasecmp( val, "on" ) ||
+ !strcmp( val, "1" );
+}
+
+void
+load_config( const char *path, config_t ***stor )
+{
+ config_t **sstor, *cfg;
+ FILE *fp;
+ char *p, *cmd, *val;
+ int line = 0;
+ char buf[1024];
+
+ if (!(fp = fopen( path, "r" ))) {
+ if (errno != ENOENT)
+ perror( "fopen" );
+ return;
+ }
+ if (!Quiet && !Debug && !Verbose)
+ printf( "Reading configuration file %s\n", path );
+ buf[sizeof(buf) - 1] = 0;
+ cfg = &global;
+ while (fgets( buf, sizeof(buf) - 1, fp )) {
+ p = buf;
+ cmd = next_arg( &p );
+ val = next_arg( &p );
+ line++;
+ if (!cmd || *cmd == '#')
+ continue;
+ if (!val) {
+ fprintf( stderr, "%s:%d: parameter missing\n", path, line );
+ continue;
+ }
+ if (!strcasecmp( "Mailbox", cmd )) {
+ if (o2o)
+ break;
+ cfg = **stor = nfmalloc( sizeof(config_t) );
+ *stor = &cfg->next;
+ memcpy( cfg, &global, sizeof(config_t) );
+ /* not expanded at this point */
+ cfg->path = nfstrdup( val );
+ } else if (!strcasecmp( "OneToOne", cmd )) {
+ if (boxes) {
+ forbid:
+ fprintf( stderr,
+ "%s:%d: keyword '%s' allowed only in global section\n",
+ path, line, cmd );
+ continue;
+ }
+ o2o = is_true( val );
+ } else if (!strcasecmp( "Maildir", cmd )) {
+ if (boxes)
+ goto forbid;
+ maildir = nfstrdup( val );
+ xmaildir = expand_strdup( val );
+ } else if (!strcasecmp( "Folder", cmd )) {
+ if (boxes)
+ goto forbid;
+ folder = nfstrdup( val );
+ } else if (!strcasecmp( "Inbox", cmd )) {
+ if (boxes)
+ goto forbid;
+ inbox = nfstrdup( val );
+ } else if (!strcasecmp( "Host", cmd )) {
+ if (!memcmp( "imaps:", val, 6 )) {
+ val += 6;
+ cfg->use_imaps = 1;
+ cfg->port = 993;
+ cfg->use_sslv2 = 1;
+ cfg->use_sslv3 = 1;
+ }
+ cfg->host = nfstrdup( val );
+ } else if (!strcasecmp( "User", cmd ))
+ cfg->user = nfstrdup( val );
+ else if (!strcasecmp( "Pass", cmd ))
+ cfg->pass = nfstrdup( val );
+ else if (!strcasecmp ( "Port", cmd ))
+ cfg->port = atoi( val );
+ else if (!strcasecmp ( "Box", cmd ))
+ cfg->box = nfstrdup( val );
+ else if (!strcasecmp ( "Alias", cmd )) {
+ if (!boxes) {
+ fprintf( stderr,
+ "%s:%d: keyword 'Alias' allowed only in mailbox specification\n",
+ path, line );
+ continue;
+ }
+ cfg->alias = nfstrdup( val );
+ } else if (!strcasecmp( "MaxSize", cmd ))
+ cfg->max_size = atol( val );
+ else if (!strcasecmp ( "MaxMessages", cmd ))
+ cfg->max_messages = atol( val );
+ else if (!strcasecmp ( "UseNamespace", cmd ))
+ cfg->use_namespace = is_true( val );
+ else if (!strcasecmp ( "CopyDeletedTo", cmd ))
+ cfg->copy_deleted_to = nfstrdup( val );
+ else if (!strcasecmp ( "Tunnel", cmd ))
+ cfg->tunnel = nfstrdup( val );
+ else if (!strcasecmp ( "Expunge", cmd ))
+ cfg->expunge = is_true( val );
+ else if (!strcasecmp( "Delete", cmd ))
+ cfg->delete = is_true( val );
+ else if (!strcasecmp( "CertificateFile", cmd ))
+ cfg->cert_file = expand_strdup( val );
+ else if (!strcasecmp( "RequireSSL", cmd ))
+ cfg->require_ssl = is_true( val );
+ else if (!strcasecmp( "UseSSLv2", cmd ))
+ cfg->use_sslv2 = is_true( val );
+ else if (!strcasecmp( "UseSSLv3", cmd ))
+ cfg->use_sslv3 = is_true( val );
+ else if (!strcasecmp( "UseTLSv1", cmd ))
+ cfg->use_tlsv1 = is_true( val );
+ else if (!strcasecmp( "RequireCRAM", cmd ))
+ cfg->require_cram = is_true( val );
+ else if (buf[0])
+ fprintf( stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd );
+ }
+ fclose( fp );
+ if (o2o) {
+ if (!global.host && !global.tunnel) {
+ fprintf( stderr, "Neither Host nor Tunnel given to OneToOne. Aborting.\n" );
+ exit( 1 );
+ }
+ } else
+ for (sstor = &boxes; (cfg = *sstor); ) {
+ if (!cfg->host && !cfg->tunnel) {
+ fprintf( stderr, "Mailbox '%s' has neither Host nor Tunnel. Skipping.\n",
+ cfg->alias ? cfg->alias : cfg->path );
+ if (&cfg->next == *stor)
+ *stor = sstor;
+ *sstor = cfg->next;
+ continue;
+ }
+ sstor = &cfg->next;
+ }
+}
+
+static const char *
+tb( int on )
+{
+ return on ? "yes" : "no";
+}
+
+static void
+write_imap_server( FILE *fp, config_t *cfg )
+{
+ config_t *pbox;
+ char *p, *p2;
+ int hl, a1, a2, a3, a4;
+ char buf[128];
+ static int tunnels;
+
+ if (cfg->tunnel) {
+ nfasprintf( (char **)&cfg->server_name, "tunnel%d", ++tunnels );
+ fprintf( fp, "IMAPAccount %s\nTunnel \"%s\"\n",
+ cfg->server_name, cfg->tunnel );
+ } else {
+ if (sscanf( cfg->host, "%d.%d.%d.%d", &a1, &a2, &a3, &a4 ) == 4)
+ cfg->server_name = nfstrdup( cfg->host );
+ else {
+ p = strrchr( cfg->host, '.' );
+ if (!p)
+ hl = nfsnprintf( buf, sizeof(buf), "%s", cfg->host );
+ else {
+ hl = nfsnprintf( buf, sizeof(buf), "%.*s", p - cfg->host, cfg->host );
+ p2 = strrchr( buf, '.' );
+ if (p2)
+ hl = sprintf( buf, "%s", p2 + 1 );
+ }
+ if (boxes) /* !o2o */
+ for (pbox = boxes; pbox != cfg; pbox = pbox->next)
+ if (!memcmp( pbox->server_name, buf, hl + 1 )) {
+ nfasprintf( (char **)&cfg->server_name, "%s-%d", buf, ++pbox->servers );
+ goto gotsrv;
+ }
+ cfg->server_name = nfstrdup( buf );
+ cfg->servers = 1;
+ gotsrv: ;
+ }
+ fprintf( fp, "IMAPAccount %s\n", cfg->server_name );
+ if (cfg->use_imaps)
+ fprintf( fp, "Host imaps:%s\n", cfg->host );
+ else
+ fprintf( fp, "Host %s\n", cfg->host );
+ fprintf( fp, "Port %d\n", cfg->port );
+ }
+ if (cfg->user)
+ fprintf( fp, "User %s\n", cfg->user );
+ if (cfg->pass)
+ fprintf( fp, "Pass \"%s\"\n", cfg->pass );
+ fprintf( fp, "RequireCRAM %s\nRequireSSL %s\n"
+ "UseSSLv2 %s\nUseSSLv3 %s\nUseTLSv1 %s\n",
+ tb(cfg->require_cram), tb(cfg->require_ssl),
+ tb(cfg->use_sslv2), tb(cfg->use_sslv3), tb(cfg->use_tlsv1) );
+ if ((cfg->use_imaps || cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1) &&
+ cfg->cert_file)
+ fprintf( fp, "CertificateFile %s\n", cfg->cert_file );
+ fputc( '\n', fp );
+}
+
+static void
+write_imap_store( FILE *fp, config_t *cfg )
+{
+ if (cfg->stores > 1)
+ nfasprintf( (char **)&cfg->store_name, "%s-%d", cfg->server_name, cfg->stores );
+ else
+ cfg->store_name = cfg->server_name;
+ fprintf( fp, "IMAPStore %s\nAccount %s\n",
+ cfg->store_name, cfg->server_name );
+ if (*folder)
+ fprintf( fp, "Path \"%s\"\n", folder );
+ else
+ fprintf( fp, "UseNamespace %s\n", tb(cfg->use_namespace) );
+ if (inbox)
+ fprintf( fp, "MapInbox \"%s\"\n", inbox );
+ if (cfg->copy_deleted_to)
+ fprintf( fp, "Trash \"%s\"\n", cfg->copy_deleted_to );
+ fputc( '\n', fp );
+}
+
+static void
+write_channel_parm( FILE *fp, config_t *cfg )
+{
+ if (cfg->max_size)
+ fprintf( fp, "MaxSize %d\n", cfg->max_size );
+ if (cfg->max_messages)
+ fprintf( fp, "MaxMessages %d\n", cfg->max_messages );
+ if (!cfg->delete)
+ fputs( "Sync New ReNew Flags\n", fp );
+ if (cfg->expunge)
+ fputs( "Expunge Both\n", fp );
+ fputc( '\n', fp );
+}
+
+static int
+mstrcmp( const char *s1, const char *s2 )
+{
+ if (s1 == s2)
+ return 0;
+ if (!s1 || !s2)
+ return 1;
+ return strcmp( s1, s2 );
+}
+
+void
+write_config( int fd )
+{
+ FILE *fp;
+ const char *cn, *scn;
+ config_t *box, *sbox, *pbox;
+
+ if (!(fp = fdopen( fd, "w" ))) {
+ perror( "fdopen" );
+ return;
+ }
+
+ fprintf( fp, "SyncState *\n\nMaildirStore local\nPath \"%s/\"\nAltMap %s\n\n", maildir, tb( altmap > 0 ) );
+ if (o2o) {
+ write_imap_server( fp, &global );
+ write_imap_store( fp, &global );
+ fprintf( fp, "Channel o2o\nMaster :%s:\nSlave :local:\nPattern %%\n", global.store_name );
+ write_channel_parm( fp, &global );
+ } else {
+ for (box = boxes; box; box = box->next) {
+ for (pbox = boxes; pbox != box; pbox = pbox->next) {
+ if (box->tunnel) {
+ if (mstrcmp( pbox->tunnel, box->tunnel ))
+ continue;
+ } else {
+ if (mstrcmp( pbox->host, box->host ) ||
+ pbox->use_imaps != box->use_imaps ||
+ pbox->port != box->port)
+ continue;
+ }
+ if (mstrcmp( pbox->user, box->user ) ||
+ mstrcmp( pbox->pass, box->pass )) /* nonsense */
+ continue;
+ if ((box->use_imaps || box->use_sslv2 ||
+ box->use_sslv3 || box->use_tlsv1) &&
+ mstrcmp( pbox->cert_file, box->cert_file )) /* nonsense */
+ continue;
+ if (pbox->use_imaps != box->use_imaps ||
+ pbox->use_sslv2 != box->use_sslv2 ||
+ pbox->use_sslv3 != box->use_sslv3 ||
+ pbox->use_tlsv1 != box->use_tlsv1)
+ continue;
+ box->server_name = pbox->server_name;
+ for (sbox = boxes; sbox != box; sbox = sbox->next) {
+ if (sbox->server_name != box->server_name ||
+ mstrcmp( sbox->copy_deleted_to, box->copy_deleted_to ) ||
+ (!*folder && sbox->use_namespace != box->use_namespace))
+ continue;
+ box->store_name = sbox->store_name;
+ goto gotall;
+ }
+ box->stores = ++pbox->stores;
+ goto gotsrv;
+ }
+ write_imap_server( fp, box );
+ box->stores = 1;
+ gotsrv:
+ write_imap_store( fp, box );
+ gotall:
+ if (box->alias)
+ cn = box->alias;
+ else {
+ cn = strrchr( box->path, '/' );
+ if (cn)
+ cn++;
+ else
+ cn = box->path;
+ }
+ for (sbox = boxes; sbox != box; sbox = sbox->next) {
+ if (sbox->alias)
+ scn = sbox->alias;
+ else {
+ scn = strrchr( sbox->path, '/' );
+ if (scn)
+ scn++;
+ else
+ scn = sbox->path;
+ }
+ if (mstrcmp( cn, scn ))
+ continue;
+ nfasprintf( (char **)&box->channel_name, "%s-%d", cn, ++sbox->channels );
+ goto gotchan;
+ }
+ box->channels = 1;
+ box->channel_name = cn;
+ gotchan:
+ fprintf( fp, "Channel %s\nMaster :%s:%s\nSlave :local:%s\n",
+ box->channel_name, box->store_name, box->box, box->path );
+ write_channel_parm( fp, box );
+ }
+
+ }
+
+ fclose( fp );
+}
+
+config_t *
+find_box( const char *s )
+{
+ config_t *p;
+ char *t;
+
+ for (p = boxes; p; p = p->next) {
+ if (!strcmp( s, p->path ) || (p->alias && !strcmp( s, p->alias )))
+ return p;
+ /* check to see if the full pathname was specified on the
+ * command line.
+ */
+ t = expand_strdup( p->path );
+ if (!strcmp( s, t )) {
+ free( t );
+ return p;
+ }
+ free( t );
+ }
+ return 0;
+}
diff --git a/src/compat/convert.c b/src/compat/convert.c
@@ -0,0 +1,259 @@
+/*
+ * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "isync.h"
+
+#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 <time.h>
+
+#include <db.h>
+
+static const char *subdirs[] = { "cur", "new", "tmp" };
+
+static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
+
+static int
+parse_info( const char *s )
+{
+ unsigned i;
+ int flags;
+
+ flags = 0;
+ if (s && *(s + 1) == '2' && *(s + 2) == ',')
+ for (s += 3, i = 0; i < as(Flags); i++)
+ if (strchr( s, Flags[i] ))
+ flags |= (1 << i);
+ return flags;
+}
+
+typedef struct {
+ int uid, flags;
+} msg_t;
+
+static int
+compare_uids( const void *l, const void *r )
+{
+ return ((msg_t *)l)->uid - ((msg_t *)r)->uid;
+}
+
+static DBT key, value;
+static struct flock lck;
+
+void
+convert( config_t *box )
+{
+ DIR *d;
+ struct dirent *e;
+ char *s, *p, *mboxdir;
+ FILE *fp;
+ msg_t *msgs;
+ DB *db;
+ int i, ret, fd, uidval, maxuid, bl, uid, rmsgs, nmsgs, uv[2];
+ unsigned u;
+ struct stat sb;
+ char buf[_POSIX_PATH_MAX], diumname[_POSIX_PATH_MAX],
+ uvname[_POSIX_PATH_MAX], sname[_POSIX_PATH_MAX],
+ iuvname[_POSIX_PATH_MAX], imuname[_POSIX_PATH_MAX],
+ ilname[_POSIX_PATH_MAX], iumname[_POSIX_PATH_MAX];
+
+ mboxdir = expand_strdup( box->path );
+ nfsnprintf( iuvname, sizeof(iuvname), "%s/isyncuidvalidity", mboxdir );
+ nfsnprintf( diumname, sizeof(iumname), "%s/.isyncuidmap.db", mboxdir );
+ nfsnprintf( uvname, sizeof(uvname), "%s/.uidvalidity", mboxdir );
+ if (stat( iuvname, &sb )) {
+ if (!stat( diumname, &sb ))
+ altmap++;
+ else if (!stat( uvname, &sb ))
+ altmap--;
+ err1:
+ free( mboxdir );
+ return;
+ }
+ for (i = 0; i < 3; i++) {
+ nfsnprintf( buf, sizeof(buf), "%s/%s", mboxdir, subdirs[i] );
+ if (stat( buf, &sb )) {
+ fprintf( stderr, "ERROR: stat %s: %s (errno %d)\n", buf,
+ strerror(errno), errno );
+ fprintf( stderr,
+ "ERROR: %s does not appear to be a valid maildir style mailbox\n",
+ mboxdir );
+ goto err1;
+ }
+ }
+ nfsnprintf( iumname, sizeof(iumname), "%s/isyncuidmap.db", mboxdir );
+ nfsnprintf( imuname, sizeof(imuname), "%s/isyncmaxuid", mboxdir );
+ nfsnprintf( ilname, sizeof(ilname), "%s/isynclock", mboxdir );
+ nfsnprintf( sname, sizeof(sname), "%s/.mbsyncstate", mboxdir );
+
+ if ((fd = open( ilname, O_WRONLY|O_CREAT, 0600 )) < 0) {
+ perror( ilname );
+ goto err1;
+ }
+#if SEEK_SET != 0
+ lck.l_whence = SEEK_SET;
+#endif
+#if F_WRLCK != 0
+ lck.l_type = F_WRLCK;
+#endif
+ if (fcntl( fd, F_SETLKW, &lck )) {
+ perror( ilname );
+ err2:
+ close( fd );
+ goto err1;
+ }
+
+ if (!(fp = fopen( iuvname, "r" ))) {
+ perror( iuvname );
+ goto err2;
+ }
+ fscanf( fp, "%d", &uidval );
+ fclose( fp );
+ if (!(fp = fopen( imuname, "r" ))) {
+ perror( imuname );
+ goto err2;
+ }
+ fscanf( fp, "%d", &maxuid );
+ fclose( fp );
+
+ if (!stat( iumname, &sb )) {
+ if (db_create( &db, 0, 0 )) {
+ fputs( "dbcreate failed\n", stderr );
+ goto err2;
+ }
+ if (db->open( db, 0, iumname, 0, DB_HASH, 0, 0 )) {
+ fputs( "cannot open db\n", stderr );
+ db->close( db, 0 );
+ goto err2;
+ }
+ altmap++;
+ } else {
+ db = 0;
+ altmap--;
+ }
+
+ msgs = 0;
+ rmsgs = 0;
+ nmsgs = 0;
+ for (i = 0; i < 2; i++) {
+ bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", mboxdir, subdirs[i] );
+ if (!(d = opendir( buf ))) {
+ perror( "opendir" );
+ err4:
+ if (msgs)
+ free( msgs );
+ if (db)
+ db->close( db, 0 );
+ goto err2;
+ }
+ while ((e = readdir( d ))) {
+ if (*e->d_name == '.')
+ continue;
+ s = strchr( e->d_name, ':' );
+ if (db) {
+ key.data = e->d_name;
+ key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name );
+ if ((ret = db->get( db, 0, &key, &value, 0 ))) {
+ if (ret != DB_NOTFOUND)
+ db->err( db, ret, "Maildir error: db->get()" );
+ continue;
+ }
+ uid = *(int *)value.data;
+ } else if ((p = strstr( e->d_name, ",U=" )))
+ uid = atoi( p + 3 );
+ else
+ continue;
+ if (nmsgs == rmsgs) {
+ rmsgs = rmsgs * 2 + 100;
+ msgs = nfrealloc( msgs, rmsgs * sizeof(msg_t) );
+ }
+ msgs[nmsgs].uid = uid;
+ msgs[nmsgs++].flags = parse_info( s );
+ }
+ closedir( d );
+ }
+
+ qsort( msgs, nmsgs, sizeof(msg_t), compare_uids );
+
+ if (!(fp = fopen( sname, "w" ))) {
+ perror( sname );
+ goto err4;
+ }
+ if (box->max_messages) {
+ if (!nmsgs)
+ i = maxuid;
+ else {
+ i = nmsgs - box->max_messages;
+ if (i < 0)
+ i = 0;
+ i = msgs[i].uid - 1;
+ }
+ } else
+ i = 0;
+ fprintf( fp, "%d:%d %d:%d:%d\n", uidval, maxuid, uidval, i, maxuid );
+ for (i = 0; i < nmsgs; i++) {
+ fprintf( fp, "%d %d ", msgs[i].uid, msgs[i].uid );
+ for (u = 0; u < as(Flags); u++)
+ if (msgs[i].flags & (1 << u))
+ fputc( Flags[u], fp );
+ fputc( '\n', fp );
+ }
+ fclose( fp );
+
+ if (db) {
+ key.data = (void *)"UIDVALIDITY";
+ key.size = 11;
+ uv[0] = uidval;
+ uv[1] = maxuid;
+ value.data = uv;
+ value.size = sizeof(uv);
+ if ((ret = db->put( db, 0, &key, &value, 0 ))) {
+ db->err( db, ret, "Maildir error: db->put()" );
+ goto err4;
+ }
+ db->close( db, 0 );
+ rename( iumname, diumname );
+ } else {
+ if (!(fp = fopen( uvname, "w" ))) {
+ perror( uvname );
+ goto err4;
+ }
+ fprintf( fp, "%d\n%d\n", uidval, maxuid );
+ fclose( fp );
+ }
+
+ unlink( iuvname );
+ unlink( imuname );
+
+ close( fd );
+ unlink( ilname );
+
+ if (msgs)
+ free( msgs );
+ free( mboxdir );
+ return;
+}
diff --git a/src/compat/isync.1 b/src/compat/isync.1
@@ -0,0 +1,318 @@
+.ig
+\" isync - mbsync wrapper: IMAP4 to Maildir mailbox synchronizer
+\" Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+\" Copyright (C) 2002-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, write to the Free Software
+\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\"
+..
+.TH isync 1 "2004 Mar 27"
+..
+.SH NAME
+isync - synchronize IMAP4 and Maildir mailboxes
+..
+.SH SYNOPSIS
+\fBisync\fR [\fIoptions\fR ...] {\fImailbox\fR ...|\fI-a\fR|\fI-l\fR}
+..
+.SH DESCRIPTION
+\fBisync\fR is a command line application which synchronizes local
+Maildir mailboxes with remote IMAP4 mailboxes, suitable for use in
+IMAP-disconnected mode. Multiple copies of the remote IMAP4 mailboxes can
+be maintained, and all flags are synchronized.
+.br
+\fBisync\fR is only a wrapper binary around \fBmbsync\fR to simplify upgrades.
+It will automatically migrate the UID mapping from previous versions of
+\fBisync\fR (even before 0.8) to the new format, and transparently call
+\fBmbsync\fR. If you were using \fBisync\fR version 0.8 or 0.9.x you might
+want to use \fBmdconvert\fR to convert the mailboxes to the more efficient
+\fBnative\fR UID storage scheme after migrating them.
+..
+.SH OPTIONS
+.TP
+\fB-c\fR, \fB--config\fR \fIfile\fR
+Read configuration from \fIfile\fR.
+By default, the configuration is read from ~/.isyncrc if it exists.
+.TP
+\fB-1\fR, \fB--one-to-one\fR
+Instead of using the mailbox specifications in ~/.isyncrc, isync will pick up
+all mailboxes from the local directory and remote folder and map them 1:1
+onto each other according to their names.
+.TP
+\fB-I\fR, \fB--inbox\fR \fImailbox\fR
+Exception to the 1:1 mapping created by -1: the special IMAP mailbox \fIINBOX\fR
+is mapped to the local \fImailbox\fR (relative to the maildir).
+.TP
+\fB-a\fR, \fB--all\fR
+Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the
+1:1 mapping).
+.TP
+\fB-l\fR, \fB--list\fR
+Don't synchronize anything, but list all mailboxes and exit.
+.TP
+\fB-L\fR, \fB--create-local\fR
+Automatically create the local Maildir mailbox if it doesn't already
+exist.
+.TP
+\fB-R\fR, \fB--create-remote\fR
+Automatically create the remote IMAP mailbox if it doesn't already exist.
+.TP
+\fB-C\fR, \fB--create\fR
+Automatically create any mailboxes if they don't already exist.
+This is simply a combination of -L and -R.
+.TP
+\fB-d\fR, \fB--delete\fR
+Causes \fBisync\fR to propagate message deletions.
+By default, \fIdead\fR messages are \fBnot\fR deleted.
+.TP
+\fB-e\fR, \fB--expunge\fR
+Causes \fBisync\fR to permanently remove all messages marked for deletion.
+By default, \fIdeleted\fR messages are \fBnot\fR expunged.
+.TP
+\fB-f\fR, \fB--fast\fR
+Only fetch new messages existing on the server into the local mailbox.
+Message deletions and flag changes will not be propagated.
+.TP
+\fB-h\fR, \fB--help\fR
+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 for imap,
+993 for imaps)
+.TP
+\fB-q\fR, \fB--quiet\fR
+Suppress informational messages.
+If specified twice, suppress warning messages as well.
+.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 [\fBimaps:\fR]\fIhost\fR
+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
+\fB-M\fR, \fB--maildir\fR \fIdir\fR
+Specifies the location for your local mailboxes.
+.TP
+\fB-F\fR, \fB--folder\fR \fIfolder\fR/
+Specifies the location for your remote mailboxes.
+.TP
+\fB-v\fR, \fB--version\fR
+Displays \fBisync\fR version information.
+.TP
+\fB-V\fR, \fB--verbose\fR
+Enables \fIverbose\fR mode, which displays the IMAP4 network traffic.
+.TP
+\fB-D\fR, \fB--debug\fR
+Enable printing of \fIdebug\fR messages.
+.TP
+\fB-w\fR, \fB--write\fR
+Don't run \fBmbsync\fR, but instead write a permanent config file for it.
+The UID mappings of all configured mailboxes will be migrated.
+Note that some command line options that would affect an actual sync operation
+will be incorporated into the new config file as well.
+The name of the new config file is determined by replacing the last occurrence
+of "isync" with "mbsync", or appending ".mbsync" if "isync" was not found.
+.TP
+\fB-W\fR, \fB--writeto\fR \fIfile\fR
+Like \fB-w\fR, but use the specified name for the new config file.
+..
+.SH CONFIGURATION
+\fBisync\fR by default reads \fI~/.isyncrc\fR to load configuration data.
+Each non-empty line of the configuration file that does not start with a
+hash mark 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 \fIMailbox\fR command, apply to this mailbox only.
+..
+.TP
+\fBHost\fR [\fBimaps:\fR]\fIname\fR
+Defines the DNS name or IP address of the IMAP server. If the hostname is
+prefixed with \fBimaps:\fR the connection is assumed to be a SSL connection
+to port 993 (though you can change this by placing a \fBPort\fR command
+\fBafter\fR the \fBHost\fR command).
+Note that modern servers support SSL on the default port 143.
+\fBisync\fR will always attempt to use SSL if available.
+..
+.TP
+\fBPort\fR \fIport\fR
+Defines the TCP port number of the IMAP server (Default: 143 for imap,
+993 for imaps)
+..
+.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 \fIusername\fR on the IMAP server.
+Note that this option is \fBNOT\fR required.
+If no password is specified in the configuration file, \fBisync\fR
+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
+\fBCopyDeletedTo\fR \fImailbox\fR
+Specifies the remote IMAP mailbox to copy deleted messages to prior to
+expunging (Default: none).
+..
+.TP
+\fBDelete\fR \fIyes\fR|\fIno\fR
+Specifies whether message deletions are propagated. (Default: no).
+\fBNOTE:\fR The \fI-d\fR command line option overrides this setting when
+set to \fIno\fR.
+..
+.TP
+\fBExpunge\fR \fIyes\fR|\fIno\fR
+Specifies whether deleted messages are expunged. (Default: no).
+\fBNOTE:\fR The \fI-e\fR command line option overrides this setting when
+set to \fIno\fR.
+..
+.TP
+\fBMailDir\fR \fIdirectory\fR
+Specifies the location of your local mailboxes if a relative path is
+specified in a \fIMailbox\fR command (Default: \fI~\fR).
+\fBNOTE:\fR This directive is allowed only in the \fIglobal\fR
+section (see below).
+..
+.TP
+\fBFolder\fR \fIdirectory\fR/
+Specifies the location of your IMAP mailboxes
+specified in \fIBox\fR commands (Default: \fI""\fR).
+\fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually
+a slash) to this specification.
+\fBNOTE 2:\fR This directive is allowed only in the \fIglobal\fR
+section (see below).
+..
+.TP
+\fBMaxMessages\fR \fIcount\fR
+Sets the number of messages \fBisync\fR should keep in the local copy of a
+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 recent messages will not be
+automatically deleted.
+If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR.
+(Default: 0)
+..
+.TP
+\fBMaxSize\fR \fIbytes\fR
+Messages larger than that many bytes will not be transferred over the wire.
+This is useful for weeding out messages with large attachments.
+If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR.
+(Default: 0)
+..
+.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
+\fBUseNamespace\fR \fIyes\fR|\fIno\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 \fIFolder\fR was specified.
+(Default: \fIyes\fR)
+..
+.TP
+\fBRequireCRAM\fR \fIyes\fR|\fIno\fR
+If set to \fIyes\fR, \fBisync\fR will abort the connection if no CRAM-MD5
+authentication is possible. (Default: \fIno\fR)
+..
+.TP
+\fBRequireSSL\fR \fIyes\fR|\fIno\fR
+\fBisync\fR will abort the connection if a TLS/SSL session cannot be
+established with the IMAP server. (Default: \fIyes\fR)
+..
+.TP
+\fBCertificateFile\fR \fIpath\fR
+File containing X.509 CA certificates used to verify server identities.
+..
+.TP
+\fBUseSSLv2\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use SSLv2 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseSSLv3\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use SSLv3 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseTLSv1\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR)
+..
+.TP
+\fBOneToOne\fR
+\fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up
+all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map
+them 1:1 onto each other according to their names.
+\fBNOTE:\fR This directive is allowed only in the \fIglobal\fR
+section (see below).
+..
+.TP
+\fBInbox\fR \fImailbox\fR
+Exception to the OneToOne mapping: the special IMAP mailbox \fIINBOX\fR
+is mapped to the local \fImailbox\fR (relative to the \fIMailDir\fR).
+\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
+section (see below).
+..
+.P
+Configuration commands that appear prior to the first \fBMailbox\fR
+command are considered to be \fIglobal\fR
+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 \fBUser\fR command before
+the first \fBMailbox\fR command, and then leave out the \fBUser\fR command
+in the sections for each mailbox.
+\fBisync\fR will then use the global value by default.
+..
+.SH FILES
+.TP
+.B ~/.isyncrc
+Default configuration file
+..
+.SH BUGS
+The configuration file takes precedence over command line options.
+.br
+Use -c /dev/null to work around.
+.P
+See the \fBINHERENT PROBLEMS\fR section in the \fBmbsync\fR man page, too.
+..
+.SH SEE ALSO
+mbsync(1), mdconvert(1), mutt(1), maildir(5)
+.P
+Up to date information on \fBisync\fR can be found at http://isync.sf.net/
+..
+.SH AUTHOR
+Written by Michael R. Elkins <me@mutt.org>,
+.br
+maintained by Oswald Buddenhagen <ossi@users.sf.net>.
diff --git a/src/compat/isync.h b/src/compat/isync.h
@@ -0,0 +1,100 @@
+/*
+ * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdarg.h>
+
+#define as(ar) (sizeof(ar)/sizeof(ar[0]))
+
+#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
+
+typedef struct config {
+ struct config *next;
+
+ const char *server_name;
+ int servers;
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+ char *tunnel;
+ unsigned int require_cram:1;
+ unsigned int require_ssl:1;
+ unsigned int use_imaps:1;
+ unsigned int use_sslv2:1;
+ unsigned int use_sslv3:1;
+ unsigned int use_tlsv1:1;
+ char *cert_file;
+
+ const char *store_name;
+ int stores;
+ char *copy_deleted_to;
+ unsigned int use_namespace:1;
+
+ const char *channel_name;
+ int channels;
+ const char *alias;
+ const char *box;
+ const char *path; /* path relative to .maildir, or absolute path */
+ int max_size;
+ unsigned int max_messages;
+ unsigned int expunge:1;
+ unsigned int delete:1;
+} config_t;
+
+extern int Quiet, Verbose, Debug;
+
+extern const char *Home;
+
+extern config_t global, *boxes;
+
+extern const char *maildir, *xmaildir, *folder, *inbox;
+extern int o2o, altmap;
+
+/* config.c */
+void load_config( const char *, config_t *** );
+void write_config( int );
+char *expand_strdup( const char * );
+config_t *find_box( const char * );
+
+/* convert.c */
+void convert( config_t * );
+
+/* util.c */
+char *next_arg( char ** );
+void *nfmalloc( size_t sz );
+void *nfrealloc( void *mem, size_t sz );
+char *nfstrdup( const char *str );
+int nfvasprintf( char **str, const char *fmt, va_list va );
+int nfasprintf( char **str, const char *fmt, ... );
+int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+void ATTR_NORETURN oob( void );
diff --git a/isyncrc.sample b/src/compat/isyncrc.sample
diff --git a/src/compat/main.c b/src/compat/main.c
@@ -0,0 +1,431 @@
+/*
+ * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "isync.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <dirent.h>
+
+#if HAVE_GETOPT_LONG
+# define _GNU_SOURCE
+# include <getopt.h>
+struct option Opts[] = {
+ {"write", 0, NULL, 'w' },
+ {"writeto", 0, NULL, 'W' },
+ {"all", 0, NULL, 'a' },
+ {"list", 0, NULL, 'l'},
+ {"config", 1, NULL, 'c'},
+ {"create", 0, NULL, 'C'},
+ {"create-local", 0, NULL, 'L'},
+ {"create-remote", 0, NULL, 'R'},
+ {"delete", 0, NULL, 'd'},
+ {"expunge", 0, NULL, 'e'},
+ {"fast", 0, NULL, 'f'},
+ {"help", 0, NULL, 'h'},
+ {"remote", 1, NULL, 'r'},
+ {"folder", 1, NULL, 'F'},
+ {"maildir", 1, NULL, 'M'},
+ {"one-to-one", 0, NULL, '1'},
+ {"inbox", 1, NULL, 'I'},
+ {"host", 1, NULL, 's'},
+ {"port", 1, NULL, 'p'},
+ {"debug", 0, NULL, 'D'},
+ {"quiet", 0, NULL, 'q'},
+ {"user", 1, NULL, 'u'},
+ {"version", 0, NULL, 'v'},
+ {"verbose", 0, NULL, 'V'},
+ {0, 0, 0, 0}
+};
+#endif
+
+static void
+version( void )
+{
+ puts( PACKAGE " " VERSION );
+ exit( 0 );
+}
+
+static void
+usage( int code )
+{
+ fputs(
+PACKAGE " " VERSION " - mbsync wrapper: IMAP4 to maildir synchronizer\n"
+"Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>\n"
+"Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>\n"
+"usage:\n"
+" " PACKAGE " [ flags ] mailbox [mailbox ...]\n"
+" " PACKAGE " [ flags ] -a\n"
+" " PACKAGE " [ flags ] -l\n"
+" -a, --all synchronize all defined mailboxes\n"
+" -l, --list list all defined mailboxes and exit\n"
+" -L, --create-local create local maildir mailbox if nonexistent\n"
+" -R, --create-remote create remote imap mailbox if nonexistent\n"
+" -C, --create create both local and remote mailboxes if nonexistent\n"
+" -d, --delete delete local msgs that don't exist on the server\n"
+" -e, --expunge expunge deleted messages\n"
+" -f, --fast only fetch new messages\n"
+" -r, --remote BOX remote mailbox\n"
+" -F, --folder DIR remote IMAP folder containing mailboxes\n"
+" -M, --maildir DIR local directory containing mailboxes\n"
+" -1, --one-to-one map every IMAP <folder>/box to <maildir>/box\n"
+" -I, --inbox BOX map IMAP INBOX to <maildir>/BOX (exception to -1)\n"
+" -s, --host HOST IMAP server address\n"
+" -p, --port PORT server IMAP port\n"
+" -u, --user USER IMAP user name\n"
+" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\n"
+" -D, --debug print debugging messages\n"
+" -V, --verbose verbose mode (display network traffic)\n"
+" -q, --quiet don't display progress info\n"
+" -v, --version display version\n"
+" -h, --help display this help message\n\n"
+"Note that this is a wrapper binary only; the \"real\" isync is named \"mbsync\".\n"
+"Options to permanently transform your old isync configuration:\n"
+" -w, --write write permanent mbsync configuration\n"
+" -W, --writeto FILE write permanent mbsync configuration to FILE\n",
+ code ? stderr : stdout );
+ exit( code );
+}
+
+static const char *
+strrstr( const char *h, const char *n )
+{
+ char *p = strstr( h, n );
+ if (!p)
+ return 0;
+ do {
+ h = p;
+ p = strstr( h + 1, n );
+ } while (p);
+ return h;
+}
+
+static void
+add_arg( char ***args, const char *arg )
+{
+ int nu = 0;
+ if (*args)
+ for (; (*args)[nu]; nu++);
+ *args = nfrealloc( *args, sizeof(char *) * (nu + 2));
+ (*args)[nu] = nfstrdup( arg );
+ (*args)[nu + 1] = 0;
+}
+
+#define OP_EXPUNGE (1<<0)
+#define OP_DELETE (1<<1)
+#define OP_FAST (1<<2)
+#define OP_CREATE_REMOTE (1<<3)
+#define OP_CREATE_LOCAL (1<<4)
+
+int Quiet, Verbose, Debug;
+config_t global, *boxes;
+const char *maildir, *xmaildir, *folder, *inbox;
+int o2o, altmap;
+
+const char *Home;
+
+int
+main( int argc, char **argv )
+{
+ config_t *box, **stor;
+ char *config = 0, *outconfig = 0, **args;
+ int i, pl, fd, mod, all, list, ops, writeout;
+ struct stat st;
+ char path1[_POSIX_PATH_MAX], path2[_POSIX_PATH_MAX];
+
+ if (!(Home = getenv("HOME"))) {
+ fputs( "Fatal: $HOME not set\n", stderr );
+ return 1;
+ }
+
+ /* defaults */
+ /* XXX the precedence is borked:
+ it's defaults < cmdline < file instead of defaults < file < cmdline */
+ global.port = 143;
+ global.box = "INBOX";
+ global.use_namespace = 1;
+ global.require_ssl = 1;
+ global.use_tlsv1 = 1;
+ folder = "";
+ maildir = "~";
+ xmaildir = Home;
+
+#define FLAGS "wW:alCLRc:defhp:qu:r:F:M:1I:s:vVD"
+
+ mod = all = list = ops = writeout = Quiet = Verbose = Debug = 0;
+#if HAVE_GETOPT_LONG
+ while ((i = getopt_long( argc, argv, FLAGS, Opts, NULL )) != -1)
+#else
+ while ((i = getopt( argc, argv, FLAGS )) != -1)
+#endif
+ {
+ switch (i) {
+ case 'W':
+ outconfig = optarg;
+ /* plopp */
+ case 'w':
+ writeout = 1;
+ break;
+ case 'l':
+ list = 1;
+ /* plopp */
+ case 'a':
+ all = 1;
+ break;
+ case '1':
+ o2o = 1;
+ mod = 1;
+ break;
+ case 'C':
+ ops |= OP_CREATE_REMOTE|OP_CREATE_LOCAL;
+ break;
+ case 'L':
+ ops |= OP_CREATE_LOCAL;
+ break;
+ case 'R':
+ ops |= OP_CREATE_REMOTE;
+ break;
+ case 'c':
+ config = optarg;
+ break;
+ case 'd':
+ ops |= OP_DELETE;
+ break;
+ case 'e':
+ ops |= OP_EXPUNGE;
+ break;
+ case 'f':
+ ops |= OP_FAST;
+ break;
+ case 'p':
+ global.port = atoi( optarg );
+ mod = 1;
+ break;
+ case 'r':
+ global.box = optarg;
+ mod = 1;
+ break;
+ case 'F':
+ folder = optarg;
+ mod = 1;
+ break;
+ case 'M':
+ maildir = optarg;
+ mod = 1;
+ break;
+ case 'I':
+ inbox = optarg;
+ mod = 1;
+ break;
+ case 's':
+#if HAVE_LIBSSL
+ if (!strncasecmp( "imaps:", optarg, 6 )) {
+ global.use_imaps = 1;
+ global.port = 993;
+ global.use_sslv2 = 1;
+ global.use_sslv3 = 1;
+ optarg += 6;
+ }
+#endif
+ global.host = optarg;
+ mod = 1;
+ break;
+ case 'u':
+ global.user = optarg;
+ mod = 1;
+ break;
+ case 'D':
+ Debug = 1;
+ break;
+ case 'V':
+ Verbose = 1;
+ break;
+ case 'q':
+ Quiet++;
+ break;
+ case 'v':
+ version();
+ case 'h':
+ usage( 0 );
+ default:
+ usage( 1 );
+ }
+ }
+
+ if (config) {
+ if (*config != '/') {
+ if (!getcwd( path1, sizeof(path1) )) {
+ fprintf( stderr, "Can't obtain working directory\n" );
+ return 1;
+ }
+ pl = strlen( path1 );
+ nfsnprintf( path1 + pl, sizeof(path1) - pl, "/%s", config );
+ config = path1;
+ }
+ } else {
+ nfsnprintf( path1, sizeof(path1), "%s/.isyncrc", Home );
+ config = path1;
+ }
+ stor = &boxes;
+ load_config( config, &stor );
+
+ if (!all && !o2o)
+ for (i = optind; argv[i]; i++)
+ if (!(box = find_box( argv[i] ))) {
+ box = nfmalloc( sizeof(config_t) );
+ memcpy( box, &global, sizeof(config_t) );
+ box->path = argv[i];
+ *stor = box;
+ stor = &box->next;
+ mod = 1;
+ }
+
+ if (writeout) {
+ all = 1;
+ if (mod)
+ fprintf( stderr,
+ "Warning: command line switches that influence the resulting config file\n"
+ "have been supplied.\n" );
+ } else {
+ if (!argv[optind] && !all) {
+ fprintf( stderr, "No mailbox specified. Try isync -h\n" );
+ return 1;
+ }
+ }
+
+ if (all) {
+ if (o2o) {
+ DIR * dir;
+ struct dirent *de;
+
+ if (!(dir = opendir( xmaildir ))) {
+ fprintf( stderr, "%s: %s\n", xmaildir, strerror(errno) );
+ return 1;
+ }
+ while ((de = readdir( dir ))) {
+ if (*de->d_name == '.')
+ continue;
+ nfsnprintf( path1, sizeof(path1), "%s/%s/cur", xmaildir, de->d_name );
+ if (stat( path1, &st ) || !S_ISDIR( st.st_mode ))
+ continue;
+ global.path = de->d_name;
+ global.box = (inbox && !strcmp( inbox, global.path )) ?
+ "INBOX" : global.path;
+ convert( &global );
+ }
+ closedir( dir );
+ } else
+ for (box = boxes; box; box = box->next)
+ convert( box );
+ } else {
+ for (i = optind; argv[i]; i++)
+ if (o2o) {
+ global.path = argv[i];
+ global.box =
+ (inbox && !strcmp( global.path, inbox )) ?
+ "INBOX" : global.path;
+ convert( &global );
+ } else
+ convert( find_box( argv[i] ) );
+ }
+
+ if (writeout) {
+ if (!outconfig) {
+ const char *p = strrchr( config, '/' );
+ if (!p)
+ p = config;
+ p = strrstr( p, "isync" );
+ if (!p)
+ nfsnprintf( path2, sizeof(path2), "%s.mbsync", config );
+ else
+ nfsnprintf( path2, sizeof(path2), "%.*smb%s", p - config, config, p + 1 );
+ outconfig = path2;
+ }
+ if ((fd = creat( outconfig, 0666 )) < 0) {
+ fprintf( stderr, "Error: cannot write new config %s: %s\n", outconfig, strerror(errno) );
+ return 1;
+ }
+ } else {
+ strcpy( path2, "/tmp/mbsyncrcXXXXXX" );
+ if ((fd = mkstemp( path2 )) < 0) {
+ fprintf( stderr, "Can't create temp file\n" );
+ return 1;
+ }
+ }
+ write_config( fd );
+
+ if (writeout)
+ return 0;
+ args = 0;
+ add_arg( &args, "mbsync" );
+ if (Verbose)
+ add_arg( &args, "-V" );
+ if (Debug)
+ add_arg( &args, "-D" );
+ for (; Quiet; Quiet--)
+ add_arg( &args, "-q" );
+ add_arg( &args, "-cT" );
+ add_arg( &args, path2 );
+ if (ops & OP_FAST)
+ add_arg( &args, "-Ln" );
+ else if (!(ops & OP_DELETE))
+ add_arg( &args, "-nNf" );
+ if (ops & OP_CREATE_REMOTE)
+ add_arg( &args, "-Cm" );
+ if (ops & OP_CREATE_LOCAL)
+ add_arg( &args, "-Cs" );
+ if (ops & OP_EXPUNGE)
+ add_arg( &args, "-X" );
+ if (list)
+ add_arg( &args, "-l" );
+ if (o2o) {
+ if (all)
+ add_arg( &args, "o2o" );
+ else {
+ char buf[1024];
+ strcpy( buf, "o2o:" );
+ strcat( buf, argv[optind] );
+ while (argv[++optind]) {
+ strcat( buf, "," );
+ strcat( buf, argv[optind] );
+ }
+ add_arg( &args, buf );
+ }
+ } else {
+ if (all)
+ add_arg( &args, "-a" );
+ else
+ for (; argv[optind]; optind++)
+ add_arg( &args, find_box( argv[optind] )->channel_name );
+ }
+ execvp( args[0], args );
+ perror( args[0] );
+ return 1;
+}
diff --git a/src/compat/util.c b/src/compat/util.c
@@ -0,0 +1,156 @@
+/*
+ * isync - mbsync wrapper: IMAP4 to maildir mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "isync.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <pwd.h>
+#include <ctype.h>
+
+char *
+next_arg( char **s )
+{
+ char *ret;
+
+ if (!s || !*s)
+ return 0;
+ while (isspace( (unsigned char) **s ))
+ (*s)++;
+ if (!**s) {
+ *s = 0;
+ return 0;
+ }
+ if (**s == '"') {
+ ++*s;
+ ret = *s;
+ *s = strchr( *s, '"' );
+ } else {
+ ret = *s;
+ while (**s && !isspace( (unsigned char) **s ))
+ (*s)++;
+ }
+ if (*s) {
+ if (**s)
+ *(*s)++ = 0;
+ if (!**s)
+ *s = 0;
+ }
+ return ret;
+}
+
+#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 = 0;
+ else if ((*strp = malloc( len + 1 ))) {
+ if (len >= sizeof(tmp))
+ vsprintf( *strp, fmt, ap );
+ else
+ memcpy( *strp, tmp, len + 1 );
+ }
+ return len;
+}
+#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 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
+ oob();
+ va_end( va );
+ return ret;
+}
+
+static void ATTR_NORETURN
+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 *
+nfrealloc( void *mem, size_t sz )
+{
+ char *ret;
+
+ if (!(ret = realloc( mem, sz )) && sz)
+ oom();
+ return ret;
+}
+
+char *
+nfstrdup( const char *str )
+{
+ char *ret;
+
+ if (!(ret = strdup( str )))
+ oom();
+ return ret;
+}
+
+int
+nfvasprintf( char **str, const char *fmt, va_list va )
+{
+ int ret = vasprintf( str, fmt, va );
+ if (!*str)
+ 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;
+}
diff --git a/src/config.c b/src/config.c
@@ -1,8 +1,7 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
+/*
+ * mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 2002-2003 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2002-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
@@ -18,7 +17,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
- * As a special exception, isync may be linked with the OpenSSL library,
+ * As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
@@ -33,254 +32,411 @@
#include <stdlib.h>
#include <stdio.h>
-config_t *boxes = 0;
+store_conf_t *stores;
+channel_conf_t *channels;
+group_conf_t *groups;
+int global_mops, global_sops;
+char *global_sync_state;
-/* set defaults from the global configuration section */
-static void
-config_defaults (config_t * conf)
+int
+parse_bool( conffile_t *cfile )
{
- memcpy (conf, &global, sizeof (config_t));
+ 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" ))
+ fprintf( stderr, "%s:%d: invalid boolean value '%s'\n",
+ cfile->file, cfile->line, cfile->val );
+ return 0;
}
-static char *
-my_strndup (const char *s, size_t nchars)
+int
+parse_int( conffile_t *cfile )
{
- char *r = malloc (sizeof (char) * (nchars + 1));
- if (r)
- {
- memcpy (r, s, nchars);
- r[nchars] = 0;
- }
- return r;
+ char *p;
+ int ret;
+
+ ret = strtol( cfile->val, &p, 10 );
+ if (*p) {
+ fprintf( stderr, "%s:%d: invalid integer value '%s'\n",
+ cfile->file, cfile->line, cfile->val );
+ return 0;
+ }
+ return ret;
}
-char *
-expand_strdup (const char *s)
+int
+parse_size( conffile_t *cfile )
{
- char path[_POSIX_PATH_MAX];
- struct passwd *pw;
- const char *p;
+ char *p;
+ int ret;
- if (*s == '~')
- {
- s++;
- if (*s == '/')
- {
- /* current user */
- pw = getpwuid (getuid ());
- p = s + 1;
- }
- else
- {
- char *user;
-
- p = strchr (s, '/');
- if (p)
- {
- user = my_strndup (s, (int)(p - s));
+ ret = strtol (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++;
- }
- else
- user = strdup (s);
- pw = getpwnam (user);
- free (user);
+ if (*p) {
+ fprintf (stderr, "%s:%d: invalid size '%s'\n",
+ cfile->file, cfile->line, cfile->val);
+ return 0;
}
- if (!pw)
- return 0;
- snprintf (path, sizeof (path), "%s/%s", pw->pw_dir, p ? p : "");
- s = path;
- }
- else if (*s != '/')
- {
- snprintf (path, sizeof (path), "%s/%s",
- global.maildir ? global.maildir : "", s);
- s = path;
- }
- return strdup (s);
+ return ret;
}
-static int
-is_true (const char *val)
+static int
+getopt_helper( conffile_t *cfile, int *cops, int *mops, int *sops, char **sync_state )
{
- return
- !strcasecmp (val, "yes") ||
- !strcasecmp (val, "true") ||
- !strcasecmp (val, "on") ||
- !strcmp (val, "1");
+ char *arg;
+
+ 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 ))
+ *sops |= OP_RENEW;
+ else if (!strcasecmp( "PullNew", arg ))
+ *sops |= OP_NEW;
+ else if (!strcasecmp( "PullDelete", arg ))
+ *sops |= OP_DELETE;
+ else if (!strcasecmp( "PullFlags", arg ))
+ *sops |= OP_FLAGS;
+ else if (!strcasecmp( "PushReNew", arg ))
+ *mops |= OP_RENEW;
+ else if (!strcasecmp( "PushNew", arg ))
+ *mops |= OP_NEW;
+ else if (!strcasecmp( "PushDelete", arg ))
+ *mops |= OP_DELETE;
+ else if (!strcasecmp( "PushFlags", arg ))
+ *mops |= OP_FLAGS;
+ else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
+ *cops |= XOP_PULL|XOP_PUSH;
+ else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg ))
+ fprintf( stderr, "%s:%d: invalid Sync arg '%s'\n",
+ cfile->file, cfile->line, arg );
+ while ((arg = next_arg( &cfile->rest )));
+ *mops |= XOP_HAVE_TYPE;
+ } else if (!strcasecmp( "Expunge", cfile->cmd )) {
+ arg = cfile->val;
+ do
+ if (!strcasecmp( "Both", arg ))
+ *cops |= OP_EXPUNGE;
+ else if (!strcasecmp( "Master", arg ))
+ *mops |= OP_EXPUNGE;
+ else if (!strcasecmp( "Slave", arg ))
+ *sops |= OP_EXPUNGE;
+ else if (strcasecmp( "None", arg ))
+ fprintf( stderr, "%s:%d: invalid Expunge arg '%s'\n",
+ cfile->file, cfile->line, arg );
+ while ((arg = next_arg( &cfile->rest )));
+ *mops |= XOP_HAVE_EXPUNGE;
+ } else if (!strcasecmp( "Create", cfile->cmd )) {
+ arg = cfile->val;
+ do
+ if (!strcasecmp( "Both", arg ))
+ *cops |= OP_CREATE;
+ else if (!strcasecmp( "Master", arg ))
+ *mops |= OP_CREATE;
+ else if (!strcasecmp( "Slave", arg ))
+ *sops |= OP_CREATE;
+ else if (strcasecmp( "None", arg ))
+ fprintf( stderr, "%s:%d: invalid Create arg '%s'\n",
+ cfile->file, cfile->line, arg );
+ while ((arg = next_arg( &cfile->rest )));
+ *mops |= XOP_HAVE_CREATE;
+ } else if (!strcasecmp( "SyncState", cfile->cmd ))
+ *sync_state = expand_strdup( cfile->val );
+ else
+ return 0;
+ return 1;
}
-void
-load_config (const char *where, int *o2o)
+int
+getcline( conffile_t *cfile )
{
- char path[_POSIX_PATH_MAX];
- char buf[1024];
- struct passwd *pw;
- config_t **stor = &boxes, *cfg;
- int line = 0;
- FILE *fp;
- char *p, *cmd, *val;
+ char *p;
- if (!where)
- {
- pw = getpwuid (getuid ());
- snprintf (path, sizeof (path), "%s/.isyncrc", pw->pw_dir);
- where = path;
- }
+ while (fgets( cfile->buf, cfile->bufl, cfile->fp )) {
+ cfile->line++;
+ p = cfile->buf;
+ if (!(cfile->cmd = next_arg( &p )))
+ return 1;
+ if (*cfile->cmd == '#')
+ continue;
+ if (!(cfile->val = next_arg( &p ))) {
+ fprintf( stderr, "%s:%d: parameter missing\n",
+ cfile->file, cfile->line );
+ continue;
+ }
+ cfile->rest = p;
+ return 1;
+ }
+ return 0;
+}
- info ("Reading configuration file %s\n", where);
+/* XXX - this does not detect None conflicts ... */
+int
+merge_ops( int cops, int *mops, int *sops )
+{
+ int aops;
- fp = fopen (where, "r");
- if (!fp)
- {
- if (errno != ENOENT)
- perror ("fopen");
- return;
- }
- buf[sizeof buf - 1] = 0;
- cfg = &global;
- while ((fgets (buf, sizeof (buf) - 1, fp)))
- {
- p = buf;
- cmd = next_arg (&p);
- val = next_arg (&p);
- line++;
- if (!cmd || *cmd == '#')
- continue;
- if (!val) {
- fprintf (stderr, "%s:%d: parameter missing\n", path, line);
- continue;
- }
- if (!strcasecmp ("mailbox", cmd))
- {
- if (*o2o)
- break;
- cfg = *stor = malloc (sizeof (config_t));
- stor = &cfg->next;
- config_defaults (cfg);
- /* not expanded at this point */
- cfg->path = strdup (val);
- }
- else if (!strcasecmp ("OneToOne", cmd))
- {
- if (boxes) {
- forbid:
- fprintf (stderr,
- "%s:%d: keyword '%s' allowed only in global section\n",
- path, line, cmd);
- continue;
- }
- *o2o = is_true (val);
+ aops = *mops | *sops;
+ if (*mops & XOP_HAVE_TYPE) {
+ if (aops & OP_MASK_TYPE) {
+ if (aops & cops & OP_MASK_TYPE) {
+ cfl:
+ fprintf( stderr, "Conflicting Sync args specified.\n" );
+ return 1;
+ }
+ *mops |= cops & OP_MASK_TYPE;
+ *sops |= cops & OP_MASK_TYPE;
+ if (cops & XOP_PULL) {
+ if (*sops & OP_MASK_TYPE)
+ goto cfl;
+ *sops |= OP_MASK_TYPE;
+ }
+ if (cops & XOP_PUSH) {
+ if (*mops & OP_MASK_TYPE)
+ goto cfl;
+ *mops |= 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)
+ *sops |= cops & OP_MASK_TYPE;
+ if (cops & XOP_PUSH)
+ *mops |= cops & OP_MASK_TYPE;
+ }
}
- else if (!strcasecmp ("maildir", cmd))
- {
- if (boxes)
- goto forbid;
- /* this only affects the global setting */
- global.maildir = expand_strdup (val);
+ if (*mops & XOP_HAVE_EXPUNGE) {
+ if (aops & cops & OP_EXPUNGE) {
+ fprintf( stderr, "Conflicting Expunge args specified.\n" );
+ return 1;
+ }
+ *mops |= cops & OP_EXPUNGE;
+ *sops |= cops & OP_EXPUNGE;
}
- else if (!strcasecmp ("folder", cmd))
- {
- if (boxes)
- goto forbid;
- /* this only affects the global setting */
- global.folder = strdup (val);
+ if (*mops & XOP_HAVE_CREATE) {
+ if (aops & cops & OP_CREATE) {
+ fprintf( stderr, "Conflicting Create args specified.\n" );
+ return 1;
+ }
+ *mops |= cops & OP_CREATE;
+ *sops |= cops & OP_CREATE;
}
- else if (!strcasecmp ("inbox", cmd))
- {
- if (boxes)
- goto forbid;
- /* this only affects the global setting */
- global.inbox = strdup (val);
- }
- else if (!strcasecmp ("host", cmd))
- {
-#if HAVE_LIBSSL
- if (!strncasecmp ("imaps:", val, 6))
- {
- val += 6;
- cfg->use_imaps = 1;
- cfg->port = 993;
- cfg->use_sslv2 = 1;
- cfg->use_sslv3 = 1;
- }
-#endif
- cfg->host = strdup (val);
- }
- else if (!strcasecmp ("user", cmd))
- cfg->user = strdup (val);
- else if (!strcasecmp ("pass", cmd))
- cfg->pass = strdup (val);
- else if (!strcasecmp ("port", cmd))
- cfg->port = atoi (val);
- else if (!strcasecmp ("box", cmd))
- cfg->box = strdup (val);
- else if (!strcasecmp ("alias", cmd))
- {
- if (!boxes) {
- fprintf (stderr,
- "%s:%d: keyword 'alias' allowed only in mailbox specification\n",
- path, line);
- continue;
- }
- cfg->alias = strdup (val);
- }
- else if (!strcasecmp ("maxsize", cmd))
- cfg->max_size = atol (val);
- else if (!strcasecmp ("MaxMessages", cmd))
- cfg->max_messages = atol (val);
- else if (!strcasecmp ("UseNamespace", cmd))
- cfg->use_namespace = is_true (val);
- else if (!strcasecmp ("CopyDeletedTo", cmd))
- cfg->copy_deleted_to = strdup (val);
- else if (!strcasecmp ("Tunnel", cmd))
- cfg->tunnel = strdup (val);
- else if (!strcasecmp ("Expunge", cmd))
- cfg->expunge = is_true (val);
- else if (!strcasecmp ("Delete", cmd))
- cfg->delete = is_true (val);
-#if HAVE_LIBSSL
- else if (!strcasecmp ("CertificateFile", cmd))
- cfg->cert_file = expand_strdup (val);
- else if (!strcasecmp ("RequireSSL", cmd))
- cfg->require_ssl = is_true (val);
- else if (!strcasecmp ("UseSSLv2", cmd))
- cfg->use_sslv2 = is_true (val);
- else if (!strcasecmp ("UseSSLv3", cmd))
- cfg->use_sslv3 = is_true (val);
- else if (!strcasecmp ("UseTLSv1", cmd))
- cfg->use_tlsv1 = is_true (val);
- else if (!strcasecmp ("RequireCRAM", cmd))
- cfg->require_cram = is_true (val);
-#endif
- else if (buf[0])
- fprintf (stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd);
- }
- fclose (fp);
+ return 0;
}
-config_t *
-find_box (const char *s)
+int
+load_config( const char *where, int pseudo )
{
- config_t *p = boxes;
+ conffile_t cfile;
+ store_conf_t *store, **storeapp = &stores, **sptarg;
+ channel_conf_t *channel, **channelapp = &channels;
+ group_conf_t *group, **groupapp = &groups;
+ string_list_t *chanlist, **chanlistapp;
+ char *arg, *p, **ntarg;
+ int err, len, cops, gcops, max_size;
+ char path[_POSIX_PATH_MAX];
+ char buf[1024];
- for (; p; p = p->next)
- {
- if (!strcmp (s, p->path) || (p->alias && !strcmp (s, p->alias)))
- return p;
- else
- {
- /* check to see if the full pathname was specified on the
- * command line.
- */
- char *t = expand_strdup (p->path);
+ if (!where) {
+ nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
+ cfile.file = path;
+ } else
+ cfile.file = where;
+
+ if (!pseudo)
+ info( "Reading configuration file %s\n", cfile.file );
+
+ if (!(cfile.fp = fopen( cfile.file, "r" ))) {
+ perror( "Cannot open config file" );
+ return 1;
+ }
+ buf[sizeof(buf) - 1] = 0;
+ cfile.buf = buf;
+ cfile.bufl = sizeof(buf) - 1;
+ cfile.line = 0;
+
+ gcops = err = 0;
+ reloop:
+ while (getcline( &cfile )) {
+ if (!cfile.cmd)
+ continue;
+ if (imap_driver.parse_store( &cfile, &store, &err ) ||
+ maildir_driver.parse_store( &cfile, &store, &err ))
+ {
+ if (store) {
+ if (!store->path)
+ store->path = "";
+ *storeapp = store;
+ storeapp = &store->next;
+ *storeapp = 0;
+ }
+ }
+ else if (!strcasecmp( "Channel", cfile.cmd ))
+ {
+ channel = nfcalloc( sizeof(*channel) );
+ channel->name = nfstrdup( cfile.val );
+ cops = 0;
+ max_size = -1;
+ while (getcline( &cfile ) && cfile.cmd) {
+ if (!strcasecmp( "MaxSize", cfile.cmd ))
+ max_size = parse_size( &cfile );
+ else if (!strcasecmp( "MaxMessages", cfile.cmd ))
+ channel->max_messages = parse_int( &cfile );
+ else if (!strcasecmp( "Pattern", cfile.cmd ) ||
+ !strcasecmp( "Patterns", cfile.cmd ))
+ {
+ arg = cfile.val;
+ do
+ add_string_list( &channel->patterns, arg );
+ while ((arg = next_arg( &cfile.rest )));
+ }
+ else if (!strcasecmp( "Master", cfile.cmd )) {
+ sptarg = &channel->master;
+ ntarg = &channel->master_name;
+ goto linkst;
+ } else if (!strcasecmp( "Slave", cfile.cmd )) {
+ sptarg = &channel->slave;
+ ntarg = &channel->slave_name;
+ linkst:
+ if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
+ fprintf( stderr, "%s:%d: malformed mailbox spec\n",
+ cfile.file, cfile.line );
+ err = 1;
+ continue;
+ }
+ *p = 0;
+ for (store = stores; store; store = store->next)
+ if (!strcmp( store->name, cfile.val + 1 )) {
+ memset( sptarg, 0, sizeof(*sptarg) );
+ *sptarg = store;
+ goto stpcom;
+ }
+ fprintf( stderr, "%s:%d: unknown store '%s'\n",
+ cfile.file, cfile.line, cfile.val + 1 );
+ err = 1;
+ continue;
+ stpcom:
+ if (*++p)
+ *ntarg = nfstrdup( p );
+ } else if (!getopt_helper( &cfile, &cops, &channel->mops, &channel->sops, &channel->sync_state )) {
+ fprintf( stderr, "%s:%d: unknown keyword '%s'\n",
+ cfile.file, cfile.line, cfile.cmd );
+ err = 1;
+ }
+ }
+ if (!channel->master) {
+ fprintf( stderr, "channel '%s' refers to no master store\n", channel->name );
+ err = 1;
+ } else if (!channel->slave) {
+ fprintf( stderr, "channel '%s' refers to no slave store\n", channel->name );
+ err = 1;
+ } else if (merge_ops( cops, &channel->mops, &channel->sops ))
+ err = 1;
+ else {
+ if (max_size >= 0)
+ channel->master->max_size = channel->slave->max_size = max_size;
+ *channelapp = channel;
+ channelapp = &channel->next;
+ }
+ }
+ else if (!strcasecmp( "Group", cfile.cmd ))
+ {
+ group = nfmalloc( sizeof(*group) );
+ group->name = nfstrdup( cfile.val );
+ *groupapp = group;
+ groupapp = &group->next;
+ *groupapp = 0;
+ chanlistapp = &group->channels;
+ *chanlistapp = 0;
+ p = cfile.rest;
+ while ((arg = next_arg( &p ))) {
+ addone:
+ len = strlen( arg );
+ chanlist = nfmalloc( sizeof(*chanlist) + len );
+ memcpy( chanlist->string, arg, len + 1 );
+ *chanlistapp = chanlist;
+ chanlistapp = &chanlist->next;
+ *chanlistapp = 0;
+ }
+ while (getcline( &cfile )) {
+ if (!cfile.cmd)
+ goto reloop;
+ if (!strcasecmp( "Channel", cfile.cmd ) ||
+ !strcasecmp( "Channels", cfile.cmd ))
+ {
+ p = cfile.rest;
+ arg = cfile.val;
+ goto addone;
+ }
+ else
+ {
+ fprintf( stderr, "%s:%d: unknown keyword '%s'\n",
+ cfile.file, cfile.line, cfile.cmd );
+ err = 1;
+ }
+ }
+ break;
+ }
+ else if (!getopt_helper( &cfile, &gcops, &global_mops, &global_sops, &global_sync_state ))
+ {
+ fprintf( stderr, "%s:%d: unknown section keyword '%s'\n",
+ cfile.file, cfile.line, cfile.cmd );
+ err = 1;
+ while (getcline( &cfile ))
+ if (!cfile.cmd)
+ goto reloop;
+ break;
+ }
+ }
+ fclose (cfile.fp);
+ err |= merge_ops( gcops, &global_mops, &global_sops );
+ if (!global_sync_state)
+ global_sync_state = expand_strdup( "~/." EXE "/" );
+ if (!err && pseudo)
+ unlink( where );
+ return err;
+}
- if (!strcmp (s, t))
- {
- free (t);
- return p;
- }
- free (t);
+void
+parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err )
+{
+ 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 {
+ fprintf( stderr, "%s:%d: unknown keyword '%s'\n",
+ cfg->file, cfg->line, cfg->cmd );
+ *err = 1;
}
- }
- return 0;
}
diff --git a/src/cram.c b/src/cram.c
@@ -1,89 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2000-2002 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
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-#include <config.h>
-
-#if HAVE_LIBSSL
-
-#include "isync.h"
-
-#include <assert.h>
-#include <string.h>
-#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));
-
- 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);
-
- 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
diff --git a/src/dotlock.c b/src/dotlock.c
@@ -1,102 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2002 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
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-/*
- * this file contains routines to establish a mutex using a `dotlock' file
- */
-
-#include "dotlock.h"
-
-#include <unistd.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#if TESTING
-#include <stdio.h>
-#endif
-
-int dotlock_lock (const char *path, int *fd)
-{
- struct flock lck;
-
- *fd = open (path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
- if (*fd == -1)
- return -1;
- memset (&lck, 0, sizeof(lck));
-#if SEEK_SET != 0
- lck.l_whence = SEEK_SET;
-#endif
-#if F_WRLCK != 0
- lck.l_type = F_WRLCK;
-#endif
- if (fcntl (*fd, F_SETLK, &lck))
- {
- close (*fd);
- *fd = -1;
- return -1;
- }
- return 0;
-}
-
-int dotlock_unlock (int *fd)
-{
- int r = 0;
- struct flock lck;
-
- if (*fd != -1)
- {
- memset (&lck, 0, sizeof(lck));
-#if SEEK_SET != 0
- lck.l_whence = SEEK_SET;
-#endif
-#if F_UNLCK != 0
- lck.l_type = F_UNLCK;
-#endif
- if (fcntl (*fd, F_SETLK, &lck))
- r = -1;
- close (*fd);
- *fd = -1;
- }
- return r;
-}
-
-#if TESTING
-int main (void)
-{
- int fd;
-
- if (dotlock_lock ("./lock", &fd))
- {
- perror ("dotlock_lock");
- goto done;
- }
- puts ("sleeping for 5 seconds");
- sleep(5);
- if (dotlock_unlock (&fd))
- {
- perror ("dotlock_unlock");
- }
-done:
- exit (0);
-}
-#endif
diff --git a/src/dotlock.h b/src/dotlock.h
@@ -1,25 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2002 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
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-int dotlock_lock (const char *, int *);
-int dotlock_unlock (int *);
diff --git a/src/drv_imap.c b/src/drv_imap.c
@@ -0,0 +1,1817 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2004 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#if HAVE_LIBSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/hmac.h>
+#endif
+
+typedef struct imap_server_conf {
+ struct imap_server_conf *next;
+ char *name;
+ char *tunnel;
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+#if HAVE_LIBSSL
+ char *cert_file;
+ unsigned use_imaps:1;
+ unsigned require_ssl:1;
+ unsigned use_sslv2:1;
+ unsigned use_sslv3:1;
+ unsigned use_tlsv1:1;
+ unsigned require_cram:1;
+#endif
+} imap_server_conf_t;
+
+typedef struct imap_store_conf {
+ store_conf_t gen;
+ imap_server_conf_t *server;
+ unsigned use_namespace:1;
+} imap_store_conf_t;
+
+typedef struct imap_message {
+ message_t gen;
+/* int seq; will be needed when expunges are tracked */
+} imap_message_t;
+
+#define NIL (void*)0x1
+#define LIST (void*)0x2
+
+typedef struct _list {
+ struct _list *next, *child;
+ char *val;
+ int len;
+} list_t;
+
+typedef struct {
+ int fd;
+#if HAVE_LIBSSL
+ SSL *ssl;
+ unsigned int use_ssl:1;
+#endif
+} Socket_t;
+
+typedef struct {
+ Socket_t sock;
+ int bytes;
+ int offset;
+ char buf[1024];
+} buffer_t;
+
+struct imap_cmd;
+#define max_in_progress 50 /* make this configurable? */
+
+typedef struct imap {
+ int uidnext; /* from SELECT responses */
+ list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+ string_list_t *boxes; /* LIST results */
+ message_t **msgapp; /* FETCH results */
+ unsigned caps, rcaps; /* CAPABILITY results */
+ /* command queue */
+ int nexttag, num_in_progress, literal_pending;
+ struct imap_cmd *in_progress, **in_progress_append;
+#if HAVE_LIBSSL
+ SSL_CTX *SSLContext;
+#endif
+ buffer_t buf; /* this is BIG, so put it last */
+} imap_t;
+
+typedef struct imap_store {
+ store_t gen;
+ imap_t *imap;
+ const char *prefix;
+ unsigned /*currentnc:1,*/ trashnc:1;
+} imap_store_t;
+
+struct imap_cmd_cb {
+ int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
+ void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response);
+ void *ctx;
+ char *data;
+ int dlen;
+ int uid;
+ unsigned create:1;
+};
+
+struct imap_cmd {
+ struct imap_cmd *next;
+ struct imap_cmd_cb cb;
+ char *cmd;
+ int tag;
+};
+
+#define CAP(cap) (imap->caps & (1 << (cap)))
+
+enum CAPABILITY {
+ NOLOGIN = 0,
+ UIDPLUS,
+ LITERALPLUS,
+ NAMESPACE,
+#if HAVE_LIBSSL
+ CRAM,
+ STARTTLS,
+#endif
+};
+
+static const char *cap_list[] = {
+ "LOGINDISABLED",
+ "UIDPLUS",
+ "LITERAL+",
+ "NAMESPACE",
+#if HAVE_LIBSSL
+ "AUTH=CRAM-MD5",
+ "STARTTLS",
+#endif
+};
+
+#define RESP_OK 0
+#define RESP_NO 1
+#define RESP_BAD 2
+
+static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
+
+
+static const char *Flags[] = {
+ "\\Draft",
+ "\\Flagged",
+ "\\Answered",
+ "\\Seen",
+ "\\Deleted",
+};
+
+#if HAVE_LIBSSL
+
+/* 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) {
+ fprintf( stderr, "Error, no server certificate\n" );
+ return -1;
+ }
+
+ err = SSL_get_verify_result( ssl );
+ if (err == X509_V_OK)
+ return 0;
+
+ fprintf( stderr, "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) );
+ info( "\nSubject: %s\n", buf );
+ X509_NAME_oneline( X509_get_issuer_name( cert ), buf, sizeof(buf) );
+ info( "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 );
+ info( "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 );
+ info( " to: %s\n", buf );
+
+ fputs( "\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"
+ "\nAccept this certificate anyway? [no]: ", stderr );
+ if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y')) {
+ ret = 0;
+ fprintf( stderr, "\n*** Fine, but don't say I didn't warn you!\n\n" );
+ }
+ return ret;
+}
+
+static int
+init_ssl_ctx( imap_store_t *ctx )
+{
+ imap_t *imap = ctx->imap;
+ imap_store_conf_t *conf = (imap_store_conf_t *)ctx->gen.conf;
+ imap_server_conf_t *srvc = conf->server;
+ SSL_METHOD *method;
+ int options = 0;
+
+ if (srvc->use_tlsv1 && !srvc->use_sslv2 && !srvc->use_sslv3)
+ method = TLSv1_client_method();
+ else
+ method = SSLv23_client_method();
+ imap->SSLContext = SSL_CTX_new( method );
+
+ if (!srvc->cert_file) {
+ fprintf( stderr, "Error, CertificateFile not defined\n" );
+ return -1;
+ } else if (access( srvc->cert_file, R_OK ))
+ warn( "*** Warning: can't read CertificateFile, so can't verify server certificates\n" );
+ else if (!SSL_CTX_load_verify_locations( imap->SSLContext, srvc->cert_file, NULL )) {
+ fprintf( stderr, "Error, SSL_CTX_load_verify_locations: %s\n",
+ ERR_error_string( ERR_get_error(), 0 ) );
+ return -1;
+ }
+
+ if (!srvc->use_sslv2)
+ options |= SSL_OP_NO_SSLv2;
+ if (!srvc->use_sslv3)
+ options |= SSL_OP_NO_SSLv3;
+ if (!srvc->use_tlsv1)
+ options |= SSL_OP_NO_TLSv1;
+
+ SSL_CTX_set_options( imap->SSLContext, options );
+
+ /* we check the result of the verification after SSL_connect() */
+ SSL_CTX_set_verify( imap->SSLContext, SSL_VERIFY_NONE, 0 );
+ return 0;
+}
+#endif /* HAVE_LIBSSL */
+
+static void
+socket_perror( const char *func, Socket_t *sock, int ret )
+{
+#if HAVE_LIBSSL
+ int err;
+
+ if (sock->use_ssl) {
+ switch ((err = SSL_get_error( sock->ssl, ret ))) {
+ case SSL_ERROR_SYSCALL:
+ case SSL_ERROR_SSL:
+ if ((err = ERR_get_error()) == 0) {
+ if (ret == 0)
+ fprintf( stderr, "SSL_%s:got EOF\n", func );
+ else
+ fprintf( stderr, "SSL_%s:%d:%s\n", func, errno, strerror(errno) );
+ } else
+ fprintf( stderr, "SSL_%s:%d:%s\n", func, err, ERR_error_string( err, 0 ) );
+ return;
+ default:
+ fprintf( stderr, "SSL_%s:%d:unhandled SSL error\n", func, err );
+ break;
+ }
+ return;
+ }
+#else
+ (void)sock;
+#endif
+ if (ret < 0)
+ perror( func );
+ else
+ fprintf( stderr, "%s: unexpected EOF\n", func );
+}
+
+static int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+ int n =
+#if HAVE_LIBSSL
+ sock->use_ssl ? SSL_read( sock->ssl, buf, len ) :
+#endif
+ read( sock->fd, buf, len );
+ if (n <= 0) {
+ socket_perror( "read", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+static int
+socket_write( Socket_t *sock, char *buf, int len )
+{
+ int n =
+#if HAVE_LIBSSL
+ sock->use_ssl ? SSL_write( sock->ssl, buf, len ) :
+#endif
+ write( sock->fd, buf, len );
+ if (n != len) {
+ socket_perror( "write", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+static int
+socket_pending( Socket_t *sock )
+{
+ int num = -1;
+
+ if (ioctl( sock->fd, FIONREAD, &num ) < 0)
+ return -1;
+ if (num > 0)
+ return num;
+#if HAVE_LIBSSL
+ if (sock->use_ssl)
+ return SSL_pending( sock->ssl );
+#endif
+ return 0;
+}
+
+/* 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) {
+ /* shift down used bytes */
+ *s = b->buf;
+
+ assert( start <= b->bytes );
+ n = b->bytes - start;
+
+ if (n)
+ memcpy( 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)
+ 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 */
+ if (Verbose)
+ puts( *s );
+ return 0;
+ }
+ }
+
+ b->offset++;
+ }
+ /* not reached */
+}
+
+static struct imap_cmd *
+v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
+ const char *fmt, va_list ap )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *cmd;
+ int n, bufl;
+ char buf[1024];
+
+ cmd = nfmalloc( sizeof(struct imap_cmd) );
+ nfvasprintf( &cmd->cmd, fmt, ap );
+ cmd->tag = ++imap->nexttag;
+
+ if (cb)
+ cmd->cb = *cb;
+ else
+ memset( &cmd->cb, 0, sizeof(cmd->cb) );
+
+ while (imap->literal_pending)
+ get_cmd_result( ctx, 0 );
+
+ bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
+ "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
+ cmd->tag, cmd->cmd, cmd->cb.dlen );
+ if (Verbose) {
+ if (imap->num_in_progress)
+ printf( "(%d in progress) ", imap->num_in_progress );
+ if (memcmp( cmd->cmd, "LOGIN", 5 ))
+ printf( ">>> %s", buf );
+ else
+ printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
+ }
+ if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
+ free( cmd->cmd );
+ free( cmd );
+ if (cb && cb->data)
+ free( cb->data );
+ return NULL;
+ }
+ if (cmd->cb.data) {
+ if (CAP(LITERALPLUS)) {
+ n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen );
+ free( cmd->cb.data );
+ if (n != cmd->cb.dlen ||
+ (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
+ {
+ free( cmd->cmd );
+ free( cmd );
+ return NULL;
+ }
+ cmd->cb.data = 0;
+ } else
+ imap->literal_pending = 1;
+ } else if (cmd->cb.cont)
+ imap->literal_pending = 1;
+ cmd->next = 0;
+ *imap->in_progress_append = cmd;
+ imap->in_progress_append = &cmd->next;
+ imap->num_in_progress++;
+ return cmd;
+}
+
+static struct imap_cmd *
+issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ struct imap_cmd *ret;
+ va_list ap;
+
+ va_start( ap, fmt );
+ ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ return ret;
+}
+
+static struct imap_cmd *
+issue_imap_cmd_w( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *ret;
+ va_list ap;
+
+ va_start( ap, fmt );
+ ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ while (imap->num_in_progress > max_in_progress ||
+ socket_pending( &imap->buf.sock ))
+ get_cmd_result( ctx, 0 );
+ return ret;
+}
+
+static int
+imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ va_list ap;
+ struct imap_cmd *cmdp;
+
+ va_start( ap, fmt );
+ cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ if (!cmdp)
+ return RESP_BAD;
+
+ return get_cmd_result( ctx, cmdp );
+}
+
+static int
+imap_exec_b( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ va_list ap;
+ struct imap_cmd *cmdp;
+
+ va_start( ap, fmt );
+ cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ if (!cmdp)
+ return DRV_STORE_BAD;
+
+ switch (get_cmd_result( ctx, cmdp )) {
+ case RESP_BAD: return DRV_STORE_BAD;
+ case RESP_NO: return DRV_BOX_BAD;
+ default: return DRV_OK;
+ }
+}
+
+static int
+imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ va_list ap;
+ struct imap_cmd *cmdp;
+
+ va_start( ap, fmt );
+ cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ if (!cmdp)
+ return DRV_STORE_BAD;
+
+ switch (get_cmd_result( ctx, cmdp )) {
+ case RESP_BAD: return DRV_STORE_BAD;
+ case RESP_NO: return DRV_MSG_BAD;
+ default: return DRV_OK;
+ }
+}
+
+/*
+static void
+drain_imap_replies( imap_t *imap )
+{
+ while (imap->num_in_progress)
+ get_cmd_result( imap, 0 );
+}
+*/
+
+static int
+is_atom( list_t *list )
+{
+ return list && list->val && list->val != NIL && list->val != LIST;
+}
+
+static int
+is_list( list_t *list )
+{
+ return list && list->val == LIST;
+}
+
+static void
+free_list( list_t *list )
+{
+ list_t *tmp;
+
+ for (; list; list = tmp) {
+ tmp = list->next;
+ if (is_list( list ))
+ free_list( list->child );
+ else if (is_atom( list ))
+ free( list->val );
+ free( list );
+ }
+}
+
+static int
+parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
+{
+ list_t *cur;
+ char *s = *sp, *p;
+ int n, bytes;
+
+ for (;;) {
+ while (isspace( (unsigned char)*s ))
+ s++;
+ if (level && *s == ')') {
+ s++;
+ break;
+ }
+ *curp = cur = nfmalloc( sizeof(*cur) );
+ curp = &cur->next;
+ cur->val = 0; /* for clean bail */
+ if (*s == '(') {
+ /* sublist */
+ s++;
+ cur->val = LIST;
+ if (parse_imap_list_l( imap, &s, &cur->child, level + 1 ))
+ goto bail;
+ } else if (imap && *s == '{') {
+ /* literal */
+ bytes = cur->len = strtol( s + 1, &s, 10 );
+ if (*s != '}')
+ goto bail;
+
+ s = cur->val = nfmalloc( cur->len );
+
+ /* 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;
+
+ memcpy( s, imap->buf.buf + imap->buf.offset, n );
+ s += n;
+ bytes -= n;
+
+ /* mark that we used part of the buffer */
+ imap->buf.offset += n;
+
+ /* now read the rest of the message */
+ while (bytes > 0) {
+ if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0)
+ goto bail;
+ s += n;
+ bytes -= n;
+ }
+
+ if (buffer_gets( &imap->buf, &s ))
+ goto bail;
+ } else if (*s == '"') {
+ /* quoted string */
+ s++;
+ p = s;
+ for (; *s != '"'; s++)
+ if (!*s)
+ goto bail;
+ cur->len = s - p;
+ s++;
+ cur->val = nfmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ } else {
+ /* atom */
+ p = s;
+ for (; *s && !isspace( (unsigned char)*s ); s++)
+ if (level && *s == ')')
+ break;
+ cur->len = s - p;
+ if (cur->len == 3 && !memcmp ("NIL", p, 3))
+ cur->val = NIL;
+ else {
+ cur->val = nfmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ }
+ }
+
+ if (!level)
+ break;
+ if (!*s)
+ goto bail;
+ }
+ *sp = s;
+ *curp = 0;
+ return 0;
+
+ bail:
+ *curp = 0;
+ return -1;
+}
+
+static list_t *
+parse_imap_list( imap_t *imap, char **sp )
+{
+ list_t *head;
+
+ if (!parse_imap_list_l( imap, sp, &head, 0 ))
+ return head;
+ free_list( head );
+ return NULL;
+}
+
+static list_t *
+parse_list( char **sp )
+{
+ return parse_imap_list( 0, sp );
+}
+
+static int
+parse_fetch( imap_t *imap, char *cmd ) /* move this down */
+{
+ list_t *tmp, *list, *flags;
+ char *body = 0;
+ imap_message_t *cur;
+ msg_data_t *msgdata;
+ struct imap_cmd *cmdp;
+ int uid = 0, mask = 0, status = 0, size = 0;
+ unsigned i;
+
+ list = parse_imap_list( imap, &cmd );
+
+ if (!is_list( list )) {
+ fprintf( stderr, "IMAP error: bogus FETCH response\n" );
+ free_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 );
+ else
+ fprintf( stderr, "IMAP error: unable to parse UID\n" );
+ } else if (!strcmp( "FLAGS", tmp->val )) {
+ tmp = tmp->next;
+ if (is_list( tmp )) {
+ for (flags = tmp->child; flags; flags = flags->next) {
+ if (is_atom( flags )) {
+ if (!strcmp( "\\Recent", flags->val )) {
+ status |= M_RECENT;
+ goto flagok;
+ }
+ for (i = 0; i < as(Flags); i++)
+ if (!strcmp( Flags[i], flags->val )) {
+ mask |= 1 << i;
+ goto flagok;
+ }
+ fprintf( stderr, "IMAP warning: unknown flag %s\n", flags->val );
+ flagok: ;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse FLAGS list\n" );
+ }
+ status |= M_FLAGS;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse FLAGS\n" );
+ } else if (!strcmp( "RFC822.SIZE", tmp->val )) {
+ tmp = tmp->next;
+ if (is_atom( tmp ))
+ size = atoi( tmp->val );
+ else
+ fprintf( stderr, "IMAP error: unable to parse SIZE\n" );
+ } else if (!strcmp( "BODY[]", tmp->val )) {
+ tmp = tmp->next;
+ if (is_atom( tmp )) {
+ body = tmp->val;
+ tmp->val = 0; /* don't free together with list */
+ size = tmp->len;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse BODY[]\n" );
+ }
+ }
+ }
+
+ if (body) {
+ for (cmdp = imap->in_progress; cmdp; cmdp = cmdp->next)
+ if (cmdp->cb.uid == uid)
+ goto gotuid;
+ fprintf( stderr, "IMAP error: unexpected FETCH response (UID %d)\n", uid );
+ free_list( list );
+ return -1;
+ gotuid:
+ msgdata = (msg_data_t *)cmdp->cb.ctx;
+ msgdata->data = body;
+ msgdata->len = size;
+ msgdata->crlf = 1;
+ if (status & M_FLAGS)
+ msgdata->flags = mask;
+ } else if (uid) { /* ignore async flag updates for now */
+ /* XXX this will need sorting for out-of-order (multiple queries) */
+ cur = nfcalloc( sizeof(*cur) );
+ *imap->msgapp = &cur->gen;
+ imap->msgapp = &cur->gen.next;
+ cur->gen.next = 0;
+ cur->gen.uid = uid;
+ cur->gen.flags = mask;
+ cur->gen.status = status;
+ cur->gen.size = size;
+ }
+
+ free_list( list );
+ return 0;
+}
+
+static void
+parse_capability( imap_t *imap, char *cmd )
+{
+ char *arg;
+ unsigned i;
+
+ imap->caps = 0x80000000;
+ while ((arg = next_arg( &cmd )))
+ for (i = 0; i < as(cap_list); i++)
+ if (!strcmp( cap_list[i], arg ))
+ imap->caps |= 1 << i;
+ imap->rcaps = imap->caps;
+}
+
+static int
+parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
+{
+ imap_t *imap = ctx->imap;
+ char *arg, *p;
+
+ if (*s != '[')
+ return RESP_OK; /* no response code */
+ s++;
+ if (!(p = strchr( s, ']' ))) {
+ fprintf( stderr, "IMAP error: malformed response code\n" );
+ return RESP_BAD;
+ }
+ *p++ = 0;
+ arg = next_arg( &s );
+ if (!strcmp( "UIDVALIDITY", arg )) {
+ if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) {
+ fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" );
+ return RESP_BAD;
+ }
+ } else if (!strcmp( "UIDNEXT", arg )) {
+ if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) {
+ fprintf( stderr, "IMAP error: malformed NEXTUID status\n" );
+ return RESP_BAD;
+ }
+ } else if (!strcmp( "CAPABILITY", arg )) {
+ parse_capability( imap, s );
+ } else if (!strcmp( "ALERT", arg )) {
+ /* RFC2060 says that these messages MUST be displayed
+ * to the user
+ */
+ for (; isspace( (unsigned char)*p ); p++);
+ fprintf( stderr, "*** IMAP ALERT *** %s\n", p );
+ } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) {
+ if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) ||
+ !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg )))
+ {
+ fprintf( stderr, "IMAP error: malformed APPENDUID status\n" );
+ return RESP_BAD;
+ }
+ }
+ return RESP_OK;
+}
+
+static void
+parse_search( imap_t *imap, char *cmd )
+{
+ char *arg;
+ struct imap_cmd *cmdp;
+ int uid;
+
+ arg = next_arg( &cmd );
+ if (!arg || !(uid = atoi( arg ))) {
+ fprintf( stderr, "IMAP error: malformed SEARCH response\n" );
+ return;
+ }
+
+ /* Find the first command that expects a UID - this is guaranteed
+ * to come in-order, as there are no other means to identify which
+ * SEARCH response belongs to which request.
+ */
+ for (cmdp = imap->in_progress; cmdp; cmdp = cmdp->next)
+ if (cmdp->cb.uid == -1) {
+ *(int *)cmdp->cb.ctx = uid;
+ return;
+ }
+ fprintf( stderr, "IMAP error: unexpected SEARCH response (UID %u)\n", uid );
+}
+
+static void
+parse_list_rsp( imap_store_t *ctx, char *cmd )
+{
+ imap_t *imap = ctx->imap;
+ char *arg;
+ list_t *list, *lp;
+ int l;
+
+ list = parse_list( &cmd );
+ if (list->val == LIST)
+ for (lp = list->child; lp; lp = lp->next)
+ if (is_atom( lp ) && !strcasecmp( lp->val, "\\NoSelect" )) {
+ free_list( list );
+ return;
+ }
+ free_list( list );
+ (void) next_arg( &cmd ); /* skip delimiter */
+ arg = next_arg( &cmd );
+ l = strlen( ctx->gen.conf->path );
+ if (memcmp( arg, ctx->gen.conf->path, l ))
+ return;
+ arg += l;
+ if (!memcmp( arg + strlen( arg ) - 5, ".lock", 5 )) /* workaround broken servers */
+ return;
+ add_string_list( &imap->boxes, arg );
+}
+
+static int
+get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *cmdp, **pcmdp, *ncmdp;
+ char *cmd, *arg, *arg1, *p;
+ int n, resp, resp2, tag;
+
+ for (;;) {
+ if (buffer_gets( &imap->buf, &cmd ))
+ return RESP_BAD;
+
+ arg = next_arg( &cmd );
+ if (*arg == '*') {
+ arg = next_arg( &cmd );
+ if (!arg) {
+ fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+ return RESP_BAD;
+ }
+
+ if (!strcmp( "NAMESPACE", arg )) {
+ imap->ns_personal = parse_list( &cmd );
+ imap->ns_other = parse_list( &cmd );
+ imap->ns_shared = parse_list( &cmd );
+ } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
+ !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
+ if ((resp = parse_response_code( ctx, 0, cmd )) != RESP_OK)
+ return resp;
+ } else if (!strcmp( "CAPABILITY", arg ))
+ parse_capability( imap, cmd );
+ else if (!strcmp( "LIST", arg ))
+ parse_list_rsp( ctx, cmd );
+ else if (!strcmp( "SEARCH", arg ))
+ parse_search( imap, cmd );
+ else if ((arg1 = next_arg( &cmd ))) {
+ if (!strcmp( "EXISTS", arg1 ))
+ ctx->gen.count = atoi( arg );
+ else if (!strcmp( "RECENT", arg1 ))
+ ctx->gen.recent = atoi( arg );
+ else if(!strcmp ( "FETCH", arg1 )) {
+ if (parse_fetch( imap, cmd ))
+ return RESP_BAD;
+ }
+ } else {
+ fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+ return RESP_BAD;
+ }
+ } else if (!imap->in_progress) {
+ fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
+ return RESP_BAD;
+ } else if (*arg == '+') {
+ /* This can happen only with the last command underway, as
+ it enforces a round-trip. */
+ cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
+ offsetof(struct imap_cmd, next));
+ if (cmdp->cb.data) {
+ n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen );
+ free( cmdp->cb.data );
+ cmdp->cb.data = 0;
+ if (n != (int)cmdp->cb.dlen)
+ return RESP_BAD;
+ } else if (cmdp->cb.cont) {
+ if (cmdp->cb.cont( ctx, cmdp, cmd ))
+ return RESP_BAD;
+ } else {
+ fprintf( stderr, "IMAP error: unexpected command continuation request\n" );
+ return RESP_BAD;
+ }
+ if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
+ return RESP_BAD;
+ if (!cmdp->cb.cont)
+ imap->literal_pending = 0;
+ if (!tcmd)
+ return DRV_OK;
+ } else {
+ tag = atoi( arg );
+ for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
+ if (cmdp->tag == tag)
+ goto gottag;
+ fprintf( stderr, "IMAP error: unexpected tag %s\n", arg );
+ return RESP_BAD;
+ gottag:
+ if (!(*pcmdp = cmdp->next))
+ imap->in_progress_append = pcmdp;
+ imap->num_in_progress--;
+ if (cmdp->cb.cont || cmdp->cb.data)
+ imap->literal_pending = 0;
+ arg = next_arg( &cmd );
+ if (!strcmp( "OK", arg ))
+ resp = DRV_OK;
+ else {
+ if (!strcmp( "NO", arg )) {
+ if (cmdp->cb.create && cmd && !memcmp( cmd, "[TRYCREATE]", 11 )) { /* SELECT, APPEND or UID COPY */
+ p = strchr( cmdp->cmd, '"' );
+ if (!issue_imap_cmd( ctx, 0, "CREATE %.*s", strchr( p + 1, '"' ) - p + 1, p )) {
+ resp = RESP_BAD;
+ goto normal;
+ }
+ /* not waiting here violates the spec, but a server that does not
+ grok this nonetheless violates it too. */
+ cmdp->cb.create = 0;
+ if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) {
+ resp = RESP_BAD;
+ goto normal;
+ }
+ free( cmdp->cmd );
+ free( cmdp );
+ if (!tcmd)
+ return 0; /* ignored */
+ if (cmdp == tcmd)
+ tcmd = ncmdp;
+ continue;
+ }
+ resp = RESP_NO;
+ } else /*if (!strcmp( "BAD", arg ))*/
+ resp = RESP_BAD;
+ fprintf( stderr, "IMAP command '%s' returned an error: %s %s\n",
+ memcmp (cmdp->cmd, "LOGIN", 5) ?
+ cmdp->cmd : "LOGIN <user> <pass>",
+ arg, cmd ? cmd : "");
+ }
+ if ((resp2 = parse_response_code( ctx, cmdp->cb.ctx, cmd )) > resp)
+ resp = resp2;
+ normal:
+ if (cmdp->cb.done)
+ cmdp->cb.done( ctx, cmdp, resp );
+ if (cmdp->cb.data)
+ free( cmdp->cb.data );
+ free( cmdp->cmd );
+ free( cmdp );
+ if (!tcmd || tcmd == cmdp)
+ return resp;
+ }
+ }
+ /* not reached */
+}
+
+static void
+imap_close_server( imap_store_t *ictx )
+{
+ imap_t *imap = ictx->imap;
+
+ if (imap->buf.sock.fd != -1) {
+ imap_exec( ictx, 0, "LOGOUT" );
+ close( imap->buf.sock.fd );
+ }
+#ifdef HAVE_LIBSSL
+ if (imap->SSLContext)
+ SSL_CTX_free( imap->SSLContext );
+#endif
+ free_list( imap->ns_personal );
+ free_list( imap->ns_other );
+ free_list( imap->ns_shared );
+ free( imap );
+}
+
+static void
+imap_close_store( store_t *ctx )
+{
+ imap_close_server( (imap_store_t *)ctx );
+ free_generic_messages( ctx->msgs );
+ free( ctx );
+}
+
+#ifdef HAVE_LIBSSL
+static int
+start_tls( imap_store_t *ctx )
+{
+ imap_t *imap = ctx->imap;
+ int ret;
+ static int ssl_inited;
+
+ if (!ssl_inited) {
+ SSL_library_init();
+ SSL_load_error_strings();
+ ssl_inited = 1;
+ }
+
+ if (init_ssl_ctx( ctx ))
+ return 1;
+
+ imap->buf.sock.ssl = SSL_new( imap->SSLContext );
+ SSL_set_fd( imap->buf.sock.ssl, imap->buf.sock.fd );
+ if ((ret = SSL_connect( imap->buf.sock.ssl )) <= 0) {
+ socket_perror( "connect", &imap->buf.sock, ret );
+ return 1;
+ }
+
+ /* verify the server certificate */
+ if (verify_cert( imap->buf.sock.ssl ))
+ return 1;
+
+ imap->buf.sock.use_ssl = 1;
+ info( "Connection is now encrypted\n" );
+ return 0;
+}
+
+#define ENCODED_SIZE(n) (4*((n+2)/3))
+
+static char
+hexchar( unsigned int b )
+{
+ if (b < 10)
+ return '0' + b;
+ return 'a' + (b - 10);
+}
+
+/* XXX merge into do_cram_auth? */
+static 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 = nfcalloc( 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 ) );
+
+ 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 );
+ }
+
+ nfsnprintf( buf, sizeof(buf), "%s %s", user, hex );
+
+ len = strlen( buf );
+ len = ENCODED_SIZE( len ) + 1;
+ final = nfmalloc( len );
+ final[len - 1] = 0;
+
+ assert( EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, strlen( buf ) ) == len - 1 );
+
+ return final;
+}
+
+static int
+do_cram_auth (imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt)
+{
+ imap_t *imap = ctx->imap;
+ imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
+ char *resp;
+ int n, l;
+
+ resp = cram( prompt, srvc->user, srvc->pass );
+
+ if (Verbose)
+ printf( ">+> %s\n", resp );
+ l = strlen( resp );
+ n = socket_write( &imap->buf.sock, resp, l );
+ free( resp );
+ if (n != l)
+ return -1;
+ if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
+ return -1;
+ cmdp->cb.cont = 0;
+ return 0;
+}
+#endif
+
+static store_t *
+imap_open_store( store_conf_t *conf, store_t *oldctx )
+{
+ imap_store_conf_t *cfg = (imap_store_conf_t *)conf;
+ imap_server_conf_t *srvc = cfg->server;
+ imap_store_t *ctx = (imap_store_t *)oldctx;
+ imap_t *imap;
+ char *arg, *rsp;
+ struct hostent *he;
+ struct sockaddr_in addr;
+ int s, a[2], preauth;
+#if HAVE_LIBSSL
+ int use_ssl;
+#endif
+
+ if (ctx) {
+ if (((imap_store_conf_t *)(ctx->gen.conf))->server == cfg->server) {
+ ctx->gen.conf = conf;
+ imap = ctx->imap;
+ goto final;
+ }
+ imap_close_server( ctx );
+ } else
+ ctx = nfcalloc( sizeof(*ctx) );
+
+ ctx->gen.conf = conf;
+ ctx->imap = imap = nfcalloc( sizeof(*imap) );
+ imap->buf.sock.fd = -1;
+ imap->in_progress_append = &imap->in_progress;
+
+ /* open connection to IMAP server */
+#if HAVE_LIBSSL
+ use_ssl = 0;
+#endif
+
+ if (srvc->tunnel) {
+ info( "Starting tunnel '%s'... ", srvc->tunnel );
+
+ if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
+ perror( "socketpair" );
+ exit( 1 );
+ }
+
+ if (fork() == 0) {
+ if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1)
+ _exit( 127 );
+ close( a[0] );
+ close( a[1] );
+ execl( "/bin/sh", "sh", "-c", srvc->tunnel, 0 );
+ _exit( 127 );
+ }
+
+ close (a[0]);
+
+ imap->buf.sock.fd = a[1];
+
+ info( "ok\n" );
+ } else {
+ memset( &addr, 0, sizeof(addr) );
+ addr.sin_port = htons( srvc->port );
+ addr.sin_family = AF_INET;
+
+ info( "Resolving %s... ", srvc->host );
+ he = gethostbyname( srvc->host );
+ if (!he) {
+ perror( "gethostbyname" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
+
+ s = socket( PF_INET, SOCK_STREAM, 0 );
+
+ info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
+ if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
+ close( s );
+ perror( "connect" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ imap->buf.sock.fd = s;
+
+#if HAVE_LIBSSL
+ if (srvc->use_imaps) {
+ if (start_tls( ctx ))
+ goto bail;
+ use_ssl = 1;
+ }
+#endif
+ }
+
+ /* read the greeting string */
+ if (buffer_gets( &imap->buf, &rsp )) {
+ fprintf( stderr, "IMAP error: no greeting response\n" );
+ goto bail;
+ }
+ arg = next_arg( &rsp );
+ if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
+ fprintf( stderr, "IMAP error: invalid greeting response\n" );
+ goto bail;
+ }
+ preauth = 0;
+ if (!strcmp( "PREAUTH", arg ))
+ preauth = 1;
+ else if (strcmp( "OK", arg ) != 0) {
+ fprintf( stderr, "IMAP error: unknown greeting response\n" );
+ goto bail;
+ }
+ parse_response_code( ctx, 0, rsp );
+ if (!imap->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+ goto bail;
+
+ if (!preauth) {
+#if HAVE_LIBSSL
+ if (!srvc->use_imaps && (srvc->use_sslv2 || srvc->use_sslv3 || srvc->use_tlsv1)) {
+ /* always try to select SSL support if available */
+ if (CAP(STARTTLS)) {
+ if (imap_exec( ctx, 0, "STARTTLS" ) != RESP_OK)
+ goto bail;
+ if (start_tls( ctx ))
+ goto bail;
+ use_ssl = 1;
+
+ if (imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+ goto bail;
+ } else {
+ if (srvc->require_ssl) {
+ fprintf( stderr, "IMAP error: SSL support not available\n" );
+ goto bail;
+ } else
+ warn( "IMAP warning: SSL support not available\n" );
+ }
+ }
+#endif
+
+ info ("Logging in...\n");
+ if (!srvc->user) {
+ fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
+ goto bail;
+ }
+ if (!srvc->pass) {
+ char prompt[80];
+ sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host );
+ arg = getpass( prompt );
+ if (!arg) {
+ perror( "getpass" );
+ exit( 1 );
+ }
+ if (!*arg) {
+ fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host );
+ goto bail;
+ }
+ /*
+ * getpass() returns a pointer to a static buffer. make a copy
+ * for long term storage.
+ */
+ srvc->pass = nfstrdup( arg );
+ }
+#if HAVE_LIBSSL
+ if (CAP(CRAM)) {
+ struct imap_cmd_cb cb;
+
+ info( "Authenticating with CRAM-MD5\n" );
+ memset( &cb, 0, sizeof(cb) );
+ cb.cont = do_cram_auth;
+ if (imap_exec( ctx, &cb, "AUTHENTICATE CRAM-MD5" ) != RESP_OK)
+ goto bail;
+ } else if (srvc->require_cram) {
+ fprintf( stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n" );
+ goto bail;
+ } else
+#endif
+ {
+ if (CAP(NOLOGIN)) {
+ fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
+ goto bail;
+ }
+#if HAVE_LIBSSL
+ if (!use_ssl)
+#endif
+ warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+ if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
+ fprintf( stderr, "IMAP error: LOGIN failed\n" );
+ goto bail;
+ }
+ }
+ } /* !preauth */
+
+ final:
+ ctx->prefix = "";
+ if (*conf->path)
+ ctx->prefix = conf->path;
+ else if (cfg->use_namespace && CAP(NAMESPACE)) {
+ /* get NAMESPACE info */
+ if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK)
+ goto bail;
+ /* XXX for now assume personal namespace */
+ if (is_list( imap->ns_personal ) &&
+ is_list( imap->ns_personal->child ) &&
+ is_atom( imap->ns_personal->child->child ))
+ ctx->prefix = imap->ns_personal->child->child->val;
+ }
+ ctx->trashnc = 1;
+ return (store_t *)ctx;
+
+ bail:
+ imap_close_store( &ctx->gen );
+ return 0;
+}
+
+static void
+imap_prepare( store_t *gctx, int opts )
+{
+ free_generic_messages( gctx->msgs );
+ gctx->msgs = 0;
+ gctx->opts = opts;
+}
+
+static int
+imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ imap_t *imap = ctx->imap;
+ const char *prefix;
+ int ret, i, j, bl;
+ struct imap_cmd_cb cb;
+ char buf[1000];
+
+
+ if (!strcmp( gctx->name, "INBOX" )) {
+// imap->currentnc = 0;
+ prefix = "";
+ } else {
+// imap->currentnc = 1; /* could use LIST results for that */
+ prefix = ctx->prefix;
+ }
+
+ memset( &cb, 0, sizeof(cb) );
+ cb.create = gctx->opts & OPEN_CREATE;
+ if ((ret = imap_exec_b( ctx, &cb, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK)
+ goto bail;
+
+ if (gctx->count) {
+ imap->msgapp = &gctx->msgs;
+ sort_ints( excs, nexcs );
+ for (i = 0; i < nexcs; ) {
+ for (bl = 0; i < nexcs && bl < 960; i++) {
+ if (bl)
+ buf[bl++] = ',';
+ bl += sprintf( buf + bl, "%d", excs[i] );
+ j = i;
+ for (; i + 1 < nexcs && excs[i + 1] == excs[i] + 1; i++);
+ if (i != j)
+ bl += sprintf( buf + bl, ":%d", excs[i] );
+ }
+ if ((ret = imap_exec_b( ctx, 0, "UID FETCH %s (UID%s%s)", buf,
+ (gctx->opts & OPEN_FLAGS) ? " FLAGS" : "",
+ (gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK)
+ goto bail;
+ }
+ if (maxuid == INT_MAX)
+ maxuid = imap->uidnext ? imap->uidnext - 1 : 1000000000;
+ if (maxuid >= minuid &&
+ (ret = imap_exec_b( ctx, 0, "UID FETCH %d:%d (UID%s%s)", minuid, maxuid,
+ (gctx->opts & OPEN_FLAGS) ? " FLAGS" : "",
+ (gctx->opts & OPEN_SIZE) ? " RFC822.SIZE" : "" )) != DRV_OK)
+ goto bail;
+ }
+
+ ret = DRV_OK;
+
+ bail:
+ if (excs)
+ free( excs );
+ return ret;
+}
+
+static int
+imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data )
+{
+ struct imap_cmd_cb cb;
+
+ memset( &cb, 0, sizeof(cb) );
+ cb.uid = msg->uid;
+ cb.ctx = data;
+ return imap_exec_m( (imap_store_t *)ctx, &cb, "UID FETCH %d (%sBODY.PEEK[])",
+ msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " );
+}
+
+static int
+imap_make_flags( int flags, char *buf )
+{
+ const char *s;
+ unsigned i, d;
+
+ for (i = d = 0; i < as(Flags); i++)
+ if (flags & (1 << i)) {
+ buf[d++] = ' ';
+ for (s = Flags[i]; *s; s++)
+ buf[d++] = *s;
+ }
+ buf[0] = '(';
+ buf[d++] = ')';
+ return d;
+}
+
+static int
+imap_flags_helper( imap_store_t *ctx, int uid, char what, int flags)
+{
+ char buf[256];
+
+ buf[imap_make_flags( flags, buf )] = 0;
+ return issue_imap_cmd_w( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ) ? DRV_OK : DRV_STORE_BAD;
+}
+
+static int
+imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ int ret;
+
+ if (msg) {
+ uid = msg->uid;
+ add &= ~msg->flags;
+ del &= msg->flags;
+ msg->flags |= add;
+ msg->flags &= ~del;
+ }
+ if ((!add || (ret = imap_flags_helper( ctx, uid, '+', add )) == DRV_OK) &&
+ (!del || (ret = imap_flags_helper( ctx, uid, '-', del )) == DRV_OK))
+ return DRV_OK;
+ return ret;
+}
+
+static int
+imap_close( store_t *ctx )
+{
+ return imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" );
+}
+
+static int
+imap_trash_msg( store_t *gctx, message_t *msg )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ struct imap_cmd_cb cb;
+
+ memset( &cb, 0, sizeof(cb) );
+ cb.create = 1;
+ return imap_exec_m( ctx, &cb, "UID COPY %d \"%s%s\"",
+ msg->uid, ctx->prefix, gctx->conf->trash );
+}
+
+#define TUIDL 8
+
+static int
+imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ imap_t *imap = ctx->imap;
+ struct imap_cmd_cb cb;
+ char *fmap, *buf;
+ const char *prefix, *box;
+ int ret, i, j, d, len, extra, nocr;
+ int start, sbreak = 0, ebreak = 0;
+ char flagstr[128], tuid[TUIDL * 2 + 1];
+
+ memset( &cb, 0, sizeof(cb) );
+
+ fmap = data->data;
+ len = data->len;
+ nocr = !data->crlf;
+ extra = 0, i = 0;
+ if (!CAP(UIDPLUS) && uid) {
+ nloop:
+ start = i;
+ while (i < len)
+ if (fmap[i++] == '\n') {
+ extra += nocr;
+ if (i - 2 + nocr == start) {
+ sbreak = ebreak = i - 2 + nocr;
+ goto mktid;
+ }
+ if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
+ extra -= (ebreak = i) - (sbreak = start) + nocr;
+ goto mktid;
+ }
+ goto nloop;
+ }
+ /* invalid message */
+ free( fmap );
+ return DRV_MSG_BAD;
+ mktid:
+ for (j = 0; j < TUIDL; j++)
+ sprintf( tuid + j * 2, "%02x", arc4_getbyte() );
+ extra += 8 + TUIDL * 2 + 2;
+ }
+ if (nocr)
+ for (; i < len; i++)
+ if (fmap[i] == '\n')
+ extra++;
+
+ cb.dlen = len + extra;
+ buf = cb.data = nfmalloc( cb.dlen );
+ i = 0;
+ if (!CAP(UIDPLUS) && uid) {
+ if (nocr) {
+ for (; i < sbreak; i++)
+ if (fmap[i] == '\n') {
+ *buf++ = '\r';
+ *buf++ = '\n';
+ } else
+ *buf++ = fmap[i];
+ } else {
+ memcpy( buf, fmap, sbreak );
+ buf += sbreak;
+ }
+ memcpy( buf, "X-TUID: ", 8 );
+ buf += 8;
+ memcpy( buf, tuid, TUIDL * 2 );
+ buf += TUIDL * 2;
+ *buf++ = '\r';
+ *buf++ = '\n';
+ i = ebreak;
+ }
+ if (nocr) {
+ for (; i < len; i++)
+ if (fmap[i] == '\n') {
+ *buf++ = '\r';
+ *buf++ = '\n';
+ } else
+ *buf++ = fmap[i];
+ } else
+ memcpy( buf, fmap + i, len - i );
+
+ free( fmap );
+
+ d = 0;
+ if (data->flags) {
+ d = imap_make_flags( data->flags, flagstr );
+ flagstr[d++] = ' ';
+ }
+ flagstr[d] = 0;
+
+ if (!uid) {
+ box = gctx->conf->trash;
+ prefix = ctx->prefix;
+ cb.create = 1;
+ if (ctx->trashnc)
+ imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
+ } else {
+ box = gctx->name;
+ prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+ cb.create = gctx->opts & OPEN_CREATE;
+ /*if (ctx->currentnc)
+ imap->caps = imap->rcaps & ~(1 << LITERALPLUS);*/
+ }
+ cb.ctx = uid;
+ ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+ imap->caps = imap->rcaps;
+ if (ret != DRV_OK)
+ return ret;
+ if (!uid)
+ ctx->trashnc = 0;
+ else {
+ /*ctx->currentnc = 0;*/
+ gctx->count++;
+ }
+
+ if (CAP(UIDPLUS) || !uid)
+ return DRV_OK;
+
+ /* Didn't receive an APPENDUID */
+ cb.uid = -1; /* we're looking for a UID */
+ cb.data = 0; /* reset; ctx still set */
+ return imap_exec_m( ctx, &cb, "UID SEARCH HEADER X-TUID %s", tuid );
+}
+
+static int
+imap_list( store_t *gctx, string_list_t **retb )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ imap_t *imap = ctx->imap;
+ int ret;
+
+ imap->boxes = 0;
+ if ((ret = imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix )) != DRV_OK)
+ return ret;
+ *retb = imap->boxes;
+ return DRV_OK;
+}
+
+static int
+imap_check( store_t *gctx )
+{
+ (void) gctx;
+ /* flush queue here */
+ return DRV_OK;
+}
+
+imap_server_conf_t *servers, **serverapp = &servers;
+
+static int
+imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
+{
+ imap_store_conf_t *store;
+ imap_server_conf_t *server, *srv, sserver;
+
+ if (!strcasecmp( "IMAPAccount", cfg->cmd )) {
+ server = nfcalloc( sizeof(*server) );
+ server->name = nfstrdup( cfg->val );
+ *serverapp = server;
+ serverapp = &server->next;
+ store = 0;
+ } else if (!strcasecmp( "IMAPStore", cfg->cmd )) {
+ store = nfcalloc( sizeof(*store) );
+ store->gen.driver = &imap_driver;
+ store->gen.name = nfstrdup( cfg->val );
+ store->use_namespace = 1;
+ memset( &sserver, 0, sizeof(sserver) );
+ server = &sserver;
+ } else
+ return 0;
+
+#if HAVE_LIBSSL
+ /* this will probably annoy people, but its the best default just in
+ * case people forget to turn it on
+ */
+ server->require_ssl = 1;
+ server->use_tlsv1 = 1;
+#endif
+
+ while (getcline( cfg ) && cfg->cmd) {
+ if (!strcasecmp( "Account", cfg->cmd )) {
+ for (srv = servers; srv; srv = srv->next)
+ if (srv->name && !strcmp( srv->name, cfg->val ))
+ goto gotsrv;
+ fprintf( stderr, "%s:%d: unknown IMAP account '%s'\n",
+ cfg->file, cfg->line, cfg->val );
+ *err = 1;
+ continue;
+ gotsrv:
+ store->server = srv;
+ } else if (!strcasecmp( "Host", cfg->cmd )) {
+#if HAVE_LIBSSL
+ if (!memcmp( "imaps:", cfg->val, 6 )) {
+ cfg->val += 6;
+ server->use_imaps = 1;
+ server->use_sslv2 = 1;
+ server->use_sslv3 = 1;
+ if (!server->port)
+ server->port = 993;
+ } else
+#endif
+ {
+ if (!memcmp( "imap:", cfg->val, 5 ))
+ cfg->val += 5;
+ if (!server->port)
+ server->port = 143;
+ }
+ if (!memcmp( "//", cfg->val, 2 ))
+ cfg->val += 2;
+ server->host = nfstrdup( cfg->val );
+ }
+ else if (!strcasecmp( "User", cfg->cmd ))
+ server->user = nfstrdup( cfg->val );
+ else if (!strcasecmp( "Pass", cfg->cmd ))
+ server->pass = nfstrdup( cfg->val );
+ else if (!strcasecmp( "Port", cfg->cmd ))
+ server->port = parse_int( cfg );
+#if HAVE_LIBSSL
+ else if (!strcasecmp( "CertificateFile", cfg->cmd ))
+ server->cert_file = expand_strdup( cfg->val );
+ else if (!strcasecmp( "RequireSSL", cfg->cmd ))
+ server->require_ssl = parse_bool( cfg );
+ else if (!strcasecmp( "UseSSLv2", cfg->cmd ))
+ server->use_sslv2 = parse_bool( cfg );
+ else if (!strcasecmp( "UseSSLv3", cfg->cmd ))
+ server->use_sslv3 = parse_bool( cfg );
+ else if (!strcasecmp( "UseTLSv1", cfg->cmd ))
+ server->use_tlsv1 = parse_bool( cfg );
+ else if (!strcasecmp( "RequireCRAM", cfg->cmd ))
+ server->require_cram = parse_bool( cfg );
+#endif
+ else if (!strcasecmp( "Tunnel", cfg->cmd ))
+ server->tunnel = nfstrdup( cfg->val );
+ else if (store) {
+ if (!strcasecmp( "UseNamespace", cfg->cmd ))
+ store->use_namespace = parse_bool( cfg );
+ else if (!strcasecmp( "Path", cfg->cmd ))
+ store->gen.path = nfstrdup( cfg->val );
+ else
+ parse_generic_store( &store->gen, cfg, err );
+ } else {
+ fprintf( stderr, "%s:%d: unknown keyword '%s'\n",
+ cfg->file, cfg->line, cfg->cmd );
+ *err = 1;
+ }
+ }
+ if (!store || !store->server) {
+ if (!server->tunnel && !server->host) {
+ if (store)
+ fprintf( stderr, "IMAP store '%s' has incomplete connection details\n", store->gen.name );
+ else
+ fprintf( stderr, "IMAP account '%s' has incomplete connection details\n", server->name );
+ *err = 1;
+ /* leaking server/store */
+ *storep = 0;
+ return 1;
+ }
+ }
+ *storep = &store->gen;
+ if (store && !store->server) {
+ store->server = nfmalloc( sizeof(sserver) );
+ memcpy( store->server, &sserver, sizeof(sserver) );
+ }
+ return 1;
+}
+
+struct driver imap_driver = {
+ imap_parse_store,
+ imap_open_store,
+ imap_close_store,
+ imap_list,
+ imap_prepare,
+ imap_select,
+ imap_fetch_msg,
+ imap_store_msg,
+ imap_set_flags,
+ imap_trash_msg,
+ imap_check,
+ imap_close
+};
diff --git a/src/drv_maildir.c b/src/drv_maildir.c
@@ -0,0 +1,1153 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2004 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <time.h>
+
+#define USE_DB 1
+
+#ifdef USE_DB
+#include <db.h>
+#endif /* USE_DB */
+
+typedef struct maildir_store_conf {
+ store_conf_t gen;
+ char *inbox;
+#ifdef USE_DB
+ int alt_map;
+#endif /* USE_DB */
+} maildir_store_conf_t;
+
+typedef struct maildir_message {
+ message_t gen;
+ char *base;
+} maildir_message_t;
+
+typedef struct maildir_store {
+ store_t gen;
+ int uvfd, uvok, nuid;
+ int minuid, maxuid, nexcs, *excs;
+#ifdef USE_DB
+ DB *db;
+#endif /* USE_DB */
+} maildir_store_t;
+
+#ifdef USE_DB
+static DBT key, value; /* no need to be reentrant, and this saves lots of memset()s */
+#endif /* USE_DB */
+static struct flock lck;
+
+static int MaildirCount;
+
+static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
+
+static unsigned char
+maildir_parse_flags( const char *base )
+{
+ const char *s;
+ unsigned i;
+ unsigned char flags;
+
+ flags = 0;
+ if ((s = strstr( base, ":2," )))
+ for (s += 3, i = 0; i < as(Flags); i++)
+ if (strchr( s, Flags[i] ))
+ flags |= (1 << i);
+ return flags;
+}
+
+static void maildir_close_store( store_t *gctx );
+
+static store_t *
+maildir_open_store( store_conf_t *conf, store_t *oldctx )
+{
+ maildir_store_t *ctx;
+ struct stat st;
+
+ if (oldctx)
+ maildir_close_store( oldctx );
+ if (stat( conf->path, &st ) || !S_ISDIR(st.st_mode)) {
+ fprintf( stderr, "Maildir error: cannot open store %s\n", conf->path );
+ return 0;
+ }
+ ctx = nfcalloc( sizeof(*ctx) );
+ ctx->gen.conf = conf;
+ ctx->uvfd = -1;
+ return &ctx->gen;
+}
+
+static void
+free_maildir_messages( message_t *msg )
+{
+ message_t *tmsg;
+
+ for (; (tmsg = msg); msg = tmsg) {
+ tmsg = msg->next;
+ free( ((maildir_message_t *)msg)->base );
+ free( msg );
+ }
+}
+
+static void
+maildir_cleanup( store_t *gctx )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+
+ free_maildir_messages( gctx->msgs );
+#ifdef USE_DB
+ if (ctx->db)
+ ctx->db->close( ctx->db, 0 );
+#endif /* USE_DB */
+ if (gctx->path)
+ free( gctx->path );
+ if (ctx->excs)
+ free( ctx->excs );
+ if (ctx->uvfd >= 0)
+ close( ctx->uvfd );
+}
+
+static void
+maildir_close_store( store_t *gctx )
+{
+ maildir_cleanup( gctx );
+ free( gctx );
+}
+
+static int
+maildir_list( store_t *gctx, string_list_t **retb )
+{
+ DIR *dir;
+ struct dirent *de;
+
+ if (!(dir = opendir( gctx->conf->path ))) {
+ fprintf( stderr, "%s: %s\n", gctx->conf->path, strerror(errno) );
+ return DRV_STORE_BAD;
+ }
+ *retb = 0;
+ while ((de = readdir( dir ))) {
+ struct stat st;
+ char buf[PATH_MAX];
+
+ if (*de->d_name == '.')
+ continue;
+ nfsnprintf( buf, sizeof(buf), "%s%s/cur", gctx->conf->path, de->d_name );
+ if (stat( buf, &st ) || !S_ISDIR(st.st_mode))
+ continue;
+ add_string_list( retb, de->d_name );
+ }
+ closedir (dir);
+
+ return DRV_OK;
+}
+
+static const char *subdirs[] = { "cur", "new", "tmp" };
+
+typedef struct {
+ char *base;
+ int size;
+ unsigned uid:31, recent:1;
+} msg_t;
+
+typedef struct {
+ msg_t *ents;
+ int nents, nalloc;
+} msglist_t;
+
+static void
+maildir_free_scan( msglist_t *msglist )
+{
+ int i;
+
+ if (msglist->ents) {
+ for (i = 0; i < msglist->nents; i++)
+ if (msglist->ents[i].base)
+ free( msglist->ents[i].base );
+ free( msglist->ents );
+ }
+}
+
+#define _24_HOURS (3600 * 24)
+
+static int
+maildir_validate( const char *prefix, const char *box, int create )
+{
+ DIR *dirp;
+ struct dirent *entry;
+ time_t now;
+ int i, bl;
+ struct stat st;
+ char buf[_POSIX_PATH_MAX];
+
+ bl = nfsnprintf( buf, sizeof(buf) - 4, "%s%s/", prefix, box );
+ if (stat( buf, &st )) {
+ if (errno == ENOENT) {
+ if (create) {
+ if (mkdir( buf, 0700 )) {
+ fprintf( stderr, "Maildir error: mkdir %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ return DRV_STORE_BAD;
+ }
+ for (i = 0; i < 3; i++) {
+ memcpy( buf + bl, subdirs[i], 4 );
+ if (mkdir( buf, 0700 )) {
+ fprintf( stderr, "Maildir error: mkdir %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ return DRV_BOX_BAD;
+ }
+ }
+ } else {
+ fprintf( stderr, "Maildir error: mailbox '%s' does not exist\n", buf );
+ return DRV_BOX_BAD;
+ }
+ } else {
+ fprintf( stderr, "Maildir error: stat %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ return DRV_BOX_BAD;
+ }
+ } else {
+ for (i = 0; i < 3; i++) {
+ memcpy( buf + bl, subdirs[i], 4 );
+ if (stat( buf, &st ) || !S_ISDIR(st.st_mode)) {
+ fprintf( stderr, "Maildir error: '%s' is no valid mailbox\n", buf );
+ return DRV_BOX_BAD;
+ }
+ }
+ memcpy( buf + bl, "tmp/", 5 );
+ bl += 4;
+ if (!(dirp = opendir( buf ))) {
+ fprintf( stderr, "Maildir error: opendir: %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ return DRV_BOX_BAD;
+ }
+ time( &now );
+ while ((entry = readdir( dirp ))) {
+ nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", entry->d_name );
+ if (stat( buf, &st ))
+ fprintf( stderr, "Maildir error: stat: %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ else if (S_ISREG(st.st_mode) && now - st.st_ctime >= _24_HOURS) {
+ /* this should happen infrequently enough that it won't be
+ * bothersome to the user to display when it occurs.
+ */
+ info( "Maildir notice: removing stale file %s\n", buf );
+ if (unlink( buf ))
+ fprintf( stderr, "Maildir error: unlink: %s: %s (errno %d)\n",
+ buf, strerror(errno), errno );
+ }
+ }
+ closedir( dirp );
+ }
+ return DRV_OK;
+}
+
+static int
+maildir_uidval_lock( maildir_store_t *ctx )
+{
+ int n;
+ char buf[128];
+
+ /* This is legacy only */
+ if (flock( ctx->uvfd, LOCK_EX ) < 0) {
+ fprintf( stderr, "Maildir error: cannot flock UIDVALIDITY.\n" );
+ return DRV_BOX_BAD;
+ }
+ /* This (theoretically) works over NFS. Let's hope nobody else did
+ the same in the opposite order, as we'd deadlock then. */
+#if SEEK_SET != 0
+ lck.l_whence = SEEK_SET;
+#endif
+ lck.l_type = F_WRLCK;
+ if (fcntl( ctx->uvfd, F_SETLKW, &lck )) {
+ fprintf( stderr, "Maildir error: cannot fcntl lock UIDVALIDITY.\n" );
+ return DRV_BOX_BAD;
+ }
+ lseek( ctx->uvfd, 0, SEEK_SET );
+ if ((n = read( ctx->uvfd, buf, sizeof(buf) )) <= 0 ||
+ (buf[n] = 0, sscanf( buf, "%d\n%d", &ctx->gen.uidvalidity, &ctx->nuid ) != 2)) {
+ info( "Maildir notice: cannot read UIDVALIDITY, creating new.\n" );
+ ctx->gen.uidvalidity = time( 0 );
+ ctx->nuid = 0;
+ ctx->uvok = 0;
+ } else
+ ctx->uvok = 1;
+ return DRV_OK;
+}
+
+static void
+maildir_uidval_unlock( maildir_store_t *ctx )
+{
+ lck.l_type = F_UNLCK;
+ fcntl( ctx->uvfd, F_SETLK, &lck );
+ /* This is legacy only */
+ flock( ctx->uvfd, LOCK_UN );
+}
+
+static int
+maildir_obtain_uid( maildir_store_t *ctx, int *uid )
+{
+ int n;
+ char buf[128];
+
+ *uid = ++ctx->nuid;
+ n = sprintf( buf, "%d\n%d\n", ctx->gen.uidvalidity, ctx->nuid );
+ lseek( ctx->uvfd, 0, SEEK_SET );
+ if (write( ctx->uvfd, buf, n ) != n || ftruncate( ctx->uvfd, n )) {
+ fprintf( stderr, "Maildir error: cannot write UIDVALIDITY.\n" );
+ return DRV_BOX_BAD;
+ }
+ return DRV_OK;
+}
+
+#ifdef USE_DB
+static void
+make_key( DBT *tkey, char *name )
+{
+ char *u = strpbrk( name, ":," );
+ tkey->data = name;
+ tkey->size = u ? (size_t)(u - name) : strlen( name );
+}
+
+static int
+maildir_set_uid( maildir_store_t *ctx, const char *name, int *uid )
+{
+ int ret, uv[2];
+
+ *uid = ++ctx->nuid;
+ key.data = (void *)"UIDVALIDITY";
+ key.size = 11;
+ uv[0] = ctx->gen.uidvalidity;
+ uv[1] = ctx->nuid;
+ value.data = uv;
+ value.size = sizeof(uv);
+ if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 ))) {
+ tbork:
+ ctx->db->err( ctx->db, ret, "Maildir error: db->put()" );
+ bork:
+ ctx->db->close( ctx->db, 0 );
+ ctx->db = 0;
+ return DRV_BOX_BAD;
+ }
+ make_key( &key, (char *)name );
+ value.data = uid;
+ value.size = sizeof(*uid);
+ if ((ret = ctx->db->put( ctx->db, 0, &key, &value, 0 )))
+ goto tbork;
+ if ((ret = ctx->db->sync( ctx->db, 0 ))) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->sync()" );
+ goto bork;
+ }
+ return DRV_OK;
+}
+#endif /* USE_DB */
+
+static int
+maildir_compare( const void *l, const void *r )
+{
+ msg_t *lm = (msg_t *)l, *rm = (msg_t *)r;
+ char *ldot, *rdot, *ldot2, *rdot2, *lseq, *rseq;
+ int ret, llen, rlen;
+
+ if ((ret = lm->uid - rm->uid))
+ return ret;
+
+ /* No UID, so sort by arrival date. We should not do this, but we rely
+ on the suggested unique file name scheme - we have no choice. */
+ /* The first field are always the seconds. Alphabetical sort should be
+ faster than numeric. */
+ if (!(ldot = strchr( lm->base, '.' )) || !(rdot = strchr( rm->base, '.' )))
+ goto stronly; /* Should never happen ... */
+ llen = ldot - lm->base, rlen = rdot - rm->base;
+ /* The shorter number is smaller. Really. This won't trigger with any
+ mail created after Sep 9 2001 anyway. */
+ if ((ret = llen - rlen))
+ return ret;
+ if ((ret = memcmp( lm->base, rm->base, llen )))
+ return ret;
+
+ ldot++, rdot++;
+
+ if ((llen = strtol( ldot, &ldot2, 10 ))) {
+ if (!(rlen = strtol( rdot, &rdot2, 10 )))
+ goto stronly; /* Comparing apples to oranges ... */
+ /* Classical PID specs */
+ if ((ret = llen - rlen)) {
+ retpid:
+ /* Handle PID wraparound. This works only on systems
+ where PIDs are not reused too fast */
+ if (ret > 20000 || ret < -20000)
+ ret = -ret;
+ return ret;
+ }
+ return (*ldot2 != '_' ? 0 : atoi( ldot2 + 1 )) -
+ (*rdot2 != '_' ? 0 : atoi( rdot2 + 1 ));
+ }
+
+ if (!(ldot2 = strchr( ldot, '.' )) || !(rdot2 = strchr( rdot, '.' )))
+ goto stronly; /* Should never happen ... */
+ llen = ldot2 - ldot, rlen = rdot2 - rdot;
+
+ if (((lseq = memchr( ldot, '#', llen )) && (rseq = memchr( rdot, '#', rlen ))) ||
+ ((lseq = memchr( ldot, 'M', llen )) && (rseq = memchr( rdot, 'M', rlen ))))
+ return atoi( lseq + 1 ) - atoi( rseq + 1 );
+
+ if ((lseq = memchr( ldot, 'P', llen )) && (rseq = memchr( rdot, 'P', rlen ))) {
+ if ((ret = atoi( lseq + 1 ) - atoi( rseq + 1 )))
+ goto retpid;
+ if ((lseq = memchr( ldot, 'Q', llen )) && (rseq = memchr( rdot, 'Q', rlen )))
+ return atoi( lseq + 1 ) - atoi( rseq + 1 );
+ }
+
+ stronly:
+ /* Fall-back, so the sort order is defined at all */
+ return strcmp( lm->base, rm->base );
+}
+
+static int
+maildir_scan( maildir_store_t *ctx, msglist_t *msglist )
+{
+ DIR *d;
+ struct dirent *e;
+ const char *u, *ru;
+#ifdef USE_DB
+ DB *tdb;
+ DBC *dbc;
+#endif /* USE_DB */
+ msg_t *entry;
+ int i, j, uid, bl, ml, fnl, ret;
+ struct stat st;
+ char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
+
+#ifdef USE_DB
+ if (!ctx->db)
+#endif /* USE_DB */
+ if ((ret = maildir_uidval_lock( ctx )) != DRV_OK)
+ return ret;
+
+ again:
+ msglist->ents = 0;
+ msglist->nents = msglist->nalloc = 0;
+ ctx->gen.count = ctx->gen.recent = 0;
+ if (ctx->uvok || ctx->maxuid == INT_MAX) {
+#ifdef USE_DB
+ if (ctx->db) {
+ if (db_create( &tdb, 0, 0 )) {
+ fputs( "Maildir error: db_create() failed\n", stderr );
+ return DRV_BOX_BAD;
+ }
+ if (tdb->open( tdb, 0, 0, 0, DB_HASH, DB_CREATE, 0 )) {
+ fputs( "Maildir error: tdb->open() failed\n", stderr );
+ tdb->close( tdb, 0 );
+ return DRV_BOX_BAD;
+ }
+ }
+#endif /* USE_DB */
+ bl = nfsnprintf( buf, sizeof(buf) - 4, "%s/", ctx->gen.path );
+ for (i = 0; i < 2; i++) {
+ memcpy( buf + bl, subdirs[i], 4 );
+ if (!(d = opendir( buf ))) {
+ perror( buf );
+#ifdef USE_DB
+ if (!ctx->db)
+#endif /* USE_DB */
+ maildir_uidval_unlock( ctx );
+#ifdef USE_DB
+ bork:
+#endif /* USE_DB */
+ maildir_free_scan( msglist );
+#ifdef USE_DB
+ if (ctx->db)
+ tdb->close( tdb, 0 );
+#endif /* USE_DB */
+ return DRV_BOX_BAD;
+ }
+ while ((e = readdir( d ))) {
+ if (*e->d_name == '.')
+ continue;
+ ctx->gen.count++;
+ ctx->gen.recent += i;
+#ifdef USE_DB
+ if (ctx->db) {
+ make_key( &key, e->d_name );
+ if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) {
+ if (ret != DB_NOTFOUND) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->get()" );
+ ctx->db->close( ctx->db, 0 );
+ ctx->db = 0;
+ goto bork;
+ }
+ uid = INT_MAX;
+ } else {
+ value.size = 0;
+ if ((ret = tdb->put( tdb, 0, &key, &value, 0 ))) {
+ tdb->err( tdb, ret, "Maildir error: tdb->put()" );
+ goto bork;
+ }
+ uid = *(int *)value.data;
+ }
+ } else
+#endif /* USE_DB */
+ {
+ uid = (ctx->uvok && (u = strstr( e->d_name, ",U=" ))) ? atoi( u + 3 ) : 0;
+ if (!uid)
+ uid = INT_MAX;
+ }
+ if (uid <= ctx->maxuid) {
+ if (uid < ctx->minuid) {
+ for (j = 0; j < ctx->nexcs; j++)
+ if (ctx->excs[j] == uid)
+ goto oke;
+ continue;
+ oke: ;
+ }
+ if (msglist->nalloc == msglist->nents) {
+ msglist->nalloc = msglist->nalloc * 2 + 100;
+ msglist->ents = nfrealloc( msglist->ents, msglist->nalloc * sizeof(msg_t) );
+ }
+ entry = &msglist->ents[msglist->nents++];
+ entry->base = nfstrdup( e->d_name );
+ entry->uid = uid;
+ entry->recent = i;
+ entry->size = 0;
+ }
+ }
+ closedir( d );
+ }
+#ifdef USE_DB
+ if (ctx->db) {
+ if ((ret = ctx->db->cursor( ctx->db, 0, &dbc, 0 )))
+ ctx->db->err( ctx->db, ret, "Maildir error: db->cursor()" );
+ else {
+ for (;;) {
+ if ((ret = dbc->c_get( dbc, &key, &value, DB_NEXT ))) {
+ if (ret != DB_NOTFOUND)
+ ctx->db->err( ctx->db, ret, "Maildir error: db->c_get()" );
+ break;
+ }
+ if ((key.size != 11 || memcmp( key.data, "UIDVALIDITY", 11 )) &&
+ (ret = tdb->get( tdb, 0, &key, &value, 0 ))) {
+ if (ret != DB_NOTFOUND) {
+ tdb->err( tdb, ret, "Maildir error: tdb->get()" );
+ break;
+ }
+ if ((ret = dbc->c_del( dbc, 0 ))) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->c_del()" );
+ break;
+ }
+ }
+ }
+ dbc->c_close( dbc );
+ }
+ tdb->close( tdb, 0 );
+ }
+#endif /* USE_DB */
+ qsort( msglist->ents, msglist->nents, sizeof(msg_t), maildir_compare );
+ for (uid = i = 0; i < msglist->nents; i++) {
+ entry = &msglist->ents[i];
+ if (entry->uid != INT_MAX) {
+ if (uid == entry->uid) {
+ info( "Maildir notice: duplicate UID; changing UIDVALIDITY.\n" );
+ ctx->gen.uidvalidity = time( 0 );
+ ctx->nuid = 0;
+ ctx->uvok = 0;
+#ifdef USE_DB
+ if (ctx->db)
+ ctx->db->truncate( ctx->db, 0, 0 /* &u_int32_t_dummy */, 0 );
+#endif /* USE_DB */
+ maildir_free_scan( msglist );
+ goto again;
+ }
+ uid = entry->uid;
+ if (ctx->gen.opts & OPEN_SIZE)
+ nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base );
+#ifdef USE_DB
+ } else if (ctx->db) {
+ if ((ret = maildir_set_uid( ctx, entry->base, &uid )) != DRV_OK) {
+ maildir_free_scan( msglist );
+ return ret;
+ }
+ entry->uid = uid;
+ if (ctx->gen.opts & OPEN_SIZE)
+ nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s", subdirs[entry->recent], entry->base );
+#endif /* USE_DB */
+ } else {
+ if ((ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK) {
+ maildir_free_scan( msglist );
+ return ret;
+ }
+ entry->uid = uid;
+ if ((u = strstr( entry->base, ",U=" )))
+ for (ru = u + 3; isdigit( (unsigned char)*ru ); ru++);
+ else
+ u = ru = strchr( entry->base, ':' );
+ if (u)
+ ml = u - entry->base;
+ else
+ ru = "", ml = INT_MAX;
+ fnl = nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], ml, entry->base, uid, ru ) + 1 - 4;
+ memcpy( nbuf, buf, bl + 4 );
+ nfsnprintf( nbuf + bl + 4, sizeof(nbuf) - bl - 4, "%s", entry->base );
+ if (rename( nbuf, buf )) {
+ if (errno != ENOENT) {
+ perror( buf );
+ maildir_uidval_unlock( ctx );
+ maildir_free_scan( msglist );
+ return DRV_BOX_BAD;
+ }
+ maildir_free_scan( msglist );
+ goto again;
+ }
+ free( entry->base );
+ entry->base = nfmalloc( fnl );
+ memcpy( entry->base, buf + bl + 4, fnl );
+ }
+ if (ctx->gen.opts & OPEN_SIZE) {
+ if (stat( buf, &st )) {
+ maildir_free_scan( msglist );
+ goto again;
+ }
+ entry->size = st.st_size;
+ }
+ }
+ }
+#ifdef USE_DB
+ if (!ctx->db)
+#endif /* ! USE_DB */
+ maildir_uidval_unlock( ctx );
+ return DRV_OK;
+}
+
+static void
+maildir_init_msg( maildir_store_t *ctx, maildir_message_t *msg, msg_t *entry )
+{
+ msg->base = entry->base;
+ entry->base = 0; /* prevent deletion */
+ msg->gen.size = entry->size;
+ if (entry->recent)
+ msg->gen.status |= M_RECENT;
+ if (ctx->gen.opts & OPEN_FLAGS) {
+ msg->gen.status |= M_FLAGS;
+ msg->gen.flags = maildir_parse_flags( msg->base );
+ } else
+ msg->gen.flags = 0;
+}
+
+static void
+maildir_app_msg( maildir_store_t *ctx, message_t ***msgapp, msg_t *entry )
+{
+ maildir_message_t *msg = nfmalloc( sizeof(*msg) );
+ msg->gen.next = **msgapp;
+ **msgapp = &msg->gen;
+ *msgapp = &msg->gen.next;
+ msg->gen.uid = entry->uid;
+ msg->gen.status = 0;
+ maildir_init_msg( ctx, msg, entry );
+}
+
+static void
+maildir_prepare( store_t *gctx, int opts )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+
+ maildir_cleanup( gctx );
+ gctx->msgs = 0;
+ ctx->uvfd = -1;
+#ifdef USE_DB
+ ctx->db = 0;
+#endif /* USE_DB */
+ if (!strcmp( gctx->name, "INBOX" ))
+ gctx->path = nfstrdup( ((maildir_store_conf_t *)gctx->conf)->inbox );
+ else
+ nfasprintf( &gctx->path, "%s%s", gctx->conf->path, gctx->name );
+ if (opts & OPEN_SETFLAGS)
+ opts |= OPEN_OLD;
+ if (opts & OPEN_EXPUNGE)
+ opts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS;
+ gctx->opts = opts;
+}
+
+static int
+maildir_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+ message_t **msgapp;
+ msglist_t msglist;
+ int i;
+#ifdef USE_DB
+ int ret;
+#endif /* USE_DB */
+ char uvpath[_POSIX_PATH_MAX];
+
+ ctx->minuid = minuid;
+ ctx->maxuid = maxuid;
+ ctx->excs = nfrealloc( excs, nexcs * sizeof(int) );
+ ctx->nexcs = nexcs;
+
+ if (maildir_validate( gctx->path, "", ctx->gen.opts & OPEN_CREATE ) != DRV_OK)
+ return DRV_BOX_BAD;
+
+ nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", gctx->path );
+#ifndef USE_DB
+ if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) < 0) {
+ perror( uvpath );
+ return DRV_BOX_BAD;
+ }
+#else
+ if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) {
+ nfsnprintf( uvpath, sizeof(uvpath), "%s/.isyncuidmap.db", gctx->path );
+ if ((ctx->uvfd = open( uvpath, O_RDWR, 0600 )) < 0) {
+ if (((maildir_store_conf_t *)gctx->conf)->alt_map) {
+ if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
+ goto dbok;
+ } else {
+ nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", gctx->path );
+ if ((ctx->uvfd = open( uvpath, O_RDWR|O_CREAT, 0600 )) >= 0)
+ goto fnok;
+ }
+ perror( uvpath );
+ return DRV_BOX_BAD;
+ }
+ dbok:
+#if SEEK_SET != 0
+ lck.l_whence = SEEK_SET;
+#endif
+ lck.l_type = F_WRLCK;
+ if (fcntl( ctx->uvfd, F_SETLKW, &lck )) {
+ perror( uvpath );
+ bork:
+ close( ctx->uvfd );
+ ctx->uvfd = -1;
+ return DRV_BOX_BAD;
+ }
+ if (db_create( &ctx->db, 0, 0 )) {
+ fputs( "Maildir error: db_create() failed\n", stderr );
+ goto bork;
+ }
+ if ((ret = ctx->db->open( ctx->db, 0, uvpath, 0, DB_HASH, DB_CREATE, 0 ))) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->open(%s)", uvpath );
+ dbork:
+ ctx->db->close( ctx->db, 0 );
+ goto bork;
+ }
+ key.data = (void *)"UIDVALIDITY";
+ key.size = 11;
+ if ((ret = ctx->db->get( ctx->db, 0, &key, &value, 0 ))) {
+ if (ret != DB_NOTFOUND) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->get()" );
+ goto dbork;
+ }
+ info( "Maildir notice: cannot read UIDVALIDITY, creating new.\n" );
+ ctx->gen.uidvalidity = time( 0 );
+ ctx->nuid = 0;
+ ctx->uvok = 0;
+ } else {
+ ctx->gen.uidvalidity = ((int *)value.data)[0];
+ ctx->nuid = ((int *)value.data)[1];
+ ctx->uvok = 1;
+ }
+ }
+ fnok:
+#endif /* USE_DB */
+
+ if (maildir_scan( ctx, &msglist ) != DRV_OK)
+ return DRV_BOX_BAD;
+ msgapp = &ctx->gen.msgs;
+ for (i = 0; i < msglist.nents; i++)
+ maildir_app_msg( ctx, &msgapp, msglist.ents + i );
+ maildir_free_scan( &msglist );
+
+ return DRV_OK;
+}
+
+static int
+maildir_rescan( maildir_store_t *ctx )
+{
+ message_t **msgapp;
+ maildir_message_t *msg;
+ msglist_t msglist;
+ int i;
+
+ if (maildir_scan( ctx, &msglist ) != DRV_OK)
+ return DRV_BOX_BAD;
+ ctx->gen.recent = 0;
+ for (msgapp = &ctx->gen.msgs, i = 0;
+ (msg = (maildir_message_t *)*msgapp) || i < msglist.nents; )
+ {
+ if (!msg || msglist.ents[i].uid < msg->gen.uid) {
+ debug( "ignoring new message %d\n", msglist.ents[i].uid );
+ /* maildir_app_msg( ctx, &msgapp, msglist.ents + i ); */
+ i++;
+ } else if (i >= msglist.nents || msglist.ents[i].uid > msg->gen.uid) {
+ debug( "purging deleted message %d\n", msg->gen.uid );
+ msg->gen.status = M_DEAD;
+ msgapp = &msg->gen.next;
+ } else {
+ debug( "updating message %d\n", msg->gen.uid );
+ msg->gen.status &= ~(M_FLAGS|M_RECENT);
+ free( msg->base );
+ maildir_init_msg( ctx, msg, msglist.ents + i );
+ i++, msgapp = &msg->gen.next;
+ }
+ }
+ maildir_free_scan( &msglist );
+ return DRV_OK;
+}
+
+static int
+maildir_again( maildir_store_t *ctx, maildir_message_t *msg, const char *fn )
+{
+ int ret;
+
+ if (errno != ENOENT) {
+ perror( fn );
+ return DRV_BOX_BAD;
+ }
+ if ((ret = maildir_rescan( ctx )) != DRV_OK)
+ return ret;
+ return (msg->gen.status & M_DEAD) ? DRV_OK : DRV_MSG_BAD;
+}
+
+static int
+maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+ maildir_message_t *msg = (maildir_message_t *)gmsg;
+ int fd, ret;
+ struct stat st;
+ char buf[_POSIX_PATH_MAX];
+
+ for (;;) {
+ nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base );
+ if ((fd = open( buf, O_RDONLY )) >= 0)
+ break;
+ if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK)
+ return ret;
+ }
+ fstat( fd, &st );
+ data->crlf = 0;
+ data->len = st.st_size;
+ data->data = nfmalloc( data->len );
+ if (read( fd, data->data, data->len ) != data->len) {
+ perror( buf );
+ close( fd );
+ return DRV_MSG_BAD;
+ }
+ close( fd );
+ if (!(gmsg->status & M_FLAGS))
+ data->flags = maildir_parse_flags( msg->base );
+ return DRV_OK;
+}
+
+static int
+maildir_make_flags( int flags, char *buf )
+{
+ unsigned i, d;
+
+ buf[0] = ':';
+ buf[1] = '2';
+ buf[2] = ',';
+ for (d = 3, i = 0; i < as(Flags); i++)
+ if (flags & (1 << i))
+ buf[d++] = Flags[i];
+ buf[d] = 0;
+ return d;
+}
+
+static int
+maildir_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+ const char *prefix, *box;
+ int ret, fd, bl;
+ char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX], fbuf[NUM_FLAGS + 3], base[128];
+
+ bl = nfsnprintf( base, sizeof(base), "%ld.%d_%d.%s", time( 0 ), Pid, ++MaildirCount, Hostname );
+ if (uid) {
+#ifdef USE_DB
+ if (ctx->db) {
+ if ((ret = maildir_set_uid( ctx, base, uid )) != DRV_OK) {
+ free( data->data );
+ return ret;
+ }
+ } else
+#endif /* USE_DB */
+ {
+ if ((ret = maildir_uidval_lock( ctx )) != DRV_OK ||
+ (ret = maildir_obtain_uid( ctx, uid )) != DRV_OK)
+ return ret;
+ maildir_uidval_unlock( ctx );
+ nfsnprintf( base + bl, sizeof(base) - bl, ",U=%d", *uid );
+ }
+ prefix = gctx->path;
+ box = "";
+ } else {
+ prefix = gctx->conf->path;
+ box = gctx->conf->trash;
+ }
+
+ maildir_make_flags( data->flags, fbuf );
+ nfsnprintf( buf, sizeof(buf), "%s%s/tmp/%s%s", prefix, box, base, fbuf );
+ if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
+ if (errno != ENOENT) {
+ perror( buf );
+ free( data->data );
+ return DRV_BOX_BAD;
+ }
+ if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, gctx->opts & OPEN_CREATE )) != DRV_OK) {
+ free( data->data );
+ return ret;
+ }
+ if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
+ perror( buf );
+ free( data->data );
+ return DRV_BOX_BAD;
+ }
+ }
+ strip_cr( data );
+ ret = write( fd, data->data, data->len );
+ free( data->data );
+ if (ret != data->len) {
+ if (ret < 0)
+ perror( buf );
+ else
+ fprintf( stderr, "Maildir error: %s: partial write\n", buf );
+ close( fd );
+ return DRV_BOX_BAD;
+ }
+ close( fd );
+ nfsnprintf( nbuf, sizeof(nbuf), "%s%s/new/%s%s", prefix, box, base, fbuf );
+ if (rename( buf, nbuf )) {
+ perror( nbuf );
+ return DRV_BOX_BAD;
+ }
+ if (uid)
+ gctx->count++;
+ return DRV_OK;
+}
+
+static int
+maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+ maildir_message_t *msg = (maildir_message_t *)gmsg;
+ char *s, *p;
+ unsigned i;
+ int j, ret, ol, fl, bbl, bl, tl;
+ char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
+
+ (void) uid;
+ bbl = nfsnprintf( buf, sizeof(buf), "%s/", gctx->path );
+ for (;;) {
+ bl = bbl + nfsnprintf( buf + bbl, sizeof(buf) - bbl, "%s/", subdirs[gmsg->status & M_RECENT] );
+ ol = strlen( msg->base );
+ if ((int)sizeof(buf) - bl < ol + 3 + NUM_FLAGS)
+ oob();
+ memcpy( buf + bl, msg->base, ol + 1 );
+ memcpy( nbuf, buf, bl + ol + 1 );
+ if ((s = strstr( nbuf + bl, ":2," ))) {
+ s += 3;
+ fl = ol - (s - (nbuf + bl));
+ for (i = 0; i < as(Flags); i++) {
+ if ((p = strchr( s, Flags[i] ))) {
+ if (del & (1 << i)) {
+ memcpy( p, p + 1, fl - (p - s) );
+ fl--;
+ }
+ } else if (add & (1 << i)) {
+ for (j = 0; j < fl && Flags[i] > s[j]; j++);
+ fl++;
+ memmove( s + j + 1, s + j, fl - j );
+ s[j] = Flags[i];
+ }
+ }
+ tl = ol + 3 + fl;
+ } else {
+ tl = ol + maildir_make_flags( msg->gen.flags, nbuf + bl + ol );
+ }
+ if (!rename( buf, nbuf ))
+ break;
+ if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK)
+ return ret;
+ }
+ free( msg->base );
+ msg->base = nfmalloc( tl + 1 );
+ memcpy( msg->base, nbuf + bl, tl + 1 );
+ msg->gen.flags |= add;
+ msg->gen.flags &= ~del;
+
+ return DRV_OK;
+}
+
+#ifdef USE_DB
+static int
+maildir_purge_msg( maildir_store_t *ctx, const char *name )
+{
+ int ret;
+
+ make_key( &key, (char *)name );
+ if ((ret = ctx->db->del( ctx->db, 0, &key, 0 ))) {
+ ctx->db->err( ctx->db, ret, "Maildir error: db->del()" );
+ ctx->db->close( ctx->db, 0 );
+ ctx->db = 0;
+ return DRV_BOX_BAD;
+ }
+ return DRV_OK;
+}
+#endif /* USE_DB */
+
+static int
+maildir_trash_msg( store_t *gctx, message_t *gmsg )
+{
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+ maildir_message_t *msg = (maildir_message_t *)gmsg;
+ char *s;
+ int ret;
+ struct stat st;
+ char buf[_POSIX_PATH_MAX], nbuf[_POSIX_PATH_MAX];
+
+ for (;;) {
+ nfsnprintf( buf, sizeof(buf), "%s/%s/%s", gctx->path, subdirs[gmsg->status & M_RECENT], msg->base );
+ s = strstr( msg->base, ":2," );
+ nfsnprintf( nbuf, sizeof(nbuf), "%s%s/new/%ld.%d_%d.%s%s", gctx->conf->path, gctx->conf->trash,
+ time( 0 ), Pid, ++MaildirCount, Hostname, s ? s : "" );
+ if (!rename( buf, nbuf ))
+ break;
+ if (!stat( buf, &st )) {
+ if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, 1 )) != DRV_OK)
+ return ret;
+ if (!rename( buf, nbuf ))
+ break;
+ perror( nbuf );
+ return DRV_BOX_BAD;
+ }
+ if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK)
+ return ret;
+ }
+ gmsg->status |= M_DEAD;
+ gctx->count--;
+
+#ifdef USE_DB
+ if (ctx->db)
+ return maildir_purge_msg( ctx, msg->base );
+#endif /* USE_DB */
+ return DRV_OK;
+}
+
+static int
+maildir_close( store_t *gctx )
+{
+#ifdef USE_DB
+ maildir_store_t *ctx = (maildir_store_t *)gctx;
+#endif /* USE_DB */
+ message_t *msg;
+ int basel, retry, ret;
+ char buf[_POSIX_PATH_MAX];
+
+ for (;;) {
+ retry = 0;
+ basel = nfsnprintf( buf, sizeof(buf), "%s/", gctx->path );
+ for (msg = gctx->msgs; msg; msg = msg->next)
+ if (!(msg->status & M_DEAD) && (msg->flags & F_DELETED)) {
+ nfsnprintf( buf + basel, sizeof(buf) - basel, "%s/%s", subdirs[msg->status & M_RECENT], ((maildir_message_t *)msg)->base );
+ if (unlink( buf )) {
+ if (errno == ENOENT)
+ retry = 1;
+ else
+ perror( buf );
+ } else {
+ msg->status |= M_DEAD;
+ gctx->count--;
+#ifdef USE_DB
+ if (ctx->db && (ret = maildir_purge_msg( ctx, ((maildir_message_t *)msg)->base )) != DRV_OK)
+ return ret;
+#endif /* USE_DB */
+ }
+ }
+ if (!retry)
+ return DRV_OK;
+ if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK)
+ return ret;
+ }
+}
+
+static int
+maildir_check( store_t *gctx )
+{
+ (void) gctx;
+ return DRV_OK;
+}
+
+static int
+maildir_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
+{
+ maildir_store_conf_t *store;
+
+ if (strcasecmp( "MaildirStore", cfg->cmd ))
+ return 0;
+ store = nfcalloc( sizeof(*store) );
+ store->gen.driver = &maildir_driver;
+ store->gen.name = nfstrdup( cfg->val );
+
+ while (getcline( cfg ) && cfg->cmd)
+ if (!strcasecmp( "Inbox", cfg->cmd ))
+ store->inbox = expand_strdup( cfg->val );
+ else if (!strcasecmp( "Path", cfg->cmd ))
+ store->gen.path = expand_strdup( cfg->val );
+#ifdef USE_DB
+ else if (!strcasecmp( "AltMap", cfg->cmd ))
+ store->alt_map = parse_bool( cfg );
+#endif /* USE_DB */
+ else
+ parse_generic_store( &store->gen, cfg, err );
+ if (!store->inbox)
+ store->inbox = expand_strdup( "~/Maildir" );
+ *storep = &store->gen;
+ return 1;
+}
+
+struct driver maildir_driver = {
+ maildir_parse_store,
+ maildir_open_store,
+ maildir_close_store,
+ maildir_list,
+ maildir_prepare,
+ maildir_select,
+ maildir_fetch_msg,
+ maildir_store_msg,
+ maildir_set_flags,
+ maildir_trash_msg,
+ maildir_check,
+ maildir_close
+};
diff --git a/src/imap.c b/src/imap.c
@@ -1,1413 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 2002-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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-#include "isync.h"
-
-#include <assert.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/time.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.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
-
-#define as(ar) (sizeof(ar)/sizeof(ar[0]))
-
-#define CAP(cap) (imap->caps & (1 << (cap)))
-
-static int Tag;
-
-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)
- {
- fprintf (stderr, "Error, no server certificate\n");
- return -1;
- }
-
- err = SSL_get_verify_result (ssl);
- if (err == X509_V_OK)
- return 0;
-
- fprintf (stderr, "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));
- info ("\nSubject: %s\n", buf);
- X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf));
- info ("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);
- info ("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);
- info (" to: %s\n", buf);
-
- fprintf (stderr,
- "\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"
- "\nAccept this certificate anyway? [no]: ");
- if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y'))
- {
- ret = 0;
- fprintf (stderr, "\n*** Fine, but don't say I didn't warn you!\n\n");
- }
- return ret;
-}
-
-static int
-init_ssl (config_t * conf)
-{
- SSL_METHOD *method;
- int options = 0;
-
- if (!conf->cert_file)
- {
- fprintf (stderr, "Error, CertificateFile not defined\n");
- return -1;
- }
- SSL_library_init ();
- SSL_load_error_strings ();
-
- if (conf->use_tlsv1 && !conf->use_sslv2 && !conf->use_sslv3)
- method = TLSv1_client_method ();
- else
- method = SSLv23_client_method ();
-
- SSLContext = SSL_CTX_new (method);
-
- if (access (conf->cert_file, R_OK))
- {
- if (errno != ENOENT)
- {
- perror ("access");
- return -1;
- }
- warn ("*** Warning: CertificateFile doesn't exist, can't verify server certificates\n");
- }
- else
- if (!SSL_CTX_load_verify_locations
- (SSLContext, conf->cert_file, NULL))
- {
- fprintf (stderr, "Error, SSL_CTX_load_verify_locations: %s\n",
- ERR_error_string (ERR_get_error (), 0));
- return -1;
- }
-
- if (!conf->use_sslv2)
- options |= SSL_OP_NO_SSLv2;
- if (!conf->use_sslv3)
- options |= SSL_OP_NO_SSLv3;
- if (!conf->use_tlsv1)
- options |= SSL_OP_NO_TLSv1;
-
- SSL_CTX_set_options (SSLContext, options);
-
- /* 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);
-}
-
-static void
-socket_perror (const char *func, Socket_t *sock, int ret)
-{
-#if HAVE_LIBSSL
- int err;
-
- if (sock->use_ssl)
- {
- switch ((err = SSL_get_error (sock->ssl, ret)))
- {
- case SSL_ERROR_SYSCALL:
- case SSL_ERROR_SSL:
- if ((err = ERR_get_error ()) == 0)
- {
- if (ret == 0)
- fprintf (stderr, "SSL_%s:got EOF\n", func);
- else
- fprintf (stderr, "SSL_%s:%d:%s\n", func,
- errno, strerror (errno));
- }
- else
- fprintf (stderr, "SSL_%s:%d:%s\n", func, err,
- ERR_error_string (err, 0));
- return;
- default:
- fprintf (stderr, "SSL_%s:%d:unhandled SSL error\n", func, err);
- break;
- }
- return;
- }
-#else
- (void) sock;
-#endif
- if (ret)
- perror (func);
- else
- fprintf (stderr, "%s: unexpected EOF\n", func);
-}
-
-/* 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)
- {
- socket_perror ("read", b->sock, n);
- 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 */
- if (Verbose) {
- puts (*s);
- fflush (stdout);
- }
- return 0;
- }
- }
-
- b->offset++;
- }
- /* not reached */
-}
-
-static int
-parse_fetch (imap_t *imap, char *cmd)
-{
- list_t *tmp, *list, *flags;
- unsigned int uid = 0;
- unsigned int mask = 0;
- unsigned int size = 0;
- message_t *cur;
-
- list = parse_list (cmd, 0);
-
- if (!is_list (list)) {
- free_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 */
- free_list (list);
- return 0;
- }
- else if (uid > imap->maxuid)
- imap->maxuid = uid;
- }
- else
- fprintf (stderr, "IMAP error: unable to parse UID\n");
- }
- else if (!strcmp ("FLAGS", tmp->val))
- {
- tmp = tmp->next;
- if (is_list (tmp))
- {
- for (flags = tmp->child; 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
- fprintf (stderr, "IMAP error: unknown flag %s\n",
- flags->val);
- }
- else
- fprintf (stderr, "IMAP error: unable to parse FLAGS list\n");
- }
- }
- else
- fprintf (stderr, "IMAP error: unable to parse FLAGS\n");
- }
- 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;
-
- free_list (list);
- return 0;
-}
-
-/* Keep this in sync with enum CAPABILITY */
-const char *cap_list[] = {
- "LOGINDISABLED",
- "UIDPLUS",
- "NAMESPACE",
-#if HAVE_LIBSSL
- "AUTH=CRAM-MD5",
- "STARTTLS",
-#endif
-};
-
-static void
-parse_capability (imap_t *imap, char *cmd)
-{
- char *arg;
- unsigned i;
-
- imap->caps = 0x80000000;
- while ((arg = next_arg (&cmd)))
- for (i = 0; i < as(cap_list); i++)
- if (!strcmp (cap_list[i], arg))
- imap->caps |= 1 << i;
-}
-
-static void
-parse_response_code (imap_t * imap, char *s)
-{
- char *arg, *p;
-
- if (*s != '[')
- return; /* no response code */
- s++;
- if (!(p = strchr (s, ']')))
- {
- fprintf (stderr, "IMAP error: malformed response code\n");
- return;
- }
- *p++ = 0;
-
- arg = next_arg (&s);
-
- if (!strcmp ("UIDVALIDITY", arg))
- {
- arg = next_arg (&s);
- imap->uidvalidity = atol (arg);
- }
- else if (!strcmp ("CAPABILITY", arg))
- {
- parse_capability (imap, s);
- }
- else if (!strcmp ("ALERT", arg))
- {
- /* RFC2060 says that these messages MUST be displayed
- * to the user
- */
- for (; isspace ((unsigned char)*p); p++);
- fprintf (stderr, "*** IMAP ALERT *** %s\n", p);
- }
-}
-
-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;
- config_t *box;
- int n;
-
- 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) {
- printf (">>> %s", buf);
- fflush (stdout);
- }
- n = socket_write (imap->sock, buf, strlen (buf));
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
- return -1;
- }
-
- for (;;)
- {
- next:
- if (buffer_gets (imap->buf, &cmd))
- return -1;
-
- arg = next_arg (&cmd);
- if (*arg == '*')
- {
- arg = next_arg (&cmd);
- if (!arg)
- {
- fprintf (stderr, "IMAP error: unable to parse untagged response\n");
- 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 ("BYE", arg))
- {
- parse_response_code (imap, cmd);
- }
- else if (!strcmp ("CAPABILITY", arg))
- {
- parse_capability (imap, cmd);
- }
- else if (!strcmp ("LIST", arg))
- {
- list_t *list, *lp;
- int l;
-
- list = parse_list (cmd, &cmd);
- if (list->val == LIST)
- for (lp = list->child; lp; lp = lp->next)
- if (is_atom (lp) &&
- !strcasecmp (lp->val, "\\NoSelect"))
- {
- free_list (list);
- goto next;
- }
- free_list (list);
- (void) next_arg (&cmd); /* skip delimiter */
- arg = next_arg (&cmd);
- l = strlen (global.folder);
- if (memcmp (arg, global.folder, l))
- goto next;
- arg += l;
- if (!memcmp (arg + strlen (arg) - 5, ".lock", 5))
- goto next;
- for (box = boxes; box; box = box->next)
- if (!strcmp (box->box, arg))
- goto next;
- box = malloc (sizeof (config_t));
- memcpy (box, &global, sizeof (config_t));
- box->path = strdup (arg);
- box->box = box->path;
- box->next = boxes;
- boxes = box;
- }
- 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))
- {
- if (parse_fetch (imap, cmd))
- return -1;
- }
- }
- else
- {
- fprintf (stderr, "IMAP error: unable to parse untagged response\n");
- return -1;
- }
- }
-#if HAVE_LIBSSL
- else if (*arg == '+')
- {
- char *resp;
-
- if (!imap->cram)
- {
- fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n");
- return -1;
- }
- resp = cram (cmd, imap->box->user, imap->box->pass);
-
- if (Verbose) {
- printf (">+> %s\n", resp);
- fflush (stdout);
- }
- n = socket_write (imap->sock, resp, strlen (resp));
- free (resp);
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
- return -1;
- }
- n = socket_write (imap->sock, "\r\n", 2);
- if (n <= 0)
- {
- socket_perror ("write", imap->sock, n);
- return -1;
- }
- imap->cram = 0;
- }
-#endif
- else if (atoi (arg) != Tag)
- {
- fprintf (stderr, "IMAP error: wrong tag\n");
- return -1;
- }
- else
- {
- arg = next_arg (&cmd);
- parse_response_code (imap, cmd);
- if (!strcmp ("OK", arg))
- return 0;
- return -1;
- }
- }
- /* not reached */
-}
-
-#ifdef HAVE_LIBSSL
-static int
-start_tls (imap_t *imap, config_t * cfg)
-{
- int ret;
-
- /* initialize SSL */
- if (init_ssl (cfg))
- return 1;
-
- imap->sock->ssl = SSL_new (SSLContext);
- SSL_set_fd (imap->sock->ssl, imap->sock->fd);
- if ((ret = SSL_connect (imap->sock->ssl)) <= 0)
- {
- socket_perror ("connect", imap->sock, ret);
- return 1;
- }
-
- /* verify the server certificate */
- if (verify_cert (imap->sock->ssl))
- return 1;
-
- imap->sock->use_ssl = 1;
- puts ("SSL support enabled");
- return 0;
-}
-#endif
-
-imap_t *
-imap_connect (config_t * cfg)
-{
- int s;
- struct sockaddr_in addr;
- struct hostent *he;
- imap_t *imap;
- char *arg, *rsp;
- int preauth;
-#if HAVE_LIBSSL
- int use_ssl;
-#endif
- int a[2];
-
- imap = calloc (1, sizeof (imap_t));
- imap->box = cfg;
- imap->sock = calloc (1, sizeof (Socket_t));
- imap->buf = calloc (1, sizeof (buffer_t));
- imap->buf->sock = imap->sock;
- imap->sock->fd = -1;
-
- /* open connection to IMAP server */
-
- if (cfg->tunnel)
- {
- info ("Starting tunnel '%s'...", cfg->tunnel);
- fflush (stdout);
-
- if (socketpair (PF_UNIX, SOCK_STREAM, 0, a))
- {
- perror ("socketpair");
- exit (1);
- }
-
- if (fork () == 0)
- {
- if (dup2 (a[0], 0) == -1 || dup2 (a[0], 1) == -1)
- {
- _exit (127);
- }
- close (a[0]);
- close (a[1]);
- execl ("/bin/sh", "sh", "-c", cfg->tunnel, 0);
- _exit (127);
- }
-
- close (a[0]);
-
- imap->sock->fd = a[1];
-
- info ("ok\n");
- }
- else
- {
- memset (&addr, 0, sizeof (addr));
- addr.sin_port = htons (cfg->port);
- addr.sin_family = AF_INET;
-
- info ("Resolving %s... ", cfg->host);
- fflush (stdout);
- he = gethostbyname (cfg->host);
- if (!he)
- {
- perror ("gethostbyname");
- goto bail;
- }
- info ("ok\n");
-
- addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
-
- s = socket (PF_INET, SOCK_STREAM, 0);
-
- info ("Connecting to %s:%hu... ", inet_ntoa (addr.sin_addr),
- ntohs (addr.sin_port));
- fflush (stdout);
- if (connect (s, (struct sockaddr *) &addr, sizeof (addr)))
- {
- close (s);
- perror ("connect");
- goto bail;
- }
- info ("ok\n");
-
- imap->sock->fd = s;
- }
-
-#if HAVE_LIBSSL
- use_ssl = 0;
- if (cfg->use_imaps) {
- if (start_tls (imap, cfg))
- goto bail;
- use_ssl = 1;
- }
-#endif
-
- /* read the greeting string */
- if (buffer_gets (imap->buf, &rsp))
- {
- fprintf (stderr, "IMAP error: no greeting response\n");
- goto bail;
- }
- arg = next_arg (&rsp);
- if (!arg || *arg != '*' || (arg = next_arg (&rsp)) == NULL)
- {
- fprintf (stderr, "IMAP error: invalid greeting response\n");
- goto bail;
- }
- preauth = 0;
- if (!strcmp ("PREAUTH", arg))
- preauth = 1;
- else if (strcmp ("OK", arg) != 0)
- {
- fprintf (stderr, "IMAP error: unknown greeting response\n");
- goto bail;
- }
- parse_response_code (imap, rsp);
- if (!imap->caps && imap_exec (imap, "CAPABILITY"))
- goto bail;
-
- if (!preauth)
- {
-
-#if HAVE_LIBSSL
- if (!cfg->use_imaps)
- {
- if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1)
- {
- /* always try to select SSL support if available */
- if (CAP(STARTTLS))
- {
- if (imap_exec (imap, "STARTTLS"))
- goto bail;
- if (start_tls (imap, cfg))
- goto bail;
- use_ssl = 1;
-
- if (imap_exec (imap, "CAPABILITY"))
- goto bail;
- }
- else
- {
- if (cfg->require_ssl)
- {
- fprintf (stderr, "IMAP error: SSL support not available\n");
- goto bail;
- }
- else
- warn ("IMAP warning: SSL support not available\n");
- }
- }
- }
-#endif
-
- info ("Logging in...\n");
-
- if (!cfg->pass)
- {
- /*
- * if we don't have a global password set, prompt the user for
- * it now.
- */
- if (!global.pass)
- {
- char prompt[80];
- sprintf(prompt, "Password (mailbox %s@%s/%s): ",
- cfg->user, cfg->host, cfg->box);
- global.pass = getpass (prompt);
- if (!global.pass)
- {
- perror ("getpass");
- exit (1);
- }
- if (!*global.pass)
- {
- fprintf (stderr, "Skipping %s, no password\n", cfg->path);
- global.pass = NULL; /* force retry */
- goto bail;
- }
- /*
- * getpass() returns a pointer to a static buffer. make a copy
- * for long term storage.
- */
- global.pass = strdup (global.pass);
- }
- cfg->pass = strdup (global.pass);
- }
-
-#if HAVE_LIBSSL
- if (CAP(CRAM))
- {
- info ("Authenticating with CRAM-MD5\n");
- imap->cram = 1;
- if (imap_exec (imap, "AUTHENTICATE CRAM-MD5"))
- goto bail;
- }
- else if (imap->box->require_cram)
- {
- fprintf (stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n");
- goto bail;
- }
- else
-#endif
- {
- if (CAP(NOLOGIN))
- {
- fprintf (stderr, "Skipping %s, server forbids LOGIN\n", cfg->path);
- goto bail;
- }
-#if HAVE_LIBSSL
- if (!use_ssl)
-#endif
- warn ("*** IMAP Warning *** Password is being sent in the clear\n");
- if (imap_exec (imap, "LOGIN \"%s\" \"%s\"", cfg->user, cfg->pass))
- {
- fprintf (stderr, "IMAP error: LOGIN failed\n");
- goto bail;
- }
- }
- } /* !preauth */
-
- /* get NAMESPACE info */
- if (!global.folder && cfg->use_namespace && CAP(NAMESPACE))
- {
- if (imap_exec (imap, "NAMESPACE"))
- goto bail;
- }
- return imap;
-
- bail:
- imap_close (imap);
- return 0;
-}
-
-static int
-mstrcmp (const char *s1, const char *s2)
-{
- if (s1 == s2)
- return 0;
- if (!s1 || !s2)
- return 1;
- return strcmp (s1, s2);
-}
-
-/* `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 imap_flags)
-{
- if (imap)
- {
- /* determine whether or not we can reuse the existing session */
- if (mstrcmp (box->tunnel, imap->box->tunnel) ||
- mstrcmp (box->host, imap->box->host) ||
- mstrcmp (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);
- }
- else
- {
- /* reset mailbox-specific state info */
- imap->box = box;
- imap->recent = 0;
- imap->deleted = 0;
- imap->count = 0;
- imap->maxuid = 0;
- free_message (imap->msgs);
- imap->msgs = 0;
- goto gotimap;
- }
- }
- if (!(imap = imap_connect (box)))
- return 0;
- gotimap:
-
- if (global.folder)
- imap->prefix = !strcmp (box->box, "INBOX") ? "" : global.folder;
- else
- {
- imap->prefix = "";
- /* 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))
- imap->prefix = imap->ns_personal->child->child->val;
- }
-
- info ("Selecting IMAP mailbox... ");
- fflush (stdout);
- if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)) {
- if (imap_flags & IMAP_CREATE) {
- if (imap_exec (imap, "CREATE \"%s%s\"", imap->prefix, box->box))
- goto bail;
- if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box))
- goto bail;
- } else
- goto bail;
- }
- info ("%d messages, %d recent\n", imap->count, imap->recent);
-
- info ("Reading IMAP mailbox index\n");
- imap->minuid = minuid;
- if (imap->count > 0)
- {
- if (imap_exec (imap, "UID FETCH %d:* (FLAGS%s)", minuid,
- (imap_flags & IMAP_GET_SIZE) ? " RFC822.SIZE" : ""))
- goto bail;
- }
-
- return imap;
-
- bail:
- imap_close (imap);
- return 0;
-}
-
-void
-imap_close (imap_t * imap)
-{
- if (imap)
- {
- if (imap->sock->fd != -1)
- {
- 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;
- ssize_t n;
-
- while (start < len)
- {
- while (end < len && buf[end] != '\r')
- end++;
- n = write (fd, buf + start, end - start);
- if (n == -1)
- {
- perror ("write");
- return -1;
- }
- else if ((size_t) n != end - start)
- {
- /* short write, try again */
- start += n;
- }
- else
- {
- /* write complete */
- end++;
- start = end;
- }
- }
- return 0;
-}
-
-static int
-send_server (Socket_t * sock, const char *fmt, ...)
-{
- char buf[128];
- char cmd[128];
- va_list ap;
- int n;
-
- va_start (ap, fmt);
- vsnprintf (buf, sizeof (buf), fmt, ap);
- va_end (ap);
-
- snprintf (cmd, sizeof (cmd), "%d %s\r\n", ++Tag, buf);
- if (Verbose) {
- printf (">>> %s", cmd);
- fflush (stdout);
- }
- n = socket_write (sock, cmd, strlen (cmd));
- if (n <= 0)
- {
- socket_perror ("write", sock, n);
- return -1;
- }
-
- return 0;
-}
-
-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 (*cmd == '*')
- {
- /* need to figure out how long the message is
- * * <msgno> FETCH (RFC822 {<size>}
- */
-
- next_arg (&cmd); /* * */
- next_arg (&cmd); /* <msgno> */
- arg = next_arg (&cmd); /* FETCH */
-
- if (strcasecmp ("FETCH", arg) != 0)
- {
- /* this is likely an untagged response, such as when new
- * mail arrives in the middle of the session. just skip
- * it for now.
- *
- * eg.,
- * "* 4000 EXISTS"
- * "* 2 RECENT"
- *
- */
- info ("IMAP info: skipping untagged response: %s\n", arg);
- continue;
- }
-
- while ((arg = next_arg (&cmd)) && *arg != '{')
- ;
- if (!arg)
- {
- fprintf (stderr, "IMAP error: parse error getting size\n");
- return -1;
- }
- bytes = strtol (arg + 1, 0, 10);
-
- /* 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.
- */
- if (write_strip (fd, imap->buf->buf + imap->buf->offset, n))
- {
- /* write failed, message is not delivered */
- return -1;
- }
-
- bytes -= 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)
- {
- if (write_strip (fd, buf, n))
- {
- /* write failed */
- return -1;
- }
- bytes -= n;
- }
- else
- {
- socket_perror ("read", imap->sock, n);
- return -1;
- }
- }
-
- buffer_gets (imap->buf, &cmd);
- }
- else
- {
- arg = next_arg (&cmd);
- if (!arg || atoi (arg) != Tag)
- {
- fprintf (stderr, "IMAP error: wrong tag\n");
- 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");
-}
-
-int
-imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox)
-{
- return imap_exec (imap, "UID COPY %u \"%s%s\"", uid, imap->prefix,
- mailbox);
-}
-
-int
-imap_append_message (imap_t * imap, int fd, message_t * msg)
-{
- char *fmap;
- int extra, uid, tuidl = 0;
- char flagstr[128], tuid[128];
- char *s;
- size_t i;
- size_t start;
- size_t len, sbreak = 0, ebreak = 0;
- char *arg;
- struct timeval tv;
- pid_t pid = getpid();
-
- len = msg->size;
- /* ugh, we need to count the number of newlines */
- fmap = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0);
- if (!fmap)
- {
- perror ("mmap");
- return -1;
- }
-
- extra = 0, i = 0;
- if (!CAP(UIDPLUS))
- {
- nloop:
- start = i;
- while (i < len)
- if (fmap[i++] == '\n')
- {
- extra++;
- if (i - 1 == start)
- {
- sbreak = ebreak = i - 1;
- goto mktid;
- }
- if (!memcmp (fmap + start, "X-TUID: ", 8))
- {
- extra -= (ebreak = i) - (sbreak = start) + 1;
- goto mktid;
- }
- goto nloop;
- }
- /* invalid mesasge */
- goto bail;
- mktid:
- gettimeofday (&tv, 0);
- tuidl = sprintf (tuid, "X-TUID: %08lx%05lx%04x\r\n",
- tv.tv_sec, tv.tv_usec, pid);
- extra += tuidl;
- }
- for (; i < len; i++)
- if (fmap[i] == '\n')
- extra++;
-
- flagstr[0] = 0;
- if (msg->flags)
- {
- if (msg->flags & D_DELETED)
- strcat (flagstr," \\Deleted");
- if (msg->flags & D_ANSWERED)
- strcat (flagstr," \\Answered");
- if (msg->flags & D_SEEN)
- strcat (flagstr," \\Seen");
- if (msg->flags & D_FLAGGED)
- strcat (flagstr," \\Flagged");
- if (msg->flags & D_DRAFT)
- strcat (flagstr," \\Draft");
- flagstr[0] = '(';
- strcat (flagstr,") ");
- }
-
- send_server (imap->sock, "APPEND %s%s %s{%d}",
- imap->prefix, imap->box->box, flagstr, len + extra);
-
- if (buffer_gets (imap->buf, &s))
- goto bail;
-
- if (*s != '+')
- {
- fprintf (stderr, "IMAP error: expected `+' from server (aborting)\n");
- goto bail;
- }
-
- i = 0;
- if (!CAP(UIDPLUS))
- {
- n1loop:
- start = i;
- while (i < sbreak)
- if (fmap[i++] == '\n')
- {
- socket_write (imap->sock, fmap + start, i - 1 - start);
- socket_write (imap->sock, "\r\n", 2);
- goto n1loop;
- }
- socket_write (imap->sock, tuid, tuidl);
- i = ebreak;
- }
- n2loop:
- start = i;
- while (i < len)
- if (fmap[i++] == '\n')
- {
- socket_write (imap->sock, fmap + start, i - 1 - start);
- socket_write (imap->sock, "\r\n", 2);
- goto n2loop;
- }
- socket_write (imap->sock, fmap + start, len - start);
- socket_write (imap->sock, "\r\n", 2);
-
- munmap (fmap, len);
-
- for (;;)
- {
- if (buffer_gets (imap->buf, &s))
- return -1;
-
- arg = next_arg (&s);
- if (*arg == '*')
- {
- /* XXX just ignore it for now */
- }
- else if (atoi (arg) != Tag)
- {
- fprintf (stderr, "IMAP error: wrong tag\n");
- return -1;
- }
- else
- {
- arg = next_arg (&s);
- if (strcmp (arg, "OK"))
- return -1;
- arg = next_arg (&s);
- if (*arg != '[')
- break;
- arg++;
- if (strcasecmp ("APPENDUID", arg))
- {
- fprintf (stderr, "IMAP error: expected APPENDUID\n");
- break;
- }
- arg = next_arg (&s);
- if (!arg)
- break;
- if (atoi (arg) != (int) imap->uidvalidity)
- {
- fprintf (stderr, "IMAP error: UIDVALIDITY doesn't match APPENDUID\n");
- return -1;
- }
- arg = next_arg (&s);
- if (!arg)
- break;
- uid = strtol (arg, &s, 10);
- if (*s != ']')
- {
- /* parse error */
- break;
- }
- return uid;
- }
- }
-
- /* didn't receive an APPENDUID */
- send_server (imap->sock,
- "UID SEARCH HEADER X-TUID %08lx%05lx%04x",
- tv.tv_sec, tv.tv_usec, pid);
- uid = 0;
- for (;;)
- {
- if (buffer_gets (imap->buf, &s))
- return -1;
-
- arg = next_arg (&s);
- if (*arg == '*')
- {
- arg = next_arg (&s);
- if (!strcmp (arg, "SEARCH"))
- {
- arg = next_arg (&s);
- if (!arg)
- fprintf (stderr, "IMAP error: incomplete SEARCH response\n");
- else
- uid = atoi (arg);
- }
- }
- else if (atoi (arg) != (int) Tag)
- {
- fprintf (stderr, "IMAP error: wrong tag\n");
- return -1;
- }
- else
- {
- arg = next_arg (&s);
- if (strcmp (arg, "OK"))
- return -1;
- return uid;
- }
- }
-
- return 0;
-
- bail:
- munmap (fmap, len);
- return -1;
-}
-
-int
-imap_list (imap_t * imap)
-{
- return imap_exec (imap, "LIST \"\" \"%s*\"", global.folder);
-}
-
diff --git a/src/isync.h b/src/isync.h
@@ -1,6 +1,5 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
+/*
+ * mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
*
@@ -18,219 +17,233 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
- * As a special exception, isync may be linked with the OpenSSL library,
+ * As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
+#define _GNU_SOURCE
+
#include <config.h>
#include <sys/types.h>
-
-#include <db.h>
-
-#if HAVE_LIBSSL
-# include <openssl/ssl.h>
+#include <stdio.h>
+
+#define as(ar) (sizeof(ar)/sizeof(ar[0]))
+
+#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
-typedef struct
-{
- int fd;
-#if HAVE_LIBSSL
- SSL *ssl;
- unsigned int use_ssl:1;
-#endif
-} Socket_t;
-
-typedef struct
-{
- Socket_t *sock;
- int bytes;
- int offset;
- char buf[1024];
-}
-buffer_t;
-
-typedef struct config config_t;
-typedef struct mailbox mailbox_t;
-typedef struct message message_t;
-
-struct config
-{
- char *maildir;
- char *path; /* path relative to .maildir, or absolute path */
- char *host;
- int port;
- char *user;
- char *pass;
- char *folder;
- char *box;
- char *inbox;
- char *alias;
- char *copy_deleted_to;
- char *tunnel;
- unsigned int max_messages;
- off_t 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;
- unsigned int expunge:1;
- unsigned int delete:1;
- unsigned int wanted:1;
+#define EXE "mbsync"
+
+typedef struct {
+ const char *file;
+ FILE *fp;
+ char *buf;
+ int bufl;
+ int line;
+ char *cmd, *val, *rest;
+} conffile_t;
+
+#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 XOP_PUSH (1<<6)
+#define XOP_PULL (1<<7)
+#define XOP_MASK_DIR (XOP_PUSH|XOP_PULL)
+#define XOP_HAVE_TYPE (1<<8)
+#define XOP_HAVE_EXPUNGE (1<<9)
+#define XOP_HAVE_CREATE (1<<10)
+
+typedef struct driver driver_t;
+
+typedef struct store_conf {
+ struct store_conf *next;
+ char *name;
+ driver_t *driver;
+ const char *path; /* should this be here? its interpretation is driver-specific */
+ char *map_inbox;
+ char *trash;
+ unsigned max_size; /* off_t is overkill */
+ unsigned trash_remote_new:1, trash_only_new:1;
+} store_conf_t;
+
+typedef struct string_list {
+ struct string_list *next;
+ char string[1];
+} string_list_t;
+
+typedef struct channel_conf {
+ struct channel_conf *next;
+ char *name;
+ store_conf_t *master, *slave;
+ char *master_name, *slave_name;
+ char *sync_state;
+ string_list_t *patterns;
+ int mops, sops;
+ unsigned max_messages; /* for slave only */
+} channel_conf_t;
+
+typedef struct group_conf {
+ struct group_conf *next;
+ char *name;
+ string_list_t *channels;
+} group_conf_t;
+
+/* For message->flags */
+/* Keep the mailbox driver flag definitions in sync! */
+/* The order is according to alphabetical maildir flag sort */
+#define F_DRAFT (1<<0) /* Draft */
+#define F_FLAGGED (1<<1) /* Flagged */
+#define F_ANSWERED (1<<2) /* Replied */
+#define F_SEEN (1<<3) /* Seen */
+#define F_DELETED (1<<4) /* Trashed */
+#define NUM_FLAGS 5
+
+/* 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 */
+#define M_PROCESSED (1<<3) /* registered in pair */
+#define M_NOT_SYNCED (1<<4) /* not in remote mailbox, yet */
+#define M_EXPIRED (1<<5) /* kicked out by MaxMessages */
+
+typedef struct message {
+ struct message *next;
+ /* string_list_t *keywords; */
+ size_t size; /* zero implies "not fetched" */
+ int uid;
+ unsigned char flags, status;
+} message_t;
+
+/* For opts, both in store and driver_t->select() */
+#define OPEN_OLD (1<<0)
+#define OPEN_NEW (1<<1)
+#define OPEN_FLAGS (1<<2)
+#define OPEN_SIZE (1<<3)
+#define OPEN_CREATE (1<<4)
+#define OPEN_EXPUNGE (1<<5)
+#define OPEN_SETFLAGS (1<<6)
+#define OPEN_APPEND (1<<7)
+
+typedef struct store {
+ store_conf_t *conf; /* foreign */
+
+ /* currently open mailbox */
+ const char *name; /* foreign! maybe preset? */
+ char *path; /* own */
+ message_t *msgs; /* own */
+ int uidvalidity;
+ unsigned char opts; /* maybe preset? */
+ /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
+ int count; /* # of messages */
+ int recent; /* # of recent messages - don't trust this beyond the initial read */
+} store_t;
+
+typedef struct {
+ char *data;
+ int len;
+ unsigned char flags;
+ unsigned char crlf:1;
+} msg_data_t;
+
+#define DRV_OK 0
+#define DRV_MSG_BAD -1
+#define DRV_BOX_BAD -2
+#define DRV_STORE_BAD -3
+
+struct driver {
+ int (*parse_store)( conffile_t *cfg, store_conf_t **storep, int *err );
+ store_t *(*open_store)( store_conf_t *conf, store_t *oldctx );
+ void (*close_store)( store_t *ctx );
+ int (*list)( store_t *ctx, string_list_t **boxes );
+ void (*prepare)( store_t *ctx, int opts );
+ int (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs );
+ int (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data );
+ int (*store_msg)( store_t *ctx, msg_data_t *data, int *uid ); /* if uid is null, store to trash */
+ int (*set_flags)( store_t *ctx, message_t *msg, int uid, int add, int del ); /* msg can be null, therefore uid as a fallback */
+ int (*trash_msg)( store_t *ctx, message_t *msg ); /* This may expunge the original message immediately, but it needn't to */
+ int (*check)( store_t *ctx ); /* IMAP-style: flush */
+ int (*close)( store_t *ctx ); /* IMAP-style: expunge inclusive */
};
-/* struct representing local mailbox file */
-struct mailbox
-{
- DB *db;
- char *path;
- message_t *msgs;
- int lockfd;
- unsigned int deleted; /* # of deleted messages */
- unsigned int uidvalidity;
- unsigned int maxuid; /* largest uid we know about */
- unsigned int uidseen : 1; /* flag indicating whether or not we saw a
- valid value for UIDVALIDITY */
-};
-/* 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;
- size_t 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 dead:1; /* message doesn't exist on the server */
- unsigned int wanted:1; /* when using MaxMessages, keep this message */
-};
+/* main.c */
-/* struct used for parsing IMAP lists */
-typedef struct _list list_t;
+extern int Pid;
+extern char Hostname[256];
+extern const char *Home;
-#define NIL (void*)0x1
-#define LIST (void*)0x2
-struct _list
-{
- char *val;
- list_t *next;
- list_t *child;
-};
+/* util.c */
-/* 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 */
- char *prefix; /* namespace prefix */
- 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 caps;
-#if HAVE_LIBSSL
- unsigned int cram:1;
-#endif
-}
-imap_t;
-
-/* Keep in sync with cap_list */
-enum CAPABILITY {
- NOLOGIN,
- UIDPLUS,
- NAMESPACE,
-#if HAVE_LIBSSL
- CRAM,
- STARTTLS,
-#endif
-};
+extern int Verbose, Quiet, Debug;
-/* 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 */
+void debug( const char *, ... );
+void info( const char *, ... );
+void infoc( char );
+void warn( const char *, ... );
-/* flags for maildir_open */
-#define OPEN_FAST (1<<0) /* fast open - don't parse */
-#define OPEN_CREATE (1<<1) /* create mailbox if nonexistent */
+char *next_arg( char ** );
-/* flags for imap_open */
-#define IMAP_CREATE (1<<0) /* Create remote mailboxes if necessary */
-#define IMAP_GET_SIZE (1<<1) /* Request the RFC 822 SIZE */
+void add_string_list( string_list_t **list, const char *str );
+void free_string_list( string_list_t *list );
+void free_generic_messages( message_t * );
-extern config_t global;
-extern config_t *boxes;
-extern char Hostname[256];
-extern int Verbose, Quiet;
+void strip_cr( msg_data_t *msgdata );
-extern void info (const char *, ...);
-extern void infoc (char);
-extern void warn (const char *, ...);
+void *nfmalloc( size_t sz );
+void *nfcalloc( size_t sz );
+void *nfrealloc( void *mem, size_t sz );
+char *nfstrdup( const char *str );
+int nfvasprintf( char **str, const char *fmt, va_list va );
+int nfasprintf( char **str, const char *fmt, ... );
+int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+void ATTR_NORETURN oob( void );
-#if HAVE_LIBSSL
-extern SSL_CTX *SSLContext;
+char *expand_strdup( const char *s );
-char *cram (const char *, const char *, const char *);
-#endif
+void sort_ints( int *arr, int len );
+
+void arc4_init( void );
+unsigned char arc4_getbyte( void );
+
+/* sync.c */
+
+#define SYNC_OK 0
+#define SYNC_FAIL 1
+#define SYNC_MASTER_BAD 2
+#define SYNC_SLAVE_BAD 3
+
+int sync_boxes( store_t *, const char *,
+ store_t *, const char *,
+ channel_conf_t * );
+
+/* config.c */
+
+extern channel_conf_t *channels;
+extern group_conf_t *groups;
+extern int global_mops, global_sops;
+extern char *global_sync_state;
+
+int parse_bool( conffile_t *cfile );
+int parse_int( conffile_t *cfile );
+int parse_size( conffile_t *cfile );
+int getcline( conffile_t *cfile );
+int merge_ops( int cops, int *mops, int *sops );
+int load_config( const char *filename, int pseudo );
+void parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err );
-char *next_arg (char **);
-
-int sync_mailbox (mailbox_t *, imap_t *, int, unsigned int, unsigned int);
-
-void load_config (const char *, int *);
-char * expand_strdup (const char *s);
-config_t *find_box (const char *);
-
-void imap_close (imap_t *);
-int imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox);
-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_connect (config_t *);
-imap_t *imap_open (config_t *, unsigned int, imap_t *, int);
-int imap_append_message (imap_t *, int, message_t *);
-int imap_list (imap_t *);
-
-mailbox_t *maildir_open (const char *, int flags);
-int maildir_expunge (mailbox_t *, int);
-int maildir_set_uidvalidity (mailbox_t *, unsigned int uidvalidity);
-void maildir_close (mailbox_t *);
-int maildir_update_maxuid (mailbox_t * mbox);
-
-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);
-
-#define strfcpy(a,b,c) {strncpy(a,b,c);(a)[c-1]=0;}
+/* drv_*.c */
+extern driver_t maildir_driver, imap_driver;
diff --git a/src/list.c b/src/list.c
@@ -1,179 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2000-2002 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
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-#include "isync.h"
-
-#include <ctype.h>
-#include <stdlib.h>
-#include <string.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 (tmp))
- 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
diff --git a/src/maildir.c b/src/maildir.c
@@ -1,459 +0,0 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
- * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 2002-2003 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, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * As a special exception, isync may be linked with the OpenSSL library,
- * despite that library's more restrictive license.
- */
-
-#include "isync.h"
-#include "dotlock.h"
-
-#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 <time.h>
-
-/* 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++;
- }
- }
-}
-
-/*
- * There are three possible results of this function:
- * >1 uid was already seen
- * 0 uid was not yet seen
- * -1 unable to read uid because of some other error
- */
-
-static int
-read_uid (const char *path, const char *file, unsigned int *uid /* out */)
-{
- char full[_POSIX_PATH_MAX];
- int fd;
- int ret = -1;
- int len;
- char buf[64], *ptr;
-
- snprintf (full, sizeof (full), "%s/%s", path, file);
- fd = open (full, O_RDONLY);
- if (fd == -1)
- {
- if (errno != ENOENT)
- {
- perror (full);
- return -1;
- }
- return 0; /* doesn't exist */
- }
- len = read (fd, buf, sizeof (buf) - 1);
- if (len == -1)
- perror ("read");
- else
- {
- buf[len] = 0;
- errno = 0;
- *uid = strtoul (buf, &ptr, 10);
- if (errno)
- perror ("strtoul");
- else if (ptr && *ptr == '\n')
- ret = 1;
- /* else invalid value */
- }
- close (fd);
- return ret;
-}
-
-/*
- * open a maildir mailbox.
- * if OPEN_FAST is set, 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.
- * if OPEN_CREATE is set, we create the mailbox if it doesn't already exist.
- */
-mailbox_t *
-maildir_open (const char *path, int flags)
-{
- char buf[_POSIX_PATH_MAX];
- DIR *d;
- struct dirent *e;
- message_t **cur;
- message_t *p;
- mailbox_t *m;
- char *s;
- int count = 0;
- struct stat sb;
- const char *subdirs[] = { "cur", "new", "tmp" };
- int i, ret;
- DBT key, value;
-
- m = calloc (1, sizeof (mailbox_t));
- m->lockfd = -1;
- /* filename expansion happens here, not in the config parser */
- m->path = expand_strdup (path);
-
- if (stat (m->path, &sb))
- {
- if (errno == ENOENT && (flags & OPEN_CREATE))
- {
- if (mkdir (m->path, S_IRUSR | S_IWUSR | S_IXUSR))
- {
- fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n",
- m->path, strerror (errno), errno);
- goto err;
- }
-
- for (i = 0; i < 3; i++)
- {
- snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]);
- if (mkdir (buf, S_IRUSR | S_IWUSR | S_IXUSR))
- {
- fprintf (stderr, "ERROR: mkdir %s: %s (errno %d)\n",
- buf, strerror (errno), errno);
- goto err;
- }
- }
-
- }
- else
- {
- fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", m->path,
- strerror (errno), errno);
- goto err;
- }
- }
- else
- {
- /* check to make sure this looks like a valid maildir box */
- for (i = 0; i < 3; i++)
- {
- snprintf (buf, sizeof (buf), "%s/%s", m->path, subdirs[i]);
- if (stat (buf, &sb))
- {
- fprintf (stderr, "ERROR: stat %s: %s (errno %d)\n", buf,
- strerror (errno), errno);
- fprintf (stderr,
- "ERROR: %s does not appear to be a valid maildir style mailbox\n",
- m->path);
- goto err;
- }
- }
- }
-
- /*
- * we need a mutex on the maildir because of the state files that isync
- * uses.
- */
- snprintf (buf, sizeof (buf), "%s/isynclock", m->path);
- if (dotlock_lock (buf, &m->lockfd))
- goto err;
-
- /* check for the uidvalidity value */
- i = read_uid (m->path, "isyncuidvalidity", &m->uidvalidity);
- if (i == -1)
- goto err;
- else if (i > 0)
- m->uidseen = 1;
-
- /* load the current maxuid */
- if (read_uid (m->path, "isyncmaxuid", &m->maxuid) == -1)
- goto err;
-
- snprintf (buf, sizeof (buf), "%s/isyncuidmap.db", m->path);
- if (db_create (&m->db, 0, 0)) {
- fputs ("dbcreate failed\n", stderr);
- goto err;
- }
- if ((ret = m->db->set_pagesize (m->db, 4096)) != 0 ||
- (ret = m->db->set_h_ffactor (m->db, 40)) != 0 ||
- (ret = m->db->set_h_nelem (m->db, 1)) != 0) {
- fputs ("Error configuring database\n", stderr);
- goto err;
- }
- m->db->open (m->db, buf, 0, DB_HASH, DB_CREATE, S_IRUSR | S_IWUSR);
- if (m->db == NULL)
- {
- fputs ("ERROR: unable to open UID db\n", stderr);
- goto err;
- }
-
- if (flags & OPEN_FAST)
- return m;
-
- cur = &m->msgs;
- for (; count < 2; count++)
- {
- /* read the msgs from the new subdir */
- snprintf (buf, sizeof (buf), "%s/%s", m->path,
- (count == 0) ? "new" : "cur");
- d = opendir (buf);
- if (!d)
- {
- perror ("opendir");
- goto err;
- }
- 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 = 0;
- p->new = (count == 0);
-
- /* determine the UID for this message. The basename (sans
- * flags) is used as the key in the db
- */
- memset (&key, 0, sizeof(key));
- memset (&value, 0, sizeof(value));
- key.data = p->file;
- s = strchr (p->file, ':');
- key.size = s ? (size_t) (s - p->file) : strlen (p->file);
- ret = m->db->get (m->db, 0, &key, &value, 0);
- if (ret == DB_NOTFOUND) {
- /* Every locally generated message triggers this ... */
- /*warn ("Warning: no UID for message %.*s\n",
- key.size, p->file);*/
- } else if (ret) {
- fprintf (stderr, "Unexpected error (%d) from db_get(%.*s)\n",
- ret, key.size, p->file);
- } else if (ret == 0) {
- p->uid = *((int *) value.data);
- if (p->uid > m->maxuid)
- m->maxuid = p->uid;
- }
- if (s)
- parse_info (p, s + 1);
- if (p->flags & D_DELETED)
- m->deleted++;
- cur = &p->next;
- }
- closedir (d);
- }
- return m;
-
- err:
- if (m->db)
- m->db->close (m->db, 0);
- dotlock_unlock (&m->lockfd);
- free (m->path);
- free (m);
- return NULL;
-}
-
-/* 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 *s;
- DBT key;
- char path[_POSIX_PATH_MAX];
-
- while (*cur)
- {
- if ((dead == 0 && (*cur)->flags & D_DELETED) ||
- (dead && (*cur)->dead))
- {
- tmp = *cur;
- snprintf (path, sizeof (path), "%s/%s/%s",
- mbox->path, tmp->new ? "new" : "cur", tmp->file);
- if (unlink (path))
- perror (path);
- /* remove the message from the UID map */
- memset (&key, 0, sizeof(key));
- key.data = tmp->file;
- s = strchr (tmp->file, ':');
- key.size = s ? (size_t) (s - tmp->file) : strlen (key.data);
- mbox->db->del (mbox->db, 0, &key, 0);
- mbox->db->sync (mbox->db, 0);
- *cur = (*cur)->next;
- free (tmp->file);
- free (tmp);
- }
- else
- cur = &(*cur)->next;
- }
- return 0;
-}
-
-int
-maildir_update_maxuid (mailbox_t * mbox)
-{
- int fd;
- char buf[64];
- size_t len;
- char path[_POSIX_PATH_MAX];
- int ret = 0;
-
- snprintf (path, sizeof (path), "%s/isyncmaxuid", mbox->path);
- fd = open (path, O_WRONLY | O_CREAT, 0600);
- if (fd == -1)
- {
- perror ("open");
- return -1;
- }
-
- /* 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;
- }
-
- if (close (fd))
- ret = -1;
-
- return ret;
-}
-
-#define _24_HOURS (3600 * 24)
-
-static void
-maildir_clean_tmp (const char *mbox)
-{
- char path[_POSIX_PATH_MAX];
- DIR *dirp;
- struct dirent *entry;
- struct stat st;
- time_t now;
-
- snprintf (path, sizeof (path), "%s/tmp", mbox);
- dirp = opendir (path);
- if (dirp == NULL)
- {
- fprintf (stderr, "maildir_clean_tmp: opendir: %s: %s (errno %d)\n",
- path, strerror (errno), errno);
- return;
- }
- /* assuming this scan will take less than a second, we only need to
- * check the time once before the following loop.
- */
- time (&now);
- while ((entry = readdir (dirp)))
- {
- snprintf (path, sizeof (path), "%s/tmp/%s", mbox, entry->d_name);
- if (stat (path, &st))
- fprintf (stderr, "maildir_clean_tmp: stat: %s: %s (errno %d)\n",
- path, strerror (errno), errno);
- else if (S_ISREG (st.st_mode) && now - st.st_ctime >= _24_HOURS)
- {
- /* this should happen infrequently enough that it won't be
- * bothersome to the user to display when it occurs.
- */
- info ("Notice: removing stale file %s\n", path);
- if (unlink (path))
- fprintf (stderr,
- "maildir_clean_tmp: unlink: %s: %s (errno %d)\n",
- path, strerror (errno), errno);
- }
- }
- closedir(dirp);
-}
-
-void
-maildir_close (mailbox_t * mbox)
-{
- if (mbox->db)
- mbox->db->close (mbox->db, 0);
-
- /* release the mutex on the mailbox */
- dotlock_unlock (&mbox->lockfd);
-
- /* per the maildir(5) specification, delivery agents are supposed to
- * set a 24-hour timer on items placed in the `tmp' directory.
- */
- maildir_clean_tmp (mbox->path);
-
- free (mbox->path);
- free_message (mbox->msgs);
- memset (mbox, 0xff, sizeof (mailbox_t));
- free (mbox);
-}
-
-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);
-}
diff --git a/src/main.c b/src/main.c
@@ -1,6 +1,5 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
+/*
+ * mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
* Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
*
@@ -18,473 +17,597 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
- * As a special exception, isync may be linked with the OpenSSL library,
+ * As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
#include "isync.h"
-#include <sys/types.h>
-#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <pwd.h>
-#include <stdio.h>
-#include <errno.h>
#include <string.h>
-#include <ctype.h>
-#include <dirent.h>
-
-int Quiet;
-
-void
-info (const char *msg, ...)
-{
- va_list va;
-
- if (!Quiet)
- {
- va_start (va, msg);
- vprintf (msg, va);
- va_end (va);
- }
-}
-
-void
-infoc (char c)
-{
- if (!Quiet)
- putchar (c);
-}
-void
-warn (const char *msg, ...)
-{
- va_list va;
-
- if (Quiet < 2)
- {
- va_start (va, msg);
- vfprintf (stderr, msg, va);
- va_end (va);
- }
-}
-
-#if HAVE_GETOPT_LONG
-# define _GNU_SOURCE
-# include <getopt.h>
-struct option Opts[] = {
- {"all", 0, NULL, 'a'},
- {"list", 0, NULL, 'l'},
- {"config", 1, NULL, 'c'},
- {"create", 0, NULL, 'C'},
- {"create-local", 0, NULL, 'L'},
- {"create-remote", 0, NULL, 'R'},
- {"delete", 0, NULL, 'd'},
- {"expunge", 0, NULL, 'e'},
- {"fast", 0, NULL, 'f'},
- {"help", 0, NULL, 'h'},
- {"remote", 1, NULL, 'r'},
- {"folder", 1, NULL, 'F'},
- {"maildir", 1, NULL, 'M'},
- {"one-to-one", 0, NULL, '1'},
- {"inbox", 1, NULL, 'I'},
- {"host", 1, NULL, 's'},
- {"port", 1, NULL, 'p'},
- {"quiet", 0, NULL, 'q'},
- {"user", 1, NULL, 'u'},
- {"version", 0, NULL, 'v'},
- {"verbose", 0, NULL, 'V'},
- {0, 0, 0, 0}
-};
-#endif
-
-config_t global;
-char Hostname[256];
-int Verbose = 0;
+int Pid; /* for maildir and imap */
+char Hostname[256]; /* for maildir */
+const char *Home; /* for config */
static void
-version (void)
+version( void )
{
- puts (PACKAGE " " VERSION);
- exit (0);
+ puts( PACKAGE " " VERSION );
+ exit( 0 );
}
static void
-usage (int code)
+usage( int code )
{
- fputs (
-PACKAGE " " VERSION " IMAP4 to maildir synchronizer\n"
+ fputs(
+PACKAGE " " VERSION " - mailbox synchronizer\n"
"Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>\n"
"Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>\n"
+"Copyright (C) 2004 Theodore Ts'o <tytso@mit.edu>\n"
"usage:\n"
-" " PACKAGE " [ flags ] mailbox [mailbox ...]\n"
-" " PACKAGE " [ flags ] -a\n"
-" " PACKAGE " [ flags ] -l\n"
-" -a, --all synchronize all defined mailboxes\n"
-" -l, --list list all defined mailboxes and exit\n"
-" -L, --create-local create local maildir mailbox if nonexistent\n"
-" -R, --create-remote create remote imap mailbox if nonexistent\n"
-" -C, --create create both local and remote mailboxes if nonexistent\n"
-" -d, --delete delete local msgs that don't exist on the server\n"
-" -e, --expunge expunge deleted messages\n"
-" -f, --fast only fetch new messages\n"
-" -r, --remote BOX remote mailbox\n"
-" -F, --folder DIR remote IMAP folder containing mailboxes\n"
-" -M, --maildir DIR local directory containing mailboxes\n"
-" -1, --one-to-one map every IMAP <folder>/box to <maildir>/box\n"
-" -I, --inbox BOX map IMAP INBOX to <maildir>/BOX (exception to -1)\n"
-" -s, --host HOST IMAP server address\n"
-" -p, --port PORT server IMAP port\n"
-" -u, --user USER IMAP user name\n"
-" -c, --config CONFIG read an alternate config file (default: ~/.isyncrc)\n"
+" " EXE " [flags] {{channel[:box,...]|group} ...|-a}\n"
+" -a, --all operate on all defined channels\n"
+" -l, --list list mailboxes instead of syncing them\n"
+" -n, --new propagate new messages\n"
+" -d, --delete propagate message deletions\n"
+" -f, --flags propagate message flag changes\n"
+" -N, --renew propagate previously not propagated new messages\n"
+" -L, --pull propagate from master to slave\n"
+" -H, --push propagate from slave to master\n"
+" -C, --create create mailboxes if nonexistent\n"
+" -X, --expunge expunge deleted messages\n"
+" -c, --config CONFIG read an alternate config file (default: ~/." EXE "rc)\n"
+" -D, --debug print debugging messages\n"
" -V, --verbose verbose mode (display network traffic)\n"
" -q, --quiet don't display progress info\n"
" -v, --version display version\n"
" -h, --help display this help message\n"
-"Compile time options:\n"
+"\nIf neither --pull nor --push are specified, both are active.\n"
+"If neither --new, --delete, --flags nor --renew are specified, all are active.\n"
+"Direction and operation can be concatenated like --pull-new, etc.\n"
+"--create and --expunge can be suffixed with -master/-slave. Read the man page.\n"
+"\nSupported mailbox formats are: IMAP4rev1, Maildir\n"
+"\nCompile time options:\n"
#if HAVE_LIBSSL
" +HAVE_LIBSSL\n"
#else
" -HAVE_LIBSSL\n"
#endif
- , code ? stderr : stdout);
- exit (code);
+ , code ? stderr : stdout );
+ exit( code );
}
-char *
-next_arg (char **s)
+static int
+matches( const char *t, const char *p )
{
- char *ret;
-
- if (!s)
- return 0;
- if (!*s)
- return 0;
- while (isspace ((unsigned char) **s))
- (*s)++;
- if (!**s)
- {
- *s = 0;
- return 0;
- }
- if (**s == '"')
- {
- ++*s;
- ret = *s;
- *s = strchr (*s, '"');
- }
- else
- {
- ret = *s;
- while (**s && !isspace ((unsigned char) **s))
- (*s)++;
- }
- if (*s)
- {
- if (**s)
- *(*s)++ = 0;
- if (!**s)
- *s = 0;
- }
- return ret;
+ for (;;) {
+ if (!*p)
+ return !*t;
+ if (*p == '*') {
+ p++;
+ do {
+ if (matches( t, p ))
+ return 1;
+ } while (*t++);
+ return 0;
+ } else if (*p == '%') {
+ p++;
+ do {
+ if (*t == '.' || *t == '/') /* this is "somewhat" hacky ... */
+ return 0;
+ if (matches( t, p ))
+ return 1;
+ } while (*t++);
+ return 0;
+ } else {
+ if (*p != *t)
+ return 0;
+ p++, t++;
+ }
+ }
}
-int
-main (int argc, char **argv)
+static string_list_t *
+filter_boxes( string_list_t *boxes, string_list_t *patterns )
{
- int i;
- int ret;
- config_t *box = 0;
- mailbox_t *mail = 0;
- 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;
- int all = 0;
- int list = 0;
- int o2o = 0;
- int mbox_open_mode = 0;
- int imap_flags = 0;
-
- pw = getpwuid (getuid ());
-
- /* defaults */
- memset (&global, 0, sizeof (global));
- /* XXX the precedence is borked:
- it's defaults < cmdline < file instead of defaults < file < cmdline */
- global.port = 143;
- global.box = "INBOX";
- global.folder = "";
- global.user = strdup (pw->pw_name);
- global.maildir = strdup (pw->pw_dir);
- 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_tlsv1 = 1;
-#endif
-
-#define FLAGS "alCLRc:defhp:qu:r:F:M:1I:s:vV"
-
-#if HAVE_GETOPT_LONG
- while ((i = getopt_long (argc, argv, FLAGS, Opts, NULL)) != -1)
-#else
- while ((i = getopt (argc, argv, FLAGS)) != -1)
-#endif
- {
- switch (i)
- {
- case 'l':
- list = 1;
- /* plopp */
- case 'a':
- all = 1;
- break;
- case '1':
- o2o = 1;
- break;
- case 'C':
- mbox_open_mode |= OPEN_CREATE;
- imap_flags |= IMAP_CREATE;
- break;
- case 'L':
- mbox_open_mode |= OPEN_CREATE;
- break;
- case 'R':
- imap_flags |= IMAP_CREATE;
- break;
- case 'c':
- config = optarg;
- break;
- case 'd':
- delete = 1;
- break;
- case 'e':
- expunge = 1;
- break;
- case 'f':
- mbox_open_mode |= OPEN_FAST;
- fast = 1;
- break;
- case 'p':
- global.port = atoi (optarg);
- break;
- case 'q':
- Quiet++;
- Verbose = 0;
- break;
- case 'r':
- global.box = optarg;
- break;
- case 'F':
- global.folder = optarg;
- break;
- case 'M':
- global.maildir = optarg;
- break;
- case 'I':
- global.inbox = optarg;
- break;
- case 's':
-#if HAVE_LIBSSL
- if (!strncasecmp ("imaps:", optarg, 6))
- {
- global.use_imaps = 1;
- global.port = 993;
- global.use_sslv2 = 1;
- global.use_sslv3 = 1;
- optarg += 6;
+ string_list_t *nboxes = 0, *cpat;
+ const char *ps;
+ int not, fnot;
+
+ for (; boxes; boxes = boxes->next) {
+ fnot = 1;
+ for (cpat = patterns; cpat; cpat = cpat->next) {
+ ps = cpat->string;
+ if (*ps == '!') {
+ ps++;
+ not = 1;
+ } else
+ not = 0;
+ if (matches( boxes->string, ps )) {
+ fnot = not;
+ break;
+ }
}
-#endif
- global.host = optarg;
- break;
- case 'u':
- global.user = optarg;
- break;
- case 'V':
- Verbose = 1;
- break;
- case 'v':
- version ();
- case 'h':
- usage (0);
- default:
- usage (1);
- }
- }
-
- if (!argv[optind] && !all)
- {
- fprintf (stderr, "No mailbox specified. Try isync -h\n");
- return 1;
- }
-
- gethostname (Hostname, sizeof (Hostname));
-
- load_config (config, &o2o);
-
- if (all && o2o)
- {
- DIR *dir;
- struct dirent *de;
-
- if (global.inbox) {
- boxes = malloc (sizeof (config_t));
- memcpy (boxes, &global, sizeof (config_t));
- boxes->box = "INBOX";
- boxes->path = global.inbox;
+ if (!fnot)
+ add_string_list( &nboxes, boxes->string );
}
+ return nboxes;
+}
- if (!(dir = opendir (global.maildir))) {
- fprintf (stderr, "%s: %s\n", global.maildir, strerror(errno));
- return 1;
+static void
+merge_actions( channel_conf_t *chan, int mops, int sops, int have, int mask, int def )
+{
+ if (mops & have) {
+ chan->mops &= ~mask;
+ chan->mops |= mops & mask;
+ chan->sops &= ~mask;
+ chan->sops |= sops & mask;
+ } else if (!(chan->mops & have)) {
+ if (global_mops & have) {
+ chan->mops |= global_mops & mask;
+ chan->sops |= global_sops & mask;
+ } else {
+ chan->mops |= def;
+ chan->sops |= def;
+ }
}
- while ((de = readdir (dir))) {
- struct stat st;
- char buf[PATH_MAX];
+}
- if (*de->d_name == '.')
- continue;
- if (global.inbox && !strcmp (global.inbox, de->d_name))
- continue;
- snprintf (buf, sizeof(buf), "%s/%s/cur", global.maildir, de->d_name);
- if (stat (buf, &st) || !S_ISDIR (st.st_mode))
- continue;
- box = malloc (sizeof (config_t));
- memcpy (box, &global, sizeof (config_t));
- box->path = strdup (de->d_name);
- box->box = box->path;
- box->next = boxes;
- boxes = box;
+int
+main( int argc, char **argv )
+{
+ channel_conf_t *chan;
+ store_conf_t *mconf, *sconf;
+ group_conf_t *group;
+ driver_t *mdriver, *sdriver;
+ store_t *mctx, *sctx;
+ string_list_t *umboxes, *usboxes, *mboxes, *sboxes, *mbox, *sbox, **mboxp, **sboxp, *cboxes, *chanptr;
+ char *config = 0, *channame, *boxlist, *opt, *ochar;
+ int all = 0, list = 0, cops = 0, mops = 0, sops = 0, gumboxes, gusboxes;
+ int oind, ret, op, multiple, pseudo = 0;
+
+ gethostname( Hostname, sizeof(Hostname) );
+ if ((ochar = strchr( Hostname, '.' )))
+ *ochar = 0;
+ Pid = getpid();
+ if (!(Home = getenv("HOME"))) {
+ fputs( "Fatal: $HOME not set\n", stderr );
+ return 1;
}
- closedir (dir);
-
- imap = imap_connect (&global);
- if (!imap)
- return 1;
- if (imap_list (imap))
- return 1;
- }
- if (list)
- {
- for (box = boxes; box; box = box->next)
- puts (box->path);
- return 0;
- }
- ret = 0;
- for (box = boxes; (all && box) || (!all && argv[optind]); optind++)
- {
- if (!all)
- {
- if (o2o || NULL == (box = find_box (argv[optind])))
- {
- /* if enough info is given on the command line, don't worry if
- * the mailbox isn't defined.
- */
- if (!global.host)
- {
- fprintf (stderr, "%s: no such mailbox\n", argv[optind]);
- /* continue is ok here because we are not handling the
- * `all' case.
- */
- continue;
+ arc4_init();
+
+ for (oind = 1, ochar = 0; oind < argc; ) {
+ if (!ochar || !*ochar) {
+ if (argv[oind][0] != '-')
+ break;
+ if (argv[oind][1] == '-') {
+ opt = argv[oind++] + 2;
+ if (!*opt)
+ break;
+ if (!strcmp( opt, "config" )) {
+ if (oind >= argc) {
+ fprintf( stderr, "--config requires an argument.\n" );
+ return 1;
+ }
+ config = argv[oind++];
+ } else if (!memcmp( opt, "config=", 7 ))
+ config = opt + 7;
+ else if (!strcmp( opt, "all" ))
+ all = 1;
+ else if (!strcmp( opt, "list" ))
+ list = 1;
+ else if (!strcmp( opt, "help" ))
+ usage( 0 );
+ else if (!strcmp( opt, "version" ))
+ version();
+ else if (!strcmp( opt, "quiet" ))
+ Quiet++;
+ else if (!strcmp( opt, "verbose" )) {
+ Verbose = 1;
+ if (!Quiet)
+ Quiet = 1;
+ } else if (!strcmp( opt, "debug" )) {
+ Debug = 1;
+ if (!Quiet)
+ Quiet = 1;
+ } else if (!strcmp( opt, "pull" ))
+ cops |= XOP_PULL, mops |= XOP_HAVE_TYPE;
+ else if (!strcmp( opt, "push" ))
+ cops |= XOP_PUSH, mops |= XOP_HAVE_TYPE;
+ else if (!memcmp( opt, "create", 6 )) {
+ opt += 6;
+ op = OP_CREATE|XOP_HAVE_CREATE;
+ lcop:
+ if (!*opt)
+ cops |= op;
+ else if (!strcmp( opt, "-master" ))
+ mops |= op, ochar++;
+ else if (!strcmp( opt, "-slave" ))
+ sops |= op, ochar++;
+ else
+ goto badopt;
+ mops |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
+ } else if (!memcmp( opt, "expunge", 7 )) {
+ opt += 7;
+ op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
+ goto lcop;
+ } else if (!strcmp( opt, "no-expunge" ))
+ mops |= XOP_HAVE_EXPUNGE;
+ else if (!strcmp( opt, "no-create" ))
+ mops |= XOP_HAVE_CREATE;
+ else if (!strcmp( opt, "full" ))
+ mops |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
+ else if (!strcmp( opt, "noop" ))
+ mops |= XOP_HAVE_TYPE;
+ else if (!memcmp( opt, "pull", 4 )) {
+ op = XOP_PULL;
+ lcac:
+ opt += 4;
+ if (!*opt)
+ cops |= op;
+ else if (*opt == '-') {
+ opt++;
+ goto rlcac;
+ } else
+ goto badopt;
+ } else if (!memcmp( opt, "push", 4 )) {
+ op = XOP_PUSH;
+ goto lcac;
+ } else {
+ op = 0;
+ rlcac:
+ if (!strcmp( opt, "new" ))
+ op |= OP_NEW;
+ else if (!strcmp( opt, "renew" ))
+ op |= OP_RENEW;
+ else if (!strcmp( opt, "delete" ))
+ op |= OP_DELETE;
+ else if (!strcmp( opt, "flags" ))
+ op |= OP_FLAGS;
+ else {
+ badopt:
+ fprintf( stderr, "Unknown option '%s'\n", argv[oind - 1] );
+ return 1;
+ }
+ switch (op & XOP_MASK_DIR) {
+ case XOP_PULL: sops |= op & OP_MASK_TYPE; break;
+ case XOP_PUSH: mops |= op & OP_MASK_TYPE; break;
+ default: cops |= op; break;
+ }
+ mops |= XOP_HAVE_TYPE;
+ }
+ continue;
+ }
+ ochar = argv[oind++] + 1;
+ if (!*ochar) {
+ fprintf( stderr, "Invalid option '-'\n" );
+ return 1;
+ }
+ }
+ switch (*ochar++) {
+ case 'a':
+ all = 1;
+ break;
+ case 'l':
+ list = 1;
+ break;
+ case 'c':
+ if (*ochar == 'T') {
+ ochar++;
+ pseudo = 1;
+ }
+ if (oind >= argc) {
+ fprintf( stderr, "-c requires an argument.\n" );
+ return 1;
+ }
+ config = argv[oind++];
+ break;
+ case 'C':
+ op = OP_CREATE|XOP_HAVE_CREATE;
+ cop:
+ if (*ochar == 'm')
+ mops |= op, ochar++;
+ else if (*ochar == 's')
+ sops |= op, ochar++;
+ else if (*ochar == '-')
+ ochar++;
+ else
+ cops |= op;
+ mops |= op & (XOP_HAVE_CREATE|XOP_HAVE_EXPUNGE);
+ break;
+ case 'X':
+ op = OP_EXPUNGE|XOP_HAVE_EXPUNGE;
+ goto cop;
+ case 'F':
+ cops |= XOP_PULL|XOP_PUSH;
+ case '0':
+ mops |= XOP_HAVE_TYPE;
+ break;
+ case 'n':
+ case 'd':
+ case 'f':
+ case 'N':
+ --ochar;
+ op = 0;
+ cac:
+ for (;; ochar++) {
+ if (*ochar == 'n')
+ op |= OP_NEW;
+ else if (*ochar == 'd')
+ op |= OP_DELETE;
+ else if (*ochar == 'f')
+ op |= OP_FLAGS;
+ else if (*ochar == 'N')
+ op |= OP_RENEW;
+ else
+ break;
+ }
+ if (op & OP_MASK_TYPE)
+ switch (op & XOP_MASK_DIR) {
+ case XOP_PULL: sops |= op & OP_MASK_TYPE; break;
+ case XOP_PUSH: mops |= op & OP_MASK_TYPE; break;
+ default: cops |= op; break;
+ }
+ else
+ cops |= op;
+ mops |= XOP_HAVE_TYPE;
+ break;
+ case 'L':
+ op = XOP_PULL;
+ goto cac;
+ case 'H':
+ op = XOP_PUSH;
+ goto cac;
+ case 'q':
+ Quiet++;
+ break;
+ case 'V':
+ Verbose = 1;
+ if (!Quiet)
+ Quiet = 1;
+ break;
+ case 'D':
+ Debug = 1;
+ if (!Quiet)
+ Quiet = 1;
+ break;
+ case 'v':
+ version();
+ case 'h':
+ usage( 0 );
+ default:
+ fprintf( stderr, "Unknown option '-%c'\n", *(ochar - 1) );
+ return 1;
}
- global.path = argv[optind];
- box = &global;
- if (o2o)
- global.box =
- (global.inbox && !strcmp (global.path, global.inbox)) ?
- "INBOX" : global.path;
- }
}
- do {
- info ("Mailbox %s\n", box->path);
- mail = maildir_open (box->path, mbox_open_mode);
- if (!mail)
- {
- fprintf (stderr, "%s: unable to open mailbox\n", box->path);
- ret = 1;
- break;
- }
+ if (merge_ops( cops, &mops, &sops ))
+ return 1;
- if (box->max_size)
- imap_flags |= IMAP_GET_SIZE;
- imap = imap_open (box, fast ? mail->maxuid + 1 : 1, imap, imap_flags);
- if (!imap)
- {
- fprintf (stderr, "%s: skipping mailbox due to IMAP error\n",
- box->path);
- ret = 1;
- break;
- }
+ if (load_config( config, pseudo ))
+ return 1;
- info ("Synchronizing\n");
- i = (delete || box->delete) ? SYNC_DELETE : 0;
- i |= (expunge || box->expunge) ? SYNC_EXPUNGE : 0;
- if (sync_mailbox (mail, imap, i, box->max_size, box->max_messages))
- {
- imap_close (imap); /* Just to be safe. Don't really know
- * what the problem was.
- */
- imap = NULL; /* context no longer valid */
- ret = 1;
- break;
- }
+ if (!all && !argv[oind]) {
+ fputs( "No channel specified. Try '" EXE " -h'\n", stderr );
+ return 1;
+ }
+ if (!channels) {
+ fputs( "No channels defined. Try 'man " EXE "'\n", stderr );
+ return 1;
+ }
- if (!fast)
- {
- if ((expunge || box->expunge) &&
- (imap->deleted || mail->deleted))
- {
- /* remove messages marked for deletion */
- info ("Expunging %d messages from server\n", imap->deleted);
- if (imap_expunge (imap))
- {
- imap_close (imap);
- imap = NULL;
+ ret = 0;
+ chan = channels;
+ chanptr = 0;
+ mctx = sctx = 0;
+ mconf = sconf = 0; /* make-gcc-happy */
+ mdriver = sdriver = 0; /* make-gcc-happy */
+ gumboxes = gusboxes = 0;
+ umboxes = usboxes = 0;
+ if (all)
+ multiple = channels->next != 0;
+ else if (argv[oind + 1])
+ multiple = 1;
+ else {
+ multiple = 0;
+ for (group = groups; group; group = group->next)
+ if (!strcmp( group->name, argv[oind] )) {
+ multiple = 1;
+ break;
+ }
+ }
+ for (;;) {
+ boxlist = 0;
+ if (!all) {
+ if (chanptr)
+ channame = chanptr->string;
+ else {
+ for (group = groups; group; group = group->next)
+ if (!strcmp( group->name, argv[oind] )) {
+ chanptr = group->channels;
+ channame = chanptr->string;
+ goto gotgrp;
+ }
+ channame = argv[oind];
+ gotgrp: ;
+ }
+ if ((boxlist = strchr( channame, ':' )))
+ *boxlist++ = 0;
+ for (chan = channels; chan; chan = chan->next)
+ if (!strcmp( chan->name, channame ))
+ goto gotchan;
+ fprintf( stderr, "No channel or group named '%s' defined.\n", channame );
ret = 1;
- break;
- }
- info ("Expunging %d messages from local mailbox\n",
- mail->deleted);
- if (maildir_expunge (mail, 0)) {
+ goto gotnone;
+ gotchan: ;
+ }
+ merge_actions( chan, mops, sops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
+ merge_actions( chan, mops, sops, XOP_HAVE_CREATE, OP_CREATE, 0 );
+ merge_actions( chan, mops, sops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
+
+ mboxes = sboxes = cboxes = 0;
+ /* possible todo: handle master <-> slave swaps */
+ if (mctx) {
+ if (mconf == chan->master)
+ goto gotmctx;
+ free_string_list( umboxes );
+ umboxes = 0;
+ gumboxes = 0;
+ if (mconf->driver != chan->master->driver) {
+ mdriver->close_store( mctx );
+ mctx = 0;
+ }
+ }
+ mconf = chan->master;
+ mdriver = mconf->driver;
+ if (!(mctx = mdriver->open_store( chan->master, mctx ))) {
ret = 1;
- break;
- }
+ goto next;
}
- /* 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) {
- if (maildir_expunge (mail, 1)) {
+ gotmctx:
+ if (sctx) {
+ if (sconf == chan->slave)
+ goto gotsctx;
+ free_string_list( usboxes );
+ usboxes = 0;
+ gusboxes = 0;
+ if (sconf->driver != chan->slave->driver) {
+ sdriver->close_store( sctx );
+ sctx = 0;
+ }
+ }
+ sconf = chan->slave;
+ sdriver = sconf->driver;
+ if (!(sctx = sdriver->open_store( chan->slave, sctx ))) {
ret = 1;
- break;
- }
+ goto next;
}
- }
-
- } while (0);
-
- /* we never sync the same mailbox twice, so close it now */
- if (mail)
- maildir_close (mail);
-
- /* the imap connection is not closed so we can keep the connection
- * open, and there is no IMAP command for un-SELECT-ing a mailbox.
- */
- if (all)
- box = box->next;
- }
- /* gracefully close connection to the IMAP server */
- imap_close (imap);
- return ret;
+ gotsctx:
+ info( "Channel %s\n", chan->name );
+ if (list && multiple)
+ printf( "%s:\n", chan->name );
+ if (boxlist) {
+ for (boxlist = strtok( boxlist, ",\n" ); boxlist; boxlist = strtok( 0, ",\n" ))
+ if (list)
+ puts( boxlist );
+ else
+ switch (sync_boxes( mctx, boxlist, sctx, boxlist, chan )) {
+ case SYNC_MASTER_BAD: goto screwm;
+ case SYNC_SLAVE_BAD: goto screws;
+ case SYNC_FAIL: ret = 1;
+ }
+ } else if (chan->patterns) {
+ if (!gumboxes) {
+ if (mdriver->list( mctx, &umboxes ) != DRV_OK) {
+ screwm:
+ mdriver->close_store( mctx );
+ free_string_list( umboxes );
+ umboxes = 0;
+ gumboxes = 0;
+ mctx = 0;
+ ret = 1;
+ goto next;
+ } else {
+ gumboxes = 1;
+ if (mctx->conf->map_inbox)
+ add_string_list( &umboxes, mctx->conf->map_inbox );
+ }
+ }
+ if (!gusboxes) {
+ if (sdriver->list( sctx, &usboxes ) != DRV_OK) {
+ screws:
+ sdriver->close_store( sctx );
+ free_string_list( usboxes );
+ usboxes = 0;
+ gusboxes = 0;
+ sctx = 0;
+ ret = 1;
+ goto next;
+ } else {
+ gusboxes = 1;
+ if (sctx->conf->map_inbox)
+ add_string_list( &usboxes, sctx->conf->map_inbox );
+ }
+ }
+ mboxes = filter_boxes( umboxes, chan->patterns );
+ sboxes = filter_boxes( usboxes, chan->patterns );
+ for (mboxp = &mboxes; (mbox = *mboxp); ) {
+ for (sboxp = &sboxes; (sbox = *sboxp); sboxp = &sbox->next)
+ if (!strcmp( sbox->string, mbox->string )) {
+ *sboxp = sbox->next;
+ free( sbox );
+ *mboxp = mbox->next;
+ mbox->next = cboxes;
+ cboxes = mbox;
+ goto gotdupe;
+ }
+ mboxp = &mbox->next;
+ gotdupe: ;
+ }
+ for (mbox = cboxes; mbox; mbox = mbox->next)
+ if (list)
+ puts( mbox->string );
+ else
+ switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) {
+ case SYNC_MASTER_BAD: goto screwm;
+ case SYNC_SLAVE_BAD: goto screws;
+ case SYNC_FAIL: ret = 1;
+ }
+ if ((chan->sops & OP_MASK_TYPE) && (chan->sops & OP_CREATE)) {
+ for (mbox = mboxes; mbox; mbox = mbox->next)
+ if (list)
+ puts( mbox->string );
+ else
+ switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) {
+ case SYNC_MASTER_BAD: goto screwm;
+ case SYNC_SLAVE_BAD: goto screws;
+ case SYNC_FAIL: ret = 1;
+ }
+ }
+ if ((chan->mops & OP_MASK_TYPE) && (chan->mops & OP_CREATE)) {
+ for (mbox = sboxes; mbox; mbox = mbox->next)
+ if (list)
+ puts( mbox->string );
+ else
+ switch (sync_boxes( mctx, mbox->string, sctx, mbox->string, chan )) {
+ case SYNC_MASTER_BAD: goto screwm;
+ case SYNC_SLAVE_BAD: goto screws;
+ case SYNC_FAIL: ret = 1;
+ }
+ }
+ } else
+ if (list)
+ printf( "%s <=> %s\n", chan->master_name, chan->slave_name );
+ else
+ switch (sync_boxes( mctx, chan->master_name, sctx, chan->slave_name, chan )) {
+ case SYNC_MASTER_BAD: goto screwm;
+ case SYNC_SLAVE_BAD: goto screws;
+ case SYNC_FAIL: ret = 1;
+ }
+
+ next:
+ free_string_list( cboxes );
+ free_string_list( mboxes );
+ free_string_list( sboxes );
+ if (all) {
+ if (!(chan = chan->next))
+ break;
+ } else {
+ if (chanptr && (chanptr = chanptr->next))
+ continue;
+ gotnone:
+ if (!argv[++oind])
+ break;
+ }
+ }
+ free_string_list( usboxes );
+ if (sctx)
+ sdriver->close_store( sctx );
+ free_string_list( umboxes );
+ if (mctx)
+ mdriver->close_store( mctx );
+
+ return ret;
}
diff --git a/src/mbsync.1 b/src/mbsync.1
@@ -0,0 +1,471 @@
+.ig
+\" mbsync - mailbox synchronizer
+\" Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+\" Copyright (C) 2002-2004 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, write to the Free Software
+\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\"
+\" As a special exception, mbsync may be linked with the OpenSSL library,
+\" despite that library's more restrictive license.
+..
+.TH mbsync 1 "2004 Mar 27"
+..
+.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 (as opposed to some other mail synchronizers).
+OTOH, \fBmbsync\fR is susceptible to UID validity changes (that \fIshould\fR
+never happen, but see "Compatibility" in the README).
+Synchronization state is kept in one local text file per mailbox pair;
+multiple replicas of a 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[\fBm\fR][\fBs\fR], \fB--create\fR[\fB-master\fR|\fB-slave\fR]
+Override any \fBCreate\fR options from the config file. See below.
+.TP
+\fB-X\fR[\fBm\fR][\fBs\fR], \fB--expunge\fR[\fB-master\fR|\fB-slave\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 the IMAP4 network traffic.
+.TP
+\fB-D\fR, \fB--debug\fR
+Enable printing \fIdebug\fR information.
+.TP
+\fB-q\fR, \fB--quiet\fR
+Suppress informational messages.
+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).
+All keywords (including those used as arguments) are case-insensitive.
+There are a few global options, the rest applies to particular sections.
+Sections are started by a section keyword and are terminated by an empty line
+or end of file.
+Every section defines an object with a 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 remote Stores, so a server connection can be
+shared between multiple Stores. A Group aggregates multiple Channels to
+save typing on the command line.
+..
+.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
+in the Channels section.
+Note that you \fBmust\fR append a slash if you want to specify an entire
+directory.
+(Default: \fI""\fR)
+..
+.TP
+\fBMaxSize\fR \fIsize\fR[\fBk\fR|\fBm\fR][\fBb\fR]
+Messages larger than that will not be propagated into this Store.
+This is useful for weeding out messages with large attachments.
+\fBK\fR and \fBM\fR can be appended to the size to specify KiBytes resp.
+MeBytes instead of bytes. \fBB\fR is accepted but superflous.
+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 is backed by
+the \fBINBOX\fR. Makes sense in conjunction with \fBPatterns\fR in the
+Channels section.
+..
+.TP
+\fBTrash\fR \fImailbox\fR
+Specifies a mailbox (relative to \fBPath\fR) to copy deleted messages to
+prior to expunging. See \fBINHERENT PROBLEMS\fR below.
+(Default: none)
+..
+.TP
+\fBTrashNewOnly\fR \fIyes\fR|\fIno\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 \fIno\fR).
+(Default: \fIno\fR)
+..
+.TP
+\fBTrashRemoteNew\fR \fIyes\fR|\fIno\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: \fIno\fR)
+..
+.SS Maildir Stores
+The reference point for relative \fBPath\fRs is $HOME.
+.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, endianess 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 \fIyes\fR|\fIno\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.
+(Default: \fIno\fR)
+..
+.TP
+\fBInbox\fR \fIpath\fR
+The location of the \fBINBOX\fR. This is \fInot\fR relative to \fBPath\fR.
+(Default: \fI~/Maildir\fR)
+..
+.SS IMAP4 Accounts
+.TP
+\fBIMAPAccount\fR \fIname\fR
+Define the IMAP4 Account \fIname\fR, opening a section for its parameters.
+..
+.TP
+\fBHost\fR [\fBimaps:\fR]\fIhost\fR
+Specify the DNS name or IP address of the IMAP server. If \fIhost\fR is
+prefixed with \fBimaps:\fR the connection is assumed to be an SSL connection
+to port 993.
+Note that modern servers support SSL on the default port 143 via the
+STARTTLS extension, which will be used automatically by default.
+..
+.TP
+\fBPort\fR \fIport\fR
+Specify the TCP port number of the IMAP server. (Default: 143 for imap,
+993 for imaps)
+..
+.TP
+\fBUser\fR \fIusername\fR
+Specify the login name on the IMAP server. (Default: current local user)
+..
+.TP
+\fBPass\fR \fIpassword\fR
+Specify the password for \fIusername\fR on the IMAP server.
+Note that this option is \fBNOT\fR required.
+If no password is specified in the configuration file, \fBmbsync\fR
+will prompt you for it.
+..
+.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. This makes most other IMAPAccount options superflous.
+..
+.TP
+\fBRequireCRAM\fR \fIyes\fR|\fIno\fR
+If set to \fIyes\fR, \fBmbsync\fR will abort the connection if no CRAM-MD5
+authentication is possible. (Default: \fIno\fR)
+..
+.TP
+\fBRequireSSL\fR \fIyes\fR|\fIno\fR
+\fBmbsync\fR will abort the connection if a TLS/SSL session cannot be
+established with the IMAP server. (Default: \fIyes\fR)
+..
+.TP
+\fBCertificateFile\fR \fIpath\fR
+File containing X.509 CA certificates used to verify server identities.
+This option is \fImandatory\fR if SSL is used. See \fBSSL CERTIFICATES\fR below.
+..
+.TP
+\fBUseSSLv2\fR \fIyes\fR|\fIno\fR
+Use SSLv2 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if an imaps \fBHost\fR is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseSSLv3\fR \fIyes\fR|\fIno\fR
+Use SSLv3 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if an imaps \fBHost\fR is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseTLSv1\fR \fIyes\fR|\fIno\fR
+Use TLSv1 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR)
+..
+.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 \fIyes\fR|\fIno\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: \fIyes\fR)
+..
+.SS Channels
+.TP
+\fBChannel\fR \fIname\fR
+Define the Channel \fIname\fR, opening a section for its parameters.
+..
+.TP
+{\fBMaster\fR|\fBSlave\fR} \fB:\fIstore\fB:\fR[\fImailbox\fR]
+Specify the Master resp. Slave Store to be connected by this Channel.
+If \fImailbox\fR is omitted, \fBINBOX\fR is assumed.
+The \fImailbox\fR specification can be overridden by \fBPatterns\fR, which
+in turn can be overridden by a mailbox list in a Channel reference (a Group
+specification or the command line).
+..
+.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 both
+Master and Slave. 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.
+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 Master and Slave. 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 Slave 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 recent messages will not
+be automatically deleted.
+If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR
+(Default: \fI0\fR).
+..
+.TP
+\fBSync\fR {\fINone\fR|[\fIPull\fR] [\fIPush\fR] [\fINew\fR] [\fIReNew\fR] [\fIDelete\fR] [\fIFlags\fR]|\fIFull\fR}
+Select the synchronization operation(s) to perform:
+.br
+\fBPull\fR - propagate changes from Master to Slave.
+.br
+\fBPush\fR - propagate changes from Slave to Master.
+.br
+\fBNew\fR - propagate newly appeared messages.
+.br
+\fBReNew\fR - previously refused messages are re-evaluated for propagation.
+Useful after flagging affected messages in the source Store or enlarging
+MaxSize in the destination Store.
+.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 particularily 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 Master to the Slave,
+"\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 Slave to the Master.
+.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 Master to the Slave and any changes
+from the Slave to the Master.
+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 {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR}
+Automatically create missing mailboxes [on the Master/Slave].
+Otherwise print an error message and skip that mailbox pair if a mailbox
+does not exist.
+(Global default: \fINone\fR)
+..
+.TP
+\fBExpunge\fR {\fINone\fR|\fIMaster\fR|\fISlave\fR|\fIBoth\fR}
+Permanently remove all messages [on the Master/Slave] marked for deletion.
+(Global default: \fINone\fR)
+..
+.P
+\fBSync\fR, \fBCreate\fR and \fBExpunge\fR can be used outside 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
+Slave mailbox itself; this has the advantage that you needn't to care for the
+state file if you delete the mailbox, but it works only with Maildir mailboxes,
+obviously. Otherwise this is interpreted as a string to prepend to the Slave
+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:\fImaster\fB:\fImaster-box\fB_:\fIslave\fB:\fIslave-box\fR.
+.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. 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.
+..
+.SH SSL CERTIFICATES
+[to be done]
+..
+.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 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 not simultaneously accessed by \fBmbsync\fR and another mail client.
+..
+.SH FILES
+.TP
+.B ~/.mbsyncrc
+Default configuration file
+.TP
+.B ~/.mbsync/
+Directory containing synchronization state files
+..
+.SH SEE ALSO
+mdconvert(1), isync(1), mutt(1), maildir(5)
+.P
+Up to date information on \fBmbsync\fR can be found at http://isync.sf.net/
+..
+.SH AUTHOR
+Written by Michael R. Elkins <me@mutt.org>,
+.br
+rewritten and maintained by Oswald Buddenhagen <ossi@users.sf.net>,
+.br
+contributions by Theodore Y. Ts'o <tytso@mit.edu>.
diff --git a/src/mbsyncrc.sample b/src/mbsyncrc.sample
@@ -0,0 +1,82 @@
+# 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
+Pass xxxxxxxx
+CertificateFile /etc/ssl/certs/ca-certificates.crt
+
+Channel work
+Master :work:
+Slave :local:work
+Expunge Slave
+Sync PullNew Push
+
+
+IMAPStore personal
+Host host.play.com
+Port 6789
+RequireSSL no
+
+Channel personal
+Master :personal:
+Slave :local:personal
+Expunge Both
+MaxMessages 150
+MaxSize 200k
+
+IMAPStore remote
+Tunnel "ssh -q host.remote.com /usr/sbin/imapd"
+
+Channel remote
+Master :remote:
+Slave :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
+Master :st1:somebox
+Slave :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/
+
+Channel o2o
+Master :server:
+Slave :mirror:
+Patterns %
+
+Group partial o2o:inbox,sent-mail,foobar
diff --git a/src/mdconvert.1 b/src/mdconvert.1
@@ -0,0 +1,51 @@
+.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, write to the Free Software
+\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+..
+.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>.
diff --git a/src/mdconvert.c b/src/mdconvert.c
@@ -0,0 +1,256 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <config.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"
+
+static int
+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) {
+ fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
+ abort();
+ }
+ va_end( va );
+ return ret;
+}
+
+static const char *subdirs[] = { "cur", "new" };
+static struct flock lck;
+static DBT key, value;
+
+static inline 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)
+ perror( spath );
+ return 1;
+ }
+ if (fcntl( sfd, F_SETLKW, &lck )) {
+ perror( spath );
+ goto sbork;
+ }
+ if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) {
+ perror( tdpath );
+ goto sbork;
+ }
+ if (db_create( &db, 0, 0 )) {
+ fputs( "Error: db_create() failed\n", stderr );
+ goto tbork;
+ }
+ if ((ret = db->open( db, 0, dbpath, 0, DB_HASH, DB_CREATE, 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) )) <= 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, 0, &key, &value, 0 ))) {
+ db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box );
+ goto dbork;
+ }
+ } else {
+ if ((ret = db->get( db, 0, &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 ))) {
+ perror( "opendir" );
+ 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 = INT_MAX;
+ 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, 0, &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, 0, &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)
+ goto again;
+ perror( buf );
+ ebork:
+ closedir( d );
+ goto dbork;
+ }
+
+ }
+ closedir( d );
+ }
+
+ db->close( db, 0 );
+ close( dfd );
+ if (rename( tdpath, dpath )) {
+ perror( dpath );
+ return 1;
+ }
+ if (unlink( spath ))
+ perror( 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;
+}
+
diff --git a/src/sync.c b/src/sync.c
@@ -1,8 +1,7 @@
-/* $Id$
- *
- * isync - IMAP4 to maildir mailbox synchronizer
+/*
+ * mbsync - mailbox synchronizer
* Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
- * Copyright (C) 2002-2003 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2002-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
@@ -18,7 +17,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
- * As a special exception, isync may be linked with the OpenSSL library,
+ * As a special exception, mbsync may be linked with the OpenSSL library,
* despite that library's more restrictive license.
*/
@@ -34,367 +33,940 @@
#include <errno.h>
#include <sys/stat.h>
-static unsigned int MaildirCount = 0;
+static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
-message_t *
-find_msg (message_t * list, unsigned int uid)
+static int
+parse_flags( const char *buf )
{
- for (; list; list = list->next)
- if (list->uid == uid)
- return list;
- return 0;
+ unsigned flags, i, d;
+
+ for (flags = i = d = 0; i < as(Flags); i++)
+ if (buf[d] == Flags[i]) {
+ flags |= (1 << i);
+ d++;
+ }
+ return flags;
}
static int
-set_uid (DB * db, const char *f, unsigned int uid)
+make_flags( int flags, char *buf )
{
- char *s;
- DBT key, val;
- int ret;
-
- memset (&key, 0, sizeof(key));
- memset (&val, 0, sizeof(val));
- key.data = (void *) f;
- s = strchr (f, ':');
- key.size = s ? (size_t) (s - f) : strlen (f);
- val.data = (void *) &uid;
- val.size = sizeof (uid);
- ret = db->put (db, 0, &key, &val, 0);
- if (ret < 0)
- fprintf (stderr, "Unexpected error (%d) from db_put(%.*s, %d)\n",
- ret, key.size, f, uid);
- db->sync(db, 0);
- return 0;
+ unsigned i, d;
+
+ for (i = d = 0; i < as(Flags); i++)
+ if (flags & (1 << i))
+ buf[d++] = Flags[i];
+ buf[d] = 0;
+ return d;
}
-int
-sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
- unsigned int max_size, unsigned int max_msgs)
+static void
+makeopts( int dops, store_conf_t *dconf, int *dopts,
+ store_conf_t *sconf, int *sopts )
{
- 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;
- int upload = 0;
- unsigned int msg_count;
-
- if (mbox->uidseen)
- {
- if (mbox->uidvalidity != imap->uidvalidity)
- {
- /* if the UIDVALIDITY value has changed, it means all our
- * local UIDs are invalid, so we can't sync.
- */
- fprintf (stderr,
- "ERROR: UIDVALIDITY of '%s' changed on server\n",
- imap->box->box);
- return -1;
+ if (dops & (OP_DELETE|OP_FLAGS)) {
+ *dopts |= OPEN_SETFLAGS;
+ *sopts |= OPEN_OLD;
+ if (dops & OP_FLAGS)
+ *sopts |= OPEN_FLAGS;
+ }
+ if (dops & (OP_NEW|OP_RENEW)) {
+ *dopts |= OPEN_APPEND;
+ if (dops & OP_RENEW)
+ *sopts |= OPEN_OLD;
+ if (dops & OP_NEW)
+ *sopts |= OPEN_NEW;
+ if (dops & OP_EXPUNGE)
+ *sopts |= OPEN_FLAGS;
+ if (dconf->max_size)
+ *sopts |= OPEN_SIZE;
+ }
+ if (dops & OP_EXPUNGE) {
+ *dopts |= OPEN_EXPUNGE;
+ if (dconf->trash) {
+ if (!dconf->trash_only_new)
+ *dopts |= OPEN_OLD;
+ *dopts |= OPEN_NEW|OPEN_FLAGS;
+ } else if (sconf->trash && sconf->trash_remote_new)
+ *dopts |= OPEN_NEW|OPEN_FLAGS;
}
- }
- else if (maildir_set_uidvalidity (mbox, imap->uidvalidity))
- {
- fputs ("ERROR: unable to store UIDVALIDITY\n", stderr);
- return -1;
- }
-
- if (mbox->maxuid == 0 || imap->maxuid > mbox->maxuid)
- {
- mbox->maxuid = imap->maxuid;
- }
-
- /* 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)
- {
- /* if this message wasn't fetched from the server, attempt to
- * upload it
- */
- if (cur->uid == (unsigned int) -1)
- {
- struct stat sb;
-
- if ((cur->flags & D_DELETED) && (flags & SYNC_EXPUNGE))
- {
- /*
- * This message is marked as deleted and we are
- * expunging. Don't upload to the server.
- */
- continue;
+ if (dops & OP_CREATE)
+ *dopts |= OPEN_CREATE;
+}
+
+static void
+dump_box( store_t *ctx )
+{
+ message_t *msg;
+ char fbuf[16]; /* enlarge when support for keywords is added */
+
+ if (Debug)
+ for (msg = ctx->msgs; msg; msg = msg->next) {
+ make_flags( msg->flags, fbuf );
+ printf( " message %d, %s, %d\n", msg->uid, fbuf, msg->size );
}
+}
- if (!upload)
- info ("Uploading messages");
- infoc ('.');
- fflush (stdout);
- upload++;
-
- /* upload the message if its not too big */
- snprintf (path, sizeof (path), "%s/%s/%s", mbox->path,
- cur->new ? "new" : "cur", cur->file);
- if (stat (path, &sb))
- {
- perror (path);
- continue; /* not fatal */
+static message_t *
+findmsg( store_t *ctx, int uid, message_t **nmsg, const char *who )
+{
+ message_t *msg;
+
+ if (uid > 0) {
+ if (*nmsg && (*nmsg)->uid == uid) {
+ debug( " %s came in sequence\n", who );
+ msg = *nmsg;
+ found:
+ *nmsg = msg->next;
+ if (!(msg->status & M_DEAD)) {
+ msg->status |= M_PROCESSED;
+ return msg;
+ }
+ debug( " ... but it vanished under our feet!\n" );
+ } else {
+ for (msg = ctx->msgs; msg; msg = msg->next)
+ if (msg->uid == uid) {
+ debug( " %s came out of sequence\n", who );
+ goto found;
+ }
+ debug( " %s not present\n", who );
}
- if (imap->box->max_size > 0
- && sb.st_size > imap->box->max_size)
- {
- info ("Local message %s is too large (%lu), skipping...\n",
- cur->file, (unsigned long) sb.st_size);
- continue;
+ } else
+ debug( " no %s expected\n", who );
+ return 0;
+}
+
+#define S_DEAD (1<<0)
+#define S_EXPIRED (1<<1)
+#define S_DEL_MASTER (1<<2)
+#define S_DEL_SLAVE (1<<3)
+#define S_EXP_SLAVE (1<<4)
+
+typedef struct sync_rec {
+ struct sync_rec *next;
+ /* string_list_t *keywords; */
+ int muid, suid;
+ unsigned char flags, status;
+} sync_rec_t;
+
+
+#define EX_OK 0
+#define EX_FAIL 1
+#define EX_STORE_BAD 2
+#define EX_RSTORE_BAD 3
+
+static int
+expunge( store_t *ctx, store_t *rctx )
+{
+ driver_t *driver = ctx->conf->driver, *rdriver = rctx->conf->driver;
+ message_t *msg;
+ msg_data_t msgdata;
+
+ for (msg = ctx->msgs; msg; msg = msg->next)
+ if (msg->flags & F_DELETED) {
+ if (ctx->conf->trash) {
+ if (!ctx->conf->trash_only_new || (msg->status & M_NOT_SYNCED)) {
+ debug( " trashing message %d\n", msg->uid );
+ switch (driver->trash_msg( ctx, msg )) {
+ case DRV_STORE_BAD: return EX_STORE_BAD;
+ default: return EX_FAIL;
+ case DRV_OK: break;
+ }
+ } else
+ debug( " not trashing message %d - not new\n", msg->uid );
+ } else if (rctx->conf->trash && rctx->conf->trash_remote_new) {
+ if (msg->status & M_NOT_SYNCED) {
+ if (!rctx->conf->max_size || msg->size <= rctx->conf->max_size) {
+ debug( " remote trashing message %d\n", msg->uid );
+ msgdata.flags = msg->flags;
+ switch (driver->fetch_msg( ctx, msg, &msgdata )) {
+ case DRV_STORE_BAD: return EX_STORE_BAD;
+ default: return EX_FAIL;
+ case DRV_OK: break;
+ }
+ switch (rdriver->store_msg( rctx, &msgdata, 0 )) {
+ case DRV_STORE_BAD: return EX_RSTORE_BAD;
+ default: return EX_FAIL;
+ case DRV_OK: break;
+ }
+ } else
+ debug( " not remote trashing message %d - too big\n", msg->uid );
+ } else
+ debug( " not remote trashing message %d - not new\n", msg->uid );
+ }
+ }
+
+ switch (driver->close( ctx )) {
+ case DRV_STORE_BAD: return EX_STORE_BAD;
+ default: return EX_FAIL;
+ case DRV_OK: return EX_OK;;
+ }
+}
+
+/* cases:
+ a) both non-null
+ b) only master null
+ b.1) muid 0
+ b.2) muid -1
+ b.3) master not scanned
+ b.4) master gone
+ c) only slave null
+ c.1) suid 0
+ c.2) suid -1
+ c.3) slave not scanned
+ c.4) slave gone
+ d) both null
+ d.1) both gone
+ d.2) muid 0, slave not scanned
+ d.3) muid -1, slave not scanned
+ d.4) master gone, slave not scanned
+ d.5) muid 0, slave gone
+ d.6) muid -1, slave gone
+ d.7) suid 0, master not scanned
+ d.8) suid -1, master not scanned
+ d.9) slave gone, master not scanned
+ d.10) suid 0, master gone
+ d.11) suid -1, master gone
+ impossible cases: both muid & suid 0 or -1, both not scanned
+*/
+static int
+sync_old( int tops, store_t *sctx, store_t *tctx, store_conf_t *tconf, FILE *jfp, int pull,
+ unsigned char *nflags, sync_rec_t *srec, message_t *smsg, message_t *tmsg, int dels, int delt )
+{
+ driver_t *tdriver = tctx->conf->driver, *sdriver = sctx->conf->driver;
+ int uid, tuid, unex;
+ unsigned char sflags, aflags, dflags, rflags;
+ msg_data_t msgdata;
+
+ /* excludes (push) c.3) d.2) d.3) d.4) / (pull) b.3) d.7) d.8) d.9) */
+ tuid = pull ? srec->suid : srec->muid;
+ if (!tuid) {
+ /* b.1) / c.1) */
+ debug( pull ? " no more slave\n" : " no more master\n" );
+ } else if (dels) {
+ /* c.4) d.9) / b.4) d.4) */
+ debug( pull ? " master vanished\n" : " slave vanished\n" );
+ if (tmsg && tmsg->flags != *nflags)
+ info( "Info: conflicting changes in (%d,%d)\n", srec->muid, srec->suid );
+ if (tops & OP_DELETE) {
+ debug( pull ? " -> pulling delete\n" : " -> pushing delete\n" );
+ switch (tdriver->set_flags( tctx, tmsg, tuid, F_DELETED, 0 )) {
+ case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD;
+ case DRV_BOX_BAD: return SYNC_FAIL;
+ default: /* ok */ break;
+ case DRV_OK:
+ if (pull) {
+ fprintf( jfp, "< %d %d 0\n", srec->muid, srec->suid );
+ srec->muid = 0;
+ } else {
+ fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid );
+ srec->suid = 0;
+ }
+ }
}
- fd = open (path, O_RDONLY);
- if (fd == -1)
- {
- /* This can happen if the message was simply deleted (ok)
- or the flags changed (not ok - maildir sucks). */
- fprintf (stderr, "Error, unable to open %s: %s (errno %d)\n",
- path, strerror (errno), errno);
- continue;
+ } else if (!smsg)
+ /* c.1) c.2) d.7) d.8) / b.1) b.2) d.2) d.3) */
+ ;
+ else if (tuid < 0) {
+ /* b.2) / c.2) */
+ debug( pull ? " no slave yet\n" : " no master yet\n" );
+ if (tops & OP_RENEW) {
+ if ((tops & OP_EXPUNGE) && (smsg->flags & F_DELETED)) {
+ debug( pull ? " -> not pulling - would be expunged anyway\n" : " -> not pushing - would be expunged anyway\n" );
+ smsg->status |= M_NOT_SYNCED;
+ } else {
+ if ((smsg->flags & F_FLAGGED) || !tconf->max_size || smsg->size <= tconf->max_size) {
+ debug( pull ? " -> pulling it\n" : " -> pushing it\n" );
+ msgdata.flags = smsg->flags;
+ switch (sdriver->fetch_msg( sctx, smsg, &msgdata )) {
+ case DRV_STORE_BAD: return pull ? SYNC_MASTER_BAD : SYNC_SLAVE_BAD;
+ case DRV_BOX_BAD: return SYNC_FAIL;
+ default: /* ok */ smsg->status |= M_NOT_SYNCED; break;
+ case DRV_OK:
+ smsg->flags = msgdata.flags;
+ switch (tdriver->store_msg( tctx, &msgdata, &uid )) {
+ case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD;
+ default: return SYNC_FAIL;
+ case DRV_OK:
+ if (pull) {
+ srec->suid = uid;
+ fprintf( jfp, "> %d -1 %d\n", srec->muid, srec->suid );
+ } else {
+ srec->muid = uid;
+ fprintf( jfp, "< -1 %d %d\n", srec->suid, srec->muid );
+ }
+ *nflags = smsg->flags;
+ }
+ }
+ } else {
+ debug( pull ? " -> not pulling - still too big\n" : " -> not pushing - still too big\n" );
+ smsg->status |= M_NOT_SYNCED;
+ }
+ }
+ } else
+ smsg->status |= M_NOT_SYNCED;
+ } else if (!delt) {
+ /* a) & b.3) / c.3) */
+ debug( pull ? " may pull\n" : " may push\n" );
+ if (tops & OP_FLAGS) {
+ debug( pull ? " -> pulling flags\n" : " -> pushing flags\n" );
+ sflags = smsg->flags;
+ aflags = sflags & ~*nflags;
+ dflags = ~sflags & *nflags;
+ unex = 0;
+ if (srec->status & S_EXPIRED) {
+ if (!pull) {
+ if (sflags & F_DELETED) {
+ if (!(sflags & F_FLAGGED))
+ aflags &= ~F_DELETED;
+ } else
+ unex = 1;
+ } else {
+ if ((sflags & F_FLAGGED) && !(sflags & F_DELETED)) {
+ unex = 1;
+ dflags |= F_DELETED;
+ }
+ }
+ }
+ rflags = (*nflags | aflags) & ~dflags;
+ if ((tops & OP_EXPUNGE) && (rflags & F_DELETED) &&
+ (!tctx->conf->trash || tctx->conf->trash_only_new))
+ {
+ aflags &= F_DELETED;
+ dflags = 0;
+ }
+ switch (tdriver->set_flags( tctx, tmsg, tuid, aflags, dflags )) {
+ case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD;
+ case DRV_BOX_BAD: return SYNC_FAIL;
+ default: /* ok */ break;
+ case DRV_OK:
+ *nflags = rflags;
+ if (unex) {
+ debug( "unexpiring pair(%d,%d)\n", srec->muid, srec->suid );
+ /* log last, so deletion can't be misinterpreted! */
+ fprintf( jfp, "~ %d %d 0\n", srec->muid, srec->suid );
+ srec->status &= ~S_EXPIRED;
+ }
+ }
}
+ } /* else b.4) / c.4) */
+ return SYNC_OK;
+}
- cur->size = sb.st_size;
- cur->uid = imap_append_message (imap, fd, cur);
- if (cur->uid != (unsigned int) -1) {
- /* update the db */
- set_uid (mbox->db, cur->file, cur->uid);
- if (!cur->uid)
- warn ("Warning: no UID for new messge %s\n", cur->file);
- else if (cur->uid > mbox->maxuid)
- mbox->maxuid = cur->uid;
+static int
+sync_new( int tops, store_t *sctx, store_t *tctx, store_conf_t *tconf, FILE *jfp, sync_rec_t ***srecadd, int pull, int *smaxuid )
+{
+ driver_t *tdriver = tctx->conf->driver, *sdriver = sctx->conf->driver;
+ sync_rec_t *srec;
+ message_t *msg;
+ int nmsgs, uid;
+ msg_data_t msgdata;
+
+ for (nmsgs = 0, msg = sctx->msgs; msg; msg = msg->next)
+ if (!(msg->status & M_PROCESSED)) {
+ if (tops & OP_NEW) {
+ debug( pull ? "new message %d on master\n" : "new message %d on slave\n", msg->uid );
+ if ((tops & OP_EXPUNGE) && (msg->flags & F_DELETED)) {
+ debug( pull ? " not pulling - would be expunged anyway\n" : " not pushing - would be expunged anyway\n" );
+ msg->status |= M_NOT_SYNCED;
+ } else {
+ if ((msg->flags & F_FLAGGED) || !tconf->max_size || msg->size <= tconf->max_size) {
+ debug( pull ? " pulling it\n" : " pushing it\n" );
+ if (!nmsgs)
+ info( pull ? "Pulling new messages..." : "Pushing new messages..." );
+ else
+ infoc( '.' );
+ nmsgs++;
+ msgdata.flags = msg->flags;
+ switch (sdriver->fetch_msg( sctx, msg, &msgdata )) {
+ case DRV_STORE_BAD: return pull ? SYNC_MASTER_BAD : SYNC_SLAVE_BAD;
+ case DRV_BOX_BAD: return SYNC_FAIL;
+ case DRV_MSG_BAD: /* ok */ msg->status |= M_NOT_SYNCED; continue;
+ }
+ msg->flags = msgdata.flags;
+ switch (tdriver->store_msg( tctx, &msgdata, &uid )) {
+ case DRV_STORE_BAD: return pull ? SYNC_SLAVE_BAD : SYNC_MASTER_BAD;
+ default: return SYNC_FAIL;
+ case DRV_OK: break;
+ }
+ } else {
+ debug( pull ? " not pulling - too big\n" : " not pushing - too big\n" );
+ msg->status |= M_NOT_SYNCED;
+ uid = -1;
+ }
+ srec = nfmalloc( sizeof(*srec) );
+ if (pull) {
+ srec->muid = msg->uid;
+ srec->suid = uid;
+ } else {
+ srec->muid = uid;
+ srec->suid = msg->uid;
+ }
+ srec->flags = msg->flags;
+ srec->status = 0;
+ srec->next = 0;
+ **srecadd = srec;
+ *srecadd = &srec->next;
+ fprintf( jfp, "+ %d %d %u\n", srec->muid, srec->suid, srec->flags );
+ if (*smaxuid < msg->uid) {
+ *smaxuid = msg->uid;
+ fprintf( jfp, pull ? "( %d\n" : ") %d\n", msg->uid );
+ }
+ }
+ } else
+ msg->status |= M_NOT_SYNCED;
}
+ if (nmsgs)
+ info( " %d messages\n", nmsgs );
+ return SYNC_OK;
+}
- close (fd);
- }
- /*
- * message used to exist on server but no longer does (we know
- * this beacause it has a UID associated with it).
- */
- else if (flags & SYNC_DELETE)
- {
- cur->flags |= D_DELETED;
- cur->dead = 1;
- mbox->deleted++;
- }
- /* if the user doesn't want local msgs deleted when they don't
- * exist on the server, warn that such messages exist.
- */
- else
- info ("Local message %u doesn't exist on server\n", cur->uid);
- continue;
+static char *
+clean_strdup( const char *s )
+{
+ char *cs;
+ int i;
+
+ cs = nfstrdup( s );
+ for (i = 0; cs[i]; i++)
+ if (cs[i] == '/')
+ cs[i] = '!';
+ return cs;
+}
+
+int
+sync_boxes( store_t *mctx, const char *mname,
+ store_t *sctx, const char *sname,
+ channel_conf_t *chan )
+{
+ driver_t *mdriver = mctx->conf->driver, *sdriver = sctx->conf->driver;
+ message_t *mmsg, *smsg, *nmmsg, *nsmsg;
+ sync_rec_t *recs, *srec, **srecadd, *nsrec;
+ char *dname, *jname, *nname, *lname, *s, *cmname, *csname;
+ FILE *dfp, *jfp, *nfp;
+ int mopts, sopts;
+ int nom, nos, delm, dels, mex, sex;
+ int muidval, suidval, smaxxuid, mmaxuid, smaxuid, minwuid, maxwuid;
+ int t1, t2, t3;
+ int lfd, ret, line, todel, delt, i, *mexcs, nmexcs, rmexcs;
+ unsigned char nflags;
+ struct stat st;
+ struct flock lck;
+ char fbuf[16]; /* enlarge when support for keywords is added */
+ char buf[64];
+
+ ret = SYNC_OK;
+ recs = 0, srecadd = &recs;
+
+ nmmsg = nsmsg = 0;
+
+ mctx->uidvalidity = sctx->uidvalidity = 0;
+ mopts = sopts = 0;
+ makeopts( chan->sops, chan->slave, &sopts, chan->master, &mopts );
+ makeopts( chan->mops, chan->master, &mopts, chan->slave, &sopts );
+ if ((chan->sops & (OP_NEW|OP_RENEW)) && chan->max_messages)
+ sopts |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS;
+ if (!mname || (mctx->conf->map_inbox && !strcmp( mctx->conf->map_inbox, mname )))
+ mname = "INBOX";
+ mctx->name = mname;
+ mdriver->prepare( mctx, mopts );
+ if (!sname || (sctx->conf->map_inbox && !strcmp( sctx->conf->map_inbox, sname )))
+ sname = "INBOX";
+ sctx->name = sname;
+ sdriver->prepare( sctx, sopts );
+
+ if (!strcmp( chan->sync_state ? chan->sync_state : global_sync_state, "*" )) {
+ if (!sctx->path) {
+ fprintf( stderr, "Error: store '%s' does not support in-box sync state\n", chan->slave->name );
+ return SYNC_SLAVE_BAD;
+ }
+ nfasprintf( &dname, "%s/." EXE "state", sctx->path );
+ } else {
+ csname = clean_strdup( sname );
+ if (chan->sync_state)
+ nfasprintf( &dname, "%s%s", chan->sync_state, csname );
+ else {
+ cmname = clean_strdup( mname );
+ nfasprintf( &dname, "%s:%s:%s_:%s:%s", global_sync_state,
+ chan->master->name, cmname, chan->slave->name, csname );
+ free( cmname );
+ }
+ free( csname );
+ }
+ nfasprintf( &jname, "%s.journal", dname );
+ nfasprintf( &nname, "%s.new", dname );
+ nfasprintf( &lname, "%s.lock", dname );
+ muidval = suidval = smaxxuid = mmaxuid = smaxuid = 0;
+ memset( &lck, 0, sizeof(lck) );
+#if SEEK_SET != 0
+ lck.l_whence = SEEK_SET;
+#endif
+#if F_WRLCK != 0
+ lck.l_type = F_WRLCK;
+#endif
+ if ((lfd = open( lname, O_WRONLY|O_CREAT, 0666 )) < 0) {
+ if (errno != ENOENT) {
+ lferr:
+ fprintf( stderr, "Error: cannot create lock file %s: %s\n", lname, strerror(errno) );
+ ret = SYNC_FAIL;
+ goto bail2;
+ }
+ goto skiprd;
+ }
+ if (fcntl( lfd, F_SETLK, &lck )) {
+ lckerr:
+ fprintf( stderr, "Error: channel :%s:%s-:%s:%s is locked\n",
+ chan->master->name, mname, chan->slave->name, sname );
+ ret = SYNC_FAIL;
+ goto bail1;
+ }
+ if ((dfp = fopen( dname, "r" ))) {
+ debug( "reading sync state %s ...\n", dname );
+ if (fscanf( dfp, "%d:%d %d:%d:%d\n", &muidval, &mmaxuid, &suidval, &smaxxuid, &smaxuid) != 5) {
+ fprintf( stderr, "Error: invalid sync state header in %s\n", dname );
+ fclose( dfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ line = 1;
+ while (fgets( buf, sizeof(buf), dfp )) {
+ line++;
+ fbuf[0] = 0;
+ if (sscanf( buf, "%d %d %15s\n", &t1, &t2, fbuf ) < 2) {
+ fprintf( stderr, "Error: invalid sync state entry at %s:%d\n", dname, line );
+ fclose( dfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ srec = nfmalloc( sizeof(*srec) );
+ srec->muid = t1;
+ srec->suid = t2;
+ s = fbuf;
+ if (*s == 'X') {
+ s++;
+ srec->status = S_EXPIRED;
+ } else
+ srec->status = 0;
+ srec->flags = parse_flags( s );
+ debug( " entry (%d,%d,%u,%s)\n", srec->muid, srec->suid, srec->flags, srec->status & S_EXPIRED ? "X" : "" );
+ srec->next = 0;
+ *srecadd = srec;
+ srecadd = &srec->next;
+ }
+ fclose( dfp );
+ } else {
+ if (errno != ENOENT) {
+ fprintf( stderr, "Error: cannot read sync state %s\n", dname );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ }
+ if ((jfp = fopen( jname, "r" ))) {
+ if (!stat( nname, &st )) {
+ debug( "recovering journal ...\n" );
+ line = 0;
+ srec = recs;
+ while (fgets( buf, sizeof(buf), jfp )) {
+ line++;
+ if (buf[0] == '^')
+ srec = recs;
+ else {
+ if (buf[0] == '(' || buf[0] == ')' ?
+ (sscanf( buf + 2, "%d\n", &t1 ) != 1) :
+ buf[0] == '-' || buf[0] == '|' ?
+ (sscanf( buf + 2, "%d %d\n", &t1, &t2 ) != 2) :
+ (sscanf( buf + 2, "%d %d %d\n", &t1, &t2, &t3 ) != 3))
+ {
+ fprintf( stderr, "Error: malformed journal entry at %s:%d\n", jname, line );
+ fclose( jfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ if (buf[0] == '(')
+ mmaxuid = t1;
+ else if (buf[0] == ')')
+ smaxuid = t1;
+ else if (buf[0] == '|') {
+ muidval = t1;
+ suidval = t2;
+ } else if (buf[0] == '+') {
+ srec = nfmalloc( sizeof(*srec) );
+ srec->muid = t1;
+ srec->suid = t2;
+ srec->flags = t3;
+ debug( " new entry(%d,%d,%u)\n", t1, t2, t3 );
+ srec->status = 0;
+ srec->next = 0;
+ *srecadd = srec;
+ srecadd = &srec->next;
+ } else {
+ for (; srec; srec = srec->next)
+ if (srec->muid == t1 && srec->suid == t2)
+ goto syncfnd;
+ fprintf( stderr, "Error: journal entry at %s:%d refers to non-existing sync state entry\n", jname, line );
+ fclose( jfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ syncfnd:
+ debug( " entry(%d,%d,%u) ", srec->muid, srec->suid, srec->flags );
+ switch (buf[0]) {
+ case '-':
+ debug( "killed\n" );
+ srec->status = S_DEAD;
+ break;
+ case '<':
+ debug( "master now %d\n", t3 );
+ srec->muid = t3;
+ break;
+ case '>':
+ debug( "slave now %d\n", t3 );
+ srec->suid = t3;
+ break;
+ case '*':
+ debug( "flags now %d\n", t3 );
+ srec->flags = t3;
+ break;
+ case '~':
+ debug( "expired now %d\n", t3 );
+ if (t3) {
+ if (smaxxuid < t2)
+ smaxxuid = t2;
+ srec->status |= S_EXPIRED;
+ } else
+ srec->status &= ~S_EXPIRED;
+ break;
+ default:
+ fprintf( stderr, "Error: unrecognized journal entry at %s:%d\n", jname, line );
+ fclose( jfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ }
+ }
+ }
+ }
+ fclose( jfp );
+ } else {
+ if (errno != ENOENT) {
+ fprintf( stderr, "Error: cannot read journal %s\n", jname );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ }
+ skiprd:
+
+ if (sctx->opts & OPEN_NEW)
+ maxwuid = INT_MAX;
+ else if (sctx->opts & OPEN_OLD) {
+ maxwuid = 0;
+ for (srec = recs; srec; srec = srec->next)
+ if (!(srec->status & S_DEAD) && srec->suid > maxwuid)
+ maxwuid = srec->suid;
+ } else
+ maxwuid = 0;
+ info( "Selecting slave... " );
+ debug( "selecting slave [1,%d]\n", maxwuid );
+ switch (sdriver->select( sctx, (sctx->opts & OPEN_OLD) ? 1 : smaxuid + 1, maxwuid, 0, 0 )) {
+ case DRV_STORE_BAD: ret = SYNC_SLAVE_BAD; goto bail;
+ case DRV_BOX_BAD: ret = SYNC_FAIL; goto bail;
+ }
+ info( "%d messages, %d recent\n", sctx->count, sctx->recent );
+ dump_box( sctx );
+
+ if (suidval && suidval != sctx->uidvalidity) {
+ fprintf( stderr, "Error: UIDVALIDITY of slave changed\n" );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+
+ s = strrchr( dname, '/' );
+ *s = 0;
+ mkdir( dname, 0700 );
+ *s = '/';
+ if (lfd < 0) {
+ if ((lfd = open( lname, O_WRONLY|O_CREAT, 0666 )) < 0)
+ goto lferr;
+ if (fcntl( lfd, F_SETLK, &lck ))
+ goto lckerr;
}
- tmp->processed = 1;
-
- /* if the message is deleted, and CopyDeletedTo is set, and we
- * are expunging, make a copy of the message now.
- */
- if (((cur->flags | tmp->flags) & D_DELETED) != 0 &&
- (flags & SYNC_EXPUNGE) && imap->box->copy_deleted_to)
- {
- if (imap_copy_message (imap, cur->uid,
- imap->box->copy_deleted_to))
- {
- fprintf (stderr,
- "ERROR: unable to copy deleted message to \"%s\"\n",
- imap->box->copy_deleted_to);
- return -1;
- }
+ if (!(nfp = fopen( nname, "w" ))) {
+ fprintf( stderr, "Error: cannot write new sync state %s\n", nname );
+ ret = SYNC_FAIL;
+ goto bail;
}
+ if (!(jfp = fopen( jname, "a" ))) {
+ fprintf( stderr, "Error: cannot write journal %s\n", jname );
+ fclose( nfp );
+ ret = SYNC_FAIL;
+ goto bail;
+ }
+ setlinebuf( jfp );
+
+ mexcs = 0;
+ nmexcs = rmexcs = 0;
+ minwuid = INT_MAX;
+ if (smaxxuid) {
+ debug( "preparing master selection - max expired slave uid is %d\n", smaxxuid );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ if (srec->status & S_EXPIRED) {
+ if (!srec->suid || ((sctx->opts & OPEN_OLD) && !findmsg( sctx, srec->suid, &nsmsg, "slave" )))
+ srec->status |= S_EXP_SLAVE;
+ else if (minwuid > srec->muid)
+ minwuid = srec->muid;
+ } else if (smaxxuid < srec->suid && minwuid > srec->muid)
+ minwuid = srec->muid;
+ }
+ debug( " min non-orphaned master uid is %d\n", minwuid );
+ fprintf( jfp, "^\n" ); /* if any S_EXP_SLAVE */
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ if (srec->status & S_EXP_SLAVE) {
+ if (minwuid > srec->muid && mmaxuid >= srec->muid) {
+ debug( " -> killing (%d,%d)\n", srec->muid, srec->suid );
+ srec->status = S_DEAD;
+ fprintf( jfp, "- %d %d\n", srec->muid, srec->suid );
+ } else if (srec->suid) {
+ debug( " -> orphaning (%d,[%d])\n", srec->muid, srec->suid );
+ fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid );
+ srec->suid = 0;
+ }
+ } else if (minwuid > srec->muid) {
+ if (srec->suid < 0) {
+ if (mmaxuid >= srec->muid) {
+ debug( " -> killing (%d,%d)\n", srec->muid, srec->suid );
+ srec->status = S_DEAD;
+ fprintf( jfp, "- %d %d\n", srec->muid, srec->suid );
+ }
+ } else if (srec->muid > 0 && srec->suid && (mctx->opts & OPEN_OLD) &&
+ (!(mctx->opts & OPEN_NEW) || mmaxuid >= srec->muid)) {
+ if (nmexcs == rmexcs) {
+ rmexcs = rmexcs * 2 + 100;
+ mexcs = nfrealloc( mexcs, rmexcs * sizeof(int) );
+ }
+ mexcs[nmexcs++] = srec->muid;
+ }
+ }
+ }
+ debug( " exception list is:" );
+ for (i = 0; i < nmexcs; i++)
+ debug( " %d", mexcs[i] );
+ debug( "\n" );
+ } else if (mctx->opts & OPEN_OLD)
+ minwuid = 1;
+ if (mctx->opts & OPEN_NEW) {
+ if (minwuid > mmaxuid + 1)
+ minwuid = mmaxuid + 1;
+ maxwuid = INT_MAX;
+ } else if (mctx->opts & OPEN_OLD) {
+ maxwuid = 0;
+ for (srec = recs; srec; srec = srec->next)
+ if (!(srec->status & S_DEAD) && srec->muid > maxwuid)
+ maxwuid = srec->muid;
+ } else
+ maxwuid = 0;
+ info( "Selecting master... " );
+ debug( "selecting master [%d,%d]\n", minwuid, maxwuid );
+ switch (mdriver->select( mctx, minwuid, maxwuid, mexcs, nmexcs )) {
+ case DRV_STORE_BAD: ret = SYNC_MASTER_BAD; goto finish;
+ case DRV_BOX_BAD: ret = SYNC_FAIL; goto finish;
+ }
+ info( "%d messages, %d recent\n", mctx->count, mctx->recent );
+ dump_box( mctx );
- /* 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));
-
- /* don't bother renaming the file if we are just going to
- * remove it later.
- */
- if ((cur->flags & D_DELETED) == 0 || (flags & SYNC_EXPUNGE) == 0)
- {
- size_t sl;
-
- /* generate old path */
- snprintf (path, sizeof (path), "%s/%s/%s",
- mbox->path, cur->new ? "new" : "cur", cur->file);
-
- /* generate new path */
- strcpy (newpath, path);
- p = strchr (newpath, ':');
- sl = p ? (size_t)(p - newpath) : strlen (newpath);
- snprintf (newpath + sl, sizeof (newpath) - sl, ":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" : "");
-
- if (rename (path, newpath))
- {
- perror ("Warning: cannot set flags on message");
- }
- else
- {
- /* update the filename in the msg struct */
- p = strrchr (newpath, '/');
- free (cur->file);
- cur->file = strdup (p + 1);
- }
- }
+ if (muidval && muidval != mctx->uidvalidity) {
+ fprintf( stderr, "Error: UIDVALIDITY of master changed\n" );
+ ret = SYNC_FAIL;
+ goto finish;
}
- }
-
- if (upload)
- info (" %d messages.\n", upload);
-
- if (max_msgs == 0)
- max_msgs = UINT_MAX;
- else
- {
- /* expire messages in excess of the max-count for this mailbox.
- * flagged mails are considered sacrosant and not deleted.
- * we have already done the upload to the server, so messing with
- * the flags variable do not have remote side effects.
- */
- for (cur = imap->msgs, msg_count = 0;
- cur && msg_count < max_msgs; cur = cur->next, msg_count++)
- {
- tmp = find_msg (mbox->msgs, cur->uid);
- if (tmp)
- tmp->wanted = 1;
+
+ if (!muidval || !suidval) {
+ muidval = mctx->uidvalidity;
+ suidval = sctx->uidvalidity;
+ fprintf( jfp, "| %d %d\n", muidval, suidval );
}
- for (cur = mbox->msgs; cur; cur = cur->next)
- {
- if (!cur->wanted && !(cur->flags & D_FLAGGED))
- {
- cur->flags |= D_DELETED;
- cur->dead = 1;
- mbox->deleted++;
- }
+
+ info( "Synchronizing\n" );
+ debug( "synchronizing old entries\n" );
+ fprintf( jfp, "^\n" );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ debug( "pair (%d,%d)\n", srec->muid, srec->suid );
+ mmsg = findmsg( mctx, srec->muid, &nmmsg, "master" );
+ smsg = (srec->status & S_EXP_SLAVE) ? 0 : findmsg( sctx, srec->suid, &nsmsg, "slave" );
+ nom = !mmsg && (mctx->opts & OPEN_OLD);
+ nos = !smsg && (sctx->opts & OPEN_OLD);
+ if (nom && nos) {
+ debug( " vanished\n" );
+ /* d.1) d.5) d.6) d.10) d.11) */
+ srec->status = S_DEAD;
+ fprintf( jfp, "- %d %d\n", srec->muid, srec->suid );
+ } else {
+ delm = nom && (srec->muid > 0);
+ dels = nos && (srec->suid > 0);
+ nflags = srec->flags;
+
+ if ((ret = sync_old( chan->mops, sctx, mctx, chan->master, jfp, 0, &nflags, srec, smsg, mmsg, dels, delm )) != SYNC_OK ||
+ (ret = sync_old( chan->sops, mctx, sctx, chan->slave, jfp, 1, &nflags, srec, mmsg, smsg, delm, dels )) != SYNC_OK)
+ goto finish;
+
+ if (srec->flags != nflags) {
+ debug( " updating flags (%u -> %u)\n", srec->flags, nflags );
+ srec->flags = nflags;
+ fprintf( jfp, "* %d %d %u\n", srec->muid, srec->suid, nflags );
+ }
+ if (mmsg && (mmsg->flags & F_DELETED))
+ srec->status |= S_DEL_MASTER;
+ if (smsg && (smsg->flags & F_DELETED))
+ srec->status |= S_DEL_SLAVE;
+ }
}
- }
-
- for (cur = imap->msgs, msg_count = 0;
- cur && msg_count < max_msgs; cur = cur->next, msg_count++)
- {
- 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)
- {
- info ("Remote message %u skipped because it is too big (%u)\n",
- cur->uid, cur->size);
- continue;
- }
-
- /* construct the flags part of the file name. */
-
- *suffix = 0;
- if (cur->flags & ~(D_RECENT | D_DRAFT))
- {
- 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/%ld_%d.%d.%s%s",
- mbox->path, time (0), MaildirCount++, getpid (),
- Hostname, suffix);
-
- if ((fd = open (path, O_WRONLY | O_CREAT | O_EXCL, 0600)) > 0)
- break;
- if (errno != EEXIST)
- {
- perror (path);
- break;
+
+ debug( "synchronizing new entries\n" );
+ if ((ret = sync_new( chan->mops, sctx, mctx, chan->master, jfp, &srecadd, 0, &smaxuid )) != SYNC_OK ||
+ (ret = sync_new( chan->sops, mctx, sctx, chan->slave, jfp, &srecadd, 1, &mmaxuid )) != SYNC_OK)
+ goto finish;
+
+ if ((chan->sops & (OP_NEW|OP_RENEW)) && chan->max_messages) {
+ debug( "expiring excessive entries\n" );
+ todel = sctx->count - chan->max_messages;
+ for (smsg = sctx->msgs; smsg && todel > 0; smsg = smsg->next)
+ if (!(smsg->status & M_DEAD) && (smsg->flags & F_DELETED))
+ todel--;
+ delt = 0;
+ for (smsg = sctx->msgs; smsg && todel > 0; smsg = smsg->next) {
+ if ((smsg->status & M_DEAD) || (smsg->flags & F_DELETED))
+ continue;
+ if ((smsg->flags & F_FLAGGED) || (smsg->status & M_NOT_SYNCED)) /* add M_DESYNCED? */
+ todel--;
+ else if (!(smsg->status & M_RECENT)) {
+ smsg->status |= M_EXPIRED;
+ delt++;
+ todel--;
+ }
+ }
+ if (delt) {
+ fprintf( jfp, "^\n" );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & (S_DEAD|S_EXPIRED))
+ continue;
+ smsg = findmsg( sctx, srec->suid, &nsmsg, "slave" );
+ if (smsg && (smsg->status & M_EXPIRED)) {
+ debug( " expiring pair(%d,%d)\n", srec->muid, srec->suid );
+ /* log first, so deletion can't be misinterpreted! */
+ fprintf( jfp, "~ %d %d 1\n", srec->muid, srec->suid );
+ if (smaxxuid < srec->suid)
+ smaxxuid = srec->suid;
+ srec->status |= S_EXPIRED;
+ switch (sdriver->set_flags( sctx, smsg, 0, F_DELETED, 0 )) {
+ case DRV_STORE_BAD: ret = SYNC_SLAVE_BAD; goto finish;
+ case DRV_BOX_BAD: ret = SYNC_FAIL; goto finish;
+ default: /* ok */ break;
+ case DRV_OK: srec->status |= S_DEL_SLAVE;
+ }
+ }
+ }
}
+ }
- sleep (2);
- }
-
- if (fd < 0)
- continue;
-
- /* give some visual feedback that something is happening */
- if (!fetched)
- info ("Fetching new messages");
- infoc ('.');
- fflush (stdout);
- fetched++;
-
- ret = imap_fetch_message (imap, cur->uid, fd);
-
- if (fsync (fd))
- {
- perror ("fsync");
- close (fd);
- }
- else 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); /* hack: ignore \recent, use !\seen instead */
-
- /* its ok if this fails, the next time we sync the message
- * will get pulled down
- */
- if (link (path, newpath))
- perror ("link");
- else
- {
- /* update the db with the UID mapping for this file */
- set_uid (mbox->db, p + 1, cur->uid);
- if (cur->uid > mbox->maxuid)
- mbox->maxuid = cur->uid;
+ /* Doing CLOSE here instead of EXPUNGE above saves network traffic.
+ But it costs more server power for single-file formats. And it
+ makes disk-full/quota-exceeded more probable. */
+ mex = sex = 0;
+ if (chan->mops & OP_EXPUNGE) {
+ info( "Expunging master\n" );
+ debug( "expunging master\n" );
+ switch (expunge( mctx, sctx )) {
+ case EX_STORE_BAD: ret = SYNC_MASTER_BAD; goto finish;
+ case EX_RSTORE_BAD: ret = SYNC_SLAVE_BAD; goto finish;
+ default: ret = SYNC_FAIL; break;
+ case EX_OK: mex = 1;
+ }
+ }
+ if (chan->sops & OP_EXPUNGE) {
+ info( "Expunging slave\n" );
+ debug( "expunging slave\n" );
+ switch (expunge( sctx, mctx )) {
+ case EX_STORE_BAD: ret = SYNC_SLAVE_BAD; goto finish;
+ case EX_RSTORE_BAD: ret = SYNC_MASTER_BAD; goto finish;
+ default: ret = SYNC_FAIL; break;
+ case EX_OK: mex = 1;
+ }
+ }
+ if (mex || sex) {
+ /* This cleanup is not strictly necessary, as the next full sync
+ would throw out the dead entries anyway. But ... */
+
+ minwuid = INT_MAX;
+ if (smaxxuid) {
+ debug( "preparing entry purge - max expired slave uid is %d\n", smaxxuid );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ if (!((srec->suid <= 0 || ((srec->status & S_DEL_SLAVE) && sex)) &&
+ (srec->muid <= 0 || ((srec->status & S_DEL_MASTER) && mex) || (srec->status & S_EXPIRED))) &&
+ smaxxuid < srec->suid && minwuid > srec->muid)
+ minwuid = srec->muid;
+ }
+ debug( " min non-orphaned master uid is %d\n", minwuid );
}
- }
- /* always remove the temp file */
- unlink (path);
+ fprintf( jfp, "^\n" );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ if (srec->suid <= 0 || ((srec->status & S_DEL_SLAVE) && sex)) {
+ if (srec->muid <= 0 || ((srec->status & S_DEL_MASTER) && mex)) {
+ debug( " -> killing (%d,%d)\n", srec->muid, srec->suid );
+ srec->status = S_DEAD;
+ fprintf( jfp, "- %d %d\n", srec->muid, srec->suid );
+ } else if (srec->status & S_EXPIRED) {
+ if (mmaxuid >= srec->muid && minwuid > srec->muid) {
+ debug( " -> killing (%d,%d)\n", srec->muid, srec->suid );
+ srec->status = S_DEAD;
+ fprintf( jfp, "- %d %d\n", srec->muid, srec->suid );
+ } else if (srec->suid) {
+ debug( " -> orphaning (%d,[%d])\n", srec->muid, srec->suid );
+ fprintf( jfp, "> %d %d 0\n", srec->muid, srec->suid );
+ srec->suid = 0;
+ }
+ }
+ }
+ }
}
- }
- if (fetched)
- info (" %d messages\n", fetched);
+ finish:
+ fprintf( nfp, "%d:%d %d:%d:%d\n", muidval, mmaxuid, suidval, smaxxuid, smaxuid );
+ for (srec = recs; srec; srec = srec->next) {
+ if (srec->status & S_DEAD)
+ continue;
+ make_flags( srec->flags, fbuf );
+ fprintf( nfp, "%d %d %s%s\n", srec->muid, srec->suid,
+ srec->status & S_EXPIRED ? "X" : "", fbuf );
+ }
- if (maildir_update_maxuid (mbox))
- return -1;
+ fclose( nfp );
+ fclose( jfp );
+ /* order is important! */
+ rename( nname, dname );
+ unlink( jname );
- return 0;
+ bail:
+ for (srec = recs; srec; srec = nsrec) {
+ nsrec = srec->next;
+ free( srec );
+ }
+ unlink( lname );
+ bail1:
+ close( lfd );
+ bail2:
+ free( lname );
+ free( nname );
+ free( jname );
+ free( dname );
+ return ret;
}
+
diff --git a/src/util.c b/src/util.c
@@ -0,0 +1,389 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <pwd.h>
+#include <ctype.h>
+
+int Verbose, Quiet, Debug;
+
+void
+debug( const char *msg, ... )
+{
+ va_list va;
+
+ if (Debug) {
+ va_start( va, msg );
+ vprintf( msg, va );
+ va_end( va );
+ }
+}
+
+void
+info( const char *msg, ... )
+{
+ va_list va;
+
+ if (!Quiet) {
+ va_start( va, msg );
+ vprintf( msg, va );
+ va_end( va );
+ fflush( stdout );
+ }
+}
+
+void
+infoc( char c )
+{
+ if (!Quiet) {
+ putchar( c );
+ fflush( stdout );
+ }
+}
+
+void
+warn( const char *msg, ... )
+{
+ va_list va;
+
+ if (Quiet < 2) {
+ va_start( va, msg );
+ vfprintf( stderr, msg, va );
+ va_end( va );
+ }
+}
+
+char *
+next_arg( char **s )
+{
+ char *ret;
+
+ if (!s || !*s)
+ return 0;
+ while (isspace( (unsigned char) **s ))
+ (*s)++;
+ if (!**s) {
+ *s = 0;
+ return 0;
+ }
+ if (**s == '"') {
+ ++*s;
+ ret = *s;
+ *s = strchr( *s, '"' );
+ } else {
+ ret = *s;
+ while (**s && !isspace( (unsigned char) **s ))
+ (*s)++;
+ }
+ if (*s) {
+ if (**s)
+ *(*s)++ = 0;
+ if (!**s)
+ *s = 0;
+ }
+ return ret;
+}
+
+void
+add_string_list( string_list_t **list, const char *str )
+{
+ string_list_t *elem;
+ int len;
+
+ len = strlen( str );
+ elem = nfmalloc( sizeof(*elem) + len );
+ elem->next = *list;
+ *list = elem;
+ memcpy( elem->string, str, len + 1 );
+}
+
+void
+free_string_list( string_list_t *list )
+{
+ string_list_t *tlist;
+
+ for (; list; list = tlist) {
+ tlist = list->next;
+ free( list );
+ }
+}
+
+void
+free_generic_messages( message_t *msgs )
+{
+ message_t *tmsg;
+
+ for (; msgs; msgs = tmsg) {
+ tmsg = msgs->next;
+ free( msgs );
+ }
+}
+
+void
+strip_cr( msg_data_t *msgdata )
+{
+ int i, o;
+
+ if (msgdata->crlf) {
+ for (i = o = 0; i < msgdata->len; i++)
+ if (msgdata->data[i] != '\r')
+ msgdata->data[o++] = msgdata->data[i];
+ msgdata->len = o;
+ msgdata->crlf = 0;
+ }
+}
+
+#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 = 0;
+ else if ((*strp = malloc( len + 1 ))) {
+ if (len >= sizeof(tmp))
+ vsprintf( *strp, fmt, ap );
+ else
+ memcpy( *strp, tmp, len + 1 );
+ }
+ return len;
+}
+#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 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
+ oob();
+ va_end( va );
+ return ret;
+}
+
+static void ATTR_NORETURN
+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 *
+nfstrdup( const char *str )
+{
+ char *ret;
+
+ if (!(ret = strdup( str )))
+ oom();
+ return ret;
+}
+
+int
+nfvasprintf( char **str, const char *fmt, va_list va )
+{
+ int ret = vasprintf( str, fmt, va );
+ if (!*str)
+ 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;
+}
+*/
+
+static char *
+my_strndup( const char *s, size_t nchars )
+{
+ char *r = nfmalloc( nchars + 1 );
+ memcpy( r, s, nchars );
+ r[nchars] = 0;
+ return r;
+}
+
+char *
+expand_strdup( const char *s )
+{
+ struct passwd *pw;
+ const char *p, *q;
+ char *r;
+
+ if (*s == '~') {
+ s++;
+ if (!*s) {
+ p = 0;
+ q = Home;
+ } else if (*s == '/') {
+ p = s;
+ q = Home;
+ } else {
+ if ((p = strchr( s, '/' ))) {
+ r = my_strndup( s, (int)(p - s) );
+ pw = getpwnam( r );
+ free( r );
+ } else
+ pw = getpwnam( s );
+ if (!pw)
+ return 0;
+ q = pw->pw_dir;
+ }
+ nfasprintf( &r, "%s%s", q, p ? p : "" );
+ return r;
+ } else
+ return nfstrdup( s );
+}
+
+static int
+compare_ints( const void *l, const void *r )
+{
+ return *(int *)l - *(int *)r;
+}
+
+void
+sort_ints( int *arr, int len )
+{
+ qsort( arr, len, sizeof(int), compare_ints );
+}
+
+
+static struct {
+ unsigned char i, j, s[256];
+} rs;
+
+void
+arc4_init( void )
+{
+ int i, fd;
+ unsigned char j, si, dat[128];
+
+ if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
+ fprintf( stderr, "Fatal: no random number source available.\n" );
+ exit( 3 );
+ }
+ if (read( fd, dat, 128 ) != 128) {
+ fprintf( stderr, "Fatal: cannot read random number source.\n" );
+ exit( 3 );
+ }
+ close( fd );
+
+ for (i = 0; i < 256; i++)
+ rs.s[i] = 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();
+}
+
+unsigned char
+arc4_getbyte( void )
+{
+ unsigned char 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];
+}