diff --git a/mission-control-plugins/dbus-acl.c b/mission-control-plugins/dbus-acl.c index 652f18b..246fe8c 100644 --- a/mission-control-plugins/dbus-acl.c +++ b/mission-control-plugins/dbus-acl.c @@ -58,6 +58,8 @@ * A single object can implement more than one interface. */ +#include "config.h" + #include #include #include @@ -66,8 +68,8 @@ #ifdef ENABLE_DEBUG #define DEBUG(_p, _format, ...) \ - g_debug ("%s: %s: " _format, G_STRFUNC, \ - (_p != NULL) ? mcp_dbus_acl_name (_p) : "NULL", ##__VA_ARGS__) + g_debug ("dbus-acl: %s: %s: " _format, G_STRFUNC, \ + (_p != NULL) ? mcp_dbus_acl_name (_p) : "-", ##__VA_ARGS__) #else /* ENABLE_DEBUG */ @@ -245,7 +247,8 @@ mcp_dbus_acl_authorised (const TpDBusDaemon *dbus, DEBUG (plugin, "checking ACL for %s", name); - permitted = iface->authorised (plugin, dbus, context, type, name, params); + if (iface->authorised != NULL) + permitted = iface->authorised (plugin, dbus, context, type, name, params); if (!permitted) break; @@ -282,31 +285,33 @@ mcp_dbus_acl_authorised_async_step (DBusAclAuthData *ad, { if (permitted) { - if (ad->next_acl != NULL && ad->next_acl->data != NULL) + while (ad->next_acl != NULL && ad->next_acl->data != NULL) { McpDBusAcl *plugin = MCP_DBUS_ACL (ad->next_acl->data); McpDBusAclIface *iface = MCP_DBUS_ACL_GET_IFACE (plugin); if (ad->acl != NULL) - DEBUG (ad->acl, ":A: passed ACL for %s", ad->name); + DEBUG (ad->acl, "passed ACL for %s", ad->name); /* take the next plugin off the next_acl list */ ad->next_acl = g_list_next (ad->next_acl); ad->acl = plugin; - /* kick off the next async authoriser in the chain */ - iface->authorised_async (plugin, ad); + if (iface->authorised_async != NULL) + { + /* kick off the next async authoriser in the chain */ + iface->authorised_async (plugin, ad); - /* don't clean up, the next async acl will call us when it's done: */ - return; + /* don't clean up, the next async acl will call us when it's + * done: */ + return; + } } - else /* reached the end of the plugin list: call actual handler */ - { - if (ad->acl != NULL) - DEBUG (ad->acl, ":B: passed ACL for %s", ad->name); - ad->handler (ad->context, ad->data); - } + if (ad->acl != NULL) + DEBUG (ad->acl, "passed final ACL for %s", ad->name); + + ad->handler (ad->context, ad->data); } else { @@ -374,6 +379,9 @@ mcp_dbus_acl_authorised_async (TpDBusDaemon *dbus, ad->handler = handler; ad->next_acl = acls; + DEBUG (NULL, "DBus access ACL verification: %u rules for %s", + g_list_length (acls), + name); mcp_dbus_acl_authorised_async_step (ad, TRUE); } @@ -385,6 +393,9 @@ mcp_dbus_acl_name (const McpDBusAcl *self) g_return_val_if_fail (iface != NULL, FALSE); + if (iface->name == NULL) + return G_OBJECT_TYPE_NAME (self); + return iface->name; } @@ -395,5 +406,8 @@ mcp_dbus_acl_description (const McpDBusAcl *self) g_return_val_if_fail (iface != NULL, FALSE); + if (iface->desc == NULL) + return "(no description)"; + return iface->desc; } diff --git a/mission-control-plugins/dbus-acl.h b/mission-control-plugins/dbus-acl.h index 3ed3abc..8567b18 100644 --- a/mission-control-plugins/dbus-acl.h +++ b/mission-control-plugins/dbus-acl.h @@ -112,9 +112,9 @@ void mcp_dbus_acl_iface_implement_authorised (McpDBusAclIface *iface, void mcp_dbus_acl_iface_implement_authorised_async (McpDBusAclIface *iface, DBusAclAsyncAuthoriser method); -const gchar *mcp_dbus_acl_name (const McpDBusAcl *storage); +const gchar *mcp_dbus_acl_name (const McpDBusAcl *acl); -const gchar *mcp_dbus_acl_description (const McpDBusAcl *storage); +const gchar *mcp_dbus_acl_description (const McpDBusAcl *acl); G_END_DECLS diff --git a/mission-control-plugins/dispatch-operation-policy.c b/mission-control-plugins/dispatch-operation-policy.c index b0e2f36..e31be47 100644 --- a/mission-control-plugins/dispatch-operation-policy.c +++ b/mission-control-plugins/dispatch-operation-policy.c @@ -36,10 +36,7 @@ * #McpDispatchOperationPolicy, then return an instance of that subclass from * mcp_plugin_ref_nth_object(). * - * The contents of the #McpDispatchOperationPolicyIface struct are not public, - * so to provide an implementation of the check method, - * plugins should call mcp_dispatch_operation_policy_iface_implement_check() - * from the interface initialization function, like this: + * A typical plugin might look like this: * * * G_DEFINE_TYPE_WITH_CODE (MyPlugin, my_plugin, @@ -53,8 +50,9 @@ * cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface, * gpointer unused G_GNUC_UNUSED) * { - * mcp_dispatch_operation_policy_iface_implement_check (iface, - * my_plugin_check_cdo); + * iface->check = my_plugin_check_cdo; + * iface->handler_is_suitable_async = my_plugin_handler_is_suitable_async; + * iface->handler_is_suitable_finish = my_plugin_handler_is_suitable_finish; * } * * @@ -64,12 +62,6 @@ #include -struct _McpDispatchOperationPolicyIface { - GTypeInterface parent; - - void (*check) (McpDispatchOperationPolicy *, McpDispatchOperation *); -}; - GType mcp_dispatch_operation_policy_get_type (void) { @@ -102,6 +94,57 @@ mcp_dispatch_operation_policy_get_type (void) } /** + * McpDispatchOperationPolicyIface: + * @parent: the parent type + * @check: an implementation of mcp_dispatch_operation_policy_check(); + * %NULL is equivalent to an implementation that does nothing + * @handler_is_suitable_async: an implementation of + * mcp_dispatch_operation_policy_handler_is_suitable_async(); + * %NULL is treated as equivalent to an implementation that accepts + * every handler, i.e. always asynchronously returns %TRUE + * @handler_is_suitable_finish: an implementation of + * mcp_dispatch_operation_policy_handler_is_suitable_finish(); + * %NULL is treated as equivalent to an implementation that accepts any + * #GSimpleAsyncResult + */ + +/** + * McpDispatchOperationPolicyCb: + * @policy: an implementation of this interface, provided by a plugin + * @dispatch_operation: an object representing a dispatch operation, i.e. + * a bundle of channels being dispatched + * + * Signature of an implementation of mcp_dispatch_operation_policy_check(). + */ + +/** + * McpDispatchOperationPolicyHandlerIsSuitableAsync: + * @policy: an implementation of this interface, provided by a plugin + * @handler: a proxy for the Handler's D-Bus API, or %NULL if the Handler + * is calling Claim (so its well-known name is not immediately obvious) + * @unique_name: The Handler's unique name, or empty or %NULL if it has not yet + * been started + * @dispatch_operation: an object representing a dispatch operation, i.e. + * a bundle of channels being dispatched + * @callback: callback to be called on success or failure + * @user_data: user data for the callback + * + * Signature of mcp_dispatch_operation_policy_handler_is_suitable_async() + */ + +/** + * McpDispatchOperationPolicyFinisher: + * @policy: an implementation of this interface, provided by a plugin + * @result: the asynchronous result passed to a #GAsyncReadyCallback + * @error: (allow-none): used to return an error + * + * Signature of a virtual method used to finish an asynchronous operation + * that succeeds or fails, but does not return any additional value. + * + * Returns: %TRUE if the operation succeeded, %FALSE on error + */ + +/** * mcp_dispatch_operation_policy_check: * @policy: an implementation of this interface, provided by a plugin * @dispatch_operation: an object representing a dispatch operation, i.e. @@ -134,11 +177,102 @@ mcp_dispatch_operation_policy_check (McpDispatchOperationPolicy *policy, * @iface: the interface * @impl: an implementation of the virtual method * mcp_dispatch_operation_policy_check() + * + * This method is no longer necessary: just set iface->check = impl instead. */ void mcp_dispatch_operation_policy_iface_implement_check ( McpDispatchOperationPolicyIface *iface, - void (*impl) (McpDispatchOperationPolicy *, McpDispatchOperation *)) + McpDispatchOperationPolicyCb impl) { iface->check = impl; } + +/** + * mcp_dispatch_operation_policy_handler_is_suitable_async: + * @policy: an implementation of this interface, provided by a plugin + * @handler: a proxy for the Handler's D-Bus API, or %NULL if the Handler + * is calling Claim (so its well-known name is not immediately obvious) + * @unique_name: The Handler's unique name, or empty or %NULL if it has not yet + * been started + * @dispatch_operation: an object representing a dispatch operation, i.e. + * a bundle of channels being dispatched + * @callback: callback to be called on success or failure + * @user_data: user data for the callback + * + * Check whether a handler is "suitable" for these channels. For instance, + * this could be used to ensure that only the platform's default UI can be + * used for particular channels, even if MC would normally consider + * a third-party UI to be a better match. + * + * Mission Control calls all implementations of this method in parallel + * and waits for them all to return. If any of them raises an error, + * the handler is considered to be unsuitable. + */ +void +mcp_dispatch_operation_policy_handler_is_suitable_async ( + McpDispatchOperationPolicy *policy, + TpClient *handler, + const gchar *unique_name, + McpDispatchOperation *dispatch_operation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + McpDispatchOperationPolicyIface *iface = + MCP_DISPATCH_OPERATION_POLICY_GET_IFACE (policy); + + g_return_if_fail (iface != NULL); + + if (iface->handler_is_suitable_async != NULL) + { + iface->handler_is_suitable_async (policy, handler, unique_name, + dispatch_operation, callback, user_data); + } + else + { + /* unimplemented: the default is to succeed */ + GSimpleAsyncResult *simple = g_simple_async_result_new ( + (GObject *) policy, callback, user_data, + mcp_dispatch_operation_policy_handler_is_suitable_async); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + } +} + +/** + * @policy: an implementation of this interface, provided by a plugin + * @result: the asynchronous result passed to the #GAsyncReadyCallback + * @error: (allow-none): used to return an error + * + * Finish a call to mcp_dispatch_operation_policy_handler_is_suitable_async(). + * + * Returns: %TRUE if the handler is suitable; %FALSE if the handler is + * unsuitable or there was an error + */ +gboolean +mcp_dispatch_operation_policy_handler_is_suitable_finish ( + McpDispatchOperationPolicy *policy, + GAsyncResult *result, + GError **error) +{ + McpDispatchOperationPolicyIface *iface = + MCP_DISPATCH_OPERATION_POLICY_GET_IFACE (policy); + + g_return_val_if_fail (iface != NULL, FALSE); + + if (iface->handler_is_suitable_finish != NULL) + { + return iface->handler_is_suitable_finish (policy, result, error); + } + else + { + /* accept any GSimpleAsyncResult regardless of source tag, so we can + * use it with the default implementation of _async or with most + * user-supplied implementations */ + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); + + return !g_simple_async_result_propagate_error ( + (GSimpleAsyncResult *) result, error); + } +} diff --git a/mission-control-plugins/dispatch-operation-policy.h b/mission-control-plugins/dispatch-operation-policy.h index 8f0f7f4..a558721 100644 --- a/mission-control-plugins/dispatch-operation-policy.h +++ b/mission-control-plugins/dispatch-operation-policy.h @@ -27,6 +27,8 @@ #include +#include + G_BEGIN_DECLS /* API for plugins to implement */ @@ -48,13 +50,49 @@ typedef struct _McpDispatchOperationPolicyIface McpDispatchOperationPolicyIface; GType mcp_dispatch_operation_policy_get_type (void) G_GNUC_CONST; /* virtual methods */ + +typedef void (*McpDispatchOperationPolicyCb) ( + McpDispatchOperationPolicy *policy, + McpDispatchOperation *dispatch_operation); + void mcp_dispatch_operation_policy_check (McpDispatchOperationPolicy *policy, McpDispatchOperation *dispatch_operation); -/* vtable manipulation - the vtable is private to allow for expansion */ +typedef void (*McpDispatchOperationPolicyHandlerIsSuitableAsync) ( + McpDispatchOperationPolicy *policy, + TpClient *handler, + const gchar *unique_name, + McpDispatchOperation *dispatch_operation, + GAsyncReadyCallback callback, + gpointer user_data); +typedef gboolean (*McpDispatchOperationPolicyFinisher) ( + McpDispatchOperationPolicy *policy, + GAsyncResult *result, + GError **error); + +void mcp_dispatch_operation_policy_handler_is_suitable_async ( + McpDispatchOperationPolicy *policy, + TpClient *handler, + const gchar *unique_name, + McpDispatchOperation *dispatch_operation, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mcp_dispatch_operation_policy_handler_is_suitable_finish ( + McpDispatchOperationPolicy *policy, + GAsyncResult *result, + GError **error); + void mcp_dispatch_operation_policy_iface_implement_check ( McpDispatchOperationPolicyIface *iface, - void (*impl) (McpDispatchOperationPolicy *, McpDispatchOperation *)); + McpDispatchOperationPolicyCb impl); + +struct _McpDispatchOperationPolicyIface { + GTypeInterface parent; + + McpDispatchOperationPolicyCb check; + McpDispatchOperationPolicyHandlerIsSuitableAsync handler_is_suitable_async; + McpDispatchOperationPolicyFinisher handler_is_suitable_finish; +}; G_END_DECLS diff --git a/plugins/Makefile.am b/plugins/Makefile.am index d801724..23ccc6c 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -7,21 +7,14 @@ INCLUDES = \ -DLIBDIR="@libdir@" \ -DLIBVERSION="@MCP_ABI_VERSION@" -EXTRA_DIST = - -instdir = @libdir@/mission-control-plugins.@MCP_ABI_VERSION@ - -plugin_LDFLAGS = -module -shared -avoid-version -rpath @abs_builddir@ - -inst_LTLIBRARIES = +noinst_LTLIBRARIES = if ENABLE_AEGIS -inst_LTLIBRARIES += mcp-dbus-aegis-acl.la - -mcp_dbus_aegis_acl_la_SOURCES = mcp-dbus-aegis-acl.c -mcp_dbus_aegis_acl_la_LDFLAGS = \ - $(plugin_LDFLAGS) \ +noinst_LTLIBRARIES += libmcp-aegis.la +libmcp_aegis_la_SOURCES = mcp-dbus-aegis-acl.c +libmcp_aegis_la_LIBADD = \ + $(top_builddir)/mission-control-plugins/libmission-control-plugins.la \ $(TELEPATHY_LIBS) \ $(DBUS_LIBS) \ $(AEGIS_LIBS) diff --git a/plugins/mcp-dbus-aegis-acl.c b/plugins/mcp-dbus-aegis-acl.c index 71d953c..d639e36 100644 --- a/plugins/mcp-dbus-aegis-acl.c +++ b/plugins/mcp-dbus-aegis-acl.c @@ -1,5 +1,5 @@ /* - * An Aegis/libcreds plugin that checks the caller's permission tokens + * A pseudo-plugin that checks the caller's Aegis permission tokens * * Copyright © 2010-2011 Nokia Corporation * Copyright © 2010-2011 Collabora Ltd. @@ -19,11 +19,39 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "config.h" + +#ifdef G_LOG_DOMAIN +#undef G_LOG_DOMAIN +#endif +#define G_LOG_DOMAIN "mission-control-DBus-Access-ACL" + +#ifdef ENABLE_DEBUG +#define DEBUG(_f, ...) g_debug ("%s: " _f, G_STRLOC, ##__VA_ARGS__) +#else +#define DEBUG(_f, ...) do {} while (0) +#endif + +#include +#include +#include +#include + #include + #include #include -#include -#include + +typedef struct _AegisAcl AegisAcl; +typedef struct _AegisAclClass AegisAclClass; + +struct _AegisAcl { + GObject parent; +}; + +struct _AegisAclClass { + GObjectClass parent_class; +}; #define CREATE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".CreateChannel" #define ENSURE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".EnsureChannel" @@ -32,7 +60,8 @@ #define AEGIS_CALL_TOKEN "Cellular" -#define DEBUG g_debug +/* implemented by the Aegis-patched dbus-daemon */ +#define AEGIS_INTERFACE "com.meego.DBus.Creds" #define PLUGIN_NAME "dbus-aegis-acl" #define PLUGIN_DESCRIPTION \ @@ -40,63 +69,52 @@ "associated with the calling process ID and determine whether " \ "the DBus call or property access should be allowed" -static gboolean token_initialised = FALSE; static creds_value_t aegis_token = CREDS_BAD; static creds_type_t aegis_type = CREDS_BAD; -static gchar *restricted[] = - { - CREATE_CHANNEL, - ENSURE_CHANNEL, - SEND_MESSAGE, - NULL - }; - -static void dbus_acl_iface_init (McpDBusAclIface *, +static void aegis_acl_iface_init (McpDBusAclIface *, + gpointer); +static void aegis_cdo_policy_iface_init (McpDispatchOperationPolicyIface *, gpointer); -typedef struct { - GObject parent; -} DBusAegisAcl; - -typedef struct { - GObjectClass parent_class; - creds_value_t token; - creds_type_t token_type; -} DBusAegisAclClass; - -GType dbus_aegis_acl_get_type (void) G_GNUC_CONST; - -#define DBUS_AEGIS_ACL(o) \ - (G_TYPE_CHECK_INSTANCE_CAST ((o), dbus_aegis_acl_get_type (), \ - DBusAegisAcl)) +static GType aegis_acl_get_type (void); -G_DEFINE_TYPE_WITH_CODE (DBusAegisAcl, dbus_aegis_acl, +G_DEFINE_TYPE_WITH_CODE (AegisAcl, aegis_acl, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, dbus_acl_iface_init)); + G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, aegis_acl_iface_init); + G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY, + aegis_cdo_policy_iface_init)) static void -dbus_aegis_acl_init (DBusAegisAcl *self) +aegis_acl_init (AegisAcl *self) { } static void -dbus_aegis_acl_class_init (DBusAegisAclClass *cls) +aegis_acl_class_init (AegisAclClass *cls) { - if (token_initialised == TRUE) + if (aegis_type != CREDS_BAD) return; aegis_type = creds_str2creds (AEGIS_CALL_TOKEN, &aegis_token); } +static gchar *restricted_methods[] = + { + CREATE_CHANNEL, + ENSURE_CHANNEL, + SEND_MESSAGE, + NULL + }; + static gboolean method_is_filtered (const gchar *method) { guint i; - for (i = 0; restricted[i] != NULL; i++) + for (i = 0; restricted_methods[i] != NULL; i++) { - if (!tp_strdiff (method, restricted[i])) + if (!tp_strdiff (method, restricted_methods[i])) return TRUE; } @@ -139,23 +157,76 @@ is_filtered (DBusAclType type, return FALSE; } +/* For simplicity we don't implement non-trivial conversion between + * dbus-glib's arrays of guint, and libcreds' arrays of uint32_t. + * If this assertion fails on your platform, you'll need to implement it. */ +G_STATIC_ASSERT (sizeof (guint) == sizeof (uint32_t)); + static gboolean -pid_is_permitted (const McpDBusAcl *self, pid_t pid) +caller_creds_are_enough (const gchar *name, + const GArray *au) { - gboolean ok = FALSE; + creds_t caller_creds = creds_import ((const uint32_t *) au->data, au->len); + gboolean ok = creds_have_p (caller_creds, aegis_type, aegis_token); - if (pid != 0) +#ifdef ENABLE_DEBUG + if (ok) + { + DEBUG ("Caller %s is appropriately privileged", name); + } + else { - creds_t caller = creds_gettask (pid); + char buf[1024]; + creds_type_t debug_type; + creds_value_t debug_value; + int i = 0; - DEBUG ("creds_have_p (creds_gettask (%d) -> %p, %d, %ld)", - pid, caller, aegis_type, aegis_token); - ok = creds_have_p (caller, aegis_type, aegis_token); - DEBUG (" --> %s", ok ? "TRUE" : "FALSE"); + DEBUG ("Caller %s has these credentials:", name); - creds_free (caller); + while ((debug_type = creds_list (caller_creds, i, &debug_value)) + != CREDS_BAD) + { + creds_creds2str (debug_type, debug_value, buf, sizeof (buf)); + DEBUG ("- %s", buf); + } + + DEBUG ("but they are insufficient"); } +#endif + creds_free (caller_creds); + return ok; +} + +static gboolean +check_peer_creds_sync (DBusGConnection *dgc, + const gchar *bus_name) +{ + DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + AEGIS_INTERFACE); + GArray *au = NULL; + GError *error = NULL; + gboolean ok; + + if (dbus_g_proxy_call (proxy, "GetConnectionCredentials", &error, + G_TYPE_STRING, bus_name, + G_TYPE_INVALID, + DBUS_TYPE_G_UINT_ARRAY, &au, + G_TYPE_INVALID)) + { + ok = caller_creds_are_enough (bus_name, au); + g_array_unref (au); + } + else + { + DEBUG ("GetConnectionCredentials failed: %s", error->message); + g_clear_error (&error); + ok = FALSE; + } + + g_object_unref (proxy); return ok; } @@ -172,24 +243,11 @@ caller_authorised (const McpDBusAcl *self, if (is_filtered (type, name, params)) { - pid_t pid = 0; - GError *error = NULL; gchar *caller = dbus_g_method_get_sender ((DBusGMethodInvocation *) call); - DBusGProxy *proxy = dbus_g_proxy_new_for_name (dgc, - DBUS_SERVICE_DBUS, - DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS); - dbus_g_proxy_call (proxy, "GetConnectionUnixProcessID", &error, - G_TYPE_STRING, caller, - G_TYPE_INVALID, - G_TYPE_UINT, &pid, - G_TYPE_INVALID); - - ok = pid_is_permitted (self, pid); + ok = check_peer_creds_sync (dgc, caller); g_free (caller); - g_object_unref (proxy); } DEBUG ("sync Aegis ACL check [%s]", ok ? "Allowed" : "Forbidden"); @@ -204,24 +262,30 @@ async_authorised_cb (DBusGProxy *proxy, { GError *error = NULL; DBusAclAuthData *ad = data; - pid_t pid = 0; + GArray *au = NULL; const McpDBusAcl *self = ad->acl; gboolean permitted = FALSE; - /* if this returns FALSE, there's no PID, which means something bizarre * - * and untrustowrthy is going on, which in turn means we must deny: can't * - * authorise without first authenticating */ + /* if this returns FALSE, there are no credentials, which means something + * untrustworthy is going on, which in turn means we must deny: can't + * authorise without first authenticating */ permitted = dbus_g_proxy_end_call (proxy, call, &error, - G_TYPE_UINT, &pid, + DBUS_TYPE_G_UINT_ARRAY, &au, G_TYPE_INVALID); if (permitted) - permitted = pid_is_permitted (self, pid); + { + permitted = caller_creds_are_enough (ad->name, au); + g_array_unref (au); + } else - g_error_free (error); + { + DEBUG ("GetConnectionCredentials failed: %s", error->message); + g_clear_error (&error); + } - DEBUG ("finished async Aegis ACL check [%u -> %s]", - pid, permitted ? "Allowed" : "Forbidden"); + DEBUG ("finished async Aegis ACL check [%s]", + permitted ? "Allowed" : "Forbidden"); mcp_dbus_acl_authorised_async_step (ad, permitted); @@ -244,9 +308,9 @@ caller_async_authorised (const McpDBusAcl *self, proxy = dbus_g_proxy_new_for_name (dgc, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, - DBUS_INTERFACE_DBUS); + AEGIS_INTERFACE); - dbus_g_proxy_begin_call (proxy, "GetConnectionUnixProcessID", + dbus_g_proxy_begin_call (proxy, "GetConnectionCredentials", async_authorised_cb, data, NULL, @@ -263,7 +327,7 @@ caller_async_authorised (const McpDBusAcl *self, static void -dbus_acl_iface_init (McpDBusAclIface *iface, +aegis_acl_iface_init (McpDBusAclIface *iface, gpointer unused G_GNUC_UNUSED) { mcp_dbus_acl_iface_set_name (iface, PLUGIN_NAME); @@ -273,18 +337,80 @@ dbus_acl_iface_init (McpDBusAclIface *iface, mcp_dbus_acl_iface_implement_authorised_async (iface, caller_async_authorised); } -GObject * -mcp_plugin_ref_nth_object (guint n) +static gchar *restricted_cms[] = { "ring", "mmscm", NULL }; + +static inline gboolean +cm_is_restricted (const gchar *cm_name) { - DEBUG ("Initializing mcp-dbus-caller-id plugin (n=%u)", n); + guint i; - switch (n) + for (i = 0; restricted_cms[i] != NULL; i++) { - case 0: - return g_object_new (dbus_aegis_acl_get_type (), NULL); + if (!tp_strdiff (restricted_cms[i], cm_name)) + return TRUE; + } + + return FALSE; +} - default: - return NULL; +static void +handler_is_suitable_async (McpDispatchOperationPolicy *self, + TpClient *recipient, + const gchar *unique_name, + McpDispatchOperation *dispatch_op, + GAsyncReadyCallback callback, + gpointer user_data) +{ + const gchar *manager = mcp_dispatch_operation_get_cm_name (dispatch_op); + GSimpleAsyncResult *simple = g_simple_async_result_new ((GObject *) self, + callback, user_data, handler_is_suitable_async); + gboolean ok = TRUE; + + if (cm_is_restricted (manager)) + { + TpDBusDaemon *dbus = tp_dbus_daemon_dup (NULL); + + /* if MC started successfully, we ought to have one */ + g_assert (dbus != NULL); + + if (!tp_str_empty (unique_name)) + { + ok = check_peer_creds_sync (tp_proxy_get_dbus_connection (dbus), + unique_name); + } + else + { + g_assert (recipient != NULL); + + ok = check_peer_creds_sync (tp_proxy_get_dbus_connection (dbus), + tp_proxy_get_bus_name (recipient)); + } + + if (!ok) + { + g_simple_async_result_set_error (simple, TP_ERRORS, + TP_ERROR_PERMISSION_DENIED, "insufficient Aegis credentials"); + } + + g_object_unref (dbus); } + + DEBUG ("sync Aegis CDO policy check [%s]", ok ? "Allowed" : "Forbidden"); + + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); +} + +static void +aegis_cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface, + gpointer unused G_GNUC_UNUSED) +{ + iface->handler_is_suitable_async = handler_is_suitable_async; + /* the default finish function accepts our GSimpleAsyncResult */ } +GObject * +aegis_acl_new (void) +{ + return g_object_new (aegis_acl_get_type (), NULL); +} diff --git a/plugins/mcp-dbus-aegis-acl.h b/plugins/mcp-dbus-aegis-acl.h new file mode 100644 index 0000000..96baaf4 --- /dev/null +++ b/plugins/mcp-dbus-aegis-acl.h @@ -0,0 +1,34 @@ +/* + * A pseudo-plugin that checks the caller's Aegis permission tokens + * + * Copyright © 2010-2011 Nokia Corporation + * Copyright © 2010-2011 Collabora Ltd. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AEGIS_ACL_H +#define AEGIS_ACL_H + +#include +#include + +G_BEGIN_DECLS + +GObject *aegis_acl_new (void); + +G_END_DECLS + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 860374c..dbc10d3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -117,6 +117,10 @@ libmcd_convenience_la_LIBADD += $(LIBACCOUNTS_SSO_LIBS) INCLUDES += $(LIBACCOUNTS_SSO_CFLAGS) endif +if ENABLE_AEGIS +libmcd_convenience_la_LIBADD += $(top_builddir)/plugins/libmcp-aegis.la +endif + noinst_LTLIBRARIES = libmcd-convenience.la if ENABLE_MCD_PLUGINS diff --git a/src/mcd-dispatch-operation.c b/src/mcd-dispatch-operation.c index bcc31f5..8f79d5b 100644 --- a/src/mcd-dispatch-operation.c +++ b/src/mcd-dispatch-operation.c @@ -243,6 +243,13 @@ struct _McdDispatchOperationPrivate * A reference is held for each pending approver. */ gsize ado_pending; + /* The number of plugins whose decision we're waiting for, + * regarding whether a handler is in fact suitable. */ + gsize handler_suitable_pending; + + /* If non-NULL, a plugin has decided the selected handler is unsuitable. */ + GError *handler_unsuitable; + /* If TRUE, we're dispatching a channel request and it was cancelled */ gboolean cancelled; @@ -250,9 +257,9 @@ struct _McdDispatchOperationPrivate * after observers */ gboolean observe_only; - /* If TRUE, we're in the middle of calling HandleChannels. This is a - * client lock. */ - gboolean calling_handle_channels; + /* If non-NULL, we're in the middle of asking plugins whether we may call + * HandleChannels, or doing so. This is a client lock. */ + McdClientProxy *trying_handler; /* If TRUE, we've tried all the BypassApproval handlers, which happens * before we run approvers. */ @@ -457,9 +464,9 @@ _mcd_dispatch_operation_check_client_locks (McdDispatchOperation *self) /* if we've called one Handler, we may not continue until it responds * with an error */ - if (self->priv->calling_handle_channels) + if (self->priv->trying_handler != NULL) { - DEBUG ("waiting for HandleChannels to return"); + DEBUG ("waiting for handler_is_suitable or HandleChannels to return"); return; } @@ -917,27 +924,97 @@ dispatch_operation_handle_with (TpSvcChannelDispatchOperation *cdo, context); } +typedef struct { + McdDispatchOperation *self; + DBusGMethodInvocation *context; + gsize handler_suitable_pending; +} ClaimAttempt; + +static void +claim_attempt_resolve (ClaimAttempt *claim_attempt) +{ + if (claim_attempt->context != NULL) + { + g_queue_push_tail (claim_attempt->self->priv->approvals, + approval_new_claim (claim_attempt->context)); + _mcd_dispatch_operation_check_client_locks (claim_attempt->self); + } + + g_object_unref (claim_attempt->self); + g_slice_free (ClaimAttempt, claim_attempt); +} + +static void +claim_attempt_suitability_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + ClaimAttempt *claim_attempt = user_data; + GError *error = NULL; + + if (!mcp_dispatch_operation_policy_handler_is_suitable_finish ( + MCP_DISPATCH_OPERATION_POLICY (source), res, &error)) + { + if (claim_attempt->context != NULL) + dbus_g_method_return_error (claim_attempt->context, error); + + claim_attempt->context = NULL; + g_error_free (error); + } + + if (--claim_attempt->handler_suitable_pending == 0) + { + DEBUG ("all plugins have finished, resolving claim attempt"); + claim_attempt_resolve (claim_attempt); + } +} + static void dispatch_operation_claim (TpSvcChannelDispatchOperation *cdo, DBusGMethodInvocation *context) { McdDispatchOperation *self = MCD_DISPATCH_OPERATION (cdo); - McdDispatchOperationPrivate *priv = self->priv; + ClaimAttempt *claim_attempt; + gchar *sender = dbus_g_method_get_sender (context); + McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION ( + self->priv->plugin_api); + const GList *p; if (self->priv->result != NULL) { - gchar *sender = dbus_g_method_get_sender (context); - DEBUG ("Giving error to %s: %s", sender, priv->result->message); - dbus_g_method_return_error (context, priv->result); + DEBUG ("Giving error to %s: %s", sender, self->priv->result->message); + dbus_g_method_return_error (context, self->priv->result); + goto finally; + } - g_free (sender); + claim_attempt = g_slice_new0 (ClaimAttempt); + claim_attempt->self = g_object_ref (self); + claim_attempt->context = context; + claim_attempt->handler_suitable_pending = 0; - return; + for (p = mcp_list_objects (); p != NULL; p = g_list_next (p)) + { + if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data)) + { + McpDispatchOperationPolicy *plugin = p->data; + + DEBUG ("%s: checking policy for %s", + G_OBJECT_TYPE_NAME (plugin), sender); + + claim_attempt->handler_suitable_pending++; + mcp_dispatch_operation_policy_handler_is_suitable_async (plugin, + NULL, sender, plugin_api, + claim_attempt_suitability_cb, + claim_attempt); + } } - g_queue_push_tail (priv->approvals, approval_new_claim (context)); - _mcd_dispatch_operation_check_client_locks (self); + if (claim_attempt->handler_suitable_pending == 0) + claim_attempt_resolve (claim_attempt); + +finally: + g_free (sender); } static void @@ -1881,7 +1958,7 @@ _mcd_dispatch_operation_handle_channels_cb (TpClient *client, tp_proxy_get_bus_name (client)); } - self->priv->calling_handle_channels = FALSE; + tp_clear_object (&self->priv->trying_handler); _mcd_dispatch_operation_check_client_locks (self); } @@ -2215,14 +2292,29 @@ _mcd_dispatch_operation_run_clients (McdDispatchOperation *self) * Invoke the handler for the given channels. */ static void -mcd_dispatch_operation_handle_channels (McdDispatchOperation *self, - McdClientProxy *handler) +mcd_dispatch_operation_handle_channels (McdDispatchOperation *self) { GHashTable *handler_info; GHashTable *request_properties; - g_assert (!self->priv->calling_handle_channels); - self->priv->calling_handle_channels = TRUE; + g_assert (self->priv->trying_handler != NULL); + + if (self->priv->handler_unsuitable != NULL) + { + GError *tmp = self->priv->handler_unsuitable; + + /* move the error out of the way first, in case the callback + * tries a different handler which will also want to check + * handler_unsuitable */ + self->priv->handler_unsuitable = NULL; + + _mcd_dispatch_operation_handle_channels_cb ( + (TpClient *) self->priv->trying_handler, + tmp, self, NULL); + g_error_free (tmp); + + return; + } handler_info = tp_asv_new (NULL, NULL); collect_satisfied_requests (self->priv->channels, NULL, @@ -2231,7 +2323,7 @@ mcd_dispatch_operation_handle_channels (McdDispatchOperation *self, MC_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP, request_properties); request_properties = NULL; - _mcd_client_proxy_handle_channels (handler, + _mcd_client_proxy_handle_channels (self->priv->trying_handler, -1, self->priv->channels, self->priv->handle_with_time, handler_info, _mcd_dispatch_operation_handle_channels_cb, g_object_ref (self), g_object_unref, NULL); @@ -2239,6 +2331,76 @@ mcd_dispatch_operation_handle_channels (McdDispatchOperation *self, g_hash_table_unref (handler_info); } +static void +mcd_dispatch_operation_handler_decision_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + McdDispatchOperation *self = user_data; + GError *error = NULL; + + if (!mcp_dispatch_operation_policy_handler_is_suitable_finish ( + MCP_DISPATCH_OPERATION_POLICY (source), res, &error)) + { + /* ignore any errors after the first */ + if (self->priv->handler_unsuitable == NULL) + g_propagate_error (&self->priv->handler_unsuitable, error); + else + g_error_free (error); + } + + if (--self->priv->handler_suitable_pending == 0) + { + mcd_dispatch_operation_handle_channels (self); + } + + g_object_unref (self); +} + +static void +mcd_dispatch_operation_try_handler (McdDispatchOperation *self, + McdClientProxy *handler) +{ + TpClient *handler_client = (TpClient *) handler; + const GList *p; + McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION ( + self->priv->plugin_api); + + g_assert (self->priv->trying_handler == NULL); + self->priv->trying_handler = g_object_ref (handler); + + self->priv->handler_suitable_pending = 0; + + DEBUG ("%s: channel ACL verification [%u channels]", + self->priv->unique_name, + g_list_length (self->priv->channels)); + + for (p = mcp_list_objects (); p != NULL; p = g_list_next (p)) + { + if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data)) + { + McpDispatchOperationPolicy *plugin = p->data; + + DEBUG ("%s: checking policy for %s", + G_OBJECT_TYPE_NAME (plugin), + tp_proxy_get_object_path (handler)); + + self->priv->handler_suitable_pending++; + mcp_dispatch_operation_policy_handler_is_suitable_async (plugin, + handler_client, + _mcd_client_proxy_get_unique_name (handler), + plugin_api, + mcd_dispatch_operation_handler_decision_cb, + g_object_ref (self)); + } + } + + if (self->priv->handler_suitable_pending == 0) + { + mcd_dispatch_operation_handle_channels (self); + } +} + static gboolean _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self) { @@ -2269,7 +2431,7 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self) if (handler != NULL && (approval->type == APPROVAL_TYPE_HANDLE_WITH || !failed)) { - mcd_dispatch_operation_handle_channels (self, handler); + mcd_dispatch_operation_try_handler (self, handler); return TRUE; } @@ -2278,11 +2440,14 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self) * can legitimately try more handlers. */ if (approval->type == APPROVAL_TYPE_HANDLE_WITH) { - GError gone = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + GError gone = { TP_ERRORS, + TP_ERROR_NOT_IMPLEMENTED, "The requested Handler does not exist" }; g_queue_pop_head (self->priv->approvals); + dbus_g_method_return_error (approval->context, &gone); + approval->context = NULL; approval_free (approval); return TRUE; @@ -2304,7 +2469,7 @@ _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self) if (handler != NULL && !failed && (is_approved || _mcd_client_proxy_get_bypass_approval (handler))) { - mcd_dispatch_operation_handle_channels (self, handler); + mcd_dispatch_operation_try_handler (self, handler); return TRUE; } } diff --git a/src/mcd-manager.c b/src/mcd-manager.c index d7911e9..9df13ce 100644 --- a/src/mcd-manager.c +++ b/src/mcd-manager.c @@ -220,10 +220,8 @@ _mcd_manager_constructor (GType type, guint n_params, { GObjectClass *object_class = (GObjectClass *)mcd_manager_parent_class; McdManager *manager; - McdManagerPrivate *priv; manager = MCD_MANAGER (object_class->constructor (type, n_params, params)); - priv = manager->priv; g_return_val_if_fail (manager != NULL, NULL); diff --git a/src/plugin-account.c b/src/plugin-account.c index 1680914..cfb83ab 100644 --- a/src/plugin-account.c +++ b/src/plugin-account.c @@ -303,6 +303,9 @@ sort_and_cache_plugins () const GList *p; static gboolean plugins_cached = FALSE; + if (plugins_cached) + return; + /* not guaranteed to have been called, but idempotent: */ _mcd_plugin_loader_init (); diff --git a/src/plugin-loader.c b/src/plugin-loader.c index 35e3625..7062c3a 100644 --- a/src/plugin-loader.c +++ b/src/plugin-loader.c @@ -25,6 +25,12 @@ #include +#include "mcd-debug.h" + +#if ENABLE_AEGIS +#include "plugins/mcp-dbus-aegis-acl.h" +#endif + static gsize ready = 0; void @@ -32,12 +38,25 @@ _mcd_plugin_loader_init (void) { if (g_once_init_enter (&ready)) { +#if ENABLE_AEGIS + GObject *pseudo_plugin; +#endif const gchar *dir = g_getenv ("MC_FILTER_PLUGIN_DIR"); if (dir == NULL) dir = MCD_PLUGIN_LOADER_DIR; mcp_read_dir (dir); + +#if ENABLE_AEGIS + /* The last object added by mcp_add_object() will be treated as highest + * priority, at least for the interfaces used here */ + DEBUG ("Initialising built-in Aegis ACL plugin"); + pseudo_plugin = G_OBJECT (aegis_acl_new ()); + mcp_add_object (pseudo_plugin); + g_object_unref (pseudo_plugin); +#endif + g_once_init_leave (&ready, 1); } } diff --git a/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py index 50bc803..eeab38c 100644 --- a/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py +++ b/tests/twisted/dispatcher/dispatch-delayed-by-mini-plugin.py @@ -174,10 +174,24 @@ def test(q, bus, mc): q.dbus_return(e.message, signature='') q.dbus_return(k.message, signature='') + empathy_cdo = bus.get_object(cs.CD, cdo_path) + empathy_cdo_iface = dbus.Interface(empathy_cdo, cs.CDO) + call_async(q, empathy_cdo_iface, 'Claim') + + check_handler = q.expect('dbus-method-call', path='/com/example/Policy', + interface='com.example.Policy', method='CheckHandler') + q.dbus_raise(check_handler.message, 'com.example.Errors.No', + "That handler doesn't have enough options") + q.expect('dbus-error', method='Claim', name=cs.PERMISSION_DENIED) + kopete_cdo = bus.get_object(cs.CD, cdo_path) kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO) call_async(q, kopete_cdo_iface, 'Claim') + check_handler = q.expect('dbus-method-call', path='/com/example/Policy', + interface='com.example.Policy', method='CheckHandler') + q.dbus_return(check_handler.message, signature='') + q.expect_many( EventPattern('dbus-signal', path=cdo_path, signal='Finished'), EventPattern('dbus-signal', path=cs.CD_PATH, @@ -187,6 +201,67 @@ def test(q, bus, mc): sync_dbus(bus, q, mc) + # Try again; this time we'll reject a selected handler + e, chan, cdo_path = signal_channel_expect_query(q, bus, account, conn, + empathy, kopete) + + # The request is fine, continue... + q.dbus_return(e.message, signature='') + + e, k = q.expect_many( + EventPattern('dbus-method-call', + path=empathy.object_path, + interface=cs.APPROVER, method='AddDispatchOperation', + handled=False), + EventPattern('dbus-method-call', + path=kopete.object_path, + interface=cs.APPROVER, method='AddDispatchOperation', + handled=False), + ) + q.dbus_return(e.message, signature='') + q.dbus_return(k.message, signature='') + + kopete_cdo = bus.get_object(cs.CD, cdo_path) + kopete_cdo_iface = dbus.Interface(kopete_cdo, cs.CDO) + call_async(q, kopete_cdo_iface, 'HandleWith', + cs.tp_name_prefix + '.Client.Kopete') + + check_handler = q.expect('dbus-method-call', path='/com/example/Policy', + interface='com.example.Policy', method='CheckHandler') + q.dbus_raise(check_handler.message, 'com.example.Errors.No', + 'That handler is not good enough') + q.expect('dbus-error', method='HandleWith', name=cs.PERMISSION_DENIED) + + # well, let's try *something*... Kopete has been marked as failed, + # so this will try Empathy + call_async(q, kopete_cdo_iface, 'HandleWith', '') + + check_handler = q.expect('dbus-method-call', path='/com/example/Policy', + interface='com.example.Policy', method='CheckHandler') + q.dbus_raise(check_handler.message, 'com.example.Errors.No', + 'That handler is no good either') + + # Oops... we ran out of handlers + _, _, _, e = q.expect_many( + EventPattern('dbus-error', method='HandleWith', + name=cs.PERMISSION_DENIED), + EventPattern('dbus-signal', path=cdo_path, + interface=cs.CDO, signal='Finished'), + EventPattern('dbus-signal', path=cs.CD_PATH, + interface=cs.CD_IFACE_OP_LIST, + signal='DispatchOperationFinished', + args=[cdo_path]), + EventPattern('dbus-method-call', + path=chan.object_path, + interface=cs.CHANNEL_IFACE_DESTROYABLE, + method='Destroy', + handled=False), + ) + q.dbus_return(e.message, signature='') + chan.close() + + sync_dbus(bus, q, mc) + # From now on we no longer want to forbid HandleChannels, but we do want # to forbid AddDispatchOperation q.unforbid_events(forbidden) @@ -231,6 +306,14 @@ def test(q, bus, mc): q.dbus_return(policy_request.message, signature='') + # Now we want to pass the channel to the selected handler. + # What does the policy service think about that? + check_handler = q.expect('dbus-method-call', path='/com/example/Policy', + interface='com.example.Policy', method='CheckHandler') + + # Yeah, we're OK with that. + q.dbus_return(check_handler.message, signature='') + e = q.expect('dbus-method-call', path=kopete.object_path, interface=cs.HANDLER, method='HandleChannels', diff --git a/tests/twisted/mcp-plugin.c b/tests/twisted/mcp-plugin.c index 53aa173..67e3a8f 100644 --- a/tests/twisted/mcp-plugin.c +++ b/tests/twisted/mcp-plugin.c @@ -30,6 +30,35 @@ #define DEBUG g_debug +/* ------ TestNoOpPlugin -------------------------------------- */ +/* doesn't implement anything, to check that NULL pointers are OK */ + +typedef struct { + GObject parent; +} TestNoOpPlugin; + +typedef struct { + GObjectClass parent_class; +} TestNoOpPluginClass; + +GType test_no_op_plugin_get_type (void) G_GNUC_CONST; + +G_DEFINE_TYPE_WITH_CODE (TestNoOpPlugin, test_no_op_plugin, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (MCP_TYPE_DBUS_ACL, NULL); + G_IMPLEMENT_INTERFACE (MCP_TYPE_REQUEST_POLICY, NULL); + G_IMPLEMENT_INTERFACE (MCP_TYPE_DISPATCH_OPERATION_POLICY, NULL)) + +static void +test_no_op_plugin_init (TestNoOpPlugin *self) +{ +} + +static void +test_no_op_plugin_class_init (TestNoOpPluginClass *cls) +{ +} + /* ------ TestPermissionPlugin -------------------------------------- */ typedef struct { @@ -65,6 +94,7 @@ test_permission_plugin_class_init (TestPermissionPluginClass *cls) typedef struct { McpDispatchOperation *dispatch_operation; McpDispatchOperationDelay *delay; + GSimpleAsyncResult *result; } PermissionContext; static void @@ -72,7 +102,15 @@ permission_context_free (gpointer p) { PermissionContext *ctx = p; - mcp_dispatch_operation_end_delay (ctx->dispatch_operation, ctx->delay); + if (ctx->delay != NULL) + mcp_dispatch_operation_end_delay (ctx->dispatch_operation, ctx->delay); + + if (ctx->result != NULL) + { + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + } + g_object_unref (ctx->dispatch_operation); g_slice_free (PermissionContext, ctx); } @@ -87,9 +125,18 @@ permission_cb (DBusPendingCall *pc, if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR) { DEBUG ("Permission denied"); - mcp_dispatch_operation_leave_channels (ctx->dispatch_operation, - TRUE, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED, - "Computer says no"); + + if (ctx->result != NULL) + { + g_simple_async_result_set_error (ctx->result, TP_ERRORS, + TP_ERROR_PERMISSION_DENIED, "No, sorry"); + } + else + { + mcp_dispatch_operation_leave_channels (ctx->dispatch_operation, + TRUE, TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED, + "Computer says no"); + } } else { @@ -180,11 +227,106 @@ finally: } static void +handler_is_suitable_async (McpDispatchOperationPolicy *self, + TpClient *recipient, + const gchar *unique_name, + McpDispatchOperation *dispatch_operation, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple = g_simple_async_result_new ((GObject *) self, + callback, user_data, handler_is_suitable_async); + GHashTable *properties = mcp_dispatch_operation_ref_nth_channel_properties ( + dispatch_operation, 0); + PermissionContext *ctx = NULL; + + DEBUG ("enter"); + + if (properties == NULL) + { + DEBUG ("no channels!?"); + goto finally; + } + + /* currently this example just checks the first channel */ + + if (!tp_strdiff (tp_asv_get_string (properties, + TP_IFACE_CHANNEL ".TargetID"), + "policy@example.net")) + { + TpDBusDaemon *dbus_daemon = tp_dbus_daemon_dup (NULL); + DBusGConnection *gconn = tp_proxy_get_dbus_connection (dbus_daemon); + DBusConnection *libdbus = dbus_g_connection_get_connection (gconn); + DBusPendingCall *pc = NULL; + DBusMessage *message; + + ctx = g_slice_new0 (PermissionContext); + ctx->dispatch_operation = g_object_ref (dispatch_operation); + ctx->result = simple; + /* take ownership */ + simple = NULL; + + /* in a real policy-mechanism you'd give some details, like the + * channel's properties or object path, and the name of the handler */ + message = dbus_message_new_method_call ("com.example.Policy", + "/com/example/Policy", "com.example.Policy", "CheckHandler"); + + if (!dbus_connection_send_with_reply (libdbus, message, + &pc, -1)) + { + g_error ("out of memory"); + } + + dbus_message_unref (message); + + if (pc == NULL) + { + DEBUG ("got disconnected from D-Bus..."); + + goto finally; + } + + /* pc is unreffed by permission_cb */ + + DEBUG ("Waiting for permission"); + + if (dbus_pending_call_get_completed (pc)) + { + permission_cb (pc, ctx); + goto finally; + } + + if (!dbus_pending_call_set_notify (pc, permission_cb, ctx, + permission_context_free)) + { + g_error ("Out of memory"); + } + + /* ctx will be freed later */ + ctx = NULL; + } + +finally: + if (ctx != NULL) + permission_context_free (ctx); + + g_hash_table_unref (properties); + + if (simple != NULL) + { + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + } +} + +static void cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface, gpointer unused G_GNUC_UNUSED) { - mcp_dispatch_operation_policy_iface_implement_check (iface, - test_permission_plugin_check_cdo); + iface->check = test_permission_plugin_check_cdo; + + iface->handler_is_suitable_async = handler_is_suitable_async; + /* the default finish function accepts our GSimpleAsyncResult */ } typedef struct { @@ -378,8 +520,7 @@ static void rej_cdo_policy_iface_init (McpDispatchOperationPolicyIface *iface, gpointer unused G_GNUC_UNUSED) { - mcp_dispatch_operation_policy_iface_implement_check (iface, - test_rejection_plugin_check_cdo); + iface->check = test_rejection_plugin_check_cdo; } static void @@ -429,13 +570,21 @@ mcp_plugin_ref_nth_object (guint n) switch (n) { case 0: - return g_object_new (test_permission_plugin_get_type (), + return g_object_new (test_no_op_plugin_get_type (), NULL); case 1: + return g_object_new (test_permission_plugin_get_type (), + NULL); + + case 2: return g_object_new (test_rejection_plugin_get_type (), NULL); + case 3: + return g_object_new (test_no_op_plugin_get_type (), + NULL); + default: return NULL; }