Replace functions which called once with their bodies
[pidgin-git.git] / libpurple / plugins / keyrings / internalkeyring.c
blob5dcd675681a20d6a8ffd3708a36427b253c0b1fc
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 <purple.h>
30 #include <nettle/aes.h>
31 #include <nettle/cbc.h>
32 #include <nettle/pbkdf2.h>
34 #define INTKEYRING_NAME N_("Internal keyring")
35 #define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
36 "storage behaviour for libpurple.")
37 #define INTKEYRING_AUTHORS { "Tomek Wasilczyk <twasilczyk@pidgin.im>",NULL }
38 #define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
39 #define INTKEYRING_DOMAIN (g_quark_from_static_string(INTKEYRING_ID))
41 #define INTKEYRING_VERIFY_STR "[verification-string]"
42 #define INTKEYRING_PBKDF2_ITERATIONS 10000
43 #define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000
44 #define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
45 #define INTKEYRING_KEY_LEN AES256_KEY_SIZE
46 #define INTKEYRING_ENCRYPT_BUFF_LEN 1000
47 #define INTKEYRING_ENCRYPTED_MIN_LEN 50
48 #define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256"
50 #define INTKEYRING_PREFS "/plugins/keyrings/internal/"
52 /* win32 build defines such macro to override read() routine */
53 #undef read
55 typedef struct
57 enum
59 INTKEYRING_REQUEST_READ,
60 INTKEYRING_REQUEST_SAVE
61 } type;
62 PurpleAccount *account;
63 gchar *password;
64 union
66 PurpleKeyringReadCallback read;
67 PurpleKeyringSaveCallback save;
68 } cb;
69 gpointer cb_data;
70 } intkeyring_request;
72 typedef struct
74 guchar *data;
75 size_t len;
76 } intkeyring_buff_t;
78 static intkeyring_buff_t *intkeyring_key;
79 static GHashTable *intkeyring_passwords = NULL;
80 static GHashTable *intkeyring_ciphertexts = NULL;
82 static gboolean intkeyring_opened = FALSE;
83 static gboolean intkeyring_unlocked = FALSE;
85 static GList *intkeyring_pending_requests = NULL;
86 static void *intkeyring_masterpw_uirequest = NULL;
88 static PurpleKeyring *keyring_handler = NULL;
90 static void
91 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
92 gpointer data);
93 static void
94 intkeyring_save(PurpleAccount *account, const gchar *password,
95 PurpleKeyringSaveCallback cb, gpointer data);
96 static void
97 intkeyring_reencrypt_passwords(void);
98 static void
99 intkeyring_unlock(const gchar *message);
101 static void
102 intkeyring_request_free(intkeyring_request *req)
104 g_return_if_fail(req != NULL);
106 purple_str_wipe(req->password);
107 g_free(req);
110 static intkeyring_buff_t *
111 intkeyring_buff_new(guchar *data, size_t len)
113 intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
115 ret->data = data;
116 ret->len = len;
118 return ret;
121 static void
122 intkeyring_buff_free(intkeyring_buff_t *buff)
124 if (buff == NULL)
125 return;
127 memset(buff->data, 0, buff->len);
128 g_free(buff->data);
129 g_free(buff);
132 static intkeyring_buff_t *
133 intkeyring_buff_from_base64(const gchar *base64)
135 guchar *data;
136 gsize len;
138 data = g_base64_decode(base64, &len);
140 return intkeyring_buff_new(data, len);
143 /************************************************************************/
144 /* Generic encryption stuff */
145 /************************************************************************/
147 static intkeyring_buff_t *
148 intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt)
150 intkeyring_buff_t *ret;
152 g_return_val_if_fail(passphrase != NULL, NULL);
154 ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
155 INTKEYRING_KEY_LEN);
157 pbkdf2_hmac_sha256(strlen(passphrase), (const uint8_t *)passphrase,
158 purple_prefs_get_int(INTKEYRING_PREFS"pbkdf2_iterations"),
159 salt->len, salt->data, ret->len, ret->data);
161 return ret;
164 static intkeyring_buff_t *
165 intkeyring_gen_salt(size_t len)
167 intkeyring_buff_t *ret;
168 size_t filled = 0;
170 g_return_val_if_fail(len > 0, NULL);
172 ret = intkeyring_buff_new(g_new(guchar, len), len);
174 while (filled < len) {
175 guint32 r = g_random_int();
176 int i;
178 for (i = 0; i < 4; i++) {
179 ret->data[filled++] = r & 0xFF;
180 if (filled >= len)
181 break;
182 r >>= 8;
186 return ret;
190 * Encrypts a plaintext using the specified key.
192 * Random IV will be generated and stored with ciphertext.
194 * Encryption scheme:
195 * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
196 * [ control string ] ++ [ pkcs7 padding ] )
197 * where:
198 * IV: Random, 128bit IV.
199 * plaintext: The plaintext.
200 * min length padding: The padding used to hide the rough length of short
201 * plaintexts, may have a length of 0.
202 * control string: Constant string, verifies corectness of decryption.
203 * pkcs7 padding: The padding used to determine total length of encrypted
204 * content (also provides some verification).
206 * @param key The AES key.
207 * @param str The NUL-terminated plaintext.
208 * @return The ciphertext with IV, encoded as base64. Must be g_free'd.
210 static gchar *
211 intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
213 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
214 intkeyring_buff_t *iv;
215 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
216 size_t plaintext_len, text_len, verify_len;
217 int padding_len;
218 guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
219 ssize_t encrypted_size;
221 g_return_val_if_fail(key != NULL, NULL);
222 g_return_val_if_fail(str != NULL, NULL);
224 text_len = strlen(str);
225 verify_len = strlen(INTKEYRING_VERIFY_STR);
226 plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN;
227 if (plaintext_len < text_len)
228 plaintext_len = text_len;
230 g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext),
231 NULL);
233 memset(plaintext, 0, plaintext_len);
234 memcpy(plaintext, str, text_len);
235 memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
236 plaintext_len += verify_len;
238 /* Pad PKCS7 */
239 padding_len = AES_BLOCK_SIZE - (plaintext_len % AES_BLOCK_SIZE);
241 if (plaintext_len + padding_len > INTKEYRING_ENCRYPT_BUFF_LEN) {
242 purple_debug_error("keyring-internal",
243 "Internal keyring encrypt buffer too small");
244 return NULL;
247 memset(plaintext + plaintext_len, padding_len, padding_len);
248 plaintext_len += padding_len;
250 /* Encrypt */
251 iv = intkeyring_gen_salt(AES_BLOCK_SIZE);
252 g_return_val_if_fail(iv != NULL, NULL);
253 aes256_set_encrypt_key(&ctx.ctx, key->data);
254 CBC_SET_IV(&ctx, iv->data);
256 memcpy(encrypted_raw, iv->data, iv->len);
258 CBC_ENCRYPT(&ctx, aes256_encrypt, plaintext_len,
259 encrypted_raw + iv->len, plaintext);
260 encrypted_size = plaintext_len;
261 encrypted_size += iv->len;
263 memset(plaintext, 0, plaintext_len);
264 intkeyring_buff_free(iv);
266 if (encrypted_size < 0)
267 return NULL;
269 return g_base64_encode(encrypted_raw, encrypted_size);
273 static gchar *
274 intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
276 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
277 guchar *encrypted_raw;
278 gsize encrypted_size;
279 size_t iv_len, verify_len, text_len;
280 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
281 const gchar *verify_str = NULL;
282 size_t plaintext_len;
283 guint padding_len;
284 guint i;
285 gchar *ret;
287 g_return_val_if_fail(key != NULL, NULL);
288 g_return_val_if_fail(str != NULL, NULL);
290 encrypted_raw = g_base64_decode(str, &encrypted_size);
291 g_return_val_if_fail(encrypted_raw != NULL, NULL);
293 iv_len = AES_BLOCK_SIZE;
294 if (encrypted_size < iv_len) {
295 g_free(encrypted_raw);
296 return NULL;
299 /* Decrypt */
300 aes256_set_decrypt_key(&ctx.ctx, key->data);
301 CBC_SET_IV(&ctx, encrypted_raw);
302 CBC_DECRYPT(&ctx, aes256_decrypt, encrypted_size - iv_len,
303 plaintext, encrypted_raw + iv_len);
304 plaintext_len = encrypted_size - iv_len;
305 g_free(encrypted_raw);
307 /* Unpad PKCS7 */
308 padding_len = plaintext[plaintext_len - 1];
309 if (padding_len == 0 || padding_len > AES_BLOCK_SIZE ||
310 padding_len > plaintext_len) {
311 purple_debug_warning("internal-keyring",
312 "Invalid padding length: %d (total %" G_GSIZE_FORMAT
313 ") - most probably, the key was invalid\n",
314 padding_len, plaintext_len);
315 return NULL;
318 plaintext_len -= padding_len;
319 for (i = 0; i < padding_len; ++i) {
320 if (plaintext[plaintext_len + i] != padding_len) {
321 purple_debug_warning("internal-keyring",
322 "Padding doesn't match at pos %d (found %02x, "
323 "expected %02x) - "
324 "most probably, the key was invalid\n",
325 i, plaintext[plaintext_len + i], padding_len);
326 return NULL;
330 memset(plaintext + plaintext_len, 0, padding_len);
332 /* Verify */
333 verify_len = strlen(INTKEYRING_VERIFY_STR);
334 /* Don't remove the len > 0 check! */
335 if (plaintext_len > 0 && (gsize)plaintext_len > verify_len &&
336 plaintext[plaintext_len] == '\0')
338 verify_str = (gchar*)plaintext + plaintext_len - verify_len;
341 if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) {
342 purple_debug_warning("keyring-internal",
343 "Verification failed on decryption\n");
344 memset(plaintext, 0, sizeof(plaintext));
345 return NULL;
347 g_assert(plaintext_len > 0);
349 text_len = plaintext_len - verify_len;
350 ret = g_new(gchar, text_len + 1);
351 memcpy(ret, plaintext, text_len);
352 memset(plaintext, 0, plaintext_len);
353 ret[text_len] = '\0';
355 return ret;
358 /************************************************************************/
359 /* Password encryption */
360 /************************************************************************/
362 static gboolean
363 intkeyring_change_masterpw(const gchar *new_password)
365 intkeyring_buff_t *salt, *key;
366 gchar *verifier = NULL, *salt_b64 = NULL;
367 int old_iter;
368 gboolean succ = TRUE;;
370 g_return_val_if_fail(intkeyring_unlocked, FALSE);
372 old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations");
373 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
374 purple_prefs_get_int(INTKEYRING_PREFS
375 "pbkdf2_desired_iterations"));
377 salt = intkeyring_gen_salt(32);
378 key = intkeyring_derive_key(new_password, salt);
380 if (salt && key && key->len == INTKEYRING_KEY_LEN) {
381 /* In fact, verify str will be concatenated twice before
382 * encryption (it's used as a suffix in encryption routine),
383 * but it's not a problem.
385 verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR);
386 salt_b64 = g_base64_encode(salt->data, salt->len);
389 if (!verifier || !salt_b64) {
390 purple_debug_error("keyring-internal", "Failed to change "
391 "master password\n");
392 succ = FALSE;
393 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
394 old_iter);
395 } else {
396 purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
397 salt_b64);
398 purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
399 verifier);
401 intkeyring_buff_free(intkeyring_key);
402 intkeyring_key = key;
403 key = NULL;
405 intkeyring_reencrypt_passwords();
407 purple_signal_emit(purple_keyring_get_handle(),
408 "password-migration", NULL);
411 g_free(salt_b64);
412 g_free(verifier);
413 intkeyring_buff_free(salt);
414 intkeyring_buff_free(key);
416 return succ;
419 static void
420 intkeyring_process_queue(void)
422 GList *requests, *it;
423 gboolean open = intkeyring_unlocked;
425 requests = g_list_first(intkeyring_pending_requests);
426 intkeyring_pending_requests = NULL;
428 for (it = requests; it != NULL; it = g_list_next(it)) {
429 intkeyring_request *req = it->data;
431 if (open && req->type == INTKEYRING_REQUEST_READ) {
432 intkeyring_read(req->account, req->cb.read,
433 req->cb_data);
434 } else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
435 intkeyring_save(req->account, req->password,
436 req->cb.save, req->cb_data);
437 } else if (open)
438 g_assert_not_reached();
439 else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) {
440 GError *error = g_error_new_literal(PURPLE_KEYRING_ERROR,
441 PURPLE_KEYRING_ERROR_CANCELLED,
442 _("Operation cancelled."));
443 if (req->type == INTKEYRING_REQUEST_READ) {
444 req->cb.read(req->account, NULL, error,
445 req->cb_data);
446 } else if (req->type == INTKEYRING_REQUEST_SAVE)
447 req->cb.save(req->account, error, req->cb_data);
448 else
449 g_assert_not_reached();
450 g_error_free(error);
453 intkeyring_request_free(req);
455 g_list_free(requests);
458 static void
459 intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
461 gchar *plaintext;
463 plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
464 if (plaintext == NULL) {
465 purple_debug_warning("keyring-internal",
466 "Failed to decrypt a password\n");
467 return;
470 g_hash_table_replace(intkeyring_passwords, account, plaintext);
473 static void
474 intkeyring_encrypt_password_if_needed(PurpleAccount *account)
476 const gchar *plaintext;
477 gchar *ciphertext;
479 if (intkeyring_key == NULL) {
480 g_hash_table_remove(intkeyring_ciphertexts, account);
481 return;
484 ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
485 if (ciphertext != NULL)
486 return;
488 plaintext = g_hash_table_lookup(intkeyring_passwords, account);
489 if (plaintext == NULL)
490 return;
492 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
493 g_return_if_fail(ciphertext != NULL);
495 g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
498 static void
499 intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
500 gpointer _unused)
502 intkeyring_encrypt_password_if_needed(account);
505 static void
506 intkeyring_reencrypt_passwords(void)
508 g_hash_table_remove_all(intkeyring_ciphertexts);
509 g_hash_table_foreach(intkeyring_passwords,
510 intkeyring_encrypt_passwords_if_needed_it, NULL);
513 static void
514 intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
515 gpointer _unused)
517 intkeyring_decrypt_password(account, ciphertext);
520 /************************************************************************/
521 /* Opening and unlocking keyring */
522 /************************************************************************/
524 static void
525 intkeyring_unlock_ok(gpointer _unused,
526 PurpleRequestFields *fields)
528 const gchar *masterpw;
529 gchar *verifier;
530 intkeyring_buff_t *salt, *key;
532 intkeyring_masterpw_uirequest = NULL;
534 if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS
535 "encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0)
537 purple_notify_error(NULL,
538 _("Unlocking internal keyring"),
539 _("Selected encryption method is not supported."),
540 _("Most probably, your passwords were encrypted with "
541 "newer Pidgin/libpurple version, please update."),
542 NULL);
543 return;
546 masterpw = purple_request_fields_get_string(fields, "password");
548 if (masterpw == NULL || masterpw[0] == '\0') {
549 intkeyring_unlock(_("No password entered."));
550 return;
553 salt = intkeyring_buff_from_base64(purple_prefs_get_string(
554 INTKEYRING_PREFS "pbkdf2_salt"));
555 key = intkeyring_derive_key(masterpw, salt);
556 intkeyring_buff_free(salt);
558 verifier = intkeyring_decrypt(key, purple_prefs_get_string(
559 INTKEYRING_PREFS "key_verifier"));
561 if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) {
562 g_free(verifier);
563 intkeyring_buff_free(key);
564 intkeyring_unlock(_("Invalid master password entered, "
565 "try again."));
566 return;
569 g_free(verifier);
570 intkeyring_key = key;
571 intkeyring_unlocked = TRUE;
573 g_hash_table_foreach(intkeyring_ciphertexts,
574 intkeyring_unlock_decrypt, NULL);
576 intkeyring_process_queue();
579 static void
580 intkeyring_unlock_cancel(gpointer _unused,
581 PurpleRequestFields *fields)
583 intkeyring_masterpw_uirequest = NULL;
584 intkeyring_process_queue();
587 static void
588 intkeyring_unlock(const gchar *message)
590 PurpleRequestFields *fields;
591 PurpleRequestFieldGroup *group;
592 PurpleRequestField *field;
593 const gchar *primary_msg, *secondary_msg = NULL;
595 if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL)
596 return;
598 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
599 intkeyring_unlocked = TRUE;
600 intkeyring_process_queue();
601 return;
604 fields = purple_request_fields_new();
605 group = purple_request_field_group_new(NULL);
606 purple_request_fields_add_group(fields, group);
608 field = purple_request_field_string_new("password",
609 _("Master password"), "", FALSE);
610 purple_request_field_string_set_masked(field, TRUE);
611 purple_request_field_group_add_field(group, field);
613 primary_msg = _("Please, enter master password");
614 if (message) {
615 secondary_msg = primary_msg;
616 primary_msg = message;
619 intkeyring_masterpw_uirequest = purple_request_fields(NULL,
620 _("Unlocking internal keyring"),
621 primary_msg, secondary_msg, fields,
622 _("OK"), G_CALLBACK(intkeyring_unlock_ok),
623 _("Cancel"), G_CALLBACK(intkeyring_unlock_cancel),
624 NULL, NULL);
627 static void
628 intkeyring_open(void)
630 if (intkeyring_opened)
631 return;
632 intkeyring_opened = TRUE;
634 intkeyring_passwords = g_hash_table_new_full(g_direct_hash,
635 g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
636 intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash,
637 g_direct_equal, NULL, g_free);
640 /************************************************************************/
641 /* Keyring interface implementation */
642 /************************************************************************/
644 static void
645 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
646 gpointer data)
648 const char *password;
649 GError *error;
651 intkeyring_open();
653 if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
654 account) != NULL)
656 intkeyring_request *req = g_new0(intkeyring_request, 1);
658 req->type = INTKEYRING_REQUEST_READ;
659 req->account = account;
660 req->cb.read = cb;
661 req->cb_data = data;
662 intkeyring_pending_requests =
663 g_list_append(intkeyring_pending_requests, req);
665 intkeyring_unlock(NULL);
666 return;
669 password = g_hash_table_lookup(intkeyring_passwords, account);
671 if (password != NULL) {
672 purple_debug_misc("keyring-internal",
673 "Got password for account %s (%s).\n",
674 purple_account_get_username(account),
675 purple_account_get_protocol_id(account));
676 if (cb != NULL)
677 cb(account, password, NULL, data);
678 } else {
679 if (purple_debug_is_verbose()) {
680 purple_debug_misc("keyring-internal",
681 "No password for account %s (%s).\n",
682 purple_account_get_username(account),
683 purple_account_get_protocol_id(account));
685 error = g_error_new_literal(PURPLE_KEYRING_ERROR,
686 PURPLE_KEYRING_ERROR_NOPASSWORD,
687 _("Password not found."));
688 if (cb != NULL)
689 cb(account, NULL, error, data);
690 g_error_free(error);
694 static void
695 intkeyring_save(PurpleAccount *account, const gchar *password,
696 PurpleKeyringSaveCallback cb, gpointer data)
698 void *old_password;
700 intkeyring_open();
702 if (!intkeyring_unlocked) {
703 intkeyring_request *req;
705 if (password == NULL) {
706 g_hash_table_remove(intkeyring_ciphertexts, account);
707 g_hash_table_remove(intkeyring_passwords, account);
708 if (cb)
709 cb(account, NULL, data);
710 return;
713 req = g_new0(intkeyring_request, 1);
714 req->type = INTKEYRING_REQUEST_SAVE;
715 req->account = account;
716 req->password = g_strdup(password);
717 req->cb.save = cb;
718 req->cb_data = data;
719 intkeyring_pending_requests =
720 g_list_append(intkeyring_pending_requests, req);
722 intkeyring_unlock(NULL);
723 return;
726 g_hash_table_remove(intkeyring_ciphertexts, account);
728 old_password = g_hash_table_lookup(intkeyring_passwords, account);
730 if (password == NULL)
731 g_hash_table_remove(intkeyring_passwords, account);
732 else {
733 g_hash_table_replace(intkeyring_passwords, account,
734 g_strdup(password));
737 intkeyring_encrypt_password_if_needed(account);
739 if (!(password == NULL && old_password == NULL)) {
740 purple_debug_misc("keyring-internal",
741 "Password %s for account %s (%s).\n",
742 (password == NULL ? "removed" : (old_password == NULL ?
743 "saved" : "updated")),
744 purple_account_get_username(account),
745 purple_account_get_protocol_id(account));
746 } else if (purple_debug_is_verbose()) {
747 purple_debug_misc("keyring-internal",
748 "Password for account %s (%s) was already removed.\n",
749 purple_account_get_username(account),
750 purple_account_get_protocol_id(account));
753 if (cb != NULL)
754 cb(account, NULL, data);
757 static void
758 intkeyring_close(void)
760 if (!intkeyring_opened)
761 return;
762 intkeyring_opened = FALSE;
763 intkeyring_unlocked = FALSE;
765 if (intkeyring_masterpw_uirequest) {
766 purple_request_close(PURPLE_REQUEST_FIELDS,
767 intkeyring_masterpw_uirequest);
769 g_warn_if_fail(intkeyring_masterpw_uirequest == NULL);
770 g_warn_if_fail(intkeyring_pending_requests == NULL);
772 intkeyring_buff_free(intkeyring_key);
773 intkeyring_key = NULL;
774 g_hash_table_destroy(intkeyring_passwords);
775 intkeyring_passwords = NULL;
776 g_hash_table_destroy(intkeyring_ciphertexts);
777 intkeyring_ciphertexts = NULL;
780 static gboolean
781 intkeyring_import_password(PurpleAccount *account, const char *mode,
782 const char *data, GError **error)
784 g_return_val_if_fail(account != NULL, FALSE);
785 g_return_val_if_fail(data != NULL, FALSE);
787 intkeyring_open();
789 if (mode == NULL)
790 mode = "cleartext";
792 if (g_strcmp0(mode, "cleartext") == 0) {
793 g_hash_table_replace(intkeyring_passwords, account,
794 g_strdup(data));
795 return TRUE;
796 } else if (g_strcmp0(mode, "ciphertext") == 0) {
797 if (intkeyring_unlocked)
798 intkeyring_decrypt_password(account, data);
799 else {
800 g_hash_table_replace(intkeyring_ciphertexts, account,
801 g_strdup(data));
803 return TRUE;
804 } else {
805 g_set_error_literal(error, PURPLE_KEYRING_ERROR,
806 PURPLE_KEYRING_ERROR_BACKENDFAIL,
807 _("Invalid password storage mode."));
808 return FALSE;
812 static gboolean
813 intkeyring_export_password(PurpleAccount *account, const char **mode,
814 char **data, GError **error, GDestroyNotify *destroy)
816 gchar *ciphertext = NULL;
817 intkeyring_open();
819 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
820 gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
821 account);
823 if (cleartext == NULL)
824 return FALSE;
826 *mode = "cleartext";
827 *data = g_strdup(cleartext);
828 *destroy = (GDestroyNotify)purple_str_wipe;
829 return TRUE;
832 ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
833 account));
835 if (ciphertext == NULL && intkeyring_unlocked) {
836 gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
837 account);
839 if (plaintext == NULL)
840 return FALSE;
842 purple_debug_warning("keyring-internal", "Encrypted password "
843 "is missing at export (it shouldn't happen)\n");
844 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
847 if (ciphertext == NULL)
848 return FALSE;
850 *mode = "ciphertext";
851 *data = ciphertext;
852 *destroy = (GDestroyNotify)g_free;
853 return TRUE;
856 static PurpleRequestFields *
857 intkeyring_read_settings(void)
859 PurpleRequestFields *fields;
860 PurpleRequestFieldGroup *group;
861 PurpleRequestField *field;
863 fields = purple_request_fields_new();
864 group = purple_request_field_group_new(NULL);
865 purple_request_fields_add_group(fields, group);
867 field = purple_request_field_bool_new("encrypt_passwords",
868 _("Encrypt passwords"), purple_prefs_get_bool(
869 INTKEYRING_PREFS "encrypt_passwords"));
870 purple_request_field_group_add_field(group, field);
872 group = purple_request_field_group_new(_("Master password"));
873 purple_request_fields_add_group(fields, group);
875 field = purple_request_field_string_new("passphrase1",
876 _("New passphrase:"), "", FALSE);
877 purple_request_field_string_set_masked(field, TRUE);
878 purple_request_field_group_add_field(group, field);
880 field = purple_request_field_string_new("passphrase2",
881 _("New passphrase (again):"), "", FALSE);
882 purple_request_field_string_set_masked(field, TRUE);
883 purple_request_field_group_add_field(group, field);
885 group = purple_request_field_group_new(_("Advanced settings"));
886 purple_request_fields_add_group(fields, group);
888 field = purple_request_field_int_new("pbkdf2_desired_iterations",
889 _("Number of PBKDF2 iterations:"), purple_prefs_get_int(
890 INTKEYRING_PREFS "pbkdf2_desired_iterations"),
891 INTKEYRING_PBKDF2_ITERATIONS_MIN,
892 INTKEYRING_PBKDF2_ITERATIONS_MAX);
893 purple_request_field_group_add_field(group, field);
895 return fields;
898 static gboolean
899 intkeyring_apply_settings(void *notify_handle,
900 PurpleRequestFields *fields)
902 const gchar *passphrase, *passphrase2;
904 intkeyring_unlock(_("You have to unlock the keyring first."));
905 if (!intkeyring_unlocked)
906 return FALSE;
908 passphrase = purple_request_fields_get_string(fields, "passphrase1");
909 if (g_strcmp0(passphrase, "") == 0)
910 passphrase = NULL;
911 passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
912 if (g_strcmp0(passphrase2, "") == 0)
913 passphrase2 = NULL;
915 if (g_strcmp0(passphrase, passphrase2) != 0) {
916 purple_notify_error(notify_handle,
917 _("Internal keyring settings"),
918 _("Passphrases do not match"), NULL, NULL);
919 return FALSE;
922 if (purple_request_fields_get_bool(fields, "encrypt_passwords") &&
923 !passphrase && !intkeyring_key)
925 purple_notify_error(notify_handle,
926 _("Internal keyring settings"),
927 _("You have to set up a Master password, if you want "
928 "to enable encryption"), NULL, NULL);
929 return FALSE;
932 if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
933 passphrase)
935 purple_notify_error(notify_handle,
936 _("Internal keyring settings"),
937 _("You don't need any master password, if you won't "
938 "enable passwords encryption"), NULL, NULL);
939 return FALSE;
942 purple_prefs_set_string(INTKEYRING_PREFS "encryption_method",
943 INTKEYRING_ENCRYPTION_METHOD);
945 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
946 purple_request_fields_get_integer(fields,
947 "pbkdf2_desired_iterations"));
949 if (passphrase != NULL) {
950 if (!intkeyring_change_masterpw(passphrase))
951 return FALSE;
954 purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords",
955 purple_request_fields_get_bool(fields, "encrypt_passwords"));
957 purple_signal_emit(purple_keyring_get_handle(), "password-migration",
958 NULL);
961 return TRUE;
964 static PurplePluginInfo *
965 plugin_query(GError **error)
967 const gchar * const authors[] = INTKEYRING_AUTHORS;
969 return purple_plugin_info_new(
970 "id", INTKEYRING_ID,
971 "name", INTKEYRING_NAME,
972 "version", DISPLAY_VERSION,
973 "category", N_("Keyring"),
974 "summary", "Internal Keyring Plugin",
975 "description", INTKEYRING_DESCRIPTION,
976 "authors", authors,
977 "website", PURPLE_WEBSITE,
978 "abi-version", PURPLE_ABI_VERSION,
979 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
980 NULL
984 static gboolean
985 plugin_load(PurplePlugin *plugin, GError **error)
987 purple_prefs_add_none("/plugins/keyrings");
988 purple_prefs_add_none("/plugins/keyrings/internal");
989 purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE);
990 purple_prefs_add_string(INTKEYRING_PREFS "encryption_method",
991 INTKEYRING_ENCRYPTION_METHOD);
992 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
993 INTKEYRING_PBKDF2_ITERATIONS);
994 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations",
995 INTKEYRING_PBKDF2_ITERATIONS);
996 purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", "");
997 purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", "");
999 keyring_handler = purple_keyring_new();
1001 purple_keyring_set_name(keyring_handler, _(INTKEYRING_NAME));
1002 purple_keyring_set_id(keyring_handler, INTKEYRING_ID);
1003 purple_keyring_set_read_password(keyring_handler,
1004 intkeyring_read);
1005 purple_keyring_set_save_password(keyring_handler,
1006 intkeyring_save);
1007 purple_keyring_set_close_keyring(keyring_handler,
1008 intkeyring_close);
1009 purple_keyring_set_import_password(keyring_handler,
1010 intkeyring_import_password);
1011 purple_keyring_set_export_password(keyring_handler,
1012 intkeyring_export_password);
1013 purple_keyring_set_read_settings(keyring_handler,
1014 intkeyring_read_settings);
1015 purple_keyring_set_apply_settings(keyring_handler,
1016 intkeyring_apply_settings);
1018 purple_keyring_register(keyring_handler);
1020 return TRUE;
1023 static gboolean
1024 plugin_unload(PurplePlugin *plugin, GError **error)
1026 if (purple_keyring_get_inuse() == keyring_handler) {
1027 g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently "
1028 "in use.");
1029 purple_debug_warning("keyring-internal",
1030 "keyring in use, cannot unload\n");
1031 return FALSE;
1034 intkeyring_close();
1036 purple_keyring_unregister(keyring_handler);
1037 purple_keyring_free(keyring_handler);
1038 keyring_handler = NULL;
1040 if (intkeyring_key != NULL) {
1041 purple_debug_warning("keyring-internal", "Master key should be "
1042 "cleaned up at this point\n");
1043 intkeyring_buff_free(intkeyring_key);
1044 intkeyring_key = NULL;
1047 return TRUE;
1050 PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);