]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
Added forcecr option.
[~andy/fetchmail] / driver.c
index 9cdf0939e2f9d835a756adce39759dda703048c6..701224f00cb0ecbf02d2a44a1d9111c9bb4ab9c0 100644 (file)
--- a/driver.c
+++ b/driver.c
@@ -26,6 +26,9 @@
 #if defined(HAVE_ALLOCA_H)
 #include <alloca.h>
 #endif
+#if defined(HAVE_SYS_ITIMER_H)
+#include <sys/itimer.h>
+#endif
 #include  <sys/time.h>
 #include  <signal.h>
 
@@ -54,7 +57,6 @@
 
 extern char *strstr(); /* needed on sysV68 R3V7.1. */
 
-int batchlimit;                /* how often to tear down the delivery connection */
 int fetchlimit;                /* how often to tear down the server connection */
 int batchcount;                /* count of messages sent in current batch */
 int peek_capable;      /* can we peek for better error recovery? */
@@ -69,7 +71,7 @@ static int tagnum;
 static char *shroud;   /* string to shroud in debug output, if  non-NULL */
 static int mytimeout;  /* value of nonreponse timeout */
 
-static void vtalarm(int timeleft)
+static void set_timeout(int timeleft)
 /* reset the nonresponse-timeout */
 {
     struct itimerval ntimeout;
@@ -77,15 +79,20 @@ static void vtalarm(int timeleft)
     ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
     ntimeout.it_value.tv_sec  = timeleft;
     ntimeout.it_value.tv_usec = 0;
-    setitimer(ITIMER_VIRTUAL, &ntimeout, (struct itimerval *)NULL);
+    setitimer(ITIMER_REAL, &ntimeout, (struct itimerval *)NULL);
 }
 
-static void vtalarm_handler (int signal)
-/* handle server-timeout SIGVTALARM signal */
+static void timeout_handler (int signal)
+/* handle server-timeout SIGALRM signal */
 {
     longjmp(restart, 1);
 }
 
+#define XMIT_ACCEPT            1
+#define XMIT_REJECT            2
+#define XMIT_ANTISPAM          3       
+static int accept_count, reject_count;
+
 #ifdef HAVE_RES_SEARCH
 #define MX_RETRIES     3
 
@@ -115,7 +122,7 @@ static int is_host_alias(const char *name, struct query *ctl)
        return(TRUE);
     else if (strcmp(name, ctl->server.canonical_name) == 0)
        return(TRUE);
-    else if (ctl->server.no_dns)
+    else if (!ctl->server.dns)
        return(FALSE);
 
     /*
@@ -147,7 +154,6 @@ static int is_host_alias(const char *name, struct query *ctl)
                "nameserver failure while looking for `%s' during poll of %s.",
                name, ctl->server.names->id);
            ctl->errcount++;
-           longjmp(restart, 2);        /* try again next poll cycle */
            break;
        }
 
@@ -173,7 +179,6 @@ static int is_host_alias(const char *name, struct query *ctl)
                "nameserver failure while looking for `%s' during poll of %s.",
                name, ctl->server.names->id);
            ctl->errcount++;
-           longjmp(restart, 2);        /* try again next poll cycle */
            break;
        }
     }
@@ -207,7 +212,8 @@ struct idlist **xmit_names; /* list of recipient names parsed out */
     {
        if (outlevel == O_VERBOSE)
            error(0, 0, "mapped %s to local %s", name, lname);
-       save_str(xmit_names, -1, lname);
+       save_str(xmit_names, XMIT_ACCEPT, lname);
+       accept_count++;
     }
 }
 
@@ -240,14 +246,15 @@ struct idlist **xmit_names;       /* list of recipient names parsed out */
                    {
                        char    *rhs;
 
-                       rhs = atsign + 1 + (strlen(atsign) - strlen(idp->id));
+                       rhs = atsign + (strlen(atsign) - strlen(idp->id));
                        if ((rhs[-1] == '.' || rhs[-1] == '@')
-                                       && strcmp(rhs, idp->id) == 0)
+                                       && strcasecmp(rhs, idp->id) == 0)
                        {
                            if (outlevel == O_VERBOSE)
                                error(0, 0, "passed through %s matching %s", 
                                      cp, idp->id);
-                           save_str(xmit_names, -1, cp);
+                           save_str(xmit_names, XMIT_ACCEPT, cp);
+                           accept_count++;
                            continue;
                        }
                    }
@@ -259,7 +266,11 @@ struct idlist **xmit_names;        /* list of recipient names parsed out */
                     * going and try to find a mapping to a client name.
                     */
                    if (!is_host_alias(atsign+1, ctl))
