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 */
100static const char* skip_url_prefix(const char *url,
101                                   int *enclosedp,
102                                   const char **scheme);
103
104static void apr_ldap_pvt_hex_unescape(char *s);
105
106static int apr_ldap_pvt_unhex(int c);
107
108static 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 */
117APU_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 */
137APU_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 */
157APU_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
174static 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
230static 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 */
259APU_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 */
582APU_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
606static 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
633static 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 */
644static 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