pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
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
);