Fix crash on deleting password from passwordstore.
[claws.git] / src / passwordstore.c
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_gtk.h"
38
39 static GSList *_password_store;
40
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)
44 {
45         GSList *item;
46         PasswordBlock *block;
47
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);
51
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;
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 >= 0 && block_type < NUM_PWS_TYPES,
76                         NULL);
77         g_return_val_if_fail(block_name != NULL, NULL);
78
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;
84         }
85
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);
95
96         _password_store = g_slist_append(_password_store, block);
97
98         return block;
99 }
100
101 ///////////////////////////////////////////////////////////////
102
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)
109 {
110         const gchar *p;
111         PasswordBlock *block;
112         gchar *encrypted_password;
113
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);
118
119         /* Empty password string equals null password for us. */
120         if (password == NULL || strlen(password) == 0)
121                 p = NULL;
122         else
123                 p = password;
124
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" : "") );
129
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;
136
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;
141                 }
142         }
143
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;
156                         }
157                 } else {
158                         /* password is already in encrypted form already */
159                         encrypted_password = g_strdup(p);
160                 }
161
162                 // add encrypted password to the block
163                 g_hash_table_insert(block->entries,
164                                 g_strdup(password_id),
165                                 encrypted_password);
166         }
167
168         return TRUE;
169 }
170
171 /* Retrieves a password. */
172 gchar *passwd_store_get(PasswordBlockType block_type,
173                 const gchar *block_name,
174                 const gchar *password_id)
175 {
176         PasswordBlock *block;
177         gchar *encrypted_password, *password;
178
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);
183
184         debug_print("Getting password '%s' from block (%d/%s)\n",
185                         password_id, block_type, block_name);
186
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;
191         }
192
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;
199         }
200
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;
207         }
208
209         // return decrypted password
210         return password;
211 }
212
213 gboolean passwd_store_delete_block(PasswordBlockType block_type,
214                 const gchar *block_name)
215 {
216         PasswordBlock *block;
217
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);
221
222         debug_print("Deleting block (%d/%s)\n", block_type, block_name);
223
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;
228         }
229
230         g_hash_table_destroy(block->entries);
231         block->entries = NULL;
232         return TRUE;
233 }
234
235 gboolean passwd_store_set_account(gint account_id,
236                 const gchar *password_id,
237                 const gchar *password,
238                 gboolean encrypted)
239 {
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;
245 }
246
247 gchar *passwd_store_get_account(gint account_id,
248                 const gchar *password_id)
249 {
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;
254 }
255
256 /* Reencrypts all stored passwords. */
257 void passwd_store_reencrypt_all(const gchar *old_mpwd,
258                 const gchar *new_mpwd)
259 {
260         PasswordBlock *block;
261         GSList *item;
262         GList *keys, *eitem;
263         gchar *encrypted_password, *decrypted_password, *key;
264
265         g_return_if_fail(old_mpwd != NULL);
266         g_return_if_fail(new_mpwd != NULL);
267
268         for (item = _password_store; item != NULL; item = item->next) {
269                 block = (PasswordBlock *)item->data;
270                 if (block == NULL)
271                         continue; /* Just in case. */
272
273                 debug_print("Reencrypting passwords in block (%d/%s).\n",
274                                 block->block_type, block->block_name);
275
276                 if (g_hash_table_size(block->entries) == 0)
277                         continue;
278
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;
285
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;
290                         }
291
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;
298                         }
299
300                         g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
301                 }
302
303                 g_list_free(keys);
304         }
305
306         debug_print("Reencrypting done.\n");
307 }
308
309 static gint _write_to_file(FILE *fp)
310 {
311         PasswordBlock *block;
312         GSList *item;
313         GList *keys, *eitem;
314         gchar *typestr, *line, *key, *pwd;
315
316         for (item = _password_store; item != NULL; item = item->next) {
317                 block = (PasswordBlock*)item->data;
318                 if (block == NULL)
319                         continue; /* Just in case. */
320
321                 /* Do not save empty blocks. */
322                 if (g_hash_table_size(block->entries) == 0)
323                         continue;
324
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";
333                 }
334                 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
335
336                 if (fputs(line, fp) == EOF) {
337                         FILE_OP_ERROR("password store", "fputs");
338                         g_free(line);
339                         return -1;
340                 }
341                 g_free(line);
342
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;
349
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;
355                         }
356                         g_free(line);
357                 }
358                 g_list_free(keys);
359
360                 if (item->next != NULL && fputs("\n", fp) == EOF) {
361                         FILE_OP_ERROR("password store", "fputs");
362                         return -1;
363                 }
364
365         }
366
367         return 1;
368 }
369
370 void passwd_store_write_config(void)
371 {
372         gchar *rcpath;
373         PrefFile *pfile;
374
375         debug_print("Writing password store...\n");
376
377         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
378                         PASSWORD_STORE_RC, NULL);
379
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;
384         }
385
386         g_free(rcpath);
387
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");
393         }
394 }
395
396 void passwd_store_read_config(void)
397 {
398         gchar *rcpath, *contents, **lines, **line, *typestr, *name;
399         GError *error = NULL;
400         guint i = 0;
401         PasswordBlock *block = NULL;
402         PasswordBlockType type;
403
404         // TODO: passwd_store_clear();
405
406         debug_print("Reading password store from file...\n");
407
408         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
409                         PASSWORD_STORE_RC, NULL);
410
411         if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
412                 g_warning("couldn't read password store from file: %s\n", error->message);
413                 g_error_free(error);
414                 g_free(rcpath);
415                 return;
416         }
417         g_free(rcpath);
418
419         lines = g_strsplit(contents, "\n", -1);
420
421         while (lines[i] != NULL) {
422                 if (*lines[i] == '[') {
423                         /* Beginning of a new block */
424                         line = g_strsplit_set(lines[i], "[:]", -1);
425                         if (line[0] != NULL && strlen(line[0]) == 0
426                                         && line[1] != NULL && strlen(line[1]) > 0
427                                         && line[2] != NULL && strlen(line[2]) > 0
428                                         && line[3] != NULL && strlen(line[3]) == 0) {
429                                 typestr = line[1];
430                                 name = line[2];
431                                 if (!strcmp(typestr, "core")) {
432                                         type = PWS_CORE;
433                                 } else if (!strcmp(typestr, "account")) {
434                                         type = PWS_ACCOUNT;
435                                 } else if (!strcmp(typestr, "plugin")) {
436                                         type = PWS_PLUGIN;
437                                 } else {
438                                         debug_print("Uknown password block type: '%s'\n", typestr);
439                                         g_strfreev(line);
440                                         i++; continue;
441                                 }
442
443                                 if ((block = _new_block(type, name)) == NULL) {
444                                         debug_print("Duplicate password block, ignoring: (%d/%s)\n",
445                                                         type, name);
446                                         g_strfreev(line);
447                                         i++; continue;
448                                 }
449                         }
450                         g_strfreev(line);
451                 } else if (strlen(lines[i]) > 0 && block != NULL) {
452                         /* If we have started a password block, test for a
453                          * "password_id = password" line. */
454                         line = g_strsplit(lines[i], " ", -1);
455                         if (line[0] != NULL && strlen(line[0]) > 0
456                                         && line[1] != NULL && strlen(line[1]) > 0
457                                         && line[2] == NULL) {
458                                 debug_print("Adding password '%s'\n", line[0]);
459                                 g_hash_table_insert(block->entries,
460                                                 g_strdup(line[0]), g_strdup(line[1]));
461                         }
462                         g_strfreev(line);
463                 }
464                 i++;
465         }
466         g_strfreev(lines);
467 }