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