Wrap file I/O to claws_* to benefit from custom locking when
[claws.git] / src / passwordstore.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2016 The Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
28 #endif
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32
33 #include "common/defs.h"
34 #include "common/utils.h"
35 #include "passwordstore.h"
36 #include "password.h"
37 #include "prefs_common.h"
38 #include "prefs_gtk.h"
39 #include "prefs_migration.h"
40 #include "claws_io.h"
41
42 static GSList *_password_store;
43
44 /* Finds password block of given type and name in the pwdstore. */
45 static PasswordBlock *_get_block(PasswordBlockType block_type,
46                 const gchar *block_name)
47 {
48         GSList *item;
49         PasswordBlock *block;
50
51         g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
52         g_return_val_if_fail(block_name != NULL, NULL);
53
54         for (item = _password_store; item != NULL; item = item->next) {
55                 block = (PasswordBlock *)item->data;
56                 if (block->block_type == block_type &&
57                                 !strcmp(block->block_name, block_name))
58                         return block;
59         }
60
61         return NULL;
62 }
63
64 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
65 {
66         if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
67                 return TRUE;
68         return FALSE;
69 }
70
71 /* Creates a new, empty block and adds it to the pwdstore. */
72 static PasswordBlock *_new_block(PasswordBlockType block_type,
73                 const gchar *block_name)
74 {
75         PasswordBlock *block;
76
77         g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
78         g_return_val_if_fail(block_name != NULL, NULL);
79
80         /* First check to see if the block doesn't already exist. */
81         if (_get_block(block_type, block_name)) {
82                 debug_print("Block (%d/%s) already exists.\n",
83                                 block_type, block_name);
84                 return NULL;
85         }
86
87         /* Let's create an empty block, and add it to pwdstore. */
88         block = g_new0(PasswordBlock, 1);
89         block->block_type = block_type;
90         block->block_name = g_strdup(block_name);
91         block->entries = g_hash_table_new_full(g_str_hash,
92                         (GEqualFunc)_hash_equal_func,
93                         g_free, g_free);
94         debug_print("Created password block (%d/%s)\n",
95                         block_type, block_name);
96
97         _password_store = g_slist_append(_password_store, block);
98
99         return block;
100 }
101
102 /*************************************************************/
103
104 /* Stores a password. */
105 gboolean passwd_store_set(PasswordBlockType block_type,
106                 const gchar *block_name,
107                 const gchar *password_id,
108                 const gchar *password,
109                 gboolean encrypted)
110 {
111         const gchar *p;
112         PasswordBlock *block;
113         gchar *encrypted_password;
114
115         g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
116         g_return_val_if_fail(block_name != NULL, FALSE);
117         g_return_val_if_fail(password_id != NULL, FALSE);
118
119         /* Empty password string equals null password for us. */
120         if (password == NULL || strlen(password) == 0)
121                 p = NULL;
122         else
123                 p = password;
124
125         debug_print("%s password '%s' in block (%d/%s)%s\n",
126                         (p == NULL ? "Deleting" : "Storing"),
127                         password_id, block_type, block_name,
128                         (encrypted ? ", already encrypted" : "") );
129
130         /* find correct block (create if needed) */
131         if ((block = _get_block(block_type, block_name)) == NULL) {
132                 /* If caller wants to delete a password, and even its block
133                  * doesn't exist, we're done. */
134                 if (p == NULL)
135                         return TRUE;
136
137                 if ((block = _new_block(block_type, block_name)) == NULL) {
138                         debug_print("Could not create password block (%d/%s)\n",
139                                         block_type, block_name);
140                         return FALSE;
141                 }
142         }
143
144         if (p == NULL) {
145                 /* NULL password was passed to us, so delete the entry with
146                  * corresponding id */
147                 g_hash_table_remove(block->entries, password_id);
148         } else {
149                 if (!encrypted) {
150                         /* encrypt password before saving it */
151                         if ((encrypted_password =
152                                                 password_encrypt(p, NULL)) == NULL) {
153                                 debug_print("Could not encrypt password '%s' for block (%d/%s).\n",
154                                                 password_id, block_type, block_name);
155                                 return FALSE;
156                         }
157                 } else {
158                         /* password is already in encrypted form already */
159                         encrypted_password = g_strdup(p);
160                 }
161
162                 /* add encrypted password to the block */
163                 g_hash_table_insert(block->entries,
164                                 g_strdup(password_id),
165                                 encrypted_password);
166         }
167
168         return TRUE;
169 }
170
171 /* Retrieves a password. */
172 gchar *passwd_store_get(PasswordBlockType block_type,
173                 const gchar *block_name,
174                 const gchar *password_id)
175 {
176         PasswordBlock *block;
177         gchar *encrypted_password, *password;
178
179         g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
180         g_return_val_if_fail(block_name != NULL, NULL);
181         g_return_val_if_fail(password_id != NULL, NULL);
182
183         debug_print("Getting password '%s' from block (%d/%s)\n",
184                         password_id, block_type, block_name);
185
186         /* find correct block */
187         if ((block = _get_block(block_type, block_name)) == NULL) {
188                 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
189                 return NULL;
190         }
191
192         /* grab pointer to encrypted password */
193         if ((encrypted_password =
194                                 g_hash_table_lookup(block->entries, password_id)) == NULL) {
195                 debug_print("Password '%s' in block (%d/%s) not found.\n",
196                                 password_id, block_type, block_name);
197                 return NULL;
198         }
199
200         /* decrypt password */
201         if ((password =
202                                 password_decrypt(encrypted_password, NULL)) == NULL) {
203                 debug_print("Could not decrypt password '%s' for block (%d/%s).\n",
204                                 password_id, block_type, block_name);
205                 return NULL;
206         }
207
208         /* return decrypted password */
209         return password;
210 }
211
212 /* Checks if a password exists in the password store.
213  * No decryption happens. */
214 gboolean passwd_store_has_password(PasswordBlockType block_type,
215                 const gchar *block_name,
216                 const gchar *password_id)
217 {
218         PasswordBlock *block;
219
220         g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
221         g_return_val_if_fail(block_name != NULL, FALSE);
222         g_return_val_if_fail(password_id != NULL, FALSE);
223
224         /* find correct block */
225         if ((block = _get_block(block_type, block_name)) == NULL) {
226                 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
227                 return FALSE;
228         }
229
230         /* do we have specified password in this block? */
231         if (g_hash_table_lookup(block->entries, password_id) != NULL) {
232                 return TRUE; /* yes */
233         }
234
235         return FALSE; /* no */
236 }
237
238
239 gboolean passwd_store_delete_block(PasswordBlockType block_type,
240                 const gchar *block_name)
241 {
242         PasswordBlock *block;
243
244         g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
245         g_return_val_if_fail(block_name != NULL, FALSE);
246
247         debug_print("Deleting block (%d/%s)\n", block_type, block_name);
248
249         /* find correct block */
250         if ((block = _get_block(block_type, block_name)) == NULL) {
251                 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
252                 return FALSE;
253         }
254
255         g_hash_table_destroy(block->entries);
256         block->entries = NULL;
257         return TRUE;
258 }
259
260 gboolean passwd_store_set_account(gint account_id,
261                 const gchar *password_id,
262                 const gchar *password,
263                 gboolean encrypted)
264 {
265         gchar *uid = g_strdup_printf("%d", account_id);
266         gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
267                         password_id, password, encrypted);
268         g_free(uid);
269         return ret;
270 }
271
272 gchar *passwd_store_get_account(gint account_id,
273                 const gchar *password_id)
274 {
275         gchar *uid = g_strdup_printf("%d", account_id);
276         gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
277         g_free(uid);
278         return ret;
279 }
280
281 gboolean passwd_store_has_password_account(gint account_id,
282                 const gchar *password_id)
283 {
284         gchar *uid = g_strdup_printf("%d", account_id);
285         gboolean ret = passwd_store_has_password(PWS_ACCOUNT, uid, password_id);
286         g_free(uid);
287         return ret;
288 }
289
290 /* Reencrypts all stored passwords. */
291 void passwd_store_reencrypt_all(const gchar *old_mpwd,
292                 const gchar *new_mpwd)
293 {
294         PasswordBlock *block;
295         GSList *item;
296         GList *keys, *eitem;
297         gchar *encrypted_password, *decrypted_password, *key;
298
299         g_return_if_fail(old_mpwd != NULL);
300         g_return_if_fail(new_mpwd != NULL);
301
302         for (item = _password_store; item != NULL; item = item->next) {
303                 block = (PasswordBlock *)item->data;
304                 if (block == NULL)
305                         continue; /* Just in case. */
306
307                 debug_print("Reencrypting passwords in block (%d/%s).\n",
308                                 block->block_type, block->block_name);
309
310                 if (g_hash_table_size(block->entries) == 0)
311                         continue;
312
313                 keys = g_hash_table_get_keys(block->entries);
314                 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
315                         key = (gchar *)eitem->data;
316                         if ((encrypted_password =
317                                                 g_hash_table_lookup(block->entries, key)) == NULL)
318                                 continue;
319
320                         if ((decrypted_password =
321                                                 password_decrypt(encrypted_password, old_mpwd)) == NULL) {
322                                 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
323                                 continue;
324                         }
325
326                         encrypted_password = password_encrypt(decrypted_password, new_mpwd);
327                         memset(decrypted_password, 0, strlen(decrypted_password));
328                         g_free(decrypted_password);
329                         if (encrypted_password == NULL) {
330                                 debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key);
331                                 continue;
332                         }
333
334                         g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
335                 }
336
337                 g_list_free(keys);
338         }
339
340         debug_print("Reencrypting done.\n");
341 }
342
343 static gint _write_to_file(FILE *fp)
344 {
345         PasswordBlock *block;
346         GSList *item;
347         GList *keys, *eitem;
348         gchar *typestr, *line, *key, *pwd;
349
350         /* Write out the config_version */
351         line = g_strdup_printf("[config_version:%d]\n", CLAWS_CONFIG_VERSION);
352         if (claws_fputs(line, fp) == EOF) {
353                 FILE_OP_ERROR("password store, config_version", "claws_fputs");
354                 g_free(line);
355                 return -1;
356         }
357         g_free(line);
358
359         /* Add a newline if needed */
360         if (_password_store != NULL && claws_fputs("\n", fp) == EOF) {
361                 FILE_OP_ERROR("password store", "claws_fputs");
362                 return -1;
363         }
364
365         /* Write out each password store block */
366         for (item = _password_store; item != NULL; item = item->next) {
367                 block = (PasswordBlock*)item->data;
368                 if (block == NULL)
369                         continue; /* Just in case. */
370
371                 /* Do not save empty blocks. */
372                 if (g_hash_table_size(block->entries) == 0)
373                         continue;
374
375                 /* Prepare the section header string and write it out. */
376                 typestr = NULL;
377                 if (block->block_type == PWS_CORE) {
378                         typestr = "core";
379                 } else if (block->block_type == PWS_ACCOUNT) {
380                         typestr = "account";
381                 } else if (block->block_type == PWS_PLUGIN) {
382                         typestr = "plugin";
383                 }
384                 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
385
386                 if (claws_fputs(line, fp) == EOF) {
387                         FILE_OP_ERROR("password store", "claws_fputs");
388                         g_free(line);
389                         return -1;
390                 }
391                 g_free(line);
392
393                 /* Now go through all passwords in the block and write each out. */
394                 keys = g_hash_table_get_keys(block->entries);
395                 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
396                         key = (gchar *)eitem->data;
397                         if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL)
398                                 continue;
399
400                         line = g_strdup_printf("%s %s\n", key, pwd);
401                         if (claws_fputs(line, fp) == EOF) {
402                                 FILE_OP_ERROR("password store", "claws_fputs");
403                                 g_free(line);
404                                 return -1;
405                         }
406                         g_free(line);
407                 }
408                 g_list_free(keys);
409
410                 /* Add a separating new line if there is another block remaining */
411                 if (item->next != NULL && claws_fputs("\n", fp) == EOF) {
412                         FILE_OP_ERROR("password store", "claws_fputs");
413                         return -1;
414                 }
415
416         }
417
418         return 1;
419 }
420
421 void passwd_store_write_config(void)
422 {
423         gchar *rcpath;
424         PrefFile *pfile;
425
426         debug_print("Writing password store...\n");
427
428         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
429                         PASSWORD_STORE_RC, NULL);
430
431         if ((pfile = prefs_write_open(rcpath)) == NULL) {
432                 g_warning("failed to open password store file for writing");
433                 g_free(rcpath);
434                 return;
435         }
436
437         g_free(rcpath);
438
439         if (_write_to_file(pfile->fp) < 0) {
440                 g_warning("failed to write password store to file");
441                 prefs_file_close_revert(pfile);
442         } else if (prefs_file_close(pfile) < 0) {
443                 g_warning("failed to properly close password store file after writing");
444         }
445 }
446
447 int passwd_store_read_config(void)
448 {
449         gchar *rcpath, *contents, **lines, **line, *typestr, *name;
450         GError *error = NULL;
451         guint i = 0;
452         PasswordBlock *block = NULL;
453         PasswordBlockType type;
454         gboolean reading_config_version = FALSE;
455         gint config_version = -1;
456
457         /* TODO: passwd_store_clear(); */
458
459         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
460                         PASSWORD_STORE_RC, NULL);
461
462         debug_print("Reading password store from file '%s'\n", rcpath);
463
464         if (!g_file_test(rcpath, G_FILE_TEST_EXISTS)) {
465                 debug_print("File does not exist, looks like a new configuration.\n");
466                 g_free(rcpath);
467                 return 0;
468         }
469
470         if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
471                 g_warning("couldn't read password store from file: %s", error->message);
472                 g_error_free(error);
473                 g_free(rcpath);
474                 return -1;
475         }
476         g_free(rcpath);
477
478         lines = g_strsplit(contents, "\n", -1);
479
480         g_free(contents);
481
482         while (lines[i] != NULL) {
483                 if (*lines[i] == '[') {
484                         /* Beginning of a new block */
485                         line = g_strsplit_set(lines[i], "[:]", -1);
486                         if (line[0] != NULL && strlen(line[0]) == 0
487                                         && line[1] != NULL && strlen(line[1]) > 0
488                                         && line[2] != NULL && strlen(line[2]) > 0
489                                         && line[3] != NULL && strlen(line[3]) == 0) {
490                                 typestr = line[1];
491                                 name = line[2];
492                                 if (!strcmp(typestr, "core")) {
493                                         type = PWS_CORE;
494                                 } else if (!strcmp(typestr, "account")) {
495                                         type = PWS_ACCOUNT;
496                                 } else if (!strcmp(typestr, "plugin")) {
497                                         type = PWS_PLUGIN;
498                                 } else if (!strcmp(typestr, "config_version")) {
499                                         reading_config_version = TRUE;
500                                         config_version = atoi(name);
501                                 } else {
502                                         debug_print("Unknown password block type: '%s'\n", typestr);
503                                         g_strfreev(line);
504                                         i++; continue;
505                                 }
506
507                                 if (reading_config_version) {
508                                         if (config_version < 0) {
509                                                 debug_print("config_version:%d looks invalid, ignoring it\n",
510                                                                 config_version);
511                                                 config_version = -1; /* set to default value if missing */
512                                                 i++; continue;
513                                         }
514                                         debug_print("config_version in file is %d\n", config_version);
515                                         reading_config_version = FALSE;
516                                 } else {
517                                         if ((block = _new_block(type, name)) == NULL) {
518                                                 debug_print("Duplicate password block, ignoring: (%d/%s)\n",
519                                                                 type, name);
520                                                 g_strfreev(line);
521                                                 i++; continue;
522                                         }
523                                 }
524                         }
525                         g_strfreev(line);
526                 } else if (strlen(lines[i]) > 0 && block != NULL) {
527                         /* If we have started a password block, test for a
528                          * "password_id = password" line. */
529                         line = g_strsplit(lines[i], " ", -1);
530                         if (line[0] != NULL && strlen(line[0]) > 0
531                                         && line[1] != NULL && strlen(line[1]) > 0
532                                         && line[2] == NULL) {
533                                 debug_print("Adding password '%s'\n", line[0]);
534                                 g_hash_table_insert(block->entries,
535                                                 g_strdup(line[0]), g_strdup(line[1]));
536                         }
537                         g_strfreev(line);
538                 }
539                 i++;
540         }
541         g_strfreev(lines);
542
543         if (prefs_update_config_version_password_store(config_version) < 0) {
544                 debug_print("Password store configuration file version upgrade failed\n");
545                 return -2;
546         }
547
548         return g_slist_length(_password_store);
549 }