]> Pileus Git - ~andy/fetchmail/blobdiff - driver.c
gcc -Wall cleanup.
[~andy/fetchmail] / driver.c
index e940ae296d7777474ac01ec6e9c32432156a443b..15d2da9a36496c48881b9f81657ca98a125db43a 100644 (file)
--- a/driver.c
+++ b/driver.c
@@ -1,23 +1,35 @@
-/* Copyright 1996 by Eric S. Raymond
+/*
+ * driver.c -- generic driver for mail fetch method protocols
+ *
+ * Copyright 1996 by Eric S. Raymond
  * All rights reserved.
  * For license terms, see the file COPYING in this directory.
  */
 
-/***********************************************************************
-  module:       driver.c
-  project:      fetchmail
-  programmer:   Eric S. Raymond
-  description:  Generic driver for mail fetch method protocols
-
- ***********************************************************************/
-
 #include  <config.h>
 #include  <stdio.h>
-#include  <malloc.h>
+#include  <setjmp.h>
+#include  <ctype.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
 #include  <sys/time.h>
 #include  <signal.h>
 
+#ifdef HAVE_GETHOSTBYNAME
+#include <netdb.h>
+#include "mx.h"
+#endif /* HAVE_GETHOSTBYNAME */
+
 #ifdef KERBEROS_V4
 #include <krb.h>
 #include <des.h>
 
 #define        SMTP_PORT       25      /* standard SMTP service port */
 
-static struct method *protocol;
-
-static int alarmed; /* A flag to indicate that SIGALRM happened */
-int timeout = CLIENT_TIMEOUT;
+static const struct method *protocol;
+static jmp_buf restart;
 
 char tag[TAGLEN];
 static int tagnum;
 #define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
 
-/*********************************************************************
-  function:      
-  description:   hack message headers so replies will work properly
+static char *shroud;
 
-  arguments:
-    after        where to put the hacked header
-    before       header to hack
-    host         name of the pop header
+static int strcrlf(dst, src, count)
+/* replace LFs with CR-LF; return length of string with replacements */
+char *dst;     /* new string with CR-LFs */
+char *src;     /* original string with LFs */
+int count;     /* length of src */
+{
+  int len = count;
+
+  while (count--)
+  {
+      if (*src == '\n')
+      {
+         *dst++ = '\r';
+         len++;
+      }
+      *dst++ = *src++;
+  }
+  *dst = '\0';
+  return len;
+}
 
-  return value:  none.
-  calls:         none.
- *********************************************************************/
+static void alarm_handler (int signal)
+/* handle server-timeout signal */
+{
+    longjmp(restart, 1);
+}
 
 static void reply_hack(buf, host)
-/* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
-char *buf;
-const char *host;
+/* hack message headers so replies will work properly */
+char *buf;             /* header to be hacked */
+const char *host;      /* server hostname */
 {
     const char *from;
-    int state = 0;
+    int state = 0, tokencount = 0;
     char mycopy[POPBUFSIZE+1];
 
     if (strncmp("From: ", buf, 6)
@@ -81,24 +107,37 @@ const char *host;
 
        case 1:   /* we've seen the colon, we're looking for addresses */
            if (*from == '"')
-               state = 2;
+               state = 3;
            else if (*from == '(')
-               state = 3;    
+               state = 4;    
            else if (*from == '<' || isalnum(*from))
-               state = 4;
+               state = 5;
+           else if (isspace(*from))
+               state = 2;
+           else if (*from == ',')
+               tokencount = 0;
            break;
 
-       case 2:   /* we're in a quoted human name, copy and ignore */
+       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 3:   /* we're in a parenthesized human name, copy and ignore */
+       case 4:   /* we're in a parenthesized human name, copy and ignore */
            if (*from == ')')
                state = 1;
            break;
 
-       case 4:   /* the real work gets done here */
+       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
@@ -106,19 +145,35 @@ const char *host;
             */
            /* if the address part contains an @, don't mess with it */
            if (*from == '@')
-               state = 5;
+               state = 6;
 
            /* If the address token is not properly terminated, ignore it. */
            else if (*from == ' ' || *from == '\t')
-               state = 1;
+           {
+               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.  Cases \r and \n catch the case
-            * of a single ID alone on the line.
+            * comma-separated bare IDs.
             */
-           else if (strchr(">,\r\n", *from))
+           else if (strchr(">,", *from))
            {
                strcpy(buf, "@");
                strcat(buf, host);
@@ -126,10 +181,19 @@ const char *host;
                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;
+           }
+
            /* everything else, including alphanumerics, just passes through */
            break;
 
-       case 5:   /* we're in a remote mail ID, no need to append hostname */
+       case 6:   /* we're in a remote mail ID, no need to append hostname */
            if (*from == '>' || *from == ',' || isspace(*from))
                state = 1;
            break;
@@ -141,25 +205,19 @@ const char *host;
     *buf++ = '\0';
 }
 
-/*********************************************************************
-  function:      nxtaddr
-  description:   Parse addresses in succession out of a specified RFC822
-                 header.  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.
-  arguments:     
-    hdr          header line to be parsed, NUL to continue in previous hdr
-
-  return value:  next address, or NUL if there is no next address
-  calls:         none
- *********************************************************************/
-
 static char *nxtaddr(hdr)
-char *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 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;
@@ -181,7 +239,13 @@ char *hdr;
            break;
 
        case 1:   /* we've seen the colon, now grab the address */
-           if ((*hp == '\n') || (*hp == ','))  /* end of address list */
+           if (*hp == '\n')    /* end of address list */
+           {
+               *tp++ = '\0';
+               state = 6;
+               return(address);
+           }
+           else if (*hp == ',')  /* end of address */
            {
                *tp++ = '\0';
                return(address);
@@ -255,70 +319,169 @@ char *hdr;
                state = 4;
            }
            break;
+
+       case 6:
+           return(NULL);
+           break;
        }
     }
 
     return(NULL);
 }
 
