]> Pileus Git - ~andy/fetchmail/blobdiff - imap.c
Attempt merging from 6.3.24.
[~andy/fetchmail] / imap.c
diff --git a/imap.c b/imap.c
index e22fea4008cec25ee07dd7c339f57c7ea88fb929..65d225381b7c1b926c5d38d1e8312dbaf67c6ee2 100644 (file)
--- a/imap.c
+++ b/imap.c
 #include  <string.h>
 #include  <strings.h>
 #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"
+#include  "gettext.h"
 
 /* imap_version values */
-#define IMAP2          -1      /* IMAP2 or IMAP2BIS, RFC1176 */
 #define IMAP4          0       /* IMAP4 rev 0, RFC1730 */
 #define IMAP4rev1      1       /* IMAP4 rev 1, RFC2060 */
 
@@ -179,7 +176,7 @@ static int imap_untagged_response(int sock, const char *buf)
     return(PS_SUCCESS);
 }
 
-static int imap_response(int sock, char *argbuf)
+static int imap_response(int sock, char *argbuf, struct RecvSplit *rs)
 /* parse command response */
 {
     char buf[MSGBUFSIZE+1];
@@ -188,7 +185,11 @@ static int imap_response(int sock, char *argbuf)
        int     ok;
        char    *cp;
 
-       if ((ok = gen_recv(sock, buf, sizeof(buf))))
+       if (rs)
+           ok = gen_recv_split(sock, buf, sizeof(buf), rs);
+       else
+           ok = gen_recv(sock, buf, sizeof(buf));
+       if (ok != PS_SUCCESS)
            return(ok);
 
        /* all tokens in responses are caseblind */
@@ -269,7 +270,7 @@ static int imap_ok(int sock, char *argbuf)
 {
     int ok;
 
-    while ((ok = imap_response(sock, argbuf)) == PS_UNTAGGED)
+    while ((ok = imap_response(sock, argbuf, NULL)) == PS_UNTAGGED)
        ; /* wait for the tagged response */
     return(ok);
 }
@@ -290,57 +291,13 @@ static int imap_ok(int sock, char *argbuf)
 
 static int do_imap_ntlm(int sock, struct query *ctl)
 {
-    tSmbNtlmAuthRequest request;
-    tSmbNtlmAuthChallenge challenge;
-    tSmbNtlmAuthResponse response;
-
-    char msgbuf[2048];
-    int result,len;
+    int result;
 
     gen_send(sock, "AUTHENTICATE NTLM");
 
-    if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
+    if ((result = ntlm_helper(sock, ctl, "IMAP")))
        return result;
-  
-    if (msgbuf[0] != '+')
-       return PS_AUTHFAIL;
-  
-    buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
-
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthRequest(stdout, &request);
-
-    memset(msgbuf,0,sizeof msgbuf);
-    to64frombits (msgbuf, &request, SmbLength(&request));
-  
-    if (outlevel >= O_MONITOR)
-       report(stdout, "IMAP> %s\n", msgbuf);
-  
-    strcat(msgbuf,"\r\n");
-    SockWrite (sock, msgbuf, strlen (msgbuf));
-
-    if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
-       return result;
-  
-    len = from64tobits (&challenge, msgbuf, sizeof(challenge));
-    
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthChallenge(stdout, &challenge);
-    
-    buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
-  
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthResponse(stdout, &response);
-  
-    memset(msgbuf,0,sizeof msgbuf);
-    to64frombits (msgbuf, &response, SmbLength(&response));
-
-    if (outlevel >= O_MONITOR)
-       report(stdout, "IMAP> %s\n", msgbuf);
-      
-    strcat(msgbuf,"\r\n");
-    SockWrite (sock, msgbuf, strlen (msgbuf));
-  
+
     result = imap_ok (sock, NULL);
     if (result == PS_SUCCESS)
        return PS_SUCCESS;
@@ -364,7 +321,7 @@ static void imap_canonicalize(char *result, char *raw, size_t maxlen)
     result[j] = '\0';
 }
 
-static void capa_probe(int sock, struct query *ctl)
+static int capa_probe(int sock, struct query *ctl)
 /* set capability variables from a CAPA probe */
 {
     int        ok;
@@ -394,12 +351,8 @@ static void capa_probe(int sock, struct query *ctl)
                report(stdout, GT_("Protocol identified as IMAP4 rev 0\n"));
        }
     }
