OpenSolaris_b135/cmd/vt/vtdaemon.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * vtdaemon is responsible for the session secure switch via hotkeys.
 *
 * vtdaemon itself, like ttymon(1M), is also running on a virtual
 * console device (/dev/vt/1), and provides a text console session
 * for password input and authentication. The /dev/vt/1 special text
 * console is reserved and end users cannot switch to it via hotkeys.
 *
 *
 * The hotkey event request can come from either kernel or Xserver,
 * and a door server is setup to handle the request:
 *
 *   1) All text console hotkeys (e.g. "Alt + F#") are intercepted by
 *      the kernel console driver which sends a door upcall to the
 *      vtdaemon via door_upcall (target_vt).
 *
 *   2) All Xserver hotkeys ("Alt + Ctrl + F#") are intercepted by
 *      Xserver which sends a door call to the vtdaemon via
 *      door_call (target_vt).
 *
 *
 * server_for_door receives and handles any door server requests:
 *
 *   Firstly, check source session:
 *
 *      . If it's from kernel for a text console source session,
 *        then directly go to check the target session.
 *
 *      . If it's from Xserver for a graphical source session and the vt
 *        associated with the Xserver is currently active:
 *          check if a user has logged in, if true, issue an internal
 *          VT_EV_LOCK event to the main thread to request lock for
 *          the graphical source session; else, directly go to check
 *          the target session.
 *
 *      . otherwise, discard this request.
 *
 *
 *    Secondly, check the target session
 *
 *      . if the target session is a text one that no one has logged in
 *        or a graphical one, issue an internal VT_EV_ACTIVATE event to
 *        the main thread to request the actual VT switch.
 *
 *      . otherwise, the target session is a text one that someone has
 *        logged in, issue an internal VT_EV_AUTH event to the main
 *        thread to request authentication for the target session.
 *
 *
 * The main thread of vtdaemon is a loop waiting for internal events
 * which come from door call threads:
 *
 *   1)  VT_EV_AUTH      to authenticate for target session:
 *
 *                       firstly switch to the vtdaemon special text console;
 *                       then prompt for password (target_owner on target_vt),
 *                       e.g. "User Bob's password on vt/#: ".
 *
 *                       if the password is correct (authentication succeeds),
 *                       then actually issue the VT switch; otherwise, ignore
 *                       the request.
 *
 *   2)  VT_EV_LOCK      to lock the graphical source session:
 *
 *                       activate screenlock for this graphical session.
 *                       vtdaemon just invokes existing front-end command line
 *                       tools (e.g. xscreensaver-command -lock for JDS) to
 *                       lock the display.
 *
 *   3)  VT_EV_ACTIVATE  to directly switch to the target session
 *
 *
 * There is a system/vtdaemon:default SMF service for vtdaemon.
 *
 *	There's a "hotkeys" property (BOOLEAN) in the
 *	system/vtdaemon:default SMF service, which allows authorized
 *	users to dynamically enable or disable VT switch via hotkeys.
 *      Its default value is TRUE (enabled).
 *
 *	There's a "secure" property (BOOLEAN) in the
 *	system/vtdaemon:default SMF service, which allows authorized
 *	users to dynamically enable or disable hotkeys are secure.
 *	If disabled, the user can freely switch to any session without
 *	authentication. Its default value is TRUE (enabled).
 *
 *
 *  By default, there's only 16 virtual console device nodes (from
 *  /dev/vt/0 to /dev/vt/15). There's a property "nodecount"
 *  (default value is 16) in the system/vtdaemon:default SMF
 *  service, so authorized users can configure it to have more
 *  or less virtual console device nodes.
 *
 *  Xserver needs to switch back to previous active vt via VT_EV_X_EXIT
 *  door event request when it's exiting, so vtdaemon always needs to
 *  be there even if the hotkeys switch is disabled, otherwise the screen
 *  will be just blank when Xserver exits.
 */

#include <sys/param.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <syslog.h>
#include <deflt.h>

#include <bsm/adt.h>
#include <bsm/adt_event.h>

#include <alloca.h>
#include <assert.h>
#include <errno.h>
#include <door.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <synch.h>
#include <thread.h>
#include <unistd.h>
#include <wait.h>
#include <limits.h>
#include <zone.h>
#include <priv.h>
#include <pwd.h>
#include <utmpx.h>
#include <procfs.h>
#include <poll.h>
#include <termio.h>
#include <security/pam_appl.h>
#include <time.h>
#include <sys/console.h>
#include <assert.h>
#include <syslog.h>

#include <sys/vt.h>
#include <sys/vtdaemon.h>

/*
 * The door file /var/run/vt/vtdaemon_door
 */
#define	VT_TMPDIR "/var/run/vt"

#define	VT_DAEMON_ARG	0
#define	VT_DAEMON_CONSOLE_FILE	"/dev/vt/1"

#define	VT_IS_SYSTEM_CONSOLE(vtno)	((vtno) == 1)

