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/smtp_client.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
const events = require('events');
|
|
12
12
|
|
|
13
13
|
// npm deps
|
|
14
|
-
const generic_pool = require('generic-pool');
|
|
15
14
|
const ipaddr = require('ipaddr.js');
|
|
16
15
|
const net_utils = require('haraka-net-utils');
|
|
17
16
|
const utils = require('haraka-utils');
|
|
@@ -21,7 +20,7 @@ const line_socket = require('./line_socket');
|
|
|
21
20
|
const logger = require('./logger');
|
|
22
21
|
const HostPool = require('./host_pool');
|
|
23
22
|
|
|
24
|
-
const smtp_regexp = /^(
|
|
23
|
+
const smtp_regexp = /^(\d{3})([ -])(.*)/;
|
|
25
24
|
const STATE = {
|
|
26
25
|
IDLE: 1,
|
|
27
26
|
ACTIVE: 2,
|
|
@@ -30,11 +29,11 @@ const STATE = {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
class SMTPClient extends events.EventEmitter {
|
|
33
|
-
constructor (
|
|
32
|
+
constructor (opts = {}) {
|
|
34
33
|
super();
|
|
35
34
|
this.uuid = utils.uuid();
|
|
36
|
-
this.connect_timeout = parseInt(connect_timeout) || 30;
|
|
37
|
-
this.socket = socket || line_socket.connect(port, host);
|
|
35
|
+
this.connect_timeout = parseInt(opts.connect_timeout) || 30;
|
|
36
|
+
this.socket = opts.socket || line_socket.connect(opts.port, opts.host);
|
|
38
37
|
this.socket.setTimeout(this.connect_timeout * 1000);
|
|
39
38
|
this.socket.setKeepAlive(true);
|
|
40
39
|
this.state = STATE.IDLE;
|
|
@@ -44,8 +43,8 @@ class SMTPClient extends events.EventEmitter {
|
|
|
44
43
|
this.authenticating= false;
|
|
45
44
|
this.authenticated = false;
|
|
46
45
|
this.auth_capabilities = [];
|
|
47
|
-
this.host = host;
|
|
48
|
-
this.port = port;
|
|
46
|
+
this.host = opts.host;
|
|
47
|
+
this.port = opts.port;
|
|
49
48
|
this.smtputf8 = false;
|
|
50
49
|
|
|
51
50
|
const client = this;
|
|
@@ -68,7 +67,10 @@ class SMTPClient extends events.EventEmitter {
|
|
|
68
67
|
|
|
69
68
|
if (client.command === 'auth' || client.authenticating) {
|
|
70
69
|
logger.loginfo(`SERVER RESPONSE, CLIENT ${client.command}, authenticating=${client.authenticating},code=${code},cont=${cont},msg=${msg}`);
|
|
71
|
-
if (/^3/.test(code) &&
|
|
70
|
+
if (/^3/.test(code) && (
|
|
71
|
+
msg === 'VXNlcm5hbWU6' ||
|
|
72
|
+
msg === 'dXNlcm5hbWU6' // Workaround ill-mannered SMTP servers (namely smtp.163.com)
|
|
73
|
+
)) {
|
|
72
74
|
client.emit('auth_username');
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
@@ -150,7 +152,7 @@ class SMTPClient extends events.EventEmitter {
|
|
|
150
152
|
|
|
151
153
|
client.socket.on('connect', () => {
|
|
152
154
|
// Replace connection timeout with idle timeout
|
|
153
|
-
client.socket.setTimeout((idle_timeout || 300) * 1000);
|
|
155
|
+
client.socket.setTimeout((opts.idle_timeout || 300) * 1000);
|
|
154
156
|
if (!client.socket.remoteAddress) {
|
|
155
157
|
// "Value may be undefined if the socket is destroyed"
|
|
156
158
|
logger.logdebug('socket.remoteAddress undefined');
|
|
@@ -161,28 +163,28 @@ class SMTPClient extends events.EventEmitter {
|
|
|
161
163
|
|
|
162
164
|
function closed (msg) {
|
|
163
165
|
return error => {
|
|
164
|
-
if (!error)
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
// msg is e.g. "errored" or "timed out"
|
|
166
|
+
if (!error) error = '';
|
|
167
|
+
|
|
168
168
|
// error is e.g. "Error: connect ECONNREFUSED"
|
|
169
169
|
const errMsg = `${client.uuid}: [${client.host}:${client.port}] SMTP connection ${msg} ${error}`;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
170
|
+
|
|
171
|
+
/* eslint-disable no-fallthrough */
|
|
173
172
|
switch (client.state) {
|
|
174
173
|
case STATE.ACTIVE:
|
|
174
|
+
client.emit('error', errMsg);
|
|
175
175
|
case STATE.IDLE:
|
|
176
176
|
case STATE.RELEASED:
|
|
177
177
|
client.destroy();
|
|
178
178
|
break;
|
|
179
|
+
case STATE.DESTROYED:
|
|
180
|
+
if (msg === 'errored' || msg === 'timed out') {
|
|
181
|
+
client.emit('connection-error', errMsg);
|
|
182
|
+
}
|
|
183
|
+
break
|
|
179
184
|
default:
|
|
180
185
|
}
|
|
181
|
-
if ((msg === 'errored' || msg === 'timed out') && client.state === STATE.DESTROYED) {
|
|
182
|
-
client.emit('connection-error', errMsg);
|
|
183
|
-
} // don't return, continue (original behavior)
|
|
184
186
|
|
|
185
|
-
logger.logdebug(`[
|
|
187
|
+
logger.logdebug(`[smtp_client] ${errMsg} (state=${client.state})`);
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
|
|
@@ -217,17 +219,8 @@ class SMTPClient extends events.EventEmitter {
|
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
release () {
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
this.destroy();
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
logger.logdebug(`[smtp_client_pool] ${this.uuid} resetting, state=${this.state}`);
|
|
227
|
-
if (this.state === STATE.DESTROYED) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
this.state = STATE.RELEASED;
|
|
222
|
+
if (this.state === STATE.DESTROYED) return;
|
|
223
|
+
logger.logdebug(`[smtp_client] ${this.uuid} releasing, state=${this.state}`);
|
|
231
224
|
|
|
232
225
|
[
|
|
233
226
|
'auth', 'bad_code', 'capabilities', 'client_protocol', 'connection-error',
|
|
@@ -237,47 +230,33 @@ class SMTPClient extends events.EventEmitter {
|
|
|
237
230
|
this.removeAllListeners(l);
|
|
238
231
|
})
|
|
239
232
|
|
|
240
|
-
this.
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
this.on('rset', () => {
|
|
245
|
-
logger.logdebug(`[smtp_client_pool] ${this.uuid} releasing, state=${this.state}`);
|
|
246
|
-
if (this.state === STATE.DESTROYED) return;
|
|
247
|
-
|
|
248
|
-
this.state = STATE.IDLE;
|
|
249
|
-
this.removeAllListeners('rset');
|
|
250
|
-
this.removeAllListeners('bad_code');
|
|
251
|
-
this.pool.release(this);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
this.send_command('RSET');
|
|
233
|
+
if (this.connected) this.send_command('QUIT');
|
|
234
|
+
this.destroy()
|
|
255
235
|
}
|
|
256
236
|
|
|
257
237
|
destroy () {
|
|
258
|
-
if (this.state
|
|
259
|
-
|
|
260
|
-
|
|
238
|
+
if (this.state === STATE.DESTROYED) return
|
|
239
|
+
this.state = STATE.DESTROYED;
|
|
240
|
+
this.socket.destroy();
|
|
261
241
|
}
|
|
262
242
|
|
|
263
243
|
upgrade (tls_options) {
|
|
264
|
-
const this_logger = logger;
|
|
265
244
|
|
|
266
245
|
this.socket.upgrade(tls_options, (verified, verifyError, cert, cipher) => {
|
|
267
|
-
|
|
246
|
+
logger.loginfo(`secured:${
|
|
247
|
+
|
|
268
248
|
(cipher) ? ` cipher=${cipher.name} version=${cipher.version}` : ''
|
|
269
249
|
} verified=${verified}${
|
|
270
250
|
(verifyError) ? ` error="${verifyError}"` : ''
|
|
271
|
-
}${(cert
|
|
272
|
-
}${(cert
|
|
273
|
-
}${(cert
|
|
274
|
-
}${(cert
|
|
251
|
+
}${(cert?.subject) ? ` cn="${cert.subject.CN}" organization="${cert.subject.O}"` : ''
|
|
252
|
+
}${(cert?.issuer) ? ` issuer="${cert.issuer.O}"` : ''
|
|
253
|
+
}${(cert?.valid_to) ? ` expires="${cert.valid_to}"` : ''
|
|
254
|
+
}${(cert?.fingerprint) ? ` fingerprint=${cert.fingerprint}` : ''}`);
|
|
275
255
|
});
|
|
276
256
|
}
|
|
277
257
|
|
|
278
|
-
|
|
279
258
|
is_dead_sender (plugin, connection) {
|
|
280
|
-
if (connection
|
|
259
|
+
if (connection?.transaction) return false;
|
|
281
260
|
|
|
282
261
|
// This likely means the sender went away on us, cleanup.
|
|
283
262
|
connection.logwarn(plugin, "transaction went away, releasing smtp_client");
|
|
@@ -288,64 +267,14 @@ class SMTPClient extends events.EventEmitter {
|
|
|
288
267
|
|
|
289
268
|
exports.smtp_client = SMTPClient;
|
|
290
269
|
|
|
291
|
-
// Separate pools are kept for each set of server attributes.
|
|
292
|
-
exports.get_pool = (server, port, host, cfg) => {
|
|
293
|
-
port = port || 25;
|
|
294
|
-
host = host || 'localhost';
|
|
295
|
-
if (cfg === undefined) cfg = {};
|
|
296
|
-
const connect_timeout = cfg.connect_timeout || 30;
|
|
297
|
-
const pool_timeout = cfg.pool_timeout || cfg.timeout || 300;
|
|
298
|
-
const auth_user = cfg.auth_user || 'no_user';
|
|
299
|
-
const name = `${port}:${host}:${pool_timeout}:${auth_user}`;
|
|
300
|
-
if (!server.notes.pool) server.notes.pool = {};
|
|
301
|
-
if (server.notes.pool[name]) return server.notes.pool[name];
|
|
302
|
-
|
|
303
|
-
const pool = generic_pool.Pool({
|
|
304
|
-
name,
|
|
305
|
-
create: callback => {
|
|
306
|
-
const smtp_client = new SMTPClient(port, host, connect_timeout, pool_timeout);
|
|
307
|
-
logger.logdebug(`[smtp_client_pool] uuid=${smtp_client.uuid} host=${host}` +
|
|
308
|
-
` port=${port} pool_timeout=${pool_timeout} created`);
|
|
309
|
-
callback(null, smtp_client);
|
|
310
|
-
},
|
|
311
|
-
destroy: smtp_client => {
|
|
312
|
-
logger.logdebug(`[smtp_client_pool] ${smtp_client.uuid} destroyed, state={smtp_client.state}`);
|
|
313
|
-
smtp_client.state = STATE.DESTROYED;
|
|
314
|
-
smtp_client.socket.destroy();
|
|
315
|
-
// Remove pool object from server notes once empty
|
|
316
|
-
const size = pool.getPoolSize();
|
|
317
|
-
if (size === 0) {
|
|
318
|
-
delete server.notes.pool[name];
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
max: cfg.max_connections || 1000,
|
|
322
|
-
idleTimeoutMillis: (pool_timeout - 1) * 1000,
|
|
323
|
-
log: (str, level) => {
|
|
324
|
-
level = (level === 'verbose') ? 'debug' : level;
|
|
325
|
-
logger[`log${level}`](`[smtp_client_pool] [${name}] ${str}`);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const acquire = pool.acquire;
|
|
330
|
-
pool.acquire = (callback, priority) => {
|
|
331
|
-
function callback_wrapper (err, smtp_client) {
|
|
332
|
-
smtp_client.pool = pool;
|
|
333
|
-
smtp_client.state = STATE.ACTIVE;
|
|
334
|
-
callback(err, smtp_client);
|
|
335
|
-
}
|
|
336
|
-
acquire.call(pool, callback_wrapper, priority);
|
|
337
|
-
};
|
|
338
|
-
server.notes.pool[name] = pool;
|
|
339
|
-
return pool;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
270
|
// Get a smtp_client for the given attributes.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
271
|
+
// used only in testing
|
|
272
|
+
exports.get_client = (server, callback, opts = {}) => {
|
|
273
|
+
const smtp_client = new SMTPClient(opts)
|
|
274
|
+
logger.logdebug(`[smtp_client] uuid=${smtp_client.uuid} host=${opts.host} port=${opts.port} created`)
|
|
275
|
+
callback(smtp_client)
|
|
346
276
|
}
|
|
347
277
|
|
|
348
|
-
|
|
349
278
|
exports.onCapabilitiesOutbound = (smtp_client, secured, connection, config, on_secured) => {
|
|
350
279
|
for (const line in smtp_client.response) {
|
|
351
280
|
if (/^XCLIENT/.test(smtp_client.response[line])) {
|
|
@@ -383,8 +312,8 @@ exports.onCapabilitiesOutbound = (smtp_client, secured, connection, config, on_s
|
|
|
383
312
|
if (auth_matches) {
|
|
384
313
|
smtp_client.auth_capabilities = [];
|
|
385
314
|
auth_matches = auth_matches[1].split(' ');
|
|
386
|
-
for (
|
|
387
|
-
smtp_client.auth_capabilities.push(
|
|
315
|
+
for (const authMatch of auth_matches) {
|
|
316
|
+
smtp_client.auth_capabilities.push(authMatch.toLowerCase());
|
|
388
317
|
}
|
|
389
318
|
}
|
|
390
319
|
}
|
|
@@ -406,122 +335,119 @@ exports.get_client_plugin = (plugin, connection, c, callback) => {
|
|
|
406
335
|
}
|
|
407
336
|
|
|
408
337
|
const hostport = get_hostport(connection, connection.server, c);
|
|
338
|
+
const smtp_client = new SMTPClient(hostport)
|
|
339
|
+
logger.loginfo(`[smtp_client] uuid=${smtp_client.uuid} host=${hostport.host} port=${hostport.port} created`);
|
|
409
340
|
|
|
410
|
-
|
|
341
|
+
connection.logdebug(plugin, `Got smtp_client: ${smtp_client.uuid}`);
|
|
411
342
|
|
|
412
|
-
|
|
413
|
-
connection.logdebug(plugin, `Got smtp_client: ${smtp_client.uuid}`);
|
|
343
|
+
let secured = false;
|
|
414
344
|
|
|
415
|
-
|
|
345
|
+
smtp_client.load_tls_config(plugin.tls_options);
|
|
416
346
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
delete this.next;
|
|
423
|
-
next(retval, msg);
|
|
424
|
-
}
|
|
347
|
+
smtp_client.call_next = function (retval, msg) {
|
|
348
|
+
if (this.next) {
|
|
349
|
+
const { next } = this;
|
|
350
|
+
delete this.next;
|
|
351
|
+
next(retval, msg);
|
|
425
352
|
}
|
|
353
|
+
}
|
|
426
354
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
355
|
+
smtp_client.on('client_protocol', (line) => {
|
|
356
|
+
connection.logprotocol(plugin, `C: ${line}`);
|
|
357
|
+
})
|
|
430
358
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
359
|
+
smtp_client.on('server_protocol', (line) => {
|
|
360
|
+
connection.logprotocol(plugin, `S: ${line}`);
|
|
361
|
+
})
|
|
434
362
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
smtp_client.send_command(command, connection.local.host);
|
|
441
|
-
}
|
|
363
|
+
function helo (command) {
|
|
364
|
+
if (smtp_client.xclient) {
|
|
365
|
+
smtp_client.send_command(command, connection.hello.host);
|
|
442
366
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
function on_secured () {
|
|
447
|
-
secured = true;
|
|
448
|
-
smtp_client.secured = true;
|
|
449
|
-
smtp_client.emit('greeting', 'EHLO');
|
|
367
|
+
else {
|
|
368
|
+
smtp_client.send_command(command, connection.local.host);
|
|
450
369
|
}
|
|
370
|
+
}
|
|
371
|
+
smtp_client.on('greeting', helo);
|
|
372
|
+
smtp_client.on('xclient', helo);
|
|
373
|
+
|
|
374
|
+
function on_secured () {
|
|
375
|
+
if (secured) return;
|
|
376
|
+
secured = true;
|
|
377
|
+
smtp_client.secured = true;
|
|
378
|
+
smtp_client.emit('greeting', 'EHLO');
|
|
379
|
+
}
|
|
451
380
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
381
|
+
smtp_client.on('capabilities', () => {
|
|
382
|
+
exports.onCapabilitiesOutbound(smtp_client, secured, connection, c, on_secured);
|
|
383
|
+
});
|
|
455
384
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
smtp_client.send_command('MAIL', `FROM:${connection.transaction.mail_from.format(!smtp_client.smtp_utf8)}`);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
385
|
+
smtp_client.on('helo', () => {
|
|
386
|
+
if (!c.auth || smtp_client.authenticated) {
|
|
387
|
+
if (smtp_client.is_dead_sender(plugin, connection)) return;
|
|
464
388
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
389
|
+
smtp_client.send_command('MAIL', `FROM:${connection.transaction.mail_from.format(!smtp_client.smtp_utf8)}`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (c.auth.type === null || typeof (c.auth.type) === 'undefined') return; // Ignore blank
|
|
394
|
+
const auth_type = c.auth.type.toLowerCase();
|
|
395
|
+
if (!smtp_client.auth_capabilities.includes(auth_type)) {
|
|
396
|
+
throw new Error(`Auth type "${auth_type}" not supported by server (supports: ${smtp_client.auth_capabilities.join(',')})`);
|
|
397
|
+
}
|
|
398
|
+
switch (auth_type) {
|
|
399
|
+
case 'plain':
|
|
400
|
+
if (!c.auth.user || !c.auth.pass) {
|
|
401
|
+
throw new Error("Must include auth.user and auth.pass for PLAIN auth.");
|
|
402
|
+
}
|
|
403
|
+
logger.logdebug(`[smtp_client] uuid=${smtp_client.uuid} authenticating as "${c.auth.user}"`);
|
|
404
|
+
smtp_client.send_command('AUTH', `PLAIN ${utils.base64(`${c.auth.user}\0${c.auth.user}\0${c.auth.pass}`)}`);
|
|
405
|
+
break;
|
|
406
|
+
case 'cram-md5':
|
|
407
|
+
throw new Error("Not implemented");
|
|
408
|
+
default:
|
|
409
|
+
throw new Error(`Unknown AUTH type: ${auth_type}`);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
485
412
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
413
|
+
smtp_client.on('auth', () => {
|
|
414
|
+
// if authentication has been handled by plugin(s)
|
|
415
|
+
if (smtp_client.authenticating) return;
|
|
489
416
|
|
|
490
|
-
|
|
417
|
+
if (smtp_client.is_dead_sender(plugin, connection)) return;
|
|
491
418
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
419
|
+
smtp_client.authenticated = true;
|
|
420
|
+
smtp_client.send_command('MAIL', `FROM:${connection.transaction.mail_from.format(!smtp_client.smtp_utf8)}`);
|
|
421
|
+
});
|
|
495
422
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
423
|
+
// these errors only get thrown when the connection is still active
|
|
424
|
+
smtp_client.on('error', (msg) => {
|
|
425
|
+
connection.logwarn(plugin, msg);
|
|
426
|
+
smtp_client.call_next();
|
|
427
|
+
});
|
|
501
428
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
429
|
+
// these are the errors thrown when the connection is dead
|
|
430
|
+
smtp_client.on('connection-error', (error) => {
|
|
431
|
+
// error contains e.g. "Error: connect ECONNREFUSE"
|
|
432
|
+
logger.logerror(`backend failure: ${smtp_client.host}:${smtp_client.port} - ${error}`);
|
|
433
|
+
const { host_pool } = connection.server.notes;
|
|
434
|
+
// only exists for if forwarding_host_pool is set in the config
|
|
435
|
+
if (host_pool) {
|
|
436
|
+
host_pool.failed(smtp_client.host, smtp_client.port);
|
|
437
|
+
}
|
|
438
|
+
smtp_client.call_next();
|
|
439
|
+
});
|
|
513
440
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
smtp_client.emit('helo');
|
|
520
|
-
}
|
|
441
|
+
if (smtp_client.connected) {
|
|
442
|
+
if (smtp_client.xclient) {
|
|
443
|
+
smtp_client.send_command('XCLIENT', `ADDR=${connection.remote.ip}`);
|
|
521
444
|
}
|
|
445
|
+
else {
|
|
446
|
+
smtp_client.emit('helo');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
522
449
|
|
|
523
|
-
|
|
524
|
-
});
|
|
450
|
+
callback(null, smtp_client);
|
|
525
451
|
}
|
|
526
452
|
|
|
527
453
|
function get_hostport (connection, server, cfg) {
|
|
@@ -539,15 +465,12 @@ function get_hostport (connection, server, cfg) {
|
|
|
539
465
|
const host = server.notes.host_pool.get_host();
|
|
540
466
|
if (host) return host; // { host: 1.2.3.4, port: 567 }
|
|
541
467
|
|
|
542
|
-
logger.logerror('[
|
|
468
|
+
logger.logerror('[smtp_client] no backend hosts in pool!');
|
|
543
469
|
throw new Error("no backend hosts found in pool!");
|
|
544
470
|
}
|
|
545
471
|
|
|
546
|
-
if (cfg.host && cfg.port) {
|
|
547
|
-
return { host: cfg.host, port: cfg.port };
|
|
548
|
-
}
|
|
472
|
+
if (cfg.host && cfg.port) return { host: cfg.host, port: cfg.port };
|
|
549
473
|
|
|
550
|
-
logger.logwarn("[
|
|
551
|
-
"were not found in config file");
|
|
474
|
+
logger.logwarn("[smtp_client] forwarding_host_pool or host and port were not found in config file");
|
|
552
475
|
throw new Error("You must specify either forwarding_host_pool or host and port");
|
|
553
476
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Address
|
|
3
|
+
const { Address } = require('address-rfc2821');
|
|
4
4
|
const fixtures = require('haraka-test-fixtures');
|
|
5
5
|
const stub_connection = fixtures.connection;
|
|
6
|
-
//
|
|
7
|
-
const transaction
|
|
6
|
+
// const transaction = fixtures.transaction; // not yet sufficient
|
|
7
|
+
const transaction = require('../../transaction');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -91,8 +91,7 @@ exports.createHMailItem = (outbound_context, options, callback) => {
|
|
|
91
91
|
callback('No hmail producted');
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
|
-
for (
|
|
95
|
-
const hmail = hmails[j];
|
|
94
|
+
for (const hmail of hmails) {
|
|
96
95
|
hmail.hostlist = [ delivery_domain ];
|
|
97
96
|
callback(null, hmail);
|
|
98
97
|
}
|
|
@@ -153,8 +152,7 @@ function getNextEntryFromPlaybook (ofType, playbook) {
|
|
|
153
152
|
return false;
|
|
154
153
|
}
|
|
155
154
|
if (playbook[0].from == ofType) {
|
|
156
|
-
|
|
157
|
-
return entry;
|
|
155
|
+
return playbook.shift();
|
|
158
156
|
}
|
|
159
157
|
return false;
|
|
160
158
|
}
|
|
@@ -53,7 +53,7 @@ function make_test (module_path, test_path, additional_sandbox) {
|
|
|
53
53
|
exports.add_tests = (module_path, tests_path, test_exports, add_to_sandbox) => {
|
|
54
54
|
const additional_sandbox = add_to_sandbox || {};
|
|
55
55
|
const tests = fs.readdirSync(tests_path).filter(dot_files);
|
|
56
|
-
for (
|
|
57
|
-
test_exports[
|
|
56
|
+
for (const test of tests) {
|
|
57
|
+
test_exports[test] = make_test(module_path, tests_path + test, additional_sandbox);
|
|
58
58
|
}
|
|
59
59
|
}
|
package/tests/host_pool.js
CHANGED
|
@@ -105,34 +105,33 @@ exports.HostPool = {
|
|
|
105
105
|
|
|
106
106
|
let num_reqs = 0;
|
|
107
107
|
const MockSocket = function MockSocket (pool) {
|
|
108
|
-
const self = this;
|
|
109
108
|
|
|
110
109
|
// these are the methods called from probe_dead_host
|
|
111
110
|
|
|
112
111
|
// setTimeout on the socket
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
this.pretendTimeout = () => {};
|
|
113
|
+
this.setTimeout = (ms, cb) => {
|
|
114
|
+
this.pretendTimeout = cb;
|
|
116
115
|
};
|
|
117
116
|
// handle socket.on('error', ....
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
this.listeners = {};
|
|
118
|
+
this.on = (eventname, cb) => {
|
|
119
|
+
this.listeners[eventname] = cb;
|
|
121
120
|
};
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
this.emit = eventname => {
|
|
122
|
+
this.listeners[eventname]();
|
|
124
123
|
};
|
|
125
124
|
// handle socket.connect(...
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
this.connected = () => {};
|
|
126
|
+
this.connect = (port, host, cb) => {
|
|
128
127
|
switch (++num_reqs) {
|
|
129
128
|
case 1:
|
|
130
129
|
// the first time through we pretend it timed out
|
|
131
|
-
|
|
130
|
+
this.pretendTimeout();
|
|
132
131
|
break;
|
|
133
132
|
case 2:
|
|
134
133
|
// the second time through, pretend socket error
|
|
135
|
-
|
|
134
|
+
this.emit('error');
|
|
136
135
|
break;
|
|
137
136
|
case 3:
|
|
138
137
|
// the third time around, the socket connected
|
|
@@ -144,7 +143,7 @@ exports.HostPool = {
|
|
|
144
143
|
process.exit(1);
|
|
145
144
|
}
|
|
146
145
|
};
|
|
147
|
-
|
|
146
|
+
this.destroy = () => {};
|
|
148
147
|
|
|
149
148
|
};
|
|
150
149
|
|
package/tests/logger.js
CHANGED
|
@@ -319,8 +319,8 @@ exports.add_log_methods = {
|
|
|
319
319
|
this.logger.add_log_methods(testObj);
|
|
320
320
|
const levels = ['DATA','PROTOCOL','DEBUG','INFO','NOTICE','WARN','ERROR','CRIT','ALERT','EMERG'];
|
|
321
321
|
test.expect(levels.length);
|
|
322
|
-
for (
|
|
323
|
-
test.ok('function' === typeof(testObj[`log${
|
|
322
|
+
for (const level of levels) {
|
|
323
|
+
test.ok('function' === typeof(testObj[`log${level.toLowerCase()}`]));
|
|
324
324
|
}
|
|
325
325
|
test.done();
|
|
326
326
|
},
|