/*
 *	$Source: /u2/projects/toehold/RCS/toehold.c,v $
 *	$Header: toehold.c,v 1.3 87/01/07 11:10:27 jtkohl Locked $
 */

#ifndef lint
static char *rcsid_toehold_c = "$Header: toehold.c,v 1.3 87/01/07 11:10:27 jtkohl Locked $";
#endif	lint

#include <strings.h>
#include <sys/file.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sgtty.h>
#include <utmp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <ttyent.h>
#include <X/Xlib.h>
#define WAKE_UP_INTERVAL 120	/* How often to check for cleanup */
#define X_START_WAIT 60		/* How long to wait before giving up on X */
#define X_WAIT_INTERVAL 1	/* How long to wait between trying X */
#define DISPLAY ":0"		/* What display we expect to run on */

#ifdef OLD_ARGV_FORMAT
#define MAGIC_SEPARATOR "//"
#endif

#define LINESIZE 128
#define NARGS 20		/* Must be at least 3 */

#define SCPYN(a, b) strcpy(a, b, sizeof(a))
#define SCMPN(a, b) strcmp(a, b, sizeof(a))

#ifndef NULL
#define NULL (char *) 0
#endif

int pid = 0;
int wpid = 0;
char *tty = NULL;

struct sgttyb mode;

/* Messages */
static char message[]="Hit any key to start.";
static char deactivate_fail[]="/etc/athena/deactivate NOT FOUND\n\r";
static char activate_fail[]="/etc/athena/activate NOT FOUND\n\r";

/* Program names */
static char activate_prog[]="/etc/athena/activate";
static char deactivate_prog[]="/etc/athena/deactivate";

/* Files */
static char utmpf[]="/etc/utmp";
static char wtmpf[]="/usr/adm/wtmp";

#define MSG(x) write(1, x, strlen(x));

/* Display clearing constants */
/* CLRHEIGHT should be the height of the display */
/* CLRWIDTH should be the width of the display */
#ifdef ibm032
#define CLRHEIGHT_AED 52
#define CLRWIDTH_AED 80
#define CLRHEIGHT_APA16 32
#define CLRWIDTH_APA16 80
int clrheight = CLRHEIGHT_APA16;
int clrwidth = CLRWIDTH_APA16;
#else
#define CLRHEIGHT 56
#define CLRWIDTH 120
int clrheight = CLRHEIGHT;
int clrwidth = CLRWIDTH;
#endif

main(argc, argv)
int argc;
char **argv;
{
    int npid;
    void die(), wait_alarm();
    char line[LINESIZE];
    char *dev;
#ifdef OLD_ARGV_FORMAT
    int i, j;
#else
    struct ttyent *tty_entry, *getttynam();
#endif
    char *xargv[NARGS], *xtermargv[NARGS];
    char *prog;
    Display *display;

    /* Ignored signals */
    signal(SIGSTOP, SIG_IGN);
    signal(SIGTSTP, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGTTOU, SIG_IGN);

    /* These signals cause us to shutdown cleanly */
    signal(SIGHUP, die);
    signal(SIGINT, die);
    signal(SIGTERM, die);

    dev = argv[--argc];		/* Passed by init */
    tty = argv[--argc];		/* For X and xterm */

#ifdef OLD_ARGV_FORMAT
    /* Split rest of argv into two pieces */
    /* I wrote this at 2 am, give me a break */
    for(i=1, j=0; i<argc && j<NARGS-2; i++, j++) {
	xargv[j]=argv[i];
	if(!strcmp(argv[i], MAGIC_SEPARATOR)) {
	    xargv[j++] = tty;
	    xargv[j] = NULL;
	    goto found_separator;
	}	    
    }
    /* Didn't find MAGIC_SEPARATOR, we lose */
    exit(1);

  found_separator:

    /* Get xterm argv */
    for(j=0, i++; i<argc && j<NARGS-2; i++, j++) {
	xtermargv[j]=argv[i];
    }
    xtermargv[j++] = tty;
    xtermargv[j] = NULL;

    /* End of argument-parsing cruft */
#else
#ifdef ibm032
    /* is is an apa16 or an aed? */
    if (((tty_entry = getttynam("console")) != (struct ttyent *) NULL) &&
	!strcmp(tty_entry->ty_type, "ibmaed")) {
	    clrwidth = CLRWIDTH_AED;
	    clrheight = CLRHEIGHT_AED;
	}
#endif ibm032
    if(!(tty_entry = getttynam(tty))) exit(1);
    parse_args(tty_entry->ty_getty, tty, xtermargv);
    parse_args(tty_entry->ty_window, tty, xargv);
#endif

    close(0);
    strcpy(line, "/dev/");
    strcat(line, dev);
    open(line, O_RDWR, 0622);
    dup2(0, 1);
    dup2(1, 2);

    /* Clear the display and put our message in a random place */
    cooked_mode();
#ifdef ibm032
    MSG("\033L"); /* ESC-L: clear and home; ESC-S: inverse */
#endif
    spew(clrheight, '\n');
    srandom(getpid());
    spew(random() % (clrwidth - strlen(message)), ' ');
    MSG(message);
    spew(random() % clrheight, '\n');
    spew(random() % clrwidth, ' ');

    /* Wait for the user to type a key */
    raw_mode();
    signal(SIGALRM, wait_alarm);
    alarm(WAKE_UP_INTERVAL);
    ioctl(0, TIOCFLUSH, 0);
    while(read(0, line, 1) < 0);
    signal(SIGALRM, SIG_IGN);
    alarm(0);
    cooked_mode();

    /* Activate the workstation */
    npid = fork();
    if(!npid) {
	execlp(activate_prog, activate_prog, 0);
	MSG(activate_fail);
	exit(1);
    }
    wait(0);

    /* Start up X */
    wpid = fork();
    if(!wpid) {
	sigsetmask(0);
	prog = *xargv;
	*xargv = "-";
	execv(prog, xargv);
	perror("executing X server");
	exit(1);
    }

    /* Loop until we lose */
    /* This is the xterm to logout loop */
    for(;;) {

	/* Kludge to speed up xterm startup */
	signal(SIGALRM, die);
	alarm(X_START_WAIT);
	while(!(display = XOpenDisplay(DISPLAY))) sleep(X_WAIT_INTERVAL);

	/* X is here, do the wait */
	alarm(0);
	XCloseDisplay(display);
	XFlush();
	/* End of kludge */

	/* Start up xterm */
	pid = fork();
	if(!pid) {
	    int i, max;

	    max = getdtablesize();
	    for(i=0; i < max; close(i++));
	    setpgrp(0,0);
	    sigsetmask(0);
	    prog = *xtermargv;
	    *xtermargv = "-";
	    execv(prog, xtermargv);
	    perror("executing xterm");
	    exit(1);
	}

	/* Set timeout for login */
	alarm(WAKE_UP_INTERVAL);
	signal(SIGALRM, wait_alarm);

	/* This is the wait loop */
	for(;;) {
	    npid = wait(0);
	    /* If the X bought it, we lose big */
	    if(npid == wpid) {
		die(2);		/* Never returns */
	    }
	    /* If xterm exited, clean up utmp and such */
	    else if(npid == pid) {
		kill(wpid, SIGHUP);
		pid=0;
		rmut(tty);
		break;		/* Out of wait loop */
	    }
	    /* Otherwise, just continue, since we don't know what happened */
	    continue;		/* Wait loop */
	}
    }
}

