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/mailheader.js
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// An RFC 2822 email header parser
|
|
3
|
-
/* eslint no-control-regex: 0 */
|
|
4
|
-
|
|
5
|
-
const logger = require('./logger');
|
|
6
|
-
const libmime = require('libmime');
|
|
7
|
-
const libqp = require('libqp');
|
|
8
|
-
let Iconv;
|
|
9
|
-
try { Iconv = require('iconv').Iconv }
|
|
10
|
-
catch (err) {
|
|
11
|
-
logger.logdebug("No iconv available - install with 'npm install iconv'");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class Header {
|
|
15
|
-
constructor (options) {
|
|
16
|
-
this.headers = {};
|
|
17
|
-
this.headers_decoded = {};
|
|
18
|
-
this.header_list = [];
|
|
19
|
-
this.options = options;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
parse (lines) {
|
|
23
|
-
const self = this;
|
|
24
|
-
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
if (/^[ \t]/.test(line)) {
|
|
27
|
-
// continuation
|
|
28
|
-
this.header_list[this.header_list.length - 1] += line;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
this.header_list.push(line);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for (const header of this.header_list) {
|
|
36
|
-
const match = header.match(/^([^\s:]*):\s*([\s\S]*)$/);
|
|
37
|
-
if (match) {
|
|
38
|
-
const key = match[1].toLowerCase();
|
|
39
|
-
const val = match[2];
|
|
40
|
-
|
|
41
|
-
this._add_header(key, val, "push");
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
logger.lognotice(`Header did not look right: ${header}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Now add decoded versions
|
|
49
|
-
Object.keys(this.headers).forEach((key2) => {
|
|
50
|
-
self.headers[key2].forEach((val2) => {
|
|
51
|
-
self._add_header_decode(key2, val2, 'push');
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
decode_header (val) {
|
|
57
|
-
// Fold continuations
|
|
58
|
-
val = val.replace(/\r?\n/g, '');
|
|
59
|
-
|
|
60
|
-
const rfc2231_params = {
|
|
61
|
-
kv: {},
|
|
62
|
-
keys: {},
|
|
63
|
-
cur_key: '',
|
|
64
|
-
cur_enc: '',
|
|
65
|
-
cur_lang: '', // Secondary languages are ignored for our purposes
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
val = _decode_rfc2231(rfc2231_params, val);
|
|
69
|
-
|
|
70
|
-
// console.log(rfc2231_params);
|
|
71
|
-
|
|
72
|
-
// strip 822 comments in the most basic way - does not support nested comments
|
|
73
|
-
// val = val.replace(/\([^\)]*\)/, '');
|
|
74
|
-
|
|
75
|
-
if (Iconv && !/^[\x00-\x7f]*$/.test(val)) {
|
|
76
|
-
// 8 bit values in the header
|
|
77
|
-
const matches = /\bcharset\s*=\s*["']?([\w_-]*)/.exec(this.get('content-type'));
|
|
78
|
-
if (matches && !/UTF-?8/i.test(matches[1])) {
|
|
79
|
-
const encoding = matches[1];
|
|
80
|
-
const source = Buffer.from(val, 'binary');
|
|
81
|
-
val = try_convert(source, encoding).toString();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (! (/=\?/.test(val)) ) {
|
|
86
|
-
// no encoded stuff
|
|
87
|
-
return val;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return val
|
|
91
|
-
// strip whitespace between encoded-words, rfc 2047 6.2
|
|
92
|
-
.replace(/(=\?.+?\?=)\s+(?==\?.+?\?=)/g,"$1")
|
|
93
|
-
// decode each encoded match
|
|
94
|
-
.replace(/=\?([\w_-]+)(\*[\w_-]+)?\?([bqBQ])\?([\s\S]*?)\?=/g, _decode_header);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get (key) {
|
|
98
|
-
return (this.headers[key.toLowerCase()] || []).join("\n");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
get_all (key) {
|
|
102
|
-
return Object.freeze([...(this.headers[key.toLowerCase()] || [])]);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
get_decoded (key) {
|
|
106
|
-
return (this.headers_decoded[key.toLowerCase()] || []).join("\n");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
remove (key) {
|
|
110
|
-
key = key.toLowerCase();
|
|
111
|
-
delete this.headers[key];
|
|
112
|
-
delete this.headers_decoded[key];
|
|
113
|
-
|
|
114
|
-
this._remove_more(key);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
_remove_more (key) {
|
|
118
|
-
const key_len = key.length;
|
|
119
|
-
for (let i=0, l=this.header_list.length; i < l; i++) {
|
|
120
|
-
if (this.header_list[i].substring(0, key_len + 1).toLowerCase() === `${key}:`) {
|
|
121
|
-
this.header_list.splice(i, 1);
|
|
122
|
-
return this._remove_more(key);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
add (key, value) {
|
|
128
|
-
if (!key) key = 'X-Haraka-Blank';
|
|
129
|
-
value = value.replace(/(\r?\n)*$/, '');
|
|
130
|
-
if (/[^\x00-\x7f]/.test(value)) {
|
|
131
|
-
value = libmime.encodeWords(value, 'Q');
|
|
132
|
-
}
|
|
133
|
-
this._add_header(key.toLowerCase(), value, "unshift");
|
|
134
|
-
this._add_header_decode(key.toLowerCase(), value, "unshift");
|
|
135
|
-
this.header_list.unshift(`${key}: ${value}\n`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
_add_header (key, value, method) {
|
|
139
|
-
this.headers[key] = this.headers[key] || [];
|
|
140
|
-
this.headers[key][method](value);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
_add_header_decode (key, value, method) {
|
|
144
|
-
const val = this.decode_header(value);
|
|
145
|
-
// console.log(key + ': ' + val);
|
|
146
|
-
this.headers_decoded[key] = this.headers_decoded[key] || [];
|
|
147
|
-
this.headers_decoded[key][method](val);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
add_end (key, value) {
|
|
151
|
-
if (!key) key = 'X-Haraka-Blank';
|
|
152
|
-
value = value.replace(/(\r?\n)*$/, '');
|
|
153
|
-
if (/[^\x00-\x7f]/.test(value)) {
|
|
154
|
-
value = libmime.encodeWords(value, 'Q');
|
|
155
|
-
}
|
|
156
|
-
this._add_header(key.toLowerCase(), value, "push");
|
|
157
|
-
this._add_header_decode(key.toLowerCase(), value, "push");
|
|
158
|
-
this.header_list.push(`${key}: ${value}\n`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
lines () {
|
|
162
|
-
return Object.freeze([...this.header_list]);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
toString () {
|
|
166
|
-
return this.header_list.join("\n");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
exports.Header = Header;
|
|
171
|
-
exports.Iconv = Iconv;
|
|
172
|
-
|
|
173
|
-
function try_convert (data, encoding) {
|
|
174
|
-
try {
|
|
175
|
-
const converter = new Iconv(encoding, "UTF-8");
|
|
176
|
-
data = converter.convert(data);
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
// TODO: raise a flag for this for possible scoring
|
|
180
|
-
logger.logwarn(`initial iconv conversion from ${encoding} to UTF-8 failed: ${err.message}`);
|
|
181
|
-
if (err.code !== 'EINVAL') {
|
|
182
|
-
try {
|
|
183
|
-
const converter = new Iconv(encoding, "UTF-8//TRANSLIT//IGNORE");
|
|
184
|
-
data = converter.convert(data);
|
|
185
|
-
}
|
|
186
|
-
catch (e) {
|
|
187
|
-
logger.logerror(`iconv from ${encoding} to UTF-8 failed: ${e.message}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return data;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function _decode_header (matched, encoding, lang, cte, data) {
|
|
196
|
-
cte = cte.toUpperCase();
|
|
197
|
-
|
|
198
|
-
switch (cte) {
|
|
199
|
-
case 'Q':
|
|
200
|
-
data = libqp.decode(data.replace(/_/g, ' '));
|
|
201
|
-
break;
|
|
202
|
-
case 'B':
|
|
203
|
-
data = Buffer.from(data, "base64");
|
|
204
|
-
break;
|
|
205
|
-
default:
|
|
206
|
-
logger.logerror(`Invalid header encoding type: ${cte}`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// convert with iconv if encoding != UTF-8
|
|
210
|
-
if (Iconv && !(/UTF-?8/i.test(encoding))) {
|
|
211
|
-
data = try_convert(data, encoding);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return data.toString();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function _decode_rfc2231 (params, str) {
|
|
218
|
-
_parse_rfc2231(params, str);
|
|
219
|
-
|
|
220
|
-
for (const key in params.keys) {
|
|
221
|
-
str += ` ${key}="`;
|
|
222
|
-
/* eslint no-constant-condition: 0 */
|
|
223
|
-
let merged = '';
|
|
224
|
-
for (let i=0; true; i++) {
|
|
225
|
-
const _key = `${key}*${i}`;
|
|
226
|
-
const _val = params.kv[_key];
|
|
227
|
-
if (_val === undefined) break;
|
|
228
|
-
merged += _val;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
merged = decodeURIComponent(merged);
|
|
233
|
-
}
|
|
234
|
-
catch (e) {
|
|
235
|
-
logger.logerror(`Decode header failed: ${key}: ${merged}`);
|
|
236
|
-
}
|
|
237
|
-
merged = params.cur_enc ? try_convert(merged, params.cur_enc) : merged;
|
|
238
|
-
|
|
239
|
-
str += `${merged}";`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return str;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function _parse_rfc2231 (params, str) {
|
|
246
|
-
/*
|
|
247
|
-
To explain the regexp below, the params are:
|
|
248
|
-
|
|
249
|
-
parameter := attribute "=" value
|
|
250
|
-
|
|
251
|
-
attribute := token
|
|
252
|
-
; Matching of attributes
|
|
253
|
-
; is ALWAYS case-insensitive.
|
|
254
|
-
|
|
255
|
-
token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
|
|
256
|
-
or tspecials>
|
|
257
|
-
|
|
258
|
-
tspecials := "(" / ")" / "<" / ">" / "@" /
|
|
259
|
-
"," / ";" / ":" / "\" / <">
|
|
260
|
-
"/" / "[" / "]" / "?" / "="
|
|
261
|
-
; Must be in quoted-string,
|
|
262
|
-
; to use within parameter values
|
|
263
|
-
*/
|
|
264
|
-
const sub_matches = /(([!#$%&'*+.0-9A-Zdiff^_`a-z{|}~-]*)\*)(\d*)=(\s*".*?[^\\]";?|\S*)/.exec(str);
|
|
265
|
-
if (!sub_matches) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
const key = sub_matches[1];
|
|
269
|
-
let key_actual = sub_matches[2];
|
|
270
|
-
let key_id = sub_matches[3] || '0';
|
|
271
|
-
let value = sub_matches[4].replace(/;$/, '');
|
|
272
|
-
|
|
273
|
-
str = str.replace(sub_matches[0], ''); // strip it out, so we move to next
|
|
274
|
-
|
|
275
|
-
const key_extract = /^(.*?)(\*(\d+)\*)$/.exec(key);
|
|
276
|
-
if (key_extract) {
|
|
277
|
-
key_actual = key_extract[1];
|
|
278
|
-
key_id = key_extract[3];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const quote = /^\s*"(.*)"$/.exec(value);
|
|
282
|
-
if (quote) {
|
|
283
|
-
value = quote[1];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const lang_match = /^(.*?)'(.*?)'(.*)/.exec(value);
|
|
287
|
-
if (lang_match) {
|
|
288
|
-
if (key_actual == params.cur_key && lang_match[2] != params.cur_lang) {
|
|
289
|
-
return _parse_rfc2231(params, str); // same key, different lang, throw it away
|
|
290
|
-
}
|
|
291
|
-
params.cur_enc = lang_match[1];
|
|
292
|
-
params.cur_lang = lang_match[2];
|
|
293
|
-
value = lang_match[3];
|
|
294
|
-
}
|
|
295
|
-
else if (key_actual != params.cur_key) {
|
|
296
|
-
params.cur_lang = '';
|
|
297
|
-
params.cur_enc = '';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
params.cur_key = key_actual;
|
|
301
|
-
params.keys[key_actual] = '';
|
|
302
|
-
params.kv[`${key_actual}*${key_id}`] = value;
|
|
303
|
-
return _parse_rfc2231(params, str); // Get next one
|
|
304
|
-
}
|