[gnome-keyring/dbus-api] Implement basics of prompting for operations.



commit 15a7df11c798c881875596c3432afa340f6443f9
Author: Stef Walter <stef memberwebs com>
Date:   Sun Nov 1 15:55:02 2009 +0000

    Implement basics of prompting for operations.
    
    * Implement Service.Unlock method.
     * Hook into prompting code.
     * Implementing what was recently posted on the mailing list,
       about prompt objects, and returning them from operations that
       need to prompt.
     * Buildable, but not yet runnable.

 configure.in                          |    1 +
 daemon/.gitignore                     |    6 +-
 daemon/Makefile.am                    |    2 +
 daemon/dbus/Makefile.am               |    4 +-
 daemon/dbus/gkd-secrets-objects.c     |   31 +
 daemon/dbus/gkd-secrets-objects.h     |    4 +
 daemon/dbus/gkd-secrets-prompt.c      |  387 +++++++++++++
 daemon/dbus/gkd-secrets-prompt.h      |   70 +++
 daemon/dbus/gkd-secrets-service.c     |  116 +++-
 daemon/dbus/gkd-secrets-service.h     |    6 +
 daemon/dbus/gkd-secrets-types.h       |    6 +-
 daemon/dbus/gkd-secrets-unlock.c      |  351 ++++++++++++
 daemon/dbus/gkd-secrets-unlock.h      |   56 ++
 daemon/prompt/Makefile.am             |   29 +
 daemon/prompt/gkd-prompt-marshal.list |    1 +
 daemon/prompt/gkd-prompt.c            |  975 +++++++++++++++++++++++++++++++++
 daemon/prompt/gkd-prompt.h            |  102 ++++
 17 files changed, 2119 insertions(+), 28 deletions(-)
---
diff --git a/configure.in b/configure.in
index e1f95fd..34373b7 100644
--- a/configure.in
+++ b/configure.in
@@ -532,6 +532,7 @@ daemon/dbus/Makefile
 daemon/keyrings/Makefile
 daemon/keyrings/tests/Makefile
 daemon/pkcs11/Makefile
+daemon/prompt/Makefile
 daemon/ui/Makefile
 daemon/util/Makefile
 daemon/util/tests/Makefile
diff --git a/daemon/.gitignore b/daemon/.gitignore
index a7e6dc6..f68cc1c 100644
--- a/daemon/.gitignore
+++ b/daemon/.gitignore
@@ -1,5 +1,5 @@
-/.libs
-/.deps
+.libs
+.deps
 Makefile
 Makefile.in
 /gnome-keyring-ask
@@ -7,3 +7,5 @@ Makefile.in
 /org.gnome.keyring.service
 /gnome-keyring-daemon.desktop
 /gnome-keyring-daemon.desktop.in
+*-marshal.[ch]
+
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index b3759a3..2c8201f 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -1,6 +1,7 @@
 SUBDIRS = \
 	util \
 	ui \
+	prompt \
 	keyrings \
 	pkcs11 \
 	dbus \
@@ -33,6 +34,7 @@ gnome_keyring_daemon_LDADD = \
 	$(top_builddir)/daemon/dbus/libgkr-dbus.la \
 	$(top_builddir)/daemon/keyrings/libgkr-keyrings.la \
 	$(top_builddir)/daemon/ui/libgkr-ui.la \
+	$(top_builddir)/daemon/prompt/libgkd-prompt.la \
 	$(top_builddir)/daemon/util/libgkr-daemon-util.la \
 	$(top_builddir)/library/libgnome-keyring-common.la \
 	$(top_builddir)/pkcs11/plex-layer/libgck-plex-layer.la \
diff --git a/daemon/dbus/Makefile.am b/daemon/dbus/Makefile.am
index 36f2d96..818ad16 100644
--- a/daemon/dbus/Makefile.am
+++ b/daemon/dbus/Makefile.am
@@ -19,9 +19,11 @@ libgkr_dbus_la_SOURCES = \
 	gkd-dbus-session.c \
 	gkd-dbus-util.c gkd-dbus-util.h \
 	gkd-secrets-objects.c gkd-secrets-objects.h \
+	gkd-secrets-prompt.c gkd-secrets-prompt.h \
 	gkd-secrets-service.c gkd-secrets-service.h \
 	gkd-secrets-session.c gkd-secrets-session.h \
-	gkd-secrets-types.h
+	gkd-secrets-types.h \
+	gkd-secrets-unlock.c gkd-secrets-unlock.h
 
 libgkr_dbus_la_LIBADD = \
 	$(GLIB_LIBS) \
diff --git a/daemon/dbus/gkd-secrets-objects.c b/daemon/dbus/gkd-secrets-objects.c
index 165b2ea..c9c8b5d 100644
--- a/daemon/dbus/gkd-secrets-objects.c
+++ b/daemon/dbus/gkd-secrets-objects.c
@@ -1395,3 +1395,34 @@ gkd_secrets_objects_handle_search_items (GkdSecretsObjects *self, DBusMessage *m
 
 	return reply;
 }
