Commit Diff


commit - 1e0d215510627c58cc63bdfe4282801251ee9c75
commit + 8fff88de81a48b1dd32ae121542e09e3bc7de5bb
blob - fea4dad3c0d4a46ec4cb57de7adf7c590c028fb0
blob + 96cf3b14b0c85458806353e72e65656a9bcdcdfb
--- Makefile
+++ Makefile
@@ -29,7 +29,7 @@ SRCS+=ctl.c show.c if.c version.c route.c conf.c compl
 SRCS+=bridge.c tunnel.c media.c sysctl.c passwd.c pfsync.c carp.c
 SRCS+=trunk.c who.c more.c stringlist.c utils.c sqlite3.c ppp.c prompt.c
 SRCS+=nopt.c pflow.c wg.c nameserver.c ndp.c umb.c utf8.c cmdargs.c ctlargs.c
-SRCS+=helpcommands.c makeargv.c hashtable.c mantab.c
+SRCS+=helpcommands.c makeargv.c hashtable.c mantab.c rcctl.c
 CLEANFILES+=compile.c mantab.c
 LDADD=-lutil -ledit -ltermcap -lsqlite3 -L/usr/local/lib #-static
 
blob - b2f75596c7a2341b035284c80351db2dd73c1944
blob + 8b7578ecc43d1ab35f18bb10d0cee8dd99f424e8
--- conf.c
+++ conf.c
@@ -92,7 +92,6 @@ int default_rxprio(char *);
 int default_llpriority(char *);
 int islateif(char *);
 int isdefaultroute(struct sockaddr *, struct sockaddr *);
-int scantext(char *, char *);
 int ipv6ll_db_compare(struct sockaddr_in6 *, struct sockaddr_in6 *,
     char *);
 
blob - 2e4977cfe088faa4a96086ab42f1262d2a78f9c1
blob + 94b17ae5e3a5d3c069e228469ed61a5106204519
--- externs.h
+++ externs.h
@@ -96,6 +96,7 @@ u_long default_mtu(char *);
 int conf_routes(FILE *, char *, int, int, int);
 int conf_dhcrelay(char *, char *, int);
 int dhcpleased_has_address(char *, const char *, const char *);
+int scantext(char *, char *);
 
 /* show.c */
 void p_rttables(int, u_int, int);
@@ -187,6 +188,7 @@ extern pid_t child;
 #define HALT		"/sbin/halt"
 #define SU		"/usr/bin/su"
 #define DOAS		"/usr/bin/doas"
+#define RCCTL		"/usr/sbin/rcctl"
 #define SAVESCRIPT	"/usr/local/bin/save.sh"
 #ifndef DHCPLEASES
 #define DHCPLEASES	"/var/db/dhcpd.leases"
@@ -529,13 +531,16 @@ char *format_k(uint64_t amt);
 int db_create_table_rtables(void);
 int db_create_table_flag_x(char *);
 int db_create_table_nameservers(void);
+int db_create_table_rcctl_flags(void);
 int db_insert_flag_x(char *, char *, int, int, char *);
 int db_insert_rtables(int, char *);
+int db_insert_rcctl_flag(char *, char *, int, char *, char *);
 int db_insert_nameserver(char *);
 int db_delete_rtables_rtable(int);
 int db_delete_flag_x_ctl(char *, char *, int);
 int db_delete_flag_x_ctl_data(char *, char *, char *);
 int db_delete_nameservers(void);
+int db_delete_rcctl_flag(char *, int, char *, char *);
 #ifdef _STRINGLIST_H
 int db_select_flag_x_ctl_data(StringList *, char *, char *, char *);
 int db_select_flag_x_ctl(StringList *, char *, char *);
@@ -546,6 +551,9 @@ int db_select_name_rtable(StringList *, int);
 int db_select_flag_x_ctl_rtable(StringList *, char *, int);
 int db_select_flag_x_data_ctl_rtable(StringList *, char *, char *, int);
 int db_select_nameservers(StringList *);
