add missing file exists check
[project/uhttpd.git] / ubus-session.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
6  *
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
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
148         c = blobmsg_open_table(&buf, "acls");
149         uh_ubus_session_dump_acls(ses, &buf);
150         blobmsg_close_table(&buf, c);
151
152         c = blobmsg_open_table(&buf, "data");
153         uh_ubus_session_dump_data(ses, &buf);
154         blobmsg_close_table(&buf, c);
155
156         ubus_send_reply(ctx, req, buf.head);
157 }
158
159 static void
160 uh_ubus_touch_session(struct uh_ubus_session *ses)
161 {
162         uloop_timeout_set(&ses->t, ses->timeout * 1000);
163 }
164
165 static void
166 uh_ubus_session_destroy(struct uh_ubus_session *ses)
167 {
168         struct uh_ubus_session_acl *acl, *nacl;
169         struct uh_ubus_session_data *data, *ndata;
170
171         uloop_timeout_cancel(&ses->t);
172         avl_remove_all_elements(&ses->acls, acl, avl, nacl)
173                 free(acl);
174
175         avl_remove_all_elements(&ses->data, data, avl, ndata)
176                 free(data);
177
178         avl_delete(&sessions, &ses->avl);
179         free(ses);
180 }
181
182 static void uh_ubus_session_timeout(struct uloop_timeout *t)
183 {
184         struct uh_ubus_session *ses;
185
186         ses = container_of(t, struct uh_ubus_session, t);
187         uh_ubus_session_destroy(ses);
188 }
189
190 static struct uh_ubus_session *
191 uh_ubus_session_create(int timeout)
192 {
193         struct uh_ubus_session *ses;
194
195         ses = calloc(1, sizeof(*ses));
196         if (!ses)
197                 return NULL;
198
199         ses->timeout  = timeout;
200         ses->avl.key  = ses->id;
201         uh_ubus_random(ses->id);
202
203         avl_insert(&sessions, &ses->avl);
204         avl_init(&ses->acls, avl_strcmp, true, NULL);
205         avl_init(&ses->data, avl_strcmp, false, NULL);
206
207         ses->t.cb = uh_ubus_session_timeout;
208         uh_ubus_touch_session(ses);
209
210         return ses;
211 }
212
213 struct uh_ubus_session *
214 uh_ubus_session_get(const char *id)
215 {
216         struct uh_ubus_session *ses;
217
218         ses = avl_find_element(&sessions, id, ses, avl);
219         if (!ses)
220                 return NULL;
221
222         uh_ubus_touch_session(ses);
223         return ses;
224 }
225
226 static int
227 uh_ubus_handle_create(struct ubus_context *ctx, struct ubus_object *obj,
228                                           struct ubus_request_data *req, const char *method,
229                                           struct blob_attr *msg)
230 {
231         struct uh_ubus_session *ses;
232         struct blob_attr *tb;
233         int timeout = UBUS_DEFAULT_SESSION_TIMEOUT;
234
235         blobmsg_parse(&new_policy, 1, &tb, blob_data(msg), blob_len(msg));
236         if (tb)
237                 timeout = blobmsg_get_u32(tb);
238
239         ses = uh_ubus_session_create(timeout);
240         if (ses)
241                 uh_ubus_session_dump(ses, ctx, req);
242
243         return 0;
244 }
245
246 static int
247 uh_ubus_handle_list(struct ubus_context *ctx, struct ubus_object *obj,
248                                         struct ubus_request_data *req, const char *method,
249                                         struct blob_attr *msg)
250 {
251         struct uh_ubus_session *ses;
252         struct blob_attr *tb;
253
254         blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
255
256         if (!tb) {
257                 avl_for_each_element(&sessions, ses, avl)
258                         uh_ubus_session_dump(ses, ctx, req);
259                 return 0;
260         }
261
262         ses = uh_ubus_session_get(blobmsg_data(tb));
263         if (!ses)
264                 return UBUS_STATUS_NOT_FOUND;
265
266         uh_ubus_session_dump(ses, ctx, req);
267
268         return 0;
269 }
270
271 static int
272 uh_id_len(const char *str)
273 {
274         return strcspn(str, "*?[");
275 }
276
277 static int
278 uh_ubus_session_grant(struct uh_ubus_session *ses, struct ubus_context *ctx,
279                       const char *object, const char *function)
280 {
281         struct uh_ubus_session_acl *acl;
282         char *new_obj, *new_func, *new_id;
283         int id_len;
284
285         if (!object || !function)
286                 return UBUS_STATUS_INVALID_ARGUMENT;
287
288         uh_foreach_matching_acl_prefix(acl, ses, object, function) {
289                 if (!strcmp(acl->object, object) &&
290                     !strcmp(acl->function, function))
291                         return 0;
292         }
293
294         id_len = uh_id_len(object);
295         acl = calloc_a(sizeof(*acl),
296                 &new_obj, strlen(object) + 1,
297                 &new_func, strlen(function) + 1,
298                 &new_id, id_len + 1);
299
300         if (!acl)
301                 return UBUS_STATUS_UNKNOWN_ERROR;
302
303         acl->object = strcpy(new_obj, object);
304         acl->function = strcpy(new_func, function);
305         acl->avl.key = strncpy(new_id, object, id_len);
306         avl_insert(&ses->acls, &acl->avl);
307
308         return 0;
309 }
310
311 static int
312 uh_ubus_session_revoke(struct uh_ubus_session *ses, struct ubus_context *ctx,
313                        const char *object, const char *function)
314 {
315         struct uh_ubus_session_acl *acl, *next;
316         int id_len;
317         char *id;
318
319         if (!object && !function) {
320                 avl_remove_all_elements(&ses->acls, acl, avl, next)
321                         free(acl);
322                 return 0;
323         }
324
325         id_len = uh_id_len(object);
326         id = alloca(id_len + 1);
327         strncpy(id, object, id_len);
328         id[id_len] = 0;
329
330         acl = avl_find_element(&ses->acls, id, acl, avl);
331         while (acl) {
332                 if (!avl_is_last(&ses->acls, &acl->avl))
333                         next = avl_next_element(acl, avl);
334                 else
335                         next = NULL;
336
337                 if (strcmp(id, acl->avl.key) != 0)
338                         break;
339
340                 if (!strcmp(acl->object, object) &&
341                     !strcmp(acl->function, function)) {
342                         avl_delete(&ses->acls, &acl->avl);
343                         free(acl);
344                 }
345                 acl = next;
346         }
347
348         return 0;
349 }
350
351
352 static int
353 uh_ubus_handle_acl(struct ubus_context *ctx, struct ubus_object *obj,
354                    struct ubus_request_data *req, const char *method,
355                    struct blob_attr *msg)
356 {
357         struct uh_ubus_session *ses;
358         struct blob_attr *tb[__UH_UBUS_SA_MAX];
359         struct blob_attr *attr, *sattr;
360         const char *object, *function;
361         int rem1, rem2;
362
363         int (*cb)(struct uh_ubus_session *ses, struct ubus_context *ctx,
364                   const char *object, const char *function);
365
366         blobmsg_parse(acl_policy, __UH_UBUS_SA_MAX, tb, blob_data(msg), blob_len(msg));
367
368         if (!tb[UH_UBUS_SA_SID])
369                 return UBUS_STATUS_INVALID_ARGUMENT;
370
371         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SA_SID]));
372         if (!ses)
373                 return UBUS_STATUS_NOT_FOUND;
374
375         if (!strcmp(method, "grant"))
376                 cb = uh_ubus_session_grant;
377         else
378                 cb = uh_ubus_session_revoke;
379
380         if (!tb[UH_UBUS_SA_OBJECTS])
381                 return cb(ses, ctx, NULL, NULL);
382
383         blobmsg_for_each_attr(attr, tb[UH_UBUS_SA_OBJECTS], rem1) {
384                 if (blob_id(attr) != BLOBMSG_TYPE_ARRAY)
385                         continue;
386
387                 object = NULL;
388                 function = NULL;
389
390                 blobmsg_for_each_attr(sattr, attr, rem2) {
391                         if (blob_id(sattr) != BLOBMSG_TYPE_STRING)
392                                 continue;
393
394                         if (!object)
395                                 object = blobmsg_data(sattr);
396                         else if (!function)
397                                 function = blobmsg_data(sattr);
398                         else
399                                 break;
400                 }
401
402                 if (object && function)
403                         cb(ses, ctx, object, function);
404         }
405
406         return 0;
407 }
408
409 static int
410 uh_ubus_handle_set(struct ubus_context *ctx, struct ubus_object *obj,
411                                    struct ubus_request_data *req, const char *method,
412                                    struct blob_attr *msg)
413 {
414         struct uh_ubus_session *ses;
415         struct uh_ubus_session_data *data;
416         struct blob_attr *tb[__UH_UBUS_SA_MAX];
417         struct blob_attr *attr;
418         int rem;
419
420         blobmsg_parse(set_policy, __UH_UBUS_SS_MAX, tb, blob_data(msg), blob_len(msg));
421
422         if (!tb[UH_UBUS_SS_SID] || !tb[UH_UBUS_SS_VALUES])
423                 return UBUS_STATUS_INVALID_ARGUMENT;
424
425         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SS_SID]));
426         if (!ses)
427                 return UBUS_STATUS_NOT_FOUND;
428
429         blobmsg_for_each_attr(attr, tb[UH_UBUS_SS_VALUES], rem) {
430                 if (!blobmsg_name(attr)[0])
431                         continue;
432
433                 data = avl_find_element(&ses->data, blobmsg_name(attr), data, avl);
434                 if (data) {
435                         avl_delete(&ses->data, &data->avl);
436                         free(data);
437                 }
438
439                 data = calloc(1, sizeof(*data) + blob_pad_len(attr));
440                 if (!data)
441                         break;
442
443                 memcpy(data->attr, attr, blob_pad_len(attr));
444                 data->avl.key = blobmsg_name(data->attr);
445                 avl_insert(&ses->data, &data->avl);
446         }
447
448         return 0;
449 }
450
451 static int
452 uh_ubus_handle_get(struct ubus_context *ctx, struct ubus_object *obj,
453                                    struct ubus_request_data *req, const char *method,
454                                    struct blob_attr *msg)
455 {
456         struct uh_ubus_session *ses;
457         struct uh_ubus_session_data *data;
458         struct blob_attr *tb[__UH_UBUS_SA_MAX];
459         struct blob_attr *attr;
460         void *c;
461         int rem;
462
463         blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
464
465         if (!tb[UH_UBUS_SG_SID])
466                 return UBUS_STATUS_INVALID_ARGUMENT;
467
468         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
469         if (!ses)
470                 return UBUS_STATUS_NOT_FOUND;
471
472         blob_buf_init(&buf, 0);
473         c = blobmsg_open_table(&buf, "values");
474
475         if (!tb[UH_UBUS_SG_KEYS]) {
476                 uh_ubus_session_dump_data(ses, &buf);
477                 return 0;
478         }
479
480         blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
481                 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
482                         continue;
483
484                 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
485                 if (!data)
486                         continue;
487
488                 blobmsg_add_field(&buf, blobmsg_type(data->attr),
489                                   blobmsg_name(data->attr),
490                                   blobmsg_data(data->attr),
491                                   blobmsg_data_len(data->attr));
492         }
493
494         blobmsg_close_table(&buf, c);
495         ubus_send_reply(ctx, req, buf.head);
496
497         return 0;
498 }
499
500 static int
501 uh_ubus_handle_unset(struct ubus_context *ctx, struct ubus_object *obj,
502                                      struct ubus_request_data *req, const char *method,
503                                      struct blob_attr *msg)
504 {
505         struct uh_ubus_session *ses;
506         struct uh_ubus_session_data *data, *ndata;
507         struct blob_attr *tb[__UH_UBUS_SA_MAX];
508         struct blob_attr *attr;
509         int rem;
510
511         blobmsg_parse(get_policy, __UH_UBUS_SG_MAX, tb, blob_data(msg), blob_len(msg));
512
513         if (!tb[UH_UBUS_SG_SID])
514                 return UBUS_STATUS_INVALID_ARGUMENT;
515
516         ses = uh_ubus_session_get(blobmsg_data(tb[UH_UBUS_SG_SID]));
517         if (!ses)
518                 return UBUS_STATUS_NOT_FOUND;
519
520         if (!tb[UH_UBUS_SG_KEYS]) {
521                 avl_remove_all_elements(&ses->data, data, avl, ndata)
522                         free(data);
523                 return 0;
524         }
525
526         blobmsg_for_each_attr(attr, tb[UH_UBUS_SG_KEYS], rem) {
527                 if (blob_id(attr) != BLOBMSG_TYPE_STRING)
528                         continue;
529
530                 data = avl_find_element(&ses->data, blobmsg_data(attr), data, avl);
531                 if (!data)
532                         continue;
533
534                 avl_delete(&ses->data, &data->avl);
535                 free(data);
536         }
537
538         return 0;
539 }
540
541 static int
542 uh_ubus_handle_destroy(struct ubus_context *ctx, struct ubus_object *obj,
543                                            struct ubus_request_data *req, const char *method,
544                                            struct blob_attr *msg)
545 {
546         struct uh_ubus_session *ses;
547         struct blob_attr *tb;
548
549         blobmsg_parse(&sid_policy, 1, &tb, blob_data(msg), blob_len(msg));
550
551         if (!tb)
552                 return UBUS_STATUS_INVALID_ARGUMENT;
553
554         ses = uh_ubus_session_get(blobmsg_data(tb));
555         if (!ses)
556                 return UBUS_STATUS_NOT_FOUND;
557
558         uh_ubus_session_destroy(ses);
559
560         return 0;
561 }
562
563 bool uh_ubus_session_acl_allowed(struct uh_ubus_session *ses, const char *obj, const char *fun)
564 {
565         struct uh_ubus_session_acl *acl;
566
567         uh_foreach_matching_acl(acl, ses, obj, fun)
568                 return true;
569
570         return false;
571 }
572
573 int ubus_session_api_init(struct ubus_context *ctx)
574 {
575         static const struct ubus_method session_methods[] = {
576                 UBUS_METHOD("create",  uh_ubus_handle_create,  &new_policy),
577                 UBUS_METHOD("list",    uh_ubus_handle_list,    &sid_policy),
578                 UBUS_METHOD("grant",   uh_ubus_handle_acl,     acl_policy),
579                 UBUS_METHOD("revoke",  uh_ubus_handle_acl,     acl_policy),
580                 UBUS_METHOD("set",     uh_ubus_handle_set,     set_policy),
581                 UBUS_METHOD("get",     uh_ubus_handle_get,     get_policy),
582                 UBUS_METHOD("unset",   uh_ubus_handle_unset,   get_policy),
583                 UBUS_METHOD("destroy", uh_ubus_handle_destroy, &sid_policy),
584         };
585
586         static struct ubus_object_type session_type =
587                 UBUS_OBJECT_TYPE("uhttpd", session_methods);
588
589         static struct ubus_object obj = {
590                 .name = "session",
591                 .type = &session_type,
592                 .methods = session_methods,
593                 .n_methods = ARRAY_SIZE(session_methods),
594         };
595
596         avl_init(&sessions, avl_strcmp, false, NULL);
597
598         return ubus_add_object(ctx, &obj);
599 }