diff options
Diffstat (limited to 'usr/src/cmd/vt/vtdaemon.c')
-rw-r--r-- | usr/src/cmd/vt/vtdaemon.c | 1400 |
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); +} |