123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- // Copyright 2011 Mark Cavage, Inc. All rights reserved.
- const assert = require('assert')
- const EventEmitter = require('events').EventEmitter
- const net = require('net')
- const tls = require('tls')
- const util = require('util')
- // var asn1 = require('@ldapjs/asn1')
- const VError = require('verror').VError
- const { DN, RDN } = require('@ldapjs/dn')
- const errors = require('./errors')
- const Protocol = require('@ldapjs/protocol')
- const messages = require('@ldapjs/messages')
- const Parser = require('./messages').Parser
- const LdapResult = messages.LdapResult
- const AbandonResponse = messages.AbandonResponse
- const AddResponse = messages.AddResponse
- const BindResponse = messages.BindResponse
- const CompareResponse = messages.CompareResponse
- const DeleteResponse = messages.DeleteResponse
- const ExtendedResponse = messages.ExtensionResponse
- const ModifyResponse = messages.ModifyResponse
- const ModifyDnResponse = messages.ModifyDnResponse
- const SearchRequest = messages.SearchRequest
- const SearchResponse = require('./messages/search_response')
- /// --- Globals
- // var Ber = asn1.Ber
- // var BerReader = asn1.BerReader
- // const 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 }
- const handlers = []
- for (let i = start; i < end; i++) {
- if (Array.isArray(argv[i])) {
- const arr = argv[i]
- for (let j = 0; j < arr.length; j++) {
- if (typeof arr[j] !== 'function') {
- throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
- }
- handlers.push(arr[j])
- }
- } else if (typeof argv[i] === 'function') {
- handlers.push(argv[i])
- } else {
- throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
- }
- }
- return handlers
- }
- function getResponse (req) {
- assert.ok(req)
- let Response
- switch (req.protocolOp) {
- case Protocol.operations.LDAP_REQ_BIND:
- Response = BindResponse
- break
- case Protocol.operations.LDAP_REQ_ABANDON:
- Response = AbandonResponse
- break
- case Protocol.operations.LDAP_REQ_ADD:
- Response = AddResponse
- break
- case Protocol.operations.LDAP_REQ_COMPARE:
- Response = CompareResponse
- break
- case Protocol.operations.LDAP_REQ_DELETE:
- Response = DeleteResponse
- break
- case Protocol.operations.LDAP_REQ_EXTENSION:
- Response = ExtendedResponse
- break
- case Protocol.operations.LDAP_REQ_MODIFY:
- Response = ModifyResponse
- break
- case Protocol.operations.LDAP_REQ_MODRDN:
- Response = ModifyDnResponse
- break
- case Protocol.operations.LDAP_REQ_SEARCH:
- Response = SearchResponse
- break
- case Protocol.operations.LDAP_REQ_UNBIND:
- // TODO: when the server receives an unbind request this made up response object was returned.
- // Instead, we need to just terminate the connection. ~ jsumners
- Response = class extends LdapResult {
- status = 0
- end () {
- req.connection.end()
- }
- }
- break
- default:
- return null
- }
- assert.ok(Response)
- const res = new Response({
- messageId: req.messageId,
- attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
- })
- res.log = req.log
- res.connection = req.connection
- res.logId = req.logId
- if (typeof res.end !== 'function') {
- // This is a hack to re-add the original tight coupling of the message
- // objects and the server connection.
- // TODO: remove this during server refactoring ~ jsumners 2023-02-16
- switch (res.protocolOp) {
- case 0: {
- res.end = abandonResponseEnd
- break
- }
- case Protocol.operations.LDAP_RES_COMPARE: {
- res.end = compareResponseEnd
- break
- }
- default: {
- res.end = defaultResponseEnd
- break
- }
- }
- }
- return res
- }
- /**
- * Response connection end handler for most responses.
- *
- * @param {number} status
- */
- function defaultResponseEnd (status) {
- if (typeof status === 'number') { this.status = status }
- const ber = this.toBer()
- this.log.debug('%s: sending: %j', this.connection.ldap.id, this.pojo)
- try {
- this.connection.write(ber.buffer)
- } catch (error) {
- this.log.warn(
- error,
- '%s failure to write message %j',
- this.connection.ldap.id,
- this.pojo
- )
- }
- }
- /**
- * Response connection end handler for ABANDON responses.
- */
- function abandonResponseEnd () {}
- /**
- * Response connection end handler for COMPARE responses.
- *
- * @param {number | boolean} status
- */
- function compareResponseEnd (status) {
- let result = 0x06
- if (typeof status === 'boolean') {
- if (status === false) {
- result = 0x05
- }
- } else {
- result = status
- }
- return defaultResponseEnd.call(this, result)
- }
- 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()
- }
- function defaultNoOpHandler (req, res, 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()
- }
- 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()
- }
- /// --- 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.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')
- }
- }
- } else {
- options = {}
- }
- const self = this
- EventEmitter.call(this, options)
- this._chain = []
- this.log = options.log
- const log = this.log
- function setupConnection (c) {
- assert.ok(c)
- if (c.type === 'unix') {
- c.remoteAddress = self.server.path
- c.remotePort = c.fd
- } else if (c.socket) {
- // TLS
- c.remoteAddress = c.socket.remoteAddress
- c.remotePort = c.socket.remotePort
- }
- const rdn = new RDN({ cn: 'anonymous' })
- c.ldap = {
- id: c.remoteAddress + ':' + c.remotePort,
- config: options,
- _bindDN: new DN({ rdns: [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 (closeError) {
- log.trace('%s close; had_err=%j', c.ldap.id, closeError)
- c.end()
- })
- c.ldap.__defineGetter__('bindDN', function () {
- return c.ldap._bindDN
- })
- c.ldap.__defineSetter__('bindDN', function (val) {
- if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
- throw new TypeError('DN required')
- }
- c.ldap._bindDN = val
- return val
- })
- return c
- }
- self.newConnection = function (conn) {
- // TODO: make `newConnection` available on the `Server` prototype
- // https://github.com/ldapjs/node-ldapjs/pull/727/files#r636572294
- setupConnection(conn)
- log.trace('new connection from %s', conn.ldap.id)
- conn.parser = new Parser({
- log: options.log
- })
- conn.parser.on('message', function (req) {
- // TODO: this is mutating the `@ldapjs/message` objects.
- // We should avoid doing that. ~ jsumners 2023-02-16
- req.connection = conn
- req.logId = conn.ldap.id + '::' + req.messageId
- req.startTime = new Date().getTime()
- log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)
- const res = getResponse(req)
- if (!res) {
- log.warn('Unimplemented server method: %s', req.type)
- conn.destroy()
- return false
- }
- // parse string DNs for routing/etc
- try {
- switch (req.protocolOp) {
- case Protocol.operations.LDAP_REQ_BIND: {
- req.name = DN.fromString(req.name)
- break
- }
- case Protocol.operations.LDAP_REQ_ADD:
- case Protocol.operations.LDAP_REQ_COMPARE:
- case Protocol.operations.LDAP_REQ_DELETE: {
- if (typeof req.entry === 'string') {
- req.entry = DN.fromString(req.entry)
- } else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
- throw Error('invalid entry object for operation')
- }
- break
- }
- case Protocol.operations.LDAP_REQ_MODIFY: {
- req.object = DN.fromString(req.object)
- break
- }
- case Protocol.operations.LDAP_REQ_MODRDN: {
- if (typeof req.entry === 'string') {
- req.entry = DN.fromString(req.entry)
- } else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
- throw Error('invalid entry object for operation')
- }
- // TODO: handle newRdn/Superior
- break
- }
- case Protocol.operations.LDAP_REQ_SEARCH: {
- break
- }
- default: {
- break
- }
- }
- } catch (e) {
- return res.end(errors.LDAP_INVALID_DN_SYNTAX)
- }
- res.connection = conn
- res.logId = req.logId
- res.requestDN = req.dn
- const chain = self._getHandlerChain(req, res)
- let i = 0
- return (function messageIIFE (err) {
- function sendError (sendErr) {
- res.status = sendErr.code || errors.LDAP_OPERATIONS_ERROR
- res.matchedDN = req.suffix ? req.suffix.toString() : ''
- res.errorMessage = sendErr.message || ''
- return res.end()
- }
- function after () {
- if (!self._postChain || !self._postChain.length) { return }
- function next () {} // stub out next for the post chain
- self._postChain.forEach(function (cb) {
- cb.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()
- }
- try {
- const next = messageIIFE
- if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }
- if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND && res.status === 0) {
- // 0 length == anonymous bind
- if (req.dn.length === 0 && req.credentials === '') {
- conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
- } else {
- conn.ldap.bindDN = DN.fromString(req.dn)
- }
- }
- // unbind clear bindDN for safety
- // conn should terminate on unbind (RFC4511 4.3)
- if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
- conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
- }
- 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))
- }
- }())
- })
- conn.parser.on('error', function (err, message) {
- self.emit('error', new VError(err, 'Parser error for %s', conn.ldap.id))
- if (!message) { return conn.destroy() }
- const res = getResponse(message)
- if (!res) { return conn.destroy() }
- res.status = 0x02 // protocol error
- res.errorMessage = err.toString()
- return conn.end(res.toBer())
- })
- conn.on('data', function (data) {
- log.trace('data on %s: %s', conn.ldap.id, util.inspect(data))
- conn.parser.write(data)
- })
- } // end newConnection
- this.routes = {}
- if ((options.cert || options.certificate) && options.key) {
- options.cert = options.cert || options.certificate
- this.server = tls.createServer(options, options.connectionRouter ? options.connectionRouter : self.newConnection)
- } else {
- this.server = net.createServer(options.connectionRouter ? options.connectionRouter : self.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)
- Object.defineProperties(Server.prototype, {
- maxConnections: {
- get: function getMaxConnections () {
- return this.server.maxConnections
- },
- set: function setMaxConnections (val) {
- this.server.maxConnections = val
- },
- configurable: false
- },
- connections: {
- get: function getConnections () {
- return this.server.connections
- },
- configurable: false
- },
- name: {
- get: function getName () {
- return 'LDAPServer'
- },
- configurable: false
- },
- url: {
- get: function getURL () {
- let str
- const addr = this.server.address()
- if (!addr) {
- return null
- }
- if (!addr.family) {
- str = 'ldapi://'
- str += this.host.replace(/\//g, '%2f')
- return str
- }
- if (this.server instanceof tls.Server) {
- str = 'ldaps://'
- } else {
- str = 'ldap://'
- }
- let host = this.host
- // Node 18 switched family from returning a string to returning a number
- // https://nodejs.org/api/net.html#serveraddress
- if (addr.family === 'IPv6' || addr.family === 6) {
- host = '[' + this.host + ']'
- }
- str += host + ':' + this.port
- return str
- },
- configurable: false
- }
- })
- module.exports = Server
- /**
- * 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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.LDAP_REQ_ADD, 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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.LDAP_REQ_BIND, name, args)
- }
- /**
- * 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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.LDAP_REQ_COMPARE, 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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.LDAP_REQ_DELETE, name, args)
- }
- /**
- * 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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.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) {
- const args = Array.prototype.slice.call(arguments, 1)
- return this._mount(Protocol.operations.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 () {
- const args = Array.prototype.slice.call(arguments, 0)
- return this._mount(Protocol.operations.LDAP_REQ_UNBIND, 'unbind', args, true)
- }
- Server.prototype.use = function use () {
- const args = Array.prototype.slice.call(arguments)
- const chain = mergeFunctionArgs(args, 0, args.length)
- const self = this
- chain.forEach(function (c) {
- self._chain.push(c)
- })
- }
- Server.prototype.after = function () {
- if (!this._postChain) { this._postChain = [] }
- const self = this
- mergeFunctionArgs(arguments).forEach(function (h) {
- self._postChain.push(h)
- })
- }
- // All these just re-expose 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') }
- if (typeof (host) === 'function') {
- callback = host
- host = '127.0.0.1'
- }
- if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
- // Disambiguate between string ports and file paths
- port = parseInt(port, 10)
- }
- const self = this
- function cbListen () {
- if (typeof (port) === 'number') {
- self.host = self.address().address
- self.port = self.address().port
- } else {
- self.host = port
- self.port = self.server.fd
- }
- if (typeof (callback) === 'function') { callback() }
- }
- 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 (callback) {
- return this.server.close(callback)
- }
- Server.prototype.address = function () {
- return this.server.address()
- }
- Server.prototype.getConnections = function (callback) {
- return this.server.getConnections(callback)
- }
- Server.prototype._getRoute = function (_dn, backend) {
- if (!backend) { backend = this }
- let name
- if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
- name = _dn.toString()
- } else {
- name = _dn
- }
- if (!this.routes[name]) {
- 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]
- }
- 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) {
- const self = this
- const reversedRDNsToKeys = {}
- // Generate mapping of reversedRDNs(DN) -> routeKey
- Object.keys(this.routes).forEach(function (key) {
- const _dn = self.routes[key].dn
- // Ignore non-DN routes such as exop or unbind
- if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
- const reversed = _dn.clone()
- reversed.reverse()
- reversedRDNsToKeys[reversed.toString()] = key
- }
- })
- const 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
- }
- Server.prototype._getHandlerChain = function _getHandlerChain (req) {
- assert.ok(req)
- const self = this
- const routes = this.routes
- let route
- // check anonymous bind
- if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND &&
- req.dn.toString() === '' &&
- req.credentials === '') {
- return {
- backend: self,
- handlers: [defaultNoOpHandler]
- }
- }
- const op = '0x' + req.protocolOp.toString(16)
- // Special cases are exops, unbinds and abandons. Handle those first.
- if (req.protocolOp === Protocol.operations.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]
- }
- }
- } else if (req.protocolOp === Protocol.operations.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.operations.LDAP_REQ_ABANDON) {
- return {
- backend: self,
- handlers: [defaultNoOpHandler]
- }
- }
- // Otherwise, match via DN rules
- const keys = this._sortedRouteKeys()
- let fallbackHandler = [noSuffixHandler]
- // invalid DNs in non-strict mode are routed to the default handler
- const testDN = (typeof (req.dn) === 'string') ? DN.fromString(req.dn) : req.dn
- assert.ok(testDN)
- for (let i = 0; i < keys.length; i++) {
- const 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]) {
- // 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 = 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') }
- let backend = this
- let index = 0
- if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) {
- backend = argv[0]
- index = 1
- }
- const route = this._getRoute(notDN ? name : DN.fromString(name), backend)
- const chain = this._chain.slice()
- argv.slice(index).forEach(function (a) {
- chain.push(a)
- })
- route['0x' + op.toString(16)] = mergeFunctionArgs(chain)
- return this
- }
|