]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
SockGets is gone.
[~andy/fetchmail] / driver.c
index 6cb18cd8a347362075f3614cc8b4c086647c4857..6ef734bfe37796959d530fcecd074625881c153d 100644 (file)
--- a/driver.c
+++ b/driver.c
@@ -9,15 +9,23 @@
 #include  <config.h>
 #include  <stdio.h>
 #include  <setjmp.h>
+#include  <errno.h>
+#include  <ctype.h>
+#include  <string.h>
 #if defined(STDC_HEADERS)
 #include  <stdlib.h>
-#include  <string.h>
+#endif
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
 #endif
 #if defined(HAVE_STDARG_H)
 #include  <stdarg.h>
 #else
 #include  <varargs.h>
 #endif
+#if defined(HAVE_ALLOCA_H)
+#include <alloca.h>
+#endif
 #include  <sys/time.h>
 #include  <signal.h>
 
 #include  "fetchmail.h"
 #include  "smtp.h"
 
+/* BSD portability hack...I know, this is an ugly place to put it */
+#if !defined(SIGCHLD) && defined(SIGCLD)
+#define SIGCHLD        SIGCLD
+#endif
+
 #define        SMTP_PORT       25      /* standard SMTP service port */
 
-static struct method *protocol;
+int batchlimit;                /* how often to tear down the delivery connection */
+int batchcount;                /* count of messages sent in current batch */
+int peek_capable;      /* can we peek for better error recovery? */
+
+static const struct method *protocol;
 static jmp_buf restart;
 
 char tag[TAGLEN];
 static int tagnum;
 #define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
 
-static char *shroud;
+static char *shroud;   /* string to shroud in debug output, if  non-NULL */
+static int mytimeout;  /* value of nonreponse timeout */
 
 static int strcrlf(dst, src, count)
 /* replace LFs with CR-LF; return length of string with replacements */
@@ -68,332 +86,148 @@ int count;        /* length of src */
   return len;
 }
 
-static void alarm_handler (int signal)
-/* handle server-timeout signal */
-{
-    longjmp(restart, 1);
-}
-
-static void reply_hack(buf, host)
-/* hack message headers so replies will work properly */
-char *buf;             /* header to be hacked */
-const char *host;      /* server hostname */
+static void vtalarm(int timeleft)
+/* reset the nonresponse-timeout */
 {
-    const char *from;
-    int state = 0, tokencount = 0;
-    char mycopy[POPBUFSIZE+1];
-
-    if (strncmp("From: ", buf, 6)
-       && strncmp("To: ", buf, 4)
-       && strncmp("Reply-", buf, 6)
-       && strncmp("Cc: ", buf, 4)
-       && strncmp("Bcc: ", buf, 5)) {
-       return;
-    }
-
-    strcpy(mycopy, buf);
-    for (from = mycopy; *from; from++)
-    {
-       switch (state)
-       {
-       case 0:   /* before header colon */
-           if (*from == ':')
-               state = 1;
-           break;
-
-       case 1:   /* we've seen the colon, we're looking for addresses */
-           if (*from == '"')
-               state = 3;
-           else if (*from == '(')
-               state = 4;    
-           else if (*from == '<' || isalnum(*from))
-               state = 5;
-           else if (isspace(*from))
-               state = 2;
-           else if (*from == ',')
-               tokencount = 0;
-           break;
-
-       case 2:     /* found a token boundary -- reset without copying */
-           if (*from != ' ' && *from != '\t')
-           {
-               tokencount++;
-               state = 1;
-               --from;
-               continue;
-           }
-
-       case 3:   /* we're in a quoted human name, copy and ignore */
-           if (*from == '"')
-               state = 1;
-           break;
-
-       case 4:   /* we're in a parenthesized human name, copy and ignore */
-           if (*from == ')')
-               state = 1;
-           break;
-
-       case 5:   /* the real work gets done here */
-           /*
-            * We're in something that might be an address part,
-            * either a bare unquoted/unparenthesized text or text
-            * enclosed in <> as per RFC822.
-            */
-           /* if the address part contains an @, don't mess with it */
-           if (*from == '@')
-               state = 6;
-
-           /* If the address token is not properly terminated, ignore it. */
-           else if (*from == ' ' || *from == '\t')
-           {
-               const char *cp;
-
-               /*
-                * The only lookahead case.  If we're looking at space or tab,
-                * we might be looking at a local name immediately followed
-                * by a human name.
-                */
-               for (cp = from; isspace(*cp); cp++)
-                   continue;
-               if (*cp == '(')
-               {
-                   strcpy(buf, "@");
-                   strcat(buf, host);
-                   buf += strlen(buf);
-                   state = 1;
-               }
-           }
-
-           /*
-            * On proper termination with no @, insert hostname.
-            * Case '>' catches <>-enclosed mail IDs.  Case ',' catches
-            * comma-separated bare IDs.
-            */
-           else if (strchr(">,", *from))
-           {
-               strcpy(buf, "@");
-               strcat(buf, host);
-               buf += strlen(buf);
-               state = 1;
-           }
-
-           /* a single local name alone on the line */
-           else if (*from == '\n' && tokencount == 0)
-           {
-               strcpy(buf, "@");
-               strcat(buf, host);
-               buf += strlen(buf);
-               state = 2;
-           }
+    struct itimerval ntimeout;
 
-           /* everything else, including alphanumerics, just passes through */
-           break;
-
-       case 6:   /* we're in a remote mail ID, no need to append hostname */
-           if (*from == '>' || *from == ',' || isspace(*from))
-               state = 1;
-           break;
-       }
-
-       /* all characters from the old buffer get copied to the new one */
-       *buf++ = *from;
-    }
-    *buf++ = '\0';
+    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);
 }
 
