server.js 26 KB

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