commit cf701097c0209a288ec673790e9d7be676499f34 Author: Matthieu Lalonde Date: Fri Jan 7 14:25:08 2011 -0500 Initial Import of node-ldapsearch diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..7f814ab --- /dev/null +++ b/src/Makefile @@ -0,0 +1,2 @@ +all: + cd .. && node-waf build \ No newline at end of file diff --git a/src/ldap.cc b/src/ldap.cc new file mode 100644 index 0000000..ea0f3d0 --- /dev/null +++ b/src/ldap.cc @@ -0,0 +1,249 @@ +// Provides Node.JS binding for ldap_simple_bind(). +// See README +// 2010, Joe Walnes, joe@walnes.com, http://joewalnes.com/ + + +/* +Here's the basic flow of events. LibEIO is used to ensure that +the LDAP calls occur on a background thread and do not block +the main Node event loop. + ++----------------------+ +------------------------+ +| Main Node Event Loop | | Background Thread Pool | ++----------------------+ +------------------------+ + +User application +| +V +JavaScript: authenticate() +| +V +ldapauth.cc: Authenticate() +| ++-------------------------> libauth.cc: EIO_Authenticate() +| | +V V +(user application carries ldap_simple_bind() +on doing its stuff) | +| (wait for response +(no blocking) from server) +| | +(sometime later) (got response) +| | +ldapauth.cc: EIO_AfterAuthenticate() <----------+ +| +V +Invoke user supplied JS callback + +*/ + +#include +#include +#include +#include +#include +#include + +using namespace v8; +using namespace std; + +#define THROW(message) ThrowException(Exception::TypeError(String::New(message))) + +// Data passed between threads +struct auth_request +{ +// Input params + char *uri; + LDAPURLDesc *ludpp; + LDAP *ldap; +// Callback function + Persistent cbSuccess; + Persistent cbFailure; +// Result + bool connected; + bool authenticated; +}; + +// Runs on background thread, performing the actual LDAP request. +static int EIO_Open(eio_req *req) +{ + HandleScope scope; + int ver = 3; + struct auth_request *auth_req = (struct auth_request*)(req->data); + unsigned long int len = 5 + sizeof(auth_req->ludpp->lud_scheme) + sizeof(auth_req->ludpp->lud_host) + sizeof(auth_req->ludpp->lud_port); + char uri[len]; + + sprintf(uri, "%s://%s:%d/", auth_req->ludpp->lud_scheme, auth_req->ludpp->lud_host, auth_req->ludpp->lud_port); + + auth_req->ldap = ldap_init(auth_req->ludpp->lud_host, auth_req->ludpp->lud_port); + + int res = ldap_initialize(&auth_req->ldap, uri); + + if (auth_req->ldap == NULL || res < 0) { + auth_req->connected = false; + } else { + //ldap_set_option(ldap, LDAP_OPT_RESTART, LDAP_OPT_ON); + ldap_set_option(auth_req->ldap, LDAP_OPT_PROTOCOL_VERSION, &ver); + + auth_req->connected = true; + } + + return 0; +} + +// Called on main event loop when background thread has completed +static int EIO_AfterOpen(eio_req *req) +{ + ev_unref(EV_DEFAULT_UC); + HandleScope scope; + Local js_result_list; + LDAPMessage *ldap_res; + struct auth_request *auth_req = (struct auth_request *)(req->data); + int res, res1, msgid; + Handle callback_args[2]; + + callback_args[0] = Local::New(Boolean::New(true)); + callback_args[1] = (Handle)Undefined(); + + if (auth_req->connected == true) { + res = ldap_search(auth_req->ldap, auth_req->ludpp->lud_dn, LDAP_SCOPE_SUBTREE, auth_req->ludpp->lud_filter, auth_req->ludpp->lud_attrs, 0); + + if (res != LDAP_SERVER_DOWN) { + if ((res1 = ldap_result(auth_req->ldap, LDAP_RES_ANY, 1, NULL, &ldap_res)) >= 1) { + + msgid = ldap_msgid(ldap_res); + + switch(res1) { + case LDAP_RES_SEARCH_RESULT: { + LDAPMessage * entry = NULL; + BerElement * berptr = NULL; + char * attrname = NULL; + char ** vals; + Local js_result; + Local js_attr_vals; + int j; + char * dn; + + int entry_count = ldap_count_entries(auth_req->ldap, ldap_res); + js_result_list = Array::New(entry_count); + + for (entry = ldap_first_entry(auth_req->ldap, ldap_res), j = 0 ; entry ; + entry = ldap_next_entry(auth_req->ldap, entry), j++) { + js_result = Object::New(); + js_result_list->Set(Integer::New(j), js_result); + + dn = ldap_get_dn(auth_req->ldap, entry); + + for (attrname = ldap_first_attribute(auth_req->ldap, entry, &berptr) ; + attrname ; attrname = ldap_next_attribute(auth_req->ldap, entry, berptr)) { + vals = ldap_get_values(auth_req->ldap, entry, attrname); + int num_vals = ldap_count_values(vals); + js_attr_vals = Array::New(num_vals); + js_result->Set(String::New(attrname), js_attr_vals); + for (int i = 0 ; vals[i] ; i++) { + js_attr_vals->Set(Integer::New(i), String::New(vals[i])); + } // all values for this attr added. + ldap_value_free(vals); + ldap_memfree(attrname); + } // attrs for this entry added. Next entry. + js_result->Set(String::New("dn"), String::New(dn)); + ber_free(berptr,0); + ldap_memfree(dn); + } // all entries done. + + callback_args[0] = (Handle)Undefined(); + callback_args[1] = js_result_list; + } break; + + default: { + callback_args[0] = Local::New(String::New(ldap_err2string(res1))); + callback_args[1] = (Handle)Undefined(); + } break; + } + + ldap_msgfree(ldap_res); + } else { + callback_args[0] = Local::New(String::New(ldap_err2string(res1))); + callback_args[1] = (Handle)Undefined(); + } + } + } + + // Invoke callback JS function + auth_req->cbSuccess->Call(Context::GetCurrent()->Global(), 2, callback_args); + + // Cleanup auth_request struct + + auth_req->cbSuccess.Dispose(); + ldap_free_urldesc(auth_req->ludpp); + free(auth_req); + + return 0; +} + +// Exposed authenticate() JavaScript function +static Handle Open(const Arguments& args) +{ + HandleScope scope; + +// Validate args. + if (args.Length() < 2) return THROW("Required arguments: ldap_uri, callback"); + if (!args[0]->IsString()) return THROW("ldap_uri should be a string"); + if (!args[1]->IsFunction()) return THROW("success callback should be a function"); + + if (args.Length() == 3) { + if (!args[2]->IsFunction()) { + return THROW("error callback should be a function"); + } + } + +// Input params. + String::Utf8Value uri(args[0]); + Local cbSuccess = Local::Cast(args[1]); + Local cbFailure; + + if (args.Length() == 3) { + cbFailure = Local::Cast(args[2]); + } + +// Store all parameters in auth_request struct, which shall be passed across threads. + struct auth_request *auth_req = (struct auth_request*) calloc(1, sizeof(struct auth_request)); + auth_req->uri = strdup(*uri); + auth_req->cbSuccess = Persistent::New(cbSuccess); + + if (args.Length() == 3) { + auth_req->cbFailure = Persistent::New(cbFailure); + } + + if (ldap_url_parse(auth_req->uri, &auth_req->ludpp) == 0) { + eio_custom(EIO_Open, EIO_PRI_DEFAULT, EIO_AfterOpen, auth_req); + ev_ref(EV_DEFAULT_UC); + } else { + ldap_free_urldesc(auth_req->ludpp); + Handle callback_args[2]; + + callback_args[0] = Local::New(String::New("Invalid LDAP URI")); + callback_args[1] = (Handle)Undefined(); + + if (args.Length() == 3) { + auth_req->cbFailure->Call(Context::GetCurrent()->Global(), 2, callback_args); + auth_req->cbFailure.Dispose(); + } else { + auth_req->cbSuccess->Call(Context::GetCurrent()->Global(), 2, callback_args); + auth_req->cbSuccess.Dispose(); + } + + free(auth_req); + } + + + return Undefined(); +} + +// Entry point for native Node module +extern "C" void + init (Handle target) +{ + HandleScope scope; + target->Set(String::New("Search"), FunctionTemplate::New(Open)->GetFunction()); +} \ No newline at end of file diff --git a/test.js b/test.js new file mode 100755 index 0000000..ebb223e --- /dev/null +++ b/test.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +var sys = require("sys"), +LDAPClient = require("./build/default/ldap.node"); // Path to ldapauth.node + +LDAPClient.Search("ldaps://ldap.example.tld/ou=people,dc=example,dc=tld?*?sub?uid=*", + function(err, result) { + if (err) { + sys.puts(err); + } else { + sys.puts("Result: "); + console.log(result); + } + } +); \ No newline at end of file diff --git a/wscript b/wscript new file mode 100644 index 0000000..21fe4ee --- /dev/null +++ b/wscript @@ -0,0 +1,35 @@ +import Options, Utils +from os import unlink, symlink, chdir +from os.path import exists + +srcdir = '.' +blddir = 'build' +VERSION = '0.0.2' + +def set_options(opt): + opt.tool_options('compiler_cxx') + +def configure(conf): + conf.check_tool('compiler_cxx') + conf.check_tool('node_addon') + + conf.env.append_unique('CPPFLAGS', ["-I/usr/local/include"]) + conf.env.append_unique('CXXFLAGS', ["-Wall"]) + + conf.env.append_unique('LINKFLAGS', ["-L/usr/local/lib"]) + + +def build(bld): + obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') + #obj.cxxflags = ['-DLDAP_DEPRECATED'] + obj.cxxflags = ["-DLDAP_DEPRECATED", "-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall"] + obj.target = 'ldap' + obj.source = './src/ldap.cc' + obj.lib = ['ldap'] + +#def shutdown(): + # HACK to get bindings.node out of build directory. + # better way to do this? + #t = 'ldapsearch.node' + #symlink('build/default/' + t, t) +