diff options
author | Kay Sievers <kay.sievers@vrfy.org> | 2009-05-17 03:41:47 +0200 |
---|---|---|
committer | Kay Sievers <kay.sievers@vrfy.org> | 2009-05-17 03:41:47 +0200 |
commit | c62bb736ecb6bd301e6963d47d66a4a2424cb18a (patch) | |
tree | fd26c36c5f7e50c3ad817593a3f59e6767fc638b | |
parent | 71babbe65397cac70ccda8cb266f6f67d17b0be6 (diff) |
udev-acl: tool to grant device node access to local users
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | configure.ac | 45 | ||||
-rw-r--r-- | udev-acl/.gitignore | 2 | ||||
-rw-r--r-- | udev-acl/70-acl.rules | 36 | ||||
-rw-r--r-- | udev-acl/Makefile.am | 11 | ||||
-rw-r--r-- | udev-acl/udev-acl.c | 320 |
6 files changed, 378 insertions, 40 deletions
@@ -1,3 +1,7 @@ +udev-extras 20090516 +==================== + + udev-extras 20090226 ==================== New usb-db and pci-db tools to lookup vendor/product strings diff --git a/configure.ac b/configure.ac index 3244f94..5955656 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_INIT([udev-extras], - [20090509], + [20090516], [linux-hotplug@vger.kernel.org]) AC_PREREQ(2.60) AM_INIT_AUTOMAKE([check-news foreign 1.9 subdir-objects dist-bzip2]) @@ -11,31 +11,40 @@ dnl prefix is /usr, exec_prefix in /, if overridden exec_prefix follows prefix AC_PREFIX_DEFAULT([/usr]) test "$prefix" = NONE && test "$exec_prefix" = NONE && exec_prefix= -PKG_CHECK_MODULES(LIBUSB, libusb >= 0.1.12) -AC_SUBST(LIBUSB_CFLAGS) -AC_SUBST(LIBUSB_LIBS) +AC_ARG_WITH(udev-prefix, + AS_HELP_STRING([--with-udev-prefix=DIR], [add prefix to internal udev path names]), + [], [with_udev_prefix='${exec_prefix}']) +udev_prefix=$with_udev_prefix +AC_SUBST(udev_prefix) +AC_PROG_AWK +AC_PATH_PROG([GPERF], [gperf]) +if test -z "$GPERF"; then + AC_MSG_ERROR(Could not find gperf) +fi +AC_PATH_PROG([XSLTPROC], [xsltproc]) +if test -z "$XSLTPROC"; then + AC_MSG_ERROR(Could not find xsltproc) +fi + +AC_DEFINE(LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE, 1, [I know the API is subject to change]) PKG_CHECK_MODULES(LIBUDEV, libudev >= 141) AC_SUBST(LIBUDEV_CFLAGS) AC_SUBST(LIBUDEV_LIBS) -PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82) -AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)]) +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.7.0) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) -AC_PATH_PROG([XSLTPROC], [xsltproc]) -AC_PROG_AWK -AC_PATH_PROG([GPERF], [gperf]) -if test -z "$GPERF"; then - AC_MSG_ERROR(Could not find gperf) -fi +PKG_CHECK_MODULES(LIBUSB, libusb >= 0.1.12) +AC_SUBST(LIBUSB_CFLAGS) +AC_SUBST(LIBUSB_LIBS) -AC_ARG_WITH(udev-prefix, - AS_HELP_STRING([--with-udev-prefix=DIR], [add prefix to internal udev path names]), - [], [with_udev_prefix='${exec_prefix}']) -udev_prefix=$with_udev_prefix -AC_SUBST(udev_prefix) +AC_CHECK_LIB([acl], [acl_init], [:], AC_MSG_ERROR([libacl not found])) +AC_CHECK_HEADER([acl/libacl.h], [:], AC_MSG_ERROR([libacl header not found])) -AC_DEFINE(LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE, 1, [I know the API is subject to change]) +PKG_CHECK_MODULES(USBUTILS, usbutils >= 0.82) +AC_SUBST([USB_DATABASE], [$($PKG_CONFIG --variable=usbids usbutils)]) AC_CHECK_FILES([/usr/share/pci.ids], [pciids=/usr/share/pci.ids]) AC_CHECK_FILES([/usr/share/hwdata/pci.ids], [pciids=/usr/share/hwdata/pci.ids]) diff --git a/udev-acl/.gitignore b/udev-acl/.gitignore index 8d92eea..08891fe 100644 --- a/udev-acl/.gitignore +++ b/udev-acl/.gitignore @@ -1 +1 @@ -usbdev_id +udev-acl diff --git a/udev-acl/70-acl.rules b/udev-acl/70-acl.rules index 6771b0e..708198a 100644 --- a/udev-acl/70-acl.rules +++ b/udev-acl/70-acl.rules @@ -1,40 +1,34 @@ # do not edit this file, it will be overwritten on update -# support for the following ACL "tags" get merged -# ACL_CDROM -# ACL_SCANNER -# ACL_AUDIO -# ACL_VIDEO -# ACL_MEDIA -# ACL_AUTH - -ACTION!="add|change", GOTO="acl_end" - -# will be removed when 2.6.29 is out -SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="", ENV{DEVTYPE}=="usb_device", WAIT_FOR_SYSFS="descriptors" +ENV{MAJOR}=="", GOTO="acl_end" +ACTION!="add|change", GOTO="acl_apply" # PTP/MTP protocol devices, cameras, portable media players SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="", ENV{DEVTYPE}=="usb_device", IMPORT{program}="usb_id --export %p" -SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:060101:*", ENV{ACL_MEDIA}="1" +SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:060101:*", ENV{ACL_SET}="1" # SCSI scanners -KERNEL=="sg[0-9]*", ATTRS{type}=="6", ENV{ACL_SCANNER}="1" -KERNEL=="sg[0-9]*", ATTRS{type}=="3", ATTRS{vendor}=="HP|EPSON|Epson", ENV{ACL_SCANNER}="1" +KERNEL=="sg[0-9]*", ATTRS{type}=="6", ENV{ACL_SET}="1" +KERNEL=="sg[0-9]*", ATTRS{type}=="3", ATTRS{vendor}=="HP|EPSON|Epson", ENV{ACL_SET}="1" # USB scanners -ENV{libsane_matched}=="yes", ENV{ACL_SCANNER}="1" +ENV{libsane_matched}=="yes", ENV{ACL_SET}="1" # optical drives -SUBSYSTEM=="block", ENV{ID_CDROM}=="1", ENV{ACL_CDROM}="1" +SUBSYSTEM=="block", ENV{ID_CDROM}=="1", ENV{ACL_SET}="1" # sound devices -SUBSYSTEM=="sound", ENV{ACL_AUDIO}="1" +SUBSYSTEM=="sound", ENV{ACL_SET}="1" # webcams, frame grabber, TV cards -SUBSYSTEM=="video4linux", ENV{ACL_VIDEO}="1" -SUBSYSTEM=="dvb", ENV{ACL_VIDEO}="1" +SUBSYSTEM=="video4linux", ENV{ACL_SET}="1" +SUBSYSTEM=="dvb", ENV{ACL_SET}="1" # fingerprint readers -SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="2016", ENV{ACL_AUTH}="1" +SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="2016", ENV{ACL_SET}="1" + +# apply ACL for all locally logged in users +LABEL="acl_apply", ENV{ACL_SET}=="?*", TEST=="/var/run/ConsoleKit/database", \ + RUN+="udev-acl --action=$env{ACTION} --device=$env{DEVNAME}" LABEL="acl_end" diff --git a/udev-acl/Makefile.am b/udev-acl/Makefile.am index e4dabcb..45168b7 100644 --- a/udev-acl/Makefile.am +++ b/udev-acl/Makefile.am @@ -1,4 +1,15 @@ include $(top_srcdir)/Makefile.am.inc +udevhomedir = $(udev_prefix)/lib/udev +udevhome_PROGRAMS = udev-acl + +udev_acl_SOURCES = udev-acl.c +udev_acl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS) +udev_acl_LDADD = -lacl $(LIBUDEV_LIBS) $(GLIB_LIBS) + udevrulesdir = $(udev_prefix)/lib/udev/rules.d dist_udevrules_DATA = 70-acl.rules + +install-exec-hook: + mkdir -p $(DESTDIR)$(prefix)/lib/ConsoleKit/run-session.d + ln -sf $(udevhomedir)/udev-acl $(DESTDIR)$(prefix)/lib/ConsoleKit/run-session.d/udev-acl.ck diff --git a/udev-acl/udev-acl.c b/udev-acl/udev-acl.c new file mode 100644 index 0000000..443ba45 --- /dev/null +++ b/udev-acl/udev-acl.c @@ -0,0 +1,320 @@ +/* + * 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 sessions */ +static GSList *uids_with_local_session(const char *own_id) +{ + GSList *list = NULL; + GKeyFile *keyfile; + int ret = 0; + + 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) { + /* exclude our own session */ + if (g_str_has_suffix(groups[i], own_id)) + continue; + } + if (!g_key_file_get_boolean(keyfile, groups[i], "is_local", 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) +{ + const char *id; + const char *local; + const char *session; + + id = getenv("CK_SESSION_USER_UID"); + if (id == NULL) + return -1; + + local = getenv("CK_SESSION_IS_LOCAL"); + if (local == NULL) + return -1; + + session = getenv("CK_SESSION_ID"); + if (session == NULL) + return -1; + + if (strcmp(local, "true") != 0) + return -1; + + if (strcmp(action, "session_added") == 0) + *add = 1; + else if (strcmp(action, "session_removed") == 0) + *add = 0; + else + return -1; + + *own_session = session; + *uid = strtoul(id, NULL, 10); + 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_SET", "1"); + 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' }, + {} + }; + const char* file; + 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 to all matching devices, if last session goes away */ + GSList *list; + + list = uids_with_local_session(own_session); + if (!uid_in_list(list, uid)) + apply_acl_to_devices(uid, 0); + g_slist_free(list); + } + } else if (device != NULL) { + /* update list of ACLs of all current session uids to a given device */ + GSList *list; + GSList *l; + + list = uids_with_local_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; +} |