[gnome-disk-utility] Improve robustness / error handling for the client TCP bridging bits



commit 6ae46f9d1a648f1c9dad475f86f4ebfd8127b001
Author: David Zeuthen <davidz redhat com>
Date:   Mon Dec 7 13:21:37 2009 -0500

    Improve robustness / error handling for the client TCP bridging bits

 src/gdu/gdu-ssh-bridge.c   |  231 ++++++++++++++++++++++++++++++++++----------
 src/palimpsest/gdu-shell.c |    2 +-
 2 files changed, 181 insertions(+), 52 deletions(-)
---
diff --git a/src/gdu/gdu-ssh-bridge.c b/src/gdu/gdu-ssh-bridge.c
index da8121e..3f993d6 100644
--- a/src/gdu/gdu-ssh-bridge.c
+++ b/src/gdu/gdu-ssh-bridge.c
@@ -23,6 +23,9 @@
 #include <glib/gi18n-lib.h>
 
 #include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+
 #include <gio/gunixinputstream.h>
 #include <gio/gunixoutputstream.h>
 #include <dbus/dbus-glib-lowlevel.h>
@@ -52,8 +55,25 @@
  *  malicious users on both the Client and Server may interfere.
  */
 
+/* TODO: Need to do all this async (or in a thread) so we can have API like this
+ *
+ *   void gdu_pool_new_for_address            (const gchar         *ssh_address,
+ *                                             GMountOperation     *connect_operation,
+ *                                             GCancellable        *cancellable,
+ *                                             GAsyncReadyCallback  callback,
+ *                                             gpointer             user_data);
+ *
+ *   GduPool *gdu_pool_new_for_address_finish (GAsyncResult        *res,
+ *                                             GError             **error);
+ *
+ * on the GduPool object. Also, we won't initially use the @connect_operation parameter for
+ * now - but can do if (or once) we supply our own program to use in $SSH_ASKPASS.
+ */
+
 typedef struct {
         gchar *secret;
+
+        GCancellable *cancellable;
         GMainLoop *loop;
         DBusConnection *server_connection;
         gboolean authorized;
@@ -80,6 +100,8 @@ bridge_data_free (BridgeData *data)
                                                data);
                 dbus_connection_unref (data->server_connection);
         }
+        if (data->cancellable != NULL)
+                g_object_unref (data->cancellable);
         g_free (data);
 }
 
@@ -178,6 +200,26 @@ child_setup (gpointer user_data)
         }
 }
 
+static void
+fixup_newlines (gchar *s)
+{
+        gsize len;
+
+        if (s == NULL)
+                goto out;
+
+        len = strlen (s);
+        if (len < 1)
+                goto out;
+
+        if (s[len - 1] == '\r')
+                s[len - 1] = '\0';
+
+ out:
+        ;
+}
+
+
 DBusGConnection *
 _gdu_ssh_bridge_connect (GduPool          *pool,
                          const gchar      *ssh_address,
@@ -221,6 +263,7 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
         stdin_data_stream = NULL;
         stdout_data_stream = NULL;
         stderr_data_stream = NULL;
+        ssh_pid = 0;
 
         data = g_new0 (BridgeData, 1);
         str = g_string_new (NULL);
@@ -229,6 +272,8 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
                 g_string_append_printf (str, "%08x", r);
         }
         data->secret = g_string_free (str, FALSE);
+        data->cancellable = g_cancellable_new ();
+        data->loop = g_main_loop_new (NULL, FALSE);
 
         /* Create and start the local DBusServer */
         for (local_port = 9000; local_port < 10000; local_port++) {
@@ -240,7 +285,9 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
                         if (g_strcmp0 (dbus_error.name, "org.freedesktop.DBus.Error.AddressInUse") == 0) {
                                 continue;
                         } else {
-                                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
+                                g_set_error (error,
+                                             GDU_ERROR,
+                                             GDU_ERROR_FAILED,
                                              _("Error listening to address `localhost:%d': %s: %s\n"),
                                              local_port,
                                              dbus_error.name,
@@ -268,7 +315,9 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
                                                  NULL);
         /* Allow only anonymous auth */
         if (!dbus_server_set_auth_mechanisms (server, auth_mechanisms)) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
                              _("Error setting auth mechanisms on local DBusServer\n"));
                 goto out;
         }
@@ -330,34 +379,96 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
         g_object_unref (stdout_stream);
         g_object_unref (stderr_stream);
 