+                   {
+                       save_str(xmit_names, XMIT_REJECT, cp);
+                       reject_count++;
                        continue;
+                   }
                    atsign[0] = '\0';
                }
 
@@ -270,7 +281,7 @@ struct idlist **xmit_names; /* list of recipient names parsed out */
 }
 
 char *parse_received(struct query *ctl, char *bufp)
-/* try to extract */
+/* try to extract real addressee from the Received line */
 {
     char *ok;
     static char rbuf[HOSTLEN + USERNAMELEN + 4]; 
@@ -342,62 +353,74 @@ char *parse_received(struct query *ctl, char *bufp)
 static FILE *smtp_open(struct query *ctl)
 /* try to open a socket to the appropriate SMTP server for this query */ 
 {
-    struct query *lead;
-
-    lead = ctl->lead_smtp; /* go to the SMTP leader for this query */
+    struct idlist      *idp;
 
     /* maybe it's time to close the socket in order to force delivery */
-    if (batchlimit && lead->smtp_sockfp && batchcount++ == batchlimit)
+    if (ctl->batchlimit && ctl->smtp_sockfp && batchcount++ == ctl->batchlimit)
     {
-       fclose(lead->smtp_sockfp);
-       lead->smtp_sockfp = (FILE *)NULL;
+       fclose(ctl->smtp_sockfp);
+       ctl->smtp_sockfp = (FILE *)NULL;
        batchcount = 0;
     }
 
-    /* 
-     * 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.
-     *
-     * In fact this code relies on the RFC1123 requirement that the
-     * SMTP listener must accept messages even if verification of the
-     * HELO name fails (RFC1123 section 5.2.5, paragraph 2).
-     */
-
-    /* if no socket to this host is already set up, try to open ESMTP */
-    if (lead->smtp_sockfp == (FILE *)NULL)
+    /* run down the SMTP hunt list looking for a server that's up */
+    for (idp = ctl->smtphunt; idp; idp = idp->next)
     {
-       if ((lead->smtp_sockfp = SockOpen(lead->smtphost, SMTP_PORT)) == (FILE *)NULL)
-           return((FILE *)NULL);
-       else if (SMTP_ok(lead->smtp_sockfp) != SM_OK
-                || SMTP_ehlo(lead->smtp_sockfp, 
-                             ctl->server.names->id,
-                             &lead->server.esmtp_options) != SM_OK)
+       /* 
+        * 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.
+        *
+        * In fact this code relies on the RFC1123 requirement that the
+        * SMTP listener must accept messages even if verification of the
+        * HELO name fails (RFC1123 section 5.2.5, paragraph 2).
+        */
+
+       /* if no socket to this host is already set up, try to open ESMTP */
+       if (ctl->smtp_sockfp == (FILE *)NULL)
        {
-           /*
-            * RFC 1869 warns that some listeners hang up on a failed EHLO,
-            * so it's safest not to assume the socket will still be good.
-            */
-           fclose(lead->smtp_sockfp);
-           lead->smtp_sockfp = (FILE *)NULL;
+           if ((ctl->smtp_sockfp = SockOpen(idp->id,SMTP_PORT))==(FILE *)NULL)
+               continue;
+           else if (SMTP_ok(ctl->smtp_sockfp) != SM_OK
+                    || SMTP_ehlo(ctl->smtp_sockfp, 
+                                 ctl->server.names->id,
+                                 &ctl->server.esmtp_options) != SM_OK)
+           {
+               /*
+                * RFC 1869 warns that some listeners hang up on a failed EHLO,
+                * so it's safest not to assume the socket will still be good.
+                */
+               fclose(ctl->smtp_sockfp);
+               ctl->smtp_sockfp = (FILE *)NULL;
+           }
+           else
+           {
+               ctl->smtphost = idp->id;
+               break;
+           }
        }
-    }
 
-    /* if opening for ESMTP failed, try SMTP */
-    if (lead->smtp_sockfp == (FILE *)NULL)
-    {
-       if ((lead->smtp_sockfp = SockOpen(lead->smtphost, SMTP_PORT)) == (FILE *)NULL)
-           return((FILE *)NULL);
-       else if (SMTP_ok(lead->smtp_sockfp) != SM_OK
-                || SMTP_helo(lead->smtp_sockfp, ctl->server.names->id) != SM_OK)
+       /* if opening for ESMTP failed, try SMTP */
+       if (ctl->smtp_sockfp == (FILE *)NULL)
        {
-           fclose(lead->smtp_sockfp);
-           lead->smtp_sockfp = (FILE *)NULL;
+           if ((ctl->smtp_sockfp = SockOpen(idp->id,SMTP_PORT))==(FILE *)NULL)
+               continue;
+           else if (SMTP_ok(ctl->smtp_sockfp) != SM_OK
+                    || SMTP_helo(ctl->smtp_sockfp, ctl->server.names->id) != SM_OK)
+           {
+               fclose(ctl->smtp_sockfp);
+               ctl->smtp_sockfp = (FILE *)NULL;
+           }
+           else
+           {
+               ctl->smtphost = idp->id;
+               break;
+           }
        }
     }
 
-    return(lead->smtp_sockfp);
+    return(ctl->smtp_sockfp);
 }
 
 static int gen_readmsg(sockfp, len, delimited, ctl, realname)
@@ -410,8 +433,8 @@ char *realname;             /* real name of host */
 {
     char buf [MSGBUFSIZE+1]; 
     int        from_offs, to_offs, cc_offs, bcc_offs, ctt_offs, env_offs;
-    char *headers, *received_for;
-    int n, oldlen, ch, sizeticker, delete_ok;
+    char *headers, *received_for, *return_path;
+    int n, oldlen, ch, sizeticker, delete_ok, remaining;
     FILE *sinkfp;
     RETSIGTYPE (*sigchld)();
 #ifdef HAVE_GETHOSTBYNAME
@@ -423,12 +446,15 @@ char *realname;           /* real name of host */
 #ifdef HAVE_RES_SEARCH
     int                        no_local_matches = FALSE;
 #endif /* HAVE_RES_SEARCH */
+    int                        olderrs;
 
     sizeticker = 0;
     delete_ok = TRUE;
+    remaining = len;
+    olderrs = ctl->errcount;
 
     /* read message headers */
-    headers = received_for = NULL;
+    headers = received_for = return_path = NULL;
     from_offs = to_offs = cc_offs = bcc_offs = ctt_offs = env_offs = -1;
     oldlen = 0;
     for (;;)
@@ -440,7 +466,20 @@ char *realname;            /* real name of host */
        do {
            if (!SockGets(buf, sizeof(buf)-1, sockfp))
                return(PS_SOCKET);
-           vtalarm(ctl->server.timeout);
+
+           /* lines may not be properly CRLF terminated; fix this for qmail */
+           if (ctl->forcecr)
+           {
+               cp = buf + strlen(buf) - 1;
+               if (cp > buf && *cp == '\n' && cp[-1] != '\r')
+               {
+                   *cp++ = '\r';
+                   *cp++ = '\n';
+                   *cp++ = '\0';
+               }
+           }
+
+           set_timeout(ctl->server.timeout);
            /* leave extra room for reply_hack to play with */
            line = realloc(line, strlen(line) + strlen(buf) + HOSTLEN + 1);
            strcat(line, buf);
@@ -451,17 +490,17 @@ char *realname;           /* real name of host */
            ((ch = SockPeek(sockfp)) == ' ' || ch == '\t');
 
        /* write the message size dots */
-       if ((n = strlen(line)) > 0)
+       n = strlen(line);
+       if ((outlevel > O_SILENT && outlevel < O_VERBOSE) && n > 0)
        {
            sizeticker += n;
            while (sizeticker >= SIZETICKER)
            {
-               if (outlevel > O_SILENT)
-                   error_build(".");
+               error_build(".");
                sizeticker -= SIZETICKER;
            }
        }
-       len -= n;
+       remaining -= n;
 
        /* check for end of headers; don't save terminating line */
        if (line[0] == '\r' && line[1] == '\n')
@@ -470,7 +509,30 @@ char *realname;            /* real name of host */
            break;
        }
      
-       if (!ctl->no_rewrite)
+
+       /*
+        * OK, this is messy.  If we're forwarding by SMTP, it's the
+        * SMTP-receiver's job (according to RFC821, page 22, section
+        * 4.1.1) to generate a Return-Path line on final delivery.
+        * The trouble is, we've already got one because the
+        * mailserver's SMTP thought *it* was responsible for final
+        * delivery.
+        *
+        * Stash away the contents of Return-Path for use in generating
+        * MAIL FROM later on, then prevent the header from being saved
+        * with the others.  In effect, we strip it off here.
+        *
+        * If the SMTP server conforms to the standards, and fetchmail gets the
+        * envelope sender from the Return-Path, the new Return-Path should be
+        * exactly the same as the original one.
+        */
+       if (!ctl->mda && !strncasecmp("Return-Path:", line, 12))
+       {
+           return_path = xstrdup(nxtaddr(line));
+           continue;
+       }
+
+       if (ctl->rewrite)
            reply_hack(line, realname);
 
        if (!headers)
@@ -505,7 +567,8 @@ char *realname;             /* real name of host */
        else if (!strncasecmp("To:", line, 3))
            to_offs = (line - headers);
 
-       else if (env_offs == -1 && !strncasecmp(ctl->server.envelope,
+       else if (ctl->server.envelope != STRING_DISABLED && env_offs == -1
+                && !strncasecmp(ctl->server.envelope,
                                                line,
                                                strlen(ctl->server.envelope)))
            env_offs = (line - headers);
@@ -520,11 +583,28 @@ char *realname;           /* real name of host */
            ctt_offs = (line - headers);
 
 #ifdef HAVE_RES_SEARCH
-       else if (MULTIDROP(ctl) && !received_for && !strncasecmp("Received:", line, 9))
+       else if (ctl->server.envelope != STRING_DISABLED && MULTIDROP(ctl) && !received_for && !strncasecmp("Received:", line, 9))
            received_for = parse_received(ctl, line);
 #endif /* HAVE_RES_SEARCH */
     }
 
+    /*
+     * Hack time.  If the first line of the message was blank, with no headers
+     * (this happens occasionally due to bad gatewaying software) cons up
+     * a set of fake headers.  
+     *
+     * If you modify the fake header template below, be sure you don't
+     * make either From or To address @-less, otherwise the reply_hack
+     * logic will do bad things.
+     */
+    if (headers == (char *)NULL)
+    {
+       sprintf(buf, 
+       "From: <FETCHMAIL-DAEMON@%s>\r\nTo: %s@localhost\r\nSubject: Headerless mail from %s's mailbox on %s\r\n",
+               fetchmailhost, user, ctl->remotename, realname);
+       headers = xstrdup(buf);
+    }
+
     /*
      * We can now process message headers before reading the text.
      * In fact we have to, as this will tell us where to forward to.
@@ -532,7 +612,7 @@ char *realname;             /* real name of host */
 
     /* cons up a list of local recipients */
     xmit_names = (struct idlist *)NULL;
-    bad_addresses = good_addresses = 0;
+    bad_addresses = good_addresses = accept_count = reject_count = 0;
 #ifdef HAVE_RES_SEARCH
     /* is this a multidrop box? */
     if (MULTIDROP(ctl))
@@ -559,10 +639,10 @@ char *realname;           /* real name of host */
            if (bcc_offs > -1)
                find_server_names(headers + bcc_offs, ctl, &xmit_names);
        }
-       if (!xmit_names)
+       if (!accept_count)
        {
            no_local_matches = TRUE;
-           save_str(&xmit_names, -1, user);
+           save_str(&xmit_names, XMIT_ACCEPT, user);
            if (outlevel == O_VERBOSE)
                error(0, 0, 
                      "no local matches, forwarding to %s",
@@ -571,10 +651,20 @@ char *realname;           /* real name of host */
     }
     else       /* it's a single-drop box, use first localname */
 #endif /* HAVE_RES_SEARCH */
-       save_str(&xmit_names, -1, ctl->localnames->id);
+       save_str(&xmit_names, XMIT_ACCEPT, ctl->localnames->id);
+
 
-    /* time to address the message */
-    if (ctl->mda)      /* we have a declared MDA */
+    /*
+     * Time to either address the message or decide we can't deliver it yet.
+     */
+    if (ctl->errcount > olderrs)       /* there were DNS errors above */
+    {
+       delete_ok = FALSE;
+       sinkfp = (FILE *)NULL;
+       if (outlevel == O_VERBOSE)
+           error(0,0, "forwarding and deletion suppressed due to DNS errors");
+    }
+    else if (ctl->mda)         /* we have a declared MDA */
     {
        int     length = 0;
        char    *names, *cmd;
@@ -584,14 +674,16 @@ char *realname;           /* real name of host */
         * long lists of users and (re)implement %s.
         */
        for (idp = xmit_names; idp; idp = idp->next)
-           length += (strlen(idp->id) + 1);
+           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)
-       {
-           strcat(names, idp->id);
-           strcat(names, " ");
-       }
+           if (idp->val.num == XMIT_ACCEPT)
+           {
+               strcat(names, idp->id);
+               strcat(names, " ");
+           }
        cmd = (char *)alloca(strlen(ctl->mda) + length);
        sprintf(cmd, ctl->mda, names);
        if (outlevel == O_VERBOSE)
@@ -616,7 +708,7 @@ char *realname;             /* real name of host */
 
        if (!sinkfp)
        {
-           error(0, 0, "MDA open failed");
+           error(0, -1, "MDA open failed");
            return(PS_IOERR);
        }
 
@@ -631,7 +723,10 @@ char *realname;            /* real name of host */
        if (!ctl->mda && ((sinkfp = smtp_open(ctl)) == NULL))
        {
            free_str_list(&xmit_names);
-           error(0, 0, "SMTP connect failed");
+           error(0, -1, "SMTP connect to %s failed",
+                 ctl->smtphost ? ctl->smtphost : "localhost");
+           if (return_path)
+               free(return_path);
            return(PS_SMTP);
        }
 
@@ -653,11 +748,16 @@ char *realname;           /* real name of host */
            sprintf(options + strlen(options), " SIZE=%d", len);
 
        /*
-        * Try to get the SMTP listener to take the header
-        * From address as MAIL FROM (this makes the logging
-        * nicer).  If it won't, fall back on the calling-user
-        * ID.  This won't affect replies, which use the header
-        * From address anyway.
+        * If there is a Return-Path address on the message, this was
+        * almost certainly the MAIL FROM address given the originating
+        * sendmail.  This is the best thing to use for logging the
+        * message origin (it sets up the right behavior for bounces and
+        * mailing lists).  Otherwise, take the From address.
+        *
+        * Try to get the SMTP listener to take the Return-Path or
+        * From address as MAIL FROM .  If it won't, fall back on the
+        * calling-user ID.  This won't affect replies, which use the
+        * header From address anyway.
         *
         * RFC 1123 requires that the domain name part of the
         * MAIL FROM address be "canonicalized", that is a
@@ -666,21 +766,24 @@ char *realname;           /* real name of host */
         * is if rewrite is on).  RFC 1123 is silent on whether
         * a nonexistent hostname part is considered canonical.
         *
-        * This is a potential problem if the MTAs further
-        * upstream didn't pass canonicalized From lines, *and*
-        * the local SMTP listener insists on them.
+        * This is a potential problem if the MTAs further upstream
+        * didn't pass canonicalized From/Return-Path lines, *and* the
+        * local SMTP listener insists on them.
         */
-       if (from_offs == -1 || !(ap = nxtaddr(headers + from_offs)))
+       ap = (char *)NULL;
+       if (return_path)
+           ap = return_path;
+       else if (from_offs == -1 || !(ap = nxtaddr(headers + from_offs)))
            ap = user;
        if (SMTP_from(sinkfp, ap, options) != SM_OK)
        {
            int smtperr = atoi(smtp_response);
 
            if (smtperr >= 400)
-               error(0, 0, "SMTP error: %s", smtp_response);
+               error(0, -1, "SMTP error: %s", smtp_response);
 
            /*
-            * There'a one problem with this flow of control;
+            * There's one problem with this flow of control;
             * there's no way to avoid reading the whole message
             * off the server, even if the MAIL FROM response 
             * tells us that it's just to be discarded.  We could
@@ -727,7 +830,9 @@ char *realname;             /* real name of host */
            default:    /* retry with invoking user's address */
                if (SMTP_from(sinkfp, user, options) != SM_OK)
                {
-                   error(0,0,"SMTP error: %s", smtp_response);
+                   error(0, -1, "SMTP error: %s", smtp_response);
+                   if (return_path)
+                       free(return_path);
                    return(PS_SMTP);    /* should never happen */
                }
            }
@@ -744,19 +849,22 @@ char *realname;           /* real name of host */
         * problem.
         */
        for (idp = xmit_names; idp; idp = idp->next)
-           if (SMTP_rcpt(sinkfp, idp->id) == SM_OK)
-               good_addresses++;
-           else
-           {
-               bad_addresses++;
-               idp->val.num = 0;
-               error(0, 0, 
-                     "SMTP listener doesn't like recipient address `%s'", idp->id);
-           }
+           if (idp->val.num == XMIT_ACCEPT)
+               if (SMTP_rcpt(sinkfp, idp->id) == SM_OK)
+                   good_addresses++;
+               else
+               {
+                   bad_addresses++;
+                   idp->val.num = XMIT_ANTISPAM;
+                   error(0, 0, 
+                         "SMTP listener doesn't like recipient address `%s'", idp->id);
+               }
        if (!good_addresses && SMTP_rcpt(sinkfp, user) != SM_OK)
        {
            error(0, 0, 
                  "can't even send to calling user!");
+           if (return_path)
+               free(return_path);
            return(PS_SMTP);
        }
 
@@ -764,6 +872,8 @@ char *realname;             /* real name of host */
        SMTP_data(sinkfp);
 
     skiptext:;
+       if (return_path)
+           free(return_path);
     }
 
     /* we may need to strip carriage returns */
@@ -819,7 +929,16 @@ char *realname;            /* real name of host */
 #ifdef HAVE_RES_SEARCH
        if (no_local_matches)
        {
-           strcat(errhd, "no recipient addresses matched declared local names");
+           if (reject_count != 1)
+               strcat(errhd, "no recipient addresses matched declared local names");
+           else
+           {
+               for (idp = xmit_names; idp; idp = idp->next)
+                   if (idp->val.num == XMIT_REJECT)
+                       break;
+               sprintf(errhd+strlen(errhd), "recipient address %s didn't match any local name", idp->id);
+           }
+
            if (bad_addresses)
                strcat(errhd, "; ");
        }
@@ -830,13 +949,13 @@ char *realname;           /* real name of host */
            strcat(errhd, "SMTP listener rejected local recipient addresses: ");
            errlen = strlen(errhd);
            for (idp = xmit_names; idp; idp = idp->next)
-               if (!idp->val.num)
+               if (idp->val.num == XMIT_ANTISPAM)
                    errlen += strlen(idp->id) + 2;
 
            errmsg = alloca(errlen+3);
            (void) strcpy(errmsg, errhd);
            for (idp = xmit_names; idp; idp = idp->next)
-               if (!idp->val.num)
+               if (idp->val.num == XMIT_ANTISPAM)
                {
                    strcat(errmsg, idp->id);
                    if (idp->next)
@@ -883,11 +1002,11 @@ char *realname;          /* real name of host */
      */
 
     /* pass through the text lines */
-    while (delimited || len > 0)
+    while (delimited || remaining > 0)
     {
        if (!SockGets(buf, sizeof(buf)-1, sockfp))
            return(PS_SOCKET);
-       vtalarm(ctl->server.timeout);
+       set_timeout(ctl->server.timeout);
 
        /* write the message size dots */
        if ((n = strlen(buf)) > 0)
@@ -900,7 +1019,19 @@ char *realname;           /* real name of host */
                sizeticker -= SIZETICKER;
            }
        }
-       len -= n;
+       remaining -= n;
+
+       /* fix messages that have only \n line-termination (for qmail) */
+       if (ctl->forcecr)
+       {
+           cp = buf + strlen(buf) - 1;
+           if (cp > buf && *cp == '\n' && cp[-1] != '\r')
+           {
+               *cp++ = '\r';
+               *cp++ = '\n';
+               *cp++ = '\0';
+           }
+       }
 
        /* check for end of message */
        if (delimited && *buf == '.')
@@ -965,7 +1096,7 @@ char *realname;            /* real name of host */
        signal(SIGCHLD, sigchld);
        if (rc)
        {
-           error(0, 0, "MDA exited abnormally or returned nonzero status");
+           error(0, -1, "MDA exited abnormally or returned nonzero status");
            return(PS_IOERR);
        }
     }
