Blame src/openvt.c

Packit Service 50ad14
#include "config.h"
Packit Service 50ad14
Packit Service 50ad14
#include <fcntl.h>
Packit Service 50ad14
#include <limits.h>
Packit Service 50ad14
#include <stdio.h>
Packit Service 50ad14
#include <stdarg.h>
Packit Service 50ad14
#include <unistd.h>
Packit Service 50ad14
#include <getopt.h>
Packit Service 50ad14
#include <dirent.h>
Packit Service 50ad14
#include <pwd.h>
Packit Service 50ad14
#include <errno.h>
Packit Service 50ad14
#include <sys/ioctl.h>
Packit Service 50ad14
#include <sys/stat.h>
Packit Service 50ad14
#include <sys/vt.h>
Packit Service 50ad14
#include <sys/wait.h>
Packit Service 50ad14
#include <sys/file.h>
Packit Service 50ad14
Packit Service 50ad14
#include "version.h"
Packit Service 50ad14
#include "kbd_error.h"
Packit Service 50ad14
#include "xmalloc.h"
Packit Service 50ad14
#include "getfd.h"
Packit Service 50ad14
Packit Service 50ad14
#ifdef COMPAT_HEADERS
Packit Service 50ad14
#include "compat/linux-limits.h"
Packit Service 50ad14
#endif
Packit Service 50ad14
Packit Service 50ad14
// There must be a universal way to find these!
Packit Service 50ad14
#define TRUE (1)
Packit Service 50ad14
#define FALSE (0)
Packit Service 50ad14
Packit Service 50ad14
#ifdef ESIX_5_3_2_D
Packit Service 50ad14
#define VTBASE "/dev/vt%02d"
Packit Service 50ad14
#endif
Packit Service 50ad14
Packit Service 50ad14
// Where your VTs are hidden
Packit Service 50ad14
#ifdef __linux__
Packit Service 50ad14
#define VTNAME "/dev/tty%d"
Packit Service 50ad14
#define VTNAME2 "/dev/vc/%d"
Packit Service 50ad14
#endif
Packit Service 50ad14
Packit Service 50ad14
#ifndef VTNAME
Packit Service 50ad14
#error vt device name must be defined
Packit Service 50ad14
#endif
Packit Service 50ad14
Packit Service 50ad14
static void
Packit Service 50ad14
    __attribute__((noreturn))
Packit Service 50ad14
    print_help(int ret)
