mailauth 7.2.0 → 7.3.1

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/index.d.ts CHANGED
@@ -109,6 +109,24 @@ export interface AuthenticateOptions {
109
109
  * `warnings` array of the affected result. It is never written into the headers
110
110
  */
111
111
  strict?: boolean;
112
+
113
+ /**
114
+ * Verify DKIM2 header fields as well (default: false). Experimental, built against
115
+ * draft-ietf-dkim-dkim2-spec-06. The result is added to Authentication-Results with the
116
+ * `dkim2` method of draft-gondwana-dkim2-authres-00 and does not take part in DMARC
117
+ */
118
+ dkim2?: boolean;
119
+
120
+ /**
121
+ * RCPT TO addresses of the delivery, checked against the rt= tag of the highest
122
+ * DKIM2-Signature. `sender` is checked against its mf= tag
123
+ */
124
+ rcptTo?: string | string[];
125
+
126
+ /**
127
+ * Extra DKIM2 verifier options
128
+ */
129
+ dkim2Options?: Pick<DKIM2VerifyOptions, 'curTime' | 'maxSignatureAge' | 'maxFutureTime' | 'maxInstances' | 'checkReplay'>;
112
130
  }
113
131
 
114
132
  /**
@@ -852,6 +870,11 @@ export interface AuthenticateResult {
852
870
  */
853
871
  dkim: DKIMVerifyResult;
854
872
 
873
+ /**
874
+ * DKIM2 verification result, false unless the `dkim2` option is set
875
+ */
876
+ dkim2: DKIM2VerifyResult | false;
877
+
855
878
  /**
856
879
  * SPF verification result
857
880
  */
