mailauth 4.0.2 → 4.2.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/README.md +33 -0
- package/lib/dmarc/get-dmarc-record.js +75 -0
- package/lib/dmarc/verify.js +1 -67
- package/lib/tools.js +1 -9
- package/licenses.txt +11 -12
- package/man/mailauth.1 +1 -1
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -294,6 +294,39 @@ process.stdout.write(headers); // authentication results
|
|
|
294
294
|
process.stdout.write(message);
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
+
## DMARC
|
|
298
|
+
|
|
299
|
+
DMARC is verified as part of the authentication process and even as the `dmarc` handler is exported, it requires input from previous steps.
|
|
300
|
+
|
|
301
|
+
### Helpers
|
|
302
|
+
|
|
303
|
+
#### getDmarcRecord
|
|
304
|
+
|
|
305
|
+
Returns parsed DMARC DNS record for a domain, or a subdomain
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
const getDmarcRecord = require('mailauth/lib/dmarc/get-dmarc-record');
|
|
309
|
+
const dmarcRecord = getDmarcRecord("ethereal.email");
|
|
310
|
+
console.log(dmarcRecord);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Output**
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
{
|
|
317
|
+
v: 'DMARC1',
|
|
318
|
+
p: 'none',
|
|
319
|
+
pct: 100,
|
|
320
|
+
rua: 'mailto:re+joqy8fpatm3@dmarc.postmarkapp.com',
|
|
321
|
+
sp: 'none',
|
|
322
|
+
aspf: 'r',
|
|
323
|
+
rr: 'v=DMARC1; p=none; pct=100; rua=mailto:re+joqy8fpatm3@dmarc.postmarkapp.com; sp=none; aspf=r;',
|
|
324
|
+
isOrgRecord: false
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`isOrgRecord` is `true` for sudomains, where organizational domain's DMARC policy applies, so use the `sp`, not `p` policy.
|
|
329
|
+
|
|
297
330
|
## BIMI
|
|
298
331
|
|
|
299
332
|
Brand Indicators for Message Identification (BIMI) support is based on [draft-blank-ietf-bimi-01](https://tools.ietf.org/html/draft-blank-ietf-bimi-01).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const psl = require('psl');
|
|
4
|
+
const dns = require('dns').promises;
|
|
5
|
+
|
|
6
|
+
const resolveTxt = async (domain, resolver) => {
|
|
7
|
+
try {
|
|
8
|
+
let txt = await resolver(`_dmarc.${domain}`, 'TXT');
|
|
9
|
+
if (!txt || !txt.length) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
txt = txt.map(row => row.join('').trim()).filter(row => /^v=DMARC1\b/i.test(row));
|
|
14
|
+
|
|
15
|
+
if (txt.length !== 1) {
|
|
16
|
+
//no records or multiple records yield in no policy
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return txt[0];
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err.code === 'ENOTFOUND' || err.code === 'ENODATA') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getDmarcRecord = async (domain, resolver) => {
|
|
30
|
+
resolver = resolver || dns.resolve;
|
|
31
|
+
|
|
32
|
+
let txt = await resolveTxt(domain, resolver);
|
|
33
|
+
let isOrgRecord = false;
|
|
34
|
+
|
|
35
|
+
if (!txt) {
|
|
36
|
+
let orgDomain = psl.get(domain);
|
|
37
|
+
if (orgDomain !== domain) {
|
|
38
|
+
// try org domain as well
|
|
39
|
+
txt = await resolveTxt(orgDomain, resolver);
|
|
40
|
+
isOrgRecord = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!txt) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let parsed = Object.fromEntries(
|
|
49
|
+
txt
|
|
50
|
+
.split(';')
|
|
51
|
+
.map(e => e.trim())
|
|
52
|
+
.filter(e => e)
|
|
53
|
+
.map(e => {
|
|
54
|
+
let splitPos = e.indexOf('=');
|
|
55
|
+
if (splitPos < 0) {
|
|
56
|
+
return [e.toLowerCase().trim(), false];
|
|
57
|
+
} else if (splitPos === 0) {
|
|
58
|
+
return [false, e];
|
|
59
|
+
}
|
|
60
|
+
let key = e.substr(0, splitPos).toLowerCase().trim();
|
|
61
|
+
let val = e.substr(splitPos + 1);
|
|
62
|
+
if (['pct', 'ri'].includes(key)) {
|
|
63
|
+
val = parseInt(val, 10) || 0;
|
|
64
|
+
}
|
|
65
|
+
return [key, val];
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
parsed.rr = txt;
|
|
70
|
+
parsed.isOrgRecord = isOrgRecord;
|
|
71
|
+
|
|
72
|
+
return parsed;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
module.exports = getDmarcRecord;
|
package/lib/dmarc/verify.js
CHANGED
|
@@ -4,73 +4,7 @@ const dns = require('dns').promises;
|
|
|
4
4
|
const punycode = require('punycode/');
|
|
5
5
|
const psl = require('psl');
|
|
6
6
|
const { formatAuthHeaderRow, getAlignment } = require('../tools');
|
|
7
|
-
|
|
8
|
-
const resolveTxt = async (domain, resolver) => {
|
|
9
|
-
try {
|
|
10
|
-
let txt = await resolver(`_dmarc.${domain}`, 'TXT');
|
|
11
|
-
if (!txt || !txt.length) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
txt = txt.map(row => row.join('').trim()).filter(row => /^v=DMARC1\b/i.test(row));
|
|
16
|
-
|
|
17
|
-
if (txt.length !== 1) {
|
|
18
|
-
//no records or multiple records yield in no policy
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return txt[0];
|
|
23
|
-
} catch (err) {
|
|
24
|
-
if (err.code === 'ENOTFOUND' || err.code === 'ENODATA') {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
throw err;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const getDmarcRecord = async (domain, resolver) => {
|
|
32
|
-
let txt = await resolveTxt(domain, resolver);
|
|
33
|
-
let isOrgRecord = false;
|
|
34
|
-
|
|
35
|
-
if (!txt) {
|
|
36
|
-
let orgDomain = psl.get(domain);
|
|
37
|
-
if (orgDomain !== domain) {
|
|
38
|
-
// try org domain as well
|
|
39
|
-
txt = await resolveTxt(orgDomain, resolver);
|
|
40
|
-
isOrgRecord = true;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!txt) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let parsed = Object.fromEntries(
|
|
49
|
-
txt
|
|
50
|
-
.split(';')
|
|
51
|
-
.map(e => e.trim())
|
|
52
|
-
.filter(e => e)
|
|
53
|
-
.map(e => {
|
|
54
|
-
let splitPos = e.indexOf('=');
|
|
55
|
-
if (splitPos < 0) {
|
|
56
|
-
return [e.toLowerCase().trim(), false];
|
|
57
|
-
} else if (splitPos === 0) {
|
|
58
|
-
return [false, e];
|
|
59
|
-
}
|
|
60
|
-
let key = e.substr(0, splitPos).toLowerCase().trim();
|
|
61
|
-
let val = e.substr(splitPos + 1);
|
|
62
|
-
if (['pct', 'ri'].includes(key)) {
|
|
63
|
-
val = parseInt(val, 10) || 0;
|
|
64
|
-
}
|
|
65
|
-
return [key, val];
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
parsed.rr = txt;
|
|
70
|
-
parsed.isOrgRecord = isOrgRecord;
|
|
71
|
-
|
|
72
|
-
return parsed;
|
|
73
|
-
};
|
|
7
|
+
const getDmarcRecord = require('./get-dmarc-record');
|
|
74
8
|
|
|
75
9
|
const verifyDmarc = async opts => {
|
|
76
10
|
let { headerFrom, spfDomains, dkimDomains, resolver, arcResult } = opts;
|
package/lib/tools.js
CHANGED
|
@@ -10,7 +10,6 @@ const https = require('https');
|
|
|
10
10
|
const packageData = require('../package');
|
|
11
11
|
const parseDkimHeaders = require('./parse-dkim-headers');
|
|
12
12
|
const psl = require('psl');
|
|
13
|
-
const pki = require('node-forge').pki;
|
|
14
13
|
const Joi = require('joi');
|
|
15
14
|
const base64Schema = Joi.string().base64({ paddingRequired: false });
|
|
16
15
|
|
|
@@ -288,14 +287,7 @@ const getPublicKey = async (type, name, minBitLength, resolver) => {
|
|
|
288
287
|
throw err;
|
|
289
288
|
}
|
|
290
289
|
|
|
291
|
-
let modulusLength;
|
|
292
|
-
if (publicKeyObj.asymmetricKeyDetails) {
|
|
293
|
-
modulusLength = publicKeyObj.asymmetricKeyDetails.modulusLength;
|
|
294
|
-
} else {
|
|
295
|
-
// fall back to node-forge
|
|
296
|
-
const pubKeyData = pki.publicKeyFromPem(publicKeyPem.toString());
|
|
297
|
-
modulusLength = pubKeyData.n.bitLength();
|
|
298
|
-
}
|
|
290
|
+
let modulusLength = publicKeyObj.asymmetricKeyDetails.modulusLength;
|
|
299
291
|
|
|
300
292
|
if (keyType === 'rsa' && modulusLength < 1024) {
|
|
301
293
|
let err = new Error('RSA key too short');
|
package/licenses.txt
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
name license type
|
|
2
|
-
---- ------------
|
|
3
|
-
@postalsys/vmc MIT
|
|
4
|
-
fast-xml-parser MIT
|
|
5
|
-
ipaddr.js MIT
|
|
6
|
-
joi BSD-3-Clause
|
|
7
|
-
libmime MIT
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
yargs MIT git+https://github.com/yargs/yargs.git 17.5.1 n/a
|
|
1
|
+
name license type link installed version author
|
|
2
|
+
---- ------------ ---- ----------------- ------
|
|
3
|
+
@postalsys/vmc MIT https://registry.npmjs.org/@postalsys/vmc/-/vmc-1.0.6.tgz 1.0.6 Postal Systems OÜ
|
|
4
|
+
fast-xml-parser MIT git+https://github.com/NaturalIntelligence/fast-xml-parser.git 4.0.15 Amit Gupta (https://amitkumargupta.work/)
|
|
5
|
+
ipaddr.js MIT git://github.com/whitequark/ipaddr.js.git 2.0.1 whitequark <whitequark@whitequark.org>
|
|
6
|
+
joi BSD-3-Clause git://github.com/hapijs/joi.git 17.7.0 n/a
|
|
7
|
+
libmime MIT git://github.com/andris9/libmime.git 5.2.0 Andris Reinman <andris@kreata.ee>
|
|
8
|
+
nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.9.0 Andris Reinman
|
|
9
|
+
psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.9.0 Lupo Montero <lupomontero@gmail.com> (https://lupomontero.com/)
|
|
10
|
+
punycode MIT git+https://github.com/mathiasbynens/punycode.js.git 2.3.0 Mathias Bynens https://mathiasbynens.be/
|
|
11
|
+
yargs MIT git+https://github.com/yargs/yargs.git 17.6.2 n/a
|
package/man/mailauth.1
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,29 +32,28 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"chai": "4.3.
|
|
36
|
-
"eslint": "8.
|
|
35
|
+
"chai": "4.3.7",
|
|
36
|
+
"eslint": "8.33.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
|
-
"eslint-config-prettier": "8.
|
|
38
|
+
"eslint-config-prettier": "8.6.0",
|
|
39
39
|
"js-yaml": "4.1.0",
|
|
40
|
-
"license-report": "6.
|
|
40
|
+
"license-report": "6.3.0",
|
|
41
41
|
"marked": "0.7.0",
|
|
42
42
|
"marked-man": "0.7.0",
|
|
43
43
|
"mbox-reader": "1.1.5",
|
|
44
|
-
"mocha": "10.
|
|
44
|
+
"mocha": "10.2.0",
|
|
45
45
|
"pkg": "5.8.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@postalsys/vmc": "1.0.6",
|
|
49
|
-
"fast-xml-parser": "4.
|
|
49
|
+
"fast-xml-parser": "4.1.1",
|
|
50
50
|
"ipaddr.js": "2.0.1",
|
|
51
|
-
"joi": "17.
|
|
52
|
-
"libmime": "5.1
|
|
53
|
-
"
|
|
54
|
-
"nodemailer": "6.7.8",
|
|
51
|
+
"joi": "17.7.0",
|
|
52
|
+
"libmime": "5.2.1",
|
|
53
|
+
"nodemailer": "6.9.1",
|
|
55
54
|
"psl": "1.9.0",
|
|
56
|
-
"punycode": "2.
|
|
57
|
-
"yargs": "17.
|
|
55
|
+
"punycode": "2.3.0",
|
|
56
|
+
"yargs": "17.6.2"
|
|
58
57
|
},
|
|
59
58
|
"engines": {
|
|
60
59
|
"node": ">=16.0.0"
|