xref: /curl/lib/ldap.c (revision e0866dd0)
1 /***************************************************************************
2  *                      _   _ ____  _
3  *  Project         ___| | | |  _ \| |
4  *                 / __| | | | |_) | |
5  *                | (__| |_| |  _ <| |___
6  *                 \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 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 https://curl.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  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 #include "curl_setup.h"
26 
27 #if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP)
28 
29 /*
30  * Notice that USE_OPENLDAP is only a source code selection switch. When
31  * libcurl is built with USE_OPENLDAP defined the libcurl source code that
32  * gets compiled is the code from openldap.c, otherwise the code that gets
33  * compiled is the code from ldap.c.
34  *
35  * When USE_OPENLDAP is defined a recent version of the OpenLDAP library
36  * might be required for compilation and runtime. In order to use ancient
37  * OpenLDAP library versions, USE_OPENLDAP shall not be defined.
38  */
39 
40 /* Wincrypt must be included before anything that could include OpenSSL. */
41 #if defined(USE_WIN32_CRYPTO)
42 #include <wincrypt.h>
43 /* Undefine wincrypt conflicting symbols for BoringSSL. */
44 #undef X509_NAME
45 #undef X509_EXTENSIONS
46 #undef PKCS7_ISSUER_AND_SERIAL
47 #undef PKCS7_SIGNER_INFO
48 #undef OCSP_REQUEST
49 #undef OCSP_RESPONSE
50 #endif
51 
52 #ifdef USE_WIN32_LDAP           /* Use Windows LDAP implementation. */
53 # ifdef _MSC_VER
54 #  pragma warning(push)
55 #  pragma warning(disable: 4201)
56 # endif
57 # include <subauth.h>  /* for [P]UNICODE_STRING */
58 # ifdef _MSC_VER
59 #  pragma warning(pop)
60 # endif
61 # include <winldap.h>
62 # ifndef LDAP_VENDOR_NAME
63 #  error Your Platform SDK is NOT sufficient for LDAP support! \
64          Update your Platform SDK, or disable LDAP support!
65 # else
66 #  include <winber.h>
67 # endif
68 #else
69 # define LDAP_DEPRECATED 1      /* Be sure ldap_init() is defined. */
70 # ifdef HAVE_LBER_H
71 #  include <lber.h>
72 # endif
73 # include <ldap.h>
74 # if (defined(HAVE_LDAP_SSL) && defined(HAVE_LDAP_SSL_H))
75 #  include <ldap_ssl.h>
76 # endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */
77 #endif
78 
79 #include "urldata.h"
80 #include <curl/curl.h>
81 #include "sendf.h"
82 #include "escape.h"
83 #include "progress.h"
84 #include "transfer.h"
85 #include "strcase.h"
86 #include "strtok.h"
87 #include "curl_ldap.h"
88 #include "curl_multibyte.h"
89 #include "curl_base64.h"
90 #include "connect.h"
91 /* The last 3 #include files should be in this order */
92 #include "curl_printf.h"
93 #include "curl_memory.h"
94 #include "memdebug.h"
95 
96 #ifndef HAVE_LDAP_URL_PARSE
97 
98 /* Use our own implementation. */
99 
100 struct ldap_urldesc {
101   char   *lud_host;
102   int     lud_port;
103 #if defined(USE_WIN32_LDAP)
104   TCHAR  *lud_dn;
105   TCHAR **lud_attrs;
106 #else
107   char   *lud_dn;
108   char  **lud_attrs;
109 #endif
110   int     lud_scope;
111 #if defined(USE_WIN32_LDAP)
112   TCHAR  *lud_filter;
113 #else
114   char   *lud_filter;
115 #endif
116   char  **lud_exts;
117   size_t    lud_attrs_dups; /* how many were dup'ed, this field is not in the
118                                "real" struct so can only be used in code
119                                without HAVE_LDAP_URL_PARSE defined */
120 };
121 
122 #undef LDAPURLDesc
123 #define LDAPURLDesc struct ldap_urldesc
124 
125 static int  _ldap_url_parse(struct Curl_easy *data,
126                             const struct connectdata *conn,
127                             LDAPURLDesc **ludp);
128 static void _ldap_free_urldesc(LDAPURLDesc *ludp);
129 
130 #undef ldap_free_urldesc
131 #define ldap_free_urldesc       _ldap_free_urldesc
132 #endif
133 
134 #ifdef DEBUG_LDAP
135   #define LDAP_TRACE(x)   do { \
136                             _ldap_trace("%u: ", __LINE__); \
137                             _ldap_trace x; \
138                           } while(0)
139 
140   static void _ldap_trace(const char *fmt, ...) CURL_PRINTF(1, 2);
141 #else
142   #define LDAP_TRACE(x)   Curl_nop_stmt
143 #endif
144 
145 #if defined(USE_WIN32_LDAP) && defined(ldap_err2string)
146 /* Use ansi error strings in UNICODE builds */
147 #undef ldap_err2string
148 #define ldap_err2string ldap_err2stringA
149 #endif
150 
151 #if defined(USE_WIN32_LDAP) && defined(_MSC_VER) && (_MSC_VER <= 1600)
152 /* Workaround for warning:
153    'type cast' : conversion from 'int' to 'void *' of greater size */
154 #undef LDAP_OPT_ON
155 #undef LDAP_OPT_OFF
156 #define LDAP_OPT_ON   ((void *)(size_t)1)
157 #define LDAP_OPT_OFF  ((void *)(size_t)0)
158 #endif
159 
160 static CURLcode ldap_do(struct Curl_easy *data, bool *done);
161 
162 /*
163  * LDAP protocol handler.
164  */
165 
166 const struct Curl_handler Curl_handler_ldap = {
167   "ldap",                               /* scheme */
168   ZERO_NULL,                            /* setup_connection */
169   ldap_do,                              /* do_it */
170   ZERO_NULL,                            /* done */
171   ZERO_NULL,                            /* do_more */
172   ZERO_NULL,                            /* connect_it */
173   ZERO_NULL,                            /* connecting */
174   ZERO_NULL,                            /* doing */
175   ZERO_NULL,                            /* proto_getsock */
176   ZERO_NULL,                            /* doing_getsock */
177   ZERO_NULL,                            /* domore_getsock */
178   ZERO_NULL,                            /* perform_getsock */
179   ZERO_NULL,                            /* disconnect */
180   ZERO_NULL,                            /* write_resp */
181   ZERO_NULL,                            /* write_resp_hd */
182   ZERO_NULL,                            /* connection_check */
183   ZERO_NULL,                            /* attach connection */
184   PORT_LDAP,                            /* defport */
185   CURLPROTO_LDAP,                       /* protocol */
186   CURLPROTO_LDAP,                       /* family */
187   PROTOPT_NONE                          /* flags */
188 };
189 
190 #ifdef HAVE_LDAP_SSL
191 /*
192  * LDAPS protocol handler.
193  */
194 
195 const struct Curl_handler Curl_handler_ldaps = {
196   "ldaps",                              /* scheme */
197   ZERO_NULL,                            /* setup_connection */
198   ldap_do,                              /* do_it */
199   ZERO_NULL,                            /* done */
200   ZERO_NULL,                            /* do_more */
201   ZERO_NULL,                            /* connect_it */
202   ZERO_NULL,                            /* connecting */
203   ZERO_NULL,                            /* doing */
204   ZERO_NULL,                            /* proto_getsock */
205   ZERO_NULL,                            /* doing_getsock */
206   ZERO_NULL,                            /* domore_getsock */
207   ZERO_NULL,                            /* perform_getsock */
208   ZERO_NULL,                            /* disconnect */
209   ZERO_NULL,                            /* write_resp */
210   ZERO_NULL,                            /* write_resp_hd */
211   ZERO_NULL,                            /* connection_check */
212   ZERO_NULL,                            /* attach connection */
213   PORT_LDAPS,                           /* defport */
214   CURLPROTO_LDAPS,                      /* protocol */
215   CURLPROTO_LDAP,                       /* family */
216   PROTOPT_SSL                           /* flags */
217 };
218 #endif
219 
220 #if defined(USE_WIN32_LDAP)
221 
222 #if defined(USE_WINDOWS_SSPI)
ldap_win_bind_auth(LDAP * server,const char * user,const char * passwd,unsigned long authflags)223 static int ldap_win_bind_auth(LDAP *server, const char *user,
224                               const char *passwd, unsigned long authflags)
225 {
226   ULONG method = 0;
227   SEC_WINNT_AUTH_IDENTITY cred;
228   int rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
229 
230   memset(&cred, 0, sizeof(cred));
231 
232 #if defined(USE_SPNEGO)
233   if(authflags & CURLAUTH_NEGOTIATE) {
234     method = LDAP_AUTH_NEGOTIATE;
235   }
236   else
237 #endif
238 #if defined(USE_NTLM)
239   if(authflags & CURLAUTH_NTLM) {
240     method = LDAP_AUTH_NTLM;
241   }
242   else
243 #endif
244 #if !defined(CURL_DISABLE_DIGEST_AUTH)
245   if(authflags & CURLAUTH_DIGEST) {
246     method = LDAP_AUTH_DIGEST;
247   }
248   else
249 #endif
250   {
251     /* required anyway if one of upper preprocessor definitions enabled */
252   }
253 
254   if(method && user && passwd) {
255     rc = Curl_create_sspi_identity(user, passwd, &cred);
256     if(!rc) {
257       rc = ldap_bind_s(server, NULL, (TCHAR *)&cred, method);
258       Curl_sspi_free_identity(&cred);
259     }
260   }
261   else {
262     /* proceed with current user credentials */
263     method = LDAP_AUTH_NEGOTIATE;
264     rc = ldap_bind_s(server, NULL, NULL, method);
265   }
266   return rc;
267 }
268 #endif /* #if defined(USE_WINDOWS_SSPI) */
269 
ldap_win_bind(struct Curl_easy * data,LDAP * server,const char * user,const char * passwd)270 static int ldap_win_bind(struct Curl_easy *data, LDAP *server,
271                          const char *user, const char *passwd)
272 {
273   int rc = LDAP_INVALID_CREDENTIALS;
274 
275   PTCHAR inuser = NULL;
276   PTCHAR inpass = NULL;
277 
278   if(user && passwd && (data->set.httpauth & CURLAUTH_BASIC)) {
279     inuser = curlx_convert_UTF8_to_tchar((char *) user);
280     inpass = curlx_convert_UTF8_to_tchar((char *) passwd);
281 
282     rc = ldap_simple_bind_s(server, inuser, inpass);
283 
284     curlx_unicodefree(inuser);
285     curlx_unicodefree(inpass);
286   }
287 #if defined(USE_WINDOWS_SSPI)
288   else {
289     rc = ldap_win_bind_auth(server, user, passwd, data->set.httpauth);
290   }
291 #endif
292 
293   return rc;
294 }
295 #endif /* #if defined(USE_WIN32_LDAP) */
296 
297 #if defined(USE_WIN32_LDAP)
298 #define FREE_ON_WINLDAP(x) curlx_unicodefree(x)
299 #else
300 #define FREE_ON_WINLDAP(x)
301 #endif
302 
303 
ldap_do(struct Curl_easy * data,bool * done)304 static CURLcode ldap_do(struct Curl_easy *data, bool *done)
305 {
306   CURLcode result = CURLE_OK;
307   int rc = 0;
308   LDAP *server = NULL;
309   LDAPURLDesc *ludp = NULL;
310   LDAPMessage *ldapmsg = NULL;
311   LDAPMessage *entryIterator;
312   int num = 0;
313   struct connectdata *conn = data->conn;
314   int ldap_proto = LDAP_VERSION3;
315   int ldap_ssl = 0;
316   char *val_b64 = NULL;
317   size_t val_b64_sz = 0;
318 #ifdef LDAP_OPT_NETWORK_TIMEOUT
319   struct timeval ldap_timeout = {10, 0}; /* 10 sec connection/search timeout */
320 #endif
321 #if defined(USE_WIN32_LDAP)
322   TCHAR *host = NULL;
323 #else
324   char *host = NULL;
325 #endif
326   char *user = NULL;
327   char *passwd = NULL;
328 
329   *done = TRUE; /* unconditionally */
330   infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d",
331         LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION);
332   infof(data, "LDAP local: %s", data->state.url);
333 
334 #ifdef HAVE_LDAP_URL_PARSE
335   rc = ldap_url_parse(data->state.url, &ludp);
336 #else
337   rc = _ldap_url_parse(data, conn, &ludp);
338 #endif
339   if(rc) {
340     failf(data, "Bad LDAP URL: %s", ldap_err2string(rc));
341     result = CURLE_URL_MALFORMAT;
342     goto quit;
343   }
344 
345   /* Get the URL scheme (either ldap or ldaps) */
346   if(conn->given->flags & PROTOPT_SSL)
347     ldap_ssl = 1;
348   infof(data, "LDAP local: trying to establish %s connection",
349         ldap_ssl ? "encrypted" : "cleartext");
350 
351 #if defined(USE_WIN32_LDAP)
352   host = curlx_convert_UTF8_to_tchar(conn->host.name);
353   if(!host) {
354     result = CURLE_OUT_OF_MEMORY;
355 
356     goto quit;
357   }
358 #else
359   host = conn->host.name;
360 #endif
361 
362   if(data->state.aptr.user) {
363     user = conn->user;
364     passwd = conn->passwd;
365   }
366 
367 #ifdef LDAP_OPT_NETWORK_TIMEOUT
368   ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout);
369 #endif
370   ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
371 
372   if(ldap_ssl) {
373 #ifdef HAVE_LDAP_SSL
374 #ifdef USE_WIN32_LDAP
375     /* Win32 LDAP SDK doesn't support insecure mode without CA! */
376     server = ldap_sslinit(host, conn->primary.remote_port, 1);
377     ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON);
378 #else
379     int ldap_option;
380     char *ldap_ca = conn->ssl_config.CAfile;
381 #if defined(CURL_HAS_NOVELL_LDAPSDK)
382     rc = ldapssl_client_init(NULL, NULL);
383     if(rc != LDAP_SUCCESS) {
384       failf(data, "LDAP local: ldapssl_client_init %s", ldap_err2string(rc));
385       result = CURLE_SSL_CERTPROBLEM;
386       goto quit;
387     }
388     if(conn->ssl_config.verifypeer) {
389       /* Novell SDK supports DER or BASE64 files. */
390       int cert_type = LDAPSSL_CERT_FILETYPE_B64;
391       if((data->set.ssl.cert_type) &&
392          (strcasecompare(data->set.ssl.cert_type, "DER")))
393         cert_type = LDAPSSL_CERT_FILETYPE_DER;
394       if(!ldap_ca) {
395         failf(data, "LDAP local: ERROR %s CA cert not set",
396               (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"));
397         result = CURLE_SSL_CERTPROBLEM;
398         goto quit;
399       }
400       infof(data, "LDAP local: using %s CA cert '%s'",
401             (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"),
402             ldap_ca);
403       rc = ldapssl_add_trusted_cert(ldap_ca, cert_type);
404       if(rc != LDAP_SUCCESS) {
405         failf(data, "LDAP local: ERROR setting %s CA cert: %s",
406               (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"),
407               ldap_err2string(rc));
408         result = CURLE_SSL_CERTPROBLEM;
409         goto quit;
410       }
411       ldap_option = LDAPSSL_VERIFY_SERVER;
412     }
413     else
414       ldap_option = LDAPSSL_VERIFY_NONE;
415     rc = ldapssl_set_verify_mode(ldap_option);
416     if(rc != LDAP_SUCCESS) {
417       failf(data, "LDAP local: ERROR setting cert verify mode: %s",
418               ldap_err2string(rc));
419       result = CURLE_SSL_CERTPROBLEM;
420       goto quit;
421     }
422     server = ldapssl_init(host, conn->primary.remote_port, 1);
423     if(!server) {
424       failf(data, "LDAP local: Cannot connect to %s:%u",
425             conn->host.dispname, conn->primary.remote_port);
426       result = CURLE_COULDNT_CONNECT;
427       goto quit;
428     }
429 #elif defined(LDAP_OPT_X_TLS)
430     if(conn->ssl_config.verifypeer) {
431       /* OpenLDAP SDK supports BASE64 files. */
432       if((data->set.ssl.cert_type) &&
433          (!strcasecompare(data->set.ssl.cert_type, "PEM"))) {
434         failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type");
435         result = CURLE_SSL_CERTPROBLEM;
436         goto quit;
437       }
438       if(!ldap_ca) {
439         failf(data, "LDAP local: ERROR PEM CA cert not set");
440         result = CURLE_SSL_CERTPROBLEM;
441         goto quit;
442       }
443       infof(data, "LDAP local: using PEM CA cert: %s", ldap_ca);
444       rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca);
445       if(rc != LDAP_SUCCESS) {
446         failf(data, "LDAP local: ERROR setting PEM CA cert: %s",
447                 ldap_err2string(rc));
448         result = CURLE_SSL_CERTPROBLEM;
449         goto quit;
450       }
451       ldap_option = LDAP_OPT_X_TLS_DEMAND;
452     }
453     else
454       ldap_option = LDAP_OPT_X_TLS_NEVER;
455 
456     rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option);
457     if(rc != LDAP_SUCCESS) {
458       failf(data, "LDAP local: ERROR setting cert verify mode: %s",
459               ldap_err2string(rc));
460       result = CURLE_SSL_CERTPROBLEM;
461       goto quit;
462     }
463     server = ldap_init(host, conn->primary.remote_port);
464     if(!server) {
465       failf(data, "LDAP local: Cannot connect to %s:%u",
466             conn->host.dispname, conn->primary.remote_port);
467       result = CURLE_COULDNT_CONNECT;
468       goto quit;
469     }
470     ldap_option = LDAP_OPT_X_TLS_HARD;
471     rc = ldap_set_option(server, LDAP_OPT_X_TLS, &ldap_option);
472     if(rc != LDAP_SUCCESS) {
473       failf(data, "LDAP local: ERROR setting SSL/TLS mode: %s",
474               ldap_err2string(rc));
475       result = CURLE_SSL_CERTPROBLEM;
476       goto quit;
477     }
478 /*
479     rc = ldap_start_tls_s(server, NULL, NULL);
480     if(rc != LDAP_SUCCESS) {
481       failf(data, "LDAP local: ERROR starting SSL/TLS mode: %s",
482               ldap_err2string(rc));
483       result = CURLE_SSL_CERTPROBLEM;
484       goto quit;
485     }
486 */
487 #else
488     (void)ldap_option;
489     (void)ldap_ca;
490     /* we should probably never come up to here since configure
491        should check in first place if we can support LDAP SSL/TLS */
492     failf(data, "LDAP local: SSL/TLS not supported with this version "
493             "of the OpenLDAP toolkit\n");
494     result = CURLE_SSL_CERTPROBLEM;
495     goto quit;
496 #endif
497 #endif
498 #endif /* CURL_LDAP_USE_SSL */
499   }
500   else if(data->set.use_ssl > CURLUSESSL_TRY) {
501     failf(data, "LDAP local: explicit TLS not supported");
502     result = CURLE_NOT_BUILT_IN;
503     goto quit;
504   }
505   else {
506     server = ldap_init(host, conn->primary.remote_port);
507     if(!server) {
508       failf(data, "LDAP local: Cannot connect to %s:%u",
509             conn->host.dispname, conn->primary.remote_port);
510       result = CURLE_COULDNT_CONNECT;
511       goto quit;
512     }
513   }
514 #ifdef USE_WIN32_LDAP
515   ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
516   rc = ldap_win_bind(data, server, user, passwd);
517 #else
518   rc = ldap_simple_bind_s(server, user, passwd);
519 #endif
520   if(!ldap_ssl && rc) {
521     ldap_proto = LDAP_VERSION2;
522     ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
523 #ifdef USE_WIN32_LDAP
524     rc = ldap_win_bind(data, server, user, passwd);
525 #else
526     rc = ldap_simple_bind_s(server, user, passwd);
527 #endif
528   }
529   if(rc) {
530 #ifdef USE_WIN32_LDAP
531     failf(data, "LDAP local: bind via ldap_win_bind %s",
532           ldap_err2string(rc));
533 #else
534     failf(data, "LDAP local: bind via ldap_simple_bind_s %s",
535           ldap_err2string(rc));
536 #endif
537     result = CURLE_LDAP_CANNOT_BIND;
538     goto quit;
539   }
540 
541   Curl_pgrsSetDownloadCounter(data, 0);
542   rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope,
543                      ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg);
544 
545   if(rc && rc != LDAP_SIZELIMIT_EXCEEDED) {
546     failf(data, "LDAP remote: %s", ldap_err2string(rc));
547     result = CURLE_LDAP_SEARCH_FAILED;
548     goto quit;
549   }
550 
551   num = 0;
552   for(entryIterator = ldap_first_entry(server, ldapmsg);
553       entryIterator;
554       entryIterator = ldap_next_entry(server, entryIterator), num++) {
555     BerElement *ber = NULL;
556 #if defined(USE_WIN32_LDAP)
557     TCHAR *attribute;
558 #else
559     char *attribute;
560 #endif
561     int i;
562 
563     /* Get the DN and write it to the client */
564     {
565       char *name;
566       size_t name_len;
567 #if defined(USE_WIN32_LDAP)
568       TCHAR *dn = ldap_get_dn(server, entryIterator);
569       name = curlx_convert_tchar_to_UTF8(dn);
570       if(!name) {
571         ldap_memfree(dn);
572 
573         result = CURLE_OUT_OF_MEMORY;
574 
575         goto quit;
576       }
577 #else
578       char *dn = name = ldap_get_dn(server, entryIterator);
579 #endif
580       name_len = strlen(name);
581 
582       result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4);
583       if(result) {
584         FREE_ON_WINLDAP(name);
585         ldap_memfree(dn);
586         goto quit;
587       }
588 
589       result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len);
590       if(result) {
591         FREE_ON_WINLDAP(name);
592         ldap_memfree(dn);
593         goto quit;
594       }
595 
596       result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1);
597       if(result) {
598         FREE_ON_WINLDAP(name);
599         ldap_memfree(dn);
600 
601         goto quit;
602       }
603 
604       FREE_ON_WINLDAP(name);
605       ldap_memfree(dn);
606     }
607 
608     /* Get the attributes and write them to the client */
609     for(attribute = ldap_first_attribute(server, entryIterator, &ber);
610         attribute;
611         attribute = ldap_next_attribute(server, entryIterator, ber)) {
612       BerValue **vals;
613       size_t attr_len;
614 #if defined(USE_WIN32_LDAP)
615       char *attr = curlx_convert_tchar_to_UTF8(attribute);
616       if(!attr) {
617         if(ber)
618           ber_free(ber, 0);
619 
620         result = CURLE_OUT_OF_MEMORY;
621 
622         goto quit;
623       }
624 #else
625       char *attr = attribute;
626 #endif
627       attr_len = strlen(attr);
628 
629       vals = ldap_get_values_len(server, entryIterator, attribute);
630       if(vals) {
631         for(i = 0; (vals[i] != NULL); i++) {
632           result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1);
633           if(result) {
634             ldap_value_free_len(vals);
635             FREE_ON_WINLDAP(attr);
636             ldap_memfree(attribute);
637             if(ber)
638               ber_free(ber, 0);
639 
640             goto quit;
641           }
642 
643           result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len);
644           if(result) {
645             ldap_value_free_len(vals);
646             FREE_ON_WINLDAP(attr);
647             ldap_memfree(attribute);
648             if(ber)
649               ber_free(ber, 0);
650 
651             goto quit;
652           }
653 
654           result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2);
655           if(result) {
656             ldap_value_free_len(vals);
657             FREE_ON_WINLDAP(attr);
658             ldap_memfree(attribute);
659             if(ber)
660               ber_free(ber, 0);
661 
662             goto quit;
663           }
664 
665           if((attr_len > 7) &&
666              (strcmp(";binary", attr + (attr_len - 7)) == 0)) {
667             /* Binary attribute, encode to base64. */
668             result = Curl_base64_encode(vals[i]->bv_val, vals[i]->bv_len,
669                                         &val_b64, &val_b64_sz);
670             if(result) {
671               ldap_value_free_len(vals);
672               FREE_ON_WINLDAP(attr);
673               ldap_memfree(attribute);
674               if(ber)
675                 ber_free(ber, 0);
676 
677               goto quit;
678             }
679 
680             if(val_b64_sz > 0) {
681               result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64,
682                                          val_b64_sz);
683               free(val_b64);
684               if(result) {
685                 ldap_value_free_len(vals);
686                 FREE_ON_WINLDAP(attr);
687                 ldap_memfree(attribute);
688                 if(ber)
689                   ber_free(ber, 0);
690 
691                 goto quit;
692               }
693             }
694           }
695           else {
696             result = Curl_client_write(data, CLIENTWRITE_BODY, vals[i]->bv_val,
697                                        vals[i]->bv_len);
698             if(result) {
699               ldap_value_free_len(vals);
700               FREE_ON_WINLDAP(attr);
701               ldap_memfree(attribute);
702               if(ber)
703                 ber_free(ber, 0);
704 
705               goto quit;
706             }
707           }
708 
709           result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1);
710           if(result) {
711             ldap_value_free_len(vals);
712             FREE_ON_WINLDAP(attr);
713             ldap_memfree(attribute);
714             if(ber)
715               ber_free(ber, 0);
716 
717             goto quit;
718           }
719         }
720 
721         /* Free memory used to store values */
722         ldap_value_free_len(vals);
723       }
724 
725       /* Free the attribute as we are done with it */
726       FREE_ON_WINLDAP(attr);
727       ldap_memfree(attribute);
728 
729       result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1);
730       if(result)
731         goto quit;
732     }
733 
734     if(ber)
735       ber_free(ber, 0);
736   }
737 
738 quit:
739   if(ldapmsg) {
740     ldap_msgfree(ldapmsg);
741     LDAP_TRACE(("Received %d entries\n", num));
742   }
743   if(rc == LDAP_SIZELIMIT_EXCEEDED)
744     infof(data, "There are more than %d entries", num);
745   if(ludp)
746     ldap_free_urldesc(ludp);
747   if(server)
748     ldap_unbind_s(server);
749 #if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK)
750   if(ldap_ssl)
751     ldapssl_client_deinit();
752 #endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */
753 
754   FREE_ON_WINLDAP(host);
755 
756   /* no data to transfer */
757   Curl_xfer_setup(data, -1, -1, FALSE, -1);
758   connclose(conn, "LDAP connection always disable reuse");
759 
760   return result;
761 }
762 
763 #ifdef DEBUG_LDAP
_ldap_trace(const char * fmt,...)764 static void _ldap_trace(const char *fmt, ...)
765 {
766   static int do_trace = -1;
767   va_list args;
768 
769   if(do_trace == -1) {
770     const char *env = getenv("CURL_TRACE");
771     do_trace = (env && strtol(env, NULL, 10) > 0);
772   }
773   if(!do_trace)
774     return;
775 
776   va_start(args, fmt);
777   vfprintf(stderr, fmt, args);
778   va_end(args);
779 }
780 #endif
781 
782 #ifndef HAVE_LDAP_URL_PARSE
783 
784 /*
785  * Return scope-value for a scope-string.
786  */
str2scope(const char * p)787 static int str2scope(const char *p)
788 {
789   if(strcasecompare(p, "one"))
790     return LDAP_SCOPE_ONELEVEL;
791   if(strcasecompare(p, "onetree"))
792     return LDAP_SCOPE_ONELEVEL;
793   if(strcasecompare(p, "base"))
794     return LDAP_SCOPE_BASE;
795   if(strcasecompare(p, "sub"))
796     return LDAP_SCOPE_SUBTREE;
797   if(strcasecompare(p, "subtree"))
798     return LDAP_SCOPE_SUBTREE;
799   return (-1);
800 }
801 
802 /*
803  * Split 'str' into strings separated by commas.
804  * Note: out[] points into 'str'.
805  */
split_str(char * str,char *** out,size_t * count)806 static bool split_str(char *str, char ***out, size_t *count)
807 {
808   char **res;
809   char *lasts;
810   char *s;
811   size_t  i;
812   size_t items = 1;
813 
814   s = strchr(str, ',');
815   while(s) {
816     items++;
817     s = strchr(++s, ',');
818   }
819 
820   res = calloc(items, sizeof(char *));
821   if(!res)
822     return FALSE;
823 
824   for(i = 0, s = strtok_r(str, ",", &lasts); s && i < items;
825       s = strtok_r(NULL, ",", &lasts), i++)
826     res[i] = s;
827 
828   *out = res;
829   *count = items;
830 
831   return TRUE;
832 }
833 
834 /*
835  * Break apart the pieces of an LDAP URL.
836  * Syntax:
837  *   ldap://<hostname>:<port>/<base_dn>?<attributes>?<scope>?<filter>?<ext>
838  *
839  * <hostname> already known from 'conn->host.name'.
840  * <port>     already known from 'conn->remote_port'.
841  * extract the rest from 'data->state.path+1'. All fields are optional.
842  * e.g.
843  *   ldap://<hostname>:<port>/?<attributes>?<scope>?<filter>
844  * yields ludp->lud_dn = "".
845  *
846  * Defined in RFC4516 section 2.
847  */
_ldap_url_parse2(struct Curl_easy * data,const struct connectdata * conn,LDAPURLDesc * ludp)848 static int _ldap_url_parse2(struct Curl_easy *data,
849                             const struct connectdata *conn, LDAPURLDesc *ludp)
850 {
851   int rc = LDAP_SUCCESS;
852   char *p;
853   char *path;
854   char *q = NULL;
855   char *query = NULL;
856   size_t i;
857 
858   if(!data ||
859      !data->state.up.path ||
860      data->state.up.path[0] != '/' ||
861      !strncasecompare("LDAP", data->state.up.scheme, 4))
862     return LDAP_INVALID_SYNTAX;
863 
864   ludp->lud_scope = LDAP_SCOPE_BASE;
865   ludp->lud_port  = conn->remote_port;
866   ludp->lud_host  = conn->host.name;
867 
868   /* Duplicate the path */
869   p = path = strdup(data->state.up.path + 1);
870   if(!path)
871     return LDAP_NO_MEMORY;
872 
873   /* Duplicate the query if present */
874   if(data->state.up.query) {
875     q = query = strdup(data->state.up.query);
876     if(!query) {
877       free(path);
878       return LDAP_NO_MEMORY;
879     }
880   }
881 
882   /* Parse the DN (Distinguished Name) */
883   if(*p) {
884     char *dn = p;
885     char *unescaped;
886     CURLcode result;
887 
888     LDAP_TRACE(("DN '%s'\n", dn));
889 
890     /* Unescape the DN */
891     result = Curl_urldecode(dn, 0, &unescaped, NULL, REJECT_ZERO);
892     if(result) {
893       rc = LDAP_NO_MEMORY;
894 
895       goto quit;
896     }
897 
898 #if defined(USE_WIN32_LDAP)
899     /* Convert the unescaped string to a tchar */
900     ludp->lud_dn = curlx_convert_UTF8_to_tchar(unescaped);
901 
902     /* Free the unescaped string as we are done with it */
903     free(unescaped);
904 
905     if(!ludp->lud_dn) {
906       rc = LDAP_NO_MEMORY;
907 
908       goto quit;
909     }
910 #else
911     ludp->lud_dn = unescaped;
912 #endif
913   }
914 
915   p = q;
916   if(!p)
917     goto quit;
918 
919   /* Parse the attributes. skip "??" */
920   q = strchr(p, '?');
921   if(q)
922     *q++ = '\0';
923 
924   if(*p) {
925     char **attributes;
926     size_t count = 0;
927 
928     /* Split the string into an array of attributes */
929     if(!split_str(p, &attributes, &count)) {
930       rc = LDAP_NO_MEMORY;
931 
932       goto quit;
933     }
934 
935     /* Allocate our array (+1 for the NULL entry) */
936 #if defined(USE_WIN32_LDAP)
937     ludp->lud_attrs = calloc(count + 1, sizeof(TCHAR *));
938 #else
939     ludp->lud_attrs = calloc(count + 1, sizeof(char *));
940 #endif
941     if(!ludp->lud_attrs) {
942       free(attributes);
943 
944       rc = LDAP_NO_MEMORY;
945 
946       goto quit;
947     }
948 
949     for(i = 0; i < count; i++) {
950       char *unescaped;
951       CURLcode result;
952 
953       LDAP_TRACE(("attr[%zu] '%s'\n", i, attributes[i]));
954 
955       /* Unescape the attribute */
956       result = Curl_urldecode(attributes[i], 0, &unescaped, NULL,
957                               REJECT_ZERO);
958       if(result) {
959         free(attributes);
960 
961         rc = LDAP_NO_MEMORY;
962 
963         goto quit;
964       }
965 
966 #if defined(USE_WIN32_LDAP)
967       /* Convert the unescaped string to a tchar */
968       ludp->lud_attrs[i] = curlx_convert_UTF8_to_tchar(unescaped);
969 
970       /* Free the unescaped string as we are done with it */
971       free(unescaped);
972 
973       if(!ludp->lud_attrs[i]) {
974         free(attributes);
975 
976         rc = LDAP_NO_MEMORY;
977 
978         goto quit;
979       }
980 #else
981       ludp->lud_attrs[i] = unescaped;
982 #endif
983 
984       ludp->lud_attrs_dups++;
985     }
986 
987     free(attributes);
988   }
989 
990   p = q;
991   if(!p)
992     goto quit;
993 
994   /* Parse the scope. skip "??" */
995   q = strchr(p, '?');
996   if(q)
997     *q++ = '\0';
998 
999   if(*p) {
1000     ludp->lud_scope = str2scope(p);
1001     if(ludp->lud_scope == -1) {
1002       rc = LDAP_INVALID_SYNTAX;
1003 
1004       goto quit;
1005     }
1006     LDAP_TRACE(("scope %d\n", ludp->lud_scope));
1007   }
1008 
1009   p = q;
1010   if(!p)
1011     goto quit;
1012 
1013   /* Parse the filter */
1014   q = strchr(p, '?');
1015   if(q)
1016     *q++ = '\0';
1017 
1018   if(*p) {
1019     char *filter = p;
1020     char *unescaped;
1021     CURLcode result;
1022 
1023     LDAP_TRACE(("filter '%s'\n", filter));
1024 
1025     /* Unescape the filter */
1026     result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_ZERO);
1027     if(result) {
1028       rc = LDAP_NO_MEMORY;
1029 
1030       goto quit;
1031     }
1032 
1033 #if defined(USE_WIN32_LDAP)
1034     /* Convert the unescaped string to a tchar */
1035     ludp->lud_filter = curlx_convert_UTF8_to_tchar(unescaped);
1036 
1037     /* Free the unescaped string as we are done with it */
1038     free(unescaped);
1039 
1040     if(!ludp->lud_filter) {
1041       rc = LDAP_NO_MEMORY;
1042 
1043       goto quit;
1044     }
1045 #else
1046     ludp->lud_filter = unescaped;
1047 #endif
1048   }
1049 
1050   p = q;
1051   if(p && !*p) {
1052     rc = LDAP_INVALID_SYNTAX;
1053 
1054     goto quit;
1055   }
1056 
1057 quit:
1058   free(path);
1059   free(query);
1060 
1061   return rc;
1062 }
1063 
_ldap_url_parse(struct Curl_easy * data,const struct connectdata * conn,LDAPURLDesc ** ludpp)1064 static int _ldap_url_parse(struct Curl_easy *data,
1065                            const struct connectdata *conn,
1066                            LDAPURLDesc **ludpp)
1067 {
1068   LDAPURLDesc *ludp = calloc(1, sizeof(*ludp));
1069   int rc;
1070 
1071   *ludpp = NULL;
1072   if(!ludp)
1073     return LDAP_NO_MEMORY;
1074 
1075   rc = _ldap_url_parse2(data, conn, ludp);
1076   if(rc != LDAP_SUCCESS) {
1077     _ldap_free_urldesc(ludp);
1078     ludp = NULL;
1079   }
1080   *ludpp = ludp;
1081   return (rc);
1082 }
1083 
_ldap_free_urldesc(LDAPURLDesc * ludp)1084 static void _ldap_free_urldesc(LDAPURLDesc *ludp)
1085 {
1086   if(!ludp)
1087     return;
1088 
1089 #if defined(USE_WIN32_LDAP)
1090   curlx_unicodefree(ludp->lud_dn);
1091   curlx_unicodefree(ludp->lud_filter);
1092 #else
1093   free(ludp->lud_dn);
1094   free(ludp->lud_filter);
1095 #endif
1096 
1097   if(ludp->lud_attrs) {
1098     size_t i;
1099     for(i = 0; i < ludp->lud_attrs_dups; i++) {
1100 #if defined(USE_WIN32_LDAP)
1101       curlx_unicodefree(ludp->lud_attrs[i]);
1102 #else
1103       free(ludp->lud_attrs[i]);
1104 #endif
1105     }
1106     free(ludp->lud_attrs);
1107   }
1108 
1109   free(ludp);
1110 }
1111 #endif  /* !HAVE_LDAP_URL_PARSE */
1112 #endif  /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */
1113