@@ -1318,6 +1341,342 @@ export interface DKIMVerifyOptions {
1318
1341
  */
1319
1342
  export function dkimVerify(input: MessageInput, options?: DKIMVerifyOptions): Promise<DKIMVerifyResult>;
1320
1343
 
1344
+ // ============================================================================
1345
+ // DKIM2 (experimental, draft-ietf-dkim-dkim2-spec-06, draft-ietf-dkim-dkim2-dns-00)
1346
+ // ============================================================================
1347
+
1348
+ /**
1349
+ * A DKIM2 Recipe (draft-ietf-dkim-dkim2-spec-06 section 5). Each step either copies a range of
1350
+ * header fields (numbered from the bottom) or body lines (numbered from the top), or emits data
1351
+ */
1352
+ export type DKIM2RecipeStep = { c: [number, number] } | { d: string[] };
1353
+
1354
+ export interface DKIM2Recipe {
1355
+ /**
1356
+ * Steps per lower case header field name. An empty array removes every field of that name
1357
+ */
1358
+ h?: Record<string, DKIM2RecipeStep[]>;
1359
+
1360
+ /**
1361
+ * Steps for the body, or null when the previous body can not be recreated
1362
+ */
1363
+ b?: DKIM2RecipeStep[] | null;
1364
+ }
1365
+
1366
+ export interface DKIM2SignatureKey {
1367
+ /**
1368
+ * Key selector
1369
+ */
1370
+ selector: string;
1371
+
1372
+ /**
1373
+ * Private key, PEM or a raw 32 byte Ed25519 key
1374
+ */
1375
+ privateKey: string | Buffer;
1376
+
1377
+ /**
1378
+ * "rsa-sha256" or "ed25519-sha256", follows the key type when not set
1379
+ */
1380
+ algorithm?: 'rsa-sha256' | 'ed25519-sha256';
1381
+ }
1382
+
1383
+ /**
1384
+ * DKIM2 signing options
1385
+ */
1386
+ export interface DKIM2SignOptions {
1387
+ /**
1388
+ * Signing domain (d= tag)
1389
+ */
1390
+ signingDomain: string;
1391
+
1392
+ /**
1393
+ * Signing keys. At most two keys per algorithm, each with its own selector
1394
+ */
1395
+ signatureData?: DKIM2SignatureKey[];
1396
+
1397
+ /**
1398
+ * Selector of a single key, used with `privateKey` when `signatureData` is not set
1399
+ */
1400
+ selector?: string;
1401
+
1402
+ /**
1403
+ * A single private key, used with `selector` when `signatureData` is not set
1404
+ */
1405
+ privateKey?: string | Buffer;
1406
+
1407
+ /**
1408
+ * Algorithm of the single key
1409
+ */
1410
+ algorithm?: 'rsa-sha256' | 'ed25519-sha256';
1411
+
1412
+ /**
1413
+ * SMTP MAIL FROM the message is sent with (mf= tag), "" or "<>" for the null sender.
1414
+ * Required unless `nextDomain` is set
1415
+ */
1416
+ mailFrom?: string;
1417
+
1418
+ /**
1419
+ * SMTP RCPT TO addresses the message is sent to (rt= tag). Required unless `nextDomain` is set
1420
+ */
1421
+ rcptTo?: string | string[];
1422
+
1423
+ /**
1424
+ * Domain of the next DKIM2-Signature for an imaginary hop (nd= tag), instead of mailFrom and rcptTo
1425
+ */
1426
+ nextDomain?: string;
1427
+
1428
+ /**
1429
+ * Nonce (n= tag), at most 64 printable ASCII characters without ";"
1430
+ */
1431
+ nonce?: string;
1432
+
1433
+ /**
1434
+ * Flags (f= tag), such as "donotmodify", "donotexplode", "exploded", "feedback" or "feedhere"
1435
+ */
1436
+ flags?: string[];
1437
+
1438
+ /**
1439
+ * Hash algorithms of a new Message-Instance (default: ["sha256"])
1440
+ */
1441
+ hashAlgorithms?: ('sha256' | 'sha512')[];
1442
+
1443
+ /**
1444
+ * Recipe that recreates the previous instance of a message that was changed since its
1445
+ * highest Message-Instance. It is checked against that instance before signing
1446
+ */
1447
+ recipe?: DKIM2Recipe;
1448
+
1449
+ /**
1450
+ * Signing time (t= tag), defaults to now
1451
+ */
1452
+ signTime?: Date | string | number;
1453
+ }
1454
+
1455
+ /**
1456
+ * DKIM2 signing result
1457
+ */
1458
+ export interface DKIM2SignResult {
1459
+ /**
1460
+ * Header fields to prepend to the message, each ending with CRLF
1461
+ */
1462
+ signatures: string;
1463
+
1464
+ /**
1465
+ * The new DKIM2-Signature header field
1466
+ */
1467
+ signature: string;
1468
+
1469
+ /**
1470
+ * The new Message-Instance header field, null when the message did not change
1471
+ */
1472
+ messageInstance: string | null;
1473
+
1474
+ /**
1475
+ * i= of the new signature
1476
+ */
1477
+ i: number;
1478
+
1479
+ /**
1480
+ * m= of the new signature
1481
+ */
1482
+ m: number;
1483
+ }
1484
+
1485
+ /**
1486
+ * Signs a message with DKIM2. Rejects for invalid options, an invalid DKIM2 chain on the
1487
+ * message, or a recipe that does not recreate the previous instance
1488
+ */
1489
+ export function dkim2Sign(input: MessageInput, options: DKIM2SignOptions): Promise<DKIM2SignResult>;
1490
+
1491
+ /**
1492
+ * Transform stream that prepends the DKIM2 header fields to the message
1493
+ */
1494
+ export class Dkim2SignStream extends Transform {
1495
+ constructor(options: DKIM2SignOptions);
1496
+ }
1497
+
1498
+ /**
1499
+ * DKIM2 verification options
1500
+ */
1501
+ export interface DKIM2VerifyOptions {
1502
+ /**
1503
+ * Custom DNS resolver function
1504
+ */
1505
+ resolver?: DNSResolver;
1506
+
1507
+ /**
1508
+ * SMTP MAIL FROM of the delivery, "" for the null sender. Compared with mf= of the highest
1509
+ * DKIM2-Signature when set
1510
+ */
1511
+ mailFrom?: string;
1512
+
1513
+ /**
1514
+ * SMTP RCPT TO addresses of the delivery, each of which has to be listed in rt= of the highest
1515
+ * DKIM2-Signature
1516
+ */
1517
+ rcptTo?: string | string[];
1518
+
1519
+ /**
1520
+ * Seconds after t= when a signature expires (default: 1209600, 14 days), false to not check
1521
+ */
1522
+ maxSignatureAge?: number | false;
1523
+
1524
+ /**
1525
+ * Seconds a t= may be ahead of the current time (default: 300), false to not check. Section 8.4
1526
+ * allows ignoring signatures from the future
1527
+ */
1528
+ maxFutureTime?: number | false;
1529
+
1530
+ /**
1531
+ * Time to verify against (default: now)
1532
+ */
1533
+ curTime?: Date | string | number;
1534
+
1535
+ /**
1536
+ * Most Message-Instance, and most DKIM2-Signature, header fields processed (default: 20)
1537
+ */
1538
+ maxInstances?: number;
1539
+
1540
+ /**
1541
+ * Called with the replay key (the m=1 hashes) of the message. Return true when a message with
1542
+ * the same key was seen before; without an exploded flag the result is then FAIL
1543
+ */
1544
+ checkReplay?: (replay: { key: string; exploded: boolean }) => Promise<boolean> | boolean;
1545
+ }
1546
+
1547
+ export type DKIM2Result = 'pass' | 'fail' | 'permerror' | 'temperror';
1548
+
1549
+ export type DKIM2Warning = 'mail-from-not-checked' | 'rcpt-to-not-checked';
1550
+
1551
+ export interface DKIM2Error {
1552
+ result: DKIM2Result;
1553
+
1554
+ /**
1555
+ * Human-readable string of draft-ietf-dkim-dkim2-spec-06 section 11
1556
+ */
1557
+ message: string;
1558
+
1559
+ /**
1560
+ * i= of the DKIM2-Signature the error is reported against
1561
+ */
1562
+ i?: number;
1563
+
1564
+ /**
1565
+ * m= of the Message-Instance the error is about
1566
+ */
1567
+ m?: number;
1568
+ }
1569
+
1570
+ export interface DKIM2InstanceResult {
1571
+ m: number;
1572
+ hashes: { algorithm: string; header: 'pass' | 'fail'; body: 'pass' | 'fail' | 'unknown' }[];
1573
+ recipe?: { headers: string[]; body: 'unchanged' | 'recipe' | 'unrecoverable' };
1574
+ }
1575
+
1576
+ export interface DKIM2SignatureValueResult {
1577
+ selector: string;
1578
+ algorithm: string;
1579
+
1580
+ /**
1581
+ * "none" for an algorithm that is not supported and ignored
1582
+ */
1583
+ result?: DKIM2Result | 'none';
1584
+ comment?: string;
1585
+ rr?: string;
1586
+ modulusLength?: number;
1587
+
1588
+ /**
1589
+ * The key record has the t=y testing flag
1590
+ */
1591
+ testing?: boolean;
1592
+ }
1593
+
1594
+ export interface DKIM2SignatureResult {
1595
+ i: number;
1596
+ m: number;
1597
+ signingDomain: string;
1598
+ timestamp: number;
1599
+ signTime: string;
1600
+ flags: string[];
1601
+ mailFrom?: string;
1602
+ rcptTo?: string[];
1603
+ nextDomain?: string;
1604
+ nonce?: string;
1605
+ status: { result: DKIM2Result | 'skipped'; comment?: string };
1606
+ values: DKIM2SignatureValueResult[];
1607
+ }
1608
+
1609
+ /**
1610
+ * DKIM2 verification result
1611
+ */
1612
+ export interface DKIM2VerifyResult {
1613
+ status: {
1614
+ result: DKIM2Result | 'none';
1615
+
1616
+ /**
1617
+ * The human-readable string of the reported failure
1618
+ */
1619
+ comment?: string;
1620
+ header?: { d?: string; i?: number };
1621
+
1622
+ /**
1623
+ * The parts of the SMTP envelope that were not given, so the highest DKIM2-Signature was
1624
+ * not compared with them and an unchanged copy sent to someone else would pass as well.
1625
+ * Only present when there is something to report, never written into `info`
1626
+ */
1627
+ warnings?: DKIM2Warning[];
1628
+ };
1629
+
1630
+ /**
1631
+ * Authentication-Results resinfo
1632
+ */
1633
+ info: string;
1634
+ errors: DKIM2Error[];
1635
+ instances: DKIM2InstanceResult[];
1636
+ signatures: DKIM2SignatureResult[];
1637
+
1638
+ /**
1639
+ * The key for replay detection and whether a signature allows copies
1640
+ */
1641
+ replay?: { key: string; exploded: boolean };
1642
+ }
1643
+
1644
+ /**
1645
+ * DKIM2 hashing options
1646
+ */
1647
+ export interface DKIM2HashOptions {
1648
+ /**
1649
+ * Hash algorithms (default: ["sha256"])
1650
+ */
1651
+ algorithms?: ('sha256' | 'sha512')[];
1652
+ }
1653
+
1654
+ /**
1655
+ * DKIM2 hashes of a message
1656
+ */
1657
+ export interface DKIM2HashResult {
1658
+ /**
1659
+ * Names of the hashed header fields, in the order they are hashed
1660
+ */
1661
+ headers: string[];
1662
+
1663
+ /**
1664
+ * The hash-sets of the h= tag of a Message-Instance
1665
+ */
1666
+ hashes: { algorithm: 'sha256' | 'sha512'; headerHash: string; bodyHash: string }[];
1667
+ }
1668
+
1669
+ /**
1670
+ * Computes the DKIM2 header and body hashes of a message, as they go into the h= tag of a
1671
+ * Message-Instance header field. Rejects for an unsupported hash algorithm
1672
+ */
1673
+ export function dkim2Hash(input: MessageInput, options?: DKIM2HashOptions): Promise<DKIM2HashResult>;
1674
+
1675
+ /**
1676
+ * Verifies the DKIM2 header fields of a message
1677
+ */
1678
+ export function dkim2Verify(input: MessageInput, options?: DKIM2VerifyOptions): Promise<DKIM2VerifyResult>;
1679
+
1321
1680
  // ============================================================================
1322
1681
  // SPF
1323
1682
  // ============================================================================
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { dkim2Hash } = require('../dkim2/hash');
4
+ const fs = require('node:fs');
5
+
6
+ const cmd = async argv => {
7
+ let source = argv.email;
8
+ if (argv.verbose) {
9
+ console.error(`Reading email message from ${source || 'standard input'}`);
10
+ }
11
+
12
+ let { headers, hashes } = await dkim2Hash(source ? fs.createReadStream(source) : process.stdin, { algorithms: argv.algo });
13
+
14
+ if (argv.verbose) {
15
+ console.error(`Hashed header fields: ${headers.join(', ') || '(none)'}`);
16
+ console.error('--------');
17
+ }
18
+
19
+ // the hash-sets of the h= tag of a Message-Instance (section 7.3)
20
+ for (let { algorithm, headerHash, bodyHash } of hashes) {
21
+ process.stdout.write(`${algorithm}:${headerHash}:${bodyHash}\n`);
22
+ }
23
+ };
24
+
25
+ module.exports = cmd;
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const { dkim2Sign } = require('../dkim2/sign');
4
+ const { GathererStream } = require('../gatherer-stream');
5
+ const fs = require('node:fs');
6
+
7
+ const cmd = async argv => {
8
+ let source = argv.email;
9
+ let stream;
10
+
11
+ if (argv.verbose) {
12
+ console.error(`Reading email message from ${source || 'standard input'}`);
13
+ }
14
+
15
+ stream = source ? fs.createReadStream(source) : process.stdin;
16
+
17
+ let privateKeys = argv.privateKey || [];
18
+ let selectors = argv.selector || [];
19
+ if (privateKeys.length !== selectors.length) {
20
+ let err = new Error('Every --private-key needs a --selector, in the same order');
21
+ throw err;
22
+ }
23
+
24
+ let gatherer = new GathererStream({ gather: !argv.headersOnly });
25
+ stream.pipe(gatherer);
26
+ stream.on('error', err => gatherer.emit('error', err));
27
+
28
+ let signatureData = [];
29
+ for (let i = 0; i < privateKeys.length; i++) {
30
+ signatureData.push({ selector: selectors[i], privateKey: await fs.promises.readFile(privateKeys[i], 'utf-8') });
31
+ }
32
+
33
+ let options = {
34
+ signingDomain: argv.domain,
35
+ signatureData,
36
+ signTime: argv.time ? new Date(argv.time * 1000) : new Date(),
37
+ flags: argv.flag,
38
+ nonce: argv.nonce
39
+ };
40
+
41
+ if (argv.hash?.length) {
42
+ options.hashAlgorithms = argv.hash;
43
+ }
44
+
45
+ if (argv.nextDomain) {
46
+ options.nextDomain = argv.nextDomain;
47
+ } else {
48
+ options.mailFrom = argv.mailFrom;
49
+ options.rcptTo = argv.rcptTo;
50
+ }
51
+
52
+ if (argv.recipe) {
53
+ options.recipe = JSON.parse(await fs.promises.readFile(argv.recipe, 'utf-8'));
54
+ }
55
+
56
+ if (argv.verbose) {
57
+ console.error(`Signing domain: ${options.signingDomain}`);
58
+ console.error(`Selectors: ${selectors.join(', ')}`);
59
+ console.error(`Signing time: ${options.signTime.toISOString()}`);
60
+ console.error('--------');
61
+ }
62
+
63
+ let signResult = await dkim2Sign(gatherer, options);
64
+
65
+ process.stdout.write(signResult.signatures);
66
+ if (!argv.headersOnly) {
67
+ // print the full message as well
68
+ await new Promise((resolve, reject) => {
69
+ let msgStream = gatherer.replay();
70
+ msgStream.pipe(process.stdout, { end: false });
71
+ msgStream.on('end', resolve);
72
+ msgStream.on('error', reject);
73
+ });
74
+ }
75
+ };
76
+
77
+ module.exports = cmd;
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const { dkim2Verify } = require('../dkim2/verify');
4
+ const { createResolver } = require('./resolver');
5
+ const fs = require('node:fs');
6
+
7
+ const cmd = async argv => {
8
+ let source = argv.email;
9
+
10
+ if (argv.verbose) {
11
+ console.error(`Reading email message from ${source || 'standard input'}`);
12
+ }
13
+
14
+ let stream = source ? fs.createReadStream(source) : process.stdin;
15
+
16
+ let opts = {
17
+ resolver: await createResolver(argv),
18
+ rcptTo: argv.rcptTo
19
+ };
20
+
21
+ if (typeof argv.mailFrom === 'string') {
22
+ opts.mailFrom = argv.mailFrom;
23
+ }
24
+
25
+ if (typeof argv.time === 'number') {
26
+ opts.curTime = new Date(argv.time * 1000);
27
+ }
28
+
29
+ if (typeof argv.maxAge === 'number') {
30
+ // 0 turns the expiration check off
31
+ opts.maxSignatureAge = argv.maxAge || false;
32
+ }
33
+
34
+ if (typeof argv.maxFuture === 'number') {
35
+ // 0 turns the check off, like --max-age
36
+ opts.maxFutureTime = argv.maxFuture || false;
37
+ }
38
+
39
+ if (typeof argv.maxInstances === 'number') {
40
+ opts.maxInstances = argv.maxInstances;
41
+ }
42
+
43
+ let result = await dkim2Verify(stream, opts);
44
+
45
+ if (argv.verbose) {
46
+ for (let warning of result.status.warnings || []) {
47
+ console.error(`Warning: ${warning}`);
48
+ }
49
+ }
50
+
51
+ if (argv.headersOnly) {
52
+ process.stdout.write(result.info + '\n');
53
+ return;
54
+ }
55
+
56
+ process.stdout.write(JSON.stringify(result, false, 2) + '\n');
57
+ };
58
+
59
+ module.exports = cmd;
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { authenticate } = require('../mailauth');
4
4
  const fs = require('node:fs');
5
- const { resolve } = require('node:dns').promises;
5
+ const { createResolver } = require('./resolver');
6
6
 
7
7
  const cmd = async argv => {
8
8
  let source = argv.email;
@@ -27,9 +27,14 @@ const cmd = async argv => {
27
27
  const opts = {
28
28
  trustReceived: true,
29
29
  strict: !!argv.strict,
30
- rejectRsaSha1: !!argv.rejectRsaSha1
30
+ rejectRsaSha1: !!argv.rejectRsaSha1,
31
+ dkim2: !!argv.dkim2
31
32
  };
32
33
 
34
+ if (argv.rcptTo) {
35
+ opts.rcptTo = argv.rcptTo;
36
+ }
37
+
33
38
  if (argv.clientIp) {
34
39
  opts.ip = argv.clientIp;
35
40
  }
@@ -48,37 +53,7 @@ const cmd = async argv => {
48
53
  }
49
54
  }
50
55
 
51
- if (argv.dnsCache) {
52
- let dnsCache = JSON.parse(await fs.promises.readFile(argv.dnsCache, 'utf-8'));
53
-
54
- opts.resolver = async (name, rr) => {
55
- let match = dnsCache?.[name]?.[rr];
56
-
57
- if (argv.verbose) {
58
- console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'} (using cache)`);
59
- }
60
-
61
- if (!match) {
62
- let err = new Error('Error');
63
- err.code = 'ENOTFOUND';
64
- throw err;
65
- }
66
-
67
- return match;
68
- };
69
- } else if (argv.verbose) {
70
- opts.resolver = async (name, rr) => {
71
- let match;
72
- try {
73
- match = await resolve(name, rr);
74
- console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'}`);
75
- return match;
76
- } catch (err) {
77
- console.error(`DNS query for ${rr} ${name}: ${err.message}${err.code ? ` [${err.code}]` : ''}`);
78
- throw err;
79
- }
80
- };
81
- }
56
+ opts.resolver = await createResolver(argv);
82
57
 
83
58
  let result = await authenticate(stream, opts);
84
59
  process.stdout.write(JSON.stringify(result, false, 2) + '\n');
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const { resolve } = require('node:dns').promises;
5
+
6
+ /**
7
+ * The DNS resolver for a command: answers from the --dns-cache file, or real DNS queries that are
8
+ * logged with --verbose. Undefined when neither is set, so the library uses its default resolver
9
+ *
10
+ * @param {Object} argv Command options
11
+ * @returns {Function|undefined} async (name, rrtype) resolver
12
+ */
13
+ const createResolver = async argv => {
14
+ if (argv.dnsCache) {
15
+ let dnsCache = JSON.parse(await fs.promises.readFile(argv.dnsCache, 'utf-8'));
16
+
17
+ return async (name, rr) => {
18
+ let match = dnsCache?.[name]?.[rr];
19
+
20
+ if (argv.verbose) {
21
+ console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'} (using cache)`);
22
+ }
23
+
24
+ if (!match) {
25
+ let err = new Error('Error');
26
+ err.code = 'ENOTFOUND';
27
+ throw err;
28
+ }
29
+
30
+ return match;
31
+ };
32
+ }
33
+
34
+ if (argv.verbose) {
35
+ return async (name, rr) => {
36
+ let match;
37
+ try {
38
+ match = await resolve(name, rr);
39
+ console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'}`);
40
+ return match;
41
+ } catch (err) {
42
+ console.error(`DNS query for ${rr} ${name}: ${err.message}${err.code ? ` [${err.code}]` : ''}`);
43
+ throw err;
44
+ }
45
+ };
46
+ }
47
+
48
+ return undefined;
49
+ };
50
+
51
+ module.exports = { createResolver };
@@ -5,7 +5,7 @@ const { authenticate } = require('../mailauth');
5
5
  const { createSeal } = require('../arc');
6
6
  const fs = require('node:fs');
7
7
  const { GathererStream } = require('../gatherer-stream');
8
- const { resolve } = require('node:dns').promises;
8
+ const { createResolver } = require('./resolver');
9
9
 
10
10
  const writeMessageBody = async gatherer =>
11
11
  new Promise((resolve, reject) => {
@@ -161,37 +161,7 @@ const cmd = async argv => {
161
161
  }
162
162
  }
163
163
 
164
- if (argv.dnsCache) {
165
- let dnsCache = JSON.parse(await fs.promises.readFile(argv.dnsCache, 'utf-8'));
166
-
167
- opts.resolver = async (name, rr) => {
168
- let match = dnsCache?.[name]?.[rr];
169
-
170
- if (argv.verbose) {
171
- console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'} (using cache)`);
172
- }
173
-
174
- if (!match) {
175
- let err = new Error('Error');
176
- err.code = 'ENOTFOUND';
177
- throw err;
178
- }
179
-
180
- return match;
181
- };
182
- } else if (argv.verbose) {
183
- opts.resolver = async (name, rr) => {
184
- let match;
185
- try {
186
- match = await resolve(name, rr);
187
- console.error(`DNS query for ${rr} ${name}: ${match ? JSON.stringify(match) : 'not found'}`);
188
- return match;
189
- } catch (err) {
190
- console.error(`DNS query for ${rr} ${name}: ${err.message}${err.code ? ` [${err.code}]` : ''}`);
191
- throw err;
192
- }
193
- };
194
- }
164
+ opts.resolver = await createResolver(argv);
195
165
 
196
166
  let result = await authenticate(gatherer, opts);
197
167