Imported Upstream version 2.0.90
[platform/upstream/kbd.git] / src / openvt.c
1 #include "config.h"
2
3 #include <fcntl.h>
4 #include <limits.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <stdarg.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <getopt.h>
11 #include <dirent.h>
12 #include <pwd.h>
13 #include <errno.h>
14 #include <sys/ioctl.h>
15 #include <sys/stat.h>
16 #include <sys/vt.h>
17 #include <sys/wait.h>
18 #include <sys/file.h>
19
20 #include "libcommon.h"
21
22 #ifdef COMPAT_HEADERS
23 #include "compat/linux-limits.h"
24 #endif
25
26 // There must be a universal way to find these!
27 #define TRUE (1)
28 #define FALSE (0)
29
30 #ifdef ESIX_5_3_2_D
31 #define VTBASE "/dev/vt%02d"
32 #endif
33
34 // Where your VTs are hidden
35 #ifdef __linux__
36 #define VTNAME "/dev/tty%d"
37 #define VTNAME2 "/dev/vc/%d"
38 #endif
39
40 #ifndef VTNAME
41 #error vt device name must be defined
42 #endif
43
44 static void
45     __attribute__((noreturn))
46     print_help(int ret)
47 {
48         printf(_("Usage: %s [OPTIONS] -- command\n"
49                  "\n"
50                  "This utility helps you to start a program on a new virtual terminal (VT).\n"
51                  "\n"
52                  "Options:\n"
53                  "  -c, --console=NUM   use the given VT number;\n"
54                  "  -e, --exec          execute the command, without forking;\n"
55                  "  -f, --force         force opening a VT without checking;\n"
56                  "  -l, --login         make the command a login shell;\n"
57                  "  -u, --user          figure out the owner of the current VT;\n"
58                  "  -s, --switch        switch to the new VT;\n"
59                  "  -w, --wait          wait for command to complete;\n"
60                  "  -v, --verbose       print a message for each action;\n"
61                  "  -V, --version       print program version and exit;\n"
62                  "  -h, --help          output a brief help message.\n"
63                  "\n"),
64                get_progname());
65         exit(ret);
66 }
67
68 /*
69  * Support for Spawn_Console: openvt running from init
70  * added by Joshua Spoerri, Thu Jul 18 21:13:16 EDT 1996
71  *
72  *  -u     Figure  out  the  owner  of the current VT, and run
73  *         login as that user.  Suitable to be called by init.
74  *         Shouldn't be used with -c or -l.
75  *         Sample inittab line:
76  *                kb::kbrequest:/usr/bin/openvt -us
77  *
78  * It is the job of authenticate_user() to find out who
79  * produced this keyboard signal.  It is called only as root.
80  *
81  * Note that there is a race condition: curvt may not be the vt
82  * from which the keyboard signal was produced.
83  * (Possibly the signal was not produced at the keyboard at all,
84  * but by a "kill -SIG 1".  However, only root can do this.)
85  *
86  * Conclusion: do not use this in high security environments.
87  * Or fix the code below to be more suspicious.
88  *
89  * Maybe it is better to just start a login at the new vt,
90  * instead of pre-authenticating the user with "login -f".
91  */
92
93 static char *
94 authenticate_user(int curvt)
95 {
96         DIR *dp;
97         struct dirent *dentp;
98         struct stat buf;
99         dev_t console_dev;
100         ino_t console_ino;
101         uid_t console_uid;
102         char filename[NAME_MAX + 12];
103         struct passwd *pwnam;
104
105         if (!(dp = opendir("/proc")))
106                 kbd_error(EXIT_FAILURE, errno, "opendir(/proc)");
107
108         /* get the current tty */
109         /* try /dev/ttyN, then /dev/vc/N */
110         sprintf(filename, VTNAME, curvt);
111         if (stat(filename, &buf)) {
112                 int errsv = errno;
113                 sprintf(filename, VTNAME2, curvt);
114                 if (stat(filename, &buf)) {
115                         /* give error message for first attempt */
116                         sprintf(filename, VTNAME, curvt);
117                         kbd_error(EXIT_FAILURE, errsv, "%s", filename);
118                 }
119         }
120         console_dev = buf.st_dev;
121         console_ino = buf.st_ino;
122         console_uid = buf.st_uid;
123
124         /* get the owner of current tty */
125         if (!(pwnam = getpwuid(console_uid)))
126                 kbd_error(EXIT_FAILURE, errno, "getpwuid");
127
128         /* check to make sure that user has a process on that tty */
129         /* this will fail for example when X is running on the tty */
130         while ((dentp = readdir(dp))) {
131                 sprintf(filename, "/proc/%s/fd/0", dentp->d_name);
132
133                 if (stat(filename, &buf))
134                         continue;
135
136                 if (buf.st_dev == console_dev && buf.st_ino == console_ino && buf.st_uid == console_uid)
137                         goto got_a_process;
138         }
139
140         kbd_error(EXIT_FAILURE, 0, _("Couldn't find owner of current tty!"));
141
142 got_a_process:
143         closedir(dp);
144
145         return pwnam->pw_name;
146 }
147
148 static int
149 open_vt(char *vtname, int force)
150 {
151         int fd;
152
153         if ((fd = open(vtname, O_RDWR)) == -1)
154                 return -1;
155
156         if (ioctl(fd, TIOCSCTTY, force) == -1) {
157                 close(fd);
158                 return -1;
159         }
160
161         return fd;
162 }
163
164 int main(int argc, char *argv[])
165 {
166         int opt, i;
167         struct vt_stat vtstat;
168         int pid          = 0;
169         int vtno         = -1;
170         int fd           = -1;
171         int consfd       = -1;
172         int force        = 0;
173         char optc        = FALSE;
174         char show        = FALSE;
175         char login       = FALSE;
176         char verbose     = FALSE;
177         char direct_exec = FALSE;
178         char do_wait     = FALSE;
179         char as_user     = FALSE;
180         char vtname[PATH_MAX+1];
181         char *cmd = NULL, *def_cmd = NULL, *username = NULL;
182
183         struct option long_options[] = {
184                 { "help", no_argument, 0, 'h' },
185                 { "version", no_argument, 0, 'V' },
186                 { "verbose", no_argument, 0, 'v' },
187                 { "exec", no_argument, 0, 'e' },
188                 { "force", no_argument, 0, 'f' },
189                 { "login", no_argument, 0, 'l' },
190                 { "user", no_argument, 0, 'u' },
191                 { "switch", no_argument, 0, 's' },
192                 { "wait", no_argument, 0, 'w' },
193                 { "console", required_argument, 0, 'c' },
194                 { 0, 0, 0, 0 }
195         };
196
197         set_progname(argv[0]);
198         setuplocale();
199
200         while ((opt = getopt_long(argc, argv, "c:lsfuewhvV", long_options, NULL)) != -1) {
201                 switch (opt) {
202                         case 'c':
203                                 optc = 1; /* vtno was specified by the user */
204                                 vtno = (int)atol(optarg);
205
206                                 if (vtno <= 0 || vtno > 63)
207                                         kbd_error(5, 0, _("%s: Illegal vt number"), optarg);
208
209                                 /* close security holes - until we can do this safely */
210                                 if (setuid(getuid()) < 0)
211                                         kbd_error(5, errno, "%s: setuid", optarg);
212                                 break;
213                         case 'l':
214                                 login = TRUE;
215                                 break;
216                         case 's':
217                                 show = TRUE;
218                                 break;
219                         case 'v':
220                                 verbose = TRUE;
221                                 break;
222                         case 'f':
223                                 force = 1;
224                                 break;
225                         case 'e':
226                                 direct_exec = TRUE;
227                                 break;
228                         case 'w':
229                                 do_wait = TRUE;
230                                 break;
231                         case 'u':
232                                 /* we'll let 'em get away with the meaningless -ul combo */
233                                 if (getuid())
234                                         kbd_error(EXIT_FAILURE, 0, _("Only root can use the -u flag."));
235
236                                 as_user = TRUE;
237                                 break;
238                         case 'V':
239                                 print_version_and_exit();
240                                 break;
241                         default:
242                         case 'h':
243                                 print_help(EXIT_SUCCESS);
244                                 break;
245                 }
246         }
247
248         for (i = 0; i < 3; i++) {
249                 struct stat st;
250
251                 if (fstat(i, &st) == -1 && open("/dev/null", O_RDWR) == -1)
252                         kbd_error(EXIT_FAILURE, errno, "open(/dev/null)");
253         }
254
255         if ((consfd = getfd(NULL)) < 0)
256                 kbd_error(2, 0, _("Couldn't get a file descriptor referring to the console"));
257
258         if (ioctl(consfd, VT_GETSTATE, &vtstat) < 0)
259                 kbd_error(4, errno, "ioctl(VT_GETSTATE)");
260
261         if (vtno == -1) {
262                 if (ioctl(consfd, VT_OPENQRY, &vtno) < 0 || vtno == -1)
263                         kbd_error(3, errno, _("Cannot find a free vt"));
264
265         } else if (!force) {
266                 if (vtno >= 16)
267                         kbd_error(7, 0, _("Cannot check whether vt %d is free; use `%s -f' to force."),
268                                   vtno, get_progname());
269
270                 if (vtstat.v_state & (1 << vtno))
271                         kbd_error(7, 0, _("vt %d is in use; command aborted; use `%s -f' to force."),
272                                   vtno, get_progname());
273         }
274
275         if (as_user)
276                 username = authenticate_user(vtstat.v_active);
277         else {
278                 if (!(argc > optind)) {
279                         def_cmd = getenv("SHELL");
280                         if (def_cmd == NULL)
281                                 kbd_error(7, 0, _("Unable to find command."));
282                         cmd = xmalloc(strlen(def_cmd) + 2);
283                 } else {
284                         cmd = xmalloc(strlen(argv[optind]) + 2);
285                 }
286
287                 if (login)
288                         strcpy(cmd, "-");
289                 else
290                         cmd[0] = '\0';
291
292                 if (def_cmd)
293                         strcat(cmd, def_cmd);
294                 else
295                         strcat(cmd, argv[optind]);
296
297                 if (login)
298                         argv[optind] = cmd++;
299         }
300
301         if (direct_exec || ((pid = fork()) == 0)) {
302                 /* leave current vt */
303                 if (!direct_exec) {
304 #ifdef ESIX_5_3_2_D
305 #ifdef HAVE_SETPGRP
306                         if (setpgrp() < 0)
307 #else
308                         if (1)
309 #endif /* HAVE_SETPGRP */
310 #else
311                         if (setsid() < 0)
312 #endif /* ESIX_5_3_2_D */
313                                 kbd_error(5, errno, _("Unable to set new session"));
314                 }
315
316                 snprintf(vtname, PATH_MAX, VTNAME, vtno);
317
318                 /* Can we open the vt we want? */
319                 if ((fd = open_vt(vtname, force)) == -1) {
320                         int errsv = errno;
321                         if (!optc) {
322                                 /* We found vtno ourselves - it is free according
323                                    to the kernel, but we cannot open it. Maybe X
324                                    used it and did a chown.  Try a few vt's more
325                                    before giving up. Note: the 16 is a kernel limitation. */
326                                 for (i = vtno + 1; i < 16; i++) {
327                                         if ((vtstat.v_state & (1 << i)) == 0) {
328                                                 snprintf(vtname, PATH_MAX, VTNAME, i);
329                                                 if ((fd = open_vt(vtname, force)) >= 0) {
330                                                         vtno = i;
331                                                         goto got_vtno;
332                                                 }
333                                         }
334                                 }
335                                 snprintf(vtname, PATH_MAX, VTNAME, vtno);
336                         }
337                         kbd_error(5, errsv, _("Unable to open %s"), vtname);
338                 }
339         got_vtno:
340                 if (verbose)
341                         kbd_warning(0, _("Using VT %s"), vtname);
342
343                 /* Maybe we are suid root, and the -c option was given.
344                    Check that the real user can access this VT.
345                    We assume getty has made any in use VT non accessable */
346                 if (access(vtname, R_OK | W_OK) < 0)
347                         kbd_error(5, errno, _("Cannot open %s read/write"), vtname);
348
349                 if (!as_user && !geteuid()) {
350                         uid_t uid = getuid();
351                         if (chown(vtname, uid, getgid()) == -1)
352                                 kbd_error(5, errno, "chown");
353                         if (setuid(uid) < 0)
354                                 kbd_error(5, errno, "setuid");
355                 }
356
357                 if (show) {
358                         if (ioctl(fd, VT_ACTIVATE, vtno))
359                                 kbd_error(1, errno, _("Couldn't activate vt %d"), vtno);
360
361                         if (ioctl(fd, VT_WAITACTIVE, vtno))
362                                 kbd_error(1, errno, _("Activation interrupted?"));
363                 }
364                 close(0);
365                 close(1);
366                 close(2);
367                 close(consfd);
368
369                 if ((dup2(fd, 0) == -1) || (dup2(fd, 1) == -1) || (dup2(fd, 2) == -1))
370                         kbd_error(1, errno, "dup");
371
372                 /* slight problem: after "openvt -su" has finished, the
373                    utmp entry is not removed */
374                 if (as_user)
375                         execlp("login", "login", "-f", username, NULL);
376                 else if (def_cmd)
377                         execlp(cmd, def_cmd, NULL);
378                 else
379                         execvp(cmd, &argv[optind]);
380
381                 kbd_error(127, errno, "exec");
382         }
383
384         if (pid < 0)
385                 kbd_error(6, errno, "fork");
386
387         if (do_wait) {
388                 int retval = 0; /* actual value returned form process */
389
390                 wait(NULL);
391                 waitpid(pid, &retval, 0);
392
393                 if (show) { /* Switch back... */
394                         if (ioctl(consfd, VT_ACTIVATE, vtstat.v_active))
395                                 kbd_error(8, errno, "ioctl(VT_ACTIVATE)");
396
397                         /* wait to be really sure we have switched */
398                         if (ioctl(consfd, VT_WAITACTIVE, vtstat.v_active))
399                                 kbd_error(8, errno, "ioctl(VT_WAITACTIVE)");
400
401                         if (ioctl(consfd, VT_DISALLOCATE, vtno))
402                                 kbd_error(8, 0, _("Couldn't deallocate console %d"), vtno);
403                 }
404
405                 /* if all our stuff went right, we want to return the exit code of the command we ran
406                    super vital for scripting loops etc */
407                 return (retval);
408         }
409
410         return EXIT_SUCCESS;
411 }