]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
Note Earl's regression fix for SSL_CTX_clear_options() on older OpenSSL.
[~andy/fetchmail] / driver.c
index 32f752db6aeb3cc37d8ba6d1c29a83ba0a2f0bec..c2917268cedfce70fa9d70f085bad27b7f4f201b 100644 (file)
--- a/driver.c
+++ b/driver.c
 #endif
 #include <netdb.h>
 #ifdef HAVE_PKG_hesiod
+#ifdef __cplusplus
+extern "C" {
+#endif
 #include <hesiod.h>
+#ifdef __cplusplus
+}
+#endif
 #endif
 
 #include <langinfo.h>
 #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;
@@ -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);
                }
            }
 
@@ -762,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
@@ -793,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? */
@@ -845,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;
@@ -859,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;
        }
-       else if (js == THROW_TIMEOUT)
+
+       if (ai1) {
+           fm_freeaddrinfo(ai1); ai1 = NULL;
+       }
+       
+       if (js == THROW_TIMEOUT)
        {
            if (phase == OPEN_WAIT)
                report(stdout,
@@ -942,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;
        }
@@ -1016,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;
@@ -1043,7 +1054,7 @@ static int do_session(
                    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);
                }
            }
        }
@@ -1055,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;
@@ -1072,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);
@@ -1106,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;
@@ -1159,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)
            {
@@ -1178,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
@@ -1304,6 +1305,7 @@ is restored."));
            pass = 0;
            do {
                dispatches = 0;
+               transient_errors = 0;
                ++pass;
 
                /* reset timeout, in case we did an IDLE */
@@ -1433,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)
                    {
@@ -1448,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 */
@@ -1516,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.
@@ -1577,14 +1590,16 @@ closeUp:
     /* 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);
 }