]> Pileus Git - ~andy/fetchmail/blobdiff - imap.c
don't complain about READ-ONLY IMAP folders in --fetchall --keep mode.
[~andy/fetchmail] / imap.c
diff --git a/imap.c b/imap.c
index 4d6727b13abd1d3415d59498b77c464fb1ecc9f7..68e87eb873e39fbc991cded0e5ce3c1cd6bd0991 100644 (file)
--- a/imap.c
+++ b/imap.c
 #define IMAP4          0       /* IMAP4 rev 0, RFC1730 */
 #define IMAP4rev1      1       /* IMAP4 rev 1, RFC2060 */
 
+/* global variables: please reinitialize them explicitly for proper
+ * working in daemon mode */
+
+/* TODO: session variables to be initialized before server greeting */
+static int preauth = FALSE;
+
+/* session variables initialized in capa_probe() or imap_getauth() */
+static char capabilities[MSGBUFSIZE+1];
+static int imap_version = IMAP4;
+static flag do_idle = FALSE, has_idle = FALSE;
+static int expunge_period = 1;
+
+/* mailbox variables initialized in imap_getrange() */
 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 int expunged = 0;
 static unsigned int *unseen_messages;
 
+/* for "IMAP> EXPUNGE" */
+static int recentcount_ok = 0;
+
+/* for "IMAP> IDLE" */
+static int saved_timeout = 0;
+
 static int imap_ok(int sock, char *argbuf)
 /* parse command response */
 {
@@ -50,79 +65,90 @@ static int imap_ok(int sock, char *argbuf)
            if (islower((unsigned char)*cp))
                *cp = toupper((unsigned char)*cp);
 
-       /* interpret untagged status responses */
-       if (strstr(buf, "* CAPABILITY"))
-       {
-           strlcpy(capabilities, buf + 12, sizeof(capabilities));
-       }
-       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, GT_("bogus message count!"));
-               return(PS_PROTOCOL);
+       /* interpret untagged status responses
+        * First check if we really have an untagged response, starting
+        * with "*" SPACE. Then, for each individual check, use a BLANK
+        * before the word to avoid confusion with the \Recent flag or
+        * similar */
+       if (buf[0] == '*' && buf[1] == ' ') {
+           if (strstr(buf, " CAPABILITY")) {
+               strlcpy(capabilities, buf + 12, sizeof(capabilities));
            }
-
-           /*
-            * 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
-            * server the IDLE is finished by shipping back a DONE when we
-            * see an EXISTS.  Only after that will a tagged response be
-            * shipped.  The idling flag also gets cleared on a timeout.
-            */
-           if (stage == STAGE_IDLE)
+           else if (strstr(buf, " EXISTS"))
            {
-               /* If IDLE isn't supported, we were only sending NOOPs anyway. */
-               if (has_idle)
+               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))
                {
-                   /* 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");
+                   report(stderr, GT_("bogus message count!"));
+                   return(PS_PROTOCOL);
                }
 
-               mytimeout = saved_timeout;
-               stage = STAGE_FETCH;
+               /*
+                * 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
+                * server the IDLE is finished by shipping back a DONE when we
+                * see an EXISTS.  Only after that will a tagged response be
+                * shipped.  The idling flag also gets cleared on a timeout.
+                */
+               if (stage == STAGE_IDLE)
+               {
+                   /* 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;
+               }
+           }
+           else if (strstr(buf, " RECENT"))
+           {
+               recentcount_ok = 1;
+               recentcount = atoi(buf+2);
+           }
+           else if (strstr(buf, " EXPUNGE"))
+           {
+               /* the response "* 10 EXPUNGE" means that the currently
+                * tenth (i.e. only one) message has been deleted */
+               if (atoi(buf+2) > 0)
+                   count--;
+               if (count < 0)
+                   count = 0;
+           }
+           else if (strstr(buf, " PREAUTH"))
+           {
+               preauth = TRUE;
+           }
+               /*
+                * The server may decide to make the mailbox read-only, 
+                * which causes fetchmail to go into a endless loop
+                * fetching the same message over and over again. 
+                * 
+                * However, for check_only, we use EXAMINE which will
+                * mark the mailbox read-only as per the RFC.
+                * 
+                * This checks for the condition and aborts if 
+                * the mailbox is read-only. 
+                *
+                * See RFC 2060 section 6.3.1 (SELECT).
+                * See RFC 2060 section 6.3.2 (EXAMINE).
+                */ 
+           else if (!check_only && strstr(buf, "[READ-ONLY]"))
+           {
+               return(PS_LOCKBUSY);
            }
        }
