]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/main.c
add reopen_log(), call it after using PAM stuff
[ppp.git] / pppd / main.c
index 3e7ea420cc606a6b9fbee1f0b7d2dc019c156dee..c618a2616f600367aa134b59e33086f07b071bdc 100644 (file)
@@ -18,7 +18,7 @@
  */
 
 #ifndef lint
-static char rcsid[] = "$Id: main.c,v 1.60 1999/03/16 03:14:50 paulus Exp $";
+static char rcsid[] = "$Id: main.c,v 1.70 1999/03/31 05:39:42 paulus Exp $";
 #endif
 
 #include <stdio.h>
@@ -76,22 +76,25 @@ int ifunit;                 /* Interface unit number */
 char *progname;                        /* Name of this program */
 char hostname[MAXNAMELEN];     /* Our hostname */
 static char pidfilename[MAXPATHLEN];   /* name of pid file */
-static char default_devnam[MAXPATHLEN];        /* name of default device */
+static char ppp_devnam[MAXPATHLEN]; /* name of PPP tty (maybe ttypx) */
 static pid_t pid;              /* Our pid */
 static uid_t uid;              /* Our real user-id */
 static int conn_running;       /* we have a [dis]connector running */
 
-int ttyfd = -1;                        /* Serial port file descriptor */
+int ttyfd;                     /* Serial port file descriptor */
 mode_t tty_mode = -1;          /* Original access permissions to tty */
 int baud_rate;                 /* Actual bits/second for serial device */
 int hungup;                    /* terminal has been hung up */
 int privileged;                        /* we're running as real uid root */
 int need_holdoff;              /* need holdoff period before restarting */
 int detached;                  /* have detached from terminal */
-int log_to_stderr;             /* send log messages to stderr too */
+int log_to_fd;                 /* send log messages to this fd too */
 
-static int fd_ppp;             /* fd for talking PPP */
+static int fd_ppp = -1;                /* fd for talking PPP */
 static int fd_loop;            /* fd for getting demand-dial packets */
+static int pty_master;         /* fd for master side of pty */
+static int pty_slave;          /* fd for slave side of pty */
+static int real_ttyfd;         /* fd for actual serial port (not pty) */
 
 int phase;                     /* where the link is at */
 int kill_link;
@@ -107,6 +110,7 @@ u_char outpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for outgoing packet */
 u_char inpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for incoming packet */
 
 static int n_children;         /* # child processes still running */
+static int got_sigchld;                /* set if we have received a SIGCHLD */
 
 static int locked;             /* lock() has succeeded */
 
@@ -115,6 +119,13 @@ char *no_ppp_msg = "Sorry - this system lacks PPP kernel support\n";
 GIDSET_TYPE groups[NGROUPS_MAX];/* groups the user is in */
 int ngroups;                   /* How many groups valid in groups */
 
+static struct timeval start_time;      /* Time when link was started. */
+
+struct pppd_stats link_stats;
+int link_stats_valid;
+
+static int charshunt_pid;      /* Process ID for charshunt */
+
 /* Prototypes for procedures local to this file. */
 
 static void create_pidfile __P((void));
@@ -131,11 +142,19 @@ static void toggle_debug __P((int));
 static void open_ccp __P((int));
 static void bad_signal __P((int));
 static void holdoff_end __P((void *));
-static int device_script __P((char *, int, int));
-static void reap_kids __P((void));
+static int device_script __P((char *, int, int, int));
+static void reap_kids __P((int waitfor));
 static void pr_log __P((void *, char *, ...));
 static void logit __P((int, char *, va_list));
 static void vslp_printer __P((void *, char *, ...));
+static void format_packet __P((u_char *, int, void (*) (void *, char *, ...),
+                              void *));
+static void record_child __P((int, char *, void (*) (void *), void *));
+static int start_charshunt __P((int, int));
+static void charshunt_done __P((void *));
+static void charshunt __P((int, int, char *));
+static int record_write(FILE *, int code, u_char *buf, int nb,
+                       struct timeval *);
 
 extern char    *ttyname __P((int));
 extern char    *getlogin __P((void));
@@ -187,22 +206,15 @@ main(argc, argv)
     struct protent *protp;
     struct stat statbuf;
     char numbuf[16];
+    struct timeval now;
 
     phase = PHASE_INITIALIZE;
-    p = ttyname(0);
-    if (p)
-       strlcpy(devnam, sizeof(devnam), p);
-    strlcpy(default_devnam, sizeof(default_devnam), devnam);
+    log_to_fd = -1;
 
     script_env = NULL;
 
     /* Initialize syslog facilities */
-#ifdef ULTRIX
-    openlog("pppd", LOG_PID);
-#else
-    openlog("pppd", LOG_PID | LOG_NDELAY, LOG_PPP);
-    setlogmask(LOG_UPTO(LOG_INFO));
-#endif
+    reopen_log();
 
     if (gethostname(hostname, MAXNAMELEN) < 0 ) {
        option_error("Couldn't get hostname: %m");
@@ -230,7 +242,25 @@ main(argc, argv)
     if (!options_from_file(_PATH_SYSOPTIONS, !privileged, 0, 1)
        || !options_from_user())
        exit(1);
+    using_pty = notty || ptycommand != NULL;
     scan_args(argc-1, argv+1); /* look for tty name on command line */
+
+    /*
+     * Work out the device name, if it hasn't already been specified.
+     */
+    if (!using_pty) {
+       p = isatty(0)? ttyname(0): NULL;
+       if (p != NULL) {
+           if (default_device)
+               strlcpy(devnam, p, sizeof(devnam));
+           else if (strcmp(devnam, p) == 0)
+               default_device = 1;
+       }
+    }
+
+    /*
+     * Parse the tty options file and the command line.
+     */
     if (!options_for_tty()
        || !parse_args(argc-1, argv+1))
        exit(1);
@@ -263,19 +293,31 @@ main(argc, argv)
        exit(1);
     }
 
