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