1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2008 Carlos Garcia Campos
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Carlos Garcia Campos <carlosgc@gnome.org>
21  */
22 
23 #include <config.h>
24 
25 #define SECRET_API_SUBJECT_TO_CHANGE 1
26 
27 #ifdef HAVE_KEYRING
28 #include <libsecret/secret.h>
29 #endif
30 
31 #include "gvfskeyring.h"
32 
33 gboolean
g_vfs_keyring_is_available(void)34 g_vfs_keyring_is_available (void)
35 {
36 #ifdef HAVE_KEYRING
37   return TRUE;
38 #else
39   return FALSE;
40 #endif
41 }
42 
43 #ifdef HAVE_KEYRING
44 
45 static void
insert_string(const gchar * key,const gchar * value,GHashTable ** attributes)46 insert_string (const gchar *key,
47 	       const gchar *value,
48 	       GHashTable **attributes)
49 {
50   if (*attributes == NULL)
51     return;
52 
53   if (!g_utf8_validate (value, -1, NULL))
54     {
55       g_warning ("Non-utf8 value for key %s\n", key);
56       g_hash_table_unref (*attributes);
57       *attributes = NULL;
58     }
59 
60   g_hash_table_insert (*attributes,
61 		       g_strdup (key),
62 		       g_strdup (value));
63 }
64 
65 static void
insert_int(const gchar * key,gint value,GHashTable ** attributes)66 insert_int (const gchar *key,
67 	    gint value,
68 	    GHashTable **attributes)
69 {
70   if (*attributes == NULL)
71     return;
72 
73   g_hash_table_insert (*attributes,
74 		       g_strdup (key),
75 		       g_strdup_printf ("%d", value));
76 }
77 
78 static GHashTable *
build_network_attributes(const gchar * username,const gchar * host,const gchar * domain,const gchar * protocol,const gchar * object,const gchar * authtype,guint32 port)79 build_network_attributes (const gchar *username,
80                           const gchar *host,
81                           const gchar *domain,
82                           const gchar *protocol,
83                           const gchar *object,
84                           const gchar *authtype,
85                           guint32 port)
86 {
87   GHashTable *attributes;
88 
89   attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
90 
91   if (username)
92     insert_string ("user", username, &attributes);
93   if (host)
94     insert_string ("server", host, &attributes);
95   if (domain)
96     insert_string ("domain", domain, &attributes);
97   if (protocol)
98     insert_string ("protocol", protocol, &attributes);
99   if (object)
100     insert_string ("object", object, &attributes);
101   if (authtype)
102     insert_string ("authtype", authtype, &attributes);
103   if (port != 0)
104     insert_int ("port", (gint)port, &attributes);
105 
106   return attributes;
107 }
108 
109 static gchar *
build_network_label(const gchar * user,const gchar * server,const gchar * object,guint32 port)110 build_network_label (const gchar *user,
111                      const gchar *server,
112                      const gchar *object,
113                      guint32  port)
114 {
115   GString *s;
116   gchar *name;
117 
118   if (server != NULL)
119     {
120       s = g_string_new (NULL);
121       if (user != NULL)
122         {
123           g_string_append_uri_escaped (s, user, G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, TRUE);
124           g_string_append (s, "@");
125         }
126       g_string_append (s, server);
127       if (port != 0)
128         g_string_append_printf (s, ":%d", port);
129       if (object != NULL)
130         g_string_append_printf (s, "/%s", object);
131       name = g_string_free (s, FALSE);
132     }
133   else
134     {
135       name = g_strdup ("network password");
136     }
137   return name;
138 }
139 
140 static gint
compare_specificity(gconstpointer a,gconstpointer b)141 compare_specificity (gconstpointer  a,
142 		     gconstpointer  b)
143 {
144   GHashTable  *attributes_a, *attributes_b;
145   SecretItem  *item_a, *item_b;
146   int res;
147 
148   item_a = SECRET_ITEM (a);
149   attributes_a = secret_item_get_attributes (item_a);
150 
151   item_b = SECRET_ITEM (b);
152   attributes_b = secret_item_get_attributes (item_b);
153 
154   res = g_hash_table_size (attributes_a) - g_hash_table_size (attributes_b);
155 
156   /* Prefer the most recent item if they are equal in specificity. */
157   if (res == 0)
158     res = secret_item_get_modified (item_b) - secret_item_get_modified (item_a);
159 
160   g_hash_table_unref (attributes_a);
161   g_hash_table_unref (attributes_b);
162 
163   return res;
164 }
165 
166 #endif /* HAVE_KEYRING */
167 
168 gboolean
g_vfs_keyring_lookup_password(const gchar * username,const gchar * host,const gchar * domain,const gchar * protocol,const gchar * object,const gchar * authtype,guint32 port,gchar ** username_out,gchar ** domain_out,gchar ** password_out)169 g_vfs_keyring_lookup_password (const gchar *username,
170                                const gchar *host,
171                                const gchar *domain,
172                                const gchar *protocol,
173 			       const gchar *object,
174 			       const gchar *authtype,
175 			       guint32      port,
176                                gchar      **username_out,
177                                gchar      **domain_out,
178                                gchar      **password_out)
179 {
180 #ifdef HAVE_KEYRING
181   GHashTable  *attributes;
182   SecretItem  *item;
183   SecretValue *secret;
184   GList       *plist;
185   GError      *error = NULL;
186 
187 
188   attributes = build_network_attributes (username, host, domain, protocol, object, authtype, port);
189   plist = secret_service_search_sync (NULL, SECRET_SCHEMA_COMPAT_NETWORK, attributes,
190                                       SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS |
191 				      SECRET_SEARCH_ALL,
192                                       NULL, &error);
193   g_hash_table_unref (attributes);
194 
195   if (error != NULL)
196     {
197        g_error_free (error);
198        return FALSE;
199     }
200 
201   if (plist == NULL)
202     return FALSE;
203 
204   /* We want the least specific result, so we sort the return values.
205      For instance, given both items for ftp://host:port and ftp://host
206      in the keyring we always want to use the ftp://host one for
207      i.e. ftp://host/some/path. */
208 
209   plist = g_list_sort (plist, compare_specificity);
210 
211   item = SECRET_ITEM (plist->data);
212   secret = secret_item_get_secret (item);
213   attributes = secret_item_get_attributes (item);
214   g_list_free_full (plist, g_object_unref);
215 
216   if (secret == NULL)
217     {
218       if (attributes)
219         g_hash_table_unref (attributes);
220       return FALSE;
221     }
222 
223   *password_out = g_strdup (secret_value_get (secret, NULL));
224   secret_value_unref (secret);
225 
226   if (username_out)
227     *username_out = g_strdup (g_hash_table_lookup (attributes, "user"));
228 
229   if (domain_out)
230     *domain_out = g_strdup (g_hash_table_lookup (attributes, "domain"));
231 
232   g_hash_table_unref (attributes);
233   return TRUE;
234 #else
235   return FALSE;
236 #endif /* HAVE_KEYRING */
237 }
238 
239 gboolean
g_vfs_keyring_save_password(const gchar * username,const gchar * host,const gchar * domain,const gchar * protocol,const gchar * object,const gchar * authtype,guint32 port,const gchar * password,GPasswordSave flags)240 g_vfs_keyring_save_password (const gchar  *username,
241                              const gchar  *host,
242                              const gchar  *domain,
243                              const gchar  *protocol,
244 			     const gchar  *object,
245 			     const gchar  *authtype,
246 			     guint32       port,
247                              const gchar  *password,
248                              GPasswordSave flags)
249 {
250 #ifdef HAVE_KEYRING
251   const gchar       *keyring;
252   GHashTable        *attributes;
253   gchar             *label;
254   gboolean           ret;
255 
256   if (flags == G_PASSWORD_SAVE_NEVER)
257     return FALSE;
258 
259   keyring = (flags == G_PASSWORD_SAVE_FOR_SESSION) ? SECRET_COLLECTION_SESSION : SECRET_COLLECTION_DEFAULT;
260 
261   label = build_network_label (username, host, object, port);
262   attributes = build_network_attributes (username, host, domain, protocol, object, authtype, port);
263 
264   ret = secret_password_storev_sync (SECRET_SCHEMA_COMPAT_NETWORK, attributes,
265                                      keyring, label, password, NULL, NULL);
266 
267   g_free (label);
268   g_hash_table_unref (attributes);
269 
270   return ret;
271 #else
272   return FALSE;
273 #endif /* HAVE_KEYRING */
274 }
275