/*
 *   Copyright (c) 1996 David Parsons. 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 David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``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 DAVID
 *  PARSONS 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.
 */

static const char rcsid[] = "Mastodon $Id: s.getty.c 1.3 02/05/04 01:25:39-07:00 root@egypt.pell.portland.or.us $";

/*
 * a getty, written from scratch.
 *
 * usage: getty [-cdmq] [-i issue] [-l login] tty [tag [term-type]]
 *
 *  -c		do a consistancy check on /etc/gettydefs
 *  -d		don't clear the screen before the login prompt.
 *  -m		this is a modem-- don't reset the line before prompting.
 *  -q		don't display the issue file.
 *  -i		name an issue file.
 *  -l		use the following program as the login program.
 *  tty		the tty we're talking to, in /dev
 *  tag		which /etc/gettydefs entry to use.
 *  term-type	what sort of terminal is this (for clearing the screen)
 */

#include <stdio.h>
#include <fcntl.h>
#include <paths.h>
#include <sys/utsname.h>
#include <stdarg.h>
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <termcap.h>
#include <signal.h>
#include <errno.h>

#include "gettydefs.h"

void error(char *fmt, ...);
int getlogin(GDE *p, char *name, int len);

static struct utsname sysinfo;	/* system information for login: prompt */
int tty;			/* tty we're talking to */
char *program;			/* our program name, for syslog() */
int local = 1;		/* if we're on a modem, don't hangup before prompt*/
char *issue = "/etc/issue";	/* issue file before login */
int dumb = 0;			/* dumb terminal - don't bother to clear */
char *clear = (char*)0;		/* clear screen */
char *logintty = (char*)0;	/* the tty we're on, for @L escape */

#define CTL(x)	(x - '@')
#define DEL	0x7f

/* if no gettydefs tag is supplied, use this one.
 */
GDE default_tio = {
    "default",					/* tag */
    { BRKINT|IGNPAR|ISTRIP|ICRNL|IXON,		/* before */
      OPOST|NL0|CR0|TAB0|BS0|VT0|FF0|ONLCR,
      B9600|CS8|CREAD|CLOCAL,
      ISIG|ICANON|ECHO|ECHOK,
      0,
      { CTL('C'), CTL('\\'), DEL, CTL('U'), CTL('D'), 0, 0, CTL('Q'),
	CTL('S'), CTL('Z'), CTL('M'), CTL('R'), CTL('O'), CTL('W'), CTL('V') }
    },
    { BRKINT|IGNPAR|ICRNL|IXON,			/* after */
      OPOST|NL0|CR0|TAB0|BS0|VT0|FF0|ONLCR,
      B9600|CS8|CREAD|CLOCAL,
      ISIG|ICANON|ECHO|ECHOK,
      0,
      { CTL('C'), CTL('\\'), DEL, CTL('U'), CTL('D'), 0, 0, CTL('Q'),
	CTL('S'), CTL('Z'), CTL('M'), CTL('R'), CTL('O'), CTL('W'), CTL('V') }
    },
    "@S login: ",				/* prompt */
    "default",					/* nexttag */
};

extern int getopt();
extern int optind, opterr;
extern char *optarg;

/*
 * getty, in the flesh
 */
main(int argc, char **argv)
{
    char terminal[80];	/* full pathname to the tty device */
    char name[20];	/* the user's login name. */
    char term_type[80];	/* the terminal type, for putenv() */
    GDE *tio;		/* the gettydefs entry we're using now */
    int opt;		/* option, for getopt() and other functions */
    char *login_program = _PATH_LOGIN;	/* our login program, defaulted */
    static char termcap_bfr[2048];	/* a scratchspace for termcaps */

    if (program=strchr(argv[0], '/'))
	++program;
    else
	program = argv[0];

    /* first we pick apart the arguments
     */
    opterr=0;
    while ((opt=getopt(argc, argv, "cdmqi:l:")) != EOF) {
	switch (opt) {
	case 'm': local = 0; break;
	case 'l': login_program = optarg; break;
	case 'c': gettycheck = 1; break;
	case 'q': issue = (char*)0; break;
	case 'i': issue = optarg; break;
	case 'd': dumb = 1; break;
	}
    }
    argc -= (optind-1);
    argv += (optind-1);

    /* load the gettydefs file, (optionally checking if -c was specified) */
    loadgettydefs("/etc/gettydefs");

    if (gettycheck)
	exit(gettyerrors ? 1 : 0);

    if (argc < 2)
	error("usage: getty [options] tty [tag [term-type]]");

    if (argc >= 3) {
	tio = getgettydef(argv[2]);
	if (tio == (GDE*)0)
	    error("unknown tag <%s>", argv[2]);
    }
    else
	tio = &default_tio;

    /* If we're setting the terminal type, poke around in /etc/termcap
     * to find out how to clear the screen
     */
    if ((argc >= 4) && !dumb) {
	int ret;
	char *p = termcap_bfr;

	sprintf(term_type, "TERM=%s", argv[3]);
	putenv(term_type);
	if (tgetent(termcap_bfr, argv[3]) != 1)
	    dumb = 1;
	else
	    clear = tgetstr("cl", &p);
    }

#if 0
    if (strchr(argv[1], '/') != 0)
	error("badly formed tty name <%s>", argv[1]);
#endif

    close(0);	/* close the standard file descriptors, then open */
    close(1);	/* the terminal and fill them in */
    close(2);

    sprintf(terminal, "/dev/%s", logintty = argv[1]);
    if ((tty = open(terminal, O_RDWR)) < 0)
	error("cannot open tty <%s>", argv[1]);

    dup(tty);
    dup(tty);

    opt = getpid();		/* get a new process group */
    ioctl(0, TIOCSPGRP, &opt);

    if (uname(&sysinfo) != 0)
	error("uname failed: %s", strerror(errno));

    /* keep asking for a login until we get one or we
     * time out and die.
     */
    while ((opt=getlogin(tio, name, sizeof name)) <= 0) {
	if (opt < 0) {
	    strcpy(name, tio->nexttag);
	    if ((tio = getgettydef(tio->nexttag)) == (GDE*)0)
		error("unknown tag <%s>", name);
	}
    }
    if (opt > 0) {
	execl(login_program, "login", name, 0);
	error("cannot exec login program");
    }
    exit(0);
} /* main */


