client.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. 'use strict'
  2. const requestQueueFactory = require('./request-queue')
  3. const messageTrackerFactory = require('./message-tracker')
  4. const { MAX_MSGID } = require('./constants')
  5. const EventEmitter = require('events').EventEmitter
  6. const net = require('net')
  7. const tls = require('tls')
  8. const util = require('util')
  9. const once = require('once')
  10. const backoff = require('backoff')
  11. const vasync = require('vasync')
  12. const assert = require('assert-plus')
  13. const VError = require('verror').VError
  14. const Attribute = require('@ldapjs/attribute')
  15. const Change = require('@ldapjs/change')
  16. const Control = require('../controls/index').Control
  17. const { Control: LdapControl } = require('@ldapjs/controls')
  18. const SearchPager = require('./search_pager')
  19. const Protocol = require('@ldapjs/protocol')
  20. const { DN } = require('@ldapjs/dn')
  21. const errors = require('../errors')
  22. const filters = require('@ldapjs/filter')
  23. const Parser = require('../messages/parser')
  24. const url = require('../url')
  25. const CorkedEmitter = require('../corked_emitter')
  26. /// --- Globals
  27. const messages = require('@ldapjs/messages')
  28. const {
  29. AbandonRequest,
  30. AddRequest,
  31. BindRequest,
  32. CompareRequest,
  33. DeleteRequest,
  34. ExtensionRequest: ExtendedRequest,
  35. ModifyRequest,
  36. ModifyDnRequest: ModifyDNRequest,
  37. SearchRequest,
  38. UnbindRequest,
  39. LdapResult: LDAPResult,
  40. SearchResultEntry: SearchEntry,
  41. SearchResultReference: SearchReference
  42. } = messages
  43. const PresenceFilter = filters.PresenceFilter
  44. const ConnectionError = errors.ConnectionError
  45. const CMP_EXPECT = [errors.LDAP_COMPARE_TRUE, errors.LDAP_COMPARE_FALSE]
  46. // node 0.6 got rid of FDs, so make up a client id for logging
  47. let CLIENT_ID = 0
  48. /// --- Internal Helpers
  49. function nextClientId () {
  50. if (++CLIENT_ID === MAX_MSGID) { return 1 }
  51. return CLIENT_ID
  52. }
  53. function validateControls (controls) {
  54. if (Array.isArray(controls)) {
  55. controls.forEach(function (c) {
  56. if (!(c instanceof Control) && !(c instanceof LdapControl)) { throw new TypeError('controls must be [Control]') }
  57. })
  58. } else if (controls instanceof Control || controls instanceof LdapControl) {
  59. controls = [controls]
  60. } else {
  61. throw new TypeError('controls must be [Control]')
  62. }
  63. return controls
  64. }
  65. function ensureDN (input) {
  66. if (DN.isDn(input)) {
  67. return input
  68. } else if (typeof (input) === 'string') {
  69. return DN.fromString(input)
  70. } else {
  71. throw new Error('invalid DN')
  72. }
  73. }
  74. /// --- API
  75. /**
  76. * Constructs a new client.
  77. *
  78. * The options object is required, and must contain either a URL (string) or
  79. * a socketPath (string); the socketPath is only if you want to talk to an LDAP
  80. * server over a Unix Domain Socket. Additionally, you can pass in a bunyan
  81. * option that is the result of `new Logger()`, presumably after you've
  82. * configured it.
  83. *
  84. * @param {Object} options must have either url or socketPath.
  85. * @throws {TypeError} on bad input.
  86. */
  87. function Client (options) {
  88. assert.ok(options)
  89. EventEmitter.call(this, options)
  90. const self = this
  91. this.urls = options.url ? [].concat(options.url).map(url.parse) : []
  92. this._nextServer = 0
  93. // updated in connectSocket() after each connect
  94. this.host = undefined
  95. this.port = undefined
  96. this.secure = undefined
  97. this.url = undefined
  98. this.tlsOptions = options.tlsOptions
  99. this.socketPath = options.socketPath || false
  100. this.log = options.log.child({ clazz: 'Client' }, true)
  101. this.timeout = parseInt((options.timeout || 0), 10)
  102. this.connectTimeout = parseInt((options.connectTimeout || 0), 10)
  103. this.idleTimeout = parseInt((options.idleTimeout || 0), 10)
  104. if (options.reconnect) {
  105. // Fall back to defaults if options.reconnect === true
  106. const rOpts = (typeof (options.reconnect) === 'object')
  107. ? options.reconnect
  108. : {}
  109. this.reconnect = {
  110. initialDelay: parseInt(rOpts.initialDelay || 100, 10),
  111. maxDelay: parseInt(rOpts.maxDelay || 10000, 10),
  112. failAfter: parseInt(rOpts.failAfter, 10) || Infinity
  113. }
  114. }
  115. this.queue = requestQueueFactory({
  116. size: parseInt((options.queueSize || 0), 10),
  117. timeout: parseInt((options.queueTimeout || 0), 10)
  118. })
  119. if (options.queueDisable) {
  120. this.queue.freeze()
  121. }
  122. // Implicitly configure setup action to bind the client if bindDN and
  123. // bindCredentials are passed in. This will more closely mimic PooledClient
  124. // auto-login behavior.
  125. if (options.bindDN !== undefined &&
  126. options.bindCredentials !== undefined) {
  127. this.on('setup', function (clt, cb) {
  128. clt.bind(options.bindDN, options.bindCredentials, function (err) {
  129. if (err) {
  130. if (self._socket) {
  131. self._socket.destroy()
  132. }
  133. self.emit('error', err)
  134. }
  135. cb(err)
  136. })
  137. })
  138. }
  139. this._socket = null
  140. this.connected = false
  141. this.connect()
  142. }
  143. util.inherits(Client, EventEmitter)
  144. module.exports = Client
  145. /**
  146. * Sends an abandon request to the LDAP server.
  147. *
  148. * The callback will be invoked as soon as the data is flushed out to the
  149. * network, as there is never a response from abandon.
  150. *
  151. * @param {Number} messageId the messageId to abandon.
  152. * @param {Control} controls (optional) either a Control or [Control].
  153. * @param {Function} callback of the form f(err).
  154. * @throws {TypeError} on invalid input.
  155. */
  156. Client.prototype.abandon = function abandon (messageId, controls, callback) {
  157. assert.number(messageId, 'messageId')
  158. if (typeof (controls) === 'function') {
  159. callback = controls
  160. controls = []
  161. } else {
  162. controls = validateControls(controls)
  163. }
  164. assert.func(callback, 'callback')
  165. const req = new AbandonRequest({
  166. abandonId: messageId,
  167. controls
  168. })
  169. return this._send(req, 'abandon', null, callback)
  170. }
  171. /**
  172. * Adds an entry to the LDAP server.
  173. *
  174. * Entry can be either [Attribute] or a plain JS object where the
  175. * values are either a plain value or an array of values. Any value (that's
  176. * not an array) will get converted to a string, so keep that in mind.
  177. *
  178. * @param {String} name the DN of the entry to add.
  179. * @param {Object} entry an array of Attributes to be added or a JS object.
  180. * @param {Control} controls (optional) either a Control or [Control].
  181. * @param {Function} callback of the form f(err, res).
  182. * @throws {TypeError} on invalid input.
  183. */
  184. Client.prototype.add = function add (name, entry, controls, callback) {
  185. assert.ok(name !== undefined, 'name')
  186. assert.object(entry, 'entry')
  187. if (typeof (controls) === 'function') {
  188. callback = controls
  189. controls = []
  190. } else {
  191. controls = validateControls(controls)
  192. }
  193. assert.func(callback, 'callback')
  194. if (Array.isArray(entry)) {
  195. entry.forEach(function (a) {
  196. if (!Attribute.isAttribute(a)) { throw new TypeError('entry must be an Array of Attributes') }
  197. })
  198. } else {
  199. const save = entry
  200. entry = []
  201. Object.keys(save).forEach(function (k) {
  202. const attr = new Attribute({ type: k })
  203. if (Array.isArray(save[k])) {
  204. save[k].forEach(function (v) {
  205. attr.addValue(v.toString())
  206. })
  207. } else if (Buffer.isBuffer(save[k])) {
  208. attr.addValue(save[k])
  209. } else {
  210. attr.addValue(save[k].toString())
  211. }
  212. entry.push(attr)
  213. })
  214. }
  215. const req = new AddRequest({
  216. entry: ensureDN(name),
  217. attributes: entry,
  218. controls
  219. })
  220. return this._send(req, [errors.LDAP_SUCCESS], null, callback)
  221. }
  222. /**
  223. * Performs a simple authentication against the server.
  224. *
  225. * @param {String} name the DN to bind as.
  226. * @param {String} credentials the userPassword associated with name.
  227. * @param {Control} controls (optional) either a Control or [Control].
  228. * @param {Function} callback of the form f(err, res).
  229. * @throws {TypeError} on invalid input.
  230. */
  231. Client.prototype.bind = function bind (name,
  232. credentials,
  233. controls,
  234. callback,
  235. _bypass) {
  236. if (
  237. typeof (name) !== 'string' &&
  238. Object.prototype.toString.call(name) !== '[object LdapDn]'
  239. ) {
  240. throw new TypeError('name (string) required')
  241. }
  242. assert.optionalString(credentials, 'credentials')
  243. if (typeof (controls) === 'function') {
  244. callback = controls
  245. controls = []
  246. } else {
  247. controls = validateControls(controls)
  248. }
  249. assert.func(callback, 'callback')
  250. const req = new BindRequest({
  251. name: name || '',
  252. authentication: 'Simple',
  253. credentials: credentials || '',
  254. controls
  255. })
  256. // Connection errors will be reported to the bind callback too (useful when the LDAP server is not available)
  257. const self = this
  258. function callbackWrapper (err, ret) {
  259. self.removeListener('connectError', callbackWrapper)
  260. callback(err, ret)
  261. }
  262. this.addListener('connectError', callbackWrapper)
  263. return this._send(req, [errors.LDAP_SUCCESS], null, callbackWrapper, _bypass)
  264. }
  265. /**
  266. * Compares an attribute/value pair with an entry on the LDAP server.
  267. *
  268. * @param {String} name the DN of the entry to compare attributes with.
  269. * @param {String} attr name of an attribute to check.
  270. * @param {String} value value of an attribute to check.
  271. * @param {Control} controls (optional) either a Control or [Control].
  272. * @param {Function} callback of the form f(err, boolean, res).
  273. * @throws {TypeError} on invalid input.
  274. */
  275. Client.prototype.compare = function compare (name,
  276. attr,
  277. value,
  278. controls,
  279. callback) {
  280. assert.ok(name !== undefined, 'name')
  281. assert.string(attr, 'attr')
  282. assert.string(value, 'value')
  283. if (typeof (controls) === 'function') {
  284. callback = controls
  285. controls = []
  286. } else {
  287. controls = validateControls(controls)
  288. }
  289. assert.func(callback, 'callback')
  290. const req = new CompareRequest({
  291. entry: ensureDN(name),
  292. attribute: attr,
  293. value,
  294. controls
  295. })
  296. return this._send(req, CMP_EXPECT, null, function (err, res) {
  297. if (err) { return callback(err) }
  298. return callback(null, (res.status === errors.LDAP_COMPARE_TRUE), res)
  299. })
  300. }
  301. /**
  302. * Deletes an entry from the LDAP server.
  303. *
  304. * @param {String} name the DN of the entry to delete.
  305. * @param {Control} controls (optional) either a Control or [Control].
  306. * @param {Function} callback of the form f(err, res).
  307. * @throws {TypeError} on invalid input.
  308. */
  309. Client.prototype.del = function del (name, controls, callback) {
  310. assert.ok(name !== undefined, 'name')
  311. if (typeof (controls) === 'function') {
  312. callback = controls
  313. controls = []
  314. } else {
  315. controls = validateControls(controls)
  316. }
  317. assert.func(callback, 'callback')
  318. const req = new DeleteRequest({
  319. entry: ensureDN(name),
  320. controls
  321. })
  322. return this._send(req, [errors.LDAP_SUCCESS], null, callback)
  323. }
  324. /**
  325. * Performs an extended operation on the LDAP server.
  326. *
  327. * Pretty much none of the LDAP extended operations return an OID
  328. * (responseName), so I just don't bother giving it back in the callback.
  329. * It's on the third param in `res` if you need it.
  330. *
  331. * @param {String} name the OID of the extended operation to perform.
  332. * @param {String} value value to pass in for this operation.
  333. * @param {Control} controls (optional) either a Control or [Control].
  334. * @param {Function} callback of the form f(err, value, res).
  335. * @throws {TypeError} on invalid input.
  336. */
  337. Client.prototype.exop = function exop (name, value, controls, callback) {
  338. assert.string(name, 'name')
  339. if (typeof (value) === 'function') {
  340. callback = value
  341. controls = []
  342. value = undefined
  343. }
  344. if (typeof (controls) === 'function') {
  345. callback = controls
  346. controls = []
  347. } else {
  348. controls = validateControls(controls)
  349. }
  350. assert.func(callback, 'callback')
  351. const req = new ExtendedRequest({
  352. requestName: name,
  353. requestValue: value,
  354. controls
  355. })
  356. return this._send(req, [errors.LDAP_SUCCESS], null, function (err, res) {
  357. if (err) { return callback(err) }
  358. return callback(null, res.responseValue || '', res)
  359. })
  360. }
  361. /**
  362. * Performs an LDAP modify against the server.
  363. *
  364. * @param {String} name the DN of the entry to modify.
  365. * @param {Change} change update to perform (can be [Change]).
  366. * @param {Control} controls (optional) either a Control or [Control].
  367. * @param {Function} callback of the form f(err, res).
  368. * @throws {TypeError} on invalid input.
  369. */
  370. Client.prototype.modify = function modify (name, change, controls, callback) {
  371. assert.ok(name !== undefined, 'name')
  372. assert.object(change, 'change')
  373. const changes = []
  374. function changeFromObject (obj) {
  375. if (!obj.operation && !obj.type) { throw new Error('change.operation required') }
  376. if (typeof (obj.modification) !== 'object') { throw new Error('change.modification (object) required') }
  377. if (Object.keys(obj.modification).length === 2 &&
  378. typeof (obj.modification.type) === 'string' &&
  379. Array.isArray(obj.modification.vals)) {
  380. // Use modification directly if it's already normalized:
  381. changes.push(new Change({
  382. operation: obj.operation || obj.type,
  383. modification: obj.modification
  384. }))
  385. } else {
  386. // Normalize the modification object
  387. Object.keys(obj.modification).forEach(function (k) {
  388. const mod = {}
  389. mod[k] = obj.modification[k]
  390. changes.push(new Change({
  391. operation: obj.operation || obj.type,
  392. modification: mod
  393. }))
  394. })
  395. }
  396. }
  397. if (Change.isChange(change)) {
  398. changes.push(change)
  399. } else if (Array.isArray(change)) {
  400. change.forEach(function (c) {
  401. if (Change.isChange(c)) {
  402. changes.push(c)
  403. } else {
  404. changeFromObject(c)
  405. }
  406. })
  407. } else {
  408. changeFromObject(change)
  409. }
  410. if (typeof (controls) === 'function') {
  411. callback = controls
  412. controls = []
  413. } else {
  414. controls = validateControls(controls)
  415. }
  416. assert.func(callback, 'callback')
  417. const req = new ModifyRequest({
  418. object: ensureDN(name),
  419. changes,
  420. controls
  421. })
  422. return this._send(req, [errors.LDAP_SUCCESS], null, callback)
  423. }
  424. /**
  425. * Performs an LDAP modifyDN against the server.
  426. *
  427. * This does not allow you to keep the old DN, as while the LDAP protocol
  428. * has a facility for that, it's stupid. Just Search/Add.
  429. *
  430. * This will automatically deal with "new superior" logic.
  431. *
  432. * @param {String} name the DN of the entry to modify.
  433. * @param {String} newName the new DN to move this entry to.
  434. * @param {Control} controls (optional) either a Control or [Control].
  435. * @param {Function} callback of the form f(err, res).
  436. * @throws {TypeError} on invalid input.
  437. */
  438. Client.prototype.modifyDN = function modifyDN (name,
  439. newName,
  440. controls,
  441. callback) {
  442. assert.ok(name !== undefined, 'name')
  443. assert.string(newName, 'newName')
  444. if (typeof (controls) === 'function') {
  445. callback = controls
  446. controls = []
  447. } else {
  448. controls = validateControls(controls)
  449. }
  450. assert.func(callback)
  451. const newDN = DN.fromString(newName)
  452. const req = new ModifyDNRequest({
  453. entry: DN.fromString(name),
  454. deleteOldRdn: true,
  455. controls
  456. })
  457. if (newDN.length !== 1) {
  458. req.newRdn = DN.fromString(newDN.shift().toString())
  459. req.newSuperior = newDN
  460. } else {
  461. req.newRdn = newDN
  462. }
  463. return this._send(req, [errors.LDAP_SUCCESS], null, callback)
  464. }
  465. /**
  466. * Performs an LDAP search against the server.
  467. *
  468. * Note that the defaults for options are a 'base' search, if that's what
  469. * you want you can just pass in a string for options and it will be treated
  470. * as the search filter. Also, you can either pass in programatic Filter
  471. * objects or a filter string as the filter option.
  472. *
  473. * Note that this method is 'special' in that the callback 'res' param will
  474. * have two important events on it, namely 'entry' and 'end' that you can hook
  475. * to. The former will emit a SearchEntry object for each record that comes
  476. * back, and the latter will emit a normal LDAPResult object.
  477. *
  478. * @param {String} base the DN in the tree to start searching at.
  479. * @param {Object} options parameters:
  480. * - {String} scope default of 'base'.
  481. * - {String} filter default of '(objectclass=*)'.
  482. * - {Array} attributes [string] to return.
  483. * - {Boolean} attrsOnly whether to return values.
  484. * @param {Control} controls (optional) either a Control or [Control].
  485. * @param {Function} callback of the form f(err, res).
  486. * @throws {TypeError} on invalid input.
  487. */
  488. Client.prototype.search = function search (base,
  489. options,
  490. controls,
  491. callback,
  492. _bypass) {
  493. assert.ok(base !== undefined, 'search base')
  494. if (Array.isArray(options) || (options instanceof Control)) {
  495. controls = options
  496. options = {}
  497. } else if (typeof (options) === 'function') {
  498. callback = options
  499. controls = []
  500. options = {
  501. filter: new PresenceFilter({ attribute: 'objectclass' })
  502. }
  503. } else if (typeof (options) === 'string') {
  504. options = { filter: filters.parseString(options) }
  505. } else if (typeof (options) !== 'object') {
  506. throw new TypeError('options (object) required')
  507. }
  508. if (typeof (options.filter) === 'string') {
  509. options.filter = filters.parseString(options.filter)
  510. } else if (!options.filter) {
  511. options.filter = new PresenceFilter({ attribute: 'objectclass' })
  512. } else if (Object.prototype.toString.call(options.filter) !== '[object FilterString]') {
  513. throw new TypeError('options.filter (Filter) required')
  514. }
  515. if (typeof (controls) === 'function') {
  516. callback = controls
  517. controls = []
  518. } else {
  519. controls = validateControls(controls)
  520. }
  521. assert.func(callback, 'callback')
  522. if (options.attributes) {
  523. if (!Array.isArray(options.attributes)) {
  524. if (typeof (options.attributes) === 'string') {
  525. options.attributes = [options.attributes]
  526. } else {
  527. throw new TypeError('options.attributes must be an Array of Strings')
  528. }
  529. }
  530. }
  531. const self = this
  532. const baseDN = ensureDN(base)
  533. function sendRequest (ctrls, emitter, cb) {
  534. const req = new SearchRequest({
  535. baseObject: baseDN,
  536. scope: options.scope || 'base',
  537. filter: options.filter,
  538. derefAliases: options.derefAliases || Protocol.search.NEVER_DEREF_ALIASES,
  539. sizeLimit: options.sizeLimit || 0,
  540. timeLimit: options.timeLimit || 10,
  541. typesOnly: options.typesOnly || false,
  542. attributes: options.attributes || [],
  543. controls: ctrls
  544. })
  545. return self._send(req,
  546. [errors.LDAP_SUCCESS],
  547. emitter,
  548. cb,
  549. _bypass)
  550. }
  551. if (options.paged) {
  552. // Perform automated search paging
  553. const pageOpts = typeof (options.paged) === 'object' ? options.paged : {}
  554. let size = 100 // Default page size
  555. if (pageOpts.pageSize > 0) {
  556. size = pageOpts.pageSize
  557. } else if (options.sizeLimit > 1) {
  558. // According to the RFC, servers should ignore the paging control if
  559. // pageSize >= sizelimit. Some might still send results, but it's safer
  560. // to stay under that figure when assigning a default value.
  561. size = options.sizeLimit - 1
  562. }
  563. const pager = new SearchPager({
  564. callback,
  565. controls,
  566. pageSize: size,
  567. pagePause: pageOpts.pagePause,
  568. sendRequest
  569. })
  570. pager.begin()
  571. } else {
  572. sendRequest(controls, new CorkedEmitter(), callback)
  573. }
  574. }
  575. /**
  576. * Unbinds this client from the LDAP server.
  577. *
  578. * Note that unbind does not have a response, so this callback is actually
  579. * optional; either way, the client is disconnected.
  580. *
  581. * @param {Function} callback of the form f(err).
  582. * @throws {TypeError} if you pass in callback as not a function.
  583. */
  584. Client.prototype.unbind = function unbind (callback) {
  585. if (!callback) { callback = function () {} }
  586. if (typeof (callback) !== 'function') { throw new TypeError('callback must be a function') }
  587. // When the socket closes, it is useful to know whether it was due to a
  588. // user-initiated unbind or something else.
  589. this.unbound = true
  590. if (!this._socket) { return callback() }
  591. const req = new UnbindRequest()
  592. return this._send(req, 'unbind', null, callback)
  593. }
  594. /**
  595. * Attempt to secure connection with StartTLS.
  596. */
  597. Client.prototype.starttls = function starttls (options,
  598. controls,
  599. callback,
  600. _bypass) {
  601. assert.optionalObject(options)
  602. options = options || {}
  603. callback = once(callback)
  604. const self = this
  605. if (this._starttls) {
  606. return callback(new Error('STARTTLS already in progress or active'))
  607. }
  608. function onSend (sendErr, emitter) {
  609. if (sendErr) {
  610. callback(sendErr)
  611. return
  612. }
  613. /*
  614. * Now that the request has been sent, block all outgoing messages
  615. * until an error is received or we successfully complete the setup.
  616. */
  617. // TODO: block traffic
  618. self._starttls = {
  619. started: true
  620. }
  621. emitter.on('error', function (err) {
  622. self._starttls = null
  623. callback(err)
  624. })
  625. emitter.on('end', function (_res) {
  626. const sock = self._socket
  627. /*
  628. * Unplumb socket data during SSL negotiation.
  629. * This will prevent the LDAP parser from stumbling over the TLS
  630. * handshake and raising a ruckus.
  631. */
  632. sock.removeAllListeners('data')
  633. options.socket = sock
  634. const secure = tls.connect(options)
  635. secure.once('secureConnect', function () {
  636. /*
  637. * Wire up 'data' and 'error' handlers like the normal socket.
  638. * Handling 'end' events isn't necessary since the underlying socket
  639. * will handle those.
  640. */
  641. secure.removeAllListeners('error')
  642. secure.on('data', function onData (data) {
  643. self.log.trace('data event: %s', util.inspect(data))
  644. self._tracker.parser.write(data)
  645. })
  646. secure.on('error', function (err) {
  647. self.log.trace({ err }, 'error event: %s', new Error().stack)
  648. self.emit('error', err)
  649. sock.destroy()
  650. })
  651. callback(null)
  652. })
  653. secure.once('error', function (err) {
  654. // If the SSL negotiation failed, to back to plain mode.
  655. self._starttls = null
  656. secure.removeAllListeners()
  657. callback(err)
  658. })
  659. self._starttls.success = true
  660. self._socket = secure
  661. })
  662. }
  663. const req = new ExtendedRequest({
  664. requestName: '1.3.6.1.4.1.1466.20037',
  665. requestValue: null,
  666. controls
  667. })
  668. return this._send(req,
  669. [errors.LDAP_SUCCESS],
  670. new EventEmitter(),
  671. onSend,
  672. _bypass)
  673. }
  674. /**
  675. * Disconnect from the LDAP server and do not allow reconnection.
  676. *
  677. * If the client is instantiated with proper reconnection options, it's
  678. * possible to initiate new requests after a call to unbind since the client
  679. * will attempt to reconnect in order to fulfill the request.
  680. *
  681. * Calling destroy will prevent any further reconnection from occurring.
  682. *
  683. * @param {Object} err (Optional) error that was cause of client destruction
  684. */
  685. Client.prototype.destroy = function destroy (err) {
  686. this.destroyed = true
  687. this.queue.freeze()
  688. // Purge any queued requests which are now meaningless
  689. this.queue.flush(function (msg, expect, emitter, cb) {
  690. if (typeof (cb) === 'function') {
  691. cb(new Error('client destroyed'))
  692. }
  693. })
  694. if (this.connected) {
  695. this.unbind()
  696. }
  697. if (this._socket) {
  698. this._socket.destroy()
  699. }
  700. this.emit('destroy', err)
  701. }
  702. /**
  703. * Initiate LDAP connection.
  704. */
  705. Client.prototype.connect = function connect () {
  706. if (this.connecting || this.connected) {
  707. return
  708. }
  709. const self = this
  710. const log = this.log
  711. let socket
  712. let tracker
  713. // Establish basic socket connection
  714. function connectSocket (cb) {
  715. const server = self.urls[self._nextServer]
  716. self._nextServer = (self._nextServer + 1) % self.urls.length
  717. cb = once(cb)
  718. function onResult (err, res) {
  719. if (err) {
  720. if (self.connectTimer) {
  721. clearTimeout(self.connectTimer)
  722. self.connectTimer = null
  723. }
  724. self.emit('connectError', err)
  725. }
  726. cb(err, res)
  727. }
  728. function onConnect () {
  729. if (self.connectTimer) {
  730. clearTimeout(self.connectTimer)
  731. self.connectTimer = null
  732. }
  733. socket.removeAllListeners('error')
  734. .removeAllListeners('connect')
  735. .removeAllListeners('secureConnect')
  736. tracker.id = nextClientId() + '__' + tracker.id
  737. self.log = self.log.child({ ldap_id: tracker.id }, true)
  738. // Move on to client setup
  739. setupClient(cb)
  740. }
  741. const port = (server && server.port) || self.socketPath
  742. const host = server && server.hostname
  743. if (server && server.secure) {
  744. socket = tls.connect(port, host, self.tlsOptions)
  745. socket.once('secureConnect', onConnect)
  746. } else {
  747. socket = net.connect(port, host)
  748. socket.once('connect', onConnect)
  749. }
  750. socket.once('error', onResult)
  751. initSocket(server)
  752. // Setup connection timeout handling, if desired
  753. if (self.connectTimeout) {
  754. self.connectTimer = setTimeout(function onConnectTimeout () {
  755. if (!socket || !socket.readable || !socket.writeable) {
  756. socket.destroy()
  757. self._socket = null
  758. onResult(new ConnectionError('connection timeout'))
  759. }
  760. }, self.connectTimeout)
  761. }
  762. }
  763. // Initialize socket events and LDAP parser.
  764. function initSocket (server) {
  765. tracker = messageTrackerFactory({
  766. id: server ? server.href : self.socketPath,
  767. parser: new Parser({ log })
  768. })
  769. // This won't be set on TLS. So. Very. Annoying.
  770. if (typeof (socket.setKeepAlive) !== 'function') {
  771. socket.setKeepAlive = function setKeepAlive (enable, delay) {
  772. return socket.socket
  773. ? socket.socket.setKeepAlive(enable, delay)
  774. : false
  775. }
  776. }
  777. socket.on('data', function onData (data) {
  778. log.trace('data event: %s', util.inspect(data))
  779. tracker.parser.write(data)
  780. })
  781. // The "router"
  782. //
  783. // This is invoked after the incoming BER has been parsed into a JavaScript
  784. // object.
  785. tracker.parser.on('message', function onMessage (message) {
  786. message.connection = self._socket
  787. const trackedObject = tracker.fetch(message.messageId)
  788. if (!trackedObject) {
  789. log.error({ message: message.pojo }, 'unmatched server message received')
  790. return false
  791. }
  792. const { message: trackedMessage, callback } = trackedObject
  793. if (!callback) {
  794. log.error({ message: message.pojo }, 'unsolicited message')
  795. return false
  796. }
  797. // Some message types have narrower implementations and require extra
  798. // parsing to be complete. In particular, ExtensionRequest messages will
  799. // return responses that do not identify the request that generated them.
  800. // Therefore, we have to match the response to the request and handle
  801. // the extra processing accordingly.
  802. switch (trackedMessage.type) {
  803. case 'ExtensionRequest': {
  804. const extensionType = ExtendedRequest.recognizedOIDs().lookupName(trackedMessage.requestName)
  805. switch (extensionType) {
  806. case 'PASSWORD_MODIFY': {
  807. message = messages.PasswordModifyResponse.fromResponse(message)
  808. break
  809. }
  810. case 'WHO_AM_I': {
  811. message = messages.WhoAmIResponse.fromResponse(message)
  812. break
  813. }
  814. default:
  815. }
  816. break
  817. }
  818. default:
  819. }
  820. return callback(message)
  821. })
  822. tracker.parser.on('error', function onParseError (err) {
  823. self.emit('error', new VError(err, 'Parser error for %s',
  824. tracker.id))
  825. self.connected = false
  826. socket.end()
  827. })
  828. }
  829. // After connect, register socket event handlers and run any setup actions
  830. function setupClient (cb) {
  831. cb = once(cb)
  832. // Indicate failure if anything goes awry during setup
  833. function bail (err) {
  834. socket.destroy()
  835. cb(err || new Error('client error during setup'))
  836. }
  837. // Work around lack of close event on tls.socket in node < 0.11
  838. ((socket.socket) ? socket.socket : socket).once('close', bail)
  839. socket.once('error', bail)
  840. socket.once('end', bail)
  841. socket.once('timeout', bail)
  842. socket.once('cleanupSetupListeners', function onCleanup () {
  843. socket.removeListener('error', bail)
  844. .removeListener('close', bail)
  845. .removeListener('end', bail)
  846. .removeListener('timeout', bail)
  847. })
  848. self._socket = socket
  849. self._tracker = tracker
  850. // Run any requested setup (such as automatically performing a bind) on
  851. // socket before signalling successful connection.
  852. // This setup needs to bypass the request queue since all other activity is
  853. // blocked until the connection is considered fully established post-setup.
  854. // Only allow bind/search/starttls for now.
  855. const basicClient = {
  856. bind: function bindBypass (name, credentials, controls, callback) {
  857. return self.bind(name, credentials, controls, callback, true)
  858. },
  859. search: function searchBypass (base, options, controls, callback) {
  860. return self.search(base, options, controls, callback, true)
  861. },
  862. starttls: function starttlsBypass (options, controls, callback) {
  863. return self.starttls(options, controls, callback, true)
  864. },
  865. unbind: self.unbind.bind(self)
  866. }
  867. vasync.forEachPipeline({
  868. func: function (f, callback) {
  869. f(basicClient, callback)
  870. },
  871. inputs: self.listeners('setup')
  872. }, function (err, _res) {
  873. if (err) {
  874. self.emit('setupError', err)
  875. }
  876. cb(err)
  877. })
  878. }
  879. // Wire up "official" event handlers after successful connect/setup
  880. function postSetup () {
  881. // cleanup the listeners we attached in setup phrase.
  882. socket.emit('cleanupSetupListeners');
  883. // Work around lack of close event on tls.socket in node < 0.11
  884. ((socket.socket) ? socket.socket : socket).once('close',
  885. self._onClose.bind(self))
  886. socket.on('end', function onEnd () {
  887. log.trace('end event')
  888. self.emit('end')
  889. socket.end()
  890. })
  891. socket.on('error', function onSocketError (err) {
  892. log.trace({ err }, 'error event: %s', new Error().stack)
  893. self.emit('error', err)
  894. socket.destroy()
  895. })
  896. socket.on('timeout', function onTimeout () {
  897. log.trace('timeout event')
  898. self.emit('socketTimeout')
  899. socket.end()
  900. })
  901. const server = self.urls[self._nextServer]
  902. if (server) {
  903. self.host = server.hostname
  904. self.port = server.port
  905. self.secure = server.secure
  906. }
  907. }
  908. let retry
  909. let failAfter
  910. if (this.reconnect) {
  911. retry = backoff.exponential({
  912. initialDelay: this.reconnect.initialDelay,
  913. maxDelay: this.reconnect.maxDelay
  914. })
  915. failAfter = this.reconnect.failAfter
  916. if (this.urls.length > 1 && failAfter) {
  917. failAfter *= this.urls.length
  918. }
  919. } else {
  920. retry = backoff.exponential({
  921. initialDelay: 1,
  922. maxDelay: 2
  923. })
  924. failAfter = this.urls.length || 1
  925. }
  926. retry.failAfter(failAfter)
  927. retry.on('ready', function (num, _delay) {
  928. if (self.destroyed) {
  929. // Cease connection attempts if destroyed
  930. return
  931. }
  932. connectSocket(function (err) {
  933. if (!err) {
  934. postSetup()
  935. self.connecting = false
  936. self.connected = true
  937. self.emit('connect', socket)
  938. self.log.debug('connected after %d attempt(s)', num + 1)
  939. // Flush any queued requests
  940. self._flushQueue()
  941. self._connectRetry = null
  942. } else {
  943. retry.backoff(err)
  944. }
  945. })
  946. })
  947. retry.on('fail', function (err) {
  948. if (self.destroyed) {
  949. // Silence any connect/setup errors if destroyed
  950. return
  951. }
  952. self.log.debug('failed to connect after %d attempts', failAfter)
  953. // Communicate the last-encountered error
  954. if (err instanceof ConnectionError) {
  955. self.emitError('connectTimeout', err)
  956. } else if (err.code === 'ECONNREFUSED') {
  957. self.emitError('connectRefused', err)
  958. } else {
  959. self.emit('error', err)
  960. }
  961. })
  962. this._connectRetry = retry
  963. this.connecting = true
  964. retry.backoff()
  965. }
  966. /// --- Private API
  967. /**
  968. * Flush queued requests out to the socket.
  969. */
  970. Client.prototype._flushQueue = function _flushQueue () {
  971. // Pull items we're about to process out of the queue.
  972. this.queue.flush(this._send.bind(this))
  973. }
  974. /**
  975. * Clean up socket/parser resources after socket close.
  976. */
  977. Client.prototype._onClose = function _onClose (closeError) {
  978. const socket = this._socket
  979. const tracker = this._tracker
  980. socket.removeAllListeners('connect')
  981. .removeAllListeners('data')
  982. .removeAllListeners('drain')
  983. .removeAllListeners('end')
  984. .removeAllListeners('error')
  985. .removeAllListeners('timeout')
  986. this._socket = null
  987. this.connected = false;
  988. ((socket.socket) ? socket.socket : socket).removeAllListeners('close')
  989. this.log.trace('close event had_err=%s', closeError ? 'yes' : 'no')
  990. this.emit('close', closeError)
  991. // On close we have to walk the outstanding messages and go invoke their
  992. // callback with an error.
  993. tracker.purge(function (msgid, cb) {
  994. if (socket.unbindMessageID !== msgid) {
  995. return cb(new ConnectionError(tracker.id + ' closed'))
  996. } else {
  997. // Unbinds will be communicated as a success since we're closed
  998. // TODO: we are faking this "UnbindResponse" object in order to make
  999. // tests pass. There is no such thing as an "unbind response" in the LDAP
  1000. // protocol. When the client is revamped, this logic should be removed.
  1001. // ~ jsumners 2023-02-16
  1002. const Unbind = class extends LDAPResult {
  1003. messageID = msgid
  1004. messageId = msgid
  1005. status = 'unbind'
  1006. }
  1007. const unbind = new Unbind()
  1008. return cb(unbind)
  1009. }
  1010. })
  1011. // Trash any parser or starttls state
  1012. this._tracker = null
  1013. delete this._starttls
  1014. // Automatically fire reconnect logic if the socket was closed for any reason
  1015. // other than a user-initiated unbind.
  1016. if (this.reconnect && !this.unbound) {
  1017. this.connect()
  1018. }
  1019. this.unbound = false
  1020. return false
  1021. }
  1022. /**
  1023. * Maintain idle timer for client.
  1024. *
  1025. * Will start timer to fire 'idle' event if conditions are satisfied. If
  1026. * conditions are not met and a timer is running, it will be cleared.
  1027. *
  1028. * @param {Boolean} override explicitly disable timer.
  1029. */
  1030. Client.prototype._updateIdle = function _updateIdle (override) {
  1031. if (this.idleTimeout === 0) {
  1032. return
  1033. }
  1034. // Client must be connected but not waiting on any request data
  1035. const self = this
  1036. function isIdle (disable) {
  1037. return ((disable !== true) &&
  1038. (self._socket && self.connected) &&
  1039. (self._tracker.pending === 0))
  1040. }
  1041. if (isIdle(override)) {
  1042. if (!this._idleTimer) {
  1043. this._idleTimer = setTimeout(function () {
  1044. // Double-check idleness in case socket was torn down
  1045. if (isIdle()) {
  1046. self.emit('idle')
  1047. }
  1048. }, this.idleTimeout)
  1049. }
  1050. } else {
  1051. if (this._idleTimer) {
  1052. clearTimeout(this._idleTimer)
  1053. this._idleTimer = null
  1054. }
  1055. }
  1056. }
  1057. /**
  1058. * Attempt to send an LDAP request.
  1059. */
  1060. Client.prototype._send = function _send (message,
  1061. expect,
  1062. emitter,
  1063. callback,
  1064. _bypass) {
  1065. assert.ok(message)
  1066. assert.ok(expect)
  1067. assert.optionalObject(emitter)
  1068. assert.ok(callback)
  1069. // Allow connect setup traffic to bypass checks
  1070. if (_bypass && this._socket && this._socket.writable) {
  1071. return this._sendSocket(message, expect, emitter, callback)
  1072. }
  1073. if (!this._socket || !this.connected) {
  1074. if (!this.queue.enqueue(message, expect, emitter, callback)) {
  1075. callback(new ConnectionError('connection unavailable'))
  1076. }
  1077. // Initiate reconnect if needed
  1078. if (this.reconnect) {
  1079. this.connect()
  1080. }
  1081. return false
  1082. } else {
  1083. this._flushQueue()
  1084. return this._sendSocket(message, expect, emitter, callback)
  1085. }
  1086. }
  1087. Client.prototype._sendSocket = function _sendSocket (message,
  1088. expect,
  1089. emitter,
  1090. callback) {
  1091. const conn = this._socket
  1092. const tracker = this._tracker
  1093. const log = this.log
  1094. const self = this
  1095. let timer = false
  1096. let sentEmitter = false
  1097. function sendResult (event, obj) {
  1098. if (event === 'error') {
  1099. self.emit('resultError', obj)
  1100. }
  1101. if (emitter) {
  1102. if (event === 'error') {
  1103. // Error will go unhandled if emitter hasn't been sent via callback.
  1104. // Execute callback with the error instead.
  1105. if (!sentEmitter) { return callback(obj) }
  1106. }
  1107. return emitter.emit(event, obj)
  1108. }
  1109. if (event === 'error') { return callback(obj) }
  1110. return callback(null, obj)
  1111. }
  1112. function messageCallback (msg) {
  1113. if (timer) { clearTimeout(timer) }
  1114. log.trace({ msg: msg ? msg.pojo : null }, 'response received')
  1115. if (expect === 'abandon') { return sendResult('end', null) }
  1116. if (msg instanceof SearchEntry || msg instanceof SearchReference) {
  1117. let event = msg.constructor.name
  1118. // Generate the event name for the event emitter, i.e. "searchEntry"
  1119. // and "searchReference".
  1120. event = (event[0].toLowerCase() + event.slice(1)).replaceAll('Result', '')
  1121. return sendResult(event, msg)
  1122. } else {
  1123. tracker.remove(message.messageId)
  1124. // Potentially mark client as idle
  1125. self._updateIdle()
  1126. if (msg instanceof LDAPResult) {
  1127. if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
  1128. return sendResult('error', errors.getError(msg))
  1129. }
  1130. return sendResult('end', msg)
  1131. } else if (msg instanceof Error) {
  1132. return sendResult('error', msg)
  1133. } else {
  1134. return sendResult('error', new errors.ProtocolError(msg.type))
  1135. }
  1136. }
  1137. }
  1138. function onRequestTimeout () {
  1139. self.emit('timeout', message)
  1140. const { callback: cb } = tracker.fetch(message.messageId)
  1141. if (cb) {
  1142. // FIXME: the timed-out request should be abandoned
  1143. cb(new errors.TimeoutError('request timeout (client interrupt)'))
  1144. }
  1145. }
  1146. function writeCallback () {
  1147. if (expect === 'abandon') {
  1148. // Mark the messageId specified as abandoned
  1149. tracker.abandon(message.abandonId)
  1150. // No need to track the abandon request itself
  1151. tracker.remove(message.id)
  1152. return callback(null)
  1153. } else if (expect === 'unbind') {
  1154. conn.unbindMessageID = message.id
  1155. // Mark client as disconnected once unbind clears the socket
  1156. self.connected = false
  1157. // Some servers will RST the connection after receiving an unbind.
  1158. // Socket errors are blackholed since the connection is being closed.
  1159. conn.removeAllListeners('error')
  1160. conn.on('error', function () {})
  1161. conn.end()
  1162. } else if (emitter) {
  1163. sentEmitter = true
  1164. callback(null, emitter)
  1165. emitter.emit('searchRequest', message)
  1166. return
  1167. }
  1168. return false
  1169. }
  1170. // Start actually doing something...
  1171. tracker.track(message, messageCallback)
  1172. // Mark client as active
  1173. this._updateIdle(true)
  1174. if (self.timeout) {
  1175. log.trace('Setting timeout to %d', self.timeout)
  1176. timer = setTimeout(onRequestTimeout, self.timeout)
  1177. }
  1178. log.trace('sending request %j', message.pojo)
  1179. try {
  1180. const messageBer = message.toBer()
  1181. return conn.write(messageBer.buffer, writeCallback)
  1182. } catch (e) {
  1183. if (timer) { clearTimeout(timer) }
  1184. log.trace({ err: e }, 'Error writing message to socket')
  1185. return callback(e)
  1186. }
  1187. }
  1188. Client.prototype.emitError = function emitError (event, err) {
  1189. if (event !== 'error' && err && this.listenerCount(event) === 0) {
  1190. if (typeof err === 'string') {
  1191. err = event + ': ' + err
  1192. } else if (err.message) {
  1193. err.message = event + ': ' + err.message
  1194. }
  1195. this.emit('error', err)
  1196. }
  1197. this.emit(event, err)
  1198. }