[hotssh/wip/libssh] Port to libssh



commit 302a6e02a2410fa453c786715c53c0dfb25cf2d9
Author: Colin Walters <walters verbum org>
Date:   Sat Oct 19 10:52:39 2013 -0400

    Port to libssh
    
    It handles GSSAPI and is already used by KDE, so it's a better choice
    for us.

 configure.ac                         |    2 +-
 libgssh/gssh-channel-input-stream.c  |   10 +-
 libgssh/gssh-channel-input-stream.h  |    2 +-
 libgssh/gssh-channel-output-stream.c |   31 ++---
 libgssh/gssh-channel-output-stream.h |    2 +-
 libgssh/gssh-channel-private.h       |    4 +-
 libgssh/gssh-channel.c               |   12 +-
 libgssh/gssh-connection-private.h    |   16 +-
 libgssh/gssh-connection.c            |  287 ++++++++++++++++++++++------------
 libgssh/libssh-porting.txt           |  112 +++++++++++++
 src/hotssh-app.c                     |    4 -
 11 files changed, 337 insertions(+), 145 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index ca77593..51be55f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -34,7 +34,7 @@ GLIB_GSETTINGS
 
 PKG_CHECK_MODULES(BUILDDEP_GIO_UNIX, [gio-unix-2.0 >= 2.34])
 PKG_CHECK_MODULES(BUILDDEP_HOTSSHAPP, [gio-unix-2.0 >= 2.34 gtk+-3.0 >= 3.10.0 vte-2.90])