+int db_select_rcctl_options(StringList *, char *, int);
+int db_select_rcctl_subcmd(StringList *, char *, int, char *);
+int db_select_rcctl_optarg(StringList *, char *, int, char *);
 #endif
 int db_select_flag_x_dbflag_rtable(char *, char *, int);
 
@@ -594,3 +602,17 @@ int hashtable_foreach(struct hashtable *,
     int (*cb)(void *, size_t, void *, size_t, void *),
     void *cb_arg);
 int hashtable_num_entries(struct hashtable *);
+
+/* rcctl.c */
+void rcctl_enable(char *, char *);
+void rcctl_disable(char *, char *);
+void rcctl_flag_clear(char *, char *, char *, char *);
+void rcctl_flag_set(char *, char *, char *, char *);
+void rcctl_flag_set_require_arg(char *, char *, char *, char *);
+void rcctl_flag_replace(char *, char *, char *, char *, char *, char *);
+void rcctl_flag_clear6(char *, char *, char *, char *, char *, char*);
+void conf_rcctl(char *, char *, int, FILE *);
+void rcctl_stop(char *);
+void rcctl_start(char *);
+int rcctl_has_status(char *, char *);
+void rcctl_reload(char *, char *);
blob - 5ac4ac0033e7778afc6cfc5541952605609804d1
blob + 5f21c7e2add436fe0866b6779663d4cc9886378a
--- main.c
+++ main.c
@@ -172,6 +172,8 @@ create_db(void)
 		printf("%% database pppoeipaddrmode creation failed\n");
 	if (db_create_table_flag_x("pin") < 0)
 		printf("%% database pin creation failed\n");
+	if (db_create_table_rcctl_flags() < 0)
+		printf("%% database rcctl flags creation failed\n");
 }
 
 int
