Line data Source code
1 : /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 : /* gkm-secret-textual.c - Textual non-encrypted format for the keyring
3 :
4 : Copyright (C) 2007, 2009 Stefan Walter
5 :
6 : The Gnome Keyring Library is free software; you can redistribute it and/or
7 : modify it under the terms of the GNU Library General Public License as
8 : published by the Free Software Foundation; either version 2 of the
9 : License, or (at your option) any later version.
10 :
11 : The Gnome Keyring Library is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : Library General Public License for more details.
15 :
16 : You should have received a copy of the GNU Library General Public
17 : License along with the Gnome Library; see the file COPYING.LIB. If not,
18 : <http://www.gnu.org/licenses/>.
19 :
20 : Author: Stef Walter <stef@memberwebs.com>
21 : */
22 :
23 : #include "config.h"
24 :
25 : #include "gkm-secret-collection.h"
26 : #include "gkm-secret-compat.h"
27 : #include "gkm-secret-data.h"
28 : #include "gkm-secret-fields.h"
29 : #include "gkm-secret-item.h"
30 : #include "gkm-secret-textual.h"
31 :
32 : #include "egg/egg-error.h"
33 : #include "egg/egg-hex.h"
34 : #include "egg/egg-secure-memory.h"
35 :
36 : #include "gkm/gkm-secret.h"
37 :
38 : #include <glib.h>
39 :
40 : #include <sys/types.h>
41 :
42 : #include <ctype.h>
43 : #include <stdlib.h>
44 : #include <string.h>
45 :
46 : static void
47 29 : key_file_set_uint64 (GKeyFile *file, const gchar *group,
48 : const gchar *key, guint64 value)
49 : {
50 : gchar buffer[64];
51 29 : g_snprintf (buffer, sizeof (buffer), "%llu",
52 : (long long unsigned int)value);
53 29 : g_key_file_set_value (file, group, key, buffer);
54 29 : }
55 :
56 : static gboolean
57 182 : key_file_get_uint64 (GKeyFile *file, const gchar *group,
58 : const gchar *key, guint64 *value)
59 : {
60 : gchar *str, *end;
61 :
62 182 : str = g_key_file_get_value (file, group, key, NULL);
63 182 : if (!str)
64 1 : return FALSE;
65 :
66 181 : *value = g_ascii_strtoull (str, &end, 10);
67 181 : if (end[0]) {
68 1 : g_free (str);
69 1 : return FALSE;
70 : }
71 :
72 180 : g_free (str);
73 180 : return TRUE;
74 : }
75 :
76 : static void
77 5 : generate_attributes (GKeyFile *file, GkmSecretItem *item)
78 : {
79 : GHashTable *attributes;
80 : gchar *groupname;
81 : GList *names, *l;
82 : guint32 number;
83 5 : gint index = 0;
84 :
85 5 : attributes = gkm_secret_item_get_fields (item);
86 5 : g_return_if_fail (attributes);
87 :
88 5 : names = gkm_secret_fields_get_names (attributes);
89 14 : for (l = names; l; l = g_list_next (l)) {
90 9 : groupname = g_strdup_printf ("%s:attribute%d",
91 9 : gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (item)),
92 : index);
93 :
94 9 : g_key_file_set_string (file, groupname, "name", l->data);
95 :
96 : /*
97 : * COMPATIBILITY:
98 : *
99 : * Our new Secrets API doesn't support integer attributes. However, to have
100 : * compatibility with old keyring code reading this file, we need to set
101 : * the type=uint32 attribute appropriately where expected.
102 : *
103 : * If there's an extra compat-uint32 attribute and the name of this attribute
104 : * is contained in that list, then write as a uint32.
105 : */
106 :
107 : /* Determine if it's a uint32 compatible value, and store as such if it is */
108 9 : if (gkm_secret_fields_get_compat_uint32 (attributes, l->data, &number)) {
109 3 : g_key_file_set_string (file, groupname, "type", "uint32");
110 3 : key_file_set_uint64 (file, groupname, "value", number);
111 :
112 : /* A normal string attribute */
113 : } else {
114 6 : g_key_file_set_string (file, groupname, "type", "string");
115 6 : g_key_file_set_string (file, groupname, "value", gkm_secret_fields_get (attributes, l->data));
116 : }
117 :
118 9 : g_free (groupname);
119 9 : ++index;
120 : }
121 5 : g_list_free (names);
122 : }
123 :
124 : static void
125 51 : parse_attributes (GKeyFile *file,
126 : GkmSecretItem *item,
127 : const gchar **groups,
128 : gint compat_type)
129 : {
130 : GHashTable *attributes;
131 : const gchar *identifier;
132 : const gchar **g;
133 : gchar *prefix;
134 : gchar *name, *type;
135 : guint64 number;
136 : const gchar *schema_name;
137 :
138 : /* Now do the attributes */
139 :
140 51 : identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (item));
141 51 : prefix = g_strdup_printf ("%s:attribute", identifier);
142 51 : attributes = gkm_secret_fields_new ();
143 :
144 512 : for (g = groups; *g; ++g) {
145 461 : if (!g_str_has_prefix (*g, prefix))
146 356 : continue;
147 :
148 105 : name = g_key_file_get_string (file, *g, "name", NULL);
149 105 : if (!name)
150 0 : continue;
151 :
152 105 : type = g_key_file_get_string (file, *g, "type", NULL);
153 :
154 : /* A uint32 type value */
155 105 : if (type && g_str_equal (type, "uint32")) {
156 28 : if (key_file_get_uint64 (file, *g, "value", &number))
157 26 : gkm_secret_fields_add_compat_uint32 (attributes, name, number);
158 28 : g_free (name);
159 :
160 : /* A string type value */
161 : } else {
162 77 : gkm_secret_fields_take (attributes, name,
163 : g_key_file_get_string (file, *g, "value", NULL));
164 : }
165 :
166 105 : g_free (type);
167 : }
168 :
169 51 : gkm_secret_item_set_fields (item, attributes);
170 :
171 51 : schema_name = g_hash_table_lookup (attributes, GKM_SECRET_FIELD_SCHEMA);
172 51 : if (schema_name == NULL)
173 50 : schema_name = gkm_secret_compat_format_item_type (compat_type);
174 51 : gkm_secret_item_set_schema (item, schema_name);
175 :
176 51 : g_hash_table_unref (attributes);
177 51 : g_free (prefix);
178 51 : }
179 :
180 : static void
181 5 : generate_acl (GKeyFile *file, GkmSecretItem *item)
182 : {
183 : const gchar *identifier;
184 : GkmSecretAccess *ac;
185 : gchar *groupname;
186 : GList *acl;
187 : gint i;
188 :
189 : /*
190 : * COMPATIBILITY: If we loaded ACLs and they're set on the item,
191 : * then store them back in.
192 : */
193 :
194 5 : identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (item));
195 5 : acl = g_object_get_data (G_OBJECT (item), "compat-acl");
196 5 : for (i = 0; acl != NULL; acl = g_list_next (acl), ++i) {
197 0 : ac = acl->data;
198 :
199 : /* Build a group name */
200 0 : groupname = g_strdup_printf ("%s:acl%d", identifier, i);
201 :
202 0 : if (ac->display_name)
203 0 : g_key_file_set_string (file, groupname, "display-name", ac->display_name);
204 0 : if (ac->pathname)
205 0 : g_key_file_set_string (file, groupname, "path", ac->pathname);
206 :
207 0 : g_key_file_set_boolean (file, groupname, "read-access",
208 0 : ac->types_allowed & GKM_SECRET_ACCESS_READ);
209 0 : g_key_file_set_boolean (file, groupname, "write-access",
210 0 : ac->types_allowed & GKM_SECRET_ACCESS_WRITE);
211 0 : g_key_file_set_boolean (file, groupname, "remove-access",
212 0 : ac->types_allowed & GKM_SECRET_ACCESS_REMOVE);
213 :
214 0 : g_free (groupname);
215 : }
216 5 : }
217 :
218 : static void
219 51 : parse_acl (GKeyFile *file, GkmSecretItem *item, const gchar **groups)
220 : {
221 : GkmSecretAccessType access_type;
222 : GkmSecretAccess *ac;
223 : const gchar *identifier;
224 : const gchar **g;
225 : gchar *prefix;
226 : gchar *path, *display;
227 51 : GError *err = NULL;
228 : GList *acl;
229 :
230 : /*
231 : * COMPATIBILITY: We don't actually use ACLs, but if we find them in the
232 : * file, then load them and save back later.
233 : */
234 :
235 51 : identifier = gkm_secret_object_get_identifier (GKM_SECRET_OBJECT (item));
236 51 : prefix = g_strdup_printf ("%s:acl", identifier);
237 51 : acl = NULL;
238 :
239 512 : for (g = groups; *g; ++g) {
240 461 : if (!g_str_has_prefix (*g, prefix))
241 415 : continue;
242 46 : path = g_key_file_get_string (file, *g, "path", NULL);
243 46 : if (!path)
244 0 : continue;
245 :
246 46 : display = g_key_file_get_string (file, *g, "display-name", NULL);
247 :
248 46 : access_type = 0;
249 :
250 46 : if (g_key_file_get_boolean (file, *g, "read-access", &err) && !err)
251 46 : access_type |= GKM_SECRET_ACCESS_READ;
252 46 : g_clear_error (&err);
253 :
254 46 : if (g_key_file_get_boolean (file, *g, "write-access", &err) && !err)
255 46 : access_type |= GKM_SECRET_ACCESS_WRITE;
256 46 : g_clear_error (&err);
257 :
258 46 : if (g_key_file_get_boolean (file, *g, "remove-access", &err) && !err)
259 46 : access_type |= GKM_SECRET_ACCESS_REMOVE;
260 46 : g_clear_error (&err);
261 :
262 46 : ac = g_new0 (GkmSecretAccess, 1);
263 46 : ac->display_name = display;
264 46 : ac->pathname = path;
265 46 : ac->types_allowed = access_type;
266 :
267 46 : acl = g_list_prepend (acl, ac);
268 : }
269 :
270 51 : g_object_set_data_full (G_OBJECT (item), "compat-acl", acl, gkm_secret_compat_acl_free);
271 51 : g_free (prefix);
272 51 : }
273 :
274 : static void
275 5 : generate_item (GKeyFile *file, GkmSecretItem *item, GkmSecretData *sdata)
276 : {
277 : GkmSecretObject *obj;
278 : const gchar *value;
279 : const gchar *identifier;
280 : const guchar *secret;
281 : gsize n_secret;
282 : gchar *hex;
283 :
284 5 : g_assert (file);
285 5 : g_assert (GKM_IS_SECRET_ITEM (item));
286 5 : g_assert (GKM_IS_SECRET_DATA (sdata));
287 :
288 5 : obj = GKM_SECRET_OBJECT (item);
289 5 : identifier = gkm_secret_object_get_identifier (obj);
290 :
291 5 : value = gkm_secret_item_get_schema (item);
292 5 : g_key_file_set_integer (file, identifier, "item-type",
293 5 : gkm_secret_compat_parse_item_type (value));
294 :
295 5 : value = gkm_secret_object_get_label (obj);
296 5 : if (value != NULL)
297 5 : g_key_file_set_string (file, identifier, "display-name", value);
298 :
299 5 : secret = gkm_secret_data_get_raw (sdata, identifier, &n_secret);
300 5 : if (secret != NULL) {
301 : /* A textual secret. Note that secrets are always null-terminated. */
302 3 : if (g_utf8_validate ((gchar*)secret, n_secret, NULL)) {
303 2 : g_key_file_set_value (file, identifier, "secret", (gchar*)secret);
304 :
305 : /* A non-textual secret */
306 : } else {
307 1 : hex = egg_hex_encode (secret, n_secret);
308 1 : g_key_file_set_value (file, identifier, "binary-secret", hex);
309 1 : g_free (hex);
310 : }
311 : }
312 :
313 5 : key_file_set_uint64 (file, identifier, "mtime", gkm_secret_object_get_modified (obj));
314 5 : key_file_set_uint64 (file, identifier, "ctime", gkm_secret_object_get_created (obj));
315 :
316 5 : generate_attributes (file, item);
317 5 : generate_acl (file, item);
318 5 : }
319 :
320 : static void
321 51 : parse_item (GKeyFile *file, GkmSecretItem *item, GkmSecretData *sdata,
322 : const gchar **groups)
323 : {
324 : GkmSecretObject *obj;
325 : const gchar *identifier;
326 51 : GError *err = NULL;
327 : GkmSecret *secret;
328 : guchar *binary;
329 : gsize n_binary;
330 : gchar *val;
331 : guint64 num;
332 : gint type;
333 :
334 : /* First the main item data */
335 :
336 51 : obj = GKM_SECRET_OBJECT (item);
337 51 : identifier = gkm_secret_object_get_identifier (obj);
338 :
339 51 : type = g_key_file_get_integer (file, identifier, "item-type", &err);
340 51 : if (err) {
341 0 : g_clear_error (&err);
342 0 : type = 0;
343 : }
344 :
345 51 : val = g_key_file_get_string (file, identifier, "display-name", NULL);
346 51 : gkm_secret_object_set_label (obj, val);
347 51 : g_free (val);
348 :
349 51 : if (sdata) {
350 13 : secret = NULL;
351 :
352 : /* A textual secret */
353 13 : val = g_key_file_get_string (file, identifier, "secret", NULL);
354 13 : if (val != NULL) {
355 12 : secret = gkm_secret_new_from_password (val);
356 12 : g_free (val);
357 :
358 : /* A binary secret */
359 : } else {
360 1 : val = g_key_file_get_string (file, identifier, "binary-secret", NULL);
361 1 : if (val != NULL) {
362 1 : binary = egg_hex_decode (val, -1, &n_binary);
363 1 : secret = gkm_secret_new (binary, n_binary);
364 1 : g_free (binary);
365 1 : g_free (val);
366 : }
367 : }
368 :
369 : /* Put the secret in the right place */
370 13 : if (secret == NULL) {
371 0 : gkm_secret_data_remove_secret (sdata, identifier);
372 : } else {
373 13 : gkm_secret_data_set_secret (sdata, identifier, secret);
374 13 : g_object_unref (secret);
375 : }
376 : }
377 :
378 51 : num = 0;
379 51 : if (key_file_get_uint64 (file, identifier, "mtime", &num))
380 51 : gkm_secret_object_set_modified (obj, num);
381 51 : num = 0;
382 51 : if (key_file_get_uint64 (file, identifier, "ctime", &num))
383 51 : gkm_secret_object_set_created (obj, num);
384 :
385 : /* Now the other stuff */
386 51 : parse_attributes (file, item, groups, type);
387 51 : parse_acl (file, item, groups);
388 51 : }
389 :
390 : GkmDataResult
391 8 : gkm_secret_textual_write (GkmSecretCollection *collection, GkmSecretData *sdata,
392 : gpointer *data, gsize *n_data)
393 : {
394 : GkmSecretObject *obj;
395 : GList *items, *l;
396 : const gchar *value;
397 : GKeyFile *file;
398 8 : GError *err = NULL;
399 : gint idle_timeout;
400 :
401 8 : g_return_val_if_fail (GKM_IS_SECRET_COLLECTION (collection), GKM_DATA_FAILURE);
402 8 : g_return_val_if_fail (GKM_IS_SECRET_DATA (sdata), GKM_DATA_LOCKED);
403 8 : g_return_val_if_fail (data && n_data, GKM_DATA_FAILURE);
404 :
405 8 : obj = GKM_SECRET_OBJECT (collection);
406 8 : file = g_key_file_new ();
407 :
408 8 : value = gkm_secret_object_get_label (obj);
409 8 : if (value != NULL)
410 8 : g_key_file_set_string (file, "keyring", "display-name", value);
411 :
412 8 : key_file_set_uint64 (file, "keyring", "ctime", gkm_secret_object_get_created (obj));
413 8 : key_file_set_uint64 (file, "keyring", "mtime", gkm_secret_object_get_modified (obj));
414 :
415 8 : idle_timeout = gkm_secret_collection_get_lock_idle (collection);
416 8 : g_key_file_set_boolean (file, "keyring", "lock-on-idle", idle_timeout > 0);
417 8 : if (idle_timeout)
418 0 : g_key_file_set_integer (file, "keyring", "lock-timeout", idle_timeout);
419 8 : idle_timeout = gkm_secret_collection_get_lock_after (collection);
420 8 : g_key_file_set_boolean (file, "keyring", "lock-after", idle_timeout > 0);
421 8 : if (idle_timeout)
422 0 : g_key_file_set_integer (file, "keyring", "lock-timeout", idle_timeout);
423 :
424 8 : items = gkm_secret_collection_get_items (collection);
425 13 : for (l = items; l; l = g_list_next (l))
426 5 : generate_item (file, l->data, sdata);
427 8 : g_list_free (items);
428 :
429 8 : *data = (guchar*)g_key_file_to_data (file, n_data, &err);
430 8 : g_key_file_free (file);
431 :
432 8 : if (!*data) {
433 0 : g_warning ("couldn't generate textual keyring file: %s", egg_error_message (err));
434 0 : return GKM_DATA_FAILURE;
435 : }
436 :
437 8 : return GKM_DATA_SUCCESS;
438 : }
439 :
440 : static void
441 3 : remove_unavailable_item (gpointer key, gpointer dummy, gpointer user_data)
442 : {
443 : /* Called to remove items from a keyring that no longer exist */
444 :
445 3 : GkmSecretCollection *collection = GKM_SECRET_COLLECTION (user_data);
446 : GkmSecretItem *item;
447 :
448 3 : g_assert (GKM_IS_SECRET_COLLECTION (collection));
449 :
450 3 : item = gkm_secret_collection_get_item (collection, key);
451 3 : if (item != NULL)
452 3 : gkm_secret_collection_remove_item (collection, item);
453 3 : }
454 :
455 : GkmDataResult
456 27 : gkm_secret_textual_read (GkmSecretCollection *collection, GkmSecretData *sdata,
457 : gconstpointer data, gsize n_data)
458 : {
459 : GkmSecretObject *obj;
460 : GkmSecretItem *item;
461 : GList *items, *l;
462 27 : GError *err = NULL;
463 27 : GKeyFile *file = NULL;
464 27 : gchar **groups = NULL;
465 27 : GkmDataResult res = GKM_DATA_FAILURE;
466 27 : gchar *start = NULL;
467 : const gchar *identifier;
468 27 : GHashTable *checks = NULL;
469 : gint lock_timeout;
470 : gchar *value;
471 : guint64 num;
472 : gchar **g;
473 :
474 27 : g_return_val_if_fail (GKM_IS_SECRET_COLLECTION (collection), GKM_DATA_FAILURE);
475 27 : g_return_val_if_fail (!sdata || GKM_IS_SECRET_DATA (sdata), GKM_DATA_FAILURE);
476 :
477 27 : file = g_key_file_new ();
478 27 : obj = GKM_SECRET_OBJECT (collection);
479 :
480 27 : if (!n_data) {
481 0 : res = GKM_DATA_UNRECOGNIZED;
482 0 : goto done;
483 : }
484 :
485 27 : if (!g_key_file_load_from_data (file, (const gchar*)data, n_data, G_KEY_FILE_NONE, &err)) {
486 1 : if (g_error_matches (err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE))
487 1 : res = GKM_DATA_UNRECOGNIZED;
488 1 : goto done;
489 : }
490 :
491 26 : start = g_key_file_get_start_group (file);
492 26 : if (!start || !g_str_equal (start, "keyring")) {
493 0 : g_message ("invalid keyring file: wrong header group");
494 0 : goto done;
495 : }
496 :
497 26 : value = g_key_file_get_string (file, "keyring", "display-name", NULL);
498 26 : gkm_secret_object_set_label (obj, value);
499 26 : g_free (value);
500 :
501 26 : num = 0;
502 26 : key_file_get_uint64 (file, "keyring", "ctime", &num);
503 26 : gkm_secret_object_set_created (obj, num);
504 :
505 26 : num = 0;
506 26 : key_file_get_uint64 (file, "keyring", "mtime", &num);
507 26 : gkm_secret_object_set_modified (obj, num);
508 :
509 : /* Not currently used :( */
510 26 : lock_timeout = g_key_file_get_integer (file, "keyring", "lock-timeout", NULL);
511 26 : if (g_key_file_get_boolean (file, "keyring", "lock-after", NULL))
512 0 : gkm_secret_collection_set_lock_idle (collection, lock_timeout);
513 26 : else if (g_key_file_get_boolean (file, "keyring", "lock-on-idle", NULL))
514 0 : gkm_secret_collection_set_lock_idle (collection, lock_timeout);
515 :
516 26 : g_object_set_data (G_OBJECT (collection), "lock-timeout", GINT_TO_POINTER (lock_timeout));
517 :
518 : /* Build a Hash table where we can track ids we haven't yet seen */
519 26 : checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
520 26 : items = gkm_secret_collection_get_items (collection);
521 36 : for (l = items; l; l = g_list_next (l)) {
522 10 : identifier = gkm_secret_object_get_identifier (l->data);
523 10 : g_hash_table_replace (checks, g_strdup (identifier), "unused");
524 : }
525 26 : g_list_free (items);
526 :
527 26 : groups = g_key_file_get_groups (file, NULL);
528 254 : for (g = groups; *g; ++g) {
529 228 : identifier = *g;
530 228 : if (g_str_equal (identifier, "keyring") || strchr (identifier, ':'))
531 177 : continue;
532 :
533 : /* We've seen this id */
534 51 : g_hash_table_remove (checks, identifier);
535 :
536 51 : item = gkm_secret_collection_get_item (collection, identifier);
537 51 : if (item == NULL)
538 44 : item = gkm_secret_collection_new_item (collection, identifier);
539 51 : parse_item (file, item, sdata, (const gchar**)groups);
540 : }
541 :
542 26 : g_hash_table_foreach (checks, (GHFunc)remove_unavailable_item, collection);
543 26 : res = GKM_DATA_SUCCESS;
544 :
545 27 : done:
546 27 : if (checks)
547 26 : g_hash_table_destroy (checks);
548 27 : if (file)
549 27 : g_key_file_free (file);
550 27 : g_strfreev (groups);
551 27 : g_free (start);
552 27 : g_clear_error (&err);
553 :
554 27 : return res;
555 : }
|