Skip to content

Commit

Permalink
add: bind rr parsing for CAA, DNSKEY, DS, HINFO, LOC
Browse files Browse the repository at this point in the history
- add: bind rr parsing for CAA, DNSKEY, DS, HINFO, LOC
  • Loading branch information
msimerson committed Mar 25, 2022
1 parent 9306a0f commit 29c0878
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Zisi.edu:venera.isi.edu:action.domains.isi.edu:20:7200:600:3600000:60:60::
- [ ] write a named.conf file parser
- [x] write a bind zone file parser
- [x] write a tinydns data file parser
- [ ] add parsing support for all RRs supported by dns-rr
- [ ] add BIND parsing for all RRs supported by dns-rr
- normalize BIND zone records
- [x] expand `@` to zone name
- [x] empty names are same as previous RR record
Expand Down
156 changes: 121 additions & 35 deletions src/bind-grammar.ne
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

main -> (statement eol):+

statement -> blank | ttl | origin | soa | ns | mx | a | txt | aaaa | cname | dname | ptr
statement -> blank | ttl | origin | a | aaaa | caa | cname | dname | dnskey |
ds | hinfo | loc | mx | ns | ptr | soa | txt

times_3[X] -> $X $X $X
eol -> "\n" | "\r"

blank -> _
Expand All @@ -17,51 +19,86 @@ ttl -> "$TTL" __ uint _ (comment):? _ {% asTTL %}

origin -> "$ORIGIN" __ hostname _ (comment):? _ {% asOrigin %}

soa -> hostname ( __ uint ):? ( __ class ):? __ "SOA"
__ hostname __ hostname __ "("
_ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
_ ")" _ (comment):? {% asRR %}

ns -> hostname (__ uint):? (__ class):? __ "NS"
__ hostname _ (comment):? _ {% asRR %}

mx -> hostname (__ uint):? (__ class):? __ "MX"
__ uint __ hostname _ (comment):? {% asRR %}

a -> hostname (__ uint):? (__ class):? __ "A"
__ ip4 _ (comment):? _ {% asRR %}

txt -> hostname (__ uint):? (__ class):? __ "TXT"
__ (dqstring _):+ (comment):? _ {% asRR %}

aaaa -> hostname (__ uint):? (__ class):? __ "AAAA"
__ ip6 _ (comment):? _ {% asRR %}

caa -> hostname (__ uint):? (__ class):? __ "CAA"
__ uint __ (ALPHA_LC_NUM):+ __ QUOTE_OR_NO_WS _ (comment):? _ {% asRR %}

cname -> hostname (__ uint):? (__ class):? __ "CNAME"
__ hostname _ (comment):? _ {% asRR %}

dname -> hostname (__ uint):? (__ class):? __ "DNAME"
__ hostname _ (comment):? _ {% asRR %}

dnskey -> hostname (__ uint):? (__ class):? __ "DNSKEY"
__ uint __ uint __ uint __
"(" _ (BASE64):+ _ ")" _ (comment):? _ {% asRR %}

ds -> hostname (__ uint):? (__ class):? __ "DS"
__ uint __ uint __ uint __
"(" _ (HEX_WS):+ _ ")" _ (comment):? {% asRR %}

hinfo -> hostname (__ uint):? (__ class):? __ "HINFO"
__ (wordchars):+ __ (wordchars):+
_ (comment):? {% asRR %}

loc -> hostname (__ uint):? (__ class):? __ "LOC"
__ uint (__ uint):? (__ udec __):? ("N" | "S")
__ uint (__ uint):? (__ udec __):? ("E" | "W")
__ (word "m") times_3[(__ (word "m")):?]
_ (comment):? {% asRR %}

mx -> hostname (__ uint):? (__ class):? __ "MX"
__ uint __ hostname _ (comment):? {% asRR %}

#naptr -> hostname (__ uint):? (__ class):? __ "NAPTR"
#__ uint __ uint __ QUOTED __ QUOTED
#__ QUOTED __ replacement _ (comment):? {% asRR %}

ns -> hostname (__ uint):? (__ class):? __ "NS"
__ hostname _ (comment):? _ {% asRR %}

ptr -> hostname (__ uint):? (__ class):? __ "PTR"
__ hostname _ (comment):? _ {% asRR %}

uint -> [0-9]:+ {% (d) => parseInt(d[0].join("")) %}
#rrsig -> hostname (__ uint):? (__ class):? __ "RRSIG"

hostname -> ALPHA_NUM_DASH_U:* {% (d) => d[0].join("") %}
#smimea -> hostname (__ uint):? (__ class):? __ "SMIMEA"