@@ -974,7 +1105,8 @@ char *realname;            /* real name of host */
        /* write message terminator */
        if (SMTP_eom(sinkfp) != SM_OK)
        {
-           error(0, 0, "SMTP listener refused delivery");
+           error(0, -1, "SMTP listener refused delivery");
+           ctl->errcount++;
            return(PS_TRANSIENT);
        }
     }
@@ -1010,7 +1142,7 @@ const char *canonical;    /* server name */
     free (ticket);
     if (rem != KSUCCESS)
     {
-       error(0, 0, "kerberos error %s", (krb_get_err_text (rem)));
+       error(0, -1, "kerberos error %s", (krb_get_err_text (rem)));
        return (PS_ERROR);
     }
     return (0);
@@ -1029,7 +1161,7 @@ const struct method *proto;       /* protocol method table */
 #ifndef KERBEROS_V4
     if (ctl->server.authenticate == A_KERBEROS)
     {
-       error(0, 0, "Kerberos support not linked.");
+       error(0, -1, "Kerberos support not linked.");
        return(PS_ERROR);
     }
 #endif /* KERBEROS_V4 */
@@ -1066,8 +1198,8 @@ const struct method *proto;       /* protocol method table */
     error_init(poll_interval == 0 && !logfile);
 
     /* set up the server-nonresponse timeout */
