فهرست منبع

Bunch 'o patches from Oswald Buddenhagen:

i implemented some cool stuff (tm).
first, the long missing "create server-side missing mailboxes". -C now
creates both local and remote boxes; -L and -R create only local/remote.
second, i implemented a 1:1 remote:local folder mapping (-1) with an
optional INBOX exception (inbox/-I). the remote folder is specified with
the folder keyword (or -F switch) and takes precedence over the
namespace setting. the local directory with the mailboxes can now be
specified on the command line, too (-M).

another patch:
- made the -1 switch settable permanently (OneToOne). after all, you
  usually define your mailbox layout once forever. removed -A, as it is
  semantically -a modified by -1.
- cleaned up message output a bit. still, the quiet variable should be
  used throughout the program. at best, create some generic output
  function, which obeys a global verbosity level variable.
- optimized + cleaned up configuration parser slightly
- minor cleanups

add an (almost) unique id to every uploaded message and search for it
right after. i thought about using the message-id, but a) it is not
guaranteed to be unique in a mailbox (imagine you edit a mail and store
the dupe in the same box) and b) some mails (e.g., postponed) don't even
have one. a downside of the current implementation is, that this
id-header remains in the mailbox, but given that it wastes only 27 bytes
per mail and removing it would mean several roundtrips more, this seems
acceptable.
i changed the line-counting loop to use a mmapped file instead of
reading it in chunks, as it makes things simpler and is probably even
faster for big mails.
the amount of goto statements in my code may be scary, but c is simply
lacking a multi-level break statement. :)

this is the "shut up" patch. :) it makes the -q option consequent, so to
say.
additionally it adds an -l option which gathers all defined/found
mailboxes and just outputs the list. don't ask what i need it for. ;)
Michael Elkins 22 سال پیش
والد
کامیت
7cd74a1179
8فایلهای تغییر یافته به همراه782 افزوده شده و 666 حذف شده
  1. 12 0
      .cvsignore
  2. 79 126
      config.c
  3. 349 265
      imap.c
  4. 142 176
      isync.1
  5. 10 3
      isync.h
  6. 4 4
      maildir.c
  7. 163 57
      main.c
  8. 23 35
      sync.c

+ 12 - 0
.cvsignore

@@ -0,0 +1,12 @@
+.deps
+Makefile
+Makefile.in
+autom4te.cache
+aclocal.m4
+build-stamp
+config.cache
+config.log
+config.status
+configure
+configure-stamp
+isync

+ 79 - 126
config.c

@@ -91,13 +91,23 @@ expand_strdup (const char *s)
     return strdup (s);
 }
 
+static int
+is_true (const char *val)
+{
+    return
+	!strcasecmp (val, "yes") ||
+	!strcasecmp (val, "true") ||
+	!strcasecmp (val, "on") ||
+	!strcmp (val, "1");
+}
+
 void
-load_config (const char *where)
+load_config (const char *where, int *o2o)
 {
     char path[_POSIX_PATH_MAX];
     char buf[1024];
     struct passwd *pw;
-    config_t **cur = &boxes;
+    config_t **cur = &boxes, *cfg;
     int line = 0;
     FILE *fp;
     char *p, *cmd, *val;
@@ -109,7 +119,7 @@ load_config (const char *where)
 	where = path;
     }
 
-    printf ("Reading %s\n", where);
+    info ("Reading configuration file %s\n", where);
 
     fp = fopen (where, "r");
     if (!fp)
@@ -119,6 +129,7 @@ load_config (const char *where)
 	return;
     }
     buf[sizeof buf - 1] = 0;
+    cfg = &global;
     while ((fgets (buf, sizeof (buf) - 1, fp)))
     {
 	p = buf;
@@ -129,174 +140,116 @@ load_config (const char *where)
 	    continue;
 	if (!strcasecmp ("mailbox", cmd))
 	{
-	    if (*cur)
-		cur = &(*cur)->next;
-	    *cur = calloc (1, sizeof (config_t));
-	    config_defaults (*cur);
+	    if (*o2o)
+		break;
+	    cur = &(*cur)->next;
+	    cfg = *cur = malloc (sizeof (config_t));
+	    config_defaults (cfg);
 	    /* not expanded at this point */
-	    (*cur)->path = strdup (val);
+	    cfg->path = strdup (val);
+	}
+	else if (!strcasecmp ("OneToOne", cmd))
+	{
+	    if (*cur) {
+	      forbid:
+		fprintf (stderr,
+			 "%s:%d: keyword '%s' allowed only in global section\n",
+			 path, line, cmd);
+		continue;
+	    }
+	    *o2o = is_true (val);
 	}
 	else if (!strcasecmp ("maildir", cmd))
 	{
+	    if (*cur)
+		goto forbid;
 	    /* this only affects the global setting */
 	    free (global.maildir);
 	    global.maildir = expand_strdup (val);
 	}
+	else if (!strcasecmp ("folder", cmd))
+	{
+	    if (*cur)
+		goto forbid;
+	    /* this only affects the global setting */
+	    global.folder = strdup (val);
+	}
+	else if (!strcasecmp ("inbox", cmd))
+	{
+	    if (*cur)
+		goto forbid;
+	    /* this only affects the global setting */
+	    global.inbox = strdup (val);
+	}
 	else if (!strcasecmp ("host", cmd))
 	{
 #if HAVE_LIBSSL
 	    if (!strncasecmp ("imaps:", val, 6))
 	    {
 		val += 6;
-		if (*cur)
-		{
-		    (*cur)->use_imaps = 1;
-		    (*cur)->port = 993;
-		    (*cur)->use_sslv2 = 1;
-		    (*cur)->use_sslv3 = 1;
-		}
-		else
-		{
-		    global.use_imaps = 1;
-		    global.port = 993;
-		    global.use_sslv2 = 1;
-		    global.use_sslv3 = 1;
-		}
+		cfg->use_imaps = 1;
+		cfg->port = 993;
+		cfg->use_sslv2 = 1;
+		cfg->use_sslv3 = 1;
 	    }
 #endif
-	    if (*cur)
-		(*cur)->host = strdup (val);
-	    else
-		global.host = strdup (val);
+	    cfg->host = strdup (val);
 	}
 	else if (!strcasecmp ("user", cmd))
 	{
 	    if (*cur)
 		(*cur)->user = strdup (val);
-	    else
+	    else {
+		free (global.user);
 		global.user = strdup (val);
+	    }
 	}
 	else if (!strcasecmp ("pass", cmd))
-	{
-	    if (*cur)
-		(*cur)->pass = strdup (val);
-	    else
-		global.pass = strdup (val);
-	}
+	    cfg->pass = strdup (val);
 	else if (!strcasecmp ("port", cmd))
-	{
-	    if (*cur)
-		(*cur)->port = atoi (val);
-	    else
-		global.port = atoi (val);
-	}
+	    cfg->port = atoi (val);
 	else if (!strcasecmp ("box", cmd))
-	{
-	    if (*cur)
-		(*cur)->box = strdup (val);
-	    else
-		global.box = strdup (val);
-	}
+	    cfg->box = strdup (val);
 	else if (!strcasecmp ("alias", cmd))
 	{
-	    if (*cur)
-		(*cur)->alias = strdup (val);
+	    if (!*cur) {
+		fprintf (stderr,
+			 "%s:%d: keyword 'alias' allowed only in mailbox specification\n",
+			 path, line);
+		continue;
+	    }
+	    cfg->alias = strdup (val);
 	}
 	else if (!strcasecmp ("maxsize", cmd))
-	{
-	    if (*cur)
-		(*cur)->max_size = atol (val);
-	    else
-		global.max_size = atol (val);
-	}
+	    cfg->max_size = atol (val);
 	else if (!strcasecmp ("MaxMessages", cmd))
-	{
-	    if (*cur)
-		(*cur)->max_messages = atol (val);
-	    else
-		global.max_messages = atol (val);
-	}
+	    cfg->max_messages = atol (val);
 	else if (!strcasecmp ("UseNamespace", cmd))
-	{
-	    if (*cur)
-		(*cur)->use_namespace = (strcasecmp (val, "yes") == 0);
-	    else
-		global.use_namespace = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->use_namespace = is_true (val);
 	else if (!strcasecmp ("CopyDeletedTo", cmd))
-	{
-	    if (*cur)
-		(*cur)->copy_deleted_to = strdup (val);
-	    else
-		global.copy_deleted_to = strdup (val);
-	}
+	    cfg->copy_deleted_to = strdup (val);
 	else if (!strcasecmp ("Tunnel", cmd))
-	{
-	    if (*cur)
-		(*cur)->tunnel = strdup (val);
-	    else
-		global.tunnel = strdup (val);
-	}
+	    cfg->tunnel = strdup (val);
 	else if (!strcasecmp ("Expunge", cmd))
-	{
-	    if (*cur)
-		(*cur)->expunge = (strcasecmp (val, "yes") == 0);
-	    else
-		global.expunge = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->expunge = is_true (val);
 	else if (!strcasecmp ("Delete", cmd))
-	{
-	    if (*cur)
-		(*cur)->delete = (strcasecmp (val, "yes") == 0);
-	    else
-		global.delete = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->delete = is_true (val);
 #if HAVE_LIBSSL
 	else if (!strcasecmp ("CertificateFile", cmd))
-	{
-	    if (*cur)
-		(*cur)->cert_file = expand_strdup (val);
-	    else
-		global.cert_file = expand_strdup (val);
-	}
+	    cfg->cert_file = expand_strdup (val);
 	else if (!strcasecmp ("RequireSSL", cmd))
-	{
-	    if (*cur)
-		(*cur)->require_ssl = (strcasecmp (val, "yes") == 0);
-	    else
-		global.require_ssl = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->require_ssl = is_true (val);
 	else if (!strcasecmp ("UseSSLv2", cmd))
-	{
-	    if (*cur)
-		(*cur)->use_sslv2 = (strcasecmp (val, "yes") == 0);
-	    else
-		global.use_sslv2 = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->use_sslv2 = is_true (val);
 	else if (!strcasecmp ("UseSSLv3", cmd))
-	{
-	    if (*cur)
-		(*cur)->use_sslv3 = (strcasecmp (val, "yes") == 0);
-	    else
-		global.use_sslv3 = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->use_sslv3 = is_true (val);
 	else if (!strcasecmp ("UseTLSv1", cmd))
-	{
-	    if (*cur)
-		(*cur)->use_tlsv1 = (strcasecmp (val, "yes") == 0);
-	    else
-		global.use_tlsv1 = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->use_tlsv1 = is_true (val);
 	else if (!strcasecmp ("RequireCRAM", cmd))
-	{
-	    if (*cur)
-		(*cur)->require_cram = (strcasecmp (val, "yes") == 0);
-	    else
-		global.require_cram = (strcasecmp (val, "yes") == 0);
-	}
+	    cfg->require_cram = is_true (val);
 #endif
 	else if (buf[0])
-	    printf ("%s:%d:unknown keyword:%s\n", path, line, cmd);
+	    fprintf (stderr, "%s:%d: unknown keyword '%s'\n", path, line, cmd);
     }
     fclose (fp);
 }

+ 349 - 265
imap.c

@@ -20,6 +20,8 @@
 
 #include <assert.h>
 #include <unistd.h>
+#include <sys/mman.h>
+#include <sys/time.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <errno.h>
@@ -77,7 +79,7 @@ verify_cert (SSL * ssl)
     cert = SSL_get_peer_certificate (ssl);
     if (!cert)
     {
-	puts ("Error, no server certificate");
+	fprintf (stderr, "Error, no server certificate\n");
 	return -1;
     }
 
@@ -85,34 +87,33 @@ verify_cert (SSL * ssl)
     if (err == X509_V_OK)
 	return 0;
 
-    printf ("Error, can't verify certificate: %s (%d)\n",
-	    X509_verify_cert_error_string (err), err);
+    fprintf (stderr, "Error, can't verify certificate: %s (%d)\n",
+	     X509_verify_cert_error_string (err), err);
 
     X509_NAME_oneline (X509_get_subject_name (cert), buf, sizeof (buf));
-    printf ("\nSubject: %s\n", buf);
+    info ("\nSubject: %s\n", buf);
     X509_NAME_oneline (X509_get_issuer_name (cert), buf, sizeof (buf));
-    printf ("Issuer:  %s\n", 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);
-    printf ("Valid from: %s\n", buf);
+    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);
-    printf ("      to:   %s\n", buf);
+    info ("      to:   %s\n", buf);
 
-    printf
-	("\n*** WARNING ***  There is no way to verify this certificate.  It is\n"
+    fprintf (stderr, 
+	"\n*** WARNING ***  There is no way to verify this certificate.  It is\n"
 	 "                 possible that a hostile attacker has replaced the\n"
-	 "                 server certificate.  Continue at your own risk!\n");
-    printf ("\nAccept this certificate anyway? [no]: ");
-    fflush (stdout);
+	 "                 server certificate.  Continue at your own risk!\n"
+         "\nAccept this certificate anyway? [no]: ");
     if (fgets (buf, sizeof (buf), stdin) && (buf[0] == 'y' || buf[0] == 'Y'))
     {
 	ret = 0;
-	puts ("\n*** Fine, but don't say I didn't warn you!\n");
+	fprintf (stderr, "\n*** Fine, but don't say I didn't warn you!\n\n");
     }
     return ret;
 }
@@ -125,7 +126,7 @@ init_ssl (config_t * conf)
 
     if (!conf->cert_file)
     {
-	puts ("Error, CertificateFile not defined");
+	fprintf (stderr, "Error, CertificateFile not defined\n");
 	return -1;
     }
     SSL_library_init ();
@@ -145,15 +146,15 @@ init_ssl (config_t * conf)
 	    perror ("access");
 	    return -1;
 	}
-	puts
-	    ("*** Warning, CertificateFile doesn't exist, can't verify server certificates");
+	fprintf (stderr, 
+	    "*** Warning, CertificateFile doesn't exist, can't verify server certificates\n");
     }
     else
 	if (!SSL_CTX_load_verify_locations
 	    (SSLContext, conf->cert_file, NULL))
     {
-	printf ("Error, SSL_CTX_load_verify_locations: %s\n",
-		ERR_error_string (ERR_get_error (), 0));
+	fprintf (stderr, "Error, SSL_CTX_load_verify_locations: %s\n",
+		 ERR_error_string (ERR_get_error (), 0));
 	return -1;
     }
 
@@ -320,7 +321,7 @@ parse_fetch (imap_t * imap, list_t * list)
 			imap->maxuid = uid;
 		}
 		else
-		    puts ("Error, unable to parse UID");
+		    fprintf (stderr, "IMAP error: unable to parse UID\n");
 	    }
 	    else if (!strcmp ("FLAGS", tmp->val))
 	    {
@@ -346,15 +347,15 @@ parse_fetch (imap_t * imap, list_t * list)
 			    else if (!strcmp ("\\Recent", flags->val))
 				mask |= D_RECENT;
 			    else
-				printf ("Warning, unknown flag %s\n",
+				fprintf (stderr, "IMAP error: unknown flag %s\n",
 					flags->val);
 			}
 			else
-			    puts ("Error, unable to parse FLAGS list");
+			    fprintf (stderr, "IMAP error: unable to parse FLAGS list\n");
 		    }
 		}
 		else
