1/* see LICENSE file for copyright and license details */
2#include "../wexec.h"
3#include "../config.h"
4#include "../paths.h"
5#include "../util.h"
6
7#include <errno.h>
8#include <setjmp.h>
9#include <signal.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13#include <sys/wait.h>
14#include <unistd.h>
15
16#define MAX_BUILTINS 256
17#define MAX_POPEN 32
18
19struct builtin {
20 const char *name;
21 wexec_fn fn;
22};
23
24static struct builtin builtins[MAX_BUILTINS];
25static int nbuiltins;
26
27struct popen_entry {
28 FILE *fp;
29 pid_t pid;
30};
31
32static struct popen_entry popen_tab[MAX_POPEN];
33static int npopen;
34
35static char *(*wexec_env_lookup)(const char *name) = NULL;
36
37void
38wexec_register_env_lookup(char *(*fn)(const char *name))
39{
40 wexec_env_lookup = fn;
41}
42
43static const char *
44wexec_getenv(const char *name)
45{
46 if (wexec_env_lookup)
47 return wexec_env_lookup(name);
48 return getenv(name);
49}
50
51int
52wpopen_track(FILE *fp, pid_t pid)
53{
54 if (npopen >= MAX_POPEN)
55 return -1;
56 popen_tab[npopen].fp = fp;
57 popen_tab[npopen].pid = pid;
58 npopen++;
59 return 0;
60}
61
62pid_t
63wpopen_untrack(FILE *fp)
64{
65 int i;
66 pid_t pid = -1;
67 for (i = 0; i < npopen; i++) {
68 if (popen_tab[i].fp == fp) {
69 pid = popen_tab[i].pid;
70 popen_tab[i] = popen_tab[npopen - 1];
71 npopen--;
72 break;
73 }
74 }
75 return pid;
76}
77
78int
79wexec_register(const char *name, wexec_fn fn)
80{
81 int i;
82 for (i = 0; i < nbuiltins; i++) {
83 if (strcmp(builtins[i].name, name) == 0)
84 return -1;
85 }
86 if (nbuiltins >= MAX_BUILTINS)
87 return -1;
88 builtins[nbuiltins].name = name;
89 builtins[nbuiltins].fn = fn;
90 nbuiltins++;
91 return 0;
92}
93
94int
95wexec_is_builtin(const char *name)
96{
97 int i;
98 for (i = 0; i < nbuiltins; i++) {
99 if (strcmp(builtins[i].name, name) == 0)
100 return 1;
101 }
102 return 0;
103}
104
105int
106wexec_is_nofork(const char *name)
107{
108#include "nofork_list.h"
109 int i;
110 for (i = 0; nofork_list[i]; i++) {
111 if (strcmp(nofork_list[i], name) == 0)
112 return 1;
113 }
114 return 0;
115}
116
117#if FEATURE_NOEXEC
118static int noexec_enabled = 1;
119
120#if FEATURE_NOEXEC_OVERRIDE
121static void
122noexec_read_env(void)
123{
124 const char *e = wexec_getenv("ARUU_NOEXEC");
125 if (e && strcmp(e, "0") == 0)
126 noexec_enabled = 0;
127 else
128 noexec_enabled = 1;
129}
130#endif
131
132void
133wexec_set_noexec(int enabled)
134{
135 noexec_enabled = enabled;
136}
137
138int
139wexec_get_noexec(void)
140{
141#if FEATURE_NOEXEC_OVERRIDE
142 noexec_read_env();
143#endif
144 return noexec_enabled;
145}
146#endif
147
148#if FEATURE_NOFORK
149static int nofork_enabled = 1;
150
151#if FEATURE_NOFORK_OVERRIDE
152static void
153nofork_read_env(void)
154{
155 const char *e = wexec_getenv("ARUU_NOFORK");
156 if (e && strcmp(e, "0") == 0)
157 nofork_enabled = 0;
158 else
159 nofork_enabled = 1;
160}
161#endif
162
163void
164wexec_set_nofork(int enabled)
165{
166 nofork_enabled = enabled;
167}
168
169int
170wexec_get_nofork(void)
171{
172#if FEATURE_NOFORK_OVERRIDE
173 nofork_read_env();
174#endif
175 return nofork_enabled;
176}
177#endif
178
179char *
180wwhich(const char *name)
181{
182 const char *path, *p, *end;
183 char *buf;
184 size_t dlen, nlen;
185 struct stat st;
186
187#if FEATURE_NOEXEC
188 /* when builtin dispatch is active, registered builtins have no filesystem path */
189 if (wexec_get_noexec() && wexec_is_builtin(name))
190 return NULL;
191#endif
192
193 /* slash present: treat as a literal path */
194 if (strchr(name, '/') != NULL) {
195 if (stat(name, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 0111))
196 return estrdup(name);
197 return NULL;
198 }
199
200 path = wexec_getenv("PATH");
201 if (path == NULL)
202 path = ARUU_PATH_DEFPATH;
203
204 nlen = strlen(name);
205 p = path;
206 for (;;) {
207 end = strchr(p, ':');
208 dlen = end ? (size_t)(end - p) : strlen(p);
209 /* skip empty components (current dir) for safety */
210 if (dlen > 0) {
211 buf = emalloc(dlen + 1 + nlen + 1);
212 memcpy(buf, p, dlen);
213 buf[dlen] = '/';
214 memcpy(buf + dlen + 1, name, nlen + 1);
215 if (stat(buf, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 0111))
216 return buf;
217 free(buf);
218 }
219 if (!end)
220 break;
221 p = end + 1;
222 }
223 return NULL;
224}
225
226#if FEATURE_NOEXEC || FEATURE_NOFORK
227static sigjmp_buf inproc_jmp;
228static volatile sig_atomic_t inproc_active = 0;
229static volatile int inproc_exit_status = 0;
230
231static void
232inproc_sig_handler(int signo)
233{
234 if (inproc_active) {
235 siglongjmp(inproc_jmp, signo);
236 }
237}
238
239int
240wexec_call_builtin(const char *name, char *const *argv)
241{
242 struct sigaction sa, osa_int, osa_quit, osa_pipe;
243 int i, argc, ret, sig;
244 char **p;
245
246 sig = sigsetjmp(inproc_jmp, 1);
247 if (sig != 0) {
248 inproc_active = 0;
249 sigaction(SIGINT, &osa_int, NULL);
250 sigaction(SIGQUIT, &osa_quit, NULL);
251 sigaction(SIGPIPE, &osa_pipe, NULL);
252 if (sig == 1)
253 return inproc_exit_status;
254 kill(getpid(), sig);
255 return 128 + sig;
256 }
257
258 sa.sa_handler = inproc_sig_handler;
259 sigemptyset(&sa.sa_mask);
260 sa.sa_flags = 0;
261 sigaction(SIGINT, &sa, &osa_int);
262 sigaction(SIGQUIT, &sa, &osa_quit);
263
264 sa.sa_handler = SIG_IGN;
265 sigaction(SIGPIPE, &sa, &osa_pipe);
266
267 inproc_active = 1;
268
269 ret = -1;
270 argc = 0;
271 for (p = (char **)argv; *p; p++)
272 argc++;
273 for (i = 0; i < nbuiltins; i++) {
274 if (strcmp(builtins[i].name, name) == 0) {
275 fflush(stdout);
276 fflush(stderr);
277 ret = builtins[i].fn(argc, (char **)argv);
278 fflush(stdout);
279 fflush(stderr);
280 break;
281 }
282 }
283
284 inproc_active = 0;
285
286 sigaction(SIGINT, &osa_int, NULL);
287 sigaction(SIGQUIT, &osa_quit, NULL);
288 sigaction(SIGPIPE, &osa_pipe, NULL);
289
290 return ret;
291}
292#endif
293
294static int
295fork_exec(const char *path, char *const *argv, int use_path)
296{
297 pid_t pid;
298 int st;
299
300 pid = fork();
301 if (pid < 0) {
302 weprintf("fork:");
303 return -1;
304 }
305 if (pid == 0) {
306 const char *base = strrchr(path, '/');
307 base = base ? base + 1 : path;
308#if FEATURE_NOEXEC
309 if (wexec_get_noexec() && wexec_is_builtin(base)) {
310 struct sigaction sa;
311 int i, ret;
312 for (i = 1; i < NSIG; i++) {
313 if (sigaction(i, NULL, &sa) == 0) {
314 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
315 sa.sa_handler = SIG_DFL;
316 sa.sa_flags = 0;
317 sigemptyset(&sa.sa_mask);
318 sigaction(i, &sa, NULL);
319 }
320 }
321 }
322 ret = wexec_call_builtin(base, argv);
323 _exit(ret);
324 }
325#endif
326 if (use_path)
327 execvp(path, argv);
328 else
329 execv(path, argv);
330 weprintf(use_path ? "execvp %s:" : "execv %s:", path);
331 _exit((errno == ENOENT) ? 127 : 126);
332 }
333 if (waitpid(pid, &st, 0) < 0) {
334 weprintf("waitpid:");
335 return -1;
336 }
337 if (WIFEXITED(st))
338 return WEXITSTATUS(st);
339 if (WIFSIGNALED(st))
340 return 128 + WTERMSIG(st);
341 return -1;
342}
343
344int
345wexecvp(const char *name, char *const *argv)
346{
347#if FEATURE_NOFORK
348 if (wexec_get_nofork() && wexec_is_builtin(name) && wexec_is_nofork(name)) {
349 fflush(stdout);
350 fflush(stderr);
351 return wexec_call_builtin(name, argv);
352 }
353#endif
354 return fork_exec(name, argv, 1);
355}
356
357int
358wexecv(const char *path, char *const *argv)
359{
360 const char *base = strrchr(path, '/');
361 base = base ? base + 1 : path;
362#if FEATURE_NOFORK
363 if (wexec_get_nofork() && wexec_is_builtin(base) && wexec_is_nofork(base)) {
364 fflush(stdout);
365 fflush(stderr);
366 return wexec_call_builtin(base, argv);
367 }
368#endif
369 return fork_exec(path, argv, 0);
370}
371
372FILE *
373wpopen(const char *name, char *const *argv, const char *mode)
374{
375 int pfd[2];
376 pid_t pid;
377 int parent_end, child_end;
378 char *sh_argv[4];
379#if FEATURE_NOEXEC
380 struct sigaction sa;
381 int i;
382#endif
383
384 if (pipe(pfd) < 0) {
385 weprintf("pipe:");
386 return NULL;
387 }
388
389 if (*mode == 'r') {
390 parent_end = pfd[0];
391 child_end = pfd[1];
392 } else {
393 parent_end = pfd[1];
394 child_end = pfd[0];
395 }
396
397 pid = fork();
398 if (pid < 0) {
399 weprintf("fork:");
400 close(pfd[0]);
401 close(pfd[1]);
402 return NULL;
403 }
404
405 if (pid == 0) {
406 close(parent_end);
407 if (child_end != (*mode == 'r' ? STDOUT_FILENO : STDIN_FILENO)) {
408 dup2(child_end, *mode == 'r' ? STDOUT_FILENO : STDIN_FILENO);
409 close(child_end);
410 }
411 if (argv) {
412#if FEATURE_NOEXEC
413 if (wexec_get_noexec() && wexec_is_builtin(name)) {
414 int argc;
415 char **p;
416
417 /* reset caught signal handlers to default for inproc command */
418 for (i = 1; i < NSIG; i++) {
419 if (sigaction(i, NULL, &sa) == 0) {
420 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
421 sa.sa_handler = SIG_DFL;
422 sa.sa_flags = 0;
423 sigemptyset(&sa.sa_mask);
424 sigaction(i, &sa, NULL);
425 }
426 }
427 }
428
429 argc = 0;
430 for (p = (char **)argv; *p; p++)
431 argc++;
432 wexec_call_builtin(name, argv);
433 fflush(stdout);
434 fflush(stderr);
435 _exit(0);
436 }
437#endif
438 execvp(name, argv);
439 weprintf("execvp %s:", name);
440 _exit((errno == ENOENT) ? 127 : 126);
441 } else {
442 sh_argv[0] = "sh";
443 sh_argv[1] = "-c";
444 sh_argv[2] = (char *)name;
445 sh_argv[3] = NULL;
446#if FEATURE_NOEXEC
447 if (wexec_get_noexec() && wexec_is_builtin("sh")) {
448 /* reset caught signal handlers to default for inproc command */
449 for (i = 1; i < NSIG; i++) {
450 if (sigaction(i, NULL, &sa) == 0) {
451 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
452 sa.sa_handler = SIG_DFL;
453 sa.sa_flags = 0;
454 sigemptyset(&sa.sa_mask);
455 sigaction(i, &sa, NULL);
456 }
457 }
458 }
459
460 wexec_call_builtin("sh", sh_argv);
461 fflush(stdout);
462 fflush(stderr);
463 _exit(0);
464 }
465#endif
466 execvp("sh", sh_argv);
467 weprintf("execvp sh:");
468 _exit((errno == ENOENT) ? 127 : 126);
469 }
470 }
471
472 close(child_end);
473 {
474 FILE *fp = fdopen(parent_end, mode);
475 if (!fp) {
476 weprintf("fdopen:");
477 close(parent_end);
478 kill(pid, SIGTERM);
479 waitpid(pid, NULL, 0);
480 return NULL;
481 }
482 if (wpopen_track(fp, pid) < 0) {
483 fclose(fp);
484 kill(pid, SIGTERM);
485 waitpid(pid, NULL, 0);
486 return NULL;
487 }
488 return fp;
489 }
490}
491
492int
493wpclose(FILE *fp)
494{
495 int st;
496 pid_t pid;
497
498 pid = wpopen_untrack(fp);
499 if (fclose(fp) == EOF)
500 weprintf("fclose:");
501 if (pid < 0)
502 return -1;
503 if (waitpid(pid, &st, 0) < 0) {
504 weprintf("waitpid:");
505 return -1;
506 }
507 if (WIFEXITED(st))
508 return WEXITSTATUS(st);
509 if (WIFSIGNALED(st))
510 return 128 + WTERMSIG(st);
511 return -1;
512}
513
514int
515wsystem(const char *cmd)
516{
517#if FEATURE_NOEXEC
518 if (wexec_get_noexec() && wexec_is_builtin("sh")) {
519 pid_t pid;
520 int st;
521
522 pid = fork();
523 if (pid < 0) {
524 weprintf("fork:");
525 return -1;
526 }
527 if (pid == 0) {
528 char *sh_argv[4];
529 struct sigaction sa;
530 int i;
531 int ret;
532
533 /* reset caught signal handlers to default for inproc command */
534 for (i = 1; i < NSIG; i++) {
535 if (sigaction(i, NULL, &sa) == 0) {
536 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
537 sa.sa_handler = SIG_DFL;
538 sa.sa_flags = 0;
539 sigemptyset(&sa.sa_mask);
540 sigaction(i, &sa, NULL);
541 }
542 }
543 }
544
545 sh_argv[0] = "sh";
546 sh_argv[1] = "-c";
547 sh_argv[2] = (char *)cmd;
548 sh_argv[3] = NULL;
549 ret = wexec_call_builtin("sh", sh_argv);
550 _exit(ret);
551 }
552 if (waitpid(pid, &st, 0) < 0) {
553 weprintf("waitpid:");
554 return -1;
555 }
556 if (WIFEXITED(st))
557 return WEXITSTATUS(st);
558 if (WIFSIGNALED(st))
559 return 128 + WTERMSIG(st);
560 return -1;
561 }
562#endif
563 {
564 char *sh_argv[4];
565 int ret;
566 sh_argv[0] = "sh";
567 sh_argv[1] = "-c";
568 sh_argv[2] = (char *)cmd;
569 sh_argv[3] = NULL;
570 ret = fork_exec("sh", sh_argv, 1);
571 return ret;
572 }
573}
574
575void
576wexecvp_self(const char *name, char *const *argv)
577{
578#if FEATURE_NOEXEC
579 if (wexec_get_noexec() && wexec_is_builtin(name)) {
580 struct sigaction sa;
581 int i;
582 int ret;
583
584 /* reset caught signal handlers to default for inproc command */
585 for (i = 1; i < NSIG; i++) {
586 if (sigaction(i, NULL, &sa) == 0) {
587 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
588 sa.sa_handler = SIG_DFL;
589 sa.sa_flags = 0;
590 sigemptyset(&sa.sa_mask);
591 sigaction(i, &sa, NULL);
592 }
593 }
594 }
595
596 fflush(stdout);
597 fflush(stderr);
598 ret = wexec_call_builtin(name, argv);
599 fflush(stdout);
600 fflush(stderr);
601 exit(ret);
602 }
603#endif
604 execvp(name, argv);
605 weprintf("execvp %s:", name);
606 exit((errno == ENOENT) ? 127 : 126);
607}
608
609void
610wexecv_self(const char *path, char *const *argv)
611{
612#if FEATURE_NOEXEC
613 {
614 const char *base = strrchr(path, '/');
615 base = base ? base + 1 : path;
616 if (wexec_get_noexec() && wexec_is_builtin(base)) {
617 struct sigaction sa;
618 int i;
619 int ret;
620
621 /* reset caught signal handlers to default for inproc command */
622 for (i = 1; i < NSIG; i++) {
623 if (sigaction(i, NULL, &sa) == 0) {
624 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
625 sa.sa_handler = SIG_DFL;
626 sa.sa_flags = 0;
627 sigemptyset(&sa.sa_mask);
628 sigaction(i, &sa, NULL);
629 }
630 }
631 }
632
633 fflush(stdout);
634 fflush(stderr);
635 ret = wexec_call_builtin(base, argv);
636 fflush(stdout);
637 fflush(stderr);
638 exit(ret);
639 }
640 }
641#endif
642 execv(path, argv);
643 weprintf("execv %s:", path);
644 exit((errno == ENOENT) ? 127 : 126);
645}
646
647void(exit)(int);
648
649void
650wexec_exit(int code)
651{
652#if FEATURE_NOEXEC || FEATURE_NOFORK
653 if (inproc_active) {
654 inproc_exit_status = code;
655 siglongjmp(inproc_jmp, 1);
656 }
657#endif
658 (exit)(code);
659}