Fix buffer length handling in GnuTLS password encryption and decryption.
[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 < NUM_PWS_TYPES, NULL);
49         g_return_val_if_fail(block_name != NULL, NULL);
50
51         for (item = _password_store; item != NULL; item = item->next) {
52                 block = (PasswordBlock *)item->data;
53                 if (block->block_type == block_type &&
54                                 !strcmp(block->block_name, block_name))
55                         return block;
56         }
57
58         return NULL;
59 }
60
61 static gboolean _hash_equal_func(gconstpointer a, gconstpointer b)
62 {
63         if (g_strcmp0((const gchar *)a, (const gchar *)b) == 0)
64                 return TRUE;
65         return FALSE;
66 }
67
68 /* Creates a new, empty block and adds it to the pwdstore. */
69 static PasswordBlock *_new_block(PasswordBlockType block_type,
70                 const gchar *block_name)
71 {
72         PasswordBlock *block;
73
74         g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
75         g_return_val_if_fail(block_name != NULL, NULL);
76
77         /* First check to see if the block doesn't already exist. */
78         if (_get_block(block_type, block_name)) {
79                 debug_print("Block (%d/%s) already exists.\n",
80                                 block_type, block_name);
81                 return NULL;
82         }
83
84         /* Let's create an empty block, and add it to pwdstore. */
85         block = g_new0(PasswordBlock, 1);
86         block->block_type = block_type;
87         block->block_name = g_strdup(block_name);
88         block->entries = g_hash_table_new_full(g_str_hash,
89                         (GEqualFunc)_hash_equal_func,
90                         g_free, g_free);
91         debug_print("Created password block (%d/%s)\n",
92                         block_type, block_name);
93
94         _password_store = g_slist_append(_password_store, block);
95
96         return block;
97 }
98
99 /*************************************************************/
100
101 /* Stores a password. */
102 gboolean passwd_store_set(PasswordBlockType block_type,
103                 const gchar *block_name,
104                 const gchar *password_id,
105                 const gchar *password,
106                 gboolean encrypted)
107 {
108         const gchar *p;
109         PasswordBlock *block;
110         gchar *encrypted_password;
111
112         g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
113         g_return_val_if_fail(block_name != NULL, FALSE);
114         g_return_val_if_fail(password_id != NULL, FALSE);
115
116         /* Empty password string equals null password for us. */
117         if (password == NULL || strlen(password) == 0)
118                 p = NULL;
119         else
120                 p = password;
121
122         debug_print("%s password '%s' in block (%d/%s)%s\n",
123                         (p == NULL ? "Deleting" : "Storing"),
124                         password_id, block_type, block_name,
125                         (encrypted ? ", already encrypted" : "") );
126
127         /* find correct block (create if needed) */
128         if ((block = _get_block(block_type, block_name)) == NULL) {
129                 /* If caller wants to delete a password, and even its block
130                  * doesn't exist, we're done. */
131                 if (p == NULL)
132                         return TRUE;
133
134                 if ((block = _new_block(block_type, block_name)) == NULL) {
135                         debug_print("Could not create password block (%d/%s)\n",
136                                         block_type, block_name);
137                         return FALSE;
138                 }
139         }
140
141         if (p == NULL) {
142                 /* NULL password was passed to us, so delete the entry with
143                  * corresponding id */
144                 g_hash_table_remove(block->entries, password_id);
145         } else {
146                 if (!encrypted) {
147                         /* encrypt password before saving it */
148                         if ((encrypted_password =
149                                                 password_encrypt(p, NULL)) == NULL) {
150                                 debug_print("Could not encrypt password '%s' for block (%d/%s).\n",
151                                                 password_id, block_type, block_name);
152                                 return FALSE;
153                         }
154                 } else {
155                         /* password is already in encrypted form already */
156                         encrypted_password = g_strdup(p);
157                 }
158
159                 /* add encrypted password to the block */
160                 g_hash_table_insert(block->entries,
161                                 g_strdup(password_id),
162                                 encrypted_password);
163         }
164
165         return TRUE;
166 }
167
168 /* Retrieves a password. */
169 gchar *passwd_store_get(PasswordBlockType block_type,
170                 const gchar *block_name,
171                 const gchar *password_id)
172 {
173         PasswordBlock *block;
174         gchar *encrypted_password, *password;
175
176         g_return_val_if_fail(block_type < NUM_PWS_TYPES, NULL);
177         g_return_val_if_fail(block_name != NULL, NULL);
178         g_return_val_if_fail(password_id != NULL, NULL);
179
180         debug_print("Getting password '%s' from block (%d/%s)\n",
181                         password_id, block_type, block_name);
182
183         /* find correct block */
184         if ((block = _get_block(block_type, block_name)) == NULL) {
185                 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
186                 return NULL;
187         }
188
189         /* grab pointer to encrypted password */
190         if ((encrypted_password =
191                                 g_hash_table_lookup(block->entries, password_id)) == NULL) {
192                 debug_print("Password '%s' in block (%d/%s) not found.\n",
193                                 password_id, block_type, block_name);
194                 return NULL;
195         }
196
197         /* decrypt password */
198         if ((password =
199                                 password_decrypt(encrypted_password, NULL)) == NULL) {
200                 debug_print("Could not decrypt password '%s' for block (%d/%s).\n",
201                                 password_id, block_type, block_name);
202                 return NULL;
203         }
204
205         /* return decrypted password */
206         return password;
207 }
208
209 gboolean passwd_store_delete_block(PasswordBlockType block_type,
210                 const gchar *block_name)
211 {
212         PasswordBlock *block;
213
214         g_return_val_if_fail(block_type < NUM_PWS_TYPES, FALSE);
215         g_return_val_if_fail(block_name != NULL, FALSE);
216
217         debug_print("Deleting block (%d/%s)\n", block_type, block_name);
218
219         /* find correct block */
220         if ((block = _get_block(block_type, block_name)) == NULL) {
221                 debug_print("Block (%d/%s) not found.\n", block_type, block_name);
222                 return FALSE;
223         }
224
225         g_hash_table_destroy(block->entries);
226         block->entries = NULL;
227         return TRUE;
228 }
229
230 gboolean passwd_store_set_account(gint account_id,
231                 const gchar *password_id,
232                 const gchar *password,
233                 gboolean encrypted)
234 {
235         gchar *uid = g_strdup_printf("%d", account_id);
236         gboolean ret = passwd_store_set(PWS_ACCOUNT, uid,
237                         password_id, password, encrypted);
238         g_free(uid);
239         return ret;
240 }
241
242 gchar *passwd_store_get_account(gint account_id,
243                 const gchar *password_id)
244 {
245         gchar *uid = g_strdup_printf("%d", account_id);
246         gchar *ret = passwd_store_get(PWS_ACCOUNT, uid, password_id);
247         g_free(uid);
248         return ret;
249 }
250
251 /* Reencrypts all stored passwords. */
252 void passwd_store_reencrypt_all(const gchar *old_mpwd,
253                 const gchar *new_mpwd)
254 {
255         PasswordBlock *block;
256         GSList *item;
257         GList *keys, *eitem;
258         gchar *encrypted_password, *decrypted_password, *key;
259
260         g_return_if_fail(old_mpwd != NULL);
261         g_return_if_fail(new_mpwd != NULL);
262
263         for (item = _password_store; item != NULL; item = item->next) {
264                 block = (PasswordBlock *)item->data;
265                 if (block == NULL)
266                         continue; /* Just in case. */
267
268                 debug_print("Reencrypting passwords in block (%d/%s).\n",
269                                 block->block_type, block->block_name);
270
271                 if (g_hash_table_size(block->entries) == 0)
272                         continue;
273
274                 keys = g_hash_table_get_keys(block->entries);
275                 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
276                         key = (gchar *)eitem->data;
277                         if ((encrypted_password =
278                                                 g_hash_table_lookup(block->entries, key)) == NULL)
279                                 continue;
280
281                         if ((decrypted_password =
282                                                 password_decrypt(encrypted_password, old_mpwd)) == NULL) {
283                                 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key);
284                                 continue;
285                         }
286
287                         encrypted_password = password_encrypt(decrypted_password, new_mpwd);
288                         memset(decrypted_password, 0, strlen(decrypted_password));
289                         g_free(decrypted_password);
290                         if (encrypted_password == NULL) {
291                                 debug_print("Reencrypt: couldn't encrypt password for '%s'.\n", key);
292                                 continue;
293                         }
294
295                         g_hash_table_insert(block->entries, g_strdup(key), encrypted_password);
296                 }
297
298                 g_list_free(keys);
299         }
300
301         debug_print("Reencrypting done.\n");
302 }
303
304 static gint _write_to_file(FILE *fp)
305 {
306         PasswordBlock *block;
307         GSList *item;
308         GList *keys, *eitem;
309         gchar *typestr, *line, *key, *pwd;
310
311         for (item = _password_store; item != NULL; item = item->next) {
312                 block = (PasswordBlock*)item->data;
313                 if (block == NULL)
314                         continue; /* Just in case. */
315
316                 /* Do not save empty blocks. */
317                 if (g_hash_table_size(block->entries) == 0)
318                         continue;
319
320                 /* Prepare the section header string and write it out. */
321                 typestr = NULL;
322                 if (block->block_type == PWS_CORE) {
323                         typestr = "core";
324                 } else if (block->block_type == PWS_ACCOUNT) {
325                         typestr = "account";
326                 } else if (block->block_type == PWS_PLUGIN) {
327                         typestr = "plugin";
328                 }
329                 line = g_strdup_printf("[%s:%s]\n", typestr, block->block_name);
330
331                 if (fputs(line, fp) == EOF) {
332                         FILE_OP_ERROR("password store", "fputs");
333                         g_free(line);
334                         return -1;
335                 }
336                 g_free(line);
337
338                 /* Now go through all passwords in the block and write each out. */
339                 keys = g_hash_table_get_keys(block->entries);
340                 for (eitem = keys; eitem != NULL; eitem = eitem->next) {
341                         key = (gchar *)eitem->data;
342                         if ((pwd = g_hash_table_lookup(block->entries, key)) == NULL)
343                                 continue;
344
345                         line = g_strdup_printf("%s %s\n", key, pwd);
346                         if (fputs(line, fp) == EOF) {
347                                 FILE_OP_ERROR("password store", "fputs");
348                                 g_free(line);
349                                 return -1;
350                         }
351                         g_free(line);
352                 }
353                 g_list_free(keys);
354
355                 if (item->next != NULL && fputs("\n", fp) == EOF) {
356                         FILE_OP_ERROR("password store", "fputs");
357                         return -1;
358                 }
359
360         }
361
362         return 1;
363 }
364
365 void passwd_store_write_config(void)
366 {
367         gchar *rcpath;
368         PrefFile *pfile;
369
370         debug_print("Writing password store...\n");
371
372         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
373                         PASSWORD_STORE_RC, NULL);
374
375         if ((pfile = prefs_write_open(rcpath)) == NULL) {
376                 g_warning("failed to open password store file for writing");
377                 g_free(rcpath);
378                 return;
379         }
380
381         g_free(rcpath);
382
383         if (_write_to_file(pfile->fp) < 0) {
384                 g_warning("failed to write password store to file");
385                 prefs_file_close_revert(pfile);
386         } else if (prefs_file_close(pfile) < 0) {
387                 g_warning("failed to properly close password store file after writing");
388         }
389 }
390
391 void passwd_store_read_config(void)
392 {
393         gchar *rcpath, *contents, **lines, **line, *typestr, *name;
394         GError *error = NULL;
395         guint i = 0;
396         PasswordBlock *block = NULL;
397         PasswordBlockType type;
398
399         /* TODO: passwd_store_clear(); */
400
401         debug_print("Reading password store from file...\n");
402
403         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
404                         PASSWORD_STORE_RC, NULL);
405
406         if (!g_file_get_contents(rcpath, &contents, NULL, &error)) {
407                 g_warning("couldn't read password store from file: %s", error->message);
408                 g_error_free(error);
409                 g_free(rcpath);
410                 return;
411         }
412         g_free(rcpath);
413
414         lines = g_strsplit(contents, "\n", -1);
415
416         g_free(contents);
417
418         while (lines[i] != NULL) {
419                 if (*lines[i] == '[') {
420                         /* Beginning of a new block */
421                         line = g_strsplit_set(lines[i], "[:]", -1);
422                         if (line[0] != NULL && strlen(line[0]) == 0
423                                         && line[1] != NULL && strlen(line[1]) > 0
424                                         && line[2] != NULL && strlen(line[2]) > 0
425                                         && line[3] != NULL && strlen(line[3]) == 0) {
426                                 typestr = line[1];
427                                 name = line[2];
428                                 if (!strcmp(typestr, "core")) {
429                                         type = PWS_CORE;
430                                 } else if (!strcmp(typestr, "account")) {
431                                         type = PWS_ACCOUNT;
432                                 } else if (!strcmp(typestr, "plugin")) {
433                                         type = PWS_PLUGIN;
434                                 } else {
435                                         debug_print("Unknown password block type: '%s'\n", typestr);
436                                         g_strfreev(line);
437                                         i++; continue;
438                                 }
439
440                                 if ((block = _new_block(type, name)) == NULL) {
441                                         debug_print("Duplicate password block, ignoring: (%d/%s)\n",
442                                                         type, name);
443                                         g_strfreev(line);
444                                         i++; continue;
445                                 }
446                         }
447                         g_strfreev(line);
448                 } else if (strlen(lines[i]) > 0 && block != NULL) {
449                         /* If we have started a password block, test for a
450                          * "password_id = password" line. */
451                         line = g_strsplit(lines[i], " ", -1);
452                         if (line[0] != NULL && strlen(line[0]) > 0
453                                         && line[1] != NULL && strlen(line[1]) > 0
454                                         && line[2] == NULL) {
455                                 debug_print("Adding password '%s'\n", line[0]);
456                                 g_hash_table_insert(block->entries,
457                                                 g_strdup(line[0]), g_strdup(line[1]));
458                         }
459                         g_strfreev(line);
460                 }
461                 i++;
462         }
463         g_strfreev(lines);
464 }