[gcr/wip/dueno/ssh-askpass: 2/3] gcr: Implement GcrSshAskpass API



commit f30a10ffb4b8106625f778faabe4d254f869655a
Author: Stef Walter <stefw redhat com>
Date:   Tue Sep 2 11:15:05 2014 +0200

    gcr: Implement GcrSshAskpass API
    
    This allows calling ssh programs like ssh-add or ssh itself, and
    handling the password prompts.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=735873

 Makefile.am                         |    2 +
 docs/reference/gcr/gcr-docs.sgml    |    1 +
 docs/reference/gcr/gcr-sections.txt |   17 ++
 gcr/Makefile.am                     |   22 ++-
 gcr/gcr-base.h                      |    1 +
 gcr/gcr-ssh-askpass.c               |  531 +++++++++++++++++++++++++++++++++++
 gcr/gcr-ssh-askpass.h               |   53 ++++
 gcr/test-ssh-askpass.c              |  173 ++++++++++++
 ui/Makefile.am                      |    2 +-
 9 files changed, 800 insertions(+), 2 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 53f8776..e62566f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -76,6 +76,7 @@ AM_CPPFLAGS = \
        -DSRCDIR="\"@abs_srcdir@\"" \
        -DBUILDDIR="\"@abs_builddir@\"" \
        -DLOCALEDIR=\""$(datadir)/locale"\" \
+       -DLIBEXECDIR="\"$(libexecdir)\"" \
        $(GLIB_CFLAGS)
 
 LDADD = \
@@ -87,6 +88,7 @@ TESTS =
 check_PROGRAMS =
 check_LTLIBRARIES =
 lib_LTLIBRARIES =
+libexec_PROGRAMS =
 noinst_LTLIBRARIES =
 noinst_PROGRAMS = $(check_PROGRAMS)
 
diff --git a/docs/reference/gcr/gcr-docs.sgml b/docs/reference/gcr/gcr-docs.sgml
index 799d574..749bebe 100644
--- a/docs/reference/gcr/gcr-docs.sgml
+++ b/docs/reference/gcr/gcr-docs.sgml
@@ -54,6 +54,7 @@
                <xi:include href="xml/gcr-prompt-dialog.xml"/>
                <xi:include href="xml/gcr-system-prompt.xml"/>
                <xi:include href="xml/gcr-system-prompter.xml"/>
+               <xi:include href="xml/gcr-ssh-askpass.xml"/>
        </part>
 
        <part id="storage">
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index fb4f78b..de171dd 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -696,6 +696,23 @@ gcr_secure_memory_is_secure
 </SECTION>
 
 <SECTION>
+<FILE>gcr-ssh-askpass</FILE>
+GcrSshAskpass
+gcr_ssh_askpass_new
+gcr_ssh_askpass_get_interaction
+gcr_ssh_askpass_child_setup
+<SUBSECTION Standard>
+GCR_IS_SSH_ASKPASS
+GCR_IS_SSH_ASKPASS_CLASS
+GCR_SSH_ASKPASS
+GCR_SSH_ASKPASS_CLASS
+GCR_SSH_ASKPASS_GET_CLASS
+GCR_TYPE_SSH_ASKPASS
+GcrSshAskpassClass
+gcr_ssh_askpass_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gcr-private</FILE>
 <SUBSECTION Private>
 GCR_GNUPG_COLLECTION
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 99f50a6..692875b 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -25,6 +25,7 @@ gcr_HEADER_FILES = \
        gcr/gcr-secure-memory.h \
        gcr/gcr-simple-certificate.h \
        gcr/gcr-simple-collection.h \
+       gcr/gcr-ssh-askpass.h \
        gcr/gcr-system-prompt.h \
        gcr/gcr-system-prompter.h \
        gcr/gcr-trust.h \
@@ -74,6 +75,7 @@ gcr_PUBLIC_FILES = \
        gcr/gcr-secure-memory.c gcr/gcr-secure-memory.h \
        gcr/gcr-simple-certificate.c gcr/gcr-simple-certificate.h \
        gcr/gcr-simple-collection.c gcr/gcr-simple-collection.h \
