xref: /illumos-gate/usr/src/lib/libldap5/sources/ldap/common/digest_md5.c (revision 628e3cbed6489fa1db545d8524a06cd6535af456)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Copyright (c) 1998-1999 Innosoft International, Inc.  All Rights Reserved.
31  *
32  * Copyright (c) 1996-1997 Critical Angle Inc. All Rights Reserved.
33  */
34 
35 #include <stdio.h>
36 #include <string.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <ctype.h>
41 #include <md5.h>
42 #include <sys/time.h>
43 
44 #include "lber.h"
45 #include "ldap.h"
46 #include "ldap-int.h"
47 
48 /*
49  * DIGEST-MD5 SASL Mechanism
50  */
51 
52 /* use this instead of "const unsigned char" to eliminate compiler warnings */
53 typedef /* const */ unsigned char CONST_UCHAR;
54 
55 /* size of a digest result */
56 #define	DIGEST_SIZE	 16
57 
58 /* size of a digest hex string */
59 #define	DIGEST_HEX_SIZE (DIGEST_SIZE * 2 + 1)
60 
61 /*
62  * extra bytes which a client response needs in addition to size of
63  * server challenge */
64 #define	DIGEST_CLIENT_EXTRA (DIGEST_HEX_SIZE + 128)
65 
66 /* erase a digest_attrs_t structure */
67 #define	digest_clear(attrs) memset((attrs), 0, sizeof (digest_attrs_t))
68 
69 /*
70  * broken-out digest attributes (with quotes removed)
71  *  probably not NUL terminated.
72  */
73 typedef struct {
74 	const char *realm, *nonce, *cnonce, *qop, *user, *resp, *dom;
75 	const char *max, *stale, *ncount, *uri, *charset;
76 	int rlen, nlen, clen, qlen, ulen, resplen, dlen;
77 	int mlen, slen, nclen, urilen, charsetlen;
78 	char ncbuf[9];
79 } digest_attrs_t;
80 
81 static const char hextab[] = "0123456789abcdef";
82 static CONST_UCHAR colon[] = ":";
83 
84 /*
85  * Make a nonce (NUL terminated)
86  *  buf	-- buffer for result
87  *  maxlen -- max length of result
88  * returns final length or -1 on error
89  */
90 static int
91 digest_nonce(char *buf, int maxlen)
92 {
93 	/*
94 	 * it shouldn't matter too much if two threads step on this counter
95 	 * at the same time, but mutexing it wouldn't hurt
96 	 */
97 	static int counter;
98 	char *dst;
99 	int len;
100 	struct chal_info {
101 		time_t mytime;
102 		unsigned char digest[16];
103 	} cinfo;
104 	MD5_CTX ctx;
105 	long r;
106 	static int set_rand = 0;
107 	unsigned char *p;
108 	int j;
109 	int fd;
110 	int got_random;
111 
112 	/* initialize challenge */
113 	if (maxlen < 2 * sizeof (cinfo))
114 		return (-1);
115 	dst = buf;
116 
117 	/* get a timestamp */
118 	time(&cinfo.mytime);
119 
120 	/* get some randomness */
121 
122 	got_random = 0;
123 	fd = open("/dev/urandom", O_RDONLY);
124 	if (fd != -1) {
125 	    got_random =
126 		(read(fd, &r, sizeof (r)) == sizeof (r));
127 	    close(fd);
128 	}
129 
130 	if (!got_random) {
131 	    if (set_rand == 0) {
132 		struct timeval tv;
133 
134 		r = cinfo.mytime - (getpid() *65536) + (random() & 0xffff);
135 
136 		gettimeofday(&tv, NULL);
137 		r ^= tv.tv_usec;
138 		r ^= gethostid();
139 
140 		srandom(r);
141 		set_rand = 1;
142 	    }
143 
144 	    r = random();
145 	}
146 
147 	MD5Init(&ctx);
148 	MD5Update(&ctx, (unsigned char *) &r, sizeof (r));
149 	MD5Update(&ctx, (unsigned char *) &counter, sizeof (counter));
150 	++counter;
151 	MD5Final(cinfo.digest, &ctx);
152 
153 	/* compute hex for result */
154 	for (j = 0, p = (unsigned char *)&cinfo; j < sizeof (cinfo); ++j) {
155 		dst[j * 2]	= hextab[p[j] >> 4];
156 		dst[j * 2 + 1]	= hextab[p[j] & 0xf];
157 	}
158 
159 	/* take the entire time_t, plus at least 6 bytes of MD5 output */
160 	len = ((sizeof (time_t) + 6) * 2);
161 	dst += len;
162 	maxlen -= len;
163 
164 	*dst = '\0';
165 
166 	return (dst - buf);
167 }
168 
169 /*
170  * if the string is entirely in the 8859-1 subset of UTF-8, then translate
171  * to 8859-1 prior to MD5
172  */
173 static void
174 MD5_UTF8_8859_1(MD5_CTX *ctx, CONST_UCHAR *base, int len)
175 {
176 	CONST_UCHAR *scan, *end;
177 	unsigned char cbuf;
178 
179 	end = base + len;
180 	for (scan = base; scan < end; ++scan) {
181 		if (*scan > 0xC3) break; /* abort if outside 8859-1 */
182 		if (*scan >= 0xC0 && *scan <= 0xC3) {
183 		    if (++scan == end || *scan < 0x80 || *scan > 0xBF) break;
184 		}
185 	}
186 	/* if we found a character outside 8859-1, don't alter string */
187 	if (scan < end) {
188 		MD5Update(ctx, base, len);
189 		return;
190 	}
191 
192 	/* convert to 8859-1 prior to applying hash */
193 	do {
194 		for (scan = base; scan < end && *scan < 0xC0; ++scan)
195 			;
196 		if (scan != base) MD5Update(ctx, base, scan - base);
197 		if (scan + 1 >= end) break;
198 		cbuf = ((scan[0] & 0x3) << 6) | (scan[1] & 0x3f);
199 		MD5Update(ctx, &cbuf, 1);
200 		base = scan + 2;
201 	} while (base < end);
202 }
203 
204 /*
205  * Compute MD5( "<user>:<realm>:<pass>" )
206  *  if use8859_1 is non-zero, then user/realm is 8859-1 charset
207  *  if supplied lengths are 0, strlen() is used
208  *  places result in hash_pass (of size DIGEST_SIZE) and returns it.
209  */
210 static unsigned char *
211 digest_hash_pass(const char *user, int ulen, const char *realm, int rlen,
212 		const char *pass, int passlen, int use8859_1,
213 		unsigned char *hash_pass)
214 {
215 	MD5_CTX ctx;
216 
217 	MD5Init(&ctx);
218 	if (ulen == 0) ulen = strlen(user);
219 	if (use8859_1) {
220 		MD5Update(&ctx, (CONST_UCHAR *) user, ulen);
221 	} else {
222 		MD5_UTF8_8859_1(&ctx, (CONST_UCHAR *) user, ulen);
223 	}
224 	MD5Update(&ctx, colon, 1);
225 	if (rlen == 0) rlen = strlen(realm);
226 	if (use8859_1) {
227 		MD5Update(&ctx, (CONST_UCHAR *) realm, rlen);
228 	} else {
229 		MD5_UTF8_8859_1(&ctx, (CONST_UCHAR *) realm, rlen);
230 	}
231 	MD5Update(&ctx, colon, 1);
232 	if (passlen == 0) passlen = strlen(pass);
233 	MD5Update(&ctx, (CONST_UCHAR *) pass, passlen);
234 	MD5Final(hash_pass, &ctx);
235 
236 	return (hash_pass);
237 }
238 
239 /*
240  * Compute MD5("<hash_pass>:<nonce>:<cnonce>")
241  * places result in hash_a1 and returns hash_a1
242  * note that hash_pass and hash_a1 may be the same
243  */
244 static unsigned char *
245 digest_hash_a1(const digest_attrs_t *attr, CONST_UCHAR *hash_pass,
246 		unsigned char *hash_a1)
247 {
248 	MD5_CTX ctx;
249 
250 	MD5Init(&ctx);
251 	MD5Update(&ctx, hash_pass, DIGEST_SIZE);
252 	MD5Update(&ctx, colon, 1);
253 	MD5Update(&ctx, (CONST_UCHAR *) attr->nonce, attr->nlen);
254 	MD5Update(&ctx, colon, 1);
255 	MD5Update(&ctx, (CONST_UCHAR *) attr->cnonce, attr->clen);
256 	MD5Final(hash_a1, &ctx);
257 
258 	return (hash_a1);
259 }
260 
261 /*
262  * calculate hash response for digest auth.
263  *  outresp must be buffer of at least DIGEST_HEX_SIZE
264  *  outresp and hex_int may be the same
265  *  method may be NULL if mlen is 0
266  */
267 static void
268 digest_calc_resp(const digest_attrs_t *attr,
269 		CONST_UCHAR *hash_a1, const char *method, int mlen,
270 		CONST_UCHAR *hex_int, char *outresp)
271 {
272 	static CONST_UCHAR defncount[] = ":00000001:";
273 	static CONST_UCHAR empty_hex_int[] =
274 			"00000000000000000000000000000000";
275 	MD5_CTX ctx;
276 	unsigned char resp[DIGEST_SIZE];
277 	unsigned char *hex_a1 = (unsigned char *) outresp;
278 	unsigned char *hex_a2 = (unsigned char *) outresp;
279 	unsigned j;
280 
281 	/* compute hash of A2 and put in resp */
282 	MD5Init(&ctx);
283 	if (mlen == 0 && method != NULL) mlen = strlen(method);
284 	if (mlen) MD5Update(&ctx, (CONST_UCHAR *) method, mlen);
285 	MD5Update(&ctx, colon, 1);
286 	if (attr->urilen != 0) {
287 		MD5Update(&ctx, (CONST_UCHAR *) attr->uri, attr->urilen);
288 	}
289 	if (attr->qlen != 4 || strncasecmp(attr->qop, "auth", 4) != 0) {
290 		MD5Update(&ctx, colon, 1);
291 	if (hex_int == NULL) hex_int = empty_hex_int;
292 		MD5Update(&ctx, hex_int, DIGEST_SIZE * 2);
293 	}
294 	MD5Final(resp, &ctx);
295 
296 	/* compute hex_a1 from hash_a1 */
297 	for (j = 0; j < DIGEST_SIZE; ++j) {
298 		hex_a1[j * 2]	 = hextab[hash_a1[j] >> 4];
299 		hex_a1[j * 2 + 1] = hextab[hash_a1[j] & 0xf];
300 	}
301 
302 	/* compute response */
303 	MD5Init(&ctx);
304 	MD5Update(&ctx, hex_a1, DIGEST_SIZE * 2);
305 	MD5Update(&ctx, colon, 1);
306 	MD5Update(&ctx, (CONST_UCHAR *) attr->nonce, attr->nlen);
307 	if (attr->ncount != NULL) {
308 		MD5Update(&ctx, colon, 1);
309 		MD5Update(&ctx, (CONST_UCHAR *) attr->ncount, attr->nclen);
310 		MD5Update(&ctx, colon, 1);
311 	} else {
312 		MD5Update(&ctx, defncount, sizeof (defncount) - 1);
313 	}
314 	MD5Update(&ctx, (CONST_UCHAR *) attr->cnonce, attr->clen);
315 	MD5Update(&ctx, colon, 1);
316 	MD5Update(&ctx, (CONST_UCHAR *) attr->qop, attr->qlen);
317 	MD5Update(&ctx, colon, 1);
318 
319 	/* compute hex_a2 from hash_a2 */
320 	for (j = 0; j < DIGEST_SIZE; ++j) {
321 		hex_a2[j * 2]	 = hextab[resp[j] >> 4];
322 		hex_a2[j * 2 + 1] = hextab[resp[j] & 0xf];
323 	}
324 	MD5Update(&ctx, hex_a2, DIGEST_SIZE * 2);
325 	MD5Final(resp, &ctx);
326 
327 	/* generate hex output */
328 	for (j = 0; j < DIGEST_SIZE; ++j) {
329 		outresp[j * 2]	 = hextab[resp[j] >> 4];
330 		outresp[j * 2 + 1] = hextab[resp[j] & 0xf];
331 	}
332 	outresp[DIGEST_SIZE * 2] = '\0';
333 	memset(resp, 0, sizeof (resp));
334 }
335 
336 /*
337  * generate the client response from attributes
338  *  either one of hash_pass and hash_a1 may be NULL
339  *  hash_a1 is used on re-authentication and takes precedence over hash_pass
340  */
341 static int
342 digest_client_resp(const char *method, int mlen,
343 		CONST_UCHAR *hash_pass, CONST_UCHAR *hash_a1,
344 		digest_attrs_t *attr, /* in/out attributes */
345 		char *outbuf, int maxout, int *plen)
346 {
347 #define	prefixsize (sizeof (prefix) - 4 * 4 - 1)
348 #define	suffixsize (sizeof (rstr) + sizeof (qstr) - 1 + DIGEST_SIZE * 2)
349 	static const char prefix[] =
350 	"username=\"%.*s\",realm=\"%.*s\",nonce=\"%.*s\",nc=%.*s,cnonce=\"";
351 	static const char rstr[] = "\",response=";
352 	static const char qstr[] = ",qop=auth";
353 	static const char chstr[] = "charset=";
354 	char *scan;
355 	int len;
356 	char hexbuf[DIGEST_HEX_SIZE];
357 	unsigned char hashbuf[DIGEST_SIZE];
358 
359 	/* make sure we have mandatory attributes */
360 	if (attr->nonce == NULL || attr->nlen == 0 ||
361 	    attr->realm == NULL || attr->rlen == 0 ||
362 	    attr->qop == NULL || attr->qlen == 0 ||
363 	    (attr->nclen != 0 && attr->nclen != 8)) {
364 		return (-5);
365 	}
366 	if (mlen != 0 && method == NULL)
367 		return (-7);
368 
369 	/* initialize ncount */
370 	if (attr->ncount == NULL) {
371 		strcpy(attr->ncbuf, "00000001");
372 		attr->ncount = attr->ncbuf;
373 		attr->nclen = 8;
374 	} else if (attr->ncount == attr->ncbuf) {
375 		/* increment ncount */
376 		scan = attr->ncbuf + 7;
377 		while (scan >= attr->ncbuf) {
378 			if (*scan == '9') {
379 				*scan = 'a';
380 				break;
381 			} else if (*scan != 'f') {
382 				++*scan;
383 				break;
384 			}
385 			*scan = '0';
386 			--scan;
387 		}
388 	}
389 
390 	/* sanity check length */
391 	len = prefixsize + attr->ulen + attr->rlen + attr->nlen + attr->nclen;
392 	if (attr->charsetlen > 0) {
393 		/* includes 1 for a comma */
394 		len += sizeof (chstr) + attr->charsetlen;
395 	}
396 	if (len + suffixsize >= maxout)
397 		return (-3);
398 
399 	scan = outbuf;
400 
401 	/* charset */
402 	if (attr->charsetlen > 0 && attr->charset != NULL) {
403 		memcpy(scan, chstr, sizeof (chstr) - 1);
404 		scan += sizeof (chstr) - 1;
405 		memcpy(scan, attr->charset, attr->charsetlen);
406 		scan += attr->charsetlen;
407 		*scan++ = ',';
408 	}
409 
410 	/* generate string up to the client nonce */
411 	sprintf(scan, prefix, attr->ulen, attr->user,
412 		attr->rlen, attr->realm, attr->nlen, attr->nonce,
413 		attr->nclen, attr->ncount);
414 	scan = outbuf + len;
415 
416 	/* generate client nonce */
417 	len = digest_nonce(scan, maxout - (scan - outbuf));
418 	if (len < 0)
419 		return (len);
420 	attr->cnonce = scan;
421 	attr->clen = len;
422 	scan += len;
423 	if (scan - outbuf + suffixsize > maxout)
424 		return (-3);
425 
426 	/* compute response */
427 	if (hash_a1 == NULL) {
428 		if (hash_pass == NULL)
429 			return (-7);
430 		hash_a1 = digest_hash_a1(attr, hash_pass, hashbuf);
431 	}
432 	digest_calc_resp(attr, hash_a1, method, mlen, NULL, hexbuf);
433 
434 	/* finish it */
435 	memcpy(scan, rstr, sizeof (rstr) - 1);
436 	scan += sizeof (rstr) - 1;
437 	memcpy(scan, hexbuf, DIGEST_SIZE * 2);
438 	attr->resp = scan;
439 	attr->resplen = DIGEST_SIZE * 2;
440 	scan += DIGEST_SIZE * 2;
441 	memcpy(scan, qstr, sizeof (qstr));
442 
443 	/* set final length */
444 	if (plen != NULL) *plen = scan - outbuf + sizeof (qstr) - 1;
445 
446 	return (0);
447 }
448 
449 #define	lstreqcase(conststr, val, len) ((len) == sizeof (conststr) - 1 && \
450 		strncasecmp((conststr), (val), sizeof (conststr) - 1) == 0)
451 
452 /* parse a digest auth string */
453 static int
454 digest_parse(const char *str, int len, digest_attrs_t *attr_out)
455 {
456 	static const char rstr[] = "realm";
457 	static const char nstr[] = "nonce";
458 	static const char cstr[] = "cnonce";
459 	static const char qstr[] = "qop";
460 	static const char ustr[] = "username";
461 	static const char respstr[] = "response";
462 	static const char dstr[] = "domain";
463 	static const char mstr[] = "maxbuf";
464 	static const char sstr[] = "stale";
465 	static const char ncstr[] = "nc";
466 	static const char uristr[] = "digest-uri";
467 	static const char charsetstr[] = "charset";
468 	const char *scan, *attr, *val, *end;
469 	int alen, vlen;
470 
471 	if (len == 0) len = strlen(str);
472 	scan = str;
473 	end = str + len;
474 	for (;;) {
475 		/* skip over commas */
476 		while (scan < end && (*scan == ',' || isspace(*scan))) ++scan;
477 		/* parse attribute */
478 		attr = scan;
479 		while (scan < end && *scan != '=') ++scan;
480 		alen = scan - attr;
481 		if (!alen || scan == end || scan + 1 == end) {
482 			return (-5);
483 		}
484 
485 		/* parse value */
486 		if (scan[1] == '"') {
487 			scan += 2;
488 			val = scan;
489 			while (scan < end && *scan != '"') {
490 				/* skip over "\" quoting, but don't remove it */
491 				if (*scan == '\\') {
492 					if (scan + 1 == end)
493 						return (-5);
494 					scan += 2;
495 				} else {
496 					++scan;
497 				}
498 			}
499 			vlen = scan - val;
500 			if (*scan != '"')
501 				return (-5);
502 			++scan;
503 		} else {
504 			++scan;
505 			val = scan;
506 			while (scan < end && *scan != ',') ++scan;
507 			vlen = scan - val;
508 		}
509 		if (!vlen)
510 			return (-5);
511 
512 		/* lookup the attribute */
513 		switch (*attr) {
514 		    case 'c':
515 		    case 'C':
516 			if (lstreqcase(cstr, attr, alen)) {
517 				attr_out->cnonce = val;
518 				attr_out->clen = vlen;
519 			}
520 			if (lstreqcase(charsetstr, attr, alen)) {
521 				attr_out->charset = val;
522 				attr_out->charsetlen = vlen;
523 			}
524 			break;
525 		    case 'd':
526 		    case 'D':
527 			if (lstreqcase(dstr, attr, alen)) {
528 				attr_out->dom = val;
529 				attr_out->dlen = vlen;
530 			}
531 			if (lstreqcase(uristr, attr, alen)) {
532 				attr_out->uri = val;
533 				attr_out->urilen = vlen;
534 			}
535 			break;
536 		    case 'm':
537 		    case 'M':
538 			if (lstreqcase(mstr, attr, alen)) {
539 				attr_out->max = val;
540 				attr_out->mlen = vlen;
541 			}
542 			break;
543 		    case 'n':
544 		    case 'N':
545 			if (lstreqcase(nstr, attr, alen)) {
546 				attr_out->nonce = val;
547 				attr_out->nlen = vlen;
548 			}
549 			if (lstreqcase(ncstr, attr, alen)) {
550 				attr_out->ncount = val;
551 				attr_out->nclen = vlen;
552 			}
553 			break;
554 		    case 'q':
555 		    case 'Q':
556 			if (lstreqcase(qstr, attr, alen)) {
557 				attr_out->qop = val;
558 				attr_out->qlen = vlen;
559 			}
560 			break;
561 		    case 'r':
562 		    case 'R':
563 			if (lstreqcase(rstr, attr, alen)) {
564 				attr_out->realm = val;
565 				attr_out->rlen = vlen;
566 			}
567 			if (lstreqcase(respstr, attr, alen)) {
568 				attr_out->resp = val;
569 				attr_out->resplen = vlen;
570 			}
571 			break;
572 		    case 's':
573 		    case 'S':
574 			if (lstreqcase(sstr, attr, alen)) {
575 				attr_out->stale = val;
576 				attr_out->slen = vlen;
577 			}
578 			break;
579 		    case 'u':
580 		    case 'U':
581 			if (lstreqcase(ustr, attr, alen)) {
582 				attr_out->user = val;
583 				attr_out->ulen = vlen;
584 			}
585 			break;
586 		}
587 
588 		/* we should be at the end of the string or a comma */
589 		if (scan == end) break;
590 		if (*scan != ',')
591 			return (-5);
592 	}
593 
594 	return (0);
595 }
596 
597 static int ldap_digest_md5_encode(
598 	const char *challenge,
599 	const char *username,
600 	const char *passwd,
601 	char **digest
602 )
603 {
604 	unsigned char hash_pass[DIGEST_SIZE];
605 	digest_attrs_t attrs;
606 	char *outbuf;
607 	int outlen;
608 	int ret;
609 
610 	/* validate args */
611 	if (challenge == NULL || username == NULL || passwd == NULL) {
612 		return (LDAP_PARAM_ERROR);
613 	}
614 
615 	/* parse the challenge */
616 	digest_clear(&attrs);
617 	ret = digest_parse(challenge, 0, &attrs);
618 	if (ret != 0)
619 		return (LDAP_DECODING_ERROR);
620 
621 	/* server MUST specify support for charset=utf-8 */
622 	if (attrs.charsetlen != 5 ||
623 	    strncasecmp(attrs.charset, "utf-8", 5) != 0) {
624 		LDAPDebug(LDAP_DEBUG_TRACE,
625 			"server did not specify charset=utf-8\n",
626 			0, 0, 0);
627 		return (LDAP_NOT_SUPPORTED);
628 	}
629 
630 	/* set up digest attributes */
631 	attrs.user = username;
632 	attrs.ulen = strlen(attrs.user);
633 
634 	/* allocate the output buffer */
635 	outlen = strlen(challenge) + DIGEST_CLIENT_EXTRA + 1;
636 	outbuf = (char *)malloc(outlen);
637 	if (outbuf == NULL)
638 		return (LDAP_NO_MEMORY);
639 
640 	/* hash the password */
641 	digest_hash_pass(username, 0, attrs.realm, attrs.rlen,
642 				passwd, 0, 0, hash_pass),
643 
644 	/* create the response */
645 	ret = digest_client_resp("AUTHENTICATE", 12, hash_pass, NULL,
646 			&attrs, outbuf, outlen, &outlen);
647 	memset(hash_pass, 0, DIGEST_SIZE);
648 	if (ret != 0) {
649 		free(outbuf);
650 		return (LDAP_DECODING_ERROR);
651 	}
652 
653 	/* null terminate the response */
654 	*(outbuf+outlen) = '\0';
655 
656 	*digest = outbuf;
657 	return (LDAP_SUCCESS);
658 }
659 
660 int ldap_x_sasl_digest_md5_bind_s(
661 	LDAP *ld,
662 	char *user_name,
663 	struct berval *cred,
664 	LDAPControl **serverctrls,
665 	LDAPControl **clientctrls)
666 {
667 	struct berval	*challenge = NULL;
668 	int		errnum;
669 	char		*digest = NULL;
670 	struct berval	resp;
671 
672 	LDAPDebug(LDAP_DEBUG_TRACE, "ldap_x_sasl_digest_md5_bind_s\n", 0, 0, 0);
673 
674 	/* Add debug */
675 	if (ld == NULL || user_name == NULL || cred == NULL ||
676 	    cred->bv_val == NULL)
677 		return (LDAP_PARAM_ERROR);
678 
679 	if (ld->ld_version < LDAP_VERSION3)
680 		return (LDAP_PARAM_ERROR);
681 
682 	errnum = ldap_sasl_bind_s(ld, NULL, LDAP_SASL_DIGEST_MD5,
683 		NULL, serverctrls, clientctrls, &challenge);
684 
685 	if (errnum == LDAP_SASL_BIND_IN_PROGRESS) {
686 		if (challenge != NULL) {
687 			LDAPDebug(LDAP_DEBUG_TRACE,
688 				"SASL challenge: %s\n",
689 				challenge->bv_val, 0, 0);
690 			errnum = ldap_digest_md5_encode(challenge->bv_val,
691 				user_name, cred->bv_val, &digest);
692 			ber_bvfree(challenge);
693 			challenge = NULL;
694 			if (errnum == LDAP_SUCCESS) {
695 				resp.bv_val = digest;
696 				resp.bv_len = strlen(digest);
697 				LDAPDebug(LDAP_DEBUG_TRACE,
698 					"SASL reply: %s\n",
699 					digest, 0, 0);
700 				errnum = ldap_sasl_bind_s(ld, NULL,
701 					LDAP_SASL_DIGEST_MD5, &resp,
702 					serverctrls, clientctrls, &challenge);
703 				free(digest);
704 			}
705 			if (challenge != NULL)
706 				ber_bvfree(challenge);
707 		} else {
708 			errnum = LDAP_NO_MEMORY; /* TO DO: What val? */
709 		}
710 	}
711 
712 	LDAP_MUTEX_LOCK(ld, LDAP_ERR_LOCK);
713 	ld->ld_errno = errnum;
714 	LDAP_MUTEX_UNLOCK(ld, LDAP_ERR_LOCK);
715 	return (errnum);
716 }
717 
718 static int
719 sasl_digest_md5_bind_1(
720 	LDAP *ld,
721 	char *user_name,
722 	LDAPControl **serverctrls,
723 	LDAPControl **clientctrls,
724 	int *msgidp)
725 {
726 	if (ld == NULL || user_name == NULL || msgidp == NULL)
727 		return (LDAP_PARAM_ERROR);
728 
729 	if (ld->ld_version < LDAP_VERSION3)
730 		return (LDAP_PARAM_ERROR);
731 
732 	return (ldap_sasl_bind(ld, NULL, LDAP_SASL_DIGEST_MD5,
733 		NULL, serverctrls, clientctrls, msgidp));
734 }
735 
736 static int
737 sasl_digest_md5_bind_2(
738 	LDAP *ld,
739 	char *user_name,
740 	struct berval *cred,
741 	LDAPControl **serverctrls,
742 	LDAPControl **clientctrls,
743 	LDAPMessage *result,
744 	int *msgidp)
745 {
746 	struct berval	*challenge = NULL;
747 	struct berval	resp;
748 	int		errnum;
749 	char		*digest = NULL;
750 	int		err;
751 
752 	if (ld == NULL || user_name == NULL || cred == NULL ||
753 	    cred->bv_val == NULL || result == NULL || msgidp == NULL)
754 		return (LDAP_PARAM_ERROR);
755 
756 	if (ld->ld_version < LDAP_VERSION3)
757 		return (LDAP_PARAM_ERROR);
758 
759 	err = ldap_result2error(ld, result, 0);
760 	if (err != LDAP_SASL_BIND_IN_PROGRESS)
761 		return (err);
762 
763 	if ((err = ldap_parse_sasl_bind_result(ld, result, &challenge, 0))
764 			!= LDAP_SUCCESS)
765 		return (err);
766 	if (challenge == NULL)
767 		return (LDAP_NO_MEMORY);
768 
769 	err = ldap_digest_md5_encode(challenge->bv_val,
770 			user_name, cred->bv_val, &digest);
771 	ber_bvfree(challenge);
772 
773 	if (err == LDAP_SUCCESS) {
774 		resp.bv_val = digest;
775 		resp.bv_len = strlen(digest);
776 		LDAPDebug(LDAP_DEBUG_TRACE, "SASL reply: %s\n",
777 			digest, 0, 0);
778 		err = ldap_sasl_bind(ld, NULL, LDAP_SASL_DIGEST_MD5,
779 			&resp, serverctrls, clientctrls, msgidp);
780 		free(digest);
781 	}
782 	return (err);
783 }
784 
785 int ldap_x_sasl_digest_md5_bind(
786 	LDAP *ld,
787 	char *user_name,
788 	struct berval *cred,
789 	LDAPControl **serverctrls,
790 	LDAPControl **clientctrls,
791 	struct timeval *timeout,
792 	LDAPMessage **result)
793 {
794 	LDAPMessage	*res = NULL;
795 	int		msgid;
796 	int		rc;
797 
798 	if (ld == NULL || user_name == NULL || cred == NULL ||
799 		result == NULL)
800 		return (LDAP_PARAM_ERROR);
801 
802 	if (ld->ld_version < LDAP_VERSION3)
803 		return (LDAP_PARAM_ERROR);
804 
805 	*result = NULL;
806 
807 	rc = sasl_digest_md5_bind_1(ld, user_name,
808 		serverctrls, clientctrls, &msgid);
809 	if (rc != LDAP_SUCCESS)
810 		return (rc);
811 
812 	rc = ldap_result(ld, msgid, 1, timeout, &res);
813 	if (rc == -1) {
814 		if (res != NULL)
815 			ldap_msgfree(res);
816 		return (ldap_get_lderrno(ld, NULL, NULL));
817 	}
818 	rc = ldap_result2error(ld, res, 0);
819 	if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
820 		*result = res;
821 		return (rc);
822 	}
823 
824 	rc = sasl_digest_md5_bind_2(ld, user_name, cred,
825 		serverctrls, clientctrls, res, &msgid);
826 	ldap_msgfree(res);
827 	res = NULL;
828 
829 	if (rc != LDAP_SUCCESS)
830 		return (rc);
831 
832 	rc = ldap_result(ld, msgid, 1, timeout, &res);
833 	if (rc == -1) {
834 		if (res != NULL)
835 			ldap_msgfree(res);
836 		return (ldap_get_lderrno(ld, NULL, NULL));
837 	}
838 	*result = res;
839 	rc = ldap_result2error(ld, res, 0);
840 	return (rc);
841 }
842