]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
Before showdots,
[~andy/fetchmail] / driver.c
index 0e63c19d6864578cb8561d1e355cfbb111795550..e0b86d6cd056d1c106611599f5258db85782e2b5 100644 (file)
--- a/driver.c
+++ b/driver.c
@@ -3,8 +3,6 @@
  *
  * Copyright 1997 by Eric S. Raymond
  * For license terms, see the file COPYING in this directory.
- *
- * i18n by Arnaldo Carvalho de Melo <acme@conectiva.com.br> 7-Nov-1998
  */
 
 #include  "config.h"
 #include  <sys/time.h>
 #include  <signal.h>
 
+#ifdef HAVE_NET_SOCKET_H
+#include <net/socket.h>
+#endif
+
 #ifdef HAVE_RES_SEARCH
 #include <netdb.h>
 #include "mx.h"
 #include "fetchmail.h"
 #include "tunable.h"
 
+/* throw types for runtime errors */
+#define THROW_TIMEOUT  1               /* server timed out */
+#define THROW_SIGPIPE  2               /* SIGPIPE on stream socket */
+
 #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? */
+int stage;             /* where are we? */
 int phase;             /* where are we, for error-logging purposes? */
+int mytimeout;         /* value of nonreponse timeout */
 
 static const struct method *protocol;
 static jmp_buf restart;
@@ -89,14 +96,13 @@ static int tagnum;
 #define GENSYM (sprintf(tag, "A%04d", ++tagnum % TAGMOD), tag)
 
 static char shroud[PASSWORDLEN];       /* string to shroud in debug output */
-static int mytimeout;                  /* value of nonreponse timeout */
 static int timeoutcount;               /* count consecutive timeouts */
 static int msglen;                     /* actual message length */
 
 void set_timeout(int timeleft)
 /* reset the nonresponse-timeout */
 {
-#ifndef __EMX__
+#if !defined(__EMX__) && !defined(__BEOS__) 
     struct itimerval ntimeout;
 
     if (timeleft == 0)
@@ -110,10 +116,16 @@ void set_timeout(int timeleft)
 }
 
 static void timeout_handler (int signal)
-/* handle server-timeout SIGALRM signal */
+/* handle SIGALRM signal indicating a server timeout */
 {
     timeoutcount++;
-    longjmp(restart, 1);
+    longjmp(restart, THROW_TIMEOUT);
+}
+
+static void sigpipe_handler (int signal)
+/* handle SIGPIPE signal indicating a broken stream socket */
+{
+    longjmp(restart, THROW_SIGPIPE);
 }
 
 static int accept_count, reject_count;
@@ -134,13 +146,15 @@ static void map_name(const char *name, struct query *ctl, struct idlist **xmit_n
     if (lname != (char *)NULL)
     {
        if (outlevel >= O_DEBUG)
-           error(0, 0, _("mapped %s to local %s"), name, lname);
+           report(stdout, _("mapped %s to local %s\n"), name, lname);
        save_str(xmit_names, lname, XMIT_ACCEPT);
        accept_count++;
     }
 }
 
-void find_server_names(const char *hdr, struct query *ctl, struct idlist **xmit_names)
+static void find_server_names(const char *hdr,
+                             struct query *ctl,
+                             struct idlist **xmit_names)
 /* parse names out of a RFC822 header into an ID list */
 /*   hdr:              RFC822 header in question */
 /*   ctl:              list of permissible aliases */
@@ -189,7 +203,7 @@ void find_server_names(const char *hdr, struct query *ctl, struct idlist **xmit_
                        strcasecmp(rhs, idp->id) == 0)
                    {
                        if (outlevel >= O_DEBUG)
-                           error(0, 0, _("passed through %s matching %s"), 
+                           report(stdout, _("passed through %s matching %s\n"), 
                                  cp, idp->id);
                        save_str(xmit_names, cp, XMIT_ACCEPT);
                        accept_count++;
@@ -221,6 +235,19 @@ void find_server_names(const char *hdr, struct query *ctl, struct idlist **xmit_
     }
 }
 
+/*
+ * Return zero on a syntactically invalid address, nz on a valid one.
+ *
+ * This used to be strchr(a, '.'), but it turns out that lines like this
+ *
+ * Received: from punt-1.mail.demon.net by mailstore for markb@ordern.com
+ *          id 938765929:10:27223:2; Fri, 01 Oct 99 08:18:49 GMT
+ *
+ * are not uncommon.  So now we just check that the following token is
+ * not itself an email address.
+ */
+#define VALID_ADDRESS(a)       !strchr(a, '@')
+
 static char *parse_received(struct query *ctl, char *bufp)
 /* try to extract real address from the Received line */
 /* If a valid Received: line is found, we return the full address in
@@ -231,11 +258,9 @@ static char *parse_received(struct query *ctl, char *bufp)
  * which makes nxtaddr() behave correctly. 
  */
 {
-    char *ok = (char *)NULL;
+    char *base, *ok = (char *)NULL;
     static char rbuf[HOSTLEN + USERNAMELEN + 4]; 
 
-    if (outlevel >= O_DEBUG)
-       error(0, 0, _("analyzing Received line:\n%s"), bufp);
     /*
      * Try to extract the real envelope addressee.  We look here
      * specifically for the mailserver's Received line.
@@ -244,18 +269,37 @@ 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 ")) && isspace(ok[-1]))
-    {
-       char    *sp, *tp;
+    if (outlevel >= O_DEBUG)
+       report(stdout, _("analyzing Received line:\n%s"), bufp);
 
-       /* extract space-delimited token after "by " */
-       for (sp = ok + 3; isspace(*sp); sp++)
+    /* search for whitepace-surrounded "by" followed by valid address */
+    for (base = bufp;  ; base = ok + 2)
+    {
+       if (!(ok = strstr(base, "by")))
+           break;
+       else if (!isspace(ok[-1]) || !isspace(ok[2]))
            continue;
-       tp = rbuf;
-       for (; !isspace(*sp); sp++)
-           *tp++ = *sp;
-       *tp = '\0';
+       else
+       {
+           char        *sp, *tp;
+
+           /* extract space-delimited token after "by" */
+           for (sp = ok + 2; isspace(*sp); sp++)
+               continue;
+           tp = rbuf;
+           for (; !isspace(*sp); sp++)
+               *tp++ = *sp;
+           *tp = '\0';
 
+           /* look for valid address */
+           if (VALID_ADDRESS(rbuf))
+               break;
+           else
+               ok = sp - 1;    /* arrange to skip this token */
+       }
+    }
+    if (ok)
+    {
        /*
         * If it's a DNS name of the mail server, look for the
         * recipient name after a following "for".  Otherwise
@@ -264,21 +308,47 @@ static char *parse_received(struct query *ctl, char *bufp)
        if (is_host_alias(rbuf, ctl))
        {
            if (outlevel >= O_DEBUG)
-               error(0, 0
-                     _("line accepted, %s is an alias of the mailserver"), rbuf);
+               report(stdout
+                     _("line accepted, %s is an alias of the mailserver\n"), rbuf);
        }
        else
        {
            if (outlevel >= O_DEBUG)
-               error(0, 0
-                     _("line rejected, %s is not an alias of the mailserver"), 
+               report(stdout
+                     _("line rejected, %s is not an alias of the mailserver\n"), 
                      rbuf);
            return(NULL);
        }
 
-       if ((ok = strstr(sp, "for")) && isspace(ok[3]) && isspace(ok[-1]))
+       /* search for whitepace-surrounded "for" followed by xxxx@yyyy */
+       for (base = ok + 4 + strlen(rbuf);  ; base = ok + 2)
+       {
+           if (!(ok = strstr(base, "for")))
+               break;
+           else if (!isspace(ok[-1]) || !isspace(ok[3]))
+               continue;
+           else
+           {
+               char    *sp, *tp;
+
+               /* extract space-delimited token after "for" */
+               for (sp = ok + 3; isspace(*sp); sp++)
+                   continue;
+               tp = rbuf;
+               for (; !isspace(*sp); sp++)
+                   *tp++ = *sp;
+               *tp = '\0';
+
+               if (strchr(rbuf, '@'))
+                   break;
+               else
+                   ok = sp - 1;        /* arrange to skip this token */
+           }
+       }
+       if (ok)
        {
            flag        want_gt = FALSE;
+           char        *sp, *tp;
 
            /* char after "for" could be space or a continuation newline */
            for (sp = ok + 4; isspace(*sp); sp++)
@@ -316,7 +386,7 @@ static char *parse_received(struct query *ctl, char *bufp)
     if (!ok)
     {
        if (outlevel >= O_DEBUG)
-           error(0, 0, _("no Received address found"));
+           report(stdout, _("no Received address found\n"));
        return(NULL);
     }
     else
@@ -325,7 +395,7 @@ static char *parse_received(struct query *ctl, char *bufp)
            char *lf = rbuf + strlen(rbuf)-1;
            *lf = '\0';
            if (outlevel >= O_DEBUG)
-               error(0, 0, _("found Received address `%s'"), rbuf+2);
+               report(stdout, _("found Received address `%s'\n"), rbuf+2);
            *lf = '\n';
        }
        return(rbuf);
@@ -338,7 +408,11 @@ static struct msgblk msgblk;
 
 #define EMPTYLINE(s)   ((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0')
 
-static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl, int num)
+static int readheaders(int sock,
+                      long fetchlen,
+                      long reallen,
+                      struct query *ctl,
+                      int num)
 /* read message headers and ship to SMTP or MDA */
 /*   sock:             to which the server is connected */
 /*   fetchlen:         length of message according to fetch response */
@@ -360,7 +434,7 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     int                        from_offs, reply_to_offs, resent_from_offs;
     int                        app_from_offs, sender_offs, resent_sender_offs;
     int                        env_offs;
-    char               *received_for, *rcv, *cp;
+    char               *received_for, *rcv, *cp, *delivered_to;
     int                n, linelen, oldlen, ch, remaining, skipcount;
     struct idlist      *idp;
     flag               no_local_matches = FALSE;
@@ -374,7 +448,17 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
 
     /* read message headers */
     msgblk.reallen = reallen;
-    msgblk.headers = received_for = NULL;
+
+    /*
+     * We used to free the header block unconditionally at the end of 
+     * readheaders, but it turns out that if close_sink() hits an error
+     * condition the code for sending bouncemail will actually look
+     * at the freed storage and coredump...
+     */
+    if (msgblk.headers)
+       free(msgblk.headers);
+
+    msgblk.headers = received_for = delivered_to = NULL;
     from_offs = reply_to_offs = resent_from_offs = app_from_offs = 
        sender_offs = resent_sender_offs = env_offs = -1;
     oldlen = 0;
@@ -413,6 +497,13 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
                }
            }
 
