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