+
+GP11Object*
+gkd_secrets_objects_lookup_collection (GkdSecretsObjects *self, const gchar *caller,
+                                       const gchar *objpath)
+{
+	GP11Session *session;
+	GP11Object *coll;
+	gchar *coll_id;
+	gchar *item_id;
+
+	g_return_val_if_fail (GKD_SECRETS_IS_OBJECTS (self), NULL);
+	g_return_val_if_fail (objpath, NULL);
+	g_return_val_if_fail (caller, NULL);
+
+	/* The session we're using to access the object */
+	session = gkd_secrets_service_get_pkcs11_session (self->service, caller);
+	g_return_val_if_fail (session, NULL);
+
+	/* Figure out which collection or item we're talking about */
+	if (!parse_collection_and_item_from_path (objpath, &coll_id, &item_id))
+		return NULL;
+
+	g_return_val_if_fail (coll_id, NULL);
+	coll = collection_for_identifier (session, coll_id);
+
+	g_free (coll_id);
+	g_free (item_id);
+
+	return coll;
+
+}
diff --git a/daemon/dbus/gkd-secrets-objects.h b/daemon/dbus/gkd-secrets-objects.h
index 2fcf4ab..2ec1453 100644
--- a/daemon/dbus/gkd-secrets-objects.h
+++ b/daemon/dbus/gkd-secrets-objects.h
@@ -61,4 +61,8 @@ void                gkd_secrets_objects_append_item_paths        (GkdSecretsObje
 
 GP11Slot*           gkd_secrets_objects_get_pkcs11_slot          (GkdSecretsObjects *self);
 
+GP11Object*         gkd_secrets_objects_lookup_collection        (GkdSecretsObjects *self,
+                                                                  const gchar *caller,
+                                                                  const gchar *objpath);
+
 #endif /* __GKD_SECRETS_OBJECTS_H__ */
diff --git a/daemon/dbus/gkd-secrets-prompt.c b/daemon/dbus/gkd-secrets-prompt.c
new file mode 100644
index 0000000..ec4246b
--- /dev/null
+++ b/daemon/dbus/gkd-secrets-prompt.c
@@ -0,0 +1,387 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 "gkd-secrets-service.h"
+#include "gkd-secrets-prompt.h"
+#include "gkd-secrets-objects.h"
+#include "gkd-secrets-types.h"
+#include "gkd-dbus-util.h"
+
+#include "prompt/gkd-prompt.h"
+
+#include <string.h>
+
+enum {
+	PROP_0,
+	PROP_CALLER,
+	PROP_OBJECT_PATH,
+	PROP_SERVICE
+};
+
+struct _GkdSecretsPromptPrivate {
+	GkdPrompt parent;
+	gchar *object_path;
+	GkdSecretsService *service;
+	gboolean prompted;
+	gboolean completed;
+	gchar *caller;
+	gchar *window_id;
+	GList *objects;
+};
+
+G_DEFINE_TYPE (GkdSecretsPrompt, gkd_secrets_prompt, GKD_TYPE_PROMPT);
+
+static guint unique_prompt_number = 0;
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static GkdPrompt*
+on_prompt_attention (gpointer user_data)
+{
+	GkdSecretsPrompt *self = user_data;
+
+	/* Check with the derived class */
+	g_return_val_if_fail (GKD_SECRETS_PROMPT_GET_CLASS (self)->prompt_ready, NULL);
+	GKD_SECRETS_PROMPT_GET_CLASS (self)->prompt_ready (self);
+
+	if (self->pv->completed)
+		return NULL;
+	return g_object_ref (self);
+}
+
+static void
+emit_completed (GkdSecretsPrompt *self, gboolean dismissed)
+{
+	DBusMessage *signal;
+	DBusMessageIter iter;
+	dbus_bool_t bval;
+
+	signal = dbus_message_new_signal (self->pv->object_path, SECRETS_PROMPT_INTERFACE,
+	                                  "Completed");
+	dbus_message_set_destination (signal, self->pv->caller);
+	dbus_message_iter_init_append (signal, &iter);
+
+	g_return_if_fail (GKD_SECRETS_PROMPT_GET_CLASS (self)->encode_result);
+	GKD_SECRETS_PROMPT_GET_CLASS (self)->encode_result (self, &iter);
+
+	bval = dismissed;
+	dbus_message_iter_append_basic (&iter, DBUS_TYPE_BOOLEAN, &bval);
+
+	gkd_secrets_service_send (self->pv->service, signal);
+	dbus_message_unref (signal);
+}
+
+/* -----------------------------------------------------------------------------
+ * DBUS
+ */
+
+static DBusMessage*
+prompt_method_prompt (GkdSecretsPrompt *self, DBusMessage *message)
+{
+	DBusMessage *reply;
+	const char *window_id;
+
+	/* Act as if this object no longer exists */
+	if (self->pv->completed)
+		return NULL;
+
+	if (!dbus_message_get_args (message, NULL, DBUS_TYPE_STRING,
+	                            &window_id, DBUS_TYPE_INVALID))
+		return NULL;
+
+	/* Prompt can only be called once */
+	if (self->pv->prompted)
+		return dbus_message_new_error (message, SECRETS_ERROR_ALREADY_EXISTS,
+		                               "This prompt has already been shown.");
+
+	gkd_prompt_set_window_id (GKD_PROMPT (self), window_id);
+	gkd_prompt_request_attention_async (window_id, on_prompt_attention,
+	                                    g_object_ref (self), g_object_unref);
+	self->pv->prompted = TRUE;
+
+	reply = dbus_message_new_method_return (message);
+	dbus_message_append_args (reply, DBUS_TYPE_INVALID);
+	return reply;
+}
+
+static DBusMessage*
+prompt_method_dismiss (GkdSecretsPrompt *self, DBusMessage *message)
+{
+	DBusMessage *reply;
+
+	/* Act as if this object no longer exists */
+	if (self->pv->completed)
+		return NULL;
+
+	if (!dbus_message_get_args (message, NULL, DBUS_TYPE_INVALID))
+		return NULL;
+
+	gkd_secrets_prompt_dismiss (self);
+
+	reply = dbus_message_new_method_return (message);
+	dbus_message_append_args (reply, DBUS_TYPE_INVALID);
+	return reply;
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static gboolean
+gkd_secrets_prompt_responded (GkdPrompt *base)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (base);
+	gint res;
+
+	res = gkd_prompt_get_response (GKD_PROMPT (self));
+	if (res == GKD_RESPONSE_NO || res == GKD_RESPONSE_FAILURE) {
+		gkd_secrets_prompt_dismiss (self);
+		return TRUE;
+	}
+
+	/* Check with the prompt ready guys */
+	g_return_val_if_fail (GKD_SECRETS_PROMPT_GET_CLASS (self)->prompt_ready, TRUE);
+	GKD_SECRETS_PROMPT_GET_CLASS (self)->prompt_ready (self);
+	return self->pv->completed;
+}
+
+static void
+gkd_secrets_prompt_ready (GkdSecretsPrompt *self)
+{
+	/* Default implementation, unused */
+	g_return_if_reached ();
+}
+
+static void
+gkd_secrets_prompt_encode_result (GkdSecretsPrompt *self, DBusMessageIter *iter)
+{
+	/* Default implementation, unused */
+	g_return_if_reached ();
+}
+
+static GObject*
+gkd_secrets_prompt_constructor (GType type, guint n_props, GObjectConstructParam *props)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (G_OBJECT_CLASS (gkd_secrets_prompt_parent_class)->constructor(type, n_props, props));
+
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (self->pv->caller, NULL);
+	g_return_val_if_fail (self->pv->service, NULL);
+
+	/* Setup the path for the object */
+	self->pv->object_path = g_strdup_printf (SECRETS_PROMPT_PREFIX "/p%d", ++unique_prompt_number);
+
+	return G_OBJECT (self);
+}
+
+static void
+gkd_secrets_prompt_init (GkdSecretsPrompt *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GKD_SECRETS_TYPE_PROMPT, GkdSecretsPromptPrivate);
+}
+
+static void
+gkd_secrets_prompt_dispose (GObject *obj)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (obj);
+
+	g_free (self->pv->object_path);
+	self->pv->object_path = NULL;
+
+	if (self->pv->service) {
+		g_object_remove_weak_pointer (G_OBJECT (self->pv->service),
+		                              (gpointer*)&(self->pv->service));
+		self->pv->service = NULL;
+	}
+
+	G_OBJECT_CLASS (gkd_secrets_prompt_parent_class)->dispose (obj);
+}
+
+static void
+gkd_secrets_prompt_finalize (GObject *obj)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (obj);
+
+	g_assert (!self->pv->object_path);
+	g_assert (!self->pv->service);
+
+	g_free (self->pv->caller);
+	self->pv->caller = NULL;
+
+	G_OBJECT_CLASS (gkd_secrets_prompt_parent_class)->finalize (obj);
+}
+
+static void
+gkd_secrets_prompt_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                  GParamSpec *pspec)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (obj);
+
+	switch (prop_id) {
+	case PROP_CALLER:
+		g_return_if_fail (!self->pv->caller);
+		self->pv->caller = g_value_dup_string (value);
+		break;
+	case PROP_SERVICE:
+		g_return_if_fail (!self->pv->service);
+		self->pv->service = g_value_get_object (value);
+		g_return_if_fail (self->pv->service);
+		g_object_add_weak_pointer (G_OBJECT (self->pv->service),
+		                           (gpointer*)&(self->pv->service));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gkd_secrets_prompt_get_property (GObject *obj, guint prop_id, GValue *value,
+                                     GParamSpec *pspec)
+{
+	GkdSecretsPrompt *self = GKD_SECRETS_PROMPT (obj);
+
+	switch (prop_id) {
+	case PROP_CALLER:
+		g_value_set_string (value, gkd_secrets_prompt_get_caller (self));
+		break;
+	case PROP_OBJECT_PATH:
+		g_value_set_boxed (value, gkd_secrets_prompt_get_object_path (self));
+		break;
+	case PROP_SERVICE:
+		g_value_set_object (value, self->pv->service);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+gkd_secrets_prompt_class_init (GkdSecretsPromptClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GkdPromptClass *prompt_class = GKD_PROMPT_CLASS (klass);
+
+	gobject_class->constructor = gkd_secrets_prompt_constructor;
+	gobject_class->dispose = gkd_secrets_prompt_dispose;
+	gobject_class->finalize = gkd_secrets_prompt_finalize;
+	gobject_class->set_property = gkd_secrets_prompt_set_property;
+	gobject_class->get_property = gkd_secrets_prompt_get_property;
+
+	prompt_class->responded = gkd_secrets_prompt_responded;
+
+	klass->encode_result = gkd_secrets_prompt_encode_result;
+	klass->prompt_ready = gkd_secrets_prompt_ready;
+
+	g_type_class_add_private (klass, sizeof (GkdSecretsPromptPrivate));
+
+	g_object_class_install_property (gobject_class, PROP_CALLER,
+		g_param_spec_string ("caller", "Caller", "DBus caller name",
+		                     NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY ));
+
+	g_object_class_install_property (gobject_class, PROP_OBJECT_PATH,
+	        g_param_spec_string ("object-path", "Object Path", "DBus Object Path",
+		                     NULL, G_PARAM_READABLE));
+
+	g_object_class_install_property (gobject_class, PROP_SERVICE,
+		g_param_spec_object ("service", "Service", "Service which owns this prompt",
+		                     GKD_SECRETS_TYPE_SERVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+DBusMessage*
+gkd_secrets_prompt_dispatch (GkdSecretsPrompt *self, DBusMessage *message)
+{
+	DBusMessage *reply = NULL;
+	const gchar *caller;
+
+	g_return_val_if_fail (message, NULL);
+	g_return_val_if_fail (GKD_SECRETS_IS_PROMPT (self), NULL);
+
+	/* This should already have been caught elsewhere */
+	caller = dbus_message_get_sender (message);
+	if (!caller || !g_str_equal (caller, self->pv->caller))
+		g_return_val_if_reached (NULL);
+
+	/* org.freedesktop.Secrets.Prompt.Prompt() */
+	else if (dbus_message_is_method_call (message, SECRETS_PROMPT_INTERFACE, "Prompt"))
+		reply = prompt_method_prompt (self, message);
+
+	/* org.freedesktop.Secrets.Prompt.Negotiate() */
+	else if (dbus_message_is_method_call (message, SECRETS_PROMPT_INTERFACE, "Dismiss"))
+		reply = prompt_method_dismiss (self, message);
+
+	return reply;
+}
+
+const gchar*
+gkd_secrets_prompt_get_caller (GkdSecretsPrompt *self)
+{
+	g_return_val_if_fail (GKD_SECRETS_IS_PROMPT (self), NULL);
+	return self->pv->caller;
+}
+
+const gchar*
+gkd_secrets_prompt_get_object_path (GkdSecretsPrompt *self)
+{
+	g_return_val_if_fail (GKD_SECRETS_IS_PROMPT (self), NULL);
+	return self->pv->object_path;
+}
+
+void
+gkd_secrets_prompt_complete (GkdSecretsPrompt *self)
+{
+	g_return_if_fail (GKD_SECRETS_IS_PROMPT (self));
+	g_return_if_fail (!self->pv->completed);
+	self->pv->completed = TRUE;
+	emit_completed (self, TRUE);
+}
+
+void
+gkd_secrets_prompt_dismiss (GkdSecretsPrompt *self)
+{
+	g_return_if_fail (GKD_SECRETS_IS_PROMPT (self));
+	g_return_if_fail (!self->pv->completed);
+	self->pv->completed = TRUE;
+	emit_completed (self, FALSE);
+}
+
+GP11Object*
+gkd_secrets_prompt_lookup_collection (GkdSecretsPrompt *self, const gchar *objpath)
+{
+	GkdSecretsObjects *objects;
+
+	g_return_val_if_fail (GKD_SECRETS_IS_PROMPT (self), NULL);
+	g_return_val_if_fail (self->pv->service, NULL);
+
+	objects = gkd_secrets_service_get_objects (self->pv->service);
+	g_return_val_if_fail (objects, NULL);
+
+	return gkd_secrets_objects_lookup_collection (objects, self->pv->caller, objpath);
+}
diff --git a/daemon/dbus/gkd-secrets-prompt.h b/daemon/dbus/gkd-secrets-prompt.h
new file mode 100644
index 0000000..9c57931
--- /dev/null
+++ b/daemon/dbus/gkd-secrets-prompt.h
@@ -0,0 +1,70 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2009 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef __GKD_SECRETS_PROMPT_H__
+#define __GKD_SECRETS_PROMPT_H__
+
+#include <glib-object.h>
+
+#include "gkd-secrets-types.h"
+
+#include "prompt/gkd-prompt.h"
+
+#define GKD_SECRETS_TYPE_PROMPT               (gkd_secrets_prompt_get_type ())
+#define GKD_SECRETS_PROMPT(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GKD_SECRETS_TYPE_PROMPT, GkdSecretsPrompt))
+#define GKD_SECRETS_PROMPT_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GKD_SECRETS_TYPE_PROMPT, GkdSecretsPromptClass))
+#define GKD_SECRETS_IS_PROMPT(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GKD_SECRETS_TYPE_PROMPT))
+#define GKD_SECRETS_IS_PROMPT_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GKD_SECRETS_TYPE_PROMPT))
+#define GKD_SECRETS_PROMPT_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GKD_SECRETS_TYPE_PROMPT, GkdSecretsPromptClass))
+
+typedef struct _GkdSecretsPromptClass GkdSecretsPromptClass;
+typedef struct _GkdSecretsPromptPrivate GkdSecretsPromptPrivate;
+
+struct _GkdSecretsPrompt {
+	GkdPrompt parent;
+	GkdSecretsPromptPrivate *pv;
+};
+
+struct _GkdSecretsPromptClass {
+	GObjectClass parent_class;
+
+	/* virtual methods */
+	void (*prompt_ready) (GkdSecretsPrompt *self);
+	void (*encode_result) (GkdSecretsPrompt *self, DBusMessageIter *iter);
+};
+
+GType               gkd_secrets_prompt_get_type               (void);
+
+DBusMessage*        gkd_secrets_prompt_dispatch               (GkdSecretsPrompt *self,
+                                                               DBusMessage *message);
+
+const gchar*        gkd_secrets_prompt_get_caller             (GkdSecretsPrompt *self);
+
+const gchar*        gkd_secrets_prompt_get_object_path        (GkdSecretsPrompt *self);
+
+GP11Object*         gkd_secrets_prompt_lookup_collection      (GkdSecretsPrompt *self,
+                                                               const gchar *objpath);
+
+void                gkd_secrets_prompt_complete               (GkdSecretsPrompt *self);
+
+void                gkd_secrets_prompt_dismiss                (GkdSecretsPrompt *self);
+
+#endif /* __GKD_SECRETS_PROMPT_H__ */
diff --git a/daemon/dbus/gkd-secrets-service.c b/daemon/dbus/gkd-secrets-service.c
index b27d0e3..68871fa 100644
--- a/daemon/dbus/gkd-secrets-service.c
+++ b/daemon/dbus/gkd-secrets-service.c
@@ -23,9 +23,11 @@
 
 #include "gkd-dbus-util.h"
 #include "gkd-secrets-objects.h"
+#include "gkd-secrets-prompt.h"
 #include "gkd-secrets-service.h"
 #include "gkd-secrets-session.h"
 #include "gkd-secrets-types.h"
+#include "gkd-secrets-unlock.h"
 
 #include "egg/egg-unix-credentials.h"
 
@@ -67,7 +69,8 @@ typedef struct _ServiceClient {
 	pid_t caller_pid;
 	CK_G_APPLICATION app;
 	GP11Session *pkcs11_session;
-	GList *sessions;
+	GHashTable *sessions;
+	GHashTable *prompts;
 } ServiceClient;
 
 /* Forward declaration */
@@ -113,17 +116,17 @@ object_path_has_prefix (const gchar *path, const gchar *prefix)
 }
 
 static void
-dispose_session (GkdSecretsSession *session)
+dispose_and_unref (gpointer object)
 {
-	g_object_run_dispose (G_OBJECT (session));
-	g_object_unref (session);
+	g_return_if_fail (G_IS_OBJECT (object));
+	g_object_run_dispose (G_OBJECT (object));
+	g_object_unref (object);
 }
 
 static void
 free_client (gpointer data)
 {
 	ServiceClient *client = data;
-	GList *l;
 
 	if (!client)
 		return;
@@ -140,9 +143,9 @@ free_client (gpointer data)
 		g_object_unref (client->pkcs11_session);
 	}
 
-	/* The secrets API sessions client has open */
-	for (l = client->sessions; l; l = g_list_next (l))
-		dispose_session (l->data);
+	/* The sessions and prompts the client has open */
+	g_hash_table_destroy (client->sessions);
+	g_hash_table_destroy (client->prompts);
 
 	g_free (client);
 }
@@ -206,6 +209,8 @@ on_get_connection_unix_process_id (DBusPendingCall *pending, gpointer user_data)
 		if (caller_pid != 0)
 			client->caller_exec = egg_unix_credentials_executable (caller_pid);
 		client->app.applicationData = client;
+		client->sessions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, dispose_and_unref);
+		client->prompts = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, dispose_and_unref);
 
 		g_hash_table_replace (self->clients, client->caller_peer, client);
 	}
@@ -256,7 +261,6 @@ initialize_service_client (GkdSecretsService *self, DBusMessage *message)
 	dbus_pending_call_unref (pending);
 }
 
