1 /* vi: set et sw=4 ts=8 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */
3 /*
4  * This file is part of mission-control
5  *
6  * Copyright © 2007-2011 Nokia Corporation.
7  * Copyright © 2009-2012 Collabora Ltd.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24 #include "config.h"
25 
26 #include "mcd-account-manager.h"
27 
28 #include <string.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <dbus/dbus-glib-lowlevel.h>
32 #include <dbus/dbus.h>
33 
34 #include <telepathy-glib/telepathy-glib.h>
35 #include <telepathy-glib/telepathy-glib-dbus.h>
36 
37 #include "mcd-account-manager-priv.h"
38 #include "mcd-storage.h"
39 
40 #include "mcd-account.h"
41 #include "mcd-account-config.h"
42 #include "mcd-account-priv.h"
43 #include "mcd-connection-priv.h"
44 #include "mcd-dbusprop.h"
45 #include "mcd-master-priv.h"
46 #include "mcd-misc.h"
47 #include "mcd-storage.h"
48 #include "mission-control-plugins/mission-control-plugins.h"
49 #include "mission-control-plugins/implementation.h"
50 #include "plugin-loader.h"
51 
52 #include "_gen/interfaces.h"
53 
54 #define PARAM_PREFIX "param-"
55 #define WRITE_CONF_DELAY    500
56 
57 #define MCD_ACCOUNT_MANAGER_PRIV(account_manager) \
58     (MCD_ACCOUNT_MANAGER (account_manager)->priv)
59 
60 static void account_manager_iface_init (TpSvcAccountManagerClass *iface,
61 					gpointer iface_data);
62 static void account_manager_hidden_iface_init (
63     McSvcAccountManagerInterfaceHiddenClass *iface,
64     gpointer iface_data);
65 static void properties_iface_init (TpSvcDBusPropertiesClass *iface,
66 				   gpointer iface_data);
67 
68 static void _mcd_account_manager_constructed (GObject *obj);
69 
70 static const McdDBusProp account_manager_properties[];
71 static const McdDBusProp account_manager_hidden_properties[];
72 
73 static const McdInterfaceData account_manager_interfaces[] = {
74     MCD_IMPLEMENT_IFACE (tp_svc_account_manager_get_type,
75 			 account_manager,
76 			 TP_IFACE_ACCOUNT_MANAGER),
77     MCD_IMPLEMENT_IFACE (mc_svc_account_manager_interface_hidden_get_type,
78 			 account_manager_hidden,
79 			 MC_IFACE_ACCOUNT_MANAGER_INTERFACE_HIDDEN),
80     { NULL, }
81 };
82 
83 G_DEFINE_TYPE_WITH_CODE (McdAccountManager, mcd_account_manager, G_TYPE_OBJECT,
84 			 MCD_DBUS_INIT_INTERFACES (account_manager_interfaces);
85 			 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
86 						properties_iface_init);
87 			)
88 
89 struct _McdAccountManagerPrivate
90 {
91     TpDBusDaemon *dbus_daemon;
92     TpSimpleClientFactory *client_factory;
93     McdConnectivityMonitor *minotaur;
94 
95     McdStorage *storage;
96     GHashTable *accounts;
97 
98     gchar *account_connections_dir;  /* directory for temporary file */
99     gchar *account_connections_file; /* in account_connections_dir */
100 
101     gboolean dbus_registered;
102 };
103 
104 typedef struct
105 {
106     McdAccountManager *account_manager;
107     McpAccountStorage *storage_plugin;
108     McdAccount *account;
109     gint account_lock;
110 } McdLoadAccountsData;
111 
112 typedef struct
113 {
114     McdAccountManager *account_manager;
115     GHashTable *parameters;
116     GHashTable *properties;
117     McdGetAccountCb callback;
118     gpointer user_data;
119     GDestroyNotify destroy;
120 
121     gboolean ok;
122     GError *error;
123 } McdCreateAccountData;
124 
125 typedef struct
126 {
127     McdAccount *account;
128     gchar *key;
129 } McdAlterAccountData;
130 
131 enum
132 {
133     PROP_0,
134     PROP_DBUS_DAEMON,
135     PROP_CLIENT_FACTORY
136 };
137 
138 static guint write_conf_id = 0;
139 
140 static void register_dbus_service (McdAccountManager *account_manager);
141 
142 static void release_load_accounts_lock (McdLoadAccountsData *lad);
143 static void add_account (McdAccountManager *manager, McdAccount *account,
144     const gchar *source);
145 static void account_loaded (McdAccount *account,
146                             const GError *error,
147                             gpointer user_data);
148 
149 /* calback chain for asynchronously updates from backends: */
150 static void
async_altered_validity_cb(McdAccount * account,const GError * invalid_reason,gpointer data)151 async_altered_validity_cb (McdAccount *account, const GError *invalid_reason, gpointer data)
152 {
153     DEBUG ("asynchronously altered account %s is %svalid",
154            mcd_account_get_unique_name (account), (invalid_reason == NULL) ? "" : "in");
155 
156     g_object_unref (account);
157 }
158 
159 static void
async_altered_manager_cb(McdManager * cm,const GError * error,gpointer data)160 async_altered_manager_cb (McdManager *cm, const GError *error, gpointer data)
161 {
162     McdAccount *account = data;
163     const gchar *name = NULL;
164 
165     if (cm != NULL)
166         name = mcd_manager_get_name (cm);
167 
168     if (error != NULL)
169         DEBUG ("manager %s not ready: %s", name, error->message);
170     else
171         DEBUG ("manager %s is ready", name);
172 
173     /* this triggers the final parameter check which results in dbus signals *
174      * being fired and (potentially) the account going online automatically  */
175     mcd_account_check_validity (account, async_altered_validity_cb, NULL);
176 
177     g_object_unref (cm);
178 }
179 
180 /* account has been updated by a third party, and the McpAccountStorage *
181  * plugin has just informed us of this fact                             */
182 static void
altered_cb(GObject * storage,const gchar * name,gpointer data)183 altered_cb (GObject *storage, const gchar *name, gpointer data)
184 {
185     McdAccountManager *am = MCD_ACCOUNT_MANAGER (data);
186     McdMaster *master = mcd_master_get_default ();
187     McdAccount *account = NULL;
188     McdManager *cm = NULL;
189     const gchar *cm_name = NULL;
190 
191     account = mcd_account_manager_lookup_account (am, name);
192 
193     if (G_UNLIKELY (!account))
194     {
195         g_warning ("%s: account %s does not exist", G_STRFUNC, name);
196         return;
197     }
198 
199     /* in theory, the CM is already ready by this point, but make sure: */
200     cm_name = mcd_account_get_manager_name (account);
201 
202     if (cm_name != NULL)
203         cm = _mcd_master_lookup_manager (master, cm_name);
204 
205     if (cm != NULL)
206     {
207         g_object_ref (cm);
208         g_object_ref (account);
209         mcd_manager_call_when_ready (cm, async_altered_manager_cb, account);
210     }
211 }
212 
213 static void
async_altered_one_manager_cb(McdManager * cm,const GError * error,gpointer data)214 async_altered_one_manager_cb (McdManager *cm,
215                               const GError *error,
216                               gpointer data)
217 {
218     McdAlterAccountData *altered = data;
219     const gchar *name = NULL;
220 
221     if (cm != NULL)
222         name = mcd_manager_get_name (cm);
223 
224     if (error != NULL)
225         DEBUG ("manager %s not ready: %s", name, error->message);
226     else
227         DEBUG ("manager %s is ready", name);
228 
229     /* this triggers the final parameter check which results in dbus signals *
230      * being fired and (potentially) the account going online automatically  */
231     mcd_account_altered_by_plugin (altered->account, altered->key);
232 
233     g_object_unref (cm);
234     g_object_unref (altered->account);
235     g_free (altered->key);
236     g_slice_free (McdAlterAccountData, altered);
237 }
238 
239 
240 static void
altered_one_cb(GObject * storage,const gchar * account_name,const gchar * key,gpointer data)241 altered_one_cb (GObject *storage,
242                 const gchar *account_name,
243                 const gchar *key,
244                 gpointer data)
245 {
246     McdAccountManager *am = MCD_ACCOUNT_MANAGER (data);
247     McdMaster *master = mcd_master_get_default ();
248     McdAccount *account = NULL;
249     McdManager *cm = NULL;
250     const gchar *cm_name = NULL;
251 
252     account = mcd_account_manager_lookup_account (am, account_name);
253 
254     if (G_UNLIKELY (!account))
255     {
256         g_warning ("%s: account %s does not exist", G_STRFUNC, account_name);
257         return;
258     }
259 
260     /* in theory, the CM is already ready by this point, but make sure: */
261     cm_name = mcd_account_get_manager_name (account);
262 
263     if (cm_name != NULL)
264         cm = _mcd_master_lookup_manager (master, cm_name);
265 
266     if (cm != NULL)
267     {
268         McdAlterAccountData *altered = g_slice_new0 (McdAlterAccountData);
269 
270         g_object_ref (cm);
271         altered->account = g_object_ref (account);
272         altered->key = g_strdup (key);
273 
274         mcd_manager_call_when_ready (cm, async_altered_one_manager_cb, altered);
275     }
276 }
277 
278 /* callbacks for the various stages in an backend-driven account creation */
279 static void
async_created_validity_cb(McdAccount * account,const GError * invalid_reason,gpointer data)280 async_created_validity_cb (McdAccount *account, const GError *invalid_reason, gpointer data)
281 {
282     DEBUG ("asynchronously created account %s is %svalid",
283            mcd_account_get_unique_name (account), (invalid_reason == NULL) ? "" : "in");
284 
285     /* safely cached in the accounts hash by now */
286     g_object_unref (account);
287 }
288 
289 static void
async_created_manager_cb(McdManager * cm,const GError * error,gpointer data)290 async_created_manager_cb (McdManager *cm, const GError *error, gpointer data)
291 {
292     McdLoadAccountsData *lad = data;
293     McdAccount *account = lad->account;
294     McdAccountManager *am = lad->account_manager;
295     McpAccountStorage *plugin = lad->storage_plugin;
296     const gchar *name = NULL;
297 
298     if (cm != NULL)
299         name = mcd_manager_get_name (cm);
300 
301     if (error != NULL)
302         DEBUG ("manager %s not ready: %s", name, error->message);
303     else
304         DEBUG ("manager %s is ready", name);
305 
306     /* this takes a ref to the account and stores it in the accounts hash */
307     add_account (am, account, mcp_account_storage_name (plugin));
308 
309     /* this will free the McdLoadAccountsData, don't use it after this */
310     _mcd_account_load (account, account_loaded, lad);
311 
312     /* this triggers the final parameter check which results in dbus signals *
313      * being fired and (potentially) the account going online automatically  */
314     mcd_account_check_validity (account, async_created_validity_cb, NULL);
315 
316     g_object_unref (cm);
317 }
318 
319 /* account created by an McpAccountStorage plugin after the initial setup   *
320  * since the plugin does not have our cache, we need to poke the plugin     *
321  * to fetch the named account explicitly at this point (ie it's a read, not *
322  * not a write, from the plugin's POV:                                      */
323 static void
created_cb(GObject * storage_plugin_obj,const gchar * name,gpointer data)324 created_cb (GObject *storage_plugin_obj,
325     const gchar *name,
326     gpointer data)
327 {
328     McpAccountStorage *plugin = MCP_ACCOUNT_STORAGE (storage_plugin_obj);
329     McdAccountManager *am = MCD_ACCOUNT_MANAGER (data);
330     McdAccountManagerPrivate *priv = MCD_ACCOUNT_MANAGER_PRIV (am);
331     McdLoadAccountsData *lad = g_slice_new (McdLoadAccountsData);
332     McdAccount *account = NULL;
333     McdStorage *storage = priv->storage;
334     McdMaster *master = mcd_master_get_default ();
335     McdManager *cm = NULL;
336     const gchar *cm_name = NULL;
337 
338     lad->account_manager = am;
339     lad->storage_plugin = plugin;
340     lad->account_lock = 1; /* will be released at the end of this function */
341 
342     /* actually fetch the data into our cache from the plugin: */
343     if (mcd_storage_add_account_from_plugin (storage, plugin, name))
344     {
345         account = mcd_account_new (am, name, priv->minotaur);
346         lad->account = account;
347     }
348     else
349     {
350         /* that function already warned about it */
351         goto finish;
352     }
353 
354     if (G_UNLIKELY (!account))
355     {
356         g_warning ("%s: account %s failed to instantiate", G_STRFUNC, name);
357         goto finish;
358     }
359 
360     cm_name = mcd_account_get_manager_name (account);
361 
362     if (cm_name != NULL)
363         cm = _mcd_master_lookup_manager (master, cm_name);
364 
365     if (cm != NULL)
366     {
367         lad->account_lock++;
368         g_object_ref (cm);
369         mcd_manager_call_when_ready (cm, async_created_manager_cb, lad);
370     }
371     else
372     {
373         /* account is well and truly broken. forget it even existed: */
374         g_warning ("%s: account %s has no manager, ignoring it",
375                    G_STRFUNC, name);
376         g_object_unref (account);
377     }
378 
379 finish:
380     release_load_accounts_lock (lad);
381 }
382 
383 static void
toggled_cb(GObject * plugin,const gchar * name,gboolean on,gpointer data)384 toggled_cb (GObject *plugin, const gchar *name, gboolean on, gpointer data)
385 {
386   McpAccountStorage *storage_plugin = MCP_ACCOUNT_STORAGE (plugin);
387   McdAccountManager *manager = MCD_ACCOUNT_MANAGER (data);
388   McdAccount *account = NULL;
389   GError *error = NULL;
390 
391   account = mcd_account_manager_lookup_account (manager, name);
392 
393   DEBUG ("%s plugin reports %s became %sabled",
394       mcp_account_storage_name (storage_plugin), name, on ? "en" : "dis");
395 
396   if (account == NULL)
397     {
398       g_warning ("%s: Unknown account %s from %s plugin",
399           G_STRFUNC, name, mcp_account_storage_name (storage_plugin));
400       return;
401     }
402 
403   _mcd_account_set_enabled (account, on, FALSE,
404                             MCD_DBUS_PROP_SET_FLAG_NONE, &error);
405 
406   if (error != NULL)
407     {
408       g_warning ("Error setting Enabled for %s: %s", name, error->message);
409       g_clear_error (&error);
410     }
411 }
412 
413 static void
reconnect_cb(GObject * plugin,const gchar * name,gpointer data)414 reconnect_cb (GObject *plugin, const gchar *name, gpointer data)
415 {
416   McpAccountStorage *storage_plugin = MCP_ACCOUNT_STORAGE (plugin);
417   McdAccountManager *manager = MCD_ACCOUNT_MANAGER (data);
418   McdAccount *account = NULL;
419 
420   account = mcd_account_manager_lookup_account (manager, name);
421 
422   DEBUG ("%s plugin request %s reconnection",
423       mcp_account_storage_name (storage_plugin), name);
424 
425   if (account == NULL)
426     {
427       g_warning ("%s: Unknown account %s from %s plugin",
428           G_STRFUNC, name, mcp_account_storage_name (storage_plugin));
429       return;
430     }
431 
432   /* Storage ask to reconnect when important parameters changed, which is an
433    * user action. */
434   _mcd_account_reconnect (account, TRUE);
435 }
436 
437 static void
_mcd_account_delete_cb(McdAccount * account,const GError * error,gpointer data)438 _mcd_account_delete_cb (McdAccount *account, const GError *error, gpointer data)
439 {
440     /* no need to do anything other than release the account ref, which *
441      * should be the last ref we hold by the time this rolls arouns:    */
442     g_object_unref (account);
443 }
444 
445 /* a backend plugin notified us that an account was vaporised: remove it */
446 static void
deleted_cb(GObject * plugin,const gchar * name,gpointer data)447 deleted_cb (GObject *plugin, const gchar *name, gpointer data)
448 {
449     McpAccountStorage *storage_plugin = MCP_ACCOUNT_STORAGE (plugin);
450     McdAccountManager *manager = MCD_ACCOUNT_MANAGER (data);
451     McdAccount *account = NULL;
452 
453     account = g_hash_table_lookup (manager->priv->accounts, name);
454 
455     DEBUG ("%s reported deletion of %s (%p)",
456            mcp_account_storage_name (storage_plugin), name, account);
457 
458     if (account != NULL)
459     {
460         const gchar * object_path = mcd_account_get_object_path (account);
461 
462         g_object_ref (account);
463         /* this unhooks the account's signal handlers */
464         g_hash_table_remove (manager->priv->accounts, name);
465         tp_svc_account_manager_emit_account_removed (manager, object_path);
466         mcd_account_delete (account, _mcd_account_delete_cb, NULL);
467     }
468 }
469 
470 static gboolean
get_account_connection(const gchar * file_contents,const gchar * path,gchar ** p_bus_name,gchar ** p_account_name)471 get_account_connection (const gchar *file_contents, const gchar *path,
472                         gchar **p_bus_name, gchar **p_account_name)
473 {
474     const gchar *line, *tab1, *tab2, *endline;
475     const gchar *connection_path, *bus_name, *account_name;
476     size_t len;
477 
478     g_return_val_if_fail (path != NULL, FALSE);
479     if (!file_contents) return FALSE;
480 
481     len = strlen (path);
482     line = file_contents;
483     while ((tab1 = strchr (line, '\t')) != NULL)
484     {
485         connection_path = line;
486 
487         bus_name = tab1 + 1;
488         tab2 = strchr (bus_name, '\t');
489         if (!tab2) break;
490 
491         account_name = tab2 + 1;
492         endline = strchr (account_name, '\n');
493         if (!endline) break;
494 
495         if (line + len == tab1 &&
496             strncmp (path, connection_path, len) == 0)
497         {
498             *p_bus_name = g_strndup (bus_name, tab2 - bus_name);
499             *p_account_name = g_strndup (account_name, endline - account_name);
500             return TRUE;
501         }
502         line = endline + 1;
503     }
504     return FALSE;
505 }
506 
507 static gboolean
recover_connection(McdAccountManager * account_manager,gchar * file_contents,const gchar * name)508 recover_connection (McdAccountManager *account_manager, gchar *file_contents,
509                     const gchar *name)
510 {
511     McdAccount *account;
512     McdConnection *connection;
513     McdManager *manager;
514     McdMaster *master;
515     const gchar *manager_name;
516     gchar *object_path, *bus_name, *account_name;
517     GError *error = NULL;
518     gboolean ret = FALSE;
519 
520     master = mcd_master_get_default ();
521     g_return_val_if_fail (MCD_IS_MASTER (master), FALSE);
522 
523     object_path = g_strdelimit (g_strdup_printf ("/%s", name), ".", '/');
524     if (!get_account_connection (file_contents, object_path,
525                                  &bus_name, &account_name))
526         goto err_match;
527 
528     account = g_hash_table_lookup (account_manager->priv->accounts,
529                                    account_name);
530     if (!account || !mcd_account_is_enabled (account))
531         goto err_account;
532 
533     DEBUG ("account is %s", mcd_account_get_unique_name (account));
534     manager_name = mcd_account_get_manager_name (account);
535 
536     manager = _mcd_master_lookup_manager (master, manager_name);
537     if (G_UNLIKELY (!manager))
538     {
539         DEBUG ("Manager %s not found", manager_name);
540         goto err_manager;
541     }
542 
543     connection = mcd_manager_create_connection (manager, account);
544     if (G_UNLIKELY (!connection)) goto err_connection;
545 
546     _mcd_connection_set_tp_connection (connection, bus_name, object_path,
547                                        &error);
548     if (G_UNLIKELY (error))
549     {
550         DEBUG ("got error: %s", error->message);
551         g_error_free (error);
552         goto err_connection;
553     }
554     ret = TRUE;
555 
556 err_connection:
557 err_manager:
558 err_account:
559     g_free (account_name);
560     g_free (bus_name);
561 err_match:
562     g_free (object_path);
563     return ret;
564 }
565 
566 static void
list_connection_names_cb(const gchar * const * names,gsize n,const gchar * const * cms,const gchar * const * protocols,const GError * error,gpointer user_data,GObject * weak_object)567 list_connection_names_cb (const gchar * const *names, gsize n,
568                           const gchar * const *cms,
569                           const gchar * const *protocols,
570                           const GError *error, gpointer user_data,
571                           GObject *weak_object)
572 {
573     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (weak_object);
574     McdAccountManagerPrivate *priv = account_manager->priv;
575     gchar *contents = NULL;
576     guint i;
577 
578     DEBUG ("%" G_GSIZE_FORMAT " connections", n);
579 
580     /* if the file has no contents, we don't really care why */
581     if (!g_file_get_contents (priv->account_connections_file, &contents,
582                               NULL, NULL))
583     {
584         contents = NULL;
585     }
586 
587     for (i = 0; i < n; i++)
588     {
589         g_return_if_fail (names[i] != NULL);
590         DEBUG ("Connection %s", names[i]);
591         if (!recover_connection (account_manager, contents, names[i]))
592         {
593             /* Close the connection */
594             TpConnection *proxy;
595             gchar *path;
596 
597             path = g_strdup_printf ("/%s", names[i]);
598             g_strdelimit (path, ".", '/');
599 
600             DEBUG ("Killing connection");
601             proxy = tp_simple_client_factory_ensure_connection (
602                 priv->client_factory, path, NULL, NULL);
603 
604             if (proxy)
605             {
606                 tp_cli_connection_call_disconnect (proxy, -1, NULL, NULL,
607                                                    NULL, NULL);
608                 g_object_unref (proxy);
609             }
610 
611             g_free (path);
612         }
613     }
614     g_free (contents);
615 }
616 
617 static void
on_account_validity_changed(McdAccount * account,gboolean valid,McdAccountManager * account_manager)618 on_account_validity_changed (McdAccount *account, gboolean valid,
619 			     McdAccountManager *account_manager)
620 {
621     const gchar *object_path;
622 
623     object_path = mcd_account_get_object_path (account);
624 
625     if (_mcd_account_is_hidden (account))
626     {
627         mc_svc_account_manager_interface_hidden_emit_hidden_account_validity_changed (
628             account_manager, object_path, valid);
629     }
630     else
631     {
632         tp_svc_account_manager_emit_account_validity_changed (account_manager,
633                                                               object_path,
634                                                               valid);
635     }
636 }
637 
638 static void
on_account_removed(McdAccount * account,McdAccountManager * account_manager)639 on_account_removed (McdAccount *account, McdAccountManager *account_manager)
640 {
641     McdAccountManagerPrivate *priv = account_manager->priv;
642     McdStorage *storage = priv->storage;
643     const gchar *name, *object_path;
644 
645     object_path = mcd_account_get_object_path (account);
646 
647     if (_mcd_account_is_hidden (account))
648     {
649         mc_svc_account_manager_interface_hidden_emit_hidden_account_removed (
650             account_manager, object_path);
651     }
652     else
653     {
654         tp_svc_account_manager_emit_account_removed (account_manager,
655                                                      object_path);
656     }
657 
658     name = mcd_account_get_unique_name (account);
659     g_hash_table_remove (priv->accounts, name);
660 
661     mcd_storage_delete_account (storage, name);
662     mcd_account_manager_write_conf_async (account_manager, account, NULL,
663                                           NULL);
664 }
665 
666 static inline void
disconnect_signal(gpointer instance,gpointer func)667 disconnect_signal (gpointer instance, gpointer func)
668 {
669     g_signal_handlers_disconnect_matched (instance,
670                                           G_SIGNAL_MATCH_FUNC,
671                                           0, 0, NULL, func, NULL);
672 }
673 
674 static void
unref_account(gpointer data)675 unref_account (gpointer data)
676 {
677     McdAccount *account = MCD_ACCOUNT (data);
678 
679     DEBUG ("called for %s", mcd_account_get_unique_name (account));
680 
681     disconnect_signal (account, on_account_validity_changed);
682     disconnect_signal (account, on_account_removed);
683 
684     g_object_unref (account);
685 }
686 
687 static void _mcd_account_manager_store_account_connections (
688     McdAccountManager *);
689 
690 static void
add_account(McdAccountManager * account_manager,McdAccount * account,const gchar * source)691 add_account (McdAccountManager *account_manager, McdAccount *account,
692     const gchar *source)
693 {
694     McdAccountManagerPrivate *priv = account_manager->priv;
695     McdAccount *existing;
696     const gchar *name;
697 
698     name = mcd_account_get_unique_name (account);
699     DEBUG ("adding account %s (%p) from %s", name, account, source);
700 
701     existing = mcd_account_manager_lookup_account (account_manager, name);
702     if (existing != NULL)
703     {
704         g_warning ("...but we already have an account %p with that name!", existing);
705     }
706 
707     g_hash_table_insert (priv->accounts, (gchar *)name,
708                          g_object_ref (account));
709 
710     /* if we have to connect to any signals from the account object, this is
711      * the place to do it */
712     g_signal_connect (account, "validity-changed",
713 		      G_CALLBACK (on_account_validity_changed),
714 		      account_manager);
715     g_signal_connect (account, "removed", G_CALLBACK (on_account_removed),
716 		      account_manager);
717     tp_g_signal_connect_object (account, "connection-path-changed",
718         G_CALLBACK (_mcd_account_manager_store_account_connections),
719         account_manager, G_CONNECT_SWAPPED);
720 
721     /* some reports indicate this doesn't always fire for async backend  *
722      * accounts: testing here hasn't shown this, but at least we will be *
723      * able to tell if this happens from MC debug logs now:              */
724     DEBUG ("account %s validity: %d", name, mcd_account_is_valid (account));
725     /* if the account is already valid, synthesize a signal indicating that
726      * it's been added */
727     if (mcd_account_is_valid (account))
728         on_account_validity_changed (account, TRUE, account_manager);
729 }
730 
731 static void
mcd_create_account_data_free(McdCreateAccountData * cad)732 mcd_create_account_data_free (McdCreateAccountData *cad)
733 {
734     g_hash_table_unref (cad->parameters);
735     tp_clear_pointer (&cad->properties, g_hash_table_unref);
736 
737     if (G_UNLIKELY (cad->error))
738         g_error_free (cad->error);
739 
740     g_slice_free (McdCreateAccountData, cad);
741 }
742 
743 static gboolean
set_new_account_properties(McdAccount * account,GHashTable * properties,GError ** error)744 set_new_account_properties (McdAccount *account,
745                             GHashTable *properties,
746                             GError **error)
747 {
748     GHashTableIter iter;
749     gpointer key, value;
750     gboolean ok = TRUE;
751 
752     g_hash_table_iter_init (&iter, properties);
753 
754     while (g_hash_table_iter_next (&iter, &key, &value) && ok)
755     {
756         gchar *name = key;
757         gchar *dot, *iface, *pname;
758 
759         if ((dot = strrchr (name, '.')) != NULL)
760         {
761             iface = g_strndup (name, dot - name);
762             pname = dot + 1;
763             ok = mcd_dbusprop_set_property (TP_SVC_DBUS_PROPERTIES (account),
764                                             iface, pname, value, error);
765             g_free (iface);
766         }
767         else
768         {
769             g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
770                          "Malformed property name: %s", name);
771             ok = FALSE;
772         }
773     }
774 
775     return ok;
776 }
777 
778 static void
complete_account_creation_finish(McdAccount * account,McdCreateAccountData * cad)779 complete_account_creation_finish (McdAccount *account,
780                                   McdCreateAccountData *cad)
781 {
782     McdAccountManager *account_manager = cad->account_manager;
783 
784     if (!cad->ok)
785     {
786         mcd_account_delete (account, NULL, NULL);
787         tp_clear_object (&account);
788     }
789 
790     mcd_account_manager_write_conf_async (account_manager, account, NULL,
791                                           NULL);
792 
793     if (cad->callback != NULL)
794         cad->callback (account_manager, account, cad->error, cad->user_data);
795     mcd_create_account_data_free (cad);
796 
797     tp_clear_object (&account);
798 }
799 
800 static void
complete_account_creation_check_validity_cb(McdAccount * account,const GError * invalid_reason,gpointer user_data)801 complete_account_creation_check_validity_cb (McdAccount *account,
802                                              const GError *invalid_reason,
803                                              gpointer user_data)
804 {
805     McdCreateAccountData *cad = user_data;
806 
807     if (invalid_reason != NULL)
808     {
809         cad->ok = FALSE;
810         g_set_error_literal (&cad->error, invalid_reason->domain,
811             invalid_reason->code, invalid_reason->message);
812     }
813 
814     complete_account_creation_finish (account, cad);
815 }
816 
817 static void
complete_account_creation_set_cb(McdAccount * account,GPtrArray * not_yet,const GError * set_error,gpointer user_data)818 complete_account_creation_set_cb (McdAccount *account, GPtrArray *not_yet,
819                                   const GError *set_error, gpointer user_data)
820 {
821     McdCreateAccountData *cad = user_data;
822     McdAccountManager *account_manager;
823     cad->ok = TRUE;
824 
825     account_manager = cad->account_manager;
826 
827     if (set_error != NULL)
828     {
829         cad->ok = FALSE;
830         g_set_error_literal (&cad->error, set_error->domain, set_error->code,
831                              set_error->message);
832     }
833 
834     if (cad->ok && cad->properties != NULL)
835     {
836         cad->ok = set_new_account_properties (account, cad->properties, &cad->error);
837     }
838 
839     if (cad->ok)
840     {
841         add_account (account_manager, account, G_STRFUNC);
842         mcd_account_check_validity (account, complete_account_creation_check_validity_cb, cad);
843     }
844     else
845     {
846         complete_account_creation_finish (account, cad);
847     }
848 }
849 
850 static void
complete_account_creation(McdAccount * account,const GError * cb_error,gpointer user_data)851 complete_account_creation (McdAccount *account,
852                            const GError *cb_error,
853                            gpointer user_data)
854 {
855     McdCreateAccountData *cad = user_data;
856     McdAccountManager *account_manager;
857 
858     account_manager = cad->account_manager;
859     if (G_UNLIKELY (cb_error))
860     {
861         cad->callback (account_manager, account, cb_error, cad->user_data);
862         mcd_create_account_data_free (cad);
863         return;
864     }
865 
866     _mcd_account_set_parameters (account, cad->parameters, NULL,
867                                  complete_account_creation_set_cb,
868                                  cad);
869 }
870 
871 void
_mcd_account_manager_create_account(McdAccountManager * account_manager,const gchar * manager,const gchar * protocol,const gchar * display_name,GHashTable * params,GHashTable * properties,McdGetAccountCb callback,gpointer user_data,GDestroyNotify destroy)872 _mcd_account_manager_create_account (McdAccountManager *account_manager,
873                                      const gchar *manager,
874                                      const gchar *protocol,
875                                      const gchar *display_name,
876                                      GHashTable *params,
877                                      GHashTable *properties,
878                                      McdGetAccountCb callback,
879                                      gpointer user_data,
880                                      GDestroyNotify destroy)
881 {
882     McdAccountManagerPrivate *priv = account_manager->priv;
883     McdStorage *storage = priv->storage;
884     McdCreateAccountData *cad;
885     McdAccount *account;
886     gchar *unique_name = NULL;
887     const gchar *provider;
888     GError *e = NULL;
889 
890     DEBUG ("called");
891     if (G_UNLIKELY (manager == NULL || manager[0] == 0 ||
892                     protocol == NULL || protocol[0] == 0))
893     {
894         GError error = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
895             "Invalid parameters"};
896         callback (account_manager, NULL, &error, user_data);
897         if (destroy)
898             destroy (user_data);
899         return;
900     }
901 
902     provider = tp_asv_get_string (properties,
903                                   TP_PROP_ACCOUNT_INTERFACE_STORAGE_STORAGE_PROVIDER);
904 
905     unique_name = mcd_storage_create_account (storage, provider,
906                                               manager, protocol, params,
907                                               &e);
908 
909     if (unique_name == NULL)
910     {
911         callback (account_manager, NULL, e, user_data);
912         g_clear_error (&e);
913         if (destroy)
914             destroy (user_data);
915         return;
916     }
917 
918     /* create the basic account keys */
919     mcd_storage_set_string (storage, unique_name,
920                             MC_ACCOUNTS_KEY_MANAGER, manager);
921     mcd_storage_set_string (storage, unique_name,
922                             MC_ACCOUNTS_KEY_PROTOCOL, protocol);
923 
924     if (display_name != NULL)
925         mcd_storage_set_string (storage, unique_name,
926                                 MC_ACCOUNTS_KEY_DISPLAY_NAME, display_name);
927 
928     account = mcd_account_new (account_manager, unique_name, priv->minotaur);
929     g_free (unique_name);
930 
931     if (G_LIKELY (account))
932     {
933         cad = g_slice_new (McdCreateAccountData);
934         cad->account_manager = account_manager;
935         cad->parameters = g_hash_table_ref (params);
936         cad->properties = (properties ? g_hash_table_ref (properties) : NULL);
937         cad->callback = callback;
938         cad->user_data = user_data;
939         cad->destroy = destroy;
940         cad->error = NULL;
941         _mcd_account_load (account, complete_account_creation, cad);
942     }
943     else
944     {
945         GError error = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "" };
946         callback (account_manager, NULL, &error, user_data);
947         if (destroy)
948             destroy (user_data);
949     }
950 }
951 
952 static void
create_account_cb(McdAccountManager * account_manager,McdAccount * account,const GError * error,gpointer user_data)953 create_account_cb (McdAccountManager *account_manager, McdAccount *account,
954                    const GError *error, gpointer user_data)
955 {
956     DBusGMethodInvocation *context = user_data;
957     const gchar *object_path;
958 
959     if (G_UNLIKELY (error))
960     {
961         dbus_g_method_return_error (context, (GError *)error);
962 	return;
963     }
964 
965     g_return_if_fail (MCD_IS_ACCOUNT (account));
966     object_path = mcd_account_get_object_path (account);
967     tp_svc_account_manager_return_from_create_account (context, object_path);
968 }
969 
970 static void
account_manager_create_account(TpSvcAccountManager * self,const gchar * manager,const gchar * protocol,const gchar * display_name,GHashTable * parameters,GHashTable * properties,DBusGMethodInvocation * context)971 account_manager_create_account (TpSvcAccountManager *self,
972                                 const gchar *manager,
973                                 const gchar *protocol,
974                                 const gchar *display_name,
975                                 GHashTable *parameters,
976                                 GHashTable *properties,
977                                 DBusGMethodInvocation *context)
978 {
979     _mcd_account_manager_create_account (MCD_ACCOUNT_MANAGER (self),
980                                          manager, protocol, display_name,
981                                          parameters, properties,
982                                          create_account_cb, context, NULL);
983 }
984 
985 static void
account_manager_iface_init(TpSvcAccountManagerClass * iface,gpointer iface_data)986 account_manager_iface_init (TpSvcAccountManagerClass *iface,
987 			    gpointer iface_data)
988 {
989 #define IMPLEMENT(x) tp_svc_account_manager_implement_##x (\
990     iface, account_manager_##x)
991     IMPLEMENT(create_account);
992 #undef IMPLEMENT
993 }
994 
995 static void
account_manager_hidden_iface_init(McSvcAccountManagerInterfaceHiddenClass * iface,gpointer iface_data)996 account_manager_hidden_iface_init (
997     McSvcAccountManagerInterfaceHiddenClass *iface,
998     gpointer iface_data)
999 {
1000 }
1001 
1002 static void
accounts_to_gvalue(GHashTable * accounts,gboolean valid,gboolean hidden,GValue * value)1003 accounts_to_gvalue (GHashTable *accounts, gboolean valid, gboolean hidden,
1004                     GValue *value)
1005 {
1006     static GType ao_type = G_TYPE_INVALID;
1007     GPtrArray *account_array;
1008     GHashTableIter iter;
1009     McdAccount *account;
1010     gpointer k;
1011 
1012     if (G_UNLIKELY (ao_type == G_TYPE_INVALID))
1013         ao_type = dbus_g_type_get_collection ("GPtrArray",
1014                                               DBUS_TYPE_G_OBJECT_PATH);
1015 
1016     account_array = g_ptr_array_sized_new (g_hash_table_size (accounts));
1017 
1018     g_hash_table_iter_init (&iter, accounts);
1019 
1020     while (g_hash_table_iter_next (&iter, &k, (gpointer)&account))
1021     {
1022         if (mcd_account_is_valid (account) == valid &&
1023             _mcd_account_is_hidden (account) == hidden)
1024         {
1025             g_ptr_array_add (account_array,
1026                              g_strdup (mcd_account_get_object_path (account)));
1027         }
1028     }
1029 
1030     g_value_init (value, ao_type);
1031     g_value_take_boxed (value, account_array);
1032 }
1033 
1034 static void
get_valid_accounts(TpSvcDBusProperties * self,const gchar * name,GValue * value)1035 get_valid_accounts (TpSvcDBusProperties *self, const gchar *name,
1036 		    GValue *value)
1037 {
1038     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (self);
1039     McdAccountManagerPrivate *priv = account_manager->priv;
1040 
1041     DEBUG ("called");
1042     accounts_to_gvalue (priv->accounts, TRUE, FALSE, value);
1043 }
1044 
1045 static void
get_invalid_accounts(TpSvcDBusProperties * self,const gchar * name,GValue * value)1046 get_invalid_accounts (TpSvcDBusProperties *self, const gchar *name,
1047 		      GValue *value)
1048 {
1049     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (self);
1050     McdAccountManagerPrivate *priv = account_manager->priv;
1051 
1052     DEBUG ("called");
1053     accounts_to_gvalue (priv->accounts, FALSE, FALSE, value);
1054 }
1055 
1056 static void
get_supported_account_properties(TpSvcDBusProperties * svc,const gchar * name,GValue * value)1057 get_supported_account_properties (TpSvcDBusProperties *svc,
1058                                   const gchar *name,
1059                                   GValue *value)
1060 {
1061     static const gchar * const supported[] = {
1062         TP_IFACE_ACCOUNT ".AutomaticPresence",
1063         TP_IFACE_ACCOUNT ".Enabled",
1064         TP_IFACE_ACCOUNT ".Icon",
1065         TP_IFACE_ACCOUNT ".Nickname",
1066         TP_IFACE_ACCOUNT ".ConnectAutomatically",
1067         TP_IFACE_ACCOUNT ".RequestedPresence",
1068         TP_IFACE_ACCOUNT ".Supersedes",
1069         TP_PROP_ACCOUNT_SERVICE,
1070         TP_IFACE_ACCOUNT_INTERFACE_AVATAR ".Avatar",
1071         MC_IFACE_ACCOUNT_INTERFACE_CONDITIONS ".Condition",
1072         TP_PROP_ACCOUNT_INTERFACE_STORAGE_STORAGE_PROVIDER,
1073         MC_IFACE_ACCOUNT_INTERFACE_HIDDEN ".Hidden",
1074         NULL
1075     };
1076 
1077     g_value_init (value, G_TYPE_STRV);
1078     g_value_set_static_boxed (value, supported);
1079 }
1080 
1081 static const McdDBusProp account_manager_properties[] = {
1082     { "ValidAccounts", NULL, get_valid_accounts },
1083     { "InvalidAccounts", NULL, get_invalid_accounts },
1084     { "Interfaces", NULL, mcd_dbus_get_interfaces },
1085     { "SupportedAccountProperties", NULL, get_supported_account_properties },
1086     { 0 },
1087 };
1088 
1089 static void
get_valid_hidden_accounts(TpSvcDBusProperties * self,const gchar * name,GValue * value)1090 get_valid_hidden_accounts (TpSvcDBusProperties *self, const gchar *name,
1091                            GValue *value)
1092 {
1093     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (self);
1094     McdAccountManagerPrivate *priv = account_manager->priv;
1095 
1096     accounts_to_gvalue (priv->accounts, TRUE, TRUE, value);
1097 }
1098 
1099 static void
get_invalid_hidden_accounts(TpSvcDBusProperties * self,const gchar * name,GValue * value)1100 get_invalid_hidden_accounts (TpSvcDBusProperties *self, const gchar *name,
1101                              GValue *value)
1102 {
1103     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (self);
1104     McdAccountManagerPrivate *priv = account_manager->priv;
1105 
1106     accounts_to_gvalue (priv->accounts, FALSE, TRUE, value);
1107 }
1108 
1109 static const McdDBusProp account_manager_hidden_properties[] = {
1110     { "ValidHiddenAccounts", NULL, get_valid_hidden_accounts },
1111     { "InvalidHiddenAccounts", NULL, get_invalid_hidden_accounts },
1112     { 0 },
1113 };
1114 
1115 static void
properties_iface_init(TpSvcDBusPropertiesClass * iface,gpointer iface_data)1116 properties_iface_init (TpSvcDBusPropertiesClass *iface, gpointer iface_data)
1117 {
1118 #define IMPLEMENT(x) tp_svc_dbus_properties_implement_##x (\
1119     iface, dbusprop_##x)
1120     IMPLEMENT(set);
1121     IMPLEMENT(get);
1122     IMPLEMENT(get_all);
1123 #undef IMPLEMENT
1124 }
1125 
1126 static gboolean
write_conf(gpointer userdata)1127 write_conf (gpointer userdata)
1128 {
1129     McdStorage *storage = MCD_STORAGE (userdata);
1130 
1131     DEBUG ("called");
1132     g_source_remove (write_conf_id);
1133     write_conf_id = 0;
1134 
1135     mcd_storage_commit (storage, NULL);
1136 
1137     return TRUE;
1138 }
1139 
1140 static void
release_load_accounts_lock(McdLoadAccountsData * lad)1141 release_load_accounts_lock (McdLoadAccountsData *lad)
1142 {
1143     g_return_if_fail (lad->account_lock > 0);
1144     lad->account_lock--;
1145     DEBUG ("called, count is now %d", lad->account_lock);
1146 
1147     if (lad->account_lock == 0)
1148     {
1149         register_dbus_service (lad->account_manager);
1150         g_slice_free (McdLoadAccountsData, lad);
1151     }
1152 }
1153 
1154 static void
account_loaded(McdAccount * account,const GError * error,gpointer user_data)1155 account_loaded (McdAccount *account, const GError *error, gpointer user_data)
1156 {
1157     McdLoadAccountsData *lad = user_data;
1158 
1159     if (error)
1160     {
1161         g_warning ("%s: got error: %s", G_STRFUNC, error->message);
1162         g_hash_table_remove (lad->account_manager->priv->accounts,
1163                              mcd_account_get_unique_name (account));
1164     }
1165 
1166     release_load_accounts_lock (lad);
1167 }
1168 
1169 static void
uncork_storage_plugins(McdAccountManager * account_manager)1170 uncork_storage_plugins (McdAccountManager *account_manager)
1171 {
1172     McdAccountManagerPrivate *priv = MCD_ACCOUNT_MANAGER_PRIV (account_manager);
1173 
1174     mcd_account_manager_write_conf_async (account_manager, NULL, NULL, NULL);
1175     mcd_storage_ready (priv->storage);
1176 }
1177 
1178 typedef struct
1179 {
1180     McdAccountManager *self;
1181     McdAccount *account;
1182     McdLoadAccountsData *lad;
1183 } MigrateCtx;
1184 
1185 static MigrateCtx *
migrate_ctx_new(McdAccountManager * self,McdAccount * account,McdLoadAccountsData * lad)1186 migrate_ctx_new (McdAccountManager *self,
1187                  McdAccount *account,
1188                  McdLoadAccountsData *lad)
1189 {
1190     MigrateCtx *ctx = g_slice_new (MigrateCtx);
1191 
1192     ctx->self = g_object_ref (self);
1193     ctx->account = g_object_ref (account);
1194     ctx->lad = lad;
1195 
1196     /* Lock attempting to migrate the account */
1197     lad->account_lock++;
1198     return ctx;
1199 }
1200 
1201 static void
migrate_ctx_free(MigrateCtx * ctx)1202 migrate_ctx_free (MigrateCtx *ctx)
1203 {
1204     g_object_unref (ctx->self);
1205     g_object_unref (ctx->account);
1206     release_load_accounts_lock (ctx->lad);
1207     g_slice_free (MigrateCtx, ctx);
1208 }
1209 
1210 
1211 static void
migrate_delete_account_cb(McdAccount * account,const GError * error,gpointer user_data)1212 migrate_delete_account_cb (McdAccount *account,
1213                            const GError *error,
1214                            gpointer user_data)
1215 {
1216     MigrateCtx *ctx = user_data;
1217 
1218     migrate_ctx_free (ctx);
1219 }
1220 
1221 static void
migrate_create_account_cb(McdAccountManager * account_manager,McdAccount * account,const GError * error,gpointer user_data)1222 migrate_create_account_cb (McdAccountManager *account_manager,
1223                            McdAccount *account,
1224                            const GError *error,
1225                            gpointer user_data)
1226 {
1227     MigrateCtx *ctx = user_data;
1228 
1229     if (error != NULL)
1230     {
1231         DEBUG ("Failed to create account: %s", error->message);
1232         _mcd_account_set_enabled (ctx->account, FALSE, TRUE,
1233                                   MCD_DBUS_PROP_SET_FLAG_NONE, NULL);
1234         migrate_ctx_free (ctx);
1235         return;
1236     }
1237 
1238     DEBUG ("Account %s migrated, removing it",
1239            mcd_account_get_unique_name (ctx->account));
1240 
1241     mcd_account_delete (ctx->account, migrate_delete_account_cb, ctx);
1242 }
1243 
1244 static void
migrate_butterfly_haze_ready(McdManager * manager,const GError * error,gpointer user_data)1245 migrate_butterfly_haze_ready (McdManager *manager,
1246                               const GError *error,
1247                               gpointer user_data)
1248 {
1249     MigrateCtx *ctx = user_data;
1250     gchar *display_name;
1251     GValue v = G_VALUE_INIT;
1252     GValue password_v = G_VALUE_INIT;
1253     GHashTable *parameters, *properties;
1254     gchar *str;
1255     GPtrArray *supersedes;
1256     GPtrArray *old_supersedes;
1257 
1258     if (error != NULL)
1259     {
1260         DEBUG ("Can't find Haze: %s", error->message);
1261         _mcd_account_set_enabled (ctx->account, FALSE, TRUE,
1262                                   MCD_DBUS_PROP_SET_FLAG_NONE, NULL);
1263         goto error;
1264     }
1265 
1266     /* Parameters; the only mandatory one is 'account' */
1267     if (!mcd_account_get_parameter_of_known_type (ctx->account,
1268                                                   "account", G_TYPE_STRING,
1269                                                   &v, NULL))
1270     {
1271         _mcd_account_set_enabled (ctx->account, FALSE, TRUE,
1272                                   MCD_DBUS_PROP_SET_FLAG_NONE, NULL);
1273         goto error;
1274     }
1275 
1276     parameters = g_hash_table_new (g_str_hash, g_str_equal);
1277     g_hash_table_insert (parameters, "account", &v);
1278 
1279     /* If MC is storing the password, let's copy that too, so Empathy
1280      * can migrate it somewhere better. */
1281     if (mcd_account_get_parameter_of_known_type (ctx->account,
1282                                                  "password", G_TYPE_STRING,
1283                                                  &password_v, NULL))
1284     {
1285         g_hash_table_insert (parameters, "password", &password_v);
1286     }
1287 
1288     display_name = mcd_account_dup_display_name (ctx->account);
1289 
1290     /* Properties */
1291     properties = tp_asv_new (NULL, NULL);
1292 
1293     str = mcd_account_dup_icon (ctx->account);
1294     if (str != NULL)
1295         tp_asv_take_string (properties, TP_PROP_ACCOUNT_ICON, str);
1296 
1297     tp_asv_set_boolean (properties, TP_PROP_ACCOUNT_ENABLED,
1298                         mcd_account_is_enabled (ctx->account));
1299 
1300     str = mcd_account_dup_nickname (ctx->account);
1301     if (str != NULL)
1302         tp_asv_take_string (properties, TP_PROP_ACCOUNT_NICKNAME, str);
1303 
1304     supersedes = g_ptr_array_new ();
1305     old_supersedes = _mcd_account_get_supersedes (ctx->account);
1306 
1307     if (old_supersedes != NULL)
1308     {
1309         guint i;
1310 
1311         for (i = 0; i < old_supersedes->len; i++)
1312             g_ptr_array_add (supersedes,
1313                              g_strdup (g_ptr_array_index (old_supersedes, i)));
1314     }
1315 
1316     g_ptr_array_add (supersedes,
1317                      g_strdup (mcd_account_get_object_path (ctx->account)));
1318     tp_asv_take_boxed (properties, TP_PROP_ACCOUNT_SUPERSEDES,
1319                        TP_ARRAY_TYPE_OBJECT_PATH_LIST, supersedes);
1320 
1321     /* Set the service while we're on it */
1322     tp_asv_set_string (properties, TP_PROP_ACCOUNT_SERVICE, "windows-live");
1323 
1324     _mcd_account_manager_create_account (ctx->self,
1325                                        "haze", "msn", display_name,
1326                                        parameters, properties,
1327                                        migrate_create_account_cb, ctx, NULL);
1328 
1329     g_value_unset (&v);
1330     g_free (display_name);
1331     g_hash_table_unref (parameters);
1332     g_hash_table_unref (properties);
1333     return;
1334 
1335 error:
1336     migrate_ctx_free (ctx);
1337 }
1338 
1339 static void
butterfly_account_loaded(McdAccount * account,const GError * error,gpointer user_data)1340 butterfly_account_loaded (McdAccount *account,
1341                           const GError *error,
1342                           gpointer user_data)
1343 {
1344     MigrateCtx *ctx = user_data;
1345     McdMaster *master = mcd_master_get_default ();
1346     McdManager *manager;
1347 
1348     if (error != NULL)
1349         goto error;
1350 
1351     DEBUG ("Try migrating butterfly account %s",
1352            mcd_account_get_unique_name (account));
1353 
1354     /* Check if Haze is installed */
1355     manager = _mcd_master_lookup_manager (master, "haze");
1356     if (manager == NULL)
1357     {
1358         DEBUG ("Can't find Haze");
1359         _mcd_account_set_enabled (account, FALSE, TRUE,
1360                                   MCD_DBUS_PROP_SET_FLAG_NONE, NULL);
1361         goto error;
1362     }
1363 
1364     mcd_manager_call_when_ready (manager, migrate_butterfly_haze_ready, ctx);
1365     return;
1366 
1367 error:
1368     migrate_ctx_free (ctx);
1369 }
1370 
1371 static void
migrate_butterfly_account(McdAccountManager * self,McdAccount * account,McdLoadAccountsData * lad)1372 migrate_butterfly_account (McdAccountManager *self,
1373                            McdAccount *account,
1374                            McdLoadAccountsData *lad)
1375 {
1376     MigrateCtx *ctx;
1377 
1378     ctx = migrate_ctx_new (self, account, lad);
1379 
1380     _mcd_account_load (account, butterfly_account_loaded, ctx);
1381 }
1382 
1383 /* Migrate some specific type of account. If something went wrong during the
1384  * migration we disable it. */
1385 static void
migrate_accounts(McdAccountManager * self,McdLoadAccountsData * lad)1386 migrate_accounts (McdAccountManager *self,
1387                   McdLoadAccountsData *lad)
1388 {
1389     McdAccountManagerPrivate *priv = self->priv;
1390     GHashTableIter iter;
1391     gpointer v;
1392 
1393     g_hash_table_iter_init (&iter, priv->accounts);
1394     while (g_hash_table_iter_next (&iter, NULL, &v))
1395     {
1396         McdAccount *account = v;
1397         TpConnectionManager *cm;
1398 
1399         cm = mcd_account_get_cm (account);
1400 
1401         if (cm == NULL)
1402             continue;
1403 
1404         if (!tp_strdiff (tp_connection_manager_get_name (cm), "butterfly"))
1405             migrate_butterfly_account (self, account, lad);
1406     }
1407 }
1408 
1409 /**
1410  * _mcd_account_manager_setup:
1411  * @account_manager: the #McdAccountManager.
1412  *
1413  * This function must be called by the McdMaster; it reads the accounts from
1414  * the config file, and it needs a McdMaster instance to be active.
1415  */
1416 void
_mcd_account_manager_setup(McdAccountManager * account_manager)1417 _mcd_account_manager_setup (McdAccountManager *account_manager)
1418 {
1419     McdAccountManagerPrivate *priv = account_manager->priv;
1420     McdStorage *storage = priv->storage;
1421     McdLoadAccountsData *lad;
1422     gchar **accounts, **name;
1423     GHashTableIter iter;
1424     gpointer v;
1425 
1426     tp_list_connection_names (priv->dbus_daemon,
1427                               list_connection_names_cb, NULL, NULL,
1428                               (GObject *)account_manager);
1429 
1430     lad = g_slice_new (McdLoadAccountsData);
1431     lad->account_manager = account_manager;
1432     lad->account_lock = 1; /* will be released at the end of this function */
1433 
1434     accounts = mcd_storage_dup_accounts (storage, NULL);
1435 
1436     for (name = accounts; *name != NULL; name++)
1437     {
1438         gboolean plausible = FALSE;
1439         const gchar *manager = NULL;
1440         const gchar *protocol = NULL;
1441         McdAccount *account = mcd_account_manager_lookup_account (
1442             account_manager, *name);
1443 
1444         if (account != NULL)
1445         {
1446             /* FIXME: this shouldn't really happen */
1447             DEBUG ("already have account %p called '%s'; skipping", account, *name);
1448             continue;
1449         }
1450 
1451         account = mcd_account_new (account_manager, *name, priv->minotaur);
1452 
1453         if (G_UNLIKELY (!account))
1454         {
1455             g_warning ("%s: account %s failed to instantiate", G_STRFUNC,
1456                        *name);
1457             continue;
1458         }
1459 
1460         manager = mcd_account_get_manager_name (account);
1461         protocol = mcd_account_get_protocol_name (account);
1462 
1463         plausible = !tp_str_empty (manager) && !tp_str_empty (protocol);
1464 
1465         if (G_UNLIKELY (!plausible))
1466         {
1467             const gchar *dbg_manager = (manager == NULL) ? "(nil)" : manager;
1468             const gchar *dbg_protocol = (protocol == NULL) ? "(nil)" : protocol;
1469 
1470             g_warning ("%s: account %s has implausible manager/protocol: %s/%s",
1471                        G_STRFUNC, *name, dbg_manager, dbg_protocol);
1472             g_object_unref (account);
1473             continue;
1474         }
1475 
1476         lad->account_lock++;
1477         add_account (lad->account_manager, account, "keyfile");
1478         _mcd_account_load (account, account_loaded, lad);
1479         g_object_unref (account);
1480     }
1481     g_strfreev (accounts);
1482 
1483     uncork_storage_plugins (account_manager);
1484 
1485     migrate_accounts (account_manager, lad);
1486 
1487     release_load_accounts_lock (lad);
1488 
1489     g_hash_table_iter_init (&iter, account_manager->priv->accounts);
1490 
1491     while (g_hash_table_iter_next (&iter, NULL, &v))
1492       _mcd_account_maybe_autoconnect (v);
1493 }
1494 
1495 static void
register_dbus_service(McdAccountManager * account_manager)1496 register_dbus_service (McdAccountManager *account_manager)
1497 {
1498     McdAccountManagerPrivate *priv = account_manager->priv;
1499     GError *error = NULL;
1500 
1501     if (priv->dbus_registered)
1502         return;
1503 
1504     if (!tp_dbus_daemon_request_name (priv->dbus_daemon,
1505                                       TP_ACCOUNT_MANAGER_BUS_NAME,
1506                                       TRUE /* idempotent */, &error))
1507     {
1508         /* FIXME: put in proper error handling when MC gains the ability to
1509          * be the AM or the CD but not both */
1510         g_warning("Failed registering '%s' service: %s",
1511                   TP_ACCOUNT_MANAGER_BUS_NAME, error->message);
1512         g_error_free (error);
1513         exit (1);
1514     }
1515 
1516     priv->dbus_registered = TRUE;
1517 
1518     tp_dbus_daemon_register_object (priv->dbus_daemon,
1519                                     TP_ACCOUNT_MANAGER_OBJECT_PATH,
1520                                     account_manager);
1521 }
1522 
1523 static void
set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)1524 set_property (GObject *obj, guint prop_id,
1525 	      const GValue *val, GParamSpec *pspec)
1526 {
1527     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (obj);
1528     McdAccountManagerPrivate *priv = account_manager->priv;
1529 
1530     switch (prop_id)
1531     {
1532     case PROP_CLIENT_FACTORY:
1533         g_assert (priv->client_factory == NULL);  /* construct-only */
1534         priv->client_factory =
1535             TP_SIMPLE_CLIENT_FACTORY (g_value_dup_object (val));
1536         priv->dbus_daemon =
1537             tp_simple_client_factory_get_dbus_daemon (priv->client_factory);
1538         g_object_ref (priv->dbus_daemon);
1539         break;
1540 
1541     default:
1542 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1543 	break;
1544     }
1545 }
1546 
1547 static void
get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)1548 get_property (GObject *obj, guint prop_id,
1549 	      GValue *val, GParamSpec *pspec)
1550 {
1551     McdAccountManagerPrivate *priv = MCD_ACCOUNT_MANAGER_PRIV (obj);
1552 
1553     switch (prop_id)
1554     {
1555     case PROP_DBUS_DAEMON:
1556 	g_value_set_object (val, priv->dbus_daemon);
1557 	break;
1558     default:
1559 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1560 	break;
1561     }
1562 }
1563 
1564 static void
_mcd_account_manager_finalize(GObject * object)1565 _mcd_account_manager_finalize (GObject *object)
1566 {
1567     McdAccountManagerPrivate *priv = MCD_ACCOUNT_MANAGER_PRIV (object);
1568 
1569     if (write_conf_id)
1570     {
1571         write_conf (priv->storage);
1572         g_assert (write_conf_id == 0);
1573     }
1574 
1575     tp_clear_object (&priv->storage);
1576     g_free (priv->account_connections_dir);
1577     remove (priv->account_connections_file);
1578     g_free (priv->account_connections_file);
1579 
1580     g_hash_table_unref (priv->accounts);
1581 
1582     G_OBJECT_CLASS (mcd_account_manager_parent_class)->finalize (object);
1583 }
1584 
1585 static void
_mcd_account_manager_dispose(GObject * object)1586 _mcd_account_manager_dispose (GObject *object)
1587 {
1588     McdAccountManagerPrivate *priv = MCD_ACCOUNT_MANAGER_PRIV (object);
1589 
1590     tp_clear_object (&priv->dbus_daemon);
1591     tp_clear_object (&priv->client_factory);
1592     tp_clear_object (&priv->minotaur);
1593 
1594     G_OBJECT_CLASS (mcd_account_manager_parent_class)->dispose (object);
1595 }
1596 
1597 static void
mcd_account_manager_class_init(McdAccountManagerClass * klass)1598 mcd_account_manager_class_init (McdAccountManagerClass *klass)
1599 {
1600     GObjectClass *object_class = G_OBJECT_CLASS (klass);
1601     g_type_class_add_private (object_class, sizeof (McdAccountManagerPrivate));
1602 
1603     object_class->dispose = _mcd_account_manager_dispose;
1604     object_class->finalize = _mcd_account_manager_finalize;
1605     object_class->set_property = set_property;
1606     object_class->get_property = get_property;
1607     object_class->constructed = _mcd_account_manager_constructed;
1608 
1609     g_object_class_install_property
1610         (object_class, PROP_DBUS_DAEMON,
1611          g_param_spec_object ("dbus-daemon", "DBus daemon", "DBus daemon",
1612                               TP_TYPE_DBUS_DAEMON,
1613                               G_PARAM_READABLE));
1614 
1615     g_object_class_install_property
1616         (object_class, PROP_CLIENT_FACTORY,
1617          g_param_spec_object ("client-factory",
1618                               "Client factory",
1619                               "Client factory",
1620                               TP_TYPE_SIMPLE_CLIENT_FACTORY,
1621                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
1622 }
1623 
1624 static const gchar *
get_connections_cache_dir(void)1625 get_connections_cache_dir (void)
1626 {
1627     const gchar *from_env = g_getenv ("MC_ACCOUNT_DIR");
1628 
1629     if (from_env != NULL)
1630     {
1631         return from_env;
1632     }
1633 
1634     if ((ACCOUNTS_CACHE_DIR)[0] != '\0')
1635     {
1636         return ACCOUNTS_CACHE_DIR;
1637     }
1638 
1639     return g_get_user_cache_dir ();
1640 }
1641 
1642 static void
mcd_account_manager_init(McdAccountManager * account_manager)1643 mcd_account_manager_init (McdAccountManager *account_manager)
1644 {
1645     McdAccountManagerPrivate *priv;
1646 
1647     priv = G_TYPE_INSTANCE_GET_PRIVATE ((account_manager),
1648 					MCD_TYPE_ACCOUNT_MANAGER,
1649 					McdAccountManagerPrivate);
1650     account_manager->priv = priv;
1651 }
1652 
1653 static void
_mcd_account_manager_constructed(GObject * obj)1654 _mcd_account_manager_constructed (GObject *obj)
1655 {
1656     McdAccountManager *account_manager = MCD_ACCOUNT_MANAGER (obj);
1657     McdAccountManagerPrivate *priv = account_manager->priv;
1658     guint i = 0;
1659     static struct { const gchar *name; GCallback handler; } sig[] =
1660       { { "created", G_CALLBACK (created_cb) },
1661         { "altered", G_CALLBACK (altered_cb) },
1662         { "toggled", G_CALLBACK (toggled_cb) },
1663         { "deleted", G_CALLBACK (deleted_cb) },
1664         { "altered-one", G_CALLBACK (altered_one_cb) },
1665         { "reconnect", G_CALLBACK (reconnect_cb) },
1666         { NULL, NULL } };
1667 
1668     DEBUG ("");
1669 
1670     priv->minotaur = mcd_connectivity_monitor_new ();
1671 
1672     priv->storage = mcd_storage_new (priv->dbus_daemon);
1673     priv->accounts = g_hash_table_new_full (g_str_hash, g_str_equal,
1674                                             NULL, unref_account);
1675 
1676     priv->account_connections_dir = g_strdup (get_connections_cache_dir ());
1677     priv->account_connections_file =
1678         g_build_filename (priv->account_connections_dir, ".mc_connections",
1679                           NULL);
1680 
1681     DEBUG ("loading plugins");
1682     mcd_storage_load (priv->storage);
1683 
1684     /* hook up all the storage plugin signals to their handlers: */
1685     for (i = 0; sig[i].name != NULL; i++)
1686     {
1687         mcd_storage_connect_signal (sig[i].name, sig[i].handler,
1688                                     account_manager);
1689     }
1690 
1691     /* initializes the interfaces */
1692     mcd_dbus_init_interfaces_instances (account_manager);
1693 }
1694 
1695 McdAccountManager *
mcd_account_manager_new(TpSimpleClientFactory * client_factory)1696 mcd_account_manager_new (TpSimpleClientFactory *client_factory)
1697 {
1698     gpointer *obj;
1699 
1700     obj = g_object_new (MCD_TYPE_ACCOUNT_MANAGER,
1701                         "client-factory", client_factory,
1702                         NULL);
1703     return MCD_ACCOUNT_MANAGER (obj);
1704 }
1705 
1706 /**
1707  * mcd_account_manager_get_dbus_daemon:
1708  * @account_manager: the #McdAccountManager.
1709  *
1710  * Returns: the #TpDBusDaemon.
1711  */
1712 TpDBusDaemon *
mcd_account_manager_get_dbus_daemon(McdAccountManager * account_manager)1713 mcd_account_manager_get_dbus_daemon (McdAccountManager *account_manager)
1714 {
1715     g_return_val_if_fail (MCD_IS_ACCOUNT_MANAGER (account_manager), NULL);
1716 
1717     return account_manager->priv->dbus_daemon;
1718 }
1719 
1720 McdConnectivityMonitor *
mcd_account_manager_get_connectivity_monitor(McdAccountManager * self)1721 mcd_account_manager_get_connectivity_monitor (McdAccountManager *self)
1722 {
1723   g_return_val_if_fail (MCD_IS_ACCOUNT_MANAGER (self), NULL);
1724   return self->priv->minotaur;
1725 }
1726 
1727 /**
1728  * McdAccountManagerWriteConfCb:
1729  * @account_manager: the #McdAccountManager
1730  * @error: a set #GError on failure or %NULL if there was no error
1731  * @user_data: user data
1732  *
1733  * The callback from mcd_account_manager_write_conf_async(). If the config
1734  * writing was successful, @error will be %NULL, otherwise it will be set
1735  * with the appropriate error.
1736  */
1737 
1738 /**
1739  * mcd_account_manager_write_conf_async:
1740  * @account_manager: the #McdAccountManager
1741  * @account: the account to be written, or %NULL to flush all accounts
1742  * @callback: a callback to be called on write success or failure
1743  * @user_data: data to be passed to @callback
1744  *
1745  * Write the account manager configuration to disk.
1746  */
1747 void
mcd_account_manager_write_conf_async(McdAccountManager * account_manager,McdAccount * account,McdAccountManagerWriteConfCb callback,gpointer user_data)1748 mcd_account_manager_write_conf_async (McdAccountManager *account_manager,
1749                                       McdAccount *account,
1750                                       McdAccountManagerWriteConfCb callback,
1751                                       gpointer user_data)
1752 {
1753     McdStorage *storage = NULL;
1754     const gchar *account_name = NULL;
1755 
1756     g_return_if_fail (MCD_IS_ACCOUNT_MANAGER (account_manager));
1757 
1758     storage = account_manager->priv->storage;
1759 
1760     if (account != NULL)
1761     {
1762         account_name = mcd_account_get_unique_name (account);
1763 
1764         DEBUG ("updating %s", account_name);
1765         mcd_storage_commit (storage, account_name);
1766     }
1767     else
1768     {
1769         GStrv groups;
1770         gsize n_accounts = 0;
1771 
1772         groups = mcd_storage_dup_accounts (storage, &n_accounts);
1773         DEBUG ("updating all %" G_GSIZE_FORMAT " accounts", n_accounts);
1774 
1775         mcd_storage_commit (storage, NULL);
1776 
1777         g_strfreev (groups);
1778     }
1779 
1780     if (callback != NULL)
1781         callback (account_manager, NULL, user_data);
1782 }
1783 
1784 GHashTable *
_mcd_account_manager_get_accounts(McdAccountManager * account_manager)1785 _mcd_account_manager_get_accounts (McdAccountManager *account_manager)
1786 {
1787     return account_manager->priv->accounts;
1788 }
1789 
1790 McdAccount *
mcd_account_manager_lookup_account(McdAccountManager * account_manager,const gchar * name)1791 mcd_account_manager_lookup_account (McdAccountManager *account_manager,
1792 				    const gchar *name)
1793 {
1794     McdAccountManagerPrivate *priv = account_manager->priv;
1795 
1796     return g_hash_table_lookup (priv->accounts, name);
1797 }
1798 
1799 McdAccount *
mcd_account_manager_lookup_account_by_path(McdAccountManager * account_manager,const gchar * object_path)1800 mcd_account_manager_lookup_account_by_path (McdAccountManager *account_manager,
1801 					    const gchar *object_path)
1802 {
1803     McdAccountManagerPrivate *priv = account_manager->priv;
1804 
1805     if (!g_str_has_prefix (object_path, TP_ACCOUNT_OBJECT_PATH_BASE))
1806     {
1807         /* can't possibly be right */
1808         return NULL;
1809     }
1810 
1811     return g_hash_table_lookup (priv->accounts,
1812         object_path + strlen (TP_ACCOUNT_OBJECT_PATH_BASE));
1813 }
1814 
1815 /*
1816  * _mcd_account_manager_store_account_connections:
1817  * @account_manager: the #McdAccountManager.
1818  *
1819  * This function is used to remember what connection an account was bound to.
1820  * The data is stored in a temporary file, and can be read when MC restarts
1821  * after a crash.
1822  */
1823 static void
_mcd_account_manager_store_account_connections(McdAccountManager * manager)1824 _mcd_account_manager_store_account_connections (McdAccountManager *manager)
1825 {
1826     McdAccountManagerPrivate *priv;
1827     GHashTableIter iter;
1828     const gchar *account_name, *connection_path, *connection_name;
1829     McdAccount *account;
1830     FILE *file;
1831 
1832     g_return_if_fail (MCD_IS_ACCOUNT_MANAGER (manager));
1833     priv = manager->priv;
1834 
1835     /* make $XDG_CACHE_DIR (or whatever) if it doesn't exist */
1836     g_mkdir_with_parents (priv->account_connections_dir, 0700);
1837     _mcd_chmod_private (priv->account_connections_dir);
1838 
1839     file = fopen (priv->account_connections_file, "w");
1840     if (G_UNLIKELY (!file)) return;
1841 
1842     g_hash_table_iter_init (&iter, priv->accounts);
1843     while (g_hash_table_iter_next (&iter, (gpointer)&account_name,
1844                                    (gpointer)&account))
1845     {
1846         McdConnection *connection;
1847 
1848         connection = mcd_account_get_connection (account);
1849         if (connection)
1850         {
1851             connection_path = mcd_connection_get_object_path (connection);
1852             connection_name = mcd_connection_get_name (connection);
1853             if (connection_path && connection_name)
1854                 fprintf (file, "%s\t%s\t%s\n",
1855                          connection_path, connection_name, account_name);
1856         }
1857     }
1858     fclose (file);
1859 }
1860 
1861 McdStorage *
mcd_account_manager_get_storage(McdAccountManager * account_manager)1862 mcd_account_manager_get_storage (McdAccountManager *account_manager)
1863 {
1864     return account_manager->priv->storage;
1865 }
1866 
1867