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/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
|
-
}
|
package/plugins/aliases.js
DELETED
|
@@ -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
|
-
}
|