-		    puts ("Error, unable to parse FLAGS");
+		    fprintf (stderr, "IMAP error: unable to parse FLAGS\n");
 	    }
 	    else if (!strcmp ("RFC822.SIZE", tmp->val))
 	    {
@@ -400,8 +401,7 @@ parse_response_code (imap_t * imap, char *s)
 	/* RFC2060 says that these messages MUST be displayed
 	 * to the user
 	 */
-	fputs ("***ALERT*** ", stdout);
-	puts (s);
+	fprintf (stderr, "*** IMAP ALERT *** %s\n", s);
     }
 }
 
@@ -414,6 +414,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
     char *cmd;
     char *arg;
     char *arg1;
+    config_t *box;
     int n;
 
     va_start (ap, fmt);
@@ -422,10 +423,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 
     snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp);
     if (Verbose)
-    {
-	fputs (">>> ", stdout);
-	fputs (buf, stdout);
-    }
+	printf (">>> %s", buf);
     n = socket_write (imap->sock, buf, strlen (buf));
     if (n <= 0)
     {
@@ -435,6 +433,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 
     for (;;)
     {
+      next:
 	if (buffer_gets (imap->buf, &cmd))
 	    return -1;
 	if (Verbose)
@@ -446,7 +445,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 	    arg = next_arg (&cmd);
 	    if (!arg)
 	    {
-		puts ("Error, unable to parse untagged command");
+		fprintf (stderr, "IMAP error: unable to parse untagged response\n");
 		return -1;
 	    }
 
@@ -464,17 +463,50 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 	    }
 	    else if (!strcmp ("CAPABILITY", arg))
 	    {
-#if HAVE_LIBSSL
 		while ((arg = next_arg (&cmd)))
 		{
-		    if (!strcmp ("STARTTLS", arg))
+		    if (!strcmp ("UIDPLUS", arg))
+			imap->have_uidplus = 1;
+#if HAVE_LIBSSL
+		    else if (!strcmp ("STARTTLS", arg))
 			imap->have_starttls = 1;
 		    else if (!strcmp ("AUTH=CRAM-MD5", arg))
 			imap->have_cram = 1;
 		    else if (!strcmp ("NAMESPACE", arg))
 			imap->have_namespace = 1;
-		}
 #endif
+		}
+	    }
+	    else if (!strcmp ("LIST", arg))
+	    {
+		list_t *list, *lp;
+		int l;
+
+		list = parse_list (cmd, &cmd);
+		if (list->val == LIST)
+		    for (lp = list->child; lp; lp = lp->next)
+			if (is_atom (lp) &&
+			    !strcasecmp (lp->val, "\\NoSelect"))
+			{
+			    free_list (list);
+			    goto next;
+			}
+		free_list (list);
+		(void) next_arg (&cmd);	/* skip delimiter */
+		arg = next_arg (&cmd);
+		l = strlen (global.folder);
+		if (memcmp (arg, global.folder, l))
+		    goto next;
+		arg += l;
+		for (box = boxes; box; box = box->next)
+		    if (!strcmp (box->box, arg))
+			goto next;
+		box = malloc (sizeof (config_t));
+		memcpy (box, &global, sizeof (config_t));
+		box->path = strdup (arg);
+		box->box = box->path;
+		box->next = boxes;
+		boxes = box;
 	    }
 	    else if ((arg1 = next_arg (&cmd)))
 	    {
@@ -499,7 +531,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 	    }
 	    else
 	    {
-		puts ("Error, unable to parse untagged command");
+		fprintf (stderr, "IMAP error: unable to parse untagged response\n");
 		return -1;
 	    }
 	}
@@ -510,7 +542,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 
 	    if (!imap->cram)
 	    {
-		puts ("Error, not doing CRAM-MD5 authentication");
+		fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n");
 		return -1;
 	    }
 	    resp = cram (cmd, imap->box->user, imap->box->pass);
@@ -535,7 +567,7 @@ imap_exec (imap_t * imap, const char *fmt, ...)
 #endif
 	else if ((size_t) atol (arg) != Tag)
 	{
-	    puts ("wrong tag");
+	    fprintf (stderr, "IMAP error: wrong tag\n");
 	    return -1;
 	}
 	else
@@ -550,79 +582,32 @@ imap_exec (imap_t * imap, const char *fmt, ...)
     /* not reached */
 }
 
-/* `box' is the config info for the maildrop to sync.  `minuid' is the
- * minimum UID to consider.  in normal mode this will be 1, but in --fast
- * mode we only fetch messages newer than the last one seen in the local
- * mailbox.
- */
 imap_t *
-imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
+imap_connect (config_t * cfg)
 {
-  int ret;
-  int s;
+  int s, ret;
   struct sockaddr_in addr;
   struct hostent *he;
+  imap_t *imap;
   char *arg, *rsp;
-  int reuse = 0;
   int preauth = 0;
 #if HAVE_LIBSSL
   int use_ssl = 0;
 #endif
+    int a[2];
 
-  (void) flags;
-
-  if (imap)
-  {
-    /* determine whether or not we can reuse the existing session */
-    if (strcmp (box->host, imap->box->host) ||
-	strcmp (box->user, imap->box->user) ||
-	box->port != imap->box->port
-#if HAVE_LIBSSL
-	/* ensure that security requirements are met */
-	|| (box->require_ssl ^ imap->box->require_ssl)
-	|| (box->require_cram ^ imap->box->require_cram)
-#endif
-       )
-    {
-      /* can't reuse */
-      imap_close (imap);
-      imap = 0;
-    }
-    else
-    {
-      reuse = 1;
-      /* reset mailbox-specific state info */
-      imap->recent = 0;
-      imap->deleted = 0;
-      imap->count = 0;
-      imap->maxuid = 0;
-      free_message (imap->msgs);
-      imap->msgs = 0;
-    }
-  }
-
-  if (!imap)
-  {
     imap = calloc (1, sizeof (imap_t));
+    imap->box = cfg;
     imap->sock = calloc (1, sizeof (Socket_t));
     imap->buf = calloc (1, sizeof (buffer_t));
     imap->buf->sock = imap->sock;
     imap->sock->fd = -1;
-  }
-
-  imap->box = box;
-  imap->minuid = minuid;
-  imap->prefix = "";
-
-  if (!reuse)
-  {
-    int a[2];
 
     /* open connection to IMAP server */
 
-    if (box->tunnel)
+    if (cfg->tunnel)
     {
-      printf ("Starting tunnel '%s'...", box->tunnel);
+      info ("Starting tunnel '%s'...", cfg->tunnel);
       fflush (stdout);
 
       if (socketpair (PF_UNIX, SOCK_STREAM, 0, a))
@@ -639,7 +624,7 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 	}
 	close (a[0]);
 	close (a[1]);
-	execl ("/bin/sh", "sh", "-c", box->tunnel, 0);
+	execl ("/bin/sh", "sh", "-c", cfg->tunnel, 0);
 	_exit (127);
       }
 
@@ -647,90 +632,80 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 
       imap->sock->fd = a[1];
 
-      puts ("ok");
+      info ("ok\n");
     }
     else
     {
       memset (&addr, 0, sizeof (addr));
-      addr.sin_port = htons (box->port);
+      addr.sin_port = htons (cfg->port);
       addr.sin_family = AF_INET;
 
-      printf ("Resolving %s... ", box->host);
+      info ("Resolving %s... ", cfg->host);
       fflush (stdout);
-      he = gethostbyname (box->host);
+      he = gethostbyname (cfg->host);
       if (!he)
       {
 	perror ("gethostbyname");
-	return 0;
+	goto bail;
       }
-      puts ("ok");
+      info ("ok\n");
 
       addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
 
       s = socket (PF_INET, SOCK_STREAM, 0);
 
-      printf ("Connecting to %s:%hu... ", inet_ntoa (addr.sin_addr),
-	      ntohs (addr.sin_port));
+      info ("Connecting to %s:%hu... ", inet_ntoa (addr.sin_addr),
+	     ntohs (addr.sin_port));
       fflush (stdout);
       if (connect (s, (struct sockaddr *) &addr, sizeof (addr)))
       {
+	close (s);
 	perror ("connect");
-	exit (1);
+	goto bail;
       }
-      puts ("ok");
+      info ("ok\n");
 
       imap->sock->fd = s;
     }
-  }
 
-  do
-  {
-    /* if we are reusing the existing connection, we can skip the
-     * authentication steps.
-     */
-    if (!reuse)
-    {
       /* read the greeting string */
       if (buffer_gets (imap->buf, &rsp))
       {
-        puts ("Error, no greeting response");
-        ret = -1;
-        break;
+        fprintf (stderr, "IMAP error: no greeting response\n");
+	goto bail;
       }
       if (Verbose)
         puts (rsp);
       arg = next_arg (&rsp);
       if (!arg || *arg != '*' || (arg = next_arg (&rsp)) == NULL)
       {
-        puts ("Error, invalid greeting response");
-        ret = -1;
-        break;
+        fprintf (stderr, "IMAP error: invalid greeting response\n");
+	goto bail;
       }
       if (!strcmp ("PREAUTH", arg))
         preauth = 1;
       else if (strcmp ("OK", arg) != 0)
       {
-        puts ("Error, unknown greeting response");
-        ret = -1;
-        break;
+        fprintf (stderr, "IMAP error: unknown greeting response\n");
+	goto bail;
       }
 
 #if HAVE_LIBSSL
-      if (box->use_imaps)
+      if (cfg->use_imaps)
 	use_ssl = 1;
       else
       {
 	/* let's see what this puppy can do... */
-	if ((ret = imap_exec (imap, "CAPABILITY")))
-	  break;
+	if (imap_exec (imap, "CAPABILITY"))
+	  goto bail;
 
-	if (box->use_sslv2 || box->use_sslv3 || box->use_tlsv1)
+	if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1)
 	{
 	  /* always try to select SSL support if available */
 	  if (imap->have_starttls)
 	  {
-	    if ((ret = imap_exec (imap, "STARTTLS")))
-	      break;
+	    if (imap_exec (imap, "STARTTLS"))
+	      goto bail;
 	    use_ssl = 1;
 	  }
 	}
@@ -738,40 +713,36 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 
       if (!use_ssl)
       {
-	if (box->require_ssl)
+	if (cfg->require_ssl)
 	{
-	  puts ("Error, SSL support not available");
-	  ret = -1;
-	  break;
+	  fprintf (stderr, "IMAP error: SSL support not available\n");
+	  goto bail;
 	}
-	else if (box->use_sslv2 || box->use_sslv3 || box->use_tlsv1)
-	  puts ("Warning, SSL support not available");
+	else if (cfg->use_sslv2 || cfg->use_sslv3 || cfg->use_tlsv1)
+	  fprintf (stderr, "IMAP warning: SSL support not available\n");
       }
       else
       {
 	/* initialize SSL */
-	if (init_ssl (box))
-	{
-	  ret = -1;
-	  break;
-	}
+	if (init_ssl (cfg))
+	  goto bail;
 
 	imap->sock->ssl = SSL_new (SSLContext);
 	SSL_set_fd (imap->sock->ssl, imap->sock->fd);
-	ret = SSL_connect (imap->sock->ssl);
-	if (ret <= 0)
+	if ((ret = SSL_connect (imap->sock->ssl)) <= 0)
 	{
 	  socket_perror ("connect", imap->sock, ret);
-	  break;
+	  goto bail;
 	}
 
 	/* verify the server certificate */
-	if ((ret = verify_cert (imap->sock->ssl)))
-	  break;
+	if (verify_cert (imap->sock->ssl))
+	  goto bail;
 
 	/* to conform to RFC2595 we need to forget all information
 	 * retrieved from CAPABILITY invocations before STARTTLS.
 	 */
+	imap->have_uidplus = 0;
 	imap->have_namespace = 0;
 	imap->have_cram = 0;
 	imap->have_starttls = 0;
@@ -779,19 +750,19 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 	imap->sock->use_ssl = 1;
 	puts ("SSL support enabled");
 
-	if ((ret = imap_exec (imap, "CAPABILITY")))
-	  break;
+	if (imap_exec (imap, "CAPABILITY"))
+	  goto bail;
       }
 #else
-      if ((ret = imap_exec (imap, "CAPABILITY")))
-	break;
+      if (imap_exec (imap, "CAPABILITY"))
+	goto bail;
 #endif
 
       if (!preauth)
       {
-	puts ("Logging in...");
+	info ("Logging in...\n");
 
-	if (!box->pass)
+	if (!cfg->pass)
 	{
 	  /*
 	   * if we don't have a global password set, prompt the user for
@@ -807,9 +778,9 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 	    }
 	    if (!*global.pass)
 	    {
-	      fprintf (stderr, "Skipping %s, no password", box->path);
+	      fprintf (stderr, "Skipping %s, no password\n", cfg->path);
 	      global.pass = NULL; /* force retry */
-	      break;
+	      goto bail;
 	    }
 	    /*
 	     * getpass() returns a pointer to a static buffer.  make a copy
@@ -817,23 +788,21 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 	     */
 	    global.pass = strdup (global.pass);
 	  }
-	  box->pass = strdup (global.pass);
+	  cfg->pass = strdup (global.pass);
 	}
 
 #if HAVE_LIBSSL
 	if (imap->have_cram)
 	{
-	  puts ("Authenticating with CRAM-MD5");
+	  info ("Authenticating with CRAM-MD5\n");
 	  imap->cram = 1;
-	  if ((ret = imap_exec (imap, "AUTHENTICATE CRAM-MD5")))
-	    break;
+	  if (imap_exec (imap, "AUTHENTICATE CRAM-MD5"))
+	    goto bail;
 	}
 	else if (imap->box->require_cram)
 	{
-	  puts
-	    ("Error, CRAM-MD5 authentication is not supported by server");
-	  ret = -1;
-	  break;
+	  fprintf (stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n");
+	  goto bail;
 	}
 	else
 #endif
@@ -841,58 +810,108 @@ imap_open (config_t * box, unsigned int minuid, imap_t * imap, int flags)
 #if HAVE_LIBSSL
 	  if (!use_ssl)
 #endif
-	    puts
-	      ("*** Warning *** Password is being sent in the clear");
-	  if (
-	      (ret =
-	       imap_exec (imap, "LOGIN \"%s\" \"%s\"", box->user,
-			  box->pass)))
+	    fprintf (stderr, "*** IMAP Warning *** Password is being sent in the clear\n");
+	  if (imap_exec (imap, "LOGIN \"%s\" \"%s\"", cfg->user, cfg->pass))
 	  {
-	    puts ("Error, LOGIN failed");
-	    break;
+	    fprintf (stderr, "IMAP error: LOGIN failed\n");
+	    goto bail;
 	  }
 	}
       }
 
       /* get NAMESPACE info */
