haraka-plugin-karma 1.0.12 → 2.0.0

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