root/sbin/slattach/slattach.c
/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Rick Adams.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * @(#) Copyright (c) 1988 Regents of the University of California. All rights reserved.
 * @(#)slattach.c       4.6 (Berkeley) 6/1/90
 * $FreeBSD: src/sbin/slattach/slattach.c,v 1.36 1999/08/28 00:14:25 peter Exp $
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <err.h>
#include <fcntl.h>
#include <libutil.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <unistd.h>

#include <net/if.h>
#include <net/slip.h>

#define DEFAULT_BAUD    115200

static void sighup_handler(int);        /* SIGHUP handler */
static void sigint_handler(int);        /* SIGINT handler */
static void sigterm_handler(int);       /* SIGTERM handler */
static void sigurg_handler(int);        /* SIGURG handler */
static void exit_handler(int);          /* run exit_cmd iff specified upon exit. */
static void setup_line(int);            /* configure terminal settings */
static void slip_discipline(void);      /* switch to slip line discipline */
static void configure_network(void);    /* configure slip interface */
static void acquire_line(void);         /* get tty device as controlling terminal */
static void usage(void);

int     fd = -1;
char    *dev = NULL;    /* path name of the tty (e.g. /dev/tty01) */
char    *dvname;                /* basename of dev */
int     locked = 0;             /* uucp lock active */
int     flow_control = 0;       /* non-zero to enable hardware flow control. */
int     modem_control = HUPCL;  /* !CLOCAL+HUPCL iff we watch carrier. */
int     comstate;               /* TIOCMGET current state of serial driver */
int     redial_on_startup = 0;  /* iff non-zero execute redial_cmd on startup */
speed_t speed = DEFAULT_BAUD;   /* baud rate of tty */
int     slflags = 0;            /* compression flags */
int     unit = -1;              /* slip device unit number */
int     foreground = 0;         /* act as demon if zero, else don't fork. */
int     keepal = 0;             /* keepalive timeout */
int     outfill = 0;            /* outfill timeout */
int     sl_unit = -1;           /* unit number */
int     uucp_lock = 0;          /* do uucp locking */
int     exiting = 0;            /* already running exit_handler */

struct  termios tty;            /* tty configuration/state */

char    tty_path[32];           /* path name of the tty (e.g. /dev/tty01) */
char    pidfilename[40];        /* e.g. /var/run/slattach.tty01.pid */
char    *redial_cmd = NULL;     /* command to exec upon shutdown. */
char    *config_cmd = NULL;     /* command to exec if slip unit changes. */
char    *exit_cmd = NULL;               /* command to exec before exiting. */

static void
usage(void)
{
        fprintf(stderr, "%s\n%s\n",
"usage: slattach [-acfhlnz] [-e command] [-r command] [-s speed] [-u command]",
"                [-L] [-K timeout] [-O timeout] [-S unit] device");
        /* do not exit here */
}

int
main(int argc, char **argv)
{
        int option;

        while ((option = getopt(argc, argv, "ace:fhlnr:s:u:zLK:O:S:")) != -1) {
                switch (option) {
                case 'a':
                        slflags |= IFF_LINK2;
                        slflags &= ~IFF_LINK0;
                        break;
                case 'c':
                        slflags |= IFF_LINK0;
                        slflags &= ~IFF_LINK2;
                        break;
                case 'e':
                        exit_cmd = (char*) strdup (optarg);
                        break;
                case 'f':
                        foreground = 1;
                        break;
                case 'h':
                        flow_control |= CRTSCTS;
                        break;
                case 'l':
                        modem_control = CLOCAL; /* clear HUPCL too */
                        break;
                case 'n':
                        slflags |= IFF_LINK1;
                        break;
                case 'r':
                        redial_cmd = (char*) strdup (optarg);
                        break;
                case 's':
                        speed = atoi(optarg);
                        break;
                case 'u':
                        config_cmd = (char*) strdup (optarg);
                        break;
                case 'z':
                        redial_on_startup = 1;
                        break;
                case 'L':
                        uucp_lock = 1;
                        break;
                case 'K':
                        keepal = atoi(optarg);
                        break;
                case 'O':
                        outfill = atoi(optarg);
                        break;
                case 'S':
                        sl_unit = atoi(optarg);
                        break;
                case '?':
                default:
                        usage();
                        exit_handler(1);
                }
        }

        if (optind == argc - 1)
                dev = argv[optind];

        if (optind < (argc - 1))
            warnx("too many args, first='%s'", argv[optind]);
        if (optind > (argc - 1))
            warnx("not enough args");
        if (dev == NULL) {
                usage();
                exit_handler(2);
        }
        if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) {
                strcpy(tty_path, _PATH_DEV);
                strcat(tty_path, "/");
                strncat(tty_path, dev, 10);
                dev = tty_path;
        }
        dvname = strrchr(dev, '/'); /* always succeeds */
        dvname++;                   /* trailing tty pathname component */
        snprintf(pidfilename, sizeof(pidfilename),
            "%sslattach.%s.pid", _PATH_VARRUN, dvname);
        printf("%s\n",pidfilename);

        if (!foreground)
                daemon(0,0);    /* fork, setsid, chdir /, and close std*. */
        /* daemon() closed stderr, so log errors from here on. */
        openlog("slattach",LOG_CONS|LOG_PID,LOG_DAEMON);

        acquire_line();         /* get tty device as controlling terminal */
        setup_line(0);          /* configure for slip line discipline */
        slip_discipline();      /* switch to slip line discipline */

        /* upon INT log a timestamp and exit.  */
        if (signal(SIGINT,sigint_handler) == SIG_ERR)
                syslog(LOG_NOTICE,"cannot install SIGINT handler: %m");
        /* upon TERM log a timestamp and exit.  */
        if (signal(SIGTERM,sigterm_handler) == SIG_ERR)
                syslog(LOG_NOTICE,"cannot install SIGTERM handler: %m");
        /* upon HUP redial and reconnect.  */
        if (signal(SIGHUP,sighup_handler) == SIG_ERR)
                syslog(LOG_NOTICE,"cannot install SIGHUP handler: %m");

        if (redial_on_startup)
                sighup_handler(0);
        else if (!(modem_control & CLOCAL)) {
                if (ioctl(fd, TIOCMGET, &comstate) < 0)
                        syslog(LOG_NOTICE,"cannot get carrier state: %m");
                if (!(comstate & TIOCM_CD)) { /* check for carrier */
                        /* force a redial if no carrier */
                        kill (getpid(), SIGHUP);
                } else
                        configure_network();
        } else
                configure_network(); /* configure the network if needed. */

        for (;;) {
                sigset_t mask;
                sigemptyset(&mask);
                sigsuspend(&mask);
        }
}

/* Close all FDs, fork, reopen tty port as 0-2, and make it the
   controlling terminal for our process group. */
static void
acquire_line(void)
{
        int ttydisc = TTYDISC;
        int oflags;
        FILE *pid_file;

        /* reset to tty discipline */
        if (fd >= 0 && ioctl(fd, TIOCSETD, &ttydisc) < 0) {
                syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
                exit_handler(1);
        }

        close(STDIN_FILENO); /* close FDs before forking. */
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        if (fd > 2)
                close(fd);

        signal(SIGHUP, SIG_IGN); /* ignore HUP signal when parent dies. */
        if (daemon(0,0)) {       /* fork, setsid, chdir /, and close std*. */
                syslog(LOG_ERR, "daemon(0,0): %m");
                exit_handler(1);
        }

        while (getppid () != 1)
                sleep (1);      /* Wait for parent to die. */

        /* create PID file */
        if((pid_file = fopen(pidfilename, "w"))) {
                fprintf(pid_file, "%ld\n", (long)getpid());
                fclose(pid_file);
        }

        if (signal(SIGHUP,sighup_handler) == SIG_ERR) /* Re-enable HUP signal */
                syslog(LOG_NOTICE,"cannot install SIGHUP handler: %m");

        if (uucp_lock) {
                /* unlock not needed here, always re-lock with new pid */
                int res;
                if ((res = uu_lock(dvname)) != UU_LOCK_OK) {
                        if (res != UU_LOCK_INUSE)
                                syslog(LOG_ERR, "uu_lock: %s", uu_lockerr(res));
                        syslog(LOG_ERR, "can't lock %s", dev);
                        exit_handler(1);
                }
                locked = 1;
        }

        if ((fd = open(dev, O_RDWR | O_NONBLOCK, 0)) < 0) {
                syslog(LOG_ERR, "open(%s) %m", dev);
                exit_handler(1);
        }
        /* Turn off O_NONBLOCK for dumb redialers, if any. */
        if ((oflags = fcntl(fd, F_GETFL)) == -1) {
                syslog(LOG_ERR, "fcntl(F_GETFL) failed: %m");
                exit_handler(1);
        }
        if (fcntl(fd, F_SETFL, oflags & ~O_NONBLOCK) == -1) {
                syslog(LOG_ERR, "fcntl(F_SETFL) failed: %m");
                exit_handler(1);
        }
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > 2)
                close (fd);
        fd = STDIN_FILENO;

        /* acquire the serial line as a controlling terminal. */
        if (ioctl(fd, TIOCSCTTY, 0) < 0) {
                syslog(LOG_ERR,"ioctl(TIOCSCTTY): %m");
                exit_handler(1);
        }
        /* Make us the foreground process group associated with the
           slip line which is our controlling terminal. */
        if (tcsetpgrp(fd, getpid()) < 0)
                syslog(LOG_NOTICE,"tcsetpgrp failed: %m");
}

