session: fix acl dumping if last object of previous scope is equal to first object...
[project/rpcd.git] / session.c
1 /*
2 * luci-rpcd - LuCI UBUS RPC server
3 *
4 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
5 * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
6 *
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.
10 *
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.
18 */
19
20 #include <libubox/avl-cmp.h>
21 #include <libubox/utils.h>
22 #include <libubus.h>
23 #include <fnmatch.h>
24
25 #include "session.h"
26
27 static struct avl_tree sessions;
28 static struct blob_buf buf;
29
30 static const struct blobmsg_policy new_policy = {
31 .name = "timeout", .type = BLOBMSG_TYPE_INT32
32 };
33
34 static const struct blobmsg_policy sid_policy = {
35 .name = "sid", .type = BLOBMSG_TYPE_STRING
36 };
37
38 enum {
39 RPC_SS_SID,
40 RPC_SS_VALUES,
41 __RPC_SS_MAX,
42 };
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 },
46 };
47
48 enum {
49 RPC_SG_SID,
50 RPC_SG_KEYS,
51 __RPC_SG_MAX,
52 };
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 },
56 };
57
58 enum {
59 RPC_SA_SID,
60 RPC_SA_SCOPE,
61 RPC_SA_OBJECTS,
62 __RPC_SA_MAX,
63 };
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 },
68 };
69
70 enum {
71 RPC_SP_SID,
72 RPC_SP_SCOPE,
73 RPC_SP_OBJECT,
74 RPC_SP_FUNCTION,
75 __RPC_SP_MAX,
76 };
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 },
82 };
83
84 /*
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
89 */
90 #define uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \
91 for (_acl = avl_find_le_element(_avl, _obj, _acl, avl); \
92 _acl; \
93 _acl = avl_is_first(_avl, &(_acl)->avl) ? NULL : \
94 avl_prev_element((_acl), avl))
95
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))
101
102 static void
103 rpc_random(char *dest)
104 {
105 unsigned char buf[16] = { 0 };
106 FILE *f;
107 int i;
108
109 f = fopen("/dev/urandom", "r");
110 if (!f)
111 return;
112
113 fread(buf, 1, sizeof(buf), f);
114 fclose(f);
115
116 for (i = 0; i < sizeof(buf); i++)
117 sprintf(dest + (i<<1), "%02x", buf[i]);
118 }
119
120 static void
121 rpc_session_dump_data(struct rpc_session *ses, struct blob_buf *b)
122 {
123 struct rpc_session_data *d;
124
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));
128 }
129 }
130
131 static void
132 rpc_session_dump_acls(struct rpc_session *ses, struct blob_buf *b)
133 {
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;
139
140 avl_for_each_element(&ses->acls, acl_scope, avl) {
141 if (!lastscope || strcmp(acl_scope->avl.key, lastscope))
142 {
143 if (c) blobmsg_close_table(b, c);
144 c = blobmsg_open_table(b, acl_scope->avl.key);
145 lastobj = NULL;
146 }
147
148 d = NULL;
149
150 avl_for_each_element(&acl_scope->acls, acl, avl) {
151 if (!lastobj || strcmp(acl->object, lastobj))
152 {
153 if (d) blobmsg_close_array(b, d);
154 d = blobmsg_open_array(b, acl->object);
155 }
156
157 blobmsg_add_string(b, NULL, acl->function);
158 lastobj = acl->object;
159 }
160
161 if (d) blobmsg_close_array(b, d);
162 }
163
164 if (c) blobmsg_close_table(b, c);
165 }
166
167 static void
168 rpc_session_dump(struct rpc_session *ses,
169 struct ubus_context *ctx,
170 struct ubus_request_data *req)
171 {
172 void *c;
173
174 blob_buf_init(&buf, 0);
175
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);
179
180 c = blobmsg_open_table(&buf, "acls");
181 rpc_session_dump_acls(ses, &buf);
182 blobmsg_close_table(&buf, c);
183
184 c = blobmsg_open_table(&buf, "data");
185 rpc_session_dump_data(ses, &buf);
186 blobmsg_close_table(&buf, c);
187
188 ubus_send_reply(ctx, req, buf.head);
189 }
190
191 static void
192 rpc_touch_session(struct rpc_session *ses)
193 {
194 uloop_timeout_set(&ses->t, ses->timeout * 1000);
195 }
196
197 static void
198 rpc_session_destroy(struct rpc_session *ses)
199 {
200 struct rpc_session_acl *acl, *nacl;
201 struct rpc_session_acl_scope *acl_scope, *nacl_scope;
202 struct rpc_session_data *data, *ndata;
203
204 uloop_timeout_cancel(&ses->t);
205
206 avl_for_each_element_safe(&ses->acls, acl_scope, avl, nacl_scope) {
207 avl_remove_all_elements(&acl_scope->acls, acl, avl, nacl)
208 free(acl);
209
210 avl_delete(&ses->acls, &acl_scope->avl);
211 free(acl_scope);
212 }
213
214 avl_remove_all_elements(&ses->data, data, avl, ndata)
215 free(data);
216
217 avl_delete(&sessions, &ses->avl);
218 free(ses);
219 }
220
221 static void rpc_session_timeout(struct uloop_timeout *t)
222 {
223 struct rpc_session *ses;
224
225 ses = container_of(t, struct rpc_session, t);
226 rpc_session_destroy(ses);
227 }
228
229 static struct rpc_session *
230 rpc_session_create(int timeout)
231 {
232 struct rpc_session *ses;
233
234 ses = calloc(1, sizeof(*ses));
235 if (!ses)
236 return NULL;
237
238 ses->timeout = timeout;
239 ses->avl.key = ses->id;
240 rpc_random(ses->id);
241
242 avl_insert(&sessions, &ses->avl);
243 avl_init(&ses->acls, avl_strcmp, true, NULL);
244 avl_init(&ses->data, avl_strcmp, false, NULL);
245
246 ses->t.cb = rpc_session_timeout;
247 rpc_touch_session(ses);
248
249 return ses;
250 }
251
252 static struct rpc_session *
253 rpc_session_get(const char *id)
254 {
255 struct rpc_session *ses;
256
257 ses = avl_find_element(&sessions, id, ses, avl);
258 if (!ses)
259 return NULL;
260
261 rpc_touch_session(ses);
262 return ses;
263 }
264
265 static int
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)
269 {
270 struct rpc_session *ses;
271 struct blob_attr *tb;
272 int timeout = RPC_DEFAULT_SESSION_TIMEOUT;
273
274 blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
275 if (tb)
276 timeout = blobmsg_get_u32(tb);
277
278 ses = rpc_session_create(timeout);
279 if (ses)
280 rpc_session_dump(ses, ctx, req);
281
282 return 0;
283 }
284
285 static int
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)
289 {
290 struct rpc_session *ses;
291 struct blob_attr *tb;
292
293 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
294
295 if (!tb) {
296 avl_for_each_element(&sessions, ses, avl)
297 rpc_session_dump(ses, ctx, req);
298 return 0;
299 }
300
301 ses = rpc_session_get(blobmsg_data(tb));
302 if (!ses)
303 return UBUS_STATUS_NOT_FOUND;
304
305 rpc_session_dump(ses, ctx, req);
306
307 return 0;
308 }
309
310 static int
311 uh_id_len(const char *str)
312 {
313 return strcspn(str, "*?[");
314 }
315
316 static int
317 rpc_session_grant(struct rpc_session *ses, struct ubus_context *ctx,
318 const char *scope, const char *object, const char *function)
319 {
320 struct rpc_session_acl *acl;
321 struct rpc_session_acl_scope *acl_scope;
322 char *new_scope, *new_obj, *new_func, *new_id;
323 int id_len;
324
325 if (!object || !function)
326 return UBUS_STATUS_INVALID_ARGUMENT;
327
328 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
329
330 if (acl_scope) {
331 uh_foreach_matching_acl_prefix(acl, &acl_scope->acls, object, function) {
332 if (!strcmp(acl->object, object) &&
333 !strcmp(acl->function, function))
334 return 0;
335 }
336 }
337
338 if (!acl_scope) {
339 acl_scope = calloc_a(sizeof(*acl_scope),
340 &new_scope, strlen(scope) + 1);
341
342 if (!acl_scope)
343 return UBUS_STATUS_UNKNOWN_ERROR;
344
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);
348 }
349
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);
355
356 if (!acl)
357 return UBUS_STATUS_UNKNOWN_ERROR;
358
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);
363
364 return 0;
365 }
366
367 static int
368 rpc_session_revoke(struct rpc_session *ses, struct ubus_context *ctx,
369 const char *scope, const char *object, const char *function)
370 {
371 struct rpc_session_acl *acl, *next;
372 struct rpc_session_acl_scope *acl_scope;
373 int id_len;
374 char *id;
375
376 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
377
378 if (!acl_scope)
379 return 0;
380
381 if (!object && !function) {
382 avl_remove_all_elements(&acl_scope->acls, acl, avl, next)
383 free(acl);
384 avl_delete(&ses->acls, &acl_scope->avl);
385 free(acl_scope);
386 return 0;
387 }
388
389 id_len = uh_id_len(object);
390 id = alloca(id_len + 1);
391 strncpy(id, object, id_len);
392 id[id_len] = 0;
393
394 acl = avl_find_element(&acl_scope->acls, id, acl, avl);
395 while (acl) {
396 if (!avl_is_last(&acl_scope->acls, &acl->avl))
397 next = avl_next_element(acl, avl);
398 else
399 next = NULL;
400
401 if (strcmp(id, acl->avl.key) != 0)
402 break;
403
404 if (!strcmp(acl->object, object) &&
405 !strcmp(acl->function, function)) {
406 avl_delete(&acl_scope->acls, &acl->avl);
407 free(acl);
408 }
409 acl = next;
410 }
411
412 if (avl_is_empty(&acl_scope->acls)) {
413 avl_delete(&ses->acls, &acl_scope->avl);
414 free(acl_scope);
415 }
416
417 return 0;
418 }
419
420
421 static int
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)
425 {
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";
431 int rem1, rem2;
432
433 int (*cb)(struct rpc_session *ses, struct ubus_context *ctx,
434 const char *scope, const char *object, const char *function);
435
436 blobmsg_parse(acl_policy, __RPC_SA_MAX, tb, blob_data(msg), blob_len(msg));
437
438 if (!tb[RPC_SA_SID])
439 return UBUS_STATUS_INVALID_ARGUMENT;
440
441 ses = rpc_session_get(blobmsg_data(tb[RPC_SA_SID]));
442 if (!ses)
443 return UBUS_STATUS_NOT_FOUND;
444
445 if (tb[RPC_SA_SCOPE])
446 scope = blobmsg_data(tb[RPC_SA_SCOPE]);
447
448 if (!strcmp(method, "grant"))
449 cb = rpc_session_grant;
450 else
451 cb = rpc_session_revoke;
452
453 if (!tb[RPC_SA_OBJECTS])
454 return cb(ses, ctx, scope, NULL, NULL);
455
456 blobmsg_for_each_attr(attr, tb[RPC_SA_OBJECTS], rem1) {
457 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
458 continue;
459
460 object = NULL;
461 function = NULL;
462
463 blobmsg_for_each_attr(sattr, attr, rem2) {
464 if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
465 continue;
466
467 if (!object)
468 object = blobmsg_data(sattr);
469 else if (!function)
470 function = blobmsg_data(sattr);
471 else
472 break;
473 }
474
475 if (object && function)
476 cb(ses, ctx, scope, object, function);
477 }
478
479 return 0;
480 }
481
482 static bool
483 rpc_session_acl_allowed(struct rpc_session *ses, const char *scope,
484 const char *obj, const char *fun)
485 {
486 struct rpc_session_acl *acl;
487 struct rpc_session_acl_scope *acl_scope;
488
489 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
490
491 if (acl_scope) {
492 uh_foreach_matching_acl(acl, &acl_scope->acls, obj, fun)
493 return true;
494 }
495
496 return false;
497 }
498
499 static int
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)
503 {
504 struct rpc_session *ses;
505 struct blob_attr *tb[__RPC_SP_MAX];
506 const char *scope = "ubus";
507 bool allow;
508
509 blobmsg_parse(perm_policy, __RPC_SP_MAX, tb, blob_data(msg), blob_len(msg));
510
511 if (!tb[RPC_SP_SID] || !tb[RPC_SP_OBJECT] || !tb[RPC_SP_FUNCTION])
512 return UBUS_STATUS_INVALID_ARGUMENT;
513
514 ses = rpc_session_get(blobmsg_data(tb[RPC_SP_SID]));
515 if (!ses)
516 return UBUS_STATUS_NOT_FOUND;
517
518 if (tb[RPC_SP_SCOPE])
519 scope = blobmsg_data(tb[RPC_SP_SCOPE]);
520
521 allow = rpc_session_acl_allowed(ses, scope,
522 blobmsg_data(tb[RPC_SP_OBJECT]),
523 blobmsg_data(tb[RPC_SP_FUNCTION]));
524
525 blob_buf_init(&buf, 0);
526 blobmsg_add_u8(&buf, "access", allow);
527 ubus_send_reply(ctx, req, buf.head);
528
529 return 0;
530 }
531
532 static int
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)
536 {
537 struct rpc_session *ses;
538 struct rpc_session_data *data;
539 struct blob_attr *tb[__RPC_SA_MAX];
540 struct blob_attr *attr;
541 int rem;
542
543 blobmsg_parse(set_policy, __RPC_SS_MAX, tb, blob_data(msg), blob_len(msg));
544
545 if (!tb[RPC_SS_SID] || !tb[RPC_SS_VALUES])
546 return UBUS_STATUS_INVALID_ARGUMENT;
547
548 ses = rpc_session_get(blobmsg_data(tb[RPC_SS_SID]));
549 if (!ses)
550 return UBUS_STATUS_NOT_FOUND;
551
552 blobmsg_for_each_attr(attr, tb[RPC_SS_VALUES], rem) {
553 if (!blobmsg_name(attr)[0])
554 continue;
555
556 data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
557 if (data) {
558 avl_delete(&ses->data, &data->avl);
559 free(data);
560 }
561
562 data = calloc(1, sizeof(*data) + blob_pad_len(attr));
563 if (!data)
564 break;
565
566 memcpy(data->attr, attr, blob_pad_len(attr));
567 data->avl.key = blobmsg_name(data->attr);
568 avl_insert(&ses->data, &data->avl);
569 }
570
571 return 0;
572 }
573
574 static int
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)
578 {
579 struct rpc_session *ses;
580 struct rpc_session_data *data;
581 struct blob_attr *tb[__RPC_SA_MAX];
582 struct blob_attr *attr;
583 void *c;
584 int rem;
585
586 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
587
588 if (!tb[RPC_SG_SID])
589 return UBUS_STATUS_INVALID_ARGUMENT;
590
591 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
592 if (!ses)
593 return UBUS_STATUS_NOT_FOUND;
594
595 blob_buf_init(&buf, 0);
596 c = blobmsg_open_table(&buf, "values");
597
598 if (tb[RPC_SG_KEYS])
599 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
600 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
601 continue;
602
603 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
604 if (!data)
605 continue;
606
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));
611 }
612 else
613 rpc_session_dump_data(ses, &buf);
614
615 blobmsg_close_table(&buf, c);
616 ubus_send_reply(ctx, req, buf.head);
617
618 return 0;
619 }
620
621 static int
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)
625 {
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;
630 int rem;
631
632 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
633
634 if (!tb[RPC_SG_SID])
635 return UBUS_STATUS_INVALID_ARGUMENT;
636
637 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
638 if (!ses)
639 return UBUS_STATUS_NOT_FOUND;
640
641 if (!tb[RPC_SG_KEYS]) {
642 avl_remove_all_elements(&ses->data, data, avl, ndata)
643 free(data);
644 return 0;
645 }
646
647 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
648 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
649 continue;
650
651 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
652 if (!data)
653 continue;
654
655 avl_delete(&ses->data, &data->avl);
656 free(data);
657 }
658
659 return 0;
660 }
661
662 static int
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)
666 {
667 struct rpc_session *ses;
668 struct blob_attr *tb;
669
670 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
671
672 if (!tb)
673 return UBUS_STATUS_INVALID_ARGUMENT;
674
675 ses = rpc_session_get(blobmsg_data(tb));
676 if (!ses)
677 return UBUS_STATUS_NOT_FOUND;
678
679 rpc_session_destroy(ses);
680
681 return 0;
682 }
683
684 int rpc_session_api_init(struct ubus_context *ctx)
685 {
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),
696 };
697
698 static struct ubus_object_type session_type =
699 UBUS_OBJECT_TYPE("luci-rpc-session", session_methods);
700
701 static struct ubus_object obj = {
702 .name = "session",
703 .type = &session_type,
704 .methods = session_methods,
705 .n_methods = ARRAY_SIZE(session_methods),
706 };
707
708 avl_init(&sessions, avl_strcmp, false, NULL);
709
710 return ubus_add_object(ctx, &obj);
711 }