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/outbound/hmail.js
CHANGED
|
@@ -6,17 +6,17 @@ const dns = require('dns');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const net = require('net');
|
|
8
8
|
|
|
9
|
-
const Address = require('address-rfc2821')
|
|
9
|
+
const { Address } = require('address-rfc2821');
|
|
10
10
|
const config = require('haraka-config');
|
|
11
11
|
const constants = require('haraka-constants');
|
|
12
12
|
const DSN = require('haraka-dsn');
|
|
13
|
+
const message = require('haraka-email-message')
|
|
13
14
|
const net_utils = require('haraka-net-utils');
|
|
14
15
|
const Notes = require('haraka-notes');
|
|
15
16
|
const utils = require('haraka-utils');
|
|
16
17
|
|
|
17
18
|
const logger = require('../logger');
|
|
18
19
|
const plugins = require('../plugins');
|
|
19
|
-
const Header = require('../mailheader').Header;
|
|
20
20
|
|
|
21
21
|
const client_pool = require('./client_pool');
|
|
22
22
|
const _qfile = require('./qfile');
|
|
@@ -48,9 +48,8 @@ class HMailItem extends events.EventEmitter {
|
|
|
48
48
|
super();
|
|
49
49
|
|
|
50
50
|
const parts = _qfile.parts(filename);
|
|
51
|
-
if (!parts) {
|
|
52
|
-
|
|
53
|
-
}
|
|
51
|
+
if (!parts) throw new Error(`Bad filename: ${filename}`);
|
|
52
|
+
|
|
54
53
|
this.path = filePath;
|
|
55
54
|
this.filename = filename;
|
|
56
55
|
this.next_process = parts.next_attempt;
|
|
@@ -158,14 +157,12 @@ class HMailItem extends events.EventEmitter {
|
|
|
158
157
|
if (obc.cfg.disabled) {
|
|
159
158
|
// try again in 1 second if delivery is disabled
|
|
160
159
|
this.logdebug("delivery disabled temporarily. Retrying in 1s.");
|
|
161
|
-
|
|
162
|
-
setTimeout(() => { hmail.send(); }, 1000);
|
|
160
|
+
setTimeout(() => { this.send(); }, 1000);
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
163
|
|
|
166
164
|
if (!this.todo) {
|
|
167
|
-
|
|
168
|
-
this.once('ready', () => { self._send(); });
|
|
165
|
+
this.once('ready', () => { this._send(); });
|
|
169
166
|
}
|
|
170
167
|
else {
|
|
171
168
|
this._send();
|
|
@@ -180,9 +177,8 @@ class HMailItem extends events.EventEmitter {
|
|
|
180
177
|
if (retval === constants.delay) {
|
|
181
178
|
// Try again in 'delay' seconds.
|
|
182
179
|
this.logdebug(`Delivery of this email delayed for ${delay_seconds} seconds`);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
temp_fail_queue.add(hmail.filename, delay_seconds * 1000, () => { delivery_queue.push(hmail); });
|
|
180
|
+
this.next_cb();
|
|
181
|
+
temp_fail_queue.add(this.filename, delay_seconds * 1000, () => { delivery_queue.push(this); });
|
|
186
182
|
}
|
|
187
183
|
else {
|
|
188
184
|
this.logdebug(`Sending mail: ${this.filename}`);
|
|
@@ -191,12 +187,11 @@ class HMailItem extends events.EventEmitter {
|
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
get_mx () {
|
|
194
|
-
const domain = this.todo
|
|
190
|
+
const { domain } = this.todo;
|
|
195
191
|
plugins.run_hooks('get_mx', this, domain);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
get_mx_respond (retval, mx) {
|
|
199
|
-
const hmail = this;
|
|
200
195
|
switch (retval) {
|
|
201
196
|
case constants.ok: {
|
|
202
197
|
let mx_list;
|
|
@@ -214,49 +209,48 @@ class HMailItem extends events.EventEmitter {
|
|
|
214
209
|
}
|
|
215
210
|
mx_list = [{priority: 0, exchange: matches[1], port: matches[3]}];
|
|
216
211
|
}
|
|
217
|
-
|
|
218
|
-
return
|
|
212
|
+
this.logdebug(`Got a MX from Plugin: ${this.todo.domain} => 0 ${JSON.stringify(mx)}`);
|
|
213
|
+
return this.found_mx(null, mx_list);
|
|
219
214
|
}
|
|
220
215
|
case constants.deny:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
216
|
+
this.logwarn(`get_mx plugin returned DENY: ${mx}`);
|
|
217
|
+
this.todo.rcpt_to.forEach(rcpt => {
|
|
218
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No MX for ${this.todo.domain}`));
|
|
224
219
|
});
|
|
225
|
-
return
|
|
220
|
+
return this.bounce(`No MX for ${this.todo.domain}`);
|
|
226
221
|
case constants.denysoft:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
this.logwarn(`get_mx plugin returned DENYSOFT: ${mx}`);
|
|
223
|
+
this.todo.rcpt_to.forEach(rcpt => {
|
|
224
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Temporary MX lookup error for ${this.todo.domain}`, 450));
|
|
230
225
|
});
|
|
231
|
-
return
|
|
226
|
+
return this.temp_fail(`Temporary MX lookup error for ${this.todo.domain}`);
|
|
232
227
|
}
|
|
233
228
|
|
|
234
229
|
// if none of the above return codes, drop through to this...
|
|
235
230
|
mx_lookup.lookup_mx(this.todo.domain, (err, mxs) => {
|
|
236
|
-
|
|
231
|
+
this.found_mx(err, mxs);
|
|
237
232
|
});
|
|
238
233
|
}
|
|
239
234
|
|
|
240
235
|
found_mx (err, mxs) {
|
|
241
|
-
const hmail = this;
|
|
242
236
|
if (err) {
|
|
243
237
|
this.lognotice(`MX Lookup for ${this.todo.domain} failed: ${err}`);
|
|
244
238
|
if (err.code === dns.NXDOMAIN || err.code === dns.NOTFOUND) {
|
|
245
239
|
this.todo.rcpt_to.forEach(rcpt => {
|
|
246
|
-
|
|
240
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`No Such Domain: ${this.todo.domain}`));
|
|
247
241
|
});
|
|
248
242
|
this.bounce(`No Such Domain: ${this.todo.domain}`);
|
|
249
243
|
}
|
|
250
244
|
else if (err.code === 'NOMX') {
|
|
251
245
|
this.todo.rcpt_to.forEach(rcpt => {
|
|
252
|
-
|
|
246
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Nowhere to deliver mail to for domain: ${this.todo.domain}`));
|
|
253
247
|
});
|
|
254
|
-
this.bounce(`Nowhere to deliver mail to for domain: ${
|
|
248
|
+
this.bounce(`Nowhere to deliver mail to for domain: ${this.todo.domain}`);
|
|
255
249
|
}
|
|
256
250
|
else {
|
|
257
251
|
// every other error is transient
|
|
258
252
|
this.todo.rcpt_to.forEach(rcpt => {
|
|
259
|
-
|
|
253
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_unspecified(`DNS lookup failure: ${this.todo.domain}`));
|
|
260
254
|
});
|
|
261
255
|
this.temp_fail(`DNS lookup failure: ${err}`);
|
|
262
256
|
}
|
|
@@ -267,7 +261,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
267
261
|
// support draft-delany-nullmx-02
|
|
268
262
|
if (mxlist.length === 1 && mxlist[0].priority === 0 && mxlist[0].exchange === '') {
|
|
269
263
|
this.todo.rcpt_to.forEach(rcpt => {
|
|
270
|
-
|
|
264
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`));
|
|
271
265
|
});
|
|
272
266
|
return this.bounce(`Domain ${this.todo.domain} sends and receives no email (NULL MX)`);
|
|
273
267
|
}
|
|
@@ -294,105 +288,100 @@ class HMailItem extends events.EventEmitter {
|
|
|
294
288
|
}
|
|
295
289
|
|
|
296
290
|
try_deliver () {
|
|
297
|
-
const self = this;
|
|
298
291
|
|
|
299
292
|
// check if there are any MXs left
|
|
300
293
|
if (this.mxlist.length === 0) {
|
|
301
294
|
this.todo.rcpt_to.forEach(rcpt => {
|
|
302
|
-
|
|
295
|
+
this.extend_rcpt_with_dsn(rcpt, DSN.addr_bad_dest_system(`Tried all MXs ${this.todo.domain}`));
|
|
303
296
|
});
|
|
304
297
|
return this.temp_fail("Tried all MXs");
|
|
305
298
|
}
|
|
306
299
|
|
|
307
300
|
const mx = this.mxlist.shift();
|
|
308
|
-
|
|
301
|
+
const host = mx.exchange;
|
|
309
302
|
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
303
|
+
this.force_tls = this.todo.force_tls;
|
|
304
|
+
if (!this.force_tls) {
|
|
305
|
+
if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, host)) {
|
|
306
|
+
this.logdebug(`Forcing TLS for host ${host}`);
|
|
307
|
+
this.force_tls = true;
|
|
308
|
+
}
|
|
309
|
+
if (net_utils.ip_in_list(obtls.cfg.force_tls_hosts, this.todo.domain)) {
|
|
310
|
+
this.logdebug(`Forcing TLS for domain ${this.todo.domain}`);
|
|
311
|
+
this.force_tls = true;
|
|
312
|
+
}
|
|
319
313
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
314
|
+
// IP or IP:port
|
|
315
|
+
if (net.isIP(host)) {
|
|
316
|
+
this.hostlist = [ host ];
|
|
317
|
+
return this.try_deliver_host(mx);
|
|
318
|
+
}
|
|
324
319
|
}
|
|
325
320
|
|
|
326
|
-
host = mx.exchange;
|
|
327
|
-
const family = mx.family;
|
|
328
|
-
|
|
329
|
-
this.loginfo(`Looking up ${family} records for: ${host}`);
|
|
330
|
-
|
|
331
321
|
// we have a host, look up the addresses for the host
|
|
332
322
|
// and try each in order they appear
|
|
333
|
-
|
|
334
|
-
dns.resolve(host, family, (err, addresses) => {
|
|
323
|
+
dns.resolve(host, mx.family, (err, addresses) => {
|
|
335
324
|
if (err) {
|
|
336
|
-
|
|
337
|
-
return
|
|
325
|
+
this.lognotice(`DNS (${mx.family}) for ${host} failed: ${err}`);
|
|
326
|
+
return this.try_deliver(); // try next MX
|
|
338
327
|
}
|
|
339
328
|
if (addresses.length === 0) {
|
|
340
329
|
// NODATA or empty host list
|
|
341
|
-
|
|
342
|
-
return
|
|
330
|
+
this.lognotice(`DNS (${mx.family}) for ${host} resulted in no data`);
|
|
331
|
+
return this.try_deliver(); // try next MX
|
|
343
332
|
}
|
|
344
|
-
|
|
345
|
-
|
|
333
|
+
this.logdebug(`DNS (${mx.family}) for ${host} -> ${addresses.join(',')}`);
|
|
334
|
+
this.hostlist = addresses;
|
|
335
|
+
this.try_deliver_host(mx);
|
|
346
336
|
});
|
|
347
337
|
}
|
|
348
338
|
|
|
349
339
|
try_deliver_host (mx) {
|
|
350
|
-
const self = this;
|
|
351
340
|
|
|
352
|
-
if (
|
|
353
|
-
return
|
|
341
|
+
if (this.hostlist.length === 0) {
|
|
342
|
+
return this.try_deliver(); // try next MX
|
|
354
343
|
}
|
|
355
344
|
|
|
356
345
|
// Allow transaction notes to set outbound IP
|
|
357
|
-
if (!mx.bind &&
|
|
358
|
-
mx.bind =
|
|
346
|
+
if (!mx.bind && this.todo.notes.outbound_ip) {
|
|
347
|
+
mx.bind = this.todo.notes.outbound_ip;
|
|
359
348
|
}
|
|
360
349
|
|
|
361
350
|
// Allow transaction notes to set outbound IP helo
|
|
362
351
|
if (!mx.bind_helo){
|
|
363
|
-
if (
|
|
364
|
-
mx.bind_helo =
|
|
352
|
+
if (this.todo.notes.outbound_helo) {
|
|
353
|
+
mx.bind_helo = this.todo.notes.outbound_helo;
|
|
365
354
|
}
|
|
366
355
|
else {
|
|
367
356
|
mx.bind_helo = net_utils.get_primary_host_name();
|
|
368
357
|
}
|
|
369
358
|
}
|
|
370
359
|
|
|
371
|
-
let host =
|
|
360
|
+
let host = this.hostlist.shift();
|
|
372
361
|
const port = mx.port || 25;
|
|
373
362
|
|
|
374
363
|
if (mx.path) {
|
|
375
364
|
host = mx.path;
|
|
376
365
|
}
|
|
377
366
|
|
|
378
|
-
this.
|
|
367
|
+
this.logdebug(`delivering from: ${mx.bind_helo} to: ${host}:${port}${mx.using_lmtp ? " using LMTP" : ""} (${delivery_queue.length()}) (${temp_fail_queue.length()})`)
|
|
379
368
|
client_pool.get_client(port, host, mx.bind, !!mx.path, (err, socket) => {
|
|
380
369
|
if (err) {
|
|
381
|
-
if (
|
|
382
|
-
logger.lognotice(`[outbound] Failed to get
|
|
370
|
+
if (/connection timed out|connect ECONNREFUSED/.test(err)) {
|
|
371
|
+
logger.lognotice(`[outbound] Failed to get socket: ${err}`);
|
|
383
372
|
}
|
|
384
373
|
else {
|
|
385
|
-
logger.logerror(`[outbound] Failed to get
|
|
374
|
+
logger.logerror(`[outbound] Failed to get socket: ${err}`);
|
|
386
375
|
}
|
|
387
376
|
// try next host
|
|
388
|
-
return
|
|
377
|
+
return this.try_deliver_host(mx);
|
|
389
378
|
}
|
|
390
|
-
|
|
379
|
+
this.try_deliver_host_on_socket(mx, host, port, socket);
|
|
391
380
|
});
|
|
392
381
|
}
|
|
393
382
|
|
|
394
383
|
try_deliver_host_on_socket (mx, host, port, socket) {
|
|
395
|
-
const self
|
|
384
|
+
const self = this;
|
|
396
385
|
let processing_mail = true;
|
|
397
386
|
let command = mx.using_lmtp ? 'connect_lmtp' : 'connect';
|
|
398
387
|
|
|
@@ -406,24 +395,24 @@ class HMailItem extends events.EventEmitter {
|
|
|
406
395
|
});
|
|
407
396
|
|
|
408
397
|
socket.once('error', err => {
|
|
409
|
-
if (processing_mail)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
})
|
|
398
|
+
if (!processing_mail) return
|
|
399
|
+
|
|
400
|
+
self.logerror(`Ongoing connection failed to ${host}:${port} : ${err}`);
|
|
401
|
+
processing_mail = false;
|
|
402
|
+
client_pool.release_client(socket, port, host, mx.bind, true);
|
|
403
|
+
if (err.source === 'tls') // exception thrown from tls_socket during tls upgrade
|
|
404
|
+
return obtls.mark_tls_nogo(host, () => { return self.try_deliver_host(mx); });
|
|
405
|
+
// try the next MX
|
|
406
|
+
self.try_deliver_host(mx);
|
|
407
|
+
})
|
|
419
408
|
|
|
420
409
|
socket.once('close', () => {
|
|
421
|
-
if (processing_mail)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
410
|
+
if (!processing_mail) return
|
|
411
|
+
|
|
412
|
+
self.logerror(`Remote end ${host}:${port} closed connection while we were processing mail. Trying next MX.`);
|
|
413
|
+
processing_mail = false;
|
|
414
|
+
client_pool.release_client(socket, port, host, mx.bind, true);
|
|
415
|
+
self.try_deliver_host(mx);
|
|
427
416
|
});
|
|
428
417
|
|
|
429
418
|
let fin_sent = false;
|
|
@@ -559,7 +548,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
559
548
|
|
|
560
549
|
switch (mx.auth_type.toUpperCase()) {
|
|
561
550
|
case 'PLAIN':
|
|
562
|
-
return send_command('AUTH', `PLAIN ${utils.base64(
|
|
551
|
+
return send_command('AUTH', `PLAIN ${utils.base64(`\0${mx.auth_user}\0${mx.auth_pass}`)}`);
|
|
563
552
|
case 'LOGIN':
|
|
564
553
|
authenticating = true;
|
|
565
554
|
return send_command('AUTH', 'LOGIN');
|
|
@@ -667,15 +656,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
667
656
|
self.discard();
|
|
668
657
|
}
|
|
669
658
|
|
|
670
|
-
|
|
671
|
-
client_pool.release_client(socket, port, host, mx.bind, fin_sent);
|
|
672
|
-
}
|
|
673
|
-
else if (obc.cfg.pool_concurrency_max && !mx.using_lmtp) {
|
|
674
|
-
send_command('RSET');
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
send_command('QUIT');
|
|
678
|
-
}
|
|
659
|
+
send_command('QUIT');
|
|
679
660
|
}
|
|
680
661
|
|
|
681
662
|
socket.on('line', line => {
|
|
@@ -742,7 +723,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
742
723
|
rcpt.dsn_smtp_response = response.join(' ');
|
|
743
724
|
rcpt.dsn_remote_mta = mx.exchange;
|
|
744
725
|
});
|
|
745
|
-
send_command(
|
|
726
|
+
send_command('QUIT');
|
|
746
727
|
processing_mail = false;
|
|
747
728
|
return self.temp_fail(`Upstream error: ${code} ${(extc) ? `${extc} ` : ''}${reason}`);
|
|
748
729
|
}
|
|
@@ -780,7 +761,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
780
761
|
rcpt.dsn_smtp_response = response.join(' ');
|
|
781
762
|
rcpt.dsn_remote_mta = mx.exchange;
|
|
782
763
|
});
|
|
783
|
-
send_command(
|
|
764
|
+
send_command('QUIT');
|
|
784
765
|
processing_mail = false;
|
|
785
766
|
return self.temp_fail(`Upstream error: ${code} ${(extc) ? `${extc} ` : ''}${reason}`);
|
|
786
767
|
}
|
|
@@ -831,7 +812,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
831
812
|
rcpt.dsn_smtp_response = response.join(' ');
|
|
832
813
|
rcpt.dsn_remote_mta = mx.exchange;
|
|
833
814
|
});
|
|
834
|
-
send_command(
|
|
815
|
+
send_command('QUIT');
|
|
835
816
|
processing_mail = false;
|
|
836
817
|
return self.bounce(reason, { mx });
|
|
837
818
|
}
|
|
@@ -862,13 +843,13 @@ class HMailItem extends events.EventEmitter {
|
|
|
862
843
|
loginfo.version = cipher.version;
|
|
863
844
|
}
|
|
864
845
|
if (verifyError) loginfo.error = verifyError;
|
|
865
|
-
if (cert
|
|
846
|
+
if (cert?.subject) {
|
|
866
847
|
loginfo.cn = cert.subject.CN;
|
|
867
848
|
loginfo.organization = cert.subject.O;
|
|
868
849
|
}
|
|
869
|
-
if (cert
|
|
870
|
-
if (cert
|
|
871
|
-
if (cert
|
|
850
|
+
if (cert?.issuer) loginfo.issuer = cert.issuer.O;
|
|
851
|
+
if (cert?.valid_to) loginfo.expires = cert.valid_to;
|
|
852
|
+
if (cert?.fingerprint) loginfo.fingerprint = cert.fingerprint;
|
|
872
853
|
self.loginfo('secured', loginfo);
|
|
873
854
|
|
|
874
855
|
if (self.force_tls && !authorized) {
|
|
@@ -935,13 +916,6 @@ class HMailItem extends events.EventEmitter {
|
|
|
935
916
|
}
|
|
936
917
|
break;
|
|
937
918
|
case 'quit':
|
|
938
|
-
if (obc.cfg.pool_concurrency_max) {
|
|
939
|
-
self.logerror("We should NOT have sent QUIT from here...");
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
client_pool.release_client(socket, port, host, mx.bind, fin_sent);
|
|
943
|
-
}
|
|
944
|
-
break;
|
|
945
919
|
case 'rset':
|
|
946
920
|
client_pool.release_client(socket, port, host, mx.bind, fin_sent);
|
|
947
921
|
break;
|
|
@@ -953,9 +927,9 @@ class HMailItem extends events.EventEmitter {
|
|
|
953
927
|
});
|
|
954
928
|
|
|
955
929
|
if (socket.__fromPool) {
|
|
956
|
-
logger.logdebug('[outbound] got
|
|
930
|
+
logger.logdebug('[outbound] got socket, trying to deliver');
|
|
957
931
|
secured = socket.isEncrypted();
|
|
958
|
-
logger.logdebug(`[outbound] got
|
|
932
|
+
logger.logdebug(`[outbound] got ${secured ? 'TLS ' : '' }socket, trying to deliver`);
|
|
959
933
|
send_command('MAIL', `FROM:${self.todo.mail_from.format(!smtp_properties.smtp_utf8)}`);
|
|
960
934
|
}
|
|
961
935
|
}
|
|
@@ -973,12 +947,11 @@ class HMailItem extends events.EventEmitter {
|
|
|
973
947
|
}
|
|
974
948
|
|
|
975
949
|
populate_bounce_message (from, to, reason, cb) {
|
|
976
|
-
const self = this;
|
|
977
950
|
|
|
978
951
|
let buf = '';
|
|
979
952
|
const original_header_lines = [];
|
|
980
953
|
let headers_done = false;
|
|
981
|
-
const header = new Header();
|
|
954
|
+
const header = new message.Header();
|
|
982
955
|
|
|
983
956
|
try {
|
|
984
957
|
const data_stream = this.data_stream();
|
|
@@ -1001,14 +974,14 @@ class HMailItem extends events.EventEmitter {
|
|
|
1001
974
|
if (original_header_lines.length > 0) {
|
|
1002
975
|
header.parse(original_header_lines);
|
|
1003
976
|
}
|
|
1004
|
-
|
|
977
|
+
this.populate_bounce_message_with_headers(from, to, reason, header, cb);
|
|
1005
978
|
});
|
|
1006
979
|
data_stream.on('error', err => {
|
|
1007
980
|
cb(err);
|
|
1008
981
|
});
|
|
1009
982
|
}
|
|
1010
983
|
catch (err) {
|
|
1011
|
-
|
|
984
|
+
this.populate_bounce_message_with_headers(from, to, reason, header, cb);
|
|
1012
985
|
}
|
|
1013
986
|
}
|
|
1014
987
|
|
|
@@ -1035,7 +1008,6 @@ class HMailItem extends events.EventEmitter {
|
|
|
1035
1008
|
* @param cb - a callback for fn(err, message_body_lines)
|
|
1036
1009
|
*/
|
|
1037
1010
|
populate_bounce_message_with_headers (from, to, reason, header, cb) {
|
|
1038
|
-
const self = this;
|
|
1039
1011
|
const CRLF = '\r\n';
|
|
1040
1012
|
|
|
1041
1013
|
const originalMessageId = header.get('Message-Id');
|
|
@@ -1175,10 +1147,10 @@ class HMailItem extends events.EventEmitter {
|
|
|
1175
1147
|
bounce_body.push(`Original-Envelope-Id: ${originalMessageId.replace(/(\r?\n)*$/, '')}${CRLF}`);
|
|
1176
1148
|
}
|
|
1177
1149
|
bounce_body.push(`Reporting-MTA: dns;${net_utils.get_primary_host_name()}${CRLF}`);
|
|
1178
|
-
if (
|
|
1179
|
-
bounce_body.push(`Arrival-Date: ${utils.date_to_str(new Date(
|
|
1150
|
+
if (this.todo.queue_time) {
|
|
1151
|
+
bounce_body.push(`Arrival-Date: ${utils.date_to_str(new Date(this.todo.queue_time))}${CRLF}`);
|
|
1180
1152
|
}
|
|
1181
|
-
|
|
1153
|
+
this.todo.rcpt_to.forEach(rcpt_to => {
|
|
1182
1154
|
bounce_body.push(CRLF);
|
|
1183
1155
|
bounce_body.push(`Final-Recipient: rfc822;${rcpt_to.address()}${CRLF}`);
|
|
1184
1156
|
let dsn_action = null;
|
|
@@ -1211,7 +1183,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1211
1183
|
bounce_body.push(`Action: ${dsn_action}${CRLF}`);
|
|
1212
1184
|
}
|
|
1213
1185
|
if (rcpt_to.dsn_status) {
|
|
1214
|
-
let dsn_status = rcpt_to
|
|
1186
|
+
let { dsn_status } = rcpt_to;
|
|
1215
1187
|
if (rcpt_to.dsn_code || rcpt_to.dsn_msg) {
|
|
1216
1188
|
dsn_status += " (";
|
|
1217
1189
|
if (rcpt_to.dsn_code) {
|
|
@@ -1266,9 +1238,7 @@ class HMailItem extends events.EventEmitter {
|
|
|
1266
1238
|
bounce (err, opts) {
|
|
1267
1239
|
this.loginfo(`bouncing mail: ${err}`);
|
|
1268
1240
|
if (!this.todo) {
|
|
1269
|
-
|
|
1270
|
-
const self = this;
|
|
1271
|
-
self.once('ready', () => { self._bounce(err, opts); });
|
|
1241
|
+
this.once('ready', () => { this._bounce(err, opts); });
|
|
1272
1242
|
return;
|
|
1273
1243
|
}
|
|
1274
1244
|
this._bounce(err, opts);
|
|
@@ -1394,20 +1364,18 @@ class HMailItem extends events.EventEmitter {
|
|
|
1394
1364
|
parts.next_attempt = Date.now() + delay;
|
|
1395
1365
|
parts.attempts = this.num_failures;
|
|
1396
1366
|
const new_filename = _qfile.name(parts);
|
|
1397
|
-
// const new_filename = this`.filename.replace(/^(\d+)_(\d+)_/, until + '_' + this.num_failures + '_');
|
|
1398
1367
|
|
|
1399
|
-
const hmail = this;
|
|
1400
1368
|
fs.rename(this.path, path.join(queue_dir, new_filename), err => {
|
|
1401
1369
|
if (err) {
|
|
1402
|
-
return
|
|
1370
|
+
return this.bounce(`Error re-queueing email: ${err}`);
|
|
1403
1371
|
}
|
|
1404
1372
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1373
|
+
this.path = path.join(queue_dir, new_filename);
|
|
1374
|
+
this.filename = new_filename;
|
|
1407
1375
|
|
|
1408
|
-
|
|
1376
|
+
this.next_cb();
|
|
1409
1377
|
|
|
1410
|
-
temp_fail_queue.add(
|
|
1378
|
+
temp_fail_queue.add(this.filename, delay, () => { delivery_queue.push(this); });
|
|
1411
1379
|
});
|
|
1412
1380
|
}
|
|
1413
1381
|
|
|
@@ -1458,17 +1426,15 @@ class HMailItem extends events.EventEmitter {
|
|
|
1458
1426
|
fs.rename(tmp_path, dest_path, err => {
|
|
1459
1427
|
if (err) {
|
|
1460
1428
|
err_handler(err, "tmp file rename");
|
|
1429
|
+
return
|
|
1461
1430
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
split_mail
|
|
1465
|
-
|
|
1466
|
-
});
|
|
1467
|
-
}
|
|
1431
|
+
const split_mail = new HMailItem (fname, dest_path, hmail.notes);
|
|
1432
|
+
split_mail.once('ready', () => {
|
|
1433
|
+
cb(split_mail);
|
|
1434
|
+
});
|
|
1468
1435
|
});
|
|
1469
1436
|
});
|
|
1470
1437
|
ws.destroySoon();
|
|
1471
|
-
return;
|
|
1472
1438
|
});
|
|
1473
1439
|
}
|
|
1474
1440
|
|
package/outbound/index.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
const async = require('async');
|
|
7
|
-
const Address = require('address-rfc2821')
|
|
7
|
+
const { Address } = require('address-rfc2821');
|
|
8
8
|
const config = require('haraka-config');
|
|
9
9
|
const constants = require('haraka-constants');
|
|
10
10
|
const net_utils = require('haraka-net-utils');
|
|
@@ -20,12 +20,9 @@ const obc = require('./config');
|
|
|
20
20
|
const queuelib = require('./queue');
|
|
21
21
|
const HMailItem = require('./hmail');
|
|
22
22
|
const TODOItem = require('./todo');
|
|
23
|
-
const pools = require('./client_pool');
|
|
24
23
|
const _qfile = exports.qfile = require('./qfile');
|
|
25
24
|
|
|
26
|
-
const queue_dir = queuelib
|
|
27
|
-
const temp_fail_queue = queuelib.temp_fail_queue;
|
|
28
|
-
const delivery_queue = queuelib.delivery_queue;
|
|
25
|
+
const { queue_dir, temp_fail_queue, delivery_queue } = queuelib;
|
|
29
26
|
|
|
30
27
|
exports.temp_fail_queue = temp_fail_queue;
|
|
31
28
|
exports.delivery_queue = delivery_queue;
|
|
@@ -42,34 +39,30 @@ exports.load_pid_queue = queuelib.load_pid_queue;
|
|
|
42
39
|
exports.ensure_queue_dir = queuelib.ensure_queue_dir;
|
|
43
40
|
exports.load_queue = queuelib.load_queue;
|
|
44
41
|
exports.stats = queuelib.stats;
|
|
45
|
-
exports.drain_pools = pools.drain_pools;
|
|
46
42
|
|
|
47
43
|
process.on('message', msg => {
|
|
48
|
-
if (msg.event
|
|
44
|
+
if (!msg.event) return
|
|
45
|
+
|
|
46
|
+
if (msg.event === 'outbound.load_pid_queue') {
|
|
49
47
|
exports.load_pid_queue(msg.data);
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
52
|
-
if (msg.event
|
|
50
|
+
if (msg.event === 'outbound.flush_queue') {
|
|
53
51
|
exports.flush_queue(msg.domain, process.pid);
|
|
54
52
|
return;
|
|
55
53
|
}
|
|
56
|
-
if (msg.event
|
|
54
|
+
if (msg.event === 'outbound.shutdown') {
|
|
57
55
|
logger.loginfo("[outbound] Shutting down temp fail queue");
|
|
58
|
-
exports.drain_pools();
|
|
59
56
|
temp_fail_queue.shutdown();
|
|
60
57
|
return;
|
|
61
58
|
}
|
|
62
|
-
if (msg.event && msg.event === 'outbound.drain_pools') {
|
|
63
|
-
exports.drain_pools();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
59
|
// ignores the message
|
|
67
60
|
});
|
|
68
61
|
|
|
69
62
|
exports.send_email = function () {
|
|
70
63
|
|
|
71
64
|
if (arguments.length === 2) {
|
|
72
|
-
logger.
|
|
65
|
+
logger.logdebug("[outbound] Sending email as a transaction");
|
|
73
66
|
return this.send_trans_email(arguments[0], arguments[1]);
|
|
74
67
|
}
|
|
75
68
|
|
|
@@ -227,7 +220,6 @@ function get_deliveries (transaction) {
|
|
|
227
220
|
}
|
|
228
221
|
|
|
229
222
|
exports.send_trans_email = function (transaction, next) {
|
|
230
|
-
const self = this;
|
|
231
223
|
|
|
232
224
|
// add potentially missing headers
|
|
233
225
|
if (!transaction.header.get_all('Message-Id').length) {
|
|
@@ -243,9 +235,7 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
243
235
|
transaction.add_leading_header('Received', `(${obc.cfg.received_header}); ${utils.date_to_str(new Date())}`);
|
|
244
236
|
}
|
|
245
237
|
|
|
246
|
-
const connection = {
|
|
247
|
-
transaction,
|
|
248
|
-
};
|
|
238
|
+
const connection = { transaction };
|
|
249
239
|
|
|
250
240
|
logger.add_log_methods(connection);
|
|
251
241
|
if (!transaction.results) {
|
|
@@ -264,7 +254,7 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
264
254
|
const todo = new TODOItem(deliv.domain, deliv.rcpts, transaction);
|
|
265
255
|
todo.uuid = `${todo.uuid}.${todo_index}`;
|
|
266
256
|
todo_index++;
|
|
267
|
-
|
|
257
|
+
this.process_delivery(ok_paths, todo, hmails, cb);
|
|
268
258
|
},
|
|
269
259
|
(err) => {
|
|
270
260
|
if (err) {
|
|
@@ -276,8 +266,7 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
276
266
|
return;
|
|
277
267
|
}
|
|
278
268
|
|
|
279
|
-
for (
|
|
280
|
-
const hmail = hmails[j];
|
|
269
|
+
for (const hmail of hmails) {
|
|
281
270
|
delivery_queue.push(hmail);
|
|
282
271
|
}
|
|
283
272
|
|
|
@@ -292,8 +281,7 @@ exports.send_trans_email = function (transaction, next) {
|
|
|
292
281
|
}
|
|
293
282
|
|
|
294
283
|
exports.process_delivery = function (ok_paths, todo, hmails, cb) {
|
|
295
|
-
|
|
296
|
-
logger.loginfo(`[outbound] Processing delivery for domain: ${todo.domain}`);
|
|
284
|
+
logger.loginfo(`[outbound] Transaction delivery for domain: ${todo.domain}`);
|
|
297
285
|
const fname = _qfile.name();
|
|
298
286
|
const tmp_path = path.join(queue_dir, `${_qfile.platformDOT}${fname}`);
|
|
299
287
|
const ws = new FsyncWriteStream(tmp_path, { flags: constants.WRITE_EXCL });
|
|
@@ -321,7 +309,7 @@ exports.process_delivery = function (ok_paths, todo, hmails, cb) {
|
|
|
321
309
|
cb("Queueing failed");
|
|
322
310
|
})
|
|
323
311
|
|
|
324
|
-
|
|
312
|
+
this.build_todo(todo, ws, () => {
|
|
325
313
|
todo.message_stream.pipe(ws, { line_endings: '\r\n', dot_stuffing: true, ending_dot: false });
|
|
326
314
|
});
|
|
327
315
|
}
|