/* Set the tty flags and set DTR. */
/* Call as setup_line(CLOCAL) to force clocal assertion. */
static void
setup_line(int cflag)
{
        tty.c_lflag = tty.c_iflag = tty.c_oflag = 0;
        tty.c_cflag = CREAD | CS8 | flow_control | modem_control | cflag;
        cfsetispeed(&tty, speed);
        cfsetospeed(&tty, speed);
        /* set the line speed and flow control */
        if (tcsetattr(fd, TCSAFLUSH, &tty) < 0) {
                syslog(LOG_ERR, "tcsetattr(TCSAFLUSH): %m");
                exit_handler(1);
        }
        /* set data terminal ready */
        if (ioctl(fd, TIOCSDTR) < 0) {
                syslog(LOG_ERR, "ioctl(TIOCSDTR): %m");
                exit_handler(1);
        }
}

/* Put the line in slip discipline. */
static void
slip_discipline(void)
{
        struct ifreq ifr;
        int slipdisc = SLIPDISC;
        int s, tmp_unit = -1;

        /* Switch to slip line discipline. */
        if (ioctl(fd, TIOCSETD, &slipdisc) < 0) {
                syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
                exit_handler(1);
        }

        if (sl_unit >= 0 && ioctl(fd, SLIOCSUNIT, &sl_unit) < 0) {
                syslog(LOG_ERR, "ioctl(SLIOCSUNIT): %m");
                exit_handler(1);
        }

        /* find out what unit number we were assigned */
        if (ioctl(fd, SLIOCGUNIT, (caddr_t)&tmp_unit) < 0) {
                syslog(LOG_ERR, "ioctl(SLIOCGUNIT): %m");
                exit_handler(1);
        }

        if (tmp_unit < 0) {
                syslog(LOG_ERR, "bad unit (%d) from ioctl(SLIOCGUNIT)",tmp_unit);
                exit_handler(1);
        }

        if (keepal > 0) {
                signal(SIGURG, sigurg_handler);
                if (ioctl(fd, SLIOCSKEEPAL, &keepal) < 0) {
                        syslog(LOG_ERR, "ioctl(SLIOCSKEEPAL): %m");
                        exit_handler(1);
                }
        }
        if (outfill > 0 && ioctl(fd, SLIOCSOUTFILL, &outfill) < 0) {
                syslog(LOG_ERR, "ioctl(SLIOCSOUTFILL): %m");
                exit_handler(1);
        }

        /* open a socket as the handle to the interface */
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s < 0) {
                syslog(LOG_ERR, "socket: %m");
                exit_handler(1);
        }
        sprintf(ifr.ifr_name, "sl%d", tmp_unit);

        /* get the flags for the interface */
        if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr) < 0) {
                syslog(LOG_ERR, "ioctl (SIOCGIFFLAGS): %m");
                exit_handler(1);
        }

        /* Assert any compression or no-icmp flags. */