-
 /* -----------------------------------------------------------------------------
  * DBUS
  */
@@ -355,16 +359,62 @@ service_method_open_session (GkdSecretsService *self, DBusMessage *message)
 	/* Take ownership of the session */
 	client = g_hash_table_lookup (self->clients, caller);
 	g_return_val_if_fail (client, NULL);
-	client->sessions = g_list_prepend (client->sessions, session);
+	path = gkd_secrets_session_get_object_path (session);
+	g_return_val_if_fail (!g_hash_table_lookup (client->sessions, path), NULL);
+	g_hash_table_replace (client->sessions, (gpointer)path, session);
 
 	/* Return the response */
-	path = gkd_secrets_session_get_object_path (session);
 	reply = dbus_message_new_method_return (message);
 	dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
 	return reply;
 }
 
 static DBusMessage*
+service_method_unlock (GkdSecretsService *self, DBusMessage *message)
+{
+	char **objpaths, **o;
+	GkdSecretsUnlock *unlock;
+	ServiceClient *client;
+	DBusMessage *reply;
+	const char *caller;
+	const gchar *path;
+
+	if (!dbus_message_get_args (message, NULL,
+	                            DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &objpaths,
+	                            DBUS_TYPE_INVALID))
+		return NULL;
+
+	caller = dbus_message_get_sender (message);
+	unlock = gkd_secrets_unlock_new (self, caller);
+	for (o = objpaths; o && *o; ++o)
+		gkd_secrets_unlock_queue (unlock, *o);
+	dbus_free_string_array (objpaths);
+
+	/* So do we need to prompt? */
+	if (gkd_secrets_unlock_have_queued (unlock)) {
+		client = g_hash_table_lookup (self->clients, caller);
+		g_return_val_if_fail (client, NULL);
+		path = gkd_secrets_prompt_get_object_path (GKD_SECRETS_PROMPT (unlock));
+		g_hash_table_replace (client->sessions, (gpointer)path, g_object_ref (unlock));
+
+	/* No need to prompt */
+	} else {
+		path = "";
+	}
+
+	reply = dbus_message_new_method_return (message);
+	dbus_message_append_args (reply,
+	                          DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, gkd_secrets_unlock_get_results (unlock),
+	                          DBUS_TYPE_OBJECT_PATH, path,
+	                          DBUS_TYPE_INVALID);
+
+	gkd_secrets_unlock_reset_results (unlock);
+	g_object_unref (unlock);
+
+	return reply;
+}
+
+static DBusMessage*
 service_message_handler (GkdSecretsService *self, DBusMessage *message)
 {
 	DBusMessage *reply = NULL;
@@ -388,9 +438,9 @@ service_message_handler (GkdSecretsService *self, DBusMessage *message)
 	if (dbus_message_is_method_call (message, SECRETS_SERVICE_INTERFACE, "SearchItems"))
 		return gkd_secrets_objects_handle_search_items (self->objects, message, NULL);
 
-	/* org.freedesktop.Secrets.Service.BeginAuthenticate() */
-	if (dbus_message_is_method_call (message, SECRETS_SERVICE_INTERFACE, "BeginAuthenticate"))
-		g_return_val_if_reached (NULL); /* TODO: Need to implement */
+	/* org.freedesktop.Secrets.Service.Unlock() */
+	if (dbus_message_is_method_call (message, SECRETS_SERVICE_INTERFACE, "Unlock"))
+		reply = service_method_unlock (self, message);
 
 	/* org.freedesktop.Secrets.Service.CompleteAuthenticate() */
 	if (dbus_message_is_method_call (message, SECRETS_SERVICE_INTERFACE, "CompleteAuthenticate"))
@@ -421,7 +471,7 @@ service_dispatch_message (GkdSecretsService *self, DBusMessage *message)
 	const gchar *caller;
 	ServiceClient *client;
 	const gchar *path;
-	GList *l;
+	gpointer object;
 
 	g_assert (GKD_SECRETS_IS_SERVICE (self));
 	g_assert (message);
@@ -447,12 +497,15 @@ service_dispatch_message (GkdSecretsService *self, DBusMessage *message)
 
 	/* Dispatched to a session, find a session in this client */
 	if (object_path_has_prefix (path, SECRETS_SESSION_PREFIX)) {
-		for (l = client->sessions; l; l = g_list_next (l)) {
-			if (g_str_equal (path, gkd_secrets_session_get_object_path (l->data))) {
-				reply = gkd_secrets_session_dispatch (l->data, message);
-				break;
-			}
-		}
+		object = g_hash_table_lookup (client->sessions, path);
+		if (object != NULL)
+			reply = gkd_secrets_session_dispatch (object, message);
+
+	/* Dispatched to a prompt, find a prompt in this client */
+	} else if (object_path_has_prefix (path, SECRETS_PROMPT_PREFIX)) {
+		object = g_hash_table_lookup (client->prompts, path);
+		if (object != NULL)
+			reply = gkd_secrets_prompt_dispatch (object, message);
 
 	/* Dispatched to a collection, off it goes */
 	} else if (object_path_has_prefix (path, SECRETS_COLLECTION_PREFIX)) {
@@ -607,7 +660,7 @@ gkd_secrets_service_dispose (GObject *obj)
 		self->match_rule = NULL;
 	}
 
-	/* Closes all the sessions */
+	/* Closes all the clients */
 	g_hash_table_remove_all (self->clients);
 
 	/* Hide all the objects */
@@ -707,6 +760,20 @@ gkd_secrets_service_class_init (GkdSecretsServiceClass *klass)
  * PUBLIC
  */
 
+void
+gkd_secrets_service_send (GkdSecretsService *self, DBusMessage *message)
+{
+	g_return_if_fail (GKD_SECRETS_IS_SERVICE (self));
+	dbus_connection_send (self->connection, message, NULL);
+}
+
+GkdSecretsObjects*
+gkd_secrets_service_get_objects (GkdSecretsService *self)
+{
+	g_return_val_if_fail (GKD_SECRETS_IS_SERVICE (self), NULL);
+	return self->objects;
+}
+
 DBusConnection*
 gkd_secrets_service_get_connection (GkdSecretsService *self)
 {
@@ -757,6 +824,7 @@ gkd_secrets_service_close_session (GkdSecretsService *self, GkdSecretsSession *s
 {
 	ServiceClient *client;
 	const gchar *caller;
+	const gchar *path;
 
 	g_return_if_fail (GKD_SECRETS_IS_SERVICE (self));
 	g_return_if_fail (GKD_SECRETS_IS_SESSION (session));
@@ -765,8 +833,8 @@ gkd_secrets_service_close_session (GkdSecretsService *self, GkdSecretsSession *s
 	client = g_hash_table_lookup (self->clients, caller);
 	g_return_if_fail (client);
 
-	client->sessions = g_list_remove (client->sessions, session);
-	dispose_session (session);
+	path = gkd_secrets_session_get_object_path (session);
+	g_hash_table_remove (client->sessions, path);
 }
 
 #if 0
diff --git a/daemon/dbus/gkd-secrets-service.h b/daemon/dbus/gkd-secrets-service.h
index 063b6d5..f63dfc9 100644
--- a/daemon/dbus/gkd-secrets-service.h
+++ b/daemon/dbus/gkd-secrets-service.h
@@ -57,12 +57,18 @@ GP11Slot*               gkd_secrets_service_get_pkcs11_slot        (GkdSecretsSe
 GP11Session*            gkd_secrets_service_get_pkcs11_session     (GkdSecretsService *self,
                                                                     const gchar *caller);
 
+GkdSecretsObjects*      gkd_secrets_service_get_objects            (GkdSecretsService *self);
+
 #if 0
 void                    gkd_secrets_service_refresh                (GkdSecretsService *self);
 #endif
 
 void                    gkd_secrets_service_close_session          (GkdSecretsService *self,
                                                                     GkdSecretsSession *sess);
+
+void                    gkd_secrets_service_send                   (GkdSecretsService *self,
+                                                                    DBusMessage *message);
+
 #if 0
 GkdSecretsCollection*   gkd_secrets_service_get_default_collection (GkdSecretsService *self);
 #endif
diff --git a/daemon/dbus/gkd-secrets-types.h b/daemon/dbus/gkd-secrets-types.h
index 081b89d..39183d7 100644
--- a/daemon/dbus/gkd-secrets-types.h
+++ b/daemon/dbus/gkd-secrets-types.h
@@ -25,9 +25,10 @@
 #define BUS_INTERFACE                  "org.freedesktop.DBus"
 #define PROPERTIES_INTERFACE           "org.freedesktop.DBus.Properties"
 
-#define SECRETS_SERVICE_INTERFACE      "org.freedesktop.Secrets.Service"
 #define SECRETS_COLLECTION_INTERFACE   "org.freedesktop.Secrets.Collection"
 #define SECRETS_ITEM_INTERFACE         "org.freedesktop.Secrets.Item"
+#define SECRETS_PROMPT_INTERFACE       "org.freedesktop.Secrets.Prompt"
+#define SECRETS_SERVICE_INTERFACE      "org.freedesktop.Secrets.Service"
 
 #define SECRETS_SERVICE_PATH           "/org/freedesktop/secrets"
 #define SECRETS_SERVICE                "org.freedesktop.secrets"
@@ -35,6 +36,7 @@
 #define SECRETS_INTERFACE_PREFIX       "org.freedesktop.Secrets"
 #define SECRETS_COLLECTION_PREFIX      "/org/freedesktop/secrets/collection"
 #define SECRETS_SESSION_PREFIX         "/org/freedesktop/secrets/session"
+#define SECRETS_PROMPT_PREFIX          "/org/freedesktop/secrets/prompt"
 
 #define SECRETS_ERROR_ALREADY_EXISTS   "org.freedesktop.Secrets.Error.AlreadyExists"
 #define SECRETS_ERROR_IS_LOCKED        "org.freedesktop.Secrets.Error.IsLocked"
@@ -44,7 +46,9 @@
 typedef struct _GkdSecretsCollection GkdSecretsCollection;
 typedef struct _GkdSecretsItem GkdSecretsItem;
 typedef struct _GkdSecretsObjects GkdSecretsObjects;
+typedef struct _GkdSecretsPrompt GkdSecretsPrompt;
 typedef struct _GkdSecretsService GkdSecretsService;
 typedef struct _GkdSecretsSession GkdSecretsSession;
+typedef struct _GkdSecretsUnlock GkdSecretsUnlock;
 
 #endif /* __GKD_SECRETS_TYPES_H__ */
diff --git a/daemon/dbus/gkd-secrets-unlock.c b/daemon/dbus/gkd-secrets-unlock.c
new file mode 100644
index 0000000..ce7df4c
--- /dev/null
+++ b/daemon/dbus/gkd-secrets-unlock.c
@@ -0,0 +1,351 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 "gkd-secrets-service.h"
+#include "gkd-secrets-prompt.h"
+#include "gkd-secrets-types.h"
+#include "gkd-secrets-unlock.h"
+
+#include "egg/egg-secure-memory.h"
+
+#include "pkcs11/pkcs11i.h"
+
+#include <glib/gi18n.h>
+
+#include <gp11/gp11.h>
+
+#include <string.h>
+
+struct _GkdSecretsUnlock {
+	GkdSecretsPrompt parent;
+	GQueue *queued;
+	gchar *current;
+	GArray *results;
+};
+
+G_DEFINE_TYPE (GkdSecretsUnlock, gkd_secrets_unlock, GKD_SECRETS_TYPE_UNLOCK);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static void
+prepare_unlock_prompt (GkdSecretsUnlock *self, GP11Object *coll)
+{
+	GError *error = NULL;
+	GkdPrompt *prompt;
+	gpointer data;
+	gsize n_data;
+	gchar *label;
+	gchar *text;
+
+	g_assert (GKD_SECRETS_IS_UNLOCK (self));
+	g_assert (coll);
+
+	prompt = GKD_PROMPT (self);
+
+	data = gp11_object_get_data (coll, CKA_LABEL, &n_data, &error);
+	if (!data) {
+		g_warning ("couldn't get label for collection: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	if (!data || !n_data)
+		label = g_strdup (_("Unnamed"));
+	else
+		label = g_strndup (data, n_data);
+	g_free (data);
+
+	gkd_prompt_reset (prompt);
+
+	gkd_prompt_set_title (prompt, _("Unlock Keyring"));
+
+	text = g_markup_printf_escaped (_("Enter password for keyring '%s' to unlock"), label);
+	gkd_prompt_set_primary_text (prompt, text);
+	g_free (text);
+
+	text = g_markup_printf_escaped (_("An application wants access to the keyring '%s', but it is locked"), label);
+	gkd_prompt_set_secondary_text (prompt, text);
+	g_free (text);
+
+	gkd_prompt_hide_widget (prompt, "name_area");
+	gkd_prompt_hide_widget (prompt, "original_area");
+	gkd_prompt_hide_widget (prompt, "confirm_area");
+	gkd_prompt_hide_widget (prompt, "details_area");
+
+	g_free (label);
+}
+
+static void
+set_warning_wrong (GkdSecretsUnlock *self)
+{
+	g_assert (GKD_SECRETS_IS_UNLOCK (self));
+	gkd_prompt_set_warning (GKD_PROMPT (self), _("The unlock password was incorrect"));
+}
+
+static gboolean
+authenticate_collection (GkdSecretsUnlock *self, GP11Object *coll, gboolean *locked)
+{
+	GError *error = NULL;
+	GP11Attributes *attrs;
+	GP11Session *session;
+	GP11Object *auth;
+	gchar *password;
+	gsize n_password;
+
+	g_assert (GKD_SECRETS_IS_UNLOCK (self));
+	g_assert (locked);
+	g_assert (coll);
+
+	attrs = gp11_object_get (coll, &error, CKA_G_LOCKED, GP11_INVALID);
+	if (!attrs) {
+		if (error->code != CKR_OBJECT_HANDLE_INVALID)
+			g_warning ("couldn't check locked status of collection: %s",
+			           error->message);
+		g_clear_error (&error);
+		return FALSE;
+	}
+
+	if (!gp11_attributes_find_boolean (attrs, CKA_G_LOCKED, locked))
+		g_return_val_if_reached (FALSE);
+	if (!locked)
+		return TRUE;
+
+	session = gp11_object_get_session (coll);
+	g_return_val_if_fail (session, FALSE);
+
+	if (!gkd_prompt_has_response (GKD_PROMPT (self)))
+		return TRUE; /* Bail out early, just checking locked status */
+
+	password = gkd_prompt_get_password (GKD_PROMPT (self), "password");
+	n_password = password ? strlen (password) : 0;
+	auth = gp11_session_create_object (session, &error,
+	                                   CKA_CLASS, GP11_ULONG, CKO_GNOME_AUTHENTICATOR,
+	                                   CKA_GNOME_OBJECT, GP11_ULONG, gp11_object_get_handle (coll),
+	                                   CKA_GNOME_TRANSIENT, GP11_BOOLEAN, TRUE,
+	                                   CKA_TOKEN, GP11_BOOLEAN, FALSE,
+	                                   CKA_VALUE, n_password, password,
+	                                   GP11_INVALID);
+	egg_secure_strfree (password);
+	g_object_unref (session);
+
+	if (auth) {
+		g_object_unref (auth);
+		*locked = FALSE;
+		return TRUE; /* Operation succeeded, and unlocked */
+
+	} else {
+		if (error->code == (gint)CKR_PIN_INCORRECT) {
+			g_clear_error (&error);
+			*locked = TRUE;
+			return TRUE; /* Operation succeded, although not unlocked*/
+
+		} else {
+			g_warning ("couldn't create authenticator for collection: %s",
+			           error->message);
+			g_clear_error (&error);
+			return FALSE; /* Operation failed */
+		}
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static void
+gkd_secrets_unlock_prompt_ready (GkdSecretsPrompt *base)
+{
+	GkdSecretsUnlock *self = GKD_SECRETS_UNLOCK (base);
+	GP11Object *coll;
+	gboolean locked;
+	gchar *objpath;
+
+	/* Already prompted for an item */
+	if (self->current) {
+		coll = gkd_secrets_prompt_lookup_collection (base, self->current);
+
+		/* If the object or collection is gone, no need to unlock */
+		if (coll == NULL) {
+			g_free (self->current);
+			self->current = NULL;
+
+		} else {
+			/* Try to unlock the collection */
+			if (!authenticate_collection (self, coll, &locked)) {
+				g_free (self->current);
+				self->current = NULL;
+
+			/* Collection still locked, prompt again */
+			} else if (locked) {
+				prepare_unlock_prompt (self, coll);
+				set_warning_wrong (self);
+
+			/* Collection not locked, done with this one */
+			} else {
+				g_array_append_val (self->results, self->current);
+				self->current = NULL;
+			}
+
+			g_object_unref (coll);
+		}
+	}
+
+	/* Queue the next item? */
+	while (!self->current) {
+		objpath = g_queue_pop_head (self->queued);
+
+		/* Nothing more to prompt for? */
+		if (!objpath) {
+			gkd_secrets_prompt_complete (base);
+			break;
+		}
+
+		/* Find the collection, make sure it's still around */
+		coll = gkd_secrets_prompt_lookup_collection (base, objpath);
+		if (coll == NULL) {
+			g_free (objpath);
+			continue;
+		}
+
+		/* Make sure this collection still needs unlocking */
+		if (!authenticate_collection (self, coll, &locked)) {
+			g_object_unref (coll);
+			g_free (objpath);
+			continue;
+		} else if (!locked) {
+			g_array_append_val (self->results, self->current);
+			g_object_unref (coll);
+			continue;
+		}
+
+		prepare_unlock_prompt (self, coll);
+		g_object_unref (coll);
+		self->current = objpath;
+	}
+}
+
+static void
+gkd_secrets_unlock_encode_result (GkdSecretsPrompt *base, DBusMessageIter *iter)
+{
+	GkdSecretsUnlock *self = GKD_SECRETS_UNLOCK (base);
+	DBusMessageIter variant;
+	DBusMessageIter array;
+	gint i;
+
+	dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, "ao", &variant);
+	dbus_message_iter_open_container (&variant, DBUS_TYPE_ARRAY, "o", &array);
+
+	for (i = 0; i < self->results->len; ++i)
+		dbus_message_iter_append_basic (&array, DBUS_TYPE_OBJECT_PATH,
+		                                g_array_index (self->results, gchar*, i));
+
+	dbus_message_iter_close_container (&variant, &array);
+	dbus_message_iter_close_container (iter, &variant);
+}
+
+static void
+gkd_secrets_unlock_init (GkdSecretsUnlock *self)
+{
+	self->queued = g_queue_new ();
+	self->results = g_array_new (TRUE, TRUE, sizeof (gchar*));
+}
+
+static void
+gkd_secrets_unlock_finalize (GObject *obj)
+{
+	GkdSecretsUnlock *self = GKD_SECRETS_UNLOCK (obj);
+
+	if (self->queued) {
+		while (!g_queue_is_empty (self->queued))
+			g_free (g_queue_pop_head (self->queued));
+		g_queue_free (self->queued);
+		self->queued = NULL;
+	}
+
+	if (self->results) {
+		gkd_secrets_unlock_reset_results (self);
+		g_array_free (self->results, TRUE);
+		self->results = NULL;
+	}
+
+	g_free (self->current);
+	self->current = NULL;
+
+	G_OBJECT_CLASS (gkd_secrets_unlock_parent_class)->finalize (obj);
+}
+
+static void
+gkd_secrets_unlock_class_init (GkdSecretsUnlockClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GkdSecretsPromptClass *prompt_class = GKD_SECRETS_PROMPT_CLASS (klass);
+
+	gobject_class->finalize = gkd_secrets_unlock_finalize;
+	prompt_class->prompt_ready = gkd_secrets_unlock_prompt_ready;
+	prompt_class->encode_result = gkd_secrets_unlock_encode_result;
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+GkdSecretsUnlock*
+gkd_secrets_unlock_new (GkdSecretsService *service, const gchar *caller)
+{
+	return g_object_new (GKD_SECRETS_TYPE_UNLOCK, "service", service, "caller", caller, NULL);
+}
+
+void
+gkd_secrets_unlock_queue (GkdSecretsUnlock *self, const gchar *objpath)
+{
+	g_return_if_fail (GKD_SECRETS_IS_UNLOCK (self));
+	g_return_if_fail (objpath);
+	g_queue_push_tail (self->queued, g_strdup (objpath));
+}
+
+gboolean
+gkd_secrets_unlock_have_queued (GkdSecretsUnlock *self)
+{
+	g_return_val_if_fail (GKD_SECRETS_IS_UNLOCK (self), FALSE);
+	return !g_queue_is_empty (self->queued) && !self->current;
+}
+
+gchar**
+gkd_secrets_unlock_get_results (GkdSecretsUnlock *self)
+{
+	g_return_val_if_fail (GKD_SECRETS_IS_UNLOCK (self), NULL);
+	return (gchar**)self->results->data;
+}
+
+void
+gkd_secrets_unlock_reset_results (GkdSecretsUnlock *self)
+{
+	gint i;
+
+	g_return_if_fail (GKD_SECRETS_IS_UNLOCK (self));
+
+	for (i = 0; i < self->results->len; ++i)
+		g_free (g_array_index (self->results, gchar*, i));
+	g_array_set_size (self->results, 0);
+}
diff --git a/daemon/dbus/gkd-secrets-unlock.h b/daemon/dbus/gkd-secrets-unlock.h
new file mode 100644
index 0000000..0dcdff5
--- /dev/null
+++ b/daemon/dbus/gkd-secrets-unlock.h
@@ -0,0 +1,56 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2009 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef __GKD_SECRETS_UNLOCK_H__
+#define __GKD_SECRETS_UNLOCK_H__
+
+#include <glib-object.h>
+
+#include "gkd-secrets-types.h"
+
+#define GKD_SECRETS_TYPE_UNLOCK               (gkd_secrets_unlock_get_type ())
+#define GKD_SECRETS_UNLOCK(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GKD_SECRETS_TYPE_UNLOCK, GkdSecretsUnlock))
+#define GKD_SECRETS_UNLOCK_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GKD_SECRETS_TYPE_UNLOCK, GkdSecretsUnlockClass))
+#define GKD_SECRETS_IS_UNLOCK(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GKD_SECRETS_TYPE_UNLOCK))
+#define GKD_SECRETS_IS_UNLOCK_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GKD_SECRETS_TYPE_UNLOCK))
+#define GKD_SECRETS_UNLOCK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GKD_SECRETS_TYPE_UNLOCK, GkdSecretsUnlockClass))
+
+typedef struct _GkdSecretsUnlockClass GkdSecretsUnlockClass;
+
+struct _GkdSecretsUnlockClass {
+	GObjectClass parent_class;
+};
+
+GType               gkd_secrets_unlock_get_type               (void);
+
+GkdSecretsUnlock*   gkd_secrets_unlock_new                    (GkdSecretsService *service,
+                                                               const gchar *caller);
+
+void                gkd_secrets_unlock_queue                  (GkdSecretsUnlock *self,
+                                                               const gchar *objpath);
+
+gboolean            gkd_secrets_unlock_have_queued            (GkdSecretsUnlock *self);
+
+gchar**             gkd_secrets_unlock_get_results            (GkdSecretsUnlock *self);
+
+void                gkd_secrets_unlock_reset_results          (GkdSecretsUnlock *self);
+
+#endif /* __GKD_SECRETS_UNLOCK_H__ */
diff --git a/daemon/prompt/Makefile.am b/daemon/prompt/Makefile.am
new file mode 100644
index 0000000..57036aa
--- /dev/null
+++ b/daemon/prompt/Makefile.am
@@ -0,0 +1,29 @@
+
+INCLUDES = \
+	-I$(top_srcdir) \
+	-I$(top_builddir) \
+	-DLIBEXECDIR=\""$(libexecdir)"\" \
+	$(GOBJECT_CFLAGS) \
+	$(GLIB_CFLAGS)
+
+noinst_LTLIBRARIES = libgkd-prompt.la
+
+BUILT_SOURCES = \
+	gkd-prompt-marshal.c gkd-prompt-marshal.h
+
+libgkd_prompt_la_SOURCES = \
+	gkd-prompt.c gkd-prompt.h \
+	$(BUILT_SOURCES)
+
+libgkd_prompt_la_LIBADD = \
+	$(GLIB_LIBS) \
+	$(GOBJECT_LIBS)
+
+gkd-prompt-marshal.h: gkd-prompt-marshal.list $(GLIB_GENMARSHAL)
+	$(GLIB_GENMARSHAL) $< --header --prefix=gkd_prompt_marshal > $@
+
+gkd-prompt-marshal.c: gkd-prompt-marshal.list $(GLIB_GENMARSHAL)
+	echo "#include \"gkd-prompt-marshal.h\"" > $@ && \
+	$(GLIB_GENMARSHAL) $< --body --prefix=gkd_prompt_marshal >> $@
+
+EXTRA_DIST = gkd-prompt-marshal.list
diff --git a/daemon/prompt/gkd-prompt-marshal.list b/daemon/prompt/gkd-prompt-marshal.list
new file mode 100644
index 0000000..6ff7797
--- /dev/null
+++ b/daemon/prompt/gkd-prompt-marshal.list
@@ -0,0 +1 @@
+BOOLEAN:VOID
diff --git a/daemon/prompt/gkd-prompt.c b/daemon/prompt/gkd-prompt.c
new file mode 100644
index 0000000..89b0cc9
--- /dev/null
+++ b/daemon/prompt/gkd-prompt.c
@@ -0,0 +1,975 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 "gkd-prompt.h"
+#include "gkd-prompt-marshal.h"
+
+#include "egg/egg-cleanup.h"
+#include "egg/egg-dh.h"
+#include "egg/egg-hex.h"
+#include "egg/egg-secure-memory.h"
+#include "egg/egg-spawn.h"
+
+#include <gcrypt.h>
+
+enum {
+	RESPONDED,
+	COMPLETED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _GkdPromptPrivate {
+	GKeyFile *input;
+	GKeyFile *output;
+	gchar *executable;
+	gboolean completed;
+	gboolean failure;
+
+	/* Transport crypto */
+	gcry_mpi_t secret;
+	gcry_mpi_t prime;
+	guchar *key;
+	gsize n_key;
+
+	/* Information about child */
+	GPid pid;
+
+	/* Input and output */
+	gchar *in_data;
+	gsize in_offset;
+	gsize in_length;
+	GString *out_data;
+	GString *err_data;
+	guint io_tag;
+};
+
+G_DEFINE_TYPE (GkdPrompt, gkd_prompt, G_TYPE_OBJECT);
+
+/* -----------------------------------------------------------------------------
+ * INTERNAL
+ */
+
+static void
+kill_process (GkdPrompt *self)
+{
+	if (self->pv->pid)
+		kill (self->pv->pid, SIGTERM);
+}
+
+static void
+mark_completed (GkdPrompt *self)
+{
+	g_assert (!self->pv->completed);
+	self->pv->completed = TRUE;
+	g_signal_emit (self, signals[COMPLETED], 0);
+}
+
+static gboolean
+on_standard_input (int fd, gpointer user_data)
+{
+	GkdPrompt *self = GKD_PROMPT (self);
+	gssize ret;
+
+	g_return_val_if_fail (GKD_IS_PROMPT (self), FALSE);
+
+	if (self->pv->in_offset >= self->pv->in_length)
+		return FALSE;
+
+	g_assert (self->pv->in_data);
+	ret = egg_spawn_write_input (fd, self->pv->in_data + self->pv->in_offset,
+	                             self->pv->in_length - self->pv->in_offset);
+
+	if (ret <= 0) {
+		g_warning ("couldn't write all input to prompt process");
+		self->pv->failure = TRUE;
+		return FALSE;
+	}
+
+	self->pv->in_offset += ret;
+	return TRUE;
+}
+
+static gboolean
+on_standard_output (int fd, gpointer user_data)
+{
+	GkdPrompt *self = GKD_PROMPT (self);
+	gchar buffer[1024];
+	gssize ret;
+
+	g_return_val_if_fail (GKD_IS_PROMPT (self), FALSE);
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read output data from prompt process");
+		self->pv->failure = TRUE;
+		return FALSE;
+	}
+
+	if (!self->pv->out_data)
+		self->pv->out_data = g_string_new_len (buffer, ret);
+	else
+		g_string_append_len (self->pv->out_data, buffer, ret);
+
+	return (ret > 0);
+}
+
+static gboolean
+on_standard_error (int fd, gpointer user_data)
+{
+	GkdPrompt *self = GKD_PROMPT (self);
+	gchar buffer[1024];
+	gssize ret;
+	gchar *ptr;
+
+	g_return_val_if_fail (GKD_IS_PROMPT (self), FALSE);
+
+	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
+	if (ret < 0) {
+		g_warning ("couldn't read error data from prompt process");
+		self->pv->failure = TRUE;
+		return FALSE;
+	}
+
+	if (!self->pv->err_data)
+		self->pv->err_data = g_string_new_len (buffer, ret);
+	else
+		g_string_append_len (self->pv->err_data, buffer, ret);
+
+	/* Print all stderr lines as messages */
+	while ((ptr = strchr (self->pv->err_data->str, '\n')) != NULL) {
+		*ptr = '\0';
+		g_message ("%s", self->pv->err_data->str);
+		g_string_erase (self->pv->err_data, 0,
+		                ptr - self->pv->err_data->str);
+	}
+
+	return ret > 0;
+}
+
+static void
+on_io_completed (gpointer user_data)
+{
+	GkdPrompt *self = GKD_PROMPT (self);
+	GError *error = NULL;
+
+	g_return_if_fail (GKD_IS_PROMPT (self));
+
+	g_assert (!self->pv->output);
+	g_assert (self->pv->io_tag != 0);
+	g_assert (!self->pv->completed);
+
+	/* Should be the last call we receive */
+	self->pv->io_tag = 0;
+
+	/* Print out any remaining errors */
+	if (self->pv->err_data && self->pv->err_data->len)
+		g_message ("%s", self->pv->err_data->str);
+
+	/* Parse the output data properly */
+	if (!self->pv->failure) {
+		self->pv->output = g_key_file_new ();
+		if (!g_key_file_load_from_data (self->pv->output, self->pv->out_data->str,
+						self->pv->out_data->len, G_KEY_FILE_NONE, &error)) {
+			g_key_file_free (self->pv->output);
+			g_warning ("couldn't parse output from prompt: %s",
+				   error && error->message ? error->message : "");
+			g_clear_error (&error);
+			self->pv->failure = TRUE;
+		} else {
+			g_signal_emit (self, signals[RESPONDED], 0);
+		}
+	}
+}
+
+static void
+on_child_exited (GPid pid, gint status, gpointer user_data)
+{
+	GkdPrompt *self = GKD_PROMPT (self);
+	gint code;
+
+	if (pid == self->pv->pid) {
+		self->pv->pid = 0;
+		if (!self->pv->failure) {
+			if (WIFEXITED (status)) {
+				code = WEXITSTATUS (status);
+				if (code != 0) {
+					g_warning ("prompt process exited with failure code: %d", code);
+					self->pv->failure = TRUE;
+				}
+			} else if (WIFSIGNALED (status)) {
+				code = WTERMSIG (status);
+				g_warning ("prompt process was killed with signal: %d", code);
+				self->pv->failure = TRUE;
+			}
+		}
+	}
+
+	g_spawn_close_pid (pid);
+}
+
+static gboolean
+encode_input_mpi (GkdPrompt *self, const gchar *section,
+                  const gchar *field, gcry_mpi_t mpi)
+{
+	gcry_error_t gcry;
+	guchar *data;
+	gsize n_data;
+
+	g_assert (self->pv->input);
+
+	/* Get the size */
+	gcry = gcry_mpi_print (GCRYMPI_FMT_HEX, NULL, 0, &n_data, mpi);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	data = g_malloc0 (n_data + 1);
+
+	/* Write into buffer */
+	gcry = gcry_mpi_print (GCRYMPI_FMT_HEX, data, n_data, &n_data, mpi);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	g_key_file_set_value (self->pv->input, section, field, (gchar*)data);
+	g_free (data);
+
+	return TRUE;
+}
+
+static void
+prepare_transport_crypto (GkdPrompt *self)
+{
+	gcry_mpi_t pub, base;
+
+	g_assert (!self->pv->prime);
+	g_assert (!self->pv->secret);
+
+	/* Figure out our prime, base, public and secret bits */
+	if (!egg_dh_default_params (&self->pv->prime, &base) ||
+	    !egg_dh_gen_secret (self->pv->prime, base, &pub, &self->pv->secret))
+		g_return_if_reached ();
+
+	/* Send over the prime, base, and public bits */
+	if (!encode_input_mpi (self, "transport", "prime", self->pv->prime) ||
+	    !encode_input_mpi (self, "transport", "base", base) ||
+	    !encode_input_mpi (self, "transport", "public", pub))
+		g_return_if_reached ();
+
+	gcry_mpi_release (base);
+	gcry_mpi_release (pub);
+}
+
+static gboolean
+decode_output_mpi (GkdPrompt *self, const gchar *section,
+                   const gchar *field, gcry_mpi_t *mpi)
+{
+	gcry_error_t gcry;
+	gchar *data;
+
+	g_assert (self->pv->output);
+
+	data = g_key_file_get_value (self->pv->output, section, field, NULL);
+	if (!data)
+		return FALSE;
+
+	gcry = gcry_mpi_scan (mpi, GCRYMPI_FMT_HEX, data, 0, NULL);
+	g_free (data);
+
+	return (gcry == 0);
+}
+
+static guchar*
+decode_output_hex (GkdPrompt *self, const gchar *section,
+                   const gchar *field, gsize *n_result)
+{
+	guchar *result;
+	gchar *data;
+
+	g_assert (self->pv->output);
+
+	data = g_key_file_get_value (self->pv->output, section, field, NULL);
+	if (!data)
+		return NULL;
+
+	result = egg_hex_decode (data, -1, n_result);
+	g_free (data);
+	return result;
+}
+
+static gboolean
+receive_transport_crypto (GkdPrompt *self)
+{
+	gcry_mpi_t key, peer;
+	gcry_error_t gcry;
+	guchar *buffer;
+	gsize n_buffer;
+	gboolean ret;
+
+	g_assert (self->pv->output);
+
+	if (!decode_output_mpi (self, "transport", "public", &peer))
+		return FALSE;
+
+	ret = egg_dh_gen_key (peer, self->pv->secret, self->pv->prime, &key);
+	gcry_mpi_release (peer);
+	if (!ret)
+		return FALSE;
+
+	/* Write the key out to raw data */
+	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &n_buffer, peer);
+	g_return_val_if_fail (gcry == 0, FALSE);
+	buffer = egg_secure_alloc (n_buffer);
+	gcry = gcry_mpi_print (GCRYMPI_FMT_USG, buffer, n_buffer, &n_buffer, peer);
+	g_return_val_if_fail (gcry == 0, FALSE);
+
+	/* Allocate memory for hashed key */
+	egg_secure_free (self->pv->key);
+	g_assert (16 == gcry_md_get_algo_dlen (GCRY_MD_MD5));
+	self->pv->key = egg_secure_alloc (16);
+	self->pv->n_key = 16;
+
+	/* Use that as the input to derive a key for 128-bit AES */
+	gcry_md_hash_buffer (GCRY_MD_MD5, self->pv->key, buffer, n_buffer);
+
+	egg_secure_free (buffer);
+	return TRUE;
+}
+
+static gchar*
+decrypt_transport_crypto (GkdPrompt *self, guchar *data, gsize n_data,
+                          guchar *iv, gsize n_iv)
+{
+	gcry_cipher_hd_t cih;
+	gcry_error_t gcry;
+	gchar *result;
+	gsize pos;
+
+	g_assert (self->pv->key);
+	g_assert (self->pv->n_key == 16);
+
+	if (n_iv != 16) {
+		g_warning ("prompt response has iv of wrong length");
+		return NULL;
+	}
+
+	if (n_data % 16 != 0) {
+		g_warning ("prompt response encrypted password of wrong length");
+		return NULL;
+	}
+
+	gcry = gcry_cipher_open (&cih, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
+	if (gcry) {
+		g_warning ("couldn't create aes cipher context: %s", gcry_strerror (gcry));
+		return NULL;
+	}
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setkey (cih, self->pv->key, 16);
+	g_return_val_if_fail (gcry == 0, NULL);
+
+	/* 16 = 128 bits */
+	gcry = gcry_cipher_setiv (cih, iv, 16);
+	g_return_val_if_fail (gcry == 0, NULL);
+
+	/* Allocate memory for the result */
+	result = egg_secure_alloc (n_data);
+
+	for (pos = 0; pos < n_data; pos += 16) {
+		gcry = gcry_cipher_decrypt (cih, result + pos, 16, data + pos, 16);
+		g_return_val_if_fail (gcry == 0, NULL);
+	}
+
+	gcry_cipher_close (cih);
+
+	if (!g_utf8_validate (result, n_data, NULL)) {
+		egg_secure_free (result);
+		return NULL;
+	}
+
+	return result;
+}
+
+
+static gboolean
+prepare_input_data (GkdPrompt *self)
+{
+	GError *error = NULL;
+
+	g_assert (self->pv->input);
+
+	prepare_transport_crypto (self);
+
+	self->pv->in_data = g_key_file_to_data (self->pv->input, &self->pv->in_length, &error);
+	if (!self->pv->in_data) {
+		g_warning ("couldn't encode data for prompt: %s",
+		           error && error->message ? error->message : "");
+		g_clear_error (&error);
+		self->pv->failure = TRUE;
+		mark_completed (self);
+		return FALSE;
+	}
+
+	/* No further modifications to input are possible */
+	g_key_file_free (self->pv->input);
+	self->pv->input = NULL;
+
+	return TRUE;
+}
+
+static void
+display_async_prompt (GkdPrompt *self)
+{
+	EggSpawnCallbacks callbacks;
+	GError *error = NULL;
+	gchar **names, **envp;
+	int i, n;
+
+	char *argv[] = {
+		self->pv->executable,
+		NULL,
+	};
+
+	g_assert (!self->pv->pid);
+
+	/* Fires completed event when fails */
+	if (!prepare_input_data (self))
+		return;
+
+	/* Any environment we have */
+	names = g_listenv ();
+	for (n = 0; names && names[n]; ++n);
+	envp = g_new (char*, n + 2);
+	for (i = 0; i < n; i++)
+		envp[i] = g_strdup_printf ("%s=%s", names[i], g_getenv (names[i]));
+	envp[i++] = NULL;
+	g_strfreev (names);
+
+	memset (&callbacks, 0, sizeof (callbacks));
+	callbacks.standard_input = on_standard_input;
+	callbacks.standard_output = on_standard_output;
+	callbacks.standard_error = on_standard_error;
+	callbacks.completed = on_io_completed;
+	callbacks.finalize_func = g_object_unref;
+
+	self->pv->io_tag = egg_spawn_async_with_callbacks (NULL, argv, envp, G_SPAWN_DO_NOT_REAP_CHILD,
+	                                                   &self->pv->pid, &callbacks, g_object_ref (self),
+	                                                   NULL, &error);
+	if (!self->pv->io_tag) {
+		g_warning ("couldn't spawn prompt tool: %s",
+		           error && error->message ? error->message : "");
+		g_clear_error (&error);
+		self->pv->pid = 0;
+		self->pv->failure = TRUE;
+		mark_completed (self);
+		return;
+	}
+
+	g_child_watch_add_full (G_PRIORITY_DEFAULT, self->pv->pid, on_child_exited,
+	                        g_object_ref (self), g_object_unref);
+}
+
+static void
+clear_prompt_data (GkdPrompt *self)
+{
+	if (self->pv->input)
+		g_key_file_free (self->pv->input);
+	self->pv->input = NULL;
+
+	if (self->pv->output)
+		g_key_file_free (self->pv->output);
+	self->pv->output = NULL;
+
+	self->pv->failure = FALSE;
+
+	g_free (self->pv->in_data);
+	self->pv->in_data = NULL;
+	self->pv->in_length = 0;
+	self->pv->in_offset = 0;
+
+	if (self->pv->out_data)
+		g_string_free (self->pv->out_data, TRUE);
+	self->pv->out_data = NULL;
+
+	if (self->pv->err_data)
+		g_string_free (self->pv->err_data, TRUE);
+	self->pv->err_data = NULL;
+
+	if (self->pv->io_tag)
+		g_source_remove (self->pv->io_tag);
+	self->pv->io_tag = 0;
+
+	if (self->pv->prime)
+		gcry_mpi_release (self->pv->prime);
+	self->pv->prime = NULL;
+
+	if (self->pv->secret)
+		gcry_mpi_release (self->pv->secret);
+	self->pv->secret = NULL;
+
+	if (self->pv->key) {
+		egg_secure_clear (self->pv->key, self->pv->n_key);
+		egg_secure_free (self->pv->key);
+		self->pv->key = NULL;
+		self->pv->n_key = 0;
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * OBJECT
+ */
+
+static GObject*
+gkd_prompt_constructor (GType type, guint n_props, GObjectConstructParam *props)
+{
+	GkdPrompt *self = GKD_PROMPT (G_OBJECT_CLASS (gkd_prompt_parent_class)->constructor(type, n_props, props));
+	g_return_val_if_fail (self, NULL);
+
+	if (!self->pv->executable)
+		self->pv->executable = g_strdup (LIBEXECDIR "/gnome-keyring-ask");
+
+	return G_OBJECT (self);
+}
+
+static void
+gkd_prompt_init (GkdPrompt *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GKD_TYPE_PROMPT, GkdPromptPrivate);
+	gkd_prompt_reset (self);
+}
+
+static void
+gkd_prompt_dispose (GObject *obj)
+{
+	GkdPrompt *self = GKD_PROMPT (obj);
+
+	kill_process (self);
+	clear_prompt_data (self);
+
+	G_OBJECT_CLASS (gkd_prompt_parent_class)->dispose (obj);
+}
+
+static void
+gkd_prompt_finalize (GObject *obj)
+{
+	GkdPrompt *self = GKD_PROMPT (obj);
+
+	g_assert (self->pv->pid == 0);
+	g_assert (!self->pv->in_data);
+	g_assert (!self->pv->out_data);
+	g_assert (!self->pv->err_data);
+	g_assert (!self->pv->io_tag);
+	g_assert (!self->pv->prime);
+	g_assert (!self->pv->secret);
+	g_assert (!self->pv->key);
+
+	g_free (self->pv->executable);
+	self->pv->executable = NULL;
+
+	G_OBJECT_CLASS (gkd_prompt_parent_class)->finalize (obj);
+}
+
+static void
+gkd_prompt_class_init (GkdPromptClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->constructor = gkd_prompt_constructor;
+	gobject_class->dispose = gkd_prompt_dispose;
+	gobject_class->finalize = gkd_prompt_finalize;
+
+	g_type_class_add_private (klass, sizeof (GkdPromptPrivate));
+
+	signals[COMPLETED] = g_signal_new ("signal", GKD_TYPE_PROMPT,
+	                                   G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkdPromptClass, completed),
+	                                   NULL, NULL, g_cclosure_marshal_VOID__VOID,
+	                                   G_TYPE_NONE, 0);
+
+	signals[RESPONDED] = g_signal_new ("signal", GKD_TYPE_PROMPT,
+	                                   G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkdPromptClass, responded),
+	                                   g_signal_accumulator_true_handled, NULL, gkd_prompt_marshal_BOOLEAN__VOID,
+	                                   G_TYPE_BOOLEAN, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * PUBLIC
+ */
+
+void
+gkd_prompt_set_title (GkdPrompt *self, const gchar *title)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_value (self->pv->input, "prompt", "title", title);
+}
+
+void
+gkd_prompt_set_primary_text (GkdPrompt *self, const gchar *primary)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_value (self->pv->input, "prompt", "primary", primary);
+}
+
+void
+gkd_prompt_set_secondary_text (GkdPrompt *self, const gchar *secondary)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_value (self->pv->input, "prompt", "secondary", secondary);
+}
+
+void
+gkd_prompt_show_widget (GkdPrompt *self, const gchar *widget)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_boolean (self->pv->input, "visibility", widget, TRUE);
+}
+
+void
+gkd_prompt_hide_widget (GkdPrompt *self, const gchar *widget)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_boolean (self->pv->input, "visibility", widget, FALSE);
+}
+
+void
+gkd_prompt_select_widget (GkdPrompt *self, const gchar *widget)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	g_key_file_set_boolean (self->pv->input, "selected", widget, TRUE);
+}
+
+gboolean
+gkd_prompt_has_response (GkdPrompt *self)
+{
+	g_return_val_if_fail (GKD_IS_PROMPT (self), FALSE);
+	return self->pv->output ? TRUE : FALSE;
+}
+
+gint
+gkd_prompt_get_response (GkdPrompt *self)
+{
+	gchar *response;
+	guint ret;
+
+	g_return_val_if_fail (GKD_IS_PROMPT (self), GKD_RESPONSE_FAILURE);
+	if (!self->pv->failure)
+		return GKD_RESPONSE_FAILURE;
+
+	g_return_val_if_fail (self->pv->output, GKD_RESPONSE_FAILURE);
+
+	response = g_key_file_get_value (self->pv->output, "prompt", "response", NULL);
+	if (!response) {
+		ret = GKD_RESPONSE_NONE;
+	} else if (g_str_equal (response, "ok")) {
+		ret = GKD_RESPONSE_OK;
+	} else if (g_str_equal (response, "no")) {
+		ret =  GKD_RESPONSE_NO;
+	} else if (g_str_equal (response, "other")) {
+		ret = GKD_RESPONSE_OTHER;
+	} else {
+		g_warning ("invalid response field received from prompt: %s", response);
+		ret = GKD_RESPONSE_NONE;
+	}
+
+	g_free (response);
+	return ret;
+}
+
+gchar*
+gkd_prompt_get_password (GkdPrompt *self, const gchar *password_type)
+{
+	gboolean encrypted;
+	gchar *result;
+	guchar *data;
+	gsize n_data;
+	guchar *iv;
+	gsize n_iv;
+
+	g_return_val_if_fail (GKD_IS_PROMPT (self), NULL);
+	g_return_val_if_fail (self->pv->output, NULL);
+
+	if (!self->pv->failure)
+		return NULL;
+
+	g_assert (self->pv->output);
+
+	if (!password_type)
+		password_type = "password";
+
+	encrypted = g_key_file_get_boolean (self->pv->output, password_type, "encrypted", NULL);
+	if (!encrypted)
+		return g_key_file_get_string (self->pv->output, password_type, "value", NULL);
+
+	/* Parse the encryption params and figure out a key */
+	if (!self->pv->key && !receive_transport_crypto (self))
+		g_return_val_if_reached (NULL);
+
+	/* Parse out an IV */
+	iv = decode_output_hex (self, password_type, "iv", &n_iv);
+	if (iv == NULL) {
+		g_warning ("prompt response has encrypted password, but no iv set");
+		return NULL;
+	}
+
+	/* Parse out the password */
+	data = decode_output_hex (self, password_type, "value", &n_data);
+	if (data == NULL) {
+		g_warning ("prompt response missing encrypted password value");
+		g_free (iv);
+		return NULL;
+	}
+
+	result = decrypt_transport_crypto (self, data, n_data, iv, n_iv);
+	g_free (data);
+	g_free (iv);
+
+	return result;
+}
+
+gboolean
+gkd_prompt_is_widget_selected (GkdPrompt *self, const gchar *widget)
+{
+	g_return_val_if_fail (GKD_IS_PROMPT (self), FALSE);
+	g_return_val_if_fail (self->pv->output, FALSE);
+
+	if (!self->pv->failure)
+		return FALSE;
+
+	g_assert (self->pv->output);
+	return g_key_file_get_boolean (self->pv->output, "selected", widget, NULL);
+}
+
+void
+gkd_prompt_set_window_id (GkdPrompt *self, const gchar *window_id)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	if (!window_id)
+		g_key_file_remove_key (self->pv->input, "prompt", "window-id", NULL);
+	else
+		g_key_file_set_value (self->pv->input, "prompt", "window-id", window_id);
+}
+
+void
+gkd_prompt_set_warning (GkdPrompt *self, const gchar *warning)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->input);
+	if (!warning)
+		g_key_file_remove_key (self->pv->input, "prompt", "warning", NULL);
+	else
+		g_key_file_set_value (self->pv->input, "prompt", "warning", warning);
+}
+
+void
+gkd_prompt_reset (GkdPrompt *self)
+{
+	g_return_if_fail (GKD_IS_PROMPT (self));
+	g_return_if_fail (self->pv->completed);
+
+	kill_process (self);
+	self->pv->pid = 0;
+
+	clear_prompt_data (self);
+	self->pv->input = g_key_file_new ();
+}
+
+/* ----------------------------------------------------------------------------------
+ * ATTENTION QUEUES
+ */
+
+/* Forward declaration */
+static void next_attention_req (const gchar *);
+
+typedef struct _Attention {
+	gchar *window_id;
+	GkdPromptAttentionFunc callback;
+	GDestroyNotify destroy;
+	gpointer user_data;
+	gulong completed_tag;
+	GkdPrompt *prompt;
+} AttentionReq;
+
+static GHashTable *attention_reqs = NULL;
+
+static void
+clear_attention_reqs (gpointer unused)
+{
+	g_assert (attention_reqs);
+	g_hash_table_destroy (attention_reqs);
+}
+
+static AttentionReq*
+alloc_attention_req (const gchar *window_id)
+{
+	AttentionReq *att;
+
+	g_assert (window_id);
+
+	att = g_slice_new0 (AttentionReq);
+	att->window_id = g_strdup (window_id);
+	return att;
+}
+
+static void
+free_attention_req (gpointer data)
+{
+	AttentionReq *att = data;
+
+	if (att) {
+		g_free (att->window_id);
+		if (att->destroy)
+			(att->destroy) (att->user_data);
+		if (att->prompt)
+			g_object_unref (att->prompt);
+		g_slice_free (AttentionReq, att);
+	}
+}
+
+static void
+free_attention_queue (gpointer data)
+{
+	GQueue *queue = data;
+	AttentionReq *att;
+
+	if (queue) {
+		while (!g_queue_is_empty (queue)) {
+			att = g_queue_pop_head (queue);
+			free_attention_req (att);
+		}
+		g_queue_free (queue);
+	}
+}
+
+static GQueue*
+alloc_attention_queue (void)
+{
+	return g_queue_new ();
+}
+
+static void
+done_attention_req (gpointer user_data, GClosure *unused)
+{
+	AttentionReq *att = user_data;
+	g_assert (att);
+	g_signal_handler_disconnect (att->prompt, att->completed_tag);
+	next_attention_req (att->window_id);
+}
+
+static void
+next_attention_req (const gchar *window_id)
+{
+	AttentionReq *att;
+	GQueue *queue;
+
+	g_assert (window_id);
+	g_assert (attention_reqs);
+
+	queue = g_hash_table_lookup (attention_reqs, window_id);
+	g_return_if_fail (queue);
+
+	/* Nothing more to process for this window */
+	if (g_queue_is_empty (queue)) {
+		g_hash_table_remove (attention_reqs, window_id);
+		return;
+	}
+
+	/* Get the next one out */
+	att = g_queue_pop_head (queue);
+	g_assert (att);
+	g_assert (att->window_id);
+	g_assert (g_str_equal (att->window_id, window_id));
+	g_assert (!att->prompt);
+	g_assert (att->callback);
+
+	/* Callback populates the prompt */
+	att->prompt = (att->callback) (att->user_data);
+
+	/* Don't show the prompt */
+	if (att->prompt == NULL) {
+		free_attention_req (att);
+		next_attention_req (window_id);
+		return;
+	}
+
+	att->completed_tag = g_signal_connect_data (att->prompt, "completed",
+	                                            G_CALLBACK (done_attention_req), att,
+	                                            (GClosureNotify)free_attention_req,
+	                                            G_CONNECT_AFTER);
+
+	/* Actually display the prompt, "completed" signal will fire */
+	gkd_prompt_set_window_id (att->prompt, window_id);
+	display_async_prompt (att->prompt);
+}
+
+static gboolean
+service_attention_req (gpointer user_data)
+{
+	AttentionReq *att = user_data;
+	gboolean now = FALSE;
+	GQueue *queue;
+
+	g_assert (att);
+
+	if (!attention_reqs) {
+		attention_reqs = g_hash_table_new_full (g_str_hash, g_str_equal,
+		                                        g_free, free_attention_queue);
+		egg_cleanup_register (clear_attention_reqs, NULL);
+	}
+
+	queue = g_hash_table_lookup (attention_reqs, att->window_id);
+	if (queue == NULL) {
+		queue = alloc_attention_queue ();
+		g_hash_table_insert (attention_reqs, g_strdup (att->window_id), queue);
+		now = TRUE;
+	}
+
+	g_queue_push_tail (queue, att);
+	if (now == TRUE)
+		next_attention_req (att->window_id);
+
+	/* Remove this timeout handler after one call */
+	return FALSE;
+}
+
+void
+gkd_prompt_request_attention_async (const gchar *window_id, GkdPromptAttentionFunc callback,
+                                    gpointer user_data, GDestroyNotify destroy_notify)
+{
+	AttentionReq *att;
+
+	g_return_if_fail (callback);
+
+	if (!window_id)
+		window_id = "";
+	att = alloc_attention_req (window_id);
+	att->callback = callback;
+	att->user_data = user_data;
+	att->destroy = destroy_notify;
+
+	g_timeout_add (0, service_attention_req, att);
+}
+
diff --git a/daemon/prompt/gkd-prompt.h b/daemon/prompt/gkd-prompt.h
new file mode 100644
index 0000000..2f5265f
--- /dev/null
+++ b/daemon/prompt/gkd-prompt.h
@@ -0,0 +1,102 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2009 Stefan Walter
+ *
+ * This program 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+#ifndef __GKD_PROMPT_H__
+#define __GKD_PROMPT_H__
+
+#include <glib-object.h>
+
+typedef enum {
+	GKD_RESPONSE_FAILURE      = -1,
+	GKD_RESPONSE_NONE         = 0,
+	GKD_RESPONSE_NO           = 1,
+	GKD_RESPONSE_OK           = 2,
+	GKD_RESPONSE_OTHER        = 3,
+} GkrAskResponse;
+
+#define GKD_TYPE_PROMPT               (gkd_prompt_get_type ())
+#define GKD_PROMPT(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GKD_TYPE_PROMPT, GkdPrompt))
+#define GKD_PROMPT_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GKD_TYPE_PROMPT, GkdPromptClass))
+#define GKD_IS_PROMPT(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GKD_TYPE_PROMPT))
+#define GKD_IS_PROMPT_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GKD_TYPE_PROMPT))
+#define GKD_PROMPT_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GKD_TYPE_PROMPT, GkdPromptClass))
+
+typedef struct _GkdPrompt GkdPrompt;
+typedef struct _GkdPromptClass GkdPromptClass;
+typedef struct _GkdPromptPrivate GkdPromptPrivate;
+
+struct _GkdPrompt {
+	GObject parent;
+	GkdPromptPrivate *pv;
+};
+
+struct _GkdPromptClass {
+	GObjectClass parent_class;
+	gboolean (*responded) (GkdPrompt *self);
+	void (*completed) (GkdPrompt *self);
+};
+
+GType               gkd_prompt_get_type               (void);
+
+void                gkd_prompt_reset                  (GkdPrompt *prompt);
+
+void                gkd_prompt_set_title              (GkdPrompt *prompt,
+                                                       const gchar *title);
+
+void                gkd_prompt_set_primary_text       (GkdPrompt *prompt,
+                                                       const gchar *primary);
+
+void                gkd_prompt_set_secondary_text     (GkdPrompt *prompt,
+                                                       const gchar *secondary);
+
+void                gkd_prompt_set_warning            (GkdPrompt *prompt,
+                                                       const gchar *warning);
+
+void                gkd_prompt_set_window_id          (GkdPrompt *prompt,
+                                                       const gchar *window_id);
+
+void                gkd_prompt_show_widget            (GkdPrompt *prompt,
+                                                       const gchar *widget);
+
+void                gkd_prompt_hide_widget            (GkdPrompt *prompt,
+                                                       const gchar *widget);
+
+void                gkd_prompt_select_widget          (GkdPrompt *prompt,
+                                                       const gchar *widget);
+
+gboolean            gkd_prompt_has_response           (GkdPrompt *prompt);
+
+gint                gkd_prompt_get_response           (GkdPrompt *prompt);
+
+gchar*              gkd_prompt_get_password           (GkdPrompt *prompt,
+                                                       const gchar *password_type);
+
+gboolean            gkd_prompt_is_widget_selected     (GkdPrompt *prompt,
+                                                       const gchar *widget);
+
+typedef GkdPrompt*  (*GkdPromptAttentionFunc)             (gpointer user_data);
+
+void                gkd_prompt_request_attention_async    (const gchar *window_id,
+                                                           GkdPromptAttentionFunc callback,
+                                                           gpointer user_data,
+                                                           GDestroyNotify destroy_notify);
+
+#endif /* __GKD_PROMPT_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]