1/* 2 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6#pragma ident "%Z%%M% %I% %E% SMI" 7 8/* 9 * Copyright (c) 1996 Regents of the University of Michigan. 10 * All rights reserved. 11 * 12 * LIBLDAP url.c -- LDAP URL related routines 13 * 14 * LDAP URLs look like this: 15 * l d a p : / / hostport / dn [ ? attributes [ ? scope [ ? filter [ ? extensions ] ] ] ] 16 * 17 * where: 18 * attributes is a comma separated list 19 * scope is one of these three strings: base one sub (default=base) 20 * filter is an string-represented filter as in RFC 1558 21 * extensions is a comma separated list of extension 22 * and extension is like this: [ ! ] oid/x-oid [ = value ] 23 * 24 * e.g., ldap://ldap.itd.umich.edu/c=US?o,description?one?o=umich 25 * 26 * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl> 27 */ 28 29#ifndef lint 30static char copyright[] = "@(#) Copyright (c) 1996 Regents of the University of Michigan.\nAll rights reserved.\n"; 31#endif 32 33#include <stdio.h> 34#include <string.h> 35#include <ctype.h> 36 37#ifdef MACOS 38#include <stdlib.h> 39#include "macos.h" 40#endif /* MACOS */ 41 42#if defined( DOS ) || defined( _WIN32 ) 43#include <stdlib.h> 44#include <malloc.h> 45#include "msdos.h" 46#endif /* DOS || _WIN32 */ 47 48#if !defined(MACOS) && !defined(DOS) && !defined( _WIN32 ) 49#include <sys/time.h> 50#include <sys/types.h> 51#include <sys/socket.h> 52#endif /* !MACOS && !DOS && !_WIN32 */ 53 54#include "lber.h" 55#include "ldap.h" 56#include "ldap-private.h" 57#include "ldap-int.h" 58 59 60#ifdef NEEDPROTOS 61static int skip_url_prefix( char **urlp, int *enclosedp ); 62static void hex_unescape( char *s ); 63static int unhex( char c ); 64#else /* NEEDPROTOS */ 65static int skip_url_prefix(); 66static void hex_unescape(); 67static int unhex(); 68#endif /* NEEDPROTOS */ 69 70 71int 72ldap_is_ldap_url( char *url ) 73{ 74 int enclosed; 75 76 return( url != NULL && skip_url_prefix( &url, &enclosed )); 77} 78 79 80static int 81skip_url_prefix( char **urlp, int *enclosedp ) 82{ 83/* 84 * return non-zero if this looks like a LDAP URL; zero if not 85 * if non-zero returned, *urlp will be moved past "ldap://" part of URL 86 */ 87 if ( *urlp == NULL ) { 88 return( 0 ); 89 } 90 91 /* skip leading '<' (if any) */ 92 if ( **urlp == '<' ) { 93 *enclosedp = 1; 94 ++*urlp; 95 } else { 96 *enclosedp = 0; 97 } 98 99 /* skip leading "URL:" (if any) */ 100 if ( strlen( *urlp ) >= LDAP_URL_URLCOLON_LEN && strncasecmp( 101 *urlp, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { 102 *urlp += LDAP_URL_URLCOLON_LEN; 103 } 104 105 /* check for missing "ldap://" prefix */ 106 if ( strlen( *urlp ) < LDAP_URL_PREFIX_LEN || 107 strncasecmp( *urlp, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) != 0 ) { 108 return( 0 ); 109 } 110 111 /* skip over "ldap://" prefix and return success */ 112 *urlp += LDAP_URL_PREFIX_LEN; 113 return( 1 ); 114} 115 116int ldap_url_extension_parse( char *exts, LDAPURLExt *** lueppp) 117{ 118 /* Pick apart the pieces of an LDAP URL Extensions */ 119 /* No copy of exts is made, LDAPURLExt's points to exts string */ 120 LDAPURLExt ** lues; 121 LDAPURLExt *luep; 122 int i = 0; 123 char *p = exts; 124 char *ptr, *ptr2; 125 126 *lueppp = NULL; 127 128 /* Count the number of , in extensions */ 129 while ( (p = strchr (p, ',')) != NULL){ 130 i++; 131 } 132 /* There are at most i+1 extensions */ 133 if ((lues = (LDAPURLExt **)calloc(i+2, sizeof(LDAPURLExt *))) == NULL){ 134 return (LDAP_URL_ERR_MEM); 135 } 136 137 p = exts; 138 i = 0; 139 140 while ( p ) { 141 if ((ptr = strchr(p, ',')) != NULL) 142 *ptr++ = '\0'; 143 else 144 ptr = NULL; 145 146 if ((luep = (LDAPURLExt *)calloc(1, sizeof(LDAPURLExt))) == NULL){ 147 ldap_free_urlexts(lues); 148 return (LDAP_URL_ERR_MEM); 149 } 150 lues[i] = luep; 151 152 if (*p == '!'){ 153 luep->lue_iscritical = 1; 154 p++; 155 } 156 luep->lue_type = p; 157 158 if (( ptr2 = strchr(p, '=')) != NULL) { 159 *ptr2++ = '\0'; 160 luep->lue_value = ptr2; 161 hex_unescape(ptr2); 162 } 163 164 i++; 165 p = ptr; 166 } 167 *lueppp = lues; 168 169 return( 0 ); 170} 171 172 173int 174ldap_url_parse( char *url, LDAPURLDesc **ludpp ) 175{ 176/* 177 * Pick apart the pieces of an LDAP URL. 178 */ 179 180 LDAPURLDesc *ludp; 181 char *attrs = NULL; 182 char *p = NULL; 183 char *q = NULL; 184 char *x = NULL; 185 int enclosed, i, nattrs, errcode; 186 187 Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 262, "ldap_url_parse(%s)\n"), url, 0, 0 ); 188 189 *ludpp = NULL; /* pessimistic */ 190 191 if ( !skip_url_prefix( &url, &enclosed )) { 192 return( LDAP_URL_ERR_NOTLDAP ); 193 } 194 195 /* allocate return struct */ 196 if (( ludp = (LDAPURLDesc *)calloc( 1, sizeof( LDAPURLDesc ))) 197 == NULLLDAPURLDESC ) { 198 return( LDAP_URL_ERR_MEM ); 199 } 200 201 ludp->lud_port = LDAP_PORT; 202 203 /* make working copy of the remainder of the URL */ 204 if (( url = strdup( url )) == NULL ) { 205 ldap_free_urldesc( ludp ); 206 return( LDAP_URL_ERR_MEM ); 207 } 208 209 if ( enclosed && *((p = url + strlen( url ) - 1)) == '>' ) { 210 *p = '\0'; 211 } 212 213 /* set defaults */ 214 /* LP By default don't set them... Then we can check if they are present or not in URL */ 215 ludp->lud_scope = LDAP_SCOPE_UNKNOWN; 216 ludp->lud_filter = NULL; 217 218 219 /* lud_string is the only malloc'd string space we use */ 220 ludp->lud_string = url; 221 222 /* scan forward for '/' that marks end of hostport and begin. of dn */ 223 if (( ludp->lud_dn = strchr( url, '/' )) != NULL ) { 224 *ludp->lud_dn++ = '\0'; 225 } 226 227 /* terminate hostport; point to start of dn */ 228 229 if (( p = strchr( url, ':' )) != NULL ) { 230 *p++ = '\0'; 231 ludp->lud_port = atoi( p ); 232 } 233 234 if ( *url == '\0' ) { 235 ludp->lud_host = NULL; 236 } else { 237 ludp->lud_host = url; 238 hex_unescape( ludp->lud_host ); 239 } 240 241 if (ludp->lud_dn != NULL){ 242 /* scan for '?' that marks end of dn and beginning of attributes */ 243 if (( attrs = strchr( ludp->lud_dn, '?' )) != NULL ) { 244 /* terminate dn; point to start of attrs. */ 245 *attrs++ = '\0'; 246 247 /* scan for '?' that marks end of attrs and begin. of scope */ 248 if (( p = strchr( attrs, '?' )) != NULL ) { 249 /* 250 * terminate attrs; point to start of scope and scan for 251 * '?' that marks end of scope and begin. of filter 252 */ 253 *p++ = '\0'; 254 255 if (( q = strchr( p, '?' )) != NULL ) { 256 /* terminate scope; point to start of filter */ 257 *q++ = '\0'; 258 259 if (( x = strchr(q, '?')) != NULL ) { 260 /* terminate filter; point to start of extension */ 261 *x++ = '\0'; 262 263 if (*x != '\0'){ 264 /* parse extensions */ 265 } 266 } 267 268 if ( *q != '\0' ) { 269 ludp->lud_filter = q; 270 hex_unescape( ludp->lud_filter ); 271 } 272 } 273 274 if ( strcasecmp( p, "one" ) == 0 ) { 275 ludp->lud_scope = LDAP_SCOPE_ONELEVEL; 276 } else if ( strcasecmp( p, "base" ) == 0 ) { 277 ludp->lud_scope = LDAP_SCOPE_BASE; 278 } else if ( strcasecmp( p, "sub" ) == 0 ) { 279 ludp->lud_scope = LDAP_SCOPE_SUBTREE; 280 } else if ( *p != '\0' ) { 281 ldap_free_urldesc( ludp ); 282 return( LDAP_URL_ERR_BADSCOPE ); 283 } 284 } 285 } 286 if ( *ludp->lud_dn == '\0' ) { 287 ludp->lud_dn = NULL; 288 } else { 289 hex_unescape( ludp->lud_dn ); 290 } 291 292 /* 293 * if attrs list was included, turn it into a null-terminated array 294 */ 295 if ( attrs != NULL && *attrs != '\0' ) { 296 for ( nattrs = 1, p = attrs; *p != '\0'; ++p ) { 297 if ( *p == ',' ) { 298 ++nattrs; 299 } 300 } 301 302 if (( ludp->lud_attrs = (char **)calloc( nattrs + 1, 303 sizeof( char * ))) == NULL ) { 304 ldap_free_urldesc( ludp ); 305 return( LDAP_URL_ERR_MEM ); 306 } 307 308 for ( i = 0, p = attrs; i < nattrs; ++i ) { 309 ludp->lud_attrs[ i ] = p; 310 if (( p = strchr( p, ',' )) != NULL ) { 311 *p++ ='\0'; 312 } 313 hex_unescape( ludp->lud_attrs[ i ] ); 314 } 315 } 316 317 if (x != NULL && *x != '\0'){ 318 if (errcode = ldap_url_extension_parse(x, &ludp->lud_extensions)){ 319 ldap_free_urldesc(ludp); 320 return ( errcode ); 321 } 322 } 323 } 324 325 *ludpp = ludp; 326 327 return( 0 ); 328} 329 330void ldap_free_urlexts( LDAPURLExt ** lues) 331{ 332 int i; 333 for (i = 0; lues[i] != NULL; i++){ 334 free(lues[i]); 335 } 336 free(lues); 337} 338 339 340void 341ldap_free_urldesc( LDAPURLDesc *ludp ) 342{ 343 if ( ludp != NULLLDAPURLDESC ) { 344 if ( ludp->lud_string != NULL ) { 345 free( ludp->lud_string ); 346 } 347 if ( ludp->lud_attrs != NULL ) { 348 free( ludp->lud_attrs ); 349 } 350 if (ludp->lud_extensions != NULL) { 351 ldap_free_urlexts(ludp->lud_extensions); 352 } 353 free( ludp ); 354 } 355} 356 357 358 359int 360ldap_url_search( LDAP *ld, char *url, int attrsonly ) 361{ 362 int err; 363 LDAPURLDesc *ludp; 364 BerElement *ber; 365 LDAPServer *srv = NULL; 366 367#ifdef _REENTRANT 368 LOCK_LDAP(ld); 369#endif 370 if ( ldap_url_parse( url, &ludp ) != 0 ) { 371 ld->ld_errno = LDAP_PARAM_ERROR; 372#ifdef _REENTRANT 373 UNLOCK_LDAP(ld); 374#endif 375 return( -1 ); 376 } 377 378 if (( ber = ldap_build_search_req( ld, ludp->lud_dn, 379 ludp->lud_scope == LDAP_SCOPE_UNKNOWN ? LDAP_SCOPE_BASE : ludp->lud_scope, 380 ludp->lud_filter ? ludp->lud_filter : "(objectclass=*)", 381 ludp->lud_attrs, attrsonly, NULL, NULL, -1 )) == NULLBER ) { 382#ifdef _REENTRANT 383 UNLOCK_LDAP(ld); 384#endif 385 return( -1 ); 386 } 387 388 err = 0; 389 390 if ( ludp->lud_host != NULL || ludp->lud_port != 0 ) { 391 if (( srv = (LDAPServer *)calloc( 1, sizeof( LDAPServer ))) 392 == NULL || ( srv->lsrv_host = strdup( ludp->lud_host == 393 NULL ? ld->ld_defhost : ludp->lud_host )) == NULL ) { 394 if ( srv != NULL ) { 395 free( srv ); 396 } 397 ld->ld_errno = LDAP_NO_MEMORY; 398 err = -1; 399 } else { 400 if ( ludp->lud_port == 0 ) { 401 srv->lsrv_port = LDAP_PORT; 402 } else { 403 srv->lsrv_port = ludp->lud_port; 404 } 405 } 406 } 407 408 if ( err != 0 ) { 409 ber_free( ber, 1 ); 410 } else { 411 err = send_server_request( ld, ber, ld->ld_msgid, NULL, srv, NULL, 1 ); 412 } 413 414 ldap_free_urldesc( ludp ); 415 416#ifdef _REENTRANT 417 UNLOCK_LDAP(ld); 418#endif 419 return( err ); 420} 421 422 423int 424ldap_url_search_st( LDAP *ld, char *url, int attrsonly, 425 struct timeval *timeout, LDAPMessage **res ) 426{ 427 int msgid; 428 int retcode = LDAP_SUCCESS; 429 430 if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) { 431 return( ld->ld_errno ); 432 } 433 434 if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 ) { 435 return( ld->ld_errno ); 436 } 437 438 if ( ld->ld_errno == LDAP_TIMEOUT ) { 439 (void) ldap_abandon( ld, msgid ); 440 ld->ld_errno = LDAP_TIMEOUT; 441 return( ld->ld_errno ); 442 } 443 444#ifdef _REENTRANT 445 LOCK_LDAP(ld); 446#endif 447 retcode = ldap_parse_result(ld, *res, &ld->ld_errno, &ld->ld_matched, &ld->ld_error, 448 &ld->ld_referrals, &ld->ld_ret_ctrls, 0); 449 if (retcode == LDAP_SUCCESS) 450 retcode = ld->ld_errno; 451#ifdef _REENTRANT 452 UNLOCK_LDAP(ld); 453#endif 454 455 return (retcode); 456} 457 458 459int 460ldap_url_search_s( LDAP *ld, char *url, int attrsonly, LDAPMessage **res ) 461{ 462 int msgid; 463 int retcode = LDAP_SUCCESS; 464 465 if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) { 466 return( ld->ld_errno ); 467 } 468 469 if ( ldap_result( ld, msgid, 1, (struct timeval *)NULL, res ) == -1 ) { 470 return( ld->ld_errno ); 471 } 472 473#ifdef _REENTRANT 474 LOCK_LDAP(ld); 475#endif 476 retcode = ldap_parse_result(ld, *res, &ld->ld_errno, &ld->ld_matched, &ld->ld_error, 477 &ld->ld_referrals, &ld->ld_ret_ctrls, 0); 478 if (retcode == LDAP_SUCCESS) 479 retcode = ld->ld_errno; 480#ifdef _REENTRANT 481 UNLOCK_LDAP(ld); 482#endif 483 484 return (retcode); 485} 486 487 488static void 489hex_unescape( char *s ) 490{ 491/* 492 * Remove URL hex escapes from s... done in place. The basic concept for 493 * this routine is borrowed from the WWW library HTUnEscape() routine. 494 */ 495 char *p; 496 497 for ( p = s; *s != '\0'; ++s ) { 498 if ( *s == '%' ) { 499 if ( *++s != '\0' ) { 500 *p = unhex( *s ) << 4; 501 } 502 if ( *++s != '\0' ) { 503 *p++ += unhex( *s ); 504 } 505 } else { 506 *p++ = *s; 507 } 508 } 509 510 *p = '\0'; 511} 512 513 514static int 515unhex( char c ) 516{ 517 return( c >= '0' && c <= '9' ? c - '0' 518 : c >= 'A' && c <= 'F' ? c - 'A' + 10 519 : c - 'a' + 10 ); 520} 521 522 523/* 524 * Locate the LDAP URL associated with a DNS domain name. 525 * 526 * The supplied DNS domain name is converted into a distinguished 527 * name. The directory entry specified by that distinguished name 528 * is searched for a labeledURI attribute. If successful then the 529 * LDAP URL is returned. If unsuccessful then that entry's parent 530 * is searched and so on until the target distinguished name is 531 * reduced to only two nameparts. 532 * 533 * For example, if 'ny.eng.wiz.com' is the DNS domain then the 534 * following entries are searched until one succeeds: 535 * dc=ny,dc=eng,dc=wiz,dc=com 536 * dc=eng,dc=wiz,dc=com 537 * dc=wiz,dc=com 538 * 539 * If dns_name is NULL then the environment variable LOCALDOMAIN is used. 540 * If attrs is not NULL then it is appended to the URL's attribute list. 541 * If scope is not NULL then it overrides the URL's scope. 542 * If filter is not NULL then it is merged with the URL's filter. 543 * 544 * If an error is encountered then zero is returned, otherwise a string 545 * URL is returned. The caller should free the returned string if it is 546 * non-zero. 547 */ 548 549char * 550ldap_dns_to_url( 551 LDAP *ld, 552 char *dns_name, 553 char *attrs, 554 char *scope, 555 char *filter 556) 557{ 558 char *dn; 559 char *url = 0; 560 char *url2 = 0; 561 LDAPURLDesc *urldesc; 562 char *cp; 563 char *cp2; 564 size_t attrs_len = 0; 565 size_t scope_len = 0; 566 size_t filter_len = 0; 567 int nameparts; 568 int no_attrs = 0; 569 int no_scope = 0; 570 571 if (dns_name == 0) { 572 dns_name = (char *)getenv("LOCALDOMAIN"); 573 } 574 575 if ((ld == NULL) || ((dn = ldap_dns_to_dn(dns_name, &nameparts)) == 576 NULL)) 577 return (0); 578 579 if ((url = ldap_dn_to_url(ld, dn, nameparts)) == NULL) { 580 free(dn); 581 return (0); 582 } 583 free(dn); 584 585 /* merge filter and/or scope and/or attributes with URL */ 586 if (attrs || scope || filter) { 587 588 if (attrs) 589 attrs_len = strlen(attrs) + 2; /* for comma and NULL */ 590 591 if (scope) 592 scope_len = strlen(scope) + 1; /* for NULL */ 593 594 if (filter) 595 filter_len = strlen(filter) + 4; 596 /* for ampersand, parentheses and NULL */ 597 598 if (ldap_is_ldap_url(url)) { 599 600 if ((url2 = (char *)malloc(attrs_len + scope_len + 601 filter_len + strlen(url) + 1)) == NULL) { 602 return (0); 603 } 604 cp = url; 605 cp2 = url2; 606 607 /* copy URL scheme, hostname, port number and DN */ 608 while (*cp && (*cp != '?')) { 609 *cp2++ = *cp++; 610 } 611 612 /* handle URL attributes */ 613 614 if (*cp == '?') { /* test first '?' */ 615 *cp2++ = *cp++; /* copy first '?' */ 616 617 if (*cp == '?') { /* test second '?' */ 618 619 /* insert supplied attributes */ 620 if (attrs) { 621 while (*attrs) { 622 *cp2++ = *attrs++; 623 } 624 } else { 625 no_attrs = 1; 626 } 627 628 } else { 629 630 /* copy URL attributes */ 631 while (*cp && (*cp != '?')) { 632 *cp2++ = *cp++; 633 } 634 635 /* append supplied attributes */ 636 if (attrs) { 637 *cp2++ = ','; 638 while (*attrs) { 639 *cp2++ = *attrs++; 640 } 641 } 642 } 643 644 } else { 645 /* append supplied attributes */ 646 if (attrs) { 647 *cp2++ = '?'; 648 while (*attrs) { 649 *cp2++ = *attrs++; 650 } 651 } else { 652 no_attrs = 1; 653 } 654 } 655 656 /* handle URL scope */ 657 658 if (*cp == '?') { /* test second '?' */ 659 *cp2++ = *cp++; /* copy second '?' */ 660 661 if (*cp == '?') { /* test third '?' */ 662 663 /* insert supplied scope */ 664 if (scope) { 665 while (*scope) { 666 *cp2++ = *scope++; 667 } 668 } else { 669 no_scope = 1; 670 } 671 672 } else { 673 674 if (scope) { 675 /* skip over URL scope */ 676 while (*cp && (*cp != '?')) { 677 *cp++; 678 } 679 /* insert supplied scope */ 680 while (*scope) { 681 *cp2++ = *scope++; 682 } 683 } else { 684 685 /* copy URL scope */ 686 while (*cp && (*cp != '?')) { 687 *cp2++ = *cp++; 688 } 689 } 690 } 691 692 } else { 693 /* append supplied scope */ 694 if (scope) { 695 if (no_attrs) { 696 *cp2++ = '?'; 697 } 698 *cp2++ = '?'; 699 while (*scope) { 700 *cp2++ = *scope++; 701 } 702 } else { 703 no_scope = 1; 704 } 705 } 706 707 /* handle URL filter */ 708 709 if (*cp == '?') { /* test third '?' */ 710 *cp2++ = *cp++; /* copy third '?' */ 711 712 if (filter) { 713 714 /* merge URL and supplied filters */ 715 716 *cp2++ = '('; 717 *cp2++ = '&'; 718 /* copy URL filter */ 719 while (*cp) { 720 *cp2++ = *cp++; 721 } 722 /* append supplied filter */ 723 while (*filter) { 724 *cp2++ = *filter++; 725 } 726 *cp2++ = ')'; 727 } else { 728 729 /* copy URL filter */ 730 while (*cp) { 731 *cp2++ = *cp++; 732 } 733 } 734 735 } else { 736 /* append supplied filter */ 737 if (filter) { 738 if (no_scope) { 739 if (no_attrs) { 740 *cp2++ = '?'; 741 } 742 *cp2++ = '?'; 743 } 744 *cp2++ = '?'; 745 while (*filter) { 746 *cp2++ = *filter++; 747 } 748 } 749 } 750 751 *cp2++ = '\0'; 752 free (url); 753 url = url2; 754 755 } else { 756 return (0); /* not an LDAP URL */ 757 } 758 } 759 return (url); 760} 761 762 763/* 764 * Locate the LDAP URL associated with a distinguished name. 765 * 766 * The number of nameparts in the supplied distinguished name must be 767 * provided. The specified directory entry is searched for a labeledURI 768 * attribute. If successful then the LDAP URL is returned. If unsuccessful 769 * then that entry's parent is searched and so on until the target 770 * distinguished name is reduced to only two nameparts. 771 * 772 * For example, if 'l=ny,ou=eng,o=wiz,c=us' is the distinguished name 773 * then the following entries are searched until one succeeds: 774 * l=ny,ou=eng,o=wiz,c=us 775 * ou=eng,o=wiz,c=us 776 * o=wiz,c=us 777 * 778 * If an error is encountered then zero is returned, otherwise a string 779 * URL is returned. The caller should free the returned string if it is 780 * non-zero. 781 */ 782 783char * 784ldap_dn_to_url( 785 LDAP *ld, 786 char *dn, 787 int nameparts 788) 789{ 790 char *next_dn = dn; 791 char *url = 0; 792 char *attrs[2] = {"labeledURI", 0}; 793 LDAPMessage *res, *e; 794 char **vals; 795 796 /* 797 * Search for a URL in the named entry or its parent entry. 798 * Continue until only 2 nameparts remain. 799 */ 800 while (dn && (nameparts > 1) && (! url)) { 801 802 /* search for the labeledURI attribute */ 803 if (ldap_search_s(ld, dn, LDAP_SCOPE_BASE, 804 "(objectClass=*)", attrs, 0, &res) == LDAP_SUCCESS) { 805 806 /* locate the first entry returned */ 807 if ((e = ldap_first_entry(ld, res)) != NULL) { 808 809 /* locate the labeledURI attribute */ 810 if ((vals = 811 ldap_get_values(ld, e, "labeledURI")) != 812 NULL) { 813 814 /* copy the attribute value */ 815 if ((url = strdup((char *)vals[0])) != 816 NULL) { 817 ldap_value_free(vals); 818 } 819 } 820 } 821 /* free the search results */ 822 ldap_msgfree(res); 823 } 824 825 if (! url) { 826 /* advance along the DN by one namepart */ 827 if (next_dn = strchr(dn, ',')) { 828 next_dn++; 829 dn = next_dn; 830 nameparts--; 831 } 832 } 833 } 834 835 return (url); 836} 837