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