summaryrefslogtreecommitdiff
path: root/luci2/src
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2013-09-09 17:01:22 +0200
committerJo-Philipp Wich <jow@openwrt.org>2013-09-09 17:01:22 +0200
commit2f0e65dd27549ef4fde5b18588083968a59b66bd (patch)
tree371bf5fd09ddf206442848a95796d41bf81f95b8 /luci2/src
downloadluci2-ui-2f0e65dd27549ef4fde5b18588083968a59b66bd.tar.gz
Initial commit of LuCI2
Diffstat (limited to 'luci2/src')
-rw-r--r--luci2/src/CMakeLists.txt6
-rw-r--r--luci2/src/io/CMakeLists.txt29
-rw-r--r--luci2/src/io/login.c281
-rw-r--r--luci2/src/io/login.h32
-rw-r--r--luci2/src/io/main.c711
-rw-r--r--luci2/src/io/multipart_parser.c309
-rw-r--r--luci2/src/io/multipart_parser.h48
-rw-r--r--luci2/src/rpcd/CMakeLists.txt18
-rw-r--r--luci2/src/rpcd/luci2.c2224
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
+};