/* mingetty.c

mingetty is GPL

*
 *  Copyright (C) 1996 Florian La Roche
 *  florian@jurix.jura.uni-sb.de florian@suse.de
 *
 *  Newer versions should be on susix.jura.uni-sb.de/pub/linux/source/system
 *  or sunsite.unc.edu/pub/Linux/system/Admin/login or /pub/Linux/system/
 *  Daemons/init/.
 *
 *  utmp-handling is from agetty in util-linux 2.5 (probably from
 *  Peter Orbaek )
 *
 *  --user is added by Dr. J"org Weule (weule@acm.org).
 *  This option is used to skip the login procedure.
 *  /.et/autologin can be used as an alternative. This file is used
 *  to make the configuration of the system more convenient for
 *  tools like YaST and others (I hope they will use it).
 *  Adding '--user myname' to /.et/inittab is something for hackers.
 *
 *  The administrator or user can add '. tty_select_profile' or
 *  'source tty_select_login' to execute the startup files 
 *  .profile.ttyN or .login.ttyN by the login shell resp.
 *  By myself I added the line 'xinit' to .login.tty5 to startup
 *  the window system by turning on my computer like other OSes do.
 *  I'm the only user of my computer and so it's convenient for me at home.
 *
 *  S.u.S.E. - GmbH has paid some of the time that was needed to write
 *  this program. Thanks a lot for their support.
 *
 *  This getty can only be used as console getty. It is very small, but
 *  should be very reliable. For a modem getty, I'd also use nothing else
 *  but mgetty.
 *
 *  Usage: mingetty [--noclear] [--user user] tty
 *  Example entry in /.et/inittab: 1:123:respawn:/.sbi/mingetty tty1
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 *
 *  /.et/autologin is a file to specify the usernames for automatically
 *  login: Each line is staring with the tty name and ending with
 *  the username seperated by white space. (J"org Weule, weule@acm.org)
 *
 */

#define DEBUG_THIS 1

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef linux
#include 
#define USE_SYSLOG
#endif

 /* If USE_SYSLOG is undefined all diagnostics go directly to /dev/console. */
#ifdef	USE_SYSLOG
#include 
#endif

#define	ISSUE "/.et/issue"	/* displayed before the login prompt */
#include 
#include 

#define LOGIN "cLIeNUX login: "	/* login prompt */

/* name of this program (argv[0]) */
static char *progname;
/* on which tty line are we sitting? (e.g. tty1) */
static char *tty;
/* some information about this host */
static struct utsname uts;
/* the hostname */
static char hn[MAXHOSTNAMELEN + 1];
/* process ID of this program */
static pid_t pid;
/* do not send a reset string to the terminal ? */
static int noclear = 0;

/*
 * output error messages
 */
static void error (const char *fmt, ...)
{
	va_list va_alist;
	char buf[256], *bp;
#ifndef	USE_SYSLOG
	int fd;
#endif

#ifdef USE_SYSLOG
	buf[0] = '\0';
	bp = buf;
#else
	strcpy (buf, progname);
	strcat (buf, ": ");
	bp = buf + strlen (buf);
#endif

	va_start (va_alist, fmt);
	vsprintf (bp, fmt, va_alist);
	va_end (va_alist);

#ifdef	USE_SYSLOG
	openlog (progname, LOG_PID, LOG_AUTH);
	syslog (LOG_ERR, buf);
	closelog ();
#else
	strcat (bp, "\r\n");
	if ((fd = open ("/dev/console", 1)) >= 0) {
		write (fd, buf, strlen (buf));
		close (fd);
	}
#endif
	exit (1);
}

/*
 * update_utmp - update our utmp entry
 *
 * The utmp file holds miscellaneous information about things started by
 * /.sbi/init and other system-related events. Our purpose is to update
 * the utmp entry for the current process, in particular the process
 * type and the tty line we are listening to. Return successfully only
 * if the utmp file can be opened for update, and if we are able to find
 * our entry in the utmp file.
##
uh,  and what if we don't succeed?



 */


