Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-sasl-digest-md5.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 <ctype.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <glib/gi18n-lib.h>
33
34 #include "camel-charset-map.h"
35 #include "camel-iconv.h"
36 #include "camel-mime-utils.h"
37 #include "camel-net-utils.h"
38 #include "camel-network-settings.h"
39 #ifdef G_OS_WIN32
40 #include <winsock2.h>
41 #include <ws2tcpip.h>
42 #ifdef HAVE_WSPIAPI_H
43 #include <wspiapi.h>
44 #endif
45 #endif
46 #include "camel-sasl-digest-md5.h"
47
48 #define d(x)
49
50 #define PARANOID(x) x
51
52 #define CAMEL_SASL_DIGEST_MD5_GET_PRIVATE(obj) \
53         (G_TYPE_INSTANCE_GET_PRIVATE \
54         ((obj), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5Private))
55
56 /* Implements rfc2831 */
57
58 static CamelServiceAuthType sasl_digest_md5_auth_type = {
59         N_("DIGEST-MD5"),
60
61         N_("This option will connect to the server using a "
62            "secure DIGEST-MD5 password, if the server supports it."),
63
64         "DIGEST-MD5",
65         TRUE
66 };
67
68 enum {
69         STATE_AUTH,
70         STATE_FINAL
71 };
72
73 typedef struct {
74         const gchar *name;
75         guint type;
76 } DataType;
77
78 enum {
79         DIGEST_REALM,
80         DIGEST_NONCE,
81         DIGEST_QOP,
82         DIGEST_STALE,
83         DIGEST_MAXBUF,
84         DIGEST_CHARSET,
85         DIGEST_ALGORITHM,
86         DIGEST_CIPHER,
87         DIGEST_UNKNOWN
88 };
89
90 static DataType digest_args[] = {
91         { "realm",     DIGEST_REALM     },
92         { "nonce",     DIGEST_NONCE     },
93         { "qop",       DIGEST_QOP       },
94         { "stale",     DIGEST_STALE     },
95         { "maxbuf",    DIGEST_MAXBUF    },
96         { "charset",   DIGEST_CHARSET   },
97         { "algorithm", DIGEST_ALGORITHM },
98         { "cipher",    DIGEST_CIPHER    },
99         { NULL,        DIGEST_UNKNOWN   }
100 };
101
102 #define QOP_AUTH           (1 << 0)
103 #define QOP_AUTH_INT       (1 << 1)
104 #define QOP_AUTH_CONF      (1 << 2)
105 #define QOP_INVALID        (1 << 3)
106
107 static DataType qop_types[] = {
108         { "auth",      QOP_AUTH      },
109         { "auth-int",  QOP_AUTH_INT  },
110         { "auth-conf", QOP_AUTH_CONF },
111         { NULL,        QOP_INVALID   }
112 };
113
114 #define CIPHER_DES         (1 << 0)
115 #define CIPHER_3DES        (1 << 1)
116 #define CIPHER_RC4         (1 << 2)
117 #define CIPHER_RC4_40      (1 << 3)
118 #define CIPHER_RC4_56      (1 << 4)
119 #define CIPHER_INVALID     (1 << 5)
120
121 static DataType cipher_types[] = {
122         { "des",    CIPHER_DES     },
123         { "3des",   CIPHER_3DES    },
124         { "rc4",    CIPHER_RC4     },
125         { "rc4-40", CIPHER_RC4_40  },
126         { "rc4-56", CIPHER_RC4_56  },
127         { NULL,     CIPHER_INVALID }
128 };
129
130 struct _param {
131         gchar *name;
132         gchar *value;
133 };
134
135 struct _DigestChallenge {
136         GPtrArray *realms;
137         gchar *nonce;
138         guint qop;
139         gboolean stale;
140         gint32 maxbuf;
141         gchar *charset;
142         gchar *algorithm;
143         guint cipher;
144         GList *params;
145 };
146
147 struct _DigestURI {
148         gchar *type;
149         gchar *host;
150         gchar *name;
151 };
152
153 struct _DigestResponse {
154         gchar *username;
155         gchar *realm;
156         gchar *nonce;
157         gchar *cnonce;
158         gchar nc[9];
159         guint qop;
160         struct _DigestURI *uri;
161         gchar resp[33];
162         guint32 maxbuf;
163         gchar *charset;
164         guint cipher;
165         gchar *authzid;
166         gchar *param;
167 };
168
169 struct _CamelSaslDigestMd5Private {
170         struct _DigestChallenge *challenge;
171         struct _DigestResponse *response;
172         gint state;
173 };
174
175 G_DEFINE_TYPE (CamelSaslDigestMd5, camel_sasl_digest_md5, CAMEL_TYPE_SASL)
176
177 static void
178 decode_lwsp (const gchar **in)
179 {
180         const gchar *inptr = *in;
181
182         while (isspace (*inptr))
183                 inptr++;
184
185         *in = inptr;
186 }
187
188 static gchar *
189 decode_quoted_string (const gchar **in)
190 {
191         const gchar *inptr = *in;
192         gchar *out = NULL, *outptr;
193         gint outlen;
194         gint c;
195
196         decode_lwsp (&inptr);
197         if (*inptr == '"') {
198                 const gchar *intmp;
199                 gint skip = 0;
200
201                 /* first, calc length */
202                 inptr++;
203                 intmp = inptr;
204                 while ((c = *intmp++) && c != '"') {
205                         if (c == '\\' && *intmp) {
206                                 intmp++;
207                                 skip++;
208                         }
209                 }
210
211                 outlen = intmp - inptr - skip;
212                 out = outptr = g_malloc (outlen + 1);
213
214                 while ((c = *inptr++) && c != '"') {
215                         if (c == '\\' && *inptr) {
216                                 c = *inptr++;
217                         }
218                         *outptr++ = c;
219                 }
220                 *outptr = '\0';
221         }
222
223         *in = inptr;
224
225         return out;
226 }
227
228 static gchar *
229 decode_token (const gchar **in)
230 {
231         const gchar *inptr = *in;
232         const gchar *start;
233
234         decode_lwsp (&inptr);
235         start = inptr;
236
237         while (*inptr && *inptr != '=' && *inptr != ',')
238                 inptr++;
239
240         if (inptr > start) {
241                 *in = inptr;
242                 return g_strndup (start, inptr - start);
243         } else {
244                 return NULL;
245         }
246 }
247
248 static gchar *
249 decode_value (const gchar **in)
250 {
251         const gchar *inptr = *in;
252
253         decode_lwsp (&inptr);
254         if (*inptr == '"') {
255                 d (printf ("decoding quoted string token\n"));
256                 return decode_quoted_string (in);
257         } else {
258                 d (printf ("decoding string token\n"));
259                 return decode_token (in);
260         }
261 }
262
263 static GList *
264 parse_param_list (const gchar *tokens)
265 {
266         GList *params = NULL;
267         struct _param *param;
268         const gchar *ptr;
269
270         for (ptr = tokens; ptr && *ptr; ) {
271                 param = g_new0 (struct _param, 1);
272                 param->name = decode_token (&ptr);
273                 if (*ptr == '=') {
274                         ptr++;
275                         param->value = decode_value (&ptr);
276                 }
277
278                 params = g_list_prepend (params, param);
279
280                 if (*ptr == ',')
281                         ptr++;
282         }
283
284         return params;
285 }
286
287 static guint
288 decode_data_type (DataType *dtype,
289                   const gchar *name)
290 {
291         gint i;
292
293         for (i = 0; dtype[i].name; i++) {
294                 if (!g_ascii_strcasecmp (dtype[i].name, name))
295                         break;
296         }
297
298         return dtype[i].type;
299 }
300
301 #define get_digest_arg(name) decode_data_type (digest_args, name)
302 #define decode_qop(name)     decode_data_type (qop_types, name)
303 #define decode_cipher(name)  decode_data_type (cipher_types, name)
304
305 static const gchar *
306 type_to_string (DataType *dtype,
307                 guint type)
308 {
309         gint i;
310
311         for (i = 0; dtype[i].name; i++) {
312                 if (dtype[i].type == type)
313                         break;
314         }
315
316         return dtype[i].name;
317 }
318
319 #define qop_to_string(type)    type_to_string (qop_types, type)
320 #define cipher_to_string(type) type_to_string (cipher_types, type)
321
322 static void
323 digest_abort (gboolean *have_type,
324               gboolean *abort)
325 {
326         if (*have_type)
327                 *abort = TRUE;
328         *have_type = TRUE;
329 }
330
331 static struct _DigestChallenge *
332 parse_server_challenge (const gchar *tokens,
333                         gboolean *abort)
334 {
335         struct _DigestChallenge *challenge = NULL;
336         GList *params, *p;
337         const gchar *ptr;
338 #ifdef PARANOID
339         gboolean got_algorithm = FALSE;
340         gboolean got_stale = FALSE;
341         gboolean got_maxbuf = FALSE;
342         gboolean got_charset = FALSE;
343 #endif /* PARANOID */
344
345         params = parse_param_list (tokens);
346         if (!params) {
347                 *abort = TRUE;
348                 return NULL;
349         }
350
351         *abort = FALSE;
352
353         challenge = g_new0 (struct _DigestChallenge, 1);
354         challenge->realms = g_ptr_array_new ();
355         challenge->maxbuf = 65536;
356
357         for (p = params; p; p = p->next) {
358                 struct _param *param = p->data;
359                 gint type;
360
361                 type = get_digest_arg (param->name);
362                 switch (type) {
363                 case DIGEST_REALM:
364                         for (ptr = param->value; ptr && *ptr; ) {
365                                 gchar *token;
366
367                                 token = decode_token (&ptr);
368                                 if (token)
369                                         g_ptr_array_add (challenge->realms, token);
370
371                                 if (*ptr == ',')
372                                         ptr++;
373                         }
374                         g_free (param->value);
375                         g_free (param->name);
376                         g_free (param);
377                         break;
378                 case DIGEST_NONCE:
379                         g_free (challenge->nonce);
380                         challenge->nonce = param->value;
381                         g_free (param->name);
382                         g_free (param);
383                         break;
384                 case DIGEST_QOP:
385                         for (ptr = param->value; ptr && *ptr; ) {
386                                 gchar *token;
387
388                                 token = decode_token (&ptr);
389                                 if (token)
390                                         challenge->qop |= decode_qop (token);
391
392                                 if (*ptr == ',')
393                                         ptr++;
394                         }
395
396                         if (challenge->qop & QOP_INVALID)
397                                 challenge->qop = QOP_INVALID;
398                         g_free (param->value);
399                         g_free (param->name);
400                         g_free (param);
401                         break;
402                 case DIGEST_STALE:
403                         PARANOID (digest_abort (&got_stale, abort));
404                         if (!g_ascii_strcasecmp (param->value, "true"))
405                                 challenge->stale = TRUE;
406                         else
407                                 challenge->stale = FALSE;
408                         g_free (param->value);
409                         g_free (param->name);
410                         g_free (param);
411                         break;
412                 case DIGEST_MAXBUF:
413                         PARANOID (digest_abort (&got_maxbuf, abort));
414                         challenge->maxbuf = atoi (param->value);
415                         g_free (param->value);
416                         g_free (param->name);
417                         g_free (param);
418                         break;
419                 case DIGEST_CHARSET:
420                         PARANOID (digest_abort (&got_charset, abort));
421                         g_free (challenge->charset);
422                         if (param->value && *param->value)
423                                 challenge->charset = param->value;
424                         else
425                                 challenge->charset = NULL;
426                         g_free (param->name);
427                         g_free (param);
428                         break;
429                 case DIGEST_ALGORITHM:
430                         PARANOID (digest_abort (&got_algorithm, abort));
431                         g_free (challenge->algorithm);
432                         challenge->algorithm = param->value;
433                         g_free (param->name);
434                         g_free (param);
435                         break;
436                 case DIGEST_CIPHER:
437                         for (ptr = param->value; ptr && *ptr; ) {
438                                 gchar *token;
439
440                                 token = decode_token (&ptr);
441                                 if (token)
442                                         challenge->cipher |= decode_cipher (token);
443
444                                 if (*ptr == ',')
445                                         ptr++;
446                         }
447                         if (challenge->cipher & CIPHER_INVALID)
448                                 challenge->cipher = CIPHER_INVALID;
449                         g_free (param->value);
450                         g_free (param->name);
451                         g_free (param);
452                         break;
453                 default:
454                         challenge->params = g_list_prepend (challenge->params, param);
455                         break;
456                 }
457         }
458
459         g_list_free (params);
460
461         return challenge;
462 }
463
464 static gchar *
465 digest_uri_to_string (struct _DigestURI *uri)
466 {
467         if (uri->name)
468                 return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
469         else
470                 return g_strdup_printf ("%s/%s", uri->type, uri->host);
471 }
472
473 static void
474 compute_response (struct _DigestResponse *resp,
475                   const gchar *passwd,
476                   gboolean client,
477                   guchar out[33])
478 {
479         GString *buffer;
480         GChecksum *checksum;
481         guint8 *digest;
482         gsize length;
483         gchar *hex_a1;
484         gchar *hex_a2;
485         gchar *hex_kd;
486         gchar *uri;
487
488         buffer = g_string_sized_new (256);
489         length = g_checksum_type_get_length (G_CHECKSUM_MD5);
490         digest = g_alloca (length);
491
492         /* Compute A1. */
493
494         g_string_append (buffer, resp->username);
495         g_string_append_c (buffer, ':');
496         g_string_append (buffer, resp->realm);
497         g_string_append_c (buffer, ':');
498         g_string_append (buffer, passwd);
499
500         checksum = g_checksum_new (G_CHECKSUM_MD5);
501         g_checksum_update (
502                 checksum, (const guchar *) buffer->str, buffer->len);
503         g_checksum_get_digest (checksum, digest, &length);
504         g_checksum_free (checksum);
505
506         /* Clear the buffer. */
507         g_string_truncate (buffer, 0);
508
509         g_string_append_len (buffer, (gchar *) digest, length);
510         g_string_append_c (buffer, ':');
511         g_string_append (buffer, resp->nonce);
512         g_string_append_c (buffer, ':');
513         g_string_append (buffer, resp->cnonce);
514         if (resp->authzid != NULL) {
515                 g_string_append_c (buffer, ':');
516                 g_string_append (buffer, resp->authzid);
517         }
518
519         hex_a1 = g_compute_checksum_for_string (
520                 G_CHECKSUM_MD5, buffer->str, buffer->len);
521
522         /* Clear the buffer. */
523         g_string_truncate (buffer, 0);
524
525         /* Compute A2. */
526
527         if (client) {
528                 /* We are calculating the client response. */
529                 g_string_append (buffer, "AUTHENTICATE:");
530         } else {
531                 /* We are calculating the server rspauth. */
532                 g_string_append_c (buffer, ':');
533         }
534
535         uri = digest_uri_to_string (resp->uri);
536         g_string_append (buffer, uri);
537         g_free (uri);
538
539         if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
540                 g_string_append (buffer, ":00000000000000000000000000000000");
541
542         hex_a2 = g_compute_checksum_for_string (
543                 G_CHECKSUM_MD5, buffer->str, buffer->len);
544
545         /* Clear the buffer. */
546         g_string_truncate (buffer, 0);
547
548         /* Compute KD. */
549
550         g_string_append (buffer, hex_a1);
551         g_string_append_c (buffer, ':');
552         g_string_append (buffer, resp->nonce);
553         g_string_append_c (buffer, ':');
554         g_string_append_len (buffer, resp->nc, 8);
555         g_string_append_c (buffer, ':');
556         g_string_append (buffer, resp->cnonce);
557         g_string_append_c (buffer, ':');
558         g_string_append (buffer, qop_to_string (resp->qop));
559         g_string_append_c (buffer, ':');
560         g_string_append (buffer, hex_a2);
561
562         hex_kd = g_compute_checksum_for_string (
563                 G_CHECKSUM_MD5, buffer->str, buffer->len);
564
565         g_strlcpy ((gchar *) out, hex_kd, 33);
566
567         g_free (hex_a1);
568         g_free (hex_a2);
569         g_free (hex_kd);
570
571         g_string_free (buffer, TRUE);
572 }
573
574 static struct _DigestResponse *
575 generate_response (struct _DigestChallenge *challenge,
576                    const gchar *host,
577                    const gchar *protocol,
578                    const gchar *user,
579                    const gchar *passwd)
580 {
581         struct _DigestResponse *resp;
582         struct _DigestURI *uri;
583         GChecksum *checksum;
584         guint8 *digest;
585         gsize length;
586         gchar *bgen;
587
588         length = g_checksum_type_get_length (G_CHECKSUM_MD5);
589         digest = g_alloca (length);
590
591         resp = g_new0 (struct _DigestResponse, 1);
592         resp->username = g_strdup (user);
593         /* FIXME: we should use the preferred realm */
594         if (challenge->realms && challenge->realms->len > 0)
595                 resp->realm = g_strdup (challenge->realms->pdata[0]);
596         else
597                 resp->realm = g_strdup ("");
598
599         resp->nonce = g_strdup (challenge->nonce);
600
601         /* generate the cnonce */
602         bgen = g_strdup_printf (
603                 "%p:%lu:%lu",
604                 (gpointer) resp,
605                 (gulong) getpid (),
606                 (gulong) time (NULL));
607         checksum = g_checksum_new (G_CHECKSUM_MD5);
608         g_checksum_update (checksum, (guchar *) bgen, -1);
609         g_checksum_get_digest (checksum, digest, &length);
610         g_checksum_free (checksum);
611         g_free (bgen);
612
613         /* take our recommended 64 bits of entropy */
614         resp->cnonce = g_base64_encode ((guchar *) digest, 8);
615
616         /* we don't support re-auth so the nonce count is always 1 */
617         strcpy (resp->nc, "00000001");
618
619         /* choose the QOP */
620         /* FIXME: choose - probably choose "auth" ??? */
621         resp->qop = QOP_AUTH;
622
623         /* create the URI */
624         uri = g_new0 (struct _DigestURI, 1);
625         uri->type = g_strdup (protocol);
626         uri->host = g_strdup (host);
627         uri->name = NULL;
628         resp->uri = uri;
629
630         /* charsets... yay */
631         if (challenge->charset) {
632                 /* I believe that this is only ever allowed to be
633                  * UTF-8. We strdup the charset specified by the
634                  * challenge anyway, just in case it's not UTF-8.
635                  */
636                 resp->charset = g_strdup (challenge->charset);
637         }
638
639         resp->cipher = CIPHER_INVALID;
640         if (resp->qop == QOP_AUTH_CONF) {
641                 /* FIXME: choose a cipher? */
642                 resp->cipher = CIPHER_INVALID;
643         }
644
645         /* we don't really care about this... */
646         resp->authzid = NULL;
647
648         compute_response (resp, passwd, TRUE, (guchar *) resp->resp);
649
650         return resp;
651 }
652
653 static GByteArray *
654 digest_response (struct _DigestResponse *resp)
655 {
656         GByteArray *buffer;
657         const gchar *str;
658         gchar *buf;
659
660         buffer = g_byte_array_new ();
661         g_byte_array_append (buffer, (guint8 *) "username=\"", 10);
662         if (resp->charset) {
663                 /* Encode the username using the requested charset */
664                 gchar *username, *outbuf;
665                 const gchar *charset;
666                 gsize len, outlen;
667                 const gchar *inbuf;
668                 iconv_t cd;
669
670                 charset = camel_iconv_locale_charset ();
671                 if (!charset)
672                         charset = "iso-8859-1";
673
674                 cd = camel_iconv_open (resp->charset, charset);
675
676                 len = strlen (resp->username);
677                 outlen = 2 * len; /* plenty of space */
678
679                 outbuf = username = g_malloc0 (outlen + 1);
680                 inbuf = resp->username;
681                 if (cd == (iconv_t) -1 || camel_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (gsize) -1) {
682                         /* We can't convert to UTF-8 - pretend we never got a charset param? */
683                         g_free (resp->charset);
684                         resp->charset = NULL;
685
686                         /* Set the username to the non-UTF-8 version */
687                         g_free (username);
688                         username = g_strdup (resp->username);
689                 }
690
691                 if (cd != (iconv_t) -1)
692                         camel_iconv_close (cd);
693
694                 g_byte_array_append (buffer, (guint8 *) username, strlen (username));
695                 g_free (username);
696         } else {
697                 g_byte_array_append (buffer, (guint8 *) resp->username, strlen (resp->username));
698         }
699
700         g_byte_array_append (buffer, (guint8 *) "\",realm=\"", 9);
701         g_byte_array_append (buffer, (guint8 *) resp->realm, strlen (resp->realm));
702
703         g_byte_array_append (buffer, (guint8 *) "\",nonce=\"", 9);
704         g_byte_array_append (buffer, (guint8 *) resp->nonce, strlen (resp->nonce));
705
706         g_byte_array_append (buffer, (guint8 *) "\",cnonce=\"", 10);
707         g_byte_array_append (buffer, (guint8 *) resp->cnonce, strlen (resp->cnonce));
708
709         g_byte_array_append (buffer, (guint8 *) "\",nc=", 5);
710         g_byte_array_append (buffer, (guint8 *) resp->nc, 8);
711
712         g_byte_array_append (buffer, (guint8 *) ",qop=", 5);
713         str = qop_to_string (resp->qop);
714         g_byte_array_append (buffer, (guint8 *) str, strlen (str));
715
716         g_byte_array_append (buffer, (guint8 *) ",digest-uri=\"", 13);
717         buf = digest_uri_to_string (resp->uri);
718         g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
719         g_free (buf);
720
721         g_byte_array_append (buffer, (guint8 *) "\",response=", 11);
722         g_byte_array_append (buffer, (guint8 *) resp->resp, 32);
723
724         if (resp->maxbuf > 0) {
725                 g_byte_array_append (buffer, (guint8 *) ",maxbuf=", 8);
726                 buf = g_strdup_printf ("%u", resp->maxbuf);
727                 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
728                 g_free (buf);
729         }
730
731         if (resp->charset) {
732                 g_byte_array_append (buffer, (guint8 *) ",charset=", 9);
733                 g_byte_array_append (buffer, (guint8 *) resp->charset, strlen ((gchar *) resp->charset));
734         }
735
736         if (resp->cipher != CIPHER_INVALID) {
737                 str = cipher_to_string (resp->cipher);
738                 if (str) {
739                         g_byte_array_append (buffer, (guint8 *) ",cipher=\"", 9);
740                         g_byte_array_append (buffer, (guint8 *) str, strlen (str));
741                         g_byte_array_append (buffer, (guint8 *) "\"", 1);
742                 }
743         }
744
745         if (resp->authzid) {
746                 g_byte_array_append (buffer, (guint8 *) ",authzid=\"", 10);
747                 g_byte_array_append (buffer, (guint8 *) resp->authzid, strlen (resp->authzid));
748                 g_byte_array_append (buffer, (guint8 *) "\"", 1);
749         }
750
751         return buffer;
752 }
753
754 static void
755 sasl_digest_md5_finalize (GObject *object)
756 {
757         CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
758         struct _DigestChallenge *c = sasl->priv->challenge;
759         struct _DigestResponse *r = sasl->priv->response;
760         GList *p;
761         gint i;
762
763         if (c != NULL) {
764                 for (i = 0; i < c->realms->len; i++)
765                         g_free (c->realms->pdata[i]);
766                 g_ptr_array_free (c->realms, TRUE);
767
768                 g_free (c->nonce);
769                 g_free (c->charset);
770                 g_free (c->algorithm);
771                 for (p = c->params; p; p = p->next) {
772                         struct _param *param = p->data;
773
774                         g_free (param->name);
775                         g_free (param->value);
776                         g_free (param);
777                 }
778                 g_list_free (c->params);
779                 g_free (c);
780         }
781
782         if (r != NULL) {
783                 g_free (r->username);
784                 g_free (r->realm);
785                 g_free (r->nonce);
786                 g_free (r->cnonce);
787                 if (r->uri) {
788                         g_free (r->uri->type);
789                         g_free (r->uri->host);
790                 g_free (r->uri->name);
791                 }
792                 g_free (r->charset);
793                 g_free (r->authzid);
794                 g_free (r->param);
795                 g_free (r);
796         }
797
798         /* Chain up to parent's finalize() method. */
799         G_OBJECT_CLASS (camel_sasl_digest_md5_parent_class)->finalize (object);
800 }
801
802 static GByteArray *
803 sasl_digest_md5_challenge_sync (CamelSasl *sasl,
804                                 GByteArray *token,
805                                 GCancellable *cancellable,
806                                 GError **error)
807 {
808         CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
809         struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
810         CamelNetworkSettings *network_settings;
811         CamelSettings *settings;
812         CamelService *service;
813         struct _param *rspauth;
814         GByteArray *ret = NULL;
815         gboolean abort = FALSE;
816         const gchar *ptr;
817         guchar out[33];
818         gchar *tokens;
819         struct addrinfo *ai, hints;
820         const gchar *service_name;
821         const gchar *password;
822         gchar *host;
823         gchar *user;
824
825         /* Need to wait for the server */
826         if (!token)
827                 return NULL;
828
829         service = camel_sasl_get_service (sasl);
830         service_name = camel_sasl_get_service_name (sasl);
831
832         settings = camel_service_ref_settings (service);
833         g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
834
835         network_settings = CAMEL_NETWORK_SETTINGS (settings);
836         host = camel_network_settings_dup_host (network_settings);
837         user = camel_network_settings_dup_user (network_settings);
838
839         g_object_unref (settings);
840
841         g_return_val_if_fail (user != NULL, NULL);
842
843         if (host == NULL)
844                 host = g_strdup ("localhost");
845
846         password = camel_service_get_password (service);
847         g_return_val_if_fail (password != NULL, NULL);
848
849         switch (priv->state) {
850         case STATE_AUTH:
851                 if (token->len > 2048) {
852                         g_set_error (
853                                 error, CAMEL_SERVICE_ERROR,
854                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
855                                 _("Server challenge too long (>2048 octets)"));
856                         goto exit;
857                 }
858
859                 tokens = g_strndup ((gchar *) token->data, token->len);
860                 priv->challenge = parse_server_challenge (tokens, &abort);
861                 g_free (tokens);
862                 if (!priv->challenge || abort) {
863                         g_set_error (
864                                 error, CAMEL_SERVICE_ERROR,
865                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
866                                 _("Server challenge invalid\n"));
867                         goto exit;
868                 }
869
870                 if (priv->challenge->qop == QOP_INVALID) {
871                         g_set_error (
872                                 error, CAMEL_SERVICE_ERROR,
873                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
874                                 _("Server challenge contained invalid "
875                                 "\"Quality of Protection\" token"));
876                         goto exit;
877                 }
878
879                 memset (&hints, 0, sizeof (hints));
880                 hints.ai_flags = AI_CANONNAME;
881                 ai = camel_getaddrinfo (
882                         host, NULL, &hints, cancellable, NULL);
883                 if (ai && ai->ai_canonname)
884                         ptr = ai->ai_canonname;
885                 else
886                         ptr = "localhost.localdomain";
887
888                 priv->response = generate_response (
889                         priv->challenge, ptr, service_name,
890                         user, password);
891                 if (ai)
892                         camel_freeaddrinfo (ai);
893                 ret = digest_response (priv->response);
894
895                 break;
896         case STATE_FINAL:
897                 if (token->len)
898                         tokens = g_strndup ((gchar *) token->data, token->len);
899                 else
900                         tokens = NULL;
901
902                 if (!tokens || !*tokens) {
903                         g_free (tokens);
904                         g_set_error (
905                                 error, CAMEL_SERVICE_ERROR,
906                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
907                                 _("Server response did not contain "
908                                 "authorization data"));
909                         goto exit;
910                 }
911
912                 rspauth = g_new0 (struct _param, 1);
913
914                 ptr = tokens;
915                 rspauth->name = decode_token (&ptr);
916                 if (*ptr == '=') {
917                         ptr++;
918                         rspauth->value = decode_value (&ptr);
919                 }
920                 g_free (tokens);
921
922                 if (!rspauth->value) {
923                         g_free (rspauth->name);
924                         g_free (rspauth);
925                         g_set_error (
926                                 error, CAMEL_SERVICE_ERROR,
927                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
928                                 _("Server response contained incomplete "
929                                 "authorization data"));
930                         goto exit;
931                 }
932
933                 compute_response (priv->response, password, FALSE, out);
934                 if (memcmp (out, rspauth->value, 32) != 0) {
935                         g_free (rspauth->name);
936                         g_free (rspauth->value);
937                         g_free (rspauth);
938                         g_set_error (
939                                 error, CAMEL_SERVICE_ERROR,
940                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
941                                 _("Server response does not match"));
942                         camel_sasl_set_authenticated (sasl, TRUE);
943                         goto exit;
944                 }
945
946                 g_free (rspauth->name);
947                 g_free (rspauth->value);
948                 g_free (rspauth);
949
950                 ret = g_byte_array_new ();
951
952                 camel_sasl_set_authenticated (sasl, TRUE);
953         default:
954                 break;
955         }
956
957         priv->state++;
958
959 exit:
960         g_free (host);
961         g_free (user);
962
963         return ret;
964 }
965
966 static void
967 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *class)
968 {
969         GObjectClass *object_class;
970         CamelSaslClass *sasl_class;
971
972         g_type_class_add_private (class, sizeof (CamelSaslDigestMd5Private));
973
974         object_class = G_OBJECT_CLASS (class);
975         object_class->finalize = sasl_digest_md5_finalize;
976
977         sasl_class = CAMEL_SASL_CLASS (class);
978         sasl_class->auth_type = &sasl_digest_md5_auth_type;
979         sasl_class->challenge_sync = sasl_digest_md5_challenge_sync;
980 }
981
982 static void
983 camel_sasl_digest_md5_init (CamelSaslDigestMd5 *sasl)
984 {
985         sasl->priv = CAMEL_SASL_DIGEST_MD5_GET_PRIVATE (sasl);
986 }