aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-sasl-digest-md5.c
blob: fa0d9a817512bfad3df84d57fb989a0c366dfb8b (plain) (tree)
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486





















                                                                           
                    
                   

      



                   
                  



                                  
 

































































































































































































































































































































































































































































                                                                                                        



                                                                  















































                                                                                      












                                                                                                    
                          
                       


                        

















                                                                         
        

                                 


                                    
                        
                     

                                                                             

                                                           
                                          

         



                                               
                                                                    



                                                                           


                                    













                                                                                         
                                 












                                                                              
                                                            






























                                                                     




                                                                 



                                         
                                             



















                                                          





























                                                                                           







































































































                                                                                               
                                   




























































                                                                                                              
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2001 Ximian, Inc. (www.ximian.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <iconv.h>
#include "camel-sasl-digest-md5.h"
#include "camel-mime-utils.h"
#include <e-util/md5-utils.h>


#define d(x)

#define PARANOID(x) x

CamelServiceAuthType camel_sasl_digest_md5_authtype = {
    N_("DIGEST-MD5"),

    N_("This option will connect to the server using a "
       "secure DIGEST-MD5 password, if the server supports it."),

    "DIGEST-MD5",
    TRUE
};

static CamelSaslClass *parent_class = NULL;

/* Returns the class for a CamelSaslDigestMd5 */
#define CSCM_CLASS(so) CAMEL_SASL_DIGEST_MD5_CLASS (CAMEL_OBJECT_GET_CLASS (so))

static GByteArray *digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);

enum {
    STATE_AUTH,
    STATE_FINAL
};

typedef struct {
    char *name;
    guint type;
} DataType;

enum {
    DIGEST_REALM,
    DIGEST_NONCE,
    DIGEST_QOP,
    DIGEST_STALE,
    DIGEST_MAXBUF,
    DIGEST_CHARSET,
    DIGEST_ALGORITHM,
    DIGEST_CIPHER,
    DIGEST_UNKNOWN
};

static DataType digest_args[] = {
    { "realm",     DIGEST_REALM     },
    { "nonce",     DIGEST_NONCE     },
    { "qop",       DIGEST_QOP       },
    { "stale",     DIGEST_STALE     },
    { "maxbuf",    DIGEST_MAXBUF    },
    { "charset",   DIGEST_CHARSET   },
    { "algorithm", DIGEST_ALGORITHM },
    { "cipher",    DIGEST_CIPHER    },
    { NULL,        DIGEST_UNKNOWN   }
};

#define QOP_AUTH           (1<<0)
#define QOP_AUTH_INT       (1<<1)
#define QOP_AUTH_CONF      (1<<2)
#define QOP_INVALID        (1<<3)

static DataType qop_types[] = {
    { "auth",      QOP_AUTH      },
    { "auth-int",  QOP_AUTH_INT  },
    { "auth-conf", QOP_AUTH_CONF },
    { NULL,        QOP_INVALID   }
};

#define CIPHER_DES         (1<<0)
#define CIPHER_3DES        (1<<1)
#define CIPHER_RC4         (1<<2)
#define CIPHER_RC4_40      (1<<3)
#define CIPHER_RC4_56      (1<<4)
#define CIPHER_INVALID     (1<<5)

static DataType cipher_types[] = {
    { "des",    CIPHER_DES     },
    { "3des",   CIPHER_3DES    },
    { "rc4",    CIPHER_RC4     },
    { "rc4-40", CIPHER_RC4_40  },
    { "rc4-56", CIPHER_RC4_56  },
    { NULL,     CIPHER_INVALID }
};

struct _param {
    char *name;
    char *value;
};

struct _DigestChallenge {
    GPtrArray *realms;
    char *nonce;
    guint qop;
    gboolean stale;
    gint32 maxbuf;
    char *charset;
    char *algorithm;
    guint cipher;
    GList *params;
};

struct _DigestURI {
    char *type;
    char *host;
    char *name;
};

