Haraka 2.8.28 → 3.0.0

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.
Files changed (134) hide show
  1. package/.eslintrc.yaml +2 -10
  2. package/Changes.md +68 -2
  3. package/Dockerfile +1 -1
  4. package/Plugins.md +7 -4
  5. package/README.md +2 -6
  6. package/config/outbound.ini +0 -7
  7. package/config/plugins +1 -1
  8. package/config/smtp.ini +1 -1
  9. package/config/smtp_forward.ini +2 -8
  10. package/config/smtp_proxy.ini +0 -6
  11. package/connection.js +178 -204
  12. package/coverage/lcov.info +13863 -0
  13. package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
  14. package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
  15. package/dkim.js +65 -73
  16. package/docs/Body.md +1 -22
  17. package/docs/CoreConfig.md +2 -2
  18. package/docs/Header.md +1 -47
  19. package/docs/Outbound.md +8 -36
  20. package/endpoint.js +1 -1
  21. package/haraka.js +1 -1
  22. package/host_pool.js +8 -12
  23. package/logger.js +25 -32
  24. package/outbound/client_pool.js +11 -153
  25. package/outbound/config.js +5 -11
  26. package/outbound/hmail.js +109 -143
  27. package/outbound/index.js +13 -25
  28. package/outbound/mx_lookup.js +10 -7
  29. package/outbound/queue.js +8 -12
  30. package/outbound/timer_queue.js +2 -4
  31. package/outbound/tls.js +17 -18
  32. package/outbound/todo.js +1 -0
  33. package/package.json +42 -40
  34. package/plugins/auth/auth_base.js +39 -63
  35. package/plugins/auth/auth_bridge.js +3 -4
  36. package/plugins/auth/auth_proxy.js +16 -16
  37. package/plugins/auth/auth_vpopmaild.js +30 -37
  38. package/plugins/auth/flat_file.js +9 -13
  39. package/plugins/avg.js +9 -11
  40. package/plugins/backscatterer.js +1 -1
  41. package/plugins/block_me.js +2 -6
  42. package/plugins/bounce.js +106 -124
  43. package/plugins/clamd.js +59 -63
  44. package/plugins/data.signatures.js +6 -6
  45. package/plugins/data.uribl.js +1 -415
  46. package/plugins/delay_deny.js +19 -20
  47. package/plugins/dkim_sign.js +56 -62
  48. package/plugins/dkim_verify.js +9 -8
  49. package/plugins/dns_list_base.js +43 -42
  50. package/plugins/dnsbl.js +41 -46
  51. package/plugins/dnswl.js +23 -26
  52. package/plugins/early_talker.js +24 -28
  53. package/plugins/esets.js +8 -11
  54. package/plugins/greylist.js +161 -190
  55. package/plugins/helo.checks.js +175 -197
  56. package/plugins/mail_from.is_resolvable.js +38 -38
  57. package/plugins/messagesniffer.js +33 -40
  58. package/plugins/prevent_credential_leaks.js +7 -5
  59. package/plugins/process_title.js +16 -17
  60. package/plugins/queue/deliver.js +2 -2
  61. package/plugins/queue/lmtp.js +5 -6
  62. package/plugins/queue/qmail-queue.js +11 -13
  63. package/plugins/queue/quarantine.js +25 -34
  64. package/plugins/queue/rabbitmq.js +3 -2
  65. package/plugins/queue/rabbitmq_amqplib.js +9 -9
  66. package/plugins/queue/smtp_bridge.js +5 -4
  67. package/plugins/queue/smtp_forward.js +81 -89
  68. package/plugins/queue/smtp_proxy.js +21 -22
  69. package/plugins/queue/test.js +2 -1
  70. package/plugins/rcpt_to.host_list_base.js +20 -30
  71. package/plugins/rcpt_to.in_host_list.js +12 -14
  72. package/plugins/rcpt_to.max_count.js +7 -5
  73. package/plugins/record_envelope_addresses.js +4 -6
  74. package/plugins/relay.js +64 -74
  75. package/plugins/reseed_rng.js +1 -2
  76. package/plugins/spamassassin.js +56 -68
  77. package/plugins/status.js +2 -3
  78. package/plugins/tarpit.js +8 -11
  79. package/plugins/tls.js +14 -17
  80. package/plugins/toobusy.js +6 -8
  81. package/plugins/xclient.js +14 -25
  82. package/plugins.js +24 -29
  83. package/rfc1869.js +2 -2
  84. package/server.js +3 -13
  85. package/smtp_client.js +138 -215
  86. package/tests/config/smtp_forward.ini +0 -6
  87. package/tests/fixtures/line_socket.js +1 -1
  88. package/tests/fixtures/util_hmailitem.js +5 -7
  89. package/tests/fixtures/vm_harness.js +2 -2
  90. package/tests/host_pool.js +13 -14
  91. package/tests/installation/plugins/inherits.js +1 -2
  92. package/tests/logger.js +2 -2
  93. package/tests/plugins/bounce.js +6 -8
  94. package/tests/plugins/dkim_signer.js +7 -7
  95. package/tests/plugins/dns_list_base.js +7 -7
  96. package/tests/plugins/helo.checks.js +1 -1
  97. package/tests/plugins/mail_from.is_resolvable.js +10 -54
  98. package/tests/plugins/queue/smtp_forward.js +11 -11
  99. package/tests/plugins/rcpt_to.host_list_base.js +1 -1
  100. package/tests/plugins/rcpt_to.in_host_list.js +1 -1
  101. package/tests/plugins/spamassassin.js +1 -1
  102. package/tests/queue/multibyte +0 -0
  103. package/tests/queue/plain +0 -0
  104. package/tests/rfc1869.js +4 -1
  105. package/tests/server.js +15 -9
  106. package/tests/smtp_client/auth.js +4 -14
  107. package/tests/smtp_client/basic.js +5 -15
  108. package/tests/smtp_client.js +7 -3
  109. package/tests/transaction.js +72 -19
  110. package/tls_socket.js +75 -85
  111. package/transaction.js +7 -9
  112. package/attachment_stream.js +0 -118
  113. package/bin/spf +0 -48
  114. package/chunkemitter.js +0 -75
  115. package/config/data.uribl.excludes +0 -202
  116. package/config/data.uribl.ini +0 -37
  117. package/config/spf.ini +0 -1
  118. package/docs/plugins/attachment.md +0 -92
  119. package/docs/plugins/data.uribl.md +0 -120
  120. package/docs/plugins/spf.md +0 -142
  121. package/mailbody.js +0 -502
  122. package/mailheader.js +0 -304
  123. package/messagestream.js +0 -441
  124. package/plugins/aliases.js +0 -120
  125. package/plugins/attachment.js +0 -503
  126. package/plugins/connect.p0f.js +0 -5
  127. package/plugins/spf.js +0 -327
  128. package/spf.js +0 -689
  129. package/tests/mailbody.js +0 -348
  130. package/tests/mailheader.js +0 -138
  131. package/tests/messagestream.js +0 -34
  132. package/tests/plugins/aliases.js +0 -376
  133. package/tests/plugins/spf.js +0 -251
  134. package/tests/spf.js +0 -96