blob - /dev/null
blob + 141ecc46a5c345428227128f0e0031dda7815fc1 (mode 644)
--- /dev/null
+++ rcctl.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "stringlist.h"
+#include "externs.h"
+#include "ctl.h"
+
+static int
+is_enabled_in_db(char *nshcmd)
+{
+	int dbflag;
+
+	dbflag = db_select_flag_x_dbflag_rtable("ctl", nshcmd, cli_rtable);
+	return (dbflag == DB_X_ENABLE || dbflag == DB_X_ENABLE_DEFAULT);
+}
+
+void
+rcctl_enable(char *daemon, char *nshcmd)
+{
+	if (is_enabled_in_db(nshcmd))
+		return;
+
+	if (interactive_mode)
+		printf("%% run \"%s reload\" to start %s\n", nshcmd, daemon);
+}
+
+static int
+is_disabled_in_db(char *nshcmd)
+{
+	int dbflag;
+
+	dbflag = db_select_flag_x_dbflag_rtable("ctl", nshcmd, cli_rtable);
+	return (dbflag == DB_X_DISABLE || dbflag == DB_X_DISABLE_ALWAYS);
+}
+
+void
+rcctl_disable(char *daemon, char *nshcmd)
+{
+	if (is_disabled_in_db(nshcmd))
+		return;
+
+	if (interactive_mode)
+		printf("%% run \"%s reload\" to stop %s\n", nshcmd, daemon);
+}
+
+void
+rcctl_flag_clear(char *daemon, char *subcmd, char *option, char *optarg)
+{
+	if (db_delete_rcctl_flag(daemon, cli_rtable, option, optarg) < 0)
+		printf("%% database delete failure %s %s\n", daemon, option);
+}
+
+/*
+ * Set an rcctl flag. Both the corresponding nsh sub command name and
+ * the option which rcctl should set must be specified.
+ *
+ * Sample ctl2 arguments array, where the option expects no argument:
+ * { "daemon", "subcmd", "-X", "", NULL }
+ *
+ * Sample ctl2 arguments array, where the option expects an argument:
+ * { "daemon", "subcmd", "-X", REQ, NULL }
+ */
+void
+rcctl_flag_set(char *daemon, char *subcmd, char *option, char *optarg)
+{
+	/* prevent duplicate rows */
+	rcctl_flag_clear(daemon, subcmd, option, optarg);
+
+	if (db_insert_rcctl_flag(daemon, subcmd, cli_rtable, option, optarg) < 0)
+		printf("%% database insert failure: %s %s%s%s\n", daemon, option,
+		    optarg ? " " : "", optarg ? optarg : "");
+}
+
+void
+rcctl_flag_set_require_arg(char *daemon, char *subcmd, char *option,
+    char *optarg)
+{
+	if (optarg == NULL || optarg[0] == '\0') {
+		printf("%% missing argument to %s option\n", option);
+		return;
+	}
+
+	rcctl_flag_set(daemon, subcmd, option, optarg);
+}
+/*
+ * Replace passing of one rcctl flag with another.
+ * For use with options that are mutually exclusive, such as -4/-6 in syslogd.
+ *
+ * Sample ctl2 arguments array, where both old and new options expect
+ * no arguments: { "daemon", "subcmd", "-X", "", "-Y", "", NULL }
+ *
+ * Sample ctl2 arguments array, where both old and new options expect
+ * a mandatory argument: { "daemon", "subcmd", "-X", REQ, "-Y", REQ, NULL }
+ */
+void
+rcctl_flag_replace(char *daemon, char *subcmd, char *oldflag, char *oldvalue,
+    char *newflag, char *newvalue)
+{
+	rcctl_flag_clear(daemon, subcmd, oldflag, oldvalue);
+	rcctl_flag_set(daemon, subcmd, newflag, newvalue);
+}
+
+/*
+ * Accept 6 arguments for symmetry with rcctl_flag_replace(), and behave
+ * just like rcctl_flag_clear().
+ */
+void
+rcctl_flag_clear6(char *daemon, char *subcmd, char *oldflag, char *oldvalue,
+    char *newflag, char *newvalue)
+{
+	rcctl_flag_clear(daemon, subcmd, oldflag, oldvalue);
+}
+
+void
+conf_rcctl(char *daemon, char *nshcmd, int rdomain, FILE *output)
+{
+	int dbflag;
+	int reload = 0;
+	int i;
+	StringList *options;
+
+	dbflag = db_select_flag_x_dbflag_rtable("ctl", nshcmd, rdomain);
+	if (dbflag <= 0) {
+		struct daemons2 *daemons2;
+		struct ctl2 *ctl2;
+
+		daemons2 = (struct daemons2 *)genget(nshcmd,
+		    (char **)ctl_daemons2, sizeof(struct daemons2));
+		if (daemons2 == NULL || Ambiguous(daemons2)) {
+			printf("%% Internal error - Invalid argument %s\n", nshcmd);
+			return;
+		}
+
+		ctl2 = daemons2->table;
+		dbflag = ctl2->flag_x; /* default 'enabled' value */
+	}
+
+	switch (dbflag) {
+	case DB_X_ENABLE:
+		/* daemon is disabled by rc.d(8) unless explicitly enabled */
+		fprintf(output, "%s enable\n", nshcmd);
+		reload = 1;
+		break;
+	case DB_X_ENABLE_DEFAULT:
+		/* daemon is enabled by rc.d(8) unless explicitly disabled */
+		break;
+	case DB_X_DISABLE:
+	case DB_X_DISABLE_ALWAYS:
+		fprintf(output, "%s disable\n", nshcmd);
+		reload = 1;
+		break;
+	default:
+		printf("%% bad ctl %s flag 0x%x in database\n",
+		    nshcmd, dbflag);
+		return;
+	}
+
+	options = sl_init();
+
+	if (db_select_rcctl_options(options, daemon, cli_rtable) < 0) {
+		printf("%% database select failure: %s\n", daemon);
+		sl_free(options, 1);
+		return;
+	}
+	for (i = 0; i < options->sl_cur; i++) {
+		StringList *subcmd = sl_init();
+		StringList *optarg = sl_init();
+		char *option = options->sl_str[i];
+
+		if (db_select_rcctl_subcmd(subcmd, daemon, cli_rtable, option) < 0) {
+			printf("%% database select failure: %s %s\n", daemon, option);
+			continue;
+		}
+
+		if (db_select_rcctl_optarg(optarg, daemon, cli_rtable,
+		    option) < 0) {
+			printf("%% database select failure: %s %s\n", daemon, option);
+			sl_free(subcmd, 1);
+			continue;
+		}
+
+		if (subcmd->sl_cur) {
+			if (optarg->sl_cur && optarg->sl_str[0][0] != '\0') {
+				fprintf(output, "%s %s \"%s\"\n", nshcmd,
+				    subcmd->sl_str[0], optarg->sl_str[0]);
+			} else
+				fprintf(output, "%s %s\n", nshcmd, subcmd->sl_str[0]);
+		}
+
+		reload = 1;
+		sl_free(subcmd, 1);
+		sl_free(optarg, 1);
+	}
+
+	if (reload)
+		fprintf(output, "%s reload\n", nshcmd);
+}
+
+static int
+run_rcctl(char *argv[], int outfd)
+{
+	int ret;
+
+	ret = cmdargs_output(RCCTL, argv, outfd, -1);
+	if (ret < 0)
+		printf("%% could not run %s\n", RCCTL);
+	return ret;
+}
+
+static int
+get_rcscript(char *buf, size_t bufsize, char *daemon)
+{
+	int ret;
+
+	if (cli_rtable > 0)
+		ret = snprintf(buf, bufsize, "%s%d", daemon, cli_rtable);
+	else
+		ret = snprintf(buf, bufsize, "%s", daemon);
+
+	if (ret < 0) {
+		printf("%% snprintf %s\n", strerror(errno));
+		return -1;
+	} else if ((size_t)ret >= bufsize) {
+		printf("%% daemon name too long: %s\n", daemon);
+		return -1;
+	}
+
+	return 0;
+}
+
+void
+rcctl_stop(char *daemon)
+{
+	char rcscript[64];
+	char *stop_argv[] = { RCCTL, "stop", rcscript, NULL };
+
+	if (get_rcscript(rcscript, sizeof(rcscript), daemon))
+		return;
+
+	printf("%% stopping %s\n", rcscript);
+	run_rcctl(stop_argv, -1);
+}
+
+void
+rcctl_start(char *daemon)
+{
+	char rcscript[64];
+	char *start_argv[] = { RCCTL, "start", rcscript, NULL };
+
+	if (get_rcscript(rcscript, sizeof(rcscript), daemon))
+		return;
+
+	printf("%% starting %s\n", rcscript);
+	run_rcctl(start_argv, -1);
+}
+
+int
+rcctl_has_status(char *daemon, char *status)
+{
+	char rcscript[64];
+	char *argv[] = { RCCTL, "ls", status, NULL };
+	char outpath[PATH_MAX];
+	char text[128];
+	int fd = -1, found = 0, len;
+
+	if (get_rcscript(rcscript, sizeof(rcscript), daemon))
+		return 0;
+
+	len = snprintf(text, sizeof(text), "%s\n", rcscript);
+	if (len < 0) {
+		printf("%% snprintf: %s\n", strerror(errno));
+		return 0;
+	}
+	if ((size_t)len >= sizeof(text)) {
+		printf("%% daemon name too long: %s\n", rcscript);
+		return 0;
+	}
+
+	strlcpy(outpath, "/tmp/nsh-XXXXXX", sizeof(outpath));
+	fd = mkstemp(outpath);
+	if (fd == -1) {
+		printf("%% mkstemp: %s\n", strerror(errno));
+		return 0;
+	}
+
+	/* rcctl will exit with code 1 if a daemon is matching */
+	if (run_rcctl(argv, fd) == 1 && scantext(outpath, text))
+		found = 1;
+
+	unlink(outpath);
+	close(fd);
+	return found;
+}
+
+void
+rcctl_reload(char *daemon, char *nshcmd)
+{
+	char rcscript[64];
+	char *setflags_argv[] = { RCCTL, "set", rcscript, "flags", "", NULL };
+	char *reload_argv[] = { RCCTL, "reload", rcscript, NULL };
+	char *enable_argv[] = { RCCTL, "enable", rcscript, NULL };
+	char *disable_argv[] = { RCCTL, "disable", rcscript, NULL };
+	StringList *dbflags, *flags;
+	char *s = NULL;
+	int i;
+
+	if (get_rcscript(rcscript, sizeof(rcscript), daemon))
+		return;
+
+	if (is_disabled_in_db(nshcmd)) {
+		run_rcctl(disable_argv, -1);
+		if (rcctl_has_status(daemon, "rogue"))
+			rcctl_stop(daemon);
+		return;
+	}
+
+	dbflags = sl_init();
+	flags = sl_init();
+
+	if (db_select_rcctl_options(dbflags, daemon, cli_rtable) < 0) {
+		printf("%% database select failure: %s\n", daemon);
+		goto done;
+	}
+
+	for (i = 0; i < dbflags->sl_cur; i++) {
+		char *option = dbflags->sl_str[i];
+		StringList *optarg;
+
+		optarg = sl_init();
+		if (db_select_rcctl_optarg(optarg, daemon, cli_rtable,
+		    option) < 0) {
+			printf("%% database select failure: %s %s\n", daemon, option);
+			sl_free(optarg, 1);
+			goto done;
+		}
+
+		s = strdup(option);
+		if (s == NULL) {
+			printf("%% strdup: %s\n", strerror(errno));
+			goto done;
+		}
+		sl_add(flags, s);
+		s = NULL;
+
+		if (optarg->sl_cur) {
+			s = strdup(optarg->sl_str[0]);
+			if (s == NULL) {
+				printf("%% strdup: %s\n", strerror(errno));
+				sl_free(optarg, 1);
+				goto done;
+			}
+			sl_add(flags, s);
+			s = NULL;
+		}
+
+		sl_free(optarg, 1);
+	}
+
+	s = sl_str(flags, " ");
+	if (s == NULL) {
+		printf("%% sl_str failure\n");
+		goto done;
+	}
+	setflags_argv[4] = s;
+
+	if (run_rcctl(enable_argv, -1) != 0) {
+		printf("%% could not enable %s\n", rcscript);
+		goto done;
+	}
+
+	printf("%% setting %s flags: %s\n", rcscript, setflags_argv[4]);
+	if (run_rcctl(setflags_argv, -1) != 0) {
+		printf("%% could not set %s flags\n", rcscript);
+		goto done;
+	}
+
+	if (rcctl_has_status(daemon, "failed")) {
+		rcctl_start(daemon);
+	} else {
+		printf("%% reloading %s\n", rcscript);
+		run_rcctl(reload_argv, -1);
+	}
+done:
+	sl_free(dbflags, 1);
+	sl_free(flags, 1);
+	free(s);
+}
blob - edaaafa80021cd6627de6cf130ea941d0e55ed69
blob + 9921fb2860d5b5a5de6e088028d6959fca9e4a66
--- sqlite3.c
+++ sqlite3.c
@@ -52,6 +52,17 @@ db_create_table_flag_x(char *name)
 }
 
 int
