mailauth 4.1.0 → 4.3.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 CHANGED
@@ -294,6 +294,41 @@ 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(domain [,resolver])
304
+
305
+ Returns parsed DMARC DNS record for a domain or a subdomain or `false` is no record exists.
306
+
307
+ ```
308
+ const getDmarcRecord = require('mailauth/lib/dmarc/get-dmarc-record');
309
+ const dmarcRecord = await 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
+
330
+ Optionally set `resolver` argument with custom resolver (uses `dns.resolve` by default).
331
+
297
332
  ## BIMI
298
333
 
299
334
  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).
@@ -349,7 +384,7 @@ if (policy.mode === 'enforce') {
349
384
  // must use TLS
350
385
  }
351
386
 
352
- if (policy.mx && !policyMatch) {
387
+ if (policy.mx && !policyMatch.valid) {
353
388
  // can't connect, unlisted MX
354
389
  }
355
390
  ```
@@ -386,7 +421,7 @@ The function returns an object with the following properties:
386
421
  Check if a resolved MX hostname is valid by MTA-STS policy or not.
387
422
 
388
423
  ```
389
- validateMx(mx, policy) -> Boolean
424
+ validateMx(mx, policy) -> Object
390
425
  ```
391
426
 
392
427
  Where
@@ -394,7 +429,7 @@ Where
394
429
  - **mx** is the resolved MX hostname (eg. "gmail-smtp-in.l.google.com")
395
430
  - **policy** is the policy object returned by `getPolicy()`
396
431
 
397
- The function returns a boolean. If it is `true`, then MX hostname is allowed to use.
432
+ The function returns an object. If `{valid}` is `true`, then MX hostname is allowed to be used.
398
433
 
399
434
  ## Testing
400
435
 
@@ -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;
@@ -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/mta-sts.js CHANGED
@@ -139,9 +139,13 @@ const parsePolicy = file => {
139
139
  */
140
140
  const validateMx = (mx, policy) => {
141
141
  policy = policy || { mode: 'none' };
142
- if (policy.mode === 'none' || policy.mode === 'testing') {
142
+ if (policy.mode === 'none' || !policy.mode) {
143
143
  // nothing to check for
144
- return true;
144
+ return {
145
+ valid: true,
146
+ mode: policy.mode || 'none',
147
+ testing: policy.mode === 'testing'
148
+ };
145
149
  }
146
150
 
147
151
  mx = (mx || '').toString().trim().toLowerCase();
@@ -161,15 +165,29 @@ const validateMx = (mx, policy) => {
161
165
  // remove wildcard
162
166
  allowed = allowed.substr(1);
163
167
  if (mx.substr(-allowed.length) === allowed) {
164
- return true;
168
+ return {
169
+ valid: true,
170
+ mode: policy.mode || 'none',
171
+ match: allowed,
172
+ testing: policy.mode === 'testing'
173
+ };
165
174
  }
166
175
  } else if (allowed === mx) {
167
- return true;
176
+ return {
177
+ valid: true,
178
+ mode: policy.mode || 'none',
179
+ match: allowed,
180
+ testing: policy.mode === 'testing'
181
+ };
168
182
  }
169
183
  }
170
184
 
171
185
  // no match found
172
- return false;
186
+ return {
187
+ valid: false,
188
+ mode: policy.mode || 'none',
189
+ testing: policy.mode === 'testing'
190
+ };
173
191
  };
174
192
 
175
193
  /**
package/licenses.txt CHANGED
@@ -1,12 +1,11 @@
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.10 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.6.1 n/a
7
- libmime MIT git://github.com/andris9/libmime.git 5.1.0 Andris Reinman <andris@kreata.ee>
8
- node-forge (BSD-3-Clause OR GPL-2.0) git+https://github.com/digitalbazaar/forge.git 1.3.1 Digital Bazaar, Inc. support@digitalbazaar.com http://digitalbazaar.com/
9
- nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.7.8 Andris Reinman
10
- psl MIT git+ssh://git@github.com/lupomontero/psl.git 1.9.0 Lupo Montero <lupomontero@gmail.com> (https://lupomontero.com/)
11
- punycode MIT git+https://github.com/bestiejs/punycode.js.git 2.1.1 Mathias Bynens https://mathiasbynens.be/
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.1.1 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.1 Andris Reinman <andris@kreata.ee>
8
+ nodemailer MIT git+https://github.com/nodemailer/nodemailer.git 6.9.1 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
@@ -1,4 +1,4 @@
1
- .TH "MAILAUTH" "1" "January 2023" "v4.0.2" "Mailauth Help"
1
+ .TH "MAILAUTH" "1" "March 2023" "v4.2.0" "Mailauth Help"
2
2
  .SH "NAME"
3
3
  \fBmailauth\fR
4
4
  .QP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailauth",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "Email authentication library for Node.js",
5
5
  "main": "lib/mailauth.js",
6
6
  "scripts": {
@@ -33,7 +33,7 @@
33
33
  "homepage": "https://github.com/postalsys/mailauth",
34
34
  "devDependencies": {
35
35
  "chai": "4.3.7",
36
- "eslint": "8.32.0",
36
+ "eslint": "8.35.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "8.6.0",
39
39
  "js-yaml": "4.1.0",
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@postalsys/vmc": "1.0.6",
49
- "fast-xml-parser": "4.0.15",
49
+ "fast-xml-parser": "4.1.3",
50
50
  "ipaddr.js": "2.0.1",
51
- "joi": "17.7.0",
52
- "libmime": "5.2.0",
53
- "nodemailer": "6.9.0",
51
+ "joi": "17.8.3",
52
+ "libmime": "5.2.1",
53
+ "nodemailer": "6.9.1",
54
54
  "psl": "1.9.0",
55
55
  "punycode": "2.3.0",
56
- "yargs": "17.6.2"
56
+ "yargs": "17.7.1"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=16.0.0"