cleanup()
{
    int procid;

    if (pid)  {
	kill(pid, SIGTERM);
    }
    if (wpid) {
	kill(wpid, SIGHUP);
	kill(wpid, SIGTERM);

#ifdef ibm032
	procid = wait(0);
	if (pid && (procid == pid)) procid = wait(0);
        MSG("\033S");		/* if aed, reverse video on X kill */
#endif ibm032

    }
    rmut(tty);
}

void die(code)
int(code);
{
    signal(SIGALRM, SIG_IGN);
    cleanup();
    exit(code);
}
    
void deactivate_workstation()
{
    signal(SIGALRM, SIG_IGN);
    cleanup();
    /* Run cleanup daemon */
    /* Init will restart us when it's done */
    cooked_mode();
    execlp(deactivate_prog, deactivate_prog, 0);
    MSG(deactivate_fail);
    exit(1);
}

/* Gets called WAKE_UP_INTERVAL seconds after xterm exits */
/* or after no key is hit */
/* Runs cleanup daemon if no users are logged in */
void wait_alarm()
{
    struct utmp utmp;
    int file;

    if((file = open(utmpf, O_RDONLY, 0)) >= 0) {
	while(read(file, (char *) &utmp, sizeof(utmp)) > 0) {
	    if(*utmp.ut_name) goto no_deactivate;
	}
	deactivate_workstation();
    }

    /* Here if someone's logged in or we can't open utmp */
  no_deactivate:
    /* If we don't have an X, restart toehold */
    if(!wpid) {
	die(0);
    }
}

/* These routines are for setting the tty modes of the controlling terminal */
raw_mode()
{
    ioctl(0, TIOCGETP, &mode);
    mode.sg_flags = mode.sg_flags & ~ECHO | RAW;
    ioctl(0, TIOCSETP, &mode);
}

cooked_mode()
{
    ioctl(0, TIOCGETP, &mode);
    mode.sg_flags = mode.sg_flags & ~RAW | ECHO;
    ioctl(0, TIOCSETP, &mode);
}

/*
 * Remove utmp entry.
 * Takes tty name as its argument
 */
rmut(tty)
char *tty;
{
	register f;
	int found = 0;
	static unsigned utmpsize;
	static struct utmp *utmp;
	register struct utmp *u;
	int nutmp;
	struct stat statbf;

	if(tty == NULL) return;

	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, tty) ||
				    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) {
		        struct utmp wtmp;
			SCPYN(wtmp.ut_line, tty);
			SCPYN(wtmp.ut_name, "");
			SCPYN(wtmp.ut_host, "");
			time(&wtmp.ut_time);
			write(f, (char *)&wtmp, sizeof(wtmp));
			close(f);
		}
	}
}

/* Put n copies of character c out to the standard output */
spew(n, c)
int n;
char c;
{
    int i;

    for(i=0; i < n; i++) {
	write(1, &c, 1);
    }
}

/* Parse string into argv, appending arg */
parse_args(string, arg, argv)
char *string;
char *arg;
char *argv[];
{
    int i;

    for(i=0; i < NARGS-2; ) {
	while(*string == ' ' && *string != '\0') string++;
	if(*string == '\0') break;
	argv[i++] = string;
	if((string = index(string, ' ')) == NULL) break;
	*string++ = '\0';
    }
    argv[i++] = arg;
    argv[i] = NULL;
}
