root/usr/src/lib/libldap5/sources/ldap/common/digest_md5.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 1998-1999 Innosoft International, Inc.  All Rights Reserved.
 *
 * Copyright (c) 1996-1997 Critical Angle Inc. All Rights Reserved.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <md5.h>
#include <sys/time.h>

#include "lber.h"
#include "ldap.h"
#include "ldap-int.h"

/*
 * DIGEST-MD5 SASL Mechanism
 */

/* use this instead of "const unsigned char" to eliminate compiler warnings */
typedef /* const */ unsigned char CONST_UCHAR;

/* size of a digest result */
#define DIGEST_SIZE      16

/* size of a digest hex string */
#define DIGEST_HEX_SIZE (DIGEST_SIZE * 2 + 1)

/*
 * extra bytes which a client response needs in addition to size of
 * server challenge */
#define DIGEST_CLIENT_EXTRA (DIGEST_HEX_SIZE + 128)

/* erase a digest_attrs_t structure */
#define digest_clear(attrs) memset((attrs), 0, sizeof (digest_attrs_t))

/*
 * broken-out digest attributes (with quotes removed)
 *  probably not NUL terminated.
 */
typedef struct {
        const char *realm, *nonce, *cnonce, *qop, *user, *resp, *dom;
        const char *max, *stale, *ncount, *uri, *charset;
        int rlen, nlen, clen, qlen, ulen, resplen, dlen;
        int mlen, slen, nclen, urilen, charsetlen;
        char ncbuf[9];
} digest_attrs_t;

static const char hextab[] = "0123456789abcdef";
static CONST_UCHAR colon[] = ":";

/*
 * Make a nonce (NUL terminated)
 *  buf -- buffer for result
 *  maxlen -- max length of result
 * returns final length or -1 on error
 */
static int
digest_nonce(char *buf, int maxlen)
{
        /*
         * it shouldn't matter too much if two threads step on this counter
         * at the same time, but mutexing it wouldn't hurt
         */
        static int counter;
        char *dst;
        int len;
        struct chal_info {
                time_t mytime;
                unsigned char digest[16];
        } cinfo;
        MD5_CTX ctx;
        long r;
        static int set_rand = 0;
        unsigned char *p;
        int j;
        int fd;
        int got_random;

        /* initialize challenge */
        if (maxlen < 2 * sizeof (cinfo))
                return (-1);
        dst = buf;

        /* get a timestamp */
        time(&cinfo.mytime);

        /* get some randomness */

        got_random = 0;
        fd = open("/dev/urandom", O_RDONLY);
        if (fd != -1) {
            got_random =
                (read(fd, &r, sizeof (r)) == sizeof (r));
            close(fd);
        }

        if (!got_random) {
            if (set_rand == 0) {
                struct timeval tv;

                r = cinfo.mytime - (getpid() *65536) + (random() & 0xffff);

                gettimeofday(&tv, NULL);
                r ^= tv.tv_usec;
                r ^= gethostid();

                srandom(r);
                set_rand = 1;
            }

            r = random();
        }

        MD5Init(&ctx);
        MD5Update(&ctx, (unsigned char *) &r, sizeof (r));
        MD5Update(&ctx, (unsigned char *) &counter, sizeof (counter));
        ++counter;
        MD5Final(cinfo.digest, &ctx);

        /* compute hex for result */
        for (j = 0, p = (unsigned char *)&cinfo; j < sizeof (cinfo); ++j) {
                dst[j * 2]      = hextab[p[j] >> 4];
                dst[j * 2 + 1]  = hextab[p[j] & 0xf];
        }

        /* take the entire time_t, plus at least 6 bytes of MD5 output */
        len = ((sizeof (time_t) + 6) * 2);
        dst += len;
        maxlen -= len;

        *dst = '\0';

        return (dst - buf);
}

/*
 * if the string is entirely in the 8859-1 subset of UTF-8, then translate
 * to 8859-1 prior to MD5
 */