+db_create_table_rcctl_flags(void)
+{
+	char		query[QSZ];
+
+	snprintf(query, QSZ, "CREATE TABLE IF NOT EXISTS rcctlflag "
+	    "(daemon TEXT, subcmd TEXT, rtable INTEGER, "
+	    "option TEXT, optarg TEXT)");
+	return(sq3simple(query, NULL));
+}
+
+int
 db_insert_flag_x(char *name, char *ctl, int rtableid, int flag, char *data)
 {
 	char		query[QSZ];
@@ -61,7 +72,24 @@ db_insert_flag_x(char *name, char *ctl, int rtableid, 
 	return(sq3simple(query, NULL));
 }
 
+/*
+ * Add an option and its argument to the set of rcctl flags we pass to
+ * a given daemon. The sub-command nsh uses to represent the flag is
+ * recorded as well. This is useful e.g. when we generate an nshrc config
+ * which sets rcctl options via nsh commands.
+ */
 int
+db_insert_rcctl_flag(char *daemon, char *subcmd, int rtableid,
+    char *option, char *optarg)
+{
+	char		query[QSZ];
+
+	snprintf(query, QSZ, "INSERT INTO rcctlflag VALUES('%s', '%s', "
+	    "%d, '%s', '%s')", daemon, subcmd, rtableid, option, optarg);
+	return(sq3simple(query, NULL));
+}
+
+int
 db_insert_rtables(int rtableid, char *name)
 {
 	char		query[QSZ];
@@ -104,6 +132,24 @@ db_delete_flag_x_ctl_data(char *name, char *ctl, char 
 	char		query[QSZ];
 
 	snprintf(query, QSZ, "DELETE FROM '%s' WHERE ctl='%s' AND data='%s'", name, ctl, data);
+	return(sq3simple(query, NULL));
+}
+
+/* Remove an option from the set of rcctl flags we pass to a given daemon .*/
+int
+db_delete_rcctl_flag(char *daemon, int rtableid, char *option, char *optarg)
+{
+	char		query[QSZ];
+
+	if (optarg && optarg[0] != '\0') {
+		snprintf(query, QSZ, "DELETE FROM rcctlflag WHERE daemon='%s' "
+		    "AND rtable=%d AND option='%s' AND optarg='%s'",
+		    daemon, rtableid, option, optarg);
+	} else {
+		snprintf(query, QSZ, "DELETE FROM rcctlflag WHERE daemon='%s' "
+		    "AND rtable=%d AND option='%s'", daemon, rtableid, option);
+	}
+
 	return(sq3simple(query, NULL));
 }
 
@@ -134,7 +180,49 @@ db_select_flag_x_ctl(StringList *words, char *name, ch
 	return(sq3simple(query, words));
 }
 
+/* Obtain the list of rcctl option flags we set for a given daemon. */
 int
+db_select_rcctl_options(StringList *words, char *daemon, int rtable)
+{
+	char		query [QSZ];
+
+	snprintf(query, QSZ, "SELECT option FROM rcctlflag WHERE daemon='%s' "
+	    "AND rtable=%d", daemon, rtable);
+	return(sq3simple(query, words));
+}
+
+/*
+ * Map an rcctl option flag to an nsh sub-command which sets the flag or
+ * clears the flag when "no ..." is used.
+ */
+int
+db_select_rcctl_subcmd(StringList *words, char *daemon, int rtable,
+    char *option)
+{
+	char		query [QSZ];
+
+	snprintf(query, QSZ, "SELECT subcmd FROM rcctlflag WHERE daemon='%s' "
+	    "AND rtable=%d AND option='%s'", daemon, rtable, option);
+	return(sq3simple(query, words));
+}
+
+/*
+ * Look up an option argument for a given option. Not all options accept
+ * arguments but handling this is up to the caller (e.g. insert an empty
+ * string as optarg to represent "no argument").
+ */
+int
+db_select_rcctl_optarg(StringList *words, char *daemon, int rtable,
+    char *option)
+{
+	char		query [QSZ];
+
+	snprintf(query, QSZ, "SELECT optarg FROM rcctlflag WHERE daemon='%s' "
+	    "AND rtable=%d AND option='%s'", daemon, rtable, option);
+	return(sq3simple(query, words));
+}
+
+int
 db_select_rtable_rtables(StringList *words)
 {
 	char query[]="SELECT rtable FROM rtables";