-    sigsave = signal(SIGVTALRM, vtalarm_handler);
-    vtalarm(mytimeout = ctl->server.timeout);
+    sigsave = signal(SIGALRM, timeout_handler);
+    set_timeout(mytimeout = ctl->server.timeout);
 
     if ((js = setjmp(restart)) == 1)
     {
@@ -1076,11 +1208,6 @@ const struct method *proto;      /* protocol method table */
                ctl->server.timeout, ctl->server.names->id);
        ok = PS_ERROR;
     }
-    else if (js == 2)
-    {
-       /* error message printed at point of longjmp */
-       ok = PS_ERROR;
-    }
     else
     {
        char buf [POPBUFSIZE+1];
@@ -1102,19 +1229,19 @@ const struct method *proto;     /* protocol method table */
 #ifndef EHOSTUNREACH
 #define EHOSTUNREACH (-1)
 #endif
-           if (errno != EHOSTUNREACH)
+           if (outlevel == O_VERBOSE || errno != EHOSTUNREACH)
                error(0, errno, "connecting to host");
            ok = PS_SOCKET;
            goto closeUp;
        }
 
 #ifdef KERBEROS_V4
-       if (ctl->authenticate == A_KERBEROS)
+       if (ctl->server.authenticate == A_KERBEROS)
        {
            ok = kerberos_auth(fileno(sockfp), ctl->server.canonical_name);
            if (ok != 0)
                goto cleanUp;
-           vtalarm(ctl->server.timeout);
+           set_timeout(ctl->server.timeout);
        }
 #endif /* KERBEROS_V4 */
 
