If there are any ACLs present other than global wildcard "*", the AVL tree comparator will compare ACL key to object name. However, object name can be NULL in cases where ACL check is done on call to internal ubus objects (e.g. ubus monitor). With this change we skip checking ACLs on such NULL objects. Signed-off-by: Denis Osvald <denis.osvald@sartura.hr>
495 lines
11 KiB
C
495 lines
11 KiB
C
/*
|
|
* Copyright (C) 2015 John Crispin <blogic@openwrt.org>
|
|
*
|
|
* 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
|
|
* as published by the Free Software Foundation
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <glob.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
|
|
#include <libubox/vlist.h>
|
|
#include <libubox/blobmsg_json.h>
|
|
#include <libubox/avl-cmp.h>
|
|
|
|
#include "ubusd.h"
|
|
|
|
#ifndef SO_PEERCRED
|
|
struct ucred {
|
|
int pid;
|
|
int uid;
|
|
int gid;
|
|
};
|
|
#endif
|
|
|
|
struct ubusd_acl_obj {
|
|
struct avl_node avl;
|
|
struct list_head list;
|
|
|
|
const char *user;
|
|
const char *group;
|
|
|
|
struct blob_attr *methods;
|
|
struct blob_attr *tags;
|
|
struct blob_attr *priv;
|
|
bool subscribe;
|
|
bool publish;
|
|
};
|
|
|
|
struct ubusd_acl_file {
|
|
struct vlist_node avl;
|
|
|
|
const char *user;
|
|
const char *group;
|
|
|
|
struct blob_attr *blob;
|
|
struct list_head acl;
|
|
|
|
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)
|
|
{
|
|
if (obj->user && !strcmp(cl->user, obj->user))
|
|
return 0;
|
|
|
|
if (obj->group && !strcmp(cl->group, obj->group))
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ubusd_acl_check(struct ubus_client *cl, const char *obj,
|
|
const char *method, enum ubusd_acl_type type)
|
|
{
|
|
struct ubusd_acl_obj *acl;
|
|
struct blob_attr *cur;
|
|
int rem;
|
|
|
|
if (!cl->uid || !obj)
|
|
return 0;
|
|
|
|
acl = avl_find_ge_element(&ubusd_acls, obj, acl, avl);
|
|
if (!acl)
|
|
return -1;
|
|
|
|
avl_for_element_to_last(&ubusd_acls, acl, acl, avl) {
|
|
int diff = ubusd_acl_match_path(obj, acl->avl.key, NULL);
|
|
|
|
if (diff)
|
|
break;
|
|
|
|
if (ubusd_acl_match_cred(cl, acl))
|
|
continue;
|
|
|
|
switch (type) {
|
|
case UBUS_ACL_PUBLISH:
|
|
if (acl->publish)
|
|
return 0;
|
|
break;
|
|
|
|
case UBUS_ACL_SUBSCRIBE:
|
|
if (acl->subscribe)
|
|
return 0;
|
|
break;
|
|
|
|
case UBUS_ACL_ACCESS:
|
|
if (acl->methods)
|
|
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))
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ubusd_acl_init_client(struct ubus_client *cl, int fd)
|
|
{
|
|
struct ucred cred;
|
|
struct passwd *pwd;
|
|
struct group *group;
|
|
|
|
#ifdef SO_PEERCRED
|
|
unsigned int len = sizeof(struct ucred);
|
|
|
|
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
|
|
return -1;
|
|
#else
|
|
memset(&cred, 0, sizeof(cred));
|
|
#endif
|
|
|
|
pwd = getpwuid(cred.uid);
|
|
if (!pwd)
|
|
return -1;
|
|
|
|
group = getgrgid(cred.gid);
|
|
if (!group)
|
|
return -1;
|
|
|
|
cl->uid = cred.uid;
|
|
cl->gid = cred.gid;
|
|
|
|
cl->group = strdup(group->gr_name);
|
|
cl->user = strdup(pwd->pw_name);
|
|
|
|
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)
|
|
{
|
|
struct ubusd_acl_obj *p, *q;
|
|
|
|
list_for_each_entry_safe(p, q, &file->acl, list) {
|
|
avl_delete(&ubusd_acls, &p->avl);
|
|
list_del(&p->list);
|
|
free(p);
|
|
}
|
|
|
|
free(file);
|
|
}
|
|
|
|
enum {
|
|
ACL_ACCESS_METHODS,
|
|
ACL_ACCESS_TAGS,
|
|
ACL_ACCESS_PRIV,
|
|
__ACL_ACCESS_MAX
|
|
};
|
|
|
|
static const struct blobmsg_policy acl_obj_policy[__ACL_ACCESS_MAX] = {
|
|
[ACL_ACCESS_METHODS] = { .name = "methods", .type = BLOBMSG_TYPE_ARRAY },
|
|
[ACL_ACCESS_TAGS] = { .name = "tags", .type = BLOBMSG_TYPE_ARRAY },
|
|
[ACL_ACCESS_PRIV] = { .name = "acl", .type = BLOBMSG_TYPE_TABLE },
|
|
};
|
|
|
|
static struct ubusd_acl_obj*
|
|
ubusd_acl_alloc_obj(struct ubusd_acl_file *file, const char *obj)
|
|
{
|
|
struct ubusd_acl_obj *o;
|
|
char *k;
|
|
|
|
o = calloc_a(sizeof(*o), &k, strlen(obj) + 1);
|
|
o->user = file->user;
|
|
o->group = file->group;
|
|
o->avl.key = k;
|
|
strcpy(k, obj);
|
|
|
|
while (*k) {
|
|
if (*k == '*')
|
|
*k = '\t';
|
|
k++;
|
|
}
|
|
|
|
list_add(&o->list, &file->acl);
|
|
avl_insert(&ubusd_acls, &o->avl);
|
|
|
|
return o;
|
|
}
|
|
|
|
static void
|
|
ubusd_acl_add_access(struct ubusd_acl_file *file, struct blob_attr *obj)
|
|
{
|
|
struct blob_attr *tb[__ACL_ACCESS_MAX];
|
|
struct ubusd_acl_obj *o;
|
|
|
|
blobmsg_parse(acl_obj_policy, __ACL_ACCESS_MAX, tb, blobmsg_data(obj),
|
|
blobmsg_data_len(obj));
|
|
|
|
if (!tb[ACL_ACCESS_METHODS] && !tb[ACL_ACCESS_TAGS] && !tb[ACL_ACCESS_PRIV])
|
|
return;
|
|
|
|
o = ubusd_acl_alloc_obj(file, blobmsg_name(obj));
|
|
|
|
o->methods = tb[ACL_ACCESS_METHODS];
|
|
o->tags = tb[ACL_ACCESS_TAGS];
|
|
o->priv = tb[ACL_ACCESS_PRIV];
|
|
|
|
if (file->user || file->group)
|
|
file->ok = 1;
|
|
}
|
|
|
|
static void
|
|
ubusd_acl_add_subscribe(struct ubusd_acl_file *file, const char *obj)
|
|
{
|
|
struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
|
|
|
|
o->subscribe = true;
|
|
}
|
|
|
|
static void
|
|
ubusd_acl_add_publish(struct ubusd_acl_file *file, const char *obj)
|
|
{
|
|
struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
|
|
|
|
o->publish = true;
|
|
}
|
|
|
|
enum {
|
|
ACL_USER,
|
|
ACL_GROUP,
|
|
ACL_ACCESS,
|
|
ACL_PUBLISH,
|
|
ACL_SUBSCRIBE,
|
|
ACL_INHERIT,
|
|
__ACL_MAX
|
|
};
|
|
|
|
static const struct blobmsg_policy acl_policy[__ACL_MAX] = {
|
|
[ACL_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING },
|
|
[ACL_GROUP] = { .name = "group", .type = BLOBMSG_TYPE_STRING },
|
|
[ACL_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_TABLE },
|
|
[ACL_PUBLISH] = { .name = "publish", .type = BLOBMSG_TYPE_ARRAY },
|
|
[ACL_SUBSCRIBE] = { .name = "subscribe", .type = BLOBMSG_TYPE_ARRAY },
|
|
[ACL_INHERIT] = { .name = "inherit", .type = BLOBMSG_TYPE_ARRAY },
|
|
};
|
|
|
|
static void
|
|
ubusd_acl_file_add(struct ubusd_acl_file *file)
|
|
{
|
|
struct blob_attr *tb[__ACL_MAX], *cur;
|
|
int rem;
|
|
|
|
blobmsg_parse(acl_policy, __ACL_MAX, tb, blob_data(file->blob),
|
|
blob_len(file->blob));
|
|
|
|
if (tb[ACL_USER])
|
|
file->user = blobmsg_get_string(tb[ACL_USER]);
|
|
else if (tb[ACL_GROUP])
|
|
file->group = blobmsg_get_string(tb[ACL_GROUP]);
|
|
else
|
|
return;
|
|
|
|
if (tb[ACL_ACCESS])
|
|
blobmsg_for_each_attr(cur, tb[ACL_ACCESS], rem)
|
|
ubusd_acl_add_access(file, cur);
|
|
|
|
if (tb[ACL_SUBSCRIBE])
|
|
blobmsg_for_each_attr(cur, tb[ACL_SUBSCRIBE], rem)
|
|
if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
|
|
ubusd_acl_add_subscribe(file, blobmsg_get_string(cur));
|
|
|
|
if (tb[ACL_PUBLISH])
|
|
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));
|
|
}
|
|
|
|
static void
|
|
ubusd_acl_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
|
|
struct vlist_node *node_old)
|
|
{
|
|
struct ubusd_acl_file *file;
|
|
|
|
if (node_old) {
|
|
file = container_of(node_old, struct ubusd_acl_file, avl);
|
|
ubusd_acl_file_free(file);
|
|
}
|
|
|
|
if (node_new) {
|
|
file = container_of(node_new, struct ubusd_acl_file, avl);
|
|
ubusd_acl_file_add(file);
|
|
}
|
|
}
|
|
|
|
static struct ubus_msg_buf *
|
|
ubusd_create_sequence_event_msg(void *priv, const char *id)
|
|
{
|
|
void *s;
|
|
|
|
blob_buf_init(&b, 0);
|
|
blob_put_int32(&b, UBUS_ATTR_OBJID, 0);
|
|
blob_put_string(&b, UBUS_ATTR_METHOD, id);
|
|
s = blob_nest_start(&b, UBUS_ATTR_DATA);
|
|
blobmsg_add_u32(&b, "sequence", ubusd_acl_seq);
|
|
blob_nest_end(&b, s);
|
|
|
|
return ubus_msg_new(b.head, blob_raw_len(b.head), true);
|
|
}
|
|
|
|
static VLIST_TREE(ubusd_acl_files, avl_strcmp, ubusd_acl_update_cb, false, false);
|
|
|
|
static int
|
|
ubusd_acl_load_file(const char *filename)
|
|
{
|
|
struct ubusd_acl_file *file;
|
|
void *blob;
|
|
|
|
blob_buf_init(&bbuf, 0);
|
|
if (!blobmsg_add_json_from_file(&bbuf, filename)) {
|
|
syslog(LOG_ERR, "failed to parse %s\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
file = calloc_a(sizeof(*file), &blob, blob_raw_len(bbuf.head));
|
|
if (!file)
|
|
return -1;
|
|
|
|
file->blob = blob;
|
|
|
|
memcpy(blob, bbuf.head, blob_raw_len(bbuf.head));
|
|
INIT_LIST_HEAD(&file->acl);
|
|
|
|
vlist_add(&ubusd_acl_files, &file->avl, filename);
|
|
syslog(LOG_INFO, "loading %s\n", filename);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ubusd_acl_load(void)
|
|
{
|
|
struct stat st;
|
|
glob_t gl;
|
|
int j;
|
|
const char *suffix = "/*.json";
|
|
char *path = alloca(strlen(ubusd_acl_dir) + strlen(suffix) + 1);
|
|
|
|
sprintf(path, "%s%s", ubusd_acl_dir, suffix);
|
|
if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
|
|
return;
|
|
|
|
vlist_update(&ubusd_acl_files);
|
|
for (j = 0; j < gl.gl_pathc; j++) {
|
|
if (stat(gl.gl_pathv[j], &st) || !S_ISREG(st.st_mode))
|
|
continue;
|
|
|
|
if (st.st_uid || st.st_gid) {
|
|
syslog(LOG_ERR, "%s has wrong owner\n", gl.gl_pathv[j]);
|
|
continue;
|
|
}
|
|
if (st.st_mode & (S_IWOTH | S_IWGRP | S_IXOTH)) {
|
|
syslog(LOG_ERR, "%s has wrong permissions\n", gl.gl_pathv[j]);
|
|
continue;
|
|
}
|
|
ubusd_acl_load_file(gl.gl_pathv[j]);
|
|
}
|
|
|
|
globfree(&gl);
|
|
vlist_flush(&ubusd_acl_files);
|
|
ubusd_acl_seq++;
|
|
ubusd_send_event(NULL, "ubus.acl.sequence", ubusd_create_sequence_event_msg, NULL);
|
|
}
|
|
|
|
static void
|
|
ubusd_reply_add(struct ubus_object *obj)
|
|
{
|
|
struct ubusd_acl_obj *acl;
|
|
|
|
if (!obj->path.key)
|
|
return;
|
|
|
|
acl = avl_find_ge_element(&ubusd_acls, obj->path.key, acl, avl);
|
|
if (!acl)
|
|
return;
|
|
|
|
avl_for_element_to_last(&ubusd_acls, acl, acl, avl) {
|
|
void *c;
|
|
|
|
if (!acl->priv)
|
|
continue;
|
|
|
|
if (!ubusd_acl_match_path(obj->path.key, acl->avl.key, NULL))
|
|
continue;
|
|
|
|
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;
|
|
void *d, *a;
|
|
|
|
if (!attr[UBUS_ATTR_OBJID])
|
|
return UBUS_STATUS_INVALID_ARGUMENT;
|
|
|
|
obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID]));
|
|
if (!obj)
|
|
return UBUS_STATUS_NOT_FOUND;
|
|
|
|
blob_buf_init(&b, 0);
|
|
blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id);
|
|
d = blob_nest_start(&b, UBUS_ATTR_DATA);
|
|
|
|
blobmsg_add_u32(&b, "seq", ubusd_acl_seq);
|
|
a = blobmsg_open_array(&b, "acl");
|
|
list_for_each_entry(obj, &cl->objects, list)
|
|
ubusd_reply_add(obj);
|
|
blobmsg_close_table(&b, a);
|
|
|
|
blob_nest_end(&b, d);
|
|
|
|
ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 UBUS_STATUS_INVALID_COMMAND;
|
|
}
|
|
|
|
void ubusd_acl_init(void)
|
|
{
|
|
avl_init(&ubusd_acls, ubusd_acl_match_path, true, NULL);
|
|
acl_obj = ubusd_create_object_internal(NULL, UBUS_SYSTEM_OBJECT_ACL);
|
|
acl_obj->recv_msg = ubusd_acl_recv;
|
|
}
|