diff options
Diffstat (limited to 'defaults/gconf-defaults.c')
-rw-r--r-- | defaults/gconf-defaults.c | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/defaults/gconf-defaults.c b/defaults/gconf-defaults.c new file mode 100644 index 00000000..295f5db0 --- /dev/null +++ b/defaults/gconf-defaults.c @@ -0,0 +1,753 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Matthias Clasen <mclasen@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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/types.h> +#include <pwd.h> + +#include <glib.h> +#include <glib-object.h> + +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include <polkit-dbus/polkit-dbus.h> +#include <polkit/polkit.h> + +#define GCONF_ENABLE_INTERNALS +#include <gconf/gconf-client.h> +#include <gconf/gconf-engine.h> + +#include "gconf-defaults.h" +#include "gconf-defaults-glue.h" + +static gboolean +do_exit (gpointer user_data) +{ + g_debug ("Exiting due to inactivity"); + exit (1); + return FALSE; +} + +static guint timer_id = 0; + +static void +stop_killtimer (void) +{ + if (timer_id > 0) { + g_source_remove (timer_id); + timer_id = 0; + } +} + +static void +start_killtimer (void) +{ + g_debug ("Setting killtimer to 30 seconds..."); + timer_id = g_timeout_add_seconds (30, do_exit, NULL); +} + +struct GConfDefaultsPrivate +{ + DBusGConnection *system_bus_connection; + DBusGProxy *system_bus_proxy; + PolKitContext *pol_ctx; +}; + +static void gconf_defaults_finalize (GObject *object); + +G_DEFINE_TYPE (GConfDefaults, gconf_defaults, G_TYPE_OBJECT) + +#define GCONF_DEFAULTS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GCONF_TYPE_DEFAULTS, GConfDefaultsPrivate)) + +GQuark +gconf_defaults_error_quark (void) +{ + static GQuark ret = 0; + + if (ret == 0) { + ret = g_quark_from_static_string ("gconf_defaults_error"); + } + + return ret; +} + + +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GType +gconf_defaults_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) + { + static const GEnumValue values[] = + { + ENUM_ENTRY (GCONF_DEFAULTS_ERROR_GENERAL, "GeneralError"), + ENUM_ENTRY (GCONF_DEFAULTS_ERROR_NOT_PRIVILEGED, "NotPrivileged"), + { 0, 0, 0 } + }; + + g_assert (GCONF_DEFAULTS_NUM_ERRORS == G_N_ELEMENTS (values) - 1); + + etype = g_enum_register_static ("GConfDefaultsError", values); + } + + return etype; +} + + +static GObject * +gconf_defaults_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GConfDefaults *mechanism; + GConfDefaultsClass *klass; + + klass = GCONF_DEFAULTS_CLASS (g_type_class_peek (GCONF_TYPE_DEFAULTS)); + + mechanism = GCONF_DEFAULTS (G_OBJECT_CLASS (gconf_defaults_parent_class)->constructor ( + type, + n_construct_properties, + construct_properties)); + + return G_OBJECT (mechanism); +} + +enum { + SYSTEM_SET, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +gconf_defaults_class_init (GConfDefaultsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gconf_defaults_constructor; + object_class->finalize = gconf_defaults_finalize; + + signals[SYSTEM_SET] = g_signal_new ("system-set", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GConfDefaultsClass, system_set), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, G_TYPE_STRV); + + g_type_class_add_private (klass, sizeof (GConfDefaultsPrivate)); + + dbus_g_object_type_install_info (GCONF_TYPE_DEFAULTS, &dbus_glib_gconf_defaults_object_info); + + dbus_g_error_domain_register (GCONF_DEFAULTS_ERROR, NULL, GCONF_DEFAULTS_TYPE_ERROR); + +} + +static void +gconf_defaults_init (GConfDefaults *mechanism) +{ + mechanism->priv = GCONF_DEFAULTS_GET_PRIVATE (mechanism); +} + +static void +gconf_defaults_finalize (GObject *object) +{ + GConfDefaults *mechanism; + + g_return_if_fail (object != NULL); + g_return_if_fail (GCONF_IS_DEFAULTS (object)); + + mechanism = GCONF_DEFAULTS (object); + + g_return_if_fail (mechanism->priv != NULL); + + g_object_unref (mechanism->priv->system_bus_proxy); + + G_OBJECT_CLASS (gconf_defaults_parent_class)->finalize (object); +} + +static gboolean +pk_io_watch_have_data (GIOChannel *channel, GIOCondition condition, gpointer user_data) +{ + int fd; + PolKitContext *pk_context = user_data; + fd = g_io_channel_unix_get_fd (channel); + polkit_context_io_func (pk_context, fd); + return TRUE; +} + +static int +pk_io_add_watch (PolKitContext *pk_context, int fd) +{ + guint id = 0; + GIOChannel *channel; + channel = g_io_channel_unix_new (fd); + if (channel == NULL) + goto out; + id = g_io_add_watch (channel, G_IO_IN, pk_io_watch_have_data, pk_context); + if (id == 0) { + g_io_channel_unref (channel); + goto out; + } + g_io_channel_unref (channel); +out: + return id; +} + +static void +pk_io_remove_watch (PolKitContext *pk_context, int watch_id) +{ + g_source_remove (watch_id); +} + +static gboolean +register_mechanism (GConfDefaults *mechanism) +{ + GError *error = NULL; + + mechanism->priv->pol_ctx = polkit_context_new (); + polkit_context_set_io_watch_functions (mechanism->priv->pol_ctx, pk_io_add_watch, pk_io_remove_watch); + if (!polkit_context_init (mechanism->priv->pol_ctx, NULL)) { + g_critical ("cannot initialize libpolkit"); + goto error; + } + + error = NULL; + mechanism->priv->system_bus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (mechanism->priv->system_bus_connection == NULL) { + if (error != NULL) { + g_critical ("error getting system bus: %s", error->message); + g_error_free (error); + } + goto error; + } + + dbus_g_connection_register_g_object (mechanism->priv->system_bus_connection, "/", + G_OBJECT (mechanism)); + + mechanism->priv->system_bus_proxy = dbus_g_proxy_new_for_name (mechanism->priv->system_bus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + + start_killtimer (); + + return TRUE; + +error: + return FALSE; +} + + +GConfDefaults * +gconf_defaults_new (void) +{ + GObject *object; + gboolean res; + + object = g_object_new (GCONF_TYPE_DEFAULTS, NULL); + + res = register_mechanism (GCONF_DEFAULTS (object)); + if (! res) { + g_object_unref (object); + return NULL; + } + + return GCONF_DEFAULTS (object); +} + +static const char * +polkit_action_for_gconf_path (GConfDefaults *mechanism, + const char *annotation_key, + const char *path) +{ + PolKitPolicyCache *cache; + PolKitPolicyFileEntry *entry; + char *prefix, *p; + const char *action; + + cache = polkit_context_get_policy_cache (mechanism->priv->pol_ctx); + prefix = g_strdup (path); + + while (1) { + entry = polkit_policy_cache_get_entry_by_annotation (cache, + annotation_key, + prefix); + if (entry) { + action = polkit_policy_file_entry_get_id (entry); + break; + } + + p = strrchr (prefix, '/'); + + if (p == NULL || p == prefix) { + action = NULL; + break; + } + + *p = 0; + } + + g_free (prefix); + + return action; +} + +static gboolean +check_polkit_for_action (GConfDefaults *mechanism, + DBusGMethodInvocation *context, + const char *action) +{ + const char *sender; + GError *error; + DBusError dbus_error; + PolKitCaller *pk_caller; + PolKitAction *pk_action; + PolKitResult pk_result; + + error = NULL; + + /* Check that caller is privileged */ + sender = dbus_g_method_get_sender (context); + dbus_error_init (&dbus_error); + pk_caller = polkit_caller_new_from_dbus_name ( + dbus_g_connection_get_connection (mechanism->priv->system_bus_connection), + sender, + &dbus_error); + if (pk_caller == NULL) { + error = g_error_new (GCONF_DEFAULTS_ERROR, + GCONF_DEFAULTS_ERROR_GENERAL, + "Error getting information about caller: %s: %s", + dbus_error.name, dbus_error.message); + dbus_error_free (&dbus_error); + dbus_g_method_return_error (context, error); + g_error_free (error); + return FALSE; + } + + pk_action = polkit_action_new (); + polkit_action_set_action_id (pk_action, action); + pk_result = polkit_context_is_caller_authorized (mechanism->priv->pol_ctx, pk_action, pk_caller, TRUE, NULL); + polkit_caller_unref (pk_caller); + + if (pk_result != POLKIT_RESULT_YES) { + dbus_error_init (&dbus_error); + polkit_dbus_error_generate (pk_action, pk_result, &dbus_error); + dbus_set_g_error (&error, &dbus_error); + dbus_g_method_return_error (context, error); + dbus_error_free (&dbus_error); + g_error_free (error); + polkit_action_unref (pk_action); + return FALSE; + } + + polkit_action_unref (pk_action); + return TRUE; +} + +static char * +gconf_address_for_caller (GConfDefaults *mechanism, + DBusGMethodInvocation *context, + GError **gerror) +{ + char *sender; + DBusConnection *conn; + uid_t uid; + struct passwd *pwd; + char *result; + DBusError error; + + conn = dbus_g_connection_get_connection (mechanism->priv->system_bus_connection); + sender = dbus_g_method_get_sender (context); + + dbus_error_init (&error); + uid = dbus_bus_get_unix_user (conn, sender, &error); + g_free (sender); + if (uid == (unsigned)-1) { + dbus_set_g_error (gerror, &error); + dbus_error_free (&error); + return NULL; + } + + pwd = getpwuid (uid); + if (pwd == NULL) { + g_set_error (gerror, + 0, 0, + "Failed to get passwd information for uid %d", uid); + return NULL; + } + + result = g_strconcat ("xml:merged:", pwd->pw_dir, "/.gconf", NULL); + return result; +} + +static gboolean +path_is_excluded (const char *path, + const char **excludes) +{ + int i; + + for (i = 0; excludes && excludes[i]; i++) { + if (g_str_has_prefix (path, excludes[i])) + return TRUE; + } + + return FALSE; +} + +static void +copy_tree (GConfClient *src, + const char *path, + GConfChangeSet *changes, + const char **excludes) +{ + GSList *list, *l; + GConfEntry *entry; + + if (path_is_excluded (path, excludes)) + return; + + list = gconf_client_all_entries (src, path, NULL); + for (l = list; l; l = l->next) { + entry = l->data; + if (!path_is_excluded (entry->key, excludes)) + gconf_change_set_set (changes, entry->key, entry->value); + } + g_slist_foreach (list, (GFunc)gconf_entry_free, NULL); + g_slist_free (list); + + list = gconf_client_all_dirs (src, path, NULL); + for (l = list; l; l = l->next) + copy_tree (src, (const char *)l->data, changes, excludes); + g_slist_foreach (list, (GFunc)g_free, NULL); + g_slist_free (list); +} + +static void +copy_entry (GConfClient *src, + const char *path, + GConfChangeSet *changes, + const char **excludes) +{ + GConfValue *value; + + if (path_is_excluded (path, excludes)) + return; + + value = gconf_client_get (src, path, NULL); + if (value) { + gconf_change_set_set (changes, path, value); + gconf_value_free (value); + } +} + + +static void +do_copy (GConfDefaults *mechanism, + gboolean mandatory, + const char **includes, + const char **excludes, + DBusGMethodInvocation *context, + GConfChangeSet **changeset_out) +{ + char *address = NULL; + GConfClient *source = NULL; + GConfClient *dest = NULL; + GConfChangeSet *changes = NULL; + GConfEngine *engine; + GError *error; + GError *error2; + const char *action; + const char *annotation_key; + const char *default_action; + const char *dest_address; + int i; + + if (changeset_out) + *changeset_out = NULL; + + stop_killtimer (); + + /* check privileges for each include */ + if (mandatory) { + annotation_key = "org.gnome.gconf.defaults.set-mandatory.prefix"; + default_action = "org.gnome.gconf.defaults.set-mandatory"; + dest_address = "xml:merged:/etc/gconf/gconf.xml.mandatory"; + } + else { + annotation_key = "org.gnome.gconf.defaults.set-system.prefix"; + default_action = "org.gnome.gconf.defaults.set-system"; + dest_address = "xml:merged:/etc/gconf/gconf.xml.system"; + } + + for (i = 0; includes[i]; i++) { + action = polkit_action_for_gconf_path (mechanism, annotation_key, includes[i]); + if (action == NULL) + action = default_action; + + if (!check_polkit_for_action (mechanism, context, action)) + goto out; + } + + error = NULL; + engine = gconf_engine_get_local (dest_address, &error); + if (error) + goto cleanup; + + dest = gconf_client_get_for_engine (engine); + gconf_engine_unref (engine); + + /* find the address to from the caller id */ + address = gconf_address_for_caller (mechanism, context, &error); + if (error) + goto cleanup; + + engine = gconf_engine_get_local (address, &error); + if (error) + goto cleanup; + + source = gconf_client_get_for_engine (engine); + gconf_engine_unref (engine); + + changes = gconf_change_set_new (); + + /* recursively copy each include, leaving out the excludes */ + for (i = 0; includes[i]; i++) { + if (gconf_client_dir_exists (source, includes[i], NULL)) + copy_tree (source, includes[i], changes, excludes); + else + copy_entry (source, includes[i], changes, excludes); + } + + gconf_client_commit_change_set (dest, changes, FALSE, &error); + gconf_client_suggest_sync (dest, NULL); + + if (changeset_out) { + *changeset_out = changes; + changes = NULL; + } + +cleanup: + g_free (address); + if (changes) + gconf_change_set_unref (changes); + if (dest) + g_object_unref (dest); + if (source) + g_object_unref (source); + + if (error) { + g_print ("failed to set GConf values: %s\n", error->message); + error2 = g_error_new (GCONF_DEFAULTS_ERROR, + GCONF_DEFAULTS_ERROR_GENERAL, + error->message); + g_error_free (error); + + dbus_g_method_return_error (context, error2); + g_error_free (error2); + } + else + dbus_g_method_return (context); + +out: + start_killtimer (); +} + +static void +append_key (GConfChangeSet *cs, + const gchar *key, + GConfValue *value, + gpointer user_data) +{ + GPtrArray *keys = (GPtrArray *) user_data; + + g_ptr_array_add (keys, (gpointer) key); +} + +void +gconf_defaults_set_system (GConfDefaults *mechanism, + const char **includes, + const char **excludes, + DBusGMethodInvocation *context) +{ + GConfChangeSet *changes = NULL; + GPtrArray *keys; + + do_copy (mechanism, FALSE, includes, excludes, context, &changes); + + if (!changes) + return; + + keys = g_ptr_array_new (); + gconf_change_set_foreach (changes, append_key, keys); + g_ptr_array_add (keys, NULL); + + g_signal_emit (mechanism, signals[SYSTEM_SET], 0, keys->pdata); + + g_ptr_array_free (keys, TRUE); + gconf_change_set_unref (changes); +} + +void +gconf_defaults_set_mandatory (GConfDefaults *mechanism, + const char **includes, + const char **excludes, + DBusGMethodInvocation *context) +{ + do_copy (mechanism, TRUE, includes, excludes, context, NULL); +} + +static void +unset_tree (GConfClient *dest, + const char *path, + GConfChangeSet *changes, + const char **excludes) +{ + GSList *list, *l; + GConfEntry *entry; + + if (path_is_excluded (path, excludes)) + return; + + list = gconf_client_all_entries (dest, path, NULL); + for (l = list; l; l = l->next) { + entry = l->data; + if (!path_is_excluded (entry->key, excludes)) + gconf_change_set_unset (changes, entry->key); + } + g_slist_foreach (list, (GFunc)gconf_entry_free, NULL); + g_slist_free (list); + + list = gconf_client_all_dirs (dest, path, NULL); + for (l = list; l; l = l->next) + unset_tree (dest, (const char *)l->data, changes, excludes); + g_slist_foreach (list, (GFunc)g_free, NULL); + g_slist_free (list); +} + +static void +unset_entry (GConfClient *dest, + const char *path, + GConfChangeSet *changes, + const char **excludes) +{ + if (path_is_excluded (path, excludes)) + return; + + gconf_change_set_unset (changes, path); +} + +static void +unset_in_db (GConfDefaults *mechanism, + const char *address, + const char **includes, + const char **excludes, + GError **error) +{ + GConfEngine *engine; + GConfClient *dest = NULL; + GConfChangeSet *changes = NULL; + int i; + + engine = gconf_engine_get_local (address, error); + if (*error) + goto out; + + dest = gconf_client_get_for_engine (engine); + gconf_engine_unref (engine); + + changes = gconf_change_set_new (); + + /* recursively copy each include, leaving out the excludes */ + for (i = 0; includes[i]; i++) { + if (gconf_client_dir_exists (dest, includes[i], NULL)) + unset_tree (dest, includes[i], changes, excludes); + else + unset_entry (dest, includes[i], changes, excludes); + } + + gconf_client_commit_change_set (dest, changes, TRUE, error); + gconf_client_suggest_sync (dest, NULL); + +out: + if (dest) + g_object_unref (dest); + if (changes) + gconf_change_set_unref (changes); +} + +void +gconf_defaults_unset_mandatory (GConfDefaults *mechanism, + const char **includes, + const char **excludes, + DBusGMethodInvocation *context) +{ + const char *annotation_key; + const char *default_action; + int i; + const char *action; + GError *error; + GError *error2; + + stop_killtimer (); + + annotation_key = "org.gnome.gconf.defaults.set-mandatory.prefix"; + default_action = "org.gnome.gconf.defaults.set-mandatory"; + + for (i = 0; includes[i]; i++) { + action = polkit_action_for_gconf_path (mechanism, annotation_key, includes[i]); + if (action == NULL) + action = default_action; + + if (!check_polkit_for_action (mechanism, context, action)) + goto out; + } + + error = NULL; + unset_in_db (mechanism,"xml:merged:/etc/gconf/gconf.xml.mandatory", + includes, excludes, &error); + + if (error) { + error2 = g_error_new (GCONF_DEFAULTS_ERROR, + GCONF_DEFAULTS_ERROR_GENERAL, + error->message); + g_error_free (error); + + dbus_g_method_return_error (context, error2); + g_error_free (error2); + } + else + dbus_g_method_return (context); +out: + start_killtimer(); +} |