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