static void
MD5_UTF8_8859_1(MD5_CTX *ctx, CONST_UCHAR *base, int len)
{
        CONST_UCHAR *scan, *end;
        unsigned char cbuf;

        end = base + len;
        for (scan = base; scan < end; ++scan) {
                if (*scan > 0xC3) break; /* abort if outside 8859-1 */
                if (*scan >= 0xC0 && *scan <= 0xC3) {
                    if (++scan == end || *scan < 0x80 || *scan > 0xBF) break;
                }
        }
        /* if we found a character outside 8859-1, don't alter string */
        if (scan < end) {
                MD5Update(ctx, base, len);
                return;
        }

        /* convert to 8859-1 prior to applying hash */
        do {
                for (scan = base; scan < end && *scan < 0xC0; ++scan)
                        ;
                if (scan != base) MD5Update(ctx, base, scan - base);
                if (scan + 1 >= end) break;
                cbuf = ((scan[0] & 0x3) << 6) | (scan[1] & 0x3f);
                MD5Update(ctx, &cbuf, 1);
                base = scan + 2;
        } while (base < end);
}

/*
 * Compute MD5( "<user>:<realm>:<pass>" )
 *  if use8859_1 is non-zero, then user/realm is 8859-1 charset
 *  if supplied lengths are 0, strlen() is used
 *  places result in hash_pass (of size DIGEST_SIZE) and returns it.
 */
static unsigned char *
digest_hash_pass(const char *user, int ulen, const char *realm, int rlen,
                const char *pass, int passlen, int use8859_1,
                unsigned char *hash_pass)
{
        MD5_CTX ctx;

        MD5Init(&ctx);
        if (ulen == 0) ulen = strlen(user);
        if (use8859_1) {
                MD5Update(&ctx, (CONST_UCHAR *) user, ulen);
        } else {
                MD5_UTF8_8859_1(&ctx, (CONST_UCHAR *) user, ulen);
        }
        MD5Update(&ctx, colon, 1);
        if (rlen == 0) rlen = strlen(realm);
        if (use8859_1) {
                MD5Update(&ctx, (CONST_UCHAR *) realm, rlen);
        } else {
                MD5_UTF8_8859_1(&ctx, (CONST_UCHAR *) realm, rlen);
        }
        MD5Update(&ctx, colon, 1);
        if (passlen == 0) passlen = strlen(pass);
        MD5Update(&ctx, (CONST_UCHAR *) pass, passlen);
        MD5Final(hash_pass, &ctx);

        return (hash_pass);
}

/*
 * Compute MD5("<hash_pass>:<nonce>:<cnonce>")
 * places result in hash_a1 and returns hash_a1
 * note that hash_pass and hash_a1 may be the same
 */
static unsigned char *
digest_hash_a1(const digest_attrs_t *attr, CONST_UCHAR *hash_pass,
                unsigned char *hash_a1)
{
        MD5_CTX ctx;

        MD5Init(&ctx);
        MD5Update(&ctx, hash_pass, DIGEST_SIZE);
        MD5Update(&ctx, colon, 1);
        MD5Update(&ctx, (CONST_UCHAR *) attr->nonce, attr->nlen);
        MD5Update(&ctx, colon, 1);
        MD5Update(&ctx, (CONST_UCHAR *) attr->cnonce, attr->clen);
        MD5Final(hash_a1, &ctx);

        return (hash_a1);
}

/*
 * calculate hash response for digest auth.
 *  outresp must be buffer of at least DIGEST_HEX_SIZE
 *  outresp and hex_int may be the same
 *  method may be NULL if mlen is 0
 */