-    else if (ok == PS_ERROR)
-    {
-       imap_version = IMAP2;
-       if (outlevel >= O_DEBUG)
-           report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n"));
-    }
+    else
+       return ok;
 
     /* 
      * Handle idling.  We depend on coming through here on startup
@@ -416,7 +369,9 @@ static void capa_probe(int sock, struct query *ctl)
            report(stdout, GT_("will idle after poll\n"));
     }
 
-    peek_capable = (imap_version >= IMAP4);
+    peek_capable = TRUE;
+
+    return PS_SUCCESS;
 }
 
 static int do_authcert (int sock, const char *command, const char *name)
@@ -441,9 +396,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
 /* apply for connection authorization */
 {
     int ok = 0;
-#ifdef SSL_ENABLE
-    int got_tls = 0;
-#endif
     (void)greeting;
 
     /*
@@ -455,7 +407,8 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     else
        expunge_period = 1;
 
-    capa_probe(sock, ctl);
+    if ((ok = capa_probe(sock, ctl)))
+       return ok;
 
     /* 
      * If either (a) we saw a PREAUTH token in the greeting, or
@@ -477,16 +430,17 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
        if (ctl->sslcommonname)
            commonname = ctl->sslcommonname;
 
-       if (strstr(capabilities, "STARTTLS"))
+       if (strstr(capabilities, "STARTTLS")
+               || must_tls(ctl)) /* if TLS is mandatory, ignore capabilities */
        {
            /* Use "tls1" rather than ctl->sslproto because tls1 is the only
             * protocol that will work with STARTTLS.  Don't need to worry
             * whether TLS is mandatory or opportunistic unless SSLOpen() fails
             * (see below). */
            if (gen_transact(sock, "STARTTLS") == PS_SUCCESS
-                   && SSLOpen(sock, ctl->sslcert, ctl->sslkey, "tls1", ctl->sslcertck,
+                   && (set_timeout(mytimeout), SSLOpen(sock, ctl->sslcert, ctl->sslkey, "tls1", ctl->sslcertck,
                        ctl->sslcertfile, ctl->sslcertpath, ctl->sslfingerprint, commonname,
-                       ctl->server.pollname, &ctl->remotename) != -1)
+                       ctl->server.pollname, &ctl->remotename)) != -1)
            {
                /*
                 * RFC 2595 says this:
@@ -501,22 +455,20 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
                 * Now that we're confident in our TLS connection we can
                 * guarantee a secure capability re-probe.
                 */
-               got_tls = 1;
-               capa_probe(sock, ctl);
+               if ((ok = capa_probe(sock, ctl)))
+                   return ok;
                if (outlevel >= O_VERBOSE)
                {
                    report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname);
                }
-           }
-       }
-
-       if (!got_tls) {
-           if (must_tls(ctl)) {
+           } else if (must_tls(ctl)) {
                /* Config required TLS but we couldn't guarantee it, so we must
                 * stop. */
+               set_timeout(0);
                report(stderr, GT_("%s: upgrade to TLS failed.\n"), commonname);
                return PS_SOCKET;
            } else {
+               set_timeout(0);
                if (outlevel >= O_VERBOSE) {
                    report(stdout, GT_("%s: opportunistic upgrade to TLS failed, trying to continue\n"), commonname);
                }
@@ -573,8 +525,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
        if ((ok = do_gssauth(sock, "AUTHENTICATE", "imap",
                        ctl->server.truename, ctl->remotename)))
        {
-           /* SASL cancellation of authentication */
-           gen_send(sock, "*");
            if (ctl->server.authenticate != A_ANY)
                 return ok;
        } else  {
@@ -583,24 +533,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     }
 #endif /* GSSAPI */
 
-#ifdef 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 ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
-       {
-           /* SASL cancellation of authentication */
-           gen_send(sock, "*");
-           if(ctl->server.authenticate != A_ANY)
-                return ok;
-       }
-       else
-           return ok;
-    }
-#endif /* KERBEROS_V4 */
-
     /*
      * No such luck.  OK, now try the variants that mask your password
      * in a challenge-response.
@@ -611,8 +543,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     {
        if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
        {
-           /* SASL cancellation of authentication */
-           gen_send(sock, "*");
            if(ctl->server.authenticate != A_ANY)
                 return ok;
        }
@@ -648,8 +578,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
        && 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;
        }
@@ -664,15 +592,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
     }
 #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, 
