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