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