ubusd_acl: rework wildcard support
[oweals/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 }