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