@nictool/dns-resource-record 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codeclimate.yml +25 -0
- package/CHANGELOG.md +253 -0
- package/DEVELOP.md +23 -0
- package/LICENSE +29 -0
- package/README.md +267 -0
- package/index.js +67 -0
- package/lib/tinydns.js +186 -0
- package/package.json +40 -0
- package/rr/a.js +65 -0
- package/rr/aaaa.js +137 -0
- package/rr/caa.js +129 -0
- package/rr/cert.js +75 -0
- package/rr/cname.js +76 -0
- package/rr/dname.js +74 -0
- package/rr/dnskey.js +117 -0
- package/rr/ds.js +104 -0
- package/rr/hinfo.js +84 -0
- package/rr/ipseckey.js +159 -0
- package/rr/key.js +73 -0
- package/rr/loc.js +216 -0
- package/rr/mx.js +86 -0
- package/rr/naptr.js +146 -0
- package/rr/ns.js +74 -0
- package/rr/nsec.js +61 -0
- package/rr/nsec3.js +99 -0
- package/rr/nsec3param.js +84 -0
- package/rr/openpgpkey.js +44 -0
- package/rr/ptr.js +65 -0
- package/rr/rrsig.js +101 -0
- package/rr/sig.js +100 -0
- package/rr/smimea.js +73 -0
- package/rr/soa.js +140 -0
- package/rr/spf.js +54 -0
- package/rr/srv.js +122 -0
- package/rr/sshfp.js +86 -0
- package/rr/tlsa.js +106 -0
- package/rr/txt.js +122 -0
- package/rr/uri.js +95 -0
- package/rr.js +291 -0
- package/test/a.js +76 -0
- package/test/aaaa.js +79 -0
- package/test/base.js +138 -0
- package/test/caa.js +104 -0
- package/test/cert.js +48 -0
- package/test/cname.js +41 -0
- package/test/dname.js +53 -0
- package/test/dnskey.js +44 -0
- package/test/ds.js +44 -0
- package/test/fake.js +26 -0
- package/test/hinfo.js +75 -0
- package/test/ipseckey.js +115 -0
- package/test/key.js +37 -0
- package/test/loc.js +71 -0
- package/test/mx.js +75 -0
- package/test/naptr.js +40 -0
- package/test/ns.js +53 -0
- package/test/nsec.js +38 -0
- package/test/nsec3.js +26 -0
- package/test/nsec3param.js +26 -0
- package/test/openpgpkey.js +35 -0
- package/test/ptr.js +54 -0
- package/test/rr.js +196 -0
- package/test/smimea.js +45 -0
- package/test/soa.js +77 -0
- package/test/spf.js +48 -0
- package/test/srv.js +81 -0
- package/test/sshfp.js +62 -0
- package/test/tinydns.js +140 -0
- package/test/tlsa.js +54 -0
- package/test/txt.js +70 -0
- package/test/uri.js +61 -0
package/lib/tinydns.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
|
|
2
|
+
export const SPECIALCHARS = {
|
|
3
|
+
'+': [ 'A' ],
|
|
4
|
+
'-': [ undefined ], // disabled RR
|
|
5
|
+
'%': [ 'location' ],
|
|
6
|
+
'.': [ 'SOA', 'NS', 'A' ],
|
|
7
|
+
'&': [ 'NS', 'A' ],
|
|
8
|
+
'=': [ 'A', 'PTR' ],
|
|
9
|
+
'@': [ 'MX', 'A' ],
|
|
10
|
+
'#': [ 'comment' ],
|
|
11
|
+
"'": [ 'TXT' ],
|
|
12
|
+
'^': [ 'PTR' ],
|
|
13
|
+
'C': [ 'CNAME' ],
|
|
14
|
+
'Z': [ 'SOA' ],
|
|
15
|
+
':': [ 'generic' ],
|
|
16
|
+
'3': [ 'AAAA' ],
|
|
17
|
+
'6': [ 'AAAA', 'PTR' ],
|
|
18
|
+
'S': [ 'SRV' ],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const octalRe = new RegExp(/\\(?:[1-7][0-7]{0,2}|[0-7]{2,3})/, 'g')
|
|
22
|
+
|
|
23
|
+
export function escapeOctal (re, str) {
|
|
24
|
+
let escaped = ''
|
|
25
|
+
str.split(/(.{1})/g).map(c => {
|
|
26
|
+
escaped += re.test(c) ? charToOctal(c) : c
|
|
27
|
+
})
|
|
28
|
+
return escaped
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function octalToChar (str) {
|
|
32
|
+
// relace instances of \NNN with ASCII
|
|
33
|
+
return str.replace(octalRe, o => String.fromCharCode(parseInt(o.substring(1), 8)))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function octalToHex (str) {
|
|
37
|
+
// relace instances of \NNN with Hex
|
|
38
|
+
return str.replace(octalRe, o => {
|
|
39
|
+
// parseInt(n, 8) -> from octal to decimal
|
|
40
|
+
// .toString(16) -> decimal to hex
|
|
41
|
+
return parseInt(o.substring(1), 8).toString(16).padStart(2, 0)
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function octalToUInt8 (str) {
|
|
46
|
+
const b = Buffer.alloc(1)
|
|
47
|
+
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
48
|
+
return b.readUInt8()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function octalToUInt16 (str) {
|
|
52
|
+
const b = Buffer.alloc(2)
|
|
53
|
+
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
54
|
+
b.writeUInt8(parseInt(str.substring(5,8), 8), 1)
|
|
55
|
+
return b.readUInt16BE()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function octalToUInt32 (str) {
|
|
59
|
+
const b = Buffer.alloc(4)
|
|
60
|
+
b.writeUInt8(parseInt(str.substring(1,4), 8), 0)
|
|
61
|
+
b.writeUInt8(parseInt(str.substring(5,8), 8), 1)
|
|
62
|
+
b.writeUInt8(parseInt(str.substring(9,12), 8), 2)
|
|
63
|
+
b.writeUInt8(parseInt(str.substring(13,16), 8), 3)
|
|
64
|
+
return b.readUInt32BE()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function packString (str) {
|
|
68
|
+
return str.match(/(.{1,255})/g).map(s => {
|
|
69
|
+
const len = Buffer.alloc(1)
|
|
70
|
+
len.writeUInt8(s.length)
|
|
71
|
+
return `${UInt8toOctal(len.readUInt8(0))}${s}`
|
|
72
|
+
}).join('')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function unpackString (str) {
|
|
76
|
+
const asBuf = Buffer.from(octalToChar(str.toString()))
|
|
77
|
+
const res = []
|
|
78
|
+
let pos = 0
|
|
79
|
+
let len
|
|
80
|
+
while ((len = asBuf.readUInt8(pos))) { // encoded length byte
|
|
81
|
+
pos++
|
|
82
|
+
res.push(asBuf.slice(pos, pos + len).toString())
|
|
83
|
+
pos = +(pos + len)
|
|
84
|
+
if (pos >= asBuf.length) break
|
|
85
|
+
}
|
|
86
|
+
return res
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function packDomainName (fqdn) {
|
|
90
|
+
const labelRegEx = new RegExp(/[^A-Za-z0-9-.]/, 'g')
|
|
91
|
+
|
|
92
|
+
// RFC 1035, 3.3 Standard RRs
|
|
93
|
+
// The standard wire format for DNS names. (1 octet length + octets)
|
|
94
|
+
let packed = ''
|
|
95
|
+
fqdn.split('.').map(label => {
|
|
96
|
+
if (label === undefined || !label.length) return
|
|
97
|
+
|
|
98
|
+
const len = Buffer.alloc(1)
|
|
99
|
+
len.writeUInt8(label.length)
|
|
100
|
+
packed += UInt8toOctal(len.readUInt8(0))
|
|
101
|
+
|
|
102
|
+
packed += escapeOctal(labelRegEx, label)
|
|
103
|
+
})
|
|
104
|
+
packed += '\\000' // terminates with a zero length label
|
|
105
|
+
return packed
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function unpackDomainName (fqdn) {
|
|
109
|
+
|
|
110
|
+
fqdn = Buffer.from(octalToChar(fqdn.toString()))
|
|
111
|
+
|
|
112
|
+
const labels = []
|
|
113
|
+
let pos = 0
|
|
114
|
+
let len
|
|
115
|
+
while ((len = fqdn.readUInt8(pos))) { // encoded length byte
|
|
116
|
+
pos++
|
|
117
|
+
labels.push(fqdn.slice(pos, pos + len).toString())
|
|
118
|
+
pos = +(pos + len)
|
|
119
|
+
}
|
|
120
|
+
const r = `${labels.join('.')}.`
|
|
121
|
+
// char position + length of last label + label length chars + null byte
|
|
122
|
+
const strLen = pos + len + labels.length * 4 + 1
|
|
123
|
+
return [ r, strLen ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function packHex (str) {
|
|
127
|
+
let r = ''
|
|
128
|
+
for (let i = 0; i < str.length; i = i+2) {
|
|
129
|
+
// nibble off 2 hex bytes, encode to octal
|
|
130
|
+
r += UInt8toOctal(parseInt(str.slice(i, i+2), 16))
|
|
131
|
+
}
|
|
132
|
+
return r
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function charToOctal (c) {
|
|
136
|
+
if (typeof c === 'number') return UInt8toOctal(c)
|
|
137
|
+
|
|
138
|
+
return UInt8toOctal(c.charCodeAt(0))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function UInt8toOctal (n) {
|
|
142
|
+
if (n > 255) throw new Error('UInt8toOctal does not work on numbers > 255')
|
|
143
|
+
|
|
144
|
+
return `\\${parseInt(n, 10).toString(8).padStart(3, 0)}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function UInt16toOctal (n) {
|
|
148
|
+
let r = ''
|
|
149
|
+
const pri = Buffer.alloc(2)
|
|
150
|
+
pri.writeUInt16BE(n)
|
|
151
|
+
r += UInt8toOctal(pri.readUInt8(0))
|
|
152
|
+
r += UInt8toOctal(pri.readUInt8(1))
|
|
153
|
+
return r
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function UInt32toOctal (n) {
|
|
157
|
+
let r = ''
|
|
158
|
+
const pri = Buffer.alloc(4)
|
|
159
|
+
pri.writeUInt32BE(n)
|
|
160
|
+
for (let i = 0; i < 4; i++) {
|
|
161
|
+
r += UInt8toOctal(pri.readUInt8(i))
|
|
162
|
+
}
|
|
163
|
+
return r
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function ipv4toOctal (ip) {
|
|
167
|
+
return UInt32toOctal(ip.split`.`.reduce((int, value) => int * 256 + +value))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function octalToIPv4 (str) {
|
|
171
|
+
const asInt = octalToUInt32(str)
|
|
172
|
+
return [ 24,16,8,0 ].map(n => (asInt >> n) & 0xff).join('.')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function base64toOctal (str) {
|
|
176
|
+
const bytes = Buffer.from(str, 'base64')
|
|
177
|
+
let escaped = ''
|
|
178
|
+
for (const b of bytes) {
|
|
179
|
+
escaped += /[A-Za-z0-9\-.]/.test(String.fromCharCode(b)) ? String.fromCharCode(b) : UInt8toOctal(b)
|
|
180
|
+
}
|
|
181
|
+
return escaped
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function octalToBase64 (str) {
|
|
185
|
+
return Buffer.from(octalToChar(str), 'binary').toString('base64')
|
|
186
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nictool/dns-resource-record",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "DNS Resource Records",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "npx eslint *.js lib rr test",
|
|
9
|
+
"lintfix": "npx eslint --fix *.js lib rr test",
|
|
10
|
+
"test": "npx mocha",
|
|
11
|
+
"versions": "npx dependency-version-checker check"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+ssh://git@github.com/NicTool/dns-resource-record.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"DNS",
|
|
19
|
+
"resource",
|
|
20
|
+
"record",
|
|
21
|
+
"validator",
|
|
22
|
+
"validation",
|
|
23
|
+
"import",
|
|
24
|
+
"export",
|
|
25
|
+
"BIND",
|
|
26
|
+
"djbdns",
|
|
27
|
+
"tinydns",
|
|
28
|
+
"nictool"
|
|
29
|
+
],
|
|
30
|
+
"author": "Matt Simerson <matt@tnpi.net>",
|
|
31
|
+
"license": "BSD-3-Clause",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/NicTool/dns-resource-record/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/NicTool/dns-resource-record#readme",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"eslint": "^8.11.0",
|
|
38
|
+
"mocha": "^9.2.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/rr/a.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
import net from 'net'
|
|
3
|
+
|
|
4
|
+
import RR from '../rr.js'
|
|
5
|
+
|
|
6
|
+
export default class A extends RR {
|
|
7
|
+
constructor (opts) {
|
|
8
|
+
super(opts)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/****** Resource record specific setters *******/
|
|
12
|
+
setAddress (val) {
|
|
13
|
+
if (!val) throw new Error('A: address is required')
|
|
14
|
+
if (!net.isIPv4(val)) throw new Error('A address must be IPv4')
|
|
15
|
+
this.set('address', val)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getDescription () {
|
|
19
|
+
return 'Address'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getRdataFields (arg) {
|
|
23
|
+
return [ 'address' ]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getRFCs () {
|
|
27
|
+
return [ 1035 ]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getTypeId () {
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/****** IMPORTERS *******/
|
|
35
|
+
fromTinydns (opts) {
|
|
36
|
+
// +fqdn:ip:ttl:timestamp:lo
|
|
37
|
+
const [ owner, ip, ttl, ts, loc ] = opts.tinyline.substring(1).split(':')
|
|
38
|
+
|
|
39
|
+
return new A({
|
|
40
|
+
owner : this.fullyQualify(owner),
|
|
41
|
+
type : 'A',
|
|
42
|
+
address : ip,
|
|
43
|
+
ttl : parseInt(ttl, 10),
|
|
44
|
+
timestamp: ts,
|
|
45
|
+
location : loc !== '' && loc !== '\n' ? loc : '',
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fromBind (opts) {
|
|
50
|
+
// test.example.com 3600 IN A 192.0.2.127
|
|
51
|
+
const [ owner, ttl, c, type, address ] = opts.bindline.split(/\s+/)
|
|
52
|
+
return new A({
|
|
53
|
+
owner,
|
|
54
|
+
ttl : parseInt(ttl, 10),
|
|
55
|
+
class: c,
|
|
56
|
+
type,
|
|
57
|
+
address,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/****** EXPORTERS *******/
|
|
62
|
+
toTinydns () {
|
|
63
|
+
return `+${this.getTinyFQDN('owner')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
|
|
64
|
+
}
|
|
65
|
+
}
|
package/rr/aaaa.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
|
|
2
|
+
import net from 'net'
|
|
3
|
+
|
|
4
|
+
import RR from '../rr.js'
|
|
5
|
+
import * as TINYDNS from '../lib/tinydns.js'
|
|
6
|
+
|
|
7
|
+
export default class AAAA extends RR {
|
|
8
|
+
constructor (opts) {
|
|
9
|
+
super(opts)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/****** Resource record specific setters *******/
|
|
13
|
+
setAddress (val) {
|
|
14
|
+
if (!val) throw new Error('AAAA: address is required')
|
|
15
|
+
if (!net.isIPv6(val)) throw new Error(`AAAA: address must be IPv6 (${val})`)
|
|
16
|
+
|
|
17
|
+
this.set('address', this.expand(val.toLowerCase())) // lower case: RFC 5952
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getCompressed (val) {
|
|
21
|
+
this.compress(val || this.get('address'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getDescription () {
|
|
25
|
+
return 'Address IPv6'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getRdataFields (arg) {
|
|
29
|
+
return [ 'address' ]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getRFCs () {
|
|
33
|
+
return [ 3596 ]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getTypeId () {
|
|
37
|
+
return 28
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/****** IMPORTERS *******/
|
|
41
|
+
fromTinydns (opts) {
|
|
42
|
+
const str = opts.tinyline
|
|
43
|
+
let fqdn, ip, n, rdata, ttl, ts, loc
|
|
44
|
+
|
|
45
|
+
switch (str[0]) {
|
|
46
|
+
case ':':
|
|
47
|
+
// GENERIC => :fqdn:28:rdata:ttl:timestamp:lo
|
|
48
|
+
[ fqdn, n, rdata, ttl, ts, loc ] = str.substring(1).split(':')
|
|
49
|
+
if (n != 28) throw new Error('AAAA fromTinydns, invalid n')
|
|
50
|
+
ip = TINYDNS.octalToHex(rdata).match(/([0-9a-fA-F]{4})/g).join(':')
|
|
51
|
+
break
|
|
52
|
+
case '3':
|
|
53
|
+
case '6':
|
|
54
|
+
// AAAA => 3fqdn:ip:x:ttl:timestamp:lo
|
|
55
|
+
// AAAA,PTR => 6fqdn:ip:x:ttl:timestamp:lo
|
|
56
|
+
[ fqdn, rdata, ttl, ts, loc ] = str.substring(1).split(':')
|
|
57
|
+
ip = rdata.match(/(.{4})/g).join(':')
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new AAAA({
|
|
62
|
+
owner : this.fullyQualify(fqdn),
|
|
63
|
+
ttl : parseInt(ttl, 10),
|
|
64
|
+
type : 'AAAA',
|
|
65
|
+
address : ip,
|
|
66
|
+
timestamp: ts,
|
|
67
|
+
location : loc !== '' && loc !== '\n' ? loc : '',
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fromBind (opts) {
|
|
72
|
+
// test.example.com 3600 IN AAAA ...
|
|
73
|
+
const [ owner, ttl, c, type, ip ] = opts.bindline.split(/\s+/)
|
|
74
|
+
return new AAAA({
|
|
75
|
+
owner,
|
|
76
|
+
ttl : parseInt(ttl, 10),
|
|
77
|
+
class : c,
|
|
78
|
+
type,
|
|
79
|
+
address: this.expand(ip),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
compress (val) {
|
|
84
|
+
/*
|
|
85
|
+
* RFC 5952
|
|
86
|
+
* 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
|
|
87
|
+
* 4.2.1 The use of the symbol "::" MUST be used to its maximum capability.
|
|
88
|
+
* 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
|
|
89
|
+
* 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
|
|
90
|
+
* 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
|
|
91
|
+
*/
|
|
92
|
+
let r = val
|
|
93
|
+
.replace(/0000/g, '0') // 4.1 0000 -> 0
|
|
94
|
+
.replace(/:0+([1-9a-fA-F])/g, ':$1') // 4.1 remove leading zeros
|
|
95
|
+
|
|
96
|
+
const mostConsecutiveZeros = [
|
|
97
|
+
new RegExp(/0?(?::0){6,}:0?/),
|
|
98
|
+
new RegExp(/0?(?::0){5,}:0?/),
|
|
99
|
+
new RegExp(/0?(?::0){4,}:0?/),
|
|
100
|
+
new RegExp(/0?(?::0){3,}:0?/),
|
|
101
|
+
new RegExp(/0?(?::0){2,}:0?/),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
for (const re of mostConsecutiveZeros) {
|
|
105
|
+
if (re.test(r)) {
|
|
106
|
+
r = r.replace(re, '::')
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return r
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
expand (val, delimiter) {
|
|
115
|
+
if (delimiter === undefined) delimiter = ':'
|
|
116
|
+
|
|
117
|
+
const colons = val.match(/:/g)
|
|
118
|
+
if (colons && colons.length < 7) {
|
|
119
|
+
// console.log(`AAAA: restoring compressed colons`)
|
|
120
|
+
val = val.replace(/::/, ':'.repeat(9 - colons.length))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// restore compressed leading zeros
|
|
124
|
+
return val.split(':').map(s => s.padStart(4, 0)).join(delimiter)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/****** EXPORTERS *******/
|
|
128
|
+
toBind (zone_opts) {
|
|
129
|
+
return `${this.getPrefix(zone_opts)}\t${this.compress(this.get('address'))}\n`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toTinydns () {
|
|
133
|
+
// from AAAA notation (8 groups of 4 hex digits) to 16 escaped octals
|
|
134
|
+
const rdata = TINYDNS.packHex(this.expand(this.get('address'), ''))
|
|
135
|
+
return this.getTinydnsGeneric(rdata)
|
|
136
|
+
}
|
|
137
|
+
}
|
package/rr/caa.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
|
|
2
|
+
import RR from '../rr.js'
|
|
3
|
+
import * as TINYDNS from '../lib/tinydns.js'
|
|
4
|
+
|
|
5
|
+
export default class CAA extends RR {
|
|
6
|
+
constructor (opts) {
|
|
7
|
+
super(opts)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/****** Resource record specific setters *******/
|
|
11
|
+
setFlags (val) {
|
|
12
|
+
this.is8bitInt('CAA', 'flags', val)
|
|
13
|
+
|
|
14
|
+
if (![ 0, 128 ].includes(val)) {
|
|
15
|
+
throw new Error(`CAA flags ${val} not recognized, ${this.citeRFC()}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.set('flags', val)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setTag (val) {
|
|
22
|
+
if (typeof val !== 'string'
|
|
23
|
+
|| val.length < 1
|
|
24
|
+
|| /[^a-z0-9]/.test(val))
|
|
25
|
+
throw new Error(`CAA tag must be a sequence of ASCII letters and numbers in lowercase, ${this.citeRFC()}`)
|
|
26
|
+
|
|
27
|
+
if (![ 'issue', 'issuewild', 'iodef' ].includes(val)) {
|
|
28
|
+
throw new Error(`CAA tag ${val} not recognized: ${this.citeRFC()}`)
|
|
29
|
+
}
|
|
30
|
+
this.set('tag', val)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setValue (val) {
|
|
34
|
+
// either (2) a quoted string or
|
|
35
|
+
// (1) a contiguous set of characters without interior spaces
|
|
36
|
+
if (this.isQuoted(val)) {
|
|
37
|
+
val = val.replace(/^["']|["']$/g, '') // strip quotes
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// if (/\s/.test(val)) throw new Error(`CAA value may not have spaces unless quoted: RFC 8659`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// check if val starts with one of iodefSchemes
|
|
44
|
+
if (this.get('tag') === 'iodef') {
|
|
45
|
+
const iodefSchemes = [ 'mailto:', 'http:', 'https:' ]
|
|
46
|
+
if (!iodefSchemes.filter(s => val.startsWith(s)).length) {
|
|
47
|
+
throw new Error(`CAA value must have valid iodefScheme prefix, ${this.citeRFC()}`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.set('value', val)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getDescription () {
|
|
55
|
+
return 'Certification Authority Authorization'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getQuotedFields () {
|
|
59
|
+
return [ 'value' ]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getRdataFields (arg) {
|
|
63
|
+
return [ 'flags', 'tag', 'value' ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getRFCs () {
|
|
67
|
+
return [ 6844, 8659 ]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getTypeId () {
|
|
71
|
+
return 257
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/****** IMPORTERS *******/
|
|
75
|
+
fromTinydns (opts) {
|
|
76
|
+
// CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
|
|
77
|
+
const [ fqdn, n, rdata, ttl, ts, loc ] = opts.tinyline.substring(1).split(':')
|
|
78
|
+
if (n != 257) throw new Error('CAA fromTinydns, invalid n')
|
|
79
|
+
|
|
80
|
+
const flags = TINYDNS.octalToUInt8(rdata.substring(0, 4))
|
|
81
|
+
const taglen = TINYDNS.octalToUInt8(rdata.substring(4, 8))
|
|
82
|
+
|
|
83
|
+
const unescaped = TINYDNS.octalToChar(rdata.substring(8))
|
|
84
|
+
const tag = unescaped.substring(0, taglen)
|
|
85
|
+
const fingerprint = unescaped.substring(taglen)
|
|
86
|
+
|
|
87
|
+
return new CAA({
|
|
88
|
+
owner : this.fullyQualify(fqdn),
|
|
89
|
+
ttl : parseInt(ttl, 10),
|
|
90
|
+
type : 'CAA',
|
|
91
|
+
flags,
|
|
92
|
+
tag,
|
|
93
|
+
value : fingerprint,
|
|
94
|
+
timestamp: ts,
|
|
95
|
+
location : loc !== '' && loc !== '\n' ? loc : '',
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fromBind (opts) {
|
|
100
|
+
// test.example.com 3600 IN CAA flags, tags, value
|
|
101
|
+
const fields = opts.bindline.match(/^([^\s]+)\s+([0-9]+)\s+(\w+)\s+(\w+)\s+([0-9]+)\s+(\w+)\s+("[^"]+"|[^\s]+?)\s*$/i)
|
|
102
|
+
if (!fields) throw new Error(`unable to parse: ${opts.bindline}`)
|
|
103
|
+
|
|
104
|
+
const [ owner, ttl, c, type, flags, tag, value ] = fields.slice(1)
|
|
105
|
+
return new CAA({
|
|
106
|
+
owner,
|
|
107
|
+
ttl : parseInt(ttl, 10),
|
|
108
|
+
class: c,
|
|
109
|
+
type,
|
|
110
|
+
flags: parseInt(flags, 10),
|
|
111
|
+
tag,
|
|
112
|
+
value,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/****** EXPORTERS *******/
|
|
117
|
+
|
|
118
|
+
toTinydns () {
|
|
119
|
+
let rdata = ''
|
|
120
|
+
rdata += TINYDNS.UInt8toOctal(this.get('flags'))
|
|
121
|
+
|
|
122
|
+
rdata += TINYDNS.UInt8toOctal(this.get('tag').length)
|
|
123
|
+
rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag'))
|
|
124
|
+
|
|
125
|
+
rdata += TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.getQuoted('value'))
|
|
126
|
+
|
|
127
|
+
return this.getTinydnsGeneric(rdata)
|
|
128
|
+
}
|
|
129
|
+
}
|
package/rr/cert.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
import RR from '../rr.js'
|
|
3
|
+
|
|
4
|
+
export default class CERT extends RR {
|
|
5
|
+
constructor (opts) {
|
|
6
|
+
super(opts)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/****** Resource record specific setters *******/
|
|
10
|
+
setCertType (val) {
|
|
11
|
+
// The type field is the certificate type
|
|
12
|
+
// the type field as an unsigned decimal integer or as a mnemonic symbol
|
|
13
|
+
// this.is16bitInt('CERT', 'type', val)
|
|
14
|
+
|
|
15
|
+
this.set('cert type', val)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setKeyTag (val) {
|
|
19
|
+
// The key tag field is the 16-bit value
|
|
20
|
+
// The key tag field is represented as an unsigned decimal integer.
|
|
21
|
+
|
|
22
|
+
this.is16bitInt('CERT', 'key tag', val)
|
|
23
|
+
|
|
24
|
+
this.set('key tag', val)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setAlgorithm (val) {
|
|
28
|
+
// The algorithm field has the same meaning as the algorithm field in DNSKEY
|
|
29
|
+
// The algorithm field is represented as an unsigned decimal integer
|
|
30
|
+
this.is8bitInt('CERT', 'algorithm', val)
|
|
31
|
+
|
|
32
|
+
this.set('algorithm', val)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setCertificate (val) {
|
|
36
|
+
// certificate/CRL portion is represented in base 64 [16] and may be
|
|
37
|
+
// divided into any number of white-space-separated substrings
|
|
38
|
+
this.set('certificate', val)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getDescription () {
|
|
42
|
+
return 'Certificate'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getRdataFields () {
|
|
46
|
+
return [ 'cert type', 'key tag', 'algorithm', 'certificate' ]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getRFCs () {
|
|
50
|
+
return [ 2538, 4398 ]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getTypeId () {
|
|
54
|
+
return 37
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/****** IMPORTERS *******/
|
|
58
|
+
|
|
59
|
+
fromBind (opts) {
|
|
60
|
+
// test.example.com 3600 IN CERT certtype, keytag, algo, cert
|
|
61
|
+
const [ owner, ttl, c, type, certtype, keytag, algo, certificate ] = opts.bindline.split(/\s+/)
|
|
62
|
+
return new CERT({
|
|
63
|
+
owner,
|
|
64
|
+
ttl : parseInt(ttl, 10),
|
|
65
|
+
class : c,
|
|
66
|
+
type,
|
|
67
|
+
'cert type': /^[0-9]+$/.test(certtype) ? parseInt(certtype, 10) : certtype,
|
|
68
|
+
'key tag' : parseInt(keytag, 10),
|
|
69
|
+
algorithm : parseInt(algo, 10),
|
|
70
|
+
certificate,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/****** EXPORTERS *******/
|
|
75
|
+
}
|
package/rr/cname.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
import net from 'net'
|
|
3
|
+
|
|
4
|
+
import RR from '../rr.js'
|
|
5
|
+
|
|
6
|
+
export default class CNAME extends RR {
|
|
7
|
+
constructor (opts) {
|
|
8
|
+
super(opts)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/****** Resource record specific setters *******/
|
|
12
|
+
setCname (val) {
|
|
13
|
+
// A <domain-name> which specifies the canonical or primary
|
|
14
|
+
// name for the owner. The owner name is an alias.
|
|
15
|
+
|
|
16
|
+
if (!val) throw new Error('CNAME: cname is required')
|
|
17
|
+
|
|
18
|
+
if (net.isIPv4(val) || net.isIPv6(val))
|
|
19
|
+
throw new Error(`CNAME: cname must be a FQDN: RFC 2181`)
|
|
20
|
+
|
|
21
|
+
if (!this.isFullyQualified('CNAME', 'cname', val)) return
|
|
22
|
+
if (!this.isValidHostname('CNAME', 'cname', val)) return
|
|
23
|
+
|
|
24
|
+
// RFC 4034: letters in the DNS names are lower cased
|
|
25
|
+
this.set('cname', val.toLowerCase())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getDescription () {
|
|
29
|
+
return 'Canonical Name'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getRdataFields (arg) {
|
|
33
|
+
return [ 'cname' ]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getRFCs () {
|
|
37
|
+
return [ 1035, 2181 ]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getTypeId () {
|
|
41
|
+
return 5
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/****** IMPORTERS *******/
|
|
45
|
+
fromTinydns (opts) {
|
|
46
|
+
// Cfqdn:p:ttl:timestamp:lo
|
|
47
|
+
const [ fqdn, p, ttl, ts, loc ] = opts.tinyline.substring(1).split(':')
|
|
48
|
+
|
|
49
|
+
return new CNAME({
|
|
50
|
+
owner : this.fullyQualify(fqdn),
|
|
51
|
+
ttl : parseInt(ttl, 10),
|
|
52
|
+
type : 'CNAME',
|
|
53
|
+
cname : this.fullyQualify(p),
|
|
54
|
+
timestamp: ts,
|
|
55
|
+
location : loc !== '' && loc !== '\n' ? loc : '',
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fromBind (opts) {
|
|
60
|
+
// test.example.com 3600 IN CNAME ...
|
|
61
|
+
const [ owner, ttl, c, type, cname ] = opts.bindline.split(/\s+/)
|
|
62
|
+
return new CNAME({
|
|
63
|
+
owner,
|
|
64
|
+
ttl : parseInt(ttl, 10),
|
|
65
|
+
class: c,
|
|
66
|
+
type,
|
|
67
|
+
cname,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/****** EXPORTERS *******/
|
|
72
|
+
|
|
73
|
+
toTinydns () {
|
|
74
|
+
return `C${this.getTinyFQDN('owner')}:${this.get('cname')}:${this.getTinydnsPostamble()}\n`
|
|
75
|
+
}
|
|
76
|
+
}
|