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
|
@@ -4,12 +4,11 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
exports.register = function () {
|
|
7
|
-
const plugin = this;
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
this.load_quarantine_ini();
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
this.register_hook('queue', 'quarantine');
|
|
11
|
+
this.register_hook('queue_outbound', 'quarantine');
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
exports.hook_init_master = function (next, server) {
|
|
@@ -19,9 +18,8 @@ exports.hook_init_master = function (next, server) {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
exports.load_quarantine_ini = function () {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
plugin.load_quarantine_ini();
|
|
21
|
+
this.cfg = this.config.get('quarantine.ini', () => {
|
|
22
|
+
this.load_quarantine_ini();
|
|
25
23
|
})
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -34,33 +32,26 @@ const zeroPad = exports.zeroPad = (n, digits) => {
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
exports.clean_tmp_directory = function (next) {
|
|
37
|
-
|
|
38
|
-
// NOTE: This is deliberately syncronous to ensure that this
|
|
39
|
-
// is completed prior to any messages being received.
|
|
40
|
-
const plugin = this;
|
|
41
|
-
const tmp_dir = path.join(plugin.get_base_dir(), 'tmp');
|
|
35
|
+
const tmp_dir = path.join(this.get_base_dir(), 'tmp');
|
|
42
36
|
|
|
43
37
|
if (fs.existsSync(tmp_dir)) {
|
|
44
38
|
const dirent = fs.readdirSync(tmp_dir);
|
|
45
|
-
|
|
46
|
-
for (
|
|
47
|
-
fs.unlinkSync(path.join(tmp_dir,
|
|
39
|
+
this.loginfo(`Removing temporary files from: ${tmp_dir}`);
|
|
40
|
+
for (const element of dirent) {
|
|
41
|
+
fs.unlinkSync(path.join(tmp_dir, element));
|
|
48
42
|
}
|
|
49
43
|
}
|
|
50
44
|
next();
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
function wants_quarantine (connection) {
|
|
54
|
-
|
|
55
|
-
return connection.notes.quarantine;
|
|
48
|
+
const { notes, transaction } = connection ?? {}
|
|
56
49
|
|
|
57
|
-
if (
|
|
58
|
-
return connection.transaction.notes.quarantine;
|
|
50
|
+
if (notes.quarantine) return notes.quarantine;
|
|
59
51
|
|
|
60
|
-
if (
|
|
61
|
-
return true;
|
|
52
|
+
if (transaction.notes.quarantine) return transaction.notes.quarantine;
|
|
62
53
|
|
|
63
|
-
return
|
|
54
|
+
return transaction.notes.get('queue.wants') === 'quarantine';
|
|
64
55
|
}
|
|
65
56
|
|
|
66
57
|
exports.get_base_dir = function () {
|
|
@@ -69,19 +60,17 @@ exports.get_base_dir = function () {
|
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
exports.init_quarantine_dir = function (done) {
|
|
72
|
-
const
|
|
73
|
-
const tmp_dir = path.join(plugin.get_base_dir(), 'tmp');
|
|
63
|
+
const tmp_dir = path.join(this.get_base_dir(), 'tmp');
|
|
74
64
|
fs.promises.mkdir(tmp_dir, { recursive: true })
|
|
75
|
-
.then(made =>
|
|
76
|
-
.catch(err =>
|
|
65
|
+
.then(made => this.loginfo(`created ${tmp_dir}`))
|
|
66
|
+
.catch(err => this.logerror(`Unable to create ${tmp_dir}`))
|
|
77
67
|
.finally(done);
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
exports.quarantine = function (next, connection) {
|
|
81
|
-
const plugin = this;
|
|
82
71
|
|
|
83
72
|
const quarantine = wants_quarantine(connection);
|
|
84
|
-
|
|
73
|
+
this.logdebug(`quarantine: ${quarantine}`);
|
|
85
74
|
if (!quarantine) return next();
|
|
86
75
|
|
|
87
76
|
// Calculate date in YYYYMMDD format
|
|
@@ -96,8 +85,10 @@ exports.quarantine = function (next, connection) {
|
|
|
96
85
|
subdir = path.join(quarantine, yyyymmdd);
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
const txn
|
|
100
|
-
|
|
88
|
+
const txn = connection?.transaction;
|
|
89
|
+
if (!txn) return next();
|
|
90
|
+
|
|
91
|
+
const base_dir = this.get_base_dir();
|
|
101
92
|
const msg_dir = path.join(base_dir, subdir);
|
|
102
93
|
const tmp_path = path.join(base_dir, 'tmp', txn.uuid);
|
|
103
94
|
const msg_path = path.join(msg_dir, txn.uuid);
|
|
@@ -109,25 +100,25 @@ exports.quarantine = function (next, connection) {
|
|
|
109
100
|
// final destination.
|
|
110
101
|
fs.promises.mkdir(msg_dir, { recursive: true })
|
|
111
102
|
.catch(err => {
|
|
112
|
-
connection.logerror(
|
|
103
|
+
connection.logerror(this, `Error creating directory: ${msg_dir}`);
|
|
113
104
|
next();
|
|
114
105
|
})
|
|
115
106
|
.then(ok => {
|
|
116
107
|
const ws = fs.createWriteStream(tmp_path);
|
|
117
108
|
|
|
118
109
|
ws.on('error', err => {
|
|
119
|
-
connection.logerror(
|
|
110
|
+
connection.logerror(this, `Error writing quarantine file: ${err.message}`);
|
|
120
111
|
return next();
|
|
121
112
|
});
|
|
122
113
|
ws.on('close', () => {
|
|
123
114
|
fs.link(tmp_path, msg_path, err => {
|
|
124
115
|
if (err) {
|
|
125
|
-
connection.logerror(
|
|
116
|
+
connection.logerror(this, `Error writing quarantine file: ${err}`);
|
|
126
117
|
}
|
|
127
118
|
else {
|
|
128
119
|
// Add a note to where we stored the message
|
|
129
120
|
txn.notes.quarantined = msg_path;
|
|
130
|
-
txn.results.add(
|
|
121
|
+
txn.results.add(this, { pass: msg_path, emit: true });
|
|
131
122
|
// Now delete the temporary file
|
|
132
123
|
fs.unlink(tmp_path, () => {});
|
|
133
124
|
}
|
|
@@ -20,6 +20,8 @@ exports.register = function () {
|
|
|
20
20
|
|
|
21
21
|
//Actual magic of publishing message to rabbit when email comes happen here.
|
|
22
22
|
exports.hook_queue = (next, connection) => {
|
|
23
|
+
if (!connection?.transaction) return next();
|
|
24
|
+
|
|
23
25
|
//Calling the get_data method and when it gets the data on callback, publish the message to queue with routing key.
|
|
24
26
|
connection.transaction.message_stream.get_data(buffere => {
|
|
25
27
|
const exchangeData = exports.exchangeMapping[exchangeName + queueName]
|
|
@@ -51,11 +53,10 @@ exports.hook_queue = (next, connection) => {
|
|
|
51
53
|
|
|
52
54
|
//This initializes the connection to rabbitmq server, It reads values from rabbitmq.ini file in config directory.
|
|
53
55
|
exports.init_rabbitmq_server = function () {
|
|
54
|
-
const plugin = this;
|
|
55
56
|
// this is called during init of rabbitmq
|
|
56
57
|
|
|
57
58
|
//Read the config file rabbitmq
|
|
58
|
-
const config =
|
|
59
|
+
const config = this.config.get('rabbitmq.ini');
|
|
59
60
|
//Just putting the defaults
|
|
60
61
|
const options = {};
|
|
61
62
|
let confirm = true;
|
|
@@ -11,20 +11,20 @@ exports.register = function () {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
exports.rabbitmq_queue = function (next, connection) {
|
|
14
|
-
|
|
14
|
+
if (!connection?.transaction) return next();
|
|
15
|
+
|
|
15
16
|
connection.transaction.message_stream.get_data(str => {
|
|
16
|
-
if (channel
|
|
17
|
+
if (channel?.sendToQueue(queue, str, {deliveryMode})) {
|
|
17
18
|
return next(OK);
|
|
18
19
|
}
|
|
19
20
|
else {
|
|
20
|
-
|
|
21
|
+
this.logerror("Failed to queue to rabbitmq");
|
|
21
22
|
return next();
|
|
22
23
|
}
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
exports.init_amqp_connection = function () {
|
|
27
|
-
const plugin = this;
|
|
28
28
|
const cfg = this.config.get("rabbitmq.ini").rabbitmq;
|
|
29
29
|
|
|
30
30
|
const protocol = cfg.protocol || "amqp";
|
|
@@ -43,30 +43,30 @@ exports.init_amqp_connection = function () {
|
|
|
43
43
|
|
|
44
44
|
amqp.connect(`${protocol}://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}${vhost}`, (err, conn) => {
|
|
45
45
|
if (err) {
|
|
46
|
-
|
|
46
|
+
this.logerror(`Connection to rabbitmq failed: ${err}`);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
// TODO: if !confirm conn.createChannel...
|
|
50
50
|
conn.createConfirmChannel((err2, ch) => {
|
|
51
51
|
if (err2) {
|
|
52
|
-
|
|
52
|
+
this.logerror(`Error creating rabbitmq channel: ${err2}`);
|
|
53
53
|
return conn.close();
|
|
54
54
|
}
|
|
55
55
|
ch.assertExchange(exchangeName, exchangeType, {durable}, (err3, ok) => {
|
|
56
56
|
if (err3) {
|
|
57
|
-
|
|
57
|
+
this.logerror(`Error asserting rabbitmq exchange: ${err3}`);
|
|
58
58
|
return conn.close();
|
|
59
59
|
}
|
|
60
60
|
ch.assertQueue(queueName,
|
|
61
61
|
{durable, autoDelete},
|
|
62
62
|
(err4, ok2) => {
|
|
63
63
|
if (err4) {
|
|
64
|
-
|
|
64
|
+
this.logerror(`Error asserting rabbitmq queue: ${err4}`);
|
|
65
65
|
return conn.close();
|
|
66
66
|
}
|
|
67
67
|
queue = ok2.queue;
|
|
68
68
|
channel = ch;
|
|
69
|
-
|
|
69
|
+
this.register_hook('queue', 'rabbitmq_queue');
|
|
70
70
|
}
|
|
71
71
|
);
|
|
72
72
|
});
|
|
@@ -6,14 +6,15 @@ exports.register = function () {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
exports.load_flat_ini = function () {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
plugin.load_flat_ini();
|
|
9
|
+
this.cfg = this.config.get('smtp_bridge.ini', () => {
|
|
10
|
+
this.load_flat_ini();
|
|
12
11
|
});
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
exports.hook_data_post = (next, connection) => {
|
|
16
|
-
const txn = connection
|
|
15
|
+
const txn = connection?.transaction;
|
|
16
|
+
if (!txn) return next();
|
|
17
|
+
|
|
17
18
|
// Copy auth notes to transaction notes so they're available in hmail.todo.notes
|
|
18
19
|
txn.notes.auth_user = connection.notes.auth_user;
|
|
19
20
|
txn.notes.auth_passwd = connection.notes.auth_passwd;
|
|
@@ -9,33 +9,35 @@ const url = require('url');
|
|
|
9
9
|
const smtp_client_mod = require('./smtp_client');
|
|
10
10
|
|
|
11
11
|
exports.register = function () {
|
|
12
|
-
|
|
13
|
-
plugin.load_errs = [];
|
|
12
|
+
this.load_errs = [];
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
this.load_smtp_forward_ini();
|
|
16
15
|
|
|
17
|
-
if (
|
|
16
|
+
if (this.load_errs.length > 0) return;
|
|
18
17
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
18
|
+
if (this.cfg.main.check_sender) {
|
|
19
|
+
this.register_hook('mail', 'check_sender');
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
22
|
+
if (this.cfg.main.check_recipient) {
|
|
23
|
+
this.register_hook('rcpt', 'check_recipient');
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
this.register_hook('queue', 'queue_forward');
|
|
27
|
+
|
|
28
|
+
if (this.cfg.main.enable_outbound) {
|
|
29
|
+
this.register_hook('queue_outbound', 'queue_forward');
|
|
30
|
+
}
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
this.register_hook('get_mx', 'get_mx'); // for relaying outbound messages
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
exports.load_smtp_forward_ini = function () {
|
|
33
|
-
const plugin = this;
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
this.cfg = this.config.get('smtp_forward.ini', {
|
|
36
38
|
booleans: [
|
|
37
39
|
'-main.enable_tls',
|
|
38
|
-
'
|
|
40
|
+
'-main.enable_outbound',
|
|
39
41
|
'main.one_message_per_rcpt',
|
|
40
42
|
'-main.check_sender',
|
|
41
43
|
'-main.check_recipient',
|
|
@@ -44,93 +46,86 @@ exports.load_smtp_forward_ini = function () {
|
|
|
44
46
|
],
|
|
45
47
|
},
|
|
46
48
|
() => {
|
|
47
|
-
|
|
49
|
+
this.load_smtp_forward_ini();
|
|
48
50
|
});
|
|
49
|
-
|
|
50
|
-
if (plugin.cfg.main.enable_outbound) {
|
|
51
|
-
plugin.lognotice('outbound enabled, will default to disabled in Haraka v3 (see #1472)');
|
|
52
|
-
}
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
exports.get_config = function (conn) {
|
|
56
|
-
const plugin = this;
|
|
57
54
|
|
|
58
|
-
if (!conn.transaction) return
|
|
55
|
+
if (!conn.transaction) return this.cfg.main;
|
|
59
56
|
|
|
60
57
|
let dom;
|
|
61
|
-
if (
|
|
62
|
-
if (!conn.transaction.mail_from) return
|
|
58
|
+
if (this.cfg.main.domain_selector === 'mail_from') {
|
|
59
|
+
if (!conn.transaction.mail_from) return this.cfg.main;
|
|
63
60
|
dom = conn.transaction.mail_from.host;
|
|
64
61
|
}
|
|
65
62
|
else {
|
|
66
|
-
if (!conn.transaction.rcpt_to[0]) return
|
|
63
|
+
if (!conn.transaction.rcpt_to[0]) return this.cfg.main;
|
|
67
64
|
dom = conn.transaction.rcpt_to[0].host;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
if (!dom) return
|
|
71
|
-
if (!
|
|
67
|
+
if (!dom) return this.cfg.main;
|
|
68
|
+
if (!this.cfg[dom]) return this.cfg.main; // no specific route
|
|
72
69
|
|
|
73
|
-
return
|
|
70
|
+
return this.cfg[dom];
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
exports.is_outbound_enabled = function (dom_cfg) {
|
|
77
|
-
const plugin = this;
|
|
78
74
|
|
|
79
75
|
if ('enable_outbound' in dom_cfg) return dom_cfg.enable_outbound; // per-domain flag
|
|
80
76
|
|
|
81
|
-
return
|
|
77
|
+
return this.cfg.main.enable_outbound; // follow the global configuration
|
|
82
78
|
};
|
|
83
79
|
|
|
84
80
|
exports.check_sender = function (next, connection, params) {
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
87
|
-
const txn = connection.transaction;
|
|
81
|
+
const txn = connection?.transaction;
|
|
82
|
+
if (!txn) return;
|
|
88
83
|
|
|
89
84
|
const email = params[0].address();
|
|
90
85
|
if (!email) {
|
|
91
|
-
txn.results.add(
|
|
86
|
+
txn.results.add(this, {skip: 'mail_from.null', emit: true});
|
|
92
87
|
return next();
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
const domain = params[0].host.toLowerCase();
|
|
96
|
-
if (!
|
|
91
|
+
if (!this.cfg[domain]) return next();
|
|
97
92
|
|
|
98
93
|
// domain is defined in smtp_forward.ini
|
|
99
94
|
txn.notes.local_sender = true;
|
|
100
95
|
|
|
101
96
|
if (!connection.relaying) {
|
|
102
|
-
txn.results.add(
|
|
97
|
+
txn.results.add(this, {fail: 'mail_from!spoof'});
|
|
103
98
|
return next(DENY, "Spoofed MAIL FROM");
|
|
104
99
|
}
|
|
105
100
|
|
|
106
|
-
txn.results.add(
|
|
101
|
+
txn.results.add(this, {pass: 'mail_from'});
|
|
107
102
|
return next();
|
|
108
103
|
}
|
|
109
104
|
|
|
110
105
|
exports.set_queue = function (connection, queue_wanted, domain) {
|
|
111
|
-
const plugin = this;
|
|
112
106
|
|
|
113
|
-
let dom_cfg =
|
|
107
|
+
let dom_cfg = this.cfg[domain];
|
|
114
108
|
if (dom_cfg === undefined) dom_cfg = {};
|
|
115
109
|
|
|
116
|
-
if (!queue_wanted) queue_wanted = dom_cfg.queue ||
|
|
110
|
+
if (!queue_wanted) queue_wanted = dom_cfg.queue || this.cfg.main.queue;
|
|
117
111
|
if (!queue_wanted) return true;
|
|
118
112
|
|
|
119
|
-
|
|
113
|
+
|
|
114
|
+
let dst_host = dom_cfg.host || this.cfg.main.host;
|
|
120
115
|
if (dst_host) dst_host = `smtp://${dst_host}`;
|
|
121
116
|
|
|
122
|
-
const notes = connection
|
|
117
|
+
const notes = connection?.transaction?.notes;
|
|
118
|
+
if (!notes) return false;
|
|
123
119
|
if (!notes.get('queue.wants')) {
|
|
124
120
|
notes.set('queue.wants', queue_wanted);
|
|
125
|
-
if (dst_host)
|
|
126
|
-
notes.set('queue.next_hop', dst_host);
|
|
127
|
-
}
|
|
121
|
+
if (dst_host) notes.set('queue.next_hop', dst_host);
|
|
128
122
|
return true;
|
|
129
123
|
}
|
|
130
124
|
|
|
131
125
|
// multiple recipients with same destination
|
|
132
126
|
if (notes.get('queue.wants') === queue_wanted) {
|
|
133
127
|
if (!dst_host) return true;
|
|
128
|
+
|
|
134
129
|
const next_hop = notes.get('queue.next_hop');
|
|
135
130
|
if (!next_hop) return true;
|
|
136
131
|
if (next_hop === dst_host) return true;
|
|
@@ -141,49 +136,47 @@ exports.set_queue = function (connection, queue_wanted, domain) {
|
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
exports.check_recipient = function (next, connection, params) {
|
|
144
|
-
const
|
|
145
|
-
const txn = connection.transaction;
|
|
139
|
+
const txn = connection?.transaction;
|
|
146
140
|
if (!txn) return;
|
|
147
141
|
|
|
148
142
|
const rcpt = params[0];
|
|
149
143
|
if (!rcpt.host) {
|
|
150
|
-
txn.results.add(
|
|
144
|
+
txn.results.add(this, {skip: 'rcpt!domain'});
|
|
151
145
|
return next();
|
|
152
146
|
}
|
|
153
147
|
|
|
154
148
|
if (connection.relaying && txn.notes.local_sender) {
|
|
155
|
-
|
|
156
|
-
txn.results.add(
|
|
149
|
+
this.set_queue(connection, 'outbound');
|
|
150
|
+
txn.results.add(this, {pass: 'relaying local_sender'});
|
|
157
151
|
return next(OK);
|
|
158
152
|
}
|
|
159
153
|
|
|
160
154
|
const domain = rcpt.host.toLowerCase();
|
|
161
|
-
if (
|
|
162
|
-
if (
|
|
163
|
-
txn.results.add(
|
|
155
|
+
if (this.cfg[domain] !== undefined) {
|
|
156
|
+
if (this.set_queue(connection, 'smtp_forward', domain)) {
|
|
157
|
+
txn.results.add(this, {pass: 'rcpt_to'});
|
|
164
158
|
return next(OK);
|
|
165
159
|
}
|
|
166
|
-
txn.results.add(
|
|
160
|
+
txn.results.add(this, {pass: 'rcpt_to.split'});
|
|
167
161
|
return next(DENYSOFT, "Split transaction, retry soon");
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
// the MAIL FROM domain is not local and neither is the RCPT TO
|
|
171
165
|
// Another RCPT plugin may vouch for this recipient.
|
|
172
|
-
txn.results.add(
|
|
166
|
+
txn.results.add(this, {msg: 'rcpt!local'});
|
|
173
167
|
return next();
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
exports.auth = function (cfg, connection, smtp_client) {
|
|
177
|
-
const plugin = this;
|
|
178
171
|
|
|
179
|
-
connection.loginfo(
|
|
172
|
+
connection.loginfo(this, `Configuring authentication for SMTP server ${cfg.host}:${cfg.port}`);
|
|
180
173
|
smtp_client.on('capabilities', () => {
|
|
181
|
-
connection.loginfo(
|
|
174
|
+
connection.loginfo(this, 'capabilities received');
|
|
182
175
|
|
|
183
176
|
if ('secured' in smtp_client) {
|
|
184
|
-
connection.loginfo(
|
|
177
|
+
connection.loginfo(this, 'secured is pending');
|
|
185
178
|
if (smtp_client.secured === false) {
|
|
186
|
-
connection.loginfo(
|
|
179
|
+
connection.loginfo(this, "Waiting for STARTTLS to complete. AUTH postponed");
|
|
187
180
|
return;
|
|
188
181
|
}
|
|
189
182
|
}
|
|
@@ -194,18 +187,19 @@ exports.auth = function (cfg, connection, smtp_client) {
|
|
|
194
187
|
}
|
|
195
188
|
|
|
196
189
|
if (cfg.auth_type === 'plain') {
|
|
197
|
-
connection.loginfo(
|
|
190
|
+
connection.loginfo(this, `Authenticating with AUTH PLAIN ${cfg.auth_user}`);
|
|
198
191
|
smtp_client.send_command('AUTH', `PLAIN ${base64(`\0${cfg.auth_user}\0${cfg.auth_pass}`)}`);
|
|
192
|
+
return
|
|
199
193
|
}
|
|
200
|
-
|
|
194
|
+
|
|
195
|
+
if (cfg.auth_type === 'login') {
|
|
201
196
|
smtp_client.authenticating = true;
|
|
202
197
|
smtp_client.authenticated = false;
|
|
203
198
|
|
|
204
|
-
connection.loginfo(
|
|
199
|
+
connection.loginfo(this, `Authenticating with AUTH LOGIN ${cfg.auth_user}`);
|
|
205
200
|
smtp_client.send_command('AUTH', 'LOGIN');
|
|
206
201
|
smtp_client.on('auth', () => {
|
|
207
|
-
//
|
|
208
|
-
|
|
202
|
+
// do nothing
|
|
209
203
|
});
|
|
210
204
|
smtp_client.on('auth_username', () => {
|
|
211
205
|
smtp_client.send_command(base64(cfg.auth_user));
|
|
@@ -218,16 +212,15 @@ exports.auth = function (cfg, connection, smtp_client) {
|
|
|
218
212
|
}
|
|
219
213
|
|
|
220
214
|
exports.forward_enabled = function (conn, dom_cfg) {
|
|
221
|
-
const plugin = this;
|
|
222
215
|
|
|
223
216
|
const q_wants = conn.transaction.notes.get('queue.wants');
|
|
224
217
|
if (q_wants && q_wants !== 'smtp_forward') {
|
|
225
|
-
conn.logdebug(
|
|
218
|
+
conn.logdebug(this, `skipping, unwanted (${q_wants})`);
|
|
226
219
|
return false;
|
|
227
220
|
}
|
|
228
221
|
|
|
229
|
-
if (conn.relaying && !
|
|
230
|
-
conn.logdebug(
|
|
222
|
+
if (conn.relaying && !this.is_outbound_enabled(dom_cfg)) {
|
|
223
|
+
conn.logdebug(this, 'skipping, outbound disabled');
|
|
231
224
|
return false;
|
|
232
225
|
}
|
|
233
226
|
|
|
@@ -236,30 +229,29 @@ exports.forward_enabled = function (conn, dom_cfg) {
|
|
|
236
229
|
|
|
237
230
|
exports.queue_forward = function (next, connection) {
|
|
238
231
|
const plugin = this;
|
|
239
|
-
const
|
|
240
|
-
const txn = connection.transaction;
|
|
232
|
+
const txn = connection?.transaction;
|
|
241
233
|
|
|
242
|
-
const cfg = plugin.get_config(
|
|
243
|
-
if (!plugin.forward_enabled(
|
|
234
|
+
const cfg = plugin.get_config(connection);
|
|
235
|
+
if (!plugin.forward_enabled(connection, cfg)) return next();
|
|
244
236
|
|
|
245
|
-
smtp_client_mod.get_client_plugin(plugin,
|
|
237
|
+
smtp_client_mod.get_client_plugin(plugin, connection, cfg, (err, smtp_client) => {
|
|
246
238
|
smtp_client.next = next;
|
|
247
239
|
|
|
248
240
|
let rcpt = 0;
|
|
249
241
|
|
|
250
|
-
if (cfg.auth_user) plugin.auth(cfg,
|
|
242
|
+
if (cfg.auth_user) plugin.auth(cfg, connection, smtp_client);
|
|
251
243
|
|
|
252
|
-
|
|
244
|
+
connection.loginfo(plugin, `forwarding to ${
|
|
253
245
|
cfg.forwarding_host_pool ? 'host_pool' : `${cfg.host}:${cfg.port}`}`
|
|
254
246
|
);
|
|
255
247
|
|
|
256
248
|
function get_rs () {
|
|
257
|
-
if (
|
|
258
|
-
return
|
|
249
|
+
if (txn) return txn.results;
|
|
250
|
+
return connection.results;
|
|
259
251
|
}
|
|
260
252
|
|
|
261
253
|
function dead_sender () {
|
|
262
|
-
if (smtp_client.is_dead_sender(plugin,
|
|
254
|
+
if (smtp_client.is_dead_sender(plugin, connection)) {
|
|
263
255
|
get_rs().add(plugin, { err: 'dead sender' });
|
|
264
256
|
return true;
|
|
265
257
|
}
|
|
@@ -267,7 +259,7 @@ exports.queue_forward = function (next, connection) {
|
|
|
267
259
|
}
|
|
268
260
|
|
|
269
261
|
function send_rcpt () {
|
|
270
|
-
if (dead_sender()) return;
|
|
262
|
+
if (dead_sender() || !txn) return;
|
|
271
263
|
if (rcpt === txn.rcpt_to.length) {
|
|
272
264
|
smtp_client.send_command('DATA');
|
|
273
265
|
return;
|
|
@@ -293,7 +285,8 @@ exports.queue_forward = function (next, connection) {
|
|
|
293
285
|
});
|
|
294
286
|
|
|
295
287
|
smtp_client.on('dot', () => {
|
|
296
|
-
if (dead_sender()) return;
|
|
288
|
+
if (dead_sender() || !txn) return;
|
|
289
|
+
|
|
297
290
|
get_rs().add(plugin, { pass: smtp_client.response });
|
|
298
291
|
if (rcpt < txn.rcpt_to.length) {
|
|
299
292
|
smtp_client.send_command('RSET');
|
|
@@ -304,12 +297,12 @@ exports.queue_forward = function (next, connection) {
|
|
|
304
297
|
});
|
|
305
298
|
|
|
306
299
|
smtp_client.on('rset', () => {
|
|
307
|
-
if (dead_sender()) return;
|
|
300
|
+
if (dead_sender() || !txn) return;
|
|
308
301
|
smtp_client.send_command('MAIL', `FROM:${txn.mail_from}`);
|
|
309
302
|
});
|
|
310
303
|
|
|
311
304
|
smtp_client.on('bad_code', (code, msg) => {
|
|
312
|
-
if (dead_sender()) return;
|
|
305
|
+
if (dead_sender() || !txn) return;
|
|
313
306
|
smtp_client.call_next(((code && code[0] === '5') ? DENY : DENYSOFT),
|
|
314
307
|
msg);
|
|
315
308
|
smtp_client.release();
|
|
@@ -333,18 +326,17 @@ exports.get_mx_next_hop = next_hop => {
|
|
|
333
326
|
}
|
|
334
327
|
|
|
335
328
|
exports.get_mx = function (next, hmail, domain) {
|
|
336
|
-
const plugin = this;
|
|
337
329
|
|
|
338
330
|
// hmail.todo not defined in tests.
|
|
339
331
|
if (hmail.todo.notes.next_hop) {
|
|
340
|
-
return next(OK,
|
|
332
|
+
return next(OK, this.get_mx_next_hop(hmail.todo.notes.next_hop));
|
|
341
333
|
}
|
|
342
334
|
|
|
343
|
-
const dom =
|
|
344
|
-
const cfg =
|
|
335
|
+
const dom = this.cfg.main.domain_selector === 'mail_from' ? hmail.todo.mail_from.host.toLowerCase() : domain.toLowerCase();
|
|
336
|
+
const cfg = this.cfg[dom];
|
|
345
337
|
|
|
346
338
|
if (cfg === undefined) {
|
|
347
|
-
|
|
339
|
+
this.logdebug(`using DNS MX for: ${domain}`);
|
|
348
340
|
return next();
|
|
349
341
|
}
|
|
350
342
|
|
|
@@ -355,14 +347,14 @@ exports.get_mx = function (next, hmail, domain) {
|
|
|
355
347
|
|
|
356
348
|
const mx = {
|
|
357
349
|
priority: 0,
|
|
358
|
-
exchange: cfg.host ||
|
|
359
|
-
port: cfg.port ||
|
|
350
|
+
exchange: cfg.host || this.cfg.main.host,
|
|
351
|
+
port: cfg.port || this.cfg.main.port || 25,
|
|
360
352
|
}
|
|
361
353
|
|
|
362
354
|
// apply auth/mx options
|
|
363
355
|
mx_opts.forEach(o => {
|
|
364
356
|
if (cfg[o] === undefined) return;
|
|
365
|
-
mx[o] =
|
|
357
|
+
mx[o] = this.cfg[dom][o];
|
|
366
358
|
})
|
|
367
359
|
|
|
368
360
|
return next(OK, mx);
|