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/plugins/spamassassin.js
CHANGED
|
@@ -5,13 +5,11 @@ const sock = require('./line_socket');
|
|
|
5
5
|
const utils = require('haraka-utils');
|
|
6
6
|
|
|
7
7
|
exports.register = function () {
|
|
8
|
-
|
|
9
|
-
plugin.load_spamassassin_ini();
|
|
8
|
+
this.load_spamassassin_ini();
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
exports.load_spamassassin_ini = function () {
|
|
13
|
-
|
|
14
|
-
plugin.cfg = plugin.config.get('spamassassin.ini', {
|
|
12
|
+
this.cfg = this.config.get('spamassassin.ini', {
|
|
15
13
|
booleans: [
|
|
16
14
|
'+add_headers',
|
|
17
15
|
'+check.authenticated',
|
|
@@ -24,7 +22,7 @@ exports.load_spamassassin_ini = function () {
|
|
|
24
22
|
'-defer.scan_timeout',
|
|
25
23
|
],
|
|
26
24
|
}, () => {
|
|
27
|
-
|
|
25
|
+
this.load_spamassassin_ini();
|
|
28
26
|
});
|
|
29
27
|
|
|
30
28
|
const defaults = {
|
|
@@ -36,31 +34,29 @@ exports.load_spamassassin_ini = function () {
|
|
|
36
34
|
};
|
|
37
35
|
|
|
38
36
|
for (const key in defaults) {
|
|
39
|
-
if (
|
|
40
|
-
|
|
37
|
+
if (this.cfg.main[key]) continue;
|
|
38
|
+
this.cfg.main[key] = defaults[key];
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
[
|
|
44
42
|
'reject_threshold', 'relay_reject_threshold',
|
|
45
43
|
'munge_subject_threshold', 'max_size'
|
|
46
44
|
].forEach(item => {
|
|
47
|
-
if (!
|
|
48
|
-
|
|
45
|
+
if (!this.cfg.main[item]) return;
|
|
46
|
+
this.cfg.main[item] = Number(this.cfg.main[item]);
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
exports.hook_data_post = function (next, connection) {
|
|
53
|
-
const plugin = this;
|
|
54
|
-
const conn = connection;
|
|
55
|
-
const txn = connection.transaction;
|
|
56
51
|
|
|
57
|
-
if (
|
|
52
|
+
if (this.should_skip(connection)) return next();
|
|
58
53
|
|
|
59
|
-
txn.
|
|
54
|
+
const txn = connection.transaction;
|
|
55
|
+
txn.remove_header(this.cfg.main.spamc_auth_header); // just to be safe
|
|
60
56
|
|
|
61
|
-
const username =
|
|
62
|
-
const headers =
|
|
63
|
-
const socket =
|
|
57
|
+
const username = this.get_spamd_username(connection);
|
|
58
|
+
const headers = this.get_spamd_headers(connection, username);
|
|
59
|
+
const socket = this.get_spamd_socket(next, connection, headers);
|
|
64
60
|
|
|
65
61
|
const spamd_response = { headers: {} };
|
|
66
62
|
let state = 'line0';
|
|
@@ -68,7 +64,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
68
64
|
const start = Date.now();
|
|
69
65
|
|
|
70
66
|
socket.on('line', line => {
|
|
71
|
-
|
|
67
|
+
connection.logprotocol(this, `Spamd C: ${line} state=${state}`);
|
|
72
68
|
line = line.replace(/\r?\n/, '');
|
|
73
69
|
if (state === 'line0') {
|
|
74
70
|
spamd_response.line0 = line;
|
|
@@ -92,7 +88,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
92
88
|
else if (state === 'headers') {
|
|
93
89
|
const m = line.match(/^X-Spam-([\x21-\x39\x3B-\x7E]+):\s*(.*)/);
|
|
94
90
|
if (m) {
|
|
95
|
-
|
|
91
|
+
connection.logdebug(this, `header: ${line}`);
|
|
96
92
|
last_header = m[1];
|
|
97
93
|
spamd_response.headers[m[1]] = m[2];
|
|
98
94
|
return;
|
|
@@ -107,14 +103,14 @@ exports.hook_data_post = function (next, connection) {
|
|
|
107
103
|
});
|
|
108
104
|
|
|
109
105
|
socket.once('end', () => {
|
|
110
|
-
if (!
|
|
106
|
+
if (!connection.transaction) return next() // client gone
|
|
111
107
|
|
|
112
|
-
if (spamd_response.headers
|
|
108
|
+
if (spamd_response.headers?.Tests) {
|
|
113
109
|
spamd_response.tests = spamd_response.headers.Tests.replace(/\s/g, '');
|
|
114
110
|
}
|
|
115
111
|
if (spamd_response.tests === undefined) {
|
|
116
112
|
// strip the 'tests' from the X-Spam-Status header
|
|
117
|
-
if (spamd_response.headers
|
|
113
|
+
if (spamd_response.headers?.Status) {
|
|
118
114
|
// SpamAssassin appears to have a bug that causes a space not to
|
|
119
115
|
// be added before autolearn= when the header line has been folded.
|
|
120
116
|
// So we modify the regexp here not to match autolearn onwards.
|
|
@@ -127,29 +123,28 @@ exports.hook_data_post = function (next, connection) {
|
|
|
127
123
|
|
|
128
124
|
// do stuff with the results...
|
|
129
125
|
txn.notes.spamassassin = spamd_response;
|
|
130
|
-
|
|
126
|
+
connection.results.add(this, {
|
|
131
127
|
time: (Date.now() - start)/1000,
|
|
132
128
|
hits: spamd_response.hits,
|
|
133
129
|
flag: spamd_response.flag,
|
|
134
130
|
});
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
this.fixup_old_headers(txn);
|
|
133
|
+
this.do_header_updates(connection, spamd_response);
|
|
134
|
+
this.log_results(connection, spamd_response);
|
|
139
135
|
|
|
140
|
-
const exceeds_err =
|
|
136
|
+
const exceeds_err = this.score_too_high(connection, spamd_response);
|
|
141
137
|
if (exceeds_err) return next(DENY, exceeds_err);
|
|
142
138
|
|
|
143
|
-
|
|
139
|
+
this.munge_subject(connection, spamd_response.score);
|
|
144
140
|
|
|
145
141
|
next();
|
|
146
142
|
});
|
|
147
143
|
}
|
|
148
144
|
|
|
149
145
|
exports.fixup_old_headers = function (txn) {
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const headers = txn.notes.spamassassin.headers;
|
|
146
|
+
const action = this.cfg.main.old_headers_action;
|
|
147
|
+
const { headers } = txn.notes.spamassassin;
|
|
153
148
|
|
|
154
149
|
let key;
|
|
155
150
|
switch (action) {
|
|
@@ -178,29 +173,27 @@ exports.fixup_old_headers = function (txn) {
|
|
|
178
173
|
}
|
|
179
174
|
|
|
180
175
|
exports.munge_subject = function (conn, score) {
|
|
181
|
-
const
|
|
182
|
-
const munge = plugin.cfg.main.munge_subject_threshold;
|
|
176
|
+
const munge = this.cfg.main.munge_subject_threshold;
|
|
183
177
|
if (!munge) return;
|
|
184
178
|
if (parseFloat(score) < parseFloat(munge)) return;
|
|
185
179
|
|
|
186
180
|
const subj = conn.transaction.header.get('Subject');
|
|
187
|
-
const subject_re = new RegExp(`^${utils.regexp_escape(
|
|
181
|
+
const subject_re = new RegExp(`^${utils.regexp_escape(this.cfg.main.subject_prefix)}`);
|
|
188
182
|
if (subject_re.test(subj)) return; // prevent double munge
|
|
189
183
|
|
|
190
184
|
conn.transaction.remove_header('Subject');
|
|
191
|
-
conn.transaction.add_header('Subject', `${
|
|
185
|
+
conn.transaction.add_header('Subject', `${this.cfg.main.subject_prefix} ${subj}`);
|
|
192
186
|
}
|
|
193
187
|
|
|
194
188
|
exports.do_header_updates = function (conn, spamd_response) {
|
|
195
|
-
const plugin = this;
|
|
196
189
|
if (spamd_response.flag === 'Yes') {
|
|
197
190
|
// X-Spam-Flag is added by SpamAssassin
|
|
198
191
|
conn.transaction.remove_header('precedence');
|
|
199
192
|
conn.transaction.add_header('Precedence', 'junk');
|
|
200
193
|
}
|
|
201
194
|
|
|
202
|
-
const modern =
|
|
203
|
-
if ( !
|
|
195
|
+
const modern = this.cfg.main.modern_status_syntax;
|
|
196
|
+
if ( !this.cfg.main.add_headers ) return;
|
|
204
197
|
|
|
205
198
|
for (const key in spamd_response.headers) {
|
|
206
199
|
if (!key || key === '' || key === undefined) continue;
|
|
@@ -218,31 +211,27 @@ exports.do_header_updates = function (conn, spamd_response) {
|
|
|
218
211
|
}
|
|
219
212
|
|
|
220
213
|
exports.score_too_high = function (conn, spamd_response) {
|
|
221
|
-
const
|
|
222
|
-
const score = spamd_response.score;
|
|
214
|
+
const { score } = spamd_response;
|
|
223
215
|
if (conn.relaying) {
|
|
224
|
-
const rmax =
|
|
216
|
+
const rmax = this.cfg.main.relay_reject_threshold;
|
|
225
217
|
if (rmax && (score >= rmax)) {
|
|
226
218
|
return "spam score exceeded relay threshold";
|
|
227
219
|
}
|
|
228
220
|
}
|
|
229
221
|
|
|
230
|
-
const max =
|
|
231
|
-
if (max && (score >= max))
|
|
232
|
-
return "spam score exceeded threshold";
|
|
233
|
-
}
|
|
222
|
+
const max = this.cfg.main.reject_threshold;
|
|
223
|
+
if (max && (score >= max)) return "spam score exceeded threshold";
|
|
234
224
|
|
|
235
|
-
return
|
|
225
|
+
return '';
|
|
236
226
|
}
|
|
237
227
|
|
|
238
228
|
exports.get_spamd_username = function (conn) {
|
|
239
|
-
const plugin = this;
|
|
240
229
|
|
|
241
230
|
let user = conn.transaction.notes.spamd_user; // 1st priority
|
|
242
231
|
if (user && user !== undefined) return user;
|
|
243
232
|
|
|
244
|
-
if (!
|
|
245
|
-
user =
|
|
233
|
+
if (!this.cfg.main.spamd_user) return 'default'; // when not defined
|
|
234
|
+
user = this.cfg.main.spamd_user;
|
|
246
235
|
|
|
247
236
|
// Enable per-user SA prefs
|
|
248
237
|
if (user === 'first-recipient') { // special cases
|
|
@@ -259,7 +248,6 @@ exports.get_spamd_username = function (conn) {
|
|
|
259
248
|
}
|
|
260
249
|
|
|
261
250
|
exports.get_spamd_headers = function (conn, username) {
|
|
262
|
-
const plugin = this;
|
|
263
251
|
// http://svn.apache.org/repos/asf/spamassassin/trunk/spamd/PROTOCOL
|
|
264
252
|
const headers = [
|
|
265
253
|
'HEADERS SPAMC/1.4',
|
|
@@ -269,7 +257,7 @@ exports.get_spamd_headers = function (conn, username) {
|
|
|
269
257
|
`X-Haraka-UUID: ${conn.transaction.uuid}`,
|
|
270
258
|
];
|
|
271
259
|
if (conn.relaying) {
|
|
272
|
-
headers.push(`${
|
|
260
|
+
headers.push(`${this.cfg.main.spamc_auth_header}: true`);
|
|
273
261
|
}
|
|
274
262
|
|
|
275
263
|
return headers;
|
|
@@ -335,8 +323,7 @@ exports.get_spamd_socket = function (next, conn, headers) {
|
|
|
335
323
|
}
|
|
336
324
|
|
|
337
325
|
exports.log_results = function (conn, spamd_response) {
|
|
338
|
-
const
|
|
339
|
-
const cfg = plugin.cfg.main;
|
|
326
|
+
const cfg = this.cfg.main;
|
|
340
327
|
const reject_threshold = (conn.relaying) ? (cfg.relay_reject_threshold || cfg.reject_threshold) : cfg.reject_threshold;
|
|
341
328
|
|
|
342
329
|
const human_text = `status=${spamd_response.flag}` +
|
|
@@ -345,49 +332,50 @@ exports.log_results = function (conn, spamd_response) {
|
|
|
345
332
|
`, reject=${reject_threshold}` +
|
|
346
333
|
`, tests="${spamd_response.tests}"`;
|
|
347
334
|
|
|
348
|
-
conn.transaction.results.add(
|
|
335
|
+
conn.transaction.results.add(this, {
|
|
349
336
|
human: human_text,
|
|
350
337
|
status: spamd_response.flag, score: parseFloat(spamd_response.score),
|
|
351
338
|
required: parseFloat(spamd_response.reqd), reject: reject_threshold, tests: spamd_response.tests,
|
|
352
339
|
emit: true});
|
|
353
340
|
}
|
|
354
341
|
|
|
355
|
-
exports.should_skip = function (
|
|
356
|
-
const
|
|
342
|
+
exports.should_skip = function (connection = {}) {
|
|
343
|
+
const { transaction } = connection;
|
|
344
|
+
if (!transaction) return true;
|
|
357
345
|
|
|
358
346
|
// a message might be skipped for multiple reasons, store each in results
|
|
359
347
|
let result = false; // default
|
|
360
348
|
|
|
361
|
-
const max =
|
|
349
|
+
const max = this.cfg.main.max_size;
|
|
362
350
|
if (max) {
|
|
363
|
-
const size =
|
|
351
|
+
const size = connection.transaction.data_bytes;
|
|
364
352
|
if (size > max) {
|
|
365
|
-
|
|
353
|
+
connection.transaction.results.add(this, { skip: `size ${utils.prettySize(size)} exceeds max: ${utils.prettySize(max)}`});
|
|
366
354
|
result = true;
|
|
367
355
|
}
|
|
368
356
|
}
|
|
369
357
|
|
|
370
|
-
if (
|
|
371
|
-
|
|
358
|
+
if (this.cfg.check.authenticated == false && connection.notes.auth_user) {
|
|
359
|
+
connection.transaction.results.add(this, { skip: 'authed'});
|
|
372
360
|
result = true;
|
|
373
361
|
}
|
|
374
362
|
|
|
375
|
-
if (
|
|
376
|
-
|
|
363
|
+
if (this.cfg.check.relay == false && connection.relaying) {
|
|
364
|
+
connection.transaction.results.add(this, { skip: 'relay'});
|
|
377
365
|
result = true;
|
|
378
366
|
}
|
|
379
367
|
|
|
380
|
-
if (
|
|
381
|
-
|
|
368
|
+
if (this.cfg.check.local_ip == false && connection.remote.is_local) {
|
|
369
|
+
connection.transaction.results.add(this, { skip: 'local_ip'});
|
|
382
370
|
result = true;
|
|
383
371
|
}
|
|
384
372
|
|
|
385
|
-
if (
|
|
386
|
-
if (
|
|
373
|
+
if (this.cfg.check.private_ip == false && connection.remote.is_private) {
|
|
374
|
+
if (this.cfg.check.local_ip == true && connection.remote.is_local) {
|
|
387
375
|
// local IPs are included in private IPs
|
|
388
376
|
}
|
|
389
377
|
else {
|
|
390
|
-
|
|
378
|
+
connection.transaction.results.add(this, { skip: 'private_ip'});
|
|
391
379
|
result = true;
|
|
392
380
|
}
|
|
393
381
|
}
|
package/plugins/status.js
CHANGED
|
@@ -148,7 +148,7 @@ exports.queue_discard = function (file, cb) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
exports.queue_push = function (file, cb) {
|
|
151
|
-
const queue = this.outbound.temp_fail_queue
|
|
151
|
+
const { queue } = this.outbound.temp_fail_queue;
|
|
152
152
|
|
|
153
153
|
for (let i = 0; i < queue.length; i++) {
|
|
154
154
|
if (queue[i].id !== file) continue;
|
|
@@ -214,10 +214,9 @@ exports.call_master = (cmd, cb) => {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
exports.call_workers = function (cmd, cb) {
|
|
217
|
-
const self = this;
|
|
218
217
|
|
|
219
218
|
async.map(server.cluster.workers, (w, done) => {
|
|
220
|
-
|
|
219
|
+
this.call_worker(w, cmd, done);
|
|
221
220
|
}, cb);
|
|
222
221
|
}
|
|
223
222
|
|
package/plugins/tarpit.js
CHANGED
|
@@ -8,30 +8,27 @@ let hooks_to_delay = [
|
|
|
8
8
|
|
|
9
9
|
exports.register = function () {
|
|
10
10
|
// Register tarpit function last
|
|
11
|
-
const plugin = this;
|
|
12
11
|
|
|
13
|
-
const cfg =
|
|
14
|
-
if (cfg
|
|
12
|
+
const cfg = this.config.get('tarpit.ini');
|
|
13
|
+
if (cfg?.main.hooks_to_delay) {
|
|
15
14
|
hooks_to_delay = cfg.main.hooks_to_delay.split(/[\s,;]+/);
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
for (
|
|
19
|
-
|
|
20
|
-
plugin.register_hook(hook, 'tarpit');
|
|
17
|
+
for (const hook of hooks_to_delay) {
|
|
18
|
+
this.register_hook(hook, 'tarpit');
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
exports.tarpit = function (next, connection) {
|
|
25
|
-
const
|
|
23
|
+
const { transaction } = connection;
|
|
24
|
+
if (!transaction) return next();
|
|
26
25
|
|
|
27
26
|
let delay = connection.notes.tarpit;
|
|
28
27
|
|
|
29
|
-
if (!delay
|
|
30
|
-
delay = connection.transaction.notes.tarpit;
|
|
31
|
-
}
|
|
28
|
+
if (!delay) delay = transaction.notes.tarpit;
|
|
32
29
|
|
|
33
30
|
if (!delay) return next();
|
|
34
31
|
|
|
35
|
-
connection.loginfo(
|
|
32
|
+
connection.loginfo(this, `tarpitting response for ${delay}s`);
|
|
36
33
|
setTimeout(() => next(), delay * 1000);
|
|
37
34
|
}
|
package/plugins/tls.js
CHANGED
|
@@ -21,18 +21,17 @@ exports.shutdown = () => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
exports.advertise_starttls = function (next, connection) {
|
|
24
|
-
const plugin = this;
|
|
25
24
|
|
|
26
25
|
// if no TLS setup incomplete/invalid, don't advertise
|
|
27
26
|
if (!tls_socket.tls_valid) {
|
|
28
|
-
|
|
27
|
+
this.logerror('no valid TLS config');
|
|
29
28
|
return next();
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/* Caution: do not advertise STARTTLS if already TLS upgraded */
|
|
33
32
|
if (connection.tls.enabled) return next();
|
|
34
33
|
|
|
35
|
-
if (
|
|
34
|
+
if (this.net_utils.ip_in_list(tls_socket.cfg.no_tls_hosts, connection.remote.ip)) {
|
|
36
35
|
return next();
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -52,20 +51,19 @@ exports.advertise_starttls = function (next, connection) {
|
|
|
52
51
|
return enable_tls();
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
const redis = server.notes
|
|
54
|
+
const { redis } = server.notes;
|
|
56
55
|
const dbkey = `no_tls|${connection.remote.ip}`;
|
|
57
56
|
|
|
58
|
-
redis.get(dbkey
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
57
|
+
redis.get(dbkey)
|
|
58
|
+
.then(dbr => {
|
|
59
|
+
if (!dbr) return enable_tls();
|
|
60
|
+
connection.results.add(this, { msg: 'no_tls'});
|
|
61
|
+
next(CONT, 'STARTTLS disabled because previous attempt failed')
|
|
62
|
+
})
|
|
63
|
+
.catch(err => {
|
|
64
|
+
connection.results.add(this, {err});
|
|
65
|
+
enable_tls();
|
|
66
|
+
})
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
exports.set_notls = function (connection) {
|
|
@@ -138,7 +136,6 @@ exports.hook_disconnect = (next, connection) => {
|
|
|
138
136
|
}
|
|
139
137
|
|
|
140
138
|
exports.emit_upgrade_msg = function (conn, verified, verifyErr, cert, cipher) {
|
|
141
|
-
const plugin = this;
|
|
142
139
|
let msg = 'secured:';
|
|
143
140
|
if (cipher) {
|
|
144
141
|
msg += ` cipher=${cipher.name} version=${cipher.version}`;
|
|
@@ -154,6 +151,6 @@ exports.emit_upgrade_msg = function (conn, verified, verifyErr, cert, cipher) {
|
|
|
154
151
|
if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}`;
|
|
155
152
|
}
|
|
156
153
|
|
|
157
|
-
conn.loginfo(
|
|
154
|
+
conn.loginfo(this, msg);
|
|
158
155
|
return msg;
|
|
159
156
|
}
|
package/plugins/toobusy.js
CHANGED
|
@@ -4,26 +4,24 @@ let toobusy;
|
|
|
4
4
|
let was_busy = false;
|
|
5
5
|
|
|
6
6
|
exports.register = function () {
|
|
7
|
-
const plugin = this;
|
|
8
7
|
|
|
9
8
|
try {
|
|
10
9
|
toobusy = require('toobusy-js');
|
|
11
10
|
}
|
|
12
11
|
catch (e) {
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
this.logerror(e);
|
|
13
|
+
this.logerror("try: 'npm install -g toobusy-js'");
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
this.loadConfig();
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
this.register_hook('connect', 'check_busy', -100);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
exports.loadConfig = function () {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
plugin.loadConfig();
|
|
23
|
+
let maxLag = this.config.get('toobusy.maxlag','value', () => {
|
|
24
|
+
this.loadConfig();
|
|
27
25
|
});
|
|
28
26
|
|
|
29
27
|
maxLag = parseInt(maxLag);
|
package/plugins/xclient.js
CHANGED
|
@@ -11,9 +11,8 @@ exports.register = function () {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
exports.load_xclient_hosts = function () {
|
|
14
|
-
const self = this;
|
|
15
14
|
const cfg = this.config.get('xclient.hosts', 'list', () => {
|
|
16
|
-
|
|
15
|
+
this.load_xclient_hosts();
|
|
17
16
|
});
|
|
18
17
|
const ah = {};
|
|
19
18
|
for (const i in cfg) {
|
|
@@ -23,10 +22,7 @@ exports.load_xclient_hosts = function () {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
function xclient_allowed (ip) {
|
|
26
|
-
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
25
|
+
return !!(ip === '127.0.0.1' || ip === '::1' || allowed_hosts[ip]);
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
exports.hook_capabilities = (next, connection) => {
|
|
@@ -37,17 +33,14 @@ exports.hook_capabilities = (next, connection) => {
|
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
40
|
-
if (params[0] !== 'XCLIENT')
|
|
41
|
-
return next();
|
|
42
|
-
}
|
|
36
|
+
if (params[0] !== 'XCLIENT') return next();
|
|
43
37
|
|
|
44
38
|
// XCLIENT is not allowed after transaction start
|
|
45
|
-
if (connection
|
|
46
|
-
return next(DENY,
|
|
47
|
-
DSN.proto_unspecified('Mail transaction in progress', 503));
|
|
39
|
+
if (connection?.transaction) {
|
|
40
|
+
return next(DENY, DSN.proto_unspecified('Mail transaction in progress', 503));
|
|
48
41
|
}
|
|
49
42
|
|
|
50
|
-
if (!(xclient_allowed(connection
|
|
43
|
+
if (!(xclient_allowed(connection?.remote?.ip))) {
|
|
51
44
|
return next(DENY, DSN.proto_unspecified('Not authorized', 550));
|
|
52
45
|
}
|
|
53
46
|
|
|
@@ -55,8 +48,8 @@ exports.hook_unrecognized_command = function (next, connection, params) {
|
|
|
55
48
|
// Process arguments
|
|
56
49
|
const args = (new String(params[1])).toLowerCase().split(/ /);
|
|
57
50
|
const xclient = {};
|
|
58
|
-
for (
|
|
59
|
-
const match = /^([^=]+)=([^ ]+)/.exec(
|
|
51
|
+
for (const arg of args) {
|
|
52
|
+
const match = /^([^=]+)=([^ ]+)/.exec(arg);
|
|
60
53
|
if (match) {
|
|
61
54
|
connection.logdebug(this, `found key=${match[1]} value=${match[2]}`);
|
|
62
55
|
switch (match[1]) {
|
|
@@ -94,18 +87,17 @@ exports.hook_unrecognized_command = function (next, connection, params) {
|
|
|
94
87
|
}
|
|
95
88
|
break;
|
|
96
89
|
default:
|
|
97
|
-
connection.logwarn(this, `unknown argument: ${
|
|
90
|
+
connection.logwarn(this, `unknown argument: ${arg}`);
|
|
98
91
|
}
|
|
99
92
|
}
|
|
100
93
|
else {
|
|
101
|
-
connection.logwarn(this, `unknown argument: ${
|
|
94
|
+
connection.logwarn(this, `unknown argument: ${arg}`);
|
|
102
95
|
}
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
// Abort if we don't have a valid IP address
|
|
106
99
|
if (!xclient.addr) {
|
|
107
|
-
return next(DENY,
|
|
108
|
-
DSN.proto_invalid_cmd_args('No valid IP address found', 501));
|
|
100
|
+
return next(DENY, DSN.proto_invalid_cmd_args('No valid IP address found', 501));
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
// Apply changes
|
|
@@ -125,10 +117,7 @@ exports.hook_unrecognized_command = function (next, connection, params) {
|
|
|
125
117
|
}
|
|
126
118
|
connection.esmtp = (xclient.proto === 'esmtp');
|
|
127
119
|
connection.xclient = true;
|
|
128
|
-
if (!xclient.name)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
else {
|
|
132
|
-
return next(NEXT_HOOK, 'connect');
|
|
133
|
-
}
|
|
120
|
+
if (!xclient.name) return next(NEXT_HOOK, 'lookup_rdns');
|
|
121
|
+
|
|
122
|
+
return next(NEXT_HOOK, 'connect');
|
|
134
123
|
}
|