-      if (box->use_namespace && imap->have_namespace)
+      if (cfg->use_namespace && imap->have_namespace)
       {
-	if ((ret = imap_exec (imap, "NAMESPACE")))
-	  break;
+	if (imap_exec (imap, "NAMESPACE"))
+	  goto bail;
       }
-    }			/* !reuse */
+      return imap;
+
+ bail:
+  imap_close (imap);
+  return 0;
+}
+
+/* `box' is the config info for the maildrop to sync.  `minuid' is the
+ * minimum UID to consider.  in normal mode this will be 1, but in --fast
+ * mode we only fetch messages newer than the last one seen in the local
+ * mailbox.
+ */
+imap_t *
+imap_open (config_t * box, unsigned int minuid, imap_t * imap, int imap_create)
+{
+  if (imap)
+  {
+    /* determine whether or not we can reuse the existing session */
+    if (strcmp (box->host, imap->box->host) ||
+	strcmp (box->user, imap->box->user) ||
+	box->port != imap->box->port
+#if HAVE_LIBSSL
+	/* ensure that security requirements are met */
+	|| (box->require_ssl ^ imap->box->require_ssl)
+	|| (box->require_cram ^ imap->box->require_cram)
+#endif
+       )
+    {
+      /* can't reuse */
+      imap_close (imap);
+    }
+    else
+    {
+      /* reset mailbox-specific state info */
+      imap->box = box;
+      imap->recent = 0;
+      imap->deleted = 0;
+      imap->count = 0;
+      imap->maxuid = 0;
+      free_message (imap->msgs);
+      imap->msgs = 0;
+      goto gotimap;
+    }
+  }
+  if (!(imap = imap_connect (box)))
+    return 0;
+ gotimap:
 
+  if (global.folder)
+    imap->prefix = !strcmp (box->box, "INBOX") ? "" : global.folder;
+  else
+  {
+    imap->prefix = "";
     /* XXX for now assume personal namespace */
-    if (imap->box->use_namespace && is_list (imap->ns_personal) &&
+    if (imap->box->use_namespace &&
+	is_list (imap->ns_personal) &&
 	is_list (imap->ns_personal->child) &&
 	is_atom (imap->ns_personal->child->child))
-    {
       imap->prefix = imap->ns_personal->child->child->val;
-    }
+  }
 
-    fputs ("Selecting mailbox... ", stdout);
+    info ("Selecting IMAP mailbox... ");
     fflush (stdout);
-    if ((ret = imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)))
-      break;
-    printf ("%d messages, %d recent\n", imap->count, imap->recent);
+    if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box)) {
+      if (imap_create) {
+	if (imap_exec (imap, "CREATE \"%s%s\"", imap->prefix, box->box))
+	  goto bail;
+        if (imap_exec (imap, "SELECT \"%s%s\"", imap->prefix, box->box))
+	  goto bail;
+      } else
+        goto bail;
+    }
+    info ("%d messages, %d recent\n", imap->count, imap->recent);
 
-    puts ("Reading IMAP mailbox index");
+    info ("Reading IMAP mailbox index\n");
+    imap->minuid = minuid;
     if (imap->count > 0)
     {
-      if ((ret = imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)",
-			    imap->minuid)))
-	break;
+      if (imap_exec (imap, "UID FETCH %d:* (FLAGS RFC822.SIZE)", minuid))
+	goto bail;
     }
-  }
-  while (0);
 
-  if (ret)
-  {
-    imap_close (imap);
-    imap = 0;
-  }
+    return imap;
 
-  return imap;
+ bail:
+  imap_close (imap);
+  return 0;
 }
 
 void
@@ -900,8 +919,11 @@ imap_close (imap_t * imap)
 {
   if (imap)
   {
-    imap_exec (imap, "LOGOUT");
-    close (imap->sock->fd);
+    if (imap->sock->fd != -1)
+    {
+      imap_exec (imap, "LOGOUT");
+      close (imap->sock->fd);
+    }
     free (imap->sock);
     free (imap->buf);
     free_message (imap->msgs);
@@ -1009,7 +1031,7 @@ imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
 	 * "* 2 RECENT"
 	 *
 	 */
-	printf ("skipping untagged response: %s\n", arg);
+	info ("IMAP info: skipping untagged response: %s\n", arg);
 	continue;
       }
 
@@ -1017,7 +1039,7 @@ imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
 	;
       if (!arg)
       {
-	puts ("parse error getting size");
+	fprintf (stderr, "IMAP error: parse error getting size\n");
 	return -1;
       }
       bytes = strtol (arg + 1, 0, 10);
@@ -1077,7 +1099,7 @@ imap_fetch_message (imap_t * imap, unsigned int uid, int fd)
       arg = next_arg (&cmd);
       if (!arg || (size_t) atoi (arg) != Tag)
       {
-	puts ("wrong tag");
+	fprintf (stderr, "IMAP error: wrong tag\n");
 	return -1;
       }
       arg = next_arg (&cmd);
@@ -1124,108 +1146,119 @@ imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox)
 int
 imap_append_message (imap_t * imap, int fd, message_t * msg)
 {
-  char buf[1024];
-  size_t len;
-  size_t sofar = 0;
-  int lines = 0;
-  char flagstr[128];
+  char *fmap;
+  int extra, uid, tuidl = 0;
+  char flagstr[128], tuid[128];
   char *s;
   size_t i;
-  size_t start, end;
+  size_t start;
+  size_t len, sbreak = 0, ebreak = 0;
   char *arg;
+  struct timeval tv;
+  pid_t pid = getpid();
 
+  len = msg->size;
   /* ugh, we need to count the number of newlines */
-  while (sofar < msg->size)
+  fmap = (char *)mmap (0, len, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (!fmap)
   {
-    len = msg->size - sofar;
-    if (len > sizeof (buf))
-      len = sizeof (buf);
-    len = read (fd, buf, len);
-    if (len == (size_t) - 1)
-    {
-      perror ("read");
-      return -1;
-    }
-    for (i = 0; i < len; i++)
-      if (buf[i] == '\n')
-	lines++;
-    sofar += len;
+    perror ("mmap");
+    return -1;
+  }
+
+  extra = 0, i = 0;
+  if (!imap->have_uidplus)
+  {
+   nloop:
+    start = i;
+    while (i < len)
+      if (fmap[i++] == '\n')
+      {
+	extra++;
+	if (i - 1 == start)
+	{
+	  sbreak = ebreak = i - 1;
+	  goto mktid;
+	}
+	if (!memcmp (fmap + start, "X-TUID: ", 8))
+	{
+	  extra -= (ebreak = i) - (sbreak = start) + 1;
+	  goto mktid;
+	}
+	goto nloop;
+      }
+    /* invalid mesasge */
+    goto bail;
+   mktid:
+    gettimeofday (&tv, 0);
+    tuidl = sprintf (tuid, "X-TUID: %08lx%05lx%04x\r\n", 
+			   tv.tv_sec, tv.tv_usec, pid);
+    extra += tuidl;
   }
+  for (; i < len; i++)
+    if (fmap[i] == '\n')
+      extra++;
 
   flagstr[0] = 0;
   if (msg->flags)
   {
-    strcpy (flagstr, "(");
     if (msg->flags & D_DELETED)
-      snprintf (flagstr + strlen (flagstr),
-		sizeof (flagstr) - strlen (flagstr), "%s\\Deleted",
-		flagstr[1] ? " " : "");
+      strcat (flagstr," \\Deleted");
     if (msg->flags & D_ANSWERED)
-      snprintf (flagstr + strlen (flagstr),
-		sizeof (flagstr) - strlen (flagstr), "%s\\Answered",
-		flagstr[1] ? " " : "");
+      strcat (flagstr," \\Answered");
     if (msg->flags & D_SEEN)
-      snprintf (flagstr + strlen (flagstr),
-		sizeof (flagstr) - strlen (flagstr), "%s\\Seen",
-		flagstr[1] ? " " : "");
+      strcat (flagstr," \\Seen");
     if (msg->flags & D_FLAGGED)
-      snprintf (flagstr + strlen (flagstr),
-		sizeof (flagstr) - strlen (flagstr), "%s\\Flagged",
-		flagstr[1] ? " " : "");
+      strcat (flagstr," \\Flagged");
     if (msg->flags & D_DRAFT)
-      snprintf (flagstr + strlen (flagstr),
-		sizeof (flagstr) - strlen (flagstr), "%s\\Draft",
-		flagstr[1] ? " " : "");
-    snprintf (flagstr + strlen (flagstr),
-	      sizeof (flagstr) - strlen (flagstr), ") ");
+      strcat (flagstr," \\Draft");
+    flagstr[0] = '(';
+    strcat (flagstr,") ");
   }
 
   send_server (imap->sock, "APPEND %s%s %s{%d}",
-	       imap->prefix, imap->box->box, flagstr, msg->size + lines);
+	       imap->prefix, imap->box->box, flagstr, len + extra);
 
   if (buffer_gets (imap->buf, &s))
-    return -1;
+    goto bail;
   if (Verbose)
     puts (s);
 
   if (*s != '+')
   {
-    puts ("Error, expected `+' from server (aborting)");
-    return -1;
+    fprintf (stderr, "IMAP error: expected `+' from server (aborting)\n");
+    goto bail;
   }
 
-  /* rewind */
-  lseek (fd, 0, 0);
-
-  sofar = 0;
-  while (sofar < msg->size)
+  i = 0;
+  if (!imap->have_uidplus)
   {
-    len = msg->size - sofar;
-    if (len > sizeof (buf))
-      len = sizeof (buf);
-    len = read (fd, buf, len);
-    if (len == (size_t) - 1)
-      return -1;
-    start = 0;
-    while (start < len)
-    {
-      end = start;
-      while (end < len && buf[end] != '\n')
-	end++;
-      if (start != end)
-	socket_write (imap->sock, buf + start, end - start);
-      /* only send a crlf if we actually hit the end of a line.  we
-       * might be in the middle of a line in which case we don't
-       * send one.
-       */
-      if (end != len)
+   n1loop:
+    start = i;
+    while (i < sbreak)
+      if (fmap[i++] == '\n')
+      {
+	socket_write (imap->sock, fmap + start, i - 1 - start);
 	socket_write (imap->sock, "\r\n", 2);
-      start = end + 1;
-    }
-    sofar += len;
+	goto n1loop;
+      }
+    socket_write (imap->sock, tuid, tuidl);
+    i = ebreak;
   }
+ n2loop:
+  start = i;
+  while (i < len)
+    if (fmap[i++] == '\n')
+    {
+      socket_write (imap->sock, fmap + start, i - 1 - start);
+      socket_write (imap->sock, "\r\n", 2);
+      goto n2loop;
+    }
+  socket_write (imap->sock, fmap + start, len - start);
   socket_write (imap->sock, "\r\n", 2);
 
+  munmap (fmap, len);
+
   for (;;)
   {
     if (buffer_gets (imap->buf, &s))
@@ -1241,13 +1274,11 @@ imap_append_message (imap_t * imap, int fd, message_t * msg)
     }
     else if (atoi (arg) != (int) Tag)
     {
-      puts ("wrong tag");
+      fprintf (stderr, "IMAP error: wrong tag\n");
       return -1;
     }
     else
     {
-      int uid;
-
       arg = next_arg (&s);
       if (strcmp (arg, "OK"))
 	return -1;
@@ -1257,7 +1288,7 @@ imap_append_message (imap_t * imap, int fd, message_t * msg)
       arg++;
       if (strcasecmp ("APPENDUID", arg))
       {
-	puts ("Error, expected APPENDUID");
+	fprintf (stderr, "IMAP error: expected APPENDUID\n");
 	break;
       }
       arg = next_arg (&s);
@@ -1265,7 +1296,7 @@ imap_append_message (imap_t * imap, int fd, message_t * msg)
 	break;
       if (atoi (arg) != (int) imap->uidvalidity)
       {
-	puts ("Error, UIDVALIDITY doesn't match APPENDUID");
+	fprintf (stderr, "IMAP error: UIDVALIDITY doesn't match APPENDUID\n");
 	return -1;
       }
       arg = next_arg (&s);
@@ -1281,5 +1312,58 @@ imap_append_message (imap_t * imap, int fd, message_t * msg)
     }
   }
 