static void
digest_calc_resp(const digest_attrs_t *attr,
                CONST_UCHAR *hash_a1, const char *method, int mlen,
                CONST_UCHAR *hex_int, char *outresp)
{
        static CONST_UCHAR defncount[] = ":00000001:";
        static CONST_UCHAR empty_hex_int[] =
                        "00000000000000000000000000000000";
        MD5_CTX ctx;
        unsigned char resp[DIGEST_SIZE];
        unsigned char *hex_a1 = (unsigned char *) outresp;
        unsigned char *hex_a2 = (unsigned char *) outresp;
        unsigned j;

        /* compute hash of A2 and put in resp */
        MD5Init(&ctx);
        if (mlen == 0 && method != NULL) mlen = strlen(method);
        if (mlen) MD5Update(&ctx, (CONST_UCHAR *) method, mlen);
        MD5Update(&ctx, colon, 1);
        if (attr->urilen != 0) {
                MD5Update(&ctx, (CONST_UCHAR *) attr->uri, attr->urilen);
        }
        if (attr->qlen != 4 || strncasecmp(attr->qop, "auth", 4) != 0) {
                MD5Update(&ctx, colon, 1);
        if (hex_int == NULL) hex_int = empty_hex_int;
                MD5Update(&ctx, hex_int, DIGEST_SIZE * 2);
        }
        MD5Final(resp, &ctx);

        /* compute hex_a1 from hash_a1 */
        for (j = 0; j < DIGEST_SIZE; ++j) {
                hex_a1[j * 2]    = hextab[hash_a1[j] >> 4];
                hex_a1[j * 2 + 1] = hextab[hash_a1[j] & 0xf];
        }

        /* compute response */
        MD5Init(&ctx);
        MD5Update(&ctx, hex_a1, DIGEST_SIZE * 2);
        MD5Update(&ctx, colon, 1);
        MD5Update(&ctx, (CONST_UCHAR *) attr->nonce, attr->nlen);
        if (attr->ncount != NULL) {
                MD5Update(&ctx, colon, 1);
                MD5Update(&ctx, (CONST_UCHAR *) attr->ncount, attr->nclen);
                MD5Update(&ctx, colon, 1);
        } else {
                MD5Update(&ctx, defncount, sizeof (defncount) - 1);
        }
        MD5Update(&ctx, (CONST_UCHAR *) attr->cnonce, attr->clen);
        MD5Update(&ctx, colon, 1);
        MD5Update(&ctx, (CONST_UCHAR *) attr->qop, attr->qlen);
        MD5Update(&ctx, colon, 1);

        /* compute hex_a2 from hash_a2 */
        for (j = 0; j < DIGEST_SIZE; ++j) {
                hex_a2[j * 2]    = hextab[resp[j] >> 4];
                hex_a2[j * 2 + 1] = hextab[resp[j] & 0xf];
        }
        MD5Update(&ctx, hex_a2, DIGEST_SIZE * 2);
        MD5Final(resp, &ctx);

        /* generate hex output */
        for (j = 0; j < DIGEST_SIZE; ++j) {
                outresp[j * 2]   = hextab[resp[j] >> 4];
                outresp[j * 2 + 1] = hextab[resp[j] & 0xf];
        }
        outresp[DIGEST_SIZE * 2] = '\0';
        memset(resp, 0, sizeof (resp));
}

/*
 * generate the client response from attributes
 *  either one of hash_pass and hash_a1 may be NULL
 *  hash_a1 is used on re-authentication and takes precedence over hash_pass
 */
