/*
* Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2018 Hans Dedecker <dedeckeh@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
#include <libubox/vlist.h>
#include <libubox/blobmsg_json.h>
#include <libubox/avl-cmp.h>
+#include <libubox/ulog.h>
#include "ubusd.h"
struct avl_node avl;
struct list_head list;
+ bool partial;
+
const char *user;
const char *group;
struct blob_attr *priv;
bool subscribe;
bool publish;
+ bool listen;
+ bool send;
};
struct ubusd_acl_file {
int ok;
};
+const char *ubusd_acl_dir = "/usr/share/acl.d";
static struct blob_buf bbuf;
static struct avl_tree ubusd_acls;
static int ubusd_acl_seq;
static struct ubus_object *acl_obj;
-static int
-ubusd_acl_match_path(const void *k1, const void *k2, void *ptr)
-{
- const char *name = k1;
- const char *match = k2;
- char *wildcard = strstr(match, "\t");
-
- if (wildcard)
- return strncmp(name, match, wildcard - match);
-
- return strcmp(name, match);
-}
-
static int
ubusd_acl_match_cred(struct ubus_client *cl, struct ubusd_acl_obj *obj)
{
const char *method, enum ubusd_acl_type type)
{
struct ubusd_acl_obj *acl;
- struct blob_attr *cur;
- int rem;
+ int match_len = 0;
- if (!cl->gid && !cl->uid)
+ if (!cl || !cl->uid || !obj)
return 0;
- acl = avl_find_ge_element(&ubusd_acls, obj, acl, avl);
- while (acl && !avl_is_last(&ubusd_acls, &acl->avl)) {
- int diff = ubusd_acl_match_path(obj, acl->avl.key, NULL);
-
- if (diff)
+ /*
+ * Since this tree is sorted alphabetically, we can only expect
+ * to find matching entries as long as the number of matching
+ * characters between the access list string and the object path
+ * is monotonically increasing.
+ */
+ avl_for_each_element(&ubusd_acls, acl, avl) {
+ const char *key = acl->avl.key;
+ int cur_match_len;
+ bool full_match;
+
+ full_match = ubus_strmatch_len(obj, key, &cur_match_len);
+ if (cur_match_len < match_len)
break;
- if (ubusd_acl_match_cred(cl, acl)) {
- acl = avl_next_element(acl, avl);
- continue;
+ match_len = cur_match_len;
+
+ if (!full_match) {
+ if (!acl->partial)
+ continue;
+
+ if (match_len != (int) strlen(key))
+ continue;
}
+ if (ubusd_acl_match_cred(cl, acl))
+ continue;
+
switch (type) {
case UBUS_ACL_PUBLISH:
if (acl->publish)
return 0;
break;
+ case UBUS_ACL_LISTEN:
+ if (acl->listen)
+ return 0;
+ break;
+
+ case UBUS_ACL_SEND:
+ if (acl->send)
+ return 0;
+ break;
+
case UBUS_ACL_ACCESS:
- if (acl->methods)
+ if (acl->methods) {
+ struct blob_attr *cur;
+ char *cur_method;
+ size_t rem;
+
blobmsg_for_each_attr(cur, acl->methods, rem)
- if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
- if (!ubusd_acl_match_path(method, blobmsg_get_string(cur), NULL))
+ if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING) {
+ cur_method = blobmsg_get_string(cur);
+
+ if (!strcmp(method, cur_method) || !strcmp("*", cur_method))
return 0;
+ }
+ }
break;
}
- acl = avl_next_element(acl, avl);
}
return -1;
#ifdef SO_PEERCRED
unsigned int len = sizeof(struct ucred);
- if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
+ ULOG_ERR("Failed getsockopt(): %m\n");
return -1;
+ }
#else
memset(&cred, 0, sizeof(cred));
#endif
pwd = getpwuid(cred.uid);
- if (!pwd)
+ if (!pwd) {
+ ULOG_ERR("Failed getpwuid(): %m\n");
return -1;
+ }
group = getgrgid(cred.gid);
- if (!group)
+ if (!group) {
+ ULOG_ERR("Failed getgrgid(): %m\n");
return -1;
+ }
cl->uid = cred.uid;
cl->gid = cred.gid;
return 0;
}
+void
+ubusd_acl_free_client(struct ubus_client *cl)
+{
+ free(cl->group);
+ free(cl->user);
+}
+
static void
ubusd_acl_file_free(struct ubusd_acl_file *file)
{
ubusd_acl_alloc_obj(struct ubusd_acl_file *file, const char *obj)
{
struct ubusd_acl_obj *o;
+ int len = strlen(obj);
char *k;
+ bool partial = false;
- o = calloc_a(sizeof(*o), &k, strlen(obj) + 1);
+ if (obj[len - 1] == '*') {
+ partial = true;
+ len--;
+ }
+
+ o = calloc_a(sizeof(*o), &k, len + 1);
+ o->partial = partial;
o->user = file->user;
o->group = file->group;
- o->avl.key = k;
- strcpy(k, obj);
-
- while (*k) {
- if (*k == '*')
- *k = '\t';
- k++;
- }
+ o->avl.key = memcpy(k, obj, len);
list_add(&o->list, &file->acl);
avl_insert(&ubusd_acls, &o->avl);
o->publish = true;
}
+static void ubusd_acl_add_listen(struct ubusd_acl_file *file, const char *obj)
+{
+ struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
+
+ o->listen = true;
+}
+
+static void ubusd_acl_add_send(struct ubusd_acl_file *file, const char *obj)
+{
+ struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
+
+ o->send = true;
+}
+
enum {
ACL_USER,
ACL_GROUP,
ACL_PUBLISH,
ACL_SUBSCRIBE,
ACL_INHERIT,
+ ACL_LISTEN,
+ ACL_SEND,
__ACL_MAX
};
[ACL_PUBLISH] = { .name = "publish", .type = BLOBMSG_TYPE_ARRAY },
[ACL_SUBSCRIBE] = { .name = "subscribe", .type = BLOBMSG_TYPE_ARRAY },
[ACL_INHERIT] = { .name = "inherit", .type = BLOBMSG_TYPE_ARRAY },
+ [ACL_LISTEN] = { .name= "listen", .type = BLOBMSG_TYPE_ARRAY },
+ [ACL_SEND] = { .name= "send", .type = BLOBMSG_TYPE_ARRAY },
};
static void
ubusd_acl_file_add(struct ubusd_acl_file *file)
{
struct blob_attr *tb[__ACL_MAX], *cur;
- int rem;
+ size_t rem;
blobmsg_parse(acl_policy, __ACL_MAX, tb, blob_data(file->blob),
blob_len(file->blob));
else
return;
- if (!tb[ACL_ACCESS] && !tb[ACL_PUBLISH] && !tb[ACL_INHERIT])
- return;
-
if (tb[ACL_ACCESS])
blobmsg_for_each_attr(cur, tb[ACL_ACCESS], rem)
ubusd_acl_add_access(file, cur);
blobmsg_for_each_attr(cur, tb[ACL_PUBLISH], rem)
if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
ubusd_acl_add_publish(file, blobmsg_get_string(cur));
+
+ if (tb[ACL_LISTEN])
+ blobmsg_for_each_attr(cur, tb[ACL_LISTEN], rem)
+ if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
+ ubusd_acl_add_listen(file, blobmsg_get_string(cur));
+
+ if (tb[ACL_SEND])
+ blobmsg_for_each_attr(cur, tb[ACL_SEND], rem)
+ if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
+ ubusd_acl_add_send(file, blobmsg_get_string(cur));
}
static void
{
struct stat st;
glob_t gl;
- int j;
+ size_t j;
+ const char *suffix = "/*.json";
+ char *path = alloca(strlen(ubusd_acl_dir) + strlen(suffix) + 1);
- if (glob("/usr/share/acl.d/*.json", GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
+ sprintf(path, "%s%s", ubusd_acl_dir, suffix);
+ if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
return;
vlist_update(&ubusd_acl_files);
ubusd_reply_add(struct ubus_object *obj)
{
struct ubusd_acl_obj *acl;
+ int match_len = 0;
if (!obj->path.key)
return;
- acl = avl_find_ge_element(&ubusd_acls, obj->path.key, acl, avl);
- while (acl && !avl_is_last(&ubusd_acls, &acl->avl) &&
- !ubusd_acl_match_path(obj->path.key, acl->avl.key, NULL)) {
- if (acl->priv) {
- void *c = blobmsg_open_table(&b, NULL);
+ /*
+ * Since this tree is sorted alphabetically, we can only expect
+ * to find matching entries as long as the number of matching
+ * characters between the access list string and the object path
+ * is monotonically increasing.
+ */
+ avl_for_each_element(&ubusd_acls, acl, avl) {
+ const char *key = acl->avl.key;
+ int cur_match_len;
+ bool full_match;
+ void *c;
+
+ if (!acl->priv)
+ continue;
- blobmsg_add_string(&b, "obj", obj->path.key);
- if (acl->user)
- blobmsg_add_string(&b, "user", acl->user);
- if (acl->group)
- blobmsg_add_string(&b, "group", acl->group);
+ full_match = ubus_strmatch_len(obj->path.key, key, &cur_match_len);
+ if (cur_match_len < match_len)
+ break;
+
+ match_len = cur_match_len;
- if (acl->priv)
- blobmsg_add_field(&b, blobmsg_type(acl->priv), "acl",
- blobmsg_data(acl->priv), blobmsg_data_len(acl->priv));
+ if (!full_match) {
+ if (!acl->partial)
+ continue;
- blobmsg_close_table(&b, c);
+ if (match_len != (int) strlen(key))
+ continue;
}
- acl = avl_next_element(acl, avl);
+
+ c = blobmsg_open_table(&b, NULL);
+ blobmsg_add_string(&b, "obj", obj->path.key);
+ if (acl->user)
+ blobmsg_add_string(&b, "user", acl->user);
+ if (acl->group)
+ blobmsg_add_string(&b, "group", acl->group);
+
+ blobmsg_add_field(&b, blobmsg_type(acl->priv), "acl",
+ blobmsg_data(acl->priv), blobmsg_data_len(acl->priv));
+
+ blobmsg_close_table(&b, c);
}
}
+
static int ubusd_reply_query(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr, struct blob_attr *msg)
{
struct ubus_object *obj;
static int ubusd_acl_recv(struct ubus_client *cl, struct ubus_msg_buf *ub, const char *method, struct blob_attr *msg)
{
if (!strcmp(method, "query"))
- return ubusd_reply_query(cl, ub, ubus_parse_msg(ub->data), msg);
+ return ubusd_reply_query(cl, ub, ubus_parse_msg(ub->data, blob_raw_len(ub->data)), msg);
return UBUS_STATUS_INVALID_COMMAND;
}
void ubusd_acl_init(void)
{
- avl_init(&ubusd_acls, ubusd_acl_match_path, true, NULL);
+ ubus_init_string_tree(&ubusd_acls, true);
acl_obj = ubusd_create_object_internal(NULL, UBUS_SYSTEM_OBJECT_ACL);
acl_obj->recv_msg = ubusd_acl_recv;
}