static void update_utmp (void)
{
	struct utmp ut;
	int ut_fd;
	struct utmp *utp;

	utmpname (_PATH_UTMP);
	setutent ();
	while ((utp = getutent ()))
		if (utp->ut_type == INIT_PROCESS && utp->ut_pid == pid)
			break;

	if (utp) {
		memcpy (&ut, utp, sizeof (ut));
	} else {
		/* some inits don't initialize utmp... */
		/* XXX we should print out a warning message */
		memset (&ut, 0, sizeof (ut));
		strncpy (ut.ut_id, tty + 3, sizeof (ut.ut_id));
	}
	endutent ();

	strncpy (ut.ut_user, "LOGIN", sizeof (ut.ut_user));
	strncpy (ut.ut_line, tty, sizeof (ut.ut_line));
	time (&ut.ut_time);
	ut.ut_type = LOGIN_PROCESS;
	ut.ut_pid = pid;

	pututline (&ut);
	endutent ();

	if ((ut_fd = open (_PATH_WTMP, O_APPEND | O_WRONLY)) >= 0) {
		flock (ut_fd, LOCK_EX);
		write (ut_fd, &ut, sizeof (ut));
		flock (ut_fd, LOCK_UN);
		close (ut_fd);
	}
}



/* open_tty - set up tty as standard { input, output, error } */
static void open_tty (void)
{
	struct sigaction sa;
	struct stat st;
	char buf[20];

	/* Get rid of the present stdout/stderr. */
	close (1);
	close (2);

	/* Set up new standard input. */
	strcpy (buf, "/dev/");
	strcat (buf, tty);
	if (stat (buf, &st) < 0)
		error ("%s: %s", buf, sys_errlist[errno]);
	if ((st.st_mode & S_IFMT) != S_IFCHR)
		error ("%s: not a character device", buf);
	chown (tty, 0, 0);	/* root, root */
	chmod (tty, 0600);	/* crw------- */
	close (0);
	if (open (buf, O_RDWR, 0) != 0)
		error ("%s: cannot open as standard input: %s", buf,
				sys_errlist[errno]);

	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	sigemptyset (&sa.sa_mask);
	sigaction (SIGHUP, &sa, NULL);

	/* vhangup() will replace all open file descriptors that point to our
	   controlling tty by a dummy that will deny further reading/writing
	   to our device. It will also reset the tty to sane defaults, so we
	   don't have to modify the tty device for sane settings.
	   We also get a SIGHUP/SIGCONT.
	 */
	vhangup ();
	close (0);

	if (open (buf, O_RDWR, 0) != 0)
		error ("%s: cannot open as standard input: %s", buf,
				sys_errlist[errno]);

	/* Set up standard output and standard error file descriptors. */
	if (dup (0) != 1 || dup (0) != 2)
		error ("%s: dup problem: %s", buf, sys_errlist[errno]);

	/* Write a reset string to the terminal. This is very linux-specific
	   and should be checked for other systems. */
	if (! noclear)
		write (0, "\033c", 2);

	sa.sa_handler = SIG_DFL;
	sa.sa_flags = 0;
	sigemptyset (&sa.sa_mask);
	sigaction (SIGHUP, &sa, NULL);

#if	DEBUG_THIS
	printf ("session=%d, pid=%d, pgid=%d\n", getsid (0), getpid (),
			getpgid (0));
#endif
}

static void output_special_char (unsigned char c)
{
	switch (c) {
	case 's':
		printf ("%s", uts.sysname);
		break;
	case 'n':
		printf ("%s", uts.nodename);
		break;
	case 'r':
		printf ("%s", uts.release);
		break;
	case 'v':
		printf ("%s", uts.version);
		break;
	case 'm':
		printf ("%s", uts.machine);
		break;
	case 'o':
		printf ("%s", uts.domainname);
		break;
	case 'd':
	case 't':
		{
			char *weekday[] =
			{"Sun", "Mon", "Tue", "Wed", "Thu",
			 "Fri", "Sat"};
			char *month[] =
			{"Jan", "Feb", "Mar", "Apr", "May",
			 "Jun", "Jul", "Aug", "Sep", "Oct",
			 "Nov", "Dec"};
			time_t now;
			struct tm *tm;

			time (&now);
			tm = localtime (&now);

			if (c == 'd')
				printf ("%s %s %d  %d",
				    weekday[tm->tm_wday], month[tm->tm_mon],
					tm->tm_mday,
				     tm->tm_year < 70 ? tm->tm_year + 2000 :
					tm->tm_year + 1900);
			else
				printf ("%02d:%02d:%02d",
					tm->tm_hour, tm->tm_min, tm->tm_sec);

			break;
		}
	case 'l':
		printf ("%s", tty);
		break;
	case 'u':
	case 'U':
		{
			int users = 0;
			struct utmp *ut;
			setutent ();
			while ((ut = getutent ()))
				if (ut->ut_type == USER_PROCESS)
					users++;
			endutent ();
			printf ("%d", users);
			if (c == 'U')
				printf (" user%s", users == 1 ? "" : "s");
			break;
		}
	default:
		putchar (c);
	}
}