@@ -1122,7 +1249,7 @@ const struct method *proto;       /* protocol method table */
        ok = (protocol->parse_response)(sockfp, buf);
        if (ok != 0)
            goto cleanUp;
-       vtalarm(ctl->server.timeout);
+       set_timeout(ctl->server.timeout);
 
        /*
         * Try to parse the host's actual name out of the greeting
@@ -1200,29 +1327,36 @@ const struct method *proto;     /* protocol method table */
            strcpy(realname, ctl->server.names->id);
 
        /* try to get authorized to fetch mail */
-       shroud = ctl->password;
-       ok = (protocol->getauth)(sockfp, ctl, buf);
-       shroud = (char *)NULL;
-       if (ok == PS_ERROR)
-           ok = PS_AUTHFAIL;
-       if (ok != 0)
+       if (protocol->getauth)
        {
-           error(0, 0, "Authorization failure on %s@%s", 
-                 ctl->remotename,
-                 realname);
-           goto cleanUp;
+           shroud = ctl->password;
+           ok = (protocol->getauth)(sockfp, ctl, buf);
+           shroud = (char *)NULL;
+           if (ok == PS_ERROR)
+               ok = PS_AUTHFAIL;
+           if (ok != 0)
+           {
+               error(0, -1, "Authorization failure on %s@%s", 
+                     ctl->remotename,
+                     realname);
+               goto cleanUp;
+           }
+           set_timeout(ctl->server.timeout);
        }
