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.
- package/.eslintrc.yaml +2 -10
- package/Changes.md +68 -2
- package/Dockerfile +1 -1
- package/Plugins.md +7 -4
- package/README.md +2 -6
- 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 +65 -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 +42 -40
- 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,72 @@ 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
|
-
let record = res[r];
|
|
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 record of res) {
|
|
314
306
|
if (!record.includes('p=')) {
|
|
315
|
-
|
|
307
|
+
this.debug(`${this.identity}: ignoring TXT record: ${record}`);
|
|
316
308
|
continue;
|
|
317
309
|
}
|
|
318
|
-
|
|
310
|
+
this.debug(`${this.identity}: got DNS record: ${record}`);
|
|
319
311
|
const rec = record.replace(/\r?\n/g, '').replace(/\s+/g,'');
|
|
320
312
|
const split = rec.split(';');
|
|
321
|
-
for (
|
|
322
|
-
const split2 =
|
|
323
|
-
if (split2[0])
|
|
313
|
+
for (const element of split) {
|
|
314
|
+
const split2 = element.split('=');
|
|
315
|
+
if (split2[0]) this.dns_fields[split2[0]] = split2[1];
|
|
324
316
|
}
|
|
325
317
|
|
|
326
318
|
// Validate
|
|
327
|
-
if (!
|
|
328
|
-
return
|
|
319
|
+
if (!this.dns_fields.v || this.dns_fields.v !== 'DKIM1') {
|
|
320
|
+
return this.result('invalid version', 'invalid');
|
|
329
321
|
}
|
|
330
|
-
if (
|
|
331
|
-
if (
|
|
332
|
-
let s =
|
|
322
|
+
if (this.dns_fields.g) {
|
|
323
|
+
if (this.dns_fields.g !== '*') {
|
|
324
|
+
let s = this.dns_fields.g;
|
|
333
325
|
// Escape any special regexp characters
|
|
334
326
|
s = s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
335
327
|
// Make * a non-greedy match against anything except @
|
|
336
328
|
s = s.replace('\\*','[^@]*?');
|
|
337
329
|
const reg = new RegExp(`^${s}@`);
|
|
338
|
-
|
|
339
|
-
if (!reg.test(
|
|
340
|
-
return
|
|
330
|
+
this.debug(`${this.identity}: matching ${this.dns_fields.g} against i=${this.fields.i} regexp=${reg.toString()}`);
|
|
331
|
+
if (!reg.test(this.fields.i)) {
|
|
332
|
+
return this.result('inapplicable key', 'invalid');
|
|
341
333
|
}
|
|
342
334
|
}
|
|
343
335
|
}
|
|
344
336
|
else {
|
|
345
|
-
return
|
|
337
|
+
return this.result('inapplicable key', 'invalid');
|
|
346
338
|
}
|
|
347
|
-
if (
|
|
348
|
-
const hashes =
|
|
349
|
-
for (
|
|
350
|
-
const hash =
|
|
351
|
-
if (!
|
|
352
|
-
return
|
|
339
|
+
if (this.dns_fields.h) {
|
|
340
|
+
const hashes = this.dns_fields.h.split(':');
|
|
341
|
+
for (const hashElement of hashes) {
|
|
342
|
+
const hash = hashElement.trim();
|
|
343
|
+
if (!this.fields.a.includes(hash)) {
|
|
344
|
+
return this.result('inappropriate hash algorithm', 'invalid');
|
|
353
345
|
}
|
|
354
346
|
}
|
|
355
347
|
}
|
|
356
|
-
if (
|
|
357
|
-
if (!
|
|
358
|
-
return
|
|
348
|
+
if (this.dns_fields.k) {
|
|
349
|
+
if (!this.fields.a.includes(this.dns_fields.k)) {
|
|
350
|
+
return this.result('inappropriate key type', 'invalid');
|
|
359
351
|
}
|
|
360
352
|
}
|
|
361
|
-
if (
|
|
362
|
-
const flags =
|
|
363
|
-
for (
|
|
364
|
-
const flag =
|
|
353
|
+
if (this.dns_fields.t) {
|
|
354
|
+
const flags = this.dns_fields.t.split(':');
|
|
355
|
+
for (const flagElement of flags) {
|
|
356
|
+
const flag = flagElement.trim();
|
|
365
357
|
if (flag === 'y') {
|
|
366
358
|
// Test mode
|
|
367
|
-
|
|
359
|
+
this.test_mode = true;
|
|
368
360
|
}
|
|
369
361
|
else if (flag === 's') {
|
|
370
362
|
// 'i' and 'd' domain much match exactly
|
|
371
|
-
let i =
|
|
363
|
+
let { i } = this.fields
|
|
372
364
|
i = i.substr(i.indexOf('@')+1, i.length)
|
|
373
365
|
if (i.toLowerCase() !== this.fields.d.toLowerCase()) {
|
|
374
366
|
return this.result('i/d selector domain mismatch (t=s)', 'invalid')
|
|
@@ -376,26 +368,27 @@ class DKIMObject {
|
|
|
376
368
|
}
|
|
377
369
|
}
|
|
378
370
|
}
|
|
379
|
-
if (!
|
|
371
|
+
if (!this.dns_fields.p) return this.result('key revoked', 'invalid');
|
|
380
372
|
|
|
381
373
|
// crypto.verifier requires the key in PEM format
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
this.public_key = `-----BEGIN PUBLIC KEY-----\r\n${
|
|
375
|
+
|
|
376
|
+
this.dns_fields.p.replace(/(.{1,76})/g, '$1\r\n')
|
|
384
377
|
}-----END PUBLIC KEY-----\r\n`;
|
|
385
378
|
|
|
386
379
|
let verified;
|
|
387
380
|
try {
|
|
388
|
-
verified =
|
|
389
|
-
|
|
381
|
+
verified = this.verifier.verify(this.public_key, this.fields.b, 'base64');
|
|
382
|
+
this.debug(`${this.identity}: verified=${verified}`);
|
|
390
383
|
}
|
|
391
384
|
catch (e) {
|
|
392
|
-
|
|
393
|
-
return
|
|
385
|
+
this.debug(`${this.identity}: verification error: ${e.message}`);
|
|
386
|
+
return this.result('verification error', 'invalid');
|
|
394
387
|
}
|
|
395
|
-
return
|
|
388
|
+
return this.result(null, ((verified) ? 'pass' : 'fail'));
|
|
396
389
|
}
|
|
397
390
|
// We didn't find a valid DKIM record for this signature
|
|
398
|
-
|
|
391
|
+
this.result('no key for signature', 'invalid');
|
|
399
392
|
});
|
|
400
393
|
}
|
|
401
394
|
|
|
@@ -508,9 +501,9 @@ class DKIMVerifyStream extends Stream {
|
|
|
508
501
|
|
|
509
502
|
// Set the overall result based on this precedence order
|
|
510
503
|
const rr = ['pass','tempfail','fail','invalid','none'];
|
|
511
|
-
for (
|
|
512
|
-
if (!self.result || (self.result && self.result !==
|
|
513
|
-
self.result =
|
|
504
|
+
for (const element of rr) {
|
|
505
|
+
if (!self.result || (self.result && self.result !== element && result.result === element)) {
|
|
506
|
+
self.result = element;
|
|
514
507
|
}
|
|
515
508
|
}
|
|
516
509
|
}
|
|
@@ -543,14 +536,14 @@ class DKIMVerifyStream extends Stream {
|
|
|
543
536
|
if (!this._in_body) {
|
|
544
537
|
this._in_body = true;
|
|
545
538
|
// Parse the headers
|
|
546
|
-
for (
|
|
547
|
-
const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(
|
|
539
|
+
for (const header of this.headers) {
|
|
540
|
+
const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(header);
|
|
548
541
|
if (!match) continue;
|
|
549
542
|
const header_name = match[1];
|
|
550
543
|
if (!header_name) continue;
|
|
551
544
|
const hn = header_name.toLowerCase();
|
|
552
545
|
if (!this.header_idx[hn]) this.header_idx[hn] = [];
|
|
553
|
-
this.header_idx[hn].push(
|
|
546
|
+
this.header_idx[hn].push(header);
|
|
554
547
|
}
|
|
555
548
|
if (!this.header_idx['dkim-signature']) {
|
|
556
549
|
this._no_signatures_found = true;
|
|
@@ -563,8 +556,8 @@ class DKIMVerifyStream extends Stream {
|
|
|
563
556
|
const dkim_headers = this.header_idx['dkim-signature'];
|
|
564
557
|
this.debug(`Found ${dkim_headers.length} DKIM signatures`);
|
|
565
558
|
this.pending = dkim_headers.length;
|
|
566
|
-
for (
|
|
567
|
-
this.dkim_objects.push(new DKIMObject(
|
|
559
|
+
for (const dkimHeader of dkim_headers) {
|
|
560
|
+
this.dkim_objects.push(new DKIMObject(dkimHeader, this.header_idx, callback, this.opts));
|
|
568
561
|
}
|
|
569
562
|
if (this.pending === 0) {
|
|
570
563
|
process.nextTick(() => {
|
|
@@ -587,8 +580,8 @@ class DKIMVerifyStream extends Stream {
|
|
|
587
580
|
}
|
|
588
581
|
}
|
|
589
582
|
else {
|
|
590
|
-
for (
|
|
591
|
-
|
|
583
|
+
for (const dkimObject of this.dkim_objects) {
|
|
584
|
+
dkimObject.add_body_line(line);
|
|
592
585
|
}
|
|
593
586
|
}
|
|
594
587
|
if (once) {
|
|
@@ -606,13 +599,12 @@ class DKIMVerifyStream extends Stream {
|
|
|
606
599
|
|
|
607
600
|
end (buf) {
|
|
608
601
|
this.handle_buf(((buf) ? buf : null));
|
|
609
|
-
for (
|
|
610
|
-
|
|
602
|
+
for (const dkimObject of this.dkim_objects) {
|
|
603
|
+
dkimObject.end();
|
|
611
604
|
}
|
|
612
605
|
if (this.pending === 0 && this._no_signatures_found === false) {
|
|
613
|
-
const self = this;
|
|
614
606
|
process.nextTick(() => {
|
|
615
|
-
|
|
607
|
+
this.cb(null, this.result, this.results);
|
|
616
608
|
});
|
|
617
609
|
}
|
|
618
610
|
}
|
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}`;
|