]> Pileus Git - ~andy/fetchmail/blobdiff - fetchmail.c
Caseblind comparison of names, more explicitness about Kerberos.
[~andy/fetchmail] / fetchmail.c
index 4d55bf91d489792be4f280ef72fd8d41cf7db742..c311bad08ac90ccf9bb95aa028e4c4a69c2702d6 100644 (file)
@@ -5,46 +5,51 @@
  */
 
 #include <config.h>
+
 #include <stdio.h>
 #include <ctype.h>
-#include <time.h>
-
 #if defined(STDC_HEADERS)
 #include <stdlib.h>
-#include <string.h>
 #endif
-
 #if defined(HAVE_UNISTD_H)
 #include <unistd.h>
 #endif
-
+#if defined(HAVE_ALLOCA_H)
+#include <alloca.h>
+#endif
+#include <string.h>
 #include <signal.h>
+#if defined(HAVE_SYSLOG)
+#include <syslog.h>
+#endif
 #include <pwd.h>
 #include <errno.h>
-
+#include <sys/time.h>
 #include <sys/types.h>
-#include <sys/file.h>
-#include <sys/wait.h>
 #include <sys/stat.h>
-#include <sys/time.h>
-#include <fcntl.h>
+#include <sys/wait.h>
 
 #ifdef HAVE_GETHOSTBYNAME
 #include <netdb.h>
 #endif /* HAVE_GETHOSTBYNAME */
 
+#ifdef SUNOS
+#include <stdlib.h>
+#endif
+
 #include "fetchmail.h"
+#include "tunable.h"
+#include "smtp.h"
 #include "getopt.h"
+#include "netrc.h"
 
 #define DROPDEAD       6       /* maximum bad socket opens */
 
-#ifdef HAVE_PROTOTYPES
 /* prototypes for internal functions */
-static void load_params(int, char **, int);
+static int load_params(int, char **, int);
 static void dump_params (struct query *);
 static int query_host(struct query *);
 static char *visbuf(const char *);
-#endif
 
 /* controls the detail level of status/progress messages written to stderr */
 int outlevel;          /* see the O_.* constants above */
@@ -54,33 +59,55 @@ int yydebug;                /* enable parse debugging */
 int poll_interval;     /* poll interval in seconds */
 int nodetach;          /* if TRUE, don't detach daemon process */
 char *logfile;         /* log file for daemon mode */
+int use_syslog;                /* if --syslog was set */
 int quitmode;          /* if --quit was set */
 int check_only;                /* if --probe was set */
+char *cmd_logfile;     /* if --logfile was set */
+int cmd_daemon;        /* if --daemon was set */
 
 /* miscellaneous global controls */
 char *rcfile;          /* path name of rc file */
 char *idfile;          /* UID list file */
 int versioninfo;       /* emit only version info */
 char *user;            /* the name of the invoking user */
+char *fetchmailhost;   /* the name of the host running fetchmail */
+char *program_name;    /* the name to prefix error messages with */
+
+static char *lockfile;         /* name of lockfile */
+static int querystatus;                /* status of query */
+static int lastsig;            /* last signal received */
 
-static void termhook();
-static char *lockfile;
-static int popstatus;
+static void termhook();                /* forward declaration of exit hook */
 
-RETSIGTYPE donothing(sig) int sig; {signal(sig, donothing);}
+RETSIGTYPE donothing(sig) int sig; {signal(sig, donothing); lastsig = sig;}
+
+#ifdef HAVE_ATEXIT
+static void unlockit(void)
+#else  /* use on_exit(), e.g. on SunOS */
+static void unlockit(int n, void *p)
+#endif
+/* must-do actions for exit (but we can't count on being able to do malloc) */
+{
+    unlink(lockfile);
+}
 
-int main (argc,argv)
-int argc;
-char **argv;
-{ 
-    int st, lossage, bkgd = FALSE;
+int main (int argc, char **argv)
+{
+    int st, bkgd = FALSE;
     int parsestatus, implicitmode;
     char *home, *tmpdir, tmpbuf[BUFSIZ]; 
     struct passwd *pw;
     struct query *ctl;
     FILE       *lockfp;
+    netrc_entry *netrc_list;
+    char *netrc_file;
     pid_t pid;
 
+    if ((program_name = strrchr(argv[0], '/')) != NULL)
+       ++program_name;
+    else
+       program_name = argv[0];
+
     if ((user = getenv("USER")) == (char *)NULL)
         user = getenv("LOGNAME");
 
@@ -98,6 +125,14 @@ char **argv;
        }
     }
 
+    /* we'll need this for the SMTP forwarding target and error messages */
+    if (gethostname(tmpbuf, sizeof(tmpbuf)))
+    {
+       fprintf(stderr, "fetchmail: can't determine fetchmail's host!");
+       exit(PS_IOERR);
+    }
+    fetchmailhost = xstrdup(tmpbuf);
+
     /*
      * Backward-compatibility hack.  If we're called by the name of the
      * ancestral popclient, look for .poprc.  This will actually work 
@@ -124,37 +159,40 @@ char **argv;
     if ((parsestatus = parsecmdline(argc,argv,&cmd_opts)) < 0)
        exit(PS_SYNTAX);
 
+    /* this hint to stdio should help messages come out in the right order */
+    setvbuf(stdout, NULL, _IOLBF, POPBUFSIZE);
+
     if (versioninfo)
-       printf("This is fetchmail release %s pl %s\n", RELEASE_ID, PATCHLEVEL);
+       printf("This is fetchmail release %s\n", RELEASE_ID);
 
     /* avoid parsing the config file if all we're doing is killing a daemon */ 
     if (!quitmode)