-              GT_("Required LOGIN capability not supported by server\n"));
-    }
-#endif /* __UNUSED__ */
-
     /* 
      * We're stuck with sending the password en clair.
      * The reason for this odd-looking logic is that some
@@ -703,8 +622,6 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
        free(remotename);
        if (ok)
        {
-           /* SASL cancellation of authentication */
-           gen_send(sock, "*");
            if(ctl->server.authenticate != A_ANY)
                 return ok;
        }
@@ -813,45 +730,33 @@ static int imap_idle(int sock)
     return(ok);
 }
 
-/* maximum number of numbers we can process in "SEARCH" response */
-# define IMAP_SEARCH_MAX 1000
-
 static int imap_search(int sock, struct query *ctl, int count)
 /* search for unseen messages */
 {
-    int ok, first, last;
+    int ok;
     char buf[MSGBUFSIZE+1], *cp;
 
     /* Don't count deleted messages. Enabled only for IMAP4 servers or
      * higher and only when keeping mails. This flag will have an
      * effect only when user has marked some unread mails for deletion
      * using another e-mail client. */
-    flag skipdeleted = (imap_version >= IMAP4) && ctl->keep;
+    flag skipdeleted = ctl->keep;
     const char *undeleted;
 
-    /* Skip range search if there are less than or equal to
-     * IMAP_SEARCH_MAX mails. */
-    flag skiprangesearch = (count <= IMAP_SEARCH_MAX);
+    /* structure to keep the end portion of the incomplete response */
+    struct RecvSplit rs;
 
     /* startcount is higher than count so that if there are no
      * unseen messages, imap_getsizes() will not need to do
      * anything! */
     startcount = count + 1;
 
-    for (first = 1, last = IMAP_SEARCH_MAX; first <= count; first += IMAP_SEARCH_MAX, last += IMAP_SEARCH_MAX)
+    for (;;)
     {
-       if (last > count)
-           last = count;
-
-restartsearch:
        undeleted = (skipdeleted ? " UNDELETED" : "");
-       if (skiprangesearch)
-           gen_send(sock, "SEARCH UNSEEN%s", undeleted);
-       else if (last == first)
-           gen_send(sock, "SEARCH %d UNSEEN%s", last, undeleted);
-       else
-           gen_send(sock, "SEARCH %d:%d UNSEEN%s", first, last, undeleted);
-       while ((ok = imap_response(sock, buf)) == PS_UNTAGGED)
+       gen_send(sock, "SEARCH UNSEEN%s", undeleted);
+       gen_recv_split_init("* SEARCH", &rs);
+       while ((ok = imap_response(sock, buf, &rs)) == PS_UNTAGGED)
        {
            if ((cp = strstr(buf, "* SEARCH")))
            {
@@ -883,39 +788,25 @@ restartsearch:
                }
            }
        }
-       /* if there is a protocol error on the first loop, try a
-        * different search command */
-       if (ok == PS_ERROR && first == 1)
+       if (ok != PS_ERROR) /* success or non-protocol error */
+           return(ok);
+
+       /* there is a protocol error. try a different search command. */
+       if (skipdeleted)
        {
-           if (skipdeleted)
-           {
-               /* retry with "SEARCH 1:1000 UNSEEN" */
-               skipdeleted = FALSE;
-               goto restartsearch;
-           }
-           if (!skiprangesearch)
-           {
-               /* retry with "SEARCH UNSEEN" */
-               skiprangesearch = TRUE;
-               goto restartsearch;
-           }
-           /* try with "FETCH 1:n FLAGS" */
-           goto fetchflags;
+           /* retry with "SEARCH UNSEEN" */
+           skipdeleted = FALSE;
+           continue;
        }
-       if (ok != PS_SUCCESS)
-           return(ok);
-       /* loop back only when searching in range */
-       if (skiprangesearch)
-           break;
+       /* try with "FETCH 1:n FLAGS" */
+       break;
     }
-    return(PS_SUCCESS);
 
-fetchflags:
     if (count == 1)
        gen_send(sock, "FETCH %d FLAGS", count);
     else
        gen_send(sock, "FETCH %d:%d FLAGS", 1, count);
