diff --git a/examples/inmemory.js b/examples/inmemory.js new file mode 100644 index 0000000..c398ce5 --- /dev/null +++ b/examples/inmemory.js @@ -0,0 +1,197 @@ +var ldap = require('../lib/index'); + + +///--- Shared handlers + +function authorize(req, res, next) { + if (!req.connection.ldap.bindDN.equals('cn=root')) + return next(new ldap.InsufficientAccessRightsError()); + + return next(); +} + + +///--- Globals + +var SUFFIX = 'o=smartdc'; +var db = {}; +var server = ldap.createServer(); + + + +server.bind('cn=root', function(req, res, next) { + if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') + return next(new ldap.InvalidCredentialsError()); + + res.end(); + return next(); +}); + +server.add(SUFFIX, authorize, function(req, res, next) { + var dn = req.dn.toString(); + + if (db[dn]) + return next(new ldap.EntryAlreadyExistsError(dn)); + + db[dn] = req.toObject().attributes; + res.end(); + return next(); +}); + +server.bind(SUFFIX, function(req, res, next) { + var dn = req.dn.toString(); + if (!db[dn]) + return next(new ldap.NoSuchObjectError(dn)); + + if (!dn[dn].userpassword) + return next(new ldap.NoSuchAttributeError('userPassword')); + + if (db[dn].userpassword !== req.credentials) + return next(new ldap.InvalidCredentialsError()); + + res.end(); + return next(); +}); + +server.compare(SUFFIX, authorize, function(req, res, next) { + var dn = req.dn.toString(); + if (!db[dn]) + return next(new ldap.NoSuchObjectError(dn)); + + if (!db[dn][req.attribute]) + return next(new ldap.NoSuchAttributeError(req.attribute)); + + var matches = false; + var vals = db[dn][req.attribute]; + for (var i = 0; i < vals.length; i++) { + if (vals[i] === req.value) { + matches = true; + break; + } + } + + res.end(matches); + return next(); +}); + +server.del(SUFFIX, authorize, function(req, res, next) { + var dn = req.dn.toString(); + if (!db[dn]) + return next(new ldap.NoSuchObjectError(dn)); + + delete db[dn]; + + res.end(); + return next(); +}); + +server.modify(SUFFIX, authorize, function(req, res, next) { + var dn = req.dn.toString(); + if (!req.changes.length) + return next(new ldap.ProtocolError('changes required')); + if (!db[dn]) + return next(new ldap.NoSuchObjectError(dn)); + + var entry = db[dn]; + + for (var i = 0; i < req.changes.length; i++) { + mod = req.changes[i].modification; + switch (req.changes[i].operation) { + case 'replace': + if (!entry[mod.type]) + return next(new ldap.NoSuchAttributeError(mod.type)); + + if (!mod.vals || !mod.vals.length) { + delete entry[mod.type]; + } else { + entry[mod.type] = mod.vals; + } + + break; + + case 'add': + if (!entry[mod.type]) { + entry[mod.type] = mod.vals; + } else { + mod.vals.forEach(function(v) { + if (entry[mod.type].indexOf(v) === -1) + entry[mod.type].push(v); + }); + } + + break; + + case 'delete': + if (!entry[mod.type]) + return next(new ldap.NoSuchAttributeError(mod.type)); + + delete entry[mod.type]; + + break; + } + } + + res.end(); + return next(); +}); + +server.search(SUFFIX, authorize, function(req, res, next) { + var dn = req.dn.toString(); + if (!db[dn]) + return next(new ldap.NoSuchObjectError(dn)); + + var scopeCheck; + + switch (req.scope) { + case 'base': + if (req.filter.matches(db[dn])) { + res.send({ + dn: dn, + attributes: db[dn] + }); + } + + res.end(); + return next(); + + case 'one': + scopeCheck = function(k) { + if (req.dn.equals(k)) + return true; + + var parent = ldap.parseDN(k).parent(); + return (parent ? parent.equals(req.dn) : false); + }; + break; + + case 'sub': + scopeCheck = function(k) { + return (req.dn.equals(k) || req.dn.parentOf(k)); + }; + + break; + } + + Object.keys(db).forEach(function(key) { + if (!scopeCheck(key)) + return; + + if (req.filter.matches(db[key])) { + res.send({ + dn: key, + attributes: db[key] + }); + } + }); + + res.end(); + return next(); +}); + + + +///--- Fire it up + +server.listen(1389, function() { + console.log('LDAP server up at: %s', server.url); +}); diff --git a/snoopldap.d b/examples/snoopldap.d similarity index 100% rename from snoopldap.d rename to examples/snoopldap.d diff --git a/lib/messages/unbind_response.js b/lib/messages/unbind_response.js index 4a9f30f..21ff8ce 100644 --- a/lib/messages/unbind_response.js +++ b/lib/messages/unbind_response.js @@ -3,6 +3,8 @@ var assert = require('assert'); var util = require('util'); +var dtrace = require('../dtrace'); + var LDAPMessage = require('./result'); var Protocol = require('../protocol'); @@ -38,6 +40,21 @@ UnbindResponse.prototype.end = function(status) { this.log.trace('%s: unbinding!', this.connection.ldap.id); this.connection.end(); + + var self = this; + if (self._dtraceOp && self._dtraceId) { + dtrace.fire('server-' + self._dtraceOp + '-done', function() { + var c = self.connection || {ldap: {}}; + return [ + self._dtraceId || 0, + (c.remoteAddress || ''), + c.ldap.bindDN ? c.ldap.bindDN.toString() : '', + (self.requestDN ? self.requestDN.toString() : ''), + 0, + '' + ]; + }); + } }; diff --git a/lib/server.js b/lib/server.js index 09c1acc..2b42f70 100644 --- a/lib/server.js +++ b/lib/server.js @@ -135,27 +135,7 @@ function defaultHandler(req, res, next) { } -function defaultAbandonHandler(req, res, next) { - assert.ok(req); - assert.ok(res); - assert.ok(next); - - res.end(); - return next(); -} - - -function defaultUnbindHandler(req, res, next) { - assert.ok(req); - assert.ok(res); - assert.ok(next); - - res.end(); - return next(); -} - - -function defaultAnonymousBindHandler(req, res, next) { +function defaultNoOpHandler(req, res, next) { assert.ok(req); assert.ok(res); assert.ok(next); @@ -187,32 +167,69 @@ function noExOpHandler(req, res, next) { } -function getArgumentsWithDTrace(args, op, cb1, cb2) { - assert.ok(op); +function fireDTraceProbe(req, res) { + assert.ok(req); - var index = 0; - if (typeof(args[0]) === 'object') - index = 1; + req._dtraceId = res._dtraceId = dtrace._nextId(); + var probeArgs = [ + req._dtraceId, + req.connection.remoteAddress || 'localhost', + req.connection.ldap.bindDN.toString(), + req.dn.toString(), + ]; - args.splice(index, 0, function(req, res, next) { - req._dtraceId = res._dtraceId = dtrace._nextId(); - res._dtraceOp = op; - dtrace.fire('server-' + res._dtraceOp + '-start', function() { - return [ - res._dtraceId, - req.connection.remoteAddress, - req.connection.ldap.bindDN.toString(), - req.dn.toString(), - cb1 ? cb1(req, res) : undefined, - cb2 ? cb2(req, res) : undefined - ]; - }); - return next(); + var op; + switch (req.protocolOp) { + case Protocol.LDAP_REQ_ABANDON: + op = 'abandon'; + break; + case Protocol.LDAP_REQ_ADD: + op = 'add'; + probeArgs.push(req.attributes.length); + break; + case Protocol.LDAP_REQ_BIND: + op = 'bind'; + break; + case Protocol.LDAP_REQ_COMPARE: + op = 'compare'; + probeArgs.push(req.attribute); + probeArgs.push(req.value); + break; + case Protocol.LDAP_REQ_DELETE: + op = 'delete'; + break; + case Protocol.LDAP_REQ_EXTENSION: + op = 'exop'; + probeArgs.push(req.name); + probeArgs.push(req.value); + break; + case Protocol.LDAP_REQ_MODIFY: + op = 'modify'; + probeArgs.push(req.changes.length); + break; + case Protocol.LDAP_REQ_MODRDN: + op = 'modifydn'; + probeArgs.push(req.newRdn.toString()); + probeArgs.push((req.newSuperior ? req.newSuperior.toString() : '')); + break; + case Protocol.LDAP_REQ_SEARCH: + op = 'search'; + probeArgs.push(req.scope); + probeArgs.push(req.filter.toString()); + break; + case Protocol.LDAP_REQ_UNBIND: + op = 'unbind'; + break; + } + + res._dtraceOp = op; + dtrace.fire('server-' + op + '-start', function() { + return probeArgs; }); - - return args; } + + ///--- API /** @@ -338,7 +355,7 @@ function Server(options) { res.logId = req.logId; res.requestDN = req.dn; - var chain = self._getHandlerChain(req); + var chain = self._getHandlerChain(req, res); var i = 0; return function(err) { @@ -468,12 +485,7 @@ module.exports = Server; * @throws {TypeError} on bad input */ Server.prototype.add = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'add', - function(req, res) { - return req.attributes.length; - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_ADD, name, args); }; @@ -489,9 +501,7 @@ Server.prototype.add = function(name) { * @throws {TypeError} on bad input */ Server.prototype.bind = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'bind'); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_BIND, name, args); }; @@ -507,15 +517,7 @@ Server.prototype.bind = function(name) { * @throws {TypeError} on bad input */ Server.prototype.compare = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'compare', - function(req, res) { - return req.attribute; - }, - function(req, res) { - return req.value; - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_COMPARE, name, args); }; @@ -531,9 +533,7 @@ Server.prototype.compare = function(name) { * @throws {TypeError} on bad input */ Server.prototype.del = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'delete'); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_DELETE, name, args); }; @@ -549,15 +549,7 @@ Server.prototype.del = function(name) { * @throws {TypeError} on bad input. */ Server.prototype.exop = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'exop', - function(req, res) { - return req.name; - }, - function(req, res) { - return req.value; - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true); }; @@ -573,12 +565,7 @@ Server.prototype.exop = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modify = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'modify', - function(req, res) { - return req.changes.length; - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_MODIFY, name, args); }; @@ -594,16 +581,7 @@ Server.prototype.modify = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modifyDN = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'modifydn', - function(req, res) { - return req.newRdn.toString(); - }, - function(req, res) { - return (req.newSuperior ? - req.newSuperior.toString() : ''); - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_MODRDN, name, args); }; @@ -619,15 +597,7 @@ Server.prototype.modifyDN = function(name) { * @throws {TypeError} on bad input */ Server.prototype.search = function(name) { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'search', - function(req, res) { - return req.scope; - }, - function(req, res) { - return req.filter.toString(); - }); - + var args = Array.prototype.slice.call(arguments, 1); return this._mount(Protocol.LDAP_REQ_SEARCH, name, args); }; @@ -642,9 +612,7 @@ Server.prototype.search = function(name) { * @throws {TypeError} on bad input */ Server.prototype.unbind = function() { - var args = getArgumentsWithDTrace(Array.prototype.slice.call(arguments, 1), - 'unbind'); - + var args = Array.prototype.slice.call(arguments, 0); return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true); }; @@ -726,16 +694,18 @@ Server.prototype._getRoute = function(_dn, backend) { }; -Server.prototype._getHandlerChain = function(req) { +Server.prototype._getHandlerChain = function(req, res) { assert.ok(req); + fireDTraceProbe(req, res); + // check anonymous bind if (req.protocolOp === Protocol.LDAP_REQ_BIND && req.dn.toString() === '' && req.credentials === '') { return { backend: self, - handlers: [defaultAnonymousBindHandler] + handlers: [defaultNoOpHandler] }; } @@ -763,7 +733,7 @@ Server.prototype._getHandlerChain = function(req) { return routes['unbind'][op]; self.log.debug('%s unbind request %j', req.logId, req.json); - return [defaultUnbindHandler]; + return [defaultNoOpHandler]; } return { @@ -773,7 +743,7 @@ Server.prototype._getHandlerChain = function(req) { } else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) { return { backend: self, - handlers: [defaultAbandonHandler] + handlers: [defaultNoOpHandler] }; } @@ -811,7 +781,7 @@ Server.prototype._mount = function(op, name, argv, notDN) { if (typeof(name) !== 'string') throw new TypeError('name (string) required'); - if (argv.length < 1) + if (!argv.length) throw new Error('at least one handler required'); var backend = this; diff --git a/package.json b/package.json index f40c7a7..27cfad7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "ldapjs", "homepage": "http://ldapjs.org", "description": "LDAP client and server APIs", - "version": "0.3.1", + "version": "0.3.2", "repository": { "type": "git", "url": "git://github.com/mcavage/node-ldapjs.git"