Haraka 2.8.28 → 3.0.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/.eslintrc.yaml +2 -10
- package/Changes.md +84 -2
- package/Dockerfile +1 -1
- package/Plugins.md +9 -4
- package/README.md +2 -6
- package/bin/haraka +5 -4
- package/config/outbound.ini +0 -7
- package/config/plugins +1 -1
- package/config/smtp.ini +1 -1
- package/config/smtp_forward.ini +2 -8
- package/config/smtp_proxy.ini +0 -6
- package/connection.js +178 -204
- package/coverage/lcov.info +13863 -0
- package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
- package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
- package/dkim.js +66 -73
- package/docs/Body.md +1 -22
- package/docs/CoreConfig.md +2 -2
- package/docs/Header.md +1 -47
- package/docs/Outbound.md +8 -36
- package/endpoint.js +1 -1
- package/haraka.js +1 -1
- package/host_pool.js +8 -12
- package/logger.js +25 -32
- package/outbound/client_pool.js +11 -153
- package/outbound/config.js +5 -11
- package/outbound/hmail.js +109 -143
- package/outbound/index.js +13 -25
- package/outbound/mx_lookup.js +10 -7
- package/outbound/queue.js +8 -12
- package/outbound/timer_queue.js +2 -4
- package/outbound/tls.js +17 -18
- package/outbound/todo.js +1 -0
- package/package.json +57 -55
- package/plugins/auth/auth_base.js +39 -63
- package/plugins/auth/auth_bridge.js +3 -4
- package/plugins/auth/auth_proxy.js +16 -16
- package/plugins/auth/auth_vpopmaild.js +30 -37
- package/plugins/auth/flat_file.js +9 -13
- package/plugins/avg.js +9 -11
- package/plugins/backscatterer.js +1 -1
- package/plugins/block_me.js +2 -6
- package/plugins/bounce.js +106 -124
- package/plugins/clamd.js +59 -63
- package/plugins/data.signatures.js +6 -6
- package/plugins/data.uribl.js +1 -415
- package/plugins/delay_deny.js +19 -20
- package/plugins/dkim_sign.js +56 -62
- package/plugins/dkim_verify.js +9 -8
- package/plugins/dns_list_base.js +43 -42
- package/plugins/dnsbl.js +41 -46
- package/plugins/dnswl.js +23 -26
- package/plugins/early_talker.js +24 -28
- package/plugins/esets.js +8 -11
- package/plugins/greylist.js +161 -190
- package/plugins/helo.checks.js +175 -197
- package/plugins/mail_from.is_resolvable.js +38 -38
- package/plugins/messagesniffer.js +33 -40
- package/plugins/prevent_credential_leaks.js +7 -5
- package/plugins/process_title.js +16 -17
- package/plugins/queue/deliver.js +2 -2
- package/plugins/queue/lmtp.js +5 -6
- package/plugins/queue/qmail-queue.js +11 -13
- package/plugins/queue/quarantine.js +25 -34
- package/plugins/queue/rabbitmq.js +3 -2
- package/plugins/queue/rabbitmq_amqplib.js +9 -9
- package/plugins/queue/smtp_bridge.js +5 -4
- package/plugins/queue/smtp_forward.js +81 -89
- package/plugins/queue/smtp_proxy.js +21 -22
- package/plugins/queue/test.js +2 -1
- package/plugins/rcpt_to.host_list_base.js +20 -30
- package/plugins/rcpt_to.in_host_list.js +12 -14
- package/plugins/rcpt_to.max_count.js +7 -5
- package/plugins/record_envelope_addresses.js +4 -6
- package/plugins/relay.js +64 -74
- package/plugins/reseed_rng.js +1 -2
- package/plugins/spamassassin.js +56 -68
- package/plugins/status.js +2 -3
- package/plugins/tarpit.js +8 -11
- package/plugins/tls.js +14 -17
- package/plugins/toobusy.js +6 -8
- package/plugins/xclient.js +14 -25
- package/plugins.js +24 -29
- package/rfc1869.js +2 -2
- package/server.js +3 -13
- package/smtp_client.js +138 -215
- package/tests/config/smtp_forward.ini +0 -6
- package/tests/fixtures/line_socket.js +1 -1
- package/tests/fixtures/util_hmailitem.js +5 -7
- package/tests/fixtures/vm_harness.js +2 -2
- package/tests/host_pool.js +13 -14
- package/tests/installation/plugins/inherits.js +1 -2
- package/tests/logger.js +2 -2
- package/tests/plugins/bounce.js +6 -8
- package/tests/plugins/dkim_signer.js +7 -7
- package/tests/plugins/dns_list_base.js +7 -7
- package/tests/plugins/helo.checks.js +1 -1
- package/tests/plugins/mail_from.is_resolvable.js +10 -54
- package/tests/plugins/queue/smtp_forward.js +11 -11
- package/tests/plugins/rcpt_to.host_list_base.js +1 -1
- package/tests/plugins/rcpt_to.in_host_list.js +1 -1
- package/tests/plugins/spamassassin.js +1 -1
- package/tests/queue/multibyte +0 -0
- package/tests/queue/plain +0 -0
- package/tests/rfc1869.js +4 -1
- package/tests/server.js +15 -9
- package/tests/smtp_client/auth.js +4 -14
- package/tests/smtp_client/basic.js +5 -15
- package/tests/smtp_client.js +7 -3
- package/tests/transaction.js +72 -19
- package/tls_socket.js +75 -85
- package/transaction.js +7 -9
- package/attachment_stream.js +0 -118
- package/bin/spf +0 -48
- package/chunkemitter.js +0 -75
- package/config/data.uribl.excludes +0 -202
- package/config/data.uribl.ini +0 -37
- package/config/spf.ini +0 -1
- package/docs/plugins/attachment.md +0 -92
- package/docs/plugins/data.uribl.md +0 -120
- package/docs/plugins/spf.md +0 -142
- package/mailbody.js +0 -502
- package/mailheader.js +0 -304
- package/messagestream.js +0 -441
- package/plugins/aliases.js +0 -120
- package/plugins/attachment.js +0 -503
- package/plugins/connect.p0f.js +0 -5
- package/plugins/spf.js +0 -327
- package/spf.js +0 -689
- package/tests/mailbody.js +0 -348
- package/tests/mailheader.js +0 -138
- package/tests/messagestream.js +0 -34
- package/tests/plugins/aliases.js +0 -376
- package/tests/plugins/spf.js +0 -251
- package/tests/spf.js +0 -96
package/plugins/attachment.js
DELETED
|
@@ -1,503 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/*eslint no-shadow: ["error", { "allow": ["file", "depth", "code", "signal"] }]*/
|
|
3
|
-
// attachment
|
|
4
|
-
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const { spawn } = require('child_process');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
const utils = require('haraka-utils');
|
|
10
|
-
|
|
11
|
-
let tmp;
|
|
12
|
-
let bsdtar_path;
|
|
13
|
-
let archives_disabled = false;
|
|
14
|
-
const default_archive_extns = [
|
|
15
|
-
'.zip', '.tar', '.tgz', '.taz', '.z', '.gz', '.rar', '.7z'
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
exports.register = function () {
|
|
19
|
-
try {
|
|
20
|
-
tmp = require('tmp');
|
|
21
|
-
tmp.setGracefulCleanup();
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
archives_disabled = true;
|
|
25
|
-
this.logwarn(`This plugin requires the 'tmp' module to extract filenames from archive files`);
|
|
26
|
-
}
|
|
27
|
-
this.load_attachment_ini();
|
|
28
|
-
this.register_hook('data_post', 'wait_for_attachment_hooks');
|
|
29
|
-
this.register_hook('data_post', 'check_attachments');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
exports.load_attachment_ini = function () {
|
|
33
|
-
const plugin = this;
|
|
34
|
-
|
|
35
|
-
plugin.cfg = plugin.config.get('attachment.ini', () => {
|
|
36
|
-
plugin.load_attachment_ini();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
plugin.cfg.timeout = (plugin.cfg.main.timeout || 30) * 1000;
|
|
40
|
-
plugin.archive_max_depth = plugin.cfg.main.archive_max_depth || 5;
|
|
41
|
-
plugin.archive_exts = options_to_array(plugin.cfg.main.archive_extensions) ||
|
|
42
|
-
default_archive_extns;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
exports.find_bsdtar_path = cb => {
|
|
46
|
-
let found = false;
|
|
47
|
-
let i = 0;
|
|
48
|
-
['/bin', '/usr/bin', '/usr/local/bin'].forEach((dir) => {
|
|
49
|
-
if (found) return;
|
|
50
|
-
i++;
|
|
51
|
-
fs.stat(`${dir}/bsdtar`, (err, stats) => {
|
|
52
|
-
i--;
|
|
53
|
-
if (found) return;
|
|
54
|
-
if (err) {
|
|
55
|
-
if (i===0) cb(new Error('bsdtar not found'));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
found = true;
|
|
59
|
-
cb(null, dir);
|
|
60
|
-
});
|
|
61
|
-
if (i===0) cb(new Error('bsdtar not found'));
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
exports.hook_init_master = exports.hook_init_child = function (next) {
|
|
66
|
-
const plugin = this;
|
|
67
|
-
plugin.find_bsdtar_path((err, dir) => {
|
|
68
|
-
if (err) {
|
|
69
|
-
archives_disabled = true;
|
|
70
|
-
plugin.logwarn(`This plugin requires the 'bsdtar' binary to extract filenames from archive files`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
plugin.logdebug(`found bsdtar in ${dir}`);
|
|
74
|
-
bsdtar_path = `${dir}/bsdtar`;
|
|
75
|
-
}
|
|
76
|
-
return next();
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function options_to_array (options) {
|
|
81
|
-
if (!options) return false;
|
|
82
|
-
const arr = options.toLowerCase().replace(/\s+/,' ').split(/[;, ]/);
|
|
83
|
-
let len = arr.length;
|
|
84
|
-
while (len--) {
|
|
85
|
-
// Remove any empty elements
|
|
86
|
-
if (arr[len] === "" || arr[len] === null) {
|
|
87
|
-
arr.splice(len, 1);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
arr[len] = arr[len].trim();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return (arr.length ? arr : false);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
exports.unarchive_recursive = function (connection, f, archive_file_name, cb) {
|
|
97
|
-
if (archives_disabled) {
|
|
98
|
-
connection.logdebug(this, 'archive support disabled');
|
|
99
|
-
return cb();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const plugin = this;
|
|
103
|
-
const files = [];
|
|
104
|
-
const tmpfiles = [];
|
|
105
|
-
const depth_exceeded = false;
|
|
106
|
-
let count = 0;
|
|
107
|
-
let done_cb = false;
|
|
108
|
-
let timer;
|
|
109
|
-
|
|
110
|
-
function do_cb (err, files2) {
|
|
111
|
-
if (timer) clearTimeout(timer);
|
|
112
|
-
if (done_cb) return;
|
|
113
|
-
|
|
114
|
-
done_cb = true;
|
|
115
|
-
deleteTempFiles();
|
|
116
|
-
return cb(err, files2);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function deleteTempFiles () {
|
|
120
|
-
tmpfiles.forEach(t => {
|
|
121
|
-
fs.close(t[0], () => {
|
|
122
|
-
connection.logdebug(plugin, `closed fd: ${t[0]}`);
|
|
123
|
-
fs.unlink(t[1], () => {
|
|
124
|
-
connection.logdebug(plugin, `deleted tempfile: ${t[1]}`);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function listFiles (in_file, prefix, depth) {
|
|
131
|
-
if (!depth) depth = 0;
|
|
132
|
-
if (depth >= plugin.archive_max_depth || depth_exceeded) {
|
|
133
|
-
if (count === 0) {
|
|
134
|
-
return do_cb(new Error('maximum archive depth exceeded'));
|
|
135
|
-
}
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
count++;
|
|
139
|
-
const bsdtar = spawn(bsdtar_path, [ '-tf', in_file ], {
|
|
140
|
-
'cwd': '/tmp',
|
|
141
|
-
'env': { 'LANG': 'C' },
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Start timer
|
|
145
|
-
let t1_timeout = false;
|
|
146
|
-
const t1_timer = setTimeout(() => {
|
|
147
|
-
t1_timeout = true;
|
|
148
|
-
bsdtar.kill();
|
|
149
|
-
return do_cb(new Error('bsdtar timed out'));
|
|
150
|
-
}, plugin.cfg.timeout);
|
|
151
|
-
|
|
152
|
-
let lines = '';
|
|
153
|
-
bsdtar.stdout.on('data', (data) => {
|
|
154
|
-
lines += data;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
let stderr = '';
|
|
158
|
-
bsdtar.stderr.on('data', (data) => {
|
|
159
|
-
stderr += data;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
bsdtar.on('exit', (code, signal) => {
|
|
163
|
-
count--;
|
|
164
|
-
if (t1_timeout) return;
|
|
165
|
-
clearTimeout(t1_timer);
|
|
166
|
-
if (code && code > 0) {
|
|
167
|
-
// Error was returned
|
|
168
|
-
return do_cb(new Error(`bsdtar returned error code: ${code} error=${stderr.replace(/\r?\n/,' ')}`));
|
|
169
|
-
}
|
|
170
|
-
if (signal) {
|
|
171
|
-
// Process terminated due to signal
|
|
172
|
-
return do_cb(new Error(`bsdtar terminated by signal: ${signal}`));
|
|
173
|
-
}
|
|
174
|
-
// Process filenames
|
|
175
|
-
const fl = lines.split(/\r?\n/);
|
|
176
|
-
for (let i=0; i<fl.length; i++) {
|
|
177
|
-
const file = fl[i];
|
|
178
|
-
// Skip any blank lines
|
|
179
|
-
if (!file) continue;
|
|
180
|
-
connection.logdebug(plugin, `file: ${file} depth=${depth}`);
|
|
181
|
-
files.push((prefix ? `${prefix}/` : '') + file);
|
|
182
|
-
const extn = path.extname(file.toLowerCase());
|
|
183
|
-
if (!plugin.archive_exts.includes(extn) &&
|
|
184
|
-
!plugin.archive_exts.includes(extn.substring(1))) {
|
|
185
|
-
// Not an archive file extension
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
connection.logdebug(plugin, `need to extract file: ${file}`);
|
|
189
|
-
count++;
|
|
190
|
-
depth++;
|
|
191
|
-
((file, depth) => {
|
|
192
|
-
tmp.file((err, tmpfile, fd) => {
|
|
193
|
-
count--;
|
|
194
|
-
if (err) return do_cb(err.message);
|
|
195
|
-
connection.logdebug(plugin, `created tmp file: ${tmpfile} (fd=${fd}) for file ${prefix ? `${prefix}/` : ''} ${file}`);
|
|
196
|
-
tmpfiles.push([fd, tmpfile]);
|
|
197
|
-
// Extract this file from the archive
|
|
198
|
-
count++;
|
|
199
|
-
const cmd = spawn(bsdtar_path,
|
|
200
|
-
[ '-Oxf', in_file, `--include=${file}` ],
|
|
201
|
-
{
|
|
202
|
-
'cwd': '/tmp',
|
|
203
|
-
'env': {
|
|
204
|
-
'LANG': 'C'
|
|
205
|
-
},
|
|
206
|
-
}
|
|
207
|
-
);
|
|
208
|
-
// Start timer
|
|
209
|
-
let t2_timeout = false;
|
|
210
|
-
const t2_timer = setTimeout(() => {
|
|
211
|
-
t2_timeout = true;
|
|
212
|
-
return do_cb(new Error(`bsdtar timed out extracting file ${file}`));
|
|
213
|
-
}, plugin.cfg.timeout);
|
|
214
|
-
|
|
215
|
-
// Create WriteStream for this file
|
|
216
|
-
const tws = fs.createWriteStream(tmpfile, { fd });
|
|
217
|
-
err = "";
|
|
218
|
-
|
|
219
|
-
cmd.stderr.on('data', (data) => {
|
|
220
|
-
err += data;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
cmd.on('exit', (code, signal) => {
|
|
224
|
-
count--;
|
|
225
|
-
if (t2_timeout) return;
|
|
226
|
-
clearTimeout(t2_timer);
|
|
227
|
-
if (code && code > 0) {
|
|
228
|
-
// Error was returned
|
|
229
|
-
return do_cb(new Error(`bsdtar returned error code: ${code} error=${err.replace(/\r?\n/,' ')}`));
|
|
230
|
-
}
|
|
231
|
-
if (signal) {
|
|
232
|
-
// Process terminated due to signal
|
|
233
|
-
return do_cb(new Error(`bsdtar terminated by signal: ${signal}`));
|
|
234
|
-
}
|
|
235
|
-
// Recurse
|
|
236
|
-
return listFiles(tmpfile, (prefix ? `${prefix}/` : '') + file, depth);
|
|
237
|
-
});
|
|
238
|
-
cmd.stdout.pipe(tws);
|
|
239
|
-
});
|
|
240
|
-
})(file, depth);
|
|
241
|
-
}
|
|
242
|
-
connection.loginfo(plugin, `finish: count=${count} depth=${depth}`);
|
|
243
|
-
if (count === 0) {
|
|
244
|
-
return do_cb(null, files);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
timer = setTimeout(() => {
|
|
250
|
-
return do_cb(new Error('timeout unpacking attachments'));
|
|
251
|
-
}, plugin.cfg.timeout);
|
|
252
|
-
|
|
253
|
-
listFiles(f, archive_file_name);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
exports.start_attachment = function (connection, ctype, filename, body, stream) {
|
|
257
|
-
const plugin = this;
|
|
258
|
-
const txn = connection.transaction;
|
|
259
|
-
|
|
260
|
-
function next () {
|
|
261
|
-
if (txn.notes.attachment_count === 0 && txn.notes.attachment_next) {
|
|
262
|
-
return txn.notes.attachment_next();
|
|
263
|
-
}
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Parse Content-Type
|
|
268
|
-
let ct;
|
|
269
|
-
if ((ct = ctype.match(/^([^/]+\/[^;\r\n ]+)/)) && ct[1]) {
|
|
270
|
-
connection.logdebug(plugin, `found content type: ${ct[1]}`);
|
|
271
|
-
txn.notes.attachment_ctypes.push(ct[1]);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Parse filename
|
|
275
|
-
let ext;
|
|
276
|
-
let fileext = '.unknown';
|
|
277
|
-
if (filename) {
|
|
278
|
-
if ((ext = filename.match(/(\.[^. ]+)$/)) && ext[1]) {
|
|
279
|
-
fileext = ext[1].toLowerCase();
|
|
280
|
-
}
|
|
281
|
-
txn.notes.attachment_files.push(filename);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Calculate and report the md5 of each attachment
|
|
285
|
-
const md5 = crypto.createHash('md5');
|
|
286
|
-
let digest;
|
|
287
|
-
let bytes = 0;
|
|
288
|
-
|
|
289
|
-
stream.on('data', (data) => {
|
|
290
|
-
md5.update(data);
|
|
291
|
-
bytes += data.length;
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
stream.once('end', () => {
|
|
295
|
-
stream.pause();
|
|
296
|
-
|
|
297
|
-
digest = md5.digest('hex');
|
|
298
|
-
connection.loginfo(plugin, `file="${filename}" ctype="${ctype}" md5=${digest} bytes=${bytes}`);
|
|
299
|
-
txn.notes.attachments.push({
|
|
300
|
-
ctype: ((ct && ct[1]) ? ct[1].toLowerCase() : 'unknown/unknown'),
|
|
301
|
-
filename: (filename ? filename : ''),
|
|
302
|
-
extension: (ext && ext[1] ? ext[1].toLowerCase() : ''),
|
|
303
|
-
md5: ((digest) ? digest : ''),
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
if (!filename) return;
|
|
308
|
-
connection.logdebug(plugin, `found attachment file: ${filename}`);
|
|
309
|
-
// See if filename extension matches archive extension list
|
|
310
|
-
// We check with the dot prefixed and without
|
|
311
|
-
if (archives_disabled || (!plugin.archive_exts.includes(fileext) &&
|
|
312
|
-
!plugin.archive_exts.includes(fileext.substring(1)))) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
connection.logdebug(plugin, `found ${fileext} on archive list`);
|
|
316
|
-
txn.notes.attachment_count++;
|
|
317
|
-
stream.connection = connection;
|
|
318
|
-
stream.pause();
|
|
319
|
-
tmp.file((err, fn, fd) => {
|
|
320
|
-
function cleanup () {
|
|
321
|
-
fs.close(fd, () => {
|
|
322
|
-
connection.logdebug(plugin, `closed fd: ${fd}`);
|
|
323
|
-
fs.unlink(fn, () => {
|
|
324
|
-
connection.logdebug(plugin, `unlinked: ${fn}`);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
stream.resume();
|
|
328
|
-
}
|
|
329
|
-
if (err) {
|
|
330
|
-
txn.notes.attachment_result = [ DENYSOFT, err.message ];
|
|
331
|
-
connection.logerror(plugin, `Error writing tempfile: ${err.message}`);
|
|
332
|
-
txn.notes.attachment_count--;
|
|
333
|
-
cleanup();
|
|
334
|
-
stream.resume();
|
|
335
|
-
return next();
|
|
336
|
-
}
|
|
337
|
-
connection.logdebug(plugin, `Got tmpfile: attachment="${filename}" tmpfile="${fn}" fd={fd}`);
|
|
338
|
-
|
|
339
|
-
const ws = fs.createWriteStream(fn);
|
|
340
|
-
stream.pipe(ws);
|
|
341
|
-
stream.resume();
|
|
342
|
-
|
|
343
|
-
ws.on('error', (error) => {
|
|
344
|
-
txn.notes.attachment_count--;
|
|
345
|
-
txn.notes.attachment_result = [ DENYSOFT, error.message ];
|
|
346
|
-
connection.logerror(plugin, `stream error: ${error.message}`);
|
|
347
|
-
cleanup();
|
|
348
|
-
return next();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
ws.on('close', () => {
|
|
352
|
-
connection.logdebug(plugin, 'end of stream reached');
|
|
353
|
-
connection.pause();
|
|
354
|
-
plugin.unarchive_recursive(connection, fn, filename, (error, files) => {
|
|
355
|
-
txn.notes.attachment_count--;
|
|
356
|
-
cleanup();
|
|
357
|
-
if (err) {
|
|
358
|
-
connection.logerror(plugin, error.message);
|
|
359
|
-
if (err.message === 'maximum archive depth exceeded') {
|
|
360
|
-
txn.notes.attachment_result = [ DENY, 'Message contains nested archives exceeding the maximum depth' ];
|
|
361
|
-
}
|
|
362
|
-
else if (/Encrypted file is unsupported/i.test(error.message)) {
|
|
363
|
-
if (!plugin.cfg.main.allow_encrypted_archives) {
|
|
364
|
-
txn.notes.attachment_result = [ DENY, 'Message contains encrypted archive' ];
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
else if (/Mac metadata is too large/i.test(error.message)) {
|
|
368
|
-
// Skip this error
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
if (!connection.relaying) {
|
|
372
|
-
txn.notes.attachment_result = [ DENYSOFT, 'Error unpacking archive' ];
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
txn.notes.attachment_archive_files = txn.notes.attachment_archive_files.concat(files);
|
|
378
|
-
}
|
|
379
|
-
connection.resume();
|
|
380
|
-
return next();
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
exports.hook_data = function (next, connection) {
|
|
388
|
-
const plugin = this;
|
|
389
|
-
const txn = connection.transaction;
|
|
390
|
-
txn.parse_body = 1;
|
|
391
|
-
txn.notes.attachment_count = 0;
|
|
392
|
-
txn.notes.attachments = [];
|
|
393
|
-
txn.notes.attachment_ctypes = [];
|
|
394
|
-
txn.notes.attachment_files = [];
|
|
395
|
-
txn.notes.attachment_archive_files = [];
|
|
396
|
-
txn.attachment_hooks((ctype, filename, body, stream) => {
|
|
397
|
-
plugin.start_attachment(connection, ctype, filename, body, stream);
|
|
398
|
-
});
|
|
399
|
-
return next();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
exports.check_attachments = function (next, connection) {
|
|
403
|
-
const txn = connection.transaction;
|
|
404
|
-
const ctype_config = this.config.get('attachment.ctype.regex','list');
|
|
405
|
-
const file_config = this.config.get('attachment.filename.regex','list');
|
|
406
|
-
const archive_config = this.config.get('attachment.archive.filename.regex','list');
|
|
407
|
-
|
|
408
|
-
// Add in any wildcard configuration
|
|
409
|
-
const ctype_wc = this.config.get('attachment.ctype.wc', 'list');
|
|
410
|
-
for (let i=0; i<ctype_wc.length; i++) {
|
|
411
|
-
ctype_config.push(utils.wildcard_to_regexp(ctype_wc[i]));
|
|
412
|
-
}
|
|
413
|
-
const file_wc = this.config.get('attachment.filename.wc', 'list');
|
|
414
|
-
for (let i=0; i<file_wc.length; i++) {
|
|
415
|
-
file_config.push(utils.wildcard_to_regexp(file_wc[i]));
|
|
416
|
-
}
|
|
417
|
-
const archive_wc = this.config.get('attachment.archive.filename.wc', 'list');
|
|
418
|
-
for (let i=0; i<archive_wc.length; i++) {
|
|
419
|
-
archive_config.push(utils.wildcard_to_regexp(archive_wc[i]));
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Check for any stored errors from the attachment hooks
|
|
423
|
-
if (txn.notes.attachment_result) {
|
|
424
|
-
const result = txn.notes.attachment_result;
|
|
425
|
-
return next(result[0], result[1]);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const ctypes = txn.notes.attachment_ctypes;
|
|
429
|
-
|
|
430
|
-
// Add in any content type from message body
|
|
431
|
-
const body = txn.body;
|
|
432
|
-
let body_ct;
|
|
433
|
-
if (body && (body_ct = /^([^/]+\/[^;\r\n ]+)/.exec(body.header.get('content-type')))) {
|
|
434
|
-
connection.logdebug(this, `found content type: ${body_ct[1]}`);
|
|
435
|
-
ctypes.push(body_ct[1]);
|
|
436
|
-
}
|
|
437
|
-
// MIME parts
|
|
438
|
-
if (body && body.children) {
|
|
439
|
-
for (let c=0; c<body.children.length; c++) {
|
|
440
|
-
let child_ct;
|
|
441
|
-
if (body.children[c] && (child_ct = /^([^/]+\/[^;\r\n ]+)/.exec(body.children[c].header.get('content-type')))) {
|
|
442
|
-
connection.logdebug(this, `found content type: ${child_ct[1]}`);
|
|
443
|
-
ctypes.push(child_ct[1]);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const ctypes_result = this.check_items_against_regexps(ctypes, ctype_config);
|
|
449
|
-
if (ctypes_result) {
|
|
450
|
-
connection.loginfo(this, `match ctype="${ctypes_result[0]}" regexp=/${ctypes_result[1]}/`);
|
|
451
|
-
return next(DENY, `Message contains unacceptable content type (${ctypes_result[0]})`);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const files = txn.notes.attachment_files;
|
|
455
|
-
const files_result = this.check_items_against_regexps(files, file_config);
|
|
456
|
-
if (files_result) {
|
|
457
|
-
connection.loginfo(this, `match file="${files_result[0]}" regexp=/${files_result[1]}/`);
|
|
458
|
-
return next(DENY, `Message contains unacceptable attachment (${files_result[0]})`);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const archive_files = txn.notes.attachment_archive_files;
|
|
462
|
-
const archives_result = this.check_items_against_regexps(archive_files, archive_config);
|
|
463
|
-
if (archives_result) {
|
|
464
|
-
connection.loginfo(this, `match file="${archives_result[0]}" regexp=/${archives_result[1]}/`);
|
|
465
|
-
return next(DENY, `Message contains unacceptable attachment (${archives_result[0]})`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return next();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
exports.check_items_against_regexps = function (items, regexps) {
|
|
472
|
-
if ((regexps && Array.isArray(regexps) && regexps.length > 0) &&
|
|
473
|
-
(items && Array.isArray(items) && items.length > 0)) {
|
|
474
|
-
for (let r=0; r < regexps.length; r++) {
|
|
475
|
-
let reg;
|
|
476
|
-
try {
|
|
477
|
-
reg = new RegExp(regexps[r], 'i');
|
|
478
|
-
}
|
|
479
|
-
catch (e) {
|
|
480
|
-
this.logerror(`skipping invalid regexp: /${regexps[r]}/ (${e})`);
|
|
481
|
-
}
|
|
482
|
-
if (reg) {
|
|
483
|
-
for (let i=0; i < items.length; i++) {
|
|
484
|
-
if (reg.test(items[i])) {
|
|
485
|
-
return [ items[i], regexps[r] ];
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
exports.wait_for_attachment_hooks = (next, connection) => {
|
|
495
|
-
const txn = connection.transaction;
|
|
496
|
-
if (txn.notes.attachment_count > 0) {
|
|
497
|
-
// this.loginfo("We still have attachment hooks running");
|
|
498
|
-
txn.notes.attachment_next = next;
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
next();
|
|
502
|
-
}
|
|
503
|
-
}
|