| 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); |
|