Skip to content

Commit

Permalink
release 0.2.0 (#2)
Browse files Browse the repository at this point in the history
- add: expandShortcuts
- add: bin/import
- use async for parseZoneFile and expandShortcuts
- SOA: capture comments
  • Loading branch information
msimerson committed Mar 23, 2022
1 parent 1c5be9d commit 0ac19a7
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

name: Module Tests
name: Tests

on: [ push ]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coveralls.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

on: [ pull_request ]

name: Test Coverage
name: Coverage

jobs:

Expand Down
23 changes: 23 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#### 1.N.N - YYYY-MM-DD


#### 0.2.0 - 2022-03-22

- add expandShortcuts
- added bin/import
- use async for parseZoneFile and expandShortcuts
- SOA: capture comments


#### 0.1.0 - 2022-03-17

- Bind zonefile parser, using nearley: #1

- stab #1: parses cadillac.net
- test #2: add isi.edu zone
- allow comments after SOA closing parens
- DRY the grammar -> object functions
- add \r to eol, for windows
- local copy of builtin-whitespace, adds \r char
- ci: remove windows support, times out, I think upstream nearley issue
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,24 @@ DNS zones have numerous rules regarding the records that can exist in them. Exam

- serial numbers must increment when changes are made
- multiple identical RRs are not allowed - RFC 2181
- CAA takes tag into account, SRV: port
- multiple CNAMES with the same name are not allowed
- CNAME cannot coexist with SIG,NXT,KEY,RRSIG,NSEC
- A cannot coexist with CNAME
- CNAME cannot coexist with SIG,NXT,KEY,RRSIG,NSEC,A,AAAA
- MX and NS records cannot point to CNAME

Etc, etc, etc..

This module will input a collection of [dns-resource-records](https://github.com/msimerson/dns-resource-record) and validate that all the zone records can coexist.
This module will input a collection of [dns-resource-records](https://github.com/msimerson/dns-resource-record) and validate that all the zone records can coexist.


## TODO

- [ ] write a named.conf file parser
- [x] write a bind zone file parser
- normalize the zone records
- [x] expand `@` to zone name
- [x] empty names are same as previous RR record
- [x] missing TTLs inherit zone TTL, or zone MINIMUM
- [x] expand hostnames to FQDNs
- [x] ALL: name field
- [x] MX: exchange, CNAME: cname, SOA: mname, rname, NS: dname
69 changes: 69 additions & 0 deletions bin/import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!node

const fs = require('fs')
const path = require('path')
const os = require('os')

const dz = require('../index')

const filePath = process.argv[2]
if (!filePath) usage()

function usage () {
console.log(`\n ${process.argv[1]} file\n`)
process.exit(1)
}

console.log(`reading file ${filePath}`)

fs.readFile(filePath, (err, buf) => {
if (err) throw err

const base = path.basename(filePath)
const asString = fileAsString(buf, base)

dz.parseZoneFile(asString)
.then(dz.expandShortcuts)
.then(zoneArray => {
// console.log(zoneArray)
switch (process.argv[3]) {
case 'toBind':
toBind(zoneArray, base)
break
case 'toTinydns':
toTinydns(zoneArray)
break
default:
console.log(zoneArray)
}
})
.catch(e => {
console.error(e.message)
})
})

function fileAsString (buf, base) {
let str = buf.toString()

if (!/^\$ORIGIN/m.test(str)) {
console.log(`inserting $ORIGIN ${base}`)
str = `$ORIGIN ${base}.${os.EOL}${str}`
}
// console.log(str)
return str
}

function toBind (zoneArray, origin) {
for (const rr of zoneArray) {
let out = rr.toBind()
const reduceRE = new RegExp(`^([^\\s]+).${origin}.`)
out = out.replace(reduceRE, '$1')
process.stdout.write(out)
}
}

function toTinydns (zoneArray) {
for (const rr of zoneArray) {
process.stdout.write(rr.toTinydns())
}
}
95 changes: 91 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@

const nearley = require('nearley')
const grammar = require('./grammar.js')
const RR = require('dns-resource-record')

const grammar = nearley.Grammar.fromCompiled(require('./grammar.js'))
grammar.start = 'main'

exports.parseZoneFile = str => {
exports.parseZoneFile = async str => {

const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar))
const parser = new nearley.Parser(grammar)
parser.feed(str)
parser.feed(`\n`) // for no EOL after last record
parser.feed(`\n`) // in case no EOL after last record

if (parser.length > 1) {
console.error(`ERROR: ambigious parser rule`)
Expand All @@ -24,3 +26,88 @@ exports.parseZoneFile = str => {
}
return flat
}

exports.expandShortcuts = async zoneArray => {
let ttl = 0
let implicitOrigin = ''
let origin = ''
let lastName = ''
const expanded = []
const empty = [ undefined, null ]
// console.log(zoneArray)

for (let i = 0; i < zoneArray.length; i++) {
const entry = zoneArray[i]

if (entry.$TTL) {
ttl = entry.$TTL; continue
}

// When a zone is first read, there is an implicit $ORIGIN <zone_name>.
// note the trailing dot. The current $ORIGIN is appended to the domain
// specified in the $ORIGIN argument if it is not absolute. -- BIND 9
if (entry.implicitOrigin) { // zone 'name' in named.conf
implicitOrigin = origin = fullyQualify(entry.implicitOrigin)
continue
}
if (entry.$ORIGIN) { // declared $ORIGIN within zone file
origin = fullyQualify(entry.$ORIGIN, implicitOrigin)
continue
}
if (!origin) throw new Error(`zone origin ambiguous, cowardly bailing out`)

if (ttl === 0 && entry.type === 'SOA' && entry.minimum) ttl = entry.minimum
if (empty.includes(entry.ttl )) entry.ttl = ttl
if (empty.includes(entry.class)) entry.class = 'IN'

// expand NAME shortcuts
if (entry.name === '@') entry.name = origin

// "If a line begins with a blank, then the owner is assumed to be the
// same as that of the previous RR" -- BIND 9 manual
if (entry.name === '' && lastName) entry.name = lastName

if (entry.name) {
entry.name = fullyQualify(entry.name, origin)
}
else {
entry.name = `${origin}`.toLowerCase()
}

if (entry.name !== lastName) lastName = entry.name

expandRdata(entry, origin, ttl)

try {
expanded.push(new RR[entry.type](entry))
}
catch (e) {
console.error(`I encounted this error: \n`)
console.error(e.message)
console.error(`\nwhile processing this RR: \n`)
console.log(entry)
}
}
return expanded
}

function fullyQualify (hostname, origin) {
if (hostname.endsWith('.')) return hostname
return `${hostname}.${origin}`.toLowerCase()
}

function expandRdata (entry, origin, ttl) {
switch (entry.type) {
case 'SOA':
for (const f of [ 'mname', 'rname' ]) {
entry[f] = fullyQualify(entry[f], origin)
}
break
case 'MX':
entry.exchange = fullyQualify(entry.exchange, origin)
break
case 'NS':
entry.dname = fullyQualify(entry.dname, origin)
break
}
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dns-zone-validator",
"version": "0.1.0",
"version": "0.2.0",
"description": "DNS Zone Validator",
"main": "index.js",
"scripts": {
Expand All @@ -9,7 +9,8 @@
"lint": "npx eslint index.js test/*.js",
"lintfix": "npx eslint --fix index.js test/*.js",
"postinstall": "npx -p nearley nearleyc src/bind-grammar.ne -o grammar.js",
"test": "npx mocha"
"test": "npx mocha",
"versions": "npx dependency-version-checker check"
},
"repository": {
"type": "git",
Expand All @@ -28,10 +29,11 @@
},
"homepage": "https://github.com/msimerson/dns-zone-validator#readme",
"devDependencies": {
"eslint": "^8.0.1",
"mocha": "^9.1.3"
"eslint": "^8.11.0",
"mocha": "^9.2.2"
},
"dependencies": {
"dns-resource-record": "^0.9.3",
"nearley": "^2.20.1"
}
}

0 comments on commit 0ac19a7

Please sign in to comment.