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.
Files changed (135) hide show
  1. package/.eslintrc.yaml +2 -10
  2. package/Changes.md +84 -2
  3. package/Dockerfile +1 -1
  4. package/Plugins.md +9 -4
  5. package/README.md +2 -6
  6. package/bin/haraka +5 -4
  7. package/config/outbound.ini +0 -7
  8. package/config/plugins +1 -1
  9. package/config/smtp.ini +1 -1
  10. package/config/smtp_forward.ini +2 -8
  11. package/config/smtp_proxy.ini +0 -6
  12. package/connection.js +178 -204
  13. package/coverage/lcov.info +13863 -0
  14. package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
  15. package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
  16. package/dkim.js +66 -73
  17. package/docs/Body.md +1 -22
  18. package/docs/CoreConfig.md +2 -2
  19. package/docs/Header.md +1 -47
  20. package/docs/Outbound.md +8 -36
  21. package/endpoint.js +1 -1
  22. package/haraka.js +1 -1
  23. package/host_pool.js +8 -12
  24. package/logger.js +25 -32
  25. package/outbound/client_pool.js +11 -153
  26. package/outbound/config.js +5 -11
  27. package/outbound/hmail.js +109 -143
  28. package/outbound/index.js +13 -25
  29. package/outbound/mx_lookup.js +10 -7
  30. package/outbound/queue.js +8 -12
  31. package/outbound/timer_queue.js +2 -4
  32. package/outbound/tls.js +17 -18
  33. package/outbound/todo.js +1 -0
  34. package/package.json +57 -55
  35. package/plugins/auth/auth_base.js +39 -63
  36. package/plugins/auth/auth_bridge.js +3 -4
  37. package/plugins/auth/auth_proxy.js +16 -16
  38. package/plugins/auth/auth_vpopmaild.js +30 -37
  39. package/plugins/auth/flat_file.js +9 -13
  40. package/plugins/avg.js +9 -11
  41. package/plugins/backscatterer.js +1 -1
  42. package/plugins/block_me.js +2 -6
  43. package/plugins/bounce.js +106 -124
  44. package/plugins/clamd.js +59 -63
  45. package/plugins/data.signatures.js +6 -6
  46. package/plugins/data.uribl.js +1 -415
  47. package/plugins/delay_deny.js +19 -20
  48. package/plugins/dkim_sign.js +56 -62
  49. package/plugins/dkim_verify.js +9 -8
  50. package/plugins/dns_list_base.js +43 -42
  51. package/plugins/dnsbl.js +41 -46
  52. package/plugins/dnswl.js +23 -26
  53. package/plugins/early_talker.js +24 -28
  54. package/plugins/esets.js +8 -11
  55. package/plugins/greylist.js +161 -190
  56. package/plugins/helo.checks.js +175 -197
  57. package/plugins/mail_from.is_resolvable.js +38 -38
  58. package/plugins/messagesniffer.js +33 -40
  59. package/plugins/prevent_credential_leaks.js +7 -5
  60. package/plugins/process_title.js +16 -17
  61. package/plugins/queue/deliver.js +2 -2
  62. package/plugins/queue/lmtp.js +5 -6
  63. package/plugins/queue/qmail-queue.js +11 -13
  64. package/plugins/queue/quarantine.js +25 -34
  65. package/plugins/queue/rabbitmq.js +3 -2
  66. package/plugins/queue/rabbitmq_amqplib.js +9 -9
  67. package/plugins/queue/smtp_bridge.js +5 -4
  68. package/plugins/queue/smtp_forward.js +81 -89
  69. package/plugins/queue/smtp_proxy.js +21 -22
  70. package/plugins/queue/test.js +2 -1
  71. package/plugins/rcpt_to.host_list_base.js +20 -30
  72. package/plugins/rcpt_to.in_host_list.js +12 -14
  73. package/plugins/rcpt_to.max_count.js +7 -5
  74. package/plugins/record_envelope_addresses.js +4 -6
  75. package/plugins/relay.js +64 -74
  76. package/plugins/reseed_rng.js +1 -2
  77. package/plugins/spamassassin.js +56 -68
  78. package/plugins/status.js +2 -3
  79. package/plugins/tarpit.js +8 -11
  80. package/plugins/tls.js +14 -17
  81. package/plugins/toobusy.js +6 -8
  82. package/plugins/xclient.js +14 -25
  83. package/plugins.js +24 -29
  84. package/rfc1869.js +2 -2
  85. package/server.js +3 -13
  86. package/smtp_client.js +138 -215
  87. package/tests/config/smtp_forward.ini +0 -6
  88. package/tests/fixtures/line_socket.js +1 -1
  89. package/tests/fixtures/util_hmailitem.js +5 -7
  90. package/tests/fixtures/vm_harness.js +2 -2
  91. package/tests/host_pool.js +13 -14
  92. package/tests/installation/plugins/inherits.js +1 -2
  93. package/tests/logger.js +2 -2
  94. package/tests/plugins/bounce.js +6 -8
  95. package/tests/plugins/dkim_signer.js +7 -7
  96. package/tests/plugins/dns_list_base.js +7 -7
  97. package/tests/plugins/helo.checks.js +1 -1
  98. package/tests/plugins/mail_from.is_resolvable.js +10 -54
  99. package/tests/plugins/queue/smtp_forward.js +11 -11
  100. package/tests/plugins/rcpt_to.host_list_base.js +1 -1
  101. package/tests/plugins/rcpt_to.in_host_list.js +1 -1
  102. package/tests/plugins/spamassassin.js +1 -1
  103. package/tests/queue/multibyte +0 -0
  104. package/tests/queue/plain +0 -0
  105. package/tests/rfc1869.js +4 -1
  106. package/tests/server.js +15 -9
  107. package/tests/smtp_client/auth.js +4 -14
  108. package/tests/smtp_client/basic.js +5 -15
  109. package/tests/smtp_client.js +7 -3
  110. package/tests/transaction.js +72 -19
  111. package/tls_socket.js +75 -85
  112. package/transaction.js +7 -9
  113. package/attachment_stream.js +0 -118
  114. package/bin/spf +0 -48
  115. package/chunkemitter.js +0 -75
  116. package/config/data.uribl.excludes +0 -202
  117. package/config/data.uribl.ini +0 -37
  118. package/config/spf.ini +0 -1
  119. package/docs/plugins/attachment.md +0 -92
  120. package/docs/plugins/data.uribl.md +0 -120
  121. package/docs/plugins/spf.md +0 -142
  122. package/mailbody.js +0 -502
  123. package/mailheader.js +0 -304
  124. package/messagestream.js +0 -441
  125. package/plugins/aliases.js +0 -120
  126. package/plugins/attachment.js +0 -503
  127. package/plugins/connect.p0f.js +0 -5
  128. package/plugins/spf.js +0 -327
  129. package/spf.js +0 -689
  130. package/tests/mailbody.js +0 -348
  131. package/tests/mailheader.js +0 -138
  132. package/tests/messagestream.js +0 -34
  133. package/tests/plugins/aliases.js +0 -376
  134. package/tests/plugins/spf.js +0 -251
  135. 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;