/*-
 * Copyright (c) 1990 The Regents of the University of California
 * All rights reserved.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 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
char copyright[] =
"@(#) Copyright (c) 1990 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char rcsid[] = "$Id: sliplogin.c,v 1.6.1 1995/08/01 root Exp root $";
#endif /* not lint */

/*
 * sliplogin.c
 * [MUST BE RUN SUID ROOT]
 *
 * This program initializes its own tty port to be an async TCP/IP interface.
 * It sets the line discipline to slip, invokes a shell script to initialize
 * the network interface, then pauses forever waiting for hangup.
 *
 * It is a remote descendant of several similar programs with incestuous ties:
 * - Kirk Smith's slipconf, modified by Richard Johnsson @ DEC WRL.
 * - slattach, probably by Rick Adams but touched by countless hordes.
 * - the original sliplogin for 4.2bsd, Doug Kingston the mover behind it.
 *
 * There are two forms of usage:
 *
 * "sliplogin"
 * Invoked simply as "sliplogin", the program looks up the username
 * in the file /etc/slip.hosts.
 * If an entry is found, the line on fd0 is configured for SLIP operation
 * as specified in the file.
 *
 * "sliplogin slipuser < /dev/ttyb"
 * Invoked by root with a username, the name is looked up in the
 * /etc/slip.hosts file and if found fd0 is configured as in case 1.
 */

#define _POSIX_SOURCE 1
#include <sys/types.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <sys/syslog.h>
#include <netdb.h>
#include <pwd.h>
#include <sys/stat.h>

#include <sys/termios.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include "pathnames.h"

#ifndef N_SLIP
#define N_SLIP SLIPDISC /* slip line discipline */
#endif


int	slip_mode;
int	linespeed;
int	uid;
char loginargs[BUFSIZ];
char loginfile[MAXPATHLEN];
char loginname[BUFSIZ];
char slopt[5][16];
char laddr[16];
char raddr[16];
char unit[32];
volatile int HUP=-1;

struct slip_modes {
	char *sm_name;
	int	sm_value;
}	 modes[] = {
	"normal",		0,	/* SL_MODE_SLIP */
	"compressed",	1,	/* SL_MODE_CSLIP */
	"6bit",			2,	/* SL_MODE_SLIP6, printable slip */
	"ax25",			4,	/* SL_MODE_AX25 */
	"auto",			8	/* SL_MODE_ADAPTIVE */
};


/* POSIX sux ! What comes next ? Forcement to create your own
   error table ? Rewrite your programs when higher baudrates
   get added ? */
struct speedtab {
   speed_t symbol;
   int real; 
} speeds[] = {
#ifdef B50
    { B50, 50 },
#endif
#ifdef B75
    { B75, 75 },
#endif
#ifdef B110
    { B110, 110 },
#endif
#ifdef B134
    { B134, 134 },
#endif
#ifdef B150
    { B150, 150 },
#endif
#ifdef B200
    { B200, 200 },
#endif
#ifdef B300
    { B300, 300 },
#endif
#ifdef B600
    { B600, 600 },
#endif
#ifdef B1200
    { B1200, 1200 },
#endif
#ifdef B1800
    { B1800, 1800 },
#endif
#ifdef B2000
    { B2000, 2000 },
#endif
#ifdef B2400
    { B2400, 2400 },
#endif
#ifdef B3600
    { B3600, 3600 },
#endif
#ifdef B4800
    { B4800, 4800 },
#endif
#ifdef B7200
    { B7200, 7200 },
#endif
#ifdef B9600
    { B9600, 9600 },
#endif
#ifdef B19200
    { B19200, 19200 },
#endif
#ifdef B38400
    { B38400, 38400 },
#endif
#ifdef EXTA
    { EXTA, 19200 },
#endif
#ifdef EXTB
    { EXTB , 38400 },
#endif
#ifdef B57600
    { B57600, 57600 },
#endif
#ifdef B115200
    { B115200, 115200 },
#endif
    { 0, 0 }
};

int translate_speed(speed_t speed)
{
struct speedtab *ptr;

for (ptr=speeds; ptr->real != 0; ptr++)
 if (speed == ptr->symbol) return(ptr->real);
return (0);
}

