]> Pileus Git - ~andy/fetchmail/blobdiff - imap.c
i18n fixes, cs.po updates, by Miloslav Trmac.
[~andy/fetchmail] / imap.c
diff --git a/imap.c b/imap.c
index 7ea1ef98d22ae710e5dae40bde618862c080a3fe..9ebbe7ab183a43d87981a27b26961b0a6edd701d 100644 (file)
--- a/imap.c
+++ b/imap.c
 #include  <ctype.h>
 #if defined(STDC_HEADERS)
 #include  <stdlib.h>
+#include  <limits.h>
+#include  <errno.h>
 #endif
 #include  "fetchmail.h"
 #include  "socket.h"
 
 #include  "i18n.h"
 
-#if OPIE_ENABLE
-#endif /* OPIE_ENABLE */
-
-#ifndef strstr         /* glibc-2.1 declares this as a macro */
-extern char *strstr(); /* needed on sysV68 R3V7.1. */
-#endif /* strstr */
-
 /* imap_version values */
 #define IMAP2          -1      /* IMAP2 or IMAP2BIS, RFC1176 */
 #define IMAP4          0       /* IMAP4 rev 0, RFC1730 */
 #define IMAP4rev1      1       /* IMAP4 rev 1, RFC2060 */
 
-static int count, unseen, deletions, imap_version, preauth; 
-static int expunged, expunge_period, saved_timeout;
-static flag do_idle;
+static int count = 0, recentcount = 0, unseen = 0, deletions = 0;
+static int recentcount_ok = 0;
+static unsigned int startcount = 1;
+static int expunged, expunge_period, saved_timeout = 0;
+static int imap_version, preauth;
+static flag do_idle, has_idle;
 static char capabilities[MSGBUFSIZE+1];
 static unsigned int *unseen_messages;
 
@@ -49,15 +47,31 @@ static int imap_ok(int sock, char *argbuf)
 
        /* all tokens in responses are caseblind */
        for (cp = buf; *cp; cp++)
-           if (islower(*cp))
-               *cp = toupper(*cp);
+           if (islower((unsigned char)*cp))
+               *cp = toupper((unsigned char)*cp);
 
        /* interpret untagged status responses */
        if (strstr(buf, "* CAPABILITY"))
+       {
            strncpy(capabilities, buf + 12, sizeof(capabilities));
+           capabilities[sizeof(capabilities)-1] = '\0';
+       }
        else if (strstr(buf, "EXISTS"))
        {
            count = atoi(buf+2);
+           /*
+            * Don't trust the message count passed by the server.
+            * Without this check, it might be possible to do a
+            * DNS-spoofing attack that would pass back a ridiculous 
+            * count, and allocate a malloc area that would overlap
+            * a portion of the stack.
+            */
+           if (count > INT_MAX/sizeof(int))
+           {
+               report(stderr, "bogus message count!");
+               return(PS_PROTOCOL);
+           }
+
            /*
             * Nasty kluge to handle RFC2177 IDLE.  If we know we're idling
             * we can't wait for the tag matching the IDLE; we have to tell the
@@ -67,15 +81,31 @@ static int imap_ok(int sock, char *argbuf)
             */
            if (stage == STAGE_IDLE)
            {
-               /* we do our own write and report here to disable tagging */
-               SockWrite(sock, "DONE\r\n", 6);
-               if (outlevel >= O_MONITOR)
-                   report(stdout, "IMAP> DONE\n");
+               /* If IDLE isn't supported, we were only sending NOOPs anyway. */
+               if (has_idle)
+               {
+                   /* we do our own write and report here to disable tagging */
+                   SockWrite(sock, "DONE\r\n", 6);
+                   if (outlevel >= O_MONITOR)
+                       report(stdout, "IMAP> DONE\n");
+               }
 
                mytimeout = saved_timeout;
                stage = STAGE_FETCH;
            }
        }
