FreeBSD-5.3/sbin/slattach/slattach.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 * 4. 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.
 */

#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1988 Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "from: @(#)slattach.c	4.6 (Berkeley) 6/1/90";
#endif
static const char rcsid[] =
  "$FreeBSD: src/sbin/slattach/slattach.c,v 1.40 2004/04/09 19:58:39 markm Exp $";
#endif /* not lint */

#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	9600

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

int	fd = -1;
char	*dev = (char *)0;	/* 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 daemon 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 = 0;	/* command to exec upon shutdown. */
char	*config_cmd = 0;	/* command to exec if slip unit changes. */
char	*exit_cmd = 0;		/* command to exec before exiting. */

static void
usage()
{
	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 = 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 = strdup (optarg);
			break;
		case 's':
			speed = atoi(optarg);
			break;
		case 'u':
			config_cmd = 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 == (char *)0) {
		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();
	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. */
void acquire_line()
{
	int ttydisc = TTYDISC;
	int oflags;
	FILE *pidfile;

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

	(void)close(STDIN_FILENO); /* close FDs before forking. */
	(void)close(STDOUT_FILENO);
	(void)close(STDERR_FILENO);
	if (fd > 2)
		(void)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((pidfile = fopen(pidfilename, "w"))) {
		fprintf(pidfile, "%ld\n", (long)getpid());
		fclose(pidfile);
	}

	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);
	}
	(void)dup2(fd, STDIN_FILENO);
	(void)dup2(fd, STDOUT_FILENO);
	(void)dup2(fd, STDERR_FILENO);
	if (fd > 2)
		(void)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. */
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. */
void slip_discipline()
{
	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. */
void configure_network()
{
	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. */
void sighup_handler()
{
	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. */
void sigint_handler()
{
	if(exiting) return;
	syslog(LOG_NOTICE,"SIGINT on %s (sl%d); exiting",dev,unit);
	exit_handler(0);
}
/* Signal handler for SIGURG. */
void sigurg_handler()
{
	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. */
void sigterm_handler()
{
	if(exiting) return;
	syslog(LOG_NOTICE,"SIGTERM on %s (sl%d); exiting",dev,unit);
	exit_handler(0);
}
/* Run config_cmd if specified before exiting. */
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 */
	(void)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: */