add ubus support
authorFelix Fietkau <nbd@openwrt.org>
Mon, 7 Jan 2013 01:56:48 +0000 (02:56 +0100)
committerFelix Fietkau <nbd@openwrt.org>
Mon, 7 Jan 2013 01:56:48 +0000 (02:56 +0100)
CMakeLists.txt
main.c
plugin.c
plugin.h
session-test.sh [new file with mode: 0755]
ubus.c [new file with mode: 0644]
ubus.h [new file with mode: 0644]
uhttpd.h

index 889c32e..0d9dcd4 100644 (file)
@@ -6,6 +6,7 @@ ADD_DEFINITIONS(-Os -Wall -Werror -Wmissing-declarations --std=gnu99 -g3)
 
 OPTION(TLS_SUPPORT "TLS support" ON)
 OPTION(LUA_SUPPORT "Lua support" ON)
+OPTION(UBUS_SUPPORT "ubus support" ON)
 
 IF(APPLE)
   INCLUDE_DIRECTORIES(/opt/local/include)
@@ -56,6 +57,13 @@ IF(LUA_SUPPORT)
        TARGET_LINK_LIBRARIES(uhttpd_lua ${LUA_LIBS} m dl)
 ENDIF()
 
+IF(UBUS_SUPPORT)
+       SET(PLUGINS ${PLUGINS} uhttpd_ubus)
+       ADD_DEFINITIONS(-DHAVE_UBUS)
+       ADD_LIBRARY(uhttpd_ubus MODULE ubus.c)
+       TARGET_LINK_LIBRARIES(uhttpd_ubus ubus ubox blobmsg_json json)
+ENDIF()
+
 IF(PLUGINS)
        SET_TARGET_PROPERTIES(${PLUGINS} PROPERTIES
                PREFIX ""
diff --git a/main.c b/main.c
index 0dfa4ca..73a925c 100644 (file)
--- a/main.c
+++ b/main.c
@@ -40,6 +40,7 @@ static int run_server(void)
 {
        uloop_init();
        uh_setup_listeners();
+       uh_plugin_post_init();
        uloop_run();
 
        return 0;
@@ -338,6 +339,15 @@ int main(int argc, char **argv)
                        conf.lua_handler = optarg;
                        break;
 #endif
+#ifdef HAVE_UBUS
+               case 'u':
+                       conf.ubus_prefix = optarg;
+                       break;
+
+               case 'U':
+                       conf.ubus_socket = optarg;
+                       break;
+#endif
                default:
                        return usage(argv[0]);
                }
@@ -376,6 +386,10 @@ int main(int argc, char **argv)
                        return 1;
        }
 #endif
+#ifdef HAVE_UBUS
+       if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so"))
+               return 1;
+#endif
 
        /* fork (if not disabled) */
        if (!nofork) {
index ecf6660..56e049c 100644 (file)
--- a/plugin.c
+++ b/plugin.c
@@ -21,6 +21,8 @@
 #include "uhttpd.h"
 #include "plugin.h"
 
+static LIST_HEAD(plugins);
+
 static const struct uhttpd_ops ops = {
        .dispatch_add = uh_dispatch_add,
        .path_match = uh_path_match,
@@ -53,5 +55,14 @@ int uh_plugin_init(const char *name)
                return -ENOENT;
        }
 
+       list_add(&p->list, &plugins);
        return p->init(&ops, &conf);
 }
+
+void uh_plugin_post_init(void)
+{
+       struct uhttpd_plugin *p;
+
+       list_for_each_entry(p, &plugins, list)
+               p->post_init();
+}
index 25bcaa3..3a2b2ea 100644 (file)
--- a/plugin.h
+++ b/plugin.h
@@ -37,5 +37,8 @@ struct uhttpd_ops {
 };
 
 struct uhttpd_plugin {
+       struct list_head list;
+
        int (*init)(const struct uhttpd_ops *ops, struct config *conf);
+       void (*post_init)(void);
 };