#define SLMASK (~(IFF_LINK0 | IFF_LINK1 | IFF_LINK2))
        ifr.ifr_flags &= SLMASK;
        ifr.ifr_flags |= slflags;
        if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr) < 0) {
                syslog(LOG_ERR, "ioctl (SIOCSIFFLAGS): %m");
                exit_handler(1);
        }
        close(s);
}

/* configure the interface, e.g. by passing the unit number to a script. */
static void
configure_network(void)
{
        int new_unit;

        /* find out what unit number we were assigned */
        if (ioctl(fd, SLIOCGUNIT, (caddr_t)&new_unit) < 0) {
                syslog(LOG_ERR, "ioctl(SLIOCGUNIT): %m");
                exit_handler(1);
        }
        /* iff the unit number changes either invoke config_cmd or punt. */
        if (config_cmd) {
                char *s;
                s = (char*) malloc(strlen(config_cmd) + 32);
                if (s == NULL) {
                        syslog(LOG_ERR, "malloc failed");
                        exit(1);
                }
                sprintf (s, "%s %d %d", config_cmd, unit, new_unit);
                syslog(LOG_NOTICE, "configuring %s (sl%d):", dev, unit);
                syslog(LOG_NOTICE, "  '%s'", s);
                system(s);
                free (s);
                unit = new_unit;
        } else {
                /* don't compare unit numbers if this is the first time to attach. */
                if (unit < 0)
                        unit = new_unit;
                if (new_unit != unit) {
                        syslog(LOG_ERR,
        "slip unit changed from sl%d to sl%d, but no -u CMD was specified!",
                            unit, new_unit);
                        exit_handler(1);
                }
                syslog(LOG_NOTICE,"sl%d connected to %s at %d baud",unit,dev,speed);
        }
}

