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