[hotssh] Add ~/.config/hotssh/hostdb.ini, store extra data there



commit 0431a61f151e9dc579516906b1237f270c0b21d9
Author: Colin Walters <walters verbum org>
Date:   Sun Dec 1 16:38:31 2013 -0500

    Add ~/.config/hotssh/hostdb.ini, store extra data there
    
    The hostdb merges ~/.ssh/known_hosts with extra data such as:
    
     * Timestamp of last connection
     * Username
    
    And in the future other stuff.  Perhaps we'll stop reading
    ~/.ssh/known_hosts and do our own thing.

 src/hotssh-hostdb.c |  348 ++++++++++++++++++++++++++++++++++++++++++++++++---
 src/hotssh-hostdb.h |    5 +
 src/hotssh-tab.c    |    3 +
 3 files changed, 341 insertions(+), 15 deletions(-)
---
diff --git a/src/hotssh-hostdb.c b/src/hotssh-hostdb.c
index 8dbb687..0473131 100644
--- a/src/hotssh-hostdb.c
+++ b/src/hotssh-hostdb.c
@@ -28,6 +28,13 @@
 
 #include "libgsystem.h"
 
+enum {
+  HOTSSH_HOSTDB_COLUMN_HOSTNAME,
+  HOTSSH_HOSTDB_COLUMN_LAST_USED,
+  HOTSSH_HOSTDB_COLUMN_IS_KNOWN,
+  HOTSSH_HOSTDB_COLUMN_USERNAME
+};
+
 struct _HotSshHostDB
 {
   GObject parent;
@@ -43,14 +50,148 @@ typedef struct _HotSshHostDBPrivate HotSshHostDBPrivate;
 struct _HotSshHostDBPrivate
 {
   GtkListStore *model;
+  GKeyFile *extradb;
   GFile *openssh_dir;
   GFile *openssh_knownhosts_path;
-  GFile *hotssh_extra;
+  GFile *hotssh_extradb;
   GFileMonitor *knownhosts_monitor;
+  GFileMonitor *hotssh_extradb_monitor;
+
+  guint idle_save_extradb_id;
+  char *new_extradb_contents;
 };
 
 G_DEFINE_TYPE_WITH_PRIVATE(HotSshHostDB, hotssh_hostdb, G_TYPE_OBJECT)
 
+static char *
+host_group_key_to_host (const char *group)
+{
+  char *hostname = NULL;
+  const char *host_group_prefix = "host \"";
+  const char *lastquote;
+  const char *hoststart;
+  
+  if (!g_str_has_prefix (group, host_group_prefix))
+    return NULL;
+
+  hoststart = group + strlen (host_group_prefix);
+  lastquote = strchr (hoststart, '"');
+  if (!lastquote)
+    return NULL;
+      
+  hostname = g_strndup (hoststart, lastquote - hoststart);
+  if (!(hostname && hostname[0]))
+    {
+      g_free (hostname);
+      return NULL;
+    }
+
+  return hostname;
+}
+
+static gboolean
+sync_extradb_to_model (HotSshHostDB    *self,
+                       const char      *groupname,
+                       const char      *hostname,
+                       GtkTreeIter     *iter)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  guint64 last_used;
+  gs_free char *username = NULL;
+  GError *temp_error = NULL;
+
+  last_used = g_key_file_get_uint64 (priv->extradb, groupname, "last-used", &temp_error);
+  if (temp_error)
+    {
+      g_clear_error (&temp_error);
+      return FALSE;
+    }
+  else
+    {
+      gtk_list_store_set (priv->model, iter,
+                          HOTSSH_HOSTDB_COLUMN_LAST_USED, last_used,
+                          -1);
+    }
+
+  username = g_key_file_get_string (priv->extradb, groupname, "username", NULL);
+  if (username && username[0])
+    {
+      gtk_list_store_set (priv->model, iter,
+                          HOTSSH_HOSTDB_COLUMN_USERNAME, username,
+                          -1);
+    }
+  return TRUE;
+}
+
+static void
+merge_databases (HotSshHostDB *self)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  gs_strfreev char **extradb_groups = NULL;
+  gs_unref_hashtable GHashTable *known_hosts
+    = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+  char **strviter;
+  GtkTreeIter iter;
+  
+  if (!gtk_tree_model_get_iter_first ((GtkTreeModel*)priv->model, &iter))
+    return;
+  while (TRUE)
+    {
+      gs_free char *hostname = NULL;
+      gs_free char *section = NULL;
+      gs_free char *username = NULL;
+      gboolean is_known = FALSE;
+      gboolean remove = FALSE;
+
+      gtk_tree_model_get ((GtkTreeModel*)priv->model, &iter,
+                          HOTSSH_HOSTDB_COLUMN_HOSTNAME, &hostname,
+                          HOTSSH_HOSTDB_COLUMN_IS_KNOWN, &is_known,
+                          -1);
+
+      if (is_known)
+        g_hash_table_add (known_hosts, g_strdup (hostname));
+
+      section = g_strdup_printf ("host \"%s\"", hostname);
+      if (!sync_extradb_to_model (self, section, hostname, &iter))
+        {
+          if (!is_known)
+            remove = TRUE;
+        }
+
+      if (remove)
+        {
+          if (!gtk_list_store_remove (priv->model, &iter))
+            break;
+        }
+      else
+        {
+          if (!gtk_tree_model_iter_next ((GtkTreeModel*)priv->model, &iter))
+            break;
+        }
+    }
+
+  extradb_groups = g_key_file_get_groups (priv->extradb, NULL);
+  for (strviter = extradb_groups; strviter && *strviter; strviter++)
+    {
+      const char *group = *strviter;
+      gs_free char *hostname = NULL;
+
+      hostname = host_group_key_to_host (group);
+      if (!hostname)
+        continue;
+      
+      if (!g_hash_table_contains (known_hosts, hostname))
+        {
+          gtk_list_store_append (priv->model, &iter);
+          gtk_list_store_set (priv->model, &iter,
+                              HOTSSH_HOSTDB_COLUMN_HOSTNAME, hostname,
+                              HOTSSH_HOSTDB_COLUMN_IS_KNOWN, FALSE,
+                              -1);
+          (void) sync_extradb_to_model (self, group, hostname, &iter);
+        }
+    }
+}
+
 static void
 on_knownhosts_changed (GFileMonitor        *monitor,
                        GFile               *src,
@@ -100,7 +241,11 @@ on_knownhosts_changed (GFileMonitor        *monitor,
       if (comma)
         *comma = '\0';
       gtk_list_store_append (priv->model, &modeliter);
-      gtk_list_store_set (priv->model, &modeliter, 0, parts[0], 1, 0, -1);
+      gtk_list_store_set (priv->model, &modeliter,
+                          HOTSSH_HOSTDB_COLUMN_HOSTNAME, parts[0],
+                          HOTSSH_HOSTDB_COLUMN_LAST_USED, 0,
+                          HOTSSH_HOSTDB_COLUMN_IS_KNOWN, TRUE,
+                          -1);
 
     next:
       if (eol)
@@ -110,6 +255,9 @@ on_knownhosts_changed (GFileMonitor        *monitor,
     }
 
   g_debug ("Read %d known hosts", gtk_tree_model_iter_n_children ((GtkTreeModel*)priv->model, NULL));
+
+  if (priv->extradb)
+    merge_databases (self);
   
  out:
   if (local_error)
@@ -121,6 +269,174 @@ on_knownhosts_changed (GFileMonitor        *monitor,
 }
 
 static void
+on_extradb_changed (GFileMonitor        *monitor,
+                    GFile               *src,
+                    GFile               *other,
+                    GFileMonitorEvent    event,
+                    gpointer             user_data)
+{
+  HotSshHostDB *self = user_data;
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  GError *local_error = NULL;
+
+  g_clear_pointer (&priv->extradb, g_key_file_unref);
+  priv->extradb = g_key_file_new ();
+  
+  if (!g_key_file_load_from_file (priv->extradb, gs_file_get_path_cached (priv->hotssh_extradb), 0, 
&local_error))
+    {
+      if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+        g_clear_error (&local_error);
+      else
+        goto out;
+    }
+
+  if (priv->extradb)
+    merge_databases (self);
+  
+ out:
+  if (local_error)
+    {
+      g_debug ("Failed to read '%s': %s", gs_file_get_path_cached (priv->openssh_knownhosts_path),
+               local_error->message);
+      g_clear_error (&local_error);
+    }
+}
+
+static gboolean
+hostname_to_iter (HotSshHostDB    *self,
+                  const char      *hostname,
+                  GtkTreeIter     *iter)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+
+  if (!gtk_tree_model_get_iter_first ((GtkTreeModel*)priv->model, iter))
+    return FALSE;
+
+  while (TRUE)
+    {
+      gs_free char *model_hostname = NULL;
+
+      gtk_tree_model_get ((GtkTreeModel*)priv->model, iter,
+                          HOTSSH_HOSTDB_COLUMN_HOSTNAME, &model_hostname,
+                          -1);
+
+      if (g_ascii_strcasecmp (hostname, model_hostname) != 0)
+        {
+          gtk_tree_model_iter_next ((GtkTreeModel*)priv->model, iter);
+          continue;
+        }
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+on_replace_extradb_contents_complete (GObject                *src,
+                                      GAsyncResult           *result,
+                                      gpointer                user_data)
+{
+  HotSshHostDB *self = user_data;
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  GError *local_error = NULL;
+
+  priv->idle_save_extradb_id = 0;
+
+  if (!g_file_replace_contents_finish ((GFile*)src, result, NULL, &local_error))
+    goto out;
+
+ out:
+  g_clear_pointer (&priv->new_extradb_contents, g_free);
+  if (local_error)
+    {
+      g_warning ("Failed to save '%s': %s",
+                 gs_file_get_path_cached (priv->hotssh_extradb),
+                 local_error->message);
+      g_clear_error (&local_error);
+    }
+}
+
+static gboolean
+idle_save_extradb (gpointer user_data)
+{
+  HotSshHostDB *self = user_data;
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  gsize len;
+
+  priv->new_extradb_contents = g_key_file_to_data (priv->extradb, &len, NULL);
+  g_assert (priv->new_extradb_contents);
+
+  g_file_replace_contents_async (priv->hotssh_extradb, priv->new_extradb_contents, len, NULL,
+                                 FALSE, 0, NULL,
+                                 on_replace_extradb_contents_complete, self);
+  return FALSE;
+}
+
+static void
+queue_save_extradb (HotSshHostDB    *self)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  if (priv->idle_save_extradb_id > 0)
+    return;
+  priv->idle_save_extradb_id = g_timeout_add_seconds (5, idle_save_extradb, self);
+}
+
+void
+hotssh_hostdb_host_used (HotSshHostDB *self,
+                         const char   *hostname)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  GtkTreeIter iter;
+  gs_free char *groupname = g_strdup_printf ("host \"%s\"", hostname);
+
+  if (!hostname_to_iter (self, hostname, &iter))
+    return;
+
+  g_key_file_set_uint64 (priv->extradb, groupname, "last-used", g_get_real_time () / G_USEC_PER_SEC);
+  queue_save_extradb (self);
+}
+
+void
+hotssh_hostdb_set_username (HotSshHostDB *self,
+                            const char   *hostname,
+                            const char   *username)
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  GtkTreeIter iter;
+  gs_free char *groupname = g_strdup_printf ("host \"%s\"", hostname);
+
+  if (!hostname_to_iter (self, hostname, &iter))
+    return;
+
+  g_key_file_set_string (priv->extradb, groupname, "username", username);
+
+  queue_save_extradb (self);
+}
+
+static GFileMonitor *
+monitor_file_bind_noerror (HotSshHostDB       *self,
+                           GFile              *path,
+                           void (*callback) (GFileMonitor *, GFile *, GFile *, GFileMonitorEvent, gpointer 
user_data))
+{
+  G_GNUC_UNUSED HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
+  GError *local_error = NULL;
+  GFileMonitor *ret;
+
+  ret = g_file_monitor (path, 0, NULL, &local_error);
+  if (!ret)
+    {
+      g_error ("Failed to monitor '%s': %s",
+               gs_file_get_path_cached (path),
+               local_error->message);
+    }
+  
+  g_signal_connect (ret, "changed", G_CALLBACK (callback), self);
+  callback (ret, NULL, NULL, 0, self);
+  return ret;
+}
+
+static void
 hotssh_hostdb_init (HotSshHostDB *self)
 {
   HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private (self);
@@ -128,8 +444,11 @@ hotssh_hostdb_init (HotSshHostDB *self)
   GError *local_error = NULL;
   gs_free char *knownhosts_path = NULL;
   gs_free char *openssh_path = NULL;
+  gs_free char *hotssh_config_dir = NULL;
+  gs_free char *hotssh_extradb_path = NULL;
 
-  priv->model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT64);
+  priv->model = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_BOOLEAN,
+                                    G_TYPE_STRING);
   homedir = g_get_home_dir ();
   g_assert (homedir);
 