-       load_params(argc, argv, optind);
+       implicitmode = load_params(argc, argv, optind);
 
     /* set up to do lock protocol */
-    if ((tmpdir = getenv("TMPDIR")) == (char *)NULL)
-       tmpdir = "/tmp";
-    strcpy(tmpbuf, tmpdir);
-    strcat(tmpbuf, "/fetchmail-");
-    strcat(tmpbuf, user);
+    if (!getuid())
+       strcpy(tmpbuf, "/var/run/fetchmail.pid");
+    else {
+       strcpy(tmpbuf, home);
+       strcat(tmpbuf, "/.fetchmail");
+    }
 
     /* perhaps we just want to check options? */
     if (versioninfo) {
-           printf("Taking options from command line");
-           if (access(rcfile, 0))
-               printf("\n");
-           else
-               printf(" and %s\n", rcfile);
-           if (outlevel == O_VERBOSE)
-               printf("Lockfile at %s\n", tmpbuf);
+       printf("Taking options from command line");
+       if (access(rcfile, 0))
+           printf("\n");
+       else
+           printf(" and %s\n", rcfile);
+       if (outlevel == O_VERBOSE)
+           printf("Lockfile at %s\n", tmpbuf);
        for (ctl = querylist; ctl; ctl = ctl->next) {
-           if (ctl->active && !(implicitmode && ctl->skip))
+           if (ctl->active && !(implicitmode && ctl->server.skip))
                dump_params(ctl);
        }
        if (querylist == NULL)
            (void) fprintf(stderr,
-               "No mailservers set up -- perhaps %s is missing?\n",
-                         rcfile);
+               "No mailservers set up -- perhaps %s is missing?\n", rcfile);
        exit(0);
     }
     else if (!quitmode && querylist == NULL) {
@@ -179,7 +217,7 @@ char **argv;
            fprintf(stderr,"fetchmail: removing stale lockfile\n");
            pid = -1;
            bkgd = FALSE;
-           remove(lockfile);
+           unlink(lockfile);
        }
        fclose(lockfp);
     }
@@ -202,7 +240,7 @@ char **argv;
        {
            fprintf(stderr,"fetchmail: %s fetchmail at %d killed.\n",
                    bkgd ? "background" : "foreground", pid);
-           remove(lockfile);
+           unlink(lockfile);
            exit(0);
        }
     }