+  /* didn't receive an APPENDUID */
+  send_server (imap->sock,
+	       "UID SEARCH HEADER X-TUID %08lx%05lx%04x", 
+	       tv.tv_sec, tv.tv_usec, pid);
+  uid = 0;
+  for (;;)
+  {
+    if (buffer_gets (imap->buf, &s))
+      return -1;
+
+    if (Verbose)
+      puts (s);
+
+    arg = next_arg (&s);
+    if (*arg == '*')
+    {
+      arg = next_arg (&s);
+      if (!strcmp (arg, "SEARCH"))
+      {
+	arg = next_arg (&s);
+	if (!arg)
+	{
+	  fprintf (stderr, "IMAP error: incomplete SEARCH response\n");
+	  return -1;
+	}
+	uid = atoi (arg);
+      }
+    }
+    else if (atoi (arg) != (int) Tag)
+    {
+      fprintf (stderr, "IMAP error: wrong tag\n");
+      return -1;
+    }
+    else
+    {
+      arg = next_arg (&s);
+      if (strcmp (arg, "OK"))
+	return -1;
+      return uid;
+    }
+  }
+
   return 0;
+
+ bail:
+  munmap (fmap, len);
+  return -1;
 }
+
+int
+imap_list (imap_t * imap)
+{
+  return imap_exec (imap, "LIST \"\" \"%s*\"", global.folder);
+}
+

+ 142 - 176
isync.1

@@ -1,6 +1,6 @@
 .ig
 \" isync - IMAP4 to maildir mailbox synchronizer
-\" Copyright (C) 2000-2 Michael R. Elkins <me@mutt.org>
+\" Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
 \"
 \"  This program is free software; you can redistribute it and/or modify
 \"  it under the terms of the GNU General Public License as published by
@@ -16,66 +16,70 @@
 \"  along with this program; if not, write to the Free Software
 \"  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 ..
-.TH isync 1 "2002 Jun 17"
+.TH isync 1 "2002 Oct 14"
 ..
 .SH NAME
 isync - synchronize IMAP4 and maildir mailboxes
 ..
 .SH SYNOPSIS
-.B isync
-[
-.I options...
-]
-.I mailbox
-[
-.I mailbox ...
-]
+\fBisync\fR [\fIoptions...\fR] \fImailbox\fR [\fImailbox ...\fR]
+.br
+\fBisync\fR [\fIoptions...\fR] \fI-a\fR
+.br
+\fBisync\fR [\fIoptions...\fR] \fI-l\fR
 ..
 .SH DESCRIPTION
-.B isync
-is a command line application which synchronizes a local maildir-style
-mailbox with a remote IMAP4 mailbox, suitable for use in IMAP-disconnected
-mode.  Multiple copies of the remote IMAP4 mailbox can be maintained, and
-all flags are synchronized.
+\fBisync\fR is a command line application which synchronizes a local
+maildir-style mailbox with a remote IMAP4 mailbox, suitable for use in
+IMAP-disconnected mode.  Multiple copies of the remote IMAP4 mailbox can 
+be maintained, and all flags are synchronized.
 ..
 .SH OPTIONS
 .TP
+\fB-c\fR, \fB--config\fR \fIfile\fR
+Read configuration from \fIfile\fR.
+By default, the configuration is read from ~/.isyncrc if it exists.
+.TP
+\fB-1\fR, \fB--one-to-one\fR
+Instead of using the mailbox specifications in ~/.isyncrc, isync will pick up
+all mailboxes from the local directory and remote folder and map them 1:1 
+onto each other according to their names.
+.TP
+\fB-I\fR, \fB--inbox\fR \fImailbox\fR
+Exception to the 1:1 mapping created by -1: the special IMAP mailbox \fIINBOX\fR
+is mapped to the local \fImailbox\fR (relative to the maildir).
+.TP
 \fB-a\fR, \fB--all\fR
-Synchronize all mailboxes specified in the user's ~/.isyncrc.
+Synchronize all mailboxes (either specified in ~/.isyncrc or determined by the
+1:1 mapping).
 .TP
-\fB-C\fR, \fB--create\fR
+\fB-l\fR, \fB--list\fR
+Don't synchronize anything, but list all mailboxes and exit.
+.TP
+\fB-L\fR, \fB--create-local\fR
 Automatically create the local maildir-style mailbox if it doesn't already
 exist.
 .TP
-\fB-c\fR, \fB--config\fR \fIfile\fR
-Read configuration from
-.I file
-By default, configuration is read from ~/.isyncrc if it exists.
-.TP
-.B -d, --delete
-Causes
-.B isync
-to delete messages from the local maildir mailbox which do not exist on the
-IMAP server.  By default,
-.I dead
-messages are
-.B not
-deleted.
+\fB-R\fR, \fB--create-remote\fR
+Automatically create the remote IMAP mailbox if it doesn't already exist.
+.TP
+\fB-C\fR, \fB--create\fR
+Automatically create any mailboxes if they doesn't already exist.
+.TP
+\fB-d\fR, \fB--delete\fR
+Causes \fBisync\fR to delete messages from the local maildir mailbox 
+which do not exist on the IMAP server.  By default, \fIdead\fR messages
+are \fBnot\fR deleted.
 .TP
 \fB-e\fR, \fB--expunge\fR
-Causes
-.B isync
-to permanently remove all messages marked for deletion in both the local
-maildir mailbox and the remote IMAP mailbox.  By default, messages are
-.B not
-expunged.
+Causes \fBisync\fR to permanently remove all messages marked for deletion
+in both the local maildir mailbox and the remote IMAP mailbox.  By default,
+messages are \fBnot\fR expunged.
 .TP
 \fB-f\fR, \fB--fast\fR
-Causes
-.B isync
-to skip the step of synchronzing message flags between the local maildir
-mailbox and the IMAP mailbox.  Only new messages existing on the server will
-be fetched into the local mailbox.
+Causes \fBisync\fR to skip the step of synchronzing message flags between the
+local maildir mailbox and the IMAP mailbox.  Only new messages existing on the
+server will be fetched into the local mailbox.
 .TP
 \fB-h\fR, \fB--help\fR
 Displays a summary of command line options
@@ -96,44 +100,34 @@ Specifies the hostname of the IMAP server
 \fB-u\fR, \fB--user\fR \fIuser\fR
 Specifies the login name to access the IMAP server (default: $USER)
 .TP
-.B -v, --version
-Displays
-.B isync
-version information
+\fB-M\fR, \fB--maildir\fR \fIdir\fR
+Specifies the location for your local mailboxes.
+.TP
+\fB-F\fR, \fB--folder\fR \fIfolder\fR/
+Specifies the location for your remote mailboxes.
+.TP
+\fB-v\fR, \fB--version\fR
+Displays \fBisync\fR version information.
 .TP
-.B -V, --verbose
-Enables
-.I verbose
-mode, which displays the IMAP4 network traffic.
+\fB-V\fR, \fB--verbose\fR
+Enables \fIverbose\fR mode, which displays the IMAP4 network traffic.
 ..
 .SH CONFIGURATION
-.B isync
-reads
-.I ~/.isyncrc
-to load default configuration data.  Each line of the configuration file
-consists of a command.  The following commands are understood:
+\fBisync\fR reads \fI~/.isyncrc\fR to load default configuration data.
+Each line of the configuration file consists of a command.
+The following commands are understood:
 .TP
 \fBMailbox\fR \fIpath\fR
 Defines a local maildir mailbox.  All configuration commands following this
-line, up until the next
-.I Mailbox
-command, apply to this mailbox only.
+line, up until the next \fIMailbox\fR command, apply to this mailbox only.
 ..
 .TP
 \fBHost\fR \fB[\fRimaps:\fB]\fR\fIname\fR
 Defines the DNS name or IP address of the IMAP server.  If the hostname is
