/*
 * Copyright (c) 1980,1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#ifndef lint
static	char sccsid[] = "@(#)init.c 1.1 94/10/31 SMI"; /* from UCB 5.6 5/26/86 */
#endif not lint

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <utmp.h>
#include <setjmp.h>
#include <sys/reboot.h>
#include <errno.h>
#include <sys/file.h>
#include <ttyent.h>
#include <sys/syslog.h>
#include <sys/stat.h>
#include <pwd.h>
#include <sys/label.h>
#include <sys/audit.h>
#include <pwdadj.h>

#define	LINSIZ	sizeof(wtmp.ut_line)
#define	CMDSIZ	200	/* max string length for getty or window command*/
#define	ALL	p = itab; p ; p = p->next
#define	EVER	;;
#define SCPYN(a, b)	strncpy(a, b, sizeof(a))
#define SCMPN(a, b)	strncmp(a, b, sizeof(a))

char	shell[] = "/sbin/sh";
char	singleshell[]	= "/single/sh";
char	oldshell[]	= "/bin/sh";
char	minus[]	= "-";
char	bootc[]	= "/etc/rc.boot";
char	runc[]	= "/etc/rc";
char	utmpf[]	= "/etc/utmp";
char	wtmpf[]	= "/var/adm/wtmp";
char	ctty[]	= "/dev/console";
char	ttys[] = "/etc/ttys";
char	ttytab[] = "/etc/ttytab";
char	pwdf[]	= "/etc/passwd";
char	pwaf[]	= "/etc/security/passwd.adjunct";

struct utmp wtmp;
struct	tab
{
	char	line[LINSIZ];
	char	comn[CMDSIZ];
	char	xflag;
	int	pid;
	int	wpid;		/* window system pid for SIGHUP	*/
	char	wcmd[CMDSIZ];	/* command to start window system process */
	time_t	gettytime;
	int	gettycnt;
	time_t	windtime;
	int	windcnt;
	struct	tab *next;
} *itab;

jmp_buf	sjbuf, shutpass;
time_t	time0;

void	runrc();
void	merge();
void	reset();
void	idlehup();
void	idle();
char	*strcpy(), *strcat();
long	lseek();

struct	sigvec rvec = { reset, sigmask(SIGHUP), 0 };


