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