]> git.0d.be Git - empathy.git/blob - libempathy/empathy-server-sasl-handler.c
Merge remote-tracking branch 'jonny/ft'
[empathy.git] / libempathy / empathy-server-sasl-handler.c
1 /*
2  * empathy-server-sasl-handler.c - Source for EmpathyServerSASLHandler
3  * Copyright (C) 2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #include "empathy-server-sasl-handler.h"
21
22 #include <telepathy-glib/telepathy-glib.h>
23
24 #include <extensions/extensions.h>
25
26 #include <string.h>
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_SASL
29 #include "empathy-debug.h"
30 #include "empathy-keyring.h"
31
32 enum {
33   PROP_CHANNEL = 1,
34   PROP_ACCOUNT,
35   LAST_PROPERTY,
36 };
37
38 /* signal enum */
39 enum {
40   AUTH_PASSWORD_FAILED,
41   INVALIDATED,
42   LAST_SIGNAL,
43 };
44
45 static guint signals[LAST_SIGNAL] = {0};
46
47 typedef struct {
48   TpChannel *channel;
49   TpAccount *account;
50
51   GSimpleAsyncResult *result;
52
53   gchar *password;
54   gboolean save_password;
55
56   GSimpleAsyncResult *async_init_res;
57 } EmpathyServerSASLHandlerPriv;
58
59 static void async_initable_iface_init (GAsyncInitableIface *iface);
60
61 G_DEFINE_TYPE_WITH_CODE (EmpathyServerSASLHandler, empathy_server_sasl_handler,
62     G_TYPE_OBJECT,
63     G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init));
64
65 static const gchar *sasl_statuses[] = {
66   "not started",
67   "in progress",
68   "server succeeded",
69   "client accepted",
70   "succeeded",
71   "server failed",
72   "client failed",
73 };
74
75 static void
76 empathy_server_sasl_handler_set_password_cb (GObject *source,
77     GAsyncResult *result,
78     gpointer user_data)
79 {
80   GError *error = NULL;
81
82   if (!empathy_keyring_set_account_password_finish (TP_ACCOUNT (source), result,
83           &error))
84     {
85       DEBUG ("Failed to set password: %s", error->message);
86       g_clear_error (&error);
87     }
88   else
89     {
90       DEBUG ("Password set successfully.");
91     }
92 }
93
94 static void
95 sasl_status_changed_cb (TpChannel *channel,
96     TpSASLStatus status,
97     const gchar *error,
98     GHashTable *details,
99     gpointer user_data,
100     GObject *weak_object)
101 {
102   EmpathyServerSASLHandler *self = EMPATHY_SERVER_SASL_HANDLER (weak_object);
103   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (weak_object)->priv;
104
105   /* buh boh */
106   if (status >= G_N_ELEMENTS (sasl_statuses))
107     {
108       DEBUG ("SASL status changed to unknown status");
109       return;
110     }
111
112   DEBUG ("SASL status changed to '%s'", sasl_statuses[status]);
113
114   if (status == TP_SASL_STATUS_SERVER_SUCCEEDED)
115     {
116       if (priv->save_password)
117         {
118           DEBUG ("Saving password in keyring");
119
120           empathy_keyring_set_account_password_async (priv->account,
121               priv->password, empathy_server_sasl_handler_set_password_cb,
122               NULL);
123         }
124
125       DEBUG ("Calling AcceptSASL");
126       tp_cli_channel_interface_sasl_authentication_call_accept_sasl (
127           priv->channel, -1, NULL, NULL, NULL, NULL);
128     }
129   else if (status == TP_SASL_STATUS_SUCCEEDED)
130     {
131       DEBUG ("SASL succeeded, calling Close");
132       tp_cli_channel_call_close (priv->channel, -1,
133           NULL, NULL, NULL, NULL);
134     }
135   else if (status == TP_SASL_STATUS_SERVER_FAILED)
136    {
137      if (!tp_strdiff (error, TP_ERROR_STR_AUTHENTICATION_FAILED))
138        {
139          g_signal_emit (self, signals[AUTH_PASSWORD_FAILED], 0, priv->password);
140        }
141    }
142 }
143
144 static gboolean
145 empathy_server_sasl_handler_give_password (gpointer data)
146 {
147   EmpathyServerSASLHandler *self = data;
148   EmpathyServerSASLHandlerPriv *priv = self->priv;
149
150   empathy_server_sasl_handler_provide_password (self,
151       priv->password, FALSE);
152
153   return FALSE;
154 }
155
156 static void
157 empathy_server_sasl_handler_get_password_async_cb (GObject *source,
158     GAsyncResult *result,
159     gpointer user_data)
160 {
161   EmpathyServerSASLHandlerPriv *priv;
162   const gchar *password;
163   GError *error = NULL;
164
165   priv = EMPATHY_SERVER_SASL_HANDLER (user_data)->priv;
166
167   password = empathy_keyring_get_account_password_finish (TP_ACCOUNT (source),
168       result, &error);
169
170   if (password != NULL)
171     {
172       priv->password = g_strdup (password);
173
174       /* Do this in an idle so the async result will get there
175        * first. */
176       g_idle_add (empathy_server_sasl_handler_give_password, user_data);
177     }
178
179   g_simple_async_result_complete (priv->async_init_res);
180   tp_clear_object (&priv->async_init_res);
181 }
182
183 static void
184 empathy_server_sasl_handler_init_async (GAsyncInitable *initable,
185     gint io_priority,
186     GCancellable *cancellable,
187     GAsyncReadyCallback callback,
188     gpointer user_data)
189 {
190   EmpathyServerSASLHandler *self = EMPATHY_SERVER_SASL_HANDLER (initable);
191   EmpathyServerSASLHandlerPriv *priv = self->priv;
192
193   g_assert (priv->account != NULL);
194
195   priv->async_init_res = g_simple_async_result_new (G_OBJECT (self),
196       callback, user_data, empathy_server_sasl_handler_new_async);
197
198   empathy_keyring_get_account_password_async (priv->account,
199       empathy_server_sasl_handler_get_password_async_cb, self);
200 }
201
202 static gboolean
203 empathy_server_sasl_handler_init_finish (GAsyncInitable *initable,
204     GAsyncResult *res,
205     GError **error)
206 {
207   if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res),
208           error))
209     return FALSE;
210
211   return TRUE;
212 }
213
214 static void
215 async_initable_iface_init (GAsyncInitableIface *iface)
216 {
217   iface->init_async = empathy_server_sasl_handler_init_async;
218   iface->init_finish = empathy_server_sasl_handler_init_finish;
219 }
220
221 static void
222 channel_invalidated_cb (TpProxy *proxy,
223     guint domain,
224     gint code,
225     gchar *message,
226     EmpathyServerSASLHandler *self)
227 {
228   g_signal_emit (self, signals[INVALIDATED], 0);
229 }
230
231 static void
232 empathy_server_sasl_handler_constructed (GObject *object)
233 {
234   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (object)->priv;
235   GError *error = NULL;
236
237   tp_cli_channel_interface_sasl_authentication_connect_to_sasl_status_changed (
238       priv->channel, sasl_status_changed_cb, NULL, NULL, object, &error);
239
240   if (error != NULL)
241     {
242       DEBUG ("Failed to connect to SASLStatusChanged: %s", error->message);
243       g_clear_error (&error);
244     }
245
246   tp_g_signal_connect_object (priv->channel, "invalidated",
247       G_CALLBACK (channel_invalidated_cb), object, 0);
248 }
249
250 static void
251 empathy_server_sasl_handler_get_property (GObject *object,
252     guint property_id,
253     GValue *value,
254     GParamSpec *pspec)
255 {
256   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (object)->priv;
257
258   switch (property_id)
259     {
260     case PROP_CHANNEL:
261       g_value_set_object (value, priv->channel);
262       break;
263     case PROP_ACCOUNT:
264       g_value_set_object (value, priv->account);
265       break;
266     default:
267       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
268       break;
269     }
270 }
271
272 static void
273 empathy_server_sasl_handler_set_property (GObject *object,
274     guint property_id,
275     const GValue *value,
276     GParamSpec *pspec)
277 {
278   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (object)->priv;
279
280   switch (property_id)
281     {
282     case PROP_CHANNEL:
283       priv->channel = g_value_dup_object (value);
284       break;
285     case PROP_ACCOUNT:
286       priv->account = g_value_dup_object (value);
287       break;
288     default:
289       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290       break;
291     }
292 }
293
294 static void
295 empathy_server_sasl_handler_dispose (GObject *object)
296 {
297   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (object)->priv;
298
299   DEBUG ("%p", object);
300
301   tp_clear_object (&priv->channel);
302   tp_clear_object (&priv->account);
303
304   G_OBJECT_CLASS (empathy_server_sasl_handler_parent_class)->dispose (object);
305 }
306
307 static void
308 empathy_server_sasl_handler_finalize (GObject *object)
309 {
310   EmpathyServerSASLHandlerPriv *priv = EMPATHY_SERVER_SASL_HANDLER (object)->priv;
311
312   DEBUG ("%p", object);
313
314   tp_clear_pointer (&priv->password, g_free);
315
316   G_OBJECT_CLASS (empathy_server_sasl_handler_parent_class)->finalize (object);
317 }
318
319 static void
320 empathy_server_sasl_handler_class_init (EmpathyServerSASLHandlerClass *klass)
321 {
322   GObjectClass *oclass = G_OBJECT_CLASS (klass);
323   GParamSpec *pspec;
324
325   oclass->constructed = empathy_server_sasl_handler_constructed;
326   oclass->get_property = empathy_server_sasl_handler_get_property;
327   oclass->set_property = empathy_server_sasl_handler_set_property;
328   oclass->dispose = empathy_server_sasl_handler_dispose;
329   oclass->finalize = empathy_server_sasl_handler_finalize;
330
331   g_type_class_add_private (klass, sizeof (EmpathyServerSASLHandlerPriv));
332
333   pspec = g_param_spec_object ("channel", "The TpChannel",
334       "The TpChannel this handler is supposed to handle.",
335       TP_TYPE_CHANNEL,
336       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
337   g_object_class_install_property (oclass, PROP_CHANNEL, pspec);
338
339   pspec = g_param_spec_object ("account", "The TpAccount",
340       "The TpAccount this channel belongs to.",
341       TP_TYPE_ACCOUNT,
342       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
343   g_object_class_install_property (oclass, PROP_ACCOUNT, pspec);
344
345   signals[AUTH_PASSWORD_FAILED] = g_signal_new ("auth-password-failed",
346       G_TYPE_FROM_CLASS (klass),
347       G_SIGNAL_RUN_LAST, 0,
348       NULL, NULL,
349       g_cclosure_marshal_generic,
350       G_TYPE_NONE, 1, G_TYPE_STRING);
351
352   signals[INVALIDATED] = g_signal_new ("invalidated",
353       G_TYPE_FROM_CLASS (klass),
354       G_SIGNAL_RUN_LAST, 0,
355       NULL, NULL,
356       g_cclosure_marshal_generic,
357       G_TYPE_NONE, 0);
358 }
359
360 static void
361 empathy_server_sasl_handler_init (EmpathyServerSASLHandler *self)
362 {
363   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
364       EMPATHY_TYPE_SERVER_SASL_HANDLER, EmpathyServerSASLHandlerPriv);
365 }
366
367 EmpathyServerSASLHandler *
368 empathy_server_sasl_handler_new_finish (GAsyncResult *result,
369     GError **error)
370 {
371   GObject *object, *source_object;
372
373   source_object = g_async_result_get_source_object (result);
374
375   object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
376       result, error);
377   g_object_unref (source_object);
378
379   if (object != NULL)
380     return EMPATHY_SERVER_SASL_HANDLER (object);
381   else
382     return NULL;
383 }
384
385 void
386 empathy_server_sasl_handler_new_async (TpAccount *account,
387     TpChannel *channel,
388     GAsyncReadyCallback callback,
389     gpointer user_data)
390 {
391   g_return_if_fail (TP_IS_ACCOUNT (account));
392   g_return_if_fail (TP_IS_CHANNEL (channel));
393   g_return_if_fail (callback != NULL);
394
395   g_async_initable_new_async (EMPATHY_TYPE_SERVER_SASL_HANDLER,
396       G_PRIORITY_DEFAULT, NULL, callback, user_data,
397       "account", account,
398       "channel", channel,
399       NULL);
400 }
401
402 static void
403 start_mechanism_with_data_cb (TpChannel *proxy,
404     const GError *error,
405     gpointer user_data,
406     GObject *weak_object)
407 {
408   if (error != NULL)
409     {
410       DEBUG ("Failed to start mechanism: %s", error->message);
411       return;
412     }
413
414   DEBUG ("Started mechanism successfully");
415 }
416
417 void
418 empathy_server_sasl_handler_provide_password (
419     EmpathyServerSASLHandler *handler,
420     const gchar *password,
421     gboolean remember)
422 {
423   EmpathyServerSASLHandlerPriv *priv;
424   GArray *array;
425   gboolean may_save_response, may_save_response_valid;
426
427   g_return_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (handler));
428
429   priv = handler->priv;
430
431   array = g_array_sized_new (TRUE, FALSE,
432       sizeof (gchar), strlen (password));
433
434   g_array_append_vals (array, password, strlen (password));
435
436   DEBUG ("Calling StartMechanismWithData with our password");
437
438   tp_cli_channel_interface_sasl_authentication_call_start_mechanism_with_data (
439       priv->channel, -1, "X-TELEPATHY-PASSWORD", array,
440       start_mechanism_with_data_cb, NULL, NULL, G_OBJECT (handler));
441
442   g_array_unref (array);
443
444   DEBUG ("%sremembering the password", remember ? "" : "not ");
445
446   /* determine if we are permitted to save the password locally */
447   may_save_response = tp_asv_get_boolean (
448       tp_channel_borrow_immutable_properties (priv->channel),
449       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_MAY_SAVE_RESPONSE,
450       &may_save_response_valid);
451
452   if (!may_save_response_valid)
453     {
454       DEBUG ("MaySaveResponse unknown, assuming TRUE");
455       may_save_response = TRUE;
456     }
457
458   if (remember)
459     {
460       if (may_save_response)
461         {
462           g_free (priv->password);
463
464           /* We'll save the password if we manage to connect */
465           priv->password = g_strdup (password);
466           priv->save_password = TRUE;
467         }
468       else if (tp_proxy_has_interface_by_id (priv->channel,
469             EMP_IFACE_QUARK_CHANNEL_INTERFACE_CREDENTIALS_STORAGE))
470         {
471           DEBUG ("Channel implements Ch.I.CredentialsStorage");
472         }
473       else
474         {
475           DEBUG ("Asked to remember password, but doing so is not permitted");
476         }
477     }
478
479   if (!may_save_response)
480     {
481       /* delete any password present, it shouldn't be there */
482       empathy_keyring_delete_account_password_async (priv->account, NULL, NULL);
483     }
484
485   /* Additionally, if we implement Ch.I.CredentialsStorage, inform that
486    * whether we want to remember the password */
487   if (tp_proxy_has_interface_by_id (priv->channel,
488         EMP_IFACE_QUARK_CHANNEL_INTERFACE_CREDENTIALS_STORAGE))
489     {
490       emp_cli_channel_interface_credentials_storage_call_store_credentials (
491           TP_PROXY (priv->channel), -1, remember, NULL, NULL, NULL, NULL);
492     }
493 }
494
495 void
496 empathy_server_sasl_handler_cancel (EmpathyServerSASLHandler *handler)
497 {
498   EmpathyServerSASLHandlerPriv *priv;
499
500   g_return_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (handler));
501
502   priv = handler->priv;
503
504   DEBUG ("Cancelling SASL mechanism...");
505
506   tp_cli_channel_interface_sasl_authentication_call_abort_sasl (
507       priv->channel, -1, TP_SASL_ABORT_REASON_USER_ABORT,
508       "User cancelled the authentication",
509       NULL, NULL, NULL, NULL);
510 }
511
512 TpAccount *
513 empathy_server_sasl_handler_get_account (EmpathyServerSASLHandler *handler)
514 {
515   EmpathyServerSASLHandlerPriv *priv;
516
517   g_return_val_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (handler), NULL);
518
519   priv = handler->priv;
520
521   return priv->account;
522 }
523
524 TpChannel *
525 empathy_server_sasl_handler_get_channel (EmpathyServerSASLHandler *handler)
526 {
527   EmpathyServerSASLHandlerPriv *priv;
528
529   g_return_val_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (handler), NULL);
530
531   priv = handler->priv;
532
533   return priv->channel;
534 }
535
536 gboolean
537 empathy_server_sasl_handler_has_password (EmpathyServerSASLHandler *handler)
538 {
539   EmpathyServerSASLHandlerPriv *priv;
540
541   g_return_val_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (handler), FALSE);
542
543   priv = handler->priv;
544
545   return (priv->password != NULL);
546 }
547
548 /**
549  * empathy_server_sasl_handler_can_save_response_somewhere:
550  * @self:
551  *
552  * Returns: %TRUE if the response can be saved somewhere, either the keyring
553  *   or via Ch.I.CredentialsStorage
554  */
555 gboolean
556 empathy_server_sasl_handler_can_save_response_somewhere (
557     EmpathyServerSASLHandler *self)
558 {
559   EmpathyServerSASLHandlerPriv *priv;
560   gboolean may_save_response, may_save_response_valid;
561   gboolean has_storage_iface;
562
563   g_return_val_if_fail (EMPATHY_IS_SERVER_SASL_HANDLER (self), FALSE);
564
565   priv = self->priv;
566
567   /* determine if we are permitted to save the password locally */
568   may_save_response = tp_asv_get_boolean (
569       tp_channel_borrow_immutable_properties (priv->channel),
570       TP_PROP_CHANNEL_INTERFACE_SASL_AUTHENTICATION_MAY_SAVE_RESPONSE,
571       &may_save_response_valid);
572
573   if (!may_save_response_valid)
574     {
575       DEBUG ("MaySaveResponse unknown, assuming TRUE");
576       may_save_response = TRUE;
577     }
578
579   has_storage_iface = tp_proxy_has_interface_by_id (priv->channel,
580       EMP_IFACE_QUARK_CHANNEL_INTERFACE_CREDENTIALS_STORAGE);
581
582   return may_save_response || has_storage_iface;
583 }