Support running an executable file when a new lease is acquired.
If no 'script-file = SCRIPTFILE' is specified in the configuration file and if no '-X SCRIPTFILE' or '--script-file SCRIPTFILE' command argument is provided, then this functionality is entirely inactive and no associated subprocess is spawned. Otherwise, ndhc will spawn a subprocess that runs as root that has the sole job of forking off a subprocess that exec's the specified script in a sanitized and fixed-state environment whenever a new DHCPv4 lease is acquired. Note that this script is provided no information about ndhc or the DHCP state in the environment or in any argument fields; it is the responsibility of this script to gather whatever information it needs from either the filesystem or syscalls. This design is intended to avoid the historical problems that are associated with dhcp clients invoking scripts. The path of the scriptfile cannot be changed after ndhc is initially run; ndhc forks off the privsep script subprocess that executes scripts after it has read the configuration file and command arguments, but before it begins processing network data; thus, it is impossible for the network-handling process to modify or influence the script assuming proper OS memory protection. The privsep channel communicates that the script should be run by simply writing a newline; anything else will result in ndhc terminating itself. Before the recommended way to update system state after a change in lease information was to run the fcactus program and watch the associated leasefile for the interface for modification; now no external program is needed for this job.
This commit is contained in:
parent
2fb16567f1
commit
a9874d4959
22
cfg.rl
22
cfg.rl
@ -85,6 +85,9 @@ struct cfgparse {
|
|||||||
action state_dir {
|
action state_dir {
|
||||||
copy_cmdarg(state_dir, ccfg.buf, sizeof state_dir, "state-dir");
|
copy_cmdarg(state_dir, ccfg.buf, sizeof state_dir, "state-dir");
|
||||||
}
|
}
|
||||||
|
action script_file {
|
||||||
|
copy_cmdarg(script_file, ccfg.buf, sizeof script_file, "script-file");
|
||||||
|
}
|
||||||
action seccomp_enforce {
|
action seccomp_enforce {
|
||||||
log_line("seccomp_enforce option is deprecated; please remove it");
|
log_line("seccomp_enforce option is deprecated; please remove it");
|
||||||
log_line("In the meanwhile, it is ignored and seccomp is disabled.");
|
log_line("In the meanwhile, it is ignored and seccomp is disabled.");
|
||||||
@ -184,6 +187,7 @@ struct cfgparse {
|
|||||||
sockd_user = 'sockd-user' value @sockd_user;
|
sockd_user = 'sockd-user' value @sockd_user;
|
||||||
chroot = 'chroot' value @chroot;
|
chroot = 'chroot' value @chroot;
|
||||||
state_dir = 'state-dir' value @state_dir;
|
state_dir = 'state-dir' value @state_dir;
|
||||||
|
script_file = 'script-file' value @script_file;
|
||||||
seccomp_enforce = 'seccomp-enforce' boolval @seccomp_enforce;
|
seccomp_enforce = 'seccomp-enforce' boolval @seccomp_enforce;
|
||||||
relentless_defense = 'relentless-defense' boolval @relentless_defense;
|
relentless_defense = 'relentless-defense' boolval @relentless_defense;
|
||||||
arp_probe_wait = 'arp-probe-wait' value @arp_probe_wait;
|
arp_probe_wait = 'arp-probe-wait' value @arp_probe_wait;
|
||||||
@ -199,9 +203,9 @@ struct cfgparse {
|
|||||||
main := blankline |
|
main := blankline |
|
||||||
clientid | hostname | interface | now |
|
clientid | hostname | interface | now |
|
||||||
request | vendorid | user | ifch_user | sockd_user | chroot |
|
request | vendorid | user | ifch_user | sockd_user | chroot |
|
||||||
state_dir | seccomp_enforce | relentless_defense | arp_probe_wait |
|
state_dir | script_file | seccomp_enforce | relentless_defense |
|
||||||
arp_probe_num | arp_probe_min | arp_probe_max | gw_metric |
|
arp_probe_wait | arp_probe_num | arp_probe_min | arp_probe_max |
|
||||||
resolv_conf | dhcp_set_hostname | rfkill_idx | s6_notify
|
gw_metric | resolv_conf | dhcp_set_hostname | rfkill_idx | s6_notify
|
||||||
;
|
;
|
||||||
}%%
|
}%%
|
||||||
|
|
||||||
@ -287,6 +291,7 @@ static void parse_cfgfile(const char *fname)
|
|||||||
sockd_user = ('-D'|'--sockd-user') argval @sockd_user;
|
sockd_user = ('-D'|'--sockd-user') argval @sockd_user;
|
||||||
chroot = ('-C'|'--chroot') argval @chroot;
|
chroot = ('-C'|'--chroot') argval @chroot;
|
||||||
state_dir = ('-s'|'--state-dir') argval @state_dir;
|
state_dir = ('-s'|'--state-dir') argval @state_dir;
|
||||||
|
script_file = ('-X'|'--script-file') argval @script_file;
|
||||||
seccomp_enforce = ('-S'|'--seccomp-enforce') tbv @seccomp_enforce;
|
seccomp_enforce = ('-S'|'--seccomp-enforce') tbv @seccomp_enforce;
|
||||||
relentless_defense = ('-d'|'--relentless-defense') tbv @relentless_defense;
|
relentless_defense = ('-d'|'--relentless-defense') tbv @relentless_defense;
|
||||||
arp_probe_wait = ('-w'|'--arp-probe-wait') argval @arp_probe_wait;
|
arp_probe_wait = ('-w'|'--arp-probe-wait') argval @arp_probe_wait;
|
||||||
@ -302,12 +307,11 @@ static void parse_cfgfile(const char *fname)
|
|||||||
help = ('-?'|'--help') 0 @help;
|
help = ('-?'|'--help') 0 @help;
|
||||||
|
|
||||||
main := (
|
main := (
|
||||||
cfgfile | clientid | hostname | interface |
|
cfgfile | clientid | hostname | interface | now | request | vendorid |
|
||||||
now | request | vendorid | user | ifch_user | sockd_user |
|
user | ifch_user | sockd_user | chroot | state_dir | script_file |
|
||||||
chroot | state_dir | seccomp_enforce | relentless_defense |
|
seccomp_enforce | relentless_defense | arp_probe_wait | arp_probe_num |
|
||||||
arp_probe_wait | arp_probe_num | arp_probe_min | arp_probe_max |
|
arp_probe_min | arp_probe_max | gw_metric | resolv_conf |
|
||||||
gw_metric | resolv_conf | dhcp_set_hostname | rfkill_idx | s6_notify |
|
dhcp_set_hostname | rfkill_idx | s6_notify | version | help
|
||||||
version | help
|
|
||||||
)*;
|
)*;
|
||||||
}%%
|
}%%
|
||||||
|
|
||||||
|
23
leasefile.c
23
leasefile.c
@ -15,6 +15,7 @@
|
|||||||
#include "nk/io.h"
|
#include "nk/io.h"
|
||||||
#include "leasefile.h"
|
#include "leasefile.h"
|
||||||
#include "ndhc.h"
|
#include "ndhc.h"
|
||||||
|
#include "scriptd.h"
|
||||||
|
|
||||||
static int leasefilefd = -1;
|
static int leasefilefd = -1;
|
||||||
|
|
||||||
@ -40,15 +41,8 @@ void open_leasefile(void)
|
|||||||
client_config.interface, __func__, leasefile, strerror(errno));
|
client_config.interface, __func__, leasefile, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_leasefile(struct in_addr ipnum)
|
static void do_write_leasefile(struct in_addr ipnum)
|
||||||
{
|
{
|
||||||
if (client_config.enable_s6_notify) {
|
|
||||||
static char buf[] = "\n";
|
|
||||||
safe_write(client_config.s6_notify_fd, buf, 1);
|
|
||||||
close(client_config.s6_notify_fd);
|
|
||||||
client_config.enable_s6_notify = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char ip[INET_ADDRSTRLEN];
|
char ip[INET_ADDRSTRLEN];
|
||||||
char out[INET_ADDRSTRLEN*2];
|
char out[INET_ADDRSTRLEN*2];
|
||||||
if (leasefilefd < 0) {
|
if (leasefilefd < 0) {
|
||||||
@ -82,3 +76,16 @@ void write_leasefile(struct in_addr ipnum)
|
|||||||
fsync(leasefilefd);
|
fsync(leasefilefd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void write_leasefile(struct in_addr ipnum)
|
||||||
|
{
|
||||||
|
do_write_leasefile(ipnum);
|
||||||
|
request_scriptd_run();
|
||||||
|
|
||||||
|
if (client_config.enable_s6_notify) {
|
||||||
|
static char buf[] = "\n";
|
||||||
|
safe_write(client_config.s6_notify_fd, buf, 1);
|
||||||
|
close(client_config.s6_notify_fd);
|
||||||
|
client_config.enable_s6_notify = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
12
ndhc.8
12
ndhc.8
@ -41,8 +41,7 @@ Specifies the directory where the DHCP state associated with the given
|
|||||||
interface will be stored. Such state will include the leased IP, the
|
interface will be stored. Such state will include the leased IP, the
|
||||||
IAID, and the DUID. The file representing the leased IP can be quite
|
IAID, and the DUID. The file representing the leased IP can be quite
|
||||||
useful for reacting to changes in IP address -- one can listen for changes
|
useful for reacting to changes in IP address -- one can listen for changes
|
||||||
to it using fanotify() or inotify() on Linux. The 'fcactus' program
|
to it using fanotify() or inotify() on Linux.
|
||||||
written by the ndhc author is designed to perform this task.
|
|
||||||
.TP
|
.TP
|
||||||
.BI \-i\ INTERFACE ,\ \-\-interface= INTERFACE
|
.BI \-i\ INTERFACE ,\ \-\-interface= INTERFACE
|
||||||
Act as a DHCP client for the specified interface. A single ndhc daemon can
|
Act as a DHCP client for the specified interface. A single ndhc daemon can
|
||||||
@ -131,6 +130,15 @@ and closed when the first DHCP lease is bound. This option should be used when
|
|||||||
ndhc is run under a s6 supervisor that implements service startup
|
ndhc is run under a s6 supervisor that implements service startup
|
||||||
notifications.
|
notifications.
|
||||||
.TP
|
.TP
|
||||||
|
.BI \-X\ SCRIPTFILE ,\ \-\-script\-file= SCRIPTFILE
|
||||||
|
If set, ndhc will spawn a subprocess that has the exclusive job of executing
|
||||||
|
the specified executable file immediately after a new lease is acquired. This
|
||||||
|
script file will run as root and will not be chrooted. It will be provided a
|
||||||
|
sanitized environment that has no inputs from the dhcp state. If this
|
||||||
|
parameter is not provided, then the ndhc-scriptd subprocess will not exist.
|
||||||
|
This facility is intended to be used for updating firewall/nat rules or similar
|
||||||
|
tasks.
|
||||||
|
.TP
|
||||||
.BI \-v ,\ \-\-version
|
.BI \-v ,\ \-\-version
|
||||||
Display the ndhc version number.
|
Display the ndhc version number.
|
||||||
.SH SIGNALS
|
.SH SIGNALS
|
||||||
|
38
ndhc.c
38
ndhc.c
@ -43,6 +43,7 @@
|
|||||||
#include "duiaid.h"
|
#include "duiaid.h"
|
||||||
#include "sockd.h"
|
#include "sockd.h"
|
||||||
#include "rfkill.h"
|
#include "rfkill.h"
|
||||||
|
#include "scriptd.h"
|
||||||
|
|
||||||
struct client_state_t cs = {
|
struct client_state_t cs = {
|
||||||
.program_init = true,
|
.program_init = true,
|
||||||
@ -400,12 +401,15 @@ static void do_ndhc_work(void)
|
|||||||
char state_dir[PATH_MAX] = "/etc/ndhc";
|
char state_dir[PATH_MAX] = "/etc/ndhc";
|
||||||
char chroot_dir[PATH_MAX] = "";
|
char chroot_dir[PATH_MAX] = "";
|
||||||
char resolv_conf_d[PATH_MAX] = "";
|
char resolv_conf_d[PATH_MAX] = "";
|
||||||
|
char script_file[PATH_MAX] = "";
|
||||||
uid_t ndhc_uid = 0;
|
uid_t ndhc_uid = 0;
|
||||||
gid_t ndhc_gid = 0;
|
gid_t ndhc_gid = 0;
|
||||||
int ifchSock[2];
|
int ifchSock[2];
|
||||||
int sockdSock[2];
|
|
||||||
int ifchStream[2];
|
int ifchStream[2];
|
||||||
|
int sockdSock[2];
|
||||||
int sockdStream[2];
|
int sockdStream[2];
|
||||||
|
int scriptdSock[2];
|
||||||
|
int scriptdStream[2];
|
||||||
|
|
||||||
static void create_ifch_ipc_sockets(void) {
|
static void create_ifch_ipc_sockets(void) {
|
||||||
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, ifchSock) < 0)
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, ifchSock) < 0)
|
||||||
@ -418,7 +422,14 @@ static void create_sockd_ipc_sockets(void) {
|
|||||||
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockdSock) < 0)
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockdSock) < 0)
|
||||||
suicide("FATAL - can't create ndhc/sockd socket: %s", strerror(errno));
|
suicide("FATAL - can't create ndhc/sockd socket: %s", strerror(errno));
|
||||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockdStream) < 0)
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockdStream) < 0)
|
||||||
suicide("FATAL - can't create ndhc/ifch socket: %s", strerror(errno));
|
suicide("FATAL - can't create ndhc/sockd socket: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_scriptd_ipc_sockets(void) {
|
||||||
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, scriptdSock) < 0)
|
||||||
|
suicide("FATAL - can't create ndhc/scriptd socket: %s", strerror(errno));
|
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, scriptdStream) < 0)
|
||||||
|
suicide("FATAL - can't create ndhc/scriptd socket: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void spawn_ifch(void)
|
static void spawn_ifch(void)
|
||||||
@ -455,6 +466,28 @@ static void spawn_sockd(void)
|
|||||||
suicide("failed to fork ndhc-sockd: %s", strerror(errno));
|
suicide("failed to fork ndhc-sockd: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void spawn_scriptd(void)
|
||||||
|
{
|
||||||
|
valid_script_file = access(script_file, R_OK | X_OK) == 0;
|
||||||
|
if (!valid_script_file) return;
|
||||||
|
|
||||||
|
log_line("Found script file: '%s'", script_file);
|
||||||
|
|
||||||
|
create_scriptd_ipc_sockets();
|
||||||
|
pid_t scriptd_pid = fork();
|
||||||
|
if (scriptd_pid == 0) {
|
||||||
|
close(scriptdSock[0]);
|
||||||
|
close(scriptdStream[0]);
|
||||||
|
// Don't share the RNG state with the master process.
|
||||||
|
nk_random_init(&cs.rnd_state);
|
||||||
|
scriptd_main();
|
||||||
|
} else if (scriptd_pid > 0) {
|
||||||
|
close(scriptdSock[1]);
|
||||||
|
close(scriptdStream[1]);
|
||||||
|
} else
|
||||||
|
suicide("failed to fork ndhc-scriptd: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
static void ndhc_main(void) {
|
static void ndhc_main(void) {
|
||||||
prctl(PR_SET_NAME, "ndhc: master");
|
prctl(PR_SET_NAME, "ndhc: master");
|
||||||
log_line("ndhc client " NDHC_VERSION " started on interface [%s].",
|
log_line("ndhc client " NDHC_VERSION " started on interface [%s].",
|
||||||
@ -548,6 +581,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
spawn_ifch();
|
spawn_ifch();
|
||||||
spawn_sockd();
|
spawn_sockd();
|
||||||
|
spawn_scriptd();
|
||||||
ndhc_main();
|
ndhc_main();
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
3
ndhc.h
3
ndhc.h
@ -69,9 +69,12 @@ extern int ifchSock[2];
|
|||||||
extern int ifchStream[2];
|
extern int ifchStream[2];
|
||||||
extern int sockdSock[2];
|
extern int sockdSock[2];
|
||||||
extern int sockdStream[2];
|
extern int sockdStream[2];
|
||||||
|
extern int scriptdSock[2];
|
||||||
|
extern int scriptdStream[2];
|
||||||
extern char state_dir[PATH_MAX];
|
extern char state_dir[PATH_MAX];
|
||||||
extern char chroot_dir[PATH_MAX];
|
extern char chroot_dir[PATH_MAX];
|
||||||
extern char resolv_conf_d[PATH_MAX];
|
extern char resolv_conf_d[PATH_MAX];
|
||||||
|
extern char script_file[PATH_MAX];
|
||||||
extern uid_t ndhc_uid;
|
extern uid_t ndhc_uid;
|
||||||
extern gid_t ndhc_gid;
|
extern gid_t ndhc_gid;
|
||||||
|
|
||||||
|
191
nk/exec.c
Normal file
191
nk/exec.c
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2003-2018 Nicholas J. Kain <njkain at gmail dot com>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include "nk/exec.h"
|
||||||
|
|
||||||
|
#define DEFAULT_ROOT_PATH "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
|
||||||
|
#define DEFAULT_PATH "/bin:/usr/bin:/usr/local/bin"
|
||||||
|
#define MAX_ARGS 256
|
||||||
|
#define MAX_ARGBUF 4096
|
||||||
|
|
||||||
|
#define NK_GEN_ENV(GEN_STR, ...) do { \
|
||||||
|
if (env_offset >= envlen) return -3; \
|
||||||
|
ssize_t snlen = snprintf(envbuf, envbuflen, GEN_STR "0", __VA_ARGS__); \
|
||||||
|
if (snlen < 0 || (size_t)snlen >= envbuflen) return -2; \
|
||||||
|
if (snlen > 0) envbuf[snlen-1] = 0; \
|
||||||
|
env[env_offset++] = envbuf; envbuf += snlen; envbuflen -= (size_t)snlen; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* uid: userid of the user account that the environment will constructed for
|
||||||
|
* chroot_path: path where the environment will be chrooted or NULL if no chroot
|
||||||
|
* path_var: value of the PATH variable in the environment or defaults if NULL
|
||||||
|
* env: array of character pointers that will be filled in with the new environment
|
||||||
|
* envlen: number of character pointers available in env; a terminal '0' ptr must be available
|
||||||
|
* envbuf: character buffer that will be used for storing state associated with env
|
||||||
|
* envbuflen: number of available characters in envbuf for use
|
||||||
|
*
|
||||||
|
* returns:
|
||||||
|
* 0 on success
|
||||||
|
* -1 if an account for uid does not exist
|
||||||
|
* -2 if there is not enough space in envbuf for the generated environment
|
||||||
|
* -3 if there is not enough space in env for the generated environment
|
||||||
|
* -4 if chdir to homedir or rootdir failed
|
||||||
|
* -5 if oom or i/o failed
|
||||||
|
*/
|
||||||
|
int nk_generate_env(uid_t uid, const char *chroot_path, const char *path_var,
|
||||||
|
char *env[], size_t envlen, char *envbuf, size_t envbuflen)
|
||||||
|
{
|
||||||
|
char pw_strs[4096];
|
||||||
|
struct passwd pw_s;
|
||||||
|
struct passwd *pw;
|
||||||
|
char *pw_buf = NULL;
|
||||||
|
int ret = 0, pwr;
|
||||||
|
|
||||||
|
getpwagain0:
|
||||||
|
pwr = getpwuid_r(uid, &pw_s, pw_strs, sizeof pw_strs, &pw);
|
||||||
|
if (pw == NULL) {
|
||||||
|
if (pwr == 0) { ret = -1; goto out; }
|
||||||
|
if (pwr == EINTR) goto getpwagain0;
|
||||||
|
if (pwr == ERANGE) {
|
||||||
|
size_t pwlen = (sizeof pw_strs >> 1) * 3;
|
||||||
|
for (;;) {
|
||||||
|
if (pw_buf) free(pw_buf);
|
||||||
|
pw_buf = malloc(pwlen);
|
||||||
|
if (!pw_buf) { ret = -5; goto out; }
|
||||||
|
getpwagain:
|
||||||
|
pwr = getpwuid_r(uid, &pw_s, pw_buf, pwlen, &pw);
|
||||||
|
if (pw == NULL) {
|
||||||
|
if (pwr == 0) { ret = -1; goto out; }
|
||||||
|
if (pwr == EINTR) goto getpwagain;
|
||||||
|
if (pwr == ERANGE) {
|
||||||
|
size_t oldpwlen = pwlen;
|
||||||
|
pwlen = (pwlen >> 1) * 3;
|
||||||
|
if (pwlen > oldpwlen) continue;
|
||||||
|
else { // overflowed
|
||||||
|
ret = -5; goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = -5; goto out;
|
||||||
|
}
|
||||||
|
break; // the pwr != 0 check below applies here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = -5; goto out;
|
||||||
|
}
|
||||||
|
if (pwr != 0) { ret = -5; goto out; }
|
||||||
|
|
||||||
|
size_t env_offset = 0;
|
||||||
|
if (envlen-- < 1) { // So we don't have to account for the terminal NULL
|
||||||
|
ret = -3;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
NK_GEN_ENV("UID=%i", uid);
|
||||||
|
NK_GEN_ENV("USER=%s", pw->pw_name);
|
||||||
|
NK_GEN_ENV("USERNAME=%s", pw->pw_name);
|
||||||
|
NK_GEN_ENV("LOGNAME=%s", pw->pw_name);
|
||||||
|
NK_GEN_ENV("HOME=%s", pw->pw_dir);
|
||||||
|
NK_GEN_ENV("SHELL=%s", pw->pw_shell);
|
||||||
|
NK_GEN_ENV("PATH=%s", path_var ? path_var : (uid > 0 ? DEFAULT_PATH : DEFAULT_ROOT_PATH));
|
||||||
|
NK_GEN_ENV("PWD=%s", !chroot_path ? pw->pw_dir : "/");
|
||||||
|
if (chroot_path && chroot(chroot_path)) { ret = -4; goto out; }
|
||||||
|
if (chdir(chroot_path ? chroot_path : "/")) { ret = -4; goto out; }
|
||||||
|
|
||||||
|
env[env_offset] = 0;
|
||||||
|
out:
|
||||||
|
free(pw_buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NK_GEN_ARG(GEN_STR, ...) do { \
|
||||||
|
ssize_t snlen = snprintf(argbuf, argbuflen, GEN_STR "0", __VA_ARGS__); \
|
||||||
|
if (snlen < 0 || (size_t)snlen >= argbuflen) { \
|
||||||
|
static const char errstr[] = "nk_execute: constructing argument list failed\n"; \
|
||||||
|
write(STDERR_FILENO, errstr, sizeof errstr); \
|
||||||
|
_Exit(EXIT_FAILURE); \
|
||||||
|
} \
|
||||||
|
if (snlen > 0) argbuf[snlen-1] = 0; \
|
||||||
|
argv[curv] = argbuf; argv[++curv] = (char *)0; \
|
||||||
|
argbuf += snlen; argbuflen -= (size_t)snlen; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||||
|
#endif
|
||||||
|
void __attribute__((noreturn))
|
||||||
|
nk_execute(const char *command, const char *args, char * const envp[])
|
||||||
|
{
|
||||||
|
char *argv[MAX_ARGS];
|
||||||
|
char argbuf_s[MAX_ARGBUF];
|
||||||
|
char *argbuf = argbuf_s;
|
||||||
|
size_t curv = 0;
|
||||||
|
size_t argbuflen = sizeof argbuf_s;
|
||||||
|
|
||||||
|
if (!command)
|
||||||
|
_Exit(EXIT_SUCCESS);
|
||||||
|
|
||||||
|
// strip the path from the command name and set argv[0]
|
||||||
|
const char *p = strrchr(command, '/');
|
||||||
|
NK_GEN_ARG("%s", p ? p + 1 : command);
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
p = args;
|
||||||
|
const char *q = args;
|
||||||
|
bool squote = false, dquote = false, atend = false;
|
||||||
|
for (;; ++p) {
|
||||||
|
switch (*p) {
|
||||||
|
default: continue;
|
||||||
|
case '\0':
|
||||||
|
atend = true;
|
||||||
|
goto endarg;
|
||||||
|
case ' ':
|
||||||
|
if (!squote && !dquote)
|
||||||
|
goto endarg;
|
||||||
|
continue;
|
||||||
|
case '\'':
|
||||||
|
if (!dquote)
|
||||||
|
squote = !squote;
|
||||||
|
continue;
|
||||||
|
case '"':
|
||||||
|
if (!squote)
|
||||||
|
dquote = !dquote;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
endarg:
|
||||||
|
{
|
||||||
|
if (p == q) break;
|
||||||
|
// Push an argument.
|
||||||
|
if (q > p) {
|
||||||
|
static const char errstr[] = "nk_execute: argument length too long\n";
|
||||||
|
write(STDERR_FILENO, errstr, sizeof errstr);
|
||||||
|
_Exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
const size_t len = (size_t)(p - q);
|
||||||
|
NK_GEN_ARG("%.*s", (int)len, q);
|
||||||
|
q = p + 1;
|
||||||
|
if (atend || curv >= (MAX_ARGS - 1))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
execve(command, argv, envp);
|
||||||
|
{
|
||||||
|
static const char errstr[] = "nk_execute: execve failed\n";
|
||||||
|
write(STDERR_FILENO, errstr, sizeof errstr);
|
||||||
|
_Exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
13
nk/exec.h
Normal file
13
nk/exec.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2003-2016 Nicholas J. Kain <njkain at gmail dot com>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#ifndef NCM_EXEC_H_
|
||||||
|
#define NCM_EXEC_H_
|
||||||
|
|
||||||
|
int nk_generate_env(uid_t uid, const char *chroot_path, const char *path_var,
|
||||||
|
char *env[], size_t envlen, char *envbuf, size_t envbuflen);
|
||||||
|
void __attribute__((noreturn))
|
||||||
|
nk_execute(const char *command, const char *args, char * const envp[]) ;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
172
scriptd.c
Normal file
172
scriptd.c
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Copyright 2022 Nicholas J. Kain <njkain at gmail dot com>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include "nk/log.h"
|
||||||
|
#include "nk/io.h"
|
||||||
|
#include "nk/exec.h"
|
||||||
|
#include "scriptd.h"
|
||||||
|
#include "ndhc.h"
|
||||||
|
#include "sys.h"
|
||||||
|
|
||||||
|
#define MAX_ENVBUF 2048
|
||||||
|
#define MAX_CENV 50
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
char *env[MAX_CENV];
|
||||||
|
char envbuf[MAX_ENVBUF];
|
||||||
|
switch ((int)fork()) {
|
||||||
|
case 0: {
|
||||||
|
int r = nk_generate_env(0, NULL, NULL, env, MAX_CENV, envbuf, sizeof envbuf);
|
||||||
|
if (r < 0) {
|
||||||
|
static const char errstr[] = "exec: failed to generate environment - ";
|
||||||
|
safe_write(STDERR_FILENO, errstr, sizeof errstr);
|
||||||
|
static const char errstr0[] = "(?) unknown error";
|
||||||
|
static const char errstr1[] = "(-1) account for uid does not exist";
|
||||||
|
static const char errstr2[] = "(-2) not enough space in envbuf";
|
||||||
|
static const char errstr3[] = "(-3) not enough space in env";
|
||||||
|
static const char errstr4[] = "(-4) chdir to homedir or rootdir failed";
|
||||||
|
switch (r) {
|
||||||
|
default: safe_write(STDERR_FILENO, errstr0, sizeof errstr0); break;
|
||||||
|
case -1: safe_write(STDERR_FILENO, errstr1, sizeof errstr1); break;
|
||||||
|
case -2: safe_write(STDERR_FILENO, errstr2, sizeof errstr2); break;
|
||||||
|
case -3: safe_write(STDERR_FILENO, errstr3, sizeof errstr3); break;
|
||||||
|
case -4: safe_write(STDERR_FILENO, errstr4, sizeof errstr4); break;
|
||||||
|
}
|
||||||
|
safe_write(STDERR_FILENO, "\n", 1);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
nk_execute(script_file, NULL, 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)
|
||||||
|
{
|
||||||
|
switch (signo) {
|
||||||
|
case SIGCHLD:
|
||||||
|
while (waitpid(-1, NULL, WNOHANG) > 0);
|
||||||
|
break;
|
||||||
|
case SIGINT:
|
||||||
|
case SIGTERM: _exit(EXIT_FAILURE); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
14
scriptd.h
Normal file
14
scriptd.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2022 Nicholas J. Kain <njkain at gmail dot com>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
#ifndef NDHC_SCRIPTD_H_
|
||||||
|
#define NDHC_SCRIPTD_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
extern bool valid_script_file;
|
||||||
|
|
||||||
|
void request_scriptd_run(void);
|
||||||
|
void scriptd_main(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user