-prefixed with
-.I imaps:
-the connection is assumed to be a SSL connection to port 993 (though you can
-change this by placing a
-.B Port
-command
-.B after
-the
-.B Host
-command.  Note that some servers support SSL on the default port 143.
-.B isync
-will always attempt to use SSL if available.
+prefixed with \fIimaps:\fR the connection is assumed to be a SSL connection
+to port 993 (though you can change this by placing a \fBPort\fR command
+\fBafter\fR the \fBHost\fR command.  Note that some servers support SSL on 
+the default port 143. \fBisync\fR will always attempt to use SSL if available.
 ..
 .TP
 \fBPort\fR \fIport\fR
@@ -150,12 +144,9 @@ Defines the login name on the IMAP server (Default: current user)
 ..
 .TP
 \fBPass\fR \fIpassword\fR
-Defines the password for
-.I username
-on the IMAP server.  Note that this option is
-.B NOT
-required.  If no password is specified in the configuration file,
-.B isync
+Defines the password for \fIusername\fR on the IMAP server.
+Note that this option is \fBNOT\fR required.
+If no password is specified in the configuration file, \fBisync\fR
 will prompt you for it.
 ..
 .TP
@@ -164,64 +155,58 @@ Defines an alias for the mailbox which can be used as a shortcut on the
 command line.
 ..
 .TP
-\fBCopyDeletedTo\fR \fIstring\fR
+\fBCopyDeletedTo\fR \fImailbox\fR
 Specifies the remote IMAP mailbox to copy deleted messages prior to
 expunging (Default: none).
 ..
 .TP
-\fBDelete\fR \fIyes|no\fR
+\fBDelete\fR \fIyes\fR|\fIno\fR
 Specifies whether messages in the local copy of the mailbox which don't
 exist on the server are automatically deleted.  (Default: no).
+\fBNOTE:\fR  The \fI-d\fR command line option overrides this setting when 
+set to \fIno\fR.
 ..
 .TP
-\fBExpunge\fR \fIyes|no\fR
+\fBExpunge\fR \fIyes\fR|\fIno\fR
 Specifies whether deleted messages are expunged by default (Default: no).
-\fBNOTE:\fR  The
-.I -e
-command line option overrides this setting when set to
-\fIno\fR.
-..
-.TP
-\fBMailDir\fR \fIstring\fR
-Specifies the location for your mailboxes if a relative path is
-specified in a
-.I Mailbox
-command (Default: \fI~\fR).
-.B NOTE:
-This directive is only meaningful the in
-.I global
+\fBNOTE:\fR  The \fI-e\fR command line option overrides this setting when 
+set to \fIno\fR.
+..
+.TP
+\fBMailDir\fR \fIdirectory\fR
+Specifies the location of your local mailboxes if a relative path is
+specified in a \fIMailbox\fR command (Default: \fI~\fR).
+\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
+section (see below).
+..
+.TP
+\fBFolder\fR \fIdirectory\fR/
+Specifies the location of your IMAP mailboxes 
+specified in \fIBox\fR commands (Default: \fI""\fR).
+\fBNOTE:\fR You \fBmust\fR append the hierarchy delimiter (usually
+a slash) to this specification.
+\fBNOTE 2:\fR This directive is only meaningful in the \fIglobal\fR
 section (see below).
 ..
 .TP
 \fBMaxMessages\fR \fIcount\fR
-Sets the number of messages
-.B isync
-should keep in a mailbox.
+Sets the number of messages \fBisync\fR should keep in a mailbox.
 This is useful for mailboxes where you keep a complete archive on the
 server, but want to mirror only the last messages (for instance, for mailing
 lists.)
 The messages that were the first to arrive in the mailbox (independent of the
-actual date of the message) will automatically be deleted if you tell
-pass
-.B isync
-the delete (-d, --delete) flag.
+actual date of the message) will automatically be deleted if you
+pass \fBisync\fR the delete (-d, --delete) flag.
 Messages that are flagged (marked as important) will not be automatically
 deleted.
-If
-.I count
-is 0, the maximum number of messages is
-.B unlimited
+If \fIcount\fR is 0, the maximum number of messages is \fBunlimited\fR
 (Default: 0).
 ..
 .TP
 \fBMaxSize\fR \fIbytes\fR
-Sets a threshold for the maximum message size (in bytes) for which
-.B isync
+Sets a threshold for the maximum message size (in bytes) for which \fBisync\fR
 should fetch from the server.  This is useful for weeding out messages with
-large attachments.  If
-.I bytes
-is 0, the maximum file size is
-.B unlimited.
+large attachments.  If \fIbytes\fR is 0, the maximum file size is \fBunlimited\fR.
 ..
 .TP
 \fBTunnel\fR \fIcommand\fR
@@ -229,79 +214,64 @@ Specify a command to run to establish a connection rather than opening a TCP
 socket.  This allows you to run an IMAP session over an SSH tunnel, for
 example.
 .TP
-\fBUseNamespace\fR \fIyes|no\fR
-Selects whether
-.B isync
-should select mailboxes using the namespace given by the NAMESPACE command.
-This is useful with broken IMAP servers. (Default:
-.I yes
-)
+\fBUseNamespace\fR \fIyes\fR|\fIno\fR
+Selects whether \fBisync\fR should select mailboxes using the namespace given 
+by the NAMESPACE command. This is useful with broken IMAP servers. (Default:
+\fIyes\fR)
 ..
 .TP
-\fBRequireCRAM\fR \fIyes|no\fR
-If set to
-.I yes
-,
-.B isync
-will require that the server accept CRAM-MD5 intead of PLAIN to authenticate
-the user.
+\fBRequireCRAM\fR \fIyes\fR|\fIno\fR
+If set to \fIyes\fR, \fBisync\fR will require that the server accept CRAM-MD5 
+intead of PLAIN to authenticate the user.
 ..
 .TP
-\fBRequireSSL\fR \fIyes|no\fR
-.B isync
-will abort the connection if a TLS/SSL session to the IMAP
-server can not be established.  (Default:
-.I yes
-)
+\fBRequireSSL\fR \fIyes\fR|\fIno\fR
+\fBisync\fR will abort the connection if a TLS/SSL session to the IMAP
+server can not be established.  (Default: \fIyes\fR)
 ..
 .TP
 \fBCertificateFile\fR \fIpath\fR
 File containing X.509 CA certificates used to verify server identities.
 ..
 .TP
-\fBUseSSLv2\fR \fIyes|no\fR
-Should
-.B isync
-use SSLv2 for communication with the IMAP server over SSL?  (Default:
-.I yes
-if the imaps port is used, otherwise
-.I no
-)
+\fBUseSSLv2\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use SSLv2 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseSSLv3\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use SSLv3 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR if the imaps port is used, otherwise \fIno\fR)
+..
+.TP
+\fBUseTLSv1\fR \fIyes\fR|\fIno\fR
+Should \fBisync\fR use TLSv1 for communication with the IMAP server over SSL?
+(Default: \fIyes\fR)
 ..
 .TP
-\fBUseSSLv3\fR \fIyes|no\fR
-Should
-.B isync
-use SSLv3 for communication with the IMAP server over SSL?  (Default:
-.I yes
-if the imaps port is used, otherwise
-.I no
-)
+\fBOneToOne\fR
+\fBisync\fR will ignore any \fIMailbox\fR specifications and instead pick up
+all mailboxes from the local \fIMailDir\fR and remote \fIFolder\fR and map 
+them 1:1 onto each other according to their names.
+\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
+section (see below).
 ..
 .TP
-\fBUseTLSv1\fR \fIyes|no\fR
-Should
-.B isync
-use TLSv1 for communication with the IMAP server over SSL?  (Default:
-.I yes
-)
+\fBInbox\fR \fImailbox\fR
+Exception to the OneToOne mapping: the special IMAP mailbox \fIINBOX\fR
+is mapped to the local \fImailbox\fR (relative to the \fIMailDir\fR).
+\fBNOTE:\fR This directive is only meaningful in the \fIglobal\fR
+section (see below).
 ..
 .P
-Configuration commands that appear prior to the first
-.B Mailbox
-command are considered to be
-.I global
+Configuration commands that appear prior to the first \fBMailbox\fR
+command are considered to be \fIglobal\fR
 options which are used as defaults when those specific options are not
 specifically set for a defined Mailbox.  For example, if you use the same
-login name for several IMAP servers, you can put a
-.B User
-command before the first
-.B Mailbox
-command, and then leave out the
-.B User
-command in the sections for each mailbox.
-.B isync
-will then use the global value by default.
+login name for several IMAP servers, you can put a \fBUser\fR command before 
+the first \fBMailbox\fR command, and then leave out the \fBUser\fR command 
+in the sections for each mailbox.
+\fBisync\fR will then use the global value by default.
 ..
 .SH FILES
 .TP
@@ -309,20 +279,16 @@ will then use the global value by default.
 Default configuration file
 ..
 .SH BUGS
-.B isync
-does not use NFS-safe locking.  It will correctly prevent concurrent
-synchronization of a mailbox on the same host, but not across NFS.
+\fBisync\fR does not use NFS-safe locking.  It will correctly prevent 
+concurrent synchronization of a mailbox on the same host, but not across NFS.
 .P
 When synchronizing multiple mailboxes on the same IMAP server, it is not
 possible to select different SSL options for each mailbox.  Only the options
 from the first mailbox are applied since the SSL session is reused.
 .P
-If new mail arrives in the IMAP mailbox after
-.B isync
+If new mail arrives in the IMAP mailbox after \fBisync\fR
 has retrieved the initial message list, the new mail will not be fetched
-until the next time
-.B isync
-is invoked.
+until the next time \fBisync\fR is invoked.
 .P
 It is currently impossible to unset the \\Flagged attribute of a message
 once it is set.  It has to be manually unset everywhere since isync
@@ -331,12 +297,12 @@ message.
 .P
 The ndbm database created for each mailbox is not portable across different
 architectures.  It currently stores the UID in host byte order.
+.P
+The configuration file takes precedence over command line options.
 .SH SEE ALSO
 mutt(1), maildir(5)
 .P
-Up to date information on
-.B isync
-can be found at
+Up to date information on \fBisync\fR can be found at
 http://www.cs.hmc.edu/~me/isync/.
 ..
 .SH AUTHOR

+ 10 - 3
isync.h

@@ -62,7 +62,9 @@ struct config
     int port;
     char *user;
     char *pass;
+    char *folder;
     char *box;
