ldapts
Version:
LDAP client
1,750 lines (1,674 loc) • 108 kB
JavaScript
import asn1 from 'asn1';
import * as assert from 'node:assert';
import * as net from 'node:net';
import * as tls from 'node:tls';
import debug from 'debug';
import { v4 } from 'uuid';
import { parseURL } from 'whatwg-url';
import { EventEmitter } from 'node:events';
class Control {
type;
critical;
constructor(type, options = {}) {
this.type = type;
this.critical = options.critical === true;
}
write(writer) {
writer.startSequence();
writer.writeString(this.type);
writer.writeBoolean(this.critical);
this.writeControl(writer);
writer.endSequence();
}
parse(reader) {
this.parseControl(reader);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
writeControl(_) {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parseControl(_) {
}
}
const { BerWriter: BerWriter$4 } = asn1;
class EntryChangeNotificationControl extends Control {
static type = "2.16.840.1.113730.3.4.7";
value;
constructor(options = {}) {
super(EntryChangeNotificationControl.type, options);
this.value = options.value;
}
parseControl(reader) {
if (reader.readSequence()) {
const changeType = reader.readInt() ?? 0;
let previousDN;
if (changeType === 8) {
previousDN = reader.readString();
}
const changeNumber = reader.readInt() ?? 0;
this.value = {
changeType,
previousDN,
changeNumber
};
}
}
writeControl(writer) {
if (!this.value) {
return;
}
const controlWriter = new BerWriter$4();
controlWriter.startSequence();
controlWriter.writeInt(this.value.changeType);
if (this.value.previousDN) {
controlWriter.writeString(this.value.previousDN);
}
controlWriter.writeInt(this.value.changeNumber);
controlWriter.endSequence();
writer.writeBuffer(controlWriter.buffer, 4);
}
}
const { Ber: Ber$5, BerWriter: BerWriter$3 } = asn1;
class PagedResultsControl extends Control {
static type = "1.2.840.113556.1.4.319";
value;
constructor(options = {}) {
super(PagedResultsControl.type, options);
this.value = options.value;
}
parseControl(reader) {
if (reader.readSequence()) {
const size = reader.readInt() ?? 0;
const cookie = reader.readString(Ber$5.OctetString, true) ?? Buffer.alloc(0);
this.value = {
size,
cookie
};
}
}
writeControl(writer) {
if (!this.value) {
return;
}
const controlWriter = new BerWriter$3();
controlWriter.startSequence();
controlWriter.writeInt(this.value.size);
if (this.value.cookie?.length) {
controlWriter.writeBuffer(this.value.cookie, Ber$5.OctetString);
} else {
controlWriter.writeString("");
}
controlWriter.endSequence();
writer.writeBuffer(controlWriter.buffer, 4);
}
}
const { BerWriter: BerWriter$2 } = asn1;
class PersistentSearchControl extends Control {
static type = "2.16.840.1.113730.3.4.3";
value;
constructor(options = {}) {
super(PersistentSearchControl.type, options);
this.value = options.value;
}
parseControl(reader) {
if (reader.readSequence()) {
const changeTypes = reader.readInt() ?? 0;
const changesOnly = reader.readBoolean() ?? false;
const returnECs = reader.readBoolean() ?? false;
this.value = {
changeTypes,
changesOnly,
returnECs
};
}
}
writeControl(writer) {
if (!this.value) {
return;
}
const controlWriter = new BerWriter$2();
controlWriter.startSequence();
controlWriter.writeInt(this.value.changeTypes);
controlWriter.writeBoolean(this.value.changesOnly);
controlWriter.writeBoolean(this.value.returnECs);
controlWriter.endSequence();
writer.writeBuffer(controlWriter.buffer, 4);
}
}
const { Ber: Ber$4, BerWriter: BerWriter$1 } = asn1;
class ServerSideSortingRequestControl extends Control {
static type = "1.2.840.113556.1.4.473";
values;
constructor(options = {}) {
super(ServerSideSortingRequestControl.type, options);
if (Array.isArray(options.value)) {
this.values = options.value;
} else if (typeof options.value === "object") {
this.values = [options.value];
} else {
this.values = [];
}
}
parseControl(reader) {
if (reader.readSequence(48)) {
while (reader.readSequence(48)) {
const attributeType = reader.readString() ?? "";
let orderingRule = "";
let reverseOrder = false;
if (reader.peek() === 128) {
orderingRule = reader.readString(128) ?? "";
}
if (reader.peek() === 129) {
reverseOrder = reader._readTag(129) !== 0;
}
this.values.push({
attributeType,
orderingRule,
reverseOrder
});
}
}
}
writeControl(writer) {
if (!this.values.length) {
return;
}
const controlWriter = new BerWriter$1();
controlWriter.startSequence(48);
for (const value of this.values) {
controlWriter.startSequence(48);
controlWriter.writeString(value.attributeType, Ber$4.OctetString);
if (value.orderingRule) {
controlWriter.writeString(value.orderingRule, 128);
}
if (typeof value.reverseOrder !== "undefined") {
controlWriter.writeBoolean(value.reverseOrder, 129);
}
controlWriter.endSequence();
}
controlWriter.endSequence();
writer.writeBuffer(controlWriter.buffer, 4);
}
}
class RDN {
attrs = {};
constructor(attrs) {
if (attrs) {
for (const [key, value] of Object.entries(attrs)) {
this.set(key, value);
}
}
}
/**
* Set an RDN pair.
* @param {string} name
* @param {string} value
* @returns {object} RDN class
*/
set(name, value) {
this.attrs[name] = value;
return this;
}
/**
* Get an RDN value at the specified name.
* @param {string} name
* @returns {string | undefined} value
*/
get(name) {
return this.attrs[name];
}
/**
* Checks, if this instance of RDN is equal to the other RDN.
* @param {object} other
* @returns true if equal; otherwise false
*/
equals(other) {
const ourKeys = Object.keys(this.attrs);
const otherKeys = Object.keys(other.attrs);
if (ourKeys.length !== otherKeys.length) {
return false;
}
ourKeys.sort();
otherKeys.sort();
for (const [i, key] of ourKeys.entries()) {
if (key == null || key !== otherKeys[i]) {
return false;
}
const ourValue = this.attrs[key];
const otherValue = other.attrs[key];
if (ourValue == null && otherValue == null) {
continue;
}
if (ourValue == null || otherValue == null || ourValue !== otherValue) {
return false;
}
}
return true;
}
/**
* Parse the RDN, escape values & return a string representation.
* @returns {string} Escaped string representation of RDN.
*/
toString() {
let str = "";
for (const [key, value] of Object.entries(this.attrs)) {
if (str) {
str += "+";
}
str += `${key}=${this._escape(value)}`;
}
return str;
}
/**
* Escape values & return a string representation.
*
* RFC defines, that these characters should be escaped:
*
* Comma ,
* Backslash character \
* Pound sign (hash sign) #
* Plus sign +
* Less than symbol <
* Greater than symbol >
* Semicolon ;
* Double quote (quotation mark) "
* Equal sign =
* Leading or trailing spaces
* @param {string} value - RDN value to be escaped
* @returns {string} Escaped string representation of RDN
*/
_escape(value = "") {
let str = "";
let current = 0;
let quoted = false;
const len = value.length;
const escaped = /["\\]/;
const special = /[#+,;<=>]/;
if (len > 0) {
quoted = value.startsWith(" ") || value[len - 1] === " ";
}
while (current < len) {
const character = value[current] ?? "";
if (escaped.test(character) || !quoted && special.test(character)) {
str += "\\";
}
if (character) {
str += character;
}
current += 1;
}
if (quoted) {
str = `"${str}"`;
}
return str;
}
}
class DN {
rdns = [];
constructor(rdns) {
if (rdns) {
if (Array.isArray(rdns)) {
this.rdns = rdns;
} else {
this.addRDNs(rdns);
}
}
}
/**
* Add an RDN component to the DN, consisting of key & value pair.
* @param {string} key
* @param {string} value
* @returns {object} DN
*/
addPairRDN(key, value) {
this.rdns.push(new RDN({ [key]: value }));
return this;
}
/**
* Add a single RDN component to the DN.
*
* Note, that this RDN can be compound (single RDN can have multiple key & value pairs).
* @param {object} rdn
* @returns {object} DN
*/
addRDN(rdn) {
if (rdn instanceof RDN) {
this.rdns.push(rdn);
} else {
this.rdns.push(new RDN(rdn));
}
return this;
}
/**
* Add multiple RDN components to the DN.
*
* This method allows different interfaces to add RDNs into the DN.
* It can:
* - join other DN into this DN
* - join list of RDNs or RDNAttributes into this DN
* - create RDNs from object map, where every key & value will create a new RDN
* @param {object|object[]} rdns
* @returns {object} DN
*/
addRDNs(rdns) {
if (rdns instanceof DN) {
this.rdns.push(...rdns.rdns);
} else if (Array.isArray(rdns)) {
for (const rdn of rdns) {
this.addRDN(rdn);
}
} else {
for (const [name, value] of Object.entries(rdns)) {
if (Array.isArray(value)) {
for (const rdnValue of value) {
this.rdns.push(
new RDN({
[name]: rdnValue
})
);
}
} else {
this.rdns.push(
new RDN({
[name]: value
})
);
}
}
}
return this;
}
getRDNs() {
return this.rdns;
}
get(index) {
return this.rdns[index];
}
set(rdn, index) {
if (rdn instanceof RDN) {
this.rdns[index] = rdn;
} else {
this.rdns[index] = new RDN(rdn);
}
return this;
}
isEmpty() {
return !this.rdns.length;
}
/**
* Checks, if this instance of DN is equal to the other DN.
* @param {object} other
* @returns true if equal; otherwise false
*/
equals(other) {
if (this.rdns.length !== other.rdns.length) {
return false;
}
for (let i = 0; i < this.rdns.length; i += 1) {
const rdn = this.rdns[i];
const otherRdn = other.rdns[i];
if (rdn == null && otherRdn == null) {
continue;
}
if (rdn == null || otherRdn == null || !rdn.equals(otherRdn)) {
return false;
}
}
return true;
}
clone() {
return new DN([...this.rdns]);
}
reverse() {
this.rdns.reverse();
return this;
}
pop() {
return this.rdns.pop();
}
shift() {
return this.rdns.shift();
}
/**
* Parse the DN, escape values & return a string representation.
* @returns String representation of DN
*/
toString() {
let str = "";
for (const rdn of this.rdns) {
if (str.length) {
str += ",";
}
str += rdn.toString();
}
return str;
}
}
class MessageParserError extends Error {
messageDetails;
constructor(message) {
super(message);
this.name = "MessageParserError";
Object.setPrototypeOf(this, MessageParserError.prototype);
}
}
class ResultCodeError extends Error {
code;
constructor(code, message) {
super(`${message} Code: 0x${code.toString(16)}`);
this.name = "ResultCodeError";
this.code = code;
Object.setPrototypeOf(this, ResultCodeError.prototype);
}
}
class AdminLimitExceededError extends ResultCodeError {
constructor(message) {
super(11, message ?? "An LDAP server limit set by an administrative authority has been exceeded.");
this.name = "AdminLimitExceededError";
Object.setPrototypeOf(this, AdminLimitExceededError.prototype);
}
}
class AffectsMultipleDSAsError extends ResultCodeError {
constructor(message) {
super(71, message ?? "The modify DN operation moves the entry from one LDAP server to another and thus requires more than one LDAP server.");
this.name = "AffectsMultipleDSAsError";
Object.setPrototypeOf(this, AffectsMultipleDSAsError.prototype);
}
}
class AliasDerefProblemError extends ResultCodeError {
constructor(message) {
super(36, message ?? "Either the client does not have access rights to read the aliased object's name or dereferencing is not allowed.");
this.name = "AliasDerefProblemError";
Object.setPrototypeOf(this, AliasDerefProblemError.prototype);
}
}
class AliasProblemError extends ResultCodeError {
constructor(message) {
super(33, message ?? "An error occurred when an alias was dereferenced.");
this.name = "AliasProblemError";
Object.setPrototypeOf(this, AliasProblemError.prototype);
}
}
class AlreadyExistsError extends ResultCodeError {
constructor(message) {
super(68, message ?? "The add operation attempted to add an entry that already exists, or that the modify operation attempted to rename an entry to the name of an entry that already exists.");
this.name = "AlreadyExistsError";
Object.setPrototypeOf(this, AlreadyExistsError.prototype);
}
}
class AuthMethodNotSupportedError extends ResultCodeError {
constructor(message) {
super(7, message ?? "The Directory Server does not support the requested Authentication Method.");
this.name = "AuthMethodNotSupportedError";
Object.setPrototypeOf(this, AuthMethodNotSupportedError.prototype);
}
}
class BusyError extends ResultCodeError {
constructor(message) {
super(51, message ?? "The LDAP server is too busy to process the client request at this time.");
this.name = "BusyError";
Object.setPrototypeOf(this, BusyError.prototype);
}
}
class ConfidentialityRequiredError extends ResultCodeError {
constructor(message) {
super(
13,
message ?? "The session is not protected by a protocol such as Transport Layer Security (TLS), which provides session confidentiality and the request will not be handled without confidentiality enabled."
);
this.name = "ConfidentialityRequiredError";
Object.setPrototypeOf(this, ConfidentialityRequiredError.prototype);
}
}
class ConstraintViolationError extends ResultCodeError {
constructor(message) {
super(
19,
message ?? "The attribute value specified in a Add Request, Modify Request or ModifyDNRequest operation violates constraints placed on the attribute. The constraint can be one of size or content (string only, no binary)."
);
this.name = "ConstraintViolationError";
Object.setPrototypeOf(this, ConstraintViolationError.prototype);
}
}
class InappropriateAuthError extends ResultCodeError {
constructor(message) {
super(48, message ?? "The client is attempting to use an authentication method incorrectly.");
this.name = "InappropriateAuthError";
Object.setPrototypeOf(this, InappropriateAuthError.prototype);
}
}
class InappropriateMatchingError extends ResultCodeError {
constructor(message) {
super(18, message ?? "The matching rule specified in the search filter does not match a rule defined for the attribute's syntax.");
this.name = "InappropriateMatchingError";
Object.setPrototypeOf(this, InappropriateMatchingError.prototype);
}
}
class InsufficientAccessError extends ResultCodeError {
constructor(message) {
super(50, message ?? "The caller does not have sufficient rights to perform the requested operation.");
this.name = "InsufficientAccessError";
Object.setPrototypeOf(this, InsufficientAccessError.prototype);
}
}
class InvalidCredentialsError extends ResultCodeError {
constructor(message) {
super(49, message ?? "Invalid credentials during a bind operation.");
this.name = "InvalidCredentialsError";
Object.setPrototypeOf(this, InvalidCredentialsError.prototype);
}
}
class InvalidDNSyntaxError extends ResultCodeError {
constructor(message) {
super(34, message ?? "The syntax of the DN is incorrect.");
this.name = "InvalidDNSyntaxError";
Object.setPrototypeOf(this, InvalidDNSyntaxError.prototype);
}
}
class InvalidSyntaxError extends ResultCodeError {
constructor(message) {
super(21, message ?? "The attribute value specified in an Add Request, Compare Request, or Modify Request operation is an unrecognized or invalid syntax for the attribute.");
this.name = "InvalidSyntaxError";
Object.setPrototypeOf(this, InvalidSyntaxError.prototype);
}
}
class IsLeafError extends ResultCodeError {
constructor(message) {
super(35, message ?? "The specified operation cannot be performed on a leaf entry.");
this.name = "IsLeafError";
Object.setPrototypeOf(this, IsLeafError.prototype);
}
}
class LoopDetectError extends ResultCodeError {
constructor(message) {
super(54, message ?? "The client discovered an alias or LDAP Referral loop, and is thus unable to complete this request.");
this.name = "LoopDetectError";
Object.setPrototypeOf(this, LoopDetectError.prototype);
}
}
class MoreResultsToReturnError extends ResultCodeError {
constructor(message) {
super(95, message);
this.name = "MoreResultsToReturnError";
Object.setPrototypeOf(this, MoreResultsToReturnError.prototype);
}
}
class NamingViolationError extends ResultCodeError {
constructor(message) {
super(64, message ?? "The Add Request or Modify DN Request operation violates the schema's structure rules.");
this.name = "NamingViolationError";
Object.setPrototypeOf(this, NamingViolationError.prototype);
}
}
class NoObjectClassModsError extends ResultCodeError {
constructor(message) {
super(69, message ?? "The modify operation attempted to modify the structure rules of an object class.");
this.name = "NoObjectClassModsError";
Object.setPrototypeOf(this, NoObjectClassModsError.prototype);
}
}
class NoSuchAttributeError extends ResultCodeError {
constructor(message) {
super(16, message ?? "The attribute specified in the Modify Request or Compare Request operation does not exist in the entry.");
this.name = "NoSuchAttributeError";
Object.setPrototypeOf(this, NoSuchAttributeError.prototype);
}
}
class NoSuchObjectError extends ResultCodeError {
constructor(message) {
super(32, message ?? "The target object cannot be found.");
this.name = "NoSuchObjectError";
Object.setPrototypeOf(this, NoSuchObjectError.prototype);
}
}
class NotAllowedOnNonLeafError extends ResultCodeError {
constructor(message) {
super(66, message ?? "The requested operation is permitted only on leaf entries.");
this.name = "NotAllowedOnNonLeafError";
Object.setPrototypeOf(this, NotAllowedOnNonLeafError.prototype);
}
}
class NotAllowedOnRDNError extends ResultCodeError {
constructor(message) {
super(67, message ?? "The modify operation attempted to remove an attribute value that forms the entry's relative distinguished name.");
this.name = "NotAllowedOnRDNError";
Object.setPrototypeOf(this, NotAllowedOnRDNError.prototype);
}
}
class NoResultError extends ResultCodeError {
constructor(message) {
super(248, message ?? "No result message for the request.");
this.name = "NoResultError";
Object.setPrototypeOf(this, NoResultError.prototype);
}
}
class ObjectClassViolationError extends ResultCodeError {
constructor(message) {
super(65, message ?? "The Add Request, Modify Request, or modify DN operation violates the object class rules for the entry.");
this.name = "ObjectClassViolationError";
Object.setPrototypeOf(this, ObjectClassViolationError.prototype);
}
}
class OperationsError extends ResultCodeError {
constructor(message) {
super(1, message ?? "Request was out of sequence with another operation in progress.");
this.name = "OperationsError";
Object.setPrototypeOf(this, OperationsError.prototype);
}
}
class ProtocolError extends ResultCodeError {
constructor(message) {
super(2, message ?? "Client sent data to the server that did not comprise a valid LDAP request.");
this.name = "ProtocolError";
Object.setPrototypeOf(this, ProtocolError.prototype);
}
}
class ResultsTooLargeError extends ResultCodeError {
constructor(message) {
super(70, message ?? "Results are too large.");
this.name = "ResultsTooLargeError";
Object.setPrototypeOf(this, ResultsTooLargeError.prototype);
}
}
class SaslBindInProgressError extends ResultCodeError {
response;
constructor(response) {
super(14, response.errorMessage || "The server is ready for the next step in the SASL authentication process. The client must send the server the same SASL Mechanism to continue the process.");
this.response = response;
this.name = "SaslBindInProgressError";
Object.setPrototypeOf(this, SaslBindInProgressError.prototype);
}
}
class SizeLimitExceededError extends ResultCodeError {
constructor(message) {
super(4, message ?? "There were more entries matching the criteria contained in a SearchRequest operation than were allowed to be returned by the size limit configuration.");
this.name = "SizeLimitExceededError";
Object.setPrototypeOf(this, SizeLimitExceededError.prototype);
}
}
class StrongAuthRequiredError extends ResultCodeError {
constructor(message) {
super(8, message ?? "Client requested an operation that requires strong authentication.");
this.name = "StrongAuthRequiredError";
Object.setPrototypeOf(this, StrongAuthRequiredError.prototype);
}
}
class TimeLimitExceededError extends ResultCodeError {
constructor(message) {
super(
3,
message ?? "Processing on the associated request Timeout limit specified by either the client request or the server administration limits has been exceeded and has been terminated because it took too long to complete."
);
this.name = "TimeLimitExceededError";
Object.setPrototypeOf(this, TimeLimitExceededError.prototype);
}
}
class TLSNotSupportedError extends ResultCodeError {
constructor(message) {
super(112, message ?? "TLS is not supported on the server.");
this.name = "TLSNotSupportedError";
Object.setPrototypeOf(this, TLSNotSupportedError.prototype);
}
}
class TypeOrValueExistsError extends ResultCodeError {
constructor(message) {
super(20, message ?? "The attribute value specified in a Add Request or Modify Request operation already exists as a value for that attribute.");
this.name = "TypeOrValueExistsError";
Object.setPrototypeOf(this, TypeOrValueExistsError.prototype);
}
}
class UnavailableCriticalExtensionError extends ResultCodeError {
constructor(message) {
super(
12,
message ?? "One or more critical extensions were not available by the LDAP server. Either the server does not support the control or the control is not appropriate for the operation type."
);
this.name = "UnavailableCriticalExtensionError";
Object.setPrototypeOf(this, UnavailableCriticalExtensionError.prototype);
}
}
class UnavailableError extends ResultCodeError {
constructor(message) {
super(52, message ?? "The LDAP server cannot process the client's bind request.");
this.name = "UnavailableError";
Object.setPrototypeOf(this, UnavailableError.prototype);
}
}
class UndefinedTypeError extends ResultCodeError {
constructor(message) {
super(17, message ?? "The attribute specified in the modify or add operation does not exist in the LDAP server's schema.");
this.name = "UndefinedTypeError";
Object.setPrototypeOf(this, UndefinedTypeError.prototype);
}
}
class UnknownStatusCodeError extends ResultCodeError {
constructor(code, message) {
super(code, message ?? "Unknown error.");
this.name = "UnknownStatusCodeError";
Object.setPrototypeOf(this, UnknownStatusCodeError.prototype);
}
}
class UnwillingToPerformError extends ResultCodeError {
constructor(message) {
super(53, message ?? "The LDAP server cannot process the request because of server-defined restrictions.");
this.name = "UnwillingToPerformError";
Object.setPrototypeOf(this, UnwillingToPerformError.prototype);
}
}
class Filter {
write(writer) {
writer.startSequence(this.type);
this.writeFilter(writer);
writer.endSequence();
}
parse(reader) {
this.parseFilter(reader);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
matches(_ = {}, __) {
return true;
}
/**
* RFC 2254 Escaping of filter strings
* Raw Escaped
* (o=Parens (R Us)) (o=Parens \28R Us\29)
* (cn=star*) (cn=star\2A)
* (filename=C:\MyFile) (filename=C:\5cMyFile)
* @param {string|Buffer} input
* @returns Escaped string
*/
escape(input) {
let escapedResult = "";
if (Buffer.isBuffer(input)) {
for (const inputChar of input) {
if (inputChar < 16) {
escapedResult += `\\0${inputChar.toString(16)}`;
} else {
escapedResult += `\\${inputChar.toString(16)}`;
}
}
} else {
for (const inputChar of input) {
switch (inputChar) {
case "*":
escapedResult += "\\2a";
break;
case "(":
escapedResult += "\\28";
break;
case ")":
escapedResult += "\\29";
break;
case "\\":
escapedResult += "\\5c";
break;
case "\0":
escapedResult += "\\00";
break;
default:
escapedResult += inputChar;
break;
}
}
}
return escapedResult;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parseFilter(_) {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
writeFilter(_) {
}
getObjectValue(objectToCheck, key, strictAttributeCase) {
let objectKey;
if (typeof objectToCheck[key] !== "undefined") {
objectKey = key;
} else if (!strictAttributeCase && key.toLowerCase() === "objectclass") {
for (const objectToCheckKey of Object.keys(objectToCheck)) {
if (objectToCheckKey.toLowerCase() === key.toLowerCase()) {
objectKey = objectToCheckKey;
break;
}
}
}
if (objectKey) {
return objectToCheck[objectKey];
}
return void 0;
}
}
const SearchFilter = {
and: 160,
or: 161,
not: 162,
equalityMatch: 163,
substrings: 164,
greaterOrEqual: 165,
lessOrEqual: 166,
present: 135,
approxMatch: 168,
extensibleMatch: 169
};
class AndFilter extends Filter {
type = SearchFilter.and;
filters;
constructor(options) {
super();
this.filters = options.filters;
}
writeFilter(writer) {
for (const filter of this.filters) {
filter.write(writer);
}
}
matches(objectToCheck = {}, strictAttributeCase) {
if (!this.filters.length) {
return true;
}
for (const filter of this.filters) {
if (!filter.matches(objectToCheck, strictAttributeCase)) {
return false;
}
}
return true;
}
toString() {
let result = "(&";
for (const filter of this.filters) {
result += filter.toString();
}
result += ")";
return result;
}
}
class ApproximateFilter extends Filter {
type = SearchFilter.approxMatch;
attribute;
value;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
this.value = options.value ?? "";
}
parseFilter(reader) {
this.attribute = (reader.readString() ?? "").toLowerCase();
this.value = reader.readString() ?? "";
}
writeFilter(writer) {
writer.writeString(this.attribute);
writer.writeString(this.value);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
matches(_ = {}, __) {
throw new Error("Approximate match implementation unknown");
}
toString() {
return `(${this.escape(this.attribute)}~=${this.escape(this.value)})`;
}
}
const { Ber: Ber$3 } = asn1;
class EqualityFilter extends Filter {
type = SearchFilter.equalityMatch;
attribute;
value;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
this.value = options.value ?? "";
}
parseFilter(reader) {
this.attribute = (reader.readString() ?? "").toLowerCase();
this.value = reader.readString() ?? "";
if (this.attribute === "objectclass") {
this.value = this.value.toLowerCase();
}
}
writeFilter(writer) {
writer.writeString(this.attribute);
if (Buffer.isBuffer(this.value)) {
writer.writeBuffer(this.value, Ber$3.OctetString);
} else {
writer.writeString(this.value);
}
}
matches(objectToCheck = {}, strictAttributeCase) {
const objectToCheckValue = this.getObjectValue(objectToCheck, this.attribute, strictAttributeCase);
if (typeof objectToCheckValue !== "undefined") {
if (Buffer.isBuffer(this.value) && Buffer.isBuffer(objectToCheckValue)) {
return this.value === objectToCheckValue;
}
const stringValue = Buffer.isBuffer(this.value) ? this.value.toString("utf8") : this.value;
if (strictAttributeCase) {
return stringValue === objectToCheckValue;
}
return stringValue.toLowerCase() === objectToCheckValue.toLowerCase();
}
return false;
}
toString() {
return `(${this.escape(this.attribute)}=${this.escape(this.value)})`;
}
}
class ExtensibleFilter extends Filter {
type = SearchFilter.extensibleMatch;
value;
rule;
matchType;
dnAttributes;
constructor(options = {}) {
super();
this.matchType = options.matchType ?? "";
this.rule = options.rule ?? "";
this.dnAttributes = options.dnAttributes === true;
this.value = options.value ?? "";
}
parseFilter(reader) {
const end = reader.offset + reader.length;
while (reader.offset < end) {
const tag = reader.peek();
switch (tag) {
case 129:
this.rule = reader.readString(tag) ?? "";
break;
case 130:
this.matchType = reader.readString(tag) ?? "";
break;
case 131:
this.value = reader.readString(tag) ?? "";
break;
case 132:
this.dnAttributes = reader.readBoolean() ?? false;
break;
default: {
let type = "<null>";
if (tag) {
type = `0x${tag.toString(16)}`;
}
throw new Error(`Invalid ext_match filter type: ${type}`);
}
}
}
}
writeFilter(writer) {
if (this.rule) {
writer.writeString(this.rule, 129);
}
if (this.matchType) {
writer.writeString(this.matchType, 130);
}
writer.writeString(this.value, 131);
if (this.dnAttributes) {
writer.writeBoolean(this.dnAttributes, 132);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
matches(_ = {}, __) {
throw new Error("Approximate match implementation unknown");
}
toString() {
let result = `(${this.escape(this.matchType)}:`;
if (this.dnAttributes) {
result += "dn:";
}
if (this.rule) {
result += `${this.escape(this.rule)}:`;
}
result += `=${this.escape(this.value)})`;
return result;
}
}
class GreaterThanEqualsFilter extends Filter {
type = SearchFilter.greaterOrEqual;
attribute;
value;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
this.value = options.value ?? "";
}
parseFilter(reader) {
this.attribute = reader.readString()?.toLowerCase() ?? "";
this.value = reader.readString() ?? "";
}
writeFilter(writer) {
writer.writeString(this.attribute);
writer.writeString(this.value);
}
matches(objectToCheck = {}, strictAttributeCase) {
const objectToCheckValue = this.getObjectValue(objectToCheck, this.attribute, strictAttributeCase);
if (typeof objectToCheckValue !== "undefined") {
if (strictAttributeCase) {
return objectToCheckValue >= this.value;
}
return objectToCheckValue.toLowerCase() >= this.value.toLowerCase();
}
return false;
}
toString() {
return `(${this.escape(this.attribute)}>=${this.escape(this.value)})`;
}
}
class LessThanEqualsFilter extends Filter {
type = SearchFilter.lessOrEqual;
attribute;
value;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
this.value = options.value ?? "";
}
parseFilter(reader) {
this.attribute = reader.readString()?.toLowerCase() ?? "";
this.value = reader.readString() ?? "";
}
writeFilter(writer) {
writer.writeString(this.attribute);
writer.writeString(this.value);
}
matches(objectToCheck = {}, strictAttributeCase) {
const objectToCheckValue = this.getObjectValue(objectToCheck, this.attribute, strictAttributeCase);
if (typeof objectToCheckValue !== "undefined") {
if (strictAttributeCase) {
return objectToCheckValue <= this.value;
}
return objectToCheckValue.toLowerCase() <= this.value.toLowerCase();
}
return false;
}
toString() {
return `(${this.escape(this.attribute)}<=${this.escape(this.value)})`;
}
}
class NotFilter extends Filter {
type = SearchFilter.not;
filter;
constructor(options) {
super();
this.filter = options.filter;
}
writeFilter(writer) {
this.filter.write(writer);
}
matches(objectToCheck = {}, strictAttributeCase) {
return !this.filter.matches(objectToCheck, strictAttributeCase);
}
toString() {
return `(!${this.filter.toString()})`;
}
}
class OrFilter extends Filter {
type = SearchFilter.or;
filters;
constructor(options) {
super();
this.filters = options.filters;
}
writeFilter(writer) {
for (const filter of this.filters) {
filter.write(writer);
}
}
matches(objectToCheck = {}, strictAttributeCase) {
if (!this.filters.length) {
return true;
}
for (const filter of this.filters) {
if (filter.matches(objectToCheck, strictAttributeCase)) {
return true;
}
}
return false;
}
toString() {
let result = "(|";
for (const filter of this.filters) {
result += filter.toString();
}
result += ")";
return result;
}
}
class PresenceFilter extends Filter {
type = SearchFilter.present;
attribute;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
}
parseFilter(reader) {
this.attribute = reader.buffer.subarray(0, reader.length).toString("utf8").toLowerCase();
reader._offset += reader.length;
}
writeFilter(writer) {
for (let i = 0; i < this.attribute.length; i += 1) {
writer.writeByte(this.attribute.charCodeAt(i));
}
}
matches(objectToCheck = {}, strictAttributeCase) {
const objectToCheckValue = this.getObjectValue(objectToCheck, this.attribute, strictAttributeCase);
return typeof objectToCheckValue !== "undefined";
}
toString() {
return `(${this.escape(this.attribute)}=*)`;
}
}
class SubstringFilter extends Filter {
type = SearchFilter.substrings;
attribute;
initial;
any;
final;
constructor(options = {}) {
super();
this.attribute = options.attribute ?? "";
this.initial = options.initial ?? "";
this.any = options.any ?? [];
this.final = options.final ?? "";
}
parseFilter(reader) {
this.attribute = reader.readString()?.toLowerCase() ?? "";
reader.readSequence();
const end = reader.offset + reader.length;
while (reader.offset < end) {
const tag = reader.peek();
switch (tag) {
case 128:
this.initial = reader.readString(tag) ?? "";
if (this.attribute === "objectclass") {
this.initial = this.initial.toLowerCase();
}
break;
case 129: {
let anyValue = reader.readString(tag) ?? "";
if (this.attribute === "objectclass") {
anyValue = anyValue.toLowerCase();
}
this.any.push(anyValue);
break;
}
case 130:
this.final = reader.readString(tag) ?? "";
if (this.attribute === "objectclass") {
this.final = this.final.toLowerCase();
}
break;
default: {
let type = "<null>";
if (tag) {
type = `0x${tag.toString(16)}`;
}
throw new Error(`Invalid substring filter type: ${type}`);
}
}
}
}
writeFilter(writer) {
writer.writeString(this.attribute);
writer.startSequence();
if (this.initial) {
writer.writeString(this.initial, 128);
}
for (const anyItem of this.any) {
writer.writeString(anyItem, 129);
}
if (this.final) {
writer.writeString(this.final, 130);
}
writer.endSequence();
}
matches(objectToCheck = {}, strictAttributeCase) {
const objectToCheckValue = this.getObjectValue(objectToCheck, this.attribute, strictAttributeCase);
if (typeof objectToCheckValue !== "undefined") {
let regexp = "";
if (this.initial) {
regexp += `^${SubstringFilter._escapeRegExp(this.initial)}.*`;
}
for (const anyItem of this.any) {
regexp += `${SubstringFilter._escapeRegExp(anyItem)}.*`;
}
if (this.final) {
regexp += `${SubstringFilter._escapeRegExp(this.final)}$`;
}
const matcher = new RegExp(regexp, strictAttributeCase ? "gmu" : "igmu");
return matcher.test(objectToCheckValue);
}
return false;
}
toString() {
let result = `(${this.escape(this.attribute)}=${this.escape(this.initial)}*`;
for (const anyItem of this.any) {
result += `${this.escape(anyItem)}*`;
}
result += `${this.escape(this.final)})`;
return result;
}
static _escapeRegExp(str) {
return str.replace(/[$()*+./?[\\\]^{|}-]/g, "\\$&");
}
}
const ProtocolOperation = {
// Misc
LDAP_VERSION_3: 3,
LBER_SET: 49,
LDAP_CONTROLS: 160,
// Requests
LDAP_REQ_BIND: 96,
LDAP_REQ_BIND_SASL: 163,
LDAP_REQ_UNBIND: 66,
LDAP_REQ_SEARCH: 99,
LDAP_REQ_MODIFY: 102,
LDAP_REQ_ADD: 104,
LDAP_REQ_DELETE: 74,
LDAP_REQ_MODRDN: 108,
LDAP_REQ_COMPARE: 110,
LDAP_REQ_ABANDON: 80,
LDAP_REQ_EXTENSION: 119,
// Responses
LDAP_RES_BIND: 97,
LDAP_RES_SEARCH_ENTRY: 100,
LDAP_RES_SEARCH_REF: 115,
LDAP_RES_SEARCH: 101,
LDAP_RES_MODIFY: 103,
LDAP_RES_ADD: 105,
LDAP_RES_DELETE: 107,
LDAP_RES_MODRDN: 109,
LDAP_RES_COMPARE: 111,
LDAP_RES_EXTENSION: 120
};
const { Ber: Ber$2, BerReader: BerReader$1 } = asn1;
class ControlParser {
static parse(reader, requestControls) {
if (reader.readSequence() === null) {
return null;
}
let type = "";
let critical = false;
let value = Buffer.alloc(0);
if (reader.length) {
const end = reader.offset + reader.length;
type = reader.readString() ?? "";
if (reader.offset < end) {
if (reader.peek() === Ber$2.Boolean) {
critical = reader.readBoolean() ?? false;
}
}
if (reader.offset < end) {
value = reader.readString(Ber$2.OctetString, true) ?? Buffer.alloc(0);
}
}
let control;
switch (type) {
case EntryChangeNotificationControl.type:
control = new EntryChangeNotificationControl({
critical
});
break;
case PagedResultsControl.type:
control = new PagedResultsControl({
critical
});
break;
case PersistentSearchControl.type:
control = new PersistentSearchControl({
critical
});
break;
case ServerSideSortingRequestControl.type:
control = new ServerSideSortingRequestControl({
critical
});
break;
default:
control = requestControls.find((requestControl) => requestControl.type === type);
break;
}
if (!control) {
return null;
}
const controlReader = new BerReader$1(value);
control.parse(controlReader);
return control;
}
}
const { BerWriter } = asn1;
class Message {
version = ProtocolOperation.LDAP_VERSION_3;
messageId = 0;
controls;
constructor(options) {
this.messageId = options.messageId;
this.controls = options.controls;
}
write() {
const writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.messageId);
writer.startSequence(this.protocolOperation);
this.writeMessage(writer);
writer.endSequence();
if (this.controls?.length) {
writer.startSequence(ProtocolOperation.LDAP_CONTROLS);
for (const control of this.controls) {
control.write(writer);
}
writer.endSequence();
}
writer.endSequence();
return writer.buffer;
}
parse(reader, requestControls) {
this.controls = [];
this.parseMessage(reader);
if (reader.peek() === ProtocolOperation.LDAP_CONTROLS) {
reader.readSequence();
const end = reader.offset + reader.length;
while (reader.offset < end) {
const control = ControlParser.parse(reader, requestControls);
if (control) {
this.controls.push(control);
}
}
}
}
toString() {
return JSON.stringify(
{
messageId: this.messageId,
messageType: this.constructor.name
},
null,
2
);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parseMessage(_) {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
writeMessage(_) {
}
}
class AbandonRequest extends Message {
protocolOperation;
abandonId;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_REQ_ABANDON;
this.abandonId = options.abandonId ?? 0;
}
/* eslint-disable no-bitwise */
writeMessage(writer) {
let i = this.abandonId;
let intSize = 4;
const mask = 4286578688;
while (((i & mask) === 0 || (i & mask) === mask) && intSize > 1) {
intSize -= 1;
i <<= 8;
}
assert.ok(intSize <= 4);
while (intSize-- > 0) {
writer.writeByte((i & 4278190080) >> 24);
i <<= 8;
}
}
parseMessage(reader) {
const { length } = reader;
if (length) {
let offset = 1;
let value;
const fb = reader.buffer[offset] ?? 0;
value = fb & 127;
for (let i = 1; i < length; i += 1) {
value <<= 8;
offset += 1;
const bufferValue = reader.buffer[offset] ?? 0;
value |= bufferValue & 255;
}
if ((fb & 128) === 128) {
value = -value;
}
reader._offset += length;
this.abandonId = value;
} else {
this.abandonId = 0;
}
}
/* eslint-enable no-bitwise */
}
const { Ber: Ber$1 } = asn1;
class Attribute {
buffers = [];
type;
values;
constructor(options = {}) {
this.type = options.type ?? "";
this.values = options.values ?? [];
}
get parsedBuffers() {
return this.buffers;
}
write(writer) {
writer.startSequence();
const { type } = this;
writer.writeString(type);
writer.startSequence(ProtocolOperation.LBER_SET);
if (this.values.length) {
for (const value of this.values) {
if (Buffer.isBuffer(value)) {
writer.writeBuffer(value, Ber$1.OctetString);
} else {
writer.writeString(value);
}
}
} else {
writer.writeStringArray([]);
}
writer.endSequence();
writer.endSequence();
}
parse(reader) {
reader.readSequence();
this.type = reader.readString() ?? "";
const isBinaryType = this._isBinaryType();
if (reader.peek() === ProtocolOperation.LBER_SET) {
if (reader.readSequence(ProtocolOperation.LBER_SET)) {
const end = reader.offset + reader.length;
while (reader.offset < end) {
const buffer = reader.readString(Ber$1.OctetString, true) ?? Buffer.alloc(0);
this.buffers.push(buffer);
if (isBinaryType) {
this.values.push(buffer);
} else {
this.values.push(buffer.toString("utf8"));
}
}
}
}
}
_isBinaryType() {
return /;binary$/i.test(this.type || "");
}
}
class AddRequest extends Message {
protocolOperation;
dn;
attributes;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_REQ_ADD;
this.dn = options.dn;
this.attributes = options.attributes ?? [];
}
writeMessage(writer) {
writer.writeString(this.dn);
writer.startSequence();
for (const attribute of this.attributes) {
attribute.write(writer);
}
writer.endSequence();
}
parseMessage(reader) {
this.dn = reader.readString() ?? "";
reader.readSequence();
const end = reader.offset + reader.length;
while (reader.offset < end) {
const attribute = new Attribute();
attribute.parse(reader);
this.attributes.push(attribute);
}
}
}
class MessageResponse extends Message {
status;
matchedDN;
errorMessage;
constructor(options) {
super(options);
this.status = options.status ?? 0;
this.matchedDN = options.matchedDN ?? "";
this.errorMessage = options.errorMessage ?? "";
}
parseMessage(reader) {
this.status = reader.readEnumeration() ?? 0;
this.matchedDN = reader.readString() ?? "";
this.errorMessage = reader.readString() ?? "";
}
}
class AddResponse extends MessageResponse {
protocolOperation;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_RES_ADD;
}
}
const { Ber } = asn1;
const SASL_MECHANISMS = ["EXTERNAL", "PLAIN", "DIGEST-MD5", "SCRAM-SHA-1"];
class BindRequest extends Message {
protocolOperation;
dn;
password;
mechanism;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_REQ_BIND;
this.dn = options.dn ?? "";
this.password = options.password ?? "";
this.mechanism = options.mechanism;
}
writeMessage(writer) {
writer.writeInt(this.version);
writer.writeString(this.dn);
if (this.mechanism) {
writer.startSequence(ProtocolOperation.LDAP_REQ_BIND_SASL);
writer.writeString(this.mechanism);
writer.writeString(this.password);
writer.endSequence();
} else {
writer.writeString(this.password, Ber.Context);
}
}
parseMessage(reader) {
this.version = reader.readInt() ?? ProtocolOperation.LDAP_VERSION_3;
this.dn = reader.readString() ?? "";
const contextCheck = reader.peek();
if (contextCheck !== Ber.Context) {
let type = "<null>";
if (contextCheck) {
type = `0x${contextCheck.toString(16)}`;
}
throw new Error(`Authentication type not supported: ${type}`);
}
this.password = reader.readString(Ber.Context) ?? "";
}
}
class BindResponse extends MessageResponse {
protocolOperation;
data = [];
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_RES_BIND;
}
parseMessage(reader) {
super.parseMessage(reader);
while (reader.remain > 0) {
const type = reader.peek();
if (type === ProtocolOperation.LDAP_CONTROLS) {
break;
}
this.data.push(reader.readString(typeof type === "number" ? type : void 0) ?? "");
}
}
}
class CompareRequest extends Message {
protocolOperation;
dn;
attribute;
value;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_REQ_COMPARE;
this.attribute = options.attribute ?? "";
this.value = options.value ?? "";
this.dn = options.dn ?? "";
}
writeMessage(writer) {
writer.writeString(this.dn);
writer.startSequence();
writer.writeString(this.attribute);
writer.writeString(this.value);
writer.endSequence();
}
parseMessage(reader) {
this.dn = reader.readString() ?? "";
reader.readSequence();
this.attribute = (reader.readString() ?? "").toLowerCase();
this.value = reader.readString() ?? "";
}
}
var CompareResult = /* @__PURE__ */ ((CompareResult2) => {
CompareResult2[CompareResult2["compareTrue"] = 6] = "compareTrue";
CompareResult2[CompareResult2["compareFalse"] = 5] = "compareFalse";
CompareResult2[CompareResult2["noSuchAttribute"] = 22] = "noSuchAttribute";
CompareResult2[CompareResult2["noSuchObject"] = 50] = "noSuchObject";
return CompareResult2;
})(CompareResult || {});
class CompareResponse extends MessageResponse {
protocolOperation;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_RES_COMPARE;
}
}
class DeleteRequest extends Message {
protocolOperation;
dn;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_REQ_DELETE;
this.dn = options.dn ?? "";
}
writeMessage(writer) {
const buffer = Buffer.from(this.dn);
for (const byte of buffer) {
writer.writeByte(byte);
}
}
parseMessage(reader) {
const { length } = reader;
this.dn = reader.buffer.subarray(0, length).toString("utf8");
reader._offset += reader.length;
}
}
class DeleteResponse extends MessageResponse {
protocolOperation;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOperation.LDAP_RES_DELETE;
}
}
class ExtendedRequest extends Message {
protocolOperation;
oid;
value;
constructor(options) {
super(options);
this.protocolOperation = ProtocolOper