/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gkd-dbus.c - hook into dbus, call other bits
Copyright (C) 2007, 2009, Stefan Walter
The Gnome Keyring Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Keyring 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stef@memberwebs.com>
*/
#include "config.h"
#include "gkd-daemon-generated.h"
#include "gkd-dbus.h"
#include "gkd-dbus-private.h"
#include "daemon/gkd-main.h"
#include "daemon/gkd-util.h"
#include "egg/egg-cleanup.h"
#include <glib.h>
#include <gio/gio.h>
static GDBusConnection *dbus_conn = NULL;
static gboolean object_registered = FALSE;
static gboolean acquired_asked = FALSE;
static gboolean acquired_service = FALSE;
#define GNOME_KEYRING_DAEMON_SERVICE "org.gnome.keyring"
#define GNOME_KEYRING_DAEMON_PATH "/org/gnome/keyring/daemon"
#define GNOME_KEYRING_DAEMON_INTERFACE "org.gnome.keyring.Daemon"
static void
cleanup_session_bus (gpointer unused)
{
if (!dbus_conn)
return;
g_clear_object (&dbus_conn);
}
static void
on_connection_close (gpointer user_data)
{
g_debug ("dbus connection closed, exiting");
gkd_main_quit ();
}
static gboolean
connect_to_session_bus (void)
{
GError *error = NULL;
if (dbus_conn)
return TRUE;
dbus_conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (!dbus_conn) {
g_message ("couldn't connect to dbus session bus: %s", error->message);
g_error_free (error);
return FALSE;
}
g_signal_connect (dbus_conn, "closed",
G_CALLBACK (on_connection_close), NULL);
egg_cleanup_register (cleanup_session_bus, NULL);
return TRUE;
}
static gboolean
handle_get_environment (GkdExportedDaemon *skeleton,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
const gchar **env;
gchar **parts;
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
for (env = gkd_util_get_environment (); *env != NULL; env++) {
parts = g_strsplit (*env, "=", 2);
g_variant_builder_add (&builder, "{ss}", parts[0], parts[1]);
g_strfreev (parts);
}
gkd_exported_daemon_complete_get_environment (skeleton, invocation,
g_variant_builder_end (&builder));
return TRUE;
}
static gboolean
handle_get_control_directory (GkdExportedDaemon *skeleton,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
gkd_exported_daemon_complete_get_control_directory (skeleton, invocation,
gkd_util_get_master_directory ());
return TRUE;
}
static void
cleanup_singleton (gpointer user_data)
{
GkdExportedDaemon *skeleton = user_data;
g_return_if_fail (dbus_conn);
if (object_registered) {
g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (skeleton), dbus_conn);
g_object_unref (skeleton);
}
object_registered = FALSE;
}
gboolean
gkd_dbus_singleton_acquire (gboolean *acquired)
{
const gchar *service = NULL;
GBusNameOwnerFlags flags = G_BUS_NAME_OWNER_FLAGS_NONE;
GVariant *acquire_variant;
guint res;
GError *error = NULL;
GkdExportedDaemon *skeleton;
g_assert (acquired);
if (!connect_to_session_bus ())
return FALSE;
/* First register the object */
if (!object_registered) {
skeleton = gkd_exported_daemon_skeleton_new ();
g_signal_connect (skeleton, "handle-get-control-directory",
G_CALLBACK (handle_get_control_directory), NULL);
g_signal_connect (skeleton, "handle-get-environment",
G_CALLBACK (handle_get_environment), NULL);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), dbus_conn,
GNOME_KEYRING_DAEMON_PATH, &error);
if (error == NULL) {
object_registered = TRUE;
egg_cleanup_register (cleanup_singleton, skeleton);
} else {
g_message ("couldn't register dbus object path: %s", error->message);
g_clear_error (&error);
}
}
/* Try and grab our name */
if (!acquired_asked) {
#ifdef WITH_DEBUG
service = g_getenv ("GNOME_KEYRING_TEST_SERVICE");
if (service && service[0])
flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE;
else
#endif
service = GNOME_KEYRING_DAEMON_SERVICE;
/* attempt to acquire the name */
acquire_variant = g_dbus_connection_call_sync (dbus_conn,
"org.freedesktop.DBus", /* bus name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"RequestName", /* method name */
g_variant_new ("(su)",
service,
flags),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, &error);
if (error != NULL) {
g_message ("couldn't request name '%s' on session bus: %s", service, error->message);
g_error_free (error);
return FALSE;
}
acquired_asked = TRUE;
g_variant_get (acquire_variant, "(u)", &res);
g_variant_unref (acquire_variant);
switch (res) {
/* We acquired the service name */
case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */
case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */
acquired_service = TRUE;
break;
/* Another daemon is running */
case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */
case 3: /* DBUS_REQUEST_NAME_REPLY_EXISTS */
acquired_service = FALSE;
break;
default:
acquired_service = FALSE;
g_return_val_if_reached (FALSE);
break;
};
}
*acquired = acquired_service;
return TRUE;
}
gchar*
gkd_dbus_singleton_control (void)
{
gchar *control = NULL;
GError *error = NULL;
GVariant *control_variant;
/* If tried to aquire the service must have failed */
g_return_val_if_fail (!acquired_service, NULL);
if (!connect_to_session_bus ())
return NULL;
control_variant = g_dbus_connection_call_sync (dbus_conn,
GNOME_KEYRING_DAEMON_SERVICE,
GNOME_KEYRING_DAEMON_PATH,
GNOME_KEYRING_DAEMON_INTERFACE,
"GetControlDirectory",
NULL, NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
1000, NULL, &error);
if (error != NULL) {
if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
g_message ("couldn't communicate with already running daemon: %s", error->message);
g_error_free (error);
return NULL;
}
g_variant_get (control_variant, "(s)", &control);
g_variant_unref (control_variant);
return control;
}
static void
dbus_cleanup (gpointer unused)
{
g_return_if_fail (dbus_conn);
gkd_dbus_secrets_cleanup (dbus_conn);
gkd_dbus_session_cleanup (dbus_conn);
gkd_dbus_environment_cleanup (dbus_conn);
}
gboolean
gkd_dbus_setup (void)
{
gboolean unused;
if (!connect_to_session_bus ())
return FALSE;
/* Our singleton, and internal service API */
gkd_dbus_singleton_acquire (&unused);
/* Session stuff */
gkd_dbus_environment_init (dbus_conn);
gkd_dbus_session_init (dbus_conn);
/* Secrets API */
gkd_dbus_secrets_init (dbus_conn);
egg_cleanup_register (dbus_cleanup, NULL);
return TRUE;
}
gboolean
gkd_dbus_invocation_matches_caller (GDBusMethodInvocation *invocation,
const char *caller)
{
const char *invocation_caller;
invocation_caller = g_dbus_method_invocation_get_sender (invocation);
if (!g_str_equal (invocation_caller, caller)) {
g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Invalid caller");
return FALSE;
}
return TRUE;
}