@@ -211,7 +249,11 @@ char **argv;
     if (pid != -1)
     {
        if (check_only)
+       {
+           fprintf(stderr,
+                "fetchmail: can't check mail while another fetchmail to same host is running.\n");
            return(PS_EXCLUDE);
+        }
        else if (!implicitmode)
        {
            fprintf(stderr,
@@ -226,7 +268,7 @@ char **argv;
                 pid);
                return(PS_EXCLUDE);
        }
-       else if (kill(pid, SIGHUP) == 0)
+       else if (kill(pid, SIGUSR1) == 0)
        {
            fprintf(stderr,
                    "fetchmail: background fetchmail at %d awakened.\n",
@@ -237,7 +279,7 @@ char **argv;
        {
            /*
             * Should never happen -- possible only if a background fetchmail
-            * croaks after the first kill probe above but before the SIGHUP
+            * croaks after the first kill probe above but before the SIGUSR1/SIGHUP
             * transmission.
             */
            fprintf(stderr,
@@ -247,29 +289,57 @@ char **argv;
        }
     }
 
+    /* parse the ~/.netrc file, for future password lookups. */
+    netrc_file = (char *) xmalloc (strlen (home) + 8);
+    strcpy (netrc_file, home);
+    strcat (netrc_file, "/.netrc");
+
+    netrc_list = parse_netrc (netrc_file);
+
     /* pick up interactively any passwords we need but don't have */ 
     for (ctl = querylist; ctl; ctl = ctl->next)
-       if (ctl->active && !(implicitmode && ctl->skip) && !ctl->password[0])
+       if (ctl->active && !(implicitmode && ctl->server.skip) && !ctl->password)
        {
-           if (ctl->authenticate == A_KERBEROS)
-             /* Server won't care what the password is, but there
-                must be some non-null string here.  */
-             (void) strncpy(ctl->password, 
-                            ctl->remotename, PASSWORDLEN-1);
+           if (ctl->server.authenticate == A_KERBEROS_V4)
+               /* Server won't care what the password is, but there
+                  must be some non-null string here.  */
+               ctl->password = ctl->remotename;
            else
-             {
+           {
+               /* Look up the host and account in the .netrc file. */
+               netrc_entry *p = search_netrc(netrc_list,ctl->server.names->id);
+               while (p && strcmp (p->account, ctl->remotename))
+                   p = search_netrc (p->next, ctl->remotename);
+
+               if (p)
+               {
+                   /* We found the entry, so use the password. */
+                   ctl->password = xstrdup(p->password);
+               }
+           }
+
+           if (ctl->server.protocol != P_ETRN && !ctl->password)
+           {
                (void) sprintf(tmpbuf, "Enter password for %s@%s: ",
-                              ctl->remotename, ctl->servername);
-               (void) strncpy(ctl->password,
-                              (char *)getpassword(tmpbuf),PASSWORDLEN-1);
-             }
+                              ctl->remotename, ctl->server.names->id);
+               ctl->password = xstrdup((char *)getpassword(tmpbuf));
+           }
        }
 
     /*
      * Maybe time to go to demon mode...
      */
-    if (poll_interval && !nodetach)
-       daemonize(logfile, termhook);
+#if defined(HAVE_SYSLOG)
+    if (use_syslog)
+       openlog(program_name, LOG_PID, LOG_MAIL);
+#endif
+
+    if (poll_interval)
+    {
+       if (!nodetach)
+           daemonize(logfile, termhook);
+       error( 0, 0, "starting fetchmail %s daemon ", RELEASE_ID);
+    }
 
     /* beyond here we don't want more than one fetchmail running per user */
     umask(0077);
@@ -286,64 +356,123 @@ char **argv;
      * side effect of interrupting any sleep that may be going on,
      * forcing fetchmail to re-poll its hosts.
      */
-    signal(SIGHUP, donothing);
+    signal(SIGUSR1, donothing);
+
+    /* pacify people who think all system daemons wake up on SIGHUP */
+    if (poll_interval && !getuid())
+       signal(SIGHUP, donothing);
 
+    /* here's the exclusion lock */
     if ( (lockfp = fopen(lockfile,"w")) != NULL ) {
        fprintf(lockfp,"%d",getpid());
        if (poll_interval)
            fprintf(lockfp," %d", poll_interval);
        fclose(lockfp);
+
+#ifdef HAVE_ATEXIT
+       atexit(unlockit);
+#else
+       on_exit(unlockit, (char *)NULL);
+#endif
     }
 
     /*
      * Query all hosts. If there's only one, the error return will
      * reflect the status of that transaction.
      */
-    lossage = 0;
     do {
-#ifdef HAVE_GETHOSTBYNAME
+#ifdef HAVE_RES_SEARCH
        sethostent(TRUE);       /* use TCP/IP for mailserver queries */
-#endif /* HAVE_GETHOSTBYNAME */
+#endif /* HAVE_RES_SEARCH */
 
+       batchcount = 0;
        for (ctl = querylist; ctl; ctl = ctl->next)
        {
-           if (ctl->active && !(implicitmode && ctl->skip))
+           if (ctl->active && !(implicitmode && ctl->server.skip))
            {
-               popstatus = query_host(ctl);
+#ifdef linux
+               /* interface_approve() does its own error logging */
+               if (!interface_approve(&ctl->server))
+                   continue;
+#endif /* linux */
 
+#ifdef HAVE_GETHOSTBYNAME
                /*
-                * Under Linux, if fetchmail is run in daemon mode
-                * with the network inaccessible, each poll leaves a
-                * socket allocated but in CLOSE state (this is
-                * visible in netstat(1)'s output).  For some reason,
-                * these sockets aren't garbage-collected until
-                * fetchmail exits.  When whatever kernel table is
-                * involved fills up, fetchmail can no longer run even
-                * if the network is up.  This does not appear to be a
-                * socket leak in fetchmail.  To avoid this
-                * problem, fetchmail commits seppuku after five
-                * unsuccessful socket opens.
+                * This functions partly as an optimization and partly
+                * as a probe to make sure our nameserver is still up.
+                * The multidrop case (especially) needs it.
                 */
-               if (popstatus == PS_SOCKET)
-                   lossage++;
-               else
-                   lossage = 0;
-               if (lossage >= DROPDEAD)
+               if (ctl->server.authenticate==A_KERBEROS_V4 || MULTIDROP(ctl))
                {
-                   fputs("fetchmail: exiting, network appears to be down\n",
-                         stderr);
-                   termhook(0);
+                   struct hostent      *namerec;
+
+                   /* compute the canonical name of the host */
+                   errno = 0;
+                   namerec = gethostbyname(ctl->server.names->id);
+                   if (namerec == (struct hostent *)NULL)
+                   {
+                       error(0, errno,
+                               "skipping %s poll, ",
+                               ctl->server.names->id);
+                       if (errno)
+                       {
+                           if (errno == ENETUNREACH)
+                               break;  /* go to sleep */
+                       }
+#ifdef HAVE_HERROR             /* NEXTSTEP doesn't */
+                       else
+                           herror("DNS error");
+#endif /* HAVE_HERROR */
+                       continue;
+                   }
+                   else
+                   {
+                       free(ctl->server.canonical_name);
+                       ctl->server.canonical_name = xstrdup((char *)namerec->h_name);
+                   }
                }
+#endif /* HAVE_GETHOSTBYNAME */
 
+               querystatus = query_host(ctl);
                if (!check_only)
-                   update_uid_lists(ctl);
+                   update_str_lists(ctl);
+#ifdef linux
+               if (ctl->server.monitor)
+                   {
+                       /* Allow some time for the link to quiesce.  One
+                        * second is usually sufficient, three is safe.
+                        * Note:  this delay is important - don't remove!
+                        */
+                       sleep(3);
+                       interface_note_activity(&ctl->server);
+                   }
+#endif
            }
        }
 
-#ifdef HAVE_GETHOSTBYNAME
+#ifdef HAVE_RES_SEARCH
        endhostent();           /* release TCP/IP connection to nameserver */
-#endif /* HAVE_GETHOSTBYNAME */
+#endif /* HAVE_RES_SEARCH */
+
+       /*
+        * Close all SMTP delivery sockets.  For optimum performance
+        * we'd like to hold them open til end of run, but (1) this
+        * loses if our poll interval is longer than the MTA's inactivity
+        * timeout, and (2) some MTAs (like smail) don't deliver after
+        * each message, but rather queue up mail and wait to actually
+        * deliver it until the input socket is closed. 
+        */
+       for (ctl = querylist; ctl; ctl = ctl->next)
+           if (ctl->smtp_sockfp)
+           {
+               SMTP_quit(ctl->smtp_sockfp);
+               fclose(ctl->smtp_sockfp);
+               ctl->smtp_sockfp = (FILE *)NULL;
+           }
 
+       /*
+        * OK, we've polled.  Now sleep.
+        */
        if (poll_interval)
        {
            if (outlevel == O_VERBOSE)
@@ -355,22 +484,31 @@ char **argv;
            }
 
            /*
-            * We can't use sleep(3) here, the alarm(2) call used to
-            * implement server nonresponse timeout collides with it.
+            * We can't use sleep(3) here because we need an alarm(3)
+            * equivalent in order to implement server nonresponse timeout.
             * We'll just assume setitimer(2) is available since fetchmail
-            * has to have the socket layer to work at all.
+            * has to have a BSDoid socket layer to work at all.
             */
            {
                struct itimerval ntimeout;
 
-               ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_sec = 0;
+               ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
                ntimeout.it_value.tv_sec  = poll_interval;
                ntimeout.it_value.tv_usec = 0;
 
-               if (setitimer(ITIMER_REAL,&ntimeout,NULL)==-1 && errno==EINTR)
-                   (void) fputs("fetchmail: awakened by SIGHUP\n", stderr);
+               setitimer(ITIMER_REAL,&ntimeout,NULL);
                signal(SIGALRM, donothing);
                pause();
+               signal(SIGALRM, SIG_IGN);
+               if (lastsig == SIGUSR1
+                       || ((poll_interval && !getuid()) && lastsig == SIGHUP))
+               {
+#ifdef SYS_SIGLIST_DECLARED
+                   error(0, 0, "awakened by %s", sys_siglist[lastsig]);
+#else
+                   error(0, 0, "awakened by signal %d", lastsig);
+#endif
+               }
            }
 
            if (outlevel == O_VERBOSE)
@@ -385,16 +523,13 @@ char **argv;
        (poll_interval);
 
     if (outlevel == O_VERBOSE)
-       fprintf(stderr,"fetchmail: normal termination, status %d\n",popstatus);
+       fprintf(stderr,"fetchmail: normal termination, status %d\n",querystatus);
 
     termhook(0);
-    exit(popstatus);
+    exit(querystatus);
 }
 
-static void load_params(argc, argv, optind)
-int    argc;
-char   **argv;
-int    optind;
+static int load_params(int argc, char **argv, int optind)
 {
     int        implicitmode, st;
     struct passwd *pw;
@@ -402,10 +537,10 @@ int       optind;
 
     memset(&def_opts, '\0', sizeof(struct query));
 
-    def_opts.protocol = P_AUTO;
-    def_opts.timeout = CLIENT_TIMEOUT;
-    strcpy(def_opts.remotename, user);
-    strcpy(def_opts.smtphost, "localhost");
+    def_opts.server.protocol = P_AUTO;
+    def_opts.server.timeout = CLIENT_TIMEOUT;
+    def_opts.remotename = user;
+    save_str(&def_opts.smtphunt, -1, fetchmailhost);
 
     /* this builds the host list */
     if (prc_parse_file(rcfile) != 0)
@@ -425,38 +560,34 @@ int       optind;
             * record from command line and defaults
             */
            for (ctl = querylist; ctl; ctl = ctl->next)
-               if (strcmp(ctl->servername, argv[optind]) == 0)
+               if (str_in_list(&ctl->server.names, argv[optind]))
                    goto foundit;
 
            ctl = hostalloc(&cmd_opts);
-           strcpy(ctl->servername, argv[optind]);
+           save_str(&ctl->server.names, -1, argv[optind]);
 
        foundit:
            ctl->active = TRUE;
        }
 
     /* if there's a defaults record, merge it and lose it */ 
-    if (querylist && strcmp(querylist->servername, "defaults") == 0)
+    if (querylist && strcmp(querylist->server.names->id, "defaults") == 0)
     {
-       for (ctl = querylist; ctl; ctl = ctl->next)
+       for (ctl = querylist->next; ctl; ctl = ctl->next)
            optmerge(ctl, querylist);
        querylist = querylist->next;
     }
 
     /* don't allow a defaults record after the first */
     for (ctl = querylist; ctl; ctl = ctl->next)
-       if (strcmp(ctl->servername, "defaults") == 0)
+       if (ctl != querylist && strcmp(ctl->server.names->id, "defaults") == 0)
            exit(PS_SYNTAX);
 
     /* merge in wired defaults, do sanity checks and prepare internal fields */
     for (ctl = querylist; ctl; ctl = ctl->next)
     {
-       if (ctl->active && !(implicitmode && ctl->skip))
+       if (ctl->active && !(implicitmode && ctl->server.skip))
        {
-#ifdef HAVE_GETHOSTBYNAME
-           struct hostent      *namerec;
-#endif /* HAVE_GETHOSTBYNAME */
-
            /* merge in defaults */
            optmerge(ctl, &def_opts);
 
@@ -467,38 +598,21 @@ int       optind;
                exit(PS_SYNTAX);
            }
 
-           /* check that delivery is going to a real local user */
+           /* make sure delivery will default to a real local user */
            if ((pw = getpwnam(user)) == (struct passwd *)NULL)
            {
                fprintf(stderr,
-                       "fetchmail: can't default delivery to %s\n", user);
+                       "fetchmail: can't set up default delivery to %s\n", user);
                exit(PS_SYNTAX);        /* has to be from bad rc file */
            }
            else
-               ctl->uid = pw->pw_uid;
-
-#ifdef HAVE_GETHOSTBYNAME
-           /*
-            * Don't do DNS lookup unless we need to because we're going
-            * to use Kerberos or process a multidrop box.  Some sites
-            * won't have DNS up at fetchmail initialization time but aren't
-            * using these features -- avoid hosing them unnecessarily.
-            */
-           if (ctl->authenticate == A_KERBEROS || MULTIDROP(ctl))
            {
-               /* compute the canonical name of the host */
-               namerec = gethostbyname(ctl->servername);
-               if (namerec == (struct hostent *)NULL)
-               {
-                   fprintf(stderr,
-                           "fetchmail: can't get canonical name of host %s\n",
-                           ctl->servername);
-                   exit(PS_SYNTAX);
-               }
-               else
-                   ctl->canonical_name = xstrdup((char *)namerec->h_name);
+               ctl->uid = pw->pw_uid;  /* for local delivery via MDA */
+               if (!ctl->localnames)   /* for local delivery via SMTP */
+                   save_str_pair(&ctl->localnames, user, NULL);
            }
-#else
+
+#if !defined(HAVE_GETHOSTBYNAME) || !defined(HAVE_RES_SEARCH)
            /* can't handle multidrop mailboxes unless we can do DNS lookups */
            if (ctl->localnames && ctl->localnames->next)
            {
@@ -506,61 +620,53 @@ int       optind;
                        stderr);
                exit(PS_SYNTAX);
            }
-#endif /* HAVE_GETHOSTBYNAME */
+#endif /* !HAVE_GETHOSTBYNAME || !HAVE_RES_SEARCH */
 
-           /*
-            * Assign SMTP leaders.  We want to allow all query blocks
-            * sharing the same SMTP host to use the same SMTP connection.
-            * To accomplish this, we initialize each query block's leader
-            * field to point to the first block in the list with a matching 
-            * SMTP host.
-            *
-            * In the typical case, there will be only one SMTP host (the
-            * client machine) and thus just one SMTP leader (and one listener
-            * process) through the entire run.
-            */
-           if (!ctl->mda[0])
-           {
-               for (mp = querylist; mp && mp != ctl; mp = mp->next)
-                   if (strcmp(mp->smtphost, ctl->smtphost) == 0)
-                   {
-                       ctl->leader = mp->leader;
-                       goto no_new_leader;
-                   }
-               ctl->leader = ctl;
-               ctl->smtp_socket = -1;
-           no_new_leader:;
-           }
+           /* compute server leaders for queries */
+           for (mp = querylist; mp && mp != ctl; mp = mp->next)
+               if (strcmp(mp->server.names->id, ctl->server.names->id) == 0)
+               {
+                   ctl->server.lead_server = mp->server.lead_server;
+                   goto no_new_server;
+               }
+           ctl->server.lead_server = &(ctl->server);
+       no_new_server:;
+
+           /* this code enables flags to be turned off */
+#define DEFAULT(flag, dflt)    if (flag == FLAG_TRUE)\
+                                       flag = TRUE;\
+                               else if (flag == FLAG_FALSE)\
+                                       flag = FALSE;\
+                               else\
+                                       flag = (dflt)
+           DEFAULT(ctl->keep, FALSE);
+           DEFAULT(ctl->flush, FALSE);
+           DEFAULT(ctl->fetchall, FALSE);
+           DEFAULT(ctl->rewrite, TRUE);
+           DEFAULT(ctl->stripcr, (ctl->mda != (char *)NULL)); 
+           DEFAULT(ctl->forcecr, FALSE);
+           DEFAULT(ctl->server.dns, TRUE);
+           DEFAULT(ctl->server.uidl, FALSE);
+#undef DEFAULT
+
+           /* plug in the semi-standard way of indicating a mail address */
+           if (ctl->server.envelope == (char *)NULL)
+               ctl->server.envelope = "X-Envelope-To:";
 
            /* sanity checks */
-           if (ctl->port < 0)
+           if (ctl->server.port < 0)
            {
                (void) fprintf(stderr,
                               "%s configuration invalid, port number cannot be negative",
-                              ctl->servername);
+                              ctl->server.names->id);
                exit(PS_SYNTAX);
            }