-       vtalarm(ctl->server.timeout);
 
        /* compute number of messages and number of new messages waiting */
        ok = (protocol->getrange)(sockfp, ctl, &count, &new);
        if (ok != 0)
            goto cleanUp;
-       vtalarm(ctl->server.timeout);
+       set_timeout(ctl->server.timeout);
 
        /* show user how many messages we downloaded */
        if (outlevel > O_SILENT)
-           if (count == 0)
+           if (count == -1)                    /* only used for ETRN */
+               error(0, 0, "Polling %s@%s", 
+                       ctl->remotename,
+                       realname);
+           else if (count == 0)
                error(0, 0, "No mail at %s@%s", 
                        ctl->remotename,
                        realname);
@@ -1249,7 +1383,7 @@ const struct method *proto;       /* protocol method table */
            ok = (proto->getsizes)(sockfp, count, msgsizes);
            if (ok != 0)
                goto cleanUp;
-           vtalarm(ctl->server.timeout);
+           set_timeout(ctl->server.timeout);
        }
 
        if (check_only)
@@ -1261,6 +1395,8 @@ const struct method *proto;       /* protocol method table */
        }
        else if (count > 0)
        {    
+           int force_retrieval, fetches;
+
            /*
             * What forces this code is that in POP3 and IMAP2BIS you can't
             * fetch a message without having it marked `seen'.  In IMAP4,
@@ -1283,9 +1419,9 @@ const struct method *proto;       /* protocol method table */
             * previous pass and forcing all messages to be considered new
             * if it's nonzero.
             */
-           int force_retrieval = !peek_capable && (ctl->errcount > 0);
+           force_retrieval = !peek_capable && (ctl->errcount > 0);
 
-           ctl->errcount = 0;
+           ctl->errcount = fetches = 0;
 
            /* read, forward, and delete messages */
            for (num = 1; num <= count; num++)
@@ -1311,7 +1447,7 @@ const struct method *proto;       /* protocol method table */
                    ok = (protocol->fetch)(sockfp, ctl, num, &len);
                    if (ok != 0)
                        goto cleanUp;
-                   vtalarm(ctl->server.timeout);
+                   set_timeout(ctl->server.timeout);
 
                    if (outlevel > O_SILENT)
                    {
@@ -1334,7 +1470,7 @@ const struct method *proto;       /* protocol method table */
                        suppress_delete = TRUE;
                    else if (ok)
                        goto cleanUp;
-                   vtalarm(ctl->server.timeout);
+                   set_timeout(ctl->server.timeout);
 
                    /* tell the server we got it OK and resynchronize */
                    if (protocol->trail)
@@ -1342,8 +1478,10 @@ const struct method *proto;      /* protocol method table */
                        ok = (protocol->trail)(sockfp, ctl, num);
                        if (ok != 0)
                            goto cleanUp;
-                       vtalarm(ctl->server.timeout);
+                       set_timeout(ctl->server.timeout);
                    }
+
+                   fetches++;
                }
 
                /*
@@ -1366,21 +1504,21 @@ const struct method *proto;     /* protocol method table */
                    ok = (protocol->delete)(sockfp, ctl, num);
                    if (ok != 0)
                        goto cleanUp;
