Haraka 3.0.2 → 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 +1393 -1211
- package/Dockerfile +3 -3
- package/Plugins.md +119 -106
- package/README.md +7 -16
- package/TODO +1 -24
- package/bin/haraka +197 -298
- package/config/auth_flat_file.ini +2 -0
- package/config/auth_vpopmaild.ini +4 -2
- 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/rabbitmq_amqplib.ini +8 -1
- 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/Connection.md +1 -1
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -210
- package/docs/Plugins.md +47 -40
- package/docs/Transaction.md +59 -82
- package/docs/{plugins → deprecated}/connect.rdns_access.md +1 -1
- package/docs/{plugins → deprecated}/mail_from.access.md +1 -1
- package/docs/{plugins → deprecated}/rcpt_to.access.md +1 -1
- package/docs/plugins/auth/auth_vpopmaild.md +15 -19
- package/docs/plugins/auth/flat_file.md +23 -30
- package/docs/plugins/queue/rabbitmq_amqplib.md +7 -0
- package/docs/plugins/queue/smtp_forward.md +1 -1
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/relay.md +2 -2
- 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 +180 -220
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +55 -43
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +66 -55
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +30 -12
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +30 -20
- package/plugins/auth/flat_file.js +17 -12
- 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/rabbitmq_amqplib.js +1 -1
- package/plugins/queue/smtp_forward.js +6 -6
- 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 +51 -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/{tests → test}/queue/multibyte +0 -0
- package/{tests → test}/queue/plain +0 -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 +9 -24
- 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/lookup_rdns.strict.ini +0 -12
- package/config/lookup_rdns.strict.timeout +0 -1
- package/config/lookup_rdns.strict.whitelist +0 -1
- package/config/lookup_rdns.strict.whitelist_regex +0 -5
- package/config/messagesniffer.ini +0 -18
- package/config/rcpt_to.blocklist +0 -1
- package/config/rdns.allow_regexps +0 -0
- package/config/rdns.deny_regexps +0 -0
- package/config/spamassassin.ini +0 -56
- package/config.js +0 -6
- 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/relay_acl.md +0 -29
- package/docs/plugins/relay_all.md +0 -15
- package/docs/plugins/relay_force_routing.md +0 -33
- 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 -381
- package/plugins/data.headers.js +0 -4
- 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/relay_all.js +0 -13
- 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/docs/{plugins → deprecated}/rcpt_to.routes.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/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,40 +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}`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (file.startsWith('error.')) {
|
|
108
|
+
logger.warn(exports, `'Skipping' error file in queue folder: ${file}`);
|
|
103
109
|
return false;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
const parts = _qfile.parts(file);
|
|
107
113
|
if (!parts) {
|
|
108
|
-
logger.
|
|
114
|
+
logger.error(exports, `Unrecognized file in queue folder: ${file}`);
|
|
109
115
|
return false;
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -135,11 +141,11 @@ exports._add_file = (file, cb) => {
|
|
|
135
141
|
const parts = _qfile.parts(file);
|
|
136
142
|
|
|
137
143
|
if (parts.next_attempt <= self.cur_time) {
|
|
138
|
-
logger.
|
|
144
|
+
logger.debug(exports, `File ${file} needs processing now`);
|
|
139
145
|
load_queue.push(file);
|
|
140
146
|
}
|
|
141
147
|
else {
|
|
142
|
-
logger.
|
|
148
|
+
logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`);
|
|
143
149
|
temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => { load_queue.push(file);});
|
|
144
150
|
}
|
|
145
151
|
|
|
@@ -154,10 +160,10 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
154
160
|
let stat_loaded = 0;
|
|
155
161
|
|
|
156
162
|
if (searchPid) {
|
|
157
|
-
logger.
|
|
163
|
+
logger.info(exports, `Grabbing queue files for pid: ${pid}`);
|
|
158
164
|
}
|
|
159
165
|
else {
|
|
160
|
-
logger.
|
|
166
|
+
logger.info(exports, "Loading the queue...");
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
async.map(input_files, (file, cb) => {
|
|
@@ -169,7 +175,7 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
169
175
|
|
|
170
176
|
self.rename_to_actual_pid(file, parts, (error, renamed_file) => {
|
|
171
177
|
if (error) {
|
|
172
|
-
logger.
|
|
178
|
+
logger.error(exports, `${error}`);
|
|
173
179
|
return cb();
|
|
174
180
|
}
|
|
175
181
|
|
|
@@ -184,16 +190,15 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
}, (err, results) => {
|
|
187
|
-
if (err) logger.
|
|
188
|
-
if (searchPid) logger.
|
|
189
|
-
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`);
|
|
190
196
|
|
|
191
197
|
async.map(results.filter((i) => i), iteratee, callback);
|
|
192
198
|
});
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
exports.stats = () => {
|
|
196
|
-
|
|
197
202
|
return {
|
|
198
203
|
queue_dir,
|
|
199
204
|
queue_count,
|
|
@@ -218,7 +223,7 @@ exports._list_file = (file, cb) => {
|
|
|
218
223
|
// we read everything
|
|
219
224
|
const todo_struct = JSON.parse(todo);
|
|
220
225
|
todo_struct.rcpt_to = todo_struct.rcpt_to.map(a => new Address (a));
|
|
221
|
-
todo_struct.mail_from = new Address
|
|
226
|
+
todo_struct.mail_from = new Address(todo_struct.mail_from);
|
|
222
227
|
todo_struct.file = file;
|
|
223
228
|
todo_struct.full_path = path.join(queue_dir, file);
|
|
224
229
|
const parts = _qfile.parts(file);
|
|
@@ -238,13 +243,13 @@ exports._list_file = (file, cb) => {
|
|
|
238
243
|
exports.flush_queue = (domain, pid) => {
|
|
239
244
|
if (domain) {
|
|
240
245
|
exports.list_queue((err, qlist) => {
|
|
241
|
-
if (err) return logger.
|
|
242
|
-
|
|
246
|
+
if (err) return logger.error(exports, `Failed to load queue: ${err}`);
|
|
247
|
+
for (const todo of qlist) {
|
|
243
248
|
if (todo.domain.toLowerCase() != domain.toLowerCase()) return;
|
|
244
249
|
if (pid && todo.pid != pid) return;
|
|
245
250
|
// console.log("requeue: ", todo);
|
|
246
251
|
delivery_queue.push(new HMailItem(todo.file, todo.full_path));
|
|
247
|
-
}
|
|
252
|
+
}
|
|
248
253
|
})
|
|
249
254
|
}
|
|
250
255
|
else {
|
|
@@ -253,36 +258,44 @@ exports.flush_queue = (domain, pid) => {
|
|
|
253
258
|
}
|
|
254
259
|
|
|
255
260
|
exports.load_pid_queue = pid => {
|
|
256
|
-
logger.
|
|
261
|
+
logger.info(exports, `Loading queue for pid: ${pid}`);
|
|
257
262
|
exports.load_queue(pid);
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
exports.ensure_queue_dir = () => {
|
|
261
|
-
// No reason to do this asynchronously
|
|
262
266
|
// this code is only run at start-up.
|
|
263
267
|
if (fs.existsSync(queue_dir)) return;
|
|
264
268
|
|
|
265
|
-
logger.
|
|
269
|
+
logger.debug(exports, `Creating queue directory ${queue_dir}`);
|
|
266
270
|
try {
|
|
267
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
|
+
}
|
|
268
283
|
}
|
|
269
284
|
catch (err) {
|
|
270
285
|
if (err.code !== 'EEXIST') {
|
|
271
|
-
logger.
|
|
286
|
+
logger.error(exports, `Error creating queue directory: ${err}`);
|
|
272
287
|
throw err;
|
|
273
288
|
}
|
|
274
289
|
}
|
|
275
290
|
}
|
|
276
291
|
|
|
277
292
|
exports.delete_dot_files = () => {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
files.forEach(file => {
|
|
293
|
+
for (const file of fs.readdirSync(queue_dir)) {
|
|
281
294
|
if (file.indexOf(_qfile.platformDOT) === 0) {
|
|
282
|
-
logger.
|
|
295
|
+
logger.warn(exports, `Removing left over dot-file: ${file}`);
|
|
283
296
|
return fs.unlinkSync(path.join(queue_dir, file));
|
|
284
297
|
}
|
|
285
|
-
}
|
|
298
|
+
}
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
exports._add_hmail = hmail => {
|
|
@@ -299,23 +312,22 @@ exports._add_hmail = hmail => {
|
|
|
299
312
|
exports.scan_queue_pids = cb => {
|
|
300
313
|
const self = exports;
|
|
301
314
|
|
|
302
|
-
// Under cluster, this is called first by the master
|
|
303
|
-
// we create the queue directory if it doesn't exist.
|
|
315
|
+
// Under cluster, this is called first by the master
|
|
304
316
|
self.ensure_queue_dir();
|
|
305
317
|
self.delete_dot_files();
|
|
306
318
|
|
|
307
319
|
fs.readdir(queue_dir, (err, files) => {
|
|
308
320
|
if (err) {
|
|
309
|
-
logger.
|
|
321
|
+
logger.error(exports, `Failed to load queue directory (${queue_dir}): ${err}`);
|
|
310
322
|
return cb(err);
|
|
311
323
|
}
|
|
312
324
|
|
|
313
325
|
const pids = {};
|
|
314
326
|
|
|
315
|
-
|
|
327
|
+
for (const file of files) {
|
|
316
328
|
const parts = self.read_parts(file);
|
|
317
329
|
if (parts) pids[parts.pid] = true;
|
|
318
|
-
}
|
|
330
|
+
}
|
|
319
331
|
|
|
320
332
|
return cb(null, Object.keys(pids));
|
|
321
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
|
}
|