-static char *nxtaddr(hdr)
-/* parse addresses in succession out of a specified RFC822 header */
-char *hdr;     /* header line to be parsed, NUL to continue in previous hdr */
+static void vtalarm_handler (int signal)
+/* handle server-timeout SIGVTALARM signal */
 {
-    static char        *hp, *tp, address[POPBUFSIZE+1];
-    static     state;
-
-    /*
-     * Note 1: RFC822 escaping with \ is *not* handled.  Note 2: it is
-     * important that this routine not stop on \r, since we use \r as
-     * a marker for RFC822 continuations below.
-     */
-
-    if (hdr)
-    {
-       hp = hdr;
-       state = 0;
-    }
-
-    for (; *hp; hp++)
-    {
-       switch (state)
-       {
-       case 0:   /* before header colon */
-           if (*hp == '\n')
-               return(NULL);
-           else if (*hp == ':')
-           {
-               state = 1;
-               tp = address;
-           }
-           break;
-
-       case 1:   /* we've seen the colon, now grab the address */
-           if (*hp == '\n')    /* end of address list */
-           {
-               *tp++ = '\0';
-               state = 6;
-               return(address);
-           }
-           else if (*hp == ',')  /* end of address */
-           {
-               *tp++ = '\0';
-               return(address);
-           }
-           else if (*hp == '"') /* quoted string */
-           {
-               state = 2;
-               *tp++ = *hp;
-           }
-           else if (*hp == '(') /* address comment -- ignore */
-               state = 3;    
-           else if (*hp == '<') /* begin <address> */
-           {
-               state = 4;
-               tp = address;
-           }
-           else if (isspace(*hp)) /* ignore space */
-               state = 1;
-           else   /* just take it */
-           {
-               state = 1;
-               *tp++ = *hp;
-           }
-           break;
-
-       case 2:   /* we're in a quoted string, copy verbatim */
-           if (*hp == '\n')
-               return(NULL);
-           if (*hp != '"')
-               *tp++ = *hp;
-           else if (*hp == '"')
-           {
-               *tp++ = *hp;
-               state = 1;
-           }
-           break;
-
-       case 3:   /* we're in a parenthesized comment, ignore */
-           if (*hp == '\n')
-               return(NULL);
-           else if (*hp == ')')
-               state = 1;
-           break;
-
-       case 4:   /* possible <>-enclosed address */
-           if (*hp == '>') /* end of address */
-           {
-               *tp++ = '\0';
-               state = 1;
-               return(address);
-           }
-           else if (*hp == '<')  /* nested <> */
-               tp = address;
-           else if (*hp == '"') /* quoted address */
-           {
-               *tp++ = *hp;
-               state = 5;
-           }
-           else  /* just copy address */
-               *tp++ = *hp;
-           break;
-
-       case 5:   /* we're in a quoted address, copy verbatim */
-           if (*hp == '\n')  /* mismatched quotes */
-               return(NULL);
-           if (*hp != '"')  /* just copy it if it isn't a quote */
-               *tp++ = *hp;
-           else if (*hp == '"')  /* end of quoted string */
-           {
-               *tp++ = *hp;
-               state = 4;
-           }
-           break;
-
-       case 6:
-           return(NULL);
-           break;
-       }
-    }
-
-    return(NULL);
+    longjmp(restart, 1);
 }
 
-#ifdef HAVE_GETHOSTBYNAME
+#ifdef HAVE_RES_SEARCH
 #define MX_RETRIES     3
 
-static int is_host_alias(name, ctl)
+static int is_host_alias(const char *name, struct query *ctl)
 /* determine whether name is a DNS alias of the hostname */
-const char *name;
-struct query   *ctl;
 {
     struct hostent     *he;
-    int                        i, n;
+    struct mxentry     *mxp, *mxrecords;
 
     /*
      * The first two checks are optimizations that will catch a good
-     * many cases.  First, check against the hostname the user specified.
-     * Odds are good this will either be the mailserver's FQDN or a
-     * suffix of it with the mailserver's domain's default host name
-     * omitted.  Next, check against the mailserver's FQDN, in case
+     * many cases.  (1) check against the hostname the user
+     * specified.  Odds are good this will either be the mailserver's
+     * FQDN or a suffix of it with the mailserver's domain's default
+     * host name omitted.  Then check the rest of the `also known as'
+     * cache accumulated by previous DNS checks.  This cache is primed
+     * by the aka list option.
+     *
+     * (2) check against the mailserver's FQDN, in case
      * it's not the same as the declared hostname.
      *
      * Either of these on a mail address is definitive.  Only if the
      * name doesn't match either is it time to call the bind library.
      * If this happens odds are good we're looking at an MX name.
      */
-    if (strcmp(name, ctl->servername) == 0)
+    if (str_in_list(&ctl->lead_server->servernames, name))
        return(TRUE);
     else if (strcmp(name, ctl->canonical_name) == 0)
        return(TRUE);
 
     /*
-     * We treat DNS lookup failure as a negative on the theory that
-     * the mailserver's DNS server is `nearby' and should be able
-     * to respond quickly and reliably.  Ergo if we get failure,
-     * the name isn't a mailserver alias.
+     * We know DNS service was up at the beginning of this poll cycle.
+     * If it's down, our nameserver has crashed.  We don't want to try
+     * delivering the current message or anything else from this
+     * mailbox until it's back up.
      */
-    else if ((he = gethostbyname(name)) && strcmp(ctl->canonical_name, he->h_name) == 0)
-       return(TRUE);
+    else if ((he = gethostbyname(name)) != (struct hostent *)NULL)
+    {
+       if (strcmp(ctl->canonical_name, he->h_name) == 0)
+           goto match;
+       else
+           return(FALSE);
+    }
+    else
+       switch (h_errno)
+       {
+       case HOST_NOT_FOUND:    /* specified host is unknown */
+       case NO_ADDRESS:        /* valid, but does not have an IP address */
+           break;
+
+       case NO_RECOVERY:       /* non-recoverable name server error */
+       case TRY_AGAIN:         /* temporary error on authoritative server */
+       default:
+           if (outlevel != O_SILENT)
+               putchar('\n');  /* terminate the progress message */
+           error(0, 0,
+               "nameserver failure while looking for `%s' during poll of %s.",
+               name, ctl->servernames->id);
+           ctl->errcount++;
+           longjmp(restart, 2);        /* try again next poll cycle */
+           break;
+       }
 
     /*
-     * Search for a name match on MX records pointing to the server
-     * site.  These may live far away, so allow a couple of retries.
+     * We're only here if DNS was OK but the gethostbyname() failed
+     * with a HOST_NOT_FOUND or NO_ADDRESS error.
+     * Search for a name match on MX records pointing to the server.
      */
-    for (i = 0; i < MX_RETRIES; i++)
+    h_errno = 0;
+    if ((mxrecords = getmxrecords(name)) == (struct mxentry *)NULL)
     {
-       struct mxentry *mxrecords, *mxp;
-       int j;
+       switch (h_errno)
+       {
+       case HOST_NOT_FOUND:    /* specified host is unknown */
+           return(FALSE);
+
+       case NO_ADDRESS:        /* valid, but does not have an IP address */
+           for (mxp = mxrecords; mxp->name; mxp++)
+               if (strcmp(name, mxp->name) == 0)
+                   goto match;
+           return(FALSE);
+           break;
 
-       mxrecords = getmxrecords(name);
+       case NO_RECOVERY:       /* non-recoverable name server error */
+       case TRY_AGAIN:         /* temporary error on authoritative server */
+       default:
+           error(0, 0,
+               "nameserver failure while looking for `%s' during poll of %s.",
+               name, ctl->servernames->id);
+           ctl->errcount++;
+           longjmp(restart, 2);        /* try again next poll cycle */
+           break;
+       }
+    }
 
-       if (mxrecords == (struct mxentry *)NULL)
-           if (h_errno == TRY_AGAIN)
-           {
-               sleep(1);
-               continue;
-           }
-           else
-               break;
+match:
+    /* add this name to relevant server's `also known as' list */
+    save_str(&ctl->lead_server->servernames, -1, name);
+    return(TRUE);
+}
 
