]> Pileus Git - ~andy/fetchmail/blobdiff - imap.c
Before the next fix round.
[~andy/fetchmail] / imap.c
diff --git a/imap.c b/imap.c
index 199c3c531fcfc6354899a33ee4927a9efc369553..9043a9ad3e6b222e7dfea551499ca4de9eaeace2 100644 (file)
--- a/imap.c
+++ b/imap.c
@@ -29,13 +29,14 @@ extern char *strstr();      /* needed on sysV68 R3V7.1. */
 #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 int count, unseen, deletions = 0;
+static int expunged, expunge_period, saved_timeout = 0;
+static int imap_version, preauth;
 static flag do_idle;
 static char capabilities[MSGBUFSIZE+1];
 static unsigned int *unseen_messages;
 
-int imap_ok(int sock, char *argbuf)
+static int imap_ok(int sock, char *argbuf)
 /* parse command response */
 {
     char buf[MSGBUFSIZE+1];
@@ -78,6 +79,22 @@ int imap_ok(int sock, char *argbuf)
        }
        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,7 +182,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 ((unsigned char*)&challenge, msgbuf, sizeof(msgbuf));
     
     if (outlevel >= O_DEBUG)
        dumpSmbNtlmAuthChallenge(stdout, &challenge);
@@ -182,7 +199,6 @@ static int do_imap_ntlm(int sock, struct query *ctl)
        report(stdout, "IMAP> %s\n", msgbuf);
       
     strcat(msgbuf,"\r\n");
-
     SockWrite (sock, msgbuf, strlen (msgbuf));
   
     if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
@@ -195,7 +211,7 @@ static int do_imap_ntlm(int sock, struct query *ctl)
 }
 #endif /* NTLM */
 
-int imap_canonicalize(char *result, char *raw, int maxlen)
+static int imap_canonicalize(char *result, char *raw, int maxlen)
 /* encode an IMAP password as per RFC1730's quoting conventions */
 {
     int i, j;
@@ -212,7 +228,7 @@ int imap_canonicalize(char *result, char *raw, int maxlen)
     return(i);
 }
 
-int imap_getauth(int sock, struct query *ctl, char *greeting)
+static int imap_getauth(int sock, struct query *ctl, char *greeting)
 /* apply for connection authorization */
 {
     int ok = 0;
@@ -221,25 +237,32 @@ int imap_getauth(int sock, struct query *ctl, char *greeting)
     capabilities[0] = '\0';
     if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
     {
-       /* UW-IMAP server 10.173 notifies in all caps */
+       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"))
        {
            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"));
     }
     else
        return(ok);
@@ -263,122 +286,156 @@ int imap_getauth(int sock, struct query *ctl, char *greeting)
     {
        do_idle = TRUE;
        if (outlevel >= O_VERBOSE)
-           report(stdout, _("will idle after poll\n"));
+           report(stdout, GT_("will idle after poll\n"));
     }
 
     /* 
      * If either (a) we saw a PREAUTH token in the greeting, or
      * (b) the user specified ssh preauthentication, then we're done.
      */
-    if (preauth || ctl->server.preauthenticate == A_SSH)
+    if (preauth || ctl->server.authenticate == A_SSH)
     {
         preauth = FALSE;  /* reset for the next session */
         return(PS_SUCCESS);
     }
 
-#if OPIE_ENABLE
-    if ((ctl->server.protocol == P_IMAP) && strstr(capabilities, "AUTH=X-OTP"))
-    {
-       if (outlevel >= O_DEBUG)
-           report(stdout, _("OTP authentication is supported\n"));
-       if (do_otp(sock, ctl) == PS_SUCCESS)
-           return(PS_SUCCESS);
-    };
-#endif /* OPIE_ENABLE */
+    /*
+     * Time to authenticate the user.
+     * Try the protocol variants that don't require passwords first.
+     */
+    ok = PS_AUTHFAIL;
 
 #ifdef GSSAPI
-    if (strstr(capabilities, "AUTH=GSSAPI"))
-    {
-        if (ctl->server.protocol == P_IMAP_GSS)
-        {
-            if (outlevel >= O_DEBUG)
-                report(stdout, _("GSS authentication is supported\n"));
-            return do_gssauth(sock, ctl->server.truename, ctl->remotename);
-        }
-    }
-    else if (ctl->server.protocol == P_IMAP_GSS)
-    {
-        report(stderr, 
-              _("Required GSS capability not supported by server\n"));
-        return(PS_AUTHFAIL);
-    }
+    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))
+       {
+           /* SASL cancellation of authentication */
+           gen_send(sock, "*");
+           if(ctl->server.authenticate != A_ANY)
+                return ok;
+       }
+       else
+           return ok;
 #endif /* GSSAPI */
 
 #ifdef KERBEROS_V4
