2022-02-07 06:35:29 +05:30
|
|
|
// Copyright 2018 Nicholas J. Kain <njkain at gmail dot com>
|
|
|
|
// SPDX-License-Identifier: MIT
|
2014-04-15 00:36:31 +05:30
|
|
|
#include <stdio.h>
|
2016-05-07 02:14:10 +05:30
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2014-04-15 00:36:31 +05:30
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include "ndhc-defines.h"
|
|
|
|
#include "cfg.h"
|
|
|
|
#include "arp.h"
|
|
|
|
#include "ndhc.h"
|
|
|
|
#include "ifchd.h"
|
|
|
|
#include "sockd.h"
|
|
|
|
#include "nk/log.h"
|
2020-10-20 16:14:31 +05:30
|
|
|
#include "nk/privs.h"
|
2016-05-07 02:14:10 +05:30
|
|
|
#include "nk/io.h"
|
2014-04-15 00:36:31 +05:30
|
|
|
|
2022-02-25 17:31:55 +05:30
|
|
|
static void copy_cmdarg(char *dest, const char *src,
|
|
|
|
size_t destlen, const char *argname)
|
|
|
|
{
|
2022-08-27 13:23:48 +05:30
|
|
|
if (!memccpy(dest, src, 0, destlen))
|
2022-02-25 17:31:55 +05:30
|
|
|
suicide("snprintf failed on %s", argname);
|
|
|
|
}
|
|
|
|
|
2014-04-15 00:36:31 +05:30
|
|
|
struct cfgparse {
|
|
|
|
char buf[MAX_BUF];
|
|
|
|
size_t buflen;
|
|
|
|
int ternary; // = 0 nothing, -1 = false, +1 = true
|
|
|
|
int cs;
|
|
|
|
};
|
|
|
|
|
|
|
|
%%{
|
|
|
|
machine cfg_actions;
|
|
|
|
access ccfg.;
|
|
|
|
|
2014-04-16 00:26:35 +05:30
|
|
|
action clear {
|
|
|
|
memset(&ccfg.buf, 0, sizeof ccfg.buf);
|
|
|
|
ccfg.buflen = 0;
|
|
|
|
ccfg.ternary = 0;
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
action append {
|
|
|
|
if (ccfg.buflen < sizeof ccfg.buf - 1)
|
|
|
|
ccfg.buf[ccfg.buflen++] = *p;
|
|
|
|
else
|
|
|
|
suicide("line or option is too long");
|
|
|
|
}
|
|
|
|
action term {
|
|
|
|
if (ccfg.buflen < sizeof ccfg.buf)
|
|
|
|
ccfg.buf[ccfg.buflen] = 0;
|
|
|
|
}
|
|
|
|
action truval { ccfg.ternary = 1; }
|
|
|
|
action falsval { ccfg.ternary = -1; }
|
|
|
|
|
|
|
|
action clientid { get_clientid_string(ccfg.buf, ccfg.buflen); }
|
|
|
|
action hostname {
|
|
|
|
copy_cmdarg(client_config.hostname, ccfg.buf,
|
|
|
|
sizeof client_config.hostname, "hostname");
|
|
|
|
}
|
|
|
|
action interface {
|
|
|
|
copy_cmdarg(client_config.interface, ccfg.buf,
|
|
|
|
sizeof client_config.interface, "interface");
|
|
|
|
}
|
|
|
|
action now {
|
|
|
|
switch (ccfg.ternary) {
|
2017-01-19 15:43:30 +05:30
|
|
|
case 1: client_config.abort_if_no_lease = true; break;
|
|
|
|
case -1: client_config.abort_if_no_lease = false; default: break;
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
action request { set_client_addr(ccfg.buf); }
|
|
|
|
action vendorid {
|
|
|
|
copy_cmdarg(client_config.vendor, ccfg.buf,
|
|
|
|
sizeof client_config.vendor, "vendorid");
|
|
|
|
}
|
|
|
|
action user {
|
|
|
|
if (nk_uidgidbyname(ccfg.buf, &ndhc_uid, &ndhc_gid))
|
|
|
|
suicide("invalid ndhc user '%s' specified", ccfg.buf);
|
|
|
|
}
|
|
|
|
action ifch_user {
|
|
|
|
if (nk_uidgidbyname(ccfg.buf, &ifch_uid, &ifch_gid))
|
|
|
|
suicide("invalid ifch user '%s' specified", ccfg.buf);
|
|
|
|
}
|
|
|
|
action sockd_user {
|
|
|
|
if (nk_uidgidbyname(ccfg.buf, &sockd_uid, &sockd_gid))
|
|
|
|
suicide("invalid sockd user '%s' specified", ccfg.buf);
|
|
|
|
}
|
|
|
|
action chroot {
|
|
|
|
copy_cmdarg(chroot_dir, ccfg.buf, sizeof chroot_dir, "chroot");
|
|
|
|
}
|
|
|
|
action state_dir {
|
|
|
|
copy_cmdarg(state_dir, ccfg.buf, sizeof state_dir, "state-dir");
|
|
|
|
}
|
2022-02-24 11:22:26 +05:30
|
|
|
action script_file {
|
|
|
|
copy_cmdarg(script_file, ccfg.buf, sizeof script_file, "script-file");
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
action seccomp_enforce {
|
2018-02-09 14:03:04 +05:30
|
|
|
log_line("seccomp_enforce option is deprecated; please remove it");
|
|
|
|
log_line("In the meanwhile, it is ignored and seccomp is disabled.");
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
action relentless_defense {
|
|
|
|
switch (ccfg.ternary) {
|
|
|
|
case 1: set_arp_relentless_def(true); break;
|
|
|
|
case -1: set_arp_relentless_def(false); default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
action arp_probe_wait {
|
|
|
|
int t = atoi(ccfg.buf);
|
|
|
|
if (t >= 0)
|
2022-08-10 21:02:30 +05:30
|
|
|
arp_probe_wait = (unsigned)t;
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
action arp_probe_num {
|
|
|
|
int t = atoi(ccfg.buf);
|
|
|
|
if (t >= 0)
|
2022-08-10 21:02:30 +05:30
|
|
|
arp_probe_num = (unsigned)t;
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
action arp_probe_min {
|
2022-08-10 21:02:30 +05:30
|
|
|
int ti = atoi(ccfg.buf);
|
|
|
|
if (ti >= 0) {
|
|
|
|
unsigned t = (unsigned)ti;
|
2014-04-15 00:36:31 +05:30
|
|
|
arp_probe_min = t;
|
2022-08-10 21:02:30 +05:30
|
|
|
if (arp_probe_min > arp_probe_max) {
|
|
|
|
t = arp_probe_max;
|
|
|
|
arp_probe_max = arp_probe_min;
|
|
|
|
arp_probe_min = t;
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
action arp_probe_max {
|
2022-08-10 21:02:30 +05:30
|
|
|
int ti = atoi(ccfg.buf);
|
|
|
|
if (ti >= 0) {
|
|
|
|
unsigned t = (unsigned)ti;
|
|
|
|
arp_probe_max = t;
|
|
|
|
if (arp_probe_min > arp_probe_max) {
|
|
|
|
t = arp_probe_max;
|
|
|
|
arp_probe_max = arp_probe_min;
|
|
|
|
arp_probe_min = t;
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
action gw_metric {
|
|
|
|
char *q;
|
|
|
|
long mt = strtol(ccfg.buf, &q, 10);
|
|
|
|
if (q == ccfg.buf)
|
|
|
|
suicide("gw-metric arg '%s' isn't a valid number", ccfg.buf);
|
|
|
|
if (mt > INT_MAX)
|
|
|
|
suicide("gw-metric arg '%s' is too large", ccfg.buf);
|
|
|
|
if (mt < 0)
|
|
|
|
mt = 0;
|
|
|
|
client_config.metric = (int)mt;
|
|
|
|
}
|
|
|
|
action resolv_conf {
|
|
|
|
copy_cmdarg(resolv_conf_d, ccfg.buf, sizeof resolv_conf_d,
|
|
|
|
"resolv-conf");
|
|
|
|
}
|
|
|
|
action dhcp_set_hostname {
|
|
|
|
switch (ccfg.ternary) {
|
|
|
|
case 1: allow_hostname = 1; break;
|
|
|
|
case -1: allow_hostname = 0; default: break;
|
|
|
|
}
|
|
|
|
}
|
2015-02-14 02:55:36 +05:30
|
|
|
action rfkill_idx {
|
2018-02-09 13:09:46 +05:30
|
|
|
uint32_t t = (uint32_t)atoi(ccfg.buf);
|
2015-02-14 02:55:36 +05:30
|
|
|
client_config.rfkillIdx = t;
|
2017-01-19 15:43:30 +05:30
|
|
|
client_config.enable_rfkill = true;
|
2015-02-14 02:55:36 +05:30
|
|
|
}
|
2022-02-13 03:01:39 +05:30
|
|
|
action s6_notify {
|
|
|
|
client_config.s6_notify_fd = atoi(ccfg.buf);
|
|
|
|
client_config.enable_s6_notify = true;
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
action version { print_version(); exit(EXIT_SUCCESS); }
|
|
|
|
action help { show_usage(); exit(EXIT_SUCCESS); }
|
|
|
|
}%%
|
|
|
|
|
|
|
|
%%{
|
|
|
|
machine file_cfg;
|
|
|
|
access ccfg.;
|
|
|
|
include cfg_actions;
|
|
|
|
|
|
|
|
spc = [ \t];
|
|
|
|
delim = spc* '=' spc*;
|
|
|
|
string = [^\n]+ >clear $append %term;
|
|
|
|
term = '\n';
|
|
|
|
value = delim string term;
|
|
|
|
truval = ('true'|'1') % truval;
|
|
|
|
falsval = ('false'|'0') % falsval;
|
|
|
|
boolval = delim (truval|falsval) term;
|
|
|
|
|
|
|
|
blankline = term;
|
|
|
|
|
|
|
|
clientid = 'clientid' value @clientid;
|
|
|
|
hostname = 'hostname' value @hostname;
|
|
|
|
interface = 'interface' value @interface;
|
|
|
|
now = 'now' boolval @now;
|
|
|
|
request = 'request' value @request;
|
|
|
|
vendorid = 'vendorid' value @vendorid;
|
|
|
|
user = 'user' value @user;
|
|
|
|
ifch_user = 'ifch-user' value @ifch_user;
|
|
|
|
sockd_user = 'sockd-user' value @sockd_user;
|
|
|
|
chroot = 'chroot' value @chroot;
|
|
|
|
state_dir = 'state-dir' value @state_dir;
|
2022-02-24 11:22:26 +05:30
|
|
|
script_file = 'script-file' value @script_file;
|
2014-04-15 00:36:31 +05:30
|
|
|
seccomp_enforce = 'seccomp-enforce' boolval @seccomp_enforce;
|
|
|
|
relentless_defense = 'relentless-defense' boolval @relentless_defense;
|
|
|
|
arp_probe_wait = 'arp-probe-wait' value @arp_probe_wait;
|
|
|
|
arp_probe_num = 'arp-probe-num' value @arp_probe_num;
|
|
|
|
arp_probe_min = 'arp-probe-min' value @arp_probe_min;
|
|
|
|
arp_probe_max = 'arp-probe-max' value @arp_probe_max;
|
|
|
|
gw_metric = 'gw-metric' value @gw_metric;
|
|
|
|
resolv_conf = 'resolv-conf' value @resolv_conf;
|
|
|
|
dhcp_set_hostname = 'dhcp-set-hostname' boolval @dhcp_set_hostname;
|
2015-02-14 02:55:36 +05:30
|
|
|
rfkill_idx = 'rfkill-idx' value @rfkill_idx;
|
2022-02-13 03:01:39 +05:30
|
|
|
s6_notify = 's6-notify' value @s6_notify;
|
2014-04-15 00:36:31 +05:30
|
|
|
|
|
|
|
main := blankline |
|
2020-10-20 16:24:17 +05:30
|
|
|
clientid | hostname | interface | now |
|
2014-04-15 00:36:31 +05:30
|
|
|
request | vendorid | user | ifch_user | sockd_user | chroot |
|
2022-02-24 11:22:26 +05:30
|
|
|
state_dir | script_file | seccomp_enforce | relentless_defense |
|
|
|
|
arp_probe_wait | arp_probe_num | arp_probe_min | arp_probe_max |
|
|
|
|
gw_metric | resolv_conf | dhcp_set_hostname | rfkill_idx | s6_notify
|
2014-04-15 00:36:31 +05:30
|
|
|
;
|
|
|
|
}%%
|
|
|
|
|
|
|
|
%% write data;
|
|
|
|
|
2022-01-12 09:05:19 +05:30
|
|
|
static void parse_cfgfile(const char *fname)
|
2014-04-15 00:36:31 +05:30
|
|
|
{
|
2016-05-07 02:14:10 +05:30
|
|
|
bool reached_eof = false;
|
2014-04-15 00:36:31 +05:30
|
|
|
struct cfgparse ccfg;
|
|
|
|
memset(&ccfg, 0, sizeof ccfg);
|
|
|
|
char l[MAX_BUF];
|
2016-05-07 02:14:10 +05:30
|
|
|
size_t lc = 0;
|
|
|
|
memset(l, 0, sizeof l);
|
|
|
|
int fd = open(fname, O_RDONLY|O_CLOEXEC, 0);
|
|
|
|
if (fd < 0)
|
|
|
|
suicide("Unable to open config file '%s'.", fname);
|
|
|
|
|
2014-04-15 00:36:31 +05:30
|
|
|
size_t linenum = 0;
|
2016-05-07 02:14:10 +05:30
|
|
|
for (;;) {
|
|
|
|
if (lc + 1 >= sizeof l) suicide("sizeof l - 1 - lc would underflow");
|
|
|
|
ssize_t rc = safe_read(fd, l + lc, sizeof l - 1 - lc);
|
|
|
|
if (rc < 0)
|
|
|
|
suicide("Error reading config file '%s'.", fname);
|
|
|
|
if (rc == 0) {
|
|
|
|
l[lc] = '\n'; rc = 1; reached_eof = true; // Emulate a LF to terminate the line.
|
|
|
|
}
|
2018-02-09 13:09:46 +05:30
|
|
|
lc += (size_t)rc;
|
2016-05-07 02:14:10 +05:30
|
|
|
|
|
|
|
size_t lstart = 0, lend = 0, consumed = 0;
|
|
|
|
for (; lend < lc; ++lend) {
|
|
|
|
if (l[lend] == '\n') {
|
|
|
|
++linenum; consumed = lend;
|
2014-04-15 00:36:31 +05:30
|
|
|
|
2016-05-07 02:14:10 +05:30
|
|
|
size_t llen = lend - lstart;
|
|
|
|
const char *p = l + lstart;
|
|
|
|
const char *pe = l + lstart + llen + 1;
|
|
|
|
%% write init;
|
|
|
|
%% write exec;
|
|
|
|
|
|
|
|
if (ccfg.cs == file_cfg_error)
|
|
|
|
suicide("error parsing config file line %zu: malformed", linenum);
|
|
|
|
if (ccfg.cs < file_cfg_first_final)
|
|
|
|
suicide("error parsing config file line %zu: incomplete", linenum);
|
|
|
|
lstart = lend + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (reached_eof)
|
|
|
|
break;
|
|
|
|
if (!consumed && lend >= sizeof l - 1)
|
2020-11-25 07:32:51 +05:30
|
|
|
suicide("Line %zu in config file '%s' is too long: %zu > %zu.",
|
2016-05-07 02:14:10 +05:30
|
|
|
linenum, fname, lend, sizeof l - 1);
|
|
|
|
|
|
|
|
if (consumed + 1 > lc) suicide("lc[%zu] - consumed[%zu] would underflow", lc, lend);
|
|
|
|
if (consumed) {
|
|
|
|
memmove(l, l + consumed + 1, lc - consumed - 1);
|
|
|
|
lc -= consumed + 1;
|
|
|
|
}
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
2016-05-07 02:14:10 +05:30
|
|
|
close(fd);
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
%%{
|
|
|
|
machine cmd_cfg;
|
|
|
|
access ccfg.;
|
|
|
|
include cfg_actions;
|
|
|
|
|
|
|
|
action cfgfile { parse_cfgfile(ccfg.buf); }
|
|
|
|
action tbv { ccfg.ternary = 1; }
|
|
|
|
|
|
|
|
string = [^\0]+ >clear $append %term;
|
|
|
|
argval = 0 string 0;
|
|
|
|
tbv = 0 % tbv;
|
|
|
|
|
|
|
|
cfgfile = ('-c'|'--config') argval @cfgfile;
|
|
|
|
clientid = ('-I'|'--clientid') argval @clientid;
|
|
|
|
hostname = ('-h'|'--hostname') argval @hostname;
|
|
|
|
interface = ('-i'|'--interface') argval @interface;
|
|
|
|
now = ('-n'|'--now') tbv @now;
|
|
|
|
request = ('-r'|'--request') argval @request;
|
|
|
|
vendorid = ('-V'|'--vendorid') argval @vendorid;
|
|
|
|
user = ('-u'|'--user') argval @user;
|
|
|
|
ifch_user = ('-U'|'--ifch-user') argval @ifch_user;
|
|
|
|
sockd_user = ('-D'|'--sockd-user') argval @sockd_user;
|
|
|
|
chroot = ('-C'|'--chroot') argval @chroot;
|
|
|
|
state_dir = ('-s'|'--state-dir') argval @state_dir;
|
2022-02-24 11:22:26 +05:30
|
|
|
script_file = ('-X'|'--script-file') argval @script_file;
|
2014-04-15 00:36:31 +05:30
|
|
|
seccomp_enforce = ('-S'|'--seccomp-enforce') tbv @seccomp_enforce;
|
|
|
|
relentless_defense = ('-d'|'--relentless-defense') tbv @relentless_defense;
|
|
|
|
arp_probe_wait = ('-w'|'--arp-probe-wait') argval @arp_probe_wait;
|
|
|
|
arp_probe_num = ('-W'|'--arp-probe-num') argval @arp_probe_num;
|
|
|
|
arp_probe_min = ('-m'|'--arp-probe-min') argval @arp_probe_min;
|
|
|
|
arp_probe_max = ('-M'|'--arp-probe-max') argval @arp_probe_max;
|
|
|
|
gw_metric = ('-t'|'--gw-metric') argval @gw_metric;
|
|
|
|
resolv_conf = ('-R'|'--resolv-conf') argval @resolv_conf;
|
|
|
|
dhcp_set_hostname = ('-H'|'--dhcp-set-hostname') tbv @dhcp_set_hostname;
|
2015-02-14 02:55:36 +05:30
|
|
|
rfkill_idx = ('-K'|'--rfkill-idx') argval @rfkill_idx;
|
2022-02-13 03:01:39 +05:30
|
|
|
s6_notify = ('-N'|'--s6-notify') argval @s6_notify;
|
2014-04-15 00:36:31 +05:30
|
|
|
version = ('-v'|'--version') 0 @version;
|
|
|
|
help = ('-?'|'--help') 0 @help;
|
|
|
|
|
|
|
|
main := (
|
2022-02-24 11:22:26 +05:30
|
|
|
cfgfile | clientid | hostname | interface | now | request | vendorid |
|
|
|
|
user | ifch_user | sockd_user | chroot | state_dir | script_file |
|
|
|
|
seccomp_enforce | relentless_defense | arp_probe_wait | arp_probe_num |
|
|
|
|
arp_probe_min | arp_probe_max | gw_metric | resolv_conf |
|
|
|
|
dhcp_set_hostname | rfkill_idx | s6_notify | version | help
|
2014-04-15 00:36:31 +05:30
|
|
|
)*;
|
|
|
|
}%%
|
|
|
|
|
|
|
|
%% write data;
|
|
|
|
|
|
|
|
void parse_cmdline(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
char argb[8192];
|
|
|
|
size_t argbl = 0;
|
|
|
|
for (size_t i = 1; i < (size_t)argc; ++i) {
|
|
|
|
ssize_t snl;
|
2022-08-09 23:47:31 +05:30
|
|
|
if (i > 1) snl = snprintf(argb + argbl, sizeof argb - argbl, "%c%s", 0, argv[i]);
|
|
|
|
else snl = snprintf(argb + argbl, sizeof argb - argbl, "%s", argv[i]);
|
2022-02-25 16:41:16 +05:30
|
|
|
if (snl < 0 || (size_t)snl > sizeof argb)
|
2014-04-15 00:36:31 +05:30
|
|
|
suicide("error parsing command line option: option too long");
|
2018-02-09 13:09:46 +05:30
|
|
|
argbl += (size_t)snl;
|
2014-04-15 00:36:31 +05:30
|
|
|
}
|
2014-04-21 21:34:13 +05:30
|
|
|
if (argbl == 0)
|
|
|
|
return;
|
2014-04-15 00:36:31 +05:30
|
|
|
struct cfgparse ccfg;
|
|
|
|
memset(&ccfg, 0, sizeof ccfg);
|
|
|
|
const char *p = argb;
|
|
|
|
const char *pe = argb + argbl + 1;
|
|
|
|
const char *eof = pe;
|
|
|
|
|
|
|
|
%% write init;
|
|
|
|
%% write exec;
|
|
|
|
|
|
|
|
if (ccfg.cs == cmd_cfg_error)
|
|
|
|
suicide("error parsing command line option: malformed");
|
|
|
|
if (ccfg.cs >= cmd_cfg_first_final)
|
|
|
|
return;
|
|
|
|
suicide("error parsing command line option: incomplete");
|
|
|
|
}
|
|
|
|
|