diff --git a/CMakeLists.txt b/CMakeLists.txt index 480d10b..8909404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,6 @@ INCLUDE_DIRECTORIES(include) OPTION(FILE_SUPPORT "File plugin support" ON) OPTION(IWINFO_SUPPORT "libiwinfo plugin support" ON) -OPTION(RPCSYS_SUPPORT "rpc-sys plugin support" ON) -OPTION(UCODE_SUPPORT "ucode plugin support" ON) SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") @@ -23,15 +21,10 @@ IF(HAVE_SHADOW) ADD_DEFINITIONS(-DHAVE_SHADOW) ENDIF() -FIND_LIBRARY(uci NAMES uci) FIND_LIBRARY(ubus NAMES ubus) FIND_LIBRARY(ubox NAMES ubox) FIND_LIBRARY(blobmsg_json NAMES blobmsg_json) FIND_LIBRARY(json NAMES json-c json) -FIND_LIBRARY(crypt NAMES crypt) -IF(crypt STREQUAL "crypt-NOTFOUND") - SET(crypt "") -ENDIF() FIND_PATH(ubus_include_dir libubus.h) INCLUDE_DIRECTORIES(${ubus_include_dir}) @@ -39,10 +32,8 @@ 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 rc.c plugin.c) -TARGET_LINK_LIBRARIES(rpcd ${ubox} ${ubus} ${uci} ${blobmsg_json} ${json} ${crypt} dl) - -SET(PLUGINS "") +ADD_EXECUTABLE(rpcd main.c plugin.c) +TARGET_LINK_LIBRARIES(rpcd ${ubox} ${ubus} ${blobmsg_json} ${json} dl) IF(FILE_SUPPORT) SET(PLUGINS ${PLUGINS} file_plugin) @@ -51,13 +42,6 @@ IF(FILE_SUPPORT) SET_TARGET_PROPERTIES(file_plugin PROPERTIES OUTPUT_NAME file PREFIX "") ENDIF() -IF(RPCSYS_SUPPORT) - SET(PLUGINS ${PLUGINS} rpcsys_plugin) - ADD_LIBRARY(rpcsys_plugin MODULE sys.c) - TARGET_LINK_LIBRARIES(rpcsys_plugin ${ubox} ${ubus}) - SET_TARGET_PROPERTIES(rpcsys_plugin PROPERTIES OUTPUT_NAME rpcsys PREFIX "") -ENDIF() - IF(IWINFO_SUPPORT) FIND_LIBRARY(iwinfo NAMES iwinfo) SET(PLUGINS ${PLUGINS} iwinfo_plugin) @@ -66,14 +50,6 @@ IF(IWINFO_SUPPORT) SET_TARGET_PROPERTIES(iwinfo_plugin PROPERTIES OUTPUT_NAME iwinfo PREFIX "") ENDIF() -IF(UCODE_SUPPORT) - FIND_LIBRARY(ucode NAMES ucode) - SET(PLUGINS ${PLUGINS} ucode_plugin) - ADD_LIBRARY(ucode_plugin MODULE ucode.c) - TARGET_LINK_LIBRARIES(ucode_plugin ${ucode}) - SET_TARGET_PROPERTIES(ucode_plugin PROPERTIES OUTPUT_NAME ucode PREFIX "") -ENDIF() - INSTALL(TARGETS rpcd ${PLUGINS} RUNTIME DESTINATION sbin LIBRARY DESTINATION lib/rpcd diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9d4c56 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# `rpcd`, an ubus RPC daemon for Liminix inspired by OpenWRT's `rpcd` + +This is NOT the `rpcd` you are used to, this one is tailored to Liminix usecases. diff --git a/exec.c b/exec.c deleted file mode 100644 index 3cd7384..0000000 --- a/exec.c +++ /dev/null @@ -1,390 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static int -rpc_errno_status(void) -{ - switch (errno) - { - case EACCES: - return UBUS_STATUS_PERMISSION_DENIED; - - case ENOTDIR: - return UBUS_STATUS_INVALID_ARGUMENT; - - case ENOENT: - return UBUS_STATUS_NOT_FOUND; - - case EINVAL: - return UBUS_STATUS_INVALID_ARGUMENT; - - default: - return UBUS_STATUS_UNKNOWN_ERROR; - } -} - -const char * -rpc_exec_lookup(const char *cmd) -{ - struct stat s; - int plen = 0, clen = strlen(cmd) + 1; - char *search, *p; - static char path[PATH_MAX]; - - if (!stat(cmd, &s) && S_ISREG(s.st_mode)) - return cmd; - - search = getenv("PATH"); - - if (!search) - search = "/bin:/usr/bin:/sbin:/usr/sbin"; - - p = search; - - do - { - if (*p != ':' && *p != '\0') - continue; - - plen = p - search; - - if ((plen + clen) >= sizeof(path)) - continue; - - strncpy(path, search, plen); - sprintf(path + plen, "/%s", cmd); - - if (!stat(path, &s) && S_ISREG(s.st_mode)) - return path; - - search = p + 1; - } - while (*p++); - - return NULL; -} - - -static void -rpc_ustream_to_blobmsg(struct blob_buf *blob, struct ustream *s, - const char *name) -{ - int len; - char *rbuf, *wbuf; - - if ((len = ustream_pending_data(s, false)) > 0) - { - wbuf = blobmsg_alloc_string_buffer(blob, name, len + 1); - - if (!wbuf) - return; - - ustream_for_each_read_buffer(s, rbuf, len) - { - memcpy(wbuf, rbuf, len); - wbuf += len; - } - - *wbuf = 0; - blobmsg_add_string_buffer(blob); - } -} - -static void -rpc_exec_reply(struct rpc_exec_context *c, int rv) -{ - uloop_timeout_cancel(&c->timeout); - uloop_process_delete(&c->process); - - if (rv == UBUS_STATUS_OK) - { - if (!c->stdout_cb && !c->stderr_cb && !c->finish_cb) - { - blobmsg_add_u32(&c->blob, "code", WEXITSTATUS(c->stat)); - rpc_ustream_to_blobmsg(&c->blob, &c->opipe.stream, "stdout"); - rpc_ustream_to_blobmsg(&c->blob, &c->epipe.stream, "stderr"); - } - } - - if (c->finish_cb) - rv = c->finish_cb(&c->blob, c->stat, c->priv); - - if (rv == UBUS_STATUS_OK) - ubus_send_reply(c->context, &c->request, c->blob.head); - - ubus_complete_deferred_request(c->context, &c->request, rv); - - blob_buf_free(&c->blob); - - ustream_free(&c->opipe.stream); - ustream_free(&c->epipe.stream); - - close(c->opipe.fd.fd); - close(c->epipe.fd.fd); - - if (c->priv) - free(c->priv); - - free(c); -} - -static void -rpc_exec_timeout_cb(struct uloop_timeout *t) -{ - struct rpc_exec_context *c = - container_of(t, struct rpc_exec_context, timeout); - - kill(c->process.pid, SIGKILL); - rpc_exec_reply(c, UBUS_STATUS_TIMEOUT); -} - -static void -rpc_exec_process_cb(struct uloop_process *p, int stat) -{ - struct rpc_exec_context *c = - container_of(p, struct rpc_exec_context, process); - - c->stat = stat; - - ustream_poll(&c->opipe.stream); - ustream_poll(&c->epipe.stream); - - close(c->opipe.fd.fd); - close(c->epipe.fd.fd); - - ustream_poll(&c->opipe.stream); - ustream_poll(&c->epipe.stream); -} - -static void -rpc_exec_ipipe_write_cb(struct ustream *s, int bytes) -{ - struct rpc_exec_context *c = - container_of(s, struct rpc_exec_context, ipipe.stream); - - if (c->stdin_cb(s, c->priv) <= 0) - { - ustream_free(&c->ipipe.stream); - close(c->ipipe.fd.fd); - } -} - -static void -rpc_exec_opipe_read_cb(struct ustream *s, int bytes) -{ - int len, rv; - char *buf; - struct rpc_exec_context *c = - container_of(s, struct rpc_exec_context, opipe.stream); - - if (c->stdout_cb) - { - do { - buf = ustream_get_read_buf(s, &len); - - if (!buf || !len) - break; - - rv = c->stdout_cb(&c->blob, buf, len, c->priv); - - if (rv <= 0) - break; - - ustream_consume(s, rv); - } while(1); - } - else if (ustream_read_buf_full(s)) - { - rpc_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); - } -} - -static void -rpc_exec_epipe_read_cb(struct ustream *s, int bytes) -{ - int len, rv; - char *buf; - struct rpc_exec_context *c = - container_of(s, struct rpc_exec_context, epipe.stream); - - if (c->stderr_cb) - { - do { - buf = ustream_get_read_buf(s, &len); - - if (!buf || !len) - break; - - rv = c->stderr_cb(&c->blob, buf, len, c->priv); - - if (rv <= 0) - break; - - ustream_consume(s, rv); - } while(1); - } - else if (ustream_read_buf_full(s)) - { - rpc_exec_reply(c, UBUS_STATUS_NOT_SUPPORTED); - } -} - -static void -rpc_exec_opipe_state_cb(struct ustream *s) -{ - struct rpc_exec_context *c = - container_of(s, struct rpc_exec_context, opipe.stream); - - if (c->opipe.stream.eof && c->epipe.stream.eof) - rpc_exec_reply(c, UBUS_STATUS_OK); -} - -static void -rpc_exec_epipe_state_cb(struct ustream *s) -{ - struct rpc_exec_context *c = - container_of(s, struct rpc_exec_context, epipe.stream); - - if (c->opipe.stream.eof && c->epipe.stream.eof) - rpc_exec_reply(c, UBUS_STATUS_OK); -} - -int -rpc_exec(const char **args, rpc_exec_write_cb_t in, - rpc_exec_read_cb_t out, rpc_exec_read_cb_t err, - rpc_exec_done_cb_t end, void *priv, struct ubus_context *ctx, - struct ubus_request_data *req) -{ - pid_t pid; - - int ipipe[2]; - int opipe[2]; - int epipe[2]; - - const char *cmd; - struct rpc_exec_context *c; - - cmd = rpc_exec_lookup(args[0]); - - if (!cmd) - return UBUS_STATUS_NOT_FOUND; - - c = malloc(sizeof(*c)); - - if (!c) - return UBUS_STATUS_UNKNOWN_ERROR; - - if (pipe(ipipe)) - goto fail_ipipe; - - if (pipe(opipe)) - goto fail_opipe; - - if (pipe(epipe)) - goto fail_epipe; - - switch ((pid = fork())) - { - case -1: - goto fail_fork; - - case 0: - uloop_done(); - - dup2(ipipe[0], 0); - dup2(opipe[1], 1); - dup2(epipe[1], 2); - - close(ipipe[0]); - close(ipipe[1]); - close(opipe[0]); - close(opipe[1]); - close(epipe[0]); - close(epipe[1]); - - if (execv(cmd, (char * const *)args)) - return rpc_errno_status(); - - default: - memset(c, 0, sizeof(*c)); - blob_buf_init(&c->blob, 0); - - c->stdin_cb = in; - c->stdout_cb = out; - c->stderr_cb = err; - c->finish_cb = end; - c->priv = priv; - - ustream_declare_read(c->opipe, opipe[0], opipe); - ustream_declare_read(c->epipe, epipe[0], epipe); - - c->process.pid = pid; - c->process.cb = rpc_exec_process_cb; - uloop_process_add(&c->process); - - c->timeout.cb = rpc_exec_timeout_cb; - uloop_timeout_set(&c->timeout, rpc_exec_timeout); - - if (c->stdin_cb) - { - ustream_declare_write(c->ipipe, ipipe[1], ipipe); - rpc_exec_ipipe_write_cb(&c->ipipe.stream, 0); - } - else - { - close(ipipe[1]); - } - - close(ipipe[0]); - close(opipe[1]); - close(epipe[1]); - - c->context = ctx; - ubus_defer_request(ctx, req, &c->request); - } - - return UBUS_STATUS_OK; - -fail_fork: - close(epipe[0]); - close(epipe[1]); - -fail_epipe: - close(opipe[0]); - close(opipe[1]); - -fail_opipe: - close(ipipe[0]); - close(ipipe[1]); - -fail_ipipe: - free(c); - return rpc_errno_status(); -} diff --git a/include/rpcd/session.h b/include/rpcd/session.h deleted file mode 100644 index 8ff3ed0..0000000 --- a/include/rpcd/session.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013 Felix Fietkau - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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. - */ - -#ifndef __RPC_SESSION_H -#define __RPC_SESSION_H - -#include -#include -#include -#include -#include -#include - -#include -#include - -#define RPC_SID_LEN 32 -#define RPC_DEFAULT_SESSION_TIMEOUT 300 -#define RPC_DEFAULT_SESSION_ID "00000000000000000000000000000000" -#define RPC_SESSION_DIRECTORY "/var/run/rpcd/sessions" -#define RPC_SESSION_ACL_DIR INSTALL_PREFIX "/share/rpcd/acl.d" - -extern char apply_sid[RPC_SID_LEN + 1]; - -struct rpc_session { - struct avl_node avl; - char id[RPC_SID_LEN + 1]; - - struct uloop_timeout t; - struct avl_tree data; - struct avl_tree acls; - - int timeout; -}; - -struct rpc_session_data { - struct avl_node avl; - struct blob_attr attr[]; -}; - -struct rpc_session_acl_scope { - struct avl_node avl; - struct avl_tree acls; -}; - -struct rpc_session_acl { - struct avl_node avl; - const char *object; - const char *function; - int sort_len; -}; - -int rpc_session_api_init(struct ubus_context *ctx); - -bool rpc_session_access(const char *sid, const char *scope, - const char *object, const char *function); - -struct rpc_session_cb { - struct list_head list; - void (*cb)(struct rpc_session *, void *); - void *priv; -}; - -void rpc_session_create_cb(struct rpc_session_cb *cb); -void rpc_session_destroy_cb(struct rpc_session_cb *cb); - -void rpc_session_freeze(void); -void rpc_session_thaw(void); - -#endif diff --git a/include/rpcd/uci.h b/include/rpcd/uci.h deleted file mode 100644 index 818036f..0000000 --- a/include/rpcd/uci.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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. - */ - -#ifndef __RPC_UCI_H -#define __RPC_UCI_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define RPC_UCI_DIR_PREFIX "/var/run/rpcd" -#define RPC_UCI_SAVEDIR_PREFIX RPC_UCI_DIR_PREFIX "/uci-" -#define RPC_SNAPSHOT_FILES RPC_UCI_DIR_PREFIX "/snapshot-files/" -#define RPC_SNAPSHOT_DELTA RPC_UCI_DIR_PREFIX "/snapshot-delta/" -#define RPC_UCI_DIR "/etc/config/" -#define RPC_APPLY_TIMEOUT 60 - -int rpc_uci_api_init(struct ubus_context *ctx); - -void rpc_uci_purge_savedirs(void); - -#endif diff --git a/main.c b/main.c index d77a814..f50e8f7 100644 --- a/main.c +++ b/main.c @@ -25,12 +25,7 @@ #include #include -#include #include -#include -#include -#include - static struct ubus_context *ctx; static bool respawn = false; @@ -39,7 +34,6 @@ int rpc_exec_timeout = RPC_EXEC_DEFAULT_TIMEOUT; static void handle_signal(int sig) { - rpc_session_freeze(); uloop_cancelled = true; respawn = (sig == SIGHUP); } @@ -93,9 +87,6 @@ int main(int argc, char **argv) return -1; } - if (stat(RPC_UCI_DIR_PREFIX, &s)) - mkdir(RPC_UCI_DIR_PREFIX, 0700); - umask(0077); signal(SIGPIPE, SIG_IGN); @@ -112,18 +103,9 @@ int main(int argc, char **argv) ubus_add_uloop(ctx); - rpc_session_api_init(ctx); - rpc_uci_api_init(ctx); - rpc_rc_api_init(ctx); rpc_plugin_api_init(ctx); - hangup = getenv("RPC_HANGUP"); - if (!hangup || strcmp(hangup, "1")) - rpc_uci_purge_savedirs(); - else - rpc_session_thaw(); - uloop_run(); ubus_free(ctx); uloop_done(); diff --git a/rc.c b/rc.c deleted file mode 100644 index 3d192f1..0000000 --- a/rc.c +++ /dev/null @@ -1,409 +0,0 @@ -// 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_LIST_NAME, - RC_LIST_SKIP_RUNNING_CHECK, - __RC_LIST_MAX -}; - -static const struct blobmsg_policy rc_list_policy[] = { - [RC_LIST_NAME] = { "name", BLOBMSG_TYPE_STRING }, - [RC_LIST_SKIP_RUNNING_CHECK] = { "skip_running_check", BLOBMSG_TYPE_BOOL }, -}; - -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; - bool skip_running_check; - const char *req_name; - - /* Info about currently processed init.d entry */ - struct { - char path[PATH_MAX]; - const char *d_name; - int start; - int stop; - bool enabled; - bool running; - bool use_procd; - } 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 >= 0) - blobmsg_add_u16(c->buf, "start", c->entry.start); - if (c->entry.stop >= 0) - blobmsg_add_u16(c->buf, "stop", c->entry.stop); - blobmsg_add_u8(c->buf, "enabled", c->entry.enabled); - if (!c->skip_running_check && c->entry.use_procd) - 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: - if (c->skip_running_check) - exit(-EFAULT); - - if (!c->entry.use_procd) - exit(-EOPNOTSUPP); - - /* 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 scanning for a specific script and entry.d_name is set - * we can assume we found a matching one in the previous - * iteration since entry.d_name is set only if a match is found. - */ - if (!e || (c->req_name && c->entry.d_name)) { - 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; - - if (c->req_name && strcmp(e->d_name, c->req_name)) - goto next; - - memset(&c->entry, 0, sizeof(c->entry)); - c->entry.start = -1; - c->entry.stop = -1; - - 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[255]; - bool beginning; - int count = 0; - - beginning = true; - while ((c->entry.start < 0 || c->entry.stop < 0 || - (!c->skip_running_check && !c->entry.use_procd)) && - count <= 10 && fgets(line, sizeof(line), fp)) { - if (beginning) { - if (!strncmp(line, "START=", 6)) { - c->entry.start = strtoul(line + 6, NULL, 0); - } else if (!strncmp(line, "STOP=", 5)) { - c->entry.stop = strtoul(line + 5, NULL, 0); - } else if (!c->skip_running_check && !strncmp(line, "USE_PROCD=", 10)) { - c->entry.use_procd = !!strtoul(line + 10, NULL, 0); - } - count++; - } - - beginning = !!strchr(line, '\n'); - } - fclose(fp); - - if (c->entry.start >= 0) { - 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) -{ - struct blob_attr *tb[__RC_LIST_MAX]; - static struct blob_buf buf; - struct rc_list_context *c; - - blobmsg_parse(rc_list_policy, __RC_LIST_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); - - 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; - } - if (tb[RC_LIST_SKIP_RUNNING_CHECK]) - c->skip_running_check = blobmsg_get_bool(tb[RC_LIST_SKIP_RUNNING_CHECK]); - if (tb[RC_LIST_NAME]) - c->req_name = blobmsg_get_string(tb[RC_LIST_NAME]); - - 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("list", rc_list, rc_list_policy), - 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); -} diff --git a/session.c b/session.c deleted file mode 100644 index 195d07d..0000000 --- a/session.c +++ /dev/null @@ -1,1445 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013 Felix Fietkau - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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. - */ - -#define _GNU_SOURCE /* crypt() */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SHADOW -#include -#endif - -#include - -static struct avl_tree sessions; -static struct blob_buf buf; - -static LIST_HEAD(create_callbacks); -static LIST_HEAD(destroy_callbacks); - -enum { - RPC_SN_TIMEOUT, - __RPC_SN_MAX, -}; -static const struct blobmsg_policy new_policy[__RPC_SN_MAX] = { - [RPC_SN_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, -}; - -enum { - RPC_SI_SID, - __RPC_SI_MAX, -}; -static const struct blobmsg_policy sid_policy[__RPC_SI_MAX] = { - [RPC_SI_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_SS_SID, - RPC_SS_VALUES, - __RPC_SS_MAX, -}; -static const struct blobmsg_policy set_policy[__RPC_SS_MAX] = { - [RPC_SS_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, - [RPC_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE }, -}; - -enum { - RPC_SG_SID, - RPC_SG_KEYS, - __RPC_SG_MAX, -}; -static const struct blobmsg_policy get_policy[__RPC_SG_MAX] = { - [RPC_SG_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, - [RPC_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY }, -}; - -enum { - RPC_SA_SID, - RPC_SA_SCOPE, - RPC_SA_OBJECTS, - __RPC_SA_MAX, -}; -static const struct blobmsg_policy acl_policy[__RPC_SA_MAX] = { - [RPC_SA_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, - [RPC_SA_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING }, - [RPC_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY }, -}; - -enum { - RPC_SP_SID, - RPC_SP_SCOPE, - RPC_SP_OBJECT, - RPC_SP_FUNCTION, - __RPC_SP_MAX, -}; -static const struct blobmsg_policy perm_policy[__RPC_SP_MAX] = { - [RPC_SP_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, - [RPC_SP_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING }, - [RPC_SP_OBJECT] = { .name = "object", .type = BLOBMSG_TYPE_STRING }, - [RPC_SP_FUNCTION] = { .name = "function", .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_DUMP_SID, - RPC_DUMP_TIMEOUT, - RPC_DUMP_EXPIRES, - RPC_DUMP_DATA, - __RPC_DUMP_MAX, -}; -static const struct blobmsg_policy dump_policy[__RPC_DUMP_MAX] = { - [RPC_DUMP_SID] = { .name = "ubus_rpc_session", .type = BLOBMSG_TYPE_STRING }, - [RPC_DUMP_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, - [RPC_DUMP_EXPIRES] = { .name = "expires", .type = BLOBMSG_TYPE_INT64 }, - [RPC_DUMP_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE }, -}; - -enum { - RPC_L_USERNAME, - RPC_L_PASSWORD, - RPC_L_TIMEOUT, - __RPC_L_MAX, -}; -static const struct blobmsg_policy login_policy[__RPC_L_MAX] = { - [RPC_L_USERNAME] = { .name = "username", .type = BLOBMSG_TYPE_STRING }, - [RPC_L_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING }, - [RPC_L_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, -}; - -/* - * Keys in the AVL tree contain all pattern characters up to the first wildcard. - * To look up entries, start with the last entry that has a key less than or - * equal to the method name, then work backwards as long as the AVL key still - * matches its counterpart in the object name - */ -#define uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \ - for (_acl = avl_find_le_element(_avl, _obj, _acl, avl); \ - _acl; \ - _acl = avl_is_first(_avl, &(_acl)->avl) ? NULL : \ - avl_prev_element((_acl), avl)) - -#define uh_foreach_matching_acl(_acl, _avl, _obj, _func) \ - uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \ - if (!strncmp((_acl)->object, _obj, (_acl)->sort_len) && \ - !fnmatch((_acl)->object, (_obj), FNM_NOESCAPE) && \ - !fnmatch((_acl)->function, (_func), FNM_NOESCAPE)) - -static int -rpc_random(char *dest) -{ - unsigned char buf[16] = { 0 }; - FILE *f; - int i; - int ret; - - f = fopen("/dev/urandom", "r"); - if (!f) - return -1; - - ret = fread(buf, 1, sizeof(buf), f); - fclose(f); - - if (ret < 0) - return ret; - - for (i = 0; i < sizeof(buf); i++) - sprintf(dest + (i<<1), "%02x", buf[i]); - - return 0; -} - -static void -rpc_session_dump_data(struct rpc_session *ses, struct blob_buf *b) -{ - struct rpc_session_data *d; - - avl_for_each_element(&ses->data, d, avl) { - blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr), - blobmsg_data(d->attr), blobmsg_data_len(d->attr)); - } -} - -static void -rpc_session_dump_acls(struct rpc_session *ses, struct blob_buf *b) -{ - struct rpc_session_acl *acl; - struct rpc_session_acl_scope *acl_scope; - const char *lastobj = NULL; - const char *lastscope = NULL; - void *c = NULL, *d = NULL; - - avl_for_each_element(&ses->acls, acl_scope, avl) { - if (!lastscope || strcmp(acl_scope->avl.key, lastscope)) - { - if (c) blobmsg_close_table(b, c); - c = blobmsg_open_table(b, acl_scope->avl.key); - lastobj = NULL; - } - - d = NULL; - - avl_for_each_element(&acl_scope->acls, acl, avl) { - if (!lastobj || strcmp(acl->object, lastobj)) - { - if (d) blobmsg_close_array(b, d); - d = blobmsg_open_array(b, acl->object); - } - - blobmsg_add_string(b, NULL, acl->function); - lastobj = acl->object; - } - - if (d) blobmsg_close_array(b, d); - } - - if (c) blobmsg_close_table(b, c); -} - -static void -rpc_session_to_blob(struct rpc_session *ses, bool acls) -{ - void *c; - - blob_buf_init(&buf, 0); - - blobmsg_add_string(&buf, "ubus_rpc_session", ses->id); - blobmsg_add_u32(&buf, "timeout", ses->timeout); - blobmsg_add_u64(&buf, "expires", uloop_timeout_remaining64(&ses->t) / 1000); - - if (acls) { - c = blobmsg_open_table(&buf, "acls"); - rpc_session_dump_acls(ses, &buf); - blobmsg_close_table(&buf, c); - } - - c = blobmsg_open_table(&buf, "data"); - rpc_session_dump_data(ses, &buf); - blobmsg_close_table(&buf, c); -} - -static void -rpc_session_dump(struct rpc_session *ses, struct ubus_context *ctx, - struct ubus_request_data *req) -{ - rpc_session_to_blob(ses, true); - - ubus_send_reply(ctx, req, buf.head); -} - -static void -rpc_touch_session(struct rpc_session *ses) -{ - if (ses->timeout > 0) - uloop_timeout_set(&ses->t, ses->timeout * 1000); -} - -static void -rpc_session_destroy(struct rpc_session *ses) -{ - struct rpc_session_acl *acl, *nacl; - struct rpc_session_acl_scope *acl_scope, *nacl_scope; - struct rpc_session_data *data, *ndata; - struct rpc_session_cb *cb; - - list_for_each_entry(cb, &destroy_callbacks, list) - cb->cb(ses, cb->priv); - - uloop_timeout_cancel(&ses->t); - - avl_for_each_element_safe(&ses->acls, acl_scope, avl, nacl_scope) { - avl_remove_all_elements(&acl_scope->acls, acl, avl, nacl) - free(acl); - - avl_delete(&ses->acls, &acl_scope->avl); - free(acl_scope); - } - - avl_remove_all_elements(&ses->data, data, avl, ndata) - free(data); - - avl_delete(&sessions, &ses->avl); - free(ses); -} - -static void rpc_session_timeout(struct uloop_timeout *t) -{ - struct rpc_session *ses; - - ses = container_of(t, struct rpc_session, t); - rpc_session_destroy(ses); -} - -static struct rpc_session * -rpc_session_new(void) -{ - struct rpc_session *ses; - - ses = calloc(1, sizeof(*ses)); - - if (!ses) - return NULL; - - ses->avl.key = ses->id; - - avl_init(&ses->acls, avl_strcmp, true, NULL); - avl_init(&ses->data, avl_strcmp, false, NULL); - - ses->t.cb = rpc_session_timeout; - - return ses; -} - -static struct rpc_session * -rpc_session_create(int timeout) -{ - struct rpc_session *ses; - struct rpc_session_cb *cb; - - ses = rpc_session_new(); - - if (!ses) - return NULL; - - if (rpc_random(ses->id)) - return NULL; - - ses->timeout = timeout; - - avl_insert(&sessions, &ses->avl); - - rpc_touch_session(ses); - - list_for_each_entry(cb, &create_callbacks, list) - cb->cb(ses, cb->priv); - - return ses; -} - -static struct rpc_session * -rpc_session_get(const char *id) -{ - struct rpc_session *ses; - - ses = avl_find_element(&sessions, id, ses, avl); - if (!ses) - return NULL; - - rpc_touch_session(ses); - return ses; -} - -static int -rpc_handle_create(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb; - int timeout = RPC_DEFAULT_SESSION_TIMEOUT; - - blobmsg_parse(new_policy, __RPC_SN_MAX, &tb, blob_data(msg), blob_len(msg)); - if (tb) - timeout = blobmsg_get_u32(tb); - - ses = rpc_session_create(timeout); - if (ses) - rpc_session_dump(ses, ctx, req); - - return 0; -} - -static int -rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb; - - blobmsg_parse(sid_policy, __RPC_SI_MAX, &tb, blob_data(msg), blob_len(msg)); - - if (!tb) { - avl_for_each_element(&sessions, ses, avl) - rpc_session_dump(ses, ctx, req); - return 0; - } - - ses = rpc_session_get(blobmsg_data(tb)); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - rpc_session_dump(ses, ctx, req); - - return 0; -} - -static int -uh_id_len(const char *str) -{ - return strcspn(str, "*?["); -} - -static int -rpc_session_grant(struct rpc_session *ses, - const char *scope, const char *object, const char *function) -{ - struct rpc_session_acl *acl; - struct rpc_session_acl_scope *acl_scope; - char *new_scope, *new_obj, *new_func, *new_id; - int id_len; - - if (!object || !function) - return UBUS_STATUS_INVALID_ARGUMENT; - - acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl); - - if (acl_scope) { - uh_foreach_matching_acl_prefix(acl, &acl_scope->acls, object, function) { - if (!strcmp(acl->object, object) && - !strcmp(acl->function, function)) - return 0; - } - } - - if (!acl_scope) { - acl_scope = calloc_a(sizeof(*acl_scope), - &new_scope, strlen(scope) + 1); - - if (!acl_scope) - return UBUS_STATUS_UNKNOWN_ERROR; - - acl_scope->avl.key = strcpy(new_scope, scope); - avl_init(&acl_scope->acls, avl_strcmp, true, NULL); - avl_insert(&ses->acls, &acl_scope->avl); - } - - id_len = uh_id_len(object); - acl = calloc_a(sizeof(*acl), - &new_obj, strlen(object) + 1, - &new_func, strlen(function) + 1, - &new_id, id_len + 1); - - if (!acl) - return UBUS_STATUS_UNKNOWN_ERROR; - - acl->object = strcpy(new_obj, object); - acl->function = strcpy(new_func, function); - acl->avl.key = strncpy(new_id, object, id_len); - avl_insert(&acl_scope->acls, &acl->avl); - - return 0; -} - -static int -rpc_session_revoke(struct rpc_session *ses, - const char *scope, const char *object, const char *function) -{ - struct rpc_session_acl *acl, *next; - struct rpc_session_acl_scope *acl_scope; - int id_len; - char *id; - - acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl); - - if (!acl_scope) - return 0; - - if (!object && !function) { - avl_remove_all_elements(&acl_scope->acls, acl, avl, next) - free(acl); - avl_delete(&ses->acls, &acl_scope->avl); - free(acl_scope); - return 0; - } - - id_len = uh_id_len(object); - id = alloca(id_len + 1); - strncpy(id, object, id_len); - id[id_len] = 0; - - acl = avl_find_element(&acl_scope->acls, id, acl, avl); - while (acl) { - if (!avl_is_last(&acl_scope->acls, &acl->avl)) - next = avl_next_element(acl, avl); - else - next = NULL; - - if (strcmp(id, acl->avl.key) != 0) - break; - - if (!strcmp(acl->object, object) && - !strcmp(acl->function, function)) { - avl_delete(&acl_scope->acls, &acl->avl); - free(acl); - } - acl = next; - } - - if (avl_is_empty(&acl_scope->acls)) { - avl_delete(&ses->acls, &acl_scope->avl); - free(acl_scope); - } - - return 0; -} - - -static int -rpc_handle_acl(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb[__RPC_SA_MAX]; - struct blob_attr *attr, *sattr; - const char *object, *function; - const char *scope = "ubus"; - int rem1, rem2; - - int (*cb)(struct rpc_session *ses, - const char *scope, const char *object, const char *function); - - blobmsg_parse(acl_policy, __RPC_SA_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_SA_SID]) - return UBUS_STATUS_INVALID_ARGUMENT; - - ses = rpc_session_get(blobmsg_data(tb[RPC_SA_SID])); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - if (tb[RPC_SA_SCOPE]) - scope = blobmsg_data(tb[RPC_SA_SCOPE]); - - if (!strcmp(method, "grant")) - cb = rpc_session_grant; - else - cb = rpc_session_revoke; - - if (!tb[RPC_SA_OBJECTS]) - return cb(ses, scope, NULL, NULL); - - blobmsg_for_each_attr(attr, tb[RPC_SA_OBJECTS], rem1) { - if (blobmsg_type(attr) != BLOBMSG_TYPE_ARRAY) - continue; - - object = NULL; - function = NULL; - - blobmsg_for_each_attr(sattr, attr, rem2) { - if (blobmsg_type(sattr) != BLOBMSG_TYPE_STRING) - continue; - - if (!object) - object = blobmsg_data(sattr); - else if (!function) - function = blobmsg_data(sattr); - else - break; - } - - if (object && function) - cb(ses, scope, object, function); - } - - return 0; -} - -static bool -rpc_session_acl_allowed(struct rpc_session *ses, const char *scope, - const char *obj, const char *fun) -{ - struct rpc_session_acl *acl; - struct rpc_session_acl_scope *acl_scope; - - acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl); - - if (acl_scope) { - uh_foreach_matching_acl(acl, &acl_scope->acls, obj, fun) - return true; - } - - return false; -} - -static int -rpc_handle_access(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb[__RPC_SP_MAX]; - const char *scope = "ubus"; - bool allow; - - blobmsg_parse(perm_policy, __RPC_SP_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_SP_SID]) - return UBUS_STATUS_INVALID_ARGUMENT; - - ses = rpc_session_get(blobmsg_data(tb[RPC_SP_SID])); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - blob_buf_init(&buf, 0); - - if (tb[RPC_SP_OBJECT] && tb[RPC_SP_FUNCTION]) - { - if (tb[RPC_SP_SCOPE]) - scope = blobmsg_data(tb[RPC_SP_SCOPE]); - - allow = rpc_session_acl_allowed(ses, scope, - blobmsg_data(tb[RPC_SP_OBJECT]), - blobmsg_data(tb[RPC_SP_FUNCTION])); - - blobmsg_add_u8(&buf, "access", allow); - } - else - { - rpc_session_dump_acls(ses, &buf); - } - - ubus_send_reply(ctx, req, buf.head); - - return 0; -} - -static void -rpc_session_set(struct rpc_session *ses, struct blob_attr *val) -{ - struct rpc_session_data *data; - - data = avl_find_element(&ses->data, blobmsg_name(val), data, avl); - if (data) { - avl_delete(&ses->data, &data->avl); - free(data); - } - - data = calloc(1, sizeof(*data) + blob_pad_len(val)); - if (!data) - return; - - memcpy(data->attr, val, blob_pad_len(val)); - data->avl.key = blobmsg_name(data->attr); - avl_insert(&ses->data, &data->avl); -} - -static int -rpc_handle_set(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb[__RPC_SS_MAX]; - struct blob_attr *attr; - int rem; - - blobmsg_parse(set_policy, __RPC_SS_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_SS_SID] || !tb[RPC_SS_VALUES]) - return UBUS_STATUS_INVALID_ARGUMENT; - - ses = rpc_session_get(blobmsg_data(tb[RPC_SS_SID])); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - blobmsg_for_each_attr(attr, tb[RPC_SS_VALUES], rem) { - if (!blobmsg_name(attr)[0]) - continue; - - rpc_session_set(ses, attr); - } - - return 0; -} - -static int -rpc_handle_get(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct rpc_session_data *data; - struct blob_attr *tb[__RPC_SG_MAX]; - struct blob_attr *attr; - void *c; - int rem; - - blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_SG_SID]) - return UBUS_STATUS_INVALID_ARGUMENT; - - ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID])); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - blob_buf_init(&buf, 0); - c = blobmsg_open_table(&buf, "values"); - - if (tb[RPC_SG_KEYS]) - blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) { - if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING) - continue; - - data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl); - if (!data) - continue; - - blobmsg_add_field(&buf, blobmsg_type(data->attr), - blobmsg_name(data->attr), - blobmsg_data(data->attr), - blobmsg_data_len(data->attr)); - } - else - rpc_session_dump_data(ses, &buf); - - blobmsg_close_table(&buf, c); - ubus_send_reply(ctx, req, buf.head); - - return 0; -} - -static int -rpc_handle_unset(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct rpc_session_data *data, *ndata; - struct blob_attr *tb[__RPC_SA_MAX]; - struct blob_attr *attr; - int rem; - - blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_SG_SID]) - return UBUS_STATUS_INVALID_ARGUMENT; - - ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID])); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - if (!tb[RPC_SG_KEYS]) { - avl_remove_all_elements(&ses->data, data, avl, ndata) - free(data); - return 0; - } - - blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) { - if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING) - continue; - - data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl); - if (!data) - continue; - - avl_delete(&ses->data, &data->avl); - free(data); - } - - return 0; -} - -static int -rpc_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct rpc_session *ses; - struct blob_attr *tb; - - blobmsg_parse(sid_policy, __RPC_SI_MAX, &tb, blob_data(msg), blob_len(msg)); - - if (!tb) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!strcmp(blobmsg_get_string(tb), RPC_DEFAULT_SESSION_ID)) - return UBUS_STATUS_PERMISSION_DENIED; - - ses = rpc_session_get(blobmsg_data(tb)); - if (!ses) - return UBUS_STATUS_NOT_FOUND; - - rpc_session_destroy(ses); - - return 0; -} - - -static bool -rpc_login_test_password(const char *hash, const char *password) -{ - char *crypt_hash; - - /* password is not set */ - if (!hash || !*hash) - { - return true; - } - - /* password hash refers to shadow/passwd */ - else if (!strncmp(hash, "$p$", 3)) - { -#ifdef HAVE_SHADOW - struct spwd *sp = getspnam(hash + 3); - - if (!sp) - return false; - - return rpc_login_test_password(sp->sp_pwdp, password); -#else - struct passwd *pw = getpwnam(hash + 3); - - if (!pw) - return false; - - return rpc_login_test_password(pw->pw_passwd, password); -#endif - } - - crypt_hash = crypt(password, hash); - - return (crypt_hash && !strcmp(crypt_hash, hash)); -} - -static struct uci_section * -rpc_login_test_login(struct uci_context *uci, - const char *username, const char *password) -{ - struct uci_package *p = NULL; - struct uci_section *s; - struct uci_element *e; - struct uci_ptr ptr = { .package = "rpcd" }; - - if (!uci_lookup_ptr(uci, &ptr, NULL, false) && ptr.p) { - uci_unload(uci, ptr.p); - ptr.flags = 0; - ptr.p = NULL; - } - - uci_load(uci, ptr.package, &p); - - if (!p) - return false; - - uci_foreach_element(&p->sections, e) - { - s = uci_to_section(e); - - if (strcmp(s->type, "login")) - continue; - - ptr.section = s->e.name; - ptr.s = NULL; - - /* test for matching username */ - ptr.option = "username"; - ptr.o = NULL; - - if (uci_lookup_ptr(uci, &ptr, NULL, true)) - continue; - - if (!ptr.o) - continue; - - if (ptr.o->type != UCI_TYPE_STRING) - continue; - - if (strcmp(ptr.o->v.string, username)) - continue; - - /* If password is NULL, we're restoring ACLs for an existing session, - * in this case do not check the password again. */ - if (!password) - return ptr.s; - - /* test for matching password */ - ptr.option = "password"; - ptr.o = NULL; - - if (uci_lookup_ptr(uci, &ptr, NULL, true)) - continue; - - if (!ptr.o) - continue; - - if (ptr.o->type != UCI_TYPE_STRING) - continue; - - if (rpc_login_test_password(ptr.o->v.string, password)) - return ptr.s; - } - - return NULL; -} - -static bool -rpc_login_test_permission(struct uci_section *s, - const char *perm, const char *group) -{ - const char *p; - struct uci_option *o; - struct uci_element *e, *l; - - /* If the login section is not provided, we're setting up acls for the - * default session, in this case uncondionally allow access to the - * "unauthenticated" access group */ - if (!s) { - return !strcmp(group, "unauthenticated"); - } - - uci_foreach_element(&s->options, e) - { - o = uci_to_option(e); - - if (o->type != UCI_TYPE_LIST) - continue; - - if (strcmp(o->e.name, perm)) - continue; - - /* Match negative expressions first. If a negative expression matches - * the current group name then deny access. */ - uci_foreach_element(&o->v.list, l) { - p = l->name; - - if (!p || *p != '!') - continue; - - while (isspace(*++p)); - - if (!*p) - continue; - - if (!fnmatch(p, group, 0)) - return false; - } - - uci_foreach_element(&o->v.list, l) { - if (!l->name || !*l->name || *l->name == '!') - continue; - - if (!fnmatch(l->name, group, 0)) - return true; - } - } - - /* make sure that write permission implies read permission */ - if (!strcmp(perm, "read")) - return rpc_login_test_permission(s, "write", group); - - return false; -} - -static void -rpc_login_setup_acl_scope(struct rpc_session *ses, - struct blob_attr *acl_perm, - struct blob_attr *acl_scope) -{ - struct blob_attr *acl_obj, *acl_func; - int rem, rem2; - - /* - * Parse ACL scopes in table notation. - * - * "": { - * "": [ - * "", - * "", - * ... - * ] - * } - */ - if (blobmsg_type(acl_scope) == BLOBMSG_TYPE_TABLE) { - blobmsg_for_each_attr(acl_obj, acl_scope, rem) { - if (blobmsg_type(acl_obj) != BLOBMSG_TYPE_ARRAY) - continue; - - blobmsg_for_each_attr(acl_func, acl_obj, rem2) { - if (blobmsg_type(acl_func) != BLOBMSG_TYPE_STRING) - continue; - - rpc_session_grant(ses, blobmsg_name(acl_scope), - blobmsg_name(acl_obj), - blobmsg_data(acl_func)); - } - } - } - - /* - * Parse ACL scopes in array notation. The permission ("read" or "write") - * will be used as function name for each object. - * - * "": [ - * "", - * "", - * ... - * ] - */ - else if (blobmsg_type(acl_scope) == BLOBMSG_TYPE_ARRAY) { - blobmsg_for_each_attr(acl_obj, acl_scope, rem) { - if (blobmsg_type(acl_obj) != BLOBMSG_TYPE_STRING) - continue; - - rpc_session_grant(ses, blobmsg_name(acl_scope), - blobmsg_data(acl_obj), - blobmsg_name(acl_perm)); - } - } -} - -static void -rpc_login_setup_acl_file(struct rpc_session *ses, struct uci_section *login, - const char *path) -{ - struct blob_buf acl = { 0 }; - struct blob_attr *acl_group, *acl_perm, *acl_scope; - int rem, rem2, rem3; - - blob_buf_init(&acl, 0); - - if (!blobmsg_add_json_from_file(&acl, path)) { - fprintf(stderr, "Failed to parse %s\n", path); - goto out; - } - - /* Iterate access groups in toplevel object */ - blob_for_each_attr(acl_group, acl.head, rem) { - /* Iterate permission objects in each access group object */ - blobmsg_for_each_attr(acl_perm, acl_group, rem2) { - if (blobmsg_type(acl_perm) != BLOBMSG_TYPE_TABLE) - continue; - - /* Only "read" and "write" permissions are defined */ - if (strcmp(blobmsg_name(acl_perm), "read") && - strcmp(blobmsg_name(acl_perm), "write")) - continue; - - /* - * Check if the current user context specifies the current - * "read" or "write" permission in the given access group. - */ - if (!rpc_login_test_permission(login, blobmsg_name(acl_perm), - blobmsg_name(acl_group))) - continue; - - /* Iterate scope objects within the permission object */ - blobmsg_for_each_attr(acl_scope, acl_perm, rem3) { - /* Setup the scopes of the access group */ - rpc_login_setup_acl_scope(ses, acl_perm, acl_scope); - - /* - * Add the access group itself as object to the "access-group" - * meta scope and the the permission level ("read" or "write") - * as function, so - * "": { - * "": { - * "": ... - * } - * } - * becomes - * "access-group": { - * "": [ - * "" - * ] - * } - * - * This allows session clients to easily query the allowed - * access groups without having to test access of each single - * // tuple defined in a group. - */ - rpc_session_grant(ses, "access-group", - blobmsg_name(acl_group), - blobmsg_name(acl_perm)); - } - } - } - -out: - blob_buf_free(&acl); -} - -static void -rpc_login_setup_acls(struct rpc_session *ses, struct uci_section *login) -{ - int i; - glob_t gl; - - if (glob(RPC_SESSION_ACL_DIR "/*.json", 0, NULL, &gl)) - return; - - for (i = 0; i < gl.gl_pathc; i++) - rpc_login_setup_acl_file(ses, login, gl.gl_pathv[i]); - - globfree(&gl); -} - -static struct rpc_session * -rpc_reclaim_apply_session(const char *expected_username) -{ - struct rpc_session_data *username; - struct rpc_session *ses; - - if (!apply_sid[0]) - return NULL; - - ses = rpc_session_get(apply_sid); - - if (!ses) - return NULL; - - username = avl_find_element(&ses->data, "username", username, avl); - - if (!username || blobmsg_type(username->attr) != BLOBMSG_TYPE_STRING) - return NULL; - - if (strcmp(blobmsg_get_string(username->attr), expected_username)) - return NULL; - - return ses; -} - -static int -rpc_handle_login(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct uci_context *uci = NULL; - struct uci_section *login; - struct rpc_session *ses; - struct blob_attr *tb[__RPC_L_MAX]; - int timeout = RPC_DEFAULT_SESSION_TIMEOUT; - int rv = 0; - - blobmsg_parse(login_policy, __RPC_L_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_L_USERNAME] || !tb[RPC_L_PASSWORD]) { - rv = UBUS_STATUS_INVALID_ARGUMENT; - goto out; - } - - uci = uci_alloc_context(); - - if (!uci) { - rv = UBUS_STATUS_UNKNOWN_ERROR; - goto out; - } - - login = rpc_login_test_login(uci, blobmsg_get_string(tb[RPC_L_USERNAME]), - blobmsg_get_string(tb[RPC_L_PASSWORD])); - - if (!login) { - rv = UBUS_STATUS_PERMISSION_DENIED; - goto out; - } - - if (tb[RPC_L_TIMEOUT]) - timeout = blobmsg_get_u32(tb[RPC_L_TIMEOUT]); - - /* - * attempt to reclaim a pending apply session, but only accept it - * if the username matches, otherwise perform a new login - */ - - ses = rpc_reclaim_apply_session(blobmsg_get_string(tb[RPC_L_USERNAME])); - - if (!ses) - ses = rpc_session_create(timeout); - - if (!ses) { - rv = UBUS_STATUS_UNKNOWN_ERROR; - goto out; - } - - rpc_login_setup_acls(ses, login); - - rpc_session_set(ses, tb[RPC_L_USERNAME]); - rpc_session_dump(ses, ctx, req); - -out: - if (uci) - uci_free_context(uci); - - return rv; -} - - -static bool -rpc_validate_sid(const char *id) -{ - if (!id) - return false; - - if (strlen(id) != RPC_SID_LEN) - return false; - - while (*id) - if (!isxdigit(*id++)) - return false; - - return true; -} - -static int -rpc_blob_to_file(const char *path, struct blob_attr *attr) -{ - int fd, len; - - fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); - - if (fd < 0) - return fd; - - len = write(fd, attr, blob_pad_len(attr)); - - close(fd); - - if (len != blob_pad_len(attr)) - { - unlink(path); - return -1; - } - - return len; -} - -static struct blob_attr * -rpc_blob_from_file(const char *path) -{ - int fd = -1, len; - struct stat s; - struct blob_attr head, *attr = NULL; - - if (stat(path, &s) || !S_ISREG(s.st_mode)) - return NULL; - - fd = open(path, O_RDONLY); - - if (fd < 0) - goto fail; - - len = read(fd, &head, sizeof(head)); - - if (len != sizeof(head) || blob_pad_len(&head) != s.st_size) - goto fail; - - attr = calloc(1, s.st_size); - - if (!attr) - goto fail; - - memcpy(attr, &head, sizeof(head)); - - len += read(fd, (char *)attr + sizeof(head), s.st_size - sizeof(head)); - - if (len != blob_pad_len(&head)) - goto fail; - - close(fd); - - return attr; - -fail: - if (fd >= 0) - close(fd); - - if (attr) - free(attr); - - return NULL; -} - -static bool -rpc_session_from_blob(struct uci_context *uci, struct blob_attr *attr) -{ - int i, rem; - const char *user = NULL; - struct rpc_session *ses; - struct uci_section *login; - struct blob_attr *tb[__RPC_DUMP_MAX], *data; - - blobmsg_parse(dump_policy, __RPC_DUMP_MAX, tb, - blob_data(attr), blob_len(attr)); - - for (i = 0; i < __RPC_DUMP_MAX; i++) - if (!tb[i]) - return false; - - ses = rpc_session_new(); - - if (!ses) - return false; - - memcpy(ses->id, blobmsg_data(tb[RPC_DUMP_SID]), RPC_SID_LEN); - - ses->timeout = blobmsg_get_u32(tb[RPC_DUMP_TIMEOUT]); - - blobmsg_for_each_attr(data, tb[RPC_DUMP_DATA], rem) { - rpc_session_set(ses, data); - - if (blobmsg_type(data) != BLOBMSG_TYPE_STRING) - continue; - - if (!strcmp(blobmsg_name(data), "username")) - user = blobmsg_get_string(data); - } - - if (uci && user) { - login = rpc_login_test_login(uci, user, NULL); - if (login) - rpc_login_setup_acls(ses, login); - } - - avl_insert(&sessions, &ses->avl); - - uloop_timeout_set(&ses->t, blobmsg_get_u64(tb[RPC_DUMP_EXPIRES]) * 1000); - - return true; -} - -int rpc_session_api_init(struct ubus_context *ctx) -{ - struct rpc_session *ses; - - static const struct ubus_method session_methods[] = { - UBUS_METHOD("create", rpc_handle_create, new_policy), - UBUS_METHOD("list", rpc_handle_list, sid_policy), - UBUS_METHOD("grant", rpc_handle_acl, acl_policy), - UBUS_METHOD("revoke", rpc_handle_acl, acl_policy), - UBUS_METHOD("access", rpc_handle_access, perm_policy), - UBUS_METHOD("set", rpc_handle_set, set_policy), - UBUS_METHOD("get", rpc_handle_get, get_policy), - UBUS_METHOD("unset", rpc_handle_unset, get_policy), - UBUS_METHOD("destroy", rpc_handle_destroy, sid_policy), - UBUS_METHOD("login", rpc_handle_login, login_policy), - }; - - static struct ubus_object_type session_type = - UBUS_OBJECT_TYPE("rpcd-plugin-session", session_methods); - - static struct ubus_object obj = { - .name = "session", - .type = &session_type, - .methods = session_methods, - .n_methods = ARRAY_SIZE(session_methods), - }; - - avl_init(&sessions, avl_strcmp, false, NULL); - - /* setup the default session */ - ses = rpc_session_new(); - - if (ses) { - strcpy(ses->id, RPC_DEFAULT_SESSION_ID); - rpc_login_setup_acls(ses, NULL); - avl_insert(&sessions, &ses->avl); - } - - return ubus_add_object(ctx, &obj); -} - -bool rpc_session_access(const char *sid, const char *scope, - const char *object, const char *function) -{ - struct rpc_session *ses = rpc_session_get(sid); - - if (!ses) - return false; - - return rpc_session_acl_allowed(ses, scope, object, function); -} - -void rpc_session_create_cb(struct rpc_session_cb *cb) -{ - if (cb && cb->cb) - list_add(&cb->list, &create_callbacks); -} - -void rpc_session_destroy_cb(struct rpc_session_cb *cb) -{ - if (cb && cb->cb) - list_add(&cb->list, &destroy_callbacks); -} - -void rpc_session_freeze(void) -{ - struct stat s; - struct rpc_session *ses; - char path[PATH_MAX]; - - if (stat(RPC_SESSION_DIRECTORY, &s)) - mkdir(RPC_SESSION_DIRECTORY, 0700); - - avl_for_each_element(&sessions, ses, avl) { - /* skip default session */ - if (!strcmp(ses->id, RPC_DEFAULT_SESSION_ID)) - continue; - - snprintf(path, sizeof(path) - 1, RPC_SESSION_DIRECTORY "/%s", ses->id); - rpc_session_to_blob(ses, false); - rpc_blob_to_file(path, buf.head); - } -} - -void rpc_session_thaw(void) -{ - DIR *d; - char path[PATH_MAX]; - struct dirent *e; - struct blob_attr *attr; - struct uci_context *uci; - - d = opendir(RPC_SESSION_DIRECTORY); - - if (!d) - return; - - uci = uci_alloc_context(); - - if (!uci) - return; - - while ((e = readdir(d)) != NULL) { - if (!rpc_validate_sid(e->d_name)) - continue; - - snprintf(path, sizeof(path) - 1, - RPC_SESSION_DIRECTORY "/%s", e->d_name); - - attr = rpc_blob_from_file(path); - - if (attr) { - rpc_session_from_blob(uci, attr); - free(attr); - } - - unlink(path); - } - - closedir(d); - - uci_free_context(uci); -} diff --git a/sys.c b/sys.c deleted file mode 100644 index da3508e..0000000 --- a/sys.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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 -#include - -#include -#include -#include -#include - -static const struct rpc_daemon_ops *ops; - -enum { - RPC_P_USER, - RPC_P_PASSWORD, - __RPC_P_MAX -}; - -static const struct blobmsg_policy rpc_password_policy[__RPC_P_MAX] = { - [RPC_P_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING }, - [RPC_P_PASSWORD] = { .name = "password", .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_UPGRADE_KEEP, - __RPC_UPGRADE_MAX -}; - -static const struct blobmsg_policy rpc_upgrade_policy[__RPC_UPGRADE_MAX] = { - [RPC_UPGRADE_KEEP] = { .name = "keep", .type = BLOBMSG_TYPE_BOOL }, -}; - -enum { - RPC_PACKAGELIST_ALL, - __RPC_PACKAGELIST_MAX -}; - -static const struct blobmsg_policy rpc_packagelist_policy[__RPC_PACKAGELIST_MAX] = { - [RPC_PACKAGELIST_ALL] = { .name = "all", .type = BLOBMSG_TYPE_BOOL }, -}; - -static int -rpc_errno_status(void) -{ - switch (errno) - { - case EACCES: - return UBUS_STATUS_PERMISSION_DENIED; - - case ENOTDIR: - return UBUS_STATUS_INVALID_ARGUMENT; - - case ENOENT: - return UBUS_STATUS_NOT_FOUND; - - case EINVAL: - return UBUS_STATUS_INVALID_ARGUMENT; - - default: - return UBUS_STATUS_UNKNOWN_ERROR; - } -} - -static int -rpc_cgi_password_set(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - pid_t pid; - int fd, fds[2]; - struct stat s; - struct blob_attr *tb[__RPC_P_MAX]; - ssize_t n; - int ret; - const char *const passwd = "/bin/passwd"; - const struct timespec ts = {0, 100 * 1000 * 1000}; - - blobmsg_parse(rpc_password_policy, __RPC_P_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_P_USER] || !tb[RPC_P_PASSWORD]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (stat(passwd, &s)) - return UBUS_STATUS_NOT_FOUND; - - if (!(s.st_mode & S_IXUSR)) - return UBUS_STATUS_PERMISSION_DENIED; - - if (pipe(fds)) - return rpc_errno_status(); - - switch ((pid = fork())) - { - case -1: - close(fds[0]); - close(fds[1]); - return rpc_errno_status(); - - case 0: - uloop_done(); - - dup2(fds[0], 0); - close(fds[0]); - close(fds[1]); - - if ((fd = open("/dev/null", O_RDWR)) > -1) - { - dup2(fd, 1); - dup2(fd, 2); - close(fd); - } - - ret = chdir("/"); - if (ret < 0) - return rpc_errno_status(); - - if (execl(passwd, passwd, - blobmsg_data(tb[RPC_P_USER]), NULL)) - return rpc_errno_status(); - - default: - close(fds[0]); - - n = write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), - blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); - if (n < 0) - return rpc_errno_status(); - - n = write(fds[1], "\n", 1); - if (n < 0) - return rpc_errno_status(); - - nanosleep(&ts, NULL); - - n = write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), - blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); - if (n < 0) - return rpc_errno_status(); - n = write(fds[1], "\n", 1); - if (n < 0) - return rpc_errno_status(); - - close(fds[1]); - - waitpid(pid, NULL, 0); - - return 0; - } -} - -static bool -is_field(const char *type, const char *line) -{ - return strncmp(line, type, strlen(type)) == 0; -} - -static bool -is_blank(const char *line) -{ - for (; *line; line++) - if (!isspace(*line)) - return false; - return true; -} - -static int -rpc_sys_packagelist(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_PACKAGELIST_MAX]; - bool all = false, installed = false, auto_installed = false; - struct blob_buf buf = { 0 }; - char line[256], tmp[128], pkg[128], ver[128]; - char *pkg_abi; - void *tbl; - - blobmsg_parse(rpc_packagelist_policy, __RPC_PACKAGELIST_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (tb[RPC_PACKAGELIST_ALL] && blobmsg_get_bool(tb[RPC_PACKAGELIST_ALL])) - all = true; - - FILE *f = fopen("/usr/lib/opkg/status", "r"); - if (!f) - return UBUS_STATUS_NOT_FOUND; - - blob_buf_init(&buf, 0); - tbl = blobmsg_open_table(&buf, "packages"); - pkg[0] = ver[0] = '\0'; - - while (fgets(line, sizeof(line), f)) { - switch (line[0]) { - case 'A': - if (is_field("ABIVersion", line)) { - /* if there is ABIVersion, remove that suffix */ - if (sscanf(line, "ABIVersion: %127s", tmp) == 1 - && strlen(tmp) < strlen(pkg)) { - pkg_abi = pkg + (strlen(pkg) - strlen(tmp)); - if (strncmp(pkg_abi, tmp, strlen(tmp)) == 0) - pkg_abi[0] = '\0'; - } - } else if (is_field("Auto-Installed", line)) - if (sscanf(line, "Auto-Installed: %63s", tmp) == 1) - auto_installed = (strcmp(tmp, "yes") == 0); - break; - case 'P': - if (is_field("Package", line)) - if (sscanf(line, "Package: %127s", pkg) != 1) - pkg[0] = '\0'; - break; - case 'V': - if (is_field("Version", line)) - if (sscanf(line, "Version: %127s", ver) != 1) - ver[0] = '\0'; - break; - case 'S': - if (is_field("Status", line)) - if (sscanf(line, "Status: install %63s installed", tmp) == 1) - installed = true; - break; - default: - if (is_blank(line)) { - if (installed && (all || !auto_installed) && pkg[0] && ver[0]) - blobmsg_add_string(&buf, pkg, ver); - pkg[0] = ver[0] = '\0'; - installed = auto_installed = false; - } - break; - } - } - - blobmsg_close_table(&buf, tbl); - ubus_send_reply(ctx, req, buf.head); - blob_buf_free(&buf); - fclose(f); - - return 0; -} - -static int -rpc_sys_upgrade_test(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - const char *cmd[4] = { "sysupgrade", "--test", "/tmp/firmware.bin", NULL }; - return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); -} - -static int -rpc_sys_upgrade_start(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_UPGRADE_MAX]; - char * const cmd[4] = { "/sbin/sysupgrade", "-n", "/tmp/firmware.bin", NULL }; - char * const cmd_keep[3] = { "/sbin/sysupgrade", "/tmp/firmware.bin", NULL }; - char * const * c = cmd; - - blobmsg_parse(rpc_upgrade_policy, __RPC_UPGRADE_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (tb[RPC_UPGRADE_KEEP] && blobmsg_get_bool(tb[RPC_UPGRADE_KEEP])) - c = cmd_keep; - - if (!fork()) { - /* wait for the RPC call to complete */ - sleep(2); - return execv(c[0], c); - } - - return 0; -} - -static int -rpc_sys_upgrade_clean(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - if (unlink("/tmp/firmware.bin")) - return rpc_errno_status(); - - return 0; -} - -static int -rpc_sys_factory(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - char * const cmd[4] = { "/sbin/jffs2reset", "-y", "-r", NULL }; - - if (!fork()) { - /* wait for the RPC call to complete */ - sleep(2); - return execv(cmd[0], cmd); - } - - return 0; -} - -static int -rpc_sys_reboot(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - if (!fork()) { - sync(); - sleep(2); - reboot(RB_AUTOBOOT); - while (1) - ; - } - - return 0; -} - -static int -rpc_sys_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx) -{ - static const struct ubus_method sys_methods[] = { - UBUS_METHOD("packagelist", rpc_sys_packagelist, rpc_packagelist_policy), - UBUS_METHOD("password_set", rpc_cgi_password_set, rpc_password_policy), - UBUS_METHOD_NOARG("upgrade_test", rpc_sys_upgrade_test), - UBUS_METHOD("upgrade_start", rpc_sys_upgrade_start, - rpc_upgrade_policy), - UBUS_METHOD_NOARG("upgrade_clean", rpc_sys_upgrade_clean), - UBUS_METHOD_NOARG("factory", rpc_sys_factory), - UBUS_METHOD_NOARG("reboot", rpc_sys_reboot), - }; - - static struct ubus_object_type sys_type = - UBUS_OBJECT_TYPE("rpcd-plugin-sys", sys_methods); - - static struct ubus_object obj = { - .name = "rpc-sys", - .type = &sys_type, - .methods = sys_methods, - .n_methods = ARRAY_SIZE(sys_methods), - }; - - ops = o; - - return ubus_add_object(ctx, &obj); -} - -struct rpc_plugin rpc_plugin = { - .init = rpc_sys_api_init -}; diff --git a/uci.c b/uci.c deleted file mode 100644 index 3cd2b82..0000000 --- a/uci.c +++ /dev/null @@ -1,1802 +0,0 @@ -/* - * rpcd - UBUS RPC server - * - * Copyright (C) 2013-2014 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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 -#include - -#include -#include - -#include -#include -#include - -static struct blob_buf buf; -static struct uci_context *cursor; -static struct uloop_timeout apply_timer; -static struct ubus_context *apply_ctx; - -char apply_sid[RPC_SID_LEN + 1]; - -enum { - RPC_G_CONFIG, - RPC_G_SECTION, - RPC_G_OPTION, - RPC_G_TYPE, - RPC_G_MATCH, - RPC_G_SESSION, - __RPC_G_MAX, -}; - -static const struct blobmsg_policy rpc_uci_get_policy[__RPC_G_MAX] = { - [RPC_G_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_G_SECTION] = { .name = "section", .type = BLOBMSG_TYPE_STRING }, - [RPC_G_OPTION] = { .name = "option", .type = BLOBMSG_TYPE_STRING }, - [RPC_G_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, - [RPC_G_MATCH] = { .name = "match", .type = BLOBMSG_TYPE_TABLE }, - [RPC_G_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_A_CONFIG, - RPC_A_TYPE, - RPC_A_NAME, - RPC_A_VALUES, - RPC_A_SESSION, - __RPC_A_MAX, -}; - -static const struct blobmsg_policy rpc_uci_add_policy[__RPC_A_MAX] = { - [RPC_A_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_A_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, - [RPC_A_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, - [RPC_A_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE }, - [RPC_A_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_S_CONFIG, - RPC_S_SECTION, - RPC_S_TYPE, - RPC_S_MATCH, - RPC_S_VALUES, - RPC_S_SESSION, - __RPC_S_MAX, -}; - -static const struct blobmsg_policy rpc_uci_set_policy[__RPC_S_MAX] = { - [RPC_S_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_S_SECTION] = { .name = "section", .type = BLOBMSG_TYPE_STRING }, - [RPC_S_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, - [RPC_S_MATCH] = { .name = "match", .type = BLOBMSG_TYPE_TABLE }, - [RPC_S_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE }, - [RPC_S_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_D_CONFIG, - RPC_D_SECTION, - RPC_D_TYPE, - RPC_D_MATCH, - RPC_D_OPTION, - RPC_D_OPTIONS, - RPC_D_SESSION, - __RPC_D_MAX, -}; - -static const struct blobmsg_policy rpc_uci_delete_policy[__RPC_D_MAX] = { - [RPC_D_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_D_SECTION] = { .name = "section", .type = BLOBMSG_TYPE_STRING }, - [RPC_D_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, - [RPC_D_MATCH] = { .name = "match", .type = BLOBMSG_TYPE_TABLE }, - [RPC_D_OPTION] = { .name = "option", .type = BLOBMSG_TYPE_STRING }, - [RPC_D_OPTIONS] = { .name = "options", .type = BLOBMSG_TYPE_ARRAY }, - [RPC_D_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_R_CONFIG, - RPC_R_SECTION, - RPC_R_OPTION, - RPC_R_NAME, - RPC_R_SESSION, - __RPC_R_MAX, -}; - -static const struct blobmsg_policy rpc_uci_rename_policy[__RPC_R_MAX] = { - [RPC_R_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_R_SECTION] = { .name = "section", .type = BLOBMSG_TYPE_STRING }, - [RPC_R_OPTION] = { .name = "option", .type = BLOBMSG_TYPE_STRING }, - [RPC_R_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, - [RPC_R_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_O_CONFIG, - RPC_O_SECTIONS, - RPC_O_SESSION, - __RPC_O_MAX, -}; - -static const struct blobmsg_policy rpc_uci_order_policy[__RPC_O_MAX] = { - [RPC_O_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_O_SECTIONS] = { .name = "sections", .type = BLOBMSG_TYPE_ARRAY }, - [RPC_O_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_C_CONFIG, - RPC_C_SESSION, - __RPC_C_MAX, -}; - -static const struct blobmsg_policy rpc_uci_config_policy[__RPC_C_MAX] = { - [RPC_C_CONFIG] = { .name = "config", .type = BLOBMSG_TYPE_STRING }, - [RPC_C_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_T_ROLLBACK, - RPC_T_TIMEOUT, - RPC_T_SESSION, - __RPC_T_MAX, -}; - -static const struct blobmsg_policy rpc_uci_apply_policy[__RPC_T_MAX] = { - [RPC_T_ROLLBACK] = { .name = "rollback", .type = BLOBMSG_TYPE_BOOL }, - [RPC_T_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 }, - [RPC_T_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -enum { - RPC_B_SESSION, - __RPC_B_MAX, -}; - -static const struct blobmsg_policy rpc_uci_rollback_policy[__RPC_B_MAX] = { - [RPC_B_SESSION] = { .name = "ubus_rpc_session", - .type = BLOBMSG_TYPE_STRING }, -}; - -/* - * Validate a uci name - */ -static bool -rpc_uci_verify_str(const char *name, bool extended, bool type) -{ - const char *c; - char *e; - - if (!name || !*name) - return false; - - if (extended && *name != '@') - extended = false; - - for (c = name + extended; *c; c++) - if (!isalnum(*c) && *c != '_' && ((!type && !extended) || *c != '-')) - break; - - if (extended) { - if (*c != '[') - return false; - - strtol(++c, &e, 10); - - return (e > c && *e == ']' && *(e+1) == 0); - } - - return (*c == 0); -} - -/* - * Check that string is a valid, shell compatible uci name - */ -static bool rpc_uci_verify_name(const char *name) { - return rpc_uci_verify_str(name, false, false); -} - -/* - * Check that string is a valid section type name - */ -static bool rpc_uci_verify_type(const char *type) { - return rpc_uci_verify_str(type, false, true); -} - -/* - * Check that the string is a valid section id, optionally in extended - * lookup notation - */ -static bool rpc_uci_verify_section(const char *section) { - return rpc_uci_verify_str(section, true, false); -} - - -/* - * Turn uci error state into ubus return code - */ -static int -rpc_uci_status(void) -{ - switch (cursor->err) - { - case UCI_OK: - return UBUS_STATUS_OK; - - case UCI_ERR_INVAL: - return UBUS_STATUS_INVALID_ARGUMENT; - - case UCI_ERR_NOTFOUND: - return UBUS_STATUS_NOT_FOUND; - - default: - return UBUS_STATUS_UNKNOWN_ERROR; - } -} - -/* - * Clear all save directories from the uci cursor and append the given path - * as new save directory. - */ -static void -rpc_uci_replace_savedir(const char *path) -{ - struct uci_element *e, *tmp; - - uci_foreach_element_safe(&cursor->delta_path, tmp, e) { - if (e->name) - free(e->name); - - free(e); - } - - cursor->delta_path.prev = &cursor->delta_path; - cursor->delta_path.next = &cursor->delta_path; - - if (path) - uci_set_savedir(cursor, path); -} - -/* - * Setup per-session delta save directory. If the passed "sid" blob attribute - * pointer is NULL then the precedure was not invoked through the ubus-rpc so - * we do not perform session isolation and use the default save directory. - */ -static void -rpc_uci_set_savedir(struct blob_attr *sid) -{ - char path[PATH_MAX]; - - if (!sid) - { - rpc_uci_replace_savedir("/tmp/.uci"); - return; - } - - snprintf(path, sizeof(path) - 1, - RPC_UCI_SAVEDIR_PREFIX "%s", blobmsg_get_string(sid)); - - rpc_uci_replace_savedir(path); -} - -/* - * Test read access to given config. If the passed "sid" blob attribute pointer - * is NULL then the precedure was not invoked through the ubus-rpc so we do not - * perform access control and always assume true. - */ -static bool -rpc_uci_read_access(struct blob_attr *sid, struct blob_attr *config) -{ - rpc_uci_set_savedir(sid); - - if (!sid) - return true; - - return rpc_session_access(blobmsg_data(sid), "uci", - blobmsg_data(config), "read"); -} - -/* - * Test write access to given config. If the passed "sid" blob attribute pointer - * is NULL then the precedure was not invoked through the ubus-rpc so we do not - * perform access control and always assume true. - */ -static bool -rpc_uci_write_access(struct blob_attr *sid, struct blob_attr *config) -{ - rpc_uci_set_savedir(sid); - - if (!sid) - return true; - - return rpc_session_access(blobmsg_data(sid), "uci", - blobmsg_data(config), "write"); -} - -/* - * Format applicable blob value as string and place a pointer to the string - * buffer in "p". Uses a static string buffer. - */ -static bool -rpc_uci_format_blob(struct blob_attr *v, const char **p) -{ - static char buf[21]; - - *p = NULL; - - switch (blobmsg_type(v)) - { - case BLOBMSG_TYPE_STRING: - *p = blobmsg_data(v); - break; - - case BLOBMSG_TYPE_INT64: - snprintf(buf, sizeof(buf), "%"PRIu64, blobmsg_get_u64(v)); - *p = buf; - break; - - case BLOBMSG_TYPE_INT32: - snprintf(buf, sizeof(buf), "%u", blobmsg_get_u32(v)); - *p = buf; - break; - - case BLOBMSG_TYPE_INT16: - snprintf(buf, sizeof(buf), "%u", blobmsg_get_u16(v)); - *p = buf; - break; - - case BLOBMSG_TYPE_INT8: - snprintf(buf, sizeof(buf), "%u", !!blobmsg_get_u8(v)); - *p = buf; - break; - - default: - break; - } - - return !!*p; -} - -/* - * Lookup the given uci_ptr and enable extended lookup format if the .section - * value of the uci_ptr looks like extended syntax. Uses an internal copy - * of the given uci_ptr to perform the lookup as failing extended section - * lookup operations in libuci will zero our the uci_ptr struct. - * Copies the internal uci_ptr back to given the uci_ptr on success. - */ -static int -rpc_uci_lookup(struct uci_ptr *ptr) -{ - int rv; - struct uci_ptr lookup = *ptr; - - if (!lookup.s && lookup.section && *lookup.section == '@') - lookup.flags |= UCI_LOOKUP_EXTENDED; - - rv = uci_lookup_ptr(cursor, &lookup, NULL, true); - - if (!rv) - *ptr = lookup; - - return rv; -} - -/* - * Checks whether the given uci_option object matches the given string value. - * 1) If the uci_option is of type list, check whether any of the list elements - * equals to the given string - * 2) If the uci_option is of type string, parse it into space separated tokens - * and check if any of the tokens equals to the given string. - * Returns true if a list element or token matched the given string. - */ -static bool -rpc_uci_match_option(struct uci_option *o, const char *cmp) -{ - struct uci_element *e; - char *s, *p; - - if (o->type == UCI_TYPE_LIST) - { - uci_foreach_element(&o->v.list, e) - if (e->name && !strcmp(e->name, cmp)) - return true; - - return false; - } - - if (!o->v.string) - return false; - - s = strdup(o->v.string); - - if (!s) - return false; - - for (p = strtok(s, " \t"); p; p = strtok(NULL, " \t")) - { - if (!strcmp(p, cmp)) - { - free(s); - return true; - } - } - - free(s); - return false; -} - -/* - * Checks whether the given uci_section matches the type and value blob attrs. - * 1) Returns false if "type" is given and the section type does not match - * the value specified in the "type" string blob attribute, else continue. - * 2) Tests whether any key in the "matches" table blob attribute exists in - * the given uci_section and whether each value is contained in the - * corresponding uci option value (see rpc_uci_match_option()). - * 3) A missing or empty "matches" table blob attribute is always considered - * to be a match. - * Returns true if "type" matches or is NULL and "matches" matches or is NULL. - */ -static bool -rpc_uci_match_section(struct uci_section *s, - struct blob_attr *type, struct blob_attr *matches) -{ - struct uci_element *e; - struct blob_attr *cur; - const char *cmp; - bool match = false; - bool empty = true; - int rem; - - if (type && strcmp(s->type, blobmsg_data(type))) - return false; - - if (!matches) - return true; - - blobmsg_for_each_attr(cur, matches, rem) - { - if (!rpc_uci_format_blob(cur, &cmp)) - continue; - - uci_foreach_element(&s->options, e) - { - if (strcmp(e->name, blobmsg_name(cur))) - continue; - - if (!rpc_uci_match_option(uci_to_option(e), cmp)) - return false; - - match = true; - } - - empty = false; - } - - return (empty || match); -} - -/* - * Dump the given uci_option value into the global blobmsg buffer and use - * given "name" as key. - * 1) If the uci_option is of type list, put a table into the blob buffer and - * add each list item as string to it. - * 2) If the uci_option is of type string, put its value directly into the blob - * buffer. - */ -static void -rpc_uci_dump_option(struct uci_option *o, const char *name) -{ - void *c; - struct uci_element *e; - - switch (o->type) - { - case UCI_TYPE_STRING: - blobmsg_add_string(&buf, name, o->v.string); - break; - - case UCI_TYPE_LIST: - c = blobmsg_open_array(&buf, name); - - uci_foreach_element(&o->v.list, e) - blobmsg_add_string(&buf, NULL, e->name); - - blobmsg_close_array(&buf, c); - break; - - default: - break; - } -} - -/* - * Dump the given uci_section object into the global blobmsg buffer and use - * given "name" as key. - * Puts a table into the blob buffer and puts each section option member value - * as value into the table using the option name as key. - * Adds three special keys ".anonymous", ".type" and ".name" which specify the - * corresponding section properties. - */ -static void -rpc_uci_dump_section(struct uci_section *s, const char *name, int index) -{ - void *c; - struct uci_option *o; - struct uci_element *e; - - c = blobmsg_open_table(&buf, name); - - blobmsg_add_u8(&buf, ".anonymous", s->anonymous); - blobmsg_add_string(&buf, ".type", s->type); - blobmsg_add_string(&buf, ".name", s->e.name); - - if (index >= 0) - blobmsg_add_u32(&buf, ".index", index); - - uci_foreach_element(&s->options, e) - { - o = uci_to_option(e); - rpc_uci_dump_option(o, o->e.name); - } - - blobmsg_close_table(&buf, c); -} - -/* - * Dump the given uci_package object into the global blobmsg buffer and use - * given "name" as key. - * Puts a table into the blob buffer and puts each package section member as - * value into the table using the section name as key. - * Only dumps sections matching the given "type" and "matches", see explaination - * of rpc_uci_match_section() for details. - */ -static void -rpc_uci_dump_package(struct uci_package *p, const char *name, - struct blob_attr *type, struct blob_attr *matches) -{ - void *c; - struct uci_element *e; - int i = -1; - - c = blobmsg_open_table(&buf, name); - - uci_foreach_element(&p->sections, e) - { - i++; - - if (!rpc_uci_match_section(uci_to_section(e), type, matches)) - continue; - - rpc_uci_dump_section(uci_to_section(e), e->name, i); - } - - blobmsg_close_table(&buf, c); -} - - -static int -rpc_uci_getcommon(struct ubus_context *ctx, struct ubus_request_data *req, - struct blob_attr *msg, bool use_state) -{ - struct blob_attr *tb[__RPC_G_MAX]; - struct uci_package *p = NULL; - struct uci_ptr ptr = { 0 }; - - blobmsg_parse(rpc_uci_get_policy, __RPC_G_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_G_CONFIG]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_read_access(tb[RPC_G_SESSION], tb[RPC_G_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - ptr.package = blobmsg_data(tb[RPC_G_CONFIG]); - - if (use_state) - uci_set_savedir(cursor, "/var/state"); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - if (tb[RPC_G_SECTION]) - { - ptr.section = blobmsg_data(tb[RPC_G_SECTION]); - - if (tb[RPC_G_OPTION]) - ptr.option = blobmsg_data(tb[RPC_G_OPTION]); - } - - if (rpc_uci_lookup(&ptr) || !(ptr.flags & UCI_LOOKUP_COMPLETE)) - goto out; - - blob_buf_init(&buf, 0); - - switch (ptr.last->type) - { - case UCI_TYPE_PACKAGE: - rpc_uci_dump_package(ptr.p, "values", tb[RPC_G_TYPE], tb[RPC_G_MATCH]); - break; - - case UCI_TYPE_SECTION: - rpc_uci_dump_section(ptr.s, "values", -1); - break; - - case UCI_TYPE_OPTION: - rpc_uci_dump_option(ptr.o, "value"); - break; - - default: - break; - } - - ubus_send_reply(ctx, req, buf.head); - -out: - uci_unload(cursor, p); - - return rpc_uci_status(); -} - -static int -rpc_uci_get(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - return rpc_uci_getcommon(ctx, req, msg, false); -} - -static int -rpc_uci_state(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - return rpc_uci_getcommon(ctx, req, msg, true); -} - -static int -rpc_uci_add(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_A_MAX]; - struct blob_attr *cur, *elem; - struct uci_package *p = NULL; - struct uci_section *s; - struct uci_ptr ptr = { 0 }; - int rem, rem2, err = 0; - - blobmsg_parse(rpc_uci_add_policy, __RPC_A_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_A_CONFIG] || !tb[RPC_A_TYPE]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_A_SESSION], tb[RPC_A_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - if (!rpc_uci_verify_type(blobmsg_data(tb[RPC_A_TYPE]))) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (tb[RPC_A_NAME] && - !rpc_uci_verify_name(blobmsg_data(tb[RPC_A_NAME]))) - return UBUS_STATUS_INVALID_ARGUMENT; - - ptr.package = blobmsg_data(tb[RPC_A_CONFIG]); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - /* add named section */ - if (tb[RPC_A_NAME]) - { - ptr.section = blobmsg_data(tb[RPC_A_NAME]); - ptr.value = blobmsg_data(tb[RPC_A_TYPE]); - ptr.option = NULL; - - if (rpc_uci_lookup(&ptr) || uci_set(cursor, &ptr)) - goto out; - } - - /* add anon section */ - else - { - if (uci_add_section(cursor, p, blobmsg_data(tb[RPC_A_TYPE]), &s) || !s) - goto out; - - ptr.section = s->e.name; - } - - if (tb[RPC_A_VALUES]) - { - blobmsg_for_each_attr(cur, tb[RPC_A_VALUES], rem) - { - ptr.flags = 0; - ptr.o = NULL; - ptr.option = blobmsg_name(cur); - - if (!rpc_uci_verify_name(ptr.option)) - { - if (!err) - err = UBUS_STATUS_INVALID_ARGUMENT; - - continue; - } - - if (rpc_uci_lookup(&ptr) || !ptr.s) - { - if (!err) - err = UBUS_STATUS_NOT_FOUND; - - continue; - } - - switch (blobmsg_type(cur)) - { - case BLOBMSG_TYPE_ARRAY: - blobmsg_for_each_attr(elem, cur, rem2) - { - if (!rpc_uci_format_blob(elem, &ptr.value)) - { - if (!err) - err = UBUS_STATUS_INVALID_ARGUMENT; - - continue; - } - - uci_add_list(cursor, &ptr); - } - - break; - - default: - if (!rpc_uci_format_blob(cur, &ptr.value)) - { - if (!err) - err = UBUS_STATUS_INVALID_ARGUMENT; - } - else - { - uci_set(cursor, &ptr); - } - - break; - } - } - } - - if (!err) - { - uci_save(cursor, p); - - blob_buf_init(&buf, 0); - blobmsg_add_string(&buf, "section", ptr.section); - ubus_send_reply(ctx, req, buf.head); - } - -out: - uci_unload(cursor, p); - - return err ? err : rpc_uci_status(); -} - -/* - * Turn value from a blob attribute into uci set operation - * 1) if the blob is of type array, delete existing option (if any) and - * emit uci add_list operations for each element - * 2) if the blob is not an array but an option of type list exists, - * delete existing list and emit uci set operation for the blob value - * 3) in all other cases only emit a set operation if there is no existing - * option of if the existing options value differs from the blob value - */ -static int -rpc_uci_merge_set(struct blob_attr *opt, struct uci_ptr *ptr) -{ - struct blob_attr *cur; - int rem, rv; - - ptr->flags = 0; - ptr->o = NULL; - ptr->option = blobmsg_name(opt); - ptr->value = NULL; - - if (!rpc_uci_verify_name(ptr->option)) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (rpc_uci_lookup(ptr) || !ptr->s) - return UBUS_STATUS_NOT_FOUND; - - if (blobmsg_type(opt) == BLOBMSG_TYPE_ARRAY) - { - if (ptr->o) { - uci_delete(cursor, ptr); - ptr->flags = 0; - } - - rv = UBUS_STATUS_INVALID_ARGUMENT; - - blobmsg_for_each_attr(cur, opt, rem) - { - if (!rpc_uci_format_blob(cur, &ptr->value)) - continue; - - uci_add_list(cursor, ptr); - rv = 0; - } - - return rv; - } - else if (ptr->o && ptr->o->type == UCI_TYPE_LIST) - { - uci_delete(cursor, ptr); - ptr->flags = 0; - - if (!rpc_uci_format_blob(opt, &ptr->value)) - return UBUS_STATUS_INVALID_ARGUMENT; - - uci_set(cursor, ptr); - } - else - { - if (!rpc_uci_format_blob(opt, &ptr->value)) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!ptr->o || !ptr->o->v.string || strcmp(ptr->o->v.string, ptr->value)) - uci_set(cursor, ptr); - } - - return 0; -} - -static int -rpc_uci_set(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_S_MAX]; - struct blob_attr *cur; - struct uci_package *p = NULL; - struct uci_element *e; - struct uci_ptr ptr = { 0 }; - int rem, rv, err = 0; - - blobmsg_parse(rpc_uci_set_policy, __RPC_S_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_S_CONFIG] || !tb[RPC_S_VALUES] || - (!tb[RPC_S_SECTION] && !tb[RPC_S_TYPE] && !tb[RPC_S_MATCH])) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_S_SESSION], tb[RPC_S_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - if (tb[RPC_S_SECTION] && - !rpc_uci_verify_section(blobmsg_data(tb[RPC_S_SECTION]))) - return UBUS_STATUS_INVALID_ARGUMENT; - - ptr.package = blobmsg_data(tb[RPC_S_CONFIG]); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - if (tb[RPC_S_SECTION]) - { - ptr.section = blobmsg_data(tb[RPC_S_SECTION]); - blobmsg_for_each_attr(cur, tb[RPC_S_VALUES], rem) - { - rv = rpc_uci_merge_set(cur, &ptr); - - if (rv) - err = rv; - } - } - else - { - uci_foreach_element(&p->sections, e) - { - if (!rpc_uci_match_section(uci_to_section(e), - tb[RPC_S_TYPE], tb[RPC_S_MATCH])) - continue; - - ptr.s = NULL; - ptr.section = e->name; - - blobmsg_for_each_attr(cur, tb[RPC_S_VALUES], rem) - { - rv = rpc_uci_merge_set(cur, &ptr); - - if (rv) - err = rv; - } - } - } - - if (!err && !ptr.s) - err = UBUS_STATUS_NOT_FOUND; - - if (!err) - uci_save(cursor, p); - - uci_unload(cursor, p); - - return err ? err : rpc_uci_status(); -} - -/* - * Delete option or section from uci specified by given blob attribute pointer - * 1) if the blob is of type array, delete any option named after each element - * 2) if the blob is of type string, delete the option named after its value - * 3) if the blob is NULL, delete entire section - */ -static int -rpc_uci_merge_delete(struct blob_attr *opt, struct uci_ptr *ptr) -{ - struct blob_attr *cur; - int rem, rv; - - if (rpc_uci_lookup(ptr) || !ptr->s) - return UBUS_STATUS_NOT_FOUND; - - if (!opt) - { - ptr->o = NULL; - ptr->option = NULL; - - uci_delete(cursor, ptr); - return 0; - } - else if (blobmsg_type(opt) == BLOBMSG_TYPE_ARRAY) - { - rv = UBUS_STATUS_NOT_FOUND; - - blobmsg_for_each_attr(cur, opt, rem) - { - if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) - continue; - - ptr->o = NULL; - ptr->option = blobmsg_data(cur); - - if (rpc_uci_lookup(ptr) || !ptr->o) - continue; - - uci_delete(cursor, ptr); - ptr->flags = 0; - rv = 0; - } - - return rv; - } - else if (blobmsg_type(opt) == BLOBMSG_TYPE_STRING) - { - ptr->o = NULL; - ptr->option = blobmsg_data(opt); - - if (rpc_uci_lookup(ptr) || !ptr->o) - return UBUS_STATUS_NOT_FOUND; - - uci_delete(cursor, ptr); - return 0; - } - - return UBUS_STATUS_INVALID_ARGUMENT; -} - -static int -rpc_uci_delete(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_D_MAX]; - struct uci_package *p = NULL; - struct uci_element *e, *tmp; - struct uci_ptr ptr = { 0 }; - int err = 0; - - blobmsg_parse(rpc_uci_delete_policy, __RPC_D_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_D_CONFIG] || - (!tb[RPC_D_SECTION] && !tb[RPC_D_TYPE] && !tb[RPC_D_MATCH])) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_D_SESSION], tb[RPC_D_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - if (tb[RPC_D_TYPE] && - !rpc_uci_verify_type(blobmsg_data(tb[RPC_D_TYPE]))) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (tb[RPC_D_SECTION] && - !rpc_uci_verify_section(blobmsg_data(tb[RPC_D_SECTION]))) - return UBUS_STATUS_INVALID_ARGUMENT; - - ptr.package = blobmsg_data(tb[RPC_D_CONFIG]); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - if (tb[RPC_D_SECTION]) - { - ptr.section = blobmsg_data(tb[RPC_D_SECTION]); - - if (tb[RPC_D_OPTIONS]) - err = rpc_uci_merge_delete(tb[RPC_D_OPTIONS], &ptr); - else - err = rpc_uci_merge_delete(tb[RPC_D_OPTION], &ptr); - } - else - { - uci_foreach_element_safe(&p->sections, tmp, e) - { - if (!rpc_uci_match_section(uci_to_section(e), - tb[RPC_D_TYPE], tb[RPC_D_MATCH])) - continue; - - ptr.s = NULL; - ptr.section = e->name; - - if (tb[RPC_D_OPTIONS]) - err = rpc_uci_merge_delete(tb[RPC_D_OPTIONS], &ptr); - else - err = rpc_uci_merge_delete(tb[RPC_D_OPTION], &ptr); - } - - if (!err && !ptr.section) - err = UBUS_STATUS_NOT_FOUND; - } - - if (!err) - uci_save(cursor, p); - - uci_unload(cursor, p); - - return err ? err : rpc_uci_status(); -} - -static int -rpc_uci_rename(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_R_MAX]; - struct uci_package *p = NULL; - struct uci_ptr ptr = { 0 }; - - blobmsg_parse(rpc_uci_rename_policy, __RPC_R_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_R_CONFIG] || !tb[RPC_R_SECTION] || !tb[RPC_R_NAME]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_R_SESSION], tb[RPC_R_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - ptr.package = blobmsg_data(tb[RPC_R_CONFIG]); - ptr.section = blobmsg_data(tb[RPC_R_SECTION]); - ptr.value = blobmsg_data(tb[RPC_R_NAME]); - - if (!rpc_uci_verify_name(ptr.value)) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (tb[RPC_R_OPTION]) - ptr.option = blobmsg_data(tb[RPC_R_OPTION]); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - if (uci_lookup_ptr(cursor, &ptr, NULL, true)) - goto out; - - if ((ptr.option && !ptr.o) || !ptr.s) - { - cursor->err = UCI_ERR_NOTFOUND; - goto out; - } - - if (uci_rename(cursor, &ptr)) - goto out; - - uci_save(cursor, p); - -out: - uci_unload(cursor, p); - - return rpc_uci_status(); -} - -static int -rpc_uci_order(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_O_MAX]; - struct blob_attr *cur; - struct uci_package *p = NULL; - struct uci_ptr ptr = { 0 }; - int rem, i = 0, err = 0; - - blobmsg_parse(rpc_uci_order_policy, __RPC_O_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_O_CONFIG] || !tb[RPC_O_SECTIONS]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_O_SESSION], tb[RPC_O_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - ptr.package = blobmsg_data(tb[RPC_O_CONFIG]); - - if (uci_load(cursor, ptr.package, &p)) - return rpc_uci_status(); - - blobmsg_for_each_attr(cur, tb[RPC_O_SECTIONS], rem) - { - if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) - { - if (!err) - err = UBUS_STATUS_INVALID_ARGUMENT; - - continue; - } - - ptr.s = NULL; - ptr.section = blobmsg_data(cur); - - if (uci_lookup_ptr(cursor, &ptr, NULL, true) || !ptr.s) - { - if (!err) - err = UBUS_STATUS_NOT_FOUND; - - continue; - } - - uci_reorder_section(cursor, ptr.s, i++); - } - - if (!err) - uci_save(cursor, p); - - uci_unload(cursor, p); - - return err ? err : rpc_uci_status(); -} - -static void -rpc_uci_dump_change(struct uci_delta *d) -{ - void *c; - const char *types[] = { - [UCI_CMD_REORDER] = "order", - [UCI_CMD_REMOVE] = "remove", - [UCI_CMD_RENAME] = "rename", - [UCI_CMD_ADD] = "add", - [UCI_CMD_LIST_ADD] = "list-add", - [UCI_CMD_LIST_DEL] = "list-del", - [UCI_CMD_CHANGE] = "set", - }; - - if (!d->section) - return; - - c = blobmsg_open_array(&buf, NULL); - - blobmsg_add_string(&buf, NULL, types[d->cmd]); - blobmsg_add_string(&buf, NULL, d->section); - - if (d->e.name) - blobmsg_add_string(&buf, NULL, d->e.name); - - if (d->value) - { - if (d->cmd == UCI_CMD_REORDER) - blobmsg_add_u32(&buf, NULL, strtoul(d->value, NULL, 10)); - else - blobmsg_add_string(&buf, NULL, d->value); - } - - blobmsg_close_array(&buf, c); -} - -static int -rpc_uci_changes(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_C_MAX]; - struct uci_package *p = NULL; - struct uci_element *e; - char **configs; - void *c, *d; - int i; - - blobmsg_parse(rpc_uci_config_policy, __RPC_C_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (tb[RPC_C_CONFIG]) - { - if (!rpc_uci_read_access(tb[RPC_C_SESSION], tb[RPC_C_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - if (uci_load(cursor, blobmsg_data(tb[RPC_C_CONFIG]), &p)) - return rpc_uci_status(); - - blob_buf_init(&buf, 0); - c = blobmsg_open_array(&buf, "changes"); - - uci_foreach_element(&p->saved_delta, e) - rpc_uci_dump_change(uci_to_delta(e)); - - blobmsg_close_array(&buf, c); - - uci_unload(cursor, p); - - ubus_send_reply(ctx, req, buf.head); - - return rpc_uci_status(); - } - - rpc_uci_set_savedir(tb[RPC_C_SESSION]); - - if (uci_list_configs(cursor, &configs)) - return rpc_uci_status(); - - blob_buf_init(&buf, 0); - - c = blobmsg_open_table(&buf, "changes"); - - for (i = 0; configs[i]; i++) - { - if (tb[RPC_C_SESSION] && - !rpc_session_access(blobmsg_data(tb[RPC_C_SESSION]), "uci", - configs[i], "read")) - continue; - - if (uci_load(cursor, configs[i], &p)) - continue; - - if (!uci_list_empty(&p->saved_delta)) - { - d = blobmsg_open_array(&buf, configs[i]); - - uci_foreach_element(&p->saved_delta, e) - rpc_uci_dump_change(uci_to_delta(e)); - - blobmsg_close_array(&buf, d); - } - - uci_unload(cursor, p); - } - - free(configs); - - blobmsg_close_table(&buf, c); - - ubus_send_reply(ctx, req, buf.head); - - return 0; -} - -static void -rpc_uci_trigger_event(struct ubus_context *ctx, const char *config) -{ - char *pkg = strdup(config); - static struct blob_buf b; - uint32_t id; - - if (!ubus_lookup_id(ctx, "service", &id)) { - void *c; - - blob_buf_init(&b, 0); - blobmsg_add_string(&b, "type", "config.change"); - c = blobmsg_open_table(&b, "data"); - blobmsg_add_string(&b, "package", pkg); - blobmsg_close_table(&b, c); - ubus_invoke(ctx, id, "event", b.head, NULL, 0, 1000); - } - free(pkg); -} - -static int -rpc_uci_revert_commit(struct ubus_context *ctx, struct blob_attr *msg, bool commit) -{ - struct blob_attr *tb[__RPC_C_MAX]; - struct uci_package *p = NULL; - struct uci_ptr ptr = { 0 }; - - if (apply_sid[0]) - return UBUS_STATUS_PERMISSION_DENIED; - - blobmsg_parse(rpc_uci_config_policy, __RPC_C_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_C_CONFIG]) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!rpc_uci_write_access(tb[RPC_C_SESSION], tb[RPC_C_CONFIG])) - return UBUS_STATUS_PERMISSION_DENIED; - - ptr.package = blobmsg_data(tb[RPC_C_CONFIG]); - - if (commit) - { - if (!uci_load(cursor, ptr.package, &p)) - { - uci_commit(cursor, &p, false); - uci_unload(cursor, p); - rpc_uci_trigger_event(ctx, blobmsg_get_string(tb[RPC_C_CONFIG])); - } - } - else - { - if (!uci_lookup_ptr(cursor, &ptr, NULL, true) && ptr.p) - { - uci_revert(cursor, &ptr); - uci_unload(cursor, ptr.p); - } - } - - return rpc_uci_status(); -} - -static int -rpc_uci_revert(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - return rpc_uci_revert_commit(ctx, msg, false); -} - -static int -rpc_uci_commit(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - return rpc_uci_revert_commit(ctx, msg, true); -} - -static int -rpc_uci_configs(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - char **configs; - void *c; - int i; - - if (uci_list_configs(cursor, &configs)) - goto out; - - blob_buf_init(&buf, 0); - - c = blobmsg_open_array(&buf, "configs"); - - for (i = 0; configs[i]; i++) - blobmsg_add_string(&buf, NULL, configs[i]); - - free(configs); - - blobmsg_close_array(&buf, c); - - ubus_send_reply(ctx, req, buf.head); - -out: - return rpc_uci_status(); -} - - -/* - * Remove given delta save directory (if any). - */ -static void -rpc_uci_purge_dir(const char *path) -{ - DIR *d; - struct stat s; - struct dirent *e; - char file[PATH_MAX]; - - if (stat(path, &s) || !S_ISDIR(s.st_mode)) - return; - - if ((d = opendir(path)) != NULL) - { - while ((e = readdir(d)) != NULL) - { - snprintf(file, sizeof(file) - 1, "%s/%s", path, e->d_name); - - if (stat(file, &s) || !S_ISREG(s.st_mode)) - continue; - - unlink(file); - } - - closedir(d); - - rmdir(path); - } -} - -static int -rpc_uci_apply_config(struct ubus_context *ctx, char *config) -{ - struct uci_package *p = NULL; - - if (!uci_load(cursor, config, &p)) { - uci_commit(cursor, &p, false); - uci_unload(cursor, p); - } - rpc_uci_trigger_event(ctx, config); - - return 0; -} - -static void -rpc_uci_copy_file(const char *src, const char *target, const char *file) -{ - char tmp[256]; - FILE *in, *out; - - snprintf(tmp, sizeof(tmp), "%s%s", src, file); - in = fopen(tmp, "rb"); - snprintf(tmp, sizeof(tmp), "%s%s", target, file); - out = fopen(tmp, "wb+"); - if (in && out) - while (!feof(in)) { - int len = fread(tmp, 1, sizeof(tmp), in); - - if(len > 0) - fwrite(tmp, 1, len, out); - } - if(in) - fclose(in); - if(out) - fclose(out); -} - -static int -rpc_uci_apply_access(const char *sid, glob_t *gl) -{ - struct stat s; - int i, c = 0; - - if (gl->gl_pathc < 3) - return UBUS_STATUS_NO_DATA; - - for (i = 0; i < gl->gl_pathc; i++) { - char *config = basename(gl->gl_pathv[i]); - - if (*config == '.') - continue; - if (stat(gl->gl_pathv[i], &s) || !s.st_size) - continue; - if (!rpc_session_access(sid, "uci", config, "write")) - return UBUS_STATUS_PERMISSION_DENIED; - c++; - } - - if (!c) - return UBUS_STATUS_NO_DATA; - - return 0; -} - -static void -rpc_uci_do_rollback(struct ubus_context *ctx, glob_t *gl) -{ - int i, deny; - char tmp[PATH_MAX]; - - /* Test apply permission to see if the initiator session still exists. - * If it does, restore the delta files as well, else just restore the - * main configuration files. */ - deny = apply_sid[0] - ? rpc_uci_apply_access(apply_sid, gl) : UBUS_STATUS_NOT_FOUND; - - if (!deny) { - snprintf(tmp, sizeof(tmp), RPC_UCI_SAVEDIR_PREFIX "%s/", apply_sid); - mkdir(tmp, 0700); - } - - /* avoid merging unrelated uci changes when restoring old configs */ - rpc_uci_replace_savedir("/dev/null"); - - for (i = 0; i < gl->gl_pathc; i++) { - char *config = basename(gl->gl_pathv[i]); - - if (*config == '.') - continue; - - rpc_uci_copy_file(RPC_SNAPSHOT_FILES, RPC_UCI_DIR, config); - rpc_uci_apply_config(ctx, config); - - if (deny) - continue; - - rpc_uci_copy_file(RPC_SNAPSHOT_DELTA, tmp, config); - } - - rpc_uci_purge_dir(RPC_SNAPSHOT_FILES); - rpc_uci_purge_dir(RPC_SNAPSHOT_DELTA); - - uloop_timeout_cancel(&apply_timer); - memset(apply_sid, 0, sizeof(apply_sid)); - apply_ctx = NULL; -} - -static void -rpc_uci_apply_timeout(struct uloop_timeout *t) -{ - glob_t gl; - char tmp[PATH_MAX]; - - snprintf(tmp, sizeof(tmp), "%s/*", RPC_SNAPSHOT_FILES); - if (glob(tmp, GLOB_PERIOD, NULL, &gl) < 0) - return; - - rpc_uci_do_rollback(apply_ctx, &gl); - - globfree(&gl); -} - -static int -rpc_uci_apply(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_T_MAX]; - int timeout = RPC_APPLY_TIMEOUT; - char tmp[PATH_MAX]; - bool rollback = false; - int ret, i; - char *sid; - glob_t gl; - - blobmsg_parse(rpc_uci_apply_policy, __RPC_T_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (tb[RPC_T_ROLLBACK]) - rollback = blobmsg_get_bool(tb[RPC_T_ROLLBACK]); - - if (apply_sid[0] && rollback) - return UBUS_STATUS_PERMISSION_DENIED; - - if (!tb[RPC_T_SESSION]) - return UBUS_STATUS_INVALID_ARGUMENT; - - sid = blobmsg_data(tb[RPC_T_SESSION]); - - if (tb[RPC_T_TIMEOUT]) - timeout = blobmsg_get_u32(tb[RPC_T_TIMEOUT]); - - rpc_uci_purge_dir(RPC_SNAPSHOT_FILES); - rpc_uci_purge_dir(RPC_SNAPSHOT_DELTA); - - if (!apply_sid[0]) { - rpc_uci_set_savedir(tb[RPC_T_SESSION]); - - mkdir(RPC_SNAPSHOT_FILES, 0700); - mkdir(RPC_SNAPSHOT_DELTA, 0700); - - snprintf(tmp, sizeof(tmp), RPC_UCI_SAVEDIR_PREFIX "%s/*", sid); - if (glob(tmp, GLOB_PERIOD, NULL, &gl) < 0) - return UBUS_STATUS_NOT_FOUND; - - snprintf(tmp, sizeof(tmp), RPC_UCI_SAVEDIR_PREFIX "%s/", sid); - - ret = rpc_uci_apply_access(sid, &gl); - if (ret) { - globfree(&gl); - return ret; - } - - /* copy SID early because rpc_uci_apply_config() will clobber buf */ - if (rollback) - strncpy(apply_sid, sid, RPC_SID_LEN); - - for (i = 0; i < gl.gl_pathc; i++) { - char *config = basename(gl.gl_pathv[i]); - struct stat s; - - if (*config == '.') - continue; - - if (stat(gl.gl_pathv[i], &s) || !s.st_size) - continue; - - rpc_uci_copy_file(RPC_UCI_DIR, RPC_SNAPSHOT_FILES, config); - rpc_uci_copy_file(tmp, RPC_SNAPSHOT_DELTA, config); - rpc_uci_apply_config(ctx, config); - } - - globfree(&gl); - - if (rollback) { - apply_timer.cb = rpc_uci_apply_timeout; - uloop_timeout_set(&apply_timer, timeout * 1000); - apply_ctx = ctx; - } - } - - return 0; -} - -static int -rpc_uci_confirm(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_B_MAX]; - char *sid; - - blobmsg_parse(rpc_uci_rollback_policy, __RPC_B_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!tb[RPC_B_SESSION]) - return UBUS_STATUS_INVALID_ARGUMENT; - - sid = blobmsg_data(tb[RPC_B_SESSION]); - - if (!apply_sid[0]) - return UBUS_STATUS_NO_DATA; - - if (strcmp(apply_sid, sid)) - return UBUS_STATUS_PERMISSION_DENIED; - - rpc_uci_purge_dir(RPC_SNAPSHOT_FILES); - rpc_uci_purge_dir(RPC_SNAPSHOT_DELTA); - - uloop_timeout_cancel(&apply_timer); - memset(apply_sid, 0, sizeof(apply_sid)); - apply_ctx = NULL; - - return 0; -} - -static int -rpc_uci_rollback(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - struct blob_attr *tb[__RPC_B_MAX]; - char tmp[PATH_MAX]; - glob_t gl; - char *sid; - - blobmsg_parse(rpc_uci_rollback_policy, __RPC_B_MAX, tb, - blob_data(msg), blob_len(msg)); - - if (!apply_sid[0]) - return UBUS_STATUS_NO_DATA; - - if (!tb[RPC_B_SESSION]) - return UBUS_STATUS_INVALID_ARGUMENT; - - sid = blobmsg_data(tb[RPC_B_SESSION]); - - if (strcmp(apply_sid, sid)) - return UBUS_STATUS_PERMISSION_DENIED; - - snprintf(tmp, sizeof(tmp), "%s/*", RPC_SNAPSHOT_FILES); - if (glob(tmp, GLOB_PERIOD, NULL, &gl) < 0) - return UBUS_STATUS_NOT_FOUND; - - rpc_uci_do_rollback(ctx, &gl); - - globfree(&gl); - - return 0; -} - -static int -rpc_uci_reload(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *method, - struct blob_attr *msg) -{ - char * const cmd[2] = { "/sbin/reload_config", NULL }; - - if (!fork()) { - /* wait for the RPC call to complete */ - sleep(2); - return execv(cmd[0], cmd); - } - - return 0; -} - -/* - * Session destroy callback to purge associated delta directory. - */ -static void -rpc_uci_purge_savedir_cb(struct rpc_session *ses, void *priv) -{ - char path[PATH_MAX]; - - snprintf(path, sizeof(path) - 1, RPC_UCI_SAVEDIR_PREFIX "%s", ses->id); - rpc_uci_purge_dir(path); -} - -/* - * Removes all delta directories which match the RPC_UCI_SAVEDIR_PREFIX. - * This is used to clean up garbage when starting rpcd. - */ -void rpc_uci_purge_savedirs(void) -{ - int i; - glob_t gl; - - if (!glob(RPC_UCI_SAVEDIR_PREFIX "*", 0, NULL, &gl)) - { - for (i = 0; i < gl.gl_pathc; i++) - rpc_uci_purge_dir(gl.gl_pathv[i]); - - globfree(&gl); - } -} - -int rpc_uci_api_init(struct ubus_context *ctx) -{ - static const struct ubus_method uci_methods[] = { - { .name = "configs", .handler = rpc_uci_configs }, - UBUS_METHOD("get", rpc_uci_get, rpc_uci_get_policy), - UBUS_METHOD("state", rpc_uci_state, rpc_uci_get_policy), - UBUS_METHOD("add", rpc_uci_add, rpc_uci_add_policy), - UBUS_METHOD("set", rpc_uci_set, rpc_uci_set_policy), - UBUS_METHOD("delete", rpc_uci_delete, rpc_uci_delete_policy), - UBUS_METHOD("rename", rpc_uci_rename, rpc_uci_rename_policy), - UBUS_METHOD("order", rpc_uci_order, rpc_uci_order_policy), - UBUS_METHOD("changes", rpc_uci_changes, rpc_uci_config_policy), - UBUS_METHOD("revert", rpc_uci_revert, rpc_uci_config_policy), - UBUS_METHOD("commit", rpc_uci_commit, rpc_uci_config_policy), - UBUS_METHOD("apply", rpc_uci_apply, rpc_uci_apply_policy), - UBUS_METHOD("confirm", rpc_uci_confirm, rpc_uci_rollback_policy), - UBUS_METHOD("rollback", rpc_uci_rollback, rpc_uci_rollback_policy), - UBUS_METHOD_NOARG("reload_config", rpc_uci_reload), - }; - - static struct ubus_object_type uci_type = - UBUS_OBJECT_TYPE("rpcd-plugin-uci", uci_methods); - - static struct ubus_object obj = { - .name = "uci", - .type = &uci_type, - .methods = uci_methods, - .n_methods = ARRAY_SIZE(uci_methods), - }; - - static struct rpc_session_cb cb = { - .cb = rpc_uci_purge_savedir_cb - }; - - cursor = uci_alloc_context(); - - if (!cursor) - return UBUS_STATUS_UNKNOWN_ERROR; - - rpc_session_destroy_cb(&cb); - - return ubus_add_object(ctx, &obj); -} diff --git a/ucode.c b/ucode.c deleted file mode 100644 index 5c84776..0000000 --- a/ucode.c +++ /dev/null @@ -1,1073 +0,0 @@ -/* - * rpcd - UBUS RPC server - ucode plugin - * - * Copyright (C) 2021 Jo-Philipp Wich - * - * Permission to use, copy, modify, and/or 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 -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include - -#define RPC_UCSCRIPT_DIRECTORY INSTALL_PREFIX "/share/rpcd/ucode" - -static struct blob_buf buf; -static int request_timeout; - -/* - * Track script instances and registered ubus objects in these lists. - * - * This is primarily done to make Valgrind happy and to mark the - * related memory as reachable. Since we don't have a teardown - * mechanism in rpcd plugins we can't orderly free the related - * ubus object and ucode VM memory anyway. - */ -static LIST_HEAD(scripts); -static LIST_HEAD(uuobjs); - -typedef struct { - struct list_head list; - uc_vm_t vm; - uc_resource_type_t *requesttype; - uc_value_t *pending_replies; - char *path; -} rpc_ucode_script_t; - -typedef struct { - struct list_head list; - rpc_ucode_script_t *script; - uc_value_t *signature; - struct ubus_object ubusobj; -} rpc_ucode_ubus_obj_t; - -typedef struct { - struct ubus_context *ubus; - struct ubus_request_data req; - struct uloop_timeout timeout; - rpc_ucode_script_t *script; - uc_value_t *func; - uc_value_t *args; - uc_value_t *info; - bool replied; -} rpc_ucode_call_ctx_t; - -static uc_parse_config_t config = { - .strict_declarations = false, - .lstrip_blocks = true, - .trim_blocks = true, - .raw_mode = true -}; - - -static rpc_ucode_script_t * -rpc_ucode_obj_to_script(struct ubus_object *obj) -{ - rpc_ucode_ubus_obj_t *uo = container_of(obj, rpc_ucode_ubus_obj_t, ubusobj); - - return uo->script; -} - -static uc_value_t * -rpc_ucode_obj_to_signature(struct ubus_object *obj) -{ - rpc_ucode_ubus_obj_t *uo = container_of(obj, rpc_ucode_ubus_obj_t, ubusobj); - - return uo->signature; -} - -static void -rpc_ucode_ucv_array_to_blob(uc_value_t *val, struct blob_buf *blob); - -static void -rpc_ucode_ucv_object_to_blob(uc_value_t *val, struct blob_buf *blob); - -static void -rpc_ucode_ucv_to_blob(const char *name, uc_value_t *val, struct blob_buf *blob) -{ - int64_t n; - void *c; - - switch (ucv_type(val)) { - case UC_NULL: - blobmsg_add_field(blob, BLOBMSG_TYPE_UNSPEC, name, NULL, 0); - break; - - case UC_BOOLEAN: - blobmsg_add_u8(blob, name, ucv_boolean_get(val)); - break; - - case UC_INTEGER: - n = ucv_int64_get(val); - - if (errno == ERANGE) - blobmsg_add_u64(blob, name, ucv_uint64_get(val)); - else if (n >= INT32_MIN && n <= INT32_MAX) - blobmsg_add_u32(blob, name, n); - else - blobmsg_add_u64(blob, name, n); - - break; - - case UC_DOUBLE: - blobmsg_add_double(blob, name, ucv_double_get(val)); - break; - - case UC_STRING: - blobmsg_add_string(blob, name, ucv_string_get(val)); - break; - - case UC_ARRAY: - c = blobmsg_open_array(blob, name); - rpc_ucode_ucv_array_to_blob(val, blob); - blobmsg_close_array(blob, c); - break; - - case UC_OBJECT: - c = blobmsg_open_table(blob, name); - rpc_ucode_ucv_object_to_blob(val, blob); - blobmsg_close_table(blob, c); - break; - - default: - break; - } -} - -static void -rpc_ucode_ucv_array_to_blob(uc_value_t *val, struct blob_buf *blob) -{ - size_t i; - - for (i = 0; i < ucv_array_length(val); i++) - rpc_ucode_ucv_to_blob(NULL, ucv_array_get(val, i), blob); -} - -static void -rpc_ucode_ucv_object_to_blob(uc_value_t *val, struct blob_buf *blob) -{ - ucv_object_foreach(val, k, v) - rpc_ucode_ucv_to_blob(k, v, blob); -} - -static uc_value_t * -rpc_ucode_blob_to_ucv(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name); - -static uc_value_t * -rpc_ucode_blob_array_to_ucv(uc_vm_t *vm, struct blob_attr *attr, size_t len, bool table) -{ - uc_value_t *o = table ? ucv_object_new(vm) : ucv_array_new(vm); - uc_value_t *v; - struct blob_attr *pos; - size_t rem = len; - const char *name; - - if (!o) - return NULL; - - __blob_for_each_attr(pos, attr, rem) { - name = NULL; - v = rpc_ucode_blob_to_ucv(vm, pos, table, &name); - - if (table && name) - ucv_object_add(o, name, v); - else if (!table) - ucv_array_push(o, v); - else - ucv_put(v); - } - - return o; -} - -static uc_value_t * -rpc_ucode_blob_to_ucv(uc_vm_t *vm, struct blob_attr *attr, bool table, const char **name) -{ - void *data; - int len; - - if (!blobmsg_check_attr(attr, false)) - return NULL; - - if (table && blobmsg_name(attr)[0]) - *name = blobmsg_name(attr); - - data = blobmsg_data(attr); - len = blobmsg_data_len(attr); - - switch (blob_id(attr)) { - case BLOBMSG_TYPE_BOOL: - return ucv_boolean_new(*(uint8_t *)data); - - case BLOBMSG_TYPE_INT16: - return ucv_int64_new((int16_t)be16_to_cpu(*(uint16_t *)data)); - - case BLOBMSG_TYPE_INT32: - return ucv_int64_new((int32_t)be32_to_cpu(*(uint32_t *)data)); - - case BLOBMSG_TYPE_INT64: - return ucv_int64_new((int64_t)be64_to_cpu(*(uint64_t *)data)); - - case BLOBMSG_TYPE_DOUBLE: - ; - union { - double d; - uint64_t u64; - } v; - - v.u64 = be64_to_cpu(*(uint64_t *)data); - - return ucv_double_new(v.d); - - case BLOBMSG_TYPE_STRING: - return ucv_string_new(data); - - case BLOBMSG_TYPE_ARRAY: - return rpc_ucode_blob_array_to_ucv(vm, data, len, false); - - case BLOBMSG_TYPE_TABLE: - return rpc_ucode_blob_array_to_ucv(vm, data, len, true); - - default: - return NULL; - } -} - -static int -rpc_ucode_validate_call_args(struct ubus_object *obj, const char *ubus_method_name, struct blob_attr *msg, uc_value_t **res) -{ - rpc_ucode_script_t *script = rpc_ucode_obj_to_script(obj); - const struct ubus_method *method = NULL; - const struct blobmsg_hdr *hdr; - struct blob_attr *attr; - bool found; - size_t i; - int len; - - for (i = 0; i < obj->n_methods; i++) { - if (!strcmp(obj->methods[i].name, ubus_method_name)) { - method = &obj->methods[i]; - break; - } - } - - if (!method) - return UBUS_STATUS_METHOD_NOT_FOUND; - - len = blob_len(msg); - - __blob_for_each_attr(attr, blob_data(msg), len) { - if (!blobmsg_check_attr_len(attr, false, len)) - return UBUS_STATUS_INVALID_ARGUMENT; - - if (!blob_is_extended(attr)) - return UBUS_STATUS_INVALID_ARGUMENT; - - hdr = blob_data(attr); - found = false; - - for (i = 0; i < method->n_policy; i++) { - if (blobmsg_namelen(hdr) != strlen(method->policy[i].name)) - continue; - - if (strcmp(method->policy[i].name, (char *)hdr->name)) - continue; - - /* named argument found but wrong type */ - if (blob_id(attr) != method->policy[i].type) - goto inval; - - found = true; - break; - } - - /* named argument not found in policy */ - if (!found) { - /* allow special ubus_rpc_session argument */ - if (!strcmp("ubus_rpc_session", (char *)hdr->name) && blob_id(attr) == BLOBMSG_TYPE_STRING) - continue; - - goto inval; - } - } - - *res = rpc_ucode_blob_array_to_ucv(&script->vm, blob_data(msg), blob_len(msg), true); - - return UBUS_STATUS_OK; - -inval: - *res = NULL; - - return UBUS_STATUS_INVALID_ARGUMENT; -} - -static uc_value_t * -rpc_ucode_gather_call_info(uc_vm_t *vm, - struct ubus_context *ctx, struct ubus_request_data *req, - struct ubus_object *obj, const char *ubus_method_name) -{ - uc_value_t *info, *o; - - info = ucv_object_new(vm); - - o = ucv_object_new(vm); - - ucv_object_add(o, "user", ucv_string_new(req->acl.user)); - ucv_object_add(o, "group", ucv_string_new(req->acl.group)); - ucv_object_add(o, "object", ucv_string_new(req->acl.object)); - - ucv_object_add(info, "acl", o); - - o = ucv_object_new(vm); - - ucv_object_add(o, "id", ucv_uint64_new(obj->id)); - ucv_object_add(o, "name", ucv_string_new(obj->name)); - - if (obj->path) - ucv_object_add(o, "path", ucv_string_new(obj->path)); - - ucv_object_add(info, "object", o); - - ucv_object_add(info, "method", ucv_string_new(ubus_method_name)); - - return info; -} - -static void -rpc_ucode_request_finish(rpc_ucode_call_ctx_t *callctx, int code, uc_value_t *reply) -{ - rpc_ucode_script_t *script = callctx->script; - uc_resource_t *r; - size_t i; - - if (callctx->replied) - return; - - if (reply) { - blob_buf_init(&buf, 0); - rpc_ucode_ucv_object_to_blob(reply, &buf); - ubus_send_reply(callctx->ubus, &callctx->req, buf.head); - } - - ubus_complete_deferred_request(callctx->ubus, &callctx->req, code); - - callctx->replied = true; - - for (i = 0; i < ucv_array_length(script->pending_replies); i++) { - r = (uc_resource_t *)ucv_array_get(script->pending_replies, i); - - if (r && r->data == callctx) { - ucv_array_set(script->pending_replies, i, NULL); - break; - } - } -} - -static void -rpc_ucode_request_timeout(struct uloop_timeout *timeout) -{ - rpc_ucode_call_ctx_t *callctx = container_of(timeout, rpc_ucode_call_ctx_t, timeout); - - rpc_ucode_request_finish(callctx, UBUS_STATUS_TIMEOUT, NULL); -} - -static int -rpc_ucode_script_call(struct ubus_context *ctx, struct ubus_object *obj, - struct ubus_request_data *req, const char *ubus_method_name, - struct blob_attr *msg) -{ - rpc_ucode_script_t *script = rpc_ucode_obj_to_script(obj); - uc_value_t *func, *args = NULL, *reqobj, *reqproto, *res; - rpc_ucode_call_ctx_t *callctx; - const char *extype; - size_t i; - int rv; - - rv = rpc_ucode_validate_call_args(obj, ubus_method_name, msg, &args); - - if (rv != UBUS_STATUS_OK) - return rv; - - func = ucv_object_get( - ucv_object_get(rpc_ucode_obj_to_signature(obj), ubus_method_name, NULL), - "call", NULL - ); - - if (!ucv_is_callable(func)) - return UBUS_STATUS_METHOD_NOT_FOUND; - - /* allocate deferred method call context */ - callctx = calloc(1, sizeof(*callctx)); - - if (!callctx) - return UBUS_STATUS_UNKNOWN_ERROR; - - callctx->ubus = ctx; - callctx->script = script; - - ubus_defer_request(ctx, req, &callctx->req); - - /* create ucode request type object and set properties */ - reqobj = uc_resource_new(script->requesttype, callctx); - reqproto = ucv_object_new(&script->vm); - - ucv_object_add(reqproto, "args", args); - ucv_object_add(reqproto, "info", - rpc_ucode_gather_call_info(&script->vm, ctx, req, obj, ubus_method_name)); - - ucv_prototype_set(ucv_prototype_get(reqobj), reqproto); - - /* push handler and request object onto stack */ - uc_vm_stack_push(&script->vm, ucv_get(func)); - uc_vm_stack_push(&script->vm, ucv_get(reqobj)); - - /* execute request handler function */ - switch (uc_vm_call(&script->vm, false, 1)) { - case EXCEPTION_NONE: - res = uc_vm_stack_pop(&script->vm); - - /* The handler function invoked a nested aync ubus request and returned it */ - if (ucv_resource_dataptr(res, "ubus.deferred")) { - /* Install guard timer in case the reply callback is never called */ - callctx->timeout.cb = rpc_ucode_request_timeout; - uloop_timeout_set(&callctx->timeout, request_timeout); - - /* Add wrapped request context into registry to prevent GC'ing - * until reply or timeout occurred */ - for (i = 0;; i++) { - if (ucv_array_get(script->pending_replies, i) == NULL) { - ucv_array_set(script->pending_replies, i, ucv_get(reqobj)); - break; - } - } - } - - /* Otherwise, when the function returned an object, treat it as - * reply data and conclude deferred request immediately */ - else if (ucv_type(res) == UC_OBJECT) { - blob_buf_init(&buf, 0); - rpc_ucode_ucv_object_to_blob(res, &buf); - ubus_send_reply(ctx, &callctx->req, buf.head); - - ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_OK); - callctx->replied = true; - } - - /* If neither a deferred ubus request, nor a plain object were - * returned and if reqobj.reply() hasn't been called, immediately - * finish deferred request with UBUS_STATUS_NO_DATA. The */ - else if (!callctx->replied) { - ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_NO_DATA); - callctx->replied = true; - } - - ucv_put(res); - break; - - /* if the handler function invoked exit(), forward exit status as ubus - * return code, map out of range values to UBUS_STATUS_UNKNOWN_ERROR. */ - case EXCEPTION_EXIT: - rv = script->vm.arg.s32; - - if (rv < UBUS_STATUS_OK || rv >= __UBUS_STATUS_LAST) - rv = UBUS_STATUS_UNKNOWN_ERROR; - - ubus_complete_deferred_request(ctx, &callctx->req, rv); - callctx->replied = true; - break; - - /* treat other exceptions as unknown error */ - default: - switch (script->vm.exception.type) { - case EXCEPTION_SYNTAX: extype = "Syntax error"; break; - case EXCEPTION_RUNTIME: extype = "Runtime error"; break; - case EXCEPTION_TYPE: extype = "Type error"; break; - case EXCEPTION_REFERENCE: extype = "Reference error"; break; - default: extype = "Exception"; - } - - res = ucv_object_get( - ucv_array_get(script->vm.exception.stacktrace, 0), - "context", NULL); - - fprintf(stderr, - "Unhandled ucode exception in '%s' method!\n%s: %s\n\n%s\n", - ubus_method_name, extype, script->vm.exception.message, - ucv_string_get(res)); - - ubus_complete_deferred_request(ctx, &callctx->req, UBUS_STATUS_UNKNOWN_ERROR); - callctx->replied = true; - break; - } - - /* release request object */ - ucv_put(reqobj); - - /* garbage collect */ - ucv_gc(&script->vm); - - return UBUS_STATUS_OK; -} - -static uc_program_t * -rpc_ucode_script_compile(const char *path, uc_source_t *src) -{ - char *syntax_error = NULL; - uc_program_t *prog; - - prog = uc_compile(&config, src, &syntax_error); - - if (!prog) - fprintf(stderr, "Unable to compile ucode script %s: %s\n", - path, syntax_error); - - uc_source_put(src); - free(syntax_error); - - return prog; -} - -static bool -rpc_ucode_script_validate(rpc_ucode_script_t *script) -{ - uc_value_t *signature = uc_vm_registry_get(&script->vm, "rpcd.ucode.signature"); - uc_value_t *args, *func; - - if (ucv_type(signature) != UC_OBJECT) { - fprintf(stderr, "Invalid object signature for ucode script %s" - " - expected dictionary, got %s\n", - script->path, ucv_typename(signature)); - - return false; - } - - ucv_object_foreach(signature, ubus_object_name, ubus_object_methods) { - if (ucv_type(ubus_object_methods) != UC_OBJECT) { - fprintf(stderr, "Invalid method signature for ucode script %s, object %s" - " - expected dictionary, got %s\n", - script->path, ubus_object_name, ucv_typename(ubus_object_methods)); - - return false; - } - - ucv_object_foreach(ubus_object_methods, ubus_method_name, ubus_method_definition) { - func = ucv_object_get(ubus_method_definition, "call", NULL); - args = ucv_object_get(ubus_method_definition, "args", NULL); - - if (ucv_type(ubus_method_definition) != UC_OBJECT) { - fprintf(stderr, "Invalid method definition for ucode script %s, object %s, method %s" - " - expected dictionary, got %s\n", - script->path, ubus_object_name, ubus_method_name, ucv_typename(ubus_method_definition)); - - return false; - } - - if (!ucv_is_callable(func)) { - fprintf(stderr, "Invalid method callback for ucode script %s, object %s, method %s" - " - expected callable, got %s\n", - script->path, ubus_object_name, ubus_method_name, ucv_typename(func)); - - return false; - } - - if (args) { - if (ucv_type(args) != UC_OBJECT) { - fprintf(stderr, "Invalid method argument definition for ucode script %s, " - "object %s, method %s - expected dictionary, got %s\n", - script->path, ubus_object_name, ubus_method_name, ucv_typename(args)); - - return false; - } - - ucv_object_foreach(args, ubus_argument_name, ubus_argument_typehint) { - switch (ucv_type(ubus_argument_typehint)) { - case UC_BOOLEAN: - case UC_INTEGER: - case UC_DOUBLE: - case UC_STRING: - case UC_ARRAY: - case UC_OBJECT: - continue; - - default: - fprintf(stderr, "Unsupported argument type for ucode script %s, object %s, " - "method %s, argument %s - expected boolean, integer, string, " - "array or object, got %s\n", - script->path, ubus_object_name, ubus_method_name, ubus_argument_name, - ucv_typename(ubus_argument_typehint)); - - return false; - } - } - } - } - } - - return true; -} - -static bool -rpc_ucode_method_register(struct ubus_method *method, const char *ubus_method_name, uc_value_t *ubus_method_arguments) -{ - struct blobmsg_policy *policy; - enum blobmsg_type type; - - method->name = strdup(ubus_method_name); - - if (!method->name) { - fprintf(stderr, "Unable to allocate ubus method name: %s\n", - strerror(errno)); - - return false; - } - - method->policy = calloc(ucv_object_length(ubus_method_arguments), sizeof(*method->policy)); - - if (!method->policy) { - fprintf(stderr, "Unable to allocate ubus method argument policy: %s\n", - strerror(errno)); - - return false; - } - - method->handler = rpc_ucode_script_call; - - ucv_object_foreach(ubus_method_arguments, ubus_argument_name, ubus_argument_typehint) { - switch (ucv_type(ubus_argument_typehint)) { - case UC_BOOLEAN: - type = BLOBMSG_TYPE_INT8; - break; - - case UC_INTEGER: - switch (ucv_int64_get(ubus_argument_typehint)) { - case 8: - type = BLOBMSG_TYPE_INT8; - break; - - case 16: - type = BLOBMSG_TYPE_INT16; - break; - - case 64: - type = BLOBMSG_TYPE_INT64; - break; - - default: - type = BLOBMSG_TYPE_INT32; - break; - } - - break; - - case UC_DOUBLE: - type = BLOBMSG_TYPE_DOUBLE; - break; - - case UC_ARRAY: - type = BLOBMSG_TYPE_ARRAY; - break; - - case UC_OBJECT: - type = BLOBMSG_TYPE_TABLE; - break; - - default: - type = BLOBMSG_TYPE_STRING; - break; - } - - policy = (struct blobmsg_policy *)&method->policy[method->n_policy++]; - - policy->type = type; - policy->name = strdup(ubus_argument_name); - - if (!policy->name) { - fprintf(stderr, "Unable to allocate ubus method argument name: %s\n", - strerror(errno)); - - return false; - } - } - - return true; -} - -static bool -rpc_ucode_script_register(struct ubus_context *ctx, rpc_ucode_script_t *script) -{ - uc_value_t *signature = uc_vm_registry_get(&script->vm, "rpcd.ucode.signature"); - const struct blobmsg_policy *policy; - rpc_ucode_ubus_obj_t *uuobj = NULL; - char *tptr, *tnptr, *onptr, *mptr; - struct ubus_method *method; - struct ubus_object *obj; - size_t typelen, namelen; - uc_value_t *args; - int rv; - - if (!rpc_ucode_script_validate(script)) - return false; - - ucv_object_foreach(signature, ubus_object_name, ubus_object_methods) { - namelen = strlen(ubus_object_name); - typelen = strlen("rpcd-plugin-ucode-") + namelen; - - uuobj = calloc_a(sizeof(*uuobj), - &onptr, namelen + 1, - &mptr, ucv_object_length(ubus_object_methods) * sizeof(struct ubus_method), - &tptr, sizeof(struct ubus_object_type), - &tnptr, typelen + 1); - - if (!uuobj) { - fprintf(stderr, "Unable to allocate ubus object signature: %s\n", - strerror(errno)); - - continue; - } - - list_add(&uuobj->list, &uuobjs); - - uuobj->script = script; - uuobj->signature = ubus_object_methods; - - snprintf(tnptr, typelen, "rpcd-plugin-ucode-%s", ubus_object_name); - - method = (struct ubus_method *)mptr; - - obj = &uuobj->ubusobj; - obj->name = strncpy(onptr, ubus_object_name, namelen); - obj->methods = method; - - obj->type = (struct ubus_object_type *)tptr; - obj->type->name = tnptr; - obj->type->methods = obj->methods; - - ucv_object_foreach(ubus_object_methods, ubus_method_name, ubus_method_definition) { - args = ucv_object_get(ubus_method_definition, "args", NULL); - - if (!rpc_ucode_method_register(&method[obj->n_methods++], ubus_method_name, args)) - goto free; - } - - obj->type = (struct ubus_object_type *)tptr; - obj->type->name = tnptr; - obj->type->methods = obj->methods; - obj->type->n_methods = obj->n_methods; - - rv = ubus_add_object(ctx, obj); - - if (rv != UBUS_STATUS_OK) { - fprintf(stderr, "Unable to register ubus object %s: %s\n", - obj->name, ubus_strerror(rv)); - - goto free; - } - - continue; - -free: - for (; obj->n_methods > 0; method++, obj->n_methods--) { - for (policy = method->policy; method->n_policy > 0; policy++, method->n_policy--) - free((char *)policy->name); - - free((char *)method->name); - free((char *)method->policy); - } - - free(uuobj); - } - - return true; -} - -static uc_value_t * -rpc_ucode_request_reply(uc_vm_t *vm, size_t nargs) -{ - rpc_ucode_call_ctx_t **callctx = uc_fn_this("rpcd.ucode.request"); - uc_value_t *reply = uc_fn_arg(0); - uc_value_t *rcode = uc_fn_arg(1); - int64_t code = UBUS_STATUS_OK; - - if (!callctx || !*callctx) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "Attempt to invoke reply() on invalid self"); - - return NULL; - } - else if (reply && ucv_type(reply) != UC_OBJECT) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "First argument to reply() must be null or an object"); - - return NULL; - } - else if (rcode && ucv_type(rcode) != UC_INTEGER) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "Second argument to reply() must be null or an integer"); - - return NULL; - } - - if ((*callctx)->replied) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "Reply has already been sent"); - - return NULL; - } - - if (rcode) { - code = ucv_int64_get(rcode); - - if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST) - code = UBUS_STATUS_UNKNOWN_ERROR; - } - - rpc_ucode_request_finish(*callctx, code, reply); - - return NULL; -} - -static uc_value_t * -rpc_ucode_request_error(uc_vm_t *vm, size_t nargs) -{ - rpc_ucode_call_ctx_t **callctx = uc_fn_this("rpcd.ucode.request"); - uc_value_t *rcode = uc_fn_arg(0); - int64_t code; - - if (!callctx || !*callctx) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "Attempt to invoke error() on invalid self"); - - return NULL; - } - else if (ucv_type(rcode) != UC_INTEGER) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "First argument to error() must be an integer"); - - return NULL; - } - - if ((*callctx)->replied) { - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "Reply has already been sent"); - - return NULL; - } - - - code = ucv_int64_get(rcode); - - if (errno == ERANGE || code < 0 || code > __UBUS_STATUS_LAST) - code = UBUS_STATUS_UNKNOWN_ERROR; - - rpc_ucode_request_finish(*callctx, code, NULL); - - return NULL; -} - -static const uc_function_list_t rpc_ucode_request_fns[] = { - { "reply", rpc_ucode_request_reply }, - { "error", rpc_ucode_request_error }, -}; - -static void -rpc_ucode_request_gc(void *ud) -{ - rpc_ucode_call_ctx_t *callctx = ud; - - uloop_timeout_cancel(&callctx->timeout); - free(callctx); -} - -static void -rpc_ucode_init_globals(rpc_ucode_script_t *script) -{ - uc_vm_t *vm = &script->vm; - uc_value_t *scope = uc_vm_scope_get(vm); - -#define status_const(name) \ - ucv_object_add(scope, #name, ucv_uint64_new(name)) - - status_const(UBUS_STATUS_OK); - status_const(UBUS_STATUS_INVALID_COMMAND); - status_const(UBUS_STATUS_INVALID_ARGUMENT); - status_const(UBUS_STATUS_METHOD_NOT_FOUND); - status_const(UBUS_STATUS_NOT_FOUND); - status_const(UBUS_STATUS_NO_DATA); - status_const(UBUS_STATUS_PERMISSION_DENIED); - status_const(UBUS_STATUS_TIMEOUT); - status_const(UBUS_STATUS_NOT_SUPPORTED); - status_const(UBUS_STATUS_UNKNOWN_ERROR); - status_const(UBUS_STATUS_CONNECTION_FAILED); - -#undef status_const - - uc_stdlib_load(scope); - - script->requesttype = uc_type_declare(vm, "rpcd.ucode.request", - rpc_ucode_request_fns, rpc_ucode_request_gc); -} - -static rpc_ucode_script_t * -rpc_ucode_script_execute(struct ubus_context *ctx, const char *path, uc_program_t *prog) -{ - rpc_ucode_script_t *script; - uc_value_t *signature; - uc_vm_status_t status; - size_t pathlen; - char *pptr; - - pathlen = strlen(path); - script = calloc_a(sizeof(*script), &pptr, pathlen + 1); - - if (!script) { - fprintf(stderr, "Unable to allocate context for ucode script %s: %s\n", - path, strerror(errno)); - - uc_program_put(prog); - - return NULL; - } - - script->path = strncpy(pptr, path, pathlen); - - uc_vm_init(&script->vm, &config); - rpc_ucode_init_globals(script); - - status = uc_vm_execute(&script->vm, prog, &signature); - - script->pending_replies = ucv_array_new(&script->vm); - - uc_vm_registry_set(&script->vm, "rpcd.ucode.signature", signature); - uc_vm_registry_set(&script->vm, "rpcd.ucode.deferreds", script->pending_replies); - - uc_program_put(prog); - ucv_gc(&script->vm); - - switch (status) { - case STATUS_OK: - if (rpc_ucode_script_register(ctx, script)) - return script; - - fprintf(stderr, "Skipping registration of ucode script %s\n", path); - break; - - case STATUS_EXIT: - fprintf(stderr, "The ucode script %s invoked exit(%" PRId64 ")\n", - path, ucv_int64_get(signature)); - break; - - case ERROR_COMPILE: - fprintf(stderr, "Compilation error while executing ucode script %s\n", path); - break; - - case ERROR_RUNTIME: - fprintf(stderr, "Runtime error while executing ucode script %s\n", path); - break; - } - - uc_vm_free(&script->vm); - free(script); - - return NULL; -} - -static int -rpc_ucode_init_script(struct ubus_context *ctx, const char *path) -{ - rpc_ucode_script_t *script; - uc_program_t *prog; - uc_source_t *src; - - src = uc_source_new_file(path); - - if (!src) { - fprintf(stderr, "Unable to open ucode script %s: %s\n", - path, strerror(errno)); - - return UBUS_STATUS_UNKNOWN_ERROR; - } - - prog = rpc_ucode_script_compile(path, src); - - if (!prog) - return UBUS_STATUS_UNKNOWN_ERROR; - - script = rpc_ucode_script_execute(ctx, path, prog); - - if (!script) - return UBUS_STATUS_UNKNOWN_ERROR; - - list_add(&script->list, &scripts); - - return UBUS_STATUS_OK; -} - -static int -rpc_ucode_api_init(const struct rpc_daemon_ops *ops, struct ubus_context *ctx) -{ - char path[PATH_MAX]; - struct dirent *e; - struct stat s; - int rv = 0; - DIR *d; - - request_timeout = *ops->exec_timeout; - - /* reopen ucode.so with RTLD_GLOBAL in order to export libucode runtime - * symbols for ucode extensions loaded later at runtime */ - if (!dlopen(RPC_LIBRARY_DIRECTORY "/ucode.so", RTLD_LAZY|RTLD_GLOBAL)) { - fprintf(stderr, "Failed to dlopen() ucode.so: %s, dynamic ucode plugins may fail\n", - dlerror()); - } - - /* initialize default module search path */ - uc_search_path_init(&config.module_search_path); - - if ((d = opendir(RPC_UCSCRIPT_DIRECTORY)) != NULL) { - while ((e = readdir(d)) != NULL) { - snprintf(path, sizeof(path), RPC_UCSCRIPT_DIRECTORY "/%s", e->d_name); - - if (stat(path, &s) || !S_ISREG(s.st_mode)) - continue; - - if (s.st_mode & S_IWOTH) { - fprintf(stderr, "Ignoring ucode script %s because it is world writable\n", - path); - - continue; - } - - rv |= rpc_ucode_init_script(ctx, path); - } - - closedir(d); - } - - return rv; -} - -struct rpc_plugin rpc_plugin = { - .init = rpc_ucode_api_init -};