-PKG_CHECK_MODULES(BUILDDEP_LIBGSSH, [gio-unix-2.0 libssh2])
+PKG_CHECK_MODULES(BUILDDEP_LIBGSSH, [gio-unix-2.0 libssh])
 
 AC_CONFIG_FILES([
 Makefile
diff --git a/libgssh/gssh-channel-input-stream.c b/libgssh/gssh-channel-input-stream.c
index e392d06..edf116d 100644
--- a/libgssh/gssh-channel-input-stream.c
+++ b/libgssh/gssh-channel-input-stream.c
@@ -194,9 +194,9 @@ _gssh_channel_input_stream_iteration (GSshChannelInputStream     *self)
     return;
 
   g_assert (!self->is_closed);
-  rc = libssh2_channel_read (self->channel->libsshchannel,
-                             self->buf, self->count);
-  if (rc == LIBSSH2_ERROR_EAGAIN)
+  rc = ssh_channel_read_nonblocking (self->channel->libsshchannel,
+                                     self->buf, self->count, 0);
+  if (rc == 0)
     return;
 
   /* This special dance is required because we may have reentered via
@@ -209,8 +209,8 @@ _gssh_channel_input_stream_iteration (GSshChannelInputStream     *self)
     }
   else
     {
-      _gssh_set_error_from_libssh2 (&local_error, "Failed to read",
-                                      self->channel->connection->session);
+      _gssh_set_error_from_libssh (&local_error, "Failed to read",
+                                   self->channel->connection->session);
       g_task_return_error (prev_task, local_error);
     }
   g_object_unref (prev_task);
diff --git a/libgssh/gssh-channel-input-stream.h b/libgssh/gssh-channel-input-stream.h
index a6618d6..0256699 100644
--- a/libgssh/gssh-channel-input-stream.h
+++ b/libgssh/gssh-channel-input-stream.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "gssh-channel.h"
-#include <libssh2.h>
+#include <libssh/libssh.h>
 
 #define GSSH_TYPE_CHANNEL_INPUT_STREAM (_gssh_channel_input_stream_get_type ())
 #define GSSH_CHANNEL_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSSH_TYPE_CHANNEL_INPUT_STREAM, 
GSshChannelInputStream))
diff --git a/libgssh/gssh-channel-output-stream.c b/libgssh/gssh-channel-output-stream.c
index 2d5d02d..4beea8d 100644
--- a/libgssh/gssh-channel-output-stream.c
+++ b/libgssh/gssh-channel-output-stream.c
@@ -239,9 +239,9 @@ _gssh_channel_output_stream_iteration (GSshChannelOutputStream     *self)
           return;
 
         data = g_bytes_get_data (self->buf, &bufsize);
-        rc = libssh2_channel_write (self->channel->libsshchannel,
-                                    (const char*)data, bufsize);
-        if (rc == LIBSSH2_ERROR_EAGAIN)
+        rc = ssh_channel_write (self->channel->libsshchannel,
+                                (const char*)data, bufsize);
+        if (rc == 0)
           break;
 
         /* This special dance is required because we may have
@@ -254,8 +254,8 @@ _gssh_channel_output_stream_iteration (GSshChannelOutputStream     *self)
           }
         else
           {
-            _gssh_set_error_from_libssh2 (&local_error, "Failed to write",
-                                            self->channel->connection->session);
+            _gssh_set_error_from_libssh (&local_error, "Failed to write",
+                                         self->channel->connection->session);
             g_task_return_error (prev_write_task, local_error);
           }
         g_object_unref (prev_write_task);
@@ -263,17 +263,17 @@ _gssh_channel_output_stream_iteration (GSshChannelOutputStream     *self)
       break;
     case GSSH_CHANNEL_OUTPUT_STREAM_STATE_REQUESTED_EOF:
       {
-        rc = libssh2_channel_send_eof (self->channel->libsshchannel);
-        if (rc == LIBSSH2_ERROR_EAGAIN)
+        rc = ssh_channel_send_eof (self->channel->libsshchannel);
+        if (rc == SSH_AGAIN)
           break;
-        else if (rc == 0)
+        else if (rc == SSH_OK)
           {
             self->state = GSSH_CHANNEL_OUTPUT_STREAM_STATE_SENT_EOF;
           }
         else
           {
-            _gssh_set_error_from_libssh2 (&local_error, "Failed to close",
-                                            self->channel->connection->session);
+            _gssh_set_error_from_libssh (&local_error, "Failed to close",
+                                         self->channel->connection->session);
             g_task_return_error (self->close_task, local_error);
             g_clear_object (&self->close_task);
             break;
@@ -281,7 +281,7 @@ _gssh_channel_output_stream_iteration (GSshChannelOutputStream     *self)
       }
       /* Fall though */
     case GSSH_CHANNEL_OUTPUT_STREAM_STATE_SENT_EOF:
-      rc = libssh2_channel_eof (self->channel->libsshchannel);
+      rc = ssh_channel_is_eof (self->channel->libsshchannel);
       if (rc == 1)
         {
           g_task_return_boolean (self->close_task, TRUE);
@@ -293,14 +293,7 @@ _gssh_channel_output_stream_iteration (GSshChannelOutputStream     *self)
           break;
         }
       else
-        {
-          g_assert (rc < 0);
-          _gssh_set_error_from_libssh2 (&local_error, "Failed await closed state",
-                                          self->channel->connection->session);
-          g_task_return_error (self->close_task, local_error);
-          g_clear_object (&self->close_task);
-          break;
-        }
+        g_assert_not_reached ();
       /* Fall through */
     case GSSH_CHANNEL_OUTPUT_STREAM_STATE_RECEIVED_EOF:
       /* Nothing */
diff --git a/libgssh/gssh-channel-output-stream.h b/libgssh/gssh-channel-output-stream.h
index eafd6fe..035a2b3 100644
--- a/libgssh/gssh-channel-output-stream.h
+++ b/libgssh/gssh-channel-output-stream.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "gssh-channel.h"
-#include <libssh2.h>
+#include <libssh/libssh.h>
 
 #define GSSH_TYPE_CHANNEL_OUTPUT_STREAM (_gssh_channel_output_stream_get_type ())
 #define GSSH_CHANNEL_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSSH_TYPE_CHANNEL_OUTPUT_STREAM, 
GSshChannelOutputStream))
diff --git a/libgssh/gssh-channel-private.h b/libgssh/gssh-channel-private.h
index 2b861c1..eae82f0 100644
--- a/libgssh/gssh-channel-private.h
+++ b/libgssh/gssh-channel-private.h
@@ -31,7 +31,7 @@ struct _GSshChannel
 
   GSshConnection *connection;
   gboolean have_pty;
-  LIBSSH2_CHANNEL *libsshchannel;
+  ssh_channel libsshchannel;
 
   GTask *pty_size_task;
   guint pty_width;
@@ -48,7 +48,7 @@ struct _GSshChannelClass
 
 GSshChannel       *_gssh_channel_new (GSshConnection  *connection,
                                       gboolean         have_pty,
-                                      LIBSSH2_CHANNEL *libsshchannel);
+                                      ssh_channel      libsshchannel);
 
 
 void _gssh_channel_iteration (GSshChannel    *self);