-    if (strstr(capabilities, "AUTH=KERBEROS_V4"))
+    if ((ctl->server.authenticate == A_ANY 
+        || ctl->server.authenticate == A_KERBEROS_V4
+        || ctl->server.authenticate == A_KERBEROS_V5) 
+       && strstr(capabilities, "AUTH=KERBEROS_V4"))
     {
-       if (outlevel >= O_DEBUG)
-           report(stdout, _("KERBEROS_V4 authentication is supported\n"));
-
-       if (ctl->server.protocol == P_IMAP_K4)
+       if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
        {
-           if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
-               /* SASL cancellation of authentication */
-               gen_send(sock, "*");
-           
-           return(ok);
+           /* SASL cancellation of authentication */
+           gen_send(sock, "*");
+           if(ctl->server.authenticate != A_ANY)
+                return ok;
        }
-       /* else fall through to ordinary AUTH=LOGIN case */
-    }
-    else if (ctl->server.protocol == P_IMAP_K4)
-    {
-       report(stderr, 
-              _("Required KERBEROS_V4 capability not supported by server\n"));
-       return(PS_AUTHFAIL);
+       else
+           return ok;
     }
 #endif /* KERBEROS_V4 */
 
-    if (strstr(capabilities, "AUTH=CRAM-MD5"))
+    /*
+     * No such luck.  OK, now try the variants that mask your password
+     * in a challenge-response.
+     */
+
+    if ((ctl->server.authenticate == A_ANY 
+        || ctl->server.authenticate == A_CRAM_MD5)
+       && strstr(capabilities, "AUTH=CRAM-MD5"))
     {
-        if (outlevel >= O_DEBUG)
-            report (stdout, _("CRAM-MD5 authentication is supported\n"));
-        if (ctl->server.protocol != P_IMAP_LOGIN)
-        {
-            if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl)))
-               /* SASL cancellation of authentication */
-               gen_send(sock, "*");
-
-            return(ok);
-        }
+       if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
+       {
+           /* SASL cancellation of authentication */
+           gen_send(sock, "*");
+           if(ctl->server.authenticate != A_ANY)
+                return ok;
+       }
+       else
+           return ok;
     }
