]> git.sur5r.net Git - openldap/blob - libraries/libldap/url.c
We'll need queue macros in -llber...
[openldap] / libraries / libldap / url.c
1 /* $OpenLDAP$ */
2 /*
3  * Copyright 1998-2000 The OpenLDAP Foundation, All Rights Reserved.
4  * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
5  */
6 /*  Portions
7  *  Copyright (c) 1996 Regents of the University of Michigan.
8  *  All rights reserved.
9  *
10  *  LIBLDAP url.c -- LDAP URL (RFC 2255) related routines
11  *
12  *  LDAP URLs look like this:
13  *    ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
14  *
15  *  where:
16  *   attributes is a comma separated list
17  *   scope is one of these three strings:  base one sub (default=base)
18  *   filter is an string-represented filter as in RFC 2254
19  *
20  *  e.g.,  ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension
21  *
22  *  We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
23  */
24
25 #include "portable.h"
26
27 #include <stdio.h>
28
29 #include <ac/stdlib.h>
30
31 #include <ac/socket.h>
32 #include <ac/string.h>
33 #include <ac/time.h>
34
35 #include "ldap-int.h"
36
37
38 /* local functions */
39 static const char* skip_url_prefix LDAP_P((
40         const char *url,
41         int *enclosedp,
42         const char **scheme ));
43
44 int ldap_pvt_url_scheme2proto( const char *scheme )
45 {
46         assert( scheme );
47
48         if( scheme == NULL ) {
49                 return -1;
50         }
51
52         if( strcmp("ldap", scheme) == 0 ) {
53                 return LDAP_PROTO_TCP;
54         }
55
56         if( strcmp("ldapi", scheme) == 0 ) {
57                 return LDAP_PROTO_IPC;
58         }
59
60         if( strcmp("ldaps", scheme) == 0 ) {
61                 return LDAP_PROTO_TCP;
62         }
63 #ifdef LDAP_CONNECTIONLESS
64         if( strcmp("cldap", scheme) == 0 ) {
65                 return LDAP_PROTO_UDP;
66         }
67 #endif
68
69         return -1;
70 }
71
72 int
73 ldap_pvt_url_scheme2tls( const char *scheme )
74 {
75         assert( scheme );
76
77         if( scheme == NULL ) {
78                 return -1;
79         }
80
81         return strcmp("ldaps", scheme) == 0;
82 }
83
84 int
85 ldap_is_ldap_url( LDAP_CONST char *url )
86 {
87         int     enclosed;
88         const char * scheme;
89
90         if( url == NULL ) {
91                 return 0;
92         }
93
94         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
95                 return 0;
96         }
97
98         return 1;
99 }
100
101 int
102 ldap_is_ldaps_url( LDAP_CONST char *url )
103 {
104         int     enclosed;
105         const char * scheme;
106
107         if( url == NULL ) {
108                 return 0;
109         }
110
111         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
112                 return 0;
113         }
114
115         return strcmp(scheme, "ldaps") == 0;
116 }
117
118 int
119 ldap_is_ldapi_url( LDAP_CONST char *url )
120 {
121         int     enclosed;
122         const char * scheme;
123
124         if( url == NULL ) {
125                 return 0;
126         }
127
128         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
129                 return 0;
130         }
131
132         return strcmp(scheme, "ldapi") == 0;
133 }
134
135 #ifdef LDAP_CONNECTIONLESS
136 int
137 ldap_is_ldapc_url( LDAP_CONST char *url )
138 {
139         int     enclosed;
140         const char * scheme;
141
142         if( url == NULL ) {
143                 return 0;
144         }
145
146         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
147                 return 0;
148         }
149
150         return strcmp(scheme, "cldap") == 0;
151 }
152 #endif
153
154 static const char*
155 skip_url_prefix(
156         const char *url,
157         int *enclosedp,
158         const char **scheme )
159 {
160         /*
161          * return non-zero if this looks like a LDAP URL; zero if not
162          * if non-zero returned, *urlp will be moved past "ldap://" part of URL
163          */
164         const char *p;
165
166         if ( url == NULL ) {
167                 return( NULL );
168         }
169
170         p = url;
171
172         /* skip leading '<' (if any) */
173         if ( *p == '<' ) {
174                 *enclosedp = 1;
175                 ++p;
176         } else {
177                 *enclosedp = 0;
178         }
179
180         /* skip leading "URL:" (if any) */
181         if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) {
182                 p += LDAP_URL_URLCOLON_LEN;
183         }
184
185         /* check for "ldap://" prefix */
186         if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
187                 /* skip over "ldap://" prefix and return success */
188                 p += LDAP_URL_PREFIX_LEN;
189                 *scheme = "ldap";
190                 return( p );
191         }
192
193         /* check for "ldaps://" prefix */
194         if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
195                 /* skip over "ldaps://" prefix and return success */
196                 p += LDAPS_URL_PREFIX_LEN;
197                 *scheme = "ldaps";
198                 return( p );
199         }
200
201         /* check for "ldapi://" prefix */
202         if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
203                 /* skip over "ldapi://" prefix and return success */
204                 p += LDAPI_URL_PREFIX_LEN;
205                 *scheme = "ldapi";
206                 return( p );
207         }
208
209 #ifdef LDAP_CONNECTIONLESS
210         /* check for "cldap://" prefix */
211         if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) {
212                 /* skip over "cldap://" prefix and return success */
213                 p += LDAPC_URL_PREFIX_LEN;
214                 *scheme = "cldap";
215                 return( p );
216         }
217 #endif
218
219         return( NULL );
220 }
221
222
223 static int str2scope( const char *p )
224 {
225         if ( strcasecmp( p, "one" ) == 0 ) {
226                 return LDAP_SCOPE_ONELEVEL;
227
228         } else if ( strcasecmp( p, "onetree" ) == 0 ) {
229                 return LDAP_SCOPE_ONELEVEL;
230
231         } else if ( strcasecmp( p, "base" ) == 0 ) {
232                 return LDAP_SCOPE_BASE;
233
234         } else if ( strcasecmp( p, "sub" ) == 0 ) {
235                 return LDAP_SCOPE_SUBTREE;
236
237         } else if ( strcasecmp( p, "subtree" ) == 0 ) {
238                 return LDAP_SCOPE_SUBTREE;
239         }
240
241         return( -1 );
242 }
243
244 static int hex_escape( char *buf, const char *s, int list )
245 {
246         int i;
247         int pos;
248         static const char hex[] = "0123456789ABCDEF";
249
250         if( s == NULL ) return 0;
251
252         for( pos=0,i=0; s[i]; i++ ) {
253                 int escape = 0;
254                 switch( s[i] ) {
255                         case ',':
256                                 escape = list;
257                                 break;
258                         case '%':
259                         case '?':
260                         case ' ':
261                         case '<':
262                         case '>':
263                         case '"':
264                         case '#':
265                         case '{':
266                         case '}':
267                         case '|':
268                         case '\\':
269                         case '^':
270                         case '~':
271                         case '`':
272                         case '[':
273                         case ']':
274                                 escape = 1;
275                                 break;
276
277                         default:
278                                 escape = s[i] < 0x20 || 0x1f >= s[i];
279                 }
280
281                 if( escape ) {
282                         buf[pos++] = '%';
283                         buf[pos++] = hex[ (s[i] >> 4) & 0x0f ];
284                         buf[pos++] = hex[ s[i] & 0x0f ];
285                 } else {
286                         buf[pos++] = s[i];
287                 }
288         }
289
290         buf[pos] = '\0';
291         return pos;
292 }
293
294 static int hex_escape_args( char *buf, char **s )
295 {
296         int pos;
297         int i;
298
299         if( s == NULL ) return 0;
300
301         pos = 0;
302         for( i=0; s[i] != NULL; i++ ) {
303                 if( pos ) {
304                         buf[pos++] = ',';
305                 }
306                 pos += hex_escape( &buf[pos], s[i], 1 );
307         }
308
309         return pos;
310 }
311
312 char * ldap_url_desc2str( LDAPURLDesc *u )
313 {
314         char *s;
315         int i;
316         int sep = 0;
317         int sofar;
318         size_t len = 0;
319         if( u == NULL ) return NULL;
320
321         if( u->lud_exts ) {
322                 for( i=0; u->lud_exts[i]; i++ ) {
323                         len += strlen( u->lud_exts[i] ) + 1;
324                 }
325                 if( !sep ) sep = 5;
326         }
327
328         if( u->lud_filter ) {
329                 len += strlen( u->lud_filter );
330                 if( !sep ) sep = 4;
331         }
332         if ( len ) len++; /* ? */
333
334         switch( u->lud_scope ) {
335                 case LDAP_SCOPE_ONELEVEL:
336                 case LDAP_SCOPE_SUBTREE:
337                 case LDAP_SCOPE_BASE:
338                         len += sizeof("base");
339                         if( !sep ) sep = 3;
340                         break;
341
342                 default:
343                         if ( len ) len++; /* ? */
344         }
345
346         if( u->lud_attrs ) {
347                 for( i=0; u->lud_attrs[i]; i++ ) {
348                         len += strlen( u->lud_attrs[i] ) + 1;
349                 }
350                 if( !sep ) sep = 2;
351         } else if ( len ) len++; /* ? */
352
353         if( u->lud_dn ) {
354                 len += strlen( u->lud_dn ) + 1;
355                 if( !sep ) sep = 1;
356         };
357
358         if( u->lud_port ) {
359                 len+=6;
360         }
361
362         if( u->lud_host ) {
363                 len+=strlen( u->lud_host );
364         }
365
366         len += strlen( u->lud_scheme ) + sizeof("://");
367
368         /* allocate enough to hex escape everything -- overkill */
369         s = LDAP_MALLOC( 3*len );
370
371         if( s == NULL ) return NULL;
372
373         if( u->lud_port ) {
374                 sprintf( s,     "%s://%s:%d%n", u->lud_scheme,
375                         u->lud_host, u->lud_port, &sofar );
376         } else {
377                 sprintf( s,     "%s://%s%n", u->lud_scheme,
378                         u->lud_host, &sofar );
379         }
380         
381         if( sep < 1 ) goto done;
382         s[sofar++] = '/';
383
384         sofar += hex_escape( &s[sofar], u->lud_dn, 0 );
385
386         if( sep < 2 ) goto done;
387         s[sofar++] = '?';
388
389         sofar += hex_escape_args( &s[sofar], u->lud_attrs );
390
391         if( sep < 3 ) goto done;
392         s[sofar++] = '?';
393
394         switch( u->lud_scope ) {
395         case LDAP_SCOPE_BASE:
396                 strcpy( &s[sofar], "base" );
397                 sofar += sizeof("base") - 1;
398                 break;
399         case LDAP_SCOPE_ONELEVEL:
400                 strcpy( &s[sofar], "one" );
401                 sofar += sizeof("one") - 1;
402                 break;
403         case LDAP_SCOPE_SUBTREE:
404                 strcpy( &s[sofar], "sub" );
405                 sofar += sizeof("sub") - 1;
406                 break;
407         }
408
409         if( sep < 4 ) goto done;
410         s[sofar++] = '?';
411
412         sofar += hex_escape( &s[sofar], u->lud_filter, 0 );
413
414         if( sep < 5 ) goto done;
415         s[sofar++] = '?';
416
417         sofar += hex_escape_args( &s[sofar], u->lud_exts );
418
419 done:
420         s[sofar] = '\0';
421         return s;
422 }
423
424 int
425 ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
426 {
427 /*
428  *  Pick apart the pieces of an LDAP URL.
429  */
430
431         LDAPURLDesc     *ludp;
432         char    *p, *q, *r;
433         int             i, enclosed;
434         const char *scheme = NULL;
435         const char *url_tmp;
436         char *url;
437
438         if( url_in == NULL || ludpp == NULL ) {
439                 return LDAP_URL_ERR_PARAM;
440         }
441
442 #ifndef LDAP_INT_IN_KERNEL
443         /* Global options may not be created yet
444          * We can't test if the global options are initialized
445          * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate
446          * the options and cause infinite recursion
447          */
448         Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 );
449 #endif
450
451         *ludpp = NULL;  /* pessimistic */
452
453         url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
454
455         if ( url_tmp == NULL ) {
456                 return LDAP_URL_ERR_BADSCHEME;
457         }
458
459         assert( scheme );
460
461         /* make working copy of the remainder of the URL */
462         url = LDAP_STRDUP( url_tmp );
463         if ( url == NULL ) {
464                 return LDAP_URL_ERR_MEM;
465         }
466
467         if ( enclosed ) {
468                 p = &url[strlen(url)-1];
469
470                 if( *p != '>' ) {
471                         LDAP_FREE( url );
472                         return LDAP_URL_ERR_BADENCLOSURE;
473                 }
474
475                 *p = '\0';
476         }
477
478         /* allocate return struct */
479         ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
480
481         if ( ludp == NULL ) {
482                 LDAP_FREE( url );
483                 return LDAP_URL_ERR_MEM;
484         }
485
486         ludp->lud_next = NULL;
487         ludp->lud_host = NULL;
488         ludp->lud_port = 0;
489         ludp->lud_dn = NULL;
490         ludp->lud_attrs = NULL;
491         ludp->lud_filter = NULL;
492         ludp->lud_scope = LDAP_SCOPE_DEFAULT;
493         ludp->lud_filter = NULL;
494         ludp->lud_exts = NULL;
495
496         ludp->lud_scheme = LDAP_STRDUP( scheme );
497
498         if ( ludp->lud_scheme == NULL ) {
499                 LDAP_FREE( url );
500                 ldap_free_urldesc( ludp );
501                 return LDAP_URL_ERR_MEM;
502         }
503
504         /* scan forward for '/' that marks end of hostport and begin. of dn */
505         p = strchr( url, '/' );
506
507         if( p != NULL ) {
508                 /* terminate hostport; point to start of dn */
509                 *p++ = '\0';
510         }
511
512         /* IPv6 syntax with [ip address]:port */
513         if ( *url == '[' ) {
514                 r = strchr( url, ']' );
515                 if ( r == NULL ) {
516                         LDAP_FREE( url );
517                         ldap_free_urldesc( ludp );
518                         return LDAP_URL_ERR_BADURL;
519                 }
520                 *r++ = '\0';
521                 q = strchr( r, ':' );
522         } else {
523                 q = strchr( url, ':' );
524         }
525
526         if ( q != NULL ) {
527                 *q++ = '\0';
528                 ldap_pvt_hex_unescape( q );
529
530                 if( *q == '\0' ) {
531                         LDAP_FREE( url );
532                         ldap_free_urldesc( ludp );
533                         return LDAP_URL_ERR_BADURL;
534                 }
535
536                 ludp->lud_port = atoi( q );
537         }
538
539         ldap_pvt_hex_unescape( url );
540
541         /* If [ip address]:port syntax, url is [ip and we skip the [ */
542         ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) );
543
544         if( ludp->lud_host == NULL ) {
545                 LDAP_FREE( url );
546                 ldap_free_urldesc( ludp );
547                 return LDAP_URL_ERR_MEM;
548         }
549
550         /*
551          * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
552          *
553          * On early Novell releases, search references/referrals were returned
554          * in this format, i.e., the dn was kind of in the scope position,
555          * but the required slash is missing. The whole thing is illegal syntax,
556          * but we need to account for it. Fortunately it can't be confused with
557          * anything real.
558          */
559         if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
560                 q++;            
561                 /* ? immediately followed by question */
562                 if( *q == '?') {
563                         q++;
564                         if( *q != '\0' ) {
565                                 /* parse dn part */
566                                 ldap_pvt_hex_unescape( q );
567                                 ludp->lud_dn = LDAP_STRDUP( q );
568                         } else {
569                                 ludp->lud_dn = LDAP_STRDUP( "" );
570                         }
571
572                         if( ludp->lud_dn == NULL ) {
573                                 LDAP_FREE( url );
574                                 ldap_free_urldesc( ludp );
575                                 return LDAP_URL_ERR_MEM;
576                         }
577                 }
578         }
579
580         if( p == NULL ) {
581                 LDAP_FREE( url );
582                 *ludpp = ludp;
583                 return LDAP_URL_SUCCESS;
584         }
585
586         /* scan forward for '?' that may marks end of dn */
587         q = strchr( p, '?' );
588
589         if( q != NULL ) {
590                 /* terminate dn part */
591                 *q++ = '\0';
592         }
593
594         if( *p != '\0' ) {
595                 /* parse dn part */
596                 ldap_pvt_hex_unescape( p );
597                 ludp->lud_dn = LDAP_STRDUP( p );
598         } else {
599                 ludp->lud_dn = LDAP_STRDUP( "" );
600         }
601
602         if( ludp->lud_dn == NULL ) {
603                 LDAP_FREE( url );
604                 ldap_free_urldesc( ludp );
605                 return LDAP_URL_ERR_MEM;
606         }
607
608         if( q == NULL ) {
609                 /* no more */
610                 LDAP_FREE( url );
611                 *ludpp = ludp;
612                 return LDAP_URL_SUCCESS;
613         }
614
615         /* scan forward for '?' that may marks end of attributes */
616         p = q;
617         q = strchr( p, '?' );
618
619         if( q != NULL ) {
620                 /* terminate attributes part */
621                 *q++ = '\0';
622         }
623
624         if( *p != '\0' ) {
625                 /* parse attributes */
626                 ldap_pvt_hex_unescape( p );
627                 ludp->lud_attrs = ldap_str2charray( p, "," );
628
629                 if( ludp->lud_attrs == NULL ) {
630                         LDAP_FREE( url );
631                         ldap_free_urldesc( ludp );
632                         return LDAP_URL_ERR_BADATTRS;
633                 }
634         }
635
636         if ( q == NULL ) {
637                 /* no more */
638                 LDAP_FREE( url );
639                 *ludpp = ludp;
640                 return LDAP_URL_SUCCESS;
641         }
642
643         /* scan forward for '?' that may marks end of scope */
644         p = q;
645         q = strchr( p, '?' );
646
647         if( q != NULL ) {
648                 /* terminate the scope part */
649                 *q++ = '\0';
650         }
651
652         if( *p != '\0' ) {
653                 /* parse the scope */
654                 ldap_pvt_hex_unescape( p );
655                 ludp->lud_scope = str2scope( p );
656
657                 if( ludp->lud_scope == -1 ) {
658                         LDAP_FREE( url );
659                         ldap_free_urldesc( ludp );
660                         return LDAP_URL_ERR_BADSCOPE;
661                 }
662         }
663
664         if ( q == NULL ) {
665                 /* no more */
666                 LDAP_FREE( url );
667                 *ludpp = ludp;
668                 return LDAP_URL_SUCCESS;
669         }
670
671         /* scan forward for '?' that may marks end of filter */
672         p = q;
673         q = strchr( p, '?' );
674
675         if( q != NULL ) {
676                 /* terminate the filter part */
677                 *q++ = '\0';
678         }
679
680         if( *p != '\0' ) {
681                 /* parse the filter */
682                 ldap_pvt_hex_unescape( p );
683
684                 if( ! *p ) {
685                         /* missing filter */
686                         LDAP_FREE( url );
687                         ldap_free_urldesc( ludp );
688                         return LDAP_URL_ERR_BADFILTER;
689                 }
690
691                 LDAP_FREE( ludp->lud_filter );
692                 ludp->lud_filter = LDAP_STRDUP( p );
693
694                 if( ludp->lud_filter == NULL ) {
695                         LDAP_FREE( url );
696                         ldap_free_urldesc( ludp );
697                         return LDAP_URL_ERR_MEM;
698                 }
699         }
700
701         if ( q == NULL ) {
702                 /* no more */
703                 LDAP_FREE( url );
704                 *ludpp = ludp;
705                 return LDAP_URL_SUCCESS;
706         }
707
708         /* scan forward for '?' that may marks end of extensions */
709         p = q;
710         q = strchr( p, '?' );
711
712         if( q != NULL ) {
713                 /* extra '?' */
714                 LDAP_FREE( url );
715                 ldap_free_urldesc( ludp );
716                 return LDAP_URL_ERR_BADURL;
717         }
718
719         /* parse the extensions */
720         ludp->lud_exts = ldap_str2charray( p, "," );
721
722         if( ludp->lud_exts == NULL ) {
723                 LDAP_FREE( url );
724                 ldap_free_urldesc( ludp );
725                 return LDAP_URL_ERR_BADEXTS;
726         }
727
728         for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
729                 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
730
731                 if( *ludp->lud_exts[i] == '!' ) {
732                         /* count the number of critical extensions */
733                         ludp->lud_crit_exts++;
734                 }
735         }
736
737         if( i == 0 ) {
738                 /* must have 1 or more */
739                 LDAP_FREE( url );
740                 ldap_free_urldesc( ludp );
741                 return LDAP_URL_ERR_BADEXTS;
742         }
743
744         /* no more */
745         *ludpp = ludp;
746         LDAP_FREE( url );
747         return LDAP_URL_SUCCESS;
748 }
749
750 int
751 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
752 {
753         int rc = ldap_url_parse_ext( url_in, ludpp );
754
755         if( rc != LDAP_URL_SUCCESS ) {
756                 return rc;
757         }
758
759         if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) {
760                 (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
761         }
762
763         if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
764                 LDAP_FREE( (*ludpp)->lud_host );
765                 (*ludpp)->lud_host = NULL;
766         }
767
768         if ((*ludpp)->lud_port == 0) {
769                 if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) {
770                         (*ludpp)->lud_port = LDAP_PORT;
771 #ifdef LDAP_CONNECTIONLESS
772                 } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) {
773                         (*ludpp)->lud_port = LDAP_PORT;
774 #endif
775                 } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) {
776                         (*ludpp)->lud_port = LDAPS_PORT;
777                 }
778         }
779
780         return rc;
781 }
782
783 LDAPURLDesc *
784 ldap_url_dup ( LDAPURLDesc *ludp )
785 {
786         LDAPURLDesc *dest;
787
788         if ( ludp == NULL ) {
789                 return NULL;
790         }
791
792         dest = LDAP_MALLOC( sizeof(LDAPURLDesc) );
793         if (dest == NULL)
794                 return NULL;
795         
796         *dest = *ludp;
797         dest->lud_scheme = NULL;
798         dest->lud_host = NULL;
799         dest->lud_dn = NULL;
800         dest->lud_filter = NULL;
801         dest->lud_attrs = NULL;
802         dest->lud_exts = NULL;
803         dest->lud_next = NULL;
804
805         if ( ludp->lud_scheme != NULL ) {
806                 dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme );
807                 if (dest->lud_scheme == NULL) {
808                         ldap_free_urldesc(dest);
809                         return NULL;
810                 }
811         }
812
813         if ( ludp->lud_host != NULL ) {
814                 dest->lud_host = LDAP_STRDUP( ludp->lud_host );
815                 if (dest->lud_host == NULL) {
816                         ldap_free_urldesc(dest);
817                         return NULL;
818                 }
819         }
820
821         if ( ludp->lud_dn != NULL ) {
822                 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
823                 if (dest->lud_dn == NULL) {
824                         ldap_free_urldesc(dest);
825                         return NULL;
826                 }
827         }
828
829         if ( ludp->lud_filter != NULL ) {
830                 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
831                 if (dest->lud_filter == NULL) {
832                         ldap_free_urldesc(dest);
833                         return NULL;
834                 }
835         }
836
837         if ( ludp->lud_attrs != NULL ) {
838                 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
839                 if (dest->lud_attrs == NULL) {
840                         ldap_free_urldesc(dest);
841                         return NULL;
842                 }
843         }
844
845         if ( ludp->lud_exts != NULL ) {
846                 dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
847                 if (dest->lud_exts == NULL) {
848                         ldap_free_urldesc(dest);
849                         return NULL;
850                 }
851         }
852
853         return dest;
854 }
855
856 LDAPURLDesc *
857 ldap_url_duplist (LDAPURLDesc *ludlist)
858 {
859         LDAPURLDesc *dest, *tail, *ludp, *newludp;
860
861         dest = NULL;
862         tail = NULL;
863         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
864                 newludp = ldap_url_dup(ludp);
865                 if (newludp == NULL) {
866                         ldap_free_urllist(dest);
867                         return NULL;
868                 }
869                 if (tail == NULL)
870                         dest = newludp;
871                 else
872                         tail->lud_next = newludp;
873                 tail = newludp;
874         }
875         return dest;
876 }
877
878 int
879 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
880 {
881         int i, rc;
882         LDAPURLDesc *ludp;
883         char **urls;
884
885         *ludlist = NULL;
886
887         if (url == NULL)
888                 return LDAP_PARAM_ERROR;
889
890         urls = ldap_str2charray((char *)url, ", ");
891         if (urls == NULL)
892                 return LDAP_NO_MEMORY;
893
894         /* count the URLs... */
895         for (i = 0; urls[i] != NULL; i++) ;
896         /* ...and put them in the "stack" backward */
897         while (--i >= 0) {
898                 rc = ldap_url_parse( urls[i], &ludp );
899                 if ( rc != 0 ) {
900                         ldap_charray_free(urls);
901                         ldap_free_urllist(*ludlist);
902                         *ludlist = NULL;
903                         return rc;
904                 }
905                 ludp->lud_next = *ludlist;
906                 *ludlist = ludp;
907         }
908         ldap_charray_free(urls);
909         return LDAP_SUCCESS;
910 }
911
912 int
913 ldap_url_parsehosts(
914         LDAPURLDesc **ludlist,
915         const char *hosts,
916         int port )
917 {
918         int i;
919         LDAPURLDesc *ludp;
920         char **specs, *p;
921
922         *ludlist = NULL;
923
924         if (hosts == NULL)
925                 return LDAP_PARAM_ERROR;
926
927         specs = ldap_str2charray((char *)hosts, ", ");
928         if (specs == NULL)
929                 return LDAP_NO_MEMORY;
930
931         /* count the URLs... */
932         for (i = 0; specs[i] != NULL; i++) /* EMPTY */;
933
934         /* ...and put them in the "stack" backward */
935         while (--i >= 0) {
936                 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
937                 if (ludp == NULL) {
938                         ldap_charray_free(specs);
939                         ldap_free_urllist(*ludlist);
940                         *ludlist = NULL;
941                         return LDAP_NO_MEMORY;
942                 }
943                 ludp->lud_port = port;
944                 ludp->lud_host = specs[i];
945                 specs[i] = NULL;
946                 p = strchr(ludp->lud_host, ':');
947                 if (p != NULL) {
948                         /* more than one :, IPv6 address */
949                         if ( strchr(p+1, ':') != NULL ) {
950                                 /* allow [address] and [address]:port */
951                                 if ( *ludp->lud_host == '[' ) {
952                                         p = LDAP_STRDUP(ludp->lud_host+1);
953                                         /* copied, make sure we free source later */
954                                         specs[i] = ludp->lud_host;
955                                         ludp->lud_host = p;
956                                         p = strchr( ludp->lud_host, ']' );
957                                         if ( p == NULL )
958                                                 return LDAP_PARAM_ERROR;
959                                         *p++ = '\0';
960                                         if ( *p != ':' ) {
961                                                 if ( *p != '\0' )
962                                                         return LDAP_PARAM_ERROR;
963                                                 p = NULL;
964                                         }
965                                 } else {
966                                         p = NULL;
967                                 }
968                         }
969                         if (p != NULL) {
970                                 *p++ = 0;
971                                 ldap_pvt_hex_unescape(p);
972                                 ludp->lud_port = atoi(p);
973                         }
974                 }
975                 ldap_pvt_hex_unescape(ludp->lud_host);
976                 ludp->lud_scheme = LDAP_STRDUP("ldap");
977                 ludp->lud_next = *ludlist;
978                 *ludlist = ludp;
979         }
980
981         /* this should be an array of NULLs now */
982         /* except entries starting with [ */
983         ldap_charray_free(specs);
984         return LDAP_SUCCESS;
985 }
986
987 char *
988 ldap_url_list2hosts (LDAPURLDesc *ludlist)
989 {
990         LDAPURLDesc *ludp;
991         int size;
992         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
993
994         if (ludlist == NULL)
995                 return NULL;
996
997         /* figure out how big the string is */
998         size = 1;       /* nul-term */
999         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1000                 size += strlen(ludp->lud_host) + 1;             /* host and space */
1001                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1002                         size += 2;
1003                 if (ludp->lud_port != 0)
1004                         size += sprintf(buf, ":%d", ludp->lud_port);
1005         }
1006         s = LDAP_MALLOC(size);
1007         if (s == NULL)
1008                 return NULL;
1009
1010         p = s;
1011         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1012                 if (strchr(ludp->lud_host, ':')) {
1013                         p += sprintf(p, "[%s]", ludp->lud_host);
1014                 } else {
1015                         strcpy(p, ludp->lud_host);
1016                         p += strlen(ludp->lud_host);
1017                 }
1018                 if (ludp->lud_port != 0)
1019                         p += sprintf(p, ":%d", ludp->lud_port);
1020                 *p++ = ' ';
1021         }
1022         if (p != s)
1023                 p--;    /* nuke that extra space */
1024         *p = 0;
1025         return s;
1026 }
1027
1028 char *
1029 ldap_url_list2urls(
1030         LDAPURLDesc *ludlist )
1031 {
1032         LDAPURLDesc *ludp;
1033         int size;
1034         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
1035
1036         if (ludlist == NULL)
1037                 return NULL;
1038
1039         /* figure out how big the string is */
1040         size = 1;       /* nul-term */
1041         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1042                 size += strlen(ludp->lud_scheme) + strlen(ludp->lud_host);
1043                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1044                         size += 2;
1045                 size += sizeof(":/// ");
1046
1047                 if (ludp->lud_port != 0) {
1048                         size += sprintf(buf, ":%d", ludp->lud_port);
1049                 }
1050         }
1051
1052         s = LDAP_MALLOC(size);
1053         if (s == NULL) {
1054                 return NULL;
1055         }
1056
1057         p = s;
1058         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1059                 p += sprintf(p,
1060                              strchr(ludp->lud_host, ':') ? "%s://[%s]" : "%s://%s",
1061                              ludp->lud_scheme, ludp->lud_host);
1062                 if (ludp->lud_port != 0)
1063                         p += sprintf(p, ":%d", ludp->lud_port);
1064                 *p++ = '/';
1065                 *p++ = ' ';
1066         }
1067         if (p != s)
1068                 p--;    /* nuke that extra space */
1069         *p = 0;
1070         return s;
1071 }
1072
1073 void
1074 ldap_free_urllist( LDAPURLDesc *ludlist )
1075 {
1076         LDAPURLDesc *ludp, *next;
1077
1078         for (ludp = ludlist; ludp != NULL; ludp = next) {
1079                 next = ludp->lud_next;
1080                 ldap_free_urldesc(ludp);
1081         }
1082 }
1083
1084 void
1085 ldap_free_urldesc( LDAPURLDesc *ludp )
1086 {
1087         if ( ludp == NULL ) {
1088                 return;
1089         }
1090         
1091         if ( ludp->lud_scheme != NULL ) {
1092                 LDAP_FREE( ludp->lud_scheme );
1093         }
1094
1095         if ( ludp->lud_host != NULL ) {
1096                 LDAP_FREE( ludp->lud_host );
1097         }
1098
1099         if ( ludp->lud_dn != NULL ) {
1100                 LDAP_FREE( ludp->lud_dn );
1101         }
1102
1103         if ( ludp->lud_filter != NULL ) {
1104                 LDAP_FREE( ludp->lud_filter);
1105         }
1106
1107         if ( ludp->lud_attrs != NULL ) {
1108                 LDAP_VFREE( ludp->lud_attrs );
1109         }
1110
1111         if ( ludp->lud_exts != NULL ) {
1112                 LDAP_VFREE( ludp->lud_exts );
1113         }
1114
1115         LDAP_FREE( ludp );
1116 }
1117
1118 static int
1119 ldap_int_unhex( int c )
1120 {
1121         return( c >= '0' && c <= '9' ? c - '0'
1122             : c >= 'A' && c <= 'F' ? c - 'A' + 10
1123             : c - 'a' + 10 );
1124 }
1125
1126 void
1127 ldap_pvt_hex_unescape( char *s )
1128 {
1129         /*
1130          * Remove URL hex escapes from s... done in place.  The basic concept for
1131          * this routine is borrowed from the WWW library HTUnEscape() routine.
1132          */
1133         char    *p;
1134
1135         for ( p = s; *s != '\0'; ++s ) {
1136                 if ( *s == '%' ) {
1137                         if ( *++s != '\0' ) {
1138                                 *p = ldap_int_unhex( *s ) << 4;
1139                         }
1140                         if ( *++s != '\0' ) {
1141                                 *p++ += ldap_int_unhex( *s );
1142                         }
1143                 } else {
1144                         *p++ = *s;
1145                 }
1146         }
1147
1148         *p = '\0';
1149 }
1150
1151