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