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