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