diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rwxr-xr-x | autogen.sh | 2 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | extras/Makefile.am | 5 | ||||
-rw-r--r-- | extras/udev-acl/.gitignore | 1 | ||||
-rw-r--r-- | extras/udev-acl/70-acl.rules | 45 | ||||
-rw-r--r-- | extras/udev-acl/Makefile.am | 15 | ||||
-rw-r--r-- | extras/udev-acl/udev-acl.c | 334 |
8 files changed, 417 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index fd7538658b..fbaf2ae58e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,9 @@ CLEANFILES = \ ACLOCAL_AMFLAGS = -I m4 -DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc +DISTCHECK_CONFIGURE_FLAGS = \ + --enable-gtk-doc \ + --enable-extras clean-local: rm -rf udev-test-install diff --git a/autogen.sh b/autogen.sh index 83af29cd21..e41bae05cf 100755 --- a/autogen.sh +++ b/autogen.sh @@ -13,7 +13,7 @@ CFLAGS="-g -Wall \ libdirname=$(basename $(cd /lib/$(gcc -print-multi-os-directory); pwd)) args="--prefix=/usr --exec-prefix= --sysconfdir=/etc \ --libdir=/usr/$libdirname --with-libdir-name=$libdirname \ ---with-selinux --enable-gtk-doc +--with-selinux --enable-gtk-doc --enable-extras" export CFLAGS="$CFLAGS -O2" ./configure $args $@ diff --git a/configure.ac b/configure.ac index 0df0f60f12..9571c902f8 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,16 @@ if test "x$enable_logging" = "xyes"; then AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) fi +AC_ARG_ENABLE([extras], + AS_HELP_STRING([--enable-extras], [enable extras with external dependencies]), + [], [enable_extras=no]) +if test "x$enable_extras" = xyes; then + PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.7.0 gobject-2.0 >= 2.7.0]) + AC_SUBST([GLIB_CFLAGS]) + AC_SUBST([GLIB_LIBS]) +fi +AM_CONDITIONAL([ENABLE_EXTRAS], [test "x$enable_extras" = xyes]) + AC_CONFIG_HEADERS(config.h) AC_CONFIG_FILES([ Makefile @@ -79,6 +89,7 @@ AC_CONFIG_FILES([ extras/rule_generator/Makefile extras/scsi_id/Makefile extras/usb_id/Makefile + extras/udev-acl/Makefile ]) AC_OUTPUT @@ -102,5 +113,7 @@ AC_MSG_RESULT([ cflags: ${CFLAGS} ldflags: ${LDFLAGS} + extras: ${enable_extras} + xsltproc: ${XSLTPROC} ]) diff --git a/extras/Makefile.am b/extras/Makefile.am index 49a23d9777..4c9b20442b 100644 --- a/extras/Makefile.am +++ b/extras/Makefile.am @@ -12,3 +12,8 @@ SUBDIRS = \ rule_generator \ scsi_id \ usb_id + +if ENABLE_EXTRAS +SUBDIRS += \ + udev-acl +endif diff --git a/extras/udev-acl/.gitignore b/extras/udev-acl/.gitignore new file mode 100644 index 0000000000..08891fed02 --- /dev/null +++ b/extras/udev-acl/.gitignore @@ -0,0 +1 @@ +udev-acl diff --git a/extras/udev-acl/70-acl.rules b/extras/udev-acl/70-acl.rules new file mode 100644 index 0000000000..fc27039597 --- /dev/null +++ b/extras/udev-acl/70-acl.rules @@ -0,0 +1,45 @@ +# do not edit this file, it will be overwritten on update + +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_MANAGE}="1" + +# digicams with proprietary protocol +ENV{ID_GPHOTO2}=="*?", ENV{ACL_MANAGE}="1" + +# SCSI scanners +KERNEL=="sg[0-9]*", ATTRS{type}=="6", ENV{ACL_MANAGE}="1" +KERNEL=="sg[0-9]*", ATTRS{type}=="3", ATTRS{vendor}=="HP|EPSON|Epson", ENV{ACL_MANAGE}="1" + +# USB scanners +ENV{libsane_matched}=="yes", ENV{ACL_MANAGE}="1" + +# optical drives +SUBSYSTEM=="block", ENV{ID_CDROM}=="1", ENV{ACL_MANAGE}="1" + +# sound devices +SUBSYSTEM=="sound", ENV{ACL_MANAGE}="1" +# sound jack-sense +SUBSYSTEM=="input", SUBSYSTEMS=="sound", ENV{ACL_MANAGE}="1" + +# webcams, frame grabber, TV cards +SUBSYSTEM=="video4linux", ENV{ACL_MANAGE}="1" +SUBSYSTEM=="dvb", ENV{ACL_MANAGE}="1" + +# fingerprint readers +SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="2016", ENV{ACL_MANAGE}="1" + +# DRI video devices +SUBSYSTEM=="drm", KERNEL=="card*", ENV{ACL_MANAGE}="1" + +# KVM +SUBSYSTEM=="misc", KERNEL=="kvm", ENV{ACL_MANAGE}="1" + +# apply ACL for all locally logged in users +LABEL="acl_apply", ENV{ACL_MANAGE}=="?*", TEST=="/var/run/ConsoleKit/database", \ + RUN+="udev-acl --action=$env{ACTION} --device=$env{DEVNAME}" + +LABEL="acl_end" diff --git a/extras/udev-acl/Makefile.am b/extras/udev-acl/Makefile.am new file mode 100644 index 0000000000..f1a5fbd6c1 --- /dev/null +++ b/extras/udev-acl/Makefile.am @@ -0,0 +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) $(GLIB_CFLAGS) +udev_acl_LDADD = -lacl $(top_builddir)/libudev/libudev.la $(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/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; +} |