From 5836d8149eb2e4f66640c2307106d20e7b4a26a4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 2 Oct 2017 12:45:33 -0600 Subject: [PATCH] separate digd.js from dig.js --- .gitignore | 2 + .jshintrc | 16 ++ README.md | 68 +++++++++ bin/digd.js | 390 +++++++++++++++++++++++++++++++++++++++++++++++ lib/dns-store.js | 22 +++ package.json | 52 +++++++ 6 files changed, 550 insertions(+) create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100755 bin/digd.js create mode 100644 lib/dns-store.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97c3e07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.*.sw* diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..63801ce --- /dev/null +++ b/.jshintrc @@ -0,0 +1,16 @@ +{ "node": true +, "browser": true +, "jquery": true +, "strict": true +, "indent": 2 +, "onevar": true +, "laxcomma": true +, "laxbreak": true +, "eqeqeq": true +, "immed": true +, "undef": true +, "unused": true +, "latedef": true +, "curly": true +, "trailing": true +} diff --git a/README.md b/README.md index e69de29..84f96db 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,68 @@ +digd.js +======= + +| [dns-suite](https://git.daplie.com/Daplie/dns-suite) +| [dig.js](https://git.daplie.com/Daplie/dig.js) +| **digd.js** + +A lightweight DNS / mDNS daemon (server) for creating and capturing DNS and mDNS +query and response packets to disk as binary and/or JSON. +Options are similar to the Unix dig command. + +Install +------- + +### with git + +```bash +# Install the latest of v1.x +npm install -g 'git+https://git@git.daplie.com/Daplie/digd.js.git#v1' +``` + +```bash +# Install exactly v1.0.0 +npm install -g 'git+https://git@git.daplie.com/Daplie/digd.js.git#v1.0.0' +``` + +### without git + +Don't have git? Well, you can also bow down to the gods of the centralized, monopolized, concentrated, *dictator*net +(as we like to call it here at Daplie Labs), if that's how you roll: + +```bash +npm install -g digd.js +``` + +Usage +----- + +```bash +digd.js --input +``` + +**Example**: + +```bash +digd.js --input ./examples/example.com.json +``` + +Options +------- + +``` +--output write query and response(s) to disk with this path prefix (ex: ./samples/dns) +--input input file to use for authoritative responses (ex: ./samples/zones.json) + +--mdns Use mDNS port (5353) and nameserver address (224.0.0.251) + +-p default 53 (mdns default: 5353) (listener is random for DNS and 5353 for mDNS) +--nameserver alias of @ +--timeout alias of +time=, but in milliseconds + +@ specify the nameserver to use for recursive DNS resolutions (defaults to system defaults) ++time= Sets the timeout for a query in seconds. ++norecurse Set `ra` flag to 0. Do not perform recursion. ++aaonly Set `aa` flag to 1. Do not respond with non-authoritative responses. + +--debug verbose output +``` diff --git a/bin/digd.js b/bin/digd.js new file mode 100755 index 0000000..5c0f65b --- /dev/null +++ b/bin/digd.js @@ -0,0 +1,390 @@ +#!/usr/bin/env node + +'use strict'; + +var cli = require('cli'); +var pkg = require('../package.json'); +var dig = require('dig.js/dns-request'); +var dgram = require('dgram'); +var dnsjs = require('dns-suite'); +var crypto = require('crypto'); +var common = require('dig.js/common'); +var defaultNameservers = require('dns').getServers(); +var hexdump; + +cli.parse({ +// 'b': [ false, 'set source IP address (defaults to 0.0.0.0)', 'string' ] + 'class': [ 'c', 'class (defaults to IN)', 'string', 'IN' ] +, 'debug': [ false, 'more verbose output', 'boolean', false ] +//, 'insecure': [ false, 'turn off RaNDOm cAPS required for securing queries'] +//, 'ipv4': [ '4', 'use ipv4 exclusively (defaults to false)', 'boolean', false ] +//, 'ipv6': [ '6', 'use ipv6 exclusively (defaults to false)', 'boolean', false ] +//, 'json': [ false, 'output results as json', 'string' ] +//, 'lint': [ false, 'attack (in the metaphorical sense) a nameserver with all sorts of queries to test for correct responses', 'string', false ] +, 'mdns': [ false, "Alias for setting defaults to -p 5353 @224.0.0.251 -t PTR -q _services._dns-sd._udp.local and waiting for multiple responses", 'boolean', false ] +, 'timeout': [ false, "How long, in milliseconds, to wait for a response. Alias of +time=", 'int', false ] +, 'output': [ 'o', 'output prefix to use for writing query and response(s) to disk', 'file' ] +, 'input': [ false, 'input file to use for authoritative responses', 'file' ] +, 'address': [ false, 'ip address(es) to listen on (defaults to 0.0.0.0,::0)', 'string' ] +, 'port': [ 'p', 'port (defaults to 53 for dns and 5353 for mdns)', 'int' ] +, 'nameserver': [ false, 'the nameserver(s) to use for recursive lookups (defaults to ' + defaultNameservers.join(',') + ')', 'string' ] +//, 'serve': [ 's', 'path to json file with array of responses to issue for given queries', 'string' ] +//, 'type': [ 't', 'type (defaults to ANY for dns and PTR for mdns)', 'string' ] +//, 'query': [ 'q', 'a superfluous explicit option to set the query as a command line flag' ] +}); + +cli.main(function (args, cli) { + args.forEach(function (arg) { + if (arg === '+norecurse') { + if (cli.norecurse) { + console.error("'+norecurse' was specified more than once"); + process.exit(1); + return; + } + cli.norecurse = true; + return; + } + }); + + if (cli.mdns) { + if (!cli.type) { + cli.type = cli.t = 'PTR'; + } + if (!cli.port) { + cli.port = cli.p = 5353; + } + if (!cli.nameserver) { + cli.nameserver = '224.0.0.251'; + } + if (!cli.query) { + cli.query = '_services._dns-sd._udp.local'; + } + if (!('timeout' in cli)) { + cli.timeout = 3000; + } + } else { + if (!cli.port) { + cli.port = cli.p = 53; + } + } + + var handlers = {}; + var server = dgram.createSocket({ + type: cli.udp6 ? 'udp6' : 'udp4' + , reuseAddr: true + }); + server.bind({ + port: cli.port + , address: cli.address + }); + + handlers.onError = function (err) { + if ('EACCES' === err.code) { + console.error(""); + console.error("EACCES: Couldn't bind to port. You probably need to use sudo, authbind, or setcap."); + console.error(""); + process.exit(123); + return; + } + console.error("error:", err.stack); + server.close(); + }; + + handlers.onMessage = function (nb, rinfo) { + var queryAb = nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength); + var query; + var count; + + try { + query = dnsjs.DNSPacket.parse(queryAb); + } catch(e) { + // TODO log bad queries (?) + console.error("Could not parse DNS query, ignoring."); + try { + hexdump = require('hexdump.js').hexdump; + console.error(hexdump(queryAb)); + console.error(''); + } catch(e) { + // ignore + } + return; + } + + if (cli.debug) { + console.log(''); + console.log('DNS Question:'); + console.log(''); + console.log(query); + console.log(''); + try { + hexdump = require('hexdump.js').hexdump; + console.log(hexdump(queryAb)); + console.log(''); + } catch(e) { + // ignore + } + } + + dig.logQuestion(query); + /* + console.log(';; Got question:'); + console.log(';; ->>HEADER<<-'); + console.log(JSON.stringify(query.header)); + console.log(''); + console.log(';; QUESTION SECTION:'); + query.question.forEach(function (q) { + console.log(';' + q.name + '.', ' ', q.className, q.typeName); + }); + */ + + function print(q) { + var printer = common.printers[q.typeName] || common.printers.ANY; + printer(q); + } + if (query.answer.length) { + console.error('[ERROR] Query contains an answer section:'); + console.log(';; ANSWER SECTION:'); + query.answer.forEach(print); + } + if (query.authority.length) { + console.log(''); + console.error('[ERROR] Query contains an authority section:'); + console.log(';; AUTHORITY SECTION:'); + query.authority.forEach(print); + } + if (query.additional.length) { + console.log(''); + console.error('[ERROR] Query contains an additional section:'); + console.log(';; ADDITIONAL SECTION:'); + query.additional.forEach(print); + } + console.log(''); + console.log(';; MSG SIZE rcvd: ' + nb.byteLength); + console.log(''); + + if (cli.output) { + console.log(''); + common.writeQuery(cli, query, queryAb); + //common.writeResponse(opts, query, nb, packet); + } + + function sendEmptyResponse(query, nx) { + var newAb; + var emptyResp = { + header: { + id: query.header.id // require('crypto').randomBytes(2).readUInt16BE(0) + , qr: 1 + , opcode: 0 + , aa: 0 // TODO it may be authoritative + , tc: 0 + , rd: query.header.rd + , ra: cli.norecurse ? 0 : 1 // TODO is this bit dependent on the rd bit? + , rcode: nx ? 3 : 0 // no error + } + , question: [] + , answer: [] + , authority: [] + , additional: [] + }; + query.question.forEach(function (q) { + emptyResp.question.push({ + name: q.name + , type: q.type + , typeName: q.typeName + , class: q.class + , className: q.className + }); + }); + + try { + newAb = dnsjs.DNSPacket.write(emptyResp); + } catch(e) { + console.error("Could not write DNS response"); + console.error(emptyResp); + return; + } + + server.send(newAb, rinfo.port, rinfo.address, function () { + console.log('[DEV] response sent (empty)'); + }); + } + + function sendResponse(newPacket) { + var newAb; + + try { + newAb = dnsjs.DNSPacket.write(newPacket); + } catch(e) { + console.error("Could not write DNS response"); + console.error(newPacket); + return; + } + + server.send(newAb, rinfo.port, rinfo.address, function () { + console.log('[DEV] response sent (local query)'); + }); + } + + function recurse() { + if (!query.header.rd) { + console.log("[Could not answer. Sent empty response.]"); + sendEmptyResponse(query, true); + return; + } + + if (cli.norecurse) { + console.log("[Could not answer. Sent empty response.]"); + sendEmptyResponse(query, true); + return; + } + + // TODO newQuery + + var newResponse = { + header: { + id: query.header.id // require('crypto').randomBytes(2).readUInt16BE(0) + , qr: 0 + , opcode: 0 + , aa: query.header.aa ? 1 : 0 // NA? not sure what this would do + , tc: 0 // NA + , rd: 1 + , ra: 0 // NA + , rcode: 0 // NA + } + , question: [] + , answer: [] + , authority: [] + , additional: [] + }; + query.question.forEach(function (q) { + newResponse.question.push({ + name: q.name + , type: q.type + , typeName: q.typeName + , class: q.class + , className: q.className + }); + + function updateCount() { + var newAb; + count -= 1; + + if (!count) { + try { + newAb = dnsjs.DNSPacket.write(newResponse); + } catch(e) { + console.error("Could not write DNS response"); + console.error(newResponse); + return; + } + + server.send(newAb, rinfo.port, rinfo.address, function () { + console.log('[DEV] response sent'); + }); + } + } + + var opts = { + onError: function () { + updateCount(); + } + , onMessage: function (packet) { + + (packet.answer||[]).forEach(function (a) { + // TODO copy each relevant property + console.log('ans', JSON.stringify(a, null, 2)); + newResponse.answer.push(a); + }); + (packet.authority||[]).forEach(function (a) { + // TODO copy each relevant property + console.log('auth', JSON.stringify(a, null, 2)); + newResponse.authority.push(a); + }); + (packet.additional||[]).forEach(function (a) { + // TODO copy each relevant property + console.log('add', JSON.stringify(a, null, 2)); + newResponse.additional.push(a); + }); + + updateCount(); + + } + , onListening: function () {} + , onSent: function (/*res*/) { + /* + if (cli.debug) { + console.log(''); + console.log('request sent to', res.nameserver); + } + */ + console.log('[DEV] response sent (recurse)'); + } + , onTimeout: function (res) { + console.log(";; [" + q.name + "] connection timed out; no servers could be reached"); + console.log(";; [timed out after " + res.timeout + "ms and 1 tries]"); + } + , onClose: function () { + console.log(''); + } + , mdns: cli.mdns + , nameserver: cli.nameserver + , port: cli.port + , timeout: cli.timeout + }; + + //dig.resolve(queryAb, opts); + dig.resolveJson(query, opts); + + console.log(';' + q.name + '.', ' ', q.className, q.typeName); + }); + } + + count = query.question.length; + if (!count) { + sendEmptyResponse(query); + return; + } + + // TODO get local answer first, if available + require('../lib/dns-store').query(cli.input, query, function (err, resp) { + if (err) { recurse(); return; } + + sendResponse(resp); + }); + + }; + + handlers.onListening = function () { + /*jshint validthis:true*/ + var server = this; + var nameserver = cli.nameserver; + var index; + + if (!nameserver) { + index = crypto.randomBytes(2).readUInt16BE(0) % defaultNameservers.length; + nameserver = defaultNameservers[index]; + if (cli.debug) { + console.log(index, defaultNameservers); + } + } + + if (cli.mdns || '224.0.0.251' === cli.nameserver) { + server.setBroadcast(true); + server.addMembership(cli.nameserver); + } + + console.log(''); + console.log('Bound and Listening:'); + console.log(server.address().address + '#' + server.address().port); + }; + + console.log(''); + if (!cli.nocmd) { + console.log('; <<>> digd.js v' + pkg.version + ' <<>> ' + process.argv.slice(2)); + console.log(';; global options: +cmd'); + } + + server.on('error', handlers.onError); + server.on('message', handlers.onMessage); + server.on('listening', handlers.onListening); +}); diff --git a/lib/dns-store.js b/lib/dns-store.js new file mode 100644 index 0000000..e0b9050 --- /dev/null +++ b/lib/dns-store.js @@ -0,0 +1,22 @@ +(function () { +'use strict'; + +/* +var fs = require('fs'); + +module.exports.ask = function (query, cb) { +}; +*/ + +module.exports.query = function (input, query, cb) { + process.nextTick(function () { + cb(new Error('No local lookup method for DNS records defined.')); + }); + /* + query.question.forEach(function (q) { + module.exports.ask(q); + }); + */ +}; + +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..ed9b019 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "digd.js", + "version": "1.0.0", + "description": "A lightweight DNS / mDNS daemon (server) for creating and capturing DNS and mDNS query and response packets to disk as binary and/or JSON. Options are similar to the Unix dig command.", + "main": "bin/digd.js", + "bin": { + "digd.js": "bin/digd.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.daplie.com:Daplie/digd.js.git" + }, + "keywords": [ + "mdig", + "mdigd", + "multicast", + "debugging", + "debug", + "cli", + "command", + "line", + "dig", + "DNS", + "mDNS", + "daemon", + "server", + "js", + "javascript", + "node", + "node.js", + "53", + "5353", + "binary", + "bin", + "capture", + "create", + "parse", + "pack", + "json", + "224.0.0.251", + "lint" + ], + "author": "AJ ONeal (https://coolaj86.com)", + "license": "MIT OR Apache-2.0", + "dependencies": { + "dig.js": "^1.2.1", + "hexdump.js": "^1.0.4" + } +}