blob: 92d904db319a5be05a9f4d53045b76fb8c7f1135 [file] [log] [blame]
/*
* openvt.c open a vt to run a new command (or shell).
*
* Copyright (c) 1994 by Jon Tombs <jon@gtex02.us.es>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
/*
* Added the not-in-use check, aeb@cwi.nl, 940924.
*
* [Accidentally starting a process on a VT that is in use
* yields unfortunate effects: two processes reading the keyboard.
* This can be a disaster if the old process was in scancode mode.]
*
* Added the -u (`as user') stuff for use from inittab,
* Joshua Spoerri <josh@cooper.edu>, 1996-07-18
*
* Fixed some bugs; made it a bit more robust; renamed to openvt
* aeb@cwi.nl, 1998-06-06.
* Applied patch by Chuck Martin <cmartin@bigfoot.com>, i18n, aeb, 990316.
* Applied patch by damjan@legolas (-e option), aeb, 2004-01-03.
*/
#include "openvt.h"
#include "nls.h"
const char *version = "openvt 1.4b - (c) Jon Tombs 1994";
#ifndef VTNAME
#error vt device name must be defined in openvt.h
#endif
int
main(int argc, char *argv[])
{
int opt, pid;
struct vt_stat vtstat;
int vtno = -1;
int fd = -1;
int consfd = -1;
char optc = FALSE;
char show = FALSE;
char login = FALSE;
char force = FALSE;
char verbose = FALSE;
char direct_exec = FALSE;
char do_wait = FALSE;
char as_user = FALSE;
char vtname[sizeof VTNAME + 2]; /* allow 999 possible VTs */
char *cmd = NULL, *def_cmd = NULL, *username = NULL;
/*
* I don't like using getopt for this, but otherwise this gets messy.
* POSIX/Gnu getopt forces the use of -- to separate child/program
* options. RTFM.
*/
while ((opt = getopt(argc, argv, "c:lsvfuew")) != -1) {
switch (opt) {
case 'c':
optc = 1; /* vtno was specified by the user */
vtno = (int) atol(optarg);
if (vtno <= 0 || vtno > 63) {
fprintf(stderr, _("openvt: %s: illegal vt number\n"), optarg);
return 5;
}
/* close security holes - until we can do this safely */
(void) setuid(getuid());
break;
case 'l':
login = TRUE;
break;
case 's':
show = TRUE;
break;
case 'v':
verbose = TRUE;
break;
case 'f':
force = TRUE;
break;
case 'e':
direct_exec = TRUE;
break;
case 'w':
do_wait = TRUE;
break;
case 'u':
/* we'll let 'em get away with the meaningless -ul combo */
if(getuid()) {
fprintf(stderr, _("openvt: only root can use the -u flag.\n"));
exit(1);
}
as_user = TRUE;
break;
default:
usage(1);
}
}
consfd = getfd();
if (consfd < 0) {
fprintf(stderr,
_("Couldnt get a file descriptor referring to the console\n"));
return(2);
}
if (ioctl(consfd, VT_GETSTATE, &vtstat) < 0) {
perror("openvt: VT_GETSTATE");
return(4);
}
if (vtno == -1) {
if ((ioctl(consfd, VT_OPENQRY, &vtno) < 0) || (vtno == -1)) {
perror("openvt: VT_OPENQRY");
fprintf(stderr, _("openvt: cannot find a free vt\n"));
return(3);
}
} else if (!force) {
if (vtno >= 16) {
fprintf(stderr, _("openvt: cannot check whether vt %d is free\n"),
vtno);
fprintf(stderr, _(" use `openvt -f' to force.\n"));
return(7);
}
if (vtstat.v_state & (1 << vtno)) {
fprintf(stderr, _("openvt: vt %d is in use; command aborted\n"), vtno);
fprintf(stderr, _(" use `openvt -f' to force.\n"));
return(7);
}
}
sprintf(vtname, VTNAME, vtno);
/* Can we open the vt we want? */
if ((fd = open(vtname, O_RDWR)) == -1) {
int errsv = errno;
if (!optc) {
/* We found vtno ourselves - it is free according
to the kernel, but we cannot open it. Maybe X
used it and did a chown. Try a few vt's more
before giving up. Note: the 16 is a kernel limitation. */
int i;
for (i=vtno+1; i<16; i++) {
if((vtstat.v_state & (1<<i)) == 0) {
sprintf(vtname, VTNAME, i);
if ((fd = open(vtname, O_RDWR)) >= 0) {
vtno = i;
goto got_vtno;
}
}
}
sprintf(vtname, VTNAME, vtno);
}
fprintf(stderr, _("openvt: Unable to open %s: %s\n"),
vtname, strerror(errsv));
return(5);
}
got_vtno:
close(fd);
/* Maybe we are suid root, and the -c option was given.
Check that the real user can access this VT.
We assume getty has made any in use VT non accessable */
if (access(vtname, R_OK | W_OK) < 0) {
int errsv = errno;
fprintf(stderr, _("openvt: Cannot open %s read/write (%s)\n"),
vtname, strerror(errsv));
return (5);
}
if (as_user)
username = authenticate_user(vtstat.v_active);
else {
if (!geteuid()) {
uid_t uid = getuid();
chown(vtname, uid, getgid());
setuid(uid);
}
if (!(argc > optind)) {
def_cmd = getenv("SHELL");
if (def_cmd == NULL)
usage(0);
cmd = malloc(strlen(def_cmd + 2));
} else {
cmd = malloc(strlen(argv[optind] + 2));
}
if (login)
strcpy(cmd, "-");
else
cmd[0] = '\0';
if (def_cmd)
strcat(cmd, def_cmd);
else
strcat(cmd, argv[optind]);
if (login)
argv[optind] = cmd++;
}
if (verbose)
fprintf(stderr, _("openvt: using VT %s\n"), vtname);
fflush(stderr);
if (direct_exec || ((pid = fork()) == 0)) {
/* leave current vt */
if (!direct_exec) {
#ifdef ESIX_5_3_2_D
if (setpgrp() < 0) {
#else
if (setsid() < 0) {
#endif
int errsv = errno;
fprintf(stderr, _("openvt: Unable to set new session (%s)\n"),
strerror(errsv));
}
}
close(0); /* so that new vt becomes stdin */
/* and grab new one */
if ((fd = open(vtname, O_RDWR)) == -1) { /* strange ... */
int errsv = errno;
fprintf(stderr, _("\nopenvt: could not open %s R/W (%s)\n"),
vtname, strerror(errsv));
fflush(stderr);
_exit (1); /* maybe above user limit? */
}
if (show) {
if (ioctl(fd, VT_ACTIVATE, vtno)) {
int errsv = errno;
fprintf(stderr, "\nopenvt: could not activate vt %d (%s)\n",
vtno, strerror(errsv));
fflush(stderr);
_exit (1); /* probably fd does not refer to a tty device file */
}
if (ioctl(fd, VT_WAITACTIVE, vtno)){
int errsv = errno;
fprintf(stderr, "\nopenvt: activation interrupted? (%s)\n",
strerror(errsv));
fflush(stderr);
_exit (1);
}
}
close(1);
close(2);
close(consfd);
dup(fd);
dup(fd);
/* slight problem: after "openvt -su" has finished, the
utmp entry is not removed */
if(as_user)
execlp("login", "login", "-f", username, NULL);
else if (def_cmd)
execlp(cmd, def_cmd, NULL);
else
execvp(cmd, &argv[optind]);
_exit(127); /* exec failed */
}
if ( pid < 0 ) {
perror("openvt: fork() error");
return(6);
}
if ( do_wait ) {
wait(NULL);
if (show) { /* Switch back... */
if (ioctl(consfd, VT_ACTIVATE, vtstat.v_active)) {
perror("VT_ACTIVATE");
return 8;
}
/* wait to be really sure we have switched */
if (ioctl(consfd, VT_WAITACTIVE, vtstat.v_active)) {
perror("VT_WAITACTIVE");
return 8;
}
if (ioctl(consfd, VT_DISALLOCATE, vtno)) {
fprintf(stderr, _("openvt: could not deallocate console %d\n"),
vtno);
return(8);
}
}
}
return 0;
}
void usage(int stat)
{
fprintf(stderr,
"Usage: openvt [-c vtnumber] [-l] [-u] [-s] [-v] [-w] -- command_line\n");
exit (stat);
}
/*
* Support for Spawn_Console: openvt running from init
* added by Joshua Spoerri, Thu Jul 18 21:13:16 EDT 1996
*
* -u Figure out the owner of the current VT, and run
* login as that user. Suitable to be called by init.
* Shouldn't be used with -c or -l.
* Sample inittab line:
* kb::kbrequest:/usr/bin/openvt -us
*
* It is the job of authenticate_user() to find out who
* produced this keyboard signal. It is called only as root.
*
* Note that there is a race condition: curvt may not be the vt
* from which the keyboard signal was produced.
* (Possibly the signal was not produced at the keyboard at all,
* but by a "kill -SIG 1". However, only root can do this.)
*
* Conclusion: do not use this in high security environments.
* Or fix the code below to be more suspicious.
*
* Maybe it is better to just start a login at the new vt,
* instead of pre-authenticating the user with "login -f".
*/
char *
authenticate_user(int curvt) {
DIR *dp;
struct dirent *dentp;
struct stat buf;
dev_t console_dev;
ino_t console_ino;
uid_t console_uid;
char filename[NAME_MAX+12];
struct passwd *pwnam;
if (!(dp=opendir("/proc"))) {
perror("/proc");
exit(1);
}
/* get the current tty */
/* try /dev/ttyN, then /dev/vc/N */
sprintf(filename, VTNAME, curvt);
if (stat(filename,&buf)) {
int errsv = errno;
sprintf(filename, VTNAME2, curvt);
if (stat(filename,&buf)) {
/* give error message for first attempt */
sprintf(filename, VTNAME, curvt);
errno = errsv;
perror(filename);
exit(1);
}
}
console_dev=buf.st_dev;
console_ino=buf.st_ino;
console_uid=buf.st_uid;
/* get the owner of current tty */
if (!(pwnam = getpwuid(console_uid))) {
perror("can't getpwuid");
exit(1);
}
/* check to make sure that user has a process on that tty */
/* this will fail for example when X is running on the tty */
while ((dentp=readdir(dp))) {
sprintf(filename,"/proc/%s/fd/0",dentp->d_name);
if (stat(filename,&buf))
continue;
if(buf.st_dev == console_dev && buf.st_ino == console_ino
&& buf.st_uid == console_uid)
goto got_a_process;
}
fprintf(stderr,"couldn't find owner of current tty!\n");
exit(1);
got_a_process:
return pwnam->pw_name;
}