diff options
author | Eric S. Raymond <esr@thyrsus.com> | 1997-09-30 18:38:10 +0000 |
---|---|---|
committer | Eric S. Raymond <esr@thyrsus.com> | 1997-09-30 18:38:10 +0000 |
commit | 9ae98cb27ed3c6856a5169f96ce11f278b057e2d (patch) | |
tree | e436c58e5ebb829307efad5d1f1368d331f8f6af | |
parent | 3e03d50356bef6820d79e94a8519213f2bfcd9e3 (diff) | |
download | fetchmail-9ae98cb27ed3c6856a5169f96ce11f278b057e2d.tar.gz fetchmail-9ae98cb27ed3c6856a5169f96ce11f278b057e2d.tar.bz2 fetchmail-9ae98cb27ed3c6856a5169f96ce11f278b057e2d.zip |
Smart re-polling support.
svn path=/trunk/; revision=1437
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | driver.c | 551 | ||||
-rw-r--r-- | etrn.c | 1 | ||||
-rw-r--r-- | fetchmail-FAQ.html | 7 | ||||
-rw-r--r-- | fetchmail.h | 1 | ||||
-rw-r--r-- | imap.c | 27 | ||||
-rw-r--r-- | pop2.c | 1 | ||||
-rw-r--r-- | pop3.c | 1 |
8 files changed, 317 insertions, 275 deletions
@@ -14,6 +14,9 @@ ------------------------------------------------------------------------------ fetchmail-4.2.9 () * Don't byte-stuff when writing to an MDA. +* IMAP mode now does smart re-polling, eliminating connection/authentication + overhead when messages are being delivered to the server mailbox while + the poll is in progress. There are 285 people on the fetchmail-friends list. @@ -1535,325 +1535,342 @@ const struct method *proto; /* protocol method table */ /* now iterate over each folder selected */ for (idp = ctl->mailboxes; idp; idp = idp->next) { - if (outlevel >= O_VERBOSE) - if (idp->next) - error(0, 0, "selecting folder %s"); - else - error(0, 0, "selecting default folder"); - - /* compute number of messages and number of new messages waiting */ - ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); - - /* show user how many messages we downloaded */ - if (idp->id) - (void) sprintf(buf, "%s@%s:%s", - ctl->remotename, realname, idp->id); - else - (void) sprintf(buf, "%s@%s", ctl->remotename, realname); - if (outlevel > O_SILENT) - if (count == -1) /* only used for ETRN */ - error(0, 0, "Polling %s", realname); - else if (count == 0) - { - /* these are pointless in normal daemon mode */ - if (poll_interval == 0 || outlevel == O_VERBOSE ) - error(0, 0, "No mail at %s", buf); - } + do { + if (outlevel >= O_VERBOSE) + if (idp->next) + error(0, 0, "selecting or re-polling folder %s"); + else + error(0, 0, "selecting or re-polling default folder"); + + /* compute # of messages and number of new messages waiting */ + ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); + + /* show user how many messages we downloaded */ + if (idp->id) + (void) sprintf(buf, "%s@%s:%s", + ctl->remotename, realname, idp->id); else - { - if (new != -1 && (count - new) > 0) - error(0, 0, "%d message%s (%d seen) at %s.", - count, count > 1 ? "s" : "", count-new, buf); + (void) sprintf(buf, "%s@%s", ctl->remotename, realname); + if (outlevel > O_SILENT) + if (count == -1) /* only used for ETRN */ + error(0, 0, "Polling %s", realname); + else if (count == 0) + { + /* these are pointless in normal daemon mode */ + if (poll_interval == 0 || outlevel == O_VERBOSE ) + error(0, 0, "No mail at %s", buf); + break; + } else - error(0, 0, "%d message%s at %s.", - count, count > 1 ? "s" : "", buf); - } - - if (check_only) - { - if (new == -1 || ctl->fetchall) - new = count; - ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL); - goto cleanUp; - } - else if (count > 0) - { - flag force_retrieval; - - /* - * What forces this code is that in POP3 and IMAP2BIS you can't - * fetch a message without having it marked `seen'. In IMAP4, - * on the other hand, you can (peek_capable is set to convey - * this). - * - * The result of being unable to peek is that if there's - * any kind of transient error (DNS lookup failure, or - * sendmail refusing delivery due to process-table limits) - * the message will be marked "seen" on the server without - * having been delivered. This is not a big problem if - * fetchmail is running in foreground, because the user - * will see a "skipped" message when it next runs and get - * clued in. - * - * But in daemon mode this leads to the message being silently - * ignored forever. This is not acceptable. - * - * We compensate for this by checking the error count from the - * previous pass and forcing all messages to be considered new - * if it's nonzero. - */ - force_retrieval = !peek_capable && (ctl->errcount > 0); + { + if (new != -1 && (count - new) > 0) + error(0, 0, "%d message%s (%d seen) at %s.", + count, count > 1 ? "s" : "", count-new, buf); + else + error(0, 0, "%d message%s at %s.", + count, count > 1 ? "s" : "", buf); + } - /* - * We need the size of each method before it's loaded in - * order to pass via the ESMTP SIZE option. If the protocol - * has a getsizes method, we presume this means it doesn't - * get reliable sizes from message fetch responses. - */ - if (proto->getsizes) + if (check_only) { - msgsizes = (int *)alloca(sizeof(int) * count); - - ok = (proto->getsizes)(sock, count, msgsizes); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); + if (new == -1 || ctl->fetchall) + new = count; + ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL); + goto cleanUp; } - - /* read, forward, and delete messages */ - for (num = 1; num <= count; num++) - { - flag toolarge = (ctl->limit > 0) - && msgsizes && (msgsizes[num-1] > ctl->limit); - flag fetch_it = !toolarge - && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num))); - flag suppress_delete = FALSE; - flag suppress_forward = FALSE; - flag retained = FALSE; + else if (count > 0) + { + flag force_retrieval; /* - * This check copes with Post Office/NT's annoying habit - * of randomly prepending bogus LIST items of length -1. - * Patrick Audley <paudley@pobox.com> tells us: - * LIST shows a size of -1, RETR and TOP return - * "-ERR System error - couldn't open message", and DELE - * succeeds but doesn't actually delete the message. + * What forces this code is that in POP3 and + * IMAP2BIS you can't fetch a message without + * having it marked `seen'. In IMAP4, on the + * other hand, you can (peek_capable is set to + * convey this). + * + * The result of being unable to peek is that if there's + * any kind of transient error (DNS lookup failure, or + * sendmail refusing delivery due to process-table limits) + * the message will be marked "seen" on the server without + * having been delivered. This is not a big problem if + * fetchmail is running in foreground, because the user + * will see a "skipped" message when it next runs and get + * clued in. + * + * But in daemon mode this leads to the message + * being silently ignored forever. This is not + * acceptable. + * + * We compensate for this by checking the error + * count from the previous pass and forcing all + * messages to be considered new if it's nonzero. */ - if (msgsizes && msgsizes[num-1] == -1) - { - if (outlevel >= O_VERBOSE) - error(0, 0, - "Skipping message %d, length -1", - num - 1); - continue; - } - - /* we may want to reject this message if it's old */ - if (!fetch_it) - { - if (outlevel > O_SILENT) - { - error_build("skipping message %d", num); - if (toolarge) - error_build(" (oversized, %d bytes)", msgsizes[num-1]); - } - } - else + force_retrieval = !peek_capable && (ctl->errcount > 0); + + /* + * We need the size of each message before it's + * loaded in order to pass via the ESMTP SIZE + * option. If the protocol has a getsizes method, + * we presume this means it doesn't get reliable + * sizes from message fetch responses. + */ + if (proto->getsizes) { - flag wholesize = !protocol->fetch_body; + msgsizes = (int *)alloca(sizeof(int) * count); - /* request a message */ - ok = (protocol->fetch_headers)(sock, ctl, num, &len); + ok = (proto->getsizes)(sock, count, msgsizes); if (ok != 0) goto cleanUp; set_timeout(ctl->server.timeout); + } - /* -1 means we didn't see a size in the response */ - if (len == -1 && msgsizes) + /* read, forward, and delete messages */ + for (num = 1; num <= count; num++) + { + flag toolarge = (ctl->limit > 0) + && msgsizes && (msgsizes[num-1] > ctl->limit); + flag fetch_it = !toolarge + && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num))); + flag suppress_delete = FALSE; + flag suppress_forward = FALSE; + flag retained = FALSE; + + /* + * This check copes with Post Office/NT's + * annoying habit of randomly prepending bogus + * LIST items of length -1. Patrick Audley + * <paudley@pobox.com> tells us: LIST shows a + * size of -1, RETR and TOP return "-ERR + * System error - couldn't open message", and + * DELE succeeds but doesn't actually delete + * the message. + */ + if (msgsizes && msgsizes[num-1] == -1) { - len = msgsizes[num - 1]; - wholesize = TRUE; + if (outlevel >= O_VERBOSE) + error(0, 0, + "Skipping message %d, length -1", + num - 1); + continue; } - if (outlevel > O_SILENT) + /* we may want to reject this message if it's old */ + if (!fetch_it) { - error_build("reading message %d of %d",num,count); - - if (len > 0) - error_build(" (%d %sbytes)", - len, wholesize ? "" : "header "); - if (outlevel == O_VERBOSE) - error_complete(0, 0, ""); - else - error_build(" "); + if (outlevel > O_SILENT) + { + error_build("skipping message %d", num); + if (toolarge) + error_build(" (oversized, %d bytes)", + msgsizes[num-1]); + } } - - /* - * Read the message headers and ship them to the - * output sink. - */ - ok = readheaders(sock, len, ctl, realname, num); - if (ok == PS_RETAINED) - suppress_forward = retained = TRUE; - else if (ok == PS_TRANSIENT) - suppress_delete = suppress_forward = TRUE; - else if (ok == PS_REFUSED) - suppress_forward = TRUE; - else if (ok) - goto cleanUp; - set_timeout(ctl->server.timeout); - - /* - * If we're using IMAP4 or something else that - * can fetch headers separately from bodies, - * it's time to request the body now. This - * fetch may be skipped if we got an anti-spam - * or other PS_REFUSED error response during - * read_headers. - */ - if (protocol->fetch_body) + else { - if (outlevel == O_VERBOSE) - fputc('\n', stderr); + flag wholesize = !protocol->fetch_body; - if ((ok = (protocol->trail)(sock, ctl, num))) + /* request a message */ + ok = (protocol->fetch_headers)(sock,ctl,num, &len); + if (ok != 0) goto cleanUp; set_timeout(ctl->server.timeout); - len = 0; - if (!suppress_forward) + + /* -1 means we didn't see a size in the response */ + if (len == -1 && msgsizes) { - if ((ok=(protocol->fetch_body)(sock,ctl,num,&len))) - goto cleanUp; - if (outlevel > O_SILENT && !wholesize) - error_build(" (%d body bytes) ", len); - set_timeout(ctl->server.timeout); + len = msgsizes[num - 1]; + wholesize = TRUE; } - } - /* process the body now */ - if (len > 0) - { - ok = readbody(sock, - ctl, - !suppress_forward, - len); - if (ok == PS_TRANSIENT) + if (outlevel > O_SILENT) + { + error_build("reading message %d of %d", + num,count); + + if (len > 0) + error_build(" (%d %sbytes)", + len, wholesize ? "" : "header "); + if (outlevel == O_VERBOSE) + error_complete(0, 0, ""); + else + error_build(" "); + } + + /* + * Read the message headers and ship them to the + * output sink. + */ + ok = readheaders(sock, len, ctl, realname, num); + if (ok == PS_RETAINED) + suppress_forward = retained = TRUE; + else if (ok == PS_TRANSIENT) suppress_delete = suppress_forward = TRUE; + else if (ok == PS_REFUSED) + suppress_forward = TRUE; else if (ok) goto cleanUp; set_timeout(ctl->server.timeout); - /* tell server we got it OK and resynchronize */ - if (protocol->trail) + /* + * If we're using IMAP4 or something else that + * can fetch headers separately from bodies, + * it's time to request the body now. This + * fetch may be skipped if we got an anti-spam + * or other PS_REFUSED error response during + * read_headers. + */ + if (protocol->fetch_body) { if (outlevel == O_VERBOSE) fputc('\n', stderr); - ok = (protocol->trail)(sock, ctl, num); - if (ok != 0) + if ((ok = (protocol->trail)(sock, ctl, num))) goto cleanUp; set_timeout(ctl->server.timeout); + len = 0; + if (!suppress_forward) + { + if ((ok=(protocol->fetch_body)(sock,ctl,num,&len))) + goto cleanUp; + if (outlevel > O_SILENT && !wholesize) + error_build(" (%d body bytes) ", len); + set_timeout(ctl->server.timeout); + } } - } - /* - * Check to see if the numbers matched? - * - * Yes, some servers foo this up horribly. - * All IMAP servers seem to get it right, and - * so does Eudora QPOP at least in 2.xx - * versions. - * - * Microsoft Exchange gets it completely - * wrong, reporting compressed rather than - * actual sizes (so the actual length of - * message is longer than the reported size). - * Another fine example of Microsoft brain death! - * - * Some older POP servers, like the old UCB - * POP server and the pre-QPOP QUALCOMM - * versions, report a longer size in the LIST - * response than actually gets shipped up. - * It's unclear what is going on here, as the - * QUALCOMM server (at least) seems to be - * reporting the on-disk size correctly. - */ - if (msgsizes && msglen != msgsizes[num-1]) - { - if (outlevel >= O_VERBOSE) - error(0, 0, - "message %d was not the expected length (%d != %d)", - num, msglen, msgsizes[num-1]); - } + /* process the body now */ + if (len > 0) + { + ok = readbody(sock, + ctl, + !suppress_forward, + len); + if (ok == PS_TRANSIENT) + suppress_delete = suppress_forward = TRUE; + else if (ok) + goto cleanUp; + set_timeout(ctl->server.timeout); - /* end-of-message processing starts here */ + /* tell server we got it OK and resynchronize */ + if (protocol->trail) + { + if (outlevel == O_VERBOSE) + fputc('\n', stderr); + + ok = (protocol->trail)(sock, ctl, num); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); + } + } - if (ctl->mda) - { - int rc; + /* + * Check to see if the numbers matched? + * + * Yes, some servers foo this up horribly. + * All IMAP servers seem to get it right, and + * so does Eudora QPOP at least in 2.xx + * versions. + * + * Microsoft Exchange gets it completely + * wrong, reporting compressed rather than + * actual sizes (so the actual length of + * message is longer than the reported size). + * Another fine example of Microsoft brain death! + * + * Some older POP servers, like the old UCB + * POP server and the pre-QPOP QUALCOMM + * versions, report a longer size in the LIST + * response than actually gets shipped up. + * It's unclear what is going on here, as the + * QUALCOMM server (at least) seems to be + * reporting the on-disk size correctly. + */ + if (msgsizes && msglen != msgsizes[num-1]) + { + if (outlevel >= O_VERBOSE) + error(0, 0, + "message %d was not the expected length (%d != %d)", + num, msglen, msgsizes[num-1]); + } - /* close the delivery pipe, we'll reopen before next message */ - rc = pclose(sinkfp); - signal(SIGCHLD, sigchld); - if (rc) + /* end-of-message processing starts here */ + + if (ctl->mda) { - error(0, -1, "MDA exited abnormally or returned nonzero status"); - goto cleanUp; + int rc; + + /* close the delivery pipe, we'll reopen before next message */ + rc = pclose(sinkfp); + signal(SIGCHLD, sigchld); + if (rc) + { + error(0, -1, "MDA exited abnormally or returned nonzero status"); + goto cleanUp; + } } - } - else if (!suppress_forward) - { - /* write message terminator */ - if (SMTP_eom(ctl->smtp_socket) != SM_OK) + else if (!suppress_forward) { - error(0, -1, "SMTP listener refused delivery"); - ctl->errcount++; - suppress_delete = TRUE; + /* write message terminator */ + if (SMTP_eom(ctl->smtp_socket) != SM_OK) + { + error(0, -1, "SMTP listener refused delivery"); + ctl->errcount++; + suppress_delete = TRUE; + } } + + fetches++; } - fetches++; - } + /* + * At this point in flow of control, either + * we've bombed on a protocol error or had + * delivery refused by the SMTP server + * (unlikely -- I've never seen it) or we've + * seen `accepted for delivery' and the + * message is shipped. It's safe to mark the + * message seen and delete it on the server + * now. + */ - /* - * At this point in flow of control, either we've bombed - * on a protocol error or had delivery refused by the SMTP - * server (unlikely -- I've never seen it) or we've seen - * `accepted for delivery' and the message is shipped. - * It's safe to mark the message seen and delete it on the - * server now. - */ + /* maybe we delete this message now? */ + if (retained) + { + if (outlevel > O_SILENT) + error_complete(0, 0, " retained"); + } + else if (protocol->delete + && !suppress_delete + && (fetch_it ? !ctl->keep : ctl->flush)) + { + deletions++; + if (outlevel > O_SILENT) + error_complete(0, 0, " flushed"); + ok = (protocol->delete)(sock, ctl, num); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); + delete_str(&ctl->newsaved, num); + } + else if (outlevel > O_SILENT) + error_complete(0, 0, " not flushed"); - /* maybe we delete this message now? */ - if (retained) - { - if (outlevel > O_SILENT) - error_complete(0, 0, " retained"); + /* perhaps this as many as we're ready to handle */ + if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches) + goto no_error; } - else if (protocol->delete - && !suppress_delete - && (fetch_it ? !ctl->keep : ctl->flush)) - { - deletions++; - if (outlevel > O_SILENT) - error_complete(0, 0, " flushed"); - ok = (protocol->delete)(sock, ctl, num); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); - delete_str(&ctl->newsaved, num); - } - else if (outlevel > O_SILENT) - error_complete(0, 0, " not flushed"); - - /* perhaps this as many as we're ready to handle */ - if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches) - goto no_error; } - } + } while + /* + * Only re-poll if we allowed deletions and had no errors. + * Otherwise it is far too easy to get into infinite loops. + */ + (protocol->retry && !ctl->keep && !ctl->errcount); } no_error: @@ -127,6 +127,7 @@ const static struct method etrn = NULL, /* no message trailer */ NULL, /* how to delete a message */ etrn_logout, /* log out, we're done */ + FALSE, /* no, we can't re-poll */ }; int doETRN (struct query *ctl) diff --git a/fetchmail-FAQ.html b/fetchmail-FAQ.html index fdbfb843..96864a77 100644 --- a/fetchmail-FAQ.html +++ b/fetchmail-FAQ.html @@ -10,7 +10,7 @@ <table width="100%" cellpadding=0><tr> <td width="30%">Back to <a href="index.html">Fetchmail Home Page</a> <td width="30%" align=center>To <a href="/~esr/sitemap.html">Site Map</a> -<td width="30%" align=right>$Date: 1997/09/27 15:31:01 $ +<td width="30%" align=right>$Date: 1997/09/30 18:38:09 $ </table> <HR> <H1>Frequently Asked Questions About Fetchmail</H1> @@ -265,7 +265,8 @@ AUTO mode).<P> If you have the option, we recommend using or installing IMAP4; it has the best facilities for tracking message "seen" states. It also -recovers from interrupted connections more gracefully than POP3.<P> +recovers from interrupted connections more gracefully than POP3, and +enables some significant performance optimizations.<P> You can find sources for IMAP software at <a href="http://www.imap.org"> The IMAP Connection</a>; we like the @@ -1446,7 +1447,7 @@ will look right.<p> <table width="100%" cellpadding=0><tr> <td width="30%">Back to <a href="index.html">Fetchmail Home Page</a> <td width="30%" align=center>To <a href="/~esr/sitemap.html">Site Map</a> -<td width="30%" align=right>$Date: 1997/09/27 15:31:01 $ +<td width="30%" align=right>$Date: 1997/09/30 18:38:09 $ </table> <P><ADDRESS>Eric S. Raymond <A HREF="mailto:esr@thyrsus.com"><esr@snark.thyrsus.com></A></ADDRESS> diff --git a/fetchmail.h b/fetchmail.h index df817234..91c6bbf6 100644 --- a/fetchmail.h +++ b/fetchmail.h @@ -171,6 +171,7 @@ struct method int (*trail)(); /* eat trailer of a message */ int (*delete)(); /* delete method */ int (*logout_cmd)(); /* logout command */ + flag retry; /* can getrange poll for new messages? */ }; #define TAGLEN 6 @@ -33,7 +33,7 @@ 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, seen, recent, unseen, deletions, expunged, imap_version; +static int count,seen,recent,unseen, deletions,expunged, imap_version, pass; int imap_ok(int sock, char *argbuf) /* parse command response */ @@ -418,11 +418,24 @@ static int imap_getrange(int sock, /* find out how many messages are waiting */ recent = unseen = -1; - ok = gen_transact(sock, "SELECT %s", folder ? folder : "INBOX"); - if (ok != 0) + + if (++pass > 1) { - error(0, 0, "mailbox selection failed"); - return(ok); + ok = gen_transact(sock, "NOOP"); + if (ok != 0) + { + error(0, 0, "re-poll failed"); + return(ok); + } + } + else + { + ok = gen_transact(sock, "SELECT %s", folder ? folder : "INBOX"); + if (ok != 0) + { + error(0, 0, "mailbox selection failed"); + return(ok); + } } *countp = count; @@ -635,6 +648,8 @@ static int imap_delete(int sock, struct query *ctl, int number) static int imap_logout(int sock, struct query *ctl) /* send logout command */ { + + /* if expunges after deletion have been suppressed, ship one now */ if (ctl->expunge <= 0 && deletions) { @@ -663,11 +678,13 @@ const static struct method imap = imap_trail, /* eat message trailer */ imap_delete, /* delete the message */ imap_logout, /* expunge and exit */ + TRUE, /* yes, we can re-poll */ }; int doIMAP(struct query *ctl) /* retrieve messages using IMAP Version 2bis or Version 4 */ { + pass = 0; return(do_protocol(ctl, &imap)); } @@ -136,6 +136,7 @@ const static struct method pop2 = pop2_trail, /* eat message trailer */ NULL, /* no POP2 delete method */ pop2_logout, /* log out, we're done */ + FALSE, /* no, we can't re-poll */ }; int doPOP2 (struct query *ctl) @@ -465,6 +465,7 @@ const static struct method pop3 = NULL, /* no message trailer */ pop3_delete, /* how to delete a message */ pop3_logout, /* log out, we're done */ + FALSE, /* no, we can't re-poll */ }; int doPOP3 (struct query *ctl) |