3 * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved.
4 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
7 * Copyright (c) 1996 Regents of the University of Michigan.
10 * LIBLDAP url.c -- LDAP URL (RFC 2255) related routines
12 * LDAP URLs look like this:
13 * ldap[s]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
16 * attributes is a comma separated list
17 * scope is one of these three strings: base one sub (default=base)
18 * filter is an string-represented filter as in RFC 2254
20 * e.g., ldap://host:port/dc=com?o,cn?base?o=openldap?extension
22 * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
29 #include <ac/stdlib.h>
32 #include <ac/socket.h>
33 #include <ac/string.h>
40 static const char* skip_url_prefix LDAP_P((
43 unsigned long *properties,
48 ldap_is_ldap_url( LDAP_CONST char *url )
50 int enclosed, protocol;
51 unsigned long properties;
57 if( skip_url_prefix( url, &enclosed, &properties, &protocol) == NULL ) {
61 return !(properties & LDAP_URL_USE_SSL);
65 ldap_is_ldaps_url( LDAP_CONST char *url )
67 int enclosed, protocol;
68 unsigned long properties;
74 if( skip_url_prefix( url, &enclosed, &properties, &protocol) == NULL ) {
78 return (properties & LDAP_URL_USE_SSL);
85 unsigned long *properties,
90 * return non-zero if this looks like a LDAP URL; zero if not
91 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
101 /* skip leading '<' (if any) */
109 /* skip leading "URL:" (if any) */
110 if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 )
112 p += LDAP_URL_URLCOLON_LEN;
117 /* check for "ldap://" prefix */
118 if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
119 /* skip over "ldap://" prefix and return success */
120 p += LDAP_URL_PREFIX_LEN;
121 *protocol = LDAP_PROTO_TCP;
125 /* check for "ldaps://" prefix */
126 if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
127 /* skip over "ldaps://" prefix and return success */
128 p += LDAPS_URL_PREFIX_LEN;
129 *protocol = LDAP_PROTO_TCP;
130 *properties |= LDAP_URL_USE_SSL;
134 /* check for "ldapi://" prefix */
135 if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
136 /* skip over "ldapi://" prefix and return success */
137 p += LDAPI_URL_PREFIX_LEN;
138 *protocol = LDAP_PROTO_LOCAL;
142 /* check for "ldapis://" prefix: should this be legal? */
143 if ( strncasecmp( p, LDAPIS_URL_PREFIX, LDAPIS_URL_PREFIX_LEN ) == 0 ) {
144 /* skip over "ldapis://" prefix and return success */
145 p += LDAPIS_URL_PREFIX_LEN;
146 *protocol = LDAP_PROTO_LOCAL;
147 *properties |= LDAP_URL_USE_SSL;
155 static int str2scope( const char *p )
157 if ( strcasecmp( p, "one" ) == 0 ) {
158 return LDAP_SCOPE_ONELEVEL;
160 } else if ( strcasecmp( p, "onetree" ) == 0 ) {
161 return LDAP_SCOPE_ONELEVEL;
163 } else if ( strcasecmp( p, "base" ) == 0 ) {
164 return LDAP_SCOPE_BASE;
166 } else if ( strcasecmp( p, "sub" ) == 0 ) {
167 return LDAP_SCOPE_SUBTREE;
169 } else if ( strcasecmp( p, "subtree" ) == 0 ) {
170 return LDAP_SCOPE_SUBTREE;
178 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
181 * Pick apart the pieces of an LDAP URL.
186 int i, enclosed, protocol;
187 unsigned long properties;
191 if( url_in == NULL && ludpp == NULL ) {
192 return LDAP_URL_ERR_PARAM;
195 Debug( LDAP_DEBUG_TRACE, "ldap_url_parse(%s)\n", url_in, 0, 0 );
197 *ludpp = NULL; /* pessimistic */
199 url_tmp = skip_url_prefix( url_in, &enclosed, &properties, &protocol );
201 if ( url_tmp == NULL ) {
202 return LDAP_URL_ERR_NOTLDAP;
205 /* make working copy of the remainder of the URL */
206 if (( url = LDAP_STRDUP( url_tmp )) == NULL ) {
207 return( LDAP_URL_ERR_MEM );
211 p = &url[strlen(url)-1];
215 return LDAP_URL_ERR_BADENCLOSURE;
221 /* allocate return struct */
222 ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
224 if ( ludp == NULL ) {
226 return LDAP_URL_ERR_MEM;
229 ludp->lud_next = NULL;
230 ludp->lud_host = NULL;
233 ludp->lud_attrs = NULL;
234 ludp->lud_filter = NULL;
235 ludp->lud_properties = properties;
236 ludp->lud_protocol = protocol;
237 ludp->lud_scope = LDAP_SCOPE_BASE;
239 ludp->lud_filter = LDAP_STRDUP("(objectClass=*)");
241 if( ludp->lud_filter == NULL ) {
243 ldap_free_urldesc( ludp );
244 return LDAP_URL_ERR_MEM;
247 /* scan forward for '/' that marks end of hostport and begin. of dn */
248 p = strchr( url, '/' );
251 /* terminate hostport; point to start of dn */
255 if (( q = strchr( url, ':' )) != NULL ) {
257 ldap_pvt_hex_unescape( q );
261 ldap_free_urldesc( ludp );
262 return LDAP_URL_ERR_BADURL;
265 ludp->lud_port = atoi( q );
268 ldap_pvt_hex_unescape( url );
269 ludp->lud_host = LDAP_STRDUP( url );
271 if( ludp->lud_host == NULL ) {
273 ldap_free_urldesc( ludp );
274 return LDAP_URL_ERR_MEM;
278 * Kluge. ldap://111.222.333.444:389??cn=abc,o=company
280 * On early Novell releases, search references/referrals were returned
281 * in this format, i.e., the dn was kind of in the scope position,
282 * but the required slash is missing. The whole thing is illegal syntax,
283 * but we need to account for it. Fortunately it can't be confused with
286 if( (p == NULL) && ((q = strchr( q, '?')) != NULL)) {
288 /* ? immediately followed by question */
293 ldap_pvt_hex_unescape( q );
294 ludp->lud_dn = LDAP_STRDUP( q );
296 ludp->lud_dn = LDAP_STRDUP( "" );
299 if( ludp->lud_dn == NULL ) {
301 ldap_free_urldesc( ludp );
302 return LDAP_URL_ERR_MEM;
310 return LDAP_URL_SUCCESS;
313 /* scan forward for '?' that may marks end of dn */
314 q = strchr( p, '?' );
317 /* terminate dn part */
323 ldap_pvt_hex_unescape( p );
324 ludp->lud_dn = LDAP_STRDUP( p );
326 ludp->lud_dn = LDAP_STRDUP( "" );
329 if( ludp->lud_dn == NULL ) {
331 ldap_free_urldesc( ludp );
332 return LDAP_URL_ERR_MEM;
339 return LDAP_URL_SUCCESS;
342 /* scan forward for '?' that may marks end of attributes */
344 q = strchr( p, '?' );
347 /* terminate attributes part */
352 /* parse attributes */
353 ldap_pvt_hex_unescape( p );
354 ludp->lud_attrs = ldap_str2charray( p, "," );
356 if( ludp->lud_attrs == NULL ) {
358 ldap_free_urldesc( ludp );
359 return LDAP_URL_ERR_BADATTRS;
367 return LDAP_URL_SUCCESS;
370 /* scan forward for '?' that may marks end of scope */
372 q = strchr( p, '?' );
375 /* terminate the scope part */
380 /* parse the scope */
381 ldap_pvt_hex_unescape( p );
382 ludp->lud_scope = str2scope( p );
384 if( ludp->lud_scope == -1 ) {
386 ldap_free_urldesc( ludp );
387 return LDAP_URL_ERR_BADSCOPE;
395 return LDAP_URL_SUCCESS;
398 /* scan forward for '?' that may marks end of filter */
400 q = strchr( p, '?' );
403 /* terminate the filter part */
408 /* parse the filter */
409 ldap_pvt_hex_unescape( p );
414 ldap_free_urldesc( ludp );
415 return LDAP_URL_ERR_BADFILTER;
418 LDAP_FREE( ludp->lud_filter );
419 ludp->lud_filter = LDAP_STRDUP( p );
421 if( ludp->lud_filter == NULL ) {
423 ldap_free_urldesc( ludp );
424 return LDAP_URL_ERR_MEM;
432 return LDAP_URL_SUCCESS;
435 /* scan forward for '?' that may marks end of extensions */
437 q = strchr( p, '?' );
442 ldap_free_urldesc( ludp );
443 return LDAP_URL_ERR_BADURL;
446 /* parse the extensions */
447 ludp->lud_exts = ldap_str2charray( p, "," );
449 if( ludp->lud_exts == NULL ) {
451 ldap_free_urldesc( ludp );
452 return LDAP_URL_ERR_BADEXTS;
455 for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
456 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
460 /* must have 1 or more */
461 ldap_charray_free( ludp->lud_exts );
463 ldap_free_urldesc( ludp );
464 return LDAP_URL_ERR_BADEXTS;
470 return LDAP_URL_SUCCESS;
474 ldap_url_dup ( LDAPURLDesc *ludp )
478 if ( ludp == NULL ) {
482 dest = LDAP_MALLOC( sizeof(LDAPURLDesc) );
487 dest->lud_next = NULL;
489 if ( ludp->lud_host != NULL ) {
490 dest->lud_host = LDAP_STRDUP( ludp->lud_host );
491 if (dest->lud_host == NULL) {
492 ldap_free_urldesc(dest);
497 if ( ludp->lud_dn != NULL ) {
498 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
499 if (dest->lud_dn == NULL) {
500 ldap_free_urldesc(dest);
505 if ( ludp->lud_filter != NULL ) {
506 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
507 if (dest->lud_filter == NULL) {
508 ldap_free_urldesc(dest);
513 if ( ludp->lud_attrs != NULL ) {
514 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
515 if (dest->lud_attrs == NULL) {
516 ldap_free_urldesc(dest);
521 if ( ludp->lud_exts != NULL ) {
522 dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
523 if (dest->lud_exts == NULL) {
524 ldap_free_urldesc(dest);
533 ldap_url_duplist (LDAPURLDesc *ludlist)
535 LDAPURLDesc *dest, *tail, *ludp, *newludp;
539 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
540 newludp = ldap_url_dup(ludp);
541 if (newludp == NULL) {
542 ldap_free_urllist(dest);
548 tail->lud_next = newludp;
555 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
564 return LDAP_PARAM_ERROR;
566 urls = ldap_str2charray((char *)url, ", ");
568 return LDAP_NO_MEMORY;
570 /* count the URLs... */
571 for (i = 0; urls[i] != NULL; i++) ;
572 /* ...and put them in the "stack" backward */
574 rc = ldap_url_parse( urls[i], &ludp );
576 ldap_charray_free(urls);
577 ldap_free_urllist(*ludlist);
581 ludp->lud_next = *ludlist;
584 ldap_charray_free(urls);
589 ldap_url_parsehosts (LDAPURLDesc **ludlist, const char *hosts )
598 return LDAP_PARAM_ERROR;
600 specs = ldap_str2charray((char *)hosts, ", ");
602 return LDAP_NO_MEMORY;
604 /* count the URLs... */
605 for (i = 0; specs[i] != NULL; i++) ;
606 /* ...and put them in the "stack" backward */
608 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
610 ldap_charray_free(specs);
611 ldap_free_urllist(*ludlist);
613 return LDAP_NO_MEMORY;
615 ludp->lud_host = specs[i];
617 p = strchr(ludp->lud_host, ':');
620 ldap_pvt_hex_unescape(p);
621 ludp->lud_port = atoi(p);
623 ldap_pvt_hex_unescape(ludp->lud_host);
624 ludp->lud_protocol = LDAP_PROTO_TCP;
625 ludp->lud_properties = 0;
626 ludp->lud_next = *ludlist;
630 /* this should be an array of NULLs now */
631 ldap_charray_free(specs);
636 ldap_url_list2hosts (LDAPURLDesc *ludlist)
640 char *s, *p, buf[32]; /* big enough to hold a long decimal # (overkill) */
645 /* figure out how big the string is */
646 size = 1; /* nul-term */
647 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
648 size += strlen(ludp->lud_host) + 1; /* host and space */
649 if (ludp->lud_port != 0)
650 size += sprintf(buf, ":%d", ludp->lud_port);
652 s = LDAP_MALLOC(size);
657 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
658 strcpy(p, ludp->lud_host);
659 p += strlen(ludp->lud_host);
660 if (ludp->lud_port != 0)
661 p += sprintf(p, ":%d", ludp->lud_port);
665 p--; /* nuke that extra space */
671 ldap_url_list2urls (LDAPURLDesc *ludlist)
675 char *s, *p, buf[32]; /* big enough to hold a long decimal # (overkill) */
680 /* figure out how big the string is */
681 size = 1; /* nul-term */
682 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
683 size += strlen(ludp->lud_host) + 1 + sizeof("ldapis:///"); /* prefix, host, /, and space */
684 if (ludp->lud_port != 0)
685 size += sprintf(buf, ":%d", ludp->lud_port);
687 s = LDAP_MALLOC(size);
692 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
693 p += sprintf(p, "ldap%s://%s", (ludp->lud_properties & LDAP_URL_USE_SSL) ? "s" : "", ludp->lud_host);
694 if (ludp->lud_port != 0)
695 p += sprintf(p, ":%d", ludp->lud_port);
700 p--; /* nuke that extra space */
706 ldap_free_urllist( LDAPURLDesc *ludlist )
708 LDAPURLDesc *ludp, *next;
710 for (ludp = ludlist; ludp != NULL; ludp = next) {
711 next = ludp->lud_next;
712 ldap_free_urldesc(ludp);
717 ldap_free_urldesc( LDAPURLDesc *ludp )
719 if ( ludp == NULL ) {
723 if ( ludp->lud_host != NULL ) {
724 LDAP_FREE( ludp->lud_host );
727 if ( ludp->lud_dn != NULL ) {
728 LDAP_FREE( ludp->lud_dn );
731 if ( ludp->lud_filter != NULL ) {
732 LDAP_FREE( ludp->lud_filter);
735 if ( ludp->lud_attrs != NULL ) {
736 LDAP_VFREE( ludp->lud_attrs );
739 if ( ludp->lud_exts != NULL ) {
740 LDAP_VFREE( ludp->lud_exts );
749 ldap_url_search( LDAP *ld, LDAP_CONST char *url, int attrsonly )
756 if ( ldap_url_parse( url, &ludp ) != 0 ) {
757 ld->ld_errno = LDAP_PARAM_ERROR;
761 ber = ldap_build_search_req( ld, ludp->lud_dn, ludp->lud_scope,
762 ludp->lud_filter, ludp->lud_attrs, attrsonly, NULL, NULL,
768 bind.ri_request = LDAP_REQ_SEARCH;
769 bind.ri_msgid = ld->ld_msgid;
770 bind.ri_url = (char *)url;
771 err = ldap_send_server_request(
772 ld, ber, ld->ld_msgid, NULL,
773 (ludp->lud_host != NULL || ludp->lud_port != 0)
778 ldap_free_urldesc( ludp );
784 ldap_url_search_st( LDAP *ld, LDAP_CONST char *url, int attrsonly,
785 struct timeval *timeout, LDAPMessage **res )
789 if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
790 return( ld->ld_errno );
793 if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 ) {
794 return( ld->ld_errno );
797 if ( ld->ld_errno == LDAP_TIMEOUT ) {
798 (void) ldap_abandon( ld, msgid );
799 ld->ld_errno = LDAP_TIMEOUT;
800 return( ld->ld_errno );
803 return( ldap_result2error( ld, *res, 0 ));
809 LDAP *ld, LDAP_CONST char *url, int attrsonly, LDAPMessage **res )
813 if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
814 return( ld->ld_errno );
817 if ( ldap_result( ld, msgid, 1, (struct timeval *)NULL, res ) == -1 ) {
818 return( ld->ld_errno );
821 return( ldap_result2error( ld, *res, 0 ));
826 ldap_pvt_hex_unescape( char *s )
829 * Remove URL hex escapes from s... done in place. The basic concept for
830 * this routine is borrowed from the WWW library HTUnEscape() routine.
834 for ( p = s; *s != '\0'; ++s ) {
836 if ( *++s != '\0' ) {
837 *p = ldap_pvt_unhex( *s ) << 4;
839 if ( *++s != '\0' ) {
840 *p++ += ldap_pvt_unhex( *s );
852 ldap_pvt_unhex( int c )
854 return( c >= '0' && c <= '9' ? c - '0'
855 : c >= 'A' && c <= 'F' ? c - 'A' + 10