haraka-plugin-karma 1.0.12 → 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/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/workflows/ci.yml +48 -0
- package/.github/workflows/publish.yml +29 -0
- package/Changes.md +26 -0
- package/README.md +3 -10
- package/config/karma.ini +1 -1
- package/index.js +456 -517
- package/package.json +8 -8
- package/test/karma.js +680 -648
- package/.travis.yml +0 -21
- package/appveyor.yml +0 -20
- package/run_tests +0 -33
package/index.js
CHANGED
|
@@ -1,591 +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
|
-
|
|
18
|
-
['unrecognized_command','helo','data','data_post','queue']
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
this.deny_hooks = utils.to_object(
|
|
17
|
+
['unrecognized_command','helo','data','data_post','queue','queue_outbound']
|
|
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(
|
|
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
|
-
|
|
290
|
-
const matches = thisAward.value.split(/\s+/);
|
|
291
|
-
const operator = matches[0];
|
|
292
|
-
const qty = matches[1];
|
|
273
|
+
const [operator, qty] = thisAward.value.split(/\s+/) // requires node 6+
|
|
293
274
|
|
|
294
275
|
switch (operator) {
|
|
295
276
|
case 'eq':
|
|
296
277
|
case 'equal':
|
|
297
278
|
case 'equals':
|
|
298
|
-
if (parseInt(thisResult[j], 10) != parseInt(qty, 10)) continue
|
|
299
|
-
break
|
|
279
|
+
if (parseInt(thisResult[j], 10) != parseInt(qty, 10)) continue
|
|
280
|
+
break
|
|
300
281
|
case 'gt':
|
|
301
|
-
if (parseInt(thisResult[j], 10) <= parseInt(qty, 10)) continue
|
|
302
|
-
break
|
|
282
|
+
if (parseInt(thisResult[j], 10) <= parseInt(qty, 10)) continue
|
|
283
|
+
break
|
|
303
284
|
case 'lt':
|
|
304
|
-
if (parseInt(thisResult[j], 10) >= parseInt(qty, 10)) continue
|
|
305
|
-
break
|
|
285
|
+
if (parseInt(thisResult[j], 10) >= parseInt(qty, 10)) continue
|
|
286
|
+
break
|
|
306
287
|
default:
|
|
307
|
-
conn.results.add(
|
|
308
|
-
continue
|
|
288
|
+
conn.results.add(this, { err: `invalid operator: ${operator}` })
|
|
289
|
+
continue
|
|
309
290
|
}
|
|
310
291
|
|
|
311
|
-
conn.results.incr(
|
|
312
|
-
conn.results.push(
|
|
292
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
293
|
+
conn.results.push(this, {awards: thisAward.id })
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
exports.check_result_exists = function (thisResult, thisAward, conn) {
|
|
298
|
+
|
|
299
|
+
/* eslint-disable no-unused-vars */
|
|
300
|
+
for (const r of thisResult) {
|
|
301
|
+
const [operator, qty] = thisAward.value.split(/\s+/)
|
|
302
|
+
|
|
303
|
+
switch (operator) {
|
|
304
|
+
case 'any':
|
|
305
|
+
case '':
|
|
306
|
+
break
|
|
307
|
+
default:
|
|
308
|
+
conn.results.add(this, { err: `invalid operator: ${operator}` })
|
|
309
|
+
continue
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
conn.results.incr(this, {score: thisAward.award})
|
|
313
|
+
conn.results.push(this, {awards: thisAward.id})
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
exports.apply_tarpit = function (connection, hook, score, next) {
|
|
317
|
-
|
|
318
|
-
if (!
|
|
318
|
+
|
|
319
|
+
if (!this.cfg.tarpit) return next() // tarpit disabled in config
|
|
319
320
|
|
|
320
321
|
// If tarpit is enabled on the reset_transaction hook, Haraka doesn't
|
|
321
322
|
// wait. Then bad things happen, like a Haraka crash.
|
|
322
|
-
if (utils.in_array(hook, ['reset_transaction','queue'])) return next()
|
|
323
|
+
if (utils.in_array(hook, ['reset_transaction','queue'])) return next()
|
|
323
324
|
|
|
324
325
|
// no delay for senders with good karma
|
|
325
|
-
const k = connection.results.get('karma')
|
|
326
|
-
if (score === undefined)
|
|
327
|
-
if (score >= 0)
|
|
326
|
+
const k = connection.results.get('karma')
|
|
327
|
+
if (score === undefined) score = parseFloat(k.score)
|
|
328
|
+
if (score >= 0) return next()
|
|
328
329
|
|
|
329
330
|
// how long to delay?
|
|
330
|
-
const delay =
|
|
331
|
-
if (!delay) return next()
|
|
331
|
+
const delay = this.tarpit_delay(score, connection, hook, k)
|
|
332
|
+
if (!delay) return next()
|
|
332
333
|
|
|
333
|
-
connection.logdebug(
|
|
334
|
+
connection.logdebug(this, `tarpitting ${hook} for ${delay}s`)
|
|
334
335
|
setTimeout(() => {
|
|
335
|
-
connection.logdebug(
|
|
336
|
-
next()
|
|
337
|
-
}, delay * 1000)
|
|
336
|
+
connection.logdebug(this, `tarpit ${hook} end`)
|
|
337
|
+
next()
|
|
338
|
+
}, delay * 1000)
|
|
338
339
|
}
|
|
339
340
|
|
|
340
341
|
exports.tarpit_delay = function (score, connection, hook, k) {
|
|
341
|
-
const plugin = this;
|
|
342
342
|
|
|
343
|
-
if (
|
|
344
|
-
connection.logdebug(
|
|
345
|
-
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)
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
const delay = score * -1
|
|
348
|
+
const delay = score * -1 // progressive tarpit
|
|
349
349
|
|
|
350
350
|
// detect roaming users based on MSA ports that require auth
|
|
351
351
|
if (utils.in_array(connection.local.port, [587,465]) &&
|
|
352
352
|
utils.in_array(hook, ['ehlo','connect'])) {
|
|
353
|
-
return
|
|
353
|
+
return this.tarpit_delay_msa(connection, delay, k)
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
const max =
|
|
356
|
+
const max = this.cfg.tarpit.max || 5
|
|
357
357
|
if (delay > max) {
|
|
358
|
-
connection.logdebug(
|
|
359
|
-
return max
|
|
358
|
+
connection.logdebug(this, `tarpit capped to: ${max}`)
|
|
359
|
+
return max
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
return delay
|
|
362
|
+
return delay
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
exports.tarpit_delay_msa = function (connection, delay, k) {
|
|
366
|
-
const
|
|
367
|
-
const trg = 'tarpit reduced for good';
|
|
366
|
+
const trg = 'tarpit reduced for good'
|
|
368
367
|
|
|
369
|
-
delay = parseFloat(delay)
|
|
368
|
+
delay = parseFloat(delay)
|
|
370
369
|
|
|
371
370
|
// Reduce delay for good history
|
|
372
|
-
const history = ((k.good || 0) - (k.bad || 0))
|
|
371
|
+
const history = ((k.good || 0) - (k.bad || 0))
|
|
373
372
|
if (history > 0) {
|
|
374
|
-
delay = delay - 2
|
|
375
|
-
connection.logdebug(
|
|
373
|
+
delay = delay - 2
|
|
374
|
+
connection.logdebug(this, `${trg} history: ${delay}`)
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
// Reduce delay for good ASN history
|
|
379
|
-
let asn = connection.results.get('asn')
|
|
380
|
-
if (!asn)
|
|
378
|
+
let asn = connection.results.get('asn')
|
|
379
|
+
if (!asn) asn = connection.results.get('geoip')
|
|
381
380
|
if (asn && asn.asn && k.neighbors > 0) {
|
|
382
|
-
connection.logdebug(
|
|
383
|
-
delay = delay - 2
|
|
381
|
+
connection.logdebug(this, `${trg} neighbors: ${delay}`)
|
|
382
|
+
delay = delay - 2
|
|
384
383
|
}
|
|
385
384
|
|
|
386
|
-
const max =
|
|
385
|
+
const max = this.cfg.tarpit.max_msa || 2
|
|
387
386
|
if (delay > max) {
|
|
388
|
-
connection.logdebug(
|
|
389
|
-
delay = max
|
|
387
|
+
connection.logdebug(this, `tarpit capped at: ${delay}`)
|
|
388
|
+
delay = max
|
|
390
389
|
}
|
|
391
390
|
|
|
392
|
-
return delay
|
|
391
|
+
return delay
|
|
393
392
|
}
|
|
394
393
|
|
|
395
394
|
exports.should_we_skip = function (connection) {
|
|
396
|
-
if (connection.remote.is_private) return true
|
|
397
|
-
if (connection.notes.disable_karma) return true
|
|
398
|
-
return false
|
|
395
|
+
if (connection.remote.is_private) return true
|
|
396
|
+
if (connection.notes.disable_karma) return true
|
|
397
|
+
return false
|
|
399
398
|
}
|
|
400
399
|
|
|
401
400
|
exports.should_we_deny = function (next, connection, hook) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
const r = connection.results.get('karma');
|
|
405
|
-
if (!r) { return next(); }
|
|
401
|
+
const r = connection.results.get('karma')
|
|
402
|
+
if (!r) return next()
|
|
406
403
|
|
|
407
|
-
|
|
404
|
+
this.check_awards(connection) // update awards first
|
|
408
405
|
|
|
409
|
-
const score = parseFloat(r.score)
|
|
406
|
+
const score = parseFloat(r.score)
|
|
410
407
|
if (isNaN(score)) {
|
|
411
|
-
connection.logerror(
|
|
412
|
-
connection.results.add(
|
|
413
|
-
return next()
|
|
408
|
+
connection.logerror(this, 'score is NaN')
|
|
409
|
+
connection.results.add(this, {score: 0})
|
|
410
|
+
return next()
|
|
414
411
|
}
|
|
415
412
|
|
|
416
|
-
let negative_limit = -5
|
|
417
|
-
if (
|
|
418
|
-
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)
|
|
419
416
|
}
|
|
420
417
|
|
|
421
418
|
if (score > negative_limit) {
|
|
422
|
-
return
|
|
419
|
+
return this.apply_tarpit(connection, hook, score, next)
|
|
423
420
|
}
|
|
424
|
-
if (!
|
|
425
|
-
return
|
|
421
|
+
if (!this.deny_hooks[hook]) {
|
|
422
|
+
return this.apply_tarpit(connection, hook, score, next)
|
|
426
423
|
}
|
|
427
424
|
|
|
428
|
-
let rejectMsg = 'very bad karma score: {score}'
|
|
429
|
-
if (
|
|
430
|
-
rejectMsg =
|
|
425
|
+
let rejectMsg = 'very bad karma score: {score}'
|
|
426
|
+
if (this.cfg.deny && this.cfg.deny.message) {
|
|
427
|
+
rejectMsg = this.cfg.deny.message
|
|
431
428
|
}
|
|
432
429
|
|
|
433
430
|
if (/\{/.test(rejectMsg)) {
|
|
434
|
-
rejectMsg = rejectMsg.replace(/\{score\}/, score)
|
|
435
|
-
rejectMsg = rejectMsg.replace(/\{uuid\}/, connection.uuid)
|
|
431
|
+
rejectMsg = rejectMsg.replace(/\{score\}/, score)
|
|
432
|
+
rejectMsg = rejectMsg.replace(/\{uuid\}/, connection.uuid)
|
|
436
433
|
}
|
|
437
434
|
|
|
438
|
-
return
|
|
439
|
-
next(constants.DENY, rejectMsg)
|
|
440
|
-
})
|
|
435
|
+
return this.apply_tarpit(connection, hook, score, () => {
|
|
436
|
+
next(constants.DENY, rejectMsg)
|
|
437
|
+
})
|
|
441
438
|
}
|
|
442
439
|
|
|
443
440
|
exports.hook_deny = function (next, connection, params) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
441
|
+
if (this.should_we_skip(connection)) return next()
|
|
447
442
|
|
|
448
443
|
// let pi_deny = params[0]; // (constants.deny, denysoft, ok)
|
|
449
444
|
// let pi_message = params[1];
|
|
450
|
-
const pi_name = params[2]
|
|
445
|
+
const pi_name = params[2]
|
|
451
446
|
// let pi_function = params[3];
|
|
452
447
|
// let pi_params = params[4];
|
|
453
|
-
const pi_hook = params[5]
|
|
448
|
+
const pi_hook = params[5]
|
|
454
449
|
|
|
455
450
|
// exceptions, whose 'DENY' should not be captured
|
|
456
451
|
if (pi_name) {
|
|
457
|
-
if (pi_name === 'karma') return next()
|
|
458
|
-
if (
|
|
459
|
-
}
|
|
460
|
-
if (pi_hook && plugin.deny_exclude_hooks[pi_hook]) {
|
|
461
|
-
return next();
|
|
452
|
+
if (pi_name === 'karma') return next()
|
|
453
|
+
if (this.deny_exclude_plugins[pi_name]) return next()
|
|
462
454
|
}
|
|
455
|
+
if (pi_hook && this.deny_exclude_hooks[pi_hook]) return next()
|
|
463
456
|
|
|
464
|
-
if (!connection.results)
|
|
465
|
-
return next(constants.OK); // resume the connection
|
|
466
|
-
}
|
|
457
|
+
if (!connection.results) return next(constants.OK) // resume the connection
|
|
467
458
|
|
|
468
459
|
// intercept any other denials
|
|
469
|
-
connection.results.add(
|
|
470
|
-
connection.results.incr(
|
|
460
|
+
connection.results.add(this, { msg: `deny: ${pi_name}` })
|
|
461
|
+
connection.results.incr(this, { score: -2 })
|
|
471
462
|
|
|
472
|
-
next(constants.OK)
|
|
463
|
+
next(constants.OK) // resume the connection
|
|
473
464
|
}
|
|
474
465
|
|
|
475
466
|
exports.hook_connect = function (next, connection) {
|
|
476
|
-
|
|
467
|
+
if (this.should_we_skip(connection)) return next()
|
|
477
468
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const asnkey = plugin.get_asn_key(connection);
|
|
469
|
+
const asnkey = this.get_asn_key(connection)
|
|
481
470
|
if (asnkey) {
|
|
482
|
-
|
|
471
|
+
this.check_asn(connection, asnkey)
|
|
483
472
|
}
|
|
484
|
-
|
|
473
|
+
this.should_we_deny(next, connection, 'connect')
|
|
485
474
|
}
|
|
486
475
|
|
|
487
476
|
exports.hook_helo = function (next, connection) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
477
|
+
if (this.should_we_skip(connection)) return next()
|
|
491
478
|
|
|
492
|
-
|
|
479
|
+
this.should_we_deny(next, connection, 'helo')
|
|
493
480
|
}
|
|
494
481
|
|
|
495
482
|
exports.hook_ehlo = function (next, connection) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
483
|
+
if (this.should_we_skip(connection)) return next()
|
|
499
484
|
|
|
500
|
-
|
|
485
|
+
this.should_we_deny(next, connection, 'ehlo')
|
|
501
486
|
}
|
|
502
487
|
|
|
503
488
|
exports.hook_vrfy = function (next, connection) {
|
|
504
|
-
|
|
489
|
+
if (this.should_we_skip(connection)) return next()
|
|
505
490
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
plugin.should_we_deny(next, connection, 'vrfy');
|
|
491
|
+
this.should_we_deny(next, connection, 'vrfy')
|
|
509
492
|
}
|
|
510
493
|
|
|
511
494
|
exports.hook_noop = function (next, connection) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
495
|
+
if (this.should_we_skip(connection)) return next()
|
|
515
496
|
|
|
516
|
-
|
|
497
|
+
this.should_we_deny(next, connection, 'noop')
|
|
517
498
|
}
|
|
518
499
|
|
|
519
500
|
exports.hook_data = function (next, connection) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
501
|
+
if (this.should_we_skip(connection)) return next()
|
|
523
502
|
|
|
524
|
-
|
|
503
|
+
this.should_we_deny(next, connection, 'data')
|
|
525
504
|
}
|
|
526
505
|
|
|
527
506
|
exports.hook_queue = function (next, connection) {
|
|
528
|
-
|
|
507
|
+
if (this.should_we_skip(connection)) return next()
|
|
529
508
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
plugin.should_we_deny(next, connection, 'queue');
|
|
509
|
+
this.should_we_deny(next, connection, 'queue')
|
|
533
510
|
}
|
|
534
511
|
|
|
535
512
|
exports.hook_reset_transaction = function (next, connection) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (plugin.should_we_skip(connection)) return next();
|
|
513
|
+
if (this.should_we_skip(connection)) return next()
|
|
539
514
|
|
|
540
|
-
connection.results.add(
|
|
541
|
-
|
|
515
|
+
connection.results.add(this, {emit: true})
|
|
516
|
+
this.should_we_deny(next, connection, 'reset_transaction')
|
|
542
517
|
}
|
|
543
518
|
|
|
544
519
|
exports.hook_unrecognized_command = function (next, connection, params) {
|
|
545
|
-
const plugin = this;
|
|
546
520
|
|
|
547
|
-
if (
|
|
521
|
+
if (this.should_we_skip(connection)) return next()
|
|
548
522
|
|
|
549
523
|
// in case karma is in config/plugins before tls
|
|
550
|
-
if (params[0].toUpperCase() === 'STARTTLS') return next()
|
|
524
|
+
if (params[0].toUpperCase() === 'STARTTLS') return next()
|
|
551
525
|
|
|
552
526
|
// in case karma is in config/plugins before AUTH plugin(s)
|
|
553
|
-
if (connection.notes.authenticating) return next()
|
|
527
|
+
if (connection.notes.authenticating) return next()
|
|
554
528
|
|
|
555
|
-
connection.results.incr(
|
|
556
|
-
connection.results.add(
|
|
529
|
+
connection.results.incr(this, {score: -1})
|
|
530
|
+
connection.results.add(this, {fail: `cmd:(${params})`})
|
|
557
531
|
|
|
558
|
-
return
|
|
532
|
+
return this.should_we_deny(next, connection, 'unrecognized_command')
|
|
559
533
|
}
|
|
560
534
|
|
|
561
535
|
exports.ip_history_from_redis = function (next, connection) {
|
|
562
|
-
const plugin = this
|
|
536
|
+
const plugin = this
|
|
563
537
|
|
|
564
|
-
if (plugin.should_we_skip(connection)) return next()
|
|
538
|
+
if (plugin.should_we_skip(connection)) return next()
|
|
565
539
|
|
|
566
|
-
const expire = (plugin.cfg.redis.expire_days || 60) * 86400
|
|
567
|
-
const dbkey =
|
|
540
|
+
const expire = (plugin.cfg.redis.expire_days || 60) * 86400 // to days
|
|
541
|
+
const dbkey = `karma|${connection.remote.ip}`
|
|
568
542
|
|
|
569
543
|
// redis plugin is emitting errors, no need to here
|
|
570
|
-
if (!plugin.db) return next()
|
|
544
|
+
if (!plugin.db) return next()
|
|
571
545
|
|
|
572
546
|
plugin.db.hgetall(dbkey, (err, dbr) => {
|
|
573
547
|
if (err) {
|
|
574
|
-
connection.results.add(plugin, {err
|
|
575
|
-
return next()
|
|
548
|
+
connection.results.add(plugin, { err })
|
|
549
|
+
return next()
|
|
576
550
|
}
|
|
577
551
|
|
|
578
552
|
if (dbr === null) {
|
|
579
|
-
plugin.init_ip(dbkey, connection.remote.ip, expire)
|
|
580
|
-
return next()
|
|
553
|
+
plugin.init_ip(dbkey, connection.remote.ip, expire)
|
|
554
|
+
return next()
|
|
581
555
|
}
|
|
582
556
|
|
|
583
557
|
plugin.db.multi()
|
|
584
558
|
.hincrby(dbkey, 'connections', 1) // increment total conn
|
|
585
559
|
.expire(dbkey, expire) // extend expiration
|
|
586
560
|
.exec((err2, replies) => {
|
|
587
|
-
if (err2) connection.results.add(plugin, {err: err2})
|
|
588
|
-
})
|
|
561
|
+
if (err2) connection.results.add(plugin, {err: err2})
|
|
562
|
+
})
|
|
589
563
|
|
|
590
564
|
const results = {
|
|
591
565
|
good: dbr.good,
|
|
@@ -597,412 +571,381 @@ exports.ip_history_from_redis = function (next, connection) {
|
|
|
597
571
|
|
|
598
572
|
// Careful: don't become self-fulfilling prophecy.
|
|
599
573
|
if (parseInt(dbr.good) > 5 && parseInt(dbr.bad) === 0) {
|
|
600
|
-
results.pass = 'all_good'
|
|
574
|
+
results.pass = 'all_good'
|
|
601
575
|
}
|
|
602
576
|
if (parseInt(dbr.bad) > 5 && parseInt(dbr.good) === 0) {
|
|
603
|
-
results.fail = 'all_bad'
|
|
577
|
+
results.fail = 'all_bad'
|
|
604
578
|
}
|
|
605
579
|
|
|
606
|
-
connection.results.add(plugin, results)
|
|
580
|
+
connection.results.add(plugin, results)
|
|
607
581
|
|
|
608
|
-
plugin.check_awards(connection)
|
|
609
|
-
return next()
|
|
610
|
-
})
|
|
582
|
+
plugin.check_awards(connection)
|
|
583
|
+
return next()
|
|
584
|
+
})
|
|
611
585
|
}
|
|
612
586
|
|
|
613
587
|
exports.hook_mail = function (next, connection, params) {
|
|
614
|
-
const plugin = this;
|
|
615
588
|
|
|
616
|
-
if (
|
|
589
|
+
if (this.should_we_skip(connection)) return next()
|
|
617
590
|
|
|
618
|
-
|
|
591
|
+
this.check_spammy_tld(params[0], connection)
|
|
619
592
|
|
|
620
593
|
// look for invalid (RFC 5321,(2)821) space in envelope from
|
|
621
|
-
const full_from = connection.current_line
|
|
594
|
+
const full_from = connection.current_line
|
|
622
595
|
if (full_from.toUpperCase().substring(0,11) !== 'MAIL FROM:<') {
|
|
623
|
-
connection.loginfo(
|
|
624
|
-
connection.results.add(
|
|
596
|
+
connection.loginfo(this, `RFC ignorant env addr format: ${full_from}`)
|
|
597
|
+
connection.results.add(this, {fail: 'rfc5321.MailFrom'})
|
|
625
598
|
}
|
|
626
599
|
|
|
627
600
|
// apply TLS awards (if defined)
|
|
628
|
-
if (
|
|
629
|
-
if (
|
|
630
|
-
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})
|
|
631
604
|
}
|
|
632
|
-
if (
|
|
633
|
-
connection.results.incr(
|
|
605
|
+
if (this.cfg.tls.unset && !connection.tls.enabled) {
|
|
606
|
+
connection.results.incr(this, {score: this.cfg.tls.unset})
|
|
634
607
|
}
|
|
635
608
|
}
|
|
636
609
|
|
|
637
|
-
return
|
|
610
|
+
return this.should_we_deny(next, connection, 'mail')
|
|
638
611
|
}
|
|
639
612
|
|
|
640
613
|
exports.hook_rcpt = function (next, connection, params) {
|
|
641
|
-
const plugin = this;
|
|
642
614
|
|
|
643
|
-
if (
|
|
615
|
+
if (this.should_we_skip(connection)) return next()
|
|
644
616
|
|
|
645
|
-
const rcpt = params[0]
|
|
617
|
+
const rcpt = params[0]
|
|
646
618
|
|
|
647
619
|
// hook_rcpt catches recipients that no rcpt_to plugin permitted
|
|
648
620
|
// hook_rcpt_ok catches accepted recipients
|
|
649
621
|
|
|
650
622
|
// odds of from_user=rcpt_user in ham: < 1%, in spam > 40%
|
|
651
623
|
// 2015-05 30-day sample: 84% spam correlation
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
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'})
|
|
655
626
|
}
|
|
656
627
|
|
|
657
|
-
|
|
628
|
+
this.check_syntax_RcptTo(connection)
|
|
658
629
|
|
|
659
|
-
connection.results.add(
|
|
630
|
+
connection.results.add(this, {fail: 'rcpt_to'})
|
|
660
631
|
|
|
661
|
-
return
|
|
632
|
+
return this.should_we_deny(next, connection, 'rcpt')
|
|
662
633
|
}
|
|
663
634
|
|
|
664
635
|
exports.hook_rcpt_ok = function (next, connection, rcpt) {
|
|
665
|
-
const plugin = this;
|
|
666
636
|
|
|
667
|
-
if (
|
|
637
|
+
if (this.should_we_skip(connection)) return next()
|
|
668
638
|
|
|
669
|
-
const txn = connection.transaction
|
|
639
|
+
const txn = connection.transaction
|
|
670
640
|
if (txn && txn.mail_from && txn.mail_from.user === rcpt.user) {
|
|
671
|
-
connection.results.add(
|
|
641
|
+
connection.results.add(this, {fail: 'env_user_match'})
|
|
672
642
|
}
|
|
673
643
|
|
|
674
|
-
|
|
644
|
+
this.check_syntax_RcptTo(connection)
|
|
675
645
|
|
|
676
|
-
return
|
|
646
|
+
return this.should_we_deny(next, connection, 'rcpt')
|
|
677
647
|
}
|
|
678
648
|
|
|
679
649
|
exports.hook_data_post = function (next, connection) {
|
|
680
650
|
// goal: prevent delivery of spam before queue
|
|
681
|
-
const plugin = this;
|
|
682
651
|
|
|
683
|
-
if (
|
|
652
|
+
if (this.should_we_skip(connection)) return next()
|
|
684
653
|
|
|
685
|
-
|
|
654
|
+
this.check_awards(connection) // update awards
|
|
686
655
|
|
|
687
|
-
const results = connection.results.collate(
|
|
688
|
-
connection.logdebug(
|
|
689
|
-
connection.transaction.remove_header('X-Haraka-Karma')
|
|
690
|
-
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)
|
|
691
660
|
|
|
692
|
-
return
|
|
661
|
+
return this.should_we_deny(next, connection, 'data_post')
|
|
693
662
|
}
|
|
694
663
|
|
|
695
664
|
exports.increment = function (connection, key, val) {
|
|
696
|
-
const plugin = this
|
|
697
|
-
if (!plugin.db) return
|
|
665
|
+
const plugin = this
|
|
666
|
+
if (!plugin.db) return
|
|
698
667
|
|
|
699
|
-
plugin.db.hincrby(
|
|
668
|
+
plugin.db.hincrby(`karma|${connection.remote.ip}`, key, 1)
|
|
700
669
|
|
|
701
|
-
const asnkey = plugin.get_asn_key(connection)
|
|
702
|
-
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)
|
|
703
672
|
}
|
|
704
673
|
|
|
705
674
|
exports.hook_disconnect = function (next, connection) {
|
|
706
|
-
const plugin = this
|
|
675
|
+
const plugin = this
|
|
707
676
|
|
|
708
|
-
|
|
677
|
+
plugin.redis_unsubscribe(connection)
|
|
709
678
|
|
|
710
|
-
plugin.
|
|
679
|
+
if (plugin.should_we_skip(connection)) return next()
|
|
711
680
|
|
|
712
|
-
const k = connection.results.get('karma')
|
|
681
|
+
const k = connection.results.get('karma')
|
|
713
682
|
if (!k || k.score === undefined) {
|
|
714
|
-
connection.results.add(plugin, {err: 'karma results missing'})
|
|
715
|
-
return next()
|
|
683
|
+
connection.results.add(plugin, {err: 'karma results missing'})
|
|
684
|
+
return next()
|
|
716
685
|
}
|
|
717
686
|
|
|
718
687
|
if (!plugin.cfg.thresholds) {
|
|
719
|
-
plugin.check_awards(connection)
|
|
720
|
-
connection.results.add(plugin, {msg: 'no action', emit: true })
|
|
721
|
-
return next()
|
|
688
|
+
plugin.check_awards(connection)
|
|
689
|
+
connection.results.add(plugin, {msg: 'no action', emit: true })
|
|
690
|
+
return next()
|
|
722
691
|
}
|
|
723
692
|
|
|
724
693
|
if (k.score > (plugin.cfg.thresholds.positive || 3)) {
|
|
725
|
-
plugin.increment(connection, 'good', 1)
|
|
694
|
+
plugin.increment(connection, 'good', 1)
|
|
726
695
|
}
|
|
727
696
|
if (k.score < 0) {
|
|
728
|
-
plugin.increment(connection, 'bad', 1)
|
|
697
|
+
plugin.increment(connection, 'bad', 1)
|
|
729
698
|
}
|
|
730
699
|
|
|
731
|
-
connection.results.add(plugin, {emit: true })
|
|
732
|
-
return next()
|
|
700
|
+
connection.results.add(plugin, {emit: true })
|
|
701
|
+
return next()
|
|
733
702
|
}
|
|
734
703
|
|
|
735
704
|
exports.get_award_loc_from_note = function (connection, award) {
|
|
736
|
-
const plugin = this;
|
|
737
705
|
|
|
738
706
|
if (connection.transaction) {
|
|
739
|
-
const obj =
|
|
740
|
-
if (obj)
|
|
707
|
+
const obj = this.assemble_note_obj(connection.transaction, award)
|
|
708
|
+
if (obj) return obj
|
|
741
709
|
}
|
|
742
710
|
|
|
743
|
-
// connection.logdebug(
|
|
744
|
-
const obj =
|
|
745
|
-
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
|
|
746
714
|
|
|
747
|
-
// connection.logdebug(
|
|
748
|
-
return
|
|
715
|
+
// connection.logdebug(this, `no conn note: ${award}`);
|
|
716
|
+
return
|
|
749
717
|
}
|
|
750
718
|
|
|
751
719
|
exports.get_award_loc_from_results = function (connection, loc_bits) {
|
|
752
720
|
|
|
753
|
-
let pi_name = loc_bits[1]
|
|
754
|
-
let notekey = loc_bits[2]
|
|
721
|
+
let pi_name = loc_bits[1]
|
|
722
|
+
let notekey = loc_bits[2]
|
|
755
723
|
|
|
756
724
|
if (phase_prefixes[pi_name]) {
|
|
757
|
-
pi_name = loc_bits[1]
|
|
758
|
-
notekey = loc_bits[3]
|
|
725
|
+
pi_name = `${loc_bits[1]}.${loc_bits[2]}`
|
|
726
|
+
notekey = loc_bits[3]
|
|
759
727
|
}
|
|
760
728
|
|
|
761
|
-
let obj
|
|
762
|
-
if (connection.transaction)
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
if (!obj)
|
|
766
|
-
|
|
767
|
-
obj = connection.results.get(pi_name);
|
|
768
|
-
}
|
|
769
|
-
if (!obj) {
|
|
770
|
-
// connection.logdebug(plugin, 'no conn results: ' + pi_name);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
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
|
|
773
735
|
|
|
774
|
-
// connection.logdebug(plugin,
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
return obj;
|
|
736
|
+
// connection.logdebug(plugin, `found results for ${pi_name}, ${notekey}`);
|
|
737
|
+
if (notekey) return obj[notekey]
|
|
738
|
+
return obj
|
|
778
739
|
}
|
|
779
740
|
|
|
780
741
|
exports.get_award_location = function (connection, award_key) {
|
|
781
742
|
// based on award key, find the requested note or result
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
if (loc_bits.length === 1) { // ex: relaying
|
|
786
|
-
return connection[bits[0]];
|
|
787
|
-
}
|
|
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
|
|
788
746
|
|
|
789
747
|
if (loc_bits[0] === 'notes') { // ex: notes.spf_mail_helo
|
|
790
|
-
return
|
|
748
|
+
return this.get_award_loc_from_note(connection, bits[0])
|
|
791
749
|
}
|
|
792
750
|
|
|
793
|
-
if (loc_bits[0] === 'results') {
|
|
794
|
-
return
|
|
751
|
+
if (loc_bits[0] === 'results') { // ex: results.geoip.distance
|
|
752
|
+
return this.get_award_loc_from_results(connection, loc_bits)
|
|
795
753
|
}
|
|
796
754
|
|
|
797
755
|
// ex: transaction.results.spf
|
|
798
|
-
if (connection.transaction &&
|
|
799
|
-
loc_bits
|
|
800
|
-
|
|
801
|
-
loc_bits.shift();
|
|
802
|
-
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)
|
|
803
759
|
}
|
|
804
760
|
|
|
805
|
-
connection.logdebug(
|
|
761
|
+
connection.logdebug(this, `unknown location for ${award_key}`)
|
|
806
762
|
}
|
|
807
763
|
|
|
808
764
|
exports.get_award_condition = function (note_key, note_val) {
|
|
809
|
-
let wants
|
|
810
|
-
const keybits = note_key.split('@')
|
|
811
|
-
if (keybits[1]) { wants = keybits[1]
|
|
765
|
+
let wants
|
|
766
|
+
const keybits = note_key.split('@')
|
|
767
|
+
if (keybits[1]) { wants = keybits[1] }
|
|
812
768
|
|
|
813
|
-
const valbits = note_val.split(/\s+/)
|
|
814
|
-
if (!valbits[1])
|
|
815
|
-
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
|
|
816
772
|
|
|
817
773
|
if (valbits[2].match(/^(equals|gt|lt|match)$/)) {
|
|
818
|
-
if (valbits[3])
|
|
774
|
+
if (valbits[3]) wants = valbits[3]
|
|
819
775
|
}
|
|
820
|
-
return wants
|
|
776
|
+
return wants
|
|
821
777
|
}
|
|
822
778
|
|
|
823
779
|
exports.check_awards = function (connection) {
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
if (!karma ) return;
|
|
827
|
-
if (!karma.todo) return;
|
|
780
|
+
const karma = connection.results.get('karma')
|
|
781
|
+
if (!karma?.todo) return
|
|
828
782
|
|
|
829
783
|
for (const key in karma.todo) {
|
|
830
784
|
// loc = terms
|
|
831
785
|
// note_location [@wants] = award [conditions]
|
|
832
786
|
// results.geoip.too_far = -1
|
|
833
787
|
// results.geoip.distance@4000 = -1 if gt 4000
|
|
834
|
-
const award_terms = karma.todo[key]
|
|
788
|
+
const award_terms = karma.todo[key]
|
|
835
789
|
|
|
836
|
-
const note =
|
|
837
|
-
if (note === undefined)
|
|
838
|
-
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)
|
|
839
793
|
|
|
840
794
|
// test the desired condition
|
|
841
|
-
const bits = award_terms.split(/\s+/)
|
|
842
|
-
const award = parseFloat(bits[0])
|
|
795
|
+
const bits = award_terms.split(/\s+/)
|
|
796
|
+
const award = parseFloat(bits[0])
|
|
843
797
|
if (!bits[1] || bits[1] !== 'if') { // no if conditions
|
|
844
|
-
if (!note)
|
|
798
|
+
if (!note) continue // failed truth test
|
|
845
799
|
if (!wants) { // no wants, truth matches
|
|
846
|
-
|
|
847
|
-
delete karma.todo[key]
|
|
848
|
-
continue
|
|
800
|
+
this.apply_award(connection, key, award)
|
|
801
|
+
delete karma.todo[key]
|
|
802
|
+
continue
|
|
849
803
|
}
|
|
850
|
-
if (note !== wants)
|
|
804
|
+
if (note !== wants) continue // didn't match
|
|
851
805
|
}
|
|
852
806
|
|
|
853
|
-
// connection.loginfo(
|
|
854
|
-
// wants);
|
|
807
|
+
// connection.loginfo(this, `check_awards, case matching for: ${wants}`
|
|
855
808
|
|
|
856
809
|
// the matching logic here is inverted, weeding out misses (continue)
|
|
857
810
|
// Matches fall through (break) to the apply_award below.
|
|
858
|
-
const condition = bits[2]
|
|
811
|
+
const condition = bits[2]
|
|
859
812
|
switch (condition) {
|
|
860
813
|
case 'equals':
|
|
861
|
-
if (wants != note) continue
|
|
862
|
-
break
|
|
814
|
+
if (wants != note) continue
|
|
815
|
+
break
|
|
863
816
|
case 'gt':
|
|
864
|
-
if (parseFloat(note) <= parseFloat(wants))
|
|
865
|
-
break
|
|
817
|
+
if (parseFloat(note) <= parseFloat(wants)) continue
|
|
818
|
+
break
|
|
866
819
|
case 'lt':
|
|
867
|
-
if (parseFloat(note) >= parseFloat(wants))
|
|
868
|
-
break
|
|
820
|
+
if (parseFloat(note) >= parseFloat(wants)) continue
|
|
821
|
+
break
|
|
869
822
|
case 'match':
|
|
870
823
|
if (Array.isArray(note)) {
|
|
871
|
-
// connection.logerror(
|
|
872
|
-
if (new RegExp(wants, 'i').test(note))
|
|
824
|
+
// connection.logerror(this, 'matching an array');
|
|
825
|
+
if (new RegExp(wants, 'i').test(note)) break
|
|
873
826
|
}
|
|
874
|
-
if (note.toString().match(new RegExp(wants, 'i')))
|
|
875
|
-
continue
|
|
827
|
+
if (note.toString().match(new RegExp(wants, 'i'))) break
|
|
828
|
+
continue
|
|
876
829
|
case 'length': {
|
|
877
|
-
const operator = bits[3]
|
|
878
|
-
if (bits[4]) { wants = bits[4]
|
|
830
|
+
const operator = bits[3]
|
|
831
|
+
if (bits[4]) { wants = bits[4] }
|
|
879
832
|
switch (operator) {
|
|
880
833
|
case 'gt':
|
|
881
|
-
if (note.length <= parseFloat(wants))
|
|
882
|
-
break
|
|
834
|
+
if (note.length <= parseFloat(wants)) continue
|
|
835
|
+
break
|
|
883
836
|
case 'lt':
|
|
884
|
-
if (note.length >= parseFloat(wants))
|
|
885
|
-
break
|
|
837
|
+
if (note.length >= parseFloat(wants)) continue
|
|
838
|
+
break
|
|
886
839
|
case 'equals':
|
|
887
|
-
if (note.length !== parseFloat(wants))
|
|
888
|
-
break
|
|
840
|
+
if (note.length !== parseFloat(wants)) continue
|
|
841
|
+
break
|
|
889
842
|
default:
|
|
890
|
-
connection.logerror(
|
|
891
|
-
|
|
892
|
-
continue;
|
|
843
|
+
connection.logerror(this, `length operator "${operator}" not supported.`)
|
|
844
|
+
continue
|
|
893
845
|
}
|
|
894
|
-
break
|
|
846
|
+
break
|
|
895
847
|
}
|
|
896
848
|
case 'in': // if in pass whitelisted
|
|
897
849
|
// let list = bits[3];
|
|
898
|
-
if (bits[4]) { wants = bits[4]
|
|
899
|
-
if (!Array.isArray(note))
|
|
900
|
-
if (!wants)
|
|
901
|
-
if (note.indexOf(wants) !== -1)
|
|
902
|
-
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
|
|
903
855
|
default:
|
|
904
|
-
continue
|
|
856
|
+
continue
|
|
905
857
|
}
|
|
906
|
-
|
|
907
|
-
delete karma.todo[key]
|
|
858
|
+
this.apply_award(connection, key, award)
|
|
859
|
+
delete karma.todo[key]
|
|
908
860
|
}
|
|
909
861
|
}
|
|
910
862
|
|
|
911
863
|
exports.apply_award = function (connection, nl, award) {
|
|
912
|
-
|
|
913
|
-
if (!award) { return; }
|
|
864
|
+
if (!award) return
|
|
914
865
|
if (isNaN(award)) { // garbage in config
|
|
915
|
-
connection.logerror(
|
|
916
|
-
|
|
917
|
-
return;
|
|
866
|
+
connection.logerror(this, `non-numeric award from: ${nl}:${award}`)
|
|
867
|
+
return
|
|
918
868
|
}
|
|
919
869
|
|
|
920
|
-
const bits = nl.split('@'); nl = bits[0]
|
|
870
|
+
const bits = nl.split('@'); nl = bits[0] // strip off @... if present
|
|
921
871
|
|
|
922
|
-
connection.results.incr(
|
|
923
|
-
connection.logdebug(
|
|
872
|
+
connection.results.incr(this, {score: award})
|
|
873
|
+
connection.logdebug(this, `applied ${nl}:${award}`)
|
|
924
874
|
|
|
925
875
|
let trimmed = nl.substring(0, 5) === 'notes' ? nl.substring(6) :
|
|
926
876
|
nl.substring(0, 7) === 'results' ? nl.substring(8) :
|
|
927
877
|
nl.substring(0,19) === 'transaction.results' ?
|
|
928
|
-
nl.substring(20) : nl
|
|
878
|
+
nl.substring(20) : nl
|
|
929
879
|
|
|
930
|
-
if (trimmed.substring(0,7) === 'rcpt_to') trimmed = trimmed.substring(8)
|
|
931
|
-
if (trimmed.substring(0,7) === 'mail_from') trimmed = trimmed.substring(10)
|
|
932
|
-
if (trimmed.substring(0,7) === 'connect') trimmed = trimmed.substring(8)
|
|
933
|
-
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)
|
|
934
884
|
|
|
935
|
-
if (award > 0)
|
|
936
|
-
if (award < 0)
|
|
885
|
+
if (award > 0) connection.results.add(this, { pass: trimmed })
|
|
886
|
+
if (award < 0) connection.results.add(this, { fail: trimmed })
|
|
937
887
|
}
|
|
938
888
|
|
|
939
889
|
exports.check_spammy_tld = function (mail_from, connection) {
|
|
940
|
-
|
|
941
|
-
if (
|
|
942
|
-
if (mail_from.isNull()) { return; } // null sender (bounce)
|
|
890
|
+
if (!this.cfg.spammy_tlds) return
|
|
891
|
+
if (mail_from.isNull()) return // null sender (bounce)
|
|
943
892
|
|
|
944
|
-
const from_tld = mail_from.host.split('.').pop()
|
|
945
|
-
// connection.logdebug(
|
|
893
|
+
const from_tld = mail_from.host.split('.').pop()
|
|
894
|
+
// connection.logdebug(this, `from_tld: ${from_tld}`);
|
|
946
895
|
|
|
947
|
-
const tld_penalty = parseFloat(
|
|
948
|
-
if (tld_penalty === 0)
|
|
896
|
+
const tld_penalty = parseFloat(this.cfg.spammy_tlds[from_tld] || 0)
|
|
897
|
+
if (tld_penalty === 0) return
|
|
949
898
|
|
|
950
|
-
connection.results.incr(
|
|
951
|
-
connection.results.add(
|
|
899
|
+
connection.results.incr(this, {score: tld_penalty})
|
|
900
|
+
connection.results.add(this, {fail: 'spammy.TLD'})
|
|
952
901
|
}
|
|
953
902
|
|
|
954
903
|
exports.check_syntax_RcptTo = function (connection) {
|
|
955
|
-
const plugin = this;
|
|
956
|
-
|
|
957
904
|
// look for an illegal (RFC 5321,(2)821) space in envelope recipient
|
|
958
|
-
const full_rcpt = connection.current_line
|
|
959
|
-
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
|
|
960
907
|
|
|
961
|
-
connection.loginfo(
|
|
962
|
-
|
|
963
|
-
connection.results.add(plugin, {fail: 'rfc5321.RcptTo'});
|
|
908
|
+
connection.loginfo(this, `illegal envelope address format: ${full_rcpt}`)
|
|
909
|
+
connection.results.add(this, {fail: 'rfc5321.RcptTo'})
|
|
964
910
|
}
|
|
965
911
|
|
|
966
912
|
exports.assemble_note_obj = function (prefix, key) {
|
|
967
|
-
let note = prefix
|
|
968
|
-
const parts = key.split('.')
|
|
913
|
+
let note = prefix
|
|
914
|
+
const parts = key.split('.')
|
|
969
915
|
while (parts.length > 0) {
|
|
970
|
-
let next = parts.shift()
|
|
916
|
+
let next = parts.shift()
|
|
971
917
|
if (phase_prefixes[next]) {
|
|
972
|
-
next = next
|
|
918
|
+
next = `${next}.${parts.shift()}`
|
|
973
919
|
}
|
|
974
|
-
note = note[next]
|
|
975
|
-
if (note === null || note === undefined)
|
|
920
|
+
note = note[next]
|
|
921
|
+
if (note === null || note === undefined) break
|
|
976
922
|
}
|
|
977
|
-
return note
|
|
923
|
+
return note
|
|
978
924
|
}
|
|
979
925
|
|
|
980
926
|
exports.check_asn = function (connection, asnkey) {
|
|
981
|
-
|
|
982
|
-
if (!plugin.db) return;
|
|
927
|
+
if (!this.db) return
|
|
983
928
|
|
|
984
|
-
const report_as = { name:
|
|
929
|
+
const report_as = { name: this.name }
|
|
985
930
|
|
|
986
|
-
if (
|
|
987
|
-
report_as.name = plugin.cfg.asn.report_as;
|
|
988
|
-
}
|
|
931
|
+
if (this.cfg.asn.report_as) report_as.name = this.cfg.asn.report_as
|
|
989
932
|
|
|
990
|
-
|
|
933
|
+
this.db.hgetall(asnkey, (err, res) => {
|
|
991
934
|
if (err) {
|
|
992
|
-
connection.results.add(
|
|
993
|
-
return
|
|
935
|
+
connection.results.add(this, { err })
|
|
936
|
+
return
|
|
994
937
|
}
|
|
995
938
|
|
|
996
939
|
if (res === null) {
|
|
997
|
-
const expire = (
|
|
998
|
-
|
|
999
|
-
return
|
|
940
|
+
const expire = (this.cfg.redis.expire_days || 60) * 86400 // days
|
|
941
|
+
this.init_asn(asnkey, expire)
|
|
942
|
+
return
|
|
1000
943
|
}
|
|
1001
944
|
|
|
1002
|
-
|
|
1003
|
-
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)
|
|
1004
947
|
const asn_results = {
|
|
1005
|
-
asn_score
|
|
948
|
+
asn_score,
|
|
1006
949
|
asn_connections: res.connections,
|
|
1007
950
|
asn_good: res.good,
|
|
1008
951
|
asn_bad: res.bad,
|
|
@@ -1011,49 +954,45 @@ exports.check_asn = function (connection, asnkey) {
|
|
|
1011
954
|
|
|
1012
955
|
if (asn_score) {
|
|
1013
956
|
if (asn_score < -5) {
|
|
1014
|
-
asn_results.fail = 'asn:history'
|
|
957
|
+
asn_results.fail = 'asn:history'
|
|
1015
958
|
}
|
|
1016
959
|
else if (asn_score > 5) {
|
|
1017
|
-
asn_results.pass = 'asn:history'
|
|
960
|
+
asn_results.pass = 'asn:history'
|
|
1018
961
|
}
|
|
1019
962
|
}
|
|
1020
963
|
|
|
1021
964
|
if (parseInt(res.bad) > 5 && parseInt(res.good) === 0) {
|
|
1022
|
-
asn_results.fail = 'asn:all_bad'
|
|
965
|
+
asn_results.fail = 'asn:all_bad'
|
|
1023
966
|
}
|
|
1024
967
|
if (parseInt(res.good) > 5 && parseInt(res.bad) === 0) {
|
|
1025
|
-
asn_results.pass = 'asn:all_good'
|
|
968
|
+
asn_results.pass = 'asn:all_good'
|
|
1026
969
|
}
|
|
1027
970
|
|
|
1028
|
-
connection.results.add(report_as, asn_results)
|
|
1029
|
-
})
|
|
971
|
+
connection.results.add(report_as, asn_results)
|
|
972
|
+
})
|
|
1030
973
|
}
|
|
1031
974
|
|
|
1032
975
|
exports.init_ip = function (dbkey, rip, expire) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
plugin.db.multi()
|
|
976
|
+
if (!this.db) return
|
|
977
|
+
this.db.multi()
|
|
1036
978
|
.hmset(dbkey, {'bad': 0, 'good': 0, 'connections': 1})
|
|
1037
979
|
.expire(dbkey, expire)
|
|
1038
|
-
.exec()
|
|
980
|
+
.exec()
|
|
1039
981
|
}
|
|
1040
982
|
|
|
1041
983
|
exports.get_asn_key = function (connection) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (!asn || !asn.asn)
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
if (!asn || !asn.asn || isNaN(asn.asn)) { return; }
|
|
1049
|
-
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}`
|
|
1050
989
|
}
|
|
1051
990
|
|
|
1052
991
|
exports.init_asn = function (asnkey, expire) {
|
|
1053
|
-
const plugin = this
|
|
1054
|
-
if (!plugin.db) return
|
|
992
|
+
const plugin = this
|
|
993
|
+
if (!plugin.db) return
|
|
1055
994
|
plugin.db.multi()
|
|
1056
995
|
.hmset(asnkey, {'bad': 0, 'good': 0, 'connections': 1})
|
|
1057
996
|
.expire(asnkey, expire * 2) // keep ASN longer
|
|
1058
|
-
.exec()
|
|
997
|
+
.exec()
|
|
1059
998
|
}
|