Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2008 Stefan Walter
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU Lesser General Public License as
8 : * published by the Free Software Foundation; either version 2.1 of
9 : * the License, or (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful, but
12 : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : * Lesser General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU Lesser General Public
17 : * License along with this program; if not, see
18 : * <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "config.h"
22 :
23 : #include "gkm-gnome2-file.h"
24 :
25 : #include "gkm/gkm-attributes.h"
26 : #include "gkm/gkm-crypto.h"
27 : #include "gkm/gkm-data-types.h"
28 : #include "gkm/gkm-util.h"
29 :
30 : #include "gkm-marshal.h"
31 :
32 : #include "egg/egg-buffer.h"
33 : #include "egg/egg-hex.h"
34 : #include "egg/egg-secure-memory.h"
35 : #include "egg/egg-symkey.h"
36 :
37 : #include <glib/gstdio.h>
38 :
39 : #include <errno.h>
40 : #include <stdlib.h>
41 : #include <string.h>
42 : #include <unistd.h>
43 :
44 : #include <gcrypt.h>
45 :
46 : enum {
47 : ENTRY_ADDED,
48 : ENTRY_CHANGED,
49 : ENTRY_REMOVED,
50 : LAST_SIGNAL
51 : };
52 :
53 : static guint signals[LAST_SIGNAL] = { 0 };
54 :
55 : struct _GkmGnome2File {
56 : GObject parent;
57 :
58 : /* The data itself */
59 : GHashTable *identifiers;
60 : GHashTable *privates;
61 : GHashTable *publics;
62 : GList *unknowns;
63 :
64 : /* All the sections seen */
65 : guint sections;
66 : gboolean incomplete;
67 :
68 : /* Stuff notseen on this read */
69 : GHashTable *checks;
70 : };
71 :
72 : typedef struct _UnknownBlock {
73 : guint type;
74 : EggBuffer buffer;
75 : } UnknownBlock;
76 :
77 845 : G_DEFINE_TYPE (GkmGnome2File, gkm_gnome2_file, G_TYPE_OBJECT);
78 :
79 : #define PUBLIC_ALLOC (EggBufferAllocator)g_realloc
80 : #define PRIVATE_ALLOC (EggBufferAllocator)egg_secure_realloc
81 :
82 : typedef GkmDataResult (*BlockFunc) (guint block, EggBuffer *buffer, GkmSecret *login, gpointer user_data);
83 :
84 : #define FILE_HEADER ((const guchar*)"Gnome Keyring Store 2\n\r\0")
85 : #define FILE_HEADER_LEN 24
86 :
87 : #define FILE_BLOCK_INDEX 0x49445832 /* ie: "IDX2" */
88 : #define FILE_BLOCK_PRIVATE 0x50525632 /* ie: "PRV2" */
89 : #define FILE_BLOCK_PUBLIC 0x50554232 /* ie: "PUB2" */
90 :
91 : #define UNUSED_VALUE GUINT_TO_POINTER (1)
92 :
93 36 : EGG_SECURE_DECLARE (data_file);
94 :
95 : /* -----------------------------------------------------------------------------
96 : * HELPERS
97 : */
98 :
99 : static void
100 50 : attribute_free (gpointer data)
101 : {
102 50 : CK_ATTRIBUTE_PTR attr = data;
103 50 : if (attr) {
104 50 : g_free (attr->pValue);
105 50 : g_slice_free (CK_ATTRIBUTE, attr);
106 : }
107 50 : }
108 :
109 : static CK_ATTRIBUTE_PTR
110 50 : attribute_dup (CK_ATTRIBUTE_PTR attr)
111 : {
112 : CK_ATTRIBUTE_PTR copy;
113 50 : g_assert (attr);
114 50 : copy = g_slice_new (CK_ATTRIBUTE);
115 50 : copy->ulValueLen = attr->ulValueLen;
116 50 : copy->pValue = g_memdup (attr->pValue, copy->ulValueLen);
117 50 : copy->type = attr->type;
118 50 : return copy;
119 : }
120 :
121 : static GHashTable*
122 56 : attributes_new (void)
123 : {
124 56 : return g_hash_table_new_full (gkm_util_ulong_hash, gkm_util_ulong_equal, NULL, attribute_free);
125 : }
126 :
127 : static GHashTable*
128 124 : entries_new (void)
129 : {
130 124 : return g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_hash_table_unref);
131 : }
132 :
133 : static gboolean
134 241 : read_all_bytes (int fd, guchar *buf, gsize len)
135 : {
136 241 : gsize all = len;
137 : int res;
138 :
139 432 : while (len > 0) {
140 241 : res = read (fd, buf, len);
141 241 : if (res < 0) {
142 0 : if (errno == EAGAIN || errno == EINTR)
143 0 : continue;
144 0 : g_warning ("couldn't read %u bytes from store file: %s",
145 : (guint)all, g_strerror (errno));
146 0 : return FALSE;
147 241 : } else if (res == 0) {
148 50 : if (len != all)
149 0 : g_warning ("couldn't read %u bytes from store file", (guint)all);
150 50 : return FALSE;
151 : } else {
152 191 : len -= res;
153 191 : buf += res;
154 : }
155 : }
156 :
157 191 : return TRUE;
158 : }
159 :
160 : static gboolean
161 79 : write_all_bytes (int fd, const guchar *buf, gsize len)
162 : {
163 79 : gsize all = len;
164 : int res;
165 :
166 158 : while (len > 0) {
167 :
168 79 : res = write (fd, buf, len);
169 79 : if (res < 0) {
170 0 : if (errno == EAGAIN || errno == EINTR)
171 0 : continue;
172 0 : g_warning ("couldn't write %u bytes to store file: %s",
173 : (guint)all, g_strerror (errno));
174 0 : return FALSE;
175 79 : } else if (res == 0) {
176 0 : g_warning ("couldn't write %u bytes to store file", (guint)all);
177 0 : return FALSE;
178 : } else {
179 79 : len -= res;
180 79 : buf += res;
181 : }
182 : }
183 :
184 79 : return TRUE;
185 : }
186 :
187 : static GkmDataResult
188 50 : parse_file_blocks (int file, BlockFunc block_func, GkmSecret *login, gpointer user_data)
189 : {
190 : gchar header[FILE_HEADER_LEN];
191 : GkmDataResult res;
192 : EggBuffer buffer;
193 : guint32 block;
194 : guint32 length;
195 : gsize offset;
196 :
197 50 : g_assert (file != -1);
198 50 : g_assert (block_func);
199 :
200 : /* Zero length file is valid */
201 50 : if (!read_all_bytes (file, (guchar*)header, FILE_HEADER_LEN))
202 17 : return TRUE;
203 :
204 : /* Check the header */
205 33 : if (memcmp (header, FILE_HEADER, FILE_HEADER_LEN) != 0) {
206 0 : g_message ("invalid header in store file");
207 0 : return FALSE;
208 : }
209 :
210 33 : egg_buffer_init_full (&buffer, 1024, (EggBufferAllocator)g_realloc);
211 :
212 33 : res = GKM_DATA_SUCCESS;
213 : for (;;) {
214 :
215 112 : egg_buffer_reset (&buffer);
216 112 : egg_buffer_resize (&buffer, 8);
217 112 : offset = 0;
218 :
219 : /* Read in a set of bytes */
220 112 : if (!read_all_bytes (file, buffer.buf, 8)) {
221 33 : res = GKM_DATA_SUCCESS; /* end of file */
222 33 : break;
223 : }
224 :
225 : /* Decode it as the number of bytes in the next section */
226 158 : if (!egg_buffer_get_uint32 (&buffer, offset, &offset, &length) ||
227 79 : !egg_buffer_get_uint32 (&buffer, offset, &offset, &block) ||
228 79 : length < 8) {
229 0 : res = GKM_DATA_FAILURE;
230 0 : g_message ("invalid block size or length in store file");
231 0 : break;
232 : }
233 :
234 : /* Read in that amount of bytes */
235 79 : egg_buffer_resize (&buffer, length - 8);
236 79 : if (!read_all_bytes (file, buffer.buf, length - 8)) {
237 0 : res = GKM_DATA_FAILURE;
238 0 : break;
239 : }
240 :
241 79 : res = (block_func) (block, &buffer, login, user_data);
242 79 : if (res != GKM_DATA_SUCCESS)
243 0 : break;
244 : }
245 :
246 33 : egg_buffer_uninit (&buffer);
247 33 : return res;
248 : }
249 :
250 : static gboolean
251 33 : write_file_block (int file, guint block, EggBuffer *buffer)
252 : {
253 : EggBuffer header;
254 : gboolean ret;
255 :
256 33 : g_assert (file != -1);
257 33 : g_assert (buffer);
258 :
259 : /* Write out the 8 bytes of header */
260 33 : egg_buffer_init_full (&header, 8, (EggBufferAllocator)g_realloc);
261 33 : egg_buffer_add_uint32 (&header, buffer->len + 8);
262 33 : egg_buffer_add_uint32 (&header, block);
263 33 : g_assert (!egg_buffer_has_error (&header));
264 33 : g_assert (header.len == 8);
265 33 : ret = write_all_bytes (file, header.buf, header.len);
266 33 : egg_buffer_uninit (&header);
267 :
268 33 : if (ret != TRUE)
269 0 : return FALSE;
270 :
271 : /* Now write out the remainder of the data */
272 33 : return write_all_bytes (file, buffer->buf, buffer->len);
273 : }
274 :
275 : static gboolean
276 19 : hash_buffer (EggBuffer *buffer)
277 : {
278 : const gchar *salgo;
279 : gsize length;
280 : guchar *hash;
281 : gsize n_hash;
282 : int algo;
283 :
284 : /* The length needs to be the first thing in the buffer */
285 19 : g_assert (buffer->len > 4);
286 19 : g_assert (egg_buffer_decode_uint32 (buffer->buf) == buffer->len);
287 :
288 19 : length = buffer->len;
289 :
290 19 : algo = GCRY_MD_SHA256;
291 19 : salgo = gcry_md_algo_name (algo);
292 19 : g_return_val_if_fail (salgo, FALSE);
293 19 : n_hash = gcry_md_get_algo_dlen (algo);
294 19 : g_return_val_if_fail (n_hash > 0, FALSE);
295 :
296 19 : egg_buffer_add_string (buffer, salgo);
297 19 : hash = egg_buffer_add_byte_array_empty (buffer, n_hash);
298 19 : g_return_val_if_fail (hash, FALSE);
299 :
300 19 : gcry_md_hash_buffer (algo, hash, buffer->buf, length);
301 19 : return TRUE;
302 : }
303 :
304 : static gboolean
305 43 : validate_buffer (EggBuffer *buffer, gsize *offset)
306 : {
307 : const guchar *hash;
308 : gchar *salgo, *check;
309 : gsize n_hash, hash_offset;
310 : guint32 length;
311 : int algo;
312 : gboolean valid;
313 :
314 43 : g_assert (buffer);
315 43 : g_assert (offset);
316 :
317 43 : *offset = 0;
318 :
319 86 : if (!egg_buffer_get_uint32 (buffer, *offset, offset, &length) ||
320 43 : !egg_buffer_get_string (buffer, length, &hash_offset, &salgo, PUBLIC_ALLOC))
321 0 : return FALSE;
322 :
323 43 : algo = gcry_md_map_name (salgo);
324 43 : if (algo == 0) {
325 0 : g_warning ("unsupported hash algorithm: %s", salgo);
326 0 : g_free (salgo);
327 0 : return FALSE;
328 : }
329 43 : g_free (salgo);
330 :
331 43 : if (!egg_buffer_get_byte_array (buffer, hash_offset, &hash_offset, &hash, &n_hash))
332 0 : return FALSE;
333 :
334 43 : if (n_hash != gcry_md_get_algo_dlen (algo)) {
335 0 : g_warning ("invalid hash length in store file");
336 0 : return FALSE;
337 : }
338 :
339 43 : check = g_malloc0 (n_hash);
340 43 : gcry_md_hash_buffer (algo, check, buffer->buf, length);
341 43 : valid = (memcmp (check, hash, n_hash) == 0);
342 43 : g_free (check);
343 :
344 43 : return valid;
345 : }
346 :
347 : static gboolean
348 18 : create_cipher (GkmSecret *login, int calgo, int halgo, const guchar *salt,
349 : gsize n_salt, guint iterations, gcry_cipher_hd_t *cipher)
350 : {
351 : gsize n_key, n_block;
352 : const gchar *password;
353 : gsize n_password;
354 : guchar *key, *iv;
355 : gcry_error_t gcry;
356 :
357 18 : g_assert (login);
358 18 : g_assert (salt);
359 18 : g_assert (cipher);
360 :
361 18 : n_key = gcry_cipher_get_algo_keylen (calgo);
362 18 : g_return_val_if_fail (n_key, FALSE);
363 18 : n_block = gcry_cipher_get_algo_blklen (calgo);
364 18 : g_return_val_if_fail (n_block, FALSE);
365 :
366 18 : password = gkm_secret_get_password (login, &n_password);
367 :
368 18 : if (!egg_symkey_generate_simple (calgo, halgo, password, n_password,
369 : salt, n_salt, iterations, &key, &iv)) {
370 0 : return FALSE;
371 : }
372 :
373 18 : gcry = gcry_cipher_open (cipher, calgo, GCRY_CIPHER_MODE_CBC, 0);
374 18 : if (gcry) {
375 0 : g_warning ("couldn't create cipher context: %s", gcry_strerror (gcry));
376 0 : egg_secure_free (key);
377 0 : g_free (iv);
378 0 : return FALSE;
379 : }
380 :
381 18 : gcry = gcry_cipher_setkey (*cipher, key, n_key);
382 18 : g_return_val_if_fail (!gcry, FALSE);
383 18 : egg_secure_free (key);
384 :
385 18 : gcry = gcry_cipher_setiv (*cipher, iv, n_block);
386 18 : g_return_val_if_fail (!gcry, FALSE);
387 18 : g_free (iv);
388 :
389 18 : return TRUE;
390 : }
391 :
392 : static gboolean
393 8 : encrypt_buffer (EggBuffer *input, GkmSecret *login, EggBuffer *output)
394 : {
395 : gcry_cipher_hd_t cipher;
396 : gcry_error_t gcry;
397 : guchar salt[8];
398 : guint32 iterations;
399 : int calgo, halgo;
400 : const gchar *salgo;
401 : guchar *dest;
402 : gsize n_block;
403 :
404 8 : g_assert (input);
405 8 : g_assert (output);
406 8 : g_assert (login);
407 :
408 : /* The algorithms we're going to use */
409 8 : calgo = GCRY_CIPHER_AES128;
410 8 : halgo = GCRY_MD_SHA256;
411 :
412 : /* Prepare us some salt */
413 8 : gcry_create_nonce (salt, sizeof (salt));
414 :
415 : /* Prepare us the iterations */
416 8 : iterations = 1000 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
417 :
418 : /* Write out crypto algorithm */
419 8 : salgo = gcry_cipher_algo_name (calgo);
420 8 : g_return_val_if_fail (salgo, FALSE);
421 8 : egg_buffer_add_string (output, salgo);
422 :
423 : /* Write out the hash algorithm */
424 8 : salgo = gcry_md_algo_name (halgo);
425 8 : g_return_val_if_fail (halgo, FALSE);
426 8 : egg_buffer_add_string (output, salgo);
427 :
428 : /* Write out the iterations */
429 8 : egg_buffer_add_uint32 (output, iterations);
430 :
431 : /* And write out the salt */
432 8 : egg_buffer_add_byte_array (output, salt, sizeof (salt));
433 :
434 : /* Okay now use the above info to create our cipher context */
435 8 : if (!create_cipher (login, calgo, halgo, salt, sizeof (salt), iterations, &cipher))
436 0 : return FALSE;
437 :
438 : /* Significant block sizes */
439 8 : n_block = gcry_cipher_get_algo_blklen (calgo);
440 8 : g_return_val_if_fail (n_block, FALSE);
441 :
442 : /* Pad the buffer to a multiple of block length */
443 58 : while (input->len % n_block != 0)
444 50 : egg_buffer_add_byte (input, 0);
445 :
446 : /* Now reserve space for it in the output block, and encrypt */
447 8 : dest = egg_buffer_add_byte_array_empty (output, input->len);
448 8 : g_return_val_if_fail (dest, FALSE);
449 :
450 8 : gcry = gcry_cipher_encrypt (cipher, dest, input->len, input->buf, input->len);
451 8 : g_return_val_if_fail (!gcry, FALSE);
452 :
453 8 : gcry_cipher_close (cipher);
454 :
455 8 : return TRUE;
456 : }
457 :
458 : static gboolean
459 10 : decrypt_buffer (EggBuffer *input, gsize *offset, GkmSecret *login, EggBuffer *output)
460 : {
461 : gcry_cipher_hd_t cipher;
462 : gcry_error_t gcry;
463 : const guchar *salt, *data;
464 : gsize n_block, n_salt, n_data;
465 : guint32 iterations;
466 : int calgo, halgo;
467 : gchar *salgo;
468 :
469 10 : g_assert (input);
470 10 : g_assert (output);
471 10 : g_assert (offset);
472 10 : g_assert (login);
473 :
474 : /* Read in and interpret the cipher algorithm */
475 10 : if (!egg_buffer_get_string (input, *offset, offset, &salgo, NULL))
476 0 : return FALSE;
477 10 : calgo = gcry_cipher_map_name (salgo);
478 10 : if (!calgo) {
479 0 : g_warning ("unsupported crypto algorithm: %s", salgo);
480 0 : g_free (salgo);
481 0 : return FALSE;
482 : }
483 10 : g_free (salgo);
484 :
485 : /* Read in and interpret the hash algorithm */
486 10 : if (!egg_buffer_get_string (input, *offset, offset, &salgo, NULL))
487 0 : return FALSE;
488 10 : halgo = gcry_md_map_name (salgo);
489 10 : if (!halgo) {
490 0 : g_warning ("unsupported crypto algorithm: %s", salgo);
491 0 : g_free (salgo);
492 0 : return FALSE;
493 : }
494 10 : g_free (salgo);
495 :
496 : /* Read in the iterations, salt, and encrypted data */
497 20 : if (!egg_buffer_get_uint32 (input, *offset, offset, &iterations) ||
498 20 : !egg_buffer_get_byte_array (input, *offset, offset, &salt, &n_salt) ||
499 10 : !egg_buffer_get_byte_array (input, *offset, offset, &data, &n_data))
500 0 : return FALSE;
501 :
502 : /* Significant block sizes */
503 10 : n_block = gcry_cipher_get_algo_blklen (calgo);
504 10 : g_return_val_if_fail (n_block, FALSE);
505 :
506 : /* Make sure the encrypted data is of a good length */
507 10 : if (n_data % n_block != 0) {
508 0 : g_warning ("encrypted data in file store is of an invalid length for algorithm");
509 0 : return FALSE;
510 : }
511 :
512 : /* Create the cipher context */
513 10 : if (!create_cipher (login, calgo, halgo, salt, n_salt, iterations, &cipher))
514 0 : return FALSE;
515 :
516 : /* Now reserve space for it in the output block, and encrypt */
517 10 : egg_buffer_reset (output);
518 10 : egg_buffer_resize (output, n_data);
519 :
520 10 : gcry = gcry_cipher_decrypt (cipher, output->buf, output->len, data, n_data);
521 10 : g_return_val_if_fail (!gcry, FALSE);
522 :
523 10 : gcry_cipher_close (cipher);
524 :
525 10 : return TRUE;
526 : }
527 :
528 : /* ----------------------------------------------------------------------------------------
529 : * INTERNAL
530 : */
531 :
532 : static GkmDataResult
533 43 : update_entries_from_block (GkmGnome2File *self, guint section, GHashTable *entries,
534 : EggBuffer *buffer, gsize *offset)
535 : {
536 : GHashTable *attributes;
537 : const gchar *identifier;
538 : gboolean added;
539 : CK_ATTRIBUTE_PTR at;
540 : CK_ATTRIBUTE attr;
541 : gpointer key, value;
542 : guint32 n_entries, i;
543 : guint32 n_attrs, j;
544 : gchar *str;
545 : guint sect;
546 : const guchar *data;
547 : gsize n_data;
548 : guint64 type;
549 :
550 43 : g_assert (GKM_IS_GNOME2_FILE (self));
551 43 : g_assert (entries);
552 43 : g_assert (buffer);
553 43 : g_assert (offset);
554 :
555 : /* The number of entries */
556 43 : if (!egg_buffer_get_uint32 (buffer, *offset, offset, &n_entries))
557 0 : return GKM_DATA_FAILURE;
558 :
559 99 : for (i = 0; i < n_entries; ++i) {
560 :
561 56 : added = FALSE;
562 :
563 : /* The attributes */
564 56 : if (!egg_buffer_get_string (buffer, *offset, offset, &str, (EggBufferAllocator)g_realloc))
565 0 : return GKM_DATA_FAILURE;
566 :
567 : /* Make sure we have this one */
568 56 : sect = GPOINTER_TO_UINT (g_hash_table_lookup (self->identifiers, str));
569 56 : if (sect != section) {
570 0 : g_message ("data file entry in wrong section: %s", str);
571 0 : g_free (str);
572 0 : return GKM_DATA_FAILURE;
573 : }
574 :
575 : /* Lookup or create a new table for it */
576 56 : if (!g_hash_table_lookup_extended (entries, str, &key, &value)) {
577 35 : added = TRUE;
578 35 : value = attributes_new ();
579 35 : key = g_strdup (str);
580 35 : g_hash_table_replace (entries, key, value);
581 : }
582 :
583 56 : g_free (str);
584 56 : identifier = key;
585 56 : attributes = value;
586 :
587 56 : if (!egg_buffer_get_uint32 (buffer, *offset, offset, &n_attrs))
588 0 : return GKM_DATA_FAILURE;
589 :
590 109 : for (j = 0; j < n_attrs; ++j) {
591 106 : if (!egg_buffer_get_uint64 (buffer, *offset, offset, &type) ||
592 53 : !egg_buffer_get_byte_array (buffer, *offset, offset, &data, &n_data))
593 0 : return GKM_DATA_FAILURE;
594 :
595 53 : attr.type = type;
596 53 : attr.pValue = (CK_VOID_PTR)data;
597 53 : attr.ulValueLen = n_data;
598 :
599 53 : at = g_hash_table_lookup (attributes, &attr.type);
600 53 : if (at != NULL && gkm_attribute_equal (&attr, at))
601 21 : continue;
602 :
603 32 : at = attribute_dup (&attr);
604 32 : g_hash_table_replace (attributes, &(at->type), at);
605 :
606 : /* Only emit the changed signal if we haven't just added this one */
607 32 : if (added == FALSE)
608 4 : g_signal_emit (self, signals[ENTRY_CHANGED], 0, identifier, attr.type);
609 : }
610 :
611 : /* A new entry was loaded */
612 56 : if (added == TRUE)
613 35 : g_signal_emit (self, signals[ENTRY_ADDED], 0, identifier);
614 : }
615 :
616 43 : return GKM_DATA_SUCCESS;
617 : }
618 :
619 : static GkmDataResult
620 33 : update_from_public_block (GkmGnome2File *self, EggBuffer *buffer)
621 : {
622 33 : gsize offset = 0;
623 :
624 33 : g_assert (GKM_IS_GNOME2_FILE (self));
625 33 : g_assert (buffer);
626 :
627 33 : self->sections |= GKM_GNOME2_FILE_SECTION_PUBLIC;
628 :
629 : /* Validate the buffer hash, failure in this case is corruption */
630 33 : if (!validate_buffer (buffer, &offset))
631 0 : return GKM_DATA_FAILURE;
632 :
633 33 : return update_entries_from_block (self, GKM_GNOME2_FILE_SECTION_PUBLIC,
634 : self->publics, buffer, &offset);
635 : }
636 :
637 : static GkmDataResult
638 13 : update_from_private_block (GkmGnome2File *self, EggBuffer *buffer, GkmSecret *login)
639 : {
640 : EggBuffer custom;
641 : GkmDataResult res;
642 : gsize offset;
643 :
644 13 : g_assert (GKM_IS_GNOME2_FILE (self));
645 13 : g_assert (buffer);
646 :
647 13 : self->sections |= GKM_GNOME2_FILE_SECTION_PRIVATE;
648 :
649 : /* Skip private blocks when not unlocked */
650 13 : if (login == NULL) {
651 3 : if (self->privates)
652 3 : g_hash_table_destroy (self->privates);
653 3 : self->privates = NULL;
654 3 : return GKM_DATA_UNRECOGNIZED;
655 : }
656 :
657 10 : offset = 0;
658 10 : egg_buffer_init_full (&custom, 1024, egg_secure_realloc);
659 :
660 : /* Decrypt the buffer */
661 10 : if (!decrypt_buffer (buffer, &offset, login, &custom)) {
662 0 : egg_buffer_uninit (&custom);
663 0 : return GKM_DATA_FAILURE;
664 : }
665 :
666 10 : offset = 0;
667 :
668 : /* Validate the buffer hash, failure is usually a bad password */
669 10 : if (!validate_buffer (&custom, &offset)) {
670 0 : egg_buffer_uninit (&custom);
671 0 : return GKM_DATA_LOCKED;
672 : }
673 :
674 : /* We're loading privates, so fill that in */
675 10 : if (!self->privates)
676 1 : self->privates = entries_new ();
677 :
678 10 : res = update_entries_from_block (self, GKM_GNOME2_FILE_SECTION_PRIVATE,
679 : self->privates, &custom, &offset);
680 10 : egg_buffer_uninit (&custom);
681 10 : return res;
682 : }
683 :
684 : static void
685 30 : copy_each_identifier (gpointer key, gpointer value, gpointer data)
686 : {
687 30 : g_hash_table_insert (data, g_strdup (key), UNUSED_VALUE);
688 30 : }
689 :
690 : static void
691 7 : remove_each_identifier (gpointer key, gpointer value, gpointer data)
692 : {
693 7 : GkmGnome2File *self = GKM_GNOME2_FILE (data);
694 : GHashTable *entries;
695 : guint section;
696 :
697 7 : g_assert (GKM_IS_GNOME2_FILE (self));
698 7 : g_assert (key);
699 :
700 7 : if (!gkm_gnome2_file_lookup_entry (self, key, §ion))
701 0 : g_assert_not_reached ();
702 :
703 7 : if (section == GKM_GNOME2_FILE_SECTION_PRIVATE)
704 5 : entries = self->privates;
705 : else
706 2 : entries = self->publics;
707 :
708 7 : if (!g_hash_table_remove (self->identifiers, key))
709 0 : g_assert_not_reached ();
710 :
711 7 : if (entries != NULL) {
712 5 : if (!g_hash_table_remove (entries, key))
713 0 : g_return_if_reached ();
714 :
715 : /*
716 : * Note that we only fire the removed signal when the identifier
717 : * was accessible. We don't fire removed for private items in
718 : * a locked file.
719 : */
720 5 : g_signal_emit (self, signals[ENTRY_REMOVED], 0, key);
721 : }
722 : }
723 :
724 : static GkmDataResult
725 33 : update_from_index_block (GkmGnome2File *self, EggBuffer *buffer)
726 : {
727 : gchar *identifier;
728 : gsize offset;
729 : guint section;
730 : guint count, i;
731 : guint value;
732 :
733 33 : g_assert (GKM_IS_GNOME2_FILE (self));
734 33 : g_assert (buffer);
735 :
736 33 : offset = 0;
737 :
738 : /* The number of entries */
739 33 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &count))
740 0 : return FALSE;
741 :
742 95 : for (i = 0; i < count; ++i) {
743 :
744 : /* The identifier */
745 62 : if (!egg_buffer_get_string (buffer, offset, &offset, &identifier, (EggBufferAllocator)g_realloc))
746 0 : break;
747 :
748 : /* The section */
749 62 : if (!egg_buffer_get_uint32 (buffer, offset, &offset, &value)) {
750 0 : g_free (identifier);
751 0 : break;
752 : }
753 :
754 62 : section = value;
755 62 : g_hash_table_replace (self->identifiers, identifier, GUINT_TO_POINTER (section));
756 :
757 : /* Track that we've seen this identifier */
758 62 : g_hash_table_remove (self->checks, identifier);
759 : }
760 :
761 : /* Completed reading all */
762 33 : if (i == count)
763 33 : return GKM_DATA_SUCCESS;
764 :
765 : /* Failed for some reason, data is bad */
766 0 : return GKM_DATA_FAILURE;
767 : }
768 :
769 : static GkmDataResult
770 79 : update_from_any_block (guint block, EggBuffer *buffer, GkmSecret *login, gpointer user_data)
771 : {
772 : UnknownBlock *unknown;
773 : GkmGnome2File *self;
774 : GkmDataResult res;
775 :
776 79 : g_assert (GKM_IS_GNOME2_FILE (user_data));
777 79 : self = GKM_GNOME2_FILE (user_data);
778 :
779 79 : switch (block) {
780 33 : case FILE_BLOCK_INDEX:
781 33 : res = update_from_index_block (self, buffer);
782 33 : break;
783 13 : case FILE_BLOCK_PRIVATE:
784 13 : res = update_from_private_block (self, buffer, login);
785 13 : break;
786 33 : case FILE_BLOCK_PUBLIC:
787 33 : res = update_from_public_block (self, buffer);
788 33 : break;
789 0 : default:
790 0 : res = GKM_DATA_UNRECOGNIZED;
791 0 : break;
792 : };
793 :
794 : /* If unrecognized data block, then stash as unknown */
795 79 : if (res == GKM_DATA_UNRECOGNIZED) {
796 3 : unknown = g_slice_new0 (UnknownBlock);
797 3 : unknown->type = block;
798 3 : egg_buffer_init_full (&unknown->buffer, buffer->len, PUBLIC_ALLOC);
799 3 : egg_buffer_append (&unknown->buffer, buffer->buf, buffer->len);
800 3 : self->unknowns = g_list_prepend (self->unknowns, unknown);
801 3 : res = GKM_DATA_SUCCESS;
802 : }
803 :
804 79 : return res;
805 : }
806 :
807 : static void
808 22 : write_each_attribute (gpointer key, gpointer value, gpointer data)
809 : {
810 22 : CK_ATTRIBUTE_PTR attr = value;
811 22 : EggBuffer *buffer = data;
812 22 : egg_buffer_add_uint64 (buffer, attr->type);
813 22 : g_assert (attr->ulValueLen != (gulong)-1);
814 22 : egg_buffer_add_byte_array (buffer, attr->pValue, attr->ulValueLen);
815 22 : }
816 :
817 : static void
818 15 : write_each_entry (gpointer key, gpointer value, gpointer data)
819 : {
820 15 : EggBuffer *buffer = data;
821 15 : const gchar *unique = key;
822 15 : GHashTable *attributes = value;
823 :
824 15 : egg_buffer_add_string (buffer, unique);
825 15 : egg_buffer_add_uint32 (buffer, g_hash_table_size (attributes));
826 15 : g_hash_table_foreach (attributes, write_each_attribute, buffer);
827 15 : }
828 :
829 : static GkmDataResult
830 19 : write_entries_to_block (GkmGnome2File *self, GHashTable *entries, EggBuffer *buffer)
831 : {
832 : gsize offset;
833 :
834 19 : g_assert (GKM_GNOME2_FILE (self));
835 19 : g_assert (entries);
836 19 : g_assert (buffer);
837 :
838 : /* Reserve space for the length */
839 19 : offset = buffer->len;
840 19 : egg_buffer_add_uint32 (buffer, 0);
841 :
842 : /* The number of attributes we'll be encountering */
843 19 : egg_buffer_add_uint32 (buffer, g_hash_table_size (entries));
844 :
845 : /* Fill in the attributes */
846 19 : g_hash_table_foreach (entries, write_each_entry, buffer);
847 :
848 19 : g_return_val_if_fail (!egg_buffer_has_error (buffer), GKM_DATA_FAILURE);
849 :
850 : /* Fill in the length */
851 19 : egg_buffer_set_uint32 (buffer, offset, buffer->len);
852 :
853 : /* Hash the entire dealio */
854 19 : if (!hash_buffer (buffer))
855 0 : return GKM_DATA_FAILURE;
856 :
857 19 : return GKM_DATA_SUCCESS;
858 : }
859 :
860 : static GkmDataResult
861 13 : write_private_to_block (GkmGnome2File *self, EggBuffer *buffer, GkmSecret *login)
862 : {
863 : EggBuffer secure;
864 : GkmDataResult res;
865 :
866 13 : g_assert (GKM_IS_GNOME2_FILE (self));
867 13 : g_assert (buffer);
868 :
869 13 : if (login == NULL) {
870 : /* Must lock the private data in some way */
871 4 : if (self->privates && g_hash_table_size (self->privates))
872 1 : return GKM_DATA_LOCKED;
873 :
874 : /* Not storing privates */
875 : else
876 3 : return GKM_DATA_UNRECOGNIZED;
877 : } else {
878 : /* We didn't load the privates, can't store them back */
879 9 : if (self->privates == NULL)
880 1 : return GKM_DATA_LOCKED;
881 : }
882 :
883 8 : egg_buffer_init_full (&secure, 1024, PRIVATE_ALLOC);
884 :
885 8 : res = write_entries_to_block (self, self->privates, &secure);
886 8 : if (res == GKM_DATA_SUCCESS)
887 8 : res = encrypt_buffer (&secure, login, buffer);
888 :
889 8 : egg_buffer_uninit (&secure);
890 8 : return res;
891 : }
892 :
893 : static GkmDataResult
894 11 : write_public_to_block (GkmGnome2File *self, EggBuffer *buffer)
895 : {
896 11 : g_assert (GKM_IS_GNOME2_FILE (self));
897 11 : g_assert (buffer);
898 :
899 11 : return write_entries_to_block (self, self->publics, buffer);
900 : }
901 :
902 : static void
903 20 : write_each_index_identifier (gpointer key, gpointer value, gpointer data)
904 : {
905 20 : egg_buffer_add_string (data, key);
906 20 : egg_buffer_add_uint32 (data, GPOINTER_TO_UINT (value));
907 20 : }
908 :
909 : static GkmDataResult
910 13 : write_index_to_block (GkmGnome2File *self, EggBuffer *buffer)
911 : {
912 13 : g_assert (GKM_IS_GNOME2_FILE (self));
913 13 : g_assert (buffer);
914 :
915 : /* The number of entries */
916 13 : egg_buffer_add_uint32 (buffer, g_hash_table_size (self->identifiers));
917 :
918 : /* Now write out all the entries */
919 13 : g_hash_table_foreach (self->identifiers, write_each_index_identifier, buffer);
920 :
921 13 : return egg_buffer_has_error (buffer) ? GKM_DATA_FAILURE : GKM_DATA_SUCCESS;
922 : }
923 :
924 : static GkmDataResult
925 34 : identifier_to_attributes (GkmGnome2File *self, const gchar *identifier, GHashTable **attributes)
926 : {
927 : GHashTable *entries;
928 : gpointer value;
929 : guint section;
930 :
931 34 : g_assert (GKM_IS_GNOME2_FILE (self));
932 34 : g_assert (identifier);
933 34 : g_assert (attributes);
934 :
935 34 : if (!g_hash_table_lookup_extended (self->identifiers, identifier, NULL, &value))
936 3 : return GKM_DATA_UNRECOGNIZED;
937 :
938 31 : section = GPOINTER_TO_UINT (value);
939 31 : if (section == GKM_GNOME2_FILE_SECTION_PRIVATE)
940 6 : entries = self->privates;
941 : else
942 25 : entries = self->publics;
943 :
944 31 : if (entries == NULL)
945 1 : return GKM_DATA_LOCKED;
946 :
947 30 : *attributes = g_hash_table_lookup (entries, identifier);
948 30 : g_return_val_if_fail (*attributes, GKM_DATA_UNRECOGNIZED);
949 :
950 30 : return GKM_DATA_SUCCESS;
951 : }
952 :
953 : static void
954 111 : free_unknown_block_list (GList *list)
955 : {
956 : UnknownBlock *unknown;
957 : GList *l;
958 :
959 114 : for (l = list; l; l = g_list_next (l)) {
960 3 : unknown = l->data;
961 3 : g_assert (unknown);
962 3 : egg_buffer_uninit (&unknown->buffer);
963 3 : g_slice_free (UnknownBlock, unknown);
964 : }
965 :
966 111 : g_list_free (list);
967 111 : }
968 :
969 : static gint
970 0 : sort_unknowns_by_type (gconstpointer a, gconstpointer b)
971 : {
972 0 : const UnknownBlock *ua = a;
973 0 : const UnknownBlock *ub = b;
974 :
975 0 : g_assert (ua);
976 0 : g_assert (ub);
977 :
978 0 : if (ua->type == ub->type)
979 0 : return 0;
980 :
981 0 : return ua->type > ub->type ? 1 : -1;
982 : }
983 :
984 : typedef struct _ForeachArgs {
985 : GkmGnome2File *self;
986 : GkmGnome2FileFunc func;
987 : gpointer user_data;
988 : } ForeachArgs;
989 :
990 : static void
991 5 : foreach_identifier (gpointer key, gpointer value, gpointer data)
992 : {
993 5 : ForeachArgs *args = data;
994 5 : g_assert (GKM_IS_GNOME2_FILE (args->self));
995 5 : (args->func) (args->self, key, args->user_data);
996 5 : }
997 :
998 : static void
999 0 : dump_attributes (gpointer key, gpointer value, gpointer user_data)
1000 : {
1001 0 : CK_ATTRIBUTE_PTR attr = value;
1002 0 : gulong *type = key;
1003 : gchar *text;
1004 :
1005 0 : g_assert (type);
1006 0 : g_assert (value);
1007 :
1008 0 : if (attr->pValue == NULL)
1009 0 : text = g_strdup ("NULL");
1010 : else
1011 0 : text = egg_hex_encode_full (attr->pValue, attr->ulValueLen, TRUE, " ", 1);
1012 :
1013 0 : g_print ("\t0x%08x: %s\n", (guint)*type, text);
1014 0 : g_free (text);
1015 0 : }
1016 :
1017 : static void
1018 0 : dump_identifier_and_attributes (GkmGnome2File *self, const gchar *identifier, gpointer user_data)
1019 : {
1020 : GHashTable *attributes;
1021 : guint section;
1022 :
1023 0 : g_assert (GKM_IS_GNOME2_FILE (self));
1024 :
1025 0 : if (!gkm_gnome2_file_lookup_entry (self, identifier, §ion))
1026 0 : g_assert_not_reached ();
1027 :
1028 0 : if (GPOINTER_TO_UINT (user_data) == section) {
1029 0 : g_print ("%s\n", identifier);
1030 0 : if (identifier_to_attributes (self, identifier, &attributes) != GKM_DATA_SUCCESS)
1031 0 : g_assert_not_reached ();
1032 0 : g_hash_table_foreach (attributes, dump_attributes, NULL);
1033 0 : g_print ("\n");
1034 : }
1035 0 : }
1036 :
1037 :
1038 : /* -----------------------------------------------------------------------------
1039 : * OBJECT
1040 : */
1041 :
1042 : static void
1043 61 : gkm_gnome2_file_init (GkmGnome2File *self)
1044 : {
1045 61 : self->identifiers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1046 61 : self->publics = entries_new ();
1047 61 : self->privates = entries_new ();
1048 :
1049 61 : self->unknowns = NULL;
1050 :
1051 61 : self->checks = NULL;
1052 61 : }
1053 :
1054 : static void
1055 61 : gkm_gnome2_file_finalize (GObject *obj)
1056 : {
1057 61 : GkmGnome2File *self = GKM_GNOME2_FILE (obj);
1058 :
1059 61 : g_assert (self->identifiers);
1060 61 : g_hash_table_destroy (self->identifiers);
1061 61 : self->identifiers = NULL;
1062 :
1063 61 : g_assert (self->checks == NULL);
1064 :
1065 61 : g_assert (self->publics);
1066 61 : g_hash_table_destroy (self->publics);
1067 61 : self->publics = NULL;
1068 :
1069 61 : if (self->privates)
1070 60 : g_hash_table_destroy (self->privates);
1071 61 : self->privates = NULL;
1072 :
1073 61 : free_unknown_block_list (self->unknowns);
1074 61 : self->unknowns = NULL;
1075 :
1076 61 : G_OBJECT_CLASS (gkm_gnome2_file_parent_class)->finalize (obj);
1077 61 : }
1078 :
1079 : static void
1080 0 : gkm_gnome2_file_set_property (GObject *obj, guint prop_id, const GValue *value,
1081 : GParamSpec *pspec)
1082 : {
1083 : switch (prop_id) {
1084 : default:
1085 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1086 0 : break;
1087 : }
1088 0 : }
1089 :
1090 : static void
1091 0 : gkm_gnome2_file_get_property (GObject *obj, guint prop_id, GValue *value,
1092 : GParamSpec *pspec)
1093 : {
1094 : switch (prop_id) {
1095 : default:
1096 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1097 0 : break;
1098 : }
1099 0 : }
1100 :
1101 : static void
1102 31 : gkm_gnome2_file_class_init (GkmGnome2FileClass *klass)
1103 : {
1104 31 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1105 :
1106 31 : gobject_class->finalize = gkm_gnome2_file_finalize;
1107 31 : gobject_class->set_property = gkm_gnome2_file_set_property;
1108 31 : gobject_class->get_property = gkm_gnome2_file_get_property;
1109 :
1110 31 : signals[ENTRY_ADDED] = g_signal_new ("entry-added", GKM_TYPE_GNOME2_FILE,
1111 : G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkmGnome2FileClass, entry_added),
1112 : NULL, NULL, g_cclosure_marshal_VOID__STRING,
1113 : G_TYPE_NONE, 1, G_TYPE_STRING);
1114 :
1115 31 : signals[ENTRY_CHANGED] = g_signal_new ("entry-changed", GKM_TYPE_GNOME2_FILE,
1116 : G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkmGnome2FileClass, entry_changed),
1117 : NULL, NULL, gkm_marshal_VOID__STRING_ULONG,
1118 : G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_ULONG);
1119 :
1120 31 : signals[ENTRY_REMOVED] = g_signal_new ("entry-removed", GKM_TYPE_GNOME2_FILE,
1121 : G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GkmGnome2FileClass, entry_removed),
1122 : NULL, NULL, g_cclosure_marshal_VOID__STRING,
1123 : G_TYPE_NONE, 1, G_TYPE_STRING);
1124 31 : }
1125 :
1126 : /* -----------------------------------------------------------------------------
1127 : * PUBLIC
1128 : */
1129 :
1130 : GkmGnome2File*
1131 61 : gkm_gnome2_file_new (void)
1132 : {
1133 61 : return g_object_new (GKM_TYPE_GNOME2_FILE, NULL);
1134 : }
1135 :
1136 : GkmDataResult
1137 50 : gkm_gnome2_file_read_fd (GkmGnome2File *self, int fd, GkmSecret *login)
1138 : {
1139 : GkmDataResult res;
1140 :
1141 50 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1142 :
1143 : /* Reads are not reentrant for a single data file */
1144 50 : g_return_val_if_fail (self->checks == NULL, GKM_DATA_FAILURE);
1145 :
1146 50 : self->sections = 0;
1147 :
1148 : /* Free all the old unknowns */
1149 50 : free_unknown_block_list (self->unknowns);
1150 50 : self->unknowns = NULL;
1151 :
1152 : /* Setup a hash table to monitor the actual data read */
1153 50 : self->checks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1154 50 : g_hash_table_foreach (self->identifiers, copy_each_identifier, self->checks);
1155 :
1156 50 : res = parse_file_blocks (fd, update_from_any_block, login, self);
1157 50 : if (res == GKM_DATA_SUCCESS) {
1158 :
1159 : /* Our last read was a success, can write */
1160 50 : self->incomplete = FALSE;
1161 :
1162 : /* Remove the ones we didn't see */
1163 50 : g_hash_table_foreach (self->checks, remove_each_identifier, self);
1164 :
1165 : /*
1166 : * There's a special where we've read a file without a private section.
1167 : * We should be ready to accept privates (and then lock them next
1168 : * time around).
1169 : */
1170 :
1171 50 : if (self->privates == NULL && !(self->sections & GKM_GNOME2_FILE_SECTION_PRIVATE))
1172 1 : self->privates = entries_new ();
1173 :
1174 : /* Note that our last read failed */
1175 : } else {
1176 0 : self->incomplete = TRUE;
1177 : }
1178 :
1179 50 : g_hash_table_destroy (self->checks);
1180 50 : self->checks = NULL;
1181 :
1182 50 : return res;
1183 : }
1184 :
1185 : GkmDataResult
1186 13 : gkm_gnome2_file_write_fd (GkmGnome2File *self, int fd, GkmSecret *login)
1187 : {
1188 13 : guint types[3] = { FILE_BLOCK_INDEX, FILE_BLOCK_PRIVATE, FILE_BLOCK_PUBLIC };
1189 : GList *unknowns, *unk;
1190 : UnknownBlock *block;
1191 : GkmDataResult res;
1192 : EggBuffer buffer;
1193 : guint type;
1194 : gint i;
1195 :
1196 13 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1197 13 : g_return_val_if_fail (!self->incomplete, GKM_DATA_FAILURE);
1198 :
1199 : /* Write out the header */
1200 13 : if (!write_all_bytes (fd, FILE_HEADER, FILE_HEADER_LEN))
1201 0 : return GKM_DATA_FAILURE;
1202 :
1203 13 : unknowns = g_list_copy (self->unknowns);
1204 13 : unknowns = g_list_sort (unknowns, sort_unknowns_by_type);
1205 13 : egg_buffer_init_full (&buffer, 8192, PUBLIC_ALLOC);
1206 :
1207 : /*
1208 : * All blocks are written in sorted order by their block
1209 : * type. This includes unknown blocks.
1210 : */
1211 :
1212 13 : unk = unknowns;
1213 13 : res = GKM_DATA_SUCCESS;
1214 :
1215 48 : for (i = 0; i < G_N_ELEMENTS(types); ++i) {
1216 37 : type = types[i];
1217 :
1218 : /* Write out all the unknowns before this block */
1219 38 : while (unk != NULL && res == GKM_DATA_SUCCESS) {
1220 2 : block = (UnknownBlock*)unk->data;
1221 2 : if (block->type > type)
1222 1 : break;
1223 1 : res = write_file_block (fd, block->type, &block->buffer);
1224 1 : unk = g_list_next (unk);
1225 : }
1226 :
1227 37 : if (res != GKM_DATA_SUCCESS)
1228 0 : break;
1229 :
1230 : /* Prepare the block of this type */
1231 37 : egg_buffer_reset (&buffer);
1232 37 : switch (type) {
1233 13 : case FILE_BLOCK_INDEX:
1234 13 : res = write_index_to_block (self, &buffer);
1235 13 : break;
1236 13 : case FILE_BLOCK_PRIVATE:
1237 13 : res = write_private_to_block (self, &buffer, login);
1238 13 : break;
1239 11 : case FILE_BLOCK_PUBLIC:
1240 11 : res = write_public_to_block (self, &buffer);
1241 11 : break;
1242 : }
1243 :
1244 : /* Write it out, if we got anything */
1245 37 : if (res == GKM_DATA_SUCCESS)
1246 32 : res = write_file_block (fd, type, &buffer);
1247 5 : else if (res == GKM_DATA_UNRECOGNIZED)
1248 3 : res = GKM_DATA_SUCCESS;
1249 :
1250 37 : if (res != GKM_DATA_SUCCESS)
1251 2 : break;
1252 : }
1253 :
1254 : /* Write out all remaining unknowns */
1255 13 : while (unk != NULL && res == GKM_DATA_SUCCESS) {
1256 0 : block = (UnknownBlock*)unk->data;
1257 0 : res = write_file_block (fd, block->type, &block->buffer);
1258 0 : unk = g_list_next (unk);
1259 : }
1260 :
1261 13 : g_list_free (unknowns);
1262 13 : egg_buffer_uninit (&buffer);
1263 13 : return res;
1264 : }
1265 :
1266 : gboolean
1267 32 : gkm_gnome2_file_lookup_entry (GkmGnome2File *self, const gchar *identifier, guint *section)
1268 : {
1269 : gpointer value;
1270 :
1271 32 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), FALSE);
1272 32 : g_return_val_if_fail (identifier, FALSE);
1273 :
1274 32 : if (!g_hash_table_lookup_extended (self->identifiers, identifier, NULL, &value))
1275 14 : return FALSE;
1276 :
1277 18 : if (section != NULL)
1278 13 : *section = GPOINTER_TO_UINT (value);
1279 :
1280 18 : return TRUE;
1281 : }
1282 :
1283 : void
1284 5 : gkm_gnome2_file_foreach_entry (GkmGnome2File *self, GkmGnome2FileFunc func, gpointer user_data)
1285 : {
1286 5 : ForeachArgs args = { self, func, user_data };
1287 :
1288 5 : g_return_if_fail (GKM_IS_GNOME2_FILE (self));
1289 5 : g_return_if_fail (func);
1290 :
1291 5 : g_hash_table_foreach (self->identifiers, foreach_identifier, &args);
1292 : }
1293 :
1294 : GkmDataResult
1295 8 : gkm_gnome2_file_unique_entry (GkmGnome2File *self, gchar **identifier)
1296 : {
1297 : gchar *base, *ext;
1298 8 : guint seed = 1;
1299 :
1300 8 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1301 8 : g_return_val_if_fail (identifier, GKM_DATA_FAILURE);
1302 :
1303 : /* Check if original is unique */
1304 8 : if (*identifier != NULL) {
1305 7 : if (!gkm_gnome2_file_lookup_entry (self, *identifier, NULL))
1306 6 : return GKM_DATA_SUCCESS;
1307 : }
1308 :
1309 2 : if (*identifier == NULL)
1310 1 : *identifier = g_strdup_printf ("object-%08x", ABS (g_random_int ()));
1311 :
1312 : /* Take ownership of the identifier, and out an extension */
1313 2 : base = *identifier;
1314 2 : *identifier = NULL;
1315 2 : ext = strrchr (base, '.');
1316 2 : if (ext != NULL)
1317 0 : *(ext++) = '\0';
1318 :
1319 2 : for (seed = 0; TRUE; ++seed) {
1320 2 : *identifier = g_strdup_printf ("%s-%d%s%s", base, seed, ext ? "." : "", ext ? ext : "");
1321 2 : if (!gkm_gnome2_file_lookup_entry (self, *identifier, NULL))
1322 2 : break;
1323 :
1324 0 : if (seed > 1000000) {
1325 0 : g_warning ("couldn't find a unique identifier in a %d tries", seed);
1326 0 : g_free (base);
1327 0 : return GKM_DATA_FAILURE;
1328 : }
1329 :
1330 0 : g_free (*identifier);
1331 0 : *identifier = NULL;
1332 : }
1333 :
1334 2 : g_free (base);
1335 2 : return GKM_DATA_SUCCESS;
1336 : }
1337 :
1338 : GkmDataResult
1339 22 : gkm_gnome2_file_create_entry (GkmGnome2File *self, const gchar *identifier, guint section)
1340 : {
1341 : GHashTable *attributes;
1342 : GHashTable *entries;
1343 :
1344 22 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1345 22 : g_return_val_if_fail (identifier, GKM_DATA_FAILURE);
1346 :
1347 22 : if (section == GKM_GNOME2_FILE_SECTION_PRIVATE) {
1348 7 : if (!self->privates)
1349 1 : return GKM_DATA_LOCKED;
1350 6 : entries = self->privates;
1351 : } else {
1352 15 : entries = self->publics;
1353 : }
1354 :
1355 : /* Make sure it's not already here */
1356 21 : g_return_val_if_fail (g_hash_table_lookup (entries, identifier) == NULL, GKM_DATA_FAILURE);
1357 :
1358 : /* Add the new entry to the appropriate table */
1359 21 : attributes = attributes_new ();
1360 21 : g_hash_table_replace (entries, g_strdup (identifier), attributes);
1361 21 : g_hash_table_replace (self->identifiers, g_strdup (identifier), GUINT_TO_POINTER (section));
1362 :
1363 21 : g_signal_emit (self, signals[ENTRY_ADDED], 0, identifier);
1364 21 : return GKM_DATA_SUCCESS;
1365 : }
1366 :
1367 : GkmDataResult
1368 5 : gkm_gnome2_file_destroy_entry (GkmGnome2File *self, const gchar *identifier)
1369 : {
1370 : GHashTable *entries;
1371 : guint section;
1372 :
1373 5 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1374 5 : g_return_val_if_fail (identifier, GKM_DATA_FAILURE);
1375 :
1376 5 : if (!gkm_gnome2_file_lookup_entry (self, identifier, §ion))
1377 2 : return GKM_DATA_UNRECOGNIZED;
1378 :
1379 3 : if (section == GKM_GNOME2_FILE_SECTION_PRIVATE) {
1380 1 : if (!self->privates)
1381 1 : return GKM_DATA_LOCKED;
1382 0 : entries = self->privates;
1383 : } else {
1384 2 : entries = self->publics;
1385 : }
1386 :
1387 2 : if (!g_hash_table_remove (self->identifiers, identifier))
1388 0 : g_return_val_if_reached (GKM_DATA_UNRECOGNIZED);
1389 2 : if (!g_hash_table_remove (entries, identifier))
1390 0 : g_return_val_if_reached (GKM_DATA_UNRECOGNIZED);
1391 :
1392 2 : g_signal_emit (self, signals[ENTRY_REMOVED], 0, identifier);
1393 2 : return GKM_DATA_SUCCESS;
1394 : }
1395 :
1396 : GkmDataResult
1397 21 : gkm_gnome2_file_write_value (GkmGnome2File *self, const gchar *identifier,
1398 : gulong type, gconstpointer value, gsize n_value)
1399 : {
1400 : GHashTable *attributes;
1401 : CK_ATTRIBUTE_PTR at;
1402 : CK_ATTRIBUTE attr;
1403 : GkmDataResult res;
1404 :
1405 21 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1406 21 : g_return_val_if_fail (identifier, GKM_DATA_FAILURE);
1407 21 : g_return_val_if_fail (value || !n_value, GKM_DATA_FAILURE);
1408 :
1409 : /* Find the right set of attributes */
1410 21 : res = identifier_to_attributes (self, identifier, &attributes);
1411 21 : if (res != GKM_DATA_SUCCESS)
1412 2 : return res;
1413 :
1414 19 : attr.type = type;
1415 19 : attr.pValue = (void*)value;
1416 19 : attr.ulValueLen = n_value;
1417 :
1418 19 : at = g_hash_table_lookup (attributes, &type);
1419 19 : if (at != NULL && gkm_attribute_equal (at, &attr))
1420 1 : return GKM_DATA_SUCCESS;
1421 :
1422 18 : at = attribute_dup (&attr);
1423 18 : g_hash_table_replace (attributes, &(at->type), at);
1424 :
1425 18 : g_signal_emit (self, signals[ENTRY_CHANGED], 0, identifier, type);
1426 18 : return GKM_DATA_SUCCESS;
1427 : }
1428 :
1429 : GkmDataResult
1430 13 : gkm_gnome2_file_read_value (GkmGnome2File *self, const gchar *identifier,
1431 : gulong type, gconstpointer *value, gsize *n_value)
1432 : {
1433 : CK_ATTRIBUTE_PTR attr;
1434 : GHashTable *attributes;
1435 : GkmDataResult res;
1436 :
1437 13 : g_return_val_if_fail (GKM_IS_GNOME2_FILE (self), GKM_DATA_FAILURE);
1438 13 : g_return_val_if_fail (identifier, GKM_DATA_FAILURE);
1439 13 : g_return_val_if_fail (value, GKM_DATA_FAILURE);
1440 13 : g_return_val_if_fail (n_value, GKM_DATA_FAILURE);
1441 :
1442 : /* Find the right set of attributes */
1443 13 : res = identifier_to_attributes (self, identifier, &attributes);
1444 13 : if (res != GKM_DATA_SUCCESS)
1445 2 : return res;
1446 :
1447 11 : attr = g_hash_table_lookup (attributes, &type);
1448 11 : if (attr == NULL)
1449 2 : return GKM_DATA_UNRECOGNIZED;
1450 :
1451 9 : g_assert (attr->type == type);
1452 9 : *value = attr->pValue;
1453 9 : *n_value = attr->ulValueLen;
1454 9 : return GKM_DATA_SUCCESS;
1455 : }
1456 :
1457 : gboolean
1458 17 : gkm_gnome2_file_have_section (GkmGnome2File *self, guint section)
1459 : {
1460 17 : return (self->sections & section) ? TRUE : FALSE;
1461 : }
1462 :
1463 : void
1464 0 : gkm_gnome2_file_dump (GkmGnome2File *self)
1465 : {
1466 0 : g_print ("PUBLIC:\n\n");
1467 0 : gkm_gnome2_file_foreach_entry (self, dump_identifier_and_attributes,
1468 : GUINT_TO_POINTER (GKM_GNOME2_FILE_SECTION_PUBLIC));
1469 0 : g_print ("PRIVATE:\n\n");
1470 0 : gkm_gnome2_file_foreach_entry (self, dump_identifier_and_attributes,
1471 : GUINT_TO_POINTER (GKM_GNOME2_FILE_SECTION_PRIVATE));
1472 0 : }
|