Compare commits

...

74 Commits

Author SHA1 Message Date
AJ ONeal
25d150de07 update 2018-03-20 20:12:32 -06:00
AJ ONeal
90ed10c129 update 2018-03-20 20:11:57 -06:00
AJ ONeal
3bf715998d v1.3.8 2017-12-15 01:58:30 -07:00
AJ ONeal
6e7a0c57c8 allow empty string for query 2017-12-15 01:54:39 -07:00
AJ ONeal
22f5297582 bump 2017-11-04 21:19:58 -06:00
AJ ONeal
79154b093d add standard files 2017-11-04 21:12:20 -06:00
AJ ONeal
9ad274a0bb add mdig.js to bar 2017-11-02 23:47:58 -06:00
AJ ONeal
56048ad5d2 v1.3.6 2017-10-28 22:03:12 -06:00
AJ ONeal
39ba065ce0 Merge branch 'master' of ssh://git.coolaj86.com:22042/coolaj86/dig.js 2017-10-28 22:03:04 -06:00
AJ ONeal
d5f5267c18 v1.3.5 2017-10-28 22:02:40 -06:00
AJ ONeal
3323379194 update urls 2017-10-28 22:02:27 -06:00
6ef8c7a475 Update 'README.md' 2017-10-29 03:40:05 +00:00
AJ ONeal
f441c0cfc5 v1.3.4 2017-10-28 02:14:47 -06:00
AJ ONeal
7bba8f18e9 Merge branch 'master' into v1.3 2017-10-27 23:50:55 -06:00
AJ ONeal
875d288db3 whitespace 2017-10-27 23:50:37 -06:00
AJ ONeal
c6ba3ccde6 update urls 2017-10-27 23:43:59 -06:00
AJ ONeal
1b79eb262f update urls 2017-10-27 23:41:34 -06:00
AJ ONeal
9373336675 add BUGS 2017-10-23 22:39:15 -06:00
AJ ONeal
e279f753f8 v1.3.3 2017-10-23 20:53:19 -06:00
AJ ONeal
17b4d6d57f update git dependencies 2017-10-23 20:53:00 -06:00
AJ ONeal
44b4801ef6 use status codes by name 2017-10-18 15:26:07 -06:00
AJ ONeal
893574a3c2 Merge branch 'master' into v1 2017-10-09 15:11:42 -06:00
AJ ONeal
4c85be0ebf catch and report parse / pack errors 2017-10-09 15:11:32 -06:00
AJ ONeal
aba58292ee Merge branch 'v1' of git.daplie.com:Daplie/dig.js into v1 2017-10-09 14:46:54 -06:00
AJ ONeal
b6bc592e56 add type<num> support, fix recasing bug 2017-10-09 14:45:07 -06:00
AJ ONeal
b3d7408db4 fix security check on id, note security concerns 2017-10-06 18:42:37 -06:00
AJ ONeal
6287f13f2b fix #4 don't force login in url 2017-10-06 10:25:29 -06:00
AJ ONeal
1533576023 v1.3.2 2017-10-02 17:11:03 -06:00
AJ ONeal
76fee917ea use lastIndexOf 2017-10-02 17:06:15 -06:00
AJ ONeal
a7ebc7ce86 v1.3.1 2017-10-02 16:56:39 -06:00
AJ ONeal
f8b2fb7ff8 fix false positive on 0x20 failure 2017-10-02 16:56:31 -06:00
AJ ONeal
e1d0322ed2 v1.3.0 2017-10-02 16:50:47 -06:00
AJ ONeal
08c3791bec v1.2.3 2017-10-02 16:50:31 -06:00
AJ ONeal
510f8b93e7 made dns0x20 warning more prominent 2017-10-02 16:50:22 -06:00
AJ ONeal
4e0a37c0f5 check dns0x20 support by default 2017-10-02 16:43:58 -06:00
AJ ONeal
7bb2e84486 v1.2.2 2017-10-02 12:49:28 -06:00
AJ ONeal
0bf55e7589 move digd.js to own repo 2017-10-02 12:49:20 -06:00
AJ ONeal
51b05e9860 Update README.md 2017-10-02 11:52:50 -06:00
AJ ONeal
1b182f7c2f Update README.md 2017-10-02 11:50:02 -06:00
AJ ONeal
09d95fd88c Update README.md 2017-10-02 11:46:55 -06:00
AJ ONeal
0c71c39dc1 Update README.md 2017-10-02 11:30:32 -06:00
AJ ONeal
fcaafbf8b9 WIP dns server 2017-09-25 18:14:27 -06:00
AJ ONeal
083df5755b small refactor 2017-09-25 15:12:58 -06:00
AJ ONeal
6d0f9b1588 WIP dns server, mov hexdump.js to own repo, add aaonly flag 2017-09-25 14:43:21 -06:00
AJ ONeal
cdd490ec42 factor out printing of header and query 2017-09-20 13:27:53 -06:00
AJ ONeal
57ced95c0d add onClose event 2017-09-20 12:50:32 -06:00
AJ ONeal
f979638090 Merge branch 'master' of git.daplie.com:Daplie/dig.js 2017-09-20 12:29:54 -06:00
AJ ONeal
03e3e527dc WIP recursively resolve 2017-09-20 12:29:43 -06:00
AJ ONeal
de6fe5039b copy dig timeout message 2017-09-20 12:18:25 -06:00
AJ ONeal
36d7aaccbb udp6 and timeout fix 2017-09-18 18:06:27 -06:00
AJ ONeal
2f318607a1 factor out dns query 2017-09-18 17:40:11 -06:00
AJ ONeal
5dae97b60d add .jshintrc 2017-09-15 18:43:18 -06:00
AJ ONeal
01e753996a [WIP] began dns server 2017-09-15 18:43:02 -06:00
AJ ONeal
51daf2378d updated formatting, small refactoring 2017-09-15 18:42:32 -06:00
Tim Caswell
0cabe30b88 Fix dependency syntax and bump last version digit 2017-08-15 16:04:31 -05:00
Tim Caswell
c7668e7381 Bump minor version to v1.2.0 and update dns-suite dependency 2017-04-13 15:28:42 -05:00
Tim Caswell
f65cd74ea3 Add CAA to printer 2017-04-12 13:11:55 -05:00
AJ ONeal
e3ccb16e42 v1.1.0 2017-03-31 00:08:51 -06:00
AJ ONeal
11677feb12 v1.0.9 2017-03-31 00:08:23 -06:00
AJ ONeal
ce84e4262d add digd.js 2017-03-31 00:08:15 -06:00
AJ ONeal
8c3126a2ac better debug output for nameserver 2017-03-31 00:03:46 -06:00
AJ ONeal
d225cfa5c6 fix nameserver (by removing prefix) 2017-03-31 00:02:49 -06:00
AJ ONeal
a41b3f8f3f small refactor 2017-03-30 23:19:52 -06:00
AJ ONeal
bf4e6398c7 v1.0.8 2017-03-30 19:29:16 -06:00
AJ ONeal
6b5ed74500 add keywords 2017-03-30 19:29:01 -06:00
AJ ONeal
97cc770a2e allow 'ANY' record 2017-03-30 19:28:32 -06:00
AJ ONeal
e440c0069f bugfix no short string for debug 2017-02-25 14:05:29 -07:00
AJ ONeal
7d869e5f8c update branches from least to greatest 2017-02-25 13:19:00 -07:00
AJ ONeal
234bfabe96 v1.0.7 2017-02-25 13:17:36 -07:00
AJ ONeal
af6a208708 add urls 2017-02-25 13:17:34 -07:00
AJ ONeal
8e6b16c257 v1.0.6 2017-02-25 13:16:09 -07:00
AJ ONeal
4ff19652b0 add urls 2017-02-25 13:16:07 -07:00
AJ ONeal
a5d6fffa26 v1.0.5 2017-02-25 13:05:17 -07:00
AJ ONeal
c428a89fb1 bugfix in parser for query 2017-02-25 13:05:07 -07:00
10 changed files with 635 additions and 298 deletions

