Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-sasl.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <string.h>
28
29 #include "camel-debug.h"
30 #include "camel-mime-utils.h"
31 #include "camel-sasl-anonymous.h"
32 #include "camel-sasl-cram-md5.h"
33 #include "camel-sasl-digest-md5.h"
34 #include "camel-sasl-gssapi.h"
35 #include "camel-sasl-login.h"
36 #include "camel-sasl-ntlm.h"
37 #include "camel-sasl-plain.h"
38 #include "camel-sasl-popb4smtp.h"
39 #include "camel-sasl.h"
40 #include "camel-service.h"
41
42 #define CAMEL_SASL_GET_PRIVATE(obj) \
43         (G_TYPE_INSTANCE_GET_PRIVATE \
44         ((obj), CAMEL_TYPE_SASL, CamelSaslPrivate))
45
46 #define w(x)
47
48 typedef struct _AsyncContext AsyncContext;
49
50 struct _CamelSaslPrivate {
51         CamelService *service;
52         gboolean authenticated;
53         gchar *service_name;
54         gchar *mechanism;
55 };
56
57 struct _AsyncContext {
58         /* arguments */
59         GByteArray *token;
60         gchar *base64_token;
61
62         /* results */
63         GByteArray *response;
64         gchar *base64_response;
65 };
66
67 enum {
68         PROP_0,
69         PROP_AUTHENTICATED,
70         PROP_MECHANISM,
71         PROP_SERVICE,
72         PROP_SERVICE_NAME
73 };
74
75 G_DEFINE_ABSTRACT_TYPE (CamelSasl, camel_sasl, CAMEL_TYPE_OBJECT)
76
77 static void
78 async_context_free (AsyncContext *async_context)
79 {
80         if (async_context->token != NULL)
81                 g_byte_array_free (async_context->token, TRUE);
82
83         if (async_context->response != NULL)
84                 g_byte_array_free (async_context->response, TRUE);
85
86         g_free (async_context->base64_token);
87         g_free (async_context->base64_response);
88
89         g_slice_free (AsyncContext, async_context);
90 }
91
92 static void
93 sasl_build_class_table_rec (GType type,
94                             GHashTable *class_table)
95 {
96         GType *children;
97         guint n_children, ii;
98
99         children = g_type_children (type, &n_children);
100
101         for (ii = 0; ii < n_children; ii++) {
102                 GType type = children[ii];
103                 CamelSaslClass *sasl_class;
104                 gpointer key;
105
106                 /* Recurse over the child's children. */
107                 sasl_build_class_table_rec (type, class_table);
108
109                 /* Skip abstract types. */
110                 if (G_TYPE_IS_ABSTRACT (type))
111                         continue;
112
113                 sasl_class = g_type_class_ref (type);
114
115                 if (sasl_class->auth_type == NULL) {
116                         g_critical (
117                                 "%s has an empty CamelServiceAuthType",
118                                 G_OBJECT_CLASS_NAME (sasl_class));
119                         g_type_class_unref (sasl_class);
120                         continue;
121                 }
122
123                 key = (gpointer) sasl_class->auth_type->authproto;
124                 g_hash_table_insert (class_table, key, sasl_class);
125         }
126
127         g_free (children);
128 }
129
130 static GHashTable *
131 sasl_build_class_table (void)
132 {
133         GHashTable *class_table;
134
135         /* Register known types. */
136         CAMEL_TYPE_SASL_ANONYMOUS;
137         CAMEL_TYPE_SASL_CRAM_MD5;
138         CAMEL_TYPE_SASL_DIGEST_MD5;
139 #ifdef HAVE_KRB5
140         CAMEL_TYPE_SASL_GSSAPI;
141 #endif
142         CAMEL_TYPE_SASL_LOGIN;
143         CAMEL_TYPE_SASL_NTLM;
144         CAMEL_TYPE_SASL_PLAIN;
145         CAMEL_TYPE_SASL_POPB4SMTP;
146
147         class_table = g_hash_table_new_full (
148                 (GHashFunc) g_str_hash,
149                 (GEqualFunc) g_str_equal,
150                 (GDestroyNotify) NULL,
151                 (GDestroyNotify) g_type_class_unref);
152
153         sasl_build_class_table_rec (CAMEL_TYPE_SASL, class_table);
154
155         return class_table;
156 }
157
158 static void
159 sasl_set_mechanism (CamelSasl *sasl,
160                     const gchar *mechanism)
161 {
162         g_return_if_fail (mechanism != NULL);
163         g_return_if_fail (sasl->priv->mechanism == NULL);
164
165         sasl->priv->mechanism = g_strdup (mechanism);
166 }
167
168 static void
169 sasl_set_service (CamelSasl *sasl,
170                   CamelService *service)
171 {
172         g_return_if_fail (CAMEL_IS_SERVICE (service));
173         g_return_if_fail (sasl->priv->service == NULL);
174
175         sasl->priv->service = g_object_ref (service);
176 }
177
178 static void
179 sasl_set_service_name (CamelSasl *sasl,
180                        const gchar *service_name)
181 {
182         g_return_if_fail (service_name != NULL);
183         g_return_if_fail (sasl->priv->service_name == NULL);
184
185         sasl->priv->service_name = g_strdup (service_name);
186 }
187
188 static void
189 sasl_set_property (GObject *object,
190                    guint property_id,
191                    const GValue *value,
192                    GParamSpec *pspec)
193 {
194         switch (property_id) {
195                 case PROP_AUTHENTICATED:
196                         camel_sasl_set_authenticated (
197                                 CAMEL_SASL (object),
198                                 g_value_get_boolean (value));
199                         return;
200
201                 case PROP_MECHANISM:
202                         sasl_set_mechanism (
203                                 CAMEL_SASL (object),
204                                 g_value_get_string (value));
205                         return;
206
207                 case PROP_SERVICE:
208                         sasl_set_service (
209                                 CAMEL_SASL (object),
210                                 g_value_get_object (value));
211                         return;
212
213                 case PROP_SERVICE_NAME:
214                         sasl_set_service_name (
215                                 CAMEL_SASL (object),
216                                 g_value_get_string (value));
217                         return;
218         }
219
220         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
221 }
222
223 static void
224 sasl_get_property (GObject *object,
225                    guint property_id,
226                    GValue *value,
227                    GParamSpec *pspec)
228 {
229         switch (property_id) {
230                 case PROP_AUTHENTICATED:
231                         g_value_set_boolean (
232                                 value, camel_sasl_get_authenticated (
233                                 CAMEL_SASL (object)));
234                         return;
235
236                 case PROP_MECHANISM:
237                         g_value_set_string (
238                                 value, camel_sasl_get_mechanism (
239                                 CAMEL_SASL (object)));
240                         return;
241
242                 case PROP_SERVICE:
243                         g_value_set_object (
244                                 value, camel_sasl_get_service (
245                                 CAMEL_SASL (object)));
246                         return;
247
248                 case PROP_SERVICE_NAME:
249                         g_value_set_string (
250                                 value, camel_sasl_get_service_name (
251                                 CAMEL_SASL (object)));
252                         return;
253         }
254
255         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
256 }
257
258 static void
259 sasl_dispose (GObject *object)
260 {
261         CamelSaslPrivate *priv;
262
263         priv = CAMEL_SASL_GET_PRIVATE (object);
264
265         if (priv->service != NULL) {
266                 g_object_unref (priv->service);
267                 priv->service = NULL;
268         }
269
270         /* Chain up to parent's dispose() method. */
271         G_OBJECT_CLASS (camel_sasl_parent_class)->dispose (object);
272 }
273
274 static void
275 sasl_finalize (GObject *object)
276 {
277         CamelSaslPrivate *priv;
278
279         priv = CAMEL_SASL_GET_PRIVATE (object);
280
281         g_free (priv->mechanism);
282         g_free (priv->service_name);
283
284         /* Chain up to parent's finalize() method. */
285         G_OBJECT_CLASS (camel_sasl_parent_class)->finalize (object);
286 }
287
288 static void
289 sasl_challenge_thread (GSimpleAsyncResult *simple,
290                        GObject *object,
291                        GCancellable *cancellable)
292 {
293         AsyncContext *async_context;
294         GError *error = NULL;
295
296         async_context = g_simple_async_result_get_op_res_gpointer (simple);
297
298         async_context->response = camel_sasl_challenge_sync (
299                 CAMEL_SASL (object), async_context->token,
300                 cancellable, &error);
301
302         if (error != NULL)
303                 g_simple_async_result_take_error (simple, error);
304 }
305
306 static void
307 sasl_challenge (CamelSasl *sasl,
308                 GByteArray *token,
309                 gint io_priority,
310                 GCancellable *cancellable,
311                 GAsyncReadyCallback callback,
312                 gpointer user_data)
313 {
314         GSimpleAsyncResult *simple;
315         AsyncContext *async_context;
316
317         async_context = g_slice_new0 (AsyncContext);
318         async_context->token = g_byte_array_new ();
319
320         g_byte_array_append (async_context->token, token->data, token->len);
321
322         simple = g_simple_async_result_new (
323                 G_OBJECT (sasl), callback, user_data, sasl_challenge);
324
325         g_simple_async_result_set_check_cancellable (simple, cancellable);
326
327         g_simple_async_result_set_op_res_gpointer (
328                 simple, async_context, (GDestroyNotify) async_context_free);
329
330         g_simple_async_result_run_in_thread (
331                 simple, sasl_challenge_thread, io_priority, cancellable);
332
333         g_object_unref (simple);
334 }
335
336 static GByteArray *
337 sasl_challenge_finish (CamelSasl *sasl,
338                        GAsyncResult *result,
339                        GError **error)
340 {
341         GSimpleAsyncResult *simple;
342         AsyncContext *async_context;
343         GByteArray *response;
344
345         g_return_val_if_fail (
346                 g_simple_async_result_is_valid (
347                 result, G_OBJECT (sasl), sasl_challenge), NULL);
348
349         simple = G_SIMPLE_ASYNC_RESULT (result);
350         async_context = g_simple_async_result_get_op_res_gpointer (simple);
351
352         if (g_simple_async_result_propagate_error (simple, error))
353                 return NULL;
354
355         response = async_context->response;
356         async_context->response = NULL;
357
358         return response;
359 }
360
361 static void
362 sasl_try_empty_password_thread (GSimpleAsyncResult *simple,
363                                 GObject *object,
364                                 GCancellable *cancellable)
365 {
366         gboolean res;
367         GError *error = NULL;
368
369         res = camel_sasl_try_empty_password_sync (
370                 CAMEL_SASL (object), cancellable, &error);
371         g_simple_async_result_set_op_res_gboolean (simple, res);
372
373         if (error != NULL)
374                 g_simple_async_result_take_error (simple, error);
375 }
376
377 static void
378 sasl_try_empty_password (CamelSasl *sasl,
379                          gint io_priority,
380                          GCancellable *cancellable,
381                          GAsyncReadyCallback callback,
382                          gpointer user_data)
383 {
384         GSimpleAsyncResult *simple;
385
386         simple = g_simple_async_result_new (
387                 G_OBJECT (sasl), callback, user_data, sasl_try_empty_password);
388
389         g_simple_async_result_set_check_cancellable (simple, cancellable);
390
391         g_simple_async_result_run_in_thread (
392                 simple, sasl_try_empty_password_thread,
393                 io_priority, cancellable);
394
395         g_object_unref (simple);
396 }
397
398 static gboolean
399 sasl_try_empty_password_finish (CamelSasl *sasl,
400                                 GAsyncResult *result,
401                                 GError **error)
402 {
403         GSimpleAsyncResult *simple;
404
405         g_return_val_if_fail (
406                 g_simple_async_result_is_valid (
407                 result, G_OBJECT (sasl), sasl_try_empty_password), FALSE);
408
409         simple = G_SIMPLE_ASYNC_RESULT (result);
410
411         if (g_simple_async_result_propagate_error (simple, error))
412                 return FALSE;
413
414         return g_simple_async_result_get_op_res_gboolean (simple);
415 }
416
417 static void
418 camel_sasl_class_init (CamelSaslClass *class)
419 {
420         GObjectClass *object_class;
421
422         g_type_class_add_private (class, sizeof (CamelSaslPrivate));
423
424         object_class = G_OBJECT_CLASS (class);
425         object_class->set_property = sasl_set_property;
426         object_class->get_property = sasl_get_property;
427         object_class->dispose = sasl_dispose;
428         object_class->finalize = sasl_finalize;
429
430         class->challenge = sasl_challenge;
431         class->challenge_finish = sasl_challenge_finish;
432         class->try_empty_password = sasl_try_empty_password;
433         class->try_empty_password_finish = sasl_try_empty_password_finish;
434
435         g_object_class_install_property (
436                 object_class,
437                 PROP_AUTHENTICATED,
438                 g_param_spec_boolean (
439                         "authenticated",
440                         "Authenticated",
441                         NULL,
442                         FALSE,
443                         G_PARAM_READWRITE));
444
445         g_object_class_install_property (
446                 object_class,
447                 PROP_MECHANISM,
448                 g_param_spec_string (
449                         "mechanism",
450                         "Mechanism",
451                         NULL,
452                         NULL,
453                         G_PARAM_READWRITE |
454                         G_PARAM_CONSTRUCT_ONLY));
455
456         g_object_class_install_property (
457                 object_class,
458                 PROP_SERVICE,
459                 g_param_spec_object (
460                         "service",
461                         "Service",
462                         NULL,
463                         CAMEL_TYPE_SERVICE,
464                         G_PARAM_READWRITE |
465                         G_PARAM_CONSTRUCT_ONLY));
466
467         g_object_class_install_property (
468                 object_class,
469                 PROP_SERVICE_NAME,
470                 g_param_spec_string (
471                         "service-name",
472                         "Service Name",
473                         NULL,
474                         NULL,
475                         G_PARAM_READWRITE |
476                         G_PARAM_CONSTRUCT_ONLY));
477 }
478
479 static void
480 camel_sasl_init (CamelSasl *sasl)
481 {
482         sasl->priv = CAMEL_SASL_GET_PRIVATE (sasl);
483 }
484
485 /**
486  * camel_sasl_new:
487  * @service_name: the SASL service name
488  * @mechanism: the SASL mechanism
489  * @service: the CamelService that will be using this SASL
490  *
491  * Returns: a new #CamelSasl object for the given @service_name,
492  * @mechanism, and @service, or %NULL if the mechanism is not
493  * supported.
494  **/
495 CamelSasl *
496 camel_sasl_new (const gchar *service_name,
497                 const gchar *mechanism,
498                 CamelService *service)
499 {
500         GHashTable *class_table;
501         CamelSaslClass *sasl_class;
502         CamelSasl *sasl = NULL;
503
504         g_return_val_if_fail (service_name != NULL, NULL);
505         g_return_val_if_fail (mechanism != NULL, NULL);
506         g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
507
508         class_table = sasl_build_class_table ();
509         sasl_class = g_hash_table_lookup (class_table, mechanism);
510
511         if (sasl_class != NULL)
512                 sasl = g_object_new (
513                         G_OBJECT_CLASS_TYPE (sasl_class),
514                         "mechanism", mechanism,
515                         "service", service,
516                         "service-name", service_name,
517                         NULL);
518
519         g_hash_table_destroy (class_table);
520
521         return sasl;
522 }
523
524 /**
525  * camel_sasl_get_authenticated:
526  * @sasl: a #CamelSasl
527  *
528  * Returns: whether or not @sasl has successfully authenticated the
529  * user. This will be %TRUE after it returns the last needed response.
530  * The caller must still pass that information on to the server and
531  * verify that it has accepted it.
532  **/
533 gboolean
534 camel_sasl_get_authenticated (CamelSasl *sasl)
535 {
536         g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
537
538         return sasl->priv->authenticated;
539 }
540
541 /**
542  * camel_sasl_try_empty_password_sync:
543  * @sasl: a #CamelSasl object
544  * @cancellable: optional #GCancellable object, or %NULL
545  * @error: return location for a #GError, or %NULL
546  *
547  * Returns: whether or not @sasl can attempt to authenticate without a
548  * password being provided by the caller. This will be %TRUE for an
549  * authentication method which can attempt to use single-sign-on
550  * credentials, but which can fall back to using a provided password
551  * so it still has the @need_password flag set in its description.
552  *
553  * Since: 3.2
554  **/
555 gboolean
556 camel_sasl_try_empty_password_sync (CamelSasl *sasl,
557                                     GCancellable *cancellable,
558                                     GError **error)
559 {
560         CamelSaslClass *class;
561
562         g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
563
564         class = CAMEL_SASL_GET_CLASS (sasl);
565
566         if (class->try_empty_password_sync == NULL)
567                 return FALSE;
568
569         return class->try_empty_password_sync (sasl, cancellable, error);
570 }
571
572 /**
573  * camel_sasl_try_empty_password:
574  * @sasl: a #CamelSasl
575  * @io_priority: the I/O priority of the request
576  * @cancellable: optional #GCancellable object, or %NULL
577  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
578  * @user_data: data to pass to the callback function
579  *
580  * Asynchronously determine whether @sasl can be used for password-less
581  * authentication, for example single-sign-on using system credentials.
582  *
583  * When the operation is finished, @callback will be called.  You can then
584  * call camel_sasl_try_empty_password_finish() to get the result of the
585  * operation.
586  *
587  * Since: 3.2
588  **/
589 void
590 camel_sasl_try_empty_password (CamelSasl *sasl,
591                                gint io_priority,
592                                GCancellable *cancellable,
593                                GAsyncReadyCallback callback,
594                                gpointer user_data)
595 {
596         CamelSaslClass *class;
597
598         g_return_if_fail (CAMEL_IS_SASL (sasl));
599
600         class = CAMEL_SASL_GET_CLASS (sasl);
601         g_return_if_fail (class->try_empty_password != NULL);
602
603         class->try_empty_password (
604                 sasl, io_priority, cancellable, callback, user_data);
605 }
606
607 /**
608  * camel_sasl_try_empty_password_finish:
609  * @sasl: a #CamelSasl
610  * @result: a #GAsyncResult
611  * @error: return location for a #GError, or %NULL
612  *
613  * Finishes the operation started with camel_sasl_try_empty_password().
614  *
615  * Returns: the SASL response.  If an error occurred, @error will also be set.
616  *
617  * Since: 3.2
618  **/
619 gboolean
620 camel_sasl_try_empty_password_finish (CamelSasl *sasl,
621                                       GAsyncResult *result,
622                                       GError **error)
623 {
624         CamelSaslClass *class;
625
626         g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
627         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
628
629         class = CAMEL_SASL_GET_CLASS (sasl);
630         g_return_val_if_fail (class->try_empty_password_finish != NULL, FALSE);
631
632         return class->try_empty_password_finish (sasl, result, error);
633 }
634
635 /**
636  * camel_sasl_set_authenticated:
637  * @sasl: a #CamelSasl
638  * @authenticated: whether we have successfully authenticated
639  *
640  * Since: 2.32
641  **/
642 void
643 camel_sasl_set_authenticated (CamelSasl *sasl,
644                               gboolean authenticated)
645 {
646         g_return_if_fail (CAMEL_IS_SASL (sasl));
647
648         if (sasl->priv->authenticated == authenticated)
649                 return;
650
651         sasl->priv->authenticated = authenticated;
652
653         g_object_notify (G_OBJECT (sasl), "authenticated");
654 }
655
656 /**
657  * camel_sasl_get_mechanism:
658  * @sasl: a #CamelSasl
659  *
660  * Since: 2.32
661  **/
662 const gchar *
663 camel_sasl_get_mechanism (CamelSasl *sasl)
664 {
665         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
666
667         return sasl->priv->mechanism;
668 }
669
670 /**
671  * camel_sasl_get_service:
672  * @sasl: a #CamelSasl
673  *
674  * Since: 2.32
675  **/
676 CamelService *
677 camel_sasl_get_service (CamelSasl *sasl)
678 {
679         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
680
681         return sasl->priv->service;
682 }
683
684 /**
685  * camel_sasl_get_service_name:
686  * @sasl: a #CamelSasl
687  *
688  * Since: 2.32
689  **/
690 const gchar *
691 camel_sasl_get_service_name (CamelSasl *sasl)
692 {
693         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
694
695         return sasl->priv->service_name;
696 }
697
698 /**
699  * camel_sasl_challenge_sync:
700  * @sasl: a #CamelSasl
701  * @token: a token, or %NULL
702  * @cancellable: optional #GCancellable object, or %NULL
703  * @error: return location for a #GError, or %NULL
704  *
705  * If @token is %NULL, generate the initial SASL message to send to
706  * the server.  (This will be %NULL if the client doesn't initiate the
707  * exchange.)  Otherwise, @token is a challenge from the server, and
708  * the return value is the response.
709  *
710  * Free the returned #GByteArray with g_byte_array_free().
711  *
712  * Returns: the SASL response or %NULL. If an error occurred, @error will
713  * also be set.
714  **/
715 GByteArray *
716 camel_sasl_challenge_sync (CamelSasl *sasl,
717                            GByteArray *token,
718                            GCancellable *cancellable,
719                            GError **error)
720 {
721         CamelSaslClass *class;
722         GByteArray *response;
723
724         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
725
726         class = CAMEL_SASL_GET_CLASS (sasl);
727         g_return_val_if_fail (class->challenge_sync != NULL, NULL);
728
729         response = class->challenge_sync (sasl, token, cancellable, error);
730         if (token != NULL)
731                 CAMEL_CHECK_GERROR (
732                         sasl, challenge_sync, response != NULL, error);
733
734         return response;
735 }
736
737 /**
738  * camel_sasl_challenge:
739  * @sasl: a #CamelSasl
740  * @token: a token, or %NULL
741  * @io_priority: the I/O priority of the request
742  * @cancellable: optional #GCancellable object, or %NULL
743  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
744  * @user_data: data to pass to the callback function
745  *
746  * If @token is %NULL, asynchronously generate the initial SASL message
747  * to send to the server.  (This will be %NULL if the client doesn't
748  * initiate the exchange.)  Otherwise, @token is a challenge from the
749  * server, and the asynchronous result is the response.
750  *
751  * When the operation is finished, @callback will be called.  You can then
752  * call camel_sasl_challenge_finish() to get the result of the operation.
753  *
754  * Since: 3.0
755  **/
756 void
757 camel_sasl_challenge (CamelSasl *sasl,
758                       GByteArray *token,
759                       gint io_priority,
760                       GCancellable *cancellable,
761                       GAsyncReadyCallback callback,
762                       gpointer user_data)
763 {
764         CamelSaslClass *class;
765
766         g_return_if_fail (CAMEL_IS_SASL (sasl));
767
768         class = CAMEL_SASL_GET_CLASS (sasl);
769         g_return_if_fail (class->challenge != NULL);
770
771         class->challenge (
772                 sasl, token, io_priority, cancellable, callback, user_data);
773 }
774
775 /**
776  * camel_sasl_challenge_finish:
777  * @sasl: a #CamelSasl
778  * @result: a #GAsyncResult
779  * @error: return location for a #GError, or %NULL
780  *
781  * Finishes the operation started with camel_sasl_challenge().  Free the
782  * returned #GByteArray with g_byte_array_free().
783  *
784  * Returns: the SASL response or %NULL.  If an error occurred, @error will
785  * also be set.
786  *
787  * Since: 3.0
788  **/
789 GByteArray *
790 camel_sasl_challenge_finish (CamelSasl *sasl,
791                              GAsyncResult *result,
792                              GError **error)
793 {
794         CamelSaslClass *class;
795
796         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
797         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
798
799         class = CAMEL_SASL_GET_CLASS (sasl);
800         g_return_val_if_fail (class->challenge_finish != NULL, NULL);
801
802         return class->challenge_finish (sasl, result, error);
803 }
804
805 /**
806  * camel_sasl_challenge_base64_sync:
807  * @sasl: a #CamelSasl
808  * @token: a base64-encoded token
809  * @cancellable: optional #GCancellable object, or %NULL
810  * @error: return location for a #GError, or %NULL
811  *
812  * As with camel_sasl_challenge_sync(), but the challenge @token and the
813  * response are both base64-encoded.
814  *
815  * Returns: the base64-encoded response
816  *
817  * Since: 3.0
818  **/
819 gchar *
820 camel_sasl_challenge_base64_sync (CamelSasl *sasl,
821                                   const gchar *token,
822                                   GCancellable *cancellable,
823                                   GError **error)
824 {
825         GByteArray *token_binary;
826         GByteArray *response_binary;
827         gchar *response;
828
829         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
830
831         if (token != NULL && *token != '\0') {
832                 guchar *data;
833                 gsize length = 0;
834
835                 data = g_base64_decode (token, &length);
836                 token_binary = g_byte_array_new ();
837                 g_byte_array_append (token_binary, data, length);
838                 g_free (data);
839         } else
840                 token_binary = NULL;
841
842         response_binary = camel_sasl_challenge_sync (
843                 sasl, token_binary, cancellable, error);
844         if (token_binary)
845                 g_byte_array_free (token_binary, TRUE);
846         if (response_binary == NULL)
847                 return NULL;
848
849         if (response_binary->len > 0)
850                 response = g_base64_encode (
851                         response_binary->data, response_binary->len);
852         else
853                 response = g_strdup ("");
854
855         g_byte_array_free (response_binary, TRUE);
856
857         return response;
858 }
859
860 /* Helper for camel_sasl_challenge_base64() */
861 static void
862 sasl_challenge_base64_thread (GSimpleAsyncResult *simple,
863                               GObject *object,
864                               GCancellable *cancellable)
865 {
866         AsyncContext *async_context;
867         GError *error = NULL;
868
869         async_context = g_simple_async_result_get_op_res_gpointer (simple);
870
871         async_context->base64_response = camel_sasl_challenge_base64_sync (
872                 CAMEL_SASL (object), async_context->base64_token,
873                 cancellable, &error);
874
875         if (error != NULL)
876                 g_simple_async_result_take_error (simple, error);
877 }
878
879 /**
880  * camel_sasl_challenge_base64:
881  * @sasl: a #CamelSasl
882  * @token: a base64-encoded token
883  * @io_priority: the I/O priority of the request
884  * @cancellable: optional #GCancellable object, or %NULL
885  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
886  * @user_data: data to pass to the callback function
887  *
888  * As with camel_sasl_challenge(), but the challenge @token and the
889  * response are both base64-encoded.
890  *
891  * When the operation is finished, @callback will be called.  You can
892  * then call camel_store_challenge_base64_finish() to get the result of
893  * the operation.
894  *
895  * Since: 3.0
896  **/
897 void
898 camel_sasl_challenge_base64 (CamelSasl *sasl,
899                              const gchar *token,
900                              gint io_priority,
901                              GCancellable *cancellable,
902                              GAsyncReadyCallback callback,
903                              gpointer user_data)
904 {
905         GSimpleAsyncResult *simple;
906         AsyncContext *async_context;
907
908         g_return_if_fail (CAMEL_IS_SASL (sasl));
909
910         async_context = g_slice_new0 (AsyncContext);
911         async_context->base64_token = g_strdup (token);
912
913         simple = g_simple_async_result_new (
914                 G_OBJECT (sasl), callback, user_data,
915                 camel_sasl_challenge_base64);
916
917         g_simple_async_result_set_check_cancellable (simple, cancellable);
918
919         g_simple_async_result_set_op_res_gpointer (
920                 simple, async_context, (GDestroyNotify) async_context_free);
921
922         g_simple_async_result_run_in_thread (
923                 simple, sasl_challenge_base64_thread,
924                 io_priority, cancellable);
925
926         g_object_unref (simple);
927 }
928
929 /**
930  * camel_sasl_challenge_base64_finish:
931  * @sasl: a #CamelSasl
932  * @result: a #GAsyncResult
933  * @error: return location for a #GError, or %NULL
934  *
935  * Finishes the operation started with camel_sasl_challenge_base64().
936  *
937  * Returns: the base64-encoded response
938  *
939  * Since: 3.0
940  **/
941 gchar *
942 camel_sasl_challenge_base64_finish (CamelSasl *sasl,
943                                     GAsyncResult *result,
944                                     GError **error)
945 {
946         GSimpleAsyncResult *simple;
947         AsyncContext *async_context;
948         gchar *response;
949
950         g_return_val_if_fail (CAMEL_IS_SASL (sasl), NULL);
951         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
952
953         g_return_val_if_fail (
954                 g_simple_async_result_is_valid (
955                 result, G_OBJECT (sasl), camel_sasl_challenge_base64), NULL);
956
957         simple = G_SIMPLE_ASYNC_RESULT (result);
958         async_context = g_simple_async_result_get_op_res_gpointer (simple);
959
960         if (g_simple_async_result_propagate_error (simple, error))
961                 return NULL;
962
963         response = async_context->base64_response;
964         async_context->base64_response = NULL;
965
966         return response;
967 }
968
969 /**
970  * camel_sasl_authtype_list:
971  * @include_plain: whether or not to include the PLAIN mechanism
972  *
973  * Returns: a #GList of SASL-supported authtypes. The caller must
974  * free the list, but not the contents.
975  **/
976 GList *
977 camel_sasl_authtype_list (gboolean include_plain)
978 {
979         CamelSaslClass *sasl_class;
980         GHashTable *class_table;
981         GList *types = NULL;
982
983         /* XXX I guess these are supposed to be common SASL auth types,
984          *     since this is called by the IMAP, POP and SMTP providers.
985          *     The returned list can be extended with other auth types
986          *     by way of camel_sasl_authtype(), so maybe we should just
987          *     drop the ad-hoc "include_plain" parameter? */
988
989         class_table = sasl_build_class_table ();
990
991         sasl_class = g_hash_table_lookup (class_table, "CRAM-MD5");
992         g_return_val_if_fail (sasl_class != NULL, types);
993         types = g_list_prepend (types, sasl_class->auth_type);
994
995         sasl_class = g_hash_table_lookup (class_table, "DIGEST-MD5");
996         g_return_val_if_fail (sasl_class != NULL, types);
997         types = g_list_prepend (types, sasl_class->auth_type);
998
999 #ifdef HAVE_KRB5
1000         sasl_class = g_hash_table_lookup (class_table, "GSSAPI");
1001         g_return_val_if_fail (sasl_class != NULL, types);
1002         types = g_list_prepend (types, sasl_class->auth_type);
1003 #endif
1004
1005         sasl_class = g_hash_table_lookup (class_table, "NTLM");
1006         g_return_val_if_fail (sasl_class != NULL, types);
1007         types = g_list_prepend (types, sasl_class->auth_type);
1008
1009         if (include_plain) {
1010                 sasl_class = g_hash_table_lookup (class_table, "PLAIN");
1011                 g_return_val_if_fail (sasl_class != NULL, types);
1012                 types = g_list_prepend (types, sasl_class->auth_type);
1013         }
1014
1015         g_hash_table_destroy (class_table);
1016
1017         return types;
1018 }
1019
1020 /**
1021  * camel_sasl_authtype:
1022  * @mechanism: the SASL mechanism to get an authtype for
1023  *
1024  * Returns: a #CamelServiceAuthType for the given mechanism, if
1025  * it is supported.
1026  **/
1027 CamelServiceAuthType *
1028 camel_sasl_authtype (const gchar *mechanism)
1029 {
1030         GHashTable *class_table;
1031         CamelSaslClass *sasl_class;
1032         CamelServiceAuthType *auth_type;
1033
1034         g_return_val_if_fail (mechanism != NULL, NULL);
1035
1036         class_table = sasl_build_class_table ();
1037         sasl_class = g_hash_table_lookup (class_table, mechanism);
1038         auth_type = (sasl_class != NULL) ? sasl_class->auth_type : NULL;
1039         g_hash_table_destroy (class_table);
1040
1041         return auth_type;
1042 }