talons

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

passwordstore.c (15615B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 2016 The Claws Mail Team
      4  *
      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.
      9  *
     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.
     14  *
     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/>.
     17  *
     18  */
     19 
     20 # include <gnutls/gnutls.h>
     21 # include <gnutls/crypto.h>
     22 
     23 #include <glib.h>
     24 #include <glib/gi18n.h>
     25 
     26 #include "common/defs.h"
     27 #include "common/utils.h"
     28 #include "passwordstore.h"
     29 #include "password.h"
     30 #include "prefs_common.h"
     31 #include "prefs_gtk.h"
     32 #include "file-utils.h"
     33 
     34 static GSList *_password_store;
     35 
     36 /* Finds password block of given type and name in the pwdstore
     37  * and returns a pointer to it, if it exists.
     38  * If link parameter is non-null, it is set to the linked list
     39  * element containing this block. */
     40 static PasswordBlock *_get_block(PasswordBlockType block_type,
     41 		const gchar *block_name, GSList **link)
     42 {
     43 	GSList *item;
     44 	PasswordBlock *block;
     45 
     46 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
     47 	g_return_val_if_fail(block_name != NULL, NULL);
     48 
     49 	for (item = _password_store; item != NULL; item = item->next) {
     50 		block = (PasswordBlock *)item->data;
     51 		if (block->block_type == block_type &&
     52 				!strcmp(block->block_name, block_name)) {
     53 			if (link != NULL)
     54 				*link = item;
     55 			return block;
     56 		}
     57 	}
     58 
     59 	return NULL;
     60 }
     61 
     62 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
     63 {
     64 	if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
     65 		return TRUE;
     66 	return FALSE;
     67 }
     68 
     69 /* Creates a new, empty block and adds it to the pwdstore. */
     70 static PasswordBlock *_new_block(PasswordBlockType block_type,
     71 		const gchar *block_name)
     72 {
     73 	PasswordBlock *block;
     74 
     75 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
     76 	g_return_val_if_fail(block_name != NULL, NULL);
     77 
     78 	/* First check to see if the block doesn't already exist. */
     79 	if (_get_block(block_type, block_name, NULL)) {
     80 		debug_print("Block (%d/%s) already exists.\n",
     81 				block_type, block_name);
     82 		return NULL;
     83 	}
     84 
     85 	/* Let's create an empty block, and add it to pwdstore. */
     86 	block = g_new0(PasswordBlock, 1);
     87 	block->block_type = block_type;
     88 	block->block_name = g_strdup(block_name);
     89 	block->entries = g_hash_table_new_full(g_str_hash,
     90 			(GEqualFunc)_hash_equal_func,
     91 			g_free, g_free);
     92 	debug_print("Created password block (%d/%s)\n",
     93 			block_type, block_name);
     94 
     95 	_password_store = g_slist_append(_password_store, block);
     96 
     97 	return block;
     98 }
     99 
    100 static void _delete_block(PasswordBlock *block)
    101 {
    102 	g_return_if_fail(block != NULL);
    103 
    104 	if (block->block_name != NULL)
    105 		g_free(block->block_name);
    106 
    107 	if (block->entries != NULL)
    108 		g_hash_table_destroy(block->entries);
    109 
    110 	g_free(block);
    111 }
    112 
    113 /*************************************************************/
    114 
    115 /* Stores a password. */
    116 gboolean passwd_store_set(PasswordBlockType block_type,
    117 		const gchar *block_name,
    118 		const gchar *password_id,
    119 		const gchar *password,
    120 		gboolean encrypted)
    121 {
    122 	const gchar *p;
    123 	PasswordBlock *block;
    124 	gchar *encrypted_password;
    125 
    126 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
    127 	g_return_val_if_fail(block_name != NULL, FALSE);
    128 	g_return_val_if_fail(password_id != NULL, FALSE);
    129 
    130 	/* Empty password string equals null password for us. */
    131 	if (password == NULL || strlen(password) == 0)
    132 		p = NULL;
    133 	else
    134 		p = password;
    135 
    136 	/* find correct block (create if needed) */
    137 	if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
    138 		/* If caller wants to delete a password, and even its block
    139 		 * doesn't exist, we're done. */
    140 		if (p == NULL)
    141 			return TRUE;
    142 
    143 		if ((block = _new_block(block_type, block_name)) == NULL) {
    144 			debug_print("Could not create password block (%d/%s)\n",
    145 					block_type, block_name);
    146 			return FALSE;
    147 		}
    148 	}
    149 
    150 	if (p == NULL) {
    151 		/* NULL password was passed to us, so delete the entry with
    152 		 * corresponding id, if it exists */
    153 		if (g_hash_table_lookup(block->entries, password_id) != NULL) {
    154 			debug_print("Deleting password for '%s' in block (%d/%s)\n",
    155 					password_id, block_type, block_name);
    156 			g_hash_table_remove(block->entries, password_id);
    157 		}
    158 	} else {
    159 		debug_print("Setting password for '%s' in block (%d/%s)%s\n",
    160 				password_id, block_type, block_name,
    161 				(encrypted ? ", already encrypted" : ""));
    162 		if (!encrypted) {
    163 			/* encrypt password before saving it */
    164 			if ((encrypted_password =
    165 						password_encrypt(p, NULL)) == NULL) {
    166 				debug_print("Could not encrypt password '%s' for block (%d/%s).\n",
    167 						password_id, block_type, block_name);
    168 				return FALSE;
    169 			}
    170 		} else {
    171 			/* password is already in encrypted form already */
    172 			encrypted_password = g_strdup(p);
    173 		}
    174 
    175 		/* add encrypted password to the block */
    176 		g_hash_table_insert(block->entries,
    177 				g_strdup(password_id),
    178 				encrypted_password);
    179 	}
    180 
    181 	return TRUE;
    182 }
    183 
    184 /* Retrieves a password. */
    185 gchar *passwd_store_get(PasswordBlockType block_type,
    186 		const gchar *block_name,
    187 		const gchar *password_id)
    188 {
    189 	PasswordBlock *block;
    190 	gchar *encrypted_password, *password;
    191 
    192 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
    193 	g_return_val_if_fail(block_name != NULL, NULL);
    194 	g_return_val_if_fail(password_id != NULL, NULL);
    195 
    196 	debug_print("Getting password '%s' from block (%d/%s)\n",
    197 			password_id, block_type, block_name);
    198 
    199 	/* find correct block */
    200 	if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
    201 		debug_print("Block (%d/%s) not found.\n", block_type, block_name);
    202 		return NULL;
    203 	}
    204 
    205 	/* grab pointer to encrypted password */
    206 	if ((encrypted_password =
    207 				g_hash_table_lookup(block->entries, password_id)) == NULL) {
    208 		debug_print("Password '%s' in block (%d/%s) not found.\n",
    209 				password_id, block_type, block_name);
    210 		return NULL;
    211 	}
    212 
    213 	/* decrypt password */
    214 	if ((password =
    215 				password_decrypt(encrypted_password, NULL)) == NULL) {
    216 		debug_print("Could not decrypt password '%s' for block (%d/%s).\n",
    217 				password_id, block_type, block_name);
    218 		return NULL;
    219 	}
    220 
    221 	/* return decrypted password */
    222 	return password;
    223 }
    224 
    225 /* Checks if a password exists in the password store.
    226  * No decryption happens. */
    227 gboolean passwd_store_has_password(PasswordBlockType block_type,
    228 		const gchar *block_name,
    229 		const gchar *password_id)
    230 {
    231 	PasswordBlock *block;
    232 
    233 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
    234 	g_return_val_if_fail(block_name != NULL, FALSE);
    235 	g_return_val_if_fail(password_id != NULL, FALSE);
    236 
    237 	/* find correct block */
    238 	if ((block = _get_block(block_type, block_name, NULL)) == NULL) {
    239 		debug_print("Block (%d/%s) not found.\n", block_type, block_name);
    240 		return FALSE;
    241 	}
    242 
    243 	/* do we have specified password in this block? */
    244 	if (g_hash_table_lookup(block->entries, password_id) != NULL) {
    245 		return TRUE; /* yes */
    246 	}
    247 
    248 	return FALSE; /* no */
    249 }
    250 
    251 
    252 gboolean passwd_store_delete_block(PasswordBlockType block_type,
    253 		const gchar *block_name)
    254 {
    255 	PasswordBlock *block;
    256 	GSList *link = NULL;
    257 
    258 	g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
    259 	g_return_val_if_fail(block_name != NULL, FALSE);
    260 
    261 	debug_print("Deleting block (%d/%s)\n", block_type, block_name);
    262 
    263 	/* find correct block */
    264 	if ((block = _get_block(block_type, block_name, &link)) == NULL) {
    265 		debug_print("Block (%d/%s) not found.\n", block_type, block_name);
    266 		return FALSE;
    267 	}
    268 
    269 	/* free the block data and remove it from the list */
    270 	_delete_block(block);
    271 	_password_store = g_slist_delete_link(_password_store, link);
    272 	return TRUE;
    273 }
    274 
    275 gboolean passwd_store_set_account(gint account_id,
    276 		const gchar *password_id,
    277 		const gchar *password,
    278 		gboolean encrypted)
    279 {
    280 	gchar *uid = g_strdup_printf("%d", account_id);
    281 	gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
    282 			password_id, password, encrypted);
    283 	g_free(uid);
    284 	return ret;
    285 }
    286 
    287 gchar *passwd_store_get_account(gint account_id,
    288 		const gchar *password_id)
    289 {
    290 	gchar *uid = g_strdup_printf("%d", account_id);
    291 	gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
    292 	g_free(uid);
    293 	return ret;
    294 }
    295 
    296 gboolean passwd_store_has_password_account(gint account_id,
    297 		const gchar *password_id)
    298 {
    299 	gchar *uid = g_strdup_printf("%d", account_id);
    300 	gboolean ret = passwd_store_has_password(PWS_ACCOUNT, uid, password_id);
    301 	g_free(uid);
    302 	return ret;
    303 }
    304 
    305 /* Reencrypts all stored passwords. */
    306 void passwd_store_reencrypt_all(const gchar *old_mpwd,
    307 		const gchar *new_mpwd)
    308 {
    309 	PasswordBlock *block;
    310 	GSList *item;
    311 	GList *keys, *eitem;
    312 	gchar *encrypted_password, *decrypted_password, *key;
    313 
    314 	g_return_if_fail(old_mpwd != NULL);
    315 	g_return_if_fail(new_mpwd != NULL);
    316 
    317 	for (item = _password_store; item != NULL; item = item->next) {
    318 		block = (PasswordBlock *)item->data;
    319 		if (block == NULL)
    320 			continue; /* Just in case. */
    321 
    322 		debug_print("Reencrypting passwords in block (%d/%s).\n",
    323 				block->block_type, block->block_name);
    324 
    325 		if (block->entries == NULL || g_hash_table_size(block->entries) == 0)
    326 			continue;
    327 
    328 		keys = g_hash_table_get_keys(block->entries);
    329 		for (eitem = keys; eitem != NULL; eitem = eitem->next) {
    330 			key = (gchar *)eitem->data;
    331 			if ((encrypted_password =
    332 						g_hash_table_lookup(block->entries, key)) == NULL)
    333 				continue;
    334 
    335 			if ((decrypted_password =
    336 						password_decrypt(encrypted_password, old_mpwd)) == NULL) {
    337 				debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
    338 				continue;
    339 			}
    340 
    341 			encrypted_password = password_encrypt(decrypted_password, new_mpwd);
    342 			memset(decrypted_password, 0, strlen(decrypted_password));
    343 			g_free(decrypted_password);
    344 			if (encrypted_password == NULL) {
    345 				debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key);
    346 				continue;
    347 			}
    348 
    349 			g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
    350 		}
    351 
    352 		g_list_free(keys);
    353 	}
    354 
    355 	debug_print("Reencrypting done.\n");
    356 }
    357 
    358 static gint _write_to_file(FILE *fp)
    359 {
    360 	PasswordBlock *block;
    361 	GSList *item;
    362 	GList *keys, *eitem;
    363 	gchar *typestr, *line, *key, *pwd;
    364 
    365 	/* Write out the config_version */
    366 	line = g_strdup_printf("[config_version:%d]\n", CLAWS_CONFIG_VERSION);
    367 	if (fputs(line, fp) == EOF) {
    368 		FILE_OP_ERROR("password store, config_version", "fputs");
    369 		g_free(line);
    370 		return -1;
    371 	}
    372 	g_free(line);
    373 
    374 	/* Add a newline if needed */
    375 	if (_password_store != NULL && fputs("\n", fp) == EOF) {
    376 		FILE_OP_ERROR("password store", "fputs");
    377 		return -1;
    378 	}
    379 
    380 	/* Write out each password store block */
    381 	for (item = _password_store; item != NULL; item = item->next) {
    382 		block = (PasswordBlock*)item->data;
    383 		if (block == NULL)
    384 			continue; /* Just in case. */
    385 
    386 		/* Do not save empty blocks. */
    387 		if (block->entries == NULL || g_hash_table_size(block->entries) == 0)
    388 			continue;
    389 
    390 		/* Prepare the section header string and write it out. */
    391 		typestr = NULL;
    392 		if (block->block_type == PWS_CORE) {
    393 			typestr = "core";
    394 		} else if (block->block_type == PWS_ACCOUNT) {
    395 			typestr = "account";
    396 		} else if (block->block_type == PWS_PLUGIN) {
    397 			typestr = "plugin";
    398 		}
    399 		line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
    400 
    401 		if (fputs(line, fp) == EOF) {
    402 			FILE_OP_ERROR("password store", "fputs");
    403 			g_free(line);
    404 			return -1;
    405 		}
    406 		g_free(line);
    407 
    408 		/* Now go through all passwords in the block and write each out. */
    409 		keys = g_hash_table_get_keys(block->entries);
    410 		for (eitem = keys; eitem != NULL; eitem = eitem->next) {
    411 			key = (gchar *)eitem->data;
    412 			if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL)
    413 				continue;
    414 
    415 			line = g_strdup_printf("%s %s\n", key, pwd);
    416 			if (fputs(line, fp) == EOF) {
    417 				FILE_OP_ERROR("password store", "fputs");
    418 				g_free(line);
    419 				return -1;
    420 			}
    421 			g_free(line);
    422 		}
    423 		g_list_free(keys);
    424 
    425 		/* Add a separating new line if there is another block remaining */
    426 		if (item->next != NULL && fputs("\n", fp) == EOF) {
    427 			FILE_OP_ERROR("password store", "fputs");
    428 			return -1;
    429 		}
    430 
    431 	}
    432 
    433 	return 1;
    434 }
    435 
    436 void passwd_store_write_config(void)
    437 {
    438 	gchar *rcpath;
    439 	PrefFile *pfile;
    440 
    441 	debug_print("Writing password store...\n");
    442 
    443 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
    444 			PASSWORD_STORE_RC, NULL);
    445 
    446 	if ((pfile = prefs_write_open(rcpath)) == NULL) {
    447 		g_warning("failed to open password store file for writing");
    448 		g_free(rcpath);
    449 		return;
    450 	}
    451 
    452 	g_free(rcpath);
    453 
    454 	if (_write_to_file(pfile->fp) < 0) {
    455 		g_warning("failed to write password store to file");
    456 		prefs_file_close_revert(pfile);
    457 	} else if (prefs_file_close(pfile) < 0) {
    458 		g_warning("failed to properly close password store file after writing");
    459 	}
    460 }
    461 
    462 int passwd_store_read_config(void)
    463 {
    464 	gchar *rcpath, *contents, **lines, **line, *typestr, *name;
    465 	GError *error = NULL;
    466 	guint i = 0;
    467 	PasswordBlock *block = NULL;
    468 	PasswordBlockType type;
    469 	gboolean reading_config_version = FALSE;
    470 	gint config_version = -1;
    471 
    472 	/* TODO: passwd_store_clear(); */
    473 
    474 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
    475 			PASSWORD_STORE_RC, NULL);
    476 
    477 	debug_print("Reading password store from file '%s'\n", rcpath);
    478 
    479 	if (!g_file_test(rcpath, G_FILE_TEST_EXISTS)) {
    480 		debug_print("File does not exist, looks like a new configuration.\n");
    481 		g_free(rcpath);
    482 		return 0;
    483 	}
    484 
    485 	if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
    486 		g_warning("couldn't read password store from file: %s", error->message);
    487 		g_error_free(error);
    488 		g_free(rcpath);
    489 		return -1;
    490 	}
    491 	g_free(rcpath);
    492 
    493 	lines = g_strsplit(contents, "\n", -1);
    494 
    495 	g_free(contents);
    496 
    497 	while (lines[i] != NULL) {
    498 		if (*lines[i] == '[') {
    499 			/* Beginning of a new block */
    500 			line = g_strsplit_set(lines[i], "[:]", -1);
    501 			if (line[0] != NULL && strlen(line[0]) == 0
    502 					&& line[1] != NULL && strlen(line[1]) > 0
    503 					&& line[2] != NULL && strlen(line[2]) > 0
    504 					&& line[3] != NULL && strlen(line[3]) == 0) {
    505 				typestr = line[1];
    506 				name = line[2];
    507 				if (!strcmp(typestr, "core")) {
    508 					type = PWS_CORE;
    509 				} else if (!strcmp(typestr, "account")) {
    510 					type = PWS_ACCOUNT;
    511 				} else if (!strcmp(typestr, "plugin")) {
    512 					type = PWS_PLUGIN;
    513 				} else if (!strcmp(typestr, "config_version")) {
    514 					reading_config_version = TRUE;
    515 					config_version = atoi(name);
    516 				} else {
    517 					debug_print("Unknown password block type: '%s'\n", typestr);
    518 					g_strfreev(line);
    519 					i++; continue;
    520 				}
    521 
    522 				if (reading_config_version) {
    523 					if (config_version < 0) {
    524 						debug_print("config_version:%d looks invalid, ignoring it\n",
    525 								config_version);
    526 						config_version = -1; /* set to default value if missing */
    527 						g_strfreev(line);
    528 						i++; continue;
    529 					}
    530 					debug_print("config_version in file is %d\n", config_version);
    531 					reading_config_version = FALSE;
    532 				} else {
    533 					if ((block = _new_block(type, name)) == NULL) {
    534 						debug_print("Duplicate password block, ignoring: (%d/%s)\n",
    535 								type, name);
    536 						g_strfreev(line);
    537 						i++; continue;
    538 					}
    539 				}
    540 			}
    541 			g_strfreev(line);
    542 		} else if (strlen(lines[i]) > 0 && block != NULL) {
    543 			/* If we have started a password block, test for a
    544 			 * "password_id = password" line. */
    545 			line = g_strsplit(lines[i], " ", -1);
    546 			if (line[0] != NULL && strlen(line[0]) > 0
    547 					&& line[1] != NULL && strlen(line[1]) > 0
    548 					&& line[2] == NULL) {
    549 				debug_print("Adding password '%s'\n", line[0]);
    550 				g_hash_table_insert(block->entries,
    551 						g_strdup(line[0]), g_strdup(line[1]));
    552 			}
    553 			g_strfreev(line);
    554 		}
    555 		i++;
    556 	}
    557 	g_strfreev(lines);
    558 
    559 	return g_slist_length(_password_store);
    560 }