-
-           /* expand MDA commands */
-           if (!check_only && ctl->mda[0])
+           if (ctl->server.protocol == P_RPOP && ctl->server.port >= 1024)
            {
-               char *argp;
-
-               /* punch nulls into the delimiting whitespace in the args */
-               for (argp = ctl->mda, ctl->mda_argcount = 1; *argp != '\0'; ctl->mda_argcount++)
-               {
-                   ctl->mda_argv[ctl->mda_argcount] = argp;
-                   while (!(*argp == '\0' || isspace(*argp)))
-                       argp++;
-                   if (*argp != '\0')
-                       *(argp++) = '\0';  
-               }
-
-               ctl->mda_argv[ctl->mda_argcount] = (char *)NULL;
-
-               ctl->mda_argv[0] = ctl->mda_argv[1];
-               if ((argp = strrchr(ctl->mda_argv[1], '/')) != (char *)NULL)
-                   ctl->mda_argv[1] = argp + 1 ;
+               (void) fprintf(stderr,
+                              "%s configuration invalid, RPOP requires a privileged port",
+                              ctl->server.names->id);
+               exit(PS_SYNTAX);
            }
        }
     }
@@ -570,6 +676,20 @@ int        optind;
        exit(st);
     else
        initialize_saved_lists(querylist, idfile);
