* @file internalkeyring.c internal keyring * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program ; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include <nettle/pbkdf2.h> #define INTKEYRING_NAME N_("Internal keyring") #define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \ "storage behaviour for libpurple.") #define INTKEYRING_AUTHORS { "Tomek Wasilczyk <twasilczyk@pidgin.im>",NULL } #define INTKEYRING_ID PURPLE_DEFAULT_KEYRING #define INTKEYRING_DOMAIN (g_quark_from_static_string(INTKEYRING_ID)) #define INTKEYRING_VERIFY_STR "[verification-string]" #define INTKEYRING_PBKDF2_ITERATIONS 10000 #define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000 #define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000 #define INTKEYRING_KEY_LEN AES256_KEY_SIZE #define INTKEYRING_ENCRYPT_BUFF_LEN 1000 #define INTKEYRING_ENCRYPTED_MIN_LEN 50 #define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256" #define INTKEYRING_PREFS "/plugins/keyrings/internal/" /* win32 build defines such macro to override read() routine */ PurpleKeyringReadCallback read; PurpleKeyringSaveCallback save; static intkeyring_buff_t *intkeyring_key; static GHashTable *intkeyring_passwords = NULL; static GHashTable *intkeyring_ciphertexts = NULL; static gboolean intkeyring_opened = FALSE; static gboolean intkeyring_unlocked = FALSE; static GList *intkeyring_pending_requests = NULL; static void *intkeyring_masterpw_uirequest = NULL; static PurpleKeyring *keyring_handler = NULL; intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb, intkeyring_save(PurpleAccount *account, const gchar *password, PurpleKeyringSaveCallback cb, gpointer data); intkeyring_reencrypt_passwords(void); intkeyring_unlock(const gchar *message); intkeyring_request_free(intkeyring_request *req) g_return_if_fail(req != NULL); purple_str_wipe(req->password); static intkeyring_buff_t * intkeyring_buff_new(guchar *data, size_t len) intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1); intkeyring_buff_free(intkeyring_buff_t *buff) memset(buff->data, 0, buff->len); static intkeyring_buff_t * intkeyring_buff_from_base64(const gchar *base64) data = g_base64_decode(base64, &len); return intkeyring_buff_new(data, len); /************************************************************************/ /* Generic encryption stuff */ /************************************************************************/ static intkeyring_buff_t * intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt) g_return_val_if_fail(passphrase != NULL, NULL); ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN), pbkdf2_hmac_sha256(strlen(passphrase), (const uint8_t *)passphrase, purple_prefs_get_int(INTKEYRING_PREFS"pbkdf2_iterations"), salt->len, salt->data, ret->len, ret->data); static intkeyring_buff_t * intkeyring_gen_salt(size_t len) g_return_val_if_fail(len > 0, NULL); ret = intkeyring_buff_new(g_new(guchar, len), len); guint32 r = g_random_int(); for (i = 0; i < 4; i++) { ret->data[filled++] = r & 0xFF; * Encrypts a plaintext using the specified key. * Random IV will be generated and stored with ciphertext. * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++ * [ control string ] ++ [ pkcs7 padding ] ) * plaintext: The plaintext. * min length padding: The padding used to hide the rough length of short * plaintexts, may have a length of 0. * control string: Constant string, verifies corectness of decryption. * pkcs7 padding: The padding used to determine total length of encrypted * content (also provides some verification). * @param key The AES key. * @param str The NUL-terminated plaintext. * @return The ciphertext with IV, encoded as base64. Must be g_free'd. intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str) struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx; guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN]; size_t plaintext_len, text_len, verify_len; guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN]; g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(str != NULL, NULL); verify_len = strlen(INTKEYRING_VERIFY_STR); plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN; if (plaintext_len < text_len) plaintext_len = text_len; g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext), memset(plaintext, 0, plaintext_len); memcpy(plaintext, str, text_len); memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len); plaintext_len += verify_len; padding_len = AES_BLOCK_SIZE - (plaintext_len % AES_BLOCK_SIZE); if (plaintext_len + padding_len > INTKEYRING_ENCRYPT_BUFF_LEN) { purple_debug_error("keyring-internal", "Internal keyring encrypt buffer too small"); memset(plaintext + plaintext_len, padding_len, padding_len); plaintext_len += padding_len; iv = intkeyring_gen_salt(AES_BLOCK_SIZE); g_return_val_if_fail(iv != NULL, NULL); aes256_set_encrypt_key(&ctx.ctx, key->data); CBC_SET_IV(&ctx, iv->data); memcpy(encrypted_raw, iv->data, iv->len); CBC_ENCRYPT(&ctx, aes256_encrypt, plaintext_len, encrypted_raw + iv->len, plaintext); encrypted_size = plaintext_len; encrypted_size += iv->len; memset(plaintext, 0, plaintext_len); intkeyring_buff_free(iv); return g_base64_encode(encrypted_raw, encrypted_size); intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str) struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx; size_t iv_len, verify_len, text_len; guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN]; const gchar *verify_str = NULL; g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(str != NULL, NULL); encrypted_raw = g_base64_decode(str, &encrypted_size); g_return_val_if_fail(encrypted_raw != NULL, NULL); if (encrypted_size < iv_len) { aes256_set_decrypt_key(&ctx.ctx, key->data); CBC_SET_IV(&ctx, encrypted_raw); CBC_DECRYPT(&ctx, aes256_decrypt, encrypted_size - iv_len, plaintext, encrypted_raw + iv_len); plaintext_len = encrypted_size - iv_len; padding_len = plaintext[plaintext_len - 1]; if (padding_len == 0 || padding_len > AES_BLOCK_SIZE || padding_len > plaintext_len) { purple_debug_warning("internal-keyring", "Invalid padding length: %d (total %" G_GSIZE_FORMAT ") - most probably, the key was invalid\n", padding_len, plaintext_len); plaintext_len -= padding_len; for (i = 0; i < padding_len; ++i) { if (plaintext[plaintext_len + i] != padding_len) { purple_debug_warning("internal-keyring", "Padding doesn't match at pos %d (found %02x, " "most probably, the key was invalid\n", i, plaintext[plaintext_len + i], padding_len); memset(plaintext + plaintext_len, 0, padding_len); verify_len = strlen(INTKEYRING_VERIFY_STR); /* Don't remove the len > 0 check! */ if (plaintext_len > 0 && (gsize)plaintext_len > verify_len && plaintext[plaintext_len] == '\0') verify_str = (gchar*)plaintext + plaintext_len - verify_len; if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) { purple_debug_warning("keyring-internal", "Verification failed on decryption\n"); memset(plaintext, 0, sizeof(plaintext)); g_assert(plaintext_len > 0); text_len = plaintext_len - verify_len; ret = g_new(gchar, text_len + 1); memcpy(ret, plaintext, text_len); memset(plaintext, 0, plaintext_len); /************************************************************************/ /* Password encryption */ /************************************************************************/ intkeyring_change_masterpw(const gchar *new_password) intkeyring_buff_t *salt, *key; gchar *verifier = NULL, *salt_b64 = NULL; g_return_val_if_fail(intkeyring_unlocked, FALSE); old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations"); purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations", purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_desired_iterations")); salt = intkeyring_gen_salt(32); key = intkeyring_derive_key(new_password, salt); if (salt && key && key->len == INTKEYRING_KEY_LEN) { /* In fact, verify str will be concatenated twice before * encryption (it's used as a suffix in encryption routine), * but it's not a problem. verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR); salt_b64 = g_base64_encode(salt->data, salt->len); if (!verifier || !salt_b64) { purple_debug_error("keyring-internal", "Failed to change " purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations", purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt", purple_prefs_set_string(INTKEYRING_PREFS "key_verifier", intkeyring_buff_free(intkeyring_key); intkeyring_reencrypt_passwords(); purple_signal_emit(purple_keyring_get_handle(), "password-migration", NULL); intkeyring_buff_free(salt); intkeyring_buff_free(key); intkeyring_process_queue(void) gboolean open = intkeyring_unlocked; requests = g_list_first(intkeyring_pending_requests); intkeyring_pending_requests = NULL; for (it = requests; it != NULL; it = g_list_next(it)) { intkeyring_request *req = it->data; if (open && req->type == INTKEYRING_REQUEST_READ) { intkeyring_read(req->account, req->cb.read, } else if (open && req->type == INTKEYRING_REQUEST_SAVE) { intkeyring_save(req->account, req->password, req->cb.save, req->cb_data); else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) { GError *error = g_error_new_literal(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_CANCELLED, _("Operation cancelled.")); if (req->type == INTKEYRING_REQUEST_READ) { req->cb.read(req->account, NULL, error, } else if (req->type == INTKEYRING_REQUEST_SAVE) req->cb.save(req->account, error, req->cb_data); intkeyring_request_free(req); intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext) plaintext = intkeyring_decrypt(intkeyring_key, ciphertext); purple_debug_warning("keyring-internal", "Failed to decrypt a password\n"); g_hash_table_replace(intkeyring_passwords, account, plaintext); intkeyring_encrypt_password_if_needed(PurpleAccount *account) if (intkeyring_key == NULL) { g_hash_table_remove(intkeyring_ciphertexts, account); ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account); plaintext = g_hash_table_lookup(intkeyring_passwords, account); ciphertext = intkeyring_encrypt(intkeyring_key, plaintext); g_return_if_fail(ciphertext != NULL); g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext); intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext, intkeyring_encrypt_password_if_needed(account); intkeyring_reencrypt_passwords(void) g_hash_table_remove_all(intkeyring_ciphertexts); g_hash_table_foreach(intkeyring_passwords, intkeyring_encrypt_passwords_if_needed_it, NULL); intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext, intkeyring_decrypt_password(account, ciphertext); /************************************************************************/ /* Opening and unlocking keyring */ /************************************************************************/ intkeyring_unlock_ok(gpointer _unused, PurpleRequestFields *fields) intkeyring_buff_t *salt, *key; intkeyring_masterpw_uirequest = NULL; if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS "encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0) purple_notify_error(NULL, _("Unlocking internal keyring"), _("Selected encryption method is not supported."), _("Most probably, your passwords were encrypted with " "newer Pidgin/libpurple version, please update."), masterpw = purple_request_fields_get_string(fields, "password"); if (masterpw == NULL || masterpw[0] == '\0') { intkeyring_unlock(_("No password entered.")); salt = intkeyring_buff_from_base64(purple_prefs_get_string( INTKEYRING_PREFS "pbkdf2_salt")); key = intkeyring_derive_key(masterpw, salt); intkeyring_buff_free(salt); verifier = intkeyring_decrypt(key, purple_prefs_get_string( INTKEYRING_PREFS "key_verifier")); if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) { intkeyring_buff_free(key); intkeyring_unlock(_("Invalid master password entered, " intkeyring_unlocked = TRUE; g_hash_table_foreach(intkeyring_ciphertexts, intkeyring_unlock_decrypt, NULL); intkeyring_process_queue(); intkeyring_unlock_cancel(gpointer _unused, PurpleRequestFields *fields) intkeyring_masterpw_uirequest = NULL; intkeyring_process_queue(); intkeyring_unlock(const gchar *message) PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; const gchar *primary_msg, *secondary_msg = NULL; if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL) if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) { intkeyring_unlocked = TRUE; intkeyring_process_queue(); fields = purple_request_fields_new(); group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); field = purple_request_field_string_new("password", _("Master password"), "", FALSE); purple_request_field_string_set_masked(field, TRUE); purple_request_field_group_add_field(group, field); primary_msg = _("Please, enter master password"); secondary_msg = primary_msg; intkeyring_masterpw_uirequest = purple_request_fields(NULL, _("Unlocking internal keyring"), primary_msg, secondary_msg, fields, _("OK"), G_CALLBACK(intkeyring_unlock_ok), _("Cancel"), G_CALLBACK(intkeyring_unlock_cancel), intkeyring_opened = TRUE; intkeyring_passwords = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe); intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); /************************************************************************/ /* Keyring interface implementation */ /************************************************************************/ intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb, if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts, intkeyring_request *req = g_new0(intkeyring_request, 1); req->type = INTKEYRING_REQUEST_READ; intkeyring_pending_requests = g_list_append(intkeyring_pending_requests, req); password = g_hash_table_lookup(intkeyring_passwords, account); purple_debug_misc("keyring-internal", "Got password for account %s (%s).\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); cb(account, password, NULL, data); if (purple_debug_is_verbose()) { purple_debug_misc("keyring-internal", "No password for account %s (%s).\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); error = g_error_new_literal(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_NOPASSWORD, _("Password not found.")); cb(account, NULL, error, data); intkeyring_save(PurpleAccount *account, const gchar *password, PurpleKeyringSaveCallback cb, gpointer data) if (!intkeyring_unlocked) { g_hash_table_remove(intkeyring_ciphertexts, account); g_hash_table_remove(intkeyring_passwords, account); req = g_new0(intkeyring_request, 1); req->type = INTKEYRING_REQUEST_SAVE; req->password = g_strdup(password); intkeyring_pending_requests = g_list_append(intkeyring_pending_requests, req); g_hash_table_remove(intkeyring_ciphertexts, account); old_password = g_hash_table_lookup(intkeyring_passwords, account); g_hash_table_remove(intkeyring_passwords, account); g_hash_table_replace(intkeyring_passwords, account, intkeyring_encrypt_password_if_needed(account); if (!(password == NULL && old_password == NULL)) { purple_debug_misc("keyring-internal", "Password %s for account %s (%s).\n", (password == NULL ? "removed" : (old_password == NULL ? purple_account_get_username(account), purple_account_get_protocol_id(account)); } else if (purple_debug_is_verbose()) { purple_debug_misc("keyring-internal", "Password for account %s (%s) was already removed.\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); intkeyring_opened = FALSE; intkeyring_unlocked = FALSE; if (intkeyring_masterpw_uirequest) { purple_request_close(PURPLE_REQUEST_FIELDS, intkeyring_masterpw_uirequest); g_warn_if_fail(intkeyring_masterpw_uirequest == NULL); g_warn_if_fail(intkeyring_pending_requests == NULL); intkeyring_buff_free(intkeyring_key); g_hash_table_destroy(intkeyring_passwords); intkeyring_passwords = NULL; g_hash_table_destroy(intkeyring_ciphertexts); intkeyring_ciphertexts = NULL; intkeyring_import_password(PurpleAccount *account, const char *mode, const char *data, GError **error) g_return_val_if_fail(account != NULL, FALSE); g_return_val_if_fail(data != NULL, FALSE); if (g_strcmp0(mode, "cleartext") == 0) { g_hash_table_replace(intkeyring_passwords, account, } else if (g_strcmp0(mode, "ciphertext") == 0) { intkeyring_decrypt_password(account, data); g_hash_table_replace(intkeyring_ciphertexts, account, g_set_error_literal(error, PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_BACKENDFAIL, _("Invalid password storage mode.")); intkeyring_export_password(PurpleAccount *account, const char **mode, char **data, GError **error, GDestroyNotify *destroy) gchar *ciphertext = NULL; if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) { gchar *cleartext = g_hash_table_lookup(intkeyring_passwords, *data = g_strdup(cleartext); *destroy = (GDestroyNotify)purple_str_wipe; ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts, if (ciphertext == NULL && intkeyring_unlocked) { gchar *plaintext = g_hash_table_lookup(intkeyring_passwords, purple_debug_warning("keyring-internal", "Encrypted password " "is missing at export (it shouldn't happen)\n"); ciphertext = intkeyring_encrypt(intkeyring_key, plaintext); *destroy = (GDestroyNotify)g_free; static PurpleRequestFields * intkeyring_read_settings(void) PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; fields = purple_request_fields_new(); group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); field = purple_request_field_bool_new("encrypt_passwords", _("Encrypt passwords"), purple_prefs_get_bool( INTKEYRING_PREFS "encrypt_passwords")); purple_request_field_group_add_field(group, field); group = purple_request_field_group_new(_("Master password")); purple_request_fields_add_group(fields, group); field = purple_request_field_string_new("passphrase1", _("New passphrase:"), "", FALSE); purple_request_field_string_set_masked(field, TRUE); purple_request_field_group_add_field(group, field); field = purple_request_field_string_new("passphrase2", _("New passphrase (again):"), "", FALSE); purple_request_field_string_set_masked(field, TRUE); purple_request_field_group_add_field(group, field); group = purple_request_field_group_new(_("Advanced settings")); purple_request_fields_add_group(fields, group); field = purple_request_field_int_new("pbkdf2_desired_iterations", _("Number of PBKDF2 iterations:"), purple_prefs_get_int( INTKEYRING_PREFS "pbkdf2_desired_iterations"), INTKEYRING_PBKDF2_ITERATIONS_MIN, INTKEYRING_PBKDF2_ITERATIONS_MAX); purple_request_field_group_add_field(group, field); intkeyring_apply_settings(void *notify_handle, PurpleRequestFields *fields) const gchar *passphrase, *passphrase2; intkeyring_unlock(_("You have to unlock the keyring first.")); if (!intkeyring_unlocked) passphrase = purple_request_fields_get_string(fields, "passphrase1"); if (g_strcmp0(passphrase, "") == 0) passphrase2 = purple_request_fields_get_string(fields, "passphrase2"); if (g_strcmp0(passphrase2, "") == 0) if (g_strcmp0(passphrase, passphrase2) != 0) { purple_notify_error(notify_handle, _("Internal keyring settings"), _("Passphrases do not match"), NULL, NULL); if (purple_request_fields_get_bool(fields, "encrypt_passwords") && !passphrase && !intkeyring_key) purple_notify_error(notify_handle, _("Internal keyring settings"), _("You have to set up a Master password, if you want " "to enable encryption"), NULL, NULL); if (!purple_request_fields_get_bool(fields, "encrypt_passwords") && purple_notify_error(notify_handle, _("Internal keyring settings"), _("You don't need any master password, if you won't " "enable passwords encryption"), NULL, NULL); purple_prefs_set_string(INTKEYRING_PREFS "encryption_method", INTKEYRING_ENCRYPTION_METHOD); purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations", purple_request_fields_get_integer(fields, "pbkdf2_desired_iterations")); if (passphrase != NULL) { if (!intkeyring_change_masterpw(passphrase)) purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords", purple_request_fields_get_bool(fields, "encrypt_passwords")); purple_signal_emit(purple_keyring_get_handle(), "password-migration", static PurplePluginInfo * plugin_query(GError **error) const gchar * const authors[] = INTKEYRING_AUTHORS; return purple_plugin_info_new( "version", DISPLAY_VERSION, "category", N_("Keyring"), "summary", "Internal Keyring Plugin", "description", INTKEYRING_DESCRIPTION, "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL, plugin_load(PurplePlugin *plugin, GError **error) purple_prefs_add_none("/plugins/keyrings"); purple_prefs_add_none("/plugins/keyrings/internal"); purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE); purple_prefs_add_string(INTKEYRING_PREFS "encryption_method", INTKEYRING_ENCRYPTION_METHOD); purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations", INTKEYRING_PBKDF2_ITERATIONS); purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations", INTKEYRING_PBKDF2_ITERATIONS); purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", ""); purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", ""); keyring_handler = purple_keyring_new(); purple_keyring_set_name(keyring_handler, _(INTKEYRING_NAME)); purple_keyring_set_id(keyring_handler, INTKEYRING_ID); purple_keyring_set_read_password(keyring_handler, purple_keyring_set_save_password(keyring_handler, purple_keyring_set_close_keyring(keyring_handler, purple_keyring_set_import_password(keyring_handler, intkeyring_import_password); purple_keyring_set_export_password(keyring_handler, intkeyring_export_password); purple_keyring_set_read_settings(keyring_handler, intkeyring_read_settings); purple_keyring_set_apply_settings(keyring_handler, intkeyring_apply_settings); purple_keyring_register(keyring_handler); plugin_unload(PurplePlugin *plugin, GError **error) if (purple_keyring_get_inuse() == keyring_handler) { g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently " purple_debug_warning("keyring-internal", "keyring in use, cannot unload\n"); purple_keyring_unregister(keyring_handler); purple_keyring_free(keyring_handler); if (intkeyring_key != NULL) { purple_debug_warning("keyring-internal", "Master key should be " "cleaned up at this point\n"); intkeyring_buff_free(intkeyring_key); PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);