]> granicus.if.org Git - apache/blob - modules/ldap/ap_ldap_url.c
Incorporate the ap_ldap incomplete API, as there is no interest or effort
[apache] / modules / ldap / ap_ldap_url.c
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 /*  ap_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 "ap_config.h"
78 #include "ap_ldap.h"
79
80 #if AP_HAS_LDAP
81
82 #if APR_HAVE_STDLIB_H
83 #include <stdlib.h>
84 #endif
85
86 #ifndef LDAPS_PORT
87 #define LDAPS_PORT              636  /* ldaps:/// default LDAP over TLS port */
88 #endif
89
90 #define AP_LDAP_URL_PREFIX         "ldap://"
91 #define AP_LDAP_URL_PREFIX_LEN     (sizeof(AP_LDAP_URL_PREFIX)-1)
92 #define AP_LDAPS_URL_PREFIX        "ldaps://"
93 #define AP_LDAPS_URL_PREFIX_LEN    (sizeof(AP_LDAPS_URL_PREFIX)-1)
94 #define AP_LDAPI_URL_PREFIX        "ldapi://"
95 #define AP_LDAPI_URL_PREFIX_LEN    (sizeof(AP_LDAPI_URL_PREFIX)-1)
96 #define AP_LDAP_URL_URLCOLON       "URL:"
97 #define AP_LDAP_URL_URLCOLON_LEN   (sizeof(AP_LDAP_URL_URLCOLON)-1)
98
99
100 /* local functions */
101 static const char* skip_url_prefix(const char *url,
102                                    int *enclosedp,
103                                    const char **scheme);
104
105 static void ap_ldap_pvt_hex_unescape(char *s);
106
107 static int ap_ldap_pvt_unhex(int c);
108
109 static char **ap_ldap_str2charray(apr_pool_t *pool,
110                                   const char *str,
111                                   const char *brkstr);
112
113
114 /**
115  * Is this URL an ldap url?
116  *
117  */
118 MODLDAP_DECLARE(int) ap_ldap_is_ldap_url(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 1;
132 }
133
134 /**
135  * Is this URL a secure ldap url?
136  *
137  */
138 MODLDAP_DECLARE(int) ap_ldap_is_ldaps_url(const char *url)
139 {
140     int enclosed;
141     const char * scheme;
142
143     if( url == NULL ) {
144         return 0;
145     }
146
147     if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
148         return 0;
149     }
150
151     return strcmp(scheme, "ldaps") == 0;
152 }
153
154 /**
155  * Is this URL an ldap socket url?
156  *
157  */
158 MODLDAP_DECLARE(int) ap_ldap_is_ldapi_url(const char *url)
159 {
160     int enclosed;
161     const char * scheme;
162
163     if( url == NULL ) {
164         return 0;
165     }
166
167     if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
168         return 0;
169     }
170
171     return strcmp(scheme, "ldapi") == 0;
172 }
173
174
175 static const char *skip_url_prefix(const char *url, int *enclosedp,
176                                    const char **scheme)
177 {
178     /*
179      * return non-zero if this looks like a LDAP URL; zero if not
180      * if non-zero returned, *urlp will be moved past "ldap://" part of URL
181      */
182     const char *p;
183
184     if ( url == NULL ) {
185         return( NULL );
186     }
187
188     p = url;
189
190     /* skip leading '<' (if any) */
191     if ( *p == '<' ) {
192         *enclosedp = 1;
193         ++p;
194     } else {
195         *enclosedp = 0;
196     }
197
198     /* skip leading "URL:" (if any) */
199     if ( strncasecmp( p, AP_LDAP_URL_URLCOLON, AP_LDAP_URL_URLCOLON_LEN ) == 0 ) {
200         p += AP_LDAP_URL_URLCOLON_LEN;
201     }
202
203     /* check for "ldap://" prefix */
204     if ( strncasecmp( p, AP_LDAP_URL_PREFIX, AP_LDAP_URL_PREFIX_LEN ) == 0 ) {
205         /* skip over "ldap://" prefix and return success */
206         p += AP_LDAP_URL_PREFIX_LEN;
207         *scheme = "ldap";
208         return( p );
209     }
210
211     /* check for "ldaps://" prefix */
212     if ( strncasecmp( p, AP_LDAPS_URL_PREFIX, AP_LDAPS_URL_PREFIX_LEN ) == 0 ) {
213         /* skip over "ldaps://" prefix and return success */
214         p += AP_LDAPS_URL_PREFIX_LEN;
215         *scheme = "ldaps";
216         return( p );
217     }
218
219     /* check for "ldapi://" prefix */
220     if ( strncasecmp( p, AP_LDAPI_URL_PREFIX, AP_LDAPI_URL_PREFIX_LEN ) == 0 ) {
221         /* skip over "ldapi://" prefix and return success */
222         p += AP_LDAPI_URL_PREFIX_LEN;
223         *scheme = "ldapi";
224         return( p );
225     }
226
227     return( NULL );
228 }
229
230
231 static int str2scope(const char *p)
232 {
233     if ( strcasecmp( p, "one" ) == 0 ) {
234         return LDAP_SCOPE_ONELEVEL;
235
236     } else if ( strcasecmp( p, "onetree" ) == 0 ) {
237         return LDAP_SCOPE_ONELEVEL;
238
239     } else if ( strcasecmp( p, "base" ) == 0 ) {
240         return LDAP_SCOPE_BASE;
241
242     } else if ( strcasecmp( p, "sub" ) == 0 ) {
243         return LDAP_SCOPE_SUBTREE;
244
245     } else if ( strcasecmp( p, "subtree" ) == 0 ) {
246         return LDAP_SCOPE_SUBTREE;
247     }
248
249     return( -1 );
250 }
251
252
253 /**
254  * Parse the URL provided into an ap_ldap_url_desc_t object.
255  *
256  * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
257  * The LDAP result code and reason string is returned in the
258  * ap_ldap_err_t structure.
259  */
260 MODLDAP_DECLARE(int) ap_ldap_url_parse_ext(apr_pool_t *pool,
261                                            const char *url_in,
262                                            ap_ldap_url_desc_t **ludpp,
263                                            ap_ldap_err_t **result_err)
264 {
265     ap_ldap_url_desc_t *ludp;
266     char        *p, *q, *r;
267     int         i, enclosed;
268     const char  *scheme = NULL;
269     const char  *url_tmp;
270     char        *url;
271
272     ap_ldap_err_t *result = (ap_ldap_err_t *)apr_pcalloc(pool, sizeof(ap_ldap_err_t));
273     *result_err = result;
274
275     /* sanity check our parameters */
276     if( url_in == NULL || ludpp == NULL ) {
277         result->reason = "Either the LDAP URL, or the URL structure was NULL. Oops.";
278         result->rc = AP_LDAP_URL_ERR_PARAM;
279         return APR_EGENERAL;
280     }
281
282     *ludpp = NULL;  /* pessimistic */
283
284     url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
285     if ( url_tmp == NULL ) {
286         result->reason = "The scheme was not recognised as a valid LDAP URL scheme.";
287         result->rc = AP_LDAP_URL_ERR_BADSCHEME;
288         return APR_EGENERAL;
289     }
290
291     /* make working copy of the remainder of the URL */
292     url = (char *)apr_pstrdup(pool, url_tmp);
293     if ( url == NULL ) {
294         result->reason = "Out of memory parsing LDAP URL.";
295         result->rc = AP_LDAP_URL_ERR_MEM;
296         return APR_EGENERAL;
297     }
298
299     if ( enclosed ) {
300         p = &url[strlen(url)-1];
301
302         if( *p != '>' ) {
303             result->reason = "Bad enclosure error while parsing LDAP URL.";
304             result->rc = AP_LDAP_URL_ERR_BADENCLOSURE;
305             return APR_EGENERAL;
306         }
307
308         *p = '\0';
309     }
310
311     /* allocate return struct */
312     ludp = (ap_ldap_url_desc_t *)apr_pcalloc(pool, sizeof(ap_ldap_url_desc_t));
313     if ( ludp == NULL ) {
314         result->reason = "Out of memory parsing LDAP URL.";
315         result->rc = AP_LDAP_URL_ERR_MEM;
316         return APR_EGENERAL;
317     }
318
319     ludp->lud_next = NULL;
320     ludp->lud_host = NULL;
321     ludp->lud_port = LDAP_PORT;
322     ludp->lud_dn = NULL;
323     ludp->lud_attrs = NULL;
324     ludp->lud_filter = NULL;
325     ludp->lud_scope = -1;
326     ludp->lud_filter = NULL;
327     ludp->lud_exts = NULL;
328
329     ludp->lud_scheme = (char *)apr_pstrdup(pool, scheme);
330     if ( ludp->lud_scheme == NULL ) {
331         result->reason = "Out of memory parsing LDAP URL.";
332         result->rc = AP_LDAP_URL_ERR_MEM;
333         return APR_EGENERAL;
334     }
335
336     if( strcasecmp( ludp->lud_scheme, "ldaps" ) == 0 ) {
337         ludp->lud_port = LDAPS_PORT;
338     }
339
340     /* scan forward for '/' that marks end of hostport and begin. of dn */
341     p = strchr( url, '/' );
342
343     if( p != NULL ) {
344         /* terminate hostport; point to start of dn */
345         *p++ = '\0';
346     }
347
348     /* IPv6 syntax with [ip address]:port */
349     if ( *url == '[' ) {
350         r = strchr( url, ']' );
351         if ( r == NULL ) {
352             result->reason = "Bad LDAP URL while parsing IPV6 syntax.";
353             result->rc = AP_LDAP_URL_ERR_BADURL;
354             return APR_EGENERAL;
355         }
356         *r++ = '\0';
357         q = strrchr( r, ':' );
358     } else {
359         q = strrchr( url, ':' );
360     }
361
362     if ( q != NULL ) {
363         ap_ldap_pvt_hex_unescape( ++q );
364
365         if( *q == '\0' ) {
366             result->reason = "Bad LDAP URL while parsing.";
367             result->rc = AP_LDAP_URL_ERR_BADURL;
368             return APR_EGENERAL;
369         }
370
371         ludp->lud_port = atoi( q );
372     }
373
374     ap_ldap_pvt_hex_unescape( url );
375
376     /* If [ip address]:port syntax, url is [ip and we skip the [ */
377     ludp->lud_host = (char *)apr_pstrdup(pool, url + ( *url == '[' ));
378     if( ludp->lud_host == NULL ) {
379         result->reason = "Out of memory parsing LDAP URL.";
380         result->rc = AP_LDAP_URL_ERR_MEM;
381         return APR_EGENERAL;
382     }
383
384     /*
385      * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
386      *
387      * On early Novell releases, search references/referrals were returned
388      * in this format, i.e., the dn was kind of in the scope position,
389      * but the required slash is missing. The whole thing is illegal syntax,
390      * but we need to account for it. Fortunately it can't be confused with
391      * anything real.
392      */
393     if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
394         q++;
395         /* ? immediately followed by question */
396         if( *q == '?') {
397             q++;
398             if( *q != '\0' ) {
399                 /* parse dn part */
400                 ap_ldap_pvt_hex_unescape( q );
401                 ludp->lud_dn = (char *)apr_pstrdup(pool, q);
402             } else {
403                 ludp->lud_dn = (char *)apr_pstrdup(pool, "");
404             }
405
406             if( ludp->lud_dn == NULL ) {
407                 result->reason = "Out of memory parsing LDAP URL.";
408                 result->rc = AP_LDAP_URL_ERR_MEM;
409                 return APR_EGENERAL;
410             }
411         }
412     }
413
414     if( p == NULL ) {
415         *ludpp = ludp;
416         return APR_SUCCESS;
417     }
418
419     /* scan forward for '?' that may marks end of dn */
420     q = strchr( p, '?' );
421
422     if( q != NULL ) {
423         /* terminate dn part */
424         *q++ = '\0';
425     }
426
427     if( *p != '\0' ) {
428         /* parse dn part */
429         ap_ldap_pvt_hex_unescape( p );
430         ludp->lud_dn = (char *)apr_pstrdup(pool, p);
431     } else {
432         ludp->lud_dn = (char *)apr_pstrdup(pool, "");
433     }
434
435     if( ludp->lud_dn == NULL ) {
436         result->reason = "Out of memory parsing LDAP URL.";
437         result->rc = AP_LDAP_URL_ERR_MEM;
438         return APR_EGENERAL;
439     }
440
441     if( q == NULL ) {
442         /* no more */
443         *ludpp = ludp;
444         return APR_SUCCESS;
445     }
446
447     /* scan forward for '?' that may marks end of attributes */
448     p = q;
449     q = strchr( p, '?' );
450
451     if( q != NULL ) {
452         /* terminate attributes part */
453         *q++ = '\0';
454     }
455
456     if( *p != '\0' ) {
457         /* parse attributes */
458         ap_ldap_pvt_hex_unescape( p );
459         ludp->lud_attrs = ap_ldap_str2charray(pool, p, ",");
460
461         if( ludp->lud_attrs == NULL ) {
462             result->reason = "Bad attributes encountered while parsing LDAP URL.";
463             result->rc = AP_LDAP_URL_ERR_BADATTRS;
464             return APR_EGENERAL;
465         }
466     }
467
468     if ( q == NULL ) {
469         /* no more */
470         *ludpp = ludp;
471         return APR_SUCCESS;
472     }
473
474     /* scan forward for '?' that may marks end of scope */
475     p = q;
476     q = strchr( p, '?' );
477
478     if( q != NULL ) {
479         /* terminate the scope part */
480         *q++ = '\0';
481     }
482
483     if( *p != '\0' ) {
484         /* parse the scope */
485         ap_ldap_pvt_hex_unescape( p );
486         ludp->lud_scope = str2scope( p );
487
488         if( ludp->lud_scope == -1 ) {
489             result->reason = "Bad scope encountered while parsing LDAP URL.";
490             result->rc = AP_LDAP_URL_ERR_BADSCOPE;
491             return APR_EGENERAL;
492         }
493     }
494
495     if ( q == NULL ) {
496         /* no more */
497         *ludpp = ludp;
498         return APR_SUCCESS;
499     }
500
501     /* scan forward for '?' that may marks end of filter */
502     p = q;
503     q = strchr( p, '?' );
504
505     if( q != NULL ) {
506         /* terminate the filter part */
507         *q++ = '\0';
508     }
509
510     if( *p != '\0' ) {
511         /* parse the filter */
512         ap_ldap_pvt_hex_unescape( p );
513
514         if( ! *p ) {
515             /* missing filter */
516             result->reason = "Bad filter encountered while parsing LDAP URL.";
517             result->rc = AP_LDAP_URL_ERR_BADFILTER;
518             return APR_EGENERAL;
519         }
520
521         ludp->lud_filter = (char *)apr_pstrdup(pool, p);
522         if( ludp->lud_filter == NULL ) {
523             result->reason = "Out of memory parsing LDAP URL.";
524             result->rc = AP_LDAP_URL_ERR_MEM;
525             return APR_EGENERAL;
526         }
527     }
528
529     if ( q == NULL ) {
530         /* no more */
531         *ludpp = ludp;
532         return APR_SUCCESS;
533     }
534
535     /* scan forward for '?' that may marks end of extensions */
536     p = q;
537     q = strchr( p, '?' );
538
539     if( q != NULL ) {
540         /* extra '?' */
541         result->reason = "Bad URL encountered while parsing LDAP URL.";
542         result->rc = AP_LDAP_URL_ERR_BADURL;
543         return APR_EGENERAL;
544     }
545
546     /* parse the extensions */
547     ludp->lud_exts = ap_ldap_str2charray(pool, p, ",");
548     if( ludp->lud_exts == NULL ) {
549         result->reason = "Bad extensions encountered while parsing LDAP URL.";
550         result->rc = AP_LDAP_URL_ERR_BADEXTS;
551         return APR_EGENERAL;
552     }
553
554     for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
555         ap_ldap_pvt_hex_unescape( ludp->lud_exts[i] );
556
557         if( *ludp->lud_exts[i] == '!' ) {
558             /* count the number of critical extensions */
559             ludp->lud_crit_exts++;
560         }
561     }
562
563     if( i == 0 ) {
564         /* must have 1 or more */
565         result->reason = "Bad extensions encountered while parsing LDAP URL.";
566         result->rc = AP_LDAP_URL_ERR_BADEXTS;
567         return APR_EGENERAL;
568     }
569
570     /* no more */
571     *ludpp = ludp;
572     return APR_SUCCESS;
573 }
574
575
576 /**
577  * Parse the URL provided into an ap_ldap_url_desc_t object.
578  *
579  * APR_SUCCESS is returned on success, APR_EGENERAL on failure.
580  * The LDAP result code and reason string is returned in the
581  * ap_ldap_err_t structure.
582  */
583 MODLDAP_DECLARE(int) ap_ldap_url_parse(apr_pool_t *pool,
584                                        const char *url_in,
585                                        ap_ldap_url_desc_t **ludpp,
586                                        ap_ldap_err_t **result_err)
587 {
588
589     int rc = ap_ldap_url_parse_ext(pool, url_in, ludpp, result_err);
590     if( rc != APR_SUCCESS ) {
591         return rc;
592     }
593
594     if ((*ludpp)->lud_scope == -1) {
595         (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
596     }
597
598     if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
599         (*ludpp)->lud_host = NULL;
600     }
601
602     return rc;
603
604 }
605
606
607 static void ap_ldap_pvt_hex_unescape(char *s)
608 {
609     /*
610      * Remove URL hex escapes from s... done in place.  The basic concept for
611      * this routine is borrowed from the WWW library HTUnEscape() routine.
612      */
613     char    *p;
614
615     for ( p = s; *s != '\0'; ++s ) {
616         if ( *s == '%' ) {
617             if ( *++s == '\0' ) {
618                 break;
619             }
620             *p = ap_ldap_pvt_unhex( *s ) << 4;
621             if ( *++s == '\0' ) {
622                 break;
623             }
624             *p++ += ap_ldap_pvt_unhex( *s );
625         } else {
626             *p++ = *s;
627         }
628     }
629
630     *p = '\0';
631 }
632
633
634 static int ap_ldap_pvt_unhex(int c)
635 {
636     return( c >= '0' && c <= '9' ? c - '0'
637         : c >= 'A' && c <= 'F' ? c - 'A' + 10
638         : c - 'a' + 10 );
639 }
640
641
642 /**
643  * Convert a string to a character array
644  */
645 static char **ap_ldap_str2charray(apr_pool_t *pool,
646                                   const char *str_in,
647                                   const char *brkstr)
648 {
649     char    **res;
650     char    *str, *s;
651     char    *lasts;
652     int i;
653
654     /* protect the input string from strtok */
655     str = (char *)apr_pstrdup(pool, str_in);
656     if( str == NULL ) {
657         return NULL;
658     }
659
660     i = 1;
661     for ( s = str; *s; s++ ) {
662         /* Warning: this strchr was previously ldap_utf8_strchr(), check
663          * whether this particular code has any charset issues.
664          */
665         if ( strchr( brkstr, *s ) != NULL ) {
666             i++;
667         }
668     }
669
670     res = (char **) apr_pcalloc(pool, (i + 1) * sizeof(char *));
671     if( res == NULL ) {
672         return NULL;
673     }
674
675     i = 0;
676
677     for ( s = (char *)apr_strtok( str, brkstr, &lasts );
678           s != NULL;
679           s = (char *)apr_strtok( NULL, brkstr, &lasts ) ) {
680
681         res[i] = (char *)apr_pstrdup(pool, s);
682         if(res[i] == NULL) {
683             return NULL;
684         }
685
686         i++;
687     }
688
689     res[i] = NULL;
690
691     return( res );
692
693 }
694
695 #endif /* AP_HAS_LDAP */