Skip to content

Commit

Permalink
Merge e09d685 into 7f10c0d
Browse files Browse the repository at this point in the history
  • Loading branch information
msimerson committed Mar 22, 2022
2 parents 7f10c0d + e09d685 commit 9421fd7
Show file tree
Hide file tree
Showing 48 changed files with 316 additions and 200 deletions.
2 changes: 1 addition & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ checks:
plugins:
eslint:
enabled: true
channel: "eslint-7"
channel: "eslint-8"
config:
config: ".eslintrc.yaml"

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

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


#### 0.9.3 - 2022-03-22

- hasValidLabels: remove trailing dot, else split returns empty string
- rename fullyQualified -> isFullyQualified
- rename validHostname -> isValidHostname
- set('type') no longer falls back on constructor.name (didn't reliably inherit)
- new fns: getTinyFQDN, fullyQualify, getPrefix, getFQDN
- when loading RR classes, ignore files that don't end with .js
- rr/txt: support data as array (improves idempotency)
- fromTinydns: fully qualify hostnames
- toTinydns: strip trailing . upon export
- rename getCommonFields -> getPrefixFields
- TXT: import BIND format w/o mangling WS
- SPF inherits from TXT


#### 0.9.2 - 2022-03-18

- mx: weight -> preference
Expand All @@ -11,7 +28,7 @@
- on `index.is*` functions which throw, use declaratively
- class: add NONE and ANY
- validHostname: allow / char
- use \__dirname to find RR mods
- use \_\_dirname to find RR mods


#### 0.9.1 - 2022-03-14
Expand Down Expand Up @@ -115,4 +132,4 @@

#### 0.2.0 - 2021-10-16

- initial release & name grab
- initial release & name grab
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ PRs are welcome, especially PRs with tests.
| **TXT** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|
| **URI** |:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|:white_check_mark:|


## TIPS

- Domain names are stored fully qualified, absolute, with the trailing dot.
- Master Zone File expansions exist at another level
- fromBIND is regex based and is naive. [dns-zone-validator](https://github.com/msimerson/dns-zone-validator) has a much more robust parser.
-


## TODO

- [x] Change all IPs to use [RFC example/doc](https://en.wikipedia.org/wiki/Reserved_IP_addresses) address space
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "dns-resource-record",
"version": "0.9.2",
"version": "0.9.3",
"description": "DNS Resource Records",
"main": "rr/index.js",
"scripts": {
"cover": "NODE_ENV=cov npx nyc --reporter=lcovonly npm run test",
"lint": "npx eslint lib/*.js rr/*.js test/*.js",
"lintfix": "npx eslint --fix lib/*.js rr/*.js test/*.js",
"test": "npx mocha"
"test": "npx mocha",
"versions": "npx dependency-version-checker check"
},
"repository": {
"type": "git",
Expand All @@ -29,8 +30,8 @@
},
"homepage": "https://github.com/msimerson/dns-resource-record#readme",
"devDependencies": {
"eslint": "^8.0.1",
"mocha": "^9.1.3"
"eslint": "^8.11.0",
"mocha": "^9.2.2"
},
"dependencies": {}
}
4 changes: 2 additions & 2 deletions rr/a.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class A extends RR {
const [ fqdn, ip, ttl, ts, loc ] = str.substring(1).split(':')

return new this.constructor({
name : fqdn,
name : this.fullyQualify(fqdn),
type : 'A',
address : ip,
ttl : parseInt(ttl, 10),
Expand All @@ -60,7 +60,7 @@ class A extends RR {

/****** EXPORTERS *******/
toTinydns () {
return `+${this.get('name')}:${this.get('address')}:${this.getEmpty('ttl')}:${this.getEmpty('timestamp')}:${this.getEmpty('location')}\n`
return `+${this.getTinyFQDN('name')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
}
}

Expand Down
2 changes: 1 addition & 1 deletion rr/aaaa.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class AAAA extends RR {

return new this.constructor({
type : 'AAAA',
name : fqdn,
name : this.fullyQualify(fqdn),
address : ip,
ttl : parseInt(ttl, 10),
timestamp: ts,
Expand Down
2 changes: 1 addition & 1 deletion rr/caa.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class CAA extends RR {

return new this.constructor({
type : 'CAA',
name : fqdn,
name : this.fullyQualify(fqdn),
flags : flags,
tag : tag,
value : fingerprint,
Expand Down
8 changes: 4 additions & 4 deletions rr/cname.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class CNAME extends RR {
if (net.isIPv4(val) || net.isIPv6(val))
throw new Error(`CNAME: cname must be a FQDN: RFC 2181`)

if (!this.fullyQualified('CNAME', 'cname', val)) return
if (!this.validHostname('CNAME', 'cname', val)) return
if (!this.isFullyQualified('CNAME', 'cname', val)) return
if (!this.isValidHostname('CNAME', 'cname', val)) return
this.set('cname', val)
}

Expand All @@ -46,7 +46,7 @@ class CNAME extends RR {

return new this.constructor({
type : 'CNAME',
name : fqdn,
name : this.fullyQualify(fqdn),
cname : p,
ttl : parseInt(ttl, 10),
timestamp: ts,
Expand All @@ -69,7 +69,7 @@ class CNAME extends RR {
/****** EXPORTERS *******/

toTinydns () {
return `C${this.get('name')}:${this.get('cname')}:${this.getEmpty('ttl')}:${this.getEmpty('timestamp')}:${this.getEmpty('location')}\n`
return `C${this.getTinyFQDN('name')}:${this.get('cname')}:${this.getTinydnsPostamble()}\n`
}
}

Expand Down
6 changes: 3 additions & 3 deletions rr/dname.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class DNAME extends RR {
if (net.isIPv4(val) || net.isIPv6(val))
throw new Error(`DNAME: target must be a domain name: RFC 6672`)

this.fullyQualified('DNAME', 'target', val)
this.validHostname('DNAME', 'target', val)
this.isFullyQualified('DNAME', 'target', val)
this.isValidHostname('DNAME', 'target', val)

this.set('target', val)
}
Expand Down Expand Up @@ -45,7 +45,7 @@ class DNAME extends RR {

return new this.constructor({
type : 'DNAME',
name : fqdn,
name : this.fullyQualify(fqdn),
target : `${TINYDNS.unpackDomainName(rdata)}.`,
ttl : parseInt(ttl, 10),
timestamp: ts,
Expand Down
4 changes: 2 additions & 2 deletions rr/dnskey.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class DS extends RR {
}

/****** IMPORTERS *******/
fromTinydns (str) {
}
// fromTinydns (str) {
// }

fromBind (str) {
// test.example.com 3600 IN DNSKEY Flags Protocol Algorithm PublicKey
Expand Down
4 changes: 2 additions & 2 deletions rr/ds.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class DS extends RR {
}

/****** IMPORTERS *******/
fromTinydns (str) {
}
// fromTinydns (str) {
// }

fromBind (str) {
// test.example.com 3600 IN DS Key Tag Algorithm, Digest Type, Digest
Expand Down
1 change: 1 addition & 0 deletions rr/hinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class HINFO extends RR {
}

fromBind () {
throw new Error('HINFO not yet supported')
}

/****** EXPORTERS *******/
Expand Down
62 changes: 47 additions & 15 deletions rr/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class RR extends Map {
if (n.length < 1 || n.length > 255)
throw new Error('Domain names must have 1-255 octets (characters): RFC 2181')

this.isFullyQualified('', 'name', n)
this.hasValidLabels(n)

// wildcard records: RFC 1034, 4592
Expand Down Expand Up @@ -103,13 +104,22 @@ class RR extends Map {
}

setType (t) {
if (!module.exports[ t || this.constructor.name ])
if (module.exports[t] === undefined)
throw new Error(`type ${t} not supported (yet)`)

this.set('type', t || this.constructor.name)
this.set('type', t)
}

getCommonFields () {
fullyQualify (str) {
if (str.endsWith('.')) return str
return `${str}.`
}

getPrefix () {
return `${this.getFQDN('name')}\t${this.get('ttl')}\t${this.get('class')}\t${this.get('type')}`
}

getPrefixFields () {
const commonFields = [ 'name', 'ttl', 'class', 'type' ]
Object.freeze(commonFields)
return commonFields
Expand All @@ -134,30 +144,51 @@ class RR extends Map {
}

getRdataFields () {
return [ ]
return []
}

getFields (arg) {
switch (arg) {
case 'common':
return this.getCommonFields()
return this.getPrefixFields()
case 'rdata':
return this.getRdataFields()
default:
return this.getCommonFields().concat(this.getRdataFields())
return this.getPrefixFields().concat(this.getRdataFields())
}
}

getFQDN (field) {
if (this.get(field).endsWith('.')) return this.get(field)
return `${this.get(field)}.`
}

getTinyFQDN (field) {
const val = this.get(field)
if (val === '') return val // empty
if (val === '.') return val // null MX

// strip off trailing ., tinydns doesn't require it for FQDN
if (val.endsWith('.')) return val.slice(0, -1)

return val
}

getTinydnsGeneric (rdata) {
return `:${this.get('name')}:${this.getTypeId()}:${rdata}:${this.getEmpty('ttl')}:${this.getEmpty('timestamp')}:${this.getEmpty('location')}\n`
return `:${this.getTinyFQDN('name')}:${this.getTypeId()}:${rdata}:${this.getTinydnsPostamble()}\n`
}

getTinydnsPostamble () {
return [ 'ttl', 'timestamp', 'location' ].map(f => this.getEmpty(f)).join(':')
}

hasValidLabels (hostname) {
// RFC 952 defined valid hostnames
// RFC 1035 limited domain label chars to letters, digits, and hyphen
// RFC 1123 allowed hostnames to start with a digit
// RFC 2181 'any binary string can be used as the label'
for (const label of hostname.split('.')) {
const fq = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname
for (const label of fq.split('.')) {
if (label.length < 1 || label.length > 63)
throw new Error('Labels must have 1-63 octets (characters), RFC 2181')
}
Expand Down Expand Up @@ -194,20 +225,20 @@ class RR extends Map {
return /^["']/.test(val) && /["']$/.test(val)
}

fullyQualified (type, blah, hostname) {
if (hostname.slice(-1) === '.') return true
isFullyQualified (type, blah, hostname) {
if (hostname.endsWith('.')) return true

throw new Error(`${type}: ${blah} must be fully qualified`)
}

toBind () {
return `${this.getFields().map(f => this.getQuoted(f)).join('\t')}\n`
}

validHostname (type, field, hostname) {
isValidHostname (type, field, hostname) {
if (!/[^a-zA-Z0-9\-._/]/.test(hostname)) return true
throw new Error(`${type}, ${field} has invalid hostname characters`)
}

toBind () {
return `${this.getPrefix()}\t${this.getRdataFields().map(f => this.getQuoted(f)).join('\t')}\n`
}
}

module.exports = {
Expand All @@ -216,6 +247,7 @@ module.exports = {

const files = fs.readdirSync(path.join(__dirname))
for (let f of files) {
if (!f.endsWith('.js')) continue
f = path.basename(f, '.js')
if (f === 'index') continue
module.exports[f.toUpperCase()] = require(`./${f}`)
Expand Down
2 changes: 1 addition & 1 deletion rr/loc.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class LOC extends RR {

return new this.constructor({
type : 'LOC',
name : fqdn,
name : this.fullyQualify(fqdn),
address : this.toHuman(l),
ttl : parseInt(ttl, 10),
timestamp: ts,
Expand Down
14 changes: 9 additions & 5 deletions rr/mx.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class MX extends RR {
if (net.isIPv4(val) || net.isIPv6(val))
throw new Error(`MX: exchange must be a FQDN: ${this.getRFCs()}`)

this.fullyQualified('MX', 'exchange', val)
this.validHostname('MX', 'exchange', val)
this.isFullyQualified('MX', 'exchange', val)
this.isValidHostname('MX', 'exchange', val)

this.set('exchange', val)
}
Expand Down Expand Up @@ -51,8 +51,8 @@ class MX extends RR {

return new this.constructor({
type : 'MX',
name : fqdn,
exchange : x,
name : this.fullyQualify(fqdn),
exchange : this.fullyQualify(x),
preference: parseInt(preference, 10) || 0,
ttl : parseInt(ttl, 10),
timestamp : ts,
Expand All @@ -75,8 +75,12 @@ class MX extends RR {
}

/****** EXPORTERS *******/
toBind () {
return `${this.getPrefix()}\t${this.get('preference')}\t${this.getFQDN('exchange')}\n`
}

toTinydns () {
return `@${this.get('name')}::${this.get('exchange')}:${this.get('preference')}:${this.getEmpty('ttl')}:${this.getEmpty('timestamp')}:${this.getEmpty('location')}\n`
return `@${this.getTinyFQDN('name')}::${this.getTinyFQDN('exchange')}:${this.get('preference')}:${this.getTinydnsPostamble()}\n`
}
}

Expand Down
2 changes: 1 addition & 1 deletion rr/naptr.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class NAPTR extends RR {

const rec = {
type : 'NAPTR',
name : fqdn,
name : this.fullyQualify(fqdn),
ttl : parseInt(ttl, 10),
timestamp : ts,
location : loc !== '' && loc !== '\n' ? loc : '',
Expand Down
Loading

0 comments on commit 9421fd7

Please sign in to comment.