/* Defaults for updating expired passwords */
#define	DEF_ATTEMPTS	3

int daemonfd;

static boolean_t vt_hotkeys = B_TRUE;		/* '-k' option to disable */
static boolean_t vt_secure = B_TRUE;		/* '-s' option to disable */

static char	vt_door_path[MAXPATHLEN];
static int	vt_door = -1;

/* protecting vt_hotkeys_pending and vt_auth_doing */
static mutex_t	vt_mutex = DEFAULTMUTEX;

static boolean_t vt_hotkeys_pending = B_FALSE;
static boolean_t vt_auth_doing = B_FALSE;

static adt_session_data_t **vt_ah_array = NULL;
static int vtnodecount = 0;

static int vt_audit_start(adt_session_data_t **, pid_t);
static void vt_audit_event(adt_session_data_t *, au_event_t, int);
static void vt_check_source_audit(void);

static int
vt_setup_signal(int signo, int mask)
{
	sigset_t set;

	(void) sigemptyset(&set);
	(void) sigaddset(&set, signo);

	if (mask)
		return (sigprocmask(SIG_BLOCK, &set, NULL));
	else
		return (sigprocmask(SIG_UNBLOCK, &set, NULL));
}

static void
do_activate_screenlock(int display_num)
{
	char dpy[16];

	(void) snprintf(dpy, sizeof (dpy), "%d", display_num);
	(void) execl("/usr/lib/vtxlock", "vtxlock", dpy, NULL);
}

static void
vt_activate_screenlock(int display)
{
	pid_t pid;

	if ((pid = fork()) == -1)
		return;

	if (pid == 0) { /* child */
		do_activate_screenlock(display);
		exit(0);
	}

	/* parent */
	while (waitpid(pid, (int *)0, 0) != pid)
		continue;
}

/*
 * Find the login process and user logged in on the target vt.
 */
static void
vt_read_utx(int target_vt, pid_t *pid, char name[])
{
	struct utmpx  *u;
	char ttyntail[sizeof (u->ut_line)];

	*pid = (pid_t)-1;

	if (VT_IS_SYSTEM_CONSOLE(target_vt)) /* system console */
		(void) snprintf(ttyntail, sizeof (ttyntail),
		    "%s", "console");
	else
		(void) snprintf(ttyntail, sizeof (ttyntail),
		    "%s%d", "vt/", target_vt);

	setutxent();
	while ((u = getutxent()) != NULL)
		/* see if this is the entry we want */
		if ((u->ut_type == USER_PROCESS) &&
		    (!nonuserx(*u)) &&
		    (u->ut_host[0] == '\0') &&
		    (strncmp(u->ut_line, ttyntail, sizeof (u->ut_line)) == 0)) {

			*pid = u->ut_pid;
			if (name != NULL) {
				(void) strncpy(name, u->ut_user,
				    sizeof (u->ut_user));
				name[sizeof (u->ut_user)] = '\0';
			}
			break;
		}

	endutxent();
}

static boolean_t
vt_is_tipline(void)
{
	static int is_tipline = 0;
	int fd;
	static char termbuf[MAX_TERM_TYPE_LEN];
	static struct cons_getterm cons_term = { sizeof (termbuf), termbuf};

	if (is_tipline != 0)
		return (is_tipline == 1);

	if ((fd = open("/dev/console", O_RDONLY)) < 0)
		return (B_FALSE);

	if (ioctl(fd, CONS_GETTERM, &cons_term) != 0 &&
	    errno == ENODEV) {
		is_tipline = 1;
	} else {
		is_tipline = -1;
	}

	(void) close(fd);
	return (is_tipline == 1);
}

static int
validate_target_vt(int target_vt)
{
	int fd;
	struct vt_stat state;

	if (target_vt < 1)
		return (-1);

	if ((fd = open(VT_DAEMON_CONSOLE_FILE, O_WRONLY)) < 0)
		return (-1);

	if (ioctl(fd, VT_GETSTATE, &state) != 0) {
		(void) close(fd);
		return (-1);
	}

	(void) close(fd);

	if (state.v_active == target_vt) {
		return (1);	/* it's current active vt */
	}

	if (target_vt == 1) {
		/*
		 * In tipline case, the system console is always
		 * available, so ignore this request.
		 */
		if (vt_is_tipline())
			return (-1);

		target_vt = 0;
	}

	/*
	 * The hotkey request and corresponding target_vt number can come
	 * from either kernel or Xserver (or other user applications).
	 * In kernel we've validated the hotkey request, but Xserver (or
	 * other user applications) cannot do it, so here we still try
	 * to validate it.
	 *
	 * VT_GETSTATE is only valid for first 16 VTs for historical reasons.
	 * Fortunately, in practice, Xserver can only send the hotkey
	 * request of target_vt number from 1 to 12 (Ctrl + Alt + F1 to F2).
	 */
	if (target_vt < 8 * sizeof (state.v_state)) {
		if ((state.v_state & (1 << target_vt)) != 0) {
			return (0);
		} else {
			return (-1);
		}
	}

	return (0);
}

