9f42226c4d178f37505105fd7e3d3ab6a3fd43ed
[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 }
146
147 d = NULL;
148
149 avl_for_each_element(&acl_scope->acls, acl, avl) {
150 if (!lastobj || strcmp(acl->object, lastobj))
151 {
152 if (d) blobmsg_close_array(b, d);
153 d = blobmsg_open_array(b, acl->object);
154 }
155
156 blobmsg_add_string(b, NULL, acl->function);
157 lastobj = acl->object;
158 }
159
160 if (d) blobmsg_close_array(b, d);
161 }
162
163 if (c) blobmsg_close_table(b, c);
164 }
165
166 static void
167 rpc_session_dump(struct rpc_session *ses,
168 struct ubus_context *ctx,
169 struct ubus_request_data *req)
170 {
171 void *c;
172
173 blob_buf_init(&buf, 0);
174
175 blobmsg_add_string(&buf, "sid", ses->id);
176 blobmsg_add_u32(&buf, "timeout", ses->timeout);
177 blobmsg_add_u32(&buf, "expires", uloop_timeout_remaining(&ses->t) / 1000);
178
179 c = blobmsg_open_table(&buf, "acls");
180 rpc_session_dump_acls(ses, &buf);
181 blobmsg_close_table(&buf, c);
182
183 c = blobmsg_open_table(&buf, "data");
184 rpc_session_dump_data(ses, &buf);
185 blobmsg_close_table(&buf, c);
186
187 ubus_send_reply(ctx, req, buf.head);
188 }
189
190 static void
191 rpc_touch_session(struct rpc_session *ses)
192 {
193 uloop_timeout_set(&ses->t, ses->timeout * 1000);
194 }
195
196 static void
197 rpc_session_destroy(struct rpc_session *ses)
198 {
199 struct rpc_session_acl *acl, *nacl;
200 struct rpc_session_acl_scope *acl_scope, *nacl_scope;
201 struct rpc_session_data *data, *ndata;
202
203 uloop_timeout_cancel(&ses->t);
204
205 avl_for_each_element_safe(&ses->acls, acl_scope, avl, nacl_scope) {
206 avl_remove_all_elements(&acl_scope->acls, acl, avl, nacl)
207 free(acl);
208
209 avl_delete(&ses->acls, &acl_scope->avl);
210 free(acl_scope);
211 }
212
213 avl_remove_all_elements(&ses->data, data, avl, ndata)
214 free(data);
215
216 avl_delete(&sessions, &ses->avl);
217 free(ses);
218 }
219
220 static void rpc_session_timeout(struct uloop_timeout *t)
221 {
222 struct rpc_session *ses;
223
224 ses = container_of(t, struct rpc_session, t);
225 rpc_session_destroy(ses);
226 }
227
228 static struct rpc_session *
229 rpc_session_create(int timeout)
230 {
231 struct rpc_session *ses;
232
233 ses = calloc(1, sizeof(*ses));
234 if (!ses)
235 return NULL;
236
237 ses->timeout = timeout;
238 ses->avl.key = ses->id;
239 rpc_random(ses->id);
240
241 avl_insert(&sessions, &ses->avl);
242 avl_init(&ses->acls, avl_strcmp, true, NULL);
243 avl_init(&ses->data, avl_strcmp, false, NULL);
244
245 ses->t.cb = rpc_session_timeout;
246 rpc_touch_session(ses);
247
248 return ses;
249 }
250
251 static struct rpc_session *
252 rpc_session_get(const char *id)
253 {
254 struct rpc_session *ses;
255
256 ses = avl_find_element(&sessions, id, ses, avl);
257 if (!ses)
258 return NULL;
259
260 rpc_touch_session(ses);
261 return ses;
262 }
263
264 static int
265 rpc_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
266 struct ubus_request_data *req, const char *method,
267 struct blob_attr *msg)
268 {
269 struct rpc_session *ses;
270 struct blob_attr *tb;
271 int timeout = RPC_DEFAULT_SESSION_TIMEOUT;
272
273 blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
274 if (tb)
275 timeout = blobmsg_get_u32(tb);
276
277 ses = rpc_session_create(timeout);
278 if (ses)
279 rpc_session_dump(ses, ctx, req);
280
281 return 0;
282 }
283
284 static int
285 rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
286 struct ubus_request_data *req, const char *method,
287 struct blob_attr *msg)
288 {
289 struct rpc_session *ses;
290 struct blob_attr *tb;
291
292 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
293
294 if (!tb) {
295 avl_for_each_element(&sessions, ses, avl)
296 rpc_session_dump(ses, ctx, req);
297 return 0;
298 }
299
300 ses = rpc_session_get(blobmsg_data(tb));
301 if (!ses)
302 return UBUS_STATUS_NOT_FOUND;
303
304 rpc_session_dump(ses, ctx, req);
305
306 return 0;
307 }
308
309 static int
310 uh_id_len(const char *str)
311 {
312 return strcspn(str, "*?[");
313 }
314
315 static int
316 rpc_session_grant(struct rpc_session *ses, struct ubus_context *ctx,
317 const char *scope, const char *object, const char *function)
318 {
319 struct rpc_session_acl *acl;
320 struct rpc_session_acl_scope *acl_scope;
321 char *new_scope, *new_obj, *new_func, *new_id;
322 int id_len;
323
324 if (!object || !function)
325 return UBUS_STATUS_INVALID_ARGUMENT;
326
327 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
328
329 if (acl_scope) {
330 uh_foreach_matching_acl_prefix(acl, &acl_scope->acls, object, function) {
331 if (!strcmp(acl->object, object) &&
332 !strcmp(acl->function, function))
333 return 0;
334 }
335 }
336
337 if (!acl_scope) {
338 acl_scope = calloc_a(sizeof(*acl_scope),
339 &new_scope, strlen(scope) + 1);
340
341 if (!acl_scope)
342 return UBUS_STATUS_UNKNOWN_ERROR;
343
344 acl_scope->avl.key = strcpy(new_scope, scope);
345 avl_init(&acl_scope->acls, avl_strcmp, true, NULL);
346 avl_insert(&ses->acls, &acl_scope->avl);
347 }
348
349 id_len = uh_id_len(object);
350 acl = calloc_a(sizeof(*acl),
351 &new_obj, strlen(object) + 1,
352 &new_func, strlen(function) + 1,
353 &new_id, id_len + 1);
354
355 if (!acl)
356 return UBUS_STATUS_UNKNOWN_ERROR;
357
358 acl->object = strcpy(new_obj, object);
359 acl->function = strcpy(new_func, function);
360 acl->avl.key = strncpy(new_id, object, id_len);
361 avl_insert(&acl_scope->acls, &acl->avl);
362
363 return 0;
364 }
365
366 static int
367 rpc_session_revoke(struct rpc_session *ses, struct ubus_context *ctx,
368 const char *scope, const char *object, const char *function)
369 {
370 struct rpc_session_acl *acl, *next;
371 struct rpc_session_acl_scope *acl_scope;
372 int id_len;
373 char *id;
374
375 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
376
377 if (!acl_scope)
378 return 0;
379
380 if (!object && !function) {
381 avl_remove_all_elements(&acl_scope->acls, acl, avl, next)
382 free(acl);
383 avl_delete(&ses->acls, &acl_scope->avl);
384 free(acl_scope);
385 return 0;
386 }
387
388 id_len = uh_id_len(object);
389 id = alloca(id_len + 1);
390 strncpy(id, object, id_len);
391 id[id_len] = 0;
392
393 acl = avl_find_element(&acl_scope->acls, id, acl, avl);
394 while (acl) {
395 if (!avl_is_last(&acl_scope->acls, &acl->avl))
396 next = avl_next_element(acl, avl);
397 else
398 next = NULL;
399
400 if (strcmp(id, acl->avl.key) != 0)
401 break;
402
403 if (!strcmp(acl->object, object) &&
404 !strcmp(acl->function, function)) {
405 avl_delete(&acl_scope->acls, &acl->avl);
406 free(acl);
407 }
408 acl = next;
409 }
410
411 if (avl_is_empty(&acl_scope->acls)) {
412 avl_delete(&ses->acls, &acl_scope->avl);
413 free(acl_scope);
414 }
415
416 return 0;
417 }
418
419
420 static int
421 rpc_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
422 struct ubus_request_data *req, const char *method,
423 struct blob_attr *msg)
424 {
425 struct rpc_session *ses;
426 struct blob_attr *tb[__RPC_SA_MAX];
427 struct blob_attr *attr, *sattr;
428 const char *object, *function;
429 const char *scope = "ubus";
430 int rem1, rem2;
431
432 int (*cb)(struct rpc_session *ses, struct ubus_context *ctx,
433 const char *scope, const char *object, const char *function);
434
435 blobmsg_parse(acl_policy, __RPC_SA_MAX, tb, blob_data(msg), blob_len(msg));
436
437 if (!tb[RPC_SA_SID])
438 return UBUS_STATUS_INVALID_ARGUMENT;
439
440 ses = rpc_session_get(blobmsg_data(tb[RPC_SA_SID]));
441 if (!ses)
442 return UBUS_STATUS_NOT_FOUND;
443
444 if (tb[RPC_SA_SCOPE])
445 scope = blobmsg_data(tb[RPC_SA_SCOPE]);
446
447 if (!strcmp(method, "grant"))
448 cb = rpc_session_grant;
449 else
450 cb = rpc_session_revoke;
451
452 if (!tb[RPC_SA_OBJECTS])
453 return cb(ses, ctx, scope, NULL, NULL);
454
455 blobmsg_for_each_attr(attr, tb[RPC_SA_OBJECTS], rem1) {
456 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
457 continue;
458
459 object = NULL;
460 function = NULL;
461
462 blobmsg_for_each_attr(sattr, attr, rem2) {
463 if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
464 continue;
465
466 if (!object)
467 object = blobmsg_data(sattr);
468 else if (!function)
469 function = blobmsg_data(sattr);
470 else
471 break;
472 }
473
474 if (object && function)
475 cb(ses, ctx, scope, object, function);
476 }
477
478 return 0;
479 }
480
481 static bool
482 rpc_session_acl_allowed(struct rpc_session *ses, const char *scope,
483 const char *obj, const char *fun)
484 {
485 struct rpc_session_acl *acl;
486 struct rpc_session_acl_scope *acl_scope;
487
488 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
489
490 if (acl_scope) {
491 uh_foreach_matching_acl(acl, &acl_scope->acls, obj, fun)
492 return true;
493 }
494
495 return false;
496 }
497
498 static int
499 rpc_handle_access(struct ubus_context *ctx, struct ubus_object *obj,
500 struct ubus_request_data *req, const char *method,
501 struct blob_attr *msg)
502 {
503 struct rpc_session *ses;
504 struct blob_attr *tb[__RPC_SP_MAX];
505 const char *scope = "ubus";
506 bool allow;
507
508 blobmsg_parse(perm_policy, __RPC_SP_MAX, tb, blob_data(msg), blob_len(msg));
509
510 if (!tb[RPC_SP_SID] || !tb[RPC_SP_OBJECT] || !tb[RPC_SP_FUNCTION])
511 return UBUS_STATUS_INVALID_ARGUMENT;
512
513 ses = rpc_session_get(blobmsg_data(tb[RPC_SP_SID]));
514 if (!ses)
515 return UBUS_STATUS_NOT_FOUND;
516
517 if (tb[RPC_SP_SCOPE])
518 scope = blobmsg_data(tb[RPC_SP_SCOPE]);
519
520 allow = rpc_session_acl_allowed(ses, scope,
521 blobmsg_data(tb[RPC_SP_OBJECT]),
522 blobmsg_data(tb[RPC_SP_FUNCTION]));
523
524 blob_buf_init(&buf, 0);
525 blobmsg_add_u8(&buf, "access", allow);
526 ubus_send_reply(ctx, req, buf.head);
527
528 return 0;
529 }
530
531 static int
532 rpc_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
533 struct ubus_request_data *req, const char *method,
534 struct blob_attr *msg)
535 {
536 struct rpc_session *ses;
537 struct rpc_session_data *data;
538 struct blob_attr *tb[__RPC_SA_MAX];
539 struct blob_attr *attr;
540 int rem;
541
542 blobmsg_parse(set_policy, __RPC_SS_MAX, tb, blob_data(msg), blob_len(msg));
543
544 if (!tb[RPC_SS_SID] || !tb[RPC_SS_VALUES])
545 return UBUS_STATUS_INVALID_ARGUMENT;
546
547 ses = rpc_session_get(blobmsg_data(tb[RPC_SS_SID]));
548 if (!ses)
549 return UBUS_STATUS_NOT_FOUND;
550
551 blobmsg_for_each_attr(attr, tb[RPC_SS_VALUES], rem) {
552 if (!blobmsg_name(attr)[0])
553 continue;
554
555 data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
556 if (data) {
557 avl_delete(&ses->data, &data->avl);
558 free(data);
559 }
560
561 data = calloc(1, sizeof(*data) + blob_pad_len(attr));
562 if (!data)
563 break;
564
565 memcpy(data->attr, attr, blob_pad_len(attr));
566 data->avl.key = blobmsg_name(data->attr);
567 avl_insert(&ses->data, &data->avl);
568 }
569
570 return 0;
571 }
572
573 static int
574 rpc_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
575 struct ubus_request_data *req, const char *method,
576 struct blob_attr *msg)
577 {
578 struct rpc_session *ses;
579 struct rpc_session_data *data;
580 struct blob_attr *tb[__RPC_SA_MAX];
581 struct blob_attr *attr;
582 void *c;
583 int rem;
584
585 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
586
587 if (!tb[RPC_SG_SID])
588 return UBUS_STATUS_INVALID_ARGUMENT;
589
590 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
591 if (!ses)
592 return UBUS_STATUS_NOT_FOUND;
593
594 blob_buf_init(&buf, 0);
595 c = blobmsg_open_table(&buf, "values");
596
597 if (tb[RPC_SG_KEYS])
598 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
599 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
600 continue;
601
602 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
603 if (!data)
604 continue;
605
606 blobmsg_add_field(&buf, blobmsg_type(data->attr),
607 blobmsg_name(data->attr),
608 blobmsg_data(data->attr),
609 blobmsg_data_len(data->attr));
610 }
611 else
612 rpc_session_dump_data(ses, &buf);
613
614 blobmsg_close_table(&buf, c);
615 ubus_send_reply(ctx, req, buf.head);
616
617 return 0;
618 }
619
620 static int
621 rpc_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
622 struct ubus_request_data *req, const char *method,
623 struct blob_attr *msg)
624 {
625 struct rpc_session *ses;
626 struct rpc_session_data *data, *ndata;
627 struct blob_attr *tb[__RPC_SA_MAX];
628 struct blob_attr *attr;
629 int rem;
630
631 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
632
633 if (!tb[RPC_SG_SID])
634 return UBUS_STATUS_INVALID_ARGUMENT;
635
636 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
637 if (!ses)
638 return UBUS_STATUS_NOT_FOUND;
639
640 if (!tb[RPC_SG_KEYS]) {
641 avl_remove_all_elements(&ses->data, data, avl, ndata)
642 free(data);
643 return 0;
644 }
645
646 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
647 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
648 continue;
649
650 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
651 if (!data)
652 continue;
653
654 avl_delete(&ses->data, &data->avl);
655 free(data);
656 }
657
658 return 0;
659 }
660
661 static int
662 rpc_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
663 struct ubus_request_data *req, const char *method,
664 struct blob_attr *msg)
665 {
666 struct rpc_session *ses;
667 struct blob_attr *tb;
668
669 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
670
671 if (!tb)
672 return UBUS_STATUS_INVALID_ARGUMENT;
673
674 ses = rpc_session_get(blobmsg_data(tb));
675 if (!ses)
676 return UBUS_STATUS_NOT_FOUND;
677
678 rpc_session_destroy(ses);
679
680 return 0;
681 }
682
683 int rpc_session_api_init(struct ubus_context *ctx)
684 {
685 static const struct ubus_method session_methods[] = {
686 UBUS_METHOD("create", rpc_handle_create, &new_policy),
687 UBUS_METHOD("list", rpc_handle_list, &sid_policy),
688 UBUS_METHOD("grant", rpc_handle_acl, acl_policy),
689 UBUS_METHOD("revoke", rpc_handle_acl, acl_policy),
690 UBUS_METHOD("access", rpc_handle_access, perm_policy),
691 UBUS_METHOD("set", rpc_handle_set, set_policy),
692 UBUS_METHOD("get", rpc_handle_get, get_policy),
693 UBUS_METHOD("unset", rpc_handle_unset, get_policy),
694 UBUS_METHOD("destroy", rpc_handle_destroy, &sid_policy),
695 };
696
697 static struct ubus_object_type session_type =
698 UBUS_OBJECT_TYPE("luci-rpc-session", session_methods);
699
700 static struct ubus_object obj = {
701 .name = "session",
702 .type = &session_type,
703 .methods = session_methods,
704 .n_methods = ARRAY_SIZE(session_methods),
705 };
706
707 avl_init(&sessions, avl_strcmp, false, NULL);
708
709 return ubus_add_object(ctx, &obj);
710 }