-    script_setenv("DEVICE", devnam);
-    slprintf(numbuf, sizeof(numbuf), "%d", baud_rate);
-    script_setenv("SPEED", numbuf);
-
-    /*
-     * If the user has specified the default device name explicitly,
-     * pretend they hadn't.
-     */
-    if (!default_device && strcmp(devnam, default_devnam) == 0)
-       default_device = 1;
+    if (using_pty) {
+       if (!default_device) {
+           option_error("%s option precludes specifying device name",
+                        notty? "notty": "pty");
+           exit(1);
+       }
+       if (ptycommand != NULL && notty) {
+           option_error("pty option is incompatible with notty option");
+           exit(1);
+       }
+       default_device = notty;
+       lockflag = 0;
+       modem = 0;
+    } else {
+       if (devnam[0] == 0) {
+           option_error("no device specified and stdin is not a tty");
+           exit(1);
+       }
+    }
     if (default_device)
        nodetach = 1;
-    log_to_stderr = !default_device;
+    else
+       log_to_fd = 1;          /* default to stdout */
+
+    script_setenv("DEVICE", devnam);
 
     /*
      * Initialize system-dependent stuff and magic number package.
@@ -403,6 +445,8 @@ main(argc, argv)
     for (;;) {
 
        need_holdoff = 1;
+       ttyfd = -1;
+       real_ttyfd = -1;
 
        if (demand) {
            /*
@@ -432,7 +476,8 @@ main(argc, argv)
                }
                if (get_loop_output())
                    break;
-               reap_kids();
+               if (got_sigchld)
+                   reap_kids(0);
            }
            remove_fd(fd_loop);
            if (kill_link && !persist)
@@ -445,6 +490,21 @@ main(argc, argv)
            info("Starting link");
        }
 
+       /*
+        * Get a pty master/slave pair if the pty, notty, or record
+        * options were specified.
+        */
+       strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
+       pty_master = -1;
+       pty_slave = -1;
+       if (ptycommand != NULL || notty || record_file != NULL) {
+           if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) {
+               error("Couldn't allocate pseudo-tty");
+               goto fail;
+           }
+           set_up_tty(pty_slave, 1);
+       }
+
        /*
         * Lock the device if we've been asked to.
         */
@@ -463,68 +523,109 @@ main(argc, argv)
         */
        hungup = 0;
        kill_link = 0;
