2 * luci-rpcd - LuCI UBUS RPC server
4 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
5 * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include <libubox/avl-cmp.h>
21 #include <libubox/utils.h>
27 static struct avl_tree sessions;
28 static struct blob_buf buf;
30 static const struct blobmsg_policy new_policy = {
31 .name = "timeout", .type = BLOBMSG_TYPE_INT32
34 static const struct blobmsg_policy sid_policy = {
35 .name = "sid", .type = BLOBMSG_TYPE_STRING
43 static const struct blobmsg_policy set_policy[__RPC_SS_MAX] = {
44 [RPC_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
45 [RPC_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE },
53 static const struct blobmsg_policy get_policy[__RPC_SG_MAX] = {
54 [RPC_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
55 [RPC_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY },
64 static const struct blobmsg_policy acl_policy[__RPC_SA_MAX] = {
65 [RPC_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
66 [RPC_SA_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING },
67 [RPC_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY },
77 static const struct blobmsg_policy perm_policy[__RPC_SP_MAX] = {
78 [RPC_SP_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
79 [RPC_SP_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING },
80 [RPC_SP_OBJECT] = { .name = "object", .type = BLOBMSG_TYPE_STRING },
81 [RPC_SP_FUNCTION] = { .name = "function", .type = BLOBMSG_TYPE_STRING },
85 * Keys in the AVL tree contain all pattern characters up to the first wildcard.
86 * To look up entries, start with the last entry that has a key less than or
87 * equal to the method name, then work backwards as long as the AVL key still
88 * matches its counterpart in the object name
90 #define uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \
91 for (_acl = avl_find_le_element(_avl, _obj, _acl, avl); \
93 _acl = avl_is_first(_avl, &(_acl)->avl) ? NULL : \
94 avl_prev_element((_acl), avl))
96 #define uh_foreach_matching_acl(_acl, _avl, _obj, _func) \
97 uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \
98 if (!strncmp((_acl)->object, _obj, (_acl)->sort_len) && \
99 !fnmatch((_acl)->object, (_obj), FNM_NOESCAPE) && \
100 !fnmatch((_acl)->function, (_func), FNM_NOESCAPE))
103 rpc_random(char *dest)
105 unsigned char buf[16] = { 0 };
109 f = fopen("/dev/urandom", "r");
113 fread(buf, 1, sizeof(buf), f);
116 for (i = 0; i < sizeof(buf); i++)
117 sprintf(dest + (i<<1), "%02x", buf[i]);
121 rpc_session_dump_data(struct rpc_session *ses, struct blob_buf *b)
123 struct rpc_session_data *d;
125 avl_for_each_element(&ses->data, d, avl) {
126 blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr),
127 blobmsg_data(d->attr), blobmsg_data_len(d->attr));
132 rpc_session_dump_acls(struct rpc_session *ses, struct blob_buf *b)
134 struct rpc_session_acl *acl;
135 struct rpc_session_acl_scope *acl_scope;
136 const char *lastobj = NULL;
137 const char *lastscope = NULL;
138 void *c = NULL, *d = NULL;
140 avl_for_each_element(&ses->acls, acl_scope, avl) {
141 if (!lastscope || strcmp(acl_scope->avl.key, lastscope))
143 if (c) blobmsg_close_table(b, c);
144 c = blobmsg_open_table(b, acl_scope->avl.key);
150 avl_for_each_element(&acl_scope->acls, acl, avl) {
151 if (!lastobj || strcmp(acl->object, lastobj))
153 if (d) blobmsg_close_array(b, d);
154 d = blobmsg_open_array(b, acl->object);
157 blobmsg_add_string(b, NULL, acl->function);
158 lastobj = acl->object;
161 if (d) blobmsg_close_array(b, d);
164 if (c) blobmsg_close_table(b, c);
168 rpc_session_dump(struct rpc_session *ses,
169 struct ubus_context *ctx,
170 struct ubus_request_data *req)
174 blob_buf_init(&buf, 0);
176 blobmsg_add_string(&buf, "sid", ses->id);
177 blobmsg_add_u32(&buf, "timeout", ses->timeout);
178 blobmsg_add_u32(&buf, "expires", uloop_timeout_remaining(&ses->t) / 1000);
180 c = blobmsg_open_table(&buf, "acls");
181 rpc_session_dump_acls(ses, &buf);
182 blobmsg_close_table(&buf, c);
184 c = blobmsg_open_table(&buf, "data");
185 rpc_session_dump_data(ses, &buf);
186 blobmsg_close_table(&buf, c);
188 ubus_send_reply(ctx, req, buf.head);
192 rpc_touch_session(struct rpc_session *ses)
194 uloop_timeout_set(&ses->t, ses->timeout * 1000);
198 rpc_session_destroy(struct rpc_session *ses)
200 struct rpc_session_acl *acl, *nacl;
201 struct rpc_session_acl_scope *acl_scope, *nacl_scope;
202 struct rpc_session_data *data, *ndata;
204 uloop_timeout_cancel(&ses->t);
206 avl_for_each_element_safe(&ses->acls, acl_scope, avl, nacl_scope) {
207 avl_remove_all_elements(&acl_scope->acls, acl, avl, nacl)
210 avl_delete(&ses->acls, &acl_scope->avl);
214 avl_remove_all_elements(&ses->data, data, avl, ndata)
217 avl_delete(&sessions, &ses->avl);
221 static void rpc_session_timeout(struct uloop_timeout *t)
223 struct rpc_session *ses;
225 ses = container_of(t, struct rpc_session, t);
226 rpc_session_destroy(ses);
229 static struct rpc_session *
230 rpc_session_create(int timeout)
232 struct rpc_session *ses;
234 ses = calloc(1, sizeof(*ses));
238 ses->timeout = timeout;
239 ses->avl.key = ses->id;
242 avl_insert(&sessions, &ses->avl);
243 avl_init(&ses->acls, avl_strcmp, true, NULL);
244 avl_init(&ses->data, avl_strcmp, false, NULL);
246 ses->t.cb = rpc_session_timeout;
247 rpc_touch_session(ses);
252 static struct rpc_session *
253 rpc_session_get(const char *id)
255 struct rpc_session *ses;
257 ses = avl_find_element(&sessions, id, ses, avl);
261 rpc_touch_session(ses);
266 rpc_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
267 struct ubus_request_data *req, const char *method,
268 struct blob_attr *msg)
270 struct rpc_session *ses;
271 struct blob_attr *tb;
272 int timeout = RPC_DEFAULT_SESSION_TIMEOUT;
274 blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
276 timeout = blobmsg_get_u32(tb);
278 ses = rpc_session_create(timeout);
280 rpc_session_dump(ses, ctx, req);
286 rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
287 struct ubus_request_data *req, const char *method,
288 struct blob_attr *msg)
290 struct rpc_session *ses;
291 struct blob_attr *tb;
293 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
296 avl_for_each_element(&sessions, ses, avl)
297 rpc_session_dump(ses, ctx, req);
301 ses = rpc_session_get(blobmsg_data(tb));
303 return UBUS_STATUS_NOT_FOUND;
305 rpc_session_dump(ses, ctx, req);
311 uh_id_len(const char *str)
313 return strcspn(str, "*?[");
317 rpc_session_grant(struct rpc_session *ses, struct ubus_context *ctx,
318 const char *scope, const char *object, const char *function)
320 struct rpc_session_acl *acl;
321 struct rpc_session_acl_scope *acl_scope;
322 char *new_scope, *new_obj, *new_func, *new_id;
325 if (!object || !function)
326 return UBUS_STATUS_INVALID_ARGUMENT;
328 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
331 uh_foreach_matching_acl_prefix(acl, &acl_scope->acls, object, function) {
332 if (!strcmp(acl->object, object) &&
333 !strcmp(acl->function, function))
339 acl_scope = calloc_a(sizeof(*acl_scope),
340 &new_scope, strlen(scope) + 1);
343 return UBUS_STATUS_UNKNOWN_ERROR;
345 acl_scope->avl.key = strcpy(new_scope, scope);
346 avl_init(&acl_scope->acls, avl_strcmp, true, NULL);
347 avl_insert(&ses->acls, &acl_scope->avl);
350 id_len = uh_id_len(object);
351 acl = calloc_a(sizeof(*acl),
352 &new_obj, strlen(object) + 1,
353 &new_func, strlen(function) + 1,
354 &new_id, id_len + 1);
357 return UBUS_STATUS_UNKNOWN_ERROR;
359 acl->object = strcpy(new_obj, object);
360 acl->function = strcpy(new_func, function);
361 acl->avl.key = strncpy(new_id, object, id_len);
362 avl_insert(&acl_scope->acls, &acl->avl);
368 rpc_session_revoke(struct rpc_session *ses, struct ubus_context *ctx,
369 const char *scope, const char *object, const char *function)
371 struct rpc_session_acl *acl, *next;
372 struct rpc_session_acl_scope *acl_scope;
376 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
381 if (!object && !function) {
382 avl_remove_all_elements(&acl_scope->acls, acl, avl, next)
384 avl_delete(&ses->acls, &acl_scope->avl);
389 id_len = uh_id_len(object);
390 id = alloca(id_len + 1);
391 strncpy(id, object, id_len);
394 acl = avl_find_element(&acl_scope->acls, id, acl, avl);
396 if (!avl_is_last(&acl_scope->acls, &acl->avl))
397 next = avl_next_element(acl, avl);
401 if (strcmp(id, acl->avl.key) != 0)
404 if (!strcmp(acl->object, object) &&
405 !strcmp(acl->function, function)) {
406 avl_delete(&acl_scope->acls, &acl->avl);
412 if (avl_is_empty(&acl_scope->acls)) {
413 avl_delete(&ses->acls, &acl_scope->avl);
422 rpc_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
423 struct ubus_request_data *req, const char *method,
424 struct blob_attr *msg)
426 struct rpc_session *ses;
427 struct blob_attr *tb[__RPC_SA_MAX];
428 struct blob_attr *attr, *sattr;
429 const char *object, *function;
430 const char *scope = "ubus";
433 int (*cb)(struct rpc_session *ses, struct ubus_context *ctx,
434 const char *scope, const char *object, const char *function);
436 blobmsg_parse(acl_policy, __RPC_SA_MAX, tb, blob_data(msg), blob_len(msg));
439 return UBUS_STATUS_INVALID_ARGUMENT;
441 ses = rpc_session_get(blobmsg_data(tb[RPC_SA_SID]));
443 return UBUS_STATUS_NOT_FOUND;
445 if (tb[RPC_SA_SCOPE])
446 scope = blobmsg_data(tb[RPC_SA_SCOPE]);
448 if (!strcmp(method, "grant"))
449 cb = rpc_session_grant;
451 cb = rpc_session_revoke;
453 if (!tb[RPC_SA_OBJECTS])
454 return cb(ses, ctx, scope, NULL, NULL);
456 blobmsg_for_each_attr(attr, tb[RPC_SA_OBJECTS], rem1) {
457 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
463 blobmsg_for_each_attr(sattr, attr, rem2) {
464 if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
468 object = blobmsg_data(sattr);
470 function = blobmsg_data(sattr);
475 if (object && function)
476 cb(ses, ctx, scope, object, function);
483 rpc_session_acl_allowed(struct rpc_session *ses, const char *scope,
484 const char *obj, const char *fun)
486 struct rpc_session_acl *acl;
487 struct rpc_session_acl_scope *acl_scope;
489 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
492 uh_foreach_matching_acl(acl, &acl_scope->acls, obj, fun)
500 rpc_handle_access(struct ubus_context *ctx, struct ubus_object *obj,
501 struct ubus_request_data *req, const char *method,
502 struct blob_attr *msg)
504 struct rpc_session *ses;
505 struct blob_attr *tb[__RPC_SP_MAX];
506 const char *scope = "ubus";
509 blobmsg_parse(perm_policy, __RPC_SP_MAX, tb, blob_data(msg), blob_len(msg));
511 if (!tb[RPC_SP_SID] || !tb[RPC_SP_OBJECT] || !tb[RPC_SP_FUNCTION])
512 return UBUS_STATUS_INVALID_ARGUMENT;
514 ses = rpc_session_get(blobmsg_data(tb[RPC_SP_SID]));
516 return UBUS_STATUS_NOT_FOUND;
518 if (tb[RPC_SP_SCOPE])
519 scope = blobmsg_data(tb[RPC_SP_SCOPE]);
521 allow = rpc_session_acl_allowed(ses, scope,
522 blobmsg_data(tb[RPC_SP_OBJECT]),
523 blobmsg_data(tb[RPC_SP_FUNCTION]));
525 blob_buf_init(&buf, 0);
526 blobmsg_add_u8(&buf, "access", allow);
527 ubus_send_reply(ctx, req, buf.head);
533 rpc_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
534 struct ubus_request_data *req, const char *method,
535 struct blob_attr *msg)
537 struct rpc_session *ses;
538 struct rpc_session_data *data;
539 struct blob_attr *tb[__RPC_SA_MAX];
540 struct blob_attr *attr;
543 blobmsg_parse(set_policy, __RPC_SS_MAX, tb, blob_data(msg), blob_len(msg));
545 if (!tb[RPC_SS_SID] || !tb[RPC_SS_VALUES])
546 return UBUS_STATUS_INVALID_ARGUMENT;
548 ses = rpc_session_get(blobmsg_data(tb[RPC_SS_SID]));
550 return UBUS_STATUS_NOT_FOUND;
552 blobmsg_for_each_attr(attr, tb[RPC_SS_VALUES], rem) {
553 if (!blobmsg_name(attr)[0])
556 data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
558 avl_delete(&ses->data, &data->avl);
562 data = calloc(1, sizeof(*data) + blob_pad_len(attr));
566 memcpy(data->attr, attr, blob_pad_len(attr));
567 data->avl.key = blobmsg_name(data->attr);
568 avl_insert(&ses->data, &data->avl);
575 rpc_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
576 struct ubus_request_data *req, const char *method,
577 struct blob_attr *msg)
579 struct rpc_session *ses;
580 struct rpc_session_data *data;
581 struct blob_attr *tb[__RPC_SA_MAX];
582 struct blob_attr *attr;
586 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
589 return UBUS_STATUS_INVALID_ARGUMENT;
591 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
593 return UBUS_STATUS_NOT_FOUND;
595 blob_buf_init(&buf, 0);
596 c = blobmsg_open_table(&buf, "values");
599 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
600 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
603 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
607 blobmsg_add_field(&buf, blobmsg_type(data->attr),
608 blobmsg_name(data->attr),
609 blobmsg_data(data->attr),
610 blobmsg_data_len(data->attr));
613 rpc_session_dump_data(ses, &buf);
615 blobmsg_close_table(&buf, c);
616 ubus_send_reply(ctx, req, buf.head);
622 rpc_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
623 struct ubus_request_data *req, const char *method,
624 struct blob_attr *msg)
626 struct rpc_session *ses;
627 struct rpc_session_data *data, *ndata;
628 struct blob_attr *tb[__RPC_SA_MAX];
629 struct blob_attr *attr;
632 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
635 return UBUS_STATUS_INVALID_ARGUMENT;
637 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
639 return UBUS_STATUS_NOT_FOUND;
641 if (!tb[RPC_SG_KEYS]) {
642 avl_remove_all_elements(&ses->data, data, avl, ndata)
647 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
648 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
651 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
655 avl_delete(&ses->data, &data->avl);
663 rpc_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
664 struct ubus_request_data *req, const char *method,
665 struct blob_attr *msg)
667 struct rpc_session *ses;
668 struct blob_attr *tb;
670 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
673 return UBUS_STATUS_INVALID_ARGUMENT;
675 ses = rpc_session_get(blobmsg_data(tb));
677 return UBUS_STATUS_NOT_FOUND;
679 rpc_session_destroy(ses);
684 int rpc_session_api_init(struct ubus_context *ctx)
686 static const struct ubus_method session_methods[] = {
687 UBUS_METHOD("create", rpc_handle_create, &new_policy),
688 UBUS_METHOD("list", rpc_handle_list, &sid_policy),
689 UBUS_METHOD("grant", rpc_handle_acl, acl_policy),
690 UBUS_METHOD("revoke", rpc_handle_acl, acl_policy),
691 UBUS_METHOD("access", rpc_handle_access, perm_policy),
692 UBUS_METHOD("set", rpc_handle_set, set_policy),
693 UBUS_METHOD("get", rpc_handle_get, get_policy),
694 UBUS_METHOD("unset", rpc_handle_unset, get_policy),
695 UBUS_METHOD("destroy", rpc_handle_destroy, &sid_policy),
698 static struct ubus_object_type session_type =
699 UBUS_OBJECT_TYPE("luci-rpc-session", session_methods);
701 static struct ubus_object obj = {
703 .type = &session_type,
704 .methods = session_methods,
705 .n_methods = ARRAY_SIZE(session_methods),
708 avl_init(&sessions, avl_strcmp, false, NULL);
710 return ubus_add_object(ctx, &obj);
713 bool rpc_session_access(const char *sid, const char *scope,
714 const char *object, const char *function)
716 struct rpc_session *ses = rpc_session_get(sid);
721 return rpc_session_acl_allowed(ses, scope, object, function);