+        /* Read and parse output from ssh */
         while (TRUE) {
-                /* Read and parse the remote port number */
+                //g_print (" - Reading...\n");
+
                 s = g_data_input_stream_read_line (stderr_data_stream,
                                                    NULL, /* gsize *length */
-                                                   NULL,
+                                                   data->cancellable,
                                                    &local_error);
                 if (s == NULL) {
                         if (local_error != NULL) {
-                                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                                             _("Error reading stderr output from host `%s': %s"),
-                                             ssh_address, local_error->message);
+                                g_set_error (error,
+                                             GDU_ERROR,
+                                             GDU_ERROR_FAILED,
+                                             _("Error reading stderr output: %s"),
+                                             local_error->message);
                                 g_error_free (local_error);
                         } else {
-                                /* This happens if ssh exits due to e.g. permission denied */
-                                g_set_error (error, GDU_ERROR, GDU_ERROR_PERMISSION_DENIED,
-                                             _("Failed to log into host `%s'"),
-                                             ssh_address);
+                                g_set_error_literal (error,
+                                                     GDU_ERROR,
+                                                     GDU_ERROR_FAILED,
+                                                     _("Error reading stderr output: No content"));
                         }
                         goto out;
-                } else if (strlen (s) == 0) {
-                        g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                                     _("Unexpected blank line in stderr output from host `%s'"),
-                                     ssh_address);
-                        g_free (s);
-                        goto out;
-                } else if (sscanf (s, "Allocated port %d for remote forward to", &remote_port) == 1) {
+                }
+
+                fixup_newlines (s);
+                //g_print (" - Read `%s'\n", s);
+
+                if (sscanf (s, "Allocated port %d for remote forward to", &remote_port) == 1) {
+                        /* got it */
                         g_free (s);
                         break;
+                } else if (strstr (s, "Permanently added") != NULL &&
+                           strstr (s, "to the list of known hosts") != NULL) {
+                        /* just continue */
+                        g_free (s);
+                } else {
+                        GString *full_error_message;
+
+                        /* otherwise assume error - Keep reading output as it may be
+                         * multi-line, e.g.
+                         *
+                         *   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+                         *   @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+                         *   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+                         *   IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+                         *   Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+                         */
+                        full_error_message = g_string_new (s);
+                        g_free (s);
+
+                        /* sleep for a bit to ensure we've buffered all the data from ssh's
+                         * stderr stream (TODO: this is slightly racy
+                         */
+                        usleep (100 * 1000);
+
+                        /* kill ssh process early so we won't block on reading additional data if there is none */
+                        if (ssh_pid > 0) {
+                                kill (ssh_pid, SIGTERM);
+                                ssh_pid = 0;
+                        }
+
+                        while (TRUE) {
+                                s = g_data_input_stream_read_line (stderr_data_stream,
+                                                                   NULL, /* gsize *length */
+                                                                   data->cancellable,
+                                                                   NULL);
+                                if (s != NULL) {
+                                        fixup_newlines (s);
+                                        g_string_append (full_error_message, s);
+                                        g_string_append_c (full_error_message, '\n');
+                                } else {
+                                        break;
+                                }
+                        }
+
+                        s = g_string_free (full_error_message, FALSE);
+                        if (strlen (s) > 0) {
+                                g_set_error_literal (error,
+                                                     GDU_ERROR,
+                                                     GDU_ERROR_FAILED,
+                                                     s);
+                        } else {
+                                g_set_error_literal (error,
+                                                     GDU_ERROR,
+                                                     GDU_ERROR_FAILED,
+                                                     _("Error logging in"));
+                        }
+                        g_free (s);
+                        goto out;
                 }
         }
 
@@ -369,11 +480,14 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
         s = g_strdup_printf ("/usr/lib/udisks/udisks-tcp-bridge -p %d\n", remote_port);
         if (!g_data_output_stream_put_string (stdin_data_stream,
                                               s,
-                                              NULL,
+                                              data->cancellable,
                                               &local_error)) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Error sending `%s' to host `%s': %s"),
-                             s, ssh_address, local_error->message);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Error sending `%s': %s"),
+                             s,
+                             local_error->message);
                 g_error_free (local_error);
                 g_free (s);
                 goto out;
@@ -384,20 +498,23 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
          */
         s = g_data_input_stream_read_line (stderr_data_stream,
                                            NULL, /* gsize *length */
-                                           NULL,
+                                           data->cancellable,
                                            &local_error);
         if (s == NULL) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Error reading stderr output from host `%s': %s"),
