diff options
Diffstat (limited to 'extras/udev-acl/udev-acl.c')
-rw-r--r-- | extras/udev-acl/udev-acl.c | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/extras/udev-acl/udev-acl.c b/extras/udev-acl/udev-acl.c new file mode 100644 index 0000000000..ec95025eb3 --- /dev/null +++ b/extras/udev-acl/udev-acl.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details: + */ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <inttypes.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <sys/stat.h> +#include <glib.h> +#include <acl/libacl.h> +#include <libudev.h> + +static int debug; + +static int set_facl(const char* filename, uid_t uid, int add) +{ + int get; + acl_t acl; + acl_entry_t entry = NULL; + acl_entry_t e; + acl_permset_t permset; + int ret; + + /* read current record */ + acl = acl_get_file(filename, ACL_TYPE_ACCESS); + if (!acl) + return -1; + + /* locate ACL_USER entry for uid */ + get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e); + while (get == 1) { + acl_tag_t t; + + acl_get_tag_type(e, &t); + if (t == ACL_USER) { + uid_t *u; + + u = (uid_t*)acl_get_qualifier(e); + if (u == NULL) { + ret = -1; + goto out; + } + if (*u == uid) { + entry = e; + acl_free(u); + break; + } + acl_free(u); + } + + get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e); + } + + /* remove ACL_USER entry for uid */ + if (!add) { + if (entry == NULL) { + ret = 0; + goto out; + } + acl_delete_entry(acl, entry); + goto update; + } + + /* create ACL_USER entry for uid */ + if (entry == NULL) { + ret = acl_create_entry(&acl, &entry); + if (ret != 0) + goto out; + acl_set_tag_type(entry, ACL_USER); + acl_set_qualifier(entry, &uid); + } + + /* add permissions for uid */ + acl_get_permset(entry, &permset); + acl_add_perm(permset, ACL_READ|ACL_WRITE); +update: + /* update record */ + if (debug) + printf("%c%u %s\n", add ? '+' : '-', uid, filename); + acl_calc_mask(&acl); + ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl); + if (ret != 0) + goto out; +out: + acl_free(acl); + return ret; +} + +/* check if a given uid is listed */ +static int uid_in_list(GSList *list, uid_t uid) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) + if (uid == GPOINTER_TO_UINT(l->data)) + return 1; + return 0; +} + +/* return list of current uids of local active sessions */ +static GSList *uids_with_local_active_session(const char *own_id) +{ + GSList *list = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + if (g_key_file_load_from_file(keyfile, "/var/run/ConsoleKit/database", 0, NULL)) { + gchar **groups; + + groups = g_key_file_get_groups(keyfile, NULL); + if (groups != NULL) { + int i; + + for (i = 0; groups[i] != NULL; i++) { + uid_t u; + + if (!g_str_has_prefix(groups[i], "Session ")) + continue; + if (own_id != NULL &&g_str_has_suffix(groups[i], own_id)) + continue; + if (!g_key_file_get_boolean(keyfile, groups[i], "is_local", NULL)) + continue; + if (!g_key_file_get_boolean(keyfile, groups[i], "is_active", NULL)) + continue; + u = g_key_file_get_integer(keyfile, groups[i], "uid", NULL); + if (u > 0 && !uid_in_list(list, u)) + list = g_slist_prepend(list, GUINT_TO_POINTER(u)); + } + g_strfreev(groups); + } + } + g_key_file_free(keyfile); + + return list; +} + +/* ConsoleKit calls us with special variables */ +static int consolekit_called(const char *action, uid_t *uid, const char **own_session, int *add) +{ + int a; + uid_t u; + const char *s; + const char *session; + + if (strcmp(action, "session_active_changed") != 0) + return -1; + + s = getenv("CK_SESSION_IS_LOCAL"); + if (s == NULL) + return -1; + if (strcmp(s, "true") != 0) + return 0; + + s = getenv("CK_SESSION_IS_ACTIVE"); + if (s == NULL) + return -1; + if (strcmp(s, "true") == 0) + a = 1; + else + a = 0; + + session = getenv("CK_SESSION_ID"); + if (session == NULL) + return -1; + + s = getenv("CK_SESSION_USER_UID"); + if (s == NULL) + return -1; + u = strtoul(s, NULL, 10); + if (u == 0) + return 0; + + *own_session = session; + *uid = u; + *add = a; + return 0; +} + +/* add or remove a ACL for a given uid from all matching devices */ +static void apply_acl_to_devices(uid_t uid, int add) +{ + struct udev *udev; + struct udev_enumerate *enumerate; + struct udev_list_entry *list_entry; + + /* iterate over all devices tagged with ACL_SET */ + udev = udev_new(); + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_property(enumerate, "ACL_MANAGE", "*"); + udev_enumerate_scan_devices(enumerate); + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(enumerate)) { + struct udev_device *device; + const char *node; + + device = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate), + udev_list_entry_get_name(list_entry)); + if (device == NULL) + continue; + node = udev_device_get_devnode(device); + if (node == NULL) + continue; + set_facl(node, uid, add); + udev_device_unref(device); + } + udev_enumerate_unref(enumerate); + udev_unref(udev); +} + +int main (int argc, char* argv[]) +{ + static const struct option options[] = { + { "action", required_argument, NULL, 'a' }, + { "device", required_argument, NULL, 'D' }, + { "user", required_argument, NULL, 'u' }, + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + int add = -1; + const char *device = NULL; + uid_t uid = 0; + const char* own_session = NULL; + int rc = 0; + + /* valgrind is more important to us than a slice allocator */ + g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, 1); + + while (1) { + int option; + + option = getopt_long(argc, argv, "+a:D:u:dh", options, NULL); + if (option == -1) + break; + + switch (option) { + case 'a': + if (strcmp(optarg, "add") == 0 || strcmp(optarg, "change") == 0) + add = 1; + else if (strcmp(optarg, "remove") == 0) + add = 0; + else + goto out; + break; + case 'D': + device = optarg; + break; + case 'u': + uid = strtoul(optarg, NULL, 10); + break; + case 'd': + debug = 1; + break; + case 'h': + printf("Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID]\n\n"); + default: + goto out; + } + } + + if (add < 0 && device == NULL && uid == 0) + consolekit_called(argv[optind], &uid, &own_session, &add); + + if (add < 0) { + fprintf(stderr, "missing action\n\n"); + rc = 2; + goto out; + } + + if (device != NULL && uid != 0) { + fprintf(stderr, "only one option, --device=DEVICEFILE or --user=UID expected\n\n"); + rc = 3; + goto out; + } + + if (uid != 0) { + if (add) { + /* Add ACL for given uid to all matching devices. */ + apply_acl_to_devices(uid, 1); + } else { + /* + * Remove ACL for given uid from all matching devices + * when there is currently no local active session. + */ + GSList *list; + + list = uids_with_local_active_session(own_session); + if (!uid_in_list(list, uid)) + apply_acl_to_devices(uid, 0); + g_slist_free(list); + } + } else if (device != NULL) { + /* + * Add ACLs for all current session uids to a given device. + * + * Or remove ACLs for uids which do not have any current local + * active session. Remove is not really interesting, because in + * most cases the device node is removed anyway. + */ + GSList *list; + GSList *l; + + list = uids_with_local_active_session(NULL); + for (l = list; l != NULL; l = g_slist_next(l)) { + uid_t u; + + u = GPOINTER_TO_UINT(l->data); + if (add || !uid_in_list(list, u)) + set_facl(device, u, add); + } + g_slist_free(list); + } else { + fprintf(stderr, "--device=DEVICEFILE or --user=UID expected\n\n"); + rc = 3; + } +out: + return rc; +} |