-       /* 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;
-       /*
-        * The server may decide to make the mailbox read-only, 
-        * which causes fetchmail to go into a endless loop
-        * fetching the same message over and over again. 
-        * 
-        * However, for check_only, we use EXAMINE which will
-        * mark the mailbox read-only as per the RFC.
-        * 
-        * This checks for the condition and aborts if 
-        * the mailbox is read-only. 
-        *
-        * See RFC 2060 section 6.3.1 (SELECT).
-        * See RFC 2060 section 6.3.2 (EXAMINE).
-        */ 
-       else if (!check_only && strstr(buf, "[READ-ONLY]"))
-           return(PS_LOCKBUSY);
     } while
        (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
 
@@ -165,10 +191,6 @@ static int imap_ok(int sock, char *argbuf)
 #ifdef NTLM_ENABLE
 #include "ntlm.h"
 
-static tSmbNtlmAuthRequest   request;
-static tSmbNtlmAuthChallenge challenge;
-static tSmbNtlmAuthResponse  response;
-
 /*
  * NTLM support by Grant Edwards.
  *
@@ -182,6 +204,10 @@ static tSmbNtlmAuthResponse  response;
 
 static int do_imap_ntlm(int sock, struct query *ctl)
 {
+    tSmbNtlmAuthRequest request;
+    tSmbNtlmAuthChallenge challenge;
+    tSmbNtlmAuthResponse response;
+
     char msgbuf[2048];
     int result,len;
 
@@ -269,7 +295,7 @@ static void capa_probe(int sock, struct query *ctl)
 
        /* capability checks are supposed to be caseblind */
        for (cp = capabilities; *cp; cp++)
-           *cp = toupper(*cp);
+           *cp = toupper((unsigned char)*cp);
 
        /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
           should expect a response in mixed-case */
@@ -297,13 +323,13 @@ static void capa_probe(int sock, struct query *ctl)
      * Handle idling.  We depend on coming through here on startup
      * and after each timeout (including timeouts during idles).
      */
+    do_idle = ctl->idle;
     if (ctl->idle)
     {
-       do_idle = TRUE;
        if (strstr(capabilities, "IDLE"))
-       {
            has_idle = TRUE;
-       }
+       else
+           has_idle = FALSE;
        if (outlevel >= O_VERBOSE)
            report(stdout, GT_("will idle after poll\n"));
     }
@@ -364,7 +390,7 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
               }
                report(stderr,
                       GT_("SSL connection failed.\n"));
-               return(PS_AUTHFAIL);
+               return PS_SOCKET;
            }
           did_stls = TRUE;
 
