-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
126 lines (112 loc) · 3.83 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const path = require('path');
const { exec, execSync } = require('child_process');
const debug = require('debug')('authheaders');
const ms = require('ms');
const semver = require('semver');
const scripts = {
authenticateMessage: path.join(
__dirname,
'scripts',
'authenticate-message.py'
)
};
// ensure python installed
try {
execSync('which python3', {
stdio: 'ignore',
encoding: 'utf8',
timeout: ms('5s')
});
} catch (err) {
debug(err);
throw new Error(`Python v3.5+ is required`);
}
// ensure python v3.5+
const version = semver.coerce(
execSync('python3 --version', { encoding: 'utf8', timeout: ms('5s') })
.split(' ')[1]
.trim()
);
if (!semver.satisfies(version, '>= 3.5'))
throw new Error(
`Python v3.5+ is required, you currently have v${version} installed`
);
const KEYS = ['spf', 'dkim', 'arc', 'dmarc'];
function authenticateMessage(message, ...args) {
const command = `python3 ${scripts.authenticateMessage} ${args.join(' ')}`;
debug(command);
return new Promise((resolve, reject) => {
const child = exec(command, {
encoding: 'utf8',
timeout: ms('10s')
});
const stdout = [];
const stderr = [];
child.stderr.on('data', (data) => {
stderr.push(data);
});
child.stdout.on('data', (data) => {
stdout.push(data);
});
child.stdin.write(message);
child.stdin.end();
child.on('close', () => {
if (stderr.length > 0) return reject(new Error(stderr.join('')));
// Authentication-Results: mx1.forwardemail.net; spf=fail reason="SPF fail - not authorized" smtp.helo=jacks-macbook-pro.local [email protected]; dkim=fail; arc=none; dmarc=fail (Used From Domain Record) header.from=forwardemail.net policy.dmarc=reject
const result = {
header: stdout.join('').trim().split('Authentication-Results: ')[1]
};
// TODO: investigate thie edge case further and follow up with authheaders/dkimpy authors
if (!result.header)
return reject(
new Error(
`No Authentication-Results header was returned. Data: ${JSON.stringify(
{ message, stdout, stderr }
)}`
)
);
for (const key of KEYS) {
result[key] = {};
}
result.dmarc.policy = 'none';
const terms = result.header
.split(/;/)
.map((t) => t.trim())
.filter((t) => t !== '');
for (const term of terms) {
const split = term.split('=');
if (term.startsWith('spf=')) {
result.spf.result = split[1].split(' ')[0];
const index = term.indexOf('reason=');
if (index !== -1)
result.spf.reason = term
.slice(index + 'reason='.length)
.split('"')[1];
} else if (term.startsWith('dkim=')) {
result.dkim.result = split[1].split(' ')[0];
const index = term.indexOf('(');
if (index !== -1)
result.dkim.reason = term.slice(index + '('.length).split(')')[0];
} else if (term.startsWith('arc=')) {
result.arc.result = split[1].split(' ')[0];
// TODO: right now this does not return a comment due to this issue:
// <https://github.com/ValiMail/authentication-headers/issues/12
} else if (term.startsWith('dmarc=')) {
result.dmarc.result = split[1].split(' ')[0];
// reason
const index = term.indexOf('(');
if (index !== -1)
result.dmarc.reason = term.slice(index + '('.length).split(')')[0];
// policy
const policyIndex = term.indexOf('policy.dmarc=');
if (policyIndex !== -1)
result.dmarc.policy = term
.slice(policyIndex + 'policy.dmarc='.length)
.split(' ')[0];
}
}
resolve(result);
});
});
}
module.exports = { authenticateMessage };