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