Thu, 27 Jun 2024 00:48:00 -0500
Update libpurple to use get id and username directly on PurpleAccount
This is part of making PurpleAccount have a PurpleContactInfo instead of being
one.
Testing Done:
Ran the turtles and opened Pidgin 3 with some accounts without issue.
Reviewed at https://reviews.imfreedom.org/r/3274/
/* * 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 library; if not, see <https://www.gnu.org/licenses/>. */ #include <glib.h> #include <glib/gi18n-lib.h> #include <purple.h> #include <QCoreApplication> #include <kwallet.h> #include "purplekwallet.h" /****************************************************************************** * Globals *****************************************************************************/ static QCoreApplication *coreApp = NULL; static PurpleCredentialProvider *instance = NULL; static char *argv[] = { (char*)"purplekwallet", }; static int argc = G_N_ELEMENTS(argv); #define PURPLE_KWALLET_DOMAIN (g_quark_from_static_string("purple-kwallet")) #define PURPLE_KWALLET_WALLET_NAME (KWallet::Wallet::NetworkWallet()) struct _PurpleKWalletProvider { PurpleCredentialProvider parent; PurpleKWalletPlugin::Engine *engine; }; G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleKWalletProvider, purple_kwallet_provider, PURPLE_TYPE_CREDENTIAL_PROVIDER, G_TYPE_FLAG_FINAL, {}) /****************************************************************************** * Helpers *****************************************************************************/ static QString purple_kwallet_get_ui_name(void) { PurpleUi *ui = NULL; QString ui_name = NULL; ui = purple_core_get_ui(); if(PURPLE_IS_UI(ui)) { ui_name = purple_ui_get_name(ui); } if(ui_name.isEmpty()) { ui_name = "libpurple"; } return ui_name; } static QString purple_kwallet_provider_account_key(PurpleAccount *account) { return QString(purple_account_get_protocol_id(account)) + ":" + purple_account_get_username(account); } static void kwallet_message_handler(QtMsgType type, const QMessageLogContext &, const QString &msg) { GLogLevelFlags log_level; switch (type) { case QtDebugMsg: log_level = G_LOG_LEVEL_DEBUG; break; case QtInfoMsg: log_level = G_LOG_LEVEL_INFO; break; case QtWarningMsg: log_level = G_LOG_LEVEL_WARNING; break; case QtCriticalMsg: log_level = G_LOG_LEVEL_CRITICAL; break; case QtFatalMsg: // don't create a fatal aka G_LOG_LEVEL_ERROR message, because // this is only a plugin log_level = G_LOG_LEVEL_CRITICAL; break; } g_log(G_LOG_DOMAIN, log_level, "%s", msg.toUtf8().constData()); } /****************************************************************************** * Request Implementation *****************************************************************************/ PurpleKWalletPlugin::Request::Request(const QString &key, GTask *task) { this->key = key; this->task = g_object_ref(task); } PurpleKWalletPlugin::Request::~Request(void) { g_clear_object(&this->task); } /****************************************************************************** * ReadRequest Implementation *****************************************************************************/ PurpleKWalletPlugin::ReadRequest::ReadRequest(const QString &key, GTask *task) : PurpleKWalletPlugin::Request(key, task) { } void PurpleKWalletPlugin::ReadRequest::execute(KWallet::Wallet *wallet) { QString password; int result = 0; bool missing; missing = KWallet::Wallet::keyDoesNotExist(PURPLE_KWALLET_WALLET_NAME, purple_kwallet_get_ui_name(), key); if(missing) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, 0, "no password stored"); g_clear_object(&this->task); return; } result = wallet->readPassword(this->key, password); if(result != 0) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, result, _("failed to read password, kwallet responded " "with error code %d"), result); } else { gchar *c_password = g_strdup(password.toUtf8().constData()); g_task_return_pointer(this->task, c_password, g_free); } g_clear_object(&this->task); } void PurpleKWalletPlugin::ReadRequest::cancel(const QString &reason) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, 0, _("failed to read password: %s"), reason.toUtf8().constData()); g_clear_object(&this->task); } /****************************************************************************** * WriteRequest Implementation *****************************************************************************/ PurpleKWalletPlugin::WriteRequest::WriteRequest(const QString &key, GTask *task, const QString &password) : PurpleKWalletPlugin::Request(key, task) { this->password = password; } void PurpleKWalletPlugin::WriteRequest::execute(KWallet::Wallet *wallet) { int result; result = wallet->writePassword(this->key, this->password); if(result != 0) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, result, _("failed to write password, kwallet " "responded with error code %d"), result); } else { g_task_return_boolean(this->task, TRUE); } g_clear_object(&this->task); } void PurpleKWalletPlugin::WriteRequest::cancel(const QString &reason) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, 0, _("failed to write password: %s"), reason.toUtf8().constData()); g_clear_object(&this->task); } /****************************************************************************** * ClearRequest Implementation *****************************************************************************/ PurpleKWalletPlugin::ClearRequest::ClearRequest(const QString &key, GTask *task) : PurpleKWalletPlugin::Request(key, task) { } void PurpleKWalletPlugin::ClearRequest::execute(KWallet::Wallet *wallet) { int result; result = wallet->removeEntry(this->key); if(result != 0) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, result, _("failed to clear password, kwallet " "responded with error code %d"), result); } else { g_task_return_boolean(this->task, TRUE); } g_clear_object(&this->task); } void PurpleKWalletPlugin::ClearRequest::cancel(const QString &reason) { g_task_return_new_error(this->task, PURPLE_KWALLET_DOMAIN, 0, _("failed to clear password: %s"), reason.toUtf8().constData()); g_clear_object(&this->task); } /****************************************************************************** * Engine Implementation *****************************************************************************/ PurpleKWalletPlugin::Engine::Engine(void) { this->queue = QQueue<PurpleKWalletPlugin::Request *>(); this->wallet = NULL; this->connected = false; this->failed = false; this->externallyClosed = false; } PurpleKWalletPlugin::Engine::~Engine(void) { this->close(); } void PurpleKWalletPlugin::Engine::open(void) { g_info("attempting to open wallet"); if(this->connected) { g_info("wallet already opened"); return; } // Reset our externallyClosed and failed states. this->externallyClosed = false; this->failed = false; // No need to check this pointer as an async open always returns non-null. this->wallet = KWallet::Wallet::openWallet(PURPLE_KWALLET_WALLET_NAME, 0, KWallet::Wallet::Asynchronous); this->failed |= !QObject::connect(this->wallet, SIGNAL(walletOpened(bool)), SLOT(opened(bool))); this->failed |= !QObject::connect(this->wallet, SIGNAL(walletClosed(void)), SLOT(closed())); if(this->failed) { g_critical("Failed to connect KWallet signals"); } } void PurpleKWalletPlugin::Engine::close(void) { while(!this->queue.isEmpty()) { PurpleKWalletPlugin::Request *request = this->queue.dequeue(); request->cancel("wallet is closing"); delete request; } if(this->wallet != NULL) { delete this->wallet; this->wallet = NULL; } this->connected = false; this->failed = false; } void PurpleKWalletPlugin::Engine::enqueue(PurpleKWalletPlugin::Request *request) { this->queue.enqueue(request); processQueue(); } void PurpleKWalletPlugin::Engine::opened(bool opened) { QString folder_name; if(!opened) { g_critical("failed to open wallet"); delete this->wallet; this->wallet = NULL; this->connected = false; this->failed = true; return; } // Handle the case where the wallet opened signal connected, but the wallet // closed signal failed to connect. if(this->failed) { g_critical("wallet opened, but failed to connect the wallet closed signal"); return; } this->connected = true; // setup our folder folder_name = purple_kwallet_get_ui_name(); if(!this->wallet->hasFolder(folder_name)) { if(!this->wallet->createFolder(folder_name)) { g_critical("failed to create folder %s in wallet.", folder_name.toUtf8().constData()); this->failed = true; } } if(!this->failed && !this->wallet->setFolder(folder_name)) { g_critical("failed to set folder to %s", folder_name.toUtf8().constData()); this->failed = true; } g_info("successfully opened the wallet"); processQueue(); } void PurpleKWalletPlugin::Engine::closed(void) { g_info("the wallet was closed externally"); this->externallyClosed = true; this->close(); } void PurpleKWalletPlugin::Engine::processQueue() { if(this->externallyClosed && this->queue.isEmpty() == false) { this->open(); } else if(this->connected || this->failed) { while(!this->queue.isEmpty()) { PurpleKWalletPlugin::Request *request = this->queue.dequeue(); if(this->failed) { request->cancel(_("failed to open kwallet")); } else { request->execute(this->wallet); } delete request; } } } /****************************************************************************** * PurpleCredentialProvider Implementation *****************************************************************************/ static void purple_kwallet_provider_activate(PurpleCredentialProvider *provider) { PurpleKWalletProvider *kwallet_provider = NULL; kwallet_provider = PURPLE_KWALLET_PROVIDER(provider); kwallet_provider->engine->open(); } static void purple_kwallet_provider_deactivate(PurpleCredentialProvider *provider) { PurpleKWalletProvider *kwallet_provider = NULL; kwallet_provider = PURPLE_KWALLET_PROVIDER(provider); kwallet_provider->engine->close(); } static void purple_kwallet_read_password_async(PurpleCredentialProvider *provider, PurpleAccount *account, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) { PurpleKWalletProvider *kwallet_provider = NULL; PurpleKWalletPlugin::ReadRequest *request = NULL; GTask *task = NULL; QString key; key = purple_kwallet_provider_account_key(account); task = g_task_new(provider, cancellable, callback, data); /* We manually set the task name otherwise the (gpointer) cast ends up in * the name. */ g_task_set_static_name(task, "purple_kwallet_read_password_async"); g_task_set_source_tag(task, (gpointer)purple_kwallet_read_password_async); request = new PurpleKWalletPlugin::ReadRequest(key, task); kwallet_provider = PURPLE_KWALLET_PROVIDER(provider); kwallet_provider->engine->enqueue(request); g_clear_object(&task); } static gchar * purple_kwallet_read_password_finish(G_GNUC_UNUSED PurpleCredentialProvider *provider, GAsyncResult *result, GError **error) { return (gchar *)g_task_propagate_pointer(G_TASK(result), error); } static void purple_kwallet_write_password_async(PurpleCredentialProvider *provider, PurpleAccount *account, const gchar *password, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) { PurpleKWalletProvider *kwallet_provider = NULL; PurpleKWalletPlugin::WriteRequest *request = NULL; GTask *task = NULL; QString key; task = g_task_new(provider, cancellable, callback, data); /* We manually set the task name otherwise the (gpointer) cast ends up in * the name. */ g_task_set_static_name(task, "purple_kwallet_write_password_async"); g_task_set_source_tag(task, (gpointer)purple_kwallet_write_password_async); key = purple_kwallet_provider_account_key(account); request = new PurpleKWalletPlugin::WriteRequest(key, task, password); kwallet_provider = PURPLE_KWALLET_PROVIDER(provider); kwallet_provider->engine->enqueue(request); g_clear_object(&task); } static gboolean purple_kwallet_write_password_finish(G_GNUC_UNUSED PurpleCredentialProvider *provider, GAsyncResult *result, GError **error) { return g_task_propagate_boolean(G_TASK(result), error); } static void purple_kwallet_clear_password_async(PurpleCredentialProvider *provider, PurpleAccount *account, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) { PurpleKWalletProvider *kwallet_provider = NULL; PurpleKWalletPlugin::ClearRequest *request = NULL; GTask *task = NULL; QString key; task = g_task_new(provider, cancellable, callback, data); /* We manually set the task name otherwise the (gpointer) cast ends up in * the name. */ g_task_set_static_name(task, "purple_kwallet_clear_password_async"); g_task_set_source_tag(task, (gpointer)purple_kwallet_clear_password_async); key = purple_kwallet_provider_account_key(account); request = new PurpleKWalletPlugin::ClearRequest(key, task); kwallet_provider = PURPLE_KWALLET_PROVIDER(provider); kwallet_provider->engine->enqueue(request); g_clear_object(&task); } static gboolean purple_kwallet_clear_password_finish(G_GNUC_UNUSED PurpleCredentialProvider *provider, GAsyncResult *result, GError **error) { return g_task_propagate_boolean(G_TASK(result), error); } /****************************************************************************** * GObject Implementation *****************************************************************************/ static void purple_kwallet_provider_dispose(GObject *obj) { PurpleKWalletProvider *provider = PURPLE_KWALLET_PROVIDER(obj); if(provider->engine != NULL) { provider->engine->close(); } G_OBJECT_CLASS(purple_kwallet_provider_parent_class)->dispose(obj); } static void purple_kwallet_provider_finalize(GObject *obj) { PurpleKWalletProvider *provider = PURPLE_KWALLET_PROVIDER(obj); if(provider->engine != NULL) { delete provider->engine; provider->engine = NULL; } G_OBJECT_CLASS(purple_kwallet_provider_parent_class)->finalize(obj); } static void purple_kwallet_provider_init(PurpleKWalletProvider *provider) { provider->engine = new PurpleKWalletPlugin::Engine(); } static void purple_kwallet_provider_class_init(PurpleKWalletProviderClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); PurpleCredentialProviderClass *provider_class = NULL; provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass); obj_class->dispose = purple_kwallet_provider_dispose; obj_class->finalize = purple_kwallet_provider_finalize; provider_class->activate = purple_kwallet_provider_activate; provider_class->deactivate = purple_kwallet_provider_deactivate; provider_class->read_password_async = purple_kwallet_read_password_async; provider_class->read_password_finish = purple_kwallet_read_password_finish; provider_class->write_password_async = purple_kwallet_write_password_async; provider_class->write_password_finish = purple_kwallet_write_password_finish; provider_class->clear_password_async = purple_kwallet_clear_password_async; provider_class->clear_password_finish = purple_kwallet_clear_password_finish; } static void purple_kwallet_provider_class_finalize(G_GNUC_UNUSED PurpleKWalletProviderClass *klass) { } /****************************************************************************** * API *****************************************************************************/ static PurpleCredentialProvider * purple_kwallet_provider_new(void) { return PURPLE_CREDENTIAL_PROVIDER(g_object_new( PURPLE_KWALLET_TYPE_PROVIDER, "id", "kwallet", "name", _("KWallet"), "description", _("A credentials management application for the KDE " "Software Compilation desktop environment"), NULL )); } /****************************************************************************** * Plugin Exports *****************************************************************************/ static GPluginPluginInfo * kwallet_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", "keyring-kwallet", "name", N_("KWallet"), "version", DISPLAY_VERSION, "category", N_("Keyring"), "summary", "KWallet Keyring Plugin", "description", N_("This plugin will store passwords in KWallet."), "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 kwallet_load(GPluginPlugin *plugin, GError **error) { PurpleCredentialManager *manager = NULL; purple_kwallet_provider_register_type(G_TYPE_MODULE(plugin)); if(coreApp == NULL) { qInstallMessageHandler(kwallet_message_handler); coreApp = new QCoreApplication(argc, argv); coreApp->setApplicationName(purple_kwallet_get_ui_name()); } if(!KWallet::Wallet::isEnabled()) { g_set_error(error, PURPLE_KWALLET_DOMAIN, 0, "KWallet service is disabled."); return FALSE; } manager = purple_credential_manager_get_default(); instance = purple_kwallet_provider_new(); return purple_credential_manager_register(manager, instance, error); } static gboolean kwallet_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; } if(coreApp != NULL) { delete coreApp; coreApp = NULL; } g_clear_object(&instance); return TRUE; } G_BEGIN_DECLS GPLUGIN_NATIVE_PLUGIN_DECLARE(kwallet) G_END_DECLS