-                   vtalarm(ctl->server.timeout);
+                   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 && ctl->fetchlimit <= num)
+               if (ctl->fetchlimit && ctl->fetchlimit <= fetches)
                    break;
            }
 
            ok = gen_transact(sockfp, protocol->exit_cmd);
            if (ok == 0)
-               ok = PS_SUCCESS;
-           vtalarm(0);
+               ok = (fetches > 0) ? PS_SUCCESS : PS_NOMAIL;
+           set_timeout(0);
            fclose(sockfp);
            goto closeUp;
        }
@@ -1388,16 +1526,16 @@ const struct method *proto;     /* protocol method table */
            ok = gen_transact(sockfp, protocol->exit_cmd);
            if (ok == 0)
                ok = PS_NOMAIL;
-           vtalarm(0);
+           set_timeout(0);
            fclose(sockfp);
            goto closeUp;
        }
 
     cleanUp:
-       vtalarm(ctl->server.timeout);
+       set_timeout(ctl->server.timeout);
        if (ok != 0 && ok != PS_SOCKET)
            gen_transact(sockfp, protocol->exit_cmd);
-       vtalarm(0);
+       set_timeout(0);
        fclose(sockfp);
     }
 
@@ -1430,10 +1568,10 @@ const struct method *proto;     /* protocol method table */
     }
     if (ok==PS_SOCKET || ok==PS_AUTHFAIL || ok==PS_SYNTAX || ok==PS_IOERR
                || ok==PS_ERROR || ok==PS_PROTOCOL || ok==PS_SMTP)