-    else if (ctl->server.protocol == P_IMAP_CRAM_MD5)
+
+#if OPIE_ENABLE
+    if ((ctl->server.authenticate == A_ANY 
+        || ctl->server.authenticate == A_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
+           return ok;
+#else
+    if (ctl->server.authenticate == A_OTP)
     {
-        report(stderr,
-               _("Required CRAM-MD5 capability not supported by server\n"));
-        return(PS_AUTHFAIL);
+       report(stderr, 
+          GT_("Required OTP capability not compiled into fetchmail\n"));
     }
+#endif /* OPIE_ENABLE */
 
 #ifdef NTLM_ENABLE
-    if (strstr (capabilities, "AUTH=NTLM"))
+    if ((ctl->server.authenticate == A_ANY 
+        || ctl->server.authenticate == A_NTLM) 
+       && strstr (capabilities, "AUTH=NTLM")) {
+       if ((ok = do_imap_ntlm(sock, ctl)))
+       {
+           /* SASL cancellation of authentication */
+           gen_send(sock, "*");
+           if(ctl->server.authenticate != A_ANY)
+                return ok;
+       }
+       else
+           return(ok);
+    }
+#else
+    if (ctl->server.authenticate == A_NTLM)
     {
-        if (outlevel >= O_DEBUG)
-            report (stdout, _("NTLM authentication is supported\n"));
-        return do_imap_ntlm (sock, ctl);
+       report(stderr, 
+          GT_("Required NTLM capability not compiled into fetchmail\n"));
     }
 #endif /* NTLM_ENABLE */
 
 #ifdef __UNUSED__      /* The Cyrus IMAP4rev1 server chokes on this */
     /* this handles either AUTH=LOGIN or AUTH-LOGIN */
-    if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN"))) {
-      report(stderr, 
-            _("Required LOGIN capability not supported by server\n"));
-      return PS_AUTHFAIL;
-    };
+    if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN")))
+    {
+       report(stderr, 
+              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"))
     {
        /* these sizes guarantee no buffer overflow */
        char    remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1];
 
        imap_canonicalize(remotename, ctl->remotename, NAMELEN);
        imap_canonicalize(password, ctl->password, PASSWORDLEN);
+
+       strcpy(shroud, ctl->password);
        ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
+       shroud[0] = '\0';
+       if (ok)
+       {
+           /* SASL cancellation of authentication */
+           gen_send(sock, "*");
+           if(ctl->server.authenticate != A_ANY)
+                return ok;
+       }
+       else
+           return(ok);
     }
 
-    if (ok)
-       return(ok);
-    
-    return(PS_SUCCESS);
+    return(ok);
 }
 
 static int internal_expunge(int sock)
@@ -437,7 +494,7 @@ static int imap_getrange(int sock,
            ok = imap_idle(sock);
        if (ok || gen_transact(sock, "NOOP"))
        {
-           report(stderr, _("re-poll failed\n"));
+           report(stderr, GT_("re-poll failed\n"));
            return(ok);
        }
        else if (count == -1)   /* no EXISTS response to NOOP/IDLE */
@@ -445,7 +502,7 @@ static int imap_getrange(int sock,
            count = 0;
        }
        if (outlevel >= O_DEBUG)
-           report(stdout, _("%d messages waiting after re-poll\n"), count);
+           report(stdout, GT_("%d messages waiting after re-poll\n"), count);
     }
     else
     {
@@ -454,15 +511,32 @@ static int imap_getrange(int sock,
                          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, GT_("%d messages waiting after first poll\n"), count);
 
        /* no messages?  then we may need to idle until we get some */
        if (count == 0 && do_idle)
            imap_idle(sock);
+
+       /*
+        * 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, GT_("%d messages waiting after expunge\n"), count);
+       }
     }
 
     *countp = count;
@@ -481,7 +555,7 @@ static int imap_getrange(int sock,
            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")))
@@ -505,7 +579,7 @@ static int imap_getrange(int sock,
 
                        if (outlevel >= O_DEBUG)
                            report(stdout, 
-                                  _("%u is unseen\n"), 
+                                  GT_("%u is unseen\n"), 
                                   unseen_messages[unseen]);
                
                        unseen++;
@@ -515,10 +589,12 @@ static int imap_getrange(int sock,
            }
        } while
            (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
-    }
+    } else
+       unseen = -1;
 
     *newp = unseen;
     expunged = 0;
+    deletions = 0;
 
     return(PS_SUCCESS);
 }
@@ -568,14 +644,19 @@ static int imap_getsizes(int sock, int count, int *sizes)
        gen_send(sock, "FETCH 1:%d RFC822.SIZE", count);
     for (;;)
     {
-       int num, size, ok;
+       unsigned int num, size;
+       int ok;
 
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
        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 > 0 && num <= count)
+               sizes[num - 1] = size;
+           else
+               report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
+       }
     }
 
     return(PS_SUCCESS);
@@ -604,6 +685,14 @@ static int imap_is_old(int sock, struct query *ctl, int number)
     return(seen);
 }
 
+static char *skip_token(char *ptr)
+{
+    while(isspace(*ptr)) ptr++;
+    while(!isspace(*ptr) && !iscntrl(*ptr)) ptr++;
+    while(isspace(*ptr)) ptr++;
+    return(ptr);
+}
+
 static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
 /* request headers of nth message */
 {
@@ -620,13 +709,22 @@ static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
     gen_send(sock, "FETCH %d RFC822.HEADER", number);
 
     /* looking for FETCH response */
-    do {
+    for (;;) 
+    {
        int     ok;
+       char    *ptr;
 
        if ((ok = gen_recv(sock, buf, sizeof(buf))))
            return(ok);
-    } while
-       (sscanf(buf+2, "%d FETCH (%*s {%d}", &num, lenp) != 2);
+       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))
+           return(PS_TRANSIENT);
+       else if (!strncmp(ptr, "BAD", 3))
+           return(PS_TRANSIENT);
+    }
 
     if (num != number)
        return(PS_ERROR);
@@ -824,7 +922,6 @@ const static struct method imap =
     TRUE,              /* this is a tagged protocol */
     FALSE,             /* no message delimiter */
     imap_ok,           /* parse command response */
-    imap_canonicalize, /* deal with embedded slashes and spaces */
     imap_getauth,      /* get authorization */
     imap_getrange,     /* query range of messages */
     imap_getsizes,     /* get sizes of messages (used for ESMTP SIZE option) */