pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Remove the docklet
2019-11-15, Gary Kramlich
565eb39a3a01
Remove the docklet
/**
* @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
"internal.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
);