summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKay Sievers <kay.sievers@vrfy.org>2009-05-17 03:41:47 +0200
committerKay Sievers <kay.sievers@vrfy.org>2009-05-17 03:41:47 +0200
commitc62bb736ecb6bd301e6963d47d66a4a2424cb18a (patch)
treefd26c36c5f7e50c3ad817593a3f59e6767fc638b
parent71babbe65397cac70ccda8cb266f6f67d17b0be6 (diff)
udev-acl: tool to grant device node access to local users
-rw-r--r--NEWS4
-rw-r--r--configure.ac45
-rw-r--r--udev-acl/.gitignore2
-rw-r--r--udev-acl/70-acl.rules36
-rw-r--r--udev-acl/Makefile.am11
-rw-r--r--udev-acl/udev-acl.c320
6 files changed, 378 insertions, 40 deletions
diff --git a/NEWS b/NEWS
index 712194d..c28bba5 100644
--- a/NEWS
+++ b/NEWS
@@ -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;
+}