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