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