-                             ssh_address, local_error->message);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Error reading stderr output: %s"),
+                             local_error->message);
                 g_error_free (local_error);
                 goto out;
         }
         if (g_strcmp0 (s, "udisks-tcp-bridge: Waiting for secret") != 0) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Unexpected stderr output from from host `%s': "
-                               "Expected `udisks-tcp-bridge: Waiting for secret' but got `%s'"),
-                             ssh_address, s);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Unexpected stderr output - expected `udisks-tcp-bridge: Waiting for secret' but got `%s'"),
+                             s);
                 g_free (s);
                 goto out;
         }
@@ -408,11 +525,13 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
         s = g_strdup_printf ("%s\n", data->secret);
         if (!g_data_output_stream_put_string (stdin_data_stream,
                                               s,
-                                              NULL,
+                                              data->cancellable,
                                               &local_error)) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Error passing authorization secret to host `%s': %s"),
-                             ssh_address, local_error->message);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Error passing authorization secret: %s"),
+                             local_error->message);
                 g_error_free (local_error);
                 memset (s, '\0', strlen (s));
                 g_free (s);
@@ -426,27 +545,30 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
          */
         s = g_data_input_stream_read_line (stderr_data_stream,
                                            NULL, /* gsize *length */
-                                           NULL,
+                                           data->cancellable,
                                            &local_error);
         if (s == NULL) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Error reading stderr from host `%s': %s"),
-                             ssh_address, local_error->message);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Error reading stderr from: %s"),
+                             local_error->message);
                 g_error_free (local_error);
                 goto out;
         }
         if (sscanf (s, "udisks-tcp-bridge: Attempting to connect to port %d", &remote_port) != 1) {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("Unexpected stderr output from host `%s': "
-                               "Expected `udisks-tcp-bridge: Attempting to connect to port %d' but got `%s'"),
-                             ssh_address, remote_port, s);
+                g_set_error (error,
+                             GDU_ERROR,
+                             GDU_ERROR_FAILED,
+                             _("Unexpected stderr output - expected `udisks-tcp-bridge: Attempting to connect to port %d' but got `%s'"),
+                             remote_port,
+                             s);
                 g_free (s);
                 goto out;
         }
         g_free (s);
 
         /* Wait for D-Bus connection and authorization */
-        data->loop = g_main_loop_new (NULL, FALSE);
         g_main_loop_run (data->loop);
 
         if (data->server_connection != NULL) {
@@ -454,20 +576,27 @@ _gdu_ssh_bridge_connect (GduPool          *pool,
                         ret = dbus_connection_get_g_connection (dbus_connection_ref (data->server_connection));
                 } else {
                         dbus_connection_close (data->server_connection);
-                        g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                                     _("The udisks-tcp-bridge program on host `%s' didn't prove it was "
-                                       "authorized: %s"),
-                                     ssh_address,
+                        g_set_error (error,
+                                     GDU_ERROR,
+                                     GDU_ERROR_FAILED,
+                                     _("The udisks-tcp-bridge program failed to prove it was authorized: %s"),
                                      data->error_message != NULL ? data->error_message : "(no detail)");
                 }
         } else {
-                g_set_error (error, GDU_ERROR, GDU_ERROR_FAILED,
-                             _("The udisks-tcp-bridge program on host `%s' didn't prove it was "
-                               "authorized"),
-                             ssh_address);
+                g_set_error_literal (error,
+                                     GDU_ERROR,
+                                     GDU_ERROR_FAILED,
+                                     _("The udisks-tcp-bridge program failed to prove it was authorized"));
         }
 
  out:
+        if (ret == NULL) {
+                /* kill ssh connection if we failed */
+                if (ssh_pid > 0) {
+                        kill (ssh_pid, SIGTERM);
+                }
+        }
+
         if (data != NULL)
                 bridge_data_free (data);
         if (server != NULL) {
diff --git a/src/palimpsest/gdu-shell.c b/src/palimpsest/gdu-shell.c
index ecaae02..5ce4cdc 100644
--- a/src/palimpsest/gdu-shell.c
+++ b/src/palimpsest/gdu-shell.c
@@ -1696,7 +1696,7 @@ create_window (GduShell *shell)
         error = NULL;
         shell->priv->pool = gdu_pool_new_for_address (shell->priv->ssh_address, &error);
         if (error != NULL) {
-                g_printerr ("Error connecting to `%s': %s",
+                g_printerr ("Error connecting to `%s': `%s'\n",
                             shell->priv->ssh_address,
                             error->message);
                 g_error_free (error);



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