/* sighup_handler() is invoked when carrier drops, eg. before redial. */
static void
sighup_handler(int signo __unused)
{
        if(exiting) return;

        if (redial_cmd == NULL) {
                syslog(LOG_NOTICE,"SIGHUP on %s (sl%d); exiting", dev, unit);
                exit_handler(1);
        }
again:
        /* invoke a shell for redial_cmd or punt. */
        if (*redial_cmd) { /* Non-empty redial command */
                syslog(LOG_NOTICE,"SIGHUP on %s (sl%d); running '%s'",
                       dev, unit, redial_cmd);
                acquire_line(); /* reopen dead line */
                setup_line(CLOCAL);
                if (locked) {
                        if (uucp_lock)
                                uu_unlock(dvname);      /* for redial */
                        locked = 0;
                }
                if (system(redial_cmd))
                        goto again;
                if (uucp_lock) {
                        int res;
                        if ((res = uu_lock(dvname)) != UU_LOCK_OK) {
                                if (res != UU_LOCK_INUSE)
                                        syslog(LOG_ERR, "uu_lock: %s", uu_lockerr(res));
                                syslog(LOG_ERR, "can't relock %s after %s, aborting",
                                        dev, redial_cmd);
                                exit_handler(1);
                        }
                        locked = 1;
                }
                /* Now check again for carrier (dial command is done): */
                if (!(modem_control & CLOCAL)) {
                        tty.c_cflag &= ~CLOCAL;
                        if (tcsetattr(fd, TCSAFLUSH, &tty) < 0) {
                                syslog(LOG_ERR, "tcsetattr(TCSAFLUSH): %m");
                                exit_handler(1);
                        }
                        ioctl(fd, TIOCMGET, &comstate);
                        if (!(comstate & TIOCM_CD)) { /* check for carrier */
                                /* force a redial if no carrier */
                                goto again;
                        }
                } else
                        setup_line(0);
        } else {        /* Empty redial command */
                syslog(LOG_NOTICE,"SIGHUP on %s (sl%d); reestablish connection",
                        dev, unit);
                acquire_line(); /* reopen dead line */
                setup_line(0);  /* restore ospeed from hangup (B0) */
                /* If modem control, just wait for carrier before attaching.
                   If no modem control, just fall through immediately. */
                if (!(modem_control & CLOCAL)) {
                        int carrier = 0;

                        syslog(LOG_NOTICE, "waiting for carrier on %s (sl%d)",
                               dev, unit);
                        /* Now wait for carrier before attaching line. */
                        /* We must poll since CLOCAL prevents signal. */
                        while (! carrier) {
                                sleep(2);
                                ioctl(fd, TIOCMGET, &comstate);
                                if (comstate & TIOCM_CD)
                                        carrier = 1;
                        }
                        syslog(LOG_NOTICE, "carrier now present on %s (sl%d)",
                               dev, unit);
                }
        }
        slip_discipline();
        configure_network();
}

