diff options
author | Matthias Andree <matthias.andree@gmx.de> | 2010-02-04 09:51:01 +0000 |
---|---|---|
committer | Matthias Andree <matthias.andree@gmx.de> | 2010-02-04 09:51:01 +0000 |
commit | dca4a906d60a197b09159bc8d8a16625b1790215 (patch) | |
tree | c6bfd891f064ebaf751eda9913edcbd3c8ce8435 | |
parent | f1c7607615ebd48807db6170937fe79bb89d47d4 (diff) | |
download | fetchmail-dca4a906d60a197b09159bc8d8a16625b1790215.tar.gz fetchmail-dca4a906d60a197b09159bc8d8a16625b1790215.tar.bz2 fetchmail-dca4a906d60a197b09159bc8d8a16625b1790215.zip |
IMAP SEARCH fixes & FETCH fallback by Sunil Shetye
* The IMAP client now uses "SEARCH UNSEEN" rather than "SEARCH UNSEEN NOT
DELETED" again on IMAP2, to fix a regression in fetchmail 6.2.5 reported by
Will Stringer in June 2004. (Sunil Shetye)
* The IMAP client now uses "SEARCH UNSEEN UNDELETED" on IMAP4 and IMAP4r1
servers (Sunil Shetye).
* Workaround: The IMAP client now falls back to "FETCH n:m FLAGS" if the server
does not support "SEARCH". (Sunil Shetye)
* The IMAP client now requests message numbers in batches of 1,000 to avoid
problems if there are more than 1860 unseen messages. (Sunil Shetye)
Note that this wasn't security relevant because fetchmail would only read up
to the maximum buffer size and leave the remainder of the string unread, going
out of synch afterwards.
svn path=/branches/BRANCH_6-3/; revision=5468
-rw-r--r-- | NEWS | 12 | ||||
-rw-r--r-- | imap.c | 169 |
2 files changed, 142 insertions, 39 deletions
@@ -67,6 +67,18 @@ fetchmail 6.3.14 (not yet released): * The SMTP client now recovers from errors (such as servers dropping the connection after errors) when sending an RSET command. Fix by Sunil Shetye. Report by James Moe. +* The IMAP client now uses "SEARCH UNSEEN" rather than "SEARCH UNSEEN NOT + DELETED" again on IMAP2, to fix a regression in fetchmail 6.2.5 reported by + Will Stringer in June 2004. (Sunil Shetye) +* The IMAP client now uses "SEARCH UNSEEN UNDELETED" on IMAP4 and IMAP4r1 + servers (Sunil Shetye). +* Workaround: The IMAP client now falls back to "FETCH n:m FLAGS" if the server + does not support "SEARCH". (Sunil Shetye) +* The IMAP client now requests message numbers in batches of 1,000 to avoid + problems if there are more than 1860 unseen messages. (Sunil Shetye) + Note that this wasn't security relevant because fetchmail would only read up + to the maximum buffer size and leave the remainder of the string unread, going + out of synch afterwards. # CHANGES * Only include gssapi.h if we're not including gssapi/gssapi.h, to fix a FreeBSD @@ -798,6 +798,135 @@ 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; + 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; + const char *undeleted; + + /* Skip range search if there are less than or equal to + * IMAP_SEARCH_MAX mails. */ + flag skiprangesearch = (count <= IMAP_SEARCH_MAX); + + /* 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) + { + 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) + { + if ((cp = strstr(buf, "* SEARCH"))) + { + char *ep; + + cp += 8; /* skip "* SEARCH" */ + while (*cp && unseen < count) + { + /* skip whitespace */ + while (*cp && isspace((unsigned char)*cp)) + cp++; + if (*cp) + { + unsigned long um; + + errno = 0; + um = strtoul(cp,&ep,10); + if (errno == 0 && um <= UINT_MAX && um <= (unsigned)count) + { + unseen_messages[unseen++] = um; + if (outlevel >= O_DEBUG) + report(stdout, GT_("%lu is unseen\n"), um); + if (startcount > um) + startcount = um; + } + cp = ep; + } + } + } + } + /* if there is a protocol error on the first loop, try a + * different search command */ + if (ok == PS_ERROR && first == 1) + { + 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; + } + if (ok != PS_SUCCESS) + return(ok); + /* loop back only when searching in range */ + if (skiprangesearch) + 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) + { + unsigned int num; + + /* expected response format: + * IMAP< * 1 FETCH (FLAGS (\Seen)) + * IMAP< * 2 FETCH (FLAGS (\Seen \Deleted)) + * IMAP< * 3 FETCH (FLAGS ()) + * IMAP< * 4 FETCH (FLAGS (\Recent)) + * IMAP< * 5 FETCH (UID 10 FLAGS (\Recent)) + */ + if (unseen < count + && sscanf(buf, "* %u FETCH ", &num) == 1 + && num >= 1 && num <= count + && strstr(buf, "FLAGS ") + && !strstr(buf, "\\SEEN") + && !strstr(buf, "\\DELETED")) + { + unseen_messages[unseen++] = num; + if (outlevel >= O_DEBUG) + report(stdout, GT_("%u is unseen\n"), num); + if (startcount > num) + startcount = num; + } + } + return(ok); +} + static int imap_getrange(int sock, struct query *ctl, const char *folder, @@ -805,7 +934,6 @@ static int imap_getrange(int sock, /* get range of messages to be fetched */ { int ok; - char buf[MSGBUFSIZE+1], *cp; /* find out how many messages are waiting */ *bytes = -1; @@ -909,44 +1037,7 @@ static int imap_getrange(int sock, memset(unseen_messages, 0, count * sizeof(unsigned int)); unseen = 0; - /* don't count deleted messages, in case user enabled keep last time */ - gen_send(sock, "SEARCH UNSEEN NOT DELETED"); - while ((ok = imap_response(sock, buf)) == PS_UNTAGGED) - { - if ((cp = strstr(buf, "* SEARCH"))) - { - 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((unsigned char)*cp)) - cp++; - if (*cp) - { - unsigned long um; - - errno = 0; - um = strtoul(cp,&ep,10); - if (errno == 0 && um <= UINT_MAX && um <= (unsigned)count) - { - unseen_messages[unseen++] = um; - if (outlevel >= O_DEBUG) - report(stdout, GT_("%lu is unseen\n"), um); - if (startcount > um) - startcount = um; - } - cp = ep; - } - } - } - } + ok = imap_search(sock, ctl, count); if (ok != 0) { report(stderr, GT_("search for unseen messages failed\n")); |