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-2011 Collabora Ltd.
8  *
9  * Contact: Naba Kumar  <naba.kumar@nokia.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * version 2.1 as published by the Free Software Foundation.
14  *
15  * This library is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23  * 02110-1301 USA
24  *
25  */
26 
27 /**
28  * SECTION:mcd-dispatcher
29  * @title: McdDispatcher
30  * @short_description: Dispatcher class to dispatch channels to handlers
31  * @see_also:
32  * @stability: Unstable
33  * @include: mcd-dispatcher.h
34  *
35  * FIXME
36  */
37 
38 #include "config.h"
39 
40 #include <dlfcn.h>
41 #include <glib.h>
42 #include <glib/gprintf.h>
43 
44 #include <dbus/dbus-glib-lowlevel.h>
45 
46 #include "mission-control-plugins/mission-control-plugins.h"
47 
48 #include "client-registry.h"
49 #include "mcd-account-priv.h"
50 #include "mcd-client-priv.h"
51 #include "mcd-connection.h"
52 #include "mcd-connection-priv.h"
53 #include "mcd-channel.h"
54 #include "mcd-master.h"
55 #include "mcd-channel-priv.h"
56 #include "mcd-dispatcher-priv.h"
57 #include "mcd-dispatch-operation-priv.h"
58 #include "mcd-handler-map-priv.h"
59 #include "mcd-misc.h"
60 #include "plugin-loader.h"
61 
62 #include "_gen/svc-dispatcher.h"
63 
64 #include <telepathy-glib/telepathy-glib.h>
65 #include <telepathy-glib/telepathy-glib-dbus.h>
66 
67 #include <telepathy-glib/proxy-subclass.h>
68 
69 #include <stdlib.h>
70 #include <string.h>
71 #include "sp_timestamp.h"
72 
73 #define CREATE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".CreateChannel"
74 #define ENSURE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".EnsureChannel"
75 #define SEND_MESSAGE \
76   TP_IFACE_CHANNEL_DISPATCHER ".Interface.Messages.DRAFT.SendMessage"
77 
78 #define MCD_DISPATCHER_PRIV(dispatcher) (MCD_DISPATCHER (dispatcher)->priv)
79 
80 static void dispatcher_iface_init (gpointer, gpointer);
81 static void messages_iface_init (gpointer, gpointer);
82 
83 
84 G_DEFINE_TYPE_WITH_CODE (McdDispatcher, mcd_dispatcher, G_TYPE_OBJECT,
85     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_DISPATCHER,
86                            dispatcher_iface_init);
87     G_IMPLEMENT_INTERFACE (MC_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_MESSAGES_DRAFT,
88                            messages_iface_init);
89     G_IMPLEMENT_INTERFACE (
90         TP_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
91         NULL);
92     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
93                            tp_dbus_properties_mixin_iface_init))
94 
95 typedef struct
96 {
97     McdDispatcher *dispatcher;
98     McdChannel *channel;
99     gint handler_locks;
100     gboolean handled;
101 } McdChannelRecover;
102 
103 typedef struct
104 {
105     McdDispatcher *dispatcher;
106     gchar *account_path;
107     GHashTable *properties;
108     gint64 user_action_time;
109     gchar *preferred_handler;
110     GHashTable *request_metadata;
111     gboolean ensure;
112 } McdChannelRequestACL;
113 
114 struct _McdDispatcherPrivate
115 {
116     /* Dispatching contexts */
117     GList *contexts;
118 
119     /* Channel dispatch operations */
120     GList *operations;
121 
122     TpDBusDaemon *dbus_daemon;
123 
124     /* hash table containing clients
125      * char *bus_name -> McdClientProxy */
126     McdClientRegistry *clients;
127 
128     McdHandlerMap *handler_map;
129 
130     McdMaster *master;
131 
132     /* connection => itself, borrowed */
133     GHashTable *connections;
134 
135     /* Initially FALSE, meaning we suppress OperationList.DispatchOperations
136      * change notification signals because nobody has retrieved that property
137      * yet. Set to TRUE the first time someone reads the DispatchOperations
138      * property. */
139     gboolean operation_list_active;
140 
141     gboolean is_disposed;
142 };
143 
144 struct cancel_call_data
145 {
146     DBusGProxy *handler_proxy;
147     DBusGProxyCall *call;
148     McdDispatcher *dispatcher;
149 };
150 
151 enum
152 {
153     PROP_0,
154     PROP_DBUS_DAEMON,
155     PROP_MCD_MASTER,
156     PROP_INTERFACES,
157     PROP_SUPPORTS_REQUEST_HINTS,
158     PROP_DISPATCH_OPERATIONS,
159 };
160 
161 static void on_operation_finished (McdDispatchOperation *operation,
162                                    McdDispatcher *self);
163 
164 static void
on_master_abort(McdMaster * master,McdDispatcherPrivate * priv)165 on_master_abort (McdMaster *master, McdDispatcherPrivate *priv)
166 {
167     tp_clear_object (&priv->master);
168 }
169 
170 static GStrv
mcd_dispatcher_dup_internal_handlers(void)171 mcd_dispatcher_dup_internal_handlers (void)
172 {
173     const gchar * const internal_handlers[] = { CDO_INTERNAL_HANDLER, NULL };
174 
175     return g_strdupv ((GStrv) internal_handlers);
176 }
177 
178 static GStrv
mcd_dispatcher_dup_possible_handlers(McdDispatcher * self,McdRequest * request,TpChannel * channel,const gchar * must_have_unique_name)179 mcd_dispatcher_dup_possible_handlers (McdDispatcher *self,
180                                       McdRequest *request,
181                                       TpChannel *channel,
182                                       const gchar *must_have_unique_name)
183 {
184     GList *handlers;
185     guint n_handlers;
186     guint i;
187     GStrv ret;
188     const GList *iter;
189     GVariant *request_properties = NULL;
190 
191     if (request != NULL)
192         request_properties = mcd_request_dup_properties (request);
193 
194     handlers = _mcd_client_registry_list_possible_handlers (
195         self->priv->clients,
196         request != NULL ? _mcd_request_get_preferred_handler (request) : NULL,
197         request_properties,
198         channel, must_have_unique_name);
199     n_handlers = g_list_length (handlers);
200 
201     tp_clear_pointer (&request_properties, g_variant_unref);
202 
203     if (handlers == NULL)
204         return NULL;
205 
206     ret = g_new0 (gchar *, n_handlers + 1);
207 
208     for (iter = handlers, i = 0; iter != NULL; iter = iter->next, i++)
209     {
210         ret[i] = g_strdup (tp_proxy_get_bus_name (iter->data));
211     }
212 
213     ret[n_handlers] = NULL;
214 
215     g_list_free (handlers);
216 
217     return ret;
218 }
219 
220 static void
on_operation_finished(McdDispatchOperation * operation,McdDispatcher * self)221 on_operation_finished (McdDispatchOperation *operation,
222                        McdDispatcher *self)
223 {
224     GList *its_link;
225 
226     g_signal_handlers_disconnect_by_func (operation,
227                                           on_operation_finished,
228                                           self);
229 
230     /* don't emit the signal if the CDO never appeared on D-Bus */
231     if (self->priv->operation_list_active &&
232         _mcd_dispatch_operation_needs_approval (operation))
233     {
234         tp_svc_channel_dispatcher_interface_operation_list_emit_dispatch_operation_finished (
235             self, _mcd_dispatch_operation_get_path (operation));
236     }
237 
238     its_link = g_list_find (self->priv->operations, operation);
239 
240     if (its_link != NULL)
241     {
242         self->priv->operations = g_list_delete_link (self->priv->operations,
243                                                      its_link);
244         g_object_unref (operation);
245     }
246 }
247 
248 static void
_mcd_dispatcher_enter_state_machine(McdDispatcher * dispatcher,McdChannel * channel,const gchar * const * possible_handlers,gboolean requested,gboolean only_observe)249 _mcd_dispatcher_enter_state_machine (McdDispatcher *dispatcher,
250                                      McdChannel *channel,
251                                      const gchar * const *possible_handlers,
252                                      gboolean requested,
253                                      gboolean only_observe)
254 {
255     McdDispatchOperation *operation;
256     McdDispatcherPrivate *priv;
257     McdAccount *account;
258 
259     g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
260     g_return_if_fail (MCD_IS_CHANNEL (channel));
261     g_return_if_fail (requested || !only_observe);
262 
263     account = mcd_channel_get_account (channel);
264     if (G_UNLIKELY (!account))
265     {
266         g_warning ("%s called with no account", G_STRFUNC);
267         return;
268     }
269 
270     priv = dispatcher->priv;
271 
272     DEBUG ("new dispatch operation for %s channel %p: %s",
273            requested ? "requested" : "unrequested",
274            channel,
275            mcd_channel_get_object_path (channel));
276 
277     operation = _mcd_dispatch_operation_new (priv->clients,
278         priv->handler_map, !requested, only_observe, channel,
279         (const gchar * const *) possible_handlers);
280 
281     if (!requested)
282     {
283         if (priv->operation_list_active)
284         {
285             tp_svc_channel_dispatcher_interface_operation_list_emit_new_dispatch_operation (
286                 dispatcher,
287                 _mcd_dispatch_operation_get_path (operation),
288                 _mcd_dispatch_operation_get_properties (operation));
289         }
290 
291         priv->operations = g_list_prepend (priv->operations,
292                                            g_object_ref (operation));
293 
294         g_signal_connect (operation, "finished",
295                           G_CALLBACK (on_operation_finished), dispatcher);
296     }
297 
298     if (_mcd_dispatch_operation_get_cancelled (operation))
299     {
300         GError error = { TP_ERROR, TP_ERROR_CANCELLED,
301             "Channel request cancelled" };
302         McdChannel *cancelled;
303 
304         cancelled = _mcd_dispatch_operation_dup_channel (operation);
305 
306         if (cancelled != NULL)
307         {
308             if (mcd_channel_get_error (cancelled) == NULL)
309                 mcd_channel_take_error (cancelled, g_error_copy (&error));
310 
311             _mcd_channel_undispatchable (cancelled);
312 
313             g_object_unref (cancelled);
314         }
315     }
316     else if (_mcd_dispatch_operation_peek_channel (operation) == NULL)
317     {
318         DEBUG ("No channels left");
319     }
320     else
321     {
322         _mcd_dispatch_operation_run_clients (operation);
323     }
324 
325     g_object_unref (operation);
326 }
327 
328 static void
_mcd_dispatcher_set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)329 _mcd_dispatcher_set_property (GObject * obj, guint prop_id,
330 			      const GValue * val, GParamSpec * pspec)
331 {
332     McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj);
333     McdMaster *master;
334 
335     switch (prop_id)
336     {
337     case PROP_DBUS_DAEMON:
338 	tp_clear_object (&priv->dbus_daemon);
339 	priv->dbus_daemon = TP_DBUS_DAEMON (g_value_dup_object (val));
340 	break;
341     case PROP_MCD_MASTER:
342 	master = g_value_get_object (val);
343 	g_object_ref (G_OBJECT (master));
344 	if (priv->master)
345         {
346             g_signal_handlers_disconnect_by_func (G_OBJECT (master), G_CALLBACK (on_master_abort), NULL);
347 	    g_object_unref (priv->master);
348         }
349 	priv->master = master;
350         g_signal_connect (G_OBJECT (master), "abort", G_CALLBACK (on_master_abort), priv);
351 	break;
352     default:
353 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
354 	break;
355     }
356 }
357 
358 static const char * const interfaces[] = {
359     TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
360     NULL
361 };
362 
363 static void
_mcd_dispatcher_get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)364 _mcd_dispatcher_get_property (GObject * obj, guint prop_id,
365 			      GValue * val, GParamSpec * pspec)
366 {
367     McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj);
368 
369     switch (prop_id)
370     {
371     case PROP_DBUS_DAEMON:
372 	g_value_set_object (val, priv->dbus_daemon);
373 	break;
374 
375     case PROP_MCD_MASTER:
376 	g_value_set_object (val, priv->master);
377 	break;
378 
379     case PROP_INTERFACES:
380         g_value_set_static_boxed (val, interfaces);
381         break;
382 
383     case PROP_SUPPORTS_REQUEST_HINTS:
384         g_value_set_boolean (val, TRUE);
385         break;
386 
387     case PROP_DISPATCH_OPERATIONS:
388         {
389             GList *iter;
390             GPtrArray *operations = g_ptr_array_new ();
391 
392             /* Side-effect: from now on, emit change notification signals for
393              * this property */
394             priv->operation_list_active = TRUE;
395 
396             for (iter = priv->operations; iter != NULL; iter = iter->next)
397             {
398                 McdDispatchOperation *op = iter->data;
399 
400                 if (_mcd_dispatch_operation_needs_approval (op) &&
401                     !_mcd_dispatch_operation_is_finished (op))
402                 {
403                     GValueArray *va = g_value_array_new (2);
404 
405                     g_value_array_append (va, NULL);
406                     g_value_array_append (va, NULL);
407 
408                     g_value_init (va->values + 0, DBUS_TYPE_G_OBJECT_PATH);
409                     g_value_init (va->values + 1,
410                                   TP_HASH_TYPE_STRING_VARIANT_MAP);
411 
412                     g_value_set_boxed (va->values + 0,
413                         _mcd_dispatch_operation_get_path (op));
414                     g_value_set_boxed (va->values + 1,
415                         _mcd_dispatch_operation_get_properties (op));
416 
417                     g_ptr_array_add (operations, va);
418                 }
419             }
420 
421             g_value_take_boxed (val, operations);
422         }
423         break;
424 
425     default:
426 	G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
427 	break;
428     }
429 }
430 
431 static void
mcd_dispatcher_client_handling_channel_cb(McdClientProxy * client,const gchar * object_path,McdDispatcher * self)432 mcd_dispatcher_client_handling_channel_cb (McdClientProxy *client,
433                                            const gchar *object_path,
434                                            McdDispatcher *self)
435 {
436     const gchar *bus_name = tp_proxy_get_bus_name (client);
437     const gchar *unique_name = _mcd_client_proxy_get_unique_name (client);
438 
439     if (unique_name == NULL || unique_name[0] == '\0')
440     {
441         /* if it said it was handling channels but it doesn't seem to exist
442          * (or worse, doesn't know whether it exists) then we don't believe
443          * it */
444         DEBUG ("%s doesn't seem to exist, assuming it's not handling %s",
445                bus_name, object_path);
446         return;
447     }
448 
449     DEBUG ("%s (%s) is handling %s", bus_name, unique_name,
450            object_path);
451 
452     _mcd_handler_map_set_path_handled (self->priv->handler_map,
453                                        object_path, unique_name, bus_name);
454 }
455 
456 static void mcd_dispatcher_update_client_caps (McdDispatcher *self,
457                                                McdClientProxy *client);
458 
459 static void
mcd_dispatcher_client_capabilities_changed_cb(McdClientProxy * client,McdDispatcher * self)460 mcd_dispatcher_client_capabilities_changed_cb (McdClientProxy *client,
461                                                McdDispatcher *self)
462 {
463     mcd_dispatcher_update_client_caps (self, client);
464 }
465 
466 static void mcd_dispatcher_client_gone_cb (McdClientProxy *client,
467                                            McdDispatcher *self);
468 
469 static void mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client,
470                                                      McdDispatcher *self);
471 
472 static void
mcd_dispatcher_discard_client(McdDispatcher * self,McdClientProxy * client)473 mcd_dispatcher_discard_client (McdDispatcher *self,
474                                McdClientProxy *client)
475 {
476     g_signal_handlers_disconnect_by_func (client,
477         mcd_dispatcher_client_capabilities_changed_cb, self);
478 
479     g_signal_handlers_disconnect_by_func (client,
480         mcd_dispatcher_client_handling_channel_cb, self);
481 
482     g_signal_handlers_disconnect_by_func (client,
483                                           mcd_dispatcher_client_gone_cb,
484                                           self);
485 
486     g_signal_handlers_disconnect_by_func (client,
487         mcd_dispatcher_client_needs_recovery_cb, self);
488 }
489 
490 static void
mcd_dispatcher_client_gone_cb(McdClientProxy * client,McdDispatcher * self)491 mcd_dispatcher_client_gone_cb (McdClientProxy *client,
492                                McdDispatcher *self)
493 {
494     mcd_dispatcher_discard_client (self, client);
495 }
496 
497 /*
498  * @channel: a non-null TpChannel which has already been dispatched
499  * @request: (allow-none): if not NULL, a request that resulted in
500  *     @channel
501  *
502  * Return a Handler to which @channel could be re-dispatched,
503  * for instance as a result of a re-request or a PresentChannel call.
504  *
505  * If @channel was dispatched to a Handler, return that Handler.
506  * Otherwise, if it was Claimed by a process, and that process
507  * has a Handler to which the channel could have been dispatched,
508  * return that Handler. Otherwise return NULL.
509  */
510 static McdClientProxy *
_mcd_dispatcher_lookup_handler(McdDispatcher * self,TpChannel * channel,McdRequest * request)511 _mcd_dispatcher_lookup_handler (McdDispatcher *self,
512                                 TpChannel *channel,
513                                 McdRequest *request)
514 {
515     McdClientProxy *handler = NULL;
516     const gchar *object_path;
517     const gchar *unique_name;
518     const gchar *well_known_name;
519 
520     object_path = tp_proxy_get_object_path (channel);
521 
522     unique_name = _mcd_handler_map_get_handler (self->priv->handler_map,
523                                                 object_path,
524                                                 &well_known_name);
525 
526     if (unique_name == NULL)
527     {
528         DEBUG ("No process is handling channel %s", object_path);
529         return NULL;
530     }
531 
532     if (well_known_name != NULL)
533     {
534         /* We know which Handler well-known name was responsible: use it if it
535          * still exists */
536         DEBUG ("Channel %s is handler by %s", object_path, well_known_name);
537         handler = _mcd_client_registry_lookup (self->priv->clients,
538                                                well_known_name);
539     }
540 
541     if (handler == NULL)
542     {
543         GList *possible_handlers;
544         GVariant *request_properties = NULL;
545 
546         /* Failing that, maybe the Handler it was dispatched to was temporary;
547          * try to pick another Handler that can deal with it, on the same
548          * unique name (i.e. in the same process).
549          * It can also happen in the case an Observer/Approver Claimed the
550          * channel; in that case we did not get its handler well known name.
551          */
552         if (request != NULL)
553             request_properties = mcd_request_dup_properties (request);
554 
555         possible_handlers = _mcd_client_registry_list_possible_handlers (
556                 self->priv->clients,
557                 request != NULL ? _mcd_request_get_preferred_handler (request) : NULL,
558                 request_properties, channel, unique_name);
559         tp_clear_pointer (&request_properties, g_variant_unref);
560 
561         if (possible_handlers != NULL)
562         {
563             DEBUG ("Pick first possible handler for channel %s", object_path);
564             handler = possible_handlers->data;
565         }
566         else
567         {
568             /* The process is still running (otherwise it wouldn't be in the
569              * handler map), but none of its well-known names is still
570              * interested in channels of that sort. Oh well, not our problem.
571              */
572             DEBUG ("process %s no longer interested in channel %s",
573                    unique_name, object_path);
574         }
575 
576         g_list_free (possible_handlers);
577     }
578 
579     return handler;
580 }
581 
582 static void
mcd_dispatcher_client_needs_recovery_cb(McdClientProxy * client,McdDispatcher * self)583 mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client,
584                                          McdDispatcher *self)
585 {
586     const GList *channels =
587         _mcd_handler_map_get_handled_channels (self->priv->handler_map);
588     const GList *observer_filters;
589     const GList *list;
590 
591     DEBUG ("called");
592 
593     observer_filters = _mcd_client_proxy_get_observer_filters (client);
594 
595     for (list = channels; list; list = list->next)
596     {
597         TpChannel *channel = list->data;
598         const gchar *object_path = tp_proxy_get_object_path (channel);
599         GVariant *properties;
600         McdClientProxy *handler;
601 
602         /* FIXME: This is not exactly the right behaviour, see fd.o#40305 */
603         handler = _mcd_dispatcher_lookup_handler (self, channel, NULL);
604         if (handler && _mcd_client_proxy_get_bypass_observers (handler))
605         {
606             DEBUG ("skipping unobservable channel %s", object_path);
607             continue;
608         }
609 
610         properties = tp_channel_dup_immutable_properties (channel);
611 
612         if (_mcd_client_match_filters (properties, observer_filters,
613             FALSE))
614         {
615             const gchar *account_path =
616                 _mcd_handler_map_get_channel_account (self->priv->handler_map,
617                     tp_proxy_get_object_path (channel));
618 
619             _mcd_client_recover_observer (client, channel, account_path);
620         }
621 
622         g_variant_unref (properties);
623     }
624 
625     /* we also need to think about channels that are still being dispatched,
626      * but have got far enough that this client wouldn't otherwise see them */
627     for (list = self->priv->operations; list != NULL; list = list->next)
628     {
629         McdDispatchOperation *op = list->data;
630 
631         if (_mcd_dispatch_operation_has_invoked_observers (op))
632         {
633             McdChannel *mcd_channel =
634                 _mcd_dispatch_operation_peek_channel (op);
635 
636             if (mcd_channel != NULL)
637             {
638                 GVariant *properties =
639                     mcd_channel_dup_immutable_properties (mcd_channel);
640 
641                 if (_mcd_client_match_filters (properties, observer_filters,
642                         FALSE))
643                 {
644                     _mcd_client_recover_observer (client,
645                         mcd_channel_get_tp_channel (mcd_channel),
646                         _mcd_dispatch_operation_get_account_path (op));
647                 }
648 
649                 g_variant_unref (properties);
650             }
651         }
652     }
653 }
654 
655 static void
mcd_dispatcher_client_added_cb(McdClientRegistry * clients,McdClientProxy * client,McdDispatcher * self)656 mcd_dispatcher_client_added_cb (McdClientRegistry *clients,
657                                 McdClientProxy *client,
658                                 McdDispatcher *self)
659 {
660     g_signal_connect (client, "gone",
661                       G_CALLBACK (mcd_dispatcher_client_gone_cb),
662                       self);
663 
664     g_signal_connect (client, "is-handling-channel",
665                       G_CALLBACK (mcd_dispatcher_client_handling_channel_cb),
666                       self);
667 
668     g_signal_connect (client, "handler-capabilities-changed",
669                       G_CALLBACK (mcd_dispatcher_client_capabilities_changed_cb),
670                       self);
671 
672     g_signal_connect (client, "need-recovery",
673                       G_CALLBACK (mcd_dispatcher_client_needs_recovery_cb),
674                       self);
675 
676 }
677 
678 static void
mcd_dispatcher_client_registry_ready_cb(McdClientRegistry * clients,McdDispatcher * self)679 mcd_dispatcher_client_registry_ready_cb (McdClientRegistry *clients,
680                                          McdDispatcher *self)
681 {
682     GHashTableIter iter;
683     gpointer p;
684     GPtrArray *vas;
685 
686     DEBUG ("All initial clients have been inspected");
687 
688     vas = _mcd_client_registry_dup_client_caps (clients);
689 
690     g_hash_table_iter_init (&iter, self->priv->connections);
691 
692     while (g_hash_table_iter_next (&iter, &p, NULL))
693     {
694         _mcd_connection_start_dispatching (p, vas);
695     }
696 
697     g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
698     g_ptr_array_unref (vas);
699 }
700 
701 static void
drop_each_operation(gpointer operation,gpointer dispatcher)702 drop_each_operation (gpointer operation,
703                      gpointer dispatcher)
704 {
705     g_signal_handlers_disconnect_by_func (operation,
706                                           on_operation_finished,
707                                           dispatcher);
708     g_object_unref (operation);
709 }
710 
711 static void
_mcd_dispatcher_dispose(GObject * object)712 _mcd_dispatcher_dispose (GObject * object)
713 {
714     McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object);
715 
716     if (priv->is_disposed)
717     {
718 	return;
719     }
720     priv->is_disposed = TRUE;
721 
722     if (priv->operations != NULL)
723     {
724         g_list_foreach (priv->operations, drop_each_operation, object);
725         tp_clear_pointer (&priv->operations, g_list_free);
726     }
727 
728     tp_clear_object (&priv->handler_map);
729 
730     if (priv->clients != NULL)
731     {
732         gpointer client_p;
733         GHashTableIter iter;
734 
735         _mcd_client_registry_init_hash_iter (priv->clients, &iter);
736 
737         while (g_hash_table_iter_next (&iter, NULL, &client_p))
738         {
739             mcd_dispatcher_discard_client ((McdDispatcher *) object, client_p);
740         }
741 
742         g_signal_handlers_disconnect_by_func (priv->clients,
743                                               mcd_dispatcher_client_added_cb,
744                                               object);
745 
746         g_signal_handlers_disconnect_by_func (priv->clients,
747             mcd_dispatcher_client_registry_ready_cb, object);
748 
749         tp_clear_object (&priv->clients);
750     }
751 
752     tp_clear_pointer (&priv->connections, g_hash_table_unref);
753     tp_clear_object (&priv->master);
754     tp_clear_object (&priv->dbus_daemon);
755 
756     G_OBJECT_CLASS (mcd_dispatcher_parent_class)->dispose (object);
757 }
758 
759 static void
mcd_dispatcher_update_client_caps(McdDispatcher * self,McdClientProxy * client)760 mcd_dispatcher_update_client_caps (McdDispatcher *self,
761                                    McdClientProxy *client)
762 {
763     GPtrArray *vas;
764     GHashTableIter iter;
765     gpointer k;
766 
767     /* If we haven't finished inspecting initial clients yet, we'll push all
768      * the client caps into all connections when we do, so do nothing.
769      *
770      * If we don't have any connections, on the other hand, then there's
771      * nothing to do. */
772     if (!_mcd_client_registry_is_ready (self->priv->clients)
773         || g_hash_table_size (self->priv->connections) == 0)
774     {
775         return;
776     }
777 
778     vas = g_ptr_array_sized_new (1);
779     g_ptr_array_add (vas, _mcd_client_proxy_dup_handler_capabilities (client));
780 
781     g_hash_table_iter_init (&iter, self->priv->connections);
782 
783     while (g_hash_table_iter_next (&iter, &k, NULL))
784     {
785         _mcd_connection_update_client_caps (k, vas);
786     }
787 
788     g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
789     g_ptr_array_unref (vas);
790 }
791 
792 static void
mcd_dispatcher_constructed(GObject * object)793 mcd_dispatcher_constructed (GObject *object)
794 {
795     DBusGConnection *dgc;
796     McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object);
797     GError *error = NULL;
798 
799     priv->handler_map = _mcd_handler_map_new (priv->dbus_daemon);
800 
801     priv->clients = _mcd_client_registry_new (priv->dbus_daemon);
802     g_signal_connect (priv->clients, "client-added",
803                       G_CALLBACK (mcd_dispatcher_client_added_cb), object);
804     g_signal_connect (priv->clients, "ready",
805                       G_CALLBACK (mcd_dispatcher_client_registry_ready_cb),
806                       object);
807 
808     dgc = tp_proxy_get_dbus_connection (TP_PROXY (priv->dbus_daemon));
809 
810     if (!tp_dbus_daemon_request_name (priv->dbus_daemon,
811                                       TP_CHANNEL_DISPATCHER_BUS_NAME,
812                                       TRUE /* idempotent */, &error))
813     {
814         /* FIXME: put in proper error handling when MC gains the ability to
815          * be the AM or the CD but not both */
816         g_warning ("Failed registering '%s' service: %s",
817                    TP_CHANNEL_DISPATCHER_BUS_NAME, error->message);
818         g_error_free (error);
819         exit (1);
820     }
821 
822     dbus_g_connection_register_g_object (dgc,
823                                          TP_CHANNEL_DISPATCHER_OBJECT_PATH,
824                                          object);
825 }
826 
827 static void
mcd_dispatcher_class_init(McdDispatcherClass * klass)828 mcd_dispatcher_class_init (McdDispatcherClass * klass)
829 {
830     static TpDBusPropertiesMixinPropImpl cd_props[] = {
831         { "Interfaces", "interfaces", NULL },
832         { "SupportsRequestHints", "supports-request-hints", NULL },
833         { NULL }
834     };
835     static TpDBusPropertiesMixinPropImpl op_list_props[] = {
836         { "DispatchOperations", "dispatch-operations", NULL },
837         { NULL }
838     };
839     static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
840         { TP_IFACE_CHANNEL_DISPATCHER,
841           tp_dbus_properties_mixin_getter_gobject_properties,
842           NULL,
843           cd_props,
844         },
845         { TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
846           tp_dbus_properties_mixin_getter_gobject_properties,
847           NULL,
848           op_list_props,
849         },
850         { NULL }
851     };
852     GObjectClass *object_class = G_OBJECT_CLASS (klass);
853 
854     g_type_class_add_private (object_class, sizeof (McdDispatcherPrivate));
855 
856     object_class->constructed = mcd_dispatcher_constructed;
857     object_class->set_property = _mcd_dispatcher_set_property;
858     object_class->get_property = _mcd_dispatcher_get_property;
859     object_class->dispose = _mcd_dispatcher_dispose;
860 
861     /* Properties */
862     g_object_class_install_property
863         (object_class, PROP_DBUS_DAEMON,
864          g_param_spec_object ("dbus-daemon", "DBus daemon", "DBus daemon",
865                               TP_TYPE_DBUS_DAEMON,
866                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
867     g_object_class_install_property
868         (object_class, PROP_MCD_MASTER,
869          g_param_spec_object ("mcd-master", "McdMaster", "McdMaster",
870                               MCD_TYPE_MASTER,
871                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
872 
873     g_object_class_install_property
874         (object_class, PROP_INTERFACES,
875          g_param_spec_boxed ("interfaces", "Interfaces", "Interfaces",
876                              G_TYPE_STRV,
877                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
878 
879     g_object_class_install_property (
880         object_class, PROP_SUPPORTS_REQUEST_HINTS,
881         g_param_spec_boolean ("supports-request-hints", "SupportsRequestHints",
882                               "Yes, we support CreateChannelWithHints etc.",
883                               TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
884 
885     g_object_class_install_property
886         (object_class, PROP_DISPATCH_OPERATIONS,
887          g_param_spec_boxed ("dispatch-operations",
888                              "ChannelDispatchOperation details",
889                              "A dbus-glib a(oa{sv})",
890                              TP_ARRAY_TYPE_DISPATCH_OPERATION_DETAILS_LIST,
891                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
892 
893     klass->dbus_properties_class.interfaces = prop_interfaces,
894     tp_dbus_properties_mixin_class_init (object_class,
895         G_STRUCT_OFFSET (McdDispatcherClass, dbus_properties_class));
896 }
897 
898 static void
mcd_dispatcher_init(McdDispatcher * dispatcher)899 mcd_dispatcher_init (McdDispatcher * dispatcher)
900 {
901     McdDispatcherPrivate *priv;
902 
903     priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher, MCD_TYPE_DISPATCHER,
904                                         McdDispatcherPrivate);
905     dispatcher->priv = priv;
906 
907     priv->operation_list_active = FALSE;
908 
909     priv->connections = g_hash_table_new (NULL, NULL);
910 
911     /* idempotent, not guaranteed to have been called yet */
912     _mcd_plugin_loader_init ();
913 }
914 
915 McdDispatcher *
mcd_dispatcher_new(TpDBusDaemon * dbus_daemon,McdMaster * master)916 mcd_dispatcher_new (TpDBusDaemon *dbus_daemon, McdMaster *master)
917 {
918     McdDispatcher *obj;
919     obj = MCD_DISPATCHER (g_object_new (MCD_TYPE_DISPATCHER,
920 					"dbus-daemon", dbus_daemon,
921 					"mcd-master", master,
922 					NULL));
923     return obj;
924 }
925 
926 /*
927  * _mcd_dispatcher_add_channel:
928  * @dispatcher: the #McdDispatcher.
929  * @channel: (transfer none): a #McdChannel which must own a #TpChannel
930  * @requested: whether the channels were requested by MC.
931  *
932  * Add @channel to the dispatching state machine.
933  */
934 void
_mcd_dispatcher_add_channel(McdDispatcher * dispatcher,McdChannel * channel,gboolean requested,gboolean only_observe)935 _mcd_dispatcher_add_channel (McdDispatcher *dispatcher,
936                              McdChannel *channel,
937                              gboolean requested,
938                              gboolean only_observe)
939 {
940     TpChannel *tp_channel = NULL;
941     GStrv possible_handlers;
942     McdRequest *request = NULL;
943     gboolean internal_request = FALSE;
944 
945     g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
946     g_return_if_fail (MCD_IS_CHANNEL (channel));
947 
948     DEBUG ("%s channel %p: %s",
949            requested ? "requested" : "unrequested",
950            channel,
951            mcd_channel_get_object_path (channel));
952 
953     if (only_observe)
954     {
955         g_return_if_fail (requested);
956 
957         /* these channels were requested "behind our back", so only call
958          * ObserveChannels on them */
959         _mcd_dispatcher_enter_state_machine (dispatcher, channel, NULL,
960                                              TRUE, TRUE);
961         return;
962     }
963 
964     /* The channel must have the TpChannel part of McdChannel's double life.
965      * It might also have the McdRequest part. */
966     tp_channel = mcd_channel_get_tp_channel (channel);
967     g_assert (tp_channel != NULL);
968 
969     request = _mcd_channel_get_request (channel);
970     internal_request = _mcd_request_is_internal (request);
971 
972     /* See if there are any handlers that can take all these channels */
973     if (internal_request)
974         possible_handlers = mcd_dispatcher_dup_internal_handlers ();
975     else
976         possible_handlers = mcd_dispatcher_dup_possible_handlers (dispatcher,
977                                                                   request,
978                                                                   tp_channel,
979                                                                   NULL);
980 
981     if (possible_handlers == NULL)
982     {
983         DEBUG ("Channel cannot be handled - making a CDO "
984                "anyway, to get Observers run");
985     }
986     else
987     {
988         DEBUG ("%s handler(s) found, dispatching channel",
989                internal_request ? "internal" : "possible");
990     }
991 
992     _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHING);
993 
994     _mcd_dispatcher_enter_state_machine (dispatcher, channel,
995         (const gchar * const *) possible_handlers, requested, FALSE);
996 
997     g_strfreev (possible_handlers);
998 }
999 
1000 static void
mcd_dispatcher_finish_reinvocation(McdChannel * request)1001 mcd_dispatcher_finish_reinvocation (McdChannel *request)
1002 {
1003     _mcd_channel_set_status (request, MCD_CHANNEL_STATUS_DISPATCHED);
1004     /* no need to keep it around any more */
1005     mcd_mission_abort (MCD_MISSION (request));
1006 }
1007 
1008 static void
reinvoke_handle_channels_cb(TpClient * client,const GError * error,gpointer user_data G_GNUC_UNUSED,GObject * weak_object)1009 reinvoke_handle_channels_cb (TpClient *client,
1010                              const GError *error,
1011                              gpointer user_data G_GNUC_UNUSED,
1012                              GObject *weak_object)
1013 {
1014     McdChannel *request = MCD_CHANNEL (weak_object);
1015 
1016     if (error != NULL)
1017     {
1018         /* The handler is already dealing with this channel, but refuses to
1019          * re-handle it. Oh well, we tried. */
1020         DEBUG ("handler %s refused re-notification about channel %p:%s: "
1021                "%s:%d: %s", tp_proxy_get_bus_name (client),
1022                request, mcd_channel_get_object_path (request),
1023                g_quark_to_string (error->domain), error->code, error->message);
1024     }
1025     else
1026     {
1027         DEBUG ("handler %s successfully notified about channel %p:%s",
1028                tp_proxy_get_bus_name (client),
1029                request, mcd_channel_get_object_path (request));
1030     }
1031 
1032     /* either way, consider the request to have been dispatched */
1033     mcd_dispatcher_finish_reinvocation (request);
1034 }
1035 
1036 /*
1037  * _mcd_dispatcher_reinvoke_handler:
1038  * @dispatcher: The #McdDispatcher.
1039  * @request: a #McdChannel that has both a #TpChannel and a #McdRequest
1040  *
1041  * Re-invoke the channel handler for @request.
1042  */
1043 static void
_mcd_dispatcher_reinvoke_handler(McdDispatcher * dispatcher,McdChannel * request)1044 _mcd_dispatcher_reinvoke_handler (McdDispatcher *dispatcher,
1045                                   McdChannel *request)
1046 {
1047     GList *request_as_list;
1048     McdClientProxy *handler = NULL;
1049     McdRequest *real_request = _mcd_channel_get_request (request);
1050     TpChannel *tp_channel = mcd_channel_get_tp_channel (request);
1051     GHashTable *handler_info;
1052     GHashTable *request_properties;
1053 
1054     g_assert (real_request != NULL);
1055     g_assert (tp_channel != NULL);
1056 
1057     request_as_list = g_list_append (NULL, request);
1058 
1059     request_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
1060         g_free, (GDestroyNotify) g_hash_table_unref);
1061     g_hash_table_insert (request_properties,
1062         g_strdup (_mcd_request_get_object_path (real_request)),
1063         _mcd_request_dup_immutable_properties (real_request));
1064 
1065     handler_info = tp_asv_new (NULL, NULL);
1066     /* hand over ownership of request_properties */
1067     tp_asv_take_boxed (handler_info, "request-properties",
1068                        TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP,
1069                        request_properties);
1070     request_properties = NULL;
1071 
1072     handler = _mcd_dispatcher_lookup_handler (dispatcher,
1073             tp_channel, real_request);
1074     if (handler == NULL)
1075     {
1076         mcd_dispatcher_finish_reinvocation (request);
1077         goto finally;
1078     }
1079 
1080     /* This is deliberately not the same call as for normal dispatching,
1081      * and it doesn't go through a dispatch operation - the error handling
1082      * is completely different, because the channel is already being
1083      * handled perfectly well. */
1084 
1085     _mcd_client_proxy_handle_channels (handler,
1086         -1, request_as_list,
1087         0, /* the request's user action time will be used automatically */
1088         handler_info,
1089         reinvoke_handle_channels_cb, NULL, NULL, (GObject *) request);
1090 
1091 finally:
1092     g_hash_table_unref (handler_info);
1093     g_list_free (request_as_list);
1094 }
1095 
1096 static McdDispatchOperation *
find_operation_from_channel(McdDispatcher * dispatcher,McdChannel * channel)1097 find_operation_from_channel (McdDispatcher *dispatcher,
1098                              McdChannel *channel)
1099 {
1100     GList *list;
1101 
1102     g_return_val_if_fail (MCD_IS_CHANNEL (channel), NULL);
1103     for (list = dispatcher->priv->operations; list != NULL; list = list->next)
1104     {
1105         McdDispatchOperation *op = list->data;
1106 
1107         if (_mcd_dispatch_operation_has_channel (op, channel))
1108             return op;
1109     }
1110     return NULL;
1111 }
1112 
1113 void
_mcd_dispatcher_add_channel_request(McdDispatcher * dispatcher,McdChannel * channel,McdChannel * request)1114 _mcd_dispatcher_add_channel_request (McdDispatcher *dispatcher,
1115                                      McdChannel *channel, McdChannel *request)
1116 {
1117     McdChannelStatus status;
1118     McdRequest *origin = _mcd_channel_get_request (request);
1119     gboolean internal = _mcd_request_is_internal (origin);
1120 
1121     status = mcd_channel_get_status (channel);
1122 
1123     /* if the channel is already dispatched, just reinvoke the handler; if it
1124      * is not, @request must mirror the status of @channel */
1125     if (status == MCD_CHANNEL_STATUS_DISPATCHED)
1126     {
1127         DEBUG ("reinvoking handler on channel %p", request);
1128 
1129         /* copy the object path and the immutable properties from the
1130          * existing channel */
1131         _mcd_channel_copy_details (request, channel);
1132 
1133         if (internal)
1134           _mcd_request_handle_internally (origin, request, FALSE);
1135         else
1136           _mcd_dispatcher_reinvoke_handler (dispatcher, request);
1137     }
1138     else
1139     {
1140         DEBUG ("non-reinvoked handling of channel %p", request);
1141         _mcd_channel_set_request_proxy (request, channel);
1142 
1143         if (internal)
1144         {
1145             _mcd_request_handle_internally (origin, request, FALSE);
1146         }
1147         else if (status == MCD_CHANNEL_STATUS_DISPATCHING)
1148         {
1149             McdDispatchOperation *op = find_operation_from_channel (dispatcher,
1150                                                                     channel);
1151             const gchar *preferred_handler =
1152               _mcd_channel_get_request_preferred_handler (request);
1153 
1154             g_return_if_fail (op != NULL);
1155 
1156             DEBUG ("channel %p is in CDO %p", channel, op);
1157             _mcd_dispatch_operation_approve (op, preferred_handler);
1158         }
1159         DEBUG ("channel %p is proxying %p", request, channel);
1160     }
1161 }
1162 
1163 void
_mcd_dispatcher_recover_channel(McdDispatcher * dispatcher,McdChannel * channel,const gchar * account_path)1164 _mcd_dispatcher_recover_channel (McdDispatcher *dispatcher,
1165                                  McdChannel *channel,
1166                                  const gchar *account_path)
1167 {
1168     McdDispatcherPrivate *priv;
1169     const gchar *path;
1170     const gchar *unique_name;
1171     const gchar *well_known_name = NULL;
1172     gboolean requested;
1173     TpChannel *tp_channel;
1174 
1175     /* we must check if the channel is already being handled by some client; to
1176      * do this, we can examine the active handlers' "HandledChannel" property.
1177      * By now, we should already have done this, because startup has completed.
1178      */
1179     g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
1180     priv = dispatcher->priv;
1181     g_return_if_fail (_mcd_client_registry_is_ready (
1182         dispatcher->priv->clients));
1183 
1184     path = mcd_channel_get_object_path (channel);
1185     tp_channel = mcd_channel_get_tp_channel (channel);
1186     g_return_if_fail (tp_channel != NULL);
1187 
1188     unique_name = _mcd_handler_map_get_handler (priv->handler_map, path,
1189                                                 &well_known_name);
1190 
1191     if (unique_name != NULL)
1192     {
1193         DEBUG ("Channel %s is already handled by process %s",
1194                path, unique_name);
1195         _mcd_channel_set_status (channel,
1196                                  MCD_CHANNEL_STATUS_DISPATCHED);
1197         _mcd_handler_map_set_channel_handled (priv->handler_map, tp_channel,
1198                                               unique_name, well_known_name,
1199                                               account_path);
1200     }
1201     else
1202     {
1203         DEBUG ("%s is unhandled, redispatching", path);
1204 
1205         requested = mcd_channel_is_requested (channel);
1206         _mcd_dispatcher_add_channel (dispatcher, channel, requested, FALSE);
1207     }
1208 }
1209 
1210 static gboolean
check_preferred_handler(const gchar * preferred_handler,GError ** error)1211 check_preferred_handler (const gchar *preferred_handler,
1212     GError **error)
1213 {
1214   g_assert (error != NULL);
1215 
1216   if (preferred_handler[0] == '\0')
1217       return TRUE;
1218 
1219   if (!tp_dbus_check_valid_bus_name (preferred_handler,
1220                                      TP_DBUS_NAME_TYPE_WELL_KNOWN,
1221                                      error))
1222   {
1223       /* The error is TP_DBUS_ERROR_INVALID_BUS_NAME, which has no D-Bus
1224        * representation; re-map to InvalidArgument. */
1225       (*error)->domain = TP_ERROR;
1226       (*error)->code = TP_ERROR_INVALID_ARGUMENT;
1227       return FALSE;
1228   }
1229 
1230   if (!g_str_has_prefix (preferred_handler, TP_CLIENT_BUS_NAME_BASE))
1231   {
1232       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1233                    "Not a Telepathy Client: %s", preferred_handler);
1234       return FALSE;
1235   }
1236 
1237   return TRUE;
1238 }
1239 
1240 static void
dispatcher_request_channel(McdDispatcher * self,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * request_metadata,DBusGMethodInvocation * context,gboolean ensure)1241 dispatcher_request_channel (McdDispatcher *self,
1242                             const gchar *account_path,
1243                             GHashTable *requested_properties,
1244                             gint64 user_action_time,
1245                             const gchar *preferred_handler,
1246                             GHashTable *request_metadata,
1247                             DBusGMethodInvocation *context,
1248                             gboolean ensure)
1249 {
1250     McdAccountManager *am;
1251     McdAccount *account;
1252     McdChannel *channel = NULL;
1253     McdRequest *request = NULL;
1254     GError *error = NULL;
1255     const gchar *path;
1256 
1257     g_return_if_fail (account_path != NULL);
1258     g_return_if_fail (requested_properties != NULL);
1259     g_return_if_fail (preferred_handler != NULL);
1260 
1261     g_object_get (self->priv->master,
1262                   "account-manager", &am,
1263                   NULL);
1264 
1265     g_assert (am != NULL);
1266 
1267     account = mcd_account_manager_lookup_account_by_path (am,
1268                                                           account_path);
1269 
1270     if (account == NULL)
1271     {
1272         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1273                      "No such account: %s", account_path);
1274         goto despair;
1275     }
1276 
1277     if (!check_preferred_handler (preferred_handler, &error))
1278         goto despair;
1279 
1280     channel = _mcd_account_create_request (self->priv->clients,
1281                                            account, requested_properties,
1282                                            user_action_time, preferred_handler,
1283                                            request_metadata, ensure,
1284                                            &request, &error);
1285 
1286     if (channel == NULL)
1287     {
1288         /* FIXME: ideally this would be emitted as a Failed signal after
1289          * Proceed is called, but for the particular failure case here (low
1290          * memory) perhaps we don't want to */
1291         goto despair;
1292     }
1293 
1294     g_assert (request != NULL);
1295     path = _mcd_request_get_object_path (request);
1296     g_assert (path != NULL);
1297 
1298     /* This is OK because the signatures of CreateChannel and EnsureChannel
1299      * are the same */
1300     tp_svc_channel_dispatcher_return_from_create_channel (context, path);
1301 
1302     _mcd_request_predict_handler (request);
1303 
1304     /* We've done all we need to with this channel: the ChannelRequests code
1305      * keeps it alive as long as is necessary. The finally clause will
1306      * free it */
1307     goto finally;
1308 
1309 despair:
1310     dbus_g_method_return_error (context, error);
1311     g_error_free (error);
1312 
1313 finally:
1314     tp_clear_object (&channel);
1315     tp_clear_object (&request);
1316     g_object_unref (am);
1317 }
1318 
1319 static void
dispatcher_channel_request_acl_cleanup(gpointer data)1320 dispatcher_channel_request_acl_cleanup (gpointer data)
1321 {
1322     McdChannelRequestACL *crd = data;
1323 
1324     DEBUG ("cleanup acl (%p)", data);
1325 
1326     g_free (crd->account_path);
1327     g_free (crd->preferred_handler);
1328     g_hash_table_unref (crd->properties);
1329     g_object_unref (crd->dispatcher);
1330     tp_clear_pointer (&crd->request_metadata, g_hash_table_unref);
1331 
1332     g_slice_free (McdChannelRequestACL, crd);
1333 }
1334 
1335 static void
dispatcher_channel_request_acl_success(DBusGMethodInvocation * context,gpointer data)1336 dispatcher_channel_request_acl_success (DBusGMethodInvocation *context,
1337                                         gpointer data)
1338 {
1339     McdChannelRequestACL *crd = data;
1340 
1341     DEBUG ("complete acl (%p)", crd);
1342 
1343     dispatcher_request_channel (MCD_DISPATCHER (crd->dispatcher),
1344                                 crd->account_path,
1345                                 crd->properties,
1346                                 crd->user_action_time,
1347                                 crd->preferred_handler,
1348                                 crd->request_metadata,
1349                                 context,
1350                                 crd->ensure);
1351 }
1352 
1353 static void
free_gvalue(gpointer gvalue)1354 free_gvalue (gpointer gvalue)
1355 {
1356     GValue *gv = gvalue;
1357 
1358     g_value_unset (gv);
1359     g_slice_free (GValue, gv);
1360 }
1361 
1362 static void
dispatcher_channel_request_acl_start(McdDispatcher * dispatcher,const gchar * method,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * request_metadata,DBusGMethodInvocation * context,gboolean ensure)1363 dispatcher_channel_request_acl_start (McdDispatcher *dispatcher,
1364                                       const gchar *method,
1365                                       const gchar *account_path,
1366                                       GHashTable *requested_properties,
1367                                       gint64 user_action_time,
1368                                       const gchar *preferred_handler,
1369                                       GHashTable *request_metadata,
1370                                       DBusGMethodInvocation *context,
1371                                       gboolean ensure)
1372 {
1373     McdChannelRequestACL *crd = g_slice_new0 (McdChannelRequestACL);
1374     GValue *account = g_slice_new0 (GValue);
1375     GHashTable *params =
1376       g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_gvalue);
1377 
1378     g_value_init (account, G_TYPE_STRING);
1379     g_value_set_string (account, account_path);
1380     g_hash_table_insert (params, "account-path", account);
1381 
1382     crd->dispatcher = g_object_ref (dispatcher);
1383     crd->account_path = g_strdup (account_path);
1384     crd->preferred_handler = g_strdup (preferred_handler);
1385     crd->properties = g_hash_table_ref (requested_properties);
1386     crd->user_action_time = user_action_time;
1387     crd->ensure = ensure;
1388     crd->request_metadata = request_metadata != NULL ?
1389         g_hash_table_ref (request_metadata) : NULL;
1390 
1391     DEBUG ("start %s.%s acl (%p)", account_path, method, crd);
1392 
1393     mcp_dbus_acl_authorised_async (dispatcher->priv->dbus_daemon,
1394                                    context,
1395                                    DBUS_ACL_TYPE_METHOD,
1396                                    method,
1397                                    params,
1398                                    dispatcher_channel_request_acl_success,
1399                                    crd,
1400                                    dispatcher_channel_request_acl_cleanup);
1401 
1402     g_hash_table_unref (params);
1403 }
1404 
1405 static void
dispatcher_create_channel(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)1406 dispatcher_create_channel (TpSvcChannelDispatcher *iface,
1407                            const gchar *account_path,
1408                            GHashTable *requested_properties,
1409                            gint64 user_action_time,
1410                            const gchar *preferred_handler,
1411                            DBusGMethodInvocation *context)
1412 {
1413     dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1414                                           CREATE_CHANNEL,
1415                                           account_path,
1416                                           requested_properties,
1417                                           user_action_time,
1418                                           preferred_handler,
1419                                           NULL,
1420                                           context,
1421                                           FALSE);
1422 }
1423 
1424 static void
dispatcher_ensure_channel(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)1425 dispatcher_ensure_channel (TpSvcChannelDispatcher *iface,
1426                            const gchar *account_path,
1427                            GHashTable *requested_properties,
1428                            gint64 user_action_time,
1429                            const gchar *preferred_handler,
1430                            DBusGMethodInvocation *context)
1431 {
1432     dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1433                                           ENSURE_CHANNEL,
1434                                           account_path,
1435                                           requested_properties,
1436                                           user_action_time,
1437                                           preferred_handler,
1438                                           NULL,
1439                                           context,
1440                                           TRUE);
1441 }
1442 
1443 static void
dispatcher_create_channel_with_hints(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * hints,DBusGMethodInvocation * context)1444 dispatcher_create_channel_with_hints (TpSvcChannelDispatcher *iface,
1445     const gchar *account_path,
1446     GHashTable *requested_properties,
1447     gint64 user_action_time,
1448     const gchar *preferred_handler,
1449     GHashTable *hints,
1450     DBusGMethodInvocation *context)
1451 {
1452     dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1453                                           CREATE_CHANNEL,
1454                                           account_path,
1455                                           requested_properties,
1456                                           user_action_time,
1457                                           preferred_handler,
1458                                           hints,
1459                                           context,
1460                                           FALSE);
1461 }
1462 
1463 static void
dispatcher_ensure_channel_with_hints(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * hints,DBusGMethodInvocation * context)1464 dispatcher_ensure_channel_with_hints (TpSvcChannelDispatcher *iface,
1465     const gchar *account_path,
1466     GHashTable *requested_properties,
1467     gint64 user_action_time,
1468     const gchar *preferred_handler,
1469     GHashTable *hints,
1470     DBusGMethodInvocation *context)
1471 {
1472     dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1473                                           ENSURE_CHANNEL,
1474                                           account_path,
1475                                           requested_properties,
1476                                           user_action_time,
1477                                           preferred_handler,
1478                                           hints,
1479                                           context,
1480                                           TRUE);
1481 }
1482 
1483 
1484 static void
mcd_dispatcher_lost_connection(gpointer data,GObject * corpse)1485 mcd_dispatcher_lost_connection (gpointer data,
1486                                 GObject *corpse)
1487 {
1488     McdDispatcher *self = MCD_DISPATCHER (data);
1489 
1490     /* not safe to dereference corpse any more, so just print its address -
1491      * that's enough to pair up with add_connection calls */
1492     DEBUG ("%p: %p", self, corpse);
1493 
1494     g_hash_table_remove (self->priv->connections, corpse);
1495     g_object_unref (self);
1496 }
1497 
1498 /* FIXME: this only needs to exist because McdConnection calls it in order
1499  * to preload caps before Connect */
1500 GPtrArray *
_mcd_dispatcher_dup_client_caps(McdDispatcher * self)1501 _mcd_dispatcher_dup_client_caps (McdDispatcher *self)
1502 {
1503     g_return_val_if_fail (MCD_IS_DISPATCHER (self), NULL);
1504 
1505     /* if we're not ready, return NULL to tell the connection not to preload */
1506     if (!_mcd_client_registry_is_ready (self->priv->clients))
1507     {
1508         return NULL;
1509     }
1510 
1511     return _mcd_client_registry_dup_client_caps (self->priv->clients);
1512 }
1513 
1514 void
_mcd_dispatcher_add_connection(McdDispatcher * self,McdConnection * connection)1515 _mcd_dispatcher_add_connection (McdDispatcher *self,
1516                                 McdConnection *connection)
1517 {
1518     g_return_if_fail (MCD_IS_DISPATCHER (self));
1519 
1520     DEBUG ("%p: %p (%s)", self, connection,
1521            mcd_connection_get_object_path (connection));
1522 
1523     g_hash_table_insert (self->priv->connections, connection, connection);
1524     g_object_weak_ref ((GObject *) connection, mcd_dispatcher_lost_connection,
1525                        g_object_ref (self));
1526 
1527     if (_mcd_client_registry_is_ready (self->priv->clients))
1528     {
1529         GPtrArray *vas =
1530             _mcd_client_registry_dup_client_caps (self->priv->clients);
1531 
1532         _mcd_connection_start_dispatching (connection, vas);
1533 
1534         g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
1535         g_ptr_array_unref (vas);
1536     }
1537     /* else _mcd_connection_start_dispatching will be called when we're ready
1538      * for it */
1539 }
1540 
1541 /* org.freedesktop.Telepathy.ChannelDispatcher.Messages */
1542 typedef struct
1543 {
1544     McdDispatcher *dispatcher;
1545     gchar *account_path;
1546     gchar *target_id;
1547     GPtrArray *payload;
1548     guint flags;
1549     guint tries;
1550     gboolean close_after;
1551     DBusGMethodInvocation *dbus_context;
1552 } MessageContext;
1553 
1554 static MessageContext *
message_context_steal(MessageContext * from)1555 message_context_steal (MessageContext *from)
1556 {
1557     MessageContext *stolen = g_slice_new0 (MessageContext);
1558 
1559     g_memmove (stolen, from, sizeof (MessageContext));
1560     memset (from, 0, sizeof (MessageContext));
1561 
1562     return stolen;
1563 }
1564 
1565 static MessageContext *
message_context_new(McdDispatcher * dispatcher,const gchar * account_path,const gchar * target_id,const GPtrArray * payload,guint flags)1566 message_context_new (McdDispatcher *dispatcher,
1567                      const gchar *account_path,
1568                      const gchar *target_id,
1569                      const GPtrArray *payload,
1570                      guint flags)
1571 {
1572     guint i;
1573     const guint size = payload->len;
1574     MessageContext *context = g_slice_new0 (MessageContext);
1575     GPtrArray *msg_copy = g_ptr_array_sized_new (size);
1576 
1577     g_ptr_array_set_free_func (msg_copy, (GDestroyNotify) g_hash_table_unref);
1578 
1579     for (i = 0; i < size; i++)
1580     {
1581         GHashTable *part = g_ptr_array_index (payload, i);
1582 
1583         g_ptr_array_add (msg_copy, _mcd_deepcopy_asv (part));
1584     }
1585 
1586     context->dispatcher = g_object_ref (dispatcher);
1587     context->account_path = g_strdup (account_path);
1588     context->target_id = g_strdup (target_id);
1589     context->payload = msg_copy;
1590     context->flags = flags;
1591     context->dbus_context = NULL;
1592 
1593     return context;
1594 }
1595 
1596 static void
message_context_return_error(MessageContext * context,const GError * error)1597 message_context_return_error (MessageContext *context, const GError *error)
1598 {
1599     if (context->dbus_context == NULL)
1600         return;
1601 
1602     dbus_g_method_return_error (context->dbus_context, error);
1603     context->dbus_context = NULL;
1604 }
1605 
1606 static void
message_context_set_return_context(MessageContext * context,DBusGMethodInvocation * dbus_context)1607 message_context_set_return_context (MessageContext *context,
1608                                     DBusGMethodInvocation *dbus_context)
1609 {
1610     context->dbus_context = dbus_context;
1611 }
1612 
1613 static void
message_context_free(gpointer ctx)1614 message_context_free (gpointer ctx)
1615 {
1616     MessageContext *context = ctx;
1617 
1618     tp_clear_pointer (&context->payload, g_ptr_array_unref);
1619     tp_clear_pointer (&context->account_path, g_free);
1620     tp_clear_pointer (&context->target_id, g_free);
1621 
1622     if (context->dbus_context != NULL)
1623     {
1624         GError *error;
1625 
1626         error = g_error_new_literal (TP_ERROR, TP_ERROR_TERMINATED,
1627                                      "Channel request failed");
1628         dbus_g_method_return_error (context->dbus_context, error);
1629         g_error_free (error);
1630     }
1631 
1632     tp_clear_object (&context->dispatcher);
1633 
1634     g_slice_free (MessageContext, context);
1635 }
1636 
1637 static void
send_message_submitted(TpChannel * proxy,const gchar * token,const GError * error,gpointer data,GObject * weak)1638 send_message_submitted (TpChannel *proxy,
1639                         const gchar *token,
1640                         const GError *error,
1641                         gpointer data,
1642                         GObject *weak)
1643 {
1644     MessageContext *message = data;
1645     DBusGMethodInvocation *context = message->dbus_context;
1646     McdChannel *channel = MCD_CHANNEL (weak);
1647     McdRequest *request = _mcd_channel_get_request (channel);
1648     gboolean close_after = message->close_after;
1649 
1650     /* this frees the dbus context, so clear it from our cache afterwards */
1651     if (error == NULL)
1652     {
1653         mc_svc_channel_dispatcher_interface_messages_draft_return_from_send_message (context, token);
1654         message_context_set_return_context (message, NULL);
1655     }
1656     else
1657     {
1658         DEBUG ("error: %s", error->message);
1659         message_context_return_error (message, error);
1660     }
1661 
1662     _mcd_request_unblock_account (message->account_path);
1663     _mcd_request_clear_internal_handler (request);
1664 
1665     if (close_after)
1666         _mcd_channel_close (channel);
1667 }
1668 
1669 static void messages_send_message_start (DBusGMethodInvocation *context,
1670                                          MessageContext *message);
1671 
1672 static void
send_message_got_channel(McdRequest * request,McdChannel * channel,gpointer data,gboolean close_after)1673 send_message_got_channel (McdRequest *request,
1674                           McdChannel *channel,
1675                           gpointer data,
1676                           gboolean close_after)
1677 {
1678     MessageContext *message = data;
1679 
1680     DEBUG ("received internal request/channel");
1681 
1682     /* successful channel creation */
1683     if (channel != NULL)
1684     {
1685         message->close_after = close_after;
1686 
1687         DEBUG ("calling send on channel interface");
1688         tp_cli_channel_interface_messages_call_send_message
1689           (mcd_channel_get_tp_channel (channel),
1690            -1,
1691            message->payload,
1692            message->flags,
1693            send_message_submitted,
1694            message,
1695            NULL,
1696            G_OBJECT (channel));
1697     }
1698     else /* doom and despair: no channel */
1699     {
1700         if (message->tries++ == 0)
1701         {
1702             messages_send_message_start (message->dbus_context, message);
1703             /* we created a new lock above, we can now release the old one: */
1704             _mcd_request_unblock_account (message->account_path);
1705         }
1706         else
1707         {
1708             GError *error = g_error_new_literal (TP_ERROR, TP_ERROR_CANCELLED,
1709                                                  "Channel closed by owner");
1710 
1711             _mcd_request_unblock_account (message->account_path);
1712             message_context_return_error (message, error);
1713             _mcd_request_clear_internal_handler (request);
1714             g_error_free (error);
1715         }
1716     }
1717 }
1718 
1719 static void
messages_send_message_acl_success(DBusGMethodInvocation * dbus_context,gpointer data)1720 messages_send_message_acl_success (DBusGMethodInvocation *dbus_context,
1721                                    gpointer data)
1722 {
1723     /* steal the contents of the message context from the ACL framework: *
1724      * this avoids a nasty double-free (and means we don't have to dup   *
1725      * the message payload memory twice)                                 */
1726     messages_send_message_start (dbus_context, message_context_steal (data));
1727 }
1728 
1729 static void
messages_send_message_start(DBusGMethodInvocation * dbus_context,MessageContext * message)1730 messages_send_message_start (DBusGMethodInvocation *dbus_context,
1731                              MessageContext *message)
1732 {
1733     McdAccountManager *am;
1734     McdAccount *account;
1735     McdChannel *channel = NULL;
1736     McdRequest *request = NULL;
1737     GError *error = NULL;
1738     GHashTable *props = NULL;
1739     GValue c_type = G_VALUE_INIT;
1740     GValue h_type = G_VALUE_INIT;
1741     GValue target = G_VALUE_INIT;
1742     McdDispatcher *self = message->dispatcher;
1743 
1744     DEBUG ("messages_send_message_acl_success [attempt #%u]", message->tries);
1745     /* the message request can now take posession of the dbus method context */
1746     message_context_set_return_context (message, dbus_context);
1747 
1748     if (tp_str_empty (message->account_path))
1749     {
1750         g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1751                              "Account path not specified");
1752         goto failure;
1753     }
1754 
1755     g_object_get (self->priv->master, "account-manager", &am, NULL);
1756 
1757     g_assert (am != NULL);
1758 
1759     account =
1760       mcd_account_manager_lookup_account_by_path (am, message->account_path);
1761 
1762     if (account == NULL)
1763     {
1764         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1765                      "No such account: %s", message->account_path);
1766         goto failure;
1767     }
1768 
1769     props = g_hash_table_new_full (g_str_hash, g_str_equal,
1770         NULL, (GDestroyNotify) g_value_unset);
1771 
1772     g_value_init (&c_type, G_TYPE_STRING);
1773     g_value_init (&h_type, G_TYPE_UINT);
1774     g_value_init (&target, G_TYPE_STRING);
1775 
1776     g_value_set_static_string (&c_type, TP_IFACE_CHANNEL_TYPE_TEXT);
1777     g_value_set_uint (&h_type, TP_HANDLE_TYPE_CONTACT);
1778     g_value_set_string (&target, message->target_id);
1779 
1780     g_hash_table_insert (props, TP_PROP_CHANNEL_CHANNEL_TYPE, &c_type);
1781     g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, &h_type);
1782     g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_ID, &target);
1783 
1784     /* compare dispatcher_request_channel: we _are_ the handler for     *
1785      * this channel so we don't need to check_preferred_handler here    *
1786      * Also: this deep-copies the props hash, so we can throw ours away */
1787     channel = _mcd_account_create_request (self->priv->clients,
1788                                            account, props, time (NULL),
1789                                            NULL, NULL, TRUE,
1790                                            &request, &error);
1791     g_hash_table_unref (props);
1792 
1793     if (channel == NULL || request == NULL)
1794     {
1795         g_set_error (&error, TP_ERROR, TP_ERROR_RESOURCE_UNAVAILABLE,
1796                      "Could not create channel request");
1797         goto failure;
1798     }
1799 
1800     _mcd_request_set_internal_handler (request,
1801                                        send_message_got_channel,
1802                                        message_context_free,
1803                                        message);
1804 
1805     /* we don't need to predict the handler either, same reason as above  *
1806      * we do, however, want to call proceed on the request, as it is ours */
1807     _mcd_request_proceed (request, NULL);
1808 
1809     goto finished;
1810 
1811 failure:
1812     message_context_return_error (message, error);
1813     message_context_free (message);
1814     g_error_free (error);
1815 
1816 finished:
1817     /* these are reffed and held open by the request infrastructure */
1818     tp_clear_object (&channel);
1819     tp_clear_object (&request);
1820 }
1821 
1822 static void
messages_send_message_acl_cleanup(gpointer data)1823 messages_send_message_acl_cleanup (gpointer data)
1824 {
1825     MessageContext *message = data;
1826 
1827     /* At this point either the messages framework or the ACL framework   *
1828      * is expected to have handled the DBus return, so we must not try to */
1829     message_context_set_return_context (message, NULL);
1830     message_context_free (message);
1831 }
1832 
1833 static void
messages_send_message(McSvcChannelDispatcherInterfaceMessagesDraft * iface,const gchar * account_path,const gchar * target_id,const GPtrArray * payload,guint flags,DBusGMethodInvocation * context)1834 messages_send_message (McSvcChannelDispatcherInterfaceMessagesDraft *iface,
1835                        const gchar *account_path,
1836                        const gchar *target_id,
1837                        const GPtrArray *payload,
1838                        guint flags,
1839                        DBusGMethodInvocation *context)
1840 {
1841     McdDispatcher *self= MCD_DISPATCHER (iface);
1842     MessageContext *message =
1843       message_context_new (self, account_path, target_id, payload, flags);
1844 
1845     /* these are for the ACL itself */
1846     GValue *account = g_slice_new0 (GValue);
1847     GHashTable *params =
1848       g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_gvalue);
1849 
1850     g_value_init (account, G_TYPE_STRING);
1851     g_value_set_string (account, account_path);
1852     g_hash_table_insert (params, "account-path", account);
1853 
1854     mcp_dbus_acl_authorised_async (self->priv->dbus_daemon,
1855                                    context,
1856                                    DBUS_ACL_TYPE_METHOD,
1857                                    SEND_MESSAGE,
1858                                    params,
1859                                    messages_send_message_acl_success,
1860                                    message,
1861                                    messages_send_message_acl_cleanup);
1862 }
1863 
1864 static void
messages_iface_init(gpointer iface,gpointer data G_GNUC_UNUSED)1865 messages_iface_init (gpointer iface, gpointer data G_GNUC_UNUSED)
1866 {
1867 #define IMPLEMENT(x) \
1868   mc_svc_channel_dispatcher_interface_messages_draft_implement_##x (iface, messages_##x)
1869     IMPLEMENT (send_message);
1870 #undef IMPLEMENT
1871 }
1872 
1873 typedef struct
1874 {
1875     McdDispatcher *self;
1876     gint64 user_action_time;
1877     DBusGMethodInvocation *context;
1878     /* List of owned ChannelToDelegate */
1879     GList *channels;
1880     /* array of owned channel path */
1881     GPtrArray *delegated;
1882     /* owned channel path -> owned GValueArray representing a
1883      * TP_STRUCT_TYPE_NOT_DELEGATED_ERROR  */
1884     GHashTable *not_delegated;
1885 } DelegateChannelsCtx;
1886 
1887 typedef struct
1888 {
1889     /* borrowed reference */
1890     DelegateChannelsCtx *ctx;
1891     McdAccount *account;
1892     McdChannel *channel;
1893     /* Queue of reffed McdClientProxy */
1894     GQueue *handlers;
1895     GError *error;
1896 }   ChannelToDelegate;
1897 
1898 static ChannelToDelegate *
channel_to_delegate_new(DelegateChannelsCtx * ctx,McdAccount * account,McdChannel * channel)1899 channel_to_delegate_new (DelegateChannelsCtx *ctx,
1900   McdAccount *account,
1901   McdChannel *channel)
1902 {
1903     ChannelToDelegate *chan = g_slice_new0 (ChannelToDelegate);
1904 
1905     chan->ctx = ctx;
1906     chan->account = g_object_ref (account);
1907     chan->channel = g_object_ref (channel);
1908     chan->handlers = g_queue_new ();
1909     chan->error = NULL;
1910     return chan;
1911 }
1912 
1913 static void
channel_to_delegate_free(ChannelToDelegate * chan)1914 channel_to_delegate_free (ChannelToDelegate *chan)
1915 {
1916     g_object_unref (chan->account);
1917     g_object_unref (chan->channel);
1918     g_queue_foreach (chan->handlers, (GFunc) g_object_unref, NULL);
1919     g_queue_free (chan->handlers);
1920     tp_clear_pointer (&chan->error, g_error_free);
1921     g_slice_free (ChannelToDelegate, chan);
1922 }
1923 
1924 static void
free_not_delegated_error(gpointer data)1925 free_not_delegated_error (gpointer data)
1926 {
1927     g_boxed_free (TP_STRUCT_TYPE_NOT_DELEGATED_ERROR, data);
1928 }
1929 
1930 static DelegateChannelsCtx *
delegate_channels_ctx_new(McdDispatcher * self,gint64 user_action_time,DBusGMethodInvocation * context)1931 delegate_channels_ctx_new (McdDispatcher *self,
1932     gint64 user_action_time,
1933     DBusGMethodInvocation *context)
1934 {
1935     DelegateChannelsCtx *ctx = g_slice_new0 (DelegateChannelsCtx);
1936 
1937     ctx->self = g_object_ref (self);
1938     ctx->user_action_time = user_action_time;
1939     ctx->context = context;
1940     ctx->channels = NULL;
1941     ctx->delegated = g_ptr_array_new_with_free_func (g_free);
1942     ctx->not_delegated = g_hash_table_new_full (g_str_hash, g_str_equal,
1943         g_free, free_not_delegated_error);
1944     return ctx;
1945 }
1946 
1947 static void
delegate_channels_ctx_free(DelegateChannelsCtx * ctx)1948 delegate_channels_ctx_free (DelegateChannelsCtx *ctx)
1949 {
1950     g_object_unref (ctx->self);
1951     g_ptr_array_unref (ctx->delegated);
1952     g_hash_table_unref (ctx->not_delegated);
1953     g_list_free_full (ctx->channels, (GDestroyNotify) channel_to_delegate_free);
1954     g_slice_free (DelegateChannelsCtx, ctx);
1955 }
1956 
1957 static void try_delegating (ChannelToDelegate *to_delegate);
1958 
1959 static void
delegation_done(ChannelToDelegate * to_delegate)1960 delegation_done (ChannelToDelegate *to_delegate)
1961 {
1962     DelegateChannelsCtx *ctx = to_delegate->ctx;
1963 
1964     ctx->channels = g_list_remove (ctx->channels, to_delegate);
1965     channel_to_delegate_free (to_delegate);
1966 
1967     if (ctx->channels == NULL)
1968       {
1969         /* We are done */
1970         tp_svc_channel_dispatcher_return_from_delegate_channels (
1971             ctx->context, ctx->delegated, ctx->not_delegated);
1972 
1973         delegate_channels_ctx_free (ctx);
1974       }
1975 }
1976 
1977 static void
delegate_channels_cb(TpClient * client,const GError * error,gpointer user_data G_GNUC_UNUSED,GObject * weak_object)1978 delegate_channels_cb (TpClient *client,
1979     const GError *error,
1980     gpointer user_data G_GNUC_UNUSED,
1981     GObject *weak_object)
1982 {
1983     ChannelToDelegate *to_delegate = user_data;
1984     DelegateChannelsCtx *ctx = to_delegate->ctx;
1985     McdClientProxy *clt_proxy = MCD_CLIENT_PROXY (client);
1986 
1987     /* If the delegation succeeded, the channel has a new handler. If
1988      * the delegation failed, the channel still has the old
1989      * handler. Either way, the channel still has a handler, so it has
1990      * been successfully dispatched (from 'handler invoked'). */
1991     _mcd_channel_set_status (to_delegate->channel,
1992         MCD_CHANNEL_STATUS_DISPATCHED);
1993 
1994     if (error != NULL)
1995       {
1996         DEBUG ("Handler refused delegated channels");
1997 
1998         if (to_delegate->error == NULL)
1999             to_delegate->error = g_error_copy (error);
2000 
2001         try_delegating (to_delegate);
2002         return;
2003       }
2004 
2005     DEBUG ("Channel %s has been delegated", mcd_channel_get_object_path (
2006         to_delegate->channel));
2007 
2008     _mcd_handler_map_set_path_handled (ctx->self->priv->handler_map,
2009         mcd_channel_get_object_path (to_delegate->channel),
2010         _mcd_client_proxy_get_unique_name (clt_proxy),
2011         tp_proxy_get_bus_name (client));
2012 
2013     g_ptr_array_add (ctx->delegated, g_strdup (
2014         mcd_channel_get_object_path (to_delegate->channel)));
2015 
2016     delegation_done (to_delegate);
2017 }
2018 
2019 static void
try_delegating(ChannelToDelegate * to_delegate)2020 try_delegating (ChannelToDelegate *to_delegate)
2021 {
2022     McdClientProxy *client;
2023     GList *channels = NULL;
2024 
2025     DEBUG ("%s",
2026         mcd_channel_get_object_path (to_delegate->channel));
2027 
2028     if (g_queue_get_length (to_delegate->handlers) == 0)
2029       {
2030         GValueArray *v;
2031         const gchar *dbus_error;
2032 
2033         if (to_delegate->error == NULL)
2034           {
2035             g_set_error (&to_delegate->error, TP_ERROR, TP_ERROR_NOT_CAPABLE,
2036                 "There is no other suitable handler");
2037           }
2038 
2039         if (to_delegate->error->domain == TP_ERROR)
2040           dbus_error = tp_error_get_dbus_name (to_delegate->error->code);
2041         else
2042           dbus_error = TP_ERROR_STR_NOT_AVAILABLE;
2043 
2044         /* We failed to delegate this channel */
2045         v = tp_value_array_build (2,
2046           G_TYPE_STRING, dbus_error,
2047           G_TYPE_STRING, to_delegate->error->message,
2048           G_TYPE_INVALID);
2049 
2050         g_hash_table_insert (to_delegate->ctx->not_delegated,
2051             g_strdup (mcd_channel_get_object_path (to_delegate->channel)),
2052             v);
2053 
2054         DEBUG ("...but failed to delegate %s: %s",
2055             mcd_channel_get_object_path (to_delegate->channel),
2056             to_delegate->error->message);
2057 
2058         delegation_done (to_delegate);
2059         return;
2060       }
2061 
2062     client = g_queue_pop_head (to_delegate->handlers);
2063 
2064     DEBUG ("...trying client %s", _mcd_client_proxy_get_unique_name (
2065         client));
2066 
2067     channels = g_list_prepend (channels, to_delegate->channel);
2068 
2069     _mcd_client_proxy_handle_channels (client, -1, channels,
2070         to_delegate->ctx->user_action_time, NULL, delegate_channels_cb,
2071         to_delegate, NULL, NULL);
2072 
2073     g_object_unref (client);
2074     g_list_free (channels);
2075 }
2076 
2077 static void
add_possible_handlers(McdDispatcher * self,ChannelToDelegate * to_delegate,TpChannel * tp_channel,const gchar * sender,const gchar * preferred_handler)2078 add_possible_handlers (McdDispatcher *self,
2079     ChannelToDelegate *to_delegate,
2080     TpChannel *tp_channel,
2081     const gchar *sender,
2082     const gchar *preferred_handler)
2083 {
2084     GStrv possible_handlers;
2085     guint i;
2086 
2087     possible_handlers = mcd_dispatcher_dup_possible_handlers (self,
2088         NULL, tp_channel, NULL);
2089 
2090     for (i = 0; possible_handlers[i] != NULL; i++)
2091       {
2092         McdClientProxy *client;
2093         const gchar *unique_name;
2094 
2095         client = _mcd_client_registry_lookup (self->priv->clients,
2096             possible_handlers[i]);
2097         g_return_if_fail (client != NULL);
2098 
2099         unique_name = _mcd_client_proxy_get_unique_name (client);
2100 
2101         /* Skip the caller */
2102         if (!tp_strdiff (unique_name, sender))
2103             continue;
2104 
2105         /* Put the preferred handler at the head of the list so it will be tried
2106          * first */
2107         if (!tp_strdiff (possible_handlers[i], preferred_handler))
2108           g_queue_push_head (to_delegate->handlers, g_object_ref (client));
2109         else
2110           g_queue_push_tail (to_delegate->handlers, g_object_ref (client));
2111       }
2112 
2113     g_strfreev (possible_handlers);
2114 }
2115 
2116 static void
dispatcher_delegate_channels(TpSvcChannelDispatcher * iface,const GPtrArray * channels,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)2117 dispatcher_delegate_channels (
2118     TpSvcChannelDispatcher *iface,
2119     const GPtrArray *channels,
2120     gint64 user_action_time,
2121     const gchar *preferred_handler,
2122     DBusGMethodInvocation *context)
2123 {
2124     McdDispatcher *self = (McdDispatcher *) iface;
2125     GError *error = NULL;
2126     gchar *sender = NULL;
2127     McdConnection *conn = NULL;
2128     DelegateChannelsCtx *ctx = NULL;
2129     McdAccountManager *am = NULL;
2130     guint i;
2131     GList *l;
2132 
2133     DEBUG ("called");
2134 
2135     if (!check_preferred_handler (preferred_handler, &error))
2136         goto error;
2137 
2138     if (channels->len == 0)
2139       {
2140         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2141             "Need at least one channel to delegate");
2142         goto error;
2143       }
2144 
2145     ctx = delegate_channels_ctx_new (self, user_action_time, context);
2146 
2147     sender = dbus_g_method_get_sender (context);
2148 
2149     g_object_get (self->priv->master, "account-manager", &am, NULL);
2150     g_assert (am != NULL);
2151 
2152     for (i = 0; i < channels->len; i++)
2153       {
2154         const gchar *chan_path = g_ptr_array_index (channels, i);
2155         const gchar *chan_account;
2156         const gchar *handler;
2157         McdChannel *mcd_channel;
2158         TpChannel *tp_channel;
2159         McdAccount *account;
2160         ChannelToDelegate *to_delegate;
2161 
2162         chan_account = _mcd_handler_map_get_channel_account (
2163             self->priv->handler_map, chan_path);
2164 
2165         if (chan_account == NULL)
2166           {
2167             g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2168                 "Unknown channel: %s", chan_path);
2169             goto error;
2170           }
2171 
2172         account = mcd_account_manager_lookup_account_by_path (am,
2173             chan_account);
2174         g_return_if_fail (account != NULL);
2175 
2176         /* Check the caller is handling the channel */
2177         handler = _mcd_handler_map_get_handler (self->priv->handler_map,
2178             chan_path, NULL);
2179         if (tp_strdiff (sender, handler))
2180          {
2181             g_set_error (&error, TP_ERROR, TP_ERROR_NOT_YOURS,
2182                 "Your are not handling channel %s", chan_path);
2183             goto error;
2184          }
2185 
2186         conn = mcd_account_get_connection (account);
2187         g_return_if_fail (conn != NULL);
2188 
2189         mcd_channel = mcd_connection_find_channel_by_path (conn, chan_path);
2190         g_return_if_fail (mcd_channel != NULL);
2191 
2192         tp_channel = mcd_channel_get_tp_channel (mcd_channel);
2193         g_return_if_fail (tp_channel != NULL);
2194 
2195         to_delegate = channel_to_delegate_new (ctx, account, mcd_channel);
2196 
2197         add_possible_handlers (self, to_delegate, tp_channel, sender,
2198             preferred_handler);
2199 
2200         ctx->channels = g_list_prepend (ctx->channels, to_delegate);
2201       }
2202 
2203     /* All the channels were ok, we can start delegating */
2204     for (l = ctx->channels; l != NULL; l = g_list_next (l))
2205       {
2206         ChannelToDelegate *to_delegate = l->data;
2207 
2208         try_delegating (to_delegate);
2209       }
2210 
2211     g_free (sender);
2212     g_object_unref (am);
2213 
2214     return;
2215 
2216 error:
2217     g_free (sender);
2218     dbus_g_method_return_error (context, error);
2219     g_error_free (error);
2220 
2221     tp_clear_pointer (&ctx, delegate_channels_ctx_free);
2222     tp_clear_object (&am);
2223 }
2224 
2225 static void
present_handle_channels_cb(TpClient * client,const GError * error,gpointer user_data,GObject * weak_object)2226 present_handle_channels_cb (TpClient *client,
2227     const GError *error,
2228     gpointer user_data,
2229     GObject *weak_object)
2230 {
2231     DBusGMethodInvocation *context = user_data;
2232     McdChannel *mcd_channel = MCD_CHANNEL (weak_object);
2233 
2234     /* Whether presenting the channel succeeded or failed, the
2235      * channel's handler hasn't been altered, so it must be set back
2236      * to the dispatched state (from 'handler invoked'). */
2237     _mcd_channel_set_status (mcd_channel,
2238         MCD_CHANNEL_STATUS_DISPATCHED);
2239 
2240     if (error != NULL)
2241       {
2242         dbus_g_method_return_error (context, error);
2243         return;
2244       }
2245 
2246     tp_svc_channel_dispatcher_return_from_present_channel (context);
2247 }
2248 
2249 static void
dispatcher_present_channel(TpSvcChannelDispatcher * iface,const gchar * channel_path,gint64 user_action_time,DBusGMethodInvocation * context)2250 dispatcher_present_channel (
2251     TpSvcChannelDispatcher *iface,
2252     const gchar *channel_path,
2253     gint64 user_action_time,
2254     DBusGMethodInvocation *context)
2255 {
2256     McdDispatcher *self = (McdDispatcher *) iface;
2257     McdAccountManager *am;
2258     const gchar *chan_account;
2259     McdAccount *account;
2260     McdConnection *conn;
2261     McdChannel *mcd_channel;
2262     GError *error = NULL;
2263     McdClientProxy *client;
2264     GList *channels = NULL;
2265 
2266     chan_account = _mcd_handler_map_get_channel_account (
2267         self->priv->handler_map, channel_path);
2268 
2269     if (chan_account == NULL)
2270       {
2271         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2272             "Unknown channel: %s", channel_path);
2273         goto error;
2274       }
2275 
2276     g_object_get (self->priv->master, "account-manager", &am, NULL);
2277     g_assert (am != NULL);
2278 
2279     account = mcd_account_manager_lookup_account_by_path (am, chan_account);
2280     g_return_if_fail (account != NULL);
2281     g_object_unref (am);
2282 
2283     conn = mcd_account_get_connection (account);
2284     g_return_if_fail (conn != NULL);
2285 
2286     mcd_channel = mcd_connection_find_channel_by_path (conn, channel_path);
2287     g_return_if_fail (mcd_channel != NULL);
2288 
2289     /* We take mcd_channel's request to base the search for a suitable Handler
2290      * on the handler that was preferred by the request that initially created
2291      * the Channel, if any.
2292      * Actually not, because of fd.o#41031 */
2293     client = _mcd_dispatcher_lookup_handler (self,
2294             mcd_channel_get_tp_channel (mcd_channel),
2295             _mcd_channel_get_request (mcd_channel));
2296     if (client == NULL)
2297       {
2298         g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2299             "Channel %s is currently not handled", channel_path);
2300         goto error;
2301       }
2302 
2303     channels = g_list_append (channels, mcd_channel);
2304 
2305     _mcd_client_proxy_handle_channels (client, -1, channels,
2306         user_action_time, NULL, present_handle_channels_cb,
2307         context, NULL, G_OBJECT (mcd_channel));
2308 
2309     g_list_free (channels);
2310     return;
2311 
2312 error:
2313     dbus_g_method_return_error (context, error);
2314     g_error_free (error);
2315 }
2316 
2317 static void
dispatcher_iface_init(gpointer g_iface,gpointer iface_data G_GNUC_UNUSED)2318 dispatcher_iface_init (gpointer g_iface,
2319                        gpointer iface_data G_GNUC_UNUSED)
2320 {
2321 #define IMPLEMENT(x) tp_svc_channel_dispatcher_implement_##x (\
2322     g_iface, dispatcher_##x)
2323     IMPLEMENT (create_channel);
2324     IMPLEMENT (ensure_channel);
2325     IMPLEMENT (create_channel_with_hints);
2326     IMPLEMENT (ensure_channel_with_hints);
2327     IMPLEMENT (delegate_channels);
2328     IMPLEMENT (present_channel);
2329 #undef IMPLEMENT
2330 }
2331