#!/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 ');
}
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 ',
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;
});
});
});
});