static void
vt_do_activate(int target_vt)
{
	(void) ioctl(daemonfd, VT_ACTIVATE, target_vt);
	(void) mutex_lock(&vt_mutex);
	vt_hotkeys_pending = B_FALSE;
	(void) mutex_unlock(&vt_mutex);
}

/* events written to fd 0 and read from fd 1 */
#define	VT_EV_AUTH	1
#define	VT_EV_LOCK	2
#define	VT_EV_ACTIVATE	3

/* events written to fd 1 and read from fd 0 */
#define	VT_EV_TERMINATE_AUTH	4

typedef struct vt_evt {
	int	ve_cmd;
	int	ve_info;	/* vtno or display num */
} vt_evt_t;

static int eventstream[2];

boolean_t
eventstream_init(void)
{
	if (pipe(eventstream) == -1)
		return (B_FALSE);
	return (B_TRUE);
}

void
eventstream_write(int channel, vt_evt_t *pevt)
{
	(void) write(eventstream[channel], pevt, sizeof (vt_evt_t));
}

static boolean_t
eventstream_read(int channel, vt_evt_t *pevt)
{
	ssize_t rval;

	rval = read(eventstream[channel], pevt, sizeof (vt_evt_t));
	return (rval > 0);
}

static void
vt_ev_request(int cmd, int info)
{
	int channel;
	vt_evt_t ve;

	ve.ve_cmd = cmd;
	ve.ve_info = info;

	channel = (cmd == VT_EV_TERMINATE_AUTH) ? 1 : 0;
	eventstream_write(channel, &ve);
}

static void
vt_clear_events(void)
{
	int rval = 0;
	struct stat buf;
	vt_evt_t evt;

	while (rval == 0) {
		rval = fstat(eventstream[0], &buf);
		if (rval != -1 && buf.st_size > 0)
			(void) eventstream_read(0, &evt);
		else
			break;
	}
}

static int vt_conv(int, struct pam_message **,
    struct pam_response **, void *);

/*ARGSUSED*/
static void
catch(int x)
{
	(void) signal(SIGINT, catch);
}

/*
 * The SIGINT (ctl_c) will restart the authentication, and re-prompt
 * the end user to input the password.
 */
static int
vt_poll()
{
	struct pollfd pollfds[2];
	vt_evt_t ve;
	int ret;

	pollfds[0].fd = eventstream[0];
	pollfds[1].fd = daemonfd;
	pollfds[0].events = pollfds[1].events =
	    POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;

	for (;;) {
		pollfds[0].revents = pollfds[1].revents = 0;

		ret = poll(pollfds,
		    sizeof (pollfds) / sizeof (struct pollfd), -1);
		if (ret == -1 && errno != EINTR) {
			continue;
		}

		if (ret == -1 && errno == EINTR)
			return (-1);

		if (pollfds[0].revents) {
			(void) eventstream_read(0, &ve);
			return (0);
		}

		if (pollfds[1].revents)
			return (1);

		return (0);

	}
}

static char
vt_getchar(int fd)
{
	char c;
	int cnt;

	cnt = read(fd, &c, 1);
	if (cnt > 0) {
		return (c);
	}

	return (EOF);
}

static char *
vt_getinput(int noecho)
{
	int c;
	int i = 0;
	struct termio tty;
	tcflag_t tty_flags;
	char input[PAM_MAX_RESP_SIZE];

	if (noecho) {
		(void) ioctl(daemonfd, TCGETA, &tty);
		tty_flags = tty.c_lflag;
		tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
		(void) ioctl(daemonfd, TCSETAF, &tty);
	}

	while ((vt_poll()) == 1) {
		if ((c = vt_getchar(daemonfd)) != '\n' && c != '\r' &&
		    c != EOF && (i < PAM_MAX_RESP_SIZE))
			input[i++] = (char)c;
		else
			break;
	}

	input[i] = '\0';

	if (noecho) {
		tty.c_lflag = tty_flags;
		(void) ioctl(daemonfd, TCSETAW, &tty);
		(void) fputc('\n', stdout);
	}

	return (strdup(input));
}

/*
 * vt_conv: vtdaemon PAM conversation function.
 * SIGINT/EINTR is handled in vt_getinput()/vt_poll().
 */

/*ARGSUSED*/
static int
vt_conv(int num_msg, struct pam_message **msg,
    struct pam_response **response, void *appdata_ptr)
{
	struct pam_message	*m;
	struct pam_response	*r;
	int			i, k;

	if (num_msg >= PAM_MAX_NUM_MSG) {
		syslog(LOG_ERR, "too many messages %d >= %d",
		    num_msg, PAM_MAX_NUM_MSG);
		*response = NULL;
		return (PAM_CONV_ERR);
	}