+       /* a space is required to avoid confusion with the \Recent flag */
+       else if (strstr(buf, " RECENT"))
+       {
+            recentcount_ok = 1;
+           recentcount = atoi(buf+2);
+       }
+        else if (strstr(buf, "EXPUNGE") && !strstr(buf, "OK"))
+        {
+            count -= atoi(buf+2);
+            if (count < 0)
+                count = 0;
+        }
        else if (strstr(buf, "PREAUTH"))
            preauth = TRUE;
        /*
@@ -108,20 +138,20 @@ static int imap_ok(int sock, char *argbuf)
        char    *cp;
 
        /* skip the tag */
-       for (cp = buf; !isspace(*cp); cp++)
+       for (cp = buf; !isspace((unsigned char)*cp); cp++)
            continue;
-       while (isspace(*cp))
+       while (isspace((unsigned char)*cp))
            cp++;
 
-        if (strncmp(cp, "OK", 2) == 0)
+        if (strncasecmp(cp, "OK", 2) == 0)
        {
            if (argbuf)
                strcpy(argbuf, cp);
            return(PS_SUCCESS);
        }
-       else if (strncmp(cp, "BAD", 3) == 0)
+       else if (strncasecmp(cp, "BAD", 3) == 0)
            return(PS_ERROR);
-       else if (strncmp(cp, "NO", 2) == 0)
+       else if (strncasecmp(cp, "NO", 2) == 0)
        {
            if (stage == STAGE_GETAUTH) 
                return(PS_AUTHFAIL);    /* RFC2060, 6.2.2 */
@@ -133,7 +163,7 @@ static int imap_ok(int sock, char *argbuf)
     }
 }
 
-#if NTLM_ENABLE
+#ifdef NTLM_ENABLE
 #include "ntlm.h"
 
 static tSmbNtlmAuthRequest   request;             
@@ -181,7 +211,7 @@ static int do_imap_ntlm(int sock, struct query *ctl)
     if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
        return result;
   
-    len = from64tobits ((unsigned char*)&challenge, msgbuf);
+    len = from64tobits ((char*)&challenge, msgbuf, sizeof(challenge));
     
     if (outlevel >= O_DEBUG)
        dumpSmbNtlmAuthChallenge(stdout, &challenge);
@@ -227,43 +257,70 @@ static int imap_canonicalize(char *result, char *raw, int maxlen)
     return(i);
 }
 
-static int imap_getauth(int sock, struct query *ctl, char *greeting)
-/* apply for connection authorization */
+static void capa_probe(int sock, struct query *ctl)
+/* set capability variables from a CAPA probe */
 {
-    int ok = 0;
+    int        ok;
 
     /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
     capabilities[0] = '\0';
     if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
     {
+       char    *cp;
+
+       /* capability checks are supposed to be caseblind */
+       for (cp = capabilities; *cp; cp++)
+           *cp = toupper(*cp);
+
        /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
           should expect a response in mixed-case */
-       if (strstr(capabilities, "IMAP4REV1") ||
-           strstr(capabilities, "IMAP4rev1"))
+       if (strstr(capabilities, "IMAP4REV1"))
        {
            imap_version = IMAP4rev1;
            if (outlevel >= O_DEBUG)
-               report(stdout, _("Protocol identified as IMAP4 rev 1\n"));
+               report(stdout, GT_("Protocol identified as IMAP4 rev 1\n"));
        }
        else
        {
            imap_version = IMAP4;
            if (outlevel >= O_DEBUG)
-               report(stdout, _("Protocol identified as IMAP4 rev 0\n"));
+               report(stdout, GT_("Protocol identified as IMAP4 rev 0\n"));
        }
     }
     else if (ok == PS_ERROR)
     {
        imap_version = IMAP2;
        if (outlevel >= O_DEBUG)
-           report(stdout, _("Protocol identified as IMAP2 or IMAP2BIS\n"));
+           report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n"));
+    }
+
+    /* 
+     * Handle idling.  We depend on coming through here on startup
+     * and after each timeout (including timeouts during idles).
+     */
+    if (ctl->idle)
+    {
+       do_idle = TRUE;
+       if (strstr(capabilities, "IDLE"))
+       {
+           has_idle = TRUE;
+       }
+       if (outlevel >= O_VERBOSE)
+           report(stdout, GT_("will idle after poll\n"));
     }
-    else
-       return(ok);
 
     peek_capable = (imap_version >= IMAP4);
+}
 
