haraka-plugin-karma 1.0.14 → 2.0.2

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