pidgin/pidgin

Make sure we remove all the files we install.
release-2.x.y
2021-04-11, Gary Kramlich
5407cbf3c5dd
Make sure we remove all the files we install.

Patch was provided by Johann Lochner.

Testing Done:
Build and ran the installer and uninstaller and verified that `C:\Program Files (x86)\Pidgin` was empty and therefore removed.

Bugs closed: PIDGIN-17515

Reviewed at https://reviews.imfreedom.org/r/612/
/*
* 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.
*
* Original des taken from gpg
*
* des.c - DES and Triple-DES encryption/decryption Algorithm
* Copyright (C) 1998 Free Software Foundation, Inc.
*
* Please see below for more legal information!
*
* According to the definition of DES in FIPS PUB 46-2 from December 1993.
* For a description of triple encryption, see:
* Bruce Schneier: Applied Cryptography. Second Edition.
* John Wiley & Sons, 1996. ISBN 0-471-12845-7. Pages 358 ff.
*
* This file is part of GnuPG.
*
* 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 "cipher.h"
#include "dbus-maybe.h"
#include "debug.h"
#include "signals.h"
#include "value.h"
/*******************************************************************************
* Structs
******************************************************************************/
struct _PurpleCipher {
gchar *name; /**< Internal name - used for searching */
PurpleCipherOps *ops; /**< Operations supported by this cipher */
guint ref; /**< Reference count */
};
struct _PurpleCipherContext {
PurpleCipher *cipher; /**< Cipher this context is under */
gpointer data; /**< Internal cipher state data */
};
/******************************************************************************
* Globals
*****************************************************************************/
static GList *ciphers = NULL;
/******************************************************************************
* PurpleCipher API
*****************************************************************************/
const gchar *
purple_cipher_get_name(PurpleCipher *cipher) {
g_return_val_if_fail(cipher, NULL);
return cipher->name;
}
guint
purple_cipher_get_capabilities(PurpleCipher *cipher) {
PurpleCipherOps *ops = NULL;
guint caps = 0;
g_return_val_if_fail(cipher, 0);
ops = cipher->ops;
g_return_val_if_fail(ops, 0);
if(ops->set_option)
caps |= PURPLE_CIPHER_CAPS_SET_OPT;
if(ops->get_option)
caps |= PURPLE_CIPHER_CAPS_GET_OPT;
if(ops->init)
caps |= PURPLE_CIPHER_CAPS_INIT;
if(ops->reset)
caps |= PURPLE_CIPHER_CAPS_RESET;
if(ops->uninit)
caps |= PURPLE_CIPHER_CAPS_UNINIT;
if(ops->set_iv)
caps |= PURPLE_CIPHER_CAPS_SET_IV;
if(ops->append)
caps |= PURPLE_CIPHER_CAPS_APPEND;
if(ops->digest)
caps |= PURPLE_CIPHER_CAPS_DIGEST;
if(ops->encrypt)
caps |= PURPLE_CIPHER_CAPS_ENCRYPT;
if(ops->decrypt)
caps |= PURPLE_CIPHER_CAPS_DECRYPT;
if(ops->set_salt)
caps |= PURPLE_CIPHER_CAPS_SET_SALT;
if(ops->get_salt_size)
caps |= PURPLE_CIPHER_CAPS_GET_SALT_SIZE;
if(ops->set_key)
caps |= PURPLE_CIPHER_CAPS_SET_KEY;
if(ops->get_key_size)
caps |= PURPLE_CIPHER_CAPS_GET_KEY_SIZE;
if(ops->set_batch_mode)
caps |= PURPLE_CIPHER_CAPS_SET_BATCH_MODE;
if(ops->get_batch_mode)
caps |= PURPLE_CIPHER_CAPS_GET_BATCH_MODE;
if(ops->get_block_size)
caps |= PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE;
if(ops->set_key_with_len)
caps |= PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN;
return caps;
}
gboolean
purple_cipher_digest_region(const gchar *name, const guchar *data,
size_t data_len, size_t in_len,
guchar digest[], size_t *out_len)
{
PurpleCipher *cipher;
PurpleCipherContext *context;
gboolean ret = FALSE;
g_return_val_if_fail(name, FALSE);
g_return_val_if_fail(data, FALSE);
cipher = purple_ciphers_find_cipher(name);
g_return_val_if_fail(cipher, FALSE);
if(!cipher->ops->append || !cipher->ops->digest) {
purple_debug_warning("cipher", "purple_cipher_region failed: "
"the %s cipher does not support appending and or "
"digesting.", cipher->name);
return FALSE;
}
context = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context, data, data_len);
ret = purple_cipher_context_digest(context, in_len, digest, out_len);
purple_cipher_context_destroy(context);
return ret;
}
/******************************************************************************
* PurpleCiphers API
*****************************************************************************/
PurpleCipher *
purple_ciphers_find_cipher(const gchar *name) {
PurpleCipher *cipher;
GList *l;
g_return_val_if_fail(name, NULL);
for(l = ciphers; l; l = l->next) {
cipher = PURPLE_CIPHER(l->data);
if(!g_ascii_strcasecmp(cipher->name, name))
return cipher;
}
return NULL;
}
PurpleCipher *
purple_ciphers_register_cipher(const gchar *name, PurpleCipherOps *ops) {
PurpleCipher *cipher = NULL;
g_return_val_if_fail(name, NULL);
g_return_val_if_fail(ops, NULL);
g_return_val_if_fail(!purple_ciphers_find_cipher(name), NULL);
cipher = g_new0(PurpleCipher, 1);
PURPLE_DBUS_REGISTER_POINTER(cipher, PurpleCipher);
cipher->name = g_strdup(name);
cipher->ops = ops;
ciphers = g_list_append(ciphers, cipher);
purple_signal_emit(purple_ciphers_get_handle(), "cipher-added", cipher);
return cipher;
}
gboolean
purple_ciphers_unregister_cipher(PurpleCipher *cipher) {
g_return_val_if_fail(cipher, FALSE);
g_return_val_if_fail(cipher->ref == 0, FALSE);
purple_signal_emit(purple_ciphers_get_handle(), "cipher-removed", cipher);
ciphers = g_list_remove(ciphers, cipher);
g_free(cipher->name);
PURPLE_DBUS_UNREGISTER_POINTER(cipher);
g_free(cipher);
return TRUE;
}
GList *
purple_ciphers_get_ciphers() {
return ciphers;
}
/******************************************************************************
* PurpleCipher Subsystem API
*****************************************************************************/
gpointer
purple_ciphers_get_handle() {
static gint handle;
return &handle;
}
/* These are implemented in the purple-ciphers sublibrary built in the ciphers
* directory. We could put a header file in there, but it's less hassle for
* the developer to just add it here since they have to register it here as
* well.
*/
PurpleCipherOps *purple_des_cipher_get_ops();
PurpleCipherOps *purple_des3_cipher_get_ops();
PurpleCipherOps *purple_hmac_cipher_get_ops();
PurpleCipherOps *purple_md4_cipher_get_ops();
PurpleCipherOps *purple_md5_cipher_get_ops();
PurpleCipherOps *purple_rc4_cipher_get_ops();
PurpleCipherOps *purple_sha1_cipher_get_ops();
PurpleCipherOps *purple_sha256_cipher_get_ops();
void
purple_ciphers_init() {
gpointer handle;
handle = purple_ciphers_get_handle();
purple_signal_register(handle, "cipher-added",
purple_marshal_VOID__POINTER, NULL, 1,
purple_value_new(PURPLE_TYPE_SUBTYPE,
PURPLE_SUBTYPE_CIPHER));
purple_signal_register(handle, "cipher-removed",
purple_marshal_VOID__POINTER, NULL, 1,
purple_value_new(PURPLE_TYPE_SUBTYPE,
PURPLE_SUBTYPE_CIPHER));
purple_ciphers_register_cipher("md5", purple_md5_cipher_get_ops());
purple_ciphers_register_cipher("sha1", purple_sha1_cipher_get_ops());
purple_ciphers_register_cipher("sha256", purple_sha256_cipher_get_ops());
purple_ciphers_register_cipher("md4", purple_md4_cipher_get_ops());
purple_ciphers_register_cipher("hmac", purple_hmac_cipher_get_ops());
purple_ciphers_register_cipher("des", purple_des_cipher_get_ops());
purple_ciphers_register_cipher("des3", purple_des3_cipher_get_ops());
purple_ciphers_register_cipher("rc4", purple_rc4_cipher_get_ops());
}
void
purple_ciphers_uninit() {
PurpleCipher *cipher;
GList *l, *ll;
for(l = ciphers; l; l = ll) {
ll = l->next;
cipher = PURPLE_CIPHER(l->data);
purple_ciphers_unregister_cipher(cipher);
}
g_list_free(ciphers);
purple_signals_unregister_by_instance(purple_ciphers_get_handle());
}
/******************************************************************************
* PurpleCipherContext API
*****************************************************************************/
void
purple_cipher_context_set_option(PurpleCipherContext *context, const gchar *name,
gpointer value)
{
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
g_return_if_fail(name);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_option)
cipher->ops->set_option(context, name, value);
else
purple_debug_warning("cipher", "the %s cipher does not support the "
"set_option operation\n", cipher->name);
}
gpointer
purple_cipher_context_get_option(PurpleCipherContext *context, const gchar *name) {
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, NULL);
g_return_val_if_fail(name, NULL);
cipher = context->cipher;
g_return_val_if_fail(cipher, NULL);
if(cipher->ops && cipher->ops->get_option)
return cipher->ops->get_option(context, name);
else {
purple_debug_warning("cipher", "the %s cipher does not support the "
"get_option operation\n", cipher->name);
return NULL;
}
}
PurpleCipherContext *
purple_cipher_context_new(PurpleCipher *cipher, void *extra) {
PurpleCipherContext *context = NULL;
g_return_val_if_fail(cipher, NULL);
cipher->ref++;
context = g_new0(PurpleCipherContext, 1);
context->cipher = cipher;
if(cipher->ops->init)
cipher->ops->init(context, extra);
return context;
}
PurpleCipherContext *
purple_cipher_context_new_by_name(const gchar *name, void *extra) {
PurpleCipher *cipher;
g_return_val_if_fail(name, NULL);
cipher = purple_ciphers_find_cipher(name);
g_return_val_if_fail(cipher, NULL);
return purple_cipher_context_new(cipher, extra);
}
void
purple_cipher_context_reset(PurpleCipherContext *context, void *extra) {
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->reset)
context->cipher->ops->reset(context, extra);
}
void
purple_cipher_context_destroy(PurpleCipherContext *context) {
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
cipher->ref--;
if(cipher->ops && cipher->ops->uninit)
cipher->ops->uninit(context);
memset(context, 0, sizeof(*context));
g_free(context);
context = NULL;
}
void
purple_cipher_context_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
{
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
g_return_if_fail(iv);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_iv)
cipher->ops->set_iv(context, iv, len);
else
purple_debug_warning("cipher", "the %s cipher does not support the set"
"initialization vector operation\n", cipher->name);
}
void
purple_cipher_context_append(PurpleCipherContext *context, const guchar *data,
size_t len)
{
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->append)
cipher->ops->append(context, data, len);
else
purple_debug_warning("cipher", "the %s cipher does not support the append "
"operation\n", cipher->name);
}
gboolean
purple_cipher_context_digest(PurpleCipherContext *context, size_t in_len,
guchar digest[], size_t *out_len)
{
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, FALSE);
cipher = context->cipher;
if(cipher->ops && cipher->ops->digest)
return cipher->ops->digest(context, in_len, digest, out_len);
else {
purple_debug_warning("cipher", "the %s cipher does not support the digest "
"operation\n", cipher->name);
return FALSE;
}
}
gboolean
purple_cipher_context_digest_to_str(PurpleCipherContext *context, size_t in_len,
gchar digest_s[], size_t *out_len)
{
/* 8k is a bit excessive, will tweak later. */
guchar digest[BUF_LEN * 4];
size_t n, dlen = 0;
g_return_val_if_fail(context, FALSE);
g_return_val_if_fail(digest_s, FALSE);
if(!purple_cipher_context_digest(context, sizeof(digest), digest, &dlen))
return FALSE;
/* in_len must be greater than dlen * 2 so we have room for the NUL. */
if(in_len <= dlen * 2)
return FALSE;
for(n = 0; n < dlen; n++)
sprintf(digest_s + (n * 2), "%02x", digest[n]);
digest_s[n * 2] = '\0';
if(out_len)
*out_len = dlen * 2;
return TRUE;
}
gint
purple_cipher_context_encrypt(PurpleCipherContext *context, const guchar data[],
size_t len, guchar output[], size_t *outlen)
{
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->encrypt)
return cipher->ops->encrypt(context, data, len, output, outlen);
else {
purple_debug_warning("cipher", "the %s cipher does not support the encrypt"
"operation\n", cipher->name);
if(outlen)
*outlen = -1;
return -1;
}
}
gint
purple_cipher_context_decrypt(PurpleCipherContext *context, const guchar data[],
size_t len, guchar output[], size_t *outlen)
{
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->decrypt)
return cipher->ops->decrypt(context, data, len, output, outlen);
else {
purple_debug_warning("cipher", "the %s cipher does not support the decrypt"
"operation\n", cipher->name);
if(outlen)
*outlen = -1;
return -1;
}
}
void
purple_cipher_context_set_salt(PurpleCipherContext *context, guchar *salt) {
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_salt)
cipher->ops->set_salt(context, salt);
else
purple_debug_warning("cipher", "the %s cipher does not support the "
"set_salt operation\n", cipher->name);
}
size_t
purple_cipher_context_get_salt_size(PurpleCipherContext *context) {
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->get_salt_size)
return cipher->ops->get_salt_size(context);
else {
purple_debug_warning("cipher", "the %s cipher does not support the "
"get_salt_size operation\n", cipher->name);
return -1;
}
}
void
purple_cipher_context_set_key(PurpleCipherContext *context, const guchar *key) {
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_key)
cipher->ops->set_key(context, key);
else
purple_debug_warning("cipher", "the %s cipher does not support the "
"set_key operation\n", cipher->name);
}
size_t
purple_cipher_context_get_key_size(PurpleCipherContext *context) {
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->get_key_size)
return cipher->ops->get_key_size(context);
else {
purple_debug_warning("cipher", "the %s cipher does not support the "
"get_key_size operation\n", cipher->name);
return -1;
}
}
void
purple_cipher_context_set_batch_mode(PurpleCipherContext *context,
PurpleCipherBatchMode mode)
{
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_batch_mode)
cipher->ops->set_batch_mode(context, mode);
else
purple_debug_warning("cipher", "The %s cipher does not support the "
"set_batch_mode operation\n", cipher->name);
}
PurpleCipherBatchMode
purple_cipher_context_get_batch_mode(PurpleCipherContext *context)
{
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->get_batch_mode)
return cipher->ops->get_batch_mode(context);
else {
purple_debug_warning("cipher", "The %s cipher does not support the "
"get_batch_mode operation\n", cipher->name);
return -1;
}
}
size_t
purple_cipher_context_get_block_size(PurpleCipherContext *context)
{
PurpleCipher *cipher = NULL;
g_return_val_if_fail(context, -1);
cipher = context->cipher;
g_return_val_if_fail(cipher, -1);
if(cipher->ops && cipher->ops->get_block_size)
return cipher->ops->get_block_size(context);
else {
purple_debug_warning("cipher", "The %s cipher does not support the "
"get_block_size operation\n", cipher->name);
return -1;
}
}
void
purple_cipher_context_set_key_with_len(PurpleCipherContext *context,
const guchar *key, size_t len)
{
PurpleCipher *cipher = NULL;
g_return_if_fail(context);
cipher = context->cipher;
g_return_if_fail(cipher);
if(cipher->ops && cipher->ops->set_key_with_len)
cipher->ops->set_key_with_len(context, key, len);
else
purple_debug_warning("cipher", "The %s cipher does not support the "
"set_key_with_len operation\n", cipher->name);
}
void
purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data) {
g_return_if_fail(context);
context->data = data;
}
gpointer
purple_cipher_context_get_data(PurpleCipherContext *context) {
g_return_val_if_fail(context, NULL);
return context->data;
}
gchar *purple_cipher_http_digest_calculate_session_key(
const gchar *algorithm,
const gchar *username,
const gchar *realm,
const gchar *password,
const gchar *nonce,
const gchar *client_nonce)
{
PurpleCipher *cipher;
PurpleCipherContext *context;
gchar hash[33]; /* We only support MD5. */
g_return_val_if_fail(username != NULL, NULL);
g_return_val_if_fail(realm != NULL, NULL);
g_return_val_if_fail(password != NULL, NULL);
g_return_val_if_fail(nonce != NULL, NULL);
/* Check for a supported algorithm. */
g_return_val_if_fail(algorithm == NULL ||
*algorithm == '\0' ||
g_ascii_strcasecmp(algorithm, "MD5") ||
g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
cipher = purple_ciphers_find_cipher("md5");
g_return_val_if_fail(cipher != NULL, NULL);
context = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context, (guchar *)username, strlen(username));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)realm, strlen(realm));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)password, strlen(password));
if (algorithm != NULL && !g_ascii_strcasecmp(algorithm, "MD5-sess"))
{
guchar digest[16];
if (client_nonce == NULL)
{
purple_cipher_context_destroy(context);
purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation.\n");
return NULL;
}
purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
purple_cipher_context_destroy(context);
context = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context, digest, sizeof(digest));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)nonce, strlen(nonce));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce));
}
purple_cipher_context_digest_to_str(context, sizeof(hash), hash, NULL);
purple_cipher_context_destroy(context);
return g_strdup(hash);
}
gchar *purple_cipher_http_digest_calculate_response(
const gchar *algorithm,
const gchar *method,
const gchar *digest_uri,
const gchar *qop,
const gchar *entity,
const gchar *nonce,
const gchar *nonce_count,
const gchar *client_nonce,
const gchar *session_key)
{
PurpleCipher *cipher;
PurpleCipherContext *context;
static gchar hash2[33]; /* We only support MD5. */
g_return_val_if_fail(method != NULL, NULL);
g_return_val_if_fail(digest_uri != NULL, NULL);
g_return_val_if_fail(nonce != NULL, NULL);
g_return_val_if_fail(session_key != NULL, NULL);
/* Check for a supported algorithm. */
g_return_val_if_fail(algorithm == NULL ||
*algorithm == '\0' ||
g_ascii_strcasecmp(algorithm, "MD5") ||
g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
/* Check for a supported "quality of protection". */
g_return_val_if_fail(qop == NULL ||
*qop == '\0' ||
g_ascii_strcasecmp(qop, "auth") ||
g_ascii_strcasecmp(qop, "auth-int"), NULL);
cipher = purple_ciphers_find_cipher("md5");
g_return_val_if_fail(cipher != NULL, NULL);
context = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context, (guchar *)method, strlen(method));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)digest_uri, strlen(digest_uri));
if (qop != NULL && !g_ascii_strcasecmp(qop, "auth-int"))
{
PurpleCipherContext *context2;
gchar entity_hash[33];
if (entity == NULL)
{
purple_cipher_context_destroy(context);
purple_debug_error("cipher", "Required entity missing for auth-int digest calculation.\n");
return NULL;
}
context2 = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context2, (guchar *)entity, strlen(entity));
purple_cipher_context_digest_to_str(context2, sizeof(entity_hash), entity_hash, NULL);
purple_cipher_context_destroy(context2);
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)entity_hash, strlen(entity_hash));
}
purple_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL);
purple_cipher_context_destroy(context);
context = purple_cipher_context_new(cipher, NULL);
purple_cipher_context_append(context, (guchar *)session_key, strlen(session_key));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)nonce, strlen(nonce));
purple_cipher_context_append(context, (guchar *)":", 1);
if (qop != NULL && *qop != '\0')
{
if (nonce_count == NULL)
{
purple_cipher_context_destroy(context);
purple_debug_error("cipher", "Required nonce_count missing for digest calculation.\n");
return NULL;
}
if (client_nonce == NULL)
{
purple_cipher_context_destroy(context);
purple_debug_error("cipher", "Required client_nonce missing for digest calculation.\n");
return NULL;
}
purple_cipher_context_append(context, (guchar *)nonce_count, strlen(nonce_count));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce));
purple_cipher_context_append(context, (guchar *)":", 1);
purple_cipher_context_append(context, (guchar *)qop, strlen(qop));
purple_cipher_context_append(context, (guchar *)":", 1);
}
purple_cipher_context_append(context, (guchar *)hash2, strlen(hash2));
purple_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL);
purple_cipher_context_destroy(context);
return g_strdup(hash2);
}