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