static int
digest_client_resp(const char *method, int mlen,
                CONST_UCHAR *hash_pass, CONST_UCHAR *hash_a1,
                digest_attrs_t *attr, /* in/out attributes */
                char *outbuf, int maxout, int *plen)
{
#define prefixsize (sizeof (prefix) - 4 * 4 - 1)
#define suffixsize (sizeof (rstr) + sizeof (qstr) - 1 + DIGEST_SIZE * 2)
        static const char prefix[] =
        "username=\"%.*s\",realm=\"%.*s\",nonce=\"%.*s\",nc=%.*s,cnonce=\"";
        static const char rstr[] = "\",response=";
        static const char qstr[] = ",qop=auth";
        static const char chstr[] = "charset=";
        char *scan;
        int len;
        char hexbuf[DIGEST_HEX_SIZE];
        unsigned char hashbuf[DIGEST_SIZE];

        /* make sure we have mandatory attributes */
        if (attr->nonce == NULL || attr->nlen == 0 ||
            attr->realm == NULL || attr->rlen == 0 ||
            attr->qop == NULL || attr->qlen == 0 ||
            (attr->nclen != 0 && attr->nclen != 8)) {
                return (-5);
        }
        if (mlen != 0 && method == NULL)
                return (-7);

        /* initialize ncount */
        if (attr->ncount == NULL) {
                strcpy(attr->ncbuf, "00000001");
                attr->ncount = attr->ncbuf;
                attr->nclen = 8;
        } else if (attr->ncount == attr->ncbuf) {
                /* increment ncount */
                scan = attr->ncbuf + 7;
                while (scan >= attr->ncbuf) {
                        if (*scan == '9') {
                                *scan = 'a';
                                break;
                        } else if (*scan != 'f') {
                                ++*scan;
                                break;
                        }
                        *scan = '0';
                        --scan;
                }
        }

        /* sanity check length */
        len = prefixsize + attr->ulen + attr->rlen + attr->nlen + attr->nclen;
        if (attr->charsetlen > 0) {
                /* includes 1 for a comma */
                len += sizeof (chstr) + attr->charsetlen;
        }
        if (len + suffixsize >= maxout)
                return (-3);

        scan = outbuf;

        /* charset */
        if (attr->charsetlen > 0 && attr->charset != NULL) {
                memcpy(scan, chstr, sizeof (chstr) - 1);
                scan += sizeof (chstr) - 1;
                memcpy(scan, attr->charset, attr->charsetlen);
                scan += attr->charsetlen;
                *scan++ = ',';
        }

        /* generate string up to the client nonce */
        sprintf(scan, prefix, attr->ulen, attr->user,
                attr->rlen, attr->realm, attr->nlen, attr->nonce,
                attr->nclen, attr->ncount);
        scan = outbuf + len;

        /* generate client nonce */
        len = digest_nonce(scan, maxout - (scan - outbuf));
        if (len < 0)
                return (len);
        attr->cnonce = scan;
        attr->clen = len;
        scan += len;
        if (scan - outbuf + suffixsize > maxout)
                return (-3);

        /* compute response */
        if (hash_a1 == NULL) {
                if (hash_pass == NULL)
                        return (-7);
                hash_a1 = digest_hash_a1(attr, hash_pass, hashbuf);
        }
        digest_calc_resp(attr, hash_a1, method, mlen, NULL, hexbuf);

        /* finish it */
        memcpy(scan, rstr, sizeof (rstr) - 1);
        scan += sizeof (rstr) - 1;
        memcpy(scan, hexbuf, DIGEST_SIZE * 2);
        attr->resp = scan;
        attr->resplen = DIGEST_SIZE * 2;
        scan += DIGEST_SIZE * 2;
        memcpy(scan, qstr, sizeof (qstr));

        /* set final length */
        if (plen != NULL) *plen = scan - outbuf + sizeof (qstr) - 1;

        return (0);
}

#define lstreqcase(conststr, val, len) ((len) == sizeof (conststr) - 1 && \
                strncasecmp((conststr), (val), sizeof (conststr) - 1) == 0)

