// Provides Node.JS binding for ldap_simple_bind(). // See README // 2010, xSmurf, xsmurf@smurfturf.net, http://mlalonde.net/ // 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()); }