diff options
author | Stef Walter <stefw@redhat.com> | 2014-09-02 11:15:05 +0200 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2018-02-24 08:24:39 +0100 |
commit | 661767af426c78dde32b9f3e10a018a3eb7015f8 (patch) | |
tree | e125485290d438f377a4bf9ff86ebc327a91ce0f | |
parent | 3548ba1367fa977728746939af3abc899e205546 (diff) | |
download | gcr-wip/dueno/ssh-askpass.tar.gz |
gcr: Implement GcrSshAskpass APIwip/dueno/ssh-askpass
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
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | docs/reference/gcr/gcr-docs.sgml | 1 | ||||
-rw-r--r-- | docs/reference/gcr/gcr-sections.txt | 17 | ||||
-rw-r--r-- | gcr/Makefile.am | 22 | ||||
-rw-r--r-- | gcr/gcr-base.h | 1 | ||||
-rw-r--r-- | gcr/gcr-base.symbols | 5 | ||||
-rw-r--r-- | gcr/gcr-ssh-askpass.c | 541 | ||||
-rw-r--r-- | gcr/gcr-ssh-askpass.h | 53 | ||||
-rw-r--r-- | gcr/test-ssh-askpass.c | 173 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | ui/Makefile.am | 2 |
11 files changed, 816 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-base.symbols b/gcr/gcr-base.symbols index c3ddfbc..7a97e9a 100644 --- a/gcr/gcr-base.symbols +++ b/gcr/gcr-base.symbols @@ -218,6 +218,11 @@ gcr_simple_collection_contains gcr_simple_collection_get_type gcr_simple_collection_new gcr_simple_collection_remove +gcr_ssh_askpass_child_setup +gcr_ssh_askpass_executable +gcr_ssh_askpass_get_interaction +gcr_ssh_askpass_get_type +gcr_ssh_askpass_new gcr_system_prompter_get_mode gcr_system_prompter_get_prompt_type gcr_system_prompter_get_prompting diff --git a/gcr/gcr-ssh-askpass.c b/gcr/gcr-ssh-askpass.c new file mode 100644 index 0000000..3261916 --- /dev/null +++ b/gcr/gcr-ssh-askpass.c @@ -0,0 +1,541 @@ +/* + * 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 guchar *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 guchar *)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, (const guchar *)"\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); + g_free (self->socket); + self->socket = NULL; + } + + if (self->directory) { + g_rmdir (self->directory); + g_free (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 ("GCR_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; + guchar *buf; + gint count; + gint i; + int ret; + int fd; + + path = g_getenv ("GCR_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, (const guchar *)message->str, message->len)) { + g_string_free (message, TRUE); + return -1; + } + g_string_free (message, TRUE); + + 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)); + } + egg_secure_free (buf); + return -1; + } + ret = 0; + } else if (ret == 0) { + break; + } else if (!write_all (1, buf, ret)) { + egg_secure_free (buf); + return -1; + } + count += ret; + } + + if (count == 1 && buf[0] == 0xff) { + egg_secure_free (buf); + return -1; + } + + egg_secure_free (buf); + 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..a6a4965 --- /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, !=, 0); + 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/po/POTFILES.in b/po/POTFILES.in index bb70c17..fad541b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -16,6 +16,7 @@ gcr/gcr-importer.c gcr/gcr-library.c gcr/gcr-parser.c gcr/gcr-prompt.c +gcr/gcr-ssh-askpass.c gcr/gcr-subject-public-key.c gcr/gcr-system-prompt.c gcr/gcr-trust.c 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 |