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