session: add support for saving and restoring session data to disk
[project/rpcd.git] / session.c
1 /*
2 * rpcd - 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 <rpcd/session.h>
26
27 static struct avl_tree sessions;
28 static struct blob_buf buf;
29
30 static LIST_HEAD(create_callbacks);
31 static LIST_HEAD(destroy_callbacks);
32
33 static const struct blobmsg_policy new_policy = {
34 .name = "timeout", .type = BLOBMSG_TYPE_INT32
35 };
36
37 static const struct blobmsg_policy sid_policy = {
38 .name = "sid", .type = BLOBMSG_TYPE_STRING
39 };
40
41 enum {
42 RPC_SS_SID,
43 RPC_SS_VALUES,
44 __RPC_SS_MAX,
45 };
46 static const struct blobmsg_policy set_policy[__RPC_SS_MAX] = {
47 [RPC_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
48 [RPC_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE },
49 };
50
51 enum {
52 RPC_SG_SID,
53 RPC_SG_KEYS,
54 __RPC_SG_MAX,
55 };
56 static const struct blobmsg_policy get_policy[__RPC_SG_MAX] = {
57 [RPC_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
58 [RPC_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY },
59 };
60
61 enum {
62 RPC_SA_SID,
63 RPC_SA_SCOPE,
64 RPC_SA_OBJECTS,
65 __RPC_SA_MAX,
66 };
67 static const struct blobmsg_policy acl_policy[__RPC_SA_MAX] = {
68 [RPC_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
69 [RPC_SA_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING },
70 [RPC_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY },
71 };
72
73 enum {
74 RPC_SP_SID,
75 RPC_SP_SCOPE,
76 RPC_SP_OBJECT,
77 RPC_SP_FUNCTION,
78 __RPC_SP_MAX,
79 };
80 static const struct blobmsg_policy perm_policy[__RPC_SP_MAX] = {
81 [RPC_SP_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
82 [RPC_SP_SCOPE] = { .name = "scope", .type = BLOBMSG_TYPE_STRING },
83 [RPC_SP_OBJECT] = { .name = "object", .type = BLOBMSG_TYPE_STRING },
84 [RPC_SP_FUNCTION] = { .name = "function", .type = BLOBMSG_TYPE_STRING },
85 };
86
87 enum {
88 RPC_DUMP_SID,
89 RPC_DUMP_TIMEOUT,
90 RPC_DUMP_EXPIRES,
91 RPC_DUMP_ACLS,
92 RPC_DUMP_DATA,
93 __RPC_DUMP_MAX,
94 };
95 static const struct blobmsg_policy dump_policy[__RPC_DUMP_MAX] = {
96 [RPC_DUMP_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
97 [RPC_DUMP_TIMEOUT] = { .name = "timeout", .type = BLOBMSG_TYPE_INT32 },
98 [RPC_DUMP_EXPIRES] = { .name = "expires", .type = BLOBMSG_TYPE_INT32 },
99 [RPC_DUMP_ACLS] = { .name = "acls", .type = BLOBMSG_TYPE_TABLE },
100 [RPC_DUMP_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
101 };
102
103 /*
104 * Keys in the AVL tree contain all pattern characters up to the first wildcard.
105 * To look up entries, start with the last entry that has a key less than or
106 * equal to the method name, then work backwards as long as the AVL key still
107 * matches its counterpart in the object name
108 */
109 #define uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \
110 for (_acl = avl_find_le_element(_avl, _obj, _acl, avl); \
111 _acl; \
112 _acl = avl_is_first(_avl, &(_acl)->avl) ? NULL : \
113 avl_prev_element((_acl), avl))
114
115 #define uh_foreach_matching_acl(_acl, _avl, _obj, _func) \
116 uh_foreach_matching_acl_prefix(_acl, _avl, _obj, _func) \
117 if (!strncmp((_acl)->object, _obj, (_acl)->sort_len) && \
118 !fnmatch((_acl)->object, (_obj), FNM_NOESCAPE) && \
119 !fnmatch((_acl)->function, (_func), FNM_NOESCAPE))
120
121 static void
122 rpc_random(char *dest)
123 {
124 unsigned char buf[16] = { 0 };
125 FILE *f;
126 int i;
127
128 f = fopen("/dev/urandom", "r");
129 if (!f)
130 return;
131
132 fread(buf, 1, sizeof(buf), f);
133 fclose(f);
134
135 for (i = 0; i < sizeof(buf); i++)
136 sprintf(dest + (i<<1), "%02x", buf[i]);
137 }
138
139 static void
140 rpc_session_dump_data(struct rpc_session *ses, struct blob_buf *b)
141 {
142 struct rpc_session_data *d;
143
144 avl_for_each_element(&ses->data, d, avl) {
145 blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr),
146 blobmsg_data(d->attr), blobmsg_data_len(d->attr));
147 }
148 }
149
150 static void
151 rpc_session_dump_acls(struct rpc_session *ses, struct blob_buf *b)
152 {
153 struct rpc_session_acl *acl;
154 struct rpc_session_acl_scope *acl_scope;
155 const char *lastobj = NULL;
156 const char *lastscope = NULL;
157 void *c = NULL, *d = NULL;
158
159 avl_for_each_element(&ses->acls, acl_scope, avl) {
160 if (!lastscope || strcmp(acl_scope->avl.key, lastscope))
161 {
162 if (c) blobmsg_close_table(b, c);
163 c = blobmsg_open_table(b, acl_scope->avl.key);
164 lastobj = NULL;
165 }
166
167 d = NULL;
168
169 avl_for_each_element(&acl_scope->acls, acl, avl) {
170 if (!lastobj || strcmp(acl->object, lastobj))
171 {
172 if (d) blobmsg_close_array(b, d);
173 d = blobmsg_open_array(b, acl->object);
174 }
175
176 blobmsg_add_string(b, NULL, acl->function);
177 lastobj = acl->object;
178 }
179
180 if (d) blobmsg_close_array(b, d);
181 }
182
183 if (c) blobmsg_close_table(b, c);
184 }
185
186 static void
187 rpc_session_to_blob(struct rpc_session *ses)
188 {
189 void *c;
190
191 blob_buf_init(&buf, 0);
192
193 blobmsg_add_string(&buf, "sid", ses->id);
194 blobmsg_add_u32(&buf, "timeout", ses->timeout);
195 blobmsg_add_u32(&buf, "expires", uloop_timeout_remaining(&ses->t) / 1000);
196
197 c = blobmsg_open_table(&buf, "acls");
198 rpc_session_dump_acls(ses, &buf);
199 blobmsg_close_table(&buf, c);
200
201 c = blobmsg_open_table(&buf, "data");
202 rpc_session_dump_data(ses, &buf);
203 blobmsg_close_table(&buf, c);
204 }
205
206 static void
207 rpc_session_dump(struct rpc_session *ses, struct ubus_context *ctx,
208 struct ubus_request_data *req)
209 {
210 rpc_session_to_blob(ses);
211
212 ubus_send_reply(ctx, req, buf.head);
213 }
214
215 static void
216 rpc_touch_session(struct rpc_session *ses)
217 {
218 uloop_timeout_set(&ses->t, ses->timeout * 1000);
219 }
220
221 static void
222 rpc_session_destroy(struct rpc_session *ses)
223 {
224 struct rpc_session_acl *acl, *nacl;
225 struct rpc_session_acl_scope *acl_scope, *nacl_scope;
226 struct rpc_session_data *data, *ndata;
227 struct rpc_session_cb *cb;
228
229 list_for_each_entry(cb, &destroy_callbacks, list)
230 cb->cb(ses, cb->priv);
231
232 uloop_timeout_cancel(&ses->t);
233
234 avl_for_each_element_safe(&ses->acls, acl_scope, avl, nacl_scope) {
235 avl_remove_all_elements(&acl_scope->acls, acl, avl, nacl)
236 free(acl);
237
238 avl_delete(&ses->acls, &acl_scope->avl);
239 free(acl_scope);
240 }
241
242 avl_remove_all_elements(&ses->data, data, avl, ndata)
243 free(data);
244
245 avl_delete(&sessions, &ses->avl);
246 free(ses);
247 }
248
249 static void rpc_session_timeout(struct uloop_timeout *t)
250 {
251 struct rpc_session *ses;
252
253 ses = container_of(t, struct rpc_session, t);
254 rpc_session_destroy(ses);
255 }
256
257 static struct rpc_session *
258 rpc_session_new(void)
259 {
260 struct rpc_session *ses;
261
262 ses = calloc(1, sizeof(*ses));
263
264 if (!ses)
265 return NULL;
266
267 ses->avl.key = ses->id;
268
269 avl_init(&ses->acls, avl_strcmp, true, NULL);
270 avl_init(&ses->data, avl_strcmp, false, NULL);
271
272 ses->t.cb = rpc_session_timeout;
273
274 return ses;
275 }
276
277 static struct rpc_session *
278 rpc_session_create(int timeout)
279 {
280 struct rpc_session *ses;
281 struct rpc_session_cb *cb;
282
283 ses = rpc_session_new();
284
285 if (!ses)
286 return NULL;
287
288 rpc_random(ses->id);
289
290 ses->timeout = timeout;
291
292 avl_insert(&sessions, &ses->avl);
293
294 rpc_touch_session(ses);
295
296 list_for_each_entry(cb, &create_callbacks, list)
297 cb->cb(ses, cb->priv);
298
299 return ses;
300 }
301
302 static struct rpc_session *
303 rpc_session_get(const char *id)
304 {
305 struct rpc_session *ses;
306
307 ses = avl_find_element(&sessions, id, ses, avl);
308 if (!ses)
309 return NULL;
310
311 rpc_touch_session(ses);
312 return ses;
313 }
314
315 static int
316 rpc_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
317 struct ubus_request_data *req, const char *method,
318 struct blob_attr *msg)
319 {
320 struct rpc_session *ses;
321 struct blob_attr *tb;
322 int timeout = RPC_DEFAULT_SESSION_TIMEOUT;
323
324 blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
325 if (tb)
326 timeout = blobmsg_get_u32(tb);
327
328 ses = rpc_session_create(timeout);
329 if (ses)
330 rpc_session_dump(ses, ctx, req);
331
332 return 0;
333 }
334
335 static int
336 rpc_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
337 struct ubus_request_data *req, const char *method,
338 struct blob_attr *msg)
339 {
340 struct rpc_session *ses;
341 struct blob_attr *tb;
342
343 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
344
345 if (!tb) {
346 avl_for_each_element(&sessions, ses, avl)
347 rpc_session_dump(ses, ctx, req);
348 return 0;
349 }
350
351 ses = rpc_session_get(blobmsg_data(tb));
352 if (!ses)
353 return UBUS_STATUS_NOT_FOUND;
354
355 rpc_session_dump(ses, ctx, req);
356
357 return 0;
358 }
359
360 static int
361 uh_id_len(const char *str)
362 {
363 return strcspn(str, "*?[");
364 }
365
366 static int
367 rpc_session_grant(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;
371 struct rpc_session_acl_scope *acl_scope;
372 char *new_scope, *new_obj, *new_func, *new_id;
373 int id_len;
374
375 if (!object || !function)
376 return UBUS_STATUS_INVALID_ARGUMENT;
377
378 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
379
380 if (acl_scope) {
381 uh_foreach_matching_acl_prefix(acl, &acl_scope->acls, object, function) {
382 if (!strcmp(acl->object, object) &&
383 !strcmp(acl->function, function))
384 return 0;
385 }
386 }
387
388 if (!acl_scope) {
389 acl_scope = calloc_a(sizeof(*acl_scope),
390 &new_scope, strlen(scope) + 1);
391
392 if (!acl_scope)
393 return UBUS_STATUS_UNKNOWN_ERROR;
394
395 acl_scope->avl.key = strcpy(new_scope, scope);
396 avl_init(&acl_scope->acls, avl_strcmp, true, NULL);
397 avl_insert(&ses->acls, &acl_scope->avl);
398 }
399
400 id_len = uh_id_len(object);
401 acl = calloc_a(sizeof(*acl),
402 &new_obj, strlen(object) + 1,
403 &new_func, strlen(function) + 1,
404 &new_id, id_len + 1);
405
406 if (!acl)
407 return UBUS_STATUS_UNKNOWN_ERROR;
408
409 acl->object = strcpy(new_obj, object);
410 acl->function = strcpy(new_func, function);
411 acl->avl.key = strncpy(new_id, object, id_len);
412 avl_insert(&acl_scope->acls, &acl->avl);
413
414 return 0;
415 }
416
417 static int
418 rpc_session_revoke(struct rpc_session *ses, struct ubus_context *ctx,
419 const char *scope, const char *object, const char *function)
420 {
421 struct rpc_session_acl *acl, *next;
422 struct rpc_session_acl_scope *acl_scope;
423 int id_len;
424 char *id;
425
426 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
427
428 if (!acl_scope)
429 return 0;
430
431 if (!object && !function) {
432 avl_remove_all_elements(&acl_scope->acls, acl, avl, next)
433 free(acl);
434 avl_delete(&ses->acls, &acl_scope->avl);
435 free(acl_scope);
436 return 0;
437 }
438
439 id_len = uh_id_len(object);
440 id = alloca(id_len + 1);
441 strncpy(id, object, id_len);
442 id[id_len] = 0;
443
444 acl = avl_find_element(&acl_scope->acls, id, acl, avl);
445 while (acl) {
446 if (!avl_is_last(&acl_scope->acls, &acl->avl))
447 next = avl_next_element(acl, avl);
448 else
449 next = NULL;
450
451 if (strcmp(id, acl->avl.key) != 0)
452 break;
453
454 if (!strcmp(acl->object, object) &&
455 !strcmp(acl->function, function)) {
456 avl_delete(&acl_scope->acls, &acl->avl);
457 free(acl);
458 }
459 acl = next;
460 }
461
462 if (avl_is_empty(&acl_scope->acls)) {
463 avl_delete(&ses->acls, &acl_scope->avl);
464 free(acl_scope);
465 }
466
467 return 0;
468 }
469
470
471 static int
472 rpc_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
473 struct ubus_request_data *req, const char *method,
474 struct blob_attr *msg)
475 {
476 struct rpc_session *ses;
477 struct blob_attr *tb[__RPC_SA_MAX];
478 struct blob_attr *attr, *sattr;
479 const char *object, *function;
480 const char *scope = "ubus";
481 int rem1, rem2;
482
483 int (*cb)(struct rpc_session *ses, struct ubus_context *ctx,
484 const char *scope, const char *object, const char *function);
485
486 blobmsg_parse(acl_policy, __RPC_SA_MAX, tb, blob_data(msg), blob_len(msg));
487
488 if (!tb[RPC_SA_SID])
489 return UBUS_STATUS_INVALID_ARGUMENT;
490
491 ses = rpc_session_get(blobmsg_data(tb[RPC_SA_SID]));
492 if (!ses)
493 return UBUS_STATUS_NOT_FOUND;
494
495 if (tb[RPC_SA_SCOPE])
496 scope = blobmsg_data(tb[RPC_SA_SCOPE]);
497
498 if (!strcmp(method, "grant"))
499 cb = rpc_session_grant;
500 else
501 cb = rpc_session_revoke;
502
503 if (!tb[RPC_SA_OBJECTS])
504 return cb(ses, ctx, scope, NULL, NULL);
505
506 blobmsg_for_each_attr(attr, tb[RPC_SA_OBJECTS], rem1) {
507 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
508 continue;
509
510 object = NULL;
511 function = NULL;
512
513 blobmsg_for_each_attr(sattr, attr, rem2) {
514 if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
515 continue;
516
517 if (!object)
518 object = blobmsg_data(sattr);
519 else if (!function)
520 function = blobmsg_data(sattr);
521 else
522 break;
523 }
524
525 if (object && function)
526 cb(ses, ctx, scope, object, function);
527 }
528
529 return 0;
530 }
531
532 static bool
533 rpc_session_acl_allowed(struct rpc_session *ses, const char *scope,
534 const char *obj, const char *fun)
535 {
536 struct rpc_session_acl *acl;
537 struct rpc_session_acl_scope *acl_scope;
538
539 acl_scope = avl_find_element(&ses->acls, scope, acl_scope, avl);
540
541 if (acl_scope) {
542 uh_foreach_matching_acl(acl, &acl_scope->acls, obj, fun)
543 return true;
544 }
545
546 return false;
547 }
548
549 static int
550 rpc_handle_access(struct ubus_context *ctx, struct ubus_object *obj,
551 struct ubus_request_data *req, const char *method,
552 struct blob_attr *msg)
553 {
554 struct rpc_session *ses;
555 struct blob_attr *tb[__RPC_SP_MAX];
556 const char *scope = "ubus";
557 bool allow;
558
559 blobmsg_parse(perm_policy, __RPC_SP_MAX, tb, blob_data(msg), blob_len(msg));
560
561 if (!tb[RPC_SP_SID] || !tb[RPC_SP_OBJECT] || !tb[RPC_SP_FUNCTION])
562 return UBUS_STATUS_INVALID_ARGUMENT;
563
564 ses = rpc_session_get(blobmsg_data(tb[RPC_SP_SID]));
565 if (!ses)
566 return UBUS_STATUS_NOT_FOUND;
567
568 if (tb[RPC_SP_SCOPE])
569 scope = blobmsg_data(tb[RPC_SP_SCOPE]);
570
571 allow = rpc_session_acl_allowed(ses, scope,
572 blobmsg_data(tb[RPC_SP_OBJECT]),
573 blobmsg_data(tb[RPC_SP_FUNCTION]));
574
575 blob_buf_init(&buf, 0);
576 blobmsg_add_u8(&buf, "access", allow);
577 ubus_send_reply(ctx, req, buf.head);
578
579 return 0;
580 }
581
582 static void
583 rpc_session_set(struct rpc_session *ses, const char *key, struct blob_attr *val)
584 {
585 struct rpc_session_data *data;
586
587 data = avl_find_element(&ses->data, key, data, avl);
588 if (data) {
589 avl_delete(&ses->data, &data->avl);
590 free(data);
591 }
592
593 data = calloc(1, sizeof(*data) + blob_pad_len(val));
594 if (!data)
595 return;
596
597 memcpy(data->attr, val, blob_pad_len(val));
598 data->avl.key = blobmsg_name(data->attr);
599 avl_insert(&ses->data, &data->avl);
600 }
601
602 static int
603 rpc_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
604 struct ubus_request_data *req, const char *method,
605 struct blob_attr *msg)
606 {
607 struct rpc_session *ses;
608 struct blob_attr *tb[__RPC_SA_MAX];
609 struct blob_attr *attr;
610 int rem;
611
612 blobmsg_parse(set_policy, __RPC_SS_MAX, tb, blob_data(msg), blob_len(msg));
613
614 if (!tb[RPC_SS_SID] || !tb[RPC_SS_VALUES])
615 return UBUS_STATUS_INVALID_ARGUMENT;
616
617 ses = rpc_session_get(blobmsg_data(tb[RPC_SS_SID]));
618 if (!ses)
619 return UBUS_STATUS_NOT_FOUND;
620
621 blobmsg_for_each_attr(attr, tb[RPC_SS_VALUES], rem) {
622 if (!blobmsg_name(attr)[0])
623 continue;
624
625 rpc_session_set(ses, blobmsg_name(attr), attr);
626 }
627
628 return 0;
629 }
630
631 static int
632 rpc_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
633 struct ubus_request_data *req, const char *method,
634 struct blob_attr *msg)
635 {
636 struct rpc_session *ses;
637 struct rpc_session_data *data;
638 struct blob_attr *tb[__RPC_SA_MAX];
639 struct blob_attr *attr;
640 void *c;
641 int rem;
642
643 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
644
645 if (!tb[RPC_SG_SID])
646 return UBUS_STATUS_INVALID_ARGUMENT;
647
648 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
649 if (!ses)
650 return UBUS_STATUS_NOT_FOUND;
651
652 blob_buf_init(&buf, 0);
653 c = blobmsg_open_table(&buf, "values");
654
655 if (tb[RPC_SG_KEYS])
656 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
657 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
658 continue;
659
660 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
661 if (!data)
662 continue;
663
664 blobmsg_add_field(&buf, blobmsg_type(data->attr),
665 blobmsg_name(data->attr),
666 blobmsg_data(data->attr),
667 blobmsg_data_len(data->attr));
668 }
669 else
670 rpc_session_dump_data(ses, &buf);
671
672 blobmsg_close_table(&buf, c);
673 ubus_send_reply(ctx, req, buf.head);
674
675 return 0;
676 }
677
678 static int
679 rpc_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
680 struct ubus_request_data *req, const char *method,
681 struct blob_attr *msg)
682 {
683 struct rpc_session *ses;
684 struct rpc_session_data *data, *ndata;
685 struct blob_attr *tb[__RPC_SA_MAX];
686 struct blob_attr *attr;
687 int rem;
688
689 blobmsg_parse(get_policy, __RPC_SG_MAX, tb, blob_data(msg), blob_len(msg));
690
691 if (!tb[RPC_SG_SID])
692 return UBUS_STATUS_INVALID_ARGUMENT;
693
694 ses = rpc_session_get(blobmsg_data(tb[RPC_SG_SID]));
695 if (!ses)
696 return UBUS_STATUS_NOT_FOUND;
697
698 if (!tb[RPC_SG_KEYS]) {
699 avl_remove_all_elements(&ses->data, data, avl, ndata)
700 free(data);
701 return 0;
702 }
703
704 blobmsg_for_each_attr(attr, tb[RPC_SG_KEYS], rem) {
705 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
706 continue;
707
708 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
709 if (!data)
710 continue;
711
712 avl_delete(&ses->data, &data->avl);
713 free(data);
714 }
715
716 return 0;
717 }
718
719 static int
720 rpc_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
721 struct ubus_request_data *req, const char *method,
722 struct blob_attr *msg)
723 {
724 struct rpc_session *ses;
725 struct blob_attr *tb;
726
727 blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
728
729 if (!tb)
730 return UBUS_STATUS_INVALID_ARGUMENT;
731
732 ses = rpc_session_get(blobmsg_data(tb));
733 if (!ses)
734 return UBUS_STATUS_NOT_FOUND;
735
736 rpc_session_destroy(ses);
737
738 return 0;
739 }
740
741
742 static bool
743 rpc_validate_sid(const char *id)
744 {
745 if (!id)
746 return false;
747
748 if (strlen(id) != RPC_SID_LEN)
749 return false;
750
751 while (*id)
752 if (!isxdigit(*id++))
753 return false;
754
755 return true;
756 }
757
758 static int
759 rpc_blob_to_file(const char *path, struct blob_attr *attr)
760 {
761 int fd, len;
762
763 fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
764
765 if (fd < 0)
766 return fd;
767
768 len = write(fd, attr, blob_pad_len(attr));
769
770 close(fd);
771
772 if (len != blob_pad_len(attr))
773 {
774 unlink(path);
775 return -1;
776 }
777
778 return len;
779 }
780
781 static struct blob_attr *
782 rpc_blob_from_file(const char *path)
783 {
784 int fd = -1, len;
785 struct stat s;
786 struct blob_attr head, *attr = NULL;
787
788 if (stat(path, &s) || !S_ISREG(s.st_mode))
789 return NULL;
790
791 fd = open(path, O_RDONLY);
792
793 if (fd < 0)
794 goto fail;
795
796 len = read(fd, &head, sizeof(head));
797
798 if (len != sizeof(head) || blob_pad_len(&head) != s.st_size)
799 goto fail;
800
801 attr = calloc(1, s.st_size);
802
803 if (!attr)
804 goto fail;
805
806 memcpy(attr, &head, sizeof(head));
807
808 len += read(fd, (char *)attr + sizeof(head), s.st_size - sizeof(head));
809
810 if (len != blob_pad_len(&head))
811 goto fail;
812
813 return attr;
814
815 fail:
816 if (fd >= 0)
817 close(fd);
818
819 if (attr)
820 free(attr);
821
822 return NULL;
823 }
824
825 static bool
826 rpc_session_from_blob(struct blob_attr *attr)
827 {
828 int i, rem, rem2, rem3;
829 struct rpc_session *ses;
830 struct blob_attr *tb[__RPC_DUMP_MAX], *scope, *object, *function;
831
832 blobmsg_parse(dump_policy, __RPC_DUMP_MAX, tb,
833 blob_data(attr), blob_len(attr));
834
835 for (i = 0; i < __RPC_DUMP_MAX; i++)
836 if (!tb[i])
837 return false;
838
839 ses = rpc_session_new();
840
841 if (!ses)
842 return false;
843
844 memcpy(ses->id, blobmsg_data(tb[RPC_DUMP_SID]), RPC_SID_LEN);
845
846 ses->timeout = blobmsg_get_u32(tb[RPC_DUMP_TIMEOUT]);
847
848 blobmsg_for_each_attr(scope, tb[RPC_DUMP_ACLS], rem) {
849 blobmsg_for_each_attr(object, scope, rem2) {
850 blobmsg_for_each_attr(function, object, rem3) {
851 rpc_session_grant(ses, NULL, blobmsg_name(scope),
852 blobmsg_name(object),
853 blobmsg_data(function));
854 }
855 }
856 }
857
858 blobmsg_for_each_attr(object, tb[RPC_DUMP_DATA], rem) {
859 rpc_session_set(ses, blobmsg_name(object), object);
860 }
861
862 avl_insert(&sessions, &ses->avl);
863
864 uloop_timeout_set(&ses->t, blobmsg_get_u32(tb[RPC_DUMP_EXPIRES]) * 1000);
865
866 return true;
867 }
868
869 int rpc_session_api_init(struct ubus_context *ctx)
870 {
871 static const struct ubus_method session_methods[] = {
872 UBUS_METHOD("create", rpc_handle_create, &new_policy),
873 UBUS_METHOD("list", rpc_handle_list, &sid_policy),
874 UBUS_METHOD("grant", rpc_handle_acl, acl_policy),
875 UBUS_METHOD("revoke", rpc_handle_acl, acl_policy),
876 UBUS_METHOD("access", rpc_handle_access, perm_policy),
877 UBUS_METHOD("set", rpc_handle_set, set_policy),
878 UBUS_METHOD("get", rpc_handle_get, get_policy),
879 UBUS_METHOD("unset", rpc_handle_unset, get_policy),
880 UBUS_METHOD("destroy", rpc_handle_destroy, &sid_policy),
881 };
882
883 static struct ubus_object_type session_type =
884 UBUS_OBJECT_TYPE("luci-rpc-session", session_methods);
885
886 static struct ubus_object obj = {
887 .name = "session",
888 .type = &session_type,
889 .methods = session_methods,
890 .n_methods = ARRAY_SIZE(session_methods),
891 };
892
893 avl_init(&sessions, avl_strcmp, false, NULL);
894
895 return ubus_add_object(ctx, &obj);
896 }
897
898 bool rpc_session_access(const char *sid, const char *scope,
899 const char *object, const char *function)
900 {
901 struct rpc_session *ses = rpc_session_get(sid);
902
903 if (!ses)
904 return false;
905
906 return rpc_session_acl_allowed(ses, scope, object, function);
907 }
908
909 void rpc_session_create_cb(struct rpc_session_cb *cb)
910 {
911 if (cb && cb->cb)
912 list_add(&cb->list, &create_callbacks);
913 }
914
915 void rpc_session_destroy_cb(struct rpc_session_cb *cb)
916 {
917 if (cb && cb->cb)
918 list_add(&cb->list, &destroy_callbacks);
919 }
920
921 void rpc_session_freeze(void)
922 {
923 struct stat s;
924 struct rpc_session *ses;
925 char path[PATH_MAX];
926
927 if (stat(RPC_SESSION_DIRECTORY, &s))
928 mkdir(RPC_SESSION_DIRECTORY, 0700);
929
930 avl_for_each_element(&sessions, ses, avl) {
931 snprintf(path, sizeof(path) - 1, RPC_SESSION_DIRECTORY "/%s", ses->id);
932 rpc_session_to_blob(ses);
933 rpc_blob_to_file(path, buf.head);
934 }
935 }
936
937 void rpc_session_thaw(void)
938 {
939 DIR *d;
940 char path[PATH_MAX];
941 struct dirent *e;
942 struct blob_attr *attr;
943
944 d = opendir(RPC_SESSION_DIRECTORY);
945
946 if (!d)
947 return;
948
949 while ((e = readdir(d)) != NULL) {
950 if (!rpc_validate_sid(e->d_name))
951 continue;
952
953 snprintf(path, sizeof(path) - 1,
954 RPC_SESSION_DIRECTORY "/%s", e->d_name);
955
956 attr = rpc_blob_from_file(path);
957
958 if (attr) {
959 rpc_session_from_blob(attr);
960 free(attr);
961 }
962
963 unlink(path);
964 }
965
966 closedir(d);
967 }