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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17/* Portions Copyright 1998-2002 The OpenLDAP Foundation 18 * All rights reserved. 19 * 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. 25 * 26 * OpenLDAP is a registered trademark of the OpenLDAP Foundation. 27 * 28 * Individual files and/or contributed packages may be copyright by 29 * other parties and subject to additional restrictions. 30 * 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/ 34 * 35 * This work also contains materials derived from public sources. 36 * 37 * Additional information about OpenLDAP can be obtained at: 38 * http://www.openldap.org/ 39 */ 40 41/* 42 * Portions Copyright (c) 1992-1996 Regents of the University of Michigan. 43 * All rights reserved. 44 * 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. 51 */ 52 53/* apr_ldap_url.c -- LDAP URL (RFC 2255) related routines 54 * 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. 59 * 60 * LDAP URLs look like this: 61 * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] 62 * 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 67 * 68 * e.g., ldap://host:port/dc=com?o,cn?base?o=openldap?extension 69 * 70 * Tolerates URLs that look like: <ldapurl> and <URL:ldapurl> 71 */ 72 73#include "apu.h" 74#include "apr_pools.h" 75#include "apr_general.h" 76#include "apr_strings.h" 77#include "apr_ldap.h" 78 79#if APR_HAS_LDAP 80 81#if APR_HAVE_STDLIB_H 82#include <stdlib.h> 83#endif 84 85#ifndef LDAPS_PORT 86#define LDAPS_PORT 636 /* ldaps:/// default LDAP over TLS port */ 87#endif 88 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) 97 98 99/* local functions */ 100static const char* skip_url_prefix(const char *url, 101 int *enclosedp, 102 const char **scheme); 103 104static void apr_ldap_pvt_hex_unescape(char *s); 105 106static int apr_ldap_pvt_unhex(int c); 107 108static char **apr_ldap_str2charray(apr_pool_t *pool, 109 const char *str, 110 const char *brkstr); 111 112 113/** 114 * Is this URL an ldap url? 115 * 116 */ 117APU_DECLARE(int) apr_ldap_is_ldap_url(const char *url) 118{ 119 int enclosed; 120 const char * scheme; 121 122 if( url == NULL ) { 123 return 0; 124 } 125 126 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 127 return 0; 128 } 129 130 return 1; 131} 132 133/** 134 * Is this URL a secure ldap url? 135 * 136 */ 137APU_DECLARE(int) apr_ldap_is_ldaps_url(const char *url) 138{ 139 int enclosed; 140 const char * scheme; 141 142 if( url == NULL ) { 143 return 0; 144 } 145 146 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 147 return 0; 148 } 149 150 return strcmp(scheme, "ldaps") == 0; 151} 152 153/** 154 * Is this URL an ldap socket url? 155 * 156 */ 157APU_DECLARE(int) apr_ldap_is_ldapi_url(const char *url) 158{ 159 int enclosed; 160 const char * scheme; 161 162 if( url == NULL ) { 163 return 0; 164 } 165 166 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 167 return 0; 168 } 169 170 return strcmp(scheme, "ldapi") == 0; 171} 172 173 174static const char *skip_url_prefix(const char *url, int *enclosedp, 175 const char **scheme) 176{ 177 /* 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 180 */ 181 const char *p; 182 183 if ( url == NULL ) { 184 return( NULL ); 185 } 186 187 p = url; 188 189 /* skip leading '<' (if any) */ 190 if ( *p == '<' ) { 191 *enclosedp = 1; 192 ++p; 193 } else { 194 *enclosedp = 0; 195 } 196 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; 200 } 201 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 ); 208 } 209 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 ); 216 } 217 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 ); 224 } 225 226 return( NULL ); 227} 228 229 230static int str2scope(const char *p) 231{ 232 if ( strcasecmp( p, "one" ) == 0 ) { 233 return LDAP_SCOPE_ONELEVEL; 234 235 } else if ( strcasecmp( p, "onetree" ) == 0 ) { 236 return LDAP_SCOPE_ONELEVEL; 237 238 } else if ( strcasecmp( p, "base" ) == 0 ) { 239 return LDAP_SCOPE_BASE; 240 241 } else if ( strcasecmp( p, "sub" ) == 0 ) { 242 return LDAP_SCOPE_SUBTREE; 243 244 } else if ( strcasecmp( p, "subtree" ) == 0 ) { 245 return LDAP_SCOPE_SUBTREE; 246 } 247 248 return( -1 ); 249} 250 251 252/** 253 * Parse the URL provided into an apr_ldap_url_desc_t object. 254 * 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. 258 */ 259APU_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) 263{ 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; 270 271 apr_ldap_err_t *result = (apr_ldap_err_t *)apr_pcalloc(pool, sizeof(apr_ldap_err_t)); 272 *result_err = result; 273 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; 279 } 280 281 *ludpp = NULL; /* pessimistic */ 282 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; 288 } 289 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; 296 } 297 298 if ( enclosed ) { 299 p = &url[strlen(url)-1]; 300 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; 305 } 306 307 *p = '\0'; 308 } 309 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; 316 } 317 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; 327 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; 333 } 334 335 if( strcasecmp( ludp->lud_scheme, "ldaps" ) == 0 ) { 336 ludp->lud_port = LDAPS_PORT; 337 } 338 339 /* scan forward for '/' that marks end of hostport and begin. of dn */ 340 p = strchr( url, '/' ); 341 342 if( p != NULL ) { 343 /* terminate hostport; point to start of dn */ 344 *p++ = '\0'; 345 } 346 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; 354 } 355 *r++ = '\0'; 356 q = strrchr( r, ':' ); 357 } else { 358 q = strrchr( url, ':' ); 359 } 360 361 if ( q != NULL ) { 362 apr_ldap_pvt_hex_unescape( ++q ); 363 364 if( *q == '\0' ) { 365 result->reason = "Bad LDAP URL while parsing."; 366 result->rc = APR_LDAP_URL_ERR_BADURL; 367 return APR_EGENERAL; 368 } 369 370 ludp->lud_port = atoi( q ); 371 } 372 373 apr_ldap_pvt_hex_unescape( url ); 374 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; 381 } 382 383 /* 384 * Kludge. ldap://111.222.333.444:389??cn=abc,o=company 385 * 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. 391 */ 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, ""); 403 } 404 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; 409 } 410 } 411 } 412 413 if( p == NULL ) { 414 *ludpp = ludp; 415 return APR_SUCCESS; 416 } 417 418 /* scan forward for '?' that may marks end of dn */ 419 q = strchr( p, '?' ); 420 421 if( q != NULL ) { 422 /* terminate dn part */ 423 *q++ = '\0'; 424 } 425 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, ""); 432 } 433 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; 438 } 439 440 if( q == NULL ) { 441 /* no more */ 442 *ludpp = ludp; 443 return APR_SUCCESS; 444 } 445 446 /* scan forward for '?' that may marks end of attributes */ 447 p = q; 448 q = strchr( p, '?' ); 449 450 if( q != NULL ) { 451 /* terminate attributes part */ 452 *q++ = '\0'; 453 } 454 455 if( *p != '\0' ) { 456 /* parse attributes */ 457 apr_ldap_pvt_hex_unescape( p ); 458 ludp->lud_attrs = apr_ldap_str2charray(pool, p, ","); 459 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; 464 } 465 } 466 467 if ( q == NULL ) { 468 /* no more */ 469 *ludpp = ludp; 470 return APR_SUCCESS; 471 } 472 473 /* scan forward for '?' that may marks end of scope */ 474 p = q; 475 q = strchr( p, '?' ); 476 477 if( q != NULL ) { 478 /* terminate the scope part */ 479 *q++ = '\0'; 480 } 481 482 if( *p != '\0' ) { 483 /* parse the scope */ 484 apr_ldap_pvt_hex_unescape( p ); 485 ludp->lud_scope = str2scope( p ); 486 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; 491 } 492 } 493 494 if ( q == NULL ) { 495 /* no more */ 496 *ludpp = ludp; 497 return APR_SUCCESS; 498 } 499 500 /* scan forward for '?' that may marks end of filter */ 501 p = q; 502 q = strchr( p, '?' ); 503 504 if( q != NULL ) { 505 /* terminate the filter part */ 506 *q++ = '\0'; 507 } 508 509 if( *p != '\0' ) { 510 /* parse the filter */ 511 apr_ldap_pvt_hex_unescape( p ); 512 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; 518 } 519 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; 525 } 526 } 527 528 if ( q == NULL ) { 529 /* no more */ 530 *ludpp = ludp; 531 return APR_SUCCESS; 532 } 533 534 /* scan forward for '?' that may marks end of extensions */ 535 p = q; 536 q = strchr( p, '?' ); 537 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; 543 } 544 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; 551 } 552 553 for( i=0; ludp->lud_exts[i] != NULL; i++ ) { 554 apr_ldap_pvt_hex_unescape( ludp->lud_exts[i] ); 555 556 if( *ludp->lud_exts[i] == '!' ) { 557 /* count the number of critical extensions */ 558 ludp->lud_crit_exts++; 559 } 560 } 561 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; 567 } 568 569 /* no more */ 570 *ludpp = ludp; 571 return APR_SUCCESS; 572} 573 574 575/** 576 * Parse the URL provided into an apr_ldap_url_desc_t object. 577 * 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. 581 */ 582APU_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) 586{ 587 588 int rc = apr_ldap_url_parse_ext(pool, url_in, ludpp, result_err); 589 if( rc != APR_SUCCESS ) { 590 return rc; 591 } 592 593 if ((*ludpp)->lud_scope == -1) { 594 (*ludpp)->lud_scope = LDAP_SCOPE_BASE; 595 } 596 597 if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') { 598 (*ludpp)->lud_host = NULL; 599 } 600 601 return rc; 602 603} 604 605 606static void apr_ldap_pvt_hex_unescape(char *s) 607{ 608 /* 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. 611 */ 612 char *p; 613 614 for ( p = s; *s != '\0'; ++s ) { 615 if ( *s == '%' ) { 616 if ( *++s == '\0' ) { 617 break; 618 } 619 *p = apr_ldap_pvt_unhex( *s ) << 4; 620 if ( *++s == '\0' ) { 621 break; 622 } 623 *p++ += apr_ldap_pvt_unhex( *s ); 624 } else { 625 *p++ = *s; 626 } 627 } 628 629 *p = '\0'; 630} 631 632 633static int apr_ldap_pvt_unhex(int c) 634{ 635 return( c >= '0' && c <= '9' ? c - '0' 636 : c >= 'A' && c <= 'F' ? c - 'A' + 10 637 : c - 'a' + 10 ); 638} 639 640 641/** 642 * Convert a string to a character array 643 */ 644static char **apr_ldap_str2charray(apr_pool_t *pool, 645 const char *str_in, 646 const char *brkstr) 647{ 648 char **res; 649 char *str, *s; 650 char *lasts; 651 int i; 652 653 /* protect the input string from strtok */ 654 str = (char *)apr_pstrdup(pool, str_in); 655 if( str == NULL ) { 656 return NULL; 657 } 658 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. 663 */ 664 if ( strchr( brkstr, *s ) != NULL ) { 665 i++; 666 } 667 } 668 669 res = (char **) apr_pcalloc(pool, (i + 1) * sizeof(char *)); 670 if( res == NULL ) { 671 return NULL; 672 } 673 674 i = 0; 675 676 for ( s = (char *)apr_strtok( str, brkstr, &lasts ); 677 s != NULL; 678 s = (char *)apr_strtok( NULL, brkstr, &lasts ) ) { 679 680 res[i] = (char *)apr_pstrdup(pool, s); 681 if(res[i] == NULL) { 682 return NULL; 683 } 684 685 i++; 686 } 687 688 res[i] = NULL; 689 690 return( res ); 691 692} 693 694#endif /* APR_HAS_LDAP */ 695