Haraka 3.0.2 → 3.0.4
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 +5 -9
- package/.prettierrc.yml +1 -0
- package/CONTRIBUTORS.md +11 -0
- package/Changes.md +1393 -1211
- package/Dockerfile +3 -3
- package/Plugins.md +119 -106
- package/README.md +7 -16
- package/TODO +1 -24
- package/bin/haraka +197 -298
- package/config/auth_flat_file.ini +2 -0
- package/config/auth_vpopmaild.ini +4 -2
- package/config/dhparams.pem +8 -0
- package/config/mail_from.is_resolvable.ini +4 -2
- package/config/me +1 -0
- package/config/outbound.ini +0 -2
- package/config/plugins +36 -35
- package/config/rabbitmq_amqplib.ini +8 -1
- package/config/smtp.ini +0 -1
- package/config/smtp.json +17 -0
- package/config/tls_cert.pem +23 -0
- package/config/tls_key.pem +28 -0
- package/connection.js +46 -73
- package/contrib/bsd-rc.d/haraka +3 -1
- package/contrib/plugin2npm.sh +6 -36
- package/docs/Connection.md +1 -1
- package/docs/CoreConfig.md +2 -2
- package/docs/Logging.md +7 -21
- package/docs/Outbound.md +104 -210
- package/docs/Plugins.md +47 -40
- package/docs/Transaction.md +59 -82
- package/docs/{plugins → deprecated}/connect.rdns_access.md +1 -1
- package/docs/{plugins → deprecated}/mail_from.access.md +1 -1
- package/docs/{plugins → deprecated}/rcpt_to.access.md +1 -1
- package/docs/plugins/auth/auth_vpopmaild.md +15 -19
- package/docs/plugins/auth/flat_file.md +23 -30
- package/docs/plugins/queue/rabbitmq_amqplib.md +7 -0
- package/docs/plugins/queue/smtp_forward.md +1 -1
- package/docs/plugins/queue/smtp_proxy.md +5 -10
- package/docs/plugins/relay.md +2 -2
- package/docs/plugins/tls.md +29 -9
- package/endpoint.js +16 -13
- package/haraka.js +10 -14
- package/host_pool.js +5 -5
- package/line_socket.js +3 -4
- package/logger.js +44 -28
- package/outbound/client_pool.js +27 -23
- package/outbound/config.js +4 -6
- package/outbound/fsync_writestream.js +1 -1
- package/outbound/hmail.js +180 -220
- package/outbound/index.js +86 -99
- package/outbound/qfile.js +1 -1
- package/outbound/queue.js +55 -43
- package/outbound/timer_queue.js +3 -2
- package/outbound/tls.js +19 -7
- package/package.json +66 -55
- package/plugins/.eslintrc.yaml +0 -6
- package/plugins/auth/auth_base.js +30 -12
- package/plugins/auth/auth_proxy.js +14 -12
- package/plugins/auth/auth_vpopmaild.js +30 -20
- package/plugins/auth/flat_file.js +17 -12
- package/plugins/block_me.js +1 -1
- package/plugins/data.signatures.js +2 -4
- package/plugins/early_talker.js +2 -1
- package/plugins/mail_from.is_resolvable.js +65 -135
- package/plugins/queue/deliver.js +4 -5
- package/plugins/queue/lmtp.js +11 -14
- package/plugins/queue/qmail-queue.js +2 -2
- package/plugins/queue/quarantine.js +2 -2
- package/plugins/queue/rabbitmq.js +16 -17
- package/plugins/queue/rabbitmq_amqplib.js +1 -1
- package/plugins/queue/smtp_forward.js +6 -6
- package/plugins/queue/smtp_proxy.js +10 -1
- package/plugins/queue/test.js +2 -2
- package/plugins/rcpt_to.host_list_base.js +5 -5
- package/plugins/rcpt_to.in_host_list.js +2 -2
- package/plugins/relay.js +6 -7
- package/plugins/reseed_rng.js +1 -1
- package/plugins/status.js +37 -33
- package/plugins/tls.js +2 -2
- package/plugins/xclient.js +3 -2
- package/plugins.js +51 -54
- package/run_tests +3 -30
- package/server.js +190 -190
- package/smtp_client.js +30 -23
- package/{tests → test}/config/plugins +0 -2
- package/{tests → test}/config/smtp.ini +1 -1
- package/test/config/tls/example.com/_.example.com.key +28 -0
- package/test/config/tls/example.com/example.com.crt +25 -0
- package/test/connection.js +302 -0
- package/test/endpoint.js +94 -0
- package/{tests → test}/fixtures/line_socket.js +1 -1
- package/{tests → test}/fixtures/util_hmailitem.js +19 -25
- package/{tests → test}/host_pool.js +42 -57
- package/test/logger.js +258 -0
- package/test/outbound/hmail.js +141 -0
- package/test/outbound/index.js +220 -0
- package/test/outbound/qfile.js +126 -0
- package/test/outbound_bounce_net_errors.js +142 -0
- package/{tests → test}/outbound_bounce_rfc3464.js +110 -122
- package/test/plugins/auth/auth_base.js +484 -0
- package/test/plugins/auth/auth_vpopmaild.js +83 -0
- package/test/plugins/early_talker.js +104 -0
- package/test/plugins/mail_from.is_resolvable.js +35 -0
- package/test/plugins/queue/smtp_forward.js +206 -0
- package/test/plugins/rcpt_to.host_list_base.js +122 -0
- package/test/plugins/rcpt_to.in_host_list.js +193 -0
- package/test/plugins/relay.js +303 -0
- package/test/plugins/status.js +130 -0
- package/test/plugins/tls.js +70 -0
- package/test/plugins.js +228 -0
- package/{tests → test}/queue/multibyte +0 -0
- package/{tests → test}/queue/plain +0 -0
- package/test/rfc1869.js +73 -0
- package/test/server.js +491 -0
- package/test/smtp_client.js +299 -0
- package/test/tls_socket.js +273 -0
- package/test/transaction.js +270 -0
- package/tls_socket.js +202 -252
- package/transaction.js +9 -24
- package/CONTRIBUTING.md +0 -1
- package/bin/dkimverify +0 -40
- package/config/access.domains +0 -13
- package/config/attachment.ctype.regex +0 -2
- package/config/attachment.filename.regex +0 -1
- package/config/avg.ini +0 -5
- package/config/bounce.ini +0 -15
- package/config/data.headers.ini +0 -61
- package/config/dkim/dkim_key_gen.sh +0 -78
- package/config/dkim_sign.ini +0 -4
- package/config/dkim_verify.ini +0 -7
- package/config/dnsbl.ini +0 -23
- package/config/greylist.ini +0 -43
- package/config/helo.checks.ini +0 -52
- package/config/lookup_rdns.strict.ini +0 -12
- package/config/lookup_rdns.strict.timeout +0 -1
- package/config/lookup_rdns.strict.whitelist +0 -1
- package/config/lookup_rdns.strict.whitelist_regex +0 -5
- package/config/messagesniffer.ini +0 -18
- package/config/rcpt_to.blocklist +0 -1
- package/config/rdns.allow_regexps +0 -0
- package/config/rdns.deny_regexps +0 -0
- package/config/spamassassin.ini +0 -56
- package/config.js +0 -6
- package/dkim.js +0 -614
- package/docs/plugins/avg.md +0 -35
- package/docs/plugins/bounce.md +0 -69
- package/docs/plugins/clamd.md +0 -147
- package/docs/plugins/esets.md +0 -8
- package/docs/plugins/greylist.md +0 -90
- package/docs/plugins/helo.checks.md +0 -135
- package/docs/plugins/messagesniffer.md +0 -163
- package/docs/plugins/relay_acl.md +0 -29
- package/docs/plugins/relay_all.md +0 -15
- package/docs/plugins/relay_force_routing.md +0 -33
- package/docs/plugins/spamassassin.md +0 -180
- package/outbound/mx_lookup.js +0 -70
- package/plugins/auth/auth_ldap.js +0 -3
- package/plugins/avg.js +0 -162
- package/plugins/backscatterer.js +0 -25
- package/plugins/bounce.js +0 -381
- package/plugins/clamd.js +0 -381
- package/plugins/data.headers.js +0 -4
- package/plugins/data.uribl.js +0 -4
- package/plugins/dkim_sign.js +0 -395
- package/plugins/dkim_verify.js +0 -62
- package/plugins/dns_list_base.js +0 -221
- package/plugins/dnsbl.js +0 -146
- package/plugins/dnswl.js +0 -58
- package/plugins/esets.js +0 -71
- package/plugins/graph.js +0 -5
- package/plugins/greylist.js +0 -645
- package/plugins/helo.checks.js +0 -533
- package/plugins/messagesniffer.js +0 -381
- package/plugins/rcpt_to.ldap.js +0 -3
- package/plugins/rcpt_to.max_count.js +0 -24
- package/plugins/relay_all.js +0 -13
- package/plugins/spamassassin.js +0 -384
- package/tests/config/dkim/example.com/dns +0 -29
- package/tests/config/dkim/example.com/private +0 -6
- package/tests/config/dkim/example.com/public +0 -4
- package/tests/config/dkim/example.com/selector +0 -1
- package/tests/config/dkim.private.key +0 -6
- package/tests/config/dkim_sign.ini +0 -4
- package/tests/config/helo.checks.ini +0 -52
- package/tests/connection.js +0 -327
- package/tests/endpoint.js +0 -128
- package/tests/fixtures/vm_harness.js +0 -59
- package/tests/logger.js +0 -327
- package/tests/outbound/hmail.js +0 -112
- package/tests/outbound/index.js +0 -324
- package/tests/outbound/qfile.js +0 -67
- package/tests/outbound_bounce_net_errors.js +0 -173
- package/tests/plugins/auth/auth_base.js +0 -463
- package/tests/plugins/auth/auth_vpopmaild.js +0 -91
- package/tests/plugins/bounce.js +0 -307
- package/tests/plugins/clamd.js +0 -224
- package/tests/plugins/deprecated/relay_acl.js +0 -140
- package/tests/plugins/deprecated/relay_all.js +0 -59
- package/tests/plugins/dkim_sign.js +0 -315
- package/tests/plugins/dkim_signer.js +0 -108
- package/tests/plugins/dns_list_base.js +0 -259
- package/tests/plugins/dnsbl.js +0 -101
- package/tests/plugins/early_talker.js +0 -115
- package/tests/plugins/greylist.js +0 -58
- package/tests/plugins/helo.checks.js +0 -525
- package/tests/plugins/mail_from.is_resolvable.js +0 -116
- package/tests/plugins/queue/smtp_forward.js +0 -221
- package/tests/plugins/rcpt_to.host_list_base.js +0 -132
- package/tests/plugins/rcpt_to.in_host_list.js +0 -218
- package/tests/plugins/relay.js +0 -339
- package/tests/plugins/spamassassin.js +0 -171
- package/tests/plugins/status.js +0 -138
- package/tests/plugins/tls.js +0 -84
- package/tests/plugins.js +0 -247
- package/tests/rfc1869.js +0 -61
- package/tests/server.js +0 -510
- package/tests/smtp_client/auth.js +0 -105
- package/tests/smtp_client/basic.js +0 -101
- package/tests/smtp_client.js +0 -80
- package/tests/tls_socket.js +0 -333
- package/tests/transaction.js +0 -284
- /package/docs/{plugins → deprecated}/dkim_sign.md +0 -0
- /package/docs/{plugins → deprecated}/dkim_verify.md +0 -0
- /package/docs/{plugins → deprecated}/dnsbl.md +0 -0
- /package/docs/{plugins → deprecated}/dnswl.md +0 -0
- /package/docs/{plugins → deprecated}/rcpt_to.routes.md +0 -0
- /package/{tests → test}/.eslintrc.yaml +0 -0
- /package/{tests → test}/config/auth_flat_file.ini +0 -0
- /package/{tests → test}/config/dhparams.pem +0 -0
- /package/{tests → test}/config/host_list +0 -0
- /package/{tests → test}/config/outbound_tls_cert.pem +0 -0
- /package/{tests → test}/config/outbound_tls_key.pem +0 -0
- /package/{tests → test}/config/smtp_forward.ini +0 -0
- /package/{tests → test}/config/tls/ec.pem +0 -0
- /package/{tests → test}/config/tls/haraka.local.pem +0 -0
- /package/{tests → test}/config/tls/mismatched.pem +0 -0
- /package/{tests → test}/config/tls.ini +0 -0
- /package/{tests → test}/config/tls_cert.pem +0 -0
- /package/{tests → test}/config/tls_key.pem +0 -0
- /package/{tests → test}/fixtures/todo_qfile.txt +0 -0
- /package/{tests → test}/installation/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/config/tls.ini +0 -0
- /package/{tests → test}/installation/node_modules/load_first/index.js +0 -0
- /package/{tests → test}/installation/node_modules/load_first/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin-flat +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/config/test-plugin.ini +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/package.json +0 -0
- /package/{tests → test}/installation/node_modules/test-plugin/test-plugin.js +0 -0
- /package/{tests → test}/installation/plugins/base_plugin.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/index.js +0 -0
- /package/{tests → test}/installation/plugins/folder_plugin/package.json +0 -0
- /package/{tests → test}/installation/plugins/inherits.js +0 -0
- /package/{tests → test}/installation/plugins/load_first.js +0 -0
- /package/{tests → test}/installation/plugins/plugin.js +0 -0
- /package/{tests → test}/installation/plugins/tls.js +0 -0
- /package/{tests → test}/loud/config/dhparams.pem +0 -0
- /package/{tests → test}/loud/config/tls/goobered.pem +0 -0
- /package/{tests → test}/loud/config/tls.ini +0 -0
- /package/{tests → test}/mail_specimen/base64-root-part.txt +0 -0
- /package/{tests → test}/mail_specimen/varied-fold-lengths-preserve-data.txt +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_fixed +0 -0
- /package/{tests → test}/queue/1507509981169_1507509981169_0_61403_e0Y0Ym_1_haraka +0 -0
- /package/{tests → test}/queue/1508269674999_1508269674999_0_34002_socVUF_1_haraka +0 -0
- /package/{tests → test}/queue/1508455115683_1508455115683_0_90253_9Q4o4V_1_haraka +0 -0
- /package/{tests → test}/queue/zero-length +0 -0
- /package/{tests → test}/test-queue/delete-me +0 -0
package/plugins/greylist.js
DELETED
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
// Greylisting plugin for Haraka
|
|
2
|
-
|
|
3
|
-
// version 0.1.4
|
|
4
|
-
|
|
5
|
-
// node builtins
|
|
6
|
-
const net = require('net');
|
|
7
|
-
const util = require('util');
|
|
8
|
-
|
|
9
|
-
// Haraka modules
|
|
10
|
-
const DSN = require('haraka-dsn');
|
|
11
|
-
const tlds = require('haraka-tld');
|
|
12
|
-
const net_utils = require('haraka-net-utils');
|
|
13
|
-
const { Address } = require('address-rfc2821');
|
|
14
|
-
|
|
15
|
-
// External NPM modules
|
|
16
|
-
const ipaddr = require('ipaddr.js');
|
|
17
|
-
|
|
18
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
19
|
-
exports.register = function (next) {
|
|
20
|
-
this.inherits('haraka-plugin-redis');
|
|
21
|
-
|
|
22
|
-
this.load_config();
|
|
23
|
-
|
|
24
|
-
this.register_hook('init_master', 'init_redis_plugin');
|
|
25
|
-
this.register_hook('init_child', 'init_redis_plugin');
|
|
26
|
-
|
|
27
|
-
// redundant - using the special hook_ nomenclature
|
|
28
|
-
// this.register_hook('rcpt_ok', 'hook_rcpt_ok');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
32
|
-
exports.load_config = function () {
|
|
33
|
-
|
|
34
|
-
this.cfg = this.config.get('greylist.ini', {
|
|
35
|
-
booleans : [
|
|
36
|
-
'+skip.dnswlorg',
|
|
37
|
-
'-skip.mailspikewl'
|
|
38
|
-
]
|
|
39
|
-
}, () => {
|
|
40
|
-
this.load_config();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.merge_redis_ini();
|
|
44
|
-
this.load_config_lists();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Load various configuration lists
|
|
48
|
-
exports.load_config_lists = function () {
|
|
49
|
-
const plugin = this;
|
|
50
|
-
|
|
51
|
-
plugin.whitelist = {};
|
|
52
|
-
plugin.list = {};
|
|
53
|
-
|
|
54
|
-
function load_list (type, file_name) {
|
|
55
|
-
plugin.whitelist[type] = {};
|
|
56
|
-
|
|
57
|
-
const list = Object.keys(plugin.cfg[file_name]);
|
|
58
|
-
|
|
59
|
-
// toLower when loading spends a fraction of a second at load time
|
|
60
|
-
// to save millions of seconds during run time.
|
|
61
|
-
for (const element of list) {
|
|
62
|
-
plugin.whitelist[type][element.toLowerCase()] = true;
|
|
63
|
-
}
|
|
64
|
-
plugin.logdebug(`whitelist {${type}} loaded from ${file_name} with ${list.length} entries`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function load_ip_list (type, file_name) {
|
|
68
|
-
plugin.whitelist[type] = [];
|
|
69
|
-
|
|
70
|
-
const list = Object.keys(plugin.cfg[file_name]);
|
|
71
|
-
|
|
72
|
-
for (const element of list) {
|
|
73
|
-
try {
|
|
74
|
-
let addr = element;
|
|
75
|
-
if (addr.match(/\/\d+$/)) {
|
|
76
|
-
addr = ipaddr.parseCIDR(addr);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
addr = ipaddr.parseCIDR(`${addr}${((net.isIPv6(addr)) ? '/128' : '/32')}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
plugin.whitelist[type].push(addr);
|
|
83
|
-
}
|
|
84
|
-
catch (e) {}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
plugin.logdebug(`whitelist {${type}} loaded from ${file_name} with ${plugin.whitelist[type].length} entries`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function load_config_list (type, file_name) {
|
|
91
|
-
plugin.list[type] = Object.keys(plugin.cfg[file_name]);
|
|
92
|
-
|
|
93
|
-
plugin.logdebug(`list {${type}} loaded from ${file_name} with ${plugin.list[type].length} entries`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
load_list('mail', 'envelope_whitelist');
|
|
97
|
-
load_list('rcpt', 'recipient_whitelist');
|
|
98
|
-
load_ip_list('ip', 'ip_whitelist');
|
|
99
|
-
|
|
100
|
-
load_config_list('dyndom', 'special_dynamic_domains');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
104
|
-
exports.shutdown = function () {
|
|
105
|
-
if (this.db) this.db.quit();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
109
|
-
|
|
110
|
-
// We check for IP and envelope whitelist
|
|
111
|
-
exports.hook_mail = function (next, connection, params) {
|
|
112
|
-
if (!connection.transaction) return next();
|
|
113
|
-
|
|
114
|
-
const mail_from = params[0];
|
|
115
|
-
|
|
116
|
-
// whitelist checks
|
|
117
|
-
if (this.ip_in_list(connection.remote.ip)) { // check connecting IP
|
|
118
|
-
|
|
119
|
-
this.loginfo(connection, 'Connecting IP was whitelisted via config');
|
|
120
|
-
connection.transaction.results.add(this, { skip : 'config-whitelist(ip)' })
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
else if (this.addr_in_list('mail', mail_from.address().toLowerCase())) { // check envelope (email & domain)
|
|
124
|
-
|
|
125
|
-
this.loginfo(connection, 'Envelope was whitelisted via config');
|
|
126
|
-
connection.transaction.results.add(this, { skip : 'config-whitelist(envelope)' });
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
const why_skip = this.process_skip_rules(connection);
|
|
131
|
-
|
|
132
|
-
if (why_skip) {
|
|
133
|
-
this.loginfo(connection, `Requested to skip the GL because skip rule matched: ${why_skip}`);
|
|
134
|
-
connection.transaction.results.add(this, { skip : `requested(${why_skip})` });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
next();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
143
|
-
|
|
144
|
-
if (this.should_skip_check(connection)) return next();
|
|
145
|
-
if (this.was_whitelisted_in_session(connection)) {
|
|
146
|
-
this.logdebug(connection, 'host already whitelisted in this session');
|
|
147
|
-
return next();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const { transaction } = connection
|
|
151
|
-
|
|
152
|
-
const ctr = transaction.results;
|
|
153
|
-
const { mail_from } = transaction;
|
|
154
|
-
|
|
155
|
-
// check rcpt in whitelist (email & domain)
|
|
156
|
-
if (this.addr_in_list('rcpt', rcpt.address().toLowerCase())) {
|
|
157
|
-
this.loginfo(connection, 'RCPT was whitelisted via config');
|
|
158
|
-
ctr.add(this, { skip : 'config-whitelist(recipient)' });
|
|
159
|
-
return next();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
this.check_and_update_white(connection, (err, white_rec) => {
|
|
163
|
-
if (err) {
|
|
164
|
-
this.logerror(connection, `Got error: ${util.inspect(err)}`);
|
|
165
|
-
return next(DENYSOFT, DSN.sec_unspecified('Backend failure. Please, retry later or contact our support.'));
|
|
166
|
-
}
|
|
167
|
-
if (white_rec) {
|
|
168
|
-
this.logdebug(connection, 'host in WHITE zone');
|
|
169
|
-
ctr.add(this, { pass : 'whitelisted' });
|
|
170
|
-
ctr.push(this, { stats : { rcpt : white_rec }, stage : 'rcpt' });
|
|
171
|
-
|
|
172
|
-
return next();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return this.process_tuple(connection, mail_from.address(), rcpt.address(), (err2, white_promo_rec) => {
|
|
176
|
-
if (err2) {
|
|
177
|
-
if (err2 instanceof Error && err2.notanerror) {
|
|
178
|
-
this.logdebug(connection, 'host in GREY zone');
|
|
179
|
-
|
|
180
|
-
ctr.add(this, {
|
|
181
|
-
fail : 'greylisted'
|
|
182
|
-
});
|
|
183
|
-
ctr.push(this, {
|
|
184
|
-
stats : {
|
|
185
|
-
rcpt : err2.record
|
|
186
|
-
},
|
|
187
|
-
stage : 'rcpt'
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
return this.invoke_outcome_cb(next, false);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
throw err2;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (!white_promo_rec) {
|
|
197
|
-
ctr.add(this, {
|
|
198
|
-
fail : 'greylisted',
|
|
199
|
-
stage : 'rcpt'
|
|
200
|
-
});
|
|
201
|
-
return this.invoke_outcome_cb(next, false);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
this.loginfo(connection, 'host has been promoted to WHITE zone');
|
|
205
|
-
ctr.add(this, {
|
|
206
|
-
pass : 'whitelisted',
|
|
207
|
-
stats : white_promo_rec,
|
|
208
|
-
stage : 'rcpt'
|
|
209
|
-
});
|
|
210
|
-
ctr.add(this, {
|
|
211
|
-
pass : 'whitelisted'
|
|
212
|
-
});
|
|
213
|
-
return this.invoke_outcome_cb(next, true);
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
220
|
-
|
|
221
|
-
// Main GL engine that accepts tuple and returns matched record or a rejection.
|
|
222
|
-
exports.process_tuple = function (connection, sender, rcpt, cb) {
|
|
223
|
-
|
|
224
|
-
const key = this.craft_grey_key(connection, sender, rcpt);
|
|
225
|
-
if (!key) return;
|
|
226
|
-
|
|
227
|
-
return this.db_lookup(key, (err, record) => {
|
|
228
|
-
if (err) {
|
|
229
|
-
if (err instanceof Error && err.what == 'db_error')
|
|
230
|
-
this.logwarn(connection, `got err from DB: ${util.inspect(err)}`);
|
|
231
|
-
throw err;
|
|
232
|
-
}
|
|
233
|
-
this.logdebug(connection, `got record: ${util.inspect(record)}`);
|
|
234
|
-
|
|
235
|
-
// { created: TS, updated: TS, lifetime: TTL, tried: Integer }
|
|
236
|
-
const now = Date.now() / 1000;
|
|
237
|
-
|
|
238
|
-
if (record &&
|
|
239
|
-
(record.created + this.cfg.period.black < now) &&
|
|
240
|
-
(record.created + record.lifetime >= now)) {
|
|
241
|
-
// Host passed greylisting
|
|
242
|
-
return this.promote_to_white(connection, record, cb);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return this.update_grey(key, !record, (err2, created_record) => {
|
|
246
|
-
const err3 = new Error('in black zone');
|
|
247
|
-
err3.record = created_record || record;
|
|
248
|
-
err3.notanerror = true;
|
|
249
|
-
return cb(err3, null);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Checks if host is _white_. Updates stats if so.
|
|
255
|
-
exports.check_and_update_white = function (connection, cb) {
|
|
256
|
-
|
|
257
|
-
const key = this.craft_white_key(connection);
|
|
258
|
-
|
|
259
|
-
return this.db_lookup(key, (err, record) => {
|
|
260
|
-
if (err) {
|
|
261
|
-
this.logwarn(connection, `got err from DB: ${util.inspect(err)}`);
|
|
262
|
-
throw err;
|
|
263
|
-
}
|
|
264
|
-
if (record) {
|
|
265
|
-
if (record.updated + record.lifetime - 2 < Date.now() / 1000) { // race "prevention".
|
|
266
|
-
this.logerror(connection, "Mischief! Race condition triggered.");
|
|
267
|
-
return cb(new Error('drunkard'));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return this.update_white_record(key, record, cb);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return cb(null, false);
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// invokes next() depending on outcome param
|
|
278
|
-
exports.invoke_outcome_cb = function (next, is_whitelisted) {
|
|
279
|
-
|
|
280
|
-
if (is_whitelisted) return next();
|
|
281
|
-
|
|
282
|
-
const text = this.cfg.main.text || '';
|
|
283
|
-
|
|
284
|
-
return next(DENYSOFT, DSN.sec_unauthorized(text, '451'));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Should we skip greylisting invokation altogether?
|
|
288
|
-
exports.should_skip_check = function (connection) {
|
|
289
|
-
const { transaction, relaying, remote } = connection ?? {}
|
|
290
|
-
|
|
291
|
-
if (!transaction) return true;
|
|
292
|
-
|
|
293
|
-
const ctr = transaction.results;
|
|
294
|
-
|
|
295
|
-
if (relaying) {
|
|
296
|
-
this.logdebug(connection, 'skipping GL for relaying host');
|
|
297
|
-
ctr.add(this, {
|
|
298
|
-
skip : 'relaying'
|
|
299
|
-
});
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (remote?.is_private) {
|
|
304
|
-
connection.logdebug(this, `skipping private IP: ${connection.remote.ip}`);
|
|
305
|
-
ctr.add(this, {
|
|
306
|
-
skip : 'private-ip'
|
|
307
|
-
});
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (ctr) {
|
|
312
|
-
if (ctr.has(this, 'skip', /^config-whitelist/)) {
|
|
313
|
-
this.loginfo(connection, 'skipping GL for host whitelisted in config');
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
if (ctr.has(this, 'skip', /^requested/)) {
|
|
317
|
-
this.loginfo(connection, 'skipping GL because was asked to previously');
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Was whitelisted previously in this session
|
|
326
|
-
exports.was_whitelisted_in_session = function (connection) {
|
|
327
|
-
if (!connection?.transaction?.results) return false;
|
|
328
|
-
return connection.transaction.results.has(this, 'pass', 'whitelisted');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
exports.process_skip_rules = function (connection) {
|
|
332
|
-
const cr = connection.results;
|
|
333
|
-
|
|
334
|
-
const skip_cfg = this.cfg.skip;
|
|
335
|
-
if (skip_cfg) {
|
|
336
|
-
if (skip_cfg.dnswlorg && cr.has('dnswl.org', 'pass', /^list\.dnswl\.org\([123]\)$/)) {
|
|
337
|
-
return 'dnswl.org(MED)'
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (skip_cfg.mailspikewl && cr.has('dnswl.org', 'pass', /^wl\.mailspike\.net\((1[7-9]|20)\)$/)) {
|
|
341
|
-
return 'mailspike(H2)'
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return '';
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
349
|
-
|
|
350
|
-
// Build greylist DB key (originally, a "tuple") off supplied params.
|
|
351
|
-
// When _to_ is false, we craft +sender+ key
|
|
352
|
-
// When _to_ is String, we craft +rcpt+ key
|
|
353
|
-
exports.craft_grey_key = function (connection, from, to) {
|
|
354
|
-
|
|
355
|
-
const crafted_host_id = this.craft_hostid(connection)
|
|
356
|
-
if (!crafted_host_id) return null;
|
|
357
|
-
|
|
358
|
-
let key = `grey:${crafted_host_id}:${(from || '<>')}`;
|
|
359
|
-
if (to != undefined) {
|
|
360
|
-
key += `:${(to || '<>')}`;
|
|
361
|
-
}
|
|
362
|
-
return key;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Build white DB key off supplied params.
|
|
366
|
-
exports.craft_white_key = function (connection) {
|
|
367
|
-
return `white:${this.craft_hostid(connection)}`;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Return so-called +hostid+.
|
|
371
|
-
exports.craft_hostid = function (connection) {
|
|
372
|
-
const plugin = this;
|
|
373
|
-
const { transaction, remote } = connection ?? {};
|
|
374
|
-
if (!transaction || !remote) return null;
|
|
375
|
-
|
|
376
|
-
if (transaction.notes?.greylist?.hostid) {
|
|
377
|
-
return transaction.notes.greylist.hostid; // "caching"
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function chsit (value, reason) { // cache the return value
|
|
381
|
-
if (!value)
|
|
382
|
-
plugin.logdebug(connection, `hostid set to IP: ${reason}`);
|
|
383
|
-
|
|
384
|
-
transaction.results.add(plugin, {
|
|
385
|
-
hostid_type : value ? 'domain' : 'ip',
|
|
386
|
-
rdns : (value || remote.ip),
|
|
387
|
-
msg : reason
|
|
388
|
-
}); // !don't move me.
|
|
389
|
-
|
|
390
|
-
value = value || remote.ip;
|
|
391
|
-
|
|
392
|
-
return ((transaction.notes.greylist = transaction.notes.greylist || {}).hostid = value);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// no rDNS . FIXME: use fcrdns results
|
|
396
|
-
if (!remote.host || [ 'Unknown' | 'DNSERROR' ].includes(remote.host))
|
|
397
|
-
return chsit(null, 'no rDNS info for this host');
|
|
398
|
-
|
|
399
|
-
remote.host = remote.host.replace(/\.$/, ''); // strip ending dot, just in case
|
|
400
|
-
|
|
401
|
-
const fcrdns = connection.results.get('fcrdns');
|
|
402
|
-
if (!fcrdns) {
|
|
403
|
-
plugin.logwarn(connection, 'No FcrDNS plugin results, fix this.');
|
|
404
|
-
return chsit(null, 'no FcrDNS plugin results');
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (!connection.results.has('fcrdns', 'pass', 'fcrdns')) // FcrDNS failed
|
|
408
|
-
return chsit(null, 'FcrDNS failed');
|
|
409
|
-
|
|
410
|
-
if (connection.results.get('fcrdns').ptr_names.length > 1) // multiple PTR returned
|
|
411
|
-
return chsit(null, 'multiple PTR returned');
|
|
412
|
-
|
|
413
|
-
if (connection.results.has('fcrdns', 'fail', /^is_generic/)) // generic/dynamic rDNS record
|
|
414
|
-
return chsit(null, 'rDNS is a generic record');
|
|
415
|
-
|
|
416
|
-
if (connection.results.has('fcrdns', 'fail', /^valid_tld/)) // invalid org domain in rDNS
|
|
417
|
-
return chsit(null, 'invalid org domain in rDNS');
|
|
418
|
-
|
|
419
|
-
// strip first label up until the tld boundary.
|
|
420
|
-
const decoupled = tlds.split_hostname(!remote.host, 3);
|
|
421
|
-
const vardom = decoupled[0]; // "variable" portion of domain
|
|
422
|
-
const dom = decoupled[1]; // "static" portion of domain
|
|
423
|
-
|
|
424
|
-
// we check for special cases where rdns looks custom/static, but really is dynamic
|
|
425
|
-
const special_case_info = plugin.check_rdns_for_special_cases(!remote.host, vardom);
|
|
426
|
-
if (special_case_info) return chsit(null, special_case_info.why);
|
|
427
|
-
|
|
428
|
-
let stripped_dom = dom;
|
|
429
|
-
|
|
430
|
-
if (vardom) {
|
|
431
|
-
|
|
432
|
-
// check for decimal IP in rDNS
|
|
433
|
-
if (vardom.match(String(net_utils.ip_to_long(remote.ip))))
|
|
434
|
-
return chsit(null, 'decimal IP');
|
|
435
|
-
|
|
436
|
-
// craft the +hostid+
|
|
437
|
-
const label = vardom.split('.').slice(1).join('.');
|
|
438
|
-
if (label)
|
|
439
|
-
stripped_dom = `${label}.${stripped_dom}`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return chsit(stripped_dom);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
446
|
-
|
|
447
|
-
// Retrieve _grey_ record
|
|
448
|
-
// not implemented
|
|
449
|
-
exports.retrieve_grey = function (rcpt_key, sender_key, cb) {
|
|
450
|
-
const multi = this.db.multi();
|
|
451
|
-
|
|
452
|
-
multi.hgetall(rcpt_key);
|
|
453
|
-
multi.hgetall(sender_key);
|
|
454
|
-
|
|
455
|
-
multi.exec((err, result) => {
|
|
456
|
-
if (err) {
|
|
457
|
-
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
458
|
-
err.what = 'db_error';
|
|
459
|
-
throw err;
|
|
460
|
-
}
|
|
461
|
-
return cb(err, result);
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Update or create _grey_ record
|
|
466
|
-
exports.update_grey = function (key, create, cb) {
|
|
467
|
-
const multi = this.db.multi();
|
|
468
|
-
|
|
469
|
-
const ts_now = Math.round(Date.now() / 1000);
|
|
470
|
-
let new_record;
|
|
471
|
-
|
|
472
|
-
if (create) {
|
|
473
|
-
const lifetime = this.cfg.period.grey;
|
|
474
|
-
new_record = {
|
|
475
|
-
created : ts_now,
|
|
476
|
-
updated : ts_now,
|
|
477
|
-
lifetime,
|
|
478
|
-
tried : 1
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
multi.hmset(key, new_record);
|
|
482
|
-
multi.expire(key, lifetime);
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
multi.hincrby(key, 'tried', 1);
|
|
486
|
-
multi.hmset(key, {
|
|
487
|
-
updated : ts_now
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
multi.exec((err, records) => {
|
|
492
|
-
if (err) {
|
|
493
|
-
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
494
|
-
err.what = 'db_error';
|
|
495
|
-
throw err;
|
|
496
|
-
}
|
|
497
|
-
return cb(null, ((create) ? new_record : false));
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Promote _grey_ record to _white_.
|
|
502
|
-
exports.promote_to_white = function (connection, grey_rec, cb) {
|
|
503
|
-
|
|
504
|
-
const ts_now = Math.round(Date.now() / 1000);
|
|
505
|
-
const white_ttl = this.cfg.period.white;
|
|
506
|
-
|
|
507
|
-
// { first_connect: TS, whitelisted: TS, updated: TS, lifetime: TTL, tried: Integer, tried_when_greylisted: Integer }
|
|
508
|
-
const white_rec = {
|
|
509
|
-
first_connect : grey_rec.created,
|
|
510
|
-
whitelisted : ts_now,
|
|
511
|
-
updated : ts_now,
|
|
512
|
-
lifetime : white_ttl,
|
|
513
|
-
tried_when_greylisted : grey_rec.tried,
|
|
514
|
-
tried : 1
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
const white_key = this.craft_white_key(connection);
|
|
518
|
-
if (!white_key) return;
|
|
519
|
-
|
|
520
|
-
return this.db.hmset(white_key, white_rec, (err, result) => {
|
|
521
|
-
if (err) {
|
|
522
|
-
this.lognotice(`DB error: ${util.inspect(err)}`);
|
|
523
|
-
err.what = 'db_error';
|
|
524
|
-
throw err;
|
|
525
|
-
}
|
|
526
|
-
this.db.expire(white_key, white_ttl, (err2, result2) => {
|
|
527
|
-
if (err2) {
|
|
528
|
-
this.lognotice(`DB error: ${util.inspect(err2)}`);
|
|
529
|
-
}
|
|
530
|
-
return cb(err2, result2);
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Update _white_ record
|
|
536
|
-
exports.update_white_record = function (key, record, cb) {
|
|
537
|
-
|
|
538
|
-
const multi = this.db.multi();
|
|
539
|
-
const ts_now = Math.round(Date.now() / 1000);
|
|
540
|
-
|
|
541
|
-
// { first_connect: TS, whitelisted: TS, updated: TS, lifetime: TTL, tried: Integer, tried_when_greylisted: Integer }
|
|
542
|
-
multi.hincrby(key, 'tried', 1);
|
|
543
|
-
multi.hmset(key, {
|
|
544
|
-
updated : ts_now
|
|
545
|
-
});
|
|
546
|
-
multi.expire(key, record.lifetime);
|
|
547
|
-
|
|
548
|
-
return multi.exec((err2, record2) => {
|
|
549
|
-
if (err2) {
|
|
550
|
-
this.lognotice(`DB error: ${util.inspect(err2)}`);
|
|
551
|
-
err2.what = 'db_error';
|
|
552
|
-
throw err2;
|
|
553
|
-
}
|
|
554
|
-
return cb(null, record2);
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
559
|
-
|
|
560
|
-
exports.db_lookup = function (key, cb) {
|
|
561
|
-
|
|
562
|
-
this.db.hgetall(key, (err, result) => {
|
|
563
|
-
if (err) {
|
|
564
|
-
this.lognotice(`DB error: ${util.inspect(err)}`, key);
|
|
565
|
-
}
|
|
566
|
-
if (result && typeof result === 'object') { // groom known-to-be numeric values
|
|
567
|
-
['created', 'updated', 'lifetime', 'tried', 'first_connect', 'whitelisted', 'tried_when_greylisted'].forEach(kk => {
|
|
568
|
-
const val = result[kk];
|
|
569
|
-
if (val !== undefined) {
|
|
570
|
-
result[kk] = Number(val);
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
return cb(null, result);
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
579
|
-
exports.addr_in_list = function (type, address) {
|
|
580
|
-
|
|
581
|
-
if (!this.whitelist[type]) {
|
|
582
|
-
this.logwarn(`List not defined: ${type}`);
|
|
583
|
-
return false;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (this.whitelist[type][address]) {
|
|
587
|
-
return true;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
try {
|
|
591
|
-
const addr = new Address(address);
|
|
592
|
-
return !!this.whitelist[type][addr.host];
|
|
593
|
-
}
|
|
594
|
-
catch (err) {
|
|
595
|
-
return false;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
exports.ip_in_list = function (ip) {
|
|
600
|
-
const ipobj = ipaddr.parse(ip);
|
|
601
|
-
|
|
602
|
-
const list = this.whitelist.ip;
|
|
603
|
-
|
|
604
|
-
for (const element of list) {
|
|
605
|
-
try {
|
|
606
|
-
if (ipobj.match(element)) {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
catch (e) {}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Match patterns in the list against (end of) domain
|
|
617
|
-
exports.domain_in_list = function (list_name, domain) {
|
|
618
|
-
const list = this.list[list_name];
|
|
619
|
-
|
|
620
|
-
if (!list) {
|
|
621
|
-
this.logwarn(`List not defined: ${list_name}`);
|
|
622
|
-
return false;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
for (const element of list) {
|
|
626
|
-
if (domain.length - domain.lastIndexOf(element) == element.length)
|
|
627
|
-
return true;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return false;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Check for special rDNS cases
|
|
634
|
-
// @return {type: 'dynamic'} if rnds is dynamic (hostid should be IP)
|
|
635
|
-
exports.check_rdns_for_special_cases = function (domain, label) {
|
|
636
|
-
|
|
637
|
-
// ptr for these is in fact dynamic
|
|
638
|
-
if (this.domain_in_list('dyndom', domain))
|
|
639
|
-
return {
|
|
640
|
-
type : 'dynamic',
|
|
641
|
-
why : 'rDNS considered dynamic: listed in dynamic.domains config list'
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
return false;
|
|
645
|
-
}
|