	*response = calloc(num_msg, sizeof (struct pam_response));
	if (*response == NULL)
		return (PAM_BUF_ERR);

	m = *msg;
	r = *response;
	for (i = 0; i < num_msg; i++) {
		int echo_off = 0;

		/* Bad message */
		if (m->msg == NULL) {
			syslog(LOG_ERR, "message[%d]: %d/NULL\n",
			    i, m->msg_style);
			goto err;
		}

		/*
		 * Fix up final newline:
		 * remove from prompts, add back for messages.
		 */
		if (m->msg[strlen(m->msg)] == '\n')
			m->msg[strlen(m->msg)] = '\0';

		r->resp = NULL;
		r->resp_retcode = 0;

		switch (m->msg_style) {

		case PAM_PROMPT_ECHO_OFF:
			echo_off = 1;
			/* FALLTHROUGH */

		case PAM_PROMPT_ECHO_ON:
			(void) fputs(m->msg, stdout);

			r->resp = vt_getinput(echo_off);
			break;

		case PAM_ERROR_MSG:
			/* the user may want to see this */
			(void) fputs(m->msg, stdout);
			(void) fputs("\n", stdout);
			break;

		case PAM_TEXT_INFO:
			(void) fputs(m->msg, stdout);
			(void) fputs("\n", stdout);
			break;

		default:
			syslog(LOG_ERR, "message[%d]: unknown type"
			    "%d/val=\"%s\"", i, m->msg_style, m->msg);

			/* error, service module won't clean up */
			goto err;
		}

		/* Next message/response */
		m++;
		r++;

	}
	return (PAM_SUCCESS);

err:
	/*
	 * Service modules don't clean up responses if an error is returned.
	 * Free responses here.
	 */
	r = *response;
	for (k = 0; k < i; k++, r++) {
		if (r->resp) {
			/* Clear before freeing -- maybe a password */
			bzero(r->resp, strlen(r->resp));
			free(r->resp);
			r->resp = NULL;
		}
	}

	free(*response);
	*response = NULL;
	return (PAM_CONV_ERR);
}

#define	DEF_FILE	"/etc/default/login"

/* Get PASSREQ from default file */
static boolean_t
vt_default(void)
{
	int flags;
	char *ptr;
	boolean_t retval = B_FALSE;

	if ((defopen(DEF_FILE)) == 0) {
		/* ignore case */
		flags = defcntl(DC_GETFLAGS, 0);
		TURNOFF(flags, DC_CASE);
		(void) defcntl(DC_SETFLAGS, flags);

		if ((ptr = defread("PASSREQ=")) != NULL &&
		    strcasecmp("YES", ptr) == 0)
			retval = B_TRUE;

		(void) defopen(NULL);
	}

	return (retval);
}

/*
 * VT_CLEAR_SCREEN_STR is the console terminal escape sequence used to
 * clear the current screen.  The vt special console (/dev/vt/1) is
 * just reserved for vtdaemon, and the TERM/termcap of it is always
 * the local sun-color, which is always supported by our kernel terminal
 * emulator.
 */
#define	VT_CLEAR_SCREEN_STR	"\033[2J\033[1;1H"

