]> git.sur5r.net Git - openldap/blob - libraries/libldap/url.c
Suck in HEAD changes since 2.1alpha
[openldap] / libraries / libldap / url.c
1 /* $OpenLDAP$ */
2 /*
3  * Copyright 1998-2002 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 #ifdef NEW_LOGGING
449         LDAP_LOG (( "url", LDAP_LEVEL_ENTRY, "ldap_url_parse_ext(%s)\n", url_in ));
450 #else
451         Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 );
452 #endif
453 #endif
454
455         *ludpp = NULL;  /* pessimistic */
456
457         url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
458
459         if ( url_tmp == NULL ) {
460                 return LDAP_URL_ERR_BADSCHEME;
461         }
462
463         assert( scheme );
464
465         /* make working copy of the remainder of the URL */
466         url = LDAP_STRDUP( url_tmp );
467         if ( url == NULL ) {
468                 return LDAP_URL_ERR_MEM;
469         }
470
471         if ( enclosed ) {
472                 p = &url[strlen(url)-1];
473
474                 if( *p != '>' ) {
475                         LDAP_FREE( url );
476                         return LDAP_URL_ERR_BADENCLOSURE;
477                 }
478
479                 *p = '\0';
480         }
481
482         /* allocate return struct */
483         ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
484
485         if ( ludp == NULL ) {
486                 LDAP_FREE( url );
487                 return LDAP_URL_ERR_MEM;
488         }
489
490         ludp->lud_next = NULL;
491         ludp->lud_host = NULL;
492         ludp->lud_port = 0;
493         ludp->lud_dn = NULL;
494         ludp->lud_attrs = NULL;
495         ludp->lud_filter = NULL;
496         ludp->lud_scope = LDAP_SCOPE_DEFAULT;
497         ludp->lud_filter = NULL;
498         ludp->lud_exts = NULL;
499
500         ludp->lud_scheme = LDAP_STRDUP( scheme );
501
502         if ( ludp->lud_scheme == NULL ) {
503                 LDAP_FREE( url );
504                 ldap_free_urldesc( ludp );
505                 return LDAP_URL_ERR_MEM;
506         }
507
508         /* scan forward for '/' that marks end of hostport and begin. of dn */
509         p = strchr( url, '/' );
510
511         if( p != NULL ) {
512                 /* terminate hostport; point to start of dn */
513                 *p++ = '\0';
514         }
515
516         /* IPv6 syntax with [ip address]:port */
517         if ( *url == '[' ) {
518                 r = strchr( url, ']' );
519                 if ( r == NULL ) {
520                         LDAP_FREE( url );
521                         ldap_free_urldesc( ludp );
522                         return LDAP_URL_ERR_BADURL;
523                 }
524                 *r++ = '\0';
525                 q = strchr( r, ':' );
526         } else {
527                 q = strchr( url, ':' );
528         }
529
530         if ( q != NULL ) {
531                 *q++ = '\0';
532                 ldap_pvt_hex_unescape( q );
533
534                 if( *q == '\0' ) {
535                         LDAP_FREE( url );
536                         ldap_free_urldesc( ludp );
537                         return LDAP_URL_ERR_BADURL;
538                 }
539
540                 ludp->lud_port = atoi( q );
541         }
542
543         ldap_pvt_hex_unescape( url );
544
545         /* If [ip address]:port syntax, url is [ip and we skip the [ */
546         ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) );
547
548         if( ludp->lud_host == NULL ) {
549                 LDAP_FREE( url );
550                 ldap_free_urldesc( ludp );
551                 return LDAP_URL_ERR_MEM;
552         }
553
554         /*
555          * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
556          *
557          * On early Novell releases, search references/referrals were returned
558          * in this format, i.e., the dn was kind of in the scope position,
559          * but the required slash is missing. The whole thing is illegal syntax,
560          * but we need to account for it. Fortunately it can't be confused with
561          * anything real.
562          */
563         if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
564                 q++;            
565                 /* ? immediately followed by question */
566                 if( *q == '?') {
567                         q++;
568                         if( *q != '\0' ) {
569                                 /* parse dn part */
570                                 ldap_pvt_hex_unescape( q );
571                                 ludp->lud_dn = LDAP_STRDUP( q );
572                         } else {
573                                 ludp->lud_dn = LDAP_STRDUP( "" );
574                         }
575
576                         if( ludp->lud_dn == NULL ) {
577                                 LDAP_FREE( url );
578                                 ldap_free_urldesc( ludp );
579                                 return LDAP_URL_ERR_MEM;
580                         }
581                 }
582         }
583
584         if( p == NULL ) {
585                 LDAP_FREE( url );
586                 *ludpp = ludp;
587                 return LDAP_URL_SUCCESS;
588         }
589
590         /* scan forward for '?' that may marks end of dn */
591         q = strchr( p, '?' );
592
593         if( q != NULL ) {
594                 /* terminate dn part */
595                 *q++ = '\0';
596         }
597
598         if( *p != '\0' ) {
599                 /* parse dn part */
600                 ldap_pvt_hex_unescape( p );
601                 ludp->lud_dn = LDAP_STRDUP( p );
602         } else {
603                 ludp->lud_dn = LDAP_STRDUP( "" );
604         }
605
606         if( ludp->lud_dn == NULL ) {
607                 LDAP_FREE( url );
608                 ldap_free_urldesc( ludp );
609                 return LDAP_URL_ERR_MEM;
610         }
611
612         if( q == NULL ) {
613                 /* no more */
614                 LDAP_FREE( url );
615                 *ludpp = ludp;
616                 return LDAP_URL_SUCCESS;
617         }
618
619         /* scan forward for '?' that may marks end of attributes */
620         p = q;
621         q = strchr( p, '?' );
622
623         if( q != NULL ) {
624                 /* terminate attributes part */
625                 *q++ = '\0';
626         }
627
628         if( *p != '\0' ) {
629                 /* parse attributes */
630                 ldap_pvt_hex_unescape( p );
631                 ludp->lud_attrs = ldap_str2charray( p, "," );
632
633                 if( ludp->lud_attrs == NULL ) {
634                         LDAP_FREE( url );
635                         ldap_free_urldesc( ludp );
636                         return LDAP_URL_ERR_BADATTRS;
637                 }
638         }
639
640         if ( q == NULL ) {
641                 /* no more */
642                 LDAP_FREE( url );
643                 *ludpp = ludp;
644                 return LDAP_URL_SUCCESS;
645         }
646
647         /* scan forward for '?' that may marks end of scope */
648         p = q;
649         q = strchr( p, '?' );
650
651         if( q != NULL ) {
652                 /* terminate the scope part */
653                 *q++ = '\0';
654         }
655
656         if( *p != '\0' ) {
657                 /* parse the scope */
658                 ldap_pvt_hex_unescape( p );
659                 ludp->lud_scope = str2scope( p );
660
661                 if( ludp->lud_scope == -1 ) {
662                         LDAP_FREE( url );
663                         ldap_free_urldesc( ludp );
664                         return LDAP_URL_ERR_BADSCOPE;
665                 }
666         }
667
668         if ( q == NULL ) {
669                 /* no more */
670                 LDAP_FREE( url );
671                 *ludpp = ludp;
672                 return LDAP_URL_SUCCESS;
673         }
674
675         /* scan forward for '?' that may marks end of filter */
676         p = q;
677         q = strchr( p, '?' );
678
679         if( q != NULL ) {
680                 /* terminate the filter part */
681                 *q++ = '\0';
682         }
683
684         if( *p != '\0' ) {
685                 /* parse the filter */
686                 ldap_pvt_hex_unescape( p );
687
688                 if( ! *p ) {
689                         /* missing filter */
690                         LDAP_FREE( url );
691                         ldap_free_urldesc( ludp );
692                         return LDAP_URL_ERR_BADFILTER;
693                 }
694
695                 LDAP_FREE( ludp->lud_filter );
696                 ludp->lud_filter = LDAP_STRDUP( p );
697
698                 if( ludp->lud_filter == NULL ) {
699                         LDAP_FREE( url );
700                         ldap_free_urldesc( ludp );
701                         return LDAP_URL_ERR_MEM;
702                 }
703         }
704
705         if ( q == NULL ) {
706                 /* no more */
707                 LDAP_FREE( url );
708                 *ludpp = ludp;
709                 return LDAP_URL_SUCCESS;
710         }
711
712         /* scan forward for '?' that may marks end of extensions */
713         p = q;
714         q = strchr( p, '?' );
715
716         if( q != NULL ) {
717                 /* extra '?' */
718                 LDAP_FREE( url );
719                 ldap_free_urldesc( ludp );
720                 return LDAP_URL_ERR_BADURL;
721         }
722
723         /* parse the extensions */
724         ludp->lud_exts = ldap_str2charray( p, "," );
725
726         if( ludp->lud_exts == NULL ) {
727                 LDAP_FREE( url );
728                 ldap_free_urldesc( ludp );
729                 return LDAP_URL_ERR_BADEXTS;
730         }
731
732         for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
733                 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
734
735                 if( *ludp->lud_exts[i] == '!' ) {
736                         /* count the number of critical extensions */
737                         ludp->lud_crit_exts++;
738                 }
739         }
740
741         if( i == 0 ) {
742                 /* must have 1 or more */
743                 LDAP_FREE( url );
744                 ldap_free_urldesc( ludp );
745                 return LDAP_URL_ERR_BADEXTS;
746         }
747
748         /* no more */
749         *ludpp = ludp;
750         LDAP_FREE( url );
751         return LDAP_URL_SUCCESS;
752 }
753
754 int
755 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
756 {
757         int rc = ldap_url_parse_ext( url_in, ludpp );
758
759         if( rc != LDAP_URL_SUCCESS ) {
760                 return rc;
761         }
762
763         if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) {
764                 (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
765         }
766
767         if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
768                 LDAP_FREE( (*ludpp)->lud_host );
769                 (*ludpp)->lud_host = NULL;
770         }
771
772         if ((*ludpp)->lud_port == 0) {
773                 if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) {
774                         (*ludpp)->lud_port = LDAP_PORT;
775 #ifdef LDAP_CONNECTIONLESS
776                 } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) {
777                         (*ludpp)->lud_port = LDAP_PORT;
778 #endif
779                 } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) {
780                         (*ludpp)->lud_port = LDAPS_PORT;
781                 }
782         }
783
784         return rc;
785 }
786
787 LDAPURLDesc *
788 ldap_url_dup ( LDAPURLDesc *ludp )
789 {
790         LDAPURLDesc *dest;
791
792         if ( ludp == NULL ) {
793                 return NULL;
794         }
795
796         dest = LDAP_MALLOC( sizeof(LDAPURLDesc) );
797         if (dest == NULL)
798                 return NULL;
799         
800         *dest = *ludp;
801         dest->lud_scheme = NULL;
802         dest->lud_host = NULL;
803         dest->lud_dn = NULL;
804         dest->lud_filter = NULL;
805         dest->lud_attrs = NULL;
806         dest->lud_exts = NULL;
807         dest->lud_next = NULL;
808
809         if ( ludp->lud_scheme != NULL ) {
810                 dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme );
811                 if (dest->lud_scheme == NULL) {
812                         ldap_free_urldesc(dest);
813                         return NULL;
814                 }
815         }
816
817         if ( ludp->lud_host != NULL ) {
818                 dest->lud_host = LDAP_STRDUP( ludp->lud_host );
819                 if (dest->lud_host == NULL) {
820                         ldap_free_urldesc(dest);
821                         return NULL;
822                 }
823         }
824
825         if ( ludp->lud_dn != NULL ) {
826                 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
827                 if (dest->lud_dn == NULL) {
828                         ldap_free_urldesc(dest);
829                         return NULL;
830                 }
831         }
832
833         if ( ludp->lud_filter != NULL ) {
834                 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
835                 if (dest->lud_filter == NULL) {
836                         ldap_free_urldesc(dest);
837                         return NULL;
838                 }
839         }
840
841         if ( ludp->lud_attrs != NULL ) {
842                 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
843                 if (dest->lud_attrs == NULL) {
844                         ldap_free_urldesc(dest);
845                         return NULL;
846                 }
847         }
848
849         if ( ludp->lud_exts != NULL ) {
850                 dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
851                 if (dest->lud_exts == NULL) {
852                         ldap_free_urldesc(dest);
853                         return NULL;
854                 }
855         }
856
857         return dest;
858 }
859
860 LDAPURLDesc *
861 ldap_url_duplist (LDAPURLDesc *ludlist)
862 {
863         LDAPURLDesc *dest, *tail, *ludp, *newludp;
864
865         dest = NULL;
866         tail = NULL;
867         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
868                 newludp = ldap_url_dup(ludp);
869                 if (newludp == NULL) {
870                         ldap_free_urllist(dest);
871                         return NULL;
872                 }
873                 if (tail == NULL)
874                         dest = newludp;
875                 else
876                         tail->lud_next = newludp;
877                 tail = newludp;
878         }
879         return dest;
880 }
881
882 int
883 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
884 {
885         int i, rc;
886         LDAPURLDesc *ludp;
887         char **urls;
888
889         *ludlist = NULL;
890
891         if (url == NULL)
892                 return LDAP_PARAM_ERROR;
893
894         urls = ldap_str2charray(url, ", ");
895         if (urls == NULL)
896                 return LDAP_NO_MEMORY;
897
898         /* count the URLs... */
899         for (i = 0; urls[i] != NULL; i++) ;
900         /* ...and put them in the "stack" backward */
901         while (--i >= 0) {
902                 rc = ldap_url_parse( urls[i], &ludp );
903                 if ( rc != 0 ) {
904                         ldap_charray_free(urls);
905                         ldap_free_urllist(*ludlist);
906                         *ludlist = NULL;
907                         return rc;
908                 }
909                 ludp->lud_next = *ludlist;
910                 *ludlist = ludp;
911         }
912         ldap_charray_free(urls);
913         return LDAP_SUCCESS;
914 }
915
916 int
917 ldap_url_parsehosts(
918         LDAPURLDesc **ludlist,
919         const char *hosts,
920         int port )
921 {
922         int i;
923         LDAPURLDesc *ludp;
924         char **specs, *p;
925
926         *ludlist = NULL;
927
928         if (hosts == NULL)
929                 return LDAP_PARAM_ERROR;
930
931         specs = ldap_str2charray(hosts, ", ");
932         if (specs == NULL)
933                 return LDAP_NO_MEMORY;
934
935         /* count the URLs... */
936         for (i = 0; specs[i] != NULL; i++) /* EMPTY */;
937
938         /* ...and put them in the "stack" backward */
939         while (--i >= 0) {
940                 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
941                 if (ludp == NULL) {
942                         ldap_charray_free(specs);
943                         ldap_free_urllist(*ludlist);
944                         *ludlist = NULL;
945                         return LDAP_NO_MEMORY;
946                 }
947                 ludp->lud_port = port;
948                 ludp->lud_host = specs[i];
949                 specs[i] = NULL;
950                 p = strchr(ludp->lud_host, ':');
951                 if (p != NULL) {
952                         /* more than one :, IPv6 address */
953                         if ( strchr(p+1, ':') != NULL ) {
954                                 /* allow [address] and [address]:port */
955                                 if ( *ludp->lud_host == '[' ) {
956                                         p = LDAP_STRDUP(ludp->lud_host+1);
957                                         /* copied, make sure we free source later */
958                                         specs[i] = ludp->lud_host;
959                                         ludp->lud_host = p;
960                                         p = strchr( ludp->lud_host, ']' );
961                                         if ( p == NULL )
962                                                 return LDAP_PARAM_ERROR;
963                                         *p++ = '\0';
964                                         if ( *p != ':' ) {
965                                                 if ( *p != '\0' )
966                                                         return LDAP_PARAM_ERROR;
967                                                 p = NULL;
968                                         }
969                                 } else {
970                                         p = NULL;
971                                 }
972                         }
973                         if (p != NULL) {
974                                 *p++ = 0;
975                                 ldap_pvt_hex_unescape(p);
976                                 ludp->lud_port = atoi(p);
977                         }
978                 }
979                 ldap_pvt_hex_unescape(ludp->lud_host);
980                 ludp->lud_scheme = LDAP_STRDUP("ldap");
981                 ludp->lud_next = *ludlist;
982                 *ludlist = ludp;
983         }
984
985         /* this should be an array of NULLs now */
986         /* except entries starting with [ */
987         ldap_charray_free(specs);
988         return LDAP_SUCCESS;
989 }
990
991 char *
992 ldap_url_list2hosts (LDAPURLDesc *ludlist)
993 {
994         LDAPURLDesc *ludp;
995         int size;
996         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
997
998         if (ludlist == NULL)
999                 return NULL;
1000
1001         /* figure out how big the string is */
1002         size = 1;       /* nul-term */
1003         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1004                 size += strlen(ludp->lud_host) + 1;             /* host and space */
1005                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1006                         size += 2;
1007                 if (ludp->lud_port != 0)
1008                         size += sprintf(buf, ":%d", ludp->lud_port);
1009         }
1010         s = LDAP_MALLOC(size);
1011         if (s == NULL)
1012                 return NULL;
1013
1014         p = s;
1015         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1016                 if (strchr(ludp->lud_host, ':')) {
1017                         p += sprintf(p, "[%s]", ludp->lud_host);
1018                 } else {
1019                         strcpy(p, ludp->lud_host);
1020                         p += strlen(ludp->lud_host);
1021                 }
1022                 if (ludp->lud_port != 0)
1023                         p += sprintf(p, ":%d", ludp->lud_port);
1024                 *p++ = ' ';
1025         }
1026         if (p != s)
1027                 p--;    /* nuke that extra space */
1028         *p = 0;
1029         return s;
1030 }
1031
1032 char *
1033 ldap_url_list2urls(
1034         LDAPURLDesc *ludlist )
1035 {
1036         LDAPURLDesc *ludp;
1037         int size;
1038         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
1039
1040         if (ludlist == NULL)
1041                 return NULL;
1042
1043         /* figure out how big the string is */
1044         size = 1;       /* nul-term */
1045         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1046                 size += strlen(ludp->lud_scheme) + strlen(ludp->lud_host);
1047                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1048                         size += 2;
1049                 size += sizeof(":/// ");
1050
1051                 if (ludp->lud_port != 0) {
1052                         size += sprintf(buf, ":%d", ludp->lud_port);
1053                 }
1054         }
1055
1056         s = LDAP_MALLOC(size);
1057         if (s == NULL) {
1058                 return NULL;
1059         }
1060
1061         p = s;
1062         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1063                 p += sprintf(p,
1064                              strchr(ludp->lud_host, ':') ? "%s://[%s]" : "%s://%s",
1065                              ludp->lud_scheme, ludp->lud_host);
1066                 if (ludp->lud_port != 0)
1067                         p += sprintf(p, ":%d", ludp->lud_port);
1068                 *p++ = '/';
1069                 *p++ = ' ';
1070         }
1071         if (p != s)
1072                 p--;    /* nuke that extra space */
1073         *p = 0;
1074         return s;
1075 }
1076
1077 void
1078 ldap_free_urllist( LDAPURLDesc *ludlist )
1079 {
1080         LDAPURLDesc *ludp, *next;
1081
1082         for (ludp = ludlist; ludp != NULL; ludp = next) {
1083                 next = ludp->lud_next;
1084                 ldap_free_urldesc(ludp);
1085         }
1086 }
1087
1088 void
1089 ldap_free_urldesc( LDAPURLDesc *ludp )
1090 {
1091         if ( ludp == NULL ) {
1092                 return;
1093         }
1094         
1095         if ( ludp->lud_scheme != NULL ) {
1096                 LDAP_FREE( ludp->lud_scheme );
1097         }
1098
1099         if ( ludp->lud_host != NULL ) {
1100                 LDAP_FREE( ludp->lud_host );
1101         }
1102
1103         if ( ludp->lud_dn != NULL ) {
1104                 LDAP_FREE( ludp->lud_dn );
1105         }
1106
1107         if ( ludp->lud_filter != NULL ) {
1108                 LDAP_FREE( ludp->lud_filter);
1109         }
1110
1111         if ( ludp->lud_attrs != NULL ) {
1112                 LDAP_VFREE( ludp->lud_attrs );
1113         }
1114
1115         if ( ludp->lud_exts != NULL ) {
1116                 LDAP_VFREE( ludp->lud_exts );
1117         }
1118
1119         LDAP_FREE( ludp );
1120 }
1121
1122 static int
1123 ldap_int_unhex( int c )
1124 {
1125         return( c >= '0' && c <= '9' ? c - '0'
1126             : c >= 'A' && c <= 'F' ? c - 'A' + 10
1127             : c - 'a' + 10 );
1128 }
1129
1130 void
1131 ldap_pvt_hex_unescape( char *s )
1132 {
1133         /*
1134          * Remove URL hex escapes from s... done in place.  The basic concept for
1135          * this routine is borrowed from the WWW library HTUnEscape() routine.
1136          */
1137         char    *p;
1138
1139         for ( p = s; *s != '\0'; ++s ) {
1140                 if ( *s == '%' ) {
1141                         if ( *++s != '\0' ) {
1142                                 *p = ldap_int_unhex( *s ) << 4;
1143                         }
1144                         if ( *++s != '\0' ) {
1145                                 *p++ += ldap_int_unhex( *s );
1146                         }
1147                 } else {
1148                         *p++ = *s;
1149                 }
1150         }
1151
1152         *p = '\0';
1153 }
1154
1155