+
+    /* if cmd_logfile was explicitly set, use it to override logfile */
+    if (cmd_logfile)
+       logfile = cmd_logfile;
+
+    /* likewise for poll_interval */
+    if (cmd_daemon >= 0)
+       poll_interval = cmd_daemon;
+
+    /* check and daemon options are not compatible */
+    if (check_only && poll_interval)
+       poll_interval = 0;
+
+    return(implicitmode);
 }
 
 void termhook(int sig)
@@ -577,24 +697,32 @@ void termhook(int sig)
 {
     struct query       *ctl;
 
-    if (sig != 0)
-       fprintf(stderr, "terminated with signal %d\n", sig);
+    /*
+     * Sending SMTP QUIT on signal is theoretically nice, but led to a 
+     * subtle bug.  If fetchmail was terminated by signal while it was 
+     * shipping message text, it would hang forever waiting for a
+     * command acknowledge.  In theory we could disable the QUIT
+     * only outside of the message send.  In practice, we don't
+     * care.  All mailservers hang up on a dropped TCP/IP connection
+     * anyway.
+     */
 
-    /* terminate all SMTP connections cleanly */
-    for (ctl = querylist; ctl; ctl = ctl->next)
-       if (ctl->leader == ctl && ctl->smtp_socket != -1)
-           SMTP_quit(ctl->smtp_socket);
+    if (sig != 0)
+        error(0, 0, "terminated with signal %d", sig);
+    else
+       /* terminate all SMTP connections cleanly */
+       for (ctl = querylist; ctl; ctl = ctl->next)
+           if (ctl->smtp_sockfp != (FILE *)NULL)
+               SMTP_quit(ctl->smtp_sockfp);
 
     if (!check_only)
        write_saved_lists(querylist, idfile);
 
-    unlink(lockfile);
-    exit(popstatus);
+    exit(querystatus);
 }
 