@@ -139,6 +458,11 @@ hotssh_hostdb_init (HotSshHostDB *self)
   priv->openssh_dir = g_file_new_for_path (openssh_path);
   priv->openssh_knownhosts_path = g_file_new_for_path (knownhosts_path);
 
+  hotssh_config_dir = g_build_filename (g_get_user_config_dir (), "hotssh", NULL);
+  (void) g_mkdir_with_parents (hotssh_config_dir, 0700);
+  hotssh_extradb_path = g_build_filename (hotssh_config_dir, "hostdb.ini", NULL);
+  priv->hotssh_extradb = g_file_new_for_path (hotssh_extradb_path);
+
   if (!g_file_query_exists (priv->openssh_dir, NULL))
     {
       if (!g_file_make_directory (priv->openssh_dir, NULL, &local_error))
@@ -149,18 +473,11 @@ hotssh_hostdb_init (HotSshHostDB *self)
         }
     }
   
-  priv->knownhosts_monitor = g_file_monitor (priv->openssh_knownhosts_path, 0, NULL,
-                                             &local_error);
-  if (!priv->knownhosts_monitor)
-    {
-      g_error ("Failed to monitor '%s': %s",
-               gs_file_get_path_cached (priv->openssh_knownhosts_path),
-               local_error->message);
-    }
-  
-  g_signal_connect (priv->knownhosts_monitor, "changed",
-                    G_CALLBACK (on_knownhosts_changed), self);
-  on_knownhosts_changed (priv->knownhosts_monitor, NULL, NULL, 0, self);
+  priv->knownhosts_monitor = monitor_file_bind_noerror (self, priv->openssh_knownhosts_path,
+                                                        on_knownhosts_changed);
+
+  priv->hotssh_extradb_monitor = monitor_file_bind_noerror (self, priv->hotssh_extradb,
+                                                            on_extradb_changed);
 }
 
 static void
