talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

commit 2a6f8d16b275c63c5daa373d58d5ae867f1f6ba2
parent 1b1298656654a49f69f538e89021fc77749c8e9b
Author: Andrej Kacian <ticho@claws-mail.org>
Date:   Thu, 18 Feb 2016 22:25:55 +0100

Implement a password store.

Diffstat:
Msrc/Makefile.am | 2++
Msrc/common/defs.h | 1+
Asrc/passwordstore.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/passwordstore.h | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 489 insertions(+), 0 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am @@ -178,6 +178,7 @@ claws_mail_SOURCES = \ partial_download.c \ password.c \ password_gtk.c \ + passwordstore.c \ pop.c \ prefs_account.c \ prefs_actions.c \ @@ -295,6 +296,7 @@ claws_mailinclude_HEADERS = \ partial_download.h \ password.h \ password_gtk.h \ + passwordstore.h \ pop.h \ prefs_account.h \ prefs_actions.h \ diff --git a/src/common/defs.h b/src/common/defs.h @@ -61,6 +61,7 @@ #define MENU_RC "menurc" #define RENDERER_RC "rendererrc" #define TAGS_RC "tagsrc" +#define PASSWORD_STORE_RC "passwordstorerc" #define QUICKSEARCH_HISTORY "quicksearch_history" #define SUMMARY_SEARCH_FROM_HISTORY "summarysearch_from_history" #define SUMMARY_SEARCH_TO_HISTORY "summarysearch_to_history" diff --git a/src/passwordstore.c b/src/passwordstore.c @@ -0,0 +1,411 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#include "claws-features.h" +#endif + +#ifdef PASSWORD_CRYPTO_GNUTLS +# include <gnutls/gnutls.h> +# include <gnutls/crypto.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> + +#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) +{ + 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); + + debug_print("%s password '%s' in block (%d/%s)%s\n", + (password == 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 && + (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 (password == 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(password, 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(password); + } + + // 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; +} + +/* 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); +} diff --git a/src/passwordstore.h b/src/passwordstore.h @@ -0,0 +1,75 @@ +/* + * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client + * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __PASSWORDSTORE_H +#define __PASSWORDSTORE_H + +#include <glib.h> + +typedef enum { + PWS_CORE, + PWS_ACCOUNT, + PWS_PLUGIN, + NUM_PWS_TYPES +} PasswordBlockType; + +typedef struct _PasswordBlock { + PasswordBlockType block_type; + gchar *block_name; + GHashTable *entries; +} PasswordBlock; + +/* Saves a password into a chosen password block. + * The block is created if it doesn't exist. + * block_type - from PasswordBlockType + * block_name - name of the block + * password_id - ID of the saved password + * password - the actual password string + * encrypted - TRUE if the password string is in an already encrypted form + * (useful mostly for migration from accountrc) + * + * Function will make a copy of the strings where necessary and doesn't + * take ownership of any of the passed arguments. */ +gboolean passwd_store_set(PasswordBlockType block_type, + const gchar *block_name, + const gchar *password_id, + const gchar *password, + gboolean encrypted); + +/* Retrieves a password. Returned string should be freed by the caller. */ +gchar *passwd_store_get(PasswordBlockType block_type, + const gchar *block_name, + const gchar *password_id); + +/* Reencrypts all stored passwords using new_mpwd as an encryption + * password. */ +void passwd_store_reencrypt_all(const gchar *old_mpwd, + const gchar *new_mpwd); + +/* Writes/reads password store to/from file. */ +void passwd_store_write_config(void); +void passwd_store_read_config(void); + +/* Macros for standard, predefined password IDs. */ +#define PWS_ACCOUNT_RECV "recv" +#define PWS_ACCOUNT_SEND "send" +#define PWS_ACCOUNT_RECV_CERT "recv_cert" +#define PWS_ACCOUNT_SEND_CERT "send_cert" + +#endif /* __PASSWORDSTORE_H */