haraka 0.0.33 → 3.3.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/.githooks/pre-commit +41 -0
- package/.prettierignore +7 -0
- package/.qlty/.gitignore +7 -0
- package/.qlty/configs/.shellcheckrc +1 -0
- package/.qlty/qlty.toml +15 -0
- package/CHANGELOG.md +1898 -0
- package/CONTRIBUTORS.md +34 -0
- package/Dockerfile +50 -0
- package/LICENSE +22 -0
- package/Plugins.md +227 -0
- package/README.md +119 -4
- package/SECURITY.md +178 -0
- package/TODO +22 -0
- package/bin/haraka +593 -0
- package/bin/haraka_grep +32 -0
- package/config/aliases +2 -0
- package/config/auth_flat_file.ini +7 -0
- package/config/auth_vpopmaild.ini +9 -0
- package/config/connection.ini +79 -0
- package/config/delay_deny.ini +7 -0
- package/config/host_list +3 -0
- package/config/host_list_regex +6 -0
- package/config/http.ini +11 -0
- package/config/lmtp.ini +7 -0
- package/config/log.ini +11 -0
- package/config/outbound.bounce_message +18 -0
- package/config/outbound.bounce_message_html +36 -0
- package/config/outbound.bounce_message_image +106 -0
- package/config/outbound.ini +24 -0
- package/config/plugins +67 -0
- package/config/smtp.ini +37 -0
- package/config/smtp_bridge.ini +4 -0
- package/config/smtp_forward.ini +31 -0
- package/config/smtp_proxy.ini +27 -0
- package/config/tarpit.timeout +1 -0
- package/config/tls.ini +83 -0
- package/config/watch.ini +12 -0
- package/config/xclient.hosts +2 -0
- package/connection.js +1865 -0
- package/contrib/Haraka.cf +6 -0
- package/contrib/Haraka.pm +35 -0
- package/contrib/bad_smtp_server.pl +25 -0
- package/contrib/bsd-rc.d/haraka +63 -0
- package/contrib/debian-init.d/haraka +87 -0
- package/contrib/haraka.init +96 -0
- package/contrib/haraka.service +23 -0
- package/contrib/plugin2npm.sh +81 -0
- package/contrib/ubuntu-upstart/haraka.conf +27 -0
- package/docs/Body.md +1 -0
- package/docs/Config.md +1 -0
- package/docs/Connection.md +153 -0
- package/docs/CoreConfig.md +96 -0
- package/docs/CustomReturnCodes.md +3 -0
- package/docs/HAProxy.md +62 -0
- package/docs/Header.md +1 -0
- package/docs/Logging.md +129 -0
- package/docs/Outbound.md +210 -0
- package/docs/Plugins.md +372 -0
- package/docs/Results.md +7 -0
- package/docs/Transaction.md +135 -0
- package/docs/Tutorial.md +183 -0
- package/docs/deprecated/access.md +3 -0
- package/docs/deprecated/backscatterer.md +9 -0
- package/docs/deprecated/connect.rdns_access.md +53 -0
- package/docs/deprecated/data.headers.md +3 -0
- package/docs/deprecated/data.nomsgid.md +7 -0
- package/docs/deprecated/data.noreceived.md +11 -0
- package/docs/deprecated/data.rfc5322_header_checks.md +11 -0
- package/docs/deprecated/dkim_sign.md +97 -0
- package/docs/deprecated/dkim_verify.md +28 -0
- package/docs/deprecated/dnsbl.md +80 -0
- package/docs/deprecated/dnswl.md +73 -0
- package/docs/deprecated/lookup_rdns.strict.md +67 -0
- package/docs/deprecated/mail_from.access.md +52 -0
- package/docs/deprecated/mail_from.blocklist.md +18 -0
- package/docs/deprecated/mail_from.nobounces.md +8 -0
- package/docs/deprecated/rcpt_to.access.md +53 -0
- package/docs/deprecated/rcpt_to.blocklist.md +18 -0
- package/docs/deprecated/rcpt_to.routes.md +3 -0
- package/docs/deprecated/rdns.regexp.md +30 -0
- package/docs/plugins/aliases.md +3 -0
- package/docs/plugins/auth/auth_bridge.md +34 -0
- package/docs/plugins/auth/auth_ldap.md +4 -0
- package/docs/plugins/auth/auth_proxy.md +36 -0
- package/docs/plugins/auth/auth_vpopmaild.md +33 -0
- package/docs/plugins/auth/flat_file.md +40 -0
- package/docs/plugins/block_me.md +18 -0
- package/docs/plugins/data.signatures.md +11 -0
- package/docs/plugins/delay_deny.md +23 -0
- package/docs/plugins/max_unrecognized_commands.md +6 -0
- package/docs/plugins/prevent_credential_leaks.md +22 -0
- package/docs/plugins/process_title.md +42 -0
- package/docs/plugins/queue/deliver.md +3 -0
- package/docs/plugins/queue/discard.md +32 -0
- package/docs/plugins/queue/lmtp.md +24 -0
- package/docs/plugins/queue/qmail-queue.md +16 -0
- package/docs/plugins/queue/quarantine.md +87 -0
- package/docs/plugins/queue/smtp_bridge.md +32 -0
- package/docs/plugins/queue/smtp_forward.md +127 -0
- package/docs/plugins/queue/smtp_proxy.md +68 -0
- package/docs/plugins/queue/test.md +7 -0
- package/docs/plugins/rcpt_to.in_host_list.md +34 -0
- package/docs/plugins/rcpt_to.max_count.md +3 -0
- package/docs/plugins/record_envelope_addresses.md +20 -0
- package/docs/plugins/relay.md +3 -0
- package/docs/plugins/reseed_rng.md +16 -0
- package/docs/plugins/status.md +41 -0
- package/docs/plugins/tarpit.md +50 -0
- package/docs/plugins/tls.md +235 -0
- package/docs/plugins/toobusy.md +27 -0
- package/docs/plugins/xclient.md +10 -0
- package/docs/tutorials/Migrating_from_v1_to_v2.md +96 -0
- package/docs/tutorials/SettingUpOutbound.md +62 -0
- package/eslint.config.mjs +2 -0
- package/haraka.js +74 -0
- package/haraka.sh +2 -0
- package/http/html/404.html +58 -0
- package/http/html/index.html +47 -0
- package/http/package.json +21 -0
- package/line_socket.js +24 -0
- package/logger.js +322 -0
- package/outbound/client_pool.js +59 -0
- package/outbound/config.js +134 -0
- package/outbound/hmail.js +1504 -0
- package/outbound/index.js +349 -0
- package/outbound/qfile.js +93 -0
- package/outbound/queue.js +399 -0
- package/outbound/tls.js +85 -0
- package/outbound/todo.js +17 -0
- package/package.json +100 -4
- package/plugins/.eslintrc.yaml +3 -0
- package/plugins/auth/auth_base.js +261 -0
- package/plugins/auth/auth_bridge.js +20 -0
- package/plugins/auth/auth_proxy.js +227 -0
- package/plugins/auth/auth_vpopmaild.js +162 -0
- package/plugins/auth/flat_file.js +44 -0
- package/plugins/block_me.js +88 -0
- package/plugins/data.signatures.js +30 -0
- package/plugins/delay_deny.js +153 -0
- package/plugins/prevent_credential_leaks.js +61 -0
- package/plugins/process_title.js +197 -0
- package/plugins/profile.js +11 -0
- package/plugins/queue/deliver.js +12 -0
- package/plugins/queue/discard.js +27 -0
- package/plugins/queue/lmtp.js +45 -0
- package/plugins/queue/qmail-queue.js +93 -0
- package/plugins/queue/quarantine.js +133 -0
- package/plugins/queue/smtp_bridge.js +45 -0
- package/plugins/queue/smtp_forward.js +371 -0
- package/plugins/queue/smtp_proxy.js +142 -0
- package/plugins/queue/test.js +15 -0
- package/plugins/rcpt_to.host_list_base.js +65 -0
- package/plugins/rcpt_to.in_host_list.js +56 -0
- package/plugins/record_envelope_addresses.js +17 -0
- package/plugins/reseed_rng.js +7 -0
- package/plugins/status.js +274 -0
- package/plugins/tarpit.js +45 -0
- package/plugins/tls.js +164 -0
- package/plugins/toobusy.js +47 -0
- package/plugins/xclient.js +124 -0
- package/plugins.js +605 -0
- package/run_tests +11 -0
- package/server.js +827 -0
- package/smtp_client.js +504 -0
- package/test/.eslintrc.yaml +11 -0
- package/test/config/auth_flat_file.ini +5 -0
- package/test/config/block_me.recipient +1 -0
- package/test/config/block_me.senders +1 -0
- package/test/config/dhparams.pem +8 -0
- package/test/config/host_list +2 -0
- package/test/config/outbound_tls_cert.pem +1 -0
- package/test/config/outbound_tls_key.pem +1 -0
- package/test/config/plugins +7 -0
- package/test/config/smtp.ini +11 -0
- package/test/config/smtp_forward.ini +30 -0
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/config/tls/haraka.local.pem +51 -0
- package/test/config/tls.ini +45 -0
- package/test/config/tls_cert.pem +21 -0
- package/test/config/tls_key.pem +28 -0
- package/test/connection.js +820 -0
- package/test/fixtures/haproxy_allowed/config/connection.ini +3 -0
- package/test/fixtures/haproxy_disabled/config/connection.ini +3 -0
- package/test/fixtures/haproxy_untrusted/config/connection.ini +3 -0
- package/test/fixtures/line_socket.js +21 -0
- package/test/fixtures/todo_qfile.txt +0 -0
- package/test/fixtures/util_hmailitem.js +156 -0
- package/test/installation/config/test-plugin-flat +1 -0
- package/test/installation/config/test-plugin.ini +10 -0
- package/test/installation/config/tls.ini +1 -0
- package/test/installation/node_modules/load_first/index.js +5 -0
- package/test/installation/node_modules/load_first/package.json +11 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin-flat +1 -0
- package/test/installation/node_modules/test-plugin/config/test-plugin.ini +9 -0
- package/test/installation/node_modules/test-plugin/package.json +5 -0
- package/test/installation/node_modules/test-plugin/test-plugin.js +5 -0
- package/test/installation/plugins/base_plugin.js +3 -0
- package/test/installation/plugins/folder_plugin/index.js +3 -0
- package/test/installation/plugins/folder_plugin/package.json +11 -0
- package/test/installation/plugins/inherits.js +7 -0
- package/test/installation/plugins/load_first.js +3 -0
- package/test/installation/plugins/plugin.js +1 -0
- package/test/installation/plugins/tls.js +3 -0
- package/test/logger.js +217 -0
- package/test/loud/config/dhparams.pem +0 -0
- package/test/loud/config/tls/goobered.pem +45 -0
- package/test/loud/config/tls.ini +43 -0
- package/test/mail_specimen/base64-root-part.txt +23 -0
- package/test/mail_specimen/varied-fold-lengths-preserve-data.txt +283 -0
- package/test/outbound/bounce_net_errors.js +133 -0
- package/test/outbound/bounce_rfc3464.js +226 -0
- package/test/outbound/hmail.js +210 -0
- package/test/outbound/index.js +385 -0
- package/test/outbound/qfile.js +124 -0
- package/test/outbound/queue.js +325 -0
- package/test/plugins/auth/auth_base.js +620 -0
- package/test/plugins/auth/auth_bridge.js +80 -0
- package/test/plugins/auth/auth_vpopmaild.js +81 -0
- package/test/plugins/auth/flat_file.js +123 -0
- package/test/plugins/block_me.js +141 -0
- package/test/plugins/data.signatures.js +111 -0
- package/test/plugins/delay_deny.js +262 -0
- package/test/plugins/prevent_credential_leaks.js +174 -0
- package/test/plugins/process_title.js +141 -0
- package/test/plugins/queue/deliver.js +98 -0
- package/test/plugins/queue/discard.js +78 -0
- package/test/plugins/queue/lmtp.js +137 -0
- package/test/plugins/queue/qmail-queue.js +98 -0
- package/test/plugins/queue/quarantine.js +80 -0
- package/test/plugins/queue/smtp_bridge.js +152 -0
- package/test/plugins/queue/smtp_forward.js +1023 -0
- package/test/plugins/queue/smtp_proxy.js +138 -0
- package/test/plugins/rcpt_to.host_list_base.js +102 -0
- package/test/plugins/rcpt_to.in_host_list.js +186 -0
- package/test/plugins/record_envelope_addresses.js +66 -0
- package/test/plugins/reseed_rng.js +34 -0
- package/test/plugins/status.js +207 -0
- package/test/plugins/tarpit.js +90 -0
- package/test/plugins/tls.js +86 -0
- package/test/plugins/toobusy.js +198 -0
- package/test/plugins/xclient.js +119 -0
- package/test/plugins.js +230 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- package/test/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- package/test/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- package/test/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- package/test/queue/zero-length +0 -0
- package/test/server.js +1012 -0
- package/test/smtp_client.js +1303 -0
- package/test/tls_socket.js +321 -0
- package/test/transaction.js +554 -0
- package/tls_socket.js +771 -0
- package/transaction.js +267 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const { Address } = require('@haraka/email-address')
|
|
7
|
+
const config = require('haraka-config')
|
|
8
|
+
const constants = require('haraka-constants')
|
|
9
|
+
const net_utils = require('haraka-net-utils')
|
|
10
|
+
const utils = require('haraka-utils')
|
|
11
|
+
const ResultStore = require('haraka-results')
|
|
12
|
+
|
|
13
|
+
const logger = require('../logger')
|
|
14
|
+
const trans = require('../transaction')
|
|
15
|
+
const plugins = require('../plugins')
|
|
16
|
+
const FsyncWriteStream = utils.FsyncWriteStream
|
|
17
|
+
|
|
18
|
+
const obc = require('./config')
|
|
19
|
+
const queuelib = require('./queue')
|
|
20
|
+
const HMailItem = require('./hmail')
|
|
21
|
+
const TODOItem = require('./todo')
|
|
22
|
+
const _qfile = (exports.qfile = require('./qfile'))
|
|
23
|
+
|
|
24
|
+
const { queue_dir, temp_fail_queue, delivery_queue } = queuelib
|
|
25
|
+
|
|
26
|
+
const smtp_ini = config.get('smtp.ini', {
|
|
27
|
+
booleans: ['+headers.add_received'],
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
exports.temp_fail_queue = temp_fail_queue
|
|
31
|
+
exports.delivery_queue = delivery_queue
|
|
32
|
+
|
|
33
|
+
exports.name = 'outbound'
|
|
34
|
+
exports.net_utils = net_utils
|
|
35
|
+
exports.config = config
|
|
36
|
+
|
|
37
|
+
const qlfns = [
|
|
38
|
+
'get_stats',
|
|
39
|
+
'list_queue',
|
|
40
|
+
'stat_queue',
|
|
41
|
+
'scan_queue_pids',
|
|
42
|
+
'flush_queue',
|
|
43
|
+
'load_pid_queue',
|
|
44
|
+
'ensure_queue_dir',
|
|
45
|
+
'init_queue',
|
|
46
|
+
'stats',
|
|
47
|
+
]
|
|
48
|
+
for (const n of qlfns) {
|
|
49
|
+
exports[n] = queuelib[n]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process.on('message', async (msg) => {
|
|
53
|
+
if (!msg.event) return
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
if (msg.event === 'outbound.load_pid_queue') {
|
|
57
|
+
await exports.load_pid_queue(msg.data)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (msg.event === 'outbound.flush_queue') {
|
|
61
|
+
await exports.flush_queue(msg.domain, process.pid)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if (msg.event === 'outbound.shutdown') {
|
|
65
|
+
logger.info(exports, 'Shutting down temp fail queue')
|
|
66
|
+
temp_fail_queue.shutdown()
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
// ignores the message
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error(exports, err)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
exports.send_email = function (from, to, contents, next, options = {}) {
|
|
77
|
+
const dot_stuffed = options.dot_stuffed ?? false
|
|
78
|
+
const notes = options.notes ?? null
|
|
79
|
+
const origin = options.origin ?? exports
|
|
80
|
+
|
|
81
|
+
logger.info('Sending email via params', origin)
|
|
82
|
+
|
|
83
|
+
const transaction = trans.createTransaction(null, smtp_ini)
|
|
84
|
+
|
|
85
|
+
logger.info(`Created transaction: ${transaction.uuid}`, origin)
|
|
86
|
+
|
|
87
|
+
// Adding notes passed as parameter
|
|
88
|
+
if (notes) transaction.notes = notes
|
|
89
|
+
|
|
90
|
+
// set MAIL FROM address, and parse if it's not an Address object
|
|
91
|
+
if (from instanceof Address) {
|
|
92
|
+
transaction.mail_from = from
|
|
93
|
+
} else {
|
|
94
|
+
try {
|
|
95
|
+
from = new Address(from)
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return next(constants.deny, `Malformed from: ${err}`)
|
|
98
|
+
}
|
|
99
|
+
transaction.mail_from = from
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Make sure to is an array
|
|
103
|
+
if (!Array.isArray(to)) to = [to]
|
|
104
|
+
|
|
105
|
+
if (to.length === 0) {
|
|
106
|
+
return next(constants.deny, 'No recipients for email')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Set RCPT TO's, and parse each if it's not an Address object.
|
|
110
|
+
for (let i = 0, l = to.length; i < l; i++) {
|
|
111
|
+
if (!(to[i] instanceof Address)) {
|
|
112
|
+
try {
|
|
113
|
+
to[i] = new Address(to[i])
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return next(constants.deny, `Malformed to address (${to[i]}): ${err}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
transaction.rcpt_to = to
|
|
121
|
+
|
|
122
|
+
// Set data_lines to lines in contents
|
|
123
|
+
if (typeof contents == 'string') {
|
|
124
|
+
let match
|
|
125
|
+
while ((match = utils.line_regexp.exec(contents))) {
|
|
126
|
+
let line = match[1]
|
|
127
|
+
line = line.replace(/\r?\n?$/, '\r\n') // make sure it ends in \r\n
|
|
128
|
+
if (dot_stuffed === false && line.length >= 3 && line.substring(0, 1) === '.') {
|
|
129
|
+
line = `.${line}`
|
|
130
|
+
}
|
|
131
|
+
transaction.add_data(Buffer.from(line))
|
|
132
|
+
contents = contents.substring(match[1].length)
|
|
133
|
+
if (contents.length === 0) {
|
|
134
|
+
break
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// Assume a stream
|
|
139
|
+
return stream_line_reader(contents, transaction, (err) => {
|
|
140
|
+
if (err) {
|
|
141
|
+
return next(constants.denysoft, `Error from stream line reader: ${err}`)
|
|
142
|
+
}
|
|
143
|
+
exports.send_trans_email(transaction, next)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
transaction.message_stream.add_line_end()
|
|
148
|
+
|
|
149
|
+
// Allow for the removal of Message-Id and/or Date headers which
|
|
150
|
+
// is useful when resending mail from a quarantine.
|
|
151
|
+
if (options.remove_msgid) {
|
|
152
|
+
transaction.remove_header('Message-Id')
|
|
153
|
+
}
|
|
154
|
+
if (options.remove_date) {
|
|
155
|
+
transaction.remove_header('Date')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.send_trans_email(transaction, next)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function stream_line_reader(stream, transaction, cb) {
|
|
162
|
+
let current_data = ''
|
|
163
|
+
function process_data(data) {
|
|
164
|
+
current_data += data.toString()
|
|
165
|
+
let results
|
|
166
|
+
while ((results = utils.line_regexp.exec(current_data))) {
|
|
167
|
+
const this_line = results[1]
|
|
168
|
+
current_data = current_data.slice(this_line.length)
|
|
169
|
+
if (!(current_data.length || this_line.length)) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
transaction.add_data(Buffer.from(this_line))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function process_end() {
|
|
177
|
+
if (current_data.length) {
|
|
178
|
+
transaction.add_data(Buffer.from(current_data))
|
|
179
|
+
}
|
|
180
|
+
current_data = ''
|
|
181
|
+
transaction.message_stream.add_line_end()
|
|
182
|
+
cb()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
stream.on('data', process_data)
|
|
186
|
+
stream.once('end', process_end)
|
|
187
|
+
stream.once('error', cb)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function get_deliveries(transaction) {
|
|
191
|
+
const deliveries = []
|
|
192
|
+
|
|
193
|
+
if (obc.cfg.always_split) {
|
|
194
|
+
logger.debug(exports, 'always split')
|
|
195
|
+
for (const rcpt of transaction.rcpt_to) {
|
|
196
|
+
deliveries.push({ domain: rcpt.host, rcpts: [rcpt] })
|
|
197
|
+
}
|
|
198
|
+
return deliveries
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// First get each domain
|
|
202
|
+
const recips = {}
|
|
203
|
+
for (const rcpt of transaction.rcpt_to) {
|
|
204
|
+
const domain = rcpt.host
|
|
205
|
+
if (!recips[domain]) {
|
|
206
|
+
recips[domain] = []
|
|
207
|
+
}
|
|
208
|
+
recips[domain].push(rcpt)
|
|
209
|
+
}
|
|
210
|
+
for (const domain of Object.keys(recips)) {
|
|
211
|
+
deliveries.push({ domain, rcpts: recips[domain] })
|
|
212
|
+
}
|
|
213
|
+
return deliveries
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
exports.send_trans_email = function (transaction, next) {
|
|
217
|
+
// add potentially missing headers
|
|
218
|
+
if (!transaction.header.get_all('Message-Id').length) {
|
|
219
|
+
logger.info(exports, 'Adding missing Message-Id header')
|
|
220
|
+
transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`)
|
|
221
|
+
}
|
|
222
|
+
if (transaction.header.get('Message-Id') === '<>') {
|
|
223
|
+
logger.info(exports, 'Replacing empty Message-Id header')
|
|
224
|
+
transaction.remove_header('Message-Id')
|
|
225
|
+
transaction.add_header('Message-Id', `<${transaction.uuid}@${net_utils.get_primary_host_name()}>`)
|
|
226
|
+
}
|
|
227
|
+
if (!transaction.header.get_all('Date').length) {
|
|
228
|
+
logger.info(exports, 'Adding missing Date header')
|
|
229
|
+
transaction.add_header('Date', utils.date_to_str(new Date()))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (obc.cfg.received_header !== 'disabled') {
|
|
233
|
+
transaction.add_leading_header('Received', `(${obc.cfg.received_header}); ${utils.date_to_str(new Date())}`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const connection = { transaction }
|
|
237
|
+
|
|
238
|
+
logger.add_log_methods(connection)
|
|
239
|
+
if (!transaction.results) {
|
|
240
|
+
logger.debug(exports, 'adding results store')
|
|
241
|
+
transaction.results = new ResultStore(connection)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
connection.pre_send_trans_email_respond = async () => {
|
|
245
|
+
const deliveries = get_deliveries(transaction)
|
|
246
|
+
const hmails = []
|
|
247
|
+
const ok_paths = []
|
|
248
|
+
|
|
249
|
+
let todo_index = 1
|
|
250
|
+
|
|
251
|
+
// See haraka/Haraka#3551
|
|
252
|
+
await new Promise((resolve) => setImmediate(resolve))
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
for (const deliv of deliveries) {
|
|
256
|
+
const todo = new TODOItem(deliv.domain, deliv.rcpts, transaction)
|
|
257
|
+
todo.uuid = `${todo.uuid}.${todo_index}`
|
|
258
|
+
todo_index++
|
|
259
|
+
await this.process_delivery(ok_paths, todo, hmails)
|
|
260
|
+
}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
for (let i = 0, l = ok_paths.length; i < l; i++) {
|
|
263
|
+
await fs.unlink(ok_paths[i]).catch(() => {})
|
|
264
|
+
}
|
|
265
|
+
transaction.results.add({ name: 'outbound' }, { err })
|
|
266
|
+
if (next) next(constants.denysoft, err)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const hmail of hmails) {
|
|
271
|
+
delivery_queue.push(hmail)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
transaction.results.add({ name: 'outbound' }, { pass: 'queued' })
|
|
275
|
+
if (next) next(constants.ok, `Message Queued (${transaction.uuid})`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
plugins.run_hooks('pre_send_trans_email', connection)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
exports.process_delivery = function (ok_paths, todo, hmails) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
logger.info(exports, `Transaction delivery for domain: ${todo.domain}`)
|
|
284
|
+
const fname = _qfile.name()
|
|
285
|
+
const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`)
|
|
286
|
+
const ws = new FsyncWriteStream(tmp_path, {
|
|
287
|
+
flags: constants.WRITE_EXCL,
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
ws.on('close', async () => {
|
|
291
|
+
const dest_path = path.join(queue_dir, fname)
|
|
292
|
+
try {
|
|
293
|
+
await fs.rename(tmp_path, dest_path)
|
|
294
|
+
hmails.push(new HMailItem(fname, dest_path, todo.notes))
|
|
295
|
+
ok_paths.push(dest_path)
|
|
296
|
+
resolve()
|
|
297
|
+
} catch (err) {
|
|
298
|
+
logger.error(exports, `Unable to rename tmp file: ${err}`)
|
|
299
|
+
await fs.unlink(tmp_path).catch(() => {})
|
|
300
|
+
reject('Queue error')
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
ws.on('error', async (err) => {
|
|
305
|
+
logger.error(exports, `Unable to write queue file (${fname}): ${err}`)
|
|
306
|
+
ws.destroy()
|
|
307
|
+
await fs.unlink(tmp_path).catch(() => {})
|
|
308
|
+
reject('Queueing failed')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
this.build_todo(todo, ws, () => {
|
|
312
|
+
todo.message_stream.pipe(ws, {
|
|
313
|
+
dot_stuffed: false,
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
exports.build_todo = (todo, ws, write_more) => {
|
|
320
|
+
const todo_str = `\n${JSON.stringify(todo, exclude_from_json, '\t')}\n`
|
|
321
|
+
const todo_len = Buffer.byteLength(todo_str)
|
|
322
|
+
|
|
323
|
+
const buf = Buffer.alloc(4 + todo_len)
|
|
324
|
+
buf.writeUInt32BE(todo_len, 0)
|
|
325
|
+
buf.write(todo_str, 4)
|
|
326
|
+
|
|
327
|
+
const continue_writing = ws.write(buf)
|
|
328
|
+
if (continue_writing) {
|
|
329
|
+
process.nextTick(write_more)
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
ws.once('drain', write_more)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Replacer function to exclude items from the queue file header
|
|
337
|
+
function exclude_from_json(key, value) {
|
|
338
|
+
switch (key) {
|
|
339
|
+
case 'message_stream':
|
|
340
|
+
return undefined
|
|
341
|
+
default:
|
|
342
|
+
return value
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// exported for testability
|
|
347
|
+
exports.TODOItem = TODOItem
|
|
348
|
+
|
|
349
|
+
exports.HMailItem = HMailItem
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const os = require('node:os')
|
|
4
|
+
const platform_dot = `${['win32', 'win64'].includes(process.platform) ? '' : '__tmp__'}.`
|
|
5
|
+
|
|
6
|
+
let QFILECOUNTER = 0
|
|
7
|
+
|
|
8
|
+
const _qfile = (module.exports = {
|
|
9
|
+
// File Name Format: $arrival_$nextattempt_$attempts_$pid_$uniquetag_$counter_$host
|
|
10
|
+
hostname: (hostname) => {
|
|
11
|
+
if (!hostname) hostname = os.hostname()
|
|
12
|
+
return hostname.replace(/\\/g, '\\057').replace(/:/g, '\\072').replace(/_/g, '\\137')
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
name(overrides) {
|
|
16
|
+
const o = overrides || {}
|
|
17
|
+
const time = _qfile.time()
|
|
18
|
+
return [
|
|
19
|
+
o.arrival || time,
|
|
20
|
+
o.next_attempt || time,
|
|
21
|
+
o.attempts || 0,
|
|
22
|
+
o.pid || process.pid,
|
|
23
|
+
o.uid || _qfile.rnd_unique(),
|
|
24
|
+
_qfile.next_counter(),
|
|
25
|
+
this.hostname(o.host),
|
|
26
|
+
].join('_')
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
time: () => new Date().getTime(),
|
|
30
|
+
|
|
31
|
+
next_counter: () => {
|
|
32
|
+
QFILECOUNTER = QFILECOUNTER < 10000 ? QFILECOUNTER + 1 : 0
|
|
33
|
+
return QFILECOUNTER
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
rnd_unique: (len = 6) => {
|
|
37
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
38
|
+
const result = []
|
|
39
|
+
for (let i = len; i > 0; --i) {
|
|
40
|
+
result.push(chars[Math.floor(Math.random() * chars.length)])
|
|
41
|
+
}
|
|
42
|
+
return result.join('')
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
parts: (filename) => {
|
|
46
|
+
if (!filename) throw new Error('No filename provided')
|
|
47
|
+
|
|
48
|
+
const PARTS_EXPECTED_OLD = 4
|
|
49
|
+
const PARTS_EXPECTED_CURRENT = 7
|
|
50
|
+
let p = filename.split('_')
|
|
51
|
+
|
|
52
|
+
// bail on unknown split lengths
|
|
53
|
+
switch (p.length) {
|
|
54
|
+
case PARTS_EXPECTED_OLD:
|
|
55
|
+
case PARTS_EXPECTED_CURRENT:
|
|
56
|
+
break
|
|
57
|
+
default:
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const time = _qfile.time()
|
|
62
|
+
if (p.length === PARTS_EXPECTED_OLD) {
|
|
63
|
+
// parse the previous string structure
|
|
64
|
+
// $nextattempt_$attempts_$pid_$uniq.$host
|
|
65
|
+
// 1484878079415_0_12345_8888.mta1.example.com
|
|
66
|
+
// var fn_re = /^(\d+)_(\d+)_(\d+)(_\d+\..*)$/
|
|
67
|
+
// match[1] = $nextattempt
|
|
68
|
+
// match[2] = $attempts
|
|
69
|
+
// match[3] = $pid
|
|
70
|
+
// match[4] = $uniq.$my_hostname
|
|
71
|
+
const fn_re = /^(\d+)_(\d+)_(\d+)_(\d+)\.(.*)$/
|
|
72
|
+
const match = filename.match(fn_re)
|
|
73
|
+
if (!match) return null
|
|
74
|
+
|
|
75
|
+
p = match.slice(1) // grab the capture groups minus the pattern
|
|
76
|
+
p.splice(3, 1, _qfile.rnd_unique(), _qfile.next_counter()) // add a fresh UID and counter
|
|
77
|
+
p.unshift(time) // prepend current timestamp -- potentially inaccurate, but non-critical and shortlived
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
arrival: parseInt(p[0]),
|
|
82
|
+
next_attempt: parseInt(p[1]),
|
|
83
|
+
attempts: parseInt(p[2]),
|
|
84
|
+
pid: parseInt(p[3]),
|
|
85
|
+
uid: p[4],
|
|
86
|
+
counter: parseInt(p[5]),
|
|
87
|
+
host: p[6],
|
|
88
|
+
age: time - parseInt(p[0]),
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
platformDOT: platform_dot,
|
|
93
|
+
})
|