-static char *showproto(proto)
+static char *showproto(int proto)
 /* protocol index to protocol name mapping */
-int proto;
 {
     switch (proto)
     {
@@ -603,6 +731,8 @@ int proto;
     case P_POP3: return("POP3"); break;
     case P_IMAP: return("IMAP"); break;
     case P_APOP: return("APOP"); break;
+    case P_RPOP: return("RPOP"); break;
+    case P_ETRN: return("ETRN"); break;
     default: return("unknown?!?"); break;
     }
 }
@@ -612,9 +742,8 @@ int proto;
  */
 static const int autoprobe[] = {P_IMAP, P_POP3, P_POP2};
 
-static int query_host(ctl)
+static int query_host(struct query *ctl)
 /* perform fetch transaction with single host */
-struct query *ctl;
 {
     int i, st;
 
@@ -623,18 +752,19 @@ struct query *ctl;
        time_t now;
 
        time(&now);
-       fprintf(stderr, "Querying %s (protocol %s) at %s",
-           ctl->servername, showproto(ctl->protocol), ctime(&now));
+       fprintf(stderr, "fetchmail: %s querying %s (protocol %s) at %s",
+           RELEASE_ID,
+           ctl->server.names->id, showproto(ctl->server.protocol), ctime(&now));
     }
-    switch (ctl->protocol) {
+    switch (ctl->server.protocol) {
     case P_AUTO:
        for (i = 0; i < sizeof(autoprobe)/sizeof(autoprobe[0]); i++)
        {
-           ctl->protocol = autoprobe[i];
+           ctl->server.protocol = autoprobe[i];
            if ((st = query_host(ctl)) == PS_SUCCESS || st == PS_NOMAIL || st == PS_AUTHFAIL)
                break;
        }
-       ctl->protocol = P_AUTO;
+       ctl->server.protocol = P_AUTO;
        return(st);
        break;
     case P_POP2:
@@ -642,58 +772,85 @@ struct query *ctl;
        break;
     case P_POP3:
     case P_APOP:
+    case P_RPOP:
        return(doPOP3(ctl));
        break;
     case P_IMAP:
        return(doIMAP(ctl));
        break;
+    case P_ETRN:
+       return(doETRN(ctl));
     default:
-       fprintf(stderr,"fetchmail: unsupported protocol selected.\n");
+       error(0, 0, "unsupported protocol selected.");
        return(PS_PROTOCOL);
     }
 }
 
-void dump_params (ctl)
+void dump_params (struct query *ctl)
 /* display query parameters in English */
-struct query *ctl;     /* query parameter block */
 {
     printf("Options for retrieving from %s@%s:\n",
-          ctl->remotename, visbuf(ctl->servername));
+          ctl->remotename, visbuf(ctl->server.names->id));
+
+    if (logfile)
+       printf("  Logfile is %s\n", logfile);
+    if (poll_interval)
+       printf("  Poll interval is %d seconds\n", poll_interval);
 #ifdef HAVE_GETHOSTBYNAME
-    if (ctl->canonical_name)
-       printf("  Canonical DNS name of server is %s.\n", ctl->canonical_name);
+    if (ctl->server.canonical_name)
+       printf("  Canonical DNS name of server is %s.\n", ctl->server.canonical_name);
 #endif /* HAVE_GETHOSTBYNAME */
-    if (ctl->skip || outlevel == O_VERBOSE)
+    if (ctl->server.names->next)
+    {
+       struct idlist *idp;
+
+       printf("  Predeclared mailserver aliases:");
+       for (idp = ctl->server.names->next; idp; idp = idp->next)
+           printf(" %s", idp->id);
+       putchar('\n');
+    }
+    if (ctl->server.skip || outlevel == O_VERBOSE)
        printf("  This host will%s be queried when no host is specified.\n",
-              ctl->skip ? " not" : "");
-    if (ctl->password[0] == '\0')
+              ctl->server.skip ? " not" : "");
+    if (!ctl->password)
        printf("  Password will be prompted for.\n");
     else if (outlevel == O_VERBOSE)