+       gcr/gcr-ssh-askpass.c gcr/gcr-ssh-askpass.h \
        gcr/gcr-system-prompt.c gcr/gcr-system-prompt.h \
        gcr/gcr-system-prompter.c gcr/gcr-system-prompter.h \
        gcr/gcr-types.h \
@@ -167,6 +169,19 @@ gcr/gcr-dbus-generated.c: $(DBUS_XML_DEFINITIONS)
 
 gcr/gcr-dbus-generated.h: gcr/gcr-dbus-generated.c
 
+libexec_PROGRAMS += gcr-ssh-askpass
+
+gcr_ssh_askpass_SOURCES = gcr/gcr-ssh-askpass.c
+
+gcr_ssh_askpass_CFLAGS = \
+       -DGCR_SSH_ASKPASS_TOOL \
+       -DGCR_COMPILATION \
+       $(GLIB_CFLAGS)
+
+gcr_ssh_askpass_LDADD = \
+       libegg.la \
+       $(GLIB_LIBS)
+
 pkgconfig_DATA += \
        gcr-base-$(GCR_MAJOR).pc
 
@@ -293,7 +308,8 @@ gcr_TESTS = \
        test-gnupg-key \
        test-gnupg-collection \
        test-gnupg-process \
-       test-system-prompt
+       test-system-prompt \
+       test-ssh-askpass
 
 test_certificate_SOURCES = gcr/test-certificate.c
 test_certificate_CFLAGS = $(gcr_CFLAGS)
@@ -359,6 +375,10 @@ test_simple_certificate_SOURCES = gcr/test-simple-certificate.c
 test_simple_certificate_CFLAGS = $(gcr_CFLAGS)
 test_simple_certificate_LDADD = $(gcr_LIBS)
 
+test_ssh_askpass_SOURCES = gcr/test-ssh-askpass.c
+test_ssh_askpass_CFLAGS = $(gcr_CFLAGS)
+test_ssh_askpass_LDADD = libegg-test.la $(gcr_LIBS)
+
 test_subject_public_key_SOURCES = gcr/test-subject-public-key.c
 test_subject_public_key_CFLAGS = $(gcr_CFLAGS)
 test_subject_public_key_LDADD = $(gcr_LIBS)
diff --git a/gcr/gcr-base.h b/gcr/gcr-base.h
index 6664845..ea80d8e 100644
--- a/gcr/gcr-base.h
+++ b/gcr/gcr-base.h
@@ -52,6 +52,7 @@
 #include <gcr/gcr-secure-memory.h>
 #include <gcr/gcr-simple-certificate.h>
 #include <gcr/gcr-simple-collection.h>
+#include <gcr/gcr-ssh-askpass.h>
 #include <gcr/gcr-system-prompt.h>
 #include <gcr/gcr-system-prompter.h>
 #include <gcr/gcr-trust.h>