soa -> hostname ( __ uint ):? ( __ class ):? __ "SOA"
__ hostname __ hostname __ "("
_ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
__ uint (ws comment):?
_ ")" _ (comment):? {% asRR %}

ALPHA_NUM_DASH_U -> [0-9A-Za-z\u0080-\uFFFF\.\-_@] {% id %}
#spf -> hostname (__ uint):? (__ class):? __ "SPF"
#srv -> hostname (__ uint):? (__ class):? __ "SRV"
#sshfp -> hostname (__ uint):? (__ class):? __ "SSHFP"
#tlsa -> hostname (__ uint):? (__ class):? __ "TLSA"

txt -> hostname (__ uint):? (__ class):? __ "TXT"
__ (dqstring _):+ (comment):? _ {% asRR %}

#uri -> hostname (__ uint):? (__ class):? __ "URI"

uint -> [0-9]:+ {% asUint %}
udec -> [0-9]:+ ("." [0-9]:+):? {% asUDec %}

hostname -> ALPHA_NUM_DASH_U:* {% asString %}

class -> "IN" | "CS" | "CH" | "HS" | "NONE" | "ANY"

#not_whitespace -> [^\n\r] {% id %}
#host_chars -> [-0-9A-Za-z\u0080-\uFFFF._@/] {% id %}
word -> (wordchars):+ {% flatten %}
wordchars -> [^\s] {% id %}

times_3[X] -> $X $X $X
#times_3[X] -> $X $X $X
times_5[X] -> $X $X $X $X $X
times_7[X] -> $X $X $X $X $X $X $X

Expand All @@ -75,8 +112,15 @@ int8 -> DIGIT |
"2" [0-4] DIGIT |
"25" [0-5]