static void
vt_do_auth(int target_vt)
{
	char	user_name[sizeof (((struct utmpx *)0)->ut_line) + 1] = {'\0'};
	pam_handle_t	*vt_pamh;
	int		err;
	int		pam_flag = 0;
	int		chpasswd_tries;
	struct pam_conv pam_conv = {vt_conv, NULL};
	pid_t		pid;
	adt_session_data_t	*ah;

	vt_read_utx(target_vt, &pid, user_name);

	if (pid == (pid_t)-1 || user_name[0] == '\0')
		return;

	if ((err = pam_start("vtdaemon", user_name, &pam_conv,
	    &vt_pamh)) != PAM_SUCCESS)
		return;

	/*
	 * firstly switch to the vtdaemon special console
	 * and clear the current screen
	 */
	(void) ioctl(daemonfd, VT_ACTIVATE, VT_DAEMON_ARG);
	(void) write(daemonfd, VT_CLEAR_SCREEN_STR,
	    strlen(VT_CLEAR_SCREEN_STR));
	(void) ioctl(daemonfd, VT_SET_TARGET, target_vt);

	(void) mutex_lock(&vt_mutex);
	vt_auth_doing = B_TRUE;
	vt_hotkeys_pending = B_FALSE;
	(void) mutex_unlock(&vt_mutex);

	/*
	 * Fetch audit handle.
	 */
	ah = vt_ah_array[target_vt - 1];

	if (vt_default())
		pam_flag = PAM_DISALLOW_NULL_AUTHTOK;

	do {
		if (VT_IS_SYSTEM_CONSOLE(target_vt))
			(void) fprintf(stdout,
			    "\nUnlock user %s on the system console\n",
			    user_name);
		else
			(void) fprintf(stdout,
			    "\nUnlock user %s on vt/%d\n", user_name,
			    target_vt);

		err = pam_authenticate(vt_pamh, pam_flag);

		(void) mutex_lock(&vt_mutex);
		if (vt_hotkeys_pending) {
			(void) mutex_unlock(&vt_mutex);
			break;
		}
		(void) mutex_unlock(&vt_mutex);

		if (err == PAM_SUCCESS) {
			err = pam_acct_mgmt(vt_pamh, pam_flag);

			(void) mutex_lock(&vt_mutex);
			if (vt_hotkeys_pending) {
				(void) mutex_unlock(&vt_mutex);
				break;
			}
			(void) mutex_unlock(&vt_mutex);

			if (err == PAM_NEW_AUTHTOK_REQD) {
				chpasswd_tries = 0;

				do {
					err = pam_chauthtok(vt_pamh,
					    PAM_CHANGE_EXPIRED_AUTHTOK);
					chpasswd_tries++;

					(void) mutex_lock(&vt_mutex);
					if (vt_hotkeys_pending) {
						(void) mutex_unlock(&vt_mutex);
						break;
					}
					(void) mutex_unlock(&vt_mutex);

				} while ((err == PAM_AUTHTOK_ERR ||
				    err == PAM_TRY_AGAIN) &&
				    chpasswd_tries < DEF_ATTEMPTS);

				(void) mutex_lock(&vt_mutex);
				if (vt_hotkeys_pending) {
					(void) mutex_unlock(&vt_mutex);
					break;
				}
				(void) mutex_unlock(&vt_mutex);

				vt_audit_event(ah, ADT_passwd, err);
			}
		}

		/*
		 * Only audit failed unlock here, successful unlock
		 * will be audited after switching to target vt.
		 */
		if (err != PAM_SUCCESS) {
			(void) fprintf(stdout, "%s",
			    pam_strerror(vt_pamh, err));

			vt_audit_event(ah, ADT_screenunlock, err);
		}

		(void) mutex_lock(&vt_mutex);
		if (vt_hotkeys_pending) {
			(void) mutex_unlock(&vt_mutex);
			break;
		}
		(void) mutex_unlock(&vt_mutex);

	} while (err != PAM_SUCCESS);

	(void) mutex_lock(&vt_mutex);
	if (!vt_hotkeys_pending) {
		/*
		 * Should be PAM_SUCCESS to reach here.
		 */
		(void) ioctl(daemonfd, VT_ACTIVATE, target_vt);

		vt_audit_event(ah, ADT_screenunlock, err);

		/*
		 * Free audit handle.
		 */
		(void) adt_end_session(ah);
		vt_ah_array[target_vt - 1] = NULL;
	}
	(void) mutex_unlock(&vt_mutex);

	(void) pam_end(vt_pamh, err);

	if (user_name != NULL)
		free(user_name);

	(void) mutex_lock(&vt_mutex);
	vt_auth_doing = B_FALSE;
	vt_clear_events();
	(void) mutex_unlock(&vt_mutex);
}

/* main thread (lock and auth) */
static void __NORETURN
vt_serve_events(void)
{
	struct pollfd pollfds[1];
	int ret;
	vt_evt_t ve;

	pollfds[0].fd = eventstream[1];
	pollfds[0].events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;

	for (;;) {
		pollfds[0].revents = 0;
		ret = poll(pollfds,
		    sizeof (pollfds) / sizeof (struct pollfd), -1);
		if (ret == -1 && errno == EINTR) {
			continue;
		}

		if (pollfds[0].revents && eventstream_read(1, &ve)) {
			/* new request */
			switch (ve.ve_cmd) {
			case VT_EV_AUTH:
				vt_do_auth(ve.ve_info);
				break;

			case VT_EV_LOCK:
				vt_activate_screenlock(ve.ve_info);
				break;

			case VT_EV_ACTIVATE:
				/* directly activate target vt */
				vt_do_activate(ve.ve_info);
				break;
			}
		}
	}
}

static void
vt_check_target_session(uint32_t target_vt)
{
	pid_t	pid = (pid_t)-1;

	if (!vt_secure) {
		vt_ev_request(VT_EV_ACTIVATE, target_vt);
		return;
	}

	/* check the target session */
	vt_read_utx(target_vt, &pid, NULL);
	if (pid  == (pid_t)-1) {
		vt_ev_request(VT_EV_ACTIVATE, target_vt);
		return;
	}

	vt_ev_request(VT_EV_AUTH, target_vt);
}

