node_modules updated, buefy add to lib folder

This commit is contained in:
2019-06-04 14:29:48 +02:00
parent 0a3f6ff240
commit 5d28bc36e1
3957 changed files with 306575 additions and 21738 deletions
+6
View File
@@ -0,0 +1,6 @@
((nil . ((indent-tabs-mode . nil)
(tab-width . 8)
(fill-column . 80)))
(js-mode . ((js-indent-level . 2)
(indent-tabs-mode . nil)
)))
+7
View File
@@ -0,0 +1,7 @@
.gitmodules
Makefile
deps
docs
test
tools
coverage
+6
View File
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.10"
- "0.12"
- "4"
- "stable"
+100
View File
@@ -0,0 +1,100 @@
# ldapjs Changelog
## 1.0.2
- Update dtrace-provider dependency
## 1.0.1
- Update dependencies
* assert-plus to 1.0.0
* bunyan to 1.8.3
* dashdash to 1.14.0
* backoff to 2.5.0
* once to 1.4.0
* vasync to 1.6.4
* verror to 1.8.1
* dtrace-provider to 0.7.0
- Drop any semblence of support for node 0.8.x
## 1.0.0
- Update dependencies
* asn1 to 0.2.3
* bunyan to 1.5.1
* dtrace-provider to 0.6.0
- Removed pooled client
- Removed custom formatting for GUIDs
- Completely overhaul DN parsing/formatting
- Add options for format preservation
- Removed `spaced()` and `rndSpaced` from DN API
- Fix parent/child rules regarding empty DNs
- Request routing overhaul
* #154 Route lookups do not depend on object property order
* #111 Null ('') DN will act as catch-all
- Add StartTLS support to client (Sponsored by: DoubleCheck Email Manager)
- Improve robustness of client reconnect logic
- Add 'resultError' event to client
- Update paged search automation in client
- Add Change.apply method for modifying objects
- #143 Preserve raw Buffer value in Control objects
- Test code coverage with node-istanbul
- Convert tests to node-tape
- Add controls for server-side sorting
- #201 Replace nopt with dashdash
- #134 Allow configuration of derefAliases client option
- #197 Properly dispatch unbind requests
- #196 Handle string ports properly in server.listen
- Add basic server API tests
- Store EqualityFilter value as Buffer
- Run full test suite during 'make test'
- #190 Add error code 123 from RFC4370
- #178 Perform strict presence testing on attribute vals
- #183 Accept buffers or strings for cert/key in createServer
- #180 Add '-i, --insecure' option and to all ldapjs-\* CLIs
- #254 Allow simple client bind with empty credentials
## 0.7.1
- #169 Update dependencies
* asn1 to 0.2.1
* pooling to 0.4.6
* assert-plus to 0.1.5
* bunyan to 0.22.1
- #173 Make dtrace-provider an optional dependency
- #142 Improve parser error handling
- #161 Properly handle close events on tls sockets
- #163 Remove buffertools dependency
- #162 Fix error event handling for pooled clients
- #159 Allow ext request message to have a buffer value
- #155 Make \*Filter.matches case insensitive for attrs
## 0.7.0
- #87 Minor update to ClientPool event pass-through
- #145 Update pooling to 0.4.5
- #144 Fix unhandled error during client connection
- Output ldapi:// URLs for UNIX domain sockets
- Support extensible matching of caseIgnore and caseIgnoreSubstrings
- Fix some ClientPool event handling
- Improve DN formatting flexibility
* Add 'spaced' function to DN objects allowing toggle of inter-RDN when
rendering to a string. ('dc=test,dc=tld' vs 'dc=test, dc=tld')
* Detect RDN spacing when parsing DN.
- #128 Fix user can't bind with inmemory example
- #139 Bump required tap version to 0.4.1
- Allow binding ldap server on an ephemeral port
## 0.6.3
- Update bunyan to 0.21.1
- Remove listeners on the right object (s/client/res/)
- Replace log4js with bunyan for binaries
- #127 socket is closed issue with pools
- #122 Allow changing TLS connection options in client
- #120 Fix a bug with formatting digits less than 16.
- #118 Fix "failed to instantiate provider" warnings in console on SmartOS
## 0.6.2 - 0.1.0
**See git history**
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2011 Mark Cavage, All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
+54
View File
@@ -0,0 +1,54 @@
# LDAPjs
[!['Build status'][travis_image_url]][travis_page_url]
[travis_image_url]: https://api.travis-ci.org/mcavage/node-ldapjs.svg
[travis_page_url]: https://travis-ci.org/mcavage/node-ldapjs
LDAPjs makes the LDAP protocol a first class citizen in Node.js.
## Usage
For full docs, head on over to <http://ldapjs.org>.
```javascript
var ldap = require('ldapjs');
var server = ldap.createServer();
server.search('dc=example', function(req, res, next) {
var obj = {
dn: req.dn.toString(),
attributes: {
objectclass: ['organization', 'top'],
o: 'example'
}
};
if (req.filter.matches(obj.attributes))
res.send(obj);
res.end();
});
server.listen(1389, function() {
console.log('ldapjs listening at ' + server.url);
});
```
To run that, assuming you've got the [OpenLDAP](http://www.openldap.org/)
client on your system:
ldapsearch -H ldap://localhost:1389 -x -b dc=example objectclass=*
## Installation
npm install ldapjs
## License
MIT.
## Bugs
See <https://github.com/mcavage/node-ldapjs/issues>.
Generated Vendored Executable
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env node
// -*- mode: js -*-
// Copyright 2011 Mark Cavage. All rights reserved.
var fs = require('fs');
var path = require('path');
var dashdash = require('dashdash');
var vasync = require('vasync');
var ldap = require('../lib/index');
var Logger = require('bunyan');
///--- Globals
var opts = [
{
names: ['file', 'f'],
type: 'string',
help: 'Input file',
helpArg: 'FILE'
},
{ group: 'General Options' },
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
names: ['debug', 'd'],
type: 'integer',
help: 'Set debug level <0-2>',
helpArg: 'LEVEL'
},
{ group: 'Connection Options' },
{
names: ['url', 'u'],
type: 'string',
help: 'LDAP server URL',
helpArg: 'URL',
default: 'ldap://127.0.0.1:389'
},
{
names: ['binddn', 'D'],
type: 'string',
help: 'Bind DN',
helpArg: 'BIND_DN',
default: ''
},
{
names: ['password', 'w'],
type: 'string',
help: 'Bind password',
helpArg: 'PASSWD',
default: ''
},
{
names: ['insecure', 'i'],
type: 'bool',
env: 'LDAPJS_TLS_INSECURE',
help: 'Disable SSL certificate verification',
default: false
}
];
var parser = dashdash.createParser({options: opts});
///--- Helpers
function usage(code, message) {
var msg = (message ? message + '\n' : '') +
'Usage: ' + path.basename(process.argv[1]) + ' [OPTIONS] [JSON]\n\n' +
parser.help({includeEnv: true});
process.stderr.write(msg + '\n');
process.exit(code);
}
function perror(err) {
if (parsed.debug) {
process.stderr.write(err.stack + '\n');
} else {
process.stderr.write(err.message + '\n');
}
process.exit(1);
}
///--- Mainline
var logLevel = 'info';
var parsed;
try {
parsed = parser.parse(process.argv);
if (parsed.file) {
parsed.file = JSON.parse(fs.readFileSync(parsed.file, 'utf8'));
if (!Array.isArray(parsed.file))
parsed.file = [parsed.file];
}
} catch (e) {
usage(1, e.toString());
}
if (parsed.help)
usage(0);
if (!parsed.file) {
parsed.file = [];
parsed._args.forEach(function (a) {
var o = JSON.parse(a);
if (Array.isArray(o)) {
o.forEach(function (i) {
parsed.file.push(o);
});
return;
}
parsed.file.push(o);
});
if (parsed.file.length === 0)
parsed.file = null;
}
if (!parsed.file)
usage(1, 'either -f or arguments must be used for adding objects');
if (parsed.debug)
logLevel = (parsed.debug > 1 ? 'trace' : 'debug');
var log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: logLevel
});
var client = ldap.createClient({
url: parsed.url,
log: log,
strictDN: false,
tlsOptions: {
rejectUnauthorized: !parsed.insecure
}
});
client.on('error', function (err) {
perror(err);
});
client.bind(parsed.binddn, parsed.password, function (err, res) {
if (err) {
perror(err);
}
vasync.forEachPipeline({
inputs: parsed.file,
func: function (entry, cb) {
var dn = entry.dn;
delete entry.dn;
client.add(dn, entry, cb);
}
}, function (err2, res2) {
if (err2) {
perror(err2);
}
client.unbind(function () { return; });
});
});
Generated Vendored Executable
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env node
// -*- mode: js -*-
// Copyright 2011 Mark Cavage. All rights reserved.
var fs = require('fs');
var path = require('path');
var dashdash = require('dashdash');
var ldap = require('../lib/index');
var Logger = require('bunyan');
///--- Globals
var opts = [
{
names: ['attribute', 'a'],
type: 'string',
help: 'Comparison attribute',
helpArg: 'ATTR'
},
{
names: ['value', 'v'],
type: 'string',
help: 'Comparison value',
helpArg: 'VAL'
},
{ group: 'General Options' },
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
names: ['debug', 'd'],
type: 'integer',
help: 'Set debug level <0-2>',
helpArg: 'LEVEL'
},
{ group: 'Connection Options' },
{
names: ['url', 'u'],
type: 'string',
help: 'LDAP server URL',
helpArg: 'URL',
default: 'ldap://127.0.0.1:389'
},
{
names: ['binddn', 'D'],
type: 'string',
help: 'Bind DN',
helpArg: 'BIND_DN',
default: ''
},
{
names: ['password', 'w'],
type: 'string',
help: 'Bind password',
helpArg: 'PASSWD',
default: ''
},
{
names: ['insecure', 'i'],
type: 'bool',
env: 'LDAPJS_TLS_INSECURE',
help: 'Disable SSL certificate verification',
default: false
}
];
var parser = dashdash.createParser({options: opts});
///--- Helpers
function usage(code, message) {
var msg = (message ? message + '\n' : '') +
'Usage: ' + path.basename(process.argv[1]) + ' [OPTIONS] DN\n\n' +
parser.help({includeEnv: true});
process.stderr.write(msg + '\n');
process.exit(code);
}
function perror(err) {
if (parsed.debug) {
process.stderr.write(err.stack + '\n');
} else {
process.stderr.write(err.message + '\n');
}
process.exit(1);
}
///--- Mainline
var logLevel = 'info';
var parsed;
try {
parsed = parser.parse(process.argv);
} catch (e) {
usage(1, e.toString());
}
if (parsed.help)
usage(0);
if (parsed._args.length < 1)
usage(1, 'DN required');
try {
parsed._args.forEach(function (dn) {
ldap.parseDN(dn);
});
} catch (e) {
usage(1, e.toString());
}
if (!parsed.attribute || typeof (parsed.value) !== 'string')
usage(1, 'attribute and value required');
if (parsed.debug)
logLevel = (parsed.debug > 1 ? 'trace' : 'debug');
var log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: logLevel
});
var client = ldap.createClient({
url: parsed.url,
log: log,
strictDN: false,
tlsOptions: {
rejectUnauthorized: !parsed.insecure
}
});
client.on('error', function (err) {
perror(err);
});
client.bind(parsed.binddn, parsed.password, function (err, res) {
if (err)
perror(err);
var finished = 0;
parsed._args.forEach(function (dn) {
client.compare(dn, parsed.attribute, parsed.value, function (err, match) {
if (err)
perror(err);
process.stdout.write(match + '\n');
if (++finished === parsed._args.length) {
client.unbind(function () {
return;
});
}
});
});
});
Generated Vendored Executable
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env node
// -*- mode: js -*-
// Copyright 2011 Mark Cavage. All rights reserved.
var fs = require('fs');
var path = require('path');
var dashdash = require('dashdash');
var ldap = require('../lib/index');
var Logger = require('bunyan');
///--- Globals
var opts = [
{ group: 'General Options' },
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
names: ['debug', 'd'],
type: 'integer',
help: 'Set debug level <0-2>',
helpArg: 'LEVEL'
},
{ group: 'Connection Options' },
{
names: ['url', 'u'],
type: 'string',
help: 'LDAP server URL',
helpArg: 'URL',
default: 'ldap://127.0.0.1:389'
},
{
names: ['binddn', 'D'],
type: 'string',
help: 'Bind DN',
helpArg: 'bind_DN',
default: ''
},
{
names: ['password', 'w'],
type: 'string',
help: 'Bind password',
helpArg: 'PASSWD',
default: ''
},
{
names: ['insecure', 'i'],
type: 'bool',
env: 'LDAPJS_TLS_INSECURE',
help: 'Disable SSL certificate verification',
default: false
}
];
var parser = dashdash.createParser({options: opts});
///--- Helpers
function usage(code, message) {
var msg = (message ? message + '\n' : '') +
'Usage: ' + path.basename(process.argv[1]) + ' [OPTIONS] DN\n\n' +
parser.help({includeEnv: true});
process.stderr.write(msg + '\n');
process.exit(code);
}
function perror(err) {
if (parsed.debug) {
process.stderr.write(err.stack + '\n');
} else {
process.stderr.write(err.message + '\n');
}
process.exit(1);
}
///--- Mainline
var logLevel = 'info';
var parsed;
try {
parsed = parser.parse(process.argv);
} catch (e) {
usage(1, e.toString());
}
if (parsed.help)
usage(0);
if (parsed._args.length < 1)
usage(1, 'DN required');
try {
parsed._args.forEach(function (dn) {
ldap.parseDN(dn);
});
} catch (e) {
usage(1, e.toString());
}
if (parsed.debug)
logLevel = (parsed.debug > 1 ? 'trace' : 'debug');
var log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: logLevel
});
var client = ldap.createClient({
url: parsed.url,
log: log,
strictDN: false,
tlsOptions: {
rejectUnauthorized: !parsed.insecure
}
});
client.on('error', function (err) {
perror(err);
});
client.bind(parsed.binddn, parsed.password, function (err, res) {
if (err)
perror(err);
var finished = 0;
function callback(err) {
if (err)
perror(err);
if (++finished === parsed._args.length)
client.unbind(function () { return; });
}
parsed._args.forEach(function (dn) {
client.del(dn, callback);
});
});
Generated Vendored Executable
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env node
// -*- mode: js -*-
// Copyright 2011 Mark Cavage. All rights reserved.
var fs = require('fs');
var path = require('path');
var dashdash = require('dashdash');
var ldap = require('../lib/index');
var Logger = require('bunyan');
///--- Globals
var opts = [
{
names: ['attribute', 'a'],
type: 'string',
help: 'Attribute to modify',
helpArg: 'ATTR'
},
{
names: ['value', 'v'],
type: 'arrayOfString',
help: 'Desired value',
helpArg: 'VAL'
},
{
names: ['type', 't'],
type: 'string',
help: 'Attribute type',
helpArg: 'TYPE'
},
{ group: 'General options' },
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
names: ['debug', 'd'],
type: 'integer',
help: 'Set debug level <0-2>',
helpArg: 'LEVEL'
},
{ group: 'Connection Options' },
{
names: ['url', 'u'],
type: 'string',
help: 'LDAP server URL',
helpArg: 'URL',
default: 'ldap://127.0.0.1:389'
},
{
names: ['binddn', 'D'],
type: 'string',
help: 'Bind DN',
helpArg: 'BIND_DN',
default: ''
},
{
names: ['password', 'w'],
type: 'string',
help: 'Bind password',
helpArg: 'PASSWD',
default: ''
},
{
names: ['insecure', 'i'],
type: 'bool',
env: 'LDAPJS_TLS_INSECURE',
help: 'Disable SSL certificate verification',
default: false
}
];
var parser = dashdash.createParser({options: opts});
///--- Helpers
function usage(code, message) {
var msg = (message ? message + '\n' : '') +
'Usage: ' + path.basename(process.argv[1]) + ' [OPTIONS] DN\n\n' +
parser.help({includeEnv: true});
process.stderr.write(msg + '\n');
process.exit(code);
}
function perror(err) {
if (parsed.debug) {
process.stderr.write(err.stack + '\n');
} else {
process.stderr.write(err.message + '\n');
}
process.exit(1);
}
///--- Mainline
var logLevel = 'info';
var parsed;
try {
parsed = parser.parse(process.argv);
} catch (e) {
usage(1, e.toString());
}
if (parsed.help)
usage(0);
if (parsed._args.length < 1)
usage(1, 'DN required');
try {
parsed._args.forEach(function (dn) {
ldap.parseDN(dn);
});
} catch (e) {
usage(1, e.toString());
}
if (!parsed.type)
parsed.type = 'replace';
if (!parsed.attribute || !Array.isArray(parsed.value))
usage(1, 'attribute and value required');
if (parsed.debug)
logLevel = (parsed.debug > 1 ? 'trace' : 'debug');
var log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: logLevel
});
var client = ldap.createClient({
url: parsed.url,
log: log,
strictDN: false,
tlsOptions: {
rejectUnauthorized: !parsed.insecure
}
});
client.on('error', function (err) {
perror(err);
});
client.bind(parsed.binddn, parsed.password, function (err, res) {
if (err)
perror(err);
var finished = 0;
var mod = {};
mod[parsed.attribute] = [];
parsed.value.forEach(function (v) {
mod[parsed.attribute].push(v);
});
var change = new ldap.Change({
type: parsed.type,
modification: mod
});
function callback(err) {
if (err)
perror(err);
if (++finished === parsed._args.length) {
client.unbind(function () {
return;
});
}
}
parsed._args.forEach(function (dn) {
client.modify(dn, change, callback);
});
});
Generated Vendored Executable
+326
View File
@@ -0,0 +1,326 @@
#!/usr/bin/env node
// -*- mode: js -*-
// Copyright 2011 Mark Cavage. All rights reserved.
var path = require('path');
var dashdash = require('dashdash');
var ldap = require('../lib/index');
var Logger = require('bunyan');
///--- Globals
dashdash.addOptionType({
name: 'ldap.Filter',
takesArg: true,
helpArg: 'LDAP_FILTER',
parseArg: function (option, optstr, arg) {
return ldap.parseFilter(arg);
}
});
dashdash.addOptionType({
name: 'ldap.scope',
takesArg: true,
helpArg: 'SCOPE',
parseArg: function (option, optstr, arg) {
if (!/^base|one|sub$/.test(arg)) {
throw new TypeError('Scope must be <base|one|sub>');
}
return arg;
}
});
dashdash.addOptionType({
name: 'ldap.outputFormat',
takesArg: true,
helpArg: 'FORMAT',
parseArg: function (option, optstr, arg) {
var formats = ['json', 'jsonl', 'jsona'];
if (formats.indexOf(arg) === -1) {
throw new TypeError('Must be valid format type');
}
return arg;
}
});
var opts = [
{
names: ['base', 'b'],
type: 'string',
help: 'Base DN of search',
helpArg: 'BASE_DN',
default: ''
},
{
names: ['scope', 's'],
type: 'ldap.scope',
help: 'Search scope <base|sub|one>',
helpArg: 'SCOPE',
default: 'sub'
},
{
names: ['timeout', 't'],
type: 'integer',
help: 'Search timeout',
helpArg: 'SECS'
},
{
names: ['persistent', 'p'],
type: 'bool',
help: 'Enable persistent search control',
default: false
},
{
names: ['paged', 'g'],
type: 'number',
help: 'Enable paged search result control',
helpArg: 'PAGE_SIZE'
},
{
names: ['control', 'c'],
type: 'arrayOfString',
help: 'Send addition control OID',
helpArg: 'OID',
default: []
},
{ group: 'General Options' },
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.'
},
{
names: ['debug', 'd'],
type: 'integer',
help: 'Set debug level <0-2>',
helpArg: 'LEVEL'
},
{ group: 'Output Options' },
{
names: ['format', 'o'],
type: 'ldap.outputFormat',
helpWrap: false,
help: ('Specify and output format. One of:\n' +
' json: JSON objects (default)\n' +
' jsonl: Line-delimited JSON\n' +
' jsona: Array of JSON objects\n'),
default: 'json'
},
{ group: 'Connection Options' },
{
names: ['url', 'u'],
type: 'string',
help: 'LDAP server URL',
helpArg: 'URL',
default: 'ldap://127.0.0.1:389'
},
{
names: ['binddn', 'D'],
type: 'string',
help: 'Bind DN',
helpArg: 'BIND_DN',
default: ''
},
{
names: ['password', 'w'],
type: 'string',
help: 'Bind password',
helpArg: 'PASSWD',
default: ''
},
{
names: ['insecure', 'i'],
type: 'bool',
env: 'LDAPJS_TLS_INSECURE',
help: 'Disable SSL certificate verification',
default: false
}
];
var parser = dashdash.createParser({options: opts});
///--- Helpers
function usage(code, message) {
var msg = (message ? message + '\n' : '') +
'Usage: ' + path.basename(process.argv[1]) +
' [OPTIONS] FILTER [ATTRIBUTES...]\n\n' +
parser.help({includeEnv: true});
process.stderr.write(msg + '\n');
process.exit(code);
}
function perror(err) {
if (parsed.debug) {
process.stderr.write(err.stack + '\n');
} else {
process.stderr.write(err.message + '\n');
}
process.exit(1);
}
function EntryFormatter(fp, format) {
this.format = format;
this.started = false;
this.ended = false;
this.fp = fp;
}
EntryFormatter.prototype.write = function write(entry) {
switch (this.format) {
case 'json':
this.fp.write(JSON.stringify(entry.object, null, 2) + '\n');
break;
case 'jsonl':
this.fp.write(JSON.stringify(entry.object) + '\n');
break;
case 'jsona':
this.fp.write((this.started) ? ',\n' : '[\n');
this.started = true;
// pretty-print with indent
this.fp.write(
JSON.stringify(entry.object, null, 2)
.split('\n')
.map(function (line) { return ' ' + line; })
.join('\n'));
break;
default:
throw new Error('invalid output format');
}
};
EntryFormatter.prototype.end = function end() {
if (this.ended) {
return;
}
this.ended = true;
if (this.format === 'jsona') {
this.fp.write('\n]\n');
}
};
///--- Mainline
var parsed;
process.stdout.on('error', function (err) {
if (err.code === 'EPIPE') {
process.exit(0);
} else {
throw err;
}
});
try {
parsed = parser.parse(process.argv);
} catch (e) {
usage(1, e.toString());
}
if (parsed.help)
usage(0);
if (parsed._args.length < 1)
usage(1, 'filter required');
try {
ldap.parseFilter(parsed._args[0]);
} catch (e) {
usage(1, e.message);
}
var logLevel = 'info';
if (parsed.debug)
logLevel = (parsed.debug > 1 ? 'trace' : 'debug');
var formatter = new EntryFormatter(process.stdout, parsed.format);
var log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: logLevel
});
var client = ldap.createClient({
url: parsed.url,
log: log,
strictDN: false,
timeout: parsed.timeout || false,
tlsOptions: {
rejectUnauthorized: !parsed.insecure
}
});
client.on('error', function (err) {
perror(err);
});
client.on('timeout', function (req) {
process.stderr.write('Timeout reached\n');
process.exit(1);
});
client.bind(parsed.binddn, parsed.password, function (err, res) {
if (err)
perror(err);
var controls = [];
parsed.control.forEach(function (c) {
controls.push(new ldap.Control({
type: c,
criticality: true
}));
});
if (parsed.persistent) {
var pCtrl = new ldap.PersistentSearchControl({
type: '2.16.840.1.113730.3.4.3',
value: {
changeTypes: 15,
changesOnly: false,
returnECs: true
}
});
controls.push(pCtrl);
}
var req = {
scope: parsed.scope || 'sub',
filter: parsed._args[0],
attributes: parsed._args.length > 1 ? parsed._args.slice(1) : []
};
if (parsed.paged) {
req.paged = {
pageSize: parsed.paged
};
}
client.search(parsed.base, req, controls, function (err, res) {
if (err)
perror(err);
res.on('searchEntry', function (entry) {
formatter.write(entry);
});
res.on('error', function (err) {
formatter.end();
perror(err);
});
res.on('end', function (res) {
formatter.end();
if (res.status !== 0) {
process.stderr.write(ldap.getMessage(res.status) + '\n');
}
client.unbind(function () {
return;
});
});
});
});
+199
View File
@@ -0,0 +1,199 @@
var ldap = require('../lib/index');
///--- Shared handlers
function authorize(req, res, next) {
/* Any user may search after bind, only cn=root has full power */
var isSearch = (req instanceof ldap.SearchRequest);
if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch)
return next(new ldap.InsufficientAccessRightsError());
return next();
}
///--- Globals
var SUFFIX = 'o=smartdc';
var db = {};
var server = ldap.createServer();
server.bind('cn=root', function (req, res, next) {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
return next(new ldap.InvalidCredentialsError());
res.end();
return next();
});
server.add(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString();
if (db[dn])
return next(new ldap.EntryAlreadyExistsError(dn));
db[dn] = req.toObject().attributes;
res.end();
return next();
});
server.bind(SUFFIX, function (req, res, next) {
var dn = req.dn.toString();
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
if (!db[dn].userpassword)
return next(new ldap.NoSuchAttributeError('userPassword'));
if (db[dn].userpassword.indexOf(req.credentials) === -1)
return next(new ldap.InvalidCredentialsError());
res.end();
return next();
});
server.compare(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString();
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
if (!db[dn][req.attribute])
return next(new ldap.NoSuchAttributeError(req.attribute));
var matches = false;
var vals = db[dn][req.attribute];
for (var i = 0; i < vals.length; i++) {
if (vals[i] === req.value) {
matches = true;
break;
}
}
res.end(matches);
return next();
});
server.del(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString();
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
delete db[dn];
res.end();
return next();
});
server.modify(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString();
if (!req.changes.length)
return next(new ldap.ProtocolError('changes required'));
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
var entry = db[dn];
for (var i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification;
switch (req.changes[i].operation) {
case 'replace':
if (!entry[mod.type])
return next(new ldap.NoSuchAttributeError(mod.type));
if (!mod.vals || !mod.vals.length) {
delete entry[mod.type];
} else {
entry[mod.type] = mod.vals;
}
break;
case 'add':
if (!entry[mod.type]) {
entry[mod.type] = mod.vals;
} else {
mod.vals.forEach(function (v) {
if (entry[mod.type].indexOf(v) === -1)
entry[mod.type].push(v);
});
}
break;
case 'delete':
if (!entry[mod.type])
return next(new ldap.NoSuchAttributeError(mod.type));
delete entry[mod.type];
break;
}
}
res.end();
return next();
});
server.search(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString();
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
var scopeCheck;
switch (req.scope) {
case 'base':
if (req.filter.matches(db[dn])) {
res.send({
dn: dn,
attributes: db[dn]
});
}
res.end();
return next();
case 'one':
scopeCheck = function (k) {
if (req.dn.equals(k))
return true;
var parent = ldap.parseDN(k).parent();
return (parent ? parent.equals(req.dn) : false);
};
break;
case 'sub':
scopeCheck = function (k) {
return (req.dn.equals(k) || req.dn.parentOf(k));
};
break;
}
Object.keys(db).forEach(function (key) {
if (!scopeCheck(key))
return;
if (req.filter.matches(db[key])) {
res.send({
dn: key,
attributes: db[key]
});
}
});
res.end();
return next();
});
///--- Fire it up
server.listen(1389, function () {
console.log('LDAP server up at: %s', server.url);
});
Generated Vendored Executable
+24
View File
@@ -0,0 +1,24 @@
#!/usr/sbin/dtrace -s
#pragma D option quiet
BEGIN
{
printf("%-8s %-8s %-16s %-15s %-15s %s\n",
"LATENCY", "OPTYPE", "REMOTE IP", "BIND DN", "REQ DN",
"STATUS");
}
ldapjs*:::server-*-start
{
starts[arg0] = timestamp;
}
ldapjs*:::server-*-done
/starts[arg0]/
{
printf("%6dms %-8s %-16s %-15s %-15s %d\n",
(timestamp - starts[arg0]) / 1000000, strtok(probename + 7, "-"),
copyinstr(arg1), copyinstr(arg2), copyinstr(arg3), arg4);
starts[arg0] = 0;
}
BIN
View File
Binary file not shown.
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2015 Joyent, Inc.
var assert = require('assert');
var util = require('util');
var isDN = require('./dn').DN.isDN;
var isAttribute = require('./attribute').isAttribute;
///--- Helpers
// Copied from mcavage/node-assert-plus
function _assert(arg, type, name) {
name = name || type;
throw new assert.AssertionError({
message: util.format('%s (%s) required', name, type),
actual: typeof (arg),
expected: type,
operator: '===',
stackStartFunction: _assert.caller
});
}
///--- API
function stringDN(input, name) {
if (isDN(input) || typeof (input) === 'string')
return;
_assert(input, 'DN or string', name);
}
function optionalStringDN(input, name) {
if (input === undefined || isDN(input) || typeof (input) === 'string')
return;
_assert(input, 'DN or string', name);
}
function optionalDN(input, name) {
if (input !== undefined && !isDN(input))
_assert(input, 'DN', name);
}
function optionalArrayOfAttribute(input, name) {
if (input === undefined)
return;
if (!Array.isArray(input) ||
input.some(function (v) { return !isAttribute(v); })) {
_assert(input, 'array of Attribute', name);
}
}
///--- Exports
module.exports = {
stringDN: stringDN,
optionalStringDN: optionalStringDN,
optionalDN: optionalDN,
optionalArrayOfAttribute: optionalArrayOfAttribute
};
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var asn1 = require('asn1');
var Protocol = require('./protocol');
///--- API
function Attribute(options) {
if (options) {
if (typeof (options) !== 'object')
throw new TypeError('options must be an object');
if (options.type && typeof (options.type) !== 'string')
throw new TypeError('options.type must be a string');
} else {
options = {};
}
this.type = options.type || '';
this._vals = [];
if (options.vals !== undefined && options.vals !== null)
this.vals = options.vals;
}
module.exports = Attribute;
Object.defineProperties(Attribute.prototype, {
buffers: {
get: function getBuffers() {
return this._vals;
},
configurable: false
},
json: {
get: function getJson() {
return {
type: this.type,
vals: this.vals
};
},
configurable: false
},
vals: {
get: function getVals() {
var eType = _bufferEncoding(this.type);
return this._vals.map(function (v) {
return v.toString(eType);
});
},
set: function setVals(vals) {
var self = this;
this._vals = [];
if (Array.isArray(vals)) {
vals.forEach(function (v) {
self.addValue(v);
});
} else {
self.addValue(vals);
}
},
configurable: false
}
});
Attribute.prototype.addValue = function addValue(val) {
if (Buffer.isBuffer(val)) {
this._vals.push(val);
} else {
this._vals.push(new Buffer(val + '', _bufferEncoding(this.type)));
}
};
/* BEGIN JSSTYLED */
Attribute.compare = function compare(a, b) {
if (!(Attribute.isAttribute(a)) || !(Attribute.isAttribute(b))) {
throw new TypeError('can only compare Attributes');
}
if (a.type < b.type) return -1;
if (a.type > b.type) return 1;
if (a.vals.length < b.vals.length) return -1;
if (a.vals.length > b.vals.length) return 1;
for (var i = 0; i < a.vals.length; i++) {
if (a.vals[i] < b.vals[i]) return -1;
if (a.vals[i] > b.vals[i]) return 1;
}
return 0;
};
/* END JSSTYLED */
Attribute.prototype.parse = function parse(ber) {
assert.ok(ber);
ber.readSequence();
this.type = ber.readString();
if (ber.peek() === Protocol.LBER_SET) {
if (ber.readSequence(Protocol.LBER_SET)) {
var end = ber.offset + ber.length;
while (ber.offset < end)
this._vals.push(ber.readString(asn1.Ber.OctetString, true));
}
}
return true;
};
Attribute.prototype.toBer = function toBer(ber) {
assert.ok(ber);
ber.startSequence();
ber.writeString(this.type);
ber.startSequence(Protocol.LBER_SET);
if (this._vals.length) {
this._vals.forEach(function (b) {
ber.writeByte(asn1.Ber.OctetString);
ber.writeLength(b.length);
for (var i = 0; i < b.length; i++)
ber.writeByte(b[i]);
});
} else {
ber.writeStringArray([]);
}
ber.endSequence();
ber.endSequence();
return ber;
};
Attribute.prototype.toString = function () {
return JSON.stringify(this.json);
};
Attribute.toBer = function (attr, ber) {
return Attribute.prototype.toBer.call(attr, ber);
};
Attribute.isAttribute = function isAttribute(attr) {
if (!attr || typeof (attr) !== 'object') {
return false;
}
if (attr instanceof Attribute) {
return true;
}
if ((typeof (attr.toBer) === 'function') &&
(typeof (attr.type) === 'string') &&
(Array.isArray(attr.vals)) &&
(attr.vals.filter(function (item) {
return (typeof (item) === 'string' ||
Buffer.isBuffer(item));
}).length === attr.vals.length)) {
return true;
}
return false;
};
function _bufferEncoding(type) {
/* JSSTYLED */
return /;binary$/.test(type) ? 'base64' : 'utf8';
}
+217
View File
@@ -0,0 +1,217 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var Attribute = require('./attribute');
var Protocol = require('./protocol');
///--- API
function Change(options) {
if (options) {
assert.object(options);
assert.optionalString(options.operation);
} else {
options = {};
}
this._modification = false;
this.operation = options.operation || options.type || 'add';
this.modification = options.modification || {};
}
Object.defineProperties(Change.prototype, {
operation: {
get: function getOperation() {
switch (this._operation) {
case 0x00: return 'add';
case 0x01: return 'delete';
case 0x02: return 'replace';
default:
throw new Error('0x' + this._operation.toString(16) + ' is invalid');
}
},
set: function setOperation(val) {
assert.string(val);
switch (val.toLowerCase()) {
case 'add':
this._operation = 0x00;
break;
case 'delete':
this._operation = 0x01;
break;
case 'replace':
this._operation = 0x02;
break;
default:
throw new Error('Invalid operation type: 0x' + val.toString(16));
}
},
configurable: false
},
modification: {
get: function getModification() {
return this._modification;
},
set: function setModification(val) {
if (Attribute.isAttribute(val)) {
this._modification = val;
return;
}
// Does it have an attribute-like structure
if (Object.keys(val).length == 2 &&
typeof (val.type) === 'string' &&
Array.isArray(val.vals)) {
this._modification = new Attribute({
type: val.type,
vals: val.vals
});
return;
}
var keys = Object.keys(val);
if (keys.length > 1) {
throw new Error('Only one attribute per Change allowed');
} else if (keys.length === 0) {
return;
}
var k = keys[0];
var _attr = new Attribute({type: k});
if (Array.isArray(val[k])) {
val[k].forEach(function (v) {
_attr.addValue(v.toString());
});
} else {
_attr.addValue(val[k].toString());
}
this._modification = _attr;
},
configurable: false
},
json: {
get: function getJSON() {
return {
operation: this.operation,
modification: this._modification ? this._modification.json : {}
};
},
configurable: false
}
});
Change.isChange = function isChange(change) {
if (!change || typeof (change) !== 'object') {
return false;
}
if ((change instanceof Change) ||
((typeof (change.toBer) === 'function') &&
(change.modification !== undefined) &&
(change.operation !== undefined))) {
return true;
}
return false;
};
Change.compare = function (a, b) {
if (!Change.isChange(a) || !Change.isChange(b))
throw new TypeError('can only compare Changes');
if (a.operation < b.operation)
return -1;
if (a.operation > b.operation)
return 1;
return Attribute.compare(a.modification, b.modification);
};
/**
* Apply a Change to properties of an object.
*
* @param {Object} change the change to apply.
* @param {Object} obj the object to apply it to.
* @param {Boolean} scalar convert single-item arrays to scalars. Default: false
*/
Change.apply = function apply(change, obj, scalar) {
assert.string(change.operation);
assert.string(change.modification.type);
assert.ok(Array.isArray(change.modification.vals));
assert.object(obj);
var type = change.modification.type;
var vals = change.modification.vals;
var data = obj[type];
if (data !== undefined) {
if (!Array.isArray(data)) {
data = [data];
}
} else {
data = [];
}
switch (change.operation) {
case 'replace':
if (vals.length === 0) {
// replace empty is a delete
delete obj[type];
return obj;
} else {
data = vals;
}
break;
case 'add':
// add only new unique entries
var newValues = vals.filter(function (entry) {
return (data.indexOf(entry) === -1);
});
data = data.concat(newValues);
break;
case 'delete':
data = data.filter(function (entry) {
return (vals.indexOf(entry) === -1);
});
if (data.length === 0) {
// Erase the attribute if empty
delete obj[type];
return obj;
}
break;
default:
break;
}
if (scalar && data.length === 1) {
// store single-value outputs as scalars, if requested
obj[type] = data[0];
} else {
obj[type] = data;
}
return obj;
};
Change.prototype.parse = function (ber) {
assert.ok(ber);
ber.readSequence();
this._operation = ber.readEnumeration();
this._modification = new Attribute();
this._modification.parse(ber);
return true;
};
Change.prototype.toBer = function (ber) {
assert.ok(ber);
ber.startSequence();
ber.writeEnumeration(this._operation);
ber = this._modification.toBer(ber);
ber.endSequence();
return ber;
};
///--- Exports
module.exports = Change;
+1484
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var Logger = require('bunyan');
var Client = require('./client');
///--- Globals
var DEF_LOG = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
serializers: Logger.stdSerializers
});
///--- Functions
function xor() {
var b = false;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] && !b) {
b = true;
} else if (arguments[i] && b) {
return false;
}
}
return b;
}
///--- Exports
module.exports = {
Client: Client,
createClient: function createClient(options) {
if (typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (options.url && typeof (options.url) !== 'string')
throw new TypeError('options.url (string) required');
if (options.socketPath && typeof (options.socketPath) !== 'string')
throw new TypeError('options.socketPath must be a string');
if (!xor(options.url, options.socketPath))
throw new TypeError('options.url ^ options.socketPath (String) required');
if (!options.log)
options.log = DEF_LOG;
if (typeof (options.log) !== 'object')
throw new TypeError('options.log must be an object');
return new Client(options);
}
};
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2014 Joyent, Inc. All rights reserved.
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var assert = require('assert-plus');
var dn = require('../dn');
var messages = require('../messages/index');
var Protocol = require('../protocol');
var PagedControl = require('../controls/paged_results_control.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'.
* 3. search - Emitted as an internal event to trigger another client search.
*/
function SearchPager(opts) {
assert.object(opts);
assert.func(opts.callback);
assert.number(opts.pageSize);
EventEmitter.call(this, {});
this.callback = opts.callback;
this.controls = opts.controls;
this.pageSize = opts.pageSize;
this.pagePause = opts.pagePause;
this.controls.forEach(function (control) {
if (control.type === PagedControl.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;
var emitter = new EventEmitter();
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, EventEmitter);
module.exports = SearchPager;
/**
* Start the paged search.
*/
SearchPager.prototype.begin = function begin() {
// Starting first page
this._nextPage(null);
};
SearchPager.prototype._onEnd = function _onEnd(res) {
var self = this;
var cookie = null;
res.controls.forEach(function (control) {
if (control.type === PagedControl.OID) {
cookie = control.value.cookie;
}
});
// Pass a noop callback by default for page events
var nullCb = function () { };
if (cookie === null) {
// paged search not supported
this.finished = true;
this.emit('page', res, nullCb);
var 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 deliverying 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 explaination 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) {
var controls = this.controls.slice(0);
controls.push(new PagedControl({
value: {
size: this.pageSize,
cookie: cookie
}
}));
this.emit('search', controls, this.childEmitter,
this._sendCallback.bind(this));
};
/**
* Callback provided to the client API for successful transmission.
*/
SearchPager.prototype._sendCallback = function _sendCallback(err, res) {
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);
}
}
};
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function Control(options) {
assert.optionalObject(options);
options = options || {};
assert.optionalString(options.type);
assert.optionalBool(options.criticality);
if (options.value) {
assert.buffer(options.value);
}
this.type = options.type || '';
this.criticality = options.critical || options.criticality || false;
this.value = options.value || null;
}
Object.defineProperties(Control.prototype, {
json: {
get: function getJson() {
var obj = {
controlType: this.type,
criticality: this.criticality,
controlValue: this.value
};
return (typeof (this._json) === 'function' ? this._json(obj) : obj);
}
}
});
Control.prototype.toBer = function toBer(ber) {
assert.ok(ber);
ber.startSequence();
ber.writeString(this.type || '');
ber.writeBoolean(this.criticality);
if (typeof (this._toBer) === 'function') {
this._toBer(ber);
} else {
if (this.value)
ber.writeString(this.value);
}
ber.endSequence();
return;
};
Control.prototype.toString = function toString() {
return this.json;
};
///--- Exports
module.exports = Control;
+89
View File
@@ -0,0 +1,89 @@
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('./control');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- API
function EntryChangeNotificationControl(options) {
assert.optionalObject(options);
options = options || {};
options.type = EntryChangeNotificationControl.OID;
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value);
} else if (typeof (options.value) === 'object') {
this._value = options.value;
} else {
throw new TypeError('options.value must be a Buffer or Object');
}
options.value = null;
}
Control.call(this, options);
}
util.inherits(EntryChangeNotificationControl, Control);
Object.defineProperties(EntryChangeNotificationControl.prototype, {
value: {
get: function () { return this._value || {}; },
configurable: false
}
});
EntryChangeNotificationControl.prototype.parse = function parse(buffer) {
assert.ok(buffer);
var ber = new BerReader(buffer);
if (ber.readSequence()) {
this._value = {
changeType: ber.readInt()
};
// if the operation was moddn, then parse the optional previousDN attr
if (this._value.changeType === 8)
this._value.previousDN = ber.readString();
this._value.changeNumber = ber.readInt();
return true;
}
return false;
};
EntryChangeNotificationControl.prototype._toBer = function (ber) {
assert.ok(ber);
if (!this._value)
return;
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.value.changeType);
if (this.value.previousDN)
writer.writeString(this.value.previousDN);
writer.writeInt(parseInt(this.value.changeNumber, 10));
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);
};
EntryChangeNotificationControl.prototype._json = function (obj) {
obj.controlValue = this.value;
return obj;
};
EntryChangeNotificationControl.OID = '2.16.840.1.113730.3.4.7';
///--- Exports
module.exports = EntryChangeNotificationControl;
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var Ber = require('asn1').Ber;
var Control = require('./control');
var EntryChangeNotificationControl =
require('./entry_change_notification_control');
var PersistentSearchControl = require('./persistent_search_control');
var PagedResultsControl = require('./paged_results_control');
var ServerSideSortingRequestControl =
require('./server_side_sorting_request_control.js');
var ServerSideSortingResponseControl =
require('./server_side_sorting_response_control.js');
///--- API
module.exports = {
getControl: function getControl(ber) {
assert.ok(ber);
if (ber.readSequence() === null)
return null;
var type;
var opts = {
criticality: false,
value: null
};
if (ber.length) {
var end = ber.offset + ber.length;
type = ber.readString();
if (ber.offset < end) {
if (ber.peek() === Ber.Boolean)
opts.criticality = ber.readBoolean();
}
if (ber.offset < end)
opts.value = ber.readString(Ber.OctetString, true);
}
var control;
switch (type) {
case PersistentSearchControl.OID:
control = new PersistentSearchControl(opts);
break;
case EntryChangeNotificationControl.OID:
control = new EntryChangeNotificationControl(opts);
break;
case PagedResultsControl.OID:
control = new PagedResultsControl(opts);
break;
case ServerSideSortingRequestControl.OID:
control = new ServerSideSortingRequestControl(opts);
break;
case ServerSideSortingResponseControl.OID:
control = new ServerSideSortingResponseControl(opts);
break;
default:
opts.type = type;
control = new Control(opts);
break;
}
return control;
},
Control: Control,
EntryChangeNotificationControl: EntryChangeNotificationControl,
PagedResultsControl: PagedResultsControl,
PersistentSearchControl: PersistentSearchControl,
ServerSideSortingRequestControl: ServerSideSortingRequestControl,
ServerSideSortingResponseControl: ServerSideSortingResponseControl
};
+87
View File
@@ -0,0 +1,87 @@
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('./control');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- API
function PagedResultsControl(options) {
assert.optionalObject(options);
options = options || {};
options.type = PagedResultsControl.OID;
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value);
} else if (typeof (options.value) === 'object') {
this._value = options.value;
} else {
throw new TypeError('options.value must be a Buffer or Object');
}
options.value = null;
}
Control.call(this, options);
}
util.inherits(PagedResultsControl, Control);
Object.defineProperties(PagedResultsControl.prototype, {
value: {
get: function () { return this._value || {}; },
configurable: false
}
});
PagedResultsControl.prototype.parse = function parse(buffer) {
assert.ok(buffer);
var ber = new BerReader(buffer);
if (ber.readSequence()) {
this._value = {};
this._value.size = ber.readInt();
this._value.cookie = ber.readString(asn1.Ber.OctetString, true);
//readString returns '' instead of a zero-length buffer
if (!this._value.cookie)
this._value.cookie = new Buffer(0);
return true;
}
return false;
};
PagedResultsControl.prototype._toBer = function (ber) {
assert.ok(ber);
if (!this._value)
return;
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.value.size);
if (this.value.cookie && this.value.cookie.length > 0) {
writer.writeBuffer(this.value.cookie, asn1.Ber.OctetString);
} else {
writer.writeString(''); //writeBuffer rejects zero-length buffers
}
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);
};
PagedResultsControl.prototype._json = function (obj) {
obj.controlValue = this.value;
return obj;
};
PagedResultsControl.OID = '1.2.840.113556.1.4.319';
///--- Exports
module.exports = PagedResultsControl;
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('./control');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- API
function PersistentSearchControl(options) {
assert.optionalObject(options);
options = options || {};
options.type = PersistentSearchControl.OID;
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value);
} else if (typeof (options.value) === 'object') {
this._value = options.value;
} else {
throw new TypeError('options.value must be a Buffer or Object');
}
options.value = null;
}
Control.call(this, options);
}
util.inherits(PersistentSearchControl, Control);
Object.defineProperties(PersistentSearchControl.prototype, {
value: {
get: function () { return this._value || {}; },
configurable: false
}
});
PersistentSearchControl.prototype.parse = function parse(buffer) {
assert.ok(buffer);
var ber = new BerReader(buffer);
if (ber.readSequence()) {
this._value = {
changeTypes: ber.readInt(),
changesOnly: ber.readBoolean(),
returnECs: ber.readBoolean()
};
return true;
}
return false;
};
PersistentSearchControl.prototype._toBer = function (ber) {
assert.ok(ber);
if (!this._value)
return;
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.value.changeTypes);
writer.writeBoolean(this.value.changesOnly);
writer.writeBoolean(this.value.returnECs);
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);
};
PersistentSearchControl.prototype._json = function (obj) {
obj.controlValue = this.value;
return obj;
};
PersistentSearchControl.OID = '2.16.840.1.113730.3.4.3';
///--- Exports
module.exports = PersistentSearchControl;
+112
View File
@@ -0,0 +1,112 @@
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('./control');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
///--- API
function ServerSideSortingRequestControl(options) {
assert.optionalObject(options);
options = options || {};
options.type = ServerSideSortingRequestControl.OID;
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value);
} else if (Array.isArray(options.value)) {
assert.arrayOfObject(options.value, 'options.value must be Objects');
for (var i = 0; i < options.value.length; i++) {
if (!options.value[i].hasOwnProperty('attributeType')) {
throw new Error('Missing required key: attributeType');
}
}
this._value = options.value;
} else if (typeof (options.value) === 'object') {
if (!options.value.hasOwnProperty('attributeType')) {
throw new Error('Missing required key: attributeType');
}
this._value = [options.value];
} else {
throw new TypeError('options.value must be a Buffer, Array or Object');
}
options.value = null;
}
Control.call(this, options);
}
util.inherits(ServerSideSortingRequestControl, Control);
Object.defineProperties(ServerSideSortingRequestControl.prototype, {
value: {
get: function () { return this._value || []; },
configurable: false
}
});
ServerSideSortingRequestControl.prototype.parse = function parse(buffer) {
assert.ok(buffer);
var ber = new BerReader(buffer);
var item;
if (ber.readSequence(0x30)) {
this._value = [];
while (ber.readSequence(0x30)) {
item = {};
item.attributeType = ber.readString(asn1.Ber.OctetString);
if (ber.peek() == 0x80) {
item.orderingRule = ber.readString(0x80);
}
if (ber.peek() == 0x81) {
item.reverseOrder = (ber._readTag(0x81) === 0 ? false : true);
}
this._value.push(item);
}
return true;
}
return false;
};
ServerSideSortingRequestControl.prototype._toBer = function (ber) {
assert.ok(ber);
if (!this._value || this.value.length === 0)
return;
var writer = new BerWriter();
writer.startSequence(0x30);
for (var i = 0; i < this.value.length; i++) {
var item = this.value[i];
writer.startSequence(0x30);
if (item.attributeType) {
writer.writeString(item.attributeType, asn1.Ber.OctetString);
}
if (item.orderingRule) {
writer.writeString(item.orderingRule, 0x80);
}
if (item.reverseOrder) {
writer.writeBoolean(item.reverseOrder, 0x81);
}
writer.endSequence();
}
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);
};
ServerSideSortingRequestControl.prototype._json = function (obj) {
obj.controlValue = this.value;
return obj;
};
ServerSideSortingRequestControl.OID = '1.2.840.113556.1.4.473';
///---Exports
module.exports = ServerSideSortingRequestControl;
@@ -0,0 +1,103 @@
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('./control');
var CODES = require('../errors/codes');
///--- Globals
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var VALID_CODES = [
CODES.LDAP_SUCCESS,
CODES.LDAP_OPERATIONS_ERROR,
CODES.LDAP_TIME_LIMIT_EXCEEDED,
CODES.LDAP_STRONG_AUTH_REQUIRED,
CODES.LDAP_ADMIN_LIMIT_EXCEEDED,
CODES.LDAP_NO_SUCH_ATTRIBUTE,
CODES.LDAP_INAPPROPRIATE_MATCHING,
CODES.LDAP_INSUFFICIENT_ACCESS_RIGHTS,
CODES.LDAP_BUSY,
CODES.LDAP_UNWILLING_TO_PERFORM,
CODES.LDAP_OTHER
];
function ServerSideSortingResponseControl(options) {
assert.optionalObject(options);
options = options || {};
options.type = ServerSideSortingResponseControl.OID;
options.criticality = false;
if (options.value) {
if (Buffer.isBuffer(options.value)) {
this.parse(options.value);
} else if (typeof (options.value) === 'object') {
if (VALID_CODES.indexOf(options.value.result) === -1) {
throw new Error('Invalid result code');
}
if (options.value.failedAttribute &&
typeof (options.value.failedAttribute) !== 'string') {
throw new Error('failedAttribute must be String');
}
this._value = options.value;
} else {
throw new TypeError('options.value must be a Buffer or Object');
}
options.value = null;
}
Control.call(this, options);
}
util.inherits(ServerSideSortingResponseControl, Control);
Object.defineProperties(ServerSideSortingResponseControl.prototype, {
value: {
get: function () { return this._value || {}; },
configurable: false
}
});
ServerSideSortingResponseControl.prototype.parse = function parse(buffer) {
assert.ok(buffer);
var ber = new BerReader(buffer);
if (ber.readSequence(0x30)) {
this._value = {};
this._value.result = ber.readEnumeration();
if (ber.peek() == 0x80) {
this._value.failedAttribute = ber.readString(0x80);
}
return true;
}
return false;
};
ServerSideSortingResponseControl.prototype._toBer = function (ber) {
assert.ok(ber);
if (!this._value || this.value.length === 0)
return;
var writer = new BerWriter();
writer.startSequence(0x30);
writer.writeEnumeration(this.value.result);
if (this.value.result !== CODES.LDAP_SUCCESS && this.value.failedAttribute) {
writer.writeString(this.value.failedAttribute, 0x80);
}
writer.endSequence();
ber.writeBuffer(writer.buffer, 0x04);
};
ServerSideSortingResponseControl.prototype._json = function (obj) {
obj.controlValue = this.value;
return obj;
};
ServerSideSortingResponseControl.OID = '1.2.840.113556.1.4.474';
///--- Exports
module.exports = ServerSideSortingResponseControl;
+500
View File
@@ -0,0 +1,500 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
///--- Helpers
function invalidDN(name) {
var e = new Error();
e.name = 'InvalidDistinguishedNameError';
e.message = name;
return e;
}
function isAlphaNumeric(c) {
var re = /[A-Za-z0-9]/;
return re.test(c);
}
function isWhitespace(c) {
var re = /\s/;
return re.test(c);
}
function repeatChar(c, n) {
var out = '';
var max = n ? n : 0;
for (var i = 0; i < max; i++)
out += c;
return out;
}
///--- API
function RDN(obj) {
var self = this;
this.attrs = {};
if (obj) {
Object.keys(obj).forEach(function (k) {
self.set(k, obj[k]);
});
}
}
RDN.prototype.set = function rdnSet(name, value, opts) {
assert.string(name, 'name (string) required');
assert.string(value, 'value (string) required');
var self = this;
var lname = name.toLowerCase();
this.attrs[lname] = {
value: value,
name: name
};
if (opts && typeof (opts) === 'object') {
Object.keys(opts).forEach(function (k) {
if (k !== 'value')
self.attrs[lname][k] = opts[k];
});
}
};
RDN.prototype.equals = function rdnEquals(rdn) {
if (typeof (rdn) !== 'object')
return false;
var ourKeys = Object.keys(this.attrs);
var theirKeys = Object.keys(rdn.attrs);
if (ourKeys.length !== theirKeys.length)
return false;
ourKeys.sort();
theirKeys.sort();
for (var i = 0; i < ourKeys.length; i++) {
if (ourKeys[i] !== theirKeys[i])
return false;
if (this.attrs[ourKeys[i]].value !== rdn.attrs[ourKeys[i]].value)
return false;
}
return true;
};
/**
* Convert RDN to string according to specified formatting options.
* (see: DN.format for option details)
*/
RDN.prototype.format = function rdnFormat(options) {
assert.optionalObject(options, 'options must be an object');
options = options || {};
var self = this;
var str = '';
function escapeValue(val, forceQuote) {
var out = '';
var cur = 0;
var len = val.length;
var quoted = false;
/* BEGIN JSSTYLED */
var escaped = /[\\\"]/;
var special = /[,=+<>#;]/;
/* END JSSTYLED */
if (len > 0) {
// Wrap strings with trailing or leading spaces in quotes
quoted = forceQuote || (val[0] == ' ' || val[len-1] == ' ');
}
while (cur < len) {
if (escaped.test(val[cur]) || (!quoted && special.test(val[cur]))) {
out += '\\';
}
out += val[cur++];
}
if (quoted)
out = '"' + out + '"';
return out;
}
function sortParsed(a, b) {
return self.attrs[a].order - self.attrs[b].order;
}
function sortStandard(a, b) {
var nameCompare = a.localeCompare(b);
if (nameCompare === 0) {
// TODO: Handle binary values
return self.attrs[a].value.localeCompare(self.attrs[b].value);
} else {
return nameCompare;
}
}
var keys = Object.keys(this.attrs);
if (options.keepOrder) {
keys.sort(sortParsed);
} else {
keys.sort(sortStandard);
}
keys.forEach(function (key) {
var attr = self.attrs[key];
if (str.length)
str += '+';
if (options.keepCase) {
str += attr.name;
} else {
if (options.upperName)
str += key.toUpperCase();
else
str += key;
}
str += '=' + escapeValue(attr.value, (options.keepQuote && attr.quoted));
});
return str;
};
RDN.prototype.toString = function rdnToString() {
return this.format();
};
// Thank you OpenJDK!
function parse(name) {
if (typeof (name) !== 'string')
throw new TypeError('name (string) required');
var cur = 0;
var len = name.length;
function parseRdn() {
var rdn = new RDN();
var order = 0;
rdn.spLead = trim();
while (cur < len) {
var opts = {
order: order
};
var attr = parseAttrType();
trim();
if (cur >= len || name[cur++] !== '=')
throw invalidDN(name);
trim();
// Parameters about RDN value are set in 'opts' by parseAttrValue
var value = parseAttrValue(opts);
rdn.set(attr, value, opts);
rdn.spTrail = trim();
if (cur >= len || name[cur] !== '+')
break;
++cur;
++order;
}
return rdn;
}
function trim() {
var count = 0;
while ((cur < len) && isWhitespace(name[cur])) {
++cur;
count++;
}
return count;
}
function parseAttrType() {
var beg = cur;
while (cur < len) {
var c = name[cur];
if (isAlphaNumeric(c) ||
c == '.' ||
c == '-' ||
c == ' ') {
++cur;
} else {
break;
}
}
// Back out any trailing spaces.
while ((cur > beg) && (name[cur - 1] == ' '))
--cur;
if (beg == cur)
throw invalidDN(name);
return name.slice(beg, cur);
}
function parseAttrValue(opts) {
if (cur < len && name[cur] == '#') {
opts.binary = true;
return parseBinaryAttrValue();
} else if (cur < len && name[cur] == '"') {
opts.quoted = true;
return parseQuotedAttrValue();
} else {
return parseStringAttrValue();
}
}
function parseBinaryAttrValue() {
var beg = cur++;
while (cur < len && isAlphaNumeric(name[cur]))
++cur;
return name.slice(beg, cur);
}
function parseQuotedAttrValue() {
var str = '';
++cur; // Consume the first quote
while ((cur < len) && name[cur] != '"') {
if (name[cur] === '\\')
cur++;
str += name[cur++];
}
if (cur++ >= len) // no closing quote
throw invalidDN(name);
return str;
}
function parseStringAttrValue() {
var beg = cur;
var str = '';
var esc = -1;
while ((cur < len) && !atTerminator()) {
if (name[cur] === '\\') {
// Consume the backslash and mark its place just in case it's escaping
// whitespace which needs to be preserved.
esc = cur++;
}
if (cur === len) // backslash followed by nothing
throw invalidDN(name);
str += name[cur++];
}
// Trim off (unescaped) trailing whitespace and rewind cursor to the end of
// the AttrValue to record whitespace length.
for (; cur > beg; cur--) {
if (!isWhitespace(name[cur - 1]) || (esc === (cur - 1)))
break;
}
return str.slice(0, cur - beg);
}
function atTerminator() {
return (cur < len &&
(name[cur] === ',' ||
name[cur] === ';' ||
name[cur] === '+'));
}
var rdns = [];
// Short-circuit for empty DNs
if (len === 0)
return new DN(rdns);
rdns.push(parseRdn());
while (cur < len) {
if (name[cur] === ',' || name[cur] === ';') {
++cur;
rdns.push(parseRdn());
} else {
throw invalidDN(name);
}
}
return new DN(rdns);
}
function DN(rdns) {
assert.optionalArrayOfObject(rdns, '[object] required');
this.rdns = rdns ? rdns.slice() : [];
this._format = {};
}
Object.defineProperties(DN.prototype, {
length: {
get: function getLength() { return this.rdns.length; },
configurable: false
}
});
/**
* Convert DN to string according to specified formatting options.
*
* Parameters:
* - options: formatting parameters (optional, details below)
*
* Options are divided into two types:
* - Preservation options: Using data recorded during parsing, details of the
* original DN are preserved when converting back into a string.
* - Modification options: Alter string formatting defaults.
*
* Preservation options _always_ take precedence over modification options.
*
* Preservation Options:
* - keepOrder: Order of multi-value RDNs.
* - keepQuote: RDN values which were quoted will remain so.
* - keepSpace: Leading/trailing spaces will be output.
* - keepCase: Parsed attr name will be output instead of lowercased version.
*
* Modification Options:
* - upperName: RDN names will be uppercased instead of lowercased.
* - skipSpace: Disable trailing space after RDN separators
*/
DN.prototype.format = function dnFormat(options) {
assert.optionalObject(options, 'options must be an object');
options = options || this._format;
var str = '';
this.rdns.forEach(function (rdn) {
var rdnString = rdn.format(options);
if (str.length !== 0) {
str += ',';
}
if (options.keepSpace) {
str += (repeatChar(' ', rdn.spLead) +
rdnString + repeatChar(' ', rdn.spTrail));
} else if (options.skipSpace === true || str.length === 0) {
str += rdnString;
} else {
str += ' ' + rdnString;
}
});
return str;
};
/**
* Set default string formatting options.
*/
DN.prototype.setFormat = function setFormat(options) {
assert.object(options, 'options must be an object');
this._format = options;
};
DN.prototype.toString = function dnToString() {
return this.format();
};
DN.prototype.parentOf = function parentOf(dn) {
if (typeof (dn) !== 'object')
dn = parse(dn);
if (this.rdns.length >= dn.rdns.length)
return false;
var diff = dn.rdns.length - this.rdns.length;
for (var i = this.rdns.length - 1; i >= 0; i--) {
var myRDN = this.rdns[i];
var theirRDN = dn.rdns[i + diff];
if (!myRDN.equals(theirRDN))
return false;
}
return true;
};
DN.prototype.childOf = function childOf(dn) {
if (typeof (dn) !== 'object')
dn = parse(dn);
return dn.parentOf(this);
};
DN.prototype.isEmpty = function isEmpty() {
return (this.rdns.length === 0);
};
DN.prototype.equals = function dnEquals(dn) {
if (typeof (dn) !== 'object')
dn = parse(dn);
if (this.rdns.length !== dn.rdns.length)
return false;
for (var i = 0; i < this.rdns.length; i++) {
if (!this.rdns[i].equals(dn.rdns[i]))
return false;
}
return true;
};
DN.prototype.parent = function dnParent() {
if (this.rdns.length !== 0) {
var save = this.rdns.shift();
var dn = new DN(this.rdns);
this.rdns.unshift(save);
return dn;
}
return null;
};
DN.prototype.clone = function dnClone() {
var dn = new DN(this.rdns);
dn._format = this._format;
return dn;
};
DN.prototype.reverse = function dnReverse() {
this.rdns.reverse();
return this;
};
DN.prototype.pop = function dnPop() {
return this.rdns.pop();
};
DN.prototype.push = function dnPush(rdn) {
assert.object(rdn, 'rdn (RDN) required');
return this.rdns.push(rdn);
};
DN.prototype.shift = function dnShift() {
return this.rdns.shift();
};
DN.prototype.unshift = function dnUnshift(rdn) {
assert.object(rdn, 'rdn (RDN) required');
return this.rdns.unshift(rdn);
};
DN.isDN = function isDN(dn) {
if (!dn || typeof (dn) !== 'object') {
return false;
}
if (dn instanceof DN) {
return true;
}
if (Array.isArray(dn.rdns)) {
// Really simple duck-typing for now
return true;
}
return false;
};
///--- Exports
module.exports = {
parse: parse,
DN: DN,
RDN: RDN
};
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.s
///--- Globals
var SERVER_PROVIDER;
var DTRACE_ID = 0;
var MAX_INT = 4294967295;
/*
* Args:
* server-*-start:
* 0 -> id
* 1 -> remoteIP
* 2 -> bindDN
* 3 -> req.dn
* 4,5 -> op specific
*
* server-*-done:
* 0 -> id
* 1 -> remoteIp
* 2 -> bindDN
* 3 -> requsetDN
* 4 -> status
* 5 -> errorMessage
*
*/
var SERVER_PROBES = {
// 4: attributes.length
'server-add-start': ['int', 'char *', 'char *', 'char *', 'int'],
'server-add-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-bind-start': ['int', 'char *', 'char *', 'char *'],
'server-bind-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: attribute, 5: value
'server-compare-start': ['int', 'char *', 'char *', 'char *',
'char *', 'char *'],
'server-compare-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-delete-start': ['int', 'char *', 'char *', 'char *'],
'server-delete-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: requestName, 5: requestValue
'server-exop-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-exop-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: changes.length
'server-modify-start': ['int', 'char *', 'char *', 'char *', 'int'],
'server-modify-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// 4: newRdn, 5: newSuperior
'server-modifydn-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-modifydn-done': ['int', 'char *', 'char *', 'char *', 'int',
'char *'],
// 4: scope, 5: filter
'server-search-start': ['int', 'char *', 'char *', 'char *', 'char *',
'char *'],
'server-search-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// Last two are searchEntry.DN and seachEntry.attributes.length
'server-search-entry': ['int', 'char *', 'char *', 'char *', 'char *', 'int'],
'server-unbind-start': ['int', 'char *', 'char *', 'char *'],
'server-unbind-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
'server-abandon-start': ['int', 'char *', 'char *', 'char *'],
'server-abandon-done': ['int', 'char *', 'char *', 'char *', 'int', 'char *'],
// remote IP
'server-connection': ['char *']
};
///--- API
module.exports = function () {
if (!SERVER_PROVIDER) {
try {
var dtrace = require('dtrace-provider');
SERVER_PROVIDER = dtrace.createDTraceProvider('ldapjs');
Object.keys(SERVER_PROBES).forEach(function (p) {
var args = SERVER_PROBES[p].splice(0);
args.unshift(p);
dtrace.DTraceProvider.prototype.addProbe.apply(SERVER_PROVIDER, args);
});
} catch (e) {
SERVER_PROVIDER = {
fire: function () {
},
enable: function () {
},
addProbe: function () {
var p = {
fire: function () {
}
};
return (p);
},
removeProbe: function () {
},
disable: function () {
}
};
}
SERVER_PROVIDER.enable();
SERVER_PROVIDER._nextId = function () {
if (DTRACE_ID === MAX_INT)
DTRACE_ID = 0;
return ++DTRACE_ID;
};
}
return SERVER_PROVIDER;
}();
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2014 Joyent, Inc. All rights reserved.
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_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_OTHER: 80,
LDAP_PROXIED_AUTHORIZATION_DENIED: 123
};
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var util = require('util');
var assert = require('assert-plus');
var LDAPResult = require('../messages').LDAPResult;
///--- Globals
var CODES = require('./codes');
var 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;
},
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;
var err = '';
var msg = '';
var pieces = code.split('_').slice(1);
for (var i = 0; i < pieces.length; i++) {
var lc = pieces[i].toLowerCase();
var 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: err,
message: msg
};
});
module.exports.getError = function (res) {
assert.ok(res instanceof LDAPResult, 'res (LDAPResult) required');
var errObj = ERRORS[res.status];
var 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');
var 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
}
});
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function AndFilter(options) {
parents.AndFilter.call(this, options);
}
util.inherits(AndFilter, parents.AndFilter);
Filter.mixin(AndFilter);
module.exports = AndFilter;
AndFilter.prototype._toBer = function (ber) {
assert.ok(ber);
this.filters.forEach(function (f) {
ber = f.toBer(ber);
});
return ber;
};
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function ApproximateFilter(options) {
parents.ApproximateFilter.call(this, options);
}
util.inherits(ApproximateFilter, parents.ApproximateFilter);
Filter.mixin(ApproximateFilter);
module.exports = ApproximateFilter;
ApproximateFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute = ber.readString().toLowerCase();
this.value = ber.readString();
return true;
};
ApproximateFilter.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var ASN1 = require('asn1').Ber;
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function EqualityFilter(options) {
parents.EqualityFilter.call(this, options);
}
util.inherits(EqualityFilter, parents.EqualityFilter);
Filter.mixin(EqualityFilter);
module.exports = EqualityFilter;
EqualityFilter.prototype.matches = function (target, strictAttrCase) {
assert.object(target, 'target');
var tv = parents.getAttrValue(target, this.attribute, strictAttrCase);
var value = this.value;
if (this.attribute.toLowerCase() === 'objectclass') {
/*
* Perform case-insensitive match for objectClass since nearly every LDAP
* implementation behaves in this manner.
*/
value = value.toLowerCase();
return parents.testValues(function (v) {
return value === v.toLowerCase();
}, tv);
} else {
return parents.testValues(function (v) {
return value === v;
}, tv);
}
};
EqualityFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute = ber.readString().toLowerCase();
this.value = ber.readString(ASN1.OctetString, true);
if (this.attribute === 'objectclass')
this.value = this.value.toLowerCase();
return true;
};
EqualityFilter.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeBuffer(this.raw, ASN1.OctetString);
return ber;
};
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
/**
* 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)
*
* Use substr_filter to avoid having * ecsaped.
*
* @author [Austin King](https://github.com/ozten)
*/
exports.escape = function (inp) {
if (typeof (inp) === 'string') {
var esc = '';
for (var i = 0; i < inp.length; i++) {
switch (inp[i]) {
case '*':
esc += '\\2a';
break;
case '(':
esc += '\\28';
break;
case ')':
esc += '\\29';
break;
case '\\':
esc += '\\5c';
break;
case '\0':
esc += '\\00';
break;
default:
esc += inp[i];
break;
}
}
return esc;
} else {
return inp;
}
};
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
// THIS IS A STUB!
//
// ldapjs does not support server side extensible matching.
// This class exists only for the client to send them.
///--- API
function ExtensibleFilter(options) {
parents.ExtensibleFilter.call(this, options);
}
util.inherits(ExtensibleFilter, parents.ExtensibleFilter);
Filter.mixin(ExtensibleFilter);
module.exports = ExtensibleFilter;
ExtensibleFilter.prototype.parse = function (ber) {
var end = ber.offset + ber.length;
while (ber.offset < end) {
var tag = ber.peek();
switch (tag) {
case 0x81:
this.rule = ber.readString(tag);
break;
case 0x82:
this.matchType = ber.readString(tag);
break;
case 0x83:
this.value = ber.readString(tag);
break;
case 0x84:
this.dnAttributes = ber.readBoolean(tag);
break;
default:
throw new Error('Invalid ext_match filter type: 0x' + tag.toString(16));
}
}
return true;
};
ExtensibleFilter.prototype._toBer = function (ber) {
assert.ok(ber);
if (this.rule)
ber.writeString(this.rule, 0x81);
if (this.matchType)
ber.writeString(this.matchType, 0x82);
ber.writeString(this.value, 0x83);
if (this.dnAttributes)
ber.writeBoolean(this.dnAttributes, 0x84);
return ber;
};
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var asn1 = require('asn1');
var Protocol = require('../protocol');
///--- Globals
var BerWriter = asn1.BerWriter;
var TYPES = {
'and': Protocol.FILTER_AND,
'or': Protocol.FILTER_OR,
'not': Protocol.FILTER_NOT,
'equal': Protocol.FILTER_EQUALITY,
'substring': Protocol.FILTER_SUBSTRINGS,
'ge': Protocol.FILTER_GE,
'le': Protocol.FILTER_LE,
'present': Protocol.FILTER_PRESENT,
'approx': Protocol.FILTER_APPROX,
'ext': Protocol.FILTER_EXT
};
///--- API
function isFilter(filter) {
if (!filter || typeof (filter) !== 'object') {
return false;
}
// Do our best to duck-type it
if (typeof (filter.toBer) === 'function' &&
typeof (filter.matches) === 'function' &&
TYPES[filter.type] !== undefined) {
return true;
}
return false;
}
function mixin(target) {
target.prototype.toBer = function toBer(ber) {
if (!ber || !(ber instanceof BerWriter))
throw new TypeError('ber (BerWriter) required');
ber.startSequence(TYPES[this.type]);
ber = this._toBer(ber);
ber.endSequence();
return ber;
};
}
module.exports = {
isFilter: isFilter,
mixin: mixin
};
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function GreaterThanEqualsFilter(options) {
parents.GreaterThanEqualsFilter.call(this, options);
}
util.inherits(GreaterThanEqualsFilter, parents.GreaterThanEqualsFilter);
Filter.mixin(GreaterThanEqualsFilter);
module.exports = GreaterThanEqualsFilter;
GreaterThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute = ber.readString().toLowerCase();
this.value = ber.readString();
return true;
};
GreaterThanEqualsFilter.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};
+207
View File
@@ -0,0 +1,207 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var asn1 = require('asn1');
var parents = require('ldap-filter');
var Protocol = require('../protocol');
var Filter = require('./filter');
var AndFilter = require('./and_filter');
var ApproximateFilter = require('./approx_filter');
var EqualityFilter = require('./equality_filter');
var ExtensibleFilter = require('./ext_filter');
var GreaterThanEqualsFilter = require('./ge_filter');
var LessThanEqualsFilter = require('./le_filter');
var NotFilter = require('./not_filter');
var OrFilter = require('./or_filter');
var PresenceFilter = require('./presence_filter');
var SubstringFilter = require('./substr_filter');
///--- Globals
var BerReader = asn1.BerReader;
///--- Internal Parsers
/*
* A filter looks like this coming in:
* Filter ::= CHOICE {
* and [0] SET OF Filter,
* or [1] SET OF Filter,
* not [2] Filter,
* equalityMatch [3] AttributeValueAssertion,
* substrings [4] SubstringFilter,
* greaterOrEqual [5] AttributeValueAssertion,
* lessOrEqual [6] AttributeValueAssertion,
* present [7] AttributeType,
* approxMatch [8] AttributeValueAssertion,
* extensibleMatch [9] MatchingRuleAssertion --v3 only
* }
*
* SubstringFilter ::= SEQUENCE {
* type AttributeType,
* SEQUENCE OF CHOICE {
* initial [0] IA5String,
* any [1] IA5String,
* final [2] IA5String
* }
* }
*
* The extensibleMatch was added in LDAPv3:
*
* MatchingRuleAssertion ::= SEQUENCE {
* matchingRule [1] MatchingRuleID OPTIONAL,
* type [2] AttributeDescription OPTIONAL,
* matchValue [3] AssertionValue,
* dnAttributes [4] BOOLEAN DEFAULT FALSE
* }
*/
function _parse(ber) {
assert.ok(ber);
function parseSet(f) {
var end = ber.offset + ber.length;
while (ber.offset < end)
f.addFilter(_parse(ber));
}
var f;
var type = ber.readSequence();
switch (type) {
case Protocol.FILTER_AND:
f = new AndFilter();
parseSet(f);
break;
case Protocol.FILTER_APPROX:
f = new ApproximateFilter();
f.parse(ber);
break;
case Protocol.FILTER_EQUALITY:
f = new EqualityFilter();
f.parse(ber);
return f;
case Protocol.FILTER_EXT:
f = new ExtensibleFilter();
f.parse(ber);
return f;
case Protocol.FILTER_GE:
f = new GreaterThanEqualsFilter();
f.parse(ber);
return f;
case Protocol.FILTER_LE:
f = new LessThanEqualsFilter();
f.parse(ber);
return f;
case Protocol.FILTER_NOT:
var _f = _parse(ber);
f = new NotFilter({
filter: _f
});
break;
case Protocol.FILTER_OR:
f = new OrFilter();
parseSet(f);
break;
case Protocol.FILTER_PRESENT:
f = new PresenceFilter();
f.parse(ber);
break;
case Protocol.FILTER_SUBSTRINGS:
f = new SubstringFilter();
f.parse(ber);
break;
default:
throw new Error('Invalid search filter type: 0x' + type.toString(16));
}
assert.ok(f);
return f;
}
function cloneFilter(input) {
var child;
if (input.type === 'and' || input.type === 'or') {
child = input.filters.map(cloneFilter);
} else if (input.type === 'not') {
child = cloneFilter(input.filter);
}
switch (input.type) {
case 'and':
return new AndFilter({filters: child});
case 'or':
return new OrFilter({filters: child});
case 'not':
return new NotFilter({filter: child});
case 'equal':
return new EqualityFilter(input);
case 'substring':
return new SubstringFilter(input);
case 'ge':
return new GreaterThanEqualsFilter(input);
case 'le':
return new LessThanEqualsFilter(input);
case 'present':
return new PresenceFilter(input);
case 'approx':
return new ApproximateFilter(input);
case 'ext':
return new ExtensibleFilter(input);
default:
throw new Error('invalid filter type:' + input.type);
}
}
function parseString(str) {
var generic = parents.parse(str);
// The filter object(s) return from ldap-filter.parse lack the toBer/parse
// decoration that native ldapjs filter possess. cloneFilter adds that back.
return cloneFilter(generic);
}
///--- API
module.exports = {
parse: function (ber) {
if (!ber || !(ber instanceof BerReader))
throw new TypeError('ber (BerReader) required');
return _parse(ber);
},
parseString: parseString,
isFilter: Filter.isFilter,
AndFilter: AndFilter,
ApproximateFilter: ApproximateFilter,
EqualityFilter: EqualityFilter,
ExtensibleFilter: ExtensibleFilter,
GreaterThanEqualsFilter: GreaterThanEqualsFilter,
LessThanEqualsFilter: LessThanEqualsFilter,
NotFilter: NotFilter,
OrFilter: OrFilter,
PresenceFilter: PresenceFilter,
SubstringFilter: SubstringFilter
};
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function LessThanEqualsFilter(options) {
parents.LessThanEqualsFilter.call(this, options);
}
util.inherits(LessThanEqualsFilter, parents.LessThanEqualsFilter);
Filter.mixin(LessThanEqualsFilter);
module.exports = LessThanEqualsFilter;
LessThanEqualsFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute = ber.readString().toLowerCase();
this.value = ber.readString();
return true;
};
LessThanEqualsFilter.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.writeString(this.value);
return ber;
};
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function NotFilter(options) {
parents.NotFilter.call(this, options);
}
util.inherits(NotFilter, parents.NotFilter);
Filter.mixin(NotFilter);
module.exports = NotFilter;
NotFilter.prototype._toBer = function (ber) {
assert.ok(ber);
return this.filter.toBer(ber);
};
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function OrFilter(options) {
parents.OrFilter.call(this, options);
}
util.inherits(OrFilter, parents.OrFilter);
Filter.mixin(OrFilter);
module.exports = OrFilter;
OrFilter.prototype._toBer = function (ber) {
assert.ok(ber);
this.filters.forEach(function (f) {
ber = f.toBer(ber);
});
return ber;
};
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function PresenceFilter(options) {
parents.PresenceFilter.call(this, options);
}
util.inherits(PresenceFilter, parents.PresenceFilter);
Filter.mixin(PresenceFilter);
module.exports = PresenceFilter;
PresenceFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute =
ber.buffer.slice(0, ber.length).toString('utf8').toLowerCase();
ber._offset += ber.length;
return true;
};
PresenceFilter.prototype._toBer = function (ber) {
assert.ok(ber);
for (var i = 0; i < this.attribute.length; i++)
ber.writeByte(this.attribute.charCodeAt(i));
return ber;
};
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var util = require('util');
var parents = require('ldap-filter');
var Filter = require('./filter');
///--- API
function SubstringFilter(options) {
parents.SubstringFilter.call(this, options);
}
util.inherits(SubstringFilter, parents.SubstringFilter);
Filter.mixin(SubstringFilter);
module.exports = SubstringFilter;
SubstringFilter.prototype.parse = function (ber) {
assert.ok(ber);
this.attribute = ber.readString().toLowerCase();
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var tag = ber.peek();
switch (tag) {
case 0x80: // Initial
this.initial = ber.readString(tag);
if (this.attribute === 'objectclass')
this.initial = this.initial.toLowerCase();
break;
case 0x81: // Any
var anyVal = ber.readString(tag);
if (this.attribute === 'objectclass')
anyVal = anyVal.toLowerCase();
this.any.push(anyVal);
break;
case 0x82: // Final
this.final = ber.readString(tag);
if (this.attribute === 'objectclass')
this.final = this.final.toLowerCase();
break;
default:
throw new Error('Invalid substrings filter type: 0x' + tag.toString(16));
}
}
return true;
};
SubstringFilter.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.attribute);
ber.startSequence();
if (this.initial)
ber.writeString(this.initial, 0x80);
if (this.any && this.any.length)
this.any.forEach(function (s) {
ber.writeString(s, 0x81);
});
if (this.final)
ber.writeString(this.final, 0x82);
ber.endSequence();
return ber;
};
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var Logger = require('bunyan');
var client = require('./client');
var Attribute = require('./attribute');
var Change = require('./change');
var Protocol = require('./protocol');
var Server = require('./server');
var assert = require('assert');
var controls = require('./controls');
var persistentSearch = require('./persistent_search');
var dn = require('./dn');
var errors = require('./errors');
var filters = require('./filters');
var messages = require('./messages');
var url = require('./url');
///--- API
module.exports = {
Client: client.Client,
createClient: client.createClient,
Server: Server,
createServer: function (options) {
if (options === undefined)
options = {};
if (typeof (options) !== 'object')
throw new TypeError('options (object) required');
if (!options.log) {
options.log = new Logger({
name: 'ldapjs',
component: 'client',
stream: process.stderr
});
}
return new Server(options);
},
Attribute: Attribute,
Change: Change,
dn: dn,
DN: dn.DN,
RDN: dn.RDN,
parseDN: dn.parse,
persistentSearch: persistentSearch,
PersistentSearchCache: persistentSearch.PersistentSearchCache,
filters: filters,
parseFilter: filters.parseString,
url: url,
parseURL: url.parse
};
///--- Export all the childrenz
var k;
for (k in Protocol) {
if (Protocol.hasOwnProperty(k))
module.exports[k] = Protocol[k];
}
for (k in messages) {
if (messages.hasOwnProperty(k))
module.exports[k] = messages[k];
}
for (k in controls) {
if (controls.hasOwnProperty(k))
module.exports[k] = controls[k];
}
for (k in filters) {
if (filters.hasOwnProperty(k)) {
if (k !== 'parse' && k !== 'parseString')
module.exports[k] = filters[k];
}
}
for (k in errors) {
if (errors.hasOwnProperty(k)) {
module.exports[k] = errors[k];
}
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
///--- API
function AbandonRequest(options) {
options = options || {};
assert.object(options);
assert.optionalNumber(options.abandonID);
options.protocolOp = Protocol.LDAP_REQ_ABANDON;
LDAPMessage.call(this, options);
this.abandonID = options.abandonID || 0;
}
util.inherits(AbandonRequest, LDAPMessage);
Object.defineProperties(AbandonRequest.prototype, {
type: {
get: function getType() { return 'AbandonRequest'; },
configurable: false
}
});
AbandonRequest.prototype._parse = function (ber, length) {
assert.ok(ber);
assert.ok(length);
// What a PITA - have to replicate ASN.1 integer logic to work around the
// way abandon is encoded and the way ldapjs framework handles "normal"
// messages
var buf = ber.buffer;
var offset = 0;
var value = 0;
var fb = buf[offset++];
value = fb & 0x7F;
for (var i = 1; i < length; i++) {
value <<= 8;
value |= (buf[offset++] & 0xff);
}
if ((fb & 0x80) == 0x80)
value = -value;
ber._offset += length;
this.abandonID = value;
return true;
};
AbandonRequest.prototype._toBer = function (ber) {
assert.ok(ber);
var i = this.abandonID;
var sz = 4;
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000)) &&
(sz > 1)) {
sz--;
i <<= 8;
}
assert.ok(sz <= 4);
while (sz-- > 0) {
ber.writeByte((i & 0xff000000) >> 24);
i <<= 8;
}
return ber;
};
AbandonRequest.prototype._json = function (j) {
assert.ok(j);
j.abandonID = this.abandonID;
return j;
};
///--- Exports
module.exports = AbandonRequest;
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./result');
var Protocol = require('../protocol');
///--- API
function AbandonResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = 0;
LDAPMessage.call(this, options);
}
util.inherits(AbandonResponse, LDAPMessage);
Object.defineProperties(AbandonResponse.prototype, {
type: {
get: function getType() { return 'AbandonResponse'; },
configurable: false
}
});
AbandonResponse.prototype.end = function (status) {};
AbandonResponse.prototype._json = function (j) {
return j;
};
///--- Exports
module.exports = AbandonResponse;
+171
View File
@@ -0,0 +1,171 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
var lassert = require('../assert');
///--- API
function AddRequest(options) {
options = options || {};
assert.object(options);
lassert.optionalStringDN(options.entry);
lassert.optionalArrayOfAttribute(options.attributes);
options.protocolOp = Protocol.LDAP_REQ_ADD;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.attributes = options.attributes ? options.attributes.slice(0) : [];
}
util.inherits(AddRequest, LDAPMessage);
Object.defineProperties(AddRequest.prototype, {
type: {
get: function getType() { return 'AddRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.entry; },
configurable: false
}
});
AddRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.entry = ber.readString();
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var a = new Attribute();
a.parse(ber);
a.type = a.type.toLowerCase();
if (a.type === 'objectclass') {
for (var i = 0; i < a.vals.length; i++)
a.vals[i] = a.vals[i].toLowerCase();
}
this.attributes.push(a);
}
this.attributes.sort(Attribute.compare);
return true;
};
AddRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.entry.toString());
ber.startSequence();
this.attributes.forEach(function (a) {
a.toBer(ber);
});
ber.endSequence();
return ber;
};
AddRequest.prototype._json = function (j) {
assert.ok(j);
j.entry = this.entry.toString();
j.attributes = [];
this.attributes.forEach(function (a) {
j.attributes.push(a.json);
});
return j;
};
AddRequest.prototype.indexOf = function (attr) {
if (!attr || typeof (attr) !== 'string')
throw new TypeError('attr (string) required');
for (var i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === attr)
return i;
}
return -1;
};
AddRequest.prototype.attributeNames = function () {
var attrs = [];
for (var i = 0; i < this.attributes.length; i++)
attrs.push(this.attributes[i].type.toLowerCase());
return attrs;
};
AddRequest.prototype.getAttribute = function (name) {
if (!name || typeof (name) !== 'string')
throw new TypeError('attribute name (string) required');
name = name.toLowerCase();
for (var i = 0; i < this.attributes.length; i++) {
if (this.attributes[i].type === name)
return this.attributes[i];
}
return null;
};
AddRequest.prototype.addAttribute = function (attr) {
if (!(attr instanceof Attribute))
throw new TypeError('attribute (Attribute) required');
return this.attributes.push(attr);
};
/**
* Returns a "pure" JS representation of this object.
*
* An example object would look like:
*
* {
* "dn": "cn=unit, dc=test",
* "attributes": {
* "cn": ["unit", "foo"],
* "objectclass": ["top", "person"]
* }
* }
*
* @return {Object} that looks like the above.
*/
AddRequest.prototype.toObject = function () {
var self = this;
var obj = {
dn: self.entry ? self.entry.toString() : '',
attributes: {}
};
if (!this.attributes || !this.attributes.length)
return obj;
this.attributes.forEach(function (a) {
if (!obj.attributes[a.type])
obj.attributes[a.type] = [];
a.vals.forEach(function (v) {
if (obj.attributes[a.type].indexOf(v) === -1)
obj.attributes[a.type].push(v);
});
});
return obj;
};
///--- Exports
module.exports = AddRequest;
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function AddResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_ADD;
LDAPResult.call(this, options);
}
util.inherits(AddResponse, LDAPResult);
///--- Exports
module.exports = AddResponse;
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var LDAP_BIND_SIMPLE = 'simple';
var LDAP_BIND_SASL = 'sasl';
///--- API
function BindRequest(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REQ_BIND;
LDAPMessage.call(this, options);
this.version = options.version || 0x03;
this.name = options.name || null;
this.authentication = options.authentication || LDAP_BIND_SIMPLE;
this.credentials = options.credentials || '';
}
util.inherits(BindRequest, LDAPMessage);
Object.defineProperties(BindRequest.prototype, {
type: {
get: function getType() { return 'BindRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.name; },
configurable: false
}
});
BindRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.version = ber.readInt();
this.name = ber.readString();
var t = ber.peek();
// TODO add support for SASL et al
if (t !== Ber.Context)
throw new Error('authentication 0x' + t.toString(16) + ' not supported');
this.authentication = LDAP_BIND_SIMPLE;
this.credentials = ber.readString(Ber.Context);
return true;
};
BindRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeInt(this.version);
ber.writeString((this.name || '').toString());
// TODO add support for SASL et al
ber.writeString((this.credentials || ''), Ber.Context);
return ber;
};
BindRequest.prototype._json = function (j) {
assert.ok(j);
j.version = this.version;
j.name = this.name;
j.authenticationType = this.authentication;
j.credentials = this.credentials;
return j;
};
///--- Exports
module.exports = BindRequest;
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function BindResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_BIND;
LDAPResult.call(this, options);
}
util.inherits(BindResponse, LDAPResult);
///--- Exports
module.exports = BindResponse;
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
var lassert = require('../assert');
///--- API
function CompareRequest(options) {
options = options || {};
assert.object(options);
assert.optionalString(options.attribute);
assert.optionalString(options.value);
lassert.optionalStringDN(options.entry);
options.protocolOp = Protocol.LDAP_REQ_COMPARE;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.attribute = options.attribute || '';
this.value = options.value || '';
}
util.inherits(CompareRequest, LDAPMessage);
Object.defineProperties(CompareRequest.prototype, {
type: {
get: function getType() { return 'CompareRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.entry; },
configurable: false
}
});
CompareRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.entry = ber.readString();
ber.readSequence();
this.attribute = ber.readString().toLowerCase();
this.value = ber.readString();
return true;
};
CompareRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.entry.toString());
ber.startSequence();
ber.writeString(this.attribute);
ber.writeString(this.value);
ber.endSequence();
return ber;
};
CompareRequest.prototype._json = function (j) {
assert.ok(j);
j.entry = this.entry.toString();
j.attribute = this.attribute;
j.value = this.value;
return j;
};
///--- Exports
module.exports = CompareRequest;
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function CompareResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_COMPARE;
LDAPResult.call(this, options);
}
util.inherits(CompareResponse, LDAPResult);
CompareResponse.prototype.end = function (matches) {
var status = 0x06;
if (typeof (matches) === 'boolean') {
if (!matches)
status = 0x05; // Compare false
} else {
status = matches;
}
return LDAPResult.prototype.end.call(this, status);
};
///--- Exports
module.exports = CompareResponse;
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
var lassert = require('../assert');
///--- API
function DeleteRequest(options) {
options = options || {};
assert.object(options);
lassert.optionalStringDN(options.entry);
options.protocolOp = Protocol.LDAP_REQ_DELETE;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
}
util.inherits(DeleteRequest, LDAPMessage);
Object.defineProperties(DeleteRequest.prototype, {
type: {
get: function getType() { return 'DeleteRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.entry; },
configurable: false
}
});
DeleteRequest.prototype._parse = function (ber, length) {
assert.ok(ber);
this.entry = ber.buffer.slice(0, length).toString('utf8');
ber._offset += ber.length;
return true;
};
DeleteRequest.prototype._toBer = function (ber) {
assert.ok(ber);
var buf = new Buffer(this.entry.toString());
for (var i = 0; i < buf.length; i++)
ber.writeByte(buf[i]);
return ber;
};
DeleteRequest.prototype._json = function (j) {
assert.ok(j);
j.entry = this.entry;
return j;
};
///--- Exports
module.exports = DeleteRequest;
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function DeleteResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_DELETE;
LDAPResult.call(this, options);
}
util.inherits(DeleteResponse, LDAPResult);
///--- Exports
module.exports = DeleteResponse;
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
///--- API
function ExtendedRequest(options) {
options = options || {};
assert.object(options);
assert.optionalString(options.requestName);
if (options.requestValue &&
!(Buffer.isBuffer(options.requestValue) ||
typeof (options.requestValue) === 'string')) {
throw new TypeError('options.requestValue must be a buffer or a string');
}
options.protocolOp = Protocol.LDAP_REQ_EXTENSION;
LDAPMessage.call(this, options);
this.requestName = options.requestName || '';
this.requestValue = options.requestValue;
}
util.inherits(ExtendedRequest, LDAPMessage);
Object.defineProperties(ExtendedRequest.prototype, {
type: {
get: function getType() { return 'ExtendedRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.requestName; },
configurable: false
},
name: {
get: function getName() { return this.requestName; },
set: function setName(val) {
assert.string(val);
this.requestName = val;
},
configurable: false
},
value: {
get: function getValue() { return this.requestValue; },
set: function setValue(val) {
if (!(Buffer.isBuffer(val) || typeof (val) === 'string'))
throw new TypeError('value must be a buffer or a string');
this.requestValue = val;
},
configurable: false
}
});
ExtendedRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.requestName = ber.readString(0x80);
if (ber.peek() === 0x81)
try {
this.requestValue = ber.readString(0x81);
} catch (e) {
this.requestValue = ber.readBuffer(0x81);
}
return true;
};
ExtendedRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.requestName, 0x80);
if (Buffer.isBuffer(this.requestValue)) {
ber.writeBuffer(this.requestValue, 0x81);
} else if (typeof (this.requestValue) === 'string') {
ber.writeString(this.requestValue, 0x81);
}
return ber;
};
ExtendedRequest.prototype._json = function (j) {
assert.ok(j);
j.requestName = this.requestName;
j.requestValue = (Buffer.isBuffer(this.requestValue)) ?
this.requestValue.toString('hex') : this.requestValue;
return j;
};
///--- Exports
module.exports = ExtendedRequest;
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ExtendedResponse(options) {
options = options || {};
assert.object(options);
assert.optionalString(options.responseName);
assert.optionalString(options.responsevalue);
this.responseName = options.responseName || undefined;
this.responseValue = options.responseValue || undefined;
options.protocolOp = Protocol.LDAP_REP_EXTENSION;
LDAPResult.call(this, options);
}
util.inherits(ExtendedResponse, LDAPResult);
Object.defineProperties(ExtendedResponse.prototype, {
type: {
get: function getType() { return 'ExtendedResponse'; },
configurable: false
},
_dn: {
get: function getDN() { return this.responseName; },
configurable: false
},
name: {
get: function getName() { return this.responseName; },
set: function setName(val) {
assert.string(val);
this.responseName = val;
},
configurable: false
},
value: {
get: function getValue() { return this.responseValue; },
set: function (val) {
assert.string(val);
this.responseValue = val;
},
configurable: false
}
});
ExtendedResponse.prototype._parse = function (ber) {
assert.ok(ber);
if (!LDAPResult.prototype._parse.call(this, ber))
return false;
if (ber.peek() === 0x8a)
this.responseName = ber.readString(0x8a);
if (ber.peek() === 0x8b)
this.responseValue = ber.readString(0x8b);
return true;
};
ExtendedResponse.prototype._toBer = function (ber) {
assert.ok(ber);
if (!LDAPResult.prototype._toBer.call(this, ber))
return false;
if (this.responseName)
ber.writeString(this.responseName, 0x8a);
if (this.responseValue)
ber.writeString(this.responseValue, 0x8b);
return ber;
};
ExtendedResponse.prototype._json = function (j) {
assert.ok(j);
j = LDAPResult.prototype._json.call(this, j);
j.responseName = this.responseName;
j.responseValue = this.responseValue;
return j;
};
///--- Exports
module.exports = ExtendedResponse;
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var Parser = require('./parser');
var AbandonRequest = require('./abandon_request');
var AbandonResponse = require('./abandon_response');
var AddRequest = require('./add_request');
var AddResponse = require('./add_response');
var BindRequest = require('./bind_request');
var BindResponse = require('./bind_response');
var CompareRequest = require('./compare_request');
var CompareResponse = require('./compare_response');
var DeleteRequest = require('./del_request');
var DeleteResponse = require('./del_response');
var ExtendedRequest = require('./ext_request');
var ExtendedResponse = require('./ext_response');
var ModifyRequest = require('./modify_request');
var ModifyResponse = require('./modify_response');
var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
///--- API
module.exports = {
LDAPMessage: LDAPMessage,
LDAPResult: LDAPResult,
Parser: Parser,
AbandonRequest: AbandonRequest,
AbandonResponse: AbandonResponse,
AddRequest: AddRequest,
AddResponse: AddResponse,
BindRequest: BindRequest,
BindResponse: BindResponse,
CompareRequest: CompareRequest,
CompareResponse: CompareResponse,
DeleteRequest: DeleteRequest,
DeleteResponse: DeleteResponse,
ExtendedRequest: ExtendedRequest,
ExtendedResponse: ExtendedResponse,
ModifyRequest: ModifyRequest,
ModifyResponse: ModifyResponse,
ModifyDNRequest: ModifyDNRequest,
ModifyDNResponse: ModifyDNResponse,
SearchRequest: SearchRequest,
SearchEntry: SearchEntry,
SearchReference: SearchReference,
SearchResponse: SearchResponse,
UnbindRequest: UnbindRequest,
UnbindResponse: UnbindResponse
};
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var Control = require('../controls').Control;
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
var BerWriter = asn1.BerWriter;
var getControl = require('../controls').getControl;
///--- API
/**
* LDAPMessage structure.
*
* @param {Object} options stuff.
*/
function LDAPMessage(options) {
assert.object(options);
this.messageID = options.messageID || 0;
this.protocolOp = options.protocolOp || undefined;
this.controls = options.controls ? options.controls.slice(0) : [];
this.log = options.log;
}
Object.defineProperties(LDAPMessage.prototype, {
id: {
get: function getId() { return this.messageID; },
configurable: false
},
dn: {
get: function getDN() { return this._dn || ''; },
configurable: false
},
type: {
get: function getType() { return 'LDAPMessage'; },
configurable: false
},
json: {
get: function () {
var out = this._json({
messageID: this.messageID,
protocolOp: this.type
});
out.controls = this.controls;
return out;
},
configurable: false
}
});
LDAPMessage.prototype.toString = function () {
return JSON.stringify(this.json);
};
LDAPMessage.prototype.parse = function (ber) {
assert.ok(ber);
if (this.log.trace())
this.log.trace('parse: data=%s', util.inspect(ber.buffer));
// Delegate off to the specific type to parse
this._parse(ber, ber.length);
// Look for controls
if (ber.peek() === 0xa0) {
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var c = getControl(ber);
if (c)
this.controls.push(c);
}
}
if (this.log.trace())
this.log.trace('Parsing done: %j', this.json);
return true;
};
LDAPMessage.prototype.toBer = function () {
var writer = new BerWriter();
writer.startSequence();
writer.writeInt(this.messageID);
writer.startSequence(this.protocolOp);
if (this._toBer)
writer = this._toBer(writer);
writer.endSequence();
if (this.controls && this.controls.length) {
writer.startSequence(0xa0);
this.controls.forEach(function (c) {
c.toBer(writer);
});
writer.endSequence();
}
writer.endSequence();
return writer.buffer;
};
///--- Exports
module.exports = LDAPMessage;
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
var dn = require('../dn');
var lassert = require('../assert');
///--- API
function ModifyDNRequest(options) {
options = options || {};
assert.object(options);
assert.optionalBool(options.deleteOldRdn);
lassert.optionalStringDN(options.entry);
lassert.optionalDN(options.newRdn);
lassert.optionalDN(options.newSuperior);
options.protocolOp = Protocol.LDAP_REQ_MODRDN;
LDAPMessage.call(this, options);
this.entry = options.entry || null;
this.newRdn = options.newRdn || null;
this.deleteOldRdn = options.deleteOldRdn || true;
this.newSuperior = options.newSuperior || null;
}
util.inherits(ModifyDNRequest, LDAPMessage);
Object.defineProperties(ModifyDNRequest.prototype, {
type: {
get: function getType() { return 'ModifyDNRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.entry; },
configurable: false
}
});
ModifyDNRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.entry = ber.readString();
this.newRdn = dn.parse(ber.readString());
this.deleteOldRdn = ber.readBoolean();
if (ber.peek() === 0x80)
this.newSuperior = dn.parse(ber.readString(0x80));
return true;
};
ModifyDNRequest.prototype._toBer = function (ber) {
//assert.ok(ber);
ber.writeString(this.entry.toString());
ber.writeString(this.newRdn.toString());
ber.writeBoolean(this.deleteOldRdn);
if (this.newSuperior) {
var s = this.newSuperior.toString();
var len = Buffer.byteLength(s);
ber.writeByte(0x80); // MODIFY_DN_REQUEST_NEW_SUPERIOR_TAG
ber.writeByte(len);
ber._ensure(len);
ber._buf.write(s, ber._offset);
ber._offset += len;
}
return ber;
};
ModifyDNRequest.prototype._json = function (j) {
assert.ok(j);
j.entry = this.entry.toString();
j.newRdn = this.newRdn.toString();
j.deleteOldRdn = this.deleteOldRdn;
j.newSuperior = this.newSuperior ? this.newSuperior.toString() : '';
return j;
};
///--- Exports
module.exports = ModifyDNRequest;
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ModifyDNResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_MODRDN;
LDAPResult.call(this, options);
}
util.inherits(ModifyDNResponse, LDAPResult);
///--- Exports
module.exports = ModifyDNResponse;
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var Change = require('../change');
var Protocol = require('../protocol');
var lassert = require('../assert');
///--- API
function ModifyRequest(options) {
options = options || {};
assert.object(options);
lassert.optionalStringDN(options.object);
lassert.optionalArrayOfAttribute(options.attributes);
options.protocolOp = Protocol.LDAP_REQ_MODIFY;
LDAPMessage.call(this, options);
this.object = options.object || null;
this.changes = options.changes ? options.changes.slice(0) : [];
}
util.inherits(ModifyRequest, LDAPMessage);
Object.defineProperties(ModifyRequest.prototype, {
type: {
get: function getType() { return 'ModifyRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.object; },
configurable: false
}
});
ModifyRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.object = ber.readString();
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end) {
var c = new Change();
c.parse(ber);
c.modification.type = c.modification.type.toLowerCase();
this.changes.push(c);
}
this.changes.sort(Change.compare);
return true;
};
ModifyRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.object.toString());
ber.startSequence();
this.changes.forEach(function (c) {
c.toBer(ber);
});
ber.endSequence();
return ber;
};
ModifyRequest.prototype._json = function (j) {
assert.ok(j);
j.object = this.object;
j.changes = [];
this.changes.forEach(function (c) {
j.changes.push(c.json);
});
return j;
};
///--- Exports
module.exports = ModifyRequest;
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var Protocol = require('../protocol');
///--- API
function ModifyResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_MODIFY;
LDAPResult.call(this, options);
}
util.inherits(ModifyResponse, LDAPResult);
///--- Exports
module.exports = ModifyResponse;
+228
View File
@@ -0,0 +1,228 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var assert = require('assert-plus');
var asn1 = require('asn1');
var VError = require('verror').VError;
var AbandonRequest = require('./abandon_request');
var AddRequest = require('./add_request');
var AddResponse = require('./add_response');
var BindRequest = require('./bind_request');
var BindResponse = require('./bind_response');
var CompareRequest = require('./compare_request');
var CompareResponse = require('./compare_response');
var DeleteRequest = require('./del_request');
var DeleteResponse = require('./del_response');
var ExtendedRequest = require('./ext_request');
var ExtendedResponse = require('./ext_response');
var ModifyRequest = require('./modify_request');
var ModifyResponse = require('./modify_response');
var ModifyDNRequest = require('./moddn_request');
var ModifyDNResponse = require('./moddn_response');
var SearchRequest = require('./search_request');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var SearchResponse = require('./search_response');
var UnbindRequest = require('./unbind_request');
var UnbindResponse = require('./unbind_response');
var LDAPResult = require('./result');
var Message = require('./message');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
///--- API
function Parser(options) {
assert.object(options);
assert.object(options.log);
EventEmitter.call(this);
this.buffer = null;
this.log = options.log;
}
util.inherits(Parser, EventEmitter);
Parser.prototype.write = function (data) {
if (!data || !Buffer.isBuffer(data))
throw new TypeError('data (buffer) required');
var nextMessage = null;
var self = this;
function end() {
if (nextMessage)
return self.write(nextMessage);
return true;
}
self.buffer = (self.buffer ? Buffer.concat([self.buffer, data]) : data);
var ber = new BerReader(self.buffer);
var 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 sort of ugly, but allows us to make miminal copies
nextMessage = self.buffer.slice(ber.offset + ber.length);
ber._size = ber.offset + ber.length;
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;
var message;
try {
// Bail here if peer isn't speaking protocol at all
message = this.getMessage(ber);
if (!message) {
return end();
}
message.parse(ber);
} catch (e) {
this.emit('error', e, message);
return false;
}
this.emit('message', message);
return end();
};
Parser.prototype.getMessage = function (ber) {
assert.ok(ber);
var self = this;
var messageID = ber.readInt();
var type = ber.readSequence();
var Message;
switch (type) {
case Protocol.LDAP_REQ_ABANDON:
Message = AbandonRequest;
break;
case Protocol.LDAP_REQ_ADD:
Message = AddRequest;
break;
case Protocol.LDAP_REP_ADD:
Message = AddResponse;
break;
case Protocol.LDAP_REQ_BIND:
Message = BindRequest;
break;
case Protocol.LDAP_REP_BIND:
Message = BindResponse;
break;
case Protocol.LDAP_REQ_COMPARE:
Message = CompareRequest;
break;
case Protocol.LDAP_REP_COMPARE:
Message = CompareResponse;
break;
case Protocol.LDAP_REQ_DELETE:
Message = DeleteRequest;
break;
case Protocol.LDAP_REP_DELETE:
Message = DeleteResponse;
break;
case Protocol.LDAP_REQ_EXTENSION:
Message = ExtendedRequest;
break;
case Protocol.LDAP_REP_EXTENSION:
Message = ExtendedResponse;
break;
case Protocol.LDAP_REQ_MODIFY:
Message = ModifyRequest;
break;
case Protocol.LDAP_REP_MODIFY:
Message = ModifyResponse;
break;
case Protocol.LDAP_REQ_MODRDN:
Message = ModifyDNRequest;
break;
case Protocol.LDAP_REP_MODRDN:
Message = ModifyDNResponse;
break;
case Protocol.LDAP_REQ_SEARCH:
Message = SearchRequest;
break;
case Protocol.LDAP_REP_SEARCH_ENTRY:
Message = SearchEntry;
break;
case Protocol.LDAP_REP_SEARCH_REF:
Message = SearchReference;
break;
case Protocol.LDAP_REP_SEARCH:
Message = SearchResponse;
break;
case Protocol.LDAP_REQ_UNBIND:
Message = UnbindRequest;
break;
default:
this.emit('error',
new Error('Op 0x' + (type ? type.toString(16) : '??') +
' not supported'),
new LDAPResult({
messageID: messageID,
protocolOp: type || Protocol.LDAP_REP_EXTENSION
}));
return false;
}
return new Message({
messageID: messageID,
log: self.log
});
};
///--- Exports
module.exports = Parser;
+129
View File
@@ -0,0 +1,129 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var dtrace = require('../dtrace');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
var BerWriter = asn1.BerWriter;
///--- API
function LDAPResult(options) {
options = options || {};
assert.object(options);
assert.optionalNumber(options.status);
assert.optionalString(options.matchedDN);
assert.optionalString(options.errorMessage);
assert.optionalArrayOfString(options.referrals);
LDAPMessage.call(this, options);
this.status = options.status || 0; // LDAP SUCCESS
this.matchedDN = options.matchedDN || '';
this.errorMessage = options.errorMessage || '';
this.referrals = options.referrals || [];
this.connection = options.connection || null;
}
util.inherits(LDAPResult, LDAPMessage);
Object.defineProperties(LDAPResult.prototype, {
type: {
get: function getType() { return 'LDAPResult'; },
configurable: false
}
});
LDAPResult.prototype.end = function (status) {
assert.ok(this.connection);
if (typeof (status) === 'number')
this.status = status;
var ber = this.toBer();
if (this.log.debug())
this.log.debug('%s: sending: %j', this.connection.ldap.id, this.json);
try {
var self = this;
this.connection.write(ber);
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
var c = self.connection || {ldap: {}};
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
status || self.status,
self.errorMessage
];
});
}
} catch (e) {
this.log.warn(e, '%s failure to write message %j',
this.connection.ldap.id, this.json);
}
};
LDAPResult.prototype._parse = function (ber) {
assert.ok(ber);
this.status = ber.readEnumeration();
this.matchedDN = ber.readString();
this.errorMessage = ber.readString();
var t = ber.peek();
if (t === Protocol.LDAP_REP_REFERRAL) {
var end = ber.offset + ber.length;
while (ber.offset < end)
this.referrals.push(ber.readString());
}
return true;
};
LDAPResult.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeEnumeration(this.status);
ber.writeString(this.matchedDN || '');
ber.writeString(this.errorMessage || '');
if (this.referrals.length) {
ber.startSequence(Protocol.LDAP_REP_REFERRAL);
ber.writeStringArray(this.referrals);
ber.endSequence();
}
return ber;
};
LDAPResult.prototype._json = function (j) {
assert.ok(j);
j.status = this.status;
j.matchedDN = this.matchedDN;
j.errorMessage = this.errorMessage;
j.referrals = this.referrals;
return j;
};
///--- Exports
module.exports = LDAPResult;
+196
View File
@@ -0,0 +1,196 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Attribute = require('../attribute');
var Protocol = require('../protocol');
var lassert = require('../assert');
///--- Globals
var BerWriter = asn1.BerWriter;
///--- API
function SearchEntry(options) {
options = options || {};
assert.object(options);
lassert.optionalStringDN(options.objectName);
options.protocolOp = Protocol.LDAP_REP_SEARCH_ENTRY;
LDAPMessage.call(this, options);
this.objectName = options.objectName || null;
this.setAttributes(options.attributes || []);
}
util.inherits(SearchEntry, LDAPMessage);
Object.defineProperties(SearchEntry.prototype, {
type: {
get: function getType() { return 'SearchEntry'; },
configurable: false
},
_dn: {
get: function getDN() { return this.objectName; },
configurable: false
},
object: {
get: function getObject() {
var obj = {
dn: this.dn.toString(),
controls: []
};
this.attributes.forEach(function (a) {
if (a.vals && a.vals.length) {
if (a.vals.length > 1) {
obj[a.type] = a.vals.slice();
} else {
obj[a.type] = a.vals[0];
}
} else {
obj[a.type] = [];
}
});
this.controls.forEach(function (element, index, array) {
obj.controls.push(element.json);
});
return obj;
},
configurable: false
},
raw: {
get: function getRaw() {
var obj = {
dn: this.dn.toString(),
controls: []
};
this.attributes.forEach(function (a) {
if (a.buffers && a.buffers.length) {
if (a.buffers.length > 1) {
obj[a.type] = a.buffers.slice();
} else {
obj[a.type] = a.buffers[0];
}
} else {
obj[a.type] = [];
}
});
this.controls.forEach(function (element, index, array) {
obj.controls.push(element.json);
});
return obj;
},
configurable: false
}
});
SearchEntry.prototype.addAttribute = function (attr) {
if (!attr || typeof (attr) !== 'object')
throw new TypeError('attr (attribute) required');
this.attributes.push(attr);
};
SearchEntry.prototype.toObject = function () {
return this.object;
};
SearchEntry.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object')
throw new TypeError('object required');
var self = this;
if (obj.controls)
this.controls = obj.controls;
if (obj.attributes)
obj = obj.attributes;
this.attributes = [];
Object.keys(obj).forEach(function (k) {
self.attributes.push(new Attribute({type: k, vals: obj[k]}));
});
return true;
};
SearchEntry.prototype.setAttributes = function (obj) {
if (typeof (obj) !== 'object')
throw new TypeError('object required');
if (Array.isArray(obj)) {
obj.forEach(function (a) {
if (!Attribute.isAttribute(a))
throw new TypeError('entry must be an Array of Attributes');
});
this.attributes = obj;
} else {
var self = this;
self.attributes = [];
Object.keys(obj).forEach(function (k) {
var attr = new Attribute({type: k});
if (Array.isArray(obj[k])) {
obj[k].forEach(function (v) {
attr.addValue(v.toString());
});
} else {
attr.addValue(obj[k].toString());
}
self.attributes.push(attr);
});
}
};
SearchEntry.prototype._json = function (j) {
assert.ok(j);
j.objectName = this.objectName.toString();
j.attributes = [];
this.attributes.forEach(function (a) {
j.attributes.push(a.json || a);
});
return j;
};
SearchEntry.prototype._parse = function (ber) {
assert.ok(ber);
this.objectName = ber.readString();
assert.ok(ber.readSequence());
var end = ber.offset + ber.length;
while (ber.offset < end) {
var a = new Attribute();
a.parse(ber);
this.attributes.push(a);
}
return true;
};
SearchEntry.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.objectName.toString());
ber.startSequence();
this.attributes.forEach(function (a) {
// This may or may not be an attribute
ber = Attribute.toBer(a, ber);
});
ber.endSequence();
return ber;
};
///--- Exports
module.exports = SearchEntry;
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var Protocol = require('../protocol');
var dn = require('../dn');
var url = require('../url');
///--- Globals
var BerWriter = asn1.BerWriter;
var parseURL = url.parse;
///--- API
function SearchReference(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_SEARCH_REF;
LDAPMessage.call(this, options);
this.uris = options.uris || [];
}
util.inherits(SearchReference, LDAPMessage);
Object.defineProperties(SearchReference.prototype, {
type: {
get: function getType() { return 'SearchReference'; },
configurable: false
},
_dn: {
get: function getDN() { return new dn.DN(''); },
configurable: false
},
object: {
get: function getObject() {
return {
dn: this.dn.toString(),
uris: this.uris.slice()
};
},
configurable: false
},
urls: {
get: function getUrls() { return this.uris; },
set: function setUrls(val) {
assert.ok(val);
assert.ok(Array.isArray(val));
this.uris = val.slice();
},
configurable: false
}
});
SearchReference.prototype.toObject = function () {
return this.object;
};
SearchReference.prototype.fromObject = function (obj) {
if (typeof (obj) !== 'object')
throw new TypeError('object required');
this.uris = obj.uris ? obj.uris.slice() : [];
return true;
};
SearchReference.prototype._json = function (j) {
assert.ok(j);
j.uris = this.uris.slice();
return j;
};
SearchReference.prototype._parse = function (ber, length) {
assert.ok(ber);
while (ber.offset < length) {
var _url = ber.readString();
parseURL(_url);
this.uris.push(_url);
}
return true;
};
SearchReference.prototype._toBer = function (ber) {
assert.ok(ber);
this.uris.forEach(function (u) {
ber.writeString(u.href || u);
});
return ber;
};
///--- Exports
module.exports = SearchReference;
+150
View File
@@ -0,0 +1,150 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var asn1 = require('asn1');
var LDAPMessage = require('./message');
var LDAPResult = require('./result');
var dn = require('../dn');
var filters = require('../filters');
var Protocol = require('../protocol');
///--- Globals
var Ber = asn1.Ber;
///--- API
function SearchRequest(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REQ_SEARCH;
LDAPMessage.call(this, options);
if (options.baseObject !== undefined) {
this.baseObject = options.baseObject;
} else {
this.baseObject = dn.parse('');
}
this.scope = options.scope || 'base';
this.derefAliases = options.derefAliases || Protocol.NEVER_DEREF_ALIASES;
this.sizeLimit = options.sizeLimit || 0;
this.timeLimit = options.timeLimit || 0;
this.typesOnly = options.typesOnly || false;
this.filter = options.filter || null;
this.attributes = options.attributes ? options.attributes.slice(0) : [];
}
util.inherits(SearchRequest, LDAPMessage);
Object.defineProperties(SearchRequest.prototype, {
type: {
get: function getType() { return 'SearchRequest'; },
configurable: false
},
_dn: {
get: function getDN() { return this.baseObject; },
configurable: false
},
scope: {
get: function getScope() {
switch (this._scope) {
case Protocol.SCOPE_BASE_OBJECT: return 'base';
case Protocol.SCOPE_ONE_LEVEL: return 'one';
case Protocol.SCOPE_SUBTREE: return 'sub';
default:
throw new Error(this._scope + ' is an invalid search scope');
}
},
set: function setScope(val) {
if (typeof (val) === 'string') {
switch (val) {
case 'base':
this._scope = Protocol.SCOPE_BASE_OBJECT;
break;
case 'one':
this._scope = Protocol.SCOPE_ONE_LEVEL;
break;
case 'sub':
this._scope = Protocol.SCOPE_SUBTREE;
break;
default:
throw new Error(val + ' is an invalid search scope');
}
} else {
this._scope = val;
}
},
configurable: false
}
});
SearchRequest.prototype._parse = function (ber) {
assert.ok(ber);
this.baseObject = ber.readString();
this.scope = ber.readEnumeration();
this.derefAliases = ber.readEnumeration();
this.sizeLimit = ber.readInt();
this.timeLimit = ber.readInt();
this.typesOnly = ber.readBoolean();
this.filter = filters.parse(ber);
// look for attributes
if (ber.peek() === 0x30) {
ber.readSequence();
var end = ber.offset + ber.length;
while (ber.offset < end)
this.attributes.push(ber.readString().toLowerCase());
}
return true;
};
SearchRequest.prototype._toBer = function (ber) {
assert.ok(ber);
ber.writeString(this.baseObject.toString());
ber.writeEnumeration(this._scope);
ber.writeEnumeration(this.derefAliases);
ber.writeInt(this.sizeLimit);
ber.writeInt(this.timeLimit);
ber.writeBoolean(this.typesOnly);
var f = this.filter || new filters.PresenceFilter({attribute: 'objectclass'});
ber = f.toBer(ber);
ber.startSequence(Ber.Sequence | Ber.Constructor);
if (this.attributes && this.attributes.length) {
this.attributes.forEach(function (a) {
ber.writeString(a);
});
}
ber.endSequence();
return ber;
};
SearchRequest.prototype._json = function (j) {
assert.ok(j);
j.baseObject = this.baseObject;
j.scope = this.scope;
j.derefAliases = this.derefAliases;
j.sizeLimit = this.sizeLimit;
j.timeLimit = this.timeLimit;
j.typesOnly = this.typesOnly;
j.filter = this.filter.toString();
j.attributes = this.attributes;
return j;
};
///--- Exports
module.exports = SearchRequest;
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPResult = require('./result');
var SearchEntry = require('./search_entry');
var SearchReference = require('./search_reference');
var dtrace = require('../dtrace');
var parseDN = require('../dn').parse;
var parseURL = require('../url').parse;
var Protocol = require('../protocol');
///--- API
function SearchResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REP_SEARCH;
LDAPResult.call(this, options);
this.attributes = options.attributes ? options.attributes.slice() : [];
this.notAttributes = [];
this.sentEntries = 0;
}
util.inherits(SearchResponse, LDAPResult);
/**
* 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');
var self = this;
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');
var savedAttrs = {};
var all = (self.attributes.indexOf('*') !== -1);
Object.keys(entry.attributes).forEach(function (a) {
var _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) {
return;
} else if (self.attributes.length && self.attributes.indexOf(_a) === -1) {
savedAttrs[a] = entry.attributes[a];
delete entry.attributes[a];
}
});
var save = entry;
entry = new SearchEntry({
objectName: typeof (save.dn) === 'string' ? parseDN(save.dn) : save.dn,
messageID: self.messageID,
log: self.log
});
entry.fromObject(save);
}
try {
if (this.log.debug())
this.log.debug('%s: sending: %j', this.connection.ldap.id, entry.json);
this.connection.write(entry.toBer());
this.sentEntries++;
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-search-entry', function () {
var c = self.connection || {ldap: {}};
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
entry.objectName.toString(),
entry.attributes.length
];
});
}
// 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.json);
}
};
SearchResponse.prototype.createSearchEntry = function (object) {
assert.object(object);
var entry = new SearchEntry({
messageID: this.messageID,
log: this.log,
objectName: object.objectName || object.dn
});
entry.fromObject((object.attributes || object));
return entry;
};
SearchResponse.prototype.createSearchReference = function (uris) {
if (!uris)
throw new TypeError('uris ([string]) required');
if (!Array.isArray(uris))
uris = [uris];
for (var i = 0; i < uris.length; i++) {
if (typeof (uris[i]) == 'string')
uris[i] = parseURL(uris[i]);
}
var self = this;
return new SearchReference({
messageID: self.messageID,
log: self.log,
uris: uris
});
};
///--- Exports
module.exports = SearchResponse;
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var LDAPMessage = require('./message');
var dn = require('../dn');
var Protocol = require('../protocol');
///--- Globals
var DN = dn.DN;
var RDN = dn.RDN;
///--- API
function UnbindRequest(options) {
options = options || {};
assert.object(options);
options.protocolOp = Protocol.LDAP_REQ_UNBIND;
LDAPMessage.call(this, options);
}
util.inherits(UnbindRequest, LDAPMessage);
Object.defineProperties(UnbindRequest.prototype, {
type: {
get: function getType() { return 'UnbindRequest'; },
configurable: false
},
_dn: {
get: function getDN() {
if (this.connection) {
return this.connection.ldap.bindDN;
} else {
return new DN([new RDN({cn: 'anonymous'})]);
}
},
configurable: false
}
});
UnbindRequest.prototype._parse = function (ber) {
assert.ok(ber);
return true;
};
UnbindRequest.prototype._toBer = function (ber) {
assert.ok(ber);
return ber;
};
UnbindRequest.prototype._json = function (j) {
assert.ok(j);
return j;
};
///--- Exports
module.exports = UnbindRequest;
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var dtrace = require('../dtrace');
var LDAPMessage = require('./result');
var Protocol = require('../protocol');
///--- API
// Ok, so there's really no such thing as an unbind 'response', but to make
// the framework not suck, I just made this up, and have it stubbed so it's
// not such a one-off.
function UnbindResponse(options) {
options = options || {};
assert.object(options);
options.protocolOp = 0;
LDAPMessage.call(this, options);
}
util.inherits(UnbindResponse, LDAPMessage);
Object.defineProperties(UnbindResponse.prototype, {
type: {
get: function getType() { return 'UnbindResponse'; },
configurable: false
}
});
/**
* Special override that just ends the connection, if present.
*
* @param {Number} status completely ignored.
*/
UnbindResponse.prototype.end = function (status) {
assert.ok(this.connection);
this.log.trace('%s: unbinding!', this.connection.ldap.id);
this.connection.end();
var self = this;
if (self._dtraceOp && self._dtraceId) {
dtrace.fire('server-' + self._dtraceOp + '-done', function () {
var c = self.connection || {ldap: {}};
return [
self._dtraceId || 0,
(c.remoteAddress || ''),
c.ldap.bindDN ? c.ldap.bindDN.toString() : '',
(self.requestDN ? self.requestDN.toString() : ''),
0,
''
];
});
}
};
UnbindResponse.prototype._json = function (j) {
return j;
};
///--- Exports
module.exports = UnbindResponse;
+123
View File
@@ -0,0 +1,123 @@
///--- Globals
var parseDN = require('./dn').parse;
var 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');
var log = req.log;
var 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');
var log = req.log;
log.debug('%s removing client', req.logId);
var 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, callback) {
// if we want to return a ECNC
if (req.persistentSearch.value.returnECs) {
var attrs = obj.attributes;
var 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: value });
} else {
return false;
}
}
function checkChangeType(req, requestType) {
return (req.persistentSearch.value.changeTypes &
getOperationType(requestType));
}
///--- Exports
module.exports = {
PersistentSearchCache: PersistentSearch,
checkChangeType: checkChangeType,
getEntryChangeNotificationControl: getEntryChangeNotificationControl
};
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
module.exports = {
// Misc
LDAP_VERSION_3: 0x03,
LBER_SET: 0x31,
LDAP_CONTROLS: 0xa0,
// Search
SCOPE_BASE_OBJECT: 0,
SCOPE_ONE_LEVEL: 1,
SCOPE_SUBTREE: 2,
NEVER_DEREF_ALIASES: 0,
DEREF_IN_SEARCHING: 1,
DEREF_BASE_OBJECT: 2,
DEREF_ALWAYS: 3,
FILTER_AND: 0xa0,
FILTER_OR: 0xa1,
FILTER_NOT: 0xa2,
FILTER_EQUALITY: 0xa3,
FILTER_SUBSTRINGS: 0xa4,
FILTER_GE: 0xa5,
FILTER_LE: 0xa6,
FILTER_PRESENT: 0x87,
FILTER_APPROX: 0xa8,
FILTER_EXT: 0xa9,
// Protocol Operations
LDAP_REQ_BIND: 0x60,
LDAP_REQ_UNBIND: 0x42,
LDAP_REQ_SEARCH: 0x63,
LDAP_REQ_MODIFY: 0x66,
LDAP_REQ_ADD: 0x68,
LDAP_REQ_DELETE: 0x4a,
LDAP_REQ_MODRDN: 0x6c,
LDAP_REQ_COMPARE: 0x6e,
LDAP_REQ_ABANDON: 0x50,
LDAP_REQ_EXTENSION: 0x77,
LDAP_REP_BIND: 0x61,
LDAP_REP_SEARCH_ENTRY: 0x64,
LDAP_REP_SEARCH_REF: 0x73,
LDAP_REP_SEARCH: 0x65,
LDAP_REP_MODIFY: 0x67,
LDAP_REP_ADD: 0x69,
LDAP_REP_DELETE: 0x6b,
LDAP_REP_MODRDN: 0x6d,
LDAP_REP_COMPARE: 0x6f,
LDAP_REP_EXTENSION: 0x78
};
+917
View File
@@ -0,0 +1,917 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var net = require('net');
var tls = require('tls');
var util = require('util');
var asn1 = require('asn1');
var VError = require('verror').VError;
var dn = require('./dn');
var dtrace = require('./dtrace');
var errors = require('./errors');
var Protocol = require('./protocol');
var Parser = require('./messages').Parser;
var AbandonResponse = require('./messages/abandon_response');
var AddResponse = require('./messages/add_response');
var BindResponse = require('./messages/bind_response');
var CompareResponse = require('./messages/compare_response');
var DeleteResponse = require('./messages/del_response');
var ExtendedResponse = require('./messages/ext_response');
var LDAPResult = require('./messages/result');
var ModifyResponse = require('./messages/modify_response');
var ModifyDNResponse = require('./messages/moddn_response');
var SearchRequest = require('./messages/search_request');
var SearchResponse = require('./messages/search_response');
var UnbindResponse = require('./messages/unbind_response');
///--- Globals
var Ber = asn1.Ber;
var BerReader = asn1.BerReader;
var 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;
var handlers = [];
for (var i = start; i < end; i++) {
if (argv[i] instanceof Array) {
var arr = argv[i];
for (var j = 0; j < arr.length; j++) {
if (!(arr[j] instanceof Function)) {
throw new TypeError('Invalid argument type: ' + typeof (arr[j]));
}
handlers.push(arr[j]);
}
} else if (argv[i] instanceof Function) {
handlers.push(argv[i]);
} else {
throw new TypeError('Invalid argument type: ' + typeof (argv[i]));
}
}
return handlers;
}
function getResponse(req) {
assert.ok(req);
var Response;
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
Response = BindResponse;
break;
case Protocol.LDAP_REQ_ABANDON:
Response = AbandonResponse;
break;
case Protocol.LDAP_REQ_ADD:
Response = AddResponse;
break;
case Protocol.LDAP_REQ_COMPARE:
Response = CompareResponse;
break;
case Protocol.LDAP_REQ_DELETE:
Response = DeleteResponse;
break;
case Protocol.LDAP_REQ_EXTENSION:
Response = ExtendedResponse;
break;
case Protocol.LDAP_REQ_MODIFY:
Response = ModifyResponse;
break;
case Protocol.LDAP_REQ_MODRDN:
Response = ModifyDNResponse;
break;
case Protocol.LDAP_REQ_SEARCH:
Response = SearchResponse;
break;
case Protocol.LDAP_REQ_UNBIND:
Response = UnbindResponse;
break;
default:
return null;
}
assert.ok(Response);
var res = new Response({
messageID: req.messageID,
log: req.log,
attributes: ((req instanceof SearchRequest) ? req.attributes : undefined)
});
res.connection = req.connection;
res.logId = req.logId;
return res;
}
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();
}
function fireDTraceProbe(req, res) {
assert.ok(req);
req._dtraceId = res._dtraceId = dtrace._nextId();
var probeArgs = [
req._dtraceId,
req.connection.remoteAddress || 'localhost',
req.connection.ldap.bindDN.toString(),
req.dn.toString()
];
var op;
switch (req.protocolOp) {
case Protocol.LDAP_REQ_ABANDON:
op = 'abandon';
break;
case Protocol.LDAP_REQ_ADD:
op = 'add';
probeArgs.push(req.attributes.length);
break;
case Protocol.LDAP_REQ_BIND:
op = 'bind';
break;
case Protocol.LDAP_REQ_COMPARE:
op = 'compare';
probeArgs.push(req.attribute);
probeArgs.push(req.value);
break;
case Protocol.LDAP_REQ_DELETE:
op = 'delete';
break;
case Protocol.LDAP_REQ_EXTENSION:
op = 'exop';
probeArgs.push(req.name);
probeArgs.push(req.value);
break;
case Protocol.LDAP_REQ_MODIFY:
op = 'modify';
probeArgs.push(req.changes.length);
break;
case Protocol.LDAP_REQ_MODRDN:
op = 'modifydn';
probeArgs.push(req.newRdn.toString());
probeArgs.push((req.newSuperior ? req.newSuperior.toString() : ''));
break;
case Protocol.LDAP_REQ_SEARCH:
op = 'search';
probeArgs.push(req.scope);
probeArgs.push(req.filter.toString());
break;
case Protocol.LDAP_REQ_UNBIND:
op = 'unbind';
break;
default:
break;
}
res._dtraceOp = op;
dtrace.fire('server-' + op + '-start', function () {
return probeArgs;
});
}
///--- 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 = {};
}
var self = this;
EventEmitter.call(this, options);
this._chain = [];
this.log = options.log;
this.strictDN = (options.strictDN !== undefined) ? options.strictDN : true;
var 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;
}
var rdn = new dn.RDN({cn: 'anonymous'});
c.ldap = {
id: c.remoteAddress + ':' + c.remotePort,
config: options,
_bindDN: new DN([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 (had_err) {
log.trace('%s close; had_err=%j', c.ldap.id, had_err);
c.end();
});
c.ldap.__defineGetter__('bindDN', function () {
return c.ldap._bindDN;
});
c.ldap.__defineSetter__('bindDN', function (val) {
if (!(val instanceof DN))
throw new TypeError('DN required');
c.ldap._bindDN = val;
return val;
});
return c;
}
function newConnection(c) {
setupConnection(c);
log.trace('new connection from %s', c.ldap.id);
dtrace.fire('server-connection', function () {
return [c.remoteAddress];
});
c.parser = new Parser({
log: options.log
});
c.parser.on('message', function (req) {
req.connection = c;
req.logId = c.ldap.id + '::' + req.messageID;
req.startTime = new Date().getTime();
if (log.debug())
log.debug('%s: message received: req=%j', c.ldap.id, req.json);
var res = getResponse(req);
if (!res) {
log.warn('Unimplemented server method: %s', req.type);
c.destroy();
return false;
}
// parse string DNs for routing/etc
try {
switch (req.protocolOp) {
case Protocol.LDAP_REQ_BIND:
req.name = dn.parse(req.name);
break;
case Protocol.LDAP_REQ_ADD:
case Protocol.LDAP_REQ_COMPARE:
case Protocol.LDAP_REQ_DELETE:
req.entry = dn.parse(req.entry);
break;
case Protocol.LDAP_REQ_MODIFY:
req.object = dn.parse(req.object);
break;
case Protocol.LDAP_REQ_MODRDN:
req.entry = dn.parse(req.entry);
// TODO: handle newRdn/Superior
break;
case Protocol.LDAP_REQ_SEARCH:
req.baseObject = dn.parse(req.baseObject);
break;
default:
break;
}
} catch (e) {
if (self.strictDN) {
return res.end(errors.LDAP_INVALID_DN_SYNTAX);
}
}
res.connection = c;
res.logId = req.logId;
res.requestDN = req.dn;
var chain = self._getHandlerChain(req, res);
var i = 0;
return function (err) {
function sendError(err) {
res.status = err.code || errors.LDAP_OPERATIONS_ERROR;
res.matchedDN = req.suffix ? req.suffix.toString() : '';
res.errorMessage = err.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 (c) {
c.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 {
var next = arguments.callee;
if (chain.handlers[i])
return chain.handlers[i++].call(chain.backend, req, res, next);
if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0)
c.ldap.bindDN = req.dn;
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));
}
}();
});
c.parser.on('error', function (err, message) {
self.emit('error', new VError(err, 'Parser error for %s', c.ldap.id));
if (!message)
return c.destroy();
var res = getResponse(message);
if (!res)
return c.destroy();
res.status = 0x02; // protocol error
res.errorMessage = err.toString();
return c.end(res.toBer());
});
c.on('data', function (data) {
if (log.trace())
log.trace('data on %s: %s', c.ldap.id, util.inspect(data));
c.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, newConnection);
} else {
this.server = net.createServer(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() {
var str;
var addr = this.server.address();
if (!addr) {
return null;
}
if (!addr.family) {
str = 'ldapi://';
str += this.host.replace(new RegExp('/', 'g'), '%2f');
return str;
}
if (this.server instanceof tls.Server) {
str = 'ldaps://';
} else {
str = 'ldap://';
}
str += this.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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) {
var args = Array.prototype.slice.call(arguments, 1);
return this._mount(Protocol.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 () {
var args = Array.prototype.slice.call(arguments, 0);
return this._mount(Protocol.LDAP_REQ_UNBIND, 'unbind', args, true);
};
Server.prototype.use = function use() {
var args = Array.prototype.slice.call(arguments);
var chain = mergeFunctionArgs(args, 0, args.length);
var self = this;
chain.forEach(function (c) {
self._chain.push(c);
});
};
Server.prototype.after = function () {
if (!this._postChain)
this._postChain = [];
var self = this;
mergeFunctionArgs(arguments).forEach(function (h) {
self._postChain.push(h);
});
};
// All these just reexpose 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 = '0.0.0.0';
}
if (typeof (port) === 'string' && /^[0-9]+$/.test(port)) {
// Disambiguate between string ports and file paths
port = parseInt(port, 10);
}
var 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 () {
return this.server.close();
};
Server.prototype.address = function () {
return this.server.address();
};
Server.prototype._getRoute = function (_dn, backend) {
assert.ok(dn);
if (!backend)
backend = this;
var name;
if (_dn instanceof dn.DN) {
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) {
var self = this;
var reversedRDNsToKeys = {};
// Generate mapping of reversedRDNs(DN) -> routeKey
Object.keys(this.routes).forEach(function (key) {
var _dn = self.routes[key].dn;
// Ignore non-DN routes such as exop or unbind
if (_dn instanceof dn.DN) {
var reversed = _dn.clone();
reversed.rdns.reverse();
reversedRDNsToKeys[reversed.format()] = key;
}
});
var 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, res) {
assert.ok(req);
fireDTraceProbe(req, res);
// check anonymous bind
if (req.protocolOp === Protocol.LDAP_REQ_BIND &&
req.dn.toString() === '' &&
req.credentials === '') {
return {
backend: self,
handlers: [defaultNoOpHandler]
};
}
var op = '0x' + req.protocolOp.toString(16);
var self = this;
var routes = this.routes;
var route;
// Special cases are exops, unbinds and abandons. Handle those first.
if (req.protocolOp === Protocol.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.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.LDAP_REQ_ABANDON) {
return {
backend: self,
handlers: [defaultNoOpHandler]
};
}
// Otherwise, match via DN rules
assert.ok(req.dn);
var keys = this._sortedRouteKeys();
var fallbackHandler = [noSuffixHandler];
// invalid DNs in non-strict mode are routed to the default handler
var testDN = (typeof (req.dn) === 'string') ? '' : req.dn;
for (var i = 0; i < keys.length; i++) {
var 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');
var backend = this;
var index = 0;
if (typeof (argv[0]) === 'object' && !Array.isArray(argv[0])) {
backend = argv[0];
index = 1;
}
var route = this._getRoute(notDN ? name : dn.parse(name), backend);
var chain = this._chain.slice();
argv.slice(index).forEach(function (a) {
chain.push(a);
});
route['0x' + op.toString(16)] = mergeFunctionArgs(chain);
return this;
};
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2011 Mark Cavage, Inc. All rights reserved.
var querystring = require('querystring');
var url = require('url');
var util = require('util');
var dn = require('./dn');
var filter = require('./filters/index');
module.exports = {
parse: function (urlStr, parseDN) {
var u = url.parse(urlStr);
if (!u.protocol || !(u.protocol === 'ldap:' || u.protocol === 'ldaps:'))
throw new TypeError(urlStr + ' is an invalid LDAP url (protocol)');
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.parse(u.pathname) : u.pathname;
}
if (u.search) {
u.attributes = [];
var 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;
}
};
+121
View File
@@ -0,0 +1,121 @@
{
"_from": "ldapjs",
"_id": "ldapjs@1.0.2",
"_inBundle": false,
"_integrity": "sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk=",
"_location": "/ldapjs",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "ldapjs",
"name": "ldapjs",
"escapedName": "ldapjs",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-1.0.2.tgz",
"_shasum": "544ff7032b7b83c68f0701328d9297aa694340f9",
"_spec": "ldapjs",
"_where": "/home/erik/Documents/workspace_brackets/a1_BME_Project_Ohm/om",
"author": {
"name": "Mark Cavage",
"email": "mcavage@gmail.com"
},
"bin": {
"ldapjs-add": "bin/ldapjs-add",
"ldapjs-compare": "bin/ldapjs-compare",
"ldapjs-delete": "bin/ldapjs-delete",
"ldapjs-modify": "bin/ldapjs-modify",
"ldapjs-search": "bin/ldapjs-search"
},
"bugs": {
"url": "https://github.com/mcavage/node-ldapjs/issues"
},
"bundleDependencies": false,
"contributors": [
{
"name": "Craig Baker"
},
{
"name": "Austin King",
"email": "shout@ozten.com"
},
{
"name": "Mathieu Lecarme",
"email": "mathieu@garambrogne.net"
},
{
"name": "Trent Mick",
"email": "trentm@gmail.com"
},
{
"name": "Yunong Xiao",
"email": "yunong@joyent.com"
},
{
"name": "Denis Vuyka",
"email": "denis.vuyka@gmail.com"
},
{
"name": "Pedro Palazón",
"email": "kusorbox@gmail.com"
},
{
"name": "Patrick Mooney",
"email": "patrick.f.mooney@gmail.com"
},
{
"name": "Matt Simerson",
"email": "matt@tnpi.net"
}
],
"dependencies": {
"asn1": "0.2.3",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"bunyan": "^1.8.3",
"dashdash": "^1.14.0",
"dtrace-provider": "~0.8",
"ldap-filter": "0.2.2",
"once": "^1.4.0",
"vasync": "^1.6.4",
"verror": "^1.8.1"
},
"deprecated": false,
"description": "LDAP client and server APIs",
"devDependencies": {
"faucet": "0.0.1",
"istanbul": "^0.4.5",
"node-uuid": "^1.4.7",
"tape": "^4.6.2"
},
"directories": {
"bin": "./bin",
"lib": "./lib"
},
"engines": {
"node": ">=0.10"
},
"homepage": "http://ldapjs.org",
"license": "MIT",
"main": "lib/index.js",
"name": "ldapjs",
"optionalDependencies": {
"dtrace-provider": "~0.8"
},
"repository": {
"type": "git",
"url": "git://github.com/mcavage/node-ldapjs.git"
},
"scripts": {
"report": "istanbul report html && open ./coverage/lcov-report/index.html",
"test": "istanbul cover --print none test/test.js | ./node_modules/.bin/faucet"
},
"version": "1.0.2"
}