pidgin/pidgin

Make libpurple depend on its schemas

13 months ago, Gary Kramlich
d039c29040d3
Make libpurple depend on its schemas

In certain situations you could build libpurple but not the schemas which would
cause runtime failures.

Testing Done:
Built

Reviewed at https://reviews.imfreedom.org/r/2452/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include <glib/gi18n-lib.h>
#include <gplugin.h>
#include <gplugin-native.h>
#include <purple.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#define KEYCHAIN_ACCESS_ID "purple/keychain-access"
#define KEYCHAIN_ACCESS_NAME N_("Keychain Access")
#define KEYCHAIN_ACCESS_SUMMARY N_("Keychain Access credential provider")
#define KEYCHAIN_ACCESS_DESCRIPTION N_("This plugin will store passwords in " \
"Keychain Access on macOS.")
#define KEYCHAIN_ACCESS_DOMAIN (g_quark_from_static_string("keychain-access"))
#define PURPLE_TYPE_KEYCHAIN_ACCESS (purple_keychain_access_get_type())
G_DECLARE_FINAL_TYPE(PurpleKeychainAccess, purple_keychain_access,
PURPLE, KEYCHAIN_ACCESS, PurpleCredentialProvider)
struct _PurpleKeychainAccess {
PurpleCredentialProvider parent;
};
G_DEFINE_DYNAMIC_TYPE(PurpleKeychainAccess, purple_keychain_access,
PURPLE_TYPE_CREDENTIAL_PROVIDER)
/* Most of this work is heavily based off of
* https://stackoverflow.com/a/58850099.
*/
/******************************************************************************
* Globals
*****************************************************************************/
static PurpleCredentialProvider *instance = NULL;
/******************************************************************************
* Helpers
*****************************************************************************/
static void
purple_keychain_access_set_task_error_from_osstatus(GTask *task,
OSStatus result)
{
CFStringRef error_msg;
gchar buff[255];
gboolean converted = FALSE;
error_msg = SecCopyErrorMessageString(result, NULL);
converted = CFStringGetCString(error_msg, buff, sizeof(buff),
kCFStringEncodingUTF8);
if(converted) {
g_task_return_new_error(task, KEYCHAIN_ACCESS_DOMAIN, result,
"%s", buff);
} else {
g_task_return_new_error(task, KEYCHAIN_ACCESS_DOMAIN, result,
"unknown error");
}
CFRelease(error_msg);
}
/******************************************************************************
* PurpleCredentialProvider Implementation
*****************************************************************************/
static void
purple_keychain_access_read_password_async(PurpleCredentialProvider *provider,
PurpleAccount *account,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data)
{
PurpleContactInfo *info = NULL;
GTask *task = NULL;
const gchar *account_name = NULL;
CFStringRef keys[4];
CFStringRef cf_account_name;
CFTypeRef values[4];
CFTypeRef item;
CFDictionaryRef query;
OSStatus result;
g_return_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider));
g_return_if_fail(PURPLE_IS_ACCOUNT(account));
info = PURPLE_CONTACT_INFO(account);
account_name = purple_contact_info_get_username(info);
cf_account_name = CFStringCreateWithCString(kCFAllocatorDefault,
account_name,
kCFStringEncodingUTF8);
/* Set our security class. */
keys[0] = kSecClass;
values[0] = kSecClassGenericPassword;
/* Set the username. */
keys[1] = kSecAttrAccount;
values[1] = cf_account_name;
/* Make sure the password is decrypted. */
keys[2] = kSecReturnData;
values[2] = kCFBooleanTrue;
/* Limit to a single result. */
keys[3] = kSecMatchLimit;
values[3] = kSecMatchLimitOne;
/* Build our query. */
query = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys,
(const void **)values, 4, NULL, NULL);
/* Finally query the item. */
result = SecItemCopyMatching(query, &item);
/* Cleanup */
CFRelease(cf_account_name);
CFRelease(query);
/* Create the task and return our results. */
task = g_task_new(provider, cancellable, callback, data);
if(result == errSecSuccess) {
if(CFGetTypeID(item) != CFDataGetTypeID()) {
g_task_return_new_error(task, KEYCHAIN_ACCESS_DOMAIN, 0,
"unexpected item found");
} else {
CFStringRef cf_password;
gchar buff[255];
cf_password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault,
(CFDataRef)item,
kCFStringEncodingUTF8);
if(CFStringGetCString(cf_password, buff, sizeof(buff),
kCFStringEncodingUTF8))
{
g_task_return_pointer(task, g_strdup(buff), g_free);
} else {
g_task_return_new_error(task, KEYCHAIN_ACCESS_DOMAIN, 0,
"failed to read password");
}
CFRelease(cf_password);
}
} else {
purple_keychain_access_set_task_error_from_osstatus(task, result);
}
g_object_unref(task);
}
static gchar *
purple_keychain_access_read_password_finish(PurpleCredentialProvider *provider,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), NULL);
g_return_val_if_fail(G_IS_ASYNC_RESULT(result), NULL);
return g_task_propagate_pointer(G_TASK(result), error);
}
static void
purple_keychain_access_write_password_async(PurpleCredentialProvider *provider,
PurpleAccount *account,
const gchar *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data)
{
PurpleContactInfo *info = NULL;
GTask *task = NULL;
const gchar *account_name = NULL;
CFStringRef keys[3];
CFTypeRef values[3];
CFDictionaryRef query;
CFStringRef cf_account_name, cf_password;
OSStatus result;
g_return_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider));
g_return_if_fail(PURPLE_IS_ACCOUNT(account));
g_return_if_fail(password != NULL);
info = PURPLE_CONTACT_INFO(account);
account_name = purple_contact_info_get_username(info);
cf_account_name = CFStringCreateWithCString(kCFAllocatorDefault,
account_name,
kCFStringEncodingUTF8);
cf_password = CFStringCreateWithCString(kCFAllocatorDefault, password,
kCFStringEncodingUTF8);
/* set our security class */
keys[0] = kSecClass;
values[0] = kSecClassGenericPassword;
/* set the username */
keys[1] = kSecAttrAccount;
values[1] = cf_account_name;
/* set the password */
keys[2] = kSecValueData;
values[2] = cf_password;
/* build our query */
query = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys,
(const void **)values, 3, NULL, NULL);
/* Finally add the item */
result = SecItemAdd(query, NULL);
/* Cleanup */
CFRelease(cf_account_name);
CFRelease(cf_password);
CFRelease(query);
/* Now create the GTask and set the result. */
task = g_task_new(provider, cancellable, callback, data);
if(result == errSecSuccess) {
g_task_return_boolean(task, TRUE);
} else {
purple_keychain_access_set_task_error_from_osstatus(task, result);
}
g_object_unref(task);
}
static gboolean
purple_keychain_access_write_password_finish(PurpleCredentialProvider *provider,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
return g_task_propagate_boolean(G_TASK(result), error);
}
static void
purple_keychain_access_clear_password_async(PurpleCredentialProvider *provider,
PurpleAccount *account,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer data)
{
PurpleContactInfo *info = NULL;
GTask *task = NULL;
const gchar *account_name = NULL;
CFStringRef keys[2];
CFTypeRef values[2];
CFDictionaryRef query;
CFStringRef cf_account_name;
OSStatus result;
g_return_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider));
g_return_if_fail(PURPLE_IS_ACCOUNT(account));
info = PURPLE_CONTACT_INFO(account);
account_name = purple_contact_info_get_username(info);
cf_account_name = CFStringCreateWithCString(kCFAllocatorDefault,
account_name,
kCFStringEncodingUTF8);
/* set our security class */
keys[0] = kSecClass;
values[0] = kSecClassGenericPassword;
/* set the username */
keys[1] = kSecAttrAccount;
values[1] = cf_account_name;
/* build our query */
query = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys,
(const void **)values, 2, NULL, NULL);
/* Finally add the item */
result = SecItemDelete(query);
/* Cleanup */
CFRelease(cf_account_name);
CFRelease(query);
/* Now create the GTask and set the result. */
task = g_task_new(provider, cancellable, callback, data);
if(result == errSecSuccess) {
g_task_return_boolean(task, TRUE);
} else {
purple_keychain_access_set_task_error_from_osstatus(task, result);
}
g_object_unref(task);
}
static gboolean
purple_keychain_access_clear_password_finish(PurpleCredentialProvider *provider,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
return g_task_propagate_boolean(G_TASK(result), error);
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static void
purple_keychain_access_init(PurpleKeychainAccess *keychain_access) {
}
static void
purple_keychain_access_class_init(PurpleKeychainAccessClass *klass) {
PurpleCredentialProviderClass *provider_class = NULL;
provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass);
provider_class->read_password_async =
purple_keychain_access_read_password_async;
provider_class->read_password_finish =
purple_keychain_access_read_password_finish;
provider_class->write_password_async =
purple_keychain_access_write_password_async;
provider_class->write_password_finish =
purple_keychain_access_write_password_finish;
provider_class->clear_password_async =
purple_keychain_access_clear_password_async;
provider_class->clear_password_finish =
purple_keychain_access_clear_password_finish;
}
static void
purple_keychain_access_class_finalize(PurpleKeychainAccessClass *klass) {
}
/******************************************************************************
* API
*****************************************************************************/
static PurpleCredentialProvider *
purple_keychain_access_new(void) {
return g_object_new(
PURPLE_TYPE_KEYCHAIN_ACCESS,
"id", KEYCHAIN_ACCESS_ID,
"name", KEYCHAIN_ACCESS_NAME,
"description", _(KEYCHAIN_ACCESS_DESCRIPTION),
NULL
);
}
/******************************************************************************
* Plugin Exports
*****************************************************************************/
static GPluginPluginInfo *
keychain_access_query(G_GNUC_UNUSED GError **error) {
const gchar * const authors[] = {
"Pidgin Developers <devel@pidgin.im>",
NULL
};
return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
"id", KEYCHAIN_ACCESS_ID,
"name", KEYCHAIN_ACCESS_NAME,
"version", DISPLAY_VERSION,
"category", N_("Credentials"),
"summary", KEYCHAIN_ACCESS_SUMMARY,
"description", KEYCHAIN_ACCESS_DESCRIPTION,
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
"flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
NULL
));
}
static gboolean
keychain_access_load(GPluginPlugin *plugin, GError **error) {
PurpleCredentialManager *manager = NULL;
gboolean ret = FALSE;
purple_keychain_access_register_type(G_TYPE_MODULE(plugin));
manager = purple_credential_manager_get_default();
instance = purple_keychain_access_new();
ret = purple_credential_manager_register(manager, instance, error);
if(!ret) {
g_clear_object(&instance);
}
return ret;
}
static gboolean
keychain_access_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
G_GNUC_UNUSED gboolean shutdown,
GError **error)
{
PurpleCredentialManager *manager = NULL;
gboolean ret = FALSE;
manager = purple_credential_manager_get_default();
ret = purple_credential_manager_unregister(manager, instance, error);
if(!ret) {
return ret;
}
g_clear_object(&instance);
return TRUE;
}
GPLUGIN_NATIVE_PLUGIN_DECLARE(keychain_access)