static boolean_t
vt_get_active_disp_info(struct vt_dispinfo *vd)
{
	int fd;
	struct vt_stat state;
	char vtname[16];

	if ((fd = open(VT_DAEMON_CONSOLE_FILE, O_RDONLY)) < 0)
		return (B_FALSE);

	if (ioctl(fd, VT_GETSTATE, &state) != 0) {
		(void) close(fd);
		return (B_FALSE);
	}
	(void) close(fd);

	(void) snprintf(vtname, sizeof (vtname), "/dev/vt/%d", state.v_active);
	if ((fd = open(vtname, O_RDONLY)) < 0)
		return (B_FALSE);

	if (ioctl(fd, VT_GETDISPINFO, vd) != 0) {
		(void) close(fd);
		return (B_FALSE);
	}

	(void) close(fd);
	return (B_TRUE);
}

/*
 * Xserver registers its pid into kernel to associate it with
 * its vt upon startup for each graphical display. So here we can
 * check if the pid is of the Xserver for the current active
 * display when we receive a special VT_EV_X_EXIT request from
 * a process. If the request does not come from the current
 * active Xserver, it is discarded.
 */
static boolean_t
vt_check_disp_active(pid_t x_pid)
{
	struct vt_dispinfo vd;

	if (vt_get_active_disp_info(&vd) &&
	    vd.v_pid == x_pid)
		return (B_TRUE);

	return (B_FALSE);
}

/*
 * check if the pid is of the Xserver for the current active display,
 * return true when it is, and then also return other associated
 * information with the Xserver.
 */
static boolean_t
vt_get_disp_info(pid_t x_pid, int *logged_in, int *display_num)
{
	struct vt_dispinfo vd;

	if (!vt_get_active_disp_info(&vd) ||
	    vd.v_pid != x_pid)
		return (B_FALSE);

	*logged_in = vd.v_login;
	*display_num = vd.v_dispnum;
	return (B_TRUE);
}

static void
vt_terminate_auth(void)
{
	struct timespec sleeptime;

	sleeptime.tv_sec = 0;
	sleeptime.tv_nsec = 1000000; /* 1ms */

	(void) mutex_lock(&vt_mutex);
	while (vt_auth_doing) {
		vt_ev_request(VT_EV_TERMINATE_AUTH, 0);

		if (vt_auth_doing) {
			(void) mutex_unlock(&vt_mutex);
			(void) nanosleep(&sleeptime, NULL);
			sleeptime.tv_nsec *= 2;
			(void) mutex_lock(&vt_mutex);
		}
	}
	(void) mutex_unlock(&vt_mutex);
}

static void
vt_do_hotkeys(pid_t pid, uint32_t target_vt)
{
	int		logged_in;
	int		display_num;

	if (validate_target_vt(target_vt) != 0)
		return;

	/*
	 * Maybe last switch action is being taken and the lock is ongoing,
	 * here we must reject the newly request.
	 */
	(void) mutex_lock(&vt_mutex);
	if (vt_hotkeys_pending) {
		(void) mutex_unlock(&vt_mutex);
		return;
	}

	/* cleared in vt_do_active and vt_do_auth */
	vt_hotkeys_pending = B_TRUE;
	(void) mutex_unlock(&vt_mutex);

	vt_terminate_auth();

	/* check source session for this hotkeys request */
	if (pid == 0) {
		/* ok, it comes from kernel. */
		if (vt_secure)
			vt_check_source_audit();

		/* then only need to check target session */
		vt_check_target_session(target_vt);
		return;
	}

	/*
	 * check if it comes from current active X graphical session,
	 * if not, ignore this request.
	 */
	if (!vt_get_disp_info(pid, &logged_in, &display_num)) {
		(void) mutex_lock(&vt_mutex);
		vt_hotkeys_pending = B_FALSE;
		(void) mutex_unlock(&vt_mutex);
		return;
	}

	if (logged_in && vt_secure)
		vt_ev_request(VT_EV_LOCK, display_num);

	vt_check_target_session(target_vt);
}

/*
 * The main routine for the door server that deals with secure hotkeys
 */
/* ARGSUSED */
static void
server_for_door(void *cookie, char *args, size_t alen, door_desc_t *dp,
    uint_t n_desc)
{
	ucred_t *uc = NULL;
	vt_cmd_arg_t *vtargp;

	/* LINTED E_BAD_PTR_CAST_ALIGN */
	vtargp = (vt_cmd_arg_t *)args;

	if (vtargp == NULL ||
	    alen != sizeof (vt_cmd_arg_t) ||
	    door_ucred(&uc) != 0) {
		(void) door_return(NULL, 0, NULL, 0);
		return;
	}

	switch (vtargp->vt_ev) {
	case VT_EV_X_EXIT:
		/*
		 * Xserver will issue this event requesting to switch back
		 * to previous active vt when it's exiting and the associated
		 * vt is currently active.
		 */
		if (vt_check_disp_active(ucred_getpid(uc)))
			vt_do_hotkeys(0, vtargp->vt_num);
		break;

	case VT_EV_HOTKEYS:
		if (!vt_hotkeys)	/* hotkeys are disabled? */
			break;

		vt_do_hotkeys(ucred_getpid(uc), vtargp->vt_num);
		break;

	default:
		break;
	}

	ucred_free(uc);
	(void) door_return(NULL, 0, NULL, 0);
}

