/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: ldap.c,v 1.69 2007-03-12 05:09:25 yangtse Exp $ ***************************************************************************/ #include "setup.h" #ifndef CURL_DISABLE_LDAP /* -- WIN32 approved -- */ #include #include #include #include #include #ifdef NEED_MALLOC_H #include #endif #include #if defined(WIN32) # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_DLFCN_H # include #endif #include "urldata.h" #include #include "sendf.h" #include "escape.h" #include "transfer.h" #include "strequal.h" #include "strtok.h" #include "ldap.h" #include "memory.h" #include "base64.h" #define _MPRINTF_REPLACE /* use our functions only */ #include #include "memdebug.h" /* WLdap32.dll functions are *not* stdcall. Must call these via __cdecl * pointers in case libcurl was compiled as fastcall (cl -Gr). Watcom * uses fastcall by default. */ #if !defined(WIN32) && !defined(__cdecl) #define __cdecl #endif #ifndef LDAP_SIZELIMIT_EXCEEDED #define LDAP_SIZELIMIT_EXCEEDED 4 #endif #ifndef LDAP_VERSION2 #define LDAP_VERSION2 2 #endif #ifndef LDAP_VERSION3 #define LDAP_VERSION3 3 #endif #ifndef LDAP_OPT_PROTOCOL_VERSION #define LDAP_OPT_PROTOCOL_VERSION 0x0011 #endif #define DLOPEN_MODE RTLD_LAZY /*! assume all dlopen() implementations have this */ #if defined(RTLD_LAZY_GLOBAL) /* It turns out some systems use this: */ # undef DLOPEN_MODE # define DLOPEN_MODE RTLD_LAZY_GLOBAL #elif defined(RTLD_GLOBAL) # undef DLOPEN_MODE # define DLOPEN_MODE (RTLD_LAZY | RTLD_GLOBAL) #endif #define DYNA_GET_FUNCTION(type, fnc) do { \ (fnc) = (type)DynaGetFunction(#fnc); \ if ((fnc) == NULL) \ return CURLE_FUNCTION_NOT_FOUND; \ } while (0) /*! CygWin etc. configure could set these, but we don't want it. * Must use WLdap32.dll code. */ #if defined(WIN32) #undef HAVE_DLOPEN #undef HAVE_LIBDL #endif /* * We use this ZERO_NULL to avoid picky compiler warnings, * when assigning a NULL pointer to a function pointer var. */ #define ZERO_NULL 0 typedef void * (*dynafunc)(void *input); /*********************************************************************** */ #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL) || defined(WIN32) static void *libldap = NULL; #if defined(DL_LBER_FILE) static void *liblber = NULL; #endif #endif struct bv { unsigned long bv_len; char *bv_val; }; static int DynaOpen(const char **mod_name) { #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL) if (libldap == NULL) { /* * libldap.so can normally resolve its dependency on liblber.so * automatically, but in broken installation it does not so * handle it here by opening liblber.so as global. */ #ifdef DL_LBER_FILE *mod_name = DL_LBER_FILE; liblber = dlopen(*mod_name, DLOPEN_MODE); if (!liblber) return 0; #endif /* Assume loading libldap.so will fail if loading of liblber.so failed */ *mod_name = DL_LDAP_FILE; libldap = dlopen(*mod_name, RTLD_LAZY); } return (libldap != NULL); #elif defined(WIN32) *mod_name = DL_LDAP_FILE; if (!libldap) libldap = (void*)LoadLibrary(*mod_name); return (libldap != NULL); #else *mod_name = ""; return (0); #endif } static void DynaClose(void) { #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL) if (libldap) { dlclose(libldap); libldap=NULL; } #ifdef DL_LBER_FILE if (liblber) { dlclose(liblber); liblber=NULL; } #endif #elif defined(WIN32) if (libldap) { FreeLibrary ((HMODULE)libldap); libldap = NULL; } #endif } static dynafunc DynaGetFunction(const char *name) { dynafunc func = (dynafunc)ZERO_NULL; #if defined(HAVE_DLOPEN) || defined(HAVE_LIBDL) if (libldap) { /* This typecast magic below was brought by Joe Halpin. In ISO C, you * cannot typecast a data pointer to a function pointer, but that's * exactly what we need to do here to avoid compiler warnings on picky * compilers! */ *(void**) (&func) = dlsym(libldap, name); } #ifdef DL_LBER_FILE if (!func && liblber) { *(void**) (&func) = dlsym(liblber, name); } #endif #elif defined(WIN32) if (libldap) { func = (dynafunc)GetProcAddress((HINSTANCE)libldap, name); } #else (void) name; #endif return func; } /*********************************************************************** */ typedef struct ldap_url_desc { struct ldap_url_desc *lud_next; char *lud_scheme; char *lud_host; int lud_port; char *lud_dn; char **lud_attrs; int lud_scope; char *lud_filter; char **lud_exts; int lud_crit_exts; } LDAPURLDesc; #ifdef WIN32 static int _ldap_url_parse (const struct connectdata *conn, LDAPURLDesc **ludp); static void _ldap_free_urldesc (LDAPURLDesc *ludp); static void (*ldap_free_urldesc)(LDAPURLDesc *) = _ldap_free_urldesc; #endif #ifdef DEBUG_LDAP #define LDAP_TRACE(x) do { \ _ldap_trace ("%u: ", __LINE__); \ _ldap_trace x; \ } while (0) static void _ldap_trace (const char *fmt, ...); #else #define LDAP_TRACE(x) ((void)0) #endif CURLcode Curl_ldap(struct connectdata *conn, bool *done) { CURLcode status = CURLE_OK; int rc = 0; #ifndef WIN32 int (*ldap_url_parse)(char *, LDAPURLDesc **); void (*ldap_free_urldesc)(void *); #endif void *(__cdecl *ldap_init)(char *, int); int (__cdecl *ldap_simple_bind_s)(void *, char *, char *); int (__cdecl *ldap_unbind_s)(void *); int (__cdecl *ldap_search_s)(void *, char *, int, char *, char **, int, void **); void *(__cdecl *ldap_first_entry)(void *, void *); void *(__cdecl *ldap_next_entry)(void *, void *); char *(__cdecl *ldap_err2string)(int); char *(__cdecl *ldap_get_dn)(void *, void *); char *(__cdecl *ldap_first_attribute)(void *, void *, void **); char *(__cdecl *ldap_next_attribute)(void *, void *, void *); void **(__cdecl *ldap_get_values_len)(void *, void *, const char *); void (__cdecl *ldap_value_free_len)(void **); void (__cdecl *ldap_memfree)(void *); void (__cdecl *ber_free)(void *, int); int (__cdecl *ldap_set_option)(void *, int, void *); void *server; LDAPURLDesc *ludp = NULL; const char *mod_name; void *result; void *entryIterator; /*! type should be 'LDAPMessage *' */ int num = 0; struct SessionHandle *data=conn->data; int ldap_proto; char *val_b64; size_t val_b64_sz; *done = TRUE; /* unconditionally */ infof(data, "LDAP local: %s\n", data->change.url); if (!DynaOpen(&mod_name)) { failf(data, "The %s LDAP library/libraries couldn't be opened", mod_name); return CURLE_LIBRARY_NOT_FOUND; } /* The types are needed because ANSI C distinguishes between * pointer-to-object (data) and pointer-to-function. */ DYNA_GET_FUNCTION(void *(__cdecl *)(char *, int), ldap_init); DYNA_GET_FUNCTION(int (__cdecl *)(void *, char *, char *), ldap_simple_bind_s); DYNA_GET_FUNCTION(int (__cdecl *)(void *), ldap_unbind_s); #ifndef WIN32 DYNA_GET_FUNCTION(int (*)(char *, LDAPURLDesc **), ldap_url_parse); DYNA_GET_FUNCTION(void (*)(void *), ldap_free_urldesc); #endif DYNA_GET_FUNCTION(int (__cdecl *)(void *, char *, int, char *, char **, int, void **), ldap_search_s); DYNA_GET_FUNCTION(void *(__cdecl *)(void *, void *), ldap_first_entry); DYNA_GET_FUNCTION(void *(__cdecl *)(void *, void *), ldap_next_entry); DYNA_GET_FUNCTION(char *(__cdecl *)(int), ldap_err2string); DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *), ldap_get_dn); DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *, void **), ldap_first_attribute); DYNA_GET_FUNCTION(char *(__cdecl *)(void *, void *, void *), ldap_next_attribute); DYNA_GET_FUNCTION(void **(__cdecl *)(void *, void *, const char *), ldap_get_values_len); DYNA_GET_FUNCTION(void (__cdecl *)(void **), ldap_value_free_len); DYNA_GET_FUNCTION(void (__cdecl *)(void *), ldap_memfree); DYNA_GET_FUNCTION(void (__cdecl *)(void *, int), ber_free); DYNA_GET_FUNCTION(int (__cdecl *)(void *, int, void *), ldap_set_option); server = (*ldap_init)(conn->host.name, (int)conn->port); if (server == NULL) { failf(data, "LDAP local: Cannot connect to %s:%d", conn->host.name, conn->port); status = CURLE_COULDNT_CONNECT; goto quit; } ldap_proto = LDAP_VERSION3; (*ldap_set_option)(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); rc = (*ldap_simple_bind_s)(server, conn->bits.user_passwd ? conn->user : NULL, conn->bits.user_passwd ? conn->passwd : NULL); if (rc != 0) { ldap_proto = LDAP_VERSION2; (*ldap_set_option)(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); rc = (*ldap_simple_bind_s)(server, conn->bits.user_passwd ? conn->user : NULL, conn->bits.user_passwd ? conn->passwd : NULL); } if (rc != 0) { failf(data, "LDAP local: %s", (*ldap_err2string)(rc)); status = CURLE_LDAP_CANNOT_BIND; goto quit; } #ifdef WIN32 rc = _ldap_url_parse(conn, &ludp); #else rc = (*ldap_url_parse)(data->change.url, &ludp); #endif if (rc != 0) { failf(data, "LDAP local: %s", (*ldap_err2string)(rc)); status = CURLE_LDAP_INVALID_URL; goto quit; } rc = (*ldap_search_s)(server, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter, ludp->lud_attrs, 0, &result); if (rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) { failf(data, "LDAP remote: %s", (*ldap_err2string)(rc)); status = CURLE_LDAP_SEARCH_FAILED; goto quit; } for(num = 0, entryIterator = (*ldap_first_entry)(server, result); entryIterator; entryIterator = (*ldap_next_entry)(server, entryIterator), num++) { void *ber = NULL; /*! is really 'BerElement **' */ void *attribute; /*! suspicious that this isn't 'const' */ char *dn = (*ldap_get_dn)(server, entryIterator); int i; Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); Curl_client_write(conn, CLIENTWRITE_BODY, (char *)dn, 0); Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); for (attribute = (*ldap_first_attribute)(server, entryIterator, &ber); attribute; attribute = (*ldap_next_attribute)(server, entryIterator, ber)) { struct bv **vals = (struct bv **) (*ldap_get_values_len)(server, entryIterator, attribute); if (vals != NULL) { for (i = 0; (vals[i] != NULL); i++) { Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); Curl_client_write(conn, CLIENTWRITE_BODY, (char *) attribute, 0); Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2); if ((strlen(attribute) > 7) && (strcmp(";binary", (char *)attribute + (strlen((char *)attribute) - 7)) == 0)) { /* Binary attribute, encode to base64. */ val_b64_sz = Curl_base64_encode(conn->data, vals[i]->bv_val, vals[i]->bv_len, &val_b64); if (val_b64_sz > 0) { Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); free(val_b64); } } else Curl_client_write(conn, CLIENTWRITE_BODY, vals[i]->bv_val, vals[i]->bv_len); Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); } /* Free memory used to store values */ (*ldap_value_free_len)((void **)vals); } Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); (*ldap_memfree)(attribute); } (*ldap_memfree)(dn); if (ber) (*ber_free)(ber, 0); } quit: LDAP_TRACE (("Received %d entries\n", num)); if (rc == LDAP_SIZELIMIT_EXCEEDED) infof(data, "There are more than %d entries\n", num); if (ludp) (*ldap_free_urldesc)(ludp); if (server) (*ldap_unbind_s)(server); DynaClose(); /* no data to transfer */ Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); conn->bits.close = TRUE; return status; } #ifdef DEBUG_LDAP static void _ldap_trace (const char *fmt, ...) { static int do_trace = -1; va_list args; if (do_trace == -1) { const char *env = getenv("CURL_TRACE"); do_trace = (env && atoi(env) > 0); } if (!do_trace) return; va_start (args, fmt); vfprintf (stderr, fmt, args); va_end (args); } #endif #ifdef WIN32 /* * Return scope-value for a scope-string. */ static int str2scope (const char *p) { if (!stricmp(p, "one")) return LDAP_SCOPE_ONELEVEL; if (!stricmp(p, "onetree")) return LDAP_SCOPE_ONELEVEL; if (!stricmp(p, "base")) return LDAP_SCOPE_BASE; if (!stricmp(p, "sub")) return LDAP_SCOPE_SUBTREE; if (!stricmp( p, "subtree")) return LDAP_SCOPE_SUBTREE; return (-1); } /* * Split 'str' into strings separated by commas. * Note: res[] points into 'str'. */ static char **split_str (char *str) { char **res, *lasts, *s; int i; for (i = 2, s = strchr(str,','); s; i++) s = strchr(++s,','); res = calloc(i, sizeof(char*)); if (!res) return NULL; for (i = 0, s = strtok_r(str, ",", &lasts); s; s = strtok_r(NULL, ",", &lasts), i++) res[i] = s; return res; } /* * Unescape the LDAP-URL components */ static bool unescape_elements (void *data, LDAPURLDesc *ludp) { int i; if (ludp->lud_filter) { ludp->lud_filter = curl_easy_unescape(data, ludp->lud_filter, 0, NULL); if (!ludp->lud_filter) return (FALSE); } for (i = 0; ludp->lud_attrs && ludp->lud_attrs[i]; i++) { ludp->lud_attrs[i] = curl_easy_unescape(data, ludp->lud_attrs[i], 0, NULL); if (!ludp->lud_attrs[i]) return (FALSE); } for (i = 0; ludp->lud_exts && ludp->lud_exts[i]; i++) { ludp->lud_exts[i] = curl_easy_unescape(data, ludp->lud_exts[i], 0, NULL); if (!ludp->lud_exts[i]) return (FALSE); } if (ludp->lud_dn) { char *dn = ludp->lud_dn; char *new_dn = curl_easy_unescape(data, dn, 0, NULL); free(dn); ludp->lud_dn = new_dn; if (!new_dn) return (FALSE); } return (TRUE); } /* * Break apart the pieces of an LDAP URL. * Syntax: * ldap://:/???? * * already known from 'conn->host.name'. * already known from 'conn->remote_port'. * extract the rest from 'conn->data->reqdata.path+1'. All fields are optional. * e.g. * ldap://:/??? * yields ludp->lud_dn = "". * * Ref. http://developer.netscape.com/docs/manuals/dirsdk/csdk30/url.htm#2831915 */ static int _ldap_url_parse2 (const struct connectdata *conn, LDAPURLDesc *ludp) { char *p, *q; int i; if (!conn->data || !conn->data->reqdata.path || conn->data->reqdata.path[0] != '/' || !checkprefix(conn->protostr, conn->data->change.url)) return LDAP_INVALID_SYNTAX; ludp->lud_scope = LDAP_SCOPE_BASE; ludp->lud_port = conn->remote_port; ludp->lud_host = conn->host.name; /* parse DN (Distinguished Name). */ ludp->lud_dn = strdup(conn->data->reqdata.path+1); if (!ludp->lud_dn) return LDAP_NO_MEMORY; p = strchr(ludp->lud_dn, '?'); LDAP_TRACE (("DN '%.*s'\n", p ? (size_t)(p-ludp->lud_dn) : strlen(ludp->lud_dn), ludp->lud_dn)); if (!p) goto success; *p++ = '\0'; /* parse attributes. skip "??". */ q = strchr(p, '?'); if (q) *q++ = '\0'; if (*p && *p != '?') { ludp->lud_attrs = split_str(p); if (!ludp->lud_attrs) return LDAP_NO_MEMORY; for (i = 0; ludp->lud_attrs[i]; i++) LDAP_TRACE (("attr[%d] '%s'\n", i, ludp->lud_attrs[i])); } p = q; if (!p) goto success; /* parse scope. skip "??" */ q = strchr(p, '?'); if (q) *q++ = '\0'; if (*p && *p != '?') { ludp->lud_scope = str2scope(p); if (ludp->lud_scope == -1) return LDAP_INVALID_SYNTAX; LDAP_TRACE (("scope %d\n", ludp->lud_scope)); } p = q; if (!p) goto success; /* parse filter */ q = strchr(p, '?'); if (q) *q++ = '\0'; if (!*p) return LDAP_INVALID_SYNTAX; ludp->lud_filter = p; LDAP_TRACE (("filter '%s'\n", ludp->lud_filter)); p = q; if (!p) goto success; /* parse extensions */ ludp->lud_exts = split_str(p); if (!ludp->lud_exts) return LDAP_NO_MEMORY; for (i = 0; ludp->lud_exts[i]; i++) LDAP_TRACE (("exts[%d] '%s'\n", i, ludp->lud_exts[i])); success: if (!unescape_elements(conn->data, ludp)) return LDAP_NO_MEMORY; return LDAP_SUCCESS; } static int _ldap_url_parse (const struct connectdata *conn, LDAPURLDesc **ludpp) { LDAPURLDesc *ludp = calloc(sizeof(*ludp), 1); int rc; *ludpp = NULL; if (!ludp) return LDAP_NO_MEMORY; rc = _ldap_url_parse2 (conn, ludp); if (rc != LDAP_SUCCESS) { _ldap_free_urldesc(ludp); ludp = NULL; } *ludpp = ludp; return (rc); } static void _ldap_free_urldesc (LDAPURLDesc *ludp) { int i; if (!ludp) return; if (ludp->lud_dn) free(ludp->lud_dn); if (ludp->lud_filter) free(ludp->lud_filter); if (ludp->lud_attrs) { for (i = 0; ludp->lud_attrs[i]; i++) free(ludp->lud_attrs[i]); free(ludp->lud_attrs); } if (ludp->lud_exts) { for (i = 0; ludp->lud_exts[i]; i++) free(ludp->lud_exts[i]); free(ludp->lud_exts); } free (ludp); } #endif /* WIN32 */ #endif /* CURL_DISABLE_LDAP */