1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. 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>
74 #include "apr_pools.h"
75 #include "apr_general.h"
76 #include "apr_strings.h"
86 #define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
89 #define APR_LDAP_URL_PREFIX "ldap://"
90 #define APR_LDAP_URL_PREFIX_LEN (sizeof(APR_LDAP_URL_PREFIX)-1)
91 #define APR_LDAPS_URL_PREFIX "ldaps://"
92 #define APR_LDAPS_URL_PREFIX_LEN (sizeof(APR_LDAPS_URL_PREFIX)-1)
93 #define APR_LDAPI_URL_PREFIX "ldapi://"
94 #define APR_LDAPI_URL_PREFIX_LEN (sizeof(APR_LDAPI_URL_PREFIX)-1)
95 #define APR_LDAP_URL_URLCOLON "URL:"
96 #define APR_LDAP_URL_URLCOLON_LEN (sizeof(APR_LDAP_URL_URLCOLON)-1)
100 static const char* skip_url_prefix(const char *url
,
102 const char **scheme
);
104 static void apr_ldap_pvt_hex_unescape(char *s
);
106 static int apr_ldap_pvt_unhex(int c
);
108 static char **apr_ldap_str2charray(apr_pool_t
*pool
,
114 * Is this URL an ldap url?
117 APU_DECLARE(int) apr_ldap_is_ldap_url(const char *url
)
126 if( skip_url_prefix( url
, &enclosed
, &scheme
) == NULL
) {
134 * Is this URL a secure ldap url?
137 APU_DECLARE(int) apr_ldap_is_ldaps_url(const char *url
)
146 if( skip_url_prefix( url
, &enclosed
, &scheme
) == NULL
) {
150 return strcmp(scheme
, "ldaps") == 0;
154 * Is this URL an ldap socket url?
157 APU_DECLARE(int) apr_ldap_is_ldapi_url(const char *url
)
166 if( skip_url_prefix( url
, &enclosed
, &scheme
) == NULL
) {
170 return strcmp(scheme
, "ldapi") == 0;
174 static const char *skip_url_prefix(const char *url
, int *enclosedp
,
178 * return non-zero if this looks like a LDAP URL; zero if not
179 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
189 /* skip leading '<' (if any) */
197 /* skip leading "URL:" (if any) */
198 if ( strncasecmp( p
, APR_LDAP_URL_URLCOLON
, APR_LDAP_URL_URLCOLON_LEN
) == 0 ) {
199 p
+= APR_LDAP_URL_URLCOLON_LEN
;
202 /* check for "ldap://" prefix */
203 if ( strncasecmp( p
, APR_LDAP_URL_PREFIX
, APR_LDAP_URL_PREFIX_LEN
) == 0 ) {
204 /* skip over "ldap://" prefix and return success */
205 p
+= APR_LDAP_URL_PREFIX_LEN
;
210 /* check for "ldaps://" prefix */
211 if ( strncasecmp( p
, APR_LDAPS_URL_PREFIX
, APR_LDAPS_URL_PREFIX_LEN
) == 0 ) {
212 /* skip over "ldaps://" prefix and return success */
213 p
+= APR_LDAPS_URL_PREFIX_LEN
;
218 /* check for "ldapi://" prefix */
219 if ( strncasecmp( p
, APR_LDAPI_URL_PREFIX
, APR_LDAPI_URL_PREFIX_LEN
) == 0 ) {
220 /* skip over "ldapi://" prefix and return success */
221 p
+= APR_LDAPI_URL_PREFIX_LEN
;
230 static int str2scope(const char *p
)
232 if ( strcasecmp( p
, "one" ) == 0 ) {
233 return LDAP_SCOPE_ONELEVEL
;
235 } else if ( strcasecmp( p
, "onetree" ) == 0 ) {
236 return LDAP_SCOPE_ONELEVEL
;
238 } else if ( strcasecmp( p
, "base" ) == 0 ) {
239 return LDAP_SCOPE_BASE
;
241 } else if ( strcasecmp( p
, "sub" ) == 0 ) {
242 return LDAP_SCOPE_SUBTREE
;
244 } else if ( strcasecmp( p
, "subtree" ) == 0 ) {
245 return LDAP_SCOPE_SUBTREE
;
253 * Parse the URL provided into an apr_ldap_url_desc_t object.
255 * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
256 * The LDAP result code and reason string is returned in the
257 * apr_ldap_err_t structure.
259 APU_DECLARE(int) apr_ldap_url_parse_ext(apr_pool_t
*pool
,
261 apr_ldap_url_desc_t
**ludpp
,
262 apr_ldap_err_t
**result_err
)
264 apr_ldap_url_desc_t
*ludp
;
267 const char *scheme
= NULL
;
271 apr_ldap_err_t
*result
= (apr_ldap_err_t
*)apr_pcalloc(pool
, sizeof(apr_ldap_err_t
));
272 *result_err
= result
;
274 /* sanity check our parameters */
275 if( url_in
== NULL
|| ludpp
== NULL
) {
276 result
->reason
= "Either the LDAP URL, or the URL structure was NULL. Oops.";
277 result
->rc
= APR_LDAP_URL_ERR_PARAM
;
281 *ludpp
= NULL
; /* pessimistic */
283 url_tmp
= skip_url_prefix( url_in
, &enclosed
, &scheme
);
284 if ( url_tmp
== NULL
) {
285 result
->reason
= "The scheme was not recognised as a valid LDAP URL scheme.";
286 result
->rc
= APR_LDAP_URL_ERR_BADSCHEME
;
290 /* make working copy of the remainder of the URL */
291 url
= (char *)apr_pstrdup(pool
, url_tmp
);
293 result
->reason
= "Out of memory parsing LDAP URL.";
294 result
->rc
= APR_LDAP_URL_ERR_MEM
;
299 p
= &url
[strlen(url
)-1];
302 result
->reason
= "Bad enclosure error while parsing LDAP URL.";
303 result
->rc
= APR_LDAP_URL_ERR_BADENCLOSURE
;
310 /* allocate return struct */
311 ludp
= (apr_ldap_url_desc_t
*)apr_pcalloc(pool
, sizeof(apr_ldap_url_desc_t
));
312 if ( ludp
== NULL
) {
313 result
->reason
= "Out of memory parsing LDAP URL.";
314 result
->rc
= APR_LDAP_URL_ERR_MEM
;
318 ludp
->lud_next
= NULL
;
319 ludp
->lud_host
= NULL
;
320 ludp
->lud_port
= LDAP_PORT
;
322 ludp
->lud_attrs
= NULL
;
323 ludp
->lud_filter
= NULL
;
324 ludp
->lud_scope
= -1;
325 ludp
->lud_filter
= NULL
;
326 ludp
->lud_exts
= NULL
;
328 ludp
->lud_scheme
= (char *)apr_pstrdup(pool
, scheme
);
329 if ( ludp
->lud_scheme
== NULL
) {
330 result
->reason
= "Out of memory parsing LDAP URL.";
331 result
->rc
= APR_LDAP_URL_ERR_MEM
;
335 if( strcasecmp( ludp
->lud_scheme
, "ldaps" ) == 0 ) {
336 ludp
->lud_port
= LDAPS_PORT
;
339 /* scan forward for '/' that marks end of hostport and begin. of dn */
340 p
= strchr( url
, '/' );
343 /* terminate hostport; point to start of dn */
347 /* IPv6 syntax with [ip address]:port */
349 r
= strchr( url
, ']' );
351 result
->reason
= "Bad LDAP URL while parsing IPV6 syntax.";
352 result
->rc
= APR_LDAP_URL_ERR_BADURL
;
356 q
= strrchr( r
, ':' );
358 q
= strrchr( url
, ':' );
362 apr_ldap_pvt_hex_unescape( ++q
);
365 result
->reason
= "Bad LDAP URL while parsing.";
366 result
->rc
= APR_LDAP_URL_ERR_BADURL
;
370 ludp
->lud_port
= atoi( q
);
373 apr_ldap_pvt_hex_unescape( url
);
375 /* If [ip address]:port syntax, url is [ip and we skip the [ */
376 ludp
->lud_host
= (char *)apr_pstrdup(pool
, url
+ ( *url
== '[' ));
377 if( ludp
->lud_host
== NULL
) {
378 result
->reason
= "Out of memory parsing LDAP URL.";
379 result
->rc
= APR_LDAP_URL_ERR_MEM
;
384 * Kludge. ldap://111.222.333.444:389??cn=abc,o=company
386 * On early Novell releases, search references/referrals were returned
387 * in this format, i.e., the dn was kind of in the scope position,
388 * but the required slash is missing. The whole thing is illegal syntax,
389 * but we need to account for it. Fortunately it can't be confused with
392 if( (p
== NULL
) && (q
!= NULL
) && ((q
= strchr( q
, '?')) != NULL
)) {
394 /* ? immediately followed by question */
399 apr_ldap_pvt_hex_unescape( q
);
400 ludp
->lud_dn
= (char *)apr_pstrdup(pool
, q
);
402 ludp
->lud_dn
= (char *)apr_pstrdup(pool
, "");
405 if( ludp
->lud_dn
== NULL
) {
406 result
->reason
= "Out of memory parsing LDAP URL.";
407 result
->rc
= APR_LDAP_URL_ERR_MEM
;
418 /* scan forward for '?' that may marks end of dn */
419 q
= strchr( p
, '?' );
422 /* terminate dn part */
428 apr_ldap_pvt_hex_unescape( p
);
429 ludp
->lud_dn
= (char *)apr_pstrdup(pool
, p
);
431 ludp
->lud_dn
= (char *)apr_pstrdup(pool
, "");
434 if( ludp
->lud_dn
== NULL
) {
435 result
->reason
= "Out of memory parsing LDAP URL.";
436 result
->rc
= APR_LDAP_URL_ERR_MEM
;
446 /* scan forward for '?' that may marks end of attributes */
448 q
= strchr( p
, '?' );
451 /* terminate attributes part */
456 /* parse attributes */
457 apr_ldap_pvt_hex_unescape( p
);
458 ludp
->lud_attrs
= apr_ldap_str2charray(pool
, p
, ",");
460 if( ludp
->lud_attrs
== NULL
) {
461 result
->reason
= "Bad attributes encountered while parsing LDAP URL.";
462 result
->rc
= APR_LDAP_URL_ERR_BADATTRS
;
473 /* scan forward for '?' that may marks end of scope */
475 q
= strchr( p
, '?' );
478 /* terminate the scope part */
483 /* parse the scope */
484 apr_ldap_pvt_hex_unescape( p
);
485 ludp
->lud_scope
= str2scope( p
);
487 if( ludp
->lud_scope
== -1 ) {
488 result
->reason
= "Bad scope encountered while parsing LDAP URL.";
489 result
->rc
= APR_LDAP_URL_ERR_BADSCOPE
;
500 /* scan forward for '?' that may marks end of filter */
502 q
= strchr( p
, '?' );
505 /* terminate the filter part */
510 /* parse the filter */
511 apr_ldap_pvt_hex_unescape( p
);
515 result
->reason
= "Bad filter encountered while parsing LDAP URL.";
516 result
->rc
= APR_LDAP_URL_ERR_BADFILTER
;
520 ludp
->lud_filter
= (char *)apr_pstrdup(pool
, p
);
521 if( ludp
->lud_filter
== NULL
) {
522 result
->reason
= "Out of memory parsing LDAP URL.";
523 result
->rc
= APR_LDAP_URL_ERR_MEM
;
534 /* scan forward for '?' that may marks end of extensions */
536 q
= strchr( p
, '?' );
540 result
->reason
= "Bad URL encountered while parsing LDAP URL.";
541 result
->rc
= APR_LDAP_URL_ERR_BADURL
;
545 /* parse the extensions */
546 ludp
->lud_exts
= apr_ldap_str2charray(pool
, p
, ",");
547 if( ludp
->lud_exts
== NULL
) {
548 result
->reason
= "Bad extensions encountered while parsing LDAP URL.";
549 result
->rc
= APR_LDAP_URL_ERR_BADEXTS
;
553 for( i
=0; ludp
->lud_exts
[i
] != NULL
; i
++ ) {
554 apr_ldap_pvt_hex_unescape( ludp
->lud_exts
[i
] );
556 if( *ludp
->lud_exts
[i
] == '!' ) {
557 /* count the number of critical extensions */
558 ludp
->lud_crit_exts
++;
563 /* must have 1 or more */
564 result
->reason
= "Bad extensions encountered while parsing LDAP URL.";
565 result
->rc
= APR_LDAP_URL_ERR_BADEXTS
;
576 * Parse the URL provided into an apr_ldap_url_desc_t object.
578 * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
579 * The LDAP result code and reason string is returned in the
580 * apr_ldap_err_t structure.
582 APU_DECLARE(int) apr_ldap_url_parse(apr_pool_t
*pool
,
584 apr_ldap_url_desc_t
**ludpp
,
585 apr_ldap_err_t
**result_err
)
588 int rc
= apr_ldap_url_parse_ext(pool
, url_in
, ludpp
, result_err
);
589 if( rc
!= APR_SUCCESS
) {
593 if ((*ludpp
)->lud_scope
== -1) {
594 (*ludpp
)->lud_scope
= LDAP_SCOPE_BASE
;
597 if ((*ludpp
)->lud_host
!= NULL
&& *(*ludpp
)->lud_host
== '\0') {
598 (*ludpp
)->lud_host
= NULL
;
606 static void apr_ldap_pvt_hex_unescape(char *s
)
609 * Remove URL hex escapes from s... done in place. The basic concept for
610 * this routine is borrowed from the WWW library HTUnEscape() routine.
614 for ( p
= s
; *s
!= '\0'; ++s
) {
616 if ( *++s
== '\0' ) {
619 *p
= apr_ldap_pvt_unhex( *s
) << 4;
620 if ( *++s
== '\0' ) {
623 *p
++ += apr_ldap_pvt_unhex( *s
);
633 static int apr_ldap_pvt_unhex(int c
)
635 return( c
>= '0' && c
<= '9' ? c
- '0'
636 : c
>= 'A' && c
<= 'F' ? c
- 'A' + 10
642 * Convert a string to a character array
644 static char **apr_ldap_str2charray(apr_pool_t
*pool
,
653 /* protect the input string from strtok */
654 str
= (char *)apr_pstrdup(pool
, str_in
);
660 for ( s
= str
; *s
; s
++ ) {
661 /* Warning: this strchr was previously ldap_utf8_strchr(), check
662 * whether this particular code has any charset issues.
664 if ( strchr( brkstr
, *s
) != NULL
) {
669 res
= (char **) apr_pcalloc(pool
, (i
+ 1) * sizeof(char *));
676 for ( s
= (char *)apr_strtok( str
, brkstr
, &lasts
);
678 s
= (char *)apr_strtok( NULL
, brkstr
, &lasts
) ) {
680 res
[i
] = (char *)apr_pstrdup(pool
, s
);
694 #endif /* APR_HAS_LDAP */