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 = "/usr/local/bin:/usr/bin:/bin";
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 _exit(127);
331 }
332 if (waitpid(pid, &st, 0) < 0) {
333 weprintf("waitpid:");
334 return -1;
335 }
336 if (WIFEXITED(st))
337 return WEXITSTATUS(st);
338 if (WIFSIGNALED(st))
339 return 128 + WTERMSIG(st);
340 return -1;
341}
342
343int
344wexecvp(const char *name, char *const *argv)
345{
346#if FEATURE_NOFORK
347 if (wexec_get_nofork() && wexec_is_builtin(name) && wexec_is_nofork(name)) {
348 fflush(stdout);
349 fflush(stderr);
350 return wexec_call_builtin(name, argv);
351 }
352#endif
353 return fork_exec(name, argv, 1);
354}
355
356int
357wexecv(const char *path, char *const *argv)
358{
359 const char *base = strrchr(path, '/');
360 base = base ? base + 1 : path;
361#if FEATURE_NOFORK
362 if (wexec_get_nofork() && wexec_is_builtin(base) && wexec_is_nofork(base)) {
363 fflush(stdout);
364 fflush(stderr);
365 return wexec_call_builtin(base, argv);
366 }
367#endif
368 return fork_exec(path, argv, 0);
369}
370
371FILE *
372wpopen(const char *name, char *const *argv, const char *mode)
373{
374 int pfd[2];
375 pid_t pid;
376 int parent_end, child_end;
377 char *sh_argv[4];
378#if FEATURE_NOEXEC
379 struct sigaction sa;
380 int i;
381#endif
382
383 if (pipe(pfd) < 0) {
384 weprintf("pipe:");
385 return NULL;
386 }
387
388 if (*mode == 'r') {
389 parent_end = pfd[0];
390 child_end = pfd[1];
391 } else {
392 parent_end = pfd[1];
393 child_end = pfd[0];
394 }
395
396 pid = fork();
397 if (pid < 0) {
398 weprintf("fork:");
399 close(pfd[0]);
400 close(pfd[1]);
401 return NULL;
402 }
403
404 if (pid == 0) {
405 close(parent_end);
406 if (child_end != (*mode == 'r' ? STDOUT_FILENO : STDIN_FILENO)) {
407 dup2(child_end, *mode == 'r' ? STDOUT_FILENO : STDIN_FILENO);
408 close(child_end);
409 }
410 if (argv) {
411#if FEATURE_NOEXEC
412 if (wexec_get_noexec() && wexec_is_builtin(name)) {
413 int argc;
414 char **p;
415
416 /* reset caught signal handlers to default for inproc command */
417 for (i = 1; i < NSIG; i++) {
418 if (sigaction(i, NULL, &sa) == 0) {
419 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
420 sa.sa_handler = SIG_DFL;
421 sa.sa_flags = 0;
422 sigemptyset(&sa.sa_mask);
423 sigaction(i, &sa, NULL);
424 }
425 }
426 }
427
428 argc = 0;
429 for (p = (char **)argv; *p; p++)
430 argc++;
431 wexec_call_builtin(name, argv);
432 fflush(stdout);
433 fflush(stderr);
434 _exit(0);
435 }
436#endif
437 execvp(name, argv);
438 _exit(127);
439 } else {
440 sh_argv[0] = "sh";
441 sh_argv[1] = "-c";
442 sh_argv[2] = (char *)name;
443 sh_argv[3] = NULL;
444#if FEATURE_NOEXEC
445 if (wexec_get_noexec() && wexec_is_builtin("sh")) {
446 /* reset caught signal handlers to default for inproc command */
447 for (i = 1; i < NSIG; i++) {
448 if (sigaction(i, NULL, &sa) == 0) {
449 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
450 sa.sa_handler = SIG_DFL;
451 sa.sa_flags = 0;
452 sigemptyset(&sa.sa_mask);
453 sigaction(i, &sa, NULL);
454 }
455 }
456 }
457
458 wexec_call_builtin("sh", sh_argv);
459 fflush(stdout);
460 fflush(stderr);
461 _exit(0);
462 }
463#endif
464 execvp("sh", sh_argv);
465 _exit(127);
466 }
467 }
468
469 close(child_end);
470 {
471 FILE *fp = fdopen(parent_end, mode);
472 if (!fp) {
473 weprintf("fdopen:");
474 close(parent_end);
475 kill(pid, SIGTERM);
476 waitpid(pid, NULL, 0);
477 return NULL;
478 }
479 if (wpopen_track(fp, pid) < 0) {
480 fclose(fp);
481 kill(pid, SIGTERM);
482 waitpid(pid, NULL, 0);
483 return NULL;
484 }
485 return fp;
486 }
487}
488
489int
490wpclose(FILE *fp)
491{
492 int st;
493 pid_t pid;
494
495 pid = wpopen_untrack(fp);
496 if (fclose(fp) == EOF)
497 weprintf("fclose:");
498 if (pid < 0)
499 return -1;
500 if (waitpid(pid, &st, 0) < 0) {
501 weprintf("waitpid:");
502 return -1;
503 }
504 if (WIFEXITED(st))
505 return WEXITSTATUS(st);
506 if (WIFSIGNALED(st))
507 return 128 + WTERMSIG(st);
508 return -1;
509}
510
511int
512wsystem(const char *cmd)
513{
514#if FEATURE_NOEXEC
515 if (wexec_get_noexec() && wexec_is_builtin("sh")) {
516 pid_t pid;
517 int st;
518
519 pid = fork();
520 if (pid < 0) {
521 weprintf("fork:");
522 return -1;
523 }
524 if (pid == 0) {
525 char *sh_argv[4];
526 struct sigaction sa;
527 int i;
528 int ret;
529
530 /* reset caught signal handlers to default for inproc command */
531 for (i = 1; i < NSIG; i++) {
532 if (sigaction(i, NULL, &sa) == 0) {
533 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
534 sa.sa_handler = SIG_DFL;
535 sa.sa_flags = 0;
536 sigemptyset(&sa.sa_mask);
537 sigaction(i, &sa, NULL);
538 }
539 }
540 }
541
542 sh_argv[0] = "sh";
543 sh_argv[1] = "-c";
544 sh_argv[2] = (char *)cmd;
545 sh_argv[3] = NULL;
546 ret = wexec_call_builtin("sh", sh_argv);
547 _exit(ret);
548 }
549 if (waitpid(pid, &st, 0) < 0) {
550 weprintf("waitpid:");
551 return -1;
552 }
553 if (WIFEXITED(st))
554 return WEXITSTATUS(st);
555 if (WIFSIGNALED(st))
556 return 128 + WTERMSIG(st);
557 return -1;
558 }
559#endif
560 {
561 char *sh_argv[4];
562 int ret;
563 sh_argv[0] = "sh";
564 sh_argv[1] = "-c";
565 sh_argv[2] = (char *)cmd;
566 sh_argv[3] = NULL;
567 ret = fork_exec("sh", sh_argv, 1);
568 return ret;
569 }
570}
571
572void
573wexecvp_self(const char *name, char *const *argv)
574{
575#if FEATURE_NOEXEC
576 if (wexec_get_noexec() && wexec_is_builtin(name)) {
577 struct sigaction sa;
578 int i;
579 int ret;
580
581 /* reset caught signal handlers to default for inproc command */
582 for (i = 1; i < NSIG; i++) {
583 if (sigaction(i, NULL, &sa) == 0) {
584 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
585 sa.sa_handler = SIG_DFL;
586 sa.sa_flags = 0;
587 sigemptyset(&sa.sa_mask);
588 sigaction(i, &sa, NULL);
589 }
590 }
591 }
592
593 fflush(stdout);
594 fflush(stderr);
595 ret = wexec_call_builtin(name, argv);
596 fflush(stdout);
597 fflush(stderr);
598 exit(ret);
599 }
600#endif
601 execvp(name, argv);
602 weprintf("execvp %s:", name);
603 exit((errno == ENOENT) ? 127 : 126);
604}
605
606void
607wexecv_self(const char *path, char *const *argv)
608{
609#if FEATURE_NOEXEC
610 {
611 const char *base = strrchr(path, '/');
612 base = base ? base + 1 : path;
613 if (wexec_get_noexec() && wexec_is_builtin(base)) {
614 struct sigaction sa;
615 int i;
616 int ret;
617
618 /* reset caught signal handlers to default for inproc command */
619 for (i = 1; i < NSIG; i++) {
620 if (sigaction(i, NULL, &sa) == 0) {
621 if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
622 sa.sa_handler = SIG_DFL;
623 sa.sa_flags = 0;
624 sigemptyset(&sa.sa_mask);
625 sigaction(i, &sa, NULL);
626 }
627 }
628 }
629
630 fflush(stdout);
631 fflush(stderr);
632 ret = wexec_call_builtin(base, argv);
633 fflush(stdout);
634 fflush(stderr);
635 exit(ret);
636 }
637 }
638#endif
639 execv(path, argv);
640 weprintf("execv %s:", path);
641 exit((errno == ENOENT) ? 127 : 126);
642}
643
644void(exit)(int);
645
646void
647wexec_exit(int code)
648{
649#if FEATURE_NOEXEC || FEATURE_NOFORK
650 if (inproc_active) {
651 inproc_exit_status = code;
652 siglongjmp(inproc_jmp, 1);
653 }
654#endif
655 (exit)(code);
656}