Merged pidgin/main into default
[pidgin-git.git] / libpurple / plugins / keyrings / secretservice.c
blobab0b5fc672b54e753b01521ebba0acff226dfe7f
1 /* purple
2 * @file secretservice.c Secret Service password storage
3 * @ingroup plugins
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program ; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 /* TODO
26 * This keyring now works (at the time of this writing), but there are
27 * some inconvenient edge cases. When looking up passwords, libsecret
28 * doesn't error if the keyring is locked. Therefore, it appears to
29 * this plugin that there's no stored password. libpurple seems to
30 * handle this as gracefully as possible, but it's still inconvenient.
31 * This plugin could possibly be ported to use libsecret's "Complete API"
32 * to resolve this if desired.
35 #include "internal.h"
36 #include "account.h"
37 #include "debug.h"
38 #include "keyring.h"
39 #include "plugins.h"
40 #include "version.h"
42 #include <libsecret/secret.h>
44 /* Translators: Secret Service is a service that runs on the user's computer.
45 It is one option for where the user's passwords can be stored. It is a
46 project name. It may not be appropriate to translate this string, but
47 transliterating to your alphabet is reasonable. More info about the
48 project can be found at https://wiki.gnome.org/Projects/Libsecret */
49 #define SECRETSERVICE_NAME N_("Secret Service")
50 #define SECRETSERVICE_ID "keyring-libsecret"
51 #define SECRETSERVICE_DOMAIN (g_quark_from_static_string(SECRETSERVICE_ID))
53 static PurpleKeyring *keyring_handler = NULL;
54 static GCancellable *keyring_cancellable = NULL;
56 static const SecretSchema purple_schema = {
57 "im.pidgin.Purple", SECRET_SCHEMA_NONE,
59 {"user", SECRET_SCHEMA_ATTRIBUTE_STRING},
60 {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING},
61 {"NULL", 0}
63 /* Reserved fields */
64 0, 0, 0, 0, 0, 0, 0, 0
67 typedef struct _InfoStorage InfoStorage;
69 struct _InfoStorage
71 PurpleAccount *account;
72 gpointer cb;
73 gpointer user_data;
76 /***********************************************/
77 /* Keyring interface */
78 /***********************************************/
79 static void
80 ss_g_error_to_keyring_error(GError **error, PurpleAccount *account)
82 GError *old_error;
83 GError *new_error = NULL;
85 g_return_if_fail(error != NULL);
87 old_error = *error;
89 if (g_error_matches(old_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
90 new_error = g_error_new(PURPLE_KEYRING_ERROR,
91 PURPLE_KEYRING_ERROR_CANCELLED,
92 _("Operation cancelled."));
93 } else if (g_error_matches(old_error, G_DBUS_ERROR,
94 G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND) ||
95 g_error_matches(old_error, G_DBUS_ERROR,
96 G_DBUS_ERROR_IO_ERROR)) {
97 purple_debug_info("keyring-libsecret",
98 "Failed to communicate with Secret "
99 "Service (account: %s (%s)).\n",
100 purple_account_get_username(account),
101 purple_account_get_protocol_id(account));
102 new_error = g_error_new(PURPLE_KEYRING_ERROR,
103 PURPLE_KEYRING_ERROR_BACKENDFAIL,
104 "Failed to communicate with Secret "
105 "Service (account: %s).",
106 purple_account_get_username(account));
107 } else if (g_error_matches(old_error, SECRET_ERROR,
108 SECRET_ERROR_IS_LOCKED)) {
109 purple_debug_info("keyring-libsecret",
110 "Secret Service is locked (account: %s (%s)).\n",
111 purple_account_get_username(account),
112 purple_account_get_protocol_id(account));
113 new_error = g_error_new(PURPLE_KEYRING_ERROR,
114 PURPLE_KEYRING_ERROR_ACCESSDENIED,
115 "Secret Service is locked (account: %s).",
116 purple_account_get_username(account));
117 } else {
118 purple_debug_error("keyring-libsecret",
119 "Unknown error (account: %s (%s), "
120 "domain: %s, code: %d): %s.\n",
121 purple_account_get_username(account),
122 purple_account_get_protocol_id(account),
123 g_quark_to_string(old_error->domain),
124 old_error->code, old_error->message);
125 new_error = g_error_new(PURPLE_KEYRING_ERROR,
126 PURPLE_KEYRING_ERROR_BACKENDFAIL,
127 "Unknown error (account: %s).",
128 purple_account_get_username(account));
131 g_clear_error(error);
132 g_propagate_error(error, new_error);
135 static void
136 ss_read_continue(GObject *object, GAsyncResult *result, gpointer data)
138 InfoStorage *storage = data;
139 PurpleAccount *account = storage->account;
140 PurpleKeyringReadCallback cb = storage->cb;
141 char *password;
142 GError *error = NULL;
144 password = secret_password_lookup_finish(result, &error);
146 if (error != NULL) {
147 ss_g_error_to_keyring_error(&error, account);
148 } else if (password == NULL) {
149 error = g_error_new(PURPLE_KEYRING_ERROR,
150 PURPLE_KEYRING_ERROR_NOPASSWORD,
151 "No password found for account: %s",
152 purple_account_get_username(account));
155 if (cb != NULL) {
156 cb(account, password, error, storage->user_data);
159 g_clear_error(&error);
160 g_free(storage);
163 static void
164 ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data)
166 InfoStorage *storage = g_new0(InfoStorage, 1);
168 storage->account = account;
169 storage->cb = cb;
170 storage->user_data = data;
172 secret_password_lookup(&purple_schema, keyring_cancellable,
173 ss_read_continue, storage,
174 "user", purple_account_get_username(account),
175 "protocol", purple_account_get_protocol_id(account), NULL);
178 static void
179 ss_save_continue(GError *error, gpointer data)
181 InfoStorage *storage = data;
182 PurpleKeyringSaveCallback cb;
183 PurpleAccount *account;
185 account = storage->account;
186 cb = storage->cb;
188 if (error != NULL) {
189 ss_g_error_to_keyring_error(&error, account);
190 } else {
191 purple_debug_info("keyring-libsecret", "Password for %s updated.\n",
192 purple_account_get_username(account));
195 if (cb != NULL)
196 cb(account, error, storage->user_data);
198 g_clear_error(&error);
199 g_free(storage);
202 static void
203 ss_store_continue(GObject *object, GAsyncResult *result, gpointer data)
205 GError *error = NULL;
207 secret_password_store_finish(result, &error);
209 ss_save_continue(error, data);
212 static void
213 ss_clear_continue(GObject *object, GAsyncResult *result, gpointer data)
215 GError *error = NULL;
217 secret_password_clear_finish(result, &error);
219 ss_save_continue(error, data);
222 static void
223 ss_save(PurpleAccount *account,
224 const gchar *password,
225 PurpleKeyringSaveCallback cb,
226 gpointer data)
228 InfoStorage *storage = g_new0(InfoStorage, 1);
230 storage->account = account;
231 storage->cb = cb;
232 storage->user_data = data;
234 if (password != NULL && *password != '\0') {
235 const char *username = purple_account_get_username(account);
236 char *label;
238 purple_debug_info("keyring-libsecret",
239 "Updating password for account %s (%s).\n",
240 username, purple_account_get_protocol_id(account));
242 label = g_strdup_printf(_("Pidgin IM password for account %s"), username);
243 secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT,
244 label, password, keyring_cancellable,
245 ss_store_continue, storage,
246 "user", username,
247 "protocol", purple_account_get_protocol_id(account),
248 NULL);
249 g_free(label);
251 } else { /* password == NULL, delete password. */
252 purple_debug_info("keyring-libsecret",
253 "Forgetting password for account %s (%s).\n",
254 purple_account_get_username(account),
255 purple_account_get_protocol_id(account));
257 secret_password_clear(&purple_schema, keyring_cancellable,
258 ss_clear_continue, storage,
259 "user", purple_account_get_username(account),
260 "protocol", purple_account_get_protocol_id(account),
261 NULL);
265 static void
266 ss_cancel(void)
268 g_cancellable_cancel(keyring_cancellable);
270 /* Swap out cancelled cancellable for new one for further operations */
271 g_clear_object(&keyring_cancellable);
272 keyring_cancellable = g_cancellable_new();
275 static void
276 ss_close(void)
278 ss_cancel();
281 static gboolean
282 ss_init(GError **error)
284 keyring_cancellable = g_cancellable_new();
286 keyring_handler = purple_keyring_new();
288 purple_keyring_set_name(keyring_handler, _(SECRETSERVICE_NAME));
289 purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID);
290 purple_keyring_set_read_password(keyring_handler, ss_read);
291 purple_keyring_set_save_password(keyring_handler, ss_save);
292 purple_keyring_set_cancel_requests(keyring_handler, ss_cancel);
293 purple_keyring_set_close_keyring(keyring_handler, ss_close);
295 purple_keyring_register(keyring_handler);
297 return TRUE;
300 static void
301 ss_uninit(void)
303 ss_close();
304 purple_keyring_unregister(keyring_handler);
305 purple_keyring_free(keyring_handler);
306 keyring_handler = NULL;
308 g_clear_object(&keyring_cancellable);
311 /***********************************************/
312 /* Plugin interface */
313 /***********************************************/
315 static PurplePluginInfo *
316 plugin_query(GError **error)
318 const gchar * const authors[] = {
319 "Elliott Sales de Andrade (qulogic[at]pidgin.im)",
320 NULL
323 return purple_plugin_info_new(
324 "id", SECRETSERVICE_ID,
325 "name", SECRETSERVICE_NAME,
326 "version", DISPLAY_VERSION,
327 "category", N_("Keyring"),
328 "summary", "Secret Service Plugin",
329 "description", N_("This plugin will store passwords in Secret Service."),
330 "authors", authors,
331 "website", PURPLE_WEBSITE,
332 "abi-version", PURPLE_ABI_VERSION,
333 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
334 NULL
338 static gboolean
339 plugin_load(PurplePlugin *plugin, GError **error)
341 return ss_init(error);
344 static gboolean
345 plugin_unload(PurplePlugin *plugin, GError **error)
347 if (purple_keyring_get_inuse() == keyring_handler) {
348 g_set_error(error, SECRETSERVICE_DOMAIN, 0, "The keyring is currently "
349 "in use.");
350 return FALSE;
353 ss_uninit();
355 return TRUE;
358 PURPLE_PLUGIN_INIT(secret_service, plugin_query, plugin_load, plugin_unload);