Skip to content

Commit

Permalink
feat: add NSD parser
Browse files Browse the repository at this point in the history
  • Loading branch information
msimerson committed Apr 8, 2022
1 parent d87f650 commit fa49d1d
Show file tree
Hide file tree
Showing 12 changed files with 517 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest ]
node-version: [ 14.x, 16.x ]
node-version: [ 14, 16 ]
fail-fast: false

steps:
Expand Down
30 changes: 30 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,33 @@ exports.parseKnotConfig = async str => {

return r
}

exports.parseNsdConfig = async str => {

// eslint-disable-next-line node/no-unpublished-require
const grammar = nearley.Grammar.fromCompiled(require('./dist/nsd.js'))
grammar.start = 'main'

const parser = new nearley.Parser(grammar)
parser.feed(str + os.EOL)
if (!str.endsWith(os.EOL)) parser.feed(os.EOL)

if (parser.results.length === 0) return []

const r = {}

const sections = [ 'key', 'pattern', 'remote-control', 'server', 'tls-auth', 'zone' ]

parser.results[0]
.filter(z => z !== null)
.map(z => {
for (const s of sections) {
if (z[s]) {
if (!r[s]) r[s] = []
r[s].push(z[s])
}
}
})

return r
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
"main": "index.js",
"bin": {},
"engines": {
"node": ">=12.0"
"node": ">=14.0"
},
"scripts": {
"bind": "npx -p nearley nearleyc src/bind.ne -o dist/bind.js",
"knot": "npx -p nearley nearleyc src/knot.ne -o dist/knot.js",
"nsd": "npx -p nearley nearleyc src/nsd.ne -o dist/nsd.js",
"nsd" : "npx -p nearley nearleyc src/nsd-moo.ne -o dist/nsd.js",
"mara": "npx -p nearley nearleyc src/mara.ne -o dist/mara.js",
"grammar": "npm run bind && npm run knot && npm run nsd",
"lint": "npx eslint index.js test/*.js",
"lintfix": "npx eslint --fix index.js test/*.js",
"postinstall": "npm run bind && npm run knot",
"postinstall": "npm run grammar",
"release": "npx standard-version",
"test": "npx mocha",
"versions": "npx dependency-version-checker check"
Expand Down
80 changes: 80 additions & 0 deletions src/bind-moo.ne
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@lexer lexer

main -> (comment | blank | block):* {% id %}

blank -> %blank {% id %}
comment -> %comment {% comment %}
block -> %options (opt | comment):+ %eoblock {% optValue %}
| %logging (opt | comment | optblock):+ %eoblock
| zone {% id %}

opt -> %opt {% optValue %}
optblock -> %optblock
zone -> %zone (zonefile | zonetype | opt | comment):+ %eoblock {% asZone %}
zonefile -> %zonefile {% asZoneFile %}
zonetype -> %zonetype {% asZoneType %}

@{%
const moo = require("moo");

const lexer = moo.compile({
blank: /^\s*?$[\n\r]/,
comment: /^\s*(?:\/\/|#)[^\n]*?\n/,
eoblock: /};\s*\n/,
zone: {
match: /\s*zone\s+[^\s]+\s+(?:in|IN)?\s*{\n/s,
value: f => f.trim().match(/zone\s+"([^"]+)"/)[1]
},
zonetype: /\s*type\s+(?:master|slave|hint);\n/,
zonefile: /\s*file\s+"[^"]*?";\n/,
logging: /\s*logging\s*{$\n/s,
options: /\s*options\s+{$\n/s,
opt: { match: /^\s*[^\n]*?;$\n/s, value: f => f.trim() },
optblock: /^\s*[^\n]+?{[^}]*?};\n/,
nl: { match: /\n\r/, lineBreaks: true },
ws: /[ \t]+/,
});

function comment (d) { return null }
function flatten (d) {
if (!d) return ''
return Array.isArray(d) ? d.flat(Infinity).join('') : d
}
function asBlock (d) {
// console.log(d)
return d
}
function optValue (d) {
// console.log(d)
if (d.type === 'obj') return d.value
return d
}
function asZoneType (d) {
// console.log(d[0].value)
return { ztype: d[0].value.trim().match(/type\s+(\w+?);/)[1] }
}
function asZoneFile (d) {
// console.log(d[0].value)
return { zfile: d[0].value.trim().match(/file\s+"([^\"]+)";/)[1] }
}
function asZone (z) {
if (Array.isArray(z)) {
if (z[0] === null) return z[0]
if (typeof z[0] === 'object') {
try {
return {
zone: z[0].value,
type: z[1].filter(y => y[0] && y[0].ztype)[0][0].ztype,
file: z[1].filter(y => y[0] && y[0].zfile)[0][0].zfile,
}
}
catch (e) {
console.log(e.message)
console.log(z)
}
}
}
return z
}

%}
51 changes: 51 additions & 0 deletions src/nsd-moo.ne
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@lexer lexer

main -> (
comment {% id %}
| blank {% id %}
| key {% id %}
| pattern {% id %}
| remote {% id %}
| server {% id %}
| tls {% id %}
| zone {% id %}
):* {% id %}

blank -> %blank {% comment %}
comment -> %comment {% comment %}
key_val -> %key_val {% id %}
key -> %key (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}
pattern -> %pattern (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}
remote -> %remote (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}
server -> %server (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}
tls -> %tls (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}
zone -> %zone (key_val {% asKeyValue %} | %comment {% comment %}):* {% asGroup %}

@{%
const moo = require("moo");

const lexer = moo.compile({
key : /^key:\s*?[\r\n]/,
pattern: /^pattern:\s*?[\r\n]/,
remote : /^remote-control:\s*?[\r\n]/,
server : /^server:\s*?[\r\n]/,
tls : /^tls-auth:\s*?[\r\n]/,
zone : /^zone:\s*?[\r\n]/,
key_val: /^[ \t]+[^\s]+?:[^\r\n#]+(?:#[^\r\n]+)?[\r\n]/,
blank : /^\s*?$[\r\n]/,
comment: /^\s*(?:#)[^\r\n]*?[\r\n]/,
});

function comment (d) { return null }

function asGroup (d) {
return { [d[0].type]: Object.assign({}, ...d[1].filter(e => typeof e !== 'null')) }
}

const kvRe = /^[ \t]+([^\s]+?):\s*?([^\r\n#]+)(#[^\r\n]+)?[\r\n]/

function asKeyValue (d) {
const match = d[0].value.match(kvRe)
return { [match[1]]: match[2].trim() }
}
%}
54 changes: 54 additions & 0 deletions src/nsd.ne
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# https://nsd.docs.nlnetlabs.nl/en/latest/manpages/nsd.conf.html

main -> (
blank {% id %}
| comment {% id %}
| key {% id %}
| pattern {% id %}
| remote {% id %}
| server {% id %}
| tlsauth {% id %}
| zone {% id %}
):*

blank -> _ eol {% asNull %}
comment -> _ "#" notEol eol {% asNull %}

key_val -> __ word ":" __ word (__ word):? (comment):? eol_ #{% asKeyVal %}

key -> "key" ":" eol (key_val {% id %}):* eol_ {% asTopObj %}
pattern -> "pattern" ":" eol (key_val {% id %}):* eol_ {% asTopObj %}
remote -> "remote-control" ":" eol (key_val {% id %}):* eol_ {% asTopObj %}
server -> "server" ":" eol (key_val {% id %}):* eol {% asTopObj %}
tlsauth -> "tls-auth" ":" eol (key_val {% id %}):* eol_ {% asTopObj %}
zone -> "zone" ":" eol (key_val {% id %} | comment {% id %}):* eol_ {% asTopObj %}

_ -> [ \t]:*
__ -> [ \t]:+
eol -> [\r\n] {% id %}
eol_ -> [\r\n]:* {% id %}
hex -> [A-Fa-f0-9] {% id %}
notEol -> [^\r\n]:*
word -> [A-Za-z-0-9_.@:/-]:+ {% flatten %}
| dqstring

dqstring -> "\"" dqcharacters "\"" {% d => Array.isArray(d[1]) ? d[1].join("") : d[1] %}
dqcharacters -> dqcharacter {% id %}
| dqcharacter dqcharacters {% d => [d[0], ...d[1]] %}
dqcharacter -> [^\"\\] {% id %}
| "\\" escape {% d => d[1] %}
escape -> ["\\/bfnrt] {% id %}
| "u" hex hex hex hex {% (d) => String.fromCharCode(`0x${d.slice(1,5).join("")}`) %}

@{%
function asArray (d) {
return Object.assign({ [d[3]]: d[6] }, ...d[9].filter(e => 'object' === typeof e))
}
function asTopObj (d) { return { [d[0]]: d[3] } }
function asKeyVal (d) { return { [d[1]]: d[4] } }
function asNull (d) { return null; }
function flatten (d) {
if (!d) return ''
return Array.isArray(d) ? d.flat(Infinity).join('') : d
}
%}
36 changes: 36 additions & 0 deletions test/fixtures/nsd/example.com
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
server:
# use this number of cpu cores
server-count: 1
# We recommend leaving this empty, otherwise use "/var/db/nsd/nsd.db"
database: ""
# the default file used for the nsd-control addzone and delzone commands
# zonelistfile: "/var/db/nsd/zone.list"
# The unprivileged user that will run NSD, can also be set to "" if
# user privilige protection is not needed
username: nsd
# Default file where all the log messages go
logfile: "/var/log/nsd.log"
# Use this pid file instead of the platform specific default
pidfile: "/var/run/nsd.pid"
# Enable if privilege "jail" is needed for unprivileged user. Note
# that other file paths may break when using chroot
# chroot: "/etc/nsd/"
# The default zone transfer file
# xfrdfile: "/var/db/nsd/xfrd.state"
# The default working directory before accessing zone files
# zonesdir: "/etc/nsd"

remote-control:
# this allows the use of 'nsd-control' to control NSD. The default is "no"
control-enable: yes
# the interface NSD listens to for nsd-control. The default is 127.0.0.1
control-interface: 127.0.0.1
# the key files that allow the use of 'nsd-control'. The default path is "/etc/nsd/". Create these using the 'nsd-control-setup' utility
server-key-file: /etc/nsd/nsd_server.key
server-cert-file: /etc/nsd/nsd_server.pem
control-key-file: /etc/nsd/nsd_control.key
control-cert-file: /etc/nsd/nsd_control.pem

zone:
name: example.com
zonefile: /etc/nsd/example.com.zone
24 changes: 24 additions & 0 deletions test/fixtures/nsd/ns2.cadillac.net.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
zone:
name: emeraldparadise.realty
zonefile: /data/nsd/emeraldparadise.realty

zone:
name: virus.realty
zonefile: /data/nsd/virus.realty

zone:
name: insanangelotx.realty
zonefile: /data/nsd/insanangelotx.realty

zone:
name: bluehawaii.realty
zonefile: /data/nsd/bluehawaii.realty

zone:
name: iz.feedback
zonefile: /data/nsd/iz.feedback

zone:
name: raveis.realty
zonefile: /data/nsd/raveis.realty

29 changes: 29 additions & 0 deletions test/fixtures/nsd/nsd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Example.com nsd.conf file
# This is a comment.

server:
server-count: 1 # use this number of cpu cores
database: "" # or use "/var/db/nsd/nsd.db"
zonelistfile: "/var/db/nsd/zone.list"
username: nsd
logfile: "/var/log/nsd.log"
pidfile: "/var/run/nsd.pid"
xfrdfile: "/var/db/nsd/xfrd.state"

zone:
name: example.com
zonefile: /etc/nsd/example.com.zone

zone:
# this server is master, 192.0.2.1 is the secondary.
name: masterzone.com
zonefile: /etc/nsd/masterzone.com.zone
notify: 192.0.2.1 NOKEY
provide-xfr: 192.0.2.1 NOKEY

zone:
# this server is secondary, 192.0.2.2 is master.
name: secondzone.com
zonefile: /etc/nsd/secondzone.com.zone
allow-notify: 192.0.2.2 NOKEY
request-xfr: 192.0.2.2 NOKEY
Loading

0 comments on commit fa49d1d

Please sign in to comment.