Comparing version 0.9.1-a to 0.9.2
1131
lib/server.js
const | ||
tls = require('tls'), | ||
net = require('net'), | ||
asn1 = require('asn1'), | ||
util = require('util'), | ||
assert = require('assert'), | ||
dn = require('./dn'), | ||
dtrace = require('./dtrace'), | ||
errors = require('./errors'), | ||
Protocol = require('./protocol'), | ||
Parser = require('./messages').Parser, | ||
AbandonResponse = require('./messages/abandon_response'), | ||
AddResponse = require('./messages/add_response'), | ||
BindResponse = require('./messages/bind_response'), | ||
CompareResponse = require('./messages/compare_response'), | ||
DeleteResponse = require('./messages/del_response'), | ||
ExtendedResponse = require('./messages/ext_response'), | ||
LDAPResult = require('./messages/result'), | ||
ModifyResponse = require('./messages/modify_response'), | ||
ModifyDNResponse = require('./messages/moddn_response'), | ||
SearchRequest = require('./messages/search_request'), | ||
SearchResponse = require('./messages/search_response'), | ||
UnbindResponse = require('./messages/unbind_response'), | ||
VError = require('verror').VError, | ||
EventEmitter = require('events').EventEmitter | ||
var assert = require('assert'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var net = require('net'); | ||
var tls = require('tls'); | ||
var util = require('util'); | ||
//# Globals | ||
var asn1 = require('asn1'); | ||
var VError = require('verror').VError; | ||
let | ||
Ber = asn1.Ber, BerReader = asn1.BerReader, | ||
DN = dn.DN, sprintf = util.format | ||
var dn = require('./dn'); | ||
var dtrace = require('./dtrace'); | ||
var errors = require('./errors'); | ||
var Protocol = require('./protocol'); | ||
//# Helpers | ||
var Parser = require('./messages').Parser; | ||
var AbandonResponse = require('./messages/abandon_response'); | ||
var AddResponse = require('./messages/add_response'); | ||
var BindResponse = require('./messages/bind_response'); | ||
var CompareResponse = require('./messages/compare_response'); | ||
var DeleteResponse = require('./messages/del_response'); | ||
var ExtendedResponse = require('./messages/ext_response'); | ||
var LDAPResult = require('./messages/result'); | ||
var ModifyResponse = require('./messages/modify_response'); | ||
var ModifyDNResponse = require('./messages/moddn_response'); | ||
var SearchRequest = require('./messages/search_request'); | ||
var SearchResponse = require('./messages/search_response'); | ||
var UnbindResponse = require('./messages/unbind_response'); | ||
///--- Globals | ||
var Ber = asn1.Ber; | ||
var BerReader = asn1.BerReader; | ||
var DN = dn.DN; | ||
var sprintf = util.format; | ||
///--- Helpers | ||
function mergeFunctionArgs(argv, start, end) { | ||
assert.ok(argv) | ||
if (!start) start = 0 | ||
if (!end) end = argv.length | ||
let handlers = [] | ||
assert.ok(argv); | ||
for (let i = start; i < end; i++) { | ||
if (!start) | ||
start = 0; | ||
if (!end) | ||
end = argv.length; | ||
var handlers = []; | ||
for (var i = start; i < end; i++) { | ||
if (argv[i] instanceof Array) { | ||
let arr = argv[i] | ||
for (let j = 0; j < arr.length; j++) { | ||
if (!(arr[j] instanceof Function)) throw new TypeError('Invalid argument type: ' + typeof (arr[j])) | ||
handlers.push(arr[j]) | ||
var arr = argv[i]; | ||
for (var j = 0; j < arr.length; j++) { | ||
if (!(arr[j] instanceof Function)) { | ||
throw new TypeError('Invalid argument type: ' + typeof (arr[j])); | ||
} | ||
handlers.push(arr[j]); | ||
} | ||
} else if (argv[i] instanceof Function) { | ||
handlers.push(argv[i]) | ||
} else throw new TypeError('Invalid argument type: ' + typeof (argv[i])) | ||
handlers.push(argv[i]); | ||
} else { | ||
throw new TypeError('Invalid argument type: ' + typeof (argv[i])); | ||
} | ||
} | ||
return handlers | ||
return handlers; | ||
} | ||
@@ -61,32 +74,51 @@ | ||
function getResponse(req) { | ||
assert.ok(req); | ||
assert.ok(req) | ||
let Response | ||
var Response; | ||
switch (req.protocolOp) { | ||
case Protocol.LDAP_REQ_BIND : Response = BindResponse; break | ||
case Protocol.LDAP_REQ_ABANDON : Response = AbandonResponse; break | ||
case Protocol.LDAP_REQ_ADD : Response = AddResponse; break | ||
case Protocol.LDAP_REQ_COMPARE : Response = CompareResponse; break | ||
case Protocol.LDAP_REQ_DELETE : Response = DeleteResponse; break | ||
case Protocol.LDAP_REQ_EXTENSION : Response = ExtendedResponse; break | ||
case Protocol.LDAP_REQ_MODIFY : Response = ModifyResponse; break | ||
case Protocol.LDAP_REQ_MODRDN : Response = ModifyDNResponse; break | ||
case Protocol.LDAP_REQ_SEARCH : Response = SearchResponse; break | ||
case Protocol.LDAP_REQ_UNBIND : Response = UnbindResponse; break | ||
default: return null | ||
case Protocol.LDAP_REQ_BIND: | ||
Response = BindResponse; | ||
break; | ||
case Protocol.LDAP_REQ_ABANDON: | ||
Response = AbandonResponse; | ||
break; | ||
case Protocol.LDAP_REQ_ADD: | ||
Response = AddResponse; | ||
break; | ||
case Protocol.LDAP_REQ_COMPARE: | ||
Response = CompareResponse; | ||
break; | ||
case Protocol.LDAP_REQ_DELETE: | ||
Response = DeleteResponse; | ||
break; | ||
case Protocol.LDAP_REQ_EXTENSION: | ||
Response = ExtendedResponse; | ||
break; | ||
case Protocol.LDAP_REQ_MODIFY: | ||
Response = ModifyResponse; | ||
break; | ||
case Protocol.LDAP_REQ_MODRDN: | ||
Response = ModifyDNResponse; | ||
break; | ||
case Protocol.LDAP_REQ_SEARCH: | ||
Response = SearchResponse; | ||
break; | ||
case Protocol.LDAP_REQ_UNBIND: | ||
Response = UnbindResponse; | ||
break; | ||
default: | ||
return null; | ||
} | ||
assert.ok(Response); | ||
assert.ok(Response) | ||
let res = new Response({ | ||
var res = new Response({ | ||
messageID: req.messageID, | ||
log: req.log, | ||
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined) | ||
}) | ||
}); | ||
res.connection = req.connection; | ||
res.logId = req.logId; | ||
res.connection = req.connection | ||
res.logId = req.logId | ||
return res | ||
return res; | ||
} | ||
@@ -96,108 +128,172 @@ | ||
function defaultHandler(req, res, next) { | ||
assert.ok(req) | ||
assert.ok(res) | ||
assert.ok(next) | ||
res.matchedDN = req.dn.toString() | ||
res.errorMessage = 'Server method not implemented' | ||
res.end(errors.LDAP_OTHER) | ||
return next() | ||
assert.ok(req); | ||
assert.ok(res); | ||
assert.ok(next); | ||
res.matchedDN = req.dn.toString(); | ||
res.errorMessage = 'Server method not implemented'; | ||
res.end(errors.LDAP_OTHER); | ||
return next(); | ||
} | ||
function defaultNoOpHandler(req, res, next) { | ||
assert.ok(req) | ||
assert.ok(res) | ||
assert.ok(next) | ||
res.end() | ||
return next() | ||
assert.ok(req); | ||
assert.ok(res); | ||
assert.ok(next); | ||
res.end(); | ||
return next(); | ||
} | ||
function noSuffixHandler(req, res, next) { | ||
assert.ok(req) | ||
assert.ok(res) | ||
assert.ok(next) | ||
res.errorMessage = 'No tree found for: ' + req.dn.toString() | ||
res.end(errors.LDAP_NO_SUCH_OBJECT) | ||
return next() | ||
assert.ok(req); | ||
assert.ok(res); | ||
assert.ok(next); | ||
res.errorMessage = 'No tree found for: ' + req.dn.toString(); | ||
res.end(errors.LDAP_NO_SUCH_OBJECT); | ||
return next(); | ||
} | ||
function noExOpHandler(req, res, next) { | ||
assert.ok(req) | ||
assert.ok(res) | ||
assert.ok(next) | ||
res.errorMessage = req.requestName + ' not supported' | ||
res.end(errors.LDAP_PROTOCOL_ERROR) | ||
return next() | ||
assert.ok(req); | ||
assert.ok(res); | ||
assert.ok(next); | ||
res.errorMessage = req.requestName + ' not supported'; | ||
res.end(errors.LDAP_PROTOCOL_ERROR); | ||
return next(); | ||
} | ||
function fireDTraceProbe(req, res) { | ||
assert.ok(req); | ||
assert.ok(req) | ||
req._dtraceId = res._dtraceId = dtrace._nextId() | ||
req._dtraceId = res._dtraceId = dtrace._nextId(); | ||
var probeArgs = [ | ||
req._dtraceId, | ||
req.connection.remoteAddress || 'localhost', | ||
req.connection.ldap.bindDN.toString(), | ||
req.dn.toString() | ||
]; | ||
let probeArgs = [ | ||
req._dtraceId, req.connection.remoteAddress || 'localhost', | ||
req.connection.ldap.bindDN.toString(), req.dn.toString() | ||
] | ||
let op | ||
var op; | ||
switch (req.protocolOp) { | ||
case Protocol.LDAP_REQ_ABANDON : op = 'abandon'; break | ||
case Protocol.LDAP_REQ_BIND : op = 'bind'; break | ||
case Protocol.LDAP_REQ_DELETE : op = 'delete'; break | ||
case Protocol.LDAP_REQ_UNBIND : op = 'unbind'; break | ||
case Protocol.LDAP_REQ_MODIFY : op = 'modify'; probeArgs.push(req.changes.length); break | ||
case Protocol.LDAP_REQ_SEARCH : op = 'search'; probeArgs.push(req.scope); probeArgs.push(req.filter.toString()); break | ||
case Protocol.LDAP_REQ_EXTENSION : op = 'exop'; probeArgs.push(req.name); probeArgs.push(req.value); break | ||
case Protocol.LDAP_REQ_COMPARE : op = 'compare'; probeArgs.push(req.attribute); probeArgs.push(req.value); break | ||
case Protocol.LDAP_REQ_MODRDN : op = 'modifydn'; probeArgs.push(req.newRdn.toString()); | ||
probeArgs.push((req.newSuperior ? req.newSuperior.toString() : '')); break | ||
default: break | ||
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; | ||
default: | ||
break; | ||
} | ||
res._dtraceOp = op | ||
dtrace.fire('server-' + op + '-start', () => { | ||
return probeArgs | ||
}) | ||
res._dtraceOp = op; | ||
dtrace.fire('server-' + op + '-start', function () { | ||
return probeArgs; | ||
}); | ||
} | ||
//# API | ||
///--- API | ||
/** | ||
* Constructs a new server that you can call .listen() on, in the various | ||
* forms node supports. You need to first assign some handlers to the various | ||
* LDAP operations however. | ||
* | ||
* The options object currently only takes a certificate/private key, and a | ||
* bunyan logger handle. | ||
* | ||
* This object exposes the following events: | ||
* - 'error' | ||
* - 'close' | ||
* | ||
* @param {Object} options (optional) parameterization object. | ||
* @throws {TypeError} on bad input. | ||
*/ | ||
function Server(options) { | ||
if (options) { | ||
if (typeof (options) !== 'object') | ||
throw new TypeError('options (object) required'); | ||
if (typeof (options.log) !== 'object') | ||
throw new TypeError('options.log must be an object'); | ||
if (options) { | ||
if (typeof (options) !== 'object') throw new TypeError('options (object) required') | ||
if (typeof (options.log) !== 'object') throw new TypeError('options.log must be an object') | ||
if (options.certificate || options.key) { | ||
if (!(options.certificate && options.key) || (typeof (options.certificate) !== 'string' && | ||
!Buffer.isBuffer(options.certificate)) || (typeof (options.key) !== 'string' && | ||
!Buffer.isBuffer(options.key))) | ||
throw new TypeError('options.certificate and options.key ' + '(string or buffer) are both required for TLS') | ||
if (!(options.certificate && options.key) || | ||
(typeof (options.certificate) !== 'string' && | ||
!Buffer.isBuffer(options.certificate)) || | ||
(typeof (options.key) !== 'string' && | ||
!Buffer.isBuffer(options.key))) { | ||
throw new TypeError('options.certificate and options.key ' + | ||
'(string or buffer) are both required for TLS'); | ||
} | ||
} | ||
} else options = {} | ||
} else { | ||
options = {}; | ||
} | ||
var self = this; | ||
let self = this | ||
EventEmitter.call(this, options) | ||
this._chain = [] | ||
this.log = options.log | ||
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true | ||
EventEmitter.call(this, options); | ||
let log = this.log | ||
this._chain = []; | ||
this.log = options.log; | ||
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true; | ||
var log = this.log; | ||
function setupConnection(c) { | ||
assert.ok(c); | ||
assert.ok(c) | ||
if (c.type === 'unix') { | ||
c.remoteAddress = self.server.path | ||
c.remotePort = c.fd | ||
c.remoteAddress = self.server.path; | ||
c.remotePort = c.fd; | ||
} else if (c.socket) { | ||
c.remoteAddress = c.socket.remoteAddress | ||
c.remotePort = c.socket.remotePort | ||
// TLS | ||
c.remoteAddress = c.socket.remoteAddress; | ||
c.remotePort = c.socket.remotePort; | ||
} | ||
let rdn = new dn.RDN({cn: 'anonymous'}) | ||
var rdn = new dn.RDN({cn: 'anonymous'}); | ||
c.ldap = { | ||
@@ -207,163 +303,191 @@ id: c.remoteAddress + ':' + c.remotePort, | ||
_bindDN: new DN([rdn]) | ||
} | ||
}; | ||
c.addListener('timeout', function () { | ||
log.trace('%s timed out', c.ldap.id); | ||
c.destroy(); | ||
}); | ||
c.addListener('end', function () { | ||
log.trace('%s shutdown', c.ldap.id); | ||
}); | ||
c.addListener('error', function (err) { | ||
log.warn('%s unexpected connection error', c.ldap.id, err); | ||
self.emit('clientError', err); | ||
c.destroy(); | ||
}); | ||
c.addListener('close', function (had_err) { | ||
log.trace('%s close; had_err=%j', c.ldap.id, had_err); | ||
c.end(); | ||
}); | ||
c.addListener('timeout', () => { | ||
log.trace('%s timed out', c.ldap.id) | ||
c.destroy() | ||
}) | ||
c.ldap.__defineGetter__('bindDN', function () { | ||
return c.ldap._bindDN; | ||
}); | ||
c.ldap.__defineSetter__('bindDN', function (val) { | ||
if (!(val instanceof DN)) | ||
throw new TypeError('DN required'); | ||
c.addListener('end', () => { | ||
log.trace('%s shutdown', c.ldap.id) | ||
}) | ||
c.addListener('error', (err) => { | ||
log.warn('%s unexpected connection error', c.ldap.id, err) | ||
self.emit('clientError', err) | ||
c.destroy() | ||
}) | ||
c.addListener('close', (had_err) => { | ||
log.trace('%s close had_err=%j', c.ldap.id, had_err) | ||
c.end() | ||
}) | ||
c.ldap.__defineGetter__('bindDN', () => { | ||
return c.ldap._bindDN | ||
}) | ||
c.ldap.__defineSetter__('bindDN', (val) => { | ||
if (!(val instanceof DN)) throw new TypeError('DN required') | ||
c.ldap._bindDN = val | ||
return val | ||
}) | ||
return c | ||
c.ldap._bindDN = val; | ||
return val; | ||
}); | ||
return c; | ||
} | ||
function newConnection(c) { | ||
setupConnection(c); | ||
log.trace('new connection from %s', c.ldap.id); | ||
setupConnection(c) | ||
log.trace('new connection from %s', c.ldap.id) | ||
dtrace.fire('server-connection', () => [c.remoteAddress]) | ||
dtrace.fire('server-connection', function () { | ||
return [c.remoteAddress]; | ||
}); | ||
c.parser = new Parser({ | ||
log: options.log | ||
}) | ||
}); | ||
c.parser.on('message', function (req) { | ||
req.connection = c; | ||
req.logId = c.ldap.id + '::' + req.messageID; | ||
req.startTime = new Date().getTime(); | ||
c.parser.on('message', (req) => { | ||
req.connection = c | ||
req.logId = c.ldap.id + '::' + req.messageID | ||
req.startTime = new Date().getTime() | ||
if (log.debug()) | ||
log.debug('%s: message received: req=%j', c.ldap.id, req.json); | ||
if (log.debug()) log.debug('%s: message received: req=%j', c.ldap.id, req.json) | ||
let res = getResponse(req) | ||
var res = getResponse(req); | ||
if (!res) { | ||
log.warn('Unimplemented server method: %s', req.type) | ||
c.destroy() | ||
return false | ||
log.warn('Unimplemented server method: %s', req.type); | ||
c.destroy(); | ||
return false; | ||
} | ||
// parse string DNs for routing/etc | ||
try { | ||
switch (req.protocolOp) { | ||
case Protocol.LDAP_REQ_BIND: req.name = dn.parse(req.name); break | ||
case Protocol.LDAP_REQ_ADD: | ||
case Protocol.LDAP_REQ_COMPARE: | ||
case Protocol.LDAP_REQ_DELETE: req.entry = dn.parse(req.entry); break | ||
case Protocol.LDAP_REQ_MODIFY: req.object = dn.parse(req.object); break | ||
case Protocol.LDAP_REQ_MODRDN: req.entry = dn.parse(req.entry); break | ||
case Protocol.LDAP_REQ_SEARCH: req.baseObject = dn.parse(req.baseObject); break | ||
default: break | ||
case Protocol.LDAP_REQ_BIND: | ||
req.name = dn.parse(req.name); | ||
break; | ||
case Protocol.LDAP_REQ_ADD: | ||
case Protocol.LDAP_REQ_COMPARE: | ||
case Protocol.LDAP_REQ_DELETE: | ||
req.entry = dn.parse(req.entry); | ||
break; | ||
case Protocol.LDAP_REQ_MODIFY: | ||
req.object = dn.parse(req.object); | ||
break; | ||
case Protocol.LDAP_REQ_MODRDN: | ||
req.entry = dn.parse(req.entry); | ||
// TODO: handle newRdn/Superior | ||
break; | ||
case Protocol.LDAP_REQ_SEARCH: | ||
req.baseObject = dn.parse(req.baseObject); | ||
break; | ||
default: | ||
break; | ||
} | ||
} catch (e) { if (self.strictDN) return res.end(errors.LDAP_INVALID_DN_SYNTAX) } | ||
} catch (e) { | ||
if (self.strictDN) { | ||
return res.end(errors.LDAP_INVALID_DN_SYNTAX); | ||
} | ||
} | ||
res.connection = c | ||
res.logId = req.logId | ||
res.requestDN = req.dn | ||
res.connection = c; | ||
res.logId = req.logId; | ||
res.requestDN = req.dn; | ||
let chain = self._getHandlerChain(req, res) | ||
var chain = self._getHandlerChain(req, res); | ||
let i = 0 | ||
var i = 0; | ||
return function (err) { | ||
function sendError(err) { | ||
res.status = err.code || errors.LDAP_OPERATIONS_ERROR | ||
res.matchedDN = req.suffix ? req.suffix.toString() : '' | ||
res.errorMessage = err.message || '' | ||
return res.end() | ||
res.status = err.code || errors.LDAP_OPERATIONS_ERROR; | ||
res.matchedDN = req.suffix ? req.suffix.toString() : ''; | ||
res.errorMessage = err.message || ''; | ||
return res.end(); | ||
} | ||
function after() { | ||
if (!self._postChain || !self._postChain.length) return | ||
function next() {} | ||
self._postChain.forEach((c) => c.call(self, req, res, next)) | ||
if (!self._postChain || !self._postChain.length) | ||
return; | ||
function next() {} // stub out next for the post chain | ||
self._postChain.forEach(function (c) { | ||
c.call(self, req, res, next); | ||
}); | ||
} | ||
if (err) { | ||
log.trace('%s sending error: %s', req.logId, err.stack || err) | ||
self.emit('clientError', err) | ||
sendError(err) | ||
return after() | ||
log.trace('%s sending error: %s', req.logId, err.stack || err); | ||
self.emit('clientError', err); | ||
sendError(err); | ||
return after(); | ||
} | ||
try { | ||
let next = arguments.callee | ||
if (chain.handlers[i]) return chain.handlers[i++].call(chain.backend, req, res, next) | ||
if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) c.ldap.bindDN = req.dn | ||
return after() | ||
var next = arguments.callee; | ||
if (chain.handlers[i]) | ||
return chain.handlers[i++].call(chain.backend, req, res, next); | ||
if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) | ||
c.ldap.bindDN = req.dn; | ||
return after(); | ||
} catch (e) { | ||
if (!e.stack) e.stack = e.toString() | ||
log.error('%s uncaught exception: %s', req.logId, e.stack) | ||
return sendError(new errors.OperationsError(e.message)) | ||
if (!e.stack) | ||
e.stack = e.toString(); | ||
log.error('%s uncaught exception: %s', req.logId, e.stack); | ||
return sendError(new errors.OperationsError(e.message)); | ||
} | ||
}() | ||
}) | ||
}(); | ||
}); | ||
c.parser.on('error', (err, message) => { | ||
self.emit('error', new VError(err, 'Parser error for %s', c.ldap.id)) | ||
c.parser.on('error', function (err, message) { | ||
self.emit('error', new VError(err, 'Parser error for %s', c.ldap.id)); | ||
let res = getResponse(message) | ||
if (!message) return c.destroy() | ||
if (!res) return c.destroy() | ||
if (!message) | ||
return c.destroy(); | ||
res.status = 0x02 // protocol error | ||
res.errorMessage = err.toString() | ||
return c.end(res.toBer()) | ||
var res = getResponse(message); | ||
if (!res) | ||
return c.destroy(); | ||
}) | ||
res.status = 0x02; // protocol error | ||
res.errorMessage = err.toString(); | ||
return c.end(res.toBer()); | ||
}); | ||
c.on('data', (data) => { | ||
if (log.trace()) log.trace('data on %s: %s', c.ldap.id, util.inspect(data)) | ||
c.parser.write(data) | ||
}) | ||
c.on('data', function (data) { | ||
if (log.trace()) | ||
log.trace('data on %s: %s', c.ldap.id, util.inspect(data)); | ||
} | ||
c.parser.write(data); | ||
}); | ||
this.routes = {} | ||
} // end newConnection | ||
this.routes = {}; | ||
if ((options.cert || options.certificate) && options.key) { | ||
options.cert = options.cert || options.certificate | ||
this.server = tls.createServer(options, newConnection) | ||
} else this.server = net.createServer(newConnection) | ||
this.server.log = options.log | ||
this.server.ldap = { config: options } | ||
this.server.on('close', () => self.emit('close')) | ||
this.server.on('error', (e) => self.emit('error', e)) | ||
options.cert = options.cert || options.certificate; | ||
this.server = tls.createServer(options, newConnection); | ||
} else { | ||
this.server = net.createServer(newConnection); | ||
} | ||
this.server.log = options.log; | ||
this.server.ldap = { | ||
config: options | ||
}; | ||
this.server.on('close', function () { | ||
self.emit('close'); | ||
}); | ||
this.server.on('error', function (err) { | ||
self.emit('error', err); | ||
}); | ||
} | ||
util.inherits(Server, EventEmitter) | ||
util.inherits(Server, EventEmitter); | ||
Object.defineProperties(Server.prototype, { | ||
maxConnections: { | ||
get: function getMaxConnections() { | ||
return this.server.maxConnections | ||
return this.server.maxConnections; | ||
}, | ||
set: function setMaxConnections(val) { | ||
this.server.maxConnections = val | ||
this.server.maxConnections = val; | ||
}, | ||
@@ -374,3 +498,3 @@ configurable: false | ||
get: function getConnections() { | ||
return this.server.connections | ||
return this.server.connections; | ||
}, | ||
@@ -381,3 +505,3 @@ configurable: false | ||
get: function getName() { | ||
return 'LDAPServer' | ||
return 'LDAPServer'; | ||
}, | ||
@@ -388,242 +512,411 @@ configurable: false | ||
get: function getURL() { | ||
let str, addr = this.server.address() | ||
if (!addr) return null | ||
var str; | ||
var addr = this.server.address(); | ||
if (!addr) { | ||
return null; | ||
} | ||
if (!addr.family) { | ||
str = 'ldapi://' | ||
str += this.host.replace(new RegExp('/', 'g'), '%2f') | ||
return str | ||
str = 'ldapi://'; | ||
str += this.host.replace(new RegExp('/', 'g'), '%2f'); | ||
return str; | ||
} | ||
if (this.server instanceof tls.Server) str = 'ldaps://' | ||
else str = 'ldap://' | ||
str += this.host + ':' + this.port | ||
return str | ||
if (this.server instanceof tls.Server) { | ||
str = 'ldaps://'; | ||
} else { | ||
str = 'ldap://'; | ||
} | ||
str += this.host + ':' + this.port; | ||
return str; | ||
}, | ||
configurable: false | ||
} | ||
}) | ||
}); | ||
module.exports = Server; | ||
module.exports = Server | ||
Server.prototype.add = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_ADD, name, args) | ||
} | ||
/** | ||
* Adds a handler (chain) for the LDAP add method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.add = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_ADD, name, args); | ||
}; | ||
Server.prototype.bind = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_BIND, name, args) | ||
} | ||
Server.prototype.compare = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_COMPARE, name, args) | ||
} | ||
/** | ||
* Adds a handler (chain) for the LDAP bind method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.bind = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_BIND, name, args); | ||
}; | ||
Server.prototype.del = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_DELETE, name, args) | ||
} | ||
Server.prototype.exop = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true) | ||
} | ||
/** | ||
* Adds a handler (chain) for the LDAP compare method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.compare = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_COMPARE, name, args); | ||
}; | ||
Server.prototype.modify = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_MODIFY, name, args) | ||
} | ||
Server.prototype.modifyDN = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_MODRDN, name, args) | ||
} | ||
/** | ||
* Adds a handler (chain) for the LDAP delete method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.del = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_DELETE, name, args); | ||
}; | ||
Server.prototype.search = (name) => { | ||
let args = Array.prototype.slice.call(arguments, 1) | ||
return this._mount(Protocol.LDAP_REQ_SEARCH, name, args) | ||
} | ||
Server.prototype.unbind = () => { | ||
let args = Array.prototype.slice.call(arguments, 0) | ||
return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true) | ||
} | ||
/** | ||
* Adds a handler (chain) for the LDAP exop method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name OID to assign this handler chain to. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input. | ||
*/ | ||
Server.prototype.exop = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true); | ||
}; | ||
/** | ||
* Adds a handler (chain) for the LDAP modify method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.modify = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_MODIFY, name, args); | ||
}; | ||
/** | ||
* Adds a handler (chain) for the LDAP modifyDN method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.modifyDN = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_MODRDN, name, args); | ||
}; | ||
/** | ||
* Adds a handler (chain) for the LDAP search method. | ||
* | ||
* Note that this is of the form f(name, [function]) where the second...N | ||
* arguments can all either be functions or arrays of functions. | ||
* | ||
* @param {String} name the DN to mount this handler chain at. | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.search = function (name) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
return this._mount(Protocol.LDAP_REQ_SEARCH, name, args); | ||
}; | ||
/** | ||
* Adds a handler (chain) for the LDAP unbind method. | ||
* | ||
* This method is different than the others and takes no mount point, as unbind | ||
* is a connection-wide operation, not constrianed to part of the DIT. | ||
* | ||
* @return {Server} this so you can chain calls. | ||
* @throws {TypeError} on bad input | ||
*/ | ||
Server.prototype.unbind = function () { | ||
var args = Array.prototype.slice.call(arguments, 0); | ||
return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true); | ||
}; | ||
Server.prototype.use = function use() { | ||
let | ||
args = Array.prototype.slice.call(arguments), | ||
chain = mergeFunctionArgs(args, 0, args.length), | ||
self = this | ||
chain.forEach((c) => self._chain.push(c)) | ||
} | ||
var args = Array.prototype.slice.call(arguments); | ||
var chain = mergeFunctionArgs(args, 0, args.length); | ||
var self = this; | ||
chain.forEach(function (c) { | ||
self._chain.push(c); | ||
}); | ||
}; | ||
Server.prototype.after = () => { | ||
if (!this._postChain) this._postChain = [] | ||
let self = this | ||
mergeFunctionArgs(arguments).forEach((h) => self._postChain.push(h)) | ||
} | ||
Server.prototype.listen = (port, host, callback) => { | ||
Server.prototype.after = function () { | ||
if (!this._postChain) | ||
this._postChain = []; | ||
var self = this; | ||
mergeFunctionArgs(arguments).forEach(function (h) { | ||
self._postChain.push(h); | ||
}); | ||
}; | ||
// All these just reexpose the requisite net.Server APIs | ||
Server.prototype.listen = function (port, host, callback) { | ||
if (typeof (port) !== 'number' && typeof (port) !== 'string') | ||
throw new TypeError('port (number or path) required') | ||
throw new TypeError('port (number or path) required'); | ||
if (typeof (host) === 'function') { | ||
callback = host | ||
host = '0.0.0.0' | ||
callback = host; | ||
host = '0.0.0.0'; | ||
} | ||
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) { | ||
port = parseInt(port, 10) | ||
// Disambiguate between string ports and file paths | ||
port = parseInt(port, 10); | ||
} | ||
var self = this; | ||
let self = this | ||
function cbListen() { | ||
if (typeof (port) === 'number') { | ||
self.host = self.address().address | ||
self.port = self.address().port | ||
self.host = self.address().address; | ||
self.port = self.address().port; | ||
} else { | ||
self.host = port | ||
self.port = self.server.fd | ||
self.host = port; | ||
self.port = self.server.fd; | ||
} | ||
if (typeof (callback) === 'function') callback() | ||
if (typeof (callback) === 'function') | ||
callback(); | ||
} | ||
if (typeof (port) === 'number') return this.server.listen(port, host, cbListen) | ||
else return this.server.listen(port, cbListen) | ||
if (typeof (port) === 'number') { | ||
return this.server.listen(port, host, cbListen); | ||
} else { | ||
return this.server.listen(port, cbListen); | ||
} | ||
}; | ||
Server.prototype.listenFD = function (fd) { | ||
this.host = 'unix-domain-socket'; | ||
this.port = fd; | ||
return this.server.listenFD(fd); | ||
}; | ||
Server.prototype.close = function () { | ||
return this.server.close(); | ||
}; | ||
Server.prototype.address = function () { | ||
return this.server.address(); | ||
}; | ||
} | ||
Server.prototype.listenFD = (fd) => { | ||
this.host = 'unix-domain-socket' | ||
this.port = fd | ||
return this.server.listenFD(fd) | ||
} | ||
Server.prototype._getRoute = function (_dn, backend) { | ||
assert.ok(dn); | ||
Server.prototype.close = () => { return this.server.close() } | ||
Server.prototype.address = () => { return this.server.address() } | ||
if (!backend) | ||
backend = this; | ||
Server.prototype._getRoute = (_dn, backend) => { | ||
assert.ok(dn) | ||
var name; | ||
if (_dn instanceof dn.DN) { | ||
name = _dn.toString(); | ||
} else { | ||
name = _dn; | ||
} | ||
if (!backend) backend = this | ||
let name | ||
if (_dn instanceof dn.DN) name = _dn.toString() | ||
else name = _dn | ||
if (!this.routes[name]) { | ||
this.routes[name] = {} | ||
this.routes[name].backend = backend | ||
this.routes[name].dn = _dn | ||
this._routeKeyCache = null | ||
this.routes[name] = {}; | ||
this.routes[name].backend = backend; | ||
this.routes[name].dn = _dn; | ||
// Force regeneration of the route key cache on next request | ||
this._routeKeyCache = null; | ||
} | ||
return this.routes[name] | ||
return this.routes[name]; | ||
}; | ||
} | ||
Server.prototype._sortedRouteKeys = function _sortedRouteKeys() { | ||
// The filtered/sorted route keys are cached to prevent needlessly | ||
// regenerating the list for every incoming request. | ||
if (!this._routeKeyCache) { | ||
let self = this, reversedRDNsToKeys = {} | ||
Object.keys(this.routes).forEach((key) => { | ||
let _dn = self.routes[key].dn | ||
var self = this; | ||
var reversedRDNsToKeys = {}; | ||
// Generate mapping of reversedRDNs(DN) -> routeKey | ||
Object.keys(this.routes).forEach(function (key) { | ||
var _dn = self.routes[key].dn; | ||
// Ignore non-DN routes such as exop or unbind | ||
if (_dn instanceof dn.DN) { | ||
let reversed = _dn.clone() | ||
reversed.rdns.reverse() | ||
reversedRDNsToKeys[reversed.format()] = key | ||
var reversed = _dn.clone(); | ||
reversed.rdns.reverse(); | ||
reversedRDNsToKeys[reversed.format()] = key; | ||
} | ||
}) | ||
let output = [] | ||
Object.keys(reversedRDNsToKeys).sort().reverse().forEach((_dn) => output.push(reversedRDNsToKeys[_dn])) | ||
this._routeKeyCache = output | ||
}); | ||
var output = []; | ||
// Reverse-sort on reversedRDS(DN) in order to output routeKey list. | ||
// This will place more specific DNs in front of their parents: | ||
// 1. dc=test, dc=domain, dc=sub | ||
// 2. dc=test, dc=domain | ||
// 3. dc=other, dc=foobar | ||
Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) { | ||
output.push(reversedRDNsToKeys[_dn]); | ||
}); | ||
this._routeKeyCache = output; | ||
} | ||
return this._routeKeyCache | ||
} | ||
return this._routeKeyCache; | ||
}; | ||
Server.prototype._getHandlerChain = function _getHandlerChain(req, res) { | ||
assert.ok(req) | ||
fireDTraceProbe(req, res) | ||
if (req.protocolOp === Protocol.LDAP_REQ_BIND | ||
&& req.dn.toString() === '' | ||
&& req.credentials === '') return { | ||
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: [defaultNoOpHandler] | ||
} | ||
}; | ||
} | ||
let | ||
op = '0x' + req.protocolOp.toString(16), | ||
self = this, routes = this.routes, route | ||
var op = '0x' + req.protocolOp.toString(16); | ||
var self = this; | ||
var routes = this.routes; | ||
var route; | ||
// Special cases are exops, unbinds and abandons. Handle those first. | ||
if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) { | ||
route = routes[req.requestName] | ||
if (route) | ||
return { backend: route.backend, handlers: (route[op] ? route[op] : [noExOpHandler]) } | ||
else | ||
return { backend: self, handlers: [noExOpHandler] } | ||
route = routes[req.requestName]; | ||
if (route) { | ||
return { | ||
backend: route.backend, | ||
handlers: (route[op] ? route[op] : [noExOpHandler]) | ||
}; | ||
} else { | ||
return { | ||
backend: self, | ||
handlers: [noExOpHandler] | ||
}; | ||
} | ||
} else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) { | ||
route = routes['unbind']; | ||
if (route) { | ||
return { | ||
backend: route.backend, | ||
handlers: route[op] | ||
}; | ||
} else { | ||
return { | ||
backend: self, | ||
handlers: [defaultNoOpHandler] | ||
}; | ||
} | ||
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) { | ||
return { | ||
backend: self, | ||
handlers: [defaultNoOpHandler] | ||
}; | ||
} | ||
route = routes['unbind'] | ||
if (route) | ||
return { backend: route.backend, handlers: route[op] } | ||
else | ||
return { backend: self, handlers: [defaultNoOpHandler] } | ||
} else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) | ||
return { backend: self, handlers: [defaultNoOpHandler] } | ||
// Otherwise, match via DN rules | ||
assert.ok(req.dn); | ||
var keys = this._sortedRouteKeys(); | ||
var fallbackHandler = [noSuffixHandler]; | ||
// invalid DNs in non-strict mode are routed to the default handler | ||
var testDN = (typeof (req.dn) === 'string') ? '' : req.dn; | ||
assert.ok(req.dn) | ||
let | ||
keys = this._sortedRouteKeys(), fallbackHandler = [noSuffixHandler], | ||
testDN = (typeof (req.dn) === 'string') ? '' : req.dn | ||
for (let i = 0; i < keys.length; i++) { | ||
let suffix = keys[i] | ||
route = routes[suffix] | ||
assert.ok(route.dn) | ||
for (var i = 0; i < keys.length; i++) { | ||
var suffix = keys[i]; | ||
route = routes[suffix]; | ||
assert.ok(route.dn); | ||
// Match a valid route or the route wildcard ('') | ||
if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') { | ||
if (route[op]) { | ||
req.suffix = route.dn | ||
return { backend: route.backend, handlers: route[op] } | ||
} else { if (suffix === '') { break } else { fallbackHandler = [defaultHandler] } } | ||
// We should be good to go. | ||
req.suffix = route.dn; | ||
return { | ||
backend: route.backend, | ||
handlers: route[op] | ||
}; | ||
} else { | ||
if (suffix === '') { | ||
break; | ||
} else { | ||
// We found a valid suffix but not a valid operation. | ||
// There might be a more generic suffix with a legitimate operation. | ||
fallbackHandler = [defaultHandler]; | ||
} | ||
} | ||
} | ||
} | ||
return { | ||
backend: self, | ||
handlers: fallbackHandler | ||
} | ||
}; | ||
}; | ||
} | ||
Server.prototype._mount = (op, name, argv, notDN) => { | ||
assert.ok(op) | ||
assert.ok(name !== undefined) | ||
assert.ok(argv) | ||
Server.prototype._mount = function (op, name, argv, notDN) { | ||
assert.ok(op); | ||
assert.ok(name !== undefined); | ||
assert.ok(argv); | ||
if (typeof (name) !== 'string') throw new TypeError('name (string) required') | ||
if (!argv.length) throw new Error('at least one handler required') | ||
if (typeof (name) !== 'string') | ||
throw new TypeError('name (string) required'); | ||
if (!argv.length) | ||
throw new Error('at least one handler required'); | ||
let backend = this, index = 0 | ||
var backend = this; | ||
var index = 0; | ||
if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) { | ||
backend = argv[0] | ||
index = 1 | ||
backend = argv[0]; | ||
index = 1; | ||
} | ||
var route = this._getRoute(notDN ? name : dn.parse(name), backend); | ||
let | ||
route = this._getRoute(notDN ? name : dn.parse(name), backend), | ||
chain = this._chain.slice() | ||
argv.slice(index).forEach((a) => chain.push(a)) | ||
route['0x' + op.toString(16)] = mergeFunctionArgs(chain) | ||
var chain = this._chain.slice(); | ||
argv.slice(index).forEach(function (a) { | ||
chain.push(a); | ||
}); | ||
route['0x' + op.toString(16)] = mergeFunctionArgs(chain); | ||
return this | ||
} | ||
return this; | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"author": "Andrea Leone <git@andrealeone.xyz>", | ||
"version": "0.9.1a", | ||
"version": "0.9.2", | ||
"license": "WTFPL", | ||
@@ -8,0 +8,0 @@ "repository": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
210815
6133
4