First commit
This commit is contained in:
1345
node_modules/ldapjs/lib/client/client.js
generated
vendored
Normal file
1345
node_modules/ldapjs/lib/client/client.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
node_modules/ldapjs/lib/client/constants.js
generated
vendored
Normal file
7
node_modules/ldapjs/lib/client/constants.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
// https://tools.ietf.org/html/rfc4511#section-4.1.1
|
||||
// Message identifiers are an integer between (0, maxint).
|
||||
MAX_MSGID: Math.pow(2, 31) - 1
|
||||
}
|
||||
23
node_modules/ldapjs/lib/client/index.js
generated
vendored
Normal file
23
node_modules/ldapjs/lib/client/index.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const logger = require('../logger')
|
||||
const Client = require('./client')
|
||||
|
||||
module.exports = {
|
||||
Client,
|
||||
createClient: function createClient (options) {
|
||||
if (isObject(options) === false) throw TypeError('options (object) required')
|
||||
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required')
|
||||
if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string')
|
||||
if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required')
|
||||
if (!options.log) options.log = logger
|
||||
if (isObject(options.log) !== true) throw TypeError('options.log must be an object')
|
||||
if (!options.log.child) options.log.child = function () { return options.log }
|
||||
|
||||
return new Client(options)
|
||||
}
|
||||
}
|
||||
|
||||
function isObject (input) {
|
||||
return Object.prototype.toString.apply(input) === '[object Object]'
|
||||
}
|
||||
25
node_modules/ldapjs/lib/client/message-tracker/ge-window.js
generated
vendored
Normal file
25
node_modules/ldapjs/lib/client/message-tracker/ge-window.js
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const { MAX_MSGID } = require('../constants')
|
||||
|
||||
/**
|
||||
* Compare a reference id with another id to determine "greater than or equal"
|
||||
* between the two values according to a sliding window.
|
||||
*
|
||||
* @param {integer} ref
|
||||
* @param {integer} comp
|
||||
*
|
||||
* @returns {boolean} `true` if the `comp` value is >= to the `ref` value
|
||||
* within the computed window, otherwise `false`.
|
||||
*/
|
||||
module.exports = function geWindow (ref, comp) {
|
||||
let max = ref + Math.floor(MAX_MSGID / 2)
|
||||
const min = ref
|
||||
if (max >= MAX_MSGID) {
|
||||
// Handle roll-over
|
||||
max = max - MAX_MSGID - 1
|
||||
return ((comp <= max) || (comp >= min))
|
||||
} else {
|
||||
return ((comp <= max) && (comp >= min))
|
||||
}
|
||||
}
|
||||
23
node_modules/ldapjs/lib/client/message-tracker/id-generator.js
generated
vendored
Normal file
23
node_modules/ldapjs/lib/client/message-tracker/id-generator.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const { MAX_MSGID } = require('../constants')
|
||||
|
||||
/**
|
||||
* Returns a function that generates message identifiers. According to RFC 4511
|
||||
* the identifers should be `(0, MAX_MSGID)`. The returned function handles
|
||||
* this and wraps around when the maximum has been reached.
|
||||
*
|
||||
* @param {integer} [start=0] Starting number in the identifier sequence.
|
||||
*
|
||||
* @returns {function} This function accepts no parameters and returns an
|
||||
* increasing sequence identifier each invocation until it reaches the maximum
|
||||
* identifier. At this point the sequence starts over.
|
||||
*/
|
||||
module.exports = function idGeneratorFactory (start = 0) {
|
||||
let currentID = start
|
||||
return function nextID () {
|
||||
const id = currentID + 1
|
||||
currentID = (id >= MAX_MSGID) ? 1 : id
|
||||
return currentID
|
||||
}
|
||||
}
|
||||
161
node_modules/ldapjs/lib/client/message-tracker/index.js
generated
vendored
Normal file
161
node_modules/ldapjs/lib/client/message-tracker/index.js
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
'use strict'
|
||||
|
||||
const idGeneratorFactory = require('./id-generator')
|
||||
const purgeAbandoned = require('./purge-abandoned')
|
||||
|
||||
/**
|
||||
* Returns a message tracker object that keeps track of which message
|
||||
* identifiers correspond to which message handlers. Also handles keeping track
|
||||
* of abandoned messages.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {string} options.id An identifier for the tracker.
|
||||
* @param {object} options.parser An object that will be used to parse messages.
|
||||
*
|
||||
* @returns {MessageTracker}
|
||||
*/
|
||||
module.exports = function messageTrackerFactory (options) {
|
||||
if (Object.prototype.toString.call(options) !== '[object Object]') {
|
||||
throw Error('options object is required')
|
||||
}
|
||||
if (!options.id || typeof options.id !== 'string') {
|
||||
throw Error('options.id string is required')
|
||||
}
|
||||
if (!options.parser || Object.prototype.toString.call(options.parser) !== '[object Object]') {
|
||||
throw Error('options.parser object is required')
|
||||
}
|
||||
|
||||
let currentID = 0
|
||||
const nextID = idGeneratorFactory()
|
||||
const messages = new Map()
|
||||
const abandoned = new Map()
|
||||
|
||||
/**
|
||||
* @typedef {object} MessageTracker
|
||||
* @property {string} id The identifier of the tracker as supplied via the options.
|
||||
* @property {object} parser The parser object given by the the options.
|
||||
*/
|
||||
const tracker = {
|
||||
id: options.id,
|
||||
parser: options.parser
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of messages awaiting response.
|
||||
*
|
||||
* @alias pending
|
||||
* @memberof! MessageTracker#
|
||||
*/
|
||||
Object.defineProperty(tracker, 'pending', {
|
||||
get () {
|
||||
return messages.size
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Move a specific message to the abanded track.
|
||||
*
|
||||
* @param {integer} msgID The identifier for the message to move.
|
||||
*
|
||||
* @memberof MessageTracker
|
||||
* @method abandon
|
||||
*/
|
||||
tracker.abandon = function abandonMessage (msgID) {
|
||||
if (messages.has(msgID) === false) return false
|
||||
const toAbandon = messages.get(msgID)
|
||||
abandoned.set(msgID, {
|
||||
age: currentID,
|
||||
message: toAbandon.message,
|
||||
cb: toAbandon.callback
|
||||
})
|
||||
return messages.delete(msgID)
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} Tracked
|
||||
* @property {object} message The tracked message. Usually the outgoing
|
||||
* request object.
|
||||
* @property {Function} callback The handler to use when receiving a
|
||||
* response to the tracked message.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the message handler for a message. Removes abandoned messages
|
||||
* that have been given time to be resolved.
|
||||
*
|
||||
* @param {integer} msgID The identifier for the message to get the handler for.
|
||||
*
|
||||
* @memberof MessageTracker
|
||||
* @method fetch
|
||||
*/
|
||||
tracker.fetch = function fetchMessage (msgID) {
|
||||
const tracked = messages.get(msgID)
|
||||
if (tracked) {
|
||||
purgeAbandoned(msgID, abandoned)
|
||||
return tracked
|
||||
}
|
||||
|
||||
// We sent an abandon request but the server either wasn't able to process
|
||||
// it or has not received it yet. Therefore, we received a response for the
|
||||
// abandoned message. So we must return the abandoned message's callback
|
||||
// to be processed normally.
|
||||
const abandonedMsg = abandoned.get(msgID)
|
||||
if (abandonedMsg) {
|
||||
return { message: abandonedMsg, callback: abandonedMsg.cb }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all message tracks, cleans up the abandoned track, and invokes
|
||||
* a callback for each message purged.
|
||||
*
|
||||
* @param {function} cb A function with the signature `(msgID, handler)`.
|
||||
*
|
||||
* @memberof MessageTracker
|
||||
* @method purge
|
||||
*/
|
||||
tracker.purge = function purgeMessages (cb) {
|
||||
messages.forEach((val, key) => {
|
||||
purgeAbandoned(key, abandoned)
|
||||
tracker.remove(key)
|
||||
cb(key, val.callback)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a message from all tracking.
|
||||
*
|
||||
* @param {integer} msgID The identifier for the message to remove from tracking.
|
||||
*
|
||||
* @memberof MessageTracker
|
||||
* @method remove
|
||||
*/
|
||||
tracker.remove = function removeMessage (msgID) {
|
||||
if (messages.delete(msgID) === false) {
|
||||
abandoned.delete(msgID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message handler to be tracked.
|
||||
*
|
||||
* @param {object} message The message object to be tracked. This object will
|
||||
* have a new property added to it: `messageId`.
|
||||
* @param {function} callback The handler for the message.
|
||||
*
|
||||
* @memberof MessageTracker
|
||||
* @method track
|
||||
*/
|
||||
tracker.track = function trackMessage (message, callback) {
|
||||
currentID = nextID()
|
||||
// This side effect is not ideal but the client doesn't attach the tracker
|
||||
// to itself until after the `.connect` method has fired. If this can be
|
||||
// refactored later, then we can possibly get rid of this side effect.
|
||||
message.messageId = currentID
|
||||
messages.set(currentID, { callback, message })
|
||||
}
|
||||
|
||||
return tracker
|
||||
}
|
||||
34
node_modules/ldapjs/lib/client/message-tracker/purge-abandoned.js
generated
vendored
Normal file
34
node_modules/ldapjs/lib/client/message-tracker/purge-abandoned.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const { AbandonedError } = require('../../errors')
|
||||
const geWindow = require('./ge-window')
|
||||
|
||||
/**
|
||||
* Given a `msgID` and a set of `abandoned` messages, remove any abandoned
|
||||
* messages that existed _prior_ to the specified `msgID`. For example, let's
|
||||
* assume the server has sent 3 messages:
|
||||
*
|
||||
* 1. A search message.
|
||||
* 2. An abandon message for the search message.
|
||||
* 3. A new search message.
|
||||
*
|
||||
* When the response for message #1 comes in, if it does, it will be processed
|
||||
* normally due to the specification. Message #2 will not receive a response, or
|
||||
* if the server does send one since the spec sort of allows it, we won't do
|
||||
* anything with it because we just discard that listener. Now the response
|
||||
* for message #3 comes in. At this point, we will issue a purge of responses
|
||||
* by passing in `msgID = 3`. This result is that we will remove the tracking
|
||||
* for message #1.
|
||||
*
|
||||
* @param {integer} msgID An upper bound for the messages to be purged.
|
||||
* @param {Map} abandoned A set of abandoned messages. Each message is an object
|
||||
* `{ age: <id>, cb: <func> }` where `age` was the current message id when the
|
||||
* abandon message was sent.
|
||||
*/
|
||||
module.exports = function purgeAbandoned (msgID, abandoned) {
|
||||
abandoned.forEach((val, key) => {
|
||||
if (geWindow(val.age, msgID) === false) return
|
||||
val.cb(new AbandonedError('client request abandoned'))
|
||||
abandoned.delete(key)
|
||||
})
|
||||
}
|
||||
36
node_modules/ldapjs/lib/client/request-queue/enqueue.js
generated
vendored
Normal file
36
node_modules/ldapjs/lib/client/request-queue/enqueue.js
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Adds requests to the queue. If a timeout has been added to the queue then
|
||||
* this will freeze the queue with the newly added item, flush it, and then
|
||||
* unfreeze it when the queue has been cleared.
|
||||
*
|
||||
* @param {object} message An LDAP message object.
|
||||
* @param {object} expect An expectation object.
|
||||
* @param {object} emitter An event emitter or `null`.
|
||||
* @param {function} cb A callback to invoke when the request is finished.
|
||||
*
|
||||
* @returns {boolean} `true` if the requested was queued. `false` if the queue
|
||||
* is not accepting any requests.
|
||||
*/
|
||||
module.exports = function enqueue (message, expect, emitter, cb) {
|
||||
if (this._queue.size >= this.size || this._frozen) {
|
||||
return false
|
||||
}
|
||||
|
||||
this._queue.add({ message, expect, emitter, cb })
|
||||
|
||||
if (this.timeout === 0) return true
|
||||
if (this._timer === null) return true
|
||||
|
||||
// A queue can have a specified time allotted for it to be cleared. If that
|
||||
// time has been reached, reject new entries until the queue has been cleared.
|
||||
this._timer = setTimeout(queueTimeout.bind(this), this.timeout)
|
||||
|
||||
return true
|
||||
|
||||
function queueTimeout () {
|
||||
this.freeze()
|
||||
this.purge()
|
||||
}
|
||||
}
|
||||
24
node_modules/ldapjs/lib/client/request-queue/flush.js
generated
vendored
Normal file
24
node_modules/ldapjs/lib/client/request-queue/flush.js
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Invokes all requests in the queue by passing them to the supplied callback
|
||||
* function and then clears all items from the queue.
|
||||
*
|
||||
* @param {function} cb A function used to handle the requests.
|
||||
*/
|
||||
module.exports = function flush (cb) {
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer)
|
||||
this._timer = null
|
||||
}
|
||||
|
||||
// We must get a local copy of the queue and clear it before iterating it.
|
||||
// The client will invoke this flush function _many_ times. If we try to
|
||||
// iterate it without a local copy and clearing first then we will overflow
|
||||
// the stack.
|
||||
const requests = Array.from(this._queue.values())
|
||||
this._queue.clear()
|
||||
for (const req of requests) {
|
||||
cb(req.message, req.expect, req.emitter, req.cb)
|
||||
}
|
||||
}
|
||||
39
node_modules/ldapjs/lib/client/request-queue/index.js
generated
vendored
Normal file
39
node_modules/ldapjs/lib/client/request-queue/index.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const enqueue = require('./enqueue')
|
||||
const flush = require('./flush')
|
||||
const purge = require('./purge')
|
||||
|
||||
/**
|
||||
* Builds a request queue object and returns it.
|
||||
*
|
||||
* @param {object} [options]
|
||||
* @param {integer} [options.size] Maximum size of the request queue. Must be
|
||||
* a number greater than `0` if supplied. Default: `Infinity`.
|
||||
* @param {integer} [options.timeout] Time in milliseconds a queue has to
|
||||
* complete the requests it contains.
|
||||
*
|
||||
* @returns {object} A queue instance.
|
||||
*/
|
||||
module.exports = function requestQueueFactory (options) {
|
||||
const opts = Object.assign({}, options)
|
||||
const q = {
|
||||
size: (opts.size > 0) ? opts.size : Infinity,
|
||||
timeout: (opts.timeout > 0) ? opts.timeout : 0,
|
||||
_queue: new Set(),
|
||||
_timer: null,
|
||||
_frozen: false
|
||||
}
|
||||
|
||||
q.enqueue = enqueue.bind(q)
|
||||
q.flush = flush.bind(q)
|
||||
q.purge = purge.bind(q)
|
||||
q.freeze = function freeze () {
|
||||
this._frozen = true
|
||||
}
|
||||
q.thaw = function thaw () {
|
||||
this._frozen = false
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
12
node_modules/ldapjs/lib/client/request-queue/purge.js
generated
vendored
Normal file
12
node_modules/ldapjs/lib/client/request-queue/purge.js
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const { TimeoutError } = require('../../errors')
|
||||
|
||||
/**
|
||||
* Flushes the queue by rejecting all pending requests with a timeout error.
|
||||
*/
|
||||
module.exports = function purge () {
|
||||
this.flush(function flushCB (a, b, c, cb) {
|
||||
cb(new TimeoutError('request queue timeout'))
|
||||
})
|
||||
}
|
||||
167
node_modules/ldapjs/lib/client/search_pager.js
generated
vendored
Normal file
167
node_modules/ldapjs/lib/client/search_pager.js
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
'use strict'
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const util = require('util')
|
||||
const assert = require('assert-plus')
|
||||
const { PagedResultsControl } = require('@ldapjs/controls')
|
||||
const CorkedEmitter = require('../corked_emitter.js')
|
||||
|
||||
/// --- API
|
||||
|
||||
/**
|
||||
* Handler object for paged search operations.
|
||||
*
|
||||
* Provided to consumers in place of the normal search EventEmitter it adds the
|
||||
* following new events:
|
||||
* 1. page - Emitted whenever the end of a result page is encountered.
|
||||
* If this is the last page, 'end' will also be emitted.
|
||||
* The event passes two arguments:
|
||||
* 1. The result object (similar to 'end')
|
||||
* 2. A callback function optionally used to continue the search
|
||||
* operation if the pagePause option was specified during
|
||||
* initialization.
|
||||
* 2. pageError - Emitted if the server does not support paged search results
|
||||
* If there are no listeners for this event, the 'error' event
|
||||
* will be emitted (and 'end' will not be). By listening to
|
||||
* 'pageError', a successful search that lacks paging will be
|
||||
* able to emit 'end'.
|
||||
*/
|
||||
function SearchPager (opts) {
|
||||
assert.object(opts)
|
||||
assert.func(opts.callback)
|
||||
assert.number(opts.pageSize)
|
||||
assert.func(opts.sendRequest)
|
||||
|
||||
CorkedEmitter.call(this, {})
|
||||
|
||||
this.callback = opts.callback
|
||||
this.controls = opts.controls
|
||||
this.pageSize = opts.pageSize
|
||||
this.pagePause = opts.pagePause
|
||||
this.sendRequest = opts.sendRequest
|
||||
|
||||
this.controls.forEach(function (control) {
|
||||
if (control.type === PagedResultsControl.OID) {
|
||||
// The point of using SearchPager is not having to do this.
|
||||
// Toss an error if the pagedResultsControl is present
|
||||
throw new Error('redundant pagedResultControl')
|
||||
}
|
||||
})
|
||||
|
||||
this.finished = false
|
||||
this.started = false
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
emitter.on('searchRequest', this.emit.bind(this, 'searchRequest'))
|
||||
emitter.on('searchEntry', this.emit.bind(this, 'searchEntry'))
|
||||
emitter.on('end', this._onEnd.bind(this))
|
||||
emitter.on('error', this._onError.bind(this))
|
||||
this.childEmitter = emitter
|
||||
}
|
||||
util.inherits(SearchPager, CorkedEmitter)
|
||||
module.exports = SearchPager
|
||||
|
||||
/**
|
||||
* Start the paged search.
|
||||
*/
|
||||
SearchPager.prototype.begin = function begin () {
|
||||
// Starting first page
|
||||
this._nextPage(null)
|
||||
}
|
||||
|
||||
SearchPager.prototype._onEnd = function _onEnd (res) {
|
||||
const self = this
|
||||
let cookie = null
|
||||
res.controls.forEach(function (control) {
|
||||
if (control.type === PagedResultsControl.OID) {
|
||||
cookie = control.value.cookie
|
||||
}
|
||||
})
|
||||
// Pass a noop callback by default for page events
|
||||
const nullCb = function () { }
|
||||
|
||||
if (cookie === null) {
|
||||
// paged search not supported
|
||||
this.finished = true
|
||||
this.emit('page', res, nullCb)
|
||||
const err = new Error('missing paged control')
|
||||
err.name = 'PagedError'
|
||||
if (this.listeners('pageError').length > 0) {
|
||||
this.emit('pageError', err)
|
||||
// If the consumer as subscribed to pageError, SearchPager is absolved
|
||||
// from delivering the fault via the 'error' event. Emitting an 'end'
|
||||
// event after 'error' breaks the contract that the standard client
|
||||
// provides, so it's only a possibility if 'pageError' is used instead.
|
||||
this.emit('end', res)
|
||||
} else {
|
||||
this.emit('error', err)
|
||||
// No end event possible per explanation above.
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (cookie.length === 0) {
|
||||
// end of paged results
|
||||
this.finished = true
|
||||
this.emit('page', nullCb)
|
||||
this.emit('end', res)
|
||||
} else {
|
||||
if (this.pagePause) {
|
||||
// Wait to fetch next page until callback is invoked
|
||||
// Halt page fetching if called with error
|
||||
this.emit('page', res, function (err) {
|
||||
if (!err) {
|
||||
self._nextPage(cookie)
|
||||
} else {
|
||||
// the paged search has been canceled so emit an end
|
||||
self.emit('end', res)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.emit('page', res, nullCb)
|
||||
this._nextPage(cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchPager.prototype._onError = function _onError (err) {
|
||||
this.finished = true
|
||||
this.emit('error', err)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a search for the next page using the returned cookie value.
|
||||
*/
|
||||
SearchPager.prototype._nextPage = function _nextPage (cookie) {
|
||||
const controls = this.controls.slice(0)
|
||||
controls.push(new PagedResultsControl({
|
||||
value: {
|
||||
size: this.pageSize,
|
||||
cookie
|
||||
}
|
||||
}))
|
||||
|
||||
this.sendRequest(controls, this.childEmitter, this._sendCallback.bind(this))
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback provided to the client API for successful transmission.
|
||||
*/
|
||||
SearchPager.prototype._sendCallback = function _sendCallback (err) {
|
||||
if (err) {
|
||||
this.finished = true
|
||||
if (!this.started) {
|
||||
// EmitSend error during the first page, bail via callback
|
||||
this.callback(err, null)
|
||||
} else {
|
||||
this.emit('error', err)
|
||||
}
|
||||
} else {
|
||||
// search successfully send
|
||||
if (!this.started) {
|
||||
this.started = true
|
||||
// send self as emitter as the client would
|
||||
this.callback(null, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
4
node_modules/ldapjs/lib/controls/index.js
generated
vendored
Normal file
4
node_modules/ldapjs/lib/controls/index.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const controls = require('@ldapjs/controls')
|
||||
module.exports = controls
|
||||
50
node_modules/ldapjs/lib/corked_emitter.js
generated
vendored
Normal file
50
node_modules/ldapjs/lib/corked_emitter.js
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
|
||||
/**
|
||||
* A CorkedEmitter is a variant of an EventEmitter where events emitted
|
||||
* wait for the appearance of the first listener of any kind. That is,
|
||||
* a CorkedEmitter will store all .emit()s it receives, to be replayed
|
||||
* later when an .on() is applied.
|
||||
* It is meant for situations where the consumers of the emitter are
|
||||
* unable to register listeners right away, and cannot afford to miss
|
||||
* any events emitted from the start.
|
||||
* Note that, whenever the first emitter (for any event) appears,
|
||||
* the emitter becomes uncorked and works as usual for ALL events, and
|
||||
* will not cache anything anymore. This is necessary to avoid
|
||||
* re-ordering emits - either everything is being buffered, or nothing.
|
||||
*/
|
||||
function CorkedEmitter () {
|
||||
const self = this
|
||||
EventEmitter.call(self)
|
||||
/**
|
||||
* An array of arguments objects (array-likes) to emit on open.
|
||||
*/
|
||||
self._outstandingEmits = []
|
||||
/**
|
||||
* Whether the normal flow of emits is restored yet.
|
||||
*/
|
||||
self._opened = false
|
||||
// When the first listener appears, we enqueue an opening.
|
||||
// It is not done immediately, so that other listeners can be
|
||||
// registered in the same critical section.
|
||||
self.once('newListener', function () {
|
||||
setImmediate(function releaseStoredEvents () {
|
||||
self._opened = true
|
||||
self._outstandingEmits.forEach(function (args) {
|
||||
self.emit.apply(self, args)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
CorkedEmitter.prototype = Object.create(EventEmitter.prototype)
|
||||
CorkedEmitter.prototype.emit = function emit (eventName) {
|
||||
if (this._opened || eventName === 'newListener') {
|
||||
EventEmitter.prototype.emit.apply(this, arguments)
|
||||
} else {
|
||||
this._outstandingEmits.push(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CorkedEmitter
|
||||
47
node_modules/ldapjs/lib/errors/codes.js
generated
vendored
Normal file
47
node_modules/ldapjs/lib/errors/codes.js
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
LDAP_SUCCESS: 0,
|
||||
LDAP_OPERATIONS_ERROR: 1,
|
||||
LDAP_PROTOCOL_ERROR: 2,
|
||||
LDAP_TIME_LIMIT_EXCEEDED: 3,
|
||||
LDAP_SIZE_LIMIT_EXCEEDED: 4,
|
||||
LDAP_COMPARE_FALSE: 5,
|
||||
LDAP_COMPARE_TRUE: 6,
|
||||
LDAP_AUTH_METHOD_NOT_SUPPORTED: 7,
|
||||
LDAP_STRONG_AUTH_REQUIRED: 8,
|
||||
LDAP_REFERRAL: 10,
|
||||
LDAP_ADMIN_LIMIT_EXCEEDED: 11,
|
||||
LDAP_UNAVAILABLE_CRITICAL_EXTENSION: 12,
|
||||
LDAP_CONFIDENTIALITY_REQUIRED: 13,
|
||||
LDAP_SASL_BIND_IN_PROGRESS: 14,
|
||||
LDAP_NO_SUCH_ATTRIBUTE: 16,
|
||||
LDAP_UNDEFINED_ATTRIBUTE_TYPE: 17,
|
||||
LDAP_INAPPROPRIATE_MATCHING: 18,
|
||||
LDAP_CONSTRAINT_VIOLATION: 19,
|
||||
LDAP_ATTRIBUTE_OR_VALUE_EXISTS: 20,
|
||||
LDAP_INVALID_ATTRIBUTE_SYNTAX: 21,
|
||||
LDAP_NO_SUCH_OBJECT: 32,
|
||||
LDAP_ALIAS_PROBLEM: 33,
|
||||
LDAP_INVALID_DN_SYNTAX: 34,
|
||||
LDAP_ALIAS_DEREF_PROBLEM: 36,
|
||||
LDAP_INAPPROPRIATE_AUTHENTICATION: 48,
|
||||
LDAP_INVALID_CREDENTIALS: 49,
|
||||
LDAP_INSUFFICIENT_ACCESS_RIGHTS: 50,
|
||||
LDAP_BUSY: 51,
|
||||
LDAP_UNAVAILABLE: 52,
|
||||
LDAP_UNWILLING_TO_PERFORM: 53,
|
||||
LDAP_LOOP_DETECT: 54,
|
||||
LDAP_SORT_CONTROL_MISSING: 60,
|
||||
LDAP_INDEX_RANGE_ERROR: 61,
|
||||
LDAP_NAMING_VIOLATION: 64,
|
||||
LDAP_OBJECTCLASS_VIOLATION: 65,
|
||||
LDAP_NOT_ALLOWED_ON_NON_LEAF: 66,
|
||||
LDAP_NOT_ALLOWED_ON_RDN: 67,
|
||||
LDAP_ENTRY_ALREADY_EXISTS: 68,
|
||||
LDAP_OBJECTCLASS_MODS_PROHIBITED: 69,
|
||||
LDAP_AFFECTS_MULTIPLE_DSAS: 71,
|
||||
LDAP_CONTROL_ERROR: 76,
|
||||
LDAP_OTHER: 80,
|
||||
LDAP_PROXIED_AUTHORIZATION_DENIED: 123
|
||||
}
|
||||
147
node_modules/ldapjs/lib/errors/index.js
generated
vendored
Normal file
147
node_modules/ldapjs/lib/errors/index.js
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
'use strict'
|
||||
|
||||
const util = require('util')
|
||||
const assert = require('assert-plus')
|
||||
|
||||
const LDAPResult = require('../messages').LDAPResult
|
||||
|
||||
/// --- Globals
|
||||
|
||||
const CODES = require('./codes')
|
||||
const ERRORS = []
|
||||
|
||||
/// --- Error Base class
|
||||
|
||||
function LDAPError (message, dn, caller) {
|
||||
if (Error.captureStackTrace) { Error.captureStackTrace(this, caller || LDAPError) }
|
||||
|
||||
this.lde_message = message
|
||||
this.lde_dn = dn
|
||||
}
|
||||
util.inherits(LDAPError, Error)
|
||||
Object.defineProperties(LDAPError.prototype, {
|
||||
name: {
|
||||
get: function getName () { return 'LDAPError' },
|
||||
configurable: false
|
||||
},
|
||||
code: {
|
||||
get: function getCode () { return CODES.LDAP_OTHER },
|
||||
configurable: false
|
||||
},
|
||||
message: {
|
||||
get: function getMessage () {
|
||||
return this.lde_message || this.name
|
||||
},
|
||||
set: function setMessage (message) {
|
||||
this.lde_message = message
|
||||
},
|
||||
configurable: false
|
||||
},
|
||||
dn: {
|
||||
get: function getDN () {
|
||||
return (this.lde_dn ? this.lde_dn.toString() : '')
|
||||
},
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
|
||||
/// --- Exported API
|
||||
|
||||
module.exports = {}
|
||||
module.exports.LDAPError = LDAPError
|
||||
|
||||
// Some whacky games here to make sure all the codes are exported
|
||||
Object.keys(CODES).forEach(function (code) {
|
||||
module.exports[code] = CODES[code]
|
||||
if (code === 'LDAP_SUCCESS') { return }
|
||||
|
||||
let err = ''
|
||||
let msg = ''
|
||||
const pieces = code.split('_').slice(1)
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
const lc = pieces[i].toLowerCase()
|
||||
const key = lc.charAt(0).toUpperCase() + lc.slice(1)
|
||||
err += key
|
||||
msg += key + ((i + 1) < pieces.length ? ' ' : '')
|
||||
}
|
||||
|
||||
if (!/\w+Error$/.test(err)) { err += 'Error' }
|
||||
|
||||
// At this point LDAP_OPERATIONS_ERROR is now OperationsError in $err
|
||||
// and 'Operations Error' in $msg
|
||||
module.exports[err] = function (message, dn, caller) {
|
||||
LDAPError.call(this, message, dn, caller || module.exports[err])
|
||||
}
|
||||
module.exports[err].constructor = module.exports[err]
|
||||
util.inherits(module.exports[err], LDAPError)
|
||||
Object.defineProperties(module.exports[err].prototype, {
|
||||
name: {
|
||||
get: function getName () { return err },
|
||||
configurable: false
|
||||
},
|
||||
code: {
|
||||
get: function getCode () { return CODES[code] },
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
|
||||
ERRORS[CODES[code]] = {
|
||||
err,
|
||||
message: msg
|
||||
}
|
||||
})
|
||||
|
||||
module.exports.getError = function (res) {
|
||||
assert.ok(res instanceof LDAPResult, 'res (LDAPResult) required')
|
||||
|
||||
const errObj = ERRORS[res.status]
|
||||
const E = module.exports[errObj.err]
|
||||
return new E(res.errorMessage || errObj.message,
|
||||
res.matchedDN || null,
|
||||
module.exports.getError)
|
||||
}
|
||||
|
||||
module.exports.getMessage = function (code) {
|
||||
assert.number(code, 'code (number) required')
|
||||
|
||||
const errObj = ERRORS[code]
|
||||
return (errObj && errObj.message ? errObj.message : '')
|
||||
}
|
||||
|
||||
/// --- Custom application errors
|
||||
|
||||
function ConnectionError (message) {
|
||||
LDAPError.call(this, message, null, ConnectionError)
|
||||
}
|
||||
util.inherits(ConnectionError, LDAPError)
|
||||
module.exports.ConnectionError = ConnectionError
|
||||
Object.defineProperties(ConnectionError.prototype, {
|
||||
name: {
|
||||
get: function () { return 'ConnectionError' },
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
|
||||
function AbandonedError (message) {
|
||||
LDAPError.call(this, message, null, AbandonedError)
|
||||
}
|
||||
util.inherits(AbandonedError, LDAPError)
|
||||
module.exports.AbandonedError = AbandonedError
|
||||
Object.defineProperties(AbandonedError.prototype, {
|
||||
name: {
|
||||
get: function () { return 'AbandonedError' },
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
|
||||
function TimeoutError (message) {
|
||||
LDAPError.call(this, message, null, TimeoutError)
|
||||
}
|
||||
util.inherits(TimeoutError, LDAPError)
|
||||
module.exports.TimeoutError = TimeoutError
|
||||
Object.defineProperties(TimeoutError.prototype, {
|
||||
name: {
|
||||
get: function () { return 'TimeoutError' },
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
84
node_modules/ldapjs/lib/index.js
generated
vendored
Normal file
84
node_modules/ldapjs/lib/index.js
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const logger = require('./logger')
|
||||
|
||||
const client = require('./client')
|
||||
const Attribute = require('@ldapjs/attribute')
|
||||
const Change = require('@ldapjs/change')
|
||||
const Protocol = require('@ldapjs/protocol')
|
||||
const Server = require('./server')
|
||||
|
||||
const controls = require('./controls')
|
||||
const persistentSearch = require('./persistent_search')
|
||||
const dn = require('@ldapjs/dn')
|
||||
const errors = require('./errors')
|
||||
const filters = require('@ldapjs/filter')
|
||||
const messages = require('./messages')
|
||||
const url = require('./url')
|
||||
|
||||
const hasOwnProperty = (target, val) => Object.prototype.hasOwnProperty.call(target, val)
|
||||
|
||||
/// --- API
|
||||
|
||||
module.exports = {
|
||||
Client: client.Client,
|
||||
createClient: client.createClient,
|
||||
|
||||
Server,
|
||||
createServer: function (options) {
|
||||
if (options === undefined) { options = {} }
|
||||
|
||||
if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
|
||||
|
||||
if (!options.log) {
|
||||
options.log = logger
|
||||
}
|
||||
|
||||
return new Server(options)
|
||||
},
|
||||
|
||||
Attribute,
|
||||
Change,
|
||||
|
||||
dn,
|
||||
DN: dn.DN,
|
||||
RDN: dn.RDN,
|
||||
parseDN: dn.DN.fromString,
|
||||
|
||||
persistentSearch,
|
||||
PersistentSearchCache: persistentSearch.PersistentSearchCache,
|
||||
|
||||
filters,
|
||||
parseFilter: filters.parseString,
|
||||
|
||||
url,
|
||||
parseURL: url.parse
|
||||
}
|
||||
|
||||
/// --- Export all the childrenz
|
||||
|
||||
let k
|
||||
|
||||
for (k in Protocol) {
|
||||
if (hasOwnProperty(Protocol, k)) { module.exports[k] = Protocol[k] }
|
||||
}
|
||||
|
||||
for (k in messages) {
|
||||
if (hasOwnProperty(messages, k)) { module.exports[k] = messages[k] }
|
||||
}
|
||||
|
||||
for (k in controls) {
|
||||
if (hasOwnProperty(controls, k)) { module.exports[k] = controls[k] }
|
||||
}
|
||||
|
||||
for (k in filters) {
|
||||
if (hasOwnProperty(filters, k)) {
|
||||
if (k !== 'parse' && k !== 'parseString') { module.exports[k] = filters[k] }
|
||||
}
|
||||
}
|
||||
|
||||
for (k in errors) {
|
||||
if (hasOwnProperty(errors, k)) {
|
||||
module.exports[k] = errors[k]
|
||||
}
|
||||
}
|
||||
6
node_modules/ldapjs/lib/logger.js
generated
vendored
Normal file
6
node_modules/ldapjs/lib/logger.js
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const logger = require('abstract-logging')
|
||||
logger.child = function () { return logger }
|
||||
|
||||
module.exports = logger
|
||||
39
node_modules/ldapjs/lib/messages/index.js
generated
vendored
Normal file
39
node_modules/ldapjs/lib/messages/index.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const messages = require('@ldapjs/messages')
|
||||
|
||||
const Parser = require('./parser')
|
||||
|
||||
const SearchResponse = require('./search_response')
|
||||
|
||||
/// --- API
|
||||
|
||||
module.exports = {
|
||||
|
||||
LDAPMessage: messages.LdapMessage,
|
||||
LDAPResult: messages.LdapResult,
|
||||
Parser,
|
||||
|
||||
AbandonRequest: messages.AbandonRequest,
|
||||
AbandonResponse: messages.AbandonResponse,
|
||||
AddRequest: messages.AddRequest,
|
||||
AddResponse: messages.AddResponse,
|
||||
BindRequest: messages.BindRequest,
|
||||
BindResponse: messages.BindResponse,
|
||||
CompareRequest: messages.CompareRequest,
|
||||
CompareResponse: messages.CompareResponse,
|
||||
DeleteRequest: messages.DeleteRequest,
|
||||
DeleteResponse: messages.DeleteResponse,
|
||||
ExtendedRequest: messages.ExtensionRequest,
|
||||
ExtendedResponse: messages.ExtensionResponse,
|
||||
ModifyRequest: messages.ModifyRequest,
|
||||
ModifyResponse: messages.ModifyResponse,
|
||||
ModifyDNRequest: messages.ModifyDnRequest,
|
||||
ModifyDNResponse: messages.ModifyDnResponse,
|
||||
SearchRequest: messages.SearchRequest,
|
||||
SearchEntry: messages.SearchResultEntry,
|
||||
SearchReference: messages.SearchResultReference,
|
||||
SearchResponse,
|
||||
UnbindRequest: messages.UnbindRequest
|
||||
|
||||
}
|
||||
249
node_modules/ldapjs/lib/messages/parser.js
generated
vendored
Normal file
249
node_modules/ldapjs/lib/messages/parser.js
generated
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const util = require('util')
|
||||
|
||||
const assert = require('assert-plus')
|
||||
const asn1 = require('@ldapjs/asn1')
|
||||
const logger = require('../logger')
|
||||
|
||||
const messages = require('@ldapjs/messages')
|
||||
const AbandonRequest = messages.AbandonRequest
|
||||
const AddRequest = messages.AddRequest
|
||||
const AddResponse = messages.AddResponse
|
||||
const BindRequest = messages.BindRequest
|
||||
const BindResponse = messages.BindResponse
|
||||
const CompareRequest = messages.CompareRequest
|
||||
const CompareResponse = messages.CompareResponse
|
||||
const DeleteRequest = messages.DeleteRequest
|
||||
const DeleteResponse = messages.DeleteResponse
|
||||
const ExtendedRequest = messages.ExtensionRequest
|
||||
const ExtendedResponse = messages.ExtensionResponse
|
||||
const ModifyRequest = messages.ModifyRequest
|
||||
const ModifyResponse = messages.ModifyResponse
|
||||
const ModifyDNRequest = messages.ModifyDnRequest
|
||||
const ModifyDNResponse = messages.ModifyDnResponse
|
||||
const SearchRequest = messages.SearchRequest
|
||||
const SearchEntry = messages.SearchResultEntry
|
||||
const SearchReference = messages.SearchResultReference
|
||||
const SearchResponse = require('./search_response')
|
||||
const UnbindRequest = messages.UnbindRequest
|
||||
const LDAPResult = messages.LdapResult
|
||||
|
||||
const Protocol = require('@ldapjs/protocol')
|
||||
|
||||
/// --- Globals
|
||||
|
||||
const BerReader = asn1.BerReader
|
||||
|
||||
/// --- API
|
||||
|
||||
function Parser (options = {}) {
|
||||
assert.object(options)
|
||||
|
||||
EventEmitter.call(this)
|
||||
|
||||
this.buffer = null
|
||||
this.log = options.log || logger
|
||||
}
|
||||
util.inherits(Parser, EventEmitter)
|
||||
|
||||
/**
|
||||
* The LDAP server/client implementations will receive data from a stream and feed
|
||||
* it into this method. This method will collect that data into an internal
|
||||
* growing buffer. As that buffer fills with enough data to constitute a valid
|
||||
* LDAP message, the data will be parsed, emitted as a message object, and
|
||||
* reset the buffer to account for any next message in the stream.
|
||||
*/
|
||||
Parser.prototype.write = function (data) {
|
||||
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data (buffer) required') }
|
||||
|
||||
let nextMessage = null
|
||||
const self = this
|
||||
|
||||
function end () {
|
||||
if (nextMessage) { return self.write(nextMessage) }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
self.buffer = self.buffer ? Buffer.concat([self.buffer, data]) : data
|
||||
|
||||
let ber = new BerReader(self.buffer)
|
||||
|
||||
let foundSeq = false
|
||||
try {
|
||||
foundSeq = ber.readSequence()
|
||||
} catch (e) {
|
||||
this.emit('error', e)
|
||||
}
|
||||
|
||||
if (!foundSeq || ber.remain < ber.length) {
|
||||
// ENOTENOUGH
|
||||
return false
|
||||
} else if (ber.remain > ber.length) {
|
||||
// ETOOMUCH
|
||||
|
||||
// This is an odd branch. Basically, it is setting `nextMessage` to
|
||||
// a buffer that represents data part of a message subsequent to the one
|
||||
// being processed. It then re-creates `ber` as a representation of
|
||||
// the message being processed and advances its offset to the value
|
||||
// position of the TLV.
|
||||
|
||||
// Set `nextMessage` to the bytes subsequent to the current message's
|
||||
// value bytes. That is, slice from the byte immediately following the
|
||||
// current message's value bytes until the end of the buffer.
|
||||
nextMessage = self.buffer.slice(ber.offset + ber.length)
|
||||
|
||||
const currOffset = ber.offset
|
||||
ber = new BerReader(ber.buffer.subarray(0, currOffset + ber.length))
|
||||
ber.readSequence()
|
||||
|
||||
assert.equal(ber.remain, ber.length)
|
||||
}
|
||||
|
||||
// If we're here, ber holds the message, and nextMessage is temporarily
|
||||
// pointing at the next sequence of data (if it exists)
|
||||
self.buffer = null
|
||||
|
||||
let message
|
||||
try {
|
||||
if (Object.prototype.toString.call(ber) === '[object BerReader]') {
|
||||
// Parse the BER into a JavaScript object representation. The message
|
||||
// objects require the full sequence in order to construct the object.
|
||||
// At this point, we have already read the sequence tag and length, so
|
||||
// we need to rewind the buffer a bit. The `.sequenceToReader` method
|
||||
// does this for us.
|
||||
message = messages.LdapMessage.parse(ber.sequenceToReader())
|
||||
} else {
|
||||
// Bail here if peer isn't speaking protocol at all
|
||||
message = this.getMessage(ber)
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return end()
|
||||
}
|
||||
|
||||
// TODO: find a better way to handle logging now that messages and the
|
||||
// server are decoupled. ~ jsumners 2023-02-17
|
||||
message.log = this.log
|
||||
} catch (e) {
|
||||
this.emit('error', e, message)
|
||||
return false
|
||||
}
|
||||
|
||||
this.emit('message', message)
|
||||
return end()
|
||||
}
|
||||
|
||||
Parser.prototype.getMessage = function (ber) {
|
||||
assert.ok(ber)
|
||||
|
||||
const self = this
|
||||
|
||||
const messageId = ber.readInt()
|
||||
const type = ber.readSequence()
|
||||
|
||||
let Message
|
||||
switch (type) {
|
||||
case Protocol.operations.LDAP_REQ_ABANDON:
|
||||
Message = AbandonRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_ADD:
|
||||
Message = AddRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_ADD:
|
||||
Message = AddResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_BIND:
|
||||
Message = BindRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_BIND:
|
||||
Message = BindResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
||||
Message = CompareRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_COMPARE:
|
||||
Message = CompareResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_DELETE:
|
||||
Message = DeleteRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_DELETE:
|
||||
Message = DeleteResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_EXTENSION:
|
||||
Message = ExtendedRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_EXTENSION:
|
||||
Message = ExtendedResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_MODIFY:
|
||||
Message = ModifyRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_MODIFY:
|
||||
Message = ModifyResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_MODRDN:
|
||||
Message = ModifyDNRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_MODRDN:
|
||||
Message = ModifyDNResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_SEARCH:
|
||||
Message = SearchRequest
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_SEARCH_ENTRY:
|
||||
Message = SearchEntry
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_SEARCH_REF:
|
||||
Message = SearchReference
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_RES_SEARCH:
|
||||
Message = SearchResponse
|
||||
break
|
||||
|
||||
case Protocol.operations.LDAP_REQ_UNBIND:
|
||||
Message = UnbindRequest
|
||||
break
|
||||
|
||||
default:
|
||||
this.emit('error',
|
||||
new Error('Op 0x' + (type ? type.toString(16) : '??') +
|
||||
' not supported'),
|
||||
new LDAPResult({
|
||||
messageId,
|
||||
protocolOp: type || Protocol.operations.LDAP_RES_EXTENSION
|
||||
}))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return new Message({
|
||||
messageId,
|
||||
log: self.log
|
||||
})
|
||||
}
|
||||
|
||||
/// --- Exports
|
||||
|
||||
module.exports = Parser
|
||||
122
node_modules/ldapjs/lib/messages/search_response.js
generated
vendored
Normal file
122
node_modules/ldapjs/lib/messages/search_response.js
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const assert = require('assert-plus')
|
||||
|
||||
const Attribute = require('@ldapjs/attribute')
|
||||
const {
|
||||
SearchResultEntry: SearchEntry,
|
||||
SearchResultReference: SearchReference,
|
||||
SearchResultDone
|
||||
} = require('@ldapjs/messages')
|
||||
|
||||
const parseDN = require('@ldapjs/dn').DN.fromString
|
||||
|
||||
/// --- API
|
||||
|
||||
class SearchResponse extends SearchResultDone {
|
||||
attributes
|
||||
notAttributes
|
||||
sentEntries
|
||||
|
||||
constructor (options = {}) {
|
||||
super(options)
|
||||
|
||||
this.attributes = options.attributes ? options.attributes.slice() : []
|
||||
this.notAttributes = []
|
||||
this.sentEntries = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to send a SearchEntry back to the client.
|
||||
*
|
||||
* @param {Object} entry an instance of SearchEntry.
|
||||
* @param {Boolean} nofiltering skip filtering notAttributes and '_' attributes.
|
||||
* Defaults to 'false'.
|
||||
*/
|
||||
SearchResponse.prototype.send = function (entry, nofiltering) {
|
||||
if (!entry || typeof (entry) !== 'object') { throw new TypeError('entry (SearchEntry) required') }
|
||||
if (nofiltering === undefined) { nofiltering = false }
|
||||
if (typeof (nofiltering) !== 'boolean') { throw new TypeError('noFiltering must be a boolean') }
|
||||
|
||||
const self = this
|
||||
|
||||
const savedAttrs = {}
|
||||
let save = null
|
||||
if (entry instanceof SearchEntry || entry instanceof SearchReference) {
|
||||
if (!entry.messageId) { entry.messageId = this.messageId }
|
||||
if (entry.messageId !== this.messageId) {
|
||||
throw new Error('SearchEntry messageId mismatch')
|
||||
}
|
||||
} else {
|
||||
if (!entry.attributes) { throw new Error('entry.attributes required') }
|
||||
|
||||
const all = (self.attributes.indexOf('*') !== -1)
|
||||
// Filter attributes in a plain object according to the magic `_` prefix
|
||||
// and presence in `notAttributes`.
|
||||
Object.keys(entry.attributes).forEach(function (a) {
|
||||
const _a = a.toLowerCase()
|
||||
if (!nofiltering && _a.length && _a[0] === '_') {
|
||||
savedAttrs[a] = entry.attributes[a]
|
||||
delete entry.attributes[a]
|
||||
} else if (!nofiltering && self.notAttributes.indexOf(_a) !== -1) {
|
||||
savedAttrs[a] = entry.attributes[a]
|
||||
delete entry.attributes[a]
|
||||
} else if (all) {
|
||||
// do nothing
|
||||
} else if (self.attributes.length && self.attributes.indexOf(_a) === -1) {
|
||||
savedAttrs[a] = entry.attributes[a]
|
||||
delete entry.attributes[a]
|
||||
}
|
||||
})
|
||||
|
||||
save = entry
|
||||
entry = new SearchEntry({
|
||||
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
|
||||
messageId: self.messageId,
|
||||
attributes: Attribute.fromObject(entry.attributes)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.pojo)
|
||||
|
||||
this.connection.write(entry.toBer().buffer)
|
||||
this.sentEntries++
|
||||
|
||||
// Restore attributes
|
||||
Object.keys(savedAttrs).forEach(function (k) {
|
||||
save.attributes[k] = savedAttrs[k]
|
||||
})
|
||||
} catch (e) {
|
||||
this.log.warn(e, '%s failure to write message %j',
|
||||
this.connection.ldap.id, this.pojo)
|
||||
}
|
||||
}
|
||||
|
||||
SearchResponse.prototype.createSearchEntry = function (object) {
|
||||
assert.object(object)
|
||||
|
||||
const entry = new SearchEntry({
|
||||
messageId: this.messageId,
|
||||
objectName: object.objectName || object.dn,
|
||||
attributes: object.attributes ?? []
|
||||
})
|
||||
return entry
|
||||
}
|
||||
|
||||
SearchResponse.prototype.createSearchReference = function (uris) {
|
||||
if (!uris) { throw new TypeError('uris ([string]) required') }
|
||||
|
||||
if (!Array.isArray(uris)) { uris = [uris] }
|
||||
|
||||
const self = this
|
||||
return new SearchReference({
|
||||
messageId: self.messageId,
|
||||
uri: uris
|
||||
})
|
||||
}
|
||||
|
||||
/// --- Exports
|
||||
|
||||
module.exports = SearchResponse
|
||||
109
node_modules/ldapjs/lib/persistent_search.js
generated
vendored
Normal file
109
node_modules/ldapjs/lib/persistent_search.js
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
/// --- Globals
|
||||
|
||||
// var parseDN = require('./dn').parse
|
||||
|
||||
const EntryChangeNotificationControl =
|
||||
require('./controls').EntryChangeNotificationControl
|
||||
|
||||
/// --- API
|
||||
|
||||
// Cache used to store connected persistent search clients
|
||||
function PersistentSearch () {
|
||||
this.clientList = []
|
||||
}
|
||||
|
||||
PersistentSearch.prototype.addClient = function (req, res, callback) {
|
||||
if (typeof (req) !== 'object') { throw new TypeError('req must be an object') }
|
||||
if (typeof (res) !== 'object') { throw new TypeError('res must be an object') }
|
||||
if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') }
|
||||
|
||||
const log = req.log
|
||||
|
||||
const client = {}
|
||||
client.req = req
|
||||
client.res = res
|
||||
|
||||
log.debug('%s storing client', req.logId)
|
||||
|
||||
this.clientList.push(client)
|
||||
|
||||
log.debug('%s stored client', req.logId)
|
||||
log.debug('%s total number of clients %s',
|
||||
req.logId, this.clientList.length)
|
||||
if (callback) { callback(client) }
|
||||
}
|
||||
|
||||
PersistentSearch.prototype.removeClient = function (req, res, callback) {
|
||||
if (typeof (req) !== 'object') { throw new TypeError('req must be an object') }
|
||||
if (typeof (res) !== 'object') { throw new TypeError('res must be an object') }
|
||||
if (callback && typeof (callback) !== 'function') { throw new TypeError('callback must be a function') }
|
||||
|
||||
const log = req.log
|
||||
log.debug('%s removing client', req.logId)
|
||||
const client = {}
|
||||
client.req = req
|
||||
client.res = res
|
||||
|
||||
// remove the client if it exists
|
||||
this.clientList.forEach(function (element, index, array) {
|
||||
if (element.req === client.req) {
|
||||
log.debug('%s removing client from list', req.logId)
|
||||
array.splice(index, 1)
|
||||
}
|
||||
})
|
||||
|
||||
log.debug('%s number of persistent search clients %s',
|
||||
req.logId, this.clientList.length)
|
||||
if (callback) { callback(client) }
|
||||
}
|
||||
|
||||
function getOperationType (requestType) {
|
||||
switch (requestType) {
|
||||
case 'AddRequest':
|
||||
case 'add':
|
||||
return 1
|
||||
case 'DeleteRequest':
|
||||
case 'delete':
|
||||
return 2
|
||||
case 'ModifyRequest':
|
||||
case 'modify':
|
||||
return 4
|
||||
case 'ModifyDNRequest':
|
||||
case 'modrdn':
|
||||
return 8
|
||||
default:
|
||||
throw new TypeError('requestType %s, is an invalid request type',
|
||||
requestType)
|
||||
}
|
||||
}
|
||||
|
||||
function getEntryChangeNotificationControl (req, obj) {
|
||||
// if we want to return a ECNC
|
||||
if (req.persistentSearch.value.returnECs) {
|
||||
const attrs = obj.attributes
|
||||
const value = {}
|
||||
value.changeType = getOperationType(attrs.changetype)
|
||||
// if it's a modDN request, fill in the previous DN
|
||||
if (value.changeType === 8 && attrs.previousDN) {
|
||||
value.previousDN = attrs.previousDN
|
||||
}
|
||||
|
||||
value.changeNumber = attrs.changenumber
|
||||
return new EntryChangeNotificationControl({ value })
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkChangeType (req, requestType) {
|
||||
return (req.persistentSearch.value.changeTypes &
|
||||
getOperationType(requestType))
|
||||
}
|
||||
|
||||
/// --- Exports
|
||||
|
||||
module.exports = {
|
||||
PersistentSearchCache: PersistentSearch,
|
||||
checkChangeType,
|
||||
getEntryChangeNotificationControl
|
||||
}
|
||||
917
node_modules/ldapjs/lib/server.js
generated
vendored
Normal file
917
node_modules/ldapjs/lib/server.js
generated
vendored
Normal file
@ -0,0 +1,917 @@
|
||||
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
|
||||
|
||||
const assert = require('assert')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const net = require('net')
|
||||
const tls = require('tls')
|
||||
const util = require('util')
|
||||
|
||||
// var asn1 = require('@ldapjs/asn1')
|
||||
const VError = require('verror').VError
|
||||
|
||||
const { DN, RDN } = require('@ldapjs/dn')
|
||||
const errors = require('./errors')
|
||||
const Protocol = require('@ldapjs/protocol')
|
||||
|
||||
const messages = require('@ldapjs/messages')
|
||||
|
||||
const Parser = require('./messages').Parser
|
||||
const LdapResult = messages.LdapResult
|
||||
const AbandonResponse = messages.AbandonResponse
|
||||
const AddResponse = messages.AddResponse
|
||||
const BindResponse = messages.BindResponse
|
||||
const CompareResponse = messages.CompareResponse
|
||||
const DeleteResponse = messages.DeleteResponse
|
||||
const ExtendedResponse = messages.ExtensionResponse
|
||||
const ModifyResponse = messages.ModifyResponse
|
||||
const ModifyDnResponse = messages.ModifyDnResponse
|
||||
const SearchRequest = messages.SearchRequest
|
||||
const SearchResponse = require('./messages/search_response')
|
||||
|
||||
/// --- Globals
|
||||
|
||||
// var Ber = asn1.Ber
|
||||
// var BerReader = asn1.BerReader
|
||||
// const DN = dn.DN
|
||||
|
||||
// var sprintf = util.format
|
||||
|
||||
/// --- Helpers
|
||||
|
||||
function mergeFunctionArgs (argv, start, end) {
|
||||
assert.ok(argv)
|
||||
|
||||
if (!start) { start = 0 }
|
||||
if (!end) { end = argv.length }
|
||||
|
||||
const handlers = []
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
if (Array.isArray(argv[i])) {
|
||||
const arr = argv[i]
|
||||
for (let j = 0; j < arr.length; j++) {
|
||||
if (typeof arr[j] !== 'function') {
|
||||
throw new TypeError('Invalid argument type: ' + typeof (arr[j]))
|
||||
}
|
||||
handlers.push(arr[j])
|
||||
}
|
||||
} else if (typeof argv[i] === 'function') {
|
||||
handlers.push(argv[i])
|
||||
} else {
|
||||
throw new TypeError('Invalid argument type: ' + typeof (argv[i]))
|
||||
}
|
||||
}
|
||||
|
||||
return handlers
|
||||
}
|
||||
|
||||
function getResponse (req) {
|
||||
assert.ok(req)
|
||||
|
||||
let Response
|
||||
|
||||
switch (req.protocolOp) {
|
||||
case Protocol.operations.LDAP_REQ_BIND:
|
||||
Response = BindResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_ABANDON:
|
||||
Response = AbandonResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_ADD:
|
||||
Response = AddResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
||||
Response = CompareResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_DELETE:
|
||||
Response = DeleteResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_EXTENSION:
|
||||
Response = ExtendedResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_MODIFY:
|
||||
Response = ModifyResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_MODRDN:
|
||||
Response = ModifyDnResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_SEARCH:
|
||||
Response = SearchResponse
|
||||
break
|
||||
case Protocol.operations.LDAP_REQ_UNBIND:
|
||||
// TODO: when the server receives an unbind request this made up response object was returned.
|
||||
// Instead, we need to just terminate the connection. ~ jsumners
|
||||
Response = class extends LdapResult {
|
||||
status = 0
|
||||
end () {
|
||||
req.connection.end()
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
return null
|
||||
}
|
||||
assert.ok(Response)
|
||||
|
||||
const res = new Response({
|
||||
messageId: req.messageId,
|
||||
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
|
||||
})
|
||||
res.log = req.log
|
||||
res.connection = req.connection
|
||||
res.logId = req.logId
|
||||
|
||||
if (typeof res.end !== 'function') {
|
||||
// This is a hack to re-add the original tight coupling of the message
|
||||
// objects and the server connection.
|
||||
// TODO: remove this during server refactoring ~ jsumners 2023-02-16
|
||||
switch (res.protocolOp) {
|
||||
case 0: {
|
||||
res.end = abandonResponseEnd
|
||||
break
|
||||
}
|
||||
|
||||
case Protocol.operations.LDAP_RES_COMPARE: {
|
||||
res.end = compareResponseEnd
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
res.end = defaultResponseEnd
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Response connection end handler for most responses.
|
||||
*
|
||||
* @param {number} status
|
||||
*/
|
||||
function defaultResponseEnd (status) {
|
||||
if (typeof status === 'number') { this.status = status }
|
||||
|
||||
const ber = this.toBer()
|
||||
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.pojo)
|
||||
|
||||
try {
|
||||
this.connection.write(ber.buffer)
|
||||
} catch (error) {
|
||||
this.log.warn(
|
||||
error,
|
||||
'%s failure to write message %j',
|
||||
this.connection.ldap.id,
|
||||
this.pojo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response connection end handler for ABANDON responses.
|
||||
*/
|
||||
function abandonResponseEnd () {}
|
||||
|
||||
/**
|
||||
* Response connection end handler for COMPARE responses.
|
||||
*
|
||||
* @param {number | boolean} status
|
||||
*/
|
||||
function compareResponseEnd (status) {
|
||||
let result = 0x06
|
||||
if (typeof status === 'boolean') {
|
||||
if (status === false) {
|
||||
result = 0x05
|
||||
}
|
||||
} else {
|
||||
result = status
|
||||
}
|
||||
return defaultResponseEnd.call(this, result)
|
||||
}
|
||||
|
||||
function defaultHandler (req, res, next) {
|
||||
assert.ok(req)
|
||||
assert.ok(res)
|
||||
assert.ok(next)
|
||||
|
||||
res.matchedDN = req.dn.toString()
|
||||
res.errorMessage = 'Server method not implemented'
|
||||
res.end(errors.LDAP_OTHER)
|
||||
return next()
|
||||
}
|
||||
|
||||
function defaultNoOpHandler (req, res, next) {
|
||||
assert.ok(req)
|
||||
assert.ok(res)
|
||||
assert.ok(next)
|
||||
|
||||
res.end()
|
||||
return next()
|
||||
}
|
||||
|
||||
function noSuffixHandler (req, res, next) {
|
||||
assert.ok(req)
|
||||
assert.ok(res)
|
||||
assert.ok(next)
|
||||
|
||||
res.errorMessage = 'No tree found for: ' + req.dn.toString()
|
||||
res.end(errors.LDAP_NO_SUCH_OBJECT)
|
||||
return next()
|
||||
}
|
||||
|
||||
function noExOpHandler (req, res, next) {
|
||||
assert.ok(req)
|
||||
assert.ok(res)
|
||||
assert.ok(next)
|
||||
|
||||
res.errorMessage = req.requestName + ' not supported'
|
||||
res.end(errors.LDAP_PROTOCOL_ERROR)
|
||||
return next()
|
||||
}
|
||||
|
||||
/// --- API
|
||||
|
||||
/**
|
||||
* Constructs a new server that you can call .listen() on, in the various
|
||||
* forms node supports. You need to first assign some handlers to the various
|
||||
* LDAP operations however.
|
||||
*
|
||||
* The options object currently only takes a certificate/private key, and a
|
||||
* bunyan logger handle.
|
||||
*
|
||||
* This object exposes the following events:
|
||||
* - 'error'
|
||||
* - 'close'
|
||||
*
|
||||
* @param {Object} options (optional) parameterization object.
|
||||
* @throws {TypeError} on bad input.
|
||||
*/
|
||||
function Server (options) {
|
||||
if (options) {
|
||||
if (typeof (options) !== 'object') { throw new TypeError('options (object) required') }
|
||||
if (typeof (options.log) !== 'object') { throw new TypeError('options.log must be an object') }
|
||||
|
||||
if (options.certificate || options.key) {
|
||||
if (!(options.certificate && options.key) ||
|
||||
(typeof (options.certificate) !== 'string' &&
|
||||
!Buffer.isBuffer(options.certificate)) ||
|
||||
(typeof (options.key) !== 'string' &&
|
||||
!Buffer.isBuffer(options.key))) {
|
||||
throw new TypeError('options.certificate and options.key ' +
|
||||
'(string or buffer) are both required for TLS')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options = {}
|
||||
}
|
||||
const self = this
|
||||
|
||||
EventEmitter.call(this, options)
|
||||
|
||||
this._chain = []
|
||||
this.log = options.log
|
||||
const log = this.log
|
||||
|
||||
function setupConnection (c) {
|
||||
assert.ok(c)
|
||||
|
||||
if (c.type === 'unix') {
|
||||
c.remoteAddress = self.server.path
|
||||
c.remotePort = c.fd
|
||||
} else if (c.socket) {
|
||||
// TLS
|
||||
c.remoteAddress = c.socket.remoteAddress
|
||||
c.remotePort = c.socket.remotePort
|
||||
}
|
||||
|
||||
const rdn = new RDN({ cn: 'anonymous' })
|
||||
|
||||
c.ldap = {
|
||||
id: c.remoteAddress + ':' + c.remotePort,
|
||||
config: options,
|
||||
_bindDN: new DN({ rdns: [rdn] })
|
||||
}
|
||||
c.addListener('timeout', function () {
|
||||
log.trace('%s timed out', c.ldap.id)
|
||||
c.destroy()
|
||||
})
|
||||
c.addListener('end', function () {
|
||||
log.trace('%s shutdown', c.ldap.id)
|
||||
})
|
||||
c.addListener('error', function (err) {
|
||||
log.warn('%s unexpected connection error', c.ldap.id, err)
|
||||
self.emit('clientError', err)
|
||||
c.destroy()
|
||||
})
|
||||
c.addListener('close', function (closeError) {
|
||||
log.trace('%s close; had_err=%j', c.ldap.id, closeError)
|
||||
c.end()
|
||||
})
|
||||
|
||||
c.ldap.__defineGetter__('bindDN', function () {
|
||||
return c.ldap._bindDN
|
||||
})
|
||||
c.ldap.__defineSetter__('bindDN', function (val) {
|
||||
if (Object.prototype.toString.call(val) !== '[object LdapDn]') {
|
||||
throw new TypeError('DN required')
|
||||
}
|
||||
|
||||
c.ldap._bindDN = val
|
||||
return val
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
self.newConnection = function (conn) {
|
||||
// TODO: make `newConnection` available on the `Server` prototype
|
||||
// https://github.com/ldapjs/node-ldapjs/pull/727/files#r636572294
|
||||
setupConnection(conn)
|
||||
log.trace('new connection from %s', conn.ldap.id)
|
||||
|
||||
conn.parser = new Parser({
|
||||
log: options.log
|
||||
})
|
||||
conn.parser.on('message', function (req) {
|
||||
// TODO: this is mutating the `@ldapjs/message` objects.
|
||||
// We should avoid doing that. ~ jsumners 2023-02-16
|
||||
req.connection = conn
|
||||
req.logId = conn.ldap.id + '::' + req.messageId
|
||||
req.startTime = new Date().getTime()
|
||||
|
||||
log.debug('%s: message received: req=%j', conn.ldap.id, req.pojo)
|
||||
|
||||
const res = getResponse(req)
|
||||
if (!res) {
|
||||
log.warn('Unimplemented server method: %s', req.type)
|
||||
conn.destroy()
|
||||
return false
|
||||
}
|
||||
|
||||
// parse string DNs for routing/etc
|
||||
try {
|
||||
switch (req.protocolOp) {
|
||||
case Protocol.operations.LDAP_REQ_BIND: {
|
||||
req.name = DN.fromString(req.name)
|
||||
break
|
||||
}
|
||||
|
||||
case Protocol.operations.LDAP_REQ_ADD:
|
||||
case Protocol.operations.LDAP_REQ_COMPARE:
|
||||
case Protocol.operations.LDAP_REQ_DELETE: {
|
||||
if (typeof req.entry === 'string') {
|
||||
req.entry = DN.fromString(req.entry)
|
||||
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
|
||||
throw Error('invalid entry object for operation')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case Protocol.operations.LDAP_REQ_MODIFY: {
|
||||
req.object = DN.fromString(req.object)
|
||||
break
|
||||
}
|
||||
|
||||
case Protocol.operations.LDAP_REQ_MODRDN: {
|
||||
if (typeof req.entry === 'string') {
|
||||
req.entry = DN.fromString(req.entry)
|
||||
} else if (Object.prototype.toString.call(req.entry) !== '[object LdapDn]') {
|
||||
throw Error('invalid entry object for operation')
|
||||
}
|
||||
// TODO: handle newRdn/Superior
|
||||
break
|
||||
}
|
||||
|
||||
case Protocol.operations.LDAP_REQ_SEARCH: {
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return res.end(errors.LDAP_INVALID_DN_SYNTAX)
|
||||
}
|
||||
|
||||
res.connection = conn
|
||||
res.logId = req.logId
|
||||
res.requestDN = req.dn
|
||||
|
||||
const chain = self._getHandlerChain(req, res)
|
||||
|
||||
let i = 0
|
||||
return (function messageIIFE (err) {
|
||||
function sendError (sendErr) {
|
||||
res.status = sendErr.code || errors.LDAP_OPERATIONS_ERROR
|
||||
res.matchedDN = req.suffix ? req.suffix.toString() : ''
|
||||
res.errorMessage = sendErr.message || ''
|
||||
return res.end()
|
||||
}
|
||||
|
||||
function after () {
|
||||
if (!self._postChain || !self._postChain.length) { return }
|
||||
|
||||
function next () {} // stub out next for the post chain
|
||||
|
||||
self._postChain.forEach(function (cb) {
|
||||
cb.call(self, req, res, next)
|
||||
})
|
||||
}
|
||||
|
||||
if (err) {
|
||||
log.trace('%s sending error: %s', req.logId, err.stack || err)
|
||||
self.emit('clientError', err)
|
||||
sendError(err)
|
||||
return after()
|
||||
}
|
||||
|
||||
try {
|
||||
const next = messageIIFE
|
||||
if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) }
|
||||
|
||||
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND && res.status === 0) {
|
||||
// 0 length == anonymous bind
|
||||
if (req.dn.length === 0 && req.credentials === '') {
|
||||
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
|
||||
} else {
|
||||
conn.ldap.bindDN = DN.fromString(req.dn)
|
||||
}
|
||||
}
|
||||
|
||||
// unbind clear bindDN for safety
|
||||
// conn should terminate on unbind (RFC4511 4.3)
|
||||
if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND && res.status === 0) {
|
||||
conn.ldap.bindDN = new DN({ rdns: [new RDN({ cn: 'anonymous' })] })
|
||||
}
|
||||
|
||||
return after()
|
||||
} catch (e) {
|
||||
if (!e.stack) { e.stack = e.toString() }
|
||||
log.error('%s uncaught exception: %s', req.logId, e.stack)
|
||||
return sendError(new errors.OperationsError(e.message))
|
||||
}
|
||||
}())
|
||||
})
|
||||
|
||||
conn.parser.on('error', function (err, message) {
|
||||
self.emit('error', new VError(err, 'Parser error for %s', conn.ldap.id))
|
||||
|
||||
if (!message) { return conn.destroy() }
|
||||
|
||||
const res = getResponse(message)
|
||||
if (!res) { return conn.destroy() }
|
||||
|
||||
res.status = 0x02 // protocol error
|
||||
res.errorMessage = err.toString()
|
||||
return conn.end(res.toBer())
|
||||
})
|
||||
|
||||
conn.on('data', function (data) {
|
||||
log.trace('data on %s: %s', conn.ldap.id, util.inspect(data))
|
||||
|
||||
conn.parser.write(data)
|
||||
})
|
||||
} // end newConnection
|
||||
|
||||
this.routes = {}
|
||||
if ((options.cert || options.certificate) && options.key) {
|
||||
options.cert = options.cert || options.certificate
|
||||
this.server = tls.createServer(options, options.connectionRouter ? options.connectionRouter : self.newConnection)
|
||||
} else {
|
||||
this.server = net.createServer(options.connectionRouter ? options.connectionRouter : self.newConnection)
|
||||
}
|
||||
this.server.log = options.log
|
||||
this.server.ldap = {
|
||||
config: options
|
||||
}
|
||||
this.server.on('close', function () {
|
||||
self.emit('close')
|
||||
})
|
||||
this.server.on('error', function (err) {
|
||||
self.emit('error', err)
|
||||
})
|
||||
}
|
||||
util.inherits(Server, EventEmitter)
|
||||
Object.defineProperties(Server.prototype, {
|
||||
maxConnections: {
|
||||
get: function getMaxConnections () {
|
||||
return this.server.maxConnections
|
||||
},
|
||||
set: function setMaxConnections (val) {
|
||||
this.server.maxConnections = val
|
||||
},
|
||||
configurable: false
|
||||
},
|
||||
connections: {
|
||||
get: function getConnections () {
|
||||
return this.server.connections
|
||||
},
|
||||
configurable: false
|
||||
},
|
||||
name: {
|
||||
get: function getName () {
|
||||
return 'LDAPServer'
|
||||
},
|
||||
configurable: false
|
||||
},
|
||||
url: {
|
||||
get: function getURL () {
|
||||
let str
|
||||
const addr = this.server.address()
|
||||
if (!addr) {
|
||||
return null
|
||||
}
|
||||
if (!addr.family) {
|
||||
str = 'ldapi://'
|
||||
str += this.host.replace(/\//g, '%2f')
|
||||
return str
|
||||
}
|
||||
if (this.server instanceof tls.Server) {
|
||||
str = 'ldaps://'
|
||||
} else {
|
||||
str = 'ldap://'
|
||||
}
|
||||
|
||||
let host = this.host
|
||||
// Node 18 switched family from returning a string to returning a number
|
||||
// https://nodejs.org/api/net.html#serveraddress
|
||||
if (addr.family === 'IPv6' || addr.family === 6) {
|
||||
host = '[' + this.host + ']'
|
||||
}
|
||||
|
||||
str += host + ':' + this.port
|
||||
return str
|
||||
},
|
||||
configurable: false
|
||||
}
|
||||
})
|
||||
module.exports = Server
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP add method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.add = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_ADD, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP bind method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.bind = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_BIND, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP compare method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.compare = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_COMPARE, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP delete method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.del = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_DELETE, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP exop method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name OID to assign this handler chain to.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input.
|
||||
*/
|
||||
Server.prototype.exop = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_EXTENSION, name, args, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP modify method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.modify = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_MODIFY, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP modifyDN method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.modifyDN = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_MODRDN, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP search method.
|
||||
*
|
||||
* Note that this is of the form f(name, [function]) where the second...N
|
||||
* arguments can all either be functions or arrays of functions.
|
||||
*
|
||||
* @param {String} name the DN to mount this handler chain at.
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.search = function (name) {
|
||||
const args = Array.prototype.slice.call(arguments, 1)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_SEARCH, name, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler (chain) for the LDAP unbind method.
|
||||
*
|
||||
* This method is different than the others and takes no mount point, as unbind
|
||||
* is a connection-wide operation, not constrianed to part of the DIT.
|
||||
*
|
||||
* @return {Server} this so you can chain calls.
|
||||
* @throws {TypeError} on bad input
|
||||
*/
|
||||
Server.prototype.unbind = function () {
|
||||
const args = Array.prototype.slice.call(arguments, 0)
|
||||
return this._mount(Protocol.operations.LDAP_REQ_UNBIND, 'unbind', args, true)
|
||||
}
|
||||
|
||||
Server.prototype.use = function use () {
|
||||
const args = Array.prototype.slice.call(arguments)
|
||||
const chain = mergeFunctionArgs(args, 0, args.length)
|
||||
const self = this
|
||||
chain.forEach(function (c) {
|
||||
self._chain.push(c)
|
||||
})
|
||||
}
|
||||
|
||||
Server.prototype.after = function () {
|
||||
if (!this._postChain) { this._postChain = [] }
|
||||
|
||||
const self = this
|
||||
mergeFunctionArgs(arguments).forEach(function (h) {
|
||||
self._postChain.push(h)
|
||||
})
|
||||
}
|
||||
|
||||
// All these just re-expose the requisite net.Server APIs
|
||||
Server.prototype.listen = function (port, host, callback) {
|
||||
if (typeof (port) !== 'number' && typeof (port) !== 'string') { throw new TypeError('port (number or path) required') }
|
||||
|
||||
if (typeof (host) === 'function') {
|
||||
callback = host
|
||||
host = '127.0.0.1'
|
||||
}
|
||||
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
|
||||
// Disambiguate between string ports and file paths
|
||||
port = parseInt(port, 10)
|
||||
}
|
||||
const self = this
|
||||
|
||||
function cbListen () {
|
||||
if (typeof (port) === 'number') {
|
||||
self.host = self.address().address
|
||||
self.port = self.address().port
|
||||
} else {
|
||||
self.host = port
|
||||
self.port = self.server.fd
|
||||
}
|
||||
|
||||
if (typeof (callback) === 'function') { callback() }
|
||||
}
|
||||
|
||||
if (typeof (port) === 'number') {
|
||||
return this.server.listen(port, host, cbListen)
|
||||
} else {
|
||||
return this.server.listen(port, cbListen)
|
||||
}
|
||||
}
|
||||
Server.prototype.listenFD = function (fd) {
|
||||
this.host = 'unix-domain-socket'
|
||||
this.port = fd
|
||||
return this.server.listenFD(fd)
|
||||
}
|
||||
Server.prototype.close = function (callback) {
|
||||
return this.server.close(callback)
|
||||
}
|
||||
Server.prototype.address = function () {
|
||||
return this.server.address()
|
||||
}
|
||||
|
||||
Server.prototype.getConnections = function (callback) {
|
||||
return this.server.getConnections(callback)
|
||||
}
|
||||
|
||||
Server.prototype._getRoute = function (_dn, backend) {
|
||||
if (!backend) { backend = this }
|
||||
|
||||
let name
|
||||
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
|
||||
name = _dn.toString()
|
||||
} else {
|
||||
name = _dn
|
||||
}
|
||||
|
||||
if (!this.routes[name]) {
|
||||
this.routes[name] = {}
|
||||
this.routes[name].backend = backend
|
||||
this.routes[name].dn = _dn
|
||||
// Force regeneration of the route key cache on next request
|
||||
this._routeKeyCache = null
|
||||
}
|
||||
|
||||
return this.routes[name]
|
||||
}
|
||||
|
||||
Server.prototype._sortedRouteKeys = function _sortedRouteKeys () {
|
||||
// The filtered/sorted route keys are cached to prevent needlessly
|
||||
// regenerating the list for every incoming request.
|
||||
if (!this._routeKeyCache) {
|
||||
const self = this
|
||||
const reversedRDNsToKeys = {}
|
||||
// Generate mapping of reversedRDNs(DN) -> routeKey
|
||||
Object.keys(this.routes).forEach(function (key) {
|
||||
const _dn = self.routes[key].dn
|
||||
// Ignore non-DN routes such as exop or unbind
|
||||
if (Object.prototype.toString.call(_dn) === '[object LdapDn]') {
|
||||
const reversed = _dn.clone()
|
||||
reversed.reverse()
|
||||
reversedRDNsToKeys[reversed.toString()] = key
|
||||
}
|
||||
})
|
||||
const output = []
|
||||
// Reverse-sort on reversedRDS(DN) in order to output routeKey list.
|
||||
// This will place more specific DNs in front of their parents:
|
||||
// 1. dc=test, dc=domain, dc=sub
|
||||
// 2. dc=test, dc=domain
|
||||
// 3. dc=other, dc=foobar
|
||||
Object.keys(reversedRDNsToKeys).sort().reverse().forEach(function (_dn) {
|
||||
output.push(reversedRDNsToKeys[_dn])
|
||||
})
|
||||
this._routeKeyCache = output
|
||||
}
|
||||
return this._routeKeyCache
|
||||
}
|
||||
|
||||
Server.prototype._getHandlerChain = function _getHandlerChain (req) {
|
||||
assert.ok(req)
|
||||
|
||||
const self = this
|
||||
const routes = this.routes
|
||||
let route
|
||||
|
||||
// check anonymous bind
|
||||
if (req.protocolOp === Protocol.operations.LDAP_REQ_BIND &&
|
||||
req.dn.toString() === '' &&
|
||||
req.credentials === '') {
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [defaultNoOpHandler]
|
||||
}
|
||||
}
|
||||
|
||||
const op = '0x' + req.protocolOp.toString(16)
|
||||
|
||||
// Special cases are exops, unbinds and abandons. Handle those first.
|
||||
if (req.protocolOp === Protocol.operations.LDAP_REQ_EXTENSION) {
|
||||
route = routes[req.requestName]
|
||||
if (route) {
|
||||
return {
|
||||
backend: route.backend,
|
||||
handlers: (route[op] ? route[op] : [noExOpHandler])
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [noExOpHandler]
|
||||
}
|
||||
}
|
||||
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_UNBIND) {
|
||||
route = routes.unbind
|
||||
if (route) {
|
||||
return {
|
||||
backend: route.backend,
|
||||
handlers: route[op]
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [defaultNoOpHandler]
|
||||
}
|
||||
}
|
||||
} else if (req.protocolOp === Protocol.operations.LDAP_REQ_ABANDON) {
|
||||
return {
|
||||
backend: self,
|
||||
handlers: [defaultNoOpHandler]
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, match via DN rules
|
||||
const keys = this._sortedRouteKeys()
|
||||
let fallbackHandler = [noSuffixHandler]
|
||||
// invalid DNs in non-strict mode are routed to the default handler
|
||||
const testDN = (typeof (req.dn) === 'string') ? DN.fromString(req.dn) : req.dn
|
||||
assert.ok(testDN)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const suffix = keys[i]
|
||||
route = routes[suffix]
|
||||
assert.ok(route.dn)
|
||||
// Match a valid route or the route wildcard ('')
|
||||
if (route.dn.equals(testDN) || route.dn.parentOf(testDN) || suffix === '') {
|
||||
if (route[op]) {
|
||||
// We should be good to go.
|
||||
req.suffix = route.dn
|
||||
return {
|
||||
backend: route.backend,
|
||||
handlers: route[op]
|
||||
}
|
||||
} else {
|
||||
if (suffix === '') {
|
||||
break
|
||||
} else {
|
||||
// We found a valid suffix but not a valid operation.
|
||||
// There might be a more generic suffix with a legitimate operation.
|
||||
fallbackHandler = [defaultHandler]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
backend: self,
|
||||
handlers: fallbackHandler
|
||||
}
|
||||
}
|
||||
|
||||
Server.prototype._mount = function (op, name, argv, notDN) {
|
||||
assert.ok(op)
|
||||
assert.ok(name !== undefined)
|
||||
assert.ok(argv)
|
||||
|
||||
if (typeof (name) !== 'string') { throw new TypeError('name (string) required') }
|
||||
if (!argv.length) { throw new Error('at least one handler required') }
|
||||
|
||||
let backend = this
|
||||
let index = 0
|
||||
|
||||
if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) {
|
||||
backend = argv[0]
|
||||
index = 1
|
||||
}
|
||||
const route = this._getRoute(notDN ? name : DN.fromString(name), backend)
|
||||
|
||||
const chain = this._chain.slice()
|
||||
argv.slice(index).forEach(function (a) {
|
||||
chain.push(a)
|
||||
})
|
||||
route['0x' + op.toString(16)] = mergeFunctionArgs(chain)
|
||||
|
||||
return this
|
||||
}
|
||||
72
node_modules/ldapjs/lib/url.js
generated
vendored
Normal file
72
node_modules/ldapjs/lib/url.js
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const querystring = require('querystring')
|
||||
const url = require('url')
|
||||
const { DN } = require('@ldapjs/dn')
|
||||
const filter = require('@ldapjs/filter')
|
||||
|
||||
module.exports = {
|
||||
|
||||
parse: function (urlStr, parseDN) {
|
||||
let parsedURL
|
||||
try {
|
||||
parsedURL = new url.URL(urlStr)
|
||||
} catch (error) {
|
||||
throw new TypeError(urlStr + ' is an invalid LDAP url (scope)')
|
||||
}
|
||||
|
||||
if (!parsedURL.protocol || !(parsedURL.protocol === 'ldap:' || parsedURL.protocol === 'ldaps:')) { throw new TypeError(urlStr + ' is an invalid LDAP url (protocol)') }
|
||||
|
||||
const u = {
|
||||
protocol: parsedURL.protocol,
|
||||
hostname: parsedURL.hostname,
|
||||
port: parsedURL.port,
|
||||
pathname: parsedURL.pathname,
|
||||
search: parsedURL.search,
|
||||
href: parsedURL.href
|
||||
}
|
||||
|
||||
u.secure = (u.protocol === 'ldaps:')
|
||||
|
||||
if (!u.hostname) { u.hostname = 'localhost' }
|
||||
|
||||
if (!u.port) {
|
||||
u.port = (u.secure ? 636 : 389)
|
||||
} else {
|
||||
u.port = parseInt(u.port, 10)
|
||||
}
|
||||
|
||||
if (u.pathname) {
|
||||
u.pathname = querystring.unescape(u.pathname.substr(1))
|
||||
u.DN = parseDN ? DN.fromString(u.pathname) : u.pathname
|
||||
}
|
||||
|
||||
if (u.search) {
|
||||
u.attributes = []
|
||||
const tmp = u.search.substr(1).split('?')
|
||||
if (tmp && tmp.length) {
|
||||
if (tmp[0]) {
|
||||
tmp[0].split(',').forEach(function (a) {
|
||||
u.attributes.push(querystring.unescape(a.trim()))
|
||||
})
|
||||
}
|
||||
}
|
||||
if (tmp[1]) {
|
||||
if (tmp[1] !== 'base' && tmp[1] !== 'one' && tmp[1] !== 'sub') { throw new TypeError(urlStr + ' is an invalid LDAP url (scope)') }
|
||||
u.scope = tmp[1]
|
||||
}
|
||||
if (tmp[2]) {
|
||||
u.filter = querystring.unescape(tmp[2])
|
||||
}
|
||||
if (tmp[3]) {
|
||||
u.extensions = querystring.unescape(tmp[3])
|
||||
}
|
||||
|
||||
if (!u.scope) { u.scope = 'base' }
|
||||
if (!u.filter) { u.filter = filter.parseString('(objectclass=*)') } else { u.filter = filter.parseString(u.filter) }
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user