16
.jshintrc Normal file
View File

@ -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
}

1
BUGS.txt Normal file
View File

@ -0,0 +1 @@
Currently allows labels to be terminated by rdata length. This was a mistake. They should always be terminated by a null character.

6
CHANGELOG Normal file
View File

@ -0,0 +1,6 @@
v1.3.6 - A suitable replacement for most of my uses for big
* Can capture dns packets in binary and JSON
* Parses common record types including:
* A,AAAA,CAA,CNAME,MX,NS,PTR,SOA,SRV,TXT
* Arbitrary TYPExxx support
* Known Bug: should error when label in rdata is not null terminated

41
LICENSE Normal file
View File

@ -0,0 +1,41 @@
Copyright 2017 AJ ONeal
This is open source software; you can redistribute it and/or modify it under the
terms of either:
a) the "MIT License"
b) the "Apache-2.0 License"
MIT License
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.
Apache-2.0 License Summary
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,27 +1,34 @@
dig.js
======
Create and capture DNS and mDNS query and response packets to disk as binary and/or JSON.
Options are similar to the Unix `dig` command.
| [dns-suite](https://git.coolaj86.com/coolaj86/dns-suite)
| **dig.js**
| [mdig.js](https://git.coolaj86.com/coolaj86/mdig.js)
| [digd.js](https://git.coolaj86.com/coolaj86/digd.js)
| Sponsored by [ppl](https://ppl.family)[.](https://dapliefounder.com)
Install with git
Create and capture DNS and mDNS query and response packets to disk as binary and/or JSON.
Options are similar to the Unix `dig` command. Supports dns0x20 security checking.
Install
-------
### with git
```bash
# Install the latest of v1.x
npm install -g 'git+https://git@git.daplie.com/Daplie/dig.js.git#v1'
npm install -g 'git+https://git.coolaj86.com/coolaj86/dig.js.git#v1'
```
```bash
# Install exactly v1.0.0
npm install -g 'git+https://git@git.daplie.com/Daplie/dig.js.git#v1.0.0'
npm install -g 'git+https://git.coolaj86.com/coolaj86/dig.js.git#v1.0.0'
```
Install without git
-------
### 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:
(as we like to call it here at ppl Labs), if that's how you roll:
```bash
npm install -g dig.js
@ -30,16 +37,14 @@ npm install -g dig.js
Usage
-----
### Format
```bash
dig.js [TYPE] <domainname>
```
### Example
**Example**:
```bash
dig.js daplie.com
dig.js coolaj86.com
```
### mDNS Browser Example
@ -59,24 +64,40 @@ dig.js -p 5353 @224.0.0.251 PTR _services._dns-sd._udp.local +time=3
### Moar Examples
```bash
dig.js A daplie.com
dig.js A coolaj86.com
dig.js -t A daplie.com
dig.js @8.8.8.8 A daplie.com
dig.js @8.8.8.8 A coolaj86.com
```
Options
-------
```
--debug
--mdns
--output <path/to/file> write query and response(s) to disk with this path prefix (ex: ./samples/dns)
-t <type> (superfluous) default ANY (mdns default: PTR)
--mdns Use mDNS port and nameserver address, and listen for multiple packets
-t <type> (superfluous) A, CNAME, MX, etc. Also supports -t type<decimal> for "unsupported" types. default ANY (mdns default: PTR)
-c <class> default IN
-p <port> default 53 (mdns default: 5353) (listener is random for DNS and 5353 for mDNS)
-q <query> (superfluous) required (ex: daplie.com)
-q <query> (superfluous) required (ex: coolaj86.com)
--nameserver <ns> alias of @<nameserver>
--timeout <ms> alias of +time=<seconds>, but in milliseconds
@<nameserver> specify the nameserver to use for DNS resolution (defaults to system defaults)
+time=<seconds> Sets the timeout for a query in seconds.
+norecurse Set `rd` flag to 0. Do not request recursion
+aaonly Set `aa` flag to 1.
--norecase Disable dns0x20 security checking (mixed casing). See https://dyn.com/blog/use-of-bit-0x20-in-dns-labels/
--recase Print the dns0x20 casing as-is rather than converting it back to lowercase. This is the default when explicitly using mixed case.
--debug verbose output
```
Security Concerns
-----------------
The 16-bit `id` of the query must match that of the response.
Extra entropy is added by using `dns0x20`, the de facto standard for RanDOmCASiNg on the query which must be matched in the response.

View File

@ -1,140 +1,193 @@
#!/usr/bin/env node
'use strict';
var dnsjs = require('dns-suite');
var dig = require('../dns-request');
var cli = require('cli');
var defaultNameservers = require('dns').getServers();
var typeRe = /^type\d+$/i;
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 ]
, '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, "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' ]
, 'port': [ 'p', 'port (defaults to 53 for dns and 5353 for mdns)', 'int' ]
, 'nameserver': [ false, 'the nameserver to use for DNS resolution (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' ]
, 'query': [ 'q', 'a superfluous explicit option to set the query as a command line flag', 'string' ]
, 'norecase': [ false, 'Disable dns0x20 security checking (mixed casing). See https://dyn.com/blog/use-of-bit-0x20-in-dns-labels/' ]
, 'recase': [ false, "Print the dns0x20 casing as-is rather than converting it back to lowercase. This is the default when explicitly using mixed case." ]
});
var fs = require('fs');
var dgram = require('dgram');
var commonTypes = [ 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT' ];
var commonPrinters = {
'ANY': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data || q.rdata || 'unknown record type');
}
var common = require('../common.js');
, 'A': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.address);
}
, 'AAAA': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.address);
}
, 'CNAME': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data + '.');
}
, 'MX': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.priority + ' ' + q.exchange + '.');
}
, 'NS': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data);
}
, 'PTR': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data);
}
/*
, 'SOA': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data);
}
*/
, 'SRV': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.priority + ' ' + q.weight + ' ' + q.port + ' ' + q.target);
}
, 'TXT': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, '"' + q.data.join('" "') + '"');
}
};
function hexdump(ab) {
var ui8 = new Uint8Array(ab);
var bytecount = 0;
var head = ' 0 1 2 3 4 5 6 7 8 9 A B C D E F';
var trail;
var str = [].slice.call(ui8).map(function (i) {
var h = i.toString(16);
if (h.length < 2) {
h = '0' + h;
}
return h;
}).join('').match(/.{1,2}/g).join(' ').match(/.{1,48}/g).map(function (str) {
var lead = bytecount.toString(16);
bytecount += 16;
while (lead.length < 7) {
lead = '0' + lead;
cli.main(function (args, cli) {
cli.implicitType = cli.type;
cli.implicitQuery = cli.query;
args.forEach(function (arg) {
if (typeRe.test(arg) || -1 !== common.types.concat([ 'ANY' ]).indexOf(arg.toUpperCase())) {
if (cli.implicitType) {
console.error("'type' was specified more than once");
process.exit(1);
return;
}
cli.implicitType = cli.t = arg.toUpperCase();
return;
}
return lead + ' ' + str;
}).join('\n');
trail = ab.byteLength.toString(16);
while (trail.length < 7) {
trail = '0' + trail;
}
return head + '\n' + str + '\n' + trail;
}
if (arg === '+aaonly' || arg === '+aaflag') {
if (cli.aaonly) {
console.error("'+aaonly' was specified more than once");
process.exit(1);
return;
}
cli.aaonly = true;
return;
}
function writeQuery(opts, query, queryAb) {
var path = require('path');
var binname = query.question[0].name + '.' + query.question[0].typeName.toLowerCase() + '.query.bin';
var jsonname = query.question[0].name + '.' + query.question[0].typeName.toLowerCase() + '.query.json';
var binpath = opts.output + '.' + binname;
var jsonpath = opts.output + '.' + jsonname;
var json = JSON.stringify(query, null, 2);
if (-1 !== ['.', '/', '\\' ].indexOf(opts.output[opts.output.length -1])) {
binpath = path.join(opts.output, binname);
jsonpath = path.join(opts.output, jsonname);
if (arg === '+norecurse') {
if (cli.norecurse) {
console.error("'+norecurse' was specified more than once");
process.exit(1);
return;
}
cli.norecurse = true;
return;
}
if (/^\+time=/.test(arg)) {
if (cli.timeout) {
console.error("'+time=' was specified more than once");
process.exit(1);
return;
}
cli.timeout = Math.round(parseInt(arg.replace(/\+time=/, ''), 10) * 1000);
return;
}
if (/^@/.test(arg)) {
if (cli.nameserver) {
console.error("'@server' was specified more than once");
process.exit(1);
return;
}
cli.nameserver = cli.n = arg.substr(1);
return;
}
if ('string' === typeof cli.implicitQuery) {
console.error("'query' was specified more than once or unrecognized flag: " + cli.implicitQuery + ", " + arg);
process.exit(1);
return;
}
cli.implicitQuery = cli.q = arg;
});
// it can happen that a TLD is created with the name of a common type
if (!cli.type && cli.implicitType && !cli.implicitQuery) {
cli.implicitQuery = cli.implicitType;
cli.implicitType = null;
}
if ('string' === typeof cli.implicitQuery) {
cli.query = cli.implicitQuery;
}
if (cli.implicitType) {
cli.type = cli.implicitType;
}
if ('string' !== typeof cli.query) {
console.error('');
console.error('Usage:');
console.error('dig.js [@server] [TYPE] [domain]');
console.error('');
console.error('Example:');
console.error('dig.js daplie.com');
console.error('');
process.exit(1);
}
if (cli.query !== cli.query.toLowerCase()) {
cli.norecase = true;
}
fs.writeFile(binpath, Buffer.from(queryAb), null, function () {
console.log('wrote ' + queryAb.byteLength + ' bytes to ' + binpath);
});
fs.writeFile(jsonpath, json, null, function () {
console.log('wrote ' + json.length + ' bytes to ' + jsonpath);
});
}
var count = 0;
function writeResponse(opts, query, nb, packet) {
var path = require('path');
var binname = query.question[0].name + '.' + query.question[0].typeName.toLowerCase() + '.' + count + '.bin';
var jsonname = query.question[0].name + '.' + query.question[0].typeName.toLowerCase() + '.' + count + '.json';
var binpath = opts.output + '.' + binname;
var jsonpath = opts.output + '.' + jsonname;
var json = JSON.stringify(packet, null, 2);
if (-1 !== ['.', '/', '\\' ].indexOf(opts.output[opts.output.length -1])) {
binpath = path.join(opts.output, binname);
jsonpath = path.join(opts.output, jsonname);
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 ('string' !== typeof cli.query) {
cli.query = '_services._dns-sd._udp.local';
}
if (!cli.timeout) {
cli.timeout = 3000;
}
} else {
if (!cli.timeout) {
cli.timeout = 5000;
}
}
count += 1;
if (!cli.norecase) {
cli.casedQuery = cli.query.split('').map(function (ch) {
// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
// ch = ch | 0x20;
return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase();
}).join('');
} else {
cli.casedQuery = cli.query;
}
fs.writeFile(binpath, nb, null, function () {
console.log('wrote ' + nb.byteLength + ' bytes to ' + binpath);
});
fs.writeFile(jsonpath, json, null, function () {
console.log('wrote ' + json.length + ' bytes to ' + jsonpath);
});
}
if (!cli.type) {
cli.type = cli.t = 'ANY';
}
if (typeRe.test(cli.type)) {
cli.rawType = parseInt(cli.type.replace('type', ''), 10);
}
if (!cli.port) {
cli.port = cli.p = 53;
}
if (!cli.class) {
cli.class = cli.c = 'IN';
}
function request(query, opts) {
var query = {
header: {
id: require('crypto').randomBytes(2).readUInt16BE(0)
, qr: 0
, opcode: 0
, aa: cli.aaonly ? 1 : 0 // NA
, tc: 0 // NA
, rd: cli.norecurse ? 0 : 1
, ra: 0 // NA
, rcode: 0 // NA
}
, question: [
{ name: cli.casedQuery
, type: cli.rawType
, typeName: cli.rawType ? undefined : cli.type
, className: cli.class
}
]
};
var dnsjs = require('dns-suite');
var queryAb = dnsjs.DNSPacket.write(query);
var hexdump = require('hexdump.js').hexdump;
if (opts.debug) {
if (cli.debug) {
console.log('');
console.log('DNS Question:');
console.log('');
@ -146,45 +199,73 @@ function request(query, opts) {
console.log('');
}
var handlers = {};
var server = dgram.createSocket({
type: 'udp4'
, reuseAddr: true
});
handlers.onError = function (err) {
console.error("error:", err.stack);
server.close();
cli.onError = function (err) {
console.error("error:", err.stack);
};
handlers.onMessage = function (nb) {
cli.onMessage = function (nb) {
var packet = dnsjs.DNSPacket.parse(nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength));
var fail0x20;
if (packet.id !== query.id) {
console.log('ignoring packet for ', packet.question[0].name);
console.error('[SECURITY] ignoring packet for \'' + packet.question[0].name + '\' due to mismatched id');
console.error(packet);
return;
}
if (!opts.mdns) {
server.close();
}
if (opts.debug) {
if (cli.debug) {
console.log('');
console.log('DNS Response:');
console.log(packet);
}
console.log('');
console.log('; <<>> dig.js ' + 'v0.0.0' + ' <<>> ' + query.question[0].name);
console.log(';; Got answer:');
console.log(';; ->>HEADER<<-');
console.log(JSON.stringify(packet.header));
console.log('');
console.log(';; QUESTION SECTION:');
packet.question.forEach(function (q) {
console.log(';' + q.name + '.', ' ', q.className, q.typeName);
// if (-1 === q.name.lastIndexOf(cli.casedQuery))
if (q.name !== cli.casedQuery) {
fail0x20 = q.name;
}
});
if (!cli.norecase && !cli.recase) {
[ 'question', 'answer', 'authority', 'additional' ].forEach(function (group) {
(packet[group]||[]).forEach(function (a) {
var an = a.name;
var i = cli.query.toLowerCase().lastIndexOf(a.name.toLowerCase()); // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
var j = a.name.toLowerCase().lastIndexOf(cli.query.toLowerCase()); // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM
// it's important to note that these should only relpace changes in casing that we expected
// any abnormalities should be left intact to go "huh?" about
// TODO detect abnormalities?
if (-1 !== i) {
// "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
a.name = a.name.replace(cli.casedQuery.substr(i), cli.query.substr(i));
} else if (-1 !== j) {
// "www.example.com".replace("EXamPLE.cOm", "example.com")
a.name = a.name.substr(0, j) + a.name.substr(j).replace(cli.casedQuery, cli.query);
}
// NOTE: right now this assumes that anything matching the query matches all the way to the end
// it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
// (but I don't think it should need to)
if (a.name.length !== an.length) {
console.error("[ERROR] question / answer mismatch: '" + an + "' != '" + a.length + "'");
console.error(a);
}
});
});
}
if (fail0x20) {
console.warn("");
console.warn(";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '" + cli.casedQuery + "' but got response for '" + fail0x20 + "'.");
console.warn("");
}
console.log(';; Got answer:');
dig.logQuestion(packet);
function print(q) {
var printer = commonPrinters[q.typeName] || commonPrinters.ANY;
var printer = common.printers[q.typeName] || common.printers.ANY;
printer(q);
}
if (packet.answer.length) {
@ -203,166 +284,66 @@ function request(query, opts) {
packet.additional.forEach(print);
}
console.log('');
console.log(';; Query time: ' + (Date.now() - cli._ts) + ' msec');
// ;; SERVER: 8.8.8.8#53(8.8.8.8)
console.log(';; SERVER: ' + cli._nameserver + '#' + cli.port + '(' + cli._nameserver + ')');
// TODO ;; WHEN: Fri Sep 15 18:25:53 2017
console.log(';; WHEN: ' + new Date().toString());
console.log(';; MSG SIZE rcvd: ' + nb.byteLength);
console.log('');
if (opts.output) {
if (cli.output) {
console.log('');
writeQuery(opts, query, queryAb);
writeResponse(opts, query, nb, packet);
common.writeQuery(cli, query, queryAb);
common.writeResponse(cli, query, nb, packet);
}
};
handlers.onListening = function () {
cli.onListening = function () {
/*jshint validthis:true*/
var server = this;
var nameserver = opts.nameserver;
var nameservers;
var index;
if (!nameserver) {
nameservers = require('dns').getServers();
index = (Math.round(Math.random() * 7777)) % nameservers.length;
nameserver = nameservers[index];
if (opts.debug) {
console.log(index, nameservers);
}
}
if (opts.mdns || '224.0.0.251' === opts.nameserver) {
server.setBroadcast(true);
server.addMembership(opts.nameserver);
}
if (opts.debug) {
if (cli.debug) {
console.log('');
console.log('Bound and Listening:');
console.log('Bound and Listening:', server.type);
console.log(server.address());
}
if (opts.debug) {
console.log('querying ' + nameserver + ':' + opts.port);
// technicially this should be a seperate event
if (cli.debug) {
console.log("querying '" + server.nameserver + "':'" + cli.port + "'");
}
server.send(Buffer.from(queryAb), opts.port, nameserver, function () {
if (opts.debug) {
};
console.log('');
if (!cli.nocmd) {
console.log('; <<>> dig.js ' + 'v0.0.0' + ' <<>> ' + process.argv.slice(2).join(' ').replace(cli.query, cli.casedQuery));
console.log(';; global options: +cmd');
}
var opts = {
onError: cli.onError
, onMessage: cli.onMessage
, onListening: cli.onListening
, onSent: function (res) {
cli._nameserver = res.nameserver;
cli._ts = Date.now();
if (cli.debug) {
console.log('');
console.log('request sent');
console.log('request sent to', res.nameserver);
}
});
}
, onTimeout: function (res) {
console.log(";; 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
};
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
// 0 dns request
// 53 dns server
// 5353 mdns
if (opts.mdns) {
server.bind(5353);
setTimeout(function () {
server.close();
}, opts.timeout || (5 * 1000));
}
else {
server.bind(0);
}
}
cli.main(function (args, cli) {
args.forEach(function (arg) {
if (-1 !== commonTypes.indexOf(arg.toUpperCase())) {
if (cli.type) {
console.error("'type' was specified more than once");
process.exit(1);
return;
}
cli.type = cli.t = arg.toUpperCase();
}
if (/^\+time=/.test(arg)) {
if (cli.timeout) {
console.error("'+time=' was specified more than once");
process.exit(1);
return;
}
cli.timeout = Math.round(parseInt(arg.replace(/\+time=/, ''), 10) * 1000);
}
if (/^@/.test(arg)) {
if (cli.nameserver) {
console.error("'@server' was specified more than once");
process.exit(1);
return;
}
cli.nameserver = cli.n = arg;
}
if (cli.query) {
console.error("'@server' was specified more than once");
process.exit(1);
return;
}
cli.query = cli.q = arg;
});
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 (!cli.timeout) {
cli.timeout = 3000;
}
}
if (!cli.type) {
cli.type = cli.t = 'ANY';
}
if (!cli.port) {
cli.port = cli.p = 53;
}
if (!cli.class) {
cli.class = cli.c = 'IN';
}
if (!cli.query) {
console.error('');
console.error('Usage:');
console.error('dig.js [@server] [TYPE] [domain]');
console.error('');
console.error('Example:');
console.error('dig.js daplie.com');
console.error('');
process.exit(1);
}
var query = {
header: {
id: require('crypto').randomBytes(2).readUInt16BE(0)
, qr: 0
, opcode: 0
, aa: 0 // NA
, tc: 0 // NA
, rd: 1
, ra: 0 // NA
, rcode: 0 // NA
}
, question: [
{ name: cli.query
, typeName: cli.type
, className: cli.class
}
]
};
request(query, cli);
dig.resolve(queryAb, opts);
});

90
common.js Normal file
View File

@ -0,0 +1,90 @@
'use strict';
var fs = require('fs');
module.exports = {
types: [ 'A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT' ]
, printers: {
'ANY': function (q) {
console.log(';' + q.name + '.', q.ttl, (q.className || q.class), (q.typeName || ('type' + q.type)), q.data || q.rdata || 'unknown record type');
}
, 'A': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.address);
}
, 'AAAA': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.address);
}
, 'CNAME': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data + '.');
}
, 'MX': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.priority + ' ' + q.exchange + '.');
}
, 'NS': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data);
}
, 'PTR': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.data);
}
, 'SOA': function (q) {
// no ';' in authority section?
console.log('' + q.name + '.', q.ttl, q.className, q.typeName, q.name_server, q.email_addr, q.sn, q.ref, q.ret, q.ex, q.nx);
}
, 'SRV': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.priority + ' ' + q.weight + ' ' + q.port + ' ' + q.target);
}
, 'TXT': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, '"' + q.data.join('" "') + '"');
}
, 'CAA': function (q) {
console.log(';' + q.name + '.', q.ttl, q.className, q.typeName, q.flag + ' ' + q.tag + ' "' + q.value + '"');
}
}
, writeQuery: function (opts, query, queryAb) {
var path = require('path');
var basename = query.question[0].name + '.'
+ (query.question[0].typeName||query.question[0].type.toString()).toLowerCase();
var binname = basename + '.query.bin';
var jsonname = basename + '.query.json';
var binpath = opts.output + '.' + binname;
var jsonpath = opts.output + '.' + jsonname;
var json = JSON.stringify(query, null, 2);
if (-1 !== ['.', '/', '\\' ].indexOf(opts.output[opts.output.length -1])) {
binpath = path.join(opts.output, binname);
jsonpath = path.join(opts.output, jsonname);
}
fs.writeFile(binpath, Buffer.from(queryAb), null, function () {
console.log('wrote ' + queryAb.byteLength + ' bytes to ' + binpath);
});
fs.writeFile(jsonpath, json, null, function () {
console.log('wrote ' + json.length + ' bytes to ' + jsonpath);
});
}
, writeResponse: function (opts, query, nb, packet) {
var me = this;
me._count = me._count || 0;
var path = require('path');
var basename = query.question[0].name + '.'
+ (query.question[0].typeName||query.question[0].type.toString()).toLowerCase();
var binname = basename + '.' + me._count + '.bin';
var jsonname = basename + '.' + me._count + '.json';
var binpath = opts.output + '.' + binname;
var jsonpath = opts.output + '.' + jsonname;
var json = JSON.stringify(packet, null, 2);
if (-1 !== ['.', '/', '\\' ].indexOf(opts.output[opts.output.length -1])) {
binpath = path.join(opts.output, binname);
jsonpath = path.join(opts.output, jsonname);
}
me._count += 1;
fs.writeFile(binpath, nb, null, function () {
console.log('wrote ' + nb.byteLength + ' bytes to ' + binpath);
});
fs.writeFile(jsonpath, json, null, function () {
console.log('wrote ' + json.length + ' bytes to ' + jsonpath);
});
}
};

154
dns-request.js Normal file
View File

@ -0,0 +1,154 @@
'use strict';
var dnsjs = require('dns-suite');
var crypto = require('crypto');
var dgram = require('dgram');
var RCODES = {
0: 'NOERROR'
, 3: 'NXDOMAIN'
, 5: 'REFUSED'
};
function logQuestion(packet) {
var flags = "";
// TODO opcode 0 QUERY rcode 0 NOERROR
console.info(';; ->>HEADER<<- [opcode: ' + packet.header.opcode + ', status: ' + (RCODES[packet.header.rcode] || packet.header.rcode) + '], id: ' + packet.header.id);
if (packet.header.tc) { console.info("Truncated [tc] (we don't know the normal way to print a tc packet... you should record this with -o tc-packet.dig and send it to us)"); }
flags += ";; flags:";
if (packet.header.qr) { flags += " qr"; }
if (packet.header.aa) { flags += " aa"; }
if (packet.header.rd) { flags += " rd"; }
if (packet.header.ra) { flags += " ra"; }
flags += "; QUERY: " + packet.question.length + ", ANSWER: " + packet.answer.length + ", AUTHORITY: " + packet.authority.length + ", ADDITIONAL: " + packet.additional.length;
console.info(flags);
if (packet.header.res1) { console.info("[res1] (we don't know how to print a packet with res1 yet)"); }
if (packet.header.res2) { console.info("[res2] (we don't know how to print a packet with res2 yet)"); }
if (packet.header.res3) { console.info("[res3] (we don't know how to print a packet with res2 yet)"); }
// {"id":32736,"qr":1,"opcode":0,"aa":0,"tc":0,"rd":1,"ra":0,"res1":0,"res2":0,"res3":0,"rcode":5}
//console.log(JSON.stringify(packet.header));
console.info('');
console.info(';; QUESTION SECTION:');
packet.question.forEach(function (q) {
console.info(';' + q.name + '.', ' ', q.className, q.typeName || ('type' + q.type));
});
}
function resolve(queryAb, opts) {
var handlers = {};
var nameservers;
var nameserver = opts.nameserver;
var index;
var udpType;
var receivedMessage;
if (!nameserver) {
nameservers = require('dns').getServers();
index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length;
nameserver = nameservers[index];
}
udpType = /:/.test(nameserver) ? 'udp6' : 'udp4';
var server = dgram.createSocket({
type: udpType
, reuseAddr: true
});
server.nameserver = nameserver;
handlers.onError = function (err) {
if (opts.onError) { opts.onError(err); } else { throw err; }
server.close();
};
handlers.onMessage = function (bin) {
receivedMessage = true;
if (!opts.mdns) {
clearTimeout(server._timeoutToken);
server.close();
}
if (opts.onMessage) { opts.onMessage(bin); }
};
handlers.onListening = function () {
/*jshint validthis:true*/
var server = this;
if (opts.mdns || '224.0.0.251' === server.nameserver) {
server.setBroadcast(true);
server.addMembership(server.nameserver || '224.0.0.251');
}
if (opts.onListening) { opts.onListening.apply(server); }
server.send(Buffer.from(queryAb), opts.port, server.nameserver, function () {
if (opts.onSent) { opts.onSent({ port: opts.port, nameserver: server.nameserver }); }
});
};
handlers.onClose = function () {
if (opts.onClose) { opts.onClose(); }
};
server.on('error', handlers.onError);
server.on('message', handlers.onMessage);
server.on('listening', handlers.onListening);
server.on('close', handlers.onClose);
// 0 dns request
// 53 dns server
// 5353 mdns
if (opts.mdns) {
server.bind(opts.port /*5353*/);
}
else {
server.bind(0);
}
var ms = opts.timeout || (5 * 1000);
server._timeoutToken = setTimeout(function () {
if (!receivedMessage && opts.onTimeout) { opts.onTimeout({ timeout: ms }); }
server.close();
}, ms);
}
function resolveJson(query, opts) {
var queryAb;
try {
queryAb = dnsjs.DNSPacket.write(query);
} catch(e) {
if ('function' === typeof opts.onError) { opts.onError(e); return; }
throw e;
}
//console.log('[DEV] nameserver', opts.nameserver);
var options = {
onError: opts.onError
, onMessage: function (nb) {
var packet;
try {
packet = dnsjs.DNSPacket.parse(nb.buffer.slice(nb.byteOffset, nb.byteOffset + nb.byteLength));
} catch(e) {
if (opts.onError) { opts.onError(e); return; }
console.error("[Error] couldn't parse incoming message");
console.error(e);
return;
}
opts.onMessage(packet);
}
, onListening: opts.onListening
, onSent: opts.onSent
, onClose: opts.onClose
, onTimeout: opts.onTimeout
, mdns: opts.mdns
, nameserver: opts.nameserver
, port: opts.port
, timeout: opts.timeout
};
return resolve(queryAb, options);
}
module.exports.resolve = resolve;
module.exports.resolveJson = resolveJson;
module.exports.request = resolve;
module.exports.requestJson = resolveJson;
module.exports.logQuestion = logQuestion;

11
merge-up.sh Normal file
View File

@ -0,0 +1,11 @@
git push
git checkout v1
git merge v1.0
git push
git checkout master
git merge v1
git push
git checkout v1.0

View File

@ -1,8 +1,9 @@
{
"name": "dig.js",
"version": "1.0.4",
"version": "1.3.9",
"description": "Create and capture DNS and mDNS query and response packets to disk as binary and/or JSON. Options are similar to the Unix `dig` command.",
"main": "index.js",
"homepage": "https://git.coolaj86.com/coolaj86/dig.js",
"bin": {
"dig.js": "./bin/dig.js"
},
@ -11,12 +12,21 @@
},
"repository": {
"type": "git",
"url": "git@git.daplie.com:Daplie/dig.js.git"
"url": "git://git.coolaj86.com:coolaj86/dig.js.git"
},
"keywords": [
"mdig",
"multicast",
"debugging",
"debug",
"cli",
"command",
"line",
"dig",
"dns",
"mdns",
"dns0x20",
"0x20",
"lint",
"capture",
"create",
@ -26,8 +36,14 @@
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT OR Apache-2.0)",
"bugs": {
"url": "https://git.coolaj86.com/coolaj86/dig.js/issues"
},
"dependencies": {
"cli": "^1.0.1",
"dns-suite": "git+https://git@git.daplie.com:Daplie/dns-suite#v1"
"dns-suite": "git+https://git.coolaj86.com/coolaj86/dns-suite.js#v1.2"
},
"optionalDependencies": {
"hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
}
}