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