-       for (;;) {
-           /* If the user specified the device name, become the
-              user before opening it. */
-           if (!devnam_info.priv)
-               seteuid(uid);
-           ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0);
-           if (!devnam_info.priv)
-               seteuid(0);
-           if (ttyfd >= 0)
-               break;
-           if (errno != EINTR)
-               error("Failed to open %s: %m", devnam);
-           if (!persist || errno != EINTR)
-               goto fail;
-       }
-       if ((fdflags = fcntl(ttyfd, F_GETFL)) == -1
-           || fcntl(ttyfd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
-           warn("Couldn't reset non-blocking mode on device: %m");
-
-       /*
-        * Do the equivalent of `mesg n' to stop broadcast messages.
-        */
-       if (fstat(ttyfd, &statbuf) < 0
-           || fchmod(ttyfd, statbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
-           warn("Couldn't restrict write permissions to %s: %m", devnam);
-       } else
-           tty_mode = statbuf.st_mode;
-
-       /* run connection script */
-       if (connector && connector[0]) {
-           MAINDEBUG(("Connecting with <%s>", connector));
-
-           if (!default_device && modem) {
-               hangup_modem(ttyfd);    /* in case modem is off hook */
-               sleep(1);
+       if (devnam[0] != 0) {
+           for (;;) {
+               /* If the user specified the device name, become the
+                  user before opening it. */
+               if (!devnam_info.priv && !default_device)
+                   seteuid(uid);
+               ttyfd = open(devnam, O_NONBLOCK | O_RDWR, 0);
+               if (!devnam_info.priv && !default_device)
+                   seteuid(0);
+               if (ttyfd >= 0)
+                   break;
+               if (errno != EINTR)
+                   error("Failed to open %s: %m", devnam);
+               if (!persist || errno != EINTR)
+                   goto fail;
            }
+           if ((fdflags = fcntl(ttyfd, F_GETFL)) == -1
+               || fcntl(ttyfd, F_SETFL, fdflags & ~O_NONBLOCK) < 0)
+               warn("Couldn't reset non-blocking mode on device: %m");
+
+           /*
+            * Do the equivalent of `mesg n' to stop broadcast messages.
+            */
+           if (fstat(ttyfd, &statbuf) < 0
+               || fchmod(ttyfd, statbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) {
+               warn("Couldn't restrict write permissions to %s: %m", devnam);
+           } else
+               tty_mode = statbuf.st_mode;
 
            /*
             * Set line speed, flow control, etc.
-            * On most systems we set CLOCAL for now so that we can talk
+            * If we have a non-null connection script,
+            * on most systems we set CLOCAL for now so that we can talk
             * to the modem before carrier comes up.  But this has the
             * side effect that we might miss it if CD drops before we
             * get to clear CLOCAL below.  On systems where we can talk
             * successfully to the modem with CLOCAL clear and CD down,
             * we could clear CLOCAL at this point.
             */
-           set_up_tty(ttyfd, 1);
+           set_up_tty(ttyfd, (connector != NULL && connector[0] != 0));
+           real_ttyfd = ttyfd;
+       }
+
+       /*
+        * If the notty and/or record option was specified,
+        * start up the character shunt now.
+        */
+       if (ptycommand != NULL) {
+           if (record_file != NULL) {
+               int ipipe[2], opipe[2], ok;
+
+               if (pipe(ipipe) < 0 || pipe(opipe) < 0)
+                   fatal("Couldn't create pipes for record option: %m");
+               ok = device_script(ptycommand, opipe[0], ipipe[1], 1) == 0
+                   && start_charshunt(ipipe[0], opipe[1]);
+               close(ipipe[0]);
+               close(ipipe[1]);
+               close(opipe[0]);
+               close(opipe[1]);
+               if (!ok)
+                   goto fail;
+           } else {
+               if (device_script(ptycommand, pty_master, pty_master, 1) < 0)
+                   goto fail;
+               ttyfd = pty_slave;
+               close(pty_master);
+               pty_master = -1;
+           }
+       } else if (notty) {
+           if (!start_charshunt(0, 1))
+               goto fail;
+       } else if (record_file != NULL) {
+           if (!start_charshunt(ttyfd, ttyfd))
+               goto fail;
+       }
 
-           if (device_script(connector, ttyfd, ttyfd) < 0) {
+       /* run connection script */
+       if (connector && connector[0]) {
+           MAINDEBUG(("Connecting with <%s>", connector));
+
+           if (real_ttyfd != -1) {
+               if (!default_device && modem) {
+                   setdtr(real_ttyfd, 0);      /* in case modem is off hook */
+                   sleep(1);
+                   setdtr(real_ttyfd, 1);
+               }
+           }
+
+           if (device_script(connector, ttyfd, ttyfd, 0) < 0) {
                error("Connect script failed");
                goto fail;
            }
 
            info("Serial connection established.");
-           sleep(1);           /* give it time to set up its terminal */
-       }
 
-       /* set line speed, flow control, etc.; clear CLOCAL if modem option */
-       set_up_tty(ttyfd, 0);
+           /* set line speed, flow control, etc.;
+              clear CLOCAL if modem option */
+           if (real_ttyfd != -1)
+               set_up_tty(real_ttyfd, 0);
+       }
 
        /* reopen tty if necessary to wait for carrier */
-       if (connector == NULL && modem) {
+       if (connector == NULL && modem && devnam[0] != 0) {
            for (;;) {
                if ((i = open(devnam, O_RDWR)) >= 0)
                    break;
@@ -536,14 +637,19 @@ main(argc, argv)
            close(i);
        }
 
+       slprintf(numbuf, sizeof(numbuf), "%d", baud_rate);
+       script_setenv("SPEED", numbuf);
+
        /* run welcome script, if any */
        if (welcomer && welcomer[0]) {
-           if (device_script(welcomer, ttyfd, ttyfd) < 0)
+           if (device_script(welcomer, ttyfd, ttyfd, 0) < 0)
                warn("Welcome script failed");
        }
 
        /* set up the serial device as a ppp interface */
        fd_ppp = establish_ppp(ttyfd);
+       if (fd_ppp < 0)
+           goto fail;
 
        if (!demand) {
            
@@ -558,8 +664,22 @@ main(argc, argv)
         * Start opening the connection and wait for
         * incoming events (reply, timeout, etc.).
         */
-       notice("Connect: %s <--> %s", ifname, devnam);
+       notice("Connect: %s <--> %s", ifname, ppp_devnam);
+       gettimeofday(&start_time, NULL);
        lcp_lowerup(0);
+
+       /*
+        * If we are initiating this connection, wait for a short
+        * time for something from the peer.  This can avoid bouncing
+        * our packets off his tty before he has it set up.
+        */
+       if (connector != NULL || ptycommand != NULL) {
+           struct timeval t;
+           t.tv_sec = 1;
+           t.tv_usec = 0;
+           wait_input(&t);
+       }
+
        lcp_open(0);            /* Start protocol */
        open_ccp_flag = 0;
        add_fd(fd_ppp);
@@ -576,11 +696,11 @@ main(argc, argv)
            }
            waiting = 0;
            calltimeout();
+           get_input();
            if (kill_link) {
                lcp_close(0, "User request");
                kill_link = 0;
            }
-           get_input();
            if (open_ccp_flag) {
                if (phase == PHASE_NETWORK) {
                    ccp_fsm[0].flags = OPT_RESTART; /* clears OPT_SILENT */
@@ -588,7 +708,21 @@ main(argc, argv)
                }
                open_ccp_flag = 0;
            }
-           reap_kids();        /* Don't leave dead kids lying around */
+           if (got_sigchld)
+               reap_kids(0);   /* Don't leave dead kids lying around */
+       }
+
+       /*
+        * Print connect time and statistics.
+        */
+       if (gettimeofday(&now, NULL) >= 0) {
+           int t = now.tv_sec - start_time.tv_sec;
+           t = (t + 5) / 6;    /* now in 1/10ths of minutes */
+           info("Connect time %d.%d minutes.", t/10, t%10);
+       }
+       if (link_stats_valid) {
+           info("Sent %d bytes, received %d bytes.",
+                link_stats.bytes_out, link_stats.bytes_in);
        }
 
        /*
@@ -601,22 +735,30 @@ main(argc, argv)
        if (demand)
            restore_loop();
        disestablish_ppp(ttyfd);
+       fd_ppp = -1;
 
        /*
         * Run disconnector script, if requested.
         * XXX we may not be able to do this if the line has hung up!
         */
        if (disconnector && !hungup) {
-           set_up_tty(ttyfd, 1);
-           if (device_script(disconnector, ttyfd, ttyfd) < 0) {
+           if (real_ttyfd >= 0)
+               set_up_tty(real_ttyfd, 1);
+           if (device_script(disconnector, ttyfd, ttyfd, 0) < 0) {
                warn("disconnect script failed");
            } else {
                info("Serial link disconnected.");
            }
        }
+       if (!hungup)
+           lcp_lowerdown(0);
 
     fail:
-       if (ttyfd >= 0)
+       if (pty_master >= 0)
+           close(pty_master);
+       if (pty_slave >= 0)
+           close(pty_slave);
+       if (real_ttyfd >= 0)
            close_tty();
        if (locked) {
            unlock();
@@ -656,7 +798,8 @@ main(argc, argv)
                    kill_link = 0;
                    phase = PHASE_DORMANT; /* allow signal to end holdoff */
                }
-               reap_kids();
+               if (got_sigchld)
+                   reap_kids(0);
            } while (phase == PHASE_HOLDOFF);
            if (!persist)
                break;
@@ -665,7 +808,7 @@ main(argc, argv)
 
     /* Wait for scripts to finish */
     while (n_children > 0)
-       reap_kids();
+       reap_kids(1);
 
     die(0);
     return 0;
@@ -684,13 +827,27 @@ detach()
        die(1);
     }
     detached = 1;
-    log_to_stderr = 0;
+    log_to_fd = -1;
     pid = getpid();
     /* update pid file if it has been written already */
     if (pidfilename[0])
        create_pidfile();
 }
 
+/*
+ * reopen_log - (re)open our connection to syslog.
+ */
+void
+reopen_log()
+{
+#ifdef ULTRIX
+    openlog("pppd", LOG_PID);
+#else
+    openlog("pppd", LOG_PID | LOG_NDELAY, LOG_PPP);
+    setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+}
+
 /*
  * Create a file containing our process ID.
  */
@@ -802,16 +959,7 @@ get_input()
 
 
 /*
- * quit - Clean up state and exit (with an error indication).
- */
-void 
-quit()
-{
-    die(1);
-}
-
-/*
- * die - like quit, except we can specify an exit status.
+ * die - clean up state and exit with the specified status.
  */
 void
 die(status)
@@ -831,7 +979,9 @@ cleanup()
 {
     sys_cleanup();
 
-    if (ttyfd >= 0)
+    if (fd_ppp >= 0)
+       disestablish_ppp(ttyfd);
+    if (real_ttyfd >= 0)
        close_tty();
 
     if (pidfilename[0] != 0 && unlink(pidfilename) < 0 && errno != ENOENT) 
@@ -848,11 +998,9 @@ cleanup()
 static void
 close_tty()
 {
-    disestablish_ppp(ttyfd);
-
     /* drop dtr to hang up */
     if (!default_device && modem) {
-       hangup_modem(ttyfd);
+       setdtr(real_ttyfd, 0);
        /*
         * This sleep is in case the serial port has CLOCAL set by default,
         * and consequently will reassert DTR when we close the device.
@@ -860,26 +1008,17 @@ close_tty()
        sleep(1);
     }
 
-    restore_tty(ttyfd);
+    restore_tty(real_ttyfd);
 
     if (tty_mode != (mode_t) -1) {
-       if (fchmod(ttyfd, tty_mode) != 0) {
+       if (fchmod(real_ttyfd, tty_mode) != 0) {
            /* XXX if devnam is a symlink, this will change the link */
            chmod(devnam, tty_mode);
        }
     }
 
-    close(ttyfd);
-    ttyfd = -1;
-}
-
-/*
- * hangup_modem - hang up the modem by clearing DTR.
- */
-void hangup_modem(ttyfd)
-    int ttyfd;
-{
-    setdtr(ttyfd, 0);
+    close(real_ttyfd);
+    real_ttyfd = -1;
 }
 
 
@@ -1040,6 +1179,8 @@ hup(sig)
     if (conn_running)
        /* Send the signal to the [dis]connector process(es) also */
        kill_my_pg(sig);
+    if (charshunt_pid)
+       kill(charshunt_pid, sig);
     if (waiting)
        siglongjmp(sigjmp, 1);
 }
@@ -1061,6 +1202,8 @@ term(sig)
     if (conn_running)
        /* Send the signal to the [dis]connector process(es) also */
        kill_my_pg(sig);
+    if (charshunt_pid)
+       kill(charshunt_pid, sig);
     if (waiting)
        siglongjmp(sigjmp, 1);
 }
@@ -1068,12 +1211,13 @@ term(sig)
 
 /*
  * chld - Catch SIGCHLD signal.
- * Calls reap_kids to get status for any dead kids.
+ * Sets a flag so we will call reap_kids in the mainline.
  */
 static void
 chld(sig)
     int sig;
 {
+    got_sigchld = 1;
     if (waiting)
        siglongjmp(sigjmp, 1);
 }
@@ -1129,53 +1273,51 @@ bad_signal(sig)
     error("Fatal signal %d", sig);
     if (conn_running)
        kill_my_pg(SIGTERM);
+    if (charshunt_pid)
+       kill(charshunt_pid, SIGTERM);
     die(1);
 }
 
 
 /*
- * device_script - run a program to connect or disconnect the
- * serial device.
+ * device_script - run a program to talk to the serial device
+ * (e.g. to run the connector or disconnector script).
  */
 static int
-device_script(program, in, out)
+device_script(program, in, out, dont_wait)
     char *program;
     int in, out;
+    int dont_wait;
 {
     int pid;
-    int status;
+    int status = 0;
     int errfd;
 
-    conn_running = 1;
+    ++conn_running;
     pid = fork();
 
     if (pid < 0) {
-       conn_running = 0;
-       fatal("Failed to create child process: %m");
+       --conn_running;
+       error("Failed to create child process: %m");
+       return -1;
     }
 
     if (pid == 0) {
        sys_close();
        closelog();
-       if (in == out) {
-           if (in != 0) {
-               dup2(in, 0);
-               close(in);
-           }
-           dup2(0, 1);
-       } else {
-           if (out == 0)
-               out = dup(out);
-           if (in != 0) {
-               dup2(in, 0);
-               close(in);
-           }
-           if (out != 1) {
-               dup2(out, 1);
-               close(out);
-           }
+       if (in == 2) {
+           /* aargh!!! */
+           int newin = dup(in);
+           if (in == out)
+               out = newin;
+           in = newin;
+       } else if (out == 2) {
+           out = dup(out);
        }
-       if (!nodetach && !updetach) {
+       if (log_to_fd >= 0) {
+           if (log_to_fd != 2)
+               dup2(log_to_fd, 2);
+       } else {
            close(2);
            errfd = open(_PATH_CONNERRS, O_WRONLY | O_APPEND | O_CREAT, 0600);
            if (errfd >= 0 && errfd != 2) {
@@ -1183,6 +1325,24 @@ device_script(program, in, out)
                close(errfd);
            }
        }
+       if (in != 0) {
+           if (out == 0)
+               out = dup(out);
+           dup2(in, 0);
+           if (in > 2)
+               close(in);
+       }
+       if (out != 1) {
+           dup2(out, 1);
+           if (out > 2)
+               close(out);
+       }
+       if (real_ttyfd > 2)
+           close(real_ttyfd);
+       if (pty_master > 2)
+           close(pty_master);
+       if (pty_slave > 2)
+           close(pty_slave);
        setuid(uid);
        if (getuid() != uid) {
            error("setuid failed");
@@ -1195,12 +1355,16 @@ device_script(program, in, out)
        /* NOTREACHED */
     }
 
-    while (waitpid(pid, &status, 0) < 0) {
-       if (errno == EINTR)
-           continue;
-       fatal("error waiting for (dis)connection process: %m");
+    if (dont_wait) {
+       record_child(pid, program, NULL, NULL);
+    } else {
+       while (waitpid(pid, &status, 0) < 0) {
+           if (errno == EINTR)
+               continue;
+           fatal("error waiting for (dis)connection process: %m");
+       }
+       --conn_running;
     }
-    conn_running = 0;
 
     return (status == 0 ? 0 : -1);
 }
@@ -1239,7 +1403,6 @@ run_program(prog, args, must_exist, done, arg)
     void *arg;
 {
     int pid;
-    struct subprocess *chp;
     struct stat sbuf;
 
     /*
@@ -1257,8 +1420,10 @@ run_program(prog, args, must_exist, done, arg)
     }
 
     pid = fork();
-    if (pid == -1)
-       fatal("Failed to create child process for %s: %m", prog);
+    if (pid == -1) {
+       error("Failed to create child process for %s: %m", prog);
+       return -1;
+    }
     if (pid == 0) {
        int new_fd;
 
@@ -1276,6 +1441,8 @@ run_program(prog, args, must_exist, done, arg)
        close (1);
        close (2);
        close (ttyfd);  /* tty interface to the ppp device */
+       if (real_ttyfd >= 0)
+           close(real_ttyfd);
 
         /* Don't pass handles to the PPP device, even by accident. */
        new_fd = open (_PATH_DEVNULL, O_RDWR);
@@ -1298,13 +1465,37 @@ run_program(prog, args, must_exist, done, arg)
 
        /* run the program */
        execve(prog, args, script_env);
-       if (must_exist || errno != ENOENT)
-           warn("Can't execute %s: %m", prog);
+       if (must_exist || errno != ENOENT) {
+           /* have to reopen the log, there's nowhere else
+              for the message to go. */
+           reopen_log();
+           syslog(LOG_ERR, "Can't execute %s: %m", prog);
+           closelog();
+       }
        _exit(-1);
     }
 
     if (debug)
        dbglog("Script %s started; pid = %d", prog, pid);
+    record_child(pid, prog, done, arg);
+
+    return pid;
+}
+
+
+/*
+ * record_child - add a child process to the list for reap_kids
+ * to use.
+ */
+static void
+record_child(pid, prog, done, arg)
+    int pid;
+    char *prog;
+    void (*done) __P((void *));
+    void *arg;
+{
+    struct subprocess *chp;
+
     ++n_children;
 
     chp = (struct subprocess *) malloc(sizeof(struct subprocess));
@@ -1318,8 +1509,6 @@ run_program(prog, args, must_exist, done, arg)
        chp->next = children;
        children = chp;
     }
-
-    return pid;
 }
 
 
@@ -1328,29 +1517,31 @@ run_program(prog, args, must_exist, done, arg)
  * and log a message for abnormal terminations.
  */
 static void
-reap_kids()
+reap_kids(waitfor)
+    int waitfor;
 {
     int pid, status;
     struct subprocess *chp, **prevp;
 
+    got_sigchld = 0;
     if (n_children == 0)
        return;
-    while ((pid = waitpid(-1, &status, WNOHANG)) != -1 && pid != 0) {
+    while ((pid = waitpid(-1, &status, (waitfor? 0: WNOHANG))) != -1
+          && pid != 0) {
        --n_children;
        for (prevp = &children; (chp = *prevp) != NULL; prevp = &chp->next)
            if (chp->pid == pid)
                break;
-       if (debug)
-           dbglog("process %d (%s) finished, status = 0x%x",
-                  pid, (chp? chp->prog: "??"), status);
        if (WIFSIGNALED(status)) {
            warn("Child process %s (pid %d) terminated with signal %d",
                 (chp? chp->prog: "??"), pid, WTERMSIG(status));
-       }
+       } else if (debug)
+           dbglog("process %d (%s) finished, status = 0x%x",
+                  pid, (chp? chp->prog: "??"), status);
        if (chp && chp->done)
            (*chp->done)(chp->arg);
     }
-    if (pid == -1 && errno != ECHILD)
+    if (pid == -1 && errno != ECHILD && errno != EINTR)
        error("Error waiting for child process: %m");
 }
 
@@ -1369,7 +1560,7 @@ log_packet(p, len, prefix, level)
     char *prefix;
     int level;
 {
-    strlcpy(line, sizeof(line), prefix);
+    strlcpy(line, prefix, sizeof(line));
     linep = line + strlen(line);
     format_packet(p, len, pr_log, NULL);
     if (linep != line)
@@ -1380,7 +1571,7 @@ log_packet(p, len, prefix, level)
  * format_packet - make a readable representation of a packet,
  * calling `printer(arg, format, ...)' to output it.
  */
-void
+static void
 format_packet(p, len, printer, arg)
     u_char *p;
     int len;
@@ -1389,7 +1580,6 @@ format_packet(p, len, printer, arg)
 {
     int i, n;
     u_short proto;
-    u_char x;
     struct protent *protp;
 
     if (len >= PPP_HDRLEN && p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) {
@@ -1406,16 +1596,25 @@ format_packet(p, len, printer, arg)
            p += n;
            len -= n;
        } else {
-           printer(arg, "[proto=0x%x]", proto);
+           for (i = 0; (protp = protocols[i]) != NULL; ++i)
+               if (proto == (protp->protocol & ~0x8000))
+                   break;
+           if (protp != 0 && protp->data_name != 0) {
+               printer(arg, "[%s data]", protp->data_name);
+               if (len > 8)
+                   printer(arg, "%.8B ...", p);
+               else
+                   printer(arg, "%.*B", len, p);
+               len = 0;
+           } else
+               printer(arg, "[proto=0x%x]", proto);
        }
     }
 
-    for (i = 0; i < len && i < 32; ++i) {
-       GETCHAR(x, p);
-       printer(arg, " %.2x", x);
-    }
-    if (i < len)
-       printer(arg, " ...");
+    if (len > 32)
+       printer(arg, "%.32B ...", p);
+    else
+       printer(arg, "%.*B", len, p);
 }
 
 static void
@@ -1442,7 +1641,7 @@ pr_log __V((void *arg, char *fmt, ...))
        syslog(LOG_DEBUG, "%s", line);
        linep = line;
     }
-    strlcpy(linep, line + sizeof(line) - linep, buf);
+    strlcpy(linep, buf, line + sizeof(line) - linep);
     linep += n;
 }
 
@@ -1881,33 +2080,38 @@ script_unsetenv(var)
  * strlcpy - like strcpy/strncpy, doesn't overflow destination buffer,
  * always leaves destination null-terminated (for len > 0).
  */
-void
-strlcpy(char *dest, size_t len, const char *src)
+size_t
+strlcpy(dest, src, len)
+    char *dest;
+    const char *src;
+    size_t len;
 {
-    if (len == 0)
-       return;
-    if (strlen(src) < len)
-       strcpy(dest, src);
-    else {
-       strncpy(dest, src, len - 1);
-       dest[len-1] = 0;
+    size_t ret = strlen(src);
+
+    if (len != 0) {
+       if (ret < len)
+           strcpy(dest, src);
+       else {
+           strncpy(dest, src, len - 1);
+           dest[len-1] = 0;
+       }
     }
+    return ret;
 }
 
 /*
  * strlcat - like strcat/strncat, doesn't overflow destination buffer,
  * always leaves destination null-terminated (for len > 0).
  */
-void
-strlcat(char *dest, size_t len, const char *src)
+size_t
+strlcat(dest, src, len)
+    char *dest;
+    const char *src;
+    size_t len;
 {
-    size_t dlen;
+    size_t dlen = strlen(dest);
 
-    if (len == 0)
-       return;
-    dlen = strlen(dest);
-    if (dlen < len - 1)
-       strlcpy(dest + dlen, len - dlen, src);
+    return dlen + strlcpy(dest + dlen, src, (len > dlen? len - dlen: 0));
 }
 
 /*
@@ -1921,14 +2125,14 @@ logit(level, fmt, args)
 {
     int n;
     char buf[256];
-    static char nl = '\n';
 
     n = vslprintf(buf, sizeof(buf), fmt, args);
     syslog(level, "%s", buf);
-    if (log_to_stderr && (level != LOG_DEBUG || debug)) {
-       if (write(2, buf, n) != n
-           || (buf[n-1] != '\n' && write(2, &nl, 1) != 1))
-           log_to_stderr = 0;
+    if (log_to_fd >= 0 && (level != LOG_DEBUG || debug)) {
+       if (buf[n-1] != '\n')
+           buf[n++] = '\n';
+       if (write(log_to_fd, buf, n) != n)
+           log_to_fd = -1;
     }
 }
 
@@ -2053,3 +2257,290 @@ dbglog __V((char *fmt, ...))
     logit(LOG_DEBUG, fmt, pvar);
     va_end(pvar);
 }
+
+/*
+ * start_charshunt - create a child process to run the character shunt.
+ */
+static int
+start_charshunt(ifd, ofd)
+    int ifd, ofd;
+{
+    int cpid;
+
+    cpid = fork();
+    if (cpid == -1) {
+       error("Can't fork process for character shunt: %m");
+       return 0;
+    }
+    if (cpid == 0) {
+       /* child */
+       close(pty_slave);
+       setuid(uid);
+       if (getuid() != uid)
+           fatal("setuid failed");
+       setgid(getgid());
+       if (!nodetach)
+           log_to_fd = -1;
+       charshunt(ifd, ofd, record_file);
+       exit(0);
+    }
+    charshunt_pid = cpid;
+    close(pty_master);
+    pty_master = -1;
+    ttyfd = pty_slave;
+    record_child(cpid, "pppd (charshunt)", charshunt_done, NULL);
+    return 1;
+}
+
+static void
+charshunt_done(arg)
+    void *arg;
+{
+    charshunt_pid = 0;
+}
+
+/*
+ * charshunt - the character shunt, which passes characters between
+ * the pty master side and the serial port (or stdin/stdout).
+ * This runs as the user (not as root).
+ * (We assume ofd >= ifd which is true the way this gets called. :-).
+ */
+static void
+charshunt(ifd, ofd, record_file)
+    int ifd, ofd;
+    char *record_file;
+{
+    int n, nfds;
+    fd_set ready, writey;
+    u_char *ibufp, *obufp;
+    int nibuf, nobuf;
+    int flags;
+    int pty_readable, stdin_readable;
+    struct timeval lasttime;
+    FILE *recordf = NULL;
+
+    /*
+     * Reset signal handlers.
+     */
+    signal(SIGHUP, SIG_IGN);           /* Hangup */
+    signal(SIGINT, SIG_DFL);           /* Interrupt */
+    signal(SIGTERM, SIG_DFL);          /* Terminate */
+    signal(SIGCHLD, SIG_DFL);
+    signal(SIGUSR1, SIG_DFL);
+    signal(SIGUSR2, SIG_DFL);
+    signal(SIGABRT, SIG_DFL);
+    signal(SIGALRM, SIG_DFL);
+    signal(SIGFPE, SIG_DFL);
+    signal(SIGILL, SIG_DFL);
+    signal(SIGPIPE, SIG_DFL);
+    signal(SIGQUIT, SIG_DFL);
+    signal(SIGSEGV, SIG_DFL);
+#ifdef SIGBUS
+    signal(SIGBUS, SIG_DFL);
+#endif
+#ifdef SIGEMT
+    signal(SIGEMT, SIG_DFL);
+#endif
+#ifdef SIGPOLL
+    signal(SIGPOLL, SIG_DFL);
+#endif
+#ifdef SIGPROF
+    signal(SIGPROF, SIG_DFL);
+#endif
+#ifdef SIGSYS
+    signal(SIGSYS, SIG_DFL);
+#endif
+#ifdef SIGTRAP
+    signal(SIGTRAP, SIG_DFL);
+#endif
+#ifdef SIGVTALRM
+    signal(SIGVTALRM, SIG_DFL);
+#endif
+#ifdef SIGXCPU
+    signal(SIGXCPU, SIG_DFL);
+#endif
+#ifdef SIGXFSZ
+    signal(SIGXFSZ, SIG_DFL);
+#endif
+
+    /*
+     * Open the record file if required.
+     */
+    if (record_file != NULL) {
+       recordf = fopen(record_file, "a");
+       if (recordf == NULL)
+           error("Couldn't create record file %s: %m", record_file);
+    }
+
+    /* set all the fds to non-blocking mode */
+    flags = fcntl(pty_master, F_GETFL);
+    if (flags == -1
+       || fcntl(pty_master, F_SETFL, flags | O_NONBLOCK) == -1)
+       warn("couldn't set pty master to nonblock: %m");
+    flags = fcntl(ifd, F_GETFL);
+    if (flags == -1
+       || fcntl(ifd, F_SETFL, flags | O_NONBLOCK) == -1)
+       warn("couldn't set %s to nonblock: %m", (ifd==0? "stdin": "tty"));
+    if (ofd != ifd) {
+       flags = fcntl(ofd, F_GETFL);
+       if (flags == -1
+           || fcntl(ofd, F_SETFL, flags | O_NONBLOCK) == -1)
+           warn("couldn't set stdout to nonblock: %m");
+    }
+
+    nibuf = nobuf = 0;
+    ibufp = obufp = NULL;
+    pty_readable = stdin_readable = 1;
+    nfds = (ofd > pty_master? ofd: pty_master) + 1;
+    if (recordf != NULL) {
+       gettimeofday(&lasttime, NULL);
+       putc(7, recordf);       /* put start marker */
+       putc(lasttime.tv_sec >> 24, recordf);
+       putc(lasttime.tv_sec >> 16, recordf);
+       putc(lasttime.tv_sec >> 8, recordf);
+       putc(lasttime.tv_sec, recordf);
+       lasttime.tv_usec = 0;
+    }
+
+    while (nibuf != 0 || nobuf != 0 || pty_readable || stdin_readable) {
+       FD_ZERO(&ready);
+       FD_ZERO(&writey);
+       if (nibuf != 0)
+           FD_SET(pty_master, &writey);
+       else if (stdin_readable)
+           FD_SET(ifd, &ready);
+       if (nobuf != 0)
+           FD_SET(ofd, &writey);
+       else if (pty_readable)
+           FD_SET(pty_master, &ready);
+       if (select(nfds, &ready, &writey, NULL, NULL) < 0) {
+           if (errno != EINTR)
+               fatal("select");
+           continue;
+       }
+       if (FD_ISSET(ifd, &ready)) {
+           ibufp = inpacket_buf;
+           nibuf = read(ifd, ibufp, sizeof(inpacket_buf));
+           if (nibuf < 0 && errno == EIO)
+               nibuf = 0;
+           if (nibuf < 0) {
+               if (!(errno == EINTR || errno == EAGAIN)) {
+                   error("Error reading standard input: %m");
+                   break;
+               }
+               nibuf = 0;
+           } else if (nibuf == 0) {
+               /* end of file from stdin */
+               stdin_readable = 0;
+               /* do a 0-length write, hopefully this will generate
+                  an EOF (hangup) on the slave side. */
+               write(pty_master, inpacket_buf, 0);
+               if (recordf)
+                   if (!record_write(recordf, 4, NULL, 0, &lasttime))
+                       recordf = NULL;
+           } else {
+               FD_SET(pty_master, &writey);
+               if (recordf)
+                   if (!record_write(recordf, 2, ibufp, nibuf, &lasttime))
+                       recordf = NULL;
+           }
+       }
+       if (FD_ISSET(pty_master, &ready)) {
+           obufp = outpacket_buf;
+           nobuf = read(pty_master, obufp, sizeof(outpacket_buf));
+           if (nobuf < 0 && errno == EIO)
+               nobuf = 0;
+           if (nobuf < 0) {
+               if (!(errno == EINTR || errno == EAGAIN)) {
+                   error("Error reading pseudo-tty master: %m");
+                   break;
+               }
+               nobuf = 0;
+           } else if (nobuf == 0) {
+               /* end of file from the pty - slave side has closed */
+               pty_readable = 0;
+               stdin_readable = 0;     /* pty is not writable now */
+               nibuf = 0;
+               close(ofd);
+               if (recordf)
+                   if (!record_write(recordf, 3, NULL, 0, &lasttime))
+                       recordf = NULL;
+           } else {
+               FD_SET(ofd, &writey);
+               if (recordf)
+                   if (!record_write(recordf, 1, obufp, nobuf, &lasttime))
+                       recordf = NULL;
+           }
+       }
+       if (FD_ISSET(ofd, &writey)) {
+           n = write(ofd, obufp, nobuf);
+           if (n < 0) {
+               if (errno != EIO) {
+                   error("Error writing standard output: %m");
+                   break;
+               }
+               pty_readable = 0;
+               nobuf = 0;
+           } else {
+               obufp += n;
+               nobuf -= n;
+           }
+       }
+       if (FD_ISSET(pty_master, &writey)) {
+           n = write(pty_master, ibufp, nibuf);
+           if (n < 0) {
+               if (errno != EIO) {
+                   error("Error writing pseudo-tty master: %m");
+                   break;
+               }
+               stdin_readable = 0;
+               nibuf = 0;
+           } else {
+               ibufp += n;
+               nibuf -= n;
+           }
+       }
+    }
+    exit(0);
+}
+
+static int
+record_write(f, code, buf, nb, tp)
+    FILE *f;
+    int code;
+    u_char *buf;
+    int nb;
+    struct timeval *tp;
+{
+    struct timeval now;
+    int diff;
+
+    gettimeofday(&now, NULL);
+    now.tv_usec /= 100000;     /* actually 1/10 s, not usec now */
+    diff = (now.tv_sec - tp->tv_sec) * 10 + (now.tv_usec - tp->tv_usec);
+    if (diff > 0) {
+       if (diff > 255) {
+           putc(5, f);
+           putc(diff >> 24, f);
+           putc(diff >> 16, f);
+           putc(diff >> 8, f);
+           putc(diff, f);
+       } else {
+           putc(6, f);
+           putc(diff, f);
+       }
+       *tp = now;
+    }
+    putc(code, f);
+    if (buf != NULL) {
+       putc(nb >> 8, f);
+       putc(nb, f);
+       fwrite(buf, nb, 1, f);
+    }
+    fflush(f);
+    if (ferror(f)) {
+       error("Error writing record file: %m");
+       return 0;
+    }
+    return 1;
+}