[PATCH rpcd] rc: new ubus object for handling /etc/init.d/ scripts

Rafał Miłecki zajec5 at gmail.com
Tue Jun 23 14:46:19 EDT 2020


From: Rafał Miłecki <rafal at milecki.pl>

This commit adds "rc" ubus object with methods "list" and "exec" for
listing and calling init.d script appropriately. It's useful for all
kind of UIs (e.g. LuCI) and custom apps.

Example:
root at OpenWrt:~# ubus call rc list
{
	"blockd": {
		"enabled": true,
		"running": true
	},
	"dnsmasq": {
		"enabled": true,
		"running": true
	}
}
root at OpenWrt:~# ubus call rc init '{ "name": "blockd", "action": "disable" }'
root at OpenWrt:~# ubus call rc init '{ "name": "dnsmasq", "action": "stop" }'
root at OpenWrt:~# ubus call rc list
{
	"blockd": {
		"enabled": false,
		"running": true
	},
	"dnsmasq": {
		"enabled": true,
		"running": false
	}
}

Signed-off-by: Rafał Miłecki <rafal at milecki.pl>
---
Final patch:
* Put code in rpcd instead of procd
* Implement "list" method
* Validate input
---
 CMakeLists.txt    |   2 +-
 include/rpcd/rc.h |   7 ++
 main.c            |   6 +-
 rc.c              | 234 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 246 insertions(+), 3 deletions(-)
 create mode 100644 include/rpcd/rc.h
 create mode 100644 rc.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3bfc286..26e011e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,7 @@ INCLUDE_DIRECTORIES(${ubus_include_dir})
 FIND_PATH(ubox_include_dir libubox/blobmsg_json.h)
 INCLUDE_DIRECTORIES(${ubox_include_dir})
 
-ADD_EXECUTABLE(rpcd main.c exec.c session.c uci.c plugin.c)
+ADD_EXECUTABLE(rpcd main.c exec.c session.c uci.c rc.c plugin.c)
 TARGET_LINK_LIBRARIES(rpcd ${ubox} ${ubus} ${uci} ${blobmsg_json} ${json} ${crypt} dl)
 
 SET(PLUGINS "")
diff --git a/include/rpcd/rc.h b/include/rpcd/rc.h
new file mode 100644
index 0000000..ca00f56
--- /dev/null
+++ b/include/rpcd/rc.h
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: ISC OR MIT
+#ifndef __RPCD_RC_H
+#define __RPCD_RC_H
+
+int rpc_rc_api_init(struct ubus_context *ctx);
+
+#endif
diff --git a/main.c b/main.c
index 9a177cf..d77a814 100644
--- a/main.c
+++ b/main.c
@@ -25,10 +25,11 @@
 #include <signal.h>
 #include <sys/stat.h>
 
+#include <rpcd/exec.h>
+#include <rpcd/plugin.h>
+#include <rpcd/rc.h>
 #include <rpcd/session.h>
 #include <rpcd/uci.h>
-#include <rpcd/plugin.h>
-#include <rpcd/exec.h>
 
 static struct ubus_context *ctx;
 static bool respawn = false;
@@ -113,6 +114,7 @@ int main(int argc, char **argv)
 
 	rpc_session_api_init(ctx);
 	rpc_uci_api_init(ctx);
+	rpc_rc_api_init(ctx);
 	rpc_plugin_api_init(ctx);
 
 	hangup = getenv("RPC_HANGUP");