diff --git a/session-test.sh b/session-test.sh
new file mode 100755 (executable)
index 0000000..4d767f1
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+. /usr/share/libubox/jshn.sh
+
+json_load "$(ubus call session create)"
+json_get_var sid sid
+
+
+json_init
+json_add_string sid "$sid"
+json_add_array "objects"
+json_add_array ""
+json_add_string "" "session"
+json_add_string "" "list"
+json_close_array
+json_close_array
+
+ubus call session grant "$(json_dump)"
+
+echo "Session: $sid"
+wget -O- "http://localhost:8080/ubus/$sid/session/list"
diff --git a/ubus.c b/ubus.c
new file mode 100644 (file)
index 0000000..2b4ac5b
--- /dev/null
+++ b/ubus.c
@@ -0,0 +1,843 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/avl.h>
+#include <libubox/avl-cmp.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <poll.h>
+
+#include "uhttpd.h"
+#include "plugin.h"
+#include "ubus.h"
+
+static const struct uhttpd_ops *ops;
+static struct config *_conf;
+#define conf (*_conf)
+
+static struct ubus_context *ctx;
+static struct avl_tree sessions;
+static struct blob_buf buf;
+
+static const struct blobmsg_policy new_policy = {
+       .name = "timeout", .type = BLOBMSG_TYPE_INT32
+};
+
+static const struct blobmsg_policy sid_policy = {
+       .name = "sid", .type = BLOBMSG_TYPE_STRING
+};
+
+enum {
+       UH_UBUS_SS_SID,
+       UH_UBUS_SS_VALUES,
+       __UH_UBUS_SS_MAX,
+};
+static const struct blobmsg_policy set_policy[__UH_UBUS_SS_MAX] = {
+       [UH_UBUS_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+       [UH_UBUS_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE },
+};
+
+enum {
+       UH_UBUS_SG_SID,
+       UH_UBUS_SG_KEYS,
+       __UH_UBUS_SG_MAX,
+};
+static const struct blobmsg_policy get_policy[__UH_UBUS_SG_MAX] = {
+       [UH_UBUS_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+       [UH_UBUS_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+enum {
+       UH_UBUS_SA_SID,
+       UH_UBUS_SA_OBJECTS,
+       __UH_UBUS_SA_MAX,
+};
+static const struct blobmsg_policy acl_policy[__UH_UBUS_SA_MAX] = {
+       [UH_UBUS_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
+       [UH_UBUS_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+/*
+ * Keys in the AVL tree contain all pattern characters up to the first wildcard.
+ * To look up entries, start with the last entry that has a key less than or
+ * equal to the method name, then work backwards as long as the AVL key still
+ * matches its counterpart in the object name
+ */
+#define uh_foreach_matching_acl_prefix(_acl, _ses, _obj, _func)                        \
+       for (_acl = avl_find_le_element(&(_ses)->acls, _obj, _acl, avl);        \
+            _acl && !strncmp((_acl)->object, _obj, (_acl)->sort_len);          \
+            _acl = avl_is_first(&(ses)->acls, &(_acl)->avl) ? NULL :           \
+                   avl_prev_element((_acl), avl))
+
+#define uh_foreach_matching_acl(_acl, _ses, _obj, _func)                       \
+       uh_foreach_matching_acl_prefix(_acl, _ses, _obj, _func)                 \
+               if (!fnmatch((_acl)->object, (_obj), FNM_NOESCAPE) &&           \
+                   !fnmatch((_acl)->function, (_func), FNM_NOESCAPE))
+
+static void
+uh_ubus_random(char *dest)
+{
+       unsigned char buf[16] = { 0 };
+       FILE *f;
+       int i;
+
+       f = fopen("/dev/urandom", "r");
+       if (!f)
+               return;
+
+       fread(buf, 1, sizeof(buf), f);
+       fclose(f);
+
+       for (i = 0; i < sizeof(buf); i++)
+               sprintf(dest + (i<<1), "%02x", buf[i]);
+}
+
+static void
+uh_ubus_session_dump_data(struct uh_ubus_session *ses, struct blob_buf *b)
+{
+       struct uh_ubus_session_data *d;
+
+       avl_for_each_element(&ses->data, d, avl) {
+               blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr),
+                                 blobmsg_data(d->attr), blobmsg_data_len(d->attr));
+       }
+}
+
+static void
+uh_ubus_session_dump_acls(struct uh_ubus_session *ses, struct blob_buf *b)
+{
+       struct uh_ubus_session_acl *acl;
+       const char *lastobj = NULL;
+       void *c = NULL;
+
+       avl_for_each_element(&ses->acls, acl, avl) {
+               if (!lastobj || strcmp(acl->object, lastobj))
+               {
+                       if (c) blobmsg_close_array(b, c);
+                       c = blobmsg_open_array(b, acl->object);
+               }
+
+               blobmsg_add_string(b, NULL, acl->function);
+               lastobj = acl->object;
+       }
+
+       if (c) blobmsg_close_array(b, c);
+}
+
+static void
+uh_ubus_session_dump(struct uh_ubus_session *ses,
+                                        struct ubus_context *ctx,
+                                        struct ubus_request_data *req)
+{
+       void *c;
+       struct blob_buf b;
+
+       memset(&b, 0, sizeof(b));
+       blob_buf_init(&b, 0);
+
+       blobmsg_add_string(&b, "sid", ses->id);
+       blobmsg_add_u32(&b, "timeout", ses->timeout);
+
+       c = blobmsg_open_table(&b, "acls");
+       uh_ubus_session_dump_acls(ses, &b);
+       blobmsg_close_table(&b, c);
+
+       c = blobmsg_open_table(&b, "data");
+       uh_ubus_session_dump_data(ses, &b);
+       blobmsg_close_table(&b, c);
+
+       ubus_send_reply(ctx, req, b.head);
+       blob_buf_free(&b);
+}
+
+static void
+uh_ubus_touch_session(struct uh_ubus_session *ses)
+{
+       uloop_timeout_set(&ses->t, ses->timeout * 1000);
+}
+
+static void
+uh_ubus_session_destroy(struct uh_ubus_session *ses)
+{
+       struct uh_ubus_session_acl *acl, *nacl;
+       struct uh_ubus_session_data *data, *ndata;
+
+       uloop_timeout_cancel(&ses->t);
+       avl_remove_all_elements(&ses->acls, acl, avl, nacl)
+               free(acl);
+
+       avl_remove_all_elements(&ses->data, data, avl, ndata)
+               free(data);
+
+       avl_delete(&sessions, &ses->avl);
+       free(ses);
+}
+
+static void uh_ubus_session_timeout(struct uloop_timeout *t)
+{
+       struct uh_ubus_session *ses;
+
+       ses = container_of(t, struct uh_ubus_session, t);
+       uh_ubus_session_destroy(ses);
+}
+
+static struct uh_ubus_session *
+uh_ubus_session_create(int timeout)
+{
+       struct uh_ubus_session *ses;
+
+       ses = calloc(1, sizeof(*ses));
+       if (!ses)
+               return NULL;
+
+       ses->timeout  = timeout;
+       ses->avl.key  = ses->id;
+       uh_ubus_random(ses->id);
+
+       avl_insert(&sessions, &ses->avl);
+       avl_init(&ses->acls, avl_strcmp, true, NULL);
+       avl_init(&ses->data, avl_strcmp, false, NULL);
+
+       ses->t.cb = uh_ubus_session_timeout;
+       uh_ubus_touch_session(ses);
+
+       return ses;
+}
+
+static struct uh_ubus_session *
+uh_ubus_session_get(const char *id)
+{
+       struct uh_ubus_session *ses;
+
+       ses = avl_find_element(&sessions, id, ses, avl);
+       if (!ses)
+               return NULL;
+
+       uh_ubus_touch_session(ses);
+       return ses;
+}
+
+static int
+uh_ubus_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
+                                         struct ubus_request_data *req, const char *method,
+                                         struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct blob_attr *tb;
+       int timeout = conf.script_timeout;
+
+       blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
+       if (tb)
+               timeout = blobmsg_get_u32(tb);
+
+       ses = uh_ubus_session_create(timeout);
+       if (ses)
+               uh_ubus_session_dump(ses, ctx, req);
+
+       return 0;
+}
+
+static int
+uh_ubus_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
+                                       struct ubus_request_data *req, const char *method,
+                                       struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct blob_attr *tb;
+
+       blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
+
+       if (!tb) {
+               avl_for_each_element(&sessions, ses, avl)
+                       uh_ubus_session_dump(ses, ctx, req);
+               return 0;
+       }
+
+       ses = uh_ubus_session_get(blobmsg_data(tb));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       uh_ubus_session_dump(ses, ctx, req);
+
+       return 0;
+}
+
+static int
+uh_id_len(const char *str)
+{
+       return strcspn(str, "*?[");
+}
+
+static int
+uh_ubus_session_grant(struct uh_ubus_session *ses, struct ubus_context *ctx,
+                     const char *object, const char *function)
+{
+       struct uh_ubus_session_acl *acl;
+       char *new_obj, *new_func, *new_id;
+       int id_len;
+
+       if (!object || !function)
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       uh_foreach_matching_acl_prefix(acl, ses, object, function) {
+               if (!strcmp(acl->object, object) &&
+                   !strcmp(acl->function, function))
+                       return 0;
+       }
+
+       id_len = uh_id_len(object);
+       acl = calloc_a(sizeof(*acl),
+               &new_obj, strlen(object) + 1,
+               &new_func, strlen(function) + 1,
+               &new_id, id_len + 1);
+
+       if (!acl)
+               return UBUS_STATUS_UNKNOWN_ERROR;
+
+       acl->object = strcpy(new_obj, object);
+       acl->function = strcpy(new_func, function);
+       acl->avl.key = strncpy(new_id, object, id_len);
+       avl_insert(&ses->acls, &acl->avl);
+
+       return 0;
+}
+
+static int
+uh_ubus_session_revoke(struct uh_ubus_session *ses, struct ubus_context *ctx,
+                      const char *object, const char *function)
+{
+       struct uh_ubus_session_acl *acl, *next;
+       int id_len;
+       char *id;
+
+       if (!object && !function) {
+               avl_remove_all_elements(&ses->acls, acl, avl, next)
+                       free(acl);
+               return 0;
+       }
+
+       id_len = uh_id_len(object);
+       id = alloca(id_len + 1);
+       strncpy(id, object, id_len);
+       id[id_len] = 0;
+
+       acl = avl_find_element(&ses->acls, id, acl, avl);
+       while (acl) {
+               if (!avl_is_last(&ses->acls, &acl->avl))
+                       next = avl_next_element(acl, avl);
+               else
+                       next = NULL;
+
+               if (strcmp(id, acl->avl.key) != 0)
+                       break;
+
+               if (!strcmp(acl->object, object) &&
+                   !strcmp(acl->function, function)) {
+                       avl_delete(&ses->acls, &acl->avl);
+                       free(acl);
+               }
+               acl = next;
+       }
+
+       return 0;
+}
+
+
+static int
+uh_ubus_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
+                  struct ubus_request_data *req, const char *method,
+                  struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct blob_attr *tb[__UH_UBUS_SA_MAX];
+       struct blob_attr *attr, *sattr;
+       const char *object, *function;
+       int rem1, rem2;
+
+       int (*cb)(struct uh_ubus_session *ses, struct ubus_context *ctx,
+                 const char *object, const char *function);
+
+       blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (!tb[UH_UBUS_SA_SID])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SA_SID]));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       if (!strcmp(method, "grant"))
+               cb = uh_ubus_session_grant;
+       else
+               cb = uh_ubus_session_revoke;
+
+       if (!tb[UH_UBUS_SA_OBJECTS])
+               return cb(ses, ctx, NULL, NULL);
+
+       blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1) {
+               if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
+                       continue;
+
+               object = NULL;
+               function = NULL;
+
+               blobmsg_for_each_attr(sattr, attr, rem2) {
+                       if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
+                               continue;
+
+                       if (!object)
+                               object = blobmsg_data(sattr);
+                       else if (!function)
+                               function = blobmsg_data(sattr);
+                       else
+                               break;
+               }
+
+               if (object && function)
+                       cb(ses, ctx, object, function);
+       }
+
+       return 0;
+}
+
+static int
+uh_ubus_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
+                                  struct ubus_request_data *req, const char *method,
+                                  struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct uh_ubus_session_data *data;
+       struct blob_attr *tb[__UH_UBUS_SA_MAX];
+       struct blob_attr *attr;
+       int rem;
+
+       blobmsg_parse(set_policy, __UH_UBUS_SS_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (!tb[UH_UBUS_SS_SID] || !tb[UH_UBUS_SS_VALUES])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SS_SID]));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       blobmsg_for_each_attr(attr, tb[UH_UBUS_SS_VALUES], rem) {
+               if (!blobmsg_name(attr)[0])
+                       continue;
+
+               data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
+               if (data) {
+                       avl_delete(&ses->data, &data->avl);
+                       free(data);
+               }
+
+               data = calloc(1, sizeof(*data) + blob_pad_len(attr));
+               if (!data)
+                       break;
+
+               memcpy(data->attr, attr, blob_pad_len(attr));
+               data->avl.key = blobmsg_name(data->attr);
+               avl_insert(&ses->data, &data->avl);
+       }
+
+       return 0;
+}
+
+static int
+uh_ubus_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
+                                  struct ubus_request_data *req, const char *method,
+                                  struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct uh_ubus_session_data *data;
+       struct blob_attr *tb[__UH_UBUS_SA_MAX];
+       struct blob_attr *attr;
+       struct blob_buf b;
+       void *c;
+       int rem;
+
+       blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (!tb[UH_UBUS_SG_SID])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       memset(&b, 0, sizeof(b));
+       blob_buf_init(&b, 0);
+       c = blobmsg_open_table(&b, "values");
+
+       if (!tb[UH_UBUS_SG_KEYS]) {
+               uh_ubus_session_dump_data(ses, &b);
+               return 0;
+       }
+
+       blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
+               if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+                       continue;
+
+               data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
+               if (!data)
+                       continue;
+
+               blobmsg_add_field(&b, blobmsg_type(data->attr),
+                                 blobmsg_name(data->attr),
+                                 blobmsg_data(data->attr),
+                                 blobmsg_data_len(data->attr));
+       }
+
+       blobmsg_close_table(&b, c);
+       ubus_send_reply(ctx, req, b.head);
+       blob_buf_free(&b);
+
+       return 0;
+}
+
+static int
+uh_ubus_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
+                                    struct ubus_request_data *req, const char *method,
+                                    struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct uh_ubus_session_data *data, *ndata;
+       struct blob_attr *tb[__UH_UBUS_SA_MAX];
+       struct blob_attr *attr;
+       int rem;
+
+       blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (!tb[UH_UBUS_SG_SID])
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       if (!tb[UH_UBUS_SG_KEYS]) {
+               avl_remove_all_elements(&ses->data, data, avl, ndata)
+                       free(data);
+               return 0;
+       }
+
+       blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
+               if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+                       continue;
+
+               data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
+               if (!data)
+                       continue;
+
+               avl_delete(&ses->data, &data->avl);
+               free(data);
+       }
+
+       return 0;
+}
+
+static int
+uh_ubus_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
+                                          struct ubus_request_data *req, const char *method,
+                                          struct blob_attr *msg)
+{
+       struct uh_ubus_session *ses;
+       struct blob_attr *tb;
+
+       blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
+
+       if (!tb)
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       ses = uh_ubus_session_get(blobmsg_data(tb));
+       if (!ses)
+               return UBUS_STATUS_NOT_FOUND;
+
+       uh_ubus_session_destroy(ses);
+
+       return 0;
+}
+
+static char *split_str(char *str)
+{
+       if (str)
+               str = strchr(str, '/');
+
+       while (str && *str == '/') {
+               *str = 0;
+               str++;
+       }
+       return str;
+}
+
+static bool
+uh_ubus_request_parse_url(struct client *cl, char *url, char **sid, char **obj, char **fun)
+{
+       url += strlen(conf.ubus_prefix);
+       while (url && *url == '/')
+               url++;
+
+       *sid = url;
+
+       url = split_str(url);
+       *obj = url;
+
+       url = split_str(url);
+       *fun = url;
+
+       return *sid && *obj && *fun;
+}
+
+static void
+uh_ubus_request_data_cb(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+       struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
+       struct client *cl = container_of(du, struct client, dispatch.ubus);
+       char *str;
+
+       if (!du->header_sent) {
+               ops->http_header(cl, 200, "OK");
+               ustream_printf(cl->us, "Content-Type: application/json\r\n\r\n");
+               du->header_sent = true;
+       }
+
+       str = blobmsg_format_json_indent(msg, true, 0);
+       ops->chunk_write(cl, str, strlen(str));
+       free(str);
+}
+
+static void
+uh_ubus_request_cb(struct ubus_request *req, int ret)
+{
+       struct dispatch_ubus *du = container_of(req, struct dispatch_ubus, req);
+       struct client *cl = container_of(du, struct client, dispatch.ubus);
+
+       if (!du->header_sent)
+               return ops->client_error(cl, 204, "No content", "Function did not return data");
+
+       ops->request_done(cl);
+}
+
+static void uh_ubus_close_fds(struct client *cl)
+{
+       if (ctx->sock.fd < 0)
+               return;
+
+       close(ctx->sock.fd);
+       ctx->sock.fd = -1;
+}
+
+static void uh_ubus_request_free(struct client *cl)
+{
+       struct dispatch_ubus *du = &cl->dispatch.ubus;
+
+       if (du->jsobj)
+               json_object_put(du->jsobj);
+
+       if (du->jstok)
+               json_tokener_free(du->jstok);
+
+       if (du->req_pending)
+               ubus_abort_request(ctx, &du->req);
+}
+
+static void uh_ubus_json_error(struct client *cl)
+{
+       ops->client_error(cl, 400, "Bad Request", "Invalid JSON data");
+}
+
+static void uh_ubus_send_request(struct client *cl, json_object *obj)
+{
+       struct dispatch *d = &cl->dispatch;
+       struct dispatch_ubus *du = &d->ubus;
+       int ret;
+
+       blob_buf_init(&buf, 0);
+
+       if (obj && !blobmsg_add_object(&buf, obj))
+               return uh_ubus_json_error(cl);
+
+       ret = ubus_invoke_async(ctx, du->obj, du->func, buf.head, &du->req);
+       if (ret)
+               return ops->client_error(cl, 500, "Internal Error",
+                       "Error sending ubus request: %s", ubus_strerror(ret));
+
+       du->req.data_cb = uh_ubus_request_data_cb;
+       du->req.complete_cb = uh_ubus_request_cb;
+       ubus_complete_request_async(ctx, &du->req);
+
+       du->req_pending = true;
+}
+
+static void uh_ubus_data_done(struct client *cl)
+{
+       struct dispatch_ubus *du = &cl->dispatch.ubus;
+       struct json_object *obj = du->jsobj;
+
+       if (!obj || json_object_get_type(obj) != json_type_object)
+               return uh_ubus_json_error(cl);
+
+       uh_ubus_send_request(cl, obj);
+}
+
+static int uh_ubus_data_send(struct client *cl, const char *data, int len)
+{
+       struct dispatch_ubus *du = &cl->dispatch.ubus;
+
+       if (du->jsobj) {
+               uh_ubus_json_error(cl);
+               return 0;
+       }
+
+       du->post_len += len;
+       if (du->post_len > UH_UBUS_MAX_POST_SIZE) {
+               ops->client_error(cl, 413, "Too Large", "Message too big");
+               return 0;
+       }
+
+       du->jsobj = json_tokener_parse_ex(du->jstok, data, len);
+       return len;
+}
+
+static void uh_ubus_defer_post(struct client *cl)
+{
+       struct dispatch *d = &cl->dispatch;
+
+       d->ubus.jstok = json_tokener_new();
+       if (d->ubus.jstok)
+               return ops->client_error(cl, 500, "Internal Error", "Internal Error");
+
+       d->data_send = uh_ubus_data_send;
+       d->data_done = uh_ubus_data_done;
+}
+
+static void uh_ubus_handle_request(struct client *cl, char *url, struct path_info *pi)
+{
+       struct uh_ubus_session_acl *acl;
+       struct uh_ubus_session *ses;
+       struct dispatch *d = &cl->dispatch;
+       char *sid, *obj, *fun;
+       bool access = false;
+
+       blob_buf_init(&buf, 0);
+
+       if (!uh_ubus_request_parse_url(cl, url, &sid, &obj, &fun))
+               return ops->client_error(cl, 400, "Bad Request", "Invalid Request");
+
+       ses = uh_ubus_session_get(sid);
+       if (!ses)
+               return ops->client_error(cl, 404, "Not Found", "No such session %s", sid);
+
+       uh_foreach_matching_acl(acl, ses, obj, fun) {
+               access = true;
+               break;
+       }
+
+       if (!access)
+               return ops->client_error(cl, 403, "Denied", "Access to object denied");
+
+       if (ubus_lookup_id(ctx, obj, &d->ubus.obj))
+               return ops->client_error(cl, 500, "Not Found", "No such object");
+
+       d->close_fds = uh_ubus_close_fds;
+       d->free = uh_ubus_request_free;
+       d->ubus.func = fun;
+
+       if (cl->request.method == UH_HTTP_MSG_POST)
+               uh_ubus_defer_post(cl);
+       else
+               uh_ubus_send_request(cl, NULL);
+}
+
+static bool
+uh_ubus_check_url(const char *url)
+{
+       return ops->path_match(conf.ubus_prefix, url);
+}
+
+static int
+uh_ubus_init(void)
+{
+       static struct dispatch_handler ubus_dispatch = {
+               .check_url = uh_ubus_check_url,
+               .handle_request = uh_ubus_handle_request,
+       };
+
+       static const struct ubus_method session_methods[] = {
+               UBUS_METHOD("create",  uh_ubus_handle_create,  &new_policy),
+               UBUS_METHOD("list",    uh_ubus_handle_list,    &sid_policy),
+               UBUS_METHOD("grant",   uh_ubus_handle_acl,     acl_policy),
+               UBUS_METHOD("revoke",  uh_ubus_handle_acl,     acl_policy),
+               UBUS_METHOD("set",     uh_ubus_handle_set,     set_policy),
+               UBUS_METHOD("get",     uh_ubus_handle_get,     get_policy),
+               UBUS_METHOD("unset",   uh_ubus_handle_unset,   get_policy),
+               UBUS_METHOD("destroy", uh_ubus_handle_destroy, &sid_policy),
+       };
+
+       static struct ubus_object_type session_type =
+               UBUS_OBJECT_TYPE("uhttpd", session_methods);
+
+       static struct ubus_object obj = {
+               .name = "session",
+               .type = &session_type,
+               .methods = session_methods,
+               .n_methods = ARRAY_SIZE(session_methods),
+       };
+
+       int ret;
+
+       ctx = ubus_connect(conf.ubus_socket);
+       if (!ctx) {
+               fprintf(stderr, "Unable to connect to ubus socket\n");
+               exit(1);
+       }
+
+       ret = ubus_add_object(ctx, &obj);
+       if (ret) {
+               fprintf(stderr, "Unable to publish ubus object: %s\n",
+                               ubus_strerror(ret));
+               exit(1);
+       }
+
+       avl_init(&sessions, avl_strcmp, false, NULL);
+       ops->dispatch_add(&ubus_dispatch);
+
+       uloop_done();
+       return 0;
+}
+
+
+static int uh_ubus_plugin_init(const struct uhttpd_ops *o, struct config *c)
+{
+       ops = o;
+       _conf = c;
+       return uh_ubus_init();
+}
+
+static void uh_ubus_post_init(void)
+{
+       ubus_add_uloop(ctx);
+}
+
+const struct uhttpd_plugin uhttpd_plugin = {
+       .init = uh_ubus_plugin_init,
+       .post_init = uh_ubus_post_init,
+};
diff --git a/ubus.h b/ubus.h
new file mode 100644 (file)
index 0000000..19e1331
--- /dev/null
+++ b/ubus.h
@@ -0,0 +1,58 @@
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ *   Copyright (C) 2012 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef __UHTTPD_UBUS_H
+#define __UHTTPD_UBUS_H
+
+#include <libubox/avl.h>
+#include <libubox/blobmsg_json.h>
+
+#define UBUS_SID_LEN   32
+#define UH_UBUS_MAX_POST_SIZE  4096
+
+struct uh_ubus_request_data {
+       const char *sid;
+       const char *object;
+       const char *function;
+};
+
+struct uh_ubus_session {
+       struct avl_node avl;
+       char id[UBUS_SID_LEN + 1];
+
+       struct uloop_timeout t;
+       struct avl_tree data;
+       struct avl_tree acls;
+
+       int timeout;
+};
+
+struct uh_ubus_session_data {
+       struct avl_node avl;
+       struct blob_attr attr[];
+};
+
+struct uh_ubus_session_acl {
+       struct avl_node avl;
+       const char *object;
+       const char *function;
+       int sort_len;
+};
+
+#endif
index 9304670..590d486 100644 (file)
--- a/uhttpd.h
+++ b/uhttpd.h
 #include <libubox/ustream.h>
 #include <libubox/blob.h>
 #include <libubox/utils.h>
