1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (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  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is Mozilla Communicator client code, released
15  * March 31, 1998.
16  *
17  * The Initial Developer of the Original Code is
18  * Netscape Communications Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 1998-1999
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either of the GNU General Public License Version 2 or later (the "GPL"),
26  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37 /*
38  *  Copyright (c) 1996 Regents of the University of Michigan.
39  *  All rights reserved.
40  *
41  */
42 /*  LIBLDAP url.c -- LDAP URL related routines
43  *
44  *  LDAP URLs look like this:
45  *    l d a p : / / [ hostport ] [ / dn [ ? [ attributes ] [ ? [ scope ]
46  *                  [ ? [ filter ] [ ? extensions ] ] ] ] ]
47  *
48  *  where:
49  *   hostport is a host or a host:port list that can be space-separated.
50  *   attributes is a comma separated list
51  *   scope is one of these three strings:  base one sub (default=base)
52  *   filter is a string-represented filter as in RFC 2254
53  *   extensions is a comma-separated list of name=value pairs.
54  *
55  *  e.g.,  ldap://ldap.itd.umich.edu/c=US?o,description?one?o=umich
56  *
57  *  To accommodate IPv6 addresses, the host portion of a host that appears
58  *  in hostport can be enclosed in square brackets, e.g
59  *
60  *  e.g.,  ldap://[fe80::a00:20ff:fee5:c0b4]:3389/dc=siroe,dc=com
61  *
62  *  We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
63  */
64 
65 #if 0
66 #  ifndef lint
67 static char copyright[] = "@(#) Copyright (c) 1996 Regents of the University of Michigan.\nAll rights reserved.\n";
68 #  endif
69 #endif
70 
71 #include "ldap-int.h"
72 
73 static int skip_url_prefix(const char** urlp, int* enclosedp, int* securep);
74 
ldap_is_ldap_url(const char * url)75 int LDAP_CALL ldap_is_ldap_url(const char* url) {
76   int enclosed, secure;
77 
78   return (url != NULL && skip_url_prefix(&url, &enclosed, &secure));
79 }
80 
skip_url_prefix(const char ** urlp,int * enclosedp,int * securep)81 static int skip_url_prefix(const char** urlp, int* enclosedp, int* securep) {
82   /*
83    * return non-zero if this looks like a LDAP URL; zero if not
84    * if non-zero returned, *urlp will be moved past "ldap://" part of URL
85    * The data that *urlp points to is not changed by this function.
86    */
87   if (*urlp == NULL) {
88     return (0);
89   }
90 
91   /* skip leading '<' (if any) */
92   if (**urlp == '<') {
93     *enclosedp = 1;
94     ++*urlp;
95   } else {
96     *enclosedp = 0;
97   }
98 
99   /* skip leading "URL:" (if any) */
100   if (strlen(*urlp) >= LDAP_URL_URLCOLON_LEN &&
101       strncasecmp(*urlp, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN) == 0) {
102     *urlp += LDAP_URL_URLCOLON_LEN;
103   }
104 
105   /* check for an "ldap://" prefix */
106   if (strlen(*urlp) >= LDAP_URL_PREFIX_LEN &&
107       strncasecmp(*urlp, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN) == 0) {
108     /* skip over URL prefix and return success */
109     *urlp += LDAP_URL_PREFIX_LEN;
110     *securep = 0;
111     return (1);
112   }
113 
114   /* check for an "ldaps://" prefix */
115   if (strlen(*urlp) >= LDAPS_URL_PREFIX_LEN &&
116       strncasecmp(*urlp, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN) == 0) {
117     /* skip over URL prefix and return success */
118     *urlp += LDAPS_URL_PREFIX_LEN;
119     *securep = 1;
120     return (1);
121   }
122 
123   return (0); /* not an LDAP URL */
124 }
125 
ldap_url_parse_no_defaults(const char * url,LDAPURLDesc ** ludpp,int dn_required)126 int LDAP_CALL ldap_url_parse_no_defaults(const char* url, LDAPURLDesc** ludpp,
127                                          int dn_required) {
128   return (nsldapi_url_parse(url, ludpp, dn_required));
129 }
130 
ldap_url_parse(const char * url,LDAPURLDesc ** ludpp)131 int LDAP_CALL ldap_url_parse(const char* url, LDAPURLDesc** ludpp) {
132   /*
133    *  Pick apart the pieces of an LDAP URL.
134    */
135   int rc;
136 
137   if ((rc = nsldapi_url_parse(url, ludpp, 1)) == 0) {
138     if ((*ludpp)->lud_scope == -1) {
139       (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
140     }
141     if ((*ludpp)->lud_filter == NULL) {
142       (*ludpp)->lud_filter = "(objectclass=*)";
143     }
144     if (*((*ludpp)->lud_dn) == '\0') {
145       (*ludpp)->lud_dn = NULL;
146     }
147   } else if (rc == LDAP_URL_UNRECOGNIZED_CRITICAL_EXTENSION) {
148     rc = LDAP_URL_ERR_PARAM; /* mapped for backwards compatibility */
149   }
150 
151   return (rc);
152 }
153 
154 /*
155  * like ldap_url_parse() with a few exceptions:
156  *   1) if dn_required is zero, a missing DN does not generate an error
157  *      (we just leave the lud_dn field NULL)
158  *   2) no defaults are set for lud_scope and lud_filter (they are set to -1
159  *      and NULL respectively if no SCOPE or FILTER are present in the URL).
160  *   3) when there is a zero-length DN in a URL we do not set lud_dn to NULL.
161  *
162  * note that LDAPv3 URL extensions are ignored unless they are marked
163  * critical, in which case an LDAP_URL_UNRECOGNIZED_CRITICAL_EXTENSION error
164  * is returned.
165  */
nsldapi_url_parse(const char * url,LDAPURLDesc ** ludpp,int dn_required)166 int nsldapi_url_parse(const char* url, LDAPURLDesc** ludpp, int dn_required) {
167   LDAPURLDesc* ludp;
168   char *urlcopy, *attrs, *scope, *extensions = NULL, *p, *q;
169   int enclosed, secure, i, nattrs, at_start;
170 
171   LDAPDebug(LDAP_DEBUG_TRACE, "nsldapi_url_parse(%s)\n", url);
172 
173   if (url == NULL || ludpp == NULL) {
174     return (LDAP_URL_ERR_PARAM);
175   }
176 
177   *ludpp = NULL; /* pessimistic */
178 
179   if (!skip_url_prefix(&url, &enclosed, &secure)) {
180     return (LDAP_URL_ERR_NOTLDAP);
181   }
182 
183   /* allocate return struct */
184   if ((ludp = (LDAPURLDesc*)NSLDAPI_CALLOC(1, sizeof(LDAPURLDesc))) ==
185       NULLLDAPURLDESC) {
186     return (LDAP_URL_ERR_MEM);
187   }
188 
189   if (secure) {
190     ludp->lud_options |= LDAP_URL_OPT_SECURE;
191   }
192 
193   /* make working copy of the remainder of the URL */
194   if ((urlcopy = nsldapi_strdup(url)) == NULL) {
195     ldap_free_urldesc(ludp);
196     return (LDAP_URL_ERR_MEM);
197   }
198 
199   if (enclosed && *((p = urlcopy + strlen(urlcopy) - 1)) == '>') {
200     *p = '\0';
201   }
202 
203   /* initialize scope and filter */
204   ludp->lud_scope = -1;
205   ludp->lud_filter = NULL;
206 
207   /* lud_string is the only malloc'd string space we use */
208   ludp->lud_string = urlcopy;
209 
210   /* scan forward for '/' that marks end of hostport and begin. of dn */
211   if ((ludp->lud_dn = strchr(urlcopy, '/')) == NULL) {
212     if (dn_required) {
213       ldap_free_urldesc(ludp);
214       return (LDAP_URL_ERR_NODN);
215     }
216   } else {
217     /* terminate hostport; point to start of dn */
218     *ludp->lud_dn++ = '\0';
219   }
220 
221   if (*urlcopy == '\0') {
222     ludp->lud_host = NULL;
223   } else {
224     ludp->lud_host = urlcopy;
225     nsldapi_hex_unescape(ludp->lud_host);
226 
227     /*
228      * Locate and strip off optional port number (:#) in host
229      * portion of URL.
230      *
231      * If more than one space-separated host is listed, we only
232      * look for a port number within the right-most one since
233      * ldap_init() will handle host parameters that look like
234      * host:port anyway.
235      */
236     if ((p = strrchr(ludp->lud_host, ' ')) == NULL) {
237       p = ludp->lud_host;
238     } else {
239       ++p;
240     }
241     if (*p == '[' && (q = strchr(p, ']')) != NULL) {
242       /* square brackets present -- skip past them */
243       p = q++;
244     }
245     if ((p = strchr(p, ':')) != NULL) {
246       *p++ = '\0';
247       ludp->lud_port = atoi(p);
248       if (*ludp->lud_host == '\0') {
249         ludp->lud_host = NULL; /* no hostname */
250       }
251     }
252   }
253 
254   /* scan for '?' that marks end of dn and beginning of attributes */
255   attrs = NULL;
256   if (ludp->lud_dn != NULL && (attrs = strchr(ludp->lud_dn, '?')) != NULL) {
257     /* terminate dn; point to start of attrs. */
258     *attrs++ = '\0';
259 
260     /* scan for '?' that marks end of attrs and begin. of scope */
261     if ((p = strchr(attrs, '?')) != NULL) {
262       /*
263        * terminate attrs; point to start of scope and scan for
264        * '?' that marks end of scope and begin. of filter
265        */
266       *p++ = '\0';
267       scope = p;
268 
269       if ((p = strchr(scope, '?')) != NULL) {
270         /* terminate scope; point to start of filter */
271         *p++ = '\0';
272         if (*p != '\0') {
273           ludp->lud_filter = p;
274           /*
275            * scan for the '?' that marks the end
276            * of the filter and the start of any
277            * extensions
278            */
279           if ((p = strchr(ludp->lud_filter, '?')) != NULL) {
280             *p++ = '\0'; /* term. filter */
281             extensions = p;
282           }
283           if (*ludp->lud_filter == '\0') {
284             ludp->lud_filter = NULL;
285           } else {
286             nsldapi_hex_unescape(ludp->lud_filter);
287           }
288         }
289       }
290 
291       if (strcasecmp(scope, "one") == 0) {
292         ludp->lud_scope = LDAP_SCOPE_ONELEVEL;
293       } else if (strcasecmp(scope, "base") == 0) {
294         ludp->lud_scope = LDAP_SCOPE_BASE;
295       } else if (strcasecmp(scope, "sub") == 0) {
296         ludp->lud_scope = LDAP_SCOPE_SUBTREE;
297       } else if (*scope != '\0') {
298         ldap_free_urldesc(ludp);
299         return (LDAP_URL_ERR_BADSCOPE);
300       }
301     }
302   }
303 
304   if (ludp->lud_dn != NULL) {
305     nsldapi_hex_unescape(ludp->lud_dn);
306   }
307 
308   /*
309    * if attrs list was included, turn it into a null-terminated array
310    */
311   if (attrs != NULL && *attrs != '\0') {
312     nsldapi_hex_unescape(attrs);
313     for (nattrs = 1, p = attrs; *p != '\0'; ++p) {
314       if (*p == ',') {
315         ++nattrs;
316       }
317     }
318 
319     if ((ludp->lud_attrs = (char**)NSLDAPI_CALLOC(nattrs + 1, sizeof(char*))) ==
320         NULL) {
321       ldap_free_urldesc(ludp);
322       return (LDAP_URL_ERR_MEM);
323     }
324 
325     for (i = 0, p = attrs; i < nattrs; ++i) {
326       ludp->lud_attrs[i] = p;
327       if ((p = strchr(p, ',')) != NULL) {
328         *p++ = '\0';
329       }
330       nsldapi_hex_unescape(ludp->lud_attrs[i]);
331     }
332   }
333 
334   /* if extensions list was included, check for critical ones */
335   if (extensions != NULL && *extensions != '\0') {
336     /* Note: at present, we do not recognize ANY extensions */
337     at_start = 1;
338     for (p = extensions; *p != '\0'; ++p) {
339       if (at_start) {
340         if (*p == '!') { /* critical extension */
341           ldap_free_urldesc(ludp);
342           return (LDAP_URL_UNRECOGNIZED_CRITICAL_EXTENSION);
343         }
344         at_start = 0;
345       } else if (*p == ',') {
346         at_start = 1;
347       }
348     }
349   }
350 
351   *ludpp = ludp;
352 
353   return (0);
354 }
355 
ldap_free_urldesc(LDAPURLDesc * ludp)356 void LDAP_CALL ldap_free_urldesc(LDAPURLDesc* ludp) {
357   if (ludp != NULLLDAPURLDESC) {
358     if (ludp->lud_string != NULL) {
359       NSLDAPI_FREE(ludp->lud_string);
360     }
361     if (ludp->lud_attrs != NULL) {
362       NSLDAPI_FREE(ludp->lud_attrs);
363     }
364     NSLDAPI_FREE(ludp);
365   }
366 }
367 
ldap_url_search(LDAP * ld,const char * url,int attrsonly)368 int LDAP_CALL ldap_url_search(LDAP* ld, const char* url, int attrsonly) {
369   int err, msgid;
370   LDAPURLDesc* ludp;
371   BerElement* ber;
372   LDAPServer* srv;
373   char* host;
374 
375   if (!NSLDAPI_VALID_LDAP_POINTER(ld)) {
376     return (-1); /* punt */
377   }
378 
379   if (ldap_url_parse(url, &ludp) != 0) {
380     LDAP_SET_LDERRNO(ld, LDAP_PARAM_ERROR, NULL, NULL);
381     return (-1);
382   }
383 
384   LDAP_MUTEX_LOCK(ld, LDAP_MSGID_LOCK);
385   msgid = ++ld->ld_msgid;
386   LDAP_MUTEX_UNLOCK(ld, LDAP_MSGID_LOCK);
387 
388   if (nsldapi_build_search_req(
389           ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter, ludp->lud_attrs,
390           attrsonly, NULL, NULL, -1, -1, msgid, &ber) != LDAP_SUCCESS) {
391     return (-1);
392   }
393 
394   err = 0;
395 
396   if (ludp->lud_host == NULL) {
397     host = ld->ld_defhost;
398   } else {
399     host = ludp->lud_host;
400   }
401 
402   if ((srv = (LDAPServer*)NSLDAPI_CALLOC(1, sizeof(LDAPServer))) == NULL ||
403       (host != NULL && (srv->lsrv_host = nsldapi_strdup(host)) == NULL)) {
404     if (srv != NULL) {
405       NSLDAPI_FREE(srv);
406     }
407     LDAP_SET_LDERRNO(ld, LDAP_NO_MEMORY, NULL, NULL);
408     err = -1;
409   } else {
410     if (ludp->lud_port != 0) {
411       /* URL includes a port - use it */
412       srv->lsrv_port = ludp->lud_port;
413     } else if (ludp->lud_host == NULL) {
414       /* URL has no port or host - use port from ld */
415       srv->lsrv_port = ld->ld_defport;
416     } else if ((ludp->lud_options & LDAP_URL_OPT_SECURE) == 0) {
417       /* ldap URL has a host but no port - use std. port */
418       srv->lsrv_port = LDAP_PORT;
419     } else {
420       /* ldaps URL has a host but no port - use std. port */
421       srv->lsrv_port = LDAPS_PORT;
422     }
423   }
424 
425   if ((ludp->lud_options & LDAP_URL_OPT_SECURE) != 0) {
426     srv->lsrv_options |= LDAP_SRV_OPT_SECURE;
427   }
428 
429   if (err != 0) {
430     ber_free(ber, 1);
431   } else {
432     err = nsldapi_send_server_request(ld, ber, msgid, NULL, srv, NULL, NULL, 1);
433   }
434 
435   ldap_free_urldesc(ludp);
436   return (err);
437 }
438 
ldap_url_search_st(LDAP * ld,const char * url,int attrsonly,struct timeval * timeout,LDAPMessage ** res)439 int LDAP_CALL ldap_url_search_st(LDAP* ld, const char* url, int attrsonly,
440                                  struct timeval* timeout, LDAPMessage** res) {
441   int msgid;
442 
443   /*
444    * It is an error to pass in a zero'd timeval.
445    */
446   if (timeout != NULL && timeout->tv_sec == 0 && timeout->tv_usec == 0) {
447     if (ld != NULL) {
448       LDAP_SET_LDERRNO(ld, LDAP_PARAM_ERROR, NULL, NULL);
449     }
450     if (res != NULL) {
451       *res = NULL;
452     }
453     return (LDAP_PARAM_ERROR);
454   }
455 
456   if ((msgid = ldap_url_search(ld, url, attrsonly)) == -1) {
457     return (LDAP_GET_LDERRNO(ld, NULL, NULL));
458   }
459 
460   if (ldap_result(ld, msgid, 1, timeout, res) == -1) {
461     return (LDAP_GET_LDERRNO(ld, NULL, NULL));
462   }
463 
464   if (LDAP_GET_LDERRNO(ld, NULL, NULL) == LDAP_TIMEOUT) {
465     (void)ldap_abandon(ld, msgid);
466     LDAP_SET_LDERRNO(ld, LDAP_TIMEOUT, NULL, NULL);
467     return (LDAP_TIMEOUT);
468   }
469 
470   return (ldap_result2error(ld, *res, 0));
471 }
472 
ldap_url_search_s(LDAP * ld,const char * url,int attrsonly,LDAPMessage ** res)473 int LDAP_CALL ldap_url_search_s(LDAP* ld, const char* url, int attrsonly,
474                                 LDAPMessage** res) {
475   int msgid;
476 
477   if ((msgid = ldap_url_search(ld, url, attrsonly)) == -1) {
478     return (LDAP_GET_LDERRNO(ld, NULL, NULL));
479   }
480 
481   if (ldap_result(ld, msgid, 1, (struct timeval*)NULL, res) == -1) {
482     return (LDAP_GET_LDERRNO(ld, NULL, NULL));
483   }
484 
485   return (ldap_result2error(ld, *res, 0));
486 }
487