/* parse a digest auth string */
static int
digest_parse(const char *str, int len, digest_attrs_t *attr_out)
{
        static const char rstr[] = "realm";
        static const char nstr[] = "nonce";
        static const char cstr[] = "cnonce";
        static const char qstr[] = "qop";
        static const char ustr[] = "username";
        static const char respstr[] = "response";
        static const char dstr[] = "domain";
        static const char mstr[] = "maxbuf";
        static const char sstr[] = "stale";
        static const char ncstr[] = "nc";
        static const char uristr[] = "digest-uri";
        static const char charsetstr[] = "charset";
        const char *scan, *attr, *val, *end;
        int alen, vlen;

        if (len == 0) len = strlen(str);
        scan = str;
        end = str + len;
        for (;;) {
                /* skip over commas */
                while (scan < end && (*scan == ',' || isspace(*scan))) ++scan;
                /* parse attribute */
                attr = scan;
                while (scan < end && *scan != '=') ++scan;
                alen = scan - attr;
                if (!alen || scan == end || scan + 1 == end) {
                        return (-5);
                }

                /* parse value */
                if (scan[1] == '"') {
                        scan += 2;
                        val = scan;
                        while (scan < end && *scan != '"') {
                                /* skip over "\" quoting, but don't remove it */
                                if (*scan == '\\') {
                                        if (scan + 1 == end)
                                                return (-5);
                                        scan += 2;
                                } else {
                                        ++scan;
                                }
                        }
                        vlen = scan - val;
                        if (*scan != '"')
                                return (-5);
                        ++scan;
                } else {
                        ++scan;
                        val = scan;
                        while (scan < end && *scan != ',') ++scan;
                        vlen = scan - val;
                }
                if (!vlen)
                        return (-5);

                /* lookup the attribute */
                switch (*attr) {
                    case 'c':
                    case 'C':
                        if (lstreqcase(cstr, attr, alen)) {
                                attr_out->cnonce = val;
                                attr_out->clen = vlen;
                        }
                        if (lstreqcase(charsetstr, attr, alen)) {
                                attr_out->charset = val;
                                attr_out->charsetlen = vlen;
                        }
                        break;
                    case 'd':
                    case 'D':
                        if (lstreqcase(dstr, attr, alen)) {
                                attr_out->dom = val;
                                attr_out->dlen = vlen;
                        }
                        if (lstreqcase(uristr, attr, alen)) {
                                attr_out->uri = val;
                                attr_out->urilen = vlen;
                        }
                        break;
                    case 'm':
                    case 'M':
                        if (lstreqcase(mstr, attr, alen)) {
                                attr_out->max = val;
                                attr_out->mlen = vlen;
                        }
                        break;
                    case 'n':
                    case 'N':
                        if (lstreqcase(nstr, attr, alen)) {
                                attr_out->nonce = val;
                                attr_out->nlen = vlen;
                        }
                        if (lstreqcase(ncstr, attr, alen)) {
                                attr_out->ncount = val;
                                attr_out->nclen = vlen;
                        }
                        break;
                    case 'q':
                    case 'Q':
                        if (lstreqcase(qstr, attr, alen)) {
                                attr_out->qop = val;
                                attr_out->qlen = vlen;
                        }
                        break;
                    case 'r':
                    case 'R':
                        if (lstreqcase(rstr, attr, alen)) {
                                attr_out->realm = val;
                                attr_out->rlen = vlen;
                        }
                        if (lstreqcase(respstr, attr, alen)) {
                                attr_out->resp = val;
                                attr_out->resplen = vlen;
                        }
                        break;
                    case 's':
                    case 'S':
                        if (lstreqcase(sstr, attr, alen)) {
                                attr_out->stale = val;
                                attr_out->slen = vlen;
                        }
                        break;
                    case 'u':
                    case 'U':
                        if (lstreqcase(ustr, attr, alen)) {
                                attr_out->user = val;
                                attr_out->ulen = vlen;
                        }
                        break;
                }

                /* we should be at the end of the string or a comma */
                if (scan == end) break;
                if (*scan != ',')
                        return (-5);
        }

        return (0);
}

