83544892ca21d6c756dc3de60cce830876d2db98
[project/uhttpd.git] / ubus-session.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
5  *   Copyright (C) 2013 Felix Fietkau <nbd@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 "ubus-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         UH_UBUS_SS_SID,
40         UH_UBUS_SS_VALUES,
41         __UH_UBUS_SS_MAX,
42 };
43 static const struct blobmsg_policy set_policy[__UH_UBUS_SS_MAX] = {
44         [UH_UBUS_SS_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
45         [UH_UBUS_SS_VALUES] = { .name = "values", .type = BLOBMSG_TYPE_TABLE },
46 };
47
48 enum {
49         UH_UBUS_SG_SID,
50         UH_UBUS_SG_KEYS,
51         __UH_UBUS_SG_MAX,
52 };
53 static const struct blobmsg_policy get_policy[__UH_UBUS_SG_MAX] = {
54         [UH_UBUS_SG_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
55         [UH_UBUS_SG_KEYS] = { .name = "keys", .type = BLOBMSG_TYPE_ARRAY },
56 };
57
58 enum {
59         UH_UBUS_SA_SID,
60         UH_UBUS_SA_OBJECTS,
61         __UH_UBUS_SA_MAX,
62 };
63 static const struct blobmsg_policy acl_policy[__UH_UBUS_SA_MAX] = {
64         [UH_UBUS_SA_SID] = { .name = "sid", .type = BLOBMSG_TYPE_STRING },
65         [UH_UBUS_SA_OBJECTS] = { .name = "objects", .type = BLOBMSG_TYPE_ARRAY },
66 };
67
68 /*
69  * Keys in the AVL tree contain all pattern characters up to the first wildcard.
70  * To look up entries, start with the last entry that has a key less than or
71  * equal to the method name, then work backwards as long as the AVL key still
72  * matches its counterpart in the object name
73  */
74 #define uh_foreach_matching_acl_prefix(_acl, _ses, _obj, _func)                 \
75         for (_acl = avl_find_le_element(&(_ses)->acls, _obj, _acl, avl);        \
76              _acl;                                                              \
77              _acl = avl_is_first(&(ses)->acls, &(_acl)->avl) ? NULL :           \
78                     avl_prev_element((_acl), avl))
79
80 #define uh_foreach_matching_acl(_acl, _ses, _obj, _func)                        \
81         uh_foreach_matching_acl_prefix(_acl, _ses, _obj, _func)                 \
82                 if (!strncmp((_acl)->object, _obj, (_acl)->sort_len) &&         \
83                     !fnmatch((_acl)->object, (_obj), FNM_NOESCAPE) &&           \
84                     !fnmatch((_acl)->function, (_func), FNM_NOESCAPE))
85
86 static void
87 uh_ubus_random(char *dest)
88 {
89         unsigned char buf[16] = { 0 };
90         FILE *f;
91         int i;
92
93         f = fopen("/dev/urandom", "r");
94         if (!f)
95                 return;
96
97         fread(buf, 1, sizeof(buf), f);
98         fclose(f);
99
100         for (i = 0; i < sizeof(buf); i++)
101                 sprintf(dest + (i<<1), "%02x", buf[i]);
102 }
103
104 static void
105 uh_ubus_session_dump_data(struct uh_ubus_session *ses, struct blob_buf *b)
106 {
107         struct uh_ubus_session_data *d;
108
109         avl_for_each_element(&ses->data, d, avl) {
110                 blobmsg_add_field(b, blobmsg_type(d->attr), blobmsg_name(d->attr),
111                                   blobmsg_data(d->attr), blobmsg_data_len(d->attr));
112         }
113 }
114
115 static void
116 uh_ubus_session_dump_acls(struct uh_ubus_session *ses, struct blob_buf *b)
117 {
118         struct uh_ubus_session_acl *acl;
119         const char *lastobj = NULL;
120         void *c = NULL;
121
122         avl_for_each_element(&ses->acls, acl, avl) {
123                 if (!lastobj || strcmp(acl->object, lastobj))
124                 {
125                         if (c) blobmsg_close_array(b, c);
126                         c = blobmsg_open_array(b, acl->object);
127                 }
128
129                 blobmsg_add_string(b, NULL, acl->function);
130                 lastobj = acl->object;
131         }
132
133         if (c) blobmsg_close_array(b, c);
134 }
135
136 static void
137 uh_ubus_session_dump(struct uh_ubus_session *ses,
138                                          struct ubus_context *ctx,
139                                          struct ubus_request_data *req)
140 {
141         void *c;
142
143         blob_buf_init(&buf, 0);
144
145         blobmsg_add_string(&buf, "sid", ses->id);
146         blobmsg_add_u32(&buf, "timeout", ses->timeout);
147         blobmsg_add_u32(&buf, "expires", uloop_timeout_remaining(&ses->t) / 1000);
148
149         c = blobmsg_open_table(&buf, "acls");
150         uh_ubus_session_dump_acls(ses, &buf);
151         blobmsg_close_table(&buf, c);
152
153         c = blobmsg_open_table(&buf, "data");
154         uh_ubus_session_dump_data(ses, &buf);
155         blobmsg_close_table(&buf, c);
156
157         ubus_send_reply(ctx, req, buf.head);
158 }
159
160 static void
161 uh_ubus_touch_session(struct uh_ubus_session *ses)
162 {
163         uloop_timeout_set(&ses->t, ses->timeout * 1000);
164 }
165
166 static void
167 uh_ubus_session_destroy(struct uh_ubus_session *ses)
168 {
169         struct uh_ubus_session_acl *acl, *nacl;
170         struct uh_ubus_session_data *data, *ndata;
171
172         uloop_timeout_cancel(&ses->t);
173         avl_remove_all_elements(&ses->acls, acl, avl, nacl)
174                 free(acl);
175
176         avl_remove_all_elements(&ses->data, data, avl, ndata)
177                 free(data);
178
179         avl_delete(&sessions, &ses->avl);
180         free(ses);
181 }
182
183 static void uh_ubus_session_timeout(struct uloop_timeout *t)
184 {
185         struct uh_ubus_session *ses;
186
187         ses = container_of(t, struct uh_ubus_session, t);
188         uh_ubus_session_destroy(ses);
189 }
190
191 static struct uh_ubus_session *
192 uh_ubus_session_create(int timeout)
193 {
194         struct uh_ubus_session *ses;
195
196         ses = calloc(1, sizeof(*ses));
197         if (!ses)
198                 return NULL;
199
200         ses->timeout  = timeout;
201         ses->avl.key  = ses->id;
202         uh_ubus_random(ses->id);
203
204         avl_insert(&sessions, &ses->avl);
205         avl_init(&ses->acls, avl_strcmp, true, NULL);
206         avl_init(&ses->data, avl_strcmp, false, NULL);
207
208         ses->t.cb = uh_ubus_session_timeout;
209         uh_ubus_touch_session(ses);
210
211         return ses;
212 }
213
214 struct uh_ubus_session *
215 uh_ubus_session_get(const char *id)
216 {
217         struct uh_ubus_session *ses;
218
219         ses = avl_find_element(&sessions, id, ses, avl);
220         if (!ses)
221                 return NULL;
222
223         uh_ubus_touch_session(ses);
224         return ses;
225 }
226
227 static int
228 uh_ubus_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
229                                           struct ubus_request_data *req, const char *method,
230                                           struct blob_attr *msg)
231 {
232         struct uh_ubus_session *ses;
233         struct blob_attr *tb;
234         int timeout = UBUS_DEFAULT_SESSION_TIMEOUT;
235
236         blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
237         if (tb)
238                 timeout = blobmsg_get_u32(tb);
239
240         ses = uh_ubus_session_create(timeout);
241         if (ses)
242                 uh_ubus_session_dump(ses, ctx, req);
243
244         return 0;
245 }
246
247 static int
248 uh_ubus_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
249                                         struct ubus_request_data *req, const char *method,
250                                         struct blob_attr *msg)
251 {
252         struct uh_ubus_session *ses;
253         struct blob_attr *tb;
254
255         blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
256
257         if (!tb) {
258                 avl_for_each_element(&sessions, ses, avl)
259                         uh_ubus_session_dump(ses, ctx, req);
260                 return 0;
261         }
262
263         ses = uh_ubus_session_get(blobmsg_data(tb));
264         if (!ses)
265                 return UBUS_STATUS_NOT_FOUND;
266
267         uh_ubus_session_dump(ses, ctx, req);
268
269         return 0;
270 }
271
272 static int
273 uh_id_len(const char *str)
274 {
275         return strcspn(str, "*?[");
276 }
277
278 static int
279 uh_ubus_session_grant(struct uh_ubus_session *ses, struct ubus_context *ctx,
280                       const char *object, const char *function)
281 {
282         struct uh_ubus_session_acl *acl;
283         char *new_obj, *new_func, *new_id;
284         int id_len;
285
286         if (!object || !function)
287                 return UBUS_STATUS_INVALID_ARGUMENT;
288
289         uh_foreach_matching_acl_prefix(acl, ses, object, function) {
290                 if (!strcmp(acl->object, object) &&
291                     !strcmp(acl->function, function))
292                         return 0;
293         }
294
295         id_len = uh_id_len(object);
296         acl = calloc_a(sizeof(*acl),
297                 &new_obj, strlen(object) + 1,
298                 &new_func, strlen(function) + 1,
299                 &new_id, id_len + 1);
300
301         if (!acl)
302                 return UBUS_STATUS_UNKNOWN_ERROR;
303
304         acl->object = strcpy(new_obj, object);
305         acl->function = strcpy(new_func, function);
306         acl->avl.key = strncpy(new_id, object, id_len);
307         avl_insert(&ses->acls, &acl->avl);
308
309         return 0;
310 }
311
312 static int
313 uh_ubus_session_revoke(struct uh_ubus_session *ses, struct ubus_context *ctx,
314                        const char *object, const char *function)
315 {
316         struct uh_ubus_session_acl *acl, *next;
317         int id_len;
318         char *id;
319
320         if (!object && !function) {
321                 avl_remove_all_elements(&ses->acls, acl, avl, next)
322                         free(acl);
323                 return 0;
324         }
325
326         id_len = uh_id_len(object);
327         id = alloca(id_len + 1);
328         strncpy(id, object, id_len);
329         id[id_len] = 0;
330
331         acl = avl_find_element(&ses->acls, id, acl, avl);
332         while (acl) {
333                 if (!avl_is_last(&ses->acls, &acl->avl))
334                         next = avl_next_element(acl, avl);
335                 else
336                         next = NULL;
337
338                 if (strcmp(id, acl->avl.key) != 0)
339                         break;
340
341                 if (!strcmp(acl->object, object) &&
342                     !strcmp(acl->function, function)) {
343                         avl_delete(&ses->acls, &acl->avl);
344                         free(acl);
345                 }
346                 acl = next;
347         }
348
349         return 0;
350 }
351
352
353 static int
354 uh_ubus_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
355                    struct ubus_request_data *req, const char *method,
356                    struct blob_attr *msg)
357 {
358         struct uh_ubus_session *ses;
359         struct blob_attr *tb[__UH_UBUS_SA_MAX];
360         struct blob_attr *attr, *sattr;
361         const char *object, *function;
362         int rem1, rem2;
363
364         int (*cb)(struct uh_ubus_session *ses, struct ubus_context *ctx,
365                   const char *object, const char *function);
366
367         blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg));
368
369         if (!tb[UH_UBUS_SA_SID])
370                 return UBUS_STATUS_INVALID_ARGUMENT;
371
372         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SA_SID]));
373         if (!ses)
374                 return UBUS_STATUS_NOT_FOUND;
375
376         if (!strcmp(method, "grant"))
377                 cb = uh_ubus_session_grant;
378         else
379                 cb = uh_ubus_session_revoke;
380
381         if (!tb[UH_UBUS_SA_OBJECTS])
382                 return cb(ses, ctx, NULL, NULL);
383
384         blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1) {
385                 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
386                         continue;
387
388                 object = NULL;
389                 function = NULL;
390
391                 blobmsg_for_each_attr(sattr, attr, rem2) {
392                         if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
393                                 continue;
394
395                         if (!object)
396                                 object = blobmsg_data(sattr);
397                         else if (!function)
398                                 function = blobmsg_data(sattr);
399                         else
400                                 break;
401                 }
402
403                 if (object && function)
404                         cb(ses, ctx, object, function);
405         }
406
407         return 0;
408 }
409
410 static int
411 uh_ubus_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
412                                    struct ubus_request_data *req, const char *method,
413                                    struct blob_attr *msg)
414 {
415         struct uh_ubus_session *ses;
416         struct uh_ubus_session_data *data;
417         struct blob_attr *tb[__UH_UBUS_SA_MAX];
418         struct blob_attr *attr;
419         int rem;
420
421         blobmsg_parse(set_policy, __UH_UBUS_SS_MAX, tb, blob_data(msg), blob_len(msg));
422
423         if (!tb[UH_UBUS_SS_SID] || !tb[UH_UBUS_SS_VALUES])
424                 return UBUS_STATUS_INVALID_ARGUMENT;
425
426         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SS_SID]));
427         if (!ses)
428                 return UBUS_STATUS_NOT_FOUND;
429
430         blobmsg_for_each_attr(attr, tb[UH_UBUS_SS_VALUES], rem) {
431                 if (!blobmsg_name(attr)[0])
432                         continue;
433
434                 data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
435                 if (data) {
436                         avl_delete(&ses->data, &data->avl);
437                         free(data);
438                 }
439
440                 data = calloc(1, sizeof(*data) + blob_pad_len(attr));
441                 if (!data)
442                         break;
443
444                 memcpy(data->attr, attr, blob_pad_len(attr));
445                 data->avl.key = blobmsg_name(data->attr);
446                 avl_insert(&ses->data, &data->avl);
447         }
448
449         return 0;
450 }
451
452 static int
453 uh_ubus_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
454                                    struct ubus_request_data *req, const char *method,
455                                    struct blob_attr *msg)
456 {
457         struct uh_ubus_session *ses;
458         struct uh_ubus_session_data *data;
459         struct blob_attr *tb[__UH_UBUS_SA_MAX];
460         struct blob_attr *attr;
461         void *c;
462         int rem;
463
464         blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
465
466         if (!tb[UH_UBUS_SG_SID])
467                 return UBUS_STATUS_INVALID_ARGUMENT;
468
469         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
470         if (!ses)
471                 return UBUS_STATUS_NOT_FOUND;
472
473         blob_buf_init(&buf, 0);
474         c = blobmsg_open_table(&buf, "values");
475
476         if (!tb[UH_UBUS_SG_KEYS]) {
477                 uh_ubus_session_dump_data(ses, &buf);
478                 return 0;
479         }
480
481         blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
482                 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
483                         continue;
484
485                 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
486                 if (!data)
487                         continue;
488
489                 blobmsg_add_field(&buf, blobmsg_type(data->attr),
490                                   blobmsg_name(data->attr),
491                                   blobmsg_data(data->attr),
492                                   blobmsg_data_len(data->attr));
493         }
494
495         blobmsg_close_table(&buf, c);
496         ubus_send_reply(ctx, req, buf.head);
497
498         return 0;
499 }
500
501 static int
502 uh_ubus_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
503                                      struct ubus_request_data *req, const char *method,
504                                      struct blob_attr *msg)
505 {
506         struct uh_ubus_session *ses;
507         struct uh_ubus_session_data *data, *ndata;
508         struct blob_attr *tb[__UH_UBUS_SA_MAX];
509         struct blob_attr *attr;
510         int rem;
511
512         blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
513
514         if (!tb[UH_UBUS_SG_SID])
515                 return UBUS_STATUS_INVALID_ARGUMENT;
516
517         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
518         if (!ses)
519                 return UBUS_STATUS_NOT_FOUND;
520
521         if (!tb[UH_UBUS_SG_KEYS]) {
522                 avl_remove_all_elements(&ses->data, data, avl, ndata)
523                         free(data);
524                 return 0;
525         }
526
527         blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
528                 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
529                         continue;
530
531                 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
532                 if (!data)
533                         continue;
534
535                 avl_delete(&ses->data, &data->avl);
536                 free(data);
537         }
538
539         return 0;
540 }
541
542 static int
543 uh_ubus_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
544                                            struct ubus_request_data *req, const char *method,
545                                            struct blob_attr *msg)
546 {
547         struct uh_ubus_session *ses;
548         struct blob_attr *tb;
549
550         blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
551
552         if (!tb)
553                 return UBUS_STATUS_INVALID_ARGUMENT;
554
555         ses = uh_ubus_session_get(blobmsg_data(tb));
556         if (!ses)
557                 return UBUS_STATUS_NOT_FOUND;
558
559         uh_ubus_session_destroy(ses);
560
561         return 0;
562 }
563
564 bool uh_ubus_session_acl_allowed(struct uh_ubus_session *ses, const char *obj, const char *fun)
565 {
566         struct uh_ubus_session_acl *acl;
567
568         uh_foreach_matching_acl(acl, ses, obj, fun)
569                 return true;
570
571         return false;
572 }
573
574 int ubus_session_api_init(struct ubus_context *ctx)
575 {
576         static const struct ubus_method session_methods[] = {
577                 UBUS_METHOD("create",  uh_ubus_handle_create,  &new_policy),
578                 UBUS_METHOD("list",    uh_ubus_handle_list,    &sid_policy),
579                 UBUS_METHOD("grant",   uh_ubus_handle_acl,     acl_policy),
580                 UBUS_METHOD("revoke",  uh_ubus_handle_acl,     acl_policy),
581                 UBUS_METHOD("set",     uh_ubus_handle_set,     set_policy),
582                 UBUS_METHOD("get",     uh_ubus_handle_get,     get_policy),
583                 UBUS_METHOD("unset",   uh_ubus_handle_unset,   get_policy),
584                 UBUS_METHOD("destroy", uh_ubus_handle_destroy, &sid_policy),
585         };
586
587         static struct ubus_object_type session_type =
588                 UBUS_OBJECT_TYPE("uhttpd", session_methods);
589
590         static struct ubus_object obj = {
591                 .name = "session",
592                 .type = &session_type,
593                 .methods = session_methods,
594                 .n_methods = ARRAY_SIZE(session_methods),
595         };
596
597         avl_init(&sessions, avl_strcmp, false, NULL);
598
599         return ubus_add_object(ctx, &obj);
600 }