[Xfce4-commits] <postler:master> Import isync revision 58e35bc74b2a41a641a03b8d9f6d

Christian Dywan noreply at xfce.org
Thu Nov 4 04:20:03 CET 2010


Updating branch refs/heads/master
         to fb75329a6d45da8aeab8e7545a84b8dbfd818c70 (commit)
       from 5dacb2ae152ae4acbc91f6e432f79ff4b8b1555a (commit)

commit fb75329a6d45da8aeab8e7545a84b8dbfd818c70
Author: Christian Dywan <christian at twotoasts.de>
Date:   Thu Nov 4 04:00:33 2010 +0100

    Import isync revision 58e35bc74b2a41a641a03b8d9f6d
    
    From git://isync.git.sourceforge.net/gitroot/isync/isync
    
    POSTLER_CHANGES is defined to surround modifications to
    upstream sources in the future.
    
    The environment variable POSTLER_MBSYNC can be set to
    a path of 'mbsync', otherwise 'postler-mbsync' is used.

 isync/config.c                   |  438 +++++++++
 isync/drv_imap.c                 | 1918 ++++++++++++++++++++++++++++++++++++++
 isync/drv_maildir.c              | 1272 +++++++++++++++++++++++++
 isync/isync.h                    |  300 ++++++
 isync/main.c                     |  746 +++++++++++++++
 isync/sync.c                     | 1699 +++++++++++++++++++++++++++++++++
 isync/util.c                     |  413 ++++++++
 {postler => isync}/wscript_build |    9 +-
 postler/postler-accounts.vala    |   11 +-
 wscript                          |   25 +-
 10 files changed, 6818 insertions(+), 13 deletions(-)