/*
 * give_prompt() prompts for a username, doing the usual run of the mill
 * sorts of escaping for a getty
 */
give_prompt(char *str)
{
    int i;
    int c;

    while (*str) {
	if (*str == '@') {
	    switch (*++str) {
	    case 'S': fputs(sysinfo.nodename, stdout); break;
	    case 'V': fputs(sysinfo.version, stdout); break;
	    case 'D': fputs(sysinfo.domainname, stdout); break;
	    case 'L': fputs(logintty, stdout); break;
	    case 'R': printf("%s %s", sysinfo.sysname, sysinfo.release); break;
	    case '@': putchar(*str); break;
	    case 0:  --str; break;
	    }
	    ++str;
	}
	else if (*str == '\\') {
	    switch (*++str) {
	    case 0: --str; break;
	    case 'b':	putchar(010); break;
	    case 'c':	break;
	    case 'f':	putchar('\f'); break;
	    case 'n':	putchar('\n'); break;
	    case 'r':	putchar('\r'); break;
	    case 's':	putchar(' '); break;
	    case 't':	putchar('\t'); break;
	    case '0': case '1': case '2': case '3': case '4':
	    case '5': case '6': case '7': case '8': case '9':
		for (c=0; isdigit(*str); ++str)
		    c = (c * 8) + (c-'0');
		putchar(c);
		break;
	    }
	    ++str;
	}
	else
	    putchar(*str++);
    }
    fflush(stdout);
} /* give_prompt */


/*
 * cat() dumps a file to the terminal
 */
cat(char *file)
{
    FILE *f;
    char c;

    if (f = fopen(file, "r")) {
	while ((c=fgetc(f)) != EOF)
	    putchar(c);
	fclose(f);
    }
} /* cat */


/*
 * ask for a usename to then feed to the login program
 */
int
getlogin(GDE *p, char *name, int len)
{
    int x;
    char c;
    char del_char = 0;
    struct termios tio;
    static struct termios login_tio = {
	0,
        0,
	0,
	0,
	0,
	{ CTL('C'), CTL('\\'), 0, CTL('U'), CTL('D'), 0, 0,
	  CTL('Q'), CTL('S'), CTL('Z'), CTL('M'), CTL('R'),
	  CTL('O'), CTL('W'), CTL('V') }
    };

    /* reset local terminal by setting the baud-rate to zero before
     * issuing the issue and/or prompt.
     */
    tio = p->before;
    if (local) {
	tio.c_cflag &= ~CBAUD;
	tcsetattr(tty, TCSANOW, &tio);
    }

    /* set the before flags, dump the issue and the login: prompt.
     */
    tio = p->before;
    tcsetattr(tty, TCSANOW, &tio);
    /* clear the screen on terminals that support it */
    if (clear && !dumb)
	write(tty, clear, strlen(clear));
    if (issue)
	cat(issue);

top:
    give_prompt(p->prompt);
    tcflush(tty, TCIOFLUSH);

    /* set up for getting the login -- set the terminal to raw mode
     * (with the notable exceptions of c_cflag & c_oflag) and read
     * each character as it comes in.
     */
    login_tio.c_cflag = p->before.c_cflag;
    login_tio.c_oflag = p->before.c_oflag;
    login_tio.c_cc[VMIN] = 1;
    login_tio.c_cc[VTIME] = 0;
    tcsetattr(tty, TCSADRAIN, &login_tio);

    /* get the login
     */
    memset(name, len, 0);
    x = 0;
    while (1) {
	if (read(tty, &c, 1) < 1)
	    return -1;
	if (c == DEL || c == CTL('H') || c == '#') {
	    if (del_char == 0 && c != '#') {
		p->after.c_cc[VERASE] = del_char = c;
	    }
	    if (x > 0) {
		write(tty, "\010 \010", 3);
		--x;
	    }
	}
	else if (c == CTL('D'))
	    return -1;
	else if (c == '\r' || c == '\n')
	    break;
	else if (isprint(c) && (x < len)) {
	    name[x++] = c;
	    write(tty, &c, 1);
	}
    }
    write(tty, "\n", 1);
    if (x > 0) {
	if (name[0] == '-')	/* no login names with '-' in them! */
	    goto top;
	name[x] = 0;
	/* be paranoid? */
	tcsetattr(tty, TCSADRAIN, &p->after);
    }
    return x;
} /* getlogin */


/* 
 * write an error message to the syslog, twiddle our thumbs for a bit,
 * then quit
 */
void
error(char *fmt, ...)
{
    va_list ptr;

    openlog(program, LOG_PID, LOG_AUTH);
    va_start(ptr, fmt);
    vsyslog(LOG_ERR, fmt, ptr);
    va_end(ptr);
    closelog();
    sleep(10);
    exit(1);
} /* error */