struct _DigestResponse {
    char *username;
    char *realm;
    char *nonce;
    char *cnonce;
    char nc[9];
    guint qop;
    struct _DigestURI *uri;
    char resp[33];
    guint32 maxbuf;
    char *charset;
    guint cipher;
    char *authzid;
    char *param;
};

struct _CamelSaslDigestMd5Private {
    struct _DigestChallenge *challenge;
    struct _DigestResponse *response;
    int state;
};

static void
camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *camel_sasl_digest_md5_class)
{
    CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_digest_md5_class);
    
    parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
    
    /* virtual method overload */
    camel_sasl_class->challenge = digest_md5_challenge;
}

static void
camel_sasl_digest_md5_init (gpointer object, gpointer klass)
{
    CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (object);
    
    sasl_digest->priv = g_new0 (struct _CamelSaslDigestMd5Private, 1);
}

static void
camel_sasl_digest_md5_finalize (CamelObject *object)
{
    CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
    struct _DigestChallenge *c = sasl->priv->challenge;
    struct _DigestResponse *r = sasl->priv->response;
    GList *p;
    int i;
    
    for (i = 0; i < c->realms->len; i++)
        g_free (c->realms->pdata[i]);
    g_ptr_array_free (c->realms, TRUE);
    g_free (c->nonce);
    g_free (c->charset);
    g_free (c->algorithm);
    for (p = c->params; p; p = p->next) {
        struct _param *param = p->data;
        
        g_free (param->name);
        g_free (param->value);
        g_free (param);
    }
    g_list_free (c->params);
    g_free (c);
    
    g_free (r->username);
    g_free (r->realm);
    g_free (r->nonce);
    g_free (r->cnonce);
    if (r->uri) {
        g_free (r->uri->type);
        g_free (r->uri->host);
        g_free (r->uri->name);
    }
    g_free (r->charset);
    g_free (r->authzid);
    g_free (r->param);
    g_free (r);
    
    g_free (sasl->priv);
}


CamelType
camel_sasl_digest_md5_get_type (void)
{
    static CamelType type = CAMEL_INVALID_TYPE;
    
    if (type == CAMEL_INVALID_TYPE) {
        type = camel_type_register (camel_sasl_get_type (),
                        "CamelSaslDigestMd5",
                        sizeof (CamelSaslDigestMd5),
                        sizeof (CamelSaslDigestMd5Class),
                        (CamelObjectClassInitFunc) camel_sasl_digest_md5_class_init,
                        NULL,
                        (CamelObjectInitFunc) camel_sasl_digest_md5_init,
                        (CamelObjectFinalizeFunc) camel_sasl_digest_md5_finalize);
    }
    
    return type;
}

static void
decode_lwsp (const char **in)
{
    const char *inptr = *in;
    
    while (isspace (*inptr))
        inptr++;
    
    *in = inptr;
}

static char *
decode_quoted_string (const char **in)
{
    const char *inptr = *in;
    char *out = NULL, *outptr;
    int outlen;
    int c;
    
    decode_lwsp (&inptr);
    if (*inptr == '"') {
        const char *intmp;
        int skip = 0;
        
        /* first, calc length */
        inptr++;
        intmp = inptr;
        while ((c = *intmp++) && c != '"') {
            if (c == '\\' && *intmp) {
                intmp++;
                skip++;
            }
        }
        
        outlen = intmp - inptr - skip;
        out = outptr = g_malloc (outlen + 1);
        
        while ((c = *inptr++) && c != '"') {
            if (c == '\\' && *inptr) {
                c = *inptr++;
            }
            *outptr++ = c;
        }
        *outptr = 0;
    }
    
    *in = inptr;
    
    return out;
}

static char *
decode_token (const char **in)
{
    const char *inptr = *in;
    const char *start;
    
    decode_lwsp (&inptr);
    start = inptr;
    
    while (*inptr && *inptr != '=' && *inptr != ',')
        inptr++;
    
    if (inptr > start) {
        *in = inptr;
        return g_strndup (start, inptr - start);
    } else {
        return NULL;
    }
}

