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