diff --git a/libgssh/gssh-channel.c b/libgssh/gssh-channel.c
index f2c92ae..caf695b 100644
--- a/libgssh/gssh-channel.c
+++ b/libgssh/gssh-channel.c
@@ -95,17 +95,17 @@ _gssh_channel_iteration (GSshChannel              *self)
   if (self->pty_size_task)
     {
       GTask *orig_pty_size_task = self->pty_size_task;
-      rc = libssh2_channel_request_pty_size (self->libsshchannel,
-                                             self->pty_width, self->pty_height);
-      if (rc == LIBSSH2_ERROR_EAGAIN)
+      rc = ssh_channel_request_pty_size (self->libsshchannel, "xterm",
+                                         self->pty_width, self->pty_height);
+      if (rc == SSH_AGAIN)
         ;
       else 
         {
           self->pty_size_task = NULL;
           if (rc < 0)
             {
-              _gssh_set_error_from_libssh2 (&local_error, "Failed to set pty size",
-                                            self->connection->session);
+              _gssh_set_error_from_libssh (&local_error, "Failed to set pty size",
+                                           self->connection->session);
               g_task_return_error (orig_pty_size_task, local_error);
             }
           else
@@ -175,7 +175,7 @@ gssh_channel_class_init (GSshChannelClass *class)
 GSshChannel *
 _gssh_channel_new (GSshConnection   *connection,
                    gboolean         have_pty,
-                   LIBSSH2_CHANNEL *libsshchannel)
+                   ssh_channel      libsshchannel)
 {
   GSshChannel *self = (GSshChannel*)g_object_new (GSSH_TYPE_CHANNEL, NULL);
   /* We don't hold a ref; if the connection goes away, it will ensure
diff --git a/libgssh/gssh-connection-private.h b/libgssh/gssh-connection-private.h
index 0e7bf22..3c750e1 100644
--- a/libgssh/gssh-connection-private.h
+++ b/libgssh/gssh-connection-private.h
@@ -21,7 +21,7 @@
 #pragma once
 
 #include "gssh-connection.h"
-#include <libssh2.h>
+#include <libssh/libssh.h>
 
 struct _GSshConnection
 {
@@ -29,16 +29,18 @@ struct _GSshConnection
 
   GSshConnectionState state;
 
+  guint paused : 1;
   guint select_inbound : 1;
   guint select_outbound : 1;
+  guint tried_userauth_none : 1;
   guint preauth_continue : 1;
-  guint unused : 29;
+  guint unused : 27;
 
   char *username;
 
-  char **authschemes;
+  GPtrArray *authschemes;
 
-  LIBSSH2_SESSION *session;
+  ssh_session session;
   GHashTable *channels;
 
   GError *cached_error;
@@ -66,6 +68,6 @@ struct _GSshConnectionClass
 typedef struct _GSshConnectionPrivate GSshConnectionPrivate;
 
 void
-_gssh_set_error_from_libssh2 (GError         **error,
-                                const char      *prefix,
-                                LIBSSH2_SESSION *session);
+_gssh_set_error_from_libssh (GError         **error,
+                             const char      *prefix,
+                             ssh_session      session);
diff --git a/libgssh/gssh-connection.c b/libgssh/gssh-connection.c
index 9f90fbf..0a01db4 100644
--- a/libgssh/gssh-connection.c
+++ b/libgssh/gssh-connection.c
@@ -43,18 +43,17 @@ typedef enum {
 typedef struct {
   char *exec_command; /* If NULL, then shell */
   GSshConnectionChannelCreationState state;
-  LIBSSH2_CHANNEL *libssh2channel;
+  ssh_channel libsshchannel;
 } GSshConnectionChannelCreationData;
 
 G_DEFINE_TYPE(GSshConnection, gssh_connection, G_TYPE_OBJECT);
 
 void
-_gssh_set_error_from_libssh2 (GError         **error,
-                                const char      *prefix,
-                                LIBSSH2_SESSION *session)
+_gssh_set_error_from_libssh (GError         **error,
+                             const char      *prefix,
+                             ssh_session      session)
 {
-  char *errmsg = NULL;
-  libssh2_session_last_error (session, &errmsg, NULL, FALSE);
+  const char *errmsg = ssh_get_error (session);
   g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s: %s", prefix, errmsg);
 }
 
@@ -91,7 +90,7 @@ 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_strfreev);
+  g_clear_pointer (&self->authschemes, g_ptr_array_unref);
 }
 
 static void