void findid(char *name)
{
	FILE *fp;
	static char mask[16];
	char user[16];
	int i, j, n;

	strcpy(loginname, name);
	if ((fp = fopen(_PATH_ACCESS, "r")) == NULL)
	{
	 fprintf(stderr,"sliplogin: %s: %s\n", _PATH_ACCESS, strerror(errno));
	 syslog(LOG_ERR, "%s: %m\n", _PATH_ACCESS);
	 exit(1);
	}
	while (fgets(loginargs, sizeof(loginargs) - 1, fp))
	{
	 if (ferror(fp)) break;
	 if (loginargs[0]=='#') continue;
	 n = sscanf(loginargs, "%15s%15s%15s%15s%15s%15s%15s%15s%15s",
						user, laddr, raddr, mask, slopt[0], slopt[1], 
						slopt[2], slopt[3], slopt[4]);
	 if ( (strcmp(user, name) != 0) && strcmp(user, "*") != 0) continue;
	 fclose(fp);
	 /* Found user - now check for dynamic IP assigning */
	 if (strcmp(raddr, "DYNAMIC")==0)
	 {
	  char *ttyn,sliptty[20],dynamic[80];
	  ttyn=ttyname(0);
      if ((fp = fopen(_PATH_SLIPTTY, "r")) == NULL)
	  {
		fprintf(stderr,"sliplogin: %s: %s\n", _PATH_SLIPTTY, strerror(errno));
		syslog(LOG_ERR, "%s: %m\n", _PATH_SLIPTTY);
		exit(1);
	  }
	  while (fgets(dynamic, 79, fp))
	  {
		if (ferror(fp)) break;
		if (dynamic[0]=='#') continue;
		sscanf(dynamic, "%15s%15s", sliptty,raddr);
		if (strcmp(ttyn, sliptty) != 0) continue;
		fclose(fp);
		fp=NULL;
		break;
	  }
	  if (fp!=NULL)
	  {
		fclose(fp);
		fprintf(stderr,"sliplogin: %s not found in %s\n", ttyn,_PATH_SLIPTTY);
		syslog(LOG_ERR,"%s not found in %s\n",ttyn,_PATH_SLIPTTY);
		syslog(LOG_ERR,"Couldn't start slip session for %s\n",name);
		exit(-1);
	  }
	  sprintf(loginargs, "%s %s %s %s %s %s %s %s %s", user, laddr, raddr,
				mask, slopt[0], slopt[1], slopt[2], slopt[3], slopt[4]);
	 } /* end if DYNAMIC */

	 /* Prepare slip_mode */
	 slip_mode = 0;
	 for (i = 0; i < n - 4; i++)
	 {
	  for (j = 0; j < sizeof(modes)/sizeof(struct slip_modes); j++)
	  {
	   if (strcmp(modes[j].sm_name, slopt[i]) == 0)
	   {
		slip_mode |= modes[j].sm_value;
		break;
	   }
	  } /* end for */
	 } /* end for */

	 /*
	  * see if there's a login file we can use.  First check for
	  * one specific to this host.  If none found, try for
	  * a generic one.
	  */
	 sprintf(loginfile, "%s.%s", _PATH_LOGIN, name);
	 if (access(loginfile, R_OK|X_OK) != 0)
	 {
	  strcpy(loginfile, _PATH_LOGIN);
	  if (access(loginfile, R_OK|X_OK))
	  {
		fputs("access denied - no login file\n", stderr);
		syslog(LOG_ERR,"access denied for %s - no %s\n",name, loginfile);
		exit(5);
	  }
	 }
	 return;
	} /* end while */
	fclose(fp);
	(void)fprintf(stderr, "SLIP access denied for %s\n", name);
	syslog(LOG_ERR, "SLIP access denied for %s\n", name);
	exit(4);
}

