/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * ck-connector.c : Code for login managers to register with ConsoleKit. * * Copyright (c) 2007 David Zeuthen * Copyright (c) 2007 William Jon McCann * * 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 #include #include #include "ck-connector.h" #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) #if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #define _CK_FUNCTION_NAME __func__ #elif defined(__GNUC__) || defined(_MSC_VER) #define _CK_FUNCTION_NAME __FUNCTION__ #else #define _CK_FUNCTION_NAME "unknown function" #endif #define CK_CONNECTOR_ERROR "org.freedesktop.CkConnector.Error" #define _CK_WARNING_FORMAT "arguments to %s() were incorrect, assertion \"%s\" failed in file %s line %d.\n" #define _ck_return_if_fail(condition) do { \ if (!(condition)) { \ fprintf (stderr, _CK_WARNING_FORMAT, _CK_FUNCTION_NAME, #condition, __FILE__, __LINE__); \ return; \ } } while (0) #define _ck_return_val_if_fail(condition, val) do { \ if (!(condition)) { \ fprintf (stderr, _CK_WARNING_FORMAT, _CK_FUNCTION_NAME, #condition, __FILE__, __LINE__); \ return val; \ } } while (0) struct _CkConnector { int refcount; char *cookie; dbus_bool_t session_created; DBusConnection *connection; }; static struct { char *name; int type; } parameter_lookup[] = { { "display-device", DBUS_TYPE_STRING }, { "x11-display-device", DBUS_TYPE_STRING }, { "x11-display", DBUS_TYPE_STRING }, { "remote-host-name", DBUS_TYPE_STRING }, { "session-type", DBUS_TYPE_STRING }, { "is-local", DBUS_TYPE_BOOLEAN }, { "unix-user", DBUS_TYPE_INT32 }, }; static int lookup_parameter_type (const char *name) { int i; int type; type = DBUS_TYPE_INVALID; for (i = 0; i < N_ELEMENTS (parameter_lookup); i++) { if (strcmp (name, parameter_lookup[i].name) == 0) { type = parameter_lookup[i].type; break; } } return type; } static dbus_bool_t add_param_basic (DBusMessageIter *iter_array, const char *name, int type, const void *value) { DBusMessageIter iter_struct; DBusMessageIter iter_variant; const char *container_type; switch (type) { case DBUS_TYPE_STRING: container_type = DBUS_TYPE_STRING_AS_STRING; break; case DBUS_TYPE_BOOLEAN: container_type = DBUS_TYPE_BOOLEAN_AS_STRING; break; case DBUS_TYPE_INT32: container_type = DBUS_TYPE_INT32_AS_STRING; break; default: goto oom; break; } if (! dbus_message_iter_open_container (iter_array, DBUS_TYPE_STRUCT, NULL, &iter_struct)) { goto oom; } if (! dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &name)) { goto oom; } if (! dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_VARIANT, container_type, &iter_variant)) { goto oom; } if (! dbus_message_iter_append_basic (&iter_variant, type, value)) { goto oom; } if (! dbus_message_iter_close_container (&iter_struct, &iter_variant)) { goto oom; } if (! dbus_message_iter_close_container (iter_array, &iter_struct)) { goto oom; } return TRUE; oom: return FALSE; } /* Frees all resources allocated and disconnects from the system * message bus. */ static void _ck_connector_free (CkConnector *connector) { if (connector->connection != NULL) { /* it's a private connection so it's all good */ dbus_connection_close (connector->connection); } if (connector->cookie != NULL) { free (connector->cookie); } free (connector); } /** * Decrements the reference count of a CkConnector, disconnecting * from the bus and freeing the connector if the count reaches 0. * * @param connector the connector * @see ck_connector_ref */ void ck_connector_unref (CkConnector *connector) { _ck_return_if_fail (connector != NULL); /* Probably should use some kind of atomic op here */ connector->refcount -= 1; if (connector->refcount == 0) { _ck_connector_free (connector); } } /** * Increments the reference count of a CkConnector. * * @param connector the connector * @returns the connector * @see ck_connector_unref */ CkConnector * ck_connector_ref (CkConnector *connector) { _ck_return_val_if_fail (connector != NULL, NULL); /* Probably should use some kind of atomic op here */ connector->refcount += 1; return connector; } /** * Constructs a new Connector to communicate with the ConsoleKit * daemon. Returns #NULL if memory can't be allocated for the * object. * * @returns a new CkConnector, free with ck_connector_unref() */ CkConnector * ck_connector_new (void) { CkConnector *connector; connector = calloc (1, sizeof (CkConnector)); if (connector == NULL) { goto oom; } connector->refcount = 1; connector->connection = NULL; connector->cookie = NULL; connector->session_created = FALSE; oom: return connector; } /** * Connects to the D-Bus system bus daemon and issues the method call * OpenSession on the ConsoleKit manager interface. The * connection to the bus is private. * * Returns FALSE on OOM, if the system bus daemon is not running, if * the ConsoleKit daemon is not running or if the caller doesn't have * sufficient privileges. * * @returns #TRUE if the operation succeeds */ dbus_bool_t ck_connector_open_session (CkConnector *connector, DBusError *error) { DBusError local_error; DBusMessage *message; DBusMessage *reply; dbus_bool_t ret; char *cookie; _ck_return_val_if_fail (connector != NULL, FALSE); _ck_return_val_if_fail ((error) == NULL || !dbus_error_is_set ((error)), FALSE); reply = NULL; message = NULL; ret = FALSE; dbus_error_init (&local_error); connector->connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &local_error); if (connector->connection == NULL) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); } goto out; } dbus_connection_set_exit_on_disconnect (connector->connection, FALSE); message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "OpenSession"); if (message == NULL) { goto out; } dbus_error_init (&local_error); reply = dbus_connection_send_with_reply_and_block (connector->connection, message, -1, &local_error); if (reply == NULL) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } dbus_error_init (&local_error); if (! dbus_message_get_args (reply, &local_error, DBUS_TYPE_STRING, &cookie, DBUS_TYPE_INVALID)) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } connector->cookie = strdup (cookie); if (connector->cookie == NULL) { goto out; } connector->session_created = TRUE; ret = TRUE; out: if (reply != NULL) { dbus_message_unref (reply); } if (message != NULL) { dbus_message_unref (message); } return ret; } static dbus_bool_t ck_connector_open_session_with_parameters_valist (CkConnector *connector, DBusError *error, const char *first_parameter_name, va_list var_args) { DBusError local_error; DBusMessage *message; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter iter_array; dbus_bool_t ret; char *cookie; const char *name; _ck_return_val_if_fail (connector != NULL, FALSE); reply = NULL; message = NULL; ret = FALSE; dbus_error_init (&local_error); connector->connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &local_error); if (connector->connection == NULL) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); } goto out; } dbus_connection_set_exit_on_disconnect (connector->connection, FALSE); message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "OpenSessionWithParameters"); if (message == NULL) { goto out; } dbus_message_iter_init_append (message, &iter); if (! dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(sv)", &iter_array)) { goto out; } name = first_parameter_name; while (name != NULL) { int type; const void *value; dbus_bool_t res; type = lookup_parameter_type (name); value = va_arg (var_args, const void *); if (type == DBUS_TYPE_INVALID) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unknown parameter: %s", name); goto out; } res = add_param_basic (&iter_array, name, type, value); if (! res) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Error adding parameter: %s", name); goto out; } name = va_arg (var_args, char *); } if (! dbus_message_iter_close_container (&iter, &iter_array)) { goto out; } dbus_error_init (&local_error); reply = dbus_connection_send_with_reply_and_block (connector->connection, message, -1, &local_error); if (reply == NULL) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } dbus_error_init (&local_error); if (! dbus_message_get_args (reply, &local_error, DBUS_TYPE_STRING, &cookie, DBUS_TYPE_INVALID)) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to open session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } connector->cookie = strdup (cookie); if (connector->cookie == NULL) { goto out; } connector->session_created = TRUE; ret = TRUE; out: if (reply != NULL) { dbus_message_unref (reply); } if (message != NULL) { dbus_message_unref (message); } return ret; } /** * Opens a new session with parameter from variable argument list. The * variable argument list should contain the name of each parameter * followed by the value to append. * For example: * * @code * * DBusError error; * dbus_int32_t v_INT32 = 500; * const char *v_STRING = "/dev/tty3"; * * dbus_error_init (&error); * ck_connector_open_session_with_parameters (connector, * &error, * "unix-user", &v_INT32, * "display-device", &v_STRING, * NULL); * @endcode * * @param error error output * @param first_parameter_name name of the first parameter * @param ... value of first parameter, list of additional name-value pairs * @returns #TRUE on success */ dbus_bool_t ck_connector_open_session_with_parameters (CkConnector *connector, DBusError *error, const char *first_parameter_name, ...) { va_list var_args; dbus_bool_t ret; _ck_return_val_if_fail (connector != NULL, FALSE); _ck_return_val_if_fail ((error) == NULL || !dbus_error_is_set ((error)), FALSE); va_start (var_args, first_parameter_name); ret = ck_connector_open_session_with_parameters_valist (connector, error, first_parameter_name, var_args); va_end (var_args); return ret; } /** * Connects to the D-Bus system bus daemon and issues the method call * OpenSessionWithParameters on the ConsoleKit manager interface. The * connection to the bus is private. * * The only parameter that is optional is x11_display - it may be set * to NULL if there is no X11 server associated with the session. * * Returns FALSE on OOM, if the system bus daemon is not running, if * the ConsoleKit daemon is not running or if the caller doesn't have * sufficient privileges. * * @param user UID for the user owning the session * @param display_device the tty device for the session * @param x11_display the value of the X11 DISPLAY for the session * @returns #TRUE if the operation succeeds */ dbus_bool_t ck_connector_open_session_for_user (CkConnector *connector, uid_t user, const char *display_device, const char *x11_display, DBusError *error) { dbus_bool_t ret; _ck_return_val_if_fail (connector != NULL, FALSE); _ck_return_val_if_fail (display_device != NULL, FALSE); _ck_return_val_if_fail ((error) == NULL || !dbus_error_is_set ((error)), FALSE); ret = ck_connector_open_session_with_parameters (connector, error, "display-device", &display_device, "x11-display", &x11_display, "unix-user", &user, NULL); return ret; } /** * Gets the cookie for the current open session. * Returns #NULL if no session is open. * * @returns a constant string with the cookie. */ const char * ck_connector_get_cookie (CkConnector *connector) { _ck_return_val_if_fail (connector != NULL, NULL); if (! connector->session_created) { return NULL; } else { return connector->cookie; } } /** * Issues the CloseSession method call on the ConsoleKit manager * interface. * * Returns FALSE on OOM, if the system bus daemon is not running, if * the ConsoleKit daemon is not running, if the caller doesn't have * sufficient privilege or if a session isn't open. * * @returns #TRUE if the operation succeeds */ dbus_bool_t ck_connector_close_session (CkConnector *connector, DBusError *error) { DBusError local_error; DBusMessage *message; DBusMessage *reply; dbus_bool_t ret; dbus_bool_t session_closed; _ck_return_val_if_fail (connector != NULL, FALSE); _ck_return_val_if_fail ((error) == NULL || !dbus_error_is_set ((error)), FALSE); reply = NULL; message = NULL; ret = FALSE; if (!connector->session_created || connector->cookie == NULL) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to close session: %s", "no session open"); goto out; } dbus_error_init (&local_error); message = dbus_message_new_method_call ("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "CloseSession"); if (message == NULL) { goto out; } if (! dbus_message_append_args (message, DBUS_TYPE_STRING, &(connector->cookie), DBUS_TYPE_INVALID)) { goto out; } dbus_error_init (&local_error); reply = dbus_connection_send_with_reply_and_block (connector->connection, message, -1, &local_error); if (reply == NULL) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to close session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } dbus_error_init (&local_error); if (! dbus_message_get_args (reply, &local_error, DBUS_TYPE_BOOLEAN, &session_closed, DBUS_TYPE_INVALID)) { if (dbus_error_is_set (&local_error)) { dbus_set_error (error, CK_CONNECTOR_ERROR, "Unable to close session: %s", local_error.message); dbus_error_free (&local_error); goto out; } } if (! session_closed) { goto out; } connector->session_created = FALSE; ret = TRUE; out: if (reply != NULL) { dbus_message_unref (reply); } if (message != NULL) { dbus_message_unref (message); } return ret; }