X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=driver.c;h=317e75949a5416a25b6f58ccedd08733f518f543;hb=bf32bf553b664f8fcef0dab2458eed6cbde144dc;hp=33d245e97e2aeeaac1d54f045f144035b53fc381;hpb=9e9c28d491e7836dcea9feea75646bd9cfc32ec9;p=~andy%2Ffetchmail diff --git a/driver.c b/driver.c index 33d245e9..317e7594 100644 --- a/driver.c +++ b/driver.c @@ -11,6 +11,9 @@ #include #include #include +#ifdef HAVE_MEMORY_H +#include +#endif /* HAVE_MEMORY_H */ #if defined(STDC_HEADERS) #include #endif @@ -24,6 +27,10 @@ #endif #if defined(HAVE_ALLOCA_H) #include +#else +#ifdef _AIX + #pragma alloca +#endif #endif #if defined(HAVE_SYS_ITIMER_H) #include @@ -36,17 +43,13 @@ #include "mx.h" #endif /* HAVE_GETHOSTBYNAME */ -#ifdef SUNOS -#include -#endif - #ifdef KERBEROS_V4 #if defined (__bsdi__) #include /* order of includes matters */ #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 @@ -69,21 +72,25 @@ #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 */ +static int msglen; /* actual message length */ static void set_timeout(int timeleft) /* reset the nonresponse-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++; } } @@ -301,7 +320,7 @@ struct idlist **xmit_names; /* list of recipient names parsed out */ static char *parse_received(struct query *ctl, char *bufp) /* try to extract real addressee from the Received line */ { - char *ok; + char *ok = (char *)NULL; static char rbuf[HOSTLEN + USERNAMELEN + 4]; /* @@ -312,9 +331,7 @@ static char *parse_received(struct query *ctl, char *bufp) * address in the Received line. Sendmail itself only * does this when the mail has a single recipient. */ - if ((ok = strstr(bufp, "by ")) == (char *)NULL) - ok = (char *)NULL; - else + if ((ok = strstr(bufp, "by ")) && isspace(ok[-1])) { char *sp, *tp; @@ -331,30 +348,28 @@ static char *parse_received(struct query *ctl, char *bufp) * recipient name after a following "for". Otherwise * punt. */ - if (is_host_alias(rbuf, ctl)) - ok = strstr(sp, "for "); - else + if (!is_host_alias(rbuf, ctl)) ok = (char *)NULL; - } - - if (ok != 0) - { - char *sp, *tp; - - tp = rbuf; - sp = ok + 4; - if (*sp == '<') - sp++; - while (*sp && *sp != '>' && *sp != '@' && *sp != ';') - if (!isspace(*sp)) - *tp++ = *sp++; - else - { - /* uh oh -- whitespace here can't be right! */ - ok = (char *)NULL; - break; - } - *tp = '\0'; + else if ((ok = strstr(sp, "for ")) && isspace(ok[-1])) + { + tp = rbuf; + sp = ok + 4; + if (*sp == '<') + sp++; + while (*sp == '@') /* skip routes */ + while (*sp++ != ':') + continue; + while (*sp && *sp != '>' && *sp != '@' && *sp != ';') + if (!isspace(*sp)) + *tp++ = *sp++; + else + { + /* uh oh -- whitespace here can't be right! */ + ok = (char *)NULL; + break; + } + *tp = '\0'; + } } if (!ok) @@ -384,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 @@ -400,8 +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; - /* run down the SMTP hunt list looking for a server that's up */ + errno = 0; + + /* + * 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. */ @@ -410,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 */ @@ -427,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); @@ -435,6 +457,9 @@ static int smtp_open(struct query *ctl) } } + if (outlevel >= O_VERBOSE && ctl->smtp_socket != -1) + error(0, 0, "forwarding to SMTP port on %s", ctl->smtphost); + return(ctl->smtp_socket); } @@ -443,8 +468,8 @@ static FILE *sinkfp; static RETSIGTYPE (*sigchld)(); static int sizeticker; -static int stuffline(struct query *ctl, char *buf, flag delimited) -/* ship a line to the given control block's SMTP server */ +static int stuffline(struct query *ctl, char *buf) +/* ship a line to the given control block's output sink (SMTP server or MDA) */ { int n; @@ -466,11 +491,21 @@ static int stuffline(struct query *ctl, char *buf, flag delimited) * use . as EOM. If it does, the server will already have * decorated any . lines it sends back up. */ - if (!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) @@ -492,12 +527,12 @@ static int stuffline(struct query *ctl, char *buf, flag delimited) 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 @@ -507,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; - 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; @@ -526,7 +561,10 @@ int num; /* index of message */ headers = received_for = NULL; from_offs = ctt_offs = env_offs = -1; oldlen = 0; - for (remaining = len; remaining > 0; remaining -= linelen) + msglen = 0; + skipcount = 0; + + for (remaining = fetchlen; remaining > 0 || protocol->delimited; remaining -= linelen) { char *line; @@ -537,6 +575,7 @@ int num; /* index of message */ if ((n = SockRead(sock, buf, sizeof(buf)-1)) == -1) return(PS_SOCKET); linelen += n; + msglen += n; /* lines may not be properly CRLF terminated; fix this for qmail */ if (ctl->forcecr) @@ -551,8 +590,9 @@ int num; /* index of message */ } set_timeout(ctl->server.timeout); - /* leave extra room for reply_hack to play with */ - line = (char *) realloc(line, strlen(line) + strlen(buf) + HOSTLEN + 1); + + line = (char *) realloc(line, strlen(line) + strlen(buf) +1); + strcat(line, buf); if (line[0] == '\r' && line[1] == '\n') break; @@ -581,9 +621,24 @@ int num; /* index of message */ break; } - /* maybe this is a special message that holds mailbox annotations? */ - if (protocol->retain_hdr && protocol->retain_hdr(num, line)) - return(PS_RETAINED); + /* + * The University of Washington IMAP server (the reference + * implementation of IMAP4 written by Mark Crispin) relies + * on being able to keep base-UID information in a special + * message at the head of the mailbox. This message should + * neither be deleted nor forwarded. + */ +#ifdef POP2_ENABLE + /* + * We disable this check under POP2 because there's no way to + * prevent deletion of the message. So at least we ought to + * forward it to the user so he or she will have some clue + * that things have gone awry. + */ + if (protocol->port != 109) +#endif /* POP2_ENABLE */ + if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) + return(PS_RETAINED); /* * This code prevents fetchmail from becoming an accessory after @@ -660,7 +715,7 @@ int num; /* index of message */ } if (ctl->rewrite) - reply_hack(line, realname); + line = reply_hack(line, ctl->server.truename); if (!headers) { @@ -696,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 ); } } @@ -724,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 */ } } @@ -744,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); } @@ -773,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. @@ -817,27 +884,90 @@ int num; /* index of message */ else if (ctl->mda) /* we have a declared MDA */ { int length = 0; - char *names, *cmd; + 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); - 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); + good_addresses++; + + 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 /* @@ -849,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 */ @@ -858,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); } @@ -866,14 +996,14 @@ int num; /* index of message */ } else { - char *ap, *ctt, options[MSGBUFSIZE], addr[128], *desthost; + char *ap, *ctt, options[MSGBUFSIZE], addr[128]; /* build a connection to the SMTP listener */ if ((smtp_open(ctl) == -1)) { - free_str_list(&xmit_names); error(0, errno, "SMTP connect to %s failed", ctl->smtphost ? ctl->smtphost : "localhost"); + free_str_list(&xmit_names); return(PS_SMTP); } @@ -886,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 @@ -1034,27 +1164,67 @@ int num; /* index of message */ SMTP_data(ctl->smtp_socket); } - /* we may need to strip carriage returns */ - if (ctl->stripcr) + n = 0; + /* + * 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) { - char *sp, *tp; + *rcv = '\0'; + n = stuffline(ctl, headers); + *rcv = 'R'; + } + if (!use_invisible && n != -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) + { + 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) + { + time_t now; - for (sp = tp = headers; *sp; sp++) - if (*sp != '\r') - *tp++ = *sp; - *tp = '\0'; + 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); + } + } } - /* write all the headers */ - n = 0; - if (ctl->mda && sinkfp) - n = fwrite(headers, 1, strlen(headers), sinkfp); - else if (ctl->smtp_socket != -1) - n = SockWrite(ctl->smtp_socket, headers, strlen(headers)); - free(headers); + if (n != -1) + n = stuffline(ctl, rcv); /* ship out rest of headers */ - if (n < 0) + if (n == -1) { error(0, errno, "writing RFC822 headers"); if (ctl->mda) @@ -1123,9 +1293,11 @@ int num; /* index of message */ } + strcat(errmsg, "\n"); + /* ship out the error line */ if (sinkfp) - stuffline(ctl, errmsg, protocol->delimited); + stuffline(ctl, errmsg); } free_str_list(&xmit_names); @@ -1135,24 +1307,23 @@ int num; /* index of message */ *cp++ = '\r'; *cp++ = '\n'; *cp++ = '\0'; - stuffline(ctl, buf, protocol->delimited); + stuffline(ctl, buf); return(PS_SUCCESS); } -static int readbody(sock, ctl, forward, len, delimited) +static int readbody(sock, ctl, forward, len) /* read and dispose of a message body presented on sock */ struct query *ctl; /* query control record */ int sock; /* to which the server is connected */ int len; /* length of message */ flag forward; /* TRUE to forward */ -flag delimited; /* does the protocol use a message delimiter? */ { int linelen; - char buf[MSGBUFSIZE+1], *cp; + char buf[MSGBUFSIZE+1]; /* pass through the text lines */ - while (delimited || len > 0) + while (protocol->delimited || len > 0) { if ((linelen = SockRead(sock, buf, sizeof(buf)-1)) == -1) { @@ -1179,16 +1350,20 @@ flag delimited; /* does the protocol use a message delimiter? */ len -= linelen; /* check for end of message */ - if (delimited && *buf == '.') + if (protocol->delimited && *buf == '.') if (buf[1] == '\r' && buf[2] == '\n' && buf[3] == '\0') break; else if (buf[1] == '\n' && buf[2] == '\0') break; + else + msglen--; /* subtract the size of the dot escape */ + + msglen += linelen; /* ship out the text line */ if (forward) { - int n = stuffline(ctl, buf, delimited); + int n = stuffline(ctl, buf); if (n < 0) { @@ -1241,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); } @@ -1252,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 @@ -1290,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); @@ -1312,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 */ @@ -1371,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) { @@ -1458,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; } @@ -1477,280 +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", buf); - 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 may need to get sizes in order to check message - * limits. Or it may be forced because the fetch methods - * don't return reliable sizes. - */ - msgsizes = (int *)NULL; - if (proto->getsizes && (proto->force_getsizes || ctl->limit > 0)) + 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; } + else if (count > 0) + { + flag force_retrieval; - /* 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; - - /* 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 + /* + * 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 (proto->getsizes) { - flag wholesize = !protocol->fetch_body; + int i; + + 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); + } - /* -1 means we didn't see a size in the response */ - if (len == -1 && msgsizes) + /* 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; + + /* + * 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 ((ok = (protocol->trail)(sock, ctl, num))) + flag wholesize = !protocol->fetch_body; + + /* 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 && !msgsizes) - 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, - protocol->delimited); - 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) { - ok = (protocol->trail)(sock, ctl, num); - if (ok != 0) + if (outlevel == O_VERBOSE) + fputc('\n', stderr); + + 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); + } } - } - /* end-of-message processing starts here */ - if (outlevel == O_VERBOSE) - fputc('\n', stderr); + /* 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); - if (ctl->mda) - { - int rc; + /* 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); + } + } - /* close the delivery pipe, we'll reopen before next message */ - rc = pclose(sinkfp); - signal(SIGCHLD, sigchld); - if (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]) { - error(0, -1, "MDA exited abnormally or returned nonzero status"); - goto cleanUp; + if (outlevel >= O_VERBOSE) + error(0, 0, + "message %d was not the expected length (%d != %d)", + num, msglen, msgsizes[num-1]); } - } - else if (!suppress_forward) - { - /* write message terminator */ - if (SMTP_eom(ctl->smtp_socket) != SM_OK) + + /* end-of-message processing starts here */ + + if (ctl->mda) + { + 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) { - 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"); - } - 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); + /* perhaps this as many as we're ready to handle */ + if (ctl->fetchlimit > 0 && ctl->fetchlimit <= fetches) + goto no_error; } - 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); @@ -1760,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); } @@ -1792,6 +1981,9 @@ const struct method *proto; /* protocol method table */ case PS_SMTP: msg = "SMTP transaction"; break; + case PS_DNS: + msg = "DNS lookup"; + break; case PS_UNDEFINED: error(0, 0, "undefined"); break; @@ -1818,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)