libpurple/plugins/keyrings/secretservice.c

changeset 40734
c792b39da167
parent 40439
e9838d634d5e
child 40790
e92d5c060155
equal deleted inserted replaced
40733:1bad06536f81 40734:c792b39da167
1 /* purple 1 /*
2 * @file secretservice.c Secret Service password storage 2 * Purple - Internet Messaging Library
3 * @ingroup plugins 3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 * 4 *
5 * Purple is the legal property of its developers, whose names are too numerous 5 * This library is free software; you can redistribute it and/or
6 * to list here. Please refer to the COPYRIGHT file distributed with this 6 * modify it under the terms of the GNU Lesser General Public
7 * source distribution. 7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
8 * 9 *
9 * This program is free software; you can redistribute it and/or modify 10 * This library is distributed in the hope that it will be useful,
10 * it under the terms of the GNU General Public License as published by 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * the Free Software Foundation; either version 2 of the License, or 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * (at your option) any later version. 13 * Lesser General Public License for more details.
13 * 14 *
14 * This program is distributed in the hope that it will be useful, 15 * You should have received a copy of the GNU Lesser General Public
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
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
22 */ 17 */
23 18
24 /* TODO 19 /* TODO
25 * 20 *
26 * This keyring now works (at the time of this writing), but there are 21 * This keyring now works (at the time of this writing), but there are
41 /* Translators: Secret Service is a service that runs on the user's computer. 36 /* Translators: Secret Service is a service that runs on the user's computer.
42 It is one option for where the user's passwords can be stored. It is a 37 It is one option for where the user's passwords can be stored. It is a
43 project name. It may not be appropriate to translate this string, but 38 project name. It may not be appropriate to translate this string, but
44 transliterating to your alphabet is reasonable. More info about the 39 transliterating to your alphabet is reasonable. More info about the
45 project can be found at https://wiki.gnome.org/Projects/Libsecret */ 40 project can be found at https://wiki.gnome.org/Projects/Libsecret */
46 #define SECRETSERVICE_NAME N_("Secret Service") 41 #define SECRETSERVICE_NAME N_("Secret Service")
47 #define SECRETSERVICE_ID "keyring-libsecret" 42 #define SECRETSERVICE_ID "secret-service"
48 #define SECRETSERVICE_DOMAIN (g_quark_from_static_string(SECRETSERVICE_ID)) 43
49 44 /******************************************************************************
50 static PurpleKeyring *keyring_handler = NULL; 45 * Globals
51 static GCancellable *keyring_cancellable = NULL; 46 *****************************************************************************/
52 47 static PurpleCredentialProvider *instance = NULL;
53 static const SecretSchema purple_schema = { 48
54 "im.pidgin.Purple", SECRET_SCHEMA_NONE, 49 static const SecretSchema purple_secret_service_schema = {
50 "im.pidgin.Purple3", SECRET_SCHEMA_NONE,
55 { 51 {
56 {"user", SECRET_SCHEMA_ATTRIBUTE_STRING}, 52 {"user", SECRET_SCHEMA_ATTRIBUTE_STRING},
57 {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING}, 53 {"protocol", SECRET_SCHEMA_ATTRIBUTE_STRING},
58 {"NULL", 0} 54 {"NULL", 0}
59 }, 55 },
60 /* Reserved fields */ 56 /* Reserved fields */
61 0, 0, 0, 0, 0, 0, 0, 0 57 0, 0, 0, 0, 0, 0, 0, 0
62 }; 58 };
63 59
64 typedef struct _InfoStorage InfoStorage; 60 #define PURPLE_TYPE_SECRET_SERVICE (purple_secret_service_get_type())
65 61 G_DECLARE_FINAL_TYPE(PurpleSecretService, purple_secret_service,
66 struct _InfoStorage 62 PURPLE, SECRET_SERVICE, PurpleCredentialProvider)
67 { 63
68 PurpleAccount *account; 64 struct _PurpleSecretService {
69 gpointer cb; 65 PurpleCredentialProvider parent;
70 gpointer user_data;
71 }; 66 };
72 67
73 /***********************************************/ 68 G_DEFINE_DYNAMIC_TYPE(PurpleSecretService, purple_secret_service,
74 /* Keyring interface */ 69 PURPLE_TYPE_CREDENTIAL_PROVIDER)
75 /***********************************************/ 70
76 static void 71 /******************************************************************************
77 ss_g_error_to_keyring_error(GError **error, PurpleAccount *account) 72 * Callbacks
78 { 73 *****************************************************************************/
79 GError *old_error; 74 static void
80 GError *new_error = NULL; 75 purple_secret_service_read_password_callback(GObject *obj,
81 76 GAsyncResult *result,
82 g_return_if_fail(error != NULL); 77 gpointer data)
83 78 {
84 old_error = *error; 79 GTask *task = G_TASK(data);
85 80 GError *error = NULL;
86 if (g_error_matches(old_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { 81 gchar *password = NULL;
87 new_error = g_error_new_literal(PURPLE_KEYRING_ERROR, 82
88 PURPLE_KEYRING_ERROR_CANCELLED, 83 password = secret_password_lookup_finish(result, &error);
89 _("Operation cancelled.")); 84
90 } else if (g_error_matches(old_error, G_DBUS_ERROR, 85 if(error != NULL) {
91 G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND) || 86 g_task_return_error(task, error);
92 g_error_matches(old_error, G_DBUS_ERROR,
93 G_DBUS_ERROR_IO_ERROR)) {
94 purple_debug_info("keyring-libsecret",
95 "Failed to communicate with Secret "
96 "Service (account: %s (%s)).\n",
97 purple_account_get_username(account),
98 purple_account_get_protocol_id(account));
99 new_error = g_error_new(PURPLE_KEYRING_ERROR,
100 PURPLE_KEYRING_ERROR_BACKENDFAIL,
101 "Failed to communicate with Secret "
102 "Service (account: %s).",
103 purple_account_get_username(account));
104 } else if (g_error_matches(old_error, SECRET_ERROR,
105 SECRET_ERROR_IS_LOCKED)) {
106 purple_debug_info("keyring-libsecret",
107 "Secret Service is locked (account: %s (%s)).\n",
108 purple_account_get_username(account),
109 purple_account_get_protocol_id(account));
110 new_error = g_error_new(PURPLE_KEYRING_ERROR,
111 PURPLE_KEYRING_ERROR_ACCESSDENIED,
112 "Secret Service is locked (account: %s).",
113 purple_account_get_username(account));
114 } else { 87 } else {
115 purple_debug_error("keyring-libsecret", 88 g_task_return_pointer(task, password, g_free);
116 "Unknown error (account: %s (%s), "
117 "domain: %s, code: %d): %s.\n",
118 purple_account_get_username(account),
119 purple_account_get_protocol_id(account),
120 g_quark_to_string(old_error->domain),
121 old_error->code, old_error->message);
122 new_error = g_error_new(PURPLE_KEYRING_ERROR,
123 PURPLE_KEYRING_ERROR_BACKENDFAIL,
124 "Unknown error (account: %s).",
125 purple_account_get_username(account));
126 } 89 }
127 90
128 g_clear_error(error); 91 g_object_unref(G_OBJECT(task));
129 g_propagate_error(error, new_error); 92 }
130 } 93
131 94 static void
132 static void 95 purple_secret_service_write_password_callback(GObject *obj,
133 ss_read_continue(GObject *object, GAsyncResult *result, gpointer data) 96 GAsyncResult *result,
134 { 97 gpointer data)
135 InfoStorage *storage = data; 98 {
136 PurpleAccount *account = storage->account; 99 GTask *task = G_TASK(data);
137 PurpleKeyringReadCallback cb = storage->cb;
138 char *password;
139 GError *error = NULL; 100 GError *error = NULL;
140 101 gboolean ret = FALSE;
141 password = secret_password_lookup_finish(result, &error); 102
142 103 ret = secret_password_store_finish(result, &error);
143 if (error != NULL) { 104
144 ss_g_error_to_keyring_error(&error, account); 105 if(error != NULL) {
145 } else if (password == NULL) { 106 g_task_return_error(task, error);
146 error = g_error_new(PURPLE_KEYRING_ERROR, 107 } else {
147 PURPLE_KEYRING_ERROR_NOPASSWORD, 108 g_task_return_boolean(task, ret);
148 "No password found for account: %s",
149 purple_account_get_username(account));
150 } 109 }
151 110
152 if (cb != NULL) { 111 g_object_unref(G_OBJECT(task));
153 cb(account, password, error, storage->user_data); 112 }
113
114 static void
115 purple_secret_service_clear_password_callback(GObject *obj,
116 GAsyncResult *result,
117 gpointer data)
118 {
119 GTask *task = G_TASK(data);
120 GError *error = NULL;
121 gboolean ret = FALSE;
122
123 ret = secret_password_clear_finish(result, &error);
124
125 if(error != NULL) {
126 g_task_return_error(task, error);
127 } else {
128 g_task_return_boolean(task, ret);
154 } 129 }
155 130
156 g_clear_error(&error); 131 g_object_unref(G_OBJECT(task));
157 g_free(storage); 132 }
158 } 133
159 134 /******************************************************************************
160 static void 135 * PurpleCredentialProvider Implementation
161 ss_read(PurpleAccount *account, PurpleKeyringReadCallback cb, gpointer data) 136 *****************************************************************************/
162 { 137 static void
163 InfoStorage *storage = g_new0(InfoStorage, 1); 138 purple_secret_service_read_password_async(PurpleCredentialProvider *provider,
164 139 PurpleAccount *account,
165 storage->account = account; 140 GCancellable *cancellable,
166 storage->cb = cb; 141 GAsyncReadyCallback callback,
167 storage->user_data = data; 142 gpointer data)
168 143 {
169 secret_password_lookup(&purple_schema, keyring_cancellable, 144 GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
170 ss_read_continue, storage, 145
171 "user", purple_account_get_username(account), 146 secret_password_lookup(&purple_secret_service_schema, cancellable,
172 "protocol", purple_account_get_protocol_id(account), NULL); 147 purple_secret_service_read_password_callback, task,
173 } 148 "user", purple_account_get_username(account),
174 149 "protocol", purple_account_get_protocol_id(account),
175 static void 150 NULL);
176 ss_save_continue(GError *error, gpointer data) 151 }
177 { 152
178 InfoStorage *storage = data; 153 static gchar *
179 PurpleKeyringSaveCallback cb; 154 purple_secret_service_read_password_finish(PurpleCredentialProvider *provider,
180 PurpleAccount *account; 155 GAsyncResult *result,
181 156 GError **error)
182 account = storage->account; 157 {
183 cb = storage->cb; 158 g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
184 159 g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
185 if (error != NULL) { 160
186 ss_g_error_to_keyring_error(&error, account); 161 return g_task_propagate_pointer(G_TASK(result), error);
187 } else { 162 }
188 purple_debug_info("keyring-libsecret", "Password for %s updated.\n", 163
189 purple_account_get_username(account)); 164 static void
190 } 165 purple_secret_service_write_password_async(PurpleCredentialProvider *provider,
191 166 PurpleAccount *account,
192 if (cb != NULL) 167 const gchar *password,
193 cb(account, error, storage->user_data); 168 GCancellable *cancellable,
194 169 GAsyncReadyCallback callback,
195 g_clear_error(&error); 170 gpointer data)
196 g_free(storage); 171 {
197 } 172 GTask *task = NULL;
198 173 gchar *label = NULL;
199 static void 174 const gchar *username = NULL;
200 ss_store_continue(GObject *object, GAsyncResult *result, gpointer data) 175
201 { 176 task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
202 GError *error = NULL; 177 username = purple_account_get_username(account);
203 178
204 secret_password_store_finish(result, &error); 179 label = g_strdup_printf(_("libpurple password for account %s"), username);
205 180 secret_password_store(&purple_secret_service_schema,
206 ss_save_continue(error, data); 181 SECRET_COLLECTION_DEFAULT, label, password,
207 } 182 cancellable,
208 183 purple_secret_service_write_password_callback, task,
209 static void 184 "user", username,
210 ss_clear_continue(GObject *object, GAsyncResult *result, gpointer data) 185 "protocol", purple_account_get_protocol_id(account),
211 { 186 NULL);
212 GError *error = NULL; 187 g_free(label);
213
214 secret_password_clear_finish(result, &error);
215
216 ss_save_continue(error, data);
217 }
218
219 static void
220 ss_save(PurpleAccount *account,
221 const gchar *password,
222 PurpleKeyringSaveCallback cb,
223 gpointer data)
224 {
225 InfoStorage *storage = g_new0(InfoStorage, 1);
226
227 storage->account = account;
228 storage->cb = cb;
229 storage->user_data = data;
230
231 if (password != NULL && *password != '\0') {
232 const char *username = purple_account_get_username(account);
233 char *label;
234
235 purple_debug_info("keyring-libsecret",
236 "Updating password for account %s (%s).\n",
237 username, purple_account_get_protocol_id(account));
238
239 label = g_strdup_printf(_("Pidgin IM password for account %s"), username);
240 secret_password_store(&purple_schema, SECRET_COLLECTION_DEFAULT,
241 label, password, keyring_cancellable,
242 ss_store_continue, storage,
243 "user", username,
244 "protocol", purple_account_get_protocol_id(account),
245 NULL);
246 g_free(label);
247
248 } else { /* password == NULL, delete password. */
249 purple_debug_info("keyring-libsecret",
250 "Forgetting password for account %s (%s).\n",
251 purple_account_get_username(account),
252 purple_account_get_protocol_id(account));
253
254 secret_password_clear(&purple_schema, keyring_cancellable,
255 ss_clear_continue, storage,
256 "user", purple_account_get_username(account),
257 "protocol", purple_account_get_protocol_id(account),
258 NULL);
259 }
260 }
261
262 static void
263 ss_cancel(void)
264 {
265 g_cancellable_cancel(keyring_cancellable);
266
267 /* Swap out cancelled cancellable for new one for further operations */
268 g_clear_object(&keyring_cancellable);
269 keyring_cancellable = g_cancellable_new();
270 }
271
272 static void
273 ss_close(void)
274 {
275 ss_cancel();
276 } 188 }
277 189
278 static gboolean 190 static gboolean
279 ss_init(GError **error) 191 purple_secret_service_write_password_finish(PurpleCredentialProvider *provider,
280 { 192 GAsyncResult *result,
281 keyring_cancellable = g_cancellable_new(); 193 GError **error)
282 194 {
283 keyring_handler = purple_keyring_new(); 195 g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
284 196 g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
285 purple_keyring_set_name(keyring_handler, _(SECRETSERVICE_NAME)); 197
286 purple_keyring_set_id(keyring_handler, SECRETSERVICE_ID); 198 return g_task_propagate_boolean(G_TASK(result), error);
287 purple_keyring_set_read_password(keyring_handler, ss_read); 199 }
288 purple_keyring_set_save_password(keyring_handler, ss_save); 200
289 purple_keyring_set_cancel_requests(keyring_handler, ss_cancel); 201 static void
290 purple_keyring_set_close_keyring(keyring_handler, ss_close); 202 purple_secret_service_clear_password_async(PurpleCredentialProvider *provider,
291 203 PurpleAccount *account,
292 purple_keyring_register(keyring_handler); 204 GCancellable *cancellable,
293 205 GAsyncReadyCallback callback,
294 return TRUE; 206 gpointer data)
295 } 207 {
296 208 GTask *task = g_task_new(G_OBJECT(provider), cancellable, callback, data);
297 static void 209
298 ss_uninit(void) 210 secret_password_clear(&purple_secret_service_schema, cancellable,
299 { 211 purple_secret_service_clear_password_callback, task,
300 ss_close(); 212 "user", purple_account_get_username(account),
301 purple_keyring_unregister(keyring_handler); 213 "protocol", purple_account_get_protocol_id(account),
302 purple_keyring_free(keyring_handler); 214 NULL);
303 keyring_handler = NULL; 215 }
304 216
305 g_clear_object(&keyring_cancellable); 217 static gboolean
306 } 218 purple_secret_service_clear_password_finish(PurpleCredentialProvider *provider,
307 219 GAsyncResult *result,
308 /***********************************************/ 220 GError **error)
309 /* Plugin interface */ 221 {
310 /***********************************************/ 222 g_return_val_if_fail(PURPLE_IS_CREDENTIAL_PROVIDER(provider), FALSE);
311 223 g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
312 static PurplePluginInfo * 224
313 plugin_query(GError **error) 225 return g_task_propagate_boolean(G_TASK(result), error);
314 { 226 }
227
228 /******************************************************************************
229 * GObject Implementation
230 *****************************************************************************/
231 static void
232 purple_secret_service_init(PurpleSecretService *ss) {
233 }
234
235 static void
236 purple_secret_service_class_init(PurpleSecretServiceClass *klass) {
237 PurpleCredentialProviderClass *provider_class = NULL;
238
239 provider_class = PURPLE_CREDENTIAL_PROVIDER_CLASS(klass);
240 provider_class->read_password_async =
241 purple_secret_service_read_password_async;
242 provider_class->read_password_finish =
243 purple_secret_service_read_password_finish;
244 provider_class->write_password_async =
245 purple_secret_service_write_password_async;
246 provider_class->write_password_finish =
247 purple_secret_service_write_password_finish;
248 provider_class->clear_password_async =
249 purple_secret_service_clear_password_async;
250 provider_class->clear_password_finish =
251 purple_secret_service_clear_password_finish;
252 }
253
254 static void
255 purple_secret_service_class_finalize(PurpleSecretServiceClass *klass) {
256 }
257
258 /******************************************************************************
259 * API
260 *****************************************************************************/
261 static PurpleCredentialProvider *
262 purple_secret_service_new(void) {
263 return PURPLE_CREDENTIAL_PROVIDER(g_object_new(
264 PURPLE_TYPE_SECRET_SERVICE,
265 "id", SECRETSERVICE_ID,
266 "name", _(SECRETSERVICE_NAME),
267 NULL
268 ));
269 }
270
271 /******************************************************************************
272 * Plugin Exports
273 *****************************************************************************/
274 G_MODULE_EXPORT GPluginPluginInfo *gplugin_query(GPluginPlugin *plugin, GError **error);
275 G_MODULE_EXPORT gboolean gplugin_load(GPluginPlugin *plugin, GError **error);
276 G_MODULE_EXPORT gboolean gplugin_unload(GPluginPlugin *plugin, GError **error);
277
278 G_MODULE_EXPORT GPluginPluginInfo *
279 gplugin_query(GPluginPlugin *plugin, GError **error) {
315 const gchar * const authors[] = { 280 const gchar * const authors[] = {
316 "Elliott Sales de Andrade (qulogic[at]pidgin.im)", 281 "Elliott Sales de Andrade (qulogic[at]pidgin.im)",
317 NULL 282 NULL
318 }; 283 };
319 284
320 return purple_plugin_info_new( 285 return GPLUGIN_PLUGIN_INFO(purple_plugin_info_new(
321 "id", SECRETSERVICE_ID, 286 "id", SECRETSERVICE_ID,
322 "name", SECRETSERVICE_NAME, 287 "name", SECRETSERVICE_NAME,
323 "version", DISPLAY_VERSION, 288 "version", DISPLAY_VERSION,
324 "category", N_("Keyring"), 289 "category", N_("Keyring"),
325 "summary", "Secret Service Plugin", 290 "summary", "Secret Service Plugin",
326 "description", N_("This plugin will store passwords in Secret Service."), 291 "description", N_("This plugin will store passwords in Secret Service."),
327 "authors", authors, 292 "authors", authors,
328 "website", PURPLE_WEBSITE, 293 "website", PURPLE_WEBSITE,
329 "abi-version", PURPLE_ABI_VERSION, 294 "abi-version", PURPLE_ABI_VERSION,
330 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL, 295 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
296 PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
331 NULL 297 NULL
332 ); 298 ));
333 } 299 }
334 300
335 static gboolean 301 G_MODULE_EXPORT gboolean
336 plugin_load(PurplePlugin *plugin, GError **error) 302 gplugin_load(GPluginPlugin *plugin, GError **error) {
337 { 303 PurpleCredentialManager *manager = NULL;
338 return ss_init(error); 304
339 } 305 purple_secret_service_register_type(G_TYPE_MODULE(plugin));
340 306
341 static gboolean 307 manager = purple_credential_manager_get_default();
342 plugin_unload(PurplePlugin *plugin, GError **error) 308
343 { 309 instance = purple_secret_service_new();
344 if (purple_keyring_get_inuse() == keyring_handler) { 310
345 g_set_error(error, SECRETSERVICE_DOMAIN, 0, "The keyring is currently " 311 return purple_credential_manager_register_provider(manager, instance,
346 "in use."); 312 error);
347 return FALSE; 313 }
314
315 G_MODULE_EXPORT gboolean
316 gplugin_unload(GPluginPlugin *plugin, GError **error) {
317 PurpleCredentialManager *manager = NULL;
318 gboolean ret = FALSE;
319
320 manager = purple_credential_manager_get_default();
321 ret = purple_credential_manager_unregister_provider(manager, instance,
322 error);
323 if(!ret) {
324 return ret;
348 } 325 }
349 326
350 ss_uninit(); 327 g_clear_object(&instance);
351 328
352 return TRUE; 329 return TRUE;
353 } 330 }
354
355 PURPLE_PLUGIN_INIT(secret_service, plugin_query, plugin_load, plugin_unload);

mercurial