[gnome-keyring/gpg-agent] Start work on gpg-agent, incomplete.



commit 302db3f520c944176be75cb7f491573038d48b6e
Author: Stef Walter <stef memberwebs com>
Date:   Sat May 8 15:49:43 2010 +0000

    Start work on gpg-agent, incomplete.
    
    Cannot yet prompt for or store passwords, so basically is
    useless. Waiting for other branches.

 configure.in                                |   20 +
 daemon/.gitignore                           |    3 +
 daemon/Makefile.am                          |    6 +-
 daemon/gkd-glue.c                           |  110 ++++++
 daemon/gkd-glue.h                           |   32 ++
 daemon/gkd-main.c                           |   30 ++-
 daemon/gnome-keyring-gpg.desktop.in.in      |   14 +
 daemon/gpg-agent/Makefile.am                |   36 ++
 daemon/gpg-agent/gkd-gpg-agent-ops.c        |  565 +++++++++++++++++++++++++++
 daemon/gpg-agent/gkd-gpg-agent-private.h    |  117 ++++++
 daemon/gpg-agent/gkd-gpg-agent-standalone.c |  136 +++++++
 daemon/gpg-agent/gkd-gpg-agent.c            |  481 +++++++++++++++++++++++
 daemon/gpg-agent/gkd-gpg-agent.h            |   40 ++
 daemon/pkcs11/gkd-pkcs11.c                  |   44 +--
 14 files changed, 1589 insertions(+), 45 deletions(-)
---
diff --git a/configure.in b/configure.in
index f09d7d4..f3d6516 100644
--- a/configure.in
+++ b/configure.in
@@ -304,6 +304,23 @@ fi
 AM_CONDITIONAL(WITH_SSH, test "$enable_ssh_agent" != "no")
 
 # --------------------------------------------------------------------
