summaryrefslogtreecommitdiff
path: root/usr/src/cmd/vt/vtdaemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/vt/vtdaemon.c')
-rw-r--r--usr/src/cmd/vt/vtdaemon.c1400
1 files changed, 1400 insertions, 0 deletions
diff --git a/usr/src/cmd/vt/vtdaemon.c b/usr/src/cmd/vt/vtdaemon.c
new file mode 100644
index 0000000000..08d100594f
--- /dev/null
+++ b/usr/src/cmd/vt/vtdaemon.c
@@ -0,0 +1,1400 @@
+/*
+ * 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
+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);
+}