First commit

This commit is contained in:
2025-10-08 11:12:59 -04:00
commit b0605a28a9
820 changed files with 100317 additions and 0 deletions

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
View 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
View 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]'
}

View 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))
}
}

View 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
View 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
}

View 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)
})
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}
}