/*
* 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
* 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
*/
/*
*/
/*
* vtdaemon is responsible for the session secure switch via hotkeys.
*
* vtdaemon itself, like ttymon(1M), is also running on a virtual
* 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's a "hotkeys" property (BOOLEAN) in the
* users to dynamically enable or disable VT switch via hotkeys.
* Its default value is TRUE (enabled).
*
* There's a "secure" property (BOOLEAN) in the
* 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
* 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/sysmacros.h>
#include <syslog.h>
#include <deflt.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 <assert.h>
#include <syslog.h>
#include <sys/vtdaemon.h>
/*
* The door file /var/run/vt/vtdaemon_door
*/
#define VT_DAEMON_ARG 0
/* Defaults for updating expired passwords */
int daemonfd;
/* protecting vt_hotkeys_pending and vt_auth_doing */
static int vtnodecount = 0;
static void vt_check_source_audit(void);
static int
{
(void) sigemptyset(&set);
if (mask)
else
}
static void
{
}
static void
{
return;
if (pid == 0) { /* child */
exit(0);
}
/* parent */
continue;
}
/*
* Find the login process and user logged in on the target vt.
*/
static void
{
struct utmpx *u;
"%s", "console");
else
setutxent();
/* see if this is the entry we want */
if ((u->ut_type == USER_PROCESS) &&
(!nonuserx(*u)) &&
(u->ut_host[0] == '\0') &&
sizeof (u->ut_user));
}
break;
}
endutxent();
}
static boolean_t
vt_is_tipline(void)
{
static int is_tipline = 0;
int fd;
if (is_tipline != 0)
return (is_tipline == 1);
return (B_FALSE);
is_tipline = 1;
} else {
is_tipline = -1;
}
return (is_tipline == 1);
}
static int
{
int fd;
if (target_vt < 1)
return (-1);
return (-1);
return (-1);
}
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).
*/
return (0);
} else {
return (-1);
}
}
return (0);
}
static void
{
(void) mutex_lock(&vt_mutex);
(void) mutex_unlock(&vt_mutex);
}
/* events written to fd 0 and read from fd 1 */
/* events written to fd 1 and read from fd 0 */
typedef struct vt_evt {
int ve_cmd;
} vt_evt_t;
eventstream_init(void)
{
return (B_FALSE);
return (B_TRUE);
}
void
{
}
static boolean_t
{
return (rval > 0);
}
static void
{
int channel;
}
static void
vt_clear_events(void)
{
int rval = 0;
while (rval == 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)
{
}
/*
* The SIGINT (ctl_c) will restart the authentication, and re-prompt
* the end user to input the password.
*/
static int
vt_poll()
{
int ret;
for (;;) {
continue;
}
return (-1);
(void) eventstream_read(0, &ve);
return (0);
}
return (1);
return (0);
}
}
static char
{
char c;
int cnt;
if (cnt > 0) {
return (c);
}
return (EOF);
}
static char *
{
int c;
int i = 0;
if (noecho) {
}
while ((vt_poll()) == 1) {
input[i++] = (char)c;
else
break;
}
input[i] = '\0';
if (noecho) {
}
}
/*
* vt_conv: vtdaemon PAM conversation function.
*/
/*ARGSUSED*/
static int
{
struct pam_message *m;
struct pam_response *r;
int i, k;
if (num_msg >= PAM_MAX_NUM_MSG) {
return (PAM_CONV_ERR);
}
return (PAM_BUF_ERR);
m = *msg;
r = *response;
for (i = 0; i < num_msg; i++) {
int echo_off = 0;
/* Bad message */
i, m->msg_style);
goto err;
}
/*
* Fix up final newline:
* remove from prompts, add back for messages.
*/
r->resp_retcode = 0;
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
echo_off = 1;
/* FALLTHROUGH */
case PAM_PROMPT_ECHO_ON:
break;
case PAM_ERROR_MSG:
/* the user may want to see this */
break;
case PAM_TEXT_INFO:
break;
default:
/* error, service module won't clean up */
goto err;
}
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 */
}
}
return (PAM_CONV_ERR);
}
/* Get PASSREQ from default file */
static boolean_t
vt_default(void)
{
int flags;
char *ptr;
/* ignore case */
}
return (retval);
}
/*
* VT_CLEAR_SCREEN_STR is the console terminal escape sequence used to
* the local sun-color, which is always supported by our kernel terminal
* emulator.
*/
static void
{
int err;
int pam_flag = 0;
int chpasswd_tries;
return;
&vt_pamh)) != PAM_SUCCESS)
return;
/*
* firstly switch to the vtdaemon special console
* and clear the current screen
*/
(void) mutex_lock(&vt_mutex);
(void) mutex_unlock(&vt_mutex);
/*
* Fetch audit handle.
*/
if (vt_default())
do {
"\nUnlock user %s on the system console\n",
else
"\nUnlock user %s on vt/%d\n", user_name,
(void) mutex_lock(&vt_mutex);
if (vt_hotkeys_pending) {
(void) mutex_unlock(&vt_mutex);
break;
}
(void) mutex_unlock(&vt_mutex);
if (err == PAM_SUCCESS) {
(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 {
(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) &&
(void) mutex_lock(&vt_mutex);
if (vt_hotkeys_pending) {
(void) mutex_unlock(&vt_mutex);
break;
}
(void) mutex_unlock(&vt_mutex);
}
}
/*
* Only audit failed unlock here, successful unlock
* will be audited after switching to target vt.
*/
if (err != PAM_SUCCESS) {
}
(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.
*/
/*
* Free audit handle.
*/
(void) adt_end_session(ah);
}
(void) mutex_unlock(&vt_mutex);
(void) mutex_lock(&vt_mutex);
(void) mutex_unlock(&vt_mutex);
}
/* main thread (lock and auth) */
static void __NORETURN
vt_serve_events(void)
{
int ret;
for (;;) {
continue;
}
/* new request */
case VT_EV_AUTH:
break;
case VT_EV_LOCK:
break;
case VT_EV_ACTIVATE:
/* directly activate target vt */
break;
}
}
}
}
static void
{
if (!vt_secure) {
return;
}
/* check the target session */
return;
}
}
static boolean_t
{
int fd;
return (B_FALSE);
return (B_FALSE);
}
return (B_FALSE);
return (B_FALSE);
}
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
{
if (vt_get_active_disp_info(&vd) &&
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
{
if (!vt_get_active_disp_info(&vd) ||
return (B_FALSE);
return (B_TRUE);
}
static void
vt_terminate_auth(void)
{
(void) mutex_lock(&vt_mutex);
while (vt_auth_doing) {
if (vt_auth_doing) {
(void) mutex_unlock(&vt_mutex);
(void) mutex_lock(&vt_mutex);
}
}
(void) mutex_unlock(&vt_mutex);
}
static void
{
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 */
(void) mutex_unlock(&vt_mutex);
/* check source session for this hotkeys request */
if (pid == 0) {
/* ok, it comes from kernel. */
if (vt_secure)
/* then only need to check target session */
return;
}
/*
* check if it comes from current active X graphical session,
* if not, ignore this request.
*/
(void) mutex_lock(&vt_mutex);
(void) mutex_unlock(&vt_mutex);
return;
}
}
/*
* The main routine for the door server that deals with secure hotkeys
*/
/* ARGSUSED */
static void
{
/* LINTED E_BAD_PTR_CAST_ALIGN */
alen != sizeof (vt_cmd_arg_t) ||
door_ucred(&uc) != 0) {
return;
}
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.
*/
break;
case VT_EV_HOTKEYS:
if (!vt_hotkeys) /* hotkeys are disabled? */
break;
break;
default:
break;
}
ucred_free(uc);
}
static boolean_t
setup_door(void)
{
return (B_FALSE);
}
(void) fdetach(vt_door_path);
(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)
{
top:
goto out;
}
goto out;
}
/*
* Lock the file to synchronize
*/
/*
* Someone else raced us here and grabbed the lock file
* first. A warning here and exit.
*/
goto out;
}
/*
* There is already something fattach()'ed to this file.
* Lets see what the door is up to.
*/
goto out;
}
(void) fdetach(vt_door_path);
goto top;
}
ret = setup_door();
out:
return (ret);
}
static boolean_t
mkvtdir(void)
{
/*
* 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.
*/
return (B_FALSE);
}
/* paranoia */
return (B_FALSE);
}
return (B_TRUE);
}
int
{
int i;
int opt;
int active;
/*
* Check that we have all privileges. It would be nice to pare
* this down, but this is at least a first cut.
*/
return (1);
}
return (1);
}
"to run this command (all privs required)");
return (1);
}
switch (opt) {
case 'k':
break;
case 's':
break;
case 'c':
break;
default:
break;
}
}
if (!mkvtdir())
return (1);
if (!eventstream_init())
return (1);
VT_TMPDIR "/vtdaemon_door");
if (!make_daemon_exclusive())
return (1);
/* only the main thread accepts SIGINT */
(void) vt_setup_signal(SIGINT, 0);
for (i = 0; i < 3; i++)
(void) close(i);
(void) setsid();
return (1);
}
if (daemonfd != 0)
if (daemonfd != 1)
if (vtnodecount >= 2)
sizeof (adt_session_data_t *))) == NULL)
return (1);
if (active == 1) {
/*
* This is for someone who restarts vtdaemon while vtdaemon
* 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.
*/
}
/*NOTREACHED*/
}
static int
{
return (-1);
(void) adt_end_session(*ah);
return (-1);
}
ucred_free(uc);
(void) adt_end_session(*ah);
return (-1);
}
ucred_free(uc);
return (0);
}
/*
* Write audit event
*/
static void
{
return;
}
(void) adt_put_event(event,
}
static void
vt_check_source_audit(void)
{
int fd;
int source_vt;
int real_vt;
return;
return;
}
/* check if it's already locked */
return;
return;
return;
}
/*
* In case the previous session terminated abnormally.
*/
}