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