X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=driver.c;h=317e75949a5416a25b6f58ccedd08733f518f543;hb=bf32bf553b664f8fcef0dab2458eed6cbde144dc;hp=2af9b4821ee979c5477068e7e47956b86e588460;hpb=d29244afe6029d72d9c2520f3515683136c813c9;p=~andy%2Ffetchmail diff --git a/driver.c b/driver.c index 2af9b482..317e7594 100644 --- a/driver.c +++ b/driver.c @@ -27,6 +27,10 @@ #endif #if defined(HAVE_ALLOCA_H) #include +#else +#ifdef _AIX + #pragma alloca +#endif #endif #if defined(HAVE_SYS_ITIMER_H) #include @@ -45,7 +49,7 @@ #include #define krb_get_err_text(e) (krb_err_txt[e]) #else -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__linux__) #define krb_get_err_text(e) (krb_err_txt[e]) #include #include @@ -68,18 +72,21 @@ #define SMTP_PORT 25 /* standard SMTP service port */ +#ifndef strstr /* glibc-2.1 declares this as a macro */ extern char *strstr(); /* needed on sysV68 R3V7.1. */ +#endif /* strstr */ int fetchlimit; /* how often to tear down the server connection */ int batchcount; /* count of messages sent in current batch */ flag peek_capable; /* can we peek for better error recovery? */ +int pass; /* how many times have we re-polled? */ static const struct method *protocol; static jmp_buf restart; char tag[TAGLEN]; static int tagnum; -#define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag) +#define GENSYM (sprintf(tag, "a%04d", ++tagnum % TAGMOD), tag) static char *shroud; /* string to shroud in debug output, if non-NULL */ static int mytimeout; /* value of nonreponse timeout */ @@ -193,7 +200,7 @@ static int is_host_alias(const char *name, struct query *ctl) case NO_RECOVERY: /* non-recoverable name server error */ case TRY_AGAIN: /* temporary error on authoritative server */ default: - error(0, 0, + error(0, -1, "nameserver failure while looking for `%s' during poll of %s.", name, ctl->server.pollname); ctl->errcount++; @@ -221,16 +228,28 @@ struct query *ctl; /* list of permissible aliases */ struct idlist **xmit_names; /* list of recipient names parsed out */ { const char *lname; - + int sl; + int off = 0; + lname = idpair_find(&ctl->localnames, name); if (!lname && ctl->wildcard) lname = name; if (lname != (char *)NULL) { + /* + * If the name of the user begins with a + * qmail virtual domain prefix, remove + * the prefix + */ + if (ctl->server.qvirtual) + { + sl=strlen(ctl->server.qvirtual); + if (!strncasecmp(lname,ctl->server.qvirtual,sl)) off=sl; + } if (outlevel == O_VERBOSE) - error(0, 0, "mapped %s to local %s", name, lname); - save_str(xmit_names, XMIT_ACCEPT, lname); + error(0, 0, "mapped %s to local %s", name, lname+off); + save_str(xmit_names, XMIT_ACCEPT, lname+off); accept_count++; } } @@ -337,6 +356,9 @@ static char *parse_received(struct query *ctl, char *bufp) sp = ok + 4; if (*sp == '<') sp++; + while (*sp == '@') /* skip routes */ + while (*sp++ != ':') + continue; while (*sp && *sp != '>' && *sp != '@' && *sp != ';') if (!isspace(*sp)) *tp++ = *sp++; @@ -377,9 +399,10 @@ static int smtp_open(struct query *ctl) { /* * RFC 1123 requires that the domain name in HELO address is a - * "valid principal domain name" for the client host. We - * violate this with malice aforethought in order to make the - * Received headers and logging look right. + * "valid principal domain name" for the client host. If we're + * running in invisible mode, violate this with malice + * aforethought in order to make the Received headers and + * logging look right. * * In fact this code relies on the RFC1123 requirement that the * SMTP listener must accept messages even if verification of the @@ -393,10 +416,15 @@ static int smtp_open(struct query *ctl) * What it will affect is the listener's logging. */ struct idlist *idp; + char *id_me = use_invisible ? ctl->server.truename : fetchmailhost; errno = 0; - /* run down the SMTP hunt list looking for a server that's up */ + /* + * Run down the SMTP hunt list looking for a server that's up. + * Use both explicit hunt entries (value TRUE) and implicit + * (default) ones (value FALSE). + */ for (idp = ctl->smtphunt; idp; idp = idp->next) { ctl->smtphost = idp->id; /* remember last host tried. */ @@ -405,8 +433,7 @@ static int smtp_open(struct query *ctl) continue; if (SMTP_ok(ctl->smtp_socket) == SM_OK && - SMTP_ehlo(ctl->smtp_socket, - ctl->server.truename, + SMTP_ehlo(ctl->smtp_socket, id_me, &ctl->server.esmtp_options) == SM_OK) break; /* success */ @@ -422,7 +449,7 @@ static int smtp_open(struct query *ctl) continue; if (SMTP_ok(ctl->smtp_socket) == SM_OK && - SMTP_helo(ctl->smtp_socket, ctl->server.truename) == SM_OK) + SMTP_helo(ctl->smtp_socket, id_me) == SM_OK) break; /* success */ close(ctl->smtp_socket); @@ -464,11 +491,21 @@ static int stuffline(struct query *ctl, char *buf) * use . as EOM. If it does, the server will already have * decorated any . lines it sends back up. */ - if (!protocol->delimited && *buf == '.') - if (sinkfp && ctl->mda) - fputs(".", sinkfp); - else if (ctl->smtp_socket != -1) - SockWrite(ctl->smtp_socket, buf, 1); + if (*buf == '.') + if (protocol->delimited) /* server has already byte-stuffed */ + { + if (ctl->mda) + ++buf; + else + /* writing to SMTP, leave the byte-stuffing in place */; + } + else /* if (!protocol->delimited) -- not byte-stuffed already */ + { + if (!ctl->mda) + SockWrite(ctl->smtp_socket, buf, 1); /* byte-stuff it */ + else + /* leave it alone */; + } /* we may need to strip carriage returns */ if (ctl->stripcr) @@ -490,12 +527,12 @@ static int stuffline(struct query *ctl, char *buf) return(n); } -static int readheaders(sock, len, ctl, realname, num) +static int readheaders(sock, fetchlen, reallen, ctl, num) /* read message headers and ship to SMTP or MDA */ int sock; /* to which the server is connected */ -long len; /* length of message */ +long fetchlen; /* length of message according to fetch response */ +long reallen; /* length of message according to getsizes */ struct query *ctl; /* query control record */ -char *realname; /* real name of host */ int num; /* index of message */ { struct addrblk @@ -505,8 +542,8 @@ int num; /* index of message */ } *addrchain = NULL, **chainptr = &addrchain; char buf[MSGBUFSIZE+1], return_path[MSGBUFSIZE+1]; int from_offs, ctt_offs, env_offs, next_address; - char *headers, *received_for, *desthost; - int n, linelen, oldlen, ch, remaining; + char *headers, *received_for, *desthost, *rcv; + int n, linelen, oldlen, ch, remaining, skipcount; char *cp; struct idlist *idp, *xmit_names; flag good_addresses, bad_addresses, has_nuls; @@ -525,8 +562,9 @@ int num; /* index of message */ from_offs = ctt_offs = env_offs = -1; oldlen = 0; msglen = 0; + skipcount = 0; - for (remaining = len; remaining > 0 || protocol->delimited; remaining -= linelen) + for (remaining = fetchlen; remaining > 0 || protocol->delimited; remaining -= linelen) { char *line; @@ -677,7 +715,7 @@ int num; /* index of message */ } if (ctl->rewrite) - line = reply_hack(line, realname); + line = reply_hack(line, ctl->server.truename); if (!headers) { @@ -713,9 +751,11 @@ int num; /* index of message */ { if( ctl->server.uidl ) { - char id[IDLEN+1]; + char id[IDLEN+1]; + /* prevent stack overflows */ + buf[IDLEN+12] = 0; sscanf( buf+12, "%s", id); - if( !str_in_list( &ctl->newsaved, id ) ) + if( !str_find( &ctl->newsaved, num ) ) save_str(&ctl->newsaved, num, id ); } } @@ -741,11 +781,19 @@ int num; /* index of message */ if (env_offs == -1 && !strncasecmp(ctl->server.envelope, line, strlen(ctl->server.envelope))) + { + if (skipcount++ != ctl->server.envskip) + continue; env_offs = (line - headers); + } } #ifdef HAVE_RES_SEARCH else if (!received_for && !strncasecmp("Received:", line, 9)) + { + if (skipcount++ != ctl->server.envskip) + continue; received_for = parse_received(ctl, line); + } #endif /* HAVE_RES_SEARCH */ } } @@ -761,9 +809,13 @@ int num; /* index of message */ */ if (headers == (char *)NULL) { +#ifdef HAVE_SNPRINTF + snprintf(buf, sizeof(buf), +#else sprintf(buf, +#endif /* HAVE_SNPRINTF */ "From: \r\nTo: %s@localhost\r\nSubject: Headerless mail from %s's mailbox on %s\r\n", - fetchmailhost, user, ctl->remotename, realname); + fetchmailhost, user, ctl->remotename, ctl->server.truename); headers = xstrdup(buf); } @@ -790,8 +842,6 @@ int num; /* index of message */ map_name(received_for, ctl, &xmit_names); else { - int i; - /* * We haven't extracted the envelope address. * So check all the header addresses. @@ -834,32 +884,90 @@ int num; /* index of message */ else if (ctl->mda) /* we have a declared MDA */ { int length = 0; - char *names, *cmd; - - desthost = "localhost"; + char *names, *before, *after; - /* - * We go through this in order to be able to handle very - * long lists of users and (re)implement %s. - */ for (idp = xmit_names; idp; idp = idp->next) if (idp->val.num == XMIT_ACCEPT) - { - length += (strlen(idp->id) + 1); good_addresses++; - } - names = (char *)alloca(length); - names[0] = '\0'; - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.num == XMIT_ACCEPT) - { - strcat(names, idp->id); - strcat(names, " "); - } - cmd = (char *)alloca(strlen(ctl->mda) + length); - sprintf(cmd, ctl->mda, names); + + desthost = "localhost"; + + length = strlen(ctl->mda) + 1; + before = xstrdup(ctl->mda); + + /* sub user addresses for %T (or %s for backward compatibility) */ + cp = (char *)NULL; + if (strstr(before, "%s") || (cp = strstr(before, "%T"))) + { + char *sp; + + if (cp && cp[1] == 'T') + cp[1] = 's'; + + /* \177 had better be out-of-band for MDA commands */ + for (sp = before; *sp; sp++) + if (*sp == '%' && sp[1] != 's' && sp[1] != 'T') + *sp = '\177'; + + /* + * We go through this in order to be able to handle very + * long lists of users and (re)implement %s. + */ + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.num == XMIT_ACCEPT) + length += (strlen(idp->id) + 1); + + names = (char *)alloca(++length); + names[0] = '\0'; + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.num == XMIT_ACCEPT) + { + strcat(names, idp->id); + strcat(names, " "); + } + after = (char *)alloca(length); +#ifdef SNPRINTF + snprintf(after, length, before, names); +#else + sprintf(after, before, names); +#endif /* SNPRINTF */ + free(before); + before = after; + + for (sp = before; *sp; sp++) + if (*sp == '\177') + *sp = '%'; + } + + /* substitute From address for %F */ + if ((cp = strstr(before, "%F"))) + { + char *from = nxtaddr(headers + from_offs); + char *sp; + + /* \177 had better be out-of-band for MDA commands */ + for (sp = before; *sp; sp++) + if (*sp == '%' && sp[1] != 'F') + *sp = '\177'; + + length += strlen(from); + after = alloca(length); + cp[1] = 's'; +#ifdef SNPRINTF + snprintf(after, length, before, from); +#else + sprintf(after, before, from); +#endif /* SNPRINTF */ + free(before); + before = after; + + for (sp = before; *sp; sp++) + if (*sp == '\177') + *sp = '%'; + } + if (outlevel == O_VERBOSE) - error(0, 0, "about to deliver with: %s", cmd); + error(0, 0, "about to deliver with: %s", before); #ifdef HAVE_SETEUID /* @@ -871,7 +979,7 @@ int num; /* index of message */ seteuid(ctl->uid); #endif /* HAVE_SETEUID */ - sinkfp = popen(cmd, "w"); + sinkfp = popen(before, "w"); #ifdef HAVE_SETEUID /* this will fail quietly if we didn't start as root */ @@ -880,7 +988,7 @@ int num; /* index of message */ if (!sinkfp) { - error(0, -1, "MDA open failed"); + error(0, 0, "MDA open failed"); return(PS_IOERR); } @@ -908,16 +1016,16 @@ int num; /* index of message */ options[0] = '\0'; if (ctl->server.esmtp_options & ESMTP_8BITMIME) if (ctl->pass8bits) - sprintf(options, " BODY=8BITMIME"); + strcpy(options, " BODY=8BITMIME"); else if ((ctt_offs >= 0) && (ctt = nxtaddr(headers + ctt_offs))) { if (!strcasecmp(ctt,"7BIT")) - sprintf(options, " BODY=7BIT"); + strcpy(options, " BODY=7BIT"); else if (!strcasecmp(ctt,"8BIT")) - sprintf(options, " BODY=8BITMIME"); + strcpy(options, " BODY=8BITMIME"); } - if ((ctl->server.esmtp_options & ESMTP_SIZE)) - sprintf(options + strlen(options), " SIZE=%ld", len); + if ((ctl->server.esmtp_options & ESMTP_SIZE) && reallen > 0) + sprintf(options + strlen(options), " SIZE=%ld", reallen); /* * If there is a Return-Path address on the message, this was @@ -1056,48 +1164,67 @@ int num; /* index of message */ SMTP_data(ctl->smtp_socket); } - - /* utter any per-message Received information we need here */ n = 0; - sprintf(buf, "Received: from %s\n", ctl->server.truename); - if (stuffline(ctl, buf) != -1) + /* + * Some server/sendmail combinations cause problems when our + * synthetic Received line is before the From header. Cope + * with this... + */ + if ((rcv = strstr(headers, "Received:")) == (char *)NULL) + rcv = headers; + if (rcv > headers) + { + *rcv = '\0'; + n = stuffline(ctl, headers); + *rcv = 'R'; + } + if (!use_invisible && n != -1) { - sprintf(buf, "\tby %s (fetchmail-%s %s run by %s)\n", - fetchmailhost, - RELEASE_ID, - protocol->name, - ctl->remotename); - if (stuffline(ctl, buf) != -1) + /* utter any per-message Received information we need here */ + sprintf(buf, "Received: from %s\n", ctl->server.truename); + n = stuffline(ctl, buf); + if (n != -1) { - time_t now; - - buf[0] = '\t'; - if (good_addresses == 0) - { - sprintf(buf+1, - "for <%s@%s> (by default); ", - user, desthost); - } - else if (good_addresses == 1) + sprintf(buf, "\tby %s (fetchmail-%s %s run for %s)\n", + fetchmailhost, + RELEASE_ID, + protocol->name, + ctl->remotename); + n = stuffline(ctl, buf); + if (n != -1) { - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.num == XMIT_ACCEPT) - break; /* only report first address */ - sprintf(buf+1, "for <%s@%s> (%s); ", - idp->id, desthost, - MULTIDROP(ctl) ? "multi-drop" : "single-drop"); - } - else - buf[1] = '\0'; + time_t now; - time(&now); - strcat(buf, ctime(&now)); - n = stuffline(ctl, buf); + buf[0] = '\t'; + if (good_addresses == 0) + { + sprintf(buf+1, + "for <%s@%s> (by default); ", + user, desthost); + } + else if (good_addresses == 1) + { + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.num == XMIT_ACCEPT) + break; /* only report first address */ + sprintf(buf+1, "for <%s@%s> (%s); ", + idp->id, desthost, + MULTIDROP(ctl) ? "multi-drop" : "single-drop"); + } + else + buf[1] = '\0'; + + time(&now); + strcat(buf, ctime(&now)); + n = stuffline(ctl, buf); + } } } - /* ship out the synthetic Received line and the headers */ - if (n == -1 || stuffline(ctl, headers) < 0) + if (n != -1) + n = stuffline(ctl, rcv); /* ship out rest of headers */ + + if (n == -1) { error(0, errno, "writing RFC822 headers"); if (ctl->mda) @@ -1193,7 +1320,7 @@ int len; /* length of message */ flag forward; /* TRUE to forward */ { int linelen; - char buf[MSGBUFSIZE+1], *cp; + char buf[MSGBUFSIZE+1]; /* pass through the text lines */ while (protocol->delimited || len > 0) @@ -1289,7 +1416,7 @@ const char *canonical; /* server name */ if (rem != KSUCCESS) { error(0, -1, "kerberos error %s", (krb_get_err_text (rem))); - return (PS_ERROR); + return (PS_AUTHFAIL); } return (0); } @@ -1300,8 +1427,8 @@ int do_protocol(ctl, proto) struct query *ctl; /* parsed options with merged-in defaults */ const struct method *proto; /* protocol method table */ { - int ok, js, pst, sock = -1; - char *msg, *cp, realname[HOSTLEN]; + int ok, js, sock = -1; + char *msg; void (*sigsave)(); #ifndef KERBEROS_V4 @@ -1338,10 +1465,10 @@ const struct method *proto; /* protocol method table */ } protocol = proto; + pass = 0; tagnum = 0; tag[0] = '\0'; /* nuke any tag hanging out from previous query */ ok = 0; - error_init(poll_interval == 0 && !logfile); /* set up the server-nonresponse timeout */ sigsave = signal(SIGALRM, timeout_handler); @@ -1360,9 +1487,9 @@ const struct method *proto; /* protocol method table */ } else { - char buf [POPBUFSIZE+1], *sp, *realhost; + char buf [POPBUFSIZE+1], *realhost; int *msgsizes, len, num, count, new, deletions = 0; - int port, fetches; + int port, fetches, dispatches; struct idlist *idp; /* execute pre-initialization command, if any */ @@ -1419,82 +1546,6 @@ const struct method *proto; /* protocol method table */ goto cleanUp; set_timeout(ctl->server.timeout); - /* - * Try to parse the host's actual name out of the greeting - * message. We do this so that the progress messages will - * make sense even if the connection is indirected through - * ssh. *Do* use this for hacking reply headers, but *don't* - * use it for error logging, as the names in the log should - * correlate directly back to rc file entries. - * - * This assumes that the first space-delimited token found - * that contains at least two dots (with the characters on - * each side of the dot alphanumeric to exclude version - * numbers) is the hostname. The hostname candidate may not - * contain @ -- if it does it's probably a mailserver - * maintainer's name. If no such token is found, fall back on - * the .fetchmailrc id. - */ - pst = 0; - sp = (char *)NULL; /* sacrifice to -Wall */ - for (cp = buf; *cp; cp++) - { - switch (pst) - { - case 0: /* skip to end of current token */ - if (*cp == ' ') - pst = 1; - break; - - case 1: /* look for blank-delimited token */ - if (*cp != ' ') - { - sp = cp; - pst = 2; - } - break; - - case 2: /* look for first dot */ - if (*cp == '@') - pst = 0; - else if (*cp == ' ') - pst = 1; - else if (*cp == '.' && isalpha(cp[1]) && isalpha(cp[-1])) - pst = 3; - break; - - case 3: /* look for second dot */ - if (*cp == '@') - pst = 0; - else if (*cp == ' ') - pst = 1; - else if (*cp == '.' && isalpha(cp[1]) && isalpha(cp[-1])) - pst = 4; - break; - - case 4: /* look for trailing space */ - if (*cp == '@') - pst = 0; - else if (*cp == ' ') - { - pst = 5; - goto done; - } - break; - } - } - done: - if (pst == 5) - { - char *tp = realname; - - while (sp < cp) - *tp++ = *sp++; - *tp = '\0'; - } - else - strcpy(realname, ctl->server.pollname); - /* try to get authorized to fetch mail */ if (protocol->getauth) { @@ -1506,14 +1557,14 @@ const struct method *proto; /* protocol method table */ if (ok == PS_LOCKBUSY) error(0, -1, "Lock-busy error on %s@%s", ctl->remotename, - realname); + ctl->server.truename); else { if (ok == PS_ERROR) ok = PS_AUTHFAIL; error(0, -1, "Authorization failure on %s@%s", ctl->remotename, - realname); + ctl->server.truename); } goto cleanUp; } @@ -1525,330 +1576,370 @@ const struct method *proto; /* protocol method table */ /* now iterate over each folder selected */ for (idp = ctl->mailboxes; idp; idp = idp->next) { - if (outlevel >= O_VERBOSE) - if (idp->next) - error(0, 0, "selecting folder %s"); - else - error(0, 0, "selecting default folder"); - - /* compute number of messages and number of new messages waiting */ - ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); + pass = 0; + do { + dispatches = 0; + ++pass; - /* show user how many messages we downloaded */ - if (idp->id) - (void) sprintf(buf, "%s@%s:%s", - ctl->remotename, realname, idp->id); - else - (void) sprintf(buf, "%s@%s", ctl->remotename, realname); - if (outlevel > O_SILENT) - if (count == -1) /* only used for ETRN */ - error(0, 0, "Polling %s", realname); - else if (count == 0) - { - /* these are pointless in normal daemon mode */ - if (poll_interval == 0 || outlevel == O_VERBOSE ) - error(0, 0, "No mail at %s", buf); - } + if (outlevel >= O_VERBOSE) + if (idp->id) + error(0, 0, "selecting or re-polling folder %s", idp->id); + else + error(0, 0, "selecting or re-polling default folder"); + + /* compute # of messages and number of new messages waiting */ + ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); + + /* show user how many messages we downloaded */ + if (idp->id) + (void) sprintf(buf, "%s at %s (folder %s)", + ctl->remotename, ctl->server.truename, idp->id); else - { - if (new != -1 && (count - new) > 0) - error(0, 0, "%d message%s (%d seen) at %s.", - count, count > 1 ? "s" : "", count-new, buf); + (void) sprintf(buf, "%s at %s", ctl->remotename, ctl->server.truename); + if (outlevel > O_SILENT) + if (count == -1) /* only used for ETRN */ + error(0, 0, "Polling %s", ctl->server.truename); + else if (count != 0) + { + if (new != -1 && (count - new) > 0) + error(0, 0, "%d message%s (%d seen) for %s.", + count, count > 1 ? "s" : "", count-new, buf); + else + error(0, 0, "%d message%s for %s.", + count, count > 1 ? "s" : "", buf); + } else - error(0, 0, "%d message%s at %s.", - count, count > 1 ? "s" : "", buf); - } - - if (check_only) - { - if (new == -1 || ctl->fetchall) - new = count; - ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL); - goto cleanUp; - } - else if (count > 0) - { - flag force_retrieval; + { + /* these are pointless in normal daemon mode */ + if (pass == 1 && (poll_interval == 0 || outlevel == O_VERBOSE)) + error(0, 0, "No mail for %s", buf); + } - /* - * What forces this code is that in POP3 and IMAP2BIS you can't - * fetch a message without having it marked `seen'. In IMAP4, - * on the other hand, you can (peek_capable is set to convey - * this). - * - * The result of being unable to peek is that if there's - * any kind of transient error (DNS lookup failure, or - * sendmail refusing delivery due to process-table limits) - * the message will be marked "seen" on the server without - * having been delivered. This is not a big problem if - * fetchmail is running in foreground, because the user - * will see a "skipped" message when it next runs and get - * clued in. - * - * But in daemon mode this leads to the message being silently - * ignored forever. This is not acceptable. - * - * We compensate for this by checking the error count from the - * previous pass and forcing all messages to be considered new - * if it's nonzero. - */ - force_retrieval = !peek_capable && (ctl->errcount > 0); + /* very important, this is where we leave the do loop */ + if (count == 0) + break; - /* - * We need the size of each method before it's loaded in - * order to pass via the ESMTP SIZE option. If the protocol - * has a getsizes method, we presume this means it doesn't - * get reliable sizes from message fetch responses. - */ - if (proto->getsizes) + if (check_only) { - msgsizes = (int *)alloca(sizeof(int) * count); - - ok = (proto->getsizes)(sock, count, msgsizes); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); + if (new == -1 || ctl->fetchall) + new = count; + ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL); + goto cleanUp; } - - /* read, forward, and delete messages */ - for (num = 1; num <= count; num++) - { - flag toolarge = (ctl->limit > 0) - && msgsizes && (msgsizes[num-1] > ctl->limit); - flag fetch_it = !toolarge - && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num))); - flag suppress_delete = FALSE; - flag suppress_forward = FALSE; - flag retained = FALSE; + else if (count > 0) + { + flag force_retrieval; /* - * This check copes with Post Office/NT's annoying habit - * of randomly prepending bogus LIST items of length -1. - * Patrick Audley tells us: - * LIST shows a size of -1, RETR and TOP return - * "-ERR System error - couldn't open message", and DELE - * succeeds but doesn't actually delete the message. + * What forces this code is that in POP3 and + * IMAP2BIS you can't fetch a message without + * having it marked `seen'. In IMAP4, on the + * other hand, you can (peek_capable is set to + * convey this). + * + * The result of being unable to peek is that if there's + * any kind of transient error (DNS lookup failure, or + * sendmail refusing delivery due to process-table limits) + * the message will be marked "seen" on the server without + * having been delivered. This is not a big problem if + * fetchmail is running in foreground, because the user + * will see a "skipped" message when it next runs and get + * clued in. + * + * But in daemon mode this leads to the message + * being silently ignored forever. This is not + * acceptable. + * + * We compensate for this by checking the error + * count from the previous pass and forcing all + * messages to be considered new if it's nonzero. + */ + force_retrieval = !peek_capable && (ctl->errcount > 0); + + /* + * We need the size of each message before it's + * loaded in order to pass via the ESMTP SIZE + * option. If the protocol has a getsizes method, + * we presume this means it doesn't get reliable + * sizes from message fetch responses. */ - if (msgsizes && msgsizes[num-1] == -1) + if (proto->getsizes) { - if (outlevel >= O_VERBOSE) - error(0, 0, - "Skipping message %d, length -1", - num - 1); - continue; - } + int i; - /* we may want to reject this message if it's old */ - if (!fetch_it) - { - if (outlevel > O_SILENT) - { - error_build("skipping message %d", num); - if (toolarge) - error_build(" (oversized, %d bytes)", msgsizes[num-1]); - } - } - else - { - flag wholesize = !protocol->fetch_body; + msgsizes = (int *)alloca(sizeof(int) * count); + for (i = 0; i < count; i++) + msgsizes[i] = -1; - /* request a message */ - ok = (protocol->fetch_headers)(sock, ctl, num, &len); + ok = (proto->getsizes)(sock, count, msgsizes); if (ok != 0) goto cleanUp; set_timeout(ctl->server.timeout); + } + + /* read, forward, and delete messages */ + for (num = 1; num <= count; num++) + { + flag toolarge = (ctl->limit > 0) + && msgsizes && (msgsizes[num-1] > ctl->limit); + flag fetch_it = !toolarge + && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num))); + flag suppress_delete = FALSE; + flag suppress_forward = FALSE; + flag retained = FALSE; - /* -1 means we didn't see a size in the response */ - if (len == -1 && msgsizes) + /* + * This check copes with Post Office/NT's + * annoying habit of randomly prepending bogus + * LIST items of length -1. Patrick Audley + * tells us: LIST shows a + * size of -1, RETR and TOP return "-ERR + * System error - couldn't open message", and + * DELE succeeds but doesn't actually delete + * the message. + */ + if (msgsizes && msgsizes[num-1] == -1) { - len = msgsizes[num - 1]; - wholesize = TRUE; + if (outlevel >= O_VERBOSE) + error(0, 0, + "Skipping message %d, length -1", + num - 1); + continue; } - if (outlevel > O_SILENT) + /* we may want to reject this message if it's old */ + if (!fetch_it) { - error_build("reading message %d of %d",num,count); - - if (len > 0) - error_build(" (%d %sbytes)", - len, wholesize ? "" : "header "); - if (outlevel == O_VERBOSE) - error_complete(0, 0, ""); - else - error_build(" "); + if (outlevel > O_SILENT) + { + error_build("skipping message %d", num); + if (toolarge) + error_build(" (oversized, %d bytes)", + msgsizes[num-1]); + } } - - /* - * Read the message headers and ship them to the - * output sink. - */ - ok = readheaders(sock, len, ctl, realname, num); - if (ok == PS_RETAINED) - suppress_forward = retained = TRUE; - else if (ok == PS_TRANSIENT) - suppress_delete = suppress_forward = TRUE; - else if (ok == PS_REFUSED) - suppress_forward = TRUE; - else if (ok) - goto cleanUp; - set_timeout(ctl->server.timeout); - - /* - * If we're using IMAP4 or something else that - * can fetch headers separately from bodies, - * it's time to request the body now. This - * fetch may be skipped if we got an anti-spam - * or other PS_REFUSED error response during - * read_headers. - */ - if (protocol->fetch_body) + else { - if (outlevel == O_VERBOSE) - fputc('\n', stderr); + flag wholesize = !protocol->fetch_body; - if ((ok = (protocol->trail)(sock, ctl, num))) + /* request a message */ + ok = (protocol->fetch_headers)(sock,ctl,num, &len); + if (ok != 0) goto cleanUp; set_timeout(ctl->server.timeout); - len = 0; - if (!suppress_forward) + + /* -1 means we didn't see a size in the response */ + if (len == -1 && msgsizes) { - if ((ok=(protocol->fetch_body)(sock,ctl,num,&len))) - goto cleanUp; - if (outlevel > O_SILENT && !wholesize) - error_build(" (%d body bytes) ", len); - set_timeout(ctl->server.timeout); + len = msgsizes[num - 1]; + wholesize = TRUE; } - } - /* process the body now */ - if (len > 0) - { - ok = readbody(sock, - ctl, - !suppress_forward, - len); - if (ok == PS_TRANSIENT) + if (outlevel > O_SILENT) + { + error_build("reading message %d of %d", + num,count); + + if (len > 0) + error_build(" (%d %sbytes)", + len, wholesize ? "" : "header "); + if (outlevel == O_VERBOSE) + error_complete(0, 0, ""); + else + error_build(" "); + } + + /* + * Read the message headers and ship them to the + * output sink. + */ + ok = readheaders(sock, len, msgsizes[num-1], + ctl, num); + if (ok == PS_RETAINED) + suppress_forward = retained = TRUE; + else if (ok == PS_TRANSIENT) suppress_delete = suppress_forward = TRUE; + else if (ok == PS_REFUSED) + suppress_forward = TRUE; else if (ok) goto cleanUp; set_timeout(ctl->server.timeout); - /* tell server we got it OK and resynchronize */ - if (protocol->trail) + /* + * If we're using IMAP4 or something else that + * can fetch headers separately from bodies, + * it's time to request the body now. This + * fetch may be skipped if we got an anti-spam + * or other PS_REFUSED error response during + * read_headers. + */ + if (protocol->fetch_body) { if (outlevel == O_VERBOSE) fputc('\n', stderr); - ok = (protocol->trail)(sock, ctl, num); - if (ok != 0) + if ((ok = (protocol->trail)(sock, ctl, num))) goto cleanUp; set_timeout(ctl->server.timeout); + len = 0; + if (!suppress_forward) + { + if ((ok=(protocol->fetch_body)(sock,ctl,num,&len))) + goto cleanUp; + if (outlevel > O_SILENT && !wholesize) + error_build(" (%d body bytes) ", len); + set_timeout(ctl->server.timeout); + } } - } - /* - * Check to see if the numbers matched? - * - * Yes, some servers foo this up horribly. - * All IMAP servers seem to get it right, and - * so does Eudora QPOP at least in 2.xx - * versions. - * - * Microsoft Exchange gets it completely - * wrong, reporting compressed rather than - * actual sizes (so the actual length of - * message is longer than the reported size). - * Another fine example of Microsoft brain death! - * - * Some older POP servers, like the old UCB - * POP server and the pre-QPOP QUALCOMM - * versions, report a longer size in the LIST - * response than actually gets shipped up. - * It's unclear what is going on here, as the - * QUALCOMM server (at least) seems to be - * reporting the on-disk size correctly. - */ - if (msgsizes && msglen != msgsizes[num-1]) - { - if (outlevel >= O_VERBOSE) - error(0, 0, - "message %d was not the expected length (%d != %d)", - num, msglen, msgsizes[num-1]); - } + /* process the body now */ + if (len > 0) + { + ok = readbody(sock, + ctl, + !suppress_forward, + len); + if (ok == PS_TRANSIENT) + suppress_delete = suppress_forward = TRUE; + else if (ok) + goto cleanUp; + set_timeout(ctl->server.timeout); - /* end-of-message processing starts here */ + /* tell server we got it OK and resynchronize */ + if (protocol->trail) + { + if (outlevel == O_VERBOSE) + fputc('\n', stderr); + + ok = (protocol->trail)(sock, ctl, num); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); + } + } - if (ctl->mda) - { - int rc; + /* count # messages forwarded on this pass */ + if (!suppress_forward) + dispatches++; + + /* + * Check to see if the numbers matched? + * + * Yes, some servers foo this up horribly. + * All IMAP servers seem to get it right, and + * so does Eudora QPOP at least in 2.xx + * versions. + * + * Microsoft Exchange gets it completely + * wrong, reporting compressed rather than + * actual sizes (so the actual length of + * message is longer than the reported size). + * Another fine example of Microsoft brain death! + * + * Some older POP servers, like the old UCB + * POP server and the pre-QPOP QUALCOMM + * versions, report a longer size in the LIST + * response than actually gets shipped up. + * It's unclear what is going on here, as the + * QUALCOMM server (at least) seems to be + * reporting the on-disk size correctly. + */ + if (msgsizes && msglen != msgsizes[num-1]) + { + if (outlevel >= O_VERBOSE) + error(0, 0, + "message %d was not the expected length (%d != %d)", + num, msglen, msgsizes[num-1]); + } + + /* end-of-message processing starts here */ - /* close the delivery pipe, we'll reopen before next message */ - rc = pclose(sinkfp); - signal(SIGCHLD, sigchld); - if (rc) + if (ctl->mda) { - error(0, -1, "MDA exited abnormally or returned nonzero status"); - goto cleanUp; + int rc; + + /* close the delivery pipe, we'll reopen before next message */ + rc = pclose(sinkfp); + signal(SIGCHLD, sigchld); + if (rc) + { + error(0, -1, "MDA exited abnormally or returned nonzero status"); + goto cleanUp; + } } - } - else if (!suppress_forward) - { - /* write message terminator */ - if (SMTP_eom(ctl->smtp_socket) != SM_OK) + else if (!suppress_forward) { - error(0, -1, "SMTP listener refused delivery"); - ctl->errcount++; - suppress_delete = TRUE; + /* write message terminator */ + if (SMTP_eom(ctl->smtp_socket) != SM_OK) + { + error(0, -1, "SMTP listener refused delivery"); + ctl->errcount++; + suppress_delete = TRUE; + } } + + fetches++; } - fetches++; - } + /* + * At this point in flow of control, either + * we've bombed on a protocol error or had + * delivery refused by the SMTP server + * (unlikely -- I've never seen it) or we've + * seen `accepted for delivery' and the + * message is shipped. It's safe to mark the + * message seen and delete it on the server + * now. + */ - /* - * At this point in flow of control, either we've bombed - * on a protocol error or had delivery refused by the SMTP - * server (unlikely -- I've never seen it) or we've seen - * `accepted for delivery' and the message is shipped. - * It's safe to mark the message seen and delete it on the - * server now. - */ + /* maybe we delete this message now? */ + if (retained) + { + if (outlevel > O_SILENT) + error_complete(0, 0, " retained"); + } + else if (protocol->delete + && !suppress_delete + && (fetch_it ? !ctl->keep : ctl->flush)) + { + deletions++; + if (outlevel > O_SILENT) + error_complete(0, 0, " flushed"); + ok = (protocol->delete)(sock, ctl, num); + if (ok != 0) + goto cleanUp; + set_timeout(ctl->server.timeout); +#ifdef POP3_ENABLE + delete_str(&ctl->newsaved, num); +#endif /* POP3_ENABLE */ + } + else if (outlevel > O_SILENT) + error_complete(0, 0, " not flushed"); - /* maybe we delete this message now? */ - if (retained) - { - if (outlevel > O_SILENT) - error_complete(0, 0, " retained"); + /* perhaps this as many as we're ready to handle */ + if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches) + goto no_error; } - else if (protocol->delete - && !suppress_delete - && (fetch_it ? !ctl->keep : ctl->flush)) - { - deletions++; - if (outlevel > O_SILENT) - error_complete(0, 0, " flushed"); - ok = (protocol->delete)(sock, ctl, num); - if (ok != 0) - goto cleanUp; - set_timeout(ctl->server.timeout); - delete_str(&ctl->newsaved, num); - } - else if (outlevel > O_SILENT) - error_complete(0, 0, " not flushed"); - - /* perhaps this as many as we're ready to handle */ - if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches) - goto no_error; } - } + } while + /* + * Only re-poll if we had some actual forwards, allowed + * deletions and had no errors. + * Otherwise it is far too easy to get into infinite loops. + */ + (dispatches && protocol->retry && !ctl->keep && !ctl->errcount); } no_error: set_timeout(ctl->server.timeout); - ok = gen_transact(sock, protocol->exit_cmd); + ok = (protocol->logout_cmd)(sock, ctl); + /* + * Hmmmm...arguably this would be incorrect if we had fetches but + * no dispatches (due to oversized messages, etc.) + */ if (ok == 0) ok = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL; set_timeout(0); @@ -1858,7 +1949,7 @@ const struct method *proto; /* protocol method table */ cleanUp: set_timeout(ctl->server.timeout); if (ok != 0 && ok != PS_SOCKET) - gen_transact(sock, protocol->exit_cmd); + (protocol->logout_cmd)(sock, ctl); set_timeout(0); close(sock); } @@ -1919,7 +2010,7 @@ closeUp: } #if defined(HAVE_STDARG_H) -void gen_send(int sock, char *fmt, ... ) +void gen_send(int sock, const char *fmt, ... ) /* assemble command in printf(3) style and send to the server */ #else void gen_send(sock, fmt, va_alist)