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/>.
22 #include "claws-features.h"
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
31 #include <glib/gi18n.h>
33 #include "common/defs.h"
34 #include "common/utils.h"
35 #include "passwordstore.h"
37 #include "prefs_common.h"
38 #include "prefs_gtk.h"
39 #include "prefs_migration.h"
42 static GSList *_password_store;
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)
51 g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
52 g_return_val_if_fail(block_name != NULL, NULL);
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))
64 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
66 if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
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)
77 g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
78 g_return_val_if_fail(block_name != NULL, NULL);
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);
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,
94 debug_print("Created password block (%d/%s)\n",
95 block_type, block_name);
97 _password_store = g_slist_append(_password_store, block);
102 /*************************************************************/
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,
112 PasswordBlock *block;
113 gchar *encrypted_password;
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);
119 /* Empty password string equals null password for us. */
120 if (password == NULL || strlen(password) == 0)
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" : "") );
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. */
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);
145 /* NULL password was passed to us, so delete the entry with
146 * corresponding id */
147 g_hash_table_remove(block->entries, password_id);
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);
158 /* password is already in encrypted form already */
159 encrypted_password = g_strdup(p);
162 /* add encrypted password to the block */
163 g_hash_table_insert(block->entries,
164 g_strdup(password_id),
171 /* Retrieves a password. */
172 gchar *passwd_store_get(PasswordBlockType block_type,
173 const gchar *block_name,
174 const gchar *password_id)
176 PasswordBlock *block;
177 gchar *encrypted_password, *password;
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);
183 debug_print("Getting password '%s' from block (%d/%s)\n",
184 password_id, block_type, block_name);
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);
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);
200 /* decrypt 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);
208 /* return decrypted password */
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)
218 PasswordBlock *block;
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);
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);
230 /* do we have specified password in this block? */
231 if (g_hash_table_lookup(block->entries, password_id) != NULL) {
232 return TRUE; /* yes */
235 return FALSE; /* no */
239 gboolean passwd_store_delete_block(PasswordBlockType block_type,
240 const gchar *block_name)
242 PasswordBlock *block;
244 g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
245 g_return_val_if_fail(block_name != NULL, FALSE);
247 debug_print("Deleting block (%d/%s)\n", block_type, block_name);
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);
255 g_hash_table_destroy(block->entries);
256 block->entries = NULL;
260 gboolean passwd_store_set_account(gint account_id,
261 const gchar *password_id,
262 const gchar *password,
265 gchar *uid = g_strdup_printf("%d", account_id);
266 gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
267 password_id, password, encrypted);
272 gchar *passwd_store_get_account(gint account_id,
273 const gchar *password_id)
275 gchar *uid = g_strdup_printf("%d", account_id);
276 gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
281 gboolean passwd_store_has_password_account(gint account_id,
282 const gchar *password_id)
284 gchar *uid = g_strdup_printf("%d", account_id);
285 gboolean ret = passwd_store_has_password(PWS_ACCOUNT, uid, password_id);
290 /* Reencrypts all stored passwords. */
291 void passwd_store_reencrypt_all(const gchar *old_mpwd,
292 const gchar *new_mpwd)
294 PasswordBlock *block;
297 gchar *encrypted_password, *decrypted_password, *key;
299 g_return_if_fail(old_mpwd != NULL);
300 g_return_if_fail(new_mpwd != NULL);
302 for (item = _password_store; item != NULL; item = item->next) {
303 block = (PasswordBlock *)item->data;
305 continue; /* Just in case. */
307 debug_print("Reencrypting passwords in block (%d/%s).\n",
308 block->block_type, block->block_name);
310 if (g_hash_table_size(block->entries) == 0)
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)
320 if ((decrypted_password =
321 password_decrypt(encrypted_password, old_mpwd)) == NULL) {
322 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
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);
334 g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
340 debug_print("Reencrypting done.\n");
343 static gint _write_to_file(FILE *fp)
345 PasswordBlock *block;
348 gchar *typestr, *line, *key, *pwd;
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");
359 /* Add a newline if needed */
360 if (_password_store != NULL && claws_fputs("\n", fp) == EOF) {
361 FILE_OP_ERROR("password store", "claws_fputs");
365 /* Write out each password store block */
366 for (item = _password_store; item != NULL; item = item->next) {
367 block = (PasswordBlock*)item->data;
369 continue; /* Just in case. */
371 /* Do not save empty blocks. */
372 if (g_hash_table_size(block->entries) == 0)
375 /* Prepare the section header string and write it out. */
377 if (block->block_type == PWS_CORE) {
379 } else if (block->block_type == PWS_ACCOUNT) {
381 } else if (block->block_type == PWS_PLUGIN) {
384 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
386 if (claws_fputs(line, fp) == EOF) {
387 FILE_OP_ERROR("password store", "claws_fputs");
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)
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");
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");
421 void passwd_store_write_config(void)
426 debug_print("Writing password store...\n");
428 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
429 PASSWORD_STORE_RC, NULL);
431 if ((pfile = prefs_write_open(rcpath)) == NULL) {
432 g_warning("failed to open password store file for writing");
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");
447 int passwd_store_read_config(void)
449 gchar *rcpath, *contents, **lines, **line, *typestr, *name;
450 GError *error = NULL;
452 PasswordBlock *block = NULL;
453 PasswordBlockType type;
454 gboolean reading_config_version = FALSE;
455 gint config_version = -1;
457 /* TODO: passwd_store_clear(); */
459 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
460 PASSWORD_STORE_RC, NULL);
462 debug_print("Reading password store from file '%s'\n", rcpath);
464 if (!g_file_test(rcpath, G_FILE_TEST_EXISTS)) {
465 debug_print("File does not exist, looks like a new configuration.\n");
470 if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
471 g_warning("couldn't read password store from file: %s", error->message);
478 lines = g_strsplit(contents, "\n", -1);
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) {
492 if (!strcmp(typestr, "core")) {
494 } else if (!strcmp(typestr, "account")) {
496 } else if (!strcmp(typestr, "plugin")) {
498 } else if (!strcmp(typestr, "config_version")) {
499 reading_config_version = TRUE;
500 config_version = atoi(name);
502 debug_print("Unknown password block type: '%s'\n", typestr);
507 if (reading_config_version) {
508 if (config_version < 0) {
509 debug_print("config_version:%d looks invalid, ignoring it\n",
511 config_version = -1; /* set to default value if missing */
514 debug_print("config_version in file is %d\n", config_version);
515 reading_config_version = FALSE;
517 if ((block = _new_block(type, name)) == NULL) {
518 debug_print("Duplicate password block, ignoring: (%d/%s)\n",
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]));
543 if (prefs_update_config_version_password_store(config_version) < 0) {
544 debug_print("Password store configuration file version upgrade failed\n");
548 return g_slist_length(_password_store);