/* * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 2016 The Claws Mail Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifdef HAVE_CONFIG_H #include "config.h" #include "claws-features.h" #endif #ifdef PASSWORD_CRYPTO_GNUTLS # include # include #endif #include #include #include "common/defs.h" #include "common/utils.h" #include "passwordstore.h" #include "password.h" #include "prefs_gtk.h" static GSList *_password_store; /* Finds password block of given type and name in the pwdstore. */ static PasswordBlock *_get_block(PasswordBlockType block_type, const gchar *block_name) { GSList *item; PasswordBlock *block; g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES, NULL); g_return_val_if_fail(block_name != NULL, NULL); for (item = _password_store; item != NULL; item = item->next) { block = (PasswordBlock *)item->data; if (block->block_type == block_type && !strcmp(block->block_name, block_name)) return block; } return NULL; } static gboolean _hash_equal_func(gconstpointer a, gconstpointer b) { if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0) return TRUE; return FALSE; } /* Creates a new, empty block and adds it to the pwdstore. */ static PasswordBlock *_new_block(PasswordBlockType block_type, const gchar *block_name) { PasswordBlock *block; g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES, NULL); g_return_val_if_fail(block_name != NULL, NULL); /* First check to see if the block doesn't already exist. */ if (_get_block(block_type, block_name)) { debug_print("Block (%d/%s) already exists.\n", block_type, block_name); return NULL; } /* Let's create an empty block, and add it to pwdstore. */ block = g_new0(PasswordBlock, 1); block->block_type = block_type; block->block_name = g_strdup(block_name); block->entries = g_hash_table_new_full(g_str_hash, (GEqualFunc)_hash_equal_func, g_free, g_free); debug_print("Created password block (%d/%s)\n", block_type, block_name); _password_store = g_slist_append(_password_store, block); return block; } /////////////////////////////////////////////////////////////// /* Stores a password. */ gboolean passwd_store_set(PasswordBlockType block_type, const gchar *block_name, const gchar *password_id, const gchar *password, gboolean encrypted) { const gchar *p = password; PasswordBlock *block; gchar *encrypted_password; g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES, FALSE); g_return_val_if_fail(block_name != NULL, FALSE); g_return_val_if_fail(password_id != NULL, FALSE); /* Empty password string equals null password for us. */ if (strlen(password) == 0) p = NULL; debug_print("%s password '%s' in block (%d/%s)%s\n", (p == NULL ? "Deleting" : "Storing"), password_id, block_type, block_name, (encrypted ? ", already encrypted" : "") ); // find correct block (create if needed) if ((block = _get_block(block_type, block_name)) == NULL) { /* If caller wants to delete a password, and even its block * doesn't exist, we're done. */ if (p == NULL) return TRUE; if ((block = _new_block(block_type, block_name)) == NULL) { debug_print("Could not create password block (%d/%s)\n", block_type, block_name); return FALSE; } } if (p == NULL) { /* NULL password was passed to us, so delete the entry with * corresponding id */ g_hash_table_remove(block->entries, password_id); } else { if (!encrypted) { /* encrypt password before saving it */ if ((encrypted_password = password_encrypt(p, NULL)) == NULL) { debug_print("Could not encrypt password '%s' for block (%d/%s).\n", password_id, block_type, block_name); return FALSE; } } else { /* password is already in encrypted form already */ encrypted_password = g_strdup(p); } // add encrypted password to the block g_hash_table_insert(block->entries, g_strdup(password_id), encrypted_password); } return TRUE; } /* Retrieves a password. */ gchar *passwd_store_get(PasswordBlockType block_type, const gchar *block_name, const gchar *password_id) { PasswordBlock *block; gchar *encrypted_password, *password; g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES, NULL); g_return_val_if_fail(block_name != NULL, NULL); g_return_val_if_fail(password_id != NULL, NULL); debug_print("Getting password '%s' from block (%d/%s)\n", password_id, block_type, block_name); // find correct block if ((block = _get_block(block_type, block_name)) == NULL) { debug_print("Block (%d/%s) not found.\n", block_type, block_name); return NULL; } // grab pointer to encrypted password if ((encrypted_password = g_hash_table_lookup(block->entries, password_id)) == NULL) { debug_print("Password '%s' in block (%d/%s) not found.\n", password_id, block_type, block_name); return NULL; } // decrypt password if ((password = password_decrypt(encrypted_password, NULL)) == NULL) { debug_print("Could not decrypt password '%s' for block (%d/%s).\n", password_id, block_type, block_name); return NULL; } // return decrypted password return password; } gboolean passwd_store_delete_block(PasswordBlockType block_type, const gchar *block_name) { PasswordBlock *block; g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES, FALSE); g_return_val_if_fail(block_name != NULL, FALSE); debug_print("Deleting block (%d/%s)\n", block_type, block_name); // find correct block if ((block = _get_block(block_type, block_name)) == NULL) { debug_print("Block (%d/%s) not found.\n", block_type, block_name); return FALSE; } g_hash_table_destroy(block->entries); block->entries = NULL; return TRUE; } gboolean passwd_store_set_account(gint account_id, const gchar *password_id, const gchar *password, gboolean encrypted) { gchar *uid = g_strdup_printf("%d", account_id); gboolean ret = passwd_store_set(PWS_ACCOUNT, uid, password_id, password, encrypted); g_free(uid); return ret; } gchar *passwd_store_get_account(gint account_id, const gchar *password_id) { gchar *uid = g_strdup_printf("%d", account_id); gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id); g_free(uid); return ret; } /* Reencrypts all stored passwords. */ void passwd_store_reencrypt_all(const gchar *old_mpwd, const gchar *new_mpwd) { PasswordBlock *block; GSList *item; GList *keys, *eitem; gchar *encrypted_password, *decrypted_password, *key; g_return_if_fail(old_mpwd != NULL); g_return_if_fail(new_mpwd != NULL); for (item = _password_store; item != NULL; item = item->next) { block = (PasswordBlock *)item->data; if (block == NULL) continue; /* Just in case. */ debug_print("Reencrypting passwords in block (%d/%s).\n", block->block_type, block->block_name); if (g_hash_table_size(block->entries) == 0) continue; keys = g_hash_table_get_keys(block->entries); for (eitem = keys; eitem != NULL; eitem = eitem->next) { key = (gchar *)eitem->data; if ((encrypted_password = g_hash_table_lookup(block->entries, key)) == NULL) continue; if ((decrypted_password = password_decrypt(encrypted_password, old_mpwd)) == NULL) { debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key); continue; } encrypted_password = password_encrypt(decrypted_password, new_mpwd); memset(decrypted_password, 0, strlen(decrypted_password)); g_free(decrypted_password); if (encrypted_password == NULL) { debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key); continue; } g_hash_table_insert(block->entries, g_strdup(key), encrypted_password); } g_list_free(keys); } debug_print("Reencrypting done.\n"); } static gint _write_to_file(FILE *fp) { PasswordBlock *block; GSList *item; GList *keys, *eitem; gchar *typestr, *line, *key, *pwd; for (item = _password_store; item != NULL; item = item->next) { block = (PasswordBlock*)item->data; if (block == NULL) continue; /* Just in case. */ /* Do not save empty blocks. */ if (g_hash_table_size(block->entries) == 0) continue; /* Prepare the section header string and write it out. */ typestr = NULL; if (block->block_type == PWS_CORE) { typestr = "core"; } else if (block->block_type == PWS_ACCOUNT) { typestr = "account"; } else if (block->block_type == PWS_PLUGIN) { typestr = "plugin"; } line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name); if (fputs(line, fp) == EOF) { FILE_OP_ERROR("password store", "fputs"); g_free(line); return -1; } g_free(line); /* Now go through all passwords in the block and write each out. */ keys = g_hash_table_get_keys(block->entries); for (eitem = keys; eitem != NULL; eitem = eitem->next) { key = (gchar *)eitem->data; if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL) continue; line = g_strdup_printf("%s %s\n", key, pwd); if (fputs(line, fp) == EOF) { FILE_OP_ERROR("password store", "fputs"); g_free(line); return -1; } g_free(line); } g_list_free(keys); if (item->next != NULL && fputs("\n", fp) == EOF) { FILE_OP_ERROR("password store", "fputs"); return -1; } } return 1; } void passwd_store_write_config(void) { gchar *rcpath; PrefFile *pfile; debug_print("Writing password store...\n"); rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PASSWORD_STORE_RC, NULL); if ((pfile = prefs_write_open(rcpath)) == NULL) { g_warning("failed to open password store file for writing"); g_free(rcpath); return; } g_free(rcpath); if (_write_to_file(pfile->fp) < 0) { g_warning("failed to write password store to file"); prefs_file_close_revert(pfile); } else if (prefs_file_close(pfile) < 0) { g_warning("failed to properly close password store file after writing"); } } void passwd_store_read_config(void) { gchar *rcpath, *contents, **lines, **line, *typestr, *name; GError *error = NULL; guint i = 0; PasswordBlock *block = NULL; PasswordBlockType type; // TODO: passwd_store_clear(); debug_print("Reading password store from file...\n"); rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PASSWORD_STORE_RC, NULL); if (!g_file_get_contents(rcpath, &contents, NULL, &error)) { g_warning("couldn't read password store from file: %s\n", error->message); g_error_free(error); g_free(rcpath); return; } g_free(rcpath); lines = g_strsplit(contents, "\n", -1); while (lines[i] != NULL) { if (*lines[i] == '[') { /* Beginning of a new block */ line = g_strsplit_set(lines[i], "[:]", -1); if (line[0] != NULL && strlen(line[0]) == 0 && line[1] != NULL && strlen(line[1]) > 0 && line[2] != NULL && strlen(line[2]) > 0 && line[3] != NULL && strlen(line[3]) == 0) { typestr = line[1]; name = line[2]; if (!strcmp(typestr, "core")) { type = PWS_CORE; } else if (!strcmp(typestr, "account")) { type = PWS_ACCOUNT; } else if (!strcmp(typestr, "plugin")) { type = PWS_PLUGIN; } else { debug_print("Uknown password block type: '%s'\n", typestr); g_strfreev(line); i++; continue; } if ((block = _new_block(type, name)) == NULL) { debug_print("Duplicate password block, ignoring: (%d/%s)\n", type, name); g_strfreev(line); i++; continue; } } g_strfreev(line); } else if (strlen(lines[i]) > 0 && block != NULL) { /* If we have started a password block, test for a * "password_id = password" line. */ line = g_strsplit(lines[i], " ", -1); if (line[0] != NULL && strlen(line[0]) > 0 && line[1] != NULL && strlen(line[1]) > 0 && line[2] == NULL) { debug_print("Adding password '%s'\n", line[0]); g_hash_table_insert(block->entries, g_strdup(line[0]), g_strdup(line[1])); } g_strfreev(line); } i++; } g_strfreev(lines); }