UNPKG

24.1 kBJavaScriptView Raw
1// Copyright 2011 Mark Cavage, Inc. All rights reserved.
2
3const assert = require('assert')
4const EventEmitter = require('events').EventEmitter
5const net = require('net')
6const tls = require('tls')
7const util = require('util')
8
9// var asn1 = require('asn1')
10const VError = require('verror').VError
11
12const dn = require('./dn')
13const dtrace = require('./dtrace')
14const errors = require('./errors')
15const Protocol = require('./protocol')
16
17const Parser = require('./messages').Parser
18const AbandonResponse = require('./messages/abandon_response')
19const AddResponse = require('./messages/add_response')
20const BindResponse = require('./messages/bind_response')
21const CompareResponse = require('./messages/compare_response')
22const DeleteResponse = require('./messages/del_response')
23const ExtendedResponse = require('./messages/ext_response')
24// var LDAPResult = require('./messages/result')
25const ModifyResponse = require('./messages/modify_response')
26const ModifyDNResponse = require('./messages/moddn_response')
27const SearchRequest = require('./messages/search_request')
28const SearchResponse = require('./messages/search_response')
29const UnbindResponse = require('./messages/unbind_response')
30
31/// --- Globals
32
33// var Ber = asn1.Ber
34// var BerReader = asn1.BerReader
35const DN = dn.DN
36
37// var sprintf = util.format
38
39/// --- Helpers
40
41function mergeFunctionArgs (argv, start, end) {
42 assert.ok(argv)
43
44 if (!start) { start = 0 }
45 if (!end) { end = argv.length }
46
47 const handlers = []
48
49 for (let i = start; i < end; i++) {
50 if (argv[i] instanceof Array) {
51 const arr = argv[i]
52 for (let j = 0; j < arr.length; j++) {
53 if (!(arr[j] instanceof Function)) {
54 throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
55 }
56 handlers.push(arr[j])
57 }
58 } else if (argv[i] instanceof Function) {
59 handlers.push(argv[i])
60 } else {
61 throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
62 }
63 }
64
65 return handlers
66}
67
68function getResponse (req) {
69 assert.ok(req)
70
71 let Response
72
73 switch (req.protocolOp) {
74 case Protocol.LDAP_REQ_BIND:
75 Response = BindResponse
76 break
77 case Protocol.LDAP_REQ_ABANDON:
78 Response = AbandonResponse
79 break
80 case Protocol.LDAP_REQ_ADD:
81 Response = AddResponse
82 break
83 case Protocol.LDAP_REQ_COMPARE:
84 Response = CompareResponse
85 break
86 case Protocol.LDAP_REQ_DELETE:
87 Response = DeleteResponse
88 break
89 case Protocol.LDAP_REQ_EXTENSION:
90 Response = ExtendedResponse
91 break
92 case Protocol.LDAP_REQ_MODIFY:
93 Response = ModifyResponse
94 break
95 case Protocol.LDAP_REQ_MODRDN:
96 Response = ModifyDNResponse
97 break
98 case Protocol.LDAP_REQ_SEARCH:
99 Response = SearchResponse
100 break
101 case Protocol.LDAP_REQ_UNBIND:
102 Response = UnbindResponse
103 break
104 default:
105 return null
106 }
107 assert.ok(Response)
108
109 const res = new Response({
110 messageID: req.messageID,
111 log: req.log,
112 attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
113 })
114 res.connection = req.connection
115 res.logId = req.logId
116
117 return res
118}
119
120function defaultHandler (req, res, next) {
121 assert.ok(req)
122 assert.ok(res)
123 assert.ok(next)
124
125 res.matchedDN = req.dn.toString()
126 res.errorMessage = 'Server method not implemented'
127 res.end(errors.LDAP_OTHER)
128 return next()
129}
130
131function defaultNoOpHandler (req, res, next) {
132 assert.ok(req)
133 assert.ok(res)
134 assert.ok(next)
135
136 res.end()
137 return next()
138}
139
140function noSuffixHandler (req, res, next) {
141 assert.ok(req)
142 assert.ok(res)
143 assert.ok(next)
144
145 res.errorMessage = 'No tree found for: ' + req.dn.toString()
146 res.end(errors.LDAP_NO_SUCH_OBJECT)
147 return next()
148}
149
150function noExOpHandler (req, res, next) {
151 assert.ok(req)
152 assert.ok(res)
153 assert.ok(next)
154
155 res.errorMessage = req.requestName + ' not supported'
156 res.end(errors.LDAP_PROTOCOL_ERROR)
157 return next()
158}
159
160function fireDTraceProbe (req, res) {
161 assert.ok(req)
162
163 req._dtraceId = res._dtraceId = dtrace._nextId()
164 const probeArgs = [
165 req._dtraceId,
166 req.connection.remoteAddress || 'localhost',
167 req.connection.ldap.bindDN.toString(),
168 req.dn.toString()
169 ]
170
171 let op
172 switch (req.protocolOp) {
173 case Protocol.LDAP_REQ_ABANDON:
174 op = 'abandon'
175 break
176 case Protocol.LDAP_REQ_ADD:
177 op = 'add'
178 probeArgs.push(req.attributes.length)
179 break
180 case Protocol.LDAP_REQ_BIND:
181 op = 'bind'
182 break
183 case Protocol.LDAP_REQ_COMPARE:
184 op = 'compare'
185 probeArgs.push(req.attribute)
186 probeArgs.push(req.value)
187 break
188 case Protocol.LDAP_REQ_DELETE:
189 op = 'delete'
190 break
191 case Protocol.LDAP_REQ_EXTENSION:
192 op = 'exop'
193 probeArgs.push(req.name)
194 probeArgs.push(req.value)
195 break
196 case Protocol.LDAP_REQ_MODIFY:
197 op = 'modify'
198 probeArgs.push(req.changes.length)
199 break
200 case Protocol.LDAP_REQ_MODRDN:
201 op = 'modifydn'
202 probeArgs.push(req.newRdn.toString())
203 probeArgs.push((req.newSuperior ? req.newSuperior.toString() : ''))
204 break
205 case Protocol.LDAP_REQ_SEARCH:
206 op = 'search'
207 probeArgs.push(req.scope)
208 probeArgs.push(req.filter.toString())
209 break
210 case Protocol.LDAP_REQ_UNBIND:
211 op = 'unbind'
212 break
213 default:
214 break
215 }
216
217 res._dtraceOp = op
218 dtrace.fire('server-' + op + '-start', function () {
219 return probeArgs
220 })
221}
222
223/// --- API
224
225/**
226 * Constructs a new server that you can call .listen() on, in the various
227 * forms node supports. You need to first assign some handlers to the various
228 * LDAP operations however.
229 *
230 * The options object currently only takes a certificate/private key, and a
231 * bunyan logger handle.
232 *
233 * This object exposes the following events:
234 * - 'error'
235 * - 'close'
236 *
237 * @param {Object} options (optional) parameterization object.
238 * @throws {TypeError} on bad input.
239 */
240function Server (options) {
241 if (options) {
242 if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
243 if (typeof (options.log) !== 'object') { throw new TypeError('options.log must be an object') }
244
245 if (options.certificate || options.key) {
246 if (!(options.certificate && options.key) ||
247 (typeof (options.certificate) !== 'string' &&
248 !Buffer.isBuffer(options.certificate)) ||
249 (typeof (options.key) !== 'string' &&
250 !Buffer.isBuffer(options.key))) {
251 throw new TypeError('options.certificate and options.key ' +
252 '(string or buffer) are both required for TLS')
253 }
254 }
255 } else {
256 options = {}
257 }
258 const self = this
259
260 EventEmitter.call(this, options)
261
262 this._chain = []
263 this.log = options.log
264 this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true
265
266 const log = this.log
267
268 function setupConnection (c) {
269 assert.ok(c)
270
271 if (c.type === 'unix') {
272 c.remoteAddress = self.server.path
273 c.remotePort = c.fd
274 } else if (c.socket) {
275 // TLS
276 c.remoteAddress = c.socket.remoteAddress
277 c.remotePort = c.socket.remotePort
278 }
279
280 const rdn = new dn.RDN({ cn: 'anonymous' })
281
282 c.ldap = {
283 id: c.remoteAddress + ':' + c.remotePort,
284 config: options,
285 _bindDN: new DN([rdn])
286 }
287 c.addListener('timeout', function () {
288 log.trace('%s timed out', c.ldap.id)
289 c.destroy()
290 })
291 c.addListener('end', function () {
292 log.trace('%s shutdown', c.ldap.id)
293 })
294 c.addListener('error', function (err) {
295 log.warn('%s unexpected connection error', c.ldap.id, err)
296 self.emit('clientError', err)
297 c.destroy()
298 })
299 c.addListener('close', function (closeError) {
300 log.trace('%s close; had_err=%j', c.ldap.id, closeError)
301 c.end()
302 })
303
304 c.ldap.__defineGetter__('bindDN', function () {
305 return c.ldap._bindDN
306 })
307 c.ldap.__defineSetter__('bindDN', function (val) {
308 if (!(val instanceof DN)) { throw new TypeError('DN required') }
309
310 c.ldap._bindDN = val
311 return val
312 })
313 return c
314 }
315
316 function newConnection (c) {
317 setupConnection(c)
318 log.trace('new connection from %s', c.ldap.id)
319
320 dtrace.fire('server-connection', function () {
321 return [c.remoteAddress]
322 })
323
324 c.parser = new Parser({
325 log: options.log
326 })
327 c.parser.on('message', function (req) {
328 req.connection = c
329 req.logId = c.ldap.id + '::' + req.messageID
330 req.startTime = new Date().getTime()
331
332 log.debug('%s: message received: req=%j', c.ldap.id, req.json)
333
334 const res = getResponse(req)
335 if (!res) {
336 log.warn('Unimplemented server method: %s', req.type)
337 c.destroy()
338 return false
339 }
340
341 // parse string DNs for routing/etc
342 try {
343 switch (req.protocolOp) {
344 case Protocol.LDAP_REQ_BIND:
345 req.name = dn.parse(req.name)
346 break
347 case Protocol.LDAP_REQ_ADD:
348 case Protocol.LDAP_REQ_COMPARE:
349 case Protocol.LDAP_REQ_DELETE:
350 req.entry = dn.parse(req.entry)
351 break
352 case Protocol.LDAP_REQ_MODIFY:
353 req.object = dn.parse(req.object)
354 break
355 case Protocol.LDAP_REQ_MODRDN:
356 req.entry = dn.parse(req.entry)
357 // TODO: handle newRdn/Superior
358 break
359 case Protocol.LDAP_REQ_SEARCH:
360 req.baseObject = dn.parse(req.baseObject)
361 break
362 default:
363 break
364 }
365 } catch (e) {
366 if (self.strictDN) {
367 return res.end(errors.LDAP_INVALID_DN_SYNTAX)
368 }
369 }
370
371 res.connection = c
372 res.logId = req.logId
373 res.requestDN = req.dn
374
375 const chain = self._getHandlerChain(req, res)
376
377 let i = 0
378 return (function messageIIFE (err) {
379 function sendError (err) {
380 res.status = err.code || errors.LDAP_OPERATIONS_ERROR
381 res.matchedDN = req.suffix ? req.suffix.toString() : ''
382 res.errorMessage = err.message || ''
383 return res.end()
384 }
385
386 function after () {
387 if (!self._postChain || !self._postChain.length) { return }
388
389 function next () {} // stub out next for the post chain
390
391 self._postChain.forEach(function (c) {
392 c.call(self, req, res, next)
393 })
394 }
395
396 if (err) {
397 log.trace('%s sending error: %s', req.logId, err.stack || err)
398 self.emit('clientError', err)
399 sendError(err)
400 return after()
401 }
402
403 try {
404 const next = messageIIFE
405 if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }
406
407 if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) { c.ldap.bindDN = req.dn }
408
409 return after()
410 } catch (e) {
411 if (!e.stack) { e.stack = e.toString() }
412 log.error('%s uncaught exception: %s', req.logId, e.stack)
413 return sendError(new errors.OperationsError(e.message))
414 }
415 }())
416 })
417
418 c.parser.on('error', function (err, message) {
419 self.emit('error', new VError(err, 'Parser error for %s', c.ldap.id))
420
421 if (!message) { return c.destroy() }
422
423 const res = getResponse(message)
424 if (!res) { return c.destroy() }
425
426 res.status = 0x02 // protocol error
427 res.errorMessage = err.toString()
428 return c.end(res.toBer())
429 })
430
431 c.on('data', function (data) {
432 log.trace('data on %s: %s', c.ldap.id, util.inspect(data))
433
434 c.parser.write(data)
435 })
436 } // end newConnection
437
438 this.routes = {}
439 if ((options.cert || options.certificate) && options.key) {
440 options.cert = options.cert || options.certificate
441 this.server = tls.createServer(options, newConnection)
442 } else {
443 this.server = net.createServer(newConnection)
444 }
445 this.server.log = options.log
446 this.server.ldap = {
447 config: options
448 }
449 this.server.on('close', function () {
450 self.emit('close')
451 })
452 this.server.on('error', function (err) {
453 self.emit('error', err)
454 })
455}
456util.inherits(Server, EventEmitter)
457Object.defineProperties(Server.prototype, {
458 maxConnections: {
459 get: function getMaxConnections () {
460 return this.server.maxConnections
461 },
462 set: function setMaxConnections (val) {
463 this.server.maxConnections = val
464 },
465 configurable: false
466 },
467 connections: {
468 get: function getConnections () {
469 return this.server.connections
470 },
471 configurable: false
472 },
473 name: {
474 get: function getName () {
475 return 'LDAPServer'
476 },
477 configurable: false
478 },
479 url: {
480 get: function getURL () {
481 let str
482 const addr = this.server.address()
483 if (!addr) {
484 return null
485 }
486 if (!addr.family) {
487 str = 'ldapi://'
488 str += this.host.replace(/\//g, '%2f')
489 return str
490 }
491 if (this.server instanceof tls.Server) {
492 str = 'ldaps://'
493 } else {
494 str = 'ldap://'
495 }
496 str += this.host + ':' + this.port
497 return str
498 },
499 configurable: false
500 }
501})
502module.exports = Server
503
504/**
505 * Adds a handler (chain) for the LDAP add method.
506 *
507 * Note that this is of the form f(name, [function]) where the second...N
508 * arguments can all either be functions or arrays of functions.
509 *
510 * @param {String} name the DN to mount this handler chain at.
511 * @return {Server} this so you can chain calls.
512 * @throws {TypeError} on bad input
513 */
514Server.prototype.add = function (name) {
515 const args = Array.prototype.slice.call(arguments, 1)
516 return this._mount(Protocol.LDAP_REQ_ADD, name, args)
517}
518
519/**
520 * Adds a handler (chain) for the LDAP bind method.
521 *
522 * Note that this is of the form f(name, [function]) where the second...N
523 * arguments can all either be functions or arrays of functions.
524 *
525 * @param {String} name the DN to mount this handler chain at.
526 * @return {Server} this so you can chain calls.
527 * @throws {TypeError} on bad input
528 */
529Server.prototype.bind = function (name) {
530 const args = Array.prototype.slice.call(arguments, 1)
531 return this._mount(Protocol.LDAP_REQ_BIND, name, args)
532}
533
534/**
535 * Adds a handler (chain) for the LDAP compare method.
536 *
537 * Note that this is of the form f(name, [function]) where the second...N
538 * arguments can all either be functions or arrays of functions.
539 *
540 * @param {String} name the DN to mount this handler chain at.
541 * @return {Server} this so you can chain calls.
542 * @throws {TypeError} on bad input
543 */
544Server.prototype.compare = function (name) {
545 const args = Array.prototype.slice.call(arguments, 1)
546 return this._mount(Protocol.LDAP_REQ_COMPARE, name, args)
547}
548
549/**
550 * Adds a handler (chain) for the LDAP delete method.
551 *
552 * Note that this is of the form f(name, [function]) where the second...N
553 * arguments can all either be functions or arrays of functions.
554 *
555 * @param {String} name the DN to mount this handler chain at.
556 * @return {Server} this so you can chain calls.
557 * @throws {TypeError} on bad input
558 */
559Server.prototype.del = function (name) {
560 const args = Array.prototype.slice.call(arguments, 1)
561 return this._mount(Protocol.LDAP_REQ_DELETE, name, args)
562}
563
564/**
565 * Adds a handler (chain) for the LDAP exop method.
566 *
567 * Note that this is of the form f(name, [function]) where the second...N
568 * arguments can all either be functions or arrays of functions.
569 *
570 * @param {String} name OID to assign this handler chain to.
571 * @return {Server} this so you can chain calls.
572 * @throws {TypeError} on bad input.
573 */
574Server.prototype.exop = function (name) {
575 const args = Array.prototype.slice.call(arguments, 1)
576 return this._mount(Protocol.LDAP_REQ_EXTENSION, name, args, true)
577}
578
579/**
580 * Adds a handler (chain) for the LDAP modify method.
581 *
582 * Note that this is of the form f(name, [function]) where the second...N
583 * arguments can all either be functions or arrays of functions.
584 *
585 * @param {String} name the DN to mount this handler chain at.
586 * @return {Server} this so you can chain calls.
587 * @throws {TypeError} on bad input
588 */
589Server.prototype.modify = function (name) {
590 const args = Array.prototype.slice.call(arguments, 1)
591 return this._mount(Protocol.LDAP_REQ_MODIFY, name, args)
592}
593
594/**
595 * Adds a handler (chain) for the LDAP modifyDN method.
596 *
597 * Note that this is of the form f(name, [function]) where the second...N
598 * arguments can all either be functions or arrays of functions.
599 *
600 * @param {String} name the DN to mount this handler chain at.
601 * @return {Server} this so you can chain calls.
602 * @throws {TypeError} on bad input
603 */
604Server.prototype.modifyDN = function (name) {
605 const args = Array.prototype.slice.call(arguments, 1)
606 return this._mount(Protocol.LDAP_REQ_MODRDN, name, args)
607}
608
609/**
610 * Adds a handler (chain) for the LDAP search method.
611 *
612 * Note that this is of the form f(name, [function]) where the second...N
613 * arguments can all either be functions or arrays of functions.
614 *
615 * @param {String} name the DN to mount this handler chain at.
616 * @return {Server} this so you can chain calls.
617 * @throws {TypeError} on bad input
618 */
619Server.prototype.search = function (name) {
620 const args = Array.prototype.slice.call(arguments, 1)
621 return this._mount(Protocol.LDAP_REQ_SEARCH, name, args)
622}
623
624/**
625 * Adds a handler (chain) for the LDAP unbind method.
626 *
627 * This method is different than the others and takes no mount point, as unbind
628 * is a connection-wide operation, not constrianed to part of the DIT.
629 *
630 * @return {Server} this so you can chain calls.
631 * @throws {TypeError} on bad input
632 */
633Server.prototype.unbind = function () {
634 const args = Array.prototype.slice.call(arguments, 0)
635 return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true)
636}
637
638Server.prototype.use = function use () {
639 const args = Array.prototype.slice.call(arguments)
640 const chain = mergeFunctionArgs(args, 0, args.length)
641 const self = this
642 chain.forEach(function (c) {
643 self._chain.push(c)
644 })
645}
646
647Server.prototype.after = function () {
648 if (!this._postChain) { this._postChain = [] }
649
650 const self = this
651 mergeFunctionArgs(arguments).forEach(function (h) {
652 self._postChain.push(h)
653 })
654}
655
656// All these just reexpose the requisite net.Server APIs
657Server.prototype.listen = function (port, host, callback) {
658 if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') }
659
660 if (typeof (host) === 'function') {
661 callback = host
662 host = '0.0.0.0'
663 }
664 if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
665 // Disambiguate between string ports and file paths
666 port = parseInt(port, 10)
667 }
668 const self = this
669
670 function cbListen () {
671 if (typeof (port) === 'number') {
672 self.host = self.address().address
673 self.port = self.address().port
674 } else {
675 self.host = port
676 self.port = self.server.fd
677 }
678
679 if (typeof (callback) === 'function') { callback() }
680 }
681
682 if (typeof (port) === 'number') {
683 return this.server.listen(port, host, cbListen)
684 } else {
685 return this.server.listen(port, cbListen)
686 }
687}
688Server.prototype.listenFD = function (fd) {
689 this.host = 'unix-domain-socket'
690 this.port = fd
691 return this.server.listenFD(fd)
692}
693Server.prototype.close = function (callback) {
694 return this.server.close(callback)
695}
696Server.prototype.address = function () {
697 return this.server.address()
698}
699
700Server.prototype.getConnections = function (callback) {
701 return this.server.getConnections(callback)
702}
703
704Server.prototype._getRoute = function (_dn, backend) {
705 assert.ok(dn)
706
707 if (!backend) { backend = this }
708
709 let name
710 if (_dn instanceof dn.DN) {
711 name = _dn.toString()
712 } else {
713 name = _dn
714 }
715
716 if (!this.routes[name]) {
717 this.routes[name] = {}
718 this.routes[name].backend = backend
719 this.routes[name].dn = _dn
720 // Force regeneration of the route key cache on next request
721 this._routeKeyCache = null
722 }
723
724 return this.routes[name]
725}
726
727Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
728 // The filtered/sorted route keys are cached to prevent needlessly
729 // regenerating the list for every incoming request.
730 if (!this._routeKeyCache) {
731 const self = this
732 const reversedRDNsToKeys = {}
733 // Generate mapping of reversedRDNs(DN) -> routeKey
734 Object.keys(this.routes).forEach(function (key) {
735 const _dn = self.routes[key].dn
736 // Ignore non-DN routes such as exop or unbind
737 if (_dn instanceof dn.DN) {
738 const reversed = _dn.clone()
739 reversed.rdns.reverse()
740 reversedRDNsToKeys[reversed.format()] = key
741 }
742 })
743 const output = []
744 // Reverse-sort on reversedRDS(DN) in order to output routeKey list.
745 // This will place more specific DNs in front of their parents:
746 // 1. dc=test, dc=domain, dc=sub
747 // 2. dc=test, dc=domain
748 // 3. dc=other, dc=foobar
749 Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) {
750 output.push(reversedRDNsToKeys[_dn])
751 })
752 this._routeKeyCache = output
753 }
754 return this._routeKeyCache
755}
756
757Server.prototype._getHandlerChain = function _getHandlerChain (req, res) {
758 assert.ok(req)
759
760 fireDTraceProbe(req, res)
761
762 const self = this
763 const routes = this.routes
764 let route
765
766 // check anonymous bind
767 if (req.protocolOp === Protocol.LDAP_REQ_BIND &&
768 req.dn.toString() === '' &&
769 req.credentials === '') {
770 return {
771 backend: self,
772 handlers: [defaultNoOpHandler]
773 }
774 }
775
776 const op = '0x' + req.protocolOp.toString(16)
777
778 // Special cases are exops, unbinds and abandons. Handle those first.
779 if (req.protocolOp === Protocol.LDAP_REQ_EXTENSION) {
780 route = routes[req.requestName]
781 if (route) {
782 return {
783 backend: route.backend,
784 handlers: (route[op] ? route[op] : [noExOpHandler])
785 }
786 } else {
787 return {
788 backend: self,
789 handlers: [noExOpHandler]
790 }
791 }
792 } else if (req.protocolOp === Protocol.LDAP_REQ_UNBIND) {
793 route = routes.unbind
794 if (route) {
795 return {
796 backend: route.backend,
797 handlers: route[op]
798 }
799 } else {
800 return {
801 backend: self,
802 handlers: [defaultNoOpHandler]
803 }
804 }
805 } else if (req.protocolOp === Protocol.LDAP_REQ_ABANDON) {
806 return {
807 backend: self,
808 handlers: [defaultNoOpHandler]
809 }
810 }
811
812 // Otherwise, match via DN rules
813 assert.ok(req.dn)
814 const keys = this._sortedRouteKeys()
815 let fallbackHandler = [noSuffixHandler]
816 // invalid DNs in non-strict mode are routed to the default handler
817 const testDN = (typeof (req.dn) === 'string') ? '' : req.dn
818
819 for (let i = 0; i < keys.length; i++) {
820 const suffix = keys[i]
821 route = routes[suffix]
822 assert.ok(route.dn)
823 // Match a valid route or the route wildcard ('')
824 if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') {
825 if (route[op]) {
826 // We should be good to go.
827 req.suffix = route.dn
828 return {
829 backend: route.backend,
830 handlers: route[op]
831 }
832 } else {
833 if (suffix === '') {
834 break
835 } else {
836 // We found a valid suffix but not a valid operation.
837 // There might be a more generic suffix with a legitimate operation.
838 fallbackHandler = [defaultHandler]
839 }
840 }
841 }
842 }
843 return {
844 backend: self,
845 handlers: fallbackHandler
846 }
847}
848
849Server.prototype._mount = function (op, name, argv, notDN) {
850 assert.ok(op)
851 assert.ok(name !== undefined)
852 assert.ok(argv)
853
854 if (typeof (name) !== 'string') { throw new TypeError('name (string) required') }
855 if (!argv.length) { throw new Error('at least one handler required') }
856
857 let backend = this
858 let index = 0
859
860 if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) {
861 backend = argv[0]
862 index = 1
863 }
864 const route = this._getRoute(notDN ? name : dn.parse(name), backend)
865
866 const chain = this._chain.slice()
867 argv.slice(index).forEach(function (a) {
868 chain.push(a)
869 })
870 route['0x' + op.toString(16)] = mergeFunctionArgs(chain)
871
872 return this
873}