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