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