-    while ((ok = imap_response(sock, buf)) == PS_UNTAGGED)
+    while ((ok = imap_response(sock, buf, NULL)) == PS_UNTAGGED)
     {
        unsigned int num;
        int consumed;
@@ -1126,7 +1017,7 @@ static int imap_getpartialsizes(int sock, int first, int last, int *sizes)
        gen_send(sock, "FETCH %d:%d RFC822.SIZE", first, last);
     else /* no unseen messages! */
        return(PS_SUCCESS);
-    while ((ok = imap_response(sock, buf)) == PS_UNTAGGED)
+    while ((ok = imap_response(sock, buf, NULL)) == PS_UNTAGGED)
     {
        unsigned int size;
        int num;
@@ -1214,7 +1105,7 @@ 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 */
-    if ((ok = imap_response(sock, buf)) == PS_UNTAGGED)
+    if ((ok = imap_response(sock, buf, NULL)) == PS_UNTAGGED)
     {
                int consumed;
        /* expected response formats:
@@ -1239,7 +1130,8 @@ static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
 
        /* try to recover for some responses */
        if (!strncmp(buf, "* NO", 4) ||
-               !strncmp(buf, "* BAD", 5))
+               !strncmp(buf, "* BAD", 5) ||
+               strstr(buf, "FETCH ()"))
        {
            return(PS_TRANSIENT);
        }
@@ -1254,7 +1146,7 @@ static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
        /* an unexpected tagged response */
        if (outlevel > O_SILENT)
            report(stderr, GT_("Incorrect FETCH response: %s.\n"), buf);
-       return(PS_ERROR);
+       return(PS_TRANSIENT);
     }
     return(ok);
 }
@@ -1280,28 +1172,23 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
      * equivalent".  However, we know of at least one server that
      * treats them differently in the presence of MIME attachments;
      * the latter form downloads the attachment, the former does not.
-     * The server is InterChange, and the fool who implemented this
-     * misfeature ought to be strung up by his thumbs.  
+     * The server is InterChange.
      *
      * When I tried working around this by disabling use of the 4rev1 form,
      * I found that doing this breaks operation with M$ Exchange.
      * Annoyingly enough, Exchange's refusal to cope is technically legal
-     * under RFC2062.  Trust Microsoft, the Great Enemy of interoperability
-     * standards, to find a way to make standards compliance irritating....
+     * under RFC2062.
      */
     switch (imap_version)
     {
     case IMAP4rev1:    /* RFC 2060 */
+    default:
        gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
        break;
 
     case IMAP4:                /* RFC 1730 */
        gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
        break;
-
-    default:           /* RFC 1176 */
-       gen_send(sock, "FETCH %d RFC822.TEXT", number);
-       break;
     }
 
     /* looking for FETCH response */
@@ -1323,6 +1210,13 @@ static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
            return PS_SUCCESS;
     }
 
+    /* Understand the empty string. Seen on Yahoo. */
+    /* XXX FIXME: we should be able to handle strings here. */
+    if (strstr(buf+10, "\"\")")) {
+           *lenp = 0;
+           return PS_SUCCESS;
+    }
+
     /*
      * Try to extract a length from the FETCH response.  RFC2060 requires
      * it to be present, but at least one IMAP server (Novell GroupWise)
@@ -1363,26 +1257,26 @@ static int imap_delete(int sock, struct query *ctl, int number)
 /* set delete flag for given message */
 {
     int        ok;
+    /* Select which flags to set on message deletion: */
+    const char delflags_seen[] = "\\Seen \\Deleted";
+    static const char *delflags;
+    /* Which environment variable to look for: */
+
+    /* DEFAULT since many fetchmail versions <= 6.3.X */
+    delflags = delflags_seen;
 
     (void)ctl;
     /* expunges change the fetch numbers */
     number -= expunged;
 
     /*
-     * Use SILENT if possible as a minor throughput optimization.
-     * Note: this has been dropped from IMAP4rev1.
-     *
      * We set Seen because there are some IMAP servers (notably HP
      * OpenMail) that do message-receipt DSNs, but only when the seen
      * bit is set.  This is the appropriate time -- we get here right
      * after the local SMTP response that says delivery was
      * successful.
      */
-    if ((ok = gen_transact(sock,
-                       imap_version == IMAP4 
-                               ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
-                               : "STORE %d +FLAGS (\\Seen \\Deleted)", 
-                       number)))
+    if ((ok = gen_transact(sock, "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)", number)))
        return(ok);
     else
        deletions++;
@@ -1410,11 +1304,7 @@ static int imap_mark_seen(int sock, struct query *ctl, int number)
     /* expunges change the message numbers */
     number -= expunged;
 
-    return(gen_transact(sock,
-       imap_version == IMAP4
-       ? "STORE %d +FLAGS.SILENT (\\Seen)"
-       : "STORE %d +FLAGS (\\Seen)",
-       number));
+    return(gen_transact(sock,"STORE %d +FLAGS.SILENT (\\Seen)", number));
 }
 
 static int imap_end_mailbox_poll(int sock, struct query *ctl)