lua: avoid truncation of large numeric values
[oweals/ubus.git] / ubusd_acl.c
1 /*
2  * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
3  * Copyright (C) 2018 Hans Dedecker <dedeckeh@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License version 2.1
7  * as published by the Free Software Foundation
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #define _GNU_SOURCE
16 #include <sys/socket.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19
20 #include <syslog.h>
21 #include <unistd.h>
22 #include <glob.h>
23 #include <grp.h>
24 #include <pwd.h>
25
26 #include <libubox/vlist.h>
27 #include <libubox/blobmsg_json.h>
28 #include <libubox/avl-cmp.h>
29
30 #include "ubusd.h"
31
32 #ifndef SO_PEERCRED
33 struct ucred {
34         int pid;
35         int uid;
36         int gid;
37 };
38 #endif
39
40 struct ubusd_acl_obj {
41         struct avl_node avl;
42         struct list_head list;
43
44         bool partial;
45
46         const char *user;
47         const char *group;
48
49         struct blob_attr *methods;
50         struct blob_attr *tags;
51         struct blob_attr *priv;
52         bool subscribe;
53         bool publish;
54         bool listen;
55         bool send;
56 };
57
58 struct ubusd_acl_file {
59         struct vlist_node avl;
60
61         const char *user;
62         const char *group;
63
64         struct blob_attr *blob;
65         struct list_head acl;
66
67         int ok;
68 };
69
70 const char *ubusd_acl_dir = "/usr/share/acl.d";
71 static struct blob_buf bbuf;
72 static struct avl_tree ubusd_acls;
73 static int ubusd_acl_seq;
74 static struct ubus_object *acl_obj;
75
76 static int
77 ubusd_acl_match_cred(struct ubus_client *cl, struct ubusd_acl_obj *obj)
78 {
79         if (obj->user && !strcmp(cl->user, obj->user))
80                 return 0;
81
82         if (obj->group && !strcmp(cl->group, obj->group))
83                 return 0;
84
85         return -1;
86 }
87
88 int
89 ubusd_acl_check(struct ubus_client *cl, const char *obj,
90                 const char *method, enum ubusd_acl_type type)
91 {
92         struct ubusd_acl_obj *acl;
93         int match_len = 0;
94
95         if (!cl || !cl->uid || !obj)
96                 return 0;
97
98         /*
99          * Since this tree is sorted alphabetically, we can only expect
100          * to find matching entries as long as the number of matching
101          * characters between the access list string and the object path
102          * is monotonically increasing.
103          */
104         avl_for_each_element(&ubusd_acls, acl, avl) {
105                 const char *key = acl->avl.key;
106                 int cur_match_len;
107                 bool full_match;
108
109                 full_match = ubus_strmatch_len(obj, key, &cur_match_len);
110                 if (cur_match_len < match_len)
111                         break;
112
113                 match_len = cur_match_len;
114
115                 if (!full_match) {
116                         if (!acl->partial)
117                                 continue;
118
119                         if (match_len != (int) strlen(key))
120                                 continue;
121                 }
122
123                 if (ubusd_acl_match_cred(cl, acl))
124                         continue;
125
126                 switch (type) {
127                 case UBUS_ACL_PUBLISH:
128                         if (acl->publish)
129                                 return 0;
130                         break;
131
132                 case UBUS_ACL_SUBSCRIBE:
133                         if (acl->subscribe)
134                                 return 0;
135                         break;
136
137                 case UBUS_ACL_LISTEN:
138                         if (acl->listen)
139                                 return 0;
140                         break;
141
142                 case UBUS_ACL_SEND:
143                         if (acl->send)
144                                 return 0;
145                         break;
146
147                 case UBUS_ACL_ACCESS:
148                         if (acl->methods) {
149                                 struct blob_attr *cur;
150                                 size_t rem;
151
152                                 blobmsg_for_each_attr(cur, acl->methods, rem)
153                                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
154                                                 if (!strcmp(method, blobmsg_get_string(cur)))
155                                                         return 0;
156                         }
157                         break;
158                 }
159         }
160
161         return -1;
162 }
163
164 int
165 ubusd_acl_init_client(struct ubus_client *cl, int fd)
166 {
167         struct ucred cred;
168         struct passwd *pwd;
169         struct group *group;
170
171 #ifdef SO_PEERCRED
172         unsigned int len = sizeof(struct ucred);
173
174         if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
175                 return -1;
176 #else
177         memset(&cred, 0, sizeof(cred));
178 #endif
179
180         pwd = getpwuid(cred.uid);
181         if (!pwd)
182                 return -1;
183
184         group = getgrgid(cred.gid);
185         if (!group)
186                 return -1;
187
188         cl->uid = cred.uid;
189         cl->gid = cred.gid;
190
191         cl->group = strdup(group->gr_name);
192         cl->user = strdup(pwd->pw_name);
193
194         return 0;
195 }
196
197 void
198 ubusd_acl_free_client(struct ubus_client *cl)
199 {
200         free(cl->group);
201         free(cl->user);
202 }
203
204 static void
205 ubusd_acl_file_free(struct ubusd_acl_file *file)
206 {
207         struct ubusd_acl_obj *p, *q;
208
209         list_for_each_entry_safe(p, q, &file->acl, list) {
210                 avl_delete(&ubusd_acls, &p->avl);
211                 list_del(&p->list);
212                 free(p);
213         }
214
215         free(file);
216 }
217
218 enum {
219         ACL_ACCESS_METHODS,
220         ACL_ACCESS_TAGS,
221         ACL_ACCESS_PRIV,
222         __ACL_ACCESS_MAX
223 };
224
225 static const struct blobmsg_policy acl_obj_policy[__ACL_ACCESS_MAX] = {
226         [ACL_ACCESS_METHODS] = { .name = "methods", .type = BLOBMSG_TYPE_ARRAY },
227         [ACL_ACCESS_TAGS] = { .name = "tags", .type = BLOBMSG_TYPE_ARRAY },
228         [ACL_ACCESS_PRIV] = { .name = "acl", .type = BLOBMSG_TYPE_TABLE },
229 };
230
231 static struct ubusd_acl_obj*
232 ubusd_acl_alloc_obj(struct ubusd_acl_file *file, const char *obj)
233 {
234         struct ubusd_acl_obj *o;
235         int len = strlen(obj);
236         char *k;
237         bool partial = false;
238
239         if (obj[len - 1] == '*') {
240                 partial = true;
241                 len--;
242         }
243
244         o = calloc_a(sizeof(*o), &k, len + 1);
245         o->partial = partial;
246         o->user = file->user;
247         o->group = file->group;
248         o->avl.key = memcpy(k, obj, len);
249
250         list_add(&o->list, &file->acl);
251         avl_insert(&ubusd_acls, &o->avl);
252
253         return o;
254 }
255
256 static void
257 ubusd_acl_add_access(struct ubusd_acl_file *file, struct blob_attr *obj)
258 {
259         struct blob_attr *tb[__ACL_ACCESS_MAX];
260         struct ubusd_acl_obj *o;
261
262         blobmsg_parse(acl_obj_policy, __ACL_ACCESS_MAX, tb, blobmsg_data(obj),
263                       blobmsg_data_len(obj));
264
265         if (!tb[ACL_ACCESS_METHODS] && !tb[ACL_ACCESS_TAGS] && !tb[ACL_ACCESS_PRIV])
266                 return;
267
268         o = ubusd_acl_alloc_obj(file, blobmsg_name(obj));
269
270         o->methods = tb[ACL_ACCESS_METHODS];
271         o->tags = tb[ACL_ACCESS_TAGS];
272         o->priv = tb[ACL_ACCESS_PRIV];
273
274         if (file->user || file->group)
275                 file->ok = 1;
276 }
277
278 static void
279 ubusd_acl_add_subscribe(struct ubusd_acl_file *file, const char *obj)
280 {
281         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
282
283         o->subscribe = true;
284 }
285
286 static void
287 ubusd_acl_add_publish(struct ubusd_acl_file *file, const char *obj)
288 {
289         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
290
291         o->publish = true;
292 }
293
294 static void ubusd_acl_add_listen(struct ubusd_acl_file *file, const char *obj)
295 {
296         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
297
298         o->listen = true;
299 }
300
301 static void ubusd_acl_add_send(struct ubusd_acl_file *file, const char *obj)
302 {
303         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
304
305         o->send = true;
306 }
307
308 enum {
309         ACL_USER,
310         ACL_GROUP,
311         ACL_ACCESS,
312         ACL_PUBLISH,
313         ACL_SUBSCRIBE,
314         ACL_INHERIT,
315         ACL_LISTEN,
316         ACL_SEND,
317         __ACL_MAX
318 };
319
320 static const struct blobmsg_policy acl_policy[__ACL_MAX] = {
321         [ACL_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING },
322         [ACL_GROUP] = { .name = "group", .type = BLOBMSG_TYPE_STRING },
323         [ACL_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_TABLE },
324         [ACL_PUBLISH] = { .name = "publish", .type = BLOBMSG_TYPE_ARRAY },
325         [ACL_SUBSCRIBE] = { .name = "subscribe", .type = BLOBMSG_TYPE_ARRAY },
326         [ACL_INHERIT] = { .name = "inherit", .type = BLOBMSG_TYPE_ARRAY },
327         [ACL_LISTEN] = { .name= "listen", .type = BLOBMSG_TYPE_ARRAY },
328         [ACL_SEND] = { .name= "send", .type = BLOBMSG_TYPE_ARRAY },
329 };
330
331 static void
332 ubusd_acl_file_add(struct ubusd_acl_file *file)
333 {
334         struct blob_attr *tb[__ACL_MAX], *cur;
335         size_t rem;
336
337         blobmsg_parse(acl_policy, __ACL_MAX, tb, blob_data(file->blob),
338                       blob_len(file->blob));
339
340         if (tb[ACL_USER])
341                 file->user = blobmsg_get_string(tb[ACL_USER]);
342         else if (tb[ACL_GROUP])
343                 file->group = blobmsg_get_string(tb[ACL_GROUP]);
344         else
345                 return;
346
347         if (tb[ACL_ACCESS])
348                 blobmsg_for_each_attr(cur, tb[ACL_ACCESS], rem)
349                         ubusd_acl_add_access(file, cur);
350
351         if (tb[ACL_SUBSCRIBE])
352                 blobmsg_for_each_attr(cur, tb[ACL_SUBSCRIBE], rem)
353                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
354                                 ubusd_acl_add_subscribe(file, blobmsg_get_string(cur));
355
356         if (tb[ACL_PUBLISH])
357                 blobmsg_for_each_attr(cur, tb[ACL_PUBLISH], rem)
358                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
359                                 ubusd_acl_add_publish(file, blobmsg_get_string(cur));
360
361         if (tb[ACL_LISTEN])
362                 blobmsg_for_each_attr(cur, tb[ACL_LISTEN], rem)
363                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
364                                 ubusd_acl_add_listen(file, blobmsg_get_string(cur));
365
366         if (tb[ACL_SEND])
367                 blobmsg_for_each_attr(cur, tb[ACL_SEND], rem)
368                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
369                                 ubusd_acl_add_send(file, blobmsg_get_string(cur));
370 }
371
372 static void
373 ubusd_acl_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
374         struct vlist_node *node_old)
375 {
376         struct ubusd_acl_file *file;
377
378         if (node_old) {
379                 file = container_of(node_old, struct ubusd_acl_file, avl);
380                 ubusd_acl_file_free(file);
381         }
382
383         if (node_new) {
384                 file = container_of(node_new, struct ubusd_acl_file, avl);
385                 ubusd_acl_file_add(file);
386         }
387 }
388
389 static struct ubus_msg_buf *
390 ubusd_create_sequence_event_msg(void *priv, const char *id)
391 {
392         void *s;
393
394         blob_buf_init(&b, 0);
395         blob_put_int32(&b, UBUS_ATTR_OBJID, 0);
396         blob_put_string(&b, UBUS_ATTR_METHOD, id);
397         s = blob_nest_start(&b, UBUS_ATTR_DATA);
398         blobmsg_add_u32(&b, "sequence", ubusd_acl_seq);
399         blob_nest_end(&b, s);
400
401         return ubus_msg_new(b.head, blob_raw_len(b.head), true);
402 }
403
404 static VLIST_TREE(ubusd_acl_files, avl_strcmp, ubusd_acl_update_cb, false, false);
405
406 static int
407 ubusd_acl_load_file(const char *filename)
408 {
409         struct ubusd_acl_file *file;
410         void *blob;
411
412         blob_buf_init(&bbuf, 0);
413         if (!blobmsg_add_json_from_file(&bbuf, filename)) {
414                 syslog(LOG_ERR, "failed to parse %s\n", filename);
415                 return -1;
416         }
417
418         file = calloc_a(sizeof(*file), &blob, blob_raw_len(bbuf.head));
419         if (!file)
420                 return -1;
421
422         file->blob = blob;
423
424         memcpy(blob, bbuf.head, blob_raw_len(bbuf.head));
425         INIT_LIST_HEAD(&file->acl);
426
427         vlist_add(&ubusd_acl_files, &file->avl, filename);
428         syslog(LOG_INFO, "loading %s\n", filename);
429
430         return 0;
431 }
432
433 void
434 ubusd_acl_load(void)
435 {
436         struct stat st;
437         glob_t gl;
438         size_t j;
439         const char *suffix = "/*.json";
440         char *path = alloca(strlen(ubusd_acl_dir) + strlen(suffix) + 1);
441
442         sprintf(path, "%s%s", ubusd_acl_dir, suffix);
443         if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
444                 return;
445
446         vlist_update(&ubusd_acl_files);
447         for (j = 0; j < gl.gl_pathc; j++) {
448                 if (stat(gl.gl_pathv[j], &st) || !S_ISREG(st.st_mode))
449                         continue;
450
451                 if (st.st_uid || st.st_gid) {
452                         syslog(LOG_ERR, "%s has wrong owner\n", gl.gl_pathv[j]);
453                         continue;
454                 }
455                 if (st.st_mode & (S_IWOTH | S_IWGRP | S_IXOTH)) {
456                         syslog(LOG_ERR, "%s has wrong permissions\n", gl.gl_pathv[j]);
457                         continue;
458                 }
459                 ubusd_acl_load_file(gl.gl_pathv[j]);
460         }
461
462         globfree(&gl);
463         vlist_flush(&ubusd_acl_files);
464         ubusd_acl_seq++;
465         ubusd_send_event(NULL, "ubus.acl.sequence", ubusd_create_sequence_event_msg, NULL);
466 }
467
468 static void
469 ubusd_reply_add(struct ubus_object *obj)
470 {
471         struct ubusd_acl_obj *acl;
472         int match_len = 0;
473
474         if (!obj->path.key)
475                 return;
476
477         /*
478          * Since this tree is sorted alphabetically, we can only expect
479          * to find matching entries as long as the number of matching
480          * characters between the access list string and the object path
481          * is monotonically increasing.
482          */
483         avl_for_each_element(&ubusd_acls, acl, avl) {
484                 const char *key = acl->avl.key;
485                 int cur_match_len;
486                 bool full_match;
487                 void *c;
488
489                 if (!acl->priv)
490                         continue;
491
492                 full_match = ubus_strmatch_len(obj->path.key, key, &cur_match_len);
493                 if (cur_match_len < match_len)
494                         break;
495
496                 match_len = cur_match_len;
497
498                 if (!full_match) {
499                         if (!acl->partial)
500                                 continue;
501
502                         if (match_len != (int) strlen(key))
503                                 continue;
504                 }
505
506                 c = blobmsg_open_table(&b, NULL);
507                 blobmsg_add_string(&b, "obj", obj->path.key);
508                 if (acl->user)
509                         blobmsg_add_string(&b, "user", acl->user);
510                 if (acl->group)
511                         blobmsg_add_string(&b, "group", acl->group);
512
513                 blobmsg_add_field(&b, blobmsg_type(acl->priv), "acl",
514                         blobmsg_data(acl->priv), blobmsg_data_len(acl->priv));
515
516                 blobmsg_close_table(&b, c);
517         }
518 }
519
520 static int ubusd_reply_query(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr, struct blob_attr *msg)
521 {
522         struct ubus_object *obj;
523         void *d, *a;
524
525         if (!attr[UBUS_ATTR_OBJID])
526                 return UBUS_STATUS_INVALID_ARGUMENT;
527
528         obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID]));
529         if (!obj)
530                 return UBUS_STATUS_NOT_FOUND;
531
532         blob_buf_init(&b, 0);
533         blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id);
534         d = blob_nest_start(&b, UBUS_ATTR_DATA);
535
536         blobmsg_add_u32(&b, "seq", ubusd_acl_seq);
537         a = blobmsg_open_array(&b, "acl");
538         list_for_each_entry(obj, &cl->objects, list)
539                 ubusd_reply_add(obj);
540         blobmsg_close_table(&b, a);
541
542         blob_nest_end(&b, d);
543
544         ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA);
545
546         return 0;
547 }
548
549 static int ubusd_acl_recv(struct ubus_client *cl, struct ubus_msg_buf *ub, const char *method, struct blob_attr *msg)
550 {
551         if (!strcmp(method, "query"))
552                 return ubusd_reply_query(cl, ub, ubus_parse_msg(ub->data, blob_raw_len(ub->data)), msg);
553
554         return UBUS_STATUS_INVALID_COMMAND;
555 }
556
557 void ubusd_acl_init(void)
558 {
559         ubus_init_string_tree(&ubusd_acls, true);
560         acl_obj = ubusd_create_object_internal(NULL, UBUS_SYSTEM_OBJECT_ACL);
561         acl_obj->recv_msg = ubusd_acl_recv;
562 }