DIGIT -> [0-9] {% id %}
HEXDIG -> [0-9A-Fa-f] {% id %}
ALPHA_LC_NUM -> [0-9a-z] {% id %}
ALPHA_NUM_DASH_U-> [0-9A-Za-z\u0080-\uFFFF\.\-_@] {% id %}
DIGIT -> [0-9] {% id %}
HEXDIG -> [0-9A-Fa-f] {% id %}
HEX_WS -> [0-9A-Fa-f\s] {% id %}
BASE64 -> [A-Za-z0-9+/=\s] {% id %}
#BASE64_URL_SAFE-> [A-Za-z0-9_\-=\s] {% id %}
QUOTED -> ("\"" ([^\\"]):+ "\"") | ("'" ([^\\"]):+ "'")
QUOTE_OR_NO_WS -> "\"" ([^\\"]):+ "\"" | "'" ([^\\"]):+ "'" | ([^\s]):+

IPv6_hex -> HEXDIG |
HEXDIG HEXDIG |
Expand All @@ -95,8 +139,8 @@ IPv6v4_comp -> (IPv6_hex times_3[":" IPv6_hex]):? "::"
(IPv6_hex times_3[":" IPv6_hex] ":"):? ip4 {% flatten %}

# Whitespace: `_` is optional, `__` is mandatory.
_ -> wschar:* {% function(d) {return null;} %}
__ -> wschar:+ {% function(d) {return null;} %}
_ -> wschar:* {% asNull %}
__ -> wschar:+ {% asNull %}
ws -> wschar:* {% id %}

wschar -> [ \t\n\r\v\f] {% id %}
Expand All @@ -108,13 +152,15 @@ function flatten (d) {
return d
}

function asTTL (d) {
return { $TTL: d[2] }
function asNull (d) { return null; }
function asString (d) { return d[0].join(''); }
function asUint (d) { return parseInt(d[0].join('')) }
function asUDec (d) {
return parseFloat(d[0].join('') + (d[1] ? `.${d[1][1].join('')}` : ''))
}

function asOrigin (d) {
return { $ORIGIN: d[2] }
}
function asTTL (d) { return { $TTL: parseInt(flatten(d[2]), 10) }; }
function asOrigin (d) { return { $ORIGIN: d[2] }; }

function asRR (d) {
const r = {
Expand All @@ -131,12 +177,53 @@ function asRR (d) {
case 'AAAA':
r.address = d[6][0]
break
case 'CAA':
r.flags = d[6]
r.tag = flatten(d[8])
r.value = flatten(d[10])
break
case 'CNAME':
r.cname = d[6]
break
case 'DNAME':
r.target = d[6]
break
case 'DNSKEY':
r.flags = d[6]
r.protocol = d[8]
r.algorithm = d[10]
r.publickey = flatten(d[14]).split(/\s+/).join('')
break
case 'DS':
r['key tag'] = d[6]
r.algorithm = d[8]
r['digest type'] = d[10]
r.digest = flatten(d[14]).split(/\s+/).join('')
break
case 'HINFO':
r.cpu = flatten(d[6])
r.os = flatten(d[8])
break
case 'LOC':
r.latitude = {
degrees: d[6],
minutes: d[7][1] || 0,
seconds: d[8][1] || 0,
hemisphere: d[9][0],
}
r.longitude = {
degrees: d[11],
minutes: d[12][1] || 0,
seconds: d[13][1] || 0,
hemisphere: d[14][0], // E | W
}
r.altitude = flatten(d[16]),
r.size = flatten(d[17]) || '1m',
r.precision = {
horizontal: flatten(d[18]) || '10000m',
vertical: flatten(d[19]) || '10m',
}
break
case 'MX':
r.preference = d[6]
r.exchange = d[8]
Expand Down Expand Up @@ -170,5 +257,4 @@ function asRR (d) {
}
return r
}

%}
113 changes: 113 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ describe('parseZoneFile', function () {
})
})

const testCAAs = [
{ bind : 'nocerts.example.com CAA 0 issue ";"\n',
result: {
class: null,
flags: 0,
name : 'nocerts.example.com',
tag : 'issue',
ttl : null,
type : 'CAA',
value: '";"',
},
},
{ bind : 'certs.example.com CAA 0 issue "example.net"\n',
result: {
class: null,
flags: 0,
name : 'certs.example.com',
tag : 'issue',
ttl : null,
type : 'CAA',
value: '"example.net"',
},
},
]

for (const t of testCAAs) {
it(`parses CAA record: ${t.result.name}`, async () => {
const r = await zv.parseZoneFile(t.bind)
assert.deepStrictEqual(r[0], t.result)
})
}

it('parses a CNAME line, absolute', async () => {
const r = await zv.parseZoneFile(`www 28800 IN CNAME vhost0.theartfarm.com.\n`)
// console.dir(r, { depth: null })
Expand Down Expand Up @@ -123,6 +155,87 @@ describe('parseZoneFile', function () {
})
})

it('parses a DNSKEY record', async () => {
const r = await zv.parseZoneFile(
`example.com. 86400 IN DNSKEY 256 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3
Cbl+BBZH4b/0PY1kxkmvHjcZc8no
kfzj31GajIQKY+5CptLr3buXA10h
WqTkF7H6RfoRqXQeogmMHfpftf6z
Mv1LyBUgia7za6ZEzOJBOztyvhjL
742iU/TpPSEDhm2SNKLijfUppn1U
aNvv4w== )\n`)
// console.dir(r, { depth: null })
assert.deepStrictEqual(r[0], {
name : 'example.com.',
ttl : 86400,
class : 'IN',
type : 'DNSKEY',
flags : 256,
protocol : 3,
algorithm: 5,
publickey: 'AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQeogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU/TpPSEDhm2SNKLijfUppn1UaNvv4w==',
})
})

it('parses a DS record', async () => {
const r = await zv.parseZoneFile(
`dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A
98631FAD1A292118 )\n`)
// console.dir(r, { depth: null })
assert.deepStrictEqual(r[0], {
name : 'dskey.example.com.',
ttl : 86400,
class : 'IN',
type : 'DS',
'key tag' : 60485,
algorithm : 5,
'digest type': 1,
digest : '2BB183AF5F22588179A53B0A98631FAD1A292118',
})
})

it('parses a HINFO line', async () => {
const r = await zv.parseZoneFile(`SRI-NIC.ARPA. HINFO DEC-2060 TOPS20\n`)
// console.dir(r, { depth: null })
assert.deepStrictEqual(r[0], {
name : 'SRI-NIC.ARPA.',
ttl : null,
class: null,
type : 'HINFO',
cpu : 'DEC-2060',
os : 'TOPS20',
})
})

it('parses a LOC line', async () => {
const r = await zv.parseZoneFile(`rwy04l.logan-airport.boston. 3600 IN LOC 42 21 28.764 N 71 0 51.617 W -44m 2000m\n`)
// console.dir(r, { depth: null })
assert.deepStrictEqual(r[0], {
name : 'rwy04l.logan-airport.boston.',
ttl : 3600,
class : 'IN',
type : 'LOC',
latitude: {
degrees : 42,
hemisphere: 'N',
minutes : 21,
seconds : 28.764,
},
longitude: {
degrees : 71,
hemisphere: 'W',
minutes : 0,
seconds : 51.617,
},
altitude : '-44m',
size : '2000m',
precision: {
horizontal: '10000m',
vertical : '10m',
},
})
})

it('parses the cadillac.net zone file', async () => {
const file = './test/fixtures/zones/cadillac.net'
fs.readFile(file, (err, buf) => {
Expand Down

0 comments on commit 29c0878

Please sign in to comment.