diff --git a/gcr/gcr-ssh-askpass.c b/gcr/gcr-ssh-askpass.c
new file mode 100644
index 0000000..9a532b8
--- /dev/null
+++ b/gcr/gcr-ssh-askpass.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2014 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Auther: Stef Walter <stefw gnome org>
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-askpass.h"
+
+#include <glib-unix.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+/* Used from tests to override location */
+const char *gcr_ssh_askpass_executable = LIBEXECDIR "/gcr-ssh-askpass";
+
+/**
+ * SECTION:gcr-ssh-askpass
+ * @title: GcrSshAskpass
+ * @short_description: Allows an ssh command to callback for a password
+ *
+ * When used as the setup function while spawning an ssh command like ssh-add
+ * or ssh, this allows callbacks for passwords on the provided interaction.
+ */
+
+/**
+ * GcrSshAskpass:
+ *
+ * An object containing the password prompting state.
+ */
+
+/**
+ * GcrSshAskpassClass:
+ *
+ * The class for #GcrSshAskpass
+ */
+
+enum {
+       PROP_0,
+       PROP_INTERACTION
+};
+
+struct _GcrSshAskpass {
+       GObject parent;
+       GTlsInteraction *interaction;
+       gchar *directory;
+       gchar *socket;
+       guint source;
+       gint fd;
+       GCancellable *cancellable;
+       GMainContext *context;
+};
+
+struct _GcrSshAskpassClass {
+       GObjectClass parent;
+};
+
+G_DEFINE_TYPE (GcrSshAskpass, gcr_ssh_askpass, G_TYPE_OBJECT);
+
+static void
+gcr_ssh_askpass_init (GcrSshAskpass *self)
+{
+       self->cancellable = g_cancellable_new ();
+       self->context = g_main_context_ref_thread_default ();
+}
+
+static void
+gcr_ssh_askpass_set_property (GObject *obj,
+                              guint prop_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+       GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
+
+       switch (prop_id) {
+       case PROP_INTERACTION:
+               self->interaction = g_value_dup_object (value);
+               g_return_if_fail (self->interaction != NULL);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gcr_ssh_askpass_get_property (GObject *obj,
+                              guint prop_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+       GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
+
+       switch (prop_id) {
+       case PROP_INTERACTION:
+               g_value_set_object (value, gcr_ssh_askpass_get_interaction (self));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+               break;
+       }
+}
+
+static gboolean
+write_all (gint fd,
+           const gchar *buf,
+           gsize len)
+{
+       guint all = len;
+       int res;
+
+       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;
+}
+
+static GString *
+read_all_into_string (gint fd)
+{
+       GString *input = g_string_new ("");
+       gsize len;
+       gssize ret;
+
+       for (;;) {
+               len = input->len;
+               g_string_set_size (input, len + 256);
+               ret = read (fd, input->str + len, 256);
+               if (ret < 0) {
+                       if (errno != EINTR && errno != EAGAIN) {
+                               g_critical ("couldn't read from gcr-ssh-askpass: %s", g_strerror (errno));
+                               g_string_free (input, TRUE);
+                               return NULL;
+                       }
+               } else if (ret == 0) {
+                       return input;
+               } else {
+                       input->len = len + ret;
+                       input->str[input->len] = '\0';
+               }
+       }
+}
+
+typedef struct {
+       gint fd;
+       GTlsInteraction *interaction;
+       GCancellable *cancellable;
+} AskpassContext;
+
+static gpointer
+askpass_thread (gpointer data)
+{
+       AskpassContext *ctx = data;
+       gboolean success = FALSE;
+       GTlsPassword *password = NULL;
+       GTlsInteractionResult res;
+       GError *error = NULL;
+       const guchar *value;
+       GString *input;
+       gsize length;
+
+       input = read_all_into_string (ctx->fd);
+       if (!input)
+               goto out;
+
+       if (input->len == 0)
+               g_string_append (input, _("Enter your OpenSSH passphrase"));
+
+       g_debug ("asking for ssh-askpass password: %s", input->str);
+
+       password = g_tls_password_new (G_TLS_PASSWORD_NONE, input->str);
+       res = g_tls_interaction_invoke_ask_password (ctx->interaction, password, ctx->cancellable, &error);
+
+       g_debug ("ask password returned %d", res);
+
+       success = FALSE;
+       if (res == G_TLS_INTERACTION_HANDLED) {
+               value = g_tls_password_get_value (password, &length);
+               if (write_all (ctx->fd, (const gchar *)value, length))
+                       g_debug ("password written to gcr-ssh-askpass");
+               else
+                       g_message ("failed to write password to gcr-ssh-askpass");
+               success = TRUE;
+       } else if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+               g_warning ("couldn't prompt for password: %s", error->message);
+       } else {
+               g_debug ("unhandled or cancelled ask password");
+       }
+
+out:
+       if (!success) {
+               g_debug ("writing failure to gcr-ssh-askpass");
+               write_all (ctx->fd, "\xff", 1);
+       }
+       if (password)
+               g_object_unref (password);
+       if (input)
+               g_string_free (input, TRUE);
+       g_clear_error (&error);
+
+       g_close (ctx->fd, NULL);
+       g_object_unref (ctx->interaction);
+       g_object_unref (ctx->cancellable);
+       g_free (ctx);
+
+       return NULL;
+}
+
+static gboolean
+askpass_accept (gint fd,
+                GIOCondition cond,
+                gpointer user_data)
+{
+       GcrSshAskpass *self = user_data;
+       AskpassContext *ctx;
+       struct sockaddr_un addr;
+       socklen_t addrlen;
+       GThread *thread;
+       gint new_fd;
+
+       addrlen = sizeof (addr);
+       new_fd = accept (fd, (struct sockaddr *) &addr, &addrlen);
+       if (new_fd < 0) {
+               if (errno != EAGAIN && errno != EINTR)
+                       g_warning ("couldn't accept new control request: %s", g_strerror (errno));
+               return TRUE;
+       }
+
+       g_debug ("accepted new connection from gcr-ssh-askpass");
+
+       ctx = g_new0 (AskpassContext, 1);
+       ctx->fd = new_fd;
+       ctx->interaction = g_object_ref (self->interaction);
+       ctx->cancellable = g_object_ref (self->cancellable);
+
+       thread = g_thread_new ("ssh-askpass", askpass_thread, ctx);
+       g_thread_unref (thread);
+
+       return TRUE;
+}
+
+static void
+gcr_ssh_askpass_constructed (GObject *obj)
+{
+       GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
+       struct sockaddr_un addr;
+
+       G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->constructed (obj);
+
+       self->directory = g_build_filename (g_get_user_runtime_dir (), "ssh-askpass.XXXXXX", NULL);
+       if (!g_mkdtemp_full (self->directory, 0700)) {
+               g_warning ("couldn't create temporary directory: %s: %s", self->directory, g_strerror 
(errno));
+               return;
+       }
+
+       self->socket = g_build_filename (self->directory, "socket", NULL);
+
+       self->fd = socket (AF_UNIX, SOCK_STREAM, 0);
+       if (self->fd < 0) {
+               g_warning ("couldn't open socket: %s", g_strerror (errno));
+               return;
+       }
+
+       if (!g_unix_set_fd_nonblocking (self->fd, TRUE, NULL))
+               g_return_if_reached ();
+
+       memset (&addr, 0, sizeof (addr));
+       addr.sun_family = AF_UNIX;
+       g_strlcpy (addr.sun_path, self->socket, sizeof (addr.sun_path));
+       if (bind (self->fd, (struct sockaddr*) &addr, sizeof (addr)) < 0) {
+               g_warning ("couldn't bind to askpass socket: %s: %s", self->socket, g_strerror (errno));
+               return;
+       }
+
+       if (listen (self->fd, 128) < 0) {
+               g_warning ("couldn't listen on askpass socket: %s: %s", self->socket, g_strerror (errno));
+               return;
+       }
+
+       g_debug ("listening for gcr-ssh-askpass at: %s", self->socket);
+
+       self->source = g_unix_fd_add (self->fd, G_IO_IN, askpass_accept, self);
+}
+
+static void
+gcr_ssh_askpass_dispose (GObject *obj)
+{
+       GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
+
+       g_cancellable_cancel (self->cancellable);
+
+       if (self->source) {
+               g_source_remove (self->source);
+               self->source = 0;
+       }
+
+       if (self->fd >= 0) {
+               g_close (self->fd, NULL);
+               self->fd = -1;
+       }
+
+       if (self->socket) {
+               g_unlink (self->socket);
+               self->socket = NULL;
+       }
+
+       if (self->directory) {
+               g_rmdir (self->directory);
+               self->directory = NULL;
+       }
+
+       if (self->interaction) {
+               g_object_unref (self->interaction);
+               self->interaction = NULL;
+       }
+
+       G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->dispose (obj);
+}
+
+static void
+gcr_ssh_askpass_finalize (GObject *obj)
+{
+       GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
+
+       g_object_unref (self->cancellable);
+       g_main_context_unref (self->context);
+
+       G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->finalize (obj);
+}
+
+/**
+ * gcr_ssh_askpass_new:
+ * @interaction: the interaction to use for prompting paswords
+ *
+ * Create a new GcrSshAskpass object which can be used to spawn an
+ * ssh command and prompt for any necessary passwords.
+ *
+ * Use the gcr_ssh_askpass_child_setup() function as a callback with
+ * g_spawn_sync(), g_spawn_async() or g_spawn_async_with_pipes().
+ *
+ * Returns: (transfer full): A new #GcrSshAskpass object
+ */
+GcrSshAskpass *
+gcr_ssh_askpass_new (GTlsInteraction *interaction)
+{
+       g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), NULL);
+       return g_object_new (GCR_TYPE_SSH_ASKPASS,
+                            "interaction", interaction,
+                            NULL);
+}
+
+/**
+ * gcr_ssh_askpass_get_interaction:
+ * @self: a #GcrSshAskpass object
+ *
+ * Get the interaction associated with this object.
+ *
+ * Returns: (transfer none): the interaction
+ */
+GTlsInteraction *
+gcr_ssh_askpass_get_interaction (GcrSshAskpass *self)
+{
+       g_return_val_if_fail (GCR_IS_SSH_ASKPASS (self), NULL);
+       return self->interaction;
+}
+
+/**
+ * gcr_ssh_askpass_child_setup:
+ * @askpass: a #GcrSshAskpass object
+ *
+ * Use this function as a callback setup function passed to g_spawn_sync(),
+ * g_spawn_async(), g_spawn_async_with_pipes().
+ */
+void
+gcr_ssh_askpass_child_setup (gpointer askpass)
+{
+       GcrSshAskpass *self = askpass;
+
+       g_setenv ("SSH_ASKPASS", gcr_ssh_askpass_executable, TRUE);
+
+       /* ssh wants DISPLAY set in order to use SSH_ASKPASS */
+       if (!g_getenv ("DISPLAY"))
+               g_setenv ("DISPLAY", "x", TRUE);
+
+       /* For communicating back with ourselves */
+       if (self->socket)
+               g_setenv ("SSH_ASKPASS_SOCKET", self->socket, TRUE);
+
+       /* Close the control terminal */
+       setsid ();
+}
+
+static void
+gcr_ssh_askpass_class_init (GcrSshAskpassClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       gobject_class->get_property = gcr_ssh_askpass_get_property;
+       gobject_class->set_property = gcr_ssh_askpass_set_property;
+       gobject_class->constructed = gcr_ssh_askpass_constructed;
+       gobject_class->dispose = gcr_ssh_askpass_dispose;
+       gobject_class->finalize = gcr_ssh_askpass_finalize;
+
+       /**
+        * GcrSshAskpass:interaction:
+        *
+        * The interaction used to prompt for passwords.
+        */
+       g_object_class_install_property (gobject_class, PROP_INTERACTION,
+                  g_param_spec_object ("interaction", "Interaction", "Interaction",
+                                       G_TYPE_TLS_INTERACTION,
+                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+#ifdef GCR_SSH_ASKPASS_TOOL
+
+#include "egg/egg-secure-memory.h"
+
+EGG_SECURE_DEFINE_GLIB_GLOBALS ();
+EGG_SECURE_DECLARE ("ssh-askpass");
+
+int
+main (int argc,
+      char *argv[])
+{
+       GString *message;
+       struct sockaddr_un addr;
+       const gchar *path;
+       gchar *buf;
+       gint count;
+       gint i;
+       int ret;
+       int fd;
+
+       path = g_getenv ("SSH_ASKPASS_SOCKET");
+       if (path == NULL) {
+               g_printerr ("gcr-ssh-askpass: this program is not meant to be run directly");
+               return 2;
+       }
+
+       fd = socket (AF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0) {
+               g_warning ("couldn't open socket: %s", g_strerror (errno));
+               return -1;
+       }
+
+       memset (&addr, 0, sizeof (addr));
+       addr.sun_family = AF_UNIX;
+       g_strlcpy (addr.sun_path, path, sizeof (addr.sun_path));
+       if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0) {
+               g_warning ("couldn't connect to askpass socket: %s: %s", path, g_strerror (errno));
+               return -1;
+       }
+
+       message = g_string_new ("");
+       if (argc > 1) {
+               for (i = 1; i < argc; i++) {
+                       if (i == 1)
+                               g_string_append_c (message, ' ');
+                       g_string_append (message, argv[i]);
+               }
+       }
+
+       if (!write_all (fd, message->str, message->len))
+               return -1;
+
+       if (shutdown (fd, SHUT_WR) < 0) {
+               g_warning ("couldn't shutdown socket: %s", g_strerror (errno));
+               return -1;
+       }
+
+       count = 0;
+       buf = egg_secure_alloc (128);
+
+       for (;;) {
+               ret = read (fd, buf, 128);
+               if (ret < 0) {
+                       if (errno != EINTR && errno != EAGAIN) {
+                               if (errno != ECONNRESET) {
+                                       g_critical ("couldn't read from ssh-askpass socket: %s",
+                                                   g_strerror (errno));
+                               }
+                               return -1;
+                       }
+                       ret = 0;
+               } else if (ret == 0) {
+                       break;
+               } else if (!write_all (1, buf, ret)) {
+                       return -1;
+               }
+               count += ret;
+       }
+
+       if (count == 1 && buf[0] == 0xff)
+               return -1;
+
+       return 0;
+}
+
+#endif /* GCR_SSH_ASKPASS_TOOL */
diff --git a/gcr/gcr-ssh-askpass.h b/gcr/gcr-ssh-askpass.h
new file mode 100644
index 0000000..634011d
--- /dev/null
+++ b/gcr/gcr-ssh-askpass.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stefw gnome org>
+ */
+
+#if !defined (__GCR_INSIDE_HEADER__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> or <gcr/gcr-base.h> can be included directly."
+#endif
+
+#ifndef __GCR_SSH_ASKPASS_H__
+#define __GCR_SSH_ASKPASS_H__
+
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_SSH_ASKPASS               (gcr_ssh_askpass_get_type ())
+#define GCR_SSH_ASKPASS(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_SSH_ASKPASS, 
GcrSshAskpass))
+#define GCR_SSH_ASKPASS_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_SSH_ASKPASS, 
GcrSshAskpassClass))
+#define GCR_IS_SSH_ASKPASS(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_SSH_ASKPASS))
+#define GCR_IS_SSH_ASKPASS_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_SSH_ASKPASS))
+#define GCR_SSH_ASKPASS_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_SSH_ASKPASS, 
GcrSshAskpassClass))
+
+typedef struct _GcrSshAskpass GcrSshAskpass;
+typedef struct _GcrSshAskpassClass GcrSshAskpassClass;
+
+GType               gcr_ssh_askpass_get_type        (void);
+
+GcrSshAskpass *     gcr_ssh_askpass_new             (GTlsInteraction *interaction);
+
+GTlsInteraction *   gcr_ssh_askpass_get_interaction (GcrSshAskpass *self);
+
+void                gcr_ssh_askpass_child_setup     (gpointer askpass);
+
+G_END_DECLS
+
+#endif /* __GCR_SSH_ASKPASS_H__ */
diff --git a/gcr/test-ssh-askpass.c b/gcr/test-ssh-askpass.c
new file mode 100644
index 0000000..5127f4b
--- /dev/null
+++ b/gcr/test-ssh-askpass.c
@@ -0,0 +1,173 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2014 Stefan Walter
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   see <http://www.gnu.org/licenses/>.
+
+   Author: Stef Walter <stefw gnome org>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr-base.h"
+
+#include "egg/egg-testing.h"
+#include "egg/mock-interaction.h"
+
+#include <glib/gstdio.h>
+
+extern const char *gcr_ssh_askpass_executable;
+
+typedef struct {
+       GTlsInteraction *interaction;
+       GcrSshAskpass *askpass;
+} Test;
+
+static void
+setup (Test *test,
+       gconstpointer password)
+{
+       test->interaction = mock_interaction_new (password);
+       test->askpass = gcr_ssh_askpass_new (test->interaction);
+}
+
+static void
+teardown (Test *test,
+          gconstpointer unused)
+{
+       g_object_unref (test->interaction);
+
+       g_object_add_weak_pointer (G_OBJECT (test->askpass), (gpointer *)&test->askpass);
+       g_object_unref (test->askpass);
+       g_assert (test->askpass == NULL);
+}
+
+static void
+on_ssh_child (GPid pid,
+              gint status,
+              gpointer user_data)
+{
+       gint *out = user_data;
+       *out = status;
+}
+
+static void
+test_ssh_keygen (Test *test,
+                 gconstpointer password)
+{
+
+       GError *error = NULL;
+       gint status;
+       GPid pid;
+
+       gchar *filename = g_build_filename (SRCDIR, "gcr", "fixtures", "pem-rsa-enc.key", NULL);
+       gchar *argv[] = { "ssh-keygen", "-y", "-f", filename, NULL };
+
+       g_assert_cmpstr (password, ==, "booo");
+
+       if (g_chmod (filename, 0600) < 0)
+               g_assert_not_reached ();
+
+       g_spawn_async (SRCDIR "/gcr/fixtures", argv, NULL,
+                      G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                      gcr_ssh_askpass_child_setup, test->askpass, &pid, &error);
+
+       g_free (filename);
+
+       if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) {
+               g_test_skip ("ssh-keygen not found");
+               return;
+       }
+
+       g_assert_no_error (error);
+
+       status = -1;
+       g_child_watch_add (pid, on_ssh_child, &status);
+
+       while (status == -1)
+               g_main_context_iteration (NULL, TRUE);
+
+       g_spawn_check_exit_status (status, &error);
+       g_assert_cmpint (status, ==, 0);
+       g_assert_no_error (error);
+}
+
+static void
+test_cancelled (Test *test,
+                gconstpointer password)
+{
+
+       GError *error = NULL;
+       gint status;
+       GPid pid;
+
+       gchar *filename = g_build_filename (SRCDIR, "gcr", "fixtures", "pem-rsa-enc.key", NULL);
+       gchar *argv[] = { "ssh-keygen", "-y", "-f", filename, NULL };
+
+       g_assert_cmpstr (password, ==, NULL);
+
+       if (g_chmod (filename, 0600) < 0)
+               g_assert_not_reached ();
+
+       g_spawn_async (SRCDIR "/gcr/fixtures", argv, NULL,
+                      G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                      gcr_ssh_askpass_child_setup, test->askpass, &pid, &error);
+
+       g_free (filename);
+
+       if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) {
+               g_test_skip ("ssh-keygen not found");
+               return;
+       }
+
+       g_assert_no_error (error);
+
+       status = -1;
+       g_child_watch_add (pid, on_ssh_child, &status);
+
+       while (status == -1)
+               g_main_context_iteration (NULL, TRUE);
+
+       g_assert_cmpint (status, ==, 256);
+       g_assert_no_error (error);
+}
+
+static void
+test_properties (Test *test,
+                 gconstpointer unused)
+{
+       GTlsInteraction *interaction;
+
+       g_object_get (test->askpass, "interaction", &interaction, NULL);
+       g_assert (interaction == test->interaction);
+       g_object_unref (interaction);
+
+       g_assert (gcr_ssh_askpass_get_interaction (test->askpass) == test->interaction);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+       g_set_prgname ("test-ssh-askpass");
+
+       gcr_ssh_askpass_executable = BUILDDIR "/gcr-ssh-askpass";
+
+       g_test_add ("/gcr/ssh-askpass/ssh-keygen", Test, "booo", setup, test_ssh_keygen, teardown);
+       g_test_add ("/gcr/ssh-askpass/cancelled", Test, NULL, setup, test_cancelled, teardown);
+       g_test_add ("/gcr/ssh-askpass/properties", Test, NULL, setup, test_properties, teardown);
+
+       return g_test_run ();
+}
diff --git a/ui/Makefile.am b/ui/Makefile.am
index f5e6a67..2d2cfcd 100644
--- a/ui/Makefile.am
+++ b/ui/Makefile.am
@@ -211,7 +211,7 @@ gcr_viewer_LDADD = \
        $(GLIB_LIBS) \
        $(GTK_LIBS)
 
-libexec_PROGRAMS = gcr-prompter
+libexec_PROGRAMS += gcr-prompter
 
 gcr_prompter_SOURCES = \
        ui/gcr-prompter-tool.c


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