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 /* ap_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"
77 #include "ap_config.h"
87 #define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
90 #define AP_LDAP_URL_PREFIX "ldap://"
91 #define AP_LDAP_URL_PREFIX_LEN (sizeof(AP_LDAP_URL_PREFIX)-1)
92 #define AP_LDAPS_URL_PREFIX "ldaps://"
93 #define AP_LDAPS_URL_PREFIX_LEN (sizeof(AP_LDAPS_URL_PREFIX)-1)
94 #define AP_LDAPI_URL_PREFIX "ldapi://"
95 #define AP_LDAPI_URL_PREFIX_LEN (sizeof(AP_LDAPI_URL_PREFIX)-1)
96 #define AP_LDAP_URL_URLCOLON "URL:"
97 #define AP_LDAP_URL_URLCOLON_LEN (sizeof(AP_LDAP_URL_URLCOLON)-1)
100 /* local functions */
101 static const char* skip_url_prefix(const char *url,
103 const char **scheme);
105 static void ap_ldap_pvt_hex_unescape(char *s);
107 static int ap_ldap_pvt_unhex(int c);
109 static char **ap_ldap_str2charray(apr_pool_t *pool,
115 * Is this URL an ldap url?
118 MODLDAP_DECLARE(int) ap_ldap_is_ldap_url(const char *url)
127 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
135 * Is this URL a secure ldap url?
138 MODLDAP_DECLARE(int) ap_ldap_is_ldaps_url(const char *url)
147 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
151 return strcmp(scheme, "ldaps") == 0;
155 * Is this URL an ldap socket url?
158 MODLDAP_DECLARE(int) ap_ldap_is_ldapi_url(const char *url)
167 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
171 return strcmp(scheme, "ldapi") == 0;
175 static const char *skip_url_prefix(const char *url, int *enclosedp,
179 * return non-zero if this looks like a LDAP URL; zero if not
180 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
190 /* skip leading '<' (if any) */
198 /* skip leading "URL:" (if any) */
199 if ( strncasecmp( p, AP_LDAP_URL_URLCOLON, AP_LDAP_URL_URLCOLON_LEN ) == 0 ) {
200 p += AP_LDAP_URL_URLCOLON_LEN;
203 /* check for "ldap://" prefix */
204 if ( strncasecmp( p, AP_LDAP_URL_PREFIX, AP_LDAP_URL_PREFIX_LEN ) == 0 ) {
205 /* skip over "ldap://" prefix and return success */
206 p += AP_LDAP_URL_PREFIX_LEN;
211 /* check for "ldaps://" prefix */
212 if ( strncasecmp( p, AP_LDAPS_URL_PREFIX, AP_LDAPS_URL_PREFIX_LEN ) == 0 ) {
213 /* skip over "ldaps://" prefix and return success */
214 p += AP_LDAPS_URL_PREFIX_LEN;
219 /* check for "ldapi://" prefix */
220 if ( strncasecmp( p, AP_LDAPI_URL_PREFIX, AP_LDAPI_URL_PREFIX_LEN ) == 0 ) {
221 /* skip over "ldapi://" prefix and return success */
222 p += AP_LDAPI_URL_PREFIX_LEN;
231 static int str2scope(const char *p)
233 if ( strcasecmp( p, "one" ) == 0 ) {
234 return LDAP_SCOPE_ONELEVEL;
236 } else if ( strcasecmp( p, "onetree" ) == 0 ) {
237 return LDAP_SCOPE_ONELEVEL;
239 } else if ( strcasecmp( p, "base" ) == 0 ) {
240 return LDAP_SCOPE_BASE;
242 } else if ( strcasecmp( p, "sub" ) == 0 ) {
243 return LDAP_SCOPE_SUBTREE;
245 } else if ( strcasecmp( p, "subtree" ) == 0 ) {
246 return LDAP_SCOPE_SUBTREE;
254 * Parse the URL provided into an ap_ldap_url_desc_t object.
256 * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
257 * The LDAP result code and reason string is returned in the
258 * ap_ldap_err_t structure.
260 MODLDAP_DECLARE(int) ap_ldap_url_parse_ext(apr_pool_t *pool,
262 ap_ldap_url_desc_t **ludpp,
263 ap_ldap_err_t **result_err)
265 ap_ldap_url_desc_t *ludp;
268 const char *scheme = NULL;
272 ap_ldap_err_t *result = (ap_ldap_err_t *)apr_pcalloc(pool, sizeof(ap_ldap_err_t));
273 *result_err = result;
275 /* sanity check our parameters */
276 if( url_in == NULL || ludpp == NULL ) {
277 result->reason = "Either the LDAP URL, or the URL structure was NULL. Oops.";
278 result->rc = AP_LDAP_URL_ERR_PARAM;
282 *ludpp = NULL; /* pessimistic */
284 url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
285 if ( url_tmp == NULL ) {
286 result->reason = "The scheme was not recognised as a valid LDAP URL scheme.";
287 result->rc = AP_LDAP_URL_ERR_BADSCHEME;
291 /* make working copy of the remainder of the URL */
292 url = (char *)apr_pstrdup(pool, url_tmp);
294 result->reason = "Out of memory parsing LDAP URL.";
295 result->rc = AP_LDAP_URL_ERR_MEM;
300 p = &url[strlen(url)-1];
303 result->reason = "Bad enclosure error while parsing LDAP URL.";
304 result->rc = AP_LDAP_URL_ERR_BADENCLOSURE;
311 /* allocate return struct */
312 ludp = (ap_ldap_url_desc_t *)apr_pcalloc(pool, sizeof(ap_ldap_url_desc_t));
313 if ( ludp == NULL ) {
314 result->reason = "Out of memory parsing LDAP URL.";
315 result->rc = AP_LDAP_URL_ERR_MEM;
319 ludp->lud_next = NULL;
320 ludp->lud_host = NULL;
321 ludp->lud_port = LDAP_PORT;
323 ludp->lud_attrs = NULL;
324 ludp->lud_filter = NULL;
325 ludp->lud_scope = -1;
326 ludp->lud_filter = NULL;
327 ludp->lud_exts = NULL;
329 ludp->lud_scheme = (char *)apr_pstrdup(pool, scheme);
330 if ( ludp->lud_scheme == NULL ) {
331 result->reason = "Out of memory parsing LDAP URL.";
332 result->rc = AP_LDAP_URL_ERR_MEM;
336 if( strcasecmp( ludp->lud_scheme, "ldaps" ) == 0 ) {
337 ludp->lud_port = LDAPS_PORT;
340 /* scan forward for '/' that marks end of hostport and begin. of dn */
341 p = strchr( url, '/' );
344 /* terminate hostport; point to start of dn */
348 /* IPv6 syntax with [ip address]:port */
350 r = strchr( url, ']' );
352 result->reason = "Bad LDAP URL while parsing IPV6 syntax.";
353 result->rc = AP_LDAP_URL_ERR_BADURL;
357 q = strrchr( r, ':' );
359 q = strrchr( url, ':' );
363 ap_ldap_pvt_hex_unescape( ++q );
366 result->reason = "Bad LDAP URL while parsing.";
367 result->rc = AP_LDAP_URL_ERR_BADURL;
371 ludp->lud_port = atoi( q );
374 ap_ldap_pvt_hex_unescape( url );
376 /* If [ip address]:port syntax, url is [ip and we skip the [ */
377 ludp->lud_host = (char *)apr_pstrdup(pool, url + ( *url == '[' ));
378 if( ludp->lud_host == NULL ) {
379 result->reason = "Out of memory parsing LDAP URL.";
380 result->rc = AP_LDAP_URL_ERR_MEM;
385 * Kludge. ldap://111.222.333.444:389??cn=abc,o=company
387 * On early Novell releases, search references/referrals were returned
388 * in this format, i.e., the dn was kind of in the scope position,
389 * but the required slash is missing. The whole thing is illegal syntax,
390 * but we need to account for it. Fortunately it can't be confused with
393 if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
395 /* ? immediately followed by question */
400 ap_ldap_pvt_hex_unescape( q );
401 ludp->lud_dn = (char *)apr_pstrdup(pool, q);
403 ludp->lud_dn = (char *)apr_pstrdup(pool, "");
406 if( ludp->lud_dn == NULL ) {
407 result->reason = "Out of memory parsing LDAP URL.";
408 result->rc = AP_LDAP_URL_ERR_MEM;
419 /* scan forward for '?' that may marks end of dn */
420 q = strchr( p, '?' );
423 /* terminate dn part */
429 ap_ldap_pvt_hex_unescape( p );
430 ludp->lud_dn = (char *)apr_pstrdup(pool, p);
432 ludp->lud_dn = (char *)apr_pstrdup(pool, "");
435 if( ludp->lud_dn == NULL ) {
436 result->reason = "Out of memory parsing LDAP URL.";
437 result->rc = AP_LDAP_URL_ERR_MEM;
447 /* scan forward for '?' that may marks end of attributes */
449 q = strchr( p, '?' );
452 /* terminate attributes part */
457 /* parse attributes */
458 ap_ldap_pvt_hex_unescape( p );
459 ludp->lud_attrs = ap_ldap_str2charray(pool, p, ",");
461 if( ludp->lud_attrs == NULL ) {
462 result->reason = "Bad attributes encountered while parsing LDAP URL.";
463 result->rc = AP_LDAP_URL_ERR_BADATTRS;
474 /* scan forward for '?' that may marks end of scope */
476 q = strchr( p, '?' );
479 /* terminate the scope part */
484 /* parse the scope */
485 ap_ldap_pvt_hex_unescape( p );
486 ludp->lud_scope = str2scope( p );
488 if( ludp->lud_scope == -1 ) {
489 result->reason = "Bad scope encountered while parsing LDAP URL.";
490 result->rc = AP_LDAP_URL_ERR_BADSCOPE;
501 /* scan forward for '?' that may marks end of filter */
503 q = strchr( p, '?' );
506 /* terminate the filter part */
511 /* parse the filter */
512 ap_ldap_pvt_hex_unescape( p );
516 result->reason = "Bad filter encountered while parsing LDAP URL.";
517 result->rc = AP_LDAP_URL_ERR_BADFILTER;
521 ludp->lud_filter = (char *)apr_pstrdup(pool, p);
522 if( ludp->lud_filter == NULL ) {
523 result->reason = "Out of memory parsing LDAP URL.";
524 result->rc = AP_LDAP_URL_ERR_MEM;
535 /* scan forward for '?' that may marks end of extensions */
537 q = strchr( p, '?' );
541 result->reason = "Bad URL encountered while parsing LDAP URL.";
542 result->rc = AP_LDAP_URL_ERR_BADURL;
546 /* parse the extensions */
547 ludp->lud_exts = ap_ldap_str2charray(pool, p, ",");
548 if( ludp->lud_exts == NULL ) {
549 result->reason = "Bad extensions encountered while parsing LDAP URL.";
550 result->rc = AP_LDAP_URL_ERR_BADEXTS;
554 for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
555 ap_ldap_pvt_hex_unescape( ludp->lud_exts[i] );
557 if( *ludp->lud_exts[i] == '!' ) {
558 /* count the number of critical extensions */
559 ludp->lud_crit_exts++;
564 /* must have 1 or more */
565 result->reason = "Bad extensions encountered while parsing LDAP URL.";
566 result->rc = AP_LDAP_URL_ERR_BADEXTS;
577 * Parse the URL provided into an ap_ldap_url_desc_t object.
579 * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
580 * The LDAP result code and reason string is returned in the
581 * ap_ldap_err_t structure.
583 MODLDAP_DECLARE(int) ap_ldap_url_parse(apr_pool_t *pool,
585 ap_ldap_url_desc_t **ludpp,
586 ap_ldap_err_t **result_err)
589 int rc = ap_ldap_url_parse_ext(pool, url_in, ludpp, result_err);
590 if( rc != APR_SUCCESS ) {
594 if ((*ludpp)->lud_scope == -1) {
595 (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
598 if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
599 (*ludpp)->lud_host = NULL;
607 static void ap_ldap_pvt_hex_unescape(char *s)
610 * Remove URL hex escapes from s... done in place. The basic concept for
611 * this routine is borrowed from the WWW library HTUnEscape() routine.
615 for ( p = s; *s != '\0'; ++s ) {
617 if ( *++s == '\0' ) {
620 *p = ap_ldap_pvt_unhex( *s ) << 4;
621 if ( *++s == '\0' ) {
624 *p++ += ap_ldap_pvt_unhex( *s );
634 static int ap_ldap_pvt_unhex(int c)
636 return( c >= '0' && c <= '9' ? c - '0'
637 : c >= 'A' && c <= 'F' ? c - 'A' + 10
643 * Convert a string to a character array
645 static char **ap_ldap_str2charray(apr_pool_t *pool,
654 /* protect the input string from strtok */
655 str = (char *)apr_pstrdup(pool, str_in);
661 for ( s = str; *s; s++ ) {
662 /* Warning: this strchr was previously ldap_utf8_strchr(), check
663 * whether this particular code has any charset issues.
665 if ( strchr( brkstr, *s ) != NULL ) {
670 res = (char **) apr_pcalloc(pool, (i + 1) * sizeof(char *));
677 for ( s = (char *)apr_strtok( str, brkstr, &lasts );
679 s = (char *)apr_strtok( NULL, brkstr, &lasts ) ) {
681 res[i] = (char *)apr_pstrdup(pool, s);
695 #endif /* AP_HAS_LDAP */