6b6e17abbb68b5864dbf12443ff997ad2042801f
[platform/upstream/curl.git] / lib / ldap.c
1 /***************************************************************************
2  *                      _   _ ____  _
3  *  Project         ___| | | |  _ \| |
4  *                 / __| | | | |_) | |
5  *                | (__| |_| |  _ <| |___
6  *                 \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
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  * $Id$
22  ***************************************************************************/
23
24 #include "setup.h"
25
26 #ifndef CURL_DISABLE_LDAP
27 /* -- WIN32 approved -- */
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <ctype.h>
33 #ifdef NEED_MALLOC_H
34 #include <malloc.h>
35 #endif
36 #include <errno.h>
37
38 #ifdef CURL_LDAP_HYBRID         /* If W$ definitions are needed. */
39 # include <windows.h>
40   /* Remember we are NOT in a W$ compiler! */
41 # undef WIN32
42 # undef _WIN32
43 # undef __WIN32__
44 #endif
45
46 #ifdef CURL_LDAP_WIN            /* Use W$ LDAP implementation. */
47 # include <winldap.h>
48 #else
49 #define LDAP_DEPRECATED 1       /* Be sure ldap_init() is defined. */
50 # include <ldap.h>
51 #if (defined(HAVE_LDAP_SSL) && defined(HAVE_LDAP_SSL_H))
52 # include <ldap_ssl.h>
53 #endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */
54 #endif
55
56 #ifdef HAVE_UNISTD_H
57 # include <unistd.h>
58 #endif
59
60 #include "urldata.h"
61 #include <curl/curl.h>
62 #include "sendf.h"
63 #include "escape.h"
64 #include "transfer.h"
65 #include "strequal.h"
66 #include "strtok.h"
67 #include "curl_ldap.h"
68 #include "memory.h"
69 #include "base64.h"
70
71 #define _MPRINTF_REPLACE /* use our functions only */
72 #include <curl/mprintf.h>
73
74 #include "memdebug.h"
75
76 #ifndef HAVE_LDAP_URL_PARSE
77
78 /* Use our own implementation. */
79
80 typedef struct {
81     char   *lud_host;
82     int     lud_port;
83     char   *lud_dn;
84     char  **lud_attrs;
85     int     lud_scope;
86     char   *lud_filter;
87     char  **lud_exts;
88 } CURL_LDAPURLDesc;
89
90 #undef LDAPURLDesc
91 #define LDAPURLDesc             CURL_LDAPURLDesc
92
93 static int  _ldap_url_parse (const struct connectdata *conn,
94                              LDAPURLDesc **ludp);
95 static void _ldap_free_urldesc (LDAPURLDesc *ludp);
96
97 #undef ldap_free_urldesc
98 #define ldap_free_urldesc       _ldap_free_urldesc
99 #endif
100
101 #ifdef DEBUG_LDAP
102   #define LDAP_TRACE(x)   do { \
103                             _ldap_trace ("%u: ", __LINE__); \
104                             _ldap_trace x; \
105                           } while (0)
106
107   static void _ldap_trace (const char *fmt, ...);
108 #else
109   #define LDAP_TRACE(x)   ((void)0)
110 #endif
111
112
113 CURLcode Curl_ldap(struct connectdata *conn, bool *done)
114 {
115   CURLcode status = CURLE_OK;
116   int rc = 0;
117   LDAP *server = NULL;
118   LDAPURLDesc *ludp = NULL;
119   LDAPMessage *result = NULL;
120   LDAPMessage *entryIterator;
121   int num = 0;
122   struct SessionHandle *data=conn->data;
123   int ldap_proto;
124   int ldap_ssl = 0;
125   char *val_b64;
126   size_t val_b64_sz;
127 #ifdef LDAP_OPT_NETWORK_TIMEOUT
128   struct timeval ldap_timeout = {10,0}; /* 10 sec connection/search timeout */
129 #endif
130
131   *done = TRUE; /* unconditionally */
132   infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d\n",
133           LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION);
134   infof(data, "LDAP local: %s\n", data->change.url);
135
136 #ifdef HAVE_LDAP_URL_PARSE
137   rc = ldap_url_parse(data->change.url, &ludp);
138 #else
139   rc = _ldap_url_parse(conn, &ludp);
140 #endif
141   if (rc != 0) {
142     failf(data, "LDAP local: %s", ldap_err2string(rc));
143     status = CURLE_LDAP_INVALID_URL;
144     goto quit;
145   }
146
147   /* Get the URL scheme ( either ldap or ldaps ) */
148   if (strequal(conn->protostr, "LDAPS"))
149     ldap_ssl = 1;
150   infof(data, "LDAP local: trying to establish %s connection\n",
151           ldap_ssl ? "encrypted" : "cleartext");
152
153 #ifdef LDAP_OPT_NETWORK_TIMEOUT
154   ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout);
155 #endif
156   ldap_proto = LDAP_VERSION3;
157   ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
158
159   if (ldap_ssl) {
160 #ifdef HAVE_LDAP_SSL
161 #ifdef CURL_LDAP_WIN
162     server = ldap_sslinit(conn->host.name, (int)conn->port, 1);
163     ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON);
164 #else
165     int ldap_option;
166     int verify_cert = 0;  /* XXX fix me: need to get insecure option here! */
167     char* ldap_ca = NULL; /* XXX fix me: need to get CA path option here! */
168 #if defined(CURL_HAS_NOVELL_LDAPSDK)
169     rc = ldapssl_client_init(NULL, NULL);
170     if (rc != LDAP_SUCCESS) {
171       failf(data, "LDAP local: %s", ldap_err2string(rc));
172       status = CURLE_SSL_CERTPROBLEM;
173       goto quit;
174     }
175     if (verify_cert) {
176       /* Novell SDK supports DER or BASE64 files. */
177       rc = ldapssl_add_trusted_cert(ldap_ca, LDAPSSL_CERT_FILETYPE_B64);
178       if (rc != LDAP_SUCCESS) {
179         failf(data, "LDAP local: ERROR setting PEM CA cert: %s",
180                 ldap_err2string(rc));
181         status = CURLE_SSL_CERTPROBLEM;
182         goto quit;
183       }
184       ldap_option = LDAPSSL_VERIFY_SERVER;
185     } else {
186       ldap_option = LDAPSSL_VERIFY_NONE;
187     }
188     rc = ldapssl_set_verify_mode(ldap_option);
189     if (rc != LDAP_SUCCESS) {
190       failf(data, "LDAP local: ERROR setting verify mode: %s",
191               ldap_err2string(rc));
192       status = CURLE_SSL_CERTPROBLEM;
193       goto quit;
194     }
195     server = ldapssl_init(conn->host.name, (int)conn->port, 1);
196     if (server == NULL) {
197       failf(data, "LDAP local: Cannot connect to %s:%d",
198               conn->host.name, conn->port);
199       status = CURLE_COULDNT_CONNECT;
200       goto quit;
201     }
202 #elif defined(LDAP_OPT_X_TLS)
203     if (verify_cert) {
204       /* OpenLDAP SDK supports BASE64 files. */
205       rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca);
206       if (rc != LDAP_SUCCESS) {
207         failf(data, "LDAP local: ERROR setting PEM CA cert: %s",
208                 ldap_err2string(rc));
209         status = CURLE_SSL_CERTPROBLEM;
210         goto quit;
211       }
212       ldap_option = LDAP_OPT_X_TLS_DEMAND;
213     } else {
214       ldap_option = LDAP_OPT_X_TLS_NEVER;
215     }
216     rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option);
217     if (rc != LDAP_SUCCESS) {
218       failf(data, "LDAP local: ERROR setting verify mode: %s",
219               ldap_err2string(rc));
220       status = CURLE_SSL_CERTPROBLEM;
221       goto quit;
222     }
223     server = ldap_init(conn->host.name, (int)conn->port);
224     if (server == NULL) {
225       failf(data, "LDAP local: Cannot connect to %s:%d",
226               conn->host.name, conn->port);
227       status = CURLE_COULDNT_CONNECT;
228       goto quit;
229     }
230     ldap_option = LDAP_OPT_X_TLS_HARD;
231     rc = ldap_set_option(server, LDAP_OPT_X_TLS, &ldap_option);
232     if (rc != LDAP_SUCCESS) {
233       failf(data, "LDAP local: ERROR setting SSL/TLS mode: %s",
234               ldap_err2string(rc));
235       status = CURLE_SSL_CERTPROBLEM;
236       goto quit;
237     }
238 /*
239     rc = ldap_start_tls_s(server, NULL, NULL);
240     if (rc != LDAP_SUCCESS) {
241       failf(data, "LDAP local: ERROR starting SSL/TLS mode: %s",
242               ldap_err2string(rc));
243       status = CURLE_SSL_CERTPROBLEM;
244       goto quit;
245     }
246 */
247 #else
248     /* we should probably never come up to here since configure
249        should check in first place if we can support LDAP SSL/TLS */
250     failf(data, "LDAP local: SSL/TLS not supported with this version "
251             "of the OpenLDAP toolkit\n");
252     status = CURLE_SSL_CERTPROBLEM;
253     goto quit;
254 #endif
255 #endif
256 #endif /* CURL_LDAP_USE_SSL */
257   } else {
258     server = ldap_init(conn->host.name, (int)conn->port);
259     if (server == NULL) {
260       failf(data, "LDAP local: Cannot connect to %s:%d",
261               conn->host.name, conn->port);
262       status = CURLE_COULDNT_CONNECT;
263       goto quit;
264     }
265   }
266
267   rc = ldap_simple_bind_s(server,
268                           conn->bits.user_passwd ? conn->user : NULL,
269                           conn->bits.user_passwd ? conn->passwd : NULL);
270   if (!ldap_ssl && rc != 0) {
271     ldap_proto = LDAP_VERSION2;
272     ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
273     rc = ldap_simple_bind_s(server,
274                             conn->bits.user_passwd ? conn->user : NULL,
275                             conn->bits.user_passwd ? conn->passwd : NULL);
276   }
277   if (rc != 0) {
278      failf(data, "LDAP local: %s", ldap_err2string(rc));
279      status = CURLE_LDAP_CANNOT_BIND;
280      goto quit;
281   }
282
283   rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope,
284                      ludp->lud_filter, ludp->lud_attrs, 0, &result);
285
286   if (rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) {
287     failf(data, "LDAP remote: %s", ldap_err2string(rc));
288     status = CURLE_LDAP_SEARCH_FAILED;
289     goto quit;
290   }
291
292   for(num = 0, entryIterator = ldap_first_entry(server, result);
293       entryIterator;
294       entryIterator = ldap_next_entry(server, entryIterator), num++)
295   {
296     BerElement *ber = NULL;
297     char  *attribute;       /*! suspicious that this isn't 'const' */
298     char  *dn = ldap_get_dn(server, entryIterator);
299     int i;
300
301     Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4);
302     Curl_client_write(conn, CLIENTWRITE_BODY, (char *)dn, 0);
303     Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1);
304
305     for (attribute = ldap_first_attribute(server, entryIterator, &ber);
306          attribute;
307          attribute = ldap_next_attribute(server, entryIterator, ber))
308     {
309       BerValue **vals = ldap_get_values_len(server, entryIterator, attribute);
310
311       if (vals != NULL)
312       {
313         for (i = 0; (vals[i] != NULL); i++)
314         {
315           Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1);
316           Curl_client_write(conn, CLIENTWRITE_BODY, (char *) attribute, 0);
317           Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2);
318           if ((strlen(attribute) > 7) &&
319               (strcmp(";binary",
320                       (char *)attribute +
321                       (strlen((char *)attribute) - 7)) == 0)) {
322             /* Binary attribute, encode to base64. */
323             val_b64_sz = Curl_base64_encode(conn->data,
324                                             vals[i]->bv_val,
325                                             vals[i]->bv_len,
326                                             &val_b64);
327             if (val_b64_sz > 0) {
328               Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz);
329               free(val_b64);
330             }
331           } else
332             Curl_client_write(conn, CLIENTWRITE_BODY, vals[i]->bv_val,
333                               vals[i]->bv_len);
334           Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0);
335         }
336
337         /* Free memory used to store values */
338         ldap_value_free_len(vals);
339       }
340       Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1);
341
342       ldap_memfree(attribute);
343     }
344     ldap_memfree(dn);
345     if (ber)
346        ber_free(ber, 0);
347   }
348
349 quit:
350   if (result) {
351     ldap_msgfree(result);
352     LDAP_TRACE (("Received %d entries\n", num));
353   }
354   if (rc == LDAP_SIZELIMIT_EXCEEDED)
355     infof(data, "There are more than %d entries\n", num);
356   if (ludp)
357     ldap_free_urldesc(ludp);
358   if (server)
359     ldap_unbind_s(server);
360 #if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK)
361   if (ldap_ssl)
362     ldapssl_client_deinit();
363 #endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */
364
365   /* no data to transfer */
366   Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
367   conn->bits.close = TRUE;
368
369   return status;
370 }
371
372 #ifdef DEBUG_LDAP
373 static void _ldap_trace (const char *fmt, ...)
374 {
375   static int do_trace = -1;
376   va_list args;
377
378   if (do_trace == -1) {
379     const char *env = getenv("CURL_TRACE");
380     do_trace = (env && atoi(env) > 0);
381   }
382   if (!do_trace)
383     return;
384
385   va_start (args, fmt);
386   vfprintf (stderr, fmt, args);
387   va_end (args);
388 }
389 #endif
390
391 #ifndef HAVE_LDAP_URL_PARSE
392
393 /*
394  * Return scope-value for a scope-string.
395  */
396 static int str2scope (const char *p)
397 {
398   if (!stricmp(p, "one"))
399      return LDAP_SCOPE_ONELEVEL;
400   if (!stricmp(p, "onetree"))
401      return LDAP_SCOPE_ONELEVEL;
402   if (!stricmp(p, "base"))
403      return LDAP_SCOPE_BASE;
404   if (!stricmp(p, "sub"))
405      return LDAP_SCOPE_SUBTREE;
406   if (!stricmp( p, "subtree"))
407      return LDAP_SCOPE_SUBTREE;
408   return (-1);
409 }
410
411 /*
412  * Split 'str' into strings separated by commas.
413  * Note: res[] points into 'str'.
414  */
415 static char **split_str (char *str)
416 {
417   char **res, *lasts, *s;
418   int  i;
419
420   for (i = 2, s = strchr(str,','); s; i++)
421      s = strchr(++s,',');
422
423   res = calloc(i, sizeof(char*));
424   if (!res)
425     return NULL;
426
427   for (i = 0, s = strtok_r(str, ",", &lasts); s;
428        s = strtok_r(NULL, ",", &lasts), i++)
429     res[i] = s;
430   return res;
431 }
432
433 /*
434  * Unescape the LDAP-URL components
435  */
436 static bool unescape_elements (void *data, LDAPURLDesc *ludp)
437 {
438   int i;
439
440   if (ludp->lud_filter) {
441     ludp->lud_filter = curl_easy_unescape(data, ludp->lud_filter, 0, NULL);
442     if (!ludp->lud_filter)
443        return (FALSE);
444   }
445
446   for (i = 0; ludp->lud_attrs && ludp->lud_attrs[i]; i++) {
447     ludp->lud_attrs[i] = curl_easy_unescape(data, ludp->lud_attrs[i], 0, NULL);
448     if (!ludp->lud_attrs[i])
449        return (FALSE);
450   }
451
452   for (i = 0; ludp->lud_exts && ludp->lud_exts[i]; i++) {
453     ludp->lud_exts[i] = curl_easy_unescape(data, ludp->lud_exts[i], 0, NULL);
454     if (!ludp->lud_exts[i])
455        return (FALSE);
456   }
457
458   if (ludp->lud_dn) {
459     char *dn = ludp->lud_dn;
460     char *new_dn = curl_easy_unescape(data, dn, 0, NULL);
461
462     free(dn);
463     ludp->lud_dn = new_dn;
464     if (!new_dn)
465        return (FALSE);
466   }
467   return (TRUE);
468 }
469
470 /*
471  * Break apart the pieces of an LDAP URL.
472  * Syntax:
473  *   ldap://<hostname>:<port>/<base_dn>?<attributes>?<scope>?<filter>?<ext>
474  *
475  * <hostname> already known from 'conn->host.name'.
476  * <port>     already known from 'conn->remote_port'.
477  * extract the rest from 'conn->data->reqdata.path+1'. All fields are optional.
478  * e.g.
479  *   ldap://<hostname>:<port>/?<attributes>?<scope>?<filter>
480  * yields ludp->lud_dn = "".
481  *
482  * Ref. http://developer.netscape.com/docs/manuals/dirsdk/csdk30/url.htm#2831915
483  */
484 static int _ldap_url_parse2 (const struct connectdata *conn, LDAPURLDesc *ludp)
485 {
486   char *p, *q;
487   int i;
488
489   if (!conn->data ||
490       !conn->data->reqdata.path ||
491       conn->data->reqdata.path[0] != '/' ||
492       !checkprefix(conn->protostr, conn->data->change.url))
493     return LDAP_INVALID_SYNTAX;
494
495   ludp->lud_scope = LDAP_SCOPE_BASE;
496   ludp->lud_port  = conn->remote_port;
497   ludp->lud_host  = conn->host.name;
498
499   /* parse DN (Distinguished Name).
500    */
501   ludp->lud_dn = strdup(conn->data->reqdata.path+1);
502   if (!ludp->lud_dn)
503     return LDAP_NO_MEMORY;
504
505   p = strchr(ludp->lud_dn, '?');
506   LDAP_TRACE (("DN '%.*s'\n", p ? (size_t)(p-ludp->lud_dn) :
507                strlen(ludp->lud_dn), ludp->lud_dn));
508
509   if (!p)
510     goto success;
511
512   *p++ = '\0';
513
514   /* parse attributes. skip "??".
515    */
516   q = strchr(p, '?');
517   if (q)
518     *q++ = '\0';
519
520   if (*p && *p != '?') {
521     ludp->lud_attrs = split_str(p);
522     if (!ludp->lud_attrs)
523       return LDAP_NO_MEMORY;
524
525     for (i = 0; ludp->lud_attrs[i]; i++)
526       LDAP_TRACE (("attr[%d] '%s'\n", i, ludp->lud_attrs[i]));
527   }
528
529   p = q;
530   if (!p)
531     goto success;
532
533   /* parse scope. skip "??"
534    */
535   q = strchr(p, '?');
536   if (q)
537     *q++ = '\0';
538
539   if (*p && *p != '?') {
540     ludp->lud_scope = str2scope(p);
541     if (ludp->lud_scope == -1)
542       return LDAP_INVALID_SYNTAX;
543     LDAP_TRACE (("scope %d\n", ludp->lud_scope));
544   }
545
546   p = q;
547   if (!p)
548     goto success;
549
550   /* parse filter
551    */
552   q = strchr(p, '?');
553   if (q)
554     *q++ = '\0';
555   if (!*p)
556     return LDAP_INVALID_SYNTAX;
557
558   ludp->lud_filter = p;
559   LDAP_TRACE (("filter '%s'\n", ludp->lud_filter));
560
561   p = q;
562   if (!p)
563     goto success;
564
565   /* parse extensions
566    */
567   ludp->lud_exts = split_str(p);
568   if (!ludp->lud_exts)
569     return LDAP_NO_MEMORY;
570
571   for (i = 0; ludp->lud_exts[i]; i++)
572     LDAP_TRACE (("exts[%d] '%s'\n", i, ludp->lud_exts[i]));
573
574   success:
575   if (!unescape_elements(conn->data, ludp))
576     return LDAP_NO_MEMORY;
577   return LDAP_SUCCESS;
578 }
579
580 static int _ldap_url_parse (const struct connectdata *conn,
581                             LDAPURLDesc **ludpp)
582 {
583   LDAPURLDesc *ludp = calloc(sizeof(*ludp), 1);
584   int rc;
585
586   *ludpp = NULL;
587   if (!ludp)
588      return LDAP_NO_MEMORY;
589
590   rc = _ldap_url_parse2 (conn, ludp);
591   if (rc != LDAP_SUCCESS) {
592     _ldap_free_urldesc(ludp);
593     ludp = NULL;
594   }
595   *ludpp = ludp;
596   return (rc);
597 }
598
599 static void _ldap_free_urldesc (LDAPURLDesc *ludp)
600 {
601   int i;
602
603   if (!ludp)
604      return;
605
606   if (ludp->lud_dn)
607      free(ludp->lud_dn);
608
609   if (ludp->lud_filter)
610      free(ludp->lud_filter);
611
612   if (ludp->lud_attrs) {
613     for (i = 0; ludp->lud_attrs[i]; i++)
614        free(ludp->lud_attrs[i]);
615     free(ludp->lud_attrs);
616   }
617
618   if (ludp->lud_exts) {
619     for (i = 0; ludp->lud_exts[i]; i++)
620        free(ludp->lud_exts[i]);
621     free(ludp->lud_exts);
622   }
623   free (ludp);
624 }
625 #endif  /* !HAVE_LDAP_URL_PARSE */
626 #endif  /* CURL_DISABLE_LDAP */