Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server.js 24KB

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