diff --git a/examples/dtrace/.#binds.d b/examples/dtrace/.#binds.d new file mode 120000 index 0000000..bd615d0 --- /dev/null +++ b/examples/dtrace/.#binds.d @@ -0,0 +1 @@ +mark@bluesnoop.local.63414 \ No newline at end of file diff --git a/examples/dtrace/bindloop.js b/examples/dtrace/bindloop.js new file mode 100644 index 0000000..01fc254 --- /dev/null +++ b/examples/dtrace/bindloop.js @@ -0,0 +1,18 @@ +var assert = require('assert'); + +var ldap = require('../../lib/index'); + +var client = ldap.createClient({ + url: 'ldap://localhost:1389' +}); + + +var finished = 0; +var ITERATIONS = 1024; +for (var i = 0; i < ITERATIONS; i++) { + client.bind('cn=root', 'secret', function(err) { + assert.ifError(err); + if (++finished === ITERATIONS) + client.unbind(); + }); +} diff --git a/examples/dtrace/binds.d b/examples/dtrace/binds.d new file mode 100644 index 0000000..42cd7e1 --- /dev/null +++ b/examples/dtrace/binds.d @@ -0,0 +1,12 @@ +ldapjs*::bind:entry +{ + /*self->start = timestamp;*/ +} +ldapjs*:::return +{ + /* + /self->start/ + @ = quantize(timestamp - self->start); + self->start = 0;" + */ +} diff --git a/examples/in_memory.js b/examples/in_memory.js new file mode 100644 index 0000000..8d911a9 --- /dev/null +++ b/examples/in_memory.js @@ -0,0 +1,201 @@ +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=joyent'; +var db = {}; + +//ldap.log4js.setLevel('Trace'); +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/lib/dtrace.js b/lib/dtrace.js new file mode 100644 index 0000000..c8ad3af --- /dev/null +++ b/lib/dtrace.js @@ -0,0 +1,51 @@ +// Copyright 2011 Mark Cavage, Inc. All rights reserved. + +var dtrace = require('dtrace-provider'); + + + +///--- Globals + +var SERVER_PROVIDER; + +/* Args: + * 0 -> remoteIP + * 1 -> bindDN + * 2 -> req.dn + * 3..5 -> op specific + */ +var SERVER_PROBES = { + + add: ['char "*', 'char *', 'char *'], + bind: ['char "*', 'char *', 'char *'], + unbind: ['char "*', 'char *', 'char *'], + connection: ['char *'] + +}; + + +///--- API + +module.exports = { + + ServerProbes: SERVER_PROBES, + + + serverProvider: function() { + if (!SERVER_PROVIDER) { + SERVER_PROVIDER = dtrace.createDTraceProvider('ldapjs'); + + Object.keys(SERVER_PROBES).forEach(function(p) { + var args = SERVER_PROBES[p].splice(0); + args.unshift(p); + console.log('%j', args); + dtrace.DTraceProvider.prototype.addProbe.apply(SERVER_PROVIDER, args); + }); + + SERVER_PROVIDER.enable(); + } + + return SERVER_PROVIDER; + } + +}; diff --git a/lib/server.js b/lib/server.js index 32f0ef0..c498deb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -10,6 +10,7 @@ var asn1 = require('asn1'); var sprintf = require('sprintf').sprintf; var dn = require('./dn'); +var dtrace = require('./dtrace'); var errors = require('./errors'); var Protocol = require('./protocol'); var logStub = require('./log_stub'); @@ -215,6 +216,7 @@ function Server(options) { EventEmitter.call(this, options); + this.dtrace = dtrace.serverProvider(); this.log = options.log4js.getLogger('Server'); var log = this.log; @@ -263,6 +265,7 @@ function Server(options) { setupConnection(c); if (log.isTraceEnabled()) log.trace('new connection from %s', c.ldap.id); + self.dtrace.fire('connection', function() { return [c.remoteAddress]; }); c.parser = new Parser({ log4js: options.log4js @@ -387,9 +390,17 @@ module.exports = Server; * @throws {TypeError} on bad input */ Server.prototype.add = function(name) { - return this._mount(Protocol.LDAP_REQ_ADD, - name, - Array.prototype.slice.call(arguments, 1)); + var self = this; + + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(function(req, res, next) { + self.dtrace.fire('add', function() { + return [c.remoteAddress, c.ldap.bindDN.toString(), c.dn.toString()]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_ADD, name, args); }; @@ -404,9 +415,18 @@ Server.prototype.add = function(name) { * @throws {TypeError} on bad input */ Server.prototype.bind = function(name) { - return this._mount(Protocol.LDAP_REQ_BIND, - name, - Array.prototype.slice.call(arguments, 1)); + var self = this; + + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(function(req, res, next) { + self.dtrace.fire('bind', function() { + return [c.remoteAddress, c.ldap.bindDN.toString(), c.dn.toString()]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_BIND, name, args); + }; @@ -523,6 +543,16 @@ Server.prototype.search = function(name) { * @throws {TypeError} on bad input */ Server.prototype.unbind = function() { + var self = this; + + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(function(req, res, next) { + self.dtrace.fire('unbind', function() { + return [c.remoteAddress, c.ldap.bindDN.toString(), c.dn.toString()]; + }); + return next(); + }); + return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', Array.prototype.slice.call(arguments, 0), diff --git a/package.json b/package.json index 8a3f86c..d8ee765 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "asn1": "~0.1.5", "buffertools": "~1.0.3", + "dtrace-provider": "~0.0.2", "sprintf": "~0.1.1" }, "devDependencies": {