31f8b45f1a7b5558d3ff59f510486b154df75650
[project/ubus.git] / ubusd_acl.c
1 /*
2  * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License version 2.1
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #define _GNU_SOURCE
15 #include <sys/socket.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18
19 #include <syslog.h>
20 #include <unistd.h>
21 #include <glob.h>
22 #include <grp.h>
23 #include <pwd.h>
24
25 #include <libubox/vlist.h>
26 #include <libubox/blobmsg_json.h>
27 #include <libubox/avl-cmp.h>
28
29 #include "ubusd.h"
30
31 struct ubusd_acl_obj {
32         struct avl_node avl;
33         struct list_head list;
34
35         const char *user;
36         const char *group;
37
38         struct blob_attr *methods;
39         struct blob_attr *tags;
40         struct blob_attr *priv;
41         bool subscribe;
42         bool publish;
43 };
44
45 struct ubusd_acl_file {
46         struct vlist_node avl;
47
48         const char *user;
49         const char *group;
50
51         struct blob_attr *blob;
52         struct list_head acl;
53
54         int ok;
55 };
56
57 static struct blob_buf bbuf;
58 static struct avl_tree ubusd_acls;
59 static int ubusd_acl_seq;
60 static struct ubus_object *acl_obj;
61
62 static int
63 ubusd_acl_match_path(const void *k1, const void *k2, void *ptr)
64 {
65         const char *name = k1;
66         const char *match = k2;
67         char *wildcard = strstr(match, "\t");
68
69         if (wildcard)
70                 return strncmp(name, match, wildcard - match);
71
72         return strcmp(name, match);
73 }
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         struct blob_attr *cur;
93         int rem;
94
95         if (!cl->gid && !cl->gid)
96                 return 0;
97
98         acl = avl_find_ge_element(&ubusd_acls, obj, acl, avl);
99         while (acl && !avl_is_last(&ubusd_acls, &acl->avl)) {
100                 int diff = ubusd_acl_match_path(obj, acl->avl.key, NULL);
101
102                 if (diff)
103                         break;
104
105                 if (ubusd_acl_match_cred(cl, acl)) {
106                         acl = avl_next_element(acl, avl);
107                         continue;
108                 }
109
110                 switch (type) {
111                 case UBUS_ACL_PUBLISH:
112                         if (acl->publish)
113                                 return 0;
114                         break;
115
116                 case UBUS_ACL_SUBSCRIBE:
117                         if (acl->subscribe)
118                                 return 0;
119                         break;
120
121                 case UBUS_ACL_ACCESS:
122                         if (acl->methods)
123                                 blobmsg_for_each_attr(cur, acl->methods, rem)
124                                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
125                                                 if (!ubusd_acl_match_path(method, blobmsg_get_string(cur), NULL))
126                                                         return 0;
127                         break;
128                 }
129                 acl = avl_next_element(acl, avl);
130         }
131
132         return -1;
133 }
134
135 int
136 ubusd_acl_init_client(struct ubus_client *cl, int fd)
137 {
138         unsigned int len = sizeof(struct ucred);
139         struct ucred cred;
140         struct passwd *pwd;
141         struct group *group;
142
143         if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
144                 return -1;
145
146         pwd = getpwuid(cred.uid);
147         if (!pwd)
148                 return -1;
149
150         group = getgrgid(cred.gid);
151         if (!group)
152                 return -1;
153
154         cl->uid = cred.uid;
155         cl->gid = cred.gid;
156
157         cl->group = strdup(group->gr_name);
158         cl->user = strdup(pwd->pw_name);
159
160         return 0;
161 }
162
163 static void
164 ubusd_acl_file_free(struct ubusd_acl_file *file)
165 {
166         struct ubusd_acl_obj *p, *q;
167
168         list_for_each_entry_safe(p, q, &file->acl, list) {
169                 avl_delete(&ubusd_acls, &p->avl);
170                 list_del(&p->list);
171                 free(p);
172         }
173
174         free(file);
175 }
176
177 enum {
178         ACL_ACCESS_METHODS,
179         ACL_ACCESS_TAGS,
180         ACL_ACCESS_PRIV,
181         __ACL_ACCESS_MAX
182 };
183
184 static const struct blobmsg_policy acl_obj_policy[__ACL_ACCESS_MAX] = {
185         [ACL_ACCESS_METHODS] = { .name = "methods", .type = BLOBMSG_TYPE_ARRAY },
186         [ACL_ACCESS_TAGS] = { .name = "tags", .type = BLOBMSG_TYPE_ARRAY },
187         [ACL_ACCESS_PRIV] = { .name = "acl", .type = BLOBMSG_TYPE_TABLE },
188 };
189
190 static struct ubusd_acl_obj*
191 ubusd_acl_alloc_obj(struct ubusd_acl_file *file, const char *obj)
192 {
193         struct ubusd_acl_obj *o;
194         char *k;
195
196         o = calloc_a(1, sizeof(*o), &k, strlen(obj) + 1);
197         o->user = file->user;
198         o->group = file->group;
199         o->avl.key = k;
200         strcpy(k, obj);
201
202         while (*k) {
203                 if (*k == '*')
204                         *k = '\t';
205                 k++;
206         }
207
208         list_add(&o->list, &file->acl);
209         avl_insert(&ubusd_acls, &o->avl);
210
211         return o;
212 }
213
214 static void
215 ubusd_acl_add_access(struct ubusd_acl_file *file, struct blob_attr *obj)
216 {
217         struct blob_attr *tb[__ACL_ACCESS_MAX];
218         struct ubusd_acl_obj *o;
219
220         blobmsg_parse(acl_obj_policy, __ACL_ACCESS_MAX, tb, blobmsg_data(obj),
221                       blobmsg_data_len(obj));
222
223         if (!tb[ACL_ACCESS_METHODS] && !tb[ACL_ACCESS_TAGS] && !tb[ACL_ACCESS_PRIV])
224                 return;
225
226         o = ubusd_acl_alloc_obj(file, blobmsg_name(obj));
227
228         o->methods = tb[ACL_ACCESS_METHODS];
229         o->tags = tb[ACL_ACCESS_TAGS];
230         o->priv = tb[ACL_ACCESS_PRIV];
231
232         if (file->user || file->group)
233                 file->ok = 1;
234 }
235
236 static void
237 ubusd_acl_add_subscribe(struct ubusd_acl_file *file, const char *obj)
238 {
239         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
240
241         o->subscribe = true;
242 }
243
244 static void
245 ubusd_acl_add_publish(struct ubusd_acl_file *file, const char *obj)
246 {
247         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
248
249         o->publish = true;
250 }
251
252 enum {
253         ACL_USER,
254         ACL_GROUP,
255         ACL_ACCESS,
256         ACL_PUBLISH,
257         ACL_SUBSCRIBE,
258         ACL_INHERIT,
259         __ACL_MAX
260 };
261
262 static const struct blobmsg_policy acl_policy[__ACL_MAX] = {
263         [ACL_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING },
264         [ACL_GROUP] = { .name = "group", .type = BLOBMSG_TYPE_STRING },
265         [ACL_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_TABLE },
266         [ACL_PUBLISH] = { .name = "publish", .type = BLOBMSG_TYPE_ARRAY },
267         [ACL_SUBSCRIBE] = { .name = "subscribe", .type = BLOBMSG_TYPE_ARRAY },
268         [ACL_INHERIT] = { .name = "inherit", .type = BLOBMSG_TYPE_ARRAY },
269 };
270
271 static void
272 ubusd_acl_file_add(struct ubusd_acl_file *file)
273 {
274         struct blob_attr *tb[__ACL_MAX], *cur;
275         int rem;
276
277         blobmsg_parse(acl_policy, __ACL_MAX, tb, blob_data(file->blob),
278                       blob_len(file->blob));
279
280         if (tb[ACL_USER])
281                 file->user = blobmsg_get_string(tb[ACL_USER]);
282         else if (tb[ACL_GROUP])
283                 file->group = blobmsg_get_string(tb[ACL_GROUP]);
284         else
285                 return;
286
287         if (!tb[ACL_ACCESS] && !tb[ACL_PUBLISH] && !tb[ACL_INHERIT])
288                 return;
289
290         if (tb[ACL_ACCESS])
291                 blobmsg_for_each_attr(cur, tb[ACL_ACCESS], rem)
292                         ubusd_acl_add_access(file, cur);
293
294         if (tb[ACL_SUBSCRIBE])
295                 blobmsg_for_each_attr(cur, tb[ACL_SUBSCRIBE], rem)
296                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
297                                 ubusd_acl_add_subscribe(file, blobmsg_get_string(cur));
298
299         if (tb[ACL_PUBLISH])
300                 blobmsg_for_each_attr(cur, tb[ACL_PUBLISH], rem)
301                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
302                                 ubusd_acl_add_publish(file, blobmsg_get_string(cur));
303 }
304
305 static void
306 ubusd_acl_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
307         struct vlist_node *node_old)
308 {
309         struct ubusd_acl_file *file;
310
311         if (node_old) {
312                 file = container_of(node_old, struct ubusd_acl_file, avl);
313                 ubusd_acl_file_free(file);
314         }
315
316         if (node_new) {
317                 file = container_of(node_new, struct ubusd_acl_file, avl);
318                 ubusd_acl_file_add(file);
319         }
320 }
321
322 static struct ubus_msg_buf *
323 ubusd_create_sequence_event_msg(void *priv, const char *id)
324 {
325         void *s;
326
327         blob_buf_init(&b, 0);
328         blob_put_int32(&b, UBUS_ATTR_OBJID, 0);
329         blob_put_string(&b, UBUS_ATTR_METHOD, id);
330         s = blob_nest_start(&b, UBUS_ATTR_DATA);
331         blobmsg_add_u32(&b, "sequence", ubusd_acl_seq);
332         blob_nest_end(&b, s);
333
334         return ubus_msg_new(b.head, blob_raw_len(b.head), true);
335 }
336
337 static VLIST_TREE(ubusd_acl_files, avl_strcmp, ubusd_acl_update_cb, false, false);
338
339 static int
340 ubusd_acl_load_file(const char *filename)
341 {
342         struct ubusd_acl_file *file;
343         void *blob;
344
345         blob_buf_init(&bbuf, 0);
346         if (!blobmsg_add_json_from_file(&bbuf, filename)) {
347                 syslog(LOG_ERR, "failed to parse %s\n", filename);
348                 return -1;
349         }
350
351         file = calloc_a(sizeof(*file), &blob, blob_raw_len(bbuf.head));
352         if (!file)
353                 return -1;
354
355         file->blob = blob;
356
357         memcpy(blob, bbuf.head, blob_raw_len(bbuf.head));
358         INIT_LIST_HEAD(&file->acl);
359
360         vlist_add(&ubusd_acl_files, &file->avl, filename);
361         syslog(LOG_INFO, "loading %s\n", filename);
362
363         return 0;
364 }
365
366 void
367 ubusd_acl_load(void)
368 {
369         struct stat st;
370         glob_t gl;
371         int j;
372
373         if (glob("/usr/share/acl.d/*.json", GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
374                 return;
375
376         vlist_update(&ubusd_acl_files);
377         for (j = 0; j < gl.gl_pathc; j++) {
378                 if (stat(gl.gl_pathv[j], &st) || !S_ISREG(st.st_mode))
379                         continue;
380
381                 if (st.st_uid || st.st_gid) {
382                         syslog(LOG_ERR, "%s has wrong owner\n", gl.gl_pathv[j]);
383                         continue;
384                 }
385                 if (st.st_mode & (S_IWOTH | S_IWGRP | S_IXOTH)) {
386                         syslog(LOG_ERR, "%s has wrong permissions\n", gl.gl_pathv[j]);
387                         continue;
388                 }
389                 ubusd_acl_load_file(gl.gl_pathv[j]);
390         }
391
392         globfree(&gl);
393         vlist_flush(&ubusd_acl_files);
394         ubusd_acl_seq++;
395         ubusd_send_event(NULL, "ubus.acl.sequence", ubusd_create_sequence_event_msg, NULL);
396 }
397
398 static void
399 ubusd_reply_add(struct ubus_object *obj)
400 {
401         struct ubusd_acl_obj *acl;
402
403         if (!obj->path.key)
404                 return;
405         acl = avl_find_ge_element(&ubusd_acls, obj->path.key, acl, avl);
406         while (acl && !avl_is_last(&ubusd_acls, &acl->avl) &&
407                       !ubusd_acl_match_path(obj->path.key, acl->avl.key, NULL)) {
408
409                 if (acl->priv) {
410                         void *c = blobmsg_open_table(&b, NULL);
411
412                         blobmsg_add_string(&b, "obj", obj->path.key);
413                         if (acl->user)
414                                 blobmsg_add_string(&b, "user", acl->user);
415                         if (acl->group)
416                                 blobmsg_add_string(&b, "group", acl->group);
417
418                         if (acl->priv)
419                                 blobmsg_add_field(&b, blobmsg_type(acl->priv), "acl",
420                                         blobmsg_data(acl->priv), blobmsg_data_len(acl->priv));
421
422                         blobmsg_close_table(&b, c);
423                 }
424                 acl = avl_next_element(acl, avl);
425         }
426 }
427 static int ubusd_reply_query(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr, struct blob_attr *msg)
428 {
429         struct ubus_object *obj;
430         void *d, *a;
431
432         if (!attr[UBUS_ATTR_OBJID])
433                 return UBUS_STATUS_INVALID_ARGUMENT;
434
435         obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID]));
436         if (!obj)
437                 return UBUS_STATUS_NOT_FOUND;
438
439         blob_buf_init(&b, 0);
440         blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id);
441         d = blob_nest_start(&b, UBUS_ATTR_DATA);
442
443         blobmsg_add_u32(&b, "seq", ubusd_acl_seq);
444         a = blobmsg_open_array(&b, "acl");
445         list_for_each_entry(obj, &cl->objects, list)
446                 ubusd_reply_add(obj);
447         blobmsg_close_table(&b, a);
448
449         blob_nest_end(&b, d);
450
451         ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA);
452
453         return 0;
454 }
455
456 static int ubusd_acl_recv(struct ubus_client *cl, struct ubus_msg_buf *ub, const char *method, struct blob_attr *msg)
457 {
458         if (!strcmp(method, "query"))
459                 return ubusd_reply_query(cl, ub, ubus_parse_msg(ub->data), msg);
460
461         return UBUS_STATUS_INVALID_COMMAND;
462 }
463
464 void ubusd_acl_init(void)
465 {
466         avl_init(&ubusd_acls, ubusd_acl_match_path, true, NULL);
467         acl_obj = ubusd_create_object_internal(NULL, UBUS_SYSTEM_OBJECT_ACL);
468         acl_obj->recv_msg = ubusd_acl_recv;
469 }