static int ldap_digest_md5_encode(
        const char *challenge,
        const char *username,
        const char *passwd,
        char **digest
)
{
        unsigned char hash_pass[DIGEST_SIZE];
        digest_attrs_t attrs;
        char *outbuf;
        int outlen;
        int ret;

        /* validate args */
        if (challenge == NULL || username == NULL || passwd == NULL) {
                return (LDAP_PARAM_ERROR);
        }

        /* parse the challenge */
        digest_clear(&attrs);
        ret = digest_parse(challenge, 0, &attrs);
        if (ret != 0)
                return (LDAP_DECODING_ERROR);

        /* server MUST specify support for charset=utf-8 */
        if (attrs.charsetlen != 5 ||
            strncasecmp(attrs.charset, "utf-8", 5) != 0) {
                LDAPDebug(LDAP_DEBUG_TRACE,
                        "server did not specify charset=utf-8\n",
                        0, 0, 0);
                return (LDAP_NOT_SUPPORTED);
        }

        /* set up digest attributes */
        attrs.user = username;
        attrs.ulen = strlen(attrs.user);

        /* allocate the output buffer */
        outlen = strlen(challenge) + DIGEST_CLIENT_EXTRA + 1;
        outbuf = (char *)malloc(outlen);
        if (outbuf == NULL)
                return (LDAP_NO_MEMORY);

        /* hash the password */
        digest_hash_pass(username, 0, attrs.realm, attrs.rlen,
                                passwd, 0, 0, hash_pass),

        /* create the response */
        ret = digest_client_resp("AUTHENTICATE", 12, hash_pass, NULL,
                        &attrs, outbuf, outlen, &outlen);
        memset(hash_pass, 0, DIGEST_SIZE);
        if (ret != 0) {
                free(outbuf);
                return (LDAP_DECODING_ERROR);
        }

        /* null terminate the response */
        *(outbuf+outlen) = '\0';

        *digest = outbuf;
        return (LDAP_SUCCESS);
}

int ldap_x_sasl_digest_md5_bind_s(
        LDAP *ld,
        char *user_name,
        struct berval *cred,
        LDAPControl **serverctrls,
        LDAPControl **clientctrls)
{
        struct berval   *challenge = NULL;
        int             errnum;
        char            *digest = NULL;
        struct berval   resp;

        LDAPDebug(LDAP_DEBUG_TRACE, "ldap_x_sasl_digest_md5_bind_s\n", 0, 0, 0);

        /* Add debug */
        if (ld == NULL || user_name == NULL || cred == NULL ||
            cred->bv_val == NULL)
                return (LDAP_PARAM_ERROR);

        if (ld->ld_version < LDAP_VERSION3)
                return (LDAP_PARAM_ERROR);

        errnum = ldap_sasl_bind_s(ld, NULL, LDAP_SASL_DIGEST_MD5,
                NULL, serverctrls, clientctrls, &challenge);

        if (errnum == LDAP_SASL_BIND_IN_PROGRESS) {
                if (challenge != NULL) {
                        LDAPDebug(LDAP_DEBUG_TRACE,
                                "SASL challenge: %s\n",
                                challenge->bv_val, 0, 0);
                        errnum = ldap_digest_md5_encode(challenge->bv_val,
                                user_name, cred->bv_val, &digest);
                        ber_bvfree(challenge);
                        challenge = NULL;
                        if (errnum == LDAP_SUCCESS) {
                                resp.bv_val = digest;
                                resp.bv_len = strlen(digest);
                                LDAPDebug(LDAP_DEBUG_TRACE,
                                        "SASL reply: %s\n",
                                        digest, 0, 0);
                                errnum = ldap_sasl_bind_s(ld, NULL,
                                        LDAP_SASL_DIGEST_MD5, &resp,
                                        serverctrls, clientctrls, &challenge);
                                free(digest);
                        }
                        if (challenge != NULL)
                                ber_bvfree(challenge);
                } else {
                        errnum = LDAP_NO_MEMORY; /* TO DO: What val? */
                }
        }

        LDAP_MUTEX_LOCK(ld, LDAP_ERR_LOCK);
        ld->ld_errno = errnum;
        LDAP_MUTEX_UNLOCK(ld, LDAP_ERR_LOCK);
        return (errnum);
}

