2 * Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
6 #pragma ident "%Z%%M% %I% %E% SMI"
10 * The contents of this file are subject to the Netscape Public
11 * License Version 1.1 (the "License"); you may not use this file
12 * except in compliance with the License. You may obtain a copy of
13 * the License at http://www.mozilla.org/NPL/
15 * Software distributed under the License is distributed on an "AS
16 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
17 * implied. See the License for the specific language governing
18 * rights and limitations under the License.
20 * The Original Code is Mozilla Communicator client code, released
23 * The Initial Developer of the Original Code is Netscape
24 * Communications Corporation. Portions created by Netscape are
25 * Copyright (C) 1998-1999 Netscape Communications Corporation. All
31 * Copyright (c) 1996 Regents of the University of Michigan.
32 * All rights reserved.
35 /* LIBLDAP url.c -- LDAP URL related routines
37 * LDAP URLs look like this:
38 * l d a p : / / hostport / dn [ ? attributes [ ? scope [ ? filter ] ] ]
41 * attributes is a comma separated list
42 * scope is one of these three strings: base one sub (default=base)
43 * filter is an string-represented filter as in RFC 1558
45 * e.g., ldap://ldap.itd.umich.edu/c=US?o,description?one?o=umich
47 * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
51 static char copyright
[] = "@(#) Copyright (c) 1996 Regents of the University of Michigan.\nAll rights reserved.\n";
57 static int skip_url_prefix( const char **urlp
, int *enclosedp
, int *securep
);
62 ldap_is_ldap_url( const char *url
)
67 && skip_url_prefix( &url
, &enclosed
, &secure
));
72 skip_url_prefix( const char **urlp
, int *enclosedp
, int *securep
)
75 * return non-zero if this looks like a LDAP URL; zero if not
76 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
77 * The data that *urlp points to is not changed by this function.
79 if ( *urlp
== NULL
) {
83 /* skip leading '<' (if any) */
84 if ( **urlp
== '<' ) {
91 /* skip leading "URL:" (if any) */
92 if ( strlen( *urlp
) >= LDAP_URL_URLCOLON_LEN
&& strncasecmp(
93 *urlp
, LDAP_URL_URLCOLON
, LDAP_URL_URLCOLON_LEN
) == 0 ) {
94 *urlp
+= LDAP_URL_URLCOLON_LEN
;
97 /* check for an "ldap://" prefix */
98 if ( strlen( *urlp
) >= LDAP_URL_PREFIX_LEN
&& strncasecmp( *urlp
,
99 LDAP_URL_PREFIX
, LDAP_URL_PREFIX_LEN
) == 0 ) {
100 /* skip over URL prefix and return success */
101 *urlp
+= LDAP_URL_PREFIX_LEN
;
106 /* check for an "ldaps://" prefix */
107 if ( strlen( *urlp
) >= LDAPS_URL_PREFIX_LEN
&& strncasecmp( *urlp
,
108 LDAPS_URL_PREFIX
, LDAPS_URL_PREFIX_LEN
) == 0 ) {
109 /* skip over URL prefix and return success */
110 *urlp
+= LDAPS_URL_PREFIX_LEN
;
115 return( 0 ); /* not an LDAP URL */
121 ldap_url_parse( const char *url
, LDAPURLDesc
**ludpp
)
124 * Pick apart the pieces of an LDAP URL.
128 if (( rc
= nsldapi_url_parse( url
, ludpp
, 1 )) == 0 ) {
129 if ( (*ludpp
)->lud_scope
== -1 ) {
130 (*ludpp
)->lud_scope
= LDAP_SCOPE_BASE
;
132 if ( (*ludpp
)->lud_filter
== NULL
) {
133 (*ludpp
)->lud_filter
= "(objectclass=*)";
135 if ( *((*ludpp
)->lud_dn
) == '\0' ) {
136 (*ludpp
)->lud_dn
= NULL
;
143 /* same as ldap_url_parse(), but dn is not require */
146 ldap_url_parse_nodn(const char *url
, LDAPURLDesc
**ludpp
)
149 * Pick apart the pieces of an LDAP URL.
153 if ((rc
= nsldapi_url_parse(url
, ludpp
, 0)) == 0) {
154 if ((*ludpp
)->lud_scope
== -1) {
155 (*ludpp
)->lud_scope
= LDAP_SCOPE_BASE
;
157 if ((*ludpp
)->lud_filter
== NULL
) {
158 (*ludpp
)->lud_filter
= "(objectclass=*)";
160 if ((*ludpp
)->lud_dn
&& *((*ludpp
)->lud_dn
) == '\0') {
161 (*ludpp
)->lud_dn
= NULL
;
170 * like ldap_url_parse() with a few exceptions:
171 * 1) if dn_required is zero, a missing DN does not generate an error
172 * (we just leave the lud_dn field NULL)
173 * 2) no defaults are set for lud_scope and lud_filter (they are set to -1
174 * and NULL respectively if no SCOPE or FILTER are present in the URL).
175 * 3) when there is a zero-length DN in a URL we do not set lud_dn to NULL.
176 * 4) if an LDAPv3 URL extensions are included,
179 nsldapi_url_parse( const char *url
, LDAPURLDesc
**ludpp
, int dn_required
)
183 char *urlcopy
, *attrs
, *scope
, *extensions
= NULL
, *p
, *q
;
184 int enclosed
, secure
, i
, nattrs
, at_start
;
186 LDAPDebug( LDAP_DEBUG_TRACE
, "nsldapi_url_parse(%s)\n", url
, 0, 0 );
188 if ( url
== NULL
|| ludpp
== NULL
) {
189 return( LDAP_URL_ERR_PARAM
);
192 *ludpp
= NULL
; /* pessimistic */
194 if ( !skip_url_prefix( &url
, &enclosed
, &secure
)) {
195 return( LDAP_URL_ERR_NOTLDAP
);
198 /* allocate return struct */
199 if (( ludp
= (LDAPURLDesc
*)NSLDAPI_CALLOC( 1, sizeof( LDAPURLDesc
)))
200 == NULLLDAPURLDESC
) {
201 return( LDAP_URL_ERR_MEM
);
205 ludp
->lud_options
|= LDAP_URL_OPT_SECURE
;
208 /* make working copy of the remainder of the URL */
209 if (( urlcopy
= nsldapi_strdup( url
)) == NULL
) {
210 ldap_free_urldesc( ludp
);
211 return( LDAP_URL_ERR_MEM
);
214 if ( enclosed
&& *((p
= urlcopy
+ strlen( urlcopy
) - 1)) == '>' ) {
218 /* initialize scope and filter */
219 ludp
->lud_scope
= -1;
220 ludp
->lud_filter
= NULL
;
222 /* lud_string is the only malloc'd string space we use */
223 ludp
->lud_string
= urlcopy
;
225 /* scan forward for '/' that marks end of hostport and begin. of dn */
226 if (( ludp
->lud_dn
= strchr( urlcopy
, '/' )) == NULL
) {
228 ldap_free_urldesc( ludp
);
229 return( LDAP_URL_ERR_NODN
);
232 /* terminate hostport; point to start of dn */
233 *ludp
->lud_dn
++ = '\0';
237 if ( *urlcopy
== '\0' ) {
238 ludp
->lud_host
= NULL
;
240 ludp
->lud_host
= urlcopy
;
241 nsldapi_hex_unescape( ludp
->lud_host
);
244 * Locate and strip off optional port number (:#) in host
247 * If more than one space-separated host is listed, we only
248 * look for a port number within the right-most one since
249 * ldap_init() will handle host parameters that look like
252 if (( p
= strrchr( ludp
->lud_host
, ' ' )) == NULL
) {
257 if ( *p
== '[' && ( q
= strchr( p
, ']' )) != NULL
) {
258 /* square brackets present -- skip past them */
261 if (( p
= strchr( p
, ':' )) != NULL
) {
263 ludp
->lud_port
= atoi( p
);
264 if ( *ludp
->lud_host
== '\0' ) {
266 * no hostname and a port: invalid hostcode
267 * according to RFC 1738
269 ldap_free_urldesc(ludp
);
270 return (LDAP_URL_ERR_HOSTPORT
);
275 /* scan for '?' that marks end of dn and beginning of attributes */
277 if ( ludp
->lud_dn
!= NULL
&&
278 ( attrs
= strchr( ludp
->lud_dn
, '?' )) != NULL
) {
279 /* terminate dn; point to start of attrs. */
282 /* scan for '?' that marks end of attrs and begin. of scope */
283 if (( p
= strchr( attrs
, '?' )) != NULL
) {
285 * terminate attrs; point to start of scope and scan for
286 * '?' that marks end of scope and begin. of filter
291 if (( p
= strchr( scope
, '?' )) != NULL
) {
292 /* terminate scope; point to start of filter */
295 ludp
->lud_filter
= p
;
297 * scan for the '?' that marks the end
298 * of the filter and the start of any
301 if (( p
= strchr( ludp
->lud_filter
, '?' ))
303 *p
++ = '\0'; /* term. filter */
306 if ( *ludp
->lud_filter
== '\0' ) {
307 ludp
->lud_filter
= NULL
;
309 nsldapi_hex_unescape( ludp
->lud_filter
);
315 if ( strcasecmp( scope
, "one" ) == 0 ) {
316 ludp
->lud_scope
= LDAP_SCOPE_ONELEVEL
;
317 } else if ( strcasecmp( scope
, "base" ) == 0 ) {
318 ludp
->lud_scope
= LDAP_SCOPE_BASE
;
319 } else if ( strcasecmp( scope
, "sub" ) == 0 ) {
320 ludp
->lud_scope
= LDAP_SCOPE_SUBTREE
;
321 } else if ( *scope
!= '\0' ) {
322 ldap_free_urldesc( ludp
);
323 return( LDAP_URL_ERR_BADSCOPE
);
328 if ( ludp
->lud_dn
!= NULL
) {
329 nsldapi_hex_unescape( ludp
->lud_dn
);
333 * if attrs list was included, turn it into a null-terminated array
335 if ( attrs
!= NULL
&& *attrs
!= '\0' ) {
336 nsldapi_hex_unescape( attrs
);
337 for ( nattrs
= 1, p
= attrs
; *p
!= '\0'; ++p
) {
343 if (( ludp
->lud_attrs
= (char **)NSLDAPI_CALLOC( nattrs
+ 1,
344 sizeof( char * ))) == NULL
) {
345 ldap_free_urldesc( ludp
);
346 return( LDAP_URL_ERR_MEM
);
349 for ( i
= 0, p
= attrs
; i
< nattrs
; ++i
) {
350 ludp
->lud_attrs
[ i
] = p
;
351 if (( p
= strchr( p
, ',' )) != NULL
) {
354 nsldapi_hex_unescape( ludp
->lud_attrs
[ i
] );
358 /* if extensions list was included, check for critical ones */
359 if ( extensions
!= NULL
&& *extensions
!= '\0' ) {
360 /* Note: at present, we do not recognize ANY extensions */
362 for ( p
= extensions
; *p
!= '\0'; ++p
) {
364 if ( *p
== '!' ) { /* critical extension */
365 ldap_free_urldesc( ludp
);
366 /* this is what iplanet did *
367 return( LDAP_URL_UNRECOGNIZED_CRITICAL_EXTENSION );
368 * and this is what we do */
369 return( LDAP_URL_ERR_PARAM
);
372 } else if ( *p
== ',' ) {
387 ldap_free_urldesc( LDAPURLDesc
*ludp
)
389 if ( ludp
!= NULLLDAPURLDESC
) {
390 if ( ludp
->lud_string
!= NULL
) {
391 NSLDAPI_FREE( ludp
->lud_string
);
393 if ( ludp
->lud_attrs
!= NULL
) {
394 NSLDAPI_FREE( ludp
->lud_attrs
);
396 NSLDAPI_FREE( ludp
);
403 ldap_url_search( LDAP
*ld
, const char *url
, int attrsonly
)
411 if ( !NSLDAPI_VALID_LDAP_POINTER( ld
)) {
412 return( -1 ); /* punt */
415 if ( ldap_url_parse( url
, &ludp
) != 0 ) {
416 LDAP_SET_LDERRNO( ld
, LDAP_PARAM_ERROR
, NULL
, NULL
);
420 LDAP_MUTEX_LOCK( ld
, LDAP_MSGID_LOCK
);
421 msgid
= ++ld
->ld_msgid
;
422 LDAP_MUTEX_UNLOCK( ld
, LDAP_MSGID_LOCK
);
424 if ( nsldapi_build_search_req( ld
, ludp
->lud_dn
, ludp
->lud_scope
,
425 ludp
->lud_filter
, ludp
->lud_attrs
, attrsonly
, NULL
, NULL
,
426 -1, -1, msgid
, &ber
) != LDAP_SUCCESS
) {
432 if ( ludp
->lud_host
== NULL
) {
433 host
= ld
->ld_defhost
;
435 host
= ludp
->lud_host
;
438 if (( srv
= (LDAPServer
*)NSLDAPI_CALLOC( 1, sizeof( LDAPServer
)))
439 == NULL
|| ( host
!= NULL
&&
440 ( srv
->lsrv_host
= nsldapi_strdup( host
)) == NULL
)) {
444 LDAP_SET_LDERRNO( ld
, LDAP_NO_MEMORY
, NULL
, NULL
);
447 if ( ludp
->lud_port
!= 0 ) {
448 /* URL includes a port - use it */
449 srv
->lsrv_port
= ludp
->lud_port
;
450 } else if ( ludp
->lud_host
== NULL
) {
451 /* URL has no port or host - use port from ld */
452 srv
->lsrv_port
= ld
->ld_defport
;
453 } else if (( ludp
->lud_options
& LDAP_URL_OPT_SECURE
) == 0 ) {
454 /* ldap URL has a host but no port - use std. port */
455 srv
->lsrv_port
= LDAP_PORT
;
457 /* ldaps URL has a host but no port - use std. port */
458 srv
->lsrv_port
= LDAPS_PORT
;
462 if (( ludp
->lud_options
& LDAP_URL_OPT_SECURE
) != 0 ) {
463 srv
->lsrv_options
|= LDAP_SRV_OPT_SECURE
;
469 err
= nsldapi_send_server_request( ld
, ber
, msgid
, NULL
, srv
,
473 ldap_free_urldesc( ludp
);
480 ldap_url_search_st( LDAP
*ld
, const char *url
, int attrsonly
,
481 struct timeval
*timeout
, LDAPMessage
**res
)
486 * It is an error to pass in a zero'd timeval.
488 if ( timeout
!= NULL
&& timeout
->tv_sec
== 0 &&
489 timeout
->tv_usec
== 0 ) {
491 LDAP_SET_LDERRNO( ld
, LDAP_PARAM_ERROR
, NULL
, NULL
);
496 return( LDAP_PARAM_ERROR
);
499 if (( msgid
= ldap_url_search( ld
, url
, attrsonly
)) == -1 ) {
500 return( LDAP_GET_LDERRNO( ld
, NULL
, NULL
) );
503 if ( ldap_result( ld
, msgid
, 1, timeout
, res
) == -1 ) {
504 return( LDAP_GET_LDERRNO( ld
, NULL
, NULL
) );
507 if ( LDAP_GET_LDERRNO( ld
, NULL
, NULL
) == LDAP_TIMEOUT
) {
508 (void) ldap_abandon( ld
, msgid
);
509 LDAP_SET_LDERRNO( ld
, LDAP_TIMEOUT
, NULL
, NULL
);
510 return( LDAP_TIMEOUT
);
513 return( ldap_result2error( ld
, *res
, 0 ));
519 ldap_url_search_s( LDAP
*ld
, const char *url
, int attrsonly
, LDAPMessage
**res
)
523 if (( msgid
= ldap_url_search( ld
, url
, attrsonly
)) == -1 ) {
524 return( LDAP_GET_LDERRNO( ld
, NULL
, NULL
) );
527 if ( ldap_result( ld
, msgid
, 1, NULL
, res
) == -1 ) {
528 return( LDAP_GET_LDERRNO( ld
, NULL
, NULL
) );
531 return( ldap_result2error( ld
, *res
, 0 ));
536 * Locate the LDAP URL associated with a DNS domain name.
538 * The supplied DNS domain name is converted into a distinguished
539 * name. The directory entry specified by that distinguished name
540 * is searched for a labeledURI attribute. If successful then the
541 * LDAP URL is returned. If unsuccessful then that entry's parent
542 * is searched and so on until the target distinguished name is
543 * reduced to only two nameparts.
545 * For example, if 'ny.eng.wiz.com' is the DNS domain then the
546 * following entries are searched until one succeeds:
547 * dc=ny,dc=eng,dc=wiz,dc=com
548 * dc=eng,dc=wiz,dc=com
551 * If dns_name is NULL then the environment variable LOCALDOMAIN is used.
552 * If attrs is not NULL then it is appended to the URL's attribute list.
553 * If scope is not NULL then it overrides the URL's scope.
554 * If filter is not NULL then it is merged with the URL's filter.
556 * If an error is encountered then zero is returned, otherwise a string
557 * URL is returned. The caller should free the returned string if it is
573 LDAPURLDesc
*urldesc
;
576 size_t attrs_len
= 0;
577 size_t scope_len
= 0;
578 size_t filter_len
= 0;
584 dns_name
= (char *)getenv("LOCALDOMAIN");
587 if ((ld
== NULL
) || ((dn
= ldap_dns_to_dn(dns_name
, &nameparts
)) ==
591 if ((url
= ldap_dn_to_url(ld
, dn
, nameparts
)) == NULL
) {
597 /* merge filter and/or scope and/or attributes with URL */
598 if (attrs
|| scope
|| filter
) {
601 attrs_len
= strlen(attrs
) + 2; /* for comma and NULL */
604 scope_len
= strlen(scope
) + 1; /* for NULL */
607 filter_len
= strlen(filter
) + 4;
608 /* for ampersand, parentheses and NULL */
610 if (ldap_is_ldap_url(url
)) {
612 if ((url2
= (char *)malloc(attrs_len
+ scope_len
+
613 filter_len
+ strlen(url
) + 1)) == NULL
) {
619 /* copy URL scheme, hostname, port number and DN */
620 while (*cp
&& (*cp
!= '?')) {
624 /* handle URL attributes */
626 if (*cp
== '?') { /* test first '?' */
627 *cp2
++ = *cp
++; /* copy first '?' */
629 if (*cp
== '?') { /* test second '?' */
631 /* insert supplied attributes */
642 /* copy URL attributes */
643 while (*cp
&& (*cp
!= '?')) {
647 /* append supplied attributes */
657 /* append supplied attributes */
668 /* handle URL scope */
670 if (*cp
== '?') { /* test second '?' */
671 *cp2
++ = *cp
++; /* copy second '?' */
673 if (*cp
== '?') { /* test third '?' */
675 /* insert supplied scope */
687 /* skip over URL scope */
688 while (*cp
&& (*cp
!= '?')) {
691 /* insert supplied scope */
698 while (*cp
&& (*cp
!= '?')) {
705 /* append supplied scope */
719 /* handle URL filter */
721 if (*cp
== '?') { /* test third '?' */
722 *cp2
++ = *cp
++; /* copy third '?' */
726 /* merge URL and supplied filters */
730 /* copy URL filter */
734 /* append supplied filter */
741 /* copy URL filter */
748 /* append supplied filter */
768 return (0); /* not an LDAP URL */
775 * Locate the LDAP URL associated with a distinguished name.
777 * The number of nameparts in the supplied distinguished name must be
778 * provided. The specified directory entry is searched for a labeledURI
779 * attribute. If successful then the LDAP URL is returned. If unsuccessful
780 * then that entry's parent is searched and so on until the target
781 * distinguished name is reduced to only two nameparts.
783 * For example, if 'l=ny,ou=eng,o=wiz,c=us' is the distinguished name
784 * then the following entries are searched until one succeeds:
785 * l=ny,ou=eng,o=wiz,c=us
789 * If an error is encountered then zero is returned, otherwise a string
790 * URL is returned. The caller should free the returned string if it is
803 char *attrs
[2] = {"labeledURI", 0};
804 LDAPMessage
*res
, *e
;
808 * Search for a URL in the named entry or its parent entry.
809 * Continue until only 2 nameparts remain.
811 while (dn
&& (nameparts
> 1) && (! url
)) {
813 /* search for the labeledURI attribute */
814 if (ldap_search_s(ld
, dn
, LDAP_SCOPE_BASE
,
815 "(objectClass=*)", attrs
, 0, &res
) == LDAP_SUCCESS
) {
817 /* locate the first entry returned */
818 if ((e
= ldap_first_entry(ld
, res
)) != NULL
) {
820 /* locate the labeledURI attribute */
822 ldap_get_values(ld
, e
, "labeledURI")) !=
825 /* copy the attribute value */
826 if ((url
= strdup((char *)vals
[0])) !=
828 ldap_value_free(vals
);
832 /* free the search results */
839 /* advance along the DN by one namepart */
840 if (next_dn
= strchr(dn
, ',')) {
851 #endif /* _SOLARIS_SDK */