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