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