Haraka 3.0.3 → 3.0.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/.eslintrc.yaml +5 -9
- package/.prettierrc.yml +1 -0
- package/CONTRIBUTORS.md +11 -0
- package/Changes.md +1365 -1214
- package/Plugins.md +117 -105
- package/README.md +4 -13
- package/bin/haraka +197 -298
- package/config/auth_flat_file.ini +1 -0
- package/config/dhparams.pem +8 -0
- package/config/mail_from.is_resolvable.ini +4 -2
- package/config/me +1 -0
- package/config/outbound.ini +0 -2
- package/config/plugins +36 -35
- package/config/smtp.ini +0 -1
- package/config/smtp.json +17 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/connection.js +46 -73
- package/contrib/bsd-rc.d/haraka +3 -1
- package/contrib/plugin2npm.sh +6 -36
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -201
- package/docs/Plugins.md +2 -2
- package/docs/Transaction.md +59 -82
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/tls.md +29 -9
- package/endpoint.js +16 -13
- package/haraka.js +10 -14
- package/host_pool.js +5 -5
- package/line_socket.js +3 -4
- package/logger.js +44 -28
- package/outbound/client_pool.js +27 -23
- package/outbound/config.js +4 -6
- package/outbound/fsync_writestream.js +1 -1
- package/outbound/hmail.js +178 -218
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +51 -44
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +59 -48
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +4 -2
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +1 -1
- package/plugins/block_me.js +1 -1
- package/plugins/data.signatures.js +2 -4
- package/plugins/early_talker.js +2 -1
- package/plugins/mail_from.is_resolvable.js +65 -135
- package/plugins/queue/deliver.js +4 -5
- package/plugins/queue/lmtp.js +11 -14
- package/plugins/queue/qmail-queue.js +2 -2
- package/plugins/queue/quarantine.js +2 -2
- package/plugins/queue/rabbitmq.js +16 -17
- package/plugins/queue/smtp_forward.js +3 -3
- package/plugins/queue/smtp_proxy.js +10 -1
- package/plugins/queue/test.js +2 -2
- package/plugins/rcpt_to.host_list_base.js +5 -5
- package/plugins/rcpt_to.in_host_list.js +2 -2
- package/plugins/relay.js +6 -7
- package/plugins/reseed_rng.js +1 -1
- package/plugins/status.js +37 -33
- package/plugins/tls.js +2 -2
- package/plugins/xclient.js +3 -2
- package/plugins.js +50 -54
- package/run_tests +3 -30
- package/server.js +190 -190
- package/smtp_client.js +30 -23
- package/{tests → test}/config/plugins +0 -2
- package/{tests → test}/config/smtp.ini +1 -1
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/connection.js +302 -0
- package/test/endpoint.js +94 -0
- package/{tests → test}/fixtures/line_socket.js +1 -1
- package/{tests → test}/fixtures/util_hmailitem.js +19 -25
- package/{tests → test}/host_pool.js +42 -57
- package/test/logger.js +258 -0
- package/test/outbound/hmail.js +141 -0
- package/test/outbound/index.js +220 -0
- package/test/outbound/qfile.js +126 -0
- package/test/outbound_bounce_net_errors.js +142 -0
- package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
- package/test/plugins/auth/auth_base.js +484 -0
- package/test/plugins/auth/auth_vpopmaild.js +83 -0
- package/test/plugins/early_talker.js +104 -0
- package/test/plugins/mail_from.is_resolvable.js +35 -0
- package/test/plugins/queue/smtp_forward.js +206 -0
- package/test/plugins/rcpt_to.host_list_base.js +122 -0
- package/test/plugins/rcpt_to.in_host_list.js +193 -0
- package/test/plugins/relay.js +303 -0
- package/test/plugins/status.js +130 -0
- package/test/plugins/tls.js +70 -0
- package/test/plugins.js +228 -0
- package/test/rfc1869.js +73 -0
- package/test/server.js +491 -0
- package/test/smtp_client.js +299 -0
- package/test/tls_socket.js +273 -0
- package/test/transaction.js +270 -0
- package/tls_socket.js +202 -252
- package/transaction.js +8 -23
- package/CONTRIBUTING.md +0 -1
- package/bin/dkimverify +0 -40
- package/config/access.domains +0 -13
- package/config/attachment.ctype.regex +0 -2
- package/config/attachment.filename.regex +0 -1
- package/config/avg.ini +0 -5
- package/config/bounce.ini +0 -15
- package/config/data.headers.ini +0 -61
- package/config/dkim/dkim_key_gen.sh +0 -78
- package/config/dkim_sign.ini +0 -4
- package/config/dkim_verify.ini +0 -7
- package/config/dnsbl.ini +0 -23
- package/config/greylist.ini +0 -43
- package/config/helo.checks.ini +0 -52
- package/config/messagesniffer.ini +0 -18
- package/config/spamassassin.ini +0 -56
- package/dkim.js +0 -614
- package/docs/plugins/avg.md +0 -35
- package/docs/plugins/bounce.md +0 -69
- package/docs/plugins/clamd.md +0 -147
- package/docs/plugins/esets.md +0 -8
- package/docs/plugins/greylist.md +0 -90
- package/docs/plugins/helo.checks.md +0 -135
- package/docs/plugins/messagesniffer.md +0 -163
- package/docs/plugins/spamassassin.md +0 -180
- package/outbound/mx_lookup.js +0 -70
- package/plugins/auth/auth_ldap.js +0 -3
- package/plugins/avg.js +0 -162
- package/plugins/backscatterer.js +0 -25
- package/plugins/bounce.js +0 -381
- package/plugins/clamd.js +0 -382
- package/plugins/data.uribl.js +0 -4
- package/plugins/dkim_sign.js +0 -395
- package/plugins/dkim_verify.js +0 -62
- package/plugins/dns_list_base.js +0 -221
- package/plugins/dnsbl.js +0 -146
- package/plugins/dnswl.js +0 -58
- package/plugins/esets.js +0 -71
- package/plugins/graph.js +0 -5
- package/plugins/greylist.js +0 -645
- package/plugins/helo.checks.js +0 -533
- package/plugins/messagesniffer.js +0 -381
- package/plugins/rcpt_to.ldap.js +0 -3
- package/plugins/rcpt_to.max_count.js +0 -24
- package/plugins/spamassassin.js +0 -384
- package/tests/config/dkim/example.com/dns +0 -29
- package/tests/config/dkim/example.com/private +0 -6
- package/tests/config/dkim/example.com/public +0 -4
- package/tests/config/dkim/example.com/selector +0 -1
- package/tests/config/dkim.private.key +0 -6
- package/tests/config/dkim_sign.ini +0 -4
- package/tests/config/helo.checks.ini +0 -52
- package/tests/connection.js +0 -327
- package/tests/endpoint.js +0 -128
- package/tests/fixtures/vm_harness.js +0 -59
- package/tests/logger.js +0 -327
- package/tests/outbound/hmail.js +0 -112
- package/tests/outbound/index.js +0 -324
- package/tests/outbound/qfile.js +0 -67
- package/tests/outbound_bounce_net_errors.js +0 -173
- package/tests/plugins/auth/auth_base.js +0 -463
- package/tests/plugins/auth/auth_vpopmaild.js +0 -91
- package/tests/plugins/bounce.js +0 -307
- package/tests/plugins/clamd.js +0 -224
- package/tests/plugins/deprecated/relay_acl.js +0 -140
- package/tests/plugins/deprecated/relay_all.js +0 -59
- package/tests/plugins/dkim_sign.js +0 -315
- package/tests/plugins/dkim_signer.js +0 -108
- package/tests/plugins/dns_list_base.js +0 -259
- package/tests/plugins/dnsbl.js +0 -101
- package/tests/plugins/early_talker.js +0 -115
- package/tests/plugins/greylist.js +0 -58
- package/tests/plugins/helo.checks.js +0 -525
- package/tests/plugins/mail_from.is_resolvable.js +0 -116
- package/tests/plugins/queue/smtp_forward.js +0 -221
- package/tests/plugins/rcpt_to.host_list_base.js +0 -132
- package/tests/plugins/rcpt_to.in_host_list.js +0 -218
- package/tests/plugins/relay.js +0 -339
- package/tests/plugins/spamassassin.js +0 -171
- package/tests/plugins/status.js +0 -138
- package/tests/plugins/tls.js +0 -84
- package/tests/plugins.js +0 -247
- package/tests/rfc1869.js +0 -61
- package/tests/server.js +0 -510
- package/tests/smtp_client/auth.js +0 -105
- package/tests/smtp_client/basic.js +0 -101
- package/tests/smtp_client.js +0 -80
- package/tests/tls_socket.js +0 -333
- package/tests/transaction.js +0 -284
- /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
- /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
- /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
- /package/docs/{plugins → deprecated}/dnswl.md +0 -0
- /package/{tests → test}/.eslintrc.yaml +0 -0
- /package/{tests → test}/config/auth_flat_file.ini +0 -0
- /package/{tests → test}/config/dhparams.pem +0 -0
- /package/{tests → test}/config/host_list +0 -0
- /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
- /package/{tests → test}/config/outbound_tls_key.pem +0 -0
- /package/{tests → test}/config/smtp_forward.ini +0 -0
- /package/{tests → test}/config/tls/ec.pem +0 -0
- /package/{tests → test}/config/tls/haraka.local.pem +0 -0
- /package/{tests → test}/config/tls/mismatched.pem +0 -0
- /package/{tests → test}/config/tls.ini +0 -0
- /package/{tests → test}/config/tls_cert.pem +0 -0
- /package/{tests → test}/config/tls_key.pem +0 -0
- /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
- /package/{tests → test}/installation/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/config/tls.ini +0 -0
- /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
- /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
- /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
- /package/{tests → test}/installation/plugins/inherits.js +0 -0
- /package/{tests → test}/installation/plugins/load_first.js +0 -0
- /package/{tests → test}/installation/plugins/plugin.js +0 -0
- /package/{tests → test}/installation/plugins/tls.js +0 -0
- /package/{tests → test}/loud/config/dhparams.pem +0 -0
- /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
- /package/{tests → test}/loud/config/tls.ini +0 -0
- /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
- /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- /package/{tests → test}/queue/multibyte +0 -0
- /package/{tests → test}/queue/plain +0 -0
- /package/{tests → test}/queue/zero-length +0 -0
- /package/{tests → test}/test-queue/delete-me +0 -0
package/dkim.js
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
const dns = require('dns');
|
|
5
|
-
const { Stream } = require('stream');
|
|
6
|
-
const utils = require('haraka-utils');
|
|
7
|
-
|
|
8
|
-
//////////////////////
|
|
9
|
-
// Common functions //
|
|
10
|
-
//////////////////////
|
|
11
|
-
|
|
12
|
-
function md5 (str) {
|
|
13
|
-
if (!str) str = '';
|
|
14
|
-
const h = crypto.createHash('md5');
|
|
15
|
-
return h.update(str).digest('hex');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class Buf {
|
|
19
|
-
constructor () {
|
|
20
|
-
this.bar = [];
|
|
21
|
-
this.blen = 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
pop (buf) {
|
|
25
|
-
if (!this.bar.length) {
|
|
26
|
-
if (!buf) buf = Buffer.from('');
|
|
27
|
-
return buf;
|
|
28
|
-
}
|
|
29
|
-
if (buf?.length) {
|
|
30
|
-
this.bar.push(buf);
|
|
31
|
-
this.blen += buf.length;
|
|
32
|
-
}
|
|
33
|
-
const nb = Buffer.concat(this.bar, this.blen);
|
|
34
|
-
this.bar = [];
|
|
35
|
-
this.blen = 0;
|
|
36
|
-
return nb;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
push (buf) {
|
|
40
|
-
if (buf.length) {
|
|
41
|
-
this.bar.push(buf);
|
|
42
|
-
this.blen += buf.length;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
////////////////
|
|
48
|
-
// DKIMObject //
|
|
49
|
-
////////////////
|
|
50
|
-
|
|
51
|
-
// There is one DKIMObject created for each signature found
|
|
52
|
-
|
|
53
|
-
class DKIMObject {
|
|
54
|
-
constructor (header, header_idx, cb, opts) {
|
|
55
|
-
this.cb = cb;
|
|
56
|
-
this.sig = header;
|
|
57
|
-
this.sig_md5 = md5(header);
|
|
58
|
-
this.run_cb = false;
|
|
59
|
-
this.header_idx = JSON.parse(JSON.stringify(header_idx));
|
|
60
|
-
this.timeout = opts.timeout || 30
|
|
61
|
-
this.allowed_time_skew = opts.allowed_time_skew
|
|
62
|
-
this.fields = {};
|
|
63
|
-
this.headercanon = this.bodycanon = 'simple';
|
|
64
|
-
this.signed_headers = [];
|
|
65
|
-
this.identity = 'unknown';
|
|
66
|
-
this.line_buffer = []
|
|
67
|
-
this.dns_fields = {
|
|
68
|
-
'v': 'DKIM1',
|
|
69
|
-
'k': 'rsa',
|
|
70
|
-
'g': '*',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const [ , , dkim_signature] = /^([^:]+):\s*((?:.|[\r\n])*)$/.exec(header);
|
|
74
|
-
const sig = dkim_signature.trim().replace(/\s+/g,'');
|
|
75
|
-
const keys = sig.split(';');
|
|
76
|
-
for (const keyElement of keys) {
|
|
77
|
-
const key = keyElement.trim();
|
|
78
|
-
if (!key) continue; // skip empty keys
|
|
79
|
-
const [ , key_name, key_value] = /^([^= ]+)=((?:.|[\r\n])+)$/.exec(key) || [];
|
|
80
|
-
if (key_name) {
|
|
81
|
-
this.fields[key_name] = key_value;
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
return this.result('header parse error', 'invalid');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/////////////////////
|
|
88
|
-
// Validate fields //
|
|
89
|
-
/////////////////////
|
|
90
|
-
|
|
91
|
-
if (this.fields.v) {
|
|
92
|
-
if (this.fields.v !== '1') {
|
|
93
|
-
return this.result('incompatible version', 'invalid');
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
return this.result('missing version', 'invalid');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (this.fields.l) {
|
|
101
|
-
return this.result('length tag is unsupported', 'none');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (this.fields.a) {
|
|
105
|
-
switch (this.fields.a) {
|
|
106
|
-
case 'rsa-sha1':
|
|
107
|
-
this.bh = crypto.createHash('SHA1');
|
|
108
|
-
this.verifier = crypto.createVerify('RSA-SHA1');
|
|
109
|
-
break;
|
|
110
|
-
case 'rsa-sha256':
|
|
111
|
-
this.bh = crypto.createHash('SHA256');
|
|
112
|
-
this.verifier = crypto.createVerify('RSA-SHA256');
|
|
113
|
-
break;
|
|
114
|
-
default:
|
|
115
|
-
this.debug(`Invalid algorithm: ${this.fields.a}`);
|
|
116
|
-
return this.result('invalid algorithm', 'invalid');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
return this.result('missing algorithm', 'invalid');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (!this.fields.b) return this.result('signature missing', 'invalid');
|
|
124
|
-
if (!this.fields.bh) return this.result('body hash missing', 'invalid');
|
|
125
|
-
|
|
126
|
-
if (this.fields.c) {
|
|
127
|
-
const c = this.fields.c.split('/');
|
|
128
|
-
if (c[0]) this.headercanon = c[0];
|
|
129
|
-
if (c[1]) this.bodycanon = c[1];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!this.fields.d) return this.result('domain missing', 'invalid');
|
|
133
|
-
|
|
134
|
-
if (this.fields.h) {
|
|
135
|
-
const headers = this.fields.h.split(':');
|
|
136
|
-
for (const h of headers) {
|
|
137
|
-
this.signed_headers.push(h.trim().toLowerCase());
|
|
138
|
-
}
|
|
139
|
-
if (!this.signed_headers.includes('from')) {
|
|
140
|
-
return this.result('from field not signed', 'invalid');
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
return this.result('signed headers missing', 'invalid');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (this.fields.i) {
|
|
148
|
-
// Make sure that this is a sub-domain of the 'd' field
|
|
149
|
-
const dom = this.fields.i.substr(this.fields.i.length - this.fields.d.length);
|
|
150
|
-
if (dom.toLowerCase() !== this.fields.d.toLowerCase()) {
|
|
151
|
-
return this.result('i/d selector domain mismatch', 'invalid')
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
this.fields.i = `@${this.fields.d}`;
|
|
156
|
-
}
|
|
157
|
-
this.identity = this.fields.i;
|
|
158
|
-
|
|
159
|
-
if (this.fields.q && this.fields.q !== 'dns/txt') {
|
|
160
|
-
return this.result('unknown query method', 'invalid');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const now = new Date().getTime()/1000;
|
|
164
|
-
if (this.fields.t) {
|
|
165
|
-
if (this.fields.t > (this.allowed_time_skew ? (now + parseInt(this.allowed_time_skew)) : now)) {
|
|
166
|
-
return this.result('creation date is invalid or in the future', 'invalid')
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (this.fields.x) {
|
|
171
|
-
if (this.fields.t && parseInt(this.fields.x) < parseInt(this.fields.t)) {
|
|
172
|
-
return this.result('invalid expiration date', 'invalid');
|
|
173
|
-
}
|
|
174
|
-
if ((this.allowed_time_skew ? (now - parseInt(this.allowed_time_skew)) : now) > parseInt(this.fields.x)) {
|
|
175
|
-
return this.result(`signature expired`, 'invalid');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
this.debug(`${this.identity}: DKIM fields validated OK`);
|
|
180
|
-
this.debug(`${this.identity}: a=${this.fields.a} c=${this.headercanon}/${this.bodycanon} h=${this.signed_headers}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
debug (str) {
|
|
184
|
-
console.debug(str)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
header_canon_relaxed (header) {
|
|
188
|
-
// `|| []` prevents errors thrown when no match
|
|
189
|
-
// `\s*` eats all FWS after the colon
|
|
190
|
-
// eslint-disable-next-line prefer-const
|
|
191
|
-
let [, header_name, header_value] = /^([^:]+):\s*([^]*)$/.exec(header) || []
|
|
192
|
-
|
|
193
|
-
if (!header_name) return header;
|
|
194
|
-
if (header_value.length === 0) header_value = "\r\n"
|
|
195
|
-
|
|
196
|
-
let hc = `${header_name.toLowerCase()}:${header_value}`;
|
|
197
|
-
hc = hc.replace(/\r\n([\t ]+)/g, "$1");
|
|
198
|
-
hc = hc.replace(/[\t ]+/g, ' ');
|
|
199
|
-
hc = hc.replace(/[\t ]+(\r?\n)$/, "$1");
|
|
200
|
-
return hc;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
add_body_line (line) {
|
|
204
|
-
if (this.run_cb) return;
|
|
205
|
-
|
|
206
|
-
if (this.bodycanon === 'relaxed') {
|
|
207
|
-
line = DKIMObject.canonicalize(line)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Buffer any lines
|
|
211
|
-
const isCRLF = line.length === 2 && line[0] === 0x0d && line[1] === 0x0a;
|
|
212
|
-
const isLF = line.length === 1 && line[0] === 0x0a;
|
|
213
|
-
if (isCRLF || isLF) {
|
|
214
|
-
// Store any empty lines as both canonicalization algorithms
|
|
215
|
-
// ignore all empty lines at the end of the message body.
|
|
216
|
-
this.line_buffer.push(line)
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
if (this.line_buffer.length > 0) {
|
|
220
|
-
this.line_buffer.forEach(v => this.bh.update(v))
|
|
221
|
-
this.line_buffer = []
|
|
222
|
-
}
|
|
223
|
-
this.bh.update(line)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
result (error, result) {
|
|
228
|
-
this.run_cb = true;
|
|
229
|
-
return this.cb(
|
|
230
|
-
((error) ? new Error(error) : null),
|
|
231
|
-
{
|
|
232
|
-
identity: this.identity,
|
|
233
|
-
selector: this.fields.s,
|
|
234
|
-
domain: this.fields.d,
|
|
235
|
-
result
|
|
236
|
-
}
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
end () {
|
|
241
|
-
if (this.run_cb) return;
|
|
242
|
-
|
|
243
|
-
const bh = this.bh.digest('base64');
|
|
244
|
-
this.debug(`${this.identity}: bodyhash=${this.fields.bh} computed=${bh}`);
|
|
245
|
-
if (bh !== this.fields.bh) {
|
|
246
|
-
return this.result('body hash did not verify', 'fail');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Now we canonicalize the specified headers
|
|
250
|
-
for (const header of this.signed_headers) {
|
|
251
|
-
this.debug(`${this.identity}: canonicalize header: ${header}`);
|
|
252
|
-
if (this.header_idx[header]) {
|
|
253
|
-
// RFC 6376 section 5.4.2, read headers from bottom to top
|
|
254
|
-
const this_header = this.header_idx[header].pop();
|
|
255
|
-
if (this_header) {
|
|
256
|
-
// Skip this signature if dkim-signature is specified
|
|
257
|
-
if (header === 'dkim-signature') {
|
|
258
|
-
const h_md5 = md5(this_header);
|
|
259
|
-
if (h_md5 === this.sig_md5) {
|
|
260
|
-
this.debug(`${this.identity}: skipped our own DKIM-Signature`);
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (this.headercanon === 'simple') {
|
|
265
|
-
this.verifier.update(this_header);
|
|
266
|
-
}
|
|
267
|
-
else if (this.headercanon === 'relaxed') {
|
|
268
|
-
const hc = this.header_canon_relaxed(this_header);
|
|
269
|
-
this.verifier.update(hc);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Now add in our original DKIM-Signature header without the b= and trailing CRLF
|
|
276
|
-
let our_sig = this.sig.replace(/([:;\s\t]|^)b=([^;]+)/, '$1b=');
|
|
277
|
-
if (this.headercanon === 'relaxed') {
|
|
278
|
-
our_sig = this.header_canon_relaxed(our_sig);
|
|
279
|
-
}
|
|
280
|
-
our_sig = our_sig.replace(/\r\n$/,'');
|
|
281
|
-
this.verifier.update(our_sig);
|
|
282
|
-
|
|
283
|
-
let timeout = false;
|
|
284
|
-
const timer = setTimeout(() => {
|
|
285
|
-
timeout = true;
|
|
286
|
-
return this.result('DNS timeout', 'tempfail');
|
|
287
|
-
}, this.timeout * 1000);
|
|
288
|
-
const lookup = `${this.fields.s}._domainkey.${this.fields.d}`;
|
|
289
|
-
this.debug(`${this.identity}: DNS lookup ${lookup} (timeout= ${this.timeout}s)`);
|
|
290
|
-
dns.resolveTxt(lookup, (err, res) => {
|
|
291
|
-
if (timeout) return;
|
|
292
|
-
clearTimeout(timer);
|
|
293
|
-
if (err) {
|
|
294
|
-
switch (err.code) {
|
|
295
|
-
case dns.NOTFOUND:
|
|
296
|
-
case dns.NODATA:
|
|
297
|
-
case dns.NXDOMAIN:
|
|
298
|
-
return this.result('no key for signature', 'invalid');
|
|
299
|
-
default:
|
|
300
|
-
this.debug(`${this.identity}: DNS lookup error: ${err.code}`);
|
|
301
|
-
return this.result('key unavailable', 'tempfail');
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (!res) return this.result('no key for signature', 'invalid');
|
|
305
|
-
for (const recordSegments of res) {
|
|
306
|
-
const record = recordSegments.join('');
|
|
307
|
-
if (!record.includes('p=')) {
|
|
308
|
-
this.debug(`${this.identity}: ignoring TXT record: ${record}`);
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
this.debug(`${this.identity}: got DNS record: ${record}`);
|
|
312
|
-
const rec = record.replace(/\r?\n/g, '').replace(/\s+/g,'');
|
|
313
|
-
const split = rec.split(';');
|
|
314
|
-
for (const element of split) {
|
|
315
|
-
const split2 = element.split('=');
|
|
316
|
-
if (split2[0]) this.dns_fields[split2[0]] = split2[1];
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Validate
|
|
320
|
-
if (!this.dns_fields.v || this.dns_fields.v !== 'DKIM1') {
|
|
321
|
-
return this.result('invalid version', 'invalid');
|
|
322
|
-
}
|
|
323
|
-
if (this.dns_fields.g) {
|
|
324
|
-
if (this.dns_fields.g !== '*') {
|
|
325
|
-
let s = this.dns_fields.g;
|
|
326
|
-
// Escape any special regexp characters
|
|
327
|
-
s = s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
328
|
-
// Make * a non-greedy match against anything except @
|
|
329
|
-
s = s.replace('\\*','[^@]*?');
|
|
330
|
-
const reg = new RegExp(`^${s}@`);
|
|
331
|
-
this.debug(`${this.identity}: matching ${this.dns_fields.g} against i=${this.fields.i} regexp=${reg.toString()}`);
|
|
332
|
-
if (!reg.test(this.fields.i)) {
|
|
333
|
-
return this.result('inapplicable key', 'invalid');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
return this.result('inapplicable key', 'invalid');
|
|
339
|
-
}
|
|
340
|
-
if (this.dns_fields.h) {
|
|
341
|
-
const hashes = this.dns_fields.h.split(':');
|
|
342
|
-
for (const hashElement of hashes) {
|
|
343
|
-
const hash = hashElement.trim();
|
|
344
|
-
if (!this.fields.a.includes(hash)) {
|
|
345
|
-
return this.result('inappropriate hash algorithm', 'invalid');
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (this.dns_fields.k) {
|
|
350
|
-
if (!this.fields.a.includes(this.dns_fields.k)) {
|
|
351
|
-
return this.result('inappropriate key type', 'invalid');
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
if (this.dns_fields.t) {
|
|
355
|
-
const flags = this.dns_fields.t.split(':');
|
|
356
|
-
for (const flagElement of flags) {
|
|
357
|
-
const flag = flagElement.trim();
|
|
358
|
-
if (flag === 'y') {
|
|
359
|
-
// Test mode
|
|
360
|
-
this.test_mode = true;
|
|
361
|
-
}
|
|
362
|
-
else if (flag === 's') {
|
|
363
|
-
// 'i' and 'd' domain much match exactly
|
|
364
|
-
let { i } = this.fields
|
|
365
|
-
i = i.substr(i.indexOf('@')+1, i.length)
|
|
366
|
-
if (i.toLowerCase() !== this.fields.d.toLowerCase()) {
|
|
367
|
-
return this.result('i/d selector domain mismatch (t=s)', 'invalid')
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (!this.dns_fields.p) return this.result('key revoked', 'invalid');
|
|
373
|
-
|
|
374
|
-
// crypto.verifier requires the key in PEM format
|
|
375
|
-
this.public_key = `-----BEGIN PUBLIC KEY-----\r\n${
|
|
376
|
-
|
|
377
|
-
this.dns_fields.p.replace(/(.{1,76})/g, '$1\r\n')
|
|
378
|
-
}-----END PUBLIC KEY-----\r\n`;
|
|
379
|
-
|
|
380
|
-
let verified;
|
|
381
|
-
try {
|
|
382
|
-
verified = this.verifier.verify(this.public_key, this.fields.b, 'base64');
|
|
383
|
-
this.debug(`${this.identity}: verified=${verified}`);
|
|
384
|
-
}
|
|
385
|
-
catch (e) {
|
|
386
|
-
this.debug(`${this.identity}: verification error: ${e.message}`);
|
|
387
|
-
return this.result('verification error', 'invalid');
|
|
388
|
-
}
|
|
389
|
-
return this.result(null, ((verified) ? 'pass' : 'fail'));
|
|
390
|
-
}
|
|
391
|
-
// We didn't find a valid DKIM record for this signature
|
|
392
|
-
this.result('no key for signature', 'invalid');
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
static canonicalize (bufin) {
|
|
397
|
-
const tmp = []
|
|
398
|
-
const len = bufin.length
|
|
399
|
-
let last_chunk_idx = 0
|
|
400
|
-
let idx_wsp = 0
|
|
401
|
-
let in_wsp = false
|
|
402
|
-
|
|
403
|
-
for (let idx = 0; idx < len; idx++) {
|
|
404
|
-
const char = bufin[idx]
|
|
405
|
-
if (char === 9 || char === 32) { // inside WSP
|
|
406
|
-
if (!in_wsp) { // WSP started
|
|
407
|
-
in_wsp = true
|
|
408
|
-
idx_wsp = idx
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
else if (char === 13 || char === 10) { // CR?LF
|
|
412
|
-
if (in_wsp) { // just after WSP
|
|
413
|
-
tmp.push(bufin.slice(last_chunk_idx, idx_wsp))
|
|
414
|
-
}
|
|
415
|
-
else { // just after regular char
|
|
416
|
-
tmp.push(bufin.slice(last_chunk_idx, idx))
|
|
417
|
-
}
|
|
418
|
-
break
|
|
419
|
-
}
|
|
420
|
-
else if (in_wsp) { // regular char after WSP
|
|
421
|
-
in_wsp = false
|
|
422
|
-
tmp.push(bufin.slice(last_chunk_idx, idx_wsp))
|
|
423
|
-
tmp.push(Buffer.from(' '))
|
|
424
|
-
last_chunk_idx = idx
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
tmp.push(Buffer.from([13, 10]))
|
|
429
|
-
|
|
430
|
-
return Buffer.concat(tmp)
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
exports.DKIMObject = DKIMObject;
|
|
435
|
-
|
|
436
|
-
//////////////////////
|
|
437
|
-
// DKIMVerifyStream //
|
|
438
|
-
//////////////////////
|
|
439
|
-
|
|
440
|
-
class DKIMVerifyStream extends Stream {
|
|
441
|
-
constructor (opts, cb) {
|
|
442
|
-
super();
|
|
443
|
-
this.run_cb = false;
|
|
444
|
-
this.cb = (err, result, results) => {
|
|
445
|
-
if (!this.run_cb) {
|
|
446
|
-
this.run_cb = true;
|
|
447
|
-
return cb(err, result, results);
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
this._in_body = false;
|
|
451
|
-
this._no_signatures_found = false;
|
|
452
|
-
this.buffer = new Buf();
|
|
453
|
-
this.headers = [];
|
|
454
|
-
this.header_idx = {};
|
|
455
|
-
this.dkim_objects = [];
|
|
456
|
-
this.results = [];
|
|
457
|
-
this.result = 'none';
|
|
458
|
-
this.pending = 0;
|
|
459
|
-
this.writable = true;
|
|
460
|
-
this.opts = opts
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
debug (str) {
|
|
464
|
-
console.debug(str)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
handle_buf (buf) {
|
|
468
|
-
const self = this;
|
|
469
|
-
// Abort any further processing if the headers
|
|
470
|
-
// did not contain any DKIM-Signature fields.
|
|
471
|
-
if (this._in_body && this._no_signatures_found) {
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
let once = false;
|
|
475
|
-
if (buf === null) {
|
|
476
|
-
once = true;
|
|
477
|
-
buf = this.buffer.pop();
|
|
478
|
-
if (!!buf && buf[buf.length - 2] === 0x0d && buf[buf.length - 1] === 0x0a) {
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
buf = Buffer.concat([buf, Buffer.from('\r\n\r\n')])
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
buf = this.buffer.pop(buf);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function callback (err, result) {
|
|
488
|
-
self.pending--;
|
|
489
|
-
if (result) {
|
|
490
|
-
const results = {
|
|
491
|
-
identity: result.identity,
|
|
492
|
-
domain: result.domain,
|
|
493
|
-
selector: result.selector,
|
|
494
|
-
result: result.result,
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
if (err) {
|
|
498
|
-
results.error = err.message
|
|
499
|
-
if (self.opts.sigerror_log_level) results.emit_log_level = self.opts.sigerror_log_level
|
|
500
|
-
}
|
|
501
|
-
self.results.push(results)
|
|
502
|
-
|
|
503
|
-
// Set the overall result based on this precedence order
|
|
504
|
-
const rr = ['pass','tempfail','fail','invalid','none'];
|
|
505
|
-
for (const element of rr) {
|
|
506
|
-
if (!self.result || (self.result && self.result !== element && result.result === element)) {
|
|
507
|
-
self.result = element;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
self.debug(JSON.stringify(result));
|
|
513
|
-
|
|
514
|
-
if (self.pending === 0 && self.cb) {
|
|
515
|
-
return process.nextTick(() => {
|
|
516
|
-
self.cb(null, self.result, self.results);
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Process input buffer into lines
|
|
522
|
-
let offset = 0;
|
|
523
|
-
while ((offset = utils.indexOfLF(buf)) !== -1) {
|
|
524
|
-
let line = buf.slice(0, offset+1);
|
|
525
|
-
if (buf.length > offset) {
|
|
526
|
-
buf = buf.slice(offset+1);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Check for LF line endings and convert to CRLF if necessary
|
|
530
|
-
if (line[line.length-2] !== 0x0d) {
|
|
531
|
-
line = Buffer.concat([ line.slice(0, line.length-1), Buffer.from("\r\n") ], line.length+1);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Look for CRLF
|
|
535
|
-
if (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a) {
|
|
536
|
-
// Look for end of headers marker
|
|
537
|
-
if (!this._in_body) {
|
|
538
|
-
this._in_body = true;
|
|
539
|
-
// Parse the headers
|
|
540
|
-
for (const header of this.headers) {
|
|
541
|
-
const match = /^([^: ]+):\s*((:?.|[\r\n])*)/.exec(header);
|
|
542
|
-
if (!match) continue;
|
|
543
|
-
const header_name = match[1];
|
|
544
|
-
if (!header_name) continue;
|
|
545
|
-
const hn = header_name.toLowerCase();
|
|
546
|
-
if (!this.header_idx[hn]) this.header_idx[hn] = [];
|
|
547
|
-
this.header_idx[hn].push(header);
|
|
548
|
-
}
|
|
549
|
-
if (!this.header_idx['dkim-signature']) {
|
|
550
|
-
this._no_signatures_found = true;
|
|
551
|
-
return process.nextTick(() => {
|
|
552
|
-
self.cb(null, self.result, self.results);
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
// Create new DKIM objects for each header
|
|
557
|
-
const dkim_headers = this.header_idx['dkim-signature'];
|
|
558
|
-
this.debug(`Found ${dkim_headers.length} DKIM signatures`);
|
|
559
|
-
this.pending = dkim_headers.length;
|
|
560
|
-
for (const dkimHeader of dkim_headers) {
|
|
561
|
-
this.dkim_objects.push(new DKIMObject(dkimHeader, this.header_idx, callback, this.opts));
|
|
562
|
-
}
|
|
563
|
-
if (this.pending === 0) {
|
|
564
|
-
process.nextTick(() => {
|
|
565
|
-
if (self.cb) self.cb(new Error('no signatures found'));
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
continue; // while()
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (!this._in_body) {
|
|
574
|
-
// Parse headers
|
|
575
|
-
if (line[0] === 0x20 || line[0] === 0x09) {
|
|
576
|
-
// Header continuation
|
|
577
|
-
this.headers[this.headers.length-1] += line.toString('utf-8');
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
this.headers.push(line.toString('utf-8'));
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
for (const dkimObject of this.dkim_objects) {
|
|
585
|
-
dkimObject.add_body_line(line);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
if (once) {
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
this.buffer.push(buf);
|
|
594
|
-
return true;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
write (buf) {
|
|
598
|
-
return this.handle_buf(buf);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
end (buf) {
|
|
602
|
-
this.handle_buf(((buf) ? buf : null));
|
|
603
|
-
for (const dkimObject of this.dkim_objects) {
|
|
604
|
-
dkimObject.end();
|
|
605
|
-
}
|
|
606
|
-
if (this.pending === 0 && this._no_signatures_found === false) {
|
|
607
|
-
process.nextTick(() => {
|
|
608
|
-
this.cb(null, this.result, this.results);
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
exports.DKIMVerifyStream = DKIMVerifyStream;
|
package/docs/plugins/avg.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# avg - Anti-Virus scanner
|
|
2
|
-
|
|
3
|
-
Implement virus scanning with AVG's TCPD daemon, available for Linux/FreeBSD. AVG linux is [free for personal or commercial use](http://www.avg.com/gb-en/faq.pnuid-faq_v3_linux) and can be downloaded from [free.avg.com](http://free.avg.com/gb-en/download.prd-alf).
|
|
4
|
-
|
|
5
|
-
Messages that AVG detects as infected are rejected. Errors will cause the plugin to return temporary failures unless the defer options are changed (see below).
|
|
6
|
-
|
|
7
|
-
## Configuration
|
|
8
|
-
|
|
9
|
-
The following options can be set in avg.ini:
|
|
10
|
-
|
|
11
|
-
* port (default: 54322)
|
|
12
|
-
|
|
13
|
-
TCP port to communicate with the AVG TCPD on.
|
|
14
|
-
|
|
15
|
-
* tmpdir (default: /tmp)
|
|
16
|
-
|
|
17
|
-
AVG TCPD requires that the message be written to disk and scanned. This setting configures where any temporary files are written to. After scanning, the temporary files are automatically removed.
|
|
18
|
-
|
|
19
|
-
* connect\_timeout (default: 10)
|
|
20
|
-
|
|
21
|
-
Maximum seconds to wait for the socket to connect. Connections taking longer will cause a temporary failure to be sent to the remote MTA.
|
|
22
|
-
|
|
23
|
-
* session\_timeout
|
|
24
|
-
|
|
25
|
-
Maximum number of seconds to wait for a reply to a command before failing. A timeout will cause a temporary failure to be sent to the remote MTA.
|
|
26
|
-
|
|
27
|
-
* [defer]
|
|
28
|
-
|
|
29
|
-
By default, this plugin defers when errors or timeouts are encountered. To
|
|
30
|
-
fail open (let messages pass when errors are enounctered), set the error
|
|
31
|
-
and/or timeout values to false.
|
|
32
|
-
|
|
33
|
-
[defer]
|
|
34
|
-
error=true
|
|
35
|
-
timeout=true
|