@@ -155,11 +154,14 @@ on_socket_ready (GSocket *socket,
 static void
 recalculate_socket_state (GSshConnection   *self)
 {
-  int directions = libssh2_session_block_directions (self->session);
-  guint block_inbound = (directions & LIBSSH2_SESSION_BLOCK_INBOUND) ? 1 : 0;
-  guint block_outbound = (directions & LIBSSH2_SESSION_BLOCK_OUTBOUND) ? 1 : 0;
+  int status = ssh_get_poll_flags (self->session);
+  guint block_inbound = (status & SSH_READ_PENDING) > 0;
+  guint block_outbound = (status & SSH_WRITE_PENDING) > 0;
   GIOCondition conditions = G_IO_HUP | G_IO_ERR;
 
+  if (self->paused)
+    block_inbound = block_outbound = 0;
+
   if (block_inbound == self->select_inbound &&
       block_outbound == self->select_outbound &&
       self->socket_source != NULL)
@@ -170,7 +172,7 @@ recalculate_socket_state (GSshConnection   *self)
     conditions |= G_IO_IN;
   self->select_outbound = block_outbound;
   if (self->select_outbound)
-    conditions |= G_IO_IN;
+    conditions |= G_IO_OUT;
   
   if (self->socket_source)
     { 
@@ -242,80 +244,126 @@ process_channels (GSshConnection   *self,
         {
         case GSSH_CONNECTION_CHANNEL_CREATION_STATE_OPEN_SESSION:
           {
-            data->libssh2channel = libssh2_channel_open_session (self->session);
-            if (data->libssh2channel == NULL)
+            if (!data->libsshchannel)
+              data->libsshchannel = ssh_channel_new (self->session);
+            g_assert (data->libsshchannel);  /* Should only fail on OOM */
+            rc = ssh_channel_open_session (data->libsshchannel);
+            if (rc == SSH_OK)
+              ;
+            else if (rc == SSH_AGAIN)
               {
-                if (libssh2_session_last_error (self->session, NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN)
-                  break;
-                else
-                  {
-                    _gssh_set_error_from_libssh2 (&local_error, "Failed to open session: ", self->session);
-                    g_task_return_error (task, local_error);
-                    g_hash_table_iter_remove (&hiter);
-                    break;
-                  }
+                break;
+              }
+            else
+              {
+                _gssh_set_error_from_libssh (&local_error, "Failed to open session: ", self->session);
+                g_task_return_error (task, local_error);
+                g_hash_table_iter_remove (&hiter);
+                break;
               }
             data->state = GSSH_CONNECTION_CHANNEL_CREATION_STATE_REQUEST_PTY;
             /* Fall through */
           }
         case GSSH_CONNECTION_CHANNEL_CREATION_STATE_REQUEST_PTY:
           {
-            rc = libssh2_channel_request_pty (data->libssh2channel, "xterm");
-            if (rc == LIBSSH2_ERROR_EAGAIN)
+            rc = ssh_channel_request_pty (data->libsshchannel);
+            if (rc == SSH_OK)
+              {
+                data->state = GSSH_CONNECTION_CHANNEL_CREATION_STATE_EXEC;
+              }
+            else if (rc == SSH_AGAIN)
               break;
-            else if (rc < 0)
+            else
               {
-                _gssh_set_error_from_libssh2 (&local_error, "Failed to open session: ", self->session);
+                _gssh_set_error_from_libssh (&local_error, "Failed to open session: ", self->session);
                 g_task_return_error (task, local_error);
                 g_hash_table_iter_remove (&hiter);
                 break;
               }
-            else
-              {
-                g_assert (rc == 0);
-                data->state = GSSH_CONNECTION_CHANNEL_CREATION_STATE_EXEC;
-              }
             /* Fall through */
           } 
         case GSSH_CONNECTION_CHANNEL_CREATION_STATE_EXEC:
           {
             if (data->exec_command == NULL)
-              rc = libssh2_channel_shell (data->libssh2channel);
-            else
-              rc = libssh2_channel_process_startup (data->libssh2channel,
-                                                    "exec", 4,
-                                                    data->exec_command,
-                                                    strlen (data->exec_command));
-            if (rc == LIBSSH2_ERROR_EAGAIN)
-              break;
-            else if (rc < 0)
-              {
-                _gssh_set_error_from_libssh2 (&local_error, "Failed to exec: ", self->session);
-                g_task_return_error (task, local_error);
-                g_hash_table_iter_remove (&hiter);
-                break;
-              }
+              rc = ssh_channel_request_shell (data->libsshchannel);
             else
+              rc = ssh_channel_request_exec (data->libsshchannel,
+                                             data->exec_command);
+            if (rc == SSH_OK)
               {
                 GSshChannel *new_channel; 
 
                 g_assert (rc == 0);
 
-                new_channel = _gssh_channel_new (self, TRUE, data->libssh2channel);
+                new_channel = _gssh_channel_new (self, TRUE, data->libsshchannel);
                 g_hash_table_insert (self->channels, new_channel, new_channel);
 
                 g_task_return_pointer (task, new_channel, g_object_unref);
                 g_hash_table_iter_remove (&hiter);
                 break;
               }
+            else if (rc == SSH_AGAIN)
+              break;
+            else
+              {
+                _gssh_set_error_from_libssh (&local_error, "Failed to exec: ", self->session);
+                g_task_return_error (task, local_error);
+                g_hash_table_iter_remove (&hiter);
+                break;
+              }
           }
         }
     }
 }
 
+static gboolean
+set_hostkey_sha1 (GSshConnection           *self,
+                  GError                  **error)
+{
+  gboolean ret = FALSE;
+  int rc;
+  ssh_key key = NULL;
+  char *key_b64;
+  guint8 *key_raw;
+  gsize key_len;
+  guint8 sha1buf[20];
+  gsize sha1len = sizeof (sha1buf);
+  GChecksum *csum;
+
+  rc = ssh_get_publickey (self->session, &key);
+  if (rc != SSH_OK)
+    {
+      _gssh_set_error_from_libssh (error, "Failed to get public key", self->session);
+      goto out;
+    }
+
+  rc = ssh_pki_export_pubkey_base64 (key, &key_b64);
+  if (rc != SSH_OK)
+    {
+      _gssh_set_error_from_libssh (error, "Failed to export public key", self->session);
+      goto out;
+    }
+
+  key_raw = g_base64_decode (key_b64, &key_len);
+  g_assert (key_raw);
+
+  csum = g_checksum_new (G_CHECKSUM_SHA1);
+  g_checksum_update (csum, key_raw, key_len);
+  g_checksum_get_digest (csum, sha1buf, &sha1len);
+  g_assert (sha1len == sizeof (sha1buf));
+
+  self->remote_hostkey_sha1 = g_bytes_new (sha1buf, sha1len);
+
+  ret = TRUE;
+ out:
+  if (key)
+    ssh_key_free (key);
+  return ret;
+}
+
 static void
-gssh_connection_iteration (GSshConnection   *self,
-                             GIOCondition        condition)
+gssh_connection_iteration_internal (GSshConnection   *self,
+                                    GIOCondition        condition)
 {
   GError *local_error = NULL;
   GError **error = &local_error;
@@ -333,96 +381,127 @@ gssh_connection_iteration (GSshConnection   *self,
       break;
     case GSSH_CONNECTION_STATE_HANDSHAKING:
       {
-       int socket_fd = g_socket_get_fd (self->socket);
-        if ((rc = libssh2_session_handshake (self->session, socket_fd)) == LIBSSH2_ERROR_EAGAIN)
+        rc = ssh_connect (self->session);
+        if (rc == SSH_OK)
+          {
+            if (!set_hostkey_sha1 (self, error))
+              {
+                g_task_return_error (self->handshake_task, local_error);
+                g_clear_object (&self->handshake_task);
+                return;
+              }
+          }
+        else if (rc == SSH_AGAIN)
           {
-            recalculate_socket_state (self);
             return;
           }
-        if (rc)
+        else
           {
-            _gssh_set_error_from_libssh2 (error, "Failed to handshake SSH2 session", self->session);
+            _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;
           }
-        self->remote_hostkey_sha1 = g_bytes_new (libssh2_hostkey_hash (self->session, 
LIBSSH2_HOSTKEY_HASH_SHA1), 20);
+        
         g_task_return_boolean (self->handshake_task, TRUE);
         g_clear_object (&self->handshake_task);
+
         state_transition (self, GSSH_CONNECTION_STATE_PREAUTH);
-        goto repeat;
+        /* Fall through */
       }
     case GSSH_CONNECTION_STATE_PREAUTH:
       {
-        const char *authschemes;
-
-        if (!self->preauth_continue)
-          break;
-
-        authschemes = libssh2_userauth_list (self->session,
-                                             self->username,
-                                             strlen (self->username));
-        if (authschemes == NULL)
+        int method;
+        if (!self->tried_userauth_none)
           {
-            if (libssh2_userauth_authenticated (self->session))
+            /* 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)
               {
-                /* Unusual according to the docs, but it's
-                 * possible for the remote server to be
-                 * configured without auth.
-                 */
                 state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
                 goto repeat;
               }
-            else if (libssh2_session_last_error (self->session, NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN)
+            else if (rc == SSH_AUTH_ERROR)
               {
-                recalculate_socket_state (self);
+                _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
               {
-                _gssh_set_error_from_libssh2 (error, "Failed to list auth mechanisms", self->session);
-                state_transition_take_error (self, local_error);
-                return;
+                g_assert (rc == SSH_AUTH_DENIED);
+                self->tried_userauth_none = TRUE;
               }
           }
-        self->authschemes = g_strsplit (authschemes, ",", 0);
+
+        g_clear_pointer (&self->authschemes, g_ptr_array_unref);
+        self->authschemes = g_ptr_array_new ();
+        method = ssh_userauth_list (self->session, NULL);
+
+        if (method & SSH_AUTH_METHOD_PASSWORD)
+          g_ptr_array_add (self->authschemes, "password");
+        if (method & SSH_AUTH_METHOD_GSSAPI_MIC)
+          g_ptr_array_add (self->authschemes, "gssapi-mic");
+        if (method & SSH_AUTH_METHOD_PUBLICKEY)
+          g_ptr_array_add (self->authschemes, "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);
+
+        if (!self->preauth_continue)
+          {
+            self->paused = TRUE;
+            break;
+          }
+
         state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
-        goto repeat;
+        /* Fall through */
       }
-      break;
     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().
          */
         if (self->auth_task != NULL)
           {
-            if (self->password)
+            if ((method & SSH_AUTH_METHOD_PASSWORD) && self->password)
               {
-                rc = libssh2_userauth_password (self->session, self->username, self->password);
-                if (rc == LIBSSH2_ERROR_EAGAIN)
+                rc = ssh_userauth_password (self->session, NULL, self->password);
+                if (rc == SSH_AUTH_AGAIN)
                   {
-                    recalculate_socket_state (self);
+                    return;
                   }
-                else if (rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED)
+                else if (rc == SSH_AUTH_ERROR)
                   {
-                    g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
-                                         "Authentication failed");
+                    _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 == LIBSSH2_ERROR_PASSWORD_EXPIRED)
+                else if (rc == SSH_AUTH_DENIED)
                   {
                     g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
-                                         "Password expired");
+                                         "Authentication failed");
                     g_task_return_error (self->auth_task, local_error);
-                    g_clear_object (&self->auth_task);
                   }
-                else if (rc)
+                else if (rc == SSH_AUTH_PARTIAL)
                   {
-                    _gssh_set_error_from_libssh2 (error, "Failed to password auth", self->session);
+                    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);
-                    g_clear_object (&self->auth_task);
                   }
                 else
                   {
@@ -448,6 +527,14 @@ gssh_connection_iteration (GSshConnection   *self,
 }
 
 static void
+gssh_connection_iteration (GSshConnection   *self,
+                           GIOCondition        condition)
+{
+  gssh_connection_iteration_internal (self, condition);
+  recalculate_socket_state (self);
+}
+
+static void
 gssh_connection_iteration_default (GSshConnection   *self)
 {
   /* This is a bit of a hack, but eh... we'll just get EAGAIN */
@@ -485,7 +572,7 @@ on_socket_client_connected (GObject         *src,
   GSshConnection *self = user_data;
   GError *local_error = NULL;
   GError **error = &local_error;
-  gs_free char *version_str = NULL;
+  int fd;
 
   g_assert (src == (GObject*)self->socket_client);
 
@@ -496,22 +583,23 @@ on_socket_client_connected (GObject         *src,
 
   self->socket = g_socket_connection_get_socket (self->socketconn);
 
-  self->session = libssh2_session_init ();
+  self->session = ssh_new ();
+  ssh_set_log_level (SSH_LOG_FUNCTIONS);
   if (!self->session)
     {
       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                            "Failed to initialize SSH2 session");
       goto out;
     }
+  
+  fd = g_socket_get_fd (self->socket);
 
-  libssh2_session_set_blocking (self->session, 0);
-  version_str = g_strdup_printf ("SSH-2.0-libgssh_%s_libssh2_%s",
-                                 PACKAGE_VERSION, libssh2_version (0));
-  libssh2_session_banner_set (self->session, version_str);
+  ssh_set_blocking (self->session, 0);
+  ssh_options_set (self->session, SSH_OPTIONS_FD, &fd);
+  ssh_options_set (self->session, SSH_OPTIONS_USER, self->username);
 
   state_transition (self, GSSH_CONNECTION_STATE_HANDSHAKING);
 
-  recalculate_socket_state (self);
   gssh_connection_iteration_default (self);
 
   return;
@@ -551,13 +639,14 @@ gssh_connection_preauth_continue (GSshConnection *self)
   g_return_if_fail (self->state == GSSH_CONNECTION_STATE_PREAUTH);
   g_return_if_fail (!self->preauth_continue);
   self->preauth_continue = TRUE;
+  self->paused = FALSE;
   gssh_connection_iteration_default (self);
 }
 
 const char*const*
 gssh_connection_get_authentication_mechanisms (GSshConnection   *self)
 {
-  return (const char *const*)self->authschemes;
+  return (const char *const*)self->authschemes->pdata;
 }
 
 void
diff --git a/libgssh/libssh-porting.txt b/libgssh/libssh-porting.txt
new file mode 100644
index 0000000..7b6816b
--- /dev/null
+++ b/libgssh/libssh-porting.txt
@@ -0,0 +1,112 @@
+<sh4rm4> glib integration ?
+<walters> sh4rm4: using libssh with the GLib mainloop
+<sh4rm4> ah
+<sh4rm4> which kind of complications could arise there ?
+<sh4rm4> walters, ?
+<walters> sh4rm4: well, i'm not sure how to do it at the moment
+<sh4rm4> what makes a glib main loop so special ?
+<walters> does your application use libssh asynchronously?
+<walters> or let me rephrase - do you have any applications which need to watch for libssh events *and* 
other events, such as an X11 socket?
+<walters> in the same process
+<sh4rm4> no, it's in blocking mode
+<walters> right
+<gladiac> walters: ssh_get_status()
+<gladiac> well, it is likely we need something better
+<ar1s> nope, ssh_get_status isn't for this
+<ar1s> ssh_get_fd will get the fd from the session. Put it into a handler in your mainloop
+<ar1s> when something happens on the socket, call ...
+<walters> i'm already doing the socket connection outside of libssh, using 
https://developer.gnome.org/gio/unstable/GSocketClient.html then taking the fd out
+<ar1s> ... hmm I'd say ssh_handle_packets but that function is not public
+<walters> ar1s: but how do i know what directions to select for (POLLIN/POLLOUT)?  libssh2 has 
http://www.libssh2.org/libssh2_session_block_directions.html
+<ar1s> create an ssh_poll_ctx and add the session into it. When glib gives you an event, call 
ssh_poll_ctx_dopoll
+<ar1s> walters: that's a weak spot of libssh, I tried to make it integrable into other main loops but never 
actually did it
+<walters> i think what i really want is to add callbacks to libssh that allow my app to take over the actual 
reading/writing to the socket
+<walters> and for it to have an API like _get_block_directions()
+<ar1s> ok that's maybe what the ssh_get_status we discussed with gladiac last week was from
+<ar1s> walters: I think that's not too hard
+<gladiac> ar1s: yeah, I think so too, just a bad name for the function :)
+<ar1s> in fact we would need two getters that get the value in the ssh_pollfd struct itself
+<ar1s> walters: do you have an example of integration glib/any other network lib like libssh that is 
considered best practice ?
+<walters> libdbus was explicitly designed to only be integrated into foreign mainloops
+<walters> it has a synchronous API but you really don't want to use it that way
+<walters> since the whole idea is your app is doing something else *besides* just sending dbus messages
+<walters> of course nowadays GLib comes with its own implementation of the DBus wire protocol so we are 
making less and less use of libdbus over time
+<ar1s> if the code is still relevant I'd have a look
+<walters> https://git.gnome.org/browse/hotssh/tree/libgssh is my wrapper library for libssh2 that is used 
for my app
+<walters> the core concept is basically here: 
https://git.gnome.org/browse/hotssh/tree/libgssh/gssh-connection.c#n155  and here 
https://git.gnome.org/browse/hotssh/tree/libgssh/gssh-connection.c#n316
+<walters> we call some libssh2 API, and if anything returns EAGAIN, we then ask it which directions to use 
for poll()  (making the optimization that we only recreate the GSource which is effectively poll request + fd 
if it's changed)
+<ar1s> how does glib handle some recursive callback stuff ?
+<walters> i'm not sure which callbacks you're referring to
+<ar1s> a problem I had designing something else based on libssh: 2 sessions in the same mainloop. session 1 
receives some data, user callback reads that data and does a synchronous call on session2
+<ar1s> I had to be very careful to lock session1's socket handlers to avoir reentrancy problems
+<walters> yeah, don't do synchronous calls
+<walters> glib has some nice API for that
+<walters> for example my favorite function: 
https://developer.gnome.org/gio/unstable/GOutputStream.html#g-output-stream-splice-async
+<walters> "take this input stream (fd), and write it to the output continually in a loop, then call me back 
when done"
+<walters> and if you want to interrupt it, you can via the GCancellable
+<walters> that's how you can implement a "Cancel" for a copy operation in a UI for example
+<ar1s> oh yes that's awesome
+<ar1s> I wanted to code this in libssh for ages, that would make many tasks much easier
+<ar1s> you mean the "cancel" button that never works ?
+<walters> well, it certainly can work
+<walters> probably a good example how how this plumbing lets me implement my ssh client app is here: 
https://git.gnome.org/browse/hotssh/tree/src/hotssh-tab.c#n155   - I do an asynchronous read, when it 
returns, feed the data to the terminal widget, then start another async read
+<walters> while this going on of course gtk+ can be talking to X, etc.
+<ar1s> watching, there's a lot of good reading here
+<ar1s> except I really hate glib's C-with-objects-but-its-not-c++-we-swear
+<walters> i understand that perspective for sure, there's certainly a lot of boilerplate.  But in full 
disclosure I've been working on the platform for over 10 years, and have been co-maintaining GLib for the 
last 2, so I'm fairly invested in it =)
+<ar1s> I see, so you're just another victim :)
+<walters> hehe =)
+<ar1s> btw the last link you gave me is interesting, very clean way of implementing async IO
+<ar1s> how would you do if you needed to implement flow control on the vte_terminal_feed
+<walters> yeah it's the general pattern we use now in glib
+<ar1s> (let's say the term has a buffer and you don't want to make it too big)
+<walters> ah, that API simply doesn't block
+<walters> it just draws
+<ar1s> you can have APIs that don't block but instead buffer, the results can be worse
+<ar1s> is there a mechanism to tell you that the output buffer is full and you have a callback to tell you 
when the threshold is safe again ?
+<walters> like say i'm trying to take data from a ssh stream and copy it to another socket?
+<walters> or a local Unix pipe?
+<ar1s> yes
+<walters> in that case, I'll have a GOutputStream (for a pipe I can create one via 
g_unix_output_stream_new()), and then I just call that g_output_stream_splice() function above
+<ar1s> the unix pipe would block, when some asynchronous libs would simply grow the output buffer of the 
socket until gigabytes
+<ar1s> ok, that's plumbing I gess
+<ar1s> guess
+<walters> what happens under the hood is that both of those stream types are pollable
+<walters> so the fundamental GMainContext -> poll() operation will read/write to them when they're ready in 
a nonblocking fashion
+<walters> the implementation is here https://git.gnome.org/browse/glib/tree/gio/goutputstream.c#n1737
+<walters> it's a little complex because we have to account for streams which *aren't* pollable (i.e. don't 
have an underlying fd - e.g. GMemoryOutputStream which is just a memory buffer)
+<walters> but the async splice() is really just "do an async read, get some data, do an async write"
+<walters> repeat
+<ar1s> ok
+<ar1s> I'll watch your implementation and see what APIs we have to provide
+<ar1s> it would be cool if we could just have a contrib/ directory containing bindings to popular IO libs 
like glib, qt or libevent
+<walters> fwiw by default on GNU/Linux userspace targets Qt actually uses the GLib loop  (although it has 
its own)
+<walters> Qt obviously doesn't pull in GLib to Windows targets just for the mainloop though
+<ar1s> are you saying that most apps using QT as backend use glib for IO ?
+<ar1s> oh, glib is a dependancy of Qt on linux ?
+<walters> it's configurable, but last I looked, yes
+<walters> the reason is they want to be able to call out to the GTK+ file chooser and print dialog even in 
Qt apps when those apps are run under GNOME
+<walters> anyways =)  I'll look at ssh_session_get_status()
+<ar1s> ssh_session_get_status in master will give you useful info for output (it says if the output buffer 
is empty or not) but not for reading unfortunately
+<ar1s> but that's not really relevant because libssh consumes every input it receives, so you can always 
poll for POLL_IN
+<walters> ah, duh!  That makes sense, I hadn't thought of htat
+<walters> *that
+<ar1s> yes, ssh has an internal windowing mechanism so it's possible to stop the peer to feed us data until 
we can process it
+<ar1s> (and I think libssh2 doesn't do this properly)
+<sh4rm4> anyway, it would be nice if libssh itself stayed glib-free
+<walters> hm, i'm just reading the code and i'm uncertain about the logic for how libssh chooses whether or 
not to pass POLLOUT to poll()
+<walters> sh4rm4: i'm not suggesting revisiting those old discussions, I just want libssh to export a robust 
mainloop integration, I'm OK maintaining my own nice glib-based wrapper library
+<walters> ar1s: am I reading this right that basically it's ssh_socket_nonblocking_flush() that's saying 
"ok, we have a nonempty output buffer, set POLLOUT"?
+<walters> and then in nonblocking mode we'll invoke poll() ourself with a 0 timeout, get the timeout, but as 
a side effect we'll leave the state in the socket status that we wanted POLLOUT, so my mainloop's poll() can 
then invoke it again
+<ar1s> walters: yes. libssh tries do write without passing by the mainloop if the write_wontblock flag is set
+<ar1s> when the write_wontblock flag is not set, POLLOUT is added to the events to monitor
+<ar1s> I don't think that will cause a problem
+<ar1s> if you use these functions: api.libssh.org/master/libssh_8h_source.html (line 540)
+<ar1s> 541 sorry
+<ar1s> http://api.libssh.org/master/group__libssh__session.html#ga3f1b3c59662464eec3649d3d72a40543
+<ar1s> we should probably reword that doc but the purpose is to tell to libssh "I did poll the socket myself 
and you can write/read"
+<walters> right, OK this is basically fine.  I mean we could avoid the extra poll() call I guess if we added 
API to tell libssh that we'll be invoking poll() externally, but it doesn't really matter
+<ar1s> this api will spare you the poll
+<walters> ok, this gives me a good basis to resume porting my app and library, I'll report back how it goes 
=)  this discussion was very helpful!
+<ar1s> but the best would be to call directly the socket callbacks, but imho these callbacks are not in the 
public API
+<ar1s> you're welcome
diff --git a/src/hotssh-app.c b/src/hotssh-app.c
index be6451c..39d952c 100644
--- a/src/hotssh-app.c
+++ b/src/hotssh-app.c
@@ -75,13 +75,9 @@ hotssh_app_startup (GApplication *app)
 {
   GtkBuilder *builder;
   GMenuModel *app_menu;
-  int rc;
 
   G_APPLICATION_CLASS (hotssh_app_parent_class)->startup (app);
 
-  rc = libssh2_init (0);
-  g_assert (rc == 0);
-
   g_action_map_add_action_entries (G_ACTION_MAP (app),
                                    app_entries, G_N_ELEMENTS (app_entries),
                                    app);



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