Rename camel_service_get_settings().
[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 ("%p:%lu:%lu", (gpointer) resp,
603                                 (gulong) getpid (),
604                                 (gulong) time (NULL));
605         checksum = g_checksum_new (G_CHECKSUM_MD5);
606         g_checksum_update (checksum, (guchar *) bgen, -1);
607         g_checksum_get_digest (checksum, digest, &length);
608         g_checksum_free (checksum);
609         g_free (bgen);
610
611         /* take our recommended 64 bits of entropy */
612         resp->cnonce = g_base64_encode ((guchar *) digest, 8);
613
614         /* we don't support re-auth so the nonce count is always 1 */
615         strcpy (resp->nc, "00000001");
616
617         /* choose the QOP */
618         /* FIXME: choose - probably choose "auth" ??? */
619         resp->qop = QOP_AUTH;
620
621         /* create the URI */
622         uri = g_new0 (struct _DigestURI, 1);
623         uri->type = g_strdup (protocol);
624         uri->host = g_strdup (host);
625         uri->name = NULL;
626         resp->uri = uri;
627
628         /* charsets... yay */
629         if (challenge->charset) {
630                 /* I believe that this is only ever allowed to be
631                  * UTF-8. We strdup the charset specified by the
632                  * challenge anyway, just in case it's not UTF-8.
633                  */
634                 resp->charset = g_strdup (challenge->charset);
635         }
636
637         resp->cipher = CIPHER_INVALID;
638         if (resp->qop == QOP_AUTH_CONF) {
639                 /* FIXME: choose a cipher? */
640                 resp->cipher = CIPHER_INVALID;
641         }
642
643         /* we don't really care about this... */
644         resp->authzid = NULL;
645
646         compute_response (resp, passwd, TRUE, (guchar *) resp->resp);
647
648         return resp;
649 }
650
651 static GByteArray *
652 digest_response (struct _DigestResponse *resp)
653 {
654         GByteArray *buffer;
655         const gchar *str;
656         gchar *buf;
657
658         buffer = g_byte_array_new ();
659         g_byte_array_append (buffer, (guint8 *) "username=\"", 10);
660         if (resp->charset) {
661                 /* Encode the username using the requested charset */
662                 gchar *username, *outbuf;
663                 const gchar *charset;
664                 gsize len, outlen;
665                 const gchar *inbuf;
666                 iconv_t cd;
667
668                 charset = camel_iconv_locale_charset ();
669                 if (!charset)
670                         charset = "iso-8859-1";
671
672                 cd = camel_iconv_open (resp->charset, charset);
673
674                 len = strlen (resp->username);
675                 outlen = 2 * len; /* plenty of space */
676
677                 outbuf = username = g_malloc0 (outlen + 1);
678                 inbuf = resp->username;
679                 if (cd == (iconv_t) -1 || camel_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (gsize) -1) {
680                         /* We can't convert to UTF-8 - pretend we never got a charset param? */
681                         g_free (resp->charset);
682                         resp->charset = NULL;
683
684                         /* Set the username to the non-UTF-8 version */
685                         g_free (username);
686                         username = g_strdup (resp->username);
687                 }
688
689                 if (cd != (iconv_t) -1)
690                         camel_iconv_close (cd);
691
692                 g_byte_array_append (buffer, (guint8 *) username, strlen (username));
693                 g_free (username);
694         } else {
695                 g_byte_array_append (buffer, (guint8 *) resp->username, strlen (resp->username));
696         }
697
698         g_byte_array_append (buffer, (guint8 *) "\",realm=\"", 9);
699         g_byte_array_append (buffer, (guint8 *) resp->realm, strlen (resp->realm));
700
701         g_byte_array_append (buffer, (guint8 *) "\",nonce=\"", 9);
702         g_byte_array_append (buffer, (guint8 *) resp->nonce, strlen (resp->nonce));
703
704         g_byte_array_append (buffer, (guint8 *) "\",cnonce=\"", 10);
705         g_byte_array_append (buffer, (guint8 *) resp->cnonce, strlen (resp->cnonce));
706
707         g_byte_array_append (buffer, (guint8 *) "\",nc=", 5);
708         g_byte_array_append (buffer, (guint8 *) resp->nc, 8);
709
710         g_byte_array_append (buffer, (guint8 *) ",qop=", 5);
711         str = qop_to_string (resp->qop);
712         g_byte_array_append (buffer, (guint8 *) str, strlen (str));
713
714         g_byte_array_append (buffer, (guint8 *) ",digest-uri=\"", 13);
715         buf = digest_uri_to_string (resp->uri);
716         g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
717         g_free (buf);
718
719         g_byte_array_append (buffer, (guint8 *) "\",response=", 11);
720         g_byte_array_append (buffer, (guint8 *) resp->resp, 32);
721
722         if (resp->maxbuf > 0) {
723                 g_byte_array_append (buffer, (guint8 *) ",maxbuf=", 8);
724                 buf = g_strdup_printf ("%u", resp->maxbuf);
725                 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
726                 g_free (buf);
727         }
728
729         if (resp->charset) {
730                 g_byte_array_append (buffer, (guint8 *) ",charset=", 9);
731                 g_byte_array_append (buffer, (guint8 *) resp->charset, strlen ((gchar *) resp->charset));
732         }
733
734         if (resp->cipher != CIPHER_INVALID) {
735                 str = cipher_to_string (resp->cipher);
736                 if (str) {
737                         g_byte_array_append (buffer, (guint8 *) ",cipher=\"", 9);
738                         g_byte_array_append (buffer, (guint8 *) str, strlen (str));
739                         g_byte_array_append (buffer, (guint8 *) "\"", 1);
740                 }
741         }
742
743         if (resp->authzid) {
744                 g_byte_array_append (buffer, (guint8 *) ",authzid=\"", 10);
745                 g_byte_array_append (buffer, (guint8 *) resp->authzid, strlen (resp->authzid));
746                 g_byte_array_append (buffer, (guint8 *) "\"", 1);
747         }
748
749         return buffer;
750 }
751
752 static void
753 sasl_digest_md5_finalize (GObject *object)
754 {
755         CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
756         struct _DigestChallenge *c = sasl->priv->challenge;
757         struct _DigestResponse *r = sasl->priv->response;
758         GList *p;
759         gint i;
760
761         if (c != NULL) {
762                 for (i = 0; i < c->realms->len; i++)
763                         g_free (c->realms->pdata[i]);
764                 g_ptr_array_free (c->realms, TRUE);
765
766                 g_free (c->nonce);
767                 g_free (c->charset);
768                 g_free (c->algorithm);
769                 for (p = c->params; p; p = p->next) {
770                         struct _param *param = p->data;
771
772                         g_free (param->name);
773                         g_free (param->value);
774                         g_free (param);
775                 }
776                 g_list_free (c->params);
777                 g_free (c);
778         }
779
780         if (r != NULL) {
781                 g_free (r->username);
782                 g_free (r->realm);
783                 g_free (r->nonce);
784                 g_free (r->cnonce);
785                 if (r->uri) {
786                         g_free (r->uri->type);
787                         g_free (r->uri->host);
788                 g_free (r->uri->name);
789                 }
790                 g_free (r->charset);
791                 g_free (r->authzid);
792                 g_free (r->param);
793                 g_free (r);
794         }
795
796         /* Chain up to parent's finalize() method. */
797         G_OBJECT_CLASS (camel_sasl_digest_md5_parent_class)->finalize (object);
798 }
799
800 static GByteArray *
801 sasl_digest_md5_challenge_sync (CamelSasl *sasl,
802                                 GByteArray *token,
803                                 GCancellable *cancellable,
804                                 GError **error)
805 {
806         CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
807         struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
808         CamelNetworkSettings *network_settings;
809         CamelSettings *settings;
810         CamelService *service;
811         struct _param *rspauth;
812         GByteArray *ret = NULL;
813         gboolean abort = FALSE;
814         const gchar *ptr;
815         guchar out[33];
816         gchar *tokens;
817         struct addrinfo *ai, hints;
818         const gchar *service_name;
819         const gchar *password;
820         gchar *host;
821         gchar *user;
822
823         /* Need to wait for the server */
824         if (!token)
825                 return NULL;
826
827         service = camel_sasl_get_service (sasl);
828         service_name = camel_sasl_get_service_name (sasl);
829
830         settings = camel_service_ref_settings (service);
831         g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
832
833         network_settings = CAMEL_NETWORK_SETTINGS (settings);
834         host = camel_network_settings_dup_host (network_settings);
835         user = camel_network_settings_dup_user (network_settings);
836
837         g_object_unref (settings);
838
839         g_return_val_if_fail (user != NULL, NULL);
840
841         if (host == NULL)
842                 host = g_strdup ("localhost");
843
844         password = camel_service_get_password (service);
845         g_return_val_if_fail (password != NULL, NULL);
846
847         switch (priv->state) {
848         case STATE_AUTH:
849                 if (token->len > 2048) {
850                         g_set_error (
851                                 error, CAMEL_SERVICE_ERROR,
852                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
853                                 _("Server challenge too long (>2048 octets)"));
854                         goto exit;
855                 }
856
857                 tokens = g_strndup ((gchar *) token->data, token->len);
858                 priv->challenge = parse_server_challenge (tokens, &abort);
859                 g_free (tokens);
860                 if (!priv->challenge || abort) {
861                         g_set_error (
862                                 error, CAMEL_SERVICE_ERROR,
863                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
864                                 _("Server challenge invalid\n"));
865                         goto exit;
866                 }
867
868                 if (priv->challenge->qop == QOP_INVALID) {
869                         g_set_error (
870                                 error, CAMEL_SERVICE_ERROR,
871                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
872                                 _("Server challenge contained invalid "
873                                   "\"Quality of Protection\" token"));
874                         goto exit;
875                 }
876
877                 memset (&hints, 0, sizeof (hints));
878                 hints.ai_flags = AI_CANONNAME;
879                 ai = camel_getaddrinfo (
880                         host, NULL, &hints, cancellable, NULL);
881                 if (ai && ai->ai_canonname)
882                         ptr = ai->ai_canonname;
883                 else
884                         ptr = "localhost.localdomain";
885
886                 priv->response = generate_response (
887                         priv->challenge, ptr, service_name,
888                         user, password);
889                 if (ai)
890                         camel_freeaddrinfo (ai);
891                 ret = digest_response (priv->response);
892
893                 break;
894         case STATE_FINAL:
895                 if (token->len)
896                         tokens = g_strndup ((gchar *) token->data, token->len);
897                 else
898                         tokens = NULL;
899
900                 if (!tokens || !*tokens) {
901                         g_free (tokens);
902                         g_set_error (
903                                 error, CAMEL_SERVICE_ERROR,
904                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
905                                 _("Server response did not contain "
906                                   "authorization data"));
907                         goto exit;
908                 }
909
910                 rspauth = g_new0 (struct _param, 1);
911
912                 ptr = tokens;
913                 rspauth->name = decode_token (&ptr);
914                 if (*ptr == '=') {
915                         ptr++;
916                         rspauth->value = decode_value (&ptr);
917                 }
918                 g_free (tokens);
919
920                 if (!rspauth->value) {
921                         g_free (rspauth->name);
922                         g_free (rspauth);
923                         g_set_error (
924                                 error, CAMEL_SERVICE_ERROR,
925                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
926                                 _("Server response contained incomplete "
927                                   "authorization data"));
928                         goto exit;
929                 }
930
931                 compute_response (priv->response, password, FALSE, out);
932                 if (memcmp (out, rspauth->value, 32) != 0) {
933                         g_free (rspauth->name);
934                         g_free (rspauth->value);
935                         g_free (rspauth);
936                         g_set_error (
937                                 error, CAMEL_SERVICE_ERROR,
938                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
939                                 _("Server response does not match"));
940                         camel_sasl_set_authenticated (sasl, TRUE);
941                         goto exit;
942                 }
943
944                 g_free (rspauth->name);
945                 g_free (rspauth->value);
946                 g_free (rspauth);
947
948                 ret = g_byte_array_new ();
949
950                 camel_sasl_set_authenticated (sasl, TRUE);
951         default:
952                 break;
953         }
954
955         priv->state++;
956
957 exit:
958         g_free (host);
959         g_free (user);
960
961         return ret;
962 }
963
964 static void
965 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *class)
966 {
967         GObjectClass *object_class;
968         CamelSaslClass *sasl_class;
969
970         g_type_class_add_private (class, sizeof (CamelSaslDigestMd5Private));
971
972         object_class = G_OBJECT_CLASS (class);
973         object_class->finalize = sasl_digest_md5_finalize;
974
975         sasl_class = CAMEL_SASL_CLASS (class);
976         sasl_class->auth_type = &sasl_digest_md5_auth_type;
977         sasl_class->challenge_sync = sasl_digest_md5_challenge_sync;
978 }
979
980 static void
981 camel_sasl_digest_md5_init (CamelSaslDigestMd5 *sasl)
982 {
983         sasl->priv = CAMEL_SASL_DIGEST_MD5_GET_PRIVATE (sasl);
984 }