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