[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