Haraka 3.0.3 → 3.0.4
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 +5 -9
- package/.prettierrc.yml +1 -0
- package/CONTRIBUTORS.md +11 -0
- package/Changes.md +1365 -1214
- package/Plugins.md +117 -105
- package/README.md +4 -13
- package/bin/haraka +197 -298
- package/config/auth_flat_file.ini +1 -0
- package/config/dhparams.pem +8 -0
- package/config/mail_from.is_resolvable.ini +4 -2
- package/config/me +1 -0
- package/config/outbound.ini +0 -2
- package/config/plugins +36 -35
- package/config/smtp.ini +0 -1
- package/config/smtp.json +17 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/connection.js +46 -73
- package/contrib/bsd-rc.d/haraka +3 -1
- package/contrib/plugin2npm.sh +6 -36
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -201
- package/docs/Plugins.md +2 -2
- package/docs/Transaction.md +59 -82
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/tls.md +29 -9
- package/endpoint.js +16 -13
- package/haraka.js +10 -14
- package/host_pool.js +5 -5
- package/line_socket.js +3 -4
- package/logger.js +44 -28
- package/outbound/client_pool.js +27 -23
- package/outbound/config.js +4 -6
- package/outbound/fsync_writestream.js +1 -1
- package/outbound/hmail.js +178 -218
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +51 -44
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +59 -48
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +4 -2
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +1 -1
- package/plugins/block_me.js +1 -1
- package/plugins/data.signatures.js +2 -4
- package/plugins/early_talker.js +2 -1
- package/plugins/mail_from.is_resolvable.js +65 -135
- package/plugins/queue/deliver.js +4 -5
- package/plugins/queue/lmtp.js +11 -14
- package/plugins/queue/qmail-queue.js +2 -2
- package/plugins/queue/quarantine.js +2 -2
- package/plugins/queue/rabbitmq.js +16 -17
- package/plugins/queue/smtp_forward.js +3 -3
- package/plugins/queue/smtp_proxy.js +10 -1
- package/plugins/queue/test.js +2 -2
- package/plugins/rcpt_to.host_list_base.js +5 -5
- package/plugins/rcpt_to.in_host_list.js +2 -2
- package/plugins/relay.js +6 -7
- package/plugins/reseed_rng.js +1 -1
- package/plugins/status.js +37 -33
- package/plugins/tls.js +2 -2
- package/plugins/xclient.js +3 -2
- package/plugins.js +50 -54
- package/run_tests +3 -30
- package/server.js +190 -190
- package/smtp_client.js +30 -23
- package/{tests → test}/config/plugins +0 -2
- package/{tests → test}/config/smtp.ini +1 -1
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/connection.js +302 -0
- package/test/endpoint.js +94 -0
- package/{tests → test}/fixtures/line_socket.js +1 -1
- package/{tests → test}/fixtures/util_hmailitem.js +19 -25
- package/{tests → test}/host_pool.js +42 -57
- package/test/logger.js +258 -0
- package/test/outbound/hmail.js +141 -0
- package/test/outbound/index.js +220 -0
- package/test/outbound/qfile.js +126 -0
- package/test/outbound_bounce_net_errors.js +142 -0
- package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
- package/test/plugins/auth/auth_base.js +484 -0
- package/test/plugins/auth/auth_vpopmaild.js +83 -0
- package/test/plugins/early_talker.js +104 -0
- package/test/plugins/mail_from.is_resolvable.js +35 -0
- package/test/plugins/queue/smtp_forward.js +206 -0
- package/test/plugins/rcpt_to.host_list_base.js +122 -0
- package/test/plugins/rcpt_to.in_host_list.js +193 -0
- package/test/plugins/relay.js +303 -0
- package/test/plugins/status.js +130 -0
- package/test/plugins/tls.js +70 -0
- package/test/plugins.js +228 -0
- package/test/rfc1869.js +73 -0
- package/test/server.js +491 -0
- package/test/smtp_client.js +299 -0
- package/test/tls_socket.js +273 -0
- package/test/transaction.js +270 -0
- package/tls_socket.js +202 -252
- package/transaction.js +8 -23
- package/CONTRIBUTING.md +0 -1
- package/bin/dkimverify +0 -40
- package/config/access.domains +0 -13
- package/config/attachment.ctype.regex +0 -2
- package/config/attachment.filename.regex +0 -1
- package/config/avg.ini +0 -5
- package/config/bounce.ini +0 -15
- package/config/data.headers.ini +0 -61
- package/config/dkim/dkim_key_gen.sh +0 -78
- package/config/dkim_sign.ini +0 -4
- package/config/dkim_verify.ini +0 -7
- package/config/dnsbl.ini +0 -23
- package/config/greylist.ini +0 -43
- package/config/helo.checks.ini +0 -52
- package/config/messagesniffer.ini +0 -18
- package/config/spamassassin.ini +0 -56
- package/dkim.js +0 -614
- package/docs/plugins/avg.md +0 -35
- package/docs/plugins/bounce.md +0 -69
- package/docs/plugins/clamd.md +0 -147
- package/docs/plugins/esets.md +0 -8
- package/docs/plugins/greylist.md +0 -90
- package/docs/plugins/helo.checks.md +0 -135
- package/docs/plugins/messagesniffer.md +0 -163
- package/docs/plugins/spamassassin.md +0 -180
- package/outbound/mx_lookup.js +0 -70
- package/plugins/auth/auth_ldap.js +0 -3
- package/plugins/avg.js +0 -162
- package/plugins/backscatterer.js +0 -25
- package/plugins/bounce.js +0 -381
- package/plugins/clamd.js +0 -382
- package/plugins/data.uribl.js +0 -4
- package/plugins/dkim_sign.js +0 -395
- package/plugins/dkim_verify.js +0 -62
- package/plugins/dns_list_base.js +0 -221
- package/plugins/dnsbl.js +0 -146
- package/plugins/dnswl.js +0 -58
- package/plugins/esets.js +0 -71
- package/plugins/graph.js +0 -5
- package/plugins/greylist.js +0 -645
- package/plugins/helo.checks.js +0 -533
- package/plugins/messagesniffer.js +0 -381
- package/plugins/rcpt_to.ldap.js +0 -3
- package/plugins/rcpt_to.max_count.js +0 -24
- package/plugins/spamassassin.js +0 -384
- package/tests/config/dkim/example.com/dns +0 -29
- package/tests/config/dkim/example.com/private +0 -6
- package/tests/config/dkim/example.com/public +0 -4
- package/tests/config/dkim/example.com/selector +0 -1
- package/tests/config/dkim.private.key +0 -6
- package/tests/config/dkim_sign.ini +0 -4
- package/tests/config/helo.checks.ini +0 -52
- package/tests/connection.js +0 -327
- package/tests/endpoint.js +0 -128
- package/tests/fixtures/vm_harness.js +0 -59
- package/tests/logger.js +0 -327
- package/tests/outbound/hmail.js +0 -112
- package/tests/outbound/index.js +0 -324
- package/tests/outbound/qfile.js +0 -67
- package/tests/outbound_bounce_net_errors.js +0 -173
- package/tests/plugins/auth/auth_base.js +0 -463
- package/tests/plugins/auth/auth_vpopmaild.js +0 -91
- package/tests/plugins/bounce.js +0 -307
- package/tests/plugins/clamd.js +0 -224
- package/tests/plugins/deprecated/relay_acl.js +0 -140
- package/tests/plugins/deprecated/relay_all.js +0 -59
- package/tests/plugins/dkim_sign.js +0 -315
- package/tests/plugins/dkim_signer.js +0 -108
- package/tests/plugins/dns_list_base.js +0 -259
- package/tests/plugins/dnsbl.js +0 -101
- package/tests/plugins/early_talker.js +0 -115
- package/tests/plugins/greylist.js +0 -58
- package/tests/plugins/helo.checks.js +0 -525
- package/tests/plugins/mail_from.is_resolvable.js +0 -116
- package/tests/plugins/queue/smtp_forward.js +0 -221
- package/tests/plugins/rcpt_to.host_list_base.js +0 -132
- package/tests/plugins/rcpt_to.in_host_list.js +0 -218
- package/tests/plugins/relay.js +0 -339
- package/tests/plugins/spamassassin.js +0 -171
- package/tests/plugins/status.js +0 -138
- package/tests/plugins/tls.js +0 -84
- package/tests/plugins.js +0 -247
- package/tests/rfc1869.js +0 -61
- package/tests/server.js +0 -510
- package/tests/smtp_client/auth.js +0 -105
- package/tests/smtp_client/basic.js +0 -101
- package/tests/smtp_client.js +0 -80
- package/tests/tls_socket.js +0 -333
- package/tests/transaction.js +0 -284
- /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
- /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
- /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
- /package/docs/{plugins → deprecated}/dnswl.md +0 -0
- /package/{tests → test}/.eslintrc.yaml +0 -0
- /package/{tests → test}/config/auth_flat_file.ini +0 -0
- /package/{tests → test}/config/dhparams.pem +0 -0
- /package/{tests → test}/config/host_list +0 -0
- /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
- /package/{tests → test}/config/outbound_tls_key.pem +0 -0
- /package/{tests → test}/config/smtp_forward.ini +0 -0
- /package/{tests → test}/config/tls/ec.pem +0 -0
- /package/{tests → test}/config/tls/haraka.local.pem +0 -0
- /package/{tests → test}/config/tls/mismatched.pem +0 -0
- /package/{tests → test}/config/tls.ini +0 -0
- /package/{tests → test}/config/tls_cert.pem +0 -0
- /package/{tests → test}/config/tls_key.pem +0 -0
- /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
- /package/{tests → test}/installation/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/config/tls.ini +0 -0
- /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
- /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
- /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
- /package/{tests → test}/installation/plugins/inherits.js +0 -0
- /package/{tests → test}/installation/plugins/load_first.js +0 -0
- /package/{tests → test}/installation/plugins/plugin.js +0 -0
- /package/{tests → test}/installation/plugins/tls.js +0 -0
- /package/{tests → test}/loud/config/dhparams.pem +0 -0
- /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
- /package/{tests → test}/loud/config/tls.ini +0 -0
- /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
- /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- /package/{tests → test}/queue/multibyte +0 -0
- /package/{tests → test}/queue/plain +0 -0
- /package/{tests → test}/queue/zero-length +0 -0
- /package/{tests → test}/test-queue/delete-me +0 -0
package/outbound/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const { Address } = require('address-rfc2821');
|
|
6
|
+
const { Address } = require('address-rfc2821');
|
|
8
7
|
const config = require('haraka-config');
|
|
9
8
|
const constants = require('haraka-constants');
|
|
10
9
|
const net_utils = require('haraka-net-utils');
|
|
@@ -24,21 +23,21 @@ const _qfile = exports.qfile = require('./qfile');
|
|
|
24
23
|
|
|
25
24
|
const { queue_dir, temp_fail_queue, delivery_queue } = queuelib;
|
|
26
25
|
|
|
26
|
+
const smtp_ini = config.get('smtp.ini', { booleans: [ '+headers.add_received' ] })
|
|
27
|
+
|
|
27
28
|
exports.temp_fail_queue = temp_fail_queue;
|
|
28
29
|
exports.delivery_queue = delivery_queue;
|
|
29
30
|
|
|
31
|
+
exports.name = 'outbound';
|
|
30
32
|
exports.net_utils = net_utils;
|
|
31
33
|
exports.config = config;
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
exports
|
|
38
|
-
|
|
39
|
-
exports.ensure_queue_dir = queuelib.ensure_queue_dir;
|
|
40
|
-
exports.load_queue = queuelib.load_queue;
|
|
41
|
-
exports.stats = queuelib.stats;
|
|
35
|
+
const qlfns = ['get_stats', 'list_queue', 'stat_queue', 'scan_queue_pids', 'flush_queue',
|
|
36
|
+
'load_pid_queue', 'ensure_queue_dir', 'load_queue', 'stats'
|
|
37
|
+
]
|
|
38
|
+
for (const n of qlfns) {
|
|
39
|
+
exports[n] = queuelib[n];
|
|
40
|
+
}
|
|
42
41
|
|
|
43
42
|
process.on('message', msg => {
|
|
44
43
|
if (!msg.event) return
|
|
@@ -52,40 +51,27 @@ process.on('message', msg => {
|
|
|
52
51
|
return;
|
|
53
52
|
}
|
|
54
53
|
if (msg.event === 'outbound.shutdown') {
|
|
55
|
-
logger.
|
|
54
|
+
logger.info(exports, "Shutting down temp fail queue");
|
|
56
55
|
temp_fail_queue.shutdown();
|
|
57
56
|
return;
|
|
58
57
|
}
|
|
59
58
|
// ignores the message
|
|
60
59
|
});
|
|
61
60
|
|
|
62
|
-
exports.send_email = function () {
|
|
61
|
+
exports.send_email = function (from, to, contents, next, options = {}) {
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
63
|
+
const dot_stuffed = options.dot_stuffed ?? false;
|
|
64
|
+
const notes = options.notes ?? null;
|
|
65
|
+
const origin = options.origin ?? exports;
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
let to = arguments[1];
|
|
71
|
-
let contents = arguments[2];
|
|
72
|
-
const next = arguments[3];
|
|
73
|
-
const options = arguments[4] || {};
|
|
67
|
+
logger.info("Sending email via params", origin);
|
|
74
68
|
|
|
75
|
-
const
|
|
76
|
-
const notes = options.notes ? options.notes : null;
|
|
77
|
-
const origin = options.origin ? options.origin : null;
|
|
69
|
+
const transaction = trans.createTransaction(null, smtp_ini);
|
|
78
70
|
|
|
79
|
-
logger.
|
|
71
|
+
logger.info(`Created transaction: ${transaction.uuid}`, origin);
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
logger.loginfo(`[outbound] Created transaction: ${transaction.uuid}`, origin);
|
|
84
|
-
|
|
85
|
-
//Adding notes passed as parameter
|
|
86
|
-
if (notes) {
|
|
87
|
-
transaction.notes = notes;
|
|
88
|
-
}
|
|
73
|
+
// Adding notes passed as parameter
|
|
74
|
+
if (notes) transaction.notes = notes;
|
|
89
75
|
|
|
90
76
|
// set MAIL FROM address, and parse if it's not an Address object
|
|
91
77
|
if (from instanceof Address) {
|
|
@@ -102,10 +88,7 @@ exports.send_email = function () {
|
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
// Make sure to is an array
|
|
105
|
-
if (!(Array.isArray(to)))
|
|
106
|
-
// turn into an array
|
|
107
|
-
to = [ to ];
|
|
108
|
-
}
|
|
91
|
+
if (!(Array.isArray(to))) to = [ to ];
|
|
109
92
|
|
|
110
93
|
if (to.length === 0) {
|
|
111
94
|
return next(constants.deny, "No recipients for email");
|
|
@@ -199,10 +182,10 @@ function get_deliveries (transaction) {
|
|
|
199
182
|
const deliveries = [];
|
|
200
183
|
|
|
201
184
|
if (obc.cfg.always_split) {
|
|
202
|
-
logger.
|
|
203
|
-
transaction.rcpt_to
|
|
185
|
+
logger.debug(exports, "always split");
|
|
186
|
+
for (const rcpt of transaction.rcpt_to) {
|
|
204
187
|
deliveries.push({domain: rcpt.host, rcpts: [ rcpt ]});
|
|
205
|
-
}
|
|
188
|
+
}
|
|
206
189
|
return deliveries;
|
|
207
190
|
}
|
|
208
191
|
|
|
@@ -223,11 +206,16 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
223
206
|
|
|
224
207
|
// add potentially missing headers
|
|
225
208
|
if (!transaction.header.get_all('Message-Id').length) {
|
|
226
|
-
logger.
|
|
209
|
+
logger.info(exports, "Adding missing Message-Id header");
|
|
210
|
+
transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`);
|
|
211
|
+
}
|
|
212
|
+
if (transaction.header.get('Message-Id') === '<>') {
|
|
213
|
+
logger.info(exports, "Replacing empty Message-Id header");
|
|
214
|
+
transaction.remove_header('Message-Id');
|
|
227
215
|
transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`);
|
|
228
216
|
}
|
|
229
217
|
if (!transaction.header.get_all('Date').length) {
|
|
230
|
-
logger.
|
|
218
|
+
logger.info(exports, "Adding missing Date header");
|
|
231
219
|
transaction.add_header('Date', utils.date_to_str(new Date()));
|
|
232
220
|
}
|
|
233
221
|
|
|
@@ -239,79 +227,80 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
239
227
|
|
|
240
228
|
logger.add_log_methods(connection);
|
|
241
229
|
if (!transaction.results) {
|
|
242
|
-
logger.
|
|
230
|
+
logger.debug(exports, 'adding results store');
|
|
243
231
|
transaction.results = new ResultStore(connection);
|
|
244
232
|
}
|
|
245
233
|
|
|
246
|
-
connection.pre_send_trans_email_respond = retval => {
|
|
234
|
+
connection.pre_send_trans_email_respond = async (retval) => {
|
|
247
235
|
const deliveries = get_deliveries(transaction);
|
|
248
236
|
const hmails = [];
|
|
249
237
|
const ok_paths = [];
|
|
250
238
|
|
|
251
239
|
let todo_index = 1;
|
|
252
240
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
(err) => {
|
|
260
|
-
if (err) {
|
|
261
|
-
for (let i=0, l=ok_paths.length; i<l; i++) {
|
|
262
|
-
fs.unlink(ok_paths[i], () => {});
|
|
263
|
-
}
|
|
264
|
-
transaction.results.add({ name: 'outbound'}, { err });
|
|
265
|
-
if (next) next(constants.denysoft, err);
|
|
266
|
-
return;
|
|
241
|
+
try {
|
|
242
|
+
for (const deliv of deliveries) {
|
|
243
|
+
const todo = new TODOItem(deliv.domain, deliv.rcpts, transaction);
|
|
244
|
+
todo.uuid = `${todo.uuid}.${todo_index}`;
|
|
245
|
+
todo_index++;
|
|
246
|
+
await this.process_delivery(ok_paths, todo, hmails);
|
|
267
247
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
for (let i=0, l=ok_paths.length; i<l; i++) {
|
|
251
|
+
fs.unlink(ok_paths[i], () => {});
|
|
271
252
|
}
|
|
253
|
+
transaction.results.add({ name: 'outbound'}, { err });
|
|
254
|
+
if (next) next(constants.denysoft, err);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
272
257
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
});
|
|
258
|
+
for (const hmail of hmails) {
|
|
259
|
+
delivery_queue.push(hmail);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
transaction.results.add({ name: 'outbound'}, { pass: "queued" });
|
|
263
|
+
if (next) next(constants.ok, `Message Queued (${transaction.uuid})`);
|
|
278
264
|
}
|
|
279
265
|
|
|
280
266
|
plugins.run_hooks('pre_send_trans_email', connection);
|
|
281
267
|
}
|
|
282
268
|
|
|
283
|
-
exports.process_delivery = function (ok_paths, todo, hmails
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
269
|
+
exports.process_delivery = function (ok_paths, todo, hmails) {
|
|
270
|
+
return new Promise((resolve, reject) => {
|
|
271
|
+
|
|
272
|
+
logger.info(exports, `Transaction delivery for domain: ${todo.domain}`);
|
|
273
|
+
const fname = _qfile.name();
|
|
274
|
+
const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`);
|
|
275
|
+
const ws = new FsyncWriteStream(tmp_path, { flags: constants.WRITE_EXCL });
|
|
276
|
+
|
|
277
|
+
ws.on('close', () => {
|
|
278
|
+
const dest_path = path.join(queue_dir, fname);
|
|
279
|
+
fs.rename(tmp_path, dest_path, err => {
|
|
280
|
+
if (err) {
|
|
281
|
+
logger.error(exports, `Unable to rename tmp file!: ${err}`);
|
|
282
|
+
fs.unlink(tmp_path, () => {});
|
|
283
|
+
reject("Queue error");
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
hmails.push(new HMailItem (fname, dest_path, todo.notes));
|
|
287
|
+
ok_paths.push(dest_path);
|
|
288
|
+
resolve();
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
})
|
|
288
292
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
fs.unlink(tmp_path, () => {});
|
|
295
|
-
cb("Queue error");
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
hmails.push(new HMailItem (fname, dest_path, todo.notes));
|
|
299
|
-
ok_paths.push(dest_path);
|
|
300
|
-
cb();
|
|
301
|
-
}
|
|
293
|
+
ws.on('error', err => {
|
|
294
|
+
logger.error(exports, `Unable to write queue file (${fname}): ${err}`);
|
|
295
|
+
ws.destroy();
|
|
296
|
+
fs.unlink(tmp_path, () => {});
|
|
297
|
+
reject("Queueing failed");
|
|
302
298
|
})
|
|
303
|
-
})
|
|
304
299
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
fs.unlink(tmp_path, () => {});
|
|
309
|
-
cb("Queueing failed");
|
|
300
|
+
this.build_todo(todo, ws, () => {
|
|
301
|
+
todo.message_stream.pipe(ws, { dot_stuffing: true });
|
|
302
|
+
});
|
|
310
303
|
})
|
|
311
|
-
|
|
312
|
-
this.build_todo(todo, ws, () => {
|
|
313
|
-
todo.message_stream.pipe(ws, { line_endings: '\r\n', dot_stuffing: true, ending_dot: false });
|
|
314
|
-
});
|
|
315
304
|
}
|
|
316
305
|
|
|
317
306
|
exports.build_todo = (todo, ws, write_more) => {
|
|
@@ -346,5 +335,3 @@ function exclude_from_json (key, value) {
|
|
|
346
335
|
exports.TODOItem = TODOItem;
|
|
347
336
|
|
|
348
337
|
exports.HMailItem = HMailItem;
|
|
349
|
-
|
|
350
|
-
exports.lookup_mx = require('./mx_lookup').lookup_mx;
|
package/outbound/qfile.js
CHANGED
package/outbound/queue.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
3
|
+
const child_process = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const async = require('async');
|
|
8
|
+
const { Address } = require('address-rfc2821');
|
|
8
9
|
const config = require('haraka-config');
|
|
9
10
|
|
|
10
11
|
const logger = require('../logger');
|
|
@@ -14,6 +15,8 @@ const obc = require('./config');
|
|
|
14
15
|
const _qfile = require('./qfile');
|
|
15
16
|
const obtls = require('./tls');
|
|
16
17
|
|
|
18
|
+
exports.name = 'outbound/queue';
|
|
19
|
+
|
|
17
20
|
let queue_dir;
|
|
18
21
|
if (config.get('queue_dir')) {
|
|
19
22
|
queue_dir = path.resolve(config.get('queue_dir'));
|
|
@@ -22,7 +25,7 @@ else if (process.env.HARAKA) {
|
|
|
22
25
|
queue_dir = path.resolve(process.env.HARAKA, 'queue');
|
|
23
26
|
}
|
|
24
27
|
else {
|
|
25
|
-
queue_dir = path.resolve('
|
|
28
|
+
queue_dir = path.resolve('test', 'test-queue');
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
exports.queue_dir = queue_dir;
|
|
@@ -72,45 +75,43 @@ exports.stat_queue = cb => {
|
|
|
72
75
|
exports.load_queue = pid => {
|
|
73
76
|
// Initialise and load queue
|
|
74
77
|
// This function is called first when not running under cluster,
|
|
75
|
-
// so we create the queue directory if it doesn't already exist.
|
|
76
78
|
exports.ensure_queue_dir();
|
|
77
79
|
exports.delete_dot_files();
|
|
78
80
|
|
|
79
81
|
exports._load_cur_queue(pid, exports._add_file, () => {
|
|
80
|
-
logger.
|
|
81
|
-
logger.
|
|
82
|
-
logger.
|
|
82
|
+
logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`);
|
|
83
|
+
logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`);
|
|
84
|
+
logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`);
|
|
83
85
|
});
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
exports._load_cur_queue = (pid, iteratee, cb) => {
|
|
87
|
-
|
|
88
|
-
logger.loginfo("[outbound] Loading outbound queue from ", queue_dir);
|
|
89
|
+
logger.info(exports, "Loading outbound queue from ", queue_dir);
|
|
89
90
|
fs.readdir(queue_dir, (err, files) => {
|
|
90
91
|
if (err) {
|
|
91
|
-
return logger.
|
|
92
|
+
return logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
this.cur_time = new Date(); // set once so we're not calling it a lot
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
this.load_queue_files(pid, files, iteratee, cb);
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
exports.read_parts = file => {
|
|
101
102
|
if (file.indexOf(_qfile.platformDOT) === 0) {
|
|
102
|
-
logger.
|
|
103
|
+
logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`);
|
|
103
104
|
return false;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
if (file.startsWith('error.')) {
|
|
107
|
-
logger.
|
|
108
|
+
logger.warn(exports, `'Skipping' error file in queue folder: ${file}`);
|
|
108
109
|
return false;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
const parts = _qfile.parts(file);
|
|
112
113
|
if (!parts) {
|
|
113
|
-
logger.
|
|
114
|
+
logger.error(exports, `Unrecognized file in queue folder: ${file}`);
|
|
114
115
|
return false;
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -140,11 +141,11 @@ exports._add_file = (file, cb) => {
|
|
|
140
141
|
const parts = _qfile.parts(file);
|
|
141
142
|
|
|
142
143
|
if (parts.next_attempt <= self.cur_time) {
|
|
143
|
-
logger.
|
|
144
|
+
logger.debug(exports, `File ${file} needs processing now`);
|
|
144
145
|
load_queue.push(file);
|
|
145
146
|
}
|
|
146
147
|
else {
|
|
147
|
-
logger.
|
|
148
|
+
logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`);
|
|
148
149
|
temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => { load_queue.push(file);});
|
|
149
150
|
}
|
|
150
151
|
|
|
@@ -159,10 +160,10 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
159
160
|
let stat_loaded = 0;
|
|
160
161
|
|
|
161
162
|
if (searchPid) {
|
|
162
|
-
logger.
|
|
163
|
+
logger.info(exports, `Grabbing queue files for pid: ${pid}`);
|
|
163
164
|
}
|
|
164
165
|
else {
|
|
165
|
-
logger.
|
|
166
|
+
logger.info(exports, "Loading the queue...");
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
async.map(input_files, (file, cb) => {
|
|
@@ -174,7 +175,7 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
174
175
|
|
|
175
176
|
self.rename_to_actual_pid(file, parts, (error, renamed_file) => {
|
|
176
177
|
if (error) {
|
|
177
|
-
logger.
|
|
178
|
+
logger.error(exports, `${error}`);
|
|
178
179
|
return cb();
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -189,16 +190,15 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
}, (err, results) => {
|
|
192
|
-
if (err) logger.
|
|
193
|
-
if (searchPid) logger.
|
|
194
|
-
logger.
|
|
193
|
+
if (err) logger.err(exports, `[pid: ${pid}] ${err}`);
|
|
194
|
+
if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`);
|
|
195
|
+
logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`);
|
|
195
196
|
|
|
196
197
|
async.map(results.filter((i) => i), iteratee, callback);
|
|
197
198
|
});
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
exports.stats = () => {
|
|
201
|
-
|
|
202
202
|
return {
|
|
203
203
|
queue_dir,
|
|
204
204
|
queue_count,
|
|
@@ -223,7 +223,7 @@ exports._list_file = (file, cb) => {
|
|
|
223
223
|
// we read everything
|
|
224
224
|
const todo_struct = JSON.parse(todo);
|
|
225
225
|
todo_struct.rcpt_to = todo_struct.rcpt_to.map(a => new Address (a));
|
|
226
|
-
todo_struct.mail_from = new Address
|
|
226
|
+
todo_struct.mail_from = new Address(todo_struct.mail_from);
|
|
227
227
|
todo_struct.file = file;
|
|
228
228
|
todo_struct.full_path = path.join(queue_dir, file);
|
|
229
229
|
const parts = _qfile.parts(file);
|
|
@@ -243,13 +243,13 @@ exports._list_file = (file, cb) => {
|
|
|
243
243
|
exports.flush_queue = (domain, pid) => {
|
|
244
244
|
if (domain) {
|
|
245
245
|
exports.list_queue((err, qlist) => {
|
|
246
|
-
if (err) return logger.
|
|
247
|
-
|
|
246
|
+
if (err) return logger.error(exports, `Failed to load queue: ${err}`);
|
|
247
|
+
for (const todo of qlist) {
|
|
248
248
|
if (todo.domain.toLowerCase() != domain.toLowerCase()) return;
|
|
249
249
|
if (pid && todo.pid != pid) return;
|
|
250
250
|
// console.log("requeue: ", todo);
|
|
251
251
|
delivery_queue.push(new HMailItem(todo.file, todo.full_path));
|
|
252
|
-
}
|
|
252
|
+
}
|
|
253
253
|
})
|
|
254
254
|
}
|
|
255
255
|
else {
|
|
@@ -258,36 +258,44 @@ exports.flush_queue = (domain, pid) => {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
exports.load_pid_queue = pid => {
|
|
261
|
-
logger.
|
|
261
|
+
logger.info(exports, `Loading queue for pid: ${pid}`);
|
|
262
262
|
exports.load_queue(pid);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
exports.ensure_queue_dir = () => {
|
|
266
|
-
// No reason to do this asynchronously
|
|
267
266
|
// this code is only run at start-up.
|
|
268
267
|
if (fs.existsSync(queue_dir)) return;
|
|
269
268
|
|
|
270
|
-
logger.
|
|
269
|
+
logger.debug(exports, `Creating queue directory ${queue_dir}`);
|
|
271
270
|
try {
|
|
272
271
|
fs.mkdirSync(queue_dir, 493); // 493 == 0755
|
|
272
|
+
const cfg = config.get('smtp.ini');
|
|
273
|
+
let uid
|
|
274
|
+
let gid
|
|
275
|
+
if (cfg.user) uid = child_process.execSync(`id -u ${cfg.user}`).toString().trim();
|
|
276
|
+
if (cfg.group) gid = child_process.execSync(`id -g ${cfg.group}`).toString().trim();
|
|
277
|
+
if (uid && gid) {
|
|
278
|
+
fs.chown(queue_dir, uid, gid)
|
|
279
|
+
}
|
|
280
|
+
else if (uid) {
|
|
281
|
+
fs.chown(queue_dir, uid)
|
|
282
|
+
}
|
|
273
283
|
}
|
|
274
284
|
catch (err) {
|
|
275
285
|
if (err.code !== 'EEXIST') {
|
|
276
|
-
logger.
|
|
286
|
+
logger.error(exports, `Error creating queue directory: ${err}`);
|
|
277
287
|
throw err;
|
|
278
288
|
}
|
|
279
289
|
}
|
|
280
290
|
}
|
|
281
291
|
|
|
282
292
|
exports.delete_dot_files = () => {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
files.forEach(file => {
|
|
293
|
+
for (const file of fs.readdirSync(queue_dir)) {
|
|
286
294
|
if (file.indexOf(_qfile.platformDOT) === 0) {
|
|
287
|
-
logger.
|
|
295
|
+
logger.warn(exports, `Removing left over dot-file: ${file}`);
|
|
288
296
|
return fs.unlinkSync(path.join(queue_dir, file));
|
|
289
297
|
}
|
|
290
|
-
}
|
|
298
|
+
}
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
exports._add_hmail = hmail => {
|
|
@@ -304,23 +312,22 @@ exports._add_hmail = hmail => {
|
|
|
304
312
|
exports.scan_queue_pids = cb => {
|
|
305
313
|
const self = exports;
|
|
306
314
|
|
|
307
|
-
// Under cluster, this is called first by the master
|
|
308
|
-
// we create the queue directory if it doesn't exist.
|
|
315
|
+
// Under cluster, this is called first by the master
|
|
309
316
|
self.ensure_queue_dir();
|
|
310
317
|
self.delete_dot_files();
|
|
311
318
|
|
|
312
319
|
fs.readdir(queue_dir, (err, files) => {
|
|
313
320
|
if (err) {
|
|
314
|
-
logger.
|
|
321
|
+
logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
|
|
315
322
|
return cb(err);
|
|
316
323
|
}
|
|
317
324
|
|
|
318
325
|
const pids = {};
|
|
319
326
|
|
|
320
|
-
|
|
327
|
+
for (const file of files) {
|
|
321
328
|
const parts = self.read_parts(file);
|
|
322
329
|
if (parts) pids[parts.pid] = true;
|
|
323
|
-
}
|
|
330
|
+
}
|
|
324
331
|
|
|
325
332
|
return cb(null, Object.keys(pids));
|
|
326
333
|
});
|
package/outbound/timer_queue.js
CHANGED
|
@@ -19,8 +19,10 @@ class TQTimer {
|
|
|
19
19
|
class TimerQueue {
|
|
20
20
|
|
|
21
21
|
constructor (interval = 1000) {
|
|
22
|
+
this.name = 'outbound/timer_queue'
|
|
22
23
|
this.queue = [];
|
|
23
24
|
this.interval_timer = setInterval(() => { this.fire(); }, interval);
|
|
25
|
+
this.interval_timer.unref() // allow server to exit
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
add (id, ms, cb) {
|
|
@@ -71,7 +73,7 @@ class TimerQueue {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
drain () {
|
|
74
|
-
logger.
|
|
76
|
+
logger.debug(this, `Draining ${this.queue.length} items from the queue`);
|
|
75
77
|
while (this.queue.length) {
|
|
76
78
|
const to_run = this.queue.shift();
|
|
77
79
|
if (to_run.cb) to_run.cb();
|
|
@@ -84,4 +86,3 @@ class TimerQueue {
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
module.exports = TimerQueue;
|
|
87
|
-
|
package/outbound/tls.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const net = require('node:net')
|
|
4
|
+
|
|
5
5
|
const config = require('haraka-config');
|
|
6
6
|
const hkredis = require('haraka-plugin-redis');
|
|
7
7
|
|
|
8
|
+
const logger = require('../logger');
|
|
9
|
+
const tls_socket = require('../tls_socket');
|
|
10
|
+
|
|
8
11
|
const inheritable_opts = [
|
|
9
12
|
'key', 'cert', 'ciphers', 'minVersion', 'dhparam',
|
|
10
13
|
'requestCert', 'honorCipherOrder', 'rejectUnauthorized',
|
|
@@ -62,14 +65,23 @@ class OutboundTLS {
|
|
|
62
65
|
this.load_config();
|
|
63
66
|
// changing this var in-flight won't work
|
|
64
67
|
if (this.cfg.redis && !this.cfg.redis.disable_for_failed_hosts) return cb();
|
|
65
|
-
logger.
|
|
68
|
+
logger.debug(this, 'Will disable outbound TLS for failing TLS hosts');
|
|
66
69
|
Object.assign(this, hkredis);
|
|
67
70
|
this.merge_redis_ini();
|
|
68
71
|
this.init_redis_plugin(cb);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
get_tls_options (mx) {
|
|
72
|
-
|
|
75
|
+
// do NOT set servername to an IP address
|
|
76
|
+
if (net.isIP(mx.exchange)) {
|
|
77
|
+
// when mx.exchange looked up in DNS, from_dns has the hostname
|
|
78
|
+
if (mx.from_dns) return { ...this.cfg, servername: mx.from_dns }
|
|
79
|
+
return { ...this.cfg }
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// mx.exchange is a hostname
|
|
83
|
+
return { ...this.cfg, servername: mx.exchange }
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
// Check for if host is prohibited from TLS negotiation
|
|
@@ -82,7 +94,7 @@ class OutboundTLS {
|
|
|
82
94
|
dbr ? cb_nogo(dbr) : cb_ok();
|
|
83
95
|
})
|
|
84
96
|
.catch(err => {
|
|
85
|
-
|
|
97
|
+
logger.debug(this, `Redis returned error: ${err}`);
|
|
86
98
|
cb_ok();
|
|
87
99
|
})
|
|
88
100
|
}
|
|
@@ -93,12 +105,12 @@ class OutboundTLS {
|
|
|
93
105
|
|
|
94
106
|
if (!this.cfg.redis.disable_for_failed_hosts) return cb();
|
|
95
107
|
|
|
96
|
-
logger.
|
|
108
|
+
logger.notice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`);
|
|
97
109
|
|
|
98
110
|
this.db.setEx(dbkey, expiry, (new Date()).toISOString())
|
|
99
111
|
.then(cb)
|
|
100
112
|
.catch(err => {
|
|
101
|
-
logger.
|
|
113
|
+
logger.error(this, `Redis returned error: ${err}`);
|
|
102
114
|
})
|
|
103
115
|
}
|
|
104
116
|
}
|