// Single-require chokepoint + hardening WRAPPER for the PINNED ldapjs dependency // (an npm package, NOT a vendored source copy/fork -- see VENDORING.md). This is // the ONLY file that require()s ldapjs, so it is the one upgrade/audit point. // Our hardening lives in OUR code at a layer we control: a // per-connection inbound byte cap here (the BER parser has no size limit, so a // declared multi-GB message would buffer unbounded), the per-IP bind lockout in // harden.js, and the filter-nesting-depth guard in search.js. Re-exporting the // error classes keeps the handlers off a direct ldapjs import. const ldap = require("ldapjs"); const constants = require("../constants"); // createServer with a connectionRouter that caps inbound bytes PER LDAP MESSAGE // and then hands off to ldapjs's own connection setup. ldapjs uses // options.connectionRouter in place of its internal newConnection, so the router // MUST call server.newConnection(conn) or no parser is wired up. The cap is reset // on each parsed-message boundary (conn.parser emits 'message'): the goal is to // bound a single unbounded BER message (the parser has no size limit), NOT to // impose a connection-lifetime quota -- a per-connection counter would let an // attacker kill a legitimate long-lived connection after a few normal operations // (sequential binds/searches that each fit the cap but cumulatively exceed it). const createHardenedServer = (opts) => { let server; const options = Object.assign({}, opts, { connectionRouter: (conn) => { server.newConnection(conn); // Idle timeout: drop a connection that goes silent so slow-loris / // idle sockets cannot hoard the (capped) connection pool. ldapjs never // calls setTimeout itself, so without this connections live forever. if (typeof conn.setTimeout === "function") { conn.setTimeout(constants.LDAP_IDLE_TIMEOUT_MS); conn.on("timeout", () => conn.destroy()); } let total = 0; if (conn.parser && typeof conn.parser.on === "function") { conn.parser.on("message", () => { total = 0; }); } conn.on("data", (chunk) => { total += chunk.length; if (total > constants.LDAP_MAX_MSG_BYTES) { conn.destroy(); } }); } }); server = ldap.createServer(options); return server; }; module.exports = { createHardenedServer, InvalidCredentialsError: ldap.InvalidCredentialsError, InsufficientAccessRightsError: ldap.InsufficientAccessRightsError, OperationsError: ldap.OperationsError, ProtocolError: ldap.ProtocolError, SizeLimitExceededError: ldap.SizeLimitExceededError };