1 /* $NetBSD: url.c,v 1.3 2021/08/14 16:14:56 christos Exp $ */ 2 3 /* LIBLDAP url.c -- LDAP URL (RFC 4516) related routines */ 4 /* $OpenLDAP$ */ 5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 6 * 7 * Copyright 1998-2021 The OpenLDAP Foundation. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted only as authorized by the OpenLDAP 12 * Public License. 13 * 14 * A copy of this license is available in the file LICENSE in the 15 * top-level directory of the distribution or, alternatively, at 16 * <http://www.OpenLDAP.org/license.html>. 17 */ 18 /* Portions Copyright (c) 1996 Regents of the University of Michigan. 19 * All rights reserved. 20 */ 21 22 23 /* 24 * LDAP URLs look like this: 25 * [p]ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] 26 * 27 * where: 28 * attributes is a comma separated list 29 * scope is one of these three strings: base one sub (default=base) 30 * filter is an string-represented filter as in RFC 4515 31 * 32 * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension 33 * 34 * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl> 35 */ 36 37 #include <sys/cdefs.h> 38 __RCSID("$NetBSD: url.c,v 1.3 2021/08/14 16:14:56 christos Exp $"); 39 40 #include "portable.h" 41 42 #include <stdio.h> 43 44 #include <ac/stdlib.h> 45 #include <ac/ctype.h> 46 47 #include <ac/socket.h> 48 #include <ac/string.h> 49 #include <ac/time.h> 50 51 #include "ldap-int.h" 52 53 /* local functions */ 54 static const char* skip_url_prefix LDAP_P(( 55 const char *url, 56 int *enclosedp, 57 const char **scheme )); 58 59 int ldap_pvt_url_scheme2proto( const char *scheme ) 60 { 61 assert( scheme != NULL ); 62 63 if( scheme == NULL ) { 64 return -1; 65 } 66 67 if( strcmp("ldap", scheme) == 0 || strcmp("pldap", scheme) == 0 ) { 68 return LDAP_PROTO_TCP; 69 } 70 71 if( strcmp("ldapi", scheme) == 0 ) { 72 return LDAP_PROTO_IPC; 73 } 74 75 if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) { 76 return LDAP_PROTO_TCP; 77 } 78 #ifdef LDAP_CONNECTIONLESS 79 if( strcmp("cldap", scheme) == 0 ) { 80 return LDAP_PROTO_UDP; 81 } 82 #endif 83 84 return -1; 85 } 86 87 int ldap_pvt_url_scheme_port( const char *scheme, int port ) 88 { 89 assert( scheme != NULL ); 90 91 if( port ) return port; 92 if( scheme == NULL ) return port; 93 94 if( strcmp("ldap", scheme) == 0 || strcmp("pldap", scheme) == 0 ) { 95 return LDAP_PORT; 96 } 97 98 if( strcmp("ldapi", scheme) == 0 ) { 99 return -1; 100 } 101 102 if( strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0 ) { 103 return LDAPS_PORT; 104 } 105 106 #ifdef LDAP_CONNECTIONLESS 107 if( strcmp("cldap", scheme) == 0 ) { 108 return LDAP_PORT; 109 } 110 #endif 111 112 return -1; 113 } 114 115 int 116 ldap_pvt_url_scheme2tls( const char *scheme ) 117 { 118 assert( scheme != NULL ); 119 120 if( scheme == NULL ) { 121 return -1; 122 } 123 124 return strcmp("ldaps", scheme) == 0 || strcmp("pldaps", scheme) == 0; 125 } 126 127 int 128 ldap_pvt_url_scheme2proxied( const char *scheme ) 129 { 130 assert( scheme != NULL ); 131 132 if( scheme == NULL ) { 133 return -1; 134 } 135 136 return strcmp("pldap", scheme) == 0 || strcmp("pldaps", scheme) == 0; 137 } 138 139 int 140 ldap_is_ldap_url( LDAP_CONST char *url ) 141 { 142 int enclosed; 143 const char * scheme; 144 145 if( url == NULL ) { 146 return 0; 147 } 148 149 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 150 return 0; 151 } 152 153 return 1; 154 } 155 156 int 157 ldap_is_ldaps_url( LDAP_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, "ldaps") == 0 || strcmp(scheme, "pldaps") == 0; 171 } 172 173 int 174 ldap_is_ldapi_url( LDAP_CONST char *url ) 175 { 176 int enclosed; 177 const char * scheme; 178 179 if( url == NULL ) { 180 return 0; 181 } 182 183 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 184 return 0; 185 } 186 187 return strcmp(scheme, "ldapi") == 0; 188 } 189 190 #ifdef LDAP_CONNECTIONLESS 191 int 192 ldap_is_ldapc_url( LDAP_CONST char *url ) 193 { 194 int enclosed; 195 const char * scheme; 196 197 if( url == NULL ) { 198 return 0; 199 } 200 201 if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { 202 return 0; 203 } 204 205 return strcmp(scheme, "cldap") == 0; 206 } 207 #endif 208 209 static const char* 210 skip_url_prefix( 211 const char *url, 212 int *enclosedp, 213 const char **scheme ) 214 { 215 /* 216 * return non-zero if this looks like a LDAP URL; zero if not 217 * if non-zero returned, *urlp will be moved past "ldap://" part of URL 218 */ 219 const char *p; 220 221 if ( url == NULL ) { 222 return( NULL ); 223 } 224 225 p = url; 226 227 /* skip leading '<' (if any) */ 228 if ( *p == '<' ) { 229 *enclosedp = 1; 230 ++p; 231 } else { 232 *enclosedp = 0; 233 } 234 235 /* skip leading "URL:" (if any) */ 236 if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { 237 p += LDAP_URL_URLCOLON_LEN; 238 } 239 240 /* check for "ldap://" prefix */ 241 if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { 242 /* skip over "ldap://" prefix and return success */ 243 p += LDAP_URL_PREFIX_LEN; 244 *scheme = "ldap"; 245 return( p ); 246 } 247 248 /* check for "pldap://" prefix */ 249 if ( strncasecmp( p, PLDAP_URL_PREFIX, PLDAP_URL_PREFIX_LEN ) == 0 ) { 250 /* skip over "pldap://" prefix and return success */ 251 p += PLDAP_URL_PREFIX_LEN; 252 *scheme = "pldap"; 253 return( p ); 254 } 255 256 /* check for "ldaps://" prefix */ 257 if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { 258 /* skip over "ldaps://" prefix and return success */ 259 p += LDAPS_URL_PREFIX_LEN; 260 *scheme = "ldaps"; 261 return( p ); 262 } 263 264 /* check for "pldaps://" prefix */ 265 if ( strncasecmp( p, PLDAPS_URL_PREFIX, PLDAPS_URL_PREFIX_LEN ) == 0 ) { 266 /* skip over "pldaps://" prefix and return success */ 267 p += PLDAPS_URL_PREFIX_LEN; 268 *scheme = "pldaps"; 269 return( p ); 270 } 271 272 /* check for "ldapi://" prefix */ 273 if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { 274 /* skip over "ldapi://" prefix and return success */ 275 p += LDAPI_URL_PREFIX_LEN; 276 *scheme = "ldapi"; 277 return( p ); 278 } 279 280 #ifdef LDAP_CONNECTIONLESS 281 /* check for "cldap://" prefix */ 282 if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { 283 /* skip over "cldap://" prefix and return success */ 284 p += LDAPC_URL_PREFIX_LEN; 285 *scheme = "cldap"; 286 return( p ); 287 } 288 #endif 289 290 return( NULL ); 291 } 292 293 int 294 ldap_pvt_scope2bv( int scope, struct berval *bv ) 295 { 296 switch ( scope ) { 297 case LDAP_SCOPE_BASE: 298 BER_BVSTR( bv, "base" ); 299 break; 300 301 case LDAP_SCOPE_ONELEVEL: 302 BER_BVSTR( bv, "one" ); 303 break; 304 305 case LDAP_SCOPE_SUBTREE: 306 BER_BVSTR( bv, "sub" ); 307 break; 308 309 case LDAP_SCOPE_SUBORDINATE: 310 BER_BVSTR( bv, "subordinate" ); 311 break; 312 313 default: 314 return LDAP_OTHER; 315 } 316 317 return LDAP_SUCCESS; 318 } 319 320 const char * 321 ldap_pvt_scope2str( int scope ) 322 { 323 struct berval bv; 324 325 if ( ldap_pvt_scope2bv( scope, &bv ) == LDAP_SUCCESS ) { 326 return bv.bv_val; 327 } 328 329 return NULL; 330 } 331 332 int 333 ldap_pvt_bv2scope( struct berval *bv ) 334 { 335 static struct { 336 struct berval bv; 337 int scope; 338 } v[] = { 339 { BER_BVC( "one" ), LDAP_SCOPE_ONELEVEL }, 340 { BER_BVC( "onelevel" ), LDAP_SCOPE_ONELEVEL }, 341 { BER_BVC( "base" ), LDAP_SCOPE_BASE }, 342 { BER_BVC( "sub" ), LDAP_SCOPE_SUBTREE }, 343 { BER_BVC( "subtree" ), LDAP_SCOPE_SUBTREE }, 344 { BER_BVC( "subord" ), LDAP_SCOPE_SUBORDINATE }, 345 { BER_BVC( "subordinate" ), LDAP_SCOPE_SUBORDINATE }, 346 { BER_BVC( "children" ), LDAP_SCOPE_SUBORDINATE }, 347 { BER_BVNULL, -1 } 348 }; 349 int i; 350 351 for ( i = 0; v[ i ].scope != -1; i++ ) { 352 if ( ber_bvstrcasecmp( bv, &v[ i ].bv ) == 0 ) { 353 return v[ i ].scope; 354 } 355 } 356 357 return( -1 ); 358 } 359 360 int 361 ldap_pvt_str2scope( const char *p ) 362 { 363 struct berval bv; 364 365 ber_str2bv( p, 0, 0, &bv ); 366 367 return ldap_pvt_bv2scope( &bv ); 368 } 369 370 static const char hex[] = "0123456789ABCDEF"; 371 372 #define URLESC_NONE 0x0000U 373 #define URLESC_COMMA 0x0001U 374 #define URLESC_SLASH 0x0002U 375 376 static int 377 hex_escape_len( const char *s, unsigned list ) 378 { 379 int len; 380 381 if ( s == NULL ) { 382 return 0; 383 } 384 385 for ( len = 0; s[0]; s++ ) { 386 switch ( s[0] ) { 387 /* RFC 2396: reserved */ 388 case '?': 389 len += 3; 390 break; 391 392 case ',': 393 if ( list & URLESC_COMMA ) { 394 len += 3; 395 } else { 396 len++; 397 } 398 break; 399 400 case '/': 401 if ( list & URLESC_SLASH ) { 402 len += 3; 403 } else { 404 len++; 405 } 406 break; 407 408 case ';': 409 case ':': 410 case '@': 411 case '&': 412 case '=': 413 case '+': 414 case '$': 415 416 /* RFC 2396: unreserved mark */ 417 case '-': 418 case '_': 419 case '.': 420 case '!': 421 case '~': 422 case '*': 423 case '\'': 424 case '(': 425 case ')': 426 len++; 427 break; 428 429 /* RFC 2396: unreserved alphanum */ 430 default: 431 if ( !isalnum( (unsigned char) s[0] ) ) { 432 len += 3; 433 } else { 434 len++; 435 } 436 break; 437 } 438 } 439 440 return len; 441 } 442 443 static int 444 hex_escape( char *buf, int len, const char *s, unsigned list ) 445 { 446 int i; 447 int pos; 448 449 if ( s == NULL ) { 450 return 0; 451 } 452 453 for ( pos = 0, i = 0; s[i] && pos < len; i++ ) { 454 int escape = 0; 455 456 switch ( s[i] ) { 457 /* RFC 2396: reserved */ 458 case '?': 459 escape = 1; 460 break; 461 462 case ',': 463 if ( list & URLESC_COMMA ) { 464 escape = 1; 465 } 466 break; 467 468 case '/': 469 if ( list & URLESC_SLASH ) { 470 escape = 1; 471 } 472 break; 473 474 case ';': 475 case ':': 476 case '@': 477 case '&': 478 case '=': 479 case '+': 480 case '$': 481 482 /* RFC 2396: unreserved mark */ 483 case '-': 484 case '_': 485 case '.': 486 case '!': 487 case '~': 488 case '*': 489 case '\'': 490 case '(': 491 case ')': 492 break; 493 494 /* RFC 2396: unreserved alphanum */ 495 default: 496 if ( !isalnum( (unsigned char) s[i] ) ) { 497 escape = 1; 498 } 499 break; 500 } 501 502 if ( escape ) { 503 buf[pos++] = '%'; 504 buf[pos++] = hex[ (s[i] >> 4) & 0x0f ]; 505 buf[pos++] = hex[ s[i] & 0x0f ]; 506 507 } else { 508 buf[pos++] = s[i]; 509 } 510 } 511 512 buf[pos] = '\0'; 513 514 return pos; 515 } 516 517 static int 518 hex_escape_len_list( char **s, unsigned flags ) 519 { 520 int len; 521 int i; 522 523 if ( s == NULL ) { 524 return 0; 525 } 526 527 len = 0; 528 for ( i = 0; s[i] != NULL; i++ ) { 529 if ( len ) { 530 len++; 531 } 532 len += hex_escape_len( s[i], flags ); 533 } 534 535 return len; 536 } 537 538 static int 539 hex_escape_list( char *buf, int len, char **s, unsigned flags ) 540 { 541 int pos; 542 int i; 543 544 if ( s == NULL ) { 545 return 0; 546 } 547 548 pos = 0; 549 for ( i = 0; s[i] != NULL; i++ ) { 550 int curlen; 551 552 if ( pos ) { 553 buf[pos++] = ','; 554 len--; 555 } 556 curlen = hex_escape( &buf[pos], len, s[i], flags ); 557 len -= curlen; 558 pos += curlen; 559 } 560 561 return pos; 562 } 563 564 static int 565 desc2str_len( LDAPURLDesc *u ) 566 { 567 int sep = 0; 568 int len = 0; 569 int is_ipc = 0; 570 struct berval scope; 571 572 if ( u == NULL || u->lud_scheme == NULL ) { 573 return -1; 574 } 575 576 if ( !strcmp( "ldapi", u->lud_scheme )) { 577 is_ipc = 1; 578 } 579 580 if ( u->lud_exts ) { 581 len += hex_escape_len_list( u->lud_exts, URLESC_COMMA ); 582 if ( !sep ) { 583 sep = 5; 584 } 585 } 586 587 if ( u->lud_filter ) { 588 len += hex_escape_len( u->lud_filter, URLESC_NONE ); 589 if ( !sep ) { 590 sep = 4; 591 } 592 } 593 594 if ( ldap_pvt_scope2bv( u->lud_scope, &scope ) == LDAP_SUCCESS ) { 595 len += scope.bv_len; 596 if ( !sep ) { 597 sep = 3; 598 } 599 } 600 601 if ( u->lud_attrs ) { 602 len += hex_escape_len_list( u->lud_attrs, URLESC_NONE ); 603 if ( !sep ) { 604 sep = 2; 605 } 606 } 607 608 if ( u->lud_dn && u->lud_dn[0] ) { 609 len += hex_escape_len( u->lud_dn, URLESC_NONE ); 610 if ( !sep ) { 611 sep = 1; 612 } 613 }; 614 615 len += sep; 616 617 if ( u->lud_port ) { 618 unsigned p = u->lud_port; 619 if ( p > 65535 ) 620 return -1; 621 622 len += (p > 999 ? 5 + (p > 9999) : p > 99 ? 4 : 2 + (p > 9)); 623 } 624 625 if ( u->lud_host && u->lud_host[0] ) { 626 char *ptr; 627 len += hex_escape_len( u->lud_host, URLESC_SLASH ); 628 if ( !is_ipc && ( ptr = strchr( u->lud_host, ':' ))) { 629 if ( strchr( ptr+1, ':' )) 630 len += 2; /* IPv6, [] */ 631 } 632 } 633 634 len += strlen( u->lud_scheme ) + STRLENOF( "://" ); 635 636 return len; 637 } 638 639 static int 640 desc2str( LDAPURLDesc *u, char *s, int len ) 641 { 642 int i; 643 int sep = 0; 644 int sofar = 0; 645 int is_v6 = 0; 646 int is_ipc = 0; 647 struct berval scope = BER_BVNULL; 648 char *ptr; 649 650 if ( u == NULL ) { 651 return -1; 652 } 653 654 if ( s == NULL ) { 655 return -1; 656 } 657 658 if ( u->lud_scheme && !strcmp( "ldapi", u->lud_scheme )) { 659 is_ipc = 1; 660 } 661 662 ldap_pvt_scope2bv( u->lud_scope, &scope ); 663 664 if ( u->lud_exts ) { 665 sep = 5; 666 } else if ( u->lud_filter ) { 667 sep = 4; 668 } else if ( !BER_BVISEMPTY( &scope ) ) { 669 sep = 3; 670 } else if ( u->lud_attrs ) { 671 sep = 2; 672 } else if ( u->lud_dn && u->lud_dn[0] ) { 673 sep = 1; 674 } 675 676 if ( !is_ipc && u->lud_host && ( ptr = strchr( u->lud_host, ':' ))) { 677 if ( strchr( ptr+1, ':' )) 678 is_v6 = 1; 679 } 680 681 if ( u->lud_port ) { 682 sofar = sprintf( s, "%s://%s%s%s:%d", u->lud_scheme, 683 is_v6 ? "[" : "", 684 u->lud_host ? u->lud_host : "", 685 is_v6 ? "]" : "", 686 u->lud_port ); 687 len -= sofar; 688 689 } else { 690 sofar = sprintf( s, "%s://", u->lud_scheme ); 691 len -= sofar; 692 if ( u->lud_host && u->lud_host[0] ) { 693 if ( is_v6 ) { 694 s[sofar++] = '['; 695 len--; 696 } 697 i = hex_escape( &s[sofar], len, u->lud_host, URLESC_SLASH ); 698 sofar += i; 699 len -= i; 700 if ( is_v6 ) { 701 s[sofar++] = ']'; 702 len--; 703 } 704 } 705 } 706 707 assert( len >= 0 ); 708 709 if ( sep < 1 ) { 710 goto done; 711 } 712 713 s[sofar++] = '/'; 714 len--; 715 716 assert( len >= 0 ); 717 718 if ( u->lud_dn && u->lud_dn[0] ) { 719 i = hex_escape( &s[sofar], len, u->lud_dn, URLESC_NONE ); 720 sofar += i; 721 len -= i; 722 723 assert( len >= 0 ); 724 } 725 726 if ( sep < 2 ) { 727 goto done; 728 } 729 s[sofar++] = '?'; 730 len--; 731 732 assert( len >= 0 ); 733 734 i = hex_escape_list( &s[sofar], len, u->lud_attrs, URLESC_NONE ); 735 sofar += i; 736 len -= i; 737 738 assert( len >= 0 ); 739 740 if ( sep < 3 ) { 741 goto done; 742 } 743 s[sofar++] = '?'; 744 len--; 745 746 assert( len >= 0 ); 747 748 if ( !BER_BVISNULL( &scope ) ) { 749 strcpy( &s[sofar], scope.bv_val ); 750 sofar += scope.bv_len; 751 len -= scope.bv_len; 752 } 753 754 assert( len >= 0 ); 755 756 if ( sep < 4 ) { 757 goto done; 758 } 759 s[sofar++] = '?'; 760 len--; 761 762 assert( len >= 0 ); 763 764 i = hex_escape( &s[sofar], len, u->lud_filter, URLESC_NONE ); 765 sofar += i; 766 len -= i; 767 768 assert( len >= 0 ); 769 770 if ( sep < 5 ) { 771 goto done; 772 } 773 s[sofar++] = '?'; 774 len--; 775 776 assert( len >= 0 ); 777 778 i = hex_escape_list( &s[sofar], len, u->lud_exts, URLESC_COMMA ); 779 sofar += i; 780 len -= i; 781 782 assert( len >= 0 ); 783 784 done: 785 if ( len < 0 ) { 786 return -1; 787 } 788 789 return sofar; 790 } 791 792 char * 793 ldap_url_desc2str( LDAPURLDesc *u ) 794 { 795 int len; 796 char *s; 797 798 if ( u == NULL ) { 799 return NULL; 800 } 801 802 len = desc2str_len( u ); 803 if ( len < 0 ) { 804 return NULL; 805 } 806 807 /* allocate enough to hex escape everything -- overkill */ 808 s = LDAP_MALLOC( len + 1 ); 809 810 if ( s == NULL ) { 811 return NULL; 812 } 813 814 if ( desc2str( u, s, len ) != len ) { 815 LDAP_FREE( s ); 816 return NULL; 817 } 818 819 s[len] = '\0'; 820 821 return s; 822 } 823 824 int 825 ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp, unsigned flags ) 826 { 827 /* 828 * Pick apart the pieces of an LDAP URL. 829 */ 830 831 LDAPURLDesc *ludp; 832 char *p, *q, *r; 833 int i, enclosed, proto, is_v6 = 0; 834 const char *scheme = NULL; 835 const char *url_tmp; 836 char *url; 837 838 int check_dn = 1; 839 840 if( url_in == NULL || ludpp == NULL ) { 841 return LDAP_URL_ERR_PARAM; 842 } 843 844 #ifndef LDAP_INT_IN_KERNEL 845 /* Global options may not be created yet 846 * We can't test if the global options are initialized 847 * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate 848 * the options and cause infinite recursion 849 */ 850 Debug1( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in ); 851 #endif 852 853 *ludpp = NULL; /* pessimistic */ 854 855 url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); 856 857 if ( url_tmp == NULL ) { 858 return LDAP_URL_ERR_BADSCHEME; 859 } 860 861 assert( scheme != NULL ); 862 863 proto = ldap_pvt_url_scheme2proto( scheme ); 864 if ( proto == -1 ) { 865 return LDAP_URL_ERR_BADSCHEME; 866 } 867 868 /* make working copy of the remainder of the URL */ 869 url = LDAP_STRDUP( url_tmp ); 870 if ( url == NULL ) { 871 return LDAP_URL_ERR_MEM; 872 } 873 874 if ( enclosed ) { 875 p = &url[strlen(url)-1]; 876 877 if( *p != '>' ) { 878 LDAP_FREE( url ); 879 return LDAP_URL_ERR_BADENCLOSURE; 880 } 881 882 *p = '\0'; 883 } 884 885 /* allocate return struct */ 886 ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); 887 888 if ( ludp == NULL ) { 889 LDAP_FREE( url ); 890 return LDAP_URL_ERR_MEM; 891 } 892 893 ludp->lud_next = NULL; 894 ludp->lud_host = NULL; 895 ludp->lud_port = 0; 896 ludp->lud_dn = NULL; 897 ludp->lud_attrs = NULL; 898 ludp->lud_scope = ( flags & LDAP_PVT_URL_PARSE_NODEF_SCOPE ) ? LDAP_SCOPE_BASE : LDAP_SCOPE_DEFAULT; 899 ludp->lud_filter = NULL; 900 ludp->lud_exts = NULL; 901 902 ludp->lud_scheme = LDAP_STRDUP( scheme ); 903 904 if ( ludp->lud_scheme == NULL ) { 905 LDAP_FREE( url ); 906 ldap_free_urldesc( ludp ); 907 return LDAP_URL_ERR_MEM; 908 } 909 910 /* scan forward for '/' that marks end of hostport and begin. of dn */ 911 p = strchr( url, '/' ); 912 q = NULL; 913 914 if( p != NULL ) { 915 /* terminate hostport; point to start of dn */ 916 *p++ = '\0'; 917 } else { 918 /* check for Novell kludge, see below */ 919 p = strchr( url, '?' ); 920 if ( p ) { 921 *p++ = '\0'; 922 q = p; 923 p = NULL; 924 } 925 } 926 927 if ( proto != LDAP_PROTO_IPC ) { 928 /* IPv6 syntax with [ip address]:port */ 929 if ( *url == '[' ) { 930 r = strchr( url, ']' ); 931 if ( r == NULL ) { 932 LDAP_FREE( url ); 933 ldap_free_urldesc( ludp ); 934 return LDAP_URL_ERR_BADURL; 935 } 936 *r++ = '\0'; 937 q = strchr( r, ':' ); 938 if ( q && q != r ) { 939 LDAP_FREE( url ); 940 ldap_free_urldesc( ludp ); 941 return LDAP_URL_ERR_BADURL; 942 } 943 is_v6 = 1; 944 } else { 945 q = strchr( url, ':' ); 946 } 947 948 if ( q != NULL ) { 949 char *next; 950 951 *q++ = '\0'; 952 ldap_pvt_hex_unescape( q ); 953 954 if( *q == '\0' ) { 955 LDAP_FREE( url ); 956 ldap_free_urldesc( ludp ); 957 return LDAP_URL_ERR_BADURL; 958 } 959 960 ludp->lud_port = strtol( q, &next, 10 ); 961 if ( next == q || next[0] != '\0' ) { 962 LDAP_FREE( url ); 963 ldap_free_urldesc( ludp ); 964 return LDAP_URL_ERR_BADURL; 965 } 966 /* check for Novell kludge */ 967 if ( !p ) { 968 if ( *next != '\0' ) { 969 q = &next[1]; 970 } else { 971 q = NULL; 972 } 973 } 974 } 975 976 if ( ( flags & LDAP_PVT_URL_PARSE_DEF_PORT ) && ludp->lud_port == 0 ) { 977 if ( strcmp( ludp->lud_scheme, "ldaps" ) == 0 ) { 978 ludp->lud_port = LDAPS_PORT; 979 } else { 980 ludp->lud_port = LDAP_PORT; 981 } 982 } 983 } 984 985 ldap_pvt_hex_unescape( url ); 986 987 /* If [ip address]:port syntax, url is [ip and we skip the [ */ 988 ludp->lud_host = LDAP_STRDUP( url + is_v6 ); 989 990 if( ludp->lud_host == NULL ) { 991 LDAP_FREE( url ); 992 ldap_free_urldesc( ludp ); 993 return LDAP_URL_ERR_MEM; 994 } 995 996 if ( ( flags & LDAP_PVT_URL_PARSE_NOEMPTY_HOST ) 997 && ludp->lud_host != NULL 998 && *ludp->lud_host == '\0' ) 999 { 1000 LDAP_FREE( ludp->lud_host ); 1001 ludp->lud_host = NULL; 1002 } 1003 1004 /* 1005 * Kludge. ldap://111.222.333.444:389??cn=abc,o=company 1006 * 1007 * On early Novell releases, search references/referrals were returned 1008 * in this format, i.e., the dn was kind of in the scope position, 1009 * but the required slash is missing. The whole thing is illegal syntax, 1010 * but we need to account for it. Fortunately it can't be confused with 1011 * anything real. 1012 */ 1013 if( (p == NULL) && (q != NULL) && (*q == '?') ) { 1014 /* ? immediately followed by question */ 1015 q++; 1016 if( *q != '\0' ) { 1017 /* parse dn part */ 1018 ldap_pvt_hex_unescape( q ); 1019 ludp->lud_dn = LDAP_STRDUP( q ); 1020 1021 } else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) { 1022 ludp->lud_dn = LDAP_STRDUP( "" ); 1023 1024 } else { 1025 check_dn = 0; 1026 } 1027 1028 if ( check_dn && ludp->lud_dn == NULL ) { 1029 LDAP_FREE( url ); 1030 ldap_free_urldesc( ludp ); 1031 return LDAP_URL_ERR_MEM; 1032 } 1033 } 1034 1035 if( p == NULL ) { 1036 LDAP_FREE( url ); 1037 *ludpp = ludp; 1038 return LDAP_URL_SUCCESS; 1039 } 1040 1041 /* scan forward for '?' that may marks end of dn */ 1042 q = strchr( p, '?' ); 1043 1044 if( q != NULL ) { 1045 /* terminate dn part */ 1046 *q++ = '\0'; 1047 } 1048 1049 if( *p != '\0' ) { 1050 /* parse dn part */ 1051 ldap_pvt_hex_unescape( p ); 1052 ludp->lud_dn = LDAP_STRDUP( p ); 1053 1054 } else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) { 1055 ludp->lud_dn = LDAP_STRDUP( "" ); 1056 1057 } else { 1058 check_dn = 0; 1059 } 1060 1061 if( check_dn && ludp->lud_dn == NULL ) { 1062 LDAP_FREE( url ); 1063 ldap_free_urldesc( ludp ); 1064 return LDAP_URL_ERR_MEM; 1065 } 1066 1067 if( q == NULL ) { 1068 /* no more */ 1069 LDAP_FREE( url ); 1070 *ludpp = ludp; 1071 return LDAP_URL_SUCCESS; 1072 } 1073 1074 /* scan forward for '?' that may marks end of attributes */ 1075 p = q; 1076 q = strchr( p, '?' ); 1077 1078 if( q != NULL ) { 1079 /* terminate attributes part */ 1080 *q++ = '\0'; 1081 } 1082 1083 if( *p != '\0' ) { 1084 /* parse attributes */ 1085 ldap_pvt_hex_unescape( p ); 1086 ludp->lud_attrs = ldap_str2charray( p, "," ); 1087 1088 if( ludp->lud_attrs == NULL ) { 1089 LDAP_FREE( url ); 1090 ldap_free_urldesc( ludp ); 1091 return LDAP_URL_ERR_BADATTRS; 1092 } 1093 } 1094 1095 if ( q == NULL ) { 1096 /* no more */ 1097 LDAP_FREE( url ); 1098 *ludpp = ludp; 1099 return LDAP_URL_SUCCESS; 1100 } 1101 1102 /* scan forward for '?' that may marks end of scope */ 1103 p = q; 1104 q = strchr( p, '?' ); 1105 1106 if( q != NULL ) { 1107 /* terminate the scope part */ 1108 *q++ = '\0'; 1109 } 1110 1111 if( *p != '\0' ) { 1112 /* parse the scope */ 1113 ldap_pvt_hex_unescape( p ); 1114 ludp->lud_scope = ldap_pvt_str2scope( p ); 1115 1116 if( ludp->lud_scope == -1 ) { 1117 LDAP_FREE( url ); 1118 ldap_free_urldesc( ludp ); 1119 return LDAP_URL_ERR_BADSCOPE; 1120 } 1121 } 1122 1123 if ( q == NULL ) { 1124 /* no more */ 1125 LDAP_FREE( url ); 1126 *ludpp = ludp; 1127 return LDAP_URL_SUCCESS; 1128 } 1129 1130 /* scan forward for '?' that may marks end of filter */ 1131 p = q; 1132 q = strchr( p, '?' ); 1133 1134 if( q != NULL ) { 1135 /* terminate the filter part */ 1136 *q++ = '\0'; 1137 } 1138 1139 if( *p != '\0' ) { 1140 /* parse the filter */ 1141 ldap_pvt_hex_unescape( p ); 1142 1143 if( ! *p ) { 1144 /* missing filter */ 1145 LDAP_FREE( url ); 1146 ldap_free_urldesc( ludp ); 1147 return LDAP_URL_ERR_BADFILTER; 1148 } 1149 1150 ludp->lud_filter = LDAP_STRDUP( p ); 1151 1152 if( ludp->lud_filter == NULL ) { 1153 LDAP_FREE( url ); 1154 ldap_free_urldesc( ludp ); 1155 return LDAP_URL_ERR_MEM; 1156 } 1157 } 1158 1159 if ( q == NULL ) { 1160 /* no more */ 1161 LDAP_FREE( url ); 1162 *ludpp = ludp; 1163 return LDAP_URL_SUCCESS; 1164 } 1165 1166 /* scan forward for '?' that may marks end of extensions */ 1167 p = q; 1168 q = strchr( p, '?' ); 1169 1170 if( q != NULL ) { 1171 /* extra '?' */ 1172 LDAP_FREE( url ); 1173 ldap_free_urldesc( ludp ); 1174 return LDAP_URL_ERR_BADURL; 1175 } 1176 1177 /* parse the extensions */ 1178 ludp->lud_exts = ldap_str2charray( p, "," ); 1179 1180 if( ludp->lud_exts == NULL ) { 1181 LDAP_FREE( url ); 1182 ldap_free_urldesc( ludp ); 1183 return LDAP_URL_ERR_BADEXTS; 1184 } 1185 1186 for( i=0; ludp->lud_exts[i] != NULL; i++ ) { 1187 ldap_pvt_hex_unescape( ludp->lud_exts[i] ); 1188 1189 if( *ludp->lud_exts[i] == '!' ) { 1190 /* count the number of critical extensions */ 1191 ludp->lud_crit_exts++; 1192 } 1193 } 1194 1195 if( i == 0 ) { 1196 /* must have 1 or more */ 1197 LDAP_FREE( url ); 1198 ldap_free_urldesc( ludp ); 1199 return LDAP_URL_ERR_BADEXTS; 1200 } 1201 1202 /* no more */ 1203 *ludpp = ludp; 1204 LDAP_FREE( url ); 1205 return LDAP_URL_SUCCESS; 1206 } 1207 1208 int 1209 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) 1210 { 1211 return ldap_url_parse_ext( url_in, ludpp, LDAP_PVT_URL_PARSE_HISTORIC ); 1212 } 1213 1214 LDAPURLDesc * 1215 ldap_url_dup ( LDAPURLDesc *ludp ) 1216 { 1217 LDAPURLDesc *dest; 1218 1219 if ( ludp == NULL ) { 1220 return NULL; 1221 } 1222 1223 dest = LDAP_MALLOC( sizeof(LDAPURLDesc) ); 1224 if (dest == NULL) 1225 return NULL; 1226 1227 *dest = *ludp; 1228 dest->lud_scheme = NULL; 1229 dest->lud_host = NULL; 1230 dest->lud_dn = NULL; 1231 dest->lud_filter = NULL; 1232 dest->lud_attrs = NULL; 1233 dest->lud_exts = NULL; 1234 dest->lud_next = NULL; 1235 1236 if ( ludp->lud_scheme != NULL ) { 1237 dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme ); 1238 if (dest->lud_scheme == NULL) { 1239 ldap_free_urldesc(dest); 1240 return NULL; 1241 } 1242 } 1243 1244 if ( ludp->lud_host != NULL ) { 1245 dest->lud_host = LDAP_STRDUP( ludp->lud_host ); 1246 if (dest->lud_host == NULL) { 1247 ldap_free_urldesc(dest); 1248 return NULL; 1249 } 1250 } 1251 1252 if ( ludp->lud_dn != NULL ) { 1253 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn ); 1254 if (dest->lud_dn == NULL) { 1255 ldap_free_urldesc(dest); 1256 return NULL; 1257 } 1258 } 1259 1260 if ( ludp->lud_filter != NULL ) { 1261 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter ); 1262 if (dest->lud_filter == NULL) { 1263 ldap_free_urldesc(dest); 1264 return NULL; 1265 } 1266 } 1267 1268 if ( ludp->lud_attrs != NULL ) { 1269 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs ); 1270 if (dest->lud_attrs == NULL) { 1271 ldap_free_urldesc(dest); 1272 return NULL; 1273 } 1274 } 1275 1276 if ( ludp->lud_exts != NULL ) { 1277 dest->lud_exts = ldap_charray_dup( ludp->lud_exts ); 1278 if (dest->lud_exts == NULL) { 1279 ldap_free_urldesc(dest); 1280 return NULL; 1281 } 1282 } 1283 1284 return dest; 1285 } 1286 1287 LDAPURLDesc * 1288 ldap_url_duplist (LDAPURLDesc *ludlist) 1289 { 1290 LDAPURLDesc *dest, *tail, *ludp, *newludp; 1291 1292 dest = NULL; 1293 tail = NULL; 1294 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { 1295 newludp = ldap_url_dup(ludp); 1296 if (newludp == NULL) { 1297 ldap_free_urllist(dest); 1298 return NULL; 1299 } 1300 if (tail == NULL) 1301 dest = newludp; 1302 else 1303 tail->lud_next = newludp; 1304 tail = newludp; 1305 } 1306 return dest; 1307 } 1308 1309 static int 1310 ldap_url_parselist_int (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags ) 1311 1312 { 1313 int i, rc; 1314 LDAPURLDesc *ludp; 1315 char **urls; 1316 1317 assert( ludlist != NULL ); 1318 assert( url != NULL ); 1319 1320 *ludlist = NULL; 1321 1322 if ( sep == NULL ) { 1323 sep = ", "; 1324 } 1325 1326 urls = ldap_str2charray( url, sep ); 1327 if (urls == NULL) 1328 return LDAP_URL_ERR_MEM; 1329 1330 /* count the URLs... */ 1331 for (i = 0; urls[i] != NULL; i++) ; 1332 /* ...and put them in the "stack" backward */ 1333 while (--i >= 0) { 1334 rc = ldap_url_parse_ext( urls[i], &ludp, flags ); 1335 if ( rc != 0 ) { 1336 ldap_charray_free( urls ); 1337 ldap_free_urllist( *ludlist ); 1338 *ludlist = NULL; 1339 return rc; 1340 } 1341 ludp->lud_next = *ludlist; 1342 *ludlist = ludp; 1343 } 1344 ldap_charray_free( urls ); 1345 return LDAP_URL_SUCCESS; 1346 } 1347 1348 int 1349 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url ) 1350 { 1351 return ldap_url_parselist_int( ludlist, url, ", ", LDAP_PVT_URL_PARSE_HISTORIC ); 1352 } 1353 1354 int 1355 ldap_url_parselist_ext (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags ) 1356 { 1357 return ldap_url_parselist_int( ludlist, url, sep, flags ); 1358 } 1359 1360 int 1361 ldap_url_parsehosts( 1362 LDAPURLDesc **ludlist, 1363 const char *hosts, 1364 int port ) 1365 { 1366 int i; 1367 LDAPURLDesc *ludp; 1368 char **specs, *p; 1369 1370 assert( ludlist != NULL ); 1371 assert( hosts != NULL ); 1372 1373 *ludlist = NULL; 1374 1375 specs = ldap_str2charray(hosts, ", "); 1376 if (specs == NULL) 1377 return LDAP_NO_MEMORY; 1378 1379 /* count the URLs... */ 1380 for (i = 0; specs[i] != NULL; i++) /* EMPTY */; 1381 1382 /* ...and put them in the "stack" backward */ 1383 while (--i >= 0) { 1384 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) ); 1385 if (ludp == NULL) { 1386 ldap_charray_free(specs); 1387 ldap_free_urllist(*ludlist); 1388 *ludlist = NULL; 1389 return LDAP_NO_MEMORY; 1390 } 1391 ludp->lud_port = port; 1392 ludp->lud_host = specs[i]; 1393 specs[i] = NULL; 1394 p = strchr(ludp->lud_host, ':'); 1395 if (p != NULL) { 1396 /* more than one :, IPv6 address */ 1397 if ( strchr(p+1, ':') != NULL ) { 1398 /* allow [address] and [address]:port */ 1399 if ( *ludp->lud_host == '[' ) { 1400 p = LDAP_STRDUP(ludp->lud_host+1); 1401 /* copied, make sure we free source later */ 1402 specs[i] = ludp->lud_host; 1403 ludp->lud_host = p; 1404 p = strchr( ludp->lud_host, ']' ); 1405 if ( p == NULL ) { 1406 LDAP_FREE(ludp); 1407 ldap_charray_free(specs); 1408 return LDAP_PARAM_ERROR; 1409 } 1410 *p++ = '\0'; 1411 if ( *p != ':' ) { 1412 if ( *p != '\0' ) { 1413 LDAP_FREE(ludp); 1414 ldap_charray_free(specs); 1415 return LDAP_PARAM_ERROR; 1416 } 1417 p = NULL; 1418 } 1419 } else { 1420 p = NULL; 1421 } 1422 } 1423 if (p != NULL) { 1424 char *next; 1425 1426 *p++ = 0; 1427 ldap_pvt_hex_unescape(p); 1428 ludp->lud_port = strtol( p, &next, 10 ); 1429 if ( next == p || next[0] != '\0' ) { 1430 LDAP_FREE(ludp); 1431 ldap_charray_free(specs); 1432 return LDAP_PARAM_ERROR; 1433 } 1434 } 1435 } 1436 ldap_pvt_hex_unescape(ludp->lud_host); 1437 ludp->lud_scheme = LDAP_STRDUP("ldap"); 1438 ludp->lud_next = *ludlist; 1439 *ludlist = ludp; 1440 } 1441 1442 /* this should be an array of NULLs now */ 1443 /* except entries starting with [ */ 1444 ldap_charray_free(specs); 1445 return LDAP_SUCCESS; 1446 } 1447 1448 char * 1449 ldap_url_list2hosts (LDAPURLDesc *ludlist) 1450 { 1451 LDAPURLDesc *ludp; 1452 int size; 1453 char *s, *p, buf[32]; /* big enough to hold a long decimal # (overkill) */ 1454 1455 if (ludlist == NULL) 1456 return NULL; 1457 1458 /* figure out how big the string is */ 1459 size = 1; /* nul-term */ 1460 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { 1461 if ( ludp->lud_host == NULL ) continue; 1462 size += strlen(ludp->lud_host) + 1; /* host and space */ 1463 if (strchr(ludp->lud_host, ':')) /* will add [ ] below */ 1464 size += 2; 1465 if (ludp->lud_port != 0) 1466 size += sprintf(buf, ":%d", ludp->lud_port); 1467 } 1468 s = LDAP_MALLOC(size); 1469 if (s == NULL) 1470 return NULL; 1471 1472 p = s; 1473 for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) { 1474 if ( ludp->lud_host == NULL ) continue; 1475 if (strchr(ludp->lud_host, ':')) { 1476 p += sprintf(p, "[%s]", ludp->lud_host); 1477 } else { 1478 strcpy(p, ludp->lud_host); 1479 p += strlen(ludp->lud_host); 1480 } 1481 if (ludp->lud_port != 0) 1482 p += sprintf(p, ":%d", ludp->lud_port); 1483 *p++ = ' '; 1484 } 1485 if (p != s) 1486 p--; /* nuke that extra space */ 1487 *p = '\0'; 1488 return s; 1489 } 1490 1491 char * 1492 ldap_url_list2urls( 1493 LDAPURLDesc *ludlist ) 1494 { 1495 LDAPURLDesc *ludp; 1496 int size, sofar; 1497 char *s; 1498 1499 if ( ludlist == NULL ) { 1500 return NULL; 1501 } 1502 1503 /* figure out how big the string is */ 1504 for ( size = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) { 1505 int len = desc2str_len( ludp ); 1506 if ( len < 0 ) { 1507 return NULL; 1508 } 1509 size += len + 1; 1510 } 1511 1512 s = LDAP_MALLOC( size ); 1513 1514 if ( s == NULL ) { 1515 return NULL; 1516 } 1517 1518 for ( sofar = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) { 1519 int len; 1520 1521 len = desc2str( ludp, &s[sofar], size ); 1522 1523 if ( len < 0 ) { 1524 LDAP_FREE( s ); 1525 return NULL; 1526 } 1527 1528 sofar += len; 1529 size -= len; 1530 1531 s[sofar++] = ' '; 1532 size--; 1533 1534 assert( size >= 0 ); 1535 } 1536 1537 s[sofar - 1] = '\0'; 1538 1539 return s; 1540 } 1541 1542 void 1543 ldap_free_urllist( LDAPURLDesc *ludlist ) 1544 { 1545 LDAPURLDesc *ludp, *next; 1546 1547 for (ludp = ludlist; ludp != NULL; ludp = next) { 1548 next = ludp->lud_next; 1549 ldap_free_urldesc(ludp); 1550 } 1551 } 1552 1553 void 1554 ldap_free_urldesc( LDAPURLDesc *ludp ) 1555 { 1556 if ( ludp == NULL ) { 1557 return; 1558 } 1559 1560 if ( ludp->lud_scheme != NULL ) { 1561 LDAP_FREE( ludp->lud_scheme ); 1562 } 1563 1564 if ( ludp->lud_host != NULL ) { 1565 LDAP_FREE( ludp->lud_host ); 1566 } 1567 1568 if ( ludp->lud_dn != NULL ) { 1569 LDAP_FREE( ludp->lud_dn ); 1570 } 1571 1572 if ( ludp->lud_filter != NULL ) { 1573 LDAP_FREE( ludp->lud_filter); 1574 } 1575 1576 if ( ludp->lud_attrs != NULL ) { 1577 LDAP_VFREE( ludp->lud_attrs ); 1578 } 1579 1580 if ( ludp->lud_exts != NULL ) { 1581 LDAP_VFREE( ludp->lud_exts ); 1582 } 1583 1584 LDAP_FREE( ludp ); 1585 } 1586 1587 static int 1588 ldap_int_is_hexpair( char *s ) 1589 { 1590 int i; 1591 1592 for ( i = 0; i < 2; i++ ) { 1593 if ( s[i] >= '0' && s[i] <= '9' ) { 1594 continue; 1595 } 1596 1597 if ( s[i] >= 'A' && s[i] <= 'F' ) { 1598 continue; 1599 } 1600 1601 if ( s[i] >= 'a' && s[i] <= 'f' ) { 1602 continue; 1603 } 1604 1605 return 0; 1606 } 1607 1608 return 1; 1609 } 1610 1611 static int 1612 ldap_int_unhex( int c ) 1613 { 1614 return( c >= '0' && c <= '9' ? c - '0' 1615 : c >= 'A' && c <= 'F' ? c - 'A' + 10 1616 : c - 'a' + 10 ); 1617 } 1618 1619 void 1620 ldap_pvt_hex_unescape( char *s ) 1621 { 1622 /* 1623 * Remove URL hex escapes from s... done in place. The basic concept for 1624 * this routine is borrowed from the WWW library HTUnEscape() routine. 1625 */ 1626 char *p, 1627 *save_s = s; 1628 1629 for ( p = s; *s != '\0'; ++s ) { 1630 if ( *s == '%' ) { 1631 /* 1632 * FIXME: what if '%' is followed 1633 * by non-hexpair chars? 1634 */ 1635 if ( !ldap_int_is_hexpair( s + 1 ) ) { 1636 p = save_s; 1637 break; 1638 } 1639 1640 if ( *++s == '\0' ) { 1641 break; 1642 } 1643 *p = ldap_int_unhex( *s ) << 4; 1644 if ( *++s == '\0' ) { 1645 break; 1646 } 1647 *p++ += ldap_int_unhex( *s ); 1648 } else { 1649 *p++ = *s; 1650 } 1651 } 1652 1653 *p = '\0'; 1654 } 1655 1656