aboutsummaryrefslogtreecommitdiffstats
path: root/imap.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap.c')
-rw-r--r--imap.c322
1 files changed, 200 insertions, 122 deletions
diff --git a/imap.c b/imap.c
index 5f65ee47..54255c68 100644
--- a/imap.c
+++ b/imap.c
@@ -35,9 +35,22 @@ 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 flag has_idle = FALSE;
static int expunge_period = 1;
+static void clear_sessiondata(void) {
+ /* must match defaults above */
+ preauth = FALSE;
+ memset(capabilities, 0, sizeof(capabilities));
+ imap_version = IMAP4;
+ has_idle = FALSE;
+ expunge_period = 1;
+}
+
+/* the next ones need to be kept in synch - C89 does not consider strlen()
+ * a const initializer */
+const char *const capa_begin = " [CAPABILITY "; const unsigned capa_len = 13;
+
/* mailbox variables initialized in imap_getrange() */
static int count = 0, oldcount = 0, recentcount = 0, unseen = 0, deletions = 0;
static unsigned int startcount = 1;
@@ -51,6 +64,56 @@ static int actual_deletions = 0;
static int saved_timeout = 0, idle_timeout = 0;
static time_t idle_start_time = 0;
+static int imap_setup(struct query *ctl)
+{
+ (void)ctl;
+ clear_sessiondata();
+ return PS_SUCCESS;
+}
+
+static int imap_cleanup(struct query *ctl)
+{
+ (void)ctl;
+ clear_sessiondata();
+ return PS_SUCCESS;
+}
+
+static void copy_capabilities(const char *buf)
+{
+ strlcpy(capabilities, buf, sizeof(capabilities));
+ capabilities[strcspn(capabilities, "]")] = '\0'; /* truncate at ] */
+
+ /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we
+ should expect a response in mixed-case */
+ if (strstr(capabilities, "IMAP4REV1")) {
+ imap_version = IMAP4rev1; /* RFC-3501 (2060) */
+ if (outlevel >= O_DEBUG)
+ report(stdout, GT_("Protocol identified as IMAP4 rev 1\n"));
+ } else if (strstr(capabilities, "IMAP4")) {
+ imap_version = IMAP4; /* RFC-1730 */
+ if (outlevel >= O_DEBUG)
+ report(stdout, GT_("Protocol identified as IMAP4 rev 0\n"));
+ } else {
+ imap_version = IMAP2;
+ if (outlevel >= O_DEBUG)
+ report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n"));
+ }
+
+ /*
+ * Handle idling. We depend on coming through here on startup
+ * and after each timeout (including timeouts during idles).
+ */
+ if (strstr(capabilities, "IDLE"))
+ has_idle = TRUE;
+ else
+ has_idle = FALSE;
+ if (outlevel >= O_VERBOSE)
+ report(stdout, GT_("will idle after poll\n")); /* FIXME: rename this to can... idle for next release */
+
+ peek_capable = (imap_version >= IMAP4);
+}
+
+
static int imap_untagged_response(int sock, const char *buf)
/* interpret untagged status responses */
{
@@ -59,7 +122,7 @@ static int imap_untagged_response(int sock, const char *buf)
if (stage == STAGE_GETAUTH
&& !strncmp(buf, "* CAPABILITY", 12))
{
- strlcpy(capabilities, buf + 12, sizeof(capabilities));
+ copy_capabilities(buf + 12);
}
else if (stage == STAGE_GETAUTH
&& !strncmp(buf, "* PREAUTH", 9))
@@ -190,6 +253,7 @@ static int imap_response(int sock, char *argbuf, struct RecvSplit *rs)
/* parse command response */
{
char buf[MSGBUFSIZE+1];
+ char *tmp;
do {
int ok;
@@ -207,6 +271,9 @@ static int imap_response(int sock, char *argbuf, struct RecvSplit *rs)
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
+ /* FIXME: does not look for or handle command continuation requests,
+ * i. e. the token "+" instead of tag or "*" */
+
/* untagged responses start with "* " */
if (buf[0] == '*' && buf[1] == ' ') {
ok = imap_untagged_response(sock, buf);
@@ -226,6 +293,19 @@ static int imap_response(int sock, char *argbuf, struct RecvSplit *rs)
return(ok);
}
+ /* on login, the server may volunteer new CAPABILITY information
+ * with the tagged OK response, record it */
+ /* WARNING: this must match with what's in imap_getauth()! */
+ if (stage == STAGE_GETAUTH
+ && (tmp = strstr(buf, capa_begin)))
+ {
+ copy_capabilities(tmp + capa_len);
+
+ if (outlevel >= O_DEBUG) {
+ report(stdout, GT_("found updated capabilities list\n"));
+ }
+ }
+
if (stage == STAGE_IDLE)
{
/* reduce the timeout: servers may not reset their timeout
@@ -334,88 +414,55 @@ static void imap_canonicalize(char *result, char *raw, size_t maxlen)
static int capa_probe(int sock, struct query *ctl)
/* set capability variables from a CAPA probe */
{
- int ok;
+ int err;
- /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
- capabilities[0] = '\0';
- if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
- {
- char *cp;
-
- /* capability checks are supposed to be caseblind */
- for (cp = capabilities; *cp; 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 */
- if (strstr(capabilities, "IMAP4REV1"))
- {
- imap_version = IMAP4rev1;
- if (outlevel >= O_DEBUG)
- report(stdout, GT_("Protocol identified as IMAP4 rev 1\n"));
- }
- else
- {
- imap_version = IMAP4;
- if (outlevel >= O_DEBUG)
- 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;
+ (void)ctl;
- /*
- * 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)
- {
- if (strstr(capabilities, "IDLE"))
- has_idle = TRUE;
- else
- has_idle = FALSE;
- if (outlevel >= O_VERBOSE)
- report(stdout, GT_("will idle after poll\n"));
+ /* probe to see if we're running IMAP4 and can use RFC822.PEEK */
+ memset(capabilities, 0, sizeof capabilities);
+ err = gen_transact(sock, "CAPABILITY");
+ /* if successful, copy_capabilities() will have handled it */
+ if (err == PS_ERROR) {
+ /* this is OK for IMAP2 which did not support a CAPABILITY command */
+ err = PS_SUCCESS;
}
- peek_capable = (imap_version >= IMAP4);
-
- return PS_SUCCESS;
+ return err;
}
-static int do_authcert (int sock, const char *command, const char *name)
+static int do_auth_external (int sock, const char *command, const char *name)
/* do authentication "external" (authentication provided by client cert) */
{
+ /* FIXME: not compliant with RFC 4422 (SASL) without RFC 4959 (SASL-IR)-
+ * does not support the usual server challenge/response
+ */
char buf[256];
+ if (!strstr(capabilities, "SASL-IR")) {
+ report(stderr, GT_("server did not advertise SASL-IR extension but fetchmail's implementation requires it for AUTHENTICATE EXTERNAL\n"));
+ return PS_AUTHFAIL;
+ }
if (name && name[0])
{
- size_t len = strlen(name);
- if ((len / 3) + ((len % 3) ? 4 : 0) < sizeof(buf))
+ size_t len = strlen(name);
+ if (len64frombits(len) + 1 <= sizeof(buf)) /* +1: need to fit \0 byte */
to64frombits (buf, name, strlen(name), sizeof buf);
else
return PS_AUTHFAIL; /* buffer too small. */
}
else
- buf[0]=0;
+ {
+ strcpy(buf, "=");
+ }
return gen_transact(sock, "%s EXTERNAL %s",command,buf);
}
+/** apply for connection authorization, possibly executing STARTTLS */
static int imap_getauth(int sock, struct query *ctl, char *greeting)
-/* apply for connection authorization */
{
int ok = 0;
char *commonname;
- (void)greeting;
-
/*
* Assumption: expunges are cheap, so we want to do them
* after every message unless user said otherwise.
@@ -425,17 +472,12 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
else
expunge_period = 1;
- if ((ok = capa_probe(sock, ctl)))
- return ok;
-
- /*
- * If either (a) we saw a PREAUTH token in the greeting, or
- * (b) the user specified ssh preauthentication, then we're done.
- */
- if (preauth || ctl->server.authenticate == A_SSH)
- {
- preauth = FALSE; /* reset for the next session */
- return(PS_SUCCESS);
+ /* check if imap_ok() has already parsed CAPABILITY from the greeting when
+ * driver.c ran it on the server's greeting message - note this must match
+ * with what's in imap_response()! */
+ if (!strstr(greeting, capa_begin)) {
+ int err = capa_probe(sock, ctl);
+ if (err) return err;
}
commonname = ctl->server.pollname;
@@ -445,6 +487,19 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
commonname = ctl->sslcommonname;
#ifdef SSL_ENABLE
+ /* Defend against a PREAUTH-prevents-STARTTLS attack */
+ if (preauth && must_starttls(ctl)) {
+ if (ctl->server.plugin && A_SSH == ctl->server.authenticate) {
+ report(stderr, GT_("%s: configuration requires TLS, but STARTTLS is not permitted "
+ "because of authenticated state (PREAUTH). Aborting connection. If your plugin is secure, you can defeat STARTTLS with --sslproto '' (see manual).\n"), commonname);
+ } else {
+ report(stderr, GT_("%s: configuration requires TLS, but STARTTLS is not permitted "
+ "because of authenticated state (PREAUTH). Aborting connection. Server permitting, try --ssl instead (see manual).\n"), commonname);
+ }
+ preauth = FALSE; /* reset for the next session */
+ return PS_SOCKET;
+ }
+
if (maybe_starttls(ctl)) {
if ((strstr(capabilities, "STARTTLS") && maybe_starttls(ctl))
|| must_starttls(ctl)) /* if TLS is mandatory, ignore capabilities */
@@ -456,6 +511,11 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
ctl->sslcertfile, ctl->sslcertpath, ctl->sslfingerprint, commonname,
ctl->server.pollname, &ctl->remotename)) != -1)
{
+ if (outlevel >= O_VERBOSE)
+ {
+ report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname);
+ }
+
/*
* RFC 2595 says this:
*
@@ -469,11 +529,10 @@ 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.
*/
+ clear_sessiondata();
if ((ok = capa_probe(sock, ctl)))
- return ok;
- if (outlevel >= O_VERBOSE)
{
- report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname);
+ return ok;
}
} else if (must_starttls(ctl)) {
/* Config required TLS but we couldn't guarantee it, so we must
@@ -504,11 +563,24 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
}
#endif /* SSL_ENABLE */
+ /*
+ * If either (a) we saw a PREAUTH token in the greeting, or
+ * (b) the user specified ssh preauthentication, then we're done.
+ */
+ if (preauth || ctl->server.authenticate == A_SSH)
+ {
+ preauth = FALSE; /* reset for the next session */
+ return(PS_SUCCESS);
+ }
+
/*
* Time to authenticate the user.
* Try the protocol variants that don't require passwords first.
*/
- ok = PS_AUTHFAIL;
+ ok = PS_AUTHFAIL; /* formally, never read,
+ but let's leave this in place as a safe default
+ for future maintenance */
+ (void)ok;
/* Yahoo hack - we'll just try ID if it was offered by the server,
* and IGNORE errors. */
@@ -519,19 +591,23 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
}
}
- if ((ctl->server.authenticate == A_ANY
+ if (ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_EXTERNAL)
- && strstr(capabilities, "AUTH=EXTERNAL"))
{
- ok = do_authcert(sock, "AUTHENTICATE", ctl->remotename);
- if (ok)
- {
- /* SASL cancellation of authentication */
- gen_send(sock, "*");
- if (ctl->server.authenticate != A_ANY)
- return ok;
- } else {
- return ok;
+ if (!strstr(capabilities, "AUTH=EXTERNAL")) {
+ if (ctl->server.authenticate == A_EXTERNAL) {
+ report(stderr, GT_("%s: --auth external requested but server does not advertise it.\n"), commonname);
+ return PS_AUTHFAIL;
+ }
+ } else {
+ int err = do_auth_external(sock, "AUTHENTICATE", ctl->remotename);
+ if (err)
+ {
+ if (ctl->server.authenticate != A_ANY)
+ return err;
+ } else {
+ return PS_SUCCESS;
+ }
}
}
@@ -630,44 +706,44 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
/*
* We're stuck with sending the password en clair.
- * The reason for this odd-looking logic is that some
- * servers return LOGINDISABLED even though login
- * actually works. So arrange things in such a way that
- * setting auth passwd makes it ignore this capability.
- */
- if((ctl->server.authenticate==A_ANY&&!strstr(capabilities,"LOGINDISABLED"))
- || ctl->server.authenticate == A_PASSWORD)
+ * Older fetchmail versions permitted overriding LOGINDISABLED, documenting
+ * that it still works on some servers, but 6.4.22 disables this. */
+ if (ctl->server.authenticate == A_ANY
+ || ctl->server.authenticate == A_PASSWORD)
{
- /* these sizes guarantee no buffer overflow */
- static char *remotename, *password; /* XXX FIXME: not thread-safe but dynamic buffer is leaky on timeout */
- size_t rnl, pwl;
- rnl = 2 * strlen(ctl->remotename) + 1;
- pwl = 2 * strlen(ctl->password) + 1;
- if (remotename) xfree(remotename);
- remotename = (char *)xmalloc(rnl);
- if (password) xfree(password);
- password = (char *)xmalloc(pwl);
-
- imap_canonicalize(remotename, ctl->remotename, rnl);
- imap_canonicalize(password, ctl->password, pwl);
-
- snprintf(shroud, sizeof (shroud), "\"%s\"", password);
- ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
- memset(shroud, 0x55, sizeof(shroud));
- shroud[0] = '\0';
- memset(password, 0x55, strlen(password));
- xfree(password);
- xfree(remotename);
- if (ok)
- {
- if(ctl->server.authenticate != A_ANY)
- return ok;
+ if (strstr(capabilities, "LOGINDISABLED")) {
+ if (ctl->server.authenticate == A_PASSWORD) {
+ report(stderr, GT_("%s: --auth password requested but server forbids it (LOGINDISABLED).\n"), commonname);
+ return PS_AUTHFAIL;
+ }
+ } else {
+ /* these sizes guarantee no buffer overflow */
+ static char *remotename, *password; /* XXX FIXME: not thread-safe but dynamic buffer is leaky on timeout */
+ size_t rnl, pwl;
+ rnl = 2 * strlen(ctl->remotename) + 1;
+ pwl = 2 * strlen(ctl->password) + 1;
+ if (remotename) xfree(remotename);
+ remotename = (char *)xmalloc(rnl);
+ if (password) xfree(password);
+ password = (char *)xmalloc(pwl);
+
+ imap_canonicalize(remotename, ctl->remotename, rnl);
+ imap_canonicalize(password, ctl->password, pwl);
+
+ snprintf(shroud, sizeof (shroud), "\"%s\"", password);
+ ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
+ memset(shroud, 0x55, sizeof(shroud));
+ shroud[0] = '\0';
+ memset(password, 0x55, strlen(password));
+ xfree(password);
+ xfree(remotename);
+ return ok; /* this assumes that password is the last authentication method to try */
}
- else
- return(ok);
}
- return(ok);
+ /* if we're here, we've run out of authentication methods */
+ report(stderr, GT_("%s: we've run out of authentication methods and cannot log in.\n"), commonname);
+ return PS_AUTHFAIL;
}
static int internal_expunge(int sock)
@@ -717,7 +793,7 @@ static int imap_idle(int sock)
/* special timeout to terminate the IDLE and re-issue it
* at least every 28 minutes:
* (the server may have an inactivity timeout) */
- mytimeout = idle_timeout = 1680; /* 28 min */
+ mytimeout = idle_timeout = 600;
time(&idle_start_time);
stage = STAGE_IDLE;
/* enter IDLE mode */
@@ -897,7 +973,7 @@ static int imap_getrange(int sock,
*
* this is a while loop because imap_idle() might return on other
* mailbox changes also */
- while (recentcount == 0 && do_idle) {
+ while (recentcount == 0 && ctl->idle && has_idle) {
smtp_close(ctl, 1);
ok = imap_idle(sock);
if (ok)
@@ -954,7 +1030,7 @@ static int imap_getrange(int sock,
count), count);
}
- if (count == 0 && do_idle)
+ if (count == 0 && ctl->idle && has_idle)
{
/* no messages? then we may need to idle until we get some */
while (count == 0) {
@@ -1404,6 +1480,8 @@ static const struct method imap =
imap_end_mailbox_poll, /* end-of-mailbox processing */
imap_logout, /* expunge and exit */
TRUE, /* yes, we can re-poll */
+ imap_setup, /* setup method */
+ imap_cleanup /* cleanup method */
};
int doIMAP(struct query *ctl)