diff --git a/isync/config.c b/isync/config.c
new file mode 100644
index 0000000..ef7641a
--- /dev/null
+++ b/isync/config.c
@@ -0,0 +1,438 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi at 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#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>
+
+driver_t *drivers[N_DRIVERS] = { &maildir_driver, &imap_driver };
+
+store_conf_t *stores;
+channel_conf_t *channels;
+group_conf_t *groups;
+int global_ops[2];
+char *global_sync_state;
+
+int
+parse_bool( conffile_t *cfile )
+{
+	if (!strcasecmp( cfile->val, "yes" ) ||
+	    !strcasecmp( cfile->val, "true" ) ||
+	    !strcasecmp( cfile->val, "on" ) ||
+	    !strcmp( cfile->val, "1" ))
+		return 1;
+	if (strcasecmp( cfile->val, "no" ) &&
+	    strcasecmp( cfile->val, "false" ) &&
+	    strcasecmp( cfile->val, "off" ) &&
+	    strcmp( cfile->val, "0" ))
+		error( "%s:%d: invalid boolean value '%s'\n",
+		       cfile->file, cfile->line, cfile->val );
+	return 0;
+}
+
+int
+parse_int( conffile_t *cfile )
+{
+	char *p;
+	int ret;
+
+	ret = strtol( cfile->val, &p, 10 );
+	if (*p) {
+		error( "%s:%d: invalid integer value '%s'\n",
+		       cfile->file, cfile->line, cfile->val );
+		return 0;
+	}
+	return ret;
+}
+
+int
+parse_size( conffile_t *cfile )
+{
+	char *p;
+	int ret;
+
+	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++;
+	if (*p) {
+		fprintf (stderr, "%s:%d: invalid size '%s'\n",
+		         cfile->file, cfile->line, cfile->val);
+		return 0;
+	}
+	return ret;
+}
+
+static int
+getopt_helper( conffile_t *cfile, int *cops, int ops[], char **sync_state )
+{
+	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 ))
+				ops[S] |= OP_RENEW;
+			else if (!strcasecmp( "PullNew", arg ))
+				ops[S] |= OP_NEW;
+			else if (!strcasecmp( "PullDelete", arg ))
+				ops[S] |= OP_DELETE;
+			else if (!strcasecmp( "PullFlags", arg ))
+				ops[S] |= OP_FLAGS;
+			else if (!strcasecmp( "PushReNew", arg ))
+				ops[M] |= OP_RENEW;
+			else if (!strcasecmp( "PushNew", arg ))
+				ops[M] |= OP_NEW;
+			else if (!strcasecmp( "PushDelete", arg ))
+				ops[M] |= OP_DELETE;
+			else if (!strcasecmp( "PushFlags", arg ))
+				ops[M] |= OP_FLAGS;
+			else if (!strcasecmp( "All", arg ) || !strcasecmp( "Full", arg ))
+				*cops |= XOP_PULL|XOP_PUSH;
+			else if (strcasecmp( "None", arg ) && strcasecmp( "Noop", arg ))
+				error( "%s:%d: invalid Sync arg '%s'\n",
+				       cfile->file, cfile->line, arg );
+		while ((arg = next_arg( &cfile->rest )));
+		ops[M] |= 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 ))
+				ops[M] |= OP_EXPUNGE;
+			else if (!strcasecmp( "Slave", arg ))
+				ops[S] |= OP_EXPUNGE;
+			else if (strcasecmp( "None", arg ))
+				error( "%s:%d: invalid Expunge arg '%s'\n",
+				       cfile->file, cfile->line, arg );
+		while ((arg = next_arg( &cfile->rest )));
+		ops[M] |= 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 ))
+				ops[M] |= OP_CREATE;
+			else if (!strcasecmp( "Slave", arg ))
+				ops[S] |= OP_CREATE;
+			else if (strcasecmp( "None", arg ))
+				error( "%s:%d: invalid Create arg '%s'\n",
+				       cfile->file, cfile->line, arg );
+		while ((arg = next_arg( &cfile->rest )));
+		ops[M] |= XOP_HAVE_CREATE;
+	} else if (!strcasecmp( "SyncState", cfile->cmd ))
+		*sync_state = expand_strdup( cfile->val );
+	else
+		return 0;
+	return 1;
+}
+
+int
+getcline( conffile_t *cfile )
+{
+	char *p;
+
+	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 ))) {
+			error( "%s:%d: parameter missing\n", cfile->file, cfile->line );
+			continue;
+		}
+		cfile->rest = p;
+		return 1;
+	}
+	return 0;
+}
+
+/* XXX - this does not detect None conflicts ... */
+int
+merge_ops( int cops, int ops[] )
+{
+	int aops;
+
+	aops = ops[M] | ops[S];
+	if (ops[M] & XOP_HAVE_TYPE) {
+		if (aops & OP_MASK_TYPE) {
+			if (aops & cops & OP_MASK_TYPE) {
+			  cfl:
+				error( "Conflicting Sync args specified.\n" );
+				return 1;
+			}
+			ops[M] |= cops & OP_MASK_TYPE;
+			ops[S] |= cops & OP_MASK_TYPE;
+			if (cops & XOP_PULL) {
+				if (ops[S] & OP_MASK_TYPE)
+					goto cfl;
+				ops[S] |= OP_MASK_TYPE;
+			}
+			if (cops & XOP_PUSH) {
+				if (ops[M] & OP_MASK_TYPE)
+					goto cfl;
+				ops[M] |= OP_MASK_TYPE;
+			}
+		} else if (cops & (OP_MASK_TYPE|XOP_MASK_DIR)) {
+			if (!(cops & OP_MASK_TYPE))
+				cops |= OP_MASK_TYPE;
+			else if (!(cops & XOP_MASK_DIR))
+				cops |= XOP_PULL|XOP_PUSH;
+			if (cops & XOP_PULL)
+				ops[S] |= cops & OP_MASK_TYPE;
+			if (cops & XOP_PUSH)
+				ops[M] |= cops & OP_MASK_TYPE;
+		}
+	}
+	if (ops[M] & XOP_HAVE_EXPUNGE) {
+		if (aops & cops & OP_EXPUNGE) {
+			error( "Conflicting Expunge args specified.\n" );
+			return 1;
+		}
+		ops[M] |= cops & OP_EXPUNGE;
+		ops[S] |= cops & OP_EXPUNGE;
+	}
+	if (ops[M] & XOP_HAVE_CREATE) {
+		if (aops & cops & OP_CREATE) {
+			error( "Conflicting Create args specified.\n" );
+			return 1;
+		}
+		ops[M] |= cops & OP_CREATE;
+		ops[S] |= cops & OP_CREATE;
+	}
+	return 0;
+}
+
+int
+load_config( const char *where, int pseudo )
+{
+	conffile_t cfile;
+	store_conf_t *store, **storeapp = &stores;
+	channel_conf_t *channel, **channelapp = &channels;
+	group_conf_t *group, **groupapp = &groups;
+	string_list_t *chanlist, **chanlistapp;
+	char *arg, *p;
+	int err, len, cops, gcops, max_size, ms, i;
+	char path[_POSIX_PATH_MAX];
+	char buf[1024];
+
+	if (!where) {
+		nfsnprintf( path, sizeof(path), "%s/." EXE "rc", Home );
+		cfile.file = path;
+	} else
+		cfile.file = where;
+
+	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;
+		for (i = 0; i < N_DRIVERS; i++)
+			if (drivers[i]->parse_store( &cfile, &store, &err )) {
+				if (store) {
+					if (!store->path)
+						store->path = "";
+					*storeapp = store;
+					storeapp = &store->next;
+					*storeapp = 0;
+				}
+				goto reloop;
+			}
+		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 )) {
+					ms = M;
+					goto linkst;
+				} else if (!strcasecmp( "Slave", cfile.cmd )) {
+					ms = S;
+				  linkst:
+					if (*cfile.val != ':' || !(p = strchr( cfile.val + 1, ':' ))) {
+						error( "%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 )) {
+							channel->stores[ms] = store;
+							goto stpcom;
+						}
+					error( "%s:%d: unknown store '%s'\n",
+					       cfile.file, cfile.line, cfile.val + 1 );
+					err = 1;
+					continue;
+				  stpcom:
+					if (*++p)
+						channel->boxes[ms] = nfstrdup( p );
+				} else if (!getopt_helper( &cfile, &cops, channel->ops, &channel->sync_state )) {
+					error( "%s:%d: unknown keyword '%s'\n", cfile.file, cfile.line, cfile.cmd );
+					err = 1;
+				}
+			}
+			if (!channel->stores[M]) {
+				error( "channel '%s' refers to no master store\n", channel->name );
+				err = 1;
+			} else if (!channel->stores[S]) {
+				error( "channel '%s' refers to no slave store\n", channel->name );
+				err = 1;
+			} else if (merge_ops( cops, channel->ops ))
+				err = 1;
+			else {
+				if (max_size >= 0)
+					channel->stores[M]->max_size = channel->stores[S]->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
+				{
+					error( "%s:%d: unknown keyword '%s'\n",
+					       cfile.file, cfile.line, cfile.cmd );
+					err = 1;
+				}
+			}
+			break;
+		}
+		else if (!getopt_helper( &cfile, &gcops, global_ops, &global_sync_state ))
+		{
+			error( "%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_ops );
+	if (!global_sync_state)
+		global_sync_state = expand_strdup( "~/." EXE "/" );
+	if (!err && pseudo)
+		unlink( where );
+	return err;
+}
+
+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 {
+		error( "%s:%d: unknown keyword '%s'\n", cfg->file, cfg->line, cfg->cmd );
+		*err = 1;
+	}
+}
diff --git a/isync/drv_imap.c b/isync/drv_imap.c
new file mode 100644
index 0000000..cb45970
--- /dev/null
+++ b/isync/drv_imap.c
@@ -0,0 +1,1918 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006,2008 Oswald Buddenhagen <ossi at users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso at mit.edu>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+/* This must come before isync.h to avoid our #define S messing up
+ * blowfish.h on MacOS X. */
+#include <config.h>
+#if HAVE_LIBSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/hmac.h>
+#endif
+
+#include "isync.h"
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <limits.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+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;
+	X509_STORE *cert_store;
+#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_store {
+	store_t gen;
+	const char *prefix;
+	unsigned /*currentnc:1,*/ trashnc:1;
+	int uidnext; /* from SELECT responses */
+	unsigned got_namespace:1;
+	list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+	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_store_t;
+
+struct imap_cmd {
+	struct imap_cmd *next;
+	char *cmd;
+	int tag;
+
+	struct {
+		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 *aux;
+		char *data;
+		int data_len;
+		int uid; /* to identify fetch responses */
+		unsigned
+			create:1, /* create the mailbox if we get an error ... */
+			trycreate:1; /* ... but only if this is true or the server says so. */
+	} param;
+};
+
+#define CAP(cap) (ctx->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
+/* Some of this code is inspired by / lifted from mutt. */
+
+static int
+compare_certificates( X509 *cert, X509 *peercert,
+                      unsigned char *peermd, unsigned peermdlen )
+{
+	unsigned char md[EVP_MAX_MD_SIZE];
+	unsigned mdlen;
+
+	/* Avoid CPU-intensive digest calculation if the certificates are
+	 * not even remotely equal. */
+	if (X509_subject_name_cmp( cert, peercert ) ||
+	    X509_issuer_name_cmp( cert, peercert ))
+		return -1;
+
+	if (!X509_digest( cert, EVP_sha1(), md, &mdlen ) ||
+	    peermdlen != mdlen || memcmp( peermd, md, mdlen ))
+		return -1;
+
+	return 0;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x00904000L
+#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 )
+#else
+#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0 )
+#endif
+
+/* this gets called when a certificate is to be verified */
+static int
+verify_cert( imap_store_t *ctx )
+{
+	imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
+	SSL *ssl = ctx->buf.sock.ssl;
+	X509 *cert, *lcert;
+	BIO *bio;
+	FILE *fp;
+	int err;
+	unsigned n, i;
+	X509_STORE_CTX xsc;
+	char buf[256];
+	unsigned char md[EVP_MAX_MD_SIZE];
+
+	cert = SSL_get_peer_certificate( ssl );
+	if (!cert) {
+		error( "Error, no server certificate\n" );
+		return -1;
+	}
+
+	while (srvc->cert_file) { // So break works
+		if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) {
+			error( "Server certificate is not yet valid" );
+			break;
+		}
+		if (X509_cmp_current_time( X509_get_notAfter( cert )) <= 0) {
+			error( "Server certificate has expired" );
+			break;
+		}
+		if (!X509_digest( cert, EVP_sha1(), md, &n )) {
+			error( "*** Unable to calculate digest\n" );
+			break;
+		}
+		if (!(fp = fopen( srvc->cert_file, "rt" ))) {
+			error( "Unable to load CertificateFile '%s': %s\n",
+			       srvc->cert_file, strerror( errno ) );
+			return 0;
+		}
+		err = -1;
+		for (lcert = 0; READ_X509_KEY( fp, &lcert ); )
+			if (!(err = compare_certificates( lcert, cert, md, n )))
+				break;
+		X509_free( lcert );
+		fclose( fp );
+		if (!err)
+			return 0;
+		break;
+	}
+
+	if (!srvc->cert_store) {
+		if (!(srvc->cert_store = X509_STORE_new())) {
+			error( "Error creating certificate store\n" );
+			return -1;
+		}
+		if (!X509_STORE_set_default_paths( srvc->cert_store ))
+			warn( "Error while loading default certificate files: %s\n",
+			      ERR_error_string( ERR_get_error(), 0 ) );
+		if (!srvc->cert_file) {
+			info( "Note: CertificateFile not defined\n" );
+		} else if (!X509_STORE_load_locations( srvc->cert_store, srvc->cert_file, 0 )) {
+			error( "Error while loading certificate file '%s': %s\n",
+			       srvc->cert_file, ERR_error_string( ERR_get_error(), 0 ) );
+			return -1;
+		}
+	}
+
+	X509_STORE_CTX_init( &xsc, srvc->cert_store, cert, 0 );
+	err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc );
+	X509_STORE_CTX_cleanup( &xsc );
+	if (!err)
+		return 0;
+	error( "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 );
+	if (!X509_digest( cert, EVP_md5(), md, &n )) {
+		error( "*** Unable to calculate fingerprint\n" );
+	} else {
+		info( "Fingerprint: " );
+		for (i = 0; i < n; i += 2)
+			info( "%02X%02X ", md[i], md[i + 1] );
+		info( "\n" );
+	}
+
+	fputs( "\nAccept certificate? [y/N]: ",  stderr );
+	if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y'))
+		return 0;
+	return -1;
+}
+
+static int
+init_ssl_ctx( imap_store_t *ctx )
+{
+	imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.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();
+	ctx->SSLContext = SSL_CTX_new( method );
+
+	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( ctx->SSLContext, options );
+
+	/* we check the result of the verification after SSL_connect() */
+	SSL_CTX_set_verify( ctx->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)
+					error( "SSL_%s: got EOF\n", func );
+				else
+					error( "SSL_%s: %s\n", func, strerror(errno) );
+			} else
+				error( "SSL_%s: %s\n", func, ERR_error_string( err, 0 ) );
+			return;
+		default:
+			error( "SSL_%s: unhandled SSL error %d\n", func, err );
+			break;
+		}
+		return;
+	}
+#else
+	(void)sock;
+#endif
+	if (ret < 0)
+		perror( func );
+	else
+		error( "%s: unexpected EOF\n", func );
+}
+
+static int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+	int n;
+
+	assert( sock->fd >= 0 );
+	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;
+
+	assert( sock->fd >= 0 );
+	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)
+					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)
+				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 (DFlags & VERBOSE)
+					puts( *s );
+				return 0;
+			}
+		}
+
+		b->offset++;
+	}
+	/* not reached */
+}
+
+static struct imap_cmd *
+new_imap_cmd( void )
+{
+	struct imap_cmd *cmd = nfmalloc( sizeof(*cmd) );
+	memset( &cmd->param, 0, sizeof(cmd->param) );
+	return cmd;
+}
+
+static struct imap_cmd *
+v_submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd,
+                   const char *fmt, va_list ap )
+{
+	int n, bufl;
+	char buf[1024];
+
+	while (ctx->literal_pending)
+		get_cmd_result( ctx, 0 );
+
+	if (!cmd)
+		cmd = new_imap_cmd();
+	cmd->tag = ++ctx->nexttag;
+	nfvasprintf( &cmd->cmd, fmt, ap );
+	bufl = nfsnprintf( buf, sizeof(buf), cmd->param.data ? CAP(LITERALPLUS) ?
+	                   "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
+	                   cmd->tag, cmd->cmd, cmd->param.data_len );
+	if (DFlags & VERBOSE) {
+		if (ctx->num_in_progress)
+			printf( "(%d in progress) ", ctx->num_in_progress );
+		if (memcmp( cmd->cmd, "LOGIN", 5 ))
+			printf( ">>> %s", buf );
+		else
+			printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
+	}
+	if (socket_write( &ctx->buf.sock, buf, bufl ) != bufl) {
+		if (cmd->param.data)
+			free( cmd->param.data );
+		free( cmd->cmd );
+		free( cmd );
+		return NULL;
+	}
+	if (cmd->param.data) {
+		if (CAP(LITERALPLUS)) {
+			n = socket_write( &ctx->buf.sock, cmd->param.data, cmd->param.data_len );
+			free( cmd->param.data );
+			if (n != cmd->param.data_len ||
+			    (n = socket_write( &ctx->buf.sock, "\r\n", 2 )) != 2)
+			{
+				free( cmd->cmd );
+				free( cmd );
+				return NULL;
+			}
+			cmd->param.data = 0;
+		} else
+			ctx->literal_pending = 1;
+	} else if (cmd->param.cont)
+		ctx->literal_pending = 1;
+	cmd->next = 0;
+	*ctx->in_progress_append = cmd;
+	ctx->in_progress_append = &cmd->next;
+	ctx->num_in_progress++;
+	return cmd;
+}
+
+static struct imap_cmd *
+submit_imap_cmd( imap_store_t *ctx, struct imap_cmd *cmd, const char *fmt, ... )
+{
+	struct imap_cmd *ret;
+	va_list ap;
+
+	va_start( ap, fmt );
+	ret = v_submit_imap_cmd( ctx, cmd, fmt, ap );
+	va_end( ap );
+	return ret;
+}
+
+static int
+imap_exec( imap_store_t *ctx, struct imap_cmd *cmdp, const char *fmt, ... )
+{
+	va_list ap;
+
+	va_start( ap, fmt );
+	cmdp = v_submit_imap_cmd( ctx, cmdp, 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 *cmdp, const char *fmt, ... )
+{
+	va_list ap;
+
+	va_start( ap, fmt );
+	cmdp = v_submit_imap_cmd( ctx, cmdp, 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 *cmdp, const char *fmt, ... )
+{
+	va_list ap;
+
+	va_start( ap, fmt );
+	cmdp = v_submit_imap_cmd( ctx, cmdp, 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_store_t *ctx )
+{
+	while (ctx->num_in_progress)
+		get_cmd_result( ctx, 0 );
+}
+*/
+
+static void
+process_imap_replies( imap_store_t *ctx )
+{
+	while (ctx->num_in_progress > max_in_progress ||
+	       socket_pending( &ctx->buf.sock ))
+		get_cmd_result( ctx, 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_store_t *ctx, 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( ctx, &s, &cur->child, level + 1 ))
+				goto bail;
+		} else if (ctx && *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 = ctx->buf.bytes - ctx->buf.offset;
+
+			if (n > bytes)
+				/* the entire message fit in the buffer */
+				n = bytes;
+
+			memcpy( s, ctx->buf.buf + ctx->buf.offset, n );
+			s += n;
+			bytes -= n;
+
+			/* mark that we used part of the buffer */
+			ctx->buf.offset += n;
+
+			/* now read the rest of the message */
+			while (bytes > 0) {
+				if ((n = socket_read( &ctx->buf.sock, s, bytes )) <= 0)
+					goto bail;
+				s += n;
+				bytes -= n;
+			}
+			if (DFlags & XVERBOSE) {
+				puts( "=========" );
+				fwrite( cur->val, cur->len, 1, stdout );
+				puts( "=========" );
+			}
+
+			if (buffer_gets( &ctx->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_store_t *ctx, char **sp )
+{
+	list_t *head;
+
+	if (!parse_imap_list_l( ctx, 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_store_t *ctx, 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( ctx, &cmd );
+
+	if (!is_list( list )) {
+		error( "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
+					error( "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 (flags->val[0] == '\\') { /* ignore user-defined flags for now */
+								if (!strcmp( "Recent", flags->val + 1)) {
+									status |= M_RECENT;
+									goto flagok;
+								}
+								for (i = 0; i < as(Flags); i++)
+									if (!strcmp( Flags[i], flags->val + 1 )) {
+										mask |= 1 << i;
+										goto flagok;
+									}
+								if (flags->val[1] == 'X' && flags->val[2] == '-')
+									goto flagok; /* ignore system flag extensions */
+								error( "IMAP warning: unknown system flag %s\n", flags->val );
+							}
+						  flagok: ;
+						} else
+							error( "IMAP error: unable to parse FLAGS list\n" );
+					}
+					status |= M_FLAGS;
+				} else
+					error( "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
+					error( "IMAP error: unable to parse RFC822.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
+					error( "IMAP error: unable to parse BODY[]\n" );
+			}
+		}
+	}
+
+	if (body) {
+		for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next)
+			if (cmdp->param.uid == uid)
+				goto gotuid;
+		error( "IMAP error: unexpected FETCH response (UID %d)\n", uid );
+		free_list( list );
+		return -1;
+	  gotuid:
+		msgdata = (msg_data_t *)cmdp->param.aux;
+		msgdata->data = body;
+		msgdata->len = size;
+		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) );
+		*ctx->msgapp = &cur->gen;
+		ctx->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_store_t *ctx, char *cmd )
+{
+	char *arg;
+	unsigned i;
+
+	ctx->caps = 0x80000000;
+	while ((arg = next_arg( &cmd )))
+		for (i = 0; i < as(cap_list); i++)
+			if (!strcmp( cap_list[i], arg ))
+				ctx->caps |= 1 << i;
+	ctx->rcaps = ctx->caps;
+}
+
+static int
+parse_response_code( imap_store_t *ctx, struct imap_cmd *cmd, char *s )
+{
+	char *arg, *earg, *p;
+
+	if (*s != '[')
+		return RESP_OK;		/* no response code */
+	s++;
+	if (!(p = strchr( s, ']' ))) {
+		error( "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 = strtoll( arg, &earg, 10 ), *earg))
+		{
+			error( "IMAP error: malformed UIDVALIDITY status\n" );
+			return RESP_BAD;
+		}
+	} else if (!strcmp( "UIDNEXT", arg )) {
+		if (!(arg = next_arg( &s )) || (ctx->uidnext = strtol( arg, &p, 10 ), *p)) {
+			error( "IMAP error: malformed NEXTUID status\n" );
+			return RESP_BAD;
+		}
+	} else if (!strcmp( "CAPABILITY", arg )) {
+		parse_capability( ctx, s );
+	} else if (!strcmp( "ALERT", arg )) {
+		/* RFC2060 says that these messages MUST be displayed
+		 * to the user
+		 */
+		for (; isspace( (unsigned char)*p ); p++);
+		error( "*** IMAP ALERT *** %s\n", p );
+	} else if (cmd && cmd->param.aux && !strcmp( "APPENDUID", arg )) {
+		if (!(arg = next_arg( &s )) ||
+		    (ctx->gen.uidvalidity = strtoll( arg, &earg, 10 ), *earg) ||
+		    !(arg = next_arg( &s )) || !(*(int *)cmd->param.aux = atoi( arg )))
+		{
+			error( "IMAP error: malformed APPENDUID status\n" );
+			return RESP_BAD;
+		}
+	}
+	return RESP_OK;
+}
+
+static void
+parse_search( imap_store_t *ctx, char *cmd )
+{
+	char *arg;
+	struct imap_cmd *cmdp;
+	int uid;
+
+	if (!(arg = next_arg( &cmd )))
+		uid = -1;
+	else if (!(uid = atoi( arg ))) {
+		error( "IMAP error: malformed SEARCH response\n" );
+		return;
+	} else if (next_arg( &cmd )) {
+		warn( "IMAP warning: SEARCH returns multiple matches\n" );
+		uid = -1; /* to avoid havoc */
+	}
+
+	/* 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 = ctx->in_progress; cmdp; cmdp = cmdp->next)
+		if (cmdp->param.uid == -1) {
+			*(int *)cmdp->param.aux = uid;
+			return;
+		}
+	error( "IMAP error: unexpected SEARCH response (UID %u)\n", uid );
+}
+
+static void
+parse_list_rsp( imap_store_t *ctx, char *cmd )
+{
+	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( &ctx->gen.boxes, arg );
+}
+
+static int
+get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
+{
+	struct imap_cmd *cmdp, **pcmdp, *ncmdp;
+	char *cmd, *arg, *arg1, *p;
+	int n, resp, resp2, tag;
+
+	for (;;) {
+		if (buffer_gets( &ctx->buf, &cmd ))
+			return RESP_BAD;
+
+		arg = next_arg( &cmd );
+		if (*arg == '*') {
+			arg = next_arg( &cmd );
+			if (!arg) {
+				error( "IMAP error: unable to parse untagged response\n" );
+				return RESP_BAD;
+			}
+
+			if (!strcmp( "NAMESPACE", arg )) {
+				ctx->ns_personal = parse_list( &cmd );
+				ctx->ns_other = parse_list( &cmd );
+				ctx->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( ctx, cmd );
+			else if (!strcmp( "LIST", arg ))
+				parse_list_rsp( ctx, cmd );
+			else if (!strcmp( "SEARCH", arg ))
+				parse_search( ctx, 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( ctx, cmd ))
+						return RESP_BAD;
+				}
+			} else {
+				error( "IMAP error: unable to parse untagged response\n" );
+				return RESP_BAD;
+			}
+		} else if (!ctx->in_progress) {
+			error( "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 *)ctx->in_progress_append -
+			       offsetof(struct imap_cmd, next));
+			if (cmdp->param.data) {
+				n = socket_write( &ctx->buf.sock, cmdp->param.data, cmdp->param.data_len );
+				free( cmdp->param.data );
+				cmdp->param.data = 0;
+				if (n != (int)cmdp->param.data_len)
+					return RESP_BAD;
+			} else if (cmdp->param.cont) {
+				if (cmdp->param.cont( ctx, cmdp, cmd ))
+					return RESP_BAD;
+			} else {
+				error( "IMAP error: unexpected command continuation request\n" );
+				return RESP_BAD;
+			}
+			if (socket_write( &ctx->buf.sock, "\r\n", 2 ) != 2)
+				return RESP_BAD;
+			if (!cmdp->param.cont)
+				ctx->literal_pending = 0;
+			if (!tcmd)
+				return DRV_OK;
+		} else {
+			tag = atoi( arg );
+			for (pcmdp = &ctx->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
+				if (cmdp->tag == tag)
+					goto gottag;
+			error( "IMAP error: unexpected tag %s\n", arg );
+			return RESP_BAD;
+		  gottag:
+			if (!(*pcmdp = cmdp->next))
+				ctx->in_progress_append = pcmdp;
+			ctx->num_in_progress--;
+			if (cmdp->param.cont || cmdp->param.data)
+				ctx->literal_pending = 0;
+			arg = next_arg( &cmd );
+			if (!strcmp( "OK", arg ))
+				resp = DRV_OK;
+			else {
+				if (!strcmp( "NO", arg )) {
+					if (cmdp->param.create && cmd && (cmdp->param.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
+						p = strchr( cmdp->cmd, '"' );
+						if (!submit_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. */
+						ncmdp = nfmalloc( sizeof(*ncmdp) );
+						memcpy( &ncmdp->param, &cmdp->param, sizeof(cmdp->param) );
+						ncmdp->param.create = 0;
+						if (!submit_imap_cmd( ctx, ncmdp, "%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;
+				error( "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, cmd )) > resp)
+				resp = resp2;
+		  normal:
+			if (cmdp->param.done)
+				cmdp->param.done( ctx, cmdp, resp );
+			if (cmdp->param.data)
+				free( cmdp->param.data );
+			free( cmdp->cmd );
+			free( cmdp );
+			if (!tcmd || tcmd == cmdp)
+				return resp;
+		}
+	}
+	/* not reached */
+}
+
+static void
+imap_cancel_store( store_t *gctx )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+
+	free_generic_messages( gctx->msgs );
+	free_string_list( ctx->gen.boxes );
+	if (ctx->buf.sock.fd >= 0)
+		close( ctx->buf.sock.fd );
+#ifdef HAVE_LIBSSL
+	if (ctx->SSLContext)
+		SSL_CTX_free( ctx->SSLContext );
+#endif
+	free_list( ctx->ns_personal );
+	free_list( ctx->ns_other );
+	free_list( ctx->ns_shared );
+	free( ctx );
+}
+
+static store_t *unowned;
+
+static void
+imap_disown_store( store_t *gctx )
+{
+	free_generic_messages( gctx->msgs );
+	gctx->msgs = 0;
+	gctx->next = unowned;
+	unowned = gctx;
+}
+
+static store_t *
+imap_own_store( store_conf_t *conf )
+{
+	store_t *store, **storep;
+
+	for (storep = &unowned; (store = *storep); storep = &store->next)
+		if (store->conf == conf) {
+			*storep = store->next;
+			return store;
+		}
+	return 0;
+}
+
+static void
+imap_cleanup( void )
+{
+	store_t *ctx, *nctx;
+
+	for (ctx = unowned; ctx; ctx = nctx) {
+		nctx = ctx->next;
+		imap_exec( (imap_store_t *)ctx, 0, "LOGOUT" );
+		imap_cancel_store( ctx );
+	}
+}
+
+#ifdef HAVE_LIBSSL
+static int
+start_tls( imap_store_t *ctx )
+{
+	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;
+
+	ctx->buf.sock.ssl = SSL_new( ctx->SSLContext );
+	SSL_set_fd( ctx->buf.sock.ssl, ctx->buf.sock.fd );
+	if ((ret = SSL_connect( ctx->buf.sock.ssl )) <= 0) {
+		socket_perror( "connect", &ctx->buf.sock, ret );
+		return 1;
+	}
+
+	/* verify the server certificate */
+	if (verify_cert( ctx ))
+		return 1;
+
+	ctx->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_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 (DFlags & VERBOSE)
+		printf( ">+> %s\n", resp );
+	l = strlen( resp );
+	n = socket_write( &ctx->buf.sock, resp, l );
+	free( resp );
+	if (n != l)
+		return -1;
+	cmdp->param.cont = 0;
+	return 0;
+}
+#endif
+
+static void
+imap_open_store( store_conf_t *conf,
+                 void (*cb)( store_t *srv, void *aux ), void *aux )
+{
+	imap_store_conf_t *cfg = (imap_store_conf_t *)conf;
+	imap_server_conf_t *srvc = cfg->server;
+	imap_store_t *ctx;
+	store_t **ctxp;
+	char *arg, *rsp;
+	struct hostent *he;
+	struct sockaddr_in addr;
+	int s, a[2], preauth;
+#if HAVE_LIBSSL
+	int use_ssl;
+#endif
+
+	for (ctxp = &unowned; (ctx = (imap_store_t *)*ctxp); ctxp = &ctx->gen.next)
+		if (((imap_store_conf_t *)ctx->gen.conf)->server == srvc) {
+			*ctxp = ctx->gen.next;
+			/* One could ping the server here, but given that the idle timeout
+			 * is at least 30 minutes, this sounds pretty pointless. */
+			free_string_list( ctx->gen.boxes );
+			ctx->gen.boxes = 0;
+			ctx->gen.listed = 0;
+			ctx->gen.conf = conf;
+			goto final;
+		}
+
+	ctx = nfcalloc( sizeof(*ctx) );
+	ctx->gen.conf = conf;
+	ctx->buf.sock.fd = -1;
+	ctx->in_progress_append = &ctx->in_progress;
+
+	/* open connection to IMAP server */
+#if HAVE_LIBSSL
+	use_ssl = 0;
+#endif
+
+	if (srvc->tunnel) {
+		infon( "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, (char *)0 );
+			_exit( 127 );
+		}
+
+		close (a[0]);
+
+		ctx->buf.sock.fd = a[1];
+
+		info( "ok\n" );
+	} else {
+		memset( &addr, 0, sizeof(addr) );
+		addr.sin_port = srvc->port ? htons( srvc->port ) :
+#ifdef HAVE_LIBSSL
+		                srvc->use_imaps ? htons( 993 ) :
+#endif
+		                htons( 143 );
+		addr.sin_family = AF_INET;
+
+		infon( "Resolving %s... ", srvc->host );
+		he = gethostbyname( srvc->host );
+		if (!he) {
+			error( "IMAP error: Cannot resolve server '%s'\n", srvc->host );
+			goto bail;
+		}
+		info( "ok\n" );
+
+		addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
+
+		s = socket( PF_INET, SOCK_STREAM, 0 );
+		if (s < 0) {
+			perror( "socket" );
+			exit( 1 );
+		}
+
+		infon( "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" );
+
+		ctx->buf.sock.fd = s;
+	}
+
+#if HAVE_LIBSSL
+	if (srvc->use_imaps) {
+		if (start_tls( ctx ))
+			goto ssl_bail;
+		use_ssl = 1;
+	}
+#endif
+
+	/* read the greeting string */
+	if (buffer_gets( &ctx->buf, &rsp ))
+		goto bail;
+	arg = next_arg( &rsp );
+	if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
+		error( "IMAP error: invalid greeting response\n" );
+		goto bail;
+	}
+	preauth = 0;
+	if (!strcmp( "PREAUTH", arg ))
+		preauth = 1;
+	else if (strcmp( "OK", arg ) != 0) {
+		error( "IMAP error: unknown greeting response\n" );
+		goto bail;
+	}
+	parse_response_code( ctx, 0, rsp );
+	if (!ctx->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 ssl_bail;
+				use_ssl = 1;
+
+				if (imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+					goto bail;
+			} else {
+				if (srvc->require_ssl) {
+					error( "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) {
+			error( "Skipping account %s, no user\n", srvc->name );
+			goto bail;
+		}
+		if (!srvc->pass) {
+			char prompt[80];
+			sprintf( prompt, "Password (%s): ", srvc->name );
+			arg = getpass( prompt );
+			if (!arg) {
+				perror( "getpass" );
+				exit( 1 );
+			}
+			if (!*arg) {
+				error( "Skipping account %s, no password\n", srvc->name );
+				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 *cmd = new_imap_cmd();
+
+			info( "Authenticating with CRAM-MD5\n" );
+			cmd->param.cont = do_cram_auth;
+			if (imap_exec( ctx, cmd, "AUTHENTICATE CRAM-MD5" ) != RESP_OK)
+				goto bail;
+		} else if (srvc->require_cram) {
+			error( "IMAP error: CRAM-MD5 authentication is not supported by server\n" );
+			goto bail;
+		} else
+#endif
+		{
+			if (CAP(NOLOGIN)) {
+				error( "Skipping account %s, server forbids LOGIN\n", srvc->name );
+				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) {
+				error( "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 (!ctx->got_namespace) {
+			if (imap_exec( ctx, 0, "NAMESPACE" ) != RESP_OK) {
+				cb( 0, aux );
+				return;
+			}
+			ctx->got_namespace = 1;
+		}
+		/* XXX for now assume personal namespace */
+		if (is_list( ctx->ns_personal ) &&
+		    is_list( ctx->ns_personal->child ) &&
+		    is_atom( ctx->ns_personal->child->child ))
+			ctx->prefix = ctx->ns_personal->child->child->val;
+	}
+	ctx->trashnc = 1;
+	cb( &ctx->gen, aux );
+	return;
+
+#if HAVE_LIBSSL
+  ssl_bail:
+	/* This avoids that we try to send LOGOUT to an unusable socket. */
+	close( ctx->buf.sock.fd );
+	ctx->buf.sock.fd = -1;
+#endif
+  bail:
+	imap_cancel_store( &ctx->gen );
+	cb( 0, aux );
+}
+
+static void
+imap_prepare_paths( store_t *gctx )
+{
+	free_generic_messages( gctx->msgs );
+	gctx->msgs = 0;
+}
+
+static void
+imap_prepare_opts( store_t *gctx, int opts )
+{
+	gctx->opts = opts;
+}
+
+static int
+imap_select( store_t *gctx, int minuid, int maxuid, int *excs, int nexcs,
+             int (*cb)( int sts, void *aux ), void *aux )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+	struct imap_cmd *cmd = new_imap_cmd();
+	const char *prefix;
+	int ret, i, j, bl;
+	char buf[1000];
+
+
+	if (!strcmp( gctx->name, "INBOX" )) {
+//		ctx->currentnc = 0;
+		prefix = "";
+	} else {
+//		ctx->currentnc = 1;	/* could use LIST results for that */
+		prefix = ctx->prefix;
+	}
+
+	ctx->uidnext = -1;
+
+	cmd->param.create = (gctx->opts & OPEN_CREATE) != 0;
+	cmd->param.trycreate = 1;
+	if ((ret = imap_exec_b( ctx, cmd, "SELECT \"%s%s\"", prefix, gctx->name )) != DRV_OK)
+		goto bail;
+
+	if (gctx->count) {
+		ctx->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 = ctx->uidnext >= 0 ? ctx->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 cb( ret, aux );
+}
+
+static int
+imap_fetch_msg( store_t *ctx, message_t *msg, msg_data_t *data,
+                int (*cb)( int sts, void *aux ), void *aux )
+{
+	struct imap_cmd *cmd = new_imap_cmd();
+	cmd->param.uid = msg->uid;
+	cmd->param.aux = data;
+	return cb( imap_exec_m( (imap_store_t *)ctx, cmd, "UID FETCH %d (%sBODY.PEEK[])",
+	                        msg->uid, (msg->status & M_FLAGS) ? "" : "FLAGS " ), aux );
+}
+
+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++] = ' ';
+			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;
+	if (!submit_imap_cmd( ctx, 0, "UID STORE %d %cFLAGS.SILENT %s", uid, what, buf ))
+		return DRV_STORE_BAD;
+	process_imap_replies( ctx );
+	return DRV_OK;
+}
+
+static int
+imap_set_flags( store_t *gctx, message_t *msg, int uid, int add, int del,
+                int (*cb)( int sts, void *aux ), void *aux )
+{
+	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))
+		ret = DRV_OK;
+	return cb( ret, aux );
+}
+
+static int
+imap_close( store_t *ctx,
+            int (*cb)( int sts, void *aux ), void *aux )
+{
+	return cb( imap_exec_b( (imap_store_t *)ctx, 0, "CLOSE" ), aux );
+}
+
+static int
+imap_trash_msg( store_t *gctx, message_t *msg,
+                int (*cb)( int sts, void *aux ), void *aux )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+	struct imap_cmd *cmd = new_imap_cmd();
+	cmd->param.create = 1;
+	return cb( imap_exec_m( ctx, cmd, "UID COPY %d \"%s%s\"",
+	                        msg->uid, ctx->prefix, gctx->conf->trash ), aux );
+}
+
+static int
+imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
+                int (*cb)( int sts, int uid, void *aux ), void *aux )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+	struct imap_cmd *cmd = new_imap_cmd();
+	const char *prefix, *box;
+	int ret, d, uid;
+	char flagstr[128];
+
+	d = 0;
+	if (data->flags) {
+		d = imap_make_flags( data->flags, flagstr );
+		flagstr[d++] = ' ';
+	}
+	flagstr[d] = 0;
+
+	cmd->param.data_len = data->len;
+	cmd->param.data = data->data;
+	cmd->param.aux = &uid;
+	uid = -2;
+
+	if (to_trash) {
+		box = gctx->conf->trash;
+		prefix = ctx->prefix;
+		cmd->param.create = 1;
+		if (ctx->trashnc)
+			ctx->caps = ctx->rcaps & ~(1 << LITERALPLUS);
+	} else {
+		box = gctx->name;
+		prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+		cmd->param.create = (gctx->opts & OPEN_CREATE) != 0;
+		/*if (ctx->currentnc)
+			ctx->caps = ctx->rcaps & ~(1 << LITERALPLUS);*/
+	}
+	ret = imap_exec_m( ctx, cmd, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+	ctx->caps = ctx->rcaps;
+	if (ret != DRV_OK)
+		return cb( ret, -1, aux );
+	if (to_trash)
+		ctx->trashnc = 0;
+	else {
+		/*ctx->currentnc = 0;*/
+	}
+
+	return cb( DRV_OK, uid, aux );
+}
+
+static int
+imap_find_msg( store_t *gctx, const char *tuid,
+               int (*cb)( int sts, int uid, void *aux ), void *aux )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+	struct imap_cmd *cmd = new_imap_cmd();
+	int ret, uid;
+
+	cmd->param.uid = -1; /* we're looking for a UID */
+	cmd->param.aux = &uid;
+	uid = -1; /* in case we get no SEARCH response at all */
+	if ((ret = imap_exec_m( ctx, cmd, "UID SEARCH HEADER X-TUID %." stringify(TUIDL) "s", tuid )) != DRV_OK)
+		return cb( ret, -1, aux );
+	else
+		return cb( uid <= 0 ? DRV_MSG_BAD : DRV_OK, uid, aux );
+}
+
+static void
+imap_list( store_t *gctx,
+           void (*cb)( int sts, void *aux ), void *aux )
+{
+	imap_store_t *ctx = (imap_store_t *)gctx;
+	int ret;
+
+	if ((ret = imap_exec_b( ctx, 0, "LIST \"\" \"%s%%\"", ctx->prefix )) == DRV_OK)
+		gctx->listed = 1;
+	cb( ret, aux );
+}
+
+static void
+imap_cancel( store_t *gctx,
+             void (*cb)( int sts, void *aux ), void *aux )
+{
+	(void)gctx;
+	cb( DRV_OK, aux );
+}
+
+static void
+imap_commit( store_t *gctx )
+{
+	(void)gctx;
+}
+
+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;
+	int acc_opt = 0;
+
+	if (!strcasecmp( "IMAPAccount", cfg->cmd )) {
+		server = nfcalloc( sizeof(*server) );
+		server->name = nfstrdup( cfg->val );
+		*serverapp = server;
+		serverapp = &server->next;
+		store = 0;
+		*storep = 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;
+		*storep = &store->gen;
+		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( "Host", cfg->cmd )) {
+			/* The imap[s]: syntax is just a backwards compat hack. */
+#if HAVE_LIBSSL
+			if (!memcmp( "imaps:", cfg->val, 6 )) {
+				cfg->val += 6;
+				server->use_imaps = 1;
+				server->use_sslv2 = 1;
+				server->use_sslv3 = 1;
+			} else
+#endif
+			{
+				if (!memcmp( "imap:", cfg->val, 5 ))
+					cfg->val += 5;
+			}
+			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 );
+			if (access( server->cert_file, R_OK )) {
+				error( "%s:%d: CertificateFile '%s': %s\n",
+				       cfg->file, cfg->line, server->cert_file, strerror( errno ) );
+				*err = 1;
+			}
+		} else if (!strcasecmp( "RequireSSL", cfg->cmd ))
+			server->require_ssl = parse_bool( cfg );
+		else if (!strcasecmp( "UseIMAPS", cfg->cmd ))
+			server->use_imaps = 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( "Account", cfg->cmd )) {
+				for (srv = servers; srv; srv = srv->next)
+					if (srv->name && !strcmp( srv->name, cfg->val ))
+						goto gotsrv;
+				error( "%s:%d: unknown IMAP account '%s'\n", cfg->file, cfg->line, cfg->val );
+				*err = 1;
+				continue;
+			  gotsrv:
+				store->server = srv;
+			} else 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 );
+			continue;
+		} else {
+			error( "%s:%d: unknown/misplaced keyword '%s'\n", cfg->file, cfg->line, cfg->cmd );
+			*err = 1;
+			continue;
+		}
+		acc_opt = 1;
+	}
+	if (!store || !store->server) {
+		if (!server->tunnel && !server->host) {
+			if (store)
+				error( "IMAP store '%s' has incomplete/missing connection details\n", store->gen.name );
+			else
+				error( "IMAP account '%s' has incomplete/missing connection details\n", server->name );
+			*err = 1;
+			return 1;
+		}
+	}
+	if (store) {
+		if (!store->server) {
+			store->server = nfmalloc( sizeof(sserver) );
+			memcpy( store->server, &sserver, sizeof(sserver) );
+			store->server->name = store->gen.name;
+		} else if (acc_opt) {
+			error( "IMAP store '%s' has both Account and account-specific options\n", store->gen.name );
+			*err = 1;
+		}
+	}
+	return 1;
+}
+
+struct driver imap_driver = {
+	DRV_CRLF,
+	imap_parse_store,
+	imap_cleanup,
+	imap_open_store,
+	imap_disown_store,
+	imap_own_store,
+	imap_cancel_store,
+	imap_list,
+	imap_prepare_paths,
+	imap_prepare_opts,
+	imap_select,
+	imap_fetch_msg,
+	imap_store_msg,
+	imap_find_msg,
+	imap_set_flags,
+	imap_trash_msg,
+	imap_close,
+	imap_cancel,
+	imap_commit,
+};
diff --git a/isync/drv_maildir.c b/isync/drv_maildir.c
new file mode 100644
index 0000000..80a7cac
--- /dev/null
+++ b/isync/drv_maildir.c
@@ -0,0 +1,1272 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso at mit.edu>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * 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 __linux__
+# define LEGACY_FLOCK 1
+#endif
+
+#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;
+	char tuid[TUIDL];
+} 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_open_store( store_conf_t *conf,
+                    void (*cb)( store_t *ctx, void *aux ), void *aux )
+{
+	maildir_store_t *ctx;
+	struct stat st;
+
+	if (stat( conf->path, &st ) || !S_ISDIR(st.st_mode)) {
+		error( "Maildir error: cannot open store %s\n", conf->path );
+		cb( 0, aux );
+		return;
+	}
+	ctx = nfcalloc( sizeof(*ctx) );
+	ctx->gen.conf = conf;
+	ctx->uvfd = -1;
+	cb( &ctx->gen, aux );
+}
+
+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_disown_store( store_t *gctx )
+{
+	maildir_cleanup( gctx );
+	free_string_list( gctx->boxes );
+	free( gctx );
+}
+
+static store_t *
+maildir_own_store( store_conf_t *conf )
+{
+	(void)conf;
+	return 0;
+}
+
+static void
+maildir_cleanup_drv( void )
+{
+}
+
+static void
+maildir_list( store_t *gctx,
+              void (*cb)( int sts, void *aux ), void *aux )
+{
+	DIR *dir;
+	struct dirent *de;
+
+	if (!(dir = opendir( gctx->conf->path ))) {
+		error( "%s: %s\n", gctx->conf->path, strerror(errno) );
+		cb( DRV_STORE_BAD, aux );
+		return;
+	}
+	while ((de = readdir( dir ))) {
+		const char *inbox = ((maildir_store_conf_t *)gctx->conf)->inbox;
+		int bl;
+		struct stat st;
+		char buf[PATH_MAX];
+
+		if (*de->d_name == '.')
+			continue;
+		bl = 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( &gctx->boxes,
+		                 !memcmp( buf, inbox, bl - 4 ) && !inbox[bl - 4] ? "INBOX" : de->d_name );
+	}
+	closedir (dir);
+	gctx->listed = 1;
+
+	cb( DRV_OK, aux );
+}
+
+static const char *subdirs[] = { "cur", "new", "tmp" };
+
+typedef struct {
+	char *base;
+	int size;
+	unsigned uid:31, recent:1;
+	char tuid[TUIDL];
+} 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, j, 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 )) {
+					error( "Maildir error: mkdir %s: %s (errno %d)\n",
+					       buf, strerror(errno), errno );
+					return DRV_STORE_BAD;
+				}
+			  mkdirs:
+				for (i = 0; i < 3; i++) {
+					memcpy( buf + bl, subdirs[i], 4 );
+					if (mkdir( buf, 0700 )) {
+						error( "Maildir error: mkdir %s: %s (errno %d)\n",
+						       buf, strerror(errno), errno );
+						return DRV_BOX_BAD;
+					}
+				}
+			} else {
+				error( "Maildir error: mailbox '%s' does not exist\n", buf );
+				return DRV_BOX_BAD;
+			}
+		} else {
+			error( "Maildir error: stat %s: %s (errno %d)\n",
+			       buf, strerror(errno), errno );
+			return DRV_BOX_BAD;
+		}
+	} else {
+		for (i = j = 0; i < 3; i++) {
+			memcpy( buf + bl, subdirs[i], 4 );
+			if (!stat( buf, &st ) && S_ISDIR(st.st_mode))
+				j++;
+		}
+		if (!j)
+			goto mkdirs;
+		if (j != 3) {
+			error( "Maildir error: '%.*s' is no valid mailbox\n", bl, buf );
+			return DRV_BOX_BAD;
+		}
+		memcpy( buf + bl, "tmp/", 5 );
+		bl += 4;
+		if (!(dirp = opendir( buf ))) {
+			error( "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 ))
+				error( "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 ))
+					error( "Maildir error: unlink: %s: %s (errno %d)\n",
+					       buf, strerror(errno), errno );
+			}
+		}
+		closedir( dirp );
+	}
+	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];
+
+	if (uid)
+		*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;
+	}
+	if (uid) {
+		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_store_uid( maildir_store_t *ctx )
+{
+	int n;
+	char buf[128];
+
+	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 )) {
+		error( "Maildir error: cannot write UIDVALIDITY.\n" );
+		return DRV_BOX_BAD;
+	}
+	return DRV_OK;
+}
+
+static int
+maildir_init_uid( maildir_store_t *ctx, const char *msg )
+{
+	info( "Maildir notice: %s.\n", msg ? msg : "cannot read UIDVALIDITY, creating new" );
+	ctx->gen.uidvalidity = time( 0 );
+	ctx->nuid = 0;
+	ctx->uvok = 0;
+#ifdef USE_DB
+	if (ctx->db) {
+		u_int32_t count;
+		ctx->db->truncate( ctx->db, 0, &count, 0 );
+		return maildir_set_uid( ctx, 0, 0 );
+	}
+#endif /* USE_DB */
+	return maildir_store_uid( ctx );
+}
+
+static int
+maildir_uidval_lock( maildir_store_t *ctx )
+{
+	int n;
+	char buf[128];
+
+#ifdef LEGACY_FLOCK
+	/* This is legacy only */
+	if (flock( ctx->uvfd, LOCK_EX ) < 0) {
+		error( "Maildir error: cannot flock UIDVALIDITY.\n" );
+		return DRV_BOX_BAD;
+	}
+#endif
+	/* 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 )) {
+		error( "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)) {
+		return maildir_init_uid( ctx, 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 );
+#ifdef LEGACY_FLOCK
+	/* This is legacy only */
+	flock( ctx->uvfd, LOCK_UN );
+#endif
+}
+
+static int
+maildir_obtain_uid( maildir_store_t *ctx, int *uid )
+{
+	*uid = ++ctx->nuid;
+	return maildir_store_uid( ctx );
+}
+
+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;
+	FILE *f;
+	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, 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;
+					entry->tuid[0] = 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) {
+					if ((ret = maildir_init_uid( ctx, "duplicate UID; changing UIDVALIDITY" )) != DRV_OK) {
+						maildir_free_scan( msglist );
+						return ret;
+					}
+					maildir_free_scan( msglist );
+					goto again;
+				}
+				uid = entry->uid;
+				if (ctx->gen.opts & (OPEN_SIZE|OPEN_FIND))
+					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|OPEN_FIND))
+					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, ':' );
+				fnl = (u ?
+					nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%.*s,U=%d%s", subdirs[entry->recent], u - entry->base, entry->base, uid, ru ) :
+					nfsnprintf( buf + bl, sizeof(buf) - bl, "%s/%s,U=%d", subdirs[entry->recent], entry->base, uid ))
+					+ 1 - 4;
+				memcpy( nbuf, buf, bl + 4 );
+				nfsnprintf( nbuf + bl + 4, sizeof(nbuf) - bl - 4, "%s", entry->base );
+				if (rename( nbuf, buf )) {
+				  notok:
+					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 ))
+					goto notok;
+				entry->size = st.st_size;
+			}
+			if (ctx->gen.opts & OPEN_FIND) {
+				if (!(f = fopen( buf, "r" )))
+					goto notok;
+				while (fgets( nbuf, sizeof(nbuf), f )) {
+					if (!nbuf[0] || nbuf[0] == '\n')
+						break;
+					if (!memcmp( nbuf, "X-TUID: ", 8 ) && nbuf[8 + TUIDL] == '\n') {
+						memcpy( entry->tuid, nbuf + 8, TUIDL );
+						break;
+					}
+				}
+				fclose( f );
+			}
+		}
+		ctx->uvok = 1;
+	}
+#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;
+	strncpy( msg->tuid, entry->tuid, TUIDL );
+	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_paths( store_t *gctx )
+{
+	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 );
+}
+
+static void
+maildir_prepare_opts( store_t *gctx, int opts )
+{
+	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,
+                int (*cb)( int sts, void *aux ), void *aux )
+{
+	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 cb( DRV_BOX_BAD, aux );
+
+	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 cb( DRV_BOX_BAD, aux );
+	}
+#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 cb( DRV_BOX_BAD, aux );
+		}
+	  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 cb( DRV_BOX_BAD, aux );
+		}
+		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;
+			}
+			if (maildir_init_uid( ctx, 0 ) != DRV_OK)
+				goto dbork;
+		} 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 cb( DRV_BOX_BAD, aux );
+	msgapp = &ctx->gen.msgs;
+	for (i = 0; i < msglist.nents; i++)
+		maildir_app_msg( ctx, &msgapp, msglist.ents + i );
+	maildir_free_scan( &msglist );
+
+	return cb( DRV_OK, aux );
+}
+
+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) {
+#if 0
+			debug( "adding new message %d\n", msglist.ents[i].uid );
+			maildir_app_msg( ctx, &msgapp, msglist.ents + i );
+#else
+			debug( "ignoring new message %d\n", msglist.ents[i].uid );
+#endif
+			i++;
+		} else if (i >= msglist.nents) {
+			debug( "purging deleted message %d\n", msg->gen.uid );
+			msg->gen.status = M_DEAD;
+			msgapp = &msg->gen.next;
+		} else if (msglist.ents[i].uid < msg->gen.uid) {
+			/* this should not happen, actually */
+#if 0
+			debug( "adding new message %d\n", msglist.ents[i].uid );
+			maildir_app_msg( ctx, &msgapp, msglist.ents + i );
+#else
+			debug( "ignoring new message %d\n", msglist.ents[i].uid );
+#endif
+			i++;
+		} else if (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_MSG_BAD : DRV_OK;
+}
+
+static int
+maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data,
+                   int (*cb)( int sts, void *aux ), void *aux )
+{
+	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 cb( ret, aux );
+	}
+	fstat( fd, &st );
+	data->len = st.st_size;
+	data->data = nfmalloc( data->len );
+	if (read( fd, data->data, data->len ) != data->len) {
+		perror( buf );
+		close( fd );
+		return cb( DRV_MSG_BAD, aux );
+	}
+	close( fd );
+	if (!(gmsg->status & M_FLAGS))
+		data->flags = maildir_parse_flags( msg->base );
+	return cb( DRV_OK, aux );
+}
+
+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 to_trash,
+                   int (*cb)( int sts, int uid, void *aux ), void *aux )
+{
+	maildir_store_t *ctx = (maildir_store_t *)gctx;
+	const char *prefix, *box;
+	int ret, fd, bl, uid;
+	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 (!to_trash) {
+#ifdef USE_DB
+		if (ctx->db) {
+			if ((ret = maildir_set_uid( ctx, base, &uid )) != DRV_OK) {
+				free( data->data );
+				return cb( ret, 0, aux );
+			}
+		} else
+#endif /* USE_DB */
+		{
+			if ((ret = maildir_uidval_lock( ctx )) != DRV_OK ||
+			    (ret = maildir_obtain_uid( ctx, &uid )) != DRV_OK)
+				return cb( ret, 0, aux );
+			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 cb( DRV_BOX_BAD, 0, aux );
+		}
+		if ((ret = maildir_validate( gctx->conf->path, gctx->conf->trash, gctx->opts & OPEN_CREATE )) != DRV_OK) {
+			free( data->data );
+			return cb( ret, 0, aux );
+		}
+		if ((fd = open( buf, O_WRONLY|O_CREAT|O_EXCL, 0600 )) < 0) {
+			perror( buf );
+			free( data->data );
+			return cb( DRV_BOX_BAD, 0, aux );
+		}
+	}
+	ret = write( fd, data->data, data->len );
+	free( data->data );
+	if (ret != data->len) {
+		if (ret < 0)
+			perror( buf );
+		else
+			error( "Maildir error: %s: partial write\n", buf );
+		close( fd );
+		return cb( DRV_BOX_BAD, 0, aux );
+	}
+	close( fd );
+	/* Moving seen messages to cur/ is strictly speaking incorrect, but makes mutt happy. */
+	nfsnprintf( nbuf, sizeof(nbuf), "%s%s/%s/%s%s", prefix, box, subdirs[!(data->flags & F_SEEN)], base, fbuf );
+	if (rename( buf, nbuf )) {
+		perror( nbuf );
+		return cb( DRV_BOX_BAD, 0, aux );
+	}
+	return cb( DRV_OK, uid, aux );
+}
+
+static int
+maildir_find_msg( store_t *gctx, const char *tuid,
+                  int (*cb)( int sts, int uid, void *aux ), void *aux )
+{
+	message_t *msg;
+
+	/* using a hash table might turn out to be more appropriate ... */
+	for (msg = gctx->msgs; msg; msg = msg->next)
+		if (!(msg->status & M_DEAD) && !memcmp( ((maildir_message_t *)msg)->tuid, tuid, TUIDL ))
+			return cb( DRV_OK, msg->uid, aux );
+	return cb( DRV_MSG_BAD, -1, aux );
+}
+
+static int
+maildir_set_flags( store_t *gctx, message_t *gmsg, int uid, int add, int del,
+                   int (*cb)( int sts, void *aux ), void *aux )
+{
+	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 );
+	memcpy( nbuf, gctx->path, bbl - 1 );
+	memcpy( nbuf + bbl - 1, "/cur/", 5 );
+	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 + bl, msg->base, 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 cb( ret, aux );
+	}
+	free( msg->base );
+	msg->base = nfmalloc( tl + 1 );
+	memcpy( msg->base, nbuf + bl, tl + 1 );
+	msg->gen.flags |= add;
+	msg->gen.flags &= ~del;
+	gmsg->status &= ~M_RECENT;
+
+	return cb( DRV_OK, aux );
+}
+
+#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,
+                   int (*cb)( int sts, void *aux ), void *aux )
+{
+	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/%s/%ld.%d_%d.%s%s", gctx->conf->path, gctx->conf->trash,
+		            subdirs[gmsg->status & M_RECENT], 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 cb( ret, aux );
+			if (!rename( buf, nbuf ))
+				break;
+			if (errno != ENOENT) {
+				perror( nbuf );
+				return cb( DRV_BOX_BAD, aux );
+			}
+		}
+		if ((ret = maildir_again( ctx, msg, buf )) != DRV_OK)
+			return cb( ret, aux );
+	}
+	gmsg->status |= M_DEAD;
+	gctx->count--;
+
+#ifdef USE_DB
+	if (ctx->db)
+		return cb( maildir_purge_msg( ctx, msg->base ), aux );
+#endif /* USE_DB */
+	return cb( DRV_OK, aux );
+}
+
+static int
+maildir_close( store_t *gctx,
+               int (*cb)( int sts, void *aux ), void *aux )
+{
+#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 cb( ret, aux );
+#endif /* USE_DB */
+				}
+			}
+		if (!retry)
+			return cb( DRV_OK, aux );
+		if ((ret = maildir_rescan( (maildir_store_t *)gctx )) != DRV_OK)
+			return cb( ret, aux );
+	}
+}
+
+static void
+maildir_cancel( store_t *gctx,
+                void (*cb)( int sts, void *aux ), void *aux )
+{
+	(void)gctx;
+	cb( DRV_OK, aux );
+}
+
+static void
+maildir_commit( store_t *gctx )
+{
+	(void) gctx;
+}
+
+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 = {
+	0, /* XXX DRV_CRLF? */
+	maildir_parse_store,
+	maildir_cleanup_drv,
+	maildir_open_store,
+	maildir_disown_store,
+	maildir_own_store,
+	maildir_disown_store, /* _cancel_, but it's the same */
+	maildir_list,
+	maildir_prepare_paths,
+	maildir_prepare_opts,
+	maildir_select,
+	maildir_fetch_msg,
+	maildir_store_msg,
+	maildir_find_msg,
+	maildir_set_flags,
+	maildir_trash_msg,
+	maildir_close,
+	maildir_cancel,
+	maildir_commit,
+};
diff --git a/isync/isync.h b/isync/isync.h
new file mode 100644
index 0000000..79cc9a9
--- /dev/null
+++ b/isync/isync.h
@@ -0,0 +1,300 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+
+#define as(ar) (sizeof(ar)/sizeof(ar[0]))
+
+#define __stringify(x) #x
+#define stringify(x) __stringify(x)
+
+#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
+
+#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 */
+	const char *map_inbox;
+	const 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;
+
+#define M 0 /* master */
+#define S 1 /* slave */
+
+typedef struct channel_conf {
+	struct channel_conf *next;
+	const char *name;
+	store_conf_t *stores[2];
+	const char *boxes[2];
+	char *sync_state;
+	string_list_t *patterns;
+	int ops[2];
+	unsigned max_messages; /* for slave only */
+} channel_conf_t;
+
+typedef struct group_conf {
+	struct group_conf *next;
+	const 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 */
+
+typedef struct message {
+	struct message *next;
+	struct sync_rec *srec;
+	/* 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)
+#define OPEN_FIND       (1<<8)
+
+typedef struct store {
+	struct store *next;
+	store_conf_t *conf; /* foreign */
+	string_list_t *boxes; /* _list results - own */
+	unsigned listed:1; /* was _list already run? */
+
+	/* currently open mailbox */
+	const char *name; /* foreign! maybe preset? */
+	char *path; /* own */
+	message_t *msgs; /* own */
+	int uidvalidity;
+	unsigned 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;
+} msg_data_t;
+
+#define DRV_OK          0
+#define DRV_MSG_BAD     1
+#define DRV_BOX_BAD     2
+#define DRV_STORE_BAD   3
+#define DRV_SERVER_BAD  4
+#define DRV_CANCELED    5
+
+/* All memory belongs to the driver's user. */
+
+/*
+   This flag says that the driver CAN store messages with CRLFs,
+   not that it must. The lack of it OTOH implies that it CANNOT,
+   and as CRLF is the canonical format, we convert.
+*/
+#define DRV_CRLF        1
+
+#define TUIDL 12
+
+struct driver {
+	int flags;
+	int (*parse_store)( conffile_t *cfg, store_conf_t **storep, int *err );
+	void (*cleanup)( void );
+	void (*open_store)( store_conf_t *conf,
+	                    void (*cb)( store_t *ctx, void *aux ), void *aux );
+	void (*disown_store)( store_t *ctx );
+	store_t *(*own_store)( store_conf_t *conf );
+	void (*cancel_store)( store_t *ctx );
+	void (*list)( store_t *ctx,
+	              void (*cb)( int sts, void *aux ), void *aux );
+	void (*prepare_paths)( store_t *ctx );
+	void (*prepare_opts)( store_t *ctx, int opts );
+	int (*select)( store_t *ctx, int minuid, int maxuid, int *excs, int nexcs,
+	               int (*cb)( int sts, void *aux ), void *aux );
+	int (*fetch_msg)( store_t *ctx, message_t *msg, msg_data_t *data,
+	                  int (*cb)( int sts, void *aux ), void *aux );
+	int (*store_msg)( store_t *ctx, msg_data_t *data, int to_trash,
+	                  int (*cb)( int sts, int uid, void *aux ), void *aux );
+	int (*find_msg)( store_t *ctx, const char *tuid,
+	                 int (*cb)( int sts, int uid, void *aux ), void *aux );
+	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 (*cb)( int sts, void *aux ), void *aux );
+	int (*trash_msg)( store_t *ctx, message_t *msg, /* This may expunge the original message immediately, but it needn't to */
+	                  int (*cb)( int sts, void *aux ), void *aux );
+	int (*close)( store_t *ctx, /* IMAP-style: expunge inclusive */
+	              int (*cb)( int sts, void *aux ), void *aux );
+	void (*cancel)( store_t *ctx, /* only not yet sent commands */
+	                void (*cb)( int sts, void *aux ), void *aux );
+	void (*commit)( store_t *ctx );
+};
+
+
+/* main.c */
+
+extern int Pid;
+extern char Hostname[256];
+extern const char *Home;
+
+
+/* util.c */
+
+#define DEBUG        1
+#define VERBOSE      2
+#define XVERBOSE     4
+#define QUIET        8
+#define VERYQUIET    16
+#define KEEPJOURNAL  32
+
+extern int DFlags, Ontty;
+
+void debug( const char *, ... );
+void debugn( const char *, ... );
+void info( const char *, ... );
+void infon( const char *, ... );
+void warn( const char *, ... );
+void error( const char *, ... );
+
+char *next_arg( char ** );
+
+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 * );
+
+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 );
+
+char *expand_strdup( const char *s );
+
+void sort_ints( int *arr, int len );
+
+void arc4_init( void );
+unsigned char arc4_getbyte( void );
+
+/* sync.c */
+
+extern const char *str_ms[2], *str_hl[2];
+
+#define SYNC_OK       0 /* assumed to be 0 */
+#define SYNC_FAIL     1
+#define SYNC_BAD(ms)  (2<<(ms))
+#define SYNC_NOGOOD   8 /* internal */
+#define SYNC_CANCELED 16 /* internal */
+
+/* All passed pointers must stay alive until cb is called. */
+void sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan,
+                 void (*cb)( int sts, void *aux ), void *aux );
+
+/* config.c */
+
+#define N_DRIVERS 2
+extern driver_t *drivers[N_DRIVERS];
+
+extern channel_conf_t *channels;
+extern group_conf_t *groups;
+extern int global_ops[2];
+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 ops[] );
+int load_config( const char *filename, int pseudo );
+void parse_generic_store( store_conf_t *store, conffile_t *cfg, int *err );
+
+/* drv_*.c */
+extern driver_t maildir_driver, imap_driver;
diff --git a/isync/main.c b/isync/main.c
new file mode 100644
index 0000000..107c2e8
--- /dev/null
+++ b/isync/main.c
@@ -0,0 +1,746 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+int Pid;		/* for maildir and imap */
+char Hostname[256];	/* for maildir */
+const char *Home;	/* for config */
+
+static void
+version( void )
+{
+	puts( PACKAGE " " VERSION );
+	exit( 0 );
+}
+
+static void
+usage( int code )
+{
+	fputs(
+PACKAGE " " VERSION " - mailbox synchronizer\n"
+"Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>\n"
+"Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at users.sf.net>\n"
+"Copyright (C) 2004 Theodore Ts'o <tytso at mit.edu>\n"
+"usage:\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"
+"\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 );
+}
+
+#ifdef __linux__
+static void
+crashHandler( int n )
+{
+	int dpid;
+	char pbuf[10], pabuf[20];
+
+	close( 0 );
+	open( "/dev/tty", O_RDWR );
+	dup2( 0, 1 );
+	dup2( 0, 2 );
+	error( "*** " EXE " caught signal %d. Starting debugger ...\n", n );
+	switch ((dpid = fork())) {
+	case -1:
+		perror( "fork()" );
+		break;
+	case 0:
+		sprintf( pbuf, "%d", Pid );
+		sprintf( pabuf, "/proc/%d/exe", Pid );
+		execlp( "gdb", "gdb", pabuf, pbuf, (char *)0 );
+		perror( "execlp()" );
+		_exit( 1 );
+	default:
+		waitpid( dpid, 0, 0 );
+		break;
+	}
+	exit( 3 );
+}
+#endif
+
+static int
+matches( const char *t, const char *p )
+{
+	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++;
+		}
+	}
+}
+
+static string_list_t *
+filter_boxes( string_list_t *boxes, string_list_t *patterns )
+{
+	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;
+			}
+		}
+		if (!fnot)
+			add_string_list( &nboxes, boxes->string );
+	}
+	return nboxes;
+}
+
+static void
+merge_actions( channel_conf_t *chan, int ops[], int have, int mask, int def )
+{
+	if (ops[M] & have) {
+		chan->ops[M] &= ~mask;
+		chan->ops[M] |= ops[M] & mask;
+		chan->ops[S] &= ~mask;
+		chan->ops[S] |= ops[S] & mask;
+	} else if (!(chan->ops[M] & have)) {
+		if (global_ops[M] & have) {
+			chan->ops[M] |= global_ops[M] & mask;
+			chan->ops[S] |= global_ops[S] & mask;
+		} else {
+			chan->ops[M] |= def;
+			chan->ops[S] |= def;
+		}
+	}
+}
+
+typedef struct {
+	int t[2];
+	channel_conf_t *chan;
+	driver_t *drv[2];
+	store_t *ctx[2];
+	string_list_t *boxes[2], *cboxes, *chanptr;
+	const char *names[2];
+	char **argv, *boxlist, *boxp;
+	int oind, ret, multiple, all, list, ops[2], state[2];
+	unsigned done:1, skip:1, cben:1;
+} main_vars_t;
+
+#define AUX &mvars->t[t]
+#define MVARS(aux) \
+	int t = *(int *)aux; \
+	main_vars_t *mvars = (main_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(main_vars_t, t));
+
+#define E_START  0
+#define E_OPEN   1
+#define E_SYNC   2
+
+static void sync_chans( main_vars_t *mvars, int ent );
+
+int
+main( int argc, char **argv )
+{
+	main_vars_t mvars[1];
+	group_conf_t *group;
+	char *config = 0, *opt, *ochar;
+	int cops = 0, op, 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;
+	}
+	Ontty = isatty( 1 ) && isatty( 2 );
+	arc4_init();
+
+	memset( mvars, 0, sizeof(*mvars) );
+	mvars->t[1] = 1;
+
+	for (mvars->oind = 1, ochar = 0; ; ) {
+		if (!ochar || !*ochar) {
+			if (mvars->oind >= argc)
+				break;
+			if (argv[mvars->oind][0] != '-')
+				break;
+			if (argv[mvars->oind][1] == '-') {
+				opt = argv[mvars->oind++] + 2;
+				if (!*opt)
+					break;
+				if (!strcmp( opt, "config" )) {
+					if (mvars->oind >= argc) {
+						error( "--config requires an argument.\n" );
+						return 1;
+					}
+					config = argv[mvars->oind++];
+				} else if (!memcmp( opt, "config=", 7 ))
+					config = opt + 7;
+				else if (!strcmp( opt, "all" ))
+					mvars->all = 1;
+				else if (!strcmp( opt, "list" ))
+					mvars->list = 1;
+				else if (!strcmp( opt, "help" ))
+					usage( 0 );
+				else if (!strcmp( opt, "version" ))
+					version();
+				else if (!strcmp( opt, "quiet" )) {
+					if (DFlags & QUIET)
+						DFlags |= VERYQUIET;
+					else
+						DFlags |= QUIET;
+				} else if (!strcmp( opt, "verbose" )) {
+					if (DFlags & VERBOSE)
+						DFlags |= XVERBOSE;
+					else
+						DFlags |= VERBOSE | QUIET;
+				} else if (!strcmp( opt, "debug" ))
+					DFlags |= DEBUG | QUIET;
+				else if (!strcmp( opt, "pull" ))
+					cops |= XOP_PULL, mvars->ops[M] |= XOP_HAVE_TYPE;
+				else if (!strcmp( opt, "push" ))
+					cops |= XOP_PUSH, mvars->ops[M] |= 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" ))
+						mvars->ops[M] |= op, ochar++;
+					else if (!strcmp( opt, "-slave" ))
+						mvars->ops[S] |= op, ochar++;
+					else
+						goto badopt;
+					mvars->ops[M] |= 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" ))
+					mvars->ops[M] |= XOP_HAVE_EXPUNGE;
+				else if (!strcmp( opt, "no-create" ))
+					mvars->ops[M] |= XOP_HAVE_CREATE;
+				else if (!strcmp( opt, "full" ))
+					mvars->ops[M] |= XOP_HAVE_TYPE|XOP_PULL|XOP_PUSH;
+				else if (!strcmp( opt, "noop" ))
+					mvars->ops[M] |= 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:
+						error( "Unknown option '%s'\n", argv[mvars->oind - 1] );
+						return 1;
+					}
+					switch (op & XOP_MASK_DIR) {
+					case XOP_PULL: mvars->ops[S] |= op & OP_MASK_TYPE; break;
+					case XOP_PUSH: mvars->ops[M] |= op & OP_MASK_TYPE; break;
+					default: cops |= op; break;
+					}
+					mvars->ops[M] |= XOP_HAVE_TYPE;
+				}
+				continue;
+			}
+			ochar = argv[mvars->oind++] + 1;
+			if (!*ochar) {
+				error( "Invalid option '-'\n" );
+				return 1;
+			}
+		}
+		switch (*ochar++) {
+		case 'a':
+			mvars->all = 1;
+			break;
+		case 'l':
+			mvars->list = 1;
+			break;
+		case 'c':
+			if (*ochar == 'T') {
+				ochar++;
+				pseudo = 1;
+			}
+			if (mvars->oind >= argc) {
+				error( "-c requires an argument.\n" );
+				return 1;
+			}
+			config = argv[mvars->oind++];
+			break;
+		case 'C':
+			op = OP_CREATE|XOP_HAVE_CREATE;
+		  cop:
+			if (*ochar == 'm')
+				mvars->ops[M] |= op, ochar++;
+			else if (*ochar == 's')
+				mvars->ops[S] |= op, ochar++;
+			else if (*ochar == '-')
+				ochar++;
+			else
+				cops |= op;
+			mvars->ops[M] |= 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':
+			mvars->ops[M] |= 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: mvars->ops[S] |= op & OP_MASK_TYPE; break;
+				case XOP_PUSH: mvars->ops[M] |= op & OP_MASK_TYPE; break;
+				default: cops |= op; break;
+				}
+			else
+				cops |= op;
+			mvars->ops[M] |= XOP_HAVE_TYPE;
+			break;
+		case 'L':
+			op = XOP_PULL;
+			goto cac;
+		case 'H':
+			op = XOP_PUSH;
+			goto cac;
+		case 'q':
+			if (DFlags & QUIET)
+				DFlags |= VERYQUIET;
+			else
+				DFlags |= QUIET;
+			break;
+		case 'V':
+			if (DFlags & VERBOSE)
+				DFlags |= XVERBOSE;
+			else
+				DFlags |= VERBOSE | QUIET;
+			break;
+		case 'D':
+			DFlags |= DEBUG | QUIET;
+			break;
+		case 'J':
+			DFlags |= KEEPJOURNAL;
+			break;
+		case 'v':
+			version();
+		case 'h':
+			usage( 0 );
+		default:
+			error( "Unknown option '-%c'\n", *(ochar - 1) );
+			return 1;
+		}
+	}
+
+#ifdef __linux__
+	if (DFlags & DEBUG) {
+		signal( SIGSEGV, crashHandler );
+		signal( SIGBUS, crashHandler );
+		signal( SIGILL, crashHandler );
+	}
+#endif
+
+	if (merge_ops( cops, mvars->ops ))
+		return 1;
+
+	if (load_config( config, pseudo ))
+		return 1;
+
+	if (!mvars->all && !argv[mvars->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;
+	}
+
+	mvars->chan = channels;
+	if (mvars->all)
+		mvars->multiple = channels->next != 0;
+	else if (argv[mvars->oind + 1])
+		mvars->multiple = 1;
+	else
+		for (group = groups; group; group = group->next)
+			if (!strcmp( group->name, argv[mvars->oind] )) {
+				mvars->multiple = 1;
+				break;
+			}
+	mvars->argv = argv;
+	mvars->cben = 1;
+	sync_chans( mvars, E_START );
+	return mvars->ret;
+}
+
+#define ST_FRESH     0
+#define ST_OPEN      1
+#define ST_CLOSED    2
+
+static void store_opened( store_t *ctx, void *aux );
+static void store_listed( int sts, void *aux );
+static void done_sync_dyn( int sts, void *aux );
+static void done_sync( int sts, void *aux );
+
+#define nz(a,b) ((a)?(a):(b))
+
+static void
+sync_chans( main_vars_t *mvars, int ent )
+{
+	group_conf_t *group;
+	channel_conf_t *chan;
+	store_t *store;
+	string_list_t *mbox, *sbox, **mboxp, **sboxp;
+	char *channame;
+	int t;
+
+	if (!mvars->cben)
+		return;
+	switch (ent) {
+	case E_OPEN: goto opened;
+	case E_SYNC: goto syncone;
+	}
+	for (;;) {
+		mvars->boxlist = 0;
+		if (!mvars->all) {
+			if (mvars->chanptr)
+				channame = mvars->chanptr->string;
+			else {
+				for (group = groups; group; group = group->next)
+					if (!strcmp( group->name, mvars->argv[mvars->oind] )) {
+						mvars->chanptr = group->channels;
+						channame = mvars->chanptr->string;
+						goto gotgrp;
+					}
+				channame = mvars->argv[mvars->oind];
+			  gotgrp: ;
+			}
+			if ((mvars->boxlist = strchr( channame, ':' )))
+				*mvars->boxlist++ = 0;
+			for (chan = channels; chan; chan = chan->next)
+				if (!strcmp( chan->name, channame ))
+					goto gotchan;
+			error( "No channel or group named '%s' defined.\n", channame );
+			mvars->ret = 1;
+			goto gotnone;
+		  gotchan:
+			mvars->chan = chan;
+		}
+		merge_actions( mvars->chan, mvars->ops, XOP_HAVE_TYPE, OP_MASK_TYPE, OP_MASK_TYPE );
+		merge_actions( mvars->chan, mvars->ops, XOP_HAVE_CREATE, OP_CREATE, 0 );
+		merge_actions( mvars->chan, mvars->ops, XOP_HAVE_EXPUNGE, OP_EXPUNGE, 0 );
+
+		mvars->state[M] = mvars->state[S] = ST_FRESH;
+		info( "Channel %s\n", mvars->chan->name );
+		mvars->boxes[M] = mvars->boxes[S] = mvars->cboxes = 0;
+		mvars->skip = mvars->cben = 0;
+		for (t = 0; t < 2; t++) {
+			mvars->drv[t] = mvars->chan->stores[t]->driver;
+			if ((store = mvars->drv[t]->own_store( mvars->chan->stores[t] )))
+				store_opened( store, AUX );
+		}
+		for (t = 0; t < 2 && !mvars->skip; t++)
+			if (mvars->state[t] == ST_FRESH) {
+				info( "Opening %s %s...\n", str_ms[t], mvars->chan->stores[t]->name );
+				mvars->drv[t]->open_store( mvars->chan->stores[t], store_opened, AUX );
+			}
+		mvars->cben = 1;
+	  opened:
+		if (mvars->skip)
+			goto next;
+		if (mvars->state[M] != ST_OPEN || mvars->state[S] != ST_OPEN)
+			return;
+
+		if (mvars->boxlist)
+			mvars->boxp = mvars->boxlist;
+		else if (mvars->chan->patterns) {
+			mvars->boxes[M] = filter_boxes( mvars->ctx[M]->boxes, mvars->chan->patterns );
+			mvars->boxes[S] = filter_boxes( mvars->ctx[S]->boxes, mvars->chan->patterns );
+			for (mboxp = &mvars->boxes[M]; (mbox = *mboxp); ) {
+				for (sboxp = &mvars->boxes[S]; (sbox = *sboxp); sboxp = &sbox->next)
+					if (!strcmp( sbox->string, mbox->string )) {
+						*sboxp = sbox->next;
+						free( sbox );
+						*mboxp = mbox->next;
+						mbox->next = mvars->cboxes;
+						mvars->cboxes = mbox;
+						goto gotdupe;
+					}
+				mboxp = &mbox->next;
+			  gotdupe: ;
+			}
+		}
+
+		if (mvars->list && mvars->multiple)
+			printf( "%s:\n", mvars->chan->name );
+	  syncml:
+		mvars->done = mvars->cben = 0;
+	  syncmlx:
+		if (mvars->boxlist) {
+			if ((mvars->names[S] = strsep( &mvars->boxp, ",\n" ))) {
+				if (!*mvars->names[S])
+					mvars->names[S] = 0;
+				if (!mvars->list) {
+					mvars->names[M] = mvars->names[S];
+					sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync, mvars );
+					goto syncw;
+				}
+				puts( nz( mvars->names[S], "INBOX" ) );
+				goto syncmlx;
+			}
+		} else if (mvars->chan->patterns) {
+			if ((mbox = mvars->cboxes)) {
+				mvars->cboxes = mbox->next;
+				if (!mvars->list) {
+					mvars->names[M] = mvars->names[S] = mbox->string;
+					sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars );
+					goto syncw;
+				}
+				puts( mbox->string );
+				free( mbox );
+				goto syncmlx;
+			}
+			for (t = 0; t < 2; t++)
+				if ((mbox = mvars->boxes[t])) {
+					mvars->boxes[t] = mbox->next;
+					if ((mvars->chan->ops[1-t] & OP_MASK_TYPE) && (mvars->chan->ops[1-t] & OP_CREATE)) {
+						if (!mvars->list) {
+							mvars->names[M] = mvars->names[S] = mbox->string;
+							sync_boxes( mvars->ctx, mvars->names, mvars->chan, done_sync_dyn, mvars );
+							goto syncw;
+						}
+						puts( mbox->string );
+					}
+					free( mbox );
+					goto syncmlx;
+				}
+		} else {
+			if (!mvars->list) {
+				sync_boxes( mvars->ctx, mvars->chan->boxes, mvars->chan, done_sync, mvars );
+				mvars->skip = 1;
+			  syncw:
+				mvars->cben = 1;
+				if (!mvars->done)
+					return;
+			  syncone:
+				if (!mvars->skip)
+					goto syncml;
+			} else
+				printf( "%s <=> %s\n", nz( mvars->chan->boxes[M], "INBOX" ), nz( mvars->chan->boxes[S], "INBOX" ) );
+		}
+
+	  next:
+		for (t = 0; t < 2; t++)
+			if (mvars->state[t] == ST_OPEN) {
+				mvars->drv[t]->disown_store( mvars->ctx[t] );
+				mvars->state[t] = ST_CLOSED;
+			}
+		if (mvars->state[M] != ST_CLOSED || mvars->state[S] != ST_CLOSED) {
+			mvars->skip = mvars->cben = 1;
+			return;
+		}
+		free_string_list( mvars->cboxes );
+		free_string_list( mvars->boxes[M] );
+		free_string_list( mvars->boxes[S] );
+		if (mvars->all) {
+			if (!(mvars->chan = mvars->chan->next))
+				break;
+		} else {
+			if (mvars->chanptr && (mvars->chanptr = mvars->chanptr->next))
+				continue;
+		  gotnone:
+			if (!mvars->argv[++mvars->oind])
+				break;
+		}
+	}
+	for (t = 0; t < N_DRIVERS; t++)
+		drivers[t]->cleanup();
+}
+
+static void
+store_opened( store_t *ctx, void *aux )
+{
+	MVARS(aux)
+
+	if (!ctx) {
+		mvars->state[t] = ST_CLOSED;
+		mvars->ret = mvars->skip = 1;
+		return;
+	}
+	mvars->ctx[t] = ctx;
+	if (mvars->skip) {
+		mvars->state[t] = ST_OPEN;
+		sync_chans( mvars, E_OPEN );
+		return;
+	}
+	if (!mvars->boxlist && mvars->chan->patterns && !ctx->listed)
+		mvars->drv[t]->list( ctx, store_listed, AUX );
+	else {
+		mvars->state[t] = ST_OPEN;
+		sync_chans( mvars, E_OPEN );
+	}
+}
+
+static void
+store_listed( int sts, void *aux )
+{
+	MVARS(aux)
+
+	mvars->state[t] = ST_OPEN;
+	switch (sts) {
+	case DRV_OK:
+		if (mvars->ctx[t]->conf->map_inbox)
+			add_string_list( &mvars->ctx[t]->boxes, mvars->ctx[t]->conf->map_inbox );
+		break;
+	case DRV_STORE_BAD:
+		mvars->drv[t]->cancel_store( mvars->ctx[t] );
+		mvars->state[t] = ST_CLOSED;
+	default:
+		mvars->ret = mvars->skip = 1;
+		break;
+	}
+	sync_chans( mvars, E_OPEN );
+}
+
+static void
+done_sync_dyn( int sts, void *aux )
+{
+	main_vars_t *mvars = (main_vars_t *)aux;
+
+	free( ((char *)mvars->names[S]) - offsetof(string_list_t, string) );
+	done_sync( sts, aux );
+}
+
+static void
+done_sync( int sts, void *aux )
+{
+	main_vars_t *mvars = (main_vars_t *)aux;
+
+	mvars->done = 1;
+	if (sts) {
+		mvars->ret = 1;
+		if (sts & (SYNC_BAD(M) | SYNC_BAD(S))) {
+			mvars->skip = 1;
+			if (sts & SYNC_BAD(M))
+				mvars->state[M] = ST_CLOSED;
+			if (sts & SYNC_BAD(S))
+				mvars->state[S] = ST_CLOSED;
+		}
+	}
+	sync_chans( mvars, E_SYNC );
+}
diff --git a/isync/sync.c b/isync/sync.c
new file mode 100644
index 0000000..8c2f6b5
--- /dev/null
+++ b/isync/sync.c
@@ -0,0 +1,1699 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+const char *str_ms[] = { "master", "slave" }, *str_hl[] = { "push", "pull" };
+
+void
+Fclose( FILE *f )
+{
+	if (fclose( f ) == EOF) {
+		perror( "cannot close file" );
+		exit( 1 );
+	}
+}
+
+void
+Fprintf( FILE *f, const char *msg, ... )
+{
+	int r;
+	va_list va;
+
+	va_start( va, msg );
+	r = vfprintf( f, msg, va );
+	va_end( va );
+	if (r < 0) {
+		perror( "cannot write file" );
+		exit( 1 );
+	}
+}
+
+
+static const char Flags[] = { 'D', 'F', 'R', 'S', 'T' };
+
+static int
+parse_flags( const char *buf )
+{
+	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
+make_flags( int flags, char *buf )
+{
+	unsigned i, d;
+
+	for (i = d = 0; i < as(Flags); i++)
+		if (flags & (1 << i))
+			buf[d++] = Flags[i];
+	buf[d] = 0;
+	return d;
+}
+
+
+#define S_DEAD         (1<<0)
+#define S_DONE         (1<<1)
+#define S_DEL(ms)      (1<<(2+(ms)))
+#define S_EXPIRED      (1<<4)
+#define S_EXPIRE       (1<<5)
+#define S_NEXPIRE      (1<<6)
+#define S_EXP_S        (1<<7)
+
+#define mvBit(in,ib,ob) ((unsigned char)(((unsigned)in) * (ob) / (ib)))
+
+typedef struct sync_rec {
+	struct sync_rec *next;
+	/* string_list_t *keywords; */
+	int uid[2];
+	message_t *msg[2];
+	unsigned char status, flags, aflags[2], dflags[2];
+	char tuid[TUIDL];
+} sync_rec_t;
+
+
+/* cases:
+   a) both non-null
+   b) only master null
+   b.1) uid[M] 0
+   b.2) uid[M] -1
+   b.3) master not scanned
+   b.4) master gone
+   c) only slave null
+   c.1) uid[S] 0
+   c.2) uid[S] -1
+   c.3) slave not scanned
+   c.4) slave gone
+   d) both null
+   d.1) both gone
+   d.2) uid[M] 0, slave not scanned
+   d.3) uid[M] -1, slave not scanned
+   d.4) master gone, slave not scanned
+   d.5) uid[M] 0, slave gone
+   d.6) uid[M] -1, slave gone
+   d.7) uid[S] 0, master not scanned
+   d.8) uid[S] -1, master not scanned
+   d.9) slave gone, master not scanned
+   d.10) uid[S] 0, master gone
+   d.11) uid[S] -1, master gone
+   impossible cases: both uid[M] & uid[S] 0 or -1, both not scanned
+*/
+
+typedef struct {
+	int t[2];
+	void (*cb)( int sts, void *aux ), *aux;
+	char *dname, *jname, *nname, *lname;
+	FILE *jfp, *nfp;
+	sync_rec_t *srecs, **srecadd, **osrecadd;
+	channel_conf_t *chan;
+	store_t *ctx[2];
+	driver_t *drv[2];
+	int state[2], ret;
+	int find_old_total[2], find_old_done[2];
+	int new_total[2], new_done[2];
+	int find_new_total[2], find_new_done[2];
+	int flags_total[2], flags_done[2];
+	int trash_total[2], trash_done[2];
+	int maxuid[2], uidval[2], smaxxuid, lfd;
+	unsigned find:1;
+} sync_vars_t;
+
+#define AUX &svars->t[t]
+#define SVARS(aux) \
+	int t = *(int *)aux; \
+	sync_vars_t *svars = (sync_vars_t *)(((char *)(&((int *)aux)[-t])) - offsetof(sync_vars_t, t));
+
+/* operation dependencies:
+   select(S): -
+   find_old(S): select(S)
+   select(M): find_old(S) | -
+   find_old(M): select(M)
+   new(M), new(S), flags(M): find_old(M) & find_old(S)
+   flags(S): count(new(S))
+   find_new(x): new(x)
+   trash(x): flags(x)
+   close(x): trash(x) & find_new(x) // with expunge
+   cleanup: close(M) & close(S)
+*/
+
+#define ST_SENT_FIND_OLD   (1<<0)
+#define ST_SENT_NEW        (1<<1)
+#define ST_SENT_FIND_NEW   (1<<2)
+#define ST_SENT_FLAGS      (1<<3)
+#define ST_SENT_TRASH      (1<<4)
+#define ST_CLOSED          (1<<5)
+#define ST_CANCELED        (1<<6)
+
+#define ST_DID_EXPUNGE     (1<<16)
+
+
+typedef struct copy_vars {
+	int (*cb)( int sts, int uid, struct copy_vars *vars );
+	void *aux;
+	sync_rec_t *srec; /* also ->tuid */
+	message_t *msg;
+	msg_data_t data;
+} copy_vars_t;
+
+static int msg_fetched( int sts, void *aux );
+
+static int
+copy_msg( copy_vars_t *vars )
+{
+	SVARS(vars->aux)
+
+	vars->data.flags = vars->msg->flags;
+	return svars->drv[1-t]->fetch_msg( svars->ctx[1-t], vars->msg, &vars->data, msg_fetched, vars );
+}
+
+static int msg_stored( int sts, int uid, void *aux );
+
+static int
+msg_fetched( int sts, void *aux )
+{
+	copy_vars_t *vars = (copy_vars_t *)aux;
+	SVARS(vars->aux)
+	char *fmap, *buf;
+	int i, len, extra, scr, tcr, lcrs, crs, lines;
+	int start, sbreak = 0, ebreak = 0;
+	char c;
+
+	switch (sts) {
+	case DRV_OK:
+		vars->msg->flags = vars->data.flags;
+
+		scr = (svars->drv[1-t]->flags / DRV_CRLF) & 1;
+		tcr = (svars->drv[t]->flags / DRV_CRLF) & 1;
+		if (vars->srec || scr != tcr) {
+			fmap = vars->data.data;
+			len = vars->data.len;
+			extra = lines = crs = i = 0;
+			if (vars->srec) {
+			  nloop:
+				start = i;
+				lcrs = 0;
+				while (i < len) {
+					c = fmap[i++];
+					if (c == '\r')
+						lcrs++;
+					else if (c == '\n') {
+						if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
+							extra = (sbreak = start) - (ebreak = i);
+							goto oke;
+						}
+						lines++;
+						crs += lcrs;
+						if (i - lcrs - 1 == start) {
+							sbreak = ebreak = start;
+							goto oke;
+						}
+						goto nloop;
+					}
+				}
+				/* invalid message */
+				warn( "Warning: message %d from %s has incomplete header.\n",
+				      vars->msg->uid, str_ms[1-t] );
+				free( fmap );
+				return vars->cb( SYNC_NOGOOD, 0, vars );
+			  oke:
+				extra += 8 + TUIDL + 1 + (tcr && crs);
+			}
+			if (tcr != scr) {
+				for (; i < len; i++) {
+					c = fmap[i];
+					if (c == '\r')
+						crs++;
+					else if (c == '\n')
+						lines++;
+				}
+				extra -= crs;
+				if (tcr)
+					extra += lines;
+			}
+
+			vars->data.len = len + extra;
+			buf = vars->data.data = nfmalloc( vars->data.len );
+			i = 0;
+			if (vars->srec) {
+				if (tcr != scr) {
+					if (tcr) {
+						for (; i < sbreak; i++)
+							if ((c = fmap[i]) != '\r') {
+								if (c == '\n')
+									*buf++ = '\r';
+								*buf++ = c;
+							}
+					} else {
+						for (; i < sbreak; i++)
+							if ((c = fmap[i]) != '\r')
+								*buf++ = c;
+					}
+				} else {
+					memcpy( buf, fmap, sbreak );
+					buf += sbreak;
+				}
+
+				memcpy( buf, "X-TUID: ", 8 );
+				buf += 8;
+				memcpy( buf, vars->srec->tuid, TUIDL );
+				buf += TUIDL;
+				if (tcr && crs)
+					*buf++ = '\r';
+				*buf++ = '\n';
+				i = ebreak;
+			}
+			if (tcr != scr) {
+				if (tcr) {
+					for (; i < len; i++)
+						if ((c = fmap[i]) != '\r') {
+							if (c == '\n')
+								*buf++ = '\r';
+							*buf++ = c;
+						}
+				} else {
+					for (; i < len; i++)
+						if ((c = fmap[i]) != '\r')
+							*buf++ = c;
+				}
+			} else
+				memcpy( buf, fmap + i, len - i );
+
+			free( fmap );
+		}
+
+		return svars->drv[t]->store_msg( svars->ctx[t], &vars->data, !vars->srec, msg_stored, vars );
+	case DRV_CANCELED:
+		return vars->cb( SYNC_CANCELED, 0, vars );
+	case DRV_MSG_BAD:
+		return vars->cb( SYNC_NOGOOD, 0, vars );
+	case DRV_STORE_BAD:
+		return vars->cb( SYNC_BAD(1-t), 0, vars );
+	default:
+		return vars->cb( SYNC_FAIL, 0, vars );
+	}
+}
+
+static int
+msg_stored( int sts, int uid, void *aux )
+{
+	copy_vars_t *vars = (copy_vars_t *)aux;
+	SVARS(vars->aux)
+
+	(void)svars;
+	switch (sts) {
+	case DRV_OK:
+		return vars->cb( SYNC_OK, uid, vars );
+	case DRV_CANCELED:
+		return vars->cb( SYNC_CANCELED, 0, vars );
+	case DRV_MSG_BAD:
+		warn( "Warning: %s refuses to store message %d from %s.\n",
+		      str_ms[t], vars->msg->uid, str_ms[1-t] );
+		return vars->cb( SYNC_NOGOOD, 0, vars );
+	case DRV_STORE_BAD:
+		return vars->cb( SYNC_BAD(t), 0, vars );
+	default:
+		return vars->cb( SYNC_FAIL, 0, vars );
+	}
+}
+
+
+static void
+stats( sync_vars_t *svars )
+{
+	char buf[2][64];
+	char *cs;
+	int t, l;
+	static int cols = -1;
+
+	if (cols < 0 && (!(cs = getenv( "COLUMNS" )) || !(cols = atoi( cs ) / 2)))
+		cols = 36;
+	if (!(DFlags & QUIET)) {
+		for (t = 0; t < 2; t++) {
+			l = sprintf( buf[t], "?%d/%d +%d/%d *%d/%d #%d/%d",
+			             svars->find_old_done[t] + svars->find_new_done[t],
+			             svars->find_old_total[t] + svars->find_new_total[t],
+			             svars->new_done[t], svars->new_total[t],
+			             svars->flags_done[t], svars->flags_total[t],
+			             svars->trash_done[t], svars->trash_total[t] );
+			if (l > cols)
+				buf[t][cols - 1] = '~';
+		}
+		infon( "\rM: %.*s  S: %.*s", cols, buf[0], cols, buf[1] );
+	}
+}
+
+
+static void sync_bail( sync_vars_t *svars );
+static void sync_bail1( sync_vars_t *svars );
+static void sync_bail2( sync_vars_t *svars );
+static void cancel_done( int sts, void *aux );
+
+static void
+cancel_sync( sync_vars_t *svars )
+{
+	int t;
+
+	/* the 1st round is guaranteed not to trash svars */
+	for (t = 0; t < 2; t++)
+		if (svars->ret & SYNC_BAD(t))
+			cancel_done( DRV_STORE_BAD, AUX );
+		else
+			svars->drv[t]->cancel( svars->ctx[t], cancel_done, AUX );
+}
+
+static void
+cancel_done( int sts, void *aux )
+{
+	SVARS(aux)
+
+	if (sts != DRV_OK) {
+		svars->ret |= SYNC_BAD(t);
+		svars->drv[t]->cancel_store( svars->ctx[t] );
+	}
+	svars->state[t] |= ST_CANCELED;
+	if (svars->state[1-t] & ST_CANCELED) {
+		Fclose( svars->nfp );
+		Fclose( svars->jfp );
+		sync_bail( svars );
+	}
+}
+
+
+static int
+check_ret( int sts, sync_vars_t *svars, int t )
+{
+	switch (sts) {
+	case DRV_CANCELED:
+		return 1;
+	case DRV_STORE_BAD:
+		svars->ret |= SYNC_BAD(t);
+		cancel_sync( svars );
+		return 1;
+	case DRV_BOX_BAD:
+		svars->ret |= SYNC_FAIL;
+		cancel_sync( svars );
+		return 1;
+	}
+	return 0;
+}
+
+static int
+check_ret_aux( int sts, sync_vars_t *svars, int t, void *aux )
+{
+	if (!check_ret( sts, svars, t ))
+		return 0;
+	free( aux );
+	return 1;
+}
+
+
+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;
+}
+
+
+#define JOURNAL_VERSION "2"
+
+static int select_box( sync_vars_t *svars, int t, int minwuid, int *mexcs, int nmexcs );
+
+void
+sync_boxes( store_t *ctx[], const char *names[], channel_conf_t *chan,
+            void (*cb)( int sts, void *aux ), void *aux )
+{
+	sync_vars_t *svars;
+	sync_rec_t *srec, *nsrec;
+	char *s, *cmname, *csname;
+	FILE *jfp;
+	int opts[2], line, t1, t2, t3, t;
+	struct stat st;
+	struct flock lck;
+	char fbuf[16]; /* enlarge when support for keywords is added */
+	char buf[64];
+
+	svars = nfcalloc( sizeof(*svars) );
+	svars->t[1] = 1;
+	svars->cb = cb;
+	svars->aux = aux;
+	svars->ctx[0] = ctx[0];
+	svars->ctx[1] = ctx[1];
+	svars->chan = chan;
+	svars->uidval[0] = svars->uidval[1] = -1;
+	svars->srecadd = &svars->srecs;
+
+	for (t = 0; t < 2; t++) {
+		ctx[t]->name =
+			(!names[t] || (ctx[t]->conf->map_inbox && !strcmp( ctx[t]->conf->map_inbox, names[t] ))) ?
+				"INBOX" : names[t];
+		ctx[t]->uidvalidity = -1;
+		svars->drv[t] = ctx[t]->conf->driver;
+		svars->drv[t]->prepare_paths( ctx[t] );
+	}
+
+	if (!strcmp( chan->sync_state ? chan->sync_state : global_sync_state, "*" )) {
+		if (!ctx[S]->path) {
+			error( "Error: store '%s' does not support in-box sync state\n", chan->stores[S]->name );
+			free( svars );
+			cb( SYNC_BAD(S), aux );
+			return;
+		}
+		nfasprintf( &svars->dname, "%s/." EXE "state", ctx[S]->path );
+	} else {
+		csname = clean_strdup( ctx[S]->name );
+		if (chan->sync_state)
+			nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname );
+		else {
+			cmname = clean_strdup( ctx[M]->name );
+			nfasprintf( &svars->dname, "%s:%s:%s_:%s:%s", global_sync_state,
+			            chan->stores[M]->name, cmname, chan->stores[S]->name, csname );
+			free( cmname );
+		}
+		free( csname );
+	}
+	if (!(s = strrchr( svars->dname, '/' ))) {
+		error( "Error: invalid SyncState '%s'\n", svars->dname );
+		free( svars->dname );
+		free( svars );
+		cb( SYNC_BAD(S), aux );
+		return;
+	}
+	*s = 0;
+	if (mkdir( svars->dname, 0700 ) && errno != EEXIST) {
+		error( "Error: cannot create SyncState directory '%s': %s\n", svars->dname, strerror(errno) );
+		free( svars->dname );
+		free( svars );
+		cb( SYNC_BAD(S), aux );
+		return;
+	}
+	*s = '/';
+	nfasprintf( &svars->jname, "%s.journal", svars->dname );
+	nfasprintf( &svars->nname, "%s.new", svars->dname );
+	nfasprintf( &svars->lname, "%s.lock", svars->dname );
+	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 ((svars->lfd = open( svars->lname, O_WRONLY|O_CREAT, 0666 )) < 0) {
+		error( "Error: cannot create lock file %s: %s\n", svars->lname, strerror(errno) );
+		svars->ret = SYNC_FAIL;
+		sync_bail2( svars );
+		return;
+	}
+	if (fcntl( svars->lfd, F_SETLK, &lck )) {
+		error( "Error: channel :%s:%s-:%s:%s is locked\n",
+		         chan->stores[M]->name, ctx[M]->name, chan->stores[S]->name, ctx[S]->name );
+		svars->ret = SYNC_FAIL;
+		sync_bail1( svars );
+		return;
+	}
+	if ((jfp = fopen( svars->dname, "r" ))) {
+		debug( "reading sync state %s ...\n", svars->dname );
+		if (!fgets( buf, sizeof(buf), jfp ) || !(t = strlen( buf )) || buf[t - 1] != '\n') {
+			error( "Error: incomplete sync state header in %s\n", svars->dname );
+			fclose( jfp );
+			svars->ret = SYNC_FAIL;
+			sync_bail( svars );
+			return;
+		}
+		if (sscanf( buf, "%d:%d %d:%d:%d", &svars->uidval[M], &svars->maxuid[M], &svars->uidval[S], &svars->smaxxuid, &svars->maxuid[S]) != 5) {
+			error( "Error: invalid sync state header in %s\n", svars->dname );
+			fclose( jfp );
+			svars->ret = SYNC_FAIL;
+			sync_bail( svars );
+			return;
+		}
+		line = 1;
+		while (fgets( buf, sizeof(buf), jfp )) {
+			line++;
+			if (!(t = strlen( buf )) || buf[t - 1] != '\n') {
+				error( "Error: incomplete sync state entry at %s:%d\n", svars->dname, line );
+				fclose( jfp );
+				svars->ret = SYNC_FAIL;
+				sync_bail( svars );
+				return;
+			}
+			fbuf[0] = 0;
+			if (sscanf( buf, "%d %d %15s", &t1, &t2, fbuf ) < 2) {
+				error( "Error: invalid sync state entry at %s:%d\n", svars->dname, line );
+				fclose( jfp );
+				svars->ret = SYNC_FAIL;
+				sync_bail( svars );
+				return;
+			}
+			srec = nfmalloc( sizeof(*srec) );
+			srec->uid[M] = t1;
+			srec->uid[S] = t2;
+			s = fbuf;
+			if (*s == 'X') {
+				s++;
+				srec->status = S_EXPIRE | S_EXPIRED;
+			} else
+				srec->status = 0;
+			srec->flags = parse_flags( s );
+			debug( "  entry (%d,%d,%u,%s)\n", srec->uid[M], srec->uid[S], srec->flags, srec->status & S_EXPIRED ? "X" : "" );
+			srec->msg[M] = srec->msg[S] = 0;
+			srec->tuid[0] = 0;
+			srec->next = 0;
+			*svars->srecadd = srec;
+			svars->srecadd = &srec->next;
+		}
+		fclose( jfp );
+	} else {
+		if (errno != ENOENT) {
+			error( "Error: cannot read sync state %s\n", svars->dname );
+			svars->ret = SYNC_FAIL;
+			sync_bail( svars );
+			return;
+		}
+	}
+	line = 0;
+	if ((jfp = fopen( svars->jname, "r" ))) {
+		if (!stat( svars->nname, &st ) && fgets( buf, sizeof(buf), jfp )) {
+			debug( "recovering journal ...\n" );
+			if (!(t = strlen( buf )) || buf[t - 1] != '\n') {
+				error( "Error: incomplete journal header in %s\n", svars->jname );
+				fclose( jfp );
+				svars->ret = SYNC_FAIL;
+				sync_bail( svars );
+				return;
+			}
+			if (memcmp( buf, JOURNAL_VERSION "\n", strlen(JOURNAL_VERSION) + 1 )) {
+				error( "Error: incompatible journal version "
+				                 "(got %.*s, expected " JOURNAL_VERSION ")\n", t - 1, buf );
+				fclose( jfp );
+				svars->ret = SYNC_FAIL;
+				sync_bail( svars );
+				return;
+			}
+			srec = 0;
+			line = 1;
+			while (fgets( buf, sizeof(buf), jfp )) {
+				line++;
+				if (!(t = strlen( buf )) || buf[t - 1] != '\n') {
+					error( "Error: incomplete journal entry at %s:%d\n", svars->jname, line );
+					fclose( jfp );
+					svars->ret = SYNC_FAIL;
+					sync_bail( svars );
+					return;
+				}
+				if (buf[0] == '#' ?
+				      (t3 = 0, (sscanf( buf + 2, "%d %d %n", &t1, &t2, &t3 ) < 2) || !t3 || (t - t3 != TUIDL + 3)) :
+				      buf[0] == '(' || buf[0] == ')' ?
+				        (sscanf( buf + 2, "%d", &t1 ) != 1) :
+				        buf[0] == '+' || buf[0] == '&' || buf[0] == '-' || buf[0] == '|' || buf[0] == '/' || buf[0] == '\\' ?
+				          (sscanf( buf + 2, "%d %d", &t1, &t2 ) != 2) :
+				          (sscanf( buf + 2, "%d %d %d", &t1, &t2, &t3 ) != 3))
+				{
+					error( "Error: malformed journal entry at %s:%d\n", svars->jname, line );
+					fclose( jfp );
+					svars->ret = SYNC_FAIL;
+					sync_bail( svars );
+					return;
+				}
+				if (buf[0] == '(')
+					svars->maxuid[M] = t1;
+				else if (buf[0] == ')')
+					svars->maxuid[S] = t1;
+				else if (buf[0] == '|') {
+					svars->uidval[M] = t1;
+					svars->uidval[S] = t2;
+				} else if (buf[0] == '+') {
+					srec = nfmalloc( sizeof(*srec) );
+					srec->uid[M] = t1;
+					srec->uid[S] = t2;
+					debug( "  new entry(%d,%d)\n", t1, t2 );
+					srec->msg[M] = srec->msg[S] = 0;
+					srec->status = 0;
+					srec->flags = 0;
+					srec->tuid[0] = 0;
+					srec->next = 0;
+					*svars->srecadd = srec;
+					svars->srecadd = &srec->next;
+				} else {
+					for (nsrec = srec; srec; srec = srec->next)
+						if (srec->uid[M] == t1 && srec->uid[S] == t2)
+							goto syncfnd;
+					for (srec = svars->srecs; srec != nsrec; srec = srec->next)
+						if (srec->uid[M] == t1 && srec->uid[S] == t2)
+							goto syncfnd;
+					error( "Error: journal entry at %s:%d refers to non-existing sync state entry\n", svars->jname, line );
+					fclose( jfp );
+					svars->ret = SYNC_FAIL;
+					sync_bail( svars );
+					return;
+				  syncfnd:
+					debugn( "  entry(%d,%d,%u) ", srec->uid[M], srec->uid[S], srec->flags );
+					switch (buf[0]) {
+					case '-':
+						debug( "killed\n" );
+						srec->status = S_DEAD;
+						break;
+					case '#':
+						debug( "TUID now %." stringify(TUIDL) "s\n", buf + t3 + 2 );
+						memcpy( srec->tuid, buf + t3 + 2, TUIDL );
+						break;
+					case '&':
+						debug( "TUID %." stringify(TUIDL) "s lost\n", srec->tuid );
+						srec->flags = 0;
+						srec->tuid[0] = 0;
+						break;
+					case '<':
+						debug( "master now %d\n", t3 );
+						srec->uid[M] = t3;
+						srec->tuid[0] = 0;
+						break;
+					case '>':
+						debug( "slave now %d\n", t3 );
+						srec->uid[S] = t3;
+						srec->tuid[0] = 0;
+						break;
+					case '*':
+						debug( "flags now %d\n", t3 );
+						srec->flags = t3;
+						break;
+					case '~':
+						debug( "expire now %d\n", t3 );
+						if (t3)
+							srec->status |= S_EXPIRE;
+						else
+							srec->status &= ~S_EXPIRE;
+						break;
+					case '\\':
+						t3 = (srec->status & S_EXPIRED);
+						debug( "expire back to %d\n", t3 / S_EXPIRED );
+						if (t3)
+							srec->status |= S_EXPIRE;
+						else
+							srec->status &= ~S_EXPIRE;
+						break;
+					case '/':
+						t3 = (srec->status & S_EXPIRE);
+						debug( "expired now %d\n", t3 / S_EXPIRE );
+						if (t3) {
+							if (svars->smaxxuid < srec->uid[S])
+								svars->smaxxuid = srec->uid[S];
+							srec->status |= S_EXPIRED;
+						} else
+							srec->status &= ~S_EXPIRED;
+						break;
+					default:
+						error( "Error: unrecognized journal entry at %s:%d\n", svars->jname, line );
+						fclose( jfp );
+						svars->ret = SYNC_FAIL;
+						sync_bail( svars );
+						return;
+					}
+				}
+			}
+		}
+		fclose( jfp );
+	} else {
+		if (errno != ENOENT) {
+			error( "Error: cannot read journal %s\n", svars->jname );
+			svars->ret = SYNC_FAIL;
+			sync_bail( svars );
+			return;
+		}
+	}
+	if (!(svars->nfp = fopen( svars->nname, "w" ))) {
+		error( "Error: cannot write new sync state %s\n", svars->nname );
+		svars->ret = SYNC_FAIL;
+		sync_bail( svars );
+		return;
+	}
+	if (!(svars->jfp = fopen( svars->jname, "a" ))) {
+		error( "Error: cannot write journal %s\n", svars->jname );
+		fclose( svars->nfp );
+		svars->ret = SYNC_FAIL;
+		sync_bail( svars );
+		return;
+	}
+	setlinebuf( svars->jfp );
+	if (!line)
+		Fprintf( svars->jfp, JOURNAL_VERSION "\n" );
+
+	opts[M] = opts[S] = 0;
+	for (t = 0; t < 2; t++) {
+		if (chan->ops[t] & (OP_DELETE|OP_FLAGS)) {
+			opts[t] |= OPEN_SETFLAGS;
+			opts[1-t] |= OPEN_OLD;
+			if (chan->ops[t] & OP_FLAGS)
+				opts[1-t] |= OPEN_FLAGS;
+		}
+		if (chan->ops[t] & (OP_NEW|OP_RENEW)) {
+			opts[t] |= OPEN_APPEND;
+			if (chan->ops[t] & OP_RENEW)
+				opts[1-t] |= OPEN_OLD;
+			if (chan->ops[t] & OP_NEW)
+				opts[1-t] |= OPEN_NEW;
+			if (chan->ops[t] & OP_EXPUNGE)
+				opts[1-t] |= OPEN_FLAGS;
+			if (chan->stores[t]->max_size)
+				opts[1-t] |= OPEN_SIZE;
+		}
+		if (chan->ops[t] & OP_EXPUNGE) {
+			opts[t] |= OPEN_EXPUNGE;
+			if (chan->stores[t]->trash) {
+				if (!chan->stores[t]->trash_only_new)
+					opts[t] |= OPEN_OLD;
+				opts[t] |= OPEN_NEW|OPEN_FLAGS;
+			} else if (chan->stores[1-t]->trash && chan->stores[1-t]->trash_remote_new)
+				opts[t] |= OPEN_NEW|OPEN_FLAGS;
+		}
+		if (chan->ops[t] & OP_CREATE)
+			opts[t] |= OPEN_CREATE;
+	}
+	if ((chan->ops[S] & (OP_NEW|OP_RENEW)) && chan->max_messages)
+		opts[S] |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS;
+	if (line)
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if ((mvBit(srec->status, S_EXPIRE, S_EXPIRED) ^ srec->status) & S_EXPIRED)
+				opts[S] |= OPEN_OLD|OPEN_FLAGS;
+			if (srec->tuid[0]) {
+				if (srec->uid[M] == -2)
+					opts[M] |= OPEN_OLD|OPEN_FIND;
+				else if (srec->uid[S] == -2)
+					opts[S] |= OPEN_OLD|OPEN_FIND;
+			}
+		}
+	svars->drv[M]->prepare_opts( ctx[M], opts[M] );
+	svars->drv[S]->prepare_opts( ctx[S], opts[S] );
+
+	svars->find = line != 0;
+	if (!svars->smaxxuid && select_box( svars, M, (ctx[M]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 ))
+		return;
+	select_box( svars, S, (ctx[S]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 );
+}
+
+static int box_selected( int sts, void *aux );
+
+static int
+select_box( sync_vars_t *svars, int t, int minwuid, int *mexcs, int nmexcs )
+{
+	sync_rec_t *srec;
+	int maxwuid;
+
+	if (svars->ctx[t]->opts & OPEN_NEW) {
+		if (minwuid > svars->maxuid[t] + 1)
+			minwuid = svars->maxuid[t] + 1;
+		maxwuid = INT_MAX;
+	} else if (svars->ctx[t]->opts & OPEN_OLD) {
+		maxwuid = 0;
+		for (srec = svars->srecs; srec; srec = srec->next)
+			if (!(srec->status & S_DEAD) && srec->uid[t] > maxwuid)
+				maxwuid = srec->uid[t];
+	} else
+		maxwuid = 0;
+	info( "Selecting %s %s...\n", str_ms[t], svars->ctx[t]->name );
+	debug( maxwuid == INT_MAX ? "selecting %s [%d,inf]\n" : "selecting %s [%d,%d]\n", str_ms[t], minwuid, maxwuid );
+	return svars->drv[t]->select( svars->ctx[t], minwuid, maxwuid, mexcs, nmexcs, box_selected, AUX );
+}
+
+typedef struct {
+	void *aux;
+	sync_rec_t *srec;
+} find_vars_t;
+
+static int msg_found_sel( int sts, int uid, void *aux );
+static int msgs_found_sel( sync_vars_t *svars, int t );
+
+static int
+box_selected( int sts, void *aux )
+{
+	SVARS(aux)
+	find_vars_t *fv;
+	sync_rec_t *srec;
+
+	if (check_ret( sts, svars, t ))
+		return 1;
+	if (svars->uidval[t] >= 0 && svars->uidval[t] != svars->ctx[t]->uidvalidity) {
+		error( "Error: UIDVALIDITY of %s changed (got %d, expected %d)\n",
+		         str_ms[t], svars->ctx[t]->uidvalidity, svars->uidval[t] );
+		svars->ret |= SYNC_FAIL;
+		cancel_sync( svars );
+		return 1;
+	}
+	info( "%s: %d messages, %d recent\n", str_ms[t], svars->ctx[t]->count, svars->ctx[t]->recent );
+
+	if (svars->find) {
+		/*
+		 * Alternatively, the TUIDs could be fetched into the messages and
+		 * looked up here. This would make the search faster (probably) and
+		 * save roundtrips. On the downside, quite some additional data would
+		 * have to be fetched for every message and the IMAP driver would be
+		 * more complicated. This is a corner case anyway, so why bother.
+		 */
+		debug( "finding previously copied messages\n" );
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->uid[t] == -2 && srec->tuid[0]) {
+				debug( "  pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid );
+				svars->find_old_total[t]++;
+				stats( svars );
+				fv = nfmalloc( sizeof(*fv) );
+				fv->aux = AUX;
+				fv->srec = srec;
+				if (svars->drv[t]->find_msg( svars->ctx[t], srec->tuid, msg_found_sel, fv ))
+					return 1;
+			}
+		}
+	}
+	svars->state[t] |= ST_SENT_FIND_OLD;
+	return msgs_found_sel( svars, t );
+}
+
+static int
+msg_found_sel( int sts, int uid, void *aux )
+{
+	find_vars_t *vars = (find_vars_t *)aux;
+	SVARS(vars->aux)
+
+	if (check_ret_aux( sts, svars, t, vars ))
+		return 1;
+	switch (sts) {
+	case DRV_OK:
+		debug( "  -> new UID %d\n", uid );
+		Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid );
+		vars->srec->uid[t] = uid;
+		vars->srec->tuid[0] = 0;
+		break;
+	default:
+		debug( "  -> TUID lost\n" );
+		Fprintf( svars->jfp, "& %d %d\n", vars->srec->uid[M], vars->srec->uid[S] );
+		vars->srec->flags = 0;
+		vars->srec->tuid[0] = 0;
+		break;
+	}
+	free( vars );
+	svars->find_old_done[t]++;
+	stats( svars );
+	return msgs_found_sel( svars, t );
+}
+
+typedef struct {
+	void *aux;
+	sync_rec_t *srec;
+	int aflags, dflags;
+} flag_vars_t;
+
+static int flags_set_del( int sts, void *aux );
+static int flags_set_sync( int sts, void *aux );
+static void flags_set_sync_p2( sync_vars_t *svars, sync_rec_t *srec, int t );
+static int msgs_flags_set( sync_vars_t *svars, int t );
+static int msg_copied( int sts, int uid, copy_vars_t *vars );
+static void msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_t *tmsg, int uid );
+static int msgs_copied( sync_vars_t *svars, int t );
+
+static int
+msgs_found_sel( sync_vars_t *svars, int t )
+{
+	sync_rec_t *srec, *nsrec = 0;
+	message_t *tmsg;
+	copy_vars_t *cv;
+	flag_vars_t *fv;
+	const char *diag;
+	int uid, minwuid, *mexcs, nmexcs, rmexcs, no[2], del[2], todel, nmsgs, t1, t2;
+	int sflags, nflags, aflags, dflags, nex;
+	char fbuf[16]; /* enlarge when support for keywords is added */
+
+	if (!(svars->state[t] & ST_SENT_FIND_OLD) || svars->find_old_done[t] < svars->find_new_total[t])
+		return 0;
+
+	/*
+	 * Mapping tmsg -> srec (this variant) is dog slow for new messages.
+	 * Mapping srec -> tmsg is dog slow for deleted messages.
+	 * One solution would be using binary search on an index array.
+	 * msgs are already sorted by UID, srecs would have to be sorted by uid[t].
+	 */
+	debug( "matching messages against sync records\n" );
+	for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next) {
+		uid = tmsg->uid;
+		if (DFlags & DEBUG) {
+			make_flags( tmsg->flags, fbuf );
+			printf( svars->ctx[t]->opts & OPEN_SIZE ? "  message %5d, %-4s, %6d: " : "  message %5d, %-4s: ", uid, fbuf, tmsg->size );
+		}
+		for (srec = nsrec; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->uid[t] == uid) {
+				diag = srec == nsrec ? "adjacently" : "after gap";
+				goto found;
+			}
+		}
+		for (srec = svars->srecs; srec != nsrec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->uid[t] == uid) {
+				diag = "after reset";
+				goto found;
+			}
+		}
+		tmsg->srec = 0;
+		debug( "new\n" );
+		continue;
+	  found:
+		tmsg->srec = srec;
+		srec->msg[t] = tmsg;
+		nsrec = srec->next;
+		debug( "pairs %5d %s\n", srec->uid[1-t], diag );
+	}
+
+	if ((t == S) && svars->smaxxuid) {
+		debug( "preparing master selection - max expired slave uid is %d\n", svars->smaxxuid );
+		mexcs = 0;
+		nmexcs = rmexcs = 0;
+		minwuid = INT_MAX;
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->status & S_EXPIRED) {
+				if (!srec->uid[S] || ((svars->ctx[S]->opts & OPEN_OLD) && !srec->msg[S])) {
+					srec->status |= S_EXP_S;
+					continue;
+				}
+			} else {
+				if (svars->smaxxuid >= srec->uid[S])
+					continue;
+			}
+			if (minwuid > srec->uid[M])
+				minwuid = srec->uid[M];
+		}
+		debug( "  min non-orphaned master uid is %d\n", minwuid );
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->status & S_EXP_S) {
+				if (minwuid > srec->uid[M] && svars->maxuid[M] >= srec->uid[M]) {
+					debug( "  -> killing (%d,%d)\n", srec->uid[M], srec->uid[S] );
+					srec->status = S_DEAD;
+					Fprintf( svars->jfp, "- %d %d\n", srec->uid[M], srec->uid[S] );
+				} else if (srec->uid[S]) {
+					debug( "  -> orphaning (%d,[%d])\n", srec->uid[M], srec->uid[S] );
+					Fprintf( svars->jfp, "> %d %d 0\n", srec->uid[M], srec->uid[S] );
+					srec->uid[S] = 0;
+				}
+			} else if (minwuid > srec->uid[M]) {
+				if (srec->uid[S] < 0) {
+					if (svars->maxuid[M] >= srec->uid[M]) {
+						debug( "  -> killing (%d,%d)\n", srec->uid[M], srec->uid[S] );
+						srec->status = S_DEAD;
+						Fprintf( svars->jfp, "- %d %d\n", srec->uid[M], srec->uid[S] );
+					}
+				} else if (srec->uid[M] > 0 && srec->uid[S] && (svars->ctx[M]->opts & OPEN_OLD) &&
+				           (!(svars->ctx[M]->opts & OPEN_NEW) || svars->maxuid[M] >= srec->uid[M])) {
+					if (nmexcs == rmexcs) {
+						rmexcs = rmexcs * 2 + 100;
+						mexcs = nfrealloc( mexcs, rmexcs * sizeof(int) );
+					}
+					mexcs[nmexcs++] = srec->uid[M];
+				}
+			}
+		}
+		debugn( "  exception list is:" );
+		for (t = 0; t < nmexcs; t++)
+			debugn( " %d", mexcs[t] );
+		debug( "\n" );
+		return select_box( svars, M, minwuid, mexcs, nmexcs );
+	}
+
+	if (!(svars->state[1-t] & ST_SENT_FIND_OLD) || svars->find_old_done[1-t] < svars->find_new_total[1-t])
+		return 0;
+
+	if (svars->uidval[M] < 0 || svars->uidval[S] < 0) {
+		svars->uidval[M] = svars->ctx[M]->uidvalidity;
+		svars->uidval[S] = svars->ctx[S]->uidvalidity;
+		Fprintf( svars->jfp, "| %d %d\n", svars->uidval[M], svars->uidval[S] );
+	}
+
+	info( "Synchronizing...\n" );
+
+	debug( "synchronizing new entries\n" );
+	svars->osrecadd = svars->srecadd;
+	for (t = 0; t < 2; t++) {
+		for (nmsgs = 0, tmsg = svars->ctx[1-t]->msgs; tmsg; tmsg = tmsg->next)
+			if (tmsg->srec ? tmsg->srec->uid[t] < 0 && (tmsg->srec->uid[t] == -1 ? (svars->chan->ops[t] & OP_RENEW) : (svars->chan->ops[t] & OP_NEW)) : (svars->chan->ops[t] & OP_NEW)) {
+				debug( "new message %d on %s\n", tmsg->uid, str_ms[1-t] );
+				if ((svars->chan->ops[t] & OP_EXPUNGE) && (tmsg->flags & F_DELETED))
+					debug( "  -> not %sing - would be expunged anyway\n", str_hl[t] );
+				else {
+					if (tmsg->srec) {
+						srec = tmsg->srec;
+						srec->status |= S_DONE;
+						debug( "  -> pair(%d,%d) exists\n", srec->uid[M], srec->uid[S] );
+					} else {
+						srec = nfmalloc( sizeof(*srec) );
+						srec->next = 0;
+						*svars->srecadd = srec;
+						svars->srecadd = &srec->next;
+						srec->status = S_DONE;
+						srec->flags = 0;
+						srec->tuid[0] = 0;
+						srec->uid[1-t] = tmsg->uid;
+						srec->uid[t] = -2;
+						Fprintf( svars->jfp, "+ %d %d\n", srec->uid[M], srec->uid[S] );
+						debug( "  -> pair(%d,%d) created\n", srec->uid[M], srec->uid[S] );
+					}
+					if ((tmsg->flags & F_FLAGGED) || !svars->chan->stores[t]->max_size || tmsg->size <= svars->chan->stores[t]->max_size) {
+						if (tmsg->flags) {
+							srec->flags = tmsg->flags;
+							Fprintf( svars->jfp, "* %d %d %u\n", srec->uid[M], srec->uid[S], srec->flags );
+							debug( "  -> updated flags to %u\n", tmsg->flags );
+						}
+						for (t1 = 0; t1 < TUIDL; t1++) {
+							t2 = arc4_getbyte() & 0x3f;
+							srec->tuid[t1] = t2 < 26 ? t2 + 'A' : t2 < 52 ? t2 + 'a' - 26 : t2 < 62 ? t2 + '0' - 52 : t2 == 62 ? '+' : '/';
+						}
+						svars->new_total[t]++;
+						stats( svars );
+						cv = nfmalloc( sizeof(*cv) );
+						cv->cb = msg_copied;
+						cv->aux = AUX;
+						cv->srec = srec;
+						cv->msg = tmsg;
+						Fprintf( svars->jfp, "# %d %d %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], srec->tuid );
+						debug( "  -> %sing message, TUID %." stringify(TUIDL) "s\n", str_hl[t], srec->tuid );
+						if (copy_msg( cv ))
+							return 1;
+					} else {
+						if (tmsg->srec) {
+							debug( "  -> not %sing - still too big\n", str_hl[t] );
+							continue;
+						}
+						debug( "  -> not %sing - too big\n", str_hl[t] );
+						msg_copied_p2( svars, srec, t, tmsg, -1 );
+					}
+				}
+			}
+		svars->state[t] |= ST_SENT_NEW;
+		if (msgs_copied( svars, t ))
+			return 1;
+	}
+
+	debug( "synchronizing old entries\n" );
+	for (srec = svars->srecs; srec != *svars->osrecadd; srec = srec->next) {
+		if (srec->status & (S_DEAD|S_DONE))
+			continue;
+		debug( "pair (%d,%d)\n", srec->uid[M], srec->uid[S] );
+		no[M] = !srec->msg[M] && (svars->ctx[M]->opts & OPEN_OLD);
+		no[S] = !srec->msg[S] && (svars->ctx[S]->opts & OPEN_OLD);
+		if (no[M] && no[S]) {
+			debug( "  vanished\n" );
+			/* d.1) d.5) d.6) d.10) d.11) */
+			srec->status = S_DEAD;
+			Fprintf( svars->jfp, "- %d %d\n", srec->uid[M], srec->uid[S] );
+		} else {
+			del[M] = no[M] && (srec->uid[M] > 0);
+			del[S] = no[S] && (srec->uid[S] > 0);
+
+			for (t = 0; t < 2; t++) {
+				srec->aflags[t] = srec->dflags[t] = 0;
+				if (srec->msg[t] && (srec->msg[t]->flags & F_DELETED))
+					srec->status |= S_DEL(t);
+				/* excludes (push) c.3) d.2) d.3) d.4) / (pull) b.3) d.7) d.8) d.9) */
+				if (!srec->uid[t]) {
+					/* b.1) / c.1) */
+					debug( "  no more %s\n", str_ms[t] );
+				} else if (del[1-t]) {
+					/* c.4) d.9) / b.4) d.4) */
+					if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) && srec->msg[t]->flags != srec->flags)
+						info( "Info: conflicting changes in (%d,%d)\n", srec->uid[M], srec->uid[S] );
+					if (svars->chan->ops[t] & OP_DELETE) {
+						debug( "  %sing delete\n", str_hl[t] );
+						svars->flags_total[t]++;
+						stats( svars );
+						fv = nfmalloc( sizeof(*fv) );
+						fv->aux = AUX;
+						fv->srec = srec;
+						if (svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], F_DELETED, 0, flags_set_del, fv ))
+							return 1;
+					} else
+						debug( "  not %sing delete\n", str_hl[t] );
+				} else if (!srec->msg[1-t])
+					/* c.1) c.2) d.7) d.8) / b.1) b.2) d.2) d.3) */
+					;
+				else if (srec->uid[t] < 0)
+					/* b.2) / c.2) */
+					; /* handled as new messages (sort of) */
+				else if (!del[t]) {
+					/* a) & b.3) / c.3) */
+					if (svars->chan->ops[t] & OP_FLAGS) {
+						sflags = srec->msg[1-t]->flags;
+						if ((srec->status & (S_EXPIRE|S_EXPIRED)) && !t)
+							sflags &= ~F_DELETED;
+						srec->aflags[t] = sflags & ~srec->flags;
+						srec->dflags[t] = ~sflags & srec->flags;
+						if (DFlags & DEBUG) {
+							char afbuf[16], dfbuf[16]; /* enlarge when support for keywords is added */
+							make_flags( srec->aflags[t], afbuf );
+							make_flags( srec->dflags[t], dfbuf );
+							debug( "  %sing flags: +%s -%s\n", str_hl[t], afbuf, dfbuf );
+						}
+					} else
+						debug( "  not %sing flags\n", str_hl[t] );
+				} /* else b.4) / c.4) */
+			}
+		}
+	}
+
+	if ((svars->chan->ops[S] & (OP_NEW|OP_RENEW|OP_FLAGS)) && svars->chan->max_messages) {
+		/* Flagged and not yet synced messages older than the first not
+		 * expired message are not counted. */
+		todel = svars->ctx[S]->count + svars->new_total[S] - svars->chan->max_messages;
+		debug( "scheduling %d excess messages for expiration\n", todel );
+		for (tmsg = svars->ctx[S]->msgs; tmsg && todel > 0; tmsg = tmsg->next)
+			if (!(tmsg->status & M_DEAD) && (srec = tmsg->srec) &&
+			    ((tmsg->flags | srec->aflags[S]) & ~srec->dflags[S] & F_DELETED) &&
+			    !(srec->status & (S_EXPIRE|S_EXPIRED)))
+				todel--;
+		debug( "%d non-deleted excess messages\n", todel );
+		for (tmsg = svars->ctx[S]->msgs; tmsg; tmsg = tmsg->next) {
+			if (tmsg->status & M_DEAD)
+				continue;
+			if (!(srec = tmsg->srec) || srec->uid[M] <= 0)
+				todel--;
+			else {
+				nflags = (tmsg->flags | srec->aflags[S]) & ~srec->dflags[S];
+				if (!(nflags & F_DELETED) || (srec->status & (S_EXPIRE|S_EXPIRED))) {
+					if (nflags & F_FLAGGED)
+						todel--;
+					else if ((!(tmsg->status & M_RECENT) || (tmsg->flags & F_SEEN)) &&
+					         (todel > 0 ||
+					          ((srec->status & (S_EXPIRE|S_EXPIRED)) == (S_EXPIRE|S_EXPIRED)) ||
+					          ((srec->status & (S_EXPIRE|S_EXPIRED)) && (tmsg->flags & F_DELETED)))) {
+						srec->status |= S_NEXPIRE;
+						debug( "  pair(%d,%d)\n", srec->uid[M], srec->uid[S] );
+						todel--;
+					}
+				}
+			}
+		}
+		debug( "%d excess messages remain\n", todel );
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if ((srec->status & (S_DEAD|S_DONE)) || !srec->msg[S])
+				continue;
+			nex = (srec->status / S_NEXPIRE) & 1;
+			if (nex != ((srec->status / S_EXPIRED) & 1)) {
+				if (nex != ((srec->status / S_EXPIRE) & 1)) {
+					Fprintf( svars->jfp, "~ %d %d %d\n", srec->uid[M], srec->uid[S], nex );
+					debug( "  pair(%d,%d): %d (pre)\n", srec->uid[M], srec->uid[S], nex );
+					srec->status = (srec->status & ~S_EXPIRE) | (nex * S_EXPIRE);
+				} else
+					debug( "  pair(%d,%d): %d (pending)\n", srec->uid[M], srec->uid[S], nex );
+			}
+		}
+	}
+
+	debug( "synchronizing flags\n" );
+	for (srec = svars->srecs; srec != *svars->osrecadd; srec = srec->next) {
+		if (srec->status & (S_DEAD|S_DONE))
+			continue;
+		for (t = 0; t < 2; t++) {
+			aflags = srec->aflags[t];
+			dflags = srec->dflags[t];
+			if ((t == S) && ((mvBit(srec->status, S_EXPIRE, S_EXPIRED) ^ srec->status) & S_EXPIRED)) {
+				if (srec->status & S_NEXPIRE)
+					aflags |= F_DELETED;
+				else
+					dflags |= F_DELETED;
+			}
+			if ((svars->chan->ops[t] & OP_EXPUNGE) && (((srec->msg[t] ? srec->msg[t]->flags : 0) | aflags) & ~dflags & F_DELETED) &&
+			    (!svars->ctx[t]->conf->trash || svars->ctx[t]->conf->trash_only_new))
+			{
+				srec->aflags[t] &= F_DELETED;
+				aflags &= F_DELETED;
+				srec->dflags[t] = dflags = 0;
+			}
+			if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS)) {
+				aflags &= ~srec->msg[t]->flags;
+				dflags &= srec->msg[t]->flags;
+			}
+			if (aflags | dflags) {
+				svars->flags_total[t]++;
+				stats( svars );
+				fv = nfmalloc( sizeof(*fv) );
+				fv->aux = AUX;
+				fv->srec = srec;
+				fv->aflags = aflags;
+				fv->dflags = dflags;
+				if (svars->drv[t]->set_flags( svars->ctx[t], srec->msg[t], srec->uid[t], aflags, dflags, flags_set_sync, fv ))
+					return 1;
+			} else
+				flags_set_sync_p2( svars, srec, t );
+		}
+	}
+	for (t = 0; t < 2; t++) {
+		svars->drv[t]->commit( svars->ctx[t] );
+		svars->state[t] |= ST_SENT_FLAGS;
+		if (msgs_flags_set( svars, t ))
+			return 1;
+	}
+	return 0;
+}
+
+static int
+msg_copied( int sts, int uid, copy_vars_t *vars )
+{
+	SVARS(vars->aux)
+
+	switch (sts) {
+	case SYNC_OK:
+		msg_copied_p2( svars, vars->srec, t, vars->msg, uid );
+		break;
+	case SYNC_NOGOOD:
+		debug( "  -> killing (%d,%d)\n", vars->srec->uid[M], vars->srec->uid[S] );
+		vars->srec->status = S_DEAD;
+		Fprintf( svars->jfp, "- %d %d\n", vars->srec->uid[M], vars->srec->uid[S] );
+		break;
+	default:
+		cancel_sync( svars );
+	case SYNC_CANCELED:
+		free( vars );
+		return 1;
+	}
+	free( vars );
+	svars->new_done[t]++;
+	stats( svars );
+	return msgs_copied( svars, t );
+}
+
+static void
+msg_copied_p2( sync_vars_t *svars, sync_rec_t *srec, int t, message_t *tmsg, int uid )
+{
+	if (srec->uid[t] != uid) {
+		debug( "  -> new UID %d\n", uid );
+		Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], srec->uid[M], srec->uid[S], uid );
+		srec->uid[t] = uid;
+		srec->tuid[0] = 0;
+	}
+	if (!tmsg->srec) {
+		tmsg->srec = srec;
+		if (svars->maxuid[1-t] < tmsg->uid) {
+			svars->maxuid[1-t] = tmsg->uid;
+			Fprintf( svars->jfp, "%c %d\n", ")("[t], tmsg->uid );
+		}
+	}
+}
+
+static int msg_found_new( int sts, int uid, void *aux );
+static int sync_close( sync_vars_t *svars, int t );
+
+static int
+msgs_copied( sync_vars_t *svars, int t )
+{
+	sync_rec_t *srec;
+	find_vars_t *fv;
+
+	if (!(svars->state[t] & ST_SENT_NEW) || svars->new_done[t] < svars->new_total[t])
+		return 0;
+
+	debug( "finding just copied messages on %s\n", str_ms[t] );
+	for (srec = svars->srecs; srec; srec = srec->next) {
+		if (srec->status & S_DEAD)
+			continue;
+		if (srec->tuid[0] && srec->uid[t] == -2) {
+			debug( "  pair(%d,%d): lookup %s, TUID %." stringify(TUIDL) "s\n", srec->uid[M], srec->uid[S], str_ms[t], srec->tuid );
+			svars->find_new_total[t]++;
+			stats( svars );
+			fv = nfmalloc( sizeof(*fv) );
+			fv->aux = AUX;
+			fv->srec = srec;
+			if (svars->drv[t]->find_msg( svars->ctx[t], srec->tuid, msg_found_new, fv ))
+				return 1;
+		}
+	}
+	svars->state[t] |= ST_SENT_FIND_NEW;
+	return sync_close( svars, t );
+}
+
+static int
+msg_found_new( int sts, int uid, void *aux )
+{
+	find_vars_t *vars = (find_vars_t *)aux;
+	SVARS(vars->aux)
+
+	if (check_ret_aux( sts, svars, t, vars ))
+		return 1;
+	switch (sts) {
+	case DRV_OK:
+		debug( "  -> new UID %d\n", uid );
+		break;
+	default:
+		warn( "Warning: cannot find newly stored message %." stringify(TUIDL) "s on %s.\n", vars->srec->tuid, str_ms[t] );
+		uid = 0;
+		break;
+	}
+	Fprintf( svars->jfp, "%c %d %d %d\n", "<>"[t], vars->srec->uid[M], vars->srec->uid[S], uid );
+	vars->srec->uid[t] = uid;
+	vars->srec->tuid[0] = 0;
+	free( vars );
+	svars->find_new_done[t]++;
+	stats( svars );
+	return sync_close( svars, t );
+}
+
+static int
+flags_set_del( int sts, void *aux )
+{
+	flag_vars_t *vars = (flag_vars_t *)aux;
+	SVARS(vars->aux)
+
+	if (check_ret_aux( sts, svars, t, vars ))
+		return 1;
+	switch (sts) {
+	case DRV_OK:
+		vars->srec->status |= S_DEL(t);
+		Fprintf( svars->jfp, "%c %d %d 0\n", "><"[t], vars->srec->uid[M], vars->srec->uid[S] );
+		vars->srec->uid[1-t] = 0;
+		break;
+	}
+	free( vars );
+	svars->flags_done[t]++;
+	stats( svars );
+	return msgs_flags_set( svars, t );
+}
+
+static int
+flags_set_sync( int sts, void *aux )
+{
+	flag_vars_t *vars = (flag_vars_t *)aux;
+	SVARS(vars->aux)
+
+	if (check_ret_aux( sts, svars, t, vars ))
+		return 1;
+	switch (sts) {
+	case DRV_OK:
+		if (vars->aflags & F_DELETED)
+			vars->srec->status |= S_DEL(t);
+		else if (vars->dflags & F_DELETED)
+			vars->srec->status &= ~S_DEL(t);
+		flags_set_sync_p2( svars, vars->srec, t );
+		break;
+	}
+	free( vars );
+	svars->flags_done[t]++;
+	stats( svars );
+	return msgs_flags_set( svars, t );
+}
+
+static void
+flags_set_sync_p2( sync_vars_t *svars, sync_rec_t *srec, int t )
+{
+	int nflags, nex;
+
+	nflags = (srec->flags | srec->aflags[t]) & ~srec->dflags[t];
+	if (srec->flags != nflags) {
+		debug( "  pair(%d,%d): updating flags (%u -> %u)\n", srec->uid[M], srec->uid[S], srec->flags, nflags );
+		srec->flags = nflags;
+		Fprintf( svars->jfp, "* %d %d %u\n", srec->uid[M], srec->uid[S], nflags );
+	}
+	if (t == S) {
+		nex = (srec->status / S_NEXPIRE) & 1;
+		if (nex != ((srec->status / S_EXPIRED) & 1)) {
+			if (nex && (svars->smaxxuid < srec->uid[S]))
+				svars->smaxxuid = srec->uid[S];
+			Fprintf( svars->jfp, "/ %d %d\n", srec->uid[M], srec->uid[S] );
+			debug( "  pair(%d,%d): expired %d (commit)\n", srec->uid[M], srec->uid[S], nex );
+			srec->status = (srec->status & ~S_EXPIRED) | (nex * S_EXPIRED);
+		} else if (nex != ((srec->status / S_EXPIRE) & 1)) {
+			Fprintf( svars->jfp, "\\ %d %d\n", srec->uid[M], srec->uid[S] );
+			debug( "  pair(%d,%d): expire %d (cancel)\n", srec->uid[M], srec->uid[S], nex );
+			srec->status = (srec->status & ~S_EXPIRE) | (nex * S_EXPIRE);
+		}
+	}
+}
+
+static int msg_trashed( int sts, void *aux );
+static int msg_rtrashed( int sts, int uid, copy_vars_t *vars );
+
+static int
+msgs_flags_set( sync_vars_t *svars, int t )
+{
+	message_t *tmsg;
+	copy_vars_t *cv;
+
+	if (!(svars->state[t] & ST_SENT_FLAGS) || svars->flags_done[t] < svars->flags_total[t])
+		return 0;
+
+	if ((svars->chan->ops[t] & OP_EXPUNGE) &&
+	    (svars->ctx[t]->conf->trash || (svars->ctx[1-t]->conf->trash && svars->ctx[1-t]->conf->trash_remote_new))) {
+		debug( "trashing in %s\n", str_ms[t] );
+		for (tmsg = svars->ctx[t]->msgs; tmsg; tmsg = tmsg->next)
+			if (tmsg->flags & F_DELETED) {
+				if (svars->ctx[t]->conf->trash) {
+					if (!svars->ctx[t]->conf->trash_only_new || !tmsg->srec || tmsg->srec->uid[1-t] < 0) {
+						debug( "%s: trashing message %d\n", str_ms[t], tmsg->uid );
+						svars->trash_total[t]++;
+						stats( svars );
+						if (svars->drv[t]->trash_msg( svars->ctx[t], tmsg, msg_trashed, AUX ))
+							return 1;
+					} else
+						debug( "%s: not trashing message %d - not new\n", str_ms[t], tmsg->uid );
+				} else {
+					if (!tmsg->srec || tmsg->srec->uid[1-t] < 0) {
+						if (!svars->ctx[1-t]->conf->max_size || tmsg->size <= svars->ctx[1-t]->conf->max_size) {
+							debug( "%s: remote trashing message %d\n", str_ms[t], tmsg->uid );
+							svars->trash_total[t]++;
+							stats( svars );
+							cv = nfmalloc( sizeof(*cv) );
+							cv->cb = msg_rtrashed;
+							cv->aux = AUX;
+							cv->srec = 0;
+							cv->msg = tmsg;
+							if (copy_msg( cv ))
+								return 1;
+						} else
+							debug( "%s: not remote trashing message %d - too big\n", str_ms[t], tmsg->uid );
+					} else
+						debug( "%s: not remote trashing message %d - not new\n", str_ms[t], tmsg->uid );
+				}
+			}
+	}
+	svars->state[t] |= ST_SENT_TRASH;
+	return sync_close( svars, t );
+}
+
+static int
+msg_trashed( int sts, void *aux )
+{
+	SVARS(aux)
+
+	if (sts == DRV_MSG_BAD)
+		sts = DRV_BOX_BAD;
+	if (check_ret( sts, svars, t ))
+		return 1;
+	svars->trash_done[t]++;
+	stats( svars );
+	return sync_close( svars, t );
+}
+
+static int
+msg_rtrashed( int sts, int uid, copy_vars_t *vars )
+{
+	SVARS(vars->aux)
+
+	(void)uid;
+	switch (sts) {
+	case SYNC_OK:
+	case SYNC_NOGOOD: /* the message is gone or heavily busted */
+		break;
+	default:
+		cancel_sync( svars );
+	case SYNC_CANCELED:
+		free( vars );
+		return 1;
+	}
+	free( vars );
+	svars->trash_done[t]++;
+	stats( svars );
+	return sync_close( svars, t );
+}
+
+static int box_closed( int sts, void *aux );
+static void box_closed_p2( sync_vars_t *svars, int t );
+
+static int
+sync_close( sync_vars_t *svars, int t )
+{
+	if ((~svars->state[t] & (ST_SENT_FIND_NEW|ST_SENT_TRASH)) ||
+	    svars->find_new_done[t] < svars->find_new_total[t] ||
+	    svars->trash_done[t] < svars->trash_total[t])
+		return 0;
+
+	if ((svars->chan->ops[t] & OP_EXPUNGE) /*&& !(svars->state[t] & ST_TRASH_BAD)*/) {
+		debug( "expunging %s\n", str_ms[t] );
+		return svars->drv[t]->close( svars->ctx[t], box_closed, AUX );
+	}
+	box_closed_p2( svars, t );
+	return 0;
+}
+
+static int
+box_closed( int sts, void *aux )
+{
+	SVARS(aux)
+
+	if (check_ret( sts, svars, t ))
+		return 1;
+	svars->state[t] |= ST_DID_EXPUNGE;
+	box_closed_p2( svars, t );
+	return 0;
+}
+
+static void
+box_closed_p2( sync_vars_t *svars, int t )
+{
+	sync_rec_t *srec;
+	int minwuid;
+	char fbuf[16]; /* enlarge when support for keywords is added */
+
+	svars->state[t] |= ST_CLOSED;
+	if (!(svars->state[1-t] & ST_CLOSED))
+		return;
+
+	if ((svars->state[M] | svars->state[S]) & ST_DID_EXPUNGE) {
+		/* This cleanup is not strictly necessary, as the next full sync
+		   would throw out the dead entries anyway. But ... */
+
+		minwuid = INT_MAX;
+		if (svars->smaxxuid) {
+			debug( "preparing entry purge - max expired slave uid is %d\n", svars->smaxxuid );
+			for (srec = svars->srecs; srec; srec = srec->next) {
+				if (srec->status & S_DEAD)
+					continue;
+				if (!((srec->uid[S] <= 0 || ((srec->status & S_DEL(S)) && (svars->state[S] & ST_DID_EXPUNGE))) &&
+				      (srec->uid[M] <= 0 || ((srec->status & S_DEL(M)) && (svars->state[M] & ST_DID_EXPUNGE)) || (srec->status & S_EXPIRED))) &&
+				    svars->smaxxuid < srec->uid[S] && minwuid > srec->uid[M])
+					minwuid = srec->uid[M];
+			}
+			debug( "  min non-orphaned master uid is %d\n", minwuid );
+		}
+
+		for (srec = svars->srecs; srec; srec = srec->next) {
+			if (srec->status & S_DEAD)
+				continue;
+			if (srec->uid[S] <= 0 || ((srec->status & S_DEL(S)) && (svars->state[S] & ST_DID_EXPUNGE))) {
+				if (srec->uid[M] <= 0 || ((srec->status & S_DEL(M)) && (svars->state[M] & ST_DID_EXPUNGE)) ||
+				    ((srec->status & S_EXPIRED) && svars->maxuid[M] >= srec->uid[M] && minwuid > srec->uid[M])) {
+					debug( "  -> killing (%d,%d)\n", srec->uid[M], srec->uid[S] );
+					srec->status = S_DEAD;
+					Fprintf( svars->jfp, "- %d %d\n", srec->uid[M], srec->uid[S] );
+				} else if (srec->uid[S] > 0) {
+					debug( "  -> orphaning (%d,[%d])\n", srec->uid[M], srec->uid[S] );
+					Fprintf( svars->jfp, "> %d %d 0\n", srec->uid[M], srec->uid[S] );
+					srec->uid[S] = 0;
+				}
+			} else if (srec->uid[M] > 0 && ((srec->status & S_DEL(M)) && (svars->state[M] & ST_DID_EXPUNGE))) {
+				debug( "  -> orphaning ([%d],%d)\n", srec->uid[M], srec->uid[S] );
+				Fprintf( svars->jfp, "< %d %d 0\n", srec->uid[M], srec->uid[S] );
+				srec->uid[M] = 0;
+			}
+		}
+	}
+
+	Fprintf( svars->nfp, "%d:%d %d:%d:%d\n", svars->uidval[M], svars->maxuid[M], svars->uidval[S], svars->smaxxuid, svars->maxuid[S] );
+	for (srec = svars->srecs; srec; srec = srec->next) {
+		if (srec->status & S_DEAD)
+			continue;
+		make_flags( srec->flags, fbuf );
+		Fprintf( svars->nfp, "%d %d %s%s\n", srec->uid[M], srec->uid[S],
+		         srec->status & S_EXPIRED ? "X" : "", fbuf );
+	}
+
+	Fclose( svars->nfp );
+	Fclose( svars->jfp );
+	if (!(DFlags & KEEPJOURNAL)) {
+		/* order is important! */
+		rename( svars->nname, svars->dname );
+		unlink( svars->jname );
+	}
+
+	sync_bail( svars );
+}
+
+static void
+sync_bail( sync_vars_t *svars )
+{
+	sync_rec_t *srec, *nsrec;
+
+	for (srec = svars->srecs; srec; srec = nsrec) {
+		nsrec = srec->next;
+		free( srec );
+	}
+	unlink( svars->lname );
+	sync_bail1( svars );
+}
+
+static void
+sync_bail1( sync_vars_t *svars )
+{
+	close( svars->lfd );
+	sync_bail2( svars );
+}
+
+static void
+sync_bail2( sync_vars_t *svars )
+{
+	void (*cb)( int sts, void *aux ) = svars->cb;
+	void *aux = svars->aux;
+	int ret = svars->ret;
+
+	free( svars->lname );
+	free( svars->nname );
+	free( svars->jname );
+	free( svars->dname );
+	free( svars );
+	error( "" );
+	cb( ret, aux );
+}
+
diff --git a/isync/util.c b/isync/util.c
new file mode 100644
index 0000000..8a718e4
--- /dev/null
+++ b/isync/util.c
@@ -0,0 +1,413 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me at mutt.org>
+ * Copyright (C) 2002-2006 Oswald Buddenhagen <ossi at 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include "isync.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <pwd.h>
+#include <ctype.h>
+
+int DFlags, Ontty;
+static int need_nl;
+
+void
+debug( const char *msg, ... )
+{
+	va_list va;
+
+	if (DFlags & DEBUG) {
+		va_start( va, msg );
+		vprintf( msg, va );
+		va_end( va );
+		fflush( stdout );
+		need_nl = 0;
+	}
+}
+
+void
+debugn( const char *msg, ... )
+{
+	va_list va;
+
+	if (DFlags & DEBUG) {
+		va_start( va, msg );
+		vprintf( msg, va );
+		va_end( va );
+		fflush( stdout );
+		need_nl = Ontty;
+	}
+}
+
+void
+info( const char *msg, ... )
+{
+	va_list va;
+
+	if (!(DFlags & QUIET)) {
+		va_start( va, msg );
+		vprintf( msg, va );
+		va_end( va );
+		fflush( stdout );
+		need_nl = 0;
+	}
+}
+
+void
+infon( const char *msg, ... )
+{
+	va_list va;
+
+	if (!(DFlags & QUIET)) {
+		va_start( va, msg );
+		vprintf( msg, va );
+		va_end( va );
+		fflush( stdout );
+		need_nl = Ontty;
+	}
+}
+
+void
+warn( const char *msg, ... )
+{
+	va_list va;
+
+	if (!(DFlags & VERYQUIET)) {
+		if (need_nl) {
+			putchar( '\n' );
+			need_nl = 0;
+		}
+		va_start( va, msg );
+		vfprintf( stderr, msg, va );
+		va_end( va );
+	}
+}
+
+void
+error( const char *msg, ... )
+{
+	va_list va;
+
+	if (need_nl) {
+		putchar( '\n' );
+		need_nl = 0;
+	}
+	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 );
+	}
+}
+
+#ifndef HAVE_VASPRINTF
+static int
+vasprintf( char **strp, const char *fmt, va_list ap )
+{
+	int len;
+	char tmp[1024];
+
+	if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = malloc( len + 1 )))
+		return -1;
+	if (len >= (int)sizeof(tmp))
+		vsprintf( *strp, fmt, ap );
+	else
+		memcpy( *strp, tmp, 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 (ret < 0)
+		oom();
+	return ret;
+}
+
+int
+nfasprintf( char **str, const char *fmt, ... )
+{
+	int ret;
+	va_list va;
+
+	va_start( va, fmt );
+	ret = nfvasprintf( str, fmt, va );
+	va_end( va );
+	return ret;
+}
+
+/*
+static struct passwd *
+cur_user( void )
+{
+	char *p;
+	struct passwd *pw;
+	uid_t uid;
+
+	uid = getuid();
+	if ((!(p = getenv("LOGNAME")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) &&
+	    (!(p = getenv("USER")) || !(pw = getpwnam( p )) || pw->pw_uid != uid) &&
+	    !(pw = getpwuid( uid )))
+	{
+		fputs ("Cannot determinate current user\n", stderr);
+		return 0;
+	}
+	return pw;
+}
+*/
+
+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) {
+		error( "Fatal: no random number source available.\n" );
+		exit( 3 );
+	}
+	if (read( fd, dat, 128 ) != 128) {
+		error( "Fatal: cannot read random number source.\n" );
+		exit( 3 );
+	}
+	close( fd );
+
+	for (i = 0; i < 256; i++)
+		rs.s[i] = 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];
+}
diff --git a/postler/wscript_build b/isync/wscript_build
similarity index 67%
copy from postler/wscript_build
copy to isync/wscript_build
index 0e85405..17b3154 100644
--- a/postler/wscript_build
+++ b/isync/wscript_build
@@ -9,11 +9,8 @@
 # See the file COPYING for the full license text.
 
 obj = bld.new_task_gen ('cc', 'program')
