Implement a password store.
authorAndrej Kacian <ticho@claws-mail.org>
Thu, 18 Feb 2016 21:25:55 +0000 (22:25 +0100)
committerAndrej Kacian <ticho@claws-mail.org>
Tue, 1 Mar 2016 19:50:35 +0000 (20:50 +0100)
src/Makefile.am
src/common/defs.h
src/passwordstore.c [new file with mode: 0644]
src/passwordstore.h [new file with mode: 0644]

index f958fd5..00c2ae7 100644 (file)
@@ -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 \
index 722f386..c19a26e 100644 (file)
@@ -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
new file mode 100644 (file)
index 0000000..8e3942a
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..731027c
--- /dev/null
@@ -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 */