-       for (mxp = mxrecords; mxp->name; mxp++)
-           if (strcmp(name, mxp->name) == 0)
-               return(TRUE);
-    }
+static void map_name(name, ctl, xmit_names)
+/* add given name to xmit_names if it matches declared localnames */
+const char *name;              /* name to map */
+struct query *ctl;             /* list of permissible aliases */
+struct idlist **xmit_names;    /* list of recipient names parsed out */
+{
+    const char *lname;
 
-    return(FALSE);
+    lname = idpair_find(&ctl->localnames, name);
+    if (!lname && ctl->wildcard)
+       lname = name;
+
+    if (lname != (char *)NULL)
+    {
+       if (outlevel == O_VERBOSE)
+           error(0, 0, "mapped %s to local %s", name, lname);
+       save_str(xmit_names, -1, lname);
+    }
 }
 
 void find_server_names(hdr, ctl, xmit_names)
 /* parse names out of a RFC822 header into an ID list */
 const char *hdr;               /* RFC822 header in question */
-struct query *ctl;     /* list of permissible aliases */
+struct query *ctl;             /* list of permissible aliases */
 struct idlist **xmit_names;    /* list of recipient names parsed out */
 {
     if (hdr == (char *)NULL)
@@ -404,57 +238,105 @@ struct idlist **xmit_names;      /* list of recipient names parsed out */
 
        if ((cp = nxtaddr(hdr)) != (char *)NULL)
            do {
-               char    *atsign = strchr(cp, '@');
+               char    *atsign;
 
-               if (atsign)
-                   if (ctl->norewrite)
-                       continue;
-                   else
-                   {
-                       if (!is_host_alias(atsign+1, ctl))
-                           continue;
-                       atsign[0] = '\0';
-                   }
-               lname = idpair_find(&ctl->localnames, cp);
-               if (lname != (char *)NULL)
+               if ((atsign = strchr(cp, '@')))
                {
-                   if (outlevel == O_VERBOSE)
-                       fprintf(stderr,
-                               "fetchmail: mapped %s to local %s\n",
-                               cp, lname);
-                   save_uid(xmit_names, -1, lname);
+                   /*
+                    * Address has an @. Check to see if the right-hand part
+                    * is an alias or MX equivalent of the mailserver.  If it's
+                    * not, skip this name.  If it is, we'll keep going and try
+                    * to find a mapping to a client name.
+                    */
+                   if (!is_host_alias(atsign+1, ctl))
+                       continue;
+                   atsign[0] = '\0';
                }
+
+               map_name(cp, ctl, xmit_names);
            } while
                ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
     }
 }
-#endif /* HAVE_GETHOSTBYNAME */
+#endif /* HAVE_RES_SEARCH */
+
+static FILE *smtp_open(struct query *ctl)
+/* try to open a socket to the appropriate SMTP server for this query */ 
+{
+    ctl = ctl->lead_smtp; /* go to the SMTP leader for this query */
+
+    /* maybe it's time to close the socket in order to force delivery */
+    if (batchlimit && ctl->smtp_sockfp && batchcount++ == batchlimit)
+    {
+       fclose(ctl->smtp_sockfp);
+       ctl->smtp_sockfp = (FILE *)NULL;
+       batchcount = 0;
+    }
+
+    /* if no socket to this host is already set up, try to open one */
+    if (ctl->smtp_sockfp == (FILE *)NULL)
+    {
+       if ((ctl->smtp_sockfp = sockopen(ctl->smtphost, SMTP_PORT)) == (FILE *)NULL)
+           return((FILE *)NULL);
+       else if (SMTP_ok(ctl->smtp_sockfp) != SM_OK
+                || SMTP_helo(ctl->smtp_sockfp, ctl->servernames->id) != SM_OK)
+       {
+           fclose(ctl->smtp_sockfp);
+           ctl->smtp_sockfp = (FILE *)NULL;
+       }
+    }
 
-static int gen_readmsg (socket, mboxfd, len, delimited, ctl)
+    return(ctl->smtp_sockfp);
+}
+
+static int SockGets(char *buf, int len, FILE *sockfp)
+/* get a LF-terminated line, removing \r characters */
+{
+    int rdlen = 0;
+
+    while (--len)
+    {
+        if ((*buf = fgetc(sockfp)) == EOF)
+            return -1;
+        else
+           rdlen++;
+        if (*buf == '\n')
+            break;
+        if (*buf != '\r') /* remove all CRs */
+            buf++;
+    }
+    *buf = 0;
+    return rdlen;
+}
+
+static int gen_readmsg (sockfp, len, delimited, ctl)
 /* read message content and ship to SMTP or MDA */
-int socket;    /* to which the server is connected */
-int mboxfd;    /* descriptor to which retrieved message will be written */
-long len;      /* length of message */
-int delimited; /* does the protocol use a message delimiter? */
+FILE *sockfp;          /* to which the server is connected */
+long len;              /* length of message */
+int delimited;         /* does the protocol use a message delimiter? */
 struct query *ctl;     /* query control record */
