UNPKG

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