1 |
|
2 |
|
3 | const assert = require('assert')
|
4 | const EventEmitter = require('events').EventEmitter
|
5 | const net = require('net')
|
6 | const tls = require('tls')
|
7 | const util = require('util')
|
8 |
|
9 |
|
10 | const VError = require('verror').VError
|
11 |
|
12 | const dn = require('./dn')
|
13 | const dtrace = require('./dtrace')
|
14 | const errors = require('./errors')
|
15 | const Protocol = require('./protocol')
|
16 |
|
17 | const Parser = require('./messages').Parser
|
18 | const AbandonResponse = require('./messages/abandon_response')
|
19 | const AddResponse = require('./messages/add_response')
|
20 | const BindResponse = require('./messages/bind_response')
|
21 | const CompareResponse = require('./messages/compare_response')
|
22 | const DeleteResponse = require('./messages/del_response')
|
23 | const ExtendedResponse = require('./messages/ext_response')
|
24 |
|
25 | const ModifyResponse = require('./messages/modify_response')
|
26 | const ModifyDNResponse = require('./messages/moddn_response')
|
27 | const SearchRequest = require('./messages/search_request')
|
28 | const SearchResponse = require('./messages/search_response')
|
29 | const UnbindResponse = require('./messages/unbind_response')
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | const DN = dn.DN
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | function 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 |
|
68 | function 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 |
|
120 | function 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 |
|
131 | function 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 |
|
140 | function 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 |
|
150 | function 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 |
|
160 | function 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 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 | function 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 |
|
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 |
|
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 |
|
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 () {}
|
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
|
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 | }
|
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 | }
|
456 | util.inherits(Server, EventEmitter)
|
457 | Object.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 | })
|
502 | module.exports = Server
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 |
|
514 | Server.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 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 | Server.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 |
|
536 |
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 |
|
544 | Server.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 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 | Server.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 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 | Server.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 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 | Server.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 |
|
596 |
|
597 |
|
598 |
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 | Server.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 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 | Server.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 |
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 |
|
632 |
|
633 | Server.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 |
|
638 | Server.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 |
|
647 | Server.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 |
|
657 | Server.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 |
|
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 | }
|
688 | Server.prototype.listenFD = function (fd) {
|
689 | this.host = 'unix-domain-socket'
|
690 | this.port = fd
|
691 | return this.server.listenFD(fd)
|
692 | }
|
693 | Server.prototype.close = function (callback) {
|
694 | return this.server.close(callback)
|
695 | }
|
696 | Server.prototype.address = function () {
|
697 | return this.server.address()
|
698 | }
|
699 |
|
700 | Server.prototype.getConnections = function (callback) {
|
701 | return this.server.getConnections(callback)
|
702 | }
|
703 |
|
704 | Server.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 |
|
721 | this._routeKeyCache = null
|
722 | }
|
723 |
|
724 | return this.routes[name]
|
725 | }
|
726 |
|
727 | Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
|
728 |
|
729 |
|
730 | if (!this._routeKeyCache) {
|
731 | const self = this
|
732 | const reversedRDNsToKeys = {}
|
733 |
|
734 | Object.keys(this.routes).forEach(function (key) {
|
735 | const _dn = self.routes[key].dn
|
736 |
|
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 |
|
745 |
|
746 |
|
747 |
|
748 |
|
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 |
|
757 | Server.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 |
|
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 |
|
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 |
|
813 | assert.ok(req.dn)
|
814 | const keys = this._sortedRouteKeys()
|
815 | let fallbackHandler = [noSuffixHandler]
|
816 |
|
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 |
|
824 | if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') {
|
825 | if (route[op]) {
|
826 |
|
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 |
|
837 |
|
838 | fallbackHandler = [defaultHandler]
|
839 | }
|
840 | }
|
841 | }
|
842 | }
|
843 | return {
|
844 | backend: self,
|
845 | handlers: fallbackHandler
|
846 | }
|
847 | }
|
848 |
|
849 | Server.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 | }
|