From 35f7bd2824c40658de9589928bea7b2237c60101 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Fri, 12 Oct 2007 20:38:13 -0400 Subject: [PATCH] add the convenience class PolKitTracker This class allows a mechanism to greatly reduce the syscall and IPC overhead when checking whether a caller on the system bus message is allowed to do a specific action. In a nutshell, this class caches PolKitCaller objects and a) updates them when ConsoleKit emits ActivityChanged signals; and b) evicts such objects from the cache when the caller drops off the bus. There's also an example, in examples/tracker-example/ that shows how to use this. This example is referenced in the API docs too. --- examples/tracker-example/README | 4 + .../dk.fubar.PolKitTestService.conf | 10 + examples/tracker-example/tracker-example-client.py | 11 + examples/tracker-example/tracker-example.c | 138 ++++++++ polkit-dbus/polkit-dbus.c | 364 ++++++++++++++++++++- polkit-dbus/polkit-dbus.h | 15 + 6 files changed, 536 insertions(+), 6 deletions(-) create mode 100644 examples/tracker-example/README create mode 100644 examples/tracker-example/dk.fubar.PolKitTestService.conf create mode 100755 examples/tracker-example/tracker-example-client.py create mode 100644 examples/tracker-example/tracker-example.c diff --git a/examples/tracker-example/README b/examples/tracker-example/README new file mode 100644 index 0000000..b2d0e3b --- /dev/null +++ b/examples/tracker-example/README @@ -0,0 +1,4 @@ + +This is a simple example to show how the PolKitTracker class is +used. For a detailed explanation, refer to the API docs for +PolKitTracker. diff --git a/examples/tracker-example/dk.fubar.PolKitTestService.conf b/examples/tracker-example/dk.fubar.PolKitTestService.conf new file mode 100644 index 0000000..84650e3 --- /dev/null +++ b/examples/tracker-example/dk.fubar.PolKitTestService.conf @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/examples/tracker-example/tracker-example-client.py b/examples/tracker-example/tracker-example-client.py new file mode 100755 index 0000000..2b658c2 --- /dev/null +++ b/examples/tracker-example/tracker-example-client.py @@ -0,0 +1,11 @@ +#!/usr/bin/python + +import dbus +import time + +bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) +obj = dbus.Interface(bus.get_object("dk.fubar.PolKitTestService", "/"), "dk.fubar.PolKitTestService") + +while True: + print obj.Test() + time.sleep(1) diff --git a/examples/tracker-example/tracker-example.c b/examples/tracker-example/tracker-example.c new file mode 100644 index 0000000..406f3fc --- /dev/null +++ b/examples/tracker-example/tracker-example.c @@ -0,0 +1,138 @@ +/* + * Small example of how to use the PolKitTracker class. + * + * Copyright (C) 2007 David Zeuthen, + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +/* Note, on purpose, there is little or no error checking done + * anywhere in this program. Use at your own risk. + */ + +static void +print_caller (PolKitTracker *pk_tracker, const char *dbus_name) +{ + DBusError error; + PolKitCaller *caller; + + dbus_error_init (&error); + + caller = polkit_tracker_get_caller_from_dbus_name (pk_tracker, + dbus_name, + &error); + if (caller == NULL) { + g_warning ("Error getting PolKitCaller for '%s': %s: %s", + dbus_name, error.name, error.message); + dbus_error_free (&error); + } else { + /* got it; print it to stdout */ + printf ("\n"); + polkit_caller_debug (caller); + polkit_caller_unref (caller); + } +} + +static DBusHandlerResult +filter (DBusConnection *connection, DBusMessage *message, void *user_data) +{ + PolKitTracker *pk_tracker = (PolKitTracker *) user_data; + char *name; + char *new_service_name; + char *old_service_name; + + /* pass NameOwnerChanged signals from the bus and ConsoleKit to PolKitTracker */ + if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameOwnerChanged") || + g_str_has_prefix (dbus_message_get_interface (message), "org.freedesktop.ConsoleKit")) { + polkit_tracker_dbus_func (pk_tracker, message); + } + + /* handle calls into our test service */ + if (dbus_message_is_method_call (message, "dk.fubar.PolKitTestService", "Test")) { + DBusMessage *reply; + const char *reply_str = "Right back at y'all!"; + + print_caller (pk_tracker, dbus_message_get_sender (message)); + + reply = dbus_message_new_method_return (message); + dbus_message_append_args (reply, + DBUS_TYPE_STRING, &reply_str, + DBUS_TYPE_INVALID); + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); + + /* this one we do handle */ + return DBUS_HANDLER_RESULT_HANDLED; + } + + /* other filters might want to process this message too */ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +int +main (int argc, char *argv[]) +{ + DBusError error; + DBusConnection *con; + GMainLoop *loop; + PolKitTracker *pk_tracker; + + /* This is needed to get something out of polkit_caller_debug() */ + g_setenv ("POLKIT_DEBUG", "1", TRUE); + + loop = g_main_loop_new (NULL, FALSE); + + dbus_error_init (&error); + con = dbus_bus_get (DBUS_BUS_SYSTEM, &error); + + pk_tracker = polkit_tracker_new (); + polkit_tracker_set_system_bus_connection (pk_tracker, con); + polkit_tracker_init (pk_tracker); + + /* need to listen to NameOwnerChanged */ + dbus_bus_add_match (con, + "type='signal'" + ",interface='"DBUS_INTERFACE_DBUS"'" + ",sender='"DBUS_SERVICE_DBUS"'" + ",member='NameOwnerChanged'", + &error); + + /* need to listen to ConsoleKit signals */ + dbus_bus_add_match (con, + "type='signal',sender='org.freedesktop.ConsoleKit'", + &error); + + /* own a simple service */ + dbus_bus_request_name (con, "dk.fubar.PolKitTestService", DBUS_NAME_FLAG_REPLACE_EXISTING, &error); + + dbus_connection_add_filter (con, filter, pk_tracker, NULL); + dbus_connection_setup_with_g_main (con, g_main_loop_get_context (loop)); + + g_main_loop_run (loop); + return 0; +} diff --git a/polkit-dbus/polkit-dbus.c b/polkit-dbus/polkit-dbus.c index 5abdf1d..c18f5b2 100644 --- a/polkit-dbus/polkit-dbus.c +++ b/polkit-dbus/polkit-dbus.c @@ -32,11 +32,12 @@ * * Helper library for obtaining seat, session and caller information * via D-Bus and ConsoleKit. This library is only useful when writing - * a mechanism. If the mechanism itself is a daemon exposing a remote - * services (via e.g. D-Bus) it's often a better idea, to reduce - * roundtrips, to track and cache caller information and construct - * #PolKitCaller objects yourself based on this information (for an - * example of this, see the hald sources on how this can be done). + * a mechanism. + * + * If the mechanism itself is a daemon exposing a remote services via + * the system message bus it's often a better idea, to reduce + * roundtrips, to use the high-level #PolKitTracker class rather than + * the low-level function polkit_caller_new_from_dbus_name(). **/ #ifdef HAVE_CONFIG_H @@ -60,6 +61,7 @@ #endif #include "polkit-dbus.h" +#include /** @@ -357,7 +359,8 @@ out: * both the system bus daemon and the ConsoleKit daemon for * information. Note that this will do a lot of blocking IO so it is * best avoided if your process already tracks/caches all the - * information. + * information. For example you can use the #PolKitTracker class for + * this. * * Returns: the new object or #NULL if an error occured (in which case * @error will be set) @@ -719,3 +722,352 @@ out: g_free (proc_path); return caller; } + +/** + * PolKitTracker: + * + * Instances of this class are used to cache information about + * callers; typically this is used in scenarios where the same caller + * is calling into a mechanism multiple times. + * + * Thus, an application can use this class to get the #PolKitCaller + * object; the class will listen to both NameOwnerChanged and + * ActivityChanged signals from the message bus and update / retire + * the #PolKitCaller objects. + * + * An example of how to use #PolKitTracker is provided here. First, build the following program + * + * FIXME: MISSING XINCLUDE CONTENT + * + * with + * + * gcc -o tracker-example `pkg-config --cflags --libs dbus-glib-1 polkit-dbus` tracker-example.c + * + * Then put the following content + * + * FIXME: MISSING XINCLUDE CONTENT + * + * in the file /etc/dbus-1/system.d/dk.fubar.PolKitTestService.conf. Finally, + * create a small Python client like this + * + * FIXME: MISSING XINCLUDE CONTENT + * + * as tracker-example-client.py. Now, run tracker-example + * in one window and tracker-example-client in another. The output of + * the former should look like this + * + * + * + * 18:20:00.414: PolKitCaller: refcount=1 dbus_name=:1.473 uid=500 pid=8636 selinux_context=system_u:system_r:unconfined_t + * 18:20:00.414: PolKitSession: refcount=1 uid=0 objpath=/org/freedesktop/ConsoleKit/Session1 is_active=1 is_local=1 remote_host=(null) + * 18:20:00.414: PolKitSeat: refcount=1 objpath=/org/freedesktop/ConsoleKit/Seat1 + * + * 18:20:01.424: PolKitCaller: refcount=1 dbus_name=:1.473 uid=500 pid=8636 selinux_context=system_u:system_r:unconfined_t + * 18:20:01.424: PolKitSession: refcount=1 uid=0 objpath=/org/freedesktop/ConsoleKit/Session1 is_active=1 is_local=1 remote_host=(null) + * 18:20:01.424: PolKitSeat: refcount=1 objpath=/org/freedesktop/ConsoleKit/Seat1 + * + * 18:20:02.434: PolKitCaller: refcount=1 dbus_name=:1.473 uid=500 pid=8636 selinux_context=system_u:system_r:unconfined_t + * 18:20:02.434: PolKitSession: refcount=1 uid=0 objpath=/org/freedesktop/ConsoleKit/Session1 is_active=0 is_local=1 remote_host=(null) + * 18:20:02.434: PolKitSeat: refcount=1 objpath=/org/freedesktop/ConsoleKit/Seat1 + * + * 18:20:03.445: PolKitCaller: refcount=1 dbus_name=:1.473 uid=500 pid=8636 selinux_context=system_u:system_r:unconfined_t + * 18:20:03.445: PolKitSession: refcount=1 uid=0 objpath=/org/freedesktop/ConsoleKit/Session1 is_active=1 is_local=1 remote_host=(null) + * 18:20:03.445: PolKitSeat: refcount=1 objpath=/org/freedesktop/ConsoleKit/Seat1 + * + * + * The point of the test program is simply to gather caller + * information about clients (the small Python program, you may launch + * multiple instances of it) that repeatedly calls into the D-Bus + * service; if one runs strace(1) in front of the + * test program one will notice that there is only syscall / IPC + * overhead (except for printing to stdout) on the first call from the + * client. + * + * The careful reader will notice that, during the testing session, we + * did a quick VT switch away from the session (and back) which is + * reflected in the output. + **/ +struct _PolKitTracker { + int refcount; + DBusConnection *con; + + GHashTable *dbus_name_to_caller; +}; + +/** + * polkit_tracker_new: + * + * Creates a new #PolKitTracker object. + * + * Returns: the new object + **/ +PolKitTracker * +polkit_tracker_new (void) +{ + PolKitTracker *pk_tracker; + pk_tracker = g_new0 (PolKitTracker, 1); + pk_tracker->refcount = 1; + pk_tracker->dbus_name_to_caller = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) polkit_caller_unref); + return pk_tracker; +} + +/** + * polkit_tracker_ref: + * @pk_tracker: the tracker object + * + * Increase reference count. + * + * Returns: the object + **/ +PolKitTracker * +polkit_tracker_ref (PolKitTracker *pk_tracker) +{ + g_return_val_if_fail (pk_tracker != NULL, pk_tracker); + pk_tracker->refcount++; + return pk_tracker; +} + +/** + * polkit_tracker_unref: + * @pk_tracker: the tracker object + * + * Decreases the reference count of the object. If it becomes zero, + * the object is freed. Before freeing, reference counts on embedded + * objects are decresed by one. + **/ +void +polkit_tracker_unref (PolKitTracker *pk_tracker) +{ + g_return_if_fail (pk_tracker != NULL); + pk_tracker->refcount--; + if (pk_tracker->refcount > 0) + return; + g_hash_table_unref (pk_tracker->dbus_name_to_caller); + dbus_connection_unref (pk_tracker->con); + g_free (pk_tracker); +} + +/** + * polkit_tracker_set_system_bus_connection: + * @pk_tracker: the tracker object + * @con: the connection to the system message bus + * + * Tell the #PolKitTracker object to use the given D-Bus connection + * when it needs to fetch information from the system message bus and + * ConsoleKit services. This is used for priming the cache. + */ +void +polkit_tracker_set_system_bus_connection (PolKitTracker *pk_tracker, DBusConnection *con) +{ + g_return_if_fail (pk_tracker != NULL); + pk_tracker->con = dbus_connection_ref (con); +} + +/** + * polkit_tracker_init: + * @pk_tracker: the tracker object + * + * Initialize the tracker. + */ +void +polkit_tracker_init (PolKitTracker *pk_tracker) +{ + g_return_if_fail (pk_tracker != NULL); + /* This is currently a no-op */ +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +static void +_set_session_inactive_iter (gpointer key, PolKitCaller *caller, const char *session_objpath) +{ + char *objpath; + PolKitSession *session; + if (!polkit_caller_get_ck_session (caller, &session)) + return; + if (!polkit_session_get_ck_objref (session, &objpath)) + return; + if (strcmp (objpath, session_objpath) != 0) + return; + polkit_session_set_ck_is_active (session, FALSE); +} + +static void +_set_session_active_iter (gpointer key, PolKitCaller *caller, const char *session_objpath) +{ + char *objpath; + PolKitSession *session; + if (!polkit_caller_get_ck_session (caller, &session)) + return; + if (!polkit_session_get_ck_objref (session, &objpath)) + return; + if (strcmp (objpath, session_objpath) != 0) + return; + polkit_session_set_ck_is_active (session, TRUE); +} + +static void +_update_session_is_active (PolKitTracker *pk_tracker, const char *session_objpath, gboolean is_active) +{ + g_hash_table_foreach (pk_tracker->dbus_name_to_caller, + (GHFunc) (is_active ? _set_session_active_iter : _set_session_inactive_iter), + (gpointer) session_objpath); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +static gboolean +_remove_caller_by_session_iter (gpointer key, PolKitCaller *caller, const char *session_objpath) +{ + char *objpath; + PolKitSession *session; + if (!polkit_caller_get_ck_session (caller, &session)) + return FALSE; + if (!polkit_session_get_ck_objref (session, &objpath)) + return FALSE; + if (strcmp (objpath, session_objpath) != 0) + return FALSE; + return TRUE; +} + +static void +_remove_caller_by_session (PolKitTracker *pk_tracker, const char *session_objpath) +{ + g_hash_table_foreach_remove (pk_tracker->dbus_name_to_caller, + (GHRFunc) _remove_caller_by_session_iter, + (gpointer) session_objpath); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +static gboolean +_remove_caller_by_dbus_name_iter (gpointer key, PolKitCaller *caller, const char *dbus_name) +{ + char *name; + if (!polkit_caller_get_dbus_name (caller, &name)) + return FALSE; + if (strcmp (name, dbus_name) != 0) + return FALSE; + return TRUE; +} + +static void +_remove_caller_by_dbus_name (PolKitTracker *pk_tracker, const char *dbus_name) +{ + g_hash_table_foreach_remove (pk_tracker->dbus_name_to_caller, + (GHRFunc) _remove_caller_by_dbus_name_iter, + (gpointer) dbus_name); +} + +/*--------------------------------------------------------------------------------------------------------------*/ + +/** + * polkit_tracker_dbus_func: + * @pk_tracker: the tracker object + * @message: message to pass + * + * The owner of the #PolKitTracker object must pass signals from the + * system message bus (just NameOwnerChanged will do) and all signals + * from the ConsoleKit service into this function. + */ +void +polkit_tracker_dbus_func (PolKitTracker *pk_tracker, DBusMessage *message) +{ + + if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + char *name; + char *new_service_name; + char *old_service_name; + + if (!dbus_message_get_args (message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_service_name, + DBUS_TYPE_STRING, &new_service_name, + DBUS_TYPE_INVALID)) { + + /* TODO: should be _pk_critical */ + _pk_debug ("The NameOwnerChanged signal on the " DBUS_INTERFACE_DBUS " " + "interface has the wrong signature! Your system is misconfigured."); + goto out; + } + + if (strlen (new_service_name) == 0) { + _remove_caller_by_dbus_name (pk_tracker, name); + } + + } else if (dbus_message_is_signal (message, "org.freedesktop.ConsoleKit.Session", "ActiveChanged")) { + dbus_bool_t is_active; + DBusError error; + const char *session_objpath; + + dbus_error_init (&error); + session_objpath = dbus_message_get_path (message); + if (!dbus_message_get_args (message, &error, + DBUS_TYPE_BOOLEAN, &is_active, + DBUS_TYPE_INVALID)) { + + /* TODO: should be _pk_critical */ + _pk_debug ("The ActiveChanged signal on the org.freedesktop.ConsoleKit.Session " + "interface for object %s has the wrong signature! " + "Your system is misconfigured.", session_objpath); + + /* as a security measure, remove all sessions with this path from the cache; + * cuz then the user of PolKitTracker probably gets to deal with a DBusError + * the next time he tries something... + */ + _remove_caller_by_session (pk_tracker, session_objpath); + goto out; + } + + /* now go through all Caller objects and update the is_active field as appropriate */ + _update_session_is_active (pk_tracker, session_objpath, is_active); + } + + /* TODO: when ConsoleKit gains the ability to attach/detach a session to a seat (think + * hot-desking), we want to update our local caches too + */ + +out: + ; +} + +/** + * polkit_tracker_get_caller_from_dbus_name: + * @pk_tracker: the tracker object + * @dbus_name: unique name on the system message bus + * @error: D-Bus error + * + * This function is similar to polkit_caller_new_from_dbus_name() + * except that it uses the cache in #PolKitTracker. So on the second + * and subsequent calls, for the same D-Bus name, there will be no + * syscall or IPC overhead in calling this function. + * + * Returns: A #PolKitCaller object; the caller must use + * polkit_caller_unref() on the object when done with it. Returns + * #NULL if an error occured (in which case error will be set). + */ +PolKitCaller * +polkit_tracker_get_caller_from_dbus_name (PolKitTracker *pk_tracker, const char *dbus_name, DBusError *error) +{ + PolKitCaller *caller; + + g_return_val_if_fail (pk_tracker != NULL, NULL); + g_return_val_if_fail (pk_tracker->con != NULL, NULL); + g_return_val_if_fail (! dbus_error_is_set (error), NULL); + + caller = g_hash_table_lookup (pk_tracker->dbus_name_to_caller, dbus_name); + if (caller != NULL) + return polkit_caller_ref (caller); + + g_debug ("Have to look up %s...", dbus_name); + + caller = polkit_caller_new_from_dbus_name (pk_tracker->con, dbus_name, error); + if (caller == NULL) + return NULL; + + g_hash_table_insert (pk_tracker->dbus_name_to_caller, g_strdup (dbus_name), caller); + return polkit_caller_ref (caller); +} diff --git a/polkit-dbus/polkit-dbus.h b/polkit-dbus/polkit-dbus.h index b39e091..d0fa9f6 100644 --- a/polkit-dbus/polkit-dbus.h +++ b/polkit-dbus/polkit-dbus.h @@ -38,6 +38,21 @@ PolKitCaller *polkit_caller_new_from_dbus_name (DBusConnection *con, const cha PolKitCaller *polkit_caller_new_from_pid (DBusConnection *con, pid_t pid, DBusError *error); + +struct _PolKitTracker; +typedef struct _PolKitTracker PolKitTracker; + +PolKitTracker *polkit_tracker_new (void); +PolKitTracker *polkit_tracker_ref (PolKitTracker *pk_tracker); +void polkit_tracker_unref (PolKitTracker *pk_tracker); +void polkit_tracker_set_system_bus_connection (PolKitTracker *pk_tracker, DBusConnection *con); +void polkit_tracker_init (PolKitTracker *pk_tracker); + +void polkit_tracker_dbus_func (PolKitTracker *pk_tracker, DBusMessage *message); + +PolKitCaller *polkit_tracker_get_caller_from_dbus_name (PolKitTracker *pk_tracker, const char *dbus_name, DBusError *error); + + #endif /* POLKIT_DBUS_H */ -- 2.7.4