Haraka 2.8.28 → 3.0.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/.eslintrc.yaml +2 -10
- package/Changes.md +84 -2
- package/Dockerfile +1 -1
- package/Plugins.md +9 -4
- package/README.md +2 -6
- package/bin/haraka +5 -4
- package/config/outbound.ini +0 -7
- package/config/plugins +1 -1
- package/config/smtp.ini +1 -1
- package/config/smtp_forward.ini +2 -8
- package/config/smtp_proxy.ini +0 -6
- package/connection.js +178 -204
- package/coverage/lcov.info +13863 -0
- package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
- package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
- package/dkim.js +66 -73
- package/docs/Body.md +1 -22
- package/docs/CoreConfig.md +2 -2
- package/docs/Header.md +1 -47
- package/docs/Outbound.md +8 -36
- package/endpoint.js +1 -1
- package/haraka.js +1 -1
- package/host_pool.js +8 -12
- package/logger.js +25 -32
- package/outbound/client_pool.js +11 -153
- package/outbound/config.js +5 -11
- package/outbound/hmail.js +109 -143
- package/outbound/index.js +13 -25
- package/outbound/mx_lookup.js +10 -7
- package/outbound/queue.js +8 -12
- package/outbound/timer_queue.js +2 -4
- package/outbound/tls.js +17 -18
- package/outbound/todo.js +1 -0
- package/package.json +57 -55
- package/plugins/auth/auth_base.js +39 -63
- package/plugins/auth/auth_bridge.js +3 -4
- package/plugins/auth/auth_proxy.js +16 -16
- package/plugins/auth/auth_vpopmaild.js +30 -37
- package/plugins/auth/flat_file.js +9 -13
- package/plugins/avg.js +9 -11
- package/plugins/backscatterer.js +1 -1
- package/plugins/block_me.js +2 -6
- package/plugins/bounce.js +106 -124
- package/plugins/clamd.js +59 -63
- package/plugins/data.signatures.js +6 -6
- package/plugins/data.uribl.js +1 -415
- package/plugins/delay_deny.js +19 -20
- package/plugins/dkim_sign.js +56 -62
- package/plugins/dkim_verify.js +9 -8
- package/plugins/dns_list_base.js +43 -42
- package/plugins/dnsbl.js +41 -46
- package/plugins/dnswl.js +23 -26
- package/plugins/early_talker.js +24 -28
- package/plugins/esets.js +8 -11
- package/plugins/greylist.js +161 -190
- package/plugins/helo.checks.js +175 -197
- package/plugins/mail_from.is_resolvable.js +38 -38
- package/plugins/messagesniffer.js +33 -40
- package/plugins/prevent_credential_leaks.js +7 -5
- package/plugins/process_title.js +16 -17
- package/plugins/queue/deliver.js +2 -2
- package/plugins/queue/lmtp.js +5 -6
- package/plugins/queue/qmail-queue.js +11 -13
- package/plugins/queue/quarantine.js +25 -34
- package/plugins/queue/rabbitmq.js +3 -2
- package/plugins/queue/rabbitmq_amqplib.js +9 -9
- package/plugins/queue/smtp_bridge.js +5 -4
- package/plugins/queue/smtp_forward.js +81 -89
- package/plugins/queue/smtp_proxy.js +21 -22
- package/plugins/queue/test.js +2 -1
- package/plugins/rcpt_to.host_list_base.js +20 -30
- package/plugins/rcpt_to.in_host_list.js +12 -14
- package/plugins/rcpt_to.max_count.js +7 -5
- package/plugins/record_envelope_addresses.js +4 -6
- package/plugins/relay.js +64 -74
- package/plugins/reseed_rng.js +1 -2
- package/plugins/spamassassin.js +56 -68
- package/plugins/status.js +2 -3
- package/plugins/tarpit.js +8 -11
- package/plugins/tls.js +14 -17
- package/plugins/toobusy.js +6 -8
- package/plugins/xclient.js +14 -25
- package/plugins.js +24 -29
- package/rfc1869.js +2 -2
- package/server.js +3 -13
- package/smtp_client.js +138 -215
- package/tests/config/smtp_forward.ini +0 -6
- package/tests/fixtures/line_socket.js +1 -1
- package/tests/fixtures/util_hmailitem.js +5 -7
- package/tests/fixtures/vm_harness.js +2 -2
- package/tests/host_pool.js +13 -14
- package/tests/installation/plugins/inherits.js +1 -2
- package/tests/logger.js +2 -2
- package/tests/plugins/bounce.js +6 -8
- package/tests/plugins/dkim_signer.js +7 -7
- package/tests/plugins/dns_list_base.js +7 -7
- package/tests/plugins/helo.checks.js +1 -1
- package/tests/plugins/mail_from.is_resolvable.js +10 -54
- package/tests/plugins/queue/smtp_forward.js +11 -11
- package/tests/plugins/rcpt_to.host_list_base.js +1 -1
- package/tests/plugins/rcpt_to.in_host_list.js +1 -1
- package/tests/plugins/spamassassin.js +1 -1
- package/tests/queue/multibyte +0 -0
- package/tests/queue/plain +0 -0
- package/tests/rfc1869.js +4 -1
- package/tests/server.js +15 -9
- package/tests/smtp_client/auth.js +4 -14
- package/tests/smtp_client/basic.js +5 -15
- package/tests/smtp_client.js +7 -3
- package/tests/transaction.js +72 -19
- package/tls_socket.js +75 -85
- package/transaction.js +7 -9
- package/attachment_stream.js +0 -118
- package/bin/spf +0 -48
- package/chunkemitter.js +0 -75
- package/config/data.uribl.excludes +0 -202
- package/config/data.uribl.ini +0 -37
- package/config/spf.ini +0 -1
- package/docs/plugins/attachment.md +0 -92
- package/docs/plugins/data.uribl.md +0 -120
- package/docs/plugins/spf.md +0 -142
- package/mailbody.js +0 -502
- package/mailheader.js +0 -304
- package/messagestream.js +0 -441
- package/plugins/aliases.js +0 -120
- package/plugins/attachment.js +0 -503
- package/plugins/connect.p0f.js +0 -5
- package/plugins/spf.js +0 -327
- package/spf.js +0 -689
- package/tests/mailbody.js +0 -348
- package/tests/mailheader.js +0 -138
- package/tests/messagestream.js +0 -34
- package/tests/plugins/aliases.js +0 -376
- package/tests/plugins/spf.js +0 -251
- package/tests/spf.js +0 -96
package/spf.js
DELETED
|
@@ -1,689 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// spf
|
|
3
|
-
|
|
4
|
-
const dns = require('dns');
|
|
5
|
-
const ipaddr = require('ipaddr.js');
|
|
6
|
-
const net_utils = require('haraka-net-utils')
|
|
7
|
-
|
|
8
|
-
class SPF {
|
|
9
|
-
constructor (count, been_there) {
|
|
10
|
-
// For macro expansion
|
|
11
|
-
// This should be set before check_host() is called
|
|
12
|
-
this.helo = 'unknown';
|
|
13
|
-
this.spf_record = '';
|
|
14
|
-
|
|
15
|
-
// RFC 4408 Section 10.1
|
|
16
|
-
// Limit the number of mechanisms/modifiers that require DNS lookups to complete.
|
|
17
|
-
this.count = 0;
|
|
18
|
-
|
|
19
|
-
// If we have recursed we are supplied the count
|
|
20
|
-
if (count) this.count = count;
|
|
21
|
-
|
|
22
|
-
// Prevent circular references, this isn't covered in the RFC
|
|
23
|
-
this.been_there = {};
|
|
24
|
-
if (been_there) this.been_there = been_there;
|
|
25
|
-
|
|
26
|
-
// RFC 4408 Section 10.1
|
|
27
|
-
this.LIMIT = 10;
|
|
28
|
-
|
|
29
|
-
// Constants
|
|
30
|
-
this.SPF_NONE = 1;
|
|
31
|
-
this.SPF_PASS = 2;
|
|
32
|
-
this.SPF_FAIL = 3;
|
|
33
|
-
this.SPF_SOFTFAIL = 4;
|
|
34
|
-
this.SPF_NEUTRAL = 5;
|
|
35
|
-
this.SPF_TEMPERROR = 6;
|
|
36
|
-
this.SPF_PERMERROR = 7;
|
|
37
|
-
|
|
38
|
-
this.mech_ip4 = this.mech_ip;
|
|
39
|
-
this.mech_ip6 = this.mech_ip;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const_translate (value) {
|
|
43
|
-
const t = {};
|
|
44
|
-
for (const k in this) {
|
|
45
|
-
if (typeof this[k] === 'number') {
|
|
46
|
-
t[this[k]] = k.toUpperCase();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (t[value]) return t[value];
|
|
50
|
-
return 'UNKNOWN';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
result (value) {
|
|
54
|
-
switch (value) {
|
|
55
|
-
case this.SPF_NONE: return 'None';
|
|
56
|
-
case this.SPF_PASS: return 'Pass';
|
|
57
|
-
case this.SPF_FAIL: return 'Fail';
|
|
58
|
-
case this.SPF_SOFTFAIL: return 'SoftFail';
|
|
59
|
-
case this.SPF_NEUTRAL: return 'Neutral';
|
|
60
|
-
case this.SPF_TEMPERROR: return 'TempError';
|
|
61
|
-
case this.SPF_PERMERROR: return 'PermError';
|
|
62
|
-
default: return `Unknown (${value})`;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return_const (qualifier) {
|
|
67
|
-
switch (qualifier) {
|
|
68
|
-
case '+': return this.SPF_PASS;
|
|
69
|
-
case '-': return this.SPF_FAIL;
|
|
70
|
-
case '~': return this.SPF_SOFTFAIL;
|
|
71
|
-
case '?': return this.SPF_NEUTRAL;
|
|
72
|
-
default: return this.SPF_PERMERROR;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
expand_macros (str) {
|
|
77
|
-
const macro = /%{([slodipvh])((?:(?:\d+)?r?)?)?([-.+,/_=])?}/ig;
|
|
78
|
-
let match;
|
|
79
|
-
while ((match = macro.exec(str))) {
|
|
80
|
-
// match[1] = macro-letter
|
|
81
|
-
// match[2] = transformers
|
|
82
|
-
// match[3] = delimiter
|
|
83
|
-
if (!match[3]) match[3] = '.';
|
|
84
|
-
let strip = /(\d+)/.exec(match[2]);
|
|
85
|
-
if (strip) strip = strip[1];
|
|
86
|
-
|
|
87
|
-
const reverse = (((`${match[2]}`).indexOf('r')) !== -1);
|
|
88
|
-
let replace;
|
|
89
|
-
let kind;
|
|
90
|
-
switch (match[1]) {
|
|
91
|
-
case 's': // sender
|
|
92
|
-
replace = this.mail_from;
|
|
93
|
-
break;
|
|
94
|
-
case 'l': // local-part of sender
|
|
95
|
-
replace = (this.mail_from.split('@'))[0];
|
|
96
|
-
break;
|
|
97
|
-
case 'o': // domain of sender
|
|
98
|
-
replace = (this.mail_from.split('@'))[1];
|
|
99
|
-
break;
|
|
100
|
-
case 'd': // domain
|
|
101
|
-
replace = this.domain;
|
|
102
|
-
break;
|
|
103
|
-
case 'i': // IP
|
|
104
|
-
replace = this.ip;
|
|
105
|
-
break;
|
|
106
|
-
case 'p': // validated domain name of IP
|
|
107
|
-
// NOT IMPLEMENTED
|
|
108
|
-
replace = 'unknown';
|
|
109
|
-
break;
|
|
110
|
-
case 'v': // IP version
|
|
111
|
-
try {
|
|
112
|
-
if (this.ip_ver === 'ipv4') kind = 'in-addr';
|
|
113
|
-
if (this.ip_ver === 'ipv6') kind = 'ip6';
|
|
114
|
-
replace = kind;
|
|
115
|
-
}
|
|
116
|
-
catch (e) {}
|
|
117
|
-
break;
|
|
118
|
-
case 'h': // EHLO/HELO domain
|
|
119
|
-
replace = this.helo;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
// Process any transformers
|
|
123
|
-
if (replace) {
|
|
124
|
-
if (reverse || strip) {
|
|
125
|
-
replace = replace.split(match[3]);
|
|
126
|
-
if (strip) {
|
|
127
|
-
strip = ((strip > replace.length) ? replace.length : strip);
|
|
128
|
-
replace = replace.slice(0,strip);
|
|
129
|
-
}
|
|
130
|
-
if (reverse) replace = replace.reverse();
|
|
131
|
-
replace = replace.join('.');
|
|
132
|
-
}
|
|
133
|
-
str = str.replace(match[0], replace);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Process any other expansions
|
|
137
|
-
str = str.replace(/%%/g, '%');
|
|
138
|
-
str = str.replace(/%_/g, ' ');
|
|
139
|
-
str = str.replace(/%-/g, '%20');
|
|
140
|
-
return str;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
log_debug (str) {
|
|
144
|
-
console.error(str);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
valid_ip (ip) {
|
|
148
|
-
const ip_split = /^:([^/ ]+)(?:\/([^ ]+))?$/.exec(ip);
|
|
149
|
-
if (!ip_split) {
|
|
150
|
-
this.log_debug(`invalid IP address: ${ip}`);
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
if (!ipaddr.isValid(ip_split[1])) {
|
|
154
|
-
this.log_debug(`invalid IP address: ${ip_split[1]}`);
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
check_host (ip, domain, mail_from, cb) {
|
|
161
|
-
const self = this;
|
|
162
|
-
domain = domain.toLowerCase();
|
|
163
|
-
if (mail_from) {
|
|
164
|
-
mail_from = mail_from.toLowerCase();
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
mail_from = `postmaster@${domain}`;
|
|
168
|
-
}
|
|
169
|
-
this.ipaddr = ipaddr.parse(ip);
|
|
170
|
-
this.ip_ver = this.ipaddr.kind();
|
|
171
|
-
if (this.ip_ver === 'ipv6') {
|
|
172
|
-
this.ip = this.ipaddr.toString();
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
this.ip = ip;
|
|
176
|
-
}
|
|
177
|
-
this.domain = domain;
|
|
178
|
-
this.mail_from = mail_from;
|
|
179
|
-
|
|
180
|
-
this.log_debug(`ip=${ip} domain=${domain} mail_from=${mail_from}`);
|
|
181
|
-
|
|
182
|
-
// Get the SPF record for domain
|
|
183
|
-
dns.resolveTxt(domain, (err, txt_rrs) => {
|
|
184
|
-
if (err) {
|
|
185
|
-
self.log_debug(`error looking up TXT record: ${err.message}`);
|
|
186
|
-
switch (err.code) {
|
|
187
|
-
case dns.NOTFOUND:
|
|
188
|
-
case dns.NODATA:
|
|
189
|
-
case dns.NXDOMAIN: return cb(null, self.SPF_NONE);
|
|
190
|
-
default: return cb(null, self.SPF_TEMPERROR);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
let spf_record;
|
|
195
|
-
let match;
|
|
196
|
-
for (let txt_rr of txt_rrs) {
|
|
197
|
-
// Node 0.11.x compatibility
|
|
198
|
-
if (Array.isArray(txt_rr)) txt_rr = txt_rr.join('');
|
|
199
|
-
|
|
200
|
-
match = /^(v=spf1(?:$|\s.+$))/i.exec(txt_rr);
|
|
201
|
-
if (!match) {
|
|
202
|
-
self.log_debug(`discarding TXT record: ${txt_rr}`);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!spf_record) {
|
|
207
|
-
self.log_debug(`found SPF record for domain ${domain}: ${match[1]}`);
|
|
208
|
-
spf_record = match[1].replace(/\s+/, ' ').toLowerCase();
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
// already found an MX record
|
|
212
|
-
self.log_debug(`found additional SPF record for domain ${domain}: ${match[1]}`);
|
|
213
|
-
return cb(null, self.SPF_PERMERROR);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!spf_record) return cb(null, self.SPF_NONE); // No SPF record?
|
|
218
|
-
|
|
219
|
-
// Store the SPF record used in the object
|
|
220
|
-
self.spf_record = spf_record;
|
|
221
|
-
|
|
222
|
-
// Validate SPF record and build call chain
|
|
223
|
-
const mech_array = [];
|
|
224
|
-
const mod_array = [];
|
|
225
|
-
const mech_regexp1 = /^([-+~?])?(all|a|mx|ptr)$/;
|
|
226
|
-
const mech_regexp2 = /^([-+~?])?(a|mx|ptr|ip4|ip6|include|exists)((?::[^/ ]+(?:\/\d+(?:\/\/\d+)?)?)|\/\d+(?:\/\/\d+)?)$/;
|
|
227
|
-
const mod_regexp = /^([^ =]+)=([a-z0-9:/._-]+)$/;
|
|
228
|
-
const split = spf_record.split(' ');
|
|
229
|
-
for (let i=1; i<split.length; i++) {
|
|
230
|
-
// Skip blanks
|
|
231
|
-
let obj;
|
|
232
|
-
if (!split[i]) continue;
|
|
233
|
-
if ((match = (mech_regexp1.exec(split[i]) || mech_regexp2.exec(split[i])))) {
|
|
234
|
-
// match[1] = qualifier
|
|
235
|
-
// match[2] = mechanism
|
|
236
|
-
// match[3] = optional args
|
|
237
|
-
if (!match[1]) match[1] = '+';
|
|
238
|
-
self.log_debug(`found mechanism: ${match}`);
|
|
239
|
-
|
|
240
|
-
if (match[2] === 'ip4' || match[2] === 'ip6') {
|
|
241
|
-
if (!this.valid_ip(match[3])) return cb(null, self.SPF_PERMERROR);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
// Validate macro strings
|
|
245
|
-
if (match[3] && /%[^{%+-]/.exec(match[3])) {
|
|
246
|
-
self.log_debug('invalid macro string');
|
|
247
|
-
return cb(null, self.SPF_PERMERROR);
|
|
248
|
-
}
|
|
249
|
-
if (match[3]) {
|
|
250
|
-
// Expand macros
|
|
251
|
-
match[3] = self.expand_macros(match[3]);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
obj = {};
|
|
256
|
-
obj[match[2]] = [ match[1], match[3] ];
|
|
257
|
-
mech_array.push(obj);
|
|
258
|
-
}
|
|
259
|
-
else if ((match = mod_regexp.exec(split[i]))) {
|
|
260
|
-
self.log_debug(`found modifier: ${match}`);
|
|
261
|
-
// match[1] = modifier
|
|
262
|
-
// match[2] = name
|
|
263
|
-
// Make sure we have a method
|
|
264
|
-
if (!self[`mod_${match[1]}`]) {
|
|
265
|
-
self.log_debug(`skipping unknown modifier: ${match[1]}`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
obj = {};
|
|
269
|
-
obj[match[1]] = match[2];
|
|
270
|
-
mod_array.push(obj);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
// Syntax error
|
|
275
|
-
self.log_debug(`syntax error: ${split[i]}`);
|
|
276
|
-
return cb(null, self.SPF_PERMERROR);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
self.log_debug(`SPF record for '${self.domain}' validated OK`);
|
|
281
|
-
|
|
282
|
-
// Set-up modifier run chain
|
|
283
|
-
function mod_chain_caller (err2, result) {
|
|
284
|
-
// Throw any errors
|
|
285
|
-
if (err2) throw err2;
|
|
286
|
-
// Check limits
|
|
287
|
-
if (self.count > self.LIMIT) {
|
|
288
|
-
self.log_debug('lookup limit reached');
|
|
289
|
-
return cb(null, self.SPF_PERMERROR);
|
|
290
|
-
}
|
|
291
|
-
// Return any result that is not SPF_NONE
|
|
292
|
-
if (result && result !== self.SPF_NONE) {
|
|
293
|
-
return cb(err2, result);
|
|
294
|
-
}
|
|
295
|
-
if (!mod_array.length) {
|
|
296
|
-
return cb(null, self.SPF_NEUTRAL);
|
|
297
|
-
}
|
|
298
|
-
const next_in_chain = mod_array.shift();
|
|
299
|
-
const func = Object.keys(next_in_chain);
|
|
300
|
-
const args = next_in_chain[func];
|
|
301
|
-
self.log_debug(`running modifier: ${func} args=${args} domain=${self.domain}`);
|
|
302
|
-
self[`mod_${func}`](args, mod_chain_caller);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Run all the mechanisms first
|
|
306
|
-
function mech_chain_caller (err3, result) {
|
|
307
|
-
// Throw any errors
|
|
308
|
-
if (err3) throw err3;
|
|
309
|
-
// Check limits
|
|
310
|
-
if (self.count > self.LIMIT) {
|
|
311
|
-
self.log_debug('lookup limit reached');
|
|
312
|
-
return cb(null, self.SPF_PERMERROR);
|
|
313
|
-
}
|
|
314
|
-
// If we have a result other than SPF_NONE
|
|
315
|
-
if (result && result !== self.SPF_NONE) {
|
|
316
|
-
return cb(err3, result);
|
|
317
|
-
}
|
|
318
|
-
// Return default if no more mechanisms to run
|
|
319
|
-
if (!mech_array.length) {
|
|
320
|
-
// Now run any modifiers
|
|
321
|
-
if (mod_array.length) {
|
|
322
|
-
return mod_chain_caller();
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
return cb(null, self.SPF_NEUTRAL);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
const next_in_chain = mech_array.shift();
|
|
329
|
-
const func = Object.keys(next_in_chain);
|
|
330
|
-
const args = next_in_chain[func];
|
|
331
|
-
self.log_debug(`running mechanism: ${func} args=${args} domain=${self.domain}`);
|
|
332
|
-
self[`mech_${func}`](((args && args.length) ? args[0] : null), ((args && args.length) ? args[1] : null), mech_chain_caller);
|
|
333
|
-
}
|
|
334
|
-
// Start the chain
|
|
335
|
-
mech_chain_caller();
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
mech_all (qualifier, args, cb) {
|
|
340
|
-
return cb(null, this.return_const(qualifier));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
mech_include (qualifier, args, cb) {
|
|
344
|
-
const self = this;
|
|
345
|
-
const domain = args.substr(1);
|
|
346
|
-
// Avoid circular references
|
|
347
|
-
if (self.been_there[domain]) {
|
|
348
|
-
self.log_debug(`circular reference detected: ${domain}`);
|
|
349
|
-
return cb(null, self.SPF_NONE);
|
|
350
|
-
}
|
|
351
|
-
self.count++;
|
|
352
|
-
self.been_there[domain] = true;
|
|
353
|
-
// Recurse
|
|
354
|
-
const recurse = new SPF(self.count, self.been_there);
|
|
355
|
-
recurse.check_host(self.ip, domain, self.mail_from, (err, result) => {
|
|
356
|
-
if (!err) {
|
|
357
|
-
self.log_debug(`mech_include: domain=${domain} returned=${self.const_translate(result)}`);
|
|
358
|
-
switch (result) {
|
|
359
|
-
case self.SPF_PASS: return cb(null, self.SPF_PASS);
|
|
360
|
-
case self.SPF_FAIL:
|
|
361
|
-
case self.SPF_SOFTFAIL:
|
|
362
|
-
case self.SPF_NEUTRAL: return cb(null, self.SPF_NONE);
|
|
363
|
-
case self.SPF_TEMPERROR: return cb(null, self.SPF_TEMPERROR);
|
|
364
|
-
default: return cb(null, self.SPF_PERMERROR);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
mech_exists (qualifier, args, cb) {
|
|
371
|
-
const self = this;
|
|
372
|
-
self.count++;
|
|
373
|
-
const exists = args.substr(1);
|
|
374
|
-
dns.resolve(exists, (err, addrs) => {
|
|
375
|
-
if (err) {
|
|
376
|
-
self.log_debug(`mech_exists: ${err}`);
|
|
377
|
-
switch (err.code) {
|
|
378
|
-
case dns.NOTFOUND:
|
|
379
|
-
case dns.NODATA:
|
|
380
|
-
case dns.NXDOMAIN:
|
|
381
|
-
return cb(null, self.SPF_NONE);
|
|
382
|
-
default:
|
|
383
|
-
return cb(null, self.SPF_TEMPERROR);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
self.log_debug(`mech_exists: ${exists} result=${addrs.join(',')}`);
|
|
387
|
-
return cb(null, self.return_const(qualifier));
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
mech_a (qualifier, args, cb) {
|
|
392
|
-
const self = this;
|
|
393
|
-
self.count++;
|
|
394
|
-
// Parse any arguments
|
|
395
|
-
let cm;
|
|
396
|
-
let cidr4;
|
|
397
|
-
let cidr6;
|
|
398
|
-
if (args && (cm = /\/(\d+)(?:\/\/(\d+))?$/.exec(args))) {
|
|
399
|
-
cidr4 = cm[1];
|
|
400
|
-
cidr6 = cm[2];
|
|
401
|
-
}
|
|
402
|
-
let dm;
|
|
403
|
-
let domain = self.domain;
|
|
404
|
-
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
|
|
405
|
-
domain = dm[1];
|
|
406
|
-
}
|
|
407
|
-
// Calculate with IP method to use
|
|
408
|
-
let resolve_method;
|
|
409
|
-
let cidr;
|
|
410
|
-
if (self.ip_ver === 'ipv4') {
|
|
411
|
-
cidr = cidr4;
|
|
412
|
-
resolve_method = 'resolve4';
|
|
413
|
-
}
|
|
414
|
-
else if (self.ip_ver === 'ipv6') {
|
|
415
|
-
cidr = cidr6;
|
|
416
|
-
resolve_method = 'resolve6';
|
|
417
|
-
}
|
|
418
|
-
// Use current domain
|
|
419
|
-
dns[resolve_method](domain, (err, addrs) => {
|
|
420
|
-
if (err) {
|
|
421
|
-
self.log_debug(`mech_a: ${err}`);
|
|
422
|
-
switch (err.code) {
|
|
423
|
-
case dns.NOTFOUND:
|
|
424
|
-
case dns.NODATA:
|
|
425
|
-
case dns.NXDOMAIN: return cb(null, self.SPF_NONE);
|
|
426
|
-
default: return cb(null, self.SPF_TEMPERROR);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
for (let a=0; a<addrs.length; a++) {
|
|
430
|
-
if (cidr) {
|
|
431
|
-
// CIDR
|
|
432
|
-
const range = ipaddr.parse(addrs[a]);
|
|
433
|
-
if (self.ipaddr.match(range, cidr)) {
|
|
434
|
-
self.log_debug(`mech_a: ${self.ip} => ${addrs[a]}/${cidr}: MATCH!`);
|
|
435
|
-
return cb(null, self.return_const(qualifier));
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
self.log_debug(`mech_a: ${self.ip} => ${addrs[a]}/${cidr}: NO MATCH`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
if (addrs[a] === self.ip) {
|
|
443
|
-
return cb(null, self.return_const(qualifier));
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
self.log_debug(`mech_a: ${self.ip} => ${addrs[a]}: NO MATCH`);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return cb(null, self.SPF_NONE);
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
mech_mx (qualifier, args, cb) {
|
|
455
|
-
const self = this;
|
|
456
|
-
this.count++;
|
|
457
|
-
// Parse any arguments
|
|
458
|
-
let cm;
|
|
459
|
-
let cidr4;
|
|
460
|
-
let cidr6;
|
|
461
|
-
if (args && (cm = /\/(\d+)((?:\/\/(\d+))?)$/.exec(args))) {
|
|
462
|
-
cidr4 = cm[1];
|
|
463
|
-
cidr6 = cm[2];
|
|
464
|
-
}
|
|
465
|
-
let dm;
|
|
466
|
-
let domain = this.domain;
|
|
467
|
-
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
|
|
468
|
-
domain = dm[1];
|
|
469
|
-
}
|
|
470
|
-
// Fetch the MX records for the specified domain
|
|
471
|
-
net_utils.get_mx(domain, (err, mxes) => {
|
|
472
|
-
if (err) {
|
|
473
|
-
switch (err.code) {
|
|
474
|
-
case dns.NOTFOUND:
|
|
475
|
-
case dns.NODATA:
|
|
476
|
-
case dns.NXDOMAIN: return cb(null, self.SPF_NONE);
|
|
477
|
-
default: return cb(null, self.SPF_TEMPERROR);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
let pending = 0;
|
|
481
|
-
let addresses = [];
|
|
482
|
-
// RFC 4408 Section 10.1
|
|
483
|
-
if (mxes.length > self.LIMIT) {
|
|
484
|
-
return cb(null, self.SPF_PERMERROR);
|
|
485
|
-
}
|
|
486
|
-
for (let a=0; a<mxes.length; a++) {
|
|
487
|
-
pending++;
|
|
488
|
-
const mx = mxes[a].exchange;
|
|
489
|
-
// Calculate which IP method to use
|
|
490
|
-
let resolve_method;
|
|
491
|
-
let cidr;
|
|
492
|
-
if (self.ip_ver === 'ipv4') {
|
|
493
|
-
cidr = cidr4;
|
|
494
|
-
resolve_method = 'resolve4';
|
|
495
|
-
}
|
|
496
|
-
else if (self.ip_ver === 'ipv6') {
|
|
497
|
-
cidr = cidr6;
|
|
498
|
-
resolve_method = 'resolve6';
|
|
499
|
-
}
|
|
500
|
-
dns[resolve_method](mx, (err4, addrs) => {
|
|
501
|
-
pending--;
|
|
502
|
-
if (err4) {
|
|
503
|
-
switch (err4.code) {
|
|
504
|
-
case dns.NOTFOUND:
|
|
505
|
-
case dns.NODATA:
|
|
506
|
-
case dns.NXDOMAIN: break;
|
|
507
|
-
default: return cb(null, self.SPF_TEMPERROR);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
self.log_debug(`mech_mx: mx=${mx} addresses=${addrs.join(',')}`);
|
|
512
|
-
addresses = addrs.concat(addresses);
|
|
513
|
-
}
|
|
514
|
-
if (pending === 0) {
|
|
515
|
-
if (!addresses.length) return cb(null, self.SPF_NONE);
|
|
516
|
-
// All queries run; see if our IP matches
|
|
517
|
-
if (cidr) {
|
|
518
|
-
// CIDR match type
|
|
519
|
-
for (let i=0; i<addresses.length; i++) {
|
|
520
|
-
const range = ipaddr.parse(addresses[i]);
|
|
521
|
-
if (self.ipaddr.match(range, cidr)) {
|
|
522
|
-
self.log_debug(`mech_mx: ${self.ip} => ${addresses[i]}/${cidr}: MATCH!`);
|
|
523
|
-
return cb(null, self.return_const(qualifier));
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
self.log_debug(`mech_mx: ${self.ip} => ${addresses[i]}/${cidr}: NO MATCH`);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
// No matches
|
|
530
|
-
return cb(null, self.SPF_NONE);
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
if (addresses.includes(self.ip)) {
|
|
534
|
-
self.log_debug(`mech_mx: ${self.ip} => ${addresses.join(',')}: MATCH!`);
|
|
535
|
-
return cb(null, self.return_const(qualifier));
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
self.log_debug(`mech_mx: ${self.ip} => ${addresses.join(',')}: NO MATCH`);
|
|
539
|
-
return cb(null, self.SPF_NONE);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
// In case we didn't run any queries...
|
|
545
|
-
if (pending === 0) {
|
|
546
|
-
return cb(null, self.SPF_NONE);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
if (pending === 0) {
|
|
550
|
-
return cb(null, self.SPF_NONE);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
mech_ptr (qualifier, args, cb) {
|
|
556
|
-
const self = this;
|
|
557
|
-
this.count++;
|
|
558
|
-
let dm;
|
|
559
|
-
let domain = this.domain;
|
|
560
|
-
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
|
|
561
|
-
domain = dm[1];
|
|
562
|
-
}
|
|
563
|
-
// First do a PTR lookup for the connecting IP
|
|
564
|
-
dns.reverse(this.ip, (err, ptrs) => {
|
|
565
|
-
if (err) {
|
|
566
|
-
self.log_debug(`mech_ptr: lookup=${self.ip} => ${err}`);
|
|
567
|
-
return cb(null, self.SPF_NONE);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
let resolve_method;
|
|
571
|
-
if (self.ip_ver === 'ipv4') resolve_method = 'resolve4';
|
|
572
|
-
if (self.ip_ver === 'ipv6') resolve_method = 'resolve6';
|
|
573
|
-
let pending = 0;
|
|
574
|
-
const names = [];
|
|
575
|
-
// RFC 4408 Section 10.1
|
|
576
|
-
if (ptrs.length > self.LIMIT) {
|
|
577
|
-
return cb(null, self.SPF_PERMERROR);
|
|
578
|
-
}
|
|
579
|
-
for (let i=0; i<ptrs.length; i++) {
|
|
580
|
-
const ptr = ptrs[i];
|
|
581
|
-
pending++;
|
|
582
|
-
dns[resolve_method](ptr, (err3, addrs) => {
|
|
583
|
-
pending--;
|
|
584
|
-
if (err3) {
|
|
585
|
-
// Skip on error
|
|
586
|
-
self.log_debug(`mech_ptr: lookup=${ptr} => ${err3}`);
|
|
587
|
-
}
|
|
588
|
-
else {
|
|
589
|
-
for (let a=0; a<addrs.length; a++) {
|
|
590
|
-
if (addrs[a] === self.ip) {
|
|
591
|
-
self.log_debug(`mech_ptr: ${self.ip} => ${ptr} => ${addrs[a]}: MATCH!`);
|
|
592
|
-
names.push(ptr.toLowerCase());
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
self.log_debug(`mech_ptr: ${self.ip} => ${ptr} => ${addrs[a]}: NO MATCH`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
// Finished
|
|
600
|
-
if (pending === 0) {
|
|
601
|
-
let re;
|
|
602
|
-
// Catch bogus PTR matches e.g. ptr:*.bahnhof.se (should be ptr:bahnhof.se)
|
|
603
|
-
// These will cause a regexp error, so we can catch them.
|
|
604
|
-
try {
|
|
605
|
-
re = new RegExp(`${domain.replace('.','\\.')}$`, 'i');
|
|
606
|
-
}
|
|
607
|
-
catch (e) {
|
|
608
|
-
self.log_debug(
|
|
609
|
-
'mech_ptr',
|
|
610
|
-
{
|
|
611
|
-
domain: self.domain,
|
|
612
|
-
err: e.message
|
|
613
|
-
}
|
|
614
|
-
);
|
|
615
|
-
return cb(null, self.SPF_PERMERROR);
|
|
616
|
-
}
|
|
617
|
-
for (let t=0; t<names.length; t++) {
|
|
618
|
-
if (re.test(names[t])) {
|
|
619
|
-
self.log_debug(`mech_ptr: ${names[t]} => ${domain}: MATCH!`);
|
|
620
|
-
return cb(null, self.return_const(qualifier));
|
|
621
|
-
}
|
|
622
|
-
else {
|
|
623
|
-
self.log_debug(`mech_ptr: ${names[t]} => ${domain}: NO MATCH`);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return cb(null, self.SPF_NONE);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
if (pending === 0) {
|
|
631
|
-
// No queries run
|
|
632
|
-
return cb(null, self.SPF_NONE);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
mech_ip (qualifier, args, cb) {
|
|
639
|
-
const cidr = args.substr(1);
|
|
640
|
-
const match = /^([^/ ]+)(?:\/(\d+))?$/.exec(cidr);
|
|
641
|
-
if (!match) { return cb(null, this.SPF_NONE); }
|
|
642
|
-
|
|
643
|
-
// match[1] == ip
|
|
644
|
-
// match[2] == mask
|
|
645
|
-
try {
|
|
646
|
-
if (!match[2]) {
|
|
647
|
-
// Default masks for each IP version
|
|
648
|
-
if (this.ip_ver === 'ipv4') match[2] = '32';
|
|
649
|
-
if (this.ip_ver === 'ipv6') match[2] = '128';
|
|
650
|
-
}
|
|
651
|
-
const range = ipaddr.parse(match[1]);
|
|
652
|
-
const rtype = range.kind();
|
|
653
|
-
if (this.ip_ver !== rtype) {
|
|
654
|
-
this.log_debug(`mech_ip: ${this.ip} => ${cidr}: SKIP`);
|
|
655
|
-
return cb(null, this.SPF_NONE);
|
|
656
|
-
}
|
|
657
|
-
if (this.ipaddr.match(range, match[2])) {
|
|
658
|
-
this.log_debug(`mech_ip: ${this.ip} => ${cidr}: MATCH!`);
|
|
659
|
-
return cb(null, this.return_const(qualifier));
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
this.log_debug(`mech_ip: ${this.ip} => ${cidr}: NO MATCH`);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
catch (e) {
|
|
666
|
-
this.log_debug(e.message);
|
|
667
|
-
return cb(null, this.SPF_PERMERROR);
|
|
668
|
-
}
|
|
669
|
-
return cb(null, this.SPF_NONE);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
mod_redirect (domain, cb) {
|
|
673
|
-
// Avoid circular references
|
|
674
|
-
if (this.been_there[domain]) {
|
|
675
|
-
this.log_debug(`circular reference detected: ${domain}`);
|
|
676
|
-
return cb(null, this.SPF_NONE);
|
|
677
|
-
}
|
|
678
|
-
this.count++;
|
|
679
|
-
this.been_there[domain] = 1;
|
|
680
|
-
return this.check_host(this.ip, domain, this.mail_from, cb);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
mod_exp (str, cb) {
|
|
684
|
-
// NOT IMPLEMENTED
|
|
685
|
-
return cb(null, this.SPF_NONE);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
exports.SPF = SPF;
|