X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=pop3.c;h=9eae1441888758c9a2fee28872af495834552e47;hb=01ac1377ee68a19f055ca487022c00114df347da;hp=0fa1abdc4ead4b5b2145b1fdbda8c101b71cbe90;hpb=709fc101997116ea2eec61f8fb459c0964d40bf6;p=~andy%2Ffetchmail diff --git a/pop3.c b/pop3.c index 0fa1abdc..9eae1441 100644 --- a/pop3.c +++ b/pop3.c @@ -1,11 +1,12 @@ /* * pop3.c -- POP3 protocol methods * + * Copyright 1998 by Eric S. Raymond. * For license terms, see the file COPYING in this directory. */ #include "config.h" - +#ifdef POP3_ENABLE #include #include #include @@ -18,20 +19,25 @@ #include "fetchmail.h" #include "socket.h" +#include "i18n.h" -#if HAVE_LIBOPIE -#include -#endif /* HAVE_LIBOPIE */ - -#define PROTOCOL_ERROR {error(0, 0, "protocol error"); return(PS_ERROR);} +#if OPIE_ENABLE +#include +#endif /* OPIE_ENABLE */ +#ifndef strstr /* glibc-2.1 declares this as a macro */ extern char *strstr(); /* needed on sysV68 R3V7.1. */ +#endif /* strstr */ static int last; +#ifdef SDPS_ENABLE +char *sdps_envfrom; +char *sdps_envto; +#endif /* SDPS_ENABLE */ -#if HAVE_LIBOPIE +#if OPIE_ENABLE static char lastok[POPBUFSIZE+1]; -#endif /* HAVE_LIBOPIE */ +#endif /* OPIE_ENABLE */ int pop3_ok (int sock, char *argbuf) /* parse command response */ @@ -56,29 +62,45 @@ int pop3_ok (int sock, char *argbuf) if (strcmp(buf,"+OK") == 0) { -#if HAVE_LIBOPIE +#if OPIE_ENABLE strcpy(lastok, bufp); -#endif /* HAVE_LIBOPIE */ +#endif /* OPIE_ENABLE */ ok = 0; } - else if (strcmp(buf,"-ERR") == 0) + else if (strncmp(buf,"-ERR", 4) == 0) { + if (stage > STAGE_GETAUTH) + ok = PS_PROTOCOL; /* * We're checking for "lock busy", "unable to lock", - * "already locked" etc. here. This indicates that we - * have to wait for the server to clean up before we - * can poll again. + * "already locked", "wait a few minutes" etc. here. + * This indicates that we have to wait for the server to + * unwedge itself before we can poll again. * * PS_LOCKBUSY check empirically verified with two recent - * versions the Berkeley popper; QPOP (version 2.2) and + * versions of the Berkeley popper; QPOP (version 2.2) and * QUALCOMM Pop server derived from UCB (version 2.1.4-R3) + * These are caught by the case-indifferent "lock" check. + * The "wait" catches "mail storage services unavailable, + * wait a few minutes and try again" on the InterMail server. + * + * If these aren't picked up on correctly, fetchmail will + * think there is an authentication failure and wedge the + * connection in order to prevent futile polls. + * + * Gad, what a kluge. */ - if (strstr(bufp,"lock")||strstr(bufp,"Lock")||strstr(bufp,"LOCK")) + else if (strstr(bufp,"lock") + || strstr(bufp,"Lock") + || strstr(bufp,"LOCK") + || strstr(bufp,"wait") + /* these are blessed by RFC 2449 */ + || strstr(bufp,"[IN-USE]")||strstr(bufp,"[LOGIN-DELAY]")) ok = PS_LOCKBUSY; else ok = PS_AUTHFAIL; if (*bufp) - error(0,0,bufp); + report(stderr, "%s\n", bufp); } else ok = PS_PROTOCOL; @@ -96,30 +118,86 @@ int pop3_getauth(int sock, struct query *ctl, char *greeting) int ok; char *start,*end; char *msg; -#if HAVE_LIBOPIE +#if OPIE_ENABLE char *challenge; -#endif /* HAVE_LIBOPIE */ +#endif /* OPIE_ENABLE */ + +#ifdef SDPS_ENABLE + /* + * This needs to catch both demon.co.uk and demon.net. + * If we see either, and we're in multidrop mode, try to use + * the SDPS *ENV extension. + */ + if (!(ctl->server.sdps) && MULTIDROP(ctl) && strstr(greeting, "demon.")) + ctl->server.sdps = TRUE; +#endif /* SDPS_ENABLE */ + + /* + * In theory, we ought to probe with CAPA here (RFC 2449). + * But AFAIK this commpand is not widely implemented, and + * we have our own tests for optional commands, and it seems + * vanishingly unlikely that the RFC 2449 extended responses + * [IN-USE] and [LOGIN-DELAY] will ever be accidentally spoofed. + * So we'll not bother, and save ourselves the overhead. + */ switch (ctl->server.protocol) { case P_POP3: - if ((gen_transact(sock, "USER %s", ctl->remotename)) != 0) - PROTOCOL_ERROR - -#if defined(HAVE_LIBOPIE) && defined(OPIE_ENABLE) - /* see RFC1938: A One-Time Password System */ - if (challenge = strstr(lastok, "otp-")) +#ifdef RPA_ENABLE + /* CompuServe POP3 Servers as of 990730 want AUTH first for RPA */ + if (strstr(ctl->remotename, "@compuserve.com")) { - char response[OPIE_RESPONSE_MAX+1]; + /* AUTH command should return a list of available mechanisms */ + if (gen_transact(sock, "AUTH") == 0) + { + char buffer[10]; + flag has_rpa = FALSE; - if (opiegenerator(challenge, ctl->password, response)) - PROTOCOL_ERROR + while ((ok = gen_recv(sock, buffer, sizeof(buffer))) == 0) + { + if (buffer[0] == '.') + break; + if (strncasecmp(buffer, "rpa", 3) == 0) + has_rpa = TRUE; + } + if (has_rpa && !POP3_auth_rpa(ctl->remotename, + ctl->password, sock)) + return(PS_SUCCESS); + } - ok = gen_transact(sock, "PASS %s", response); + return(PS_AUTHFAIL); } - else -#endif /* defined(HAVE_LIBOPIE) && defined(OPIE_ENABLE) */ - /* ordinary validation, no one-time password */ - ok = gen_transact(sock, "PASS %s", ctl->password); + else /* not a CompuServe account */ +#endif /* RPA_ENABLE */ + ok = gen_transact(sock, "USER %s", ctl->remotename); + +#if OPIE_ENABLE + /* see RFC1938: A One-Time Password System */ + if (challenge = strstr(lastok, "otp-")) { + char response[OPIE_RESPONSE_MAX+1]; + int i; + + i = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? "" : ctl->password, response); + if ((i == -2) && !run.poll_interval) { + char secret[OPIE_SECRET_MAX+1]; + fprintf(stderr, _("Secret pass phrase: ")); + if (opiereadpass(secret, sizeof(secret), 0)) + i = opiegenerator(challenge, secret, response); + memset(secret, 0, sizeof(secret)); + }; + + if (i) { + ok = PS_ERROR; + break; + }; + + ok = gen_transact(sock, "PASS %s", response); + break; + } +#endif /* OPIE_ENABLE */ + + /* ordinary validation, no one-time password or RPA */ + ok = gen_transact(sock, "PASS %s", ctl->password); break; case P_APOP: @@ -128,7 +206,8 @@ int pop3_getauth(int sock, struct query *ctl, char *greeting) for (start = greeting; *start != 0 && *start != '<'; start++) continue; if (*start == 0) { - error(0, -1, "Required APOP timestamp not found in greeting"); + report(stderr, + _("Required APOP timestamp not found in greeting\n")); return(PS_AUTHFAIL); } @@ -136,44 +215,40 @@ int pop3_getauth(int sock, struct query *ctl, char *greeting) for (end = start; *end != 0 && *end != '>'; end++) continue; if (*end == 0 || end == start + 1) { - error(0, -1, "Timestamp syntax error in greeting"); + report(stderr, + _("Timestamp syntax error in greeting\n")); return(PS_AUTHFAIL); } else *++end = '\0'; /* copy timestamp and password into digestion buffer */ - msg = (char *)xmalloc((end-start+1) + strlen(ctl->password) + 1); + xalloca(msg, char *, (end-start+1) + strlen(ctl->password) + 1); strcpy(msg,start); strcat(msg,ctl->password); - strcpy(ctl->digest, MD5Digest(msg)); - free(msg); + strcpy(ctl->digest, MD5Digest((unsigned char *)msg)); ok = gen_transact(sock, "APOP %s %s", ctl->remotename, ctl->digest); break; case P_RPOP: - if ((gen_transact(sock,"USER %s", ctl->remotename)) != 0) - PROTOCOL_ERROR - - ok = gen_transact(sock, "RPOP %s", ctl->password); + if ((ok = gen_transact(sock,"USER %s", ctl->remotename)) == 0) + ok = gen_transact(sock, "RPOP %s", ctl->password); break; default: - error(0, 0, "Undefined protocol request in POP3_auth"); + report(stderr, _("Undefined protocol request in POP3_auth\n")); ok = PS_ERROR; } - /* maybe we detected a lock-busy condition? */ if (ok != 0) { + /* maybe we detected a lock-busy condition? */ if (ok == PS_LOCKBUSY) - { - error(0, 0, "lock busy! Is another session active?"); - return(PS_LOCKBUSY); - } - PROTOCOL_ERROR + report(stderr, _("lock busy! Is another session active?\n")); + + return(ok); } /* @@ -185,8 +260,11 @@ int pop3_getauth(int sock, struct query *ctl, char *greeting) */ sleep(3); /* to be _really_ safe, probably need sleep(5)! */ + /* we're peek-capable if use of TOP is enabled */ + peek_capable = !(ctl->fetchall || ctl->keep); + /* we're approved */ - return(0); + return(PS_SUCCESS); } static int @@ -204,6 +282,8 @@ pop3_gettopid( int sock, int num , char *id) break; if( ! got_it && ! strncasecmp("Message-Id:", buf, 11 )) { got_it = 1; + /* prevent stack overflows */ + buf[IDLEN+12] = 0; sscanf( buf+12, "%s", id); } } @@ -224,7 +304,7 @@ pop3_slowuidl( int sock, struct query *ctl, int *countp, int *newp) * + Otherwise run a binary search to determine the last known message */ int ok, nolinear = 0; - int first_nr, list_len, try_id, try_nr, hop_id, add_id; + int first_nr, list_len, try_id, try_nr, add_id; int num; char id [IDLEN+1]; @@ -245,7 +325,7 @@ pop3_slowuidl( int sock, struct query *ctl, int *countp, int *newp) if( (ok = pop3_gettopid( sock, try_id, id )) != 0 ) return ok; - try_nr = str_nr_in_list(&ctl->oldsaved, id); + try_nr = str_nr_last_in_list(&ctl->oldsaved, id); } else { try_id = *countp+1; try_nr = -1; @@ -274,17 +354,20 @@ pop3_slowuidl( int sock, struct query *ctl, int *countp, int *newp) try_id--; } } else { - error(0,0,"Messages inserted into list on server. " - "Cannot handle this."); + report(stderr, + _("Messages inserted into list on server. Cannot handle this.\n")); return -1; } } } - /* The first try_id messages are known -> copy them to - the newsaved list */ + /* the first try_id messages are known -> copy them to the newsaved list */ for( num = first_nr; num < list_len; num++ ) - save_str(&ctl->newsaved, num-first_nr + 1, - str_from_nr_list( &ctl->oldsaved, num )); + { + struct idlist *new = save_str(&ctl->newsaved, + str_from_nr_list(&ctl->oldsaved, num), + UID_UNSEEN); + new->val.status.num = num - first_nr + 1; + } if( nolinear ) { free_str_list(&ctl->oldsaved); @@ -299,7 +382,7 @@ pop3_slowuidl( int sock, struct query *ctl, int *countp, int *newp) static int pop3_getrange(int sock, struct query *ctl, const char *folder, - int *countp, int *newp) + int *countp, int *newp, int *bytes) /* get range of messages to be fetched */ { int ok; @@ -319,7 +402,7 @@ static int pop3_getrange(int sock, gen_send(sock, "STAT"); ok = pop3_ok(sock, buf); if (ok == 0) - sscanf(buf,"%d %*d", countp); + sscanf(buf,"%d %d", countp, bytes); else return(ok); @@ -342,7 +425,10 @@ static int pop3_getrange(int sock, if (ok == 0) { if (sscanf(buf, "%d", &last) == 0) - PROTOCOL_ERROR + { + report(stderr, _("protocol error\n")); + return(PS_ERROR); + } *newp = (*countp - last); } else @@ -352,7 +438,10 @@ static int pop3_getrange(int sock, { /* don't worry, yet! do it the slow way */ if((ok = pop3_slowuidl( sock, ctl, countp, newp))!=0) - PROTOCOL_ERROR + { + report(stderr, _("protocol error while fetching UIDLs\n")); + return(PS_ERROR); + } } else { @@ -365,10 +454,16 @@ static int pop3_getrange(int sock, break; else if (sscanf(buf, "%d %s", &num, id) == 2) { - save_str(&ctl->newsaved, num, id); + struct idlist *new; + + new = save_str(&ctl->newsaved, id, UID_UNSEEN); + new->val.status.num = num; - /* note: ID comparison is caseblind */ - if (!str_in_list(&ctl->oldsaved, id)) + if (str_in_list(&ctl->oldsaved, id, FALSE)) { + new->val.status.mark = UID_SEEN; + str_set_mark(&ctl->oldsaved, id, UID_SEEN); + } + else (*newp)++; } } @@ -376,7 +471,7 @@ static int pop3_getrange(int sock, } } - return(0); + return(PS_SUCCESS); } static int pop3_getsizes(int sock, int count, int *sizes) @@ -398,8 +493,6 @@ static int pop3_getsizes(int sock, int count, int *sizes) break; else if (sscanf(buf, "%d %d", &num, &size) == 2) sizes[num - 1] = size; - else - sizes[num - 1] = -1; } return(ok); @@ -412,24 +505,113 @@ static int pop3_is_old(int sock, struct query *ctl, int num) if (!ctl->oldsaved) return (num <= last); else - /* note: ID comparison is caseblind */ return (str_in_list(&ctl->oldsaved, - str_find (&ctl->newsaved, num))); + str_find(&ctl->newsaved, num), FALSE)); } +#ifdef UNUSED +/* + * We could use this to fetch headers only as we do for IMAP. The trouble + * is that there's no way to fetch the body only. So the following RETR + * would have to re-fetch the header. Enough messages have longer headers + * than bodies to make this a net loss. + */ +static int pop_fetch_headers(int sock, struct query *ctl,int number,int *lenp) +/* request headers of nth message */ +{ + int ok; + char buf[POPBUFSIZE+1]; + + gen_send(sock, "TOP %d 0", number); + if ((ok = pop3_ok(sock, buf)) != 0) + return(ok); + + *lenp = -1; /* we got sizes from the LIST response */ + + return(PS_SUCCESS); +} +#endif /* UNUSED */ + static int pop3_fetch(int sock, struct query *ctl, int number, int *lenp) /* request nth message */ { int ok; - char buf [POPBUFSIZE+1], *cp; + char buf[POPBUFSIZE+1]; - gen_send(sock, "RETR %d", number); +#ifdef SDPS_ENABLE + /* + * See http://www.demon.net/services/mail/sdps-tech.html + * for a description of what we're parsing here. + */ + if (ctl->server.sdps) + { + int linecount = 0; + + sdps_envfrom = (char *)NULL; + sdps_envto = (char *)NULL; + gen_send(sock, "*ENV %d", number); + do { + if (gen_recv(sock, buf, sizeof(buf))) + { + break; + } + linecount++; + switch (linecount) { + case 4: + /* No need to wrap envelope from address */ + sdps_envfrom = xmalloc(strlen(buf)+1); + strcpy(sdps_envfrom,buf); + break; + case 5: + /* Wrap address with To: <> so nxtaddr() likes it */ + sdps_envto = xmalloc(strlen(buf)+7); + sprintf(sdps_envto,"To: <%s>",buf); + break; + } + } while + (!(buf[0] == '.' && (buf[1] == '\r' || buf[1] == '\n' || buf[1] == '\0'))); + } +#endif /* SDPS_ENABLE */ + + /* + * Though the POP RFCs don't document this fact, on almost every + * POP3 server I know of messages are marked "seen" only at the + * time the OK response to a RETR is issued. + * + * This means we can use TOP to fetch the message without setting its + * seen flag. This is good! It means that if the protocol exchange + * craps out during the message, it will still be marked `unseen' on + * the server. (Exception: in early 1999 SpryNet's POP3 servers were + * reported to mark messages seen on a TOP fetch.) + * + * However...*don't* do this if we're using keep to suppress deletion! + * In that case, marking the seen flag is the only way to prevent the + * message from being re-fetched on subsequent runs. + * + * Also use RETR if fetchall is on. This gives us a workaround + * for servers like usa.net's that bungle TOP. It's pretty + * harmless because fetchall guarantees that any message dropped + * by an interrupted RETR will be picked up on the next poll of the + * site. + * + * We take advantage here of the fact that, according to all the + * POP RFCs, "if the number of lines requested by the POP3 client + * is greater than than the number of lines in the body, then the + * POP3 server sends the entire message."). + * + * The line count passed (99999999) is the maximum value CompuServe will + * accept; it's much lower than the natural value 2147483646 (the maximum + * twos-complement signed 32-bit integer minus 1) */ + if (ctl->keep || ctl->fetchall) + gen_send(sock, "RETR %d", number); + else + gen_send(sock, "TOP %d 99999999", number); if ((ok = pop3_ok(sock, buf)) != 0) return(ok); *lenp = -1; /* we got sizes from the LIST response */ - return(0); + return(PS_SUCCESS); } static int pop3_delete(int sock, struct query *ctl, int number) @@ -442,16 +624,29 @@ static int pop3_delete(int sock, struct query *ctl, int number) static int pop3_logout(int sock, struct query *ctl) /* send logout command */ { - return(gen_transact(sock, "QUIT")); + int ok; + + ok = gen_transact(sock, "QUIT"); + if (!ok) + expunge_uids(ctl); + + return(ok); } const static struct method pop3 = { "POP3", /* Post Office Protocol v3 */ +#if INET6_ENABLE + "pop3", /* standard POP3 port */ + "pop3s", /* ssl POP3 port */ +#else /* INET6_ENABLE */ 110, /* standard POP3 port */ + 995, /* ssl POP3 port */ +#endif /* INET6_ENABLE */ FALSE, /* this is not a tagged protocol */ TRUE, /* this uses a message delimiter */ pop3_ok, /* parse command response */ + NULL, /* no password canonicalization */ pop3_getauth, /* get authorization */ pop3_getrange, /* query range of messages */ pop3_getsizes, /* we can get a list of sizes */ @@ -469,12 +664,13 @@ int doPOP3 (struct query *ctl) { #ifndef MBOX if (ctl->mailboxes->id) { - fprintf(stderr,"Option --remote is not supported with POP3\n"); + fprintf(stderr,_("Option --remote is not supported with POP3\n")); return(PS_SYNTAX); } #endif /* MBOX */ - peek_capable = FALSE; + peek_capable = !ctl->fetchall; return(do_protocol(ctl, &pop3)); } +#endif /* POP3_ENABLE */ /* pop3.c ends here */