haraka-plugin-karma 1.0.14 → 2.0.0

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