Haraka 3.1.3 → 3.1.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/.prettierignore +2 -0
- package/CONTRIBUTORS.md +23 -1
- package/Changes.md +38 -0
- package/Plugins.md +81 -64
- package/README.md +1 -1
- package/bin/haraka +7 -5
- package/connection.js +15 -19
- package/docs/Plugins.md +1 -1
- package/docs/plugins/aliases.md +0 -2
- package/docs/plugins/queue/qmail-queue.md +0 -1
- package/logger.js +2 -2
- package/outbound/hmail.js +76 -83
- package/outbound/index.js +36 -34
- package/outbound/queue.js +231 -176
- package/package.json +25 -26
- package/plugins/prevent_credential_leaks.js +2 -2
- package/plugins/process_title.js +1 -1
- package/plugins/queue/smtp_forward.js +1 -1
- package/plugins/status.js +8 -5
- package/plugins/tls.js +1 -1
- package/plugins.js +19 -14
- package/rfc1869.js +10 -10
- package/run_tests +20 -2
- package/server.js +15 -10
- package/smtp_client.js +2 -9
- package/test/config/tls/haraka.local.pem +47 -47
- package/test/connection.js +286 -147
- package/test/fixtures/line_socket.js +1 -0
- package/test/fixtures/util_hmailitem.js +1 -1
- package/test/outbound/bounce_net_errors.js +176 -0
- package/test/outbound/bounce_rfc3464.js +303 -0
- package/test/outbound/hmail.js +140 -104
- package/test/outbound/index.js +61 -101
- package/test/outbound/qfile.js +25 -25
- package/test/outbound/queue.js +233 -0
- package/test/plugins/queue/smtp_forward.js +1 -1
- package/test/plugins/record_envelope_addresses.js +93 -0
- package/test/plugins/tls.js +2 -2
- package/test/plugins/xclient.js +137 -0
- package/test/rfc1869.js +43 -0
- package/test/smtp_client.js +6 -6
- package/test/transaction.js +486 -201
- package/tls_socket.js +3 -3
- package/transaction.js +33 -10
- package/config/rabbitmq.ini +0 -10
- package/config/rabbitmq_amqplib.ini +0 -19
- package/docs/plugins/queue/rabbitmq.md +0 -34
- package/docs/plugins/queue/rabbitmq_amqplib.md +0 -51
- package/plugins/queue/rabbitmq.js +0 -141
- package/plugins/queue/rabbitmq_amqplib.js +0 -96
- package/test/config/tls/ec.pem +0 -23
- package/test/config/tls/mismatched.pem +0 -49
- package/test/outbound_bounce_net_errors.js +0 -157
- package/test/outbound_bounce_rfc3464.js +0 -366
- package/test/tls_socket.js +0 -273
package/outbound/queue.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const child_process = require('node:child_process')
|
|
4
|
-
const fs = require('node:fs')
|
|
4
|
+
const fs = require('node:fs/promises')
|
|
5
5
|
const path = require('node:path')
|
|
6
6
|
|
|
7
|
-
const async = require('async')
|
|
8
7
|
const { Address } = require('address-rfc2821')
|
|
9
8
|
const config = require('haraka-config')
|
|
10
9
|
|
|
@@ -15,37 +14,92 @@ const obc = require('./config')
|
|
|
15
14
|
const _qfile = require('./qfile')
|
|
16
15
|
const obtls = require('./tls')
|
|
17
16
|
|
|
17
|
+
class Queue {
|
|
18
|
+
constructor(worker) {
|
|
19
|
+
this.worker = worker
|
|
20
|
+
this.tasks = []
|
|
21
|
+
this.running = 0
|
|
22
|
+
this._scheduled = false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
push(task) {
|
|
26
|
+
this.tasks.push(task)
|
|
27
|
+
this._schedule()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
length() {
|
|
31
|
+
return this.tasks.length + this.running
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_schedule() {
|
|
35
|
+
if (this._scheduled) return
|
|
36
|
+
this._scheduled = true
|
|
37
|
+
setImmediate(() => {
|
|
38
|
+
this._scheduled = false
|
|
39
|
+
this._process()
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_process() {
|
|
44
|
+
while (this.running < obc.cfg.concurrency_max && this.tasks.length > 0) {
|
|
45
|
+
this.running++
|
|
46
|
+
|
|
47
|
+
this.worker(this.tasks.shift())
|
|
48
|
+
.catch((err) => {
|
|
49
|
+
logger.error(exports, `Queue worker error: ${err}`)
|
|
50
|
+
})
|
|
51
|
+
.finally(() => {
|
|
52
|
+
this.running--
|
|
53
|
+
this._schedule()
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
18
59
|
exports.name = 'outbound/queue'
|
|
19
60
|
|
|
20
|
-
let queue_dir
|
|
21
61
|
if (config.get('queue_dir')) {
|
|
22
|
-
queue_dir = path.resolve(config.get('queue_dir'))
|
|
62
|
+
exports.queue_dir = path.resolve(config.get('queue_dir'))
|
|
23
63
|
} else if (process.env.HARAKA) {
|
|
24
|
-
queue_dir = path.resolve(process.env.HARAKA, 'queue')
|
|
64
|
+
exports.queue_dir = path.resolve(process.env.HARAKA, 'queue')
|
|
25
65
|
} else {
|
|
26
|
-
queue_dir = path.resolve('test', 'test-queue')
|
|
66
|
+
exports.queue_dir = path.resolve('test', 'test-queue')
|
|
27
67
|
}
|
|
28
68
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const load_queue = async.queue((file, cb) => {
|
|
32
|
-
const hmail = new HMailItem(file, path.join(queue_dir, file))
|
|
69
|
+
const load_queue = new Queue(async (file) => {
|
|
70
|
+
const hmail = new HMailItem(file, path.join(exports.queue_dir, file))
|
|
33
71
|
exports._add_hmail(hmail)
|
|
34
|
-
|
|
35
|
-
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
const onReady = () => {
|
|
74
|
+
hmail.off('error', onError)
|
|
75
|
+
resolve()
|
|
76
|
+
}
|
|
77
|
+
const onError = (err) => {
|
|
78
|
+
hmail.off('ready', onReady)
|
|
79
|
+
reject(err)
|
|
80
|
+
}
|
|
81
|
+
hmail.once('ready', onReady)
|
|
82
|
+
hmail.once('error', onError)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
36
85
|
|
|
37
86
|
let in_progress = 0
|
|
38
|
-
const delivery_queue = (exports.delivery_queue = async
|
|
87
|
+
const delivery_queue = (exports.delivery_queue = new Queue(async (hmail) => {
|
|
39
88
|
in_progress++
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
89
|
+
await new Promise((resolve) => {
|
|
90
|
+
hmail.next_cb = () => {
|
|
91
|
+
in_progress--
|
|
92
|
+
resolve()
|
|
93
|
+
}
|
|
94
|
+
if (obtls.cfg) {
|
|
95
|
+
hmail.send()
|
|
96
|
+
} else {
|
|
97
|
+
obtls.init(() => {
|
|
98
|
+
hmail.send()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
47
101
|
})
|
|
48
|
-
}
|
|
102
|
+
}))
|
|
49
103
|
|
|
50
104
|
const temp_fail_queue = (exports.temp_fail_queue = new TimerQueue())
|
|
51
105
|
|
|
@@ -53,51 +107,48 @@ let queue_count = 0
|
|
|
53
107
|
|
|
54
108
|
exports.get_stats = () => `${in_progress}/${exports.delivery_queue.length()}/${exports.temp_fail_queue.length()}`
|
|
55
109
|
|
|
56
|
-
exports.list_queue = (
|
|
57
|
-
exports._load_cur_queue(null, exports._list_file
|
|
110
|
+
exports.list_queue = async () => {
|
|
111
|
+
return exports._load_cur_queue(null, exports._list_file)
|
|
58
112
|
}
|
|
59
113
|
|
|
60
|
-
exports._stat_file = (file
|
|
114
|
+
exports._stat_file = async (file) => {
|
|
61
115
|
queue_count++
|
|
62
|
-
setImmediate(cb)
|
|
63
116
|
}
|
|
64
117
|
|
|
65
|
-
exports.stat_queue = (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (err) return cb(err)
|
|
69
|
-
return cb(null, self.stats())
|
|
70
|
-
})
|
|
118
|
+
exports.stat_queue = async () => {
|
|
119
|
+
await exports._load_cur_queue(null, exports._stat_file)
|
|
120
|
+
return exports.stats()
|
|
71
121
|
}
|
|
72
122
|
|
|
73
|
-
exports.
|
|
123
|
+
exports.init_queue = async (pid) => {
|
|
74
124
|
// Initialise and load queue
|
|
75
125
|
// This function is called first when not running under cluster,
|
|
76
|
-
exports.ensure_queue_dir()
|
|
77
|
-
exports.delete_dot_files()
|
|
126
|
+
await exports.ensure_queue_dir()
|
|
127
|
+
await exports.delete_dot_files()
|
|
78
128
|
|
|
79
|
-
exports._load_cur_queue(pid, exports._add_file
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
})
|
|
129
|
+
await exports._load_cur_queue(pid, exports._add_file)
|
|
130
|
+
logger.info(exports, `[pid: ${pid}] ${delivery_queue.length()} files in my delivery queue`)
|
|
131
|
+
logger.info(exports, `[pid: ${pid}] ${load_queue.length()} files in my load queue`)
|
|
132
|
+
logger.info(exports, `[pid: ${pid}] ${temp_fail_queue.length()} files in my temp fail queue`)
|
|
84
133
|
}
|
|
85
134
|
|
|
86
|
-
exports._load_cur_queue = (pid, iteratee
|
|
87
|
-
logger.info(exports, 'Loading outbound queue from ', queue_dir)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
135
|
+
exports._load_cur_queue = async (pid, iteratee) => {
|
|
136
|
+
logger.info(exports, 'Loading outbound queue from ', exports.queue_dir)
|
|
137
|
+
let files
|
|
138
|
+
try {
|
|
139
|
+
files = await fs.readdir(exports.queue_dir)
|
|
140
|
+
} catch (err) {
|
|
141
|
+
logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
|
|
142
|
+
throw err
|
|
143
|
+
}
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
exports.cur_time = new Date() // set once so we're not calling it a lot
|
|
94
146
|
|
|
95
|
-
|
|
96
|
-
})
|
|
147
|
+
return await exports.load_queue_files(pid, files, iteratee)
|
|
97
148
|
}
|
|
98
149
|
|
|
99
150
|
exports.read_parts = (file) => {
|
|
100
|
-
if (file.
|
|
151
|
+
if (file.startsWith(_qfile.platformDOT)) {
|
|
101
152
|
logger.warn(exports, `'Skipping' dot-file in queue folder: ${file}`)
|
|
102
153
|
return false
|
|
103
154
|
}
|
|
@@ -116,7 +167,7 @@ exports.read_parts = (file) => {
|
|
|
116
167
|
return parts
|
|
117
168
|
}
|
|
118
169
|
|
|
119
|
-
exports.rename_to_actual_pid = (file, parts
|
|
170
|
+
exports.rename_to_actual_pid = async (file, parts) => {
|
|
120
171
|
// maintain some original details for the rename
|
|
121
172
|
const new_filename = _qfile.name({
|
|
122
173
|
arrival: parts.arrival,
|
|
@@ -125,34 +176,30 @@ exports.rename_to_actual_pid = (file, parts, cb) => {
|
|
|
125
176
|
attempts: parts.attempts,
|
|
126
177
|
})
|
|
127
178
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
})
|
|
179
|
+
try {
|
|
180
|
+
await fs.rename(path.join(exports.queue_dir, file), path.join(exports.queue_dir, new_filename))
|
|
181
|
+
return new_filename
|
|
182
|
+
} catch (err) {
|
|
183
|
+
throw new Error(`Unable to rename queue file: ${file} to ${new_filename} : ${err}`)
|
|
184
|
+
}
|
|
135
185
|
}
|
|
136
186
|
|
|
137
|
-
exports._add_file = (file
|
|
138
|
-
const self = exports
|
|
187
|
+
exports._add_file = async (file) => {
|
|
139
188
|
const parts = _qfile.parts(file)
|
|
140
189
|
|
|
141
|
-
if (parts.next_attempt <=
|
|
190
|
+
if (parts.next_attempt <= exports.cur_time) {
|
|
142
191
|
logger.debug(exports, `File ${file} needs processing now`)
|
|
143
192
|
load_queue.push(file)
|
|
144
193
|
} else {
|
|
145
|
-
logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt -
|
|
146
|
-
temp_fail_queue.add(file, parts.next_attempt -
|
|
194
|
+
logger.debug(exports, `File ${file} needs processing later: ${parts.next_attempt - exports.cur_time}ms`)
|
|
195
|
+
temp_fail_queue.add(file, parts.next_attempt - exports.cur_time, () => {
|
|
147
196
|
load_queue.push(file)
|
|
148
197
|
})
|
|
149
198
|
}
|
|
150
|
-
|
|
151
|
-
cb()
|
|
199
|
+
return file
|
|
152
200
|
}
|
|
153
201
|
|
|
154
|
-
exports.load_queue_files = (pid, input_files, iteratee
|
|
155
|
-
const self = exports
|
|
202
|
+
exports.load_queue_files = async (pid, input_files, iteratee) => {
|
|
156
203
|
const searchPid = parseInt(pid)
|
|
157
204
|
|
|
158
205
|
let stat_renamed = 0
|
|
@@ -164,130 +211,134 @@ exports.load_queue_files = (pid, input_files, iteratee, callback = function () {
|
|
|
164
211
|
logger.info(exports, 'Loading the queue...')
|
|
165
212
|
}
|
|
166
213
|
|
|
167
|
-
|
|
168
|
-
input_files
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!parts) return cb()
|
|
214
|
+
const results = await Promise.all(
|
|
215
|
+
input_files.map(async (file) => {
|
|
216
|
+
const parts = exports.read_parts(file)
|
|
217
|
+
if (!parts) return null
|
|
172
218
|
|
|
173
|
-
if (searchPid) {
|
|
174
|
-
|
|
219
|
+
if (!searchPid) {
|
|
220
|
+
stat_loaded++
|
|
221
|
+
return file
|
|
222
|
+
}
|
|
175
223
|
|
|
176
|
-
|
|
177
|
-
if (error) {
|
|
178
|
-
logger.error(exports, `${error}`)
|
|
179
|
-
return cb()
|
|
180
|
-
}
|
|
224
|
+
if (parts.pid !== searchPid) return null
|
|
181
225
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
186
|
-
} else {
|
|
226
|
+
try {
|
|
227
|
+
const renamed_file = await exports.rename_to_actual_pid(file, parts)
|
|
228
|
+
stat_renamed++
|
|
187
229
|
stat_loaded++
|
|
188
|
-
|
|
230
|
+
return renamed_file
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.error(exports, `${error.message}`)
|
|
233
|
+
return null
|
|
189
234
|
}
|
|
190
|
-
},
|
|
191
|
-
(err, results) => {
|
|
192
|
-
if (err) logger.err(exports, `[pid: ${pid}] ${err}`)
|
|
193
|
-
if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
|
|
194
|
-
logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
|
|
195
|
-
|
|
196
|
-
async.map(
|
|
197
|
-
results.filter((i) => i),
|
|
198
|
-
iteratee,
|
|
199
|
-
callback,
|
|
200
|
-
)
|
|
201
|
-
},
|
|
235
|
+
}),
|
|
202
236
|
)
|
|
237
|
+
|
|
238
|
+
if (searchPid) logger.info(exports, `[pid: ${pid}] ${stat_renamed} files old PID queue fixed up`)
|
|
239
|
+
logger.debug(exports, `[pid: ${pid}] ${stat_loaded} files loaded`)
|
|
240
|
+
|
|
241
|
+
const iterateeResults = await Promise.all(results.filter((i) => i).map(async (item) => await iteratee(item)))
|
|
242
|
+
|
|
243
|
+
return iterateeResults.filter((result) => result !== null && result !== undefined)
|
|
203
244
|
}
|
|
204
245
|
|
|
205
246
|
exports.stats = () => {
|
|
206
247
|
return {
|
|
207
|
-
queue_dir,
|
|
248
|
+
queue_dir: exports.queue_dir,
|
|
208
249
|
queue_count,
|
|
209
250
|
}
|
|
210
251
|
}
|
|
211
252
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
253
|
+
// position `position`. Loops to handle partial reads.
|
|
254
|
+
// Read exactly `length` bytes into `buffer` starting at `offset`, from file
|
|
255
|
+
async function readFull(handle, buffer, offset, length, position) {
|
|
256
|
+
let totalRead = 0
|
|
257
|
+
while (totalRead < length) {
|
|
258
|
+
const { bytesRead } = await handle.read(buffer, offset + totalRead, length - totalRead, position + totalRead)
|
|
259
|
+
if (bytesRead === 0) {
|
|
260
|
+
throw new Error(`Unexpected end of file: read ${totalRead} of ${length} bytes`)
|
|
261
|
+
}
|
|
262
|
+
totalRead += bytesRead
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
exports._list_file = async (file) => {
|
|
267
|
+
let handle
|
|
268
|
+
try {
|
|
269
|
+
const filePath = path.join(exports.queue_dir, file)
|
|
270
|
+
|
|
271
|
+
handle = await fs.open(filePath, 'r')
|
|
272
|
+
|
|
273
|
+
// Read first 4 bytes to get the todo length
|
|
274
|
+
const buf = Buffer.alloc(4)
|
|
275
|
+
await readFull(handle, buf, 0, 4, 0)
|
|
224
276
|
const todo_len = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
td_reader.on('end', () => {
|
|
246
|
-
if (Buffer.byteLength(todo) !== todo_len) {
|
|
247
|
-
console.error("Didn't find right amount of data in todo for file:", file)
|
|
248
|
-
return cb()
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
})
|
|
277
|
+
|
|
278
|
+
const todoBuf = Buffer.alloc(todo_len)
|
|
279
|
+
await readFull(handle, todoBuf, 0, todo_len, 4)
|
|
280
|
+
|
|
281
|
+
const todo = todoBuf.toString('utf8')
|
|
282
|
+
const todo_struct = JSON.parse(todo)
|
|
283
|
+
todo_struct.rcpt_to = todo_struct.rcpt_to.map((a) => new Address(a))
|
|
284
|
+
todo_struct.mail_from = new Address(todo_struct.mail_from)
|
|
285
|
+
todo_struct.file = file
|
|
286
|
+
todo_struct.full_path = filePath
|
|
287
|
+
const parts = _qfile.parts(file)
|
|
288
|
+
todo_struct.pid = parts?.pid || null
|
|
289
|
+
return todo_struct
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error(`Error reading queue file: ${file}:`, err)
|
|
292
|
+
return null
|
|
293
|
+
} finally {
|
|
294
|
+
if (handle)
|
|
295
|
+
await handle.close().catch((err) => console.error(`Failed to close queue file handle for ${file}:`, err))
|
|
296
|
+
}
|
|
252
297
|
}
|
|
253
298
|
|
|
254
|
-
exports.flush_queue = (domain, pid) => {
|
|
299
|
+
exports.flush_queue = async (domain, pid) => {
|
|
255
300
|
if (domain) {
|
|
256
|
-
|
|
257
|
-
|
|
301
|
+
try {
|
|
302
|
+
const qlist = await exports.list_queue()
|
|
258
303
|
for (const todo of qlist) {
|
|
259
|
-
if (todo.domain.toLowerCase()
|
|
260
|
-
if (pid && todo.pid
|
|
261
|
-
// console.log("requeue: ", todo);
|
|
304
|
+
if (todo.domain.toLowerCase() !== domain.toLowerCase()) continue
|
|
305
|
+
if (pid && todo.pid !== pid) continue
|
|
262
306
|
delivery_queue.push(new HMailItem(todo.file, todo.full_path))
|
|
263
307
|
}
|
|
264
|
-
})
|
|
308
|
+
} catch (err) {
|
|
309
|
+
logger.error(exports, `Failed to load queue: ${err.message}`)
|
|
310
|
+
}
|
|
265
311
|
} else {
|
|
266
312
|
temp_fail_queue.drain()
|
|
267
313
|
}
|
|
268
314
|
}
|
|
269
315
|
|
|
270
|
-
exports.load_pid_queue = (pid) => {
|
|
316
|
+
exports.load_pid_queue = async (pid) => {
|
|
271
317
|
logger.info(exports, `Loading queue for pid: ${pid}`)
|
|
272
|
-
exports.
|
|
318
|
+
await exports.init_queue(pid)
|
|
273
319
|
}
|
|
274
320
|
|
|
275
|
-
exports.ensure_queue_dir = () => {
|
|
321
|
+
exports.ensure_queue_dir = async () => {
|
|
276
322
|
// this code is only run at start-up.
|
|
277
|
-
|
|
323
|
+
try {
|
|
324
|
+
await fs.access(exports.queue_dir)
|
|
325
|
+
return // directory already exists
|
|
326
|
+
} catch (ignore) {
|
|
327
|
+
// directory doesn't exist, try to create it
|
|
328
|
+
}
|
|
278
329
|
|
|
279
|
-
logger.debug(exports, `Creating queue directory ${queue_dir}`)
|
|
330
|
+
logger.debug(exports, `Creating queue directory ${exports.queue_dir}`)
|
|
280
331
|
try {
|
|
281
|
-
fs.
|
|
332
|
+
await fs.mkdir(exports.queue_dir, { mode: 493 }) // 493 == 0755
|
|
282
333
|
const cfg = config.get('smtp.ini')
|
|
283
334
|
let uid
|
|
284
335
|
let gid
|
|
285
|
-
if (cfg.user) uid = child_process.execSync(`id -u ${cfg.user}`).toString().trim()
|
|
286
|
-
if (cfg.group) gid = child_process.execSync(`id -g ${cfg.group}`).toString().trim()
|
|
336
|
+
if (cfg.user) uid = parseInt(child_process.execSync(`id -u ${cfg.user}`).toString().trim(), 10)
|
|
337
|
+
if (cfg.group) gid = parseInt(child_process.execSync(`id -g ${cfg.group}`).toString().trim(), 10)
|
|
287
338
|
if (uid && gid) {
|
|
288
|
-
fs.chown(queue_dir, uid, gid)
|
|
339
|
+
await fs.chown(exports.queue_dir, uid, gid)
|
|
289
340
|
} else if (uid) {
|
|
290
|
-
fs.chown(queue_dir, uid)
|
|
341
|
+
await fs.chown(exports.queue_dir, uid, -1)
|
|
291
342
|
}
|
|
292
343
|
} catch (err) {
|
|
293
344
|
if (err.code !== 'EEXIST') {
|
|
@@ -297,17 +348,22 @@ exports.ensure_queue_dir = () => {
|
|
|
297
348
|
}
|
|
298
349
|
}
|
|
299
350
|
|
|
300
|
-
exports.delete_dot_files = () => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
351
|
+
exports.delete_dot_files = async () => {
|
|
352
|
+
try {
|
|
353
|
+
const files = await fs.readdir(exports.queue_dir)
|
|
354
|
+
for (const file of files) {
|
|
355
|
+
if (file.startsWith(_qfile.platformDOT)) {
|
|
356
|
+
logger.warn(exports, `Removing left over dot-file: ${file}`)
|
|
357
|
+
await fs.unlink(path.join(exports.queue_dir, file))
|
|
358
|
+
}
|
|
305
359
|
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
logger.error(exports, `Error deleting dot files: ${err}`)
|
|
306
362
|
}
|
|
307
363
|
}
|
|
308
364
|
|
|
309
365
|
exports._add_hmail = (hmail) => {
|
|
310
|
-
if (hmail.next_process
|
|
366
|
+
if (hmail.next_process <= exports.cur_time) {
|
|
311
367
|
delivery_queue.push(hmail)
|
|
312
368
|
} else {
|
|
313
369
|
temp_fail_queue.add(hmail.filename, hmail.next_process - exports.cur_time, () => {
|
|
@@ -316,26 +372,25 @@ exports._add_hmail = (hmail) => {
|
|
|
316
372
|
}
|
|
317
373
|
}
|
|
318
374
|
|
|
319
|
-
exports.scan_queue_pids = (
|
|
320
|
-
const self = exports
|
|
321
|
-
|
|
375
|
+
exports.scan_queue_pids = async () => {
|
|
322
376
|
// Under cluster, this is called first by the master
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
await exports.ensure_queue_dir()
|
|
378
|
+
await exports.delete_dot_files()
|
|
325
379
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
380
|
+
let files
|
|
381
|
+
try {
|
|
382
|
+
files = await fs.readdir(exports.queue_dir)
|
|
383
|
+
} catch (err) {
|
|
384
|
+
logger.error(exports, `Failed to load queue directory (${exports.queue_dir}): ${err}`)
|
|
385
|
+
throw err
|
|
386
|
+
}
|
|
331
387
|
|
|
332
|
-
|
|
388
|
+
const pids = {}
|
|
333
389
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
390
|
+
for (const file of files) {
|
|
391
|
+
const parts = exports.read_parts(file)
|
|
392
|
+
if (parts) pids[parts.pid] = true
|
|
393
|
+
}
|
|
338
394
|
|
|
339
|
-
|
|
340
|
-
})
|
|
395
|
+
return Object.keys(pids)
|
|
341
396
|
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"server",
|
|
10
10
|
"email"
|
|
11
11
|
],
|
|
12
|
-
"version": "3.1.
|
|
12
|
+
"version": "3.1.4",
|
|
13
13
|
"homepage": "http://haraka.github.io",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -20,26 +20,25 @@
|
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"address-rfc2821": "^2.1.
|
|
23
|
+
"address-rfc2821": "^2.1.5",
|
|
24
24
|
"address-rfc2822": "^2.2.3",
|
|
25
|
-
"async": "^3.2.6",
|
|
26
25
|
"daemon": "~1.1.0",
|
|
27
|
-
"haraka-config": "^1.
|
|
26
|
+
"haraka-config": "^1.5.0",
|
|
28
27
|
"haraka-constants": "^1.0.7",
|
|
29
28
|
"haraka-dsn": "^1.1.0",
|
|
30
|
-
"haraka-email-message": "^1.
|
|
31
|
-
"haraka-message-stream": "^
|
|
29
|
+
"haraka-email-message": "^1.3.1",
|
|
30
|
+
"haraka-message-stream": "^2.0.0",
|
|
32
31
|
"haraka-net-utils": "^1.7.2",
|
|
33
32
|
"haraka-notes": "^1.1.1",
|
|
34
33
|
"haraka-plugin-redis": "^2.0.11",
|
|
35
|
-
"haraka-results": "^2.
|
|
36
|
-
"haraka-tld": "^1.3.
|
|
34
|
+
"haraka-results": "^2.3.0",
|
|
35
|
+
"haraka-tld": "^1.3.1",
|
|
37
36
|
"haraka-utils": "^1.1.4",
|
|
38
37
|
"ipaddr.js": "~2.3.0",
|
|
39
38
|
"node-gyp": "^12.2.0",
|
|
40
39
|
"nopt": "^9.0.0",
|
|
41
40
|
"npid": "~0.4.0",
|
|
42
|
-
"redis": "~5.
|
|
41
|
+
"redis": "~5.11.0",
|
|
43
42
|
"semver": "^7.7.4",
|
|
44
43
|
"sockaddr": "^1.0.1",
|
|
45
44
|
"sprintf-js": "~1.1.3"
|
|
@@ -47,20 +46,20 @@
|
|
|
47
46
|
"optionalDependencies": {
|
|
48
47
|
"haraka-plugin-access": "^1.1.10",
|
|
49
48
|
"haraka-plugin-aliases": "^1.0.3",
|
|
50
|
-
"haraka-plugin-asn": "^2.0.
|
|
51
|
-
"haraka-plugin-attachment": "^1.
|
|
49
|
+
"haraka-plugin-asn": "^2.0.6",
|
|
50
|
+
"haraka-plugin-attachment": "^1.2.0",
|
|
52
51
|
"haraka-plugin-avg": "^1.1.0",
|
|
53
|
-
"haraka-plugin-bounce": "^2.1.
|
|
52
|
+
"haraka-plugin-bounce": "^2.1.2",
|
|
54
53
|
"haraka-plugin-clamd": "^1.0.2",
|
|
55
54
|
"haraka-plugin-dcc": "^1.0.3",
|
|
56
55
|
"haraka-plugin-dkim": "^1.0.11",
|
|
57
56
|
"haraka-plugin-dns-list": "^1.2.4",
|
|
58
57
|
"haraka-plugin-early_talker": "^1.0.2",
|
|
59
|
-
"haraka-plugin-elasticsearch": "^8.1.
|
|
58
|
+
"haraka-plugin-elasticsearch": "^8.1.6",
|
|
60
59
|
"haraka-plugin-esets": "^1.0.1",
|
|
61
60
|
"haraka-plugin-fcrdns": "^1.1.2",
|
|
62
|
-
"haraka-plugin-geoip": "^1.1.
|
|
63
|
-
"haraka-plugin-greylist": "^1.0
|
|
61
|
+
"haraka-plugin-geoip": "^1.1.2",
|
|
62
|
+
"haraka-plugin-greylist": "^1.1.0",
|
|
64
63
|
"haraka-plugin-headers": "^1.1.0",
|
|
65
64
|
"haraka-plugin-helo.checks": "^1.1.0",
|
|
66
65
|
"haraka-plugin-karma": "^2.1.8",
|
|
@@ -69,23 +68,23 @@
|
|
|
69
68
|
"haraka-plugin-mail_from.is_resolvable": "^1.1.0",
|
|
70
69
|
"haraka-plugin-messagesniffer": "^1.0.1",
|
|
71
70
|
"haraka-plugin-p0f": "^1.0.11",
|
|
72
|
-
"haraka-plugin-qmail-deliverable": "^1.2
|
|
71
|
+
"haraka-plugin-qmail-deliverable": "^1.3.2",
|
|
73
72
|
"haraka-plugin-recipient-routes": "^1.3.1",
|
|
74
73
|
"haraka-plugin-relay": "^1.0.1",
|
|
75
74
|
"haraka-plugin-rspamd": "^1.4.2",
|
|
76
75
|
"haraka-plugin-spamassassin": "^1.0.3",
|
|
77
|
-
"haraka-plugin-spf": "^1.2.
|
|
76
|
+
"haraka-plugin-spf": "^1.2.11",
|
|
78
77
|
"haraka-plugin-syslog": "^1.0.7",
|
|
79
78
|
"haraka-plugin-uribl": "^1.0.10",
|
|
80
|
-
"haraka-plugin-watch": "^2.0.
|
|
79
|
+
"haraka-plugin-watch": "^2.0.9",
|
|
81
80
|
"@techteamer/ocsp": "^1.0.1"
|
|
82
81
|
},
|
|
83
82
|
"devDependencies": {
|
|
84
|
-
"@haraka/eslint-config": "^2.0.
|
|
85
|
-
"haraka-test-fixtures": "^1.
|
|
83
|
+
"@haraka/eslint-config": "^2.0.4",
|
|
84
|
+
"haraka-test-fixtures": "^1.4.1",
|
|
86
85
|
"mocha": "^11.7.5",
|
|
87
86
|
"mock-require": "^3.0.3",
|
|
88
|
-
"nodemailer": "^8.0.
|
|
87
|
+
"nodemailer": "^8.0.4"
|
|
89
88
|
},
|
|
90
89
|
"bugs": {
|
|
91
90
|
"mail": "haraka.mail@gmail.com",
|
|
@@ -96,15 +95,15 @@
|
|
|
96
95
|
"haraka_grep": "./bin/haraka_grep"
|
|
97
96
|
},
|
|
98
97
|
"scripts": {
|
|
99
|
-
"format
|
|
98
|
+
"format": "npm run prettier:fix && npm run lint:fix",
|
|
100
99
|
"lint": "npx eslint *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js bin/haraka",
|
|
101
100
|
"lint:fix": "npx eslint --fix *.js outbound plugins plugins/*/*.js test test/*/*.js test/*/*/*.js bin/haraka",
|
|
102
101
|
"prettier": "npx prettier . --check",
|
|
103
102
|
"prettier:fix": "npx prettier . --write --log-level=warn",
|
|
104
|
-
"test": "
|
|
105
|
-
"versions": "npx
|
|
106
|
-
"versions:fix": "npx
|
|
107
|
-
"
|
|
103
|
+
"test": "sh ./run_tests",
|
|
104
|
+
"versions": "npx npm-dep-mgr check",
|
|
105
|
+
"versions:fix": "npx npm-dep-mgr update",
|
|
106
|
+
"test:coverage": "npx c8 --reporter=text --reporter=text-summary npm test"
|
|
108
107
|
},
|
|
109
108
|
"prettier": {
|
|
110
109
|
"singleQuote": true,
|