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