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