+# GPG Agent support
+#
+
+AC_ARG_ENABLE([gpg-agent],
+		AC_HELP_STRING([--disable-gpg-agent],
+		[Don't include GPG agent in gnome-keyring]))
+
+if test "$enable_gpg_agent" != "no"; then
+	AC_DEFINE(WITH_GPG, 1, [Whether to build GPG agent or not])
+	gpg_status="yes"
+else
+	gpg_status="no"
+fi
+
+AM_CONDITIONAL(WITH_GPG, test "$enable_gpg_agent" != "no")
+
+# --------------------------------------------------------------------
 # Trusted Root Certificates Directory
 #
 
@@ -547,6 +564,7 @@ AC_SUBST(BINDIR)
 AC_OUTPUT([
 Makefile
 daemon/Makefile
+daemon/gnome-keyring-gpg.desktop.in
 daemon/gnome-keyring-pkcs11.desktop.in
 daemon/gnome-keyring-secrets.desktop.in
 daemon/gnome-keyring-ssh.desktop.in
@@ -554,6 +572,7 @@ daemon/control/Makefile
 daemon/control/tests/Makefile
 daemon/data/Makefile
 daemon/dbus/Makefile
+daemon/gpg-agent/Makefile
 daemon/login/Makefile
 daemon/pkcs11/Makefile
 daemon/prompt/Makefile
@@ -601,6 +620,7 @@ echo "  PAM:           $pam_status"
 echo
 echo "CONFIGURATION"
 echo "  SSH Agent:            $ssh_status"
+echo "  GPG Agent:            $gpg_status"
 echo "  Root Certificates:    $root_status"
 echo
 echo "BUILD"
diff --git a/daemon/.gitignore b/daemon/.gitignore
index 3f574c6..29304d5 100644
--- a/daemon/.gitignore
+++ b/daemon/.gitignore
@@ -6,6 +6,8 @@ Makefile.in
 /gnome-keyring-daemon
 /org.gnome.keyring.service
 /org.freedesktop.secrets.service
+/gnome-keyring-gpg.desktop
+/gnome-keyring-gpg.desktop.in
 /gnome-keyring-pkcs11.desktop
 /gnome-keyring-pkcs11.desktop.in
 /gnome-keyring-secrets.desktop
@@ -20,4 +22,5 @@ run-auto-test*
 /control/tests/test-control-init
 /control/tests/test-control-unlock
 
+/gpg-agent/gkd-gpg-agent-standalone
 /ssh-agent/gkd-ssh-agent-standalone
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 9edb13f..5f17d10 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -3,6 +3,7 @@ SUBDIRS = \
 	login \
 	control \
 	ssh-agent \
+	gpg-agent \
 	pkcs11 \
 	dbus \
 	data
@@ -25,6 +26,7 @@ INCLUDES=	\
 	$(GLIB_CFLAGS)
 
 gnome_keyring_daemon_SOURCES = \
+	gkd-glue.c gkd-glue.h \
 	gkd-main.c gkd-main.h \
 	gkd-util.c gkd-util.h
 
@@ -35,6 +37,7 @@ gnome_keyring_daemon_LDADD = \
 	$(top_builddir)/daemon/control/libgkd-control.la \
 	$(top_builddir)/daemon/prompt/libgkd-prompt.la \
 	$(top_builddir)/daemon/ssh-agent/libgkd-ssh-agent.la \
+	$(top_builddir)/daemon/gpg-agent/libgkd-gpg-agent.la \
 	$(top_builddir)/pkcs11/plex-layer/libgck-plex-layer.la \
 	$(top_builddir)/pkcs11/roots-store/libgck-roots-store.la \
 	$(top_builddir)/pkcs11/rpc-layer/libgck-rpc-layer.la \
@@ -74,7 +77,8 @@ service_PATH     = $(VALGRIND_RUN)$(bindir)
 desktop_in_files = \
 	gnome-keyring-pkcs11.desktop.in \
 	gnome-keyring-secrets.desktop.in \
-	gnome-keyring-ssh.desktop.in
+	gnome-keyring-ssh.desktop.in \
+	gnome-keyring-gpg.desktop.in
 desktopdir       = $(sysconfdir)/xdg/autostart
 desktop_DATA     = $(desktop_in_files:.desktop.in=.desktop)
 
diff --git a/daemon/gkd-glue.c b/daemon/gkd-glue.c
new file mode 100644
index 0000000..0811f6b
--- /dev/null
+++ b/daemon/gkd-glue.c
@@ -0,0 +1,110 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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-glue.h"
+#include "gkd-util.h"
+
+#include "gpg-agent/gkd-gpg-agent.h"
+#include "ssh-agent/gkd-ssh-agent.h"
+
+#include "egg/egg-cleanup.h"
+
+static void
+pkcs11_ssh_cleanup (gpointer unused)
+{
+	gkd_ssh_agent_shutdown ();
+}
+
+static gboolean
+accept_ssh_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
+{
+	if (cond == G_IO_IN)
+		gkd_ssh_agent_accept ();
+	return TRUE;
+}
+
+gboolean
+gkd_daemon_startup_ssh (void)
+{
+	GIOChannel *channel;
+	const gchar *base_dir;
+	int sock;
+
+	base_dir = gkd_util_get_master_directory ();
+	g_return_val_if_fail (base_dir, FALSE);
+
+	sock = gkd_ssh_agent_startup (base_dir);
+	if (sock == -1)
+		return FALSE;
+
+	channel = g_io_channel_unix_new (sock);
+	g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_ssh_client, NULL);
+	g_io_channel_unref (channel);
+
+	/* gck-ssh-agent sets the environment variable */
+	gkd_util_push_environment ("SSH_AUTH_SOCK", g_getenv ("SSH_AUTH_SOCK"));
+
+	egg_cleanup_register (pkcs11_ssh_cleanup, NULL);
+
+	return TRUE;
+}
+
+static void
+pkcs11_gpg_cleanup (gpointer unused)
+{
+	gkd_gpg_agent_shutdown ();
+}
+
+static gboolean
+accept_gpg_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
+{
+	if (cond == G_IO_IN)
+		gkd_gpg_agent_accept ();
+	return TRUE;
+}
+
+gboolean
+gkd_daemon_startup_gpg (void)
+{
+	GIOChannel *channel;
+	const gchar *base_dir;
+	int sock;
+
+	base_dir = gkd_util_get_master_directory ();
+	g_return_val_if_fail (base_dir, FALSE);
+
+	sock = gkd_gpg_agent_startup (base_dir);
+	if (sock == -1)
+		return FALSE;
+
+	channel = g_io_channel_unix_new (sock);
+	g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_gpg_client, NULL);
+	g_io_channel_unref (channel);
+
+	/* gck-gpg-agent sets the environment variable */
+	gkd_util_push_environment ("GPG_AGENT_INFO", g_getenv ("GPG_AGENT_INFO"));
+
+	egg_cleanup_register (pkcs11_gpg_cleanup, NULL);
+
+	return TRUE;
+}
diff --git a/daemon/gkd-glue.h b/daemon/gkd-glue.h
new file mode 100644
index 0000000..7532b15
--- /dev/null
+++ b/daemon/gkd-glue.h
@@ -0,0 +1,32 @@
+/*
+ * 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_GLUE_H_
+#define GKD_GLUE_H_
+
+#include <glib.h>
+
+
+gboolean       gkd_daemon_startup_ssh      (void);
+
+gboolean       gkd_daemon_startup_gpg      (void);
+
+#endif /* GKD_GLUE_H_ */
diff --git a/daemon/gkd-main.c b/daemon/gkd-main.c
index 6ac0b65..71fec7c 100644
--- a/daemon/gkd-main.c
+++ b/daemon/gkd-main.c
@@ -23,6 +23,7 @@
 
 #include "config.h"
 
+#include "gkd-glue.h"
 #include "gkd-main.h"
 #include "gkd-util.h"
 
@@ -77,15 +78,24 @@
 
 /* All the components to run on startup if not specified on command line */
 #ifdef WITH_SSH
-#define DEFAULT_COMPONENTS  "pkcs11,secrets,ssh"
+#	ifdef WITH_GPG
+#		define DEFAULT_COMPONENTS  "pkcs11,secrets,ssh,gpg"
+#	else
+#		define DEFAULT_COMPONENTS  "pkcs11,secrets,ssh"
+#	endif
 #else
-#define DEFAULT_COMPONENTS  "pkcs11,secrets"
+#	ifdef WITH_GPG
+#		define DEFAULT_COMPONENTS  "pkcs11,secrets,gpg"
+#	else
+#		define DEFAULT_COMPONENTS  "pkcs11,secrets"
+#	endif
 #endif
 
 static gchar* run_components = DEFAULT_COMPONENTS;
 static gboolean pkcs11_started = FALSE;
 static gboolean secrets_started = FALSE;
 static gboolean ssh_started = FALSE;
+static gboolean gpg_started = FALSE;
 
 static gboolean run_foreground = FALSE;
 static gboolean run_daemonized = FALSE;
@@ -605,7 +615,7 @@ gkr_daemon_startup_steps (const gchar *components)
 			g_message ("The SSH agent was already initialized");
 		} else {
 			ssh_started = TRUE;
-			if (!gkd_pkcs11_startup_ssh ()) {
+			if (!gkd_daemon_startup_ssh ()) {
 				ssh_started = FALSE;
 				return FALSE;
 			}
@@ -613,6 +623,20 @@ gkr_daemon_startup_steps (const gchar *components)
 	}
 #endif
 
+#ifdef WITH_GPG
+	if (strstr (components, "gpg")) {
+		if (gpg_started) {
+			g_message ("The GPG agent was already initialized");
+		} else {
+			gpg_started = TRUE;
+			if (!gkd_daemon_startup_gpg ()) {
+				gpg_started = FALSE;
+				return FALSE;
+			}
+		}
+	}
+#endif
+
 	return TRUE;
 }
 
diff --git a/daemon/gnome-keyring-gpg.desktop.in.in b/daemon/gnome-keyring-gpg.desktop.in.in
new file mode 100644
index 0000000..26af498
--- /dev/null
+++ b/daemon/gnome-keyring-gpg.desktop.in.in
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Type=Application
+_Name=GPG Password Agent
+_Comment=GNOME Keyring: GPG Agent
+Exec= VALGRIND_RUN@ gnome-keyring-daemon --start --components=gpg
+OnlyShowIn=GNOME;LXDE;
+AutostartCondition=GNOME /apps/gnome-keyring/daemon-components/gpg
+X-GNOME-Autostart-Phase=Initialization
+X-GNOME-AutoRestart=false
+X-GNOME-Autostart-Notify=true
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-keyring
+X-GNOME-Bugzilla-Component=general
+X-GNOME-Bugzilla-Version= VERSION@
diff --git a/daemon/gpg-agent/Makefile.am b/daemon/gpg-agent/Makefile.am
new file mode 100644
index 0000000..585b6d2
--- /dev/null
+++ b/daemon/gpg-agent/Makefile.am
@@ -0,0 +1,36 @@
+
+INCLUDES = \
+	-I$(top_builddir) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/pkcs11 \
+	$(GOBJECT_CFLAGS) \
+	$(GLIB_CFLAGS)
+
+# ------------------------------------------------------------------------------
+# The ssh-agent component code
+
+noinst_LTLIBRARIES = \
+	libgkd-gpg-agent.la
+
+libgkd_gpg_agent_la_SOURCES = \
+	gkd-gpg-agent.c gkd-gpg-agent.h \
+	gkd-gpg-agent-private.h \
+	gkd-gpg-agent-ops.c
+
+# ------------------------------------------------------------------------------
+# Standalone binary
+
+noinst_PROGRAMS = \
+	gkd-gpg-agent-standalone
+
+gkd_gpg_agent_standalone_SOURCES = \
+	gkd-gpg-agent-standalone.c
+
+gkd_gpg_agent_standalone_LDADD = \
+	libgkd-gpg-agent.la \
+	$(top_builddir)/gp11/libgp11.la \
+	$(top_builddir)/egg/libegg-secure.la \
+	$(GOBJECT_LIBS) \
+	$(GTHREAD_LIBS) \
+	$(LIBGCRYPT_LIBS) \
+	$(GLIB_LIBS)
\ No newline at end of file
diff --git a/daemon/gpg-agent/gkd-gpg-agent-ops.c b/daemon/gpg-agent/gkd-gpg-agent-ops.c
new file mode 100644
index 0000000..e84941d
--- /dev/null
+++ b/daemon/gpg-agent/gkd-gpg-agent-ops.c
@@ -0,0 +1,565 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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-gpg-agent.h"
+#include "gkd-gpg-agent-private.h"
+
+#include "egg/egg-error.h"
+#include "egg/egg-secure-memory.h"
+
+#include "pkcs11/pkcs11i.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#define GKD_GPG_AGENT_PASS_AS_DATA    0x00000001
+#define GKD_GPG_AGENT_REPEAT          0x00000002
+
+#define COLLECTION    "session"
+#define N_COLLECTION  7
+
+/* ----------------------------------------------------------------------------------
+ * PASSWORD STUFF
+ */
+
+static void
+keyid_to_field_attribute (const gchar *keyid, GP11Attributes *attrs)
+{
+	GString *fields = g_string_sized_new (128);
+
+	/* Remember that attribute names are sorted */
+
+	g_string_append (fields, "keyid");
+	g_string_append_c (fields, '\0');
+	g_string_append (fields, keyid);
+	g_string_append_c (fields, '\0');
+
+	g_string_append (fields, "source");
+	g_string_append_c (fields, '\0');
+	g_string_append (fields, "gnome-keyring:gpg-agent");
+	g_string_append_c (fields, '\0');
+
+	gp11_attributes_add_data (attrs, CKA_G_FIELDS, fields->str, fields->len);
+	g_string_free (fields, TRUE);
+}
+
+static gboolean
+do_clear_password (GP11Session *session, const gchar *keyid)
+{
+	GP11Attributes *attrs;
+	GList *objects, *l;
+	GError *error = NULL;
+
+	attrs = gp11_attributes_newv (CKA_CLASS, GP11_ULONG, CKO_SECRET_KEY,
+	                              GP11_INVALID);
+	keyid_to_field_attribute (keyid, attrs);
+
+	objects = gp11_session_find_objects_full (session, attrs, NULL, &error);
+	gp11_attributes_unref (attrs);
+
+	if (error) {
+		g_warning ("couldn't search for gpg agent passwords to clear: %s",
+		           egg_error_message (error));
+		g_clear_error (&error);
+		return FALSE;
+	}
+
+	if (!objects)
+		return TRUE;
+
+	/* Delete first item */
+	for (l = objects; l; l = g_list_next (l)) {
+		gp11_object_set_session (l->data, session);
+		if (gp11_object_destroy (l->data, &error)) {
+			break; /* Only delete the first item */
+		} else {
+			g_warning ("couldn't clear gpg agent password: %s",
+			           egg_error_message (error));
+			g_clear_error (&error);
+		}
+	}
+
+	gp11_list_unref_free (objects);
+	return TRUE;
+}
+
+static gchar*
+do_lookup_password (GP11Session *session, const gchar *keyid)
+{
+	GP11Attributes *attrs;
+	GList *objects, *l;
+	GError *error = NULL;
+	gpointer data = NULL;
+	gsize n_data;
+
+	attrs = gp11_attributes_newv (CKA_CLASS, GP11_ULONG, CKO_SECRET_KEY,
+	                              GP11_INVALID);
+	keyid_to_field_attribute (keyid, attrs);
+
+	objects = gp11_session_find_objects_full (session, attrs, NULL, &error);
+	gp11_attributes_unref (attrs);
+
+	if (error) {
+		g_warning ("couldn't search for gpg agent passwords to clear: %s",
+		           egg_error_message (error));
+		g_clear_error (&error);
+		return NULL;
+	}
+
+	if (!objects)
+		return NULL;
+
+	/* Return first password */
+	for (l = objects; l; l = g_list_next (l)) {
+		gp11_object_set_session (l->data, session);
+		data = gp11_object_get_data_full (l->data, CKA_VALUE, egg_secure_realloc, NULL, &n_data, &error);
+		if (error) {
+			g_warning ("couldn't lookup gpg agent password: %s", egg_error_message (error));
+			g_clear_error (&error);
+			data = NULL;
+		} else {
+			break;
+		}
+	}
+
+	gp11_list_unref_free (objects);
+
+	/* Data is null terminated */
+	return data;
+}
+
+static gchar*
+do_get_password (GP11Session *session, const gchar *keyid, const gchar *errmsg,
+                 const gchar *prompt, const gchar *description, gboolean confirm)
+{
+	gchar *password;
+
+	password = do_lookup_password (session, keyid);
+	if (password != NULL)
+		return password;
+
+	/*
+	 * Need the following to continue:
+	 * - Ability to detect or use default keyring.
+	 * - Ability to prompt to unlock keyring.
+	 */
+	g_assert (FALSE && "Not yet impelmented");
+	return NULL;
+}
+
+/* ----------------------------------------------------------------------------------
+ * PARSING and UTIL
+ */
+
+/* Is the argument a assuan null parameter? */
+static gboolean
+is_null_argument (gchar *arg)
+{
+	return (strcmp (arg, "X") == 0);
+}
+
+static const gchar HEX_CHARS[] = "0123456789ABCDEF";
+
+/* Decode an assuan parameter */
+static void
+decode_assuan_arg (gchar *arg)
+{
+	gchar *t;
+	gint len;
+
+	for (len = strlen (arg); len > 0; arg++, len--) {
+		switch (*arg) {
+		/* + becomes a space */
+		case '+':
+			*arg = ' ';
+			break;
+
+		/* hex encoded as in URIs */
+		case '%':
+			*arg = '?';
+			t = strchr (HEX_CHARS, arg[1]);
+			if (t != NULL) {
+				*arg = ((t - HEX_CHARS) & 0xf) << 4;
+				t = strchr (HEX_CHARS, arg[2]);
+				if (t != NULL)
+					*arg |= (t - HEX_CHARS) & 0xf;
+			}
+			len -= 2;
+			if (len < 1) /* last char, null terminate */
+				arg[1] = 0;
+			else /* collapse rest */
+				memmove (arg + 1, arg + 3, len);
+			break;
+		};
+	}
+}
+
+/* Parse an assuan argument that we recognize */
+static guint32
+parse_assuan_flag (gchar *flag)
+{
+	g_assert (flag);
+	if (g_str_equal (flag, GPG_AGENT_FLAG_DATA))
+		return GKD_GPG_AGENT_PASS_AS_DATA;
+	else if (g_str_has_prefix (flag, GPG_AGENT_FLAG_REPEAT)) {
+		gint count = 1;
+
+		flag += strlen(GPG_AGENT_FLAG_REPEAT);
+		if (*flag == '=') {
+			count = atoi (++flag);
+			if (!(count == 0 || count == 1))
+				g_warning ("--repeat=%d treated as --repeat=1", count);
+		}
+
+		if (count)
+			return GKD_GPG_AGENT_REPEAT;
+	}
+	return 0;
+}
+
+/* Split a line into each of it's arguments. This modifies line */
+static void
+split_arguments (gchar *line, guint32 *flags, ...)
+{
+	gchar **cur;
+	gchar *flag;
+	va_list ap;
+
+	va_start (ap, flags);
+
+	/* Initial white space */
+	while (*line && isspace (*line))
+		line++;
+
+	/* The flags */
+	if (flags) {
+		*flags = 0;
+
+		while (*line) {
+			/* Options start with a double dash */
+			if(!(line[0] == '-' && line[1] == '-'))
+				break;
+			line +=2;
+			flag = line;
+
+			/* All non-whitespace */
+			while (*line && !isspace (*line))
+				line++;
+
+			/* Skip and null any whitespace */
+			while (*line && isspace (*line)) {
+				*line = 0;
+				line++;
+			}
+
+			*flags |= parse_assuan_flag (flag);
+		}
+	}
+
+	/* The arguments */
+	while ((cur = va_arg (ap, gchar **)) != NULL) {
+		if (*line) {
+			*cur = line;
+
+			/* All non-whitespace */
+			while (*line && !isspace (*line))
+				line++;
+
+			/* Skip and null any whitespace */
+			while (*line && isspace (*line)) {
+				*line = 0;
+				line++;
+			}
+
+			decode_assuan_arg (*cur);
+		} else {
+			*cur = NULL;
+		}
+	}
+
+	va_end (ap);
+}
+
+static guint
+x11_display_dot_offset (const gchar *d)
+{
+	const gchar *p;
+	guint l = strlen (d);
+
+	for (p = d + l; *p != '.'; --p) {
+		if (p <= d)
+			break;
+		if (*p == ':')
+			break;
+	}
+	if (*p == '.')
+		l = p - d;
+
+	return l;
+}
+
+/*
+ * Displays are of the form: hostname:displaynumber.screennumber, where
+ * hostname can be empty (to indicate a local connection).
+ * Two displays are equivalent if their hostnames and displaynumbers match.
+ */
+static gboolean
+x11_displays_eq (const gchar *d1, const gchar *d2)
+{
+	guint l1, l2;
+	l1 = x11_display_dot_offset (d1);
+	l2 = x11_display_dot_offset (d2);
+	return (g_ascii_strncasecmp (d1, d2, l1 > l2 ? l1 : l2) == 0);
+}
+
+/* Does command have option? */
+static gboolean
+command_has_option (gchar *command, gchar *option)
+{
+	gboolean has_option = FALSE;
+
+	if (!strcmp (command, GPG_AGENT_GETPASS)) {
+		has_option = (!strcmp (option, GPG_AGENT_FLAG_DATA) ||
+		              !strcmp (option, GPG_AGENT_FLAG_REPEAT));
+	}
+	/* else if (other commands) */
+
+	return has_option;
+}
+
+/* Encode a password in hex */
+static gchar*
+encode_password (const gchar *pass)
+{
+	static const char HEXC[] = "0123456789abcdef";
+	int j, c;
+	gchar *enc, *k;
+
+	/* Encode the password */
+	c = sizeof (gchar *) * ((strlen (pass) * 2) + 1);
+	k = enc = egg_secure_alloc (c);
+
+	/* Simple hex encoding */
+	while (*pass) {
+		j = *(pass) >> 4 & 0xf;
+		*(k++) = HEXC[j];
+
+		j = *(pass++) & 0xf;
+		*(k++) = HEXC[j];
+	}
+
+	return enc;
+}
+
+/* ----------------------------------------------------------------------------------
+ * OPERATIONS
+ */
+
+gboolean
+gkd_gpg_agent_ops_options (GkdGpgAgentCall *call, gchar *args)
+{
+	gchar *option;
+	gsize len;
+
+	split_arguments (args, NULL, &option, NULL);
+	if (!option) {
+		g_message ("received invalid option argument");
+		return gkd_gpg_agent_send_reply (call, FALSE, "105 parameter error");
+	}
+
+	/*
+	 * If the option is a display option we make sure it's
+	 * the same as our display. Otherwise we don't answer.
+	 */
+	len = strlen (GPG_AGENT_OPT_DISPLAY);
+	if (g_ascii_strncasecmp (option, GPG_AGENT_OPT_DISPLAY, len) == 0) {
+		option += len;
+
+		if (x11_displays_eq (option, g_getenv ("DISPLAY"))) {
+			call->terminal_ok = TRUE;
+		} else {
+			g_message ("received request different display: %s", option);
+			return gkd_gpg_agent_send_reply (call, FALSE, "105 parameter conflict");
+		}
+	}
+
+	/* We don't do anything with the other options right now */
+	return gkd_gpg_agent_send_reply (call, TRUE, NULL);
+}
+
+gboolean
+gkd_gpg_agent_ops_getpass (GkdGpgAgentCall *call, gchar *args)
+{
+	gchar *id;
+	gchar *errmsg;
+	gchar *prompt;
+	gchar *description;
+	GP11Session *session;
+	gchar *password;
+	gchar *encoded;
+	guint32 flags;
+
+	/* We don't answer this unless it's from the right terminal */
+	if (!call->terminal_ok) {
+		g_message ("received passphrase request from wrong terminal");
+		return gkd_gpg_agent_send_reply (call, FALSE, "113 Server Resource Problem");
+	}
+
+	split_arguments (args, &flags, &id, &errmsg, &prompt, &description, NULL);
+
+	if (!id || !errmsg || !prompt || !description) {
+		g_message ("received invalid passphrase request");
+		return gkd_gpg_agent_send_reply (call, FALSE, "105 parameter error");
+	}
+
+	if (is_null_argument (id))
+		id = NULL;
+	if (is_null_argument (errmsg))
+		errmsg = NULL;
+	if (is_null_argument (prompt))
+		prompt = NULL;
+	if (is_null_argument (description))
+		description = NULL;
+
+	session = gkd_gpg_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+
+	password = do_get_password (session, id, errmsg, prompt, description,
+	                            flags & GKD_GPG_AGENT_REPEAT);
+
+	gkd_gpg_agent_checkin_main_session (session);
+
+	if (password == NULL) {
+		gkd_gpg_agent_send_reply (call, FALSE, "111 cancelled");
+	} else if (flags & GKD_GPG_AGENT_PASS_AS_DATA) {
+		gkd_gpg_agent_send_data (call, password);
+		gkd_gpg_agent_send_reply (call, TRUE, NULL);
+	} else {
+		encoded = encode_password (password);
+		gkd_gpg_agent_send_reply (call, TRUE, encoded);
+		egg_secure_strfree (encoded);
+	}
+
+	egg_secure_strfree (password);
+	return TRUE;
+}
+
+gboolean
+gkd_gpg_agent_ops_clrpass (GkdGpgAgentCall *call, gchar *args)
+{
+	GP11Session *session;
+	gchar *id;
+
+	/* We don't answer this unless it's from the right terminal */
+	if (!call->terminal_ok) {
+		g_message ("received passphrase request from wrong terminal");
+		return gkd_gpg_agent_send_reply (call, FALSE, "113 Server Resource Problem");
+	}
+
+	split_arguments (args, NULL, &id, NULL);
+
+	if (!id) {
+		gkd_gpg_agent_send_reply (call, FALSE, "105 parameter error");
+		g_warning ("received invalid clear pass request: %s", args);
+	}
+
+	session = gkd_gpg_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+
+	/* Ignore the result, always return success */
+	do_clear_password (session, id);
+
+	gkd_gpg_agent_checkin_main_session (session);
+
+	gkd_gpg_agent_send_reply (call, TRUE, NULL);
+	return TRUE;
+}
+
+gboolean
+gkd_gpg_agent_ops_getinfo (GkdGpgAgentCall *call, gchar *request)
+{
+	gchar *args;
+	gboolean implemented = FALSE;
+
+	args = strchr (request, ' ');
+	if (args) {
+		*args = 0;
+		args++;
+		while (isspace (*args))
+			args++;
+	}
+
+	if (!strcmp (request, "cmd_has_option")) {
+		gchar *command = args;
+		gchar *option;
+
+		if (!command || !*command)
+			return gkd_gpg_agent_send_reply (call, FALSE, "105 parameter error");
+
+		option = strchr(args, ' ');
+
+		if (option) {
+			*option = 0;
+			option++;
+			while (isspace (*option))
+				option++;
+		} else {
+			return gkd_gpg_agent_send_reply (call, FALSE, "105 parameter error");
+		}
+
+		implemented = command_has_option (command, option);
+	}
+
+	/* else if (other info request) */
+
+	if (implemented)
+		return gkd_gpg_agent_send_reply (call, TRUE, NULL);
+	else
+		return gkd_gpg_agent_send_reply (call, FALSE, "100 not implemented");
+}
+
+gboolean
+gkd_gpg_agent_ops_nop (GkdGpgAgentCall *call, gchar *args)
+{
+	return gkd_gpg_agent_send_reply (call, TRUE, NULL);
+}
+
+gboolean
+gkd_gpg_agent_ops_bye (GkdGpgAgentCall *call, gchar *args)
+{
+	gkd_gpg_agent_send_reply (call, TRUE, "closing connection");
+	return FALSE;
+}
+
+gboolean
+gkd_gpg_agent_ops_reset (GkdGpgAgentCall *call, gchar *args)
+{
+	/* We keep no state :) */
+	return gkd_gpg_agent_send_reply (call, TRUE, NULL);
+}
+
+gboolean
+gkd_gpg_agent_ops_id (GkdGpgAgentCall *call, gchar *args)
+{
+	return gkd_gpg_agent_send_reply (call, TRUE, "gnome-keyring-daemon");
+}
diff --git a/daemon/gpg-agent/gkd-gpg-agent-private.h b/daemon/gpg-agent/gkd-gpg-agent-private.h
new file mode 100644
index 0000000..5688354
--- /dev/null
+++ b/daemon/gpg-agent/gkd-gpg-agent-private.h
@@ -0,0 +1,117 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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 GKDGPGPRIVATE_H_
+#define GKDGPGPRIVATE_H_
+
+#include "egg/egg-buffer.h"
+
+#include "pkcs11/pkcs11.h"
+
+#include <gp11/gp11.h>
+
+#include <glib.h>
+
+typedef struct _GkdGpgAgentCall {
+	int sock;
+	GP11Module *module;
+	GIOChannel *channel;
+	gboolean terminal_ok;
+} GkdGpgAgentCall;
+
+/* -----------------------------------------------------------------------------
+ * GPG CONSTANTS
+ */
+
+
+/* Commands */
+#define GPG_AGENT_ID      "AGENT_ID"
+#define GPG_AGENT_NOP     "NOP"
+#define GPG_AGENT_BYE     "BYE"
+#define GPG_AGENT_RESET   "RESET"
+#define GPG_AGENT_OPTION  "OPTION"
+#define GPG_AGENT_GETPASS "GET_PASSPHRASE"
+#define GPG_AGENT_CLRPASS "CLEAR_PASSPHRASE"
+#define GPG_AGENT_GETINFO "GETINFO"
+
+#define GPG_AGENT_OPT_DISPLAY "display="
+
+/* Options */
+#define GPG_AGENT_FLAG_DATA   "data"
+#define GPG_AGENT_FLAG_CHECK  "check"
+#define GPG_AGENT_FLAG_REPEAT "repeat"
+
+/* Responses */
+#define GPG_AGENT_OK      "OK "
+#define GPG_AGENT_ERR     "ERR "
+#define GPG_AGENT_DATA    "D "
+
+/* -----------------------------------------------------------------------------
+ * gkd-gpg-agent-ops.c
+ */
+
+/* -----------------------------------------------------------------------------
+ * gkd-gpg-agent.c
+ */
+
+gboolean              gkd_gpg_agent_initialize_with_module          (GP11Module *module);
+
+GP11Session*          gkd_gpg_agent_checkout_main_session           (void);
+
+void                  gkd_gpg_agent_checkin_main_session            (GP11Session* session);
+
+gboolean              gkd_gpg_agent_send_reply                      (GkdGpgAgentCall *call,
+                                                                     gboolean ok,
+                                                                     const gchar *response);
+
+gboolean              gkd_gpg_agent_send_data                       (GkdGpgAgentCall *call,
+                                                                     const gchar *data);
+
+/* -----------------------------------------------------------------------------
+ * gkd-gpg-agent-ops
+ */
+
+
+gboolean              gkd_gpg_agent_ops_options                     (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_getpass                     (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_clrpass                     (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_getinfo                     (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_nop                         (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_bye                         (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_reset                       (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+gboolean              gkd_gpg_agent_ops_id                          (GkdGpgAgentCall *call,
+                                                                     gchar *args);
+
+#endif /*GKDGPGPRIVATE_H_*/
diff --git a/daemon/gpg-agent/gkd-gpg-agent-standalone.c b/daemon/gpg-agent/gkd-gpg-agent-standalone.c
new file mode 100644
index 0000000..ac24f85
--- /dev/null
+++ b/daemon/gpg-agent/gkd-gpg-agent-standalone.c
@@ -0,0 +1,136 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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-gpg-agent.h"
+#include "gkd-gpg-agent-private.h"
+
+#include "egg/egg-error.h"
+#include "egg/egg-secure-memory.h"
+
+#include "gp11/gp11.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+
+G_LOCK_DEFINE_STATIC (memory_mutex);
+
+void egg_memory_lock (void)
+	{ G_LOCK (memory_mutex); }
+
+void egg_memory_unlock (void)
+	{ G_UNLOCK (memory_mutex); }
+
+void* egg_memory_fallback (void *p, size_t sz)
+	{ return g_realloc (p, sz); }
+
+static gboolean
+accept_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
+{
+	gkd_gpg_agent_accept ();
+	return TRUE;
+}
+
+static gboolean
+authenticate_slot (GP11Module *module, GP11Slot *slot, gchar *label, gchar **password, gpointer unused)
+{
+	gchar *prompt = g_strdup_printf ("Enter token password (%s): ", label);
+	char *result = getpass (prompt);
+	g_free (prompt);
+	*password = g_strdup (result);
+	memset (result, 0, strlen (result));
+	return TRUE;
+}
+
+static gboolean
+authenticate_object (GP11Module *module, GP11Object *object, gchar *label, gchar **password)
+{
+	gchar *prompt = g_strdup_printf ("Enter object password (%s): ", label);
+	char *result = getpass (prompt);
+	g_free (prompt);
+	*password = g_strdup (result);
+	memset (result, 0, strlen (result));
+	return TRUE;
+}
+
+int
+main(int argc, char *argv[])
+{
+	GP11Module *module;
+	GError *error = NULL;
+	GIOChannel *channel;
+	GMainLoop *loop;
+	gboolean ret;
+	int sock;
+
+	g_type_init ();
+
+	if (!g_thread_supported ())
+		g_thread_init (NULL);
+
+	if (argc <= 1) {
+		g_message ("specify pkcs11 module on the command line");
+		return 1;
+	}
+
+	module = gp11_module_initialize (argv[1], argc > 2 ? argv[2] : NULL, &error);
+	if (!module) {
+		g_message ("couldn't load pkcs11 module: %s", egg_error_message (error));
+		g_clear_error (&error);
+		return 1;
+	}
+
+
+	g_signal_connect (module, "authenticate-slot", G_CALLBACK (authenticate_slot), NULL);
+	g_signal_connect (module, "authenticate-object", G_CALLBACK (authenticate_object), NULL);
+	gp11_module_set_auto_authenticate (module, GP11_AUTHENTICATE_OBJECTS);
+
+	ret = gkd_gpg_agent_initialize_with_module (module);
+	g_object_unref (module);
+
+	if (ret == FALSE)
+		return 1;
+
+	sock = gkd_gpg_agent_startup ("/tmp");
+	if (sock == -1)
+		return 1;
+
+	channel = g_io_channel_unix_new (sock);
+	g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_client, NULL);
+	g_io_channel_unref (channel);
+
+	g_print ("GPG_AGENT_INFO=%s\n", g_getenv ("GPG_AGENT_INFO"));
+
+	/* Run a main loop */
+	loop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+
+	gkd_gpg_agent_shutdown ();
+	gkd_gpg_agent_uninitialize ();
+
+	return 0;
+}
diff --git a/daemon/gpg-agent/gkd-gpg-agent.c b/daemon/gpg-agent/gkd-gpg-agent.c
new file mode 100644
index 0000000..e87438a
--- /dev/null
+++ b/daemon/gpg-agent/gkd-gpg-agent.c
@@ -0,0 +1,481 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "gkd-gpg-agent.h"
+#include "gkd-gpg-agent-private.h"
+
+#include "egg/egg-error.h"
+#include "egg/egg-secure-memory.h"
+
+#ifndef HAVE_SOCKLEN_T
+#define socklen_t int
+#endif
+
+/* The loaded PKCS#11 module */
+static GP11Module *pkcs11_module = NULL;
+
+#ifndef KL
+#define KL(s)               ((sizeof(s) - 1) / sizeof(s[0]))
+#endif
+
+static gboolean
+write_all (int fd, const gchar *buf, int len)
+{
+	int all = len;
+	int res;
+
+	if (len == -1)
+		all = len = strlen (buf);
+
+	while (len > 0) {
+
+		res = write (fd, buf, len);
+
+		if (res <= 0) {
+			if (errno == EAGAIN && errno == EINTR)
+				continue;
+			if (errno != EPIPE)
+				g_warning ("couldn't write %u bytes to client: %s", all,
+				           res < 0 ? g_strerror (errno) : "");
+			return FALSE;
+		} else  {
+			len -= res;
+			buf += res;
+		}
+	}
+
+	return TRUE;
+}
+
+/* Called when seahorse-actions has a response to send back */
+gboolean
+gkd_gpg_agent_send_reply (GkdGpgAgentCall *call, gboolean ok, const gchar *response)
+{
+	int fd = g_io_channel_unix_get_fd (call->channel);
+	if (!write_all (fd, ok ? GPG_AGENT_OK : GPG_AGENT_ERR, ok ? KL (GPG_AGENT_OK) : KL (GPG_AGENT_ERR)) ||
+	    (response && !write_all (fd, response, -1)) ||
+	    !write_all (fd, "\n", 1))
+		return FALSE;
+
+	return TRUE;
+}
+
+gboolean
+gkd_gpg_agent_send_data (GkdGpgAgentCall *call, const gchar *data)
+{
+	int fd = g_io_channel_unix_get_fd (call->channel);
+	if (!write_all (fd, GPG_AGENT_DATA, KL (GPG_AGENT_DATA)) ||
+	    !write_all (fd, data, -1) ||
+	    !write_all (fd, "\n", 1))
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Process a request line from client */
+static gboolean
+process_line (GkdGpgAgentCall *call, gchar *line)
+{
+	gchar *args;
+
+	g_assert (call);
+	g_assert (line);
+
+	g_strstrip (line);
+
+	if (!*line)
+		return TRUE;
+
+	/* Split the command off from the args */
+	args = strchr (line, ' ');
+	if (args) {
+		*args = 0;
+		args++;
+	} else {
+		/* Pointer to the end, empty string */
+		args = line + strlen (line);
+	}
+
+	if (g_ascii_strcasecmp (line, GPG_AGENT_OPTION) == 0)
+		return gkd_gpg_agent_ops_options (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_GETPASS) == 0)
+		return gkd_gpg_agent_ops_getpass (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_CLRPASS) == 0)
+		return gkd_gpg_agent_ops_clrpass (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_GETINFO) == 0)
+		return gkd_gpg_agent_ops_getinfo (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_NOP) == 0)
+		return gkd_gpg_agent_ops_nop (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_BYE) == 0)
+		return gkd_gpg_agent_ops_bye (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_RESET) == 0)
+		return gkd_gpg_agent_ops_reset (call, args);
+
+	else if (g_ascii_strcasecmp (line, GPG_AGENT_ID) == 0)
+		return gkd_gpg_agent_ops_id (call, args);
+
+	else {
+		g_message ("unrecognized command: %s", line);
+		return gkd_gpg_agent_send_reply (call, FALSE, "103 unknown command");
+	}
+}
+
+static gpointer
+run_client_thread (gpointer data)
+{
+	gint *socket = data;
+	GError *error;
+	GkdGpgAgentCall call;
+	gboolean ret;
+	gchar *line;
+	gsize n_line;
+
+	g_assert (GP11_IS_MODULE (pkcs11_module));
+
+	call.sock = g_atomic_int_get (socket);
+	call.channel = g_io_channel_unix_new (call.sock);
+	g_io_channel_set_encoding (call.channel, NULL, NULL);
+	g_io_channel_set_close_on_unref (call.channel, FALSE);
+	call.module = g_object_ref (pkcs11_module);
+
+	for (;;) {
+
+		/* Read in a line */
+		g_io_channel_read_line (call.channel, &line, &n_line, NULL, &error);
+		if (error != NULL) {
+			g_critical ("gpg agent couldn't read from socket: %s",
+			            egg_error_message (error));
+			g_clear_error (&error);
+			break;
+		}
+
+		/* Process that line */
+		if (line && n_line > 0)
+			ret = process_line (&call, line);
+		else
+			ret = TRUE;
+
+		g_free (line);
+
+		if (!ret)
+			break;
+	}
+
+	g_io_channel_shutdown (call.channel, FALSE, NULL);
+	g_object_unref (call.module);
+
+	close (call.sock);
+	g_atomic_int_set (socket, -1);
+
+	return NULL;
+}
+
+/* --------------------------------------------------------------------------------------
+ * SESSION MANAGEMENT
+ */
+
+/* The main PKCS#11 session that owns objects, and the mutex/cond for waiting on it */
+static GP11Session *pkcs11_main_session = NULL;
+static gboolean pkcs11_main_checked = FALSE;
+static GMutex *pkcs11_main_mutex = NULL;
+static GCond *pkcs11_main_cond = NULL;
+
+GP11Session*
+gkd_gpg_agent_checkout_main_session (void)
+{
+	GP11Session *result;
+
+	g_mutex_lock (pkcs11_main_mutex);
+
+		g_assert (GP11_IS_SESSION (pkcs11_main_session));
+		while (pkcs11_main_checked)
+			g_cond_wait (pkcs11_main_cond, pkcs11_main_mutex);
+		pkcs11_main_checked = TRUE;
+		result = g_object_ref (pkcs11_main_session);
+
+	g_mutex_unlock (pkcs11_main_mutex);
+
+	return result;
+}
+
+void
+gkd_gpg_agent_checkin_main_session (GP11Session *session)
+{
+	g_assert (GP11_IS_SESSION (session));
+
+	g_mutex_lock (pkcs11_main_mutex);
+
+		g_assert (session == pkcs11_main_session);
+		g_assert (pkcs11_main_checked);
+
+		g_object_unref (session);
+		pkcs11_main_checked = FALSE;
+		g_cond_signal (pkcs11_main_cond);
+
+	g_mutex_unlock (pkcs11_main_mutex);
+}
+
+/* --------------------------------------------------------------------------------------
+ * MAIN THREAD
+ */
+
+typedef struct _Client {
+	GThread *thread;
+	gint sock;
+	gchar *buffer;
+	gsize n_buffer;
+} Client;
+
+/* Each client thread in this list */
+static GList *socket_clients = NULL;
+
+/* The main socket we listen on */
+static int socket_fd = -1;
+
+/* The path of the socket listening on */
+static char socket_path[1024] = { 0, };
+
+void
+gkd_gpg_agent_accept (void)
+{
+	Client *client;
+	struct sockaddr_un addr;
+	socklen_t addrlen;
+	GError *error = NULL;
+	GList *l;
+	int new_fd;
+
+	g_return_if_fail (socket_fd != -1);
+
+	/* Cleanup any completed dispatch threads */
+	for (l = socket_clients; l; l = g_list_next (l)) {
+		client = l->data;
+		if (g_atomic_int_get (&client->sock) == -1) {
+			g_thread_join (client->thread);
+			g_slice_free (Client, client);
+			l->data = NULL;
+		}
+	}
+	socket_clients = g_list_remove_all (socket_clients, NULL);
+
+	addrlen = sizeof (addr);
+	new_fd = accept (socket_fd, (struct sockaddr*) &addr, &addrlen);
+	if (socket_fd < 0) {
+		g_warning ("cannot accept GPG agent connection: %s", strerror (errno));
+		return;
+	}
+
+	client = g_slice_new0 (Client);
+	client->sock = new_fd;
+
+	/* And create a new thread/process */
+	client->thread = g_thread_create (run_client_thread, &client->sock, TRUE, &error);
+	if (!client->thread) {
+		g_warning ("couldn't create thread GPG agent connection: %s",
+		           error && error->message ? error->message : "");
+		g_slice_free (Client, client);
+		return;
+	}
+
+	socket_clients = g_list_append (socket_clients, client);
+}
+
+void
+gkd_gpg_agent_shutdown (void)
+{
+	Client *client;
+	GList *l;
+
+	if (socket_fd != -1)
+		close (socket_fd);
+
+	if (*socket_path)
+		unlink (socket_path);
+
+	/* Stop all of the dispatch threads */
+	for (l = socket_clients; l; l = g_list_next (l)) {
+		client = l->data;
+
+		/* Forcibly shutdown the connection */
+		if (client->sock != -1)
+			shutdown (client->sock, SHUT_RDWR);
+		g_thread_join (client->thread);
+
+		/* This is always closed by client thread */
+		g_assert (client->sock == -1);
+		g_slice_free (Client, client);
+	}
+
+	g_list_free (socket_clients);
+	socket_clients = NULL;
+}
+
+void
+gkd_gpg_agent_uninitialize (void)
+{
+	gboolean ret;
+
+	g_assert (pkcs11_main_mutex);
+	ret = g_mutex_trylock (pkcs11_main_mutex);
+	g_assert (ret);
+
+		g_assert (GP11_IS_SESSION (pkcs11_main_session));
+		g_assert (!pkcs11_main_checked);
+		g_object_unref (pkcs11_main_session);
+		pkcs11_main_session = NULL;
+
+	g_mutex_unlock (pkcs11_main_mutex);
+	g_mutex_free (pkcs11_main_mutex);
+	g_cond_free (pkcs11_main_cond);
+
+	g_assert (pkcs11_module);
+	g_object_unref (pkcs11_module);
+}
+
+int
+gkd_gpg_agent_initialize (CK_FUNCTION_LIST_PTR funcs)
+{
+	GP11Module *module;
+	gboolean ret;
+
+	g_return_val_if_fail (funcs, -1);
+
+	module = gp11_module_new (funcs);
+	gp11_module_set_auto_authenticate (module, GP11_AUTHENTICATE_OBJECTS);
+	gp11_module_set_pool_sessions (module, TRUE);
+	ret = gkd_gpg_agent_initialize_with_module (module);
+	g_object_unref (module);
+	return ret;
+}
+
+gboolean
+gkd_gpg_agent_initialize_with_module (GP11Module *module)
+{
+	GP11Session *session = NULL;
+	GList *slots, *l;
+	GError *error = NULL;
+	GP11SlotInfo *info;
+
+	g_assert (GP11_IS_MODULE (module));
+
+	/*
+	 * Find the right slot.
+	 *
+	 * TODO: This isn't necessarily the best way to do this.
+	 * A good function could be added to gp11 library.
+	 * But needs more thought on how to do this.
+	 */
+	slots = gp11_module_get_slots (module, TRUE);
+	for (l = slots; !session && l; l = g_list_next (l)) {
+		info = gp11_slot_get_info (l->data);
+		if (g_ascii_strcasecmp ("Secret Store", info->slot_description) == 0) {
+
+			/* Try and open a session */
+			session = gp11_slot_open_session (l->data, CKF_SERIAL_SESSION, &error);
+			if (!session) {
+				g_warning ("couldn't create pkcs#11 session: %s", error->message);
+				g_clear_error (&error);
+			}
+		}
+
+		gp11_slot_info_free (info);
+	}
+
+	gp11_list_unref_free (slots);
+
+	if (!session) {
+		g_warning ("couldn't select a usable pkcs#11 slot for the ssh agent to use");
+		return FALSE;
+	}
+
+	pkcs11_module = g_object_ref (module);
+
+	pkcs11_main_mutex = g_mutex_new ();
+	pkcs11_main_cond = g_cond_new ();
+	pkcs11_main_checked = FALSE;
+	pkcs11_main_session = session;
+
+	return TRUE;
+}
+
+int
+gkd_gpg_agent_startup (const gchar *prefix)
+{
+	struct sockaddr_un addr;
+	gchar *agent_info;
+	int sock;
+
+	g_return_val_if_fail (prefix, -1);
+
+	snprintf (socket_path, sizeof (socket_path), "%s/gpg", prefix);
+	unlink (socket_path);
+
+	sock = socket (AF_UNIX, SOCK_STREAM, 0);
+	if (sock < 0) {
+		g_warning ("couldn't create socket: %s", g_strerror (errno));
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	strncpy (addr.sun_path, socket_path, sizeof (addr.sun_path));
+	if (bind (sock, (struct sockaddr *) & addr, sizeof (addr)) < 0) {
+		g_warning ("couldn't bind to socket: %s: %s", socket_path, g_strerror (errno));
+		close (sock);
+		return -1;
+	}
+
+	if (listen (sock, 128) < 0) {
+		g_warning ("couldn't listen on socket: %s", g_strerror (errno));
+		close (sock);
+		return -1;
+	}
+
+	/*
+	 * TODO: This should be <socket>:<pid>:<protocol_version>
+	 * Need to figure out a way to get the PID in there.
+	 */
+	agent_info = g_strdup_printf ("%s:0:1", socket_path);
+	g_setenv ("SSH_AUTH_SOCK", agent_info, TRUE);
+	g_free (agent_info);
+
+	socket_fd = sock;
+	return sock;
+}
diff --git a/daemon/gpg-agent/gkd-gpg-agent.h b/daemon/gpg-agent/gkd-gpg-agent.h
new file mode 100644
index 0000000..8ed8337
--- /dev/null
+++ b/daemon/gpg-agent/gkd-gpg-agent.h
@@ -0,0 +1,40 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2010 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 GKDGPGAGENT_H_
+#define GKDGPGAGENT_H_
+
+#include <glib.h>
+
+#include "pkcs11/pkcs11.h"
+
+int               gkd_gpg_agent_startup                 (const gchar *prefix);
+
+void              gkd_gpg_agent_accept                  (void);
+
+void              gkd_gpg_agent_shutdown                (void);
+
+gboolean          gkd_gpg_agent_initialize              (CK_FUNCTION_LIST_PTR funcs);
+
+void              gkd_gpg_agent_uninitialize            (void);
+
+#endif /* GKDGPGAGENT_H_ */
diff --git a/daemon/pkcs11/gkd-pkcs11.c b/daemon/pkcs11/gkd-pkcs11.c
index 1e97169..fb93f02 100644
--- a/daemon/pkcs11/gkd-pkcs11.c
+++ b/daemon/pkcs11/gkd-pkcs11.c
@@ -34,6 +34,7 @@
 #include "pkcs11/ssh-store/gck-ssh-store.h"
 #include "pkcs11/user-store/gck-user-store.h"
 
+#include "gpg-agent/gkd-gpg-agent.h"
 #include "ssh-agent/gkd-ssh-agent.h"
 
 #include <string.h>
@@ -118,7 +119,8 @@ gkd_pkcs11_initialize (void)
 
 	egg_cleanup_register (pkcs11_daemon_cleanup, NULL);
 
-	ret = gkd_ssh_agent_initialize (pkcs11_roof) &&
+	ret = gkd_gpg_agent_initialize (pkcs11_roof) &&
+	      gkd_ssh_agent_initialize (pkcs11_roof) &&
 	      gck_rpc_layer_initialize (pkcs11_roof);
 
 	return ret;
@@ -162,46 +164,6 @@ gkd_pkcs11_startup_pkcs11 (void)
 	return TRUE;
 }
 
-static void
-pkcs11_ssh_cleanup (gpointer unused)
-{
-	gkd_ssh_agent_shutdown ();
-}
-
-static gboolean
-accept_ssh_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
-{
-	if (cond == G_IO_IN)
-		gkd_ssh_agent_accept ();
-	return TRUE;
-}
-
-gboolean
-gkd_pkcs11_startup_ssh (void)
-{
-	GIOChannel *channel;
-	const gchar *base_dir;
-	int sock;
-
-	base_dir = gkd_util_get_master_directory ();
-	g_return_val_if_fail (base_dir, FALSE);
-
-	sock = gkd_ssh_agent_startup (base_dir);
-	if (sock == -1)
-		return FALSE;
-
-	channel = g_io_channel_unix_new (sock);
-	g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_ssh_client, NULL);
-	g_io_channel_unref (channel);
-
-	/* gck-ssh-agent sets the environment variable */
-	gkd_util_push_environment ("SSH_AUTH_SOCK", g_getenv ("SSH_AUTH_SOCK"));
-
-	egg_cleanup_register (pkcs11_ssh_cleanup, NULL);
-
-	return TRUE;
-}
-
 CK_FUNCTION_LIST_PTR
 gkd_pkcs11_get_functions (void)
 {



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