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