| /* |
| * 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; |
| } |