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