e426a4af95efaf1e610bf640013dd8b7978ec015
[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 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 }