]> git.0d.be Git - empathy.git/blob - libempathy/empathy-auth-factory.c
auth-factory: allow to handle more than one auth channel at the same time (#645112)
[empathy.git] / libempathy / empathy-auth-factory.c
1 /*
2  * empathy-auth-factory.c - Source for EmpathyAuthFactory
3  * Copyright (C) 2010 Collabora Ltd.
4  * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "empathy-auth-factory.h"
22
23 #include <telepathy-glib/interfaces.h>
24 #include <telepathy-glib/simple-handler.h>
25 #include <telepathy-glib/util.h>
26
27 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
28 #include "empathy-debug.h"
29 #include "empathy-keyring.h"
30 #include "empathy-server-sasl-handler.h"
31 #include "empathy-server-tls-handler.h"
32 #include "empathy-utils.h"
33
34 #include "extensions/extensions.h"
35
36 G_DEFINE_TYPE (EmpathyAuthFactory, empathy_auth_factory, TP_TYPE_BASE_CLIENT);
37
38 struct _EmpathyAuthFactoryPriv {
39   /* Keep a ref here so the auth client doesn't have to mess with
40    * refs. It will be cleared when the channel (and so the handler)
41    * gets invalidated.
42    *
43    * The channel path of the handler's channel (borrowed gchar *) ->
44    * reffed (EmpathyServerSASLHandler *)
45    * */
46   GHashTable *sasl_handlers;
47
48   gboolean dispose_run;
49 };
50
51 enum {
52   NEW_SERVER_TLS_HANDLER,
53   NEW_SERVER_SASL_HANDLER,
54   LAST_SIGNAL,
55 };
56
57 static guint signals[LAST_SIGNAL] = { 0, };
58
59 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAuthFactory)
60
61 static EmpathyAuthFactory *auth_factory_singleton = NULL;
62
63 typedef struct {
64   TpHandleChannelsContext *context;
65   EmpathyAuthFactory *self;
66 } HandlerContextData;
67
68 static void
69 handler_context_data_free (HandlerContextData *data)
70 {
71   tp_clear_object (&data->self);
72   tp_clear_object (&data->context);
73
74   g_slice_free (HandlerContextData, data);
75 }
76
77 static HandlerContextData *
78 handler_context_data_new (EmpathyAuthFactory *self,
79     TpHandleChannelsContext *context)
80 {
81   HandlerContextData *data;
82
83   data = g_slice_new0 (HandlerContextData);
84   data->self = g_object_ref (self);
85
86   if (context != NULL)
87     data->context = g_object_ref (context);
88
89   return data;
90 }
91
92 static void
93 server_tls_handler_ready_cb (GObject *source,
94     GAsyncResult *res,
95     gpointer user_data)
96 {
97   EmpathyServerTLSHandler *handler;
98   GError *error = NULL;
99   HandlerContextData *data = user_data;
100
101   handler = empathy_server_tls_handler_new_finish (res, &error);
102
103   if (error != NULL)
104     {
105       DEBUG ("Failed to create a server TLS handler; error %s",
106           error->message);
107       tp_handle_channels_context_fail (data->context, error);
108
109       g_error_free (error);
110     }
111   else
112     {
113       tp_handle_channels_context_accept (data->context);
114       g_signal_emit (data->self, signals[NEW_SERVER_TLS_HANDLER], 0,
115           handler);
116
117       g_object_unref (handler);
118     }
119
120   handler_context_data_free (data);
121 }
122
123 static void
124 sasl_handler_invalidated_cb (EmpathyServerSASLHandler *handler,
125     gpointer user_data)
126 {
127   EmpathyAuthFactory *self = user_data;
128   EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
129   TpChannel * channel;
130
131   channel = empathy_server_sasl_handler_get_channel (handler);
132   g_assert (channel != NULL);
133
134   DEBUG ("SASL handler for channel %s is invalidated, unref it",
135       tp_proxy_get_object_path (channel));
136
137   g_hash_table_remove (priv->sasl_handlers, tp_proxy_get_object_path (channel));
138 }
139
140 static void
141 server_sasl_handler_ready_cb (GObject *source,
142     GAsyncResult *res,
143     gpointer user_data)
144 {
145   EmpathyAuthFactoryPriv *priv;
146   GError *error = NULL;
147   HandlerContextData *data = user_data;
148   EmpathyServerSASLHandler *handler;
149
150   priv = GET_PRIV (data->self);
151   handler = empathy_server_sasl_handler_new_finish (res, &error);
152
153   if (error != NULL)
154     {
155       DEBUG ("Failed to create a server SASL handler; error %s",
156           error->message);
157
158       if (data->context != NULL)
159         tp_handle_channels_context_fail (data->context, error);
160
161       g_error_free (error);
162     }
163   else
164     {
165       TpChannel *channel;
166
167       if (data->context != NULL)
168         tp_handle_channels_context_accept (data->context);
169
170       channel = empathy_server_sasl_handler_get_channel (handler);
171       g_assert (channel != NULL);
172
173       /* Pass the ref to the hash table */
174       g_hash_table_insert (priv->sasl_handlers,
175           (gpointer) tp_proxy_get_object_path (channel), handler);
176
177       tp_g_signal_connect_object (handler, "invalidated",
178           G_CALLBACK (sasl_handler_invalidated_cb), data->self, 0);
179
180       g_signal_emit (data->self, signals[NEW_SERVER_SASL_HANDLER], 0,
181           handler);
182     }
183
184   handler_context_data_free (data);
185 }
186
187 static gboolean
188 common_checks (EmpathyAuthFactory *self,
189     GList *channels,
190     gboolean observe,
191     GError **error)
192 {
193   EmpathyAuthFactoryPriv *priv = GET_PRIV (self);
194   TpChannel *channel;
195   GHashTable *props;
196   const gchar * const *available_mechanisms;
197   const GError *dbus_error;
198   EmpathyServerSASLHandler *handler;
199
200   /* there can't be more than one ServerTLSConnection or
201    * ServerAuthentication channels at the same time, for the same
202    * connection/account.
203    */
204   if (g_list_length (channels) != 1)
205     {
206       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
207           "Can't %s more than one ServerTLSConnection or ServerAuthentication "
208           "channel for the same connection.", observe ? "observe" : "handle");
209
210       return FALSE;
211     }
212
213   channel = channels->data;
214
215   if (tp_channel_get_channel_type_id (channel) !=
216       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
217     {
218       /* If we are observing we care only about ServerAuthentication channels.
219        * If we are handling we care about ServerAuthentication and
220        * ServerTLSConnection channels. */
221       if (observe
222           || tp_channel_get_channel_type_id (channel) !=
223           EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
224         {
225           g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
226               "Can only %s ServerTLSConnection or ServerAuthentication channels, "
227               "this was a %s channel", observe ? "observe" : "handle",
228               tp_channel_get_channel_type (channel));
229
230           return FALSE;
231         }
232     }
233
234   handler = g_hash_table_lookup (priv->sasl_handlers,
235           tp_proxy_get_object_path (channel));
236
237   if (tp_channel_get_channel_type_id (channel) ==
238       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
239       && handler != NULL &&
240       !observe)
241     {
242       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
243           "We are already handling this channel: %s",
244           tp_proxy_get_object_path (channel));
245
246       return FALSE;
247     }
248
249   props = tp_channel_borrow_immutable_properties (channel);
250   available_mechanisms = tp_asv_get_boxed (props,
251       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_AVAILABLE_MECHANISMS,
252       G_TYPE_STRV);
253
254   if (tp_channel_get_channel_type_id (channel) ==
255       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION
256       && !tp_strv_contains (available_mechanisms, "X-TELEPATHY-PASSWORD"))
257     {
258       g_set_error_literal (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
259           "Only the X-TELEPATHY-PASSWORD SASL mechanism is supported");
260
261       return FALSE;
262     }
263
264   dbus_error = tp_proxy_get_invalidated (channel);
265
266   if (dbus_error != NULL)
267     {
268       *error = g_error_copy (dbus_error);
269       return FALSE;
270     }
271
272   return TRUE;
273 }
274
275 static void
276 handle_channels (TpBaseClient *handler,
277     TpAccount *account,
278     TpConnection *connection,
279     GList *channels,
280     GList *requests_satisfied,
281     gint64 user_action_time,
282     TpHandleChannelsContext *context)
283 {
284   TpChannel *channel;
285   GError *error = NULL;
286   EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (handler);
287   HandlerContextData *data;
288
289   DEBUG ("Handle TLS or SASL carrier channels.");
290
291   if (!common_checks (self, channels, FALSE, &error))
292     {
293       DEBUG ("Failed checks: %s", error->message);
294       tp_handle_channels_context_fail (context, error);
295       g_clear_error (&error);
296       return;
297     }
298
299   /* The common checks above have checked this is fine. */
300   channel = channels->data;
301
302   data = handler_context_data_new (self, context);
303   tp_handle_channels_context_delay (context);
304
305   /* create a handler */
306   if (tp_channel_get_channel_type_id (channel) ==
307       EMP_IFACE_QUARK_CHANNEL_TYPE_SERVER_TLS_CONNECTION)
308     {
309       empathy_server_tls_handler_new_async (channel, server_tls_handler_ready_cb,
310           data);
311     }
312   else if (tp_channel_get_channel_type_id (channel) ==
313       TP_IFACE_QUARK_CHANNEL_TYPE_SERVER_AUTHENTICATION)
314     {
315       empathy_server_sasl_handler_new_async (account, channel,
316           server_sasl_handler_ready_cb, data);
317     }
318 }
319
320 typedef struct
321 {
322   EmpathyAuthFactory *self;
323   TpObserveChannelsContext *context;
324   TpChannelDispatchOperation *dispatch_operation;
325   TpAccount *account;
326   TpChannel *channel;
327 } ObserveChannelsData;
328
329 static void
330 observe_channels_data_free (ObserveChannelsData *data)
331 {
332   g_object_unref (data->context);
333   g_object_unref (data->account);
334   g_object_unref (data->channel);
335   g_object_unref (data->dispatch_operation);
336   g_slice_free (ObserveChannelsData, data);
337 }
338
339 static void
340 claim_cb (GObject *source,
341     GAsyncResult *result,
342     gpointer user_data)
343 {
344   ObserveChannelsData *data = user_data;
345   GError *error = NULL;
346
347   if (!tp_channel_dispatch_operation_claim_finish (
348           TP_CHANNEL_DISPATCH_OPERATION (source), result, &error))
349     {
350       DEBUG ("Failed to call Claim: %s", error->message);
351       g_clear_error (&error);
352     }
353   else
354     {
355       HandlerContextData *h_data;
356
357       DEBUG ("Claim called successfully");
358
359       h_data = handler_context_data_new (data->self, NULL);
360
361       empathy_server_sasl_handler_new_async (TP_ACCOUNT (data->account),
362           data->channel, server_sasl_handler_ready_cb, h_data);
363     }
364
365   observe_channels_data_free (data);
366 }
367
368 static void
369 get_password_cb (GObject *source,
370     GAsyncResult *result,
371     gpointer user_data)
372 {
373   ObserveChannelsData *data = user_data;
374
375   if (empathy_keyring_get_account_password_finish (TP_ACCOUNT (source), result, NULL) == NULL)
376     {
377       /* We don't actually mind if this fails, just let the approver
378        * go ahead and take the channel. */
379
380       DEBUG ("We don't have a password for account %s, letting the event "
381           "manager approver take it", tp_proxy_get_object_path (source));
382
383       tp_observe_channels_context_accept (data->context);
384       observe_channels_data_free (data);
385     }
386   else
387     {
388       DEBUG ("We have a password for account %s, calling Claim",
389           tp_proxy_get_object_path (source));
390
391       tp_channel_dispatch_operation_claim_async (data->dispatch_operation,
392           claim_cb, data);
393
394       tp_observe_channels_context_accept (data->context);
395     }
396 }
397
398 static void
399 observe_channels (TpBaseClient *client,
400     TpAccount *account,
401     TpConnection *connection,
402     GList *channels,
403     TpChannelDispatchOperation *dispatch_operation,
404     GList *requests,
405     TpObserveChannelsContext *context)
406 {
407   EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (client);
408   TpChannel *channel;
409   GError *error = NULL;
410   ObserveChannelsData *data;
411
412   DEBUG ("New auth channel to observe");
413
414   if (!common_checks (self, channels, TRUE, &error))
415     {
416       DEBUG ("Failed checks: %s", error->message);
417       tp_observe_channels_context_fail (context, error);
418       g_clear_error (&error);
419       return;
420     }
421
422   /* We're now sure this is a server auth channel using the SASL auth
423    * type and X-TELEPATHY-PASSWORD is available. Great. */
424
425   channel = channels->data;
426
427   data = g_slice_new0 (ObserveChannelsData);
428   data->self = self;
429   data->context = g_object_ref (context);
430   data->dispatch_operation = g_object_ref (dispatch_operation);
431   data->account = g_object_ref (account);
432   data->channel = g_object_ref (channel);
433
434   empathy_keyring_get_account_password_async (account, get_password_cb, data);
435
436   tp_observe_channels_context_delay (context);
437 }
438
439 static GObject *
440 empathy_auth_factory_constructor (GType type,
441     guint n_params,
442     GObjectConstructParam *params)
443 {
444   GObject *retval;
445
446   if (auth_factory_singleton != NULL)
447     {
448       retval = g_object_ref (auth_factory_singleton);
449     }
450   else
451     {
452       retval = G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructor
453         (type, n_params, params);
454
455       auth_factory_singleton = EMPATHY_AUTH_FACTORY (retval);
456       g_object_add_weak_pointer (retval, (gpointer *) &auth_factory_singleton);
457     }
458
459   return retval;
460 }
461
462 static void
463 empathy_auth_factory_init (EmpathyAuthFactory *self)
464 {
465   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
466       EMPATHY_TYPE_AUTH_FACTORY, EmpathyAuthFactoryPriv);
467
468   self->priv->sasl_handlers = g_hash_table_new_full (g_str_hash, g_str_equal,
469       NULL, g_object_unref);
470 }
471
472 static void
473 empathy_auth_factory_constructed (GObject *obj)
474 {
475   EmpathyAuthFactory *self = EMPATHY_AUTH_FACTORY (obj);
476   TpBaseClient *client = TP_BASE_CLIENT (self);
477   GError *error = NULL;
478
479   /* chain up to TpBaseClient first */
480   G_OBJECT_CLASS (empathy_auth_factory_parent_class)->constructed (obj);
481
482   if (error != NULL)
483     {
484       g_critical ("Failed to get TpDBusDaemon: %s", error->message);
485       g_error_free (error);
486       return;
487     }
488
489   tp_base_client_set_handler_bypass_approval (client, FALSE);
490
491   /* Handle ServerTLSConnection and ServerAuthentication channels */
492   tp_base_client_take_handler_filter (client, tp_asv_new (
493           /* ChannelType */
494           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
495           EMP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION,
496           /* AuthenticationMethod */
497           TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
498           TP_HANDLE_TYPE_NONE, NULL));
499
500   tp_base_client_take_handler_filter (client, tp_asv_new (
501           /* ChannelType */
502           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
503           TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
504           /* AuthenticationMethod */
505           TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
506           G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
507           NULL));
508
509   /* We are also an observer so that we can see new auth channels
510    * popping up and if we have the password already saved to one
511    * account where an auth channel has just appeared we can call
512    * Claim() on the CDO so the approver won't get it, which makes
513    * sense. */
514
515   /* Observe ServerAuthentication channels */
516   tp_base_client_take_observer_filter (client, tp_asv_new (
517           /* ChannelType */
518           TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
519           TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION,
520           /* AuthenticationMethod */
521           TP_PROP_CHANNEL_TYPE_SERVER_AUTHENTICATION_AUTHENTICATION_METHOD,
522           G_TYPE_STRING, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION,
523           NULL));
524
525   tp_base_client_set_observer_delay_approvers (client, TRUE);
526 }
527
528 static void
529 empathy_auth_factory_dispose (GObject *object)
530 {
531   EmpathyAuthFactoryPriv *priv = GET_PRIV (object);
532
533   if (priv->dispose_run)
534     return;
535
536   priv->dispose_run = TRUE;
537
538   g_hash_table_unref (priv->sasl_handlers);
539
540   G_OBJECT_CLASS (empathy_auth_factory_parent_class)->dispose (object);
541 }
542
543 static void
544 empathy_auth_factory_class_init (EmpathyAuthFactoryClass *klass)
545 {
546   GObjectClass *oclass = G_OBJECT_CLASS (klass);
547   TpBaseClientClass *base_client_cls = TP_BASE_CLIENT_CLASS (klass);
548
549   oclass->constructor = empathy_auth_factory_constructor;
550   oclass->constructed = empathy_auth_factory_constructed;
551   oclass->dispose = empathy_auth_factory_dispose;
552
553   base_client_cls->handle_channels = handle_channels;
554   base_client_cls->observe_channels = observe_channels;
555
556   g_type_class_add_private (klass, sizeof (EmpathyAuthFactoryPriv));
557
558   signals[NEW_SERVER_TLS_HANDLER] =
559     g_signal_new ("new-server-tls-handler",
560       G_TYPE_FROM_CLASS (klass),
561       G_SIGNAL_RUN_LAST, 0,
562       NULL, NULL,
563       g_cclosure_marshal_VOID__OBJECT,
564       G_TYPE_NONE,
565       1, EMPATHY_TYPE_SERVER_TLS_HANDLER);
566
567   signals[NEW_SERVER_SASL_HANDLER] =
568     g_signal_new ("new-server-sasl-handler",
569       G_TYPE_FROM_CLASS (klass),
570       G_SIGNAL_RUN_LAST, 0,
571       NULL, NULL,
572       g_cclosure_marshal_VOID__OBJECT,
573       G_TYPE_NONE,
574       1, EMPATHY_TYPE_SERVER_SASL_HANDLER);
575 }
576
577 EmpathyAuthFactory *
578 empathy_auth_factory_dup_singleton (void)
579 {
580   EmpathyAuthFactory *out = NULL;
581   TpDBusDaemon *bus;
582
583   bus = tp_dbus_daemon_dup (NULL);
584   out = g_object_new (EMPATHY_TYPE_AUTH_FACTORY,
585       "dbus-daemon", bus,
586       "name", "Empathy.Auth",
587       NULL);
588   g_object_unref (bus);
589
590   return out;
591 }
592
593 gboolean
594 empathy_auth_factory_register (EmpathyAuthFactory *self,
595     GError **error)
596 {
597   return tp_base_client_register (TP_BASE_CLIENT (self), error);
598 }