+#ifdef HAVE_UBUS
+#include <libubus.h>
+#include <json/json.h>
+#endif
 #ifdef HAVE_TLS
 #include <libubox/ustream-ssl.h>
 #endif
@@ -51,6 +55,8 @@ struct config {
        const char *cgi_path;
        const char *lua_handler;
        const char *lua_prefix;
+       const char *ubus_prefix;
+       const char *ubus_socket;
        int no_symlinks;
        int no_dirlists;
        int network_timeout;
@@ -151,6 +157,19 @@ struct dispatch_handler {
        void (*handle_request)(struct client *cl, char *url, struct path_info *pi);
 };
 
+#ifdef HAVE_UBUS
+struct dispatch_ubus {
+       struct ubus_request req;
+       struct json_tokener *jstok;
+       struct json_object *jsobj;
+       uint32_t obj;
+       int post_len;
+       const char *func;
+       bool req_pending;
+       bool header_sent;
+};
+#endif
+
 struct dispatch {
        int (*data_send)(struct client *cl, const char *data, int len);
        void (*data_done)(struct client *cl);
@@ -165,6 +184,9 @@ struct dispatch {
                        int fd;
                } file;
                struct dispatch_proc proc;
+#ifdef HAVE_UBUS
+               struct dispatch_ubus ubus;
+#endif
        };
 };
 
@@ -241,5 +263,6 @@ bool uh_create_process(struct client *cl, struct path_info *pi, char *url,
                       void (*cb)(struct client *cl, struct path_info *pi, char *url));
 
 int uh_plugin_init(const char *name);
+void uh_plugin_post_init(void);
 
 #endif