blob: 4d5db4a516605ec4e79d89c148d3512a2093edf7 [file] [log] [blame]
Lucas Eckels9bd90e62012-08-06 15:07:02 -07001/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2010, Howard Chu, <hyc@openldap.org>
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "setup.h"
24
25#if !defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)
26
27/*
28 * Notice that USE_OPENLDAP is only a source code selection switch. When
29 * libcurl is built with USE_OPENLDAP defined the libcurl source code that
30 * gets compiled is the code from openldap.c, otherwise the code that gets
31 * compiled is the code from ldap.c.
32 *
33 * When USE_OPENLDAP is defined a recent version of the OpenLDAP library
34 * might be required for compilation and runtime. In order to use ancient
35 * OpenLDAP library versions, USE_OPENLDAP shall not be defined.
36 */
37
38#include <ldap.h>
39
40#include "urldata.h"
41#include <curl/curl.h>
42#include "sendf.h"
43#include "sslgen.h"
44#include "transfer.h"
45#include "curl_ldap.h"
46#include "curl_memory.h"
47#include "curl_base64.h"
48
49#define _MPRINTF_REPLACE /* use our functions only */
50#include <curl/mprintf.h>
51
52#include "memdebug.h"
53
54#ifndef _LDAP_PVT_H
55extern int ldap_pvt_url_scheme2proto(const char *);
56extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, LDAP **ld);
57#endif
58
59static CURLcode ldap_setup(struct connectdata *conn);
60static CURLcode ldap_do(struct connectdata *conn, bool *done);
61static CURLcode ldap_done(struct connectdata *conn, CURLcode, bool);
62static CURLcode ldap_connect(struct connectdata *conn, bool *done);
63static CURLcode ldap_connecting(struct connectdata *conn, bool *done);
64static CURLcode ldap_disconnect(struct connectdata *conn);
65
66static Curl_recv ldap_recv;
67
68/*
69 * LDAP protocol handler.
70 */
71
72const struct Curl_handler Curl_handler_ldap = {
73 "LDAP", /* scheme */
74 ldap_setup, /* setup_connection */
75 ldap_do, /* do_it */
76 ldap_done, /* done */
77 ZERO_NULL, /* do_more */
78 ldap_connect, /* connect_it */
79 ldap_connecting, /* connecting */
80 ZERO_NULL, /* doing */
81 ZERO_NULL, /* proto_getsock */
82 ZERO_NULL, /* doing_getsock */
83 ZERO_NULL, /* perform_getsock */
84 ldap_disconnect, /* disconnect */
85 PORT_LDAP, /* defport */
86 PROT_LDAP /* protocol */
87};
88
89#ifdef USE_SSL
90/*
91 * LDAPS protocol handler.
92 */
93
94const struct Curl_handler Curl_handler_ldaps = {
95 "LDAPS", /* scheme */
96 ldap_setup, /* setup_connection */
97 ldap_do, /* do_it */
98 ldap_done, /* done */
99 ZERO_NULL, /* do_more */
100 ldap_connect, /* connect_it */
101 ldap_connecting, /* connecting */
102 ZERO_NULL, /* doing */
103 ZERO_NULL, /* proto_getsock */
104 ZERO_NULL, /* doing_getsock */
105 ZERO_NULL, /* perform_getsock */
106 ldap_disconnect, /* disconnect */
107 PORT_LDAPS, /* defport */
108 PROT_LDAP | PROT_SSL /* protocol */
109};
110#endif
111
112static const char *url_errs[] = {
113 "success",
114 "out of memory",
115 "bad parameter",
116 "unrecognized scheme",
117 "unbalanced delimiter",
118 "bad URL",
119 "bad host or port",
120 "bad or missing attributes",
121 "bad or missing scope",
122 "bad or missing filter",
123 "bad or missing extensions"
124};
125
126typedef struct ldapconninfo {
127 LDAP *ld;
128 Curl_recv *recv; /* for stacking SSL handler */
129 Curl_send *send;
130 int proto;
131 int msgid;
132 bool ssldone;
133 bool sslinst;
134 bool didbind;
135} ldapconninfo;
136
137typedef struct ldapreqinfo {
138 int msgid;
139 int nument;
140} ldapreqinfo;
141
142static CURLcode ldap_setup(struct connectdata *conn)
143{
144 ldapconninfo *li;
145 LDAPURLDesc *lud;
146 struct SessionHandle *data=conn->data;
147 int rc, proto;
148 CURLcode status;
149
150 rc = ldap_url_parse(data->change.url, &lud);
151 if (rc != LDAP_URL_SUCCESS) {
152 const char *msg = "url parsing problem";
153 status = CURLE_URL_MALFORMAT;
154 if (rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) {
155 if (rc == LDAP_URL_ERR_MEM)
156 status = CURLE_OUT_OF_MEMORY;
157 msg = url_errs[rc];
158 }
159 failf(conn->data, "LDAP local: %s", msg);
160 return status;
161 }
162 proto = ldap_pvt_url_scheme2proto(lud->lud_scheme);
163 ldap_free_urldesc(lud);
164
165 li = calloc(1, sizeof(ldapconninfo));
166 li->proto = proto;
167 conn->proto.generic = li;
168 conn->bits.close = FALSE;
169 /* TODO:
170 * - provide option to choose SASL Binds instead of Simple
171 */
172 return CURLE_OK;
173}
174
175#ifdef USE_SSL
176static Sockbuf_IO ldapsb_tls;
177#endif
178
179static CURLcode ldap_connect(struct connectdata *conn, bool *done)
180{
181 ldapconninfo *li = conn->proto.generic;
182 struct SessionHandle *data=conn->data;
183 int rc, proto = LDAP_VERSION3;
184 char hosturl[1024], *ptr;
185
186 strcpy(hosturl, "ldap");
187 ptr = hosturl+4;
188 if (conn->protocol & PROT_SSL)
189 *ptr++ = 's';
190 snprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d",
191 conn->host.name, conn->port);
192
193 rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld);
194 if (rc) {
195 failf(data, "LDAP local: Cannot connect to %s, %s",
196 hosturl, ldap_err2string(rc));
197 return CURLE_COULDNT_CONNECT;
198 }
199
200 ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
201
202#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
203 if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
204 /* for LDAP over HTTP proxy */
205 struct HTTP http_proxy;
206 ldapconninfo *li_save;
207 CURLcode result;
208
209 /* BLOCKING */
210 /* We want "seamless" LDAP operations through HTTP proxy tunnel */
211
212 /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member
213 * conn->proto.http; we want LDAP through HTTP and we have to change the
214 * member temporarily for connecting to the HTTP proxy. After
215 * Curl_proxyCONNECT we have to set back the member to the original struct
216 * LDAP pointer
217 */
218 li_save = data->state.proto.generic;
219 memset(&http_proxy, 0, sizeof(http_proxy));
220 data->state.proto.http = &http_proxy;
221 result = Curl_proxyCONNECT(conn, FIRSTSOCKET,
222 conn->host.name, conn->remote_port);
223
224 data->state.proto.generic = li_save;
225
226 if(CURLE_OK != result)
227 return result;
228 }
229#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
230
231#ifdef USE_SSL
232 if (conn->protocol & PROT_SSL) {
233 CURLcode res;
234 if (data->state.used_interface == Curl_if_easy) {
235 res = Curl_ssl_connect(conn, FIRSTSOCKET);
236 if (res)
237 return res;
238 li->ssldone = TRUE;
239 } else {
240 res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &li->ssldone);
241 if (res)
242 return res;
243 }
244 }
245#endif
246
247 if (data->state.used_interface == Curl_if_easy)
248 return ldap_connecting(conn, done);
249
250 return CURLE_OK;
251}
252
253static CURLcode ldap_connecting(struct connectdata *conn, bool *done)
254{
255 ldapconninfo *li = conn->proto.generic;
256 struct SessionHandle *data=conn->data;
257 LDAPMessage *result = NULL;
258 struct timeval tv = {0,1}, *tvp;
259 int rc, err;
260 char *info = NULL;
261
262#ifdef USE_SSL
263 if (conn->protocol & PROT_SSL) {
264 /* Is the SSL handshake complete yet? */
265 if (!li->ssldone) {
266 CURLcode res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &li->ssldone);
267 if (res || !li->ssldone)
268 return res;
269 }
270 /* Have we installed the libcurl SSL handlers into the sockbuf yet? */
271 if (!li->sslinst) {
272 Sockbuf *sb;
273 ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb);
274 ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, conn);
275 li->sslinst = TRUE;
276 li->recv = conn->recv[FIRSTSOCKET];
277 li->send = conn->send[FIRSTSOCKET];
278 }
279 }
280#endif
281
282 if (data->state.used_interface == Curl_if_easy)
283 tvp = NULL; /* let ldap_result block indefinitely */
284 else
285 tvp = &tv;
286
287retry:
288 if (!li->didbind) {
289 char *binddn;
290 struct berval passwd;
291
292 if (conn->bits.user_passwd) {
293 binddn = conn->user;
294 passwd.bv_val = conn->passwd;
295 passwd.bv_len = strlen(passwd.bv_val);
296 } else {
297 binddn = NULL;
298 passwd.bv_val = NULL;
299 passwd.bv_len = 0;
300 }
301 rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd,
302 NULL, NULL, &li->msgid);
303 if (rc)
304 return CURLE_LDAP_CANNOT_BIND;
305 li->didbind = TRUE;
306 if (tvp)
307 return CURLE_OK;
308 }
309
310 rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &result);
311 if (rc < 0) {
312 failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc));
313 return CURLE_LDAP_CANNOT_BIND;
314 }
315 if (rc == 0) {
316 /* timed out */
317 return CURLE_OK;
318 }
319 rc = ldap_parse_result(li->ld, result, &err, NULL, &info, NULL, NULL, 1);
320 if (rc) {
321 failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc));
322 return CURLE_LDAP_CANNOT_BIND;
323 }
324 /* Try to fallback to LDAPv2? */
325 if (err == LDAP_PROTOCOL_ERROR) {
326 int proto;
327 ldap_get_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
328 if (proto == LDAP_VERSION3) {
329 ldap_memfree(info);
330 proto = LDAP_VERSION2;
331 ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto);
332 li->didbind = FALSE;
333 goto retry;
334 }
335 }
336
337 if (err) {
338 failf(data, "LDAP remote: bind failed %s %s", ldap_err2string(rc),
339 info ? info : "");
340 return CURLE_LOGIN_DENIED;
341 }
342 conn->recv[FIRSTSOCKET] = ldap_recv;
343 *done = TRUE;
344 return CURLE_OK;
345}
346
347static CURLcode ldap_disconnect(struct connectdata *conn)
348{
349 ldapconninfo *li = conn->proto.generic;
350
351 if (li) {
352 if (li->ld) {
353 ldap_unbind_ext(li->ld, NULL, NULL);
354 li->ld = NULL;
355 }
356 conn->proto.generic = NULL;
357 free(li);
358 }
359 return CURLE_OK;
360}
361
362static CURLcode ldap_do(struct connectdata *conn, bool *done)
363{
364 ldapconninfo *li = conn->proto.generic;
365 ldapreqinfo *lr;
366 CURLcode status = CURLE_OK;
367 int rc = 0;
368 LDAPURLDesc *ludp = NULL;
369 int msgid;
370 struct SessionHandle *data=conn->data;
371
372 conn->bits.close = FALSE;
373
374 infof(data, "LDAP local: %s\n", data->change.url);
375
376 rc = ldap_url_parse(data->change.url, &ludp);
377 if (rc != LDAP_URL_SUCCESS) {
378 const char *msg = "url parsing problem";
379 status = CURLE_URL_MALFORMAT;
380 if (rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) {
381 if (rc == LDAP_URL_ERR_MEM)
382 status = CURLE_OUT_OF_MEMORY;
383 msg = url_errs[rc];
384 }
385 failf(conn->data, "LDAP local: %s", msg);
386 return status;
387 }
388
389 rc = ldap_search_ext(li->ld, ludp->lud_dn, ludp->lud_scope,
390 ludp->lud_filter, ludp->lud_attrs, 0,
391 NULL, NULL, NULL, 0, &msgid);
392 ldap_free_urldesc(ludp);
393 if (rc != LDAP_SUCCESS) {
394 failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc));
395 return CURLE_LDAP_SEARCH_FAILED;
396 }
397 lr = calloc(1,sizeof(ldapreqinfo));
398 lr->msgid = msgid;
399 data->state.proto.generic = lr;
400 Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL);
401 *done = TRUE;
402 return CURLE_OK;
403}
404
405static CURLcode ldap_done(struct connectdata *conn, CURLcode res,
406 bool premature)
407{
408 ldapreqinfo *lr = conn->data->state.proto.generic;
409 (void)res;
410 (void)premature;
411
412 if (lr) {
413 /* if there was a search in progress, abandon it */
414 if (lr->msgid) {
415 ldapconninfo *li = conn->proto.generic;
416 ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL);
417 lr->msgid = 0;
418 }
419 conn->data->state.proto.generic = NULL;
420 free(lr);
421 }
422 return CURLE_OK;
423}
424
425static ssize_t ldap_recv(struct connectdata *conn, int sockindex, char *buf,
426 size_t len, CURLcode *err)
427{
428 ldapconninfo *li = conn->proto.generic;
429 struct SessionHandle *data=conn->data;
430 ldapreqinfo *lr = data->state.proto.generic;
431 int rc, ret;
432 LDAPMessage *result = NULL;
433 LDAPMessage *ent;
434 BerElement *ber = NULL;
435 struct timeval tv = {0,1};
436 (void)len;
437 (void)buf;
438 (void)sockindex;
439
440 rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_RECEIVED, &tv, &result);
441 if (rc < 0) {
442 failf(data, "LDAP local: search ldap_result %s", ldap_err2string(rc));
443 *err = CURLE_RECV_ERROR;
444 return -1;
445 }
446
447 *err = CURLE_AGAIN;
448 ret = -1;
449
450 /* timed out */
451 if (result == NULL)
452 return ret;
453
454 for (ent = ldap_first_message(li->ld, result); ent;
455 ent = ldap_next_message(li->ld, ent)) {
456 struct berval bv, *bvals, **bvp = &bvals;
457 int binary = 0, msgtype;
458
459 msgtype = ldap_msgtype(ent);
460 if (msgtype == LDAP_RES_SEARCH_RESULT) {
461 int code;
462 char *info = NULL;
463 rc = ldap_parse_result(li->ld, ent, &code, NULL, &info, NULL, NULL, 0);
464 if (rc) {
465 failf(data, "LDAP local: search ldap_parse_result %s", ldap_err2string(rc));
466 *err = CURLE_LDAP_SEARCH_FAILED;
467 } else if (code && code != LDAP_SIZELIMIT_EXCEEDED) {
468 failf(data, "LDAP remote: search failed %s %s", ldap_err2string(rc),
469 info ? info : "");
470 *err = CURLE_LDAP_SEARCH_FAILED;
471 } else {
472 /* successful */
473 if (code == LDAP_SIZELIMIT_EXCEEDED)
474 infof(data, "There are more than %d entries\n", lr->nument);
475 data->req.size = data->req.bytecount;
476 *err = CURLE_OK;
477 ret = 0;
478 }
479 lr->msgid = 0;
480 ldap_memfree(info);
481 break;
482 } else if (msgtype != LDAP_RES_SEARCH_ENTRY) {
483 continue;
484 }
485
486 lr->nument++;
487 rc = ldap_get_dn_ber(li->ld, ent, &ber, &bv);
488 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4);
489 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len);
490 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1);
491 data->req.bytecount += bv.bv_len + 5;
492
493 for (rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp);
494 rc == LDAP_SUCCESS;
495 rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp)) {
496 int i;
497
498 if (bv.bv_val == NULL) break;
499
500 if (bv.bv_len > 7 && !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7))
501 binary = 1;
502
503 for (i=0; bvals[i].bv_val != NULL; i++) {
504 int binval = 0;
505 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1);
506 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len);
507 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)":", 1);
508 data->req.bytecount += bv.bv_len + 2;
509
510 if (!binary) {
511 /* check for leading or trailing whitespace */
512 if (isspace(bvals[i].bv_val[0]) ||
513 isspace(bvals[i].bv_val[bvals[i].bv_len-1])) {
514 binval = 1;
515 } else {
516 /* check for unprintable characters */
517 unsigned int j;
518 for (j=0; j<bvals[i].bv_len; j++)
519 if (!isprint(bvals[i].bv_val[j])) {
520 binval = 1;
521 break;
522 }
523 }
524 }
525 if (binary || binval) {
526 char *val_b64;
527 /* Binary value, encode to base64. */
528 size_t val_b64_sz = Curl_base64_encode(data,
529 bvals[i].bv_val,
530 bvals[i].bv_len,
531 &val_b64);
532 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2);
533 data->req.bytecount += 2;
534 if(val_b64_sz > 0) {
535 Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz);
536 free(val_b64);
537 data->req.bytecount += val_b64_sz;
538 }
539 } else {
540 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)" ", 1);
541 Curl_client_write(conn, CLIENTWRITE_BODY, bvals[i].bv_val,
542 bvals[i].bv_len);
543 data->req.bytecount += bvals[i].bv_len + 1;
544 }
545 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0);
546 data->req.bytecount++;
547 }
548 ber_memfree(bvals);
549 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0);
550 data->req.bytecount++;
551 }
552 Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0);
553 data->req.bytecount++;
554 ber_free(ber, 0);
555 }
556 ldap_msgfree(result);
557 return ret;
558}
559
560#ifdef USE_SSL
561static int
562ldapsb_tls_setup(Sockbuf_IO_Desc *sbiod, void *arg)
563{
564 sbiod->sbiod_pvt = arg;
565 return 0;
566}
567
568static int
569ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod)
570{
571 sbiod->sbiod_pvt = NULL;
572 return 0;
573}
574
575/* We don't need to do anything because libcurl does it already */
576static int
577ldapsb_tls_close(Sockbuf_IO_Desc *sbiod)
578{
579 (void)sbiod;
580 return 0;
581}
582
583static int
584ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg)
585{
586 (void)arg;
587 if (opt == LBER_SB_OPT_DATA_READY) {
588 struct connectdata *conn = sbiod->sbiod_pvt;
589 return Curl_ssl_data_pending(conn, FIRSTSOCKET);
590 }
591 return 0;
592}
593
594static ber_slen_t
595ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
596{
597 struct connectdata *conn = sbiod->sbiod_pvt;
598 ldapconninfo *li = conn->proto.generic;
599 ber_slen_t ret;
600 CURLcode err = CURLE_RECV_ERROR;
601
602 ret = li->recv(conn, FIRSTSOCKET, buf, len, &err);
603 if (ret < 0 && err == CURLE_AGAIN) {
604 SET_SOCKERRNO(EWOULDBLOCK);
605 }
606 return ret;
607}
608
609static ber_slen_t
610ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
611{
612 struct connectdata *conn = sbiod->sbiod_pvt;
613 ldapconninfo *li = conn->proto.generic;
614 ber_slen_t ret;
615 CURLcode err = CURLE_SEND_ERROR;
616
617 ret = li->send(conn, FIRSTSOCKET, buf, len, &err);
618 if (ret < 0 && err == CURLE_AGAIN) {
619 SET_SOCKERRNO(EWOULDBLOCK);
620 }
621 return ret;
622}
623
624static Sockbuf_IO ldapsb_tls =
625{
626 ldapsb_tls_setup,
627 ldapsb_tls_remove,
628 ldapsb_tls_ctrl,
629 ldapsb_tls_read,
630 ldapsb_tls_write,
631 ldapsb_tls_close
632};
633#endif /* USE_SSL */
634
635#endif /* !CURL_DISABLE_LDAP && USE_OPENLDAP */