/* * gnome-keyring * * Copyright (C) 2014 Stef Walter * Copyright (C) 2018 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * . * * Author: Stef Walter , Daiki Ueno */ #include "config.h" #include "gcr-ssh-agent-preload.h" #include "gcr-ssh-agent-util.h" #include "egg/egg-file-tracker.h" #include enum { PROP_0, PROP_PATH }; struct _GcrSshAgentPreload { GObject object; gchar *path; GHashTable *keys_by_public_filename; GHashTable *keys_by_public_key; EggFileTracker *file_tracker; GMutex lock; }; G_DEFINE_TYPE (GcrSshAgentPreload, gcr_ssh_agent_preload, G_TYPE_OBJECT); void gcr_ssh_agent_key_info_free (gpointer boxed) { GcrSshAgentKeyInfo *info = boxed; if (!info) return; g_bytes_unref (info->public_key); g_free (info->comment); g_free (info->filename); g_free (info); } gpointer gcr_ssh_agent_key_info_copy (gpointer boxed) { GcrSshAgentKeyInfo *info = boxed; GcrSshAgentKeyInfo *copy = g_new0 (GcrSshAgentKeyInfo, 1); copy->public_key = g_bytes_ref (info->public_key); copy->comment = g_strdup (info->comment); copy->filename = g_strdup (info->filename); return copy; } static void file_load_inlock (EggFileTracker *tracker, const gchar *path, gpointer user_data); static void file_remove_inlock (EggFileTracker *tracker, const gchar *path, gpointer user_data); static void gcr_ssh_agent_preload_init (GcrSshAgentPreload *self) { self->keys_by_public_filename = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); self->keys_by_public_key = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, NULL, gcr_ssh_agent_key_info_free); } static void gcr_ssh_agent_preload_constructed (GObject *object) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object); self->file_tracker = egg_file_tracker_new (self->path, "*.pub", NULL); g_signal_connect (self->file_tracker, "file-added", G_CALLBACK (file_load_inlock), self); g_signal_connect (self->file_tracker, "file-removed", G_CALLBACK (file_remove_inlock), self); g_signal_connect (self->file_tracker, "file-changed", G_CALLBACK (file_load_inlock), self); G_OBJECT_CLASS (gcr_ssh_agent_preload_parent_class)->constructed (object); } static void gcr_ssh_agent_preload_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object); switch (prop_id) { case PROP_PATH: self->path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gcr_ssh_agent_preload_finalize (GObject *object) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object); g_free (self->path); g_clear_pointer (&self->keys_by_public_key, g_hash_table_unref); g_clear_pointer (&self->keys_by_public_filename, g_hash_table_unref); g_clear_object (&self->file_tracker); g_mutex_clear (&self->lock); G_OBJECT_CLASS (gcr_ssh_agent_preload_parent_class)->finalize (object); } static void gcr_ssh_agent_preload_class_init (GcrSshAgentPreloadClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->constructed = gcr_ssh_agent_preload_constructed; gobject_class->set_property = gcr_ssh_agent_preload_set_property; gobject_class->finalize = gcr_ssh_agent_preload_finalize; g_object_class_install_property (gobject_class, PROP_PATH, g_param_spec_string ("path", "Path", "Path", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); } static gchar * private_path_for_public (const gchar *public_path) { if (g_str_has_suffix (public_path, ".pub")) return g_strndup (public_path, strlen (public_path) - 4); return NULL; } static GBytes * file_get_contents (const gchar *path, gboolean must_be_present) { GError *error = NULL; gchar *contents; gsize length; if (!g_file_get_contents (path, &contents, &length, &error)) { if (must_be_present || error->code != G_FILE_ERROR_NOENT) g_message ("couldn't read file: %s: %s", path, error->message); g_error_free (error); return NULL; } return g_bytes_new_take (contents, length); } static void file_remove_inlock (EggFileTracker *tracker, const gchar *path, gpointer user_data) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data); GcrSshAgentKeyInfo *info; info = g_hash_table_lookup (self->keys_by_public_filename, path); if (info) { g_hash_table_remove (self->keys_by_public_filename, path); g_hash_table_remove (self->keys_by_public_key, info->public_key); } } static void file_load_inlock (EggFileTracker *tracker, const gchar *path, gpointer user_data) { GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data); gchar *private_path; GBytes *private_bytes; GBytes *public_bytes; GBytes *public_key; GcrSshAgentKeyInfo *info; gchar *comment; file_remove_inlock (tracker, path, user_data); private_path = private_path_for_public (path); private_bytes = file_get_contents (private_path, FALSE); if (!private_bytes) { g_debug ("no private key present for public key: %s", path); g_free (private_path); return; } public_bytes = file_get_contents (path, TRUE); if (public_bytes) { public_key = _gcr_ssh_agent_parse_public_key (public_bytes, &comment); if (public_key) { info = g_new0 (GcrSshAgentKeyInfo, 1); info->filename = private_path; private_path = NULL; info->public_key = public_key; info->comment = comment; g_hash_table_replace (self->keys_by_public_filename, g_strdup (path), info); g_hash_table_replace (self->keys_by_public_key, info->public_key, info); } else { g_message ("failed to parse ssh public key: %s", path); } g_bytes_unref (public_bytes); } g_bytes_unref (private_bytes); g_free (private_path); } GcrSshAgentPreload * gcr_ssh_agent_preload_new (const gchar *path) { g_return_val_if_fail (path, NULL); return g_object_new (GCR_TYPE_SSH_AGENT_PRELOAD, "path", path, NULL); } GList * gcr_ssh_agent_preload_get_keys (GcrSshAgentPreload *self) { GList *keys = NULL; GHashTableIter iter; GcrSshAgentKeyInfo *info; g_mutex_lock (&self->lock); egg_file_tracker_refresh (self->file_tracker, FALSE); g_hash_table_iter_init (&iter, self->keys_by_public_key); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&info)) keys = g_list_prepend (keys, gcr_ssh_agent_key_info_copy (info)); g_mutex_unlock (&self->lock); return keys; } GcrSshAgentKeyInfo * gcr_ssh_agent_preload_lookup_by_public_key (GcrSshAgentPreload *self, GBytes *public_key) { GcrSshAgentKeyInfo *info; g_mutex_lock (&self->lock); egg_file_tracker_refresh (self->file_tracker, FALSE); info = g_hash_table_lookup (self->keys_by_public_key, public_key); if (info) info = gcr_ssh_agent_key_info_copy (info); g_mutex_unlock (&self->lock); return info; }