-       if (ctl->protocol == P_APOP)
+       if (ctl->server.protocol == P_APOP)
            printf("  APOP secret = '%s'.\n", visbuf(ctl->password));
+       else if (ctl->server.protocol == P_RPOP)
+           printf("  RPOP id = '%s'.\n", visbuf(ctl->password));
         else
            printf("  Password = '%s'.\n", visbuf(ctl->password));
-    if (ctl->protocol == P_POP3 
-               && ctl->port == KPOP_PORT
-               && ctl->authenticate == A_KERBEROS)
+    if (ctl->server.protocol == P_POP3 
+               && ctl->server.port == KPOP_PORT
+               && ctl->server.authenticate == A_KERBEROS_V4)
        printf("  Protocol is KPOP");
     else
-    {
-       printf("  Protocol is %s", showproto(ctl->protocol));
-    }
-    if (ctl->port)
-       printf(" (using port %d)", ctl->port);
+       printf("  Protocol is %s", showproto(ctl->server.protocol));
+    if (ctl->server.port)
+       printf(" (using port %d)", ctl->server.port);
     else if (outlevel == O_VERBOSE)
        printf(" (using default port)");
+    if (ctl->server.uidl)
+       printf(" (forcing UIDL use)");
     putchar('.');
     putchar('\n');
-    if (ctl->authenticate == A_KERBEROS)
-           printf("  Kerberos authentication enabled.\n");
-    printf("  Server nonresponse timeout is %d seconds", ctl->timeout);
-    if (ctl->timeout ==  CLIENT_TIMEOUT)
+    if (ctl->server.authenticate == A_KERBEROS_V4)
+           printf("  Kerberos V4 preauthentication enabled.\n");
+    printf("  Server nonresponse timeout is %d seconds", ctl->server.timeout);
+    if (ctl->server.timeout ==  CLIENT_TIMEOUT)
        printf(" (default).\n");
     else
-       printf("\n.");
+       printf(".\n");
+    if (ctl->server.localdomains)
+    {
+       struct idlist *idp;
+
+       printf("  Local domains:");
+       for (idp = ctl->server.localdomains; idp; idp = idp->next)
+           printf(" %s", idp->id);
+       putchar('\n');
+    }
 
     printf("  %s messages will be retrieved (--all %s).\n",
           ctl->fetchall ? "All" : "Only new",
@@ -705,25 +862,43 @@ struct query *ctl;        /* query parameter block */
           ctl->flush ? "" : " not",
           ctl->flush ? "on" : "off");
     printf("  Rewrite of server-local addresses is %sabled (--norewrite %s).\n",
-          ctl->norewrite ? "dis" : "en",
-          ctl->norewrite ? "on" : "off");
+          ctl->rewrite ? "en" : "dis",
+          ctl->rewrite ? "off" : "on");
+    printf("  Carriage-return stripping is %sabled (--stripcr %s).\n",
+          ctl->stripcr ? "en" : "dis",
+          ctl->stripcr ? "on" : "off");
+    printf("  Carriage-return forcing is %sabled (--forcecr %s).\n",
+          ctl->forcecr ? "en" : "dis",
+          ctl->forcecr ? "on" : "off");
     if (ctl->limit)
-       printf("  Message size limit is %d bytes\n", ctl->limit);
+       printf("  Message size limit is %d bytes (--limit %d).\n", 
+              ctl->limit, ctl->limit);
+    else if (outlevel == O_VERBOSE)
+       printf("  No message size limit (--limit 0).\n");
+    if (ctl->fetchlimit)
+       printf("  Received-message limit is %d (--fetchlimit %d).\n",
+              ctl->fetchlimit, ctl->fetchlimit);
     else if (outlevel == O_VERBOSE)
-       printf("  No message size limit\n");
-    if (ctl->mda[0])
+       printf("  No received-message limit (--fetchlimit 0).\n");
+    if (ctl->batchlimit)
+       printf("  SMTP message batch limit is %d.\n", ctl->batchlimit);
+    else if (outlevel == O_VERBOSE)
+       printf("  No SMTP message batch limit.\n");
+    if (ctl->mda)
+       printf("  Messages will be delivered with '%s.'\n", visbuf(ctl->mda));
+    else
     {
-       char **cp;
+       struct idlist *idp;
 
-       printf("  Messages will be delivered with %s, args:",
-              visbuf(ctl->mda_argv[0]));
-       for (cp = ctl->mda_argv+1; *cp; cp++)
-           printf(" %s", visbuf(*cp));
-       putchar('\n');
+       printf("  Messages will be SMTP-forwarded to:");
+       for (idp = ctl->smtphunt; idp; idp = idp->next)
+           printf(" %s", idp->id);
+       printf("\n");
     }
-    else
-       printf("  Messages will be SMTP-forwarded to '%s'.\n",
-              visbuf(ctl->smtphost));
+    if (ctl->preconnect)
+       printf("  Server connection will be preinitialized with '%s.'\n", visbuf(ctl->preconnect));
+    else if (outlevel == O_VERBOSE)
+       printf("  No preinitialization command.\n");
     if (!ctl->localnames)
        printf("  No localnames declared for this host.\n");
     else
