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.
Files changed (135) hide show
  1. package/.eslintrc.yaml +2 -10
  2. package/Changes.md +84 -2
  3. package/Dockerfile +1 -1
  4. package/Plugins.md +9 -4
  5. package/README.md +2 -6
  6. package/bin/haraka +5 -4
  7. package/config/outbound.ini +0 -7
  8. package/config/plugins +1 -1
  9. package/config/smtp.ini +1 -1
  10. package/config/smtp_forward.ini +2 -8
  11. package/config/smtp_proxy.ini +0 -6
  12. package/connection.js +178 -204
  13. package/coverage/lcov.info +13863 -0
  14. package/coverage/tmp/coverage-42958-1658373250585-0.json +1 -0
  15. package/coverage/tmp/coverage-42961-1658373250529-0.json +1 -0
  16. package/dkim.js +66 -73
  17. package/docs/Body.md +1 -22
  18. package/docs/CoreConfig.md +2 -2
  19. package/docs/Header.md +1 -47
  20. package/docs/Outbound.md +8 -36
  21. package/endpoint.js +1 -1
  22. package/haraka.js +1 -1
  23. package/host_pool.js +8 -12
  24. package/logger.js +25 -32
  25. package/outbound/client_pool.js +11 -153
  26. package/outbound/config.js +5 -11
  27. package/outbound/hmail.js +109 -143
  28. package/outbound/index.js +13 -25
  29. package/outbound/mx_lookup.js +10 -7
  30. package/outbound/queue.js +8 -12
  31. package/outbound/timer_queue.js +2 -4
  32. package/outbound/tls.js +17 -18
  33. package/outbound/todo.js +1 -0
  34. package/package.json +57 -55
  35. package/plugins/auth/auth_base.js +39 -63
  36. package/plugins/auth/auth_bridge.js +3 -4
  37. package/plugins/auth/auth_proxy.js +16 -16
  38. package/plugins/auth/auth_vpopmaild.js +30 -37
  39. package/plugins/auth/flat_file.js +9 -13
  40. package/plugins/avg.js +9 -11
  41. package/plugins/backscatterer.js +1 -1
  42. package/plugins/block_me.js +2 -6
  43. package/plugins/bounce.js +106 -124
  44. package/plugins/clamd.js +59 -63
  45. package/plugins/data.signatures.js +6 -6
  46. package/plugins/data.uribl.js +1 -415
  47. package/plugins/delay_deny.js +19 -20
  48. package/plugins/dkim_sign.js +56 -62
  49. package/plugins/dkim_verify.js +9 -8
  50. package/plugins/dns_list_base.js +43 -42
  51. package/plugins/dnsbl.js +41 -46
  52. package/plugins/dnswl.js +23 -26
  53. package/plugins/early_talker.js +24 -28
  54. package/plugins/esets.js +8 -11
  55. package/plugins/greylist.js +161 -190
  56. package/plugins/helo.checks.js +175 -197
  57. package/plugins/mail_from.is_resolvable.js +38 -38
  58. package/plugins/messagesniffer.js +33 -40
  59. package/plugins/prevent_credential_leaks.js +7 -5
  60. package/plugins/process_title.js +16 -17
  61. package/plugins/queue/deliver.js +2 -2
  62. package/plugins/queue/lmtp.js +5 -6
  63. package/plugins/queue/qmail-queue.js +11 -13
  64. package/plugins/queue/quarantine.js +25 -34
  65. package/plugins/queue/rabbitmq.js +3 -2
  66. package/plugins/queue/rabbitmq_amqplib.js +9 -9
  67. package/plugins/queue/smtp_bridge.js +5 -4
  68. package/plugins/queue/smtp_forward.js +81 -89
  69. package/plugins/queue/smtp_proxy.js +21 -22
  70. package/plugins/queue/test.js +2 -1
  71. package/plugins/rcpt_to.host_list_base.js +20 -30
  72. package/plugins/rcpt_to.in_host_list.js +12 -14
  73. package/plugins/rcpt_to.max_count.js +7 -5
  74. package/plugins/record_envelope_addresses.js +4 -6
  75. package/plugins/relay.js +64 -74
  76. package/plugins/reseed_rng.js +1 -2
  77. package/plugins/spamassassin.js +56 -68
  78. package/plugins/status.js +2 -3
  79. package/plugins/tarpit.js +8 -11
  80. package/plugins/tls.js +14 -17
  81. package/plugins/toobusy.js +6 -8
  82. package/plugins/xclient.js +14 -25
  83. package/plugins.js +24 -29
  84. package/rfc1869.js +2 -2
  85. package/server.js +3 -13
  86. package/smtp_client.js +138 -215
  87. package/tests/config/smtp_forward.ini +0 -6
  88. package/tests/fixtures/line_socket.js +1 -1
  89. package/tests/fixtures/util_hmailitem.js +5 -7
  90. package/tests/fixtures/vm_harness.js +2 -2
  91. package/tests/host_pool.js +13 -14
  92. package/tests/installation/plugins/inherits.js +1 -2
  93. package/tests/logger.js +2 -2
  94. package/tests/plugins/bounce.js +6 -8
  95. package/tests/plugins/dkim_signer.js +7 -7
  96. package/tests/plugins/dns_list_base.js +7 -7
  97. package/tests/plugins/helo.checks.js +1 -1
  98. package/tests/plugins/mail_from.is_resolvable.js +10 -54
  99. package/tests/plugins/queue/smtp_forward.js +11 -11
  100. package/tests/plugins/rcpt_to.host_list_base.js +1 -1
  101. package/tests/plugins/rcpt_to.in_host_list.js +1 -1
  102. package/tests/plugins/spamassassin.js +1 -1
  103. package/tests/queue/multibyte +0 -0
  104. package/tests/queue/plain +0 -0
  105. package/tests/rfc1869.js +4 -1
  106. package/tests/server.js +15 -9
  107. package/tests/smtp_client/auth.js +4 -14
  108. package/tests/smtp_client/basic.js +5 -15
  109. package/tests/smtp_client.js +7 -3
  110. package/tests/transaction.js +72 -19
  111. package/tls_socket.js +75 -85
  112. package/transaction.js +7 -9
  113. package/attachment_stream.js +0 -118
  114. package/bin/spf +0 -48
  115. package/chunkemitter.js +0 -75
  116. package/config/data.uribl.excludes +0 -202
  117. package/config/data.uribl.ini +0 -37
  118. package/config/spf.ini +0 -1
  119. package/docs/plugins/attachment.md +0 -92
  120. package/docs/plugins/data.uribl.md +0 -120
  121. package/docs/plugins/spf.md +0 -142
  122. package/mailbody.js +0 -502
  123. package/mailheader.js +0 -304
  124. package/messagestream.js +0 -441
  125. package/plugins/aliases.js +0 -120
  126. package/plugins/attachment.js +0 -503
  127. package/plugins/connect.p0f.js +0 -5
  128. package/plugins/spf.js +0 -327
  129. package/spf.js +0 -689
  130. package/tests/mailbody.js +0 -348
  131. package/tests/mailheader.js +0 -138
  132. package/tests/messagestream.js +0 -34
  133. package/tests/plugins/aliases.js +0 -376
  134. package/tests/plugins/spf.js +0 -251
  135. 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
- }