[hotssh] Support public key authentication
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hotssh] Support public key authentication
- Date: Sat, 16 Nov 2013 23:07:55 +0000 (UTC)
commit 24b187e2c613554803dd88c45ccda0f577f83971
Author: Colin Walters <walters verbum org>
Date: Sat Nov 16 18:06:25 2013 -0500
Support public key authentication
This required a fairly substantial rework of the way authentication
works, and it's still not done. We need to have a better display of
authentication types, progress, etc.
Makefile-src.am | 2 +
libgssh/gssh-connection-private.h | 8 +-
libgssh/gssh-connection.c | 344 ++++++++++++++++++++++++++-----------
libgssh/gssh-connection.h | 52 ++++--
src/hotssh-password-interaction.c | 73 ++++++++
src/hotssh-password-interaction.h | 41 +++++
src/hotssh-tab.c | 148 +++++++++++++---
7 files changed, 519 insertions(+), 149 deletions(-)
---
diff --git a/Makefile-src.am b/Makefile-src.am
index 6dde95b..bf859cd 100644
--- a/Makefile-src.am
+++ b/Makefile-src.am
@@ -3,6 +3,7 @@ bin_PROGRAMS += hotssh
hotssh_headers = $(addprefix src/, \
hotssh-app.h \
hotssh-tab.h \
+ hotssh-password-interaction.h \
hotssh-win.h \
hotssh-prefs.h \
)
@@ -11,6 +12,7 @@ hotssh_SOURCES = $(hotssh_headers) \
src/main.c \
src/hotssh-app.c \
src/hotssh-tab.c \
+ src/hotssh-password-interaction.c \
src/hotssh-win.c \
src/hotssh-prefs.c \
$(NULL)
diff --git a/libgssh/gssh-connection-private.h b/libgssh/gssh-connection-private.h
index 3c750e1..0b4ea62 100644
--- a/libgssh/gssh-connection-private.h
+++ b/libgssh/gssh-connection-private.h
@@ -32,13 +32,15 @@ struct _GSshConnection
guint paused : 1;
guint select_inbound : 1;
guint select_outbound : 1;
- guint tried_userauth_none : 1;
guint preauth_continue : 1;
+ guint tried_userauth_none : 1;
guint unused : 27;
char *username;
- GPtrArray *authschemes;
+ GTlsInteraction *interaction;
+
+ GArray *authmechanisms;
ssh_session session;
GHashTable *channels;
@@ -55,7 +57,9 @@ struct _GSshConnection
char *password;
GTask *handshake_task;
+ GSshConnectionAuthMechanism current_authmech;
GTask *auth_task;
+ GTask *negotiate_task;
GHashTable *open_channel_exec_tasks;
GHashTable *channel_tasks;
};
diff --git a/libgssh/gssh-connection.c b/libgssh/gssh-connection.c
index d05bcbe..34d78b2 100644
--- a/libgssh/gssh-connection.c
+++ b/libgssh/gssh-connection.c
@@ -65,7 +65,9 @@ reset_state (GSshConnection *self)
g_assert (self->state == GSSH_CONNECTION_STATE_DISCONNECTED ||
self->state == GSSH_CONNECTION_STATE_ERROR);
+ g_clear_object (&self->interaction);
g_clear_object (&self->handshake_task);
+ g_clear_object (&self->negotiate_task);
g_clear_pointer (&self->open_channel_exec_tasks, g_hash_table_unref);
self->open_channel_exec_tasks = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
g_clear_pointer (&self->channel_tasks, g_hash_table_unref);
@@ -90,7 +92,8 @@ reset_state (GSshConnection *self)
if (self->socket_source)
g_source_destroy (self->socket_source);
g_clear_pointer (&self->socket_source, g_source_unref);
- g_clear_pointer (&self->authschemes, g_ptr_array_unref);
+ g_clear_pointer (&self->authmechanisms, g_array_unref);
+ g_clear_pointer (&self->password, g_free);
}
static void
@@ -361,13 +364,34 @@ set_hostkey_sha1 (GSshConnection *self,
return ret;
}
+static inline void
+garray_append_uint (GArray *array, guint val)
+{
+ g_array_append_val (array, val);
+}
+
+/* This evil function is necessary because we can recurse into the
+ * iteration function via g_task_return_error() calling the callback
+ * immediately.
+ */
+static void
+return_task_error_and_clear (GTask **taskptr,
+ GError *error)
+{
+ GTask *task = *taskptr;
+
+ *taskptr = NULL;
+ g_task_return_error (task, error);
+ g_object_unref (task);
+}
+
static void
gssh_connection_iteration_internal (GSshConnection *self,
GIOCondition condition)
{
GError *local_error = NULL;
GError **error = &local_error;
- int rc;
+ int rc = -1;
repeat:
@@ -386,8 +410,7 @@ gssh_connection_iteration_internal (GSshConnection *self,
{
if (!set_hostkey_sha1 (self, error))
{
- g_task_return_error (self->handshake_task, local_error);
- g_clear_object (&self->handshake_task);
+ return_task_error_and_clear (&self->handshake_task, local_error);
return;
}
}
@@ -398,8 +421,7 @@ gssh_connection_iteration_internal (GSshConnection *self,
else
{
_gssh_set_error_from_libssh (error, "Failed to handshake SSH2 session", self->session);
- g_task_return_error (self->handshake_task, local_error);
- g_clear_object (&self->handshake_task);
+ return_task_error_and_clear (&self->handshake_task, local_error);
return;
}
@@ -411,111 +433,168 @@ gssh_connection_iteration_internal (GSshConnection *self,
}
case GSSH_CONNECTION_STATE_PREAUTH:
{
+ self->paused = TRUE;
+ break;
+ }
+ case GSSH_CONNECTION_STATE_NEGOTIATE_AUTH:
+ {
int method;
- if (!self->preauth_continue)
+ rc = ssh_userauth_none (self->session, NULL);
+ if (rc == SSH_AUTH_AGAIN)
{
- self->paused = TRUE;
- break;
+ return;
}
-
- if (!self->tried_userauth_none)
+ else if (rc == SSH_AUTH_SUCCESS)
{
- /* Now try the NONE authentication; if it succeeds we jump
- * directly to authenticated.
- */
- rc = ssh_userauth_none (self->session, NULL);
- if (rc == SSH_AUTH_AGAIN)
- {
- return;
- }
- else if (rc == SSH_AUTH_SUCCESS)
- {
- state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
- goto repeat;
- }
- else if (rc == SSH_AUTH_ERROR)
- {
- _gssh_set_error_from_libssh (error, "NONE authentication failed", self->session);
- g_task_return_error (self->handshake_task, local_error);
- g_clear_object (&self->handshake_task);
- return;
- }
- else
- {
- g_assert (rc == SSH_AUTH_DENIED);
- self->tried_userauth_none = TRUE;
- }
+ state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
+ goto repeat;
+ }
+ else if (rc == SSH_AUTH_ERROR)
+ {
+ _gssh_set_error_from_libssh (error, "NONE authentication failed", self->session);
+ return_task_error_and_clear (&self->auth_task, local_error);
+ }
+ else
+ {
+ g_assert (rc == SSH_AUTH_DENIED);
+ /* Fall through if NONE failed */
}
- g_clear_pointer (&self->authschemes, g_ptr_array_unref);
- self->authschemes = g_ptr_array_new ();
+ g_clear_pointer (&self->authmechanisms, g_ptr_array_unref);
+ self->authmechanisms = g_array_new (FALSE, TRUE, sizeof (guint));
method = ssh_userauth_list (self->session, NULL);
if (method & SSH_AUTH_METHOD_PASSWORD)
- g_ptr_array_add (self->authschemes, "password");
+ garray_append_uint (self->authmechanisms, GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD);
if (method & SSH_AUTH_METHOD_GSSAPI_MIC)
- g_ptr_array_add (self->authschemes, "gssapi-mic");
+ garray_append_uint (self->authmechanisms, GSSH_CONNECTION_AUTH_MECHANISM_GSSAPI_MIC);
if (method & SSH_AUTH_METHOD_PUBLICKEY)
- g_ptr_array_add (self->authschemes, "publickey");
+ garray_append_uint (self->authmechanisms, GSSH_CONNECTION_AUTH_MECHANISM_PUBLICKEY);
if (method & SSH_AUTH_METHOD_HOSTBASED)
- g_ptr_array_add (self->authschemes, "hostbased");
+ ;
if (method & SSH_AUTH_METHOD_INTERACTIVE)
- g_ptr_array_add (self->authschemes, "keyboard-interactive");
-
- g_ptr_array_add (self->authschemes, NULL);
+ ;
state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
/* Fall through */
}
case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
{
- int method;
-
- method = ssh_userauth_list (self->session, NULL);
/* User should have connected to notify:: state and
* watch for AUTHENTICATION_REQUIRED, then call
- * gssh_connection_auth_password_async().
+ * gssh_connection_auth_async().
*/
- if (self->auth_task != NULL)
+ if (self->auth_task == NULL)
{
- if ((method & SSH_AUTH_METHOD_PASSWORD) && self->password)
+ self->paused = TRUE;
+ break;
+ }
+
+ if (!self->tried_userauth_none)
+ {
+ self->tried_userauth_none = TRUE;
+ /* Now try the NONE authentication; if it succeeds we jump
+ * directly to authenticated.
+ */
+ }
+
+ g_debug ("Trying authentication mechanism '%s'",
+ gssh_connection_auth_mechanism_to_string (self->current_authmech));
+
+ switch (self->current_authmech)
+ {
+ case GSSH_CONNECTION_AUTH_MECHANISM_NONE:
+ /* Handled above */
+ break;
+ case GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD:
+ if (!self->interaction)
{
- rc = ssh_userauth_password (self->session, NULL, self->password);
- if (rc == SSH_AUTH_AGAIN)
- {
- return;
- }
- else if (rc == SSH_AUTH_ERROR)
- {
- _gssh_set_error_from_libssh (error, "Failed to password auth", self->session);
- g_task_return_error (self->auth_task, local_error);
- g_clear_object (&self->auth_task);
- }
- else if (rc == SSH_AUTH_DENIED)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
- "Authentication failed");
- g_task_return_error (self->auth_task, local_error);
- }
- else if (rc == SSH_AUTH_PARTIAL)
- {
- g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
- "Need to continue authentication");
- g_task_return_error (self->auth_task, local_error);
- }
- else
- {
- g_task_return_boolean (self->auth_task, TRUE);
- g_clear_object (&self->auth_task);
- state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
- goto repeat;
- }
+ g_warning ("Password authentication requested, but gssh_connection_set_interaction() was not
called");
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No password interaction available");
+ return_task_error_and_clear (&self->auth_task, local_error);
+ return;
}
else
{
- g_assert_not_reached ();
+ if (!self->password)
+ {
+ gs_unref_object GTlsPassword *password =
+ g_tls_password_new (G_TLS_PASSWORD_NONE, "SSH");
+ GTlsInteractionResult result =
+ g_tls_interaction_invoke_ask_password (self->interaction, password,
+ g_task_get_cancellable (self->auth_task),
+ &local_error);
+ if (result == G_TLS_INTERACTION_FAILED)
+ {
+ return_task_error_and_clear (&self->auth_task, local_error);
+ return;
+ }
+ else
+ {
+ gsize password_len;
+ const guint8 *password_value;
+ GString *password_str;
+
+ g_assert (result == G_TLS_INTERACTION_HANDLED);
+
+ password_value = g_tls_password_get_value (password, &password_len);
+
+ if (!g_utf8_validate ((char*)password_value, password_len, NULL))
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Password is not UTF-8");
+ return_task_error_and_clear (&self->auth_task, local_error);
+ return;
+ }
+
+ password_str = g_string_new ("");
+ g_string_append_len (password_str, (char*)password_value, password_len);
+ self->password = g_string_free (password_str, FALSE);
+ }
+ }
+
+ rc = ssh_userauth_password (self->session, NULL, self->password);
}
+ break;
+ case GSSH_CONNECTION_AUTH_MECHANISM_PUBLICKEY:
+ rc = ssh_userauth_publickey_auto (self->session, NULL, NULL);
+ break;
+ case GSSH_CONNECTION_AUTH_MECHANISM_GSSAPI_MIC:
+ rc = ssh_userauth_gssapi (self->session);
+ break;
+ }
+
+ if (rc == SSH_AUTH_AGAIN)
+ ;
+ else if (rc == SSH_AUTH_ERROR)
+ {
+ gs_free char *msg = g_strdup_printf ("Failed to authenticate via mechanism '%s'",
+ gssh_connection_auth_mechanism_to_string
(self->current_authmech));
+ _gssh_set_error_from_libssh (error, msg, self->session);
+ return_task_error_and_clear (&self->auth_task, local_error);
+ g_clear_pointer (&self->password, g_free);
+ }
+ else if (rc == SSH_AUTH_DENIED)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+ "Authentication failed");
+ return_task_error_and_clear (&self->auth_task, local_error);
+ g_clear_pointer (&self->password, g_free);
+ }
+ else if (rc == SSH_AUTH_PARTIAL)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+ "Need to continue authentication");
+ return_task_error_and_clear (&self->auth_task, local_error);
+ }
+ else
+ {
+ g_task_return_boolean (self->auth_task, TRUE);
+ g_clear_object (&self->auth_task);
+ state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
+ goto repeat;
}
break;
}
@@ -565,6 +644,23 @@ on_socket_ready (GSocket *socket,
return TRUE;
}
+const char *
+gssh_connection_auth_mechanism_to_string (GSshConnectionAuthMechanism mech)
+{
+ switch (mech)
+ {
+ case GSSH_CONNECTION_AUTH_MECHANISM_NONE:
+ return "none";
+ case GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD:
+ return "password";
+ case GSSH_CONNECTION_AUTH_MECHANISM_PUBLICKEY:
+ return "publickey";
+ case GSSH_CONNECTION_AUTH_MECHANISM_GSSAPI_MIC:
+ return "gssapi-mic";
+ }
+ g_assert_not_reached ();
+}
+
static void
on_socket_client_connected (GObject *src,
GAsyncResult *result,
@@ -627,52 +723,91 @@ gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self)
return self->remote_hostkey_sha1;
}
+void
+gssh_connection_set_interaction (GSshConnection *self,
+ GTlsInteraction *interaction)
+{
+ g_clear_object (&self->interaction);
+ self->interaction = g_object_ref (interaction);
+}
+
+
/**
- * gssh_connection_preauth_continue:
+ * gssh_connection_negotiate_async:
* @self: Self
+ * @cancellable: Cancellable:
+ * @callback: Callback
+ * @user_data: User data
*
- * Call this function after having verified the host key fingerprint
- * to continue the connection.
+ * After a handshake is complete, the connection will be in
+ * %GSSH_CONNECTION_NEGOTIATE_ASYNC. You should then retrieve the
+ * host key with gssh_connection_preauth_get_fingerprint_sha1(),
+ * and verify it.
+ *
+ * Once that is complete, invoke this function to continue the
+ * connection process.
*/
void
-gssh_connection_preauth_continue (GSshConnection *self)
+gssh_connection_negotiate_async (GSshConnection *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
g_return_if_fail (self->state == GSSH_CONNECTION_STATE_PREAUTH);
- g_return_if_fail (!self->preauth_continue);
- self->preauth_continue = TRUE;
+ state_transition (self, GSSH_CONNECTION_STATE_NEGOTIATE_AUTH);
self->paused = FALSE;
gssh_connection_iteration_default (self);
}
-const char*const*
-gssh_connection_get_authentication_mechanisms (GSshConnection *self)
+gboolean
+gssh_connection_negotiate_finish (GSshConnection *self,
+ GAsyncResult *res,
+ GError **error)
{
- return (const char *const*)self->authschemes->pdata;
+ g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
+ return g_task_propagate_boolean (G_TASK (res), error);
}
+/**
+ * gssh_connection_get_authentication_mechanisms:
+ * @self: Self
+ * @out_authmechanisms: (out) (array len=out_len) (element-type guint): Array of #GSshConnectionAuthMechanism
+ * @out_len: (out): Length
+ *
+ * Return a list of available authentication mechanisms, in no
+ * particular order.
+ */
void
-gssh_connection_auth_password_async (GSshConnection *self,
- const char *password,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+gssh_connection_get_authentication_mechanisms (GSshConnection *self,
+ guint **out_authmechanisms,
+ guint *out_len)
{
g_return_if_fail (self->state == GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
- g_return_if_fail (self->auth_task == NULL);
- if (self->password)
- g_free (self->password);
- self->password = g_strdup (password);
+ *out_authmechanisms = (guint*)self->authmechanisms->data;
+ *out_len = self->authmechanisms->len;
+}
- self->auth_task = g_task_new (self, cancellable, callback, user_data);
+void
+gssh_connection_auth_async (GSshConnection *self,
+ GSshConnectionAuthMechanism mechanism,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (self->state == GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
+ g_return_if_fail (self->auth_task == NULL);
+ self->auth_task = g_task_new (self, cancellable, callback, user_data);
+ self->current_authmech = mechanism;
+ self->paused = FALSE;
gssh_connection_iteration_default (self);
}
gboolean
-gssh_connection_auth_password_finish (GSshConnection *self,
- GAsyncResult *result,
- GError **error)
+gssh_connection_auth_finish (GSshConnection *self,
+ GAsyncResult *result,
+ GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
@@ -876,3 +1011,4 @@ gssh_connection_new (GSocketConnectable *address,
"maincontext", g_main_context_get_thread_default (),
NULL);
}
+
diff --git a/libgssh/gssh-connection.h b/libgssh/gssh-connection.h
index e4bed0e..a1cf69e 100644
--- a/libgssh/gssh-connection.h
+++ b/libgssh/gssh-connection.h
@@ -34,16 +34,26 @@ typedef enum /*< prefix=GSSH_CONNECTION_STATE >*/
GSSH_CONNECTION_STATE_CONNECTING,
GSSH_CONNECTION_STATE_HANDSHAKING,
GSSH_CONNECTION_STATE_PREAUTH,
+ GSSH_CONNECTION_STATE_NEGOTIATE_AUTH,
GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED,
GSSH_CONNECTION_STATE_CONNECTED,
GSSH_CONNECTION_STATE_ERROR
} GSshConnectionState;
+typedef enum {
+ GSSH_CONNECTION_AUTH_MECHANISM_NONE,
+ GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD,
+ GSSH_CONNECTION_AUTH_MECHANISM_PUBLICKEY,
+ GSSH_CONNECTION_AUTH_MECHANISM_GSSAPI_MIC
+} GSshConnectionAuthMechanism;
+
GType gssh_connection_get_type (void);
GSshConnection *gssh_connection_new (GSocketConnectable *address,
const char *username);
+const char * gssh_connection_auth_mechanism_to_string (GSshConnectionAuthMechanism mech);
+
GSocketClient *gssh_connection_get_socket_client (GSshConnection *self);
GSshConnectionState gssh_connection_get_state (GSshConnection *self);
@@ -51,29 +61,41 @@ GSshConnectionState gssh_connection_get_state (GSshConnection *self);
void gssh_connection_reset (GSshConnection *self);
void gssh_connection_handshake_async (GSshConnection *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
gboolean gssh_connection_handshake_finish (GSshConnection *self,
- GAsyncResult *result,
- GError **error);
+ GAsyncResult *result,
+ GError **error);
+
+void gssh_connection_set_interaction (GSshConnection *self,
+ GTlsInteraction *interaction);
GBytes * gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self);
-void gssh_connection_preauth_continue (GSshConnection *self);
+void gssh_connection_negotiate_async (GSshConnection *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gssh_connection_negotiate_finish (GSshConnection *self,
+ GAsyncResult *res,
+ GError **error);
-const char*const* gssh_connection_get_authentication_mechanisms (GSshConnection *self);
+void gssh_connection_get_authentication_mechanisms (GSshConnection *self,
+ guint
**out_authmechanisms,
+ guint *out_len);
-void gssh_connection_auth_password_async (GSshConnection *self,
- const char *password,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
+void gssh_connection_auth_async (GSshConnection *self,
+ GSshConnectionAuthMechanism authmech,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
-gboolean gssh_connection_auth_password_finish (GSshConnection *self,
- GAsyncResult *result,
- GError **error);
+gboolean gssh_connection_auth_finish (GSshConnection *self,
+ GAsyncResult *result,
+ GError **error);
void gssh_connection_open_shell_async (GSshConnection *self,
GCancellable *cancellable,
diff --git a/src/hotssh-password-interaction.c b/src/hotssh-password-interaction.c
new file mode 100644
index 0000000..8589ac8
--- /dev/null
+++ b/src/hotssh-password-interaction.c
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "hotssh-password-interaction.h"
+
+struct _HotSshPasswordInteraction
+{
+ GTlsInteraction parent_instance;
+
+ GtkEntry *entry;
+};
+
+struct _HotSshPasswordInteractionClass
+{
+ GTlsInteractionClass parent_class;
+};
+
+#include <string.h>
+
+G_DEFINE_TYPE (HotSshPasswordInteraction, hotssh_password_interaction, G_TYPE_TLS_INTERACTION);
+
+static GTlsInteractionResult
+hotssh_password_interaction_ask_password (GTlsInteraction *interaction,
+ GTlsPassword *password,
+ GCancellable *cancellable,
+ GError **error)
+{
+ HotSshPasswordInteraction *self = (HotSshPasswordInteraction*)interaction;
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return G_TLS_INTERACTION_FAILED;
+
+ g_tls_password_set_value (password, (guint8*)gtk_entry_get_text (self->entry), -1);
+ return G_TLS_INTERACTION_HANDLED;
+}
+
+static void
+hotssh_password_interaction_init (HotSshPasswordInteraction *interaction)
+{
+}
+
+static void
+hotssh_password_interaction_class_init (HotSshPasswordInteractionClass *klass)
+{
+ GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
+ interaction_class->ask_password = hotssh_password_interaction_ask_password;
+}
+
+HotSshPasswordInteraction *
+hotssh_password_interaction_new (GtkEntry *entry)
+{
+ HotSshPasswordInteraction *self = g_object_new (HOTSSH_TYPE_PASSWORD_INTERACTION, NULL);
+ self->entry = entry;
+ return self;
+}
diff --git a/src/hotssh-password-interaction.h b/src/hotssh-password-interaction.h
new file mode 100644
index 0000000..cf72826
--- /dev/null
+++ b/src/hotssh-password-interaction.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define HOTSSH_TYPE_PASSWORD_INTERACTION (hotssh_password_interaction_get_type ())
+#define HOTSSH_PASSWORD_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o),
HOTSSH_TYPE_PASSWORD_INTERACTION, HotSshPasswordInteraction))
+#define HOTSSH_PASSWORD_INTERACTION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k),
HOTSSH_TYPE_PASSWORD_INTERACTION, HotSshPasswordInteractionClass))
+#define HOTSSH_IS_PASSWORD_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o),
HOTSSH_TYPE_PASSWORD_INTERACTION))
+#define HOTSSH_IS_PASSWORD_INTERACTION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k),
HOTSSH_TYPE_PASSWORD_INTERACTION))
+#define HOTSSH_PASSWORD_INTERACTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),
HOTSSH_TYPE_PASSWORD_INTERACTION, HotSshPasswordInteractionClass))
+
+typedef struct _HotSshPasswordInteraction HotSshPasswordInteraction;
+typedef struct _HotSshPasswordInteractionClass HotSshPasswordInteractionClass;
+
+GType hotssh_password_interaction_get_type (void) G_GNUC_CONST;
+
+HotSshPasswordInteraction * hotssh_password_interaction_new (GtkEntry *entry);
+
+G_END_DECLS
diff --git a/src/hotssh-tab.c b/src/hotssh-tab.c
index 3405b6b..1fc630f 100644
--- a/src/hotssh-tab.c
+++ b/src/hotssh-tab.c
@@ -19,6 +19,7 @@
*/
#include "hotssh-tab.h"
+#include "hotssh-password-interaction.h"
#include "gssh.h"
#include "libgsystem.h"
@@ -27,6 +28,12 @@
#include <stdio.h>
#include <string.h>
+static const GSshConnectionAuthMechanism default_authentication_order[] = {
+ GSSH_CONNECTION_AUTH_MECHANISM_PUBLICKEY,
+ /* GSSH_CONNECTION_AUTH_MECHANISM_GSSAPI_MIC, Seems broken */
+ GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD
+};
+
struct _HotSshTab
{
GtkNotebook parent;
@@ -50,6 +57,7 @@ struct _HotSshTabPrivate
{
GSettings *settings;
GtkWidget *terminal;
+ HotSshPasswordInteraction *password_interaction;
/* Bound via template */
GtkWidget *host_entry;
@@ -69,6 +77,7 @@ struct _HotSshTabPrivate
/* State */
HotSshTabPage active_page;
+ guint authmechanism_index;
GSocketConnectable *address;
GSshConnection *connection;
@@ -76,6 +85,8 @@ struct _HotSshTabPrivate
gboolean need_pty_size_request;
gboolean sent_pty_size_request;
+ gboolean awaiting_password_entry;
+ gboolean submitted_password;
gboolean have_outstanding_write;
gboolean have_outstanding_auth;
GQueue write_queue;
@@ -118,6 +129,7 @@ state_reset_for_new_connection (HotSshTab *self)
gtk_widget_show (priv->connection_text_container);
gtk_widget_hide (priv->hostkey_container);
gtk_widget_set_sensitive (priv->password_container, TRUE);
+ priv->awaiting_password_entry = priv->submitted_password = FALSE;
g_debug ("reset state done");
}
@@ -148,6 +160,7 @@ static void
page_transition_take_error (HotSshTab *self,
GError *error)
{
+ g_debug ("Caught error: %s", error->message);
set_status (self, error->message);
g_error_free (error);
}
@@ -231,9 +244,11 @@ on_connection_state_notify (GSshConnection *conn,
break;
case GSSH_CONNECTION_STATE_CONNECTING:
case GSSH_CONNECTION_STATE_HANDSHAKING:
- case GSSH_CONNECTION_STATE_PREAUTH:
page_transition (self, HOTSSH_TAB_PAGE_INTERSTITAL);
break;
+ case GSSH_CONNECTION_STATE_PREAUTH:
+ case GSSH_CONNECTION_STATE_NEGOTIATE_AUTH:
+ break;
case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
page_transition (self, HOTSSH_TAB_PAGE_AUTH);
iterate_authentication_modes (self);
@@ -251,9 +266,9 @@ on_connection_state_notify (GSshConnection *conn,
}
static void
-on_password_auth_complete (GObject *src,
- GAsyncResult *res,
- gpointer user_data)
+on_auth_complete (GObject *src,
+ GAsyncResult *res,
+ gpointer user_data)
{
HotSshTab *self = HOTSSH_TAB (user_data);
HotSshTabPrivate *priv = hotssh_tab_get_instance_private (self);
@@ -261,48 +276,100 @@ on_password_auth_complete (GObject *src,
priv->have_outstanding_auth = FALSE;
- if (!gssh_connection_auth_password_finish ((GSshConnection*)src, res, &local_error))
+ if (!gssh_connection_auth_finish ((GSshConnection*)src, res, &local_error))
goto out;
- g_debug ("password auth complete");
+ g_debug ("auth complete");
out:
if (local_error)
- page_transition_take_error (self, local_error);
+ {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
+ {
+ g_debug ("Authentication mechanism '%s' denied",
+ gssh_connection_auth_mechanism_to_string
(default_authentication_order[priv->authmechanism_index]));
+ g_clear_error (&local_error);
+ priv->authmechanism_index++;
+ iterate_authentication_modes (self);
+ }
+ else
+ page_transition_take_error (self, local_error);
+ }
+}
+
+static gboolean
+have_mechanism (guint *available,
+ guint n_available,
+ GSshConnectionAuthMechanism mech)
+{
+ guint i;
+ for (i = 0; i < n_available; i++)
+ if (available[i] == mech)
+ return TRUE;
+ return FALSE;
}
static void
iterate_authentication_modes (HotSshTab *self)
{
HotSshTabPrivate *priv = hotssh_tab_get_instance_private (self);
- const char *const *authschemes =
- gssh_connection_get_authentication_mechanisms (priv->connection);
- const char *const*iter;
+ guint n_mechanisms;
+ guint *available_authmechanisms;
GError *local_error = NULL;
+ gssh_connection_get_authentication_mechanisms (priv->connection,
+ &available_authmechanisms,
+ &n_mechanisms);
+
if (priv->have_outstanding_auth)
return;
- for (iter = authschemes; iter && *iter; iter++)
+ if (priv->awaiting_password_entry &&
+ !priv->submitted_password)
+ return;
+
+ while (priv->authmechanism_index < G_N_ELEMENTS (default_authentication_order) &&
+ !have_mechanism (available_authmechanisms, n_mechanisms,
+ default_authentication_order[priv->authmechanism_index]))
{
- const char *authscheme = *iter;
- if (strcmp (authscheme, "password") == 0)
- {
- const char *password = gtk_entry_get_text ((GtkEntry*)priv->password_entry);
- if (password && password[0])
- {
- gssh_connection_auth_password_async (priv->connection, password,
- priv->cancellable,
- on_password_auth_complete, self);
- priv->have_outstanding_auth = TRUE;
- break;
- }
- }
+ priv->authmechanism_index++;
}
- g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No more authentication mechanisms available");
- page_transition_take_error (self, local_error);
+ if (priv->authmechanism_index >= G_N_ELEMENTS (default_authentication_order))
+ {
+ g_set_error (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No more authentication mechanisms available");
+ goto out;
+ }
+ else
+ {
+ GSshConnectionAuthMechanism mech =
+ default_authentication_order[priv->authmechanism_index];
+ gboolean is_password = mech == GSSH_CONNECTION_AUTH_MECHANISM_PASSWORD;
+ gs_free char *authmsg =
+ g_strdup_printf ("Requesting authentication via '%s'",
+ gssh_connection_auth_mechanism_to_string (mech));
+ /* Ugly gross hack until we have separate auth pages */
+ set_status (self, authmsg);
+ gtk_widget_set_sensitive (priv->password_container,
+ is_password);
+ if (is_password)
+ {
+ priv->awaiting_password_entry = TRUE;
+ if (!priv->submitted_password)
+ return;
+ }
+
+ gssh_connection_auth_async (priv->connection,
+ mech,
+ priv->cancellable,
+ on_auth_complete, self);
+ priv->have_outstanding_auth = TRUE;
+ }
+
+ out:
+ if (local_error)
+ page_transition_take_error (self, local_error);
}
static void
@@ -377,6 +444,7 @@ on_connect (GtkButton *button,
g_clear_object (&priv->connection);
priv->connection = gssh_connection_new (priv->address, username);
+ gssh_connection_set_interaction (priv->connection, (GTlsInteraction*)priv->password_interaction);
g_signal_connect_object (priv->connection, "notify::state",
G_CALLBACK (on_connection_state_notify),
self, 0);
@@ -464,6 +532,7 @@ submit_password (HotSshTab *self)
gtk_widget_set_sensitive (priv->password_container, FALSE);
+ priv->submitted_password = TRUE;
iterate_authentication_modes (self);
}
@@ -476,13 +545,34 @@ on_connect_cancel (GtkButton *button,
}
static void
+on_negotiate_complete (GObject *src,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ HotSshTab *self = user_data;
+ GError *local_error = NULL;
+
+ if (!gssh_connection_negotiate_finish ((GSshConnection*)src, result, &local_error))
+ goto out;
+
+ iterate_authentication_modes (self);
+
+ out:
+ if (local_error)
+ page_transition_take_error (self, local_error);
+}
+
+static void
on_approve_hostkey_clicked (GtkButton *button,
gpointer user_data)
{
HotSshTab *self = user_data;
HotSshTabPrivate *priv = hotssh_tab_get_instance_private (self);
- gssh_connection_preauth_continue (priv->connection);
+ gtk_widget_set_sensitive ((GtkWidget*)button, FALSE);
+
+ gssh_connection_negotiate_async (priv->connection, priv->cancellable,
+ on_negotiate_complete, self);
}
static void
@@ -580,6 +670,8 @@ hotssh_tab_init (HotSshTab *self)
g_signal_connect_swapped (priv->password_entry, "activate", G_CALLBACK (submit_password), self);
g_signal_connect_swapped (priv->password_submit, "clicked", G_CALLBACK (submit_password), self);
+ priv->password_interaction = hotssh_password_interaction_new ((GtkEntry*)priv->password_entry);
+
priv->terminal = vte_terminal_new ();
hotssh_tab_style_updated ((GtkWidget *) self);
vte_terminal_set_audible_bell ((VteTerminal*)priv->terminal, FALSE); /* Audible bell is a terrible idea */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]