diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2013-09-09 17:01:22 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2013-09-09 17:01:22 +0200 |
commit | 2f0e65dd27549ef4fde5b18588083968a59b66bd (patch) | |
tree | 371bf5fd09ddf206442848a95796d41bf81f95b8 /luci2/src | |
download | luci2-ui-2f0e65dd27549ef4fde5b18588083968a59b66bd.tar.gz |
Initial commit of LuCI2
Diffstat (limited to 'luci2/src')
-rw-r--r-- | luci2/src/CMakeLists.txt | 6 | ||||
-rw-r--r-- | luci2/src/io/CMakeLists.txt | 29 | ||||
-rw-r--r-- | luci2/src/io/login.c | 281 | ||||
-rw-r--r-- | luci2/src/io/login.h | 32 | ||||
-rw-r--r-- | luci2/src/io/main.c | 711 | ||||
-rw-r--r-- | luci2/src/io/multipart_parser.c | 309 | ||||
-rw-r--r-- | luci2/src/io/multipart_parser.h | 48 | ||||
-rw-r--r-- | luci2/src/rpcd/CMakeLists.txt | 18 | ||||
-rw-r--r-- | luci2/src/rpcd/luci2.c | 2224 |
9 files changed, 3658 insertions, 0 deletions
diff --git a/luci2/src/CMakeLists.txt b/luci2/src/CMakeLists.txt new file mode 100644 index 0000000..6f8e928 --- /dev/null +++ b/luci2/src/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(luci2 C) + +ADD_SUBDIRECTORY(io) +ADD_SUBDIRECTORY(rpcd) diff --git a/luci2/src/io/CMakeLists.txt b/luci2/src/io/CMakeLists.txt new file mode 100644 index 0000000..44426ea --- /dev/null +++ b/luci2/src/io/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(luci2-io C) + +INCLUDE(CheckFunctionExists) + +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +FIND_LIBRARY(LIBS crypt) +IF(LIBS STREQUAL "LIBS-NOTFOUND") + SET(LIBS "") +ENDIF() + +CHECK_FUNCTION_EXISTS(getspnam HAVE_SHADOW) +IF(HAVE_SHADOW) + ADD_DEFINITIONS(-DHAVE_SHADOW) +ENDIF() + +ADD_EXECUTABLE(luci2-io main.c login.c multipart_parser.c) +TARGET_LINK_LIBRARIES(luci2-io uci ubox ubus blobmsg_json ${LIBS}) + +INSTALL(TARGETS luci2-io RUNTIME DESTINATION sbin) diff --git a/luci2/src/io/login.c b/luci2/src/io/login.c new file mode 100644 index 0000000..31e2970 --- /dev/null +++ b/luci2/src/io/login.c @@ -0,0 +1,281 @@ +/* + * luci-io - LuCI non-RPC helper + * + * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> + * + * 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 "login.h" + + +enum { + SES_NEW_SID, + __SES_NEW_MAX, +}; + +static const struct blobmsg_policy ses_new_policy[__SES_NEW_MAX] = { + [SES_NEW_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING }, +}; + +static bool +parse_acl_scope(struct blob_attr *acl_perm, struct blob_attr *acl_scope, + struct blob_buf *req) +{ + struct blob_attr *acl_obj, *acl_func; + int rem, rem2; + void *c, *d; + + if (!strcmp(blobmsg_name(acl_scope), "ubus") && + blob_id(acl_scope) == BLOBMSG_TYPE_TABLE) + { + blobmsg_add_string(req, "scope", blobmsg_name(acl_scope)); + c = blobmsg_open_array(req, "objects"); + + blobmsg_for_each_attr(acl_obj, acl_scope, rem) + { + if (blob_id(acl_obj) != BLOBMSG_TYPE_ARRAY) + continue; + + blobmsg_for_each_attr(acl_func, acl_obj, rem2) + { + if (blob_id(acl_func) != BLOBMSG_TYPE_STRING) + continue; + + d = blobmsg_open_array(req, NULL); + blobmsg_add_string(req, NULL, blobmsg_name(acl_obj)); + blobmsg_add_string(req, NULL, blobmsg_data(acl_func)); + blobmsg_close_array(req, d); + } + } + + blobmsg_close_array(req, c); + return true; + } + else if ((!strcmp(blobmsg_name(acl_scope), "uci") || + !strcmp(blobmsg_name(acl_scope), "luci-io")) && + blob_id(acl_scope) == BLOBMSG_TYPE_ARRAY) + { + blobmsg_add_string(req, "scope", blobmsg_name(acl_scope)); + c = blobmsg_open_array(req, "objects"); + + blobmsg_for_each_attr(acl_obj, acl_scope, rem) + { + if (blob_id(acl_obj) != BLOBMSG_TYPE_STRING) + continue; + + d = blobmsg_open_array(req, NULL); + blobmsg_add_string(req, NULL, blobmsg_data(acl_obj)); + blobmsg_add_string(req, NULL, blobmsg_name(acl_perm)); + blobmsg_close_array(req, d); + } + + blobmsg_close_array(req, c); + return true; + } + + return false; +} + +static struct uci_section * +test_user_access(struct uci_context *uci, const char *user) +{ + struct uci_package *p; + struct uci_section *s; + struct uci_element *e; + struct uci_ptr ptr = { .package = "luci", .option = "user" }; + + 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, "access")) + continue; + + ptr.section = s->e.name; + ptr.s = NULL; + ptr.o = NULL; + + if (uci_lookup_ptr(uci, &ptr, NULL, true)) + continue; + + if (ptr.o->type != UCI_TYPE_STRING) + continue; + + if (strcmp(ptr.o->v.string, user)) + continue; + + return ptr.s; + } + + return NULL; +} + +static bool +test_group_access(struct uci_section *s, const char *perm, const char *group) +{ + struct uci_option *o; + struct uci_element *e, *l; + + 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; + + uci_foreach_element(&o->v.list, l) + if (l->name && !fnmatch(l->name, group, 0)) + return true; + } + + if (!strcmp(perm, "read")) + return test_group_access(s, "write", group); + + return false; +} + +static void +setup_session_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct blob_attr *tb[__SES_NEW_MAX]; + const char **sid = req->priv; + + if (!msg) + return; + + blobmsg_parse(ses_new_policy, __SES_NEW_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (tb[SES_NEW_SID]) + *sid = strdup(blobmsg_data(tb[SES_NEW_SID])); +} + +const char * +setup_session(const char *user) +{ + uint32_t id; + struct blob_buf req = { 0 }, acl = { 0 }; + struct blob_attr *acl_group, *acl_perm, *acl_scope; + struct ubus_context *ctx = NULL; + struct uci_context *uci = NULL; + struct uci_section *access; + const char *sid = NULL; + int i, rem, rem2, rem3; + void *c, *d; + glob_t gl; + + uci = uci_alloc_context(); + + if (!uci) + goto out; + + access = test_user_access(uci, user); + + /* continue without access group if user is root (backward compat) */ + if (!access && strcmp(user, "root")) + goto out; + + ctx = ubus_connect(NULL); + + if (!ctx || ubus_lookup_id(ctx, "session", &id)) + goto out; + + blob_buf_init(&req, 0); + blobmsg_add_u32(&req, "timeout", 3600); + ubus_invoke(ctx, id, "create", req.head, setup_session_cb, &sid, 500); + + if (!sid) + goto out; + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "sid", sid); + c = blobmsg_open_table(&req, "values"); + blobmsg_add_string(&req, "user", user); + blobmsg_close_table(&req, c); + ubus_invoke(ctx, id, "set", req.head, NULL, NULL, 500); + + if (glob("/usr/share/luci2/acl.d/*.json", 0, NULL, &gl)) + goto out; + + for (i = 0; i < gl.gl_pathc; i++) + { + blob_buf_init(&acl, 0); + + if (!blobmsg_add_json_from_file(&acl, gl.gl_pathv[i])) + { + fprintf(stderr, "Failed to parse %s\n", gl.gl_pathv[i]); + continue; + } + + blob_for_each_attr(acl_group, acl.head, rem) + { + blobmsg_for_each_attr(acl_perm, acl_group, rem2) + { + if (blob_id(acl_perm) != BLOBMSG_TYPE_TABLE) + continue; + + if (strcmp(blobmsg_name(acl_perm), "read") && + strcmp(blobmsg_name(acl_perm), "write")) + continue; + + if (access != NULL && + !test_group_access(access, blobmsg_name(acl_perm), + blobmsg_name(acl_group))) + continue; + + blobmsg_for_each_attr(acl_scope, acl_perm, rem3) + { + blob_buf_init(&req, 0); + + if (!parse_acl_scope(acl_perm, acl_scope, &req)) + continue; + + blobmsg_add_string(&req, "sid", sid); + ubus_invoke(ctx, id, "grant", req.head, NULL, NULL, 500); + + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "sid", sid); + blobmsg_add_string(&req, "scope", "luci-ui"); + c = blobmsg_open_array(&req, "objects"); + d = blobmsg_open_array(&req, NULL); + blobmsg_add_string(&req, NULL, blobmsg_name(acl_group)); + blobmsg_add_string(&req, NULL, blobmsg_name(acl_perm)); + blobmsg_close_array(&req, d); + blobmsg_close_array(&req, c); + ubus_invoke(ctx, id, "grant", req.head, NULL, NULL, 500); + } + } + } + } + + globfree(&gl); + +out: + if (uci) + uci_free_context(uci); + + if (ctx) + ubus_free(ctx); + + return sid; +} diff --git a/luci2/src/io/login.h b/luci2/src/io/login.h new file mode 100644 index 0000000..ed7da07 --- /dev/null +++ b/luci2/src/io/login.h @@ -0,0 +1,32 @@ +/* + * luci-io - LuCI non-RPC helper + * + * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> + * + * 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 <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <glob.h> +#include <fnmatch.h> + +#include <uci.h> +#include <libubus.h> +#include <libubox/blobmsg.h> +#include <libubox/blobmsg_json.h> + + +const char * +setup_session(const char *user); diff --git a/luci2/src/io/main.c b/luci2/src/io/main.c new file mode 100644 index 0000000..478e143 --- /dev/null +++ b/luci2/src/io/main.c @@ -0,0 +1,711 @@ +/* + * luci-io - LuCI non-RPC helper + * + * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> + * + * 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 +#define _XOPEN_SOURCE 700 + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <libubus.h> +#include <libubox/blobmsg.h> + +#ifdef HAVE_SHADOW +#include <shadow.h> +#endif + +#include "login.h" +#include "multipart_parser.h" + + +enum part { + PART_UNKNOWN, + PART_SESSIONID, + PART_FILENAME, + PART_FILEMODE, + PART_FILEDATA +}; + +const char *parts[] = { + "(bug)", + "sessionid", + "filename", + "filemode", + "filedata", +}; + +struct state +{ + bool is_content_disposition; + enum part parttype; + char *sessionid; + char *filename; + bool filedata; + int filemode; + int filefd; + int tempfd; +}; + +enum { + SES_ACCESS, + __SES_MAX, +}; + +static const struct blobmsg_policy ses_policy[__SES_MAX] = { + [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL }, +}; + + +static struct state st; + +static void +session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct blob_attr *tb[__SES_MAX]; + bool *allow = (bool *)req->priv; + + if (!msg) + return; + + blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg)); + + if (tb[SES_ACCESS]) + *allow = blobmsg_get_bool(tb[SES_ACCESS]); +} + +static bool +session_access(const char *sid, const char *obj, const char *func) +{ + uint32_t id; + bool allow = false; + struct ubus_context *ctx; + static struct blob_buf req; + + ctx = ubus_connect(NULL); + + if (!ctx || ubus_lookup_id(ctx, "session", &id)) + goto out; + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "sid", sid); + blobmsg_add_string(&req, "scope", "luci-io"); + blobmsg_add_string(&req, "object", obj); + blobmsg_add_string(&req, "function", func); + + ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500); + +out: + if (ctx) + ubus_free(ctx); + + return allow; +} + +static char * +md5sum(const char *file) +{ + pid_t pid; + int fds[2]; + static char md5[33]; + + if (pipe(fds)) + return NULL; + + switch ((pid = fork())) + { + case -1: + return NULL; + + case 0: + uloop_done(); + + dup2(fds[1], 1); + + close(0); + close(2); + close(fds[0]); + close(fds[1]); + + if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL)); + return NULL; + + break; + + default: + memset(md5, 0, sizeof(md5)); + read(fds[0], md5, 32); + waitpid(pid, NULL, 0); + close(fds[0]); + close(fds[1]); + } + + return md5; +} + +static char * +datadup(const void *in, size_t len) +{ + char *out = malloc(len + 1); + + if (!out) + return NULL; + + memcpy(out, in, len); + + *(out + len) = 0; + + return out; +} + +static bool +urldecode(char *buf) +{ + char *c, *p; + + if (!buf || !*buf) + return true; + +#define hex(x) \ + (((x) <= '9') ? ((x) - '0') : \ + (((x) <= 'F') ? ((x) - 'A' + 10) : \ + ((x) - 'a' + 10))) + + for (c = p = buf; *p; c++) + { + if (*p == '%') + { + if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2))) + return false; + + *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2))); + + p += 3; + } + else if (*p == '+') + { + *c = ' '; + p++; + } + else + { + *c = *p++; + } + } + + *c = 0; + + return true; +} + +static bool +postdecode(char **fields, int n_fields) +{ + char *p; + const char *var; + static char buf[1024]; + int i, len, field, found = 0; + + var = getenv("CONTENT_TYPE"); + + if (!var || strncmp(var, "application/x-www-form-urlencoded", 33)) + return false; + + memset(buf, 0, sizeof(buf)); + + if ((len = read(0, buf, sizeof(buf) - 1)) > 0) + { + for (p = buf, i = 0; i <= len; i++) + { + if (buf[i] == '=') + { + buf[i] = 0; + + for (field = 0; field < (n_fields * 2); field += 2) + { + if (!strcmp(p, fields[field])) + { + fields[field + 1] = buf + i + 1; + found++; + } + } + } + else if (buf[i] == '&' || buf[i] == '\0') + { + buf[i] = 0; + + if (found >= n_fields) + break; + + p = buf + i + 1; + } + } + } + + for (field = 0; field < (n_fields * 2); field += 2) + if (!urldecode(fields[field + 1])) + return false; + + return (found >= n_fields); +} + +static int +response(bool success, const char *message) +{ + char *md5; + struct stat s; + + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n{\n"); + + if (success) + { + if (!stat(st.filename, &s) && (md5 = md5sum(st.filename)) != NULL) + printf("\t\"size\": %u,\n\t\"checksum\": \"%s\"\n", + (unsigned int)s.st_size, md5); + } + else + { + if (message) + printf("\t\"message\": \"%s\",\n", message); + + printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno)); + + if (st.filefd > -1) + unlink(st.filename); + } + + printf("}\n"); + + return -1; +} + +static int +failure(int e, const char *message) +{ + printf("Status: 500 Internal Server failure\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf(message); + + if (e) + printf(": %s", strerror(e)); + + return -1; +} + +static int +filecopy(void) +{ + int len; + char buf[4096]; + + if (!st.filedata) + { + close(st.tempfd); + errno = EINVAL; + return response(false, "No file data received"); + } + + if (lseek(st.tempfd, 0, SEEK_SET) < 0) + { + close(st.tempfd); + return response(false, "Failed to rewind temp file"); + } + + st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + + if (st.filefd < 0) + { + close(st.tempfd); + return response(false, "Failed to open target file"); + } + + while ((len = read(st.tempfd, buf, sizeof(buf))) > 0) + { + if (write(st.filefd, buf, len) != len) + { + close(st.tempfd); + close(st.filefd); + return response(false, "I/O failure while writing target file"); + } + } + + close(st.tempfd); + close(st.filefd); + + if (chmod(st.filename, st.filemode)) + return response(false, "Failed to chmod target file"); + + return 0; +} + +static int +header_field(multipart_parser *p, const char *data, size_t len) +{ + st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len); + return 0; +} + +static int +header_value(multipart_parser *p, const char *data, size_t len) +{ + int i, j; + + if (!st.is_content_disposition) + return 0; + + if (len < 10 || strncasecmp(data, "form-data", 9)) + return 0; + + for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--); + + if (len < 8 || strncasecmp(data, "name=\"", 6)) + return 0; + + for (data += 6, len -= 6, i = 0; i <= len; i++) + { + if (*(data + i) != '"') + continue; + + for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++) + if (!strncmp(data, parts[j], i)) + st.parttype = j; + + break; + } + + return 0; +} + +static int +data_begin_cb(multipart_parser *p) +{ + char tmpname[24] = "/tmp/luci-upload.XXXXXX"; + + if (st.parttype == PART_FILEDATA) + { + if (!st.sessionid) + return response(false, "File data without session"); + + if (!st.filename) + return response(false, "File data without name"); + + st.tempfd = mkstemp(tmpname); + + if (st.tempfd < 0) + return response(false, "Failed to create temporary file"); + + unlink(tmpname); + } + + return 0; +} + +static int +data_cb(multipart_parser *p, const char *data, size_t len) +{ + switch (st.parttype) + { + case PART_SESSIONID: + st.sessionid = datadup(data, len); + break; + + case PART_FILENAME: + st.filename = datadup(data, len); + break; + + case PART_FILEMODE: + st.filemode = strtoul(data, NULL, 8); + break; + + case PART_FILEDATA: + if (write(st.tempfd, data, len) != len) + { + close(st.tempfd); + return response(false, "I/O failure while writing temporary file"); + } + + if (!st.filedata) + st.filedata = !!len; + + break; + + default: + break; + } + + return 0; +} + +static int +data_end_cb(multipart_parser *p) +{ + if (st.parttype == PART_SESSIONID) + { + if (!session_access(st.sessionid, "upload", "write")) + { + errno = EPERM; + return response(false, "Upload permission denied"); + } + } + else if (st.parttype == PART_FILEDATA) + { + if (st.tempfd < 0) + return response(false, "Internal program failure"); + +#if 0 + /* prepare directory */ + for (ptr = st.filename; *ptr; ptr++) + { + if (*ptr == '/') + { + *ptr = 0; + + if (mkdir(st.filename, 0755)) + { + unlink(st.tmpname); + return response(false, "Failed to create destination directory"); + } + + *ptr = '/'; + } + } +#endif + + if (filecopy()) + return -1; + + return response(true, NULL); + } + + st.parttype = PART_UNKNOWN; + return 0; +} + +static multipart_parser * +init_parser(void) +{ + char *boundary; + const char *var; + + multipart_parser *p; + multipart_parser_settings s = { + .on_part_data = data_cb, + .on_headers_complete = data_begin_cb, + .on_part_data_end = data_end_cb, + .on_header_field = header_field, + .on_header_value = header_value + }; + + var = getenv("CONTENT_TYPE"); + + if (!var || strncmp(var, "multipart/form-data;", 20)) + return NULL; + + for (var += 20; *var && *var != '='; var++); + + if (*var++ != '=') + return NULL; + + boundary = malloc(strlen(var) + 3); + + if (!boundary) + return NULL; + + strcpy(boundary, "--"); + strcpy(boundary + 2, var); + + st.tempfd = -1; + st.filefd = -1; + st.filemode = 0600; + + p = multipart_parser_init(boundary, &s); + + free(boundary); + + return p; +} + +static int +main_upload(int argc, char *argv[]) +{ + int rem, len; + char buf[4096]; + multipart_parser *p; + + p = init_parser(); + + if (!p) + { + errno = EINVAL; + return response(false, "Invalid request"); + } + + while ((len = read(0, buf, sizeof(buf))) > 0) + { + rem = multipart_parser_execute(p, buf, len); + + if (rem < len) + break; + } + + multipart_parser_free(p); + + /* read remaining post data */ + while ((len = read(0, buf, sizeof(buf))) > 0); + + return 0; +} + +static int +main_backup(int argc, char **argv) +{ + pid_t pid; + time_t now; + int len; + int fds[2]; + char buf[4096]; + char datestr[16] = { 0 }; + char hostname[64] = { 0 }; + char *fields[] = { "sessionid", NULL }; + + if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read")) + return failure(0, "Backup permission denied"); + + if (pipe(fds)) + return failure(errno, "Failed to spawn pipe"); + + switch ((pid = fork())) + { + case -1: + return failure(errno, "Failed to fork process"); + + case 0: + dup2(fds[1], 1); + + close(0); + close(2); + close(fds[0]); + close(fds[1]); + + chdir("/"); + + execl("/sbin/sysupgrade", "/sbin/sysupgrade", + "--create-backup", "-", NULL); + + return -1; + + default: + now = time(NULL); + strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now)); + + if (gethostname(hostname, sizeof(hostname) - 1)) + sprintf(hostname, "OpenWrt"); + + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/x-targz\r\n"); + printf("Content-Disposition: attachment; " + "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr); + + while ((len = read(fds[0], buf, sizeof(buf))) > 0) + fwrite(buf, len, 1, stdout); + + waitpid(pid, NULL, 0); + + close(fds[0]); + close(fds[1]); + + return 0; + } +} + +static int +main_login(int argc, char **argv) +{ + char *hash, *fields[] = { "username", NULL, "password", NULL }; + const char *sid = NULL; + + if (postdecode(fields, 2)) + { +#ifdef HAVE_SHADOW + struct spwd *sp = getspnam(fields[1]); + + if (!sp) + goto inval; + + /* check whether a password is set */ + if (sp->sp_pwdp && *sp->sp_pwdp && + strcmp(sp->sp_pwdp, "!") && strcmp(sp->sp_pwdp, "x")) + { + hash = crypt(fields[3], sp->sp_pwdp); + + if (strcmp(hash, sp->sp_pwdp)) + goto inval; + } +#else + struct passwd *pw = getpwnam(fields[1]); + + if (!pw) + goto inval; + + /* check whether a password is set */ + if (pw->pw_passwd && *pw->pw_passwd && + strcmp(pw->pw_passwd, "!") && strcmp(pw->pw_passwd, "x")) + { + hash = crypt(fields[3], pw->pw_passwd); + + if (strcmp(hash, pw->pw_passwd)) + goto inval; + } +#endif + + sid = setup_session(fields[1]); + + if (!sid) + goto inval; + + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n{\n"); + printf("\t\"sessionid\": \"%s\"\n}\n", sid); + return 0; + } + +inval: + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n{}\n"); + return 1; +} + +int main(int argc, char **argv) +{ + if (strstr(argv[0], "luci-upload")) + return main_upload(argc, argv); + else if (strstr(argv[0], "luci-backup")) + return main_backup(argc, argv); + else if (strstr(argv[0], "luci-login")) + return main_login(argc, argv); + + return -1; +} diff --git a/luci2/src/io/multipart_parser.c b/luci2/src/io/multipart_parser.c new file mode 100644 index 0000000..ee82c82 --- /dev/null +++ b/luci2/src/io/multipart_parser.c @@ -0,0 +1,309 @@ +/* Based on node-formidable by Felix Geisendörfer + * Igor Afonov - afonov@gmail.com - 2012 + * MIT License - http://www.opensource.org/licenses/mit-license.php + */ + +#include "multipart_parser.h" + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +static void multipart_log(const char * format, ...) +{ +#ifdef DEBUG_MULTIPART + va_list args; + va_start(args, format); + + fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +#endif +} + +#define NOTIFY_CB(FOR) \ +do { \ + if (p->settings->on_##FOR) { \ + if (p->settings->on_##FOR(p) != 0) { \ + return i; \ + } \ + } \ +} while (0) + +#define EMIT_DATA_CB(FOR, ptr, len) \ +do { \ + if (p->settings->on_##FOR) { \ + if (p->settings->on_##FOR(p, ptr, len) != 0) { \ + return i; \ + } \ + } \ +} while (0) + + +#define LF 10 +#define CR 13 + +struct multipart_parser { + void * data; + + size_t index; + size_t boundary_length; + + unsigned char state; + + const multipart_parser_settings* settings; + + char* lookbehind; + char multipart_boundary[1]; +}; + +enum state { + s_uninitialized = 1, + s_start, + s_start_boundary, + s_header_field_start, + s_header_field, + s_headers_almost_done, + s_header_value_start, + s_header_value, + s_header_value_almost_done, + s_part_data_start, + s_part_data, + s_part_data_almost_boundary, + s_part_data_boundary, + s_part_data_almost_end, + s_part_data_end, + s_part_data_final_hyphen, + s_end +}; + +multipart_parser* multipart_parser_init + (const char *boundary, const multipart_parser_settings* settings) { + + multipart_parser* p = malloc(sizeof(multipart_parser) + + strlen(boundary) + + strlen(boundary) + 9); + + strcpy(p->multipart_boundary, boundary); + p->boundary_length = strlen(boundary); + + p->lookbehind = (p->multipart_boundary + p->boundary_length + 1); + + p->index = 0; + p->state = s_start; + p->settings = settings; + + return p; +} + +void multipart_parser_free(multipart_parser* p) { + free(p); +} + +void multipart_parser_set_data(multipart_parser *p, void *data) { + p->data = data; +} + +void *multipart_parser_get_data(multipart_parser *p) { + return p->data; +} + +size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) { + size_t i = 0; + size_t mark = 0; + char c, cl; + int is_last = 0; + + while(i < len) { + c = buf[i]; + is_last = (i == (len - 1)); + switch (p->state) { + case s_start: + multipart_log("s_start"); + p->index = 0; + p->state = s_start_boundary; + + /* fallthrough */ + case s_start_boundary: + multipart_log("s_start_boundary"); + if (p->index == p->boundary_length) { + if (c != CR) { + return i; + } + p->index++; + break; + } else if (p->index == (p->boundary_length + 1)) { + if (c != LF) { + return i; + } + p->index = 0; + NOTIFY_CB(part_data_begin); + p->state = s_header_field_start; + break; + } + if (c != p->multipart_boundary[p->index]) { + return i; + } + p->index++; + break; + + case s_header_field_start: + multipart_log("s_header_field_start"); + mark = i; + p->state = s_header_field; + + /* fallthrough */ + case s_header_field: + multipart_log("s_header_field"); + if (c == CR) { + p->state = s_headers_almost_done; + break; + } + + if (c == '-') { + break; + } + + if (c == ':') { + EMIT_DATA_CB(header_field, buf + mark, i - mark); + p->state = s_header_value_start; + break; + } + + cl = tolower(c); + if (cl < 'a' || cl > 'z') { + multipart_log("invalid character in header name"); + return i; + } + if (is_last) + EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1); + break; + + case s_headers_almost_done: + multipart_log("s_headers_almost_done"); + if (c != LF) { + return i; + } + + p->state = s_part_data_start; + break; + + case s_header_value_start: + multipart_log("s_header_value_start"); + if (c == ' ') { + break; + } + + mark = i; + p->state = s_header_value; + + /* fallthrough */ + case s_header_value: + multipart_log("s_header_value"); + if (c == CR) { + EMIT_DATA_CB(header_value, buf + mark, i - mark); + p->state = s_header_value_almost_done; + } + if (is_last) + EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1); + break; + + case s_header_value_almost_done: + multipart_log("s_header_value_almost_done"); + if (c != LF) { + return i; + } + p->state = s_header_field_start; + break; + + case s_part_data_start: + multipart_log("s_part_data_start"); + NOTIFY_CB(headers_complete); + mark = i; + p->state = s_part_data; + + /* fallthrough */ + case s_part_data: + multipart_log("s_part_data"); + if (c == CR) { + EMIT_DATA_CB(part_data, buf + mark, i - mark); + mark = i; + p->state = s_part_data_almost_boundary; + p->lookbehind[0] = CR; + break; + } + if (is_last) + EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1); + break; + + case s_part_data_almost_boundary: + multipart_log("s_part_data_almost_boundary"); + if (c == LF) { + p->state = s_part_data_boundary; + p->lookbehind[1] = LF; + p->index = 0; + break; + } + EMIT_DATA_CB(part_data, p->lookbehind, 1); + p->state = s_part_data; + mark = i --; + break; + + case s_part_data_boundary: + multipart_log("s_part_data_boundary"); + if (p->multipart_boundary[p->index] != c) { + EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index); + p->state = s_part_data; + mark = i --; + break; + } + p->lookbehind[2 + p->index] = c; + if ((++ p->index) == p->boundary_length) { + NOTIFY_CB(part_data_end); + p->state = s_part_data_almost_end; + } + break; + + case s_part_data_almost_end: + multipart_log("s_part_data_almost_end"); + if (c == '-') { + p->state = s_part_data_final_hyphen; + break; + } + if (c == CR) { + p->state = s_part_data_end; + break; + } + return i; + + case s_part_data_final_hyphen: + multipart_log("s_part_data_final_hyphen"); + if (c == '-') { + NOTIFY_CB(body_end); + p->state = s_end; + break; + } + return i; + + case s_part_data_end: + multipart_log("s_part_data_end"); + if (c == LF) { + p->state = s_header_field_start; + NOTIFY_CB(part_data_begin); + break; + } + return i; + + case s_end: + multipart_log("s_end: %02X", (int) c); + break; + + default: + multipart_log("Multipart parser unrecoverable error"); + return 0; + } + ++ i; + } + + return len; +} diff --git a/luci2/src/io/multipart_parser.h b/luci2/src/io/multipart_parser.h new file mode 100644 index 0000000..87e67f4 --- /dev/null +++ b/luci2/src/io/multipart_parser.h @@ -0,0 +1,48 @@ +/* Based on node-formidable by Felix Geisendörfer + * Igor Afonov - afonov@gmail.com - 2012 + * MIT License - http://www.opensource.org/licenses/mit-license.php + */ +#ifndef _multipart_parser_h +#define _multipart_parser_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <stdlib.h> +#include <ctype.h> + +typedef struct multipart_parser multipart_parser; +typedef struct multipart_parser_settings multipart_parser_settings; +typedef struct multipart_parser_state multipart_parser_state; + +typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length); +typedef int (*multipart_notify_cb) (multipart_parser*); + +struct multipart_parser_settings { + multipart_data_cb on_header_field; + multipart_data_cb on_header_value; + multipart_data_cb on_part_data; + + multipart_notify_cb on_part_data_begin; + multipart_notify_cb on_headers_complete; + multipart_notify_cb on_part_data_end; + multipart_notify_cb on_body_end; +}; + +multipart_parser* multipart_parser_init + (const char *boundary, const multipart_parser_settings* settings); + +void multipart_parser_free(multipart_parser* p); + +size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len); + +void multipart_parser_set_data(multipart_parser* p, void* data); +void * multipart_parser_get_data(multipart_parser* p); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/luci2/src/rpcd/CMakeLists.txt b/luci2/src/rpcd/CMakeLists.txt new file mode 100644 index 0000000..6693da5 --- /dev/null +++ b/luci2/src/rpcd/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(luci2-plugin C) + +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations -Iinclude) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +ADD_LIBRARY(luci2-plugin MODULE luci2.c) +TARGET_LINK_LIBRARIES(luci2-plugin ubox ubus) +SET_TARGET_PROPERTIES(luci2-plugin PROPERTIES OUTPUT_NAME luci2 PREFIX "") + +INSTALL(TARGETS luci2-plugin LIBRARY DESTINATION lib) diff --git a/luci2/src/rpcd/luci2.c b/luci2/src/rpcd/luci2.c new file mode 100644 index 0000000..f136c56 --- /dev/null +++ b/luci2/src/rpcd/luci2.c @@ -0,0 +1,2224 @@ +/* + * rpcd - UBUS RPC server + * + * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> + * + * 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 <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/statvfs.h> +#include <dirent.h> +#include <arpa/inet.h> +#include <signal.h> +#include <glob.h> +#include <libubox/blobmsg_json.h> +#include <libubus.h> +#include <uci.h> + +#include <rpcd/plugin.h> + +/* limit of log size buffer */ +#define RPC_LUCI2_MAX_LOGSIZE (128 * 1024) +#define RPC_LUCI2_DEF_LOGSIZE (16 * 1024) + +/* location of menu definitions */ +#define RPC_LUCI2_MENU_FILES "/usr/share/luci2/menu.d/*.json" /* */ + + +static const struct rpc_daemon_ops *ops; + +static struct blob_buf buf; +static struct uci_context *cursor; + +enum { + RPC_S_PID, + RPC_S_SIGNAL, + __RPC_S_MAX, +}; + +static const struct blobmsg_policy rpc_signal_policy[__RPC_S_MAX] = { + [RPC_S_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 }, + [RPC_S_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 }, +}; + +enum { + RPC_I_NAME, + RPC_I_ACTION, + __RPC_I_MAX, +}; + +static const struct blobmsg_policy rpc_init_policy[__RPC_I_MAX] = { + [RPC_I_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [RPC_I_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING }, +}; + +enum { + RPC_D_DATA, + __RPC_D_MAX +}; + +static const struct blobmsg_policy rpc_data_policy[__RPC_D_MAX] = { + [RPC_D_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, +}; + +enum { + RPC_K_KEYS, + __RPC_K_MAX +}; + +static const struct blobmsg_policy rpc_sshkey_policy[__RPC_K_MAX] = { + [RPC_K_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY }, +}; + +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_OM_LIMIT, + RPC_OM_OFFSET, + RPC_OM_PATTERN, + __RPC_OM_MAX +}; + +static const struct blobmsg_policy rpc_opkg_match_policy[__RPC_OM_MAX] = { + [RPC_OM_LIMIT] = { .name = "limit", .type = BLOBMSG_TYPE_INT32 }, + [RPC_OM_OFFSET] = { .name = "offset", .type = BLOBMSG_TYPE_INT32 }, + [RPC_OM_PATTERN] = { .name = "pattern", .type = BLOBMSG_TYPE_STRING }, +}; + +enum { + RPC_OP_PACKAGE, + __RPC_OP_MAX +}; + +static const struct blobmsg_policy rpc_opkg_package_policy[__RPC_OP_MAX] = { + [RPC_OP_PACKAGE] = { .name = "package", .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_MENU_SESSION, + __RPC_MENU_MAX +}; + +static const struct blobmsg_policy rpc_menu_policy[__RPC_MENU_MAX] = { + [RPC_MENU_SESSION] = { .name = "ubus_rpc_session", + .type = BLOBMSG_TYPE_STRING }, +}; + + +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 void +log_read(FILE *log, int logsize) +{ + int len; + char *logbuf; + + if (logsize == 0) + logsize = RPC_LUCI2_DEF_LOGSIZE; + + len = (logsize > RPC_LUCI2_MAX_LOGSIZE) ? RPC_LUCI2_MAX_LOGSIZE : logsize; + logbuf = blobmsg_alloc_string_buffer(&buf, "log", len + 1); + + if (!logbuf) + return; + + while (logsize > RPC_LUCI2_MAX_LOGSIZE) + { + len = logsize % RPC_LUCI2_MAX_LOGSIZE; + + if (len == 0) + len = RPC_LUCI2_MAX_LOGSIZE; + + fread(logbuf, 1, len, log); + logsize -= len; + } + + len = fread(logbuf, 1, logsize, log); + *(logbuf + len) = 0; + + blobmsg_add_string_buffer(&buf); +} + +static int +rpc_luci2_system_log(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *log; + int logsize = 0; + const char *logfile = NULL; + struct stat st; + struct uci_package *p; + struct uci_element *e; + struct uci_section *s; + struct uci_ptr ptr = { .package = "system" }; + + uci_load(cursor, ptr.package, &p); + + if (!p) + return UBUS_STATUS_NOT_FOUND; + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "system")) + continue; + + ptr.o = NULL; + ptr.option = "log_type"; + ptr.section = e->name; + uci_lookup_ptr(cursor, &ptr, NULL, true); + break; + } + + if (ptr.o && ptr.o->type == UCI_TYPE_STRING && + !strcmp(ptr.o->v.string, "file")) + { + ptr.o = NULL; + ptr.option = "log_file"; + uci_lookup_ptr(cursor, &ptr, NULL, true); + + if (ptr.o && ptr.o->type == UCI_TYPE_STRING) + logfile = ptr.o->v.string; + else + logfile = "/var/log/messages"; + + if (stat(logfile, &st) || !(log = fopen(logfile, "r"))) + goto fail; + + logsize = st.st_size; + } + else + { + ptr.o = NULL; + ptr.option = "log_size"; + uci_lookup_ptr(cursor, &ptr, NULL, true); + + if (ptr.o && ptr.o->type == UCI_TYPE_STRING) + logsize = atoi(ptr.o->v.string) * 1024; + + if (!(log = popen("logread", "r"))) + goto fail; + } + + blob_buf_init(&buf, 0); + + log_read(log, logsize); + fclose(log); + + uci_unload(cursor, p); + ubus_send_reply(ctx, req, buf.head); + return 0; + +fail: + uci_unload(cursor, p); + return rpc_errno_status(); +} + +static int +rpc_luci2_system_dmesg(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *log; + + if (!(log = popen("dmesg", "r"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + + log_read(log, RPC_LUCI2_MAX_LOGSIZE); + fclose(log); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_system_diskfree(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int i; + void *c; + struct statvfs s; + const char *fslist[] = { + "/", "root", + "/tmp", "tmp", + }; + + blob_buf_init(&buf, 0); + + for (i = 0; i < sizeof(fslist) / sizeof(fslist[0]); i += 2) + { + if (statvfs(fslist[i], &s)) + continue; + + c = blobmsg_open_table(&buf, fslist[i+1]); + + blobmsg_add_u32(&buf, "total", s.f_blocks * s.f_frsize); + blobmsg_add_u32(&buf, "free", s.f_bfree * s.f_frsize); + blobmsg_add_u32(&buf, "used", (s.f_blocks - s.f_bfree) * s.f_frsize); + + blobmsg_close_table(&buf, c); + } + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_process_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *top; + void *c, *d; + char line[1024]; + char *pid, *ppid, *user, *stat, *vsz, *pvsz, *pcpu, *cmd; + + if (!(top = popen("/bin/busybox top -bn1", "r"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "processes"); + + while (fgets(line, sizeof(line) - 1, top)) + { + pid = strtok(line, " "); + + if (*pid < '0' || *pid > '9') + continue; + + ppid = strtok(NULL, " "); + user = strtok(NULL, " "); + stat = strtok(NULL, " "); + + if (!stat) + continue; + + if (!*(stat + 1)) + *(stat + 1) = ' '; + + if (!*(stat + 2)) + *(stat + 2) = ' '; + + *(stat + 3) = 0; + + vsz = strtok(stat + 4, " "); + pvsz = strtok(NULL, " "); + pcpu = strtok(NULL, " "); + cmd = strtok(NULL, "\n"); + + if (!cmd) + continue; + + d = blobmsg_open_table(&buf, NULL); + + blobmsg_add_u32(&buf, "pid", atoi(pid)); + blobmsg_add_u32(&buf, "ppid", atoi(ppid)); + blobmsg_add_string(&buf, "user", user); + blobmsg_add_string(&buf, "stat", stat); + blobmsg_add_u32(&buf, "vsize", atoi(vsz) * 1024); + blobmsg_add_u32(&buf, "vsize_percent", atoi(pvsz)); + blobmsg_add_u32(&buf, "cpu_percent", atoi(pcpu)); + blobmsg_add_string(&buf, "command", cmd); + + blobmsg_close_table(&buf, d); + } + + fclose(top); + blobmsg_close_array(&buf, c); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_process_signal(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int pid, sig; + struct blob_attr *tb[__RPC_S_MAX]; + + blobmsg_parse(rpc_signal_policy, __RPC_S_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_S_SIGNAL] || !tb[RPC_S_PID]) + { + errno = EINVAL; + return rpc_errno_status(); + } + + pid = blobmsg_get_u32(tb[RPC_S_PID]); + sig = blobmsg_get_u32(tb[RPC_S_SIGNAL]); + + if (kill(pid, sig)) + return rpc_errno_status(); + + return 0; +} + +static int +rpc_luci2_init_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int n; + void *c, *t; + char *p, path[PATH_MAX]; + struct stat s; + struct dirent *e; + FILE *f; + DIR *d; + + if (!(d = opendir("/etc/init.d"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "initscripts"); + + while ((e = readdir(d)) != NULL) + { + snprintf(path, sizeof(path) - 1, "/etc/init.d/%s", e->d_name); + + if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR)) + continue; + + if ((f = fopen(path, "r")) != NULL) + { + n = -1; + p = fgets(path, sizeof(path) - 1, f); + + if (!p || !strstr(p, "/etc/rc.common")) + goto skip; + + t = blobmsg_open_table(&buf, NULL); + + blobmsg_add_string(&buf, "name", e->d_name); + + while (fgets(path, sizeof(path) - 1, f)) + { + p = strtok(path, "= \t"); + + if (!strcmp(p, "START") && !!(p = strtok(NULL, "= \t\n"))) + { + n = atoi(p); + blobmsg_add_u32(&buf, "start", n); + } + else if (!strcmp(p, "STOP") && !!(p = strtok(NULL, "= \t\n"))) + { + blobmsg_add_u32(&buf, "stop", atoi(p)); + break; + } + } + + if (n > -1) + { + snprintf(path, sizeof(path) - 1, "/etc/rc.d/S%02d%s", + n, e->d_name); + + blobmsg_add_u8(&buf, "enabled", + (!stat(path, &s) && (s.st_mode & S_IXUSR))); + } + else + { + blobmsg_add_u8(&buf, "enabled", 0); + } + + blobmsg_close_table(&buf, t); + +skip: + fclose(f); + } + } + + closedir(d); + blobmsg_close_array(&buf, c); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_init_action(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int fd; + pid_t pid; + struct stat s; + char path[PATH_MAX]; + const char *action; + struct blob_attr *tb[__RPC_I_MAX]; + + blobmsg_parse(rpc_init_policy, __RPC_I_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_I_NAME] || !tb[RPC_I_ACTION]) + return UBUS_STATUS_INVALID_ARGUMENT; + + action = blobmsg_data(tb[RPC_I_ACTION]); + + if (strcmp(action, "start") && strcmp(action, "stop") && + strcmp(action, "reload") && strcmp(action, "restart") && + strcmp(action, "enable") && strcmp(action, "disable")) + return UBUS_STATUS_INVALID_ARGUMENT; + + snprintf(path, sizeof(path) - 1, "/etc/init.d/%s", + (char *)blobmsg_data(tb[RPC_I_NAME])); + + if (stat(path, &s)) + return rpc_errno_status(); + + if (!(s.st_mode & S_IXUSR)) + return UBUS_STATUS_PERMISSION_DENIED; + + switch ((pid = fork())) + { + case -1: + return rpc_errno_status(); + + case 0: + uloop_done(); + + if ((fd = open("/dev/null", O_RDWR)) > -1) + { + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + + close(fd); + } + + chdir("/"); + + if (execl(path, path, action, NULL)) + return rpc_errno_status(); + + default: + return 0; + } +} + +static int +rpc_luci2_rclocal_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + char data[4096] = { 0 }; + + if (!(f = fopen("/etc/rc.local", "r"))) + return rpc_errno_status(); + + fread(data, sizeof(data) - 1, 1, f); + fclose(f); + + blob_buf_init(&buf, 0); + blobmsg_add_string(&buf, "data", data); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_rclocal_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + struct blob_attr *tb[__RPC_D_MAX]; + + blobmsg_parse(rpc_data_policy, __RPC_D_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_D_DATA] || blobmsg_data_len(tb[RPC_D_DATA]) >= 4096) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (!(f = fopen("/etc/rc.local", "w"))) + return rpc_errno_status(); + + fwrite(blobmsg_data(tb[RPC_D_DATA]), + blobmsg_data_len(tb[RPC_D_DATA]) - 1, 1, f); + + fclose(f); + return 0; +} + +static int +rpc_luci2_crontab_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + char data[4096] = { 0 }; + + if (!(f = fopen("/etc/crontabs/root", "r"))) + return rpc_errno_status(); + + fread(data, sizeof(data) - 1, 1, f); + fclose(f); + + blob_buf_init(&buf, 0); + blobmsg_add_string(&buf, "data", data); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_crontab_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + struct stat s; + struct blob_attr *tb[__RPC_D_MAX]; + + blobmsg_parse(rpc_data_policy, __RPC_D_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_D_DATA] || blobmsg_data_len(tb[RPC_D_DATA]) >= 4096) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (stat("/etc/crontabs", &s) && mkdir("/etc/crontabs", 0755)) + return rpc_errno_status(); + + if (!(f = fopen("/etc/crontabs/root", "w"))) + return rpc_errno_status(); + + fwrite(blobmsg_data(tb[RPC_D_DATA]), + blobmsg_data_len(tb[RPC_D_DATA]) - 1, 1, f); + + fclose(f); + return 0; +} + +static int +rpc_luci2_sshkeys_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + void *c; + char *p, line[4096]; + + if (!(f = fopen("/etc/dropbear/authorized_keys", "r"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "keys"); + + while (fgets(line, sizeof(line) - 1, f)) + { + for (p = line + strlen(line) - 1; (p > line) && isspace(*p); p--) + *p = 0; + + for (p = line; isspace(*p); p++) + *p = 0; + + if (*p) + blobmsg_add_string(&buf, NULL, p); + } + + blobmsg_close_array(&buf, c); + fclose(f); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_sshkeys_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + int rem; + struct blob_attr *cur, *tb[__RPC_K_MAX]; + + blobmsg_parse(rpc_sshkey_policy, __RPC_K_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_K_KEYS]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (!(f = fopen("/etc/dropbear/authorized_keys", "w"))) + return rpc_errno_status(); + + blobmsg_for_each_attr(cur, tb[RPC_K_KEYS], rem) + { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + fwrite(blobmsg_data(cur), blobmsg_data_len(cur) - 1, 1, f); + fwrite("\n", 1, 1, f); + } + + fclose(f); + return 0; +} + +static int +rpc_luci2_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]; + + 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("/usr/bin/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); + } + + chdir("/"); + + if (execl("/usr/bin/passwd", "/usr/bin/passwd", + blobmsg_data(tb[RPC_P_USER]), NULL)) + return rpc_errno_status(); + + default: + close(fds[0]); + + write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), + blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); + write(fds[1], "\n", 1); + + usleep(100 * 1000); + + write(fds[1], blobmsg_data(tb[RPC_P_PASSWORD]), + blobmsg_data_len(tb[RPC_P_PASSWORD]) - 1); + write(fds[1], "\n", 1); + + close(fds[1]); + + waitpid(pid, NULL, 0); + + return 0; + } +} + +static int +rpc_luci2_led_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + DIR *d; + FILE *f; + void *list, *led, *trigger; + char *p, *active_trigger, line[512]; + struct dirent *e; + + if (!(d = opendir("/sys/class/leds"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + list = blobmsg_open_array(&buf, "leds"); + + while ((e = readdir(d)) != NULL) + { + snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/trigger", + e->d_name); + + if (!(f = fopen(line, "r"))) + continue; + + led = blobmsg_open_table(&buf, NULL); + + blobmsg_add_string(&buf, "name", e->d_name); + + if (fgets(line, sizeof(line) - 1, f)) + { + trigger = blobmsg_open_array(&buf, "triggers"); + + for (p = strtok(line, " \n"), active_trigger = NULL; + p != NULL; + p = strtok(NULL, " \n")) + { + if (*p == '[') + { + *(p + strlen(p) - 1) = 0; + *p++ = 0; + active_trigger = p; + } + + blobmsg_add_string(&buf, NULL, p); + } + + blobmsg_close_array(&buf, trigger); + + if (active_trigger) + blobmsg_add_string(&buf, "active_trigger", active_trigger); + } + + fclose(f); + + snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/brightness", + e->d_name); + + if ((f = fopen(line, "r")) != NULL) + { + if (fgets(line, sizeof(line) - 1, f)) + blobmsg_add_u32(&buf, "brightness", atoi(line)); + + fclose(f); + } + + snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/max_brightness", + e->d_name); + + if ((f = fopen(line, "r")) != NULL) + { + if (fgets(line, sizeof(line) - 1, f)) + blobmsg_add_u32(&buf, "max_brightness", atoi(line)); + + fclose(f); + } + + blobmsg_close_table(&buf, led); + } + + closedir(d); + + blobmsg_close_array(&buf, list); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_usb_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + DIR *d; + FILE *f; + int i; + void *list, *device; + char *p, line[512]; + struct stat s; + struct dirent *e; + + const char *attributes[] = { + "manufacturer", "vendor_name", "s", + "product", "product_name", "s", + "idVendor", "vendor_id", "x", + "idProduct", "product_id", "x", + "serial", "serial", "s", + "speed", "speed", "d", + }; + + if (!(d = opendir("/sys/bus/usb/devices"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + list = blobmsg_open_array(&buf, "devices"); + + while ((e = readdir(d)) != NULL) + { + if (e->d_name[0] < '0' || e->d_name[0] > '9') + continue; + + snprintf(line, sizeof(line) - 1, + "/sys/bus/usb/devices/%s/%s", e->d_name, attributes[0]); + + if (stat(line, &s)) + continue; + + device = blobmsg_open_table(&buf, NULL); + + blobmsg_add_string(&buf, "name", e->d_name); + + for (i = 0; i < sizeof(attributes) / sizeof(attributes[0]); i += 3) + { + snprintf(line, sizeof(line) - 1, + "/sys/bus/usb/devices/%s/%s", e->d_name, attributes[i]); + + if (!(f = fopen(line, "r"))) + continue; + + if (fgets(line, sizeof(line) - 1, f)) + { + switch (*attributes[i+2]) + { + case 'x': + blobmsg_add_u32(&buf, attributes[i+1], + strtoul(line, NULL, 16)); + break; + + case 'd': + blobmsg_add_u32(&buf, attributes[i+1], + strtoul(line, NULL, 10)); + break; + + default: + if ((p = strchr(line, '\n')) != NULL) + while (p > line && isspace(*p)) + *p-- = 0; + + blobmsg_add_string(&buf, attributes[i+1], line); + break; + } + } + + fclose(f); + } + + blobmsg_close_table(&buf, device); + } + + closedir(d); + + blobmsg_close_array(&buf, list); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_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_luci2_upgrade_start(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + return 0; +} + +static int +rpc_luci2_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_luci2_backup_restore(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", "--restore-backup", + "/tmp/backup.tar.gz", NULL }; + + return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); +} + +static int +rpc_luci2_backup_clean(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + if (unlink("/tmp/backup.tar.gz")) + return rpc_errno_status(); + + return 0; +} + +static int +rpc_luci2_backup_config_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + char conf[2048] = { 0 }; + + if (!(f = fopen("/etc/sysupgrade.conf", "r"))) + return rpc_errno_status(); + + fread(conf, sizeof(conf) - 1, 1, f); + fclose(f); + + blob_buf_init(&buf, 0); + blobmsg_add_string(&buf, "config", conf); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_backup_config_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + struct blob_attr *tb[__RPC_D_MAX]; + + blobmsg_parse(rpc_data_policy, __RPC_D_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_D_DATA]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (blobmsg_data_len(tb[RPC_D_DATA]) >= 2048) + return UBUS_STATUS_NOT_SUPPORTED; + + if (!(f = fopen("/etc/sysupgrade.conf", "w"))) + return rpc_errno_status(); + + fwrite(blobmsg_data(tb[RPC_D_DATA]), + blobmsg_data_len(tb[RPC_D_DATA]) - 1, 1, f); + + fclose(f); + return 0; +} + +struct backup_state { + bool open; + void *array; +}; + +static int +backup_parse_list(struct blob_buf *blob, char *buf, int len, void *priv) +{ + struct backup_state *s = priv; + char *nl = strchr(buf, '\n'); + + if (!nl) + return 0; + + if (!s->open) + { + s->open = true; + s->array = blobmsg_open_array(blob, "files"); + } + + *nl = 0; + blobmsg_add_string(blob, NULL, buf); + + return (nl - buf + 1); +} + +static int +backup_finish_list(struct blob_buf *blob, int status, void *priv) +{ + struct backup_state *s = priv; + + if (!s->open) + return UBUS_STATUS_NO_DATA; + + blobmsg_close_array(blob, s->array); + + return UBUS_STATUS_OK; +} + +static int +rpc_luci2_backup_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct backup_state *state = NULL; + const char *cmd[3] = { "sysupgrade", "--list-backup", NULL }; + + state = malloc(sizeof(*state)); + + if (!state) + return UBUS_STATUS_UNKNOWN_ERROR; + + memset(state, 0, sizeof(*state)); + + return ops->exec(cmd, NULL, backup_parse_list, NULL, backup_finish_list, + state, ctx, req); +} + +static int +rpc_luci2_reset_test(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *mtd; + struct stat s; + char line[64] = { 0 }; + bool supported = false; + + if (!stat("/sbin/mtd", &s) && (s.st_mode & S_IXUSR)) + { + if ((mtd = fopen("/proc/mtd", "r")) != NULL) + { + while (fgets(line, sizeof(line) - 1, mtd)) + { + if (strstr(line, "\"rootfs_data\"")) + { + supported = true; + break; + } + } + + fclose(mtd); + } + } + + blob_buf_init(&buf, 0); + blobmsg_add_u8(&buf, "supported", supported); + + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_reset_start(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + switch (fork()) + { + case -1: + return rpc_errno_status(); + + case 0: + uloop_done(); + + chdir("/"); + + close(0); + close(1); + close(2); + + sleep(1); + + execl("/sbin/mtd", "/sbin/mtd", "-r", "erase", "rootfs_data", NULL); + + return rpc_errno_status(); + + default: + return 0; + } +} + +static int +rpc_luci2_reboot(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + switch (fork()) + { + case -1: + return rpc_errno_status(); + + case 0: + chdir("/"); + + close(0); + close(1); + close(2); + + sleep(1); + + execl("/sbin/reboot", "/sbin/reboot", NULL); + + return rpc_errno_status(); + + default: + return 0; + } +} + + +static FILE * +dnsmasq_leasefile(void) +{ + FILE *leases = NULL; + struct uci_package *p; + struct uci_element *e; + struct uci_section *s; + struct uci_ptr ptr = { + .package = "dhcp", + .section = NULL, + .option = "leasefile" + }; + + uci_load(cursor, ptr.package, &p); + + if (!p) + return NULL; + + uci_foreach_element(&p->sections, e) + { + s = uci_to_section(e); + + if (strcmp(s->type, "dnsmasq")) + continue; + + ptr.section = e->name; + uci_lookup_ptr(cursor, &ptr, NULL, true); + break; + } + + if (ptr.o && ptr.o->type == UCI_TYPE_STRING) + leases = fopen(ptr.o->v.string, "r"); + + uci_unload(cursor, p); + + return leases; +} + +static int +rpc_luci2_network_leases(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *leases; + void *c, *d; + char line[128]; + char *ts, *mac, *addr, *name; + time_t now = time(NULL); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "leases"); + + leases = dnsmasq_leasefile(); + + if (!leases) + goto out; + + while (fgets(line, sizeof(line) - 1, leases)) + { + ts = strtok(line, " \t"); + mac = strtok(NULL, " \t"); + addr = strtok(NULL, " \t"); + name = strtok(NULL, " \t"); + + if (!ts || !mac || !addr || !name) + continue; + + if (strchr(addr, ':')) + continue; + + d = blobmsg_open_table(&buf, NULL); + + blobmsg_add_u32(&buf, "expires", atoi(ts) - now); + blobmsg_add_string(&buf, "macaddr", mac); + blobmsg_add_string(&buf, "ipaddr", addr); + + if (strcmp(name, "*")) + blobmsg_add_string(&buf, "hostname", name); + + blobmsg_close_table(&buf, d); + } + + fclose(leases); + +out: + blobmsg_close_array(&buf, c); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_network_leases6(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *leases; + void *c, *d; + char line[128]; + char *ts, *mac, *addr, *name, *duid; + time_t now = time(NULL); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "leases"); + + leases = fopen("/tmp/hosts/6relayd", "r"); + + if (leases) + { + while (fgets(line, sizeof(line) - 1, leases)) + { + if (strncmp(line, "# ", 2)) + continue; + + strtok(line + 2, " \t"); /* iface */ + + duid = strtok(NULL, " \t"); + + strtok(NULL, " \t"); /* iaid */ + + name = strtok(NULL, " \t"); + ts = strtok(NULL, " \t"); + + strtok(NULL, " \t"); /* id */ + strtok(NULL, " \t"); /* length */ + + addr = strtok(NULL, " \t\n"); + + if (!addr) + continue; + + d = blobmsg_open_table(&buf, NULL); + + blobmsg_add_u32(&buf, "expires", atoi(ts) - now); + blobmsg_add_string(&buf, "duid", duid); + blobmsg_add_string(&buf, "ip6addr", addr); + + if (strcmp(name, "-")) + blobmsg_add_string(&buf, "hostname", name); + + blobmsg_close_array(&buf, d); + } + + fclose(leases); + } + else + { + leases = dnsmasq_leasefile(); + + if (!leases) + goto out; + + while (fgets(line, sizeof(line) - 1, leases)) + { + ts = strtok(line, " \t"); + mac = strtok(NULL, " \t"); + addr = strtok(NULL, " \t"); + name = strtok(NULL, " \t"); + duid = strtok(NULL, " \t\n"); + + if (!ts || !mac || !addr || !duid) + continue; + + if (!strchr(addr, ':')) + continue; + + d = blobmsg_open_table(&buf, NULL); + + blobmsg_add_u32(&buf, "expires", atoi(ts) - now); + blobmsg_add_string(&buf, "macaddr", mac); + blobmsg_add_string(&buf, "ip6addr", addr); + + if (strcmp(name, "*")) + blobmsg_add_string(&buf, "hostname", name); + + if (strcmp(duid, "*")) + blobmsg_add_string(&buf, "duid", name); + + blobmsg_close_table(&buf, d); + } + + fclose(leases); + } + +out: + blobmsg_close_array(&buf, c); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_network_ct_count(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + char line[128]; + + blob_buf_init(&buf, 0); + + if ((f = fopen("/proc/sys/net/netfilter/nf_conntrack_count", "r")) != NULL) + { + if (fgets(line, sizeof(line) - 1, f)) + blobmsg_add_u32(&buf, "count", atoi(line)); + + fclose(f); + } + + if ((f = fopen("/proc/sys/net/netfilter/nf_conntrack_max", "r")) != NULL) + { + if (fgets(line, sizeof(line) - 1, f)) + blobmsg_add_u32(&buf, "limit", atoi(line)); + + fclose(f); + } + + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_network_ct_table(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + int i; + void *c, *d; + char *p, line[512]; + bool seen[6]; + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "entries"); + + if ((f = fopen("/proc/net/nf_conntrack", "r")) != NULL) + { + while (fgets(line, sizeof(line) - 1, f)) + { + d = blobmsg_open_table(&buf, NULL); + memset(seen, 0, sizeof(seen)); + + for (i = 0, p = strtok(line, " "); p; i++, p = strtok(NULL, " ")) + { + if (i == 0) + blobmsg_add_u8(&buf, "ipv6", !strcmp(p, "ipv6")); + else if (i == 3) + blobmsg_add_u32(&buf, "protocol", atoi(p)); + else if (i == 4) + blobmsg_add_u32(&buf, "expires", atoi(p)); + else if (i >= 5) + { + if (*p == '[') + continue; + + if (!seen[0] && !strncmp(p, "src=", 4)) + { + blobmsg_add_string(&buf, "src", p + 4); + seen[0] = true; + } + else if (!seen[1] && !strncmp(p, "dst=", 4)) + { + blobmsg_add_string(&buf, "dest", p + 4); + seen[1] = true; + } + else if (!seen[2] && !strncmp(p, "sport=", 6)) + { + blobmsg_add_u32(&buf, "sport", atoi(p + 6)); + seen[2] = true; + } + else if (!seen[3] && !strncmp(p, "dport=", 6)) + { + blobmsg_add_u32(&buf, "dport", atoi(p + 6)); + seen[3] = true; + } + else if (!strncmp(p, "packets=", 8)) + { + blobmsg_add_u32(&buf, + seen[4] ? "tx_packets" : "rx_packets", + atoi(p + 8)); + seen[4] = true; + } + else if (!strncmp(p, "bytes=", 6)) + { + blobmsg_add_u32(&buf, + seen[5] ? "tx_bytes" : "rx_bytes", + atoi(p + 6)); + seen[5] = true; + } + } + } + + blobmsg_close_table(&buf, d); + } + + fclose(f); + } + + blobmsg_close_array(&buf, c); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static int +rpc_luci2_network_arp_table(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + void *c, *d; + char *addr, *mac, *dev, line[128]; + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "entries"); + + if ((f = fopen("/proc/net/arp", "r")) != NULL) + { + /* skip header line */ + fgets(line, sizeof(line) - 1, f); + + while (fgets(line, sizeof(line) - 1, f)) + { + addr = strtok(line, " \t"); + + strtok(NULL, " \t"); /* HW type */ + strtok(NULL, " \t"); /* Flags */ + + mac = strtok(NULL, " \t"); + + strtok(NULL, " \t"); /* Mask */ + + dev = strtok(NULL, " \t\n"); + + if (!dev) + continue; + + d = blobmsg_open_table(&buf, NULL); + blobmsg_add_string(&buf, "ipaddr", addr); + blobmsg_add_string(&buf, "macaddr", mac); + blobmsg_add_string(&buf, "device", dev); + blobmsg_close_table(&buf, d); + } + + fclose(f); + } + + blobmsg_close_array(&buf, c); + ubus_send_reply(ctx, req, buf.head); + + return 0; +} + +static void +put_hexaddr(const char *name, const char *s, const char *m) +{ + int bits; + struct in_addr a; + char as[sizeof("255.255.255.255/32\0")]; + + a.s_addr = strtoul(s, NULL, 16); + inet_ntop(AF_INET, &a, as, sizeof(as)); + + if (m) + { + for (a.s_addr = ntohl(strtoul(m, NULL, 16)), bits = 0; + a.s_addr & 0x80000000; + a.s_addr <<= 1) + bits++; + + sprintf(as + strlen(as), "/%u", bits); + } + + blobmsg_add_string(&buf, name, as); +} + +static int +rpc_luci2_network_routes(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *routes; + void *c, *d; + char *dst, *dmask, *next, *metric, *device; + char line[256]; + unsigned int n; + + if (!(routes = fopen("/proc/net/route", "r"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "routes"); + + /* skip header line */ + fgets(line, sizeof(line) - 1, routes); + + while (fgets(line, sizeof(line) - 1, routes)) + { + device = strtok(line, "\t "); + dst = strtok(NULL, "\t "); + next = strtok(NULL, "\t "); + + strtok(NULL, "\t "); /* flags */ + strtok(NULL, "\t "); /* refcount */ + strtok(NULL, "\t "); /* usecount */ + + metric = strtok(NULL, "\t "); + dmask = strtok(NULL, "\t "); + + if (!dmask) + continue; + + d = blobmsg_open_table(&buf, NULL); + + put_hexaddr("target", dst, dmask); + put_hexaddr("nexthop", next, NULL); + + n = strtoul(metric, NULL, 10); + blobmsg_add_u32(&buf, "metric", n); + + blobmsg_add_string(&buf, "device", device); + + blobmsg_close_table(&buf, d); + } + + blobmsg_close_array(&buf, c); + fclose(routes); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static void +put_hex6addr(const char *name, const char *s, const char *m) +{ + int i; + struct in6_addr a; + char as[INET6_ADDRSTRLEN + sizeof("/128")]; + +#define hex(x) \ + (((x) <= '9') ? ((x) - '0') : \ + (((x) <= 'F') ? ((x) - 'A' + 10) : \ + ((x) - 'a' + 10))) + + for (i = 0; i < 16; i++, s += 2) + a.s6_addr[i] = (16 * hex(*s)) + hex(*(s+1)); + + inet_ntop(AF_INET6, &a, as, sizeof(as)); + + if (m) + sprintf(as + strlen(as), "/%lu", strtoul(m, NULL, 16)); + + blobmsg_add_string(&buf, name, as); +} + +static int +rpc_luci2_network_routes6(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *routes; + void *c, *d; + char *src, *smask, *dst, *dmask, *next, *metric, *flags, *device; + char line[256]; + unsigned int n; + + if (!(routes = fopen("/proc/net/ipv6_route", "r"))) + return rpc_errno_status(); + + blob_buf_init(&buf, 0); + c = blobmsg_open_array(&buf, "routes"); + + while (fgets(line, sizeof(line) - 1, routes)) + { + dst = strtok(line, " "); + dmask = strtok(NULL, " "); + src = strtok(NULL, " "); + smask = strtok(NULL, " "); + next = strtok(NULL, " "); + metric = strtok(NULL, " "); + + strtok(NULL, " "); /* refcount */ + strtok(NULL, " "); /* usecount */ + + flags = strtok(NULL, " "); + device = strtok(NULL, " \n"); + + if (!device) + continue; + + n = strtoul(flags, NULL, 16); + + if (!(n & 1)) + continue; + + d = blobmsg_open_table(&buf, NULL); + + put_hex6addr("target", dst, dmask); + put_hex6addr("source", src, smask); + put_hex6addr("nexthop", next, NULL); + + n = strtoul(metric, NULL, 16); + blobmsg_add_u32(&buf, "metric", n); + + blobmsg_add_string(&buf, "device", device); + + blobmsg_close_table(&buf, d); + } + + blobmsg_close_array(&buf, c); + fclose(routes); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + + +struct opkg_state { + int cur_offset; + int cur_count; + int req_offset; + int req_count; + int total; + bool open; + void *array; +}; + +static int +opkg_parse_list(struct blob_buf *blob, char *buf, int len, void *priv) +{ + struct opkg_state *s = priv; + + char *ptr, *last; + char *nl = strchr(buf, '\n'); + char *name = NULL, *vers = NULL, *desc = NULL; + void *c; + + if (!nl) + return 0; + + s->total++; + + if (s->cur_offset++ < s->req_offset) + goto skip; + + if (s->cur_count++ >= s->req_count) + goto skip; + + if (!s->open) + { + s->open = true; + s->array = blobmsg_open_array(blob, "packages"); + } + + for (ptr = buf, last = buf, *nl = 0; ptr <= nl; ptr++) + { + if (!*ptr || (*ptr == ' ' && *(ptr+1) == '-' && *(ptr+2) == ' ')) + { + if (!name) + { + name = last; + last = ptr + 3; + *ptr = 0; + ptr += 2; + } + else if (!vers) + { + vers = last; + desc = *ptr ? (ptr + 3) : NULL; + *ptr = 0; + break; + } + } + } + + if (name && vers) + { + c = blobmsg_open_array(blob, NULL); + + blobmsg_add_string(blob, NULL, name); + blobmsg_add_string(blob, NULL, vers); + + if (desc && *desc) + blobmsg_add_string(blob, NULL, desc); + + blobmsg_close_array(blob, c); + } + +skip: + return (nl - buf + 1); +} + +static int +opkg_finish_list(struct blob_buf *blob, int status, void *priv) +{ + struct opkg_state *s = priv; + + if (!s->open) + return UBUS_STATUS_NO_DATA; + + blobmsg_close_array(blob, s->array); + blobmsg_add_u32(blob, "total", s->total); + + return UBUS_STATUS_OK; +} + +static int +opkg_exec_list(const char *action, struct blob_attr *msg, + struct ubus_context *ctx, struct ubus_request_data *req) +{ + struct opkg_state *state = NULL; + struct blob_attr *tb[__RPC_OM_MAX]; + const char *cmd[5] = { "opkg", action, "-nocase", NULL, NULL }; + + blobmsg_parse(rpc_opkg_match_policy, __RPC_OM_MAX, tb, + blob_data(msg), blob_len(msg)); + + state = malloc(sizeof(*state)); + + if (!state) + return UBUS_STATUS_UNKNOWN_ERROR; + + memset(state, 0, sizeof(*state)); + + if (tb[RPC_OM_PATTERN]) + cmd[3] = blobmsg_data(tb[RPC_OM_PATTERN]); + + if (tb[RPC_OM_LIMIT]) + state->req_count = blobmsg_get_u32(tb[RPC_OM_LIMIT]); + + if (tb[RPC_OM_OFFSET]) + state->req_offset = blobmsg_get_u32(tb[RPC_OM_OFFSET]); + + if (state->req_offset < 0) + state->req_offset = 0; + + if (state->req_count <= 0 || state->req_count > 100) + state->req_count = 100; + + return ops->exec(cmd, NULL, opkg_parse_list, NULL, opkg_finish_list, + state, ctx, req); +} + + +static int +rpc_luci2_opkg_list(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + return opkg_exec_list("list", msg, ctx, req); +} + +static int +rpc_luci2_opkg_list_installed(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + return opkg_exec_list("list-installed", msg, ctx, req); +} + +static int +rpc_luci2_opkg_find(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + return opkg_exec_list("find", msg, ctx, req); +} + +static int +rpc_luci2_opkg_update(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + const char *cmd[3] = { "opkg", "update", NULL }; + return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); +} + +static int +rpc_luci2_opkg_install(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_OP_MAX]; + const char *cmd[5] = { "opkg", "--force-overwrite", + "install", NULL, NULL }; + + blobmsg_parse(rpc_opkg_package_policy, __RPC_OP_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_OP_PACKAGE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + cmd[3] = blobmsg_data(tb[RPC_OP_PACKAGE]); + + return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); +} + +static int +rpc_luci2_opkg_remove(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_OP_MAX]; + const char *cmd[5] = { "opkg", "--force-removal-of-dependent-packages", + "remove", NULL, NULL }; + + blobmsg_parse(rpc_opkg_package_policy, __RPC_OP_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_OP_PACKAGE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + cmd[3] = blobmsg_data(tb[RPC_OP_PACKAGE]); + + return ops->exec(cmd, NULL, NULL, NULL, NULL, NULL, ctx, req); +} + +static int +rpc_luci2_opkg_config_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + char conf[2048] = { 0 }; + + if (!(f = fopen("/etc/opkg.conf", "r"))) + return rpc_errno_status(); + + fread(conf, sizeof(conf) - 1, 1, f); + fclose(f); + + blob_buf_init(&buf, 0); + blobmsg_add_string(&buf, "config", conf); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + +static int +rpc_luci2_opkg_config_set(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + FILE *f; + struct blob_attr *tb[__RPC_D_MAX]; + + blobmsg_parse(rpc_data_policy, __RPC_D_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_D_DATA]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (blobmsg_data_len(tb[RPC_D_DATA]) >= 2048) + return UBUS_STATUS_NOT_SUPPORTED; + + if (!(f = fopen("/etc/opkg.conf", "w"))) + return rpc_errno_status(); + + fwrite(blobmsg_data(tb[RPC_D_DATA]), + blobmsg_data_len(tb[RPC_D_DATA]) - 1, 1, f); + + fclose(f); + return 0; +} + + +static bool +menu_access(struct blob_attr *sid, struct blob_attr *acls, struct blob_buf *e) +{ + int rem; + struct blob_attr *acl; + bool rv = true; + void *c; + + c = blobmsg_open_table(e, "write"); + + blobmsg_for_each_attr(acl, acls, rem) + { + if (!ops->session_access(blobmsg_data(sid), "luci-ui", + blobmsg_data(acl), "read")) + { + rv = false; + break; + } + + blobmsg_add_u8(e, blobmsg_data(acl), + ops->session_access(blobmsg_data(sid), "luci-ui", + blobmsg_data(acl), "write")); + } + + blobmsg_close_table(e, c); + + return rv; +} + +static int +rpc_luci2_ui_menu(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + int i, rem, rem2; + glob_t gl; + struct blob_buf menu = { 0 }; + struct blob_buf item = { 0 }; + struct blob_attr *entry, *attr; + struct blob_attr *tb[__RPC_MENU_MAX]; + bool access; + void *c, *d; + + blobmsg_parse(rpc_menu_policy, __RPC_MENU_MAX, tb, + blob_data(msg), blob_len(msg)); + + if (!tb[RPC_MENU_SESSION]) + return UBUS_STATUS_INVALID_ARGUMENT; + + + blob_buf_init(&buf, 0); + c = blobmsg_open_table(&buf, "menu"); + + if (!glob(RPC_LUCI2_MENU_FILES, 0, NULL, &gl)) + { + for (i = 0; i < gl.gl_pathc; i++) + { + blob_buf_init(&menu, 0); + + if (!blobmsg_add_json_from_file(&menu, gl.gl_pathv[i])) + goto skip; + + blob_for_each_attr(entry, menu.head, rem) + { + access = true; + + blob_buf_init(&item, 0); + d = blobmsg_open_table(&item, blobmsg_name(entry)); + + blobmsg_for_each_attr(attr, entry, rem2) + { + if (blob_id(attr) == BLOBMSG_TYPE_ARRAY && + !strcmp(blobmsg_name(attr), "acls")) + access = menu_access(tb[RPC_MENU_SESSION], attr, &item); + else + blobmsg_add_blob(&item, attr); + } + + blobmsg_close_table(&item, d); + + if (access) + blob_for_each_attr(attr, item.head, rem2) + blobmsg_add_blob(&buf, attr); + + blob_buf_free(&item); + } + +skip: + blob_buf_free(&menu); + } + + globfree(&gl); + } + + blobmsg_close_table(&buf, c); + + ubus_send_reply(ctx, req, buf.head); + return 0; +} + + +static int +rpc_luci2_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx) +{ + int rv = 0; + + static const struct ubus_method luci2_system_methods[] = { + UBUS_METHOD_NOARG("syslog", rpc_luci2_system_log), + UBUS_METHOD_NOARG("dmesg", rpc_luci2_system_dmesg), + UBUS_METHOD_NOARG("diskfree", rpc_luci2_system_diskfree), + UBUS_METHOD_NOARG("process_list", rpc_luci2_process_list), + UBUS_METHOD("process_signal", rpc_luci2_process_signal, + rpc_signal_policy), + UBUS_METHOD_NOARG("init_list", rpc_luci2_init_list), + UBUS_METHOD("init_action", rpc_luci2_init_action, + rpc_init_policy), + UBUS_METHOD_NOARG("rclocal_get", rpc_luci2_rclocal_get), + UBUS_METHOD("rclocal_set", rpc_luci2_rclocal_set, + rpc_data_policy), + UBUS_METHOD_NOARG("crontab_get", rpc_luci2_crontab_get), + UBUS_METHOD("crontab_set", rpc_luci2_crontab_set, + rpc_data_policy), + UBUS_METHOD_NOARG("sshkeys_get", rpc_luci2_sshkeys_get), + UBUS_METHOD("sshkeys_set", rpc_luci2_sshkeys_set, + rpc_sshkey_policy), + UBUS_METHOD("password_set", rpc_luci2_password_set, + rpc_password_policy), + UBUS_METHOD_NOARG("led_list", rpc_luci2_led_list), + UBUS_METHOD_NOARG("usb_list", rpc_luci2_usb_list), + UBUS_METHOD_NOARG("upgrade_test", rpc_luci2_upgrade_test), + UBUS_METHOD("upgrade_start", rpc_luci2_upgrade_start, + rpc_upgrade_policy), + UBUS_METHOD_NOARG("upgrade_clean", rpc_luci2_upgrade_clean), + UBUS_METHOD_NOARG("backup_restore", rpc_luci2_backup_restore), + UBUS_METHOD_NOARG("backup_clean", rpc_luci2_backup_clean), + UBUS_METHOD_NOARG("backup_config_get", rpc_luci2_backup_config_get), + UBUS_METHOD("backup_config_set", rpc_luci2_backup_config_set, + rpc_data_policy), + UBUS_METHOD_NOARG("backup_list", rpc_luci2_backup_list), + UBUS_METHOD_NOARG("reset_test", rpc_luci2_reset_test), + UBUS_METHOD_NOARG("reset_start", rpc_luci2_reset_start), + UBUS_METHOD_NOARG("reboot", rpc_luci2_reboot) + }; + + static struct ubus_object_type luci2_system_type = + UBUS_OBJECT_TYPE("luci-rpc-luci2-system", luci2_system_methods); + + static struct ubus_object system_obj = { + .name = "luci2.system", + .type = &luci2_system_type, + .methods = luci2_system_methods, + .n_methods = ARRAY_SIZE(luci2_system_methods), + }; + + + static const struct ubus_method luci2_network_methods[] = { + UBUS_METHOD_NOARG("conntrack_count", rpc_luci2_network_ct_count), + UBUS_METHOD_NOARG("conntrack_table", rpc_luci2_network_ct_table), + UBUS_METHOD_NOARG("arp_table", rpc_luci2_network_arp_table), + UBUS_METHOD_NOARG("dhcp_leases", rpc_luci2_network_leases), + UBUS_METHOD_NOARG("dhcp6_leases", rpc_luci2_network_leases6), + UBUS_METHOD_NOARG("routes", rpc_luci2_network_routes), + UBUS_METHOD_NOARG("routes6", rpc_luci2_network_routes6), + }; + + static struct ubus_object_type luci2_network_type = + UBUS_OBJECT_TYPE("luci-rpc-luci2-network", luci2_network_methods); + + static struct ubus_object network_obj = { + .name = "luci2.network", + .type = &luci2_network_type, + .methods = luci2_network_methods, + .n_methods = ARRAY_SIZE(luci2_network_methods), + }; + + + static const struct ubus_method luci2_opkg_methods[] = { + UBUS_METHOD("list", rpc_luci2_opkg_list, + rpc_opkg_match_policy), + UBUS_METHOD("list_installed", rpc_luci2_opkg_list_installed, + rpc_opkg_match_policy), + UBUS_METHOD("find", rpc_luci2_opkg_find, + rpc_opkg_match_policy), + UBUS_METHOD("install", rpc_luci2_opkg_install, + rpc_opkg_package_policy), + UBUS_METHOD("remove", rpc_luci2_opkg_remove, + rpc_opkg_package_policy), + UBUS_METHOD_NOARG("update", rpc_luci2_opkg_update), + UBUS_METHOD_NOARG("config_get", rpc_luci2_opkg_config_get), + UBUS_METHOD("config_set", rpc_luci2_opkg_config_set, + rpc_data_policy) + }; + + static struct ubus_object_type luci2_opkg_type = + UBUS_OBJECT_TYPE("luci-rpc-luci2-network", luci2_opkg_methods); + + static struct ubus_object opkg_obj = { + .name = "luci2.opkg", + .type = &luci2_opkg_type, + .methods = luci2_opkg_methods, + .n_methods = ARRAY_SIZE(luci2_opkg_methods), + }; + + + static const struct ubus_method luci2_ui_methods[] = { + UBUS_METHOD_NOARG("menu", rpc_luci2_ui_menu) + }; + + static struct ubus_object_type luci2_ui_type = + UBUS_OBJECT_TYPE("luci-rpc-luci2-ui", luci2_ui_methods); + + static struct ubus_object ui_obj = { + .name = "luci2.ui", + .type = &luci2_ui_type, + .methods = luci2_ui_methods, + .n_methods = ARRAY_SIZE(luci2_ui_methods), + }; + + cursor = uci_alloc_context(); + + if (!cursor) + return UBUS_STATUS_UNKNOWN_ERROR; + + ops = o; + + rv |= ubus_add_object(ctx, &system_obj); + rv |= ubus_add_object(ctx, &network_obj); + rv |= ubus_add_object(ctx, &opkg_obj); + rv |= ubus_add_object(ctx, &ui_obj); + + return rv; +} + +const struct rpc_plugin rpc_plugin = { + .init = rpc_luci2_api_init +}; |