keystore/file: add a crypt submodule
[vlc.git] / modules / keystore / secret.c
1 /*****************************************************************************
2  * secret.c: libsecret keystore module
3  *****************************************************************************
4  * Copyright © 2015-2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <vlc_common.h>
26 #include <vlc_plugin.h>
27 #include <vlc_keystore.h>
28 #include <vlc_interrupt.h>
29
30 #include <assert.h>
31
32 #include <libsecret/secret.h>
33 #include <gio/gdbusnamewatching.h>
34
35 static int Open(vlc_object_t *);
36 static void Close(vlc_object_t *);
37
38 vlc_module_begin()
39     set_shortname(N_("libsecret keystore"))
40     set_description(N_("secrets are stored via libsecret"))
41     set_category(CAT_ADVANCED)
42     set_subcategory(SUBCAT_ADVANCED_MISC)
43     set_capability("keystore", 100)
44     set_callbacks(Open, Close)
45     /* Since we can't destroy gdbus_shared_thread_func */
46     cannot_unload_broken_library()
47 vlc_module_end ()
48
49 static const char *const ppsz_keys[] = {
50     "protocol",
51     "user",
52     "server",
53     "path",
54     "port",
55     "realm",
56     "authtype",
57 };
58 static_assert(sizeof(ppsz_keys)/sizeof(*ppsz_keys) == KEY_MAX, "key mismatch");
59
60 static int
61 str2key(const char *psz_key)
62 {
63     for (unsigned int i = 0; i < KEY_MAX; ++i)
64     {
65         if (strcmp(ppsz_keys[i], psz_key) == 0)
66             return i;
67     }
68     return -1;
69 }
70
71 static void cancellable_interrupted(void *p_data)
72 {
73     GCancellable *p_canc = p_data;
74     g_cancellable_cancel(p_canc);
75 }
76
77 static GCancellable *cancellable_register()
78 {
79     GCancellable *p_canc = g_cancellable_new();
80     if (!p_canc)
81         return NULL;
82     vlc_interrupt_register(cancellable_interrupted, p_canc);
83     return p_canc;
84 }
85
86 static void cancellable_unregister(GCancellable *p_canc)
87 {
88     if (p_canc != NULL)
89     {
90         vlc_interrupt_unregister();
91         g_object_unref(p_canc);
92     }
93 }
94
95 static GHashTable *
96 values_to_ghashtable(const char *const ppsz_values[KEY_MAX])
97 {
98     GHashTable *p_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
99                                                NULL, NULL);
100     if (!p_hash)
101         return NULL;
102     for (unsigned int i = 0; i < KEY_MAX; ++i)
103     {
104         if (ppsz_values[i])
105             g_hash_table_insert(p_hash, (gpointer) ppsz_keys[i],
106                                 (gpointer) ppsz_values[i]);
107     }
108     return p_hash;
109 }
110
111 static void
112 ghash_to_value(gpointer key, gpointer value, gpointer user_data)
113 {
114     const char **ppsz_values = user_data;
115
116     const char *psz_key = key;
117     int i_key = str2key(psz_key);
118     if (i_key == -1 || i_key >= KEY_MAX)
119         return;
120
121     ppsz_values[i_key] = strdup((const char *)value);
122 }
123
124 static int
125 ghashtable_to_values(GHashTable *g_hash, const char *ppsz_values[KEY_MAX])
126 {
127     g_hash_table_foreach(g_hash, ghash_to_value, ppsz_values);
128     return VLC_SUCCESS;
129 }
130
131 static void
132 ghashtable_insert_vlc_id(GHashTable *g_hash)
133 {
134     g_hash_table_insert(g_hash, (gpointer) ".created_by",
135                         (gpointer) VLC_KEYSTORE_NAME);
136 }
137
138 static int
139 Store(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
140       const uint8_t *p_secret, size_t i_secret_len, const char *psz_label)
141 {
142     SecretService *p_ss = (SecretService *) p_keystore->p_sys;
143     GHashTable *p_hash = values_to_ghashtable(ppsz_values);
144     if (!p_hash)
145         return VLC_EGENERIC;
146     ghashtable_insert_vlc_id(p_hash);
147
148     SecretValue *p_sv = secret_value_new((const gchar *)p_secret, i_secret_len,
149                                          "text/plain");
150     if (!p_sv)
151     {
152         g_hash_table_unref(p_hash);
153         return VLC_EGENERIC;
154     }
155
156     GCancellable *p_canc = cancellable_register();
157     gboolean b_ret = secret_service_store_sync(p_ss, NULL, p_hash,
158                                                SECRET_COLLECTION_DEFAULT,
159                                                psz_label, p_sv, p_canc, NULL);
160     cancellable_unregister(p_canc);
161
162     secret_value_unref(p_sv);
163     g_hash_table_unref(p_hash);
164     return b_ret ? VLC_SUCCESS : VLC_EGENERIC;
165 }
166
167 static GList*
168 items_search(SecretService *p_ss, const char *const ppsz_values[KEY_MAX],
169              bool b_safe)
170 {
171     GHashTable *p_hash = values_to_ghashtable(ppsz_values);
172     if (!p_hash)
173         return 0;
174
175     /* If true, do not allow to remove non VLC entries */
176     if (b_safe)
177         ghashtable_insert_vlc_id(p_hash);
178
179     GCancellable *p_canc = cancellable_register();
180     GList *p_list = secret_service_search_sync(p_ss, NULL, p_hash,
181                                                SECRET_SEARCH_ALL
182                                                | SECRET_SEARCH_UNLOCK
183                                                | SECRET_SEARCH_LOAD_SECRETS,
184                                                p_canc, NULL);
185     cancellable_unregister(p_canc);
186     g_hash_table_unref(p_hash);
187     return p_list;
188 }
189
190 static unsigned int
191 Find(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX],
192      vlc_keystore_entry **pp_entries)
193 {
194     SecretService *p_ss = (SecretService *) p_keystore->p_sys;
195
196     GList *p_list = items_search(p_ss, ppsz_values, false);
197     if (!p_list)
198         return 0;
199
200     unsigned int i_found_count = g_list_length(p_list);
201     unsigned int i_entry_count = 0;
202     vlc_keystore_entry *p_entries = calloc(i_found_count,
203                                            sizeof(vlc_keystore_entry));
204     if (!p_entries)
205         goto error;
206
207     for (GList *l = p_list; l != NULL; l = l->next)
208     {
209         SecretItem *p_item = (SecretItem *) l->data;
210         GHashTable *p_attrs = secret_item_get_attributes(p_item);
211
212         vlc_keystore_entry *p_entry = &p_entries[i_entry_count++];
213         /* fill ppsz_values */
214         if (ghashtable_to_values(p_attrs, (const char **) p_entry->ppsz_values))
215         {
216             g_hash_table_unref(p_attrs);
217             goto error;
218         }
219         g_hash_table_unref(p_attrs);
220
221         /* fill secret */
222         SecretValue *p_secret_value = secret_item_get_secret(p_item);
223         gsize i_len;
224         const gchar *psz_value = secret_value_get(p_secret_value, &i_len);
225         if (i_len > 0)
226         {
227             if (vlc_keystore_entry_set_secret(p_entry,
228                                               (const uint8_t *)psz_value, i_len))
229             {
230                 secret_value_unref(p_secret_value);
231                 goto error;
232             }
233         }
234         secret_value_unref(p_secret_value);
235     }
236     g_list_free_full(p_list, g_object_unref);
237     *pp_entries = p_entries;
238     return i_entry_count;
239
240 error:
241     g_list_free_full(p_list, g_object_unref);
242     if (i_entry_count > 0)
243         vlc_keystore_release_entries(p_entries, i_entry_count);
244     return 0;
245 }
246
247 static unsigned int
248 Remove(vlc_keystore *p_keystore, const char *const ppsz_values[KEY_MAX])
249 {
250     SecretService *p_ss = (SecretService *) p_keystore->p_sys;
251
252     GList *p_list = items_search(p_ss, ppsz_values, true);
253     if (!p_list)
254         return 0;
255
256     unsigned int i_entry_count = 0;
257     for (GList *l = p_list; l != NULL; l = l->next)
258     {
259         SecretItem *p_item = (SecretItem *) l->data;
260         secret_item_delete(p_item, NULL, NULL, NULL);
261         i_entry_count++;
262     }
263     g_list_free_full(p_list, g_object_unref);
264     return i_entry_count;
265 }
266
267 struct secrets_watch_data
268 {
269     vlc_sem_t sem;
270     bool b_running;
271 };
272
273 static void
274 dbus_appeared_cb(GDBusConnection *connection, const gchar *name,
275                 const gchar *name_owner, gpointer user_data)
276 {
277     (void) connection; (void) name; (void)name_owner;
278     struct secrets_watch_data *p_watch_data = user_data;
279     p_watch_data->b_running = true;
280     vlc_sem_post(&p_watch_data->sem);
281 }
282
283 static void
284 dbus_vanished_cb(GDBusConnection *connection, const gchar *name,
285                 gpointer user_data)
286 {
287     (void) connection; (void) name;
288     struct secrets_watch_data *p_watch_data = user_data;
289     p_watch_data->b_running = false;
290     vlc_sem_post(&p_watch_data->sem);
291 }
292
293 static int
294 Open(vlc_object_t *p_this)
295 {
296     if (!p_this->b_force)
297     {
298         /* First, check if secrets service is running using g_bus_watch_name().
299          * Indeed, secret_service_get_sync will spawn a service if it's not
300          * running, even on non Gnome environments */
301         struct secrets_watch_data watch_data;
302         watch_data.b_running = false;
303         vlc_sem_init(&watch_data.sem, 0);
304
305         guint i_id = g_bus_watch_name(G_BUS_TYPE_SESSION,
306                                       "org.freedesktop.secrets",
307                                       G_BUS_NAME_WATCHER_FLAGS_NONE,
308                                       dbus_appeared_cb, dbus_vanished_cb,
309                                       &watch_data, NULL);
310
311         /* We are guaranteed that one of the callbacks will be invoked after
312          * calling g_bus_watch_name */
313         vlc_sem_wait_i11e(&watch_data.sem);
314
315         g_bus_unwatch_name(i_id);
316         vlc_sem_destroy(&watch_data.sem);
317
318         if (!watch_data.b_running)
319             return VLC_EGENERIC;
320     }
321
322     GCancellable *p_canc = cancellable_register();
323     SecretService *p_ss = secret_service_get_sync(SECRET_SERVICE_NONE,
324                                                   p_canc, NULL);
325     cancellable_unregister(p_canc);
326     if (!p_ss)
327         return VLC_EGENERIC;
328
329     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
330
331     p_keystore->p_sys = (vlc_keystore_sys *) p_ss;
332     p_keystore->pf_store = Store;
333     p_keystore->pf_find = Find;
334     p_keystore->pf_remove = Remove;
335
336     return VLC_SUCCESS;
337 }
338
339 static void
340 Close(vlc_object_t *p_this)
341 {
342     vlc_keystore *p_keystore = (vlc_keystore *)p_this;
343     SecretService *p_ss = (SecretService *) p_keystore->p_sys;
344     g_object_unref(p_ss);
345     secret_service_disconnect();
346 }