From 7cd74a1179405bdfa627fb016b287095f7707aa0 Mon Sep 17 00:00:00 2001 From: Michael Elkins Date: Wed, 30 Oct 2002 02:23:05 +0000 Subject: [PATCH] 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. ;) --- .cvsignore | 12 ++ config.c | 205 +++++++----------- imap.c | 614 ++++++++++++++++++++++++++++++----------------------- isync.1 | 318 +++++++++++++-------------- isync.h | 13 +- maildir.c | 8 +- main.c | 220 ++++++++++++++----- sync.c | 58 ++--- 8 files changed, 782 insertions(+), 666 deletions(-) create mode 100644 .cvsignore diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..28e497f --- /dev/null +++ b/.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 diff --git a/config.c b/config.c index 9a16d57..0d8f86c 100644 --- a/config.c +++ b/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); } diff --git a/imap.c b/imap.c index 828ca48..6bb49f9 100644 --- a/imap.c +++ b/imap.c @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -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); +} + diff --git a/isync.1 b/isync.1 index d3b38d7..5c891a9 100644 --- a/isync.1 +++ b/isync.1 @@ -1,6 +1,6 @@ .ig \" isync - IMAP4 to maildir mailbox synchronizer -\" Copyright (C) 2000-2 Michael R. Elkins +\" Copyright (C) 2000-2002 Michael R. Elkins \" \" 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 diff --git a/isync.h b/isync.h index 906b5c3..7df6b97 100644 --- a/isync.h +++ b/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); diff --git a/maildir.c b/maildir.c index d69c8ed..ec92fc9 100644 --- a/maildir.c +++ b/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. diff --git a/main.c b/main.c index bff5f8a..1d75d7f 100644 --- a/main.c +++ b/main.c @@ -18,29 +18,61 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include #include +#include #include #include #include #include #include #include +#include #include "isync.h" #if HAVE_GETOPT_LONG #define _GNU_SOURCE #include +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 "); - 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 \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 /box to /box\n" +" -I, --inbox BOX map IMAP INBOX to /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 diff --git a/sync.c b/sync.c index c431018..a65d6e3 100644 --- a/sync.c +++ b/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; }