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