• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ubus/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 #include <libubox/ulog.h>
 30 
 31 #include "ubusd.h"
 32 
 33 #ifndef SO_PEERCRED
 34 struct ucred {
 35         int pid;
 36         int uid;
 37         int gid;
 38 };
 39 #endif
 40 
 41 struct ubusd_acl_obj {
 42         struct avl_node avl;
 43         struct list_head list;
 44 
 45         bool partial;
 46 
 47         const char *user;
 48         const char *group;
 49         int uid;
 50         int gid;
 51 
 52         struct blob_attr *methods;
 53         struct blob_attr *tags;
 54         struct blob_attr *priv;
 55         bool subscribe;
 56         bool publish;
 57         bool listen;
 58         bool send;
 59 };
 60 
 61 struct ubusd_acl_file {
 62         struct vlist_node avl;
 63 
 64         const char *user;
 65         const char *group;
 66         int uid;
 67         int gid;
 68 
 69         struct blob_attr *blob;
 70         struct list_head acl;
 71 
 72         int ok;
 73 };
 74 
 75 const char *ubusd_acl_dir = "/usr/share/acl.d";
 76 static struct blob_buf bbuf;
 77 static struct avl_tree ubusd_acls;
 78 static uint32_t ubusd_acl_seq;
 79 static struct ubus_object *acl_obj;
 80 
 81 static int
 82 ubusd_acl_match_cred(struct ubus_client *cl, struct ubusd_acl_obj *obj)
 83 {
 84         size_t i;
 85 
 86         if (obj->uid != -1 && cl->uid == obj->uid)
 87                 return 0;
 88 
 89         if (obj->gid != -1) {
 90                 if (cl->gid == obj->gid)
 91                         return 0;
 92 
 93                 for (i = 0; i < cl->n_extra_gid; i++)
 94                         if (cl->extra_gid[i] == obj->gid)
 95                                 return 0;
 96         }
 97 
 98         return -1;
 99 }
