[hotssh] Initial host key fingerprint checking



commit 5d80427764420d149cbbae0ad5f5680d06b96bcb
Author: Colin Walters <walters verbum org>
Date:   Thu Oct 10 21:59:33 2013 -0400

    Initial host key fingerprint checking
    
    Ok, we prompt for the key every time.  But that's better than not
    prompting...
    
    Need to read the OpenSSH known hosts at least, or consider having our
    own database.

 libgssh/gssh-connection-private.h |   12 +--
 libgssh/gssh-connection.c         |  147 ++++++++++++++++++++++---------------
 libgssh/gssh-connection.h         |    5 +
 src/hotssh-win.c                  |   64 +++++++++++++++-
 src/window.ui                     |  124 ++++++++++++++++++++++++++++---
 5 files changed, 271 insertions(+), 81 deletions(-)
---
diff --git a/libgssh/gssh-connection-private.h b/libgssh/gssh-connection-private.h
index 92abe63..0e7bf22 100644
--- a/libgssh/gssh-connection-private.h
+++ b/libgssh/gssh-connection-private.h
@@ -23,21 +23,16 @@
 #include "gssh-connection.h"
 #include <libssh2.h>
 
-typedef enum {
-  GSSH_CONNECTION_HANDSHAKE_STATE_STARTING,
-  GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST
-} GSshConnectionHandshakeState;
-
 struct _GSshConnection
 {
   GObject parent;
 
   GSshConnectionState state;
-  GSshConnectionHandshakeState handshake_state;
 
   guint select_inbound : 1;
   guint select_outbound : 1;
-  guint unused : 30;
+  guint preauth_continue : 1;
+  guint unused : 29;
 
   char *username;
 
@@ -47,6 +42,7 @@ struct _GSshConnection
   GHashTable *channels;
 
   GError *cached_error;
+  GBytes *remote_hostkey_sha1;
   GMainContext *maincontext;
   GSocketClient *socket_client;
   GSocket *socket;
@@ -56,7 +52,7 @@ struct _GSshConnection
   GCancellable *cancellable;
   char *password;
 
-  GTask *connection_task;
+  GTask *handshake_task;
   GTask *auth_task;
   GHashTable *open_channel_exec_tasks;
   GHashTable *channel_tasks;
diff --git a/libgssh/gssh-connection.c b/libgssh/gssh-connection.c
index 3f887a1..09997c6 100644
--- a/libgssh/gssh-connection.c
+++ b/libgssh/gssh-connection.c
@@ -66,7 +66,7 @@ reset_state (GSshConnection               *self)
 
   g_assert (self->state == GSSH_CONNECTION_STATE_DISCONNECTED ||
             self->state == GSSH_CONNECTION_STATE_ERROR);
-  g_clear_object (&self->connection_task);
+  g_clear_object (&self->handshake_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);
@@ -85,13 +85,13 @@ reset_state (GSshConnection               *self)
   /* This hash doesn't hold a ref */
   self->channels = g_hash_table_new_full (NULL, NULL, NULL, NULL);
 
+  g_clear_pointer (&self->remote_hostkey_sha1, g_bytes_unref);
   g_clear_error (&self->cached_error);
   g_clear_object (&self->socket);
   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);
-  self->handshake_state = GSSH_CONNECTION_HANDSHAKE_STATE_STARTING;
 }
 
 static void
