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