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