Wed, 02 Oct 2019 19:56:45 -0400
Don't pass literals to g_set_error / g_error_new.
/* purple * @file secretservice.c Secret Service password storage * @ingroup plugins * * 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 */ /* TODO * * This keyring now works (at the time of this writing), but there are * some inconvenient edge cases. When looking up passwords, libsecret * doesn't error if the keyring is locked. Therefore, it appears to * this plugin that there's no stored password. libpurple seems to * handle this as gracefully as possible, but it's still inconvenient. * This plugin could possibly be ported to use libsecret's "Complete API" * to resolve this if desired. */ #include "internal.h" #include "account.h" #include "debug.h" #include "keyring.h" #include "plugins.h" #include "version.h" #include <libsecret/secret.h> /* Translators: Secret Service is a service that runs on the user's computer. It is one option for where the user's passwords can be stored. It is a project name. It may not be appropriate to translate this string, but transliterating to your alphabet is reasonable. More info about the project can be found at https://wiki.gnome.org/Projects/Libsecret */ #define SECRETSERVICE_NAME N_("Secret Service") #define SECRETSERVICE_ID "keyring-libsecret" #define SECRETSERVICE_DOMAIN (g_quark_from_static_string(SECRETSERVICE_ID)) static PurpleKeyring *keyring_handler = NULL; static GCancellable *keyring_cancellable = NULL; static const SecretSchema purple_schema = { "im.pidgin.Purple", SECRET_SCHEMA_NONE, { {"user", SECRET_SCHEMA_ATTRIBUTE_STRING}, {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING}, {"NULL", 0} }, /* Reserved fields */ 0, 0, 0, 0, 0, 0, 0, 0 }; typedef struct _InfoStorage InfoStorage; struct _InfoStorage { PurpleAccount *account; gpointer cb; gpointer user_data; }; /***********************************************/ /* Keyring interface */ /***********************************************/ static void ss_g_error_to_keyring_error(GError **error, PurpleAccount *account) { GError *old_error; GError *new_error = NULL; g_return_if_fail(error != NULL); old_error = *error; if (g_error_matches(old_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { new_error = g_error_new_literal(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_CANCELLED, _("Operation cancelled.")); } else if (g_error_matches(old_error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND) || g_error_matches(old_error, G_DBUS_ERROR, G_DBUS_ERROR_IO_ERROR)) { purple_debug_info("keyring-libsecret", "Failed to communicate with Secret " "Service (account: %s (%s)).\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); new_error = g_error_new(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_BACKENDFAIL, "Failed to communicate with Secret " "Service (account: %s).", purple_account_get_username(account)); } else if (g_error_matches(old_error, SECRET_ERROR, SECRET_ERROR_IS_LOCKED)) { purple_debug_info("keyring-libsecret", "Secret Service is locked (account: %s (%s)).\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); new_error = g_error_new(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_ACCESSDENIED, "Secret Service is locked (account: %s).", purple_account_get_username(account)); } else { purple_debug_error("keyring-libsecret", "Unknown error (account: %s (%s), " "domain: %s, code: %d): %s.\n", purple_account_get_username(account), purple_account_get_protocol_id(account), g_quark_to_string(old_error->domain), old_error->code, old_error->message); new_error = g_error_new(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_BACKENDFAIL, "Unknown error (account: %s).", purple_account_get_username(account)); } g_clear_error(error); g_propagate_error(error, new_error); } static void ss_read_continue(GObject *object, GAsyncResult *result, gpointer data) { InfoStorage *storage = data; PurpleAccount *account = storage->account; PurpleKeyringReadCallback cb = storage->cb; char *password; GError *error = NULL; password = secret_password_lookup_finish(result, &error); if (error != NULL) { ss_g_error_to_keyring_error(&error, account); } else if (password == NULL) { error = g_error_new(PURPLE_KEYRING_ERROR, PURPLE_KEYRING_ERROR_NOPASSWORD, "No password found for account: %s", purple_account_get_username(account)); } if (cb != NULL) { cb(account, password, error, storage->user_data); } g_clear_error(&error); g_free(storage); } static void ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data) { InfoStorage *storage = g_new0(InfoStorage, 1); storage->account = account; storage->cb = cb; storage->user_data = data; secret_password_lookup(&purple_schema, keyring_cancellable, ss_read_continue, storage, "user", purple_account_get_username(account), "protocol", purple_account_get_protocol_id(account), NULL); } static void ss_save_continue(GError *error, gpointer data) { InfoStorage *storage = data; PurpleKeyringSaveCallback cb; PurpleAccount *account; account = storage->account; cb = storage->cb; if (error != NULL) { ss_g_error_to_keyring_error(&error, account); } else { purple_debug_info("keyring-libsecret", "Password for %s updated.\n", purple_account_get_username(account)); } if (cb != NULL) cb(account, error, storage->user_data); g_clear_error(&error); g_free(storage); } static void ss_store_continue(GObject *object, GAsyncResult *result, gpointer data) { GError *error = NULL; secret_password_store_finish(result, &error); ss_save_continue(error, data); } static void ss_clear_continue(GObject *object, GAsyncResult *result, gpointer data) { GError *error = NULL; secret_password_clear_finish(result, &error); ss_save_continue(error, data); } static void ss_save(PurpleAccount *account, const gchar *password, PurpleKeyringSaveCallback cb, gpointer data) { InfoStorage *storage = g_new0(InfoStorage, 1); storage->account = account; storage->cb = cb; storage->user_data = data; if (password != NULL && *password != '\0') { const char *username = purple_account_get_username(account); char *label; purple_debug_info("keyring-libsecret", "Updating password for account %s (%s).\n", username, purple_account_get_protocol_id(account)); label = g_strdup_printf(_("Pidgin IM password for account %s"), username); secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT, label, password, keyring_cancellable, ss_store_continue, storage, "user", username, "protocol", purple_account_get_protocol_id(account), NULL); g_free(label); } else { /* password == NULL, delete password. */ purple_debug_info("keyring-libsecret", "Forgetting password for account %s (%s).\n", purple_account_get_username(account), purple_account_get_protocol_id(account)); secret_password_clear(&purple_schema, keyring_cancellable, ss_clear_continue, storage, "user", purple_account_get_username(account), "protocol", purple_account_get_protocol_id(account), NULL); } } static void ss_cancel(void) { g_cancellable_cancel(keyring_cancellable); /* Swap out cancelled cancellable for new one for further operations */ g_clear_object(&keyring_cancellable); keyring_cancellable = g_cancellable_new(); } static void ss_close(void) { ss_cancel(); } static gboolean ss_init(GError **error) { keyring_cancellable = g_cancellable_new(); keyring_handler = purple_keyring_new(); purple_keyring_set_name(keyring_handler, _(SECRETSERVICE_NAME)); purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID); purple_keyring_set_read_password(keyring_handler, ss_read); purple_keyring_set_save_password(keyring_handler, ss_save); purple_keyring_set_cancel_requests(keyring_handler, ss_cancel); purple_keyring_set_close_keyring(keyring_handler, ss_close); purple_keyring_register(keyring_handler); return TRUE; } static void ss_uninit(void) { ss_close(); purple_keyring_unregister(keyring_handler); purple_keyring_free(keyring_handler); keyring_handler = NULL; g_clear_object(&keyring_cancellable); } /***********************************************/ /* Plugin interface */ /***********************************************/ static PurplePluginInfo * plugin_query(GError **error) { const gchar * const authors[] = { "Elliott Sales de Andrade (qulogic[at]pidgin.im)", NULL }; return purple_plugin_info_new( "id", SECRETSERVICE_ID, "name", SECRETSERVICE_NAME, "version", DISPLAY_VERSION, "category", N_("Keyring"), "summary", "Secret Service Plugin", "description", N_("This plugin will store passwords in Secret Service."), "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) { return ss_init(error); } static gboolean plugin_unload(PurplePlugin *plugin, GError **error) { if (purple_keyring_get_inuse() == keyring_handler) { g_set_error(error, SECRETSERVICE_DOMAIN, 0, "The keyring is currently " "in use."); return FALSE; } ss_uninit(); return TRUE; } PURPLE_PLUGIN_INIT(secret_service, plugin_query, plugin_load, plugin_unload);