diff --git a/rc.c b/rc.c
new file mode 100644
index 0000000..c3741eb
--- /dev/null
+++ b/rc.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: ISC OR MIT
+/*
+ * rpcd - UBUS RPC server
+ *
+ * Copyright (C) 2020 Rafał Miłecki <rafal at milecki.pl>
+ */
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <libubox/blobmsg.h>
+#include <libubox/uloop.h>
+#include <libubus.h>
+
+#include <rpcd/rc.h>
+
+enum {
+	RC_INIT_NAME,
+	RC_INIT_ACTION,
+	__RC_INIT_MAX
+};
+
+static const struct blobmsg_policy rc_init_policy[] = {
+	[RC_INIT_NAME] = { "name", BLOBMSG_TYPE_STRING },
+	[RC_INIT_ACTION] = { "action", BLOBMSG_TYPE_STRING },
+};
+
+/**
+ * rc_check_script - check if script is safe to execute as root
+ *
+ * Check if it's owned by root and if only root can modify it.
+ */
+static int rc_check_script(const char *path)
+{
+	struct stat s;
+
+	if (stat(path, &s))
+		return UBUS_STATUS_NOT_FOUND;
+
+	if (s.st_uid != 0 || s.st_gid != 0 || !(s.st_mode & S_IXUSR) || (s.st_mode & S_IWOTH))
+		return UBUS_STATUS_PERMISSION_DENIED;
+
+	return UBUS_STATUS_OK;
+}
+
+/**
+ * rc_exec - execute a file by pathname and return its exit status
+ */
+static int rc_exec(const char *pathname, const char *action)
+{
+	int wstatus;
+	pid_t pid;
+	int fd;
+
+	pid = fork();
+	switch (pid) {
+	case -1:
+		return -errno;
+	case 0:
+		/* Set stdin, stdout & stderr to /dev/null */
+		fd = open("/dev/null", O_RDWR);
+		if (fd >= 0) {
+			dup2(fd, 0);
+			dup2(fd, 1);
+			dup2(fd, 2);
+			close(fd);
+		}
+
+		execl(pathname, pathname, action, NULL);
+		exit(errno);
+	default:
+		if (waitpid(pid, &wstatus, 0) == -1)
+			return -errno;
+
+		if (!WIFEXITED(wstatus))
+			return -EIO;
+
+		return WEXITSTATUS(wstatus);
+	}
+}
+
+static int rc_list(struct ubus_context *ctx, struct ubus_object *obj,
+		   struct ubus_request_data *req, const char *method,
+		   struct blob_attr *msg)
+{
+	struct blob_buf buf = { };
+	struct dirent *e;
+	DIR *dir;
+
+	dir = opendir("/etc/init.d");
+	if (!dir)
+		return UBUS_STATUS_UNKNOWN_ERROR;
+
+	blob_buf_init(&buf, 0);
+
+	while ((e = readdir(dir))) {
+		char path[PATH_MAX];
+		void *c;
+
+		if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
+			continue;
+
+		snprintf(path, sizeof(path), "/etc/init.d/%s", e->d_name);
+		if (rc_check_script(path))
+			continue;
+
+		c = blobmsg_open_table(&buf, e->d_name);
+
+		blobmsg_add_u8(&buf, "enabled", !rc_exec(path, "enabled"));
+		blobmsg_add_u8(&buf, "running", !rc_exec(path, "running"));
+
+		blobmsg_close_table(&buf, c);
+	}
+
+	ubus_send_reply(ctx, req, buf.head);
+
+	return UBUS_STATUS_OK;
+}
+
+struct rc_init_context {
+	struct uloop_process process;
+	struct ubus_context *ctx;
+	struct ubus_request_data req;
+};
+
+static void rc_init_cb(struct uloop_process *p, int stat)
+{
+	struct rc_init_context *c = container_of(p, struct rc_init_context, process);
+
+	ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK);
+
+	free(c);
+}
+
+static int rc_init(struct ubus_context *ctx, struct ubus_object *obj,
+		   struct ubus_request_data *req, const char *method,
+		   struct blob_attr *msg)
+{
+	struct blob_attr *tb[__RC_INIT_MAX];
+	struct rc_init_context *c;
+	char path[PATH_MAX];
+	const char *action;
+	const char *name;
+	const char *chr;
+	pid_t pid;
+	int err;
+	int fd;
+
+	blobmsg_parse(rc_init_policy, __RC_INIT_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg));
+
+	if (!tb[RC_INIT_NAME] || !tb[RC_INIT_ACTION])
+		return UBUS_STATUS_INVALID_ARGUMENT;
+
+	name = blobmsg_get_string(tb[RC_INIT_NAME]);
+
+	/* Validate script name */
+	for (chr = name; (chr = strchr(chr, '.')); chr++) {
+		if (*(chr + 1) == '.')
+			return UBUS_STATUS_INVALID_ARGUMENT;
+	}
+	if (strchr(name, '/'))
+		return UBUS_STATUS_INVALID_ARGUMENT;
+
+	snprintf(path, sizeof(path), "/etc/init.d/%s", name);
+
+	/* Validate script privileges */
+	err = rc_check_script(path);
+	if (err)
+		return err;
+
+	action = blobmsg_get_string(tb[RC_INIT_ACTION]);
+	if (strcmp(action, "disable") &&
+	    strcmp(action, "enable") &&
+	    strcmp(action, "stop") &&
+	    strcmp(action, "start") &&
+	    strcmp(action, "restart") &&
+	    strcmp(action, "reload"))
+		return UBUS_STATUS_INVALID_ARGUMENT;
+
+	c = calloc(1, sizeof(*c));
+	if (!c)
+		return UBUS_STATUS_UNKNOWN_ERROR;
+
+	pid = fork();
+	switch (pid) {
+	case -1:
+		free(c);
+		return UBUS_STATUS_UNKNOWN_ERROR;
+	case 0:
+		/* Set stdin, stdout & stderr to /dev/null */
+		fd = open("/dev/null", O_RDWR);
+		if (fd >= 0) {
+			dup2(fd, 0);
+			dup2(fd, 1);
+			dup2(fd, 2);
+			close(fd);
+		}
+
+		execl(path, path, action, NULL);
+		exit(errno);
+	default:
+		c->ctx = ctx;
+		c->process.pid = pid;
+		c->process.cb = rc_init_cb;
+		uloop_process_add(&c->process);
+
+		ubus_defer_request(ctx, req, &c->req);
+
+		return 0; /* Deferred */
+	}
+}
+
+int rpc_rc_api_init(struct ubus_context *ctx)
+{
+	static const struct ubus_method rc_methods[] = {
+		UBUS_METHOD_NOARG("list", rc_list),
+		UBUS_METHOD("init", rc_init, rc_init_policy),
+	};
+
+	static struct ubus_object_type rc_type =
+		UBUS_OBJECT_TYPE("rc", rc_methods);
+
+	static struct ubus_object obj = {
+		.name = "rc",
+		.type = &rc_type,
+		.methods = rc_methods,
+		.n_methods = ARRAY_SIZE(rc_methods),
+	};
+
+	return ubus_add_object(ctx, &obj);
+}
-- 
2.26.1


_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
http://lists.infradead.org/mailman/listinfo/openwrt-devel


More information about the openwrt-devel mailing list