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