]> git.sur5r.net Git - openldap/blob - libraries/libldap/url.c
Update for new password codes for MSVC5
[openldap] / libraries / libldap / url.c
1 /* $OpenLDAP$ */
2 /*
3  * Copyright 1998-1999 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[s]://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         int *ldaps ));
44
45
46 int
47 ldap_is_ldap_url( LDAP_CONST char *url )
48 {
49         int     enclosed;
50         int ldaps;
51
52         if( url == NULL ) {
53                 return 0;
54         }
55
56         if( skip_url_prefix( url, &enclosed, &ldaps) == NULL ) {
57                 return 0;
58         }
59
60         return !ldaps;
61 }
62
63 int
64 ldap_is_ldaps_url( LDAP_CONST char *url )
65 {
66         int     enclosed;
67         int ldaps;
68
69         if( url == NULL ) {
70                 return 0;
71         }
72
73         if( skip_url_prefix( url, &enclosed, &ldaps) == NULL ) {
74                 return 0;
75         }
76
77         return ldaps;
78 }
79
80 static const char*
81 skip_url_prefix(
82         const char *url,
83         int *enclosedp,
84         int *ldaps )
85 {
86 /*
87  * return non-zero if this looks like a LDAP URL; zero if not
88  * if non-zero returned, *urlp will be moved past "ldap://" part of URL
89  */
90         const char *p;
91
92         if ( url == NULL ) {
93                 return( NULL );
94         }
95
96         p = url;
97
98         /* skip leading '<' (if any) */
99         if ( *p == '<' ) {
100                 *enclosedp = 1;
101                 ++p;
102         } else {
103                 *enclosedp = 0;
104         }
105
106         /* skip leading "URL:" (if any) */
107         if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 )
108         {
109                 p += LDAP_URL_URLCOLON_LEN;
110         }
111
112         /* check for "ldap://" prefix */
113         if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
114                 /* skip over "ldap://" prefix and return success */
115                 p += LDAP_URL_PREFIX_LEN;
116                 *ldaps = 0;
117                 return( p );
118         }
119
120         /* check for "ldaps://" prefix */
121         if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
122                 /* skip over "ldaps://" prefix and return success */
123                 p += LDAPS_URL_PREFIX_LEN;
124                 *ldaps = 1;
125                 return( p );
126         }
127
128         return( NULL );
129 }
130
131
132 static int str2scope( const char *p )
133 {
134         if ( strcasecmp( p, "one" ) == 0 ) {
135                 return LDAP_SCOPE_ONELEVEL;
136
137         } else if ( strcasecmp( p, "onetree" ) == 0 ) {
138                 return LDAP_SCOPE_ONELEVEL;
139
140         } else if ( strcasecmp( p, "base" ) == 0 ) {
141                 return LDAP_SCOPE_BASE;
142
143         } else if ( strcasecmp( p, "sub" ) == 0 ) {
144                 return LDAP_SCOPE_SUBTREE;
145
146         } else if ( strcasecmp( p, "subtree" ) == 0 ) {
147                 return LDAP_SCOPE_SUBTREE;
148         }
149
150         return( -1 );
151 }
152
153
154 int
155 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
156 {
157 /*
158  *  Pick apart the pieces of an LDAP URL.
159  */
160
161         LDAPURLDesc     *ludp;
162         char    *p, *q;
163         int             i, enclosed, ldaps;
164         const char *url_tmp;
165         char *url;
166
167         if( url_in == NULL && ludpp == NULL ) {
168                 return LDAP_URL_ERR_PARAM;
169         }
170
171         Debug( LDAP_DEBUG_TRACE, "ldap_url_parse(%s)\n", url_in, 0, 0 );
172
173         *ludpp = NULL;  /* pessimistic */
174
175         url_tmp = skip_url_prefix( url_in, &enclosed, &ldaps );
176
177         if ( url_tmp == NULL ) {
178                 return LDAP_URL_ERR_NOTLDAP;
179         }
180
181         /* make working copy of the remainder of the URL */
182         if (( url = LDAP_STRDUP( url_tmp )) == NULL ) {
183                 return( LDAP_URL_ERR_MEM );
184         }
185
186         if ( enclosed ) {
187                 p = &url[strlen(url)-1];
188
189                 if( *p != '>' ) {
190                         LDAP_FREE( url );
191                         return LDAP_URL_ERR_BADENCLOSURE;
192                 }
193
194                 *p = '\0';
195         }
196
197         /* allocate return struct */
198         ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
199
200         if ( ludp == NULL ) {
201                 LDAP_FREE( url );
202                 return LDAP_URL_ERR_MEM;
203         }
204
205         ludp->lud_next = NULL;
206         ludp->lud_host = NULL;
207         ludp->lud_port = 0;
208     ludp->lud_dn = NULL;
209     ludp->lud_attrs = NULL;
210     ludp->lud_filter = NULL;
211         ludp->lud_ldaps = ldaps;
212         ludp->lud_scope = LDAP_SCOPE_BASE;
213
214         ludp->lud_filter = LDAP_STRDUP("(objectClass=*)");
215
216         if( ludp->lud_filter == NULL ) {
217                 LDAP_FREE( url );
218                 ldap_free_urldesc( ludp );
219                 return LDAP_URL_ERR_MEM;
220         }
221
222         /* scan forward for '/' that marks end of hostport and begin. of dn */
223         p = strchr( url, '/' );
224
225         if( p != NULL ) {
226                 /* terminate hostport; point to start of dn */
227                 *p++ = '\0';
228         }
229
230         if (( q = strchr( url, ':' )) != NULL ) {
231                 *q++ = '\0';
232                 ldap_pvt_hex_unescape( q );
233
234                 if( *q == '\0' ) {
235                         LDAP_FREE( url );
236                         ldap_free_urldesc( ludp );
237                         return LDAP_URL_ERR_BADURL;
238                 }
239
240                 ludp->lud_port = atoi( q );
241         }
242
243         ldap_pvt_hex_unescape( url );
244         ludp->lud_host = LDAP_STRDUP( url );
245
246         if( ludp->lud_host == NULL ) {
247                 LDAP_FREE( url );
248                 ldap_free_urldesc( ludp );
249                 return LDAP_URL_ERR_MEM;
250         }
251
252         if( p == NULL ) {
253                 LDAP_FREE( url );
254                 *ludpp = ludp;
255                 return LDAP_URL_SUCCESS;
256         }
257
258         /* scan forward for '?' that may marks end of dn */
259         q = strchr( p, '?' );
260
261         if( q != NULL ) {
262                 /* terminate dn part */
263                 *q++ = '\0';
264         }
265
266         if( *p != '\0' ) {
267                 /* parse dn part */
268                 ldap_pvt_hex_unescape( p );
269                 ludp->lud_dn = LDAP_STRDUP( p );
270         } else {
271                 ludp->lud_dn = LDAP_STRDUP( "" );
272         }
273
274         if( ludp->lud_dn == NULL ) {
275                 LDAP_FREE( url );
276                 ldap_free_urldesc( ludp );
277                 return LDAP_URL_ERR_MEM;
278         }
279
280         if( q == NULL ) {
281                 /* no more */
282                 LDAP_FREE( url );
283                 *ludpp = ludp;
284                 return LDAP_URL_SUCCESS;
285         }
286
287         /* scan forward for '?' that may marks end of attributes */
288         p = q;
289         q = strchr( p, '?' );
290
291         if( q != NULL ) {
292                 /* terminate attributes part */
293                 *q++ = '\0';
294         }
295
296         if( *p != '\0' ) {
297                 /* parse attributes */
298                 ldap_pvt_hex_unescape( p );
299                 ludp->lud_attrs = ldap_str2charray( p, "," );
300
301                 if( ludp->lud_attrs == NULL ) {
302                         LDAP_FREE( url );
303                         ldap_free_urldesc( ludp );
304                         return LDAP_URL_ERR_BADATTRS;
305                 }
306         }
307
308         if ( q == NULL ) {
309                 /* no more */
310                 LDAP_FREE( url );
311                 *ludpp = ludp;
312                 return LDAP_URL_SUCCESS;
313         }
314
315         /* scan forward for '?' that may marks end of scope */
316         p = q;
317         q = strchr( p, '?' );
318
319         if( q != NULL ) {
320                 /* terminate the scope part */
321                 *q++ = '\0';
322         }
323
324         if( *p != '\0' ) {
325                 /* parse the scope */
326                 ldap_pvt_hex_unescape( p );
327                 ludp->lud_scope = str2scope( p );
328
329                 if( ludp->lud_scope == -1 ) {
330                         LDAP_FREE( url );
331                         ldap_free_urldesc( ludp );
332                         return LDAP_URL_ERR_BADSCOPE;
333                 }
334         }
335
336         if ( q == NULL ) {
337                 /* no more */
338                 LDAP_FREE( url );
339                 *ludpp = ludp;
340                 return LDAP_URL_SUCCESS;
341         }
342
343         /* scan forward for '?' that may marks end of filter */
344         p = q;
345         q = strchr( p, '?' );
346
347         if( q != NULL ) {
348                 /* terminate the filter part */
349                 *q++ = '\0';
350         }
351
352         if( *p != '\0' ) {
353                 /* parse the filter */
354                 ldap_pvt_hex_unescape( p );
355
356                 if( ! *p ) {
357                         /* missing filter */
358                         LDAP_FREE( url );
359                         ldap_free_urldesc( ludp );
360                         return LDAP_URL_ERR_BADFILTER;
361                 }
362
363                 LDAP_FREE( ludp->lud_filter );
364                 ludp->lud_filter = LDAP_STRDUP( p );
365
366                 if( ludp->lud_filter == NULL ) {
367                         LDAP_FREE( url );
368                         ldap_free_urldesc( ludp );
369                         return LDAP_URL_ERR_MEM;
370                 }
371         }
372
373         if ( q == NULL ) {
374                 /* no more */
375                 LDAP_FREE( url );
376                 *ludpp = ludp;
377                 return LDAP_URL_SUCCESS;
378         }
379
380         /* scan forward for '?' that may marks end of extensions */
381         p = q;
382         q = strchr( p, '?' );
383
384         if( q != NULL ) {
385                 /* extra '?' */
386                 LDAP_FREE( url );
387                 ldap_free_urldesc( ludp );
388                 return LDAP_URL_ERR_BADURL;
389         }
390
391         /* parse the extensions */
392         ludp->lud_exts = ldap_str2charray( p, "," );
393
394         if( ludp->lud_exts == NULL ) {
395                 LDAP_FREE( url );
396                 ldap_free_urldesc( ludp );
397                 return LDAP_URL_ERR_BADEXTS;
398         }
399
400         for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
401                 ldap_pvt_hex_unescape( ludp->lud_exts[i] );
402         }
403
404         if( i == 0 ) {
405                 /* must have 1 or more */
406                 ldap_charray_free( ludp->lud_exts );
407                 LDAP_FREE( url );
408                 ldap_free_urldesc( ludp );
409                 return LDAP_URL_ERR_BADEXTS;
410         }
411
412         /* no more */
413         *ludpp = ludp;
414         LDAP_FREE( url );
415         return LDAP_URL_SUCCESS;
416 }
417
418 LDAPURLDesc *
419 ldap_url_dup ( LDAPURLDesc *ludp )
420 {
421         LDAPURLDesc *dest;
422
423         if ( ludp == NULL ) {
424                 return NULL;
425         }
426
427         dest = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
428         if (dest == NULL)
429                 return NULL;
430         
431         if ( ludp->lud_host != NULL ) {
432                 dest->lud_host = LDAP_STRDUP( ludp->lud_host );
433                 if (dest->lud_host == NULL) {
434                         ldap_free_urldesc(dest);
435                         return NULL;
436                 }
437         }
438
439         if ( ludp->lud_dn != NULL ) {
440                 dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
441                 if (dest->lud_dn == NULL) {
442                         ldap_free_urldesc(dest);
443                         return NULL;
444                 }
445         }
446
447         if ( ludp->lud_filter != NULL ) {
448                 dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
449                 if (dest->lud_filter == NULL) {
450                         ldap_free_urldesc(dest);
451                         return NULL;
452                 }
453         }
454
455         if ( ludp->lud_attrs != NULL ) {
456                 dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
457                 if (dest->lud_attrs == NULL) {
458                         ldap_free_urldesc(dest);
459                         return NULL;
460                 }
461         }
462
463         if ( ludp->lud_exts != NULL ) {
464                 dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
465                 if (dest->lud_exts == NULL) {
466                         ldap_free_urldesc(dest);
467                         return NULL;
468                 }
469         }
470
471         dest->lud_ldaps = ludp->lud_ldaps;
472         dest->lud_port = ludp->lud_port;
473         dest->lud_scope = ludp->lud_scope;
474
475         return dest;
476 }
477
478 LDAPURLDesc *
479 ldap_url_duplist (LDAPURLDesc *ludlist)
480 {
481         LDAPURLDesc *dest, *tail, *ludp, *newludp;
482
483         dest = NULL;
484         tail = NULL;
485         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
486                 newludp = ldap_url_dup(ludp);
487                 if (newludp == NULL) {
488                         ldap_free_urllist(dest);
489                         return NULL;
490                 }
491                 if (tail == NULL)
492                         dest = newludp;
493                 else
494                         tail->lud_next = newludp;
495                 tail = newludp;
496         }
497         return dest;
498 }
499
500 int
501 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
502 {
503         int i, rc;
504         LDAPURLDesc *ludp;
505         char **urls;
506
507         *ludlist = NULL;
508
509         if (url == NULL)
510                 return LDAP_PARAM_ERROR;
511
512         urls = ldap_str2charray((char *)url, ", ");
513         if (urls == NULL)
514                 return LDAP_NO_MEMORY;
515
516         /* count the URLs... */
517         for (i = 0; urls[i] != NULL; i++) ;
518         /* ...and put them in the "stack" backward */
519         while (--i >= 0) {
520                 rc = ldap_url_parse( urls[i], &ludp );
521                 if ( rc != 0 ) {
522                         ldap_charray_free(urls);
523                         ldap_free_urllist(*ludlist);
524                         *ludlist = NULL;
525                         return rc;
526                 }
527                 ludp->lud_next = *ludlist;
528                 *ludlist = ludp;
529         }
530         ldap_charray_free(urls);
531         return LDAP_SUCCESS;
532 }
533
534 int
535 ldap_url_parsehosts (LDAPURLDesc **ludlist, const char *hosts )
536 {
537         int i;
538         LDAPURLDesc *ludp;
539         char **specs, *p;
540
541         *ludlist = NULL;
542
543         if (hosts == NULL)
544                 return LDAP_PARAM_ERROR;
545
546         specs = ldap_str2charray((char *)hosts, ", ");
547         if (specs == NULL)
548                 return LDAP_NO_MEMORY;
549
550         /* count the URLs... */
551         for (i = 0; specs[i] != NULL; i++) ;
552         /* ...and put them in the "stack" backward */
553         while (--i >= 0) {
554                 ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
555                 if (ludp == NULL) {
556                         ldap_charray_free(specs);
557                         ldap_free_urllist(*ludlist);
558                         *ludlist = NULL;
559                         return LDAP_NO_MEMORY;
560                 }
561                 ludp->lud_host = specs[i];
562                 specs[i] = NULL;
563                 p = strchr(ludp->lud_host, ':');
564                 if (p != NULL) {
565                         *p++ = 0;
566                         ludp->lud_port = atoi(p);
567                 }
568                 if (ludp->lud_port == LDAPS_PORT)
569                         ludp->lud_ldaps = 1;    /* cheat */
570                 ludp->lud_next = *ludlist;
571                 *ludlist = ludp;
572         }
573
574         /* this should be an array of NULLs now */
575         ldap_charray_free(specs);
576         return LDAP_SUCCESS;
577 }
578
579 char *
580 ldap_url_list2hosts (LDAPURLDesc *ludlist)
581 {
582         LDAPURLDesc *ludp;
583         int size;
584         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
585
586         if (ludlist == NULL)
587                 return NULL;
588
589         /* figure out how big the string is */
590         size = 1;       /* nul-term */
591         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
592                 size += strlen(ludp->lud_host) + 1;             /* host and space */
593                 if (ludp->lud_port != 0)
594                         size += sprintf(buf, ":%d", ludp->lud_port);
595         }
596         s = LDAP_MALLOC(size);
597         if (s == NULL)
598                 return NULL;
599
600         p = s;
601         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
602                 strcpy(p, ludp->lud_host);
603                 p += strlen(ludp->lud_host);
604                 if (ludp->lud_port != 0)
605                         p += sprintf(p, ":%d", ludp->lud_port);
606                 *p++ = ' ';
607         }
608         if (p != s)
609                 p--;    /* nuke that extra space */
610         *p = 0;
611         return s;
612 }
613
614 char *
615 ldap_url_list2urls (LDAPURLDesc *ludlist)
616 {
617         LDAPURLDesc *ludp;
618         int size;
619         char *s, *p, buf[32];   /* big enough to hold a long decimal # (overkill) */
620
621         if (ludlist == NULL)
622                 return NULL;
623
624         /* figure out how big the string is */
625         size = 1;       /* nul-term */
626         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
627                 size += strlen(ludp->lud_host) + 1 + sizeof("ldapis:///");      /* prefix, host, /, and space */
628                 if (ludp->lud_port != 0)
629                         size += sprintf(buf, ":%d", ludp->lud_port);
630         }
631         s = LDAP_MALLOC(size);
632         if (s == NULL)
633                 return NULL;
634
635         p = s;
636         for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
637                 p += sprintf(p, "ldap%s://%s", ludp->lud_ldaps ? "s" : "", ludp->lud_host);
638                 if (ludp->lud_port != 0)
639                         p += sprintf(p, ":%d", ludp->lud_port);
640                 *p++ = '/';
641                 *p++ = ' ';
642         }
643         if (p != s)
644                 p--;    /* nuke that extra space */
645         *p = 0;
646         return s;
647 }
648
649 void
650 ldap_free_urllist( LDAPURLDesc *ludlist )
651 {
652         LDAPURLDesc *ludp, *next;
653
654         for (ludp = ludlist; ludp != NULL; ludp = next) {
655                 next = ludp->lud_next;
656                 ldap_free_urldesc(ludp);
657         }
658 }
659
660 void
661 ldap_free_urldesc( LDAPURLDesc *ludp )
662 {
663         if ( ludp == NULL ) {
664                 return;
665         }
666         
667         if ( ludp->lud_host != NULL ) {
668                 LDAP_FREE( ludp->lud_host );
669         }
670
671         if ( ludp->lud_dn != NULL ) {
672                 LDAP_FREE( ludp->lud_dn );
673         }
674
675         if ( ludp->lud_filter != NULL ) {
676                 LDAP_FREE( ludp->lud_filter);
677         }
678
679         if ( ludp->lud_attrs != NULL ) {
680                 LDAP_VFREE( ludp->lud_attrs );
681         }
682
683         if ( ludp->lud_exts != NULL ) {
684                 LDAP_VFREE( ludp->lud_exts );
685         }
686
687         LDAP_FREE( ludp );
688 }
689
690
691
692 int
693 ldap_url_search( LDAP *ld, LDAP_CONST char *url, int attrsonly )
694 {
695         int             err;
696         LDAPURLDesc     *ludp;
697         BerElement      *ber;
698
699         if ( ldap_url_parse( url, &ludp ) != 0 ) {
700                 ld->ld_errno = LDAP_PARAM_ERROR;
701                 return( -1 );
702         }
703
704         ber = ldap_build_search_req( ld, ludp->lud_dn, ludp->lud_scope,
705             ludp->lud_filter, ludp->lud_attrs, attrsonly, NULL, NULL,
706                 -1, -1 );
707
708         if ( ber == NULL ) {
709                 err = -1;
710         } else {
711                 err = ldap_send_server_request(
712                                         ld, ber, ld->ld_msgid, NULL,
713                                         (ludp->lud_host != NULL || ludp->lud_port != 0)
714                                                 ? ludp : NULL,
715                                         NULL, 1 );
716         }
717
718         ldap_free_urldesc( ludp );
719         return( err );
720 }
721
722
723 int
724 ldap_url_search_st( LDAP *ld, LDAP_CONST char *url, int attrsonly,
725         struct timeval *timeout, LDAPMessage **res )
726 {
727         int     msgid;
728
729         if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
730                 return( ld->ld_errno );
731         }
732
733         if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 ) {
734                 return( ld->ld_errno );
735         }
736
737         if ( ld->ld_errno == LDAP_TIMEOUT ) {
738                 (void) ldap_abandon( ld, msgid );
739                 ld->ld_errno = LDAP_TIMEOUT;
740                 return( ld->ld_errno );
741         }
742
743         return( ldap_result2error( ld, *res, 0 ));
744 }
745
746
747 int
748 ldap_url_search_s(
749         LDAP *ld, LDAP_CONST char *url, int attrsonly, LDAPMessage **res )
750 {
751         int     msgid;
752
753         if (( msgid = ldap_url_search( ld, url, attrsonly )) == -1 ) {
754                 return( ld->ld_errno );
755         }
756
757         if ( ldap_result( ld, msgid, 1, (struct timeval *)NULL, res ) == -1 ) {
758                 return( ld->ld_errno );
759         }
760
761         return( ldap_result2error( ld, *res, 0 ));
762 }
763
764
765 void
766 ldap_pvt_hex_unescape( char *s )
767 {
768 /*
769  * Remove URL hex escapes from s... done in place.  The basic concept for
770  * this routine is borrowed from the WWW library HTUnEscape() routine.
771  */
772         char    *p;
773
774         for ( p = s; *s != '\0'; ++s ) {
775                 if ( *s == '%' ) {
776                         if ( *++s != '\0' ) {
777                                 *p = ldap_pvt_unhex( *s ) << 4;
778                         }
779                         if ( *++s != '\0' ) {
780                                 *p++ += ldap_pvt_unhex( *s );
781                         }
782                 } else {
783                         *p++ = *s;
784                 }
785         }
786
787         *p = '\0';
788 }
789
790
791 int
792 ldap_pvt_unhex( int c )
793 {
794         return( c >= '0' && c <= '9' ? c - '0'
795             : c >= 'A' && c <= 'F' ? c - 'A' + 10
796             : c - 'a' + 10 );
797 }