static char *
decode_value (const char **in)
{
    const char *inptr = *in;
    
    decode_lwsp (&inptr);
    if (*inptr == '"') {
        d(printf ("decoding quoted string token\n"));
        return decode_quoted_string (in);
    } else {
        d(printf ("decoding string token\n"));
        return decode_token (in);
    }
}

static GList *
parse_param_list (const char *tokens)
{
    GList *params = NULL;
    struct _param *param;
    const char *ptr;
    
    for (ptr = tokens; ptr && *ptr; ) {
        param = g_new0 (struct _param, 1);
        param->name = decode_token (&ptr);
        if (*ptr == '=') {
            ptr++;
            param->value = decode_value (&ptr);
        }
        
        params = g_list_prepend (params, param);
        
        if (*ptr == ',')
            ptr++;
    }
    
    return params;
}

static guint
decode_data_type (DataType *dtype, const char *name)
{
    int i;
    
    for (i = 0; dtype[i].name; i++) {
        if (!g_strcasecmp (dtype[i].name, name))
            break;
    }
    
    return dtype[i].type;
}

#define get_digest_arg(name) decode_data_type (digest_args, name)
#define decode_qop(name)     decode_data_type (qop_types, name)
#define decode_cipher(name)  decode_data_type (cipher_types, name)

static const char *
type_to_string (DataType *dtype, guint type)
{
    int i;
    
    for (i = 0; dtype[i].name; i++) {
        if (dtype[i].type == type)
            break;
    }
    
    return dtype[i].name;
}

#define qop_to_string(type)    type_to_string (qop_types, type)
#define cipher_to_string(type) type_to_string (cipher_types, type)

static void
digest_abort (gboolean *have_type, gboolean *abort)
{
    if (*have_type)
        *abort = TRUE;
    *have_type = TRUE;
}

static struct _DigestChallenge *
parse_server_challenge (const char *tokens, gboolean *abort)
{
    struct _DigestChallenge *challenge = NULL;
    GList *params, *p;
    const char *ptr;
#ifdef PARANOID
    gboolean got_algorithm = FALSE;
    gboolean got_stale = FALSE;
    gboolean got_maxbuf = FALSE;
    gboolean got_charset = FALSE;
#endif /* PARANOID */
    
    params = parse_param_list (tokens);
    if (!params) {
        *abort = TRUE;
        return NULL;
    }
    
    *abort = FALSE;
    
    challenge = g_new0 (struct _DigestChallenge, 1);
    challenge->realms = g_ptr_array_new ();
    challenge->maxbuf = 65536;
    
    for (p = params; p; p = p->next) {
        struct _param *param = p->data;
        int type;
        
        type = get_digest_arg (param->name);
        switch (type) {
        case DIGEST_REALM:
            for (ptr = param->value; ptr && *ptr; ) {
                char *token;
                
                token = decode_token (&ptr);
                if (token)
                    g_ptr_array_add (challenge->realms, token);
                
                if (*ptr == ',')
                    ptr++;
            }
            g_free (param->value);
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_NONCE:
            g_free (challenge->nonce);
            challenge->nonce = param->value;
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_QOP:
            for (ptr = param->value; ptr && *ptr; ) {
                char *token;
                
                token = decode_token (&ptr);
                if (token)
                    challenge->qop |= decode_qop (token);
                
                if (*ptr == ',')
                    ptr++;
            }
            
            if (challenge->qop & QOP_INVALID)
                challenge->qop = QOP_INVALID;
            g_free (param->value);
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_STALE:
            PARANOID (digest_abort (&got_stale, abort));
            if (!g_strcasecmp (param->value, "true"))
                challenge->stale = TRUE;
            else
                challenge->stale = FALSE;
            g_free (param->value);
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_MAXBUF:
            PARANOID (digest_abort (&got_maxbuf, abort));
            challenge->maxbuf = atoi (param->value);
            g_free (param->value);
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_CHARSET:
            PARANOID (digest_abort (&got_charset, abort));
            g_free (challenge->charset);
            if (param->value && *param->value)
                challenge->charset = param->value;
            else
                challenge->charset = NULL;
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_ALGORITHM:
            PARANOID (digest_abort (&got_algorithm, abort));
            g_free (challenge->algorithm);
            challenge->algorithm = param->value;
            g_free (param->name);
            g_free (param);
            break;
        case DIGEST_CIPHER:
            for (ptr = param->value; ptr && *ptr; ) {
                char *token;
                
                token = decode_token (&ptr);
                if (token)
                    challenge->cipher |= decode_cipher (token);
                
                if (*ptr == ',')
                    ptr++;
            }
            if (challenge->cipher & CIPHER_INVALID)
                challenge->cipher = CIPHER_INVALID;
            g_free (param->value);
            g_free (param->name);
            g_free (param);
            break;
        default:
            challenge->params = g_list_prepend (challenge->params, param);
            break;
        }
    }
    
    g_list_free (params);
    
    return challenge;
}

