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/dkim.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const dns = require('dns');
|
|
5
|
-
const Stream = require('stream')
|
|
5
|
+
const { Stream } = require('stream');
|
|
6
6
|
const utils = require('haraka-utils');
|
|
7
7
|
|
|
8
8
|
//////////////////////
|
|
@@ -26,7 +26,7 @@ class Buf {
|
|
|
26
26
|
if (!buf) buf = Buffer.from('');
|
|
27
27
|
return buf;
|
|
28
28
|
}
|
|
29
|
-
if (buf
|
|
29
|
+
if (buf?.length) {
|
|
30
30
|
this.bar.push(buf);
|
|
31
31
|
this.blen += buf.length;
|
|
32
32
|
}
|
|
@@ -73,8 +73,8 @@ class DKIMObject {
|
|
|
73
73
|
const [ , , dkim_signature] = /^([^:]+):\s*((?:.|[\r\n])*)$/.exec(header);
|
|
74
74
|
const sig = dkim_signature.trim().replace(/\s+/g,'');
|
|
75
75
|
const keys = sig.split(';');
|
|
76
|
-
for (
|
|
77
|
-
const key =
|
|
76
|
+
for (const keyElement of keys) {
|
|
77
|
+
const key = keyElement.trim();
|
|
78
78
|
if (!key) continue; // skip empty keys
|
|
79
79
|
const [ , key_name, key_value] = /^([^= ]+)=((?:.|[\r\n])+)$/.exec(key) || [];
|
|
80
80
|
if (key_name) {
|
|
@@ -133,8 +133,8 @@ class DKIMObject {
|
|
|
133
133
|
|
|
134
134
|
if (this.fields.h) {
|
|
135
135
|
const headers = this.fields.h.split(':');
|
|
136
|
-
for (
|
|
137
|
-
this.signed_headers.push(
|
|
136
|
+
for (const h of headers) {
|
|
137
|
+
this.signed_headers.push(h.trim().toLowerCase());
|
|
138
138
|
}
|
|
139
139
|
if (!this.signed_headers.includes('from')) {
|
|
140
140
|
return this.result('from field not signed', 'invalid');
|
|
@@ -247,8 +247,7 @@ class DKIMObject {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Now we canonicalize the specified headers
|
|
250
|
-
for (
|
|
251
|
-
const header = this.signed_headers[h];
|
|
250
|
+
for (const header of this.signed_headers) {
|
|
252
251
|
this.debug(`${this.identity}: canonicalize header: ${header}`);
|
|
253
252
|
if (this.header_idx[header]) {
|
|
254
253
|
// RFC 6376 section 5.4.2, read headers from bottom to top
|
|
@@ -281,12 +280,10 @@ class DKIMObject {
|
|
|
281
280
|
our_sig = our_sig.replace(/\r\n$/,'');
|
|
282
281
|
this.verifier.update(our_sig);
|
|
283
282
|
|
|
284
|
-
// Do the DNS lookup to retrieve the public key
|
|
285
|
-
const self = this;
|
|
286
283
|
let timeout = false;
|
|
287
284
|
const timer = setTimeout(() => {
|
|
288
285
|
timeout = true;
|
|
289
|
-
return
|
|
286
|
+
return this.result('DNS timeout', 'tempfail');
|
|
290
287
|
}, this.timeout * 1000);
|
|
291
288
|
const lookup = `${this.fields.s}._domainkey.${this.fields.d}`;
|
|
292
289
|
this.debug(`${this.identity}: DNS lookup ${lookup} (timeout= ${this.timeout}s)`);
|
|
@@ -298,77 +295,73 @@ class DKIMObject {
|
|
|
298
295
|
case dns.NOTFOUND:
|
|
299
296
|
case dns.NODATA:
|
|
300
297
|
case dns.NXDOMAIN:
|
|
301
|
-
return
|
|
298
|
+
return this.result('no key for signature', 'invalid');
|
|
302
299
|
default:
|
|
303
|
-
|
|
304
|
-
return
|
|
300
|
+
this.debug(`${this.identity}: DNS lookup error: ${err.code}`);
|
|
301
|
+
return this.result('key unavailable', 'tempfail');
|
|
305
302
|
}
|
|
306
303
|
}
|
|
307
|
-
if (!res) return
|
|
308
|
-
for (
|
|
309
|
-
|
|
310
|
-
// Node 0.11.x compatibility
|
|
311
|
-
if (Array.isArray(record)) {
|
|
312
|
-
record = record.join('');
|
|
313
|
-
}
|
|
304
|
+
if (!res) return this.result('no key for signature', 'invalid');
|
|
305
|
+
for (const recordSegments of res) {
|
|
306
|
+
const record = recordSegments.join('');
|
|
314
307
|
if (!record.includes('p=')) {
|
|
315
|
-
|
|
308
|
+
this.debug(`${this.identity}: ignoring TXT record: ${record}`);
|
|
316
309
|
continue;
|
|
317
310
|
}
|
|
318
|
-
|
|
311
|
+
this.debug(`${this.identity}: got DNS record: ${record}`);
|
|
319
312
|
const rec = record.replace(/\r?\n/g, '').replace(/\s+/g,'');
|
|
320
313
|
const split = rec.split(';');
|
|
321
|
-
for (
|
|
322
|
-
const split2 =
|
|
323
|
-
if (split2[0])
|
|
314
|
+
for (const element of split) {
|
|
315
|
+
const split2 = element.split('=');
|
|
316
|
+
if (split2[0]) this.dns_fields[split2[0]] = split2[1];
|
|
324
317
|
}
|
|
325
318
|
|
|
326
319
|
// Validate
|
|
327
|
-
if (!
|
|
328
|
-
return
|
|
320
|
+
if (!this.dns_fields.v || this.dns_fields.v !== 'DKIM1') {
|
|
321
|
+
return this.result('invalid version', 'invalid');
|
|
329
322
|
}
|
|
330
|
-
if (
|
|
331
|
-
if (
|
|
332
|
-
let s =
|
|
323
|
+
if (this.dns_fields.g) {
|
|
324
|
+
if (this.dns_fields.g !== '*') {
|
|
325
|
+
let s = this.dns_fields.g;
|
|
333
326
|
// Escape any special regexp characters
|
|
334
327
|
s = s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
335
328
|
// Make * a non-greedy match against anything except @
|
|
336
329
|
s = s.replace('\\*','[^@]*?');
|
|
337
330
|
const reg = new RegExp(`^${s}@`);
|
|
338
|
-
|
|
339
|
-
if (!reg.test(
|
|
340
|
-
return
|
|
331
|
+
this.debug(`${this.identity}: matching ${this.dns_fields.g} against i=${this.fields.i} regexp=${reg.toString()}`);
|
|
332
|
+
if (!reg.test(this.fields.i)) {
|
|
333
|
+
return this.result('inapplicable key', 'invalid');
|
|
341
334
|
}
|
|
342
335
|
}
|
|
343
336
|
}
|
|
344
337
|
else {
|
|
345
|
-
return
|
|
338
|
+
return this.result('inapplicable key', 'invalid');
|
|
346
339
|
}
|
|
347
|
-
if (
|
|
348
|
-
const hashes =
|
|
349
|
-
for (
|
|
350
|
-
const hash =
|
|
351
|
-
if (!
|
|
352
|
-
return
|
|
340
|
+
if (this.dns_fields.h) {
|
|
341
|
+
const hashes = this.dns_fields.h.split(':');
|
|
342
|
+
for (const hashElement of hashes) {
|
|
343
|
+
const hash = hashElement.trim();
|
|
344
|
+
if (!this.fields.a.includes(hash)) {
|
|
345
|
+
return this.result('inappropriate hash algorithm', 'invalid');
|
|
353
346
|
}
|
|
354
347
|
}
|
|
355
348
|
}
|
|
356
|
-
if (
|
|
357
|
-
if (!
|
|
358
|
-
return
|
|
349
|
+
if (this.dns_fields.k) {
|
|
350
|
+
if (!this.fields.a.includes(this.dns_fields.k)) {
|
|
351
|
+
return this.result('inappropriate key type', 'invalid');
|
|
359
352
|
}
|
|
360
353
|
}
|
|
361
|
-
if (
|
|
362
|
-
const flags =
|
|
363
|
-
for (
|
|
364
|
-
const flag =
|
|
354
|
+
if (this.dns_fields.t) {
|
|
355
|
+
const flags = this.dns_fields.t.split(':');
|
|
356
|
+
for (const flagElement of flags) {
|
|
357
|
+
const flag = flagElement.trim();
|
|
365
358
|
if (flag === 'y') {
|
|
366
359
|
// Test mode
|
|
367
|
-
|
|
360
|
+
this.test_mode = true;
|
|
368
361
|
}
|
|
369
362
|
else if (flag === 's') {
|
|
370
363
|
// 'i' and 'd' domain much match exactly
|
|
371
|
-
let i =
|
|
364
|
+
let { i } = this.fields
|
|
372
365
|
i = i.substr(i.indexOf('@')+1, i.length)
|
|
373
366
|
if (i.toLowerCase() !== this.fields.d.toLowerCase()) {
|
|
374
367
|
return this.result('i/d selector domain mismatch (t=s)', 'invalid')
|
|
@@ -376,26 +369,27 @@ class DKIMObject {
|
|
|
376
369
|
}
|
|
377
370
|
}
|
|
378
371
|
}
|
|
379
|
-
if (!
|
|
372
|
+
if (!this.dns_fields.p) return this.result('key revoked', 'invalid');
|
|
380
373
|
|
|
381
374
|
// crypto.verifier requires the key in PEM format
|
|
382
|
-
|
|
383
|
-
|
|
375
|
+
this.public_key = `-----BEGIN PUBLIC KEY-----\r\n${
|
|
376
|
+
|
|
377
|
+
this.dns_fields.p.replace(/(.{1,76})/g, '$1\r\n')
|
|
384
378
|
}-----END PUBLIC KEY-----\r\n`;
|
|
385
379
|
|
|
386
380
|
let verified;
|
|
387
381
|
try {
|
|
388
|
-
verified =
|
|
389
|
-
|
|
382
|
+
verified = this.verifier.verify(this.public_key, this.fields.b, 'base64');
|
|
383
|
+
this.debug(`${this.identity}: verified=${verified}`);
|
|
390
384
|
}
|
|
391
385
|
catch (e) {
|
|
392
|
-
|
|
393
|
-
return
|
|
386
|
+
this.debug(`${this.identity}: verification error: ${e.message}`);
|
|
387
|
+
return this.result('verification error', 'invalid');
|
|
394
388
|
}
|
|
395
|
-
return
|
|
389
|
+
return this.result(null, ((verified) ? 'pass' : 'fail'));
|
|
396
390
|
}
|
|
397
391
|
// We didn't find a valid DKIM record for this signature
|
|
398
|
-
|
|
392
|
+
this.result('no key for signature', 'invalid');
|
|
399
393
|
});
|
|
400
394
|
}
|
|
401
395
|
|
|
@@ -508,9 +502,9 @@ class DKIMVerifyStream extends Stream {
|
|
|
508
502
|
|
|
509
503
|
// Set the overall result based on this precedence order
|
|
510
504
|
const rr = ['pass','tempfail','fail','invalid','none'];
|
|
511
|
-
for (
|
|
512
|
-
if (!self.result || (self.result && self.result !==
|
|
513
|
-
self.result =
|
|
505
|
+
for (const element of rr) {
|
|
506
|
+
if (!self.result || (self.result && self.result !== element && result.result === element)) {
|
|
507
|
+
self.result = element;
|
|
514
508
|
}
|
|
515
509
|
}
|
|
516
510
|
}
|
|
@@ -543,14 +537,14 @@ class DKIMVerifyStream extends Stream {
|
|
|
543
537
|
if (!this._in_body) {
|
|
544
538
|
this._in_body = true;
|
|
545
539
|
// Parse the headers
|
|
546
|
-
for (
|
|
547
|
-
const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(
|
|
540
|
+
for (const header of this.headers) {
|
|
541
|
+
const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(header);
|
|
548
542
|
if (!match) continue;
|
|
549
543
|
const header_name = match[1];
|
|
550
544
|
if (!header_name) continue;
|
|
551
545
|
const hn = header_name.toLowerCase();
|
|
552
546
|
if (!this.header_idx[hn]) this.header_idx[hn] = [];
|
|
553
|
-
this.header_idx[hn].push(
|
|
547
|
+
this.header_idx[hn].push(header);
|
|
554
548
|
}
|
|
555
549
|
if (!this.header_idx['dkim-signature']) {
|
|
556
550
|
this._no_signatures_found = true;
|
|
@@ -563,8 +557,8 @@ class DKIMVerifyStream extends Stream {
|
|
|
563
557
|
const dkim_headers = this.header_idx['dkim-signature'];
|
|
564
558
|
this.debug(`Found ${dkim_headers.length} DKIM signatures`);
|
|
565
559
|
this.pending = dkim_headers.length;
|
|
566
|
-
for (
|
|
567
|
-
this.dkim_objects.push(new DKIMObject(
|
|
560
|
+
for (const dkimHeader of dkim_headers) {
|
|
561
|
+
this.dkim_objects.push(new DKIMObject(dkimHeader, this.header_idx, callback, this.opts));
|
|
568
562
|
}
|
|
569
563
|
if (this.pending === 0) {
|
|
570
564
|
process.nextTick(() => {
|
|
@@ -587,8 +581,8 @@ class DKIMVerifyStream extends Stream {
|
|
|
587
581
|
}
|
|
588
582
|
}
|
|
589
583
|
else {
|
|
590
|
-
for (
|
|
591
|
-
|
|
584
|
+
for (const dkimObject of this.dkim_objects) {
|
|
585
|
+
dkimObject.add_body_line(line);
|
|
592
586
|
}
|
|
593
587
|
}
|
|
594
588
|
if (once) {
|
|
@@ -606,13 +600,12 @@ class DKIMVerifyStream extends Stream {
|
|
|
606
600
|
|
|
607
601
|
end (buf) {
|
|
608
602
|
this.handle_buf(((buf) ? buf : null));
|
|
609
|
-
for (
|
|
610
|
-
|
|
603
|
+
for (const dkimObject of this.dkim_objects) {
|
|
604
|
+
dkimObject.end();
|
|
611
605
|
}
|
|
612
606
|
if (this.pending === 0 && this._no_signatures_found === false) {
|
|
613
|
-
const self = this;
|
|
614
607
|
process.nextTick(() => {
|
|
615
|
-
|
|
608
|
+
this.cb(null, this.result, this.results);
|
|
616
609
|
});
|
|
617
610
|
}
|
|
618
611
|
}
|
package/docs/Body.md
CHANGED
|
@@ -1,22 +1 @@
|
|
|
1
|
-
Body
|
|
2
|
-
===========
|
|
3
|
-
|
|
4
|
-
The Body object gives you access to the textual body parts of an email.
|
|
5
|
-
|
|
6
|
-
API
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
* body.bodytext
|
|
10
|
-
|
|
11
|
-
A String containing the body text. Note that HTML parts will have tags in-tact.
|
|
12
|
-
|
|
13
|
-
* body.header
|
|
14
|
-
|
|
15
|
-
The header of this MIME part. See the `Header Object` for details of the API.
|
|
16
|
-
|
|
17
|
-
* body.children
|
|
18
|
-
|
|
19
|
-
Any child MIME parts. For example a multipart/alternative mail will have a
|
|
20
|
-
main body part with just the MIME preamble in (which is usually either empty,
|
|
21
|
-
or reads something like "This is a multipart MIME message"), and two
|
|
22
|
-
children, one text/plain and one text/html.
|
|
1
|
+
moved to [Body](https://github.com/haraka/email-message#body)
|
package/docs/CoreConfig.md
CHANGED
|
@@ -26,7 +26,7 @@ The list of plugins to load
|
|
|
26
26
|
* listen\_host, port - the host and port to listen on (default: ::0 and 25)
|
|
27
27
|
* listen - (default: [::0]:25) Comma separated IP:Port addresses to listen on
|
|
28
28
|
* inactivity\_time - how long to let clients idle in seconds (default: 300)
|
|
29
|
-
* nodes - specifies how many processes to fork. The string "cpus" will fork as many children as there are CPUs (default:
|
|
29
|
+
* nodes - specifies how many processes to fork. The string "cpus" will fork as many children as there are CPUs (default: 1, which enables cluster mode with a single process)
|
|
30
30
|
* user - optionally a user to drop privileges to. Can be a string or UID.
|
|
31
31
|
* group - optionally a group to drop privileges to. Can be a string or GID.
|
|
32
32
|
* ignore\_bad\_plugins - If a plugin fails to compile by default Haraka will stop at load time.
|
|
@@ -136,4 +136,4 @@ The list of plugins to load
|
|
|
136
136
|
|
|
137
137
|
* connection\_close\_message
|
|
138
138
|
|
|
139
|
-
Defaults to `closing connection. Have a jolly good day.` can be overrridden with custom text
|
|
139
|
+
Defaults to `closing connection. Have a jolly good day.` can be overrridden with custom text
|
package/docs/Header.md
CHANGED
|
@@ -1,47 +1 @@
|
|
|
1
|
-
Header
|
|
2
|
-
=============
|
|
3
|
-
|
|
4
|
-
The Header object gives programmatic access to email headers. It is primarily
|
|
5
|
-
used from `transaction.header` but also each MIME part of the `Body` will
|
|
6
|
-
also have its own header object.
|
|
7
|
-
|
|
8
|
-
API
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
* header.get(key)
|
|
12
|
-
|
|
13
|
-
Returns the header with the name `key`. If there are multiple headers with
|
|
14
|
-
the given name (as is usually the case with "Received" for example) they will
|
|
15
|
-
be concatenated together with "\n".
|
|
16
|
-
|
|
17
|
-
* header.get\_all(key)
|
|
18
|
-
|
|
19
|
-
Returns the headers with the name `key` as an array. Multi-valued headers
|
|
20
|
-
will have multiple entries in the array.
|
|
21
|
-
|
|
22
|
-
* header.get\_decoded(key)
|
|
23
|
-
|
|
24
|
-
Works like `get(key)`, only it gives you headers decoded from any MIME encoding
|
|
25
|
-
they may have used.
|
|
26
|
-
|
|
27
|
-
* header.remove(key)
|
|
28
|
-
|
|
29
|
-
Removes all headers with the given name. DO NOT USE. This is transparent to
|
|
30
|
-
the transaction and it will not see the header(s) you removed. Instead use
|
|
31
|
-
`transaction.remove_header(key)` which will also correct the data part of
|
|
32
|
-
the email.
|
|
33
|
-
|
|
34
|
-
* header.add(key, value)
|
|
35
|
-
|
|
36
|
-
Adds a header with the given name and value. DO NOT USE. This is transparent
|
|
37
|
-
to the transaction and it will not see the header you added. Instead use
|
|
38
|
-
`transaction.add_header(key, value)` which will add the header to the data
|
|
39
|
-
part of the email.
|
|
40
|
-
|
|
41
|
-
* header.lines()
|
|
42
|
-
|
|
43
|
-
Returns the entire header as a list of lines.
|
|
44
|
-
|
|
45
|
-
* header.toString()
|
|
46
|
-
|
|
47
|
-
Returns the entire header as a string.
|
|
1
|
+
moved to [Header](https://github.com/haraka/email-message#header)
|
package/docs/Outbound.md
CHANGED
|
@@ -40,12 +40,9 @@ Default: true. Switch to false to disable TLS for outbound mail.
|
|
|
40
40
|
|
|
41
41
|
This uses the same `tls_key.pem` and `tls_cert.pem` files that the `tls`
|
|
42
42
|
plugin uses, along with other values in `tls.ini`. See the [tls plugin
|
|
43
|
-
docs](http://haraka.github.io/manual/plugins/tls.html) for information on generating those
|
|
44
|
-
files.
|
|
43
|
+
docs](http://haraka.github.io/manual/plugins/tls.html) for information on generating those files.
|
|
45
44
|
|
|
46
|
-
Within `tls.ini` you can specify global options for the values `ciphers`, `minVersion`,
|
|
47
|
-
`requestCert` and `rejectUnauthorized`, alternatively you can provide
|
|
48
|
-
separate values by putting them under a key: `[outbound]`, such as:
|
|
45
|
+
Within `tls.ini` you can specify global options for the values `ciphers`, `minVersion`, `requestCert` and `rejectUnauthorized`, alternatively you can provide separate values by putting them under a key: `[outbound]`, such as:
|
|
49
46
|
|
|
50
47
|
```
|
|
51
48
|
[outbound]
|
|
@@ -74,27 +71,6 @@ string is attached as a `Received` header to all outbound mail just before it is
|
|
|
74
71
|
|
|
75
72
|
Timeout for connecting to remote servers. Default: 30s
|
|
76
73
|
|
|
77
|
-
* `pool_timeout`
|
|
78
|
-
|
|
79
|
-
Outbound mail uses "pooled" connections. An unused pool connection will send
|
|
80
|
-
a QUIT after this time. Default: 50s
|
|
81
|
-
|
|
82
|
-
Pooled connections means that a mail to a particular IP address will hold that
|
|
83
|
-
connection open and use it the next time it is requested. This helps with
|
|
84
|
-
large scale outbound mail. If you don't send lots of mail it is advised to
|
|
85
|
-
lower the `pool_timeout` value since it may upset receiving mail servers.
|
|
86
|
-
|
|
87
|
-
Setting this value to `0` will effectively disable the use of pools. You may
|
|
88
|
-
wish to set this if you have a `get_mx` hook that picks outbound servers on
|
|
89
|
-
a per-email basis (rather than per-domain).
|
|
90
|
-
|
|
91
|
-
* `pool_concurrency_max`
|
|
92
|
-
|
|
93
|
-
Set this to `0` to completely disable the pooling code.
|
|
94
|
-
|
|
95
|
-
This value determines how many concurrent connections can be made to a single
|
|
96
|
-
IP address (destination) in the pool. Default: 10 connections.
|
|
97
|
-
|
|
98
74
|
* `local_mx_ok`
|
|
99
75
|
|
|
100
76
|
Default: false. By default, outbound to a local IP is disabled, to avoid creating
|
|
@@ -105,13 +81,9 @@ This could be useful if you want to deliver mail to localhost on another port.
|
|
|
105
81
|
|
|
106
82
|
Set this to specify the delay intervals to use between trying to re-send an email
|
|
107
83
|
that has a temporary failure condition. The setting is a comma separated list of
|
|
108
|
-
time spans and multipliers. The time span is a number followed by `s`, `m`, `h`, or `d`
|
|
109
|
-
to represent seconds, minutes, hours, and days, respectively. The multiplier is an
|
|
110
|
-
asterisk followed by an integer representing the number of times to repeat the interval.
|
|
84
|
+
time spans and multipliers. The time span is a number followed by `s`, `m`, `h`, or `d` to represent seconds, minutes, hours, and days, respectively. The multiplier is an asterisk followed by an integer representing the number of times to repeat the interval.
|
|
111
85
|
For example, the entry `1m, 5m*2, 1h*3` results in an array of delay times of
|
|
112
|
-
`[60,300,300,3600,3600,3600]` in seconds. The email will be bounced when the array
|
|
113
|
-
runs out of intervals (the 7th failure in this case). Set this to `none` to bounce the
|
|
114
|
-
email on the first temporary failure.
|
|
86
|
+
`[60,300,300,3600,3600,3600]` in seconds. The email will be bounced when the array runs out of intervals (the 7th failure in this case). Set this to `none` to bounce the email on the first temporary failure.
|
|
115
87
|
|
|
116
88
|
### outbound.bounce\_message
|
|
117
89
|
|
|
@@ -150,6 +122,8 @@ you may be interested in are:
|
|
|
150
122
|
** outbound_helo - the EHLO domain to use (again, do not set manually)
|
|
151
123
|
* queue_time - the epoch milliseconds time when this mail was queued
|
|
152
124
|
* uuid - the original transaction.uuid
|
|
125
|
+
* force_tls - if true, this mail will be sent over TLS or defer
|
|
126
|
+
|
|
153
127
|
|
|
154
128
|
Outbound Mail Hooks
|
|
155
129
|
-------------------
|
|
@@ -268,7 +242,7 @@ interface (or alias) on the local system.
|
|
|
268
242
|
|
|
269
243
|
As described above the outbound IP can be set using the `bind` parameter
|
|
270
244
|
and also the outbound helo for the IP can be set using the `bind_ehlo`
|
|
271
|
-
parameter returned
|
|
245
|
+
parameter returned by the `get_mx` hook or during the reception of the message
|
|
272
246
|
you can set a transaction note in a plugin to tell Haraka which outbound IP
|
|
273
247
|
address you would like it to use when it tries to deliver the message:
|
|
274
248
|
|
|
@@ -301,9 +275,7 @@ The contents of the bounce message are configured by a file called
|
|
|
301
275
|
contains several template entries wrapped in curly brackets. These will be
|
|
302
276
|
populated as follows:
|
|
303
277
|
|
|
304
|
-
Optional: Possibility to add HTML code (with optional image) to the bounce message is possible
|
|
305
|
-
by adding the files `config/outbound.bounce_message_html`. An image can be attached
|
|
306
|
-
to the mail by using `config/outbound.bounce_message_image`.
|
|
278
|
+
Optional: Possibility to add HTML code (with optional image) to the bounce message is possible by adding the files `config/outbound.bounce_message_html`. An image can be attached to the mail by using `config/outbound.bounce_message_image`.
|
|
307
279
|
|
|
308
280
|
* pid - the current process id
|
|
309
281
|
* date - the current date when the bounce occurred
|
package/endpoint.js
CHANGED
|
@@ -41,7 +41,7 @@ class Endpoint {
|
|
|
41
41
|
toString () {
|
|
42
42
|
if (this.mode) return `${this.path}:${this.mode}`;
|
|
43
43
|
if (this.path) return this.path;
|
|
44
|
-
if (this.host.
|
|
44
|
+
if (this.host.includes(':')) return `[${this.host}]:${this.port}`;
|
|
45
45
|
return `${this.host}:${this.port}`;
|
|
46
46
|
}
|
|
47
47
|
|
package/haraka.js
CHANGED
package/host_pool.js
CHANGED
|
@@ -28,7 +28,6 @@ class HostPool {
|
|
|
28
28
|
// takes a comma/space-separated list of ip:ports
|
|
29
29
|
// 1.1.1.1:22, 3.3.3.3:44
|
|
30
30
|
constructor (hostports_str, retry_secs) {
|
|
31
|
-
const self = this;
|
|
32
31
|
const hosts = (hostports_str || '')
|
|
33
32
|
.trim()
|
|
34
33
|
.split(/[\s,]+/)
|
|
@@ -42,11 +41,11 @@ class HostPool {
|
|
|
42
41
|
port: splithost[1]
|
|
43
42
|
};
|
|
44
43
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
this.hostports_str = hostports_str;
|
|
45
|
+
this.hosts = utils.shuffle(hosts);
|
|
46
|
+
this.dead_hosts = {}; // hostport => true/false
|
|
47
|
+
this.last_i = 0; // the last one we checked
|
|
48
|
+
this.retry_secs = retry_secs || 10;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
/* failed
|
|
@@ -91,14 +90,12 @@ class HostPool {
|
|
|
91
90
|
probe_dead_host (
|
|
92
91
|
host, port, cb_if_still_dead, cb_if_alive
|
|
93
92
|
){
|
|
94
|
-
|
|
95
|
-
const self = this;
|
|
96
93
|
logger.loginfo(`probing dead host ${host}:${port}`);
|
|
97
94
|
|
|
98
95
|
const connect_timeout_ms = 200; // keep it snappy
|
|
99
96
|
let s;
|
|
100
97
|
try {
|
|
101
|
-
s =
|
|
98
|
+
s = this.get_socket();
|
|
102
99
|
s.setTimeout(connect_timeout_ms, () => {
|
|
103
100
|
// nobody home, it's still dead
|
|
104
101
|
s.destroy();
|
|
@@ -127,8 +124,7 @@ class HostPool {
|
|
|
127
124
|
* so we can override in unit test
|
|
128
125
|
*/
|
|
129
126
|
get_socket () {
|
|
130
|
-
|
|
131
|
-
return s;
|
|
127
|
+
return new net.Socket();
|
|
132
128
|
}
|
|
133
129
|
|
|
134
130
|
/* get_host
|
|
@@ -151,7 +147,7 @@ class HostPool {
|
|
|
151
147
|
for (let i = 0; i < this.hosts.length; ++i){
|
|
152
148
|
let j = i + first_i;
|
|
153
149
|
if (j >= this.hosts.length) {
|
|
154
|
-
j
|
|
150
|
+
j -= this.hosts.length;
|
|
155
151
|
}
|
|
156
152
|
host = this.hosts[j];
|
|
157
153
|
const key = `${host.host}:${host.port}`;
|