Haraka 2.8.28 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.yaml +2 -10
- package/Changes.md +68 -2
- package/Dockerfile +1 -1
- package/Plugins.md +7 -4
- package/README.md +2 -6
- 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 +65 -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 +42 -40
- 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/outbound/mx_lookup.js
CHANGED
|
@@ -24,7 +24,7 @@ exports.lookup_mx = function lookup_mx (domain, cb) {
|
|
|
24
24
|
|
|
25
25
|
// default wrap_mx just returns our object with "priority" and "exchange" keys
|
|
26
26
|
let wrap_mx = a => a;
|
|
27
|
-
function process_dns (err, addresses) {
|
|
27
|
+
async function process_dns (err, addresses) {
|
|
28
28
|
if (err) {
|
|
29
29
|
if (err.code === 'ENODATA' || err.code === 'ENOTFOUND') {
|
|
30
30
|
// Most likely this is a hostname with no MX record
|
|
@@ -33,9 +33,12 @@ exports.lookup_mx = function lookup_mx (domain, cb) {
|
|
|
33
33
|
}
|
|
34
34
|
cb(err);
|
|
35
35
|
}
|
|
36
|
-
else if (addresses
|
|
36
|
+
else if (addresses?.length) {
|
|
37
37
|
for (let i=0,l=addresses.length; i < l; i++) {
|
|
38
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
obc.cfg.local_mx_ok ||
|
|
40
|
+
await net_utils.is_local_host(addresses[i].exchange).catch(() => null) === false
|
|
41
|
+
) {
|
|
39
42
|
const mx = wrap_mx(addresses[i]);
|
|
40
43
|
mxs.push(mx);
|
|
41
44
|
}
|
|
@@ -49,15 +52,15 @@ exports.lookup_mx = function lookup_mx (domain, cb) {
|
|
|
49
52
|
return 1;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
net_utils.get_mx(domain, (err, addresses) => {
|
|
53
|
-
if (process_dns(err, addresses)) return;
|
|
55
|
+
net_utils.get_mx(domain, async (err, addresses) => {
|
|
56
|
+
if (await process_dns(err, addresses)) return;
|
|
54
57
|
|
|
55
58
|
// if MX lookup failed, we lookup an A record. To do that we change
|
|
56
59
|
// wrap_mx() to return same thing as resolveMx() does.
|
|
57
60
|
wrap_mx = a => ({priority:0,exchange:a});
|
|
58
61
|
// IS: IPv6 compatible
|
|
59
|
-
dns.resolve(domain, (err2, addresses2) => {
|
|
60
|
-
if (process_dns(err2, addresses2)) return;
|
|
62
|
+
dns.resolve(domain, async (err2, addresses2) => {
|
|
63
|
+
if (await process_dns(err2, addresses2)) return;
|
|
61
64
|
|
|
62
65
|
err2 = new Error("Found nowhere to deliver to");
|
|
63
66
|
err2.code = 'NOMX';
|
package/outbound/queue.js
CHANGED
|
@@ -4,7 +4,7 @@ const async = require('async');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
-
const Address = require('address-rfc2821')
|
|
7
|
+
const { Address } = require('address-rfc2821');
|
|
8
8
|
const config = require('haraka-config');
|
|
9
9
|
|
|
10
10
|
const logger = require('../logger');
|
|
@@ -135,26 +135,24 @@ exports._add_file = (file, cb) => {
|
|
|
135
135
|
const parts = _qfile.parts(file);
|
|
136
136
|
|
|
137
137
|
if (parts.next_attempt <= self.cur_time) {
|
|
138
|
-
logger.logdebug(
|
|
138
|
+
logger.logdebug(`[outbound] File ${file} needs processing now`);
|
|
139
139
|
load_queue.push(file);
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
142
|
-
logger.logdebug(`[outbound] File needs processing later: ${parts.next_attempt - self.cur_time}ms`);
|
|
142
|
+
logger.logdebug(`[outbound] File ${file} needs processing later: ${parts.next_attempt - self.cur_time}ms`);
|
|
143
143
|
temp_fail_queue.add(file, parts.next_attempt - self.cur_time, () => { load_queue.push(file);});
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
cb();
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
exports.load_queue_files = (pid, input_files, iteratee, callback) => {
|
|
149
|
+
exports.load_queue_files = (pid, input_files, iteratee, callback = function () {}) => {
|
|
150
150
|
const self = exports;
|
|
151
151
|
const searchPid = parseInt(pid);
|
|
152
152
|
|
|
153
153
|
let stat_renamed = 0;
|
|
154
154
|
let stat_loaded = 0;
|
|
155
155
|
|
|
156
|
-
callback = callback || function () {};
|
|
157
|
-
|
|
158
156
|
if (searchPid) {
|
|
159
157
|
logger.loginfo(`[outbound] Grabbing queue files for pid: ${pid}`);
|
|
160
158
|
}
|
|
@@ -195,13 +193,11 @@ exports.load_queue_files = (pid, input_files, iteratee, callback) => {
|
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
exports.stats = () => {
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
|
|
197
|
+
return {
|
|
200
198
|
queue_dir,
|
|
201
199
|
queue_count,
|
|
202
200
|
};
|
|
203
|
-
|
|
204
|
-
return results;
|
|
205
201
|
}
|
|
206
202
|
|
|
207
203
|
exports._list_file = (file, cb) => {
|
|
@@ -226,7 +222,7 @@ exports._list_file = (file, cb) => {
|
|
|
226
222
|
todo_struct.file = file;
|
|
227
223
|
todo_struct.full_path = path.join(queue_dir, file);
|
|
228
224
|
const parts = _qfile.parts(file);
|
|
229
|
-
todo_struct.pid = (parts
|
|
225
|
+
todo_struct.pid = (parts?.pid) || null;
|
|
230
226
|
cb(null, todo_struct);
|
|
231
227
|
}
|
|
232
228
|
});
|
|
@@ -262,7 +258,7 @@ exports.load_pid_queue = pid => {
|
|
|
262
258
|
}
|
|
263
259
|
|
|
264
260
|
exports.ensure_queue_dir = () => {
|
|
265
|
-
// No reason
|
|
261
|
+
// No reason to do this asynchronously
|
|
266
262
|
// this code is only run at start-up.
|
|
267
263
|
if (fs.existsSync(queue_dir)) return;
|
|
268
264
|
|
package/outbound/timer_queue.js
CHANGED
|
@@ -18,11 +18,9 @@ class TQTimer {
|
|
|
18
18
|
|
|
19
19
|
class TimerQueue {
|
|
20
20
|
|
|
21
|
-
constructor (interval) {
|
|
22
|
-
const self = this;
|
|
23
|
-
interval = interval || 1000;
|
|
21
|
+
constructor (interval = 1000) {
|
|
24
22
|
this.queue = [];
|
|
25
|
-
this.interval_timer = setInterval(() => {
|
|
23
|
+
this.interval_timer = setInterval(() => { this.fire(); }, interval);
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
add (id, ms, cb) {
|
package/outbound/tls.js
CHANGED
|
@@ -74,33 +74,32 @@ class OutboundTLS {
|
|
|
74
74
|
|
|
75
75
|
// Check for if host is prohibited from TLS negotiation
|
|
76
76
|
check_tls_nogo (host, cb_ok, cb_nogo) {
|
|
77
|
-
|
|
78
|
-
if (!obtls.cfg.redis.disable_for_failed_hosts) return cb_ok();
|
|
77
|
+
if (!this.cfg.redis.disable_for_failed_hosts) return cb_ok();
|
|
79
78
|
|
|
80
79
|
const dbkey = `no_tls|${host}`;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
this.db.get(dbkey)
|
|
81
|
+
.then(dbr => {
|
|
82
|
+
dbr ? cb_nogo(dbr) : cb_ok();
|
|
83
|
+
})
|
|
84
|
+
.catch(err => {
|
|
85
|
+
this.logdebug(this, `Redis returned error: ${err}`);
|
|
86
|
+
cb_ok();
|
|
87
|
+
})
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
mark_tls_nogo (host, cb) {
|
|
92
|
-
const obtls = this;
|
|
93
91
|
const dbkey = `no_tls|${host}`;
|
|
94
|
-
const expiry =
|
|
92
|
+
const expiry = this.cfg.redis.disable_expiry || 604800;
|
|
95
93
|
|
|
96
|
-
if (!
|
|
94
|
+
if (!this.cfg.redis.disable_for_failed_hosts) return cb();
|
|
97
95
|
|
|
98
|
-
logger.lognotice(
|
|
96
|
+
logger.lognotice(this, `TLS connection failed. Marking ${host} as non-TLS for ${expiry} seconds`);
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
this.db.setex(dbkey, expiry, (new Date()).toISOString())
|
|
99
|
+
.then(cb)
|
|
100
|
+
.catch(err => {
|
|
101
|
+
logger.logerror(this, `Redis returned error: ${err}`);
|
|
102
|
+
})
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
|
package/outbound/todo.js
CHANGED
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"server",
|
|
10
10
|
"email"
|
|
11
11
|
],
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "3.0.0",
|
|
13
13
|
"homepage": "http://haraka.github.io",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -17,64 +17,68 @@
|
|
|
17
17
|
},
|
|
18
18
|
"main": "haraka.js",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=16"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"address-rfc2821" : "^2.0.1",
|
|
24
24
|
"address-rfc2822" : "^2.1.0",
|
|
25
|
-
"async" : "
|
|
25
|
+
"async" : "^3.2.4",
|
|
26
26
|
"daemon" : "~1.1.0",
|
|
27
|
-
"generic-pool" : "~2.5.4",
|
|
28
|
-
"iconv" : "~3.0.1",
|
|
29
27
|
"ipaddr.js" : "~2.0.1",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"nopt" : "~5.0.0",
|
|
28
|
+
"node-gyp" : "^9.3.1",
|
|
29
|
+
"nopt" : "~7.0.0",
|
|
33
30
|
"npid" : "~0.4.0",
|
|
34
|
-
"semver" : "~7.3.
|
|
31
|
+
"semver" : "~7.3.8",
|
|
35
32
|
"sprintf-js" : "~1.1.2",
|
|
36
|
-
"haraka-config" : "^1.0
|
|
37
|
-
"haraka-constants" : "^1.0.
|
|
38
|
-
"haraka-dsn" : "^1.0.
|
|
39
|
-
"haraka-
|
|
40
|
-
"haraka-
|
|
41
|
-
"haraka-
|
|
42
|
-
"haraka-
|
|
43
|
-
"haraka-
|
|
44
|
-
"haraka-
|
|
33
|
+
"haraka-config" : "^1.1.0",
|
|
34
|
+
"haraka-constants" : "^1.0.6",
|
|
35
|
+
"haraka-dsn" : "^1.0.4",
|
|
36
|
+
"haraka-email-message" : "^1.2.0",
|
|
37
|
+
"haraka-message-stream" : "^1.2.0",
|
|
38
|
+
"haraka-net-utils" : "^1.5.0",
|
|
39
|
+
"haraka-notes" : "^1.0.6",
|
|
40
|
+
"haraka-plugin-attachment": "^1.0.7",
|
|
41
|
+
"haraka-plugin-spf" : "1.1.3",
|
|
42
|
+
"haraka-plugin-redis" : "^2.0.5",
|
|
43
|
+
"haraka-results" : "^2.2.2",
|
|
44
|
+
"haraka-tld" : "^1.1.0",
|
|
45
|
+
"haraka-utils" : "^1.0.3",
|
|
45
46
|
"openssl-wrapper" : "^0.3.4",
|
|
46
47
|
"sockaddr" : "^1.0.1"
|
|
47
48
|
},
|
|
48
49
|
"optionalDependencies": {
|
|
49
|
-
"haraka-plugin-access" : "^1.1.
|
|
50
|
-
"haraka-plugin-
|
|
50
|
+
"haraka-plugin-access" : "^1.1.5",
|
|
51
|
+
"haraka-plugin-aliases" : "^1.0.1",
|
|
52
|
+
"haraka-plugin-asn" : "^2.0.1",
|
|
51
53
|
"haraka-plugin-auth-ldap": "^1.0.2",
|
|
52
54
|
"haraka-plugin-dcc" : "^1.0.1",
|
|
53
55
|
"haraka-plugin-elasticsearch" : "^1.0.6",
|
|
54
|
-
"haraka-plugin-fcrdns" : "^1.0
|
|
55
|
-
"haraka-plugin-graph" : "^1.0.
|
|
56
|
-
"haraka-plugin-geoip" : "^1.0.
|
|
57
|
-
"haraka-plugin-headers" : "^1.0.
|
|
58
|
-
"haraka-plugin-karma" : "^1.0
|
|
59
|
-
"haraka-plugin-limit" : "^1.0
|
|
60
|
-
"haraka-plugin-p0f" : "^1.0.
|
|
61
|
-
"haraka-plugin-qmail-deliverable" : "^1.
|
|
56
|
+
"haraka-plugin-fcrdns" : "^1.1.0",
|
|
57
|
+
"haraka-plugin-graph" : "^1.0.5",
|
|
58
|
+
"haraka-plugin-geoip" : "^1.0.17",
|
|
59
|
+
"haraka-plugin-headers" : "^1.0.3",
|
|
60
|
+
"haraka-plugin-karma" : "^2.1.0",
|
|
61
|
+
"haraka-plugin-limit" : "^1.1.0",
|
|
62
|
+
"haraka-plugin-p0f" : "^1.0.9",
|
|
63
|
+
"haraka-plugin-qmail-deliverable" : "^1.1.1",
|
|
64
|
+
"haraka-plugin-known-senders": "^1.0.8",
|
|
62
65
|
"haraka-plugin-rcpt-ldap": "^1.0.0",
|
|
63
|
-
"haraka-plugin-recipient-routes" : "^1.0.
|
|
64
|
-
"haraka-plugin-rspamd" : "^1.
|
|
66
|
+
"haraka-plugin-recipient-routes" : "^1.0.4",
|
|
67
|
+
"haraka-plugin-rspamd" : "^1.2.0",
|
|
65
68
|
"haraka-plugin-syslog" : "^1.0.3",
|
|
66
|
-
"haraka-plugin-
|
|
69
|
+
"haraka-plugin-uribl" : "^1.0.6",
|
|
70
|
+
"haraka-plugin-watch" : "^2.0.2",
|
|
67
71
|
"ocsp" : "~1.2.0",
|
|
68
|
-
"redis" : "~
|
|
72
|
+
"redis" : "~4.5.1",
|
|
69
73
|
"tmp" : "~0.2.1"
|
|
70
74
|
},
|
|
71
75
|
"devDependencies": {
|
|
72
76
|
"nodeunit-x" : "^0.15.0",
|
|
73
|
-
"haraka-test-fixtures" : "^1.
|
|
77
|
+
"haraka-test-fixtures" : "^1.2.1",
|
|
74
78
|
"mock-require" : "^3.0.3",
|
|
75
|
-
"eslint" : "^8.0",
|
|
76
|
-
"eslint-plugin-haraka" : "^1.0.
|
|
77
|
-
"nodemailer" : "6.7.
|
|
79
|
+
"eslint" : "^8.27.0",
|
|
80
|
+
"eslint-plugin-haraka" : "^1.0.15",
|
|
81
|
+
"nodemailer" : "^6.7.7"
|
|
78
82
|
},
|
|
79
83
|
"bugs": {
|
|
80
84
|
"mail": "haraka.mail@gmail.com",
|
|
@@ -82,15 +86,13 @@
|
|
|
82
86
|
},
|
|
83
87
|
"bin": {
|
|
84
88
|
"haraka": "./bin/haraka",
|
|
85
|
-
"spf": "./bin/spf",
|
|
86
89
|
"dkimverify": "./bin/dkimverify",
|
|
87
90
|
"haraka_grep": "./bin/haraka_grep"
|
|
88
91
|
},
|
|
89
92
|
"scripts": {
|
|
90
93
|
"test": "node run_tests",
|
|
91
|
-
"lint": "npx eslint *.js outbound
|
|
92
|
-
"lintfix": "npx eslint --fix *.js outbound
|
|
93
|
-
"cover": "NODE_ENV=cov npx nyc --reporter=lcovonly npm run test",
|
|
94
|
+
"lint": "npx eslint *.js outbound plugins plugins/*/*.js tests tests/*/*.js tests/*/*/*.js bin/haraka bin/dkimverify",
|
|
95
|
+
"lintfix": "npx eslint --fix *.js outbound plugins plugins/*/*.js tests tests/*/*.js tests/*/*/*.js bin/haraka bin/dkimverify",
|
|
94
96
|
"versions": "npx dependency-version-checker check"
|
|
95
97
|
}
|
|
96
98
|
}
|
|
@@ -27,31 +27,29 @@ exports.hook_capabilities = (next, connection) => {
|
|
|
27
27
|
exports.get_plain_passwd = (user, connection, cb) => cb()
|
|
28
28
|
|
|
29
29
|
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
30
|
-
const plugin = this;
|
|
31
30
|
if (params[0].toUpperCase() === AUTH_COMMAND && params[1]) {
|
|
32
|
-
return
|
|
33
|
-
params.slice(1).join(' '));
|
|
31
|
+
return this.select_auth_method(next, connection, params.slice(1).join(' '));
|
|
34
32
|
}
|
|
35
|
-
if (!connection.notes.authenticating)
|
|
33
|
+
if (!connection.notes.authenticating) return next();
|
|
36
34
|
|
|
37
35
|
const am = connection.notes.auth_method;
|
|
38
36
|
if (am === AUTH_METHOD_CRAM_MD5 && connection.notes.auth_ticket) {
|
|
39
|
-
return
|
|
37
|
+
return this.auth_cram_md5(next, connection, params);
|
|
40
38
|
}
|
|
41
39
|
if (am === AUTH_METHOD_LOGIN) {
|
|
42
|
-
return
|
|
40
|
+
return this.auth_login(next, connection, params);
|
|
43
41
|
}
|
|
44
42
|
if (am === AUTH_METHOD_PLAIN) {
|
|
45
|
-
return
|
|
43
|
+
return this.auth_plain(next, connection, params);
|
|
46
44
|
}
|
|
47
|
-
|
|
45
|
+
next();
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
51
49
|
function callback (plain_pw) {
|
|
52
|
-
if (plain_pw === null )
|
|
53
|
-
if (plain_pw !== passwd)
|
|
54
|
-
|
|
50
|
+
if (plain_pw === null ) return cb(false);
|
|
51
|
+
if (plain_pw !== passwd) return cb(false);
|
|
52
|
+
cb(true);
|
|
55
53
|
}
|
|
56
54
|
if (this.get_plain_passwd.length == 2) {
|
|
57
55
|
this.get_plain_passwd(user, callback);
|
|
@@ -66,16 +64,13 @@ exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
|
66
64
|
|
|
67
65
|
exports.check_cram_md5_passwd = function (connection, user, passwd, cb) {
|
|
68
66
|
function callback (plain_pw) {
|
|
69
|
-
if (plain_pw == null)
|
|
70
|
-
return cb(false);
|
|
71
|
-
}
|
|
67
|
+
if (plain_pw == null) return cb(false);
|
|
72
68
|
|
|
73
69
|
const hmac = crypto.createHmac('md5', plain_pw.toString());
|
|
74
70
|
hmac.update(connection.notes.auth_ticket);
|
|
75
71
|
|
|
76
|
-
if (hmac.digest('hex') === passwd)
|
|
77
|
-
|
|
78
|
-
}
|
|
72
|
+
if (hmac.digest('hex') === passwd) return cb(true);
|
|
73
|
+
|
|
79
74
|
return cb(false);
|
|
80
75
|
}
|
|
81
76
|
if (this.get_plain_passwd.length == 2) {
|
|
@@ -127,9 +122,8 @@ exports.check_user = function (next, connection, credentials, method) {
|
|
|
127
122
|
return;
|
|
128
123
|
}
|
|
129
124
|
|
|
130
|
-
if (!connection.notes.auth_fails)
|
|
131
|
-
|
|
132
|
-
}
|
|
125
|
+
if (!connection.notes.auth_fails) connection.notes.auth_fails = 0;
|
|
126
|
+
|
|
133
127
|
connection.notes.auth_fails++;
|
|
134
128
|
connection.results.add({name: 'auth'}, {
|
|
135
129
|
fail:`${plugin.name}/${method}`,
|
|
@@ -150,12 +144,10 @@ exports.check_user = function (next, connection, credentials, method) {
|
|
|
150
144
|
}
|
|
151
145
|
|
|
152
146
|
if (method === AUTH_METHOD_PLAIN || method === AUTH_METHOD_LOGIN) {
|
|
153
|
-
plugin.check_plain_passwd(connection, credentials[0], credentials[1],
|
|
154
|
-
passwd_ok);
|
|
147
|
+
plugin.check_plain_passwd(connection, credentials[0], credentials[1], passwd_ok);
|
|
155
148
|
}
|
|
156
149
|
else if (method === AUTH_METHOD_CRAM_MD5) {
|
|
157
|
-
plugin.check_cram_md5_passwd(connection, credentials[0], credentials[1],
|
|
158
|
-
passwd_ok);
|
|
150
|
+
plugin.check_cram_md5_passwd(connection, credentials[0], credentials[1], passwd_ok);
|
|
159
151
|
}
|
|
160
152
|
}
|
|
161
153
|
|
|
@@ -163,28 +155,19 @@ exports.select_auth_method = function (next, connection, method) {
|
|
|
163
155
|
const split = method.split(/\s+/);
|
|
164
156
|
method = split.shift().toUpperCase();
|
|
165
157
|
if (!connection.notes.allowed_auth_methods) return next();
|
|
166
|
-
if (!connection.notes.allowed_auth_methods.includes(method))
|
|
167
|
-
return next();
|
|
168
|
-
}
|
|
158
|
+
if (!connection.notes.allowed_auth_methods.includes(method)) return next();
|
|
169
159
|
|
|
170
160
|
if (connection.notes.authenticating) return next(DENYDISCONNECT, 'bad protocol');
|
|
171
161
|
|
|
172
162
|
connection.notes.authenticating = true;
|
|
173
163
|
connection.notes.auth_method = method;
|
|
174
164
|
|
|
175
|
-
if (method === AUTH_METHOD_PLAIN)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (method === AUTH_METHOD_LOGIN) {
|
|
179
|
-
return this.auth_login(next, connection, split);
|
|
180
|
-
}
|
|
181
|
-
if (method === AUTH_METHOD_CRAM_MD5) {
|
|
182
|
-
return this.auth_cram_md5(next, connection);
|
|
183
|
-
}
|
|
165
|
+
if (method === AUTH_METHOD_PLAIN) return this.auth_plain(next, connection, split);
|
|
166
|
+
if (method === AUTH_METHOD_LOGIN) return this.auth_login(next, connection, split);
|
|
167
|
+
if (method === AUTH_METHOD_CRAM_MD5) return this.auth_cram_md5(next, connection);
|
|
184
168
|
}
|
|
185
169
|
|
|
186
170
|
exports.auth_plain = function (next, connection, params) {
|
|
187
|
-
const plugin = this;
|
|
188
171
|
// one parameter given on line, either:
|
|
189
172
|
// AUTH PLAIN <param> or
|
|
190
173
|
// AUTH PLAIN\n
|
|
@@ -193,36 +176,32 @@ exports.auth_plain = function (next, connection, params) {
|
|
|
193
176
|
if (params[0]) {
|
|
194
177
|
const credentials = utils.unbase64(params[0]).split(/\0/);
|
|
195
178
|
credentials.shift(); // Discard authid
|
|
196
|
-
|
|
179
|
+
this.check_user(next, connection, credentials, AUTH_METHOD_PLAIN);
|
|
180
|
+
return
|
|
197
181
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
connection.respond(334, ' ', () => {
|
|
204
|
-
connection.notes.auth_plain_asked_login = true;
|
|
205
|
-
return next(OK);
|
|
206
|
-
});
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
182
|
+
|
|
183
|
+
if (connection.notes.auth_plain_asked_login) {
|
|
184
|
+
return next(DENYDISCONNECT, 'bad protocol');
|
|
209
185
|
}
|
|
186
|
+
|
|
187
|
+
connection.respond(334, ' ', () => {
|
|
188
|
+
connection.notes.auth_plain_asked_login = true;
|
|
189
|
+
next(OK);
|
|
190
|
+
});
|
|
210
191
|
}
|
|
211
192
|
|
|
212
193
|
exports.auth_login = function (next, connection, params) {
|
|
213
|
-
const plugin = this;
|
|
214
194
|
if ((!connection.notes.auth_login_asked_login && params[0]) ||
|
|
215
195
|
( connection.notes.auth_login_asked_login &&
|
|
216
196
|
!connection.notes.auth_login_userlogin)) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
197
|
+
|
|
198
|
+
if (!params[0]) return next(DENYDISCONNECT, 'bad protocol');
|
|
220
199
|
|
|
221
200
|
const login = utils.unbase64(params[0]);
|
|
222
201
|
connection.respond(334, LOGIN_STRING2, () => {
|
|
223
202
|
connection.notes.auth_login_userlogin = login;
|
|
224
203
|
connection.notes.auth_login_asked_login = true;
|
|
225
|
-
|
|
204
|
+
next(OK);
|
|
226
205
|
});
|
|
227
206
|
return;
|
|
228
207
|
}
|
|
@@ -236,30 +215,27 @@ exports.auth_login = function (next, connection, params) {
|
|
|
236
215
|
connection.notes.auth_login_userlogin = null;
|
|
237
216
|
connection.notes.auth_login_asked_login = false;
|
|
238
217
|
|
|
239
|
-
return
|
|
240
|
-
AUTH_METHOD_LOGIN);
|
|
218
|
+
return this.check_user(next, connection, credentials, AUTH_METHOD_LOGIN);
|
|
241
219
|
}
|
|
242
220
|
|
|
243
221
|
connection.respond(334, LOGIN_STRING1, () => {
|
|
244
222
|
connection.notes.auth_login_asked_login = true;
|
|
245
|
-
|
|
223
|
+
next(OK);
|
|
246
224
|
});
|
|
247
225
|
}
|
|
248
226
|
|
|
249
227
|
exports.auth_cram_md5 = function (next, connection, params) {
|
|
250
|
-
const plugin = this;
|
|
251
228
|
if (params) {
|
|
252
229
|
const credentials = utils.unbase64(params[0]).split(' ');
|
|
253
|
-
return
|
|
254
|
-
AUTH_METHOD_CRAM_MD5);
|
|
230
|
+
return this.check_user(next, connection, credentials, AUTH_METHOD_CRAM_MD5);
|
|
255
231
|
}
|
|
256
232
|
|
|
257
|
-
const ticket = `<${
|
|
233
|
+
const ticket = `<${this.hexi(Math.floor(Math.random() * 1000000))}. ${this.hexi(Date.now())}@${connection.local.host}>`;
|
|
258
234
|
|
|
259
|
-
connection.loginfo(
|
|
235
|
+
connection.loginfo(this, `ticket: ${ticket}`);
|
|
260
236
|
connection.respond(334, utils.base64(ticket), () => {
|
|
261
237
|
connection.notes.auth_ticket = ticket;
|
|
262
|
-
|
|
238
|
+
next(OK);
|
|
263
239
|
});
|
|
264
240
|
}
|
|
265
241
|
|
|
@@ -6,14 +6,13 @@ 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.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
16
|
-
let host = this.cfg.main
|
|
15
|
+
let { host } = this.cfg.main;
|
|
17
16
|
if (this.cfg.main.port) {
|
|
18
17
|
host = `${host}:${this.cfg.main.port}`;
|
|
19
18
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// Proxy AUTH requests selectively by domain
|
|
2
|
+
|
|
2
3
|
const sock = require('./line_socket');
|
|
3
4
|
const utils = require('haraka-utils');
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
const smtp_regexp = /^(\d{3})([ -])(.*)/;
|
|
5
7
|
|
|
6
8
|
exports.register = function () {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
plugin.load_tls_ini();
|
|
9
|
+
this.inherits('auth/auth_base');
|
|
10
|
+
this.load_tls_ini();
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
exports.load_tls_ini = function () {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
plugin.load_tls_ini();
|
|
14
|
+
this.tls_cfg = this.config.get('tls.ini', () => {
|
|
15
|
+
this.load_tls_ini();
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -27,8 +27,8 @@ exports.hook_capabilities = (next, connection) => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
exports.check_plain_passwd = function (connection, user, passwd, cb) {
|
|
30
|
-
let domain;
|
|
31
|
-
if (
|
|
30
|
+
let domain = /@([^@]+)$/.exec(user);
|
|
31
|
+
if (domain) {
|
|
32
32
|
domain = domain[1].toLowerCase();
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
@@ -84,7 +84,6 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
84
84
|
socket.on('error', err => {
|
|
85
85
|
connection.logerror(self, `connection failed to host ${host}: ${err}`);
|
|
86
86
|
socket.end();
|
|
87
|
-
return;
|
|
88
87
|
});
|
|
89
88
|
socket.send_command = function (cmd, data) {
|
|
90
89
|
let line = cmd + (data ? (` ${data}`) : '');
|
|
@@ -117,7 +116,7 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
117
116
|
|
|
118
117
|
connection.logdebug(self, `command state: ${command}`);
|
|
119
118
|
if (command === 'ehlo') {
|
|
120
|
-
if (code
|
|
119
|
+
if (code.startsWith('5')) {
|
|
121
120
|
// EHLO command rejected; abort
|
|
122
121
|
socket.send_command('QUIT');
|
|
123
122
|
return;
|
|
@@ -131,6 +130,7 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
131
130
|
cert = self.config.get(self.tls_cfg.main.cert || 'tls_cert.pem', 'binary');
|
|
132
131
|
if (key && cert) {
|
|
133
132
|
this.on('secure', () => {
|
|
133
|
+
if (secure) return;
|
|
134
134
|
secure = true;
|
|
135
135
|
socket.send_command('EHLO', connection.local.host);
|
|
136
136
|
});
|
|
@@ -163,21 +163,21 @@ exports.try_auth_proxy = function (connection, hosts, user, passwd, cb) {
|
|
|
163
163
|
}
|
|
164
164
|
if (command === 'auth') {
|
|
165
165
|
// Handle LOGIN
|
|
166
|
-
if (code
|
|
166
|
+
if (code.startsWith('3') && response[0] === 'VXNlcm5hbWU6') {
|
|
167
167
|
// Write to the socket directly to keep the state at 'auth'
|
|
168
168
|
this.write(`${utils.base64(user)}\r\n`);
|
|
169
169
|
response = [];
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
|
-
else if (code
|
|
172
|
+
else if (code.startsWith('3') && response[0] === 'UGFzc3dvcmQ6') {
|
|
173
173
|
this.write(`${utils.base64(passwd)}\r\n`);
|
|
174
174
|
response = [];
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
|
-
if (code
|
|
177
|
+
if (code.startsWith('5')) {
|
|
178
178
|
// Initial attempt failed; strip domain and retry.
|
|
179
|
-
|
|
180
|
-
if (
|
|
179
|
+
const u = /^([^@]+)@.+$/.exec(user)
|
|
180
|
+
if (u) {
|
|
181
181
|
user = u[1];
|
|
182
182
|
if (methods.includes('PLAIN')) {
|
|
183
183
|
socket.send_command('AUTH', `PLAIN ${utils.base64(`\0${user}\0${passwd}`)}`);
|