@amritk/nish-x86_64-linux 0.24.0 → 0.25.0
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/bin/nish +0 -0
- package/docs/AI.md +24 -4
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/nish.d.ts +100 -0
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +21 -0
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +44 -3
- package/runtime/runtime-os.c +115 -12
- package/scripts/build.sh +13 -3
- package/std/README.md +25 -8
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +132 -20
- package/std/compress/deflate.ts +638 -248
- package/std/compress/gzip.ts +20 -6
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +7 -2
- package/std/net/http1.ts +9 -9
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/dns-config` — `/etc/resolv.conf` and `/etc/hosts`, read from bytes
|
|
3
|
+
* the caller already has into fixed tables.
|
|
4
|
+
*
|
|
5
|
+
* Both files are local, but neither is trusted to be small or well formed: a
|
|
6
|
+
* container image, a DHCP client or a VPN writes them, and a resolver that
|
|
7
|
+
* believed a 40,000-entry search list or a line a megabyte long would spend a
|
|
8
|
+
* lookup on it. This module reads each into tables of fixed, documented size,
|
|
9
|
+
* and counts what did not fit rather than growing:
|
|
10
|
+
*
|
|
11
|
+
* import { DnsConfig, DnsHosts, dnsHostsFind, dnsParseHosts, dnsParseResolvConf } from "nish/net/dns-config"
|
|
12
|
+
* import { DNS_TYPE_A } from "nish/net/dns-message"
|
|
13
|
+
*
|
|
14
|
+
* const config: DnsConfig = new DnsConfig()
|
|
15
|
+
* dnsParseResolvConf(resolvBytes, 0, resolvLen, config)
|
|
16
|
+
* // config.servers[i * 18, +18) for i < config.serverCount, on port 53
|
|
17
|
+
* const hosts: DnsHosts = new DnsHosts()
|
|
18
|
+
* dnsParseHosts(hostsBytes, 0, hostsLen, hosts)
|
|
19
|
+
* const found: i32 = dnsHostsFind(hosts, "localhost", DNS_TYPE_A, 80, out, 8)
|
|
20
|
+
*
|
|
21
|
+
* **resolv.conf** is read as glibc reads it, minus what a stub resolver does
|
|
22
|
+
* not use. `nameserver` takes one IPv4 or IPv6 literal (no zone), and the first
|
|
23
|
+
* `DNS_MAX_SERVERS` (3) are kept; `search` and `domain` each replace the list,
|
|
24
|
+
* so the last one wins, and it holds at most `DNS_MAX_SEARCH` (6) domains and
|
|
25
|
+
* `DNS_MAX_SEARCH_BYTES` (256) bytes; `options` reads `ndots:n` (clamped to 0
|
|
26
|
+
* to 15), `timeout:n` (1 to 30 seconds) and `attempts:n` (1 to 5) and ignores
|
|
27
|
+
* every other option. With no nameserver line, the servers are 127.0.0.1 and
|
|
28
|
+
* ::1, as glibc's are.
|
|
29
|
+
*
|
|
30
|
+
* **hosts** is an address and then its names, one entry a line. At most
|
|
31
|
+
* `DNS_HOSTS_MAX_ENTRIES` entries and `DNS_HOSTS_MAX_NAMES` names are kept,
|
|
32
|
+
* in at most `DNS_HOSTS_MAX_NAME_BYTES` bytes; a name past a cap is dropped and
|
|
33
|
+
* counted. Names are kept in lowercase and found without case, with one
|
|
34
|
+
* trailing `.` ignored on either side; an IPv4 address answers only an A
|
|
35
|
+
* lookup and an IPv6 address only an AAAA one, `::ffff:a.b.c.d` being IPv4.
|
|
36
|
+
*
|
|
37
|
+
* **In both**, `#` and `;` start a comment that runs to the end of the line, a
|
|
38
|
+
* line no directive names is ignored, and a line longer than
|
|
39
|
+
* `DNS_CONFIG_MAX_LINE` bytes is skipped whole and counted in
|
|
40
|
+
* `skippedLines`, never cut to a shorter line that means something else.
|
|
41
|
+
* A window outside its array is a bug in the caller and panics; nothing in a
|
|
42
|
+
* file does.
|
|
43
|
+
*
|
|
44
|
+
* Addresses are the 18-byte form of `docs/LANGUAGE.md`, which
|
|
45
|
+
* `dnsParseAddress` writes from a literal. The record of what this module
|
|
46
|
+
* refuses and why is `docs/security/dns.md`.
|
|
47
|
+
*/
|
|
48
|
+
import { setU16BE, skipBlank } from "nish/bytes"
|
|
49
|
+
import { DNS_ADDRESS_BYTES, DNS_TYPE_A, DNS_TYPE_AAAA } from "nish/net/dns-message"
|
|
50
|
+
|
|
51
|
+
/** How many `nameserver` lines are kept; glibc's MAXNS. */
|
|
52
|
+
export const DNS_MAX_SERVERS: i32 = 3
|
|
53
|
+
/** How many search domains are kept. */
|
|
54
|
+
export const DNS_MAX_SEARCH: i32 = 6
|
|
55
|
+
/** How many bytes the search domains may take together. */
|
|
56
|
+
export const DNS_MAX_SEARCH_BYTES: i32 = 256
|
|
57
|
+
/** The longest line either parser reads; a longer one is skipped whole. */
|
|
58
|
+
export const DNS_CONFIG_MAX_LINE: i32 = 1024
|
|
59
|
+
/** The port every `nameserver` address carries. */
|
|
60
|
+
export const DNS_PORT: i32 = 53
|
|
61
|
+
/** The `ndots` without an option: a name with a dot is tried as it stands first. */
|
|
62
|
+
export const DNS_DEFAULT_NDOTS: i32 = 1
|
|
63
|
+
/** The highest `ndots:n` kept; a higher one is clamped to it, as glibc clamps. */
|
|
64
|
+
export const DNS_MAX_NDOTS: i32 = 15
|
|
65
|
+
/** The `timeout` without an option, in seconds. */
|
|
66
|
+
export const DNS_DEFAULT_TIMEOUT: i32 = 5
|
|
67
|
+
/** The highest `timeout:n` kept, in seconds; the lowest is 1. */
|
|
68
|
+
export const DNS_MAX_TIMEOUT: i32 = 30
|
|
69
|
+
/** The `attempts` without an option. */
|
|
70
|
+
export const DNS_DEFAULT_ATTEMPTS: i32 = 2
|
|
71
|
+
/** The highest `attempts:n` kept; the lowest is 1. */
|
|
72
|
+
export const DNS_MAX_ATTEMPTS: i32 = 5
|
|
73
|
+
|
|
74
|
+
/** How many hosts lines are kept. */
|
|
75
|
+
export const DNS_HOSTS_MAX_ENTRIES: i32 = 256
|
|
76
|
+
/** How many names all hosts entries may hold together. */
|
|
77
|
+
export const DNS_HOSTS_MAX_NAMES: i32 = 1024
|
|
78
|
+
/** How many bytes those names may take together. */
|
|
79
|
+
export const DNS_HOSTS_MAX_NAME_BYTES: i32 = 16384
|
|
80
|
+
/** The longest hosts name kept, the longest a DNS name can be. */
|
|
81
|
+
export const DNS_HOSTS_MAX_NAME: i32 = 253
|
|
82
|
+
|
|
83
|
+
const DNS_TAB: i32 = 9
|
|
84
|
+
const DNS_NEWLINE: i32 = 10
|
|
85
|
+
const DNS_RETURN: i32 = 13
|
|
86
|
+
const DNS_SPACE: i32 = 32
|
|
87
|
+
const DNS_HASH: i32 = 35
|
|
88
|
+
const DNS_DOT: i32 = 46
|
|
89
|
+
const DNS_COLON: i32 = 58
|
|
90
|
+
const DNS_SEMICOLON: i32 = 59
|
|
91
|
+
|
|
92
|
+
/** One resolv.conf, read by `dnsParseResolvConf`, made once and reusable. */
|
|
93
|
+
export class DnsConfig {
|
|
94
|
+
/** `serverCount` addresses of `DNS_ADDRESS_BYTES` each, on `DNS_PORT`. */
|
|
95
|
+
servers: u8[]
|
|
96
|
+
/** Sixteen bytes a literal is parsed into before it is kept, so a parse allocates nothing. */
|
|
97
|
+
scratch: u8[]
|
|
98
|
+
serverCount: i32 = 0
|
|
99
|
+
/** Nameserver lines past `DNS_MAX_SERVERS`, read and not kept. */
|
|
100
|
+
droppedServers: i32 = 0
|
|
101
|
+
/** The search domains, back to back, lowercase, without a trailing dot. */
|
|
102
|
+
search: u8[]
|
|
103
|
+
/** Where each of the `searchCount` domains starts in `search`, and its length. */
|
|
104
|
+
searchStart: i32[]
|
|
105
|
+
searchLength: i32[]
|
|
106
|
+
searchCount: i32 = 0
|
|
107
|
+
/** Search domains past `DNS_MAX_SEARCH` or `DNS_MAX_SEARCH_BYTES`, read and not kept. */
|
|
108
|
+
droppedSearch: i32 = 0
|
|
109
|
+
/** Dots a name needs to be tried as it stands before the search list. */
|
|
110
|
+
ndots: i32
|
|
111
|
+
/** Seconds to wait for one server's reply. */
|
|
112
|
+
timeout: i32
|
|
113
|
+
/** Rounds through the servers before giving up. */
|
|
114
|
+
attempts: i32
|
|
115
|
+
/** Lines longer than `DNS_CONFIG_MAX_LINE`, skipped whole. */
|
|
116
|
+
skippedLines: i32 = 0
|
|
117
|
+
|
|
118
|
+
constructor() {
|
|
119
|
+
this.servers = new Array<u8>(DNS_MAX_SERVERS * DNS_ADDRESS_BYTES)
|
|
120
|
+
this.scratch = new Array<u8>(16)
|
|
121
|
+
this.search = new Array<u8>(DNS_MAX_SEARCH_BYTES)
|
|
122
|
+
this.searchStart = new Array<i32>(DNS_MAX_SEARCH)
|
|
123
|
+
this.searchLength = new Array<i32>(DNS_MAX_SEARCH)
|
|
124
|
+
this.ndots = DNS_DEFAULT_NDOTS
|
|
125
|
+
this.timeout = DNS_DEFAULT_TIMEOUT
|
|
126
|
+
this.attempts = DNS_DEFAULT_ATTEMPTS
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** One hosts file, read by `dnsParseHosts`, made once and reusable. */
|
|
131
|
+
export class DnsHosts {
|
|
132
|
+
/** `entryCount` addresses of `DNS_ADDRESS_BYTES` each, port 0. */
|
|
133
|
+
addresses: u8[]
|
|
134
|
+
/** Every kept name, back to back, lowercase, without a trailing dot. */
|
|
135
|
+
names: u8[]
|
|
136
|
+
/** Sixteen bytes a literal is parsed into before it is kept, so a parse allocates nothing. */
|
|
137
|
+
scratch: u8[]
|
|
138
|
+
/** Where each of the `nameCount` names starts in `names`, its length, and its entry. */
|
|
139
|
+
nameStart: i32[]
|
|
140
|
+
nameLength: i32[]
|
|
141
|
+
nameEntry: i32[]
|
|
142
|
+
entryCount: i32 = 0
|
|
143
|
+
nameCount: i32 = 0
|
|
144
|
+
nameBytes: i32 = 0
|
|
145
|
+
/** Lines with an address past `DNS_HOSTS_MAX_ENTRIES`, read and not kept. */
|
|
146
|
+
droppedEntries: i32 = 0
|
|
147
|
+
/** Names past a cap, or longer than `DNS_HOSTS_MAX_NAME`, read and not kept. */
|
|
148
|
+
droppedNames: i32 = 0
|
|
149
|
+
/** Lines longer than `DNS_CONFIG_MAX_LINE`, skipped whole. */
|
|
150
|
+
skippedLines: i32 = 0
|
|
151
|
+
|
|
152
|
+
constructor() {
|
|
153
|
+
this.addresses = new Array<u8>(DNS_HOSTS_MAX_ENTRIES * DNS_ADDRESS_BYTES)
|
|
154
|
+
this.names = new Array<u8>(DNS_HOSTS_MAX_NAME_BYTES)
|
|
155
|
+
this.scratch = new Array<u8>(16)
|
|
156
|
+
this.nameStart = new Array<i32>(DNS_HOSTS_MAX_NAMES)
|
|
157
|
+
this.nameLength = new Array<i32>(DNS_HOSTS_MAX_NAMES)
|
|
158
|
+
this.nameEntry = new Array<i32>(DNS_HOSTS_MAX_NAMES)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Panics unless `bytes[off, off + len)` lies inside `bytes`. */
|
|
163
|
+
const dnsConfigCheckWindow = (bytes: u8[], off: i32, len: i32, who: string): void => {
|
|
164
|
+
const size: i32 = toI32(bytes.length)
|
|
165
|
+
if (off < 0 || len < 0 || off > size || len > size - off) {
|
|
166
|
+
panic(who + ": the window is outside the array")
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Whether `b` separates words on a line. */
|
|
171
|
+
const dnsIsBlank = (b: i32): boolean => b === DNS_SPACE || b === DNS_TAB || b === DNS_RETURN
|
|
172
|
+
|
|
173
|
+
/** The value of one hex digit, either case, or -1. */
|
|
174
|
+
const dnsHexValue = (b: i32): i32 => {
|
|
175
|
+
if (b >= 48 && b <= 57) {
|
|
176
|
+
return b - 48
|
|
177
|
+
}
|
|
178
|
+
if (b >= 65 && b <= 70) {
|
|
179
|
+
return b - 55
|
|
180
|
+
}
|
|
181
|
+
if (b >= 97 && b <= 102) {
|
|
182
|
+
return b - 87
|
|
183
|
+
}
|
|
184
|
+
return -1
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Writes the dotted quad `b[from, to)` as its four bytes at `out[at]`, and
|
|
189
|
+
* answers whether it was one: four decimal parts of 0 to 255, each without a
|
|
190
|
+
* leading zero, so `010` is never read as octal 8 the way `inet_aton` reads it.
|
|
191
|
+
*/
|
|
192
|
+
const dnsParseIpv4 = (b: u8[], from: i32, to: i32, out: u8[], at: i32): boolean => {
|
|
193
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
194
|
+
panic("dnsParseIpv4: the window is outside the array")
|
|
195
|
+
}
|
|
196
|
+
let i: i32 = from
|
|
197
|
+
let part: i32 = 0
|
|
198
|
+
while (part < 4) {
|
|
199
|
+
const start: i32 = i
|
|
200
|
+
let value: i32 = 0
|
|
201
|
+
while (i < to && i - start < 3 && toI32(b[i]) >= 48 && toI32(b[i]) <= 57) {
|
|
202
|
+
value = value * 10 + toI32(b[i]) - 48
|
|
203
|
+
i += 1
|
|
204
|
+
}
|
|
205
|
+
const digits: i32 = i - start
|
|
206
|
+
// A part of two or three digits worth less than 10 or 100 has a leading zero.
|
|
207
|
+
if (digits === 0 || value > 255 || (digits === 2 && value < 10) || (digits === 3 && value < 100)) {
|
|
208
|
+
return false
|
|
209
|
+
}
|
|
210
|
+
out[at + part] = toU8(value)
|
|
211
|
+
part += 1
|
|
212
|
+
if (part < 4) {
|
|
213
|
+
if (i >= to || toI32(b[i]) !== DNS_DOT) {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
i += 1
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return i === to
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Writes the IPv6 literal `b[from, to)` as sixteen bytes at `out[at]`, and
|
|
224
|
+
* answers whether it was one: eight groups of one to four hex digits, at most
|
|
225
|
+
* one `::` standing for one or more zero groups, and a dotted quad allowed in
|
|
226
|
+
* place of the last two groups.
|
|
227
|
+
*/
|
|
228
|
+
const dnsParseIpv6 = (b: u8[], from: i32, to: i32, out: u8[], at: i32): boolean => {
|
|
229
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
230
|
+
panic("dnsParseIpv6: the window is outside the array")
|
|
231
|
+
}
|
|
232
|
+
let i: i32 = from
|
|
233
|
+
let groups: i32 = 0
|
|
234
|
+
let gap: i32 = -1
|
|
235
|
+
if (to - i >= 2 && toI32(b[i]) === DNS_COLON && toI32(b[i + 1]) === DNS_COLON) {
|
|
236
|
+
gap = 0
|
|
237
|
+
i += 2
|
|
238
|
+
}
|
|
239
|
+
while (i < to) {
|
|
240
|
+
if (groups === 8 || i < 0 || i >= toI32(b.length)) {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
let j: i32 = i
|
|
244
|
+
let value: i32 = 0
|
|
245
|
+
while (j < to && j - i < 4 && dnsHexValue(toI32(b[j])) >= 0) {
|
|
246
|
+
value = value * 16 + dnsHexValue(toI32(b[j]))
|
|
247
|
+
j += 1
|
|
248
|
+
}
|
|
249
|
+
if (j < to && toI32(b[j]) === DNS_DOT) {
|
|
250
|
+
if (groups > 6 || !dnsParseIpv4(b, i, to, out, at + groups * 2)) {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
groups += 2
|
|
254
|
+
i = to
|
|
255
|
+
} else {
|
|
256
|
+
if (j === i) {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
out[at + groups * 2] = toU8(value >> 8)
|
|
260
|
+
out[at + groups * 2 + 1] = toU8(value & 255)
|
|
261
|
+
groups += 1
|
|
262
|
+
i = j
|
|
263
|
+
if (i < to) {
|
|
264
|
+
if (i < 0 || i >= toI32(b.length) || toI32(b[i]) !== DNS_COLON) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
i += 1
|
|
268
|
+
if (i < to && toI32(b[i]) === DNS_COLON) {
|
|
269
|
+
if (gap >= 0) {
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
gap = groups
|
|
273
|
+
i += 1
|
|
274
|
+
} else if (i === to) {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (gap < 0) {
|
|
281
|
+
return groups === 8
|
|
282
|
+
}
|
|
283
|
+
if (groups > 7) {
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
// The groups after the gap move to the end, and the gap fills with zeros.
|
|
287
|
+
const shift: i32 = (8 - groups) * 2
|
|
288
|
+
let k: i32 = groups * 2 - 1
|
|
289
|
+
while (k >= gap * 2) {
|
|
290
|
+
out[at + k + shift] = out[at + k]
|
|
291
|
+
k -= 1
|
|
292
|
+
}
|
|
293
|
+
k = gap * 2
|
|
294
|
+
while (k < gap * 2 + shift) {
|
|
295
|
+
out[at + k] = toU8(0)
|
|
296
|
+
k += 1
|
|
297
|
+
}
|
|
298
|
+
return true
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Writes the IPv4 or IPv6 literal `b[off, off + len)` at `out[at, at + 18)`,
|
|
303
|
+
* IPv4 as `::ffff:a.b.c.d`, with `port` big-endian after it, and answers
|
|
304
|
+
* whether it was one. `out` is written only when it was. A zone (`%eth0`),
|
|
305
|
+
* brackets and a port are not part of a literal.
|
|
306
|
+
*
|
|
307
|
+
* Each call allocates the sixteen bytes it parses into, released when it
|
|
308
|
+
* returns; the two file parsers reuse a buffer of their own instead.
|
|
309
|
+
*/
|
|
310
|
+
export const dnsParseAddress = (b: u8[], off: i32, len: i32, out: u8[], at: i32, port: i32): boolean =>
|
|
311
|
+
dnsParseAddressVia(b, off, len, out, at, port, new Array<u8>(16))
|
|
312
|
+
|
|
313
|
+
/** `dnsParseAddress`, parsing into `parsed`, sixteen bytes the caller owns, so it allocates nothing. */
|
|
314
|
+
const dnsParseAddressVia = (
|
|
315
|
+
b: u8[],
|
|
316
|
+
off: i32,
|
|
317
|
+
len: i32,
|
|
318
|
+
out: u8[],
|
|
319
|
+
at: i32,
|
|
320
|
+
port: i32,
|
|
321
|
+
parsed: u8[]
|
|
322
|
+
): boolean => {
|
|
323
|
+
dnsConfigCheckWindow(b, off, len, "dnsParseAddress")
|
|
324
|
+
if (at < 0 || at > toI32(out.length) - DNS_ADDRESS_BYTES) {
|
|
325
|
+
panic("dnsParseAddress: at is outside out")
|
|
326
|
+
}
|
|
327
|
+
if (port < 0 || port > 0xffff) {
|
|
328
|
+
panic(`dnsParseAddress: the port ${port} is not 16 bits`)
|
|
329
|
+
}
|
|
330
|
+
if (toI32(parsed.length) < 16) {
|
|
331
|
+
panic("dnsParseAddress: parsed is shorter than 16 bytes")
|
|
332
|
+
}
|
|
333
|
+
let ok: boolean = false
|
|
334
|
+
const end: i32 = off + len
|
|
335
|
+
let i: i32 = off
|
|
336
|
+
while (i >= 0 && i < end && i < toI32(b.length) && toI32(b[i]) !== DNS_COLON) {
|
|
337
|
+
i += 1
|
|
338
|
+
}
|
|
339
|
+
if (i === end) {
|
|
340
|
+
let z: i32 = 0
|
|
341
|
+
while (z < 10) {
|
|
342
|
+
parsed[z] = toU8(0)
|
|
343
|
+
z += 1
|
|
344
|
+
}
|
|
345
|
+
ok = dnsParseIpv4(b, off, end, parsed, 12)
|
|
346
|
+
parsed[10] = toU8(255)
|
|
347
|
+
parsed[11] = toU8(255)
|
|
348
|
+
} else {
|
|
349
|
+
ok = dnsParseIpv6(b, off, end, parsed, 0)
|
|
350
|
+
}
|
|
351
|
+
if (!ok) {
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
let k: i32 = 0
|
|
355
|
+
while (k < 16) {
|
|
356
|
+
out[at + k] = parsed[k]
|
|
357
|
+
k += 1
|
|
358
|
+
}
|
|
359
|
+
setU16BE(out, at + 16, toU16(port))
|
|
360
|
+
return true
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Whether the address at `a[at]` is `::ffff:a.b.c.d`, an IPv4 one. */
|
|
364
|
+
const dnsIsIpv4 = (a: u8[], at: i32): boolean => {
|
|
365
|
+
let k: i32 = 0
|
|
366
|
+
while (k < 10) {
|
|
367
|
+
if (toI32(a[at + k]) !== 0) {
|
|
368
|
+
return false
|
|
369
|
+
}
|
|
370
|
+
k += 1
|
|
371
|
+
}
|
|
372
|
+
return toI32(a[at + 10]) === 255 && toI32(a[at + 11]) === 255
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The end of the line that starts at `from`, before `to`: the offset of its
|
|
377
|
+
* newline, or `to`.
|
|
378
|
+
*/
|
|
379
|
+
const dnsLineEnd = (b: u8[], from: i32, to: i32): i32 => {
|
|
380
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
381
|
+
panic("dnsLineEnd: the window is outside the array")
|
|
382
|
+
}
|
|
383
|
+
let i: i32 = from
|
|
384
|
+
while (i < to && toI32(b[i]) !== DNS_NEWLINE) {
|
|
385
|
+
i += 1
|
|
386
|
+
}
|
|
387
|
+
return i
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Where the line `b[from, to)` ends once a `#` or `;` comment is cut from it. */
|
|
391
|
+
const dnsCommentCut = (b: u8[], from: i32, to: i32): i32 => {
|
|
392
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
393
|
+
panic("dnsCommentCut: the window is outside the array")
|
|
394
|
+
}
|
|
395
|
+
let i: i32 = from
|
|
396
|
+
while (i < to && toI32(b[i]) !== DNS_HASH && toI32(b[i]) !== DNS_SEMICOLON) {
|
|
397
|
+
i += 1
|
|
398
|
+
}
|
|
399
|
+
return i
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** The end of the word that starts at `from`, before `to`. */
|
|
403
|
+
const dnsWordEnd = (b: u8[], from: i32, to: i32): i32 => {
|
|
404
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
405
|
+
panic("dnsWordEnd: the window is outside the array")
|
|
406
|
+
}
|
|
407
|
+
let i: i32 = from
|
|
408
|
+
while (i < to && !dnsIsBlank(toI32(b[i]))) {
|
|
409
|
+
i += 1
|
|
410
|
+
}
|
|
411
|
+
return i
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Whether `b[from, to)` is the ASCII word `word`. */
|
|
415
|
+
const dnsWordIs = (b: u8[], from: i32, to: i32, word: string): boolean => {
|
|
416
|
+
const n: i32 = toI32(word.length)
|
|
417
|
+
if (to - from !== n) {
|
|
418
|
+
return false
|
|
419
|
+
}
|
|
420
|
+
let k: i32 = 0
|
|
421
|
+
while (k < n) {
|
|
422
|
+
if (toI32(b[from + k]) !== toI32(word.charCodeAt(k))) {
|
|
423
|
+
return false
|
|
424
|
+
}
|
|
425
|
+
k += 1
|
|
426
|
+
}
|
|
427
|
+
return true
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* The option `b[from, to)` read as `prefix` and a decimal count, clamped to
|
|
432
|
+
* `[low, high]`, or `fallback` when it is not that option: a digit past the
|
|
433
|
+
* count's fourth stops counting, so a hundred digits clamp rather than wrap.
|
|
434
|
+
*/
|
|
435
|
+
const dnsOption = (b: u8[], from: i32, to: i32, prefix: string, low: i32, high: i32, fallback: i32): i32 => {
|
|
436
|
+
if (from < 0 || from > to || to > toI32(b.length)) {
|
|
437
|
+
panic("dnsOption: the window is outside the array")
|
|
438
|
+
}
|
|
439
|
+
const n: i32 = toI32(prefix.length)
|
|
440
|
+
if (to - from <= n || !dnsWordIs(b, from, from + n, prefix)) {
|
|
441
|
+
return fallback
|
|
442
|
+
}
|
|
443
|
+
let value: i32 = 0
|
|
444
|
+
let i: i32 = from + n
|
|
445
|
+
while (i < to) {
|
|
446
|
+
const d: i32 = toI32(b[i]) - 48
|
|
447
|
+
if (d < 0 || d > 9) {
|
|
448
|
+
return fallback
|
|
449
|
+
}
|
|
450
|
+
if (value < 10000) {
|
|
451
|
+
value = value * 10 + d
|
|
452
|
+
}
|
|
453
|
+
i += 1
|
|
454
|
+
}
|
|
455
|
+
if (value < low) {
|
|
456
|
+
return low
|
|
457
|
+
}
|
|
458
|
+
if (value > high) {
|
|
459
|
+
return high
|
|
460
|
+
}
|
|
461
|
+
return value
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Appends `b[from, to)` to `config`'s search list, lowercase, one trailing dot dropped, or counts it dropped. */
|
|
465
|
+
const dnsAddSearch = (b: u8[], from: i32, to: i32, config: DnsConfig): void => {
|
|
466
|
+
let end: i32 = to
|
|
467
|
+
if (end > from && toI32(b[end - 1]) === DNS_DOT) {
|
|
468
|
+
end -= 1
|
|
469
|
+
}
|
|
470
|
+
const n: i32 = end - from
|
|
471
|
+
const used: i32 =
|
|
472
|
+
config.searchCount === 0
|
|
473
|
+
? 0
|
|
474
|
+
: config.searchStart[config.searchCount - 1] + config.searchLength[config.searchCount - 1]
|
|
475
|
+
if (n === 0 || config.searchCount === DNS_MAX_SEARCH || n > DNS_MAX_SEARCH_BYTES - used) {
|
|
476
|
+
config.droppedSearch += 1
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
let k: i32 = 0
|
|
480
|
+
while (k < n) {
|
|
481
|
+
const c: i32 = toI32(b[from + k])
|
|
482
|
+
config.search[used + k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
|
|
483
|
+
k += 1
|
|
484
|
+
}
|
|
485
|
+
config.searchStart[config.searchCount] = used
|
|
486
|
+
config.searchLength[config.searchCount] = n
|
|
487
|
+
config.searchCount += 1
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Reads one resolv.conf line, `b[from, to)` with its comment already cut, into `config`. */
|
|
491
|
+
const dnsResolvLine = (b: u8[], from: i32, to: i32, config: DnsConfig): void => {
|
|
492
|
+
const keyStart: i32 = skipBlank(b, from, to)
|
|
493
|
+
const keyEnd: i32 = dnsWordEnd(b, keyStart, to)
|
|
494
|
+
let i: i32 = skipBlank(b, keyEnd, to)
|
|
495
|
+
if (dnsWordIs(b, keyStart, keyEnd, "nameserver")) {
|
|
496
|
+
const end: i32 = dnsWordEnd(b, i, to)
|
|
497
|
+
if (end === i) {
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
if (config.serverCount === DNS_MAX_SERVERS) {
|
|
501
|
+
config.droppedServers += 1
|
|
502
|
+
return
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
dnsParseAddressVia(
|
|
506
|
+
b,
|
|
507
|
+
i,
|
|
508
|
+
end - i,
|
|
509
|
+
config.servers,
|
|
510
|
+
config.serverCount * DNS_ADDRESS_BYTES,
|
|
511
|
+
DNS_PORT,
|
|
512
|
+
config.scratch
|
|
513
|
+
)
|
|
514
|
+
) {
|
|
515
|
+
config.serverCount += 1
|
|
516
|
+
}
|
|
517
|
+
} else if (dnsWordIs(b, keyStart, keyEnd, "search") || dnsWordIs(b, keyStart, keyEnd, "domain")) {
|
|
518
|
+
const single: boolean = dnsWordIs(b, keyStart, keyEnd, "domain")
|
|
519
|
+
config.searchCount = 0
|
|
520
|
+
config.droppedSearch = 0
|
|
521
|
+
while (i < to) {
|
|
522
|
+
const end: i32 = dnsWordEnd(b, i, to)
|
|
523
|
+
dnsAddSearch(b, i, end, config)
|
|
524
|
+
i = skipBlank(b, end, to)
|
|
525
|
+
if (single) {
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} else if (dnsWordIs(b, keyStart, keyEnd, "options")) {
|
|
530
|
+
while (i < to) {
|
|
531
|
+
const end: i32 = dnsWordEnd(b, i, to)
|
|
532
|
+
config.ndots = dnsOption(b, i, end, "ndots:", 0, DNS_MAX_NDOTS, config.ndots)
|
|
533
|
+
config.timeout = dnsOption(b, i, end, "timeout:", 1, DNS_MAX_TIMEOUT, config.timeout)
|
|
534
|
+
config.attempts = dnsOption(b, i, end, "attempts:", 1, DNS_MAX_ATTEMPTS, config.attempts)
|
|
535
|
+
i = skipBlank(b, end, to)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Reads the resolv.conf `b[off, off + len)` into `config`, which starts over
|
|
542
|
+
* from the defaults, and gives it 127.0.0.1 and ::1 on port 53 when no
|
|
543
|
+
* nameserver line held an address.
|
|
544
|
+
*/
|
|
545
|
+
export const dnsParseResolvConf = (b: u8[], off: i32, len: i32, config: DnsConfig): void => {
|
|
546
|
+
dnsConfigCheckWindow(b, off, len, "dnsParseResolvConf")
|
|
547
|
+
config.serverCount = 0
|
|
548
|
+
config.droppedServers = 0
|
|
549
|
+
config.searchCount = 0
|
|
550
|
+
config.droppedSearch = 0
|
|
551
|
+
config.ndots = DNS_DEFAULT_NDOTS
|
|
552
|
+
config.timeout = DNS_DEFAULT_TIMEOUT
|
|
553
|
+
config.attempts = DNS_DEFAULT_ATTEMPTS
|
|
554
|
+
config.skippedLines = 0
|
|
555
|
+
const to: i32 = off + len
|
|
556
|
+
let i: i32 = off
|
|
557
|
+
while (i < to) {
|
|
558
|
+
const end: i32 = dnsLineEnd(b, i, to)
|
|
559
|
+
if (end - i > DNS_CONFIG_MAX_LINE) {
|
|
560
|
+
config.skippedLines += 1
|
|
561
|
+
} else {
|
|
562
|
+
dnsResolvLine(b, i, dnsCommentCut(b, i, end), config)
|
|
563
|
+
}
|
|
564
|
+
i = end + 1
|
|
565
|
+
}
|
|
566
|
+
if (config.serverCount === 0) {
|
|
567
|
+
dnsFallbackServers(config.servers)
|
|
568
|
+
config.serverCount = 2
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Writes 127.0.0.1 and ::1, each on `DNS_PORT`, as the first two of `servers`, allocating nothing. */
|
|
573
|
+
const dnsFallbackServers = (servers: u8[]): void => {
|
|
574
|
+
if (toI32(servers.length) < 2 * DNS_ADDRESS_BYTES) {
|
|
575
|
+
panic("dnsFallbackServers: servers has no room for two addresses")
|
|
576
|
+
}
|
|
577
|
+
let k: i32 = 0
|
|
578
|
+
while (k < 2 * DNS_ADDRESS_BYTES && k < toI32(servers.length)) {
|
|
579
|
+
servers[k] = toU8(0)
|
|
580
|
+
k += 1
|
|
581
|
+
}
|
|
582
|
+
servers[10] = toU8(255)
|
|
583
|
+
servers[11] = toU8(255)
|
|
584
|
+
servers[12] = toU8(127)
|
|
585
|
+
servers[15] = toU8(1)
|
|
586
|
+
setU16BE(servers, 16, toU16(DNS_PORT))
|
|
587
|
+
servers[DNS_ADDRESS_BYTES + 15] = toU8(1)
|
|
588
|
+
setU16BE(servers, DNS_ADDRESS_BYTES + 16, toU16(DNS_PORT))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Adds `b[from, to)` as a name of entry `entry`, lowercase, one trailing dot dropped, or counts it dropped. */
|
|
592
|
+
const dnsAddHostName = (b: u8[], from: i32, to: i32, entry: i32, hosts: DnsHosts): void => {
|
|
593
|
+
let end: i32 = to
|
|
594
|
+
if (end > from && toI32(b[end - 1]) === DNS_DOT) {
|
|
595
|
+
end -= 1
|
|
596
|
+
}
|
|
597
|
+
const n: i32 = end - from
|
|
598
|
+
if (
|
|
599
|
+
n === 0 ||
|
|
600
|
+
n > DNS_HOSTS_MAX_NAME ||
|
|
601
|
+
hosts.nameCount === DNS_HOSTS_MAX_NAMES ||
|
|
602
|
+
n > DNS_HOSTS_MAX_NAME_BYTES - hosts.nameBytes
|
|
603
|
+
) {
|
|
604
|
+
hosts.droppedNames += 1
|
|
605
|
+
return
|
|
606
|
+
}
|
|
607
|
+
let k: i32 = 0
|
|
608
|
+
while (k < n) {
|
|
609
|
+
const c: i32 = toI32(b[from + k])
|
|
610
|
+
hosts.names[hosts.nameBytes + k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
|
|
611
|
+
k += 1
|
|
612
|
+
}
|
|
613
|
+
hosts.nameStart[hosts.nameCount] = hosts.nameBytes
|
|
614
|
+
hosts.nameLength[hosts.nameCount] = n
|
|
615
|
+
hosts.nameEntry[hosts.nameCount] = entry
|
|
616
|
+
hosts.nameCount += 1
|
|
617
|
+
hosts.nameBytes += n
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Reads one hosts line, `b[from, to)` with its comment already cut, into `hosts`. */
|
|
621
|
+
const dnsHostsLine = (b: u8[], from: i32, to: i32, hosts: DnsHosts): void => {
|
|
622
|
+
const addrStart: i32 = skipBlank(b, from, to)
|
|
623
|
+
const addrEnd: i32 = dnsWordEnd(b, addrStart, to)
|
|
624
|
+
let i: i32 = skipBlank(b, addrEnd, to)
|
|
625
|
+
if (addrEnd === addrStart || i === to) {
|
|
626
|
+
return
|
|
627
|
+
}
|
|
628
|
+
if (hosts.entryCount === DNS_HOSTS_MAX_ENTRIES) {
|
|
629
|
+
hosts.droppedEntries += 1
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
const at: i32 = hosts.entryCount * DNS_ADDRESS_BYTES
|
|
633
|
+
if (!dnsParseAddressVia(b, addrStart, addrEnd - addrStart, hosts.addresses, at, 0, hosts.scratch)) {
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
const entry: i32 = hosts.entryCount
|
|
637
|
+
hosts.entryCount += 1
|
|
638
|
+
while (i < to) {
|
|
639
|
+
const end: i32 = dnsWordEnd(b, i, to)
|
|
640
|
+
dnsAddHostName(b, i, end, entry, hosts)
|
|
641
|
+
i = skipBlank(b, end, to)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Reads the hosts file `b[off, off + len)` into `hosts`, which starts over empty. */
|
|
646
|
+
export const dnsParseHosts = (b: u8[], off: i32, len: i32, hosts: DnsHosts): void => {
|
|
647
|
+
dnsConfigCheckWindow(b, off, len, "dnsParseHosts")
|
|
648
|
+
hosts.entryCount = 0
|
|
649
|
+
hosts.nameCount = 0
|
|
650
|
+
hosts.nameBytes = 0
|
|
651
|
+
hosts.droppedEntries = 0
|
|
652
|
+
hosts.droppedNames = 0
|
|
653
|
+
hosts.skippedLines = 0
|
|
654
|
+
const to: i32 = off + len
|
|
655
|
+
let i: i32 = off
|
|
656
|
+
while (i < to) {
|
|
657
|
+
const end: i32 = dnsLineEnd(b, i, to)
|
|
658
|
+
if (end - i > DNS_CONFIG_MAX_LINE) {
|
|
659
|
+
hosts.skippedLines += 1
|
|
660
|
+
} else {
|
|
661
|
+
dnsHostsLine(b, i, dnsCommentCut(b, i, end), hosts)
|
|
662
|
+
}
|
|
663
|
+
i = end + 1
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** Whether kept name `index` of `hosts` is `name[0, n)`, without case. */
|
|
668
|
+
const dnsHostNameIs = (hosts: DnsHosts, index: i32, name: string, n: i32): boolean => {
|
|
669
|
+
if (hosts.nameLength[index] !== n) {
|
|
670
|
+
return false
|
|
671
|
+
}
|
|
672
|
+
const start: i32 = hosts.nameStart[index]
|
|
673
|
+
let k: i32 = 0
|
|
674
|
+
while (k < n) {
|
|
675
|
+
const c: i32 = toI32(name.charCodeAt(k))
|
|
676
|
+
if (toI32(hosts.names[start + k]) !== (c >= 65 && c <= 90 ? c + 32 : c)) {
|
|
677
|
+
return false
|
|
678
|
+
}
|
|
679
|
+
k += 1
|
|
680
|
+
}
|
|
681
|
+
return true
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Writes the addresses `hosts` gives `name`, of `qtype` (`DNS_TYPE_A` or
|
|
686
|
+
* `DNS_TYPE_AAAA`), into `out` from offset 0 with `port`, in file order and at
|
|
687
|
+
* most `max` of them, and answers how many it wrote. An entry that names
|
|
688
|
+
* `name` twice is written once.
|
|
689
|
+
*/
|
|
690
|
+
export const dnsHostsFind = (
|
|
691
|
+
hosts: DnsHosts,
|
|
692
|
+
name: string,
|
|
693
|
+
qtype: i32,
|
|
694
|
+
port: i32,
|
|
695
|
+
out: u8[],
|
|
696
|
+
max: i32
|
|
697
|
+
): i32 => {
|
|
698
|
+
if (qtype !== DNS_TYPE_A && qtype !== DNS_TYPE_AAAA) {
|
|
699
|
+
panic(`dnsHostsFind: the type ${qtype} is not DNS_TYPE_A or DNS_TYPE_AAAA`)
|
|
700
|
+
}
|
|
701
|
+
if (max < 0 || max > toI32(out.length) / DNS_ADDRESS_BYTES) {
|
|
702
|
+
panic("dnsHostsFind: out has no room for max addresses")
|
|
703
|
+
}
|
|
704
|
+
if (port < 0 || port > 0xffff) {
|
|
705
|
+
panic(`dnsHostsFind: the port ${port} is not 16 bits`)
|
|
706
|
+
}
|
|
707
|
+
let n: i32 = toI32(name.length)
|
|
708
|
+
if (n > 0 && toI32(name.charCodeAt(n - 1)) === DNS_DOT) {
|
|
709
|
+
n -= 1
|
|
710
|
+
}
|
|
711
|
+
let found: i32 = 0
|
|
712
|
+
let last: i32 = -1
|
|
713
|
+
let i: i32 = 0
|
|
714
|
+
while (i < hosts.nameCount && found < max) {
|
|
715
|
+
const entry: i32 = hosts.nameEntry[i]
|
|
716
|
+
const at: i32 = entry * DNS_ADDRESS_BYTES
|
|
717
|
+
if (
|
|
718
|
+
entry !== last &&
|
|
719
|
+
dnsIsIpv4(hosts.addresses, at) === (qtype === DNS_TYPE_A) &&
|
|
720
|
+
dnsHostNameIs(hosts, i, name, n)
|
|
721
|
+
) {
|
|
722
|
+
let k: i32 = 0
|
|
723
|
+
while (k < 16) {
|
|
724
|
+
out[found * DNS_ADDRESS_BYTES + k] = hosts.addresses[at + k]
|
|
725
|
+
k += 1
|
|
726
|
+
}
|
|
727
|
+
setU16BE(out, found * DNS_ADDRESS_BYTES + 16, toU16(port))
|
|
728
|
+
found += 1
|
|
729
|
+
last = entry
|
|
730
|
+
}
|
|
731
|
+
i += 1
|
|
732
|
+
}
|
|
733
|
+
return found
|
|
734
|
+
}
|