-    /* 
+static int imap_getauth(int sock, struct query *ctl, char *greeting)
+/* apply for connection authorization */
+{
+    int ok = 0;
+#ifdef SSL_ENABLE
+    flag did_stls = FALSE;
+#endif /* SSL_ENABLE */
+
+    /*
      * Assumption: expunges are cheap, so we want to do them
      * after every message unless user said otherwise.
      */
@@ -272,16 +329,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     else
        expunge_period = 1;
 
-    /* 
-     * Handle idling.  We depend on coming through here on startup
-     * and after each timeout (including timeouts during idles).
-     */
-    if (strstr(capabilities, "IDLE") && ctl->idle)
-    {
-       do_idle = TRUE;
-       if (outlevel >= O_VERBOSE)
-           report(stdout, _("will idle after poll\n"));
-    }
+    capa_probe(sock, ctl);
 
     /* 
      * If either (a) we saw a PREAUTH token in the greeting, or
@@ -293,6 +341,48 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
         return(PS_SUCCESS);
     }
 
+#ifdef SSL_ENABLE
+    if ((!ctl->sslproto || !strcmp(ctl->sslproto,"tls1"))
+        && !ctl->use_ssl
+        && strstr(capabilities, "STARTTLS"))
+    {
+           char *realhost;
+
+           realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
+           ok = gen_transact(sock, "STARTTLS");
+
+           /* We use "tls1" instead of ctl->sslproto, as we want STARTTLS,
+            * not other SSL protocols
+            */
+           if (ok == PS_SUCCESS &&
+              SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1)
+           {
+              if (!ctl->sslproto && !ctl->wehaveauthed)
+              {
+                  ctl->sslproto = xstrdup("");
+                  /* repoll immediately */
+                  return(PS_REPOLL);
+              }
+               report(stderr,
+                      GT_("SSL connection failed.\n"));
+               return(PS_AUTHFAIL);
+           }
+          did_stls = TRUE;
+
+          /*
+           * RFC 2595 says this:
+           *
+           * "Once TLS has been started, the client MUST discard cached
+           * information about server capabilities and SHOULD re-issue the
+           * CAPABILITY command.  This is necessary to protect against
+           * man-in-the-middle attacks which alter the capabilities list prior
+           * to STARTTLS.  The server MAY advertise different capabilities
+           * after STARTTLS."
+           */
+          capa_probe(sock, ctl);
+    }
+#endif /* SSL_ENABLE */
+
     /*
      * Time to authenticate the user.
      * Try the protocol variants that don't require passwords first.
@@ -303,7 +393,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     if ((ctl->server.authenticate == A_ANY 
         || ctl->server.authenticate == A_GSSAPI)
        && strstr(capabilities, "AUTH=GSSAPI"))
-       if(ok = do_gssauth(sock, "AUTHENTICATE", ctl->server.truename, ctl->remotename))
+       if(ok = do_gssauth(sock, "AUTHENTICATE", "imap", ctl->server.truename, ctl->remotename))
        {
            /* SASL cancellation of authentication */
            gen_send(sock, "*");
@@ -337,9 +427,8 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
      * in a challenge-response.
      */
 