+    char *inbox;
     char *alias;
     char *copy_deleted_to;
     char *tunnel;
@@ -151,6 +153,7 @@ typedef struct
     list_t *ns_personal;
     list_t *ns_other;
     list_t *ns_shared;
+    unsigned int have_uidplus:1;
     unsigned int have_namespace:1;
 #if HAVE_LIBSSL
     unsigned int have_cram:1;
@@ -163,7 +166,6 @@ imap_t;
 /* flags for sync_mailbox */
 #define	SYNC_DELETE	(1<<0)	/* delete local that don't exist on server */
 #define SYNC_EXPUNGE	(1<<1)	/* don't fetch deleted messages */
-#define SYNC_QUIET	(1<<2)	/* only display critical errors */
 
 /* flags for maildir_open */
 #define OPEN_FAST	(1<<0)	/* fast open - don't parse */
@@ -173,7 +175,10 @@ extern config_t global;
 extern config_t *boxes;
 extern unsigned int Tag;
 extern char Hostname[256];
-extern int Verbose;
+extern int Verbose, Quiet;
+
+extern void info (const char *, ...);
+extern void infoc (char);
 
 #if HAVE_LIBSSL
 extern SSL_CTX *SSLContext;
@@ -185,7 +190,7 @@ char *next_arg (char **);
 
 int sync_mailbox (mailbox_t *, imap_t *, int, unsigned int, unsigned int);
 
-void load_config (const char *);
+void load_config (const char *, int *);
 char * expand_strdup (const char *s);
 config_t *find_box (const char *);
 void free_config (void);
@@ -195,8 +200,10 @@ int imap_copy_message (imap_t * imap, unsigned int uid, const char *mailbox);
 int imap_fetch_message (imap_t *, unsigned int, int);
 int imap_set_flags (imap_t *, unsigned int, unsigned int);
 int imap_expunge (imap_t *);
+imap_t *imap_connect (config_t *);
 imap_t *imap_open (config_t *, unsigned int, imap_t *, int);
 int imap_append_message (imap_t *, int, message_t *);
+int imap_list (imap_t *);
 
 mailbox_t *maildir_open (const char *, int flags);
 int maildir_expunge (mailbox_t *, int);

+ 4 - 4
maildir.c

@@ -242,7 +242,7 @@ maildir_open (const char *path, int flags)
 		if (p->uid > m->maxuid)
 		    m->maxuid = p->uid;
 	    }
-	    else
+	    else /* XXX remove. every locally generated message triggers this */
 		puts ("Warning, no UID for message");
 
 	    if (s)
@@ -341,7 +341,7 @@ maildir_clean_tmp (const char *mbox)
     char path[_POSIX_PATH_MAX];
     DIR *dirp;
     struct dirent *entry;
-    struct stat info;
+    struct stat st;
     time_t now;
 
     snprintf (path, sizeof (path), "%s/tmp", mbox);