static int
sasl_digest_md5_bind_1(
        LDAP *ld,
        char *user_name,
        LDAPControl **serverctrls,
        LDAPControl **clientctrls,
        int *msgidp)
{
        if (ld == NULL || user_name == NULL || msgidp == NULL)
                return (LDAP_PARAM_ERROR);

        if (ld->ld_version < LDAP_VERSION3)
                return (LDAP_PARAM_ERROR);

        return (ldap_sasl_bind(ld, NULL, LDAP_SASL_DIGEST_MD5,
                NULL, serverctrls, clientctrls, msgidp));
}

static int
sasl_digest_md5_bind_2(
        LDAP *ld,
        char *user_name,
        struct berval *cred,
        LDAPControl **serverctrls,
        LDAPControl **clientctrls,
        LDAPMessage *result,
        int *msgidp)
{
        struct berval   *challenge = NULL;
        struct berval   resp;
        int             errnum;
        char            *digest = NULL;
        int             err;

        if (ld == NULL || user_name == NULL || cred == NULL ||
            cred->bv_val == NULL || result == NULL || msgidp == NULL)
                return (LDAP_PARAM_ERROR);

        if (ld->ld_version < LDAP_VERSION3)
                return (LDAP_PARAM_ERROR);

        err = ldap_result2error(ld, result, 0);
        if (err != LDAP_SASL_BIND_IN_PROGRESS)
                return (err);

        if ((err = ldap_parse_sasl_bind_result(ld, result, &challenge, 0))
                        != LDAP_SUCCESS)
                return (err);
        if (challenge == NULL)
                return (LDAP_NO_MEMORY);

        err = ldap_digest_md5_encode(challenge->bv_val,
                        user_name, cred->bv_val, &digest);
        ber_bvfree(challenge);

        if (err == LDAP_SUCCESS) {
                resp.bv_val = digest;
                resp.bv_len = strlen(digest);
                LDAPDebug(LDAP_DEBUG_TRACE, "SASL reply: %s\n",
                        digest, 0, 0);
                err = ldap_sasl_bind(ld, NULL, LDAP_SASL_DIGEST_MD5,
                        &resp, serverctrls, clientctrls, msgidp);
                free(digest);
        }
        return (err);
}

int ldap_x_sasl_digest_md5_bind(
        LDAP *ld,
        char *user_name,
        struct berval *cred,
        LDAPControl **serverctrls,
        LDAPControl **clientctrls,
        struct timeval *timeout,
        LDAPMessage **result)
{
        LDAPMessage     *res = NULL;
        int             msgid;
        int             rc;

        if (ld == NULL || user_name == NULL || cred == NULL ||
                result == NULL)
                return (LDAP_PARAM_ERROR);

        if (ld->ld_version < LDAP_VERSION3)
                return (LDAP_PARAM_ERROR);

        *result = NULL;

        rc = sasl_digest_md5_bind_1(ld, user_name,
                serverctrls, clientctrls, &msgid);
        if (rc != LDAP_SUCCESS)
                return (rc);

        rc = ldap_result(ld, msgid, 1, timeout, &res);
        if (rc == -1) {
                if (res != NULL)
                        ldap_msgfree(res);
                return (ldap_get_lderrno(ld, NULL, NULL));
        }
        rc = ldap_result2error(ld, res, 0);
        if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
                *result = res;
                return (rc);
        }

        rc = sasl_digest_md5_bind_2(ld, user_name, cred,
                serverctrls, clientctrls, res, &msgid);
        ldap_msgfree(res);
        res = NULL;

        if (rc != LDAP_SUCCESS)
                return (rc);

        rc = ldap_result(ld, msgid, 1, timeout, &res);
        if (rc == -1) {
                if (res != NULL)
                        ldap_msgfree(res);
                return (ldap_get_lderrno(ld, NULL, NULL));
        }
        *result = res;
        rc = ldap_result2error(ld, res, 0);
        return (rc);
}