@@ -117,10 +117,10 @@ state_transition_take_error (GSshConnection       *self,
 {
   g_debug ("caught error: %s", error->message);
 
-  if (self->connection_task)
+  if (self->handshake_task)
     {
-      g_task_return_error (self->connection_task, error);
-      g_clear_object (&self->connection_task);
+      g_task_return_error (self->handshake_task, error);
+      g_clear_object (&self->handshake_task);
     }
   else
     {
@@ -332,60 +332,60 @@ gssh_connection_iteration (GSshConnection   *self,
     case GSSH_CONNECTION_STATE_HANDSHAKING:
       {
        int socket_fd = g_socket_get_fd (self->socket);
-        switch (self->handshake_state)
+        if ((rc = libssh2_session_handshake (self->session, socket_fd)) == LIBSSH2_ERROR_EAGAIN)
           {
-          case GSSH_CONNECTION_HANDSHAKE_STATE_STARTING:
-            {
-              g_debug ("handshake on socket fd=%d", socket_fd);
-              if ((rc = libssh2_session_handshake (self->session, socket_fd)) == LIBSSH2_ERROR_EAGAIN)
-                {
-                  recalculate_socket_state (self);
-                  return;
-                }
-              if (rc)
-                {
-                  _gssh_set_error_from_libssh2 (error, "Failed to handshake SSH2 session", self->session);
-                  state_transition_take_error (self, local_error);
-                  return;
-                }
-              self->handshake_state = GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST;
-              goto repeat;
-            }
-          case GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST:
-            {
-              const char *authschemes;
-
-              authschemes = libssh2_userauth_list (self->session,
-                                                   self->username,
-                                                   strlen (self->username));
-              if (authschemes == NULL)
-                {
-                  if (libssh2_userauth_authenticated (self->session))
-                    {
-                      /* 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)
-                    {
-                      recalculate_socket_state (self);
-                      return;
-                    }
-                  else
-                    {
-                      _gssh_set_error_from_libssh2 (error, "Failed to list auth mechanisms", self->session);
-                      state_transition_take_error (self, local_error);
-                      return;
-                    }
-                }
-              self->authschemes = g_strsplit (authschemes, ",", 0);
-              state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
-              goto repeat;
-            }
+            recalculate_socket_state (self);
+            return;
           }
+        if (rc)
+          {
+            _gssh_set_error_from_libssh2 (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;
+      }
+    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)
+          {
+            if (libssh2_userauth_authenticated (self->session))
+              {
+                /* 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)
+              {
+                recalculate_socket_state (self);
+                return;
+              }
+            else
+              {
+                _gssh_set_error_from_libssh2 (error, "Failed to list auth mechanisms", self->session);
+                state_transition_take_error (self, local_error);
+                return;
+              }
+          }
+        self->authschemes = g_strsplit (authschemes, ",", 0);
+        state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
+        goto repeat;
       }
       break;
     case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
@@ -468,7 +468,7 @@ on_socket_ready (GSocket *socket,
       return FALSE;
     }
 
-  g_debug ("socket ready: state %d handshake_state: %d", self->state, self->handshake_state);
+  g_debug ("socket ready: state %d", self->state);
 
   gssh_connection_iteration (self, condition);
 
@@ -524,6 +524,34 @@ gssh_connection_get_state (GSshConnection        *self)
   return self->state;
 }
 
+/**
+ * gssh_connection_preauth_get_fingerprint_sha1:
+ * @self: Self
+ *
+ * Returns: (transfer none): 20 bytes for the remote host's SHA1 fingerprint
+ */
+GBytes *
+gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self)
+{
+  return self->remote_hostkey_sha1;
+}
+
+/**
+ * gssh_connection_preauth_continue:
+ * @self: Self
+ *
+ * Call this function after having verified the host key fingerprint
+ * to continue the connection.
+ */
+void
+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;
+  gssh_connection_iteration_default (self);
+}
+
 const char*const*
 gssh_connection_get_authentication_mechanisms (GSshConnection   *self)
 {
@@ -580,8 +608,7 @@ gssh_connection_handshake_async (GSshConnection    *self,
 
   state_transition (self, GSSH_CONNECTION_STATE_CONNECTING);
 
-  self->connection_task = g_task_new (self, cancellable, callback, user_data);
-
+  self->handshake_task = g_task_new (self, cancellable, callback, user_data);
   g_socket_client_connect_async (self->socket_client, self->address, cancellable,
                                 on_socket_client_connected, self);
 }
diff --git a/libgssh/gssh-connection.h b/libgssh/gssh-connection.h
index 85540e1..e4bed0e 100644
--- a/libgssh/gssh-connection.h
+++ b/libgssh/gssh-connection.h
@@ -33,6 +33,7 @@ typedef enum /*< prefix=GSSH_CONNECTION_STATE >*/
     GSSH_CONNECTION_STATE_DISCONNECTED,
     GSSH_CONNECTION_STATE_CONNECTING,
     GSSH_CONNECTION_STATE_HANDSHAKING,
+    GSSH_CONNECTION_STATE_PREAUTH,
     GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED,
     GSSH_CONNECTION_STATE_CONNECTED,
     GSSH_CONNECTION_STATE_ERROR
@@ -58,6 +59,10 @@ gboolean                gssh_connection_handshake_finish (GSshConnection    *sel
                                                             GAsyncResult        *result,
                                                             GError             **error);
 
+GBytes *                gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self);
+
+void                    gssh_connection_preauth_continue (GSshConnection *self);
+
 const char*const*       gssh_connection_get_authentication_mechanisms (GSshConnection   *self);
 
 void                    gssh_connection_auth_password_async (GSshConnection    *self,
diff --git a/src/hotssh-win.c b/src/hotssh-win.c
index c8a33c3..433d602 100644
--- a/src/hotssh-win.c
+++ b/src/hotssh-win.c
@@ -23,6 +23,7 @@ typedef struct _HotSshWindowPrivate HotSshWindowPrivate;
 typedef enum {
   HOTSSH_WINDOW_PAGE_NEW_CONNECTION,
   HOTSSH_WINDOW_PAGE_INTERSTITAL,
+  HOTSSH_WINDOW_PAGE_AUTH,
   HOTSSH_WINDOW_PAGE_TERMINAL
 } HotSshWindowPage;
 
@@ -42,6 +43,10 @@ struct _HotSshWindowPrivate
   GtkWidget *password_entry;
   GtkWidget *password_submit;
   GtkWidget *connect_cancel_button;
+  GtkWidget *auth_cancel_button;
+  GtkWidget *hostkey_container;
+  GtkWidget *hostkey_fingerprint_label;
+  GtkWidget *approve_hostkey_button;
   GtkWidget *disconnect_button;
   GtkWidget *terminal_box;
 
@@ -92,8 +97,9 @@ state_reset_for_new_connection (HotSshWindow                *self)
   vte_terminal_reset ((VteTerminal*)priv->terminal, TRUE, TRUE);
   gtk_entry_set_text ((GtkEntry*)priv->password_entry, "");
   reset_focus_state (self);
-  gtk_widget_hide (priv->password_container);
+  gtk_label_set_text ((GtkLabel*)priv->connection_text, "");
   gtk_widget_show (priv->connection_text_container);
+  gtk_widget_hide (priv->hostkey_container);
   gtk_widget_set_sensitive (priv->password_container, TRUE);
   g_debug ("reset state done");
 }
@@ -188,6 +194,9 @@ on_open_shell_complete (GObject           *src,
 }
 
 static void
+iterate_authentication_modes (HotSshWindow          *self);
+
+static void
 on_connection_state_notify (GSshConnection   *conn,
                            GParamSpec         *pspec,
                            gpointer            user_data)
@@ -205,8 +214,16 @@ 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_WINDOW_PAGE_INTERSTITAL);
+      break;
     case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
+      page_transition (self, HOTSSH_WINDOW_PAGE_AUTH);
+      iterate_authentication_modes (self);
+      break;
     case GSSH_CONNECTION_STATE_ERROR:
+      gtk_widget_hide (priv->hostkey_container);
+      gtk_widget_show (priv->connection_text_container);
       page_transition (self, HOTSSH_WINDOW_PAGE_INTERSTITAL);
       break;
     case GSSH_CONNECTION_STATE_CONNECTED:
@@ -277,13 +294,37 @@ on_connection_handshake (GObject         *object,
                         gpointer         user_data)
 {
   HotSshWindow *self = HOTSSH_WINDOW (user_data);
+  HotSshWindowPrivate *priv = hotssh_window_get_instance_private (self);
   GError *local_error = NULL;
   GError **error = &local_error;
+  GBytes *hostkey_sha1_binary;
+  GString *buf;
+  guint i;
+  const guint8 *binbuf;
+  gsize len;
+  gs_free char *hostkey_sha1_text = NULL;
 
   if (!gssh_connection_handshake_finish ((GSshConnection*)object, result, error))
     goto out;
 
-  iterate_authentication_modes (self);
+  g_debug ("handshake complete");
+
+  hostkey_sha1_binary = gssh_connection_preauth_get_fingerprint_sha1 (priv->connection);
+  binbuf = g_bytes_get_data (hostkey_sha1_binary, &len);
+  buf = g_string_new ("");
+  for (i = 0; i < len; i++)
+    {
+      g_string_append_printf (buf, "%02X", binbuf[i]);
+      if (i < len - 1)
+       g_string_append_c (buf, ':');
+    }
+  hostkey_sha1_text = g_string_free (buf, FALSE);
+
+  gtk_label_set_text ((GtkLabel*)priv->hostkey_fingerprint_label,
+                     hostkey_sha1_text);
+  gtk_widget_hide (priv->connection_text_container);
+  gtk_widget_show (priv->hostkey_container);
+  page_transition (self, HOTSSH_WINDOW_PAGE_INTERSTITAL);
 
  out:
   if (local_error)
@@ -322,8 +363,9 @@ on_connect (GtkButton     *button,
   g_signal_connect_object (priv->connection, "notify::state",
                           G_CALLBACK (on_connection_state_notify),
                           self, 0);
+  g_debug ("connected, beginning handshake");
   gssh_connection_handshake_async (priv->connection, priv->cancellable,
-                                    on_connection_handshake, self);
+                                  on_connection_handshake, self);
 }
 
 static void
@@ -426,6 +468,16 @@ on_connect_cancel (GtkButton     *button,
 }
 
 static void
+on_approve_hostkey_clicked (GtkButton     *button,
+                           gpointer       user_data)
+{
+  HotSshWindow *self = user_data;
+  HotSshWindowPrivate *priv = hotssh_window_get_instance_private (self);
+
+  gssh_connection_preauth_continue (priv->connection);
+}
+
+static void
 send_pty_size_request (HotSshWindow             *self);
 
 static void
@@ -490,6 +542,8 @@ hotssh_window_init (HotSshWindow *self)
 
   g_signal_connect (priv->connect_button, "clicked", G_CALLBACK (on_connect), self);
   g_signal_connect (priv->connect_cancel_button, "clicked", G_CALLBACK (on_connect_cancel), self);
+  g_signal_connect (priv->auth_cancel_button, "clicked", G_CALLBACK (on_connect_cancel), self);
+  g_signal_connect (priv->approve_hostkey_button, "clicked", G_CALLBACK (on_approve_hostkey_clicked), self);
   g_signal_connect (priv->disconnect_button, "clicked", G_CALLBACK (on_disconnect), 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);
@@ -534,6 +588,10 @@ hotssh_window_class_init (HotSshWindowClass *class)
   gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, password_entry);
   gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, password_submit);
   gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, 
connect_cancel_button);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, auth_cancel_button);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, hostkey_container);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, 
hostkey_fingerprint_label);
+  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, 
approve_hostkey_button);
   gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, disconnect_button);
   gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, terminal_box);
 }