+           /*
+            * Decode MIME encoded headers. We MUST do this before
+            * looking at the Content-Type / Content-Transfer-Encoding
+            * headers (RFC 2046).
+            */
+           if (ctl->mimedecode)
+               UnMimeHeader(buf);
 
            line = (char *) realloc(line, strlen(line) + strlen(buf) +1);
 
@@ -452,7 +543,11 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
            sizeticker += linelen;
            while (sizeticker >= SIZETICKER)
            {
-               error_build(".");
+               if (!run.use_syslog && !isafile(1))
+               {
+                   fputc('.', stdout);
+                   fflush(stdout);
+               }
                sizeticker -= SIZETICKER;
            }
        }
@@ -460,6 +555,45 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
        /* we see an ordinary (non-header, non-message-delimiter line */
        has_nuls = (linelen != strlen(line));
 
+       /*
+        * When mail delivered to a multidrop mailbox on the server is
+        * addressed to multiple people on the client machine, there
+        * will be one copy left in the box for each recipient.  Thus,
+        * if the mail is addressed to N people, each recipient will
+        * get N copies.  This is bad when N > 1.
+        *
+        * Foil this by suppressing all but one copy of a message with
+        * a given Message-ID.  The accept_count test ensures that
+        * multiple pieces of email with the same Message-ID, each
+        * with a *single* addressee (the N == 1 case), won't be 
+        * suppressed.
+        *
+        * Note: This implementation only catches runs of successive
+        * messages with the same ID, but that should be good
+        * enough. A more general implementation would have to store
+        * ever-growing lists of seen message-IDs; in a long-running
+        * daemon this would turn into a memory leak even if the 
+        * implementation were perfect.
+        * 
+        * Don't mess with this code casually.  It would be way too easy
+        * to break it in a way that blackholed mail.  Better to pass
+        * the occasional duplicate than to do that...
+        */
+       if (MULTIDROP(ctl) && !strncasecmp(line, "Message-ID:", 11))
+       {
+           if (ctl->lastid && !strcasecmp(ctl->lastid, line))
+           {
+               if (accept_count > 1)
+                   return(PS_REFUSED);
+           }
+           else
+           {
+               if (ctl->lastid)
+                   free(ctl->lastid);
+               ctl->lastid = strdup(line);
+           }
+       }
+
        /*
         * The University of Washington IMAP server (the reference
         * implementation of IMAP4 written by Mark Crispin) relies
@@ -474,7 +608,11 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
         * forward it to the user so he or she will have some clue
         * that things have gone awry.
         */
+#if INET6_ENABLE
+       if (strncmp(protocol->service, "pop2", 4))
+#else /* INET6_ENABLE */
        if (protocol->port != 109)
+#endif /* INET6_ENABLE */
 #endif /* POP2_ENABLE */
            if (num == 1 && !strncasecmp(line, "X-IMAP:", 7)) {
                free(line);
@@ -509,6 +647,20 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
            continue;
        }
 
+       /*
+        * We remove all Delivered-To: headers.
+        * 
+        * This is to avoid false mail loops messages when delivering
+        * local messages to and from a Postfix/qmail mailserver. 
+        * 
+        * Should be controlled by an option
+        */
+       if (ctl->dropdelivered && !strncasecmp(line, "Delivered-To:", 13)) {
+      if (delivered_to) free(line);
+      else delivered_to = line;
+         continue;
+       }
+
        /*
         * If we see a Status line, it may have been inserted by an MUA
         * on the mail host, or it may have been inserted by the server
@@ -613,14 +765,15 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
        else if (!strncasecmp("Resent-Sender:", line, 14))
            resent_sender_offs = (line - msgblk.headers);
 
-       else if (!strncasecmp("Message-Id:", buf, 11))
+#ifdef __UNUSED__
+       else if (!strncasecmp("Message-Id:", line, 11))
        {
            if (ctl->server.uidl)
            {
                char id[IDLEN+1];
 
-               buf[IDLEN+12] = 0;              /* prevent stack overflow */
-               sscanf(buf+12, "%s", id);
+               line[IDLEN+12] = 0;             /* prevent stack overflow */
+               sscanf(line+12, "%s", id);
                if (!str_find( &ctl->newsaved, num))
                {
                    struct idlist *new = save_str(&ctl->newsaved,id,UID_SEEN);
@@ -628,6 +781,7 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
                }
            }
        }
+#endif /* __UNUSED__ */
 
        else if (!MULTIDROP(ctl))
            continue;
@@ -684,7 +838,8 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     if (!headers_ok)
     {
        if (outlevel > O_SILENT)
-           error(0,0,_("message delimiter found while scanning headers"));
+           report(stdout,
+                  _("message delimiter found while scanning headers\n"));
     }
 
     /*
@@ -713,16 +868,19 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
      * In fact we have to, as this will tell us where to forward to.
      */
 
-    /* Decode MIME encoded headers. We MUST do this before
-     * looking at the Content-Type / Content-Transfer-Encoding
-     * headers (RFC 2046).
-     */
-    if (ctl->mimedecode) {
-       UnMimeHeader(msgblk.headers);
-    }
     /* Check for MIME headers indicating possible 8-bit data */
     ctl->mimemsg = MimeBodyType(msgblk.headers, ctl->mimedecode);
 