static boolean_t
setup_door(void)
{
	if ((vt_door = door_create(server_for_door, NULL,
	    DOOR_UNREF | DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) < 0) {
		syslog(LOG_ERR, "door_create failed: %s", strerror(errno));
		return (B_FALSE);
	}

	(void) fdetach(vt_door_path);

	if (fattach(vt_door, vt_door_path) != 0) {
		syslog(LOG_ERR, "fattach to %s failed: %s",
		    vt_door_path, strerror(errno));
		(void) door_revoke(vt_door);
		(void) fdetach(vt_door_path);
		vt_door = -1;
		return (B_FALSE);
	}

	return (B_TRUE);
}

/*
 * check to see if vtdaemon is already running.
 *
 * The idea here is that we want to open the path to which we will
 * attach our door, lock it, and then make sure that no-one has beat us
 * to fattach(3c)ing onto it.
 *
 * fattach(3c) is really a mount, so there are actually two possible
 * vnodes we could be dealing with.  Our strategy is as follows:
 *
 * - If the file we opened is a regular file (common case):
 * 	There is no fattach(3c)ed door, so we have a chance of becoming
 * 	the running vtdaemon. We attempt to lock the file: if it is
 * 	already locked, that means someone else raced us here, so we
 * 	lose and give up.
 *
 * - If the file we opened is a namefs file:
 * 	This means there is already an established door fattach(3c)'ed
 * 	to the rendezvous path.  We've lost the race, so we give up.
 * 	Note that in this case we also try to grab the file lock, and
 * 	will succeed in acquiring it since the vnode locked by the
 * 	"winning" vtdaemon was a regular one, and the one we locked was
 * 	the fattach(3c)'ed door node.  At any rate, no harm is done.
 */
static boolean_t
make_daemon_exclusive(void)
{
	int doorfd = -1;
	boolean_t ret = B_FALSE;
	struct stat st;
	struct flock flock;

top:
	if ((doorfd = open(vt_door_path, O_CREAT|O_RDWR,
	    S_IREAD|S_IWRITE|S_IRGRP|S_IROTH)) < 0) {
		syslog(LOG_ERR, "failed to open %s", vt_door_path);
		goto out;
	}
	if (fstat(doorfd, &st) < 0) {
		syslog(LOG_ERR, "failed to stat %s", vt_door_path);
		goto out;
	}
	/*
	 * Lock the file to synchronize
	 */
	flock.l_type = F_WRLCK;
	flock.l_whence = SEEK_SET;
	flock.l_start = (off_t)0;
	flock.l_len = (off_t)0;
	if (fcntl(doorfd, F_SETLK, &flock) < 0) {
		/*
		 * Someone else raced us here and grabbed the lock file
		 * first.  A warning here and exit.
		 */
		syslog(LOG_ERR, "vtdaemon is already running!");
		goto out;
	}

	if (strcmp(st.st_fstype, "namefs") == 0) {
		struct door_info info;

		/*
		 * There is already something fattach()'ed to this file.
		 * Lets see what the door is up to.
		 */
		if (door_info(doorfd, &info) == 0 && info.di_target != -1) {
			syslog(LOG_ERR, "vtdaemon is already running!");
			goto out;
		}

		(void) fdetach(vt_door_path);
		(void) close(doorfd);
		goto top;
	}

	ret = setup_door();

out:
	(void) close(doorfd);
	return (ret);
}

static boolean_t
mkvtdir(void)
{
	struct stat st;
	/*
	 * We must create and lock everyone but root out of VT_TMPDIR
	 * since anyone can open any UNIX domain socket, regardless of
	 * its file system permissions.
	 */
	if (mkdir(VT_TMPDIR, S_IRWXU|S_IROTH|S_IXOTH|S_IRGRP|S_IXGRP) < 0 &&
	    errno != EEXIST) {
		syslog(LOG_ERR, "could not mkdir '%s'", VT_TMPDIR);
		return (B_FALSE);
	}
	/* paranoia */
	if ((stat(VT_TMPDIR, &st) < 0) || !S_ISDIR(st.st_mode)) {
		syslog(LOG_ERR, "'%s' is not a directory", VT_TMPDIR);
		return (B_FALSE);
	}
	(void) chmod(VT_TMPDIR, S_IRWXU|S_IROTH|S_IXOTH|S_IRGRP|S_IXGRP);
	return (B_TRUE);
}

int
main(int argc, char *argv[])
{
	int i;
	int opt;
	priv_set_t *privset;
	int active;

	openlog("vtdaemon", LOG_PID | LOG_CONS, 0);

	/*
	 * Check that we have all privileges.  It would be nice to pare
	 * this down, but this is at least a first cut.
	 */
	if ((privset = priv_allocset()) == NULL) {
		syslog(LOG_ERR, "priv_allocset failed");
		return (1);
	}

	if (getppriv(PRIV_EFFECTIVE, privset) != 0) {
		syslog(LOG_ERR, "getppriv failed", "getppriv");
		priv_freeset(privset);
		return (1);
	}

	if (priv_isfullset(privset) == B_FALSE) {
		syslog(LOG_ERR, "You lack sufficient privilege "
		    "to run this command (all privs required)");
		priv_freeset(privset);
		return (1);
	}
	priv_freeset(privset);

	while ((opt = getopt(argc, argv, "ksrc:")) != EOF) {
		switch (opt) {
		case 'k':
			vt_hotkeys = B_FALSE;
			break;
		case 's':
			vt_secure = B_FALSE;
			break;
		case 'c':
			vtnodecount = atoi(optarg);
			break;
		default:
			break;
		}
	}

	(void) vt_setup_signal(SIGINT, 1);

	if (!mkvtdir())
		return (1);

	if (!eventstream_init())
		return (1);

	(void) snprintf(vt_door_path, sizeof (vt_door_path),
	    VT_TMPDIR "/vtdaemon_door");

	if (!make_daemon_exclusive())
		return (1);

	/* only the main thread accepts SIGINT */
	(void) vt_setup_signal(SIGINT, 0);
	(void) sigset(SIGPIPE, SIG_IGN);
	(void) signal(SIGQUIT, SIG_IGN);
	(void) signal(SIGINT, catch);

	for (i = 0; i < 3; i++)
		(void) close(i);
	(void) setsid();

	if ((daemonfd = open(VT_DAEMON_CONSOLE_FILE, O_RDWR)) < 0) {
		return (1);
	}

	if (daemonfd != 0)
		(void) dup2(daemonfd, STDIN_FILENO);
	if (daemonfd != 1)
		(void) dup2(daemonfd, STDOUT_FILENO);

	if (vtnodecount >= 2)
		(void) ioctl(daemonfd, VT_CONFIG, vtnodecount);

	if ((vt_ah_array = calloc(vtnodecount - 1,
	    sizeof (adt_session_data_t *))) == NULL)
		return (1);

	(void) ioctl(daemonfd, VT_GETACTIVE, &active);

	if (active == 1) {
		/*
		 * This is for someone who restarts vtdaemon while vtdaemon
		 * is doing authentication on /dev/vt/1.
		 * A better way is to continue the authentication, but there
		 * are chances that the status of the target VT has changed.
		 * So we just clear the screen here.
		 */
		(void) write(daemonfd, VT_CLEAR_SCREEN_STR,
		    strlen(VT_CLEAR_SCREEN_STR));
	}

	vt_serve_events();
	/*NOTREACHED*/
}

static int
vt_audit_start(adt_session_data_t **ah, pid_t pid)
{
	ucred_t *uc;

	if (adt_start_session(ah, NULL, 0))
		return (-1);

	if ((uc = ucred_get(pid)) == NULL) {
		(void) adt_end_session(*ah);
		return (-1);
	}

	if (adt_set_from_ucred(*ah, uc, ADT_NEW)) {
		ucred_free(uc);
		(void) adt_end_session(*ah);
		return (-1);
	}

	ucred_free(uc);
	return (0);
}

/*
 * Write audit event
 */
static void
vt_audit_event(adt_session_data_t *ah, au_event_t event_id, int status)
{
	adt_event_data_t	*event;


	if ((event = adt_alloc_event(ah, event_id)) == NULL) {
		return;
	}

	(void) adt_put_event(event,
	    status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAILURE,
	    status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM + status);

	adt_free_event(event);
}

static void
vt_check_source_audit(void)
{
	int	fd;
	int	source_vt;
	int	real_vt;
	struct vt_stat state;
	pid_t	pid;
	adt_session_data_t *ah;

	if ((fd = open(VT_DAEMON_CONSOLE_FILE, O_WRONLY)) < 0)
		return;

	if (ioctl(fd, VT_GETSTATE, &state) != 0 ||
	    ioctl(fd, VT_GETACTIVE, &real_vt) != 0) {
		(void) close(fd);
		return;
	}

	source_vt = state.v_active;	/* 1..n */
	(void) close(fd);

	/* check if it's already locked */
	if (real_vt == 1)	/* vtdaemon is taking over the screen */
		return;

	vt_read_utx(source_vt, &pid, NULL);
	if (pid == (pid_t)-1)
		return;

	if (vt_audit_start(&ah, pid) != 0) {
		syslog(LOG_ERR, "audit start failed ");
		return;
	}

	/*
	 * In case the previous session terminated abnormally.
	 */
	if (vt_ah_array[source_vt - 1] != NULL)
		(void) adt_end_session(vt_ah_array[source_vt - 1]);

	vt_ah_array[source_vt - 1] = ah;

	vt_audit_event(ah, ADT_screenlock, PAM_SUCCESS);
}