#!/bin/sh -e ## 30-aysnc-imap.dpatch by Theodore Y. Ts'o ## ## DP: Add the beginnings of asynchronous IMAP support. So far, we only ## DP: support asynchronous flag setting, since that's the easist. ## DP: Eventually we need to support asynchronous message fetches and ## DP: uploads. if [ $# -ne 1 ]; then echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" exit 1 fi [ -f debian/patches/00patch-opts ] && . debian/patches/00patch-opts patch_opts="${patch_opts:--f --no-backup-if-mismatch}" case "$1" in -patch) patch $patch_opts -p1 < $0;; -unpatch) patch $patch_opts -p1 -R < $0;; *) echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" exit 1;; esac exit 0 @DPATCH@ diff -urNad /usr/projects/isync/SF-cvs/isync/src/imap.c isync/src/imap.c --- /usr/projects/isync/SF-cvs/isync/src/imap.c 2004-01-14 18:31:49.000000000 -0500 +++ isync/src/imap.c 2004-01-14 18:39:18.000000000 -0500 @@ -3,6 +3,7 @@ * isync - IMAP4 to maildir mailbox synchronizer * Copyright (C) 2000-2002 Michael R. Elkins * Copyright (C) 2002-2003 Oswald Buddenhagen + * Copyright (C) 2004 Theodore Ts'o * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,13 +36,33 @@ #include #include #include +#include #include +#include #include #include #if HAVE_LIBSSL # include #endif +struct imap_cmd { + unsigned int tag; + char *cmd; + int flags; + int response; + struct imap_cmd *next; + int (*complete_fn) (imap_t *imap, struct imap_cmd * cmd); +}; + +#define IMAP_FLAG_DONE 0x0001 + +static struct imap_cmd *in_progress = NULL; +static int num_in_progress = 0; +int max_in_progress_high = 50; +int max_in_progress_low = 10; + +static struct imap_cmd *get_cmd_result(imap_t *imap); + const char *Flags[] = { "\\Seen", "\\Answered", @@ -199,6 +220,22 @@ return write (sock->fd, buf, len); } +static int +socket_pending(Socket_t *sock) +{ + int num = -1; + + if (ioctl(sock->fd, FIONREAD, &num) < 0) + return -1; + if (num > 0) + return num; +#if HAVE_LIBSSL + if (sock->use_ssl) + return SSL_pending (sock->ssl); +#endif + return 0; +} + static void socket_perror (const char *func, Socket_t *sock, int ret) { @@ -301,16 +338,20 @@ } static int -parse_fetch (imap_t * imap, list_t * list) +parse_fetch (imap_t * imap, char *cmd) { - list_t *tmp; + list_t *tmp, *list; unsigned int uid = 0; unsigned int mask = 0; unsigned int size = 0; message_t *cur; - if (!is_list (list)) + list = parse_list (cmd, 0); + + if (!is_list (list)) { + free_list(list); return -1; + } for (tmp = list->child; tmp; tmp = tmp->next) { @@ -325,6 +366,7 @@ if (uid < imap->minuid) { /* already saw this message */ + free_list(list); return 0; } else if (uid > imap->maxuid) @@ -387,6 +429,7 @@ cur->flags = mask; cur->size = size; + free_list(list); return 0; } @@ -415,39 +458,114 @@ } } -static int -imap_exec (imap_t * imap, const char *fmt, ...) +static struct imap_cmd *issue_imap_cmd(imap_t *imap, + const char *fmt, ...) { va_list ap; - char tmp[256]; - char buf[256]; - char *cmd; - char *arg; - char *arg1; - config_t *box; + char tmp[1024]; + char buf[1024]; + struct imap_cmd *cmd; int n; + cmd = malloc(sizeof(struct imap_cmd)); + if (!cmd) + return NULL; + + cmd->tag = ++Tag; + cmd->flags = 0; + cmd->response = 0; + cmd->complete_fn = 0; + va_start (ap, fmt); vsnprintf (tmp, sizeof (tmp), fmt, ap); va_end (ap); - snprintf (buf, sizeof (buf), "%d %s\r\n", ++Tag, tmp); + cmd->cmd = malloc(strlen(tmp)+1); + if (cmd->cmd) + strcpy(cmd->cmd, tmp); + + snprintf (buf, sizeof (buf), "%d %s\r\n", cmd->tag, tmp); if (Verbose) { - printf (">>> %s", buf); + if (num_in_progress) + printf("(%d in progress) ", num_in_progress); + if (strncmp(tmp, "LOGIN", 5)) + printf (">>> %s", buf); + else + printf (">>> LOGIN USERNAME PASSWORD\n"); fflush (stdout); } n = socket_write (imap->sock, buf, strlen (buf)); if (n <= 0) { socket_perror ("write", imap->sock, n); - return -1; + free(cmd); + return NULL; + } + cmd->next = in_progress; + in_progress = cmd; + num_in_progress++; + if ((num_in_progress > max_in_progress_high) || + socket_pending(imap->sock)) { + while ((num_in_progress > max_in_progress_low) || + socket_pending(imap->sock)) { + if (Verbose && socket_pending(imap->sock)) + printf("(Socket input pending)\n"); + get_cmd_result(imap); + } } + return cmd; +} + +static struct imap_cmd *find_imap_cmd(unsigned int tag) +{ + struct imap_cmd *cmd, *prev; + + for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) { + if (tag == cmd->tag) { + return cmd; + } + prev = cmd; + } + return NULL; +} + +static void dequeue_imap_cmd(unsigned int tag) +{ + struct imap_cmd *cmd, *prev; + + for (prev=NULL, cmd=in_progress; cmd; cmd = cmd->next) { + if (tag != cmd->tag) { + prev = cmd; + continue; + } + if (prev) + prev->next = cmd->next; + else + in_progress = cmd->next; + cmd->next = 0; + if (cmd->cmd) + free(cmd->cmd); + cmd->cmd = 0; + free(cmd); + break; + } +} + +static struct imap_cmd *get_cmd_result(imap_t *imap) +{ + char *cmd; + char *arg; + char *arg1; + config_t *box; + int n; + unsigned int tag; + struct imap_cmd *cmdp; for (;;) { next: if (buffer_gets (imap->buf, &cmd)) - return -1; + return NULL; arg = next_arg (&cmd); if (*arg == '*') @@ -456,7 +574,7 @@ if (!arg) { fprintf (stderr, "IMAP error: unable to parse untagged response\n"); - return -1; + return NULL; } if (!strcmp ("NAMESPACE", arg)) @@ -528,23 +646,14 @@ imap->recent = atoi (arg); else if (!strcmp ("FETCH", arg1)) { - list_t *list; - - list = parse_list (cmd, 0); - - if (parse_fetch (imap, list)) - { - free_list (list); - return -1; - } - - free_list (list); + if (parse_fetch (imap, cmd)) + return NULL; } } else { fprintf (stderr, "IMAP error: unable to parse untagged response\n"); - return -1; + return NULL; } } #if HAVE_LIBSSL @@ -555,7 +664,7 @@ if (!imap->cram) { fprintf (stderr, "IMAP error, not doing CRAM-MD5 authentication\n"); - return -1; + return NULL; } resp = cram (cmd, imap->box->user, imap->box->pass); @@ -568,34 +677,88 @@ if (n <= 0) { socket_perror ("write", imap->sock, n); - return -1; + return NULL; } n = socket_write (imap->sock, "\r\n", 2); if (n <= 0) { socket_perror ("write", imap->sock, n); - return -1; + return NULL; } imap->cram = 0; } #endif - else if ((size_t) atol (arg) != Tag) - { - fprintf (stderr, "IMAP error: wrong tag\n"); - return -1; - } - else - { - arg = next_arg (&cmd); - parse_response_code (imap, cmd); - if (!strcmp ("OK", arg)) - return 0; - return -1; + else { + tag = (unsigned int) atol (arg); + cmdp = find_imap_cmd(tag); + if (!cmdp) { + fprintf(stderr, "IMAP error: sent unknown tag: %u\n", + tag); + return NULL; + } + arg = next_arg (&cmd); + if (strncmp("OK", arg, 2)) { + fprintf(stderr, "tag %u returned error: %s\n", + tag, arg); + cmdp->response = -1; + } + parse_response_code (imap, cmd); + num_in_progress--; + cmdp->flags |= IMAP_FLAG_DONE; + if (Verbose) + printf("Tag %u completed with response %d\n", + cmdp->tag, cmdp->response); + return cmdp; } } /* not reached */ } +static void flush_imap_cmds(imap_t *imap) +{ + struct imap_cmd *cmdp; + + while (num_in_progress) { + if (in_progress && in_progress->flags & IMAP_FLAG_DONE) { + dequeue_imap_cmd(in_progress->tag); + continue; + } + cmdp = get_cmd_result(imap); + if (!cmdp) + printf("Error trying to flush pending imap cmds\n"); + } +} + +static int +imap_exec (imap_t * imap, const char *fmt, ...) +{ + va_list ap; + char tmp[1024]; + struct imap_cmd *cmdp, *waitp; + int result; + + va_start (ap, fmt); + vsnprintf (tmp, sizeof (tmp), fmt, ap); + va_end (ap); + + cmdp = issue_imap_cmd(imap, "%s", tmp); + if (!cmdp) + return -1; + + if (cmdp->flags & IMAP_FLAG_DONE) + return cmdp->response; + + do { + waitp = get_cmd_result(imap); + } while (waitp->tag != cmdp->tag); + + result = cmdp->response; + dequeue_imap_cmd(cmdp->tag); + + return cmdp->response; + +} + #ifdef HAVE_LIBSSL static int start_tls (imap_t *imap, config_t * cfg) @@ -1039,6 +1202,7 @@ size_t n; char buf[1024]; + flush_imap_cmds(imap); send_server (imap->sock, "UID FETCH %d BODY.PEEK[]", uid); for (;;) @@ -1160,7 +1324,9 @@ (buf[0] != 0) ? " " : "", Flags[i]); } - return imap_exec (imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf); + if (issue_imap_cmd(imap, "UID STORE %d +FLAGS.SILENT (%s)", uid, buf)) + return 0; + return -1; } int @@ -1249,6 +1415,7 @@ strcat (flagstr,") "); } + flush_imap_cmds(imap); send_server (imap->sock, "APPEND %s%s %s{%d}", imap->prefix, imap->box->box, flagstr, len + extra); @@ -1341,6 +1508,7 @@ } /* didn't receive an APPENDUID */ + flush_imap_cmds(imap); send_server (imap->sock, "UID SEARCH HEADER X-TUID %08lx%05lx%04x", tv.tv_sec, tv.tv_usec, pid);