/* do_prompt - show login prompt, optionally preceded by /.et/issue contents */
static void do_prompt (void)
{
#if	! OLD
	FILE *fd;
#else
	int fd;
#endif
	char c;

	write (1, "\n", 1);	/* start a new line */
#if	! OLD
	if ((fd = fopen (ISSUE, "r"))) {
		while ((c = getc (fd)) != EOF) {
			if (c == '\\')
				output_special_char (getc(fd));
			else
				putchar (c);
		}
		fflush (stdout);
		fclose (fd);
	}
#else
	if ((fd = open (ISSUE, O_RDONLY)) >= 0) {
		close (fd);
	}
#endif
	write (1, hn, strlen (hn));
	write (1, LOGIN, sizeof (LOGIN) - 1);
}

/* get_logname - get user name, establish speed, erase, kill, eol */
static char *get_logname (void)
{
	static char logname[40];
	char *bp;
	unsigned char c;

	ioctl (0, TCFLSH, 0);	/* flush pending input */

	for (*logname = 0; *logname == 0;) {
		do_prompt ();
		for (bp = logname;;) {
			if (read (0, &c, 1) < 1) {
				if (errno == EINTR || errno == EIO
							|| errno == ENOENT)
					exit (0);
				error ("%s: read: %s", tty, sys_errlist[errno]);
			}
			if (c == '\n' || c == '\r') {
				*bp = 0;
				break;
			} else if (!isalnum (c) && c != '_')
				error ("%s: invalid character for login name",
								tty);
			else if (bp - logname >= sizeof (logname) - 1)
				error ("%s: too long login name", tty);
			else
				*bp++ = c;
		}
	}
	return logname;
}

static	char *username=NULL;

/* get_username() reads the file /.et/autologin to get the username */
/* /.et/autologin is a file with lines, each starting with the tty
   and ending with the username to automatically login. */
char*get_username(void){
	static char c[128];
	FILE*f=fopen("/.et/autologin","r");
	if ( f == NULL ) return NULL ;
	while ( !feof(f) ){
		fgets(c,128,f);
		if ( memcmp(tty,c,strlen(tty)) == 0 ){
			int i;
			username=c+strlen(tty);
			while(username[0] && isspace(username[0])) username++ ;
			i = strlen(username) -1 ;
			while ( i != 0 && username[i] && isspace(username[i]) ){
				username[i] = '\0' ; i-- ;
			}
			fputs("Autologin for ",stdout);
			fputs(username,stdout);
			fputs(":\n\n",stdout);
			return username;
		}
	}
	return NULL ;
}

static void usage (void)
{
	error ("usage: '%s tty' with e.g. tty=tty1");
}

static struct option const long_options[] = {
	{ "noclear", no_argument, &noclear, 1},
	{ "user", required_argument, NULL, 2},
	{ 0, 0, 0, 0 }
};

/*
 * main program
 */
int main (int argc, char **argv)
{
	char *logname;
	int c;

	progname = argv[0];
	uname (&uts);
	gethostname (hn, MAXHOSTNAMELEN);
	pid = getpid ();

	while ((c = getopt_long (argc, argv, "", long_options, (int *) 0))
			!= EOF) {
		switch (c) {
			case 0:
				break;
			case 2:
				username=optarg;
				break;
			default:
				usage ();
		}
	}

	tty = argv[optind];
	if (! tty) tty = "tty1" ;

	update_utmp ();
	open_tty ();
#if 0
#ifdef linux
	ioctl (0, TIOCSPGRP, &pid);
#endif
#endif
	/* flush input and output queues, important for modems 
		## what modems?    */
	ioctl (0, TCFLSH, 2);
	if ( username == NULL && get_username() == NULL ){
		while ((logname = get_logname ()) == 0);
		execl (_PATH_LOGIN, _PATH_LOGIN, "--", logname, NULL);
	} else {
		execl (_PATH_LOGIN, _PATH_LOGIN, "-f", username, NULL);
	}
	error ("%s: can't exec " _PATH_LOGIN ": %m", tty);
	exit (0);
}