#define IMAP4 0 /* IMAP4 rev 0, RFC1730 */
#define IMAP4rev1 1 /* IMAP4 rev 1, RFC2060 */
+/* global variables: please reinitialize them explicitly for proper
+ * working in daemon mode */
+
+/* TODO: session variables to be initialized before server greeting */
+static int preauth = FALSE;
+
+/* session variables initialized in capa_probe() or imap_getauth() */
+static char capabilities[MSGBUFSIZE+1];
+static int imap_version = IMAP4;
+static flag do_idle = FALSE, has_idle = FALSE;
+static int expunge_period = 1;
+
+/* mailbox variables initialized in imap_getrange() */
static int count = 0, recentcount = 0, unseen = 0, deletions = 0;
-static int recentcount_ok = 0;
static unsigned int startcount = 1;
-static int expunged, expunge_period, saved_timeout = 0;
-static int imap_version, preauth;
-static flag do_idle, has_idle;
-static char capabilities[MSGBUFSIZE+1];
+static int expunged = 0;
static unsigned int *unseen_messages;
+/* for "IMAP> EXPUNGE" */
+static int recentcount_ok = 0;
+
+/* for "IMAP> IDLE" */
+static int saved_timeout = 0;
+
static int imap_ok(int sock, char *argbuf)
/* parse command response */
{
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
- /* interpret untagged status responses */
- if (strstr(buf, "* CAPABILITY"))
- {
- strlcpy(capabilities, buf + 12, sizeof(capabilities));
- }
- else if (strstr(buf, "EXISTS"))
- {
- count = atoi(buf+2);
- /*
- * Don't trust the message count passed by the server.
- * Without this check, it might be possible to do a
- * DNS-spoofing attack that would pass back a ridiculous
- * count, and allocate a malloc area that would overlap
- * a portion of the stack.
- */
- if (count > INT_MAX/sizeof(int))
- {
- report(stderr, GT_("bogus message count!"));
- return(PS_PROTOCOL);
+ /* interpret untagged status responses
+ * First check if we really have an untagged response, starting
+ * with "*" SPACE. Then, for each individual check, use a BLANK
+ * before the word to avoid confusion with the \Recent flag or
+ * similar */
+ if (buf[0] == '*' && buf[1] == ' ') {
+ if (strstr(buf, " CAPABILITY")) {
+ strlcpy(capabilities, buf + 12, sizeof(capabilities));
}
-
- /*
- * Nasty kluge to handle RFC2177 IDLE. If we know we're idling
- * we can't wait for the tag matching the IDLE; we have to tell the
- * server the IDLE is finished by shipping back a DONE when we
- * see an EXISTS. Only after that will a tagged response be
- * shipped. The idling flag also gets cleared on a timeout.
- */
- if (stage == STAGE_IDLE)
+ else if (strstr(buf, " EXISTS"))
{
- /* If IDLE isn't supported, we were only sending NOOPs anyway. */
- if (has_idle)
+ count = atoi(buf+2);
+ /*
+ * Don't trust the message count passed by the server.
+ * Without this check, it might be possible to do a
+ * DNS-spoofing attack that would pass back a ridiculous
+ * count, and allocate a malloc area that would overlap
+ * a portion of the stack.
+ */
+ if (count > INT_MAX/sizeof(int))
{
- /* we do our own write and report here to disable tagging */
- SockWrite(sock, "DONE\r\n", 6);
- if (outlevel >= O_MONITOR)
- report(stdout, "IMAP> DONE\n");
+ report(stderr, GT_("bogus message count!"));
+ return(PS_PROTOCOL);
}
- mytimeout = saved_timeout;
- stage = STAGE_FETCH;
+ /*
+ * Nasty kluge to handle RFC2177 IDLE. If we know we're idling
+ * we can't wait for the tag matching the IDLE; we have to tell the
+ * server the IDLE is finished by shipping back a DONE when we
+ * see an EXISTS. Only after that will a tagged response be
+ * shipped. The idling flag also gets cleared on a timeout.
+ */
+ if (stage == STAGE_IDLE)
+ {
+ /* If IDLE isn't supported, we were only sending NOOPs anyway. */
+ if (has_idle)
+ {
+ /* we do our own write and report here to disable tagging */
+ SockWrite(sock, "DONE\r\n", 6);
+ if (outlevel >= O_MONITOR)
+ report(stdout, "IMAP> DONE\n");
+ }
+
+ mytimeout = saved_timeout;
+ stage = STAGE_FETCH;
+ }
+ }
+ else if (strstr(buf, " RECENT"))
+ {
+ recentcount_ok = 1;
+ recentcount = atoi(buf+2);
+ }
+ else if (strstr(buf, " EXPUNGE"))
+ {
+ /* the response "* 10 EXPUNGE" means that the currently
+ * tenth (i.e. only one) message has been deleted */
+ if (atoi(buf+2) > 0)
+ count--;
+ if (count < 0)
+ count = 0;
+ }
+ 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);
}
}
- /* a space is required to avoid confusion with the \Recent flag */
- else if (strstr(buf, " RECENT"))
- {
- recentcount_ok = 1;
- recentcount = atoi(buf+2);
- }
- else if (strstr(buf, "EXPUNGE") && !strstr(buf, "OK"))
- {
- count -= atoi(buf+2);
- if (count < 0)
- count = 0;
- }
- 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)));
#ifdef NTLM_ENABLE
#include "ntlm.h"
-static tSmbNtlmAuthRequest request;
-static tSmbNtlmAuthChallenge challenge;
-static tSmbNtlmAuthResponse response;
-
/*
* NTLM support by Grant Edwards.
*
static int do_imap_ntlm(int sock, struct query *ctl)
{
+ tSmbNtlmAuthRequest request;
+ tSmbNtlmAuthChallenge challenge;
+ tSmbNtlmAuthResponse response;
+
char msgbuf[2048];
int result,len;
/* capability checks are supposed to be caseblind */
for (cp = capabilities; *cp; cp++)
- *cp = toupper(*cp);
+ *cp = toupper((unsigned char)*cp);
/* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
should expect a response in mixed-case */
* Handle idling. We depend on coming through here on startup
* and after each timeout (including timeouts during idles).
*/
+ do_idle = ctl->idle;
if (ctl->idle)
{
- do_idle = TRUE;
if (strstr(capabilities, "IDLE"))
- {
has_idle = TRUE;
- }
+ else
+ has_idle = FALSE;
if (outlevel >= O_VERBOSE)
report(stdout, GT_("will idle after poll\n"));
}
}
report(stderr,
GT_("SSL connection failed.\n"));
- return(PS_AUTHFAIL);
+ return PS_SOCKET;
}
did_stls = TRUE;
if (pass > 1)
{
- /*
- * We have to have an expunge here, otherwise the re-poll will
- * infinite-loop picking up un-expunged messages -- unless the
- * expunge period is one and we've been nuking each message
- * just after deletion.
- */
- ok = 0;
- if (deletions) {
- ok = internal_expunge(sock);
- if (ok)
- {
- report(stderr, GT_("expunge failed\n"));
- return(ok);
- }
- }
-
- /*
+ /* deleted mails have already been expunged by
+ * end_mailbox_poll().
+ *
* recentcount is already set here by the last imap command which
* returned RECENT on detecting new mail. if recentcount is 0, wait
* for new mail.
- */
-
- /* this is a while loop because imap_idle() might return on other
+ *
+ * this is a while loop because imap_idle() might return on other
* mailbox changes also */
while (recentcount == 0 && do_idle) {
smtp_close(ctl, 1);
ok = gen_transact(sock,
check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
folder ? folder : "INBOX");
+ /* imap_ok returns PS_LOCKBUSY for READ-ONLY folders,
+ * which we can safely use in fetchall keep only */
+ if (ok == PS_LOCKBUSY && ctl->fetchall && ctl-> keep)
+ ok = 0;
+
if (ok != 0)
{
report(stderr, GT_("mailbox selection failed\n"));
unseen = -1;
*newp = unseen;
- count = 0;
expunged = 0;
deletions = 0;
return(ok);
/* we want response matching to be case-insensitive */
for (cp = buf; *cp; cp++)
- *cp = toupper(*cp);
+ *cp = toupper((unsigned char)*cp);
/* an untagged NO means that a message was not readable */
if (strstr(buf, "* NO"))
;
else if (strstr(buf, "OK") || strstr(buf, "NO"))
break;
- else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2)
+ else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2
+ /* some servers (like mail.internode.on.net bld-mail04) return UID information here
+ *
+ * IMAP> A0005 FETCH 1 RFC822.SIZE
+ * IMAP< * 1 FETCH (UID 16 RFC822.SIZE 1447)
+ * IMAP< A0005 OK FETCH completed
+ *
+ */
+ || sscanf(buf, "* %u FETCH (UID %*s RFC822.SIZE %u)", &num, &size) == 2)
{
if (num >= first && num <= last)
- sizes[num - first] = size;
+ sizes[num - first] = size;
else
- report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
+ report(stderr,
+ GT_("Warning: ignoring bogus data for message sizes returned by the server.\n"));
}
}
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
ptr = skip_token(buf); /* either "* " or "AXXXX " */
- if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
- break;
+ if (sscanf(ptr, "%d FETCH (RFC822.HEADER {%d}", &num, lenp) == 2
+ /* some servers (like mail.internode.on.net bld-mail04) return UID information here
+ *
+ * IMAP> A0006 FETCH 1 RFC822.HEADER
+ * IMAP< * 1 FETCH (UID 16 RFC822.HEADER {1360}
+ * ...
+ * IMAP< )
+ * IMAP< A0006 OK FETCH completed
+ *
+ */
+ || sscanf(ptr, "%d FETCH (UID %*s RFC822.HEADER {%d}", &num, lenp) == 2)
+ break;
/* try to recover from chronically fucked-up M$ Exchange servers */
else if (!strncmp(ptr, "NO", 2))
{
if ((cp = strchr(buf, '{'))) {
errno = 0;
*lenp = (int)strtol(cp + 1, (char **)NULL, 10);
- if (errno == ERANGE && (*lenp == LONG_MAX || *lenp == LONG_MIN))
+ if (errno == ERANGE || *lenp < 0)
*lenp = -1; /* length is too big/small for us to handle */
}
else
return(PS_SUCCESS);
}
-static int imap_trail(int sock, struct query *ctl, int number)
+static int imap_trail(int sock, struct query *ctl, int number, const char *tag)
/* discard tail of FETCH response after reading message text */
{
/* expunges change the fetch numbers */
for (;;)
{
- char buf[MSGBUFSIZE+1];
+ char buf[MSGBUFSIZE+1], *t;
int ok;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
/* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */
- if (strstr(buf, "OK"))
- break;
+ if (strncmp(buf, tag, strlen(tag)) == 0) {
+ t = buf + strlen(tag);
+ t += strspn(t, " \t");
+ if (strncmp(t, "OK", 2) == 0)
+ break;
+ }
}
return(PS_SUCCESS);
number));
}
+static int imap_end_mailbox_poll(int sock, struct query *ctl)
+/* cleanup mailbox before we idle or switch to another one */
+{
+ if (deletions)
+ internal_expunge(sock);
+ return(PS_SUCCESS);
+}
+
static int imap_logout(int sock, struct query *ctl)
/* send logout command */
{
imap_trail, /* eat message trailer */
imap_delete, /* delete the message */
imap_mark_seen, /* how to mark a message as seen */
+ imap_end_mailbox_poll, /* end-of-mailbox processing */
imap_logout, /* expunge and exit */
TRUE, /* yes, we can re-poll */
};