Apply patch from bug #41250, by Chris Darroch
[apr-util.git] / ldap / apr_ldap_url.c
blob52e37b253f9c8120ff17ad522104b29c46f3dd0d
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/
41 /*
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]]]]]]
63 * where:
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>
73 #include "apu.h"
74 #include "apr_pools.h"
75 #include "apr_general.h"
76 #include "apr_strings.h"
77 #include "apr_ldap.h"
79 #if APR_HAS_LDAP
81 #if APR_HAVE_STDLIB_H
82 #include <stdlib.h>
83 #endif
85 #ifndef LDAPS_PORT
86 #define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */
87 #endif
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)
99 /* local functions */
100 static const char* skip_url_prefix(const char *url,
101 int *enclosedp,
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,
109 const char *str,
110 const char *brkstr);
114 * Is this URL an ldap url?
117 APU_DECLARE(int) apr_ldap_is_ldap_url(const char *url)
119 int enclosed;
120 const char * scheme;
122 if( url == NULL ) {
123 return 0;
126 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
127 return 0;
130 return 1;
134 * Is this URL a secure ldap url?
137 APU_DECLARE(int) apr_ldap_is_ldaps_url(const char *url)
139 int enclosed;
140 const char * scheme;
142 if( url == NULL ) {
143 return 0;
146 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
147 return 0;
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)
159 int enclosed;
160 const char * scheme;
162 if( url == NULL ) {
163 return 0;
166 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
167 return 0;
170 return strcmp(scheme, "ldapi") == 0;
174 static const char *skip_url_prefix(const char *url, int *enclosedp,
175 const char **scheme)
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
181 const char *p;
183 if ( url == NULL ) {
184 return( NULL );
187 p = url;
189 /* skip leading '<' (if any) */
190 if ( *p == '<' ) {
191 *enclosedp = 1;
192 ++p;
193 } else {
194 *enclosedp = 0;
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;
206 *scheme = "ldap";
207 return( p );
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;
214 *scheme = "ldaps";
215 return( p );
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;
222 *scheme = "ldapi";
223 return( p );
226 return( NULL );
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;
248 return( -1 );
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,
260 const char *url_in,
261 apr_ldap_url_desc_t **ludpp,
262 apr_ldap_err_t **result_err)
264 apr_ldap_url_desc_t *ludp;
265 char *p, *q, *r;
266 int i, enclosed;
267 const char *scheme = NULL;
268 const char *url_tmp;
269 char *url;
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;
278 return APR_EGENERAL;
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;
287 return APR_EGENERAL;
290 /* make working copy of the remainder of the URL */
291 url = (char *)apr_pstrdup(pool, url_tmp);
292 if ( url == NULL ) {
293 result->reason = "Out of memory parsing LDAP URL.";
294 result->rc = APR_LDAP_URL_ERR_MEM;
295 return APR_EGENERAL;
298 if ( enclosed ) {
299 p = &url[strlen(url)-1];
301 if( *p != '>' ) {
302 result->reason = "Bad enclosure error while parsing LDAP URL.";
303 result->rc = APR_LDAP_URL_ERR_BADENCLOSURE;
304 return APR_EGENERAL;
307 *p = '\0';
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;
315 return APR_EGENERAL;
318 ludp->lud_next = NULL;
319 ludp->lud_host = NULL;
320 ludp->lud_port = LDAP_PORT;
321 ludp->lud_dn = NULL;
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;
332 return APR_EGENERAL;
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, '/' );
342 if( p != NULL ) {
343 /* terminate hostport; point to start of dn */
344 *p++ = '\0';
347 /* IPv6 syntax with [ip address]:port */
348 if ( *url == '[' ) {
349 r = strchr( url, ']' );
350 if ( r == NULL ) {
351 result->reason = "Bad LDAP URL while parsing IPV6 syntax.";
352 result->rc = APR_LDAP_URL_ERR_BADURL;
353 return APR_EGENERAL;
355 *r++ = '\0';
356 q = strrchr( r, ':' );
357 } else {
358 q = strrchr( url, ':' );
361 if ( q != NULL ) {
362 apr_ldap_pvt_hex_unescape( ++q );
364 if( *q == '\0' ) {
365 result->reason = "Bad LDAP URL while parsing.";
366 result->rc = APR_LDAP_URL_ERR_BADURL;
367 return APR_EGENERAL;
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;
380 return APR_EGENERAL;
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
390 * anything real.
392 if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
393 q++;
394 /* ? immediately followed by question */
395 if( *q == '?') {
396 q++;
397 if( *q != '\0' ) {
398 /* parse dn part */
399 apr_ldap_pvt_hex_unescape( q );
400 ludp->lud_dn = (char *)apr_pstrdup(pool, q);
401 } else {
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;
408 return APR_EGENERAL;
413 if( p == NULL ) {
414 *ludpp = ludp;
415 return APR_SUCCESS;
418 /* scan forward for '?' that may marks end of dn */
419 q = strchr( p, '?' );
421 if( q != NULL ) {
422 /* terminate dn part */
423 *q++ = '\0';
426 if( *p != '\0' ) {
427 /* parse dn part */
428 apr_ldap_pvt_hex_unescape( p );
429 ludp->lud_dn = (char *)apr_pstrdup(pool, p);
430 } else {
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;
437 return APR_EGENERAL;
440 if( q == NULL ) {
441 /* no more */
442 *ludpp = ludp;
443 return APR_SUCCESS;
446 /* scan forward for '?' that may marks end of attributes */
447 p = q;
448 q = strchr( p, '?' );
450 if( q != NULL ) {
451 /* terminate attributes part */
452 *q++ = '\0';
455 if( *p != '\0' ) {
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;
463 return APR_EGENERAL;
467 if ( q == NULL ) {
468 /* no more */
469 *ludpp = ludp;
470 return APR_SUCCESS;
473 /* scan forward for '?' that may marks end of scope */
474 p = q;
475 q = strchr( p, '?' );
477 if( q != NULL ) {
478 /* terminate the scope part */
479 *q++ = '\0';
482 if( *p != '\0' ) {
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;
490 return APR_EGENERAL;
494 if ( q == NULL ) {
495 /* no more */
496 *ludpp = ludp;
497 return APR_SUCCESS;
500 /* scan forward for '?' that may marks end of filter */
501 p = q;
502 q = strchr( p, '?' );
504 if( q != NULL ) {
505 /* terminate the filter part */
506 *q++ = '\0';
509 if( *p != '\0' ) {
510 /* parse the filter */
511 apr_ldap_pvt_hex_unescape( p );
513 if( ! *p ) {
514 /* missing filter */
515 result->reason = "Bad filter encountered while parsing LDAP URL.";
516 result->rc = APR_LDAP_URL_ERR_BADFILTER;
517 return APR_EGENERAL;
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;
524 return APR_EGENERAL;
528 if ( q == NULL ) {
529 /* no more */
530 *ludpp = ludp;
531 return APR_SUCCESS;
534 /* scan forward for '?' that may marks end of extensions */
535 p = q;
536 q = strchr( p, '?' );
538 if( q != NULL ) {
539 /* extra '?' */
540 result->reason = "Bad URL encountered while parsing LDAP URL.";
541 result->rc = APR_LDAP_URL_ERR_BADURL;
542 return APR_EGENERAL;
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;
550 return APR_EGENERAL;
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++;
562 if( i == 0 ) {
563 /* must have 1 or more */
564 result->reason = "Bad extensions encountered while parsing LDAP URL.";
565 result->rc = APR_LDAP_URL_ERR_BADEXTS;
566 return APR_EGENERAL;
569 /* no more */
570 *ludpp = ludp;
571 return APR_SUCCESS;
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,
583 const char *url_in,
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 ) {
590 return rc;
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;
601 return rc;
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.
612 char *p;
614 for ( p = s; *s != '\0'; ++s ) {
615 if ( *s == '%' ) {
616 if ( *++s == '\0' ) {
617 break;
619 *p = apr_ldap_pvt_unhex( *s ) << 4;
620 if ( *++s == '\0' ) {
621 break;
623 *p++ += apr_ldap_pvt_unhex( *s );
624 } else {
625 *p++ = *s;
629 *p = '\0';
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
637 : c - 'a' + 10 );
642 * Convert a string to a character array
644 static char **apr_ldap_str2charray(apr_pool_t *pool,
645 const char *str_in,
646 const char *brkstr)
648 char **res;
649 char *str, *s;
650 char *lasts;
651 int i;
653 /* protect the input string from strtok */
654 str = (char *)apr_pstrdup(pool, str_in);
655 if( str == NULL ) {
656 return NULL;
659 i = 1;
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 ) {
665 i++;
669 res = (char **) apr_pcalloc(pool, (i + 1) * sizeof(char *));
670 if( res == NULL ) {
671 return NULL;
674 i = 0;
676 for ( s = (char *)apr_strtok( str, brkstr, &lasts );
677 s != NULL;
678 s = (char *)apr_strtok( NULL, brkstr, &lasts ) ) {
680 res[i] = (char *)apr_pstrdup(pool, s);
681 if(res[i] == NULL) {
682 return NULL;
685 i++;
688 res[i] = NULL;
690 return( res );
694 #endif /* APR_HAS_LDAP */