node_modules updated, buefy add to lib folder
This commit is contained in:
+6
@@ -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
@@ -0,0 +1,7 @@
|
||||
.gitmodules
|
||||
Makefile
|
||||
deps
|
||||
docs
|
||||
test
|
||||
tools
|
||||
coverage
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4"
|
||||
- "stable"
|
||||
+100
@@ -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
@@ -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
@@ -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>.
|
||||
+171
@@ -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; });
|
||||
});
|
||||
});
|
||||
+167
@@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
+148
@@ -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);
|
||||
});
|
||||
});
|
||||
+185
@@ -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);
|
||||
});
|
||||
});
|
||||
+326
@@ -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
@@ -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);
|
||||
});
|
||||
+24
@@ -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
Binary file not shown.
+61
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+56
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
+103
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user