| #include "config.h" |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <signal.h> |
| #include <time.h> |
| #include <dirent.h> |
| #include <pwd.h> |
| #include <errno.h> |
| #include <sysexits.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/vt.h> |
| #include <sys/wait.h> |
| #include <sys/file.h> |
| |
| #include "libcommon.h" |
| |
| #ifdef COMPAT_HEADERS |
| #include "compat/linux-limits.h" |
| #endif |
| |
| // There must be a universal way to find these! |
| #define TRUE (1) |
| #define FALSE (0) |
| |
| #ifdef ESIX_5_3_2_D |
| #define VTBASE "/dev/vt%02d" |
| #endif |
| |
| // Where your VTs are hidden |
| #ifdef __linux__ |
| #define VTNAME "/dev/tty%d" |
| #define VTNAME2 "/dev/vc/%d" |
| #endif |
| |
| #ifndef VTNAME |
| #error vt device name must be defined |
| #endif |
| |
| static void KBD_ATTR_NORETURN |
| usage(int rc, const struct kbd_help *options) |
| { |
| fprintf(stderr, _("Usage: %s [option...] -- command\n"), program_invocation_short_name); |
| fprintf(stderr, "\n"); |
| fprintf(stderr, _("This utility helps you to start a program on a new virtual terminal (VT).\n")); |
| |
| print_options(options); |
| print_report_bugs(); |
| |
| exit(rc); |
| } |
| |
| /* |
| * 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". |
| */ |
| |
| static 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"))) |
| kbd_error(EXIT_FAILURE, errno, "opendir(/proc)"); |
| |
| /* get the current tty */ |
| /* try /dev/ttyN, then /dev/vc/N */ |
| snprintf(filename, sizeof(filename), VTNAME, curvt); |
| if (stat(filename, &buf)) { |
| int errsv = errno; |
| snprintf(filename, sizeof(filename), VTNAME2, curvt); |
| if (stat(filename, &buf)) { |
| /* give error message for first attempt */ |
| snprintf(filename, sizeof(filename), VTNAME, curvt); |
| kbd_error(EXIT_FAILURE, errsv, "%s", filename); |
| } |
| } |
| 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))) |
| kbd_error(EXIT_FAILURE, errno, "getpwuid"); |
| |
| /* 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))) { |
| snprintf(filename, sizeof(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; |
| } |
| |
| kbd_error(EXIT_FAILURE, 0, _("Couldn't find owner of current tty!")); |
| |
| got_a_process: |
| closedir(dp); |
| |
| return pwnam->pw_name; |
| } |
| |
| static int |
| open_vt(char *vtname, int force) |
| { |
| int fd; |
| |
| if ((fd = open(vtname, O_RDWR)) == -1) |
| return -1; |
| |
| if (ioctl(fd, TIOCSCTTY, force) == -1) { |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static void |
| sighandler(int sig KBD_ATTR_UNUSED, siginfo_t *si KBD_ATTR_UNUSED, void *uc KBD_ATTR_UNUSED) |
| { |
| return; |
| } |
| |
| static int |
| change_vt(int fd, int vtno) |
| { |
| int rc = -1; |
| timer_t timerid = NULL; |
| struct sigaction sa; |
| struct sigevent sev; |
| struct itimerspec its; |
| |
| sa.sa_flags = SA_SIGINFO; |
| sa.sa_sigaction = sighandler; |
| sigemptyset(&sa.sa_mask); |
| |
| if (sigaction(SIGALRM, &sa, NULL) < 0) { |
| kbd_warning(errno, _("Unable to set signal handler")); |
| return rc; |
| } |
| |
| sev.sigev_notify = SIGEV_SIGNAL; |
| sev.sigev_signo = SIGALRM; |
| sev.sigev_value.sival_ptr = &timerid; |
| |
| if (timer_create(CLOCK_REALTIME, &sev, &timerid) < 0) { |
| kbd_warning(errno, _("Unable to create timer")); |
| goto fail; |
| } |
| |
| its.it_value.tv_sec = 1; |
| its.it_value.tv_nsec = 0; |
| its.it_interval.tv_sec = its.it_value.tv_sec; |
| its.it_interval.tv_nsec = its.it_value.tv_nsec; |
| |
| if (timer_settime(timerid, 0, &its, NULL) < 0) { |
| kbd_warning(errno, _("Unable to set timer")); |
| goto fail; |
| } |
| |
| do { |
| errno = 0; |
| |
| if (ioctl(fd, VT_ACTIVATE, vtno) < 0 && errno != EINTR) { |
| kbd_warning(errno, _("Couldn't activate vt %d"), vtno); |
| goto fail; |
| } |
| |
| if (ioctl(fd, VT_WAITACTIVE, vtno) < 0 && errno != EINTR) { |
| kbd_warning(errno, "ioctl(VT_WAITACTIVE)"); |
| goto fail; |
| } |
| } while (errno == EINTR); |
| |
| rc = 0; |
| fail: |
| if (timerid) |
| timer_delete(timerid); |
| |
| signal(SIGALRM, SIG_DFL); |
| |
| return rc; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int opt, i; |
| struct vt_stat vtstat; |
| int pid = 0; |
| int vtno = -1; |
| int fd = -1; |
| int consfd = -1; |
| int force = 0; |
| char optc = FALSE; |
| char show = FALSE; |
| char login = FALSE; |
| char verbose = FALSE; |
| char direct_exec = FALSE; |
| char do_wait = FALSE; |
| char as_user = FALSE; |
| char vtname[PATH_MAX+1]; |
| char *cmd = NULL, *def_cmd = NULL, *username = NULL; |
| |
| setuplocale(); |
| |
| struct option long_options[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "version", no_argument, NULL, 'V' }, |
| { "verbose", no_argument, NULL, 'v' }, |
| { "exec", no_argument, NULL, 'e' }, |
| { "force", no_argument, NULL, 'f' }, |
| { "login", no_argument, NULL, 'l' }, |
| { "user", no_argument, NULL, 'u' }, |
| { "switch", no_argument, NULL, 's' }, |
| { "wait", no_argument, NULL, 'w' }, |
| { "console", required_argument, NULL, 'c' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| const struct kbd_help opthelp[] = { |
| { "-c, --console=VTNUMBER", _("use the given VT number and not the first available.") }, |
| { "-e, --exec", _("execute the command, without forking.") }, |
| { "-f, --force", _("force opening a VT without checking.") }, |
| { "-l, --login", _("make the command a login shell.") }, |
| { "-u, --user", _("figure out the owner of the current VT.") }, |
| { "-s, --switch", _("switch to the new VT.") }, |
| { "-w, --wait", _("wait for command to complete") }, |
| { "-v, --verbose", _("be more verbose.") }, |
| { "-V, --version", _("print version number.") }, |
| { "-h, --help", _("print this usage message.") }, |
| { NULL, NULL } |
| }; |
| |
| while ((opt = getopt_long(argc, argv, "c:lsfuewhvV", long_options, NULL)) != -1) { |
| switch (opt) { |
| case 'c': |
| optc = 1; /* vtno was specified by the user */ |
| vtno = (int)atol(optarg); |
| |
| if (vtno <= 0 || vtno > 63) |
| kbd_error(5, 0, _("%s: Illegal vt number"), optarg); |
| |
| /* close security holes - until we can do this safely */ |
| if (setuid(getuid()) < 0) |
| kbd_error(5, errno, "%s: setuid", optarg); |
| break; |
| case 'l': |
| login = TRUE; |
| break; |
| case 's': |
| show = TRUE; |
| break; |
| case 'v': |
| verbose = TRUE; |
| break; |
| case 'f': |
| force = 1; |
| 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()) |
| kbd_error(EXIT_FAILURE, 0, _("Only root can use the -u flag.")); |
| |
| as_user = TRUE; |
| break; |
| case 'V': |
| print_version_and_exit(); |
| break; |
| default: |
| case 'h': |
| usage(EXIT_SUCCESS, opthelp); |
| break; |
| } |
| } |
| |
| for (i = 0; i < 3; i++) { |
| struct stat st; |
| |
| if (fstat(i, &st) == -1 && open("/dev/null", O_RDWR) == -1) |
| kbd_error(EXIT_FAILURE, errno, "open(/dev/null)"); |
| } |
| |
| if ((consfd = getfd(NULL)) < 0) |
| kbd_error(2, 0, _("Couldn't get a file descriptor referring to the console.")); |
| |
| if (ioctl(consfd, VT_GETSTATE, &vtstat) < 0) |
| kbd_error(4, errno, "ioctl(VT_GETSTATE)"); |
| |
| if (vtno == -1) { |
| if (ioctl(consfd, VT_OPENQRY, &vtno) < 0 || vtno == -1) |
| kbd_error(3, errno, _("Cannot find a free vt")); |
| |
| } else if (!force) { |
| if (vtno >= 16) |
| kbd_error(7, 0, _("Cannot check whether vt %d is free; use `%s -f' to force."), |
| vtno, program_invocation_short_name); |
| |
| if (vtstat.v_state & (1 << vtno)) |
| kbd_error(7, 0, _("vt %d is in use; command aborted; use `%s -f' to force."), |
| vtno, program_invocation_short_name); |
| } |
| |
| if (as_user) |
| username = authenticate_user(vtstat.v_active); |
| else { |
| size_t cmd_size; |
| if (!(argc > optind)) { |
| def_cmd = getenv("SHELL"); |
| if (def_cmd == NULL) |
| kbd_error(7, 0, _("Unable to find command.")); |
| cmd_size = strlen(def_cmd) + 2; |
| } else { |
| cmd_size = strlen(argv[optind]) + 2; |
| } |
| |
| cmd = malloc(cmd_size); |
| if (!cmd) |
| kbd_error(EX_OSERR, errno, "malloc"); |
| |
| if (login) { |
| snprintf(cmd, cmd_size, "-%s", def_cmd ? def_cmd : argv[optind]); |
| } else { |
| snprintf(cmd, cmd_size, "%s", def_cmd ? def_cmd : argv[optind]); |
| } |
| |
| if (login) |
| argv[optind] = cmd++; |
| } |
| |
| if (direct_exec || ((pid = fork()) == 0)) { |
| /* leave current vt */ |
| if (!direct_exec) { |
| #ifdef ESIX_5_3_2_D |
| #ifdef HAVE_SETPGRP |
| if (setpgrp() < 0) |
| #else |
| if (1) |
| #endif /* HAVE_SETPGRP */ |
| #else |
| if (setsid() < 0) |
| #endif /* ESIX_5_3_2_D */ |
| kbd_error(5, errno, _("Unable to set new session")); |
| } |
| |
| snprintf(vtname, PATH_MAX, VTNAME, vtno); |
| |
| /* Can we open the vt we want? */ |
| if ((fd = open_vt(vtname, force)) == -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. */ |
| for (i = vtno + 1; i < 16; i++) { |
| if ((vtstat.v_state & (1 << i)) == 0) { |
| snprintf(vtname, PATH_MAX, VTNAME, i); |
| if ((fd = open_vt(vtname, force)) >= 0) { |
| vtno = i; |
| goto got_vtno; |
| } |
| } |
| } |
| snprintf(vtname, PATH_MAX, VTNAME, vtno); |
| } |
| errno = errsv; |
| kbd_error(5, 0, _("Unable to open file: %s: %m"), vtname); |
| } |
| got_vtno: |
| if (verbose) |
| kbd_warning(0, _("Using VT %s"), vtname); |
| |
| /* 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) |
| kbd_error(5, errno, _("Cannot open %s read/write"), vtname); |
| |
| if (!as_user && !geteuid()) { |
| uid_t uid = getuid(); |
| if (chown(vtname, uid, getgid()) == -1) |
| kbd_error(5, errno, "chown"); |
| if (setuid(uid) < 0) |
| kbd_error(5, errno, "setuid"); |
| } |
| |
| if (show && change_vt(fd, vtno) < 0) |
| exit(1); |
| |
| close(0); |
| close(1); |
| close(2); |
| close(consfd); |
| |
| if ((dup2(fd, 0) == -1) || (dup2(fd, 1) == -1) || (dup2(fd, 2) == -1)) |
| kbd_error(1, errno, "dup"); |
| |
| /* 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]); |
| |
| kbd_error(127, errno, "exec"); |
| } |
| |
| if (pid < 0) |
| kbd_error(6, errno, "fork"); |
| |
| if (do_wait) { |
| int retval = 0; /* actual value returned form process */ |
| |
| wait(NULL); |
| waitpid(pid, &retval, 0); |
| |
| if (show) { /* Switch back... */ |
| if (change_vt(consfd, vtstat.v_active) < 0) |
| exit(8); |
| |
| if (ioctl(consfd, VT_DISALLOCATE, vtno)) |
| kbd_error(8, 0, _("Couldn't deallocate console %d"), vtno); |
| } |
| |
| /* if all our stuff went right, we want to return the exit code of the command we ran |
| super vital for scripting loops etc */ |
| return (retval); |
| } |
| |
| return EXIT_SUCCESS; |
| } |