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