diff --git a/lib/openldap.c b/lib/openldap.c new file mode 100644 index 0000000..afaf6cc --- /dev/null +++ b/lib/openldap.c @@ -0,0 +1,447 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010, Howard Chu, + * + * 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. + * + ***************************************************************************/ + +#include "setup.h" + +#ifdef USE_OPENLDAP + +#include "urldata.h" +#include +#include +#include + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "memdebug.h" + +static CURLcode ldap_setup(struct connectdata *conn); +static CURLcode ldap_do(struct connectdata *conn, bool *done); +static CURLcode ldap_connect(struct connectdata *conn, bool *done); +static CURLcode ldap_connecting(struct connectdata *conn, bool *done); +static CURLcode ldap_disconnect(struct connectdata *conn); + +static Curl_recv ldap_recv; + +/* + * LDAP protocol handler. + */ + +const struct Curl_handler Curl_handler_ldap = { + "LDAP", /* scheme */ + ldap_setup, /* setup_connection */ + ldap_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ldap_connect, /* connect_it */ + ldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* perform_getsock */ + ldap_disconnect, /* disconnect */ + PORT_LDAP, /* defport */ + PROT_LDAP /* protocol */ +}; + +/* + * LDAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ldaps = { + "LDAPS", /* scheme */ + ldap_setup, /* setup_connection */ + ldap_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ldap_connect, /* connect_it */ + ldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* perform_getsock */ + ldap_disconnect, /* disconnect */ + PORT_LDAP, /* defport */ + PROT_LDAP /* protocol */ +}; + +static const char *url_errs[] = { + "success", + "out of memory", + "bad parameter", + "unrecognized scheme", + "unbalanced delimiter", + "bad URL", + "bad host or port", + "bad or missing attributes", + "bad or missing scope", + "bad or missing filter", + "bad or missing extensions" +}; + +typedef struct ldapinfo { + LDAP *ld; + curl_off_t dlsize; + int proto; + int msgid; +} ldapinfo; + +static CURLcode ldap_setup(struct connectdata *conn) +{ + ldapinfo *li; + LDAPURLDesc *lud; + struct SessionHandle *data=conn->data; + int rc, ldap_option, proto; + CURLcode status; + + rc = ldap_url_parse(data->change.url, &lud); + if (rc != LDAP_URL_SUCCESS) { + const char *msg = "url parsing problem"; + status = CURLE_URL_MALFORMAT; + if (rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { + if (rc == LDAP_URL_ERR_MEM) + status = CURLE_OUT_OF_MEMORY; + msg = url_errs[rc]; + } + failf(conn->data, "LDAP local: %s", msg); + return status; + } + proto = ldap_pvt_url_scheme2proto(lud->lud_scheme); + ldap_free_urldesc(lud); + + if(data->set.ssl.verifypeer) { + char* ldap_ca = data->set.str[STRING_SSL_CAFILE]; + /* OpenLDAP SDK supports BASE64 files. */ + if((data->set.str[STRING_CERT_TYPE]) && + (!Curl_raw_equal(data->set.str[STRING_CERT_TYPE], "PEM"))) { + failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type!"); + return CURLE_SSL_CERTPROBLEM; + } + if(!ldap_ca) { + failf(data, "LDAP local: ERROR PEM CA cert not set!"); + return CURLE_SSL_CERTPROBLEM; + } + infof(data, "LDAP local: using PEM CA cert: %s\n", ldap_ca); + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting PEM CA cert: %s", + ldap_err2string(rc)); + return CURLE_SSL_CERTPROBLEM; + } + ldap_option = LDAP_OPT_X_TLS_DEMAND; + } else { + ldap_option = LDAP_OPT_X_TLS_NEVER; + } + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting cert verify mode: %s", + ldap_err2string(rc)); + return CURLE_SSL_CERTPROBLEM; + } + li = malloc(sizeof(ldapinfo)); + li->ld = NULL; + li->dlsize = 0; + li->proto = proto; + conn->proto.generic = li; + /* TODO: + * - provide option to choose SASL Binds instead of Simple + * - set client cert options, to allow SASL/EXTERNAL Binds + */ + return CURLE_OK; +} + +static CURLcode ldap_connect(struct connectdata *conn, bool *done) +{ + ldapinfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + char *binddn; + struct berval passwd; + int rc, proto = LDAP_VERSION3; + char hosturl[1024], *ptr; + + strcpy(hosturl, "ldap"); + ptr = hosturl+4; + if (conn->protocol & PROT_SSL) + *ptr++ = 's'; + snprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d", + conn->host.name, conn->port); + + rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); + if (rc) { + failf(data, "LDAP local: Cannot connect to %s, %s", + hosturl, ldap_err2string(rc)); + return CURLE_COULDNT_CONNECT; + } + + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + + if (conn->bits.user_passwd) { + binddn = conn->user; + passwd.bv_val = conn->passwd; + passwd.bv_len = strlen(passwd.bv_val); + } else { + binddn = NULL; + passwd.bv_val = NULL; + passwd.bv_len = 0; + } + + rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &li->msgid); + if (rc == LDAP_PROTOCOL_ERROR) { + proto = LDAP_VERSION2; + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &li->msgid); + } + if (rc) { + failf(data, "LDAP local: ldap_sasl_bind %s", ldap_err2string(rc)); + return CURLE_LDAP_CANNOT_BIND; + } + if (data->state.used_interface == Curl_if_easy) + return ldap_connecting(conn, done); + return CURLE_OK; +} + +static CURLcode ldap_connecting(struct connectdata *conn, bool *done) +{ + ldapinfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + LDAPMessage *result = NULL; + struct timeval tv = {0,1}, *tvp; + int rc, err, do_retry = 0; + char *info = NULL; + + if (data->state.used_interface == Curl_if_easy) { + tvp = NULL; + do_retry = 1; + } else + tvp = &tv; +retry: + rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &result); + if (rc < 0) { + failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc)); + return CURLE_LDAP_CANNOT_BIND; + } + if (rc == 0) { + /* timed out */ + return CURLE_OK; + } + rc = ldap_parse_result(li->ld, result, &err, NULL, &info, NULL, NULL, 1); + if (rc) { + failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc)); + return CURLE_LDAP_CANNOT_BIND; + } + if (err) { + if (err != LDAP_PROTOCOL_ERROR) + failf(data, "LDAP local: bind failed %s %s", ldap_err2string(rc), + info ? info : ""); + ldap_memfree(info); + + /* Try to fallback to LDAPv2 */ + if (err == LDAP_PROTOCOL_ERROR) { + char *binddn; + struct berval passwd; + int proto = LDAP_VERSION2; + if (conn->bits.user_passwd) { + binddn = conn->user; + passwd.bv_val = conn->passwd; + passwd.bv_len = strlen(passwd.bv_val); + } else { + binddn = NULL; + passwd.bv_val = NULL; + passwd.bv_len = 0; + } + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &li->msgid); + if (rc) + return CURLE_LDAP_CANNOT_BIND; + if (!tvp && do_retry) { + do_retry = 0; + goto retry; + } + } + return CURLE_LOGIN_DENIED; + } + conn->recv = ldap_recv; + *done = TRUE; + return CURLE_OK; +} + +static CURLcode ldap_disconnect(struct connectdata *conn) +{ + ldapinfo *li = conn->proto.generic; + + if (li) { + conn->proto.generic = NULL; + if (li->ld) { + ldap_unbind(li->ld); + li->ld = NULL; + } + free(li); + } + return CURLE_OK; +} + +static CURLcode ldap_do(struct connectdata *conn, bool *done) +{ + ldapinfo *li = conn->proto.generic; + CURLcode status = CURLE_OK; + int rc = 0; + LDAPURLDesc *ludp = NULL; + LDAPMessage *entryIterator; + int num = 0; + struct SessionHandle *data=conn->data; + + infof(data, "LDAP local: %s\n", data->change.url); + + rc = ldap_url_parse(data->change.url, &ludp); + if (rc != LDAP_URL_SUCCESS) { + const char *msg = "url parsing problem"; + status = CURLE_URL_MALFORMAT; + if (rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { + if (rc == LDAP_URL_ERR_MEM) + status = CURLE_OUT_OF_MEMORY; + msg = url_errs[rc]; + } + failf(conn->data, "LDAP local: %s", msg); + return status; + } + + rc = ldap_search_ext(li->ld, ludp->lud_dn, ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, + NULL, NULL, NULL, 0, &li->msgid); + ldap_free_urldesc(ludp); + if (rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); + return CURLE_LDAP_SEARCH_FAILED; + } + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); + *done = TRUE; + return CURLE_OK; +} + +static ssize_t ldap_recv(struct connectdata *conn, int sockindex, char *buf, + size_t len, CURLcode *err) +{ + ldapinfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + int rc; + LDAPMessage *result = NULL; + LDAPMessage *ent; + BerElement *ber = NULL; + struct timeval tv = {0,1}; + + rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, &tv, &result); + if (rc < 0) { + failf(data, "LDAP local: search ldap_result %s", ldap_err2string(rc)); + *err = CURLE_RECV_ERROR; + return -1; + } + if (result == NULL) { + *err = CURLE_AGAIN; + return -1; + } + for (ent = ldap_first_message(li->ld, result); ent; + ent = ldap_next_message(li->ld, ent)) { + struct berval bv, *bvals, **bvp = &bvals; + int binary = 0, binval, msgtype; + + msgtype = ldap_msgtype(ent); + if (msgtype == LDAP_RES_SEARCH_RESULT) { + conn->data->req.size = conn->data->req.bytecount; + break; + } else if (msgtype != LDAP_RES_SEARCH_ENTRY) { + continue; + } + + rc = ldap_get_dn_ber(li->ld, ent, &ber, &bv); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + li->dlsize += bv.bv_len + 5; + + for (rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp)) { + int i; + + if (bv.bv_val == NULL) break; + + if (bv.bv_len > 7 && !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7)) + binary = 1; + + for (i=0; bvals[i].bv_val != NULL; i++) { + int binval = 0; + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2); + li->dlsize += bv.bv_len + 3; + + if (!binary) { + /* check for leading or trailing whitespace */ + if (isspace(bvals[i].bv_val[0]) || + isspace(bvals[i].bv_val[bvals[i].bv_len-1])) { + binval = 1; + } else { + /* check for unprintable characters */ + int j; + for (j=0; j 0) { + Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); + free(val_b64); + li->dlsize += val_b64_sz; + } + } else { + Curl_client_write(conn, CLIENTWRITE_BODY, bvals[i].bv_val, + bvals[i].bv_len); + li->dlsize += bvals[i].bv_len; + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + li->dlsize++; + } + ber_memfree(bvals); + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + li->dlsize++; + Curl_pgrsSetDownloadCounter(data, li->dlsize); + } + if (ber) ber_free(ber, 0); + ldap_msgfree(result); + *err = CURLE_OK; + return 0; +} + +#endif /* CURL_DISABLE_LDAP */ diff --git a/lib/openldap.h b/lib/openldap.h new file mode 100644 index 0000000..09ce90c --- /dev/null +++ b/lib/openldap.h @@ -0,0 +1,30 @@ +#ifndef __CURL_OPENLDAP_H +#define __CURL_OPENLDAP_H + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010, Howard Chu, + * + * 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. + * + ***************************************************************************/ +#ifdef USE_OPENLDAP +extern const struct Curl_handler Curl_handler_ldap; +extern const struct Curl_handler Curl_handler_ldaps; +#endif + +#endif /* __CURL_OPENLDAP_H */