Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions LDAPCnx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void LDAPCnx::Init(Local<Object> exports) {
Nan::SetPrototypeMethod(tpl, "search", Search);
Nan::SetPrototypeMethod(tpl, "delete", Delete);
Nan::SetPrototypeMethod(tpl, "bind", Bind);
Nan::SetPrototypeMethod(tpl, "saslbind", SASLBind);
Nan::SetPrototypeMethod(tpl, "add", Add);
Nan::SetPrototypeMethod(tpl, "modify", Modify);
Nan::SetPrototypeMethod(tpl, "rename", Rename);
Expand Down Expand Up @@ -195,6 +196,27 @@ void LDAPCnx::Event(uv_poll_t* handle, int status, int events) {
break;
}
case LDAP_RES_BIND:
{
int msgid = ldap_msgid(message);

if(err == LDAP_SASL_BIND_IN_PROGRESS) {
err = ld->SASLBindNext(&message);
if(err != LDAP_SUCCESS) {
errparam = Nan::Error(ldap_err2string(err));
}
else {
errparam = Nan::Undefined();
}
}

Local<Value> argv[] = {
errparam,
Nan::New(msgid)
};
ld->callback->Call(2, argv);

break;
}
case LDAP_RES_MODIFY:
case LDAP_RES_MODDN:
case LDAP_RES_ADD:
Expand Down
4 changes: 4 additions & 0 deletions LDAPCnx.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class LDAPCnx : public Nan::ObjectWrap {
static void Search (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void Delete (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void Bind (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void SASLBind (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void Add (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void Modify (const Nan::FunctionCallbackInfo<v8::Value>& info);
static void Rename (const Nan::FunctionCallbackInfo<v8::Value>& info);
Expand All @@ -38,6 +39,9 @@ class LDAPCnx : public Nan::ObjectWrap {
static void CheckTLS (const Nan::FunctionCallbackInfo<v8::Value>& info);
static int isBinary (char * attrname);

int SASLBindNext(LDAPMessage** result);
const char* sasl_mechanism;

ldap_conncb * ldap_callback;
uv_poll_t * handle;

Expand Down
64 changes: 64 additions & 0 deletions LDAPSASL.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <sasl/sasl.h>
#include "LDAPCnx.h"
#include "SASLDefaults.h"

using namespace v8;

void LDAPCnx::SASLBind(const Nan::FunctionCallbackInfo<Value>& info) {

LDAPCnx* ld = ObjectWrap::Unwrap<LDAPCnx>(info.Holder());

if (ld->ld == NULL) {
Nan::ThrowError("LDAP connection has not been established");
}

v8::String::Utf8Value mechanism(SASLDefaults::Get(info[0]));
SASLDefaults defaults(info[1], info[2], info[3], info[4]);
v8::String::Utf8Value sec_props(SASLDefaults::Get(info[5]));

if(*sec_props) {
int res = ldap_set_option(ld->ld, LDAP_OPT_X_SASL_SECPROPS, *sec_props);
if(res != LDAP_SUCCESS) {
Nan::ThrowError(ldap_err2string(res));
}
}

int msgid;
LDAPControl** sctrlsp = NULL;
LDAPMessage* message = NULL;
ld->sasl_mechanism = NULL;

int res = ldap_sasl_interactive_bind(ld->ld, NULL, *mechanism,
sctrlsp, NULL, LDAP_SASL_QUIET, &SASLDefaults::Callback, &defaults,
message, &ld->sasl_mechanism, &msgid);
if(res != LDAP_SASL_BIND_IN_PROGRESS && res != LDAP_SUCCESS) {
Nan::ThrowError(ldap_err2string(res));
}

info.GetReturnValue().Set(msgid);
}

int LDAPCnx::SASLBindNext(LDAPMessage** message) {
LDAPControl** sctrlsp = NULL;
int res;
int msgid;
while(true) {
res = ldap_sasl_interactive_bind(ld, NULL, NULL,
sctrlsp, NULL, LDAP_SASL_QUIET, NULL, NULL,
*message, &sasl_mechanism, &msgid);

if(res != LDAP_SASL_BIND_IN_PROGRESS) {
break;
}

ldap_msgfree(*message);

if(ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, message) != LDAP_SUCCESS) {
ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &res);
break;
}
}

return res;
}

9 changes: 9 additions & 0 deletions LDAPXSASL.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "LDAPCnx.h"

void LDAPCnx::SASLBind(const Nan::FunctionCallbackInfo<v8::Value>& info) {
Nan::ThrowError("LDAP module was not built with SASL support");
}

int LDAPCnx::SASLBindNext(LDAPMessage** result) {
return -1;
}
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ To install the latest release from npm:

You will also require the LDAP Development Libraries (on Ubuntu, `sudo apt-get install libldap2-dev`)

For SASL authentication support the Cyrus SASL libraries need to be installed
and OpenLDAP needs to be built with SASL support.

Reconnection
==========
If the connection fails during operation, the client library will handle the reconnection, calling the function specified in the connect option. This callback is a good place to put bind()s and other things you want to always be in place.
Expand Down Expand Up @@ -122,6 +125,32 @@ bind_options = {
Aliased to `ldap.simplebind()` for backward compatibility.


ldap.saslbind()
===
Upgrade the existing anonymous bind to an authenticated bind using SASL.

ldap.saslbind([bind_options,] function(err));

Options are:

* mechanism - If not provided SASL library will select based on the best
mechanism available on the server.
* user - Authentication user if required by mechanism
* password - Authentication user's password if required by mechanism
* realm - Non-default SASL realm if required by mechanism
* proxyuser - Authorization (proxy) user if supported by mechanism
* securityproperties - Optional SASL security properties

All parameters are optional. For example a GSSAPI (Kerberos) bind can be
initiated as follows:

```
ldap.saslbind(function(err) { if(err) throw err; });
```

For details refer to the [SASL documentation](http://cyrusimap.org/docs/cyrus-sasl).


ldap.search()
===
ldap.search(search_options, function(err, data));
Expand Down
36 changes: 36 additions & 0 deletions SASLDefaults.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <sasl/sasl.h>
#include "SASLDefaults.h"

void SASLDefaults::Set(unsigned flags, sasl_interact_t *interact) {
const char *dflt = interact->defresult;

switch (interact->id) {
case SASL_CB_AUTHNAME:
dflt = *user;
break;
case SASL_CB_PASS:
dflt = *password;
break;
case SASL_CB_GETREALM:
dflt = *realm;
break;
case SASL_CB_USER:
dflt = *proxy_user;
break;
}

interact->result = (dflt && *dflt) ? dflt : "";
interact->len = strlen((const char*)interact->result);
}

int SASLDefaults::Callback(LDAP *ld, unsigned flags, void *defaults, void *in) {
SASLDefaults* self = (SASLDefaults*)defaults;
sasl_interact_t *interact = (sasl_interact_t*)in;
while(interact->id != SASL_CB_LIST_END) {
self->Set(flags, interact);
++interact;
}

return LDAP_SUCCESS;
}

32 changes: 32 additions & 0 deletions SASLDefaults.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <nan.h>
#include <ldap.h>

struct SASLDefaults {
SASLDefaults(
const v8::Local<v8::Value>& usr,
const v8::Local<v8::Value>& pw,
const v8::Local<v8::Value>& rlm,
const v8::Local<v8::Value>& proxy
) :
user(Get(usr)),
password(Get(pw)),
realm(Get(rlm)),
proxy_user(Get(proxy))
{}

// Returns a C NULL value if not a string
static inline v8::Local<v8::Value> Get(const v8::Local<v8::Value>& v) {
return v->IsString() ? v : v8::Local<v8::Value>();
}

static int Callback(LDAP *ld, unsigned flags, void *defaults, void *in);

v8::String::Utf8Value user;
v8::String::Utf8Value password;
v8::String::Utf8Value realm;
v8::String::Utf8Value proxy_user;

private:
void Set(unsigned flags, sasl_interact_t *interact);
};

15 changes: 11 additions & 4 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"targets": [
{
"target_name": "LDAPCnx",
"sources": [ "LDAP.cc", "LDAPCnx.cc", "LDAPCookie.cc" ],
"sources": [ "LDAP.cc", "LDAPCnx.cc", "LDAPCookie.cc",
"LDAPSASL.cc", "LDAPXSASL.cc", "SASLDefaults.cc" ],
"include_dirs" : [
"<!(node -e \"require('nan')\")",
"/usr/local/include"
Expand All @@ -20,9 +21,16 @@
"-Wall",
"-g"
],

"conditions": [
[ "SASL==\"n\"", { "sources!":
["LDAPSASL.cc", "SASLDefaults.cc"] } ],
[ "SASL==\"y\"", { "sources!": ["LDAPXSASL.cc"] } ]
]
}
],
"variables": {
"SASL": "<!(test -f /usr/include/sasl/sasl.h && echo y || echo n)"
},
"conditions": [
[
"OS==\"mac\"",
Expand All @@ -41,5 +49,4 @@
]
]
}



25 changes: 24 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,29 @@ LDAP.prototype.bind = LDAP.prototype.simplebind = function(opt, fn) {
return this.enqueue(this.ld.bind(opt.binddn, opt.password), fn);
};

LDAP.prototype.saslbind = function(opt, fn) {

this.stats.binds++;

if(arguments.length == 1 && typeof arguments[0] === 'function') {
fn = opt;
opt = undefined;
}

var args = [
'mechanism','user','password','realm','proxyuser','securityproperties'
].map(function(p) { return opt == null ? undefined : opt[p]; });

if (args.filter(function(a) {
return a != null && typeof a !== 'string';
}).length ||
typeof fn !== 'function') {
throw new LDAPError('Invalid argument');
}

return this.enqueue(this.ld.saslbind.apply(this.ld, args), fn);
};

LDAP.prototype.add = function(dn, attrs, fn) {
this.stats.adds++;
if (typeof dn !== 'string' ||
Expand All @@ -160,7 +183,7 @@ LDAP.prototype.search = function(opt, fn) {
arg(opt.cookie, null)
), unwrap_cookie);
function unwrap_cookie(err, data) {
fn(err, data.data, data.cookie);
err ? fn(err) : fn(err, data.data, data.cookie);
}
};

Expand Down
39 changes: 39 additions & 0 deletions test/run_sasl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh

if [[ -z $SLAPD ]] ; then
SLAPD=/usr/local/libexec/slapd
fi

if [[ -z $SLAPADD ]] ; then
SLAPADD=/usr/local/sbin/slapadd
fi

if [[ -z $SLAPD_CONF ]] ; then
SLAPD_CONF=sasl.conf
fi

MKDIR=/bin/mkdir
RM=/bin/rm

$RM -rf openldap-data
$MKDIR openldap-data

if [[ -f slapd.pid ]] ; then
$RM slapd.pid
fi

$SLAPADD -f $SLAPD_CONF < startup.ldif
$SLAPADD -f $SLAPD_CONF < sasl.ldif
$SLAPD -d999 -f $SLAPD_CONF -hldap://localhost:1234 > sasl.log 2>&1 &

if [[ ! -f slapd.pid ]] ; then
sleep 1
fi

# Make sure SASL is enabled
if ldapsearch -H ldap://localhost:1234 -x -b "" -s base -LLL \
supportedSASLMechanisms | grep -q SASL ; then
:
else
echo slapd started but SASL not supported
fi
26 changes: 26 additions & 0 deletions test/sasl.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
include /usr/local/etc/openldap/schema/core.schema
include /usr/local/etc/openldap/schema/cosine.schema
include /usr/local/etc/openldap/schema/inetorgperson.schema

pidfile ./slapd.pid
argsfile ./slapd.args

modulepath /usr/local/libexec/openldap
moduleload back_bdb

idletimeout 100

database bdb

sasl-auxprops slapd
sasl-secprops none
authz-regexp uid=(.*),cn=PLAIN,cn=auth cn=$1,dc=sample,dc=com
authz-regexp uid=(.*),cn=authz,cn=auth cn=$1,dc=sample,dc=com
password-hash {CLEARTEXT}
authz-policy from

suffix "dc=sample,dc=com"
rootdn "cn=Manager,dc=sample,dc=com"
rootpw secret
directory ./openldap-data
index objectClass,cn,contextCSN eq
Loading