1/*	$NetBSD$	*/
2
3/* LIBLDAP url.c -- LDAP URL (RFC 4516) related routines */
4/* OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.94.2.11 2010/04/13 20:23:01 kurt Exp */
5/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 1998-2010 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 */
51static const char* skip_url_prefix LDAP_P((
52	const char *url,
53	int *enclosedp,
54	const char **scheme ));
55
56int 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
84int 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
112int
113ldap_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
124int
125ldap_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
141int
142ldap_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
158int
159ldap_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
176int
177ldap_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
194static const char*
195skip_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
262int
263ldap_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
289const char *
290ldap_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
301int
302ldap_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
329int
330ldap_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
339static const char	hex[] = "0123456789ABCDEF";
340
341#define URLESC_NONE	0x0000U
342#define URLESC_COMMA	0x0001U
343#define URLESC_SLASH	0x0002U
344
345static int
346hex_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
412static int
413hex_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
486static int
487hex_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
507static int
508hex_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
533static int
534desc2str_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
608static int
609desc2str( 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
753done:
754	if ( len < 0 ) {
755		return -1;
756	}
757
758	return sofar;
759}
760
761char *
762ldap_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
793int
794ldap_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
1177int
1178ldap_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
1183LDAPURLDesc *
1184ldap_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
1256LDAPURLDesc *
1257ldap_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
1278static int
1279ldap_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
1317int
1318ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
1319{
1320	return ldap_url_parselist_int( ludlist, url, ", ", LDAP_PVT_URL_PARSE_HISTORIC );
1321}
1322
1323int
1324ldap_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
1329int
1330ldap_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
1417char *
1418ldap_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		size += strlen(ludp->lud_host) + 1;		/* host and space */
1431		if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1432			size += 2;
1433		if (ludp->lud_port != 0)
1434			size += sprintf(buf, ":%d", ludp->lud_port);
1435	}
1436	s = LDAP_MALLOC(size);
1437	if (s == NULL)
1438		return NULL;
1439
1440	p = s;
1441	for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1442		if (strchr(ludp->lud_host, ':')) {
1443			p += sprintf(p, "[%s]", ludp->lud_host);
1444		} else {
1445			strcpy(p, ludp->lud_host);
1446			p += strlen(ludp->lud_host);
1447		}
1448		if (ludp->lud_port != 0)
1449			p += sprintf(p, ":%d", ludp->lud_port);
1450		*p++ = ' ';
1451	}
1452	if (p != s)
1453		p--;	/* nuke that extra space */
1454	*p = 0;
1455	return s;
1456}
1457
1458char *
1459ldap_url_list2urls(
1460	LDAPURLDesc *ludlist )
1461{
1462	LDAPURLDesc	*ludp;
1463	int		size, sofar;
1464	char		*s;
1465
1466	if ( ludlist == NULL ) {
1467		return NULL;
1468	}
1469
1470	/* figure out how big the string is */
1471	for ( size = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) {
1472		int	len = desc2str_len( ludp );
1473		if ( len < 0 ) {
1474			return NULL;
1475		}
1476		size += len + 1;
1477	}
1478
1479	s = LDAP_MALLOC( size );
1480
1481	if ( s == NULL ) {
1482		return NULL;
1483	}
1484
1485	for ( sofar = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) {
1486		int	len;
1487
1488		len = desc2str( ludp, &s[sofar], size );
1489
1490		if ( len < 0 ) {
1491			LDAP_FREE( s );
1492			return NULL;
1493		}
1494
1495		sofar += len;
1496		size -= len;
1497
1498		s[sofar++] = ' ';
1499		size--;
1500
1501		assert( size >= 0 );
1502	}
1503
1504	s[sofar - 1] = '\0';
1505
1506	return s;
1507}
1508
1509void
1510ldap_free_urllist( LDAPURLDesc *ludlist )
1511{
1512	LDAPURLDesc *ludp, *next;
1513
1514	for (ludp = ludlist; ludp != NULL; ludp = next) {
1515		next = ludp->lud_next;
1516		ldap_free_urldesc(ludp);
1517	}
1518}
1519
1520void
1521ldap_free_urldesc( LDAPURLDesc *ludp )
1522{
1523	if ( ludp == NULL ) {
1524		return;
1525	}
1526
1527	if ( ludp->lud_scheme != NULL ) {
1528		LDAP_FREE( ludp->lud_scheme );
1529	}
1530
1531	if ( ludp->lud_host != NULL ) {
1532		LDAP_FREE( ludp->lud_host );
1533	}
1534
1535	if ( ludp->lud_dn != NULL ) {
1536		LDAP_FREE( ludp->lud_dn );
1537	}
1538
1539	if ( ludp->lud_filter != NULL ) {
1540		LDAP_FREE( ludp->lud_filter);
1541	}
1542
1543	if ( ludp->lud_attrs != NULL ) {
1544		LDAP_VFREE( ludp->lud_attrs );
1545	}
1546
1547	if ( ludp->lud_exts != NULL ) {
1548		LDAP_VFREE( ludp->lud_exts );
1549	}
1550
1551	LDAP_FREE( ludp );
1552}
1553
1554static int
1555ldap_int_is_hexpair( char *s )
1556{
1557	int	i;
1558
1559	for ( i = 0; i < 2; i++ ) {
1560		if ( s[i] >= '0' && s[i] <= '9' ) {
1561			continue;
1562		}
1563
1564		if ( s[i] >= 'A' && s[i] <= 'F' ) {
1565			continue;
1566		}
1567
1568		if ( s[i] >= 'a' && s[i] <= 'f' ) {
1569			continue;
1570		}
1571
1572		return 0;
1573	}
1574
1575	return 1;
1576}
1577
1578static int
1579ldap_int_unhex( int c )
1580{
1581	return( c >= '0' && c <= '9' ? c - '0'
1582	    : c >= 'A' && c <= 'F' ? c - 'A' + 10
1583	    : c - 'a' + 10 );
1584}
1585
1586void
1587ldap_pvt_hex_unescape( char *s )
1588{
1589	/*
1590	 * Remove URL hex escapes from s... done in place.  The basic concept for
1591	 * this routine is borrowed from the WWW library HTUnEscape() routine.
1592	 */
1593	char	*p,
1594		*save_s = s;
1595
1596	for ( p = s; *s != '\0'; ++s ) {
1597		if ( *s == '%' ) {
1598			/*
1599			 * FIXME: what if '%' is followed
1600			 * by non-hexpair chars?
1601			 */
1602			if ( !ldap_int_is_hexpair( s + 1 ) ) {
1603				p = save_s;
1604				break;
1605			}
1606
1607			if ( *++s == '\0' ) {
1608				break;
1609			}
1610			*p = ldap_int_unhex( *s ) << 4;
1611			if ( *++s == '\0' ) {
1612				break;
1613			}
1614			*p++ += ldap_int_unhex( *s );
1615		} else {
1616			*p++ = *s;
1617		}
1618	}
1619
1620	*p = '\0';
1621}
1622
1623