/* Signal handler for SIGINT.  We just log and exit. */
static void
sigint_handler(int signo __unused)
{
        if(exiting) return;
        syslog(LOG_NOTICE,"SIGINT on %s (sl%d); exiting",dev,unit);
        exit_handler(0);
}

/* Signal handler for SIGURG. */
static void
sigurg_handler(int signo __unused)
{
        int ttydisc = TTYDISC;

        signal(SIGURG, SIG_IGN);
        if(exiting) return;
        syslog(LOG_NOTICE,"SIGURG on %s (sl%d); hangup",dev,unit);
        if (ioctl(fd, TIOCSETD, &ttydisc) < 0) {
                syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
                exit_handler(1);
        }
        cfsetospeed(&tty, B0);
        if (tcsetattr(fd, TCSANOW, &tty) < 0) {
                syslog(LOG_ERR, "tcsetattr(TCSANOW): %m");
                exit_handler(1);
        }
        /* Need to go to sighup handler in any case */
        if (modem_control & CLOCAL)
                kill (getpid(), SIGHUP);

}

/* Signal handler for SIGTERM.  We just log and exit. */
static void
sigterm_handler(int signo __unused)
{
        if(exiting) return;
        syslog(LOG_NOTICE,"SIGTERM on %s (sl%d); exiting",dev,unit);
        exit_handler(0);
}

/* Run config_cmd if specified before exiting. */
static void
exit_handler(int ret)
{
        if(exiting) return;
        exiting = 1;
        /*
         * First close the slip line in case exit_cmd wants it (like to hang
         * up a modem or something).
         */
        if (fd != -1)
                close(fd);
        if (uucp_lock && locked)
                uu_unlock(dvname);

        /* Remove the PID file */
        unlink(pidfilename);

        if (config_cmd) {
                char *s;
                s = (char*) malloc(strlen(config_cmd) + 32);
                if (s == NULL) {
                        syslog(LOG_ERR, "malloc failed");
                        exit(1);
                }
                sprintf (s, "%s %d -1", config_cmd, unit);
                syslog(LOG_NOTICE, "deconfiguring %s (sl%d):", dev, unit);
                syslog(LOG_NOTICE, "  '%s'", s);
                system(s);
                free (s);
        }
        /* invoke a shell for exit_cmd. */
        if (exit_cmd) {
                syslog(LOG_NOTICE,"exiting after running %s", exit_cmd);
                system(exit_cmd);
        }
        exit(ret);
}

/* local variables: */
/* c-indent-level: 8 */
/* c-argdecl-indent: 0 */
/* c-label-offset: -8 */
/* c-continued-statement-offset: 8 */
/* c-brace-offset: 0 */
/* comment-column: 32 */
/* end: */