-obj.name = 'postler'
-obj.target = 'postler'
+obj.target = 'postler-mbsync'
 obj.includes = '. ..'
-obj.find_sources_in_dirs ('.')
-obj.uselib = 'GIO GTHREAD GTK UNIQUE WEBKIT'
-obj.packages = 'config postler posix gio-2.0 gtk+-2.0 unique-1.0 webkit-1.0'
-obj.vapi_dirs = '.'
+obj.source = 'main.c sync.c config.c util.c drv_imap.c drv_maildir.c'
+obj.uselib = 'DB_CREATE OPENSSL'
 
diff --git a/postler/postler-accounts.vala b/postler/postler-accounts.vala
index d7900c3..56835ee 100644
--- a/postler/postler-accounts.vala
+++ b/postler/postler-accounts.vala
@@ -327,10 +327,13 @@ public class Postler.Accounts : GLib.Object {
             if (send != null)
                 command = "%s -c 'cat %s | msmtp -C %s -t'".printf (
                     Environment.get_variable ("SHELL"), send, filename);
-            else if (info.type == AccountType.IMAP)
-                command = "mbsync -c %s --pull%s mirror".printf (
-                    filename, info.sync == "full" ? " --push" : "");
-            else
+            else if (info.type == AccountType.IMAP) {
+                unowned string? mbsync = Environment.get_variable ("POSTLER_MBSYNC");
+                if (mbsync == null || mbsync == "")
+                    mbsync = "postler-mbsync";
+                command = "%s -c %s --pull%s mirror".printf (
+                    mbsync, filename, info.sync == "full" ? " --push" : "");
+            } else
                 continue;
                 generate_tool_configuration (send, info);
                 if (DirUtils.create_with_parents (info.path, 0700) != 0)
diff --git a/wscript b/wscript
index 00e6de7..070bd93 100644
--- a/wscript
+++ b/wscript
@@ -98,11 +98,29 @@ def configure (conf):
         return conf.env['HAVE_' + var]
 
     check_pkg ('gio-2.0', '2.16.0')
-    check_pkg ('gthread-2.0', '2.16.0')
     check_pkg ('unique-1.0', '0.9')
     check_pkg ('gtk+-2.0', '2.12.0', var='GTK')
     check_pkg ('webkit-1.0', '1.1.1')
 
+    # isync
+    conf.check (header_name='sys/filio.h')
+    conf.check (fragment='#define _GNU_SOURCE\n#include <stdio.h>\n' \
+        'int main(char** argv, int argc)\n' \
+        '{ char* a;\nvasprintf(&a, "%s", ""); return 0; }', \
+        define_name='HAVE_VASPRINTF', msg='Checking for function vasprintf')
+    conf.check (function_name='socket', header_name='sys/socket.h')
+    conf.check (function_name='inet_ntoa', header_name='arpa/inet.h', lib='nsl')
+    check_pkg ('openssl')
+    conf.check (function_name='dlopen', header_name='dlfcn.h', lib='dl')
+    conf.check (function_name='CRYPTO_lock',
+                header_name='openssl/crypto.h', lib='crypto')
+    conf.check (function_name='SSL_connect', lib='ssl',
+                header_name='openssl/ssl.h', define_name='HAVE_LIBSSL')
+    conf.check (function_name='db_create', header_name='db.h', lib='db')
+    conf.define ('PACKAGE', APPNAME)
+    conf.define ('VERSION', VERSION)
+    conf.define ('POSTLER_CHANGES', 1)
+
     # Store options in env, since 'Options' is not persistent
     if 'CC' in os.environ: conf.env['CC'] = os.environ['CC'].split()
     conf.env['docs'] = option_enabled ('docs')
@@ -219,7 +237,7 @@ def write_linguas_file (self):
 write_linguas_file = feature ('intltool_po')(write_linguas_file)
 
 def build (bld):
-    bld.add_subdirs ('postler icons')
+    bld.add_subdirs ('postler isync icons')
 
     bld.add_group ()
 
@@ -329,7 +347,8 @@ def shutdown ():
         except:
             pass
         try:
-            command = relfolder + os.sep + APPNAME + os.sep + APPNAME
+            mbsync = 'POSTLER_MBSYNC=' + relfolder +'/isync/postler-mbsync '
+            command = mbsync + relfolder + os.sep + APPNAME + os.sep + APPNAME
             print command
             Utils.exec_command (command)
         except Exception, msg:



More information about the Xfce4-commits mailing list