]> git.sur5r.net Git - openldap/blob - libraries/libldap/url.c
468ad4d29502903e19da9b95d4371dacd2f20423
[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/ctype.h>
32 #include <ac/socket.h>
33 #include <ac/string.h>
34 #include <ac/time.h>
35
36 #include "ldap-int.h"
37
38
39 /* local functions */
40 static const char* skip_url_prefix LDAP_P((
41         const char *url,
42         int *enclosedp,
43         const char **scheme ));
44
45 int ldap_pvt_url_scheme2proto( const char *scheme )
46 {
47         assert( scheme );
48
49         if( scheme == NULL ) {
50                 return -1;
51         }
52
53         if( strcmp("ldap", scheme) == 0 ) {
54                 return LDAP_PROTO_TCP;
55         }
56
57         if( strcmp("ldapi", scheme) == 0 ) {
58                 return LDAP_PROTO_IPC;
59         }
60
61         if( strcmp("ldaps", scheme) == 0 ) {
62                 return LDAP_PROTO_TCP;
63         }
64
65         return -1;
66 }
67
68 int ldap_pvt_url_scheme2tls( const char *scheme )
69 {
70         assert( scheme );
71
72         if( scheme == NULL ) {
73                 return -1;
74         }
75
76         return strcmp("ldaps", scheme) == 0;
77 }
78
79 int
80 ldap_is_ldap_url( LDAP_CONST char *url )
81 {
82         int     enclosed;
83         const char * scheme;
84
85         if( url == NULL ) {
86                 return 0;
87         }
88
89         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
90                 return 0;
91         }
92
93         return 1;
94 }
95
96 int
97 ldap_is_ldaps_url( LDAP_CONST char *url )
98 {
99         int     enclosed;
100         const char * scheme;
101
102         if( url == NULL ) {
103                 return 0;
104         }
105
106         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
107                 return 0;
108         }
109
110         return strcmp(scheme, "ldaps") == 0;
111 }
112
113 int
114 ldap_is_ldapi_url( LDAP_CONST char *url )
115 {
116         int     enclosed;
117         const char * scheme;
118
119         if( url == NULL ) {
120                 return 0;
121         }
122
123         if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
124                 return 0;
125         }
126
127         return strcmp(scheme, "ldapi") == 0;
128 }
129
130 static const char*
131 skip_url_prefix(
132         const char *url,
133         int *enclosedp,
134         const char **scheme )
135 {
136 /*
137  * return non-zero if this looks like a LDAP URL; zero if not
138  * if non-zero returned, *urlp will be moved past "ldap://" part of URL
139  */
140         const char *p;
141
142         if ( url == NULL ) {
143                 return( NULL );
144         }
145
146         p = url;
147
148         /* skip leading '<' (if any) */
149         if ( *p == '<' ) {
150                 *enclosedp = 1;
151                 ++p;
152         } else {
153                 *enclosedp = 0;
154         }
155
156         /* skip leading "URL:" (if any) */
157         if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 )
158         {
159                 p += LDAP_URL_URLCOLON_LEN;
160         }
161
162         /* check for "ldap://" prefix */
163         if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
164                 /* skip over "ldap://" prefix and return success */
165                 p += LDAP_URL_PREFIX_LEN;
166                 *scheme = "ldap";
167                 return( p );
168         }
169
170         /* check for "ldaps://" prefix */
171         if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
172                 /* skip over "ldaps://" prefix and return success */
173                 p += LDAPS_URL_PREFIX_LEN;
174                 *scheme = "ldaps";
175                 return( p );
176         }
177
178         /* check for "ldapi://" prefix */
179         if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
180                 /* skip over "ldapi://" prefix and return success */
181                 p += LDAPI_URL_PREFIX_LEN;
182                 *scheme = "ldapi";
183                 return( p );
184         }
185
186         return( NULL );
187 }
188
189
190 static int str2scope( const char *p )
191 {
192         if ( strcasecmp( p, "one" ) == 0 ) {
193                 return LDAP_SCOPE_ONELEVEL;
194
195         } else if ( strcasecmp( p, "onetree" ) == 0 ) {
196                 return LDAP_SCOPE_ONELEVEL;
197
198         } else if ( strcasecmp( p, "base" ) == 0 ) {
199                 return LDAP_SCOPE_BASE;
200
201         } else if ( strcasecmp( p, "sub" ) == 0 ) {
202                 return LDAP_SCOPE_SUBTREE;
203
204         } else if ( strcasecmp( p, "subtree" ) == 0 ) {
205                 return LDAP_SCOPE_SUBTREE;
206         }
207
208         return( -1 );
209 }
210
211
212 int
213 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
214 {
215 /*
216  *  Pick apart the pieces of an LDAP URL.
217  */
218
219         LDAPURLDesc     *ludp;
220         char    *p, *q, *r;
221         int             i, enclosed;
222         const char *scheme = NULL;
223         const char *url_tmp;
224         char *url;
225
226         if( url_in == NULL && ludpp == NULL ) {
227                 return LDAP_URL_ERR_PARAM;
228         }
229
230 #ifndef LDAP_INT_IN_KERNEL
231         /* Global options may not be created yet
232          * We can't test if the global options are initialized
233          * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate
234          * the options and cause infinite recursion
235          */
236         Debug( LDAP_DEBUG_TRACE, "ldap_url_parse(%s)\n", url_in, 0, 0 );
237 #endif
238
239         *ludpp = NULL;  /* pessimistic */
240
241         url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
242
243         if ( url_tmp == NULL ) {
244                 return LDAP_URL_ERR_BADSCHEME;
245         }
246
247         assert( scheme );
248
249         /* make working copy of the remainder of the URL */
250         url = LDAP_STRDUP( url_tmp );
251         if ( url == NULL ) {
252                 return LDAP_URL_ERR_MEM;
253         }
254
255         if ( enclosed ) {
256                 p = &url[strlen(url)-1];
257
258                 if( *p != '>' ) {
259                         LDAP_FREE( url );
260                         return LDAP_URL_ERR_BADENCLOSURE;
261                 }
262
263                 *p = '\0';
264         }
265
266         /* allocate return struct */
267         ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
268
269         if ( ludp == NULL ) {
270                 LDAP_FREE( url );
271                 return LDAP_URL_ERR_MEM;
272         }
273
274         ludp->lud_next = NULL;
275         ludp->lud_host = NULL;
276         ludp->lud_port = 0;
277         ludp->lud_dn = NULL;
278         ludp->lud_attrs = NULL;
279         ludp->lud_filter = NULL;
280         ludp->lud_scope = LDAP_SCOPE_BASE;
281         ludp->lud_filter = NULL;
282
283         ludp->lud_scheme = LDAP_STRDUP( scheme );
284
285         if ( ludp->lud_scheme == NULL ) {
286                 LDAP_FREE( url );
287                 ldap_free_urldesc( ludp );
288                 return LDAP_URL_ERR_MEM;
289         }
290
291         /* scan forward for '/' that marks end of hostport and begin. of dn */
292         p = strchr( url, '/' );
293
294         if( p != NULL ) {
295                 /* terminate hostport; point to start of dn */
296                 *p++ = '\0';
297         }
298
299         /* IPv6 syntax with [ip address]:port */
300         if ( *url == '[' ) {
301                 r = strchr( url, ']' );
302                 if ( r == NULL ) {
303                         LDAP_FREE( url );
304                         ldap_free_urldesc( ludp );
305                         return LDAP_URL_ERR_BADURL;
306                 }
307                 *r++ = '\0';
308                 q = strchr( r, ':' );
309         } else {
310                 q = strchr( url, ':' );
311         }
312
313         if ( q != NULL ) {
314                 *q++ = '\0';
315                 ldap_pvt_hex_unescape( q );
316
317                 if( *q == '\0' ) {
318                         LDAP_FREE( url );
319                         ldap_free_urldesc( ludp );
320                         return LDAP_URL_ERR_BADURL;
321                 }
322
323                 ludp->lud_port = atoi( q );
324         }
325
326         ldap_pvt_hex_unescape( url );
327
328         /* If [ip address]:port syntax, url is [ip and we skip the [ */
329         ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) );
330
331         if( ludp->lud_host == NULL ) {
332                 LDAP_FREE( url );
333                 ldap_free_urldesc( ludp );
334                 return LDAP_URL_ERR_MEM;
335         }
336
337         /*
338          * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
339          *
340          * On early Novell releases, search references/referrals were returned
341          * in this format, i.e., the dn was kind of in the scope position,
342          * but the required slash is missing. The whole thing is illegal syntax,
343          * but we need to account for it. Fortunately it can't be confused with
344          * anything real.
345          */
346         if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
347                 q++;            
348                 /* ? immediately followed by question */
349                 if( *q == '?') {
350                         q++;
351                         if( *q != '\0' ) {
352                                 /* parse dn part */
353                                 ldap_pvt_hex_unescape( q );
354                                 ludp->lud_dn = LDAP_STRDUP( q );
355                         } else {
356                                 ludp->lud_dn = LDAP_STRDUP( "" );
357                         }
358
359                         if( ludp->lud_dn == NULL ) {
360                                 LDAP_FREE( url );
361                                 ldap_free_urldesc( ludp );
362                                 return LDAP_URL_ERR_MEM;
363                         }
364                 }
365         }
366
367         if( p == NULL ) {
368                 LDAP_FREE( url );
369                 *ludpp = ludp;
370                 return LDAP_URL_SUCCESS;
371         }
372
373         /* scan forward for '?' that may marks end of dn */
374         q = strchr( p, '?' );
375
376         if( q != NULL ) {
377                 /* terminate dn part */
378                 *q++ = '\0';
379         }
380
381         if( *p != '\0' ) {
382                 /* parse dn part */
383                 ldap_pvt_hex_unescape( p );
384                 ludp->lud_dn = LDAP_STRDUP( p );
385         } else {
386                 ludp->lud_dn = LDAP_STRDUP( "" );
387         }
388
389         if( ludp->lud_dn == NULL ) {
390                 LDAP_FREE( url );
391                 ldap_free_urldesc( ludp );
392                 return LDAP_URL_ERR_MEM;
393         }
394
395         if( q == NULL ) {
396                 /* no more */
397                 LDAP_FREE( url );
398                 *ludpp = ludp;
399                 return LDAP_URL_SUCCESS;
400         }
401
402         /* scan forward for '?' that may marks end of attributes */
403         p = q;
404         q = strchr( p, '?' );
405
406         if( q != NULL ) {
407                 /* terminate attributes part */
408                 *q++ = '\0';
409         }
410
411         if( *p != '\0' ) {
412                 /* parse attributes */
413                 ldap_pvt_hex_unescape( p );
414                 ludp->lud_attrs = ldap_str2charray( p, "," );
415
416                 if( ludp->lud_attrs == NULL ) {
417                         LDAP_FREE( url );
418                         ldap_free_urldesc( ludp );
419                         return LDAP_URL_ERR_BADATTRS;
420                 }
421         }
422
423         if ( q == NULL ) {
424                 /* no more */
425                 LDAP_FREE( url );
426                 *ludpp = ludp;
427                 return LDAP_URL_SUCCESS;
428         }
429
430         /* scan forward for '?' that may marks end of scope */
431         p = q;
432         q = strchr( p, '?' );
433
434         if( q != NULL ) {
435                 /* terminate the scope part */
436                 *q++ = '\0';
437         }
438
439         if( *p != '\0' ) {
440                 /* parse the scope */
441                 ldap_pvt_hex_unescape( p );
442                 ludp->lud_scope = str2scope( p );
443
444                 if( ludp->lud_scope == -1 ) {
445                         LDAP_FREE( url );
446                         ldap_free_urldesc( ludp );
447                         return LDAP_URL_ERR_BADSCOPE;
448                 }
449         }
450
451         if ( q == NULL ) {
452                 /* no more */
453                 LDAP_FREE( url );
454                 *ludpp = ludp;
455                 return LDAP_URL_SUCCESS;
456         }
457
458         /* scan forward for '?' that may marks end of filter */
459         p = q;
460         q = strchr( p, '?' );
461
462         if( q != NULL ) {
463                 /* terminate the filter part */
464                 *q++ = '\0';
465         }
466
467         if( *p != '\0' ) {
468                 /* parse the filter */
469                 ldap_pvt_hex_unescape( p );
470
471                 if( ! *p ) {
472                         /* missing filter */
473                         LDAP_FREE( url );
474                         ldap_free_urldesc( ludp );
475                         return LDAP_URL_ERR_BADFILTER;
476                 }
477
478                 LDAP_FREE( ludp->lud_filter );
479                 ludp->lud_filter = LDAP_STRDUP( p );
480
481                 if( ludp->lud_filter == NULL ) {
482                         LDAP_FREE( url );
483                         ldap_free_urldesc( ludp );
484                         return LDAP_URL_ERR_MEM;
485                 }
486         }
487
488         if ( q == NULL ) {
489                 /* no more */
490                 LDAP_FREE( url );
491                 *ludpp = ludp;
492                 return LDAP_URL_SUCCESS;
493         }
494
495         /* scan forward for '?' that may marks end of extensions */
496         p = q;
497         q = strchr( p, '?' );
498
499         if( q != NULL ) {
500                 /* extra '?' */
501                 LDAP_FREE( url );
502                 ldap_free_urldesc( ludp );
503                 return LDAP_URL_ERR_BADURL;
504         }
505
506         /* parse the extensions */
507         ludp->lud_exts = ldap_str2charray( p, "," );
508
509         if( ludp->lud_exts == NULL ) {
510                 LDAP_FREE( url );
511                 ldap_free_urldesc( ludp );
512                 return LDAP_URL_ERR_BADEXTS;
513         }
514
515         for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
516                 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
517         }
518
519         if( i == 0 ) {
520                 /* must have 1 or more */
521                 ldap_charray_free( ludp->lud_exts );
522                 LDAP_FREE( url );
523                 ldap_free_urldesc( ludp );
524                 return LDAP_URL_ERR_BADEXTS;
525         }
526
527         /* no more */
528         *ludpp = ludp;
529         LDAP_FREE( url );
530         return LDAP_URL_SUCCESS;
531 }
532
533 LDAPURLDesc *
534 ldap_url_dup ( LDAPURLDesc *ludp )
535 {
536         LDAPURLDesc *dest;
537
538         if ( ludp == NULL ) {
539                 return NULL;
540         }
541
542         dest = LDAP_MALLOC( sizeof(LDAPURLDesc) );
543         if (dest == NULL)
544                 return NULL;
545         
546         *dest = *ludp;
547         dest->lud_scheme = NULL;
548         dest->lud_host = NULL;
549         dest->lud_dn = NULL;
550         dest->lud_filter = NULL;
551         dest->lud_attrs = NULL;
552         dest->lud_exts = NULL;
553         dest->lud_next = NULL;
554
555         if ( ludp->lud_scheme != NULL ) {
556                 dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme );
557                 if (dest->lud_scheme == NULL) {
558                         ldap_free_urldesc(dest);
559                         return NULL;
560                 }
561         }
562
563         if ( ludp->lud_host != NULL ) {
564                 dest->lud_host = LDAP_STRDUP( ludp->lud_host );
565                 if (dest->lud_host == NULL) {
566                         ldap_free_urldesc(dest);
567                         return NULL;
568                 }
569         }
570
571         if ( ludp->lud_dn != NULL ) {
572                 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
573                 if (dest->lud_dn == NULL) {
574                         ldap_free_urldesc(dest);
575                         return NULL;
576                 }
577         }
578
579         if ( ludp->lud_filter != NULL ) {
580                 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
581                 if (dest->lud_filter == NULL) {
582                         ldap_free_urldesc(dest);
583                         return NULL;
584                 }
585         }
586
587         if ( ludp->lud_attrs != NULL ) {
588                 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
589                 if (dest->lud_attrs == NULL) {
590                         ldap_free_urldesc(dest);
591                         return NULL;
592                 }
593         }
594
595         if ( ludp->lud_exts != NULL ) {
596                 dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
597                 if (dest->lud_exts == NULL) {
598                         ldap_free_urldesc(dest);
599                         return NULL;
600                 }
601         }
602
603         return dest;
604 }
605
606 LDAPURLDesc *
607 ldap_url_duplist (LDAPURLDesc *ludlist)
608 {
609         LDAPURLDesc *dest, *tail, *ludp, *newludp;
610
611         dest = NULL;
612         tail = NULL;
613         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
614                 newludp = ldap_url_dup(ludp);
615                 if (newludp == NULL) {
616                         ldap_free_urllist(dest);
617                         return NULL;
618                 }
619                 if (tail == NULL)
620                         dest = newludp;
621                 else
622                         tail->lud_next = newludp;
623                 tail = newludp;
624         }
625         return dest;
626 }
627
628 int
629 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
630 {
631         int i, rc;
632         LDAPURLDesc *ludp;
633         char **urls;
634
635         *ludlist = NULL;
636
637         if (url == NULL)
638                 return LDAP_PARAM_ERROR;
639
640         urls = ldap_str2charray((char *)url, ", ");
641         if (urls == NULL)
642                 return LDAP_NO_MEMORY;
643
644         /* count the URLs... */
645         for (i = 0; urls[i] != NULL; i++) ;
646         /* ...and put them in the "stack" backward */
647         while (--i >= 0) {
648                 rc = ldap_url_parse( urls[i], &ludp );
649                 if ( rc != 0 ) {
650                         ldap_charray_free(urls);
651                         ldap_free_urllist(*ludlist);
652                         *ludlist = NULL;
653                         return rc;
654                 }
655                 ludp->lud_next = *ludlist;
656                 *ludlist = ludp;
657         }
658         ldap_charray_free(urls);
659         return LDAP_SUCCESS;
660 }
661
662 int
663 ldap_url_parsehosts (LDAPURLDesc **ludlist, const char *hosts )
664 {
665         int i;
666         LDAPURLDesc *ludp;
667         char **specs, *p;
668
669         *ludlist = NULL;
670
671         if (hosts == NULL)
672                 return LDAP_PARAM_ERROR;
673
674         specs = ldap_str2charray((char *)hosts, ", ");
675         if (specs == NULL)
676                 return LDAP_NO_MEMORY;
677
678         /* count the URLs... */
679         for (i = 0; specs[i] != NULL; i++) /* EMPTY */;
680
681         /* ...and put them in the "stack" backward */
682         while (--i >= 0) {
683                 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
684                 if (ludp == NULL) {
685                         ldap_charray_free(specs);
686                         ldap_free_urllist(*ludlist);
687                         *ludlist = NULL;
688                         return LDAP_NO_MEMORY;
689                 }
690                 ludp->lud_host = specs[i];
691                 specs[i] = NULL;
692                 p = strchr(ludp->lud_host, ':');
693                 if (p != NULL) {
694                         /* more than one :, IPv6 address */
695                         if ( strchr(p+1, ':') != NULL ) {
696                                 /* allow [address] and [address]:port */
697                                 if ( *ludp->lud_host == '[' ) {
698                                         p = LDAP_STRDUP(ludp->lud_host+1);
699                                         /* copied, make sure we free source later */
700                                         specs[i] = ludp->lud_host;
701                                         ludp->lud_host = p;
702                                         p = strchr( ludp->lud_host, ']' );
703                                         if ( p == NULL )
704                                                 return LDAP_PARAM_ERROR;
705                                         *p++ = '\0';
706                                         if ( *p != ':' ) {
707                                                 if ( *p != '\0' )
708                                                         return LDAP_PARAM_ERROR;
709                                                 p = NULL;
710                                         }
711                                 } else {
712                                         p = NULL;
713                                 }
714                         }
715                         if (p != NULL) {
716                                 *p++ = 0;
717                                 ldap_pvt_hex_unescape(p);
718                                 ludp->lud_port = atoi(p);
719                         }
720                 }
721                 ldap_pvt_hex_unescape(ludp->lud_host);
722                 ludp->lud_scheme = LDAP_STRDUP("ldap");
723                 ludp->lud_next = *ludlist;
724                 *ludlist = ludp;
725         }
726
727         /* this should be an array of NULLs now */
728         /* except entries starting with [ */
729         ldap_charray_free(specs);
730         return LDAP_SUCCESS;
731 }
732
733 char *
734 ldap_url_list2hosts (LDAPURLDesc *ludlist)
735 {
736         LDAPURLDesc *ludp;
737         int size;
738         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
739
740         if (ludlist == NULL)
741                 return NULL;
742
743         /* figure out how big the string is */
744         size = 1;       /* nul-term */
745         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
746                 size += strlen(ludp->lud_host) + 1;             /* host and space */
747                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
748                         size += 2;
749                 if (ludp->lud_port != 0)
750                         size += sprintf(buf, ":%d", ludp->lud_port);
751         }
752         s = LDAP_MALLOC(size);
753         if (s == NULL)
754                 return NULL;
755
756         p = s;
757         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
758                 if (strchr(ludp->lud_host, ':')) {
759                         p += sprintf(p, "[%s]", ludp->lud_host);
760                 } else {
761                         strcpy(p, ludp->lud_host);
762                         p += strlen(ludp->lud_host);
763                 }
764                 if (ludp->lud_port != 0)
765                         p += sprintf(p, ":%d", ludp->lud_port);
766                 *p++ = ' ';
767         }
768         if (p != s)
769                 p--;    /* nuke that extra space */
770         *p = 0;
771         return s;
772 }
773
774 char *
775 ldap_url_list2urls(
776         LDAPURLDesc *ludlist )
777 {
778         LDAPURLDesc *ludp;
779         int size;
780         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
781
782         if (ludlist == NULL)
783                 return NULL;
784
785         /* figure out how big the string is */
786         size = 1;       /* nul-term */
787         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
788                 size += strlen(ludp->lud_scheme) + strlen(ludp->lud_host);
789                 if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
790                         size += 2;
791                 size += sizeof(":/// ");
792
793                 if (ludp->lud_port != 0) {
794                         size += sprintf(buf, ":%d", ludp->lud_port);
795                 }
796         }
797
798         s = LDAP_MALLOC(size);
799         if (s == NULL) {
800                 return NULL;
801         }
802
803         p = s;
804         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
805                 p += sprintf(p,
806                              strchr(ludp->lud_host, ':') ? "%s://[%s]" : "%s://%s",
807                              ludp->lud_scheme, ludp->lud_host);
808                 if (ludp->lud_port != 0)
809                         p += sprintf(p, ":%d", ludp->lud_port);
810                 *p++ = '/';
811                 *p++ = ' ';
812         }
813         if (p != s)
814                 p--;    /* nuke that extra space */
815         *p = 0;
816         return s;
817 }
818
819 void
820 ldap_free_urllist( LDAPURLDesc *ludlist )
821 {
822         LDAPURLDesc *ludp, *next;
823
824         for (ludp = ludlist; ludp != NULL; ludp = next) {
825                 next = ludp->lud_next;
826                 ldap_free_urldesc(ludp);
827         }
828 }
829
830 void
831 ldap_free_urldesc( LDAPURLDesc *ludp )
832 {
833         if ( ludp == NULL ) {
834                 return;
835         }
836         
837         if ( ludp->lud_scheme != NULL ) {
838                 LDAP_FREE( ludp->lud_scheme );
839         }
840
841         if ( ludp->lud_host != NULL ) {
842                 LDAP_FREE( ludp->lud_host );
843         }
844
845         if ( ludp->lud_dn != NULL ) {
846                 LDAP_FREE( ludp->lud_dn );
847         }
848
849         if ( ludp->lud_filter != NULL ) {
850                 LDAP_FREE( ludp->lud_filter);
851         }
852
853         if ( ludp->lud_attrs != NULL ) {
854                 LDAP_VFREE( ludp->lud_attrs );
855         }
856
857         if ( ludp->lud_exts != NULL ) {
858                 LDAP_VFREE( ludp->lud_exts );
859         }
860
861         LDAP_FREE( ludp );
862 }
863
864
865
866 int
867 ldap_url_search( LDAP *ld, LDAP_CONST char *url, int attrsonly )
868 {
869         int             err;
870         LDAPURLDesc     *ludp;
871         BerElement      *ber;
872         LDAPreqinfo  bind;
873
874         if ( ldap_url_parse( url, &ludp ) != 0 ) {
875                 ld->ld_errno = LDAP_PARAM_ERROR;
876                 return( -1 );
877         }
878
879         ber = ldap_build_search_req( ld, ludp->lud_dn, ludp->lud_scope,
880             ludp->lud_filter, ludp->lud_attrs, attrsonly, NULL, NULL,
881                 -1, -1 );
882
883         if ( ber == NULL ) {
884                 err = -1;
885         } else {
886                 bind.ri_request = LDAP_REQ_SEARCH;
887                 bind.ri_msgid = ld->ld_msgid;
888                 bind.ri_url = (char *)url;
889                 err = ldap_send_server_request(
890                                         ld, ber, ld->ld_msgid, NULL,
891                                         (ludp->lud_host != NULL || ludp->lud_port != 0)
892                                                 ? ludp : NULL,
893                                         NULL, &bind );
894         }
895
896         ldap_free_urldesc( ludp );
897         return( err );
898 }
899
900
901 int
902 ldap_url_search_st( LDAP *ld, LDAP_CONST char *url, int attrsonly,
903         struct timeval *timeout, LDAPMessage **res )
904 {
905         int     msgid;
906
907         if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
908                 return( ld->ld_errno );
909         }
910
911         if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 ) {
912                 return( ld->ld_errno );
913         }
914
915         if ( ld->ld_errno == LDAP_TIMEOUT ) {
916                 (void) ldap_abandon( ld, msgid );
917                 ld->ld_errno = LDAP_TIMEOUT;
918                 return( ld->ld_errno );
919         }
920
921         return( ldap_result2error( ld, *res, 0 ));
922 }
923
924
925 int
926 ldap_url_search_s(
927         LDAP *ld, LDAP_CONST char *url, int attrsonly, LDAPMessage **res )
928 {
929         int     msgid;
930
931         if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
932                 return( ld->ld_errno );
933         }
934
935         if ( ldap_result( ld, msgid, 1, (struct timeval *)NULL, res ) == -1 ) {
936                 return( ld->ld_errno );
937         }
938
939         return( ldap_result2error( ld, *res, 0 ));
940 }
941
942
943 void
944 ldap_pvt_hex_unescape( char *s )
945 {
946 /*
947 * Remove URL hex escapes from s... done in place.  The basic concept for
948 * this routine is borrowed from the WWW library HTUnEscape() routine.
949 */
950         char    *p;
951
952         for ( p = s; *s != '\0'; ++s ) {
953                 if ( *s == '%' ) {
954                         if ( *++s != '\0' ) {
955                                 *p = ldap_pvt_unhex( *s ) << 4;
956                         }
957                         if ( *++s != '\0' ) {
958                                 *p++ += ldap_pvt_unhex( *s );
959                         }
960                 } else {
961                         *p++ = *s;
962                 }
963         }
964
965         *p = '\0';
966 }
967
968
969 int
970 ldap_pvt_unhex( int c )
971 {
972         return( c >= '0' && c <= '9' ? c - '0'
973             : c >= 'A' && c <= 'F' ? c - 'A' + 10
974             : c - 'a' + 10 );
975 }