[gnome-keyring/gpg-agent] Start work on gpg-agent, incomplete.
- From: Stefan Walter <stefw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-keyring/gpg-agent] Start work on gpg-agent, incomplete.
- Date: Sat, 8 May 2010 15:52:09 +0000 (UTC)
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]