#ifdef vax
main()
{
	register int r11;		/* passed thru from boot */
#else
main(argc, argv)
	char **argv;
{
#endif
	int howto, oldhowto;

	if (getpid() != 1) {
		fprintf(stderr, "init already running\n");
		exit(-1);
	}
	time0 = time(0);
#ifdef vax
	howto = r11;
#else
	if (argc > 1 && argv[1][0] == '-') {
		char *cp;

		howto = 0;
		cp = &argv[1][1];
		while (*cp) switch (*cp++) {
		case 'a':
			howto |= RB_ASKNAME;
			break;
		case 's':
			howto |= RB_SINGLE;
			break;
		case 'b':
			howto |= RB_NOBOOTRC;
			break;
		}
	} else {
		howto = RB_SINGLE;
	}
#endif
	openlog("init", LOG_CONS|LOG_ODELAY, LOG_AUTH);
	sigvec(SIGTERM, &rvec, (struct sigvec *)0);
	signal(SIGTSTP, idle);
	signal(SIGSTOP, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	(void) setjmp(sjbuf);
	if ((howto&RB_NOBOOTRC) == 0 && access(bootc, 4) == 0) {
		howto |= RB_NOBOOTRC;
		if (bootrc(howto) == 0)
			/* if rc.boot exits abnormally, stay single-user */
			howto = RB_SINGLE|RB_NOBOOTRC;
	}
	for (EVER) {
		oldhowto = howto;
		howto = RB_SINGLE|RB_NOBOOTRC;
		if (setjmp(shutpass) == 0)
			shutdown();
		if (oldhowto & RB_SINGLE)
			single();
		if (runcom(oldhowto) == 0) 
			continue;
		merge();
		multiple();
	}
}

void	shutreset();

shutdown()
{
	register i;
	register struct tab *p, *p1;

	close(creat(utmpf, 0644));
	signal(SIGHUP, SIG_IGN);
	for (p = itab; p ; ) {
		term(p);
		p1 = p->next;
		free(p);
		p = p1;
	}
	itab = (struct tab *)0;
	signal(SIGALRM, shutreset);
	(void) kill(-1, SIGTERM);	/* one chance to catch it */
	sleep(5);
	alarm(30);
	for (i = 0; i < 5; i++)
		kill(-1, SIGKILL);
	while (wait((int *)0) != -1)
		;
	alarm(0);
	shutend();
}

char shutfailm[] = "WARNING: Something is hung (won't die); ps axl advised\n";

void
shutreset()
{
	if (fork() == 0) {
		int ct = open(ctty, 1);
		write(ct, shutfailm, sizeof (shutfailm));
		sleep(5);
		exit(1);
	}
	sleep(5);
	shutend();
	longjmp(shutpass, 1);
}

shutend()
{
	register i, f;

	acct(0);
	signal(SIGALRM, SIG_DFL);
	for (i = 0; i < 10; i++)
		close(i);
	f = open(wtmpf, O_WRONLY|O_APPEND);
	if (f >= 0) {
		SCPYN(wtmp.ut_line, "~");
		SCPYN(wtmp.ut_name, "shutdown");
		SCPYN(wtmp.ut_host, "");
		time(&wtmp.ut_time);
		write(f, (char *)&wtmp, sizeof(wtmp));
		close(f);
	}
	return (1);
}

bootrc(howto)
	int howto;
{
	register int pid;
	int status;

	pid = fork();
	if (pid == 0) {
		getcons(1);
		if (howto & RB_SINGLE)
			runrc(bootc, "singleuser");
		else
			runrc(bootc, (char *)0);
		exit(1);
	}
	while (wait(&status) != pid)
		;
	if (status)
		return (0);
	else
		return (1);
}

single()
{
	register pid;
	register xpid;
	extern	errno;

	do {
		pid = fork();
		if (pid == 0) {
			signal(SIGTERM, SIG_DFL);
			signal(SIGHUP, SIG_DFL);
			signal(SIGALRM, SIG_DFL);
			signal(SIGTSTP, SIG_IGN);
			signal(SIGTTIN, SIG_IGN);
			signal(SIGTTOU, SIG_IGN);
			getcons(0);
			if (checkpass() == 0) {
				execl(shell, minus, (char *)0);
				execl(singleshell, minus, (char *)0);
				execl(oldshell, minus, (char *)0);
				perror(oldshell);
			}
			exit(0);
		}
		while ((xpid = wait((int *)0)) != pid)
			if (xpid == -1 && errno == ECHILD)
				break;
	} while (xpid == -1);
}

/*
 * Verify user knows root password
 */
checkpass()
{
	char *pass;
	char *epass;
	extern char *rootpass();
	extern char *getpass();
	int not_ok = 1;

	if (!isc2secure() && cons_secure())
		return (0);

	/* Get the root password */
	epass = rootpass();
	if (epass == NULL)
		/* Go ahead if no password */
		return (0);
	while (not_ok) {
		pass = getpass("Password:");
		if (*pass == '\0')
			return (1);
		not_ok = strcmp(_crypt(pass, epass), epass);
	}
	return (0);
}

runcom(oldhowto)
	int oldhowto;
{
	register pid, f;
	int status;

	pid = fork();
	if (pid == 0) {
		getcons(1);
		if (oldhowto & RB_SINGLE)
			runrc(runc, (char *)0);
		else
			runrc(runc, "autoboot");
		exit(1);
	}
	while (wait(&status) != pid)
		;
	if (status)
		return (0);
	f = open(wtmpf, O_WRONLY|O_APPEND);
	if (f >= 0) {
		SCPYN(wtmp.ut_line, "~");
		SCPYN(wtmp.ut_name, "reboot");
		SCPYN(wtmp.ut_host, "");
		if (time0) {
			wtmp.ut_time = time0;
			time0 = 0;
		} else
			time(&wtmp.ut_time);
		write(f, (char *)&wtmp, sizeof(wtmp));
		close(f);
	}
	return (1);
}

void
runrc(rcname, arg)
	char *rcname;
	char *arg;
{
	kbd_disable();
	execl(shell, shell, rcname, arg, (char *)0);
	execl(singleshell, singleshell, rcname, arg, (char *)0);
	execl(oldshell, oldshell, rcname, arg, (char *)0);
}

struct	sigvec	mvec = { merge, sigmask(SIGTERM), 0 };
/*
 * Multi-user.  Listen for users leaving, SIGHUP's
 * which indicate ttytab has changed, and SIGTERM's which
 * are used to shutdown the system.
 */
multiple()
{
	register struct tab *p;
	register pid;
	int omask;

	sigvec(SIGHUP, &mvec, (struct sigvec *)0);
	for (EVER) {
		pid = wait((int *)0);
		if (pid == -1)
			return;
		omask = sigblock(SIGHUP);
		for (ALL) {
			/* must restart window system BEFORE emulator */
			if (p->wpid == pid || p->wpid == -1)
				wstart(p);
			if (p->pid == pid || p->pid == -1) {
				/* disown the window system */
				if (p->wpid)
					kill(p->wpid, SIGHUP);
				rmut(p);
				dfork(p);
			}
		}
		sigsetmask(omask);
	}
}

/*
 * Merge current contents of ttytab file
 * into in-core table of configured tty lines.
 * Entered as signal handler for SIGHUP.
 */
#define	FOUND	1
#define	CHANGE	2
#define WCHANGE 4

void
merge()
{
	register struct tab *p;
	register struct ttyent *t;
	register struct tab *p1;
	int ttysfd;
	register FILE *ttysf = NULL;
	struct stat ttys_stat, ttytab_stat;

	for (ALL)
		p->xflag = 0;
	setttyent();
	if (stat(ttytab, &ttytab_stat) < 0)
		syslog(LOG_ERR, "%s: %m", ttytab);
	else {
		/*
		 * If "/etc/ttys" doesn't exist, or if it is not the same
		 * file as "/etc/ttytab", create it if necessary and put
		 * an old-style "ttys" file into it.
		 */
		if (stat(ttys, &ttys_stat) < 0
		    || ttys_stat.st_dev != ttytab_stat.st_dev
		    || ttys_stat.st_ino != ttytab_stat.st_ino) {
			if ((ttysfd = open(ttys, O_WRONLY|O_CREAT|O_TRUNC, 0444)) < 0)
				syslog(LOG_WARNING, "Can't create %s: %m",
				    ttys);
			else
				ttysf = fdopen(ttysfd, "w");
		}
	}
	while (t = getttyent()) {
		if (ttysf != NULL)
			(void) fprintf(ttysf, "%c2%s\n",
			    (t->ty_status & TTY_ON) ? '1' : '0', t->ty_name);
		if ((t->ty_status & TTY_ON) == 0)
			continue;
		for (ALL) {
			if (SCMPN(p->line, t->ty_name))
				continue;
			p->xflag |= FOUND;
			if (SCMPN(p->comn, t->ty_getty)) {
				p->xflag |= CHANGE;
				SCPYN(p->comn, t->ty_getty);
			}
			if (SCMPN(p->wcmd, t->ty_window ? t->ty_window : "")) {
				p->xflag |= WCHANGE|CHANGE;
				SCPYN(p->wcmd, t->ty_window);
			}
			goto contin1;
		}

		/*
		 * Make space for a new one
		 */
		p1 = (struct tab *)calloc(1, sizeof(*p1));
		if (!p1) {
			syslog(LOG_ERR, "no space for '%s' !?!", t->ty_name);
			goto contin1;
		}
		/*
		 * Put new terminal at the end of the linked list.
		 */
		if (itab) {
			for (p = itab; p->next ; p = p->next)
				;
			p->next = p1;
		} else
			itab = p1;

		p = p1;
		SCPYN(p->line, t->ty_name);
		p->xflag |= FOUND|CHANGE;
		SCPYN(p->comn, t->ty_getty);
		if (t->ty_window && strcmp(t->ty_window, "") != 0) {
			p->xflag |= WCHANGE;
			SCPYN(p->wcmd, t->ty_window);
		}
	contin1:
		;
	}
	endttyent();
	if (ttysf != NULL)
		(void) fclose(ttysf);
	p1 = (struct tab *)0;
	for (ALL) {
		if ((p->xflag&FOUND) == 0) {
			term(p);
			wterm(p);
			if (p1)
				p1->next = p->next;
			else
				itab = p->next;
			free(p);
			p = p1 ? p1 : itab;
		} else {
			/* window system should be started first */
			if (p->xflag&WCHANGE) {
				wterm(p);
				wstart(p);
			}
			if (p->xflag&CHANGE) {
				term(p);
				dfork(p);
			}
		}
		p1 = p;
	}
}

term(p)
	register struct tab *p;
{

	if (p->pid != 0) {
		rmut(p);
		kill(p->pid, SIGKILL);
	}
	p->pid = 0;
	/* send SIGHUP to get rid of connections */
	if (p->wpid > 0)
		kill(p->wpid, SIGHUP);
}

dfork(p)
	struct tab *p;
{
	register pid;
	time_t t;
	int dowait = 0;

	time(&t);
	p->gettycnt++;
	if ((t - p->gettytime) >= 60) {
		p->gettytime = t;
		p->gettycnt = 1;
	} else if (p->gettycnt >= 5) {
		dowait = 1;
		p->gettytime = t;
		p->gettycnt = 1;
	}
	pid = fork();
	if (pid == 0) {
		signal(SIGTERM, SIG_DFL);
		signal(SIGHUP, SIG_IGN);
		sigsetmask(0);	/* since can be called from masked code */
		if (dowait) {
			syslog(LOG_ERR, "'%s %s' failing, sleeping", p->comn, p->line);
			closelog();
			sleep(30);
		}
		execit(p->comn, p->line);
		exit(0);
	}
	p->pid = pid;
}

/*
 * Remove utmp entry.
 */
rmut(p)
	register struct tab *p;
{
	register f;
	int found = 0;
	static unsigned utmpsize;
	static struct utmp *utmp;
	register struct utmp *u;
	int nutmp;
	struct stat statbf;

	f = open(utmpf, O_RDWR);
	if (f >= 0) {
		fstat(f, &statbf);
		if (utmpsize < statbf.st_size) {
			utmpsize = statbf.st_size + 10 * sizeof(struct utmp);
			if (utmp)
				utmp = (struct utmp *)realloc(utmp, utmpsize);
			else
				utmp = (struct utmp *)malloc(utmpsize);
			if (!utmp)
				syslog(LOG_ERR, "utmp malloc failed");
		}
		if (statbf.st_size && utmp) {
			nutmp = read(f, utmp, statbf.st_size);
			nutmp /= sizeof(struct utmp);
			for (u = utmp ; u < &utmp[nutmp] ; u++) {
				if (SCMPN(u->ut_line, p->line) ||
				    u->ut_name[0]==0)
					continue;
				lseek(f, ((long)u)-((long)utmp), L_SET);
				SCPYN(u->ut_name, "");
				SCPYN(u->ut_host, "");
				time(&u->ut_time);
				write(f, (char *)u, sizeof(*u));
				found++;
			}
		}
		close(f);
	}
	if (found) {
		f = open(wtmpf, O_WRONLY|O_APPEND);
		if (f >= 0) {
			SCPYN(wtmp.ut_line, p->line);
			SCPYN(wtmp.ut_name, "");
			SCPYN(wtmp.ut_host, "");
			time(&wtmp.ut_time);
			write(f, (char *)&wtmp, sizeof(wtmp));
			close(f);
		}
		/*
		 * After a proper login force reset
		 * of error detection code in dfork.
		 */
		p->gettytime = 0;
		p->windtime = 0;
	}
}

void
reset()
{

	longjmp(sjbuf, 1);
}

jmp_buf	idlebuf;

void
idlehup()
{

	longjmp(idlebuf, 1);
}

void
idle()
{
	register struct tab *p;
	register pid;

	signal(SIGHUP, idlehup);
	for (EVER) {
		if (setjmp(idlebuf))
			return;
		pid = wait((int *) 0);
		if (pid == -1) {
			sigpause(0);
			continue;
		}
		for (ALL) {
			/* if window system dies, mark it for restart */
			if (p->wpid == pid)
				p->wpid = -1;
			if (p->pid == pid) {
				rmut(p);
				p->pid = -1;
			}
		}
	}
}

wterm(p)
	register struct tab *p;
{
	if (p->wpid != 0) {
		kill(p->wpid, SIGKILL);
	}
	p->wpid = 0;
}

wstart(p)
	register struct tab *p;
{
	register pid;
	time_t t;
	int dowait = 0;

	time(&t);
	p->windcnt++;
	if ((t - p->windtime) >= 60) {
		p->windtime = t;
		p->windcnt = 1;
	} else if (p->windcnt >= 5) {
		dowait = 1;
		p->windtime = t;
		p->windcnt = 1;
	}

	pid = fork();

	if (pid == 0) {
		signal(SIGTERM, SIG_DFL);
		signal(SIGHUP,  SIG_IGN);
		sigsetmask(0);	/* since can be called from masked code */
		if (dowait) {
			syslog(LOG_ERR, "'%s %s' failing, sleeping", p->wcmd, p->line);
			closelog();
			sleep(30);
		}
		execit(p->wcmd, p->line);
		exit(0);
	}
	p->wpid = pid;
}

#define NARGS	20	/* must be at least 4 */
#define ARGLEN	512	/* total size for all the argument strings */

execit(s, arg)
	char *s;
	char *arg;	/* last argument on line */
{
	char *argv[NARGS], args[ARGLEN], *envp[1];
	register char *sp = s;
	register char *ap = args;
	register char c;
	register int i;

	/*
	 * First we have to set up the argument vector.
	 * "prog arg1 arg2" maps to exec("prog", "-", "arg1", "arg2"). 
	 */
	for (i = 1; i < NARGS - 2; i++) {
		argv[i] = ap;
		for (EVER) {
			if ((c = *sp++) == '\0' || ap >= &args[ARGLEN-1]) {
				*ap = '\0';
				goto done;
			}
			if (c == ' ') {
				*ap++ = '\0';
				while (*sp == ' ')
					sp++;
				if (*sp == '\0')
					goto done;
				break;
			}
			*ap++ = c;
		}
	}
done:
	setpgrp(0,0);
	argv[0] = argv[1];
	argv[1] = "-";
	argv[i+1] = arg;
	argv[i+2] = 0;
	envp[0] = 0;
	execve(argv[0], &argv[1], envp);
	/* report failure of exec */
	syslog(LOG_ERR, "%s: %m", argv[0]);
	closelog();
	sleep(10);	/* prevent failures from eating machine */
}

/*
 * Disable keyboard interrupts
 */
kbd_disable()
{
	if (isc2secure() || !cons_secure()) {
		signal(SIGINT, SIG_IGN);
		signal(SIGQUIT, SIG_IGN);
	}
}

int	securestate	= -1;

/*
 * Is this a C2 secure system ?
 */
isc2secure()
{

	if (securestate == -1)
		securestate = (access(pwaf, F_OK) == 0);
	return (securestate);
}

/*
 * Get the password for user ID == 0
 */
char *
rootpass()
{
	register FILE *f;
	register struct passwd *pwd;
	register struct passwd_adjunct *pwa;
	extern struct passwd *fgetpwent();
	extern struct passwd_adjunct *fgetpwaent();

	if ((f = fopen(pwdf, "r")) == NULL)
		return (NULL);
	while ((pwd = fgetpwent(f)) != NULL) {
		if (pwd->pw_uid == 0) {
			break;
		}
	}
	fclose(f);
	if (pwd == NULL)
		return (NULL);
	if ((f = fopen(pwaf, "r")) == NULL)
		return (pwd->pw_passwd);
	while ((pwa = fgetpwaent(f)) != NULL) {
		if (strcmp(pwa->pwa_name, pwd->pw_name) == 0) {
			break;
		}
	}
	fclose(f);
	if (pwa == NULL)
		return (NULL);

	return (pwa->pwa_passwd);
}

/*
 * Is the console secure?
 */
cons_secure()
{
	register struct ttyent *t;
	register char *s;
	extern char *rindex();

	s = rindex(ctty, '/');
	if (s != NULL)
		s++;
	if ((t = getttynam(s)) != NULL)
		return (t->ty_status & TTY_SECURE);
	return (0);
}

#include <sys/termios.h>
/*
 * close std{in,out,err}
 * make this process a session leader
 * get the console as this process' ctty
 * if (protect) leave the tty in a non signaling state
 */
getcons(protect)
{

	(void) close(0); (void) close(1); (void) close(2);
	(void) setpgrp(0, 0);
	(void) open(ctty, O_RDWR);
	(void) dup2(0, 1); (void) dup2(0, 2);
	if (!protect)
		return;
	/*
	 * the child grabs the tty, protecting the parent and the
	 * parent's descendants.
	 */
	if (fork() == 0) {
		int pgrp = getpid();

		(void) setpgrp(pgrp, pgrp);
		(void) ioctl(0, TIOCSPGRP, &pgrp);
		exit(0);
		/*NOTREACHED*/
	}
}