-    if ((ctl->server.authenticate == A_ANY 
-        || ctl->server.authenticate == A_CRAM_MD5)
-       && strstr(capabilities, "AUTH=CRAM-MD5"))
+    if ((ctl->server.authenticate == A_ANY && strstr(capabilities, "AUTH=CRAM-MD5"))
+       || ctl->server.authenticate == A_CRAM_MD5)
     {
        if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
        {
@@ -352,24 +441,25 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
            return ok;
     }
 
-#if OPIE_ENABLE
+#ifdef OPIE_ENABLE
     if ((ctl->server.authenticate == A_ANY 
         || ctl->server.authenticate == A_OTP)
-       && strstr(capabilities, "AUTH=X-OTP"))
+       && strstr(capabilities, "AUTH=X-OTP")) {
        if ((ok = do_otp(sock, "AUTHENTICATE", ctl)))
        {
            /* SASL cancellation of authentication */
            gen_send(sock, "*");
            if(ctl->server.authenticate != A_ANY)
                 return ok;
-       }
-       else
+       } else {
            return ok;
+       }
+    }
 #else
-    if (ctl->server.authenticate == A_NTLM)
+    if (ctl->server.authenticate == A_OTP)
     {
        report(stderr, 
-          _("Required OTP capability not compiled into fetchmail\n"));
+          GT_("Required OTP capability not compiled into fetchmail\n"));
     }
 #endif /* OPIE_ENABLE */
 
@@ -391,7 +481,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     if (ctl->server.authenticate == A_NTLM)
     {
        report(stderr, 
-          _("Required NTLM capability not compiled into fetchmail\n"));
+          GT_("Required NTLM capability not compiled into fetchmail\n"));
     }
 #endif /* NTLM_ENABLE */
 
@@ -400,14 +490,19 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN")))
     {
        report(stderr, 
-              _("Required LOGIN capability not supported by server\n"));
+              GT_("Required LOGIN capability not supported by server\n"));
     }
 #endif /* __UNUSED__ */
 
-    /* we're stuck with sending the password en clair */
-    if ((ctl->server.authenticate == A_ANY 
-        || ctl->server.authenticate == A_PASSWORD) 
-       && !strstr (capabilities, "LOGINDISABLED"))
+    /* 
+     * We're stuck with sending the password en clair.
+     * The reason for this odd-looking logic is that some
+     * servers return LOGINDISABLED even though login 
+     * actually works.  So arrange things in such a way that
+     * setting auth passwd makes it ignore this capability.
+     */
+    if((ctl->server.authenticate==A_ANY&&!strstr(capabilities,"LOGINDISABLED"))
+       || ctl->server.authenticate == A_PASSWORD)
     {
        /* these sizes guarantee no buffer overflow */
        char    remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1];
@@ -415,9 +510,19 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
        imap_canonicalize(remotename, ctl->remotename, NAMELEN);
        imap_canonicalize(password, ctl->password, PASSWORDLEN);
 
-       strcpy(shroud, ctl->password);
+       snprintf(shroud, sizeof (shroud), "\"%s\"", password);
        ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
        shroud[0] = '\0';
