1 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /* Portions Copyright 1998-2002 The OpenLDAP Foundation
18 * All rights reserved.
20 * Redistribution and use in source and binary forms, with or without
21 * modification, are permitted only as authorized by the OpenLDAP
22 * Public License. A copy of this license is available at
23 * http://www.OpenLDAP.org/license.html or in file LICENSE in the
24 * top-level directory of the distribution.
26 * OpenLDAP is a registered trademark of the OpenLDAP Foundation.
28 * Individual files and/or contributed packages may be copyright by
29 * other parties and subject to additional restrictions.
31 * This work is derived from the University of Michigan LDAP v3.3
32 * distribution. Information concerning this software is available
33 * at: http://www.umich.edu/~dirsvcs/ldap/
35 * This work also contains materials derived from public sources.
37 * Additional information about OpenLDAP can be obtained at:
38 * http://www.openldap.org/
42 * Portions Copyright (c) 1992-1996 Regents of the University of Michigan.
43 * All rights reserved.
45 * Redistribution and use in source and binary forms are permitted
46 * provided that this notice is preserved and that due credit is given
47 * to the University of Michigan at Ann Arbor. The name of the University
48 * may not be used to endorse or promote products derived from this
49 * software without specific prior written permission. This software
50 * is provided ``as is'' without express or implied warranty.
53 /* apr_ldap_url.c -- LDAP URL (RFC 2255) related routines
55 * Win32 and perhaps other non-OpenLDAP based ldap libraries may be
56 * missing ldap_url_* APIs. We focus here on the one significant
57 * aspect, which is parsing. We have [for the time being] omitted
58 * the ldap_url_search APIs.
60 * LDAP URLs look like this:
61 * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
64 * attributes is a comma separated list
65 * scope is one of these three strings: base one sub (default=base)
66 * filter is an string-represented filter as in RFC 2254
68 * e.g., ldap://host:port/dc=com?o,cn?base?o=openldap?extension
70 * Tolerates URLs that look like: <ldapurl> and <URL:ldapurl>
77 #if !APR_HAS_LDAP_URL_PARSE
79 #include "apr_general.h"
80 #include "apr_strings.h"
83 #define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
86 #define LDAP_URL_PREFIX "ldap://"
87 #define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1)
88 #define LDAPS_URL_PREFIX "ldaps://"
89 #define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1)
90 #define LDAPI_URL_PREFIX "ldapi://"
91 #define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1)
92 #define LDAP_URL_URLCOLON "URL:"
93 #define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1)
95 #define LDAP_STRDUP(x) strdup(x)
96 #define LDAP_CALLOC(n, s) calloc(n, s)
97 #define LDAP_MALLOC(n) malloc(n)
98 #define LDAP_REALLOC(x, n) realloc(x, n)
99 #define LDAP_FREE(x) free(x)
100 #define LDAP_VFREE(a) ldap_charray_free(a)
102 #define ldap_utf8_strchr(x, s) strchr(x, *s)
103 #define ldap_utf8_strtok(x, s, l) apr_strtok(x, s, l)
105 /* local functions */
106 static const char* skip_url_prefix(const char *url, int *enclosedp,
107 const char **scheme);
109 static void ldap_pvt_hex_unescape(char *s);
111 static int ldap_pvt_unhex(int c);
113 static void ldap_charray_free(char **a);
115 static char **ldap_str2charray(const char *str, const char *brkstr);
117 APU_DECLARE(int) apr_ldap_is_ldap_url(const char *url)
126 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
133 APU_DECLARE(int) apr_ldap_is_ldaps_url(const char *url)
142 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
146 return strcmp(scheme, "ldaps") == 0;
149 APU_DECLARE(int) apr_ldap_is_ldapi_url(const char *url)
158 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
162 return strcmp(scheme, "ldapi") == 0;
165 static const char *skip_url_prefix(const char *url, int *enclosedp,
169 * return non-zero if this looks like a LDAP URL; zero if not
170 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
180 /* skip leading '<' (if any) */
188 /* skip leading "URL:" (if any) */
189 if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) {
190 p += LDAP_URL_URLCOLON_LEN;
193 /* check for "ldap://" prefix */
194 if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
195 /* skip over "ldap://" prefix and return success */
196 p += LDAP_URL_PREFIX_LEN;
201 /* check for "ldaps://" prefix */
202 if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
203 /* skip over "ldaps://" prefix and return success */
204 p += LDAPS_URL_PREFIX_LEN;
209 /* check for "ldapi://" prefix */
210 if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
211 /* skip over "ldapi://" prefix and return success */
212 p += LDAPI_URL_PREFIX_LEN;
221 static int str2scope(const char *p)
223 if ( strcasecmp( p, "one" ) == 0 ) {
224 return LDAP_SCOPE_ONELEVEL;
226 } else if ( strcasecmp( p, "onetree" ) == 0 ) {
227 return LDAP_SCOPE_ONELEVEL;
229 } else if ( strcasecmp( p, "base" ) == 0 ) {
230 return LDAP_SCOPE_BASE;
232 } else if ( strcasecmp( p, "sub" ) == 0 ) {
233 return LDAP_SCOPE_SUBTREE;
235 } else if ( strcasecmp( p, "subtree" ) == 0 ) {
236 return LDAP_SCOPE_SUBTREE;
243 static int ldap_url_parse_ext(const char *url_in,
244 apr_ldap_url_desc_t **ludpp)
247 * Pick apart the pieces of an LDAP URL.
249 apr_ldap_url_desc_t *ludp;
252 const char *scheme = NULL;
256 if( url_in == NULL || ludpp == NULL ) {
257 return LDAP_URL_ERR_PARAM;
260 *ludpp = NULL; /* pessimistic */
262 url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
264 if ( url_tmp == NULL ) {
265 return LDAP_URL_ERR_BADSCHEME;
268 /* make working copy of the remainder of the URL */
269 url = LDAP_STRDUP( url_tmp );
271 return LDAP_URL_ERR_MEM;
275 p = &url[strlen(url)-1];
279 return LDAP_URL_ERR_BADENCLOSURE;
285 /* allocate return struct */
286 ludp = (apr_ldap_url_desc_t *)LDAP_CALLOC( 1, sizeof( apr_ldap_url_desc_t ));
288 if ( ludp == NULL ) {
290 return LDAP_URL_ERR_MEM;
293 ludp->lud_next = NULL;
294 ludp->lud_host = NULL;
295 ludp->lud_port = LDAP_PORT;
297 ludp->lud_attrs = NULL;
298 ludp->lud_filter = NULL;
299 ludp->lud_scope = -1;
300 ludp->lud_filter = NULL;
301 ludp->lud_exts = NULL;
303 ludp->lud_scheme = LDAP_STRDUP( scheme );
305 if ( ludp->lud_scheme == NULL ) {
307 apr_ldap_free_urldesc( ludp );
308 return LDAP_URL_ERR_MEM;
311 if( strcasecmp( ludp->lud_scheme, "ldaps" ) == 0 ) {
312 ludp->lud_port = LDAPS_PORT;
315 /* scan forward for '/' that marks end of hostport and begin. of dn */
316 p = strchr( url, '/' );
319 /* terminate hostport; point to start of dn */
323 /* IPv6 syntax with [ip address]:port */
325 r = strchr( url, ']' );
328 apr_ldap_free_urldesc( ludp );
329 return LDAP_URL_ERR_BADURL;
332 q = strchr( r, ':' );
334 q = strchr( url, ':' );
339 ldap_pvt_hex_unescape( q );
343 apr_ldap_free_urldesc( ludp );
344 return LDAP_URL_ERR_BADURL;
347 ludp->lud_port = atoi( q );
350 ldap_pvt_hex_unescape( url );
352 /* If [ip address]:port syntax, url is [ip and we skip the [ */
353 ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) );
355 if( ludp->lud_host == NULL ) {
357 apr_ldap_free_urldesc( ludp );
358 return LDAP_URL_ERR_MEM;
362 * Kludge. ldap://111.222.333.444:389??cn=abc,o=company
364 * On early Novell releases, search references/referrals were returned
365 * in this format, i.e., the dn was kind of in the scope position,
366 * but the required slash is missing. The whole thing is illegal syntax,
367 * but we need to account for it. Fortunately it can't be confused with
370 if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
372 /* ? immediately followed by question */
377 ldap_pvt_hex_unescape( q );
378 ludp->lud_dn = LDAP_STRDUP( q );
380 ludp->lud_dn = LDAP_STRDUP( "" );
383 if( ludp->lud_dn == NULL ) {
385 apr_ldap_free_urldesc( ludp );
386 return LDAP_URL_ERR_MEM;
394 return LDAP_URL_SUCCESS;
397 /* scan forward for '?' that may marks end of dn */
398 q = strchr( p, '?' );
401 /* terminate dn part */
407 ldap_pvt_hex_unescape( p );
408 ludp->lud_dn = LDAP_STRDUP( p );
410 ludp->lud_dn = LDAP_STRDUP( "" );
413 if( ludp->lud_dn == NULL ) {
415 apr_ldap_free_urldesc( ludp );
416 return LDAP_URL_ERR_MEM;
423 return LDAP_URL_SUCCESS;
426 /* scan forward for '?' that may marks end of attributes */
428 q = strchr( p, '?' );
431 /* terminate attributes part */
436 /* parse attributes */
437 ldap_pvt_hex_unescape( p );
438 ludp->lud_attrs = ldap_str2charray( p, "," );
440 if( ludp->lud_attrs == NULL ) {
442 apr_ldap_free_urldesc( ludp );
443 return LDAP_URL_ERR_BADATTRS;
451 return LDAP_URL_SUCCESS;
454 /* scan forward for '?' that may marks end of scope */
456 q = strchr( p, '?' );
459 /* terminate the scope part */
464 /* parse the scope */
465 ldap_pvt_hex_unescape( p );
466 ludp->lud_scope = str2scope( p );
468 if( ludp->lud_scope == -1 ) {
470 apr_ldap_free_urldesc( ludp );
471 return LDAP_URL_ERR_BADSCOPE;
479 return LDAP_URL_SUCCESS;
482 /* scan forward for '?' that may marks end of filter */
484 q = strchr( p, '?' );
487 /* terminate the filter part */
492 /* parse the filter */
493 ldap_pvt_hex_unescape( p );
498 apr_ldap_free_urldesc( ludp );
499 return LDAP_URL_ERR_BADFILTER;
502 LDAP_FREE( ludp->lud_filter );
503 ludp->lud_filter = LDAP_STRDUP( p );
505 if( ludp->lud_filter == NULL ) {
507 apr_ldap_free_urldesc( ludp );
508 return LDAP_URL_ERR_MEM;
516 return LDAP_URL_SUCCESS;
519 /* scan forward for '?' that may marks end of extensions */
521 q = strchr( p, '?' );
526 apr_ldap_free_urldesc( ludp );
527 return LDAP_URL_ERR_BADURL;
530 /* parse the extensions */
531 ludp->lud_exts = ldap_str2charray( p, "," );
533 if( ludp->lud_exts == NULL ) {
535 apr_ldap_free_urldesc( ludp );
536 return LDAP_URL_ERR_BADEXTS;
539 for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
540 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
542 if( *ludp->lud_exts[i] == '!' ) {
543 /* count the number of critical extensions */
544 ludp->lud_crit_exts++;
549 /* must have 1 or more */
551 apr_ldap_free_urldesc( ludp );
552 return LDAP_URL_ERR_BADEXTS;
558 return LDAP_URL_SUCCESS;
561 APU_DECLARE(int) apr_ldap_url_parse(const char *url_in,
562 apr_ldap_url_desc_t **ludpp)
564 int rc = ldap_url_parse_ext( url_in, ludpp );
566 if( rc != LDAP_URL_SUCCESS ) {
570 if ((*ludpp)->lud_scope == -1) {
571 (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
574 if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
575 LDAP_FREE( (*ludpp)->lud_host );
576 (*ludpp)->lud_host = NULL;
582 APU_DECLARE(void) apr_ldap_free_urldesc(apr_ldap_url_desc_t *ludp)
584 if ( ludp == NULL ) {
588 if ( ludp->lud_scheme != NULL ) {
589 LDAP_FREE( ludp->lud_scheme );
592 if ( ludp->lud_host != NULL ) {
593 LDAP_FREE( ludp->lud_host );
596 if ( ludp->lud_dn != NULL ) {
597 LDAP_FREE( ludp->lud_dn );
600 if ( ludp->lud_filter != NULL ) {
601 LDAP_FREE( ludp->lud_filter);
604 if ( ludp->lud_attrs != NULL ) {
605 LDAP_VFREE( ludp->lud_attrs );
608 if ( ludp->lud_exts != NULL ) {
609 LDAP_VFREE( ludp->lud_exts );
616 static void ldap_pvt_hex_unescape(char *s)
619 * Remove URL hex escapes from s... done in place. The basic concept for
620 * this routine is borrowed from the WWW library HTUnEscape() routine.
624 for ( p = s; *s != '\0'; ++s ) {
626 if ( *++s == '\0' ) {
629 *p = ldap_pvt_unhex( *s ) << 4;
630 if ( *++s == '\0' ) {
633 *p++ += ldap_pvt_unhex( *s );
643 static int ldap_pvt_unhex(int c)
645 return( c >= '0' && c <= '9' ? c - '0'
646 : c >= 'A' && c <= 'F' ? c - 'A' + 10
651 static void ldap_charray_free(char **a)
659 for ( p = a; *p != NULL; p++ ) {
665 LDAP_FREE( (char *) a );
668 static char **ldap_str2charray(const char *str_in, const char *brkstr)
675 /* protect the input string from strtok */
676 str = LDAP_STRDUP( str_in );
682 for ( s = str; *s; s++ ) {
683 if ( ldap_utf8_strchr( brkstr, s ) != NULL ) {
688 res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) );
697 for ( s = ldap_utf8_strtok( str, brkstr, &lasts );
699 s = ldap_utf8_strtok( NULL, brkstr, &lasts ) )
701 res[i] = LDAP_STRDUP( s );
704 for( --i ; i >= 0 ; i-- ) {
721 #endif /* !APR_HAS_LDAP_URL_PARSE */
723 #endif /* APR_HAS_LDAP */