Fix bug #3574: Template addressing
[claws.git] / src / passwordstore.c
blob95946efd9748a1d4fcfd7a6279bc2ddf96853264
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2016 The Claws Mail Team
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.
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.
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/>.
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #include "claws-features.h"
23 #endif
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
33 #include "common/defs.h"
34 #include "common/utils.h"
35 #include "passwordstore.h"
36 #include "password.h"
37 #include "prefs_gtk.h"
39 static GSList *_password_store;
41 /* Finds password block of given type and name in the pwdstore. */
42 static PasswordBlock *_get_block(PasswordBlockType block_type,
43 const gchar *block_name)
45 GSList *item;
46 PasswordBlock *block;
48 g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES,
49 NULL);
50 g_return_val_if_fail(block_name != NULL, NULL);
52 for (item = _password_store; item != NULL; item = item->next) {
53 block = (PasswordBlock *)item->data;
54 if (block->block_type == block_type &&
55 !strcmp(block->block_name, block_name))
56 return block;
59 return NULL;
62 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
64 if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
65 return TRUE;
66 return FALSE;
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)
73 PasswordBlock *block;
75 g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES,
76 NULL);
77 g_return_val_if_fail(block_name != NULL, NULL);
79 /* First check to see if the block doesn't already exist. */
80 if (_get_block(block_type, block_name)) {
81 debug_print("Block (%d/%s) already exists.\n",
82 block_type, block_name);
83 return NULL;
86 /* Let's create an empty block, and add it to pwdstore. */
87 block = g_new0(PasswordBlock, 1);
88 block->block_type = block_type;
89 block->block_name = g_strdup(block_name);
90 block->entries = g_hash_table_new_full(g_str_hash,
91 (GEqualFunc)_hash_equal_func,
92 g_free, g_free);
93 debug_print("Created password block (%d/%s)\n",
94 block_type, block_name);
96 _password_store = g_slist_append(_password_store, block);
98 return block;
101 ///////////////////////////////////////////////////////////////
103 /* Stores a password. */
104 gboolean passwd_store_set(PasswordBlockType block_type,
105 const gchar *block_name,
106 const gchar *password_id,
107 const gchar *password,
108 gboolean encrypted)
110 const gchar *p;
111 PasswordBlock *block;
112 gchar *encrypted_password;
114 g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES,
115 FALSE);
116 g_return_val_if_fail(block_name != NULL, FALSE);
117 g_return_val_if_fail(password_id != NULL, FALSE);
119 /* Empty password string equals null password for us. */
120 if (password == NULL || strlen(password) == 0)
121 p = NULL;
122 else
123 p = password;
125 debug_print("%s password '%s' in block (%d/%s)%s\n",
126 (p == NULL ? "Deleting" : "Storing"),
127 password_id, block_type, block_name,
128 (encrypted ? ", already encrypted" : "") );
130 // find correct block (create if needed)
131 if ((block = _get_block(block_type, block_name)) == NULL) {
132 /* If caller wants to delete a password, and even its block
133 * doesn't exist, we're done. */
134 if (p == NULL)
135 return TRUE;
137 if ((block = _new_block(block_type, block_name)) == NULL) {
138 debug_print("Could not create password block (%d/%s)\n",
139 block_type, block_name);
140 return FALSE;
144 if (p == NULL) {
145 /* NULL password was passed to us, so delete the entry with
146 * corresponding id */
147 g_hash_table_remove(block->entries, password_id);
148 } else {
149 if (!encrypted) {
150 /* encrypt password before saving it */
151 if ((encrypted_password =
152 password_encrypt(p, NULL)) == NULL) {
153 debug_print("Could not encrypt password '%s' for block (%d/%s).\n",
154 password_id, block_type, block_name);
155 return FALSE;
157 } else {
158 /* password is already in encrypted form already */
159 encrypted_password = g_strdup(p);
162 // add encrypted password to the block
163 g_hash_table_insert(block->entries,
164 g_strdup(password_id),
165 encrypted_password);
168 return TRUE;
171 /* Retrieves a password. */
172 gchar *passwd_store_get(PasswordBlockType block_type,
173 const gchar *block_name,
174 const gchar *password_id)
176 PasswordBlock *block;
177 gchar *encrypted_password, *password;
179 g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES,
180 NULL);
181 g_return_val_if_fail(block_name != NULL, NULL);
182 g_return_val_if_fail(password_id != NULL, NULL);
184 debug_print("Getting password '%s' from block (%d/%s)\n",
185 password_id, block_type, block_name);
187 // find correct block
188 if ((block = _get_block(block_type, block_name)) == NULL) {
189 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
190 return NULL;
193 // grab pointer to encrypted password
194 if ((encrypted_password =
195 g_hash_table_lookup(block->entries, password_id)) == NULL) {
196 debug_print("Password '%s' in block (%d/%s) not found.\n",
197 password_id, block_type, block_name);
198 return NULL;
201 // decrypt password
202 if ((password =
203 password_decrypt(encrypted_password, NULL)) == NULL) {
204 debug_print("Could not decrypt password '%s' for block (%d/%s).\n",
205 password_id, block_type, block_name);
206 return NULL;
209 // return decrypted password
210 return password;
213 gboolean passwd_store_delete_block(PasswordBlockType block_type,
214 const gchar *block_name)
216 PasswordBlock *block;
218 g_return_val_if_fail(block_type >= 0 && block_type < NUM_PWS_TYPES,
219 FALSE);
220 g_return_val_if_fail(block_name != NULL, FALSE);
222 debug_print("Deleting block (%d/%s)\n", block_type, block_name);
224 // find correct block
225 if ((block = _get_block(block_type, block_name)) == NULL) {
226 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
227 return FALSE;
230 g_hash_table_destroy(block->entries);
231 block->entries = NULL;
232 return TRUE;
235 gboolean passwd_store_set_account(gint account_id,
236 const gchar *password_id,
237 const gchar *password,
238 gboolean encrypted)
240 gchar *uid = g_strdup_printf("%d", account_id);
241 gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
242 password_id, password, encrypted);
243 g_free(uid);
244 return ret;
247 gchar *passwd_store_get_account(gint account_id,
248 const gchar *password_id)
250 gchar *uid = g_strdup_printf("%d", account_id);
251 gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
252 g_free(uid);
253 return ret;
256 /* Reencrypts all stored passwords. */
257 void passwd_store_reencrypt_all(const gchar *old_mpwd,
258 const gchar *new_mpwd)
260 PasswordBlock *block;
261 GSList *item;
262 GList *keys, *eitem;
263 gchar *encrypted_password, *decrypted_password, *key;
265 g_return_if_fail(old_mpwd != NULL);
266 g_return_if_fail(new_mpwd != NULL);
268 for (item = _password_store; item != NULL; item = item->next) {
269 block = (PasswordBlock *)item->data;
270 if (block == NULL)
271 continue; /* Just in case. */
273 debug_print("Reencrypting passwords in block (%d/%s).\n",
274 block->block_type, block->block_name);
276 if (g_hash_table_size(block->entries) == 0)
277 continue;
279 keys = g_hash_table_get_keys(block->entries);
280 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
281 key = (gchar *)eitem->data;
282 if ((encrypted_password =
283 g_hash_table_lookup(block->entries, key)) == NULL)
284 continue;
286 if ((decrypted_password =
287 password_decrypt(encrypted_password, old_mpwd)) == NULL) {
288 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
289 continue;
292 encrypted_password = password_encrypt(decrypted_password, new_mpwd);
293 memset(decrypted_password, 0, strlen(decrypted_password));
294 g_free(decrypted_password);
295 if (encrypted_password == NULL) {
296 debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key);
297 continue;
300 g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
303 g_list_free(keys);
306 debug_print("Reencrypting done.\n");
309 static gint _write_to_file(FILE *fp)
311 PasswordBlock *block;
312 GSList *item;
313 GList *keys, *eitem;
314 gchar *typestr, *line, *key, *pwd;
316 for (item = _password_store; item != NULL; item = item->next) {
317 block = (PasswordBlock*)item->data;
318 if (block == NULL)
319 continue; /* Just in case. */
321 /* Do not save empty blocks. */
322 if (g_hash_table_size(block->entries) == 0)
323 continue;
325 /* Prepare the section header string and write it out. */
326 typestr = NULL;
327 if (block->block_type == PWS_CORE) {
328 typestr = "core";
329 } else if (block->block_type == PWS_ACCOUNT) {
330 typestr = "account";
331 } else if (block->block_type == PWS_PLUGIN) {
332 typestr = "plugin";
334 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
336 if (fputs(line, fp) == EOF) {
337 FILE_OP_ERROR("password store", "fputs");
338 g_free(line);
339 return -1;
341 g_free(line);
343 /* Now go through all passwords in the block and write each out. */
344 keys = g_hash_table_get_keys(block->entries);
345 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
346 key = (gchar *)eitem->data;
347 if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL)
348 continue;
350 line = g_strdup_printf("%s %s\n", key, pwd);
351 if (fputs(line, fp) == EOF) {
352 FILE_OP_ERROR("password store", "fputs");
353 g_free(line);
354 return -1;
356 g_free(line);
358 g_list_free(keys);
360 if (item->next != NULL && fputs("\n", fp) == EOF) {
361 FILE_OP_ERROR("password store", "fputs");
362 return -1;
367 return 1;
370 void passwd_store_write_config(void)
372 gchar *rcpath;
373 PrefFile *pfile;
375 debug_print("Writing password store...\n");
377 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
378 PASSWORD_STORE_RC, NULL);
380 if ((pfile = prefs_write_open(rcpath)) == NULL) {
381 g_warning("failed to open password store file for writing");
382 g_free(rcpath);
383 return;
386 g_free(rcpath);
388 if (_write_to_file(pfile->fp) < 0) {
389 g_warning("failed to write password store to file");
390 prefs_file_close_revert(pfile);
391 } else if (prefs_file_close(pfile) < 0) {
392 g_warning("failed to properly close password store file after writing");
396 void passwd_store_read_config(void)
398 gchar *rcpath, *contents, **lines, **line, *typestr, *name;
399 GError *error = NULL;
400 guint i = 0;
401 PasswordBlock *block = NULL;
402 PasswordBlockType type;
404 // TODO: passwd_store_clear();
406 debug_print("Reading password store from file...\n");
408 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
409 PASSWORD_STORE_RC, NULL);
411 if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
412 g_warning("couldn't read password store from file: %s", error->message);
413 g_error_free(error);
414 g_free(rcpath);
415 return;
417 g_free(rcpath);
419 lines = g_strsplit(contents, "\n", -1);
421 g_free(contents);
423 while (lines[i] != NULL) {
424 if (*lines[i] == '[') {
425 /* Beginning of a new block */
426 line = g_strsplit_set(lines[i], "[:]", -1);
427 if (line[0] != NULL && strlen(line[0]) == 0
428 && line[1] != NULL && strlen(line[1]) > 0
429 && line[2] != NULL && strlen(line[2]) > 0
430 && line[3] != NULL && strlen(line[3]) == 0) {
431 typestr = line[1];
432 name = line[2];
433 if (!strcmp(typestr, "core")) {
434 type = PWS_CORE;
435 } else if (!strcmp(typestr, "account")) {
436 type = PWS_ACCOUNT;
437 } else if (!strcmp(typestr, "plugin")) {
438 type = PWS_PLUGIN;
439 } else {
440 debug_print("Unknown password block type: '%s'\n", typestr);
441 g_strfreev(line);
442 i++; continue;
445 if ((block = _new_block(type, name)) == NULL) {
446 debug_print("Duplicate password block, ignoring: (%d/%s)\n",
447 type, name);
448 g_strfreev(line);
449 i++; continue;
452 g_strfreev(line);
453 } else if (strlen(lines[i]) > 0 && block != NULL) {
454 /* If we have started a password block, test for a
455 * "password_id = password" line. */
456 line = g_strsplit(lines[i], " ", -1);
457 if (line[0] != NULL && strlen(line[0]) > 0
458 && line[1] != NULL && strlen(line[1]) > 0
459 && line[2] == NULL) {
460 debug_print("Adding password '%s'\n", line[0]);
461 g_hash_table_insert(block->entries,
462 g_strdup(line[0]), g_strdup(line[1]));
464 g_strfreev(line);
466 i++;
468 g_strfreev(lines);