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