+#ifdef SDPS_ENABLE
+    if (ctl->server.sdps && sdps_envfrom)
+    {
+       /* We have the real envelope return-path, stored out of band by
+        * SDPS - that's more accurate than any header is going to be.
+        */
+       strcpy(msgblk.return_path, sdps_envfrom);
+       free(sdps_envfrom);
+    } else
+#endif /* SDPS_ENABLE */
     /*
      * If there is a Return-Path address on the message, this was
      * almost certainly the MAIL FROM address given the originating
@@ -755,8 +913,24 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     /* is this a multidrop box? */
     if (MULTIDROP(ctl))
     {
+#ifdef SDPS_ENABLE
+       if (ctl->server.sdps && sdps_envto)
+       {
+           /* We have the real envelope recipient, stored out of band by
+            * SDPS - that's more accurate than any header is going to be.
+            */
+           find_server_names(sdps_envto, ctl, &msgblk.recipients);
+           free(sdps_envto);
+       } else
+#endif /* SDPS_ENABLE */ 
        if (env_offs > -1)          /* We have the actual envelope addressee */
            find_server_names(msgblk.headers + env_offs, ctl, &msgblk.recipients);
+       else if (delivered_to && ctl->server.envelope != STRING_DISABLED &&
+      ctl->server.envelope && !strcasecmp(ctl->server.envelope, "Delivered-To"))
+   {
+           find_server_names(delivered_to, ctl, &msgblk.recipients);
+       free(delivered_to);
+   }
        else if (received_for)
            /*
             * We have the Received for addressee.  
@@ -766,20 +940,13 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
             * hostnames go through.
             */
            find_server_names(received_for, ctl, &msgblk.recipients);
-#ifdef SDPS_ENABLE
-       else if (sdps_envto)
-       {
-           find_server_names(sdps_envto, ctl, &msgblk.recipients);
-           free(sdps_envto);
-       }
-#endif /* SDPS_ENABLE */ 
        else
        {
            /*
             * We haven't extracted the envelope address.
             * So check all the "Resent-To" header addresses if 
             * they exist.  If and only if they don't, consider
-            * the "To" adresses.
+            * the "To" addresses.
             */
            register struct addrblk *nextptr;
            if (resent_to_addrchain) {
@@ -807,8 +974,8 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
            no_local_matches = TRUE;
            save_str(&msgblk.recipients, run.postmaster, XMIT_ACCEPT);
            if (outlevel >= O_DEBUG)
-               error(0, 0, 
-                     _("no local matches, forwarding to %s"),
+               report(stdout,
+                     _("no local matches, forwarding to %s\n"),
                      run.postmaster);
        }
     }
@@ -822,7 +989,8 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     if (ctl->errcount > olderrs)       /* there were DNS errors above */
     {
        if (outlevel >= O_DEBUG)
-           error(0,0, _("forwarding and deletion suppressed due to DNS errors"));
+           report(stdout,
+                  _("forwarding and deletion suppressed due to DNS errors\n"));
        free(msgblk.headers);
        free_str_list(&msgblk.recipients);
        return(PS_TRANSIENT);
@@ -861,18 +1029,31 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     if (!run.invisible && n != -1)
     {
        /* utter any per-message Received information we need here */
-       sprintf(buf, "Received: from %s\r\n", ctl->server.truename);
+        if (ctl->server.trueaddr) {
+           sprintf(buf, "Received: from %s [%u.%u.%u.%u]\r\n", 
+                   ctl->server.truename,
+                   (unsigned char)ctl->server.trueaddr[0],
+                   (unsigned char)ctl->server.trueaddr[1],
+                   (unsigned char)ctl->server.trueaddr[2],
+                   (unsigned char)ctl->server.trueaddr[3]);
+       } else {
+         sprintf(buf, "Received: from %s\r\n", ctl->server.truename);
+       }
        n = stuffline(ctl, buf);
        if (n != -1)
        {
            /*
+            * This header is technically invalid under RFC822.
+            * POP3, IMAP, etc. are not legal mail-parameter values.
+            *
             * We used to include ctl->remotename in this log line,
             * but this can be secure information that would be bad
             * to reveal.
             */
-           sprintf(buf, "\tby fetchmail-%s %s\r\n",
-                   RELEASE_ID,
-                   protocol->name);
+           sprintf(buf, "\tby %s with %s (fetchmail-%s)\r\n",
+                   fetchmailhost,
+                   protocol->name,
+                   VERSION);
            n = stuffline(ctl, buf);
            if (n != -1)
            {
@@ -880,7 +1061,7 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
                if (good_addresses == 0)
                {
                    sprintf(buf+1, 
-                           "for <%s@%s> (by default); ",
+                           "for %s@%s (by default); ",
                            user, ctl->destaddr);
                }
                else if (good_addresses == 1)
@@ -889,9 +1070,14 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
                        if (idp->val.status.mark == XMIT_ACCEPT)
                            break;      /* only report first address */
                    if (strchr(idp->id, '@'))
-                       sprintf(buf+1, "for <%s>", idp->id);
+                       sprintf(buf+1, "for %s", idp->id);
                    else
-                       sprintf(buf+1, "for <%s/%s>", idp->id, ctl->destaddr);
+                       /*
+                        * This could be a bit misleading, as destaddr is
+                        * the forwarding host rather than the actual 
+                        * destination.  Most of the time they coincide.
+                        */
+                       sprintf(buf+1, "for %s@%s", idp->id, ctl->destaddr);
                    sprintf(buf+strlen(buf), " (%s); ",
                            MULTIDROP(ctl) ? "multi-drop" : "single-drop");
                }
@@ -910,13 +1096,13 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
 
     if (n == -1)
     {
-       error(0, errno, _("writing RFC822 msgblk.headers"));
+       report(stdout, _("writing RFC822 msgblk.headers\n"));
        release_sink(ctl);
        free(msgblk.headers);
        free_str_list(&msgblk.recipients);
        return(PS_IOERR);
     }
-    else if (!run.use_syslog && outlevel >= O_VERBOSE)
+    else if ((run.poll_interval == 0 || nodetach) && outlevel >= O_VERBOSE && !isafile(2))
        fputs("#", stderr);
 
     /* write error notifications */
@@ -954,13 +1140,13 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
            strcat(errhd, _("SMTP listener rejected local recipient addresses: "));
            errlen = strlen(errhd);
            for (idp = msgblk.recipients; idp; idp = idp->next)
-               if (idp->val.status.mark == XMIT_ANTISPAM)
+               if (idp->val.status.mark == XMIT_RCPTBAD)
                    errlen += strlen(idp->id) + 2;
 
            xalloca(errmsg, char *, errlen+3);
            (void) strcpy(errmsg, errhd);
            for (idp = msgblk.recipients; idp; idp = idp->next)
-               if (idp->val.status.mark == XMIT_ANTISPAM)
+               if (idp->val.status.mark == XMIT_RCPTBAD)
                {
                    strcat(errmsg, idp->id);
                    if (idp->next)
@@ -982,7 +1168,7 @@ static int readheaders(int sock, long fetchlen, long reallen, struct query *ctl,
     *cp++ = '\0';
     stuffline(ctl, buf);
 
-    free(msgblk.headers);
+/*    free(msgblk.headers); */
     free_str_list(&msgblk.recipients);
     return(headers_ok ? PS_SUCCESS : PS_TRUNCATED);
 }
@@ -999,7 +1185,16 @@ static int readbody(int sock, struct query *ctl, flag forward, int len)
     unsigned char *inbufp = buf;
     flag issoftline = FALSE;
 
-    /* pass through the text lines */
+    /*
+     * Pass through the text lines in the body.
+     *
+     * Yes, this wants to be ||, not &&.  The problem is that in the most
+     * important delimited protocol, POP3, the length is not reliable.
+     * As usual, the problem is Microsoft brain damage; see FAQ item S2.
+     * So, for delimited protocols we need to ignore the length here and
+     * instead drop out of the loop with a break statement when we see
+     * the message delimiter.
+     */
     while (protocol->delimited || len > 0)
     {
        set_timeout(mytimeout);
@@ -1017,8 +1212,11 @@ static int readbody(int sock, struct query *ctl, flag forward, int len)
            sizeticker += linelen;
            while (sizeticker >= SIZETICKER)
            {
-               if (!run.use_syslog && outlevel > O_SILENT)
-                   error_build(".");
+               if ((run.poll_interval == 0 || nodetach) && outlevel > O_SILENT && !isafile(1))
+               {
+                   fputc('.', stdout);
+                   fflush(stdout);
+               }
                sizeticker -= SIZETICKER;
            }
        }
@@ -1026,17 +1224,19 @@ static int readbody(int sock, struct query *ctl, flag forward, int len)
 
        /* check for end of message */
        if (protocol->delimited && *inbufp == '.')
+       {
            if (inbufp[1] == '\r' && inbufp[2] == '\n' && inbufp[3] == '\0')
                break;
            else if (inbufp[1] == '\n' && inbufp[2] == '\0')
                break;
            else
                msglen--;       /* subtract the size of the dot escape */
+       }
 
        msglen += linelen;
 
        if (ctl->mimedecode && (ctl->mimemsg & MSG_NEEDS_DECODE)) {
-           issoftline = UnMimeBodyline(&inbufp, (protocol->delimited && issoftline));
+           issoftline = UnMimeBodyline(&inbufp, protocol->delimited, issoftline);
            if (issoftline && (sizeof(buf)-1-(inbufp-buf) < 200))
            {
                /*
@@ -1066,12 +1266,15 @@ static int readbody(int sock, struct query *ctl, flag forward, int len)
 
            if (n < 0)
            {
-               error(0, errno, _("writing message text"));
+               report(stdout, _("writing message text\n"));
                release_sink(ctl);
                return(PS_IOERR);
            }
-           else if (outlevel >= O_VERBOSE)
-               fputc('*', stderr);
+           else if (outlevel >= O_VERBOSE && !isafile(1))
+           {
+               fputc('*', stdout);
+               fflush(stdout);
+           }
        }
     }
 
@@ -1083,7 +1286,7 @@ int
 kerberos_auth (socket, canonical) 
 /* authenticate to the server host using Kerberos V4 */
 int socket;            /* socket to server host */
-#ifdef __FreeBSD__
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
 char *canonical;       /* server name */
 #else
 const char *canonical; /* server name */
@@ -1109,7 +1312,7 @@ const char *canonical;    /* server name */
                         "KPOPV0.1"));
     if (rem != KSUCCESS)
     {
-       error(0, -1, _("kerberos error %s"), (krb_get_err_text (rem)));
+       report(stderr, _("kerberos error %s\n"), (krb_get_err_text (rem)));
        return (PS_AUTHFAIL);
     }
     return (0);
@@ -1117,8 +1320,7 @@ const char *canonical;    /* server name */
 #endif /* KERBEROS_V4 */
 
 #ifdef KERBEROS_V5
-int
-kerberos5_auth(socket, canonical)
+static int kerberos5_auth(socket, canonical)
 /* authenticate to the server host using Kerberos V5 */
 int socket;             /* socket to server host */
 const char *canonical;  /* server name */
@@ -1136,19 +1338,19 @@ const char *canonical;  /* server name */
     krb5_auth_con_init(context, &auth_context);
 
     if (retval = krb5_cc_default(context, &ccdef)) {
-        error(0, 0, "krb5_cc_default: %s", error_message(retval));
+        report(stderr, "krb5_cc_default: %s\n", error_message(retval));
         return(PS_ERROR);
     }
 
     if (retval = krb5_cc_get_principal(context, ccdef, &client)) {
-        error(0, 0, "krb5_cc_get_principal: %s", error_message(retval));
+        report(stderr, "krb5_cc_get_principal: %s\n", error_message(retval));
         return(PS_ERROR);
     }
 
     if (retval = krb5_sname_to_principal(context, canonical, "pop",
            KRB5_NT_UNKNOWN,
            &server)) {
-        error(0, 0, "krb5_sname_to_principal: %s", error_message(retval));
+        report(stderr, "krb5_sname_to_principal: %s\n", error_message(retval));
         return(PS_ERROR);
     }
 
@@ -1167,14 +1369,21 @@ const char *canonical;  /* server name */
     krb5_auth_con_free(context, auth_context);
 
     if (retval) {
+#ifdef HEIMDAL
+      if (err_ret && err_ret->e_text) {
+          report(stderr, _("krb5_sendauth: %s [server says '%*s'] \n"),
+                 error_message(retval),
+                 err_ret->e_text);
+#else
       if (err_ret && err_ret->text.length) {
-          error(0, 0, _("krb5_sendauth: %s [server says '%*s'] "),
-            error_message(retval),
-            err_ret->text.length,
-            err_ret->text.data);
-          krb5_free_error(context, err_ret);
+          report(stderr, _("krb5_sendauth: %s [server says '%*s'] \n"),
+                error_message(retval),
+                err_ret->text.length,
+                err_ret->text.data);
+#endif
+         krb5_free_error(context, err_ret);
       } else
-          error(0, 0, "krb5_sendauth: %s", error_message(retval));
+          report(stderr, "krb5_sendauth: %s\n", error_message(retval));
       return(PS_ERROR);
     }
 
@@ -1227,8 +1436,7 @@ static void send_size_warnings(struct query *ctl)
     int size, nbr;
     int msg_to_send = FALSE;
     struct idlist *head=NULL, *current=NULL;
-    int max_warning_poll_count, good, bad;
-#define OVERHD "Subject: Fetchmail oversized-messages warning.\r\n\r\nThe following oversized messages remain on the mail server:"
+    int max_warning_poll_count;
 
     head = ctl->skipped;
     if (!head)
@@ -1248,7 +1456,11 @@ static void send_size_warnings(struct query *ctl)
      */
     if (open_warning_by_mail(ctl, (struct msgblk *)NULL))
        return;
-    stuff_warning(ctl, OVERHD);
+    stuff_warning(ctl,
+          _("Subject: Fetchmail oversized-messages warning.\r\n"
+            "\r\n"
+            "The following oversized messages remain on the mail server %s:"),
+                 ctl->server.pollname);
  
     if (run.poll_interval == 0)
        max_warning_poll_count = 0;
@@ -1263,7 +1475,7 @@ static void send_size_warnings(struct query *ctl)
            nbr = current->val.status.mark;
            size = atoi(current->id);
            stuff_warning(ctl, 
-                   _("\t%d msg %d octets long skipped by fetchmail."),
+                   _("\t%d msg %d octets long skipped by fetchmail.\r\n"),
                    nbr, size);
        }
        current->val.status.num++;
@@ -1274,148 +1486,140 @@ static void send_size_warnings(struct query *ctl)
     }
 
     close_warning_by_mail(ctl, (struct msgblk *)NULL);
-#undef OVERHD
 }
 
-int do_protocol(ctl, proto)
+static int do_session(ctl, proto, maxfetch)
 /* 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 */
+const int maxfetch;            /* maximum number of messages to fetch */
 {
-    int ok, js;
+    int js;
 #ifdef HAVE_VOLATILE
-    volatile int sock = -1;    /* pacifies -Wall */
+    volatile int ok, mailserver_socket = -1;   /* pacifies -Wall */
 #else
-    int sock = -1;
+    int ok, mailserver_socket = -1;
 #endif /* HAVE_VOLATILE */
     const char *msg;
-    void (*sigsave)(int);
+    void (*pipesave)(int);
+    void (*alrmsave)(int);
     struct idlist *current=NULL, *tmp=NULL;
 
     protocol = proto;
     ctl->server.base_protocol = protocol;
 
-#ifndef KERBEROS_V4
-    if (ctl->server.preauthenticate == A_KERBEROS_V4)
-    {
-       error(0, -1, _("Kerberos V4 support not linked."));
-       return(PS_ERROR);
-    }
-#endif /* KERBEROS_V4 */
-
-#ifndef KERBEROS_V5
-    if (ctl->server.preauthenticate == A_KERBEROS_V5)
-    {
-       error(0, -1, _("Kerberos V5 support not linked."));
-       return(PS_ERROR);
-    }
-#endif /* KERBEROS_V5 */
-
-    /* lacking methods, there are some options that may fail */
-    if (!proto->is_old)
-    {
-       /* check for unsupported options */
-       if (ctl->flush) {
-           error(0, 0,
-                   _("Option --flush is not supported with %s"),
-                   proto->name);
-           return(PS_SYNTAX);
-       }
-       else if (ctl->fetchall) {
-           error(0, 0,
-                   _("Option --all is not supported with %s"),
-                   proto->name);
-           return(PS_SYNTAX);
-       }
-    }
-    if (!proto->getsizes && NUM_SPECIFIED(ctl->limit))
-    {
-       error(0, 0,
-               _("Option --limit is not supported with %s"),
-               proto->name);
-       return(PS_SYNTAX);
-    }
-
     pass = 0;
     tagnum = 0;
     tag[0] = '\0';     /* nuke any tag hanging out from previous query */
     ok = 0;
 
     /* set up the server-nonresponse timeout */
-    sigsave = signal(SIGALRM, timeout_handler);
+    alrmsave = signal(SIGALRM, timeout_handler);
     mytimeout = ctl->server.timeout;
 
-    if ((js = setjmp(restart)) == 1)
-    {
-       if (phase == OPEN_WAIT)
-           error(0, 0,
-                 _("timeout after %d seconds waiting to connect to server %s."),
-                 ctl->server.timeout, ctl->server.pollname);
-       else if (phase == SERVER_WAIT)
-           error(0, 0,
-                 _("timeout after %d seconds waiting for server %s."),
-                 ctl->server.timeout, ctl->server.pollname);
-       else if (phase == FORWARDING_WAIT)
-           error(0, 0,
-                 _("timeout after %d seconds waiting for %s."),
-                 ctl->server.timeout,
-                 ctl->mda ? "MDA" : "SMTP");
-       else if (phase == LISTENER_WAIT)
-           error(0, 0,
-                 _("timeout after %d seconds waiting for listener to respond."));
-       else
-           error(0, 0, _("timeout after %d seconds."), ctl->server.timeout);
-
-       release_sink(ctl);
-       if (ctl->smtp_socket != -1)
-           close(ctl->smtp_socket);
-       if (sock != -1)
-           SockClose(sock);
+    /* set up the broken-pipe timeout */
+    pipesave = signal(SIGPIPE, sigpipe_handler);
 
+    if ((js = setjmp(restart)))
+    {
+#ifdef HAVE_SIGPROCMASK
        /*
-        * If we've exceeded our threshold for consecutive timeouts, 
-        * try to notify the user, then mark the connection wedged.
+        * 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.
         */
-       if (timeoutcount > MAX_TIMEOUTS 
-                       && !open_warning_by_mail(ctl, (struct msgblk *)NULL))
+       sigset_t        allsigs;
+
+       sigfillset(&allsigs);
+       sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
+#endif /* HAVE_SIGPROCMASK */
+
+       if (js == THROW_SIGPIPE)
        {
-           stuff_warning(ctl,
-                         _("Subject: fetchmail sees repeated timeouts\r\n"));
-           stuff_warning(ctl,
-                         _("Fetchmail saw more than %d timouts while attempting to get mail from %s@%s."), 
-                         MAX_TIMEOUTS,
-                         ctl->remotename,
-                         ctl->server.truename);
-           stuff_warning(ctl, 
-                         _("This could mean that your mailserver is stuck, or that your SMTP listener"));
-           stuff_warning(ctl, 
-                         _("is wedged, or that your mailbox file on the server has been corrupted by"));
-           stuff_warning(ctl, 
-                         _("a server error.  You can run `fetchmail -v -v' to diagnose the problem."));
-           stuff_warning(ctl,
-                         _("Fetchmail won't poll this mailbox again until you restart it."));
-           close_warning_by_mail(ctl, (struct msgblk *)NULL);
-           ctl->wedged = TRUE;
+           signal(SIGPIPE, SIG_IGN);
+           report(stdout,
+                  _("SIGPIPE thrown from an MDA or a stream socket error\n"));
+           ok = PS_SOCKET;
+           goto cleanUp;
+       }
+       else if (js == THROW_TIMEOUT)
+       {
+           if (phase == OPEN_WAIT)
+               report(stdout,
+                      _("timeout after %d seconds waiting to connect to server %s.\n"),
+                      ctl->server.timeout, ctl->server.pollname);
+           else if (phase == SERVER_WAIT)
+               report(stdout,
+                      _("timeout after %d seconds waiting for server %s.\n"),
+                      ctl->server.timeout, ctl->server.pollname);
+           else if (phase == FORWARDING_WAIT)
+               report(stdout,
+                      _("timeout after %d seconds waiting for %s.\n"),
+                      ctl->server.timeout,
+                      ctl->mda ? "MDA" : "SMTP");
+           else if (phase == LISTENER_WAIT)
+               report(stdout,
+                      _("timeout after %d seconds waiting for listener to respond.\n"), ctl->server.timeout);
+           else
+               report(stdout, 
+                      _("timeout after %d seconds.\n"), ctl->server.timeout);
+
+           /*
+            * If we've exceeded our threshold for consecutive timeouts, 
+            * try to notify the user, then mark the connection wedged.
+            * Don't do this if the connection can idle, though; idle
+            * timeouts just mean the frequency of mail is low.
+            */
+           if (timeoutcount > MAX_TIMEOUTS 
+               && !open_warning_by_mail(ctl, (struct msgblk *)NULL))
+           {
+               stuff_warning(ctl,
+                             _("Subject: fetchmail sees repeated timeouts\r\n"));
+               stuff_warning(ctl,
+                             _("Fetchmail saw more than %d timeouts while attempting to get mail from %s@%s.\r\n"), 
+                             MAX_TIMEOUTS,
+                             ctl->remotename,
+                             ctl->server.truename);
+               stuff_warning(ctl, 
+    _("This could mean that your mailserver is stuck, or that your SMTP\r\n" \
+    "server is wedged, or that your mailbox file on the server has been\r\n" \
+    "corrupted by a server error.  You can run `fetchmail -v -v' to\r\n" \
+    "diagnose the problem.\r\n\r\n" \
+    "Fetchmail won't poll this mailbox again until you restart it.\r\n"));
+               close_warning_by_mail(ctl, (struct msgblk *)NULL);
+               ctl->wedged = TRUE;
+           }
+
+           ok = PS_ERROR;
        }
 
-       ok = PS_ERROR;
+       /* try to clean up all streams */
+       release_sink(ctl);
+       if (ctl->smtp_socket != -1)
+           SockClose(ctl->smtp_socket);
+       if (mailserver_socket != -1)
+           SockClose(mailserver_socket);
     }
     else
     {
-       char buf[POPBUFSIZE+1], *realhost;
+       char buf[MSGBUFSIZE+1], *realhost;
        int len, num, count, new, bytes, deletions = 0, *msgsizes = NULL;
-#if INET6
-       int fetches, dispatches;
-#else /* INET6 */
+#if INET6_ENABLE
+       int fetches, dispatches, oldphase;
+#else /* INET6_ENABLE */
        int port, fetches, dispatches, oldphase;
-#endif /* INET6 */
+#endif /* INET6_ENABLE */
        struct idlist *idp;
 
        /* execute pre-initialization command, if any */
        if (ctl->preconnect && (ok = system(ctl->preconnect)))
        {
-           sprintf(buf, _("pre-connection command failed with status %d"), ok);
-           error(0, 0, buf);
+           report(stderr, 
+                  _("pre-connection command failed with status %d\n"), ok);
            ok = PS_SYNTAX;
            goto closeUp;
        }
@@ -1424,56 +1628,85 @@ const struct method *proto;     /* protocol method table */
        oldphase = phase;
        phase = OPEN_WAIT;
        set_timeout(mytimeout);
-#if !INET6
+#if !INET6_ENABLE
+#ifdef SSL_ENABLE
+       port = ctl->server.port ? ctl->server.port : ( ctl->use_ssl ? protocol->sslport : protocol->port );
+#else
        port = ctl->server.port ? ctl->server.port : protocol->port;
-#endif /* !INET6 */
+#endif
+#endif /* !INET6_ENABLE */
        realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
-#if INET6
-       if ((sock = SockOpen(realhost, 
-                            ctl->server.service ? ctl->server.service : protocol->service,
-                            ctl->server.netsec)) == -1, ctl->server.plugin
-           )
-#else /* INET6 */
-       if ((sock = SockOpen(realhost, port, NULL, ctl->server.plugin)) == -1)
-#endif /* INET6 */
+
+       /* allow time for the port to be set up if we have a plugin */
+       if (ctl->server.plugin)
+           (void)sleep(1);
+#if INET6_ENABLE
+       if ((mailserver_socket = SockOpen(realhost, 
+                            ctl->server.service ? ctl->server.service : ( ctl->use_ssl ? protocol->sslservice : protocol->service ),
+                            ctl->server.netsec, ctl->server.plugin)) == -1)
+#else /* INET6_ENABLE */
+       if ((mailserver_socket = SockOpen(realhost, port, NULL, ctl->server.plugin)) == -1)
+#endif /* INET6_ENABLE */
        {
-#if !INET6
+           char        errbuf[BUFSIZ];
+#if !INET6_ENABLE
            int err_no = errno;
 #ifdef HAVE_RES_SEARCH
            if (err_no != 0 && h_errno != 0)
-               error(0, 0, _("fetchmail: internal inconsistency"));
+               report(stderr, _("fetchmail: internal inconsistency\n"));
 #endif
            /*
             * Avoid generating a bogus error every poll cycle when we're
             * in daemon mode but the connection to the outside world
             * is down.
             */
-           if (err_no == EHOSTUNREACH && run.poll_interval)
-               goto ehostunreach;
-
-           error_build(_("fetchmail: %s connection to %s failed"), 
-                        protocol->name, ctl->server.pollname);
-#ifdef HAVE_RES_SEARCH
-           if (h_errno != 0)
+           if (!((err_no == EHOSTUNREACH || err_no == ENETUNREACH) 
+                 && run.poll_interval))
            {
-               if (h_errno == HOST_NOT_FOUND)
-                   error_complete(0, 0, _(": host is unknown"));
-               else if (h_errno == NO_ADDRESS)
-                   error_complete(0, 0, _(": name is valid but has no IP address"));
-               else if (h_errno == NO_RECOVERY)
-                   error_complete(0, 0, _(": unrecoverable name server error"));
-               else if (h_errno == TRY_AGAIN)
-                   error_complete(0, 0, _(": temporary name server error"));
+               report_build(stderr, _("fetchmail: %s connection to %s failed"), 
+                            protocol->name, ctl->server.pollname);
+#ifdef HAVE_RES_SEARCH
+               if (h_errno != 0)
+               {
+                   if (h_errno == HOST_NOT_FOUND)
+                       strcpy(errbuf, _("host is unknown."));
+#ifndef __BEOS__
+                   else if (h_errno == NO_ADDRESS)
+                       strcpy(errbuf, _("name is valid but has no IP address."));
+#endif
+                   else if (h_errno == NO_RECOVERY)
+                       strcpy(errbuf, _("unrecoverable name server error."));
+                   else if (h_errno == TRY_AGAIN)
+                       strcpy(errbuf, _("temporary name server error."));
+                   else
+                       sprintf(errbuf, _("unknown DNS error %d."), h_errno);
+               }
                else
-                   error_complete(0, 0, _(": unknown DNS error %d"), h_errno);
-           }
-           else
 #endif /* HAVE_RES_SEARCH */
+                   strcpy(errbuf, strerror(err_no));
+               report_complete(stderr, ": %s\n", errbuf);
 
-               error_complete(0, err_no, "");
-
-       ehostunreach:
-#endif /* INET6 */
+#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, (struct msgblk *)NULL) == 0)
+               {
+                   stuff_warning(ctl,
+                        _("Subject: Fetchmail unreachable-server warning.\r\n"
+                          "\r\n"
+                          "Fetchmail could not reach the mail server %s:")
+                                 ctl->server.pollname);
+                   stuff_warning(ctl, errbuf, ctl->server.pollname);
+                   close_warning_by_mail(ctl, (struct msgblk *)NULL);
+               }
+#endif
+           }
+#endif /* INET6_ENABLE */
            ok = PS_SOCKET;
            set_timeout(0);
            phase = oldphase;
@@ -1482,11 +1715,22 @@ const struct method *proto;     /* protocol method table */
        set_timeout(0);
        phase = oldphase;
 
+#ifdef SSL_ENABLE
+       /* 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->sslkey,ctl->sslcert,realhost) == -1) 
+       {
+           report(stderr, _("SSL connection failed.\n"));
+           goto closeUp;
+       }
+#endif
+
 #ifdef KERBEROS_V4
        if (ctl->server.preauthenticate == A_KERBEROS_V4)
        {
            set_timeout(mytimeout);
-           ok = kerberos_auth(sock, ctl->server.truename);
+           ok = kerberos_auth(mailserver_socket, ctl->server.truename);
            set_timeout(0);
            if (ok != 0)
                goto cleanUp;
@@ -1497,7 +1741,7 @@ const struct method *proto;       /* protocol method table */
        if (ctl->server.preauthenticate == A_KERBEROS_V5)
        {
            set_timeout(mytimeout);
-           ok = kerberos5_auth(sock, ctl->server.truename);
+           ok = kerberos5_auth(mailserver_socket, ctl->server.truename);
            set_timeout(0);
            if (ok != 0)
                goto cleanUp;
@@ -1505,56 +1749,62 @@ const struct method *proto;     /* protocol method table */
 #endif /* KERBEROS_V5 */
 
        /* accept greeting message from mail server */
-       ok = (protocol->parse_response)(sock, buf);
+       ok = (protocol->parse_response)(mailserver_socket, buf);
        if (ok != 0)
            goto cleanUp;
 
        /* try to get authorized to fetch mail */
+       stage = STAGE_GETAUTH;
        if (protocol->getauth)
        {
            if (protocol->password_canonify)
-               (protocol->password_canonify)(shroud, ctl->password);
+               (protocol->password_canonify)(shroud, ctl->password, PASSWORDLEN);
            else
                strcpy(shroud, ctl->password);
 
-           ok = (protocol->getauth)(sock, ctl, buf);
+           ok = (protocol->getauth)(mailserver_socket, ctl, buf);
            if (ok != 0)
            {
                if (ok == PS_LOCKBUSY)
-                   error(0, -1, _("Lock-busy error on %s@%s"),
+                   report(stderr, _("Lock-busy error on %s@%s\n"),
                          ctl->remotename,
                          ctl->server.truename);
-               else
+               else if (ok == PS_AUTHFAIL)
                {
-                   if (ok == PS_ERROR)
-                       ok = PS_AUTHFAIL;
-                   error(0, -1, _("Authorization failure on %s@%s"), 
+                   report(stderr, _("Authorization failure on %s@%s\n"), 
                          ctl->remotename,
                          ctl->server.truename);
 
                    /*
                     * If we're running in background, try to mail the
                     * calling user a heads-up about the authentication 
-                    * failure the first time it happens.
+                    * failure once it looks like this isn't a fluke 
+                    * due to the server being temporarily inaccessible.
                     */
                    if (run.poll_interval
-                       && !ctl->wedged 
+                       && ctl->authfailcount++ > MAX_AUTHFAILS 
                        && !open_warning_by_mail(ctl, (struct msgblk *)NULL))
                    {
                        stuff_warning(ctl,
-                              _("Subject: fetchmail authentication failed\r\n"));
+                           _("Subject: fetchmail authentication failed\r\n"));
                        stuff_warning(ctl,
-                               _("Fetchmail could not get mail from %s@%s."), 
-                               ctl->remotename,
-                               ctl->server.truename);
+                           _("Fetchmail could not get mail from %s@%s.\r\n"), 
+                           ctl->remotename,
+                           ctl->server.truename);
                        stuff_warning(ctl, 
-                              _("The attempt to get authorization failed."));
-                       stuff_warning(ctl, 
-                              _("This probably means your password is invalid."));
+    _("The attempt to get authorization failed.\r\n" \
+    "This probably means your password is invalid, but POP3 servers have\r\n" \
+    "other failure modes that fetchmail cannot distinguish from this\r\n" \
+    "because they don't send useful error messages on login failure.\r\n"));
                        close_warning_by_mail(ctl, (struct msgblk *)NULL);
                        ctl->wedged = TRUE;
                    }
                }
+               else
+                   report(stderr, _("Unknown login or authentication error on %s@%s\n"),
+                         ctl->remotename,
+                         ctl->server.truename);
+                   
                goto cleanUp;
            }
        }
@@ -1569,44 +1819,23 @@ const struct method *proto;     /* protocol method table */
                dispatches = 0;
                ++pass;
 
+               /* reset timeout, in case we did an IDLE */
+               mytimeout = ctl->server.timeout;
+
                if (outlevel >= O_DEBUG)
+               {
                    if (idp->id)
-                       error(0, 0, _("selecting or re-polling folder %s"), idp->id);
+                       report(stdout, _("selecting or re-polling folder %s\n"), idp->id);
                    else
-                       error(0, 0, _("selecting or re-polling default folder"));
+                       report(stdout, _("selecting or re-polling default folder\n"));
+               }
 
                /* compute # of messages and number of new messages waiting */
-               ok = (protocol->getrange)(sock, ctl, idp->id, &count, &new, &bytes);
+               stage = STAGE_GETRANGE;
+               ok = (protocol->getrange)(mailserver_socket, ctl, idp->id, &count, &new, &bytes);
                if (ok != 0)
                    goto cleanUp;
 
-               /* 
-                * 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 (count > 0 && proto->getsizes)
-               {
-                   int i;
-
-                   xalloca(msgsizes, int *, sizeof(int) * count);
-                   for (i = 0; i < count; i++)
-                       msgsizes[i] = -1;
-
-                   ok = (proto->getsizes)(sock, count, msgsizes);
-                   if (ok != 0)
-                       goto cleanUp;
-
-                   if (bytes == -1)
-                   {
-                       bytes = 0;
-                       for (i = 0; i < count; i++)
-                           bytes += msgsizes[i];
-                   }
-               }
-
                /* show user how many messages we downloaded */
                if (idp->id)
                    (void) sprintf(buf, _("%s at %s (folder %s)"),
@@ -1615,30 +1844,32 @@ const struct method *proto;     /* protocol method table */
                    (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);
+                       report(stdout, _("Polling %s\n"), ctl->server.truename);
                    else if (count != 0)
                    {
                        if (new != -1 && (count - new) > 0)
-                           error_build(_("%d %s (%d seen) for %s"),
+                           report_build(stdout, _("%d %s (%d seen) for %s"),
                                  count, count > 1 ? _("messages") :
                                                     _("message"),
                                  count-new, buf);
                        else
-                           error_build(_("%d %s for %s"), 
+                           report_build(stdout, _("%d %s for %s"), 
                                  count, count > 1 ? _("messages") :
                                                     _("message"), buf);
                        if (bytes == -1)
-                           error_complete(0, 0, ".");
+                           report_complete(stdout, ".\n");
                        else
-                           error_complete(0, 0, _(" (%d octets)."), bytes);
+                           report_complete(stdout, _(" (%d octets).\n"), bytes);
                    }
                    else
                    {
                        /* these are pointless in normal daemon mode */
                        if (pass == 1 && (run.poll_interval == 0 || outlevel >= O_VERBOSE))
-                           error(0, 0, _("No mail for %s"), buf); 
+                           report(stdout, _("No mail for %s\n"), buf); 
                    }
+               }
 
                /* very important, this is where we leave the do loop */ 
                if (count == 0)
@@ -1648,8 +1879,13 @@ const struct method *proto;      /* protocol method table */
                {
                    if (new == -1 || ctl->fetchall)
                        new = count;
-                   ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
-                   goto cleanUp;
+                   fetches = new;      /* set error status ccorrectly */
+                   /*
+                    * There used to be a `got noerror' here, but this
+                    * prevneted checking of multiple folders.  This
+                    * comment is a reminder in case I introduced some
+                    * subtle bug by removing it...
+                    */
                }
                else if (count > 0)
                {    
@@ -1684,12 +1920,41 @@ const struct method *proto;     /* protocol method table */
                     */
                    force_retrieval = !peek_capable && (ctl->errcount > 0);
 
+                   /* 
+                    * We need the size of each message before it's
+                    * loaded in order to pass it to 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)
+                   {
+                       int     i;
+
+                       xalloca(msgsizes, int *, sizeof(int) * count);
+                       for (i = 0; i < count; i++)
+                           msgsizes[i] = -1;
+
+                       stage = STAGE_GETSIZES;
+                       ok = (proto->getsizes)(mailserver_socket, count, msgsizes);
+                       if (ok != 0)
+                           goto cleanUp;
+
+                       if (bytes == -1)
+                       {
+                           bytes = 0;
+                           for (i = 0; i < count; i++)
+                               bytes += msgsizes[i];
+                       }
+                   }
+
                    /* read, forward, and delete messages */
+                   stage = STAGE_FETCH;
                    for (num = 1; num <= count; num++)
                    {
                        flag toolarge = NUM_NONZERO(ctl->limit)
                            && msgsizes && (msgsizes[num-1] > ctl->limit);
-                       flag oldmsg = (protocol->is_old && (protocol->is_old)(sock,ctl,num));
+                       flag oldmsg = (!new) || (protocol->is_old && (protocol->is_old)(mailserver_socket,ctl,num));
                        flag fetch_it = !toolarge 
                            && (ctl->fetchall || force_retrieval || !oldmsg);
                        flag suppress_delete = FALSE;
@@ -1710,18 +1975,23 @@ const struct method *proto;     /* protocol method table */
                        if (msgsizes && msgsizes[num-1] == -1)
                        {
                            if (outlevel >= O_VERBOSE)
-                               error(0, 0
-                                     _("Skipping message %d, length -1"),
-                                     num - 1);
+                               report(stdout
+                                     _("Skipping message %d, length -1\n"),
+                                     num);
                            continue;
                        }
 
-                       /* we may want to reject this message if it's old */
+                       /*
+                        * We may want to reject this message if it's old
+                        * or oversized, and we're not forcing retrieval.
+                        */
                        if (!fetch_it)
                        {
                            if (outlevel > O_SILENT)
                            {
-                               error_build(_("skipping message %d"), num);
+                               report_build(stdout, 
+                                    _("skipping message %d (%d octets)"),
+                                    num, msgsizes[num-1]);
                                if (toolarge && !check_only) 
                                {
                                    char size[32];
@@ -1766,7 +2036,7 @@ const struct method *proto;       /* protocol method table */
                                        tmp->val.status.num = cnt;
                                    }
 
-                                   error_build(_(" (oversized, %d octets)"),
+                                   report_build(stdout, _(" (oversized, %d octets)"),
                                                msgsizes[num-1]);
                                }
                            }
@@ -1776,7 +2046,7 @@ const struct method *proto;       /* protocol method table */
                            flag wholesize = !protocol->fetch_body;
 
                            /* request a message */
-                           ok = (protocol->fetch_headers)(sock,ctl,num, &len);
+                           ok = (protocol->fetch_headers)(mailserver_socket,ctl,num, &len);
                            if (ok != 0)
                                goto cleanUp;
 
@@ -1789,23 +2059,23 @@ const struct method *proto;     /* protocol method table */
 
                            if (outlevel > O_SILENT)
                            {
-                               error_build(_("reading message %d of %d"),
+                               report_build(stdout, _("reading message %d of %d"),
                                            num,count);
 
                                if (len > 0)
-                                   error_build(_(" (%d %soctets)"),
+                                   report_build(stdout, _(" (%d %soctets)"),
                                        len, wholesize ? "" : _("header "));
                                if (outlevel >= O_VERBOSE)
-                                   error_complete(0, 0, "");
+                                   report_complete(stdout, "\n");
                                else
-                                   error_build(" ");
+                                   report_complete(stdout, " ");
                            }
 
                            /* 
                             * Read the message headers and ship them to the
                             * output sink.  
                             */
-                           ok = readheaders(sock, len, msgsizes[num-1],
+                           ok = readheaders(mailserver_socket, len, msgsizes[num-1],
                                             ctl, num);
                            if (ok == PS_RETAINED)
                                suppress_forward = retained = TRUE;
@@ -1828,18 +2098,31 @@ const struct method *proto;     /* protocol method table */
                             */
                            if (protocol->fetch_body && !suppress_readbody) 
                            {
-                               if (outlevel >= O_VERBOSE)
-                                   fputc('\n', stderr);
+                               if (outlevel >= O_VERBOSE && !isafile(1))
+                               {
+                                   fputc('\n', stdout);
+                                   fflush(stdout);
+                               }
 
-                               if ((ok = (protocol->trail)(sock, ctl, num)))
+                               if ((ok = (protocol->trail)(mailserver_socket, ctl, num)))
                                    goto cleanUp;
                                len = 0;
                                if (!suppress_forward)
                                {
-                                   if ((ok=(protocol->fetch_body)(sock,ctl,num,&len)))
+                                   if ((ok=(protocol->fetch_body)(mailserver_socket,ctl,num,&len)))
                                        goto cleanUp;
+                                    /*
+                                     * Work around a bug in Novell's
+                                    * broken GroupWise IMAP server;
+                                     * its body FETCH response is missing
+                                    * the required length for the data
+                                    * string.  This violates RFC2060.
+                                     */
+                                    if (len == -1)
+                                       len = msgsizes[num-1] - msglen;
                                    if (outlevel > O_SILENT && !wholesize)
-                                       error_build(_(" (%d body octets) "), len);
+                                       report_complete(stdout,
+                                              _(" (%d body octets) "), len);
                                }
                            }
 
@@ -1856,7 +2139,7 @@ const struct method *proto;       /* protocol method table */
                                }
                                else
                                {
-                                 ok = readbody(sock,
+                                 ok = readbody(mailserver_socket,
                                                ctl,
                                                !suppress_forward,
                                                len);
@@ -1869,10 +2152,13 @@ const struct method *proto;     /* protocol method table */
                                /* tell server we got it OK and resynchronize */
                                if (protocol->trail)
                                {
-                                   if (outlevel >= O_VERBOSE)
-                                       fputc('\n', stderr);
+                                   if (outlevel >= O_VERBOSE && !isafile(1))
+                                   {
+                                       fputc('\n', stdout);
+                                       fflush(stdout);
+                                   }
 
-                                   ok = (protocol->trail)(sock, ctl, num);
+                                   ok = (protocol->trail)(mailserver_socket, ctl, num);
                                    if (ok != 0)
                                        goto cleanUp;
                                }
@@ -1907,8 +2193,8 @@ const struct method *proto;       /* protocol method table */
                            if (msgsizes && msglen != msgsizes[num-1])
                            {
                                if (outlevel >= O_DEBUG)
-                                   error(0, 0,
-                                         _("message %d was not the expected length (%d actual != %d expected)"),
+                                   report(stdout,
+                                         _("message %d was not the expected length (%d actual != %d expected)\n"),
                                          num, msglen, msgsizes[num-1]);
                            }
 
@@ -1938,7 +2224,8 @@ const struct method *proto;       /* protocol method table */
                            struct idlist       *sdp;
 
                            for (sdp = ctl->newsaved; sdp; sdp = sdp->next)
-                               if (sdp->val.status.num == num)
+                               if ((sdp->val.status.num == num)
+                                               && (!toolarge || oldmsg))
                                    sdp->val.status.mark = UID_SEEN;
                        }
 
@@ -1946,7 +2233,7 @@ const struct method *proto;       /* protocol method table */
                        if (retained)
                        {
                            if (outlevel > O_SILENT) 
-                               error_complete(0, 0, _(" retained"));
+                               report(stdout, _(" retained\n"));
                        }
                        else if (protocol->delete
                                 && !suppress_delete
@@ -1954,8 +2241,8 @@ const struct method *proto;       /* protocol method table */
                        {
                            deletions++;
                            if (outlevel > O_SILENT) 
-                               error_complete(0, 0, _(" flushed"));
-                           ok = (protocol->delete)(sock, ctl, num);
+                               report_complete(stdout, _(" flushed\n"));
+                           ok = (protocol->delete)(mailserver_socket, ctl, num);
                            if (ok != 0)
                                goto cleanUp;
 #ifdef POP3_ENABLE
@@ -1963,19 +2250,20 @@ const struct method *proto;     /* protocol method table */
 #endif /* POP3_ENABLE */
                        }
                        else if (outlevel > O_SILENT) 
-                           error_complete(0, 0, _(" not flushed"));
+                           report_complete(stdout, _(" not flushed\n"));
 
                        /* perhaps this as many as we're ready to handle */
-                       if (NUM_NONZERO(ctl->fetchlimit)
-                                       && ctl->fetchlimit <= fetches)
+                       if (maxfetch && maxfetch <= fetches && fetches < count)
                        {
-                           error(0, 0, _("fetchlimit reached; %d messages left on server"),
-                                 count - fetches);
-                           goto no_error;
+                           report(stdout, _("fetchlimit %d reached; %d messages left on server\n"),
+                                 maxfetch, count - fetches);
+                           ok = PS_MAXFETCH;
+                           goto cleanUp;
                        }
                    }
 
-                   if (!check_only && ctl->skipped)
+                   if (!check_only && ctl->skipped
+                       && run.poll_interval > 0 && !nodetach)
                    {
                        clean_skipped_list(&ctl->skipped);
                        send_size_warnings(ctl);
@@ -1983,28 +2271,33 @@ const struct method *proto;     /* protocol method table */
                }
            } while
                  /*
-                  * Only re-poll if we had some actual forwards, allowed
-                  * deletions and had no errors.
+                  * Only re-poll if we either had some actual forwards and 
+                  * either 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:
-       ok = (protocol->logout_cmd)(sock, ctl);
+    /* no_error: */
+       /* ordinary termination with no errors -- officially log out */
+       ok = (protocol->logout_cmd)(mailserver_socket, 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;
-       SockClose(sock);
+       SockClose(mailserver_socket);
        goto closeUp;
 
     cleanUp:
+       /* we only get here on error */
        if (ok != 0 && ok != PS_SOCKET)
-           (protocol->logout_cmd)(sock, ctl);
-       SockClose(sock);
+       {
+           stage = STAGE_LOGOUT;
+           (protocol->logout_cmd)(mailserver_socket, ctl);
+       }
+       SockClose(mailserver_socket);
     }
 
     msg = (const char *)NULL;  /* sacrifice to -Wall */
@@ -2038,30 +2331,122 @@ const struct method *proto;    /* protocol method table */
        msg = _("DNS lookup");
        break;
     case PS_UNDEFINED:
-       error(0, 0, _("undefined"));
+       report(stderr, _("undefined error\n"));
        break;
     }
+    /* no report on PS_MAXFETCH or PS_UNDEFINED */
     if (ok==PS_SOCKET || ok==PS_AUTHFAIL || ok==PS_SYNTAX 
                || ok==PS_IOERR || ok==PS_ERROR || ok==PS_PROTOCOL 
-               || ok==PS_LOCKBUSY || ok==PS_SMTP)
-       error(0,-1, _("%s error while fetching from %s"), msg, ctl->server.pollname);
+               || ok==PS_LOCKBUSY || ok==PS_SMTP || ok==PS_DNS)
+       report(stderr, _("%s error while fetching from %s\n"), msg, ctl->server.pollname);
 
 closeUp:
     /* execute post-initialization command, if any */
     if (ctl->postconnect && (ok = system(ctl->postconnect)))
     {
-       char buf[80];
-
-       sprintf(buf, _("post-connection command failed with status %d"), ok);
-       error(0, 0, buf);
+       report(stderr, _("post-connection command failed with status %d\n"), ok);
        if (ok == PS_SUCCESS)
            ok = PS_SYNTAX;
     }
 
-    signal(SIGALRM, sigsave);
+    signal(SIGALRM, alrmsave);
+    signal(SIGPIPE, pipesave);
     return(ok);
 }
 
+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 */
+{
+    int        ok;
+
+#ifndef KERBEROS_V4
+    if (ctl->server.preauthenticate == A_KERBEROS_V4)
+    {
+       report(stderr, _("Kerberos V4 support not linked.\n"));
+       return(PS_ERROR);
+    }
+#endif /* KERBEROS_V4 */
+
+#ifndef KERBEROS_V5
+    if (ctl->server.preauthenticate == A_KERBEROS_V5)
+    {
+       report(stderr, _("Kerberos V5 support not linked.\n"));
+       return(PS_ERROR);
+    }
+#endif /* KERBEROS_V5 */
+
+    /* lacking methods, there are some options that may fail */
+    if (!proto->is_old)
+    {
+       /* check for unsupported options */
+       if (ctl->flush) {
+           report(stderr,
+                   _("Option --flush is not supported with %s\n"),
+                   proto->name);
+           return(PS_SYNTAX);
+       }
+       else if (ctl->fetchall) {
+           report(stderr,
+                   _("Option --all is not supported with %s\n"),
+                   proto->name);
+           return(PS_SYNTAX);
+       }
+    }
+    if (!proto->getsizes && NUM_SPECIFIED(ctl->limit))
+    {
+       report(stderr,
+               _("Option --limit is not supported with %s\n"),
+               proto->name);
+       return(PS_SYNTAX);
+    }
+
+    /*
+     * If no expunge limit or we do expunges within the driver,
+     * then just do one session, passing in any fetchlimit.
+     */
+    if (proto->retry || !NUM_SPECIFIED(ctl->expunge))
+       return(do_session(ctl, proto, NUM_VALUE_OUT(ctl->fetchlimit)));
+    /*
+     * There's an expunge limit, and it isn't handled in the driver itself.
+     * OK; do multiple sessions, each fetching a limited # of messages.
+     * Stop if the total count of retrieved messages exceeds ctl->fetchlimit
+     * (if it was nonzero).
+     */
+    else
+    {
+       int totalcount = 0; 
+       int lockouts   = 0;
+       int expunge    = NUM_VALUE_OUT(ctl->expunge);
+       int fetchlimit = NUM_VALUE_OUT(ctl->fetchlimit);
+
+       do {
+           ok = do_session(ctl, proto, expunge);
+           totalcount += expunge;
+           if (NUM_SPECIFIED(ctl->fetchlimit) && totalcount >= fetchlimit)
+               break;
+           if (ok != PS_LOCKBUSY)
+               lockouts = 0;
+           else if (lockouts >= MAX_LOCKOUTS)
+               break;
+           else /* ok == PS_LOCKBUSY */
+           {
+               /*
+                * Allow time for the server lock to release.  if we
+                * don't do this, we'll often hit a locked-mailbox
+                * condition and fail.
+                */
+               lockouts++;
+               sleep(3);
+           }
+       } while
+           (ok == PS_MAXFETCH || ok == PS_LOCKBUSY);
+
+       return(ok);
+    }
+}
+
 #if defined(HAVE_STDARG_H)
 void gen_send(int sock, const char *fmt, ... )
 #else
@@ -2110,7 +2495,7 @@ va_dcl
            *cp = '\0';
        }
        buf[strlen(buf)-2] = '\0';
-       error(0, 0, "%s> %s", protocol->name, buf);
+       report(stdout, "%s> %s\n", protocol->name, buf);
     }
 }
 
@@ -2138,7 +2523,7 @@ int size; /* length of buffer */
        if (buf[strlen(buf)-1] == '\r')
            buf[strlen(buf)-1] = '\0';
        if (outlevel >= O_MONITOR)
-           error(0, 0, "%s< %s", protocol->name, buf);
+           report(stdout, "%s< %s\n", protocol->name, buf);
        phase = oldphase;
        return(PS_SUCCESS);
     }
@@ -2196,7 +2581,7 @@ va_dcl
            *cp = '\0';
        }
        buf[strlen(buf)-1] = '\0';
-       error(0, 0, "%s> %s", protocol->name, buf);
+       report(stdout, "%s> %s\n", protocol->name, buf);
     }
 
     /* we presume this does its own response echoing */