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
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.
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]
23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
30 * Copyright (c) 1998-1999 Innosoft International, Inc. All Rights Reserved.
32 * Copyright (c) 1996-1997 Critical Angle Inc. All Rights Reserved.
49 * DIGEST-MD5 SASL Mechanism
52 /* use this instead of "const unsigned char" to eliminate compiler warnings */
53 typedef /* const */ unsigned char CONST_UCHAR
;
55 /* size of a digest result */
56 #define DIGEST_SIZE 16
58 /* size of a digest hex string */
59 #define DIGEST_HEX_SIZE (DIGEST_SIZE * 2 + 1)
62 * extra bytes which a client response needs in addition to size of
64 #define DIGEST_CLIENT_EXTRA (DIGEST_HEX_SIZE + 128)
66 /* erase a digest_attrs_t structure */
67 #define digest_clear(attrs) memset((attrs), 0, sizeof (digest_attrs_t))
70 * broken-out digest attributes (with quotes removed)
71 * probably not NUL terminated.
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
;
81 static const char hextab
[] = "0123456789abcdef";
82 static CONST_UCHAR colon
[] = ":";
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
91 digest_nonce(char *buf
, int maxlen
)
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
102 unsigned char digest
[16];
106 static int set_rand
= 0;
112 /* initialize challenge */
113 if (maxlen
< 2 * sizeof (cinfo
))
117 /* get a timestamp */
120 /* get some randomness */
123 fd
= open("/dev/urandom", O_RDONLY
);
126 (read(fd
, &r
, sizeof (r
)) == sizeof (r
));
134 r
= cinfo
.mytime
- (getpid() *65536) + (random() & 0xffff);
136 gettimeofday(&tv
, NULL
);
148 MD5Update(&ctx
, (unsigned char *) &r
, sizeof (r
));
149 MD5Update(&ctx
, (unsigned char *) &counter
, sizeof (counter
));
151 MD5Final(cinfo
.digest
, &ctx
);
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];
159 /* take the entire time_t, plus at least 6 bytes of MD5 output */
160 len
= ((sizeof (time_t) + 6) * 2);
170 * if the string is entirely in the 8859-1 subset of UTF-8, then translate
171 * to 8859-1 prior to MD5
174 MD5_UTF8_8859_1(MD5_CTX
*ctx
, CONST_UCHAR
*base
, int len
)
176 CONST_UCHAR
*scan
, *end
;
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;
186 /* if we found a character outside 8859-1, don't alter string */
188 MD5Update(ctx
, base
, len
);
192 /* convert to 8859-1 prior to applying hash */
194 for (scan
= base
; scan
< end
&& *scan
< 0xC0; ++scan
)
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);
201 } while (base
< end
);
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.
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
)
218 if (ulen
== 0) ulen
= strlen(user
);
220 MD5Update(&ctx
, (CONST_UCHAR
*) user
, ulen
);
222 MD5_UTF8_8859_1(&ctx
, (CONST_UCHAR
*) user
, ulen
);
224 MD5Update(&ctx
, colon
, 1);
225 if (rlen
== 0) rlen
= strlen(realm
);
227 MD5Update(&ctx
, (CONST_UCHAR
*) realm
, rlen
);
229 MD5_UTF8_8859_1(&ctx
, (CONST_UCHAR
*) realm
, rlen
);
231 MD5Update(&ctx
, colon
, 1);
232 if (passlen
== 0) passlen
= strlen(pass
);
233 MD5Update(&ctx
, (CONST_UCHAR
*) pass
, passlen
);
234 MD5Final(hash_pass
, &ctx
);
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
244 static unsigned char *
245 digest_hash_a1(const digest_attrs_t
*attr
, CONST_UCHAR
*hash_pass
,
246 unsigned char *hash_a1
)
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
);
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
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
)
272 static CONST_UCHAR defncount
[] = ":00000001:";
273 static CONST_UCHAR empty_hex_int
[] =
274 "00000000000000000000000000000000";
276 unsigned char resp
[DIGEST_SIZE
];
277 unsigned char *hex_a1
= (unsigned char *) outresp
;
278 unsigned char *hex_a2
= (unsigned char *) outresp
;
281 /* compute hash of A2 and put in resp */
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
);
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);
294 MD5Final(resp
, &ctx
);
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];
302 /* compute response */
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);
312 MD5Update(&ctx
, defncount
, sizeof (defncount
) - 1);
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);
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];
324 MD5Update(&ctx
, hex_a2
, DIGEST_SIZE
* 2);
325 MD5Final(resp
, &ctx
);
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];
332 outresp
[DIGEST_SIZE
* 2] = '\0';
333 memset(resp
, 0, sizeof (resp
));
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
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
)
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=";
356 char hexbuf
[DIGEST_HEX_SIZE
];
357 unsigned char hashbuf
[DIGEST_SIZE
];
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)) {
366 if (mlen
!= 0 && method
== NULL
)
369 /* initialize ncount */
370 if (attr
->ncount
== NULL
) {
371 strcpy(attr
->ncbuf
, "00000001");
372 attr
->ncount
= attr
->ncbuf
;
374 } else if (attr
->ncount
== attr
->ncbuf
) {
375 /* increment ncount */
376 scan
= attr
->ncbuf
+ 7;
377 while (scan
>= attr
->ncbuf
) {
381 } else if (*scan
!= 'f') {
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
;
396 if (len
+ suffixsize
>= maxout
)
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
;
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
);
416 /* generate client nonce */
417 len
= digest_nonce(scan
, maxout
- (scan
- outbuf
));
423 if (scan
- outbuf
+ suffixsize
> maxout
)
426 /* compute response */
427 if (hash_a1
== NULL
) {
428 if (hash_pass
== NULL
)
430 hash_a1
= digest_hash_a1(attr
, hash_pass
, hashbuf
);
432 digest_calc_resp(attr
, hash_a1
, method
, mlen
, NULL
, hexbuf
);
435 memcpy(scan
, rstr
, sizeof (rstr
) - 1);
436 scan
+= sizeof (rstr
) - 1;
437 memcpy(scan
, hexbuf
, DIGEST_SIZE
* 2);
439 attr
->resplen
= DIGEST_SIZE
* 2;
440 scan
+= DIGEST_SIZE
* 2;
441 memcpy(scan
, qstr
, sizeof (qstr
));
443 /* set final length */
444 if (plen
!= NULL
) *plen
= scan
- outbuf
+ sizeof (qstr
) - 1;
449 #define lstreqcase(conststr, val, len) ((len) == sizeof (conststr) - 1 && \
450 strncasecmp((conststr), (val), sizeof (conststr) - 1) == 0)
452 /* parse a digest auth string */
454 digest_parse(const char *str
, int len
, digest_attrs_t
*attr_out
)
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
;
471 if (len
== 0) len
= strlen(str
);
475 /* skip over commas */
476 while (scan
< end
&& (*scan
== ',' || isspace(*scan
))) ++scan
;
477 /* parse attribute */
479 while (scan
< end
&& *scan
!= '=') ++scan
;
481 if (!alen
|| scan
== end
|| scan
+ 1 == end
) {
486 if (scan
[1] == '"') {
489 while (scan
< end
&& *scan
!= '"') {
490 /* skip over "\" quoting, but don't remove it */
506 while (scan
< end
&& *scan
!= ',') ++scan
;
512 /* lookup the attribute */
516 if (lstreqcase(cstr
, attr
, alen
)) {
517 attr_out
->cnonce
= val
;
518 attr_out
->clen
= vlen
;
520 if (lstreqcase(charsetstr
, attr
, alen
)) {
521 attr_out
->charset
= val
;
522 attr_out
->charsetlen
= vlen
;
527 if (lstreqcase(dstr
, attr
, alen
)) {
529 attr_out
->dlen
= vlen
;
531 if (lstreqcase(uristr
, attr
, alen
)) {
533 attr_out
->urilen
= vlen
;
538 if (lstreqcase(mstr
, attr
, alen
)) {
540 attr_out
->mlen
= vlen
;
545 if (lstreqcase(nstr
, attr
, alen
)) {
546 attr_out
->nonce
= val
;
547 attr_out
->nlen
= vlen
;
549 if (lstreqcase(ncstr
, attr
, alen
)) {
550 attr_out
->ncount
= val
;
551 attr_out
->nclen
= vlen
;
556 if (lstreqcase(qstr
, attr
, alen
)) {
558 attr_out
->qlen
= vlen
;
563 if (lstreqcase(rstr
, attr
, alen
)) {
564 attr_out
->realm
= val
;
565 attr_out
->rlen
= vlen
;
567 if (lstreqcase(respstr
, attr
, alen
)) {
568 attr_out
->resp
= val
;
569 attr_out
->resplen
= vlen
;
574 if (lstreqcase(sstr
, attr
, alen
)) {
575 attr_out
->stale
= val
;
576 attr_out
->slen
= vlen
;
581 if (lstreqcase(ustr
, attr
, alen
)) {
582 attr_out
->user
= val
;
583 attr_out
->ulen
= vlen
;
588 /* we should be at the end of the string or a comma */
589 if (scan
== end
) break;
597 static int ldap_digest_md5_encode(
598 const char *challenge
,
599 const char *username
,
604 unsigned char hash_pass
[DIGEST_SIZE
];
605 digest_attrs_t attrs
;
611 if (challenge
== NULL
|| username
== NULL
|| passwd
== NULL
) {
612 return (LDAP_PARAM_ERROR
);
615 /* parse the challenge */
616 digest_clear(&attrs
);
617 ret
= digest_parse(challenge
, 0, &attrs
);
619 return (LDAP_DECODING_ERROR
);
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",
627 return (LDAP_NOT_SUPPORTED
);
630 /* set up digest attributes */
631 attrs
.user
= username
;
632 attrs
.ulen
= strlen(attrs
.user
);
634 /* allocate the output buffer */
635 outlen
= strlen(challenge
) + DIGEST_CLIENT_EXTRA
+ 1;
636 outbuf
= (char *)malloc(outlen
);
638 return (LDAP_NO_MEMORY
);
640 /* hash the password */
641 digest_hash_pass(username
, 0, attrs
.realm
, attrs
.rlen
,
642 passwd
, 0, 0, hash_pass
),
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
);
650 return (LDAP_DECODING_ERROR
);
653 /* null terminate the response */
654 *(outbuf
+outlen
) = '\0';
657 return (LDAP_SUCCESS
);
660 int ldap_x_sasl_digest_md5_bind_s(
664 LDAPControl
**serverctrls
,
665 LDAPControl
**clientctrls
)
667 struct berval
*challenge
= NULL
;
672 LDAPDebug(LDAP_DEBUG_TRACE
, "ldap_x_sasl_digest_md5_bind_s\n", 0, 0, 0);
675 if (ld
== NULL
|| user_name
== NULL
|| cred
== NULL
||
676 cred
->bv_val
== NULL
)
677 return (LDAP_PARAM_ERROR
);
679 if (ld
->ld_version
< LDAP_VERSION3
)
680 return (LDAP_PARAM_ERROR
);
682 errnum
= ldap_sasl_bind_s(ld
, NULL
, LDAP_SASL_DIGEST_MD5
,
683 NULL
, serverctrls
, clientctrls
, &challenge
);
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
);
694 if (errnum
== LDAP_SUCCESS
) {
695 resp
.bv_val
= digest
;
696 resp
.bv_len
= strlen(digest
);
697 LDAPDebug(LDAP_DEBUG_TRACE
,
700 errnum
= ldap_sasl_bind_s(ld
, NULL
,
701 LDAP_SASL_DIGEST_MD5
, &resp
,
702 serverctrls
, clientctrls
, &challenge
);
705 if (challenge
!= NULL
)
706 ber_bvfree(challenge
);
708 errnum
= LDAP_NO_MEMORY
; /* TO DO: What val? */
712 LDAP_MUTEX_LOCK(ld
, LDAP_ERR_LOCK
);
713 ld
->ld_errno
= errnum
;
714 LDAP_MUTEX_UNLOCK(ld
, LDAP_ERR_LOCK
);
719 sasl_digest_md5_bind_1(
722 LDAPControl
**serverctrls
,
723 LDAPControl
**clientctrls
,
726 if (ld
== NULL
|| user_name
== NULL
|| msgidp
== NULL
)
727 return (LDAP_PARAM_ERROR
);
729 if (ld
->ld_version
< LDAP_VERSION3
)
730 return (LDAP_PARAM_ERROR
);
732 return (ldap_sasl_bind(ld
, NULL
, LDAP_SASL_DIGEST_MD5
,
733 NULL
, serverctrls
, clientctrls
, msgidp
));
737 sasl_digest_md5_bind_2(
741 LDAPControl
**serverctrls
,
742 LDAPControl
**clientctrls
,
746 struct berval
*challenge
= NULL
;
752 if (ld
== NULL
|| user_name
== NULL
|| cred
== NULL
||
753 cred
->bv_val
== NULL
|| result
== NULL
|| msgidp
== NULL
)
754 return (LDAP_PARAM_ERROR
);
756 if (ld
->ld_version
< LDAP_VERSION3
)
757 return (LDAP_PARAM_ERROR
);
759 err
= ldap_result2error(ld
, result
, 0);
760 if (err
!= LDAP_SASL_BIND_IN_PROGRESS
)
763 if ((err
= ldap_parse_sasl_bind_result(ld
, result
, &challenge
, 0))
766 if (challenge
== NULL
)
767 return (LDAP_NO_MEMORY
);
769 err
= ldap_digest_md5_encode(challenge
->bv_val
,
770 user_name
, cred
->bv_val
, &digest
);
771 ber_bvfree(challenge
);
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",
778 err
= ldap_sasl_bind(ld
, NULL
, LDAP_SASL_DIGEST_MD5
,
779 &resp
, serverctrls
, clientctrls
, msgidp
);
785 int ldap_x_sasl_digest_md5_bind(
789 LDAPControl
**serverctrls
,
790 LDAPControl
**clientctrls
,
791 struct timeval
*timeout
,
792 LDAPMessage
**result
)
794 LDAPMessage
*res
= NULL
;
798 if (ld
== NULL
|| user_name
== NULL
|| cred
== NULL
||
800 return (LDAP_PARAM_ERROR
);
802 if (ld
->ld_version
< LDAP_VERSION3
)
803 return (LDAP_PARAM_ERROR
);
807 rc
= sasl_digest_md5_bind_1(ld
, user_name
,
808 serverctrls
, clientctrls
, &msgid
);
809 if (rc
!= LDAP_SUCCESS
)
812 rc
= ldap_result(ld
, msgid
, 1, timeout
, &res
);
816 return (ldap_get_lderrno(ld
, NULL
, NULL
));
818 rc
= ldap_result2error(ld
, res
, 0);
819 if (rc
!= LDAP_SASL_BIND_IN_PROGRESS
) {
824 rc
= sasl_digest_md5_bind_2(ld
, user_name
, cred
,
825 serverctrls
, clientctrls
, res
, &msgid
);
829 if (rc
!= LDAP_SUCCESS
)
832 rc
= ldap_result(ld
, msgid
, 1, timeout
, &res
);
836 return (ldap_get_lderrno(ld
, NULL
, NULL
));
839 rc
= ldap_result2error(ld
, res
, 0);