plugin: expose rpc_session_create_cb() and rpc_session_destroy_cb() in plugin ops...
[project/rpcd.git] / luci2.c
diff --git a/luci2.c b/luci2.c
index db4aac3..276c60d 100644 (file)
--- a/luci2.c
+++ b/luci2.c
@@ -1,5 +1,5 @@
 /*
- * luci-rpcd - LuCI UBUS RPC server
+ * rpcd - UBUS RPC server
  *
  *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
  *
 #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 "luci2.h"
+#include "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;
@@ -57,6 +72,15 @@ static const struct blobmsg_policy rpc_init_policy[__RPC_I_MAX] = {
 };
 
 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
 };
@@ -76,6 +100,47 @@ static const struct blobmsg_policy rpc_password_policy[__RPC_P_MAX] = {
        [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)
@@ -228,6 +293,39 @@ rpc_luci2_system_dmesg(struct ubus_context *ctx, struct ubus_object *obj,
 }
 
 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)
@@ -463,6 +561,100 @@ rpc_luci2_init_action(struct ubus_context *ctx, struct ubus_object *obj,
 }
 
 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)
@@ -600,149 +792,575 @@ rpc_luci2_password_set(struct ubus_context *ctx, struct ubus_object *obj,
        }
 }
 
-
-static FILE *
-dnsmasq_leasefile(void)
+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)
 {
-       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"
-       };
+       DIR *d;
+       FILE *f;
+       void *list, *led, *trigger;
+       char *p, *active_trigger, line[512];
+       struct dirent *e;
 
-       uci_load(cursor, ptr.package, &p);
+       if (!(d = opendir("/sys/class/leds")))
+               return rpc_errno_status();
 
-       if (!p)
-               return NULL;
+       blob_buf_init(&buf, 0);
+       list = blobmsg_open_array(&buf, "leds");
 
-       uci_foreach_element(&p->sections, e)
+       while ((e = readdir(d)) != NULL)
        {
-               s = uci_to_section(e);
+               snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/trigger",
+                        e->d_name);
 
-               if (strcmp(s->type, "dnsmasq"))
+               if (!(f = fopen(line, "r")))
                        continue;
 
-               ptr.section = e->name;
-               uci_lookup_ptr(cursor, &ptr, NULL, true);
-               break;
-       }
+               led = blobmsg_open_table(&buf, NULL);
 
-       if (ptr.o && ptr.o->type == UCI_TYPE_STRING)
-               leases = fopen(ptr.o->v.string, "r");
+               blobmsg_add_string(&buf, "name", e->d_name);
 
-       uci_unload(cursor, p);
+               if (fgets(line, sizeof(line) - 1, f))
+               {
+                       trigger = blobmsg_open_array(&buf, "triggers");
 
-       return leases;
-}
+                       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;
+                               }
 
-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);
+                               blobmsg_add_string(&buf, NULL, p);
+                       }
 
-       blob_buf_init(&buf, 0);
-       c = blobmsg_open_array(&buf, "leases");
+                       blobmsg_close_array(&buf, trigger);
 
-       leases = dnsmasq_leasefile();
+                       if (active_trigger)
+                               blobmsg_add_string(&buf, "active_trigger", active_trigger);
+               }
 
-       if (!leases)
-               goto out;
+               fclose(f);
 
-       while (fgets(line, sizeof(line) - 1, leases))
-       {
-               ts   = strtok(line, " \t");
-               mac  = strtok(NULL, " \t");
-               addr = strtok(NULL, " \t");
-               name = strtok(NULL, " \t");
+               snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/brightness",
+                        e->d_name);
 
-               if (!ts || !mac || !addr || !name)
-                       continue;
+               if ((f = fopen(line, "r")) != NULL)
+               {
+                       if (fgets(line, sizeof(line) - 1, f))
+                               blobmsg_add_u32(&buf, "brightness", atoi(line));
 
-               if (strchr(addr, ':'))
-                       continue;
+                       fclose(f);
+               }
 
-               d = blobmsg_open_table(&buf, NULL);
+               snprintf(line, sizeof(line) - 1, "/sys/class/leds/%s/max_brightness",
+                        e->d_name);
 
-               blobmsg_add_u32(&buf, "expires", atoi(ts) - now);
-               blobmsg_add_string(&buf, "macaddr", mac);
-               blobmsg_add_string(&buf, "ipaddr", addr);
+               if ((f = fopen(line, "r")) != NULL)
+               {
+                       if (fgets(line, sizeof(line) - 1, f))
+                               blobmsg_add_u32(&buf, "max_brightness", atoi(line));
 
-               if (strcmp(name, "*"))
-                       blobmsg_add_string(&buf, "hostname", name);
+                       fclose(f);
+               }
 
-               blobmsg_close_table(&buf, d);
+               blobmsg_close_table(&buf, led);
        }
 
-       fclose(leases);
+       closedir(d);
 
-out:
-       blobmsg_close_array(&buf, c);
+       blobmsg_close_array(&buf, list);
        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)
+rpc_luci2_usb_list(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);
+       DIR *d;
+       FILE *f;
+       int i;
+       void *list, *device;
+       char *p, line[512];
+       struct stat s;
+       struct dirent *e;
 
-       blob_buf_init(&buf, 0);
-       c = blobmsg_open_array(&buf, "leases");
+       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",
+       };
 
-       leases = fopen("/tmp/hosts/6relayd", "r");
+       if (!(d = opendir("/sys/bus/usb/devices")))
+               return rpc_errno_status();
 
-       if (leases)
-       {
-               while (fgets(line, sizeof(line) - 1, leases))
-               {
-                       if (strncmp(line, "# ", 2))
-                               continue;
+       blob_buf_init(&buf, 0);
+       list = blobmsg_open_array(&buf, "devices");
 
-                       strtok(line + 2, " \t"); /* iface */
+       while ((e = readdir(d)) != NULL)
+       {
+               if (e->d_name[0] < '0' || e->d_name[0] > '9')
+                       continue;
 
-                       duid = strtok(NULL, " \t");
+               snprintf(line, sizeof(line) - 1,
+                        "/sys/bus/usb/devices/%s/%s", e->d_name, attributes[0]);
 
-                       strtok(NULL, " \t"); /* iaid */
+               if (stat(line, &s))
+                       continue;
 
-                       name = strtok(NULL, " \t");
-                       ts   = strtok(NULL, " \t");
+               device = blobmsg_open_table(&buf, NULL);
 
-                       strtok(NULL, " \t"); /* id */
-                       strtok(NULL, " \t"); /* length */
+               blobmsg_add_string(&buf, "name", e->d_name);
 
-                       addr = strtok(NULL, " \t\n");
+               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 (!addr)
+                       if (!(f = fopen(line, "r")))
                                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 (fgets(line, sizeof(line) - 1, f))
+                       {
+                               switch (*attributes[i+2])
+                               {
+                               case 'x':
+                                       blobmsg_add_u32(&buf, attributes[i+1],
+                                                       strtoul(line, NULL, 16));
+                                       break;
 
-                       if (strcmp(name, "-"))
-                               blobmsg_add_string(&buf, "hostname", name);
+                               case 'd':
+                                       blobmsg_add_u32(&buf, attributes[i+1],
+                                                       strtoul(line, NULL, 10));
+                                       break;
 
-                       blobmsg_close_array(&buf, d);
-               }
+                               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);
        }
@@ -1119,24 +1737,397 @@ rpc_luci2_network_routes6(struct ubus_context *ctx, struct ubus_object *obj,
 }
 
 
-int rpc_luci2_api_init(struct ubus_context *ctx)
+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->access(blobmsg_data(sid), "luci-ui",
+                                blobmsg_data(acl), "read"))
+               {
+                       rv = false;
+                       break;
+               }
+
+               blobmsg_add_u8(e, blobmsg_data(acl),
+                              ops->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)
+                                                 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 =
@@ -1170,13 +2161,64 @@ int rpc_luci2_api_init(struct ubus_context *ctx)
                .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
+};