@@ -359,10 +359,10 @@ maildir_clean_tmp (const char *mbox)
     while ((entry = readdir (dirp)))
     {
 	snprintf (path, sizeof (path), "%s/tmp/%s", mbox, entry->d_name);
-	if (stat (path, &info))
+	if (stat (path, &st))
 	    fprintf (stderr, "maildir_clean_tmp: stat: %s: %s (errno %d)\n",
 		     path, strerror (errno), errno);
-	else if (S_ISREG (info.st_mode) && now - info.st_ctime >= _24_HOURS)
+	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.

+ 163 - 57
main.c

@@ -18,29 +18,61 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <sys/types.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <stdarg.h>
 #include <limits.h>
 #include <pwd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
 #include <ctype.h>
+#include <dirent.h>
 #include "isync.h"
 
 #if HAVE_GETOPT_LONG
 #define _GNU_SOURCE
 #include <getopt.h>
 
+int Quiet;
+
+void
+info (const char *msg, ...)
+{
+  va_list va;
+
+  if (!Quiet)
+  {
+    va_start (va, msg);
+    vprintf (msg, va);
+    va_end (va);
+  }
+}
+
+void
+infoc (char c)
+{
+    if (!Quiet)
+	putchar (c);
+}
+
 struct option Opts[] = {
     {"all", 0, NULL, 'a'},
+    {"list", 0, NULL, 'l'},
     {"config", 1, NULL, 'c'},
     {"create", 0, NULL, 'C'},
+    {"create-local", 0, NULL, 'L'},
+    {"create-remote", 0, NULL, 'R'},
     {"delete", 0, NULL, 'd'},
     {"expunge", 0, NULL, 'e'},
     {"fast", 0, NULL, 'f'},
     {"help", 0, NULL, 'h'},
     {"remote", 1, NULL, 'r'},
+    {"folder", 1, NULL, 'F'},
+    {"maildir", 1, NULL, 'M'},
+    {"one-to-one", 0, NULL, '1'},
+    {"inbox", 1, NULL, 'I'},
     {"host", 1, NULL, 's'},
     {"port", 1, NULL, 'p'},
     {"quiet", 0, NULL, 'q'},
@@ -59,36 +91,49 @@ int Verbose = 0;
 static void
 version (void)
 {
-    printf ("%s %s\n", PACKAGE, VERSION);
+    puts (PACKAGE " " VERSION);
     exit (0);
 }
 
 static void
-usage (void)
+usage (int code)
 {
-    printf ("%s %s IMAP4 to maildir synchronizer\n", PACKAGE, VERSION);
-    puts ("Copyright (C) 2000-2 Michael R. Elkins <me@mutt.org>");
-    printf ("usage: %s [ flags ] mailbox [mailbox ...]\n", PACKAGE);
-    puts ("  -a, --all	Synchronize all defined mailboxes");
-    puts ("  -c, --config CONFIG	read an alternate config file (default: ~/.isyncrc)");
-    puts ("  -C, --create		create local maildir mailbox if nonexistent");
-    puts ("  -d, --delete		delete local msgs that don't exist on the server");
-    puts ("  -e, --expunge		expunge	deleted messages from the server");
-    puts ("  -f, --fast		only fetch new messages");
-    puts ("  -h, --help		display this help message");
-    puts ("  -p, --port PORT	server IMAP port");
-    puts ("  -r, --remote BOX	remote mailbox");
-    puts ("  -s, --host HOST	IMAP server address");
-    puts ("  -u, --user USER	IMAP user name");
-    puts ("  -v, --version		display version");
-    puts ("  -V, --verbose		verbose mode (display network traffic)");
-    puts ("Compile time options:");
+    fputs (
+PACKAGE " " VERSION " IMAP4 to maildir synchronizer\n"
+"Copyright (C) 2000-2 Michael R. Elkins <me@mutt.org>\n"
+"usage:\n"
+" " PACKAGE " [ flags ] mailbox [mailbox ...]\n"
+" " PACKAGE " [ flags ] -a\n"
+" " PACKAGE " [ flags ] -l\n"
+"  -a, --all		synchronize all defined mailboxes\n"
+"  -l, --list		list all defined mailboxes and exit\n"
+"  -L, --create-local	create local maildir mailbox if nonexistent\n"
+"  -R, --create-remote	create remote imap mailbox if nonexistent\n"
+"  -C, --create		create both local and remote mailboxes if nonexistent\n"
+"  -d, --delete		delete local msgs that don't exist on the server\n"
+"  -e, --expunge		expunge	deleted messages from the server\n"
+"  -f, --fast		only fetch new messages\n"
+"  -r, --remote BOX	remote mailbox\n"
+"  -F, --folder DIR	remote IMAP folder containing mailboxes\n"
+"  -M, --maildir DIR	local directory containing mailboxes\n"
+"  -1, --one-to-one	map every IMAP <folder>/box to <maildir>/box\n"
+"  -I, --inbox BOX	map IMAP INBOX to <maildir>/BOX (exception to -1)\n"
+"  -s, --host HOST	IMAP server address\n"
+"  -p, --port PORT	server IMAP port\n"
+"  -u, --user USER	IMAP user name\n"
+"  -c, --config CONFIG	read an alternate config file (default: ~/.isyncrc)\n"
+"  -V, --verbose		verbose mode (display network traffic)\n"
+"  -q, --quiet		don't display progress info\n"
+"  -v, --version		display version\n"
+"  -h, --help		display this help message\n"
+"Compile time options:\n"
 #if HAVE_LIBSSL
-    puts ("  +HAVE_LIBSSL");
+"  +HAVE_LIBSSL\n"
 #else
-    puts ("  -HAVE_LIBSSL");
+"  -HAVE_LIBSSL\n"
 #endif
-    exit (0);
+	, code ? stderr : stdout);
+    exit (code);
 }
 
 char *
@@ -141,32 +186,33 @@ main (int argc, char **argv)
     int delete = 0;
     char *config = 0;
     struct passwd *pw;
-    int quiet = 0;
     int all = 0;
-    int create = 0;
+    int list = 0;
+    int o2o = 0;
+    int mbox_open_mode = 0;
+    int imap_create = 0;
 
     pw = getpwuid (getuid ());
 
     /* defaults */
     memset (&global, 0, sizeof (global));
+    /* XXX the precedence is borked: 
+       it's defaults < cmdline < file instead of defaults < file < cmdline */
     global.port = 143;
     global.box = "INBOX";
+    global.folder = "";
     global.user = strdup (pw->pw_name);
     global.maildir = strdup (pw->pw_dir);
-    global.max_size = 0;
-    global.max_messages = 0;
     global.use_namespace = 1;
 #if HAVE_LIBSSL
     /* this will probably annoy people, but its the best default just in
      * case people forget to turn it on
      */
     global.require_ssl = 1;
-    global.use_sslv2 = 0;
-    global.use_sslv3 = 0;
     global.use_tlsv1 = 1;
 #endif
 
-#define FLAGS "aCc:defhp:qu:r:s:vV"
+#define FLAGS "alCLRc:defhp:qu:r:F:M:1I:s:vV"
 
 #if HAVE_GETOPT_LONG
     while ((i = getopt_long (argc, argv, FLAGS, Opts, NULL)) != -1)
@@ -176,11 +222,24 @@ main (int argc, char **argv)
     {
 	switch (i)
 	{
+	    case 'l':
+		list = 1;
+		/* plopp */
 	    case 'a':
 		all = 1;
 		break;
+	    case '1':
+		o2o = 1;
+		break;
 	    case 'C':
-		create = 1;
+		mbox_open_mode |= OPEN_CREATE;
+		imap_create = 1;
+		break;
+	    case 'L':
+		mbox_open_mode |= OPEN_CREATE;
+		break;
+	    case 'R':
+		imap_create = 1;
 		break;
 	    case 'c':
 		config = optarg;
@@ -192,18 +251,29 @@ main (int argc, char **argv)
 		expunge = 1;
 		break;
 	    case 'f':
+		mbox_open_mode |= OPEN_FAST;
 		fast = 1;
 		break;
 	    case 'p':
 		global.port = atoi (optarg);
 		break;
 	    case 'q':
-		quiet = 1;
+		Quiet = 1;
 		Verbose = 0;
 		break;
 	    case 'r':
 		global.box = optarg;
 		break;
+	    case 'F':
+		global.folder = optarg;
+		break;
+	    case 'M':
+		free (global.maildir);
+		global.maildir = strdup (optarg);
+		break;
+	    case 'I':
+		global.inbox = optarg;
+		break;
 	    case 's':
 #if HAVE_LIBSSL
 		if (!strncasecmp ("imaps:", optarg, 6))
@@ -223,26 +293,70 @@ main (int argc, char **argv)
 		break;
 	    case 'v':
 		version ();
+	    case 'h':
+		usage (0);
 	    default:
-		usage ();
+		usage (1);
 	}
     }
 
     if (!argv[optind] && !all)
     {
-	puts ("No mailbox specified");
-	usage ();
+	fprintf (stderr, "No mailbox specified");
+	usage (1);
     }
 
     gethostname (Hostname, sizeof (Hostname));
 
-    load_config (config);
+    load_config (config, &o2o);
+
+    if (all && o2o)
+    {
+	DIR *dir;
+	struct dirent *de;
+
+	if (global.inbox) {
+	    boxes = malloc (sizeof (config_t));
+	    memcpy (boxes, &global, sizeof (config_t));
+	    boxes->box = "INBOX";
+	    boxes->path = global.inbox;
+	}
+
+	if (!(dir = opendir (global.maildir))) {
+	    fprintf (stderr, "%s: %s\n", global.maildir, strerror(errno));
+	    return 1;
+	}
+	while ((de = readdir (dir))) {
+	    if (*de->d_name == '.')
+		continue;
+	    if (global.inbox && !strcmp (global.inbox, de->d_name))
+		continue;
+	    box = malloc (sizeof (config_t));
+	    memcpy (box, &global, sizeof (config_t));
+	    box->path = strdup (de->d_name);
+	    box->box = box->path;
+	    box->next = boxes;
+	    boxes = box;
+	}
+	closedir (dir);
 
+	imap = imap_connect (&global);
+	if (!imap)
+	    goto bork;
+	if (imap_list (imap))
+	    goto bork;
+    }
+    if (list)
+    {
+	for (box = boxes; box; box = box->next)
+	    puts (box->path);
+	exit (0);
+    }
     for (box = boxes; (all && box) || (!all && argv[optind]); optind++)
     {
 	if (!all)
 	{
-	    if (NULL == (box = find_box (argv[optind])))
+	    if (o2o || NULL == (box = find_box (argv[optind])))
 	    {
 		/* if enough info is given on the command line, don't worry if
 		 * the mailbox isn't defined.
@@ -257,25 +371,23 @@ main (int argc, char **argv)
 		}
 		global.path = argv[optind];
 		box = &global;
+		if (o2o)
+		    global.box = 
+			(global.inbox && !strcmp (global.path, global.inbox)) ?
+			"INBOX" : global.path;
 	    }
 	}
 
 	do {
-	    if (!quiet)
-		printf ("Reading %s\n", box->path);
-	    i = 0;
-	    if (fast)
-		i |= OPEN_FAST;
-	    if (create)
-		i |= OPEN_CREATE;
-	    mail = maildir_open (box->path, i);
+	    info ("Mailbox %s\n", box->path);
+	    mail = maildir_open (box->path, mbox_open_mode);
 	    if (!mail)
 	    {
 		fprintf (stderr, "%s: unable to open mailbox\n", box->path);
 		break;
 	    }
 
-	    imap = imap_open (box, fast ? mail->maxuid + 1 : 1, imap, 0);
+	    imap = imap_open (box, fast ? mail->maxuid + 1 : 1, imap, imap_create);
 	    if (!imap)
 	    {
 		fprintf (stderr, "%s: skipping mailbox due to IMAP error\n",
@@ -283,12 +395,8 @@ main (int argc, char **argv)
 		break;
 	    }
 
-	    if (!quiet)
-		puts ("Synchronizing");
-	    i = 0;
-	    if (quiet)
-		i |= SYNC_QUIET;
-	    i |= (delete || box->delete) ? SYNC_DELETE : 0;
+	    info ("Synchronizing\n");
+	    i = (delete || box->delete) ? SYNC_DELETE : 0;
 	    i |= (expunge || box->expunge) ? SYNC_EXPUNGE : 0;
 	    if (sync_mailbox (mail, imap, i, box->max_size, box->max_messages))
 	    {
@@ -305,18 +413,15 @@ main (int argc, char **argv)
 		    (imap->deleted || mail->deleted))
 		{
 		    /* remove messages marked for deletion */
-		    if (!quiet)
-			printf ("Expunging %d messages from server\n",
-				imap->deleted);
+		    info ("Expunging %d messages from server\n", imap->deleted);
 		    if (imap_expunge (imap))
 		    {
 			imap_close (imap);
 			imap = NULL;
 			break;
 		    }
-		    if (!quiet)
-			printf ("Expunging %d messages from local mailbox\n",
-				mail->deleted);
+		    info ("Expunging %d messages from local mailbox\n",
+			  mail->deleted);
 		    if (maildir_expunge (mail, 0))
 			break;
 		}
@@ -344,6 +449,7 @@ main (int argc, char **argv)
     /* gracefully close connection to the IMAP server */
     imap_close (imap);
 
+  bork:
     free_config ();
 
 #if DEBUG

+ 23 - 35
sync.c

@@ -119,14 +119,11 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 		  continue;
 		}
 
-		if ((flags & SYNC_QUIET) == 0)
-		{
-		    if (!upload)
-			fputs ("Uploading messages", stdout);
-		    fputc ('.', stdout);
-		    fflush (stdout);
-		    upload++;
-		}
+		if (!upload)
+		    info ("Uploading messages");
+		infoc ('.');
+		fflush (stdout);
+		upload++;
 
 		/* upload the message if its not too big */
 		snprintf (path, sizeof (path), "%s/%s/%s", mbox->path,
@@ -139,16 +136,14 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 		if (imap->box->max_size > 0
 		    && sb.st_size > imap->box->max_size)
 		{
-		    if ((flags & SYNC_QUIET) == 0)
-			printf
-			    ("Warning, local message is too large (%lu), skipping...\n",
-			     (unsigned long) sb.st_size);
+		    info ("Warning, local message is too large (%lu), skipping...\n",
+			  (unsigned long) sb.st_size);
 		    continue;
 		}
 		fd = open (path, O_RDONLY);
 		if (fd == -1)
 		{
-		    printf ("Error, unable to open %s: %s (errno %d)\n",
+		    fprintf (stderr, "Error, unable to open %s: %s (errno %d)\n",
 			    path, strerror (errno), errno);
 		    continue;
 		}
@@ -156,8 +151,11 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 		cur->size = sb.st_size;
 		cur->uid = imap_append_message (imap, fd, cur);
 		/* if the server gave us back a uid, update the db */
-		if (cur->uid != (unsigned int) -1)
+		if (cur->uid != (unsigned int) -1) {
 		    set_uid (mbox->db, cur->file, cur->uid);
+		    if (!cur->uid)
+			printf("warning: no uid for new messge %s\n", cur->file);
+		}
 
 		close (fd);
 	    }
@@ -174,9 +172,8 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 	    /* if the user doesn't want local msgs deleted when they don't
 	     * exist on the server, warn that such messages exist.
 	     */
-	    else if ((flags & SYNC_QUIET) == 0)
-		printf ("Warning, uid %u doesn't exist on server\n",
-			cur->uid);
+	    else
+		info ("Warning, uid %u doesn't exist on server\n", cur->uid);
 	    continue;
 	}
 	tmp->processed = 1;
@@ -255,13 +252,10 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
     }
 
     if (upload)
-	fprintf (stdout, " %d messages.\n", upload);
+	info (" %d messages.\n", upload);
 
-    if ((flags & SYNC_QUIET) == 0)
-    {
-	fputs ("Fetching new messages", stdout);
-	fflush (stdout);
-    }
+    info ("Fetching new messages");
+    fflush (stdout);
 
     if (max_msgs == 0)
 	max_msgs = UINT_MAX;
@@ -308,10 +302,8 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 
 	    if (max_size && cur->size > max_size)
 	    {
-		if ((flags & SYNC_QUIET) == 0)
-		    printf
-			("Warning, message skipped because it is too big (%u)\n",
-			 cur->size);
+		info ("Warning, message skipped because it is too big (%u)\n",
+		      cur->size);
 		continue;
 	    }
 
@@ -348,12 +340,9 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 	    if (fd < 0)
 		continue;
 
-	    if ((flags & SYNC_QUIET) == 0)
-	    {
-		/* give some visual feedback that something is happening */
-		fputs (".", stdout);
-		fflush (stdout);
-	    }
+	    /* give some visual feedback that something is happening */
+	    infoc ('.');
+	    fflush (stdout);
 	    fetched++;
 
 	    ret = imap_fetch_message (imap, cur->uid, fd);
@@ -389,8 +378,7 @@ sync_mailbox (mailbox_t * mbox, imap_t * imap, int flags,
 	}
     }
 
-    if ((flags & SYNC_QUIET) == 0)
-	printf ("  %d messages\n", fetched);
+    info ("  %d messages\n", fetched);
 
     return 0;
 }