package/messagestream.js DELETED
@@ -1,441 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const Stream = require('stream').Stream;
5
- const utils = require('haraka-utils');
6
-
7
- const ChunkEmitter = require('./chunkemitter');
8
-
9
- const STATE_HEADERS = 1;
10
- const STATE_BODY = 2;
11
-
12
- class MessageStream extends Stream {
13
- constructor (cfg, id, headers) {
14
- super();
15
- if (!id) throw new Error('id required');
16
- this.uuid = id;
17
- this.write_ce = null;
18
- this.read_ce = null;
19
- this.bytes_read = 0;
20
- this.state = STATE_HEADERS;
21
- this.idx = {};
22
- this.end_called = false;
23
- this.end_callback = null;
24
- this.buffered = 0;
25
- this.total_buffered = 0;
26
- this._queue = [];
27
- this.max_data_inflight = 0;
28
- this.buffer_max = (!isNaN(cfg.main.spool_after) ? Number(cfg.main.spool_after) : -1);
29
- this.spooling = false;
30
- this.fd = null;
31
- this.open_pending = false;
32
- this.spool_dir = cfg.main.spool_dir || '/tmp';
33
- this.filename = `${this.spool_dir}/${id}.eml`;
34
- this.write_pending = false;
35
-
36
- this.readable = true;
37
- this.paused = false;
38
- this.headers = headers || [];
39
- this.headers_done = false;
40
- this.headers_found_eoh = false;
41
- this.line_endings = "\r\n";
42
- this.dot_stuffing = false;
43
- this.ending_dot = false;
44
- this.buffer_size = (1024 * 64);
45
- this.start = 0;
46
- this.write_complete = false;
47
- this.ws = null;
48
- this.rs = null;
49
- this.in_pipe = false;
50
- }
51
-
52
- add_line (line) {
53
- const self = this;
54
-
55
- if (typeof line === 'string') {
56
- line = Buffer.from(line);
57
- }
58
-
59
- // create a ChunkEmitter
60
- if (!this.write_ce) {
61
- this.write_ce = new ChunkEmitter();
62
- this.write_ce.on('data', chunk => {
63
- self._write(chunk);
64
- });
65
- }
66
-
67
- this.bytes_read += line.length;
68
-
69
- // Build up an index of 'interesting' data on the fly
70
- if (this.state === STATE_HEADERS) {
71
- // Look for end of headers line
72
- if (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a) {
73
- this.idx.headers = { start: 0, end: this.bytes_read-line.length };
74
- this.state = STATE_BODY;
75
- this.idx.body = { start: this.bytes_read };
76
- }
77
- }
78
-
79
- if (this.state === STATE_BODY) {
80
- // Look for MIME boundaries
81
- if (line.length > 4 && line[0] === 0x2d && line[1] == 0x2d) {
82
- let boundary = line.slice(2).toString().replace(/\s*$/,'');
83
- if (/--\s*$/.test(line)) {
84
- // End of boundary?
85
- boundary = boundary.slice(0, -2);
86
- if (this.idx[boundary]) {
87
- this.idx[boundary].end = this.bytes_read;
88
- }
89
- }
90
- else {
91
- // Start of boundary?
92
- if (!this.idx[boundary]) {
93
- this.idx[boundary] = { start: this.bytes_read-line.length };
94
- }
95
- }
96
- }
97
- }
98
-
99
- this.write_ce.fill(line);
100
- }
101
-
102
- add_line_end (cb) {
103
- // Record body end position
104
- if (this.idx.body) {
105
- this.idx.body.end = this.bytes_read;
106
- }
107
- this.end_called = true;
108
- if (cb && typeof cb === 'function') {
109
- this.end_callback = cb;
110
- }
111
- // Call _write() only if no new data was emitted
112
- // This might happen if the message size matches
113
- // the size of the chunk buffer.
114
- if (!this.write_ce.end()) {
115
- this._write();
116
- }
117
- }
118
-
119
- _write (data) {
120
- const self = this;
121
- if (data) {
122
- this.buffered += data.length;
123
- this.total_buffered += data.length;
124
- this._queue.push(data);
125
- }
126
- // Stats
127
- if (this.buffered > this.max_data_inflight) {
128
- this.max_data_inflight = this.buffered;
129
- }
130
- // Abort if we have pending disk operations
131
- if (this.open_pending || this.write_pending) return false;
132
- // Do we need to spool to disk?
133
- if (this.buffer_max !== -1 && this.total_buffered > this.buffer_max) {
134
- this.spooling = true;
135
- }
136
- // Have we completely finished writing all data?
137
- if (this.end_called && (!this.spooling || (this.spooling && !this._queue.length))) {
138
- if (this.end_callback) this.end_callback();
139
- // Do we have any waiting readers?
140
- if (this.listeners('data').length && !this.write_complete) {
141
- this.write_complete = true;
142
- setImmediate(() => {
143
- if (self.readable && !self.paused)
144
- self._read();
145
- });
146
- }
147
- else {
148
- this.write_complete = true;
149
- }
150
- return true;
151
- }
152
- if (this.buffer_max === -1 || (this.buffered < this.buffer_max && !this.spooling)) {
153
- return true;
154
- }
155
- else {
156
- // We're spooling to disk
157
- if (!this._queue.length) {
158
- return false;
159
- }
160
- }
161
-
162
- // Open file descriptor if needed
163
- if (!this.fd && !this.open_pending) {
164
- this.open_pending = true;
165
- this.ws = fs.createWriteStream(this.filename, { flags: 'wx+', end: false })
166
- this.ws.on('open', fd => {
167
- self.fd = fd;
168
- self.open_pending = false;
169
- setImmediate(() => {
170
- self._write();
171
- });
172
- });
173
- this.ws.on('error', error => {
174
- self.emit('error', error);
175
- });
176
- }
177
-
178
- if (!this.fd) return false;
179
- const to_send = this._queue.shift();
180
- this.buffered -= to_send.length;
181
- // TODO: try and implement backpressure
182
- if (!this.ws.write(to_send)) {
183
- this.write_pending = true;
184
- this.ws.once('drain', () => {
185
- self.write_pending = false;
186
- setImmediate(() => {
187
- self._write();
188
- });
189
- });
190
- return false;
191
- }
192
- else {
193
- if (this.end_called && (!this.spooling || (this.spooling && !this._queue.length))) {
194
- return self._write();
195
- }
196
- return true;
197
- }
198
- }
199
-
200
- /*
201
- ** READABLE STREAM
202
- */
203
-
204
- _read () {
205
- const self = this;
206
- if (!this.end_called) {
207
- throw new Error('end not called!');
208
- }
209
-
210
- if (!this.readable || this.paused || !this.write_complete) return;
211
-
212
- // Buffer and send headers first.
213
- //
214
- // Headers are always stored in an array of strings
215
- // as they are heavily read and modified throughout
216
- // the reception of a message.
217
- //
218
- // Typically headers will be < 32Kb (Sendmail limit)
219
- // so we do all of them in one operation before we
220
- // loop around again (and check for pause).
221
- if (this.headers.length && !this.headers_done) {
222
- this.headers_done = true;
223
- for (let i=0; i<this.headers.length; i++) {
224
- this.read_ce.fill(this.headers[i].replace(/\r?\n/g,this.line_endings));
225
- }
226
- // Add end of headers marker
227
- this.read_ce.fill(this.line_endings);
228
- // Loop
229
- setImmediate(() => {
230
- if (self.readable && !self.paused) self._read();
231
- });
232
- }
233
- else {
234
- // Read the message body by line
235
- // If we have queued entries, then we didn't
236
- // create a queue file, so we read from memory.
237
- if (this._queue.length > 0) {
238
- // TODO: implement start/end offsets
239
- for (let i=0; i<this._queue.length; i++) {
240
- this.process_buf(this._queue[i].slice(0));
241
- }
242
- this._read_finish();
243
- }
244
- else {
245
- this.rs = fs.createReadStream(null, { fd: this.fd, start: 0 });
246
- // Prevent the file descriptor from being closed
247
- this.rs.destroy = () => {};
248
- this.rs.on('error', error => {
249
- self.emit('error', error);
250
- });
251
- this.rs.on('data', chunk => {
252
- self.process_buf(chunk);
253
- });
254
- this.rs.on('end', () => {
255
- self._read_finish();
256
- });
257
- }
258
- }
259
- }
260
-
261
- process_buf (buf) {
262
- let offset = 0;
263
- while ((offset = utils.indexOfLF(buf)) !== -1) {
264
- let line = buf.slice(0, offset+1);
265
- buf = buf.slice(line.length);
266
- // Don't output headers if they where sent already
267
- if (this.headers_done && !this.headers_found_eoh) {
268
- // Allow \r\n or \n here...
269
- if (
270
- (line.length === 2 && line[0] === 0x0d && line[1] === 0x0a) ||
271
- (line.length === 1 && line[0] === 0x0a)
272
- ) {
273
- this.headers_found_eoh = true;
274
- }
275
- continue;
276
- }
277
- // Remove dot-stuffing if required
278
- if (!this.dot_stuffing && line.length >= 4 &&
279
- line[0] === 0x2e && line[1] === 0x2e
280
- ) {
281
- line = line.slice(1);
282
- }
283
- // We store lines in native CRLF format; so strip CR if requested
284
- if (this.line_endings === '\n' && line.length >= 2 &&
285
- line[line.length-1] === 0x0a && line[line.length-2] === 0x0d
286
- ) {
287
- // copy the line to a new buffer before modifying the copy
288
- line = Buffer.from(line);
289
- line[line.length-2] = 0x0a;
290
- line = line.slice(0, line.length-1);
291
- }
292
- this.read_ce.fill(line);
293
- }
294
- // Check for data left in the buffer
295
- if (buf.length > 0) {
296
- this.read_ce.fill(buf);
297
- }
298
- }
299
-
300
- _read_finish () {
301
- const self = this;
302
- // End dot required?
303
- if (this.ending_dot) {
304
- this.read_ce.fill(`.${this.line_endings}`);
305
- }
306
- // Tell the chunk emitter to send whatever is left
307
- // We don't close the fd here so we can re-use it later.
308
- this.read_ce.end(() => {
309
- if (self.clamd_style) {
310
- // Add 0 length to notify end
311
- const buf = Buffer.alloc(4);
312
- buf.writeUInt32BE(0, 0);
313
- self.emit('data', buf);
314
- }
315
- self.in_pipe = false;
316
- self.emit('end');
317
- });
318
- }
319
-
320
- pipe (destination, options) {
321
- const self = this;
322
- if (this.in_pipe) {
323
- throw new Error('Cannot pipe while currently piping');
324
- }
325
- Stream.prototype.pipe.call(this, destination, options);
326
- // Options
327
- this.line_endings = ((options && options.line_endings) ? options.line_endings : "\r\n");
328
- this.dot_stuffing = ((options && options.dot_stuffing) ? options.dot_stuffing : false);
329
- this.ending_dot = ((options && options.ending_dot) ? options.ending_dot : false);
330
- this.clamd_style = (!!((options && options.clamd_style)));
331
- this.buffer_size = ((options && options.buffer_size) ? options.buffer_size : 1024 * 64);
332
- this.start = ((options && parseInt(options.start)) ? parseInt(options.start) : 0);
333
- // Reset
334
- this.in_pipe = true;
335
- this.readable = true;
336
- this.paused = false;
337
- this.headers_done = (options && options.skip_headers);
338
- this.headers_found_eoh = false;
339
- this.rs = null;
340
- this.read_ce = new ChunkEmitter(this.buffer_size);
341
- this.read_ce.on('data', chunk => {
342
- if (self.clamd_style) {
343
- // Prefix data length to the beginning of line
344
- const buf = Buffer.alloc(chunk.length+4);
345
- buf.writeUInt32BE(chunk.length, 0);
346
- chunk.copy(buf, 4);
347
- self.emit('data', buf);
348
- }
349
- else {
350
- self.emit('data', chunk);
351
- }
352
- });
353
- // Stream won't be readable until we've finished writing and add_line_end() has been called.
354
- // As we've registered for events above, the _write() function can now detect that we
355
- // are waiting for the data and will call _read() automatically when it is finished.
356
- if (!this.write_complete) return destination;
357
- // Create this.fd only if it doesn't already exist
358
- // This is so we can re-use the already open descriptor
359
- if (!this.fd && !(this._queue.length > 0)) {
360
- fs.open(this.filename, 'r', null, (err, fd) => {
361
- if (err) throw err;
362
- self.fd = fd;
363
- self._read();
364
- });
365
- }
366
- else {
367
- self._read();
368
- }
369
- return destination;
370
- }
371
-
372
- pause () {
373
- this.paused = true;
374
- if (this.rs) this.rs.pause();
375
- }
376
-
377
- resume () {
378
- this.paused = false;
379
- if (this.rs) {
380
- this.rs.resume();
381
- }
382
- else {
383
- this._read();
384
- }
385
- }
386
-
387
- destroy () {
388
- const self = this;
389
- try {
390
- if (this.fd) {
391
- fs.close(this.fd, err => {
392
- fs.unlink(self.filename, () => {});
393
- });
394
- }
395
- else {
396
- fs.unlink(this.filename, () => {});
397
- }
398
- }
399
- catch (err) {
400
- // Ignore any errors
401
- }
402
- }
403
-
404
- get_data (options, cb) { // Or: (cb)
405
- if (arguments.length === 1) {
406
- cb = arguments[0];
407
- options = {};
408
- }
409
- const ws = new GetDataStream(cb);
410
- this.pipe(ws, options);
411
- }
412
- }
413
-
414
- module.exports = MessageStream;
415
-
416
- class GetDataStream extends Stream {
417
- constructor (cb) {
418
- super();
419
- this.cb = cb;
420
- this.buf = Buffer.alloc(0);
421
- this.writable = true;
422
- }
423
-
424
- write (obj, enc) {
425
- this.buf = Buffer.concat([this.buf, obj]);
426
- return true;
427
- }
428
-
429
- end (obj, enc) {
430
- if (obj) this.buf = Buffer.concat([this.buf, obj]);
431
- this.cb(this.buf);
432
- }
433
-
434
- destroy () {
435
- // ignore
436
- }
437
-
438
- destroySoon () {
439
- // ignore
440
- }
441
- }
@@ -1,120 +0,0 @@
1
- // This is the aliases plugin
2
- // One must not run this plugin with the queue/smtp_proxy plugin.
3
- const Address = require('address-rfc2821').Address;
4
-
5
- exports.register = function () {
6
- this.inherits('queue/discard');
7
-
8
- this.register_hook('rcpt','aliases');
9
- }
10
-
11
- exports.aliases = function (next, connection, params) {
12
- const plugin = this;
13
- const config = this.config.get('aliases', 'json') || {};
14
- const rcpt = params[0].address();
15
- const user = params[0].user;
16
- const host = params[0].host;
17
- let match = user.split(/[+-]/, 1);
18
- let action = '<missing>';
19
-
20
- if (config[rcpt]) {
21
-
22
- action = config[rcpt].action || action;
23
- match = rcpt;
24
-
25
- switch (action.toLowerCase()) {
26
- case 'drop':
27
- _drop(plugin, connection, rcpt);
28
- break;
29
- case 'alias':
30
- _alias(plugin, connection, match, config[match], host);
31
- break;
32
- default:
33
- connection.loginfo(plugin, `unknown action: ${action}`);
34
- }
35
- }
36
-
37
- if (config[`@${host}`]) {
38
-
39
- action = config[`@${host}`].action || action;
40
- match = `@${host}`;
41
-
42
- switch (action.toLowerCase()) {
43
- case 'drop':
44
- _drop(plugin, connection, `@${host}`);
45
- break;
46
- case 'alias':
47
- _alias(plugin, connection, match, config[match], host);
48
- break;
49
- default:
50
- connection.loginfo(plugin, `unknown action: ${action}`);
51
- }
52
- }
53
-
54
- if (config[user] || config[match[0]] || config[`${match[0]}@${host}`]) {
55
- if (config[user]) {
56
- action = config[user].action || action;
57
- match = user;
58
- }
59
- else if (config[match[0]]) {
60
- action = config[match[0]].action || action;
61
- match = match[0];
62
- }
63
- else {
64
- action = config[`${match[0]}@${host}`].action || action;
65
- match = `${match[0]}@${host}`;
66
- }
67
-
68
- switch (action.toLowerCase()) {
69
- case 'drop':
70
- _drop(plugin, connection, rcpt);
71
- break;
72
- case 'alias':
73
- _alias(plugin, connection, match, config[match], host);
74
- break;
75
- default:
76
- connection.loginfo(plugin, `unknown action: ${action}`);
77
- }
78
- }
79
-
80
- next();
81
- }
82
-
83
- function _drop (plugin, connection, rcpt) {
84
- connection.logdebug(plugin, `marking ${rcpt} for drop`);
85
- connection.transaction.notes.discard = true;
86
- }
87
-
88
- function _alias (plugin, connection, key, config, host) {
89
- let to;
90
- let toAddress;
91
-
92
- if (config.to) {
93
- if (Array.isArray(config.to)) {
94
- connection.logdebug(plugin, `aliasing ${connection.transaction.rcpt_to} to ${config.to}`);
95
- connection.transaction.rcpt_to.pop();
96
- for (let i = 0, len = config.to.length; i < len; i++) {
97
- toAddress = new Address(`<${config.to[i]}>`);
98
- connection.transaction.rcpt_to.push(toAddress);
99
- }
100
- }
101
- else {
102
- if (config.to.search('@') !== -1) {
103
- to = config.to;
104
- }
105
- else {
106
- to = `${config.to}@${host}`;
107
- }
108
-
109
- connection.logdebug(plugin, `aliasing ${connection.transaction.rcpt_to} to ${to}`);
110
-
111
- toAddress = new Address(`<${to}>`);
112
- connection.transaction.rcpt_to.pop();
113
- connection.transaction.rcpt_to.push(toAddress);
114
- }
115
- connection.transaction.notes.forward = true;
116
- }
117
- else {
118
- connection.loginfo(plugin, `alias failed for ${key}, no "to" field in alias config`);
119
- }
120
- }