2017-02-24 01:12:57 +00:00
#!/usr/bin/env node
2017-02-18 02:07:02 +00:00
'use strict' ;
2017-09-18 23:40:11 +00:00
var dig = require ( '../dns-request' ) ;
2017-02-18 02:07:02 +00:00
var cli = require ( 'cli' ) ;
2017-09-18 23:40:11 +00:00
var defaultNameservers = require ( 'dns' ) . getServers ( ) ;
2017-10-09 20:45:07 +00:00
var typeRe = /^type\d+$/i ;
2017-09-18 23:40:11 +00:00
2017-02-18 02:07:02 +00:00
cli . parse ( {
// 'b': [ false, 'set source IP address (defaults to 0.0.0.0)', 'string' ]
'class' : [ 'c' , 'class (defaults to IN)' , 'string' , 'IN' ]
2017-02-25 21:05:29 +00:00
, 'debug' : [ false , 'more verbose output' , 'boolean' , false ]
2017-02-18 02:07:02 +00:00
//, '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 ]
2017-09-16 00:42:32 +00:00
, 'timeout' : [ false , "How long, in milliseconds, to wait for a response. Alias of +time=" , 'int' , false ]
2017-02-18 02:07:02 +00:00
, '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' ]
2017-09-18 23:40:11 +00:00
, 'nameserver' : [ false , 'the nameserver to use for DNS resolution (defaults to ' + defaultNameservers . join ( ',' ) + ')' , 'string' ]
2017-02-18 02:07:02 +00:00
//, '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' ]
2017-12-15 08:54:39 +00:00
, 'query' : [ 'q' , 'a superfluous explicit option to set the query as a command line flag' , 'string' ]
2017-10-02 22:43:58 +00:00
, '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." ]
2017-02-18 02:07:02 +00:00
} ) ;
2017-09-16 00:42:32 +00:00
var common = require ( '../common.js' ) ;
2017-02-18 03:18:19 +00:00
2017-02-18 02:07:02 +00:00
cli . main ( function ( args , cli ) {
2017-12-15 08:54:39 +00:00
cli . implicitType = cli . type ;
cli . implicitQuery = cli . query ;
2017-02-18 02:07:02 +00:00
args . forEach ( function ( arg ) {
2017-10-09 20:45:07 +00:00
if ( typeRe . test ( arg ) || - 1 !== common . types . concat ( [ 'ANY' ] ) . indexOf ( arg . toUpperCase ( ) ) ) {
2017-12-15 08:54:39 +00:00
if ( cli . implicitType ) {
2017-02-18 02:07:02 +00:00
console . error ( "'type' was specified more than once" ) ;
process . exit ( 1 ) ;
return ;
}
2017-12-15 08:54:39 +00:00
cli . implicitType = cli . t = arg . toUpperCase ( ) ;
2017-02-25 20:05:07 +00:00
return ;
2017-02-18 02:07:02 +00:00
}
2017-09-25 20:43:21 +00:00
if ( arg === '+aaonly' || arg === '+aaflag' ) {
if ( cli . aaonly ) {
console . error ( "'+aaonly' was specified more than once" ) ;
process . exit ( 1 ) ;
return ;
}
cli . aaonly = true ;
return ;
}
2017-09-16 00:42:32 +00:00
if ( arg === '+norecurse' ) {
if ( cli . norecurse ) {
console . error ( "'+norecurse' was specified more than once" ) ;
process . exit ( 1 ) ;
return ;
}
cli . norecurse = true ;
return ;
}
2017-02-24 00:31:09 +00:00
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 ) ;
2017-02-25 20:05:07 +00:00
return ;
2017-02-24 00:31:09 +00:00
}
2017-02-18 02:07:02 +00:00
if ( /^@/ . test ( arg ) ) {
if ( cli . nameserver ) {
console . error ( "'@server' was specified more than once" ) ;
process . exit ( 1 ) ;
return ;
}
2017-03-31 06:02:49 +00:00
cli . nameserver = cli . n = arg . substr ( 1 ) ;
2017-02-25 20:05:07 +00:00
return ;
2017-02-18 02:07:02 +00:00
}
2017-12-15 08:54:39 +00:00
if ( 'string' === typeof cli . implicitQuery ) {
console . error ( "'query' was specified more than once or unrecognized flag: " + cli . implicitQuery + ", " + arg ) ;
2017-02-18 02:07:02 +00:00
process . exit ( 1 ) ;
return ;
}
2017-12-15 08:54:39 +00:00
cli . implicitQuery = cli . q = arg ;
2017-02-18 02:07:02 +00:00
} ) ;
2017-12-15 08:54:39 +00:00
// 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 ;
}
2017-02-18 02:07:02 +00:00
if ( cli . mdns ) {
if ( ! cli . type ) {
cli . type = cli . t = 'PTR' ;
}
if ( ! cli . port ) {
cli . port = cli . p = 5353 ;
}
2017-02-18 02:40:01 +00:00
if ( ! cli . nameserver ) {
cli . nameserver = '224.0.0.251' ;
}
2017-12-15 08:54:39 +00:00
if ( 'string' !== typeof cli . query ) {
2017-02-24 00:31:09 +00:00
cli . query = '_services._dns-sd._udp.local' ;
}
2017-02-24 01:22:16 +00:00
if ( ! cli . timeout ) {
cli . timeout = 3000 ;
}
2017-09-19 00:06:27 +00:00
} else {
if ( ! cli . timeout ) {
cli . timeout = 5000 ;
}
2017-02-18 02:07:02 +00:00
}
2017-10-02 22:43:58 +00:00
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 ;
}
2017-02-18 02:07:02 +00:00
if ( ! cli . type ) {
cli . type = cli . t = 'ANY' ;
}
2017-10-09 20:45:07 +00:00
if ( typeRe . test ( cli . type ) ) {
cli . rawType = parseInt ( cli . type . replace ( 'type' , '' ) , 10 ) ;
}
2017-02-18 02:07:02 +00:00
if ( ! cli . port ) {
cli . port = cli . p = 53 ;
}
if ( ! cli . class ) {
cli . class = cli . c = 'IN' ;
}
var query = {
header : {
id : require ( 'crypto' ) . randomBytes ( 2 ) . readUInt16BE ( 0 )
, qr : 0
, opcode : 0
2017-09-25 20:43:21 +00:00
, aa : cli . aaonly ? 1 : 0 // NA
, tc : 0 // NA
2017-09-16 00:42:32 +00:00
, rd : cli . norecurse ? 0 : 1
2017-09-25 20:43:21 +00:00
, ra : 0 // NA
, rcode : 0 // NA
2017-02-18 02:07:02 +00:00
}
, question : [
2017-10-02 22:43:58 +00:00
{ name : cli . casedQuery
2017-10-09 20:45:07 +00:00
, type : cli . rawType
, typeName : cli . rawType ? undefined : cli . type
2017-02-18 02:07:02 +00:00
, className : cli . class
}
]
} ;
2017-09-18 23:40:11 +00:00
var dnsjs = require ( 'dns-suite' ) ;
var queryAb = dnsjs . DNSPacket . write ( query ) ;
2017-09-25 20:43:21 +00:00
var hexdump = require ( 'hexdump.js' ) . hexdump ;
2017-09-18 23:40:11 +00:00
if ( cli . debug ) {
console . log ( '' ) ;
console . log ( 'DNS Question:' ) ;
console . log ( '' ) ;
console . log ( query ) ;
console . log ( '' ) ;
console . log ( hexdump ( queryAb ) ) ;
console . log ( '' ) ;
console . log ( dnsjs . DNSPacket . parse ( queryAb ) ) ;
console . log ( '' ) ;
}
cli . onError = function ( err ) {
console . error ( "error:" , err . stack ) ;
} ;
2017-09-20 19:27:53 +00:00
2017-09-18 23:40:11 +00:00
cli . onMessage = function ( nb ) {
var packet = dnsjs . DNSPacket . parse ( nb . buffer . slice ( nb . byteOffset , nb . byteOffset + nb . byteLength ) ) ;
2017-10-02 22:56:31 +00:00
var fail0x20 ;
2017-09-18 23:40:11 +00:00
if ( packet . id !== query . id ) {
2017-10-07 00:42:37 +00:00
console . error ( '[SECURITY] ignoring packet for \'' + packet . question [ 0 ] . name + '\' due to mismatched id' ) ;
console . error ( packet ) ;
return ;
2017-09-18 23:40:11 +00:00
}
if ( cli . debug ) {
console . log ( '' ) ;
console . log ( 'DNS Response:' ) ;
console . log ( packet ) ;
}
2017-10-02 22:56:31 +00:00
packet . question . forEach ( function ( q ) {
2017-10-02 23:06:15 +00:00
// if (-1 === q.name.lastIndexOf(cli.casedQuery))
2017-10-02 22:56:31 +00:00
if ( q . name !== cli . casedQuery ) {
fail0x20 = q . name ;
}
} ) ;
2017-10-02 22:43:58 +00:00
if ( ! cli . norecase && ! cli . recase ) {
[ 'question' , 'answer' , 'authority' , 'additional' ] . forEach ( function ( group ) {
( packet [ group ] || [ ] ) . forEach ( function ( a ) {
var an = a . name ;
2017-10-02 23:06:15 +00:00
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
2017-10-02 22:43:58 +00:00
// 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 ) ) ;
2017-10-09 20:45:07 +00:00
} else if ( - 1 !== j ) {
2017-10-02 22:43:58 +00:00
// "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 ) {
2017-10-09 20:45:07 +00:00
console . error ( "[ERROR] question / answer mismatch: '" + an + "' != '" + a . length + "'" ) ;
console . error ( a ) ;
2017-10-02 22:43:58 +00:00
}
} ) ;
} ) ;
}
2017-10-02 22:56:31 +00:00
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 ( "" ) ;
}
2017-09-18 23:40:11 +00:00
console . log ( ';; Got answer:' ) ;
2017-09-20 19:27:53 +00:00
dig . logQuestion ( packet ) ;
2017-09-18 23:40:11 +00:00
function print ( q ) {
var printer = common . printers [ q . typeName ] || common . printers . ANY ;
printer ( q ) ;
}
if ( packet . answer . length ) {
console . log ( '' ) ;
console . log ( ';; ANSWER SECTION:' ) ;
packet . answer . forEach ( print ) ;
}
if ( packet . authority . length ) {
console . log ( '' ) ;
console . log ( ';; AUTHORITY SECTION:' ) ;
packet . authority . forEach ( print ) ;
}
if ( packet . additional . length ) {
console . log ( '' ) ;
console . log ( ';; ADDITIONAL SECTION:' ) ;
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 ( cli . output ) {
console . log ( '' ) ;
common . writeQuery ( cli , query , queryAb ) ;
common . writeResponse ( cli , query , nb , packet ) ;
}
} ;
cli . onListening = function ( ) {
/*jshint validthis:true*/
var server = this ;
if ( cli . debug ) {
console . log ( '' ) ;
2017-09-19 00:06:27 +00:00
console . log ( 'Bound and Listening:' , server . type ) ;
2017-09-18 23:40:11 +00:00
console . log ( server . address ( ) ) ;
}
// technicially this should be a seperate event
if ( cli . debug ) {
console . log ( "querying '" + server . nameserver + "':'" + cli . port + "'" ) ;
}
} ;
console . log ( '' ) ;
if ( ! cli . nocmd ) {
2017-10-02 22:43:58 +00:00
console . log ( '; <<>> dig.js ' + 'v0.0.0' + ' <<>> ' + process . argv . slice ( 2 ) . join ( ' ' ) . replace ( cli . query , cli . casedQuery ) ) ;
2017-09-18 23:40:11 +00:00
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 to' , res . nameserver ) ;
}
}
2017-09-19 00:06:27 +00:00
, onTimeout : function ( res ) {
2017-09-20 18:18:25 +00:00
console . log ( ";; connection timed out; no servers could be reached" ) ;
console . log ( ";; [timed out after " + res . timeout + "ms and 1 tries]" ) ;
2017-09-19 00:06:27 +00:00
}
2017-09-20 18:50:32 +00:00
, onClose : function ( ) {
console . log ( '' ) ;
}
2017-09-18 23:40:11 +00:00
, mdns : cli . mdns
, nameserver : cli . nameserver
, port : cli . port
, timeout : cli . timeout
} ;
2017-09-25 20:43:21 +00:00
2017-09-20 18:50:32 +00:00
dig . resolve ( queryAb , opts ) ;
2017-02-18 02:07:02 +00:00
} ) ;