char *
sigstr(s)
	int s;
{
	static char buf[32];

	switch (s) {
	case SIGHUP:	return("HUP");
	case SIGINT:	return("INT");
	case SIGQUIT:	return("QUIT");
	case SIGILL:	return("ILL");
	case SIGTRAP:	return("TRAP");
	case SIGIOT:	return("IOT");
	case SIGFPE:	return("FPE");
	case SIGKILL:	return("KILL");
	case SIGBUS:	return("BUS");
	case SIGSEGV:	return("SEGV");
	case SIGPIPE:	return("PIPE");
	case SIGALRM:	return("ALRM");
	case SIGTERM:	return("TERM");
	case SIGURG:	return("URG");
	case SIGSTOP:	return("STOP");
	case SIGTSTP:	return("TSTP");
	case SIGCONT:	return("CONT");
	case SIGCHLD:	return("CHLD");
	case SIGTTIN:	return("TTIN");
	case SIGTTOU:	return("TTOU");
	case SIGXCPU:	return("XCPU");
	case SIGXFSZ:	return("XFSZ");
	case SIGVTALRM:	return("VTALRM");
	case SIGPROF:	return("PROF");
	case SIGWINCH:	return("WINCH");
#ifdef SIGLOST
	case SIGLOST:	return("LOST");
#endif
	case SIGUSR1:	return("USR1");
	case SIGUSR2:	return("USR2");
	}
	(void)sprintf(buf, "sig %d", s);
	return(buf);
}

void close_timeout()
{
 syslog(LOG_INFO, "timeout %s's slip unit %s (%s)\n", loginname, unit,
           sigstr(HUP));
#ifdef linux
 vhangup();
#endif
 exit(-1);
}

void hup_handler(int s)
{
 HUP=s; /* this avoids race conditions */
}

void exit_sliplogin()
{
	char logoutfile[MAXPATHLEN];
    struct sigaction sa;

	(void)sprintf(logoutfile, "%s.%s", _PATH_LOGOUT, loginname);
	if (access(logoutfile, R_OK|X_OK) != 0)
		(void)strcpy(logoutfile, _PATH_LOGOUT);
	if (access(logoutfile, R_OK|X_OK) == 0) {
		char logincmd[2*MAXPATHLEN+32];

		(void) sprintf(logincmd, "%s %s %d %s", logoutfile, unit, linespeed,
			       loginargs);
		(void) system(logincmd);
	}
	fcntl(0,F_SETFL,O_NONBLOCK); /* because close sometimes never returns */
    sa.sa_handler = close_timeout;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    alarm(15);
    sigaction(SIGALRM,&sa,NULL);
	(void) close(0);
    alarm(0);
    sa.sa_handler = SIG_DFL;
    sigaction(SIGALRM,&sa,NULL);
	syslog(LOG_INFO, "closed %s's slip unit %s (%s)\n", loginname, unit,
	       sigstr(HUP));
#ifdef linux
    vhangup();
#endif
	exit(1);
}

