Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2014 Stef Walter
5 : * Copyright (C) 2018 Red Hat, Inc.
6 : *
7 : * This program is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU Lesser General Public License as
9 : * published by the Free Software Foundation; either version 2.1 of
10 : * the License, or (at your option) any later version.
11 : *
12 : * This program is distributed in the hope that it will be useful, but
13 : * WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : * Lesser General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU Lesser General Public
18 : * License along with this program; if not, see
19 : * <http://www.gnu.org/licenses/>.
20 : *
21 : * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
22 : */
23 :
24 : #include "config.h"
25 :
26 : #include "gkd-ssh-agent-preload.h"
27 : #include "gkd-ssh-agent-util.h"
28 :
29 : #include "egg/egg-file-tracker.h"
30 : #include <string.h>
31 :
32 : enum {
33 : PROP_0,
34 : PROP_PATH
35 : };
36 :
37 : struct _GkdSshAgentPreload
38 : {
39 : GObject object;
40 :
41 : gchar *path;
42 : GHashTable *keys_by_public_filename;
43 : GHashTable *keys_by_public_key;
44 : EggFileTracker *file_tracker;
45 : GMutex lock;
46 : };
47 :
48 0 : G_DEFINE_TYPE (GkdSshAgentPreload, gkd_ssh_agent_preload, G_TYPE_OBJECT);
49 :
50 : void
51 0 : gkd_ssh_agent_key_info_free (gpointer boxed)
52 : {
53 0 : GkdSshAgentKeyInfo *info = boxed;
54 0 : if (!info)
55 0 : return;
56 0 : g_bytes_unref (info->public_key);
57 0 : g_free (info->comment);
58 0 : g_free (info->filename);
59 0 : g_free (info);
60 : }
61 :
62 : gpointer
63 0 : gkd_ssh_agent_key_info_copy (gpointer boxed)
64 : {
65 0 : GkdSshAgentKeyInfo *info = boxed;
66 0 : GkdSshAgentKeyInfo *copy = g_new0 (GkdSshAgentKeyInfo, 1);
67 0 : copy->public_key = g_bytes_ref (info->public_key);
68 0 : copy->comment = g_strdup (info->comment);
69 0 : copy->filename = g_strdup (info->filename);
70 0 : return copy;
71 : }
72 :
73 : static void file_load_inlock (EggFileTracker *tracker,
74 : const gchar *path,
75 : gpointer user_data);
76 : static void file_remove_inlock (EggFileTracker *tracker,
77 : const gchar *path,
78 : gpointer user_data);
79 :
80 : static void
81 0 : gkd_ssh_agent_preload_init (GkdSshAgentPreload *self)
82 : {
83 0 : g_mutex_init (&self->lock);
84 0 : self->keys_by_public_filename = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
85 0 : self->keys_by_public_key = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, NULL, gkd_ssh_agent_key_info_free);
86 0 : }
87 :
88 : static void
89 0 : gkd_ssh_agent_preload_constructed (GObject *object)
90 : {
91 0 : GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (object);
92 :
93 0 : self->file_tracker = egg_file_tracker_new (self->path, "*.pub", NULL);
94 0 : g_signal_connect (self->file_tracker, "file-added", G_CALLBACK (file_load_inlock), self);
95 0 : g_signal_connect (self->file_tracker, "file-removed", G_CALLBACK (file_remove_inlock), self);
96 0 : g_signal_connect (self->file_tracker, "file-changed", G_CALLBACK (file_load_inlock), self);
97 :
98 0 : G_OBJECT_CLASS (gkd_ssh_agent_preload_parent_class)->constructed (object);
99 0 : }
100 :
101 : static void
102 0 : gkd_ssh_agent_preload_set_property (GObject *object,
103 : guint prop_id,
104 : const GValue *value,
105 : GParamSpec *pspec)
106 : {
107 0 : GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (object);
108 :
109 0 : switch (prop_id) {
110 0 : case PROP_PATH:
111 0 : self->path = g_value_dup_string (value);
112 0 : break;
113 0 : default:
114 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
115 0 : break;
116 : }
117 0 : }
118 :
119 : static void
120 0 : gkd_ssh_agent_preload_finalize (GObject *object)
121 : {
122 0 : GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (object);
123 :
124 0 : g_free (self->path);
125 0 : g_clear_pointer (&self->keys_by_public_key, (GDestroyNotify) g_hash_table_unref);
126 0 : g_clear_pointer (&self->keys_by_public_filename, (GDestroyNotify) g_hash_table_unref);
127 0 : g_clear_object (&self->file_tracker);
128 :
129 0 : g_mutex_clear (&self->lock);
130 :
131 0 : G_OBJECT_CLASS (gkd_ssh_agent_preload_parent_class)->finalize (object);
132 0 : }
133 :
134 : static void
135 0 : gkd_ssh_agent_preload_class_init (GkdSshAgentPreloadClass *klass)
136 : {
137 0 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
138 0 : gobject_class->constructed = gkd_ssh_agent_preload_constructed;
139 0 : gobject_class->set_property = gkd_ssh_agent_preload_set_property;
140 0 : gobject_class->finalize = gkd_ssh_agent_preload_finalize;
141 0 : g_object_class_install_property (gobject_class, PROP_PATH,
142 : g_param_spec_string ("path", "Path", "Path",
143 : "",
144 : G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
145 0 : }
146 :
147 : static gchar *
148 0 : private_path_for_public (const gchar *public_path)
149 : {
150 0 : if (g_str_has_suffix (public_path, ".pub"))
151 0 : return g_strndup (public_path, strlen (public_path) - 4);
152 :
153 0 : return NULL;
154 : }
155 :
156 : static GBytes *
157 0 : file_get_contents (const gchar *path,
158 : gboolean must_be_present)
159 : {
160 0 : GError *error = NULL;
161 : gchar *contents;
162 : gsize length;
163 :
164 0 : if (!g_file_get_contents (path, &contents, &length, &error)) {
165 0 : if (must_be_present || error->code != G_FILE_ERROR_NOENT)
166 0 : g_message ("couldn't read file: %s: %s", path, error->message);
167 0 : g_error_free (error);
168 0 : return NULL;
169 : }
170 :
171 0 : return g_bytes_new_take (contents, length);
172 : }
173 :
174 : static void
175 0 : file_remove_inlock (EggFileTracker *tracker,
176 : const gchar *path,
177 : gpointer user_data)
178 : {
179 0 : GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (user_data);
180 : GkdSshAgentKeyInfo *info;
181 :
182 0 : info = g_hash_table_lookup (self->keys_by_public_filename, path);
183 0 : if (info) {
184 0 : g_hash_table_remove (self->keys_by_public_filename, path);
185 0 : g_hash_table_remove (self->keys_by_public_key, info->public_key);
186 : }
187 0 : }
188 :
189 : static void
190 0 : file_load_inlock (EggFileTracker *tracker,
191 : const gchar *path,
192 : gpointer user_data)
193 : {
194 0 : GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (user_data);
195 : gchar *private_path;
196 : GBytes *private_bytes;
197 : GBytes *public_bytes;
198 : GBytes *public_key;
199 : GkdSshAgentKeyInfo *info;
200 : gchar *comment;
201 :
202 0 : file_remove_inlock (tracker, path, user_data);
203 :
204 0 : private_path = private_path_for_public (path);
205 :
206 0 : private_bytes = file_get_contents (private_path, FALSE);
207 0 : if (!private_bytes) {
208 0 : g_debug ("no private key present for public key: %s", path);
209 0 : g_free (private_path);
210 0 : return;
211 : }
212 :
213 0 : public_bytes = file_get_contents (path, TRUE);
214 0 : if (public_bytes) {
215 0 : public_key = _gkd_ssh_agent_parse_public_key (public_bytes, &comment);
216 0 : if (public_key) {
217 0 : info = g_new0 (GkdSshAgentKeyInfo, 1);
218 0 : info->filename = private_path;
219 0 : private_path = NULL;
220 0 : info->public_key = public_key;
221 0 : info->comment = comment;
222 0 : g_hash_table_replace (self->keys_by_public_filename, g_strdup (path), info);
223 0 : g_hash_table_replace (self->keys_by_public_key, info->public_key, info);
224 : } else {
225 0 : g_message ("failed to parse ssh public key: %s", path);
226 : }
227 :
228 0 : g_bytes_unref (public_bytes);
229 : }
230 :
231 0 : g_bytes_unref (private_bytes);
232 0 : g_free (private_path);
233 : }
234 :
235 : GkdSshAgentPreload *
236 0 : gkd_ssh_agent_preload_new (const gchar *path)
237 : {
238 0 : g_return_val_if_fail (path, NULL);
239 :
240 0 : return g_object_new (GKD_TYPE_SSH_AGENT_PRELOAD, "path", path, NULL);
241 : }
242 :
243 : GList *
244 0 : gkd_ssh_agent_preload_get_keys (GkdSshAgentPreload *self)
245 : {
246 0 : GList *keys = NULL;
247 : GHashTableIter iter;
248 : GkdSshAgentKeyInfo *info;
249 :
250 0 : g_mutex_lock (&self->lock);
251 :
252 0 : egg_file_tracker_refresh (self->file_tracker, FALSE);
253 :
254 0 : g_hash_table_iter_init (&iter, self->keys_by_public_key);
255 0 : while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&info))
256 0 : keys = g_list_prepend (keys, gkd_ssh_agent_key_info_copy (info));
257 :
258 0 : g_mutex_unlock (&self->lock);
259 :
260 0 : return keys;
261 : }
262 :
263 : GkdSshAgentKeyInfo *
264 0 : gkd_ssh_agent_preload_lookup_by_public_key (GkdSshAgentPreload *self,
265 : GBytes *public_key)
266 : {
267 : GkdSshAgentKeyInfo *info;
268 :
269 0 : g_mutex_lock (&self->lock);
270 :
271 0 : egg_file_tracker_refresh (self->file_tracker, FALSE);
272 :
273 0 : info = g_hash_table_lookup (self->keys_by_public_key, public_key);
274 0 : if (info)
275 0 : info = gkd_ssh_agent_key_info_copy (info);
276 :
277 0 : g_mutex_unlock (&self->lock);
278 :
279 0 : return info;
280 : }
|