1 /* Representation of the account manager as presented to plugins. This is
2  * deliberately a "smaller" API than McdAccountManager.
3  *
4  * Copyright © 2010 Nokia Corporation
5  * Copyright © 2010-2012 Collabora Ltd.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  *
22  */
23 #include "config.h"
24 #include "mcd-storage.h"
25 
26 #include "mcd-account.h"
27 #include "mcd-account-config.h"
28 #include "mcd-debug.h"
29 #include "mcd-misc.h"
30 #include "plugin-loader.h"
31 
32 #include <errno.h>
33 #include <string.h>
34 
35 #include <telepathy-glib/telepathy-glib.h>
36 #include <telepathy-glib/telepathy-glib-dbus.h>
37 
38 #include "mission-control-plugins/implementation.h"
39 
40 /* these pseudo-plugins take care of the actual account storage/retrieval */
41 #include "mcd-account-manager-default.h"
42 
43 #if ENABLE_LIBACCOUNTS_SSO
44 #include "mcd-account-manager-sso.h"
45 # ifdef ACCOUNTS_GLIB_HIDDEN_SERVICE_TYPE
46 # include "mcd-storage-ag-hidden.h"
47 # endif
48 #endif
49 
50 #define MAX_KEY_LENGTH (DBUS_MAXIMUM_NAME_LENGTH + 6)
51 
52 static GList *stores = NULL;
53 static void sort_and_cache_plugins (void);
54 
55 enum {
56   PROP_DBUS_DAEMON = 1,
57 };
58 
59 struct _McdStorageClass {
60     GObjectClass parent;
61 };
62 
63 static void plugin_iface_init (McpAccountManagerIface *iface,
64     gpointer unused G_GNUC_UNUSED);
65 
66 G_DEFINE_TYPE_WITH_CODE (McdStorage, mcd_storage,
67     G_TYPE_OBJECT,
68     G_IMPLEMENT_INTERFACE (MCP_TYPE_ACCOUNT_MANAGER, plugin_iface_init))
69 
70 typedef struct {
71     /* owned string => GVariant
72      * e.g. { 'DisplayName': <'Frederick Bloggs'> } */
73     GHashTable *attributes;
74     /* owned string => owned GVariant
75      * e.g. { 'account': <'fred@example.com'>, 'password': <'foo'> } */
76     GHashTable *parameters;
77     /* owned string => owned string escaped as if for a keyfile
78      * e.g. { 'account': 'fred@example.com', 'password': 'foo' }
79      * keys of @parameters and @escaped_parameters are disjoint */
80     GHashTable *escaped_parameters;
81     /* set of owned strings
82      * e.g. { 'password': 'password' } */
83     GHashTable *secrets;
84 } McdStorageAccount;
85 
86 static void
mcd_storage_account_free(gpointer p)87 mcd_storage_account_free (gpointer p)
88 {
89   McdStorageAccount *sa = p;
90 
91   g_hash_table_unref (sa->attributes);
92   g_hash_table_unref (sa->parameters);
93   g_hash_table_unref (sa->escaped_parameters);
94   g_hash_table_unref (sa->secrets);
95   g_slice_free (McdStorageAccount, sa);
96 }
97 
98 static void
mcd_storage_init(McdStorage * self)99 mcd_storage_init (McdStorage *self)
100 {
101   self->accounts = g_hash_table_new_full (g_str_hash, g_str_equal,
102       g_free, mcd_storage_account_free);
103 }
104 
105 static void
storage_finalize(GObject * object)106 storage_finalize (GObject *object)
107 {
108   McdStorage *self = MCD_STORAGE (object);
109   GObjectFinalizeFunc finalize =
110     G_OBJECT_CLASS (mcd_storage_parent_class)->finalize;
111 
112   g_hash_table_unref (self->accounts);
113   self->accounts = NULL;
114 
115   if (finalize != NULL)
116     finalize (object);
117 }
118 
119 static void
storage_dispose(GObject * object)120 storage_dispose (GObject *object)
121 {
122   McdStorage *self = MCD_STORAGE (object);
123   GObjectFinalizeFunc dispose =
124     G_OBJECT_CLASS (mcd_storage_parent_class)->dispose;
125 
126   tp_clear_object (&self->dbusd);
127 
128   if (dispose != NULL)
129     dispose (object);
130 }
131 
132 static void
storage_set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)133 storage_set_property (GObject *obj, guint prop_id,
134 	      const GValue *val, GParamSpec *pspec)
135 {
136     McdStorage *self = MCD_STORAGE (obj);
137 
138     switch (prop_id)
139     {
140       case PROP_DBUS_DAEMON:
141         tp_clear_object (&self->dbusd);
142         self->dbusd = TP_DBUS_DAEMON (g_value_dup_object (val));
143         break;
144 
145       default:
146         G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
147         break;
148     }
149 }
150 
151 static void
storage_get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)152 storage_get_property (GObject *obj, guint prop_id,
153 	      GValue *val, GParamSpec *pspec)
154 {
155     McdStorage *self = MCD_STORAGE (obj);
156 
157     switch (prop_id)
158     {
159       case PROP_DBUS_DAEMON:
160         g_value_set_object (val, self->dbusd);
161         break;
162       default:
163         G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
164         break;
165     }
166 }
167 
168 static void
mcd_storage_class_init(McdStorageClass * cls)169 mcd_storage_class_init (McdStorageClass *cls)
170 {
171   GObjectClass *object_class = (GObjectClass *) cls;
172   GParamSpec *spec = g_param_spec_object ("dbus-daemon",
173       "DBus daemon",
174       "DBus daemon",
175       TP_TYPE_DBUS_DAEMON,
176       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
177 
178   object_class->set_property = storage_set_property;
179   object_class->get_property = storage_get_property;
180   object_class->dispose = storage_dispose;
181   object_class->finalize = storage_finalize;
182 
183   g_object_class_install_property (object_class, PROP_DBUS_DAEMON, spec);
184 }
185 
186 McdStorage *
mcd_storage_new(TpDBusDaemon * dbus_daemon)187 mcd_storage_new (TpDBusDaemon *dbus_daemon)
188 {
189   return g_object_new (MCD_TYPE_STORAGE,
190       "dbus-daemon", dbus_daemon,
191       NULL);
192 }
193 
194 static gchar *
mcd_keyfile_escape_variant(GVariant * variant)195 mcd_keyfile_escape_variant (GVariant *variant)
196 {
197   GValue value = G_VALUE_INIT;
198   gchar *ret;
199 
200   dbus_g_value_parse_g_variant (variant, &value);
201 
202   if (G_IS_VALUE (&value))
203     {
204       ret = mcd_keyfile_escape_value (&value);
205       g_value_unset (&value);
206     }
207   else
208     {
209       gchar *printed = g_variant_print (variant, TRUE);
210 
211       ret = NULL;
212       g_warning ("Unable to translate variant %s", printed);
213       g_free (printed);
214     }
215 
216   return ret;
217 }
218 
219 static McdStorageAccount *
lookup_account(McdStorage * self,const gchar * account)220 lookup_account (McdStorage *self,
221     const gchar *account)
222 {
223   return g_hash_table_lookup (self->accounts, account);
224 }
225 
226 static McdStorageAccount *
ensure_account(McdStorage * self,const gchar * account)227 ensure_account (McdStorage *self,
228     const gchar *account)
229 {
230   McdStorageAccount *sa = lookup_account (self, account);
231 
232   if (sa == NULL)
233     {
234       sa = g_slice_new (McdStorageAccount);
235       sa->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
236           g_free, (GDestroyNotify) g_variant_unref);
237       sa->parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
238           g_free, (GDestroyNotify) g_variant_unref);
239       sa->escaped_parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
240           g_free, g_free);
241       sa->secrets = g_hash_table_new_full (g_str_hash, g_str_equal,
242           g_free, NULL);
243       g_hash_table_insert (self->accounts, g_strdup (account), sa);
244     }
245 
246   return sa;
247 }
248 
249 static gchar *
get_value(const McpAccountManager * ma,const gchar * account,const gchar * key)250 get_value (const McpAccountManager *ma,
251     const gchar *account,
252     const gchar *key)
253 {
254   McdStorage *self = MCD_STORAGE (ma);
255   McdStorageAccount *sa = lookup_account (self, account);
256   GVariant *variant;
257   gchar *ret;
258 
259   if (sa == NULL)
260     return NULL;
261 
262   if (g_str_has_prefix (key, "param-"))
263     {
264       variant = g_hash_table_lookup (sa->parameters, key + 6);
265 
266       if (variant != NULL)
267         {
268           ret = mcd_keyfile_escape_variant (variant);
269           g_variant_unref (variant);
270           return ret;
271         }
272       else
273         {
274           /* OK, we don't have it as a variant. How about the keyfile-escaped
275            * version? */
276           return g_strdup (g_hash_table_lookup (sa->escaped_parameters,
277                 key + 6));
278         }
279     }
280   else
281     {
282       variant = g_hash_table_lookup (sa->attributes, key);
283 
284       if (variant != NULL)
285         {
286           ret = mcd_keyfile_escape_variant (variant);
287           g_variant_unref (variant);
288           return ret;
289         }
290       else
291         {
292           return NULL;
293         }
294     }
295 }
296 
297 static struct {
298     const gchar *type;
299     const gchar *name;
300 } known_attributes[] = {
301     /* Please keep this sorted by type, then by name. */
302 
303     /* Structs */
304       { "(uss)", MC_ACCOUNTS_KEY_AUTOMATIC_PRESENCE },
305 
306     /* Array of object path */
307       { "ao", MC_ACCOUNTS_KEY_SUPERSEDES },
308 
309     /* Array of string */
310       { "as", MC_ACCOUNTS_KEY_URI_SCHEMES },
311 
312     /* Booleans */
313       { "b", MC_ACCOUNTS_KEY_ALWAYS_DISPATCH },
314       { "b", MC_ACCOUNTS_KEY_CONNECT_AUTOMATICALLY },
315       { "b", MC_ACCOUNTS_KEY_ENABLED },
316       { "b", MC_ACCOUNTS_KEY_HAS_BEEN_ONLINE },
317       { "b", MC_ACCOUNTS_KEY_HIDDEN },
318 
319     /* Strings */
320       { "s", MC_ACCOUNTS_KEY_AUTO_PRESENCE_MESSAGE },
321       { "s", MC_ACCOUNTS_KEY_AUTO_PRESENCE_STATUS },
322       { "s", MC_ACCOUNTS_KEY_AVATAR_MIME },
323       { "s", MC_ACCOUNTS_KEY_AVATAR_TOKEN },
324       { "s", MC_ACCOUNTS_KEY_DISPLAY_NAME },
325       { "s", MC_ACCOUNTS_KEY_ICON },
326       { "s", MC_ACCOUNTS_KEY_MANAGER },
327       { "s", MC_ACCOUNTS_KEY_NICKNAME },
328       { "s", MC_ACCOUNTS_KEY_NORMALIZED_NAME },
329       { "s", MC_ACCOUNTS_KEY_PROTOCOL },
330       { "s", MC_ACCOUNTS_KEY_SERVICE },
331 
332     /* Integers */
333       { "u", MC_ACCOUNTS_KEY_AUTO_PRESENCE_TYPE },
334 
335       { NULL, NULL }
336 };
337 
338 const gchar *
mcd_storage_get_attribute_type(const gchar * attribute)339 mcd_storage_get_attribute_type (const gchar *attribute)
340 {
341   guint i;
342 
343   for (i = 0; known_attributes[i].type != NULL; i++)
344     {
345       if (!tp_strdiff (attribute, known_attributes[i].name))
346         return known_attributes[i].type;
347     }
348 
349   /* special case for mcd-account-conditions.c */
350   if (g_str_has_prefix (attribute, "condition-"))
351     return "s";
352 
353   return NULL;
354 }
355 
356 gboolean
mcd_storage_init_value_for_attribute(GValue * value,const gchar * attribute)357 mcd_storage_init_value_for_attribute (GValue *value,
358     const gchar *attribute)
359 {
360   const gchar *s = mcd_storage_get_attribute_type (attribute);
361 
362   if (s == NULL)
363     return FALSE;
364 
365   switch (s[0])
366     {
367       case 's':
368         g_value_init (value, G_TYPE_STRING);
369         return TRUE;
370 
371       case 'b':
372         g_value_init (value, G_TYPE_BOOLEAN);
373         return TRUE;
374 
375       case 'u':
376         /* this seems wrong but it's how we've always done it */
377         g_value_init (value, G_TYPE_INT);
378         return TRUE;
379 
380       case 'a':
381           {
382             switch (s[1])
383               {
384                 case 'o':
385                   g_value_init (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST);
386                   return TRUE;
387 
388                 case 's':
389                   g_value_init (value, G_TYPE_STRV);
390                   return TRUE;
391               }
392           }
393         break;
394 
395       case '(':
396           {
397             if (!tp_strdiff (s, "(uss)"))
398               {
399                 g_value_init (value, TP_STRUCT_TYPE_SIMPLE_PRESENCE);
400                 return TRUE;
401               }
402           }
403         break;
404     }
405 
406   return FALSE;
407 }
408 
409 static gboolean
mcpa_init_value_for_attribute(const McpAccountManager * mcpa,GValue * value,const gchar * attribute)410 mcpa_init_value_for_attribute (const McpAccountManager *mcpa,
411     GValue *value,
412     const gchar *attribute)
413 {
414   return mcd_storage_init_value_for_attribute (value, attribute);
415 }
416 
417 static void
mcpa_set_attribute(const McpAccountManager * ma,const gchar * account,const gchar * attribute,GVariant * value,McpAttributeFlags flags)418 mcpa_set_attribute (const McpAccountManager *ma,
419     const gchar *account,
420     const gchar *attribute,
421     GVariant *value,
422     McpAttributeFlags flags)
423 {
424   McdStorage *self = MCD_STORAGE (ma);
425   McdStorageAccount *sa = ensure_account (self, account);
426 
427   if (value != NULL)
428     {
429       g_hash_table_insert (sa->attributes, g_strdup (attribute),
430           g_variant_ref_sink (value));
431     }
432   else
433     {
434       g_hash_table_remove (sa->attributes, attribute);
435     }
436 }
437 
438 static void
mcpa_set_parameter(const McpAccountManager * ma,const gchar * account,const gchar * parameter,GVariant * value,McpParameterFlags flags)439 mcpa_set_parameter (const McpAccountManager *ma,
440     const gchar *account,
441     const gchar *parameter,
442     GVariant *value,
443     McpParameterFlags flags)
444 {
445   McdStorage *self = MCD_STORAGE (ma);
446   McdStorageAccount *sa = ensure_account (self, account);
447 
448   g_hash_table_remove (sa->parameters, parameter);
449   g_hash_table_remove (sa->escaped_parameters, parameter);
450 
451   if (value != NULL)
452     g_hash_table_insert (sa->parameters, g_strdup (parameter),
453         g_variant_ref_sink (value));
454 
455   if (flags & MCP_PARAMETER_FLAG_SECRET)
456     {
457       DEBUG ("flagging %s parameter %s as secret", account, parameter);
458       g_hash_table_add (sa->secrets, g_strdup (parameter));
459     }
460 }
461 
462 static void
set_value(const McpAccountManager * ma,const gchar * account,const gchar * key,const gchar * value)463 set_value (const McpAccountManager *ma,
464     const gchar *account,
465     const gchar *key,
466     const gchar *value)
467 {
468   McdStorage *self = MCD_STORAGE (ma);
469   McdStorageAccount *sa = ensure_account (self, account);
470 
471   if (g_str_has_prefix (key, "param-"))
472     {
473       g_hash_table_remove (sa->parameters, key + 6);
474       g_hash_table_remove (sa->escaped_parameters, key + 6);
475 
476       if (value != NULL)
477         g_hash_table_insert (sa->escaped_parameters, g_strdup (key + 6),
478             g_strdup (value));
479     }
480   else
481     {
482       if (value != NULL)
483         {
484           GValue tmp = G_VALUE_INIT;
485           GError *error = NULL;
486 
487           if (!mcd_storage_init_value_for_attribute (&tmp, key))
488             {
489               g_warning ("Not sure what the type of '%s' is, assuming string",
490                   key);
491               g_value_init (&tmp, G_TYPE_STRING);
492             }
493 
494           if (mcd_keyfile_unescape_value (value, &tmp, &error))
495             {
496               g_hash_table_insert (sa->attributes, g_strdup (key),
497                   g_variant_ref_sink (dbus_g_value_build_g_variant (&tmp)));
498               g_value_unset (&tmp);
499             }
500           else
501             {
502               g_warning ("Could not decode attribute '%s':'%s' from plugin: %s",
503                   key, value, error->message);
504               g_error_free (error);
505               g_hash_table_remove (sa->attributes, key);
506             }
507         }
508       else
509         {
510           g_hash_table_remove (sa->attributes, key);
511         }
512     }
513 }
514 
515 static GStrv
list_keys(const McpAccountManager * ma,const gchar * account)516 list_keys (const McpAccountManager *ma,
517            const gchar * account)
518 {
519   McdStorage *self = MCD_STORAGE (ma);
520   GPtrArray *ret = g_ptr_array_new ();
521   McdStorageAccount *sa = lookup_account (self, account);
522 
523   if (sa != NULL)
524     {
525       GHashTableIter iter;
526       gpointer k;
527 
528       g_hash_table_iter_init (&iter, sa->attributes);
529 
530       while (g_hash_table_iter_next (&iter, &k, NULL))
531         g_ptr_array_add (ret, g_strdup (k));
532 
533       g_hash_table_iter_init (&iter, sa->parameters);
534 
535       while (g_hash_table_iter_next (&iter, &k, NULL))
536         g_ptr_array_add (ret, g_strdup_printf ("param-%s", (gchar *) k));
537     }
538 
539   g_ptr_array_add (ret, NULL);
540   return (GStrv) g_ptr_array_free (ret, FALSE);
541 }
542 
543 static gboolean
is_secret(const McpAccountManager * ma,const gchar * account,const gchar * key)544 is_secret (const McpAccountManager *ma,
545     const gchar *account,
546     const gchar *key)
547 {
548   McdStorage *self = MCD_STORAGE (ma);
549   McdStorageAccount *sa = lookup_account (self, account);
550 
551   if (sa == NULL || !g_str_has_prefix (key, "param-"))
552     return FALSE;
553 
554   return g_hash_table_contains (sa->secrets, key + 6);
555 }
556 
557 static void
mcd_storage_make_secret(McdStorage * self,const gchar * account,const gchar * key)558 mcd_storage_make_secret (McdStorage *self,
559     const gchar *account,
560     const gchar *key)
561 {
562   McdStorageAccount *sa;
563 
564   g_return_if_fail (MCD_IS_STORAGE (self));
565   g_return_if_fail (account != NULL);
566   g_return_if_fail (key != NULL);
567 
568   if (!g_str_has_prefix (key, "param-"))
569     return;
570 
571   DEBUG ("flagging %s parameter %s as secret", account, key + 6);
572   sa = ensure_account (self, account);
573   g_hash_table_add (sa->secrets, g_strdup (key + 6));
574 }
575 
576 static void
make_secret(const McpAccountManager * ma,const gchar * account,const gchar * key)577 make_secret (const McpAccountManager *ma,
578     const gchar *account,
579     const gchar *key)
580 {
581   mcd_storage_make_secret (MCD_STORAGE (ma), account, key);
582 }
583 
584 static gchar *
unique_name(const McpAccountManager * ma,const gchar * manager,const gchar * protocol,const GHashTable * params)585 unique_name (const McpAccountManager *ma,
586     const gchar *manager,
587     const gchar *protocol,
588     const GHashTable *params)
589 {
590   McdStorage *self = MCD_STORAGE (ma);
591   const gchar *base = NULL;
592   gchar *esc_manager, *esc_protocol, *esc_base;
593   guint i;
594   gsize base_len = strlen (TP_ACCOUNT_OBJECT_PATH_BASE);
595   DBusGConnection *connection = tp_proxy_get_dbus_connection (self->dbusd);
596 
597   base = tp_asv_get_string (params, "account");
598 
599   if (base == NULL)
600     base = "account";
601 
602   esc_manager = tp_escape_as_identifier (manager);
603   esc_protocol = g_strdelimit (g_strdup (protocol), "-", '_');
604   esc_base = tp_escape_as_identifier (base);
605 
606   for (i = 0; i < G_MAXUINT; i++)
607     {
608       gchar *path = g_strdup_printf (
609           TP_ACCOUNT_OBJECT_PATH_BASE "%s/%s/%s%u",
610           esc_manager, esc_protocol, esc_base, i);
611 
612       if (!g_hash_table_contains (self->accounts, path + base_len) &&
613           dbus_g_connection_lookup_g_object (connection, path) == NULL)
614         {
615           gchar *ret = g_strdup (path + base_len);
616 
617           g_free (path);
618           return ret;
619         }
620 
621       g_free (path);
622     }
623 
624   return NULL;
625 }
626 
627 /* sort in descending order of priority (ie higher prio => earlier in list) */
628 static gint
account_storage_cmp(gconstpointer a,gconstpointer b)629 account_storage_cmp (gconstpointer a, gconstpointer b)
630 {
631     gint pa = mcp_account_storage_priority (a);
632     gint pb = mcp_account_storage_priority (b);
633 
634     if (pa > pb) return -1;
635     if (pa < pb) return 1;
636 
637     return 0;
638 }
639 
640 static void
add_storage_plugin(McpAccountStorage * plugin)641 add_storage_plugin (McpAccountStorage *plugin)
642 {
643   stores = g_list_insert_sorted (stores, plugin, account_storage_cmp);
644 }
645 
646 static void
add_libaccounts_plugins_if_enabled(void)647 add_libaccounts_plugins_if_enabled (void)
648 {
649 #if ENABLE_LIBACCOUNTS_SSO
650   add_storage_plugin (MCP_ACCOUNT_STORAGE (mcd_account_manager_sso_new ()));
651 # ifdef ACCOUNTS_GLIB_HIDDEN_SERVICE_TYPE
652   add_storage_plugin (MCP_ACCOUNT_STORAGE (mcd_storage_ag_hidden_new ()));
653 # endif
654 #endif
655 }
656 
657 static void
sort_and_cache_plugins()658 sort_and_cache_plugins ()
659 {
660   const GList *p;
661   static gboolean plugins_cached = FALSE;
662 
663   if (plugins_cached)
664     return;
665 
666   /* not guaranteed to have been called, but idempotent: */
667   _mcd_plugin_loader_init ();
668 
669   /* Add compiled-in plugins */
670   add_storage_plugin (MCP_ACCOUNT_STORAGE (mcd_account_manager_default_new ()));
671   add_libaccounts_plugins_if_enabled ();
672 
673   for (p = mcp_list_objects(); p != NULL; p = g_list_next (p))
674     {
675       if (MCP_IS_ACCOUNT_STORAGE (p->data))
676         {
677           McpAccountStorage *plugin = g_object_ref (p->data);
678 
679           add_storage_plugin (plugin);
680         }
681     }
682 
683   for (p = stores; p != NULL; p = g_list_next (p))
684     {
685       McpAccountStorage *plugin = p->data;
686 
687       DEBUG ("found plugin %s [%s; priority %d]\n%s",
688           mcp_account_storage_name (plugin),
689           g_type_name (G_TYPE_FROM_INSTANCE (plugin)),
690           mcp_account_storage_priority (plugin),
691           mcp_account_storage_description (plugin));
692     }
693 
694     plugins_cached = TRUE;
695 }
696 
697 void
mcd_storage_connect_signal(const gchar * signame,GCallback func,gpointer user_data)698 mcd_storage_connect_signal (const gchar *signame,
699     GCallback func,
700     gpointer user_data)
701 {
702   GList *p;
703 
704   for (p = stores; p != NULL; p = g_list_next (p))
705     {
706       McpAccountStorage *plugin = p->data;
707 
708       DEBUG ("connecting handler to %s plugin signal %s ",
709           mcp_account_storage_name (plugin), signame);
710       g_signal_connect (plugin, signame, func, user_data);
711     }
712 }
713 
714 /*
715  * mcd_storage_load:
716  * @storage: An object implementing the #McdStorage interface
717  *
718  * Load the long term account settings storage into our internal cache.
719  * Should only really be called during startup, ie before our DBus names
720  * have been claimed and other people might be relying on responses from us.
721  */
722 void
mcd_storage_load(McdStorage * self)723 mcd_storage_load (McdStorage *self)
724 {
725   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
726   GList *store = NULL;
727 
728   g_return_if_fail (MCD_IS_STORAGE (self));
729 
730   sort_and_cache_plugins ();
731 
732   store = g_list_last (stores);
733 
734   /* fetch accounts stored in plugins, in reverse priority so higher prio *
735    * plugins can overwrite lower prio ones' account data                  */
736   while (store != NULL)
737     {
738       GList *account;
739       McpAccountStorage *plugin = store->data;
740       GList *stored = mcp_account_storage_list (plugin, ma);
741       const gchar *pname = mcp_account_storage_name (plugin);
742       const gint prio = mcp_account_storage_priority (plugin);
743 
744       DEBUG ("listing from plugin %s [prio: %d]", pname, prio);
745       for (account = stored; account != NULL; account = g_list_next (account))
746         {
747           gchar *name = account->data;
748 
749           DEBUG ("fetching %s from plugin %s [prio: %d]", name, pname, prio);
750           mcd_storage_add_account_from_plugin (self, plugin, name);
751           g_free (name);
752         }
753 
754       /* already freed the contents, just need to free the list itself */
755       g_list_free (stored);
756       store = g_list_previous (store);
757     }
758 }
759 
760 /*
761  * mcd_storage_dup_accounts:
762  * @storage: An object implementing the #McdStorage interface
763  * @n: place for the number of accounts to be written to (or %NULL)
764  *
765  * Returns: a newly allocated GStrv containing the unique account names,
766  * which must be freed by the caller with g_strfreev().
767  */
768 GStrv
mcd_storage_dup_accounts(McdStorage * self,gsize * n)769 mcd_storage_dup_accounts (McdStorage *self,
770     gsize *n)
771 {
772   GPtrArray *ret = g_ptr_array_new ();
773   GHashTableIter iter;
774   gpointer k, v;
775 
776   g_hash_table_iter_init (&iter, self->accounts);
777 
778   while (g_hash_table_iter_next (&iter, &k, &v))
779     {
780       McdStorageAccount *sa = v;
781 
782       if (g_hash_table_size (sa->attributes) > 0)
783         g_ptr_array_add (ret, g_strdup (k));
784     }
785 
786   g_ptr_array_add (ret, NULL);
787   return (GStrv) g_ptr_array_free (ret, FALSE);
788 }
789 
790 /*
791  * mcd_storage_dup_attributes:
792  * @storage: An object implementing the #McdStorage interface
793  * @account: unique name of the account
794  * @n: place for the number of attributes to be written to (or %NULL)
795  *
796  * Returns: a newly allocated GStrv containing the names of all the
797  * attributes or parameters currently stored for @account. Must be
798  * freed by the caller with g_strfreev().
799  */
800 GStrv
mcd_storage_dup_attributes(McdStorage * self,const gchar * account,gsize * n)801 mcd_storage_dup_attributes (McdStorage *self,
802     const gchar *account,
803     gsize *n)
804 {
805   GPtrArray *ret = g_ptr_array_new ();
806   McdStorageAccount *sa = lookup_account (self, account);
807 
808   if (sa != NULL)
809     {
810       GHashTableIter iter;
811       gpointer k;
812 
813       g_hash_table_iter_init (&iter, sa->attributes);
814 
815       while (g_hash_table_iter_next (&iter, &k, NULL))
816         g_ptr_array_add (ret, g_strdup (k));
817     }
818 
819   g_ptr_array_add (ret, NULL);
820   return (GStrv) g_ptr_array_free (ret, FALSE);
821 }
822 
823 /*
824  * mcd_storage_get_plugin:
825  * @storage: An object implementing the #McdStorage interface
826  * @account: unique name of the account
827  *
828  * Returns: the #McpAccountStorage object which is handling the account,
829  * if any (if a new account has not yet been flushed to storage this can
830  * be %NULL).
831  *
832  * Plugins are kept in permanent storage and can never be unloaded, so
833  * the returned pointer need not be reffed or unreffed. (Indeed, it's
834  * probably safer not to)
835  */
836 McpAccountStorage *
mcd_storage_get_plugin(McdStorage * self,const gchar * account)837 mcd_storage_get_plugin (McdStorage *self,
838     const gchar *account)
839 {
840   GList *store = stores;
841   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
842   McpAccountStorage *owner = NULL;
843 
844   g_return_val_if_fail (MCD_IS_STORAGE (self), NULL);
845   g_return_val_if_fail (account != NULL, NULL);
846 
847   for (; store != NULL && owner == NULL; store = g_list_next (store))
848     {
849       McpAccountStorage *plugin = store->data;
850 
851       if (mcp_account_storage_owns (plugin, ma, account))
852         owner = plugin;
853     }
854 
855   return owner;
856 }
857 
858 /*
859  * mcd_storage_dup_string:
860  * @storage: An object implementing the #McdStorage interface
861  * @account: unique name of the account
862  * @attribute: name of the attribute to be retrieved (which must not be a
863  *  parameter)
864  *
865  * Returns: a newly allocated gchar * which must be freed with g_free().
866  */
867 gchar *
mcd_storage_dup_string(McdStorage * self,const gchar * account,const gchar * attribute)868 mcd_storage_dup_string (McdStorage *self,
869     const gchar *account,
870     const gchar *attribute)
871 {
872   GValue tmp = G_VALUE_INIT;
873   gchar *ret;
874 
875   g_return_val_if_fail (MCD_IS_STORAGE (self), NULL);
876   g_return_val_if_fail (account != NULL, NULL);
877   g_return_val_if_fail (attribute != NULL, NULL);
878   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), NULL);
879 
880   g_value_init (&tmp, G_TYPE_STRING);
881 
882   if (!mcd_storage_get_attribute (self, account, attribute, &tmp, NULL))
883     return NULL;
884 
885   ret = g_value_dup_string (&tmp);
886   g_value_unset (&tmp);
887   return ret;
888 }
889 
890 static gboolean
mcd_storage_coerce_variant_to_value(GVariant * variant,GValue * value,GError ** error)891 mcd_storage_coerce_variant_to_value (GVariant *variant,
892     GValue *value,
893     GError **error)
894 {
895   GValue tmp = G_VALUE_INIT;
896   gboolean ret;
897   gchar *escaped;
898 
899   dbus_g_value_parse_g_variant (variant, &tmp);
900 
901   if (G_VALUE_TYPE (&tmp) == G_VALUE_TYPE (value))
902     {
903       memcpy (value, &tmp, sizeof (tmp));
904       return TRUE;
905     }
906 
907   /* This is really pretty stupid but it'll do for now.
908    * FIXME: implement a better similar-type-coercion mechanism than
909    * round-tripping through a GKeyFile. */
910   escaped = mcd_keyfile_escape_value (&tmp);
911   ret = mcd_keyfile_unescape_value (escaped, value, error);
912   g_free (escaped);
913   g_value_unset (&tmp);
914   return ret;
915 }
916 
917 /*
918  * mcd_storage_get_attribute:
919  * @storage: An object implementing the #McdStorage interface
920  * @account: unique name of the account
921  * @attribute: name of the attribute to be retrieved, e.g. 'DisplayName'
922  * @value: location to return the value, initialized to the right #GType
923  * @error: a place to store any #GError<!-- -->s that occur
924  */
925 gboolean
mcd_storage_get_attribute(McdStorage * self,const gchar * account,const gchar * attribute,GValue * value,GError ** error)926 mcd_storage_get_attribute (McdStorage *self,
927     const gchar *account,
928     const gchar *attribute,
929     GValue *value,
930     GError **error)
931 {
932   McdStorageAccount *sa;
933   GVariant *variant;
934 
935   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
936   g_return_val_if_fail (account != NULL, FALSE);
937   g_return_val_if_fail (attribute != NULL, FALSE);
938   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), FALSE);
939 
940   sa = lookup_account (self, account);
941 
942   if (sa == NULL)
943     {
944       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
945           "Account %s does not exist", account);
946       return FALSE;
947     }
948 
949   variant = g_hash_table_lookup (sa->attributes, attribute);
950 
951   if (variant == NULL)
952     {
953       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
954           "Setting '%s' not stored by account %s", attribute, account);
955       return FALSE;
956     }
957 
958   return mcd_storage_coerce_variant_to_value (variant, value, error);
959 }
960 
961 /*
962  * mcd_storage_get_parameter:
963  * @storage: An object implementing the #McdStorage interface
964  * @account: unique name of the account
965  * @parameter: name of the parameter to be retrieved, e.g. 'account'
966  * @value: location to return the value, initialized to the right #GType
967  * @error: a place to store any #GError<!-- -->s that occur
968  */
969 gboolean
mcd_storage_get_parameter(McdStorage * self,const gchar * account,const gchar * parameter,GValue * value,GError ** error)970 mcd_storage_get_parameter (McdStorage *self,
971     const gchar *account,
972     const gchar *parameter,
973     GValue *value,
974     GError **error)
975 {
976   McdStorageAccount *sa;
977   const gchar *escaped;
978   GVariant *variant;
979 
980   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
981   g_return_val_if_fail (account != NULL, FALSE);
982   g_return_val_if_fail (parameter != NULL, FALSE);
983 
984   sa = lookup_account (self, account);
985 
986   if (sa == NULL)
987     {
988       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
989           "Account %s does not exist", account);
990       return FALSE;
991     }
992 
993   variant = g_hash_table_lookup (sa->parameters, parameter);
994 
995   if (variant != NULL)
996     return mcd_storage_coerce_variant_to_value (variant, value, error);
997 
998   /* OK, we don't have it as a variant. How about the keyfile-escaped
999    * version? */
1000   escaped = g_hash_table_lookup (sa->escaped_parameters, parameter);
1001 
1002   if (escaped == NULL)
1003     {
1004       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1005           "Parameter '%s' not stored by account %s", parameter, account);
1006       return FALSE;
1007     }
1008 
1009   return mcd_keyfile_unescape_value (escaped, value, error);
1010 }
1011 
1012 static gboolean
mcpa_unescape_value_from_keyfile(const McpAccountManager * unused G_GNUC_UNUSED,const gchar * escaped,GValue * value,GError ** error)1013 mcpa_unescape_value_from_keyfile (const McpAccountManager *unused G_GNUC_UNUSED,
1014     const gchar *escaped,
1015     GValue *value,
1016     GError **error)
1017 {
1018   return mcd_keyfile_unescape_value (escaped, value, error);
1019 }
1020 
1021 /*
1022  * @escaped: a keyfile-escaped string
1023  * @value: a #GValue initialized with a supported #GType
1024  * @error: used to raise an error if %FALSE is returned
1025  *
1026  * Try to interpret @escaped as a value of the type of @value. If we can,
1027  * write the resulting value into @value and return %TRUE.
1028  *
1029  * Returns: %TRUE if @escaped could be interpreted as a value of that type
1030  */
1031 gboolean
mcd_keyfile_unescape_value(const gchar * escaped,GValue * value,GError ** error)1032 mcd_keyfile_unescape_value (const gchar *escaped,
1033     GValue *value,
1034     GError **error)
1035 {
1036   GKeyFile *keyfile;
1037   gboolean ret;
1038 
1039   g_return_val_if_fail (escaped != NULL, FALSE);
1040   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
1041 
1042   keyfile = g_key_file_new ();
1043   g_key_file_set_value (keyfile, "g", "k", escaped);
1044   ret = mcd_keyfile_get_value (keyfile, "g", "k", value, error);
1045   g_key_file_free (keyfile);
1046   return ret;
1047 }
1048 
1049 /*
1050  * mcd_keyfile_get_value:
1051  * @keyfile: A #GKeyFile
1052  * @group: name of a group
1053  * @key: name of a key
1054  * @value: location to return the value, initialized to the right #GType
1055  * @error: a place to store any #GError<!-- -->s that occur
1056  */
1057 gboolean
mcd_keyfile_get_value(GKeyFile * keyfile,const gchar * group,const gchar * key,GValue * value,GError ** error)1058 mcd_keyfile_get_value (GKeyFile *keyfile,
1059     const gchar *group,
1060     const gchar *key,
1061     GValue *value,
1062     GError **error)
1063 {
1064   gboolean ret = FALSE;
1065   GType type;
1066 
1067   g_return_val_if_fail (keyfile != NULL, FALSE);
1068   g_return_val_if_fail (group != NULL, FALSE);
1069   g_return_val_if_fail (key != NULL, FALSE);
1070   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
1071 
1072   type = G_VALUE_TYPE (value);
1073 
1074   switch (type)
1075     {
1076       case G_TYPE_STRING:
1077           {
1078             gchar *v_string = g_key_file_get_string (keyfile, group,
1079                 key, error);
1080 
1081             if (v_string != NULL)
1082               {
1083                 g_value_take_string (value, v_string);
1084                 ret = TRUE;
1085               }
1086             /* else error is already set */
1087           }
1088         break;
1089 
1090       case G_TYPE_INT:
1091           {
1092             GError *e = NULL;
1093             gint v_int = g_key_file_get_integer (keyfile, group,
1094                 key, &e);
1095 
1096             if (e != NULL)
1097               {
1098                 g_propagate_error (error, e);
1099               }
1100             else
1101               {
1102                 g_value_set_int (value, v_int);
1103                 ret = TRUE;
1104               }
1105           }
1106         break;
1107 
1108       case G_TYPE_INT64:
1109           {
1110             GError *e = NULL;
1111             gint64 v_int = g_key_file_get_int64 (keyfile, group,
1112                 key, &e);
1113 
1114             if (e != NULL)
1115               {
1116                 g_propagate_error (error, e);
1117               }
1118             else
1119               {
1120                 g_value_set_int64 (value, v_int);
1121                 ret = TRUE;
1122               }
1123           }
1124         break;
1125 
1126       case G_TYPE_UINT:
1127           {
1128             GError *e = NULL;
1129             guint64 v_uint = g_key_file_get_uint64 (keyfile, group,
1130                 key, &e);
1131 
1132             if (e != NULL)
1133               {
1134                 g_propagate_error (error, e);
1135               }
1136             else if (v_uint > G_MAXUINT32)
1137               {
1138                 g_set_error (error, MCD_ACCOUNT_ERROR,
1139                     MCD_ACCOUNT_ERROR_GET_PARAMETER,
1140                     "Parameter '%s' out of range for an unsigned 32-bit "
1141                     "integer: %" G_GUINT64_FORMAT, key, v_uint);
1142               }
1143             else
1144               {
1145                 g_value_set_uint (value, v_uint);
1146                 ret = TRUE;
1147               }
1148           }
1149         break;
1150 
1151     case G_TYPE_UCHAR:
1152           {
1153             GError *e = NULL;
1154             gint v_int = g_key_file_get_integer (keyfile, group,
1155                 key, &e);
1156 
1157             if (e != NULL)
1158               {
1159                 g_propagate_error (error, e);
1160               }
1161             else if (v_int < 0 || v_int > 0xFF)
1162               {
1163                 g_set_error (error, MCD_ACCOUNT_ERROR,
1164                     MCD_ACCOUNT_ERROR_GET_PARAMETER,
1165                     "Parameter '%s' out of range for an unsigned byte: "
1166                     "%d", key, v_int);
1167               }
1168             else
1169               {
1170                 g_value_set_uchar (value, v_int);
1171                 ret = TRUE;
1172               }
1173           }
1174         break;
1175 
1176       case G_TYPE_UINT64:
1177           {
1178             GError *e = NULL;
1179             guint64 v_uint = g_key_file_get_uint64 (keyfile, group,
1180                 key, &e);
1181 
1182             if (e != NULL)
1183               {
1184                 g_propagate_error (error, e);
1185               }
1186             else
1187               {
1188                 g_value_set_uint64 (value, v_uint);
1189                 ret = TRUE;
1190               }
1191           }
1192         break;
1193 
1194       case G_TYPE_BOOLEAN:
1195           {
1196             GError *e = NULL;
1197             gboolean v_bool = g_key_file_get_boolean (keyfile, group,
1198                 key, &e);
1199 
1200             if (e != NULL)
1201               {
1202                 g_propagate_error (error, e);
1203               }
1204             else
1205               {
1206                 g_value_set_boolean (value, v_bool);
1207                 ret = TRUE;
1208               }
1209           }
1210         break;
1211 
1212       case G_TYPE_DOUBLE:
1213           {
1214             GError *e = NULL;
1215             gdouble v_double = g_key_file_get_double (keyfile, group,
1216                 key, &e);
1217 
1218             if (e != NULL)
1219               {
1220                 g_propagate_error (error, e);
1221               }
1222             else
1223               {
1224                 g_value_set_double (value, v_double);
1225                 ret = TRUE;
1226               }
1227           }
1228         break;
1229 
1230       default:
1231         if (type == G_TYPE_STRV)
1232           {
1233             gchar **v = g_key_file_get_string_list (keyfile, group,
1234                 key, NULL, error);
1235 
1236             if (v != NULL)
1237               {
1238                 g_value_take_boxed (value, v);
1239                 ret = TRUE;
1240               }
1241           }
1242         else if (type == DBUS_TYPE_G_OBJECT_PATH)
1243           {
1244             gchar *v_string = g_key_file_get_string (keyfile, group,
1245                 key, error);
1246 
1247             if (v_string == NULL)
1248               {
1249                 /* do nothing, error is already set */
1250               }
1251             else if (!tp_dbus_check_valid_object_path (v_string, NULL))
1252               {
1253                 g_set_error (error, MCD_ACCOUNT_ERROR,
1254                     MCD_ACCOUNT_ERROR_GET_PARAMETER,
1255                     "Invalid object path %s", v_string);
1256                 g_free (v_string);
1257               }
1258             else
1259               {
1260                 g_value_take_boxed (value, v_string);
1261                 ret = TRUE;
1262               }
1263           }
1264         else if (type == TP_ARRAY_TYPE_OBJECT_PATH_LIST)
1265           {
1266             gchar **v = g_key_file_get_string_list (keyfile, group,
1267                 key, NULL, error);
1268 
1269             if (v != NULL)
1270               {
1271                 gchar **iter;
1272                 GPtrArray *arr = g_ptr_array_new ();
1273 
1274                 for (iter = v; iter != NULL && *iter != NULL; iter++)
1275                   {
1276                     if (!g_variant_is_object_path (*iter))
1277                       {
1278                         g_set_error (error, MCD_ACCOUNT_ERROR,
1279                             MCD_ACCOUNT_ERROR_GET_PARAMETER,
1280                             "Invalid object path %s stored in keyfile", *iter);
1281                         g_strfreev (v);
1282                         v = NULL;
1283                         break;
1284                       }
1285                   }
1286 
1287                 for (iter = v; iter != NULL && *iter != NULL; iter++)
1288                   {
1289                     /* transfer ownership from v to arr */
1290                     g_ptr_array_add (arr, *iter);
1291                   }
1292 
1293                 /* not g_strfreev - the strings' ownership has been
1294                  * transferred */
1295                 g_free (v);
1296 
1297                 g_value_take_boxed (value, arr);
1298                 ret = TRUE;
1299               }
1300           }
1301         else if (type == TP_STRUCT_TYPE_SIMPLE_PRESENCE)
1302           {
1303             gchar **v = g_key_file_get_string_list (keyfile, group,
1304                 key, NULL, error);
1305 
1306             if (v == NULL)
1307               {
1308                 /* error is already set, do nothing */
1309               }
1310             else if (g_strv_length (v) != 3)
1311               {
1312                 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1313                     "Invalid simple-presence structure stored in keyfile");
1314               }
1315             else
1316               {
1317                 guint64 u;
1318                 gchar *endptr;
1319 
1320                 errno = 0;
1321                 u = g_ascii_strtoull (v[0], &endptr, 10);
1322 
1323                 if (errno != 0 || *endptr != '\0' || u > G_MAXUINT32)
1324                   {
1325                     g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1326                         "Invalid presence type stored in keyfile: %s", v[0]);
1327                   }
1328                 else
1329                   {
1330                     /* a syntactically valid simple presence */
1331                     g_value_take_boxed (value,
1332                         tp_value_array_build (3,
1333                           G_TYPE_UINT, (guint) u,
1334                           G_TYPE_STRING, v[1],
1335                           G_TYPE_STRING, v[2],
1336                           G_TYPE_INVALID));
1337                     ret = TRUE;
1338                   }
1339               }
1340 
1341             g_strfreev (v);
1342           }
1343         else
1344           {
1345             gchar *message =
1346               g_strdup_printf ("cannot get key %s from group %s: "
1347                   "unknown type %s",
1348                   key, group, g_type_name (type));
1349 
1350             g_warning ("%s: %s", G_STRFUNC, message);
1351             g_set_error (error, MCD_ACCOUNT_ERROR,
1352                 MCD_ACCOUNT_ERROR_GET_PARAMETER,
1353                 "%s", message);
1354             g_free (message);
1355           }
1356     }
1357 
1358   return ret;
1359 }
1360 
1361 /*
1362  * mcd_storage_get_boolean:
1363  * @storage: An object implementing the #McdStorage interface
1364  * @account: unique name of the account
1365  * @key: name of the attribute to be retrieved
1366  *
1367  * Returns: a #gboolean. Unset/unparseable values are returned as %FALSE
1368  */
1369 gboolean
mcd_storage_get_boolean(McdStorage * self,const gchar * account,const gchar * attribute)1370 mcd_storage_get_boolean (McdStorage *self,
1371     const gchar *account,
1372     const gchar *attribute)
1373 {
1374   GValue tmp = G_VALUE_INIT;
1375 
1376   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
1377   g_return_val_if_fail (account != NULL, FALSE);
1378   g_return_val_if_fail (attribute != NULL, FALSE);
1379   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), FALSE);
1380 
1381   g_value_init (&tmp, G_TYPE_BOOLEAN);
1382 
1383   if (!mcd_storage_get_attribute (self, account, attribute, &tmp, NULL))
1384     return FALSE;
1385 
1386   return g_value_get_boolean (&tmp);
1387 }
1388 
1389 /*
1390  * mcd_storage_get_integer:
1391  * @storage: An object implementing the #McdStorage interface
1392  * @account: unique name of the account
1393  * @attribute: name of the attribute to be retrieved
1394  *
1395  * Returns: a #gint. Unset or non-numeric values are returned as 0
1396  */
1397 gint
mcd_storage_get_integer(McdStorage * self,const gchar * account,const gchar * attribute)1398 mcd_storage_get_integer (McdStorage *self,
1399     const gchar *account,
1400     const gchar *attribute)
1401 {
1402   GValue tmp = G_VALUE_INIT;
1403 
1404   g_return_val_if_fail (MCD_IS_STORAGE (self), 0);
1405   g_return_val_if_fail (account != NULL, 0);
1406   g_return_val_if_fail (attribute != NULL, 0);
1407   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), 0);
1408 
1409   g_value_init (&tmp, G_TYPE_INT);
1410 
1411   if (!mcd_storage_get_attribute (self, account, attribute, &tmp, NULL))
1412     return FALSE;
1413 
1414   return g_value_get_int (&tmp);
1415 }
1416 
1417 static void
update_storage(McdStorage * self,const gchar * account,const gchar * key,GVariant * variant,const gchar * escaped,gboolean secret)1418 update_storage (McdStorage *self,
1419     const gchar *account,
1420     const gchar *key,
1421     GVariant *variant,
1422     const gchar *escaped,
1423     gboolean secret)
1424 {
1425   GList *store;
1426   gboolean done = FALSE;
1427   gboolean parameter = g_str_has_prefix (key, "param-");
1428   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
1429 
1430   if (secret)
1431     mcd_storage_make_secret (self, account, key);
1432 
1433   /* we're deleting, which is unconditional, no need to check if anyone *
1434    * claims this setting for themselves                                 */
1435   if (escaped == NULL)
1436     done = TRUE;
1437 
1438   for (store = stores; store != NULL; store = g_list_next (store))
1439     {
1440       McpAccountStorage *plugin = store->data;
1441       const gchar *pn = mcp_account_storage_name (plugin);
1442 
1443       if (done)
1444         {
1445           DEBUG ("MCP:%s -> delete %s.%s", pn, account, key);
1446           mcp_account_storage_delete (plugin, ma, account, key);
1447         }
1448       else if (variant != NULL && !parameter &&
1449           mcp_account_storage_set_attribute (plugin, ma, account, key, variant,
1450             MCP_ATTRIBUTE_FLAG_NONE))
1451         {
1452           done = TRUE;
1453           DEBUG ("MCP:%s -> store attribute %s.%s", pn, account, key);
1454         }
1455       else if (variant != NULL && parameter &&
1456           mcp_account_storage_set_parameter (plugin, ma, account, key + 6,
1457             variant,
1458             secret ? MCP_PARAMETER_FLAG_SECRET : MCP_PARAMETER_FLAG_NONE))
1459         {
1460           done = TRUE;
1461           DEBUG ("MCP:%s -> store parameter %s.%s", pn, account, key);
1462         }
1463       else
1464         {
1465           done = mcp_account_storage_set (plugin, ma, account, key, escaped);
1466           DEBUG ("MCP:%s -> %s %s.%s",
1467               pn, done ? "store" : "ignore", account, key);
1468         }
1469     }
1470 }
1471 
1472 /*
1473  * mcd_storage_set_string:
1474  * @storage: An object implementing the #McdStorage interface
1475  * @account: the unique name of an account
1476  * @key: the name of the attribute
1477  * @value: the value to be stored (or %NULL to erase it)
1478  *
1479  * Copies and stores the supplied @value (or removes it if %NULL) to the
1480  * internal cache.
1481  *
1482  * Returns: a #gboolean indicating whether the cache actually required an
1483  * update (so that the caller can decide whether to request a commit to
1484  * long term storage or not). %TRUE indicates the cache was updated and
1485  * may not be in sync with the store any longer, %FALSE indicates we already
1486  * held the value supplied.
1487  */
1488 gboolean
mcd_storage_set_string(McdStorage * self,const gchar * account,const gchar * attribute,const gchar * val)1489 mcd_storage_set_string (McdStorage *self,
1490     const gchar *account,
1491     const gchar *attribute,
1492     const gchar *val)
1493 {
1494   gboolean updated;
1495 
1496   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
1497   g_return_val_if_fail (account != NULL, FALSE);
1498   g_return_val_if_fail (attribute != NULL, FALSE);
1499   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), FALSE);
1500 
1501   if (val == NULL)
1502     {
1503       updated = mcd_storage_set_attribute (self, account, attribute, NULL);
1504     }
1505   else
1506     {
1507       GValue tmp = G_VALUE_INIT;
1508 
1509       g_value_init (&tmp, G_TYPE_STRING);
1510       g_value_set_string (&tmp, val);
1511       updated = mcd_storage_set_attribute (self, account, attribute, &tmp);
1512       g_value_unset (&tmp);
1513     }
1514 
1515   return updated;
1516 }
1517 
1518 /*
1519  * mcd_storage_set_attribute:
1520  * @storage: An object implementing the #McdStorage interface
1521  * @account: the unique name of an account
1522  * @attribute: the name of the attribute, e.g. "DisplayName"
1523  * @value: the value to be stored (or %NULL to erase it)
1524  *
1525  * Copies and stores the supplied @value (or removes it if %NULL) in the
1526  * internal cache.
1527  *
1528  * Returns: a #gboolean indicating whether the cache actually required an
1529  * update (so that the caller can decide whether to request a commit to
1530  * long term storage or not). %TRUE indicates the cache was updated and
1531  * may not be in sync with the store any longer, %FALSE indicates we already
1532  * held the value supplied.
1533  */
1534 gboolean
mcd_storage_set_attribute(McdStorage * self,const gchar * account,const gchar * attribute,const GValue * value)1535 mcd_storage_set_attribute (McdStorage *self,
1536     const gchar *account,
1537     const gchar *attribute,
1538     const GValue *value)
1539 {
1540   McdStorageAccount *sa;
1541   GVariant *old_v;
1542   GVariant *new_v;
1543   gboolean updated = FALSE;
1544 
1545   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
1546   g_return_val_if_fail (account != NULL, FALSE);
1547   g_return_val_if_fail (attribute != NULL, FALSE);
1548   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), FALSE);
1549 
1550   sa = ensure_account (self, account);
1551 
1552   if (value != NULL)
1553     new_v = g_variant_ref_sink (dbus_g_value_build_g_variant (value));
1554   else
1555     new_v = NULL;
1556 
1557   old_v = g_hash_table_lookup (sa->attributes, attribute);
1558 
1559   if (!mcd_nullable_variant_equal (old_v, new_v))
1560     {
1561       gchar *escaped = NULL;
1562 
1563       /* First put it in the attributes hash table. (Watch out, this might
1564        * invalidate old_v.) */
1565       if (new_v == NULL)
1566         g_hash_table_remove (sa->attributes, attribute);
1567       else
1568         g_hash_table_insert (sa->attributes, g_strdup (attribute),
1569             g_variant_ref (new_v));
1570 
1571       /* OK now we have to escape it in a stupid way for plugins */
1572       if (value != NULL)
1573         escaped = mcd_keyfile_escape_value (value);
1574 
1575       update_storage (self, account, attribute, new_v, escaped, FALSE);
1576       g_free (escaped);
1577       updated = TRUE;
1578     }
1579 
1580   tp_clear_pointer (&new_v, g_variant_unref);
1581   return updated;
1582 }
1583 
1584 /*
1585  * mcd_storage_set_parameter:
1586  * @storage: An object implementing the #McdStorage interface
1587  * @account: the unique name of an account
1588  * @parameter: the name of the parameter, e.g. "account"
1589  * @value: the value to be stored (or %NULL to erase it)
1590  * @secret: whether the value is confidential (might get stored in the
1591  * keyring, for example)
1592  *
1593  * Copies and stores the supplied @value (or removes it if %NULL) in the
1594  * internal cache.
1595  *
1596  * Returns: a #gboolean indicating whether the cache actually required an
1597  * update (so that the caller can decide whether to request a commit to
1598  * long term storage or not). %TRUE indicates the cache was updated and
1599  * may not be in sync with the store any longer, %FALSE indicates we already
1600  * held the value supplied.
1601  */
1602 gboolean
mcd_storage_set_parameter(McdStorage * self,const gchar * account,const gchar * parameter,const GValue * value,gboolean secret)1603 mcd_storage_set_parameter (McdStorage *self,
1604     const gchar *account,
1605     const gchar *parameter,
1606     const GValue *value,
1607     gboolean secret)
1608 {
1609   GVariant *old_v;
1610   GVariant *new_v = NULL;
1611   const gchar *old_escaped;
1612   gchar *new_escaped = NULL;
1613   McdStorageAccount *sa;
1614   gboolean updated = FALSE;
1615 
1616   g_return_val_if_fail (MCD_IS_STORAGE (self), FALSE);
1617   g_return_val_if_fail (account != NULL, FALSE);
1618   g_return_val_if_fail (parameter != NULL, FALSE);
1619 
1620   sa = ensure_account (self, account);
1621 
1622   if (value != NULL)
1623     {
1624       new_escaped = mcd_keyfile_escape_value (value);
1625       new_v = g_variant_ref_sink (dbus_g_value_build_g_variant (value));
1626     }
1627 
1628   old_v = g_hash_table_lookup (sa->parameters, parameter);
1629   old_escaped = g_hash_table_lookup (sa->escaped_parameters, parameter);
1630 
1631   if (old_v != NULL)
1632     updated = !mcd_nullable_variant_equal (old_v, new_v);
1633   else if (old_escaped != NULL)
1634     updated = tp_strdiff (old_escaped, new_escaped);
1635   else
1636     updated = (value != NULL);
1637 
1638   if (updated)
1639     {
1640       gchar key[MAX_KEY_LENGTH];
1641 
1642       g_hash_table_remove (sa->parameters, parameter);
1643       g_hash_table_remove (sa->escaped_parameters, parameter);
1644 
1645       if (new_v != NULL)
1646         g_hash_table_insert (sa->parameters, g_strdup (parameter),
1647             g_variant_ref (new_v));
1648 
1649       g_snprintf (key, sizeof (key), "param-%s", parameter);
1650       update_storage (self, account, key, new_v, new_escaped, secret);
1651       return TRUE;
1652     }
1653 
1654   g_free (new_escaped);
1655   tp_clear_pointer (&new_v, g_variant_unref);
1656   return updated;
1657 }
1658 
1659 static gchar *
mcpa_escape_value_for_keyfile(const McpAccountManager * unused G_GNUC_UNUSED,const GValue * value)1660 mcpa_escape_value_for_keyfile (const McpAccountManager *unused G_GNUC_UNUSED,
1661     const GValue *value)
1662 {
1663   return mcd_keyfile_escape_value (value);
1664 }
1665 
1666 /*
1667  * @value: a populated #GValue of a supported #GType
1668  *
1669  * Escape the contents of @value to go in a #GKeyFile. Return the
1670  * value that would go in the keyfile.
1671  *
1672  * For instance, for a boolean value TRUE this would return "true",
1673  * and for a string containing one space, it would return "\s".
1674  */
1675 gchar *
mcd_keyfile_escape_value(const GValue * value)1676 mcd_keyfile_escape_value (const GValue *value)
1677 {
1678   GKeyFile *keyfile;
1679   gchar *ret;
1680 
1681   g_return_val_if_fail (G_IS_VALUE (value), NULL);
1682 
1683   keyfile = g_key_file_new ();
1684   mcd_keyfile_set_value (keyfile, "g", "k", value);
1685   ret = g_key_file_get_value (keyfile, "g", "k", NULL);
1686   g_key_file_free (keyfile);
1687   return ret;
1688 }
1689 
1690 static gchar *
mcpa_escape_variant_for_keyfile(const McpAccountManager * unused G_GNUC_UNUSED,GVariant * variant)1691 mcpa_escape_variant_for_keyfile (const McpAccountManager *unused G_GNUC_UNUSED,
1692     GVariant *variant)
1693 {
1694   return mcd_keyfile_escape_variant (variant);
1695 }
1696 
1697 /*
1698  * mcd_keyfile_set_value:
1699  * @keyfile: a keyfile
1700  * @name: the name of a group
1701  * @key: the key in the group
1702  * @value: the value to be stored (or %NULL to erase it)
1703  *
1704  * Copies and stores the supplied @value (or removes it if %NULL) to the
1705  * internal cache.
1706  *
1707  * Returns: a #gboolean indicating whether the cache actually required an
1708  * update (so that the caller can decide whether to request a commit to
1709  * long term storage or not). %TRUE indicates the cache was updated and
1710  * may not be in sync with the store any longer, %FALSE indicates we already
1711  * held the value supplied.
1712  */
1713 gboolean
mcd_keyfile_set_value(GKeyFile * keyfile,const gchar * name,const gchar * key,const GValue * value)1714 mcd_keyfile_set_value (GKeyFile *keyfile,
1715     const gchar *name,
1716     const gchar *key,
1717     const GValue *value)
1718 {
1719   g_return_val_if_fail (name != NULL, FALSE);
1720   g_return_val_if_fail (key != NULL, FALSE);
1721 
1722   if (value == NULL)
1723     {
1724       gchar *old = g_key_file_get_value (keyfile, name, key, NULL);
1725       gboolean updated = (old != NULL);
1726 
1727       g_free (old);
1728       g_key_file_remove_key (keyfile, name, key, NULL);
1729       return updated;
1730     }
1731   else
1732     {
1733       gboolean updated = FALSE;
1734       gchar *old = g_key_file_get_value (keyfile, name, key, NULL);
1735       gchar *new = NULL;
1736       gchar *buf = NULL;
1737 
1738       switch (G_VALUE_TYPE (value))
1739         {
1740           case G_TYPE_STRING:
1741             g_key_file_set_string (keyfile, name, key,
1742                 g_value_get_string (value));
1743             break;
1744 
1745           case G_TYPE_UINT:
1746             buf = g_strdup_printf ("%u", g_value_get_uint (value));
1747             break;
1748 
1749           case G_TYPE_INT:
1750             g_key_file_set_integer (keyfile, name, key,
1751                 g_value_get_int (value));
1752             break;
1753 
1754           case G_TYPE_BOOLEAN:
1755             g_key_file_set_boolean (keyfile, name, key,
1756                 g_value_get_boolean (value));
1757             break;
1758 
1759           case G_TYPE_UCHAR:
1760             buf = g_strdup_printf ("%u", g_value_get_uchar (value));
1761             break;
1762 
1763           case G_TYPE_UINT64:
1764             buf = g_strdup_printf ("%" G_GUINT64_FORMAT,
1765                                    g_value_get_uint64 (value));
1766             break;
1767 
1768           case G_TYPE_INT64:
1769             buf = g_strdup_printf ("%" G_GINT64_FORMAT,
1770                                    g_value_get_int64 (value));
1771             break;
1772 
1773           case G_TYPE_DOUBLE:
1774             g_key_file_set_double (keyfile, name, key,
1775                 g_value_get_double (value));
1776             break;
1777 
1778           default:
1779             if (G_VALUE_HOLDS (value, G_TYPE_STRV))
1780               {
1781                 gchar **strings = g_value_get_boxed (value);
1782 
1783                 g_key_file_set_string_list (keyfile, name, key,
1784                     (const gchar **)strings,
1785                     g_strv_length (strings));
1786               }
1787             else if (G_VALUE_HOLDS (value, DBUS_TYPE_G_OBJECT_PATH))
1788               {
1789                 g_key_file_set_string (keyfile, name, key,
1790                     g_value_get_boxed (value));
1791               }
1792             else if (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST))
1793               {
1794                 GPtrArray *arr = g_value_get_boxed (value);
1795 
1796                 g_key_file_set_string_list (keyfile, name, key,
1797                     (const gchar * const *) arr->pdata, arr->len);
1798               }
1799             else if (G_VALUE_HOLDS (value, TP_STRUCT_TYPE_SIMPLE_PRESENCE))
1800               {
1801                 guint type;
1802                 /* enough for "4294967296" + \0 */
1803                 gchar printf_buf[11];
1804                 const gchar * strv[4] = { NULL, NULL, NULL, NULL };
1805 
1806                 tp_value_array_unpack (g_value_get_boxed (value), 3,
1807                     &type,
1808                     &(strv[1]),
1809                     &(strv[2]));
1810                 g_snprintf (printf_buf, sizeof (printf_buf), "%u", type);
1811                 strv[0] = printf_buf;
1812 
1813                 g_key_file_set_string_list (keyfile, name, key, strv, 3);
1814               }
1815             else
1816               {
1817                 g_warning ("Unexpected param type %s",
1818                     G_VALUE_TYPE_NAME (value));
1819                 return FALSE;
1820               }
1821         }
1822 
1823       if (buf != NULL)
1824         g_key_file_set_string (keyfile, name, key, buf);
1825 
1826       new = g_key_file_get_value (keyfile, name, key, NULL);
1827 
1828       if (tp_strdiff (old, new))
1829         updated = TRUE;
1830 
1831       g_free (new);
1832       g_free (buf);
1833       g_free (old);
1834 
1835       return updated;
1836     }
1837 }
1838 
1839 /*
1840  * mcd_storage_create_account:
1841  * @storage: An object implementing the #McdStorage interface
1842  * @provider: the desired storage provider, or %NULL
1843  * @manager: the name of the manager
1844  * @protocol: the name of the protocol
1845  * @params: A gchar * / GValue * hash table of account parameters
1846  * @error: a #GError to fill when returning %NULL
1847  *
1848  * Create a new account in storage. This should not store any
1849  * information on the long term storage until mcd_storage_commit() is called.
1850  *
1851  * See mcp_account_storage_create().
1852  *
1853  * Returns: the unique name to use for the new account, or %NULL on error.
1854  */
1855 gchar *
mcd_storage_create_account(McdStorage * self,const gchar * provider,const gchar * manager,const gchar * protocol,GHashTable * params,GError ** error)1856 mcd_storage_create_account (McdStorage *self,
1857     const gchar *provider,
1858     const gchar *manager,
1859     const gchar *protocol,
1860     GHashTable *params,
1861     GError **error)
1862 {
1863   GList *store;
1864   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
1865 
1866   g_return_val_if_fail (MCD_IS_STORAGE (self), NULL);
1867   g_return_val_if_fail (!tp_str_empty (manager), NULL);
1868   g_return_val_if_fail (!tp_str_empty (protocol), NULL);
1869 
1870   /* If a storage provider is specified, use only it or fail */
1871   if (provider != NULL)
1872     {
1873       for (store = stores; store != NULL; store = g_list_next (store))
1874         {
1875           McpAccountStorage *plugin = store->data;
1876 
1877           if (!tp_strdiff (mcp_account_storage_provider (plugin), provider))
1878             {
1879               return mcp_account_storage_create (plugin, ma, manager,
1880                   protocol, params, error);
1881             }
1882         }
1883 
1884       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1885           "Storage provider '%s' does not exist", provider);
1886 
1887       return NULL;
1888     }
1889 
1890   /* No provider specified, let's pick the first plugin able to create this
1891    * account in priority order.
1892    *
1893    * FIXME: This is rather subtle, and relies on the fact that accounts
1894    * aren't always strongly tied to a single plugin.
1895    *
1896    * For plugins that only store their accounts set up specifically
1897    * through them (like the libaccounts/SSO pseudo-plugin,
1898    * McdAccountManagerSSO), create() will fail as unimplemented,
1899    * and we'll fall through to the next plugin. Eventually we'll
1900    * reach the default keyfile+gnome-keyring plugin, or another
1901    * plugin that accepts arbitrary accounts. When set() is called,
1902    * the libaccounts/SSO plugin will reject that too, and again,
1903    * we'll fall through to a plugin that accepts arbitrary
1904    * accounts.
1905    *
1906    * Plugins that will accept arbitrary accounts being created
1907    * via D-Bus (like the default keyfile+gnome-keyring plugin,
1908    * and the account-diversion plugin in tests/twisted)
1909    * should, in principle, implement create() to be successful.
1910    * If they do, their create() will succeed, and later, so will
1911    * their set().
1912    *
1913    * We can't necessarily rely on all such plugins implementing
1914    * create(), because it isn't a mandatory part of the plugin
1915    * API (it was added later). However, as it happens, the
1916    * default plugin returns successfully from create() without
1917    * really doing anything. When we iterate through the accounts again
1918    * to call set(), higher-priority plugins are given a second
1919    * chance to intercept that; so we end up with create() in
1920    * the default plugin being followed by set() from the
1921    * higher-priority plugin. In theory that's bad because it
1922    * splits the account across two plugins, but in practice
1923    * it isn't a problem because the default plugin's create()
1924    * doesn't really do anything anyway.
1925    */
1926   for (store = stores; store != NULL; store = g_list_next (store))
1927     {
1928       McpAccountStorage *plugin = store->data;
1929       gchar *ret;
1930 
1931       ret = mcp_account_storage_create (plugin, ma, manager, protocol, params,
1932           error);
1933 
1934       if (ret != NULL)
1935         return ret;
1936 
1937       g_clear_error (error);
1938     }
1939 
1940   /* This should never happen since the default storage is always able to create
1941    * an account */
1942   g_warn_if_reached ();
1943   g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1944       "None of the storage provider are able to create the account");
1945 
1946   return NULL;
1947 }
1948 
1949 
1950 /*
1951  * mcd_storage_delete_account:
1952  * @storage: An object implementing the #McdStorage interface
1953  * @account: unique name of the account
1954  *
1955  * Removes an account's settings from long term storage.
1956  * This does not handle any of the other logic to do with removing
1957  * accounts, it merely ensures that no trace of the account remains
1958  * in long term storage once mcd_storage_commit() has been called.
1959  */
1960 void
mcd_storage_delete_account(McdStorage * self,const gchar * account)1961 mcd_storage_delete_account (McdStorage *self,
1962     const gchar *account)
1963 {
1964   GList *store;
1965   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
1966 
1967   g_return_if_fail (MCD_IS_STORAGE (self));
1968   g_return_if_fail (account != NULL);
1969 
1970   g_hash_table_remove (self->accounts, account);
1971 
1972   for (store = stores; store != NULL; store = g_list_next (store))
1973     {
1974       McpAccountStorage *plugin = store->data;
1975 
1976       mcp_account_storage_delete (plugin, ma, account, NULL);
1977     }
1978 }
1979 
1980 /*
1981  * mcd_storage_commit:
1982  * @storage: An object implementing the #McdStorage interface
1983  * @account: the unique name of an account
1984  *
1985  * Sync the long term storage (whatever it might be) with the current
1986  * state of our internal cache.
1987  */
1988 void
mcd_storage_commit(McdStorage * self,const gchar * account)1989 mcd_storage_commit (McdStorage *self, const gchar *account)
1990 {
1991   GList *store;
1992   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
1993 
1994   g_return_if_fail (MCD_IS_STORAGE (self));
1995 
1996   for (store = stores; store != NULL; store = g_list_next (store))
1997     {
1998       McpAccountStorage *plugin = store->data;
1999       const gchar *pname = mcp_account_storage_name (plugin);
2000 
2001       if (account != NULL)
2002         {
2003           DEBUG ("flushing plugin %s %s to long term storage", pname, account);
2004           mcp_account_storage_commit_one (plugin, ma, account);
2005         }
2006       else
2007         {
2008           DEBUG ("flushing plugin %s to long term storage", pname);
2009           mcp_account_storage_commit (plugin, ma);
2010         }
2011     }
2012 }
2013 
2014 /*
2015  * mcd_storage_set_strv:
2016  * @storage: An object implementing the #McdStorage interface
2017  * @account: the unique name of an account
2018  * @attribute: the name of the attribute
2019  * @strv: the string vector to be stored (where %NULL is treated as equivalent
2020  * to an empty vector)
2021  *
2022  * Copies and stores the supplied string vector to the internal cache.
2023  *
2024  * Returns: a #gboolean indicating whether the cache actually required an
2025  * update (so that the caller can decide whether to request a commit to
2026  * long term storage or not). %TRUE indicates the cache was updated and
2027  * may not be in sync with the store any longer, %FALSE indicates we already
2028  * held the value supplied.
2029  */
2030 gboolean
mcd_storage_set_strv(McdStorage * storage,const gchar * account,const gchar * attribute,const gchar * const * strv)2031 mcd_storage_set_strv (McdStorage *storage,
2032     const gchar *account,
2033     const gchar *attribute,
2034     const gchar * const *strv)
2035 {
2036   GValue v = G_VALUE_INIT;
2037   static const gchar * const *empty = { NULL };
2038   gboolean ret;
2039 
2040   g_return_val_if_fail (MCD_IS_STORAGE (storage), FALSE);
2041   g_return_val_if_fail (account != NULL, FALSE);
2042   g_return_val_if_fail (attribute != NULL, FALSE);
2043   g_return_val_if_fail (!g_str_has_prefix (attribute, "param-"), FALSE);
2044 
2045   g_value_init (&v, G_TYPE_STRV);
2046   g_value_set_static_boxed (&v, strv == NULL ? empty : strv);
2047   ret = mcd_storage_set_attribute (storage, account, attribute, &v);
2048   g_value_unset (&v);
2049   return ret;
2050 }
2051 
2052 void
mcd_storage_ready(McdStorage * self)2053 mcd_storage_ready (McdStorage *self)
2054 {
2055   GList *store;
2056   McpAccountManager *ma = MCP_ACCOUNT_MANAGER (self);
2057 
2058   for (store = stores; store != NULL; store = g_list_next (store))
2059     {
2060       McpAccountStorage *plugin = store->data;
2061       const gchar *plugin_name = mcp_account_storage_name (plugin);
2062 
2063       DEBUG ("Unblocking async account ops by %s", plugin_name);
2064       mcp_account_storage_ready (plugin, ma);
2065     }
2066 }
2067 
2068 static void
plugin_iface_init(McpAccountManagerIface * iface,gpointer unused G_GNUC_UNUSED)2069 plugin_iface_init (McpAccountManagerIface *iface,
2070     gpointer unused G_GNUC_UNUSED)
2071 {
2072   DEBUG ();
2073 
2074   iface->get_value = get_value;
2075   iface->set_value = set_value;
2076   iface->set_attribute = mcpa_set_attribute;
2077   iface->set_parameter = mcpa_set_parameter;
2078   iface->is_secret = is_secret;
2079   iface->make_secret = make_secret;
2080   iface->unique_name = unique_name;
2081   iface->list_keys = list_keys;
2082   iface->escape_value_for_keyfile = mcpa_escape_value_for_keyfile;
2083   iface->escape_variant_for_keyfile = mcpa_escape_variant_for_keyfile;
2084   iface->unescape_value_from_keyfile = mcpa_unescape_value_from_keyfile;
2085   iface->init_value_for_attribute = mcpa_init_value_for_attribute;
2086 }
2087 
2088 gboolean
mcd_storage_add_account_from_plugin(McdStorage * self,McpAccountStorage * plugin,const gchar * account)2089 mcd_storage_add_account_from_plugin (McdStorage *self,
2090     McpAccountStorage *plugin,
2091     const gchar *account)
2092 {
2093   if (!mcp_account_storage_get (plugin, MCP_ACCOUNT_MANAGER (self),
2094       account, NULL))
2095     {
2096       g_warning ("plugin %s disowned account %s",
2097                  mcp_account_storage_name (plugin), account);
2098       return FALSE;
2099     }
2100 
2101   return TRUE;
2102 }
2103