From patchwork Thu Sep 10 08:53:49 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?b?UmFmYcWCIE1pxYJlY2tp?= X-Patchwork-Id: 1361355 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.openwrt.org (client-ip=2001:8b0:10b:1231::1; helo=merlin.infradead.org; envelope-from=openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=merlin.20170209 header.b=l6j1trJ0; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=AM92Z7Yn; dkim-atps=neutral Received: from merlin.infradead.org (merlin.infradead.org [IPv6:2001:8b0:10b:1231::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BnCQx0ynzz9sSJ for ; Thu, 10 Sep 2020 18:55:53 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:Cc:List-Subscribe:List-Help:List-Post:List-Archive: List-Unsubscribe:List-Id:MIME-Version:Message-Id:Date:Subject:To:From: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:List-Owner; bh=ShPlxIYcpG++YLERJhy3WHbLsm4EasDaNU3XKR8cyJw=; b=l6j1trJ0Q9jYYlwm7fNCRNSYan 6FW9ntrKVAw/jJ2L8ZgSUoV08NKIuzLLuzgSi/T0ItX9f10sfv+ND+j/1kCtsVswtk829oDzuyT4r mFwX2kpEM/WyNQnpyidZjWusqyDWBoFGvPyJzV0Jd9HqVUesMgfTaqbs7acI1AMHcscC3crrSeeB/ 6Xb5Ljudf6IrPzgS6WwRvHjwbjYDgJ4TseR57bMOoizaNKFEyqm++WyTH+blB0XvJQ78KhTbd5HF7 m6gkPqT1xDEUCXzecpOxpRZ5Jh12KLer4eZjrwVMRC10mYs8zR5NdiNuIbU1FFkmfSj4z7VfxKd0M g24uLbSw==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1kGILa-0002dB-L5; Thu, 10 Sep 2020 08:54:26 +0000 Received: from mail-lj1-x244.google.com ([2a00:1450:4864:20::244]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1kGILX-0002by-Ts for openwrt-devel@lists.openwrt.org; Thu, 10 Sep 2020 08:54:25 +0000 Received: by mail-lj1-x244.google.com with SMTP id u21so7134525ljl.6 for ; Thu, 10 Sep 2020 01:54:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=x3gWBxxt/cW+/+cwWiCO7JskSQTRNsbhomLG76cAF/w=; b=AM92Z7Yng00Hp7KI4kc1Xsv3RBGfdueV88d6R6ho8MzUAcuWe1oFINCaycxYHxUicD T6OAxgACltemS0iXUToRwBOwxAc8CFYlNYtdV2Au7Id1uxyxqBF9jTQqxeBhDWQqnS/K N3j4jOQWM+rO5kZUQ5hi5Uwe/M4/k8bx5hMN4FJ/trgnXI6U+i0yvxdsPwB89R5F8/EO uJI8hhtHKWkoa0TbP/eCi6ZmVqpLHpbpBGtkuPebzpDIyUIQrxSTolox21oi8r6nUXrN ZwsR+4W5G/CVfdZTi9+Qzx/M1dxtBDt2aMzCDUJ/HlpBLnSn5VHLQbcpJXyDMMKuKEQ3 +Bmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=x3gWBxxt/cW+/+cwWiCO7JskSQTRNsbhomLG76cAF/w=; b=RFz4X8U3PAIkwbLaoxTJ6c2sjQ9nv/lydwS9ITGwzvednBjAQRb2Mrm27k6a1alols FEJaRgP7CTzpXl4pXrgd344JiV6cmbxpo7Ktfs0p27cuXqXSoBTzH5C8386pibCmYBEK 92wGmsnNL5S8wY/8g2jkISy/05SmfWdgBjBy63lvg3BfjQa+RZteOOyNxULpwVHLEAB4 lON0muQaCFsR9l4DX88kOz36i4XebBP+lzLcbm0eKErb+XmpGtBL7/yFWNFC/xkmvEzg 5Bsgxi3YZbEckp7kzUFJ6zf2C9f62umFweqRg6gt1ntzc6X33Ii4v4S++Yv203tUFaXq Ccfg== X-Gm-Message-State: AOAM533QWnowE6IJ30ClBBKyUUhpH9CQU7jEh789wM8XACRRo4DLTeff fXhCJ0MPbcy7DwkSKmeKyeR/1m13p1g= X-Google-Smtp-Source: ABdhPJx/p+kfGBSeZ3Y2lWl6MoeekcMLuv3gM3QZXbYUKLV1RWXX39tw/6WTCu3YN2LS1dGb7+M1aw== X-Received: by 2002:a2e:b0d2:: with SMTP id g18mr4068587ljl.198.1599728060243; Thu, 10 Sep 2020 01:54:20 -0700 (PDT) Received: from localhost.localdomain (ip-194-187-74-233.konfederacka.maverick.com.pl. [194.187.74.233]) by smtp.gmail.com with ESMTPSA id l14sm1416360lji.99.2020.09.10.01.54.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 10 Sep 2020 01:54:19 -0700 (PDT) From: =?utf-8?b?UmFmYcWCIE1pxYJlY2tp?= To: openwrt-devel@lists.openwrt.org Subject: [PATCH V4 rpc] rc: new ubus object for handling /etc/init.d/ scripts Date: Thu, 10 Sep 2020 10:53:49 +0200 Message-Id: <20200910085349.5129-1-zajec5@gmail.com> X-Mailer: git-send-email 2.27.0 MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200910_045424_016679_CAABFBC5 X-CRM114-Status: GOOD ( 28.15 ) X-Spam-Score: 0.1 (/) X-Spam-Report: SpamAssassin version 3.4.4 on merlin.infradead.org summary: Content analysis details: (0.1 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [2a00:1450:4864:20:0:0:0:244 listed in] [list.dnswl.org] 0.2 FREEMAIL_ENVFROM_END_DIGIT Envelope-from freemail username ends in digit [zajec5[at]gmail.com] 0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail provider [zajec5[at]gmail.com] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: =?utf-8?b?UmFmYcWCIE1pxYJlY2tp?= , Jo-Philipp Wich Sender: "openwrt-devel" Errors-To: openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org From: Rafał Miłecki 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@OpenWrt:~# ubus call rc list { "blockd": { "enabled": true, "running": true }, "dnsmasq": { "enabled": true, "running": true } } root@OpenWrt:~# ubus call rc init '{ "name": "blockd", "action": "disable" }' root@OpenWrt:~# ubus call rc init '{ "name": "dnsmasq", "action": "stop" }' root@OpenWrt:~# ubus call rc list { "blockd": { "enabled": false, "running": true }, "dnsmasq": { "enabled": true, "running": false } } Signed-off-by: Rafał Miłecki --- V2: Use ubus_defer_request() and uloop_process_add() when callinig init.d scripts. It's required as rpcd is single thread so waitpid() could stop it from processing requests initiaited by init.d scripts. Add timer to kill hanging init.d calls. V3: Add uloop_end() close(fd) only for fd > 2 V4: Add "start" and "stop" Use /etc/rc.d/S* to check for script being enabled. On bcm53xx it speeds up "list" call from 1.24s to 0.72s (24 scripts). --- CMakeLists.txt | 2 +- include/rpcd/rc.h | 7 + main.c | 6 +- rc.c | 359 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 371 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 #include +#include +#include +#include #include #include -#include -#include 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..35be544 --- /dev/null +++ b/rc.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: ISC OR MIT +/* + * rpcd - UBUS RPC server + * + * Copyright (C) 2020 Rafał Miłecki + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define RC_LIST_EXEC_TIMEOUT_MS 3000 + +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 }, +}; + +struct rc_list_context { + struct uloop_process process; + struct uloop_timeout timeout; + struct ubus_context *ctx; + struct ubus_request_data req; + struct blob_buf *buf; + DIR *dir; + + /* Info about currently processed init.d entry */ + struct { + char path[PATH_MAX]; + const char *d_name; + unsigned int start; + unsigned int stop; + bool enabled; + bool running; + } entry; +}; + +static void rc_list_readdir(struct rc_list_context *c); + +/** + * 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; +} + +static void rc_list_add_table(struct rc_list_context *c) +{ + void *e; + + e = blobmsg_open_table(c->buf, c->entry.d_name); + + if (c->entry.start) + blobmsg_add_u16(c->buf, "start", c->entry.start); + if (c->entry.stop) + blobmsg_add_u16(c->buf, "stop", c->entry.stop); + blobmsg_add_u8(c->buf, "enabled", c->entry.enabled); + blobmsg_add_u8(c->buf, "running", c->entry.running); + + blobmsg_close_table(c->buf, e); +} + +static void rpc_list_exec_timeout_cb(struct uloop_timeout *t) +{ + struct rc_list_context *c = container_of(t, struct rc_list_context, timeout); + + ULOG_WARN("Timeout waiting for %s\n", c->entry.path); + + uloop_process_delete(&c->process); + kill(c->process.pid, SIGKILL); + + rc_list_readdir(c); +} + +/** + * rc_exec - execute a file and call callback on complete + */ +static int rc_list_exec(struct rc_list_context *c, const char *action, uloop_process_handler cb) +{ + pid_t pid; + int err; + 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); + if (fd > 2) + close(fd); + } + + uloop_end(); + + execl(c->entry.path, c->entry.path, action, NULL); + exit(errno); + default: + c->process.pid = pid; + c->process.cb = cb; + + err = uloop_process_add(&c->process); + if (err) + return err; + + c->timeout.cb = rpc_list_exec_timeout_cb; + err = uloop_timeout_set(&c->timeout, RC_LIST_EXEC_TIMEOUT_MS); + if (err) { + uloop_process_delete(&c->process); + return err; + } + + return 0; + } +} + +static void rc_list_exec_running_cb(struct uloop_process *p, int stat) +{ + struct rc_list_context *c = container_of(p, struct rc_list_context, process); + + uloop_timeout_cancel(&c->timeout); + + c->entry.running = !stat; + rc_list_add_table(c); + + rc_list_readdir(c); +} + +static void rc_list_readdir(struct rc_list_context *c) +{ + struct dirent *e; + FILE *fp; + + e = readdir(c->dir); + if (!e) { + closedir(c->dir); + ubus_send_reply(c->ctx, &c->req, c->buf->head); + ubus_complete_deferred_request(c->ctx, &c->req, UBUS_STATUS_OK); + return; + } + + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) + goto next; + + memset(&c->entry, 0, sizeof(c->entry)); + + snprintf(c->entry.path, sizeof(c->entry.path), "/etc/init.d/%s", e->d_name); + if (rc_check_script(c->entry.path)) + goto next; + + c->entry.d_name = e->d_name; + + fp = fopen(c->entry.path, "r"); + if (fp) { + struct stat s; + char path[PATH_MAX]; + char line[128]; + + while (!c->entry.start && !c->entry.stop && fgets(line, sizeof(line), fp)) { + char *ch; + + if ((ch = strstr(line, "START="))) { + c->entry.start = strtoul(ch + 6, NULL, 0); + } else if ((ch = strstr(line, "STOP="))) { + c->entry.stop = strtoul(ch + 5, NULL, 0); + } + } + fclose(fp); + + snprintf(path, sizeof(path), "/etc/rc.d/S%02d%s", c->entry.start, c->entry.d_name); + if (!stat(path, &s) && (s.st_mode & S_IXUSR)) + c->entry.enabled = true; + } + + if (rc_list_exec(c, "running", rc_list_exec_running_cb)) + goto next; + + return; +next: + rc_list_readdir(c); +} + +/** + * rc_list - allocate listing context and start reading directory + */ +static int rc_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + static struct blob_buf buf; + struct rc_list_context *c; + + blob_buf_init(&buf, 0); + + c = calloc(1, sizeof(*c)); + if (!c) + return UBUS_STATUS_UNKNOWN_ERROR; + + c->ctx = ctx; + c->buf = &buf; + c->dir = opendir("/etc/init.d"); + if (!c->dir) { + free(c); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + ubus_defer_request(ctx, req, &c->req); + + rc_list_readdir(c); + + return 0; /* Deferred */ +} + +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); + if (fd > 2) + close(fd); + } + + uloop_end(); + + 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); +}