int main(argc, argv)
	int argc;
	char *argv[];
{
	int fd, s, ldisc, odisc, pid, unitnumber;
	speed_t speed;
	struct sigaction sa;
	char *name;
	struct termios tios, otios;
	struct passwd *pw;
	char logincmd[2*BUFSIZ+32];
	extern uid_t getuid();

	if ((name = strrchr(argv[0], '/')) == NULL)
		name = argv[0];
	s = (int) getdtablesize();
	for (fd = 3 ; fd < s ; fd++)
		(void) close(fd);
	openlog(name, LOG_PID, LOG_DAEMON);
	uid = getuid();

	/* security */
	unsetenv("IFS");
	setenv("PATH","/bin:/usr/bin:/sbin:/etc:/usr/sbin",1);

	if (argc > 1) 
	{
	 name=argv[1];
	 if (uid!=0) 
	 {
	  fprintf(stderr,"Only root may use arguments\n");
	  exit(-1);
	 }
	}
	else
	{
		pw = getpwuid(uid);
		if (pw == NULL)
		{
			(void) fprintf(stderr, "access denied - no username\n");
			syslog(LOG_ERR, "access denied - getpwuid returned 0\n");
			exit(1);
		}
		name=pw->pw_name;
	}

	/*
	 * Go to background, become process group leader (if not already)
	 * and ensure that the slip line is our controlling terminal.
	 */
/*
if (fork() > 0) exit(0);
maybe don't fork and don't go to background
*/
	/*
	 * setsid() to become process group leader
	 */
	if (setsid() < 0) syslog(LOG_ERR,"can't setsid: %m");
#ifdef TIOCSCTTY
	if (ioctl(0, TIOCSCTTY, (caddr_t)0) < 0)
		perror("sliplogin: can't set my controlling tty -");
#endif
	findid(name);
	fchmod(0, 0600);
	fprintf(stderr, "Starting %s slip for %s\n",slopt[0],loginname);
	fprintf(stderr, "Your IP address is %s , the server is %s\n", raddr,laddr);
	fflush(stderr);
	sleep(2); /* Jon Lewis needs this to get the full printf output */
	/* set up the line parameters */
	if (tcgetattr(0, &tios) < 0) {
		syslog(LOG_ERR, "tcgetattr: %m");
		exit(1);
	}
	otios = tios;
	cfmakeraw(&tios);
	tios.c_iflag &= ~IMAXBEL;
	if (tcsetattr(0, TCSAFLUSH, &tios) < 0)
	{
		syslog(LOG_ERR, "tcsetattr: %m");
		exit(1);
	}

	speed = cfgetispeed(&tios);
	linespeed=translate_speed(speed);

	/* find out what ldisc we started with */
	if (ioctl(0, TIOCGETD, (caddr_t)&odisc) < 0)
	{
		syslog(LOG_ERR, "ioctl(TIOCGETD) (1): %m");
		exit(1);
	}
	/* switch to slip line discipline */
	ldisc = N_SLIP;
	if (ioctl(0, TIOCSETD, (caddr_t)&ldisc) < 0)
	{
		syslog(LOG_ERR, "ioctl(TIOCSETD): %m");
		exit(1);
	}
	/* find out what unit number we were assigned */
#ifndef linux
	if (ioctl(0, SLIOCGUNIT, (caddr_t) &unitnumber) < 0)
	{
		syslog(LOG_ERR, "ioctl (SLIOCGUNIT): %m");
		exit(1);
	}
	sprintf(unit,"sl%d",unitnumber);
#else
	if (ioctl(0, SIOCGIFNAME, (caddr_t)unit) < 0)
	{
		syslog(LOG_ERR, "ioctl (SIOCGIFNAME): %m");
		exit(1);
	}
#endif
    sa.sa_handler = hup_handler;
	sa.sa_flags = 0;
	if (sigemptyset(&sa.sa_mask) != 0)
	{
		syslog(LOG_ERR, "I have no signal control: %m");
		exit(1);
	}

	sigaction(SIGHUP,&sa,NULL);
	sigaction(SIGTERM,&sa,NULL);

	syslog(LOG_INFO, "attaching slip unit %s for %s\n", unit, loginname);
	pid=getpid();
	(void)sprintf(logincmd, "%s %s %d %d %s", loginfile, unit, linespeed,
		      pid, loginargs);
	syslog(LOG_INFO, logincmd);
	/*
	 * aim stdout and errout at /dev/null so logincmd output won't
	 * babble into the slip tty line.
	 */
	close(1);
	if ((fd = open(_PATH_DEVNULL, O_WRONLY)) != 1) {
		if (fd < 0) {
			syslog(LOG_ERR, "open /dev/null: %m");
			exit(1);
		}
		(void) dup2(fd, 1);
		(void) close(fd);
	}
	(void) dup2(1, 2);

	/*
	 * Run login and logout scripts as root (real and effective);
	 * current route(8) is setuid root, and checks the real uid
	 * to see whether changes are allowed (or just "route get").
	 */
	(void) setuid(0);
	s=system(logincmd);
	if (s!=0)
	{
		syslog(LOG_ERR, "%s login failed: exit status %d from %s",
		       loginname, s, loginfile);
		ioctl(0, TIOCSETD, (caddr_t)&odisc);
		exit(6);
	}
	/* set slip mode */
#ifndef linux
	if (ioctl(0, SLIOCSFLAGS, (caddr_t)&slip_mode) < 0)
	{
		syslog(LOG_ERR, "ioctl(SLIOCSFLAGS): %m");
		exit(1);
	}
#else
	if (ioctl(0, SIOCSIFENCAP, (caddr_t)&slip_mode) < 0)
	{
		syslog(LOG_ERR, "ioctl (SIOCSIFENCAP): %m");
		exit(1);
	}
#endif


	/* twiddle thumbs until we get a signal */
	while (HUP==-1) pause();
	exit_sliplogin();

	/* NOTREACHED */
}
