2 * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
3 * Copyright (C) 2018 Hans Dedecker <dedeckeh@gmail.com>
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
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.
16 #include <sys/socket.h>
17 #include <sys/types.h>
26 #include <libubox/vlist.h>
27 #include <libubox/blobmsg_json.h>
28 #include <libubox/avl-cmp.h>
29 #include <libubox/ulog.h>
41 struct ubusd_acl_obj
{
43 struct list_head list
;
50 struct blob_attr
*methods
;
51 struct blob_attr
*tags
;
52 struct blob_attr
*priv
;
59 struct ubusd_acl_file
{
60 struct vlist_node avl
;
65 struct blob_attr
*blob
;
71 const char *ubusd_acl_dir
= "/usr/share/acl.d";
72 static struct blob_buf bbuf
;
73 static struct avl_tree ubusd_acls
;
74 static int ubusd_acl_seq
;
75 static struct ubus_object
*acl_obj
;
78 ubusd_acl_match_cred(struct ubus_client
*cl
, struct ubusd_acl_obj
*obj
)
80 if (obj
->user
&& !strcmp(cl
->user
, obj
->user
))
83 if (obj
->group
&& !strcmp(cl
->group
, obj
->group
))
90 ubusd_acl_check(struct ubus_client
*cl
, const char *obj
,
91 const char *method
, enum ubusd_acl_type type
)
93 struct ubusd_acl_obj
*acl
;
96 if (!cl
|| !cl
->uid
|| !obj
)
100 * Since this tree is sorted alphabetically, we can only expect
101 * to find matching entries as long as the number of matching
102 * characters between the access list string and the object path
103 * is monotonically increasing.
105 avl_for_each_element(&ubusd_acls
, acl
, avl
) {
106 const char *key
= acl
->avl
.key
;
110 full_match
= ubus_strmatch_len(obj
, key
, &cur_match_len
);
111 if (cur_match_len
< match_len
)
114 match_len
= cur_match_len
;
120 if (match_len
!= (int) strlen(key
))
124 if (ubusd_acl_match_cred(cl
, acl
))
128 case UBUS_ACL_PUBLISH
:
133 case UBUS_ACL_SUBSCRIBE
:
138 case UBUS_ACL_LISTEN
:
148 case UBUS_ACL_ACCESS
:
150 struct blob_attr
*cur
;
154 blobmsg_for_each_attr(cur
, acl
->methods
, rem
)
155 if (blobmsg_type(cur
) == BLOBMSG_TYPE_STRING
) {
156 cur_method
= blobmsg_get_string(cur
);
158 if (!strcmp(method
, cur_method
) || !strcmp("*", cur_method
))
170 ubusd_acl_init_client(struct ubus_client
*cl
, int fd
)
177 unsigned int len
= sizeof(struct ucred
);
179 if (getsockopt(fd
, SOL_SOCKET
, SO_PEERCRED
, &cred
, &len
) == -1) {
180 ULOG_ERR("Failed getsockopt(): %m\n");
184 memset(&cred
, 0, sizeof(cred
));
187 pwd
= getpwuid(cred
.uid
);
189 ULOG_ERR("Failed getpwuid(): %m\n");
193 group
= getgrgid(cred
.gid
);
195 ULOG_ERR("Failed getgrgid(): %m\n");
202 cl
->group
= strdup(group
->gr_name
);
203 cl
->user
= strdup(pwd
->pw_name
);
209 ubusd_acl_free_client(struct ubus_client
*cl
)
216 ubusd_acl_file_free(struct ubusd_acl_file
*file
)
218 struct ubusd_acl_obj
*p
, *q
;
220 list_for_each_entry_safe(p
, q
, &file
->acl
, list
) {
221 avl_delete(&ubusd_acls
, &p
->avl
);
236 static const struct blobmsg_policy acl_obj_policy
[__ACL_ACCESS_MAX
] = {
237 [ACL_ACCESS_METHODS
] = { .name
= "methods", .type
= BLOBMSG_TYPE_ARRAY
},
238 [ACL_ACCESS_TAGS
] = { .name
= "tags", .type
= BLOBMSG_TYPE_ARRAY
},
239 [ACL_ACCESS_PRIV
] = { .name
= "acl", .type
= BLOBMSG_TYPE_TABLE
},
242 static struct ubusd_acl_obj
*
243 ubusd_acl_alloc_obj(struct ubusd_acl_file
*file
, const char *obj
)
245 struct ubusd_acl_obj
*o
;
246 int len
= strlen(obj
);
248 bool partial
= false;
250 if (obj
[len
- 1] == '*') {
255 o
= calloc_a(sizeof(*o
), &k
, len
+ 1);
256 o
->partial
= partial
;
257 o
->user
= file
->user
;
258 o
->group
= file
->group
;
259 o
->avl
.key
= memcpy(k
, obj
, len
);
261 list_add(&o
->list
, &file
->acl
);
262 avl_insert(&ubusd_acls
, &o
->avl
);
268 ubusd_acl_add_access(struct ubusd_acl_file
*file
, struct blob_attr
*obj
)
270 struct blob_attr
*tb
[__ACL_ACCESS_MAX
];
271 struct ubusd_acl_obj
*o
;
273 blobmsg_parse(acl_obj_policy
, __ACL_ACCESS_MAX
, tb
, blobmsg_data(obj
),
274 blobmsg_data_len(obj
));
276 if (!tb
[ACL_ACCESS_METHODS
] && !tb
[ACL_ACCESS_TAGS
] && !tb
[ACL_ACCESS_PRIV
])
279 o
= ubusd_acl_alloc_obj(file
, blobmsg_name(obj
));
281 o
->methods
= tb
[ACL_ACCESS_METHODS
];
282 o
->tags
= tb
[ACL_ACCESS_TAGS
];
283 o
->priv
= tb
[ACL_ACCESS_PRIV
];
285 if (file
->user
|| file
->group
)
290 ubusd_acl_add_subscribe(struct ubusd_acl_file
*file
, const char *obj
)
292 struct ubusd_acl_obj
*o
= ubusd_acl_alloc_obj(file
, obj
);
298 ubusd_acl_add_publish(struct ubusd_acl_file
*file
, const char *obj
)
300 struct ubusd_acl_obj
*o
= ubusd_acl_alloc_obj(file
, obj
);
305 static void ubusd_acl_add_listen(struct ubusd_acl_file
*file
, const char *obj
)
307 struct ubusd_acl_obj
*o
= ubusd_acl_alloc_obj(file
, obj
);
312 static void ubusd_acl_add_send(struct ubusd_acl_file
*file
, const char *obj
)
314 struct ubusd_acl_obj
*o
= ubusd_acl_alloc_obj(file
, obj
);
331 static const struct blobmsg_policy acl_policy
[__ACL_MAX
] = {
332 [ACL_USER
] = { .name
= "user", .type
= BLOBMSG_TYPE_STRING
},
333 [ACL_GROUP
] = { .name
= "group", .type
= BLOBMSG_TYPE_STRING
},
334 [ACL_ACCESS
] = { .name
= "access", .type
= BLOBMSG_TYPE_TABLE
},
335 [ACL_PUBLISH
] = { .name
= "publish", .type
= BLOBMSG_TYPE_ARRAY
},
336 [ACL_SUBSCRIBE
] = { .name
= "subscribe", .type
= BLOBMSG_TYPE_ARRAY
},
337 [ACL_INHERIT
] = { .name
= "inherit", .type
= BLOBMSG_TYPE_ARRAY
},
338 [ACL_LISTEN
] = { .name
= "listen", .type
= BLOBMSG_TYPE_ARRAY
},
339 [ACL_SEND
] = { .name
= "send", .type
= BLOBMSG_TYPE_ARRAY
},
343 ubusd_acl_file_add(struct ubusd_acl_file
*file
)
345 struct blob_attr
*tb
[__ACL_MAX
], *cur
;
348 blobmsg_parse(acl_policy
, __ACL_MAX
, tb
, blob_data(file
->blob
),
349 blob_len(file
->blob
));
352 file
->user
= blobmsg_get_string(tb
[ACL_USER
]);
353 else if (tb
[ACL_GROUP
])
354 file
->group
= blobmsg_get_string(tb
[ACL_GROUP
]);
359 blobmsg_for_each_attr(cur
, tb
[ACL_ACCESS
], rem
)
360 ubusd_acl_add_access(file
, cur
);
362 if (tb
[ACL_SUBSCRIBE
])
363 blobmsg_for_each_attr(cur
, tb
[ACL_SUBSCRIBE
], rem
)
364 if (blobmsg_type(cur
) == BLOBMSG_TYPE_STRING
)
365 ubusd_acl_add_subscribe(file
, blobmsg_get_string(cur
));
368 blobmsg_for_each_attr(cur
, tb
[ACL_PUBLISH
], rem
)
369 if (blobmsg_type(cur
) == BLOBMSG_TYPE_STRING
)
370 ubusd_acl_add_publish(file
, blobmsg_get_string(cur
));
373 blobmsg_for_each_attr(cur
, tb
[ACL_LISTEN
], rem
)
374 if (blobmsg_type(cur
) == BLOBMSG_TYPE_STRING
)
375 ubusd_acl_add_listen(file
, blobmsg_get_string(cur
));
378 blobmsg_for_each_attr(cur
, tb
[ACL_SEND
], rem
)
379 if (blobmsg_type(cur
) == BLOBMSG_TYPE_STRING
)
380 ubusd_acl_add_send(file
, blobmsg_get_string(cur
));
384 ubusd_acl_update_cb(struct vlist_tree
*tree
, struct vlist_node
*node_new
,
385 struct vlist_node
*node_old
)
387 struct ubusd_acl_file
*file
;
390 file
= container_of(node_old
, struct ubusd_acl_file
, avl
);
391 ubusd_acl_file_free(file
);
395 file
= container_of(node_new
, struct ubusd_acl_file
, avl
);
396 ubusd_acl_file_add(file
);
400 static struct ubus_msg_buf
*
401 ubusd_create_sequence_event_msg(void *priv
, const char *id
)
405 blob_buf_init(&b
, 0);
406 blob_put_int32(&b
, UBUS_ATTR_OBJID
, 0);
407 blob_put_string(&b
, UBUS_ATTR_METHOD
, id
);
408 s
= blob_nest_start(&b
, UBUS_ATTR_DATA
);
409 blobmsg_add_u32(&b
, "sequence", ubusd_acl_seq
);
410 blob_nest_end(&b
, s
);
412 return ubus_msg_new(b
.head
, blob_raw_len(b
.head
), true);
415 static VLIST_TREE(ubusd_acl_files
, avl_strcmp
, ubusd_acl_update_cb
, false, false);
418 ubusd_acl_load_file(const char *filename
)
420 struct ubusd_acl_file
*file
;
423 blob_buf_init(&bbuf
, 0);
424 if (!blobmsg_add_json_from_file(&bbuf
, filename
)) {
425 syslog(LOG_ERR
, "failed to parse %s\n", filename
);
429 file
= calloc_a(sizeof(*file
), &blob
, blob_raw_len(bbuf
.head
));
435 memcpy(blob
, bbuf
.head
, blob_raw_len(bbuf
.head
));
436 INIT_LIST_HEAD(&file
->acl
);
438 vlist_add(&ubusd_acl_files
, &file
->avl
, filename
);
439 syslog(LOG_INFO
, "loading %s\n", filename
);
450 const char *suffix
= "/*.json";
451 char *path
= alloca(strlen(ubusd_acl_dir
) + strlen(suffix
) + 1);
453 sprintf(path
, "%s%s", ubusd_acl_dir
, suffix
);
454 if (glob(path
, GLOB_NOESCAPE
| GLOB_MARK
, NULL
, &gl
))
457 vlist_update(&ubusd_acl_files
);
458 for (j
= 0; j
< gl
.gl_pathc
; j
++) {
459 if (stat(gl
.gl_pathv
[j
], &st
) || !S_ISREG(st
.st_mode
))
462 if (st
.st_uid
|| st
.st_gid
) {
463 syslog(LOG_ERR
, "%s has wrong owner\n", gl
.gl_pathv
[j
]);
466 if (st
.st_mode
& (S_IWOTH
| S_IWGRP
| S_IXOTH
)) {
467 syslog(LOG_ERR
, "%s has wrong permissions\n", gl
.gl_pathv
[j
]);
470 ubusd_acl_load_file(gl
.gl_pathv
[j
]);
474 vlist_flush(&ubusd_acl_files
);
476 ubusd_send_event(NULL
, "ubus.acl.sequence", ubusd_create_sequence_event_msg
, NULL
);
480 ubusd_reply_add(struct ubus_object
*obj
)
482 struct ubusd_acl_obj
*acl
;
489 * Since this tree is sorted alphabetically, we can only expect
490 * to find matching entries as long as the number of matching
491 * characters between the access list string and the object path
492 * is monotonically increasing.
494 avl_for_each_element(&ubusd_acls
, acl
, avl
) {
495 const char *key
= acl
->avl
.key
;
503 full_match
= ubus_strmatch_len(obj
->path
.key
, key
, &cur_match_len
);
504 if (cur_match_len
< match_len
)
507 match_len
= cur_match_len
;
513 if (match_len
!= (int) strlen(key
))
517 c
= blobmsg_open_table(&b
, NULL
);
518 blobmsg_add_string(&b
, "obj", obj
->path
.key
);
520 blobmsg_add_string(&b
, "user", acl
->user
);
522 blobmsg_add_string(&b
, "group", acl
->group
);
524 blobmsg_add_field(&b
, blobmsg_type(acl
->priv
), "acl",
525 blobmsg_data(acl
->priv
), blobmsg_data_len(acl
->priv
));
527 blobmsg_close_table(&b
, c
);
531 static int ubusd_reply_query(struct ubus_client
*cl
, struct ubus_msg_buf
*ub
, struct blob_attr
**attr
, struct blob_attr
*msg
)
533 struct ubus_object
*obj
;
536 if (!attr
[UBUS_ATTR_OBJID
])
537 return UBUS_STATUS_INVALID_ARGUMENT
;
539 obj
= ubusd_find_object(blob_get_u32(attr
[UBUS_ATTR_OBJID
]));
541 return UBUS_STATUS_NOT_FOUND
;
543 blob_buf_init(&b
, 0);
544 blob_put_int32(&b
, UBUS_ATTR_OBJID
, obj
->id
.id
);
545 d
= blob_nest_start(&b
, UBUS_ATTR_DATA
);
547 blobmsg_add_u32(&b
, "seq", ubusd_acl_seq
);
548 a
= blobmsg_open_array(&b
, "acl");
549 list_for_each_entry(obj
, &cl
->objects
, list
)
550 ubusd_reply_add(obj
);
551 blobmsg_close_table(&b
, a
);
553 blob_nest_end(&b
, d
);
555 ubus_proto_send_msg_from_blob(cl
, ub
, UBUS_MSG_DATA
);
560 static int ubusd_acl_recv(struct ubus_client
*cl
, struct ubus_msg_buf
*ub
, const char *method
, struct blob_attr
*msg
)
562 if (!strcmp(method
, "query"))
563 return ubusd_reply_query(cl
, ub
, ubus_parse_msg(ub
->data
, blob_raw_len(ub
->data
)), msg
);
565 return UBUS_STATUS_INVALID_COMMAND
;
568 void ubusd_acl_init(void)
570 ubus_init_string_tree(&ubusd_acls
, true);
571 acl_obj
= ubusd_create_object_internal(NULL
, UBUS_SYSTEM_OBJECT_ACL
);
572 acl_obj
->recv_msg
= ubusd_acl_recv
;