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