X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=driver.c;h=c2917268cedfce70fa9d70f085bad27b7f4f201b;hb=d31db10231e9ed89f64fdf6e0fb7cae182aa377e;hp=250a1bff84908a6abe16b0060cc1d3018a9d1b74;hpb=d57b00768a8fa44d1e1c5f758440edabfa488baf;p=~andy%2Ffetchmail diff --git a/driver.c b/driver.c index 250a1bff..c2917268 100644 --- a/driver.c +++ b/driver.c @@ -36,7 +36,13 @@ #endif #include #ifdef HAVE_PKG_hesiod +#ifdef __cplusplus +extern "C" { +#endif #include +#ifdef __cplusplus +} +#endif #endif #include @@ -53,9 +59,10 @@ #include "getaddrinfo.h" #include "tunable.h" +#include "sdump.h" + /* throw types for runtime errors */ #define THROW_TIMEOUT 1 /* server timed out */ -#define THROW_SIGPIPE 2 /* SIGPIPE on stream socket */ /* magic values for the message length array */ #define MSGLEN_UNKNOWN 0 /* length unknown (0 is impossible) */ @@ -70,10 +77,12 @@ int batchcount; /* count of messages sent in current batch */ flag peek_capable; /* can we peek for better error recovery? */ int mailserver_socket_temp = -1; /* socket to free if connect timeout */ +struct addrinfo *ai0, *ai1; /* clean these up after signal */ + static volatile int timeoutcount = 0; /* count consecutive timeouts */ static volatile int idletimeout = 0; /* timeout occured in idle stage? */ -static jmp_buf restart; +static sigjmp_buf restart; int is_idletimeout(void) /* last timeout occured in idle stage? */ @@ -108,18 +117,14 @@ static RETSIGTYPE timeout_handler (int signal) (void)signal; if(stage != STAGE_IDLE) { timeoutcount++; - longjmp(restart, THROW_TIMEOUT); + /* XXX FIXME: this siglongjmp must die - it's not safe to be + * called from a function handler and breaks, for instance, + * getaddrinfo() */ + siglongjmp(restart, THROW_TIMEOUT); } else idletimeout = 1; } -static RETSIGTYPE sigpipe_handler (int signal) -/* handle SIGPIPE signal indicating a broken stream socket */ -{ - (void)signal; - longjmp(restart, THROW_SIGPIPE); -} - #define CLEANUP_TIMEOUT 60 /* maximum timeout during cleanup */ static int cleanupSockClose (int fd) @@ -249,15 +254,17 @@ const char *canonical; /* server name */ if (retval) { #ifdef HEIMDAL if (err_ret && err_ret->e_text) { - report(stderr, GT_("krb5_sendauth: %s [server says '%*s'] \n"), - error_message(retval), - err_ret->e_text); + char *t = err_ret->e_text; + char *tt = sdump(t, strlen(t)); + report(stderr, GT_("krb5_sendauth: %s [server says '%s']\n"), + error_message(retval), tt); + free(tt); #else if (err_ret && err_ret->text.length) { - report(stderr, GT_("krb5_sendauth: %s [server says '%*s'] \n"), - error_message(retval), - err_ret->text.length, - err_ret->text.data); + char *tt = sdump(err_ret->text.data, err_ret->text.length); + report(stderr, GT_("krb5_sendauth: %s [server says '%s']\n"), + error_message(retval), tt); + free(tt); #endif krb5_free_error(context, err_ret); } else @@ -362,11 +369,13 @@ static void send_size_warnings(struct query *ctl) size = atoi(current->id); if (ctl->limitflush) stuff_warning(NULL, ctl, - GT_(" %d msg %d octets long deleted by fetchmail."), + ngettext(" %d message %d octets long deleted by fetchmail.", + " %d messages %d octets long deleted by fetchmail.", nbr), nbr, size); else stuff_warning(NULL, ctl, - GT_(" %d msg %d octets long skipped by fetchmail."), + ngettext(" %d message %d octets long skipped by fetchmail.", + " %d messages %d octets long skipped by fetchmail.", nbr), nbr, size); } current->val.status.num++; @@ -419,9 +428,18 @@ static void mark_oversized(struct query *ctl, int size) } } +static int eat_trailer(int sock, struct query *ctl) +{ + /* we only need this LF if we're printing ticker dots + * AND we are dumping protocol traces. */ + if (outlevel >= O_VERBOSE && want_progress()) fputc('\n', stdout); + return (ctl->server.base_protocol->trail)(sock, ctl, tag); +} + static int fetch_messages(int mailserver_socket, struct query *ctl, int count, int **msgsizes, int maxfetch, - int *fetches, int *dispatches, int *deletions) + int *fetches, int *dispatches, int *deletions, + int *transient_errors) /* fetch messages in lockstep mode */ { flag force_retrieval; @@ -443,7 +461,7 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, /* Time to allocate memory to store the sizes */ xfree(*msgsizes); - *msgsizes = xmalloc(sizeof(int) * fetchsizelimit); + *msgsizes = (int *)xmalloc(sizeof(int) * fetchsizelimit); } /* @@ -485,20 +503,20 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, /* check if the message is old * Note: the size of the message may not be known here */ - if (ctl->fetchall || force_retrieval) - ; - else if (ctl->server.base_protocol->is_old && (ctl->server.base_protocol->is_old)(mailserver_socket,ctl,num)) - msgcode = MSGLEN_OLD; + if (ctl->fetchall || force_retrieval) { + /* empty */ + } else { + if (ctl->server.base_protocol->is_old && (ctl->server.base_protocol->is_old)(mailserver_socket,ctl,num)) { + msgcode = MSGLEN_OLD; + } + } if (msgcode == MSGLEN_OLD) { - /* To avoid flooding the syslog when using --keep, - * report "Skipped message" only when: - * 1) --verbose is on, or - * 2) fetchmail does not use syslog - */ - if ( (outlevel >= O_VERBOSE) || - (outlevel > O_SILENT && !run.use_syslog) - ) + /* + * To avoid flooding the logs when using --keep, report + * skipping for old messages only when --flush is on. + */ + if (outlevel > O_SILENT && ctl->flush) { report_build(stdout, GT_("skipping message %s@%s:%d"), @@ -581,6 +599,9 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, } else { + /* XXX FIXME: make this one variable, wholesize and + separatefetchbody query the same variable just with + inverted logic */ flag wholesize = !ctl->server.base_protocol->fetch_body; flag separatefetchbody = (ctl->server.base_protocol->fetch_body) ? TRUE : FALSE; @@ -592,6 +613,7 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, GT_("couldn't fetch headers, message %s@%s:%d (%d octets)\n"), ctl->remotename, ctl->server.truename, num, msgsize); + (*transient_errors)++; continue; } else if (err != 0) @@ -613,10 +635,11 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, if (len > 0) report_build(stdout, wholesize ? GT_(" (%d octets)") : GT_(" (%d header octets)"), len); - if (outlevel >= O_VERBOSE) - report_complete(stdout, "\n"); - else - report_complete(stdout, " "); + if (want_progress()) { + /* flush and add a blank to append ticker dots */ + report_flush(stdout); + putchar(' '); + } } /* @@ -628,28 +651,24 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, /* pass the suppress_readbody flag only if the underlying * protocol does not fetch the body separately */ separatefetchbody ? 0 : &suppress_readbody); + if (err == PS_RETAINED) suppress_forward = suppress_delete = retained = TRUE; else if (err == PS_TRANSIENT) + { suppress_delete = suppress_forward = TRUE; + (*transient_errors)++; + } else if (err == PS_REFUSED) suppress_forward = TRUE; - else if (err == PS_TRUNCATED) - suppress_readbody = TRUE; else if (err) return(err); /* tell server we got it OK and resynchronize */ if (separatefetchbody && ctl->server.base_protocol->trail) { - if (outlevel >= O_VERBOSE && !is_a_file(1) && !run.use_syslog) - { - fputc('\n', stdout); - fflush(stdout); - } - - if ((err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, tag))) - return(err); + err = eat_trailer(mailserver_socket, ctl); + if (err) return(err); } /* do not read the body which is not being forwarded only if @@ -682,9 +701,15 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, */ if (len == -1) len = msgsize - msgblk.msglen; - if (outlevel > O_SILENT && !wholesize) - report_complete(stdout, - GT_(" (%d body octets) "), len); + if (!wholesize) { + if (outlevel > O_SILENT) + report_build(stdout, + GT_(" (%d body octets)"), len); + if (want_progress()) { + report_flush(stdout); + putchar(' '); + } + } } /* process the body now */ @@ -692,23 +717,19 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, ctl, !suppress_forward, len); + if (err == PS_TRANSIENT) + { suppress_delete = suppress_forward = TRUE; + (*transient_errors)++; + } else if (err) return(err); /* tell server we got it OK and resynchronize */ - if (ctl->server.base_protocol->trail) - { - if (outlevel >= O_VERBOSE && !is_a_file(1) && !run.use_syslog) - { - fputc('\n', stdout); - fflush(stdout); - } - - err = (ctl->server.base_protocol->trail)(mailserver_socket, ctl, tag); - if (err != 0) - return(err); + if (ctl->server.base_protocol->trail) { + err = eat_trailer(mailserver_socket, ctl); + if (err) return(err); } } @@ -737,6 +758,9 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, * It's unclear what is going on here, as the * QUALCOMM server (at least) seems to be * reporting the on-disk size correctly. + * + * qmail-pop3d also goofs up message sizes and does not + * count the line end characters properly. */ if (msgblk.msglen != msgsize) { @@ -759,21 +783,22 @@ static int fetch_messages(int mailserver_socket, struct query *ctl, flagthemail: /* - * 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 + * 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. */ + /* in softbounce mode, suppress deletion and marking as seen */ + if (suppress_forward) + suppress_delete = suppress_delete || run.softbounce; + /* maybe we delete this message now? */ if (retained) { if (outlevel > O_SILENT) - report(stdout, GT_(" retained\n")); + report_complete(stdout, GT_(" retained\n")); } else if (ctl->server.base_protocol->delete_msg && !suppress_delete @@ -790,16 +815,11 @@ flagthemail: } else { - if ( (outlevel >= O_VERBOSE) || - /* To avoid flooding the syslog when using --keep, - * report "Skipped message" only when: - * 1) --verbose is on, or - * 2) fetchmail does not use syslog, or - * 3) the message was skipped for some other - * reason than just being old. - */ - (outlevel > O_SILENT && (!run.use_syslog || msgcode != MSGLEN_OLD)) - ) + /* + * To avoid flooding the logs when using --keep, report + * skipping of new messages only. + */ + if (outlevel > O_SILENT && msgcode != MSGLEN_OLD) report_complete(stdout, GT_(" not flushed\n")); /* maybe we mark this message as seen now? */ @@ -842,7 +862,6 @@ static int do_session( int tmperr; int deletions = 0, js; const char *msg; - SIGHANDLERTYPE pipesave; SIGHANDLERTYPE alrmsave; ctl->server.base_protocol = proto; @@ -856,36 +875,23 @@ static int do_session( alrmsave = set_signal_handler(SIGALRM, timeout_handler); mytimeout = ctl->server.timeout; - /* set up the broken-pipe timeout */ - pipesave = set_signal_handler(SIGPIPE, sigpipe_handler); - - if ((js = setjmp(restart))) + if ((js = sigsetjmp(restart,1))) { /* exception caught */ -#ifdef HAVE_SIGPROCMASK - /* - * Don't rely on setjmp() to restore the blocked-signal mask. - * It does this under BSD but is required not to under POSIX. - * - * If your Unix doesn't have sigprocmask, better hope it has - * BSD-like behavior. Otherwise you may see fetchmail get - * permanently wedged after a second timeout on a bad read, - * because alarm signals were blocked after the first. - */ sigset_t allsigs; sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); -#endif /* HAVE_SIGPROCMASK */ - - if (js == THROW_SIGPIPE) - { - set_signal_handler(SIGPIPE, SIG_IGN); - report(stdout, - GT_("SIGPIPE thrown from an MDA or a stream socket error\n")); - wait(0); + + if (ai0) { + fm_freeaddrinfo(ai0); ai0 = NULL; + } + + if (ai1) { + fm_freeaddrinfo(ai1); ai1 = NULL; } - else if (js == THROW_TIMEOUT) + + if (js == THROW_TIMEOUT) { if (phase == OPEN_WAIT) report(stdout, @@ -939,17 +945,21 @@ static int do_session( } else { - /* setjmp returned zero -> normal operation */ + /* sigsetjmp returned zero -> normal operation */ char buf[MSGBUFSIZE+1], *realhost; int count, newm, bytes; - int fetches, dispatches, oldphase; + int fetches, dispatches, transient_errors, oldphase; struct idlist *idp; /* execute pre-initialization command, if any */ if (ctl->preconnect && (err = system(ctl->preconnect))) { - report(stderr, - GT_("pre-connection command failed with status %d\n"), err); + if (WIFSIGNALED(err)) + report(stderr, + GT_("pre-connection command terminated with signal %d\n"), WTERMSIG(err)); + else + report(stderr, + GT_("pre-connection command failed with status %d\n"), WEXITSTATUS(err)); err = PS_SYNTAX; goto closeUp; } @@ -1013,13 +1023,17 @@ static int do_session( hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_CANONNAME; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif - error = getaddrinfo(ctl->server.queryname, NULL, &hints, &res); + error = fm_getaddrinfo(ctl->server.queryname, NULL, &hints, &res); if (error) { report(stderr, - GT_("couldn't find canonical DNS name of %s (%s)\n"), - ctl->server.pollname, ctl->server.queryname); + GT_("couldn't find canonical DNS name of %s (%s): %s\n"), + ctl->server.pollname, ctl->server.queryname, + gai_strerror(error)); err = PS_DNS; set_timeout(0); phase = oldphase; @@ -1028,11 +1042,19 @@ static int do_session( else { xfree(ctl->server.truename); - ctl->server.truename = xstrdup(res->ai_canonname); - ctl->server.trueaddr = xmalloc(res->ai_addrlen); + /* Older FreeBSD versions return NULL in ai_canonname + * if they cannot canonicalize, rather than copying + * the queryname here, as IEEE Std 1003.1-2001 + * requires. Work around NULL. */ + if (res->ai_canonname != NULL) { + ctl->server.truename = xstrdup(res->ai_canonname); + } else { + ctl->server.truename = xstrdup(ctl->server.queryname); + } + ctl->server.trueaddr = (struct sockaddr *)xmalloc(res->ai_addrlen); ctl->server.trueaddr_len = res->ai_addrlen; memcpy(ctl->server.trueaddr, res->ai_addr, res->ai_addrlen); - freeaddrinfo(res); + fm_freeaddrinfo(res); } } } @@ -1044,7 +1066,7 @@ static int do_session( (void)sleep(1); if ((mailserver_socket = SockOpen(realhost, ctl->server.service ? ctl->server.service : ( ctl->use_ssl ? ctl->server.base_protocol->sslservice : ctl->server.base_protocol->service ), - ctl->server.plugin)) == -1) + ctl->server.plugin, &ai0)) == -1) { char errbuf[BUFSIZ]; int err_no = errno; @@ -1061,25 +1083,6 @@ static int do_session( strlcpy(errbuf, strerror(err_no), sizeof(errbuf)); report_complete(stderr, ": %s\n", errbuf); -#ifdef __UNUSED - /* - * Don't use this. It was an attempt to address Debian bug - * #47143 (Notify user by mail when pop server nonexistent). - * Trouble is, that doesn't work; you trip over the case - * where your SLIP or PPP link is down... - */ - /* warn the system administrator */ - if (open_warning_by_mail(ctl) == 0) - { - stuff_warning(iana_charset, ctl, - GT_("Subject: Fetchmail unreachable-server warning.")); - stuff_warning(NULL, ctl, ""); - stuff_warning(NULL, ctl, GT_("Fetchmail could not reach the mail server %s:"), - ctl->server.pollname); - stuff_warning(NULL, ctl, errbuf, ctl->server.pollname); - close_warning_by_mail(ctl, (struct msgblk *)NULL); - } -#endif } err = PS_SOCKET; set_timeout(0); @@ -1095,11 +1098,15 @@ static int do_session( set_timeout(mytimeout); /* perform initial SSL handshake on open connection */ - /* Note: We pass the realhost name over for certificate - verification. We may want to make this configurable */ - if (ctl->use_ssl && SSLOpen(mailserver_socket,ctl->sslcert,ctl->sslkey,ctl->sslproto,ctl->sslcertck, - ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1) + if (ctl->use_ssl && + SSLOpen(mailserver_socket, ctl->sslcert, ctl->sslkey, + ctl->sslproto, ctl->sslcertck, + ctl->sslcertfile, ctl->sslcertpath, + ctl->sslfingerprint, ctl->sslcommonname ? + ctl->sslcommonname : realhost, ctl->server.pollname, + &ctl->remotename) == -1) { + set_timeout(0); report(stderr, GT_("SSL connection failed.\n")); err = PS_SOCKET; goto cleanUp; @@ -1148,7 +1155,9 @@ static int do_session( stage = STAGE_GETAUTH; if (ctl->server.base_protocol->getauth) { + set_timeout(mytimeout); err = (ctl->server.base_protocol->getauth)(mailserver_socket, ctl, buf); + set_timeout(0); if (err != 0) { @@ -1167,6 +1176,9 @@ static int do_session( ctl->server.truename, (ctl->wehaveauthed ? GT_(" (previously authorized)") : "") ); + if (ctl->server.authenticate == A_ANY && !ctl->wehaveauthed) { + report(stderr, GT_("For help, see http://www.fetchmail.info/fetchmail-FAQ.html#R15\n")); + } /* * If we're running in background, try to mail the @@ -1289,9 +1301,11 @@ is restored.")); /* now iterate over each folder selected */ for (idp = ctl->mailboxes; idp; idp = idp->next) { + ctl->folder = idp->id; pass = 0; do { dispatches = 0; + transient_errors = 0; ++pass; /* reset timeout, in case we did an IDLE */ @@ -1308,6 +1322,7 @@ is restored.")); /* compute # of messages and number of new messages waiting */ stage = STAGE_GETRANGE; err = (ctl->server.base_protocol->getrange)(mailserver_socket, ctl, idp->id, &count, &newm, &bytes); + if (err != 0) goto cleanUp; /* show user how many messages we downloaded */ @@ -1396,7 +1411,7 @@ is restored.")); !(proto->getpartialsizes && NUM_NONZERO(ctl->fetchsizelimit))) { xfree(msgsizes); - msgsizes = xmalloc(sizeof(int) * count); + msgsizes = (int *)xmalloc(sizeof(int) * count); for (i = 0; i < count; i++) msgsizes[i] = 0; @@ -1420,10 +1435,20 @@ is restored.")); err = fetch_messages(mailserver_socket, ctl, count, &msgsizes, maxfetch, - &fetches, &dispatches, &deletions); + &fetches, &dispatches, &deletions, + &transient_errors); if (err != PS_SUCCESS && err != PS_MAXFETCH) goto cleanUp; + if (transient_errors > MAX_TRANSIENT_ERRORS) + { + if (outlevel > O_SILENT) + { + report(stderr, GT_("Too many mails skipped (%d > %d) due to transient errors for %s\n"), + transient_errors, MAX_TRANSIENT_ERRORS, buf); + } + } + if (!check_only && ctl->skipped && run.poll_interval > 0 && !nodetach) { @@ -1435,20 +1460,22 @@ is restored.")); /* end-of-mailbox processing before we repoll or switch to another one */ if (ctl->server.base_protocol->end_mailbox_poll) { - err = (ctl->server.base_protocol->end_mailbox_poll)(mailserver_socket, ctl); - if (err) + tmperr = (ctl->server.base_protocol->end_mailbox_poll)(mailserver_socket, ctl); + if (tmperr) { + err = tmperr; goto cleanUp; + } } /* Return now if we have reached the fetchlimit */ if (maxfetch && maxfetch <= fetches) goto no_error; } while /* - * Only re-poll if we either had some actual forwards and - * either allowed deletions and had no errors. + * Only repoll if we either had some actual forwards + * or are idling for new mails and had no errors. * Otherwise it is far too easy to get into infinite loops. */ - (dispatches && ctl->server.base_protocol->retry && !ctl->keep && !ctl->errcount); + (ctl->server.base_protocol->retry && (dispatches || ctl->idle) && !ctl->errcount); } /* XXX: From this point onwards, preserve err unless a new error has occurred */ @@ -1503,7 +1530,6 @@ is restored.")); smtp_close(ctl, 0); if (mailserver_socket != -1) { cleanupSockClose(mailserver_socket); - mailserver_socket = -1; } /* If there was a connect timeout, the socket should be closed. * mailserver_socket_temp contains the socket to close. @@ -1559,25 +1585,27 @@ is restored.")); closeUp: xfree(msgsizes); + ctl->folder = NULL; /* execute wrapup command, if any */ if (ctl->postconnect && (tmperr = system(ctl->postconnect))) { - report(stderr, GT_("post-connection command failed with status %d\n"), tmperr); + if (WIFSIGNALED(tmperr)) + report(stderr, GT_("post-connection command terminated with signal %d\n"), WTERMSIG(tmperr)); + else + report(stderr, GT_("post-connection command failed with status %d\n"), WEXITSTATUS(tmperr)); if (err == PS_SUCCESS) err = PS_SYNTAX; } set_timeout(0); /* cancel any pending alarm */ set_signal_handler(SIGALRM, alrmsave); - set_signal_handler(SIGPIPE, pipesave); return(err); } -int do_protocol(ctl, proto) -/* retrieve messages from server using given protocol method table */ -struct query *ctl; /* parsed options with merged-in defaults */ -const struct method *proto; /* protocol method table */ +/** retrieve messages from server using given protocol method table */ +int do_protocol(struct query *ctl /** parsed options with merged-in defaults */, + const struct method *proto /** protocol method table */) { int err;