@@ -168,6 +485,7 @@ hotssh_hostdb_dispose (GObject *object)
 {
   HotSshHostDBPrivate *priv = hotssh_hostdb_get_instance_private ((HotSshHostDB*)object);
 
+  g_clear_pointer (&priv->extradb, g_key_file_unref);
   g_clear_object (&priv->model);
 
   G_OBJECT_CLASS (hotssh_hostdb_parent_class)->dispose (object);
diff --git a/src/hotssh-hostdb.h b/src/hotssh-hostdb.h
index cc5cdd3..217834d 100644
--- a/src/hotssh-hostdb.h
+++ b/src/hotssh-hostdb.h
@@ -33,4 +33,9 @@ HotSshHostDB           *hotssh_hostdb_get_instance (void);
 
 GtkTreeModel           *hotssh_hostdb_get_model    (HotSshHostDB *self);
 
+void                    hotssh_hostdb_host_used    (HotSshHostDB *self,
+                                                    const char   *hostname);
+void                    hotssh_hostdb_set_username    (HotSshHostDB *self,
+                                                       const char   *hostname,
+                                                       const char   *username);
 
diff --git a/src/hotssh-tab.c b/src/hotssh-tab.c
index a05b6be..be58e53 100644
--- a/src/hotssh-tab.c
+++ b/src/hotssh-tab.c
@@ -658,6 +658,9 @@ on_approve_hostkey_clicked (GtkButton     *button,
   HotSshTab *self = user_data;
   HotSshTabPrivate *priv = hotssh_tab_get_instance_private (self);
 
+  hotssh_hostdb_host_used (hotssh_hostdb_get_instance (),
+                           priv->hostname);
+
   gssh_connection_negotiate_async (priv->connection, priv->cancellable,
                                    on_negotiate_complete, self);
 


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