100 
101 int
102 ubusd_acl_check(struct ubus_client *cl, const char *obj,
103                 const char *method, enum ubusd_acl_type type)
104 {
105         struct ubusd_acl_obj *acl;
106         int match_len = 0;
107 
108         if (!cl || !cl->uid || !obj)
109                 return 0;
110 
111         /*
112          * Since this tree is sorted alphabetically, we can only expect
113          * to find matching entries as long as the number of matching
114          * characters between the access list string and the object path
115          * is monotonically increasing.
116          */
117         avl_for_each_element(&ubusd_acls, acl, avl) {
118                 const char *key = acl->avl.key;
119                 int cur_match_len;
120                 bool full_match;
121 
122                 full_match = ubus_strmatch_len(obj, key, &cur_match_len);
123                 if (cur_match_len < match_len)
124                         break;
125 
126                 match_len = cur_match_len;
127 
128                 if (!full_match) {
129                         if (!acl->partial)
130                                 continue;
131 
132                         if (match_len != (int) strlen(key))
133                                 continue;
134                 }
135 
136                 if (ubusd_acl_match_cred(cl, acl))
137                         continue;
138 
139                 switch (type) {
140                 case UBUS_ACL_PUBLISH:
141                         if (acl->publish)
142                                 return 0;
143                         break;
144 
145                 case UBUS_ACL_SUBSCRIBE:
146                         if (acl->subscribe)
147                                 return 0;
148                         break;
149 
150                 case UBUS_ACL_LISTEN:
151                         if (acl->listen)
152                                 return 0;
153                         break;
154 
155                 case UBUS_ACL_SEND:
156                         if (acl->send)
157                                 return 0;
158                         break;
159 
160                 case UBUS_ACL_ACCESS:
161                         if (acl->methods) {
162                                 struct blob_attr *cur;
163                                 char *cur_method;
164                                 size_t rem;
165 
166                                 blobmsg_for_each_attr(cur, acl->methods, rem)
167                                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING) {
168                                                 cur_method = blobmsg_get_string(cur);
169 
170                                                 if (!strcmp(method, cur_method) || !strcmp("*", cur_method))
171                                                         return 0;
172                                         }
173                         }
174                         break;
175                 }
176         }
177 
178         return -1;
179 }
180 
181 static int
182 ubusd_acl_load_extra_gids(struct ubus_client *cl, pid_t pid)
183 {
184 #ifdef __linux__
185         char path[64];
186         FILE *f;
187         char line[512];
188         gid_t gids[UBUS_CLIENT_MAX_EXTRA_GID];
189         size_t n_gids = 0;
190 
191         snprintf(path, sizeof(path), "/proc/%d/status", (int)pid);
192         f = fopen(path, "r");
193         if (!f)
194                 return 0;
195 
196         while (fgets(line, sizeof(line), f)) {
197                 if (strncmp(line, "Groups:", 7) != 0)
198                         continue;
199 
200                 char *p = line + 7;
201                 while (*p && n_gids < UBUS_CLIENT_MAX_EXTRA_GID) {
202                         char *end;
203                         unsigned long gid = strtoul(p, &end, 10);
204                         if (p == end)
205                                 break;
206                         gids[n_gids++] = gid;
207                         p = end;
208                 }
209                 break;
210         }
211 
212         fclose(f);
213 
214         if (n_gids > 0) {
215                 cl->extra_gid = malloc(n_gids * sizeof(gid_t));
216                 if (!cl->extra_gid)
217                         return -1;
218                 memcpy(cl->extra_gid, gids, n_gids * sizeof(gid_t));
219                 cl->n_extra_gid = n_gids;
220         }
221 #endif
222 
223         return 0;
224 }
225 
226 int
227 ubusd_acl_init_client(struct ubus_client *cl, int fd)
228 {
229         struct ucred cred;
230         struct passwd *pwd;
231         struct group *group;
232 
233 #ifdef SO_PEERCRED
234         unsigned int len = sizeof(struct ucred);
235 
236         if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
237                 ULOG_ERR("Failed getsockopt(): %m\n");
238                 return -1;
239         }
240 #else
241         memset(&cred, 0, sizeof(cred));
242 #endif
243 
244         pwd = getpwuid(cred.uid);
245         if (!pwd) {
246                 ULOG_ERR("Failed getpwuid(): %m\n");
247                 return -1;
248         }
249 
250         group = getgrgid(cred.gid);
251         if (!group) {
252                 ULOG_ERR("Failed getgrgid(): %m\n");
253                 return -1;
254         }
255 
256         cl->uid = cred.uid;
257         cl->gid = cred.gid;
258 
259         cl->group = strdup(group->gr_name);
260         if (!cl->group) {
261                 ULOG_ERR("Failed strdup() for group\n");
262                 return -1;
263         }
264 
265         cl->user = strdup(pwd->pw_name);
266         if (!cl->user) {
267                 ULOG_ERR("Failed strdup() for user\n");
268                 free(cl->group);
269                 cl->group = NULL;
270                 return -1;
271         }
272 
273         if (ubusd_acl_load_extra_gids(cl, cred.pid)) {
274                 ULOG_ERR("Failed to load extra gids\n");
275                 free(cl->user);
276                 cl->user = NULL;
277                 free(cl->group);
278                 cl->group = NULL;
279                 return -1;
280         }
281 
282         return 0;
283 }
284 
285 void
286 ubusd_acl_free_client(struct ubus_client *cl)
287 {
288         free(cl->extra_gid);
289         free(cl->group);
290         free(cl->user);
291 }
292 
293 static void
294 ubusd_acl_file_free(struct ubusd_acl_file *file)
295 {
296         struct ubusd_acl_obj *p, *q;
297 
298         list_for_each_entry_safe(p, q, &file->acl, list) {
299                 avl_delete(&ubusd_acls, &p->avl);
300                 list_del(&p->list);
301                 free(p);
302         }
303 
304         free(file);
305 }
306 
307 enum {
308         ACL_ACCESS_METHODS,
309         ACL_ACCESS_TAGS,
310         ACL_ACCESS_PRIV,
311         __ACL_ACCESS_MAX
312 };
313 
314 static const struct blobmsg_policy acl_obj_policy[__ACL_ACCESS_MAX] = {
315         [ACL_ACCESS_METHODS] = { .name = "methods", .type = BLOBMSG_TYPE_ARRAY },
316         [ACL_ACCESS_TAGS] = { .name = "tags", .type = BLOBMSG_TYPE_ARRAY },
317         [ACL_ACCESS_PRIV] = { .name = "acl", .type = BLOBMSG_TYPE_TABLE },
318 };
319 
320 static struct ubusd_acl_obj*
321 ubusd_acl_alloc_obj(struct ubusd_acl_file *file, const char *obj)
322 {
323         struct ubusd_acl_obj *o;
324         size_t len = strlen(obj);
325         char *k;
326         bool partial = false;
327 
328         if (!len)
329                 return NULL;
330 
331         if (obj[len - 1] == '*') {
332                 partial = true;
333                 len--;
334         }
335 
336         o = calloc_a(sizeof(*o), &k, len + 1);
337         if (!o)
338                 return NULL;
339         o->partial = partial;
340         o->user = file->user;
341         o->group = file->group;
342         o->uid = file->uid;
343         o->gid = file->gid;
344         o->avl.key = memcpy(k, obj, len);
345 
346         list_add(&o->list, &file->acl);
347         avl_insert(&ubusd_acls, &o->avl);
348 
349         return o;
350 }
351 
352 static void
353 ubusd_acl_add_access(struct ubusd_acl_file *file, struct blob_attr *obj)
354 {
355         struct blob_attr *tb[__ACL_ACCESS_MAX];
356         struct ubusd_acl_obj *o;
357 
358         blobmsg_parse(acl_obj_policy, __ACL_ACCESS_MAX, tb, blobmsg_data(obj),
359                       blobmsg_data_len(obj));
360 
361         if (!tb[ACL_ACCESS_METHODS] && !tb[ACL_ACCESS_TAGS] && !tb[ACL_ACCESS_PRIV])
362                 return;
363 
364         o = ubusd_acl_alloc_obj(file, blobmsg_name(obj));
365         if (!o)
366                 return;
367 
368         o->methods = tb[ACL_ACCESS_METHODS];
369         o->tags = tb[ACL_ACCESS_TAGS];
370         o->priv = tb[ACL_ACCESS_PRIV];
371 
372         if (file->uid > 0 || file->gid > 0)
373                 file->ok = 1;
374 }
375 
376 static void
377 ubusd_acl_add_subscribe(struct ubusd_acl_file *file, const char *obj)
378 {
379         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
380 
381         if (!o)
382                 return;
383 
384         o->subscribe = true;
385 }
386 
387 static void
388 ubusd_acl_add_publish(struct ubusd_acl_file *file, const char *obj)
389 {
390         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
391 
392         if (!o)
393                 return;
394 
395         o->publish = true;
396 }
397 
398 static void ubusd_acl_add_listen(struct ubusd_acl_file *file, const char *obj)
399 {
400         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
401 
402         if (!o)
403                 return;
404 
405         o->listen = true;
406 }
407 
408 static void ubusd_acl_add_send(struct ubusd_acl_file *file, const char *obj)
409 {
410         struct ubusd_acl_obj *o = ubusd_acl_alloc_obj(file, obj);
411 
412         if (!o)
413                 return;
414 
415         o->send = true;
416 }
417 
418 enum {
419         ACL_USER,
420         ACL_GROUP,
421         ACL_ACCESS,
422         ACL_PUBLISH,
423         ACL_SUBSCRIBE,
424         ACL_INHERIT,
425         ACL_LISTEN,
426         ACL_SEND,
427         __ACL_MAX
428 };
429 
430 static const struct blobmsg_policy acl_policy[__ACL_MAX] = {
431         [ACL_USER] = { .name = "user", .type = BLOBMSG_TYPE_STRING },
432         [ACL_GROUP] = { .name = "group", .type = BLOBMSG_TYPE_STRING },
433         [ACL_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_TABLE },
434         [ACL_PUBLISH] = { .name = "publish", .type = BLOBMSG_TYPE_ARRAY },
435         [ACL_SUBSCRIBE] = { .name = "subscribe", .type = BLOBMSG_TYPE_ARRAY },
436         [ACL_INHERIT] = { .name = "inherit", .type = BLOBMSG_TYPE_ARRAY },
437         [ACL_LISTEN] = { .name= "listen", .type = BLOBMSG_TYPE_ARRAY },
438         [ACL_SEND] = { .name= "send", .type = BLOBMSG_TYPE_ARRAY },
439 };
440 
441 static void
442 ubusd_acl_file_add(struct ubusd_acl_file *file)
443 {
444         struct blob_attr *tb[__ACL_MAX], *cur;
445         size_t rem;
446 
447         blobmsg_parse(acl_policy, __ACL_MAX, tb, blob_data(file->blob),
448                       blob_len(file->blob));
449 
450         file->uid = -1;
451         file->gid = -1;
452 
453         if (tb[ACL_USER]) {
454                 struct passwd *pwd;
455 
456                 file->user = blobmsg_get_string(tb[ACL_USER]);
457                 pwd = getpwnam(file->user);
458                 if (pwd)
459                         file->uid = pwd->pw_uid;
460                 else
461                         file->uid = 0;
462         } else if (tb[ACL_GROUP]) {
463                 struct group *grp;
464 
465                 file->group = blobmsg_get_string(tb[ACL_GROUP]);
466                 grp = getgrnam(file->group);
467                 if (grp)
468                         file->gid = grp->gr_gid;
469                 else
470                         file->gid = 0;
471         } else {
472                 return;
473         }
474 
475         if (tb[ACL_ACCESS])
476                 blobmsg_for_each_attr(cur, tb[ACL_ACCESS], rem)
477                         ubusd_acl_add_access(file, cur);
478 
479         if (tb[ACL_SUBSCRIBE])
480                 blobmsg_for_each_attr(cur, tb[ACL_SUBSCRIBE], rem)
481                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
482                                 ubusd_acl_add_subscribe(file, blobmsg_get_string(cur));
483 
484         if (tb[ACL_PUBLISH])
485                 blobmsg_for_each_attr(cur, tb[ACL_PUBLISH], rem)
486                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
487                                 ubusd_acl_add_publish(file, blobmsg_get_string(cur));
488 
489         if (tb[ACL_LISTEN])
490                 blobmsg_for_each_attr(cur, tb[ACL_LISTEN], rem)
491                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
492                                 ubusd_acl_add_listen(file, blobmsg_get_string(cur));
493 
494         if (tb[ACL_SEND])
495                 blobmsg_for_each_attr(cur, tb[ACL_SEND], rem)
496                         if (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)
497                                 ubusd_acl_add_send(file, blobmsg_get_string(cur));
498 }
499 
500 static void
501 ubusd_acl_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
502         struct vlist_node *node_old)
503 {
504         struct ubusd_acl_file *file;
505 
506         if (node_old) {
507                 file = container_of(node_old, struct ubusd_acl_file, avl);
508                 ubusd_acl_file_free(file);
509         }
510 
511         if (node_new) {
512                 file = container_of(node_new, struct ubusd_acl_file, avl);
513                 ubusd_acl_file_add(file);
514         }
515 }
516 
517 static struct ubus_msg_buf *
518 ubusd_create_sequence_event_msg(void *priv, const char *id)
519 {
520         void *s;
521 
522         blob_buf_init(&b, 0);
523         blob_put_int32(&b, UBUS_ATTR_OBJID, 0);
524         blob_put_string(&b, UBUS_ATTR_METHOD, id);
525         s = blob_nest_start(&b, UBUS_ATTR_DATA);
526         blobmsg_add_u32(&b, "sequence", ubusd_acl_seq);
527         blob_nest_end(&b, s);
528 
529         return ubus_msg_new(b.head, blob_raw_len(b.head), true);
530 }
531 
532 static VLIST_TREE(ubusd_acl_files, avl_strcmp, ubusd_acl_update_cb, false, false);
533 
534 static int
535 ubusd_acl_load_file(const char *filename)
536 {
537         struct ubusd_acl_file *file;
538         void *blob;
539 
540         blob_buf_init(&bbuf, 0);
541         if (!blobmsg_add_json_from_file(&bbuf, filename)) {
542                 syslog(LOG_ERR, "failed to parse %s\n", filename);
543                 return -1;
544         }
545 
546         file = calloc_a(sizeof(*file), &blob, blob_raw_len(bbuf.head));
547         if (!file)
548                 return -1;
549 
550         file->blob = blob;
551 
552         memcpy(blob, bbuf.head, blob_raw_len(bbuf.head));
553         INIT_LIST_HEAD(&file->acl);
554 
555         vlist_add(&ubusd_acl_files, &file->avl, filename);
556         syslog(LOG_INFO, "loading %s\n", filename);
557 
558         return 0;
559 }
560 
561 void
562 ubusd_acl_load(void)
563 {
564         struct stat st;
565         glob_t gl;
566         size_t j;
567         const char *suffix = "/*.json";
568         char *path = alloca(strlen(ubusd_acl_dir) + strlen(suffix) + 1);
569 
570         sprintf(path, "%s%s", ubusd_acl_dir, suffix);
571         if (glob(path, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl))
572                 return;
573 
574         vlist_update(&ubusd_acl_files);
575         for (j = 0; j < gl.gl_pathc; j++) {
576                 if (stat(gl.gl_pathv[j], &st) || !S_ISREG(st.st_mode))
577                         continue;
578 
579                 if (st.st_uid || st.st_gid) {
580                         syslog(LOG_ERR, "%s has wrong owner\n", gl.gl_pathv[j]);
581                         continue;
582                 }
583                 if (st.st_mode & (S_IWOTH | S_IWGRP | S_IXOTH)) {
584                         syslog(LOG_ERR, "%s has wrong permissions\n", gl.gl_pathv[j]);
585                         continue;
586                 }
587                 ubusd_acl_load_file(gl.gl_pathv[j]);
588         }
589 
590         globfree(&gl);
591         vlist_flush(&ubusd_acl_files);
592         ubusd_acl_seq++;
593         ubusd_send_event(NULL, "ubus.acl.sequence", ubusd_create_sequence_event_msg, NULL);
594 }
595 
596 static void
597 ubusd_reply_add(struct ubus_object *obj)
598 {
599         struct ubusd_acl_obj *acl;
600         int match_len = 0;
601 
602         if (!obj->path.key)
603                 return;
604 
605         /*
606          * Since this tree is sorted alphabetically, we can only expect
607          * to find matching entries as long as the number of matching
608          * characters between the access list string and the object path
609          * is monotonically increasing.
610          */
611         avl_for_each_element(&ubusd_acls, acl, avl) {
612                 const char *key = acl->avl.key;
613                 int cur_match_len;
614                 bool full_match;
615                 void *c;
616 
617                 if (!acl->priv)
618                         continue;
619 
620                 full_match = ubus_strmatch_len(obj->path.key, key, &cur_match_len);
621                 if (cur_match_len < match_len)
622                         break;
623 
624                 match_len = cur_match_len;
625 
626                 if (!full_match) {
627                         if (!acl->partial)
628                                 continue;
629 
630                         if (match_len != (int) strlen(key))
631                                 continue;
632                 }
633 
634                 c = blobmsg_open_table(&b, NULL);
635                 blobmsg_add_string(&b, "obj", obj->path.key);
636                 if (acl->user)
637                         blobmsg_add_string(&b, "user", acl->user);
638                 if (acl->group)
639                         blobmsg_add_string(&b, "group", acl->group);
640 
641                 blobmsg_add_field(&b, blobmsg_type(acl->priv), "acl",
642                         blobmsg_data(acl->priv), blobmsg_data_len(acl->priv));
643 
644                 blobmsg_close_table(&b, c);
645         }
646 }
647 
648 static int ubusd_reply_query(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr, struct blob_attr *msg)
649 {
650         struct ubus_object *obj;
651         void *d, *a;
652 
653         if (!attr[UBUS_ATTR_OBJID])
654                 return UBUS_STATUS_INVALID_ARGUMENT;
655 
656         obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID]));
657         if (!obj)
658                 return UBUS_STATUS_NOT_FOUND;
659 
660         blob_buf_init(&b, 0);
661         blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id);
662         d = blob_nest_start(&b, UBUS_ATTR_DATA);
663 
664         blobmsg_add_u32(&b, "seq", ubusd_acl_seq);
665         a = blobmsg_open_array(&b, "acl");
666         list_for_each_entry(obj, &cl->objects, list)
667                 ubusd_reply_add(obj);
668         blobmsg_close_table(&b, a);
669 
670         blob_nest_end(&b, d);
671 
672         ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA);
673 
674         return 0;
675 }
676 
677 static int ubusd_acl_recv(struct ubus_client *cl, struct ubus_msg_buf *ub, const char *method, struct blob_attr *msg)
678 {
679         if (!strcmp(method, "query"))
680                 return ubusd_reply_query(cl, ub, ubus_parse_msg(ub->data, blob_raw_len(ub->data)), msg);
681 
682         return UBUS_STATUS_INVALID_COMMAND;
683 }
684 
685 void ubusd_acl_init(void)
686 {
687         ubus_init_string_tree(&ubusd_acls, true);
688         acl_obj = ubusd_create_object_internal(NULL, UBUS_SYSTEM_OBJECT_ACL);
689         if (acl_obj)
690                 acl_obj->recv_msg = ubusd_acl_recv;
691 }
692 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt