xref: /NextBSD/contrib/apr-util/ldap/apr_ldap_url.c (revision eb1a5f8de9f7ea602c373a710f531abbf81141c4)
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /* Portions Copyright 1998-2002 The OpenLDAP Foundation
18  * All rights reserved.
19  *
20  * Redistribution and use in source and binary forms, with or without
21  * modification, are permitted only as authorized by the OpenLDAP
22  * Public License.  A copy of this license is available at
23  * http://www.OpenLDAP.org/license.html or in file LICENSE in the
24  * top-level directory of the distribution.
25  *
26  * OpenLDAP is a registered trademark of the OpenLDAP Foundation.
27  *
28  * Individual files and/or contributed packages may be copyright by
29  * other parties and subject to additional restrictions.
30  *
31  * This work is derived from the University of Michigan LDAP v3.3
32  * distribution.  Information concerning this software is available
33  * at: http://www.umich.edu/~dirsvcs/ldap/
34  *
35  * This work also contains materials derived from public sources.
36  *
37  * Additional information about OpenLDAP can be obtained at:
38  *     http://www.openldap.org/
39  */
40 
41 /*
42  * Portions Copyright (c) 1992-1996 Regents of the University of Michigan.
43  * All rights reserved.
44  *
45  * Redistribution and use in source and binary forms are permitted
46  * provided that this notice is preserved and that due credit is given
47  * to the University of Michigan at Ann Arbor. The name of the University
48  * may not be used to endorse or promote products derived from this
49  * software without specific prior written permission. This software
50  * is provided ``as is'' without express or implied warranty.
51  */
52 
53 /*  apr_ldap_url.c -- LDAP URL (RFC 2255) related routines
54  *
55  *  Win32 and perhaps other non-OpenLDAP based ldap libraries may be
56  *  missing ldap_url_* APIs.  We focus here on the one significant
57  *  aspect, which is parsing.  We have [for the time being] omitted
58  *  the ldap_url_search APIs.
59  *
60  *  LDAP URLs look like this:
61  *    ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
62  *
63  *  where:
64  *   attributes is a comma separated list
65  *   scope is one of these three strings:  base one sub (default=base)
66  *   filter is an string-represented filter as in RFC 2254
67  *
68  *  e.g.,  ldap://host:port/dc=com?o,cn?base?o=openldap?extension
69  *
70  *  Tolerates URLs that look like: <ldapurl> and <URL:ldapurl>
71  */
72 
73 #include "apu.h"
74 #include "apr_pools.h"
75 #include "apr_general.h"
76 #include "apr_strings.h"
77 #include "apr_ldap.h"
78 
79 #if APR_HAS_LDAP
80 
81 #if APR_HAVE_STDLIB_H
82 #include <stdlib.h>
83 #endif
84 
85 #ifndef LDAPS_PORT
86 #define LDAPS_PORT              636  /* ldaps:/// default LDAP over TLS port */
87 #endif
88 
89 #define APR_LDAP_URL_PREFIX         "ldap://"
90 #define APR_LDAP_URL_PREFIX_LEN     (sizeof(APR_LDAP_URL_PREFIX)-1)
91 #define APR_LDAPS_URL_PREFIX        "ldaps://"
92 #define APR_LDAPS_URL_PREFIX_LEN    (sizeof(APR_LDAPS_URL_PREFIX)-1)
93 #define APR_LDAPI_URL_PREFIX        "ldapi://"
94 #define APR_LDAPI_URL_PREFIX_LEN    (sizeof(APR_LDAPI_URL_PREFIX)-1)
95 #define APR_LDAP_URL_URLCOLON       "URL:"
96 #define APR_LDAP_URL_URLCOLON_LEN   (sizeof(APR_LDAP_URL_URLCOLON)-1)
97 
98 
99 /* local functions */
100 static const char* skip_url_prefix(const char *url,
101                                    int *enclosedp,
102                                    const char **scheme);
103 
104 static void apr_ldap_pvt_hex_unescape(char *s);
105 
106 static int apr_ldap_pvt_unhex(int c);
107 
108 static char **apr_ldap_str2charray(apr_pool_t *pool,
109                                    const char *str,
110                                    const char *brkstr);
111 
112 
113 /**
114  * Is this URL an ldap url?
115  *
116  */
apr_ldap_is_ldap_url(const char * url)117 APU_DECLARE(int) apr_ldap_is_ldap_url(const char *url)
118 {
119     int enclosed;
120     const char * scheme;
121 
122     if( url == NULL ) {
123         return 0;
124     }
125 
126     if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
127         return 0;
128     }
129 
130     return 1;
131 }
132 
133 /**
134  * Is this URL a secure ldap url?
135  *
136  */
apr_ldap_is_ldaps_url(const char * url)137 APU_DECLARE(int) apr_ldap_is_ldaps_url(const char *url)
138 {
139     int enclosed;
140     const char * scheme;
141 
142     if( url == NULL ) {
143         return 0;
144     }
145 
146     if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
147         return 0;
148     }
149 
150     return strcmp(scheme, "ldaps") == 0;
151 }
152 
153 /**
154  * Is this URL an ldap socket url?
155  *
156  */
apr_ldap_is_ldapi_url(const char * url)157 APU_DECLARE(int) apr_ldap_is_ldapi_url(const char *url)
158 {
159     int enclosed;
160     const char * scheme;
161 
162     if( url == NULL ) {
163         return 0;
164     }
165 
166     if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
167         return 0;
168     }
169 
170     return strcmp(scheme, "ldapi") == 0;
171 }
172 
173 
skip_url_prefix(const char * url,int * enclosedp,const char ** scheme)174 static const char *skip_url_prefix(const char *url, int *enclosedp,
175                                    const char **scheme)
176 {
177     /*
178      * return non-zero if this looks like a LDAP URL; zero if not
179      * if non-zero returned, *urlp will be moved past "ldap://" part of URL
180      */
181     const char *p;
182 
183     if ( url == NULL ) {
184         return( NULL );
185     }
186 
187     p = url;
188 
189     /* skip leading '<' (if any) */
190     if ( *p == '<' ) {
191         *enclosedp = 1;
192         ++p;
193     } else {
194         *enclosedp = 0;
195     }
196 
197     /* skip leading "URL:" (if any) */
198     if ( strncasecmp( p, APR_LDAP_URL_URLCOLON, APR_LDAP_URL_URLCOLON_LEN ) == 0 ) {
199         p += APR_LDAP_URL_URLCOLON_LEN;
200     }
201 
202     /* check for "ldap://" prefix */
203     if ( strncasecmp( p, APR_LDAP_URL_PREFIX, APR_LDAP_URL_PREFIX_LEN ) == 0 ) {
204         /* skip over "ldap://" prefix and return success */
205         p += APR_LDAP_URL_PREFIX_LEN;
206         *scheme = "ldap";
207         return( p );
208     }
209 
210     /* check for "ldaps://" prefix */
211     if ( strncasecmp( p, APR_LDAPS_URL_PREFIX, APR_LDAPS_URL_PREFIX_LEN ) == 0 ) {
212         /* skip over "ldaps://" prefix and return success */
213         p += APR_LDAPS_URL_PREFIX_LEN;
214         *scheme = "ldaps";
215         return( p );
216     }
217 
218     /* check for "ldapi://" prefix */
219     if ( strncasecmp( p, APR_LDAPI_URL_PREFIX, APR_LDAPI_URL_PREFIX_LEN ) == 0 ) {
220         /* skip over "ldapi://" prefix and return success */
221         p += APR_LDAPI_URL_PREFIX_LEN;
222         *scheme = "ldapi";
223         return( p );
224     }
225 
226     return( NULL );
227 }
228 
229 
str2scope(const char * p)230 static int str2scope(const char *p)
231 {
232     if ( strcasecmp( p, "one" ) == 0 ) {
233         return LDAP_SCOPE_ONELEVEL;
234 
235     } else if ( strcasecmp( p, "onetree" ) == 0 ) {
236         return LDAP_SCOPE_ONELEVEL;
237 
238     } else if ( strcasecmp( p, "base" ) == 0 ) {
239         return LDAP_SCOPE_BASE;
240 
241     } else if ( strcasecmp( p, "sub" ) == 0 ) {
242         return LDAP_SCOPE_SUBTREE;
243 
244     } else if ( strcasecmp( p, "subtree" ) == 0 ) {
245         return LDAP_SCOPE_SUBTREE;
246     }
247 
248     return( -1 );
249 }
250 
251 
252 /**
253  * Parse the URL provided into an apr_ldap_url_desc_t object.
254  *
255  * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
256  * The LDAP result code and reason string is returned in the
257  * apr_ldap_err_t structure.
258  */
apr_ldap_url_parse_ext(apr_pool_t * pool,const char * url_in,apr_ldap_url_desc_t ** ludpp,apr_ldap_err_t ** result_err)259 APU_DECLARE(int) apr_ldap_url_parse_ext(apr_pool_t *pool,
260                                         const char *url_in,
261                                         apr_ldap_url_desc_t **ludpp,
262                                         apr_ldap_err_t **result_err)
263 {
264     apr_ldap_url_desc_t *ludp;
265     char        *p, *q, *r;
266     int         i, enclosed;
267     const char  *scheme = NULL;
268     const char  *url_tmp;
269     char        *url;
270 
271     apr_ldap_err_t *result = (apr_ldap_err_t *)apr_pcalloc(pool, sizeof(apr_ldap_err_t));
272     *result_err = result;
273 
274     /* sanity check our parameters */
275     if( url_in == NULL || ludpp == NULL ) {
276         result->reason = "Either the LDAP URL, or the URL structure was NULL. Oops.";
277         result->rc = APR_LDAP_URL_ERR_PARAM;
278         return APR_EGENERAL;
279     }
280 
281     *ludpp = NULL;  /* pessimistic */
282 
283     url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
284     if ( url_tmp == NULL ) {
285         result->reason = "The scheme was not recognised as a valid LDAP URL scheme.";
286         result->rc = APR_LDAP_URL_ERR_BADSCHEME;
287         return APR_EGENERAL;
288     }
289 
290     /* make working copy of the remainder of the URL */
291     url = (char *)apr_pstrdup(pool, url_tmp);
292     if ( url == NULL ) {
293         result->reason = "Out of memory parsing LDAP URL.";
294         result->rc = APR_LDAP_URL_ERR_MEM;
295         return APR_EGENERAL;
296     }
297 
298     if ( enclosed ) {
299         p = &url[strlen(url)-1];
300 
301         if( *p != '>' ) {
302             result->reason = "Bad enclosure error while parsing LDAP URL.";
303             result->rc = APR_LDAP_URL_ERR_BADENCLOSURE;
304             return APR_EGENERAL;
305         }
306 
307         *p = '\0';
308     }
309 
310     /* allocate return struct */
311     ludp = (apr_ldap_url_desc_t *)apr_pcalloc(pool, sizeof(apr_ldap_url_desc_t));
312     if ( ludp == NULL ) {
313         result->reason = "Out of memory parsing LDAP URL.";
314         result->rc = APR_LDAP_URL_ERR_MEM;
315         return APR_EGENERAL;
316     }
317 
318     ludp->lud_next = NULL;
319     ludp->lud_host = NULL;
320     ludp->lud_port = LDAP_PORT;
321     ludp->lud_dn = NULL;
322     ludp->lud_attrs = NULL;
323     ludp->lud_filter = NULL;
324     ludp->lud_scope = -1;
325     ludp->lud_filter = NULL;
326     ludp->lud_exts = NULL;
327 
328     ludp->lud_scheme = (char *)apr_pstrdup(pool, scheme);
329     if ( ludp->lud_scheme == NULL ) {
330         result->reason = "Out of memory parsing LDAP URL.";
331         result->rc = APR_LDAP_URL_ERR_MEM;
332         return APR_EGENERAL;
333     }
334 
335     if( strcasecmp( ludp->lud_scheme, "ldaps" ) == 0 ) {
336         ludp->lud_port = LDAPS_PORT;
337     }
338 
339     /* scan forward for '/' that marks end of hostport and begin. of dn */
340     p = strchr( url, '/' );
341 
342     if( p != NULL ) {
343         /* terminate hostport; point to start of dn */
344         *p++ = '\0';
345     }
346 
347     /* IPv6 syntax with [ip address]:port */
348     if ( *url == '[' ) {
349         r = strchr( url, ']' );
350         if ( r == NULL ) {
351             result->reason = "Bad LDAP URL while parsing IPV6 syntax.";
352             result->rc = APR_LDAP_URL_ERR_BADURL;
353             return APR_EGENERAL;
354         }
355         *r++ = '\0';
356         q = strrchr( r, ':' );
357     } else {
358         q = strrchr( url, ':' );
359     }
360 
361     if ( q != NULL ) {
362         apr_ldap_pvt_hex_unescape( ++q );
363 
364         if( *q == '\0' ) {
365             result->reason = "Bad LDAP URL while parsing.";
366             result->rc = APR_LDAP_URL_ERR_BADURL;
367             return APR_EGENERAL;
368         }
369 
370         ludp->lud_port = atoi( q );
371     }
372 
373     apr_ldap_pvt_hex_unescape( url );
374 
375     /* If [ip address]:port syntax, url is [ip and we skip the [ */
376     ludp->lud_host = (char *)apr_pstrdup(pool, url + ( *url == '[' ));
377     if( ludp->lud_host == NULL ) {
378         result->reason = "Out of memory parsing LDAP URL.";
379         result->rc = APR_LDAP_URL_ERR_MEM;
380         return APR_EGENERAL;
381     }
382 
383     /*
384      * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
385      *
386      * On early Novell releases, search references/referrals were returned
387      * in this format, i.e., the dn was kind of in the scope position,
388      * but the required slash is missing. The whole thing is illegal syntax,
389      * but we need to account for it. Fortunately it can't be confused with
390      * anything real.
391      */
392     if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
393         q++;
394         /* ? immediately followed by question */
395         if( *q == '?') {
396             q++;
397             if( *q != '\0' ) {
398                 /* parse dn part */
399                 apr_ldap_pvt_hex_unescape( q );
400                 ludp->lud_dn = (char *)apr_pstrdup(pool, q);
401             } else {
402                 ludp->lud_dn = (char *)apr_pstrdup(pool, "");
403             }
404 
405             if( ludp->lud_dn == NULL ) {
406                 result->reason = "Out of memory parsing LDAP URL.";
407                 result->rc = APR_LDAP_URL_ERR_MEM;
408                 return APR_EGENERAL;
409             }
410         }
411     }
412 
413     if( p == NULL ) {
414         *ludpp = ludp;
415         return APR_SUCCESS;
416     }
417 
418     /* scan forward for '?' that may marks end of dn */
419     q = strchr( p, '?' );
420 
421     if( q != NULL ) {
422         /* terminate dn part */
423         *q++ = '\0';
424     }
425 
426     if( *p != '\0' ) {
427         /* parse dn part */
428         apr_ldap_pvt_hex_unescape( p );
429         ludp->lud_dn = (char *)apr_pstrdup(pool, p);
430     } else {
431         ludp->lud_dn = (char *)apr_pstrdup(pool, "");
432     }
433 
434     if( ludp->lud_dn == NULL ) {
435         result->reason = "Out of memory parsing LDAP URL.";
436         result->rc = APR_LDAP_URL_ERR_MEM;
437         return APR_EGENERAL;
438     }
439 
440     if( q == NULL ) {
441         /* no more */
442         *ludpp = ludp;
443         return APR_SUCCESS;
444     }
445 
446     /* scan forward for '?' that may marks end of attributes */
447     p = q;
448     q = strchr( p, '?' );
449 
450     if( q != NULL ) {
451         /* terminate attributes part */
452         *q++ = '\0';
453     }
454 
455     if( *p != '\0' ) {
456         /* parse attributes */
457         apr_ldap_pvt_hex_unescape( p );
458         ludp->lud_attrs = apr_ldap_str2charray(pool, p, ",");
459 
460         if( ludp->lud_attrs == NULL ) {
461             result->reason = "Bad attributes encountered while parsing LDAP URL.";
462             result->rc = APR_LDAP_URL_ERR_BADATTRS;
463             return APR_EGENERAL;
464         }
465     }
466 
467     if ( q == NULL ) {
468         /* no more */
469         *ludpp = ludp;
470         return APR_SUCCESS;
471     }
472 
473     /* scan forward for '?' that may marks end of scope */
474     p = q;
475     q = strchr( p, '?' );
476 
477     if( q != NULL ) {
478         /* terminate the scope part */
479         *q++ = '\0';
480     }
481 
482     if( *p != '\0' ) {
483         /* parse the scope */
484         apr_ldap_pvt_hex_unescape( p );
485         ludp->lud_scope = str2scope( p );
486 
487         if( ludp->lud_scope == -1 ) {
488             result->reason = "Bad scope encountered while parsing LDAP URL.";
489             result->rc = APR_LDAP_URL_ERR_BADSCOPE;
490             return APR_EGENERAL;
491         }
492     }
493 
494     if ( q == NULL ) {
495         /* no more */
496         *ludpp = ludp;
497         return APR_SUCCESS;
498     }
499 
500     /* scan forward for '?' that may marks end of filter */
501     p = q;
502     q = strchr( p, '?' );
503 
504     if( q != NULL ) {
505         /* terminate the filter part */
506         *q++ = '\0';
507     }
508 
509     if( *p != '\0' ) {
510         /* parse the filter */
511         apr_ldap_pvt_hex_unescape( p );
512 
513         if( ! *p ) {
514             /* missing filter */
515             result->reason = "Bad filter encountered while parsing LDAP URL.";
516             result->rc = APR_LDAP_URL_ERR_BADFILTER;
517             return APR_EGENERAL;
518         }
519 
520         ludp->lud_filter = (char *)apr_pstrdup(pool, p);
521         if( ludp->lud_filter == NULL ) {
522             result->reason = "Out of memory parsing LDAP URL.";
523             result->rc = APR_LDAP_URL_ERR_MEM;
524             return APR_EGENERAL;
525         }
526     }
527 
528     if ( q == NULL ) {
529         /* no more */
530         *ludpp = ludp;
531         return APR_SUCCESS;
532     }
533 
534     /* scan forward for '?' that may marks end of extensions */
535     p = q;
536     q = strchr( p, '?' );
537 
538     if( q != NULL ) {
539         /* extra '?' */
540         result->reason = "Bad URL encountered while parsing LDAP URL.";
541         result->rc = APR_LDAP_URL_ERR_BADURL;
542         return APR_EGENERAL;
543     }
544 
545     /* parse the extensions */
546     ludp->lud_exts = apr_ldap_str2charray(pool, p, ",");
547     if( ludp->lud_exts == NULL ) {
548         result->reason = "Bad extensions encountered while parsing LDAP URL.";
549         result->rc = APR_LDAP_URL_ERR_BADEXTS;
550         return APR_EGENERAL;
551     }
552 
553     for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
554         apr_ldap_pvt_hex_unescape( ludp->lud_exts[i] );
555 
556         if( *ludp->lud_exts[i] == '!' ) {
557             /* count the number of critical extensions */
558             ludp->lud_crit_exts++;
559         }
560     }
561 
562     if( i == 0 ) {
563         /* must have 1 or more */
564         result->reason = "Bad extensions encountered while parsing LDAP URL.";
565         result->rc = APR_LDAP_URL_ERR_BADEXTS;
566         return APR_EGENERAL;
567     }
568 
569     /* no more */
570     *ludpp = ludp;
571     return APR_SUCCESS;
572 }
573 
574 
575 /**
576  * Parse the URL provided into an apr_ldap_url_desc_t object.
577  *
578  * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
579  * The LDAP result code and reason string is returned in the
580  * apr_ldap_err_t structure.
581  */
apr_ldap_url_parse(apr_pool_t * pool,const char * url_in,apr_ldap_url_desc_t ** ludpp,apr_ldap_err_t ** result_err)582 APU_DECLARE(int) apr_ldap_url_parse(apr_pool_t *pool,
583                                     const char *url_in,
584                                     apr_ldap_url_desc_t **ludpp,
585                                     apr_ldap_err_t **result_err)
586 {
587 
588     int rc = apr_ldap_url_parse_ext(pool, url_in, ludpp, result_err);
589     if( rc != APR_SUCCESS ) {
590         return rc;
591     }
592 
593     if ((*ludpp)->lud_scope == -1) {
594         (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
595     }
596 
597     if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
598         (*ludpp)->lud_host = NULL;
599     }
600 
601     return rc;
602 
603 }
604 
605 
apr_ldap_pvt_hex_unescape(char * s)606 static void apr_ldap_pvt_hex_unescape(char *s)
607 {
608     /*
609      * Remove URL hex escapes from s... done in place.  The basic concept for
610      * this routine is borrowed from the WWW library HTUnEscape() routine.
611      */
612     char    *p;
613 
614     for ( p = s; *s != '\0'; ++s ) {
615         if ( *s == '%' ) {
616             if ( *++s == '\0' ) {
617                 break;
618             }
619             *p = apr_ldap_pvt_unhex( *s ) << 4;
620             if ( *++s == '\0' ) {
621                 break;
622             }
623             *p++ += apr_ldap_pvt_unhex( *s );
624         } else {
625             *p++ = *s;
626         }
627     }
628 
629     *p = '\0';
630 }
631 
632 
apr_ldap_pvt_unhex(int c)633 static int apr_ldap_pvt_unhex(int c)
634 {
635     return( c >= '0' && c <= '9' ? c - '0'
636         : c >= 'A' && c <= 'F' ? c - 'A' + 10
637         : c - 'a' + 10 );
638 }
639 
640 
641 /**
642  * Convert a string to a character array
643  */
apr_ldap_str2charray(apr_pool_t * pool,const char * str_in,const char * brkstr)644 static char **apr_ldap_str2charray(apr_pool_t *pool,
645                                    const char *str_in,
646                                    const char *brkstr)
647 {
648     char    **res;
649     char    *str, *s;
650     char    *lasts;
651     int i;
652 
653     /* protect the input string from strtok */
654     str = (char *)apr_pstrdup(pool, str_in);
655     if( str == NULL ) {
656         return NULL;
657     }
658 
659     i = 1;
660     for ( s = str; *s; s++ ) {
661         /* Warning: this strchr was previously ldap_utf8_strchr(), check
662          * whether this particular code has any charset issues.
663          */
664         if ( strchr( brkstr, *s ) != NULL ) {
665             i++;
666         }
667     }
668 
669     res = (char **) apr_pcalloc(pool, (i + 1) * sizeof(char *));
670     if( res == NULL ) {
671         return NULL;
672     }
673 
674     i = 0;
675 
676     for ( s = (char *)apr_strtok( str, brkstr, &lasts );
677           s != NULL;
678           s = (char *)apr_strtok( NULL, brkstr, &lasts ) ) {
679 
680         res[i] = (char *)apr_pstrdup(pool, s);
681         if(res[i] == NULL) {
682             return NULL;
683         }
684 
685         i++;
686     }
687 
688     res[i] = NULL;
689 
690     return( res );
691 
692 }
693 
694 #endif /* APR_HAS_LDAP */
695