First commit of David McCreedy's EBCDIC and TPF changes.
[platform/upstream/curl.git] / lib / ldap.c
1 /***************************************************************************
2  *                      _   _ ____  _
3  *  Project         ___| | | |  _ \| |
4  *                 / __| | | | |_) | |
5  *                | (__| |_| |  _ <| |___
6  *                \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2005, 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 HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36 #ifdef HAVE_SYS_STAT_H
37 #include <sys/stat.h>
38 #endif
39 #include <errno.h>
40
41 #if defined(WIN32)
42 # include <malloc.h>
43 # include <winldap.h>
44 #endif
45
46 #ifdef HAVE_UNISTD_H
47 # include <unistd.h>
48 #endif
49
50 #ifdef HAVE_DLFCN_H
51 # include <dlfcn.h>
52 #endif
53
54 #include "urldata.h"
55 #include <curl/curl.h>
56 #include "sendf.h"
57 #include "escape.h"
58 #include "transfer.h"
59 #include "strequal.h"
60 #include "strtok.h"
61 #include "ldap.h"
62 #include "memory.h"
63 #include "base64.h"
64
65 #define _MPRINTF_REPLACE /* use our functions only */
66 #include <curl/mprintf.h>
67
68 #include "memdebug.h"
69
70 /* WLdap32.dll functions are *not* stdcall. Must call these via __cdecl
71  * pointers in case libcurl was compiled as fastcall (cl -Gr). Watcom
72  * uses fastcall by default.
73  */
74 #if !defined(WIN32) && !defined(__cdecl)
75 #define __cdecl
76 #endif
77
78 #ifndef LDAP_SIZELIMIT_EXCEEDED
79 #define LDAP_SIZELIMIT_EXCEEDED 4
80 #endif
81 #ifndef LDAP_VERSION2
82 #define LDAP_VERSION2 2
83 #endif
84 #ifndef LDAP_VERSION3
85 #define LDAP_VERSION3 3
86 #endif
87 #ifndef LDAP_OPT_PROTOCOL_VERSION
88 #define LDAP_OPT_PROTOCOL_VERSION 0x0011
89 #endif
90
91 #define DLOPEN_MODE   RTLD_LAZY  /*! assume all dlopen() implementations have
92                                    this */
93
94 #if defined(RTLD_LAZY_GLOBAL)    /* It turns out some systems use this: */
95 # undef  DLOPEN_MODE
96 # define DLOPEN_MODE  RTLD_LAZY_GLOBAL
97 #elif defined(RTLD_GLOBAL)
98 # undef  DLOPEN_MODE
99 # define DLOPEN_MODE  (RTLD_LAZY | RTLD_GLOBAL)
100 #endif
101
102 #define DYNA_GET_FUNCTION(type, fnc) do { \
103           (fnc) = (type)DynaGetFunction(#fnc); \
104           if ((fnc) == NULL) \
105              return CURLE_FUNCTION_NOT_FOUND; \
106         } while (0)
107
108 /*! CygWin etc. configure could set these, but we don't want it.
109  * Must use WLdap32.dll code.
110  */
111 #if defined(WIN32)
112 #undef HAVE_DLOPEN
113 #undef HAVE_LIBDL
114 #endif
115
116 typedef void * (*dynafunc)(void *input);
117
118 /***********************************************************************
119  */
120 #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL) || defined(WIN32)
121 static void *libldap = NULL;
122 #if defined(DL_LBER_FILE)
123 static void *liblber = NULL;
124 #endif
125 #endif
126
127 struct bv {
128   unsigned long bv_len;
129   char  *bv_val;
130 };
131
132 static int DynaOpen(const char **mod_name)
133 {
134 #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL)
135   if (libldap == NULL) {
136     /*
137      * libldap.so can normally resolve its dependency on liblber.so
138      * automatically, but in broken installation it does not so
139      * handle it here by opening liblber.so as global.
140      */
141 #ifdef DL_LBER_FILE
142     *mod_name = DL_LBER_FILE;
143     liblber = dlopen(*mod_name, DLOPEN_MODE);
144     if (!liblber)
145       return 0;
146 #endif
147
148     /* Assume loading libldap.so will fail if loading of liblber.so failed
149      */
150     *mod_name = DL_LDAP_FILE;
151     libldap = dlopen(*mod_name, RTLD_LAZY);
152   }
153   return (libldap != NULL);
154
155 #elif defined(WIN32)
156   *mod_name = DL_LDAP_FILE;
157   if (!libldap)
158     libldap = (void*)LoadLibrary(*mod_name);
159   return (libldap != NULL);
160
161 #else
162   *mod_name = "";
163   return (0);
164 #endif
165 }
166
167 static void DynaClose(void)
168 {
169 #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL)
170   if (libldap) {
171     dlclose(libldap);
172     libldap=NULL;
173   }
174 #ifdef DL_LBER_FILE
175   if (liblber) {
176     dlclose(liblber);
177     liblber=NULL;
178   }
179 #endif
180 #elif defined(WIN32)
181   if (libldap) {
182     FreeLibrary ((HMODULE)libldap);
183     libldap = NULL;
184   }
185 #endif
186 }
187
188 static dynafunc DynaGetFunction(const char *name)
189 {
190   dynafunc func = (dynafunc)NULL;
191
192 #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL)
193   if (libldap) {
194     /* This typecast magic below was brought by Joe Halpin. In ISO C, you
195      * cannot typecast a data pointer to a function pointer, but that's
196      * exactly what we need to do here to avoid compiler warnings on picky
197      * compilers! */
198     *(void**) (&func) = dlsym(libldap, name);
199   }
200 #elif defined(WIN32)
201   if (libldap) {
202     func = (dynafunc)GetProcAddress((HINSTANCE)libldap, name);
203   }
204 #else
205   (void) name;
206 #endif
207   return func;
208 }
209
210 /***********************************************************************
211  */
212 typedef struct ldap_url_desc {
213     struct ldap_url_desc *lud_next;
214     char   *lud_scheme;
215     char   *lud_host;
216     int     lud_port;
217     char   *lud_dn;
218     char  **lud_attrs;
219     int     lud_scope;
220     char   *lud_filter;
221     char  **lud_exts;
222     int     lud_crit_exts;
223 } LDAPURLDesc;
224
225 #ifdef WIN32
226 static int  _ldap_url_parse (const struct connectdata *conn,
227                              LDAPURLDesc **ludp);
228 static void _ldap_free_urldesc (LDAPURLDesc *ludp);
229
230 static void (*ldap_free_urldesc)(LDAPURLDesc *) = _ldap_free_urldesc;
231 #endif
232
233 #ifdef DEBUG_LDAP
234   #define LDAP_TRACE(x)   do { \
235                             _ldap_trace ("%u: ", __LINE__); \
236                             _ldap_trace x; \
237                           } while (0)
238
239   static void _ldap_trace (const char *fmt, ...);
240 #else
241   #define LDAP_TRACE(x)   ((void)0)
242 #endif
243
244
245 CURLcode Curl_ldap(struct connectdata *conn, bool *done)
246 {
247   CURLcode status = CURLE_OK;
248   int rc = 0;
249 #ifndef WIN32
250   int    (*ldap_url_parse)(char *, LDAPURLDesc **);
251   void   (*ldap_free_urldesc)(void *);
252 #endif
253   void  *(__cdecl *ldap_init)(char *, int);
254   int    (__cdecl *ldap_simple_bind_s)(void *, char *, char *);
255   int    (__cdecl *ldap_unbind_s)(void *);
256   int    (__cdecl *ldap_search_s)(void *, char *, int, char *, char **,
257                                   int, void **);
258   void  *(__cdecl *ldap_first_entry)(void *, void *);
259   void  *(__cdecl *ldap_next_entry)(void *, void *);
260   char  *(__cdecl *ldap_err2string)(int);
261   char  *(__cdecl *ldap_get_dn)(void *, void *);
262   char  *(__cdecl *ldap_first_attribute)(void *, void *, void **);
263   char  *(__cdecl *ldap_next_attribute)(void *, void *, void *);
264   void **(__cdecl *ldap_get_values_len)(void *, void *, const char *);
265   void   (__cdecl *ldap_value_free_len)(void **);
266   void   (__cdecl *ldap_memfree)(void *);
267   void   (__cdecl *ber_free)(void *, int);
268   int    (__cdecl *ldap_set_option)(void *, int, void *);
269
270   void *server;
271   LDAPURLDesc *ludp = NULL;
272   const char *mod_name;
273   void *result;
274   void *entryIterator;     /*! type should be 'LDAPMessage *' */
275   int num = 0;
276   struct SessionHandle *data=conn->data;
277   int ldap_proto;
278   char *val_b64;
279   size_t val_b64_sz;
280
281   *done = TRUE; /* unconditionally */
282   infof(data, "LDAP local: %s\n", data->change.url);
283
284   if (!DynaOpen(&mod_name)) {
285     failf(data, "The %s LDAP library/libraries couldn't be opened", mod_name);
286     return CURLE_LIBRARY_NOT_FOUND;
287   }
288
289   /* The types are needed because ANSI C distinguishes between
290    * pointer-to-object (data) and pointer-to-function.
291    */
292   DYNA_GET_FUNCTION(void *(__cdecl *)(char *, int), ldap_init);
293   DYNA_GET_FUNCTION(int (__cdecl *)(void *, char *, char *),
294                     ldap_simple_bind_s);
295   DYNA_GET_FUNCTION(int (__cdecl *)(void *), ldap_unbind_s);
296 #ifndef WIN32
297   DYNA_GET_FUNCTION(int (*)(char *, LDAPURLDesc **), ldap_url_parse);
298   DYNA_GET_FUNCTION(void (*)(void *), ldap_free_urldesc);
299 #endif
300   DYNA_GET_FUNCTION(int (__cdecl *)(void *, char *, int, char *, char **, int,
301                                     void **), ldap_search_s);
302   DYNA_GET_FUNCTION(void *(__cdecl *)(void *, void *), ldap_first_entry);
303   DYNA_GET_FUNCTION(void *(__cdecl *)(void *, void *), ldap_next_entry);
304   DYNA_GET_FUNCTION(char *(__cdecl *)(int), ldap_err2string);
305   DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *), ldap_get_dn);
306   DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *, void **),
307                     ldap_first_attribute);
308   DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *, void *),
309                     ldap_next_attribute);
310   DYNA_GET_FUNCTION(void **(__cdecl *)(void *, void *, const char *),
311                     ldap_get_values_len);
312   DYNA_GET_FUNCTION(void (__cdecl *)(void **), ldap_value_free_len);
313   DYNA_GET_FUNCTION(void (__cdecl *)(void *), ldap_memfree);
314   DYNA_GET_FUNCTION(void (__cdecl *)(void *, int), ber_free);
315   DYNA_GET_FUNCTION(int (__cdecl *)(void *, int, void *), ldap_set_option);
316
317   server = (*ldap_init)(conn->host.name, (int)conn->port);
318   if (server == NULL) {
319     failf(data, "LDAP local: Cannot connect to %s:%d",
320           conn->host.name, conn->port);
321     status = CURLE_COULDNT_CONNECT;
322     goto quit;
323   }
324
325   ldap_proto = LDAP_VERSION3;
326   (*ldap_set_option)(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
327   rc = (*ldap_simple_bind_s)(server,
328                              conn->bits.user_passwd ? conn->user : NULL,
329                              conn->bits.user_passwd ? conn->passwd : NULL);
330   if (rc != 0) {
331     ldap_proto = LDAP_VERSION2;
332     (*ldap_set_option)(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
333     rc = (*ldap_simple_bind_s)(server,
334                                conn->bits.user_passwd ? conn->user : NULL,
335                                conn->bits.user_passwd ? conn->passwd : NULL);
336   }
337   if (rc != 0) {
338      failf(data, "LDAP local: %s", (*ldap_err2string)(rc));
339      status = CURLE_LDAP_CANNOT_BIND;
340      goto quit;
341   }
342
343 #ifdef WIN32
344   rc = _ldap_url_parse(conn, &ludp);
345 #else
346   rc = (*ldap_url_parse)(data->change.url, &ludp);
347 #endif
348
349   if (rc != 0) {
350      failf(data, "LDAP local: %s", (*ldap_err2string)(rc));
351      status = CURLE_LDAP_INVALID_URL;
352      goto quit;
353   }
354
355   rc = (*ldap_search_s)(server, ludp->lud_dn, ludp->lud_scope,
356                         ludp->lud_filter, ludp->lud_attrs, 0, &result);
357
358   if (rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) {
359     failf(data, "LDAP remote: %s", (*ldap_err2string)(rc));
360     status = CURLE_LDAP_SEARCH_FAILED;
361     goto quit;
362   }
363
364   for(num = 0, entryIterator = (*ldap_first_entry)(server, result);
365       entryIterator;
366       entryIterator = (*ldap_next_entry)(server, entryIterator), num++)
367   {
368     void  *ber = NULL;      /*! is really 'BerElement **' */
369     void  *attribute;       /*! suspicious that this isn't 'const' */
370     char  *dn = (*ldap_get_dn)(server, entryIterator);
371     int i;
372
373     Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4);
374     Curl_client_write(data, CLIENTWRITE_BODY, (char *)dn, 0);
375     Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1);
376
377     for (attribute = (*ldap_first_attribute)(server, entryIterator, &ber);
378          attribute;
379          attribute = (*ldap_next_attribute)(server, entryIterator, ber))
380     {
381       struct bv **vals = (struct bv **)
382         (*ldap_get_values_len)(server, entryIterator, attribute);
383
384       if (vals != NULL)
385       {
386         for (i = 0; (vals[i] != NULL); i++)
387         {
388           Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1);
389           Curl_client_write(data, CLIENTWRITE_BODY, (char *) attribute, 0);
390           Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2);
391           if ((strlen(attribute) > 7) &&
392               (strcmp(";binary",
393                       (char *)attribute +
394                       (strlen((char *)attribute) - 7)) == 0)) {
395             /* Binary attribute, encode to base64. */
396             val_b64_sz = Curl_base64_encode(vals[i]->bv_val, vals[i]->bv_len,
397                                             &val_b64);
398             if (val_b64_sz > 0) {
399               Curl_client_write(data, CLIENTWRITE_BODY, val_b64, val_b64_sz);
400               free(val_b64);
401             }
402           } else
403             Curl_client_write(data, CLIENTWRITE_BODY, vals[i]->bv_val,
404                               vals[i]->bv_len);
405           Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 0);
406         }
407
408         /* Free memory used to store values */
409         (*ldap_value_free_len)((void **)vals);
410       }
411       Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1);
412
413       (*ldap_memfree)(attribute);
414     }
415     (*ldap_memfree)(dn);
416     if (ber)
417        (*ber_free)(ber, 0);
418   }
419
420 quit:
421   LDAP_TRACE (("Received %d entries\n", num));
422   if (rc == LDAP_SIZELIMIT_EXCEEDED)
423      infof(data, "There are more than %d entries\n", num);
424   if (ludp)
425      (*ldap_free_urldesc)(ludp);
426   if (server)
427      (*ldap_unbind_s)(server);
428
429   DynaClose();
430
431   /* no data to transfer */
432   Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
433   conn->bits.close = TRUE;
434
435   return status;
436 }
437
438 #ifdef DEBUG_LDAP
439 static void _ldap_trace (const char *fmt, ...)
440 {
441   static int do_trace = -1;
442   va_list args;
443
444   if (do_trace == -1) {
445     const char *env = getenv("CURL_TRACE");
446     do_trace = (env && atoi(env) > 0);
447   }
448   if (!do_trace)
449     return;
450
451   va_start (args, fmt);
452   vfprintf (stderr, fmt, args);
453   va_end (args);
454 }
455 #endif
456
457 #ifdef WIN32
458 /*
459  * Return scope-value for a scope-string.
460  */
461 static int str2scope (const char *p)
462 {
463   if (!stricmp(p, "one"))
464      return LDAP_SCOPE_ONELEVEL;
465   if (!stricmp(p, "onetree"))
466      return LDAP_SCOPE_ONELEVEL;
467   if (!stricmp(p, "base"))
468      return LDAP_SCOPE_BASE;
469   if (!stricmp(p, "sub"))
470      return LDAP_SCOPE_SUBTREE;
471   if (!stricmp( p, "subtree"))
472      return LDAP_SCOPE_SUBTREE;
473   return (-1);
474 }
475
476 /*
477  * Split 'str' into strings separated by commas.
478  * Note: res[] points into 'str'.
479  */
480 static char **split_str (char *str)
481 {
482   char **res, *lasts, *s;
483   int  i;
484
485   for (i = 2, s = strchr(str,','); s; i++)
486      s = strchr(++s,',');
487
488   res = calloc(i, sizeof(char*));
489   if (!res)
490     return NULL;
491
492   for (i = 0, s = strtok_r(str, ",", &lasts); s;
493        s = strtok_r(NULL, ",", &lasts), i++)
494     res[i] = s;
495   return res;
496 }
497
498 /*
499  * Unescape the LDAP-URL components
500  */
501 static bool unescape_elements (void *data, LDAPURLDesc *ludp)
502 {
503   int i;
504
505   if (ludp->lud_filter) {
506     ludp->lud_filter = curl_easy_unescape(data, ludp->lud_filter, 0);
507     if (!ludp->lud_filter)
508        return (FALSE);
509   }
510
511   for (i = 0; ludp->lud_attrs && ludp->lud_attrs[i]; i++) {
512     ludp->lud_attrs[i] = curl_easy_unescape(data, ludp->lud_attrs[i], 0);
513     if (!ludp->lud_attrs[i])
514        return (FALSE);
515   }
516
517   for (i = 0; ludp->lud_exts && ludp->lud_exts[i]; i++) {
518     ludp->lud_exts[i] = curl_easy_unescape(data, ludp->lud_exts[i], 0);
519     if (!ludp->lud_exts[i])
520        return (FALSE);
521   }
522
523   if (ludp->lud_dn) {
524     char *dn = ludp->lud_dn;
525     char *new_dn = curl_easy_unescape(data, dn, 0);
526
527     free(dn);
528     ludp->lud_dn = new_dn;
529     if (!new_dn)
530        return (FALSE);
531   }
532   return (TRUE);
533 }
534
535 /*
536  * Break apart the pieces of an LDAP URL.
537  * Syntax:
538  *   ldap://<hostname>:<port>/<base_dn>?<attributes>?<scope>?<filter>?<ext>
539  *
540  * <hostname> already known from 'conn->host.name'.
541  * <port>     already known from 'conn->remote_port'.
542  * extract the rest from 'conn->path+1'. All fields are optional. e.g.
543  *   ldap://<hostname>:<port>/?<attributes>?<scope>?<filter> yields ludp->lud_dn = "".
544  *
545  * Ref. http://developer.netscape.com/docs/manuals/dirsdk/csdk30/url.htm#2831915
546  */
547 static int _ldap_url_parse2 (const struct connectdata *conn, LDAPURLDesc *ludp)
548 {
549   char *p, *q;
550   int i;
551
552   if (!conn->path || conn->path[0] != '/' ||
553       !checkprefix(conn->protostr, conn->data->change.url))
554      return LDAP_INVALID_SYNTAX;
555
556   ludp->lud_scope = LDAP_SCOPE_BASE;
557   ludp->lud_port  = conn->remote_port;
558   ludp->lud_host  = conn->host.name;
559
560   /* parse DN (Distinguished Name).
561    */
562   ludp->lud_dn = strdup(conn->path+1);
563   if (!ludp->lud_dn)
564      return LDAP_NO_MEMORY;
565
566   p = strchr(ludp->lud_dn, '?');
567   LDAP_TRACE (("DN '%.*s'\n", p ? (size_t)(p-ludp->lud_dn) :
568                strlen(ludp->lud_dn), ludp->lud_dn));
569
570   if (!p)
571      goto success;
572
573   *p++ = '\0';
574
575   /* parse attributes. skip "??".
576    */
577   q = strchr(p, '?');
578   if (q)
579      *q++ = '\0';
580
581   if (*p && *p != '?') {
582     ludp->lud_attrs = split_str(p);
583     if (!ludp->lud_attrs)
584        return LDAP_NO_MEMORY;
585
586     for (i = 0; ludp->lud_attrs[i]; i++)
587         LDAP_TRACE (("attr[%d] '%s'\n", i, ludp->lud_attrs[i]));
588   }
589
590   p = q;
591   if (!p)
592      goto success;
593
594   /* parse scope. skip "??"
595    */
596   q = strchr(p, '?');
597   if (q)
598      *q++ = '\0';
599
600   if (*p && *p != '?') {
601     ludp->lud_scope = str2scope(p);
602     if (ludp->lud_scope == -1)
603        return LDAP_INVALID_SYNTAX;
604     LDAP_TRACE (("scope %d\n", ludp->lud_scope));
605   }
606
607   p = q;
608   if (!p)
609      goto success;
610
611   /* parse filter
612    */
613   q = strchr(p, '?');
614   if (q)
615      *q++ = '\0';
616   if (!*p)
617      return LDAP_INVALID_SYNTAX;
618
619   ludp->lud_filter = p;
620   LDAP_TRACE (("filter '%s'\n", ludp->lud_filter));
621
622   p = q;
623   if (!p)
624      goto success;
625
626   /* parse extensions
627    */
628   ludp->lud_exts = split_str(p);
629   if (!ludp->lud_exts)
630      return LDAP_NO_MEMORY;
631
632   for (i = 0; ludp->lud_exts[i]; i++)
633       LDAP_TRACE (("exts[%d] '%s'\n", i, ludp->lud_exts[i]));
634
635 success:
636   if (!unescape_elements(conn->data, ludp))
637      return LDAP_NO_MEMORY;
638   return LDAP_SUCCESS;
639 }
640
641 static int _ldap_url_parse (const struct connectdata *conn,
642                             LDAPURLDesc **ludpp)
643 {
644   LDAPURLDesc *ludp = calloc(sizeof(*ludp), 1);
645   int rc;
646
647   *ludpp = NULL;
648   if (!ludp)
649      return LDAP_NO_MEMORY;
650
651   rc = _ldap_url_parse2 (conn, ludp);
652   if (rc != LDAP_SUCCESS) {
653     _ldap_free_urldesc(ludp);
654     ludp = NULL;
655   }
656   *ludpp = ludp;
657   return (rc);
658 }
659
660 static void _ldap_free_urldesc (LDAPURLDesc *ludp)
661 {
662   int i;
663
664   if (!ludp)
665      return;
666
667   if (ludp->lud_dn)
668      free(ludp->lud_dn);
669
670   if (ludp->lud_filter)
671      free(ludp->lud_filter);
672
673   if (ludp->lud_attrs) {
674     for (i = 0; ludp->lud_attrs[i]; i++)
675        free(ludp->lud_attrs[i]);
676     free(ludp->lud_attrs);
677   }
678
679   if (ludp->lud_exts) {
680     for (i = 0; ludp->lud_exts[i]; i++)
681        free(ludp->lud_exts[i]);
682     free(ludp->lud_exts);
683   }
684   free (ludp);
685 }
686 #endif  /* WIN32 */
687 #endif  /* CURL_DISABLE_LDAP */