Haraka 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +4 -0
- package/CONTRIBUTORS.md +5 -5
- package/Changes.md +69 -50
- package/Plugins.md +3 -1
- package/README.md +1 -1
- package/bin/haraka +475 -478
- package/config/outbound.ini +3 -0
- package/connection.js +1072 -1108
- package/docs/Connection.md +29 -30
- package/docs/CoreConfig.md +38 -39
- package/docs/CustomReturnCodes.md +0 -1
- package/docs/HAProxy.md +2 -2
- package/docs/Header.md +1 -1
- package/docs/Logging.md +29 -5
- package/docs/Outbound.md +93 -78
- package/docs/Plugins.md +103 -108
- package/docs/Transaction.md +49 -51
- package/docs/Tutorial.md +127 -143
- package/docs/deprecated/access.md +0 -1
- package/docs/deprecated/backscatterer.md +2 -3
- package/docs/deprecated/connect.rdns_access.md +18 -27
- package/docs/deprecated/data.headers.md +0 -1
- package/docs/deprecated/data.nomsgid.md +1 -2
- package/docs/deprecated/data.noreceived.md +1 -2
- package/docs/deprecated/data.rfc5322_header_checks.md +1 -2
- package/docs/deprecated/dkim_sign.md +13 -17
- package/docs/deprecated/dkim_verify.md +9 -17
- package/docs/deprecated/dnsbl.md +36 -38
- package/docs/deprecated/dnswl.md +41 -43
- package/docs/deprecated/lookup_rdns.strict.md +21 -34
- package/docs/deprecated/mail_from.access.md +17 -25
- package/docs/deprecated/mail_from.blocklist.md +9 -12
- package/docs/deprecated/mail_from.nobounces.md +1 -2
- package/docs/deprecated/rcpt_to.access.md +20 -27
- package/docs/deprecated/rcpt_to.blocklist.md +10 -13
- package/docs/deprecated/rcpt_to.routes.md +0 -1
- package/docs/deprecated/rdns.regexp.md +13 -15
- package/docs/plugins/aliases.md +89 -89
- package/docs/plugins/auth/auth_bridge.md +5 -7
- package/docs/plugins/auth/auth_ldap.md +11 -14
- package/docs/plugins/auth/auth_proxy.md +10 -12
- package/docs/plugins/auth/auth_vpopmaild.md +5 -6
- package/docs/plugins/auth/flat_file.md +4 -4
- package/docs/plugins/block_me.md +3 -3
- package/docs/plugins/data.signatures.md +1 -2
- package/docs/plugins/delay_deny.md +3 -4
- package/docs/plugins/max_unrecognized_commands.md +4 -4
- package/docs/plugins/prevent_credential_leaks.md +6 -6
- package/docs/plugins/process_title.md +18 -18
- package/docs/plugins/queue/deliver.md +2 -3
- package/docs/plugins/queue/discard.md +4 -4
- package/docs/plugins/queue/lmtp.md +1 -3
- package/docs/plugins/queue/qmail-queue.md +7 -9
- package/docs/plugins/queue/quarantine.md +16 -21
- package/docs/plugins/queue/rabbitmq.md +8 -11
- package/docs/plugins/queue/rabbitmq_amqplib.md +43 -39
- package/docs/plugins/queue/smtp_bridge.md +7 -10
- package/docs/plugins/queue/smtp_forward.md +42 -34
- package/docs/plugins/queue/smtp_proxy.md +30 -29
- package/docs/plugins/queue/test.md +1 -3
- package/docs/plugins/rcpt_to.in_host_list.md +6 -6
- package/docs/plugins/rcpt_to.max_count.md +1 -1
- package/docs/plugins/record_envelope_addresses.md +3 -3
- package/docs/plugins/reseed_rng.md +6 -6
- package/docs/plugins/status.md +9 -8
- package/docs/plugins/tarpit.md +7 -11
- package/docs/plugins/tls.md +12 -17
- package/docs/plugins/toobusy.md +4 -4
- package/docs/plugins/xclient.md +3 -3
- package/docs/tutorials/Migrating_from_v1_to_v2.md +19 -41
- package/docs/tutorials/SettingUpOutbound.md +6 -9
- package/endpoint.js +35 -38
- package/eslint.config.mjs +22 -19
- package/haraka.js +42 -47
- package/host_pool.js +75 -79
- package/http/html/404.html +45 -49
- package/http/html/index.html +39 -28
- package/http/package.json +2 -4
- package/line_socket.js +27 -28
- package/logger.js +182 -201
- package/outbound/client_pool.js +34 -27
- package/outbound/config.js +64 -59
- package/outbound/fsync_writestream.js +24 -25
- package/outbound/hmail.js +888 -835
- package/outbound/index.js +194 -187
- package/outbound/qfile.js +49 -52
- package/outbound/queue.js +197 -190
- package/outbound/timer_queue.js +41 -43
- package/outbound/tls.js +68 -61
- package/outbound/todo.js +11 -11
- package/package.json +38 -33
- package/plugins/.eslintrc.yaml +0 -1
- package/plugins/auth/auth_base.js +123 -127
- package/plugins/auth/auth_bridge.js +7 -7
- package/plugins/auth/auth_proxy.js +121 -126
- package/plugins/auth/auth_vpopmaild.js +84 -85
- package/plugins/auth/flat_file.js +18 -17
- package/plugins/block_me.js +31 -31
- package/plugins/data.signatures.js +13 -13
- package/plugins/delay_deny.js +65 -61
- package/plugins/prevent_credential_leaks.js +23 -23
- package/plugins/process_title.js +125 -128
- package/plugins/profile.js +5 -5
- package/plugins/queue/deliver.js +3 -3
- package/plugins/queue/discard.js +13 -14
- package/plugins/queue/lmtp.js +16 -17
- package/plugins/queue/qmail-queue.js +54 -55
- package/plugins/queue/quarantine.js +68 -70
- package/plugins/queue/rabbitmq.js +80 -87
- package/plugins/queue/rabbitmq_amqplib.js +75 -54
- package/plugins/queue/smtp_bridge.js +16 -16
- package/plugins/queue/smtp_forward.js +175 -179
- package/plugins/queue/smtp_proxy.js +69 -71
- package/plugins/queue/test.js +9 -9
- package/plugins/rcpt_to.host_list_base.js +30 -34
- package/plugins/rcpt_to.in_host_list.js +19 -19
- package/plugins/record_envelope_addresses.js +4 -4
- package/plugins/reseed_rng.js +4 -4
- package/plugins/status.js +90 -97
- package/plugins/tarpit.js +25 -14
- package/plugins/tls.js +68 -68
- package/plugins/toobusy.js +21 -23
- package/plugins/xclient.js +51 -53
- package/plugins.js +276 -293
- package/rfc1869.js +30 -35
- package/server.js +308 -299
- package/smtp_client.js +244 -228
- package/test/.eslintrc.yaml +0 -1
- package/test/connection.js +127 -134
- package/test/endpoint.js +53 -47
- package/test/fixtures/line_socket.js +12 -12
- package/test/fixtures/util_hmailitem.js +89 -85
- package/test/host_pool.js +90 -92
- package/test/installation/plugins/base_plugin.js +2 -2
- package/test/installation/plugins/folder_plugin/index.js +2 -3
- package/test/installation/plugins/inherits.js +3 -3
- package/test/installation/plugins/load_first.js +2 -3
- package/test/installation/plugins/plugin.js +1 -3
- package/test/installation/plugins/tls.js +2 -4
- package/test/logger.js +135 -116
- package/test/outbound/hmail.js +49 -35
- package/test/outbound/index.js +118 -101
- package/test/outbound/qfile.js +51 -53
- package/test/outbound_bounce_net_errors.js +84 -69
- package/test/outbound_bounce_rfc3464.js +235 -165
- package/test/plugins/auth/auth_base.js +420 -279
- package/test/plugins/auth/auth_vpopmaild.js +38 -39
- package/test/plugins/queue/smtp_forward.js +126 -104
- package/test/plugins/rcpt_to.host_list_base.js +85 -67
- package/test/plugins/rcpt_to.in_host_list.js +159 -112
- package/test/plugins/status.js +71 -64
- package/test/plugins/tls.js +37 -34
- package/test/plugins.js +97 -92
- package/test/rfc1869.js +19 -26
- package/test/server.js +293 -272
- package/test/smtp_client.js +180 -176
- package/test/tls_socket.js +62 -66
- package/test/transaction.js +159 -160
- package/tls_socket.js +331 -333
- package/transaction.js +129 -137
package/plugins/status.js
CHANGED
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict'
|
|
2
|
+
/* global server */
|
|
2
3
|
|
|
3
|
-
const fs = require('node:fs')
|
|
4
|
-
const path = require('node:path')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
5
6
|
|
|
6
7
|
exports.register = function () {
|
|
7
|
-
this.outbound = require('../outbound')
|
|
8
|
-
this.queue_dir = require('../outbound/queue').queue_dir
|
|
8
|
+
this.outbound = require('../outbound')
|
|
9
|
+
this.queue_dir = require('../outbound/queue').queue_dir
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
exports.hook_capabilities = (next, connection) => {
|
|
12
|
-
|
|
13
13
|
if (connection.remote.is_local) {
|
|
14
|
-
connection.capabilities.push('STATUS')
|
|
14
|
+
connection.capabilities.push('STATUS')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
next()
|
|
17
|
+
next()
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
21
|
-
if (params[0] !== 'STATUS') return next()
|
|
22
|
-
if (!connection.remote.is_local) return next(DENY, 'STATUS not allowed remotely')
|
|
21
|
+
if (params[0] !== 'STATUS') return next()
|
|
22
|
+
if (!connection.remote.is_local) return next(DENY, 'STATUS not allowed remotely')
|
|
23
23
|
|
|
24
24
|
this.run(params[1], (err, result) => {
|
|
25
|
-
if (err) return next(DENY, err.message)
|
|
25
|
+
if (err) return next(DENY, err.message)
|
|
26
26
|
|
|
27
|
-
connection.respond(211, result ? JSON.stringify(result) : 'null', () => next(OK))
|
|
28
|
-
})
|
|
27
|
+
connection.respond(211, result ? JSON.stringify(result) : 'null', () => next(OK))
|
|
28
|
+
})
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
exports.run = function (cmd, cb) {
|
|
32
32
|
if (server.cluster && !/^QUEUE LIST/.test(cmd)) {
|
|
33
|
-
this.call_master(cmd, cb)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
this.command_action(cmd, cb);
|
|
33
|
+
this.call_master(cmd, cb)
|
|
34
|
+
} else {
|
|
35
|
+
this.command_action(cmd, cb)
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
exports.command_action = function (cmd, cb) {
|
|
41
|
-
const params = cmd.split(' ')
|
|
40
|
+
const params = cmd.split(' ')
|
|
42
41
|
|
|
43
42
|
switch (params.shift()) {
|
|
44
43
|
case 'POOL':
|
|
45
|
-
return this.pool_action(params, cb)
|
|
44
|
+
return this.pool_action(params, cb)
|
|
46
45
|
case 'QUEUE':
|
|
47
|
-
return this.queue_action(params, cb)
|
|
46
|
+
return this.queue_action(params, cb)
|
|
48
47
|
default:
|
|
49
48
|
cb('unknown STATUS command')
|
|
50
49
|
}
|
|
@@ -53,7 +52,7 @@ exports.command_action = function (cmd, cb) {
|
|
|
53
52
|
exports.pool_action = function (params, cb) {
|
|
54
53
|
switch (params.shift()) {
|
|
55
54
|
case 'LIST':
|
|
56
|
-
return this.pool_list(cb)
|
|
55
|
+
return this.pool_list(cb)
|
|
57
56
|
default:
|
|
58
57
|
cb('unknown POOL command')
|
|
59
58
|
}
|
|
@@ -62,40 +61,40 @@ exports.pool_action = function (params, cb) {
|
|
|
62
61
|
exports.queue_action = function (params, cb) {
|
|
63
62
|
switch (params.shift()) {
|
|
64
63
|
case 'LIST':
|
|
65
|
-
return this.queue_list(cb)
|
|
64
|
+
return this.queue_list(cb)
|
|
66
65
|
case 'STATS':
|
|
67
|
-
return this.queue_stats(cb)
|
|
66
|
+
return this.queue_stats(cb)
|
|
68
67
|
case 'INSPECT':
|
|
69
|
-
return this.queue_inspect(cb)
|
|
68
|
+
return this.queue_inspect(cb)
|
|
70
69
|
case 'DISCARD':
|
|
71
|
-
return this.queue_discard(params.shift(), cb)
|
|
70
|
+
return this.queue_discard(params.shift(), cb)
|
|
72
71
|
case 'PUSH':
|
|
73
|
-
return this.queue_push(params.shift(), cb)
|
|
72
|
+
return this.queue_push(params.shift(), cb)
|
|
74
73
|
default:
|
|
75
74
|
cb('unknown QUEUE command')
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
exports.pool_list = cb => {
|
|
80
|
-
const result = {}
|
|
78
|
+
exports.pool_list = (cb) => {
|
|
79
|
+
const result = {}
|
|
81
80
|
|
|
82
81
|
if (server.notes.pool) {
|
|
83
82
|
for (const name of Object.keys(server.notes.pool)) {
|
|
84
|
-
const instance = server.notes.pool[name]
|
|
83
|
+
const instance = server.notes.pool[name]
|
|
85
84
|
|
|
86
85
|
result[name] = {
|
|
87
86
|
inUse: instance.inUseObjectsCount(),
|
|
88
|
-
size: instance.getPoolSize()
|
|
89
|
-
}
|
|
87
|
+
size: instance.getPoolSize(),
|
|
88
|
+
}
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
cb(null, result)
|
|
92
|
+
cb(null, result)
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
exports.queue_list = function (cb) {
|
|
97
96
|
this.outbound.list_queue((err, qlist = []) => {
|
|
98
|
-
const result = []
|
|
97
|
+
const result = []
|
|
99
98
|
|
|
100
99
|
for (const todo of qlist) {
|
|
101
100
|
result.push({
|
|
@@ -104,120 +103,115 @@ exports.queue_list = function (cb) {
|
|
|
104
103
|
queue_time: todo.queue_time,
|
|
105
104
|
domain: todo.domain,
|
|
106
105
|
from: todo.mail_from.toString(),
|
|
107
|
-
to: todo.rcpt_to.map((r) => r.toString())
|
|
106
|
+
to: todo.rcpt_to.map((r) => r.toString()),
|
|
108
107
|
})
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
cb(err, result)
|
|
110
|
+
cb(err, result)
|
|
112
111
|
})
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
exports.queue_stats = function (cb) {
|
|
116
|
-
cb(null, this.outbound.get_stats())
|
|
115
|
+
cb(null, this.outbound.get_stats())
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
exports.queue_inspect = function (cb) {
|
|
120
|
-
const delivery_queue_items = this.outbound.delivery_queue._tasks.toArray()
|
|
121
|
-
const fail_queue_items = this.outbound.temp_fail_queue.queue
|
|
119
|
+
const delivery_queue_items = this.outbound.delivery_queue._tasks.toArray()
|
|
120
|
+
const fail_queue_items = this.outbound.temp_fail_queue.queue
|
|
122
121
|
|
|
123
122
|
cb(null, {
|
|
124
123
|
delivery_queue: delivery_queue_items.map((hmail) => ({
|
|
125
|
-
id: hmail.file
|
|
124
|
+
id: hmail.file,
|
|
126
125
|
})),
|
|
127
126
|
temp_fail_queue: fail_queue_items.map((tqtimer) => ({
|
|
128
127
|
id: tqtimer.id,
|
|
129
|
-
fire_time: tqtimer.fire_time
|
|
130
|
-
}))
|
|
131
|
-
})
|
|
128
|
+
fire_time: tqtimer.fire_time,
|
|
129
|
+
})),
|
|
130
|
+
})
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
exports.queue_discard = function (file, cb) {
|
|
135
134
|
try {
|
|
136
|
-
this.outbound.temp_fail_queue.discard(file)
|
|
137
|
-
}
|
|
138
|
-
catch (e) {
|
|
135
|
+
this.outbound.temp_fail_queue.discard(file)
|
|
136
|
+
} catch (e) {
|
|
139
137
|
// we ignore not found error
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
fs.unlink(path.join(this.queue_dir || '', file), () => {
|
|
143
|
-
cb(null, 'OK')
|
|
144
|
-
})
|
|
141
|
+
cb(null, 'OK')
|
|
142
|
+
})
|
|
145
143
|
}
|
|
146
144
|
|
|
147
145
|
exports.queue_push = function (file, cb) {
|
|
148
|
-
const { queue } = this.outbound.temp_fail_queue
|
|
146
|
+
const { queue } = this.outbound.temp_fail_queue
|
|
149
147
|
|
|
150
148
|
for (let i = 0; i < queue.length; i++) {
|
|
151
|
-
if (queue[i].id !== file) continue
|
|
149
|
+
if (queue[i].id !== file) continue
|
|
152
150
|
|
|
153
|
-
const item = queue.splice(i, 1)[0]
|
|
154
|
-
item.cb()
|
|
151
|
+
const item = queue.splice(i, 1)[0]
|
|
152
|
+
item.cb()
|
|
155
153
|
|
|
156
|
-
break
|
|
154
|
+
break
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
cb(null, 'OK')
|
|
157
|
+
cb(null, 'OK')
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
// cluster IPC
|
|
163
161
|
|
|
164
162
|
exports.hook_init_master = function (next) {
|
|
165
|
-
const plugin = this
|
|
163
|
+
const plugin = this
|
|
166
164
|
|
|
167
|
-
if (!server.cluster) return next()
|
|
165
|
+
if (!server.cluster) return next()
|
|
168
166
|
|
|
169
|
-
function message_handler
|
|
170
|
-
if (msg.event !== 'status.request') return
|
|
167
|
+
function message_handler(sender, msg) {
|
|
168
|
+
if (msg.event !== 'status.request') return
|
|
171
169
|
|
|
172
170
|
plugin.call_workers(msg, (response) => {
|
|
173
|
-
msg.result = response.filter((el) => el != null)
|
|
174
|
-
msg.event = 'status.result'
|
|
175
|
-
sender.send(msg)
|
|
176
|
-
})
|
|
171
|
+
msg.result = response.filter((el) => el != null)
|
|
172
|
+
msg.event = 'status.result'
|
|
173
|
+
sender.send(msg)
|
|
174
|
+
})
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
server.cluster.on('message', message_handler)
|
|
180
|
-
next()
|
|
177
|
+
server.cluster.on('message', message_handler)
|
|
178
|
+
next()
|
|
181
179
|
}
|
|
182
180
|
|
|
183
181
|
exports.hook_init_child = function (next) {
|
|
184
|
-
const self = this
|
|
182
|
+
const self = this
|
|
185
183
|
|
|
186
|
-
function message_handler
|
|
187
|
-
if (msg.event !== 'status.request') return
|
|
184
|
+
function message_handler(msg) {
|
|
185
|
+
if (msg.event !== 'status.request') return
|
|
188
186
|
|
|
189
187
|
self.command_action(msg.params, (err, result) => {
|
|
190
|
-
msg.event = 'status.response'
|
|
191
|
-
msg.result = result
|
|
192
|
-
process.send(msg)
|
|
193
|
-
})
|
|
188
|
+
msg.event = 'status.response'
|
|
189
|
+
msg.result = result
|
|
190
|
+
process.send(msg)
|
|
191
|
+
})
|
|
194
192
|
}
|
|
195
193
|
|
|
196
|
-
process.on('message', message_handler)
|
|
197
|
-
next()
|
|
194
|
+
process.on('message', message_handler)
|
|
195
|
+
next()
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
exports.call_master = (cmd, cb) => {
|
|
199
|
+
function message_handler(msg) {
|
|
200
|
+
if (msg.event !== 'status.result') return
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
process.removeListener('message', message_handler);
|
|
206
|
-
cb(null, msg.result);
|
|
202
|
+
process.removeListener('message', message_handler)
|
|
203
|
+
cb(null, msg.result)
|
|
207
204
|
}
|
|
208
205
|
|
|
209
|
-
process.on('message', message_handler)
|
|
210
|
-
process.send({event: 'status.request', params: cmd})
|
|
206
|
+
process.on('message', message_handler)
|
|
207
|
+
process.send({ event: 'status.request', params: cmd })
|
|
211
208
|
}
|
|
212
209
|
|
|
213
210
|
exports.call_workers = function (cmd, cb) {
|
|
214
|
-
Promise.allSettled(
|
|
215
|
-
Object.values(server.cluster.workers).map(w => this.call_worker(w, cmd))
|
|
216
|
-
)
|
|
217
|
-
.then(r => {
|
|
211
|
+
Promise.allSettled(Object.values(server.cluster.workers).map((w) => this.call_worker(w, cmd))).then((r) => {
|
|
218
212
|
cb(
|
|
219
213
|
// r.filter(s => s.status === 'rejected').flatMap(s => s.reason),
|
|
220
|
-
r.filter(s => s.status === 'fulfilled').flatMap(s => s.value),
|
|
214
|
+
r.filter((s) => s.status === 'fulfilled').flatMap((s) => s.value),
|
|
221
215
|
)
|
|
222
216
|
})
|
|
223
217
|
}
|
|
@@ -225,25 +219,24 @@ exports.call_workers = function (cmd, cb) {
|
|
|
225
219
|
// sends command to worker and then wait for response or timeout
|
|
226
220
|
exports.call_worker = (worker, cmd) => {
|
|
227
221
|
return new Promise((resolve) => {
|
|
228
|
-
let timeout
|
|
222
|
+
let timeout
|
|
229
223
|
|
|
230
|
-
function message_handler
|
|
231
|
-
if (sender.id !== worker.id) return
|
|
232
|
-
if (msg.event !== 'status.response') return
|
|
224
|
+
function message_handler(sender, msg) {
|
|
225
|
+
if (sender.id !== worker.id) return
|
|
226
|
+
if (msg.event !== 'status.response') return
|
|
233
227
|
|
|
234
|
-
clearTimeout(timeout)
|
|
235
|
-
server.cluster.removeListener('message', message_handler)
|
|
228
|
+
clearTimeout(timeout)
|
|
229
|
+
server.cluster.removeListener('message', message_handler)
|
|
236
230
|
|
|
237
|
-
resolve(msg.result)
|
|
231
|
+
resolve(msg.result)
|
|
238
232
|
}
|
|
239
233
|
|
|
240
234
|
timeout = setTimeout(() => {
|
|
241
|
-
server.cluster.removeListener('message', message_handler)
|
|
242
|
-
resolve()
|
|
243
|
-
}, 1000)
|
|
244
|
-
|
|
235
|
+
server.cluster.removeListener('message', message_handler)
|
|
236
|
+
resolve()
|
|
237
|
+
}, 1000)
|
|
245
238
|
|
|
246
|
-
server.cluster.on('message', message_handler)
|
|
247
|
-
worker.send(cmd)
|
|
239
|
+
server.cluster.on('message', message_handler)
|
|
240
|
+
worker.send(cmd)
|
|
248
241
|
})
|
|
249
242
|
}
|
package/plugins/tarpit.js
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
1
|
// tarpit
|
|
2
2
|
|
|
3
3
|
let hooks_to_delay = [
|
|
4
|
-
'connect',
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
|
|
4
|
+
'connect',
|
|
5
|
+
'helo',
|
|
6
|
+
'ehlo',
|
|
7
|
+
'mail',
|
|
8
|
+
'rcpt',
|
|
9
|
+
'rcpt_ok',
|
|
10
|
+
'data',
|
|
11
|
+
'data_post',
|
|
12
|
+
'queue',
|
|
13
|
+
'unrecognized_command',
|
|
14
|
+
'vrfy',
|
|
15
|
+
'noop',
|
|
16
|
+
'rset',
|
|
17
|
+
'quit',
|
|
18
|
+
]
|
|
8
19
|
|
|
9
20
|
exports.register = function () {
|
|
10
21
|
// Register tarpit function last
|
|
11
22
|
|
|
12
|
-
const cfg = this.config.get('tarpit.ini')
|
|
23
|
+
const cfg = this.config.get('tarpit.ini')
|
|
13
24
|
if (cfg?.main.hooks_to_delay) {
|
|
14
|
-
hooks_to_delay = cfg.main.hooks_to_delay.split(/[\s,;]+/)
|
|
25
|
+
hooks_to_delay = cfg.main.hooks_to_delay.split(/[\s,;]+/)
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
for (const hook of hooks_to_delay) {
|
|
18
|
-
this.register_hook(hook, 'tarpit')
|
|
29
|
+
this.register_hook(hook, 'tarpit')
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
exports.tarpit = function (next, connection) {
|
|
23
|
-
const { transaction } = connection
|
|
24
|
-
if (!transaction) return next()
|
|
34
|
+
const { transaction } = connection
|
|
35
|
+
if (!transaction) return next()
|
|
25
36
|
|
|
26
|
-
let delay = connection.notes.tarpit
|
|
37
|
+
let delay = connection.notes.tarpit
|
|
27
38
|
|
|
28
|
-
if (!delay) delay = transaction.notes.tarpit
|
|
39
|
+
if (!delay) delay = transaction.notes.tarpit
|
|
29
40
|
|
|
30
|
-
if (!delay) return next()
|
|
41
|
+
if (!delay) return next()
|
|
31
42
|
|
|
32
|
-
connection.loginfo(this, `tarpitting response for ${delay}s`)
|
|
33
|
-
setTimeout(() => next(),
|
|
43
|
+
connection.loginfo(this, `tarpitting response for ${delay}s`)
|
|
44
|
+
setTimeout(() => next(), delay * 1000)
|
|
34
45
|
}
|
package/plugins/tls.js
CHANGED
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict'
|
|
2
2
|
// TLS is built into Haraka. This plugin conditionally advertises STARTTLS.
|
|
3
3
|
// see 'haraka -h tls' for help
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/* global server */
|
|
6
|
+
|
|
7
|
+
const tls_socket = require('./tls_socket')
|
|
6
8
|
|
|
7
9
|
// exported so tests can override config dir
|
|
8
|
-
exports.net_utils = require('haraka-net-utils')
|
|
10
|
+
exports.net_utils = require('haraka-net-utils')
|
|
9
11
|
|
|
10
12
|
exports.register = function () {
|
|
11
13
|
tls_socket.load_tls_ini()
|
|
12
14
|
|
|
13
15
|
if (tls_socket.cfg.redis.disable_for_failed_hosts) this.logdebug('Will disable STARTTLS for failing TLS hosts')
|
|
14
16
|
|
|
15
|
-
this.register_hook('capabilities',
|
|
17
|
+
this.register_hook('capabilities', 'advertise_starttls')
|
|
16
18
|
this.register_hook('unrecognized_command', 'upgrade_connection')
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
exports.shutdown = () => {
|
|
20
|
-
if (tls_socket.shutdown) tls_socket.shutdown()
|
|
22
|
+
if (tls_socket.shutdown) tls_socket.shutdown()
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
exports.advertise_starttls = function (next, connection) {
|
|
24
|
-
|
|
25
26
|
// if no TLS setup incomplete/invalid, don't advertise
|
|
26
27
|
if (!tls_socket.tls_valid) {
|
|
27
|
-
this.logerror('no valid TLS config')
|
|
28
|
-
return next()
|
|
28
|
+
this.logerror('no valid TLS config')
|
|
29
|
+
return next()
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/* Caution: do not advertise STARTTLS if already TLS upgraded */
|
|
32
|
-
if (connection.tls.enabled) return next()
|
|
33
|
+
if (connection.tls.enabled) return next()
|
|
33
34
|
|
|
34
35
|
if (this.net_utils.ip_in_list(tls_socket.cfg.no_tls_hosts, connection.remote.ip)) {
|
|
35
|
-
return next()
|
|
36
|
+
return next()
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function enable_tls
|
|
39
|
-
connection.capabilities.push('STARTTLS')
|
|
40
|
-
connection.tls.advertised = true
|
|
41
|
-
next()
|
|
39
|
+
function enable_tls() {
|
|
40
|
+
connection.capabilities.push('STARTTLS')
|
|
41
|
+
connection.tls.advertised = true
|
|
42
|
+
next()
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// check if local port is excluded from starttls advertisement
|
|
45
|
-
if (tls_socket.cfg.main.no_starttls_ports.includes(connection.local.port)) return next()
|
|
46
|
+
if (tls_socket.cfg.main.no_starttls_ports.includes(connection.local.port)) return next()
|
|
46
47
|
|
|
47
48
|
// exclude port 587 from NO-GO
|
|
48
|
-
if (connection.local.port === 587) return enable_tls()
|
|
49
|
+
if (connection.local.port === 587) return enable_tls()
|
|
49
50
|
|
|
50
51
|
if (!tls_socket.cfg.redis || !server.notes.redis) {
|
|
51
|
-
return enable_tls()
|
|
52
|
+
return enable_tls()
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
const { redis } = server.notes
|
|
55
|
-
const dbkey = `no_tls|${connection.remote.ip}
|
|
55
|
+
const { redis } = server.notes
|
|
56
|
+
const dbkey = `no_tls|${connection.remote.ip}`
|
|
56
57
|
|
|
57
|
-
redis
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
redis
|
|
59
|
+
.get(dbkey)
|
|
60
|
+
.then((dbr) => {
|
|
61
|
+
if (!dbr) return enable_tls()
|
|
62
|
+
connection.results.add(this, { msg: 'no_tls' })
|
|
61
63
|
next(CONT, 'STARTTLS disabled because previous attempt failed')
|
|
62
64
|
})
|
|
63
|
-
.catch(err => {
|
|
64
|
-
connection.results.add(this, {err})
|
|
65
|
-
enable_tls()
|
|
65
|
+
.catch((err) => {
|
|
66
|
+
connection.results.add(this, { err })
|
|
67
|
+
enable_tls()
|
|
66
68
|
})
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
exports.set_notls = function (connection) {
|
|
72
|
+
if (!tls_socket.cfg.redis.disable_for_failed_hosts) return
|
|
73
|
+
if (!server.notes.redis) return
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
if (!server.notes.redis) return;
|
|
75
|
+
const expiry = tls_socket.cfg.redis.disable_inbound_expiry || 3600
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
this.lognotice(connection, `STARTTLS failed. Marking ${connection.remote.ip} as non-TLS host for ${expiry} seconds`)
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, (new Date()).toISOString());
|
|
79
|
+
server.notes.redis.setEx(`no_tls|${connection.remote.ip}`, expiry, new Date().toISOString())
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
exports.upgrade_connection = function (next, connection, params) {
|
|
82
|
-
if (!connection.tls.advertised) return next()
|
|
83
|
+
if (!connection.tls.advertised) return next()
|
|
83
84
|
|
|
84
85
|
/* Watch for STARTTLS directive from client. */
|
|
85
|
-
if (params[0].toUpperCase() !== 'STARTTLS') return next()
|
|
86
|
+
if (params[0].toUpperCase() !== 'STARTTLS') return next()
|
|
86
87
|
|
|
87
88
|
/* Respond to STARTTLS command. */
|
|
88
|
-
connection.respond(220,
|
|
89
|
+
connection.respond(220, 'Go ahead.')
|
|
89
90
|
|
|
90
|
-
const plugin = this
|
|
91
|
-
let called_next = false
|
|
91
|
+
const plugin = this
|
|
92
|
+
let called_next = false
|
|
92
93
|
// adjust plugin.timeout like so: echo '45' > config/tls.timeout
|
|
93
|
-
const timeout = plugin.timeout - 1
|
|
94
|
-
|
|
95
|
-
function nextOnce
|
|
96
|
-
if (called_next) return
|
|
97
|
-
called_next = true
|
|
98
|
-
clearTimeout(connection.notes.tls_timer)
|
|
99
|
-
if (!disconnected) connection.lognotice(plugin, 'timeout setting up TLS')
|
|
100
|
-
plugin.set_notls(connection)
|
|
101
|
-
return next(DENYSOFTDISCONNECT)
|
|
94
|
+
const timeout = plugin.timeout - 1
|
|
95
|
+
|
|
96
|
+
function nextOnce(disconnected) {
|
|
97
|
+
if (called_next) return
|
|
98
|
+
called_next = true
|
|
99
|
+
clearTimeout(connection.notes.tls_timer)
|
|
100
|
+
if (!disconnected) connection.lognotice(plugin, 'timeout setting up TLS')
|
|
101
|
+
plugin.set_notls(connection)
|
|
102
|
+
return next(DENYSOFTDISCONNECT)
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
if (timeout && timeout > 0) {
|
|
105
|
-
connection.notes.tls_timer = setTimeout(nextOnce, timeout * 1000)
|
|
106
|
+
connection.notes.tls_timer = setTimeout(nextOnce, timeout * 1000)
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
connection.notes.cleanUpDisconnect = nextOnce
|
|
109
|
+
connection.notes.cleanUpDisconnect = nextOnce
|
|
109
110
|
|
|
110
111
|
/* Upgrade the connection to TLS. */
|
|
111
112
|
connection.client.upgrade((verified, verifyErr, cert, cipher) => {
|
|
112
|
-
if (called_next) return
|
|
113
|
-
clearTimeout(connection.notes.tls_timer)
|
|
114
|
-
called_next = true
|
|
113
|
+
if (called_next) return
|
|
114
|
+
clearTimeout(connection.notes.tls_timer)
|
|
115
|
+
called_next = true
|
|
115
116
|
connection.reset_transaction(() => {
|
|
116
|
-
|
|
117
117
|
connection.setTLS({
|
|
118
118
|
cipher,
|
|
119
119
|
verified,
|
|
120
120
|
authorizationError: verifyErr,
|
|
121
121
|
peerCertificate: cert,
|
|
122
|
-
})
|
|
122
|
+
})
|
|
123
123
|
|
|
124
|
-
connection.results.add(plugin, connection.tls)
|
|
125
|
-
plugin.emit_upgrade_msg(connection, verified, verifyErr, cert, cipher)
|
|
126
|
-
next(OK)
|
|
124
|
+
connection.results.add(plugin, connection.tls)
|
|
125
|
+
plugin.emit_upgrade_msg(connection, verified, verifyErr, cert, cipher)
|
|
126
|
+
next(OK)
|
|
127
127
|
})
|
|
128
128
|
})
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
exports.hook_disconnect = (next, connection) => {
|
|
132
132
|
if (connection.notes.cleanUpDisconnect) {
|
|
133
|
-
connection.notes.cleanUpDisconnect(true)
|
|
133
|
+
connection.notes.cleanUpDisconnect(true)
|
|
134
134
|
}
|
|
135
|
-
next()
|
|
135
|
+
next()
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
exports.emit_upgrade_msg = function (conn, verified, verifyErr, cert, cipher) {
|
|
139
|
-
let msg = 'secured:'
|
|
139
|
+
let msg = 'secured:'
|
|
140
140
|
if (cipher) {
|
|
141
|
-
msg += ` cipher=${cipher.name} version=${cipher.version}
|
|
141
|
+
msg += ` cipher=${cipher.name} version=${cipher.version}`
|
|
142
142
|
}
|
|
143
|
-
msg += ` verified=${verified}
|
|
144
|
-
if (verifyErr) msg += ` error="${verifyErr}"
|
|
143
|
+
msg += ` verified=${verified}`
|
|
144
|
+
if (verifyErr) msg += ` error="${verifyErr}"`
|
|
145
145
|
if (cert) {
|
|
146
146
|
if (cert.subject) {
|
|
147
|
-
msg += ` cn="${cert.subject.CN}" organization="${cert.subject.O}"
|
|
147
|
+
msg += ` cn="${cert.subject.CN}" organization="${cert.subject.O}"`
|
|
148
148
|
}
|
|
149
|
-
if (cert.issuer)
|
|
150
|
-
if (cert.valid_to)
|
|
151
|
-
if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}
|
|
149
|
+
if (cert.issuer) msg += ` issuer="${cert.issuer.O}"`
|
|
150
|
+
if (cert.valid_to) msg += ` expires="${cert.valid_to}"`
|
|
151
|
+
if (cert.fingerprint) msg += ` fingerprint=${cert.fingerprint}`
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
conn.loginfo(this,
|
|
155
|
-
return msg
|
|
154
|
+
conn.loginfo(this, msg)
|
|
155
|
+
return msg
|
|
156
156
|
}
|