-/*********************************************************************
-  function:      gen_readmsg
-  description:   Read the message content 
-
- as described in RFC 1225.
-  arguments:     
-    socket       ... to which the server is connected.
-    mboxfd       open file descriptor to which the retrieved message will
-                 be written.
-    len          length of text 
-    delimited    does the protocol use a message delimiter?
-    queryctl     host control block
-
-  return value:  zero if success else PS_* return code.
-  calls:         SockGets.
-  globals:       reads outlevel. 
- *********************************************************************/
-
-static int gen_readmsg (socket, mboxfd, len, delimited, queryctl)
-int socket;
-int mboxfd;
-long len;
-int delimited;
-struct hostrec *queryctl;
+#ifdef HAVE_GETHOSTBYNAME
+#define MX_RETRIES     3
+
+static int is_host_alias(name, ctl)
+/* determine whether name is a DNS alias of the hostname */
+const char *name;
+struct query   *ctl;
+{
+    struct hostent     *he;
+    int                        i;
+
+    /*
+     * 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
+     * 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)
+       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.
+     */
+    else if ((he = gethostbyname(name)) && strcmp(ctl->canonical_name, he->h_name) == 0)
+       return(TRUE);
+
+    /*
+     * Search for a name match on MX records pointing to the server
+     * site.  These may live far away, so allow a couple of retries.
+     */
+    for (i = 0; i < MX_RETRIES; i++)
+    {
+       struct mxentry *mxrecords, *mxp;
+
+       mxrecords = getmxrecords(name);
+
+       if (mxrecords == (struct mxentry *)NULL)
+           if (h_errno == TRY_AGAIN)
+           {
+               sleep(1);
+               continue;
+           }
+           else
+               break;
+
+       for (mxp = mxrecords; mxp->name; mxp++)
+           if (strcmp(name, mxp->name) == 0)
+               return(TRUE);
+    }
+
+    return(FALSE);
+}
+
+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 idlist **xmit_names;    /* list of recipient names parsed out */
+{
+    if (hdr == (char *)NULL)
+       return;
+    else
+    {
+       char    *cp, *lname;
+
+       if ((cp = nxtaddr(hdr)) != (char *)NULL)
+           do {
+               char    *atsign = strchr(cp, '@');
+
+               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 (outlevel == O_VERBOSE)
+                       fprintf(stderr,
+                               "fetchmail: mapped %s to local %s\n",
+                               cp, lname);
+                   save_uid(xmit_names, -1, lname);
+               }
+           } while
+               ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
+    }
+}
+#endif /* HAVE_GETHOSTBYNAME */
+
+static int gen_readmsg (socket, mboxfd, 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? */
+struct query *ctl;     /* query control record */
 { 
     char buf [MSGBUFSIZE+1]; 
-    char fromBuf[MSGBUFSIZE+1];
     char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
     int n, oldlen;
-    int inheaders;
-    int lines,sizeticker;
-    time_t now;
-    /* This keeps the retrieved message count for display purposes */
-    static int msgnum = 0;  
+    int inheaders,lines,sizeticker;
 
     /* read the message content from the server */
     inheaders = 1;
     headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
     lines = 0;
     sizeticker = 0;
-    while (delimited || len > 0) {
+    while (delimited || len > 0)
+    {
        if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
            return(PS_SOCKET);
+
+       /* write the message size dots */
+       if (n > 0)
+       {
+           sizeticker += n;
+           while (sizeticker >= SIZETICKER)
+           {
+               if (outlevel > O_SILENT)
+                   fputc('.',stderr);
+               sizeticker -= SIZETICKER;
+           }
+       }
        len -= n;
        bufp = buf;
        if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
            inheaders = 0;
-       if (*bufp == '.') {
+       if (delimited && *bufp == '.') {
            bufp++;
-           if (delimited && *bufp == 0)
+           if (*bufp == 0)
                break;  /* end of message */
        }
        strcat(bufp, "\n");
      
        if (inheaders)
         {
-           if (!queryctl->norewrite)
-               reply_hack(bufp, queryctl->servername);
+           if (!ctl->norewrite)
+               reply_hack(bufp, ctl->servername);
 
            if (!lines)
            {
@@ -356,74 +519,117 @@ struct hostrec *queryctl;
 
            if (!strncmp(bufp,"From ",5))
                unixfrom = bufp;
-           else if (!strncasecmp("From: ", bufp, 6))
+           else if (!strncasecmp("From:", bufp, 5))
                fromhdr = bufp;
-           else if (!strncasecmp("To: ", bufp, 4))
+           else if (!strncasecmp("To:", bufp, 3))
                tohdr = bufp;
-           else if (!strncasecmp("Cc: ", bufp, 4))
+           else if (!strncasecmp("Cc:", bufp, 3))
                cchdr = bufp;
-           else if (!strncasecmp("Bcc: ", bufp, 5))
+           else if (!strncasecmp("Bcc:", bufp, 4))
                bcchdr = bufp;
 
            goto skipwrite;
        }
-       else if (headers)
+       else if (headers)       /* OK, we're at end of headers now */
        {
-           char        *cp;
+           char                *cp;
+           struct idlist       *idp, *xmit_names;
+
+           /* cons up a list of local recipients */
+           xmit_names = (struct idlist *)NULL;
+#ifdef HAVE_GETHOSTBYNAME
+           /* is this a multidrop box? */
+           if (ctl->localnames != (struct idlist *)NULL
+               && ctl->localnames->next != (struct idlist *)NULL)
+           {
+               /* 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 (!xmit_names)
+                   save_uid(&xmit_names, -1, dfltuser);
+           }
+           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);
+           }
 
-           if (!queryctl->mda[0])
+           /* time to address the message */
+           if (ctl->mda[0])    /* we have a declared MDA */
            {
-               if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
-                   return(PS_SMTP);
-#ifdef SMTP_RESEND
+               int     i, nlocals = 0;
+               char    **sargv, **sp;
+
                /*
-                * This is what we'd do if fetchmail were a real MDA
-                * a la sendmail -- crack all the destination headers
-                * and send to every address we can reach via SMTP.
+                * We go through this in order to be able to handle very
+                * long lists of users.
                 */
-               if (tohdr && (cp = nxtaddr(tohdr)) != (char *)NULL)
-                   do {
-                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
-                           return(PS_SMTP);
-                   } while
-                       (cp = nxtaddr(NULL));
-               if (cchdr && (cp = nxtaddr(cchdr)) != (char *)NULL)
-                   do {
-                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
-                           return(PS_SMTP);
-                   } while
-                       (cp = nxtaddr(NULL));
-               if (bcchdr && (cp = nxtaddr(bcchdr)) != (char *)NULL)
-                   do {
-                       if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
-                           return(PS_SMTP);
-                   } while
-                       (cp = nxtaddr(NULL));
-#else
+               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];
+               for (idp = xmit_names; idp; idp = idp->next)
+                   *sp++ = idp->id;
+               *sp =  (char *)NULL;
+
+#ifdef HAVE_SETEUID
                /*
-                * Since we're really only fetching mail for one user
-                * per host query, we can be simpler
+                * Arrange to run with user's permissions if we're root.
+                * This will initialize the ownership of any files the
+                * MDA creates properly.  (The seteuid call is available
+                * under all BSDs and Linux)
                 */
-               if (SMTP_rcpt(mboxfd, queryctl->localname) == SM_UNRECOVERABLE)
+               seteuid(ctl->uid);
+#endif /* HAVE_SETEUID */
+
+               mboxfd = openmailpipe(sargv);
+
+#ifdef HAVE_SETEUID
+               /* this will fail quietly if we didn't start as root */
+               seteuid(0);
+#endif /* HAVE_SETEUID */
+
+               if (mboxfd < 0)
+                   return(PS_IOERR);
+           }
+           else
+           {
+               if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
                    return(PS_SMTP);
-#endif /* SMTP_RESEND */
+
+               for (idp = xmit_names; idp; idp = idp->next)
+                   if (SMTP_rcpt(mboxfd, idp->id) != SM_OK)
+                       return(PS_SMTP);
+
                SMTP_data(mboxfd);
                if (outlevel == O_VERBOSE)
                    fputs("SMTP> ", stderr);
            }
+           free_uid_list(&xmit_names);
 
            /* change continuation markers back to regular newlines */
            for (cp = headers; cp < headers +  oldlen; cp++)
                if (*cp == '\r')
                    *cp = '\n';
 
-           /*
-            * Strictly speaking, we ought to replace all LFs
-            * with CR-LF before shipping to an SMTP listener.
-            * Since most SMTP listeners built since the mid-1980s
-            * (and *all* Unix listeners) accept lone LF as equivalent
-            * to CR-LF, we'll skip this particular contortion.
-            */
+           /* replace all LFs with CR-LF before sending to the SMTP server */
+           if (!ctl->mda[0])
+           {
+               char *newheaders = malloc(1 + oldlen * 2);
+
+               if (newheaders == NULL)
+                   return(PS_IOERR);
+               oldlen = strcrlf(newheaders, headers, oldlen);
+               free(headers);
+               headers = newheaders;
+           }
            if (write(mboxfd,headers,oldlen) < 0)
            {
                free(headers);
@@ -438,11 +644,23 @@ struct hostrec *queryctl;
        }
 
        /* SMTP byte-stuffing */
-       if (*bufp == '.' && queryctl->mda[0] == 0)
+       if (*bufp == '.' && ctl->mda[0] == 0)
            write(mboxfd, ".", 1);
 
-       /* write this line to the file -- comment about CR-LF vs. LF applies */
-       if (write(mboxfd,bufp,strlen(bufp)) < 0)
+       /* write this line to the file after replacing all LFs with CR-LF */
+       if (!ctl->mda[0])
+       {
+           char *newbufp = malloc(1 + strlen(bufp) * 2);
+
+           if (newbufp == NULL)
+               return(PS_IOERR);
+           strcrlf(newbufp, bufp, strlen(bufp));
+           bufp = newbufp;
+       }
+       n = write(mboxfd,bufp,strlen(bufp));
+       if (!ctl->mda[0])
+           free(bufp);
+       if (n < 0)
        {
            perror("gen_readmsg: writing message text");
            return(PS_IOERR);
@@ -451,49 +669,31 @@ struct hostrec *queryctl;
            fputc('*', stderr);
 
     skipwrite:;
-
-       /* write the message size dots */
-       sizeticker += strlen(bufp);
-       while (sizeticker >= SIZETICKER)
-       {
-           if (outlevel > O_SILENT && outlevel < O_VERBOSE)
-               fputc('.',stderr);
-           sizeticker -= SIZETICKER;
-
-           /* reset timeout so we don't choke on very long messages */
-           alarm(timeout);
-       }
        lines++;
     }
 
-    if (alarmed)
-       return (0);
-    /* write message terminator */
-    if (!queryctl->mda[0])
+    if (ctl->mda[0])
+    {
+       /* close the delivery pipe, we'll reopen before next message */
+       if (closemailpipe(mboxfd))
+           return(PS_IOERR);
+    }
+    else
+    {
+       /* write message terminator */
        if (SMTP_eom(mboxfd) != SM_OK)
            return(PS_SMTP);
+    }
+
     return(0);
 }
 
 #ifdef KERBEROS_V4
-/*********************************************************************
-  function:      kerberos_auth
-  description:   Authenticate to the server host using Kerberos V4
-
-  arguments:     
-    socket       socket to server
-    servername   server name
-
-  return value:  exit code from the set of PS_.* constants defined in 
-                 fetchmail.h
-  calls:
-  globals:       reads outlevel.
- *********************************************************************/
-
 int
-kerberos_auth (socket, servername) 
-     int socket;
-     char *servername;
+kerberos_auth (socket, canonical) 
+/* authenticate to the server host using Kerberos V4 */
+int socket;            /* socket to server host */
+char *canonical;       /* server name */
 {
     char * host_primary;
     KTEXT ticket;
@@ -502,23 +702,10 @@ kerberos_auth (socket, servername)
     Key_schedule schedule;
     int rem;
   
-    /* Get the primary name of the host.  */
-    {
-       struct hostent * hp = (gethostbyname (servername));
-       if (hp == 0)
-       {
-           fprintf (stderr, "fetchmail: server %s unknown: n", servername);
-           return (PS_ERROR);
-       }
-       host_primary = ((char *) (malloc ((strlen (hp -> h_name)) + 1)));
-       strcpy (host_primary, (hp -> h_name));
-    }
-  
     ticket = ((KTEXT) (malloc (sizeof (KTEXT_ST))));
-    rem
-       = (krb_sendauth (0L, socket, ticket, "pop",
-                        host_primary,
-                        ((char *) (krb_realmofhost (host_primary))),
+    rem = (krb_sendauth (0L, socket, ticket, "pop",
+                        canonical,
+                        ((char *) (krb_realmofhost (canonical))),
                         ((unsigned long) 0),
                         (&msg_data),
                         (&cred),
@@ -527,7 +714,6 @@ kerberos_auth (socket, servername)
                         ((struct sockaddr_in *) 0),
                         "KPOPV0.1"));
     free (ticket);
-    free (host_primary);
     if (rem != KSUCCESS)
     {
        fprintf (stderr, "fetchmail: kerberos error %s\n", (krb_get_err_text (rem)));
@@ -537,39 +723,16 @@ kerberos_auth (socket, servername)
 }
 #endif /* KERBEROS_V4 */
 
-/*********************************************************************
-  function:      do_protocol
-  description:   retrieve messages from the specified mail server
-                 using a given set of methods
-
-  arguments:     
-    queryctl     fully-specified options (i.e. parsed, defaults invoked,
-                 etc).
-    proto        protocol method pointer
-
-  return value:  exit code from the set of PS_.* constants defined in 
-                 fetchmail.h
-  calls:
-  globals:       reads outlevel.
- *********************************************************************/
-
-int do_protocol(queryctl, proto)
-struct hostrec *queryctl;
-struct method *proto;
+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, len;
-    int mboxfd = -1;
-    char buf [POPBUFSIZE+1], host[HOSTLEN+1];
-    int socket;
+    int ok, mboxfd = -1;
     void (*sigsave)();
-    int num, count, deletions = 0;
-
-    alarmed = 0;
-    sigsave = signal(SIGALRM, alarm_handler);
-    alarm (timeout);
 
 #ifndef KERBEROS_V4
-    if (queryctl->authenticate == A_KERBEROS)
+    if (ctl->authenticate == A_KERBEROS)
     {
        fputs("fetchmail: Kerberos support not linked.\n", stderr);
        return(PS_ERROR);
@@ -580,260 +743,305 @@ struct method *proto;
     if (!proto->is_old)
     {
        /* check for unsupported options */
-       if (queryctl->flush) {
+       if (ctl->flush) {
            fprintf(stderr,
                    "Option --flush is not supported with %s\n",
                    proto->name);
-            alarm(0);
-            signal(SIGALRM, sigsave);
            return(PS_SYNTAX);
        }
-       else if (queryctl->fetchall) {
+       else if (ctl->fetchall) {
            fprintf(stderr,
                    "Option --all is not supported with %s\n",
                    proto->name);
-            alarm(0);
-            signal(SIGALRM, sigsave);
            return(PS_SYNTAX);
        }
     }
+    if (!proto->getsizes && ctl->limit)
+    {
+       fprintf(stderr,
+               "Option --limit is not supported with %s\n",
+               proto->name);
+       return(PS_SYNTAX);
+    }
 
-    tagnum = 0;
     protocol = proto;
-
-    /* open a socket to the mail server */
-    if ((socket = Socket(queryctl->servername,
-                        queryctl->port ? queryctl->port : protocol->port))<0 
-         || alarmed)
+    tagnum = 0;
+    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);
+    else
     {
-       perror("fetchmail, connecting to host");
-       ok = PS_SOCKET;
-       goto closeUp;
-    }
+       char buf [POPBUFSIZE+1];
+       int *msgsizes, socket, len, num, count, new, deletions = 0;
+
+       /* set up the server-nonresponse timeout */
+       sigsave = signal(SIGALRM, alarm_handler);
+       alarm(ctl->timeout);
+
+       /* open a socket to the mail server */
+       if ((socket = Socket(ctl->servername,
+                            ctl->port ? ctl->port : protocol->port))<0)
+       {
+           perror("fetchmail, connecting to host");
+           ok = PS_SOCKET;
+           goto closeUp;
+       }
 
 #ifdef KERBEROS_V4
-    if (queryctl->authentication == A_KERBEROS)
-    {
-       ok = (kerberos_auth (socket, queryctl->servername));
+       if (ctl->authenticate == A_KERBEROS)
+       {
+           ok = (kerberos_auth (socket, ctl->canonical_name));
+           if (ok != 0)
+               goto cleanUp;
+       }
+#endif /* KERBEROS_V4 */
+
+       /* accept greeting message from mail server */
+       ok = (protocol->parse_response)(socket, buf);
        if (ok != 0)
            goto cleanUp;
-    }
-#endif /* KERBEROS_V4 */
 
-    /* accept greeting message from mail server */
-    ok = (protocol->parse_response)(socket, buf);
-    if (alarmed || ok != 0)
-       goto cleanUp;
-
-    /* try to get authorized to fetch mail */
-    ok = (protocol->getauth)(socket, queryctl, buf);
-    if (alarmed || ok == PS_ERROR)
-       ok = PS_AUTHFAIL;
-    if (alarmed || ok != 0)
-       goto cleanUp;
-
-    /* compute count, and get UID list if possible */
-    if ((protocol->getrange)(socket, queryctl, &count) != 0 || alarmed)
-       goto cleanUp;
-
-    /* show user how many messages we downloaded */
-    if (outlevel > O_SILENT && outlevel < O_VERBOSE)
-       if (count == 0)
-           fprintf(stderr, "No mail from %s\n", queryctl->servername);
-       else
-           fprintf(stderr,
-                   "%d message%s from %s.\n",
-                   count, count > 1 ? "s" : "", 
-                   queryctl->servername);
+       /* try to get authorized to fetch mail */
+       shroud = ctl->password;
+       ok = (protocol->getauth)(socket, ctl, buf);
+       shroud = (char *)NULL;
+       if (ok == PS_ERROR)
+           ok = PS_AUTHFAIL;
+       if (ok != 0)
+           goto cleanUp;
 
-    if ((count > 0) && (!check_only))
-    {
-       if (queryctl->mda[0] == '\0')
-           if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0
-               || SMTP_ok(mboxfd, NULL) != SM_OK
-               || SMTP_helo(mboxfd, queryctl->servername) != SM_OK 
-                || alarmed)
+       /* compute number of messages and number of new messages waiting */
+       if ((protocol->getrange)(socket, ctl, &count, &new) != 0)
+           goto cleanUp;
+
+       /* show user how many messages we downloaded */
+       if (outlevel > O_SILENT)
+           if (count == 0)
+               fprintf(stderr, "No mail from %s@%s\n", 
+                       ctl->remotename,
+                       ctl->servername);
+           else
            {
-               ok = PS_SMTP;
-               close(mboxfd);
-               mboxfd = -1;
-               goto cleanUp;
+               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);
            }
-    
-       /* read, forward, and delete messages */
-       for (num = 1; num <= count; num++)
+
+       /* we may need to get sizes in order to check message limits */
+       msgsizes = (int *)NULL;
+       if (!ctl->fetchall && proto->getsizes && ctl->limit)
        {
-           int treat_as_new = 
-               !protocol->is_old 
-               || !(protocol->is_old)(socket, queryctl, num);
+           msgsizes = (int *)alloca(sizeof(int) * count);
 
-           /* we may want to reject this message if it's old */
-           if (treat_as_new || queryctl->fetchall)
-           {
-               /* request a message */
-               (protocol->fetch)(socket, num, &len);
+           if ((ok = (proto->getsizes)(socket, count, msgsizes)) != 0)
+               return(PS_ERROR);
+       }
 
-               if (outlevel > O_SILENT)
+       if (check_only)
+       {
+           if (new == -1 || ctl->fetchall)
+               new = count;
+           ok = ((new > 0) ? PS_SUCCESS : PS_NOMAIL);
+           goto closeUp;
+       }
+       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)
                {
-                   fprintf(stderr, "reading message %d", num);
-                   if (len > 0)
-                       fprintf(stderr, " (%d bytes)", len);
-                   if (outlevel == O_VERBOSE)
-                       fputc('\n', stderr);
-                   else
-                       fputc(' ', stderr);
+                   ok = PS_SMTP;
+                   close(mboxfd);
+                   mboxfd = -1;
+                   goto cleanUp;
                }
+    
+           /* read, forward, and delete messages */
+           for (num = 1; num <= count; num++)
+           {
+               int     toolarge = msgsizes && msgsizes[num-1]>ctl->limit;
+               int     fetch_it = ctl->fetchall ||
+                   (!(protocol->is_old && (protocol->is_old)(socket,ctl,num)) && !toolarge);
 
-               /* open the delivery pipe now if we're using an MDA */
-               if (queryctl->mda[0])
-                   if ((mboxfd = openmailpipe(queryctl)) < 0)
+               /* we may want to reject this message if it's old */
+               if (!fetch_it)
+               {
+                   if (outlevel > O_SILENT)
+                   {
+                       fprintf(stderr, "skipping message %d", num);
+                       if (toolarge)
+                           fprintf(stderr, " (oversized, %d bytes)", msgsizes[num-1]);
+                   }
+               }
+               else
+               {
+                   /* request a message */
+                   (protocol->fetch)(socket, num, &len);
+
+                   if (outlevel > O_SILENT)
+                   {
+                       fprintf(stderr, "reading message %d", num);
+                       if (len > 0)
+                           fprintf(stderr, " (%d bytes)", len);
+                       if (outlevel == O_VERBOSE)
+                           fputc('\n', stderr);
+                       else
+                           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,
+                                    len, 
+                                    protocol->delimited,
+                                    ctl);
+
+                   /* tell the server we got it OK and resynchronize */
+                   if (protocol->trail)
+                       (protocol->trail)(socket, ctl, num);
+                   if (ok != 0)
                        goto cleanUp;
+               }
 
-               /* read the message and ship it to the output sink */
-               ok = gen_readmsg(socket, mboxfd,
-                                len, 
-                                protocol->delimited,
-                                queryctl);
+               /*
+                * At this point in flow of control, either we've bombed
+                * on a protocol error or had delivery refused by the SMTP
+                * server (unlikely -- I've never seen it) or we've seen
+                * `accepted for delivery' and the message is shipped.
+                * It's safe to mark the message seen and delete it on the
+                * server now.
+                */
 
-               /* close the delivery pipe, we'll reopen before next message */
-               if (queryctl->mda[0])
-                   if ((ok = closemailpipe(mboxfd)) != 0 || alarmed)
+               /* maybe we delete this message now? */
+               if (protocol->delete
+                   && (fetch_it ? !ctl->keep : ctl->flush))
+               {
+                   deletions++;
+                   if (outlevel > O_SILENT) 
+                       fprintf(stderr, " flushed\n");
+                   ok = (protocol->delete)(socket, ctl, num);
+                   if (ok != 0)
                        goto cleanUp;
-
-               /* tell the server we got it OK and resynchronize */
-               if (protocol->trail)
-                   (protocol->trail)(socket, queryctl, num);
-               if (alarmed || ok != 0)
-                   goto cleanUp;
+               }
+               else if (outlevel > O_SILENT) 
+               {
+                   /* nuke it from the unseen-messages list */
+                   delete_uid(&ctl->newsaved, num);
+                   fprintf(stderr, " not flushed\n");
+               }
            }
 
-           /*
-            * At this point in flow of control, either we've bombed
-            * on a protocol error or had delivery refused by the SMTP
-            * server (unlikely -- I've never seen it) or we've seen
-            * `accepted for delivery' and the message is shipped.
-            * It's safe to mark the message seen and delete it on the
-            * server now.
-            */
-
-           /* maybe we delete this message now? */
-           if (protocol->delete
-               && !queryctl->keep
-               && (treat_as_new || queryctl->flush))
+           /* remove all messages flagged for deletion */
+           if (protocol->expunge_cmd && deletions > 0)
            {
-               deletions++;
-               if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
-                   fprintf(stderr, " flushed\n", num);
-               ok = (protocol->delete)(socket, queryctl, num);
-               if (alarmed || ok != 0)
+               ok = gen_transact(socket, protocol->expunge_cmd);
+               if (ok != 0)
                    goto cleanUp;
            }
-           else if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
-           {
-               /* nuke it from the unseen-messages list */
-               delete_uid(&queryctl->newsaved, num);
-               fprintf(stderr, " not flushed\n", num);
-           }
+
+           ok = gen_transact(socket, protocol->exit_cmd);
+           if (ok == 0)
+               ok = PS_SUCCESS;
+           close(socket);
+           goto closeUp;
+       }
+       else {
+           ok = gen_transact(socket, protocol->exit_cmd);
+           if (ok == 0)
+               ok = PS_NOMAIL;
+           close(socket);
+           goto closeUp;
        }
 
-       /* remove all messages flagged for deletion */
-        if (protocol->expunge_cmd && deletions > 0)
+    cleanUp:
+       if (ok != 0 && ok != PS_SOCKET)
        {
-           ok = gen_transact(socket, protocol->expunge_cmd);
-           if (alarmed || ok != 0)
-               goto cleanUp;
-        }
-
-       ok = gen_transact(socket, protocol->exit_cmd);
-       if (alarmed || ok == 0)
-           ok = PS_SUCCESS;
-       close(socket);
-       goto closeUp;
-    }
-    else if (check_only) {
-      ok = ((count > 0) ? PS_SUCCESS : PS_NOMAIL);
-      goto closeUp;
-    }
-    else {
-       ok = gen_transact(socket, protocol->exit_cmd);
-       if (ok == 0)
-           ok = PS_NOMAIL;
-       close(socket);
-       goto closeUp;
+           gen_transact(socket, protocol->exit_cmd);
+           close(socket);
+       }
     }
 
-cleanUp:
-    if (ok != 0 && ok != PS_SOCKET)
-    {
-       gen_transact(socket, protocol->exit_cmd);
-       close(socket);
-    }
+    alarm(0);
+    signal(SIGALRM, sigsave);
 
 closeUp:
     if (mboxfd != -1)
     {
-       SMTP_quit(mboxfd);
+        if (!ctl->mda[0])
+           SMTP_quit(mboxfd);
        close(mboxfd);
     }
-    alarm(0);
-    signal(SIGALRM, sigsave);
+
     return(ok);
 }
 
-/*********************************************************************
-  function:      gen_send
-  description:   Assemble command in print style and send to the server
-
-  arguments:     
-    socket       socket to which the server is connected.
-    fmt          printf-style format
-
-  return value:  none.
-  calls:         SockPuts.
-  globals:       reads outlevel.
- *********************************************************************/
-
+#if defined(HAVE_STDARG_H)
+void gen_send(int socket, char *fmt, ... )
+/* assemble command in printf(3) style and send to the server */
+{
+#else
 void gen_send(socket, fmt, va_alist)
-int socket;
-const char *fmt;
+/* assemble command in printf(3) style and send to the server */
+int socket;            /* socket to which server is connected */
+const char *fmt;       /* printf-style format */
 va_dcl {
+#endif
 
-  char buf [POPBUFSIZE+1];
-  va_list ap;
-
-  if (protocol->tagged)
-      (void) sprintf(buf, "%s ", GENSYM);
-  else
-      buf[0] = '\0';
-
-  va_start(ap);
-  vsprintf(buf + strlen(buf), fmt, ap);
-  va_end(ap);
+    char buf [POPBUFSIZE+1];
+    va_list ap;
 
-  SockPuts(socket, buf);
+    if (protocol->tagged)
+       (void) sprintf(buf, "%s ", GENSYM);
+    else
+       buf[0] = '\0';
 
-  if (outlevel == O_VERBOSE)
-    fprintf(stderr,"> %s\n", buf);
-}
+#if defined(HAVE_STDARG_H)
+    va_start(ap, fmt) ;
+#else
+    va_start(ap);
+#endif
+    vsprintf(buf + strlen(buf), fmt, ap);
+    va_end(ap);
 
-/*********************************************************************
-  function:      gen_transact
-  description:   Assemble command in print style and send to the server.
-                 then accept a protocol-dependent response.
+    SockPuts(socket, buf);
 
-  arguments:     
-    socket       socket to which the server is connected.
-    fmt          printf-style format
+    if (outlevel == O_VERBOSE)
+    {
+       char *cp;
 
-  return value:  none.
-  calls:         SockPuts.
-  globals:       reads outlevel.
- *********************************************************************/
+       if (shroud && (cp = strstr(buf, shroud)))
+           memset(cp, '*', strlen(shroud));
+       fprintf(stderr,"> %s\n", buf);
+    }
+}
 
+#if defined(HAVE_STDARG_H)
+int gen_transact(int socket, char *fmt, ... )
+/* assemble command in printf(3) style, send to server, accept a response */
+{
+#else
 int gen_transact(socket, fmt, va_alist)
-int socket;
-const char *fmt;
+/* assemble command in printf(3) style, send to server, accept a response */
+int socket;            /* socket to which server is connected */
+const char *fmt;       /* printf-style format */
 va_dcl {
+#endif
 
   int ok;
   char buf [POPBUFSIZE+1];
@@ -844,13 +1052,23 @@ va_dcl {
   else
       buf[0] = '\0';
 
+#if defined(HAVE_STDARG_H)
+  va_start(ap, fmt) ;
+#else
   va_start(ap);
+#endif
   vsprintf(buf + strlen(buf), fmt, ap);
   va_end(ap);
 
   SockPuts(socket, buf);
   if (outlevel == O_VERBOSE)
-    fprintf(stderr,"> %s\n", buf);
+  {
+      char *cp;
+
+      if (shroud && (cp = strstr(buf, shroud)))
+         memset(cp, '*', strlen(shroud));
+      fprintf(stderr,"> %s\n", buf);
+  }
 
   /* we presume this does its own response echoing */
   ok = (protocol->parse_response)(socket, buf);
@@ -858,23 +1076,4 @@ va_dcl {
   return(ok);
 }
 
-
-/*********************************************************************
-  function:      alarm_handler
-  description:   In real life process can get stuck waiting for 
-                 something. This deadlock is avoided here by this 
-                 sending  SIGALRM
-
-  arguments:     
-    signal       hopefully SIGALRM
-
-  return value:  none.
-  calls:         none
-  globals:       sets alarmed to 1
- *********************************************************************/
-void 
-alarm_handler (int signal)
-{
-    alarmed = 1;
-    fprintf(stderr,"fetchmail: timeout after %d seconds.\n", timeout);
-}
+/* driver.c ends here */