-{ 
+{
     char buf [MSGBUFSIZE+1]; 
-    char fromBuf[MSGBUFSIZE+1];
-    char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
+    char *bufp, *headers, *fromhdr,*tohdr,*cchdr,*bcchdr,*received_for,*envto;
     int n, oldlen;
     int inheaders,lines,sizeticker;
-    /* This keeps the retrieved message count for display purposes */
-    static int msgnum = 0;  
+    FILE *sinkfp;
+    RETSIGTYPE (*sigchld)();
+#ifdef HAVE_GETHOSTBYNAME
+    char rbuf[HOSTLEN + USERNAMELEN + 4]; 
+#endif /* HAVE_GETHOSTBYNAME */
 
     /* read the message content from the server */
     inheaders = 1;
-    headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
+    headers = fromhdr = tohdr = cchdr = bcchdr = received_for = envto = NULL;
     lines = 0;
     sizeticker = 0;
+    oldlen = 0;
     while (delimited || len > 0)
     {
-       if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
+       if ((n = SockGets(buf,sizeof(buf),sockfp)) < 0)
            return(PS_SOCKET);
+       vtalarm(ctl->timeout);
 
        /* write the message size dots */
        if (n > 0)
@@ -481,14 +363,12 @@ struct query *ctl;        /* query control record */
        if (inheaders)
         {
            if (!ctl->norewrite)
-               reply_hack(bufp, ctl->servername);
+               reply_hack(bufp, ctl->servernames->id);
 
            if (!lines)
            {
                oldlen = strlen(bufp);
-               headers = malloc(oldlen + 1);
-               if (headers == NULL)
-                   return(PS_IOERR);
+               headers = xmalloc(oldlen + 1);
                (void) strcpy(headers, bufp);
                bufp = headers;
            }
@@ -500,7 +380,7 @@ struct query *ctl;  /* query control record */
                 * We deal with RFC822 continuation lines here.
                 * Replace previous '\n' with '\r' so nxtaddr 
                 * and reply_hack will be able to see past it.
-                * (We know this safe because SocketGets stripped
+                * (We know this is safe because SocketGets stripped
                 * out all carriage returns in the read loop above
                 * and we haven't reintroduced any since then.)
                 * We'll undo this before writing the header.
@@ -517,16 +397,71 @@ struct query *ctl;        /* query control record */
                oldlen = newlen;
            }
 
-           if (!strncmp(bufp,"From ",5))
-               unixfrom = bufp;
-           else if (!strncasecmp("From:", bufp, 5))
+           if (!strncasecmp("From:", bufp, 5))
+               fromhdr = bufp;
+           else if (!fromhdr && !strncasecmp("Resent-From:", bufp, 12))
+               fromhdr = bufp;
+           else if (!fromhdr && !strncasecmp("Apparently-From:", bufp, 16))
                fromhdr = bufp;
            else if (!strncasecmp("To:", bufp, 3))
                tohdr = bufp;
+           else if (!strncasecmp("Apparently-To:", bufp, 14))
+               envto = bufp;
+           else if (!strncasecmp(ctl->envelope, bufp, 14))
+               envto = bufp;
            else if (!strncasecmp("Cc:", bufp, 3))
                cchdr = bufp;
            else if (!strncasecmp("Bcc:", bufp, 4))
                bcchdr = bufp;
+#ifdef HAVE_GETHOSTBYNAME
+           else if (MULTIDROP(ctl) && !strncasecmp("Received:", bufp, 9))
+           {
+               char *ok;
+
+               /*
+                * Try to extract the real envelope addressee.  We look here
+                * specifically for the mailserver's Received line.
+                * Note: this will only work for sendmail, or an MTA that
+                * shares sendmail's convention of embedding the envelope
+                * address in the Received line.
+                */
+               strcpy(rbuf, "by ");
+               strcat(rbuf, ctl->canonical_name);
+               if ((ok = strstr(bufp, rbuf)))
+                   ok = strstr(ok, "for <");
+               else
+                   ok = (char *)NULL;
+               if (ok != 0)
+               {
+                   char        *sp, *tp;
+
+                   tp = rbuf;
+                   sp = ok + 4;
+                   if (*sp == '<')
+                       sp++;
+                   while (*sp && *sp != '>' && *sp != '@' && *sp != ';')
+                       if (!isspace(*sp))
+                           *tp++ = *sp++;
+                       else
+                       {
+                           /* uh oh -- whitespace here can't be right! */
+                           ok = (char *)NULL;
+                           break;
+                       }
+                   *tp = '\0';
+               }
+
+               if (ok != 0)
+               {
+                   received_for = alloca(strlen(rbuf)+1);
+                   strcpy(received_for, rbuf);
+                   if (outlevel == O_VERBOSE)
+                       error(0, 0, 
+                               "found Received address `%s'",
+                               received_for);
+               }
+           }
+#endif /* HAVE_GETHOSTBYNAME */
 
            goto skipwrite;
        }
@@ -534,50 +469,74 @@ struct query *ctl;        /* query control record */
        {
            char                *cp;
            struct idlist       *idp, *xmit_names;
+           int                 good_addresses, bad_addresses;
+#ifdef HAVE_RES_SEARCH
+           int                 no_local_matches = FALSE;
+#endif /* HAVE_RES_SEARCH */
 
            /* cons up a list of local recipients */
            xmit_names = (struct idlist *)NULL;
-#ifdef HAVE_GETHOSTBYNAME
+           bad_addresses = good_addresses = 0;
+#ifdef HAVE_RES_SEARCH
            /* is this a multidrop box? */
-           if (ctl->localnames != (struct idlist *)NULL
-               && ctl->localnames->next != (struct idlist *)NULL)
+           if (MULTIDROP(ctl))
            {
-               /* compute the local address list */
-               find_server_names(tohdr,  ctl, &xmit_names);
-               find_server_names(cchdr,  ctl, &xmit_names);
-               find_server_names(bcchdr, ctl, &xmit_names);
-
-               /* if nothing supplied localnames, default appropriately */
+               if (envto)          /* We have the actual envelope addressee */
+                   find_server_names(envto, ctl, &xmit_names);
+               else if (received_for)
+                   /*
+                    * We have the Received for addressee.  
+                    * It has to be a mailserver address, or we
+                    * wouldn't have got here.
+                    */
+                   map_name(received_for, ctl, &xmit_names);
+               else
+               {
+                   /*
+                    * We haven't extracted the envelope address.
+                    * So check all the header addresses.
+                    */
+                   find_server_names(tohdr,  ctl, &xmit_names);
+                   find_server_names(cchdr,  ctl, &xmit_names);
+                   find_server_names(bcchdr, ctl, &xmit_names);
+               }
                if (!xmit_names)
-                   save_uid(&xmit_names, -1, dfltuser);
+               {
+                   no_local_matches = TRUE;
+                   save_str(&xmit_names, -1, user);
+                   if (outlevel == O_VERBOSE)
+                       error(0, 0, 
+                               "no local matches, forwarding to %s",
+                               user);
+               }
            }
            else        /* it's a single-drop box, use first localname */
-#endif /* HAVE_GETHOSTBYNAME */
-           {
-               if (ctl->localnames)
-                   save_uid(&xmit_names, -1, ctl->localnames->id);
-               else
-                   save_uid(&xmit_names, -1, dfltuser);
-           }
+#endif /* HAVE_RES_SEARCH */
+               save_str(&xmit_names, -1, ctl->localnames->id);
 
            /* time to address the message */
            if (ctl->mda[0])    /* we have a declared MDA */
            {
-               int     i, nlocals = 0;
-               char    **sargv, **sp;
+               int     length = 0;
+               char    *names, *cmd;
 
                /*
                 * We go through this in order to be able to handle very
-                * long lists of users.
+                * long lists of users and (re)implement %s.
                 */
                for (idp = xmit_names; idp; idp = idp->next)
-                   nlocals++;
-               sp = sargv = (char **)alloca(ctl->mda_argcount+nlocals+2);
-               for (i = 0; i <= ctl->mda_argcount; i++)
-                   *sp++ = ctl->mda_argv[i];
+                   length += (strlen(idp->id) + 1);
+               names = (char *)alloca(length);
+               names[0] = '\0';
                for (idp = xmit_names; idp; idp = idp->next)
-                   *sp++ = idp->id;
-               *sp =  (char *)NULL;
+               {
+                   strcat(names, idp->id);
+                   strcat(names, " ");
+               }
+               cmd = (char *)alloca(strlen(ctl->mda) + length);
+               sprintf(cmd, ctl->mda, names);
+               if (outlevel == O_VERBOSE)
+                   error(0, 0, "about to deliver with: %s", cmd);
 
 #ifdef HAVE_SETEUID
                /*
@@ -589,80 +548,199 @@ struct query *ctl;       /* query control record */
                seteuid(ctl->uid);
 #endif /* HAVE_SETEUID */
 
-               mboxfd = openmailpipe(sargv);
+               sinkfp = popen(cmd, "w");
 
 #ifdef HAVE_SETEUID
                /* this will fail quietly if we didn't start as root */
                seteuid(0);
 #endif /* HAVE_SETEUID */
 
-               if (mboxfd < 0)
+               if (!sinkfp)
+               {
+                   error(0, 0, "MDA open failed");
                    return(PS_IOERR);
+               }
+
+               sigchld = signal(SIGCHLD, SIG_DFL);
            }
            else
            {
-               if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
+               char    *ap;
+
+               /* build a connection to the SMTP listener */
+               if (ctl->mda[0] == '\0' && ((sinkfp = smtp_open(ctl)) == NULL))
+               {
+                   free_str_list(&xmit_names);
+                   error(0, 0, "SMTP connect failed");
                    return(PS_SMTP);
+               }
+
+               /*
+                * 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 (!fromhdr || !(ap = nxtaddr(fromhdr)))
+               {
+                   if (SMTP_from(sinkfp, user) != SM_OK)
+                       return(PS_SMTP);        /* should never happen */
+               }
+               else if (SMTP_from(sinkfp, ap) != SM_OK)
+                   if (smtp_response == 571)
+                   {
+                       /*
+                        * SMTP listener explicitly refuses to deliver
+                        * mail coming from this address, probably due
+                        * to an anti-spam domain exclusion.  Respect
+                        * this.
+                        */
+                       sinkfp = (FILE *)NULL;
+                       goto skiptext;
+                   }
+                   else if (SMTP_from(sinkfp, user) != SM_OK)
+                       return(PS_SMTP);        /* should never happen */
 
+               /* now list the recipient addressees */
                for (idp = xmit_names; idp; idp = idp->next)
-                   if (SMTP_rcpt(mboxfd, idp->id) != SM_OK)
-                       return(PS_SMTP);
+                   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 (!good_addresses && SMTP_rcpt(sinkfp, user) != SM_OK)
+               {
+                   error(0, 0, 
+                           "can't even send to calling user!");
+                   return(PS_SMTP);
+               }
 
-               SMTP_data(mboxfd);
-               if (outlevel == O_VERBOSE)
-                   fputs("SMTP> ", stderr);
+               /* tell it we're ready to send data */
+               SMTP_data(sinkfp);
+
+           skiptext:;
            }
-           free_uid_list(&xmit_names);
 
            /* change continuation markers back to regular newlines */
-           for (cp = headers; cp < headers +  oldlen; cp++)
+           for (cp = headers; cp < headers + oldlen; cp++)
                if (*cp == '\r')
                    *cp = '\n';
 
            /* replace all LFs with CR-LF before sending to the SMTP server */
            if (!ctl->mda[0])
            {
-               char *newheaders = malloc(1 + oldlen * 2);
+               char *newheaders = xmalloc(1 + oldlen * 2);
 
-               if (newheaders == NULL)
-                   return(PS_IOERR);
                oldlen = strcrlf(newheaders, headers, oldlen);
                free(headers);
                headers = newheaders;
            }
-           if (write(mboxfd,headers,oldlen) < 0)
+
+           /* write all the headers */
+           n = 0;
+           if (sinkfp)
+               n = fwrite(headers, 1, oldlen, sinkfp);
+
+           if (n < 0)
            {
                free(headers);
                headers = NULL;
-               perror("gen_readmsg: writing RFC822 headers");
+               error(0, errno, "writing RFC822 headers");
+               if (ctl->mda[0])
+               {
+                   pclose(sinkfp);
+                   signal(SIGCHLD, sigchld);
+               }
                return(PS_IOERR);
            }
            else if (outlevel == O_VERBOSE)
                fputs("#", stderr);
            free(headers);
            headers = NULL;
+
+           /* write error notifications */
+#ifdef HAVE_RES_SEARCH
+           if (no_local_matches || bad_addresses)
+#else
+           if (bad_addresses)
+#endif /* HAVE_RES_SEARCH */
+           {
+               int     errlen = 0;
+               char    errhd[USERNAMELEN + POPBUFSIZE], *errmsg;
+
+               errmsg = errhd;
+               (void) strcpy(errhd, "X-Fetchmail-Warning: ");
+#ifdef HAVE_RES_SEARCH
+               if (no_local_matches)
+               {
+                   strcat(errhd, "no recipient addresses matched declared local names");
+                   if (bad_addresses)
+                       strcat(errhd, "; ");
+               }
+#endif /* HAVE_RES_SEARCH */
+
+               if (bad_addresses)
+               {
+                   strcat(errhd, "SMTP listener rejected local recipient addresses: ");
+                   errlen = strlen(errhd);
+                   for (idp = xmit_names; idp; idp = idp->next)
+                       if (!idp->val.num)
+                           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)
+                       {
+                           strcat(errmsg, idp->id);
+                           if (idp->next)
+                               strcat(errmsg, ", ");
+                       }
+               }
+
+               strcat(errmsg, "\n");
+
+               if (sinkfp)
+                   fputs(errmsg, sinkfp);
+           }
+
+           free_str_list(&xmit_names);
        }
 
        /* SMTP byte-stuffing */
        if (*bufp == '.' && ctl->mda[0] == 0)
-           write(mboxfd, ".", 1);
+           if (sinkfp)
+               fputs(".", sinkfp);
 
-       /* write this line to the file after replacing all LFs with CR-LF */
+       /* replace all LFs with CR-LF  in the line */
        if (!ctl->mda[0])
        {
-           char *newbufp = malloc(1 + strlen(bufp) * 2);
+           char *newbufp = xmalloc(1 + strlen(bufp) * 2);
 
-           if (newbufp == NULL)
-               return(PS_IOERR);
            strcrlf(newbufp, bufp, strlen(bufp));
            bufp = newbufp;
        }
-       n = write(mboxfd,bufp,strlen(bufp));
+
+       /* ship out the text line */
+       n = 0;
+       if (sinkfp)
+           n = fwrite(bufp, 1, strlen(bufp), sinkfp);
+
        if (!ctl->mda[0])
            free(bufp);
        if (n < 0)
        {
-           perror("gen_readmsg: writing message text");
+           error(0, errno, "writing message text");
+           if (ctl->mda[0])
+           {
+               pclose(sinkfp);
+               signal(SIGCHLD, sigchld);
+           }
            return(PS_IOERR);
        }
        else if (outlevel == O_VERBOSE)
@@ -674,15 +752,25 @@ struct query *ctl;        /* query control record */
 
     if (ctl->mda[0])
     {
+       int rc;
+
        /* close the delivery pipe, we'll reopen before next message */
-       if (closemailpipe(mboxfd))
+       rc = pclose(sinkfp);
+       signal(SIGCHLD, sigchld);
+       if (rc)
+       {
+           error(0, 0, "MDA exited abnormally or returned nonzero status");
            return(PS_IOERR);
-    }
-    else
+       }
+   }
+    else if (sinkfp)
     {
        /* write message terminator */
-       if (SMTP_eom(mboxfd) != SM_OK)
+       if (SMTP_eom(sinkfp) != SM_OK)
+       {
+           error(0, 0, "SMTP listener refused delivery");
            return(PS_SMTP);
+       }
     }
 
     return(0);
@@ -693,7 +781,7 @@ int
 kerberos_auth (socket, canonical) 
 /* authenticate to the server host using Kerberos V4 */
 int socket;            /* socket to server host */
-char *canonical;       /* server name */
+const char *canonical; /* server name */
 {
     char * host_primary;
     KTEXT ticket;
@@ -716,7 +804,7 @@ char *canonical;    /* server name */
     free (ticket);
     if (rem != KSUCCESS)
     {
-       fprintf (stderr, "fetchmail: kerberos error %s\n", (krb_get_err_text (rem)));
+       error(0, 0, "kerberos error %s", (krb_get_err_text (rem)));
        return (PS_ERROR);
     }
     return (0);
@@ -725,16 +813,17 @@ char *canonical;  /* server name */
 
 int do_protocol(ctl, proto)
 /* retrieve messages from server using given protocol method table */
-struct query *ctl;     /* parsed options with merged-in defaults */
-struct method *proto;          /* protocol method table */
+struct query *ctl;             /* parsed options with merged-in defaults */
+const struct method *proto;    /* protocol method table */
 {
-    int ok, mboxfd = -1;
+    int ok, js;
+    char *msg;
     void (*sigsave)();
 
 #ifndef KERBEROS_V4
     if (ctl->authenticate == A_KERBEROS)
     {
-       fputs("fetchmail: Kerberos support not linked.\n", stderr);
+       error(0, 0, "Kerberos support not linked.");
        return(PS_ERROR);
     }
 #endif /* KERBEROS_V4 */
@@ -744,22 +833,22 @@ struct method *proto;             /* protocol method table */
     {
        /* check for unsupported options */
        if (ctl->flush) {
-           fprintf(stderr,
-                   "Option --flush is not supported with %s\n",
+           error(0, 0,
+                   "Option --flush is not supported with %s",
                    proto->name);
            return(PS_SYNTAX);
        }
        else if (ctl->fetchall) {
-           fprintf(stderr,
-                   "Option --all is not supported with %s\n",
+           error(0, 0,
+                   "Option --all is not supported with %s",
                    proto->name);
            return(PS_SYNTAX);
        }
     }
     if (!proto->getsizes && ctl->limit)
     {
-       fprintf(stderr,
-               "Option --limit is not supported with %s\n",
+       error(0, 0,
+               "Option --limit is not supported with %s",
                proto->name);
        return(PS_SYNTAX);
     }
@@ -769,24 +858,33 @@ struct method *proto;             /* protocol method table */
     tag[0] = '\0';     /* nuke any tag hanging out from previous query */
     ok = 0;
 
-    if (setjmp(restart) == 1)
-       fprintf(stderr,
-               "fetchmail: timeout after %d seconds waiting for %s.\n",
-               ctl->timeout, ctl->servername);
+    /* set up the server-nonresponse timeout */
+    sigsave = signal(SIGVTALRM, vtalarm_handler);
+    vtalarm(mytimeout = ctl->timeout);
+
+    if ((js = setjmp(restart)) == 1)
+    {
+       error(0, 0,
+               "timeout after %d seconds waiting for %s.",
+               ctl->timeout, ctl->servernames->id);
+       ok = PS_ERROR;
+    }
+    else if (js == 2)
+    {
+       /* error message printed at point of longjmp */
+       ok = PS_ERROR;
+    }
     else
     {
-       char buf [POPBUFSIZE+1], host[HOSTLEN+1];
-       int *msgsizes, socket, len, num, count, new, deletions = 0;
-
-       /* set up the server-nonresponse timeout */
-       sigsave = signal(SIGALRM, alarm_handler);
-       alarm(ctl->timeout);
+       char buf [POPBUFSIZE+1];
+       int *msgsizes, len, num, count, new, deletions = 0;
+       FILE *sockfp;
 
        /* open a socket to the mail server */
-       if ((socket = Socket(ctl->servername,
-                            ctl->port ? ctl->port : protocol->port))<0)
+       if ((sockfp = sockopen(ctl->servernames->id,
+                            ctl->port ? ctl->port : protocol->port)) == NULL)
        {
-           perror("fetchmail, connecting to host");
+           error(0, errno, "connecting to host");
            ok = PS_SOCKET;
            goto closeUp;
        }
@@ -794,45 +892,52 @@ struct method *proto;             /* protocol method table */
 #ifdef KERBEROS_V4
        if (ctl->authenticate == A_KERBEROS)
        {
-           ok = (kerberos_auth (socket, ctl->canonical_name));
-           if (ok != 0)
+           ok = kerberos_auth(fileno(sockfp), ctl->canonical_name);
+           if (ok != 0)
                goto cleanUp;
+           vtalarm(ctl->timeout);
        }
 #endif /* KERBEROS_V4 */
 
        /* accept greeting message from mail server */
-       ok = (protocol->parse_response)(socket, buf);
+       ok = (protocol->parse_response)(sockfp, buf);
        if (ok != 0)
            goto cleanUp;
+       vtalarm(ctl->timeout);
 
        /* try to get authorized to fetch mail */
        shroud = ctl->password;
-       ok = (protocol->getauth)(socket, ctl, buf);
+       ok = (protocol->getauth)(sockfp, ctl, buf);
        shroud = (char *)NULL;
        if (ok == PS_ERROR)
            ok = PS_AUTHFAIL;
        if (ok != 0)
            goto cleanUp;
+       vtalarm(ctl->timeout);
 
        /* compute number of messages and number of new messages waiting */
-       if ((protocol->getrange)(socket, ctl, &count, &new) != 0)
+       ok = (protocol->getrange)(sockfp, ctl, &count, &new);
+       if (ok != 0)
            goto cleanUp;
+       vtalarm(ctl->timeout);
 
        /* show user how many messages we downloaded */
        if (outlevel > O_SILENT)
            if (count == 0)
-               fprintf(stderr, "No mail from %s@%s\n", 
+               error(0, 0, "No mail from %s@%s", 
                        ctl->remotename,
-                       ctl->servername);
+                       ctl->servernames->id);
            else
            {
-               fprintf(stderr, "%d message%s", count, count > 1 ? "s" : ""); 
                if (new != -1 && (count - new) > 0)
-                   fprintf(stderr, " (%d seen)", count-new);
-               fprintf(stderr,
-                       " from %s@%s.\n",
-                       ctl->remotename,
-                       ctl->servername);
+                   error(0, 0, "%d message%s (%d seen) from %s@%s.",
+                               count, count > 1 ? "s" : "", count-new,
+                               ctl->remotename,
+                               ctl->servernames->id);
+               else
+                   error(0, 0, "%d message%s from %s@%s.", count, count > 1 ? "s" : "",
+                               ctl->remotename,
+                               ctl->servernames->id);
            }
 
        /* we may need to get sizes in order to check message limits */
@@ -841,36 +946,54 @@ struct method *proto;             /* protocol method table */
        {
            msgsizes = (int *)alloca(sizeof(int) * count);
 
-           if ((ok = (proto->getsizes)(socket, count, msgsizes)) != 0)
-               return(PS_ERROR);
+           ok = (proto->getsizes)(sockfp, count, msgsizes);
+           if (ok != 0)
+               goto cleanUp;
+           vtalarm(ctl->timeout);
        }
 
+
        if (check_only)
        {
            if (new == -1 || ctl->fetchall)
                new = count;
            ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
-           goto closeUp;
+           goto cleanUp;
        }
        else if (count > 0)
-       {
-           if (ctl->mda[0] == '\0')
-               if ((mboxfd = Socket(ctl->smtphost, SMTP_PORT)) < 0
-                   || SMTP_ok(mboxfd, NULL) != SM_OK
-                   || SMTP_helo(mboxfd, ctl->servername) != SM_OK)
-               {
-                   ok = PS_SMTP;
-                   close(mboxfd);
-                   mboxfd = -1;
-                   goto cleanUp;
-               }
-    
+       {    
+           /*
+            * What forces this code is that in POP3 and IMAP2BIS you can't
+            * fetch a message without having it marked `seen'.  In IMAP4,
+            * on the other hand, you can (peek_capable is set to convey
+            * this).
+            *
+            * The result of being unable to peek is that if there's
+            * any kind of transient error (DNS lookup failure, or
+            * sendmail refusing delivery due to process-table limits)
+            * the message will be marked "seen" on the server without
+            * having been delivered.  This is not a big problem if
+            * fetchmail is running in foreground, because the user
+            * will see a "skipped" message when it next runs and get
+            * clued in.
+            *
+            * But in daemon mode this leads to the message being silently
+            * ignored forever.  This is not acceptable.
+            *
+            * We compensate for this by checking the error count from the 
+            * previous pass and forcing all messages to be considered new
+            * if it's nonzero.
+            */
+           int force_retrieval = !peek_capable && (ctl->errcount > 0);
+
+           ctl->errcount = 0;
+
            /* read, forward, and delete messages */
            for (num = 1; num <= count; num++)
            {
-               int     toolarge = msgsizes && msgsizes[num-1]>ctl->limit;
+               int     toolarge = msgsizes && (msgsizes[num-1] > ctl->limit);
                int     fetch_it = ctl->fetchall ||
-                   (!(protocol->is_old && (protocol->is_old)(socket,ctl,num)) && !toolarge);
+                   (!toolarge && (force_retrieval || !(protocol->is_old && (protocol->is_old)(sockfp,ctl,num))));
 
                /* we may want to reject this message if it's old */
                if (!fetch_it)
@@ -885,7 +1008,10 @@ struct method *proto;             /* protocol method table */
                else
                {
                    /* request a message */
-                   (protocol->fetch)(socket, num, &len);
+                   ok = (protocol->fetch)(sockfp, num, &len);
+                   if (ok != 0)
+                       goto cleanUp;
+                   vtalarm(ctl->timeout);
 
                    if (outlevel > O_SILENT)
                    {
@@ -898,25 +1024,23 @@ struct method *proto;            /* protocol method table */
                            fputc(' ', stderr);
                    }
 
-                   /*
-                    * If we're forwarding via SMTP, mboxfd is initialized
-                    * at this point (it was set at start of retrieval). 
-                    * If we're using an MDA it's not set -- gen_readmsg()
-                    * may have to parse message headers to know what
-                    * delivery addresses should be passed to the MDA
-                    */
-
                    /* read the message and ship it to the output sink */
-                   ok = gen_readmsg(socket, mboxfd,
+                   ok = gen_readmsg(sockfp,
                                     len, 
                                     protocol->delimited,
                                     ctl);
+                   if (ok != 0)
+                       goto cleanUp;
+                   vtalarm(ctl->timeout);
 
                    /* tell the server we got it OK and resynchronize */
                    if (protocol->trail)
-                       (protocol->trail)(socket, ctl, num);
-                   if (ok != 0)
-                       goto cleanUp;
+                   {
+                       ok = (protocol->trail)(sockfp, ctl, num);
+                       if (ok != 0)
+                           goto cleanUp;
+                       vtalarm(ctl->timeout);
+                   }
                }
 
                /*
@@ -934,71 +1058,94 @@ struct method *proto;            /* protocol method table */
                {
                    deletions++;
                    if (outlevel > O_SILENT) 
-                       fprintf(stderr, " flushed\n", num);
-                   ok = (protocol->delete)(socket, ctl, num);
+                       fprintf(stderr, " flushed\n");
+                   ok = (protocol->delete)(sockfp, ctl, num);
                    if (ok != 0)
                        goto cleanUp;
+                   vtalarm(ctl->timeout);
+                   delete_str(&ctl->newsaved, num);
                }
                else if (outlevel > O_SILENT) 
-               {
-                   /* nuke it from the unseen-messages list */
-                   delete_uid(&ctl->newsaved, num);
-                   fprintf(stderr, " not flushed\n", num);
-               }
+                   fprintf(stderr, " not flushed\n");
            }
 
            /* remove all messages flagged for deletion */
            if (protocol->expunge_cmd && deletions > 0)
            {
-               ok = gen_transact(socket, protocol->expunge_cmd);
+               ok = gen_transact(sockfp, protocol->expunge_cmd);
                if (ok != 0)
                    goto cleanUp;
+               vtalarm(ctl->timeout);
            }
 
-           ok = gen_transact(socket, protocol->exit_cmd);
+           ok = gen_transact(sockfp, protocol->exit_cmd);
            if (ok == 0)
                ok = PS_SUCCESS;
-           close(socket);
+           vtalarm(0);
+           fclose(sockfp);
            goto closeUp;
        }
        else {
-           ok = gen_transact(socket, protocol->exit_cmd);
+           ok = gen_transact(sockfp, protocol->exit_cmd);
            if (ok == 0)
                ok = PS_NOMAIL;
-           close(socket);
+           vtalarm(0);
+           fclose(sockfp);
            goto closeUp;
        }
 
     cleanUp:
+       vtalarm(ctl->timeout);
        if (ok != 0 && ok != PS_SOCKET)
-       {
-           gen_transact(socket, protocol->exit_cmd);
-           close(socket);
-       }
+           gen_transact(sockfp, protocol->exit_cmd);
+       vtalarm(0);
+       fclose(sockfp);
     }
 
-    alarm(0);
-    signal(SIGALRM, sigsave);
-
-closeUp:
-    if (mboxfd != -1)
+    switch (ok)
     {
-        if (!ctl->mda[0])
-           SMTP_quit(mboxfd);
-       close(mboxfd);
+    case PS_SOCKET:
+       msg = "socket";
+       break;
+    case PS_AUTHFAIL:
+       msg = "authorization";
+       break;
+    case PS_SYNTAX:
+       msg = "missing or bad RFC822 header";
+       break;
+    case PS_IOERR:
+       msg = "MDA";
+       break;
+    case PS_ERROR:
+       msg = "client/server synchronization";
+       break;
+    case PS_PROTOCOL:
+       msg = "client/server protocol";
+       break;
+    case PS_SMTP:
+       msg = "SMTP transaction";
+       break;
+    case PS_UNDEFINED:
+       error(0, 0, "undefined");
+       break;
     }
+    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 talking to %s", msg, ctl->servernames->id);
 
+closeUp:
+    signal(SIGVTALRM, sigsave);
     return(ok);
 }
 
 #if defined(HAVE_STDARG_H)
-void gen_send(int socket, char *fmt, ... )
+void gen_send(FILE *sockfp, char *fmt, ... )
 /* assemble command in printf(3) style and send to the server */
 {
 #else
-void gen_send(socket, fmt, va_alist)
+void gen_send(sockfp, fmt, va_alist)
 /* assemble command in printf(3) style and send to the server */
-int socket;            /* socket to which server is connected */
+FILE *sockfp;          /* socket to which server is connected */
 const char *fmt;       /* printf-style format */
 va_dcl {
 #endif
@@ -1019,7 +1166,8 @@ va_dcl {
     vsprintf(buf + strlen(buf), fmt, ap);
     va_end(ap);
 
-    SockPuts(socket, buf);
+    strcat(buf, "\r\n");
+    fputs(buf, sockfp);
 
     if (outlevel == O_VERBOSE)
     {
@@ -1027,18 +1175,19 @@ va_dcl {
 
        if (shroud && (cp = strstr(buf, shroud)))
            memset(cp, '*', strlen(shroud));
-       fprintf(stderr,"> %s\n", buf);
+       buf[strlen(buf)-1] = '\0';
+       error(0, 0, "%s> %s", protocol->name, buf);
     }
 }
 
 #if defined(HAVE_STDARG_H)
-int gen_transact(int socket, char *fmt, ... )
+int gen_transact(FILE *sockfp, char *fmt, ... )
 /* assemble command in printf(3) style, send to server, accept a response */
 {
 #else
-int gen_transact(socket, fmt, va_alist)
+int gen_transact(sockfp, fmt, va_alist)
 /* assemble command in printf(3) style, send to server, accept a response */
-int socket;            /* socket to which server is connected */
+FILE *sockfp;          /* socket to which server is connected */
 const char *fmt;       /* printf-style format */
 va_dcl {
 #endif
@@ -1060,18 +1209,21 @@ va_dcl {
   vsprintf(buf + strlen(buf), fmt, ap);
   va_end(ap);
 
-  SockPuts(socket, buf);
+  strcat(buf, "\r\n");
+  fputs(buf, sockfp);
   if (outlevel == O_VERBOSE)
   {
       char *cp;
 
       if (shroud && (cp = strstr(buf, shroud)))
          memset(cp, '*', strlen(shroud));
-      fprintf(stderr,"> %s\n", buf);
+      buf[strlen(buf)-1] = '\0';
+      error(0, 0, "%s> %s", protocol->name, buf);
   }
 
   /* we presume this does its own response echoing */
-  ok = (protocol->parse_response)(socket, buf);
+  ok = (protocol->parse_response)(sockfp, buf);
+  vtalarm(mytimeout);
 
   return(ok);
 }