-       error(0, 0, "%s error while fetching from %s", msg, ctl->server.names->id);
+       error(0, -1, "%s error while fetching from %s", msg, ctl->server.names->id);
 
 closeUp:
-    signal(SIGVTALRM, sigsave);
+    signal(SIGALRM, sigsave);
     return(ok);
 }
 
@@ -1471,7 +1609,7 @@ va_dcl
     {
        char *cp;
 
-       if (shroud && (cp = strstr(buf, shroud)))
+       if (shroud && shroud[0] && (cp = strstr(buf, shroud)))
        {
            char        *sp;
 
@@ -1479,7 +1617,7 @@ va_dcl
            *cp++ = '*';
            while (*sp)
                *cp++ = *sp++;
-           *sp = '\0';
+           *cp = '\0';
        }
        buf[strlen(buf)-2] = '\0';
        error(0, 0, "%s> %s", protocol->name, buf);
@@ -1541,7 +1679,7 @@ va_dcl
     {
        char *cp;
 
-       if (shroud && (cp = strstr(buf, shroud)))
+       if (shroud && shroud[0] && (cp = strstr(buf, shroud)))
        {
            char        *sp;
 
@@ -1549,7 +1687,7 @@ va_dcl
            *cp++ = '*';
            while (*sp)
                *cp++ = *sp++;
-           *sp = '\0';
+           *cp = '\0';
        }
        buf[strlen(buf)-1] = '\0';
        error(0, 0, "%s> %s", protocol->name, buf);
@@ -1557,10 +1695,9 @@ va_dcl
 
     /* we presume this does its own response echoing */
     ok = (protocol->parse_response)(sockfp, buf);
-    vtalarm(mytimeout);
+    set_timeout(mytimeout);
 
     return(ok);
 }
 
 /* driver.c ends here */
-