[hotssh] Support public key authentication



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]