// Copyright 2022 Nicholas J. Kain // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include #include "nk/log.h" #include "nk/io.h" #include "nk/exec.h" #include "scriptd.h" #include "ndhc.h" #include "sys.h" bool valid_script_file = false; // Runs the 'script_file'-specified script. Called from ndhc process. void request_scriptd_run(void) { if (!valid_script_file) return; static char buf[] = "\n"; ssize_t r = safe_write(scriptdSock[0], buf, 1); if (r < 0 || (size_t)r != 1) suicide("%s: (%s) write failed: %zd", client_config.interface, __func__, r); } static void run_script(void) { struct nk_exec_env xe; switch ((int)fork()) { case 0: { int r = nk_generate_env(&xe, 0, NULL, NULL); if (r < 0) { nk_generate_env_print_error(r); exit(EXIT_FAILURE); } nk_execute(script_file, NULL, xe.env); } case -1: { static const char errstr[] = "exec: fork failed\n"; safe_write(STDERR_FILENO, errstr, sizeof errstr); exit(EXIT_FAILURE); } default: break; } } static void process_client_socket(void) { static char buf[32]; static size_t buflen; if (buflen == sizeof buf) suicide("%s: (%s) receive buffer exhausted", client_config.interface, __func__); int r = safe_recv(scriptdSock[1], buf + buflen, sizeof buf - buflen, MSG_DONTWAIT); if (r == 0) { // Remote end hung up. exit(EXIT_SUCCESS); } else if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return; suicide("%s: (%s) error reading from ndhc -> scriptd socket: %s", client_config.interface, __func__, strerror(errno)); } buflen += (size_t)r; if (buflen > 1 || buf[0] != '\n') exit(EXIT_SUCCESS); buflen = 0; run_script(); } static void do_scriptd_work(void) { struct pollfd pfds[2] = {0}; pfds[0].fd = scriptdSock[1]; pfds[0].events = POLLIN|POLLHUP|POLLERR|POLLRDHUP; pfds[1].fd = scriptdStream[1]; pfds[1].events = POLLHUP|POLLERR|POLLRDHUP; for (;;) { if (poll(pfds, 2, -1) < 0) { if (errno != EINTR) suicide("poll failed"); } if (pfds[0].revents & POLLIN) { process_client_socket(); } if (pfds[0].revents & (POLLHUP|POLLERR|POLLRDHUP)) { suicide("scriptdSock closed unexpectedly"); } if (pfds[1].revents & (POLLHUP|POLLERR|POLLRDHUP)) { exit(EXIT_SUCCESS); } } } static void signal_handler(int signo) { int serrno = errno; if (signo == SIGCHLD) { while (waitpid(-1, NULL, WNOHANG) > 0); } else if (signo == SIGINT || signo == SIGTERM) { _exit(EXIT_FAILURE); } errno = serrno; } static void setup_signals_scriptd(void) { static const int ss[] = { SIGCHLD, SIGINT, SIGTERM, SIGKILL }; sigset_t mask; if (sigprocmask(0, 0, &mask) < 0) suicide("sigprocmask failed"); for (int i = 0; ss[i] != SIGKILL; ++i) if (sigdelset(&mask, ss[i])) suicide("sigdelset failed"); if (sigaddset(&mask, SIGPIPE)) suicide("sigaddset failed"); if (sigprocmask(SIG_SETMASK, &mask, (sigset_t *)0) < 0) suicide("sigprocmask failed"); struct sigaction sa = { .sa_handler = signal_handler, .sa_flags = SA_RESTART|SA_NOCLDWAIT, }; if (sigemptyset(&sa.sa_mask)) suicide("sigemptyset failed"); for (int i = 0; ss[i] != SIGKILL; ++i) if (sigaction(ss[i], &sa, NULL)) suicide("sigaction failed"); } void scriptd_main(void) { assert(valid_script_file); prctl(PR_SET_NAME, "ndhc: scriptd"); umask(077); setup_signals_scriptd(); do_scriptd_work(); }