Haraka 3.0.0 → 3.0.2
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/Changes.md +41 -0
- package/Plugins.md +2 -0
- package/bin/haraka +5 -4
- package/config/outbound.ini +1 -1
- package/connection.js +32 -10
- package/dkim.js +2 -1
- package/docs/plugins/clamd.md +1 -1
- package/docs/plugins/queue/smtp_forward.md +15 -37
- package/docs/plugins/queue/smtp_proxy.md +9 -11
- package/outbound/tls.js +1 -1
- package/package.json +52 -52
- package/plugins/dns_list_base.js +3 -3
- package/plugins/helo.checks.js +14 -6
- package/plugins/queue/smtp_forward.js +22 -16
- package/plugins/tls.js +1 -1
- package/smtp_client.js +1 -1
- package/tests/config/helo.checks.ini +52 -0
- package/tests/plugins/dns_list_base.js +41 -31
- package/tests/plugins/helo.checks.js +212 -239
- package/tests/plugins/queue/smtp_forward.js +36 -7
- package/coverage/lcov.info +0 -13863
- package/coverage/tmp/coverage-42958-1658373250585-0.json +0 -1
- package/coverage/tmp/coverage-42961-1658373250529-0.json +0 -1
|
@@ -26,10 +26,12 @@ exports.register = function () {
|
|
|
26
26
|
this.register_hook('queue', 'queue_forward');
|
|
27
27
|
|
|
28
28
|
if (this.cfg.main.enable_outbound) {
|
|
29
|
+
// deliver local message via smtp forward when relaying=true
|
|
29
30
|
this.register_hook('queue_outbound', 'queue_forward');
|
|
30
|
-
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// may specify more specific routes for outbound
|
|
33
|
+
this.register_hook('get_mx', 'get_mx');
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
exports.load_smtp_forward_ini = function () {
|
|
@@ -75,7 +77,7 @@ exports.is_outbound_enabled = function (dom_cfg) {
|
|
|
75
77
|
if ('enable_outbound' in dom_cfg) return dom_cfg.enable_outbound; // per-domain flag
|
|
76
78
|
|
|
77
79
|
return this.cfg.main.enable_outbound; // follow the global configuration
|
|
78
|
-
}
|
|
80
|
+
}
|
|
79
81
|
|
|
80
82
|
exports.check_sender = function (next, connection, params) {
|
|
81
83
|
const txn = connection?.transaction;
|
|
@@ -99,7 +101,7 @@ exports.check_sender = function (next, connection, params) {
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
txn.results.add(this, {pass: 'mail_from'});
|
|
102
|
-
|
|
104
|
+
next();
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
exports.set_queue = function (connection, queue_wanted, domain) {
|
|
@@ -110,7 +112,6 @@ exports.set_queue = function (connection, queue_wanted, domain) {
|
|
|
110
112
|
if (!queue_wanted) queue_wanted = dom_cfg.queue || this.cfg.main.queue;
|
|
111
113
|
if (!queue_wanted) return true;
|
|
112
114
|
|
|
113
|
-
|
|
114
115
|
let dst_host = dom_cfg.host || this.cfg.main.host;
|
|
115
116
|
if (dst_host) dst_host = `smtp://${dst_host}`;
|
|
116
117
|
|
|
@@ -164,7 +165,7 @@ exports.check_recipient = function (next, connection, params) {
|
|
|
164
165
|
// the MAIL FROM domain is not local and neither is the RCPT TO
|
|
165
166
|
// Another RCPT plugin may vouch for this recipient.
|
|
166
167
|
txn.results.add(this, {msg: 'rcpt!local'});
|
|
167
|
-
|
|
168
|
+
next();
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
exports.auth = function (cfg, connection, smtp_client) {
|
|
@@ -303,20 +304,24 @@ exports.queue_forward = function (next, connection) {
|
|
|
303
304
|
|
|
304
305
|
smtp_client.on('bad_code', (code, msg) => {
|
|
305
306
|
if (dead_sender() || !txn) return;
|
|
306
|
-
smtp_client.call_next(((code && code[0] === '5') ? DENY : DENYSOFT),
|
|
307
|
-
msg);
|
|
307
|
+
smtp_client.call_next(((code && code[0] === '5') ? DENY : DENYSOFT), msg);
|
|
308
308
|
smtp_client.release();
|
|
309
309
|
});
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
exports.get_mx_next_hop = next_hop => {
|
|
314
|
-
|
|
314
|
+
// queue.wants && queue.next_hop are mechanisms for fine-grained MX routing.
|
|
315
|
+
// Plugins can specify a queue to perform the delivery as well as a route. A
|
|
316
|
+
// plugin that uses this is qmail-deliverable, which can direct email delivery
|
|
317
|
+
// via smtp_forward, outbound (SMTP), and outbound (LMTP).
|
|
318
|
+
const dest = new url.URL(next_hop);
|
|
315
319
|
const mx = {
|
|
316
320
|
priority: 0,
|
|
317
|
-
port: dest.port || 25,
|
|
321
|
+
port: dest.port || (dest.protocol === 'lmtp:' ? 24 : 25),
|
|
318
322
|
exchange: dest.hostname,
|
|
319
323
|
}
|
|
324
|
+
if (dest.protocol === 'lmtp:') mx.using_lmtp = true;
|
|
320
325
|
if (dest.auth) {
|
|
321
326
|
mx.auth_type = 'plain';
|
|
322
327
|
mx.auth_user = dest.auth.split(':')[0];
|
|
@@ -327,9 +332,11 @@ exports.get_mx_next_hop = next_hop => {
|
|
|
327
332
|
|
|
328
333
|
exports.get_mx = function (next, hmail, domain) {
|
|
329
334
|
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
335
|
+
const qw = hmail.todo.notes.get('queue.wants')
|
|
336
|
+
if (qw && qw !== 'smtp_forward') return next()
|
|
337
|
+
|
|
338
|
+
if (qw === 'smtp_forward' && hmail.todo.notes.get('queue.next_hop')) {
|
|
339
|
+
return next(OK, this.get_mx_next_hop(hmail.todo.notes.get('queue.next_hop')));
|
|
333
340
|
}
|
|
334
341
|
|
|
335
342
|
const dom = this.cfg.main.domain_selector === 'mail_from' ? hmail.todo.mail_from.host.toLowerCase() : domain.toLowerCase();
|
|
@@ -341,8 +348,7 @@ exports.get_mx = function (next, hmail, domain) {
|
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
const mx_opts = [
|
|
344
|
-
'auth_type', 'auth_user', 'auth_pass', 'bind', 'bind_helo',
|
|
345
|
-
'using_lmtp'
|
|
351
|
+
'auth_type', 'auth_user', 'auth_pass', 'bind', 'bind_helo', 'using_lmtp'
|
|
346
352
|
]
|
|
347
353
|
|
|
348
354
|
const mx = {
|
|
@@ -357,5 +363,5 @@ exports.get_mx = function (next, hmail, domain) {
|
|
|
357
363
|
mx[o] = this.cfg[dom][o];
|
|
358
364
|
})
|
|
359
365
|
|
|
360
|
-
|
|
366
|
+
next(OK, mx);
|
|
361
367
|
}
|
package/plugins/tls.js
CHANGED
|
@@ -75,7 +75,7 @@ exports.set_notls = function (connection) {
|
|
|
75
75
|
|
|
76
76
|
this.lognotice(connection, `STARTTLS failed. Marking ${connection.remote.ip} as non-TLS host for ${expiry} seconds`);
|
|
77
77
|
|
|
78
|
-
server.notes.redis.
|
|
78
|
+
server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, (new Date()).toISOString());
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
exports.upgrade_connection = function (next, connection, params) {
|
package/smtp_client.js
CHANGED
|
@@ -168,10 +168,10 @@ class SMTPClient extends events.EventEmitter {
|
|
|
168
168
|
// error is e.g. "Error: connect ECONNREFUSED"
|
|
169
169
|
const errMsg = `${client.uuid}: [${client.host}:${client.port}] SMTP connection ${msg} ${error}`;
|
|
170
170
|
|
|
171
|
+
/* eslint-disable no-fallthrough */
|
|
171
172
|
switch (client.state) {
|
|
172
173
|
case STATE.ACTIVE:
|
|
173
174
|
client.emit('error', errMsg);
|
|
174
|
-
// eslint-disable no-fallthrough
|
|
175
175
|
case STATE.IDLE:
|
|
176
176
|
case STATE.RELEASED:
|
|
177
177
|
client.destroy();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
; disable checks or reject for each test if you are worried about strictness
|
|
2
|
+
|
|
3
|
+
;dns_timeout=30
|
|
4
|
+
|
|
5
|
+
[check]
|
|
6
|
+
match_re=true
|
|
7
|
+
bare_ip=true
|
|
8
|
+
dynamic=true
|
|
9
|
+
big_company=true
|
|
10
|
+
; literal_mismatch: 1 = exact IP match, 2 = IP/24 match, 3 = /24 or RFC1918
|
|
11
|
+
literal_mismatch=2
|
|
12
|
+
valid_hostname=true
|
|
13
|
+
forward_dns=true
|
|
14
|
+
rdns_match=true
|
|
15
|
+
; host_mismatch: hostname differs between EHLO invocations
|
|
16
|
+
host_mismatch=true
|
|
17
|
+
proto_mismatch: host sent EHLO but then tries to sent HELO or vice-versa
|
|
18
|
+
proto_mismatch=true
|
|
19
|
+
|
|
20
|
+
[reject]
|
|
21
|
+
host_mismatch=true
|
|
22
|
+
proto_mismatch=true
|
|
23
|
+
rdns_match=true
|
|
24
|
+
dynamic=true
|
|
25
|
+
bare_ip=true
|
|
26
|
+
literal_mismatch=true
|
|
27
|
+
valid_hostname=true
|
|
28
|
+
forward_dns=true
|
|
29
|
+
big_company=true
|
|
30
|
+
|
|
31
|
+
[skip]
|
|
32
|
+
private_ip=true
|
|
33
|
+
relaying=true
|
|
34
|
+
whitelist=true
|
|
35
|
+
|
|
36
|
+
[bigco]
|
|
37
|
+
msn.com=msn.com
|
|
38
|
+
hotmail.com=hotmail.com
|
|
39
|
+
yahoo.com=yahoo.com,yahoo.co.jp
|
|
40
|
+
yahoo.co.jp=yahoo.com,yahoo.co.jp
|
|
41
|
+
yahoo.co.uk=yahoo.co.uk
|
|
42
|
+
excite.com=excite.com,excitenetwork.com
|
|
43
|
+
mailexcite.com=excite.com,excitenetwork.com
|
|
44
|
+
yahoo.co.jp=yahoo.com,yahoo.co.jp
|
|
45
|
+
mailexcite.com=excite.com,excitenetwork.com
|
|
46
|
+
aol.com=aol.com
|
|
47
|
+
compuserve.com=compuserve.com,adelphia.net
|
|
48
|
+
nortelnetworks.com=nortelnetworks.com,nortel.com
|
|
49
|
+
earthlink.net=earthlink.net
|
|
50
|
+
earthling.net=earthling.net
|
|
51
|
+
google.com=google.com
|
|
52
|
+
gmail.com=google.com,gmail.com
|
|
@@ -69,7 +69,7 @@ exports.multi = {
|
|
|
69
69
|
setUp : _set_up,
|
|
70
70
|
'Spamcop' (test) {
|
|
71
71
|
test.expect(4);
|
|
72
|
-
|
|
72
|
+
this.plugin.multi('127.0.0.2', 'bl.spamcop.net', (err, zone, a, pending) => {
|
|
73
73
|
test.equal(null, err);
|
|
74
74
|
if (pending) {
|
|
75
75
|
test.ok((Array.isArray(a) && a.length > 0));
|
|
@@ -78,12 +78,11 @@ exports.multi = {
|
|
|
78
78
|
else {
|
|
79
79
|
test.done();
|
|
80
80
|
}
|
|
81
|
-
}
|
|
82
|
-
this.plugin.multi('127.0.0.2', 'bl.spamcop.net', cb);
|
|
81
|
+
})
|
|
83
82
|
},
|
|
84
83
|
'CBL' (test) {
|
|
85
84
|
test.expect(4);
|
|
86
|
-
|
|
85
|
+
this.plugin.multi('127.0.0.2', 'xbl.spamhaus.org', (err, zone, a, pending) => {
|
|
87
86
|
test.equal(null, err);
|
|
88
87
|
if (pending) {
|
|
89
88
|
test.ok((Array.isArray(a) && a.length > 0));
|
|
@@ -92,12 +91,12 @@ exports.multi = {
|
|
|
92
91
|
else {
|
|
93
92
|
test.done();
|
|
94
93
|
}
|
|
95
|
-
}
|
|
96
|
-
this.plugin.multi('127.0.0.2', 'xbl.spamhaus.org', cb);
|
|
94
|
+
})
|
|
97
95
|
},
|
|
98
96
|
'Spamcop + CBL' (test) {
|
|
99
97
|
test.expect(12);
|
|
100
|
-
|
|
98
|
+
const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
|
|
99
|
+
this.plugin.multi('127.0.0.2', dnsbls, (err, zone, a, pending) => {
|
|
101
100
|
test.equal(null, err);
|
|
102
101
|
if (pending) {
|
|
103
102
|
test.ok(zone);
|
|
@@ -110,15 +109,20 @@ exports.multi = {
|
|
|
110
109
|
test.equal(false, pending);
|
|
111
110
|
test.done();
|
|
112
111
|
}
|
|
113
|
-
}
|
|
114
|
-
const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
|
|
115
|
-
this.plugin.multi('127.0.0.2', dnsbls, cb);
|
|
112
|
+
})
|
|
116
113
|
},
|
|
117
114
|
'Spamcop + CBL + negative result' (test) {
|
|
118
115
|
test.expect(12);
|
|
119
|
-
|
|
116
|
+
const dnsbls = [ 'bl.spamcop.net','xbl.spamhaus.org' ];
|
|
117
|
+
this.plugin.multi('127.0.0.1', dnsbls, (err, zone, a, pending) => {
|
|
120
118
|
test.equal(null, err);
|
|
121
|
-
|
|
119
|
+
if (a && a[0] && a[0] === '127.255.255.254') {
|
|
120
|
+
test.deepEqual(['127.255.255.254'], a)
|
|
121
|
+
console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
test.equal(null, a)
|
|
125
|
+
}
|
|
122
126
|
if (pending) {
|
|
123
127
|
test.equal(true, pending);
|
|
124
128
|
test.ok(zone);
|
|
@@ -128,14 +132,19 @@ exports.multi = {
|
|
|
128
132
|
test.equal(null, zone);
|
|
129
133
|
test.done();
|
|
130
134
|
}
|
|
131
|
-
}
|
|
132
|
-
const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
|
|
133
|
-
this.plugin.multi('127.0.0.1', dnsbls, cb);
|
|
135
|
+
})
|
|
134
136
|
},
|
|
135
137
|
'IPv6 addresses supported' (test) {
|
|
136
138
|
test.expect(12);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
|
|
140
|
+
this.plugin.multi('::1', dnsbls, (err, zone, a, pending) => {
|
|
141
|
+
if (a && a[0] && a[0] === '127.255.255.254') {
|
|
142
|
+
test.deepEqual(['127.255.255.254'], a)
|
|
143
|
+
console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
test.equal(null, a);
|
|
147
|
+
}
|
|
139
148
|
if (pending) {
|
|
140
149
|
test.deepEqual(null, err);
|
|
141
150
|
test.equal(true, pending);
|
|
@@ -147,9 +156,7 @@ exports.multi = {
|
|
|
147
156
|
test.equal(null, zone);
|
|
148
157
|
test.done();
|
|
149
158
|
}
|
|
150
|
-
}
|
|
151
|
-
const dnsbls = ['bl.spamcop.net','xbl.spamhaus.org'];
|
|
152
|
-
this.plugin.multi('::1', dnsbls, cb);
|
|
159
|
+
})
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -157,25 +164,28 @@ exports.first = {
|
|
|
157
164
|
setUp : _set_up,
|
|
158
165
|
'positive result' (test) {
|
|
159
166
|
test.expect(3);
|
|
160
|
-
|
|
167
|
+
const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
|
|
168
|
+
this.plugin.first('127.0.0.2', dnsbls, (err, zone, a) => {
|
|
161
169
|
test.equal(null, err);
|
|
162
170
|
test.ok(zone);
|
|
163
171
|
test.ok((Array.isArray(a) && a.length > 0));
|
|
164
172
|
test.done();
|
|
165
|
-
}
|
|
166
|
-
const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
|
|
167
|
-
this.plugin.first('127.0.0.2', dnsbls , cb);
|
|
173
|
+
})
|
|
168
174
|
},
|
|
169
175
|
'negative result' (test) {
|
|
170
|
-
test.expect(
|
|
171
|
-
|
|
176
|
+
test.expect(2);
|
|
177
|
+
const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
|
|
178
|
+
this.plugin.first('127.0.0.1', dnsbls, (err, zone, a) => {
|
|
172
179
|
test.equal(null, err);
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
if (a && a[0] && a[0] === '127.255.255.254') {
|
|
181
|
+
test.deepEqual(['127.255.255.254'], a)
|
|
182
|
+
console.warn(`ERROR: DNSBLs don't work with PUBLIC DNS!`)
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
test.equal(null, a);
|
|
186
|
+
}
|
|
175
187
|
test.done();
|
|
176
|
-
}
|
|
177
|
-
const dnsbls = [ 'xbl.spamhaus.org', 'bl.spamcop.net' ];
|
|
178
|
-
this.plugin.first('127.0.0.1', dnsbls, cb);
|
|
188
|
+
})
|
|
179
189
|
},
|
|
180
190
|
'each_cb' (test) {
|
|
181
191
|
test.expect(7);
|