Adapt migration for files
[pidgin-git.git] / libpurple / plugins / keyrings / internalkeyring.c
blobe3ecc6617de5f0b58c9fd3723d04ab8aef76926f
1 /**
2 * @file internalkeyring.c internal keyring
3 * @ingroup plugins
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program ; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
28 #include "account.h"
29 #include "debug.h"
30 #include "keyring.h"
31 #include "plugins.h"
32 #include "version.h"
34 #include "ciphers/aescipher.h"
35 #include "ciphers/pbkdf2cipher.h"
36 #include "ciphers/sha256hash.h"
38 #define INTKEYRING_NAME N_("Internal keyring")
39 #define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
40 "storage behaviour for libpurple.")
41 #define INTKEYRING_AUTHORS { "Tomek Wasilczyk <twasilczyk@pidgin.im>",NULL }
42 #define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
43 #define INTKEYRING_DOMAIN (g_quark_from_static_string(INTKEYRING_ID))
45 #define INTKEYRING_VERIFY_STR "[verification-string]"
46 #define INTKEYRING_PBKDF2_ITERATIONS 10000
47 #define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000
48 #define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
49 #define INTKEYRING_KEY_LEN (256/8)
50 #define INTKEYRING_ENCRYPT_BUFF_LEN 1000
51 #define INTKEYRING_ENCRYPTED_MIN_LEN 50
52 #define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256"
54 #define INTKEYRING_PREFS "/plugins/keyrings/internal/"
56 /* win32 build defines such macro to override read() routine */
57 #undef read
59 typedef struct
61 enum
63 INTKEYRING_REQUEST_READ,
64 INTKEYRING_REQUEST_SAVE
65 } type;
66 PurpleAccount *account;
67 gchar *password;
68 union
70 PurpleKeyringReadCallback read;
71 PurpleKeyringSaveCallback save;
72 } cb;
73 gpointer cb_data;
74 } intkeyring_request;
76 typedef struct
78 guchar *data;
79 size_t len;
80 } intkeyring_buff_t;
82 static intkeyring_buff_t *intkeyring_key;
83 static GHashTable *intkeyring_passwords = NULL;
84 static GHashTable *intkeyring_ciphertexts = NULL;
86 static gboolean intkeyring_opened = FALSE;
87 static gboolean intkeyring_unlocked = FALSE;
89 static GList *intkeyring_pending_requests = NULL;
90 static void *intkeyring_masterpw_uirequest = NULL;
92 static PurpleKeyring *keyring_handler = NULL;
94 static void
95 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
96 gpointer data);
97 static void
98 intkeyring_save(PurpleAccount *account, const gchar *password,
99 PurpleKeyringSaveCallback cb, gpointer data);
100 static void
101 intkeyring_reencrypt_passwords(void);
102 static void
103 intkeyring_unlock(const gchar *message);
105 static void
106 intkeyring_request_free(intkeyring_request *req)
108 g_return_if_fail(req != NULL);
110 purple_str_wipe(req->password);
111 g_free(req);
114 static intkeyring_buff_t *
115 intkeyring_buff_new(guchar *data, size_t len)
117 intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
119 ret->data = data;
120 ret->len = len;
122 return ret;
125 static void
126 intkeyring_buff_free(intkeyring_buff_t *buff)
128 if (buff == NULL)
129 return;
131 memset(buff->data, 0, buff->len);
132 g_free(buff->data);
133 g_free(buff);
136 static intkeyring_buff_t *
137 intkeyring_buff_from_base64(const gchar *base64)
139 guchar *data;
140 gsize len;
142 data = purple_base64_decode(base64, &len);
144 return intkeyring_buff_new(data, len);
147 /************************************************************************/
148 /* Generic encryption stuff */
149 /************************************************************************/
151 static intkeyring_buff_t *
152 intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt)
154 PurpleCipher *cipher;
155 PurpleHash *hash;
156 gboolean succ;
157 intkeyring_buff_t *ret;
159 g_return_val_if_fail(passphrase != NULL, NULL);
161 hash = purple_sha256_hash_new();
162 cipher = purple_pbkdf2_cipher_new(hash);
164 g_object_set(G_OBJECT(cipher), "iter_count",
165 GUINT_TO_POINTER(purple_prefs_get_int(INTKEYRING_PREFS
166 "pbkdf2_iterations")), NULL);
167 g_object_set(G_OBJECT(cipher), "out_len", GUINT_TO_POINTER(
168 INTKEYRING_KEY_LEN), NULL);
169 purple_cipher_set_salt(cipher, salt->data, salt->len);
170 purple_cipher_set_key(cipher, (const guchar*)passphrase,
171 strlen(passphrase));
173 ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
174 INTKEYRING_KEY_LEN);
175 succ = purple_cipher_digest(cipher, ret->data, ret->len);
177 g_object_unref(cipher);
178 g_object_unref(hash);
180 if (!succ) {
181 intkeyring_buff_free(ret);
182 return NULL;
185 return ret;
188 static intkeyring_buff_t *
189 intkeyring_gen_salt(size_t len)
191 intkeyring_buff_t *ret;
192 size_t filled = 0;
194 g_return_val_if_fail(len > 0, NULL);
196 ret = intkeyring_buff_new(g_new(guchar, len), len);
198 while (filled < len) {
199 guint32 r = g_random_int();
200 int i;
202 for (i = 0; i < 4; i++) {
203 ret->data[filled++] = r & 0xFF;
204 if (filled >= len)
205 break;
206 r >>= 8;
210 return ret;
214 * Encrypts a plaintext using the specified key.
216 * Random IV will be generated and stored with ciphertext.
218 * Encryption scheme:
219 * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
220 * [ control string ] ++ [ pkcs7 padding ] )
221 * where:
222 * IV: Random, 128bit IV.
223 * plaintext: The plaintext.
224 * min length padding: The padding used to hide the rough length of short
225 * plaintexts, may have a length of 0.
226 * control string: Constant string, verifies corectness of decryption.
227 * pkcs7 padding: The padding used to determine total length of encrypted
228 * content (also provides some verification).
230 * @param key The AES key.
231 * @param str The NUL-terminated plaintext.
232 * @return The ciphertext with IV, encoded as base64. Must be g_free'd.
234 static gchar *
235 intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
237 PurpleCipher *cipher;
238 intkeyring_buff_t *iv;
239 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
240 size_t plaintext_len, text_len, verify_len;
241 guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
242 ssize_t encrypted_size;
244 g_return_val_if_fail(key != NULL, NULL);
245 g_return_val_if_fail(str != NULL, NULL);
247 text_len = strlen(str);
248 verify_len = strlen(INTKEYRING_VERIFY_STR);
249 plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN;
250 if (plaintext_len < text_len)
251 plaintext_len = text_len;
253 g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext),
254 NULL);
256 cipher = purple_aes_cipher_new();
257 g_return_val_if_fail(cipher != NULL, NULL);
259 memset(plaintext, 0, plaintext_len);
260 memcpy(plaintext, str, text_len);
261 memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
262 plaintext_len += verify_len;
264 iv = intkeyring_gen_salt(purple_cipher_get_block_size(cipher));
265 g_return_val_if_fail(iv != NULL, NULL);
266 purple_cipher_set_iv(cipher, iv->data, iv->len);
267 purple_cipher_set_key(cipher, key->data, key->len);
268 purple_cipher_set_batch_mode(cipher,
269 PURPLE_CIPHER_BATCH_MODE_CBC);
271 memcpy(encrypted_raw, iv->data, iv->len);
273 encrypted_size = purple_cipher_encrypt(cipher,
274 plaintext, plaintext_len, encrypted_raw + iv->len,
275 sizeof(encrypted_raw) - iv->len);
276 encrypted_size += iv->len;
278 memset(plaintext, 0, plaintext_len);
279 intkeyring_buff_free(iv);
280 g_object_unref(cipher);
282 if (encrypted_size < 0)
283 return NULL;
285 return purple_base64_encode(encrypted_raw, encrypted_size);
289 static gchar *
290 intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
292 PurpleCipher *cipher;
293 guchar *encrypted_raw;
294 gsize encrypted_size;
295 size_t iv_len, verify_len, text_len;
296 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
297 const gchar *verify_str = NULL;
298 ssize_t plaintext_len;
299 gchar *ret;
301 g_return_val_if_fail(key != NULL, NULL);
302 g_return_val_if_fail(str != NULL, NULL);
304 cipher = purple_aes_cipher_new();
305 g_return_val_if_fail(cipher != NULL, NULL);
307 encrypted_raw = purple_base64_decode(str, &encrypted_size);
308 g_return_val_if_fail(encrypted_raw != NULL, NULL);
310 iv_len = purple_cipher_get_block_size(cipher);
311 if (encrypted_size < iv_len) {
312 g_free(encrypted_raw);
313 return NULL;
316 purple_cipher_set_iv(cipher, encrypted_raw, iv_len);
317 purple_cipher_set_key(cipher, key->data, key->len);
318 purple_cipher_set_batch_mode(cipher,
319 PURPLE_CIPHER_BATCH_MODE_CBC);
321 plaintext_len = purple_cipher_decrypt(cipher,
322 encrypted_raw + iv_len, encrypted_size - iv_len,
323 plaintext, sizeof(plaintext));
325 g_free(encrypted_raw);
326 g_object_unref(cipher);
328 verify_len = strlen(INTKEYRING_VERIFY_STR);
329 /* Don't remove the len > 0 check! */
330 if (plaintext_len > 0 && (gsize)plaintext_len > verify_len &&
331 plaintext[plaintext_len] == '\0')
333 verify_str = (gchar*)plaintext + plaintext_len - verify_len;
336 if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) {
337 purple_debug_warning("keyring-internal",
338 "Verification failed on decryption\n");
339 memset(plaintext, 0, sizeof(plaintext));
340 return NULL;
342 g_assert(plaintext_len > 0);
344 text_len = plaintext_len - verify_len;
345 ret = g_new(gchar, text_len + 1);
346 memcpy(ret, plaintext, text_len);
347 memset(plaintext, 0, plaintext_len);
348 ret[text_len] = '\0';
350 return ret;
353 /************************************************************************/
354 /* Password encryption */
355 /************************************************************************/
357 static gboolean
358 intkeyring_change_masterpw(const gchar *new_password)
360 intkeyring_buff_t *salt, *key;
361 gchar *verifier = NULL, *salt_b64 = NULL;
362 int old_iter;
363 gboolean succ = TRUE;;
365 g_return_val_if_fail(intkeyring_unlocked, FALSE);
367 old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations");
368 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
369 purple_prefs_get_int(INTKEYRING_PREFS
370 "pbkdf2_desired_iterations"));
372 salt = intkeyring_gen_salt(32);
373 key = intkeyring_derive_key(new_password, salt);
375 if (salt && key && key->len == INTKEYRING_KEY_LEN) {
376 /* In fact, verify str will be concatenated twice before
377 * encryption (it's used as a suffix in encryption routine),
378 * but it's not a problem.
380 verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR);
381 salt_b64 = purple_base64_encode(salt->data, salt->len);
384 if (!verifier || !salt_b64) {
385 purple_debug_error("keyring-internal", "Failed to change "
386 "master password\n");
387 succ = FALSE;
388 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
389 old_iter);
390 } else {
391 purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
392 salt_b64);
393 purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
394 verifier);
396 intkeyring_buff_free(intkeyring_key);
397 intkeyring_key = key;
398 key = NULL;
400 intkeyring_reencrypt_passwords();
402 purple_signal_emit(purple_keyring_get_handle(),
403 "password-migration", NULL);
406 g_free(salt_b64);
407 g_free(verifier);
408 intkeyring_buff_free(salt);
409 intkeyring_buff_free(key);
411 return succ;
414 static void
415 intkeyring_process_queue(void)
417 GList *requests, *it;
418 gboolean open = intkeyring_unlocked;
420 requests = g_list_first(intkeyring_pending_requests);
421 intkeyring_pending_requests = NULL;
423 for (it = requests; it != NULL; it = g_list_next(it)) {
424 intkeyring_request *req = it->data;
426 if (open && req->type == INTKEYRING_REQUEST_READ) {
427 intkeyring_read(req->account, req->cb.read,
428 req->cb_data);
429 } else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
430 intkeyring_save(req->account, req->password,
431 req->cb.save, req->cb_data);
432 } else if (open)
433 g_assert_not_reached();
434 else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) {
435 GError *error = g_error_new(PURPLE_KEYRING_ERROR,
436 PURPLE_KEYRING_ERROR_CANCELLED,
437 _("Operation cancelled."));
438 if (req->type == INTKEYRING_REQUEST_READ) {
439 req->cb.read(req->account, NULL, error,
440 req->cb_data);
441 } else if (req->type == INTKEYRING_REQUEST_SAVE)
442 req->cb.save(req->account, error, req->cb_data);
443 else
444 g_assert_not_reached();
445 g_error_free(error);
448 intkeyring_request_free(req);
450 g_list_free(requests);
453 static void
454 intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
456 gchar *plaintext;
458 plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
459 if (plaintext == NULL) {
460 purple_debug_warning("keyring-internal",
461 "Failed to decrypt a password\n");
462 return;
465 g_hash_table_replace(intkeyring_passwords, account, plaintext);
468 static void
469 intkeyring_encrypt_password_if_needed(PurpleAccount *account)
471 const gchar *plaintext;
472 gchar *ciphertext;
474 if (intkeyring_key == NULL) {
475 g_hash_table_remove(intkeyring_ciphertexts, account);
476 return;
479 ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
480 if (ciphertext != NULL)
481 return;
483 plaintext = g_hash_table_lookup(intkeyring_passwords, account);
484 if (plaintext == NULL)
485 return;
487 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
488 g_return_if_fail(ciphertext != NULL);
490 g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
493 static void
494 intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
495 gpointer _unused)
497 intkeyring_encrypt_password_if_needed(account);
500 static void
501 intkeyring_reencrypt_passwords(void)
503 g_hash_table_remove_all(intkeyring_ciphertexts);
504 g_hash_table_foreach(intkeyring_passwords,
505 intkeyring_encrypt_passwords_if_needed_it, NULL);
508 static void
509 intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
510 gpointer _unused)
512 intkeyring_decrypt_password(account, ciphertext);
515 /************************************************************************/
516 /* Opening and unlocking keyring */
517 /************************************************************************/
519 static void
520 intkeyring_unlock_ok(gpointer _unused,
521 PurpleRequestFields *fields)
523 const gchar *masterpw;
524 gchar *verifier;
525 intkeyring_buff_t *salt, *key;
527 intkeyring_masterpw_uirequest = NULL;
529 if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS
530 "encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0)
532 purple_notify_error(NULL,
533 _("Unlocking internal keyring"),
534 _("Selected encryption method is not supported."),
535 _("Most probably, your passwords were encrypted with "
536 "newer Pidgin/libpurple version, please update."),
537 NULL);
538 return;
541 masterpw = purple_request_fields_get_string(fields, "password");
543 if (masterpw == NULL || masterpw[0] == '\0') {
544 intkeyring_unlock(_("No password entered."));
545 return;
548 salt = intkeyring_buff_from_base64(purple_prefs_get_string(
549 INTKEYRING_PREFS "pbkdf2_salt"));
550 key = intkeyring_derive_key(masterpw, salt);
551 intkeyring_buff_free(salt);
553 verifier = intkeyring_decrypt(key, purple_prefs_get_string(
554 INTKEYRING_PREFS "key_verifier"));
556 if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) {
557 g_free(verifier);
558 intkeyring_buff_free(key);
559 intkeyring_unlock(_("Invalid master password entered, "
560 "try again."));
561 return;
564 g_free(verifier);
565 intkeyring_key = key;
566 intkeyring_unlocked = TRUE;
568 g_hash_table_foreach(intkeyring_ciphertexts,
569 intkeyring_unlock_decrypt, NULL);
571 intkeyring_process_queue();
574 static void
575 intkeyring_unlock_cancel(gpointer _unused,
576 PurpleRequestFields *fields)
578 intkeyring_masterpw_uirequest = NULL;
579 intkeyring_process_queue();
582 static void
583 intkeyring_unlock(const gchar *message)
585 PurpleRequestFields *fields;
586 PurpleRequestFieldGroup *group;
587 PurpleRequestField *field;
588 const gchar *primary_msg, *secondary_msg = NULL;
590 if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL)
591 return;
593 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
594 intkeyring_unlocked = TRUE;
595 intkeyring_process_queue();
596 return;
599 fields = purple_request_fields_new();
600 group = purple_request_field_group_new(NULL);
601 purple_request_fields_add_group(fields, group);
603 field = purple_request_field_string_new("password",
604 _("Master password"), "", FALSE);
605 purple_request_field_string_set_masked(field, TRUE);
606 purple_request_field_group_add_field(group, field);
608 primary_msg = _("Please, enter master password");
609 if (message) {
610 secondary_msg = primary_msg;
611 primary_msg = message;
614 intkeyring_masterpw_uirequest = purple_request_fields(NULL,
615 _("Unlocking internal keyring"),
616 primary_msg, secondary_msg, fields,
617 _("OK"), G_CALLBACK(intkeyring_unlock_ok),
618 _("Cancel"), G_CALLBACK(intkeyring_unlock_cancel),
619 NULL, NULL);
622 static void
623 intkeyring_open(void)
625 if (intkeyring_opened)
626 return;
627 intkeyring_opened = TRUE;
629 intkeyring_passwords = g_hash_table_new_full(g_direct_hash,
630 g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
631 intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash,
632 g_direct_equal, NULL, g_free);
635 /************************************************************************/
636 /* Keyring interface implementation */
637 /************************************************************************/
639 static void
640 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
641 gpointer data)
643 const char *password;
644 GError *error;
646 intkeyring_open();
648 if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
649 account) != NULL)
651 intkeyring_request *req = g_new0(intkeyring_request, 1);
653 req->type = INTKEYRING_REQUEST_READ;
654 req->account = account;
655 req->cb.read = cb;
656 req->cb_data = data;
657 intkeyring_pending_requests =
658 g_list_append(intkeyring_pending_requests, req);
660 intkeyring_unlock(NULL);
661 return;
664 password = g_hash_table_lookup(intkeyring_passwords, account);
666 if (password != NULL) {
667 purple_debug_misc("keyring-internal",
668 "Got password for account %s (%s).\n",
669 purple_account_get_username(account),
670 purple_account_get_protocol_id(account));
671 if (cb != NULL)
672 cb(account, password, NULL, data);
673 } else {
674 if (purple_debug_is_verbose()) {
675 purple_debug_misc("keyring-internal",
676 "No password for account %s (%s).\n",
677 purple_account_get_username(account),
678 purple_account_get_protocol_id(account));
680 error = g_error_new(PURPLE_KEYRING_ERROR,
681 PURPLE_KEYRING_ERROR_NOPASSWORD,
682 _("Password not found."));
683 if (cb != NULL)
684 cb(account, NULL, error, data);
685 g_error_free(error);
689 static void
690 intkeyring_save(PurpleAccount *account, const gchar *password,
691 PurpleKeyringSaveCallback cb, gpointer data)
693 void *old_password;
695 intkeyring_open();
697 if (!intkeyring_unlocked) {
698 intkeyring_request *req;
700 if (password == NULL) {
701 g_hash_table_remove(intkeyring_ciphertexts, account);
702 g_hash_table_remove(intkeyring_passwords, account);
703 if (cb)
704 cb(account, NULL, data);
705 return;
708 req = g_new0(intkeyring_request, 1);
709 req->type = INTKEYRING_REQUEST_SAVE;
710 req->account = account;
711 req->password = g_strdup(password);
712 req->cb.save = cb;
713 req->cb_data = data;
714 intkeyring_pending_requests =
715 g_list_append(intkeyring_pending_requests, req);
717 intkeyring_unlock(NULL);
718 return;
721 g_hash_table_remove(intkeyring_ciphertexts, account);
723 old_password = g_hash_table_lookup(intkeyring_passwords, account);
725 if (password == NULL)
726 g_hash_table_remove(intkeyring_passwords, account);
727 else {
728 g_hash_table_replace(intkeyring_passwords, account,
729 g_strdup(password));
732 intkeyring_encrypt_password_if_needed(account);
734 if (!(password == NULL && old_password == NULL)) {
735 purple_debug_misc("keyring-internal",
736 "Password %s for account %s (%s).\n",
737 (password == NULL ? "removed" : (old_password == NULL ?
738 "saved" : "updated")),
739 purple_account_get_username(account),
740 purple_account_get_protocol_id(account));
741 } else if (purple_debug_is_verbose()) {
742 purple_debug_misc("keyring-internal",
743 "Password for account %s (%s) was already removed.\n",
744 purple_account_get_username(account),
745 purple_account_get_protocol_id(account));
748 if (cb != NULL)
749 cb(account, NULL, data);
752 static void
753 intkeyring_close(void)
755 if (!intkeyring_opened)
756 return;
757 intkeyring_opened = FALSE;
758 intkeyring_unlocked = FALSE;
760 if (intkeyring_masterpw_uirequest) {
761 purple_request_close(PURPLE_REQUEST_FIELDS,
762 intkeyring_masterpw_uirequest);
764 g_warn_if_fail(intkeyring_masterpw_uirequest == NULL);
765 g_warn_if_fail(intkeyring_pending_requests == NULL);
767 intkeyring_buff_free(intkeyring_key);
768 intkeyring_key = NULL;
769 g_hash_table_destroy(intkeyring_passwords);
770 intkeyring_passwords = NULL;
771 g_hash_table_destroy(intkeyring_ciphertexts);
772 intkeyring_ciphertexts = NULL;
775 static gboolean
776 intkeyring_import_password(PurpleAccount *account, const char *mode,
777 const char *data, GError **error)
779 g_return_val_if_fail(account != NULL, FALSE);
780 g_return_val_if_fail(data != NULL, FALSE);
782 intkeyring_open();
784 if (mode == NULL)
785 mode = "cleartext";
787 if (g_strcmp0(mode, "cleartext") == 0) {
788 g_hash_table_replace(intkeyring_passwords, account,
789 g_strdup(data));
790 return TRUE;
791 } else if (g_strcmp0(mode, "ciphertext") == 0) {
792 if (intkeyring_unlocked)
793 intkeyring_decrypt_password(account, data);
794 else {
795 g_hash_table_replace(intkeyring_ciphertexts, account,
796 g_strdup(data));
798 return TRUE;
799 } else {
800 if (error != NULL) {
801 *error = g_error_new(PURPLE_KEYRING_ERROR,
802 PURPLE_KEYRING_ERROR_BACKENDFAIL,
803 _("Invalid password storage mode."));
805 return FALSE;
809 static gboolean
810 intkeyring_export_password(PurpleAccount *account, const char **mode,
811 char **data, GError **error, GDestroyNotify *destroy)
813 gchar *ciphertext = NULL;
814 intkeyring_open();
816 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
817 gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
818 account);
820 if (cleartext == NULL)
821 return FALSE;
823 *mode = "cleartext";
824 *data = g_strdup(cleartext);
825 *destroy = (GDestroyNotify)purple_str_wipe;
826 return TRUE;
829 ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
830 account));
832 if (ciphertext == NULL && intkeyring_unlocked) {
833 gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
834 account);
836 if (plaintext == NULL)
837 return FALSE;
839 purple_debug_warning("keyring-internal", "Encrypted password "
840 "is missing at export (it shouldn't happen)\n");
841 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
844 if (ciphertext == NULL)
845 return FALSE;
847 *mode = "ciphertext";
848 *data = ciphertext;
849 *destroy = (GDestroyNotify)g_free;
850 return TRUE;
853 static PurpleRequestFields *
854 intkeyring_read_settings(void)
856 PurpleRequestFields *fields;
857 PurpleRequestFieldGroup *group;
858 PurpleRequestField *field;
860 fields = purple_request_fields_new();
861 group = purple_request_field_group_new(NULL);
862 purple_request_fields_add_group(fields, group);
864 field = purple_request_field_bool_new("encrypt_passwords",
865 _("Encrypt passwords"), purple_prefs_get_bool(
866 INTKEYRING_PREFS "encrypt_passwords"));
867 purple_request_field_group_add_field(group, field);
869 group = purple_request_field_group_new(_("Master password"));
870 purple_request_fields_add_group(fields, group);
872 field = purple_request_field_string_new("passphrase1",
873 _("New passphrase:"), "", FALSE);
874 purple_request_field_string_set_masked(field, TRUE);
875 purple_request_field_group_add_field(group, field);
877 field = purple_request_field_string_new("passphrase2",
878 _("New passphrase (again):"), "", FALSE);
879 purple_request_field_string_set_masked(field, TRUE);
880 purple_request_field_group_add_field(group, field);
882 group = purple_request_field_group_new(_("Advanced settings"));
883 purple_request_fields_add_group(fields, group);
885 field = purple_request_field_int_new("pbkdf2_desired_iterations",
886 _("Number of PBKDF2 iterations:"), purple_prefs_get_int(
887 INTKEYRING_PREFS "pbkdf2_desired_iterations"),
888 INTKEYRING_PBKDF2_ITERATIONS_MIN,
889 INTKEYRING_PBKDF2_ITERATIONS_MAX);
890 purple_request_field_group_add_field(group, field);
892 return fields;
895 static gboolean
896 intkeyring_apply_settings(void *notify_handle,
897 PurpleRequestFields *fields)
899 const gchar *passphrase, *passphrase2;
901 intkeyring_unlock(_("You have to unlock the keyring first."));
902 if (!intkeyring_unlocked)
903 return FALSE;
905 passphrase = purple_request_fields_get_string(fields, "passphrase1");
906 if (g_strcmp0(passphrase, "") == 0)
907 passphrase = NULL;
908 passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
909 if (g_strcmp0(passphrase2, "") == 0)
910 passphrase2 = NULL;
912 if (g_strcmp0(passphrase, passphrase2) != 0) {
913 purple_notify_error(notify_handle,
914 _("Internal keyring settings"),
915 _("Passphrases do not match"), NULL, NULL);
916 return FALSE;
919 if (purple_request_fields_get_bool(fields, "encrypt_passwords") &&
920 !passphrase && !intkeyring_key)
922 purple_notify_error(notify_handle,
923 _("Internal keyring settings"),
924 _("You have to set up a Master password, if you want "
925 "to enable encryption"), NULL, NULL);
926 return FALSE;
929 if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
930 passphrase)
932 purple_notify_error(notify_handle,
933 _("Internal keyring settings"),
934 _("You don't need any master password, if you won't "
935 "enable passwords encryption"), NULL, NULL);
936 return FALSE;
939 purple_prefs_set_string(INTKEYRING_PREFS "encryption_method",
940 INTKEYRING_ENCRYPTION_METHOD);
942 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
943 purple_request_fields_get_integer(fields,
944 "pbkdf2_desired_iterations"));
946 if (passphrase != NULL) {
947 if (!intkeyring_change_masterpw(passphrase))
948 return FALSE;
951 purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords",
952 purple_request_fields_get_bool(fields, "encrypt_passwords"));
954 purple_signal_emit(purple_keyring_get_handle(), "password-migration",
955 NULL);
958 return TRUE;
961 static PurplePluginInfo *
962 plugin_query(GError **error)
964 const gchar * const authors[] = INTKEYRING_AUTHORS;
966 return purple_plugin_info_new(
967 "id", INTKEYRING_ID,
968 "name", INTKEYRING_NAME,
969 "version", DISPLAY_VERSION,
970 "category", N_("Keyring"),
971 "summary", "Internal Keyring Plugin",
972 "description", INTKEYRING_DESCRIPTION,
973 "authors", authors,
974 "website", PURPLE_WEBSITE,
975 "abi-version", PURPLE_ABI_VERSION,
976 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
977 NULL
981 static gboolean
982 plugin_load(PurplePlugin *plugin, GError **error)
984 purple_prefs_add_none("/plugins/keyrings");
985 purple_prefs_add_none("/plugins/keyrings/internal");
986 purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE);
987 purple_prefs_add_string(INTKEYRING_PREFS "encryption_method",
988 INTKEYRING_ENCRYPTION_METHOD);
989 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
990 INTKEYRING_PBKDF2_ITERATIONS);
991 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations",
992 INTKEYRING_PBKDF2_ITERATIONS);
993 purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", "");
994 purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", "");
996 keyring_handler = purple_keyring_new();
998 purple_keyring_set_name(keyring_handler, _(INTKEYRING_NAME));
999 purple_keyring_set_id(keyring_handler, INTKEYRING_ID);
1000 purple_keyring_set_read_password(keyring_handler,
1001 intkeyring_read);
1002 purple_keyring_set_save_password(keyring_handler,
1003 intkeyring_save);
1004 purple_keyring_set_close_keyring(keyring_handler,
1005 intkeyring_close);
1006 purple_keyring_set_import_password(keyring_handler,
1007 intkeyring_import_password);
1008 purple_keyring_set_export_password(keyring_handler,
1009 intkeyring_export_password);
1010 purple_keyring_set_read_settings(keyring_handler,
1011 intkeyring_read_settings);
1012 purple_keyring_set_apply_settings(keyring_handler,
1013 intkeyring_apply_settings);
1015 purple_keyring_register(keyring_handler);
1017 return TRUE;
1020 static gboolean
1021 plugin_unload(PurplePlugin *plugin, GError **error)
1023 if (purple_keyring_get_inuse() == keyring_handler) {
1024 g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently "
1025 "in use.");
1026 purple_debug_warning("keyring-internal",
1027 "keyring in use, cannot unload\n");
1028 return FALSE;
1031 intkeyring_close();
1033 purple_keyring_unregister(keyring_handler);
1034 purple_keyring_free(keyring_handler);
1035 keyring_handler = NULL;
1037 if (intkeyring_key != NULL) {
1038 purple_debug_warning("keyring-internal", "Master key should be "
1039 "cleaned up at this point\n");
1040 intkeyring_buff_free(intkeyring_key);
1041 intkeyring_key = NULL;
1044 return TRUE;
1047 PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);