diff --git a/src/window.ui b/src/window.ui
index 2567569..ed67e71 100644
--- a/src/window.ui
+++ b/src/window.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.15.4 on Thu Sep 19 13:48:41 2013 -->
+<!-- Generated with glade 3.15.4 on Thu Oct 10 21:41:56 2013 -->
 <interface>
   <!-- interface-requires gtk+ 3.10 -->
   <template class="HotSshWindow" parent="GtkApplicationWindow">
@@ -117,15 +117,14 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="box1">
+          <object class="GtkBox" id="box4">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="GtkBox" id="box3">
+              <object class="GtkBox" id="interstital_box">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkAlignment" id="connection_text_container">
                     <property name="visible">True</property>
@@ -135,16 +134,121 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="label" translatable="yes">Connecting...</property>
+                        <property name="justify">center</property>
                       </object>
                     </child>
                   </object>
                   <packing>
-                    <property name="expand">True</property>
+                    <property name="expand">False</property>
                     <property name="fill">True</property>
                     <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
+                  <object class="GtkBox" id="hostkey_container">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="valign">center</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkLabel" id="hostkey_label_text">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Approval of newly seen host key required; 
RSA key fingerprint is:</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="hostkey_fingerprint_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" 
translatable="yes">00:--:--:--:--:--:--:--:--:--:--:--:--:--:--:00</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="approve_hostkey_button">
+                        <property name="label" translatable="yes">Approve and Connect</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <property name="xalign">0.55000001192092896</property>
+                        <property name="yalign">0.57999998331069946</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="connect_cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="halign">end</property>
+                <property name="use_stock">True</property>
+                <property name="image_position">right</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child type="tab">
+          <object class="GtkLabel" id="label7">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">page 2</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkBox" id="box3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="baseline_position">top</property>
+                <child>
                   <object class="GtkBox" id="password_container">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
@@ -207,7 +311,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="connect_cancel_button">
+              <object class="GtkButton" id="auth_cancel_button">
                 <property name="label">gtk-cancel</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
@@ -224,7 +328,7 @@
             </child>
           </object>
           <packing>
-            <property name="position">1</property>
+            <property name="position">2</property>
           </packing>
         </child>
         <child type="tab">
@@ -234,7 +338,7 @@
             <property name="label" translatable="yes">page 2</property>
           </object>
           <packing>
-            <property name="position">1</property>
+            <property name="position">2</property>
             <property name="tab_fill">False</property>
           </packing>
         </child>
@@ -303,7 +407,7 @@
             </child>
           </object>
           <packing>
-            <property name="position">2</property>
+            <property name="position">3</property>
           </packing>
         </child>
         <child type="tab">
@@ -313,7 +417,7 @@
             <property name="label" translatable="yes">page 3</property>
           </object>
           <packing>
-            <property name="position">2</property>
+            <property name="position">3</property>
             <property name="tab_fill">False</property>
           </packing>
         </child>


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