+#ifdef SSL_ENABLE
+       /* this is for servers which claim to support TLS, but actually
+        * don't! */
+       if (did_stls && ok == PS_SOCKET && !ctl->sslproto && !ctl->wehaveauthed)
+       {
+           ctl->sslproto = xstrdup("");
+           /* repoll immediately */
+           ok = PS_REPOLL;
+       }
+#endif
        if (ok)
        {
            /* SASL cancellation of authentication */
@@ -437,9 +542,16 @@ static int internal_expunge(int sock)
 {
     int        ok;
 
+    recentcount_ok = 0;
+
     if ((ok = gen_transact(sock, "EXPUNGE")))
        return(ok);
 
+    /* some servers do not report RECENT after an EXPUNGE. in this case, 
+     * the previous value of recentcount is just ignored. */
+    if (!recentcount_ok)
+        recentcount = 0;
+
     expunged += deletions;
     deletions = 0;
 
@@ -451,13 +563,57 @@ static int internal_expunge(int sock)
 }
 
 static int imap_idle(int sock)
-/* start an RFC2177 IDLE */
+/* start an RFC2177 IDLE, or fake one if unsupported */
 {
+    int ok;
+
     stage = STAGE_IDLE;
     saved_timeout = mytimeout;
-    mytimeout = 0;
 
-    return (gen_transact(sock, "IDLE"));
+    if (has_idle) {
+       /* special timeout to terminate the IDLE and re-issue it
+        * at least every 28 minutes:
+        * (the server may have an inactivity timeout) */
+       mytimeout = 1680; /* 28 min */
+       /* enter IDLE mode */
+       ok = gen_transact(sock, "IDLE");
+
+       if (ok == PS_IDLETIMEOUT) {
+           /* send "DONE" continuation */
+           SockWrite(sock, "DONE\r\n", 6);
+           if (outlevel >= O_MONITOR)
+               report(stdout, "IMAP> DONE\n");
+       } else
+           /* not idle timeout */
+           return ok;
+    } else {  /* no idle support, fake it */
+       /* when faking an idle, we can't assume the server will
+        * send us the new messages out of the blue (RFC2060);
+        * this timeout is potentially the delay before we notice
+        * new mail (can be small since NOOP checking is cheap) */
+       mytimeout = 28;
+       ok = gen_transact(sock, "NOOP");
+       /* if there's an error (not likely) or we just found mail (stage 
+        * has changed, timeout has also been restored), we're done */
+       if (ok != 0 || stage != STAGE_IDLE)
+           return(ok);
+
+       /* wait (briefly) for an unsolicited status update */
+       ok = imap_ok(sock, NULL);
+       /* again, this is new mail or an error */
+       if (ok != PS_IDLETIMEOUT)
+           return(ok);
+    }
+
+    /* restore normal timeout value */
+    mytimeout = saved_timeout;
+    stage = STAGE_FETCH;
+
+    /* get OK IDLE message */
+    if (has_idle)
+        return imap_ok(sock, NULL);
+
+    return PS_SUCCESS;
 }
 
 static int imap_getrange(int sock, 
@@ -481,42 +637,89 @@ static int imap_getrange(int sock,
         * just after deletion.
         */
        ok = 0;
-       if (deletions && expunge_period != 1)
+       if (deletions) {
            ok = internal_expunge(sock);
-       count = -1;
-       if (do_idle)
-           ok = imap_idle(sock);
-       if (ok || gen_transact(sock, "NOOP"))
-       {
-           report(stderr, _("re-poll failed\n"));
-           return(ok);
+           if (ok)
+           {
+               report(stderr, GT_("expunge failed\n"));
+               return(ok);
+           }
        }
-       else if (count == -1)   /* no EXISTS response to NOOP/IDLE */
-       {
-           count = 0;
+
+       /*
+        * recentcount is already set here by the last imap command which
+        * returned RECENT on detecting new mail. if recentcount is 0, wait
+        * for new mail.
+        */
+
+       /* this is a while loop because imap_idle() might return on other
+        * mailbox changes also */
+       while (recentcount == 0 && do_idle) {
+           smtp_close(ctl, 1);
+           ok = imap_idle(sock);
+           if (ok)
+           {
+               report(stderr, GT_("re-poll failed\n"));
+               return(ok);
+           }
        }
+       /* if recentcount is 0, return no mail */
+       if (recentcount == 0)
+               count = 0;
        if (outlevel >= O_DEBUG)
-           report(stdout, _("%d messages waiting after re-poll\n"), count);
+           report(stdout, ngettext("%d message waiting after re-poll\n",
+                                   "%d messages waiting after re-poll\n",
+                                   count), count);
     }
     else
     {
+       count = 0;
        ok = gen_transact(sock, 
                          check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
                          folder ? folder : "INBOX");
        if (ok != 0)
        {
-           report(stderr, _("mailbox selection failed\n"));
+           report(stderr, GT_("mailbox selection failed\n"));
            return(ok);
        }
        else if (outlevel >= O_DEBUG)
-           report(stdout, _("%d messages waiting after first poll\n"), count);
+           report(stdout, ngettext("%d message waiting after first poll\n",
+                                   "%d messages waiting after first poll\n",
+                                   count), count);
 
        /* no messages?  then we may need to idle until we get some */
-       if (count == 0 && do_idle)
-           imap_idle(sock);
+       while (count == 0 && do_idle) {
+           ok = imap_idle(sock);
+           if (ok)
+           {
+               report(stderr, GT_("re-poll failed\n"));
+               return(ok);
+           }
+       }
+
+       /*
+        * We should have an expunge here to
+        * a) avoid fetching deleted mails during 'fetchall'
+        * b) getting a wrong count of mails during 'no fetchall'
+        */
+       if (!check_only && !ctl->keep && count > 0)
+       {
+           ok = internal_expunge(sock);
+           if (ok)
+           {
+               report(stderr, GT_("expunge failed\n"));
+               return(ok);
+           }
+           if (outlevel >= O_DEBUG)
+               report(stdout, ngettext("%d message waiting after expunge\n",
+                                       "%d messages waiting after expunge\n",
+                                       count), count);
+       }
     }
 
     *countp = count;
+    recentcount = 0;
+    startcount = 1;
 
     /* OK, now get a count of unseen messages and their indices */
     if (!ctl->fetchall && count > 0)
@@ -527,12 +730,13 @@ static int imap_getrange(int sock,
        memset(unseen_messages, 0, count * sizeof(unsigned int));
        unseen = 0;
 
-       gen_send(sock, "SEARCH UNSEEN");
+       /* don't count deleted messages, in case user enabled keep last time */
+       gen_send(sock, "SEARCH UNSEEN NOT DELETED");
        do {
            ok = gen_recv(sock, buf, sizeof(buf));
            if (ok != 0)
            {
-               report(stderr, _("search for unseen messages failed\n"));
+               report(stderr, GT_("search for unseen messages failed\n"));
                return(PS_PROTOCOL);
            }
            else if ((cp = strstr(buf, "* SEARCH")))
@@ -540,43 +744,54 @@ static int imap_getrange(int sock,
                char    *ep;
 
                cp += 8;        /* skip "* SEARCH" */
+               /* startcount is higher than count so that if there are no
+                * unseen messages, imap_getsizes() will not need to do
+                * anything! */
+               startcount = count + 1;
 
                while (*cp && unseen < count)
                {
                    /* skip whitespace */
-                   while (*cp && isspace(*cp))
+                   while (*cp && isspace((unsigned char)*cp))
                        cp++;
                    if (*cp) 
                    {
+                       unsigned int um;
                        /*
                         * Message numbers are between 1 and 2^32 inclusive,
                         * so unsigned int is large enough.
                         */
-                       unseen_messages[unseen]=(unsigned int)strtol(cp,&ep,10);
-
-                       if (outlevel >= O_DEBUG)
-                           report(stdout, 
-                                  _("%u is unseen\n"), 
-                                  unseen_messages[unseen]);
-               
-                       unseen++;
+                       um=(unsigned int)strtol(cp,&ep,10);
+                       if (um <= count)
+                       {
+                           unseen_messages[unseen++] = um;
+                           if (outlevel >= O_DEBUG)
+                               report(stdout, GT_("%u is unseen\n"), um);
+                           if (startcount > um)
+                               startcount = um;
+                       }
                        cp = ep;
                    }
                }
            }
        } while
            (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
+
+       if (outlevel >= O_DEBUG && unseen > 0)
+           report(stdout, GT_("%u is first unseen\n"), startcount);
     } else
        unseen = -1;
 
     *newp = unseen;
+    count = 0;
     expunged = 0;
+    deletions = 0;
 
     return(PS_SUCCESS);
 }
 
-static int imap_getsizes(int sock, int count, int *sizes)
-/* capture the sizes of all messages */
+static int imap_getpartialsizes(int sock, int first, int last, int *sizes)
+/* capture the sizes of messages #first-#last */
 {
     char buf [MSGBUFSIZE+1];
 
@@ -614,25 +829,51 @@ static int imap_getsizes(int sock, int count, int *sizes)
      * on the fact that the sizes array has been preinitialized with a
      * known-bad size value.
      */
-    if (count == 1)
-       gen_send(sock, "FETCH 1 RFC822.SIZE", count);
-    else
-       gen_send(sock, "FETCH 1:%d RFC822.SIZE", count);
+
+    /* expunges change the fetch numbers */
+    first -= expunged;
+    last -= expunged;
+
+    if (last == first)
+       gen_send(sock, "FETCH %d RFC822.SIZE", last);
+    else if (last > first)
+       gen_send(sock, "FETCH %d:%d RFC822.SIZE", first, last);
+    else /* no unseen messages! */
+       return(PS_SUCCESS);
     for (;;)
     {
-       int num, size, ok;
+       unsigned int num, size;
+       int ok;
+       char *cp;
 
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
+       /* we want response matching to be case-insensitive */
+       for (cp = buf; *cp; cp++)
+           *cp = toupper(*cp);
+       /* an untagged NO means that a message was not readable */
+       if (strstr(buf, "* NO"))
+           ;
        else if (strstr(buf, "OK") || strstr(buf, "NO"))
            break;
-       else if (sscanf(buf, "* %d FETCH (RFC822.SIZE %d)", &num, &size) == 2)
-           sizes[num - 1] = size;
+       else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) 
+       {
+           if (num >= first && num <= last)
+               sizes[num - first] = size;
+           else
+               report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
+       }
     }
 
     return(PS_SUCCESS);
 }
 
+static int imap_getsizes(int sock, int count, int *sizes)
+/* capture the sizes of all messages */
+{
+    return imap_getpartialsizes(sock, 1, count, sizes);
+}
+
 static int imap_is_old(int sock, struct query *ctl, int number)
 /* is the given message old? */
 {
@@ -656,6 +897,14 @@ static int imap_is_old(int sock, struct query *ctl, int number)
     return(seen);
 }
 
+static char *skip_token(char *ptr)
+{
+    while(isspace((unsigned char)*ptr)) ptr++;
+    while(!isspace((unsigned char)*ptr) && !iscntrl((unsigned char)*ptr)) ptr++;
+    while(isspace((unsigned char)*ptr)) ptr++;
+    return(ptr);
+}
+
 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
 /* request headers of nth message */
 {
@@ -675,15 +924,28 @@ static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
     for (;;) 
     {
        int     ok;
+       char    *ptr;
 
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
-       if (sscanf(buf+2, "%d FETCH (%*s {%d}", &num, lenp) == 2)
-           break;
-       else if (sscanf(buf+2, "%d NO", &num) == 1)
-           return(PS_ERROR);
-       else if (sscanf(buf+2, "%d BAD", &num) == 1)
-           return(PS_ERROR);
+       ptr = skip_token(buf);  /* either "* " or "AXXXX " */
+       if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
+           break;
+       /* try to recover from chronically fucked-up M$ Exchange servers */
+       else if (!strncmp(ptr, "NO", 2))
+       {
+           /* wait for a tagged response */
+           if (strstr (buf, "* NO"))
+               imap_ok (sock, 0);
+           return(PS_TRANSIENT);
+       }
+       else if (!strncmp(ptr, "BAD", 3))
+       {
+           /* wait for a tagged response */
+           if (strstr (buf, "* BAD"))
+               imap_ok (sock, 0);
+           return(PS_TRANSIENT);
+       }
     }
 
     if (num != number)
@@ -707,11 +969,6 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
      * craps out during the message, it will still be marked `unseen' on
      * the server.
      *
-     * However...*don't* do this if we're using keep to suppress deletion!
-     * In that case, marking the seen flag is the only way to prevent the
-     * message from being re-fetched on subsequent runs (and according
-     * to RFC2060 p.43 this fetch should set Seen as a side effect).
-     *
      * According to RFC2060, and Mark Crispin the IMAP maintainer,
      * FETCH %d BODY[TEXT] and RFC822.TEXT are "functionally 
      * equivalent".  However, we know of at least one server that
@@ -729,17 +986,11 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
     switch (imap_version)
     {
     case IMAP4rev1:    /* RFC 2060 */
-       if (!ctl->keep)
-           gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
-       else
-           gen_send(sock, "FETCH %d BODY[TEXT]", number);
+       gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
        break;
 
     case IMAP4:                /* RFC 1730 */
-       if (!ctl->keep)
-           gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
-       else
-           gen_send(sock, "FETCH %d RFC822.TEXT", number);
+       gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
        break;
 
     default:           /* RFC 1176 */
@@ -762,10 +1013,15 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
     /*
      * Try to extract a length from the FETCH response.  RFC2060 requires
      * it to be present, but at least one IMAP server (Novell GroupWise)
-     * botches this.
+     * botches this.  The overflow check is needed because of a broken
+     * server called dbmail that returns huge garbage lengths.
      */
-    if ((cp = strchr(buf, '{')))
-       *lenp = atoi(cp + 1);
+    if ((cp = strchr(buf, '{'))) {
+        errno = 0;
+       *lenp = (int)strtol(cp + 1, (char **)NULL, 10);
+        if (errno == ERANGE && (*lenp == LONG_MAX || *lenp == LONG_MIN))
+            *lenp = -1;    /* length is too big/small for us to handle */
+    }
     else
        *lenp = -1;     /* missing length part in FETCH reponse */
 
@@ -789,26 +1045,6 @@ static int imap_trail(int sock, struct query *ctl, int number)
        /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
        if (strstr(buf, "OK"))
            break;
-
-#ifdef __UNUSED__
-       /*
-        * Any IMAP server that fails to set Seen on a BODY[TEXT]
-        * fetch violates RFC2060 p.43 (top).  This becomes an issue
-        * when keep is on, because seen messages aren't deleted and
-        * get refetched on each poll.  As a workaround, if keep is on
-        * we can set the Seen flag explicitly.
-        *
-        * This code isn't used yet because we don't know of any IMAP
-        * servers broken in this way.
-        */
-       if (ctl->keep)
-           if ((ok = gen_transact(sock,
-                       imap_version == IMAP4 
-                               ? "STORE %d +FLAGS.SILENT (\\Seen)"
-                               : "STORE %d +FLAGS (\\Seen)", 
-                       number)))
-               return(ok);
-#endif /* __UNUSED__ */
     }
 
     return(PS_SUCCESS);
@@ -853,6 +1089,16 @@ static int imap_delete(int sock, struct query *ctl, int number)
     return(PS_SUCCESS);
 }
 
+static int imap_mark_seen(int sock, struct query *ctl, int number)
+/* mark the given message as seen */
+{
+    return(gen_transact(sock,
+       imap_version == IMAP4
+       ? "STORE %d +FLAGS.SILENT (\\Seen)"
+       : "STORE %d +FLAGS (\\Seen)",
+       number));
+}
+
 static int imap_logout(int sock, struct query *ctl)
 /* send logout command */
 {
@@ -869,10 +1115,10 @@ static int imap_logout(int sock, struct query *ctl)
     return(gen_transact(sock, "LOGOUT"));
 }
 
-const static struct method imap =
+static const struct method imap =
 {
     "IMAP",            /* Internet Message Access Protocol */
-#if INET6_ENABLE
+#ifdef INET6_ENABLE
     "imap",
     "imaps",
 #else /* INET6_ENABLE */
@@ -885,11 +1131,13 @@ const static struct method imap =
     imap_getauth,      /* get authorization */
     imap_getrange,     /* query range of messages */
     imap_getsizes,     /* get sizes of messages (used for ESMTP SIZE option) */
+    imap_getpartialsizes,      /* get sizes of subset of messages (used for ESMTP SIZE option) */
     imap_is_old,       /* no UID check */
     imap_fetch_headers,        /* request given message headers */
     imap_fetch_body,   /* request given message body */
     imap_trail,                /* eat message trailer */
     imap_delete,       /* delete the message */
+    imap_mark_seen,    /* how to mark a message as seen */
     imap_logout,       /* expunge and exit */
     TRUE,              /* yes, we can re-poll */
 };