Packit Service 50ad14
{
Packit Service 50ad14
	printf(_("Usage: %s [OPTIONS] -- command\n"
Packit Service 50ad14
	         "\n"
Packit Service 50ad14
	         "This utility help you to start a program on a new virtual terminal (VT).\n"
Packit Service 50ad14
	         "\n"
Packit Service 50ad14
	         "Options:\n"
Packit Service 50ad14
	         "  -c, --console=NUM   use the given VT number;\n"
Packit Service 50ad14
	         "  -e, --exec          execute the command, without forking;\n"
Packit Service 50ad14
	         "  -f, --force         force opening a VT without checking;\n"
Packit Service 50ad14
	         "  -l, --login         make the command a login shell;\n"
Packit Service 50ad14
	         "  -u, --user          figure out the owner of the current VT;\n"
Packit Service 50ad14
	         "  -s, --switch        switch to the new VT;\n"
Packit Service 50ad14
	         "  -w, --wait          wait for command to complete;\n"
Packit Service 50ad14
	         "  -v, --verbose       print a message for each action;\n"
Packit Service 50ad14
	         "  -V, --version       print program version and exit;\n"
Packit Service 50ad14
	         "  -h, --help          output a brief help message.\n"
Packit Service 50ad14
	         "\n"),
Packit Service 50ad14
	       progname);
Packit Service 50ad14
	exit(ret);
Packit Service 50ad14
}
Packit Service 50ad14
Packit Service 50ad14
/*
Packit Service 50ad14
 * Support for Spawn_Console: openvt running from init
Packit Service 50ad14
 * added by Joshua Spoerri, Thu Jul 18 21:13:16 EDT 1996
Packit Service 50ad14
 *
Packit Service 50ad14
 *  -u     Figure  out  the  owner  of the current VT, and run
Packit Service 50ad14
 *         login as that user.  Suitable to be called by init.
Packit Service 50ad14
 *         Shouldn't be used with -c or -l.
Packit Service 50ad14
 *         Sample inittab line:
Packit Service 50ad14
 *                kb::kbrequest:/usr/bin/openvt -us
Packit Service 50ad14
 *
Packit Service 50ad14
 * It is the job of authenticate_user() to find out who
Packit Service 50ad14
 * produced this keyboard signal.  It is called only as root.
Packit Service 50ad14
 *
Packit Service 50ad14
 * Note that there is a race condition: curvt may not be the vt
Packit Service 50ad14
 * from which the keyboard signal was produced.
Packit Service 50ad14
 * (Possibly the signal was not produced at the keyboard at all,
Packit Service 50ad14
 * but by a "kill -SIG 1".  However, only root can do this.)
Packit Service 50ad14
 *
Packit Service 50ad14
 * Conclusion: do not use this in high security environments.
Packit Service 50ad14
 * Or fix the code below to be more suspicious.
Packit Service 50ad14
 *
Packit Service 50ad14
 * Maybe it is better to just start a login at the new vt,
Packit Service 50ad14
 * instead of pre-authenticating the user with "login -f".
Packit Service 50ad14
 */
