From 695f2e4367854e84d9ad382c3ceb48dd3eac1ee2 Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Mon, 29 Aug 2011 17:24:50 -0700 Subject: [PATCH 1/3] Initial DTrace prototype --- examples/dtrace/.#binds.d | 1 + examples/dtrace/bindloop.js | 18 ++++ examples/dtrace/binds.d | 12 +++ examples/in_memory.js | 201 ++++++++++++++++++++++++++++++++++++ lib/dtrace.js | 51 +++++++++ lib/server.js | 42 ++++++-- package.json | 1 + 7 files changed, 320 insertions(+), 6 deletions(-) create mode 120000 examples/dtrace/.#binds.d create mode 100644 examples/dtrace/bindloop.js create mode 100644 examples/dtrace/binds.d create mode 100644 examples/in_memory.js create mode 100644 lib/dtrace.js 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": { From 7f43ade50b79fbfcf57710475c8bc91f914461f6 Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Tue, 30 Aug 2011 04:48:05 +0000 Subject: [PATCH 2/3] Initial DTrace prototype --- lib/dtrace.js | 34 ++++++++++-- lib/messages/result.js | 13 ++++- lib/server.js | 123 +++++++++++++++++++++++++++++++---------- 3 files changed, 135 insertions(+), 35 deletions(-) diff --git a/lib/dtrace.js b/lib/dtrace.js index c8ad3af..7b78bce 100644 --- a/lib/dtrace.js +++ b/lib/dtrace.js @@ -16,11 +16,35 @@ var SERVER_PROVIDER; */ var SERVER_PROBES = { - add: ['char "*', 'char *', 'char *'], - bind: ['char "*', 'char *', 'char *'], - unbind: ['char "*', 'char *', 'char *'], - connection: ['char *'] + // 3: attributes.length + add: ['char *', 'char *', 'char *', 'int'], + bind: ['char *', 'char *', 'char *'], + + // 3: attribute, 4: value + compare: ['char *', 'char *', 'char *', 'char *', 'char *'], + + 'delete': ['char *', 'char *', 'char *'], + + // 3: requestName, 4: requestValue + exop: ['char *', 'char *', 'char *', 'char *', 'char *'], + + // 3: changes.length + modify: ['char *', 'char *', 'char *', 'int'], + + // 3: newRdn, 4: deleteOldRdn, 5: newSuperior + modifyDN: ['char *', 'char *', 'char *', 'char *', 'int', 'char *'], + + // 3: filter, 4: scope, 5: attributes.length + search: ['char *', 'char *', 'char *', 'char *', 'char *', 'int'], + + unbind: ['char *', 'char *', 'char *'], + + // remote IP + connection: ['char *'], + + // statusCode, matchedDN, error message, remoteAddress, bindDN, req.dn + result: ['int', 'char *', 'char *', 'char *', 'char *', 'char *'] }; @@ -38,7 +62,7 @@ module.exports = { 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); }); diff --git a/lib/messages/result.js b/lib/messages/result.js index ecc20cc..8f72785 100644 --- a/lib/messages/result.js +++ b/lib/messages/result.js @@ -8,7 +8,7 @@ var asn1 = require('asn1'); var LDAPMessage = require('./message'); var Protocol = require('../protocol'); - +var dtrace = require('../dtrace').serverProvider(); ///--- Globals @@ -17,6 +17,7 @@ var Ber = asn1.Ber; var BerWriter = asn1.BerWriter; + ///--- API function LDAPResult(options) { @@ -66,11 +67,21 @@ LDAPResult.prototype.end = function(status) { this.log.debug('%s: sending: %j', this.connection.ldap.id, this.json); try { + var self = this; this.connection.write(ber); + dtrace.fire('result', function() { + return [self.status, + (self.matchedDN || ''), + (self.errorMessage || ''), + (self.connection ? self.connection.remoteAddress : ''), + (self.connection ? self.connection.ldap.bindDN.toString() : ''), + (self.requestDN ? self.requestDN.toString() : '')]; + }); } catch (e) { this.log.warn('%s failure to write message %j: %s', this.connection.ldap.id, this.json, e.toString()); } + }; diff --git a/lib/server.js b/lib/server.js index c498deb..e18fc4d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -228,9 +228,12 @@ function Server(options) { c.remotePort = c.fd; } + var rdn = new dn.RDN(); + rdn.cn = 'anonymous' c.ldap = { id: c.remoteAddress + ':' + c.remotePort, - config: options + config: options, + _bindDN: new DN([rdn]) }; c.addListener('timeout', function() { log.trace('%s timed out', c.ldap.id); @@ -249,7 +252,7 @@ function Server(options) { }); c.ldap.__defineGetter__('bindDN', function() { - return c.ldap._bindDN || new DN([{cn: 'anonymous'}]); + return c.ldap._bindDN; }); c.ldap.__defineSetter__('bindDN', function(val) { if (!(val instanceof DN)) @@ -284,6 +287,9 @@ function Server(options) { return; } + res.connection = c; + res.requestDN = req.dn; + var chain = self._getHandlerChain(req); var i = 0; @@ -395,7 +401,9 @@ Server.prototype.add = function(name) { 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()]; + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), + req.attributes.length]; }); return next(); }); @@ -420,13 +428,13 @@ Server.prototype.bind = function(name) { 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()]; + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString()]; }); return next(); }); return this._mount(Protocol.LDAP_REQ_BIND, name, args); - }; @@ -441,9 +449,19 @@ Server.prototype.bind = function(name) { * @throws {TypeError} on bad input */ Server.prototype.compare = function(name) { - return this._mount(Protocol.LDAP_REQ_COMPARE, - 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('compare', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), + req.attribute, req.value]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_COMPARE, name, args); }; @@ -458,9 +476,18 @@ Server.prototype.compare = function(name) { * @throws {TypeError} on bad input */ Server.prototype.del = function(name) { - return this._mount(Protocol.LDAP_REQ_DELETE, - 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('delete', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString()]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_DELETE, name, args); }; @@ -475,10 +502,19 @@ Server.prototype.del = function(name) { * @throws {TypeError} on bad input. */ Server.prototype.exop = function(name) { - return this._mount(Protocol.LDAP_REQ_EXTENSION, - name, - Array.prototype.slice.call(arguments, 1), - true); + var self = this; + + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(function(req, res, next) { + self.dtrace.fire('exop', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.name, + req.name, req.value]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true); }; @@ -493,9 +529,19 @@ Server.prototype.exop = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modify = function(name) { - return this._mount(Protocol.LDAP_REQ_MODIFY, - 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('modify', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), + req.changes.length]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_MODIFY, name, args); }; @@ -510,9 +556,20 @@ Server.prototype.modify = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modifyDN = function(name) { - return this._mount(Protocol.LDAP_REQ_MODRDN, - 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('modifyDN', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), + req.newRdn.toString(), (req.deleteOldRdn ? 1 : 0), + (req.newSuperior ? req.newSuperior.toString() : '')]; + }); + return next(); + }); + + return this._mount(Protocol.LDAP_REQ_MODRDN, name, args); }; @@ -527,9 +584,20 @@ Server.prototype.modifyDN = function(name) { * @throws {TypeError} on bad input */ Server.prototype.search = function(name) { - return this._mount(Protocol.LDAP_REQ_SEARCH, - 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('search', function() { + var c = req.connection; + return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), + req.filter.toString(), req.scope, req.attributes.length]; + }); + return next(); + }); + + + return this._mount(Protocol.LDAP_REQ_SEARCH, name, args); }; @@ -545,7 +613,7 @@ Server.prototype.search = function(name) { Server.prototype.unbind = function() { var self = this; - var args = Array.prototype.slice.call(arguments, 1); + var args = Array.prototype.slice.call(arguments, 0); args.unshift(function(req, res, next) { self.dtrace.fire('unbind', function() { return [c.remoteAddress, c.ldap.bindDN.toString(), c.dn.toString()]; @@ -553,10 +621,7 @@ Server.prototype.unbind = function() { return next(); }); - return this._mount(Protocol.LDAP_REQ_UNBIND, - 'unbind', - Array.prototype.slice.call(arguments, 0), - true); + return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true); }; From 7aa1bfe586ac65ded6992a73d32bf3308a41663a Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Tue, 30 Aug 2011 18:12:34 +0000 Subject: [PATCH 3/3] DTrace rework; now supporting a conn/message specific ID so you can track latency --- examples/dtrace/.#binds.d | 1 - examples/dtrace/bindloop.js | 18 --- examples/dtrace/binds.d | 12 -- examples/in_memory.js | 201 --------------------------------- lib/dn.js | 12 +- lib/dtrace.js | 75 ++++++------ lib/messages/result.js | 20 ++-- lib/messages/unbind_request.js | 12 +- lib/server.js | 165 ++++++++++++--------------- 9 files changed, 142 insertions(+), 374 deletions(-) delete mode 120000 examples/dtrace/.#binds.d delete mode 100644 examples/dtrace/bindloop.js delete mode 100644 examples/dtrace/binds.d delete mode 100644 examples/in_memory.js diff --git a/examples/dtrace/.#binds.d b/examples/dtrace/.#binds.d deleted file mode 120000 index bd615d0..0000000 --- a/examples/dtrace/.#binds.d +++ /dev/null @@ -1 +0,0 @@ -mark@bluesnoop.local.63414 \ No newline at end of file diff --git a/examples/dtrace/bindloop.js b/examples/dtrace/bindloop.js deleted file mode 100644 index 01fc254..0000000 --- a/examples/dtrace/bindloop.js +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 42cd7e1..0000000 --- a/examples/dtrace/binds.d +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 8d911a9..0000000 --- a/examples/in_memory.js +++ /dev/null @@ -1,201 +0,0 @@ -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/dn.js b/lib/dn.js index 8cd7a77..45b7cb1 100644 --- a/lib/dn.js +++ b/lib/dn.js @@ -21,7 +21,17 @@ function isWhitespace(c) { return re.test(c); } -function RDN() {} +function RDN(obj) { + var self = this; + + if (obj) { + Object.keys(obj).forEach(function(k) { + self[k] = obj[k]; + }); + } +} + + RDN.prototype.toString = function() { var self = this; diff --git a/lib/dtrace.js b/lib/dtrace.js index 7b78bce..15498aa 100644 --- a/lib/dtrace.js +++ b/lib/dtrace.js @@ -9,67 +9,64 @@ var dtrace = require('dtrace-provider'); var SERVER_PROVIDER; /* Args: - * 0 -> remoteIP - * 1 -> bindDN - * 2 -> req.dn - * 3..5 -> op specific + * 0 -> RequestId (::) + * 1 -> remoteIP + * 2 -> bindDN + * 3 -> req.dn + * 4,5 -> op specific */ var SERVER_PROBES = { - // 3: attributes.length - add: ['char *', 'char *', 'char *', 'int'], + // 4: attributes.length + add: ['char *', 'char *', 'char *', 'char *', 'int'], - bind: ['char *', 'char *', 'char *'], + bind: ['char *', 'char *', 'char *', 'char *'], - // 3: attribute, 4: value - compare: ['char *', 'char *', 'char *', 'char *', 'char *'], + // 4: attribute, 5: value + compare: ['char *', 'char *', 'char *', 'char *', 'char *', 'char *'], - 'delete': ['char *', 'char *', 'char *'], + 'delete': ['char *', 'char *', 'char *', 'char *'], - // 3: requestName, 4: requestValue - exop: ['char *', 'char *', 'char *', 'char *', 'char *'], + // 4: requestName, 5: requestValue + exop: ['char *', 'char *', 'char *', 'char *', 'char *', 'char *'], - // 3: changes.length - modify: ['char *', 'char *', 'char *', 'int'], + // 4: changes.length + modify: ['char *', 'char *', 'char *', 'char *', 'int'], - // 3: newRdn, 4: deleteOldRdn, 5: newSuperior - modifyDN: ['char *', 'char *', 'char *', 'char *', 'int', 'char *'], + // 4: newRdn, 5: newSuperior + modifyDN: ['char *', 'char *', 'char *', 'char *', 'char *', 'char *'], - // 3: filter, 4: scope, 5: attributes.length - search: ['char *', 'char *', 'char *', 'char *', 'char *', 'int'], + // 4: filter, 5: scope + search: ['char *', 'char *', 'char *', 'char *', 'char *', 'char *'], - unbind: ['char *', 'char *', 'char *'], + unbind: ['char *', 'char *', 'char *', 'char *'], // remote IP connection: ['char *'], - // statusCode, matchedDN, error message, remoteAddress, bindDN, req.dn - result: ['int', 'char *', 'char *', 'char *', 'char *', 'char *'] + request: ['char *', 'char *', 'char *', 'char *'], + + // requestId, remoteIp, bindDN, request.dn, statusCode, errorMessage + response: ['char *', 'char *', 'char *', 'char *', 'int', 'char *'] }; ///--- API -module.exports = { +module.exports = function() { + if (!SERVER_PROVIDER) { + SERVER_PROVIDER = dtrace.createDTraceProvider('ldapjs'); - ServerProbes: SERVER_PROBES, + Object.keys(SERVER_PROBES).forEach(function(p) { + var args = SERVER_PROBES[p].splice(0); + args.unshift(p); + dtrace.DTraceProvider.prototype.addProbe.apply(SERVER_PROVIDER, args); + }); - 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); - - dtrace.DTraceProvider.prototype.addProbe.apply(SERVER_PROVIDER, args); - }); - - SERVER_PROVIDER.enable(); - } - - return SERVER_PROVIDER; + SERVER_PROVIDER.enable(); } -}; + return SERVER_PROVIDER; +}(); + diff --git a/lib/messages/result.js b/lib/messages/result.js index 8f72785..1d31e4e 100644 --- a/lib/messages/result.js +++ b/lib/messages/result.js @@ -8,7 +8,7 @@ var asn1 = require('asn1'); var LDAPMessage = require('./message'); var Protocol = require('../protocol'); -var dtrace = require('../dtrace').serverProvider(); +var dtrace = require('../dtrace'); ///--- Globals @@ -69,13 +69,17 @@ LDAPResult.prototype.end = function(status) { try { var self = this; this.connection.write(ber); - dtrace.fire('result', function() { - return [self.status, - (self.matchedDN || ''), - (self.errorMessage || ''), - (self.connection ? self.connection.remoteAddress : ''), - (self.connection ? self.connection.ldap.bindDN.toString() : ''), - (self.requestDN ? self.requestDN.toString() : '')]; + + dtrace.fire('response', function() { + var c = self.connection || {ldap: {bindDN: ''}}; + return [ + (self.logId || ''), + (c.remoteAddress || ''), + c.ldap.bindDN.toString(), + (self.requestDN ? self.requestDN.toString() : ''), + self.status, + self.errorMessage + ]; }); } catch (e) { this.log.warn('%s failure to write message %j: %s', diff --git a/lib/messages/unbind_request.js b/lib/messages/unbind_request.js index 98a9d18..79f4a1f 100644 --- a/lib/messages/unbind_request.js +++ b/lib/messages/unbind_request.js @@ -8,7 +8,7 @@ var asn1 = require('asn1'); var LDAPMessage = require('./message'); var LDAPResult = require('./result'); -var DN = require('../dn').DN; +var dn = require('../dn'); var Protocol = require('../protocol'); @@ -17,6 +17,8 @@ var Protocol = require('../protocol'); var Ber = asn1.Ber; +var DN = dn.DN; +var RDN = dn.RDN; ///--- API @@ -32,8 +34,14 @@ function UnbindRequest(options) { options.protocolOp = Protocol.LDAP_REQ_UNBIND; LDAPMessage.call(this, options); + var self = this; this.__defineGetter__('type', function() { return 'UnbindRequest'; }); - this.__defineGetter__('_dn', function() { return new DN([{}]); }); + this.__defineGetter__('_dn', function() { + if (self.connection) + return self.connection.ldap.bindDN; + + return new DN([new RDN({cn: 'anonymous'})]); + }); } util.inherits(UnbindRequest, LDAPMessage); module.exports = UnbindRequest; diff --git a/lib/server.js b/lib/server.js index e18fc4d..5db5ec6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -174,6 +174,25 @@ function noExOpHandler(req, res, next) { } +function getDTraceHander(op, cb1, cb2) { + assert.ok(op); + + return function(req, res, next) { + dtrace.fire(op, function() { + var c = req.connection; + return [ + req.logId, + c.remoteAddress, + c.ldap.bindDN.toString(), + req.dn.toString(), + cb1 ? cb1(req, res) : undefined, + cb2 ? cb2(req, res) : undefined + ]; + }); + return next(); + }; +} + ///--- API @@ -216,7 +235,6 @@ function Server(options) { EventEmitter.call(this, options); - this.dtrace = dtrace.serverProvider(); this.log = options.log4js.getLogger('Server'); var log = this.log; @@ -228,8 +246,8 @@ function Server(options) { c.remotePort = c.fd; } - var rdn = new dn.RDN(); - rdn.cn = 'anonymous' + var rdn = new dn.RDN({cn: 'anonymous'}); + c.ldap = { id: c.remoteAddress + ':' + c.remotePort, config: options, @@ -268,7 +286,8 @@ 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]; }); + + dtrace.fire('connection', function() { return [c.remoteAddress]; }); c.parser = new Parser({ log4js: options.log4js @@ -280,6 +299,15 @@ function Server(options) { if (log.isDebugEnabled()) log.debug('%s: message received: req=%j', c.ldap.id, req.json); + dtrace.fire('request', function() { + return [ + req.logId, + c.remoteAddress, + c.ldap.bindDN.toString(), + req.dn.toString() + ]; + }); + var res = getResponse(req); if (!res) { log.warn('Unimplemented server method: %s', req.type); @@ -288,6 +316,7 @@ function Server(options) { } res.connection = c; + res.logId = req.logId; res.requestDN = req.dn; var chain = self._getHandlerChain(req); @@ -396,17 +425,11 @@ module.exports = Server; * @throws {TypeError} on bad input */ Server.prototype.add = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('add', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), - req.attributes.length]; - }); - return next(); - }); + args.unshift(getDTraceHander('add', + function(req, res) { + return req.attributes.length; + })); return this._mount(Protocol.LDAP_REQ_ADD, name, args); }; @@ -423,16 +446,8 @@ Server.prototype.add = function(name) { * @throws {TypeError} on bad input */ Server.prototype.bind = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('bind', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString()]; - }); - return next(); - }); + args.unshift(getDTraceHander('bind')); return this._mount(Protocol.LDAP_REQ_BIND, name, args); }; @@ -449,17 +464,14 @@ Server.prototype.bind = function(name) { * @throws {TypeError} on bad input */ Server.prototype.compare = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('compare', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), - req.attribute, req.value]; - }); - return next(); - }); + args.unshift(getDTraceHander('compare', + function(req, res) { + return req.attribute; + }, + function(req, res) { + return req.value; + })); return this._mount(Protocol.LDAP_REQ_COMPARE, name, args); }; @@ -476,16 +488,8 @@ Server.prototype.compare = function(name) { * @throws {TypeError} on bad input */ Server.prototype.del = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('delete', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString()]; - }); - return next(); - }); + args.unshift(getDTraceHander('delete')); return this._mount(Protocol.LDAP_REQ_DELETE, name, args); }; @@ -502,17 +506,14 @@ Server.prototype.del = function(name) { * @throws {TypeError} on bad input. */ Server.prototype.exop = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('exop', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.name, - req.name, req.value]; - }); - return next(); - }); + args.unshift(getDTraceHander('exop', + function(req, res) { + return req.name; + }, + function(req, res) { + return req.value; + })); return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true); }; @@ -529,17 +530,11 @@ Server.prototype.exop = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modify = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('modify', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), - req.changes.length]; - }); - return next(); - }); + args.unshift(getDTraceHander('modify', + function(req, res) { + return req.changes.length; + })); return this._mount(Protocol.LDAP_REQ_MODIFY, name, args); }; @@ -556,18 +551,15 @@ Server.prototype.modify = function(name) { * @throws {TypeError} on bad input */ Server.prototype.modifyDN = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('modifyDN', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), - req.newRdn.toString(), (req.deleteOldRdn ? 1 : 0), - (req.newSuperior ? req.newSuperior.toString() : '')]; - }); - return next(); - }); + args.unshift(getDTraceHander('modifyDN', + function(req, res) { + return req.newRdn.toString(); + }, + function(req, res) { + return (req.newSuperior ? + req.newSuperior.toString() : ''); + })); return this._mount(Protocol.LDAP_REQ_MODRDN, name, args); }; @@ -584,18 +576,14 @@ Server.prototype.modifyDN = function(name) { * @throws {TypeError} on bad input */ Server.prototype.search = function(name) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - args.unshift(function(req, res, next) { - self.dtrace.fire('search', function() { - var c = req.connection; - return [c.remoteAddress, c.ldap.bindDN.toString(), req.dn.toString(), - req.filter.toString(), req.scope, req.attributes.length]; - }); - return next(); - }); - + args.unshift(getDTraceHander('search', + function(req, res) { + return req.scope; + }, + function(req, res) { + return req.filter.toString(); + })); return this._mount(Protocol.LDAP_REQ_SEARCH, name, args); }; @@ -611,15 +599,8 @@ Server.prototype.search = function(name) { * @throws {TypeError} on bad input */ Server.prototype.unbind = function() { - var self = this; - var args = Array.prototype.slice.call(arguments, 0); - args.unshift(function(req, res, next) { - self.dtrace.fire('unbind', function() { - return [c.remoteAddress, c.ldap.bindDN.toString(), c.dn.toString()]; - }); - return next(); - }); + args.unshift(getDTraceHander('unbind')); return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true); };