summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2011-11-09 20:33:47 +0100
committerHans de Goede <hdegoede@redhat.com>2011-11-16 18:31:30 +0100
commit2161b5464fa8ad995ce6116f78aacb4c2389af55 (patch)
treed2e1930cdaf60449193e5651d928276047c1b244
parent012f2de1215b2f654571e2ca924f106c24315ba4 (diff)
Add a suid root helper to open usb device nodes
spice-client needs to be able to open the device nodes under /dev/bus/usb to be able to redirect a usb device to the guest. Normally opening these nodes is only allowed by root. This patch adds a suid root helper which asks policykit if it is ok to grant raw usb device access, and if policykit says it is ok, opens up the acl so that the spice-client can open the device node. As soon as spice-client closes the stdin of the helper, the helper removes the extra rights. This ensures that the acl gets put back to normal even if the spice client crashes. Normally the spice-client closes stdin directly after opening the device node. Signed-off-by: Hans de Goede <hdegoede@redhat.com>
-rw-r--r--configure.ac19
-rw-r--r--data/Makefile.am4
-rw-r--r--data/org.spice-space.lowlevelusbaccess.policy20
-rw-r--r--gtk/Makefile.am39
-rw-r--r--gtk/channel-usbredir.c125
-rw-r--r--gtk/spice-client-glib-usb-acl-helper.c284
-rw-r--r--gtk/usb-acl-helper.c287
-rw-r--r--gtk/usb-acl-helper.h75
8 files changed, 845 insertions, 8 deletions
diff --git a/configure.ac b/configure.ac
index 09d97bc..4a94d7b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -315,15 +315,34 @@ AC_ARG_ENABLE([usbredir],
[Enable usbredir support @<:@default=yes@:>@]),
[],
[enable_usbredir="yes"])
+AC_ARG_ENABLE([polkit],
+ AS_HELP_STRING([--enable-polkit=@<:@yes/no@:>@],
+ [Enable policykit support (for the usb acl helper)@<:@default=yes@:>@]),
+ [],
+ [enable_polkit="yes"])
if test "x$enable_usbredir" = "xno"; then
AM_CONDITIONAL(WITH_USBREDIR, false)
+ AM_CONDITIONAL(WITH_POLKIT, false)
else
PKG_CHECK_MODULES(GUDEV, gudev-1.0)
PKG_CHECK_MODULES(LIBUSB, libusb-1.0 >= 1.0.9)
PKG_CHECK_MODULES(LIBUSBREDIRHOST, libusbredirhost >= 0.3.1)
AC_DEFINE(USE_USBREDIR, [1], [Define if supporting usbredir proxying])
AM_CONDITIONAL(WITH_USBREDIR, true)
+ if test "x$enable_polkit" = "xno"; then
+ AM_CONDITIONAL(WITH_POLKIT, false)
+ else
+ PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
+ AC_CHECK_HEADER([acl/libacl.h],,
+ AC_MSG_ERROR([cannot find headers for libacl]))
+ AC_CHECK_LIB([acl], [acl_get_file], [ACL_LIBS=-lacl] [AC_SUBST(ACL_LIBS)],
+ AC_MSG_ERROR([cannot find libacl]))
+ AC_DEFINE(USE_POLKIT, [1], [Define if supporting polkit])
+ AM_CONDITIONAL(WITH_POLKIT, true)
+ POLICYDIR=`${PKG_CONFIG} polkit-gobject-1 --variable=policydir`
+ AC_SUBST(POLICYDIR)
+ fi
fi
AC_ARG_WITH([coroutine],
diff --git a/data/Makefile.am b/data/Makefile.am
index 5ad1157..71934af 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -17,6 +17,7 @@ EXTRA_DIST = \
spice-protocol.vapi \
gtkrc \
$(desktop_in_files) \
+ org.spice-space.lowlevelusbaccess.policy \
$(NULL)
CLEANFILES = \
@@ -30,4 +31,7 @@ DISTCLEANFILES = \
vapidir = $(VAPIDIR)
vapi_DATA = spice-protocol.vapi
+policydir = $(POLICYDIR)
+policy_DATA = org.spice-space.lowlevelusbaccess.policy
+
-include $(top_srcdir)/git.mk
diff --git a/data/org.spice-space.lowlevelusbaccess.policy b/data/org.spice-space.lowlevelusbaccess.policy
new file mode 100644
index 0000000..170f5ff
--- /dev/null
+++ b/data/org.spice-space.lowlevelusbaccess.policy
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
+<policyconfig>
+
+ <vendor>The Spice Project</vendor>
+ <vendor_url>http://spice-space.org/</vendor_url>
+ <icon_name>spice</icon_name>
+
+ <action id="org.spice-space.lowlevelusbaccess">
+ <description>Low level USB device access</description>
+ <message>Privileges are required for low level USB device access (for usb device pass through).</message>
+ <defaults>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
+</policyconfig>
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 17efc89..5dfa9f3 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -21,6 +21,10 @@ EXTRA_DIST = \
$(NULL)
bin_PROGRAMS = spicy snappy spicy-stats
+if WITH_POLKIT
+bin_PROGRAMS += spice-client-glib-usb-acl-helper
+endif
+
lib_LTLIBRARIES = \
libspice-client-glib-2.0.la
@@ -188,6 +192,14 @@ else
GUSB_SRCS =
endif
+if WITH_POLKIT
+USB_ACL_HELPER_SRCS = \
+ usb-acl-helper.c \
+ usb-acl-helper.h
+else
+USB_ACL_HELPER_SRCS =
+endif
+
libspice_client_glib_2_0_la_SOURCES = \
glib-compat.h \
spice-audio.c \
@@ -224,6 +236,7 @@ libspice_client_glib_2_0_la_SOURCES = \
smartcard-manager-priv.h \
usb-device-manager.c \
$(GUSB_SRCS) \
+ $(USB_ACL_HELPER_SRCS) \
\
decode.h \
decode-glz.c \
@@ -364,6 +377,32 @@ spicy_CPPFLAGS = \
$(NULL)
+if WITH_POLKIT
+spice_client_glib_usb_acl_helper_SOURCES = \
+ spice-client-glib-usb-acl-helper.c \
+ $(NULL)
+
+spice_client_glib_usb_acl_helper_LDADD = \
+ $(GLIB2_LIBS) \
+ $(GIO_LIBS) \
+ $(POLKIT_LIBS) \
+ $(ACL_LIBS) \
+ $(NULL)
+
+spice_client_glib_usb_acl_helper_CPPFLAGS = \
+ $(SPICE_CFLAGS) \
+ $(GLIB2_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(POLKIT_CFLAGS) \
+ $(NULL)
+
+install-exec-hook:
+ -chown root $(DESTDIR)$(bindir)/spice-client-glib-usb-acl-helper
+ -chmod u+s $(DESTDIR)$(bindir)/spice-client-glib-usb-acl-helper
+
+endif
+
+
snappy_SOURCES = \
snappy.c \
spice-cmdline.h \
diff --git a/gtk/channel-usbredir.c b/gtk/channel-usbredir.c
index fd54594..767e68c 100644
--- a/gtk/channel-usbredir.c
+++ b/gtk/channel-usbredir.c
@@ -26,6 +26,9 @@
#include <gusb/gusb-context-private.h>
#include <gusb/gusb-device-private.h>
#include <gusb/gusb-util.h>
+#if USE_POLKIT
+#include "usb-acl-helper.h"
+#endif
#include "channel-usbredir-priv.h"
#endif
@@ -51,6 +54,16 @@
#define SPICE_USBREDIR_CHANNEL_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USBREDIR_CHANNEL, SpiceUsbredirChannelPrivate))
+enum SpiceUsbredirChannelState {
+ STATE_DISCONNECTED,
+#if USE_POLKIT
+ STATE_WAITING_FOR_ACL_HELPER,
+#endif
+ STATE_CONNECTING,
+ STATE_CONNECTED,
+ STATE_DISCONNECTING,
+};
+
struct _SpiceUsbredirChannelPrivate {
GUsbContext *context;
GUsbDevice *device;
@@ -61,7 +74,11 @@ struct _SpiceUsbredirChannelPrivate {
const uint8_t *read_buf;
int read_buf_size;
SpiceMsgOut *msg_out;
- gboolean up;
+ enum SpiceUsbredirChannelState state;
+#if USE_POLKIT
+ GSimpleAsyncResult *result;
+ SpiceUsbAclHelper *acl_helper;
+#endif
};
static void spice_usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
@@ -111,6 +128,27 @@ static void spice_usbredir_channel_dispose(GObject *obj)
G_OBJECT_CLASS(spice_usbredir_channel_parent_class)->dispose(obj);
}
+/*
+ * Note we don't have a finalize to unref our : device / context / acl_helper /
+ * result references. The reason for this is that depending on our state they
+ * are either:
+ * 1) Already unreferenced
+ * 2) Will be unreferenced by the disconnect call from dispose
+ * 3) Will be unreferenced by spice_usbredir_channel_open_acl_cb
+ *
+ * Now the last one may seem like an issue, since what will happen if
+ * spice_usbredir_channel_open_acl_cb will run after finalization?
+ *
+ * This will never happens since the GSimpleAsyncResult created before we
+ * get into the STATE_WAITING_FOR_ACL_HELPER takes a reference to its
+ * source object, which is our SpiceUsbredirChannel object, so
+ * the finalize won't hapen until spice_usbredir_channel_open_acl_cb runs,
+ * and unrefs priv->result which will in turn unref ourselve once the
+ * complete_in_idle call it does has completed. And once
+ * spice_usbredir_channel_open_acl_cb has run, all references we hold have
+ * been released even in the 3th scenario.
+ */
+
static const spice_msg_handler usbredir_handlers[] = {
[ SPICE_MSG_SPICEVMC_DATA ] = usbredir_handle_msg,
};
@@ -125,6 +163,12 @@ static gboolean spice_usbredir_channel_open_device(
libusb_device_handle *handle = NULL;
int rc;
+ g_return_val_if_fail(priv->state == STATE_DISCONNECTED
+#if USE_POLKIT
+ || priv->state == STATE_WAITING_FOR_ACL_HELPER
+#endif
+ , FALSE);
+
rc = libusb_open(_g_usb_device_get_device(priv->device), &handle);
if (rc != 0) {
g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
@@ -148,10 +192,47 @@ static gboolean spice_usbredir_channel_open_device(
}
spice_channel_connect(SPICE_CHANNEL(channel));
+ priv->state = STATE_CONNECTING;
return TRUE;
}
+#if USE_POLKIT
+static void spice_usbredir_channel_open_acl_cb(
+ GObject *gobject, GAsyncResult *acl_res, gpointer user_data)
+{
+ SpiceUsbAclHelper *acl_helper = SPICE_USB_ACL_HELPER(gobject);
+ SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
+ SpiceUsbredirChannelPrivate *priv = channel->priv;
+ GError *err = NULL;
+
+ g_return_if_fail(acl_helper == priv->acl_helper);
+ g_return_if_fail(priv->state == STATE_WAITING_FOR_ACL_HELPER ||
+ priv->state == STATE_DISCONNECTING);
+
+ spice_usb_acl_helper_open_acl_finish(acl_helper, acl_res, &err);
+ if (!err && priv->state == STATE_DISCONNECTING) {
+ err = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ "USB redirection channel connect cancelled");
+ }
+ if (!err) {
+ spice_usbredir_channel_open_device(channel, &err);
+ }
+ if (err) {
+ g_simple_async_result_take_error(priv->result, err);
+ g_clear_object(&priv->context);
+ g_clear_object(&priv->device);
+ priv->state = STATE_DISCONNECTED;
+ }
+
+ spice_usb_acl_helper_close_acl(priv->acl_helper);
+ g_clear_object(&priv->acl_helper);
+
+ g_simple_async_result_complete_in_idle(priv->result);
+ g_clear_object(&priv->result);
+}
+#endif
+
G_GNUC_INTERNAL
void spice_usbredir_channel_connect_async(SpiceUsbredirChannel *channel,
GUsbContext *context,
@@ -173,7 +254,7 @@ void spice_usbredir_channel_connect_async(SpiceUsbredirChannel *channel,
result = g_simple_async_result_new(G_OBJECT(channel), callback, user_data,
spice_usbredir_channel_connect_async);
- if (priv->device) {
+ if (priv->state != STATE_DISCONNECTED) {
g_simple_async_result_set_error(result,
SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"Error channel is busy");
@@ -183,9 +264,22 @@ void spice_usbredir_channel_connect_async(SpiceUsbredirChannel *channel,
priv->context = g_object_ref(context);
priv->device = g_object_ref(device);
if (!spice_usbredir_channel_open_device(channel, &err)) {
+#if USE_POLKIT
+ priv->result = result;
+ priv->state = STATE_WAITING_FOR_ACL_HELPER;
+ priv->acl_helper = spice_usb_acl_helper_new();
+ spice_usb_acl_helper_open_acl(priv->acl_helper,
+ g_usb_device_get_bus(device),
+ g_usb_device_get_address(device),
+ cancellable,
+ spice_usbredir_channel_open_acl_cb,
+ channel);
+ return;
+#else
g_simple_async_result_take_error(result, err);
g_clear_object(&priv->context);
g_clear_object(&priv->device);
+#endif
}
done:
@@ -217,15 +311,27 @@ void spice_usbredir_channel_disconnect(SpiceUsbredirChannel *channel)
SPICE_DEBUG("disconnecting usb channel %p", channel);
- spice_channel_disconnect(SPICE_CHANNEL(channel), SPICE_CHANNEL_NONE);
- priv->up = FALSE;
-
- if (priv->host) {
+ switch (priv->state) {
+ case STATE_DISCONNECTED:
+ case STATE_DISCONNECTING:
+ break;
+#if USE_POLKIT
+ case STATE_WAITING_FOR_ACL_HELPER:
+ priv->state = STATE_DISCONNECTING;
+ /* We're still waiting for the acl helper -> cancel it */
+ spice_usb_acl_helper_close_acl(priv->acl_helper);
+ break;
+#endif
+ case STATE_CONNECTING:
+ case STATE_CONNECTED:
+ spice_channel_disconnect(SPICE_CHANNEL(channel), SPICE_CHANNEL_NONE);
/* This also closes the libusb handle we passed to its _open */
usbredirhost_close(priv->host);
priv->host = NULL;
g_clear_object(&priv->device);
g_clear_object(&priv->context);
+ priv->state = STATE_DISCONNECTED;
+ break;
}
}
@@ -243,7 +349,8 @@ void spice_usbredir_channel_do_write(SpiceUsbredirChannel *channel)
/* No recursion allowed! */
g_return_if_fail(priv->msg_out == NULL);
- if (!priv->up || !usbredirhost_has_data_to_write(priv->host))
+ if (priv->state != STATE_CONNECTED ||
+ !usbredirhost_has_data_to_write(priv->host))
return;
priv->msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
@@ -345,7 +452,9 @@ static void spice_usbredir_channel_up(SpiceChannel *c)
SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(c);
SpiceUsbredirChannelPrivate *priv = channel->priv;
- priv->up = TRUE;
+ g_return_if_fail(priv->state == STATE_CONNECTING);
+
+ priv->state = STATE_CONNECTED;
/* Flush any pending writes */
spice_usbredir_channel_do_write(channel);
}
diff --git a/gtk/spice-client-glib-usb-acl-helper.c b/gtk/spice-client-glib-usb-acl-helper.c
new file mode 100644
index 0000000..2e31bd3
--- /dev/null
+++ b/gtk/spice-client-glib-usb-acl-helper.c
@@ -0,0 +1,284 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2011 Red Hat, Inc.
+ Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org>
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gio/gunixinputstream.h>
+#include <polkit/polkit.h>
+#include <acl/libacl.h>
+
+#define FATAL_ERROR(...) \
+ do { \
+ /* We print the error both to stdout, for the app invoking us and \
+ stderr for the end user */ \
+ fprintf(stdout, "Error " __VA_ARGS__); \
+ fprintf(stderr, "spice-client-glib-usb-helper: Error " __VA_ARGS__); \
+ exit_status = 1; \
+ cleanup(); \
+ } while (0)
+
+#define ERROR(...) \
+ do { \
+ fprintf(stdout, __VA_ARGS__); \
+ cleanup(); \
+ } while (0)
+
+enum state {
+ STATE_WAITING_FOR_BUS_N_DEV,
+ STATE_WAITING_FOR_POL_KIT,
+ STATE_WAITING_FOR_STDIN_EOF,
+};
+
+static enum state state = STATE_WAITING_FOR_BUS_N_DEV;
+static int exit_status;
+static int busnum, devnum;
+static char path[PATH_MAX];
+static GMainLoop *loop;
+static GDataInputStream *stdin_stream;
+static GCancellable *polkit_cancellable;
+static PolkitSubject *subject;
+static PolkitAuthority *authority;
+
+/*
+ * This function is a copy of the same function in udev, written by Kay
+ * Sievers, you can find it in udev in extras/udev-acl/udev-acl.c
+ */
+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;
+
+ /* don't touch ACLs for root */
+ if (uid == 0)
+ return 0;
+
+ /* 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 */
+ acl_calc_mask(&acl);
+ ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
+ if (ret != 0)
+ goto out;
+out:
+ acl_free(acl);
+ return ret;
+}
+
+static void cleanup()
+{
+ if (polkit_cancellable)
+ g_cancellable_cancel(polkit_cancellable);
+
+ if (state == STATE_WAITING_FOR_STDIN_EOF)
+ set_facl(path, getuid(), 0);
+
+ g_main_loop_quit(loop);
+}
+
+static void check_authorization_cb(PolkitAuthority *authority,
+ GAsyncResult *res, gpointer data)
+{
+ PolkitAuthorizationResult *result;
+ GError *err = NULL;
+
+ g_clear_object(&polkit_cancellable);
+
+ result = polkit_authority_check_authorization_finish(authority, res, &err);
+ if (err) {
+ FATAL_ERROR("PoliciKit error: %s\n", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ if (!polkit_authorization_result_get_is_authorized(result)) {
+ ERROR("Not authorized\n");
+ return;
+ }
+
+ snprintf(path, PATH_MAX, "/dev/bus/usb/%03d/%03d", busnum, devnum);
+ if (set_facl(path, getuid(), 1)) {
+ FATAL_ERROR("setting facl: %s\n", strerror(errno));
+ return;
+ }
+
+ fprintf(stdout, "SUCCESS\n");
+ fflush(stdout);
+ state = STATE_WAITING_FOR_STDIN_EOF;
+}
+
+static void stdin_read_complete(GObject *src, GAsyncResult *res, gpointer data)
+{
+ char *s, *ep;
+ GError *err = NULL;
+ gsize len;
+
+ s = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(src), res,
+ &len, &err);
+ if (!s) {
+ if (err) {
+ FATAL_ERROR("Reading from stdin: %s\n", err->message);
+ g_error_free(err);
+ return;
+ }
+
+ switch (state) {
+ case STATE_WAITING_FOR_BUS_N_DEV:
+ FATAL_ERROR("EOF while waiting for bus and device num\n");
+ break;
+ case STATE_WAITING_FOR_POL_KIT:
+ ERROR("Cancelled while waiting for authorization\n");
+ break;
+ case STATE_WAITING_FOR_STDIN_EOF:
+ cleanup();
+ break;
+ }
+ return;
+ }
+
+ switch (state) {
+ case STATE_WAITING_FOR_BUS_N_DEV:
+ busnum = strtol(s, &ep, 10);
+ if (!isspace(*ep)) {
+ FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
+ break;
+ }
+ devnum = strtol(ep, &ep, 10);
+ if (*ep != '\0') {
+ FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
+ break;
+ }
+
+ state = STATE_WAITING_FOR_POL_KIT;
+
+ polkit_cancellable = g_cancellable_new();
+ polkit_authority_check_authorization(
+ authority, subject, "org.spice-space.lowlevelusbaccess", NULL,
+ POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
+ polkit_cancellable, (GAsyncReadyCallback)check_authorization_cb,
+ loop);
+
+ g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT,
+ NULL, stdin_read_complete, NULL);
+ break;
+ default:
+ FATAL_ERROR("Unexpected extra input in state %d: %s\n", state, s);
+ }
+}
+
+int main(void)
+{
+ pid_t parent_pid;
+ GInputStream *stdin_unix_stream;
+
+ g_type_init();
+
+ loop = g_main_loop_new(NULL, FALSE);
+
+ authority = polkit_authority_get_sync(NULL, NULL);
+ parent_pid = getppid ();
+ if (parent_pid == 1) {
+ FATAL_ERROR("Parent process was reaped by init(1)\n");
+ return 1;
+ }
+ subject = polkit_unix_process_new(parent_pid);
+
+ stdin_unix_stream = g_unix_input_stream_new(STDIN_FILENO, 0);
+ stdin_stream = g_data_input_stream_new(stdin_unix_stream);
+ g_data_input_stream_set_newline_type(stdin_stream,
+ G_DATA_STREAM_NEWLINE_TYPE_LF);
+ g_clear_object(&stdin_unix_stream);
+ g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT, NULL,
+ stdin_read_complete, NULL);
+
+ g_main_loop_run(loop);
+
+ if (polkit_cancellable)
+ g_clear_object(&polkit_cancellable);
+ g_object_unref(stdin_stream);
+ g_object_unref(authority);
+ g_object_unref(subject);
+ g_main_loop_unref(loop);
+
+ return exit_status;
+}
diff --git a/gtk/usb-acl-helper.c b/gtk/usb-acl-helper.c
new file mode 100644
index 0000000..e5f8b4d
--- /dev/null
+++ b/gtk/usb-acl-helper.c
@@ -0,0 +1,287 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2011 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "usb-acl-helper.h"
+
+/* ------------------------------------------------------------------ */
+/* gobject glue */
+
+#define SPICE_USB_ACL_HELPER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperPrivate))
+
+struct _SpiceUsbAclHelperPrivate {
+ GSimpleAsyncResult *result;
+ GIOChannel *in_ch;
+ GIOChannel *out_ch;
+ GCancellable *cancellable;
+ gulong cancellable_id;
+};
+
+G_DEFINE_TYPE(SpiceUsbAclHelper, spice_usb_acl_helper, G_TYPE_OBJECT);
+
+static void spice_usb_acl_helper_init(SpiceUsbAclHelper *self)
+{
+ self->priv = SPICE_USB_ACL_HELPER_GET_PRIVATE(self);
+}
+
+static void spice_usb_acl_helper_cleanup(SpiceUsbAclHelper *self)
+{
+ SpiceUsbAclHelperPrivate *priv = self->priv;
+
+ g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
+ priv->cancellable = NULL;
+ priv->cancellable_id = 0;
+
+ g_clear_object(&priv->result);
+
+ if (priv->in_ch) {
+ g_io_channel_unref(priv->in_ch);
+ priv->in_ch = NULL;
+ }
+
+ if (priv->out_ch) {
+ g_io_channel_unref(priv->out_ch);
+ priv->out_ch = NULL;
+ }
+}
+
+static void spice_usb_acl_helper_finalize(GObject *gobject)
+{
+ spice_usb_acl_helper_cleanup(SPICE_USB_ACL_HELPER(gobject));
+}
+
+static void spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = spice_usb_acl_helper_finalize;
+
+ g_type_class_add_private(klass, sizeof(SpiceUsbAclHelperPrivate));
+}
+
+/* ------------------------------------------------------------------ */
+/* callbacks */
+
+static gboolean cb_out_watch(GIOChannel *channel,
+ GIOCondition cond,
+ gpointer *user_data)
+{
+ SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
+ SpiceUsbAclHelperPrivate *priv = self->priv;
+ gboolean success = FALSE;
+ GError *err = NULL;
+ GIOStatus status;
+ gchar *string;
+ gsize size;
+
+ /* Check that we've not been cancelled */
+ if (priv->result == NULL)
+ goto done;
+
+ g_return_val_if_fail(channel == priv->out_ch, FALSE);
+
+ status = g_io_channel_read_line(priv->out_ch, &string, &size, NULL, &err);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ string[strlen(string) - 1] = 0;
+ if (!strcmp(string, "SUCCESS")) {
+ success = TRUE;
+ } else {
+ g_simple_async_result_set_error(priv->result,
+ SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+ "Error setting USB device node ACL: '%s'",
+ string);
+ }
+ g_free(string);
+ break;
+ case G_IO_STATUS_ERROR:
+ g_simple_async_result_take_error(priv->result, err);
+ break;
+ case G_IO_STATUS_EOF:
+ g_simple_async_result_set_error(priv->result,
+ SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+ "Unexpected EOF reading from acl helper stdout");
+ case G_IO_STATUS_AGAIN:
+ return TRUE; /* Wait for more input */
+ }
+
+ g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
+ priv->cancellable = NULL;
+ priv->cancellable_id = 0;
+
+ g_simple_async_result_complete_in_idle(priv->result);
+ g_clear_object(&priv->result);
+
+ if (!success)
+ spice_usb_acl_helper_cleanup(self);
+
+done:
+ g_object_unref(self);
+ return FALSE;
+}
+
+static void cancelled_cb(GCancellable *cancellable, gpointer user_data)
+{
+ SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
+
+ spice_usb_acl_helper_close_acl(self);
+}
+
+static void helper_child_watch_cb(GPid pid, gint status, gpointer user_data)
+{
+ /* Nothing to do, but we need the child watch to avoid zombies */
+}
+
+/* ------------------------------------------------------------------ */
+/* private api */
+
+G_GNUC_INTERNAL
+SpiceUsbAclHelper *spice_usb_acl_helper_new(void)
+{
+ GObject *obj;
+
+ obj = g_object_new(SPICE_TYPE_USB_ACL_HELPER, NULL);
+
+ return SPICE_USB_ACL_HELPER(obj);
+}
+
+G_GNUC_INTERNAL
+void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
+ gint busnum, gint devnum,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
+
+ SpiceUsbAclHelperPrivate *priv = self->priv;
+ GSimpleAsyncResult *result;
+ GError *err = NULL;
+ GIOStatus status;
+ GPid helper_pid;
+ gsize bytes_written;
+ gchar *argv[] = { "spice-client-glib-usb-acl-helper", NULL };
+ gint in, out;
+ gchar buf[128];
+
+ result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+ spice_usb_acl_helper_open_acl);
+
+ if (priv->out_ch) {
+ g_simple_async_result_set_error(result,
+ SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+ "Error acl-helper already has an acl open");
+ goto done;
+ }
+
+ if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
+ g_simple_async_result_take_error(result, err);
+ goto done;
+ }
+
+ if (!g_spawn_async_with_pipes(NULL, argv, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
+ NULL, NULL, &helper_pid, &in, &out, NULL, &err)) {
+ g_simple_async_result_take_error(result, err);
+ goto done;
+ }
+ g_child_watch_add(helper_pid, helper_child_watch_cb, NULL);
+
+ priv->in_ch = g_io_channel_unix_new(in);
+ g_io_channel_set_close_on_unref(priv->in_ch, TRUE);
+
+ priv->out_ch = g_io_channel_unix_new(out);
+ g_io_channel_set_close_on_unref(priv->out_ch, TRUE);
+ status = g_io_channel_set_flags(priv->out_ch, G_IO_FLAG_NONBLOCK, &err);
+ if (status != G_IO_STATUS_NORMAL) {
+ g_simple_async_result_take_error(result, err);
+ goto done;
+ }
+
+ snprintf(buf, sizeof(buf), "%d %d\n", busnum, devnum);
+ status = g_io_channel_write_chars(priv->in_ch, buf, -1,
+ &bytes_written, &err);
+ if (status != G_IO_STATUS_NORMAL) {
+ g_simple_async_result_take_error(result, err);
+ goto done;
+ }
+ status = g_io_channel_flush(priv->in_ch, &err);
+ if (status != G_IO_STATUS_NORMAL) {
+ g_simple_async_result_take_error(result, err);
+ goto done;
+ }
+
+ priv->result = result;
+ if (cancellable) {
+ priv->cancellable = cancellable;
+ priv->cancellable_id = g_cancellable_connect(cancellable,
+ G_CALLBACK(cancelled_cb),
+ self, NULL);
+ }
+ g_io_add_watch(priv->out_ch, G_IO_IN|G_IO_HUP,
+ (GIOFunc)cb_out_watch, g_object_ref(self));
+ return;
+
+done:
+ spice_usb_acl_helper_cleanup(self);
+ g_simple_async_result_complete_in_idle(result);
+ g_object_unref(result);
+}
+
+G_GNUC_INTERNAL
+gboolean spice_usb_acl_helper_open_acl_finish(
+ SpiceUsbAclHelper *self, GAsyncResult *res, GError **err)
+{
+ GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
+
+ g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
+ spice_usb_acl_helper_open_acl),
+ FALSE);
+
+ if (g_simple_async_result_propagate_error(result, err))
+ return FALSE;
+
+ return TRUE;
+}
+
+G_GNUC_INTERNAL
+void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self)
+{
+ g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
+
+ SpiceUsbAclHelperPrivate *priv = self->priv;
+
+ /* If the acl open has not completed yet report it as cancelled */
+ if (priv->result) {
+ g_simple_async_result_set_error(priv->result,
+ G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ "Setting USB device node ACL cancelled");
+ g_simple_async_result_complete_in_idle(priv->result);
+ }
+
+ spice_usb_acl_helper_cleanup(self);
+}
diff --git a/gtk/usb-acl-helper.h b/gtk/usb-acl-helper.h
new file mode 100644
index 0000000..c43bdd0
--- /dev/null
+++ b/gtk/usb-acl-helper.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2011 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __SPICE_USB_ACL_HELPER_H__
+#define __SPICE_USB_ACL_HELPER_H__
+
+#include "spice-client.h"
+#include <gio/gio.h>
+
+/* Note the entire usb-acl-helper class is private to spice-client-glib !! */
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_USB_ACL_HELPER (spice_usb_acl_helper_get_type ())
+#define SPICE_USB_ACL_HELPER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelper))
+#define SPICE_USB_ACL_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
+#define SPICE_IS_USB_ACL_HELPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_ACL_HELPER))
+#define SPICE_IS_USB_ACL_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_ACL_HELPER))
+#define SPICE_USB_ACL_HELPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
+
+#define SPICE_TYPE_USB_DEVICE (spice_usb_device_get_type())
+
+typedef struct _SpiceUsbAclHelper SpiceUsbAclHelper;
+typedef struct _SpiceUsbAclHelperClass SpiceUsbAclHelperClass;
+typedef struct _SpiceUsbAclHelperPrivate SpiceUsbAclHelperPrivate;
+
+struct _SpiceUsbAclHelper
+{
+ GObject parent;
+
+ /*< private >*/
+ SpiceUsbAclHelperPrivate *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceUsbAclHelperClass
+{
+ GObjectClass parent_class;
+};
+
+GType spice_usb_device_get_type(void);
+GType spice_usb_acl_helper_get_type(void);
+
+SpiceUsbAclHelper *spice_usb_acl_helper_new(void);
+
+void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
+ gint busnum, gint devnum,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean spice_usb_acl_helper_open_acl_finish(
+ SpiceUsbAclHelper *self, GAsyncResult *res, GError **err);
+
+void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_USB_ACL_HELPER_H__ */