/
niceSOFT
/
kbd
Обзор
Документация
Войти
/
niceSOFT
/
kbd
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/openvt.c
527 строк
13 KB
Alexey Gladkov
openvt: make -u process matching more conservative
12 май 2026, 11:20
Не верифицирован
12 май 2026, 11:20
78d5ae1
Код
Авторство
О чём код?
#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); } static int proc_pid_stat(const char *pid, uid_t *uid, dev_t *tty) { char filename[NAME_MAX + 12]; char line[BUFSIZ]; char *lp, *rp; FILE *fp; struct stat st; long tty_nr; snprintf(filename, sizeof(filename), "/proc/%s/stat", pid); fp = fopen(filename, "r"); if (!fp) return -1; if (fstat(fileno(fp), &st)) { fclose(fp); return -1; } if (!fgets(line, sizeof(line), fp)) { fclose(fp); return -1; } fclose(fp); rp = strrchr(line, ')'); if (!rp) return -1; /* * /proc/<pid>/stat fields after comm are: * state ppid pgrp session tty_nr ... */ if (!rp || sscanf(rp + 1, " %*c %*d %*d %*d %ld", &tty_nr) != 1) return -1; if (tty_nr <= 0) return -1; *uid = st.st_uid; *tty = (dev_t) tty_nr; return 0; } /* * 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_rdev; 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_rdev = buf.st_rdev; console_uid = buf.st_uid; if (console_uid == 0) kbd_error(EXIT_FAILURE, 0, _("Refusing to pre-authenticate root on current tty.")); /* 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))) { uid_t proc_uid; dev_t proc_tty; if (dentp->d_name[0] < '0' || dentp->d_name[0] > '9') continue; if (proc_pid_stat(dentp->d_name, &proc_uid, &proc_tty) < 0) continue; if (proc_uid == console_uid && proc_tty == console_rdev) 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; }