haraka-plugin-karma 2.1.2 → 2.1.4

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
@@ -2,31 +2,44 @@
2
2
  // karma - reward good and penalize bad mail senders
3
3
 
4
4
  const constants = require('haraka-constants')
5
- const redis = require('redis')
6
- const utils = require('haraka-utils')
5
+ const redis = require('redis')
6
+ const utils = require('haraka-utils')
7
7
 
8
8
  const phase_prefixes = utils.to_object([
9
- 'connect','helo','mail_from','rcpt_to','data'
9
+ 'connect',
10
+ 'helo',
11
+ 'mail_from',
12
+ 'rcpt_to',
13
+ 'data',
10
14
  ])
11
15
 
12
16
  exports.register = function () {
13
-
14
17
  this.inherits('haraka-plugin-redis')
15
18
 
16
19
  // set up defaults
17
- this.deny_hooks = utils.to_object(
18
- ['unrecognized_command','helo','data','data_post','queue','queue_outbound']
19
- )
20
+ this.deny_hooks = utils.to_object([
21
+ 'unrecognized_command',
22
+ 'helo',
23
+ 'data',
24
+ 'data_post',
25
+ 'queue',
26
+ 'queue_outbound',
27
+ ])
20
28
  this.deny_exclude_hooks = utils.to_object('rcpt_to queue queue_outbound')
21
29
  this.deny_exclude_plugins = utils.to_object([
22
- 'access', 'helo.checks', 'data.headers', 'spamassassin',
23
- 'mail_from.is_resolvable', 'clamd', 'tls'
30
+ 'access',
31
+ 'helo.checks',
32
+ 'data.headers',
33
+ 'spamassassin',
34
+ 'mail_from.is_resolvable',
35
+ 'clamd',
36
+ 'tls',
24
37
  ])
25
38
 
26
39
  this.load_karma_ini()
27
40
 
28
- this.register_hook('init_master', 'init_redis_plugin')
29
- this.register_hook('init_child', 'init_redis_plugin')
41
+ this.register_hook('init_master', 'init_redis_plugin')
42
+ this.register_hook('init_child', 'init_redis_plugin')
30
43
 
31
44
  this.register_hook('connect_init', 'results_init')
32
45
  this.register_hook('connect_init', 'ip_history_from_redis')
@@ -35,13 +48,15 @@ exports.register = function () {
35
48
  exports.load_karma_ini = function () {
36
49
  const plugin = this
37
50
 
38
- plugin.cfg = plugin.config.get('karma.ini', {
39
- booleans: [
40
- '+asn.enable',
41
- ],
42
- }, function () {
43
- plugin.load_karma_ini()
44
- })
51
+ plugin.cfg = plugin.config.get(
52
+ 'karma.ini',
53
+ {
54
+ booleans: ['+asn.enable'],
55
+ },
56
+ function () {
57
+ plugin.load_karma_ini()
58
+ },
59
+ )
45
60
 
46
61
  plugin.merge_redis_ini()
47
62
 
@@ -73,7 +88,6 @@ exports.load_karma_ini = function () {
73
88
  }
74
89
 
75
90
  exports.results_init = async function (next, connection) {
76
-
77
91
  if (this.should_we_skip(connection)) {
78
92
  connection.logdebug(this, 'skipping')
79
93
  return next()
@@ -81,7 +95,7 @@ exports.results_init = async function (next, connection) {
81
95
 
82
96
  if (connection.results.get('karma')) {
83
97
  connection.logerror(this, 'this should never happen')
84
- return next() // init once per connection
98
+ return next() // init once per connection
85
99
  }
86
100
 
87
101
  if (this.cfg.awards) {
@@ -92,10 +106,9 @@ exports.results_init = async function (next, connection) {
92
106
  const award = this.cfg.awards[key].toString()
93
107
  todo[key] = award
94
108
  }
95
- connection.results.add(this, { score:0, todo })
96
- }
97
- else {
98
- connection.results.add(this, { score:0 })
109
+ connection.results.add(this, { score: 0, todo })
110
+ } else {
111
+ connection.results.add(this, { score: 0 })
99
112
  }
100
113
 
101
114
  if (!connection.server.notes.redis) {
@@ -103,7 +116,7 @@ exports.results_init = async function (next, connection) {
103
116
  return next()
104
117
  }
105
118
 
106
- if (!this.result_awards) return next() // not configured
119
+ if (!this.result_awards) return next() // not configured
107
120
 
108
121
  if (connection.notes.redis) {
109
122
  connection.logdebug(this, `redis already subscribed`)
@@ -128,9 +141,8 @@ exports.preparse_result_awards = function () {
128
141
  // arrange results for rapid traversal by check_result() :
129
142
  // ex: karma.result_awards.clamd.fail = { .... }
130
143
  for (const anum of Object.keys(cra)) {
131
-
132
- const [pi_name, prop, operator, value, award, reason, resolv]
133
- = cra[anum].split(/(?:\s*\|\s*)/)
144
+ const [pi_name, prop, operator, value, award, reason, resolv] =
145
+ cra[anum].split(/(?:\s*\|\s*)/)
134
146
 
135
147
  const ra = this.result_awards
136
148
 
@@ -143,7 +155,6 @@ exports.preparse_result_awards = function () {
143
155
  }
144
156
 
145
157
  exports.check_result = function (connection, message) {
146
-
147
158
  // connection.loginfo(this, message);
148
159
  // {"plugin":"karma","result":{"fail":"spamassassin.hits"}}
149
160
  // {"plugin":"geoip","result":{"country":"CN"}}
@@ -152,13 +163,14 @@ exports.check_result = function (connection, message) {
152
163
  if (m && m.result && m.result.asn) {
153
164
  this.check_result_asn(m.result.asn, connection)
154
165
  }
155
- if (!this.result_awards[m.plugin]) return // no awards for plugin
166
+ if (!this.result_awards[m.plugin]) return // no awards for plugin
156
167
 
157
- for (const r of Object.keys(m.result)) { // each result in mess
158
- if (r === 'emit') continue // r: pass, fail, skip, err, ...
168
+ for (const r of Object.keys(m.result)) {
169
+ // each result in mess
170
+ if (r === 'emit') continue // r: pass, fail, skip, err, ...
159
171
 
160
172
  const pi_prop = this.result_awards[m.plugin][r]
161
- if (!pi_prop) continue // no award for this plugin property
173
+ if (!pi_prop) continue // no award for this plugin property
162
174
 
163
175
  const thisResult = m.result[r]
164
176
  // ignore empty arrays, objects, and strings
@@ -169,8 +181,8 @@ exports.check_result = function (connection, message) {
169
181
  if (typeof thisResult === 'string' && !thisResult) continue // empty
170
182
 
171
183
  // do any award conditions match this result?
172
- for (let i=0; i < pi_prop.length; i++) { // each award...
173
- const thisAward = pi_prop[i]
184
+ for (const thisAward of pi_prop) {
185
+ // each award...
174
186
  // { id: '011', operator: 'equals', value: 'all_bad', award: '-2'}
175
187
  const thisResArr = this.result_as_array(thisResult)
176
188
  switch (thisAward.operator) {
@@ -197,14 +209,13 @@ exports.check_result = function (connection, message) {
197
209
  }
198
210
 
199
211
  exports.result_as_array = function (result) {
200
-
201
212
  if (typeof result === 'string') return [result]
202
213
  if (typeof result === 'number') return [result]
203
214
  if (typeof result === 'boolean') return [result]
204
215
  if (Array.isArray(result)) return result
205
216
  if (typeof result === 'object') {
206
217
  const array = []
207
- Object.keys(result).forEach(tr => {
218
+ Object.keys(result).forEach((tr) => {
208
219
  array.push(result[tr])
209
220
  })
210
221
  return array
@@ -217,94 +228,88 @@ exports.check_result_asn = function (asn, conn) {
217
228
  if (!this.cfg.asn_awards) return
218
229
  if (!this.cfg.asn_awards[asn]) return
219
230
 
220
- conn.results.incr(this, {score: this.cfg.asn_awards[asn]})
221
- conn.results.push(this, {fail: 'asn_awards'})
231
+ conn.results.incr(this, { score: this.cfg.asn_awards[asn] })
232
+ conn.results.push(this, { fail: 'asn_awards' })
222
233
  }
223
234
 
224
235
  exports.check_result_lt = function (thisResult, thisAward, conn) {
225
-
226
- for (let j=0; j < thisResult.length; j++) {
227
- const tr = parseFloat(thisResult[j])
236
+ for (const element of thisResult) {
237
+ const tr = parseFloat(element)
228
238
  if (tr >= parseFloat(thisAward.value)) continue
229
239
  if (conn.results.has('karma', 'awards', thisAward.id)) continue
230
240
 
231
- conn.results.incr(this, {score: thisAward.award})
232
- conn.results.push(this, {awards: thisAward.id})
241
+ conn.results.incr(this, { score: thisAward.award })
242
+ conn.results.push(this, { awards: thisAward.id })
233
243
  }
234
244
  }
235
245
 
236
246
  exports.check_result_gt = function (thisResult, thisAward, conn) {
237
-
238
- for (let j=0; j < thisResult.length; j++) {
239
- const tr = parseFloat(thisResult[j])
247
+ for (const element of thisResult) {
248
+ const tr = parseFloat(element)
240
249
  if (tr <= parseFloat(thisAward.value)) continue
241
250
  if (conn.results.has('karma', 'awards', thisAward.id)) continue
242
251
 
243
- conn.results.incr(this, {score: thisAward.award})
244
- conn.results.push(this, {awards: thisAward.id})
252
+ conn.results.incr(this, { score: thisAward.award })
253
+ conn.results.push(this, { awards: thisAward.id })
245
254
  }
246
255
  }
247
256
 
248
257
  exports.check_result_equal = function (thisResult, thisAward, conn) {
249
-
250
- for (let j=0; j < thisResult.length; j++) {
258
+ for (const element of thisResult) {
251
259
  if (thisAward.value === 'true') {
252
- if (!thisResult[j]) continue
253
- }
254
- else {
255
- if (thisResult[j] != thisAward.value) continue
260
+ if (!element) continue
261
+ } else {
262
+ if (element != thisAward.value) continue
256
263
  }
257
264
  if (!/auth/.test(thisAward.plugin)) {
258
265
  // only auth attempts are scored > 1x
259
266
  if (conn.results.has('karma', 'awards', thisAward.id)) continue
260
267
  }
261
268
 
262
- conn.results.incr(this, {score: thisAward.award})
263
- conn.results.push(this, {awards: thisAward.id})
269
+ conn.results.incr(this, { score: thisAward.award })
270
+ conn.results.push(this, { awards: thisAward.id })
264
271
  }
265
272
  }
266
273
 
267
274
  exports.check_result_match = function (thisResult, thisAward, conn) {
268
275
  const re = new RegExp(thisAward.value, 'i')
269
276
 
270
- for (let i=0; i < thisResult.length; i++) {
271
- if (!re.test(thisResult[i])) continue
277
+ for (const element of thisResult) {
278
+ if (!re.test(element)) continue
272
279
  if (conn.results.has('karma', 'awards', thisAward.id)) continue
273
280
 
274
- conn.results.incr(this, {score: thisAward.award})
275
- conn.results.push(this, {awards: thisAward.id})
281
+ conn.results.incr(this, { score: thisAward.award })
282
+ conn.results.push(this, { awards: thisAward.id })
276
283
  }
277
284
  }
278
285
 
279
286
  exports.check_result_length = function (thisResult, thisAward, conn) {
280
-
281
- for (let j=0; j < thisResult.length; j++) {
287
+ for (const element of thisResult) {
282
288
  const [operator, qty] = thisAward.value.split(/\s+/) // requires node 6+
283
289
 
284
290
  switch (operator) {
285
291
  case 'eq':
286
292
  case 'equal':
287
293
  case 'equals':
288
- if (parseInt(thisResult[j], 10) != parseInt(qty, 10)) continue
294
+ if (parseInt(element, 10) != parseInt(qty, 10)) continue
289
295
  break
290
296
  case 'gt':
291
- if (parseInt(thisResult[j], 10) <= parseInt(qty, 10)) continue
297
+ if (parseInt(element, 10) <= parseInt(qty, 10)) continue
292
298
  break
293
299
  case 'lt':
294
- if (parseInt(thisResult[j], 10) >= parseInt(qty, 10)) continue
300
+ if (parseInt(element, 10) >= parseInt(qty, 10)) continue
295
301
  break
296
302
  default:
297
303
  conn.results.add(this, { err: `invalid operator: ${operator}` })
298
304
  continue
299
305
  }
300
306
 
301
- conn.results.incr(this, {score: thisAward.award})
302
- conn.results.push(this, {awards: thisAward.id })
307
+ conn.results.incr(this, { score: thisAward.award })
308
+ conn.results.push(this, { awards: thisAward.id })
303
309
  }
304
310
  }
305
311
 
306
312
  exports.check_result_exists = function (thisResult, thisAward, conn) {
307
-
308
313
  /* eslint-disable no-unused-vars */
309
314
  for (const r of thisResult) {
310
315
  const [operator, qty] = thisAward.value.split(/\s+/)
@@ -318,18 +323,17 @@ exports.check_result_exists = function (thisResult, thisAward, conn) {
318
323
  continue
319
324
  }
320
325
 
321
- conn.results.incr(this, {score: thisAward.award})
322
- conn.results.push(this, {awards: thisAward.id})
326
+ conn.results.incr(this, { score: thisAward.award })
327
+ conn.results.push(this, { awards: thisAward.id })
323
328
  }
324
329
  }
325
330
 
326
331
  exports.apply_tarpit = function (connection, hook, score, next) {
327
-
328
332
  if (!this.cfg.tarpit) return next() // tarpit disabled in config
329
333
 
330
334
  // If tarpit is enabled on the reset_transaction hook, Haraka doesn't
331
335
  // wait. Then bad things happen, like a Haraka crash.
332
- if (utils.in_array(hook, ['reset_transaction','queue'])) return next()
336
+ if (utils.in_array(hook, ['reset_transaction', 'queue'])) return next()
333
337
 
334
338
  // no delay for senders with good karma
335
339
  const k = connection.results.get('karma')
@@ -348,17 +352,18 @@ exports.apply_tarpit = function (connection, hook, score, next) {
348
352
  }
349
353
 
350
354
  exports.tarpit_delay = function (score, connection, hook, k) {
351
-
352
355
  if (this.cfg.tarpit.delay && parseFloat(this.cfg.tarpit.delay)) {
353
356
  connection.logdebug(this, 'static tarpit')
354
357
  return parseFloat(this.cfg.tarpit.delay)
355
358
  }
356
359
 
357
- const delay = score * -1 // progressive tarpit
360
+ const delay = score * -1 // progressive tarpit
358
361
 
359
362
  // detect roaming users based on MSA ports that require auth
360
- if (utils.in_array(connection.local.port, [587,465]) &&
361
- utils.in_array(hook, ['ehlo','connect'])) {
363
+ if (
364
+ utils.in_array(connection.local.port, [587, 465]) &&
365
+ utils.in_array(hook, ['ehlo', 'connect'])
366
+ ) {
362
367
  return this.tarpit_delay_msa(connection, delay, k)
363
368
  }
364
369
 
@@ -377,7 +382,7 @@ exports.tarpit_delay_msa = function (connection, delay, k) {
377
382
  delay = parseFloat(delay)
378
383
 
379
384
  // Reduce delay for good history
380
- const history = ((k.good || 0) - (k.bad || 0))
385
+ const history = (k.good || 0) - (k.bad || 0)
381
386
  if (history > 0) {
382
387
  delay = delay - 2
383
388
  connection.logdebug(this, `${trg} history: ${delay}`)
@@ -410,12 +415,12 @@ exports.should_we_deny = function (next, connection, hook) {
410
415
  const r = connection.results.get('karma')
411
416
  if (!r) return next()
412
417
 
413
- this.check_awards(connection) // update awards first
418
+ this.check_awards(connection) // update awards first
414
419
 
415
420
  const score = parseFloat(r.score)
416
- if (isNaN(score)) {
421
+ if (isNaN(score)) {
417
422
  connection.logerror(this, 'score is NaN')
418
- connection.results.add(this, {score: 0})
423
+ connection.results.add(this, { score: 0 })
419
424
  return next()
420
425
  }
421
426
 
@@ -451,10 +456,10 @@ exports.hook_deny = function (next, connection, params) {
451
456
 
452
457
  // let pi_deny = params[0]; // (constants.deny, denysoft, ok)
453
458
  // let pi_message = params[1];
454
- const pi_name = params[2]
459
+ const pi_name = params[2]
455
460
  // let pi_function = params[3];
456
461
  // let pi_params = params[4];
457
- const pi_hook = params[5]
462
+ const pi_hook = params[5]
458
463
 
459
464
  // exceptions, whose 'DENY' should not be captured
460
465
  if (pi_name) {
@@ -469,7 +474,7 @@ exports.hook_deny = function (next, connection, params) {
469
474
  connection.results.add(this, { msg: `deny: ${pi_name}` })
470
475
  connection.results.incr(this, { score: -2 })
471
476
 
472
- next(constants.OK) // resume the connection
477
+ next(constants.OK) // resume the connection
473
478
  }
474
479
 
475
480
  exports.hook_connect = function (next, connection) {
@@ -527,12 +532,11 @@ exports.hook_queue_outbound = function (next, connection) {
527
532
  exports.hook_reset_transaction = function (next, connection) {
528
533
  if (this.should_we_skip(connection)) return next()
529
534
 
530
- connection.results.add(this, {emit: true})
535
+ connection.results.add(this, { emit: true })
531
536
  this.should_we_deny(next, connection, 'reset_transaction')
532
537
  }
533
538
 
534
539
  exports.hook_unrecognized_command = function (next, connection, params) {
535
-
536
540
  if (this.should_we_skip(connection)) return next()
537
541
 
538
542
  // in case karma is in config/plugins before tls
@@ -541,8 +545,8 @@ exports.hook_unrecognized_command = function (next, connection, params) {
541
545
  // in case karma is in config/plugins before AUTH plugin(s)
542
546
  if (connection.notes.authenticating) return next()
543
547
 
544
- connection.results.incr(this, {score: -1})
545
- connection.results.add(this, {fail: `cmd:(${params})`})
548
+ connection.results.incr(this, { score: -1 })
549
+ connection.results.add(this, { fail: `cmd:(${params})` })
546
550
 
547
551
  return this.should_we_deny(next, connection, 'unrecognized_command')
548
552
  }
@@ -553,72 +557,74 @@ exports.ip_history_from_redis = function (next, connection) {
553
557
  if (this.should_we_skip(connection)) return next()
554
558
 
555
559
  const expire = (this.cfg.redis.expire_days || 60) * 86400 // to days
556
- const dbkey = `karma|${connection.remote.ip}`
560
+ const dbkey = `karma|${connection.remote.ip}`
557
561
 
558
562
  // redis plugin is emitting errors, no need to here
559
563
  if (!this.db) return next()
560
564
 
561
- this.db.hGetAll(dbkey).then(dbr => {
562
- if (dbr === null) {
563
- plugin.init_ip(dbkey, connection.remote.ip, expire)
564
- return next()
565
- }
565
+ this.db
566
+ .hGetAll(dbkey)
567
+ .then((dbr) => {
568
+ if (dbr === null) {
569
+ plugin.init_ip(dbkey, connection.remote.ip, expire)
570
+ return next()
571
+ }
566
572
 
567
- plugin.db.multi()
568
- .hIncrBy(dbkey, 'connections', 1) // increment total conn
569
- .expire(dbkey, expire) // extend expiration
570
- .exec()
571
- .catch(err => {
572
- connection.results.add(plugin, { err })
573
- })
574
-
575
- const results = {
576
- good: dbr.good,
577
- bad: dbr.bad,
578
- connections: dbr.connections,
579
- history: parseInt((dbr.good || 0) - (dbr.bad || 0)),
580
- emit: true,
581
- }
573
+ plugin.db
574
+ .multi()
575
+ .hIncrBy(dbkey, 'connections', 1) // increment total conn
576
+ .expire(dbkey, expire) // extend expiration
577
+ .exec()
578
+ .catch((err) => {
579
+ connection.results.add(plugin, { err })
580
+ })
581
+
582
+ const results = {
583
+ good: dbr.good,
584
+ bad: dbr.bad,
585
+ connections: dbr.connections,
586
+ history: parseInt((dbr.good || 0) - (dbr.bad || 0)),
587
+ emit: true,
588
+ }
582
589
 
583
- // Careful: don't become self-fulfilling prophecy.
584
- if (parseInt(dbr.good) > 5 && parseInt(dbr.bad) === 0) {
585
- results.pass = 'all_good'
586
- }
587
- if (parseInt(dbr.bad) > 5 && parseInt(dbr.good) === 0) {
588
- results.fail = 'all_bad'
589
- }
590
+ // Careful: don't become self-fulfilling prophecy.
591
+ if (parseInt(dbr.good) > 5 && parseInt(dbr.bad) === 0) {
592
+ results.pass = 'all_good'
593
+ }
594
+ if (parseInt(dbr.bad) > 5 && parseInt(dbr.good) === 0) {
595
+ results.fail = 'all_bad'
596
+ }
590
597
 
591
- connection.results.add(plugin, results)
598
+ connection.results.add(plugin, results)
592
599
 
593
- plugin.check_awards(connection)
594
- next()
595
- })
596
- .catch(err => {
600
+ plugin.check_awards(connection)
601
+ next()
602
+ })
603
+ .catch((err) => {
597
604
  connection.results.add(plugin, { err })
598
605
  next()
599
606
  })
600
607
  }
601
608
 
602
609
  exports.hook_mail = function (next, connection, params) {
603
-
604
610
  if (this.should_we_skip(connection)) return next()
605
611
 
606
612
  this.check_spammy_tld(params[0], connection)
607
613
 
608
614
  // look for invalid (RFC 5321,(2)821) space in envelope from
609
615
  const full_from = connection.current_line
610
- if (full_from.toUpperCase().substring(0,11) !== 'MAIL FROM:<') {
616
+ if (full_from.toUpperCase().substring(0, 11) !== 'MAIL FROM:<') {
611
617
  connection.loginfo(this, `RFC ignorant env addr format: ${full_from}`)
612
- connection.results.add(this, {fail: 'rfc5321.MailFrom'})
618
+ connection.results.add(this, { fail: 'rfc5321.MailFrom' })
613
619
  }
614
620
 
615
621
  // apply TLS awards (if defined)
616
622
  if (this.cfg.tls !== undefined) {
617
623
  if (this.cfg.tls.set && connection.tls.enabled) {
618
- connection.results.incr(this, {score: this.cfg.tls.set})
624
+ connection.results.incr(this, { score: this.cfg.tls.set })
619
625
  }
620
626
  if (this.cfg.tls.unset && !connection.tls.enabled) {
621
- connection.results.incr(this, {score: this.cfg.tls.unset})
627
+ connection.results.incr(this, { score: this.cfg.tls.unset })
622
628
  }
623
629
  }
624
630
 
@@ -626,7 +632,6 @@ exports.hook_mail = function (next, connection, params) {
626
632
  }
627
633
 
628
634
  exports.hook_rcpt = function (next, connection, params) {
629
-
630
635
  if (this.should_we_skip(connection)) return next()
631
636
 
632
637
  const rcpt = params[0]
@@ -637,23 +642,22 @@ exports.hook_rcpt = function (next, connection, params) {
637
642
  // odds of from_user=rcpt_user in ham: < 1%, in spam > 40%
638
643
  // 2015-05 30-day sample: 84% spam correlation
639
644
  if (connection?.transaction?.mail_from?.user === rcpt.user) {
640
- connection.results.add(this, {fail: 'env_user_match'})
645
+ connection.results.add(this, { fail: 'env_user_match' })
641
646
  }
642
647
 
643
648
  this.check_syntax_RcptTo(connection)
644
649
 
645
- connection.results.add(this, {fail: 'rcpt_to'})
650
+ connection.results.add(this, { fail: 'rcpt_to' })
646
651
 
647
652
  return this.should_we_deny(next, connection, 'rcpt')
648
653
  }
649
654
 
650
655
  exports.hook_rcpt_ok = function (next, connection, rcpt) {
651
-
652
656
  if (this.should_we_skip(connection)) return next()
653
657
 
654
658
  const txn = connection.transaction
655
659
  if (txn && txn.mail_from && txn.mail_from.user === rcpt.user) {
656
- connection.results.add(this, {fail: 'env_user_match'})
660
+ connection.results.add(this, { fail: 'env_user_match' })
657
661
  }
658
662
 
659
663
  this.check_syntax_RcptTo(connection)
@@ -666,7 +670,7 @@ exports.hook_data_post = function (next, connection) {
666
670
 
667
671
  if (this.should_we_skip(connection)) return next()
668
672
 
669
- this.check_awards(connection) // update awards
673
+ this.check_awards(connection) // update awards
670
674
 
671
675
  const results = connection.results.collate(this)
672
676
  connection.logdebug(this, `adding header: ${results}`)
@@ -692,13 +696,13 @@ exports.hook_disconnect = function (next, connection) {
692
696
 
693
697
  const k = connection.results.get('karma')
694
698
  if (!k || k.score === undefined) {
695
- connection.results.add(this, {err: 'karma results missing'})
699
+ connection.results.add(this, { err: 'karma results missing' })
696
700
  return next()
697
701
  }
698
702
 
699
703
  if (!this.cfg.thresholds) {
700
704
  this.check_awards(connection)
701
- connection.results.add(this, {msg: 'no action', emit: true })
705
+ connection.results.add(this, { msg: 'no action', emit: true })
702
706
  return next()
703
707
  }
704
708
 
@@ -709,12 +713,11 @@ exports.hook_disconnect = function (next, connection) {
709
713
  this.increment(connection, 'bad', 1)
710
714
  }
711
715
 
712
- connection.results.add(this, {emit: true })
716
+ connection.results.add(this, { emit: true })
713
717
  next()
714
718
  }
715
719
 
716
720
  exports.get_award_loc_from_note = function (connection, award) {
717
-
718
721
  if (connection.transaction) {
719
722
  const obj = this.assemble_note_obj(connection.transaction, award)
720
723
  if (obj) return obj
@@ -729,7 +732,6 @@ exports.get_award_loc_from_note = function (connection, award) {
729
732
  }
730
733
 
731
734
  exports.get_award_loc_from_results = function (connection, loc_bits) {
732
-
733
735
  let pi_name = loc_bits[1]
734
736
  let notekey = loc_bits[2]
735
737
 
@@ -756,16 +758,22 @@ exports.get_award_location = function (connection, award_key) {
756
758
  const loc_bits = bits[0].split('.')
757
759
  if (loc_bits.length === 1) return connection[bits[0]] // ex: relaying
758
760
 
759
- if (loc_bits[0] === 'notes') { // ex: notes.spf_mail_helo
761
+ if (loc_bits[0] === 'notes') {
762
+ // ex: notes.spf_mail_helo
760
763
  return this.get_award_loc_from_note(connection, bits[0])
761
764
  }
762
765
 
763
- if (loc_bits[0] === 'results') { // ex: results.geoip.distance
766
+ if (loc_bits[0] === 'results') {
767
+ // ex: results.geoip.distance
764
768
  return this.get_award_loc_from_results(connection, loc_bits)
765
769
  }
766
770
 
767
771
  // ex: transaction.results.spf
768
- if (connection.transaction && loc_bits[0] === 'transaction' && loc_bits[1] === 'results') {
772
+ if (
773
+ connection.transaction &&
774
+ loc_bits[0] === 'transaction' &&
775
+ loc_bits[1] === 'results'
776
+ ) {
769
777
  loc_bits.shift()
770
778
  return this.get_award_loc_from_results(connection.transaction, loc_bits)
771
779
  }
@@ -776,11 +784,13 @@ exports.get_award_location = function (connection, award_key) {
776
784
  exports.get_award_condition = function (note_key, note_val) {
777
785
  let wants
778
786
  const keybits = note_key.split('@')
779
- if (keybits[1]) { wants = keybits[1] }
787
+ if (keybits[1]) {
788
+ wants = keybits[1]
789
+ }
780
790
 
781
791
  const valbits = note_val.split(/\s+/)
782
792
  if (!valbits[1]) return wants
783
- if (valbits[1] !== 'if') return wants // no if condition
793
+ if (valbits[1] !== 'if') return wants // no if condition
784
794
 
785
795
  if (valbits[2].match(/^(equals|gt|lt|match)$/)) {
786
796
  if (valbits[3]) wants = valbits[3]
@@ -806,14 +816,16 @@ exports.check_awards = function (connection) {
806
816
  // test the desired condition
807
817
  const bits = award_terms.split(/\s+/)
808
818
  const award = parseFloat(bits[0])
809
- if (!bits[1] || bits[1] !== 'if') { // no if conditions
810
- if (!note) continue // failed truth test
811
- if (!wants) { // no wants, truth matches
819
+ if (!bits[1] || bits[1] !== 'if') {
820
+ // no if conditions
821
+ if (!note) continue // failed truth test
822
+ if (!wants) {
823
+ // no wants, truth matches
812
824
  this.apply_award(connection, key, award)
813
825
  delete karma.todo[key]
814
826
  continue
815
827
  }
816
- if (note !== wants) continue // didn't match
828
+ if (note !== wants) continue // didn't match
817
829
  }
818
830
 
819
831
  // connection.loginfo(this, `check_awards, case matching for: ${wants}`
@@ -840,7 +852,9 @@ exports.check_awards = function (connection) {
840
852
  continue
841
853
  case 'length': {
842
854
  const operator = bits[3]
843
- if (bits[4]) { wants = bits[4] }
855
+ if (bits[4]) {
856
+ wants = bits[4]
857
+ }
844
858
  switch (operator) {
845
859
  case 'gt':
846
860
  if (note.length <= parseFloat(wants)) continue
@@ -852,14 +866,19 @@ exports.check_awards = function (connection) {
852
866
  if (note.length !== parseFloat(wants)) continue
853
867
  break
854
868
  default:
855
- connection.logerror(this, `length operator "${operator}" not supported.`)
869
+ connection.logerror(
870
+ this,
871
+ `length operator "${operator}" not supported.`,
872
+ )
856
873
  continue
857
874
  }
858
875
  break
859
876
  }
860
- case 'in': // if in pass whitelisted
877
+ case 'in': // if in pass whitelisted
861
878
  // let list = bits[3];
862
- if (bits[4]) { wants = bits[4] }
879
+ if (bits[4]) {
880
+ wants = bits[4]
881
+ }
863
882
  if (!Array.isArray(note)) continue
864
883
  if (!wants) continue
865
884
  if (note.indexOf(wants) !== -1) break // found!
@@ -874,25 +893,31 @@ exports.check_awards = function (connection) {
874
893
 
875
894
  exports.apply_award = function (connection, nl, award) {
876
895
  if (!award) return
877
- if (isNaN(award)) { // garbage in config
896
+ if (isNaN(award)) {
897
+ // garbage in config
878
898
  connection.logerror(this, `non-numeric award from: ${nl}:${award}`)
879
899
  return
880
900
  }
881
901
 
882
- const bits = nl.split('@'); nl = bits[0] // strip off @... if present
902
+ const bits = nl.split('@')
903
+ nl = bits[0] // strip off @... if present
883
904
 
884
- connection.results.incr(this, {score: award})
905
+ connection.results.incr(this, { score: award })
885
906
  connection.logdebug(this, `applied ${nl}:${award}`)
886
907
 
887
- let trimmed = nl.substring(0, 5) === 'notes' ? nl.substring(6) :
888
- nl.substring(0, 7) === 'results' ? nl.substring(8) :
889
- nl.substring(0,19) === 'transaction.results' ?
890
- nl.substring(20) : nl
908
+ let trimmed =
909
+ nl.substring(0, 5) === 'notes'
910
+ ? nl.substring(6)
911
+ : nl.substring(0, 7) === 'results'
912
+ ? nl.substring(8)
913
+ : nl.substring(0, 19) === 'transaction.results'
914
+ ? nl.substring(20)
915
+ : nl
891
916
 
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)
917
+ if (trimmed.substring(0, 7) === 'rcpt_to') trimmed = trimmed.substring(8)
918
+ if (trimmed.substring(0, 7) === 'mail_from') trimmed = trimmed.substring(10)
919
+ if (trimmed.substring(0, 7) === 'connect') trimmed = trimmed.substring(8)
920
+ if (trimmed.substring(0, 4) === 'data') trimmed = trimmed.substring(5)
896
921
 
897
922
  if (award > 0) connection.results.add(this, { pass: trimmed })
898
923
  if (award < 0) connection.results.add(this, { fail: trimmed })
@@ -900,7 +925,7 @@ exports.apply_award = function (connection, nl, award) {
900
925
 
901
926
  exports.check_spammy_tld = function (mail_from, connection) {
902
927
  if (!this.cfg.spammy_tlds) return
903
- if (mail_from.isNull()) return // null sender (bounce)
928
+ if (mail_from.isNull()) return // null sender (bounce)
904
929
 
905
930
  const from_tld = mail_from.host.split('.').pop()
906
931
  // connection.logdebug(this, `from_tld: ${from_tld}`);
@@ -908,17 +933,17 @@ exports.check_spammy_tld = function (mail_from, connection) {
908
933
  const tld_penalty = parseFloat(this.cfg.spammy_tlds[from_tld] || 0)
909
934
  if (tld_penalty === 0) return
910
935
 
911
- connection.results.incr(this, {score: tld_penalty})
912
- connection.results.add(this, {fail: 'spammy.TLD'})
936
+ connection.results.incr(this, { score: tld_penalty })
937
+ connection.results.add(this, { fail: 'spammy.TLD' })
913
938
  }
914
939
 
915
940
  exports.check_syntax_RcptTo = function (connection) {
916
941
  // look for an illegal (RFC 5321,(2)821) space in envelope recipient
917
942
  const full_rcpt = connection.current_line
918
- if (full_rcpt.toUpperCase().substring(0,9) === 'RCPT TO:<') return
943
+ if (full_rcpt.toUpperCase().substring(0, 9) === 'RCPT TO:<') return
919
944
 
920
945
  connection.loginfo(this, `illegal envelope address format: ${full_rcpt}`)
921
- connection.results.add(this, {fail: 'rfc5321.RcptTo'})
946
+ connection.results.add(this, { fail: 'rfc5321.RcptTo' })
922
947
  }
923
948
 
924
949
  exports.assemble_note_obj = function (prefix, key) {
@@ -942,50 +967,52 @@ exports.check_asn = function (connection, asnkey) {
942
967
 
943
968
  if (this.cfg.asn.report_as) report_as.name = this.cfg.asn.report_as
944
969
 
945
- this.db.hGetAll(asnkey).then(res => {
946
- if (res === null) {
947
- const expire = (this.cfg.redis.expire_days || 60) * 86400 // days
948
- this.init_asn(asnkey, expire)
949
- return
950
- }
951
-
952
- this.db.hIncrBy(asnkey, 'connections', 1)
953
- const asn_score = parseInt(res.good || 0) - (res.bad || 0)
954
- const asn_results = {
955
- asn_score,
956
- asn_connections: res.connections,
957
- asn_good: res.good,
958
- asn_bad: res.bad,
959
- emit: true,
960
- }
970
+ this.db
971
+ .hGetAll(asnkey)
972
+ .then((res) => {
973
+ if (res === null) {
974
+ const expire = (this.cfg.redis.expire_days || 60) * 86400 // days
975
+ this.init_asn(asnkey, expire)
976
+ return
977
+ }
961
978
 
962
- if (asn_score) {
963
- if (asn_score < -5) {
964
- asn_results.fail = 'asn:history'
979
+ this.db.hIncrBy(asnkey, 'connections', 1)
980
+ const asn_score = parseInt(res.good || 0) - (res.bad || 0)
981
+ const asn_results = {
982
+ asn_score,
983
+ asn_connections: res.connections,
984
+ asn_good: res.good,
985
+ asn_bad: res.bad,
986
+ emit: true,
965
987
  }
966
- else if (asn_score > 5) {
967
- asn_results.pass = 'asn:history'
988
+
989
+ if (asn_score) {
990
+ if (asn_score < -5) {
991
+ asn_results.fail = 'asn:history'
992
+ } else if (asn_score > 5) {
993
+ asn_results.pass = 'asn:history'
994
+ }
968
995
  }
969
- }
970
996
 
971
- if (parseInt(res.bad) > 5 && parseInt(res.good) === 0) {
972
- asn_results.fail = 'asn:all_bad'
973
- }
974
- if (parseInt(res.good) > 5 && parseInt(res.bad) === 0) {
975
- asn_results.pass = 'asn:all_good'
976
- }
997
+ if (parseInt(res.bad) > 5 && parseInt(res.good) === 0) {
998
+ asn_results.fail = 'asn:all_bad'
999
+ }
1000
+ if (parseInt(res.good) > 5 && parseInt(res.bad) === 0) {
1001
+ asn_results.pass = 'asn:all_good'
1002
+ }
977
1003
 
978
- connection.results.add(report_as, asn_results)
979
- })
980
- .catch(err => {
1004
+ connection.results.add(report_as, asn_results)
1005
+ })
1006
+ .catch((err) => {
981
1007
  connection.results.add(this, { err })
982
1008
  })
983
1009
  }
984
1010
 
985
1011
  exports.init_ip = async function (dbkey, rip, expire) {
986
1012
  if (!this.db) return
987
- await this.db.multi()
988
- .hmSet(dbkey, {'bad': 0, 'good': 0, 'connections': 1})
1013
+ await this.db
1014
+ .multi()
1015
+ .hmSet(dbkey, { bad: 0, good: 0, connections: 1 })
989
1016
  .expire(dbkey, expire)
990
1017
  .exec()
991
1018
  }
@@ -1000,8 +1027,9 @@ exports.get_asn_key = function (connection) {
1000
1027
 
1001
1028
  exports.init_asn = function (asnkey, expire) {
1002
1029
  if (!this.db) return
1003
- this.db.multi()
1004
- .hmSet(asnkey, {'bad': 0, 'good': 0, 'connections': 1})
1005
- .expire(asnkey, expire * 2) // keep ASN longer
1030
+ this.db
1031
+ .multi()
1032
+ .hmSet(asnkey, { bad: 0, good: 0, connections: 1 })
1033
+ .expire(asnkey, expire * 2) // keep ASN longer
1006
1034
  .exec()
1007
1035
  }