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