|
From: Matthias A. <mat...@gm...> - 2006-02-27 18:02:31
|
Brendan Lynch <bre...@ai...> writes:
> This code works fine until a notification of new mail is received (a "*
> 1 EXISTS" message is received). At this point normally one is in the
> "imap_ok()" routine called from line 637, and this correctly receives
> the notification message and parses it, updating the "count" variable.
> However it does not the return to imap_idle() routine, but instead
> reissues a recv call having set a much longer (300s) timeout and after
> the 300s have expired it then returns an error causing fetchmail to drop
> the IMAP connection (a main point of this idle code was to keep the
> connection open for subsequent message retrieval). Net result is that
> delivery of mail is delayed by 5 minutes and the server connection is
> dropped and reestablished each time a wait for mail occurs.
>
> The problem seems to be caused by the loop condition in imap_ok():
>
> 151 }
> 152 } while
> 153 (tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
> 154
>
> This assumes that tag[0] will be set to '\0' if one is not waiting for a
> tagged response. In this case the code should not be waiting for a
> tagged response (it is waiting for an unsolicited notification).
> However the 'tag' global character array is set by the gen_transact() at
> line 630 and is not cleared before the call to imap_ok() at line 637.
This is correct, an IMAP client is supposed to parse untagged responses
until a tagged response is received. Trying with Dovecot and hacking
fetchmail a bit so it doesn't recognize RECENT and uses the NOOP
emulation code yields:
...
fetchmail: IMAP> A0010 NOOP
fetchmail: IMAP< A0010 OK NOOP completed.
fetchmail: IMAP> A0011 NOOP
fetchmail: IMAP< * 1 EXISTS
fetchmail: IMAP< * 1 RECENT
fetchmail: IMAP< A0011 OK NOOP completed.
fetchmail: IMAP> A0012 NOOP
...
So it waits for the tagged NOOP response, and this is a requirement so
it actually picks up both the EXISTS and the RECENT responses of working
servers. Servers that do not respond with a tagged response to a NOOP
command are broken.
> The fix is a very simple one-line change to imap_idle (2 lines with
> comments):
>
> 630 ok = gen_transact(sock, "NOOP");
> 631 /* if there's an error (not likely) or we just found mail
> (stage
> 632 * has changed, timeout has also been restored), we're
> done */
> 633 if (ok != 0 || stage != STAGE_IDLE)
> 634 return(ok);
> 635
> + 636 /* clear tag so imap_ok does not expect tagged response */
> + 637 tag[0]='\0';
> 638 /* wait (briefly) for an unsolicited status update */
> 639 ok = imap_ok(sock, NULL);
So this patch would sort of break the IMAP client because it would jump
out of the loop before having read the reply. This requires more
thought. Casper Gripenberg reported a similar problem, so perhaps some
common upstream server software is the culprit (and he suggested a
different fix, I'll have a look at that too).
What software is your upstream server running?
Can I see a "fetchmail -Nvvv --nosyslog" trace of a failing IMAP session
with NOOP emulation?
> A second, more minor problem is that getting a "* RECENT" notification
> does not break a caller out of the imap_idle's imap_ok() call. This
> causes an extra 28second wait after being notified about a message
> before it is actually received.
>
> Diffs for the complete set of changes against 6.3.2 are attached to this
> email.
>
> I have seen this in fetchmail 6.2.5 and 6.3.2 on linux platforms, but
> this problem should be generic to all platforms.
> diff -Naur fetchmail-6.3.2/imap.c fetchmail-6.3.2a/imap.c
> --- fetchmail-6.3.2/imap.c 2006-01-20 10:38:45.000000000 +0000
> +++ fetchmail-6.3.2a/imap.c 2006-02-23 23:54:52.000000000 +0000
> @@ -116,6 +116,17 @@
> {
> recentcount_ok = 1;
> recentcount = atoi(buf+2);
> + /*
> + * Kluge to handle IDLE simulation. If we are in STAGE_IDLE and
> + * we are simulating (has_idle unset) we need to alert calling
> + * imap_idle() to the fact that we have received a new "recent"
> + * alert in order to break the imap_idle() out of its wait.
> + */
> + if (!has_idle && stage == STAGE_IDLE)
> + {
> + mytimeout = saved_timeout;
> + stage = STAGE_FETCH;
> + }
> }
> else if (strstr(buf, " EXPUNGE"))
> {
This looks acceptable.
How can a server mark a new message "RECENT" without also sending an
"EXISTS"? I'm inclined to consider such behavior broken. I'm willing to
apply this nonetheless as I don't think it would hurt anyone.
--
Matthias Andree
|