2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2016 The Claws Mail Team
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.
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.
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/>.
22 #include "claws-features.h"
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
31 #include <glib/gi18n.h>
33 #include "common/defs.h"
34 #include "common/utils.h"
35 #include "passwordstore.h"
37 #include "prefs_common.h"
38 #include "prefs_gtk.h"
39 #include "prefs_migration.h"
40 #include "file-utils.h"
42 static GSList
*_password_store
;
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
)
54 g_return_val_if_fail(block_type
< NUM_PWS_TYPES
, NULL
);
55 g_return_val_if_fail(block_name
!= NULL
, NULL
);
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
)) {
70 static gboolean
_hash_equal_func(gconstpointer a
, gconstpointer b
)
72 if (g_strcmp0((const gchar
*)a
, (const gchar
*)b
) == 0)
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
)
83 g_return_val_if_fail(block_type
< NUM_PWS_TYPES
, NULL
);
84 g_return_val_if_fail(block_name
!= NULL
, NULL
);
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
);
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
,
100 debug_print("Created password block (%d/%s)\n",
101 block_type
, block_name
);
103 _password_store
= g_slist_append(_password_store
, block
);
108 static void _delete_block(PasswordBlock
*block
)
110 g_return_if_fail(block
!= NULL
);
112 if (block
->block_name
!= NULL
)
113 g_free(block
->block_name
);
115 if (block
->entries
!= NULL
)
116 g_hash_table_destroy(block
->entries
);
121 /*************************************************************/
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
,
131 PasswordBlock
*block
;
132 gchar
*encrypted_password
;
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
);
138 /* Empty password string equals null password for us. */
139 if (password
== NULL
|| strlen(password
) == 0)
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. */
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
);
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
);
167 debug_print("Setting password for '%s' in block (%d/%s)%s\n",
168 password_id
, block_type
, block_name
,
169 (encrypted
? ", already 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
);
179 /* password is already in encrypted form already */
180 encrypted_password
= g_strdup(p
);
183 /* add encrypted password to the block */
184 g_hash_table_insert(block
->entries
,
185 g_strdup(password_id
),
192 /* Retrieves a password. */
193 gchar
*passwd_store_get(PasswordBlockType block_type
,
194 const gchar
*block_name
,
195 const gchar
*password_id
)
197 PasswordBlock
*block
;
198 gchar
*encrypted_password
, *password
;
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
);
204 debug_print("Getting password '%s' from block (%d/%s)\n",
205 password_id
, block_type
, block_name
);
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
);
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
);
221 /* decrypt 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
);
229 /* return decrypted password */
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
)
239 PasswordBlock
*block
;
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
);
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
);
251 /* do we have specified password in this block? */
252 if (g_hash_table_lookup(block
->entries
, password_id
) != NULL
) {
253 return TRUE
; /* yes */
256 return FALSE
; /* no */
260 gboolean
passwd_store_delete_block(PasswordBlockType block_type
,
261 const gchar
*block_name
)
263 PasswordBlock
*block
;
266 g_return_val_if_fail(block_type
< NUM_PWS_TYPES
, FALSE
);
267 g_return_val_if_fail(block_name
!= NULL
, FALSE
);
269 debug_print("Deleting block (%d/%s)\n", block_type
, block_name
);
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
);
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
);
283 gboolean
passwd_store_set_account(gint account_id
,
284 const gchar
*password_id
,
285 const gchar
*password
,
288 gchar
*uid
= g_strdup_printf("%d", account_id
);
289 gboolean ret
= passwd_store_set(PWS_ACCOUNT
, uid
,
290 password_id
, password
, encrypted
);
295 gchar
*passwd_store_get_account(gint account_id
,
296 const gchar
*password_id
)
298 gchar
*uid
= g_strdup_printf("%d", account_id
);
299 gchar
*ret
= passwd_store_get(PWS_ACCOUNT
, uid
, password_id
);
304 gboolean
passwd_store_has_password_account(gint account_id
,
305 const gchar
*password_id
)
307 gchar
*uid
= g_strdup_printf("%d", account_id
);
308 gboolean ret
= passwd_store_has_password(PWS_ACCOUNT
, uid
, password_id
);
313 /* Reencrypts all stored passwords. */
314 void passwd_store_reencrypt_all(const gchar
*old_mpwd
,
315 const gchar
*new_mpwd
)
317 PasswordBlock
*block
;
320 gchar
*encrypted_password
, *decrypted_password
, *key
;
322 g_return_if_fail(old_mpwd
!= NULL
);
323 g_return_if_fail(new_mpwd
!= NULL
);
325 for (item
= _password_store
; item
!= NULL
; item
= item
->next
) {
326 block
= (PasswordBlock
*)item
->data
;
328 continue; /* Just in case. */
330 debug_print("Reencrypting passwords in block (%d/%s).\n",
331 block
->block_type
, block
->block_name
);
333 if (block
->entries
== NULL
|| g_hash_table_size(block
->entries
) == 0)
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
)
343 if ((decrypted_password
=
344 password_decrypt(encrypted_password
, old_mpwd
)) == NULL
) {
345 debug_print("Reencrypt: couldn't decrypt password for '%s'.\n", key
);
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
);
357 g_hash_table_insert(block
->entries
, g_strdup(key
), encrypted_password
);
363 debug_print("Reencrypting done.\n");
366 static gint
_write_to_file(FILE *fp
)
368 PasswordBlock
*block
;
371 gchar
*typestr
, *line
, *key
, *pwd
;
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");
382 /* Add a newline if needed */
383 if (_password_store
!= NULL
&& claws_fputs("\n", fp
) == EOF
) {
384 FILE_OP_ERROR("password store", "claws_fputs");
388 /* Write out each password store block */
389 for (item
= _password_store
; item
!= NULL
; item
= item
->next
) {
390 block
= (PasswordBlock
*)item
->data
;
392 continue; /* Just in case. */
394 /* Do not save empty blocks. */
395 if (block
->entries
== NULL
|| g_hash_table_size(block
->entries
) == 0)
398 /* Prepare the section header string and write it out. */
400 if (block
->block_type
== PWS_CORE
) {
402 } else if (block
->block_type
== PWS_ACCOUNT
) {
404 } else if (block
->block_type
== PWS_PLUGIN
) {
407 line
= g_strdup_printf("[%s:%s]\n", typestr
, block
->block_name
);
409 if (claws_fputs(line
, fp
) == EOF
) {
410 FILE_OP_ERROR("password store", "claws_fputs");
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
)
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");
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");
444 void passwd_store_write_config(void)
449 debug_print("Writing password store...\n");
451 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
452 PASSWORD_STORE_RC
, NULL
);
454 if ((pfile
= prefs_write_open(rcpath
)) == NULL
) {
455 g_warning("failed to open password store file for writing");
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");
470 int passwd_store_read_config(void)
472 gchar
*rcpath
, *contents
, **lines
, **line
, *typestr
, *name
;
473 GError
*error
= NULL
;
475 PasswordBlock
*block
= NULL
;
476 PasswordBlockType type
;
477 gboolean reading_config_version
= FALSE
;
478 gint config_version
= -1;
480 /* TODO: passwd_store_clear(); */
482 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
483 PASSWORD_STORE_RC
, NULL
);
485 debug_print("Reading password store from file '%s'\n", rcpath
);
487 if (!g_file_test(rcpath
, G_FILE_TEST_EXISTS
)) {
488 debug_print("File does not exist, looks like a new configuration.\n");
493 if (!g_file_get_contents(rcpath
, &contents
, NULL
, &error
)) {
494 g_warning("couldn't read password store from file: %s", error
->message
);
501 lines
= g_strsplit(contents
, "\n", -1);
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) {
515 if (!strcmp(typestr
, "core")) {
517 } else if (!strcmp(typestr
, "account")) {
519 } else if (!strcmp(typestr
, "plugin")) {
521 } else if (!strcmp(typestr
, "config_version")) {
522 reading_config_version
= TRUE
;
523 config_version
= atoi(name
);
525 debug_print("Unknown password block type: '%s'\n", typestr
);
530 if (reading_config_version
) {
531 if (config_version
< 0) {
532 debug_print("config_version:%d looks invalid, ignoring it\n",
534 config_version
= -1; /* set to default value if missing */
538 debug_print("config_version in file is %d\n", config_version
);
539 reading_config_version
= FALSE
;
541 if ((block
= _new_block(type
, name
)) == NULL
) {
542 debug_print("Duplicate password block, ignoring: (%d/%s)\n",
550 } else if (strlen(lines
[i
]) > 0 && block
!= NULL
) {
551 /* If we have started a password block, test for a
552 * "password_id = password" line. */
553 line
= g_strsplit(lines
[i
], " ", -1);
554 if (line
[0] != NULL
&& strlen(line
[0]) > 0
555 && line
[1] != NULL
&& strlen(line
[1]) > 0
556 && line
[2] == NULL
) {
557 debug_print("Adding password '%s'\n", line
[0]);
558 g_hash_table_insert(block
->entries
,
559 g_strdup(line
[0]), g_strdup(line
[1]));
567 if (prefs_update_config_version_password_store(config_version
) < 0) {
568 debug_print("Password store configuration file version upgrade failed\n");
572 return g_slist_length(_password_store
);