@@ -639,29 +665,14 @@ static int imap_getrange(int sock,
 
     if (pass > 1)
     {
-       /* 
-        * We have to have an expunge here, otherwise the re-poll will
-        * infinite-loop picking up un-expunged messages -- unless the
-        * expunge period is one and we've been nuking each message 
-        * just after deletion.
-        */
-       ok = 0;
-       if (deletions) {
-           ok = internal_expunge(sock);
-           if (ok)
-           {
-               report(stderr, GT_("expunge failed\n"));
-               return(ok);
-           }
-       }
-
-       /*
+       /* deleted mails have already been expunged by
+        * end_mailbox_poll().
+        *
         * 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
+        *
+        * this is a while loop because imap_idle() might return on other
         * mailbox changes also */
        while (recentcount == 0 && do_idle) {
            smtp_close(ctl, 1);
@@ -686,6 +697,11 @@ static int imap_getrange(int sock,
        ok = gen_transact(sock, 
                          check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
                          folder ? folder : "INBOX");
+       /* imap_ok returns PS_LOCKBUSY for READ-ONLY folders,
+        * which we can safely use in fetchall keep only */
+       if (ok == PS_LOCKBUSY && ctl->fetchall && ctl-> keep)
+           ok = 0;
+
        if (ok != 0)
        {
            report(stderr, GT_("mailbox selection failed\n"));
@@ -792,7 +808,6 @@ static int imap_getrange(int sock,
        unseen = -1;
 
     *newp = unseen;
-    count = 0;
     expunged = 0;
     deletions = 0;
 
@@ -859,18 +874,27 @@ static int imap_getpartialsizes(int sock, int first, int last, int *sizes)
            return(ok);
        /* we want response matching to be case-insensitive */
        for (cp = buf; *cp; cp++)
-           *cp = toupper(*cp);
+           *cp = toupper((unsigned char)*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, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) 
+       else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2
+       /* some servers (like mail.internode.on.net bld-mail04) return UID information here
+        *
+        * IMAP> A0005 FETCH 1 RFC822.SIZE
+        * IMAP< * 1 FETCH (UID 16 RFC822.SIZE 1447)
+        * IMAP< A0005 OK FETCH completed
+        *
+        */
+               || sscanf(buf, "* %u FETCH (UID %*s RFC822.SIZE %u)", &num, &size) == 2)
        {
            if (num >= first && num <= last)
-               sizes[num - first] = size;
+               sizes[num - first] = size;
            else
-               report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
+               report(stderr,
+                       GT_("Warning: ignoring bogus data for message sizes returned by the server.\n"));
        }
     }
 
@@ -938,8 +962,18 @@ static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
        ptr = skip_token(buf);  /* either "* " or "AXXXX " */
-       if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
-           break;
+       if (sscanf(ptr, "%d FETCH (RFC822.HEADER {%d}", &num, lenp) == 2
+       /* some servers (like mail.internode.on.net bld-mail04) return UID information here
+        *
+        * IMAP> A0006 FETCH 1 RFC822.HEADER
+        * IMAP< * 1 FETCH (UID 16 RFC822.HEADER {1360}
+        * ...
+        * IMAP< )
+        * IMAP< A0006 OK FETCH completed
+        *
+        */
+               || sscanf(ptr, "%d FETCH (UID %*s RFC822.HEADER {%d}", &num, lenp) == 2)
+           break;
        /* try to recover from chronically fucked-up M$ Exchange servers */
        else if (!strncmp(ptr, "NO", 2))
        {
@@ -1028,7 +1062,7 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
     if ((cp = strchr(buf, '{'))) {
         errno = 0;
        *lenp = (int)strtol(cp + 1, (char **)NULL, 10);
-        if (errno == ERANGE && (*lenp == LONG_MAX || *lenp == LONG_MIN))
+        if (errno == ERANGE || *lenp < 0)
             *lenp = -1;    /* length is too big/small for us to handle */
     }
     else
@@ -1037,7 +1071,7 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
     return(PS_SUCCESS);
 }
 
-static int imap_trail(int sock, struct query *ctl, int number)
+static int imap_trail(int sock, struct query *ctl, int number, const char *tag)
 /* discard tail of FETCH response after reading message text */
 {
     /* expunges change the fetch numbers */
@@ -1045,15 +1079,19 @@ static int imap_trail(int sock, struct query *ctl, int number)
 
     for (;;)
     {
-       char buf[MSGBUFSIZE+1];
+       char buf[MSGBUFSIZE+1], *t;
        int ok;
 
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
 
        /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
-       if (strstr(buf, "OK"))
-           break;
+       if (strncmp(buf, tag, strlen(tag)) == 0) {
+           t = buf + strlen(tag);
+           t += strspn(t, " \t");
+           if (strncmp(t, "OK", 2) == 0)
+               break;
+       }
     }
 
     return(PS_SUCCESS);
@@ -1108,6 +1146,14 @@ static int imap_mark_seen(int sock, struct query *ctl, int number)
        number));
 }
 
+static int imap_end_mailbox_poll(int sock, struct query *ctl)
+/* cleanup mailbox before we idle or switch to another one */
+{
+    if (deletions)
+       internal_expunge(sock);
+    return(PS_SUCCESS);
+}
+
 static int imap_logout(int sock, struct query *ctl)
 /* send logout command */
 {
@@ -1142,6 +1188,7 @@ static const struct method imap =
     imap_trail,                /* eat message trailer */
     imap_delete,       /* delete the message */
     imap_mark_seen,    /* how to mark a message as seen */
+    imap_end_mailbox_poll,     /* end-of-mailbox processing */
     imap_logout,       /* expunge and exit */
     TRUE,              /* yes, we can re-poll */
 };