diff options
Diffstat (limited to 'src/shared/devnode-acl.c')
-rw-r--r-- | src/shared/devnode-acl.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/src/shared/devnode-acl.c b/src/shared/devnode-acl.c new file mode 100644 index 0000000..b239699 --- /dev/null +++ b/src/shared/devnode-acl.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "sd-device.h" + +#include "acl-util.h" +#include "alloc-util.h" +#include "device-util.h" +#include "devnode-acl.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "glyph-util.h" +#include "set.h" +#include "string-util.h" + +static int flush_acl(acl_t acl) { + acl_entry_t i; + int found; + bool changed = false; + + assert(acl); + + for (found = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + found > 0; + found = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + if (acl_delete_entry(acl, i) < 0) + return -errno; + + changed = true; + } + + if (found < 0) + return -errno; + + return changed; +} + +int devnode_acl(const char *path, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + + _cleanup_(acl_freep) acl_t acl = NULL; + int r; + bool changed = false; + + assert(path); + + acl = acl_get_file(path, ACL_TYPE_ACCESS); + if (!acl) + return -errno; + + if (flush) { + + r = flush_acl(acl); + if (r < 0) + return r; + if (r > 0) + changed = true; + + } else if (del && old_uid > 0) { + acl_entry_t entry; + + r = acl_find_uid(acl, old_uid, &entry); + if (r < 0) + return r; + + if (r > 0) { + if (acl_delete_entry(acl, entry) < 0) + return -errno; + + changed = true; + } + } + + if (add && new_uid > 0) { + acl_entry_t entry; + acl_permset_t permset; + int rd, wt; + + r = acl_find_uid(acl, new_uid, &entry); + if (r < 0) + return r; + + if (r == 0) { + if (acl_create_entry(&acl, &entry) < 0) + return -errno; + + if (acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &new_uid) < 0) + return -errno; + } + + if (acl_get_permset(entry, &permset) < 0) + return -errno; + + rd = acl_get_perm(permset, ACL_READ); + if (rd < 0) + return -errno; + + wt = acl_get_perm(permset, ACL_WRITE); + if (wt < 0) + return -errno; + + if (!rd || !wt) { + + if (acl_add_perm(permset, ACL_READ|ACL_WRITE) < 0) + return -errno; + + changed = true; + } + } + + if (!changed) + return 0; + + if (acl_calc_mask(&acl) < 0) + return -errno; + + if (acl_set_file(path, ACL_TYPE_ACCESS, acl) < 0) + return -errno; + + return 0; +} + +int devnode_acl_all(const char *seat, + bool flush, + bool del, uid_t old_uid, + bool add, uid_t new_uid) { + + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_set_free_ Set *nodes = NULL; + _cleanup_closedir_ DIR *dir = NULL; + char *n; + int r; + + r = sd_device_enumerator_new(&e); + if (r < 0) + return r; + + if (isempty(seat)) + seat = "seat0"; + + /* We can only match by one tag in libudev. We choose + * "uaccess" for that. If we could match for two tags here we + * could add the seat name as second match tag, but this would + * be hardly optimizable in libudev, and hence checking the + * second tag manually in our loop is a good solution. */ + r = sd_device_enumerator_add_match_tag(e, "uaccess"); + if (r < 0) + return r; + + FOREACH_DEVICE(e, d) { + const char *node, *sn; + + /* Make sure the tag is still in place */ + if (sd_device_has_current_tag(d, "uaccess") <= 0) + continue; + + if (sd_device_get_property_value(d, "ID_SEAT", &sn) < 0 || isempty(sn)) + sn = "seat0"; + + if (!streq(seat, sn)) + continue; + + /* In case people mistag devices with nodes, we need to ignore this */ + if (sd_device_get_devname(d, &node) < 0) + continue; + + log_device_debug(d, "Found udev node %s for seat %s", node, seat); + r = set_put_strdup_full(&nodes, &path_hash_ops_free, node); + if (r < 0) + return r; + } + + /* udev exports "dead" device nodes to allow module on-demand loading, + * these devices are not known to the kernel at this moment */ + dir = opendir("/run/udev/static_node-tags/uaccess"); + if (dir) { + FOREACH_DIRENT(de, dir, return -errno) { + r = readlinkat_malloc(dirfd(dir), de->d_name, &n); + if (r == -ENOENT) + continue; + if (r < 0) { + log_debug_errno(r, + "Unable to read symlink '/run/udev/static_node-tags/uaccess/%s', ignoring: %m", + de->d_name); + continue; + } + + log_debug("Found static node %s for seat %s", n, seat); + r = set_ensure_consume(&nodes, &path_hash_ops_free, n); + if (r < 0) + return r; + } + } + + r = 0; + SET_FOREACH(n, nodes) { + int k; + + log_debug("Changing ACLs at %s for seat %s (uid "UID_FMT"%s"UID_FMT"%s%s)", + n, seat, old_uid, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), new_uid, + del ? " del" : "", add ? " add" : ""); + + k = devnode_acl(n, flush, del, old_uid, add, new_uid); + if (k == -ENOENT) + log_debug("Device %s disappeared while setting ACLs", n); + else + RET_GATHER(r, k); + } + + return r; +} |