haraka-plugin-karma 1.0.14 → 2.0.2
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/.codeclimate.yml +16 -1
- package/.eslintrc.yaml +3 -0
- package/.github/workflows/ci.yml +50 -0
- package/.github/workflows/codeql.yml +14 -0
- package/.github/workflows/publish.yml +16 -0
- package/Changes.md +35 -14
- package/README.md +3 -5
- package/index.js +462 -526
- package/package.json +8 -7
- package/test/karma.js +300 -300
- package/.github/workflows/ci-test-win.yml +0 -38
- package/.github/workflows/ci-test.yml +0 -41
- package/.github/workflows/lint.yml +0 -33
- package/.travis.yml +0 -20
- package/appveyor.yml +0 -20
package/index.js
CHANGED
|
@@ -1,609 +1,572 @@
|
|
|
1
|
-
'use strict'
|
|
1
|
+
'use strict'
|
|
2
2
|
// karma - reward good and penalize bad mail senders
|
|
3
3
|
|
|
4
|
-
const constants = require('haraka-constants')
|
|
5
|
-
const
|
|
4
|
+
const constants = require('haraka-constants')
|
|
5
|
+
const redis = require('redis')
|
|
6
|
+
const utils = require('haraka-utils')
|
|
6
7
|
|
|
7
8
|
const phase_prefixes = utils.to_object([
|
|
8
9
|
'connect','helo','mail_from','rcpt_to','data'
|
|
9
|
-
])
|
|
10
|
+
])
|
|
10
11
|
|
|
11
12
|
exports.register = function () {
|
|
12
|
-
const plugin = this;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
this.inherits('haraka-plugin-redis')
|
|
15
15
|
|
|
16
16
|
// set up defaults
|
|
17
|
-
|
|
17
|
+
this.deny_hooks = utils.to_object(
|
|
18
18
|
['unrecognized_command','helo','data','data_post','queue','queue_outbound']
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
)
|
|
20
|
+
this.deny_exclude_hooks = utils.to_object('rcpt_to queue queue_outbound')
|
|
21
|
+
this.deny_exclude_plugins = utils.to_object([
|
|
22
22
|
'access', 'helo.checks', 'data.headers', 'spamassassin',
|
|
23
23
|
'mail_from.is_resolvable', 'clamd', 'tls'
|
|
24
|
-
])
|
|
24
|
+
])
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
this.load_karma_ini()
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
this.register_hook('init_master', 'init_redis_plugin')
|
|
29
|
+
this.register_hook('init_child', 'init_redis_plugin')
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
this.register_hook('connect_init', 'results_init')
|
|
32
|
+
this.register_hook('connect_init', 'ip_history_from_redis')
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
exports.load_karma_ini = function () {
|
|
36
|
-
const plugin = this
|
|
36
|
+
const plugin = this
|
|
37
37
|
|
|
38
38
|
plugin.cfg = plugin.config.get('karma.ini', {
|
|
39
39
|
booleans: [
|
|
40
40
|
'+asn.enable',
|
|
41
41
|
],
|
|
42
42
|
}, function () {
|
|
43
|
-
plugin.load_karma_ini()
|
|
44
|
-
})
|
|
43
|
+
plugin.load_karma_ini()
|
|
44
|
+
})
|
|
45
45
|
|
|
46
|
-
plugin.merge_redis_ini()
|
|
46
|
+
plugin.merge_redis_ini()
|
|
47
47
|
|
|
48
|
-
const cfg = plugin.cfg
|
|
48
|
+
const cfg = plugin.cfg
|
|
49
49
|
if (cfg.deny && cfg.deny.hooks) {
|
|
50
|
-
plugin.deny_hooks = utils.to_object(cfg.deny.hooks)
|
|
50
|
+
plugin.deny_hooks = utils.to_object(cfg.deny.hooks)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const e = cfg.deny_excludes
|
|
53
|
+
const e = cfg.deny_excludes
|
|
54
54
|
if (e && e.hooks) {
|
|
55
|
-
plugin.deny_exclude_hooks = utils.to_object(e.hooks)
|
|
55
|
+
plugin.deny_exclude_hooks = utils.to_object(e.hooks)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (e && e.plugins) {
|
|
59
|
-
plugin.deny_exclude_plugins = utils.to_object(e.plugins)
|
|
59
|
+
plugin.deny_exclude_plugins = utils.to_object(e.plugins)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (cfg.result_awards) {
|
|
63
|
-
plugin.preparse_result_awards()
|
|
63
|
+
plugin.preparse_result_awards()
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
if (!cfg.redis) cfg.redis = {}
|
|
66
|
+
if (!cfg.redis) cfg.redis = {}
|
|
67
67
|
if (!cfg.redis.host && cfg.redis.server_ip) {
|
|
68
|
-
cfg.redis.host = cfg.redis.server_ip
|
|
68
|
+
cfg.redis.host = cfg.redis.server_ip // backwards compat
|
|
69
69
|
}
|
|
70
70
|
if (!cfg.redis.port && cfg.redis.server_port) {
|
|
71
|
-
cfg.redis.port = cfg.redis.server_port
|
|
71
|
+
cfg.redis.port = cfg.redis.server_port // backwards compat
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
exports.results_init = function (next, connection) {
|
|
76
|
-
const plugin = this;
|
|
75
|
+
exports.results_init = async function (next, connection) {
|
|
77
76
|
|
|
78
|
-
if (
|
|
79
|
-
connection.logdebug(
|
|
80
|
-
return next()
|
|
77
|
+
if (this.should_we_skip(connection)) {
|
|
78
|
+
connection.logdebug(this, 'skipping')
|
|
79
|
+
return next()
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
if (connection.results.get('karma')) {
|
|
84
|
-
connection.logerror(
|
|
85
|
-
return next()
|
|
83
|
+
connection.logerror(this, 'this should never happen')
|
|
84
|
+
return next() // init once per connection
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
if (
|
|
87
|
+
if (this.cfg.awards) {
|
|
89
88
|
// todo is a list of connection/transaction awards to 'watch' for.
|
|
90
89
|
// When discovered, apply the awards value
|
|
91
|
-
const todo = {}
|
|
92
|
-
for (const key in
|
|
93
|
-
const award =
|
|
94
|
-
todo[key] = award
|
|
90
|
+
const todo = {}
|
|
91
|
+
for (const key in this.cfg.awards) {
|
|
92
|
+
const award = this.cfg.awards[key].toString()
|
|
93
|
+
todo[key] = award
|
|
95
94
|
}
|
|
96
|
-
connection.results.add(
|
|
95
|
+
connection.results.add(this, { score:0, todo })
|
|
97
96
|
}
|
|
98
97
|
else {
|
|
99
|
-
connection.results.add(
|
|
98
|
+
connection.results.add(this, { score:0 })
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
if (!connection.server.notes.redis) {
|
|
103
|
-
connection.logerror(
|
|
104
|
-
return next()
|
|
102
|
+
connection.logerror(this, 'karma requires the redis plugin')
|
|
103
|
+
return next()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.result_awards) return next() // not configured
|
|
107
|
+
|
|
108
|
+
if (connection.notes.redis) {
|
|
109
|
+
connection.logdebug(this, `redis already subscribed`)
|
|
110
|
+
return // another plugin has already called this.
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
connection.notes.redis = redis.createClient(this.redisCfg.pubsub)
|
|
114
|
+
await connection.notes.redis.connect()
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
plugin.check_result(connection, message);
|
|
113
|
-
});
|
|
114
|
-
next();
|
|
116
|
+
const pattern = this.get_redis_sub_channel(connection)
|
|
117
|
+
connection.notes.redis.pSubscribe(pattern, (message) => {
|
|
118
|
+
this.check_result(connection, message)
|
|
115
119
|
})
|
|
120
|
+
|
|
121
|
+
next()
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
exports.preparse_result_awards = function () {
|
|
119
|
-
|
|
120
|
-
if (!plugin.result_awards) plugin.result_awards = {};
|
|
125
|
+
if (!this.result_awards) this.result_awards = {}
|
|
121
126
|
|
|
127
|
+
const cra = this.cfg.result_awards
|
|
122
128
|
// arrange results for rapid traversal by check_result() :
|
|
123
129
|
// ex: karma.result_awards.clamd.fail = { .... }
|
|
124
|
-
Object.keys(
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
operator : parts[2],
|
|
138
|
-
value : parts[3],
|
|
139
|
-
award : parts[4],
|
|
140
|
-
reason : parts[5],
|
|
141
|
-
resolution : parts[6],
|
|
142
|
-
});
|
|
143
|
-
});
|
|
130
|
+
for (const anum of Object.keys(cra)) {
|
|
131
|
+
|
|
132
|
+
const [pi_name, prop, operator, value, award, reason, resolv]
|
|
133
|
+
= cra[anum].split(/(?:\s*\|\s*)/)
|
|
134
|
+
|
|
135
|
+
const ra = this.result_awards
|
|
136
|
+
|
|
137
|
+
if (!ra[pi_name]) ra[pi_name] = {}
|
|
138
|
+
|
|
139
|
+
if (!ra[pi_name][prop]) ra[pi_name][prop] = []
|
|
140
|
+
|
|
141
|
+
ra[pi_name][prop].push({ id: anum, operator, value, award, reason, resolv })
|
|
142
|
+
}
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
exports.check_result = function (connection, message) {
|
|
147
|
-
|
|
148
|
-
// connection.loginfo(
|
|
146
|
+
|
|
147
|
+
// connection.loginfo(this, message);
|
|
149
148
|
// {"plugin":"karma","result":{"fail":"spamassassin.hits"}}
|
|
150
149
|
// {"plugin":"geoip","result":{"country":"CN"}}
|
|
151
150
|
|
|
152
|
-
const m = JSON.parse(message)
|
|
151
|
+
const m = JSON.parse(message)
|
|
153
152
|
if (m && m.result && m.result.asn) {
|
|
154
|
-
|
|
153
|
+
this.check_result_asn(m.result.asn, connection)
|
|
155
154
|
}
|
|
156
|
-
if (!
|
|
155
|
+
if (!this.result_awards[m.plugin]) return // no awards for plugin
|
|
157
156
|
|
|
158
|
-
Object.keys(m.result)
|
|
159
|
-
if (r === 'emit') return
|
|
157
|
+
for (const r of Object.keys(m.result)) { // each result in mess
|
|
158
|
+
if (r === 'emit') return // r: pass, fail, skip, err, ...
|
|
160
159
|
|
|
161
|
-
const pi_prop =
|
|
162
|
-
if (!pi_prop) return
|
|
160
|
+
const pi_prop = this.result_awards[m.plugin][r]
|
|
161
|
+
if (!pi_prop) return // no award for this plugin property
|
|
163
162
|
|
|
164
|
-
const thisResult = m.result[r]
|
|
163
|
+
const thisResult = m.result[r]
|
|
165
164
|
// ignore empty arrays, objects, and strings
|
|
166
|
-
if (Array.isArray(thisResult) && thisResult.length === 0) return
|
|
165
|
+
if (Array.isArray(thisResult) && thisResult.length === 0) return
|
|
167
166
|
if (typeof thisResult === 'object' && !Object.keys(thisResult).length) {
|
|
168
|
-
return
|
|
167
|
+
return
|
|
169
168
|
}
|
|
170
|
-
if (typeof thisResult === 'string' && !thisResult) return
|
|
169
|
+
if (typeof thisResult === 'string' && !thisResult) return // empty
|
|
171
170
|
|
|
172
171
|
// do any award conditions match this result?
|
|
173
172
|
for (let i=0; i < pi_prop.length; i++) { // each award...
|
|
174
|
-
const thisAward = pi_prop[i]
|
|
173
|
+
const thisAward = pi_prop[i]
|
|
175
174
|
// { id: '011', operator: 'equals', value: 'all_bad', award: '-2'}
|
|
176
|
-
const thisResArr =
|
|
175
|
+
const thisResArr = this.result_as_array(thisResult)
|
|
177
176
|
switch (thisAward.operator) {
|
|
178
177
|
case 'eq':
|
|
179
178
|
case 'equal':
|
|
180
179
|
case 'equals':
|
|
181
|
-
|
|
182
|
-
break
|
|
180
|
+
this.check_result_equal(thisResArr, thisAward, connection)
|
|
181
|
+
break
|
|
183
182
|
case 'match':
|
|
184
|
-
|
|
185
|
-
break
|
|
183
|
+
this.check_result_match(thisResArr, thisAward, connection)
|
|
184
|
+
break
|
|
186
185
|
case 'lt':
|
|
187
|
-
|
|
188
|
-
break
|
|
186
|
+
this.check_result_lt(thisResArr, thisAward, connection)
|
|
187
|
+
break
|
|
189
188
|
case 'gt':
|
|
190
|
-
|
|
191
|
-
break
|
|
189
|
+
this.check_result_gt(thisResArr, thisAward, connection)
|
|
190
|
+
break
|
|
192
191
|
case 'length':
|
|
193
|
-
|
|
194
|
-
break
|
|
192
|
+
this.check_result_length(thisResArr, thisAward, connection)
|
|
193
|
+
break
|
|
195
194
|
}
|
|
196
195
|
}
|
|
197
|
-
}
|
|
196
|
+
}
|
|
198
197
|
}
|
|
199
198
|
|
|
200
199
|
exports.result_as_array = function (result) {
|
|
201
200
|
|
|
202
|
-
if (typeof result === 'string') return [result]
|
|
203
|
-
if (typeof result === 'number') return [result]
|
|
204
|
-
if (typeof result === 'boolean') return [result]
|
|
205
|
-
if (Array.isArray(result)) return result
|
|
201
|
+
if (typeof result === 'string') return [result]
|
|
202
|
+
if (typeof result === 'number') return [result]
|
|
203
|
+
if (typeof result === 'boolean') return [result]
|
|
204
|
+
if (Array.isArray(result)) return result
|
|
206
205
|
if (typeof result === 'object') {
|
|
207
|
-
const array = []
|
|
206
|
+
const array = []
|
|
208
207
|
Object.keys(result).forEach(tr => {
|
|
209
|
-
array.push(result[tr])
|
|
210
|
-
})
|
|
211
|
-
return array
|
|
208
|
+
array.push(result[tr])
|
|
209
|
+
})
|
|
210
|
+
return array
|
|
212
211
|
}
|
|
213
|
-
this.loginfo(`what format is result: ${result}`)
|
|
214
|
-
return result
|
|
212
|
+
this.loginfo(`what format is result: ${result}`)
|
|
213
|
+
return result
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
exports.check_result_asn = function (asn, conn) {
|
|
218
|
-
|
|
219
|
-
if (!
|
|
220
|
-
if (!plugin.cfg.asn_awards[asn]) return;
|
|
217
|
+
if (!this.cfg.asn_awards) return
|
|
218
|
+
if (!this.cfg.asn_awards[asn]) return
|
|
221
219
|
|
|
222
|
-
conn.results.incr(
|
|
223
|
-
conn.results.push(
|
|
220
|
+
conn.results.incr(this, {score: this.cfg.asn_awards[asn]})
|
|
221
|
+
conn.results.push(this, {fail: 'asn_awards'})
|
|
224
222
|
}
|
|
225
223
|
|
|
226
224
|
exports.check_result_lt = function (thisResult, thisAward, conn) {
|
|
227
|
-
const plugin = this;
|
|
228
225
|
|
|
229
226
|
for (let j=0; j < thisResult.length; j++) {
|
|
230
|
-
const tr = parseFloat(thisResult[j])
|
|
231
|
-
if (tr >= parseFloat(thisAward.value)) continue
|
|
232
|
-
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
227
|
+
const tr = parseFloat(thisResult[j])
|
|
228
|
+
if (tr >= parseFloat(thisAward.value)) continue
|
|
229
|
+
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
233
230
|
|
|
234
|
-
conn.results.incr(
|
|
235
|
-
conn.results.push(
|
|
231
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
232
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
236
233
|
}
|
|
237
234
|
}
|
|
238
235
|
|
|
239
236
|
exports.check_result_gt = function (thisResult, thisAward, conn) {
|
|
240
|
-
const plugin = this;
|
|
241
237
|
|
|
242
238
|
for (let j=0; j < thisResult.length; j++) {
|
|
243
|
-
const tr = parseFloat(thisResult[j])
|
|
244
|
-
if (tr <= parseFloat(thisAward.value)) continue
|
|
245
|
-
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
239
|
+
const tr = parseFloat(thisResult[j])
|
|
240
|
+
if (tr <= parseFloat(thisAward.value)) continue
|
|
241
|
+
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
246
242
|
|
|
247
|
-
conn.results.incr(
|
|
248
|
-
conn.results.push(
|
|
243
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
244
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
249
245
|
}
|
|
250
246
|
}
|
|
251
247
|
|
|
252
248
|
exports.check_result_equal = function (thisResult, thisAward, conn) {
|
|
253
|
-
const plugin = this;
|
|
254
249
|
|
|
255
250
|
for (let j=0; j < thisResult.length; j++) {
|
|
256
251
|
if (thisAward.value === 'true') {
|
|
257
|
-
if (!thisResult[j]) continue
|
|
252
|
+
if (!thisResult[j]) continue
|
|
258
253
|
}
|
|
259
254
|
else {
|
|
260
|
-
if (thisResult[j] != thisAward.value) continue
|
|
255
|
+
if (thisResult[j] != thisAward.value) continue
|
|
261
256
|
}
|
|
262
257
|
if (!/auth/.test(thisAward.plugin)) {
|
|
263
258
|
// only auth attempts are scored > 1x
|
|
264
|
-
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
259
|
+
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
265
260
|
}
|
|
266
261
|
|
|
267
|
-
conn.results.incr(
|
|
268
|
-
conn.results.push(
|
|
262
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
263
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
269
264
|
}
|
|
270
265
|
}
|
|
271
266
|
|
|
272
267
|
exports.check_result_match = function (thisResult, thisAward, conn) {
|
|
273
|
-
const
|
|
274
|
-
const re = new RegExp(thisAward.value, 'i');
|
|
268
|
+
const re = new RegExp(thisAward.value, 'i')
|
|
275
269
|
|
|
276
270
|
for (let i=0; i < thisResult.length; i++) {
|
|
277
|
-
if (!re.test(thisResult[i])) continue
|
|
278
|
-
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
271
|
+
if (!re.test(thisResult[i])) continue
|
|
272
|
+
if (conn.results.has('karma', 'awards', thisAward.id)) continue
|
|
279
273
|
|
|
280
|
-
conn.results.incr(
|
|
281
|
-
conn.results.push(
|
|
274
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
275
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
282
276
|
}
|
|
283
277
|
}
|
|
284
278
|
|
|
285
279
|
exports.check_result_length = function (thisResult, thisAward, conn) {
|
|
286
|
-
const plugin = this;
|
|
287
280
|
|
|
288
281
|
for (let j=0; j < thisResult.length; j++) {
|
|
289
|
-
const [operator, qty] = thisAward.value.split(/\s+/)
|
|
282
|
+
const [operator, qty] = thisAward.value.split(/\s+/) // requires node 6+
|
|
290
283
|
|
|
291
284
|
switch (operator) {
|
|
292
285
|
case 'eq':
|
|
293
286
|
case 'equal':
|
|
294
287
|
case 'equals':
|
|
295
|
-
if (parseInt(thisResult[j], 10) != parseInt(qty, 10)) continue
|
|
296
|
-
break
|
|
288
|
+
if (parseInt(thisResult[j], 10) != parseInt(qty, 10)) continue
|
|
289
|
+
break
|
|
297
290
|
case 'gt':
|
|
298
|
-
if (parseInt(thisResult[j], 10) <= parseInt(qty, 10)) continue
|
|
299
|
-
break
|
|
291
|
+
if (parseInt(thisResult[j], 10) <= parseInt(qty, 10)) continue
|
|
292
|
+
break
|
|
300
293
|
case 'lt':
|
|
301
|
-
if (parseInt(thisResult[j], 10) >= parseInt(qty, 10)) continue
|
|
302
|
-
break
|
|
294
|
+
if (parseInt(thisResult[j], 10) >= parseInt(qty, 10)) continue
|
|
295
|
+
break
|
|
303
296
|
default:
|
|
304
|
-
conn.results.add(
|
|
305
|
-
continue
|
|
297
|
+
conn.results.add(this, { err: `invalid operator: ${operator}` })
|
|
298
|
+
continue
|
|
306
299
|
}
|
|
307
300
|
|
|
308
|
-
conn.results.incr(
|
|
309
|
-
conn.results.push(
|
|
301
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
302
|
+
conn.results.push(this, {awards: thisAward.id })
|
|
310
303
|
}
|
|
311
304
|
}
|
|
312
305
|
|
|
313
306
|
exports.check_result_exists = function (thisResult, thisAward, conn) {
|
|
314
|
-
const plugin = this;
|
|
315
307
|
|
|
316
308
|
/* eslint-disable no-unused-vars */
|
|
317
309
|
for (const r of thisResult) {
|
|
318
|
-
const [operator, qty] = thisAward.value.split(/\s+/)
|
|
310
|
+
const [operator, qty] = thisAward.value.split(/\s+/)
|
|
319
311
|
|
|
320
312
|
switch (operator) {
|
|
321
313
|
case 'any':
|
|
322
314
|
case '':
|
|
323
|
-
break
|
|
315
|
+
break
|
|
324
316
|
default:
|
|
325
|
-
conn.results.add(
|
|
326
|
-
continue
|
|
317
|
+
conn.results.add(this, { err: `invalid operator: ${operator}` })
|
|
318
|
+
continue
|
|
327
319
|
}
|
|
328
320
|
|
|
329
|
-
conn.results.incr(
|
|
330
|
-
conn.results.push(
|
|
321
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
322
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
331
323
|
}
|
|
332
324
|
}
|
|
333
325
|
|
|
334
326
|
exports.apply_tarpit = function (connection, hook, score, next) {
|
|
335
|
-
|
|
336
|
-
if (!
|
|
327
|
+
|
|
328
|
+
if (!this.cfg.tarpit) return next() // tarpit disabled in config
|
|
337
329
|
|
|
338
330
|
// If tarpit is enabled on the reset_transaction hook, Haraka doesn't
|
|
339
331
|
// wait. Then bad things happen, like a Haraka crash.
|
|
340
|
-
if (utils.in_array(hook, ['reset_transaction','queue'])) return next()
|
|
332
|
+
if (utils.in_array(hook, ['reset_transaction','queue'])) return next()
|
|
341
333
|
|
|
342
334
|
// no delay for senders with good karma
|
|
343
|
-
const k = connection.results.get('karma')
|
|
344
|
-
if (score === undefined)
|
|
345
|
-
if (score >= 0)
|
|
335
|
+
const k = connection.results.get('karma')
|
|
336
|
+
if (score === undefined) score = parseFloat(k.score)
|
|
337
|
+
if (score >= 0) return next()
|
|
346
338
|
|
|
347
339
|
// how long to delay?
|
|
348
|
-
const delay =
|
|
349
|
-
if (!delay) return next()
|
|
340
|
+
const delay = this.tarpit_delay(score, connection, hook, k)
|
|
341
|
+
if (!delay) return next()
|
|
350
342
|
|
|
351
|
-
connection.logdebug(
|
|
343
|
+
connection.logdebug(this, `tarpitting ${hook} for ${delay}s`)
|
|
352
344
|
setTimeout(() => {
|
|
353
|
-
connection.logdebug(
|
|
354
|
-
next()
|
|
355
|
-
}, delay * 1000)
|
|
345
|
+
connection.logdebug(this, `tarpit ${hook} end`)
|
|
346
|
+
next()
|
|
347
|
+
}, delay * 1000)
|
|
356
348
|
}
|
|
357
349
|
|
|
358
350
|
exports.tarpit_delay = function (score, connection, hook, k) {
|
|
359
|
-
const plugin = this;
|
|
360
351
|
|
|
361
|
-
if (
|
|
362
|
-
connection.logdebug(
|
|
363
|
-
return parseFloat(
|
|
352
|
+
if (this.cfg.tarpit.delay && parseFloat(this.cfg.tarpit.delay)) {
|
|
353
|
+
connection.logdebug(this, 'static tarpit')
|
|
354
|
+
return parseFloat(this.cfg.tarpit.delay)
|
|
364
355
|
}
|
|
365
356
|
|
|
366
|
-
const delay = score * -1
|
|
357
|
+
const delay = score * -1 // progressive tarpit
|
|
367
358
|
|
|
368
359
|
// detect roaming users based on MSA ports that require auth
|
|
369
360
|
if (utils.in_array(connection.local.port, [587,465]) &&
|
|
370
361
|
utils.in_array(hook, ['ehlo','connect'])) {
|
|
371
|
-
return
|
|
362
|
+
return this.tarpit_delay_msa(connection, delay, k)
|
|
372
363
|
}
|
|
373
364
|
|
|
374
|
-
const max =
|
|
365
|
+
const max = this.cfg.tarpit.max || 5
|
|
375
366
|
if (delay > max) {
|
|
376
|
-
connection.logdebug(
|
|
377
|
-
return max
|
|
367
|
+
connection.logdebug(this, `tarpit capped to: ${max}`)
|
|
368
|
+
return max
|
|
378
369
|
}
|
|
379
370
|
|
|
380
|
-
return delay
|
|
371
|
+
return delay
|
|
381
372
|
}
|
|
382
373
|
|
|
383
374
|
exports.tarpit_delay_msa = function (connection, delay, k) {
|
|
384
|
-
const
|
|
385
|
-
const trg = 'tarpit reduced for good';
|
|
375
|
+
const trg = 'tarpit reduced for good'
|
|
386
376
|
|
|
387
|
-
delay = parseFloat(delay)
|
|
377
|
+
delay = parseFloat(delay)
|
|
388
378
|
|
|
389
379
|
// Reduce delay for good history
|
|
390
|
-
const history = ((k.good || 0) - (k.bad || 0))
|
|
380
|
+
const history = ((k.good || 0) - (k.bad || 0))
|
|
391
381
|
if (history > 0) {
|
|
392
|
-
delay = delay - 2
|
|
393
|
-
connection.logdebug(
|
|
382
|
+
delay = delay - 2
|
|
383
|
+
connection.logdebug(this, `${trg} history: ${delay}`)
|
|
394
384
|
}
|
|
395
385
|
|
|
396
386
|
// Reduce delay for good ASN history
|
|
397
|
-
let asn = connection.results.get('asn')
|
|
398
|
-
if (!asn)
|
|
387
|
+
let asn = connection.results.get('asn')
|
|
388
|
+
if (!asn) asn = connection.results.get('geoip')
|
|
399
389
|
if (asn && asn.asn && k.neighbors > 0) {
|
|
400
|
-
connection.logdebug(
|
|
401
|
-
delay = delay - 2
|
|
390
|
+
connection.logdebug(this, `${trg} neighbors: ${delay}`)
|
|
391
|
+
delay = delay - 2
|
|
402
392
|
}
|
|
403
393
|
|
|
404
|
-
const max =
|
|
394
|
+
const max = this.cfg.tarpit.max_msa || 2
|
|
405
395
|
if (delay > max) {
|
|
406
|
-
connection.logdebug(
|
|
407
|
-
delay = max
|
|
396
|
+
connection.logdebug(this, `tarpit capped at: ${delay}`)
|
|
397
|
+
delay = max
|
|
408
398
|
}
|
|
409
399
|
|
|
410
|
-
return delay
|
|
400
|
+
return delay
|
|
411
401
|
}
|
|
412
402
|
|
|
413
403
|
exports.should_we_skip = function (connection) {
|
|
414
|
-
if (connection.remote.is_private) return true
|
|
415
|
-
if (connection.notes.disable_karma) return true
|
|
416
|
-
return false
|
|
404
|
+
if (connection.remote.is_private) return true
|
|
405
|
+
if (connection.notes.disable_karma) return true
|
|
406
|
+
return false
|
|
417
407
|
}
|
|
418
408
|
|
|
419
409
|
exports.should_we_deny = function (next, connection, hook) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const r = connection.results.get('karma');
|
|
423
|
-
if (!r) return next();
|
|
410
|
+
const r = connection.results.get('karma')
|
|
411
|
+
if (!r) return next()
|
|
424
412
|
|
|
425
|
-
|
|
413
|
+
this.check_awards(connection) // update awards first
|
|
426
414
|
|
|
427
|
-
const score = parseFloat(r.score)
|
|
415
|
+
const score = parseFloat(r.score)
|
|
428
416
|
if (isNaN(score)) {
|
|
429
|
-
connection.logerror(
|
|
430
|
-
connection.results.add(
|
|
431
|
-
return next()
|
|
417
|
+
connection.logerror(this, 'score is NaN')
|
|
418
|
+
connection.results.add(this, {score: 0})
|
|
419
|
+
return next()
|
|
432
420
|
}
|
|
433
421
|
|
|
434
|
-
let negative_limit = -5
|
|
435
|
-
if (
|
|
436
|
-
negative_limit = parseFloat(
|
|
422
|
+
let negative_limit = -5
|
|
423
|
+
if (this.cfg.thresholds && this.cfg.thresholds.negative) {
|
|
424
|
+
negative_limit = parseFloat(this.cfg.thresholds.negative)
|
|
437
425
|
}
|
|
438
426
|
|
|
439
427
|
if (score > negative_limit) {
|
|
440
|
-
return
|
|
428
|
+
return this.apply_tarpit(connection, hook, score, next)
|
|
441
429
|
}
|
|
442
|
-
if (!
|
|
443
|
-
return
|
|
430
|
+
if (!this.deny_hooks[hook]) {
|
|
431
|
+
return this.apply_tarpit(connection, hook, score, next)
|
|
444
432
|
}
|
|
445
433
|
|
|
446
|
-
let rejectMsg = 'very bad karma score: {score}'
|
|
447
|
-
if (
|
|
448
|
-
rejectMsg =
|
|
434
|
+
let rejectMsg = 'very bad karma score: {score}'
|
|
435
|
+
if (this.cfg.deny && this.cfg.deny.message) {
|
|
436
|
+
rejectMsg = this.cfg.deny.message
|
|
449
437
|
}
|
|
450
438
|
|
|
451
439
|
if (/\{/.test(rejectMsg)) {
|
|
452
|
-
rejectMsg = rejectMsg.replace(/\{score\}/, score)
|
|
453
|
-
rejectMsg = rejectMsg.replace(/\{uuid\}/, connection.uuid)
|
|
440
|
+
rejectMsg = rejectMsg.replace(/\{score\}/, score)
|
|
441
|
+
rejectMsg = rejectMsg.replace(/\{uuid\}/, connection.uuid)
|
|
454
442
|
}
|
|
455
443
|
|
|
456
|
-
return
|
|
457
|
-
next(constants.DENY, rejectMsg)
|
|
458
|
-
})
|
|
444
|
+
return this.apply_tarpit(connection, hook, score, () => {
|
|
445
|
+
next(constants.DENY, rejectMsg)
|
|
446
|
+
})
|
|
459
447
|
}
|
|
460
448
|
|
|
461
449
|
exports.hook_deny = function (next, connection, params) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
450
|
+
if (this.should_we_skip(connection)) return next()
|
|
465
451
|
|
|
466
452
|
// let pi_deny = params[0]; // (constants.deny, denysoft, ok)
|
|
467
453
|
// let pi_message = params[1];
|
|
468
|
-
const pi_name = params[2]
|
|
454
|
+
const pi_name = params[2]
|
|
469
455
|
// let pi_function = params[3];
|
|
470
456
|
// let pi_params = params[4];
|
|
471
|
-
const pi_hook = params[5]
|
|
457
|
+
const pi_hook = params[5]
|
|
472
458
|
|
|
473
459
|
// exceptions, whose 'DENY' should not be captured
|
|
474
460
|
if (pi_name) {
|
|
475
|
-
if (pi_name === 'karma') return next()
|
|
476
|
-
if (
|
|
477
|
-
}
|
|
478
|
-
if (pi_hook && plugin.deny_exclude_hooks[pi_hook]) {
|
|
479
|
-
return next();
|
|
461
|
+
if (pi_name === 'karma') return next()
|
|
462
|
+
if (this.deny_exclude_plugins[pi_name]) return next()
|
|
480
463
|
}
|
|
464
|
+
if (pi_hook && this.deny_exclude_hooks[pi_hook]) return next()
|
|
481
465
|
|
|
482
|
-
if (!connection.results)
|
|
483
|
-
return next(constants.OK); // resume the connection
|
|
484
|
-
}
|
|
466
|
+
if (!connection.results) return next(constants.OK) // resume the connection
|
|
485
467
|
|
|
486
468
|
// intercept any other denials
|
|
487
|
-
connection.results.add(
|
|
488
|
-
connection.results.incr(
|
|
469
|
+
connection.results.add(this, { msg: `deny: ${pi_name}` })
|
|
470
|
+
connection.results.incr(this, { score: -2 })
|
|
489
471
|
|
|
490
|
-
next(constants.OK)
|
|
472
|
+
next(constants.OK) // resume the connection
|
|
491
473
|
}
|
|
492
474
|
|
|
493
475
|
exports.hook_connect = function (next, connection) {
|
|
494
|
-
|
|
476
|
+
if (this.should_we_skip(connection)) return next()
|
|
495
477
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const asnkey = plugin.get_asn_key(connection);
|
|
478
|
+
const asnkey = this.get_asn_key(connection)
|
|
499
479
|
if (asnkey) {
|
|
500
|
-
|
|
480
|
+
this.check_asn(connection, asnkey)
|
|
501
481
|
}
|
|
502
|
-
|
|
482
|
+
this.should_we_deny(next, connection, 'connect')
|
|
503
483
|
}
|
|
504
484
|
|
|
505
485
|
exports.hook_helo = function (next, connection) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
486
|
+
if (this.should_we_skip(connection)) return next()
|
|
509
487
|
|
|
510
|
-
|
|
488
|
+
this.should_we_deny(next, connection, 'helo')
|
|
511
489
|
}
|
|
512
490
|
|
|
513
491
|
exports.hook_ehlo = function (next, connection) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
492
|
+
if (this.should_we_skip(connection)) return next()
|
|
517
493
|
|
|
518
|
-
|
|
494
|
+
this.should_we_deny(next, connection, 'ehlo')
|
|
519
495
|
}
|
|
520
496
|
|
|
521
497
|
exports.hook_vrfy = function (next, connection) {
|
|
522
|
-
|
|
498
|
+
if (this.should_we_skip(connection)) return next()
|
|
523
499
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
plugin.should_we_deny(next, connection, 'vrfy');
|
|
500
|
+
this.should_we_deny(next, connection, 'vrfy')
|
|
527
501
|
}
|
|
528
502
|
|
|
529
503
|
exports.hook_noop = function (next, connection) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
504
|
+
if (this.should_we_skip(connection)) return next()
|
|
533
505
|
|
|
534
|
-
|
|
506
|
+
this.should_we_deny(next, connection, 'noop')
|
|
535
507
|
}
|
|
536
508
|
|
|
537
509
|
exports.hook_data = function (next, connection) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
510
|
+
if (this.should_we_skip(connection)) return next()
|
|
541
511
|
|
|
542
|
-
|
|
512
|
+
this.should_we_deny(next, connection, 'data')
|
|
543
513
|
}
|
|
544
514
|
|
|
545
515
|
exports.hook_queue = function (next, connection) {
|
|
546
|
-
|
|
516
|
+
if (this.should_we_skip(connection)) return next()
|
|
547
517
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
plugin.should_we_deny(next, connection, 'queue');
|
|
518
|
+
this.should_we_deny(next, connection, 'queue')
|
|
551
519
|
}
|
|
552
520
|
|
|
553
521
|
exports.hook_reset_transaction = function (next, connection) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
522
|
+
if (this.should_we_skip(connection)) return next()
|
|
557
523
|
|
|
558
|
-
connection.results.add(
|
|
559
|
-
|
|
524
|
+
connection.results.add(this, {emit: true})
|
|
525
|
+
this.should_we_deny(next, connection, 'reset_transaction')
|
|
560
526
|
}
|
|
561
527
|
|
|
562
528
|
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
563
|
-
const plugin = this;
|
|
564
529
|
|
|
565
|
-
if (
|
|
530
|
+
if (this.should_we_skip(connection)) return next()
|
|
566
531
|
|
|
567
532
|
// in case karma is in config/plugins before tls
|
|
568
|
-
if (params[0].toUpperCase() === 'STARTTLS') return next()
|
|
533
|
+
if (params[0].toUpperCase() === 'STARTTLS') return next()
|
|
569
534
|
|
|
570
535
|
// in case karma is in config/plugins before AUTH plugin(s)
|
|
571
|
-
if (connection.notes.authenticating) return next()
|
|
536
|
+
if (connection.notes.authenticating) return next()
|
|
572
537
|
|
|
573
|
-
connection.results.incr(
|
|
574
|
-
connection.results.add(
|
|
538
|
+
connection.results.incr(this, {score: -1})
|
|
539
|
+
connection.results.add(this, {fail: `cmd:(${params})`})
|
|
575
540
|
|
|
576
|
-
return
|
|
541
|
+
return this.should_we_deny(next, connection, 'unrecognized_command')
|
|
577
542
|
}
|
|
578
543
|
|
|
579
544
|
exports.ip_history_from_redis = function (next, connection) {
|
|
580
|
-
const plugin = this
|
|
545
|
+
const plugin = this
|
|
581
546
|
|
|
582
|
-
if (plugin.should_we_skip(connection)) return next()
|
|
547
|
+
if (plugin.should_we_skip(connection)) return next()
|
|
583
548
|
|
|
584
|
-
const expire = (plugin.cfg.redis.expire_days || 60) * 86400
|
|
585
|
-
const dbkey = `karma|${connection.remote.ip}
|
|
549
|
+
const expire = (plugin.cfg.redis.expire_days || 60) * 86400 // to days
|
|
550
|
+
const dbkey = `karma|${connection.remote.ip}`
|
|
586
551
|
|
|
587
552
|
// redis plugin is emitting errors, no need to here
|
|
588
|
-
if (!plugin.db) return next()
|
|
589
|
-
|
|
590
|
-
plugin.db.hgetall(dbkey, (err, dbr) => {
|
|
591
|
-
if (err) {
|
|
592
|
-
connection.results.add(plugin, {err: err});
|
|
593
|
-
return next();
|
|
594
|
-
}
|
|
553
|
+
if (!plugin.db) return next()
|
|
595
554
|
|
|
555
|
+
plugin.db.hGetAll(dbkey).then(dbr => {
|
|
596
556
|
if (dbr === null) {
|
|
597
|
-
plugin.init_ip(dbkey, connection.remote.ip, expire)
|
|
598
|
-
return next()
|
|
557
|
+
plugin.init_ip(dbkey, connection.remote.ip, expire)
|
|
558
|
+
return next()
|
|
599
559
|
}
|
|
600
560
|
|
|
601
561
|
plugin.db.multi()
|
|
602
|
-
.
|
|
562
|
+
.hIncrBy(dbkey, 'connections', 1) // increment total conn
|
|
603
563
|
.expire(dbkey, expire) // extend expiration
|
|
604
|
-
.exec(
|
|
605
|
-
|
|
606
|
-
|
|
564
|
+
.exec()
|
|
565
|
+
.then(replies => {
|
|
566
|
+
console.log(replies)
|
|
567
|
+
}).catch(err2 => {
|
|
568
|
+
connection.results.add(plugin, {err: err2})
|
|
569
|
+
})
|
|
607
570
|
|
|
608
571
|
const results = {
|
|
609
572
|
good: dbr.good,
|
|
@@ -615,407 +578,381 @@ exports.ip_history_from_redis = function (next, connection) {
|
|
|
615
578
|
|
|
616
579
|
// Careful: don't become self-fulfilling prophecy.
|
|
617
580
|
if (parseInt(dbr.good) > 5 && parseInt(dbr.bad) === 0) {
|
|
618
|
-
results.pass = 'all_good'
|
|
581
|
+
results.pass = 'all_good'
|
|
619
582
|
}
|
|
620
583
|
if (parseInt(dbr.bad) > 5 && parseInt(dbr.good) === 0) {
|
|
621
|
-
results.fail = 'all_bad'
|
|
584
|
+
results.fail = 'all_bad'
|
|
622
585
|
}
|
|
623
586
|
|
|
624
|
-
connection.results.add(plugin, results)
|
|
587
|
+
connection.results.add(plugin, results)
|
|
588
|
+
|
|
589
|
+
plugin.check_awards(connection)
|
|
590
|
+
return next()
|
|
591
|
+
})
|
|
592
|
+
.catch(err => {
|
|
593
|
+
connection.results.add(plugin, { err })
|
|
594
|
+
next()
|
|
595
|
+
})
|
|
625
596
|
|
|
626
|
-
plugin.check_awards(connection);
|
|
627
|
-
return next();
|
|
628
|
-
});
|
|
629
597
|
}
|
|
630
598
|
|
|
631
599
|
exports.hook_mail = function (next, connection, params) {
|
|
632
|
-
const plugin = this;
|
|
633
600
|
|
|
634
|
-
if (
|
|
601
|
+
if (this.should_we_skip(connection)) return next()
|
|
635
602
|
|
|
636
|
-
|
|
603
|
+
this.check_spammy_tld(params[0], connection)
|
|
637
604
|
|
|
638
605
|
// look for invalid (RFC 5321,(2)821) space in envelope from
|
|
639
|
-
const full_from = connection.current_line
|
|
606
|
+
const full_from = connection.current_line
|
|
640
607
|
if (full_from.toUpperCase().substring(0,11) !== 'MAIL FROM:<') {
|
|
641
|
-
connection.loginfo(
|
|
642
|
-
connection.results.add(
|
|
608
|
+
connection.loginfo(this, `RFC ignorant env addr format: ${full_from}`)
|
|
609
|
+
connection.results.add(this, {fail: 'rfc5321.MailFrom'})
|
|
643
610
|
}
|
|
644
611
|
|
|
645
612
|
// apply TLS awards (if defined)
|
|
646
|
-
if (
|
|
647
|
-
if (
|
|
648
|
-
connection.results.incr(
|
|
613
|
+
if (this.cfg.tls !== undefined) {
|
|
614
|
+
if (this.cfg.tls.set && connection.tls.enabled) {
|
|
615
|
+
connection.results.incr(this, {score: this.cfg.tls.set})
|
|
649
616
|
}
|
|
650
|
-
if (
|
|
651
|
-
connection.results.incr(
|
|
617
|
+
if (this.cfg.tls.unset && !connection.tls.enabled) {
|
|
618
|
+
connection.results.incr(this, {score: this.cfg.tls.unset})
|
|
652
619
|
}
|
|
653
620
|
}
|
|
654
621
|
|
|
655
|
-
return
|
|
622
|
+
return this.should_we_deny(next, connection, 'mail')
|
|
656
623
|
}
|
|
657
624
|
|
|
658
625
|
exports.hook_rcpt = function (next, connection, params) {
|
|
659
|
-
const plugin = this;
|
|
660
626
|
|
|
661
|
-
if (
|
|
627
|
+
if (this.should_we_skip(connection)) return next()
|
|
662
628
|
|
|
663
|
-
const rcpt = params[0]
|
|
629
|
+
const rcpt = params[0]
|
|
664
630
|
|
|
665
631
|
// hook_rcpt catches recipients that no rcpt_to plugin permitted
|
|
666
632
|
// hook_rcpt_ok catches accepted recipients
|
|
667
633
|
|
|
668
634
|
// odds of from_user=rcpt_user in ham: < 1%, in spam > 40%
|
|
669
635
|
// 2015-05 30-day sample: 84% spam correlation
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
connection.results.add(plugin, {fail: 'env_user_match'});
|
|
636
|
+
if (connection?.transaction?.mail_from?.user === rcpt.user) {
|
|
637
|
+
connection.results.add(this, {fail: 'env_user_match'})
|
|
673
638
|
}
|
|
674
639
|
|
|
675
|
-
|
|
640
|
+
this.check_syntax_RcptTo(connection)
|
|
676
641
|
|
|
677
|
-
connection.results.add(
|
|
642
|
+
connection.results.add(this, {fail: 'rcpt_to'})
|
|
678
643
|
|
|
679
|
-
return
|
|
644
|
+
return this.should_we_deny(next, connection, 'rcpt')
|
|
680
645
|
}
|
|
681
646
|
|
|
682
647
|
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
683
|
-
const plugin = this;
|
|
684
648
|
|
|
685
|
-
if (
|
|
649
|
+
if (this.should_we_skip(connection)) return next()
|
|
686
650
|
|
|
687
|
-
const txn = connection.transaction
|
|
651
|
+
const txn = connection.transaction
|
|
688
652
|
if (txn && txn.mail_from && txn.mail_from.user === rcpt.user) {
|
|
689
|
-
connection.results.add(
|
|
653
|
+
connection.results.add(this, {fail: 'env_user_match'})
|
|
690
654
|
}
|
|
691
655
|
|
|
692
|
-
|
|
656
|
+
this.check_syntax_RcptTo(connection)
|
|
693
657
|
|
|
694
|
-
return
|
|
658
|
+
return this.should_we_deny(next, connection, 'rcpt')
|
|
695
659
|
}
|
|
696
660
|
|
|
697
661
|
exports.hook_data_post = function (next, connection) {
|
|
698
662
|
// goal: prevent delivery of spam before queue
|
|
699
|
-
const plugin = this;
|
|
700
663
|
|
|
701
|
-
if (
|
|
664
|
+
if (this.should_we_skip(connection)) return next()
|
|
702
665
|
|
|
703
|
-
|
|
666
|
+
this.check_awards(connection) // update awards
|
|
704
667
|
|
|
705
|
-
const results = connection.results.collate(
|
|
706
|
-
connection.logdebug(
|
|
707
|
-
connection.transaction.remove_header('X-Haraka-Karma')
|
|
708
|
-
connection.transaction.add_header('X-Haraka-Karma', results)
|
|
668
|
+
const results = connection.results.collate(this)
|
|
669
|
+
connection.logdebug(this, `adding header: ${results}`)
|
|
670
|
+
connection.transaction.remove_header('X-Haraka-Karma')
|
|
671
|
+
connection.transaction.add_header('X-Haraka-Karma', results)
|
|
709
672
|
|
|
710
|
-
return
|
|
673
|
+
return this.should_we_deny(next, connection, 'data_post')
|
|
711
674
|
}
|
|
712
675
|
|
|
713
676
|
exports.increment = function (connection, key, val) {
|
|
714
|
-
const plugin = this
|
|
715
|
-
if (!plugin.db) return
|
|
677
|
+
const plugin = this
|
|
678
|
+
if (!plugin.db) return
|
|
716
679
|
|
|
717
|
-
plugin.db.
|
|
680
|
+
plugin.db.hIncrBy(`karma|${connection.remote.ip}`, key, 1)
|
|
718
681
|
|
|
719
|
-
const asnkey = plugin.get_asn_key(connection)
|
|
720
|
-
if (asnkey) plugin.db.
|
|
682
|
+
const asnkey = plugin.get_asn_key(connection)
|
|
683
|
+
if (asnkey) plugin.db.hIncrBy(asnkey, key, 1)
|
|
721
684
|
}
|
|
722
685
|
|
|
723
686
|
exports.hook_disconnect = function (next, connection) {
|
|
724
|
-
const plugin = this
|
|
687
|
+
const plugin = this
|
|
725
688
|
|
|
726
|
-
plugin.redis_unsubscribe(connection)
|
|
689
|
+
plugin.redis_unsubscribe(connection)
|
|
727
690
|
|
|
728
|
-
if (plugin.should_we_skip(connection)) return next()
|
|
691
|
+
if (plugin.should_we_skip(connection)) return next()
|
|
729
692
|
|
|
730
|
-
const k = connection.results.get('karma')
|
|
693
|
+
const k = connection.results.get('karma')
|
|
731
694
|
if (!k || k.score === undefined) {
|
|
732
|
-
connection.results.add(plugin, {err: 'karma results missing'})
|
|
733
|
-
return next()
|
|
695
|
+
connection.results.add(plugin, {err: 'karma results missing'})
|
|
696
|
+
return next()
|
|
734
697
|
}
|
|
735
698
|
|
|
736
699
|
if (!plugin.cfg.thresholds) {
|
|
737
|
-
plugin.check_awards(connection)
|
|
738
|
-
connection.results.add(plugin, {msg: 'no action', emit: true })
|
|
739
|
-
return next()
|
|
700
|
+
plugin.check_awards(connection)
|
|
701
|
+
connection.results.add(plugin, {msg: 'no action', emit: true })
|
|
702
|
+
return next()
|
|
740
703
|
}
|
|
741
704
|
|
|
742
705
|
if (k.score > (plugin.cfg.thresholds.positive || 3)) {
|
|
743
|
-
plugin.increment(connection, 'good', 1)
|
|
706
|
+
plugin.increment(connection, 'good', 1)
|
|
744
707
|
}
|
|
745
708
|
if (k.score < 0) {
|
|
746
|
-
plugin.increment(connection, 'bad', 1)
|
|
709
|
+
plugin.increment(connection, 'bad', 1)
|
|
747
710
|
}
|
|
748
711
|
|
|
749
|
-
connection.results.add(plugin, {emit: true })
|
|
750
|
-
return next()
|
|
712
|
+
connection.results.add(plugin, {emit: true })
|
|
713
|
+
return next()
|
|
751
714
|
}
|
|
752
715
|
|
|
753
716
|
exports.get_award_loc_from_note = function (connection, award) {
|
|
754
|
-
const plugin = this;
|
|
755
717
|
|
|
756
718
|
if (connection.transaction) {
|
|
757
|
-
const obj =
|
|
758
|
-
if (obj)
|
|
719
|
+
const obj = this.assemble_note_obj(connection.transaction, award)
|
|
720
|
+
if (obj) return obj
|
|
759
721
|
}
|
|
760
722
|
|
|
761
|
-
// connection.logdebug(
|
|
762
|
-
const obj =
|
|
763
|
-
if (obj) return obj
|
|
723
|
+
// connection.logdebug(this, `no txn note: ${award}`);
|
|
724
|
+
const obj = this.assemble_note_obj(connection, award)
|
|
725
|
+
if (obj) return obj
|
|
764
726
|
|
|
765
|
-
// connection.logdebug(
|
|
766
|
-
return
|
|
727
|
+
// connection.logdebug(this, `no conn note: ${award}`);
|
|
728
|
+
return
|
|
767
729
|
}
|
|
768
730
|
|
|
769
731
|
exports.get_award_loc_from_results = function (connection, loc_bits) {
|
|
770
732
|
|
|
771
|
-
let pi_name = loc_bits[1]
|
|
772
|
-
let notekey = loc_bits[2]
|
|
733
|
+
let pi_name = loc_bits[1]
|
|
734
|
+
let notekey = loc_bits[2]
|
|
773
735
|
|
|
774
736
|
if (phase_prefixes[pi_name]) {
|
|
775
|
-
pi_name = `${loc_bits[1]}.${loc_bits[2]}
|
|
776
|
-
notekey = loc_bits[3]
|
|
737
|
+
pi_name = `${loc_bits[1]}.${loc_bits[2]}`
|
|
738
|
+
notekey = loc_bits[3]
|
|
777
739
|
}
|
|
778
740
|
|
|
779
|
-
let obj
|
|
780
|
-
if (connection.transaction)
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
if (!obj)
|
|
784
|
-
|
|
785
|
-
obj = connection.results.get(pi_name);
|
|
786
|
-
}
|
|
787
|
-
if (!obj) {
|
|
788
|
-
// connection.logdebug(plugin, `no conn results: ${pi_name}`);
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
741
|
+
let obj
|
|
742
|
+
if (connection.transaction) obj = connection.transaction.results.get(pi_name)
|
|
743
|
+
|
|
744
|
+
// connection.logdebug(plugin, `no txn results: ${pi_name}`);
|
|
745
|
+
if (!obj) obj = connection.results.get(pi_name)
|
|
746
|
+
if (!obj) return
|
|
791
747
|
|
|
792
748
|
// connection.logdebug(plugin, `found results for ${pi_name}, ${notekey}`);
|
|
793
|
-
if (notekey) return obj[notekey]
|
|
794
|
-
return obj
|
|
749
|
+
if (notekey) return obj[notekey]
|
|
750
|
+
return obj
|
|
795
751
|
}
|
|
796
752
|
|
|
797
753
|
exports.get_award_location = function (connection, award_key) {
|
|
798
754
|
// based on award key, find the requested note or result
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
if (loc_bits.length === 1) { // ex: relaying
|
|
803
|
-
return connection[bits[0]];
|
|
804
|
-
}
|
|
755
|
+
const bits = award_key.split('@')
|
|
756
|
+
const loc_bits = bits[0].split('.')
|
|
757
|
+
if (loc_bits.length === 1) return connection[bits[0]] // ex: relaying
|
|
805
758
|
|
|
806
759
|
if (loc_bits[0] === 'notes') { // ex: notes.spf_mail_helo
|
|
807
|
-
return
|
|
760
|
+
return this.get_award_loc_from_note(connection, bits[0])
|
|
808
761
|
}
|
|
809
762
|
|
|
810
|
-
if (loc_bits[0] === 'results') {
|
|
811
|
-
return
|
|
763
|
+
if (loc_bits[0] === 'results') { // ex: results.geoip.distance
|
|
764
|
+
return this.get_award_loc_from_results(connection, loc_bits)
|
|
812
765
|
}
|
|
813
766
|
|
|
814
767
|
// ex: transaction.results.spf
|
|
815
|
-
if (connection.transaction &&
|
|
816
|
-
loc_bits
|
|
817
|
-
|
|
818
|
-
loc_bits.shift();
|
|
819
|
-
return plugin.get_award_loc_from_results(connection.transaction, loc_bits);
|
|
768
|
+
if (connection.transaction && loc_bits[0] === 'transaction' && loc_bits[1] === 'results') {
|
|
769
|
+
loc_bits.shift()
|
|
770
|
+
return this.get_award_loc_from_results(connection.transaction, loc_bits)
|
|
820
771
|
}
|
|
821
772
|
|
|
822
|
-
connection.logdebug(
|
|
773
|
+
connection.logdebug(this, `unknown location for ${award_key}`)
|
|
823
774
|
}
|
|
824
775
|
|
|
825
776
|
exports.get_award_condition = function (note_key, note_val) {
|
|
826
|
-
let wants
|
|
827
|
-
const keybits = note_key.split('@')
|
|
828
|
-
if (keybits[1]) { wants = keybits[1]
|
|
777
|
+
let wants
|
|
778
|
+
const keybits = note_key.split('@')
|
|
779
|
+
if (keybits[1]) { wants = keybits[1] }
|
|
829
780
|
|
|
830
|
-
const valbits = note_val.split(/\s+/)
|
|
831
|
-
if (!valbits[1])
|
|
832
|
-
if (valbits[1] !== 'if')
|
|
781
|
+
const valbits = note_val.split(/\s+/)
|
|
782
|
+
if (!valbits[1]) return wants
|
|
783
|
+
if (valbits[1] !== 'if') return wants // no if condition
|
|
833
784
|
|
|
834
785
|
if (valbits[2].match(/^(equals|gt|lt|match)$/)) {
|
|
835
|
-
if (valbits[3])
|
|
786
|
+
if (valbits[3]) wants = valbits[3]
|
|
836
787
|
}
|
|
837
|
-
return wants
|
|
788
|
+
return wants
|
|
838
789
|
}
|
|
839
790
|
|
|
840
791
|
exports.check_awards = function (connection) {
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
if (!karma ) return;
|
|
844
|
-
if (!karma.todo) return;
|
|
792
|
+
const karma = connection.results.get('karma')
|
|
793
|
+
if (!karma?.todo) return
|
|
845
794
|
|
|
846
795
|
for (const key in karma.todo) {
|
|
847
796
|
// loc = terms
|
|
848
797
|
// note_location [@wants] = award [conditions]
|
|
849
798
|
// results.geoip.too_far = -1
|
|
850
799
|
// results.geoip.distance@4000 = -1 if gt 4000
|
|
851
|
-
const award_terms = karma.todo[key]
|
|
800
|
+
const award_terms = karma.todo[key]
|
|
852
801
|
|
|
853
|
-
const note =
|
|
854
|
-
if (note === undefined)
|
|
855
|
-
let wants =
|
|
802
|
+
const note = this.get_award_location(connection, key)
|
|
803
|
+
if (note === undefined) continue
|
|
804
|
+
let wants = this.get_award_condition(key, award_terms)
|
|
856
805
|
|
|
857
806
|
// test the desired condition
|
|
858
|
-
const bits = award_terms.split(/\s+/)
|
|
859
|
-
const award = parseFloat(bits[0])
|
|
807
|
+
const bits = award_terms.split(/\s+/)
|
|
808
|
+
const award = parseFloat(bits[0])
|
|
860
809
|
if (!bits[1] || bits[1] !== 'if') { // no if conditions
|
|
861
|
-
if (!note)
|
|
810
|
+
if (!note) continue // failed truth test
|
|
862
811
|
if (!wants) { // no wants, truth matches
|
|
863
|
-
|
|
864
|
-
delete karma.todo[key]
|
|
865
|
-
continue
|
|
812
|
+
this.apply_award(connection, key, award)
|
|
813
|
+
delete karma.todo[key]
|
|
814
|
+
continue
|
|
866
815
|
}
|
|
867
|
-
if (note !== wants)
|
|
816
|
+
if (note !== wants) continue // didn't match
|
|
868
817
|
}
|
|
869
818
|
|
|
870
|
-
// connection.loginfo(
|
|
819
|
+
// connection.loginfo(this, `check_awards, case matching for: ${wants}`
|
|
871
820
|
|
|
872
821
|
// the matching logic here is inverted, weeding out misses (continue)
|
|
873
822
|
// Matches fall through (break) to the apply_award below.
|
|
874
|
-
const condition = bits[2]
|
|
823
|
+
const condition = bits[2]
|
|
875
824
|
switch (condition) {
|
|
876
825
|
case 'equals':
|
|
877
|
-
if (wants != note) continue
|
|
878
|
-
break
|
|
826
|
+
if (wants != note) continue
|
|
827
|
+
break
|
|
879
828
|
case 'gt':
|
|
880
|
-
if (parseFloat(note) <= parseFloat(wants))
|
|
881
|
-
break
|
|
829
|
+
if (parseFloat(note) <= parseFloat(wants)) continue
|
|
830
|
+
break
|
|
882
831
|
case 'lt':
|
|
883
|
-
if (parseFloat(note) >= parseFloat(wants))
|
|
884
|
-
break
|
|
832
|
+
if (parseFloat(note) >= parseFloat(wants)) continue
|
|
833
|
+
break
|
|
885
834
|
case 'match':
|
|
886
835
|
if (Array.isArray(note)) {
|
|
887
|
-
// connection.logerror(
|
|
888
|
-
if (new RegExp(wants, 'i').test(note))
|
|
836
|
+
// connection.logerror(this, 'matching an array');
|
|
837
|
+
if (new RegExp(wants, 'i').test(note)) break
|
|
889
838
|
}
|
|
890
|
-
if (note.toString().match(new RegExp(wants, 'i')))
|
|
891
|
-
continue
|
|
839
|
+
if (note.toString().match(new RegExp(wants, 'i'))) break
|
|
840
|
+
continue
|
|
892
841
|
case 'length': {
|
|
893
|
-
const operator = bits[3]
|
|
894
|
-
if (bits[4]) { wants = bits[4]
|
|
842
|
+
const operator = bits[3]
|
|
843
|
+
if (bits[4]) { wants = bits[4] }
|
|
895
844
|
switch (operator) {
|
|
896
845
|
case 'gt':
|
|
897
|
-
if (note.length <= parseFloat(wants))
|
|
898
|
-
break
|
|
846
|
+
if (note.length <= parseFloat(wants)) continue
|
|
847
|
+
break
|
|
899
848
|
case 'lt':
|
|
900
|
-
if (note.length >= parseFloat(wants))
|
|
901
|
-
break
|
|
849
|
+
if (note.length >= parseFloat(wants)) continue
|
|
850
|
+
break
|
|
902
851
|
case 'equals':
|
|
903
|
-
if (note.length !== parseFloat(wants))
|
|
904
|
-
break
|
|
852
|
+
if (note.length !== parseFloat(wants)) continue
|
|
853
|
+
break
|
|
905
854
|
default:
|
|
906
|
-
connection.logerror(
|
|
907
|
-
continue
|
|
855
|
+
connection.logerror(this, `length operator "${operator}" not supported.`)
|
|
856
|
+
continue
|
|
908
857
|
}
|
|
909
|
-
break
|
|
858
|
+
break
|
|
910
859
|
}
|
|
911
860
|
case 'in': // if in pass whitelisted
|
|
912
861
|
// let list = bits[3];
|
|
913
|
-
if (bits[4]) { wants = bits[4]
|
|
914
|
-
if (!Array.isArray(note))
|
|
915
|
-
if (!wants)
|
|
916
|
-
if (note.indexOf(wants) !== -1)
|
|
917
|
-
continue
|
|
862
|
+
if (bits[4]) { wants = bits[4] }
|
|
863
|
+
if (!Array.isArray(note)) continue
|
|
864
|
+
if (!wants) continue
|
|
865
|
+
if (note.indexOf(wants) !== -1) break // found!
|
|
866
|
+
continue
|
|
918
867
|
default:
|
|
919
|
-
continue
|
|
868
|
+
continue
|
|
920
869
|
}
|
|
921
|
-
|
|
922
|
-
delete karma.todo[key]
|
|
870
|
+
this.apply_award(connection, key, award)
|
|
871
|
+
delete karma.todo[key]
|
|
923
872
|
}
|
|
924
873
|
}
|
|
925
874
|
|
|
926
875
|
exports.apply_award = function (connection, nl, award) {
|
|
927
|
-
|
|
928
|
-
if (!award) { return; }
|
|
876
|
+
if (!award) return
|
|
929
877
|
if (isNaN(award)) { // garbage in config
|
|
930
|
-
connection.logerror(
|
|
931
|
-
return
|
|
878
|
+
connection.logerror(this, `non-numeric award from: ${nl}:${award}`)
|
|
879
|
+
return
|
|
932
880
|
}
|
|
933
881
|
|
|
934
|
-
const bits = nl.split('@'); nl = bits[0]
|
|
882
|
+
const bits = nl.split('@'); nl = bits[0] // strip off @... if present
|
|
935
883
|
|
|
936
|
-
connection.results.incr(
|
|
937
|
-
connection.logdebug(
|
|
884
|
+
connection.results.incr(this, {score: award})
|
|
885
|
+
connection.logdebug(this, `applied ${nl}:${award}`)
|
|
938
886
|
|
|
939
887
|
let trimmed = nl.substring(0, 5) === 'notes' ? nl.substring(6) :
|
|
940
888
|
nl.substring(0, 7) === 'results' ? nl.substring(8) :
|
|
941
889
|
nl.substring(0,19) === 'transaction.results' ?
|
|
942
|
-
nl.substring(20) : nl
|
|
890
|
+
nl.substring(20) : nl
|
|
943
891
|
|
|
944
|
-
if (trimmed.substring(0,7) === 'rcpt_to') trimmed = trimmed.substring(8)
|
|
945
|
-
if (trimmed.substring(0,7) === 'mail_from') trimmed = trimmed.substring(10)
|
|
946
|
-
if (trimmed.substring(0,7) === 'connect') trimmed = trimmed.substring(8)
|
|
947
|
-
if (trimmed.substring(0,4) === 'data') trimmed = trimmed.substring(5)
|
|
892
|
+
if (trimmed.substring(0,7) === 'rcpt_to') trimmed = trimmed.substring(8)
|
|
893
|
+
if (trimmed.substring(0,7) === 'mail_from') trimmed = trimmed.substring(10)
|
|
894
|
+
if (trimmed.substring(0,7) === 'connect') trimmed = trimmed.substring(8)
|
|
895
|
+
if (trimmed.substring(0,4) === 'data') trimmed = trimmed.substring(5)
|
|
948
896
|
|
|
949
|
-
if (award > 0)
|
|
950
|
-
if (award < 0)
|
|
897
|
+
if (award > 0) connection.results.add(this, { pass: trimmed })
|
|
898
|
+
if (award < 0) connection.results.add(this, { fail: trimmed })
|
|
951
899
|
}
|
|
952
900
|
|
|
953
901
|
exports.check_spammy_tld = function (mail_from, connection) {
|
|
954
|
-
|
|
955
|
-
if (
|
|
956
|
-
if (mail_from.isNull()) return; // null sender (bounce)
|
|
902
|
+
if (!this.cfg.spammy_tlds) return
|
|
903
|
+
if (mail_from.isNull()) return // null sender (bounce)
|
|
957
904
|
|
|
958
|
-
const from_tld = mail_from.host.split('.').pop()
|
|
959
|
-
// connection.logdebug(
|
|
905
|
+
const from_tld = mail_from.host.split('.').pop()
|
|
906
|
+
// connection.logdebug(this, `from_tld: ${from_tld}`);
|
|
960
907
|
|
|
961
|
-
const tld_penalty = parseFloat(
|
|
962
|
-
if (tld_penalty === 0) return
|
|
908
|
+
const tld_penalty = parseFloat(this.cfg.spammy_tlds[from_tld] || 0)
|
|
909
|
+
if (tld_penalty === 0) return
|
|
963
910
|
|
|
964
|
-
connection.results.incr(
|
|
965
|
-
connection.results.add(
|
|
911
|
+
connection.results.incr(this, {score: tld_penalty})
|
|
912
|
+
connection.results.add(this, {fail: 'spammy.TLD'})
|
|
966
913
|
}
|
|
967
914
|
|
|
968
915
|
exports.check_syntax_RcptTo = function (connection) {
|
|
969
|
-
const plugin = this;
|
|
970
|
-
|
|
971
916
|
// look for an illegal (RFC 5321,(2)821) space in envelope recipient
|
|
972
|
-
const full_rcpt = connection.current_line
|
|
973
|
-
if (full_rcpt.toUpperCase().substring(0,9) === 'RCPT TO:<')
|
|
917
|
+
const full_rcpt = connection.current_line
|
|
918
|
+
if (full_rcpt.toUpperCase().substring(0,9) === 'RCPT TO:<') return
|
|
974
919
|
|
|
975
|
-
connection.loginfo(
|
|
976
|
-
connection.results.add(
|
|
920
|
+
connection.loginfo(this, `illegal envelope address format: ${full_rcpt}`)
|
|
921
|
+
connection.results.add(this, {fail: 'rfc5321.RcptTo'})
|
|
977
922
|
}
|
|
978
923
|
|
|
979
924
|
exports.assemble_note_obj = function (prefix, key) {
|
|
980
|
-
let note = prefix
|
|
981
|
-
const parts = key.split('.')
|
|
925
|
+
let note = prefix
|
|
926
|
+
const parts = key.split('.')
|
|
982
927
|
while (parts.length > 0) {
|
|
983
|
-
let next = parts.shift()
|
|
928
|
+
let next = parts.shift()
|
|
984
929
|
if (phase_prefixes[next]) {
|
|
985
|
-
next = `${next}.${parts.shift()}
|
|
930
|
+
next = `${next}.${parts.shift()}`
|
|
986
931
|
}
|
|
987
|
-
note = note[next]
|
|
988
|
-
if (note === null || note === undefined)
|
|
932
|
+
note = note[next]
|
|
933
|
+
if (note === null || note === undefined) break
|
|
989
934
|
}
|
|
990
|
-
return note
|
|
935
|
+
return note
|
|
991
936
|
}
|
|
992
937
|
|
|
993
938
|
exports.check_asn = function (connection, asnkey) {
|
|
994
|
-
|
|
995
|
-
if (!plugin.db) return;
|
|
939
|
+
if (!this.db) return
|
|
996
940
|
|
|
997
|
-
const report_as = { name:
|
|
941
|
+
const report_as = { name: this.name }
|
|
998
942
|
|
|
999
|
-
if (
|
|
1000
|
-
report_as.name = plugin.cfg.asn.report_as;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
plugin.db.hgetall(asnkey, (err, res) => {
|
|
1004
|
-
if (err) {
|
|
1005
|
-
connection.results.add(plugin, {err: err});
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
943
|
+
if (this.cfg.asn.report_as) report_as.name = this.cfg.asn.report_as
|
|
1008
944
|
|
|
945
|
+
this.db.hGetAll(asnkey).then(res => {
|
|
1009
946
|
if (res === null) {
|
|
1010
|
-
const expire = (
|
|
1011
|
-
|
|
1012
|
-
return
|
|
947
|
+
const expire = (this.cfg.redis.expire_days || 60) * 86400 // days
|
|
948
|
+
this.init_asn(asnkey, expire)
|
|
949
|
+
return
|
|
1013
950
|
}
|
|
1014
951
|
|
|
1015
|
-
|
|
1016
|
-
const asn_score = parseInt(res.good || 0) - (res.bad || 0)
|
|
952
|
+
this.db.hIncrBy(asnkey, 'connections', 1)
|
|
953
|
+
const asn_score = parseInt(res.good || 0) - (res.bad || 0)
|
|
1017
954
|
const asn_results = {
|
|
1018
|
-
asn_score
|
|
955
|
+
asn_score,
|
|
1019
956
|
asn_connections: res.connections,
|
|
1020
957
|
asn_good: res.good,
|
|
1021
958
|
asn_bad: res.bad,
|
|
@@ -1024,49 +961,48 @@ exports.check_asn = function (connection, asnkey) {
|
|
|
1024
961
|
|
|
1025
962
|
if (asn_score) {
|
|
1026
963
|
if (asn_score < -5) {
|
|
1027
|
-
asn_results.fail = 'asn:history'
|
|
964
|
+
asn_results.fail = 'asn:history'
|
|
1028
965
|
}
|
|
1029
966
|
else if (asn_score > 5) {
|
|
1030
|
-
asn_results.pass = 'asn:history'
|
|
967
|
+
asn_results.pass = 'asn:history'
|
|
1031
968
|
}
|
|
1032
969
|
}
|
|
1033
970
|
|
|
1034
971
|
if (parseInt(res.bad) > 5 && parseInt(res.good) === 0) {
|
|
1035
|
-
asn_results.fail = 'asn:all_bad'
|
|
972
|
+
asn_results.fail = 'asn:all_bad'
|
|
1036
973
|
}
|
|
1037
974
|
if (parseInt(res.good) > 5 && parseInt(res.bad) === 0) {
|
|
1038
|
-
asn_results.pass = 'asn:all_good'
|
|
975
|
+
asn_results.pass = 'asn:all_good'
|
|
1039
976
|
}
|
|
1040
977
|
|
|
1041
|
-
connection.results.add(report_as, asn_results)
|
|
1042
|
-
})
|
|
978
|
+
connection.results.add(report_as, asn_results)
|
|
979
|
+
})
|
|
980
|
+
.catch(err => {
|
|
981
|
+
connection.results.add(this, { err })
|
|
982
|
+
})
|
|
1043
983
|
}
|
|
1044
984
|
|
|
1045
985
|
exports.init_ip = function (dbkey, rip, expire) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
.hmset(dbkey, {'bad': 0, 'good': 0, 'connections': 1})
|
|
986
|
+
if (!this.db) return
|
|
987
|
+
this.db.multi()
|
|
988
|
+
.hmSet(dbkey, {'bad': 0, 'good': 0, 'connections': 1})
|
|
1050
989
|
.expire(dbkey, expire)
|
|
1051
|
-
.exec()
|
|
990
|
+
.exec()
|
|
1052
991
|
}
|
|
1053
992
|
|
|
1054
993
|
exports.get_asn_key = function (connection) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (!asn || !asn.asn)
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
if (!asn || !asn.asn || isNaN(asn.asn)) { return; }
|
|
1062
|
-
return `as${asn.asn}`;
|
|
994
|
+
if (!this.cfg.asn.enable) return
|
|
995
|
+
let asn = connection.results.get('asn')
|
|
996
|
+
if (!asn || !asn.asn) asn = connection.results.get('geoip')
|
|
997
|
+
if (!asn || !asn.asn || isNaN(asn.asn)) return
|
|
998
|
+
return `as${asn.asn}`
|
|
1063
999
|
}
|
|
1064
1000
|
|
|
1065
1001
|
exports.init_asn = function (asnkey, expire) {
|
|
1066
|
-
const plugin = this
|
|
1067
|
-
if (!plugin.db) return
|
|
1002
|
+
const plugin = this
|
|
1003
|
+
if (!plugin.db) return
|
|
1068
1004
|
plugin.db.multi()
|
|
1069
|
-
.
|
|
1005
|
+
.hmSet(asnkey, {'bad': 0, 'good': 0, 'connections': 1})
|
|
1070
1006
|
.expire(asnkey, expire * 2) // keep ASN longer
|
|
1071
|
-
.exec()
|
|
1007
|
+
.exec()
|
|
1072
1008
|
}
|