for release 4.2.0
[claws.git] / src / passwordstore.c
blobc25cd4ddc0f0885c115bb817fb01b67a625eda72
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2016 The Claws Mail Team
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.
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.
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/>.
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #include "claws-features.h"
23 #endif
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
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 "file-utils.h"
42 static GSList *_password_store;
44 /* Finds password block of given type and name in the pwdstore
45 * and returns a pointer to it, if it exists.
46 * If link parameter is non-null, it is set to the linked list
47 * element containing this block. */
48 static PasswordBlock *_get_block(PasswordBlockType block_type,
49 const gchar *block_name, GSList **link)
51 GSList *item;
52 PasswordBlock *block;
54 g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
55 g_return_val_if_fail(block_name != NULL, NULL);
57 for (item = _password_store; item != NULL; item = item->next) {
58 block = (PasswordBlock *)item->data;
59 if (block->block_type == block_type &&
60 !strcmp(block->block_name, block_name)) {
61 if (link != NULL)
62 *link = item;
63 return block;
67 return NULL;
70 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
72 if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
73 return TRUE;
74 return FALSE;
77 /* Creates a new, empty block and adds it to the pwdstore. */
78 static PasswordBlock *_new_block(PasswordBlockType block_type,
79 const gchar *block_name)
81 PasswordBlock *block;
83 g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
84 g_return_val_if_fail(block_name != NULL, NULL);
86 /* First check to see if the block doesn't already exist. */
87 if (_get_block(block_type, block_name, NULL)) {
88 debug_print("Block (%d/%s) already exists.\n",
89 block_type, block_name);
90 return NULL;
93 /* Let's create an empty block, and add it to pwdstore. */
94 block = g_new0(PasswordBlock, 1);
95 block->block_type = block_type;
96 block->block_name = g_strdup(block_name);
97 block->entries = g_hash_table_new_full(g_str_hash,
98 (GEqualFunc)_hash_equal_func,
99 g_free, g_free);
100 debug_print("Created password block (%d/%s)\n",
101 block_type, block_name);
103 _password_store = g_slist_append(_password_store, block);
105 return block;
108 static void _delete_block(PasswordBlock *block)
110 g_return_if_fail(block != NULL);
112 if (block->block_name != NULL)
113 g_free(block->block_name);
115 if (block->entries != NULL)
116 g_hash_table_destroy(block->entries);
118 g_free(block);
121 /*************************************************************/
123 /* Stores a password. */
124 gboolean passwd_store_set(PasswordBlockType block_type,
125 const gchar *block_name,
126 const gchar *password_id,
127 const gchar *password,
128 gboolean encrypted)
130 const gchar *p;
131 PasswordBlock *block;
132 gchar *encrypted_password;
134 g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
135 g_return_val_if_fail(block_name != NULL, FALSE);
136 g_return_val_if_fail(password_id != NULL, FALSE);
138 /* Empty password string equals null password for us. */
139 if (password == NULL || strlen(password) == 0)
140 p = NULL;
141 else
142 p = password;
144 /* find correct block (create if needed) */
145 if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
146 /* If caller wants to delete a password, and even its block
147 * doesn't exist, we're done. */
148 if (p == NULL)
149 return TRUE;
151 if ((block = _new_block(block_type, block_name)) == NULL) {
152 debug_print("Could not create password block (%d/%s)\n",
153 block_type, block_name);
154 return FALSE;
158 if (p == NULL) {
159 /* NULL password was passed to us, so delete the entry with
160 * corresponding id, if it exists */
161 if (g_hash_table_lookup(block->entries, password_id) != NULL) {
162 debug_print("Deleting password for '%s' in block (%d/%s)\n",
163 password_id, block_type, block_name);
164 g_hash_table_remove(block->entries, password_id);
166 } else {
167 debug_print("Setting password for '%s' in block (%d/%s)%s\n",
168 password_id, block_type, block_name,
169 (encrypted ? ", already encrypted" : ""));
170 if (!encrypted) {
171 /* encrypt password before saving it */
172 if ((encrypted_password =
173 password_encrypt(p, NULL)) == NULL) {
174 debug_print("Could not encrypt password '%s' for block (%d/%s).\n",
175 password_id, block_type, block_name);
176 return FALSE;
178 } else {
179 /* password is already in encrypted form already */
180 encrypted_password = g_strdup(p);
183 /* add encrypted password to the block */
184 g_hash_table_insert(block->entries,
185 g_strdup(password_id),
186 encrypted_password);
189 return TRUE;
192 /* Retrieves a password. */
193 gchar *passwd_store_get(PasswordBlockType block_type,
194 const gchar *block_name,
195 const gchar *password_id)
197 PasswordBlock *block;
198 gchar *encrypted_password, *password;
200 g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
201 g_return_val_if_fail(block_name != NULL, NULL);
202 g_return_val_if_fail(password_id != NULL, NULL);
204 debug_print("Getting password '%s' from block (%d/%s)\n",
205 password_id, block_type, block_name);
207 /* find correct block */
208 if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
209 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
210 return NULL;
213 /* grab pointer to encrypted password */
214 if ((encrypted_password =
215 g_hash_table_lookup(block->entries, password_id)) == NULL) {
216 debug_print("Password '%s' in block (%d/%s) not found.\n",
217 password_id, block_type, block_name);
218 return NULL;
221 /* decrypt password */
222 if ((password =
223 password_decrypt(encrypted_password, NULL)) == NULL) {
224 debug_print("Could not decrypt password '%s' for block (%d/%s).\n",
225 password_id, block_type, block_name);
226 return NULL;
229 /* return decrypted password */
230 return password;
233 /* Checks if a password exists in the password store.
234 * No decryption happens. */
235 gboolean passwd_store_has_password(PasswordBlockType block_type,
236 const gchar *block_name,
237 const gchar *password_id)
239 PasswordBlock *block;
241 g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
242 g_return_val_if_fail(block_name != NULL, FALSE);
243 g_return_val_if_fail(password_id != NULL, FALSE);
245 /* find correct block */
246 if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
247 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
248 return FALSE;
251 /* do we have specified password in this block? */
252 if (g_hash_table_lookup(block->entries, password_id) != NULL) {
253 return TRUE; /* yes */
256 return FALSE; /* no */
260 gboolean passwd_store_delete_block(PasswordBlockType block_type,
261 const gchar *block_name)
263 PasswordBlock *block;
264 GSList *link = NULL;
266 g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
267 g_return_val_if_fail(block_name != NULL, FALSE);
269 debug_print("Deleting block (%d/%s)\n", block_type, block_name);
271 /* find correct block */
272 if ((block = _get_block(block_type, block_name, &link)) == NULL) {
273 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
274 return FALSE;
277 /* free the block data and remove it from the list */
278 _delete_block(block);
279 _password_store = g_slist_delete_link(_password_store, link);
280 return TRUE;
283 gboolean passwd_store_set_account(gint account_id,
284 const gchar *password_id,
285 const gchar *password,
286 gboolean encrypted)
288 gchar *uid = g_strdup_printf("%d", account_id);
289 gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
290 password_id, password, encrypted);
291 g_free(uid);
292 return ret;
295 gchar *passwd_store_get_account(gint account_id,
296 const gchar *password_id)
298 gchar *uid = g_strdup_printf("%d", account_id);
299 gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
300 g_free(uid);
301 return ret;
304 gboolean passwd_store_has_password_account(gint account_id,
305 const gchar *password_id)
307 gchar *uid = g_strdup_printf("%d", account_id);
308 gboolean ret = passwd_store_has_password(PWS_ACCOUNT, uid, password_id);
309 g_free(uid);
310 return ret;
313 /* Reencrypts all stored passwords. */
314 void passwd_store_reencrypt_all(const gchar *old_mpwd,
315 const gchar *new_mpwd)
317 PasswordBlock *block;
318 GSList *item;
319 GList *keys, *eitem;
320 gchar *encrypted_password, *decrypted_password, *key;
322 g_return_if_fail(old_mpwd != NULL);
323 g_return_if_fail(new_mpwd != NULL);
325 for (item = _password_store; item != NULL; item = item->next) {
326 block = (PasswordBlock *)item->data;
327 if (block == NULL)
328 continue; /* Just in case. */
330 debug_print("Reencrypting passwords in block (%d/%s).\n",
331 block->block_type, block->block_name);
333 if (block->entries == NULL || g_hash_table_size(block->entries) == 0)
334 continue;
336 keys = g_hash_table_get_keys(block->entries);
337 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
338 key = (gchar *)eitem->data;
339 if ((encrypted_password =
340 g_hash_table_lookup(block->entries, key)) == NULL)
341 continue;
343 if ((decrypted_password =
344 password_decrypt(encrypted_password, old_mpwd)) == NULL) {
345 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
346 continue;
349 encrypted_password = password_encrypt(decrypted_password, new_mpwd);
350 memset(decrypted_password, 0, strlen(decrypted_password));
351 g_free(decrypted_password);
352 if (encrypted_password == NULL) {
353 debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key);
354 continue;
357 g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
360 g_list_free(keys);
363 debug_print("Reencrypting done.\n");
366 static gint _write_to_file(FILE *fp)
368 PasswordBlock *block;
369 GSList *item;
370 GList *keys, *eitem;
371 gchar *typestr, *line, *key, *pwd;
373 /* Write out the config_version */
374 line = g_strdup_printf("[config_version:%d]\n", CLAWS_CONFIG_VERSION);
375 if (claws_fputs(line, fp) == EOF) {
376 FILE_OP_ERROR("password store, config_version", "claws_fputs");
377 g_free(line);
378 return -1;
380 g_free(line);
382 /* Add a newline if needed */
383 if (_password_store != NULL && claws_fputs("\n", fp) == EOF) {
384 FILE_OP_ERROR("password store", "claws_fputs");
385 return -1;
388 /* Write out each password store block */
389 for (item = _password_store; item != NULL; item = item->next) {
390 block = (PasswordBlock*)item->data;
391 if (block == NULL)
392 continue; /* Just in case. */
394 /* Do not save empty blocks. */
395 if (block->entries == NULL || g_hash_table_size(block->entries) == 0)
396 continue;
398 /* Prepare the section header string and write it out. */
399 typestr = NULL;
400 if (block->block_type == PWS_CORE) {
401 typestr = "core";
402 } else if (block->block_type == PWS_ACCOUNT) {
403 typestr = "account";
404 } else if (block->block_type == PWS_PLUGIN) {
405 typestr = "plugin";
407 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
409 if (claws_fputs(line, fp) == EOF) {
410 FILE_OP_ERROR("password store", "claws_fputs");
411 g_free(line);
412 return -1;
414 g_free(line);
416 /* Now go through all passwords in the block and write each out. */
417 keys = g_hash_table_get_keys(block->entries);
418 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
419 key = (gchar *)eitem->data;
420 if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL)
421 continue;
423 line = g_strdup_printf("%s %s\n", key, pwd);
424 if (claws_fputs(line, fp) == EOF) {
425 FILE_OP_ERROR("password store", "claws_fputs");
426 g_free(line);
427 return -1;
429 g_free(line);
431 g_list_free(keys);
433 /* Add a separating new line if there is another block remaining */
434 if (item->next != NULL && claws_fputs("\n", fp) == EOF) {
435 FILE_OP_ERROR("password store", "claws_fputs");
436 return -1;
441 return 1;
444 void passwd_store_write_config(void)
446 gchar *rcpath;
447 PrefFile *pfile;
449 debug_print("Writing password store...\n");
451 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
452 PASSWORD_STORE_RC, NULL);
454 if ((pfile = prefs_write_open(rcpath)) == NULL) {
455 g_warning("failed to open password store file for writing");
456 g_free(rcpath);
457 return;
460 g_free(rcpath);
462 if (_write_to_file(pfile->fp) < 0) {
463 g_warning("failed to write password store to file");
464 prefs_file_close_revert(pfile);
465 } else if (prefs_file_close(pfile) < 0) {
466 g_warning("failed to properly close password store file after writing");
470 int passwd_store_read_config(void)
472 gchar *rcpath, *contents, **lines, **line, *typestr, *name;
473 GError *error = NULL;
474 guint i = 0;
475 PasswordBlock *block = NULL;
476 PasswordBlockType type;
477 gboolean reading_config_version = FALSE;
478 gint config_version = -1;
480 /* TODO: passwd_store_clear(); */
482 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
483 PASSWORD_STORE_RC, NULL);
485 debug_print("Reading password store from file '%s'\n", rcpath);
487 if (!g_file_test(rcpath, G_FILE_TEST_EXISTS)) {
488 debug_print("File does not exist, looks like a new configuration.\n");
489 g_free(rcpath);
490 return 0;
493 if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
494 g_warning("couldn't read password store from file: %s", error->message);
495 g_error_free(error);
496 g_free(rcpath);
497 return -1;
499 g_free(rcpath);
501 lines = g_strsplit(contents, "\n", -1);
503 g_free(contents);
505 while (lines[i] != NULL) {
506 if (*lines[i] == '[') {
507 /* Beginning of a new block */
508 line = g_strsplit_set(lines[i], "[:]", -1);
509 if (line[0] != NULL && strlen(line[0]) == 0
510 && line[1] != NULL && strlen(line[1]) > 0
511 && line[2] != NULL && strlen(line[2]) > 0
512 && line[3] != NULL && strlen(line[3]) == 0) {
513 typestr = line[1];
514 name = line[2];
515 if (!strcmp(typestr, "core")) {
516 type = PWS_CORE;
517 } else if (!strcmp(typestr, "account")) {
518 type = PWS_ACCOUNT;
519 } else if (!strcmp(typestr, "plugin")) {
520 type = PWS_PLUGIN;
521 } else if (!strcmp(typestr, "config_version")) {
522 reading_config_version = TRUE;
523 config_version = atoi(name);
524 } else {
525 debug_print("Unknown password block type: '%s'\n", typestr);
526 g_strfreev(line);
527 i++; continue;
530 if (reading_config_version) {
531 if (config_version < 0) {
532 debug_print("config_version:%d looks invalid, ignoring it\n",
533 config_version);
534 config_version = -1; /* set to default value if missing */
535 g_strfreev(line);
536 i++; continue;
538 debug_print("config_version in file is %d\n", config_version);
539 reading_config_version = FALSE;
540 } else {
541 if ((block = _new_block(type, name)) == NULL) {
542 debug_print("Duplicate password block, ignoring: (%d/%s)\n",
543 type, name);
544 g_strfreev(line);
545 i++; continue;
549 g_strfreev(line);
550 } else if (strlen(lines[i]) > 0 && block != NULL) {
551 /* If we have started a password block, test for a
552 * "password_id = password" line. */
553 line = g_strsplit(lines[i], " ", -1);
554 if (line[0] != NULL && strlen(line[0]) > 0
555 && line[1] != NULL && strlen(line[1]) > 0
556 && line[2] == NULL) {
557 debug_print("Adding password '%s'\n", line[0]);
558 g_hash_table_insert(block->entries,
559 g_strdup(line[0]), g_strdup(line[1]));
561 g_strfreev(line);
563 i++;
565 g_strfreev(lines);
567 if (prefs_update_config_version_password_store(config_version) < 0) {
568 debug_print("Password store configuration file version upgrade failed\n");
569 return -2;
572 return g_slist_length(_password_store);