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