sessions: add scopes for acls, default to "ubus" scope
[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 }