#include "config.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#if defined(STDC_HEADERS)
#include <stdlib.h>
#include <limits.h>
#endif
#include "fetchmail.h"
#include "socket.h"
#include "i18n.h"
#if OPIE_ENABLE
#endif
#ifndef strstr
extern char *strstr();
#endif
#define IMAP2 -1
#define IMAP4 0
#define IMAP4rev1 1
static int count = 0, recentcount = 0, unseen = 0, deletions = 0;
static unsigned int startcount = 1;
static int expunged, expunge_period, saved_timeout = 0;
static int imap_version, preauth;
static flag do_idle;
static char capabilities[MSGBUFSIZE+1];
static unsigned int *unseen_messages;
static int imap_ok(int sock, char *argbuf)
{
char buf[MSGBUFSIZE+1];
do {
int ok;
char *cp;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
for (cp = buf; *cp; cp++)
if (islower(*cp))
*cp = toupper(*cp);
if (strstr(buf, "* CAPABILITY"))
strncpy(capabilities, buf + 12, sizeof(capabilities));
else if (strstr(buf, "EXISTS"))
{
count = atoi(buf+2);
if (count > INT_MAX/sizeof(int))
{
report(stderr, "bogus message count!");
return(PS_PROTOCOL);
}
if (stage == STAGE_IDLE)
{
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 = atoi(buf+2);
}
else if (strstr(buf, "PREAUTH"))
preauth = TRUE;
else if (!check_only && strstr(buf, "[READ-ONLY]"))
return(PS_LOCKBUSY);
} while
(tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
if (tag[0] == '\0')
{
if (argbuf)
strcpy(argbuf, buf);
return(PS_SUCCESS);
}
else
{
char *cp;
for (cp = buf; !isspace(*cp); cp++)
continue;
while (isspace(*cp))
cp++;
if (strncmp(cp, "OK", 2) == 0)
{
if (argbuf)
strcpy(argbuf, cp);
return(PS_SUCCESS);
}
else if (strncmp(cp, "BAD", 3) == 0)
return(PS_ERROR);
else if (strncmp(cp, "NO", 2) == 0)
{
if (stage == STAGE_GETAUTH)
return(PS_AUTHFAIL);
else
return(PS_ERROR);
}
else
return(PS_PROTOCOL);
}
}
#if NTLM_ENABLE
#include "ntlm.h"
static tSmbNtlmAuthRequest request;
static tSmbNtlmAuthChallenge challenge;
static tSmbNtlmAuthResponse response;
static int do_imap_ntlm(int sock, struct query *ctl)
{
char msgbuf[2048];
int result,len;
gen_send(sock, "AUTHENTICATE NTLM");
if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
return result;
if (msgbuf[0] != '+')
return PS_AUTHFAIL;
buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
if (outlevel >= O_DEBUG)
dumpSmbNtlmAuthRequest(stdout, &request);
memset(msgbuf,0,sizeof msgbuf);
to64frombits (msgbuf, (unsigned char*)&request, SmbLength(&request));
if (outlevel >= O_MONITOR)
report(stdout, "IMAP> %s\n", msgbuf);
strcat(msgbuf,"\r\n");
SockWrite (sock, msgbuf, strlen (msgbuf));
if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
return result;
len = from64tobits ((char*)&challenge, msgbuf, sizeof(challenge));
if (outlevel >= O_DEBUG)
dumpSmbNtlmAuthChallenge(stdout, &challenge);
buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
if (outlevel >= O_DEBUG)
dumpSmbNtlmAuthResponse(stdout, &response);
memset(msgbuf,0,sizeof msgbuf);
to64frombits (msgbuf, (unsigned char*)&response, SmbLength(&response));
if (outlevel >= O_MONITOR)
report(stdout, "IMAP> %s\n", msgbuf);
strcat(msgbuf,"\r\n");
SockWrite (sock, msgbuf, strlen (msgbuf));
if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
return result;
if (strstr (msgbuf, "OK"))
return PS_SUCCESS;
else
return PS_AUTHFAIL;
}
#endif
static int imap_canonicalize(char *result, char *raw, int maxlen)
{
int i, j;
j = 0;
for (i = 0; i < strlen(raw) && i < maxlen; i++)
{
if ((raw[i] == '\\') || (raw[i] == '"'))
result[j++] = '\\';
result[j++] = raw[i];
}
result[j] = '\0';
return(i);
}
static int imap_getauth(int sock, struct query *ctl, char *greeting)
{
int ok = 0;
capabilities[0] = '\0';
if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS)
{
char *cp;
for (cp = capabilities; *cp; cp++)
*cp = toupper(*cp);
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);
peek_capable = (imap_version >= IMAP4);
if (NUM_SPECIFIED(ctl->expunge))
expunge_period = NUM_VALUE_OUT(ctl->expunge);
else
expunge_period = 1;
if (strstr(capabilities, "IDLE") && ctl->idle)
{
do_idle = TRUE;
if (outlevel >= O_VERBOSE)
report(stdout, GT_("will idle after poll\n"));
}
if (preauth || ctl->server.authenticate == A_SSH)
{
preauth = FALSE;
return(PS_SUCCESS);
}
ok = PS_AUTHFAIL;
#ifdef GSSAPI
if ((ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_GSSAPI)
&& strstr(capabilities, "AUTH=GSSAPI"))
if(ok = do_gssauth(sock, "AUTHENTICATE", ctl->server.truename, ctl->remotename))
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return ok;
#endif
#ifdef KERBEROS_V4
if ((ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_KERBEROS_V4
|| ctl->server.authenticate == A_KERBEROS_V5)
&& strstr(capabilities, "AUTH=KERBEROS_V4"))
{
if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename)))
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return ok;
}
#endif
#ifdef SSL_ENABLE
if ((ctl->server.authenticate == A_ANY)
&& !ctl->use_ssl
&& strstr(capabilities, "STARTTLS"))
{
char *realhost;
realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
gen_transact(sock, "STARTTLS");
if (SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1)
{
report(stderr,
GT_("SSL connection failed.\n"));
return(PS_AUTHFAIL);
}
}
#endif
if ((ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_CRAM_MD5)
&& strstr(capabilities, "AUTH=CRAM-MD5"))
{
if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL)))
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return ok;
}
#if OPIE_ENABLE
if ((ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_OTP)
&& strstr(capabilities, "AUTH=X-OTP"))
if ((ok = do_otp(sock, "AUTHENTICATE", ctl)))
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return ok;
#else
if (ctl->server.authenticate == A_OTP)
{
report(stderr,
GT_("Required OTP capability not compiled into fetchmail\n"));
}
#endif
#ifdef NTLM_ENABLE
if ((ctl->server.authenticate == A_ANY
|| ctl->server.authenticate == A_NTLM)
&& strstr (capabilities, "AUTH=NTLM")) {
if ((ok = do_imap_ntlm(sock, ctl)))
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return(ok);
}
#else
if (ctl->server.authenticate == A_NTLM)
{
report(stderr,
GT_("Required NTLM capability not compiled into fetchmail\n"));
}
#endif
#ifdef __UNUSED__
if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN")))
{
report(stderr,
GT_("Required LOGIN capability not supported by server\n"));
}
#endif
if((ctl->server.authenticate==A_ANY&&!strstr(capabilities,"LOGINDISABLED"))
|| ctl->server.authenticate == A_PASSWORD)
{
char remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1];
imap_canonicalize(remotename, ctl->remotename, NAMELEN);
imap_canonicalize(password, ctl->password, PASSWORDLEN);
strcpy(shroud, password);
ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password);
shroud[0] = '\0';
if (ok)
{
gen_send(sock, "*");
if(ctl->server.authenticate != A_ANY)
return ok;
}
else
return(ok);
}
return(ok);
}
static int internal_expunge(int sock)
{
int ok;
if ((ok = gen_transact(sock, "EXPUNGE")))
return(ok);
expunged += deletions;
deletions = 0;
#ifdef IMAP_UID
expunge_uids(ctl);
#endif
return(PS_SUCCESS);
}
static int imap_idle(int sock)
{
stage = STAGE_IDLE;
saved_timeout = mytimeout;
mytimeout = 0;
return (gen_transact(sock, "IDLE"));
}
static int imap_getrange(int sock,
struct query *ctl,
const char *folder,
int *countp, int *newp, int *bytes)
{
int ok;
char buf[MSGBUFSIZE+1], *cp;
*bytes = -1;
if (pass > 1)
{
ok = 0;
if (deletions) {
ok = internal_expunge(sock);
if (ok)
{
report(stderr, GT_("expunge failed\n"));
return(ok);
}
}
while (recentcount == 0 && do_idle) {
smtp_close(ctl, 1);
ok = imap_idle(sock);
if (ok)
{
report(stderr, GT_("re-poll failed\n"));
return(ok);
}
}
if (recentcount == 0)
count = 0;
if (outlevel >= O_DEBUG)
report(stdout, GT_("%d messages waiting after re-poll\n"), count);
}
else
{
count = 0;
ok = gen_transact(sock,
check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"",
folder ? folder : "INBOX");
if (ok != 0)
{
report(stderr, GT_("mailbox selection failed\n"));
return(ok);
}
else if (outlevel >= O_DEBUG)
report(stdout, GT_("%d messages waiting after first poll\n"), count);
while (count == 0 && do_idle) {
ok = imap_idle(sock);
if (ok)
{
report(stderr, GT_("re-poll failed\n"));
return(ok);
}
}
if (!check_only && !ctl->keep && count > 0)
{
ok = internal_expunge(sock);
if (ok)
{
report(stderr, GT_("expunge failed\n"));
return(ok);
}
if (outlevel >= O_DEBUG)
report(stdout, GT_("%d messages waiting after expunge\n"), count);
}
}
*countp = count;
recentcount = 0;
startcount = 1;
if (!ctl->fetchall && count > 0)
{
if (unseen_messages)
free(unseen_messages);
unseen_messages = xmalloc(count * sizeof(unsigned int));
memset(unseen_messages, 0, count * sizeof(unsigned int));
unseen = 0;
gen_send(sock, "SEARCH UNSEEN");
do {
ok = gen_recv(sock, buf, sizeof(buf));
if (ok != 0)
{
report(stderr, GT_("search for unseen messages failed\n"));
return(PS_PROTOCOL);
}
else if ((cp = strstr(buf, "* SEARCH")))
{
char *ep;
cp += 8;
startcount = count + 1;
while (*cp && unseen < count)
{
while (*cp && isspace(*cp))
cp++;
if (*cp)
{
unsigned int um;
um=(unsigned int)strtol(cp,&ep,10);
if (um <= count)
{
unseen_messages[unseen++] = um;
if (outlevel >= O_DEBUG)
report(stdout, GT_("%u is unseen\n"), um);
if (startcount > um)
startcount = um;
}
cp = ep;
}
}
}
} while
(tag[0] != '\0' && strncmp(buf, tag, strlen(tag)));
if (outlevel >= O_DEBUG && unseen > 0)
report(stdout, GT_("%u is first unseen\n"), startcount);
} else
unseen = -1;
*newp = unseen;
count = 0;
expunged = 0;
deletions = 0;
return(PS_SUCCESS);
}
static int imap_getsizes(int sock, int count, int *sizes)
{
char buf [MSGBUFSIZE+1];
if (count == startcount)
gen_send(sock, "FETCH %d RFC822.SIZE", count);
else if (count > startcount)
gen_send(sock, "FETCH %d:%d RFC822.SIZE", startcount, count);
else
return(PS_SUCCESS);
for (;;)
{
unsigned int num, size;
int ok;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
else 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) {
if (num > 0 && num <= count)
sizes[num - 1] = size;
else
report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n");
}
}
return(PS_SUCCESS);
}
static int imap_is_old(int sock, struct query *ctl, int number)
{
flag seen = TRUE;
int i;
seen = TRUE;
for (i = 0; i < unseen; i++)
if (unseen_messages[i] == number)
{
seen = FALSE;
break;
}
return(seen);
}
static char *skip_token(char *ptr)
{
while(isspace(*ptr)) ptr++;
while(!isspace(*ptr) && !iscntrl(*ptr)) ptr++;
while(isspace(*ptr)) ptr++;
return(ptr);
}
static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp)
{
char buf [MSGBUFSIZE+1];
int num;
number -= expunged;
gen_send(sock, "FETCH %d RFC822.HEADER", number);
for (;;)
{
int ok;
char *ptr;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
ptr = skip_token(buf);
if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2)
break;
else if (!strncmp(ptr, "NO", 2))
{
if (strstr (buf, "* NO"))
imap_ok (sock, 0);
return(PS_TRANSIENT);
}
else if (!strncmp(ptr, "BAD", 3))
{
if (strstr (buf, "* BAD"))
imap_ok (sock, 0);
return(PS_TRANSIENT);
}
}
if (num != number)
return(PS_ERROR);
else
return(PS_SUCCESS);
}
static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp)
{
char buf [MSGBUFSIZE+1], *cp;
int num;
number -= expunged;
switch (imap_version)
{
case IMAP4rev1:
if (!ctl->keep)
gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number);
else
gen_send(sock, "FETCH %d BODY[TEXT]", number);
break;
case IMAP4:
if (!ctl->keep)
gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number);
else
gen_send(sock, "FETCH %d RFC822.TEXT", number);
break;
default:
gen_send(sock, "FETCH %d RFC822.TEXT", number);
break;
}
do {
int ok;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
} while
(!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1);
if (num != number)
return(PS_ERROR);
if ((cp = strchr(buf, '{')))
*lenp = atoi(cp + 1);
else
*lenp = -1;
return(PS_SUCCESS);
}
static int imap_trail(int sock, struct query *ctl, int number)
{
for (;;)
{
char buf[MSGBUFSIZE+1];
int ok;
if ((ok = gen_recv(sock, buf, sizeof(buf))))
return(ok);
if (strstr(buf, "OK"))
break;
#ifdef __UNUSED__
if (ctl->keep)
if ((ok = gen_transact(sock,
imap_version == IMAP4
? "STORE %d +FLAGS.SILENT (\\Seen)"
: "STORE %d +FLAGS (\\Seen)",
number)))
return(ok);
#endif
}
return(PS_SUCCESS);
}
static int imap_delete(int sock, struct query *ctl, int number)
{
int ok;
number -= expunged;
if ((ok = gen_transact(sock,
imap_version == IMAP4
? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)"
: "STORE %d +FLAGS (\\Seen \\Deleted)",
number)))
return(ok);
else
deletions++;
if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0)
internal_expunge(sock);
return(PS_SUCCESS);
}
static int imap_logout(int sock, struct query *ctl)
{
if (deletions)
internal_expunge(sock);
#ifdef USE_SEARCH
if (unseen_messages)
free(unseen_messages);
#endif
return(gen_transact(sock, "LOGOUT"));
}
const static struct method imap =
{
"IMAP",
#if INET6_ENABLE
"imap",
"imaps",
#else
143,
993,
#endif
TRUE,
FALSE,
imap_ok,
imap_getauth,
imap_getrange,
imap_getsizes,
imap_is_old,
imap_fetch_headers,
imap_fetch_body,
imap_trail,
imap_delete,
imap_logout,
TRUE,
};
int doIMAP(struct query *ctl)
{
return(do_protocol(ctl, &imap));
}