Packit Service 50ad14
Packit Service 50ad14
static char *
Packit Service 50ad14
authenticate_user(int curvt)
Packit Service 50ad14
{
Packit Service 50ad14
	DIR *dp;
Packit Service 50ad14
	struct dirent *dentp;
Packit Service 50ad14
	struct stat buf;
Packit Service 50ad14
	dev_t console_dev;
Packit Service 50ad14
	ino_t console_ino;
Packit Service 50ad14
	uid_t console_uid;
Packit Service 50ad14
	char filename[NAME_MAX + 12];
Packit Service 50ad14
	struct passwd *pwnam;
Packit Service 50ad14
Packit Service 50ad14
	if (!(dp = opendir("/proc")))
Packit Service 50ad14
		kbd_error(EXIT_FAILURE, errno, "opendir(/proc)");
Packit Service 50ad14
Packit Service 50ad14
	/* get the current tty */
Packit Service 50ad14
	/* try /dev/ttyN, then /dev/vc/N */
Packit Service 50ad14
	sprintf(filename, VTNAME, curvt);
Packit Service 50ad14
	if (stat(filename, &buf)) {
Packit Service 50ad14
		int errsv = errno;
Packit Service 50ad14
		sprintf(filename, VTNAME2, curvt);
Packit Service 50ad14
		if (stat(filename, &buf)) {
Packit Service 50ad14
			/* give error message for first attempt */
Packit Service 50ad14
			sprintf(filename, VTNAME, curvt);
Packit Service 50ad14
			kbd_error(EXIT_FAILURE, errsv, "%s", filename);
Packit Service 50ad14
		}
Packit Service 50ad14
	}
Packit Service 50ad14
	console_dev = buf.st_dev;
Packit Service 50ad14
	console_ino = buf.st_ino;
Packit Service 50ad14
	console_uid = buf.st_uid;
Packit Service 50ad14
Packit Service 50ad14
	/* get the owner of current tty */
Packit Service 50ad14
	if (!(pwnam = getpwuid(console_uid)))
Packit Service 50ad14
		kbd_error(EXIT_FAILURE, errno, "getpwuid");
Packit Service 50ad14
Packit Service 50ad14
	/* check to make sure that user has a process on that tty */
Packit Service 50ad14
	/* this will fail for example when X is running on the tty */
Packit Service 50ad14
	while ((dentp = readdir(dp))) {
Packit Service 50ad14
		sprintf(filename, "/proc/%s/fd/0", dentp->d_name);
Packit Service 50ad14
Packit Service 50ad14
		if (stat(filename, &buf))
Packit Service 50ad14
			continue;
Packit Service 50ad14
Packit Service 50ad14
		if (buf.st_dev == console_dev && buf.st_ino == console_ino && buf.st_uid == console_uid)
Packit Service 50ad14
			goto got_a_process;
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	kbd_error(EXIT_FAILURE, 0, _("Couldn't find owner of current tty!"));
Packit Service 50ad14
Packit Service 50ad14
got_a_process:
Packit Service 50ad14
	closedir(dp);
Packit Service 50ad14
Packit Service 50ad14
	return pwnam->pw_name;
Packit Service 50ad14
}
Packit Service 50ad14
Packit Service 50ad14
static int
Packit Service 50ad14
open_vt(char *vtname, int force)
Packit Service 50ad14
{
Packit Service 50ad14
	int fd;
Packit Service 50ad14
Packit Service 50ad14
	if ((fd = open(vtname, O_RDWR)) == -1)
Packit Service 50ad14
		return -1;
Packit Service 50ad14
Packit Service 50ad14
	if (ioctl(fd, TIOCSCTTY, force) == -1) {
Packit Service 50ad14
		close(fd);
Packit Service 50ad14
		return -1;
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	return fd;
Packit Service 50ad14
}
Packit Service 50ad14
Packit Service 50ad14
int main(int argc, char *argv[])
Packit Service 50ad14
{
Packit Service 50ad14
	int opt, pid, i;
Packit Service 50ad14
	struct vt_stat vtstat;
Packit Service 50ad14
	int vtno         = -1;
Packit Service 50ad14
	int fd           = -1;
Packit Service 50ad14
	int consfd       = -1;
Packit Service 50ad14
	int force        = 0;
Packit Service 50ad14
	char optc        = FALSE;
Packit Service 50ad14
	char show        = FALSE;
Packit Service 50ad14
	char login       = FALSE;
Packit Service 50ad14
	char verbose     = FALSE;
Packit Service 50ad14
	char direct_exec = FALSE;
Packit Service 50ad14
	char do_wait     = FALSE;
Packit Service 50ad14
	char as_user     = FALSE;
Packit Service 50ad14
	char vtname[sizeof(VTNAME) + 2]; /* allow 999 possible VTs */
Packit Service 50ad14
	char *cmd = NULL, *def_cmd = NULL, *username = NULL;
Packit Service 50ad14
Packit Service 50ad14
	struct option long_options[] = {
Packit Service 50ad14
		{ "help", no_argument, 0, 'h' },
Packit Service 50ad14
		{ "version", no_argument, 0, 'V' },
Packit Service 50ad14
		{ "verbose", no_argument, 0, 'v' },
Packit Service 50ad14
		{ "exec", no_argument, 0, 'e' },
Packit Service 50ad14
		{ "force", no_argument, 0, 'f' },
Packit Service 50ad14
		{ "login", no_argument, 0, 'l' },
Packit Service 50ad14
		{ "user", no_argument, 0, 'u' },
Packit Service 50ad14
		{ "switch", no_argument, 0, 's' },
Packit Service 50ad14
		{ "wait", no_argument, 0, 'w' },
Packit Service 50ad14
		{ "console", required_argument, 0, 'c' },
Packit Service 50ad14
		{ 0, 0, 0, 0 }
Packit Service 50ad14
	};
Packit Service 50ad14
Packit Service 50ad14
	set_progname(argv[0]);
Packit Service 50ad14
Packit Service 50ad14
	setlocale(LC_ALL, "");
Packit Service 50ad14
	bindtextdomain(PACKAGE_NAME, LOCALEDIR);
Packit Service 50ad14
	textdomain(PACKAGE_NAME);
Packit Service 50ad14
Packit Service 50ad14
	while ((opt = getopt_long(argc, argv, "c:lsfuewhvV", long_options, NULL)) != -1) {
Packit Service 50ad14
		switch (opt) {
Packit Service 50ad14
			case 'c':
Packit Service 50ad14
				optc = 1; /* vtno was specified by the user */
Packit Service 50ad14
				vtno = (int)atol(optarg);
Packit Service 50ad14
Packit Service 50ad14
				if (vtno <= 0 || vtno > 63)
Packit Service 50ad14
					kbd_error(5, 0, _("%s: Illegal vt number"), optarg);
Packit Service 50ad14
Packit Service 50ad14
				/* close security holes - until we can do this safely */
Packit Service 50ad14
				(void)setuid(getuid());
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'l':
Packit Service 50ad14
				login = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 's':
Packit Service 50ad14
				show = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'v':
Packit Service 50ad14
				verbose = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'f':
Packit Service 50ad14
				force = 1;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'e':
Packit Service 50ad14
				direct_exec = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'w':
Packit Service 50ad14
				do_wait = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'u':
Packit Service 50ad14
				/* we'll let 'em get away with the meaningless -ul combo */
Packit Service 50ad14
				if (getuid())
Packit Service 50ad14
					kbd_error(EXIT_FAILURE, 0, _("Only root can use the -u flag."));
Packit Service 50ad14
Packit Service 50ad14
				as_user = TRUE;
Packit Service 50ad14
				break;
Packit Service 50ad14
			case 'V':
Packit Service 50ad14
				print_version_and_exit();
Packit Service 50ad14
				break;
Packit Service 50ad14
			default:
Packit Service 50ad14
			case 'h':
Packit Service 50ad14
				print_help(EXIT_SUCCESS);
Packit Service 50ad14
				break;
Packit Service 50ad14
		}
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	for (i = 0; i < 3; i++) {
Packit Service 50ad14
		struct stat st;
Packit Service 50ad14
Packit Service 50ad14
		if (fstat(i, &st) == -1 && open("/dev/null", O_RDWR) == -1)
Packit Service 50ad14
			kbd_error(EXIT_FAILURE, errno, "open(/dev/null)");
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	if ((consfd = getfd(NULL)) < 0)
Packit Service 50ad14
		kbd_error(2, 0, _("Couldn't get a file descriptor referring to the console"));
Packit Service 50ad14
Packit Service 50ad14
	if (ioctl(consfd, VT_GETSTATE, &vtstat) < 0)
Packit Service 50ad14
		kbd_error(4, errno, "ioctl(VT_GETSTATE)");
Packit Service 50ad14
Packit Service 50ad14
	if (vtno == -1) {
Packit Service 50ad14
		if (ioctl(consfd, VT_OPENQRY, &vtno) < 0 || vtno == -1)
Packit Service 50ad14
			kbd_error(3, errno, _("Cannot find a free vt"));
Packit Service 50ad14
Packit Service 50ad14
	} else if (!force) {
Packit Service 50ad14
		if (vtno >= 16)
Packit Service 50ad14
			kbd_error(7, 0, _("Cannot check whether vt %d is free; use `%s -f' to force."),
Packit Service 50ad14
			          vtno, progname);
Packit Service 50ad14
Packit Service 50ad14
		if (vtstat.v_state & (1 << vtno))
Packit Service 50ad14
			kbd_error(7, 0, _("vt %d is in use; command aborted; use `%s -f' to force."),
Packit Service 50ad14
			          vtno, progname);
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	if (as_user)
Packit Service 50ad14
		username = authenticate_user(vtstat.v_active);
Packit Service 50ad14
	else {
Packit Service 50ad14
		if (!(argc > optind)) {
Packit Service 50ad14
			def_cmd = getenv("SHELL");
Packit Service 50ad14
			if (def_cmd == NULL)
Packit Service 50ad14
				kbd_error(7, 0, _("Unable to find command."));
Packit Service 50ad14
			cmd = xmalloc(strlen(def_cmd) + 2);
Packit Service 50ad14
		} else {
Packit Service 50ad14
			cmd = xmalloc(strlen(argv[optind]) + 2);
Packit Service 50ad14
		}
Packit Service 50ad14
Packit Service 50ad14
		if (login)
Packit Service 50ad14
			strcpy(cmd, "-");
Packit Service 50ad14
		else
Packit Service 50ad14
			cmd[0] = '\0';
Packit Service 50ad14
Packit Service 50ad14
		if (def_cmd)
Packit Service 50ad14
			strcat(cmd, def_cmd);
Packit Service 50ad14
		else
Packit Service 50ad14
			strcat(cmd, argv[optind]);
Packit Service 50ad14
Packit Service 50ad14
		if (login)
Packit Service 50ad14
			argv[optind] = cmd++;
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	if (direct_exec || ((pid = fork()) == 0)) {
Packit Service 50ad14
		/* leave current vt */
Packit Service 50ad14
		if (!direct_exec) {
Packit Service 50ad14
#ifdef ESIX_5_3_2_D
Packit Service 50ad14
#ifdef HAVE_SETPGRP
Packit Service 50ad14
			if (setpgrp() < 0)
Packit Service 50ad14
#else
Packit Service 50ad14
			if (1)
Packit Service 50ad14
#endif /* HAVE_SETPGRP */
Packit Service 50ad14
#else
Packit Service 50ad14
			if (setsid() < 0)
Packit Service 50ad14
#endif /* ESIX_5_3_2_D */
Packit Service 50ad14
				kbd_error(5, errno, _("Unable to set new session"));
Packit Service 50ad14
		}
Packit Service 50ad14
Packit Service 50ad14
		sprintf(vtname, VTNAME, vtno);
Packit Service 50ad14
Packit Service 50ad14
		/* Can we open the vt we want? */
Packit Service 50ad14
		if ((fd = open_vt(vtname, force)) == -1) {
Packit Service 50ad14
			int errsv = errno;
Packit Service 50ad14
			if (!optc) {
Packit Service 50ad14
				/* We found vtno ourselves - it is free according
Packit Service 50ad14
				   to the kernel, but we cannot open it. Maybe X
Packit Service 50ad14
				   used it and did a chown.  Try a few vt's more
Packit Service 50ad14
				   before giving up. Note: the 16 is a kernel limitation. */
Packit Service 50ad14
				for (i = vtno + 1; i < 16; i++) {
Packit Service 50ad14
					if ((vtstat.v_state & (1 << i)) == 0) {
Packit Service 50ad14
						sprintf(vtname, VTNAME, i);
Packit Service 50ad14
						if ((fd = open_vt(vtname, force)) >= 0) {
Packit Service 50ad14
							vtno = i;
Packit Service 50ad14
							goto got_vtno;
Packit Service 50ad14
						}
Packit Service 50ad14
					}
Packit Service 50ad14
				}
Packit Service 50ad14
				sprintf(vtname, VTNAME, vtno);
Packit Service 50ad14
			}
Packit Service 50ad14
			kbd_error(5, errsv, _("Unable to open %s"), vtname);
Packit Service 50ad14
		}
Packit Service 50ad14
	got_vtno:
Packit Service 50ad14
		if (verbose)
Packit Service 50ad14
			kbd_warning(0, _("Using VT %s"), vtname);
Packit Service 50ad14
Packit Service 50ad14
		/* Maybe we are suid root, and the -c option was given.
Packit Service 50ad14
		   Check that the real user can access this VT.
Packit Service 50ad14
		   We assume getty has made any in use VT non accessable */
Packit Service 50ad14
		if (access(vtname, R_OK | W_OK) < 0)
Packit Service 50ad14
			kbd_error(5, errno, _("Cannot open %s read/write"), vtname);
Packit Service 50ad14
Packit Service 50ad14
		if (!as_user && !geteuid()) {
Packit Service 50ad14
			uid_t uid = getuid();
Packit Service 50ad14
			if (chown(vtname, uid, getgid()) == -1)
Packit Service 50ad14
				kbd_error(5, errno, "chown");
Packit Service 50ad14
			setuid(uid);
Packit Service 50ad14
		}
Packit Service 50ad14
Packit Service 50ad14
		if (show) {
Packit Service 50ad14
			if (ioctl(fd, VT_ACTIVATE, vtno))
Packit Service 50ad14
				kbd_error(1, errno, _("Couldn't activate vt %d"), vtno);
Packit Service 50ad14
Packit Service 50ad14
			if (ioctl(fd, VT_WAITACTIVE, vtno))
Packit Service 50ad14
				kbd_error(1, errno, _("Activation interrupted?"));
Packit Service 50ad14
		}
Packit Service 50ad14
		close(0);
Packit Service 50ad14
		close(1);
Packit Service 50ad14
		close(2);
Packit Service 50ad14
		close(consfd);
Packit Service 50ad14
Packit Service 50ad14
		if ((dup2(fd, 0) == -1) || (dup2(fd, 1) == -1) || (dup2(fd, 2) == -1))
Packit Service 50ad14
			kbd_error(1, errno, "dup");
Packit Service 50ad14
Packit Service 50ad14
		/* slight problem: after "openvt -su" has finished, the
Packit Service 50ad14
		   utmp entry is not removed */
Packit Service 50ad14
		if (as_user)
Packit Service 50ad14
			execlp("login", "login", "-f", username, NULL);
Packit Service 50ad14
		else if (def_cmd)
Packit Service 50ad14
			execlp(cmd, def_cmd, NULL);
Packit Service 50ad14
		else
Packit Service 50ad14
			execvp(cmd, &argv[optind]);
Packit Service 50ad14
Packit Service 50ad14
		kbd_error(127, errno, "exec");
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	if (pid < 0)
Packit Service 50ad14
		kbd_error(6, errno, "fork");
Packit Service 50ad14
Packit Service 50ad14
	if (do_wait) {
Packit Service 50ad14
		int retval = 0; /* actual value returned form process */
Packit Service 50ad14
Packit Service 50ad14
		wait(NULL);
Packit Service 50ad14
		waitpid(pid, &retval, 0);
Packit Service 50ad14
Packit Service 50ad14
		if (show) { /* Switch back... */
Packit Service 50ad14
			if (ioctl(consfd, VT_ACTIVATE, vtstat.v_active))
Packit Service 50ad14
				kbd_error(8, errno, "ioctl(VT_ACTIVATE)");
Packit Service 50ad14
Packit Service 50ad14
			/* wait to be really sure we have switched */
Packit Service 50ad14
			if (ioctl(consfd, VT_WAITACTIVE, vtstat.v_active))
Packit Service 50ad14
				kbd_error(8, errno, "ioctl(VT_WAITACTIVE)");
Packit Service 50ad14
Packit Service 50ad14
			if (ioctl(consfd, VT_DISALLOCATE, vtno))
Packit Service 50ad14
				kbd_error(8, 0, _("Couldn't deallocate console %d"), vtno);
Packit Service 50ad14
		}
Packit Service 50ad14
Packit Service 50ad14
		/* if all our stuff went right, we want to return the exit code of the command we ran
Packit Service 50ad14
		   super vital for scripting loops etc */
Packit Service 50ad14
		return (retval);
Packit Service 50ad14
	}
Packit Service 50ad14
Packit Service 50ad14
	return EXIT_SUCCESS;
Packit Service 50ad14
}