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