pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/**
* @file internalkeyring.c internal keyring
* @ingroup plugins
*/
/* purple
*
* 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
* source distribution.
*
* 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 <glib/gi18n-lib.h>
#include <purple.h>
#include <nettle/aes.h>
#include <nettle/cbc.h>
#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 */
#undef read
typedef struct
{
enum
{
INTKEYRING_REQUEST_READ,
INTKEYRING_REQUEST_SAVE
} type;
PurpleAccount *account;
gchar *password;
union
{
PurpleKeyringReadCallback read;
PurpleKeyringSaveCallback save;
} cb;
gpointer cb_data;
} intkeyring_request;
typedef struct
{
guchar *data;
size_t len;
} intkeyring_buff_t;
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;
static void
intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
gpointer data);
static void
intkeyring_save(PurpleAccount *account, const gchar *password,
PurpleKeyringSaveCallback cb, gpointer data);
static void
intkeyring_reencrypt_passwords(void);
static void
intkeyring_unlock(const gchar *message);
static void
intkeyring_request_free(intkeyring_request *req)
{
g_return_if_fail(req != NULL);
purple_str_wipe(req->password);
g_free(req);
}
static intkeyring_buff_t *
intkeyring_buff_new(guchar *data, size_t len)
{
intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
ret->data = data;
ret->len = len;
return ret;
}
static void
intkeyring_buff_free(intkeyring_buff_t *buff)
{
if (buff == NULL)
return;
memset(buff->data, 0, buff->len);
g_free(buff->data);
g_free(buff);
}
static intkeyring_buff_t *
intkeyring_buff_from_base64(const gchar *base64)
{
guchar *data;
gsize len;
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)
{
intkeyring_buff_t *ret;
g_return_val_if_fail(passphrase != NULL, NULL);
ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
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);
return ret;
}
static intkeyring_buff_t *
intkeyring_gen_salt(size_t len)
{
intkeyring_buff_t *ret;
size_t filled = 0;
g_return_val_if_fail(len > 0, NULL);
ret = intkeyring_buff_new(g_new(guchar, len), len);
while (filled < len) {
guint32 r = g_random_int();
int i;
for (i = 0; i < 4; i++) {
ret->data[filled++] = r & 0xFF;
if (filled >= len)
break;
r >>= 8;
}
}
return ret;
}
/**
* Encrypts a plaintext using the specified key.
*
* Random IV will be generated and stored with ciphertext.
*
* Encryption scheme:
* [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
* [ control string ] ++ [ pkcs7 padding ] )
* where:
* IV: Random, 128bit IV.
* 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.
*/
static gchar *
intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
{
struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
intkeyring_buff_t *iv;
guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
size_t plaintext_len, text_len, verify_len;
int padding_len;
guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
ssize_t encrypted_size;
g_return_val_if_fail(key != NULL, NULL);
g_return_val_if_fail(str != NULL, NULL);
text_len = strlen(str);
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),
NULL);
memset(plaintext, 0, plaintext_len);
memcpy(plaintext, str, text_len);
memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
plaintext_len += verify_len;
/* Pad PKCS7 */
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");
return NULL;
}
memset(plaintext + plaintext_len, padding_len, padding_len);
plaintext_len += padding_len;
/* Encrypt */
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);
if (encrypted_size < 0)
return NULL;
return g_base64_encode(encrypted_raw, encrypted_size);
}
static gchar *
intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
{
struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
guchar *encrypted_raw;
gsize encrypted_size;
size_t iv_len, verify_len, text_len;
guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
const gchar *verify_str = NULL;
size_t plaintext_len;
guint padding_len;
guint i;
gchar *ret;
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);
iv_len = AES_BLOCK_SIZE;
if (encrypted_size < iv_len) {
g_free(encrypted_raw);
return NULL;
}
/* Decrypt */
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;
g_free(encrypted_raw);
/* Unpad PKCS7 */
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);
return NULL;
}
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, "
"expected %02x) - "
"most probably, the key was invalid\n",
i, plaintext[plaintext_len + i], padding_len);
return NULL;
}
}
memset(plaintext + plaintext_len, 0, padding_len);
/* Verify */
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));
return NULL;
}
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);
ret[text_len] = '\0';
return ret;
}
/************************************************************************/
/* Password encryption */
/************************************************************************/
static gboolean
intkeyring_change_masterpw(const gchar *new_password)
{
intkeyring_buff_t *salt, *key;
gchar *verifier = NULL, *salt_b64 = NULL;
int old_iter;
gboolean succ = TRUE;;
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 "
"master password\n");
succ = FALSE;
purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
old_iter);
} else {
purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
salt_b64);
purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
verifier);
intkeyring_buff_free(intkeyring_key);
intkeyring_key = key;
key = NULL;
intkeyring_reencrypt_passwords();
purple_signal_emit(purple_keyring_get_handle(),
"password-migration", NULL);
}
g_free(salt_b64);
g_free(verifier);
intkeyring_buff_free(salt);
intkeyring_buff_free(key);
return succ;
}
static void
intkeyring_process_queue(void)
{
GList *requests, *it;
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,
req->cb_data);
} else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
intkeyring_save(req->account, req->password,
req->cb.save, req->cb_data);
} else if (open)
g_assert_not_reached();
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,
req->cb_data);
} else if (req->type == INTKEYRING_REQUEST_SAVE)
req->cb.save(req->account, error, req->cb_data);
else
g_assert_not_reached();
g_error_free(error);
}
intkeyring_request_free(req);
}
g_list_free(requests);
}
static void
intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
{
gchar *plaintext;
plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
if (plaintext == NULL) {
purple_debug_warning("keyring-internal",
"Failed to decrypt a password\n");
return;
}
g_hash_table_replace(intkeyring_passwords, account, plaintext);
}
static void
intkeyring_encrypt_password_if_needed(PurpleAccount *account)
{
const gchar *plaintext;
gchar *ciphertext;
if (intkeyring_key == NULL) {
g_hash_table_remove(intkeyring_ciphertexts, account);
return;
}
ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
if (ciphertext != NULL)
return;
plaintext = g_hash_table_lookup(intkeyring_passwords, account);
if (plaintext == NULL)
return;
ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
g_return_if_fail(ciphertext != NULL);
g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
}
static void
intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
gpointer _unused)
{
intkeyring_encrypt_password_if_needed(account);
}
static void
intkeyring_reencrypt_passwords(void)
{
g_hash_table_remove_all(intkeyring_ciphertexts);
g_hash_table_foreach(intkeyring_passwords,
intkeyring_encrypt_passwords_if_needed_it, NULL);
}
static void
intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
gpointer _unused)
{
intkeyring_decrypt_password(account, ciphertext);
}
/************************************************************************/
/* Opening and unlocking keyring */
/************************************************************************/
static void
intkeyring_unlock_ok(gpointer _unused,
PurpleRequestFields *fields)
{
const gchar *masterpw;
gchar *verifier;
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."),
NULL);
return;
}
masterpw = purple_request_fields_get_string(fields, "password");
if (masterpw == NULL || masterpw[0] == '\0') {
intkeyring_unlock(_("No password entered."));
return;
}
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) {
g_free(verifier);
intkeyring_buff_free(key);
intkeyring_unlock(_("Invalid master password entered, "
"try again."));
return;
}
g_free(verifier);
intkeyring_key = key;
intkeyring_unlocked = TRUE;
g_hash_table_foreach(intkeyring_ciphertexts,
intkeyring_unlock_decrypt, NULL);
intkeyring_process_queue();
}
static void
intkeyring_unlock_cancel(gpointer _unused,
PurpleRequestFields *fields)
{
intkeyring_masterpw_uirequest = NULL;
intkeyring_process_queue();
}
static void
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)
return;
if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
intkeyring_unlocked = TRUE;
intkeyring_process_queue();
return;
}
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");
if (message) {
secondary_msg = primary_msg;
primary_msg = message;
}
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),
NULL, NULL);
}
static void
intkeyring_open(void)
{
if (intkeyring_opened)
return;
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 */
/************************************************************************/
static void
intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
gpointer data)
{
const char *password;
GError *error;
intkeyring_open();
if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
account) != NULL)
{
intkeyring_request *req = g_new0(intkeyring_request, 1);
req->type = INTKEYRING_REQUEST_READ;
req->account = account;
req->cb.read = cb;
req->cb_data = data;
intkeyring_pending_requests =
g_list_append(intkeyring_pending_requests, req);
intkeyring_unlock(NULL);
return;
}
password = g_hash_table_lookup(intkeyring_passwords, account);
if (password != NULL) {
purple_debug_misc("keyring-internal",
"Got password for account %s (%s).\n",
purple_account_get_username(account),
purple_account_get_protocol_id(account));
if (cb != NULL)
cb(account, password, NULL, data);
} else {
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."));
if (cb != NULL)
cb(account, NULL, error, data);
g_error_free(error);
}
}
static void
intkeyring_save(PurpleAccount *account, const gchar *password,
PurpleKeyringSaveCallback cb, gpointer data)
{
void *old_password;
intkeyring_open();
if (!intkeyring_unlocked) {
intkeyring_request *req;
if (password == NULL) {
g_hash_table_remove(intkeyring_ciphertexts, account);
g_hash_table_remove(intkeyring_passwords, account);
if (cb)
cb(account, NULL, data);
return;
}
req = g_new0(intkeyring_request, 1);
req->type = INTKEYRING_REQUEST_SAVE;
req->account = account;
req->password = g_strdup(password);
req->cb.save = cb;
req->cb_data = data;
intkeyring_pending_requests =
g_list_append(intkeyring_pending_requests, req);
intkeyring_unlock(NULL);
return;
}
g_hash_table_remove(intkeyring_ciphertexts, account);
old_password = g_hash_table_lookup(intkeyring_passwords, account);
if (password == NULL)
g_hash_table_remove(intkeyring_passwords, account);
else {
g_hash_table_replace(intkeyring_passwords, account,
g_strdup(password));
}
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 ?
"saved" : "updated")),
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));
}
if (cb != NULL)
cb(account, NULL, data);
}
static void
intkeyring_close(void)
{
if (!intkeyring_opened)
return;
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);
intkeyring_key = NULL;
g_hash_table_destroy(intkeyring_passwords);
intkeyring_passwords = NULL;
g_hash_table_destroy(intkeyring_ciphertexts);
intkeyring_ciphertexts = NULL;
}
static gboolean
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);
intkeyring_open();
if (mode == NULL)
mode = "cleartext";
if (g_strcmp0(mode, "cleartext") == 0) {
g_hash_table_replace(intkeyring_passwords, account,
g_strdup(data));
return TRUE;
} else if (g_strcmp0(mode, "ciphertext") == 0) {
if (intkeyring_unlocked)
intkeyring_decrypt_password(account, data);
else {
g_hash_table_replace(intkeyring_ciphertexts, account,
g_strdup(data));
}
return TRUE;
} else {
g_set_error_literal(error, PURPLE_KEYRING_ERROR,
PURPLE_KEYRING_ERROR_BACKENDFAIL,
_("Invalid password storage mode."));
return FALSE;
}
}
static gboolean
intkeyring_export_password(PurpleAccount *account, const char **mode,
char **data, GError **error, GDestroyNotify *destroy)
{
gchar *ciphertext = NULL;
intkeyring_open();
if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
account);
if (cleartext == NULL)
return FALSE;
*mode = "cleartext";
*data = g_strdup(cleartext);
*destroy = (GDestroyNotify)purple_str_wipe;
return TRUE;
}
ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
account));
if (ciphertext == NULL && intkeyring_unlocked) {
gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
account);
if (plaintext == NULL)
return FALSE;
purple_debug_warning("keyring-internal", "Encrypted password "
"is missing at export (it shouldn't happen)\n");
ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
}
if (ciphertext == NULL)
return FALSE;
*mode = "ciphertext";
*data = ciphertext;
*destroy = g_free;
return TRUE;
}
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);
return fields;
}
static gboolean
intkeyring_apply_settings(void *notify_handle,
PurpleRequestFields *fields)
{
const gchar *passphrase, *passphrase2;
intkeyring_unlock(_("You have to unlock the keyring first."));
if (!intkeyring_unlocked)
return FALSE;
passphrase = purple_request_fields_get_string(fields, "passphrase1");
if (g_strcmp0(passphrase, "") == 0)
passphrase = NULL;
passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
if (g_strcmp0(passphrase2, "") == 0)
passphrase2 = NULL;
if (g_strcmp0(passphrase, passphrase2) != 0) {
purple_notify_error(notify_handle,
_("Internal keyring settings"),
_("Passphrases do not match"), NULL, NULL);
return FALSE;
}
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);
return FALSE;
}
if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
passphrase)
{
purple_notify_error(notify_handle,
_("Internal keyring settings"),
_("You don't need any master password, if you won't "
"enable passwords encryption"), NULL, NULL);
return FALSE;
}
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))
return FALSE;
}
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",
NULL);
return TRUE;
}
static PurplePluginInfo *
plugin_query(GError **error)
{
const gchar * const authors[] = INTKEYRING_AUTHORS;
return purple_plugin_info_new(
"id", INTKEYRING_ID,
"name", INTKEYRING_NAME,
"version", DISPLAY_VERSION,
"category", N_("Keyring"),
"summary", "Internal Keyring Plugin",
"description", INTKEYRING_DESCRIPTION,
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
"flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
NULL
);
}
static gboolean
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,
intkeyring_read);
purple_keyring_set_save_password(keyring_handler,
intkeyring_save);
purple_keyring_set_close_keyring(keyring_handler,
intkeyring_close);
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);
return TRUE;
}
static gboolean
plugin_unload(PurplePlugin *plugin, GError **error)
{
if (purple_keyring_get_inuse() == keyring_handler) {
g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently "
"in use.");
purple_debug_warning("keyring-internal",
"keyring in use, cannot unload\n");
return FALSE;
}
intkeyring_close();
purple_keyring_unregister(keyring_handler);
purple_keyring_free(keyring_handler);
keyring_handler = NULL;
if (intkeyring_key != NULL) {
purple_debug_warning("keyring-internal", "Master key should be "
"cleaned up at this point\n");
intkeyring_buff_free(intkeyring_key);
intkeyring_key = NULL;
}
return TRUE;
}
PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);