@@ -734,16 +909,42 @@ struct query *ctl;        /* query parameter block */
        for (idp = ctl->localnames; idp; idp = idp->next)
            ++count;
 
-       printf("  %d local names recognized.\n", count);
+       printf("  %d local name(s) recognized%s.\n",
+              count,
+              (count == 1 && !strcmp(ctl->localnames->id, user)) ? " (by default)" : "");
        if (outlevel == O_VERBOSE)
+       {
            for (idp = ctl->localnames; idp; idp = idp->next)
                if (idp->val.id2)
-                   fprintf(stderr, "\t%s -> %s\n", idp->id, idp->val.id2);
+                   printf("\t%s -> %s\n", idp->id, idp->val.id2);
                else
-                   fprintf(stderr, "\t%s\n", idp->id);
+                   printf("\t%s\n", idp->id);
+           if (ctl->wildcard)
+               fputs("*\n", stdout);
+       }
+
+       printf("  DNS lookup for multidrop addresses is %sabled.\n",
+              ctl->server.dns ? "en" : "dis",
+              ctl->server.dns ? "off" : "on");
+
+       if (count > 1)
+           if (ctl->server.envelope == STRING_DISABLED)
+               printf("  Envelope-address routing is disabled\n");
+           else
+               printf("  Envelope header is assumed to be: %s\n", ctl->server.envelope);
     }
+#ifdef linux
+    if (ctl->server.interface)
+       printf("  Connection must be through interface %s.\n", ctl->server.interface);
+    else if (outlevel == O_VERBOSE)
+       printf("  No interface requirement specified.\n");
+    if (ctl->server.monitor)
+       printf("  Polling loop will monitor %s.\n", ctl->server.monitor);
+    else if (outlevel == O_VERBOSE)
+       printf("  No monitor interface specified.\n");
+#endif
 
-    if (ctl->protocol > P_POP2)
+    if (ctl->server.protocol > P_POP2)
        if (!ctl->oldsaved)
            printf("  No UIDs saved from this host.\n");
        else
@@ -757,90 +958,12 @@ struct query *ctl;        /* query parameter block */
            printf("  %d UIDs saved.\n", count);
            if (outlevel == O_VERBOSE)
                for (idp = ctl->oldsaved; idp; idp = idp->next)
-                   fprintf(stderr, "\t%s %s\n", ctl->servername, idp->id);
-       }
-}
-
-int openmailpipe (argv)
-/* open a one-way pipe to a mail delivery agent */
-char *argv[];
-{
-    int pipefd [2];
-    int childpid;
-
-    if (outlevel == O_VERBOSE)
-    {
-       char **cp;
-
-       printf("fetchmail: about to deliver via MDA %s, args:",
-              visbuf(argv[0]));
-       for (cp = argv+1; *cp; cp++)
-           printf(" %s(%p)", visbuf(*cp), *cp);
-       putchar('\n');
-    }
-
-    if (pipe(pipefd) < 0) {
-       perror("fetchmail: openmailpipe: pipe");
-       return(-1);
-    }
-    if ((childpid = fork()) < 0) {
-       perror("fetchmail: openmailpipe: fork");
-       return(-1);
-    }
-    else if (childpid == 0) {
-
-       /* in child process space */
-       close(pipefd[1]);  /* close the 'write' end of the pipe */
-       close(0);          /* get rid of inherited stdin */
-       if (dup(pipefd[0]) != 0) {
-           fputs("fetchmail: openmailpipe: dup() failed\n",stderr);
-           exit(1);
+                   fprintf(stderr, "\t%s\n", idp->id);
        }
-
-       execv(argv[0], argv + 1);
-
-       /* if we got here, an error occurred */
-       perror("fetchmail: openmailpipe: exec");
-       _exit(PS_SYNTAX);
-
-    }
-
-    /* in the parent process space */
-    close(pipefd[0]);  /* close the 'read' end of the pipe */
-    return(pipefd[1]);
-}
-
-int closemailpipe (fd)
-/* close the pipe to the mail delivery agent */
-int fd;
-{
-    int err, status;
-    int childpid;
-
-    if ((err = close(fd)) != 0)
-       perror("fetchmail: closemailpipe: close failed");
-
-    childpid = wait(&status);
-
-#if defined(WIFEXITED) && defined(WEXITSTATUS)
-    /*
-     * Try to pass up an error if the MDA returned nonzero status,
-     * on the assumption that this means it was reporting failure.
-     */
-    if (WIFEXITED(status) == 0 && WEXITSTATUS(status) != 0)
-    {
-       perror("fetchmail: MDA exited abnormally or returned nonzero status");
-       err = -1;
-    }
-#endif
-
-    return(err);
 }
 
 /* helper functions for string interpretation and display */
 
-#define CTRL(x)        ((x) & 0x1f)
-
 void escapes(cp, tp)
 /* process standard C-style escape sequences in a string */
 const char     *cp;    /* source string with escapes */
@@ -878,11 +1001,6 @@ char              *tp;    /* target buffer for digested string */
            }
            cp++;
        }
-       else if (*cp == '^')            /* expand control-character syntax */
-       {
-           cval = CTRL(*++cp);
-           cp++;
-       }
        else
            cval = *cp++;
        *tp++ = cval;
@@ -890,9 +1008,8 @@ char               *tp;    /* target buffer for digested string */
     *tp = '\0';
 }
 
-static char *visbuf(buf)
+static char *visbuf(const char *buf)
 /* visibilize a given string */
-const char *buf;
 {
     static char vbuf[BUFSIZ];
     char *tp = vbuf;