static void
digest_hex (guchar *digest, guchar hex[33])
{
    guchar *s, *p;
    
    /* lowercase hexify that bad-boy... */
    for (s = digest, p = hex; p < hex + 32; s++, p += 2)
        sprintf (p, "%.2x", *s);
}

static char *
digest_uri_to_string (struct _DigestURI *uri)
{
    if (uri->name)
        return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
    else
        return g_strdup_printf ("%s/%s", uri->type, uri->host);
}

static void
compute_response (struct _DigestResponse *resp, const char *passwd, gboolean client, guchar out[33])
{
    guchar hex_a1[33], hex_a2[33];
    guchar digest[16];
    MD5Context ctx;
    char *buf;
    
    /* compute A1 */
    md5_init (&ctx);
    md5_update (&ctx, resp->username, strlen (resp->username));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->realm, strlen (resp->realm));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, passwd, strlen (passwd));
    md5_final (&ctx, digest);
    
    md5_init (&ctx);
    md5_update (&ctx, digest, 16);
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->nonce, strlen (resp->nonce));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
    if (resp->authzid) {
        md5_update (&ctx, ":", 1);
        md5_update (&ctx, resp->authzid, strlen (resp->authzid));
    }
    
    /* hexify A1 */
    md5_final (&ctx, digest);
    digest_hex (digest, hex_a1);
    
    /* compute A2 */
    md5_init (&ctx);
    if (client) {
        /* we are calculating the client response */
        md5_update (&ctx, "AUTHENTICATE:", strlen ("AUTHENTICATE:"));
    } else {
        /* we are calculating the server rspauth */
        md5_update (&ctx, ":", 1);
    }
    
    buf = digest_uri_to_string (resp->uri);
    md5_update (&ctx, buf, strlen (buf));
    g_free (buf);
    
    if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
        md5_update (&ctx, ":00000000000000000000000000000000", 33);
    
    /* now hexify A2 */
    md5_final (&ctx, digest);
    digest_hex (digest, hex_a2);
    
    /* compute KD */
    md5_init (&ctx);
    md5_update (&ctx, hex_a1, 32);
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->nonce, strlen (resp->nonce));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->nc, 8);
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, qop_to_string (resp->qop), strlen (qop_to_string (resp->qop)));
    md5_update (&ctx, ":", 1);
    md5_update (&ctx, hex_a2, 32);
    md5_final (&ctx, digest);
    
    digest_hex (digest, out);
}

