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