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
|
@@ -11,36 +11,36 @@ exports.register = function () {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
exports.load_ini = function () {
|
|
14
|
-
|
|
15
|
-
plugin.cfg = plugin.config.get('mail_from.is_resolvable.ini', {
|
|
14
|
+
this.cfg = this.config.get('mail_from.is_resolvable.ini', {
|
|
16
15
|
booleans: [
|
|
17
16
|
'-main.allow_mx_ip',
|
|
18
17
|
'+main.reject_no_mx',
|
|
19
18
|
],
|
|
20
19
|
}, () => {
|
|
21
|
-
|
|
20
|
+
this.load_ini();
|
|
22
21
|
});
|
|
23
22
|
|
|
24
|
-
if (isNaN(
|
|
25
|
-
|
|
23
|
+
if (isNaN(this.cfg.main.timeout)) {
|
|
24
|
+
this.cfg.main.timeout = 29;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
if (
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
if (this.timeout) {
|
|
28
|
+
if (this.timeout <= this.cfg.main.timeout) {
|
|
29
|
+
this.cfg.main.timeout = this.timeout - 1;
|
|
30
|
+
this.logwarn(`reducing plugin timeout to ${this.cfg.main.timeout}s`);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
this.re_bogus_ip = new RegExp(this.cfg.main.re_bogus_ip ||
|
|
36
35
|
'^(?:0\\.0\\.0\\.0|255\\.255\\.255\\.255|127\\.)' );
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
exports.hook_mail = function (next, connection, params) {
|
|
40
39
|
const plugin = this;
|
|
41
40
|
const mail_from = params[0];
|
|
42
|
-
const txn = connection
|
|
43
|
-
|
|
41
|
+
const txn = connection?.transaction;
|
|
42
|
+
if (!txn) return next();
|
|
43
|
+
const { results } = txn;
|
|
44
44
|
|
|
45
45
|
// Check for MAIL FROM without an @ first - ignore those here
|
|
46
46
|
if (!mail_from.host) {
|
|
@@ -83,7 +83,7 @@ exports.hook_mail = function (next, connection, params) {
|
|
|
83
83
|
if (pending_queries !== 0) return;
|
|
84
84
|
|
|
85
85
|
records = Object.keys(records);
|
|
86
|
-
if (records
|
|
86
|
+
if (records?.length) {
|
|
87
87
|
connection.logdebug(plugin, `${domain}: ${records}`);
|
|
88
88
|
results.add(plugin, {pass: 'has_fwd_dns'});
|
|
89
89
|
return mxDone();
|
|
@@ -114,21 +114,21 @@ exports.hook_mail = function (next, connection, params) {
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
connection.logdebug(plugin, `${domain}: MX ${addr.priority} ${addr.exchange} => ${addresses2}`);
|
|
117
|
-
for (
|
|
117
|
+
for (const element of addresses2) {
|
|
118
118
|
// Ignore anything obviously bogus
|
|
119
|
-
if (net.isIPv4(
|
|
120
|
-
if (plugin.re_bogus_ip.test(
|
|
121
|
-
connection.logdebug(plugin, `${addr.exchange}: discarding ${
|
|
119
|
+
if (net.isIPv4(element)){
|
|
120
|
+
if (plugin.re_bogus_ip.test(element)) {
|
|
121
|
+
connection.logdebug(plugin, `${addr.exchange}: discarding ${element}`);
|
|
122
122
|
continue;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
if (net.isIPv6(
|
|
126
|
-
if (net_utils.ipv6_bogus(
|
|
127
|
-
connection.logdebug(plugin, `${addr.exchange}: discarding ${
|
|
125
|
+
if (net.isIPv6(element)){
|
|
126
|
+
if (net_utils.ipv6_bogus(element)) {
|
|
127
|
+
connection.logdebug(plugin, `${addr.exchange}: discarding ${element}`);
|
|
128
128
|
continue;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
records[
|
|
131
|
+
records[element] = 1;
|
|
132
132
|
}
|
|
133
133
|
check_results();
|
|
134
134
|
});
|
|
@@ -139,11 +139,12 @@ exports.hook_mail = function (next, connection, params) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
exports.mxErr = function (connection, domain, type, err, mxDone) {
|
|
142
|
-
|
|
143
|
-
const txn = connection
|
|
142
|
+
|
|
143
|
+
const txn = connection?.transaction;
|
|
144
144
|
if (!txn) return;
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
|
|
146
|
+
txn.results.add(this, {msg: `${domain}:${type}:${err.message}`});
|
|
147
|
+
connection.logdebug(this, `${domain}:${type} => ${err.message}`);
|
|
147
148
|
switch (err.code) {
|
|
148
149
|
case dns.NXDOMAIN:
|
|
149
150
|
case dns.NOTFOUND:
|
|
@@ -159,31 +160,30 @@ exports.mxErr = function (connection, domain, type, err, mxDone) {
|
|
|
159
160
|
|
|
160
161
|
// IS: IPv6 compatible
|
|
161
162
|
exports.implicit_mx = function (connection, domain, mxDone) {
|
|
162
|
-
const
|
|
163
|
-
|
|
163
|
+
const txn = connection?.transaction;
|
|
164
|
+
if (!txn) return;
|
|
164
165
|
|
|
165
166
|
net_utils.get_ips_by_host(domain, (err, addresses) => {
|
|
166
167
|
if (!txn) return;
|
|
167
168
|
if (!addresses || !addresses.length) {
|
|
168
|
-
txn.results.add(
|
|
169
|
-
return mxDone(((
|
|
169
|
+
txn.results.add(this, {fail: 'has_fwd_dns'});
|
|
170
|
+
return mxDone(((this.cfg.main.reject_no_mx) ? DENY : DENYSOFT),
|
|
170
171
|
'No MX for your FROM address');
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
connection.logdebug(
|
|
174
|
+
connection.logdebug(this, `${domain}: A/AAAA => ${addresses}`);
|
|
174
175
|
let records = {};
|
|
175
|
-
for (
|
|
176
|
-
const addr = addresses[i];
|
|
176
|
+
for (const addr of addresses) {
|
|
177
177
|
// Ignore anything obviously bogus
|
|
178
178
|
if (net.isIPv4(addr)) {
|
|
179
|
-
if (
|
|
180
|
-
connection.logdebug(
|
|
179
|
+
if (this.re_bogus_ip.test(addr)) {
|
|
180
|
+
connection.logdebug(this, `${domain}: discarding ${addr}`);
|
|
181
181
|
continue;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
if (net.isIPv6(addr)) {
|
|
185
185
|
if (net_utils.ipv6_bogus(addr)) {
|
|
186
|
-
connection.logdebug(
|
|
186
|
+
connection.logdebug(this, `${domain}: discarding ${addr}`);
|
|
187
187
|
continue;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -191,12 +191,12 @@ exports.implicit_mx = function (connection, domain, mxDone) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
records = Object.keys(records);
|
|
194
|
-
if (records
|
|
195
|
-
txn.results.add(
|
|
194
|
+
if (records?.length) {
|
|
195
|
+
txn.results.add(this, {pass: 'implicit_mx'});
|
|
196
196
|
return mxDone();
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
txn.results.add(
|
|
199
|
+
txn.results.add(this, {fail: `implicit_mx(${domain})`});
|
|
200
200
|
return mxDone();
|
|
201
201
|
});
|
|
202
202
|
}
|
|
@@ -13,27 +13,26 @@ exports.register = function () {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
exports.hook_connect = function (next, connection) {
|
|
16
|
-
const self = this;
|
|
17
16
|
const cfg = this.config.get('messagesniffer.ini');
|
|
18
|
-
|
|
19
17
|
// Skip any private IP ranges
|
|
20
|
-
|
|
18
|
+
// Skip connection.transaction undefined
|
|
19
|
+
if (connection?.remote?.is_private || !connection?.transaction) return next();
|
|
21
20
|
|
|
22
21
|
// Retrieve GBUdb information for the connecting IP
|
|
23
22
|
SNFClient(`<snf><xci><gbudb><test ip='${connection.remote.ip}'/></gbudb></xci></snf>`, (err, result) => {
|
|
24
23
|
if (err) {
|
|
25
|
-
connection.logerror(
|
|
24
|
+
connection.logerror(this, err.message);
|
|
26
25
|
return next();
|
|
27
26
|
}
|
|
28
27
|
let match;
|
|
29
28
|
if ((match = /<result ((?:(?!\/>)[^])+)\/>/.exec(result))) {
|
|
30
29
|
// Log result
|
|
31
|
-
connection.loginfo(
|
|
30
|
+
connection.loginfo(this, match[1]);
|
|
32
31
|
// Populate result
|
|
33
32
|
const gbudb = {};
|
|
34
33
|
const split = match[1].toString().split(/\s+/);
|
|
35
|
-
for (
|
|
36
|
-
const split2 =
|
|
34
|
+
for (const element of split) {
|
|
35
|
+
const split2 = element.split(/=/);
|
|
37
36
|
gbudb[split2[0]] = split2[1].replace(/(?:^'|'$)/g,'');
|
|
38
37
|
}
|
|
39
38
|
// Set notes for other plugins
|
|
@@ -52,8 +51,8 @@ exports.hook_connect = function (next, connection) {
|
|
|
52
51
|
case 'caution':
|
|
53
52
|
case 'black':
|
|
54
53
|
case 'truncate':
|
|
55
|
-
if (cfg.gbudb
|
|
56
|
-
connection.loginfo(
|
|
54
|
+
if (cfg.gbudb?.[gbudb.range]) {
|
|
55
|
+
connection.loginfo(this, `range=${gbudb.range} action=${cfg.gbudb[gbudb.range]}`);
|
|
57
56
|
switch (cfg.gbudb[gbudb.range]) {
|
|
58
57
|
case 'accept':
|
|
59
58
|
// Whitelist
|
|
@@ -89,20 +88,19 @@ exports.hook_connect = function (next, connection) {
|
|
|
89
88
|
return next();
|
|
90
89
|
default:
|
|
91
90
|
// Unknown
|
|
92
|
-
connection.logerror(
|
|
93
|
-
|
|
91
|
+
connection.logerror(this, `Unknown GBUdb range: ${gbudb.range}`);
|
|
92
|
+
next();
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
97
|
-
|
|
96
|
+
next();
|
|
98
97
|
}
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
exports.hook_data_post = function (next, connection) {
|
|
103
|
-
const self = this;
|
|
104
102
|
const cfg = this.config.get('messagesniffer.ini');
|
|
105
|
-
const txn = connection
|
|
103
|
+
const txn = connection?.transaction;
|
|
106
104
|
if (!txn) return next();
|
|
107
105
|
|
|
108
106
|
function tag_subject (){
|
|
@@ -120,7 +118,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
// Check GBUdb results
|
|
123
|
-
if (connection.notes.gbudb
|
|
121
|
+
if (connection.notes.gbudb?.action) {
|
|
124
122
|
switch (connection.notes.gbudb.action) {
|
|
125
123
|
case 'accept':
|
|
126
124
|
case 'quarantine':
|
|
@@ -137,8 +135,8 @@ exports.hook_data_post = function (next, connection) {
|
|
|
137
135
|
const ws = fs.createWriteStream(tmpfile);
|
|
138
136
|
|
|
139
137
|
ws.once('error', err => {
|
|
140
|
-
connection.logerror(
|
|
141
|
-
|
|
138
|
+
connection.logerror(this, `Error writing temporary file: ${err.message}`);
|
|
139
|
+
next();
|
|
142
140
|
});
|
|
143
141
|
|
|
144
142
|
ws.once('close', () => {
|
|
@@ -162,8 +160,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
162
160
|
// Parse the returned headers and add them to the message
|
|
163
161
|
const xhdr = match[1].split('\r\n');
|
|
164
162
|
const headers = [];
|
|
165
|
-
for (
|
|
166
|
-
const line = xhdr[i];
|
|
163
|
+
for (const line of xhdr) {
|
|
167
164
|
// Check for continuation
|
|
168
165
|
if (/^\s/.test(line)) {
|
|
169
166
|
// Continuation; add to previous header value
|
|
@@ -180,8 +177,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
180
177
|
}
|
|
181
178
|
}
|
|
182
179
|
// Add headers to message
|
|
183
|
-
for (
|
|
184
|
-
const header = headers[h];
|
|
180
|
+
for (const header of headers) {
|
|
185
181
|
// If present save the group for logging purposes
|
|
186
182
|
if (header.header === 'X-MessageSniffer-SNF-Group') {
|
|
187
183
|
group = header.value.replace(/\r?\n/gm, '');
|
|
@@ -191,11 +187,11 @@ exports.hook_data_post = function (next, connection) {
|
|
|
191
187
|
// Retrieve IP address determined by GBUdb
|
|
192
188
|
const gbudb_split = header.value.split(/,\s*/);
|
|
193
189
|
gbudb_ip = gbudb_split[1];
|
|
194
|
-
connection.logdebug(
|
|
190
|
+
connection.logdebug(this, `GBUdb: ${header.value.replace(/\r?\n/gm, '')}`);
|
|
195
191
|
}
|
|
196
192
|
if (header.header === 'X-MessageSniffer-Rules') {
|
|
197
193
|
rules = header.value.replace(/\r?\n/gm, '').replace(/\s+/g,' ').trim();
|
|
198
|
-
connection.logdebug(
|
|
194
|
+
connection.logdebug(this, `rules: ${rules}`);
|
|
199
195
|
}
|
|
200
196
|
// Remove any existing headers
|
|
201
197
|
txn.remove_header(header.header);
|
|
@@ -203,7 +199,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
203
199
|
}
|
|
204
200
|
}
|
|
205
201
|
// Summary log
|
|
206
|
-
connection.loginfo(
|
|
202
|
+
connection.loginfo(this, `result: time=${elapsed}ms code=${code
|
|
207
203
|
}${gbudb_ip ? ` ip="${gbudb_ip}"` : ''
|
|
208
204
|
}${group ? ` group="${group}"` : ''
|
|
209
205
|
}${rules ? ` rule_count=${rules.split(/\s+/).length}` : ''
|
|
@@ -249,7 +245,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
249
245
|
action = cfg.message.nonzero;
|
|
250
246
|
}
|
|
251
247
|
else {
|
|
252
|
-
return next(DENY,
|
|
248
|
+
return next(DENY, `Spam detected by MessageSniffer (code=${code} group=${group})`);
|
|
253
249
|
}
|
|
254
250
|
}
|
|
255
251
|
}
|
|
@@ -258,7 +254,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
258
254
|
else {
|
|
259
255
|
// Default with no configuration
|
|
260
256
|
if (code > 1 && code !== 40) {
|
|
261
|
-
return next(DENY,
|
|
257
|
+
return next(DENY, `Spam detected by MessageSniffer (code=${code} group=${group})`);
|
|
262
258
|
}
|
|
263
259
|
else {
|
|
264
260
|
return next();
|
|
@@ -318,7 +314,7 @@ exports.hook_data_post = function (next, connection) {
|
|
|
318
314
|
}
|
|
319
315
|
else {
|
|
320
316
|
// Something must have gone wrong
|
|
321
|
-
connection.logwarn(
|
|
317
|
+
connection.logwarn(this, `unexpected response: ${result}`);
|
|
322
318
|
}
|
|
323
319
|
return next();
|
|
324
320
|
});
|
|
@@ -329,7 +325,6 @@ exports.hook_data_post = function (next, connection) {
|
|
|
329
325
|
}
|
|
330
326
|
|
|
331
327
|
exports.hook_disconnect = function (next, connection) {
|
|
332
|
-
const self = this;
|
|
333
328
|
const cfg = this.config.get('messagesniffer.ini');
|
|
334
329
|
|
|
335
330
|
// Train GBUdb on rejected messages and recipients
|
|
@@ -338,16 +333,16 @@ exports.hook_disconnect = function (next, connection) {
|
|
|
338
333
|
const snfreq = `<snf><xci><gbudb><bad ip='${connection.remote.ip}'/></gbudb></xci></snf>`;
|
|
339
334
|
SNFClient(snfreq, (err, result) => {
|
|
340
335
|
if (err) {
|
|
341
|
-
connection.logerror(
|
|
336
|
+
connection.logerror(this, err.message);
|
|
342
337
|
}
|
|
343
338
|
else {
|
|
344
|
-
connection.logdebug(
|
|
339
|
+
connection.logdebug(this, `GBUdb bad encounter added for ${connection.remote.ip}`);
|
|
345
340
|
}
|
|
346
|
-
|
|
341
|
+
next();
|
|
347
342
|
});
|
|
348
343
|
}
|
|
349
344
|
else {
|
|
350
|
-
|
|
345
|
+
next();
|
|
351
346
|
}
|
|
352
347
|
}
|
|
353
348
|
|
|
@@ -357,7 +352,7 @@ function SNFClient (req, cb) {
|
|
|
357
352
|
sock.setTimeout(30 * 1000); // Connection timeout
|
|
358
353
|
sock.once('timeout', function () {
|
|
359
354
|
this.destroy();
|
|
360
|
-
|
|
355
|
+
cb(new Error('connection timed out'));
|
|
361
356
|
});
|
|
362
357
|
sock.once('error', err => cb(err));
|
|
363
358
|
sock.once('connect', function () {
|
|
@@ -372,16 +367,14 @@ function SNFClient (req, cb) {
|
|
|
372
367
|
});
|
|
373
368
|
sock.once('end', () => {
|
|
374
369
|
// Check for result
|
|
370
|
+
if (/<result /.exec(result)) return cb(null, result);
|
|
371
|
+
|
|
375
372
|
let match;
|
|
376
|
-
if (/<
|
|
377
|
-
return cb(null, result);
|
|
378
|
-
}
|
|
379
|
-
else if ((match = /<error message='([^']+)'/.exec(result))) {
|
|
373
|
+
if ((match = /<error message='([^']+)'/.exec(result))) {
|
|
380
374
|
return cb(new Error(match[1]));
|
|
381
375
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
376
|
+
|
|
377
|
+
cb(new Error(`unexpected result: ${result}`));
|
|
385
378
|
});
|
|
386
379
|
// Start the sequence
|
|
387
380
|
sock.connect(port);
|
|
@@ -6,8 +6,10 @@ function escapeRegExp (str) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
exports.hook_data = (next, connection) => {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const { notes, transaction } = connection ?? {}
|
|
10
|
+
|
|
11
|
+
if (transaction && notes?.auth_user && notes.auth_passwd) {
|
|
12
|
+
transaction.parse_body = true;
|
|
11
13
|
}
|
|
12
14
|
next();
|
|
13
15
|
}
|
|
@@ -19,8 +21,8 @@ exports.hook_data_post = (next, connection) => {
|
|
|
19
21
|
|
|
20
22
|
let user = connection.notes.auth_user;
|
|
21
23
|
let domain;
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
+
const idx = user.indexOf('@')
|
|
25
|
+
if (idx) {
|
|
24
26
|
// If the username is qualified (e.g. user@domain.com)
|
|
25
27
|
// then we make the @domain.com part optional in the regexp.
|
|
26
28
|
domain = user.substr(idx);
|
|
@@ -34,7 +36,7 @@ exports.hook_data_post = (next, connection) => {
|
|
|
34
36
|
(domain ? `(?:${escapeRegExp(domain)})?` : '') +
|
|
35
37
|
bound_regexp, 'im');
|
|
36
38
|
|
|
37
|
-
if (look_for_credentials(user_regexp, passwd_regexp, connection
|
|
39
|
+
if (look_for_credentials(user_regexp, passwd_regexp, connection?.transaction?.body)) {
|
|
38
40
|
return next(DENY, "Credential leak detected: never give out your username/password to anyone!");
|
|
39
41
|
}
|
|
40
42
|
|
package/plugins/process_title.js
CHANGED
|
@@ -59,7 +59,7 @@ exports.hook_init_master = function (next, server) {
|
|
|
59
59
|
process.title = title;
|
|
60
60
|
server.notes.pt_concurrent_cluster = {};
|
|
61
61
|
server.notes.pt_new_out_stats = [0,0,0,0];
|
|
62
|
-
const cluster = server
|
|
62
|
+
const { cluster } = server;
|
|
63
63
|
const recvMsg = msg => {
|
|
64
64
|
let count;
|
|
65
65
|
switch (msg.event) {
|
|
@@ -120,7 +120,7 @@ exports.hook_init_master = function (next, server) {
|
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
this._interval = setupInterval(title, server);
|
|
123
|
-
|
|
123
|
+
next();
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
exports.hook_init_child = function (next, server) {
|
|
@@ -134,10 +134,9 @@ exports.hook_init_child = function (next, server) {
|
|
|
134
134
|
server.notes.pt_messages = 0;
|
|
135
135
|
server.notes.pt_mps_diff = 0;
|
|
136
136
|
server.notes.pt_mps_max = 0;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return next();
|
|
137
|
+
process.title = 'Haraka (worker)';
|
|
138
|
+
this._interval = setupInterval(process.title, server);
|
|
139
|
+
next();
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
exports.shutdown = function () {
|
|
@@ -146,19 +145,19 @@ exports.shutdown = function () {
|
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
exports.hook_connect_init = (next, connection) => {
|
|
149
|
-
const server = connection
|
|
148
|
+
const { server } = connection;
|
|
150
149
|
connection.notes.pt_connect_run = true;
|
|
151
150
|
if (server.cluster) {
|
|
152
|
-
const worker = server.cluster
|
|
151
|
+
const { worker } = server.cluster;
|
|
153
152
|
worker.send({event: 'process_title.connect', wid: worker.id});
|
|
154
153
|
}
|
|
155
154
|
server.notes.pt_connections++;
|
|
156
155
|
server.notes.pt_concurrent++;
|
|
157
|
-
|
|
156
|
+
next();
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
exports.hook_disconnect = (next, connection) => {
|
|
161
|
-
const server = connection
|
|
160
|
+
const { server } = connection;
|
|
162
161
|
// Check that the hook above ran
|
|
163
162
|
// It might not if the disconnection is immediate
|
|
164
163
|
// echo "QUIT" | nc localhost 25
|
|
@@ -177,25 +176,25 @@ exports.hook_disconnect = (next, connection) => {
|
|
|
177
176
|
worker.send({event: 'process_title.disconnect', wid: worker.id});
|
|
178
177
|
}
|
|
179
178
|
server.notes.pt_concurrent--;
|
|
180
|
-
|
|
179
|
+
next();
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
exports.hook_rcpt = (next, connection) => {
|
|
184
|
-
const server = connection
|
|
183
|
+
const { server } = connection;
|
|
185
184
|
if (server.cluster) {
|
|
186
|
-
const worker = server.cluster
|
|
185
|
+
const { worker } = server.cluster;
|
|
187
186
|
worker.send({event: 'process_title.recipient'});
|
|
188
187
|
}
|
|
189
188
|
server.notes.pt_recipients++;
|
|
190
|
-
|
|
189
|
+
next();
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
exports.hook_data = (next, connection) => {
|
|
194
|
-
const server = connection
|
|
193
|
+
const { server } = connection;
|
|
195
194
|
if (server.cluster) {
|
|
196
|
-
const worker = server.cluster
|
|
195
|
+
const { worker } = server.cluster;
|
|
197
196
|
worker.send({event: 'process_title.message'});
|
|
198
197
|
}
|
|
199
198
|
server.notes.pt_messages++;
|
|
200
|
-
|
|
199
|
+
next();
|
|
201
200
|
}
|
package/plugins/queue/deliver.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
const outbound = require('./outbound');
|
|
6
6
|
|
|
7
7
|
exports.hook_queue_outbound = (next, connection) => {
|
|
8
|
-
if (!connection
|
|
8
|
+
if (!connection?.relaying) {
|
|
9
9
|
return next(); // we're not relaying so don't deliver outbound
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
outbound.send_email(connection
|
|
12
|
+
outbound.send_email(connection?.transaction, next);
|
|
13
13
|
}
|
package/plugins/queue/lmtp.js
CHANGED
|
@@ -10,20 +10,18 @@ exports.register = function () {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
exports.load_lmtp_ini = function () {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
plugin.load_lmtp_ini();
|
|
13
|
+
this.cfg = this.config.get('lmtp.ini', () => {
|
|
14
|
+
this.load_lmtp_ini();
|
|
16
15
|
})
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
exports.hook_get_mx = function (next, hmail, domain) {
|
|
20
|
-
const plugin = this;
|
|
21
19
|
|
|
22
20
|
if (!hmail.todo.notes.using_lmtp) return next();
|
|
23
21
|
|
|
24
22
|
const mx = { using_lmtp: true, priority: 0, exchange: '127.0.0.1' };
|
|
25
23
|
|
|
26
|
-
const section =
|
|
24
|
+
const section = this.cfg[domain] || this.cfg.main;
|
|
27
25
|
if (section.path) {
|
|
28
26
|
Object.assign(mx, { path: section.path });
|
|
29
27
|
return next(OK, mx);
|
|
@@ -38,7 +36,8 @@ exports.hook_get_mx = function (next, hmail, domain) {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
exports.hook_queue = (next, connection) => {
|
|
41
|
-
const txn = connection
|
|
39
|
+
const txn = connection?.transaction;
|
|
40
|
+
if (!txn) return next();
|
|
42
41
|
|
|
43
42
|
const q_wants = txn.notes.get('queue.wants');
|
|
44
43
|
if (q_wants && q_wants !== 'lmtp') return next();
|
|
@@ -4,37 +4,36 @@ const childproc = require('child_process');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
|
|
6
6
|
exports.register = function () {
|
|
7
|
-
const plugin = this;
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
if (!fs.existsSync(
|
|
11
|
-
throw new Error(`Cannot find qmail-queue binary (${
|
|
8
|
+
this.queue_exec = this.config.get('qmail-queue.path') || '/var/qmail/bin/qmail-queue';
|
|
9
|
+
if (!fs.existsSync(this.queue_exec)) {
|
|
10
|
+
throw new Error(`Cannot find qmail-queue binary (${this.queue_exec})`);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
this.load_qmail_queue_ini();
|
|
15
14
|
|
|
16
|
-
if (
|
|
17
|
-
|
|
15
|
+
if (this.cfg.main.enable_outbound) {
|
|
16
|
+
this.register_hook('queue_outbound', 'hook_queue');
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
exports.load_qmail_queue_ini = function () {
|
|
22
|
-
const plugin = this;
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
this.cfg = this.config.get('qmail-queue.ini', {
|
|
25
23
|
booleans: [
|
|
26
24
|
'+main.enable_outbound',
|
|
27
25
|
],
|
|
28
26
|
},
|
|
29
27
|
() => {
|
|
30
|
-
|
|
28
|
+
this.load_qmail_queue_ini();
|
|
31
29
|
});
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
exports.hook_queue = function (next, connection) {
|
|
35
33
|
const plugin = this;
|
|
36
34
|
|
|
37
|
-
const txn = connection
|
|
35
|
+
const txn = connection?.transaction;
|
|
36
|
+
if (!txn) return next();
|
|
38
37
|
|
|
39
38
|
const q_wants = txn.notes.get('queue.wants');
|
|
40
39
|
if (q_wants && q_wants !== 'qmail-queue') return next();
|
|
@@ -58,9 +57,8 @@ exports.hook_queue = function (next, connection) {
|
|
|
58
57
|
connection.transaction.message_stream.pipe(qmail_queue.stdin, { line_endings: '\n' });
|
|
59
58
|
|
|
60
59
|
qmail_queue.stdin.on('close', () => {
|
|
61
|
-
if (!connection
|
|
60
|
+
if (!connection?.transaction) {
|
|
62
61
|
plugin.logerror("Transaction went away while delivering mail to qmail-queue");
|
|
63
|
-
|
|
64
62
|
try {
|
|
65
63
|
qmail_queue.stdout.end();
|
|
66
64
|
}
|