static struct _DigestResponse *
generate_response (struct _DigestChallenge *challenge, struct hostent *host,
           const char *protocol, const char *user, const char *passwd)
{
    struct _DigestResponse *resp;
    struct _DigestURI *uri;
    char *bgen, digest[16];
    
    resp = g_new0 (struct _DigestResponse, 1);
    resp->username = g_strdup (user);
    /* FIXME: we should use the preferred realm */
    if (challenge->realms && challenge->realms->len > 0)
        resp->realm = g_strdup (challenge->realms->pdata[0]);
    else
        resp->realm = g_strdup ("");
    
    resp->nonce = g_strdup (challenge->nonce);
    
    /* generate the cnonce */
    bgen = g_strdup_printf ("%p:%lu:%lu", resp,
                (unsigned long) getpid (),
                (unsigned long) time (0));
    md5_get_digest (bgen, strlen (bgen), digest);
    g_free (bgen);
    /* take our recommended 64 bits of entropy */
    resp->cnonce = base64_encode_simple (digest, 8);
    
    /* we don't support re-auth so the nonce count is always 1 */
    strcpy (resp->nc, "00000001");
    
    /* choose the QOP */
    /* FIXME: choose - probably choose "auth" ??? */
    resp->qop = QOP_AUTH;
    
    /* create the URI */
    uri = g_new0 (struct _DigestURI, 1);
    uri->type = g_strdup (protocol);
    uri->host = g_strdup (host->h_name);
    uri->name = NULL;
    resp->uri = uri;
    
    /* charsets... yay */
    if (challenge->charset) {
        /* I believe that this is only ever allowed to be
         * UTF-8. We strdup the charset specified by the
         * challenge anyway, just in case it's not UTF-8.
         */
        resp->charset = g_strdup (challenge->charset);
    }
    
    resp->cipher = CIPHER_INVALID;
    if (resp->qop == QOP_AUTH_CONF) {
        /* FIXME: choose a cipher? */
        resp->cipher = CIPHER_INVALID;
    }
    
    /* we don't really care about this... */
    resp->authzid = NULL;
    
    compute_response (resp, passwd, TRUE, resp->resp);
    
    return resp;
}

static GByteArray *
digest_response (struct _DigestResponse *resp)
{
    GByteArray *buffer;
    const char *str;
    char *buf;
    
    buffer = g_byte_array_new ();
    g_byte_array_append (buffer, "username=\"", 10);
    if (resp->charset) {
        /* Encode the username using the requested charset */
        char *charset, *username, *outbuf;
        size_t len, outlen;
        const char *buf;
        iconv_t cd;
        
        charset = getenv ("CHARSET");
        if (!charset)
            charset = "ISO-8859-1";
        
        cd = iconv_open (resp->charset, charset);
        
        len = strlen (resp->username);
        outlen = 2 * len; /* plenty of space */
        
        outbuf = username = g_malloc0 (outlen + 1);
        buf = resp->username;
        if (cd == (iconv_t) -1 || iconv (cd, &buf, &len, &outbuf, &outlen) == -1) {
            g_free (username);
            username = g_strdup (resp->username);
        }
        
        if (cd != (iconv_t) -1)
            iconv_close (cd);
        
        g_byte_array_append (buffer, username, strlen (username));
    } else {
        g_byte_array_append (buffer, resp->username, strlen (resp->username));
    }
    
    g_byte_array_append (buffer, "\",realm=\"", 9);
    g_byte_array_append (buffer, resp->realm, strlen (resp->realm));
    
    g_byte_array_append (buffer, "\",nonce=\"", 9);
    g_byte_array_append (buffer, resp->nonce, strlen (resp->nonce));
    
    g_byte_array_append (buffer, "\",cnonce=\"", 10);
    g_byte_array_append (buffer, resp->cnonce, strlen (resp->cnonce));
    
    g_byte_array_append (buffer, "\",nc=", 5);
    g_byte_array_append (buffer, resp->nc, 8);
    
    g_byte_array_append (buffer, ",qop=\"", 6);
    str = qop_to_string (resp->qop);
    g_byte_array_append (buffer, str, strlen (str));
    
    g_byte_array_append (buffer, "\",digest-uri=\"", 14);
    buf = digest_uri_to_string (resp->uri);
    g_byte_array_append (buffer, buf, strlen (buf));
    g_free (buf);
    
    g_byte_array_append (buffer, "\",response=\"", 12);
    g_byte_array_append (buffer, resp->resp, 32);
    g_byte_array_append (buffer, "\"", 1);
    
    if (resp->maxbuf > 0) {
        g_byte_array_append (buffer, ",maxbuf=", 8);
        buf = g_strdup_printf ("%d", resp->maxbuf);
        g_byte_array_append (buffer, buf, strlen (buf));
        g_free (buf);
    }
    
    if (resp->charset) {
        g_byte_array_append (buffer, ",charset=\"", 10);
        g_byte_array_append (buffer, resp->charset, strlen (resp->charset));
        g_byte_array_append (buffer, "\"", 1);
    }
    
    if (resp->cipher != CIPHER_INVALID) {
        str = cipher_to_string (resp->cipher);
        if (str) {
            g_byte_array_append (buffer, ",cipher=\"", 9);
            g_byte_array_append (buffer, str, strlen (str));
            g_byte_array_append (buffer, "\"", 1);
        }
    }
    
    if (resp->authzid) {
        g_byte_array_append (buffer, ",authzid=\"", 10);
        g_byte_array_append (buffer, resp->authzid, strlen (resp->authzid));
        g_byte_array_append (buffer, "\"", 1);
    }
    
    return buffer;
}

static GByteArray *
digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
{
    CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
    struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
    struct _param *rspauth;
    GByteArray *ret = NULL;
    gboolean abort = FALSE;
    struct hostent *h;
    const char *ptr;
    guchar out[33];
    char *tokens;
    
    /* Need to wait for the server */
    if (!token)
        return NULL;
    
    g_return_val_if_fail (sasl->service->url->passwd != NULL, NULL);
    
    switch (priv->state) {
    case STATE_AUTH:
        if (token->len > 2048) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server challenge too long (>2048 octets)\n"));
            return NULL;
        }
        
        tokens = g_strndup (token->data, token->len);
        priv->challenge = parse_server_challenge (tokens, &abort);
        g_free (tokens);
        if (!priv->challenge || abort) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server challenge invalid\n"));
            return NULL;
        }
        
        if (priv->challenge->qop == QOP_INVALID) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server challenge contained invalid "
                        "\"Quality of Protection\" token\n"));
            return NULL;
        }
        
        h = camel_service_gethost (sasl->service, ex);
        priv->response = generate_response (priv->challenge, h, sasl->service_name,
                            sasl->service->url->user,
                            sasl->service->url->passwd);
        camel_free_host(h);
        ret = digest_response (priv->response);
        
        break;
    case STATE_FINAL:
        if (token->len)
            tokens = g_strndup (token->data, token->len);
        else
            tokens = NULL;
        
        if (!tokens || !*tokens) {
            g_free (tokens);
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server response did not contain authorization data\n"));
            return NULL;
        }
        
        rspauth = g_new0 (struct _param, 1);
        
        ptr = tokens;
        rspauth->name = decode_token (&ptr);
        if (*ptr == '=') {
            ptr++;
            rspauth->value = decode_value (&ptr);
        }
        g_free (tokens);
        
        if (!rspauth->value) {
            g_free (rspauth->name);
            g_free (rspauth);
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server response contained incomplete authorization data\n"));
            return NULL;
        }
        
        compute_response (priv->response, sasl->service->url->passwd, FALSE, out);
        if (memcmp (out, rspauth->value, 32) != 0) {
            g_free (rspauth->name);
            g_free (rspauth->value);
            g_free (rspauth);
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                          _("Server response does not match\n"));
            sasl->authenticated = TRUE;
            
            return NULL;
        }
        
        g_free (rspauth->name);
        g_free (rspauth->value);
        g_free (rspauth);
        
        ret = g_byte_array_new ();
        
        sasl->authenticated = TRUE;
    default:
        break;
    }
    
    priv->state++;
    
    return ret;
}