diff --git a/.codeclimate.yml b/.codeclimate.yml
index c587f0b..ac041b3 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,9 +1,9 @@
engines:
eslint:
enabled: true
- channel: "eslint-8"
+ channel: 'eslint-9'
config:
- config: ".eslintrc.yaml"
+ config: 'eslint.config.mjs'
checks:
return-statements:
@@ -13,5 +13,5 @@ checks:
threshold: 10
ratings:
- paths:
- - "**.js"
+ paths:
+ - '**.js'
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
deleted file mode 100644
index 3f51e95..0000000
--- a/.eslintrc.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-env:
- node: true
- es6: true
- mocha: true
- es2020: true
-
-plugins: [ haraka ]
-
-extends: [ eslint:recommended, plugin:haraka/recommended ]
-
-root: true
-
-rules:
- indent: [2, 2, { SwitchCase: 1 } ]
-
-globals:
- OK: true
- CONT: true
- DENY: true
- DENYSOFT: true
- DENYDISCONNECT: true
- DENYSOFTDISCONNECT: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12d7556..6395e0f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,20 +1,19 @@
name: CI
-on: [ push, pull_request ]
+on: [push, pull_request]
env:
CI: true
jobs:
-
lint:
uses: haraka/.github/.github/workflows/lint.yml@master
ubuntu:
- needs: [ lint ]
+ needs: [lint]
uses: haraka/.github/.github/workflows/ubuntu.yml@master
windows:
- needs: [ lint ]
+ needs: [lint]
uses: haraka/.github/.github/workflows/windows.yml@master
- if: ${{ false }} # disabled, until Redis for GHA Windows exists
\ No newline at end of file
+ if: ${{ false }} # disabled, until Redis for GHA Windows exists
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 383aca2..816e8c3 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,10 +1,10 @@
-name: "CodeQL"
+name: 'CodeQL'
on:
push:
- branches: [ master ]
+ branches: [master]
pull_request:
- branches: [ master ]
+ branches: [master]
schedule:
- cron: '18 7 * * 4'
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9bfdec3..e81c15f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -14,4 +14,3 @@ jobs:
publish:
uses: haraka/.github/.github/workflows/publish.yml@master
secrets: inherit
-
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..8ded5e0
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,2 @@
+singleQuote: true
+semi: false
diff --git a/.release b/.release
index 0890e94..0bf2a09 160000
--- a/.release
+++ b/.release
@@ -1 +1 @@
-Subproject commit 0890e945e4e061c96c7b2ab45017525904c17728
+Subproject commit 0bf2a098d4792848c2103dfce0f911e00a14709e
diff --git a/Changes.md b/CHANGELOG.md
similarity index 67%
rename from Changes.md
rename to CHANGELOG.md
index aab59ca..db3432c 100644
--- a/Changes.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
+# Changelog
-#### N.N.N - YYYY-MM-DD
+The format is based on [Keep a Changelog](https://keepachangelog.com/).
+### Unreleased
+
+### [1.1.1] - 2025-01-22
+
+- deps: bump all to latest
+- style: automated code formatting with prettier
+- populate [files] in package.json
+- doc(CONTRIBUTORS): added
+- doc: mv Changes.md CHANGELOG.md
### [1.1.0] - 2023-12-12
@@ -9,55 +19,50 @@
- ci(publish): only when package.json modified
- ci: use shared workflows
-
#### 1.0.9 - 2022-05-28
- restore a plugin = this, context demands it
- add .release submodule
-
### 1.0.8 - 2022-05-24
- feat(redis): compatibility with redis 4
- dep(eslint): 4 -> 8
-- dep(pi-redis): * -> 2
+- dep(pi-redis): \* -> 2
- ci: replace travis/appveyor with GHA
- lint: prefer-template
- doc(README): badge updates
- style: plugin -> this
-
### 1.0.7 - 2019-03-16
- assure already_matched has 'this' in context.
- add some es6 interpoled strings
-
### 1.0.6 - 2018-12-06
- move declarations up to restore `var` variable hoisting behavior
-
### 1.0.5 - 2018-11-16
- reduce severity of log message when no passing dkim results
-
### 1.0.4 - 2018-11-16
- quit redis conn after last test
- add skeleton support for TLS validation
- travis: remove node 4, add node 8 CI testing
-
### 1.0.3 - 2016-02-06
- inherit redis config from redis.ini
- add tests for get_recipient_domains_by_txn
-
### 1.0.2 - 2016-02-06
- inherit from haraka-plugin-redis (vs redis)
[1.1.0]: https://github.com/haraka/haraka-plugin-known-senders/releases/tag/1.1.0
+[1.1.1]: https://github.com/haraka/haraka-plugin-known-senders/releases/tag/v1.1.1
+[1.0.8]: https://github.com/haraka/haraka-plugin-known-senders/releases/tag/1.0.8
+[1.0.9]: https://github.com/haraka/haraka-plugin-known-senders/releases/tag/1.0.9
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..81c80c7
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,9 @@
+# Contributors
+
+This handcrafted artisinal software is brought to you by:
+
+| ![](https://avatars.githubusercontent.com/u/261635?v=4)
msimerson (53) |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+
+this file is generated by [.release](https://github.com/msimerson/.release).
+Contribute to this project to get your GitHub profile included here.
diff --git a/README.md b/README.md
index 7c08703..f7a4a38 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,13 @@
[![Build Status][ci-img]][ci-url]
[![Code Climate][clim-img]][clim-url]
-[![NPM][npm-img]][npm-url]
-
# haraka-plugin-known-senders
Increase the reputation of domains you exchange email with by sending them email.
## Synopsis
-Known Senders is based on the premise that domains users send email to are domains they also want to receive email from. By maintaining lists of domains that local users send email to, a weak but helpful form of automatic whitelisting is obtained.
+Known Senders is based on the premise that domains users send email to are domains they also want to receive email from. By maintaining lists of domains that local users send email to, a weak but helpful form of trust is obtained.
## How it works
@@ -25,18 +23,16 @@ Such a karma rule would look like this:
`280 = known-senders | pass | length | gt 0 | 5 | Known Sender`
-
## Authentication
Inbound messages are only checked against the known-senders list when the sender's Organizational Domain can be validated against a form of domain authentication.
-There has currently three authentication mechanisms that can validate that a sending host has authority to send on behalf of the [purported] sending domain:
+The current authentication mechanisms that can validate that a sending host has authority to send on behalf of the [purported] sending domain are:
- FCrDNS: [Forward Confirmed reverse DNS](https://en.wikipedia.org/wiki/Forward-confirmed_reverse_DNS)
- SPF: [Sender Policy Framework](https://en.wikipedia.org/wiki/Sender_Policy_Framework)
- DKIM: [DomainKeys Identified Mail](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail)
-
## Ideas for additional authentication mechanisms
Mechanisms need not be 100% effective to be useful.
@@ -44,16 +40,11 @@ Mechanisms need not be 100% effective to be useful.
- TLS certficate names
- GeoLocation
-
## Limitations
This plugin can boost the reputation of most marginally deliverable ham. Where it doesn't help is for messages coming from a Windows Exchange server (no DKIM signing support without $$$ 3rd party plugin) on a lame ISPs network that doesn't let them configure reverse DNS and whose admins haven't the clue to set up SPF properly.
-
-
[ci-img]: https://github.com/haraka/haraka-plugin-known-senders/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/haraka/haraka-plugin-known-senders/actions/workflows/ci.yml
[clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-known-senders/badges/gpa.svg
[clim-url]: https://codeclimate.com/github/haraka/haraka-plugin-known-senders
-[npm-img]: https://nodei.co/npm/haraka-plugin-known-senders.png
-[npm-url]: https://www.npmjs.com/package/haraka-plugin-known-senders
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..52a5848
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,28 @@
+import globals from 'globals'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import js from '@eslint/js'
+import { FlatCompat } from '@eslint/eslintrc'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+})
+
+export default [
+ ...compat.extends('@haraka'),
+ {
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.mocha,
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['warn', { caughtErrorsIgnorePattern: '^ignore' }],
+ },
+ },
+]
diff --git a/index.js b/index.js
index a3e7453..4820de7 100644
--- a/index.js
+++ b/index.js
@@ -1,31 +1,31 @@
-'use strict';
+'use strict'
-const tlds = require('haraka-tld');
+const tlds = require('haraka-tld')
exports.register = function () {
- this.inherits('haraka-plugin-redis');
+ this.inherits('haraka-plugin-redis')
- this.load_sender_ini();
+ this.load_sender_ini()
- this.register_hook('init_master', 'init_redis_plugin');
- this.register_hook('init_child', 'init_redis_plugin');
+ this.register_hook('init_master', 'init_redis_plugin')
+ this.register_hook('init_child', 'init_redis_plugin')
- this.register_hook('mail', 'is_authenticated');
- this.register_hook('rcpt_ok', 'check_recipient');
- this.register_hook('queue_ok', 'update_sender');
- this.register_hook('data_post', 'is_dkim_authenticated');
+ this.register_hook('mail', 'is_authenticated')
+ this.register_hook('rcpt_ok', 'check_recipient')
+ this.register_hook('queue_ok', 'update_sender')
+ this.register_hook('data_post', 'is_dkim_authenticated')
}
exports.load_sender_ini = function () {
- const plugin = this;
+ const plugin = this
plugin.cfg = plugin.config.get('known-senders.ini', function () {
- plugin.load_sender_ini();
- });
+ plugin.load_sender_ini()
+ })
if (plugin.cfg.ignored_ods === undefined) plugin.cfg.ignored_ods = {}
- plugin.merge_redis_ini();
+ plugin.merge_redis_ini()
}
/*
@@ -35,83 +35,86 @@ exports.load_sender_ini = function () {
*
* Context: these functions run after a message has been queued.
*
-*/
+ */
exports.update_sender = async function (next, connection, params) {
- const plugin = this;
+ const plugin = this
// queue_ok arguments: next, connection, msg
// ok 1390590369 qp 634 (F82E2DD5-9238-41DC-BC95-9C3A02716AD2.1)
- let sender_od;
- let rcpt_domains;
+ let sender_od
+ let rcpt_domains
- function errNext (err) {
- connection.logerror(plugin, `update_sender: ${err}`);
- next(null, null, sender_od, rcpt_domains);
+ function errNext(err) {
+ connection.logerror(plugin, `update_sender: ${err}`)
+ next(null, null, sender_od, rcpt_domains)
}
// connection.loginfo(this, params);
- if (!connection) return errNext('no connection');
- if (!connection.transaction) return errNext('no transaction');
- if (!connection.relaying) return next();
- const txn = connection.transaction;
+ if (!connection) return errNext('no connection')
+ if (!connection.transaction) return errNext('no transaction')
+ if (!connection.relaying) return next()
+ const txn = connection.transaction
- sender_od = this.get_sender_domain_by_txn(txn);
- if (!sender_od) return errNext('no sender domain');
- if (sender_od in plugin.cfg.ignored_ods) return errNext(`ignored(${sender_od})`);
+ sender_od = this.get_sender_domain_by_txn(txn)
+ if (!sender_od) return errNext('no sender domain')
+ if (sender_od in plugin.cfg.ignored_ods)
+ return errNext(`ignored(${sender_od})`)
- rcpt_domains = this.get_recipient_domains_by_txn(txn);
+ rcpt_domains = this.get_recipient_domains_by_txn(txn)
if (rcpt_domains.length === 0) {
- return errNext(`no rcpt ODs for ${sender_od}`);
+ return errNext(`no rcpt ODs for ${sender_od}`)
}
// within this function, the sender is a local domain
// and the recipient is an external domain
try {
- const multi = this.db.multi();
+ const multi = this.db.multi()
for (const rcptDomain of rcpt_domains) {
- multi.hIncrBy(sender_od, rcptDomain, 1);
+ multi.hIncrBy(sender_od, rcptDomain, 1)
}
const replies = await multi.exec()
for (let i = 0; i < rcpt_domains.length; i++) {
- connection.loginfo(this, `saved ${sender_od} : ${rcpt_domains[i]} : ${replies[i]}`);
+ connection.loginfo(
+ this,
+ `saved ${sender_od} : ${rcpt_domains[i]} : ${replies[i]}`,
+ )
}
- next(null, null, sender_od, rcpt_domains);
- }
- catch (err) {
- connection.logerror(this, err);
- next();
+ next(null, null, sender_od, rcpt_domains)
+ } catch (err) {
+ connection.logerror(this, err)
+ next()
}
}
exports.get_sender_domain_by_txn = function (txn) {
- if (!txn.mail_from || !txn.mail_from.host) return;
- const sender_od = tlds.get_organizational_domain(txn.mail_from.host);
+ if (!txn.mail_from || !txn.mail_from.host) return
+ const sender_od = tlds.get_organizational_domain(txn.mail_from.host)
if (txn.mail_from.host !== sender_od) {
- this.logdebug(`sender: ${txn.mail_from.host} -> ${sender_od}`);
+ this.logdebug(`sender: ${txn.mail_from.host} -> ${sender_od}`)
}
- return sender_od;
+ return sender_od
}
exports.get_recipient_domains_by_txn = function (txn) {
- const plugin = this;
+ const plugin = this
- const rcpt_domains = [];
- if (!txn.rcpt_to) return rcpt_domains;
+ const rcpt_domains = []
+ if (!txn.rcpt_to) return rcpt_domains
for (const element of txn.rcpt_to) {
- if (!element.host) continue;
- const rcpt_od = tlds.get_organizational_domain(element.host);
+ if (!element.host) continue
+ const rcpt_od = tlds.get_organizational_domain(element.host)
if (element.host !== rcpt_od) {
- plugin.loginfo(`rcpt: ${element.host} -> ${rcpt_od}`);
+ plugin.loginfo(`rcpt: ${element.host} -> ${rcpt_od}`)
}
if (rcpt_domains.indexOf(rcpt_od) === -1) {
// not a duplicate, add to the list
- rcpt_domains.push(rcpt_od);
+ rcpt_domains.push(rcpt_od)
}
}
- return rcpt_domains;
+ return rcpt_domains
}
/*
@@ -122,58 +125,57 @@ exports.get_recipient_domains_by_txn = function (txn) {
*
* When verified / validated sender domains are found, check to see if
* their recipients have ever sent mail to their domain.
-*/
+ */
// early checks, on the mail hook
exports.is_authenticated = function (next, connection, params) {
-
// only validate inbound messages
- if (connection.relaying) return next();
+ if (connection.relaying) return next()
- const sender_od = this.get_sender_domain_by_txn(connection.transaction);
+ const sender_od = this.get_sender_domain_by_txn(connection.transaction)
if (sender_od in this.cfg.ignored_ods) return next()
if (this.has_fcrdns_match(sender_od, connection)) {
- connection.logdebug(this, `+fcrdns: ${sender_od}`);
- return next(null, null, sender_od);
+ connection.logdebug(this, `+fcrdns: ${sender_od}`)
+ return next(null, null, sender_od)
}
if (this.has_spf_match(sender_od, connection)) {
- connection.logdebug(this, `+spf: ${sender_od}`);
- return next(null, null, sender_od);
+ connection.logdebug(this, `+spf: ${sender_od}`)
+ return next(null, null, sender_od)
}
// Maybe: TLS verified domain?
if (connection.tls.verified) {
// TODO: get the CN and Subject Alternative Names of the cert
// then look for match with sender_od
- connection.logdebug(this, `+tls: ${sender_od}`);
+ connection.logdebug(this, `+tls: ${sender_od}`)
// return next(null, null, sender_od);
}
- next();
+ next()
}
exports.get_validated_sender_od = function (connection) {
- if (!connection || !connection.transaction) return;
- const txn_res = connection.transaction.results.get(this.name);
- if (!txn_res) return;
- return txn_res.sender;
+ if (!connection || !connection.transaction) return
+ const txn_res = connection.transaction.results.get(this.name)
+ if (!txn_res) return
+ return txn_res.sender
}
exports.get_rcpt_ods = function (connection) {
- if (!connection) return [];
- if (!connection.transaction) return [];
+ if (!connection) return []
+ if (!connection.transaction) return []
- const txn_r = connection.transaction.results.get(this.name);
- if (!txn_r) return [];
+ const txn_r = connection.transaction.results.get(this.name)
+ if (!txn_r) return []
- return txn_r.rcpt_ods;
+ return txn_r.rcpt_ods
}
exports.already_matched = function (connection) {
- const res = connection.transaction.results.get(this);
- if (!res) return false;
- return (res.pass && res.pass.length) ? true : false;
+ const res = connection.transaction.results.get(this)
+ if (!res) return false
+ return res.pass && res.pass.length ? true : false
}
exports.check_recipient = async function (next, connection, rcpt) {
@@ -181,78 +183,81 @@ exports.check_recipient = async function (next, connection, rcpt) {
// accepted it.
// inbound only
- if (connection.relaying) return next();
+ if (connection.relaying) return next()
- function errNext (err) {
- connection.logerror(this, `check_recipient: ${err}`);
- next();
+ function errNext(err) {
+ connection.logerror(this, `check_recipient: ${err}`)
+ next()
}
- if (!rcpt.host) return errNext('rcpt.host unset?');
+ if (!rcpt.host) return errNext('rcpt.host unset?')
// reduce the host portion of the email address to an OD
- const rcpt_od = tlds.get_organizational_domain(rcpt.host);
- if (!rcpt_od) return errNext(`no rcpt od for ${rcpt.host}`);
+ const rcpt_od = tlds.get_organizational_domain(rcpt.host)
+ if (!rcpt_od) return errNext(`no rcpt od for ${rcpt.host}`)
- connection.transaction.results.push(this, { rcpt_ods: rcpt_od });
+ connection.transaction.results.push(this, { rcpt_ods: rcpt_od })
// if no validated sender domain, there's nothing to do...yet
- const sender_od = this.get_validated_sender_od(connection);
- if (!sender_od) return next();
+ const sender_od = this.get_validated_sender_od(connection)
+ if (!sender_od) return next()
if (sender_od in this.cfg.ignored_ods) return errNext(`ignored(${sender_od})`)
// The sender OD is validated, check Redis for a match
try {
const reply = await this.db.hGet(rcpt_od, sender_od)
- connection.logdebug(this, `${rcpt_od} : ${sender_od} : ${reply}`);
+ connection.logdebug(this, `${rcpt_od} : ${sender_od} : ${reply}`)
if (reply) {
- connection.transaction.results.add(this, { pass: rcpt_od, count: reply });
+ connection.transaction.results.add(this, { pass: rcpt_od, count: reply })
}
- next(null, null, rcpt_od);
- }
- catch (err) {
- this.logerror(err);
- next();
+ next(null, null, rcpt_od)
+ } catch (err) {
+ this.logerror(err)
+ next()
}
}
exports.is_dkim_authenticated = async function (next, connection) {
const plugin = this
- if (connection.relaying) return next();
+ if (connection.relaying) return next()
- let rcpt_ods = [];
+ let rcpt_ods = []
- function errNext (err) {
- connection.logerror(plugin, `is_dkim_authenticated: ${err}`);
- next(null, null, rcpt_ods);
+ function errNext(err) {
+ connection.logerror(plugin, `is_dkim_authenticated: ${err}`)
+ next(null, null, rcpt_ods)
}
- function infoNext (msg) {
- connection.loginfo(plugin, `is_dkim_authenticated: ${msg}`);
- next(null, null, rcpt_ods);
+ function infoNext(msg) {
+ connection.loginfo(plugin, `is_dkim_authenticated: ${msg}`)
+ next(null, null, rcpt_ods)
}
- if (this.already_matched(connection)) return infoNext('already matched');
+ if (this.already_matched(connection)) return infoNext('already matched')
- const sender_od = this.get_validated_sender_od(connection);
- if (!sender_od) return errNext('no sender_od');
- if (sender_od in this.cfg.ignored_ods) return infoNext(`ignored(${sender_od})`)
+ const sender_od = this.get_validated_sender_od(connection)
+ if (!sender_od) return errNext('no sender_od')
+ if (sender_od in this.cfg.ignored_ods)
+ return infoNext(`ignored(${sender_od})`)
- rcpt_ods = this.get_rcpt_ods(connection);
- if (!rcpt_ods || ! rcpt_ods.length) return errNext('no rcpt_ods');
+ rcpt_ods = this.get_rcpt_ods(connection)
+ if (!rcpt_ods || !rcpt_ods.length) return errNext('no rcpt_ods')
- const dkim = connection.transaction.results.get('dkim_verify');
- if (!dkim) return infoNext('no dkim_verify results');
+ const dkim = connection.transaction.results.get('dkim_verify')
+ if (!dkim) return infoNext('no dkim_verify results')
if (!dkim.pass || !dkim.pass.length) return infoNext('no dkim pass')
try {
- const multi = this.db.multi();
+ const multi = this.db.multi()
for (const pas of dkim.pass) {
- const dkim_od = tlds.get_organizational_domain(pas);
+ const dkim_od = tlds.get_organizational_domain(pas)
if (dkim_od === sender_od) {
- connection.transaction.results.add(this, { sender: sender_od, auth: 'dkim' });
+ connection.transaction.results.add(this, {
+ sender: sender_od,
+ auth: 'dkim',
+ })
for (const rcptOd of rcpt_ods) {
- multi.hGet(rcptOd, sender_od);
+ multi.hGet(rcptOd, sender_od)
}
}
}
@@ -263,57 +268,59 @@ exports.is_dkim_authenticated = async function (next, connection) {
connection.transaction.results.add(this, {
pass: rcpt_ods[j],
count: replies[j],
- emit: true
- });
+ emit: true,
+ })
}
- next(null, null, rcpt_ods);
- }
- catch (err) {
+ next(null, null, rcpt_ods)
+ } catch (err) {
connection.logerror(this, err)
errNext(err)
}
}
exports.has_fcrdns_match = function (sender_od, connection) {
- const fcrdns = connection.results.get('fcrdns');
- if (!fcrdns) return false;
- if (!fcrdns.fcrdns) return false;
+ const fcrdns = connection.results.get('fcrdns')
+ if (!fcrdns) return false
+ if (!fcrdns.fcrdns) return false
- connection.logdebug(this, fcrdns.fcrdns);
+ connection.logdebug(this, fcrdns.fcrdns)
- let mail_host = fcrdns.fcrdns;
- if (Array.isArray(mail_host)) mail_host = fcrdns.fcrdns[0];
+ let mail_host = fcrdns.fcrdns
+ if (Array.isArray(mail_host)) mail_host = fcrdns.fcrdns[0]
- const fcrdns_od = tlds.get_organizational_domain(mail_host);
- if (fcrdns_od !== sender_od) return false;
+ const fcrdns_od = tlds.get_organizational_domain(mail_host)
+ if (fcrdns_od !== sender_od) return false
connection.transaction.results.add(this, {
- sender: sender_od, auth: 'fcrdns', emit: true
- });
- return true;
+ sender: sender_od,
+ auth: 'fcrdns',
+ emit: true,
+ })
+ return true
}
exports.has_spf_match = function (sender_od, connection) {
-
- let spf = connection.results.get('spf');
+ let spf = connection.results.get('spf')
if (spf && spf.domain && spf.result === 'Pass') {
// scope=helo (HELO/EHLO)
if (tlds.get_organizational_domain(spf.domain) === sender_od) {
- connection.transaction.results.add(this, {sender: sender_od});
- return true;
+ connection.transaction.results.add(this, { sender: sender_od })
+ return true
}
}
- spf = connection.transaction.results.get('spf');
+ spf = connection.transaction.results.get('spf')
if (spf && spf.domain && spf.result === 'Pass') {
// scope=mfrom (HELO/EHLO)
if (tlds.get_organizational_domain(spf.domain) === sender_od) {
connection.transaction.results.add(this, {
- sender: sender_od, auth: 'spf', emit: true
- });
- return true;
+ sender: sender_od,
+ auth: 'spf',
+ emit: true,
+ })
+ return true
}
}
- return false;
+ return false
}
diff --git a/package.json b/package.json
index 8b4de78..cb77e93 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,22 @@
{
"name": "haraka-plugin-known-senders",
- "version": "1.1.0",
+ "version": "1.1.1",
"description": "Increase the reputation of recognized sender domains.",
+ "files": [
+ "CHANGELOG.md",
+ "config"
+ ],
"main": "index.js",
"scripts": {
"cover": "npx nyc --reporter=lcov --hook-run-in-context npm run test",
+ "format": "npm run prettier:fix && npm run lint:fix",
"lint": "npx eslint *.js test/*.js",
- "lintfix": "npx eslint --fix *.js test/*.js",
+ "lint:fix": "npx eslint --fix *.js test/*.js",
+ "prettier": "npx prettier . --check",
+ "prettier:fix": "npx prettier . --write --log-level=warn",
"test": "npx mocha --exit",
- "versions": "npx dependency-version-checker check"
+ "versions": "npx dependency-version-checker check",
+ "versions:fix": "npx dependency-version-checker update"
},
"repository": {
"type": "git",
@@ -29,14 +37,13 @@
},
"homepage": "https://github.com/haraka/haraka-plugin-known-senders#readme",
"devDependencies": {
- "address-rfc2821": "^2.1.1",
- "eslint": "^8.55.0",
- "eslint-plugin-haraka": "^1.0.15",
- "haraka-test-fixtures": "^1.3.3",
- "mocha": "^10.2.0"
+ "address-rfc2821": "^2.1.2",
+ "haraka-test-fixtures": "^1.3.8",
+ "mocha": "^11.1.0",
+ "@haraka/eslint-config": "^2.0.2"
},
"dependencies": {
- "haraka-plugin-redis": "^2.0.6",
- "haraka-tld": "^1.2.0"
+ "haraka-plugin-redis": "^2.0.8",
+ "haraka-tld": "^1.2.3"
}
}
diff --git a/test/index.js b/test/index.js
index c82c31d..c7e4f4c 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,18 +1,16 @@
+const assert = require('assert')
-const assert = require('assert');
-
-const Address = require('address-rfc2821').Address;
-const fixtures = require('haraka-test-fixtures');
-
+const Address = require('address-rfc2821').Address
+const fixtures = require('haraka-test-fixtures')
describe('register', function () {
it('registers', function () {
- const plugin = new fixtures.plugin('index');
+ const plugin = new fixtures.plugin('index')
plugin.register()
})
it('loads the config', function () {
- const plugin = new fixtures.plugin('index');
+ const plugin = new fixtures.plugin('index')
plugin.register()
assert.deepEqual(false, 'simerson.net' in plugin.cfg.ignored_ods)
assert.deepEqual(true, 'gmail.com' in plugin.cfg.ignored_ods)
@@ -20,253 +18,278 @@ describe('register', function () {
})
describe('is_authenticated', function () {
-
- const plugin = new fixtures.plugin('index');
- plugin.register();
+ const plugin = new fixtures.plugin('index')
+ plugin.register()
beforeEach(function () {
- this.connection = fixtures.connection.createConnection();
- this.connection.results = new fixtures.result_store(this.connection);
- this.connection.transaction = fixtures.transaction.createTransaction();
- this.connection.transaction.results = new fixtures.result_store(this.connection);
+ this.connection = fixtures.connection.createConnection()
+ this.connection.results = new fixtures.result_store(this.connection)
+ this.connection.init_transaction()
})
it('returns empty when no auth found', function (done) {
- this.connection.transaction.mail_from = new Address('');
+ this.connection.transaction.mail_from = new Address('')
plugin.is_authenticated(function (null1, null2, sender_od) {
- assert.equal(sender_od, undefined);
- done();
+ assert.equal(sender_od, undefined)
+ done()
}, this.connection)
})
it('finds OD from FCrDNS match', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.results.add({ name: 'fcrdns'}, {fcrdns: 'validated-test.com'});
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ this.connection.results.add(
+ { name: 'fcrdns' },
+ { fcrdns: 'validated-test.com' },
+ )
plugin.is_authenticated(function (null1, null2, sender_od) {
- assert.equal(sender_od, 'validated-test.com');
- done();
- },
- this.connection);
+ assert.equal(sender_od, 'validated-test.com')
+ done()
+ }, this.connection)
})
it('finds OD from FCrDNS array match', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.results.add({ name: 'fcrdns'}, {fcrdns: ['validated-test.com'] });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ this.connection.results.add(
+ { name: 'fcrdns' },
+ { fcrdns: ['validated-test.com'] },
+ )
plugin.is_authenticated(function (null1, null2, sender_od) {
- assert.equal(sender_od, 'validated-test.com');
- done();
- },
- this.connection);
+ assert.equal(sender_od, 'validated-test.com')
+ done()
+ }, this.connection)
})
it('misses OD on FCrDNS miss', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.results.add({ name: 'fcrdns'}, {fcrdns: 'valid-test.com'});
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ this.connection.results.add(
+ { name: 'fcrdns' },
+ { fcrdns: 'valid-test.com' },
+ )
plugin.is_authenticated((null1, null2, sender_od) => {
- assert.equal(sender_od, undefined);
- done();
- },
- this.connection);
+ assert.equal(sender_od, undefined)
+ done()
+ }, this.connection)
})
it('finds OD on SPF mfrom match', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.transaction.results.add({ name: 'spf'}, {
- scope: 'mfrom',
- result: 'Pass',
- domain: 'spf-mfrom.com',
- });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ this.connection.transaction.results.add(
+ { name: 'spf' },
+ {
+ scope: 'mfrom',
+ result: 'Pass',
+ domain: 'spf-mfrom.com',
+ },
+ )
plugin.is_authenticated(function (null1, null2, sender_od) {
- assert.equal(sender_od, 'spf-mfrom.com');
- done();
- },
- this.connection);
+ assert.equal(sender_od, 'spf-mfrom.com')
+ done()
+ }, this.connection)
})
it('finds OD on SPF helo match', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.results.add({ name: 'spf'}, {
- scope: 'helo',
- result: 'Pass',
- domain: 'helo-pass.com',
- });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ this.connection.results.add(
+ { name: 'spf' },
+ {
+ scope: 'helo',
+ result: 'Pass',
+ domain: 'helo-pass.com',
+ },
+ )
plugin.is_authenticated((null1, null2, sender_od) => {
- assert.equal(sender_od, 'helo-pass.com');
- done();
- },
- this.connection);
+ assert.equal(sender_od, 'helo-pass.com')
+ done()
+ }, this.connection)
})
})
describe('check_recipient', function () {
- let connection;
+ let connection
beforeEach(function () {
- connection = fixtures.connection.createConnection();
- connection.transaction = fixtures.transaction.createTransaction();
+ connection = fixtures.connection.createConnection()
+ connection.init_transaction()
})
it('reduces domain to OD', function (done) {
- const plugin = new fixtures.plugin('index');
- plugin.validated_sender_od = 'example.com';
-
- plugin.check_recipient(() => {
- const res = connection.transaction.results.get(plugin.name);
- assert.equal(res.rcpt_ods[0], 'example.com');
- done();
- },
- connection,
- new Address(''));
+ const plugin = new fixtures.plugin('index')
+ plugin.validated_sender_od = 'example.com'
+
+ plugin.check_recipient(
+ () => {
+ const res = connection.transaction.results.get(plugin.name)
+ assert.equal(res.rcpt_ods[0], 'example.com')
+ done()
+ },
+ connection,
+ new Address(''),
+ )
})
})
describe('update_sender', function () {
-
beforeEach(function (done) {
- this.connection = fixtures.connection.createConnection();
- this.connection.relaying = true;
- this.connection.transaction = fixtures.transaction.createTransaction();
-
- this.plugin = new fixtures.plugin('index');
- this.plugin.inherits('haraka-plugin-redis');
- this.plugin.load_sender_ini();
- this.plugin.init_redis_plugin(done);
+ this.connection = fixtures.connection.createConnection()
+ this.connection.relaying = true
+ this.connection.init_transaction()
+
+ this.plugin = new fixtures.plugin('index')
+ this.plugin.inherits('haraka-plugin-redis')
+ this.plugin.load_sender_ini()
+ this.plugin.init_redis_plugin(done)
})
after(function () {
- this.plugin.shutdown();
+ this.plugin.shutdown()
})
it('gets the sender domain', function (done) {
- this.connection.transaction.mail_from = new Address('');
+ this.connection.transaction.mail_from = new Address('')
this.plugin.update_sender(function (null1, null2, sender_dom, rcpt_doms) {
- assert.equal(sender_dom, 'example.com');
+ assert.equal(sender_dom, 'example.com')
assert.deepEqual(rcpt_doms, [])
- done();
- },
- this.connection);
+ done()
+ }, this.connection)
})
it('gets the recipient domain', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.transaction.rcpt_to.push(new Address(''));
+ this.connection.transaction.mail_from = new Address('')
+ this.connection.transaction.rcpt_to.push(new Address(''))
this.plugin.update_sender(function (null1, null2, sender_dom, rcpt_doms) {
- assert.equal(sender_dom, 'example.com');
- assert.deepEqual(rcpt_doms, ['test1.com']);
- done();
- },
- this.connection);
+ assert.equal(sender_dom, 'example.com')
+ assert.deepEqual(rcpt_doms, ['test1.com'])
+ done()
+ }, this.connection)
})
it('gets the recipient domains', function (done) {
- this.connection.transaction.mail_from = new Address('');
- this.connection.transaction.rcpt_to.push(new Address(''));
- this.connection.transaction.rcpt_to.push(new Address(''));
+ this.connection.transaction.mail_from = new Address('')
+ this.connection.transaction.rcpt_to.push(new Address(''))
+ this.connection.transaction.rcpt_to.push(new Address(''))
this.plugin.update_sender(function (null1, null2, sender_dom, rcpt_doms) {
- assert.equal(sender_dom, 'example.com');
- assert.deepEqual(rcpt_doms, ['test1.com', 'test2.com']);
- done();
- },
- this.connection);
+ assert.equal(sender_dom, 'example.com')
+ assert.deepEqual(rcpt_doms, ['test1.com', 'test2.com'])
+ done()
+ }, this.connection)
})
})
describe('get_rcpt_ods', function () {
- it('always returns an array', function () {
- });
+ it('always returns an array', function () {})
})
describe('get_sender_domain_by_txn', function () {
-
beforeEach(function () {
- this.plugin = new fixtures.plugin('known-senders');
- this.connection = new fixtures.connection.createConnection();
- this.connection.transaction = new fixtures.transaction.createTransaction();
- });
+ this.plugin = new fixtures.plugin('known-senders')
+ this.connection = new fixtures.connection.createConnection()
+ this.connection.init_transaction()
+ })
it('returns a sender domain: example.com', function () {
- this.connection.transaction.mail_from = new Address('');
- assert.equal(this.plugin.get_sender_domain_by_txn(this.connection.transaction), 'example.com');
- });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ assert.equal(
+ this.plugin.get_sender_domain_by_txn(this.connection.transaction),
+ 'example.com',
+ )
+ })
it('returns a sender domain: mail.example.com', function () {
- this.connection.transaction.mail_from = new Address('');
- assert.equal(this.plugin.get_sender_domain_by_txn(this.connection.transaction), 'example.com');
- });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ assert.equal(
+ this.plugin.get_sender_domain_by_txn(this.connection.transaction),
+ 'example.com',
+ )
+ })
it('returns a sender domain: bbc.co.uk', function () {
- this.connection.transaction.mail_from = new Address('');
- assert.equal(this.plugin.get_sender_domain_by_txn(this.connection.transaction), 'bbc.co.uk');
- });
+ this.connection.transaction.mail_from = new Address(
+ '',
+ )
+ assert.equal(
+ this.plugin.get_sender_domain_by_txn(this.connection.transaction),
+ 'bbc.co.uk',
+ )
+ })
})
describe('get_recipient_domains_by_txn', function () {
-
beforeEach(function () {
- this.plugin = new fixtures.plugin('known-senders');
- this.connection = new fixtures.connection.createConnection();
- this.connection.transaction = new fixtures.transaction.createTransaction();
- });
+ this.plugin = new fixtures.plugin('known-senders')
+ this.connection = new fixtures.connection.createConnection()
+ this.connection.init_transaction()
+ })
it('retrieves domains from txn recipients: example.com', function () {
- const txn = this.connection.transaction;
- txn.rcpt_to.push(new Address(''));
- const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn);
- assert.deepEqual(rcpt_doms, ['example.com'], rcpt_doms);
- });
+ const txn = this.connection.transaction
+ txn.rcpt_to.push(new Address(''))
+ const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn)
+ assert.deepEqual(rcpt_doms, ['example.com'], rcpt_doms)
+ })
it('retrieves domains from txn recipients: example[1-2].com', function () {
- const txn = this.connection.transaction;
- txn.rcpt_to.push(new Address(''));
- txn.rcpt_to.push(new Address(''));
- const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn);
- assert.deepEqual(rcpt_doms, ['example1.com', 'example2.com'], rcpt_doms);
- });
+ const txn = this.connection.transaction
+ txn.rcpt_to.push(new Address(''))
+ txn.rcpt_to.push(new Address(''))
+ const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn)
+ assert.deepEqual(rcpt_doms, ['example1.com', 'example2.com'], rcpt_doms)
+ })
it('retrieves unique domains from txn recipients: example.com', function () {
- const txn = this.connection.transaction;
- txn.rcpt_to.push(new Address(''));
- txn.rcpt_to.push(new Address(''));
- const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn);
- assert.deepEqual(rcpt_doms, ['example.com'], rcpt_doms);
- });
+ const txn = this.connection.transaction
+ txn.rcpt_to.push(new Address(''))
+ txn.rcpt_to.push(new Address(''))
+ const rcpt_doms = this.plugin.get_recipient_domains_by_txn(txn)
+ assert.deepEqual(rcpt_doms, ['example.com'], rcpt_doms)
+ })
})
describe('is_dkim_authenticated', function () {
-
beforeEach(function (done) {
+ this.connection = new fixtures.connection.createConnection()
+ this.connection.init_transaction()
- this.connection = new fixtures.connection.createConnection();
- this.connection.transaction = new fixtures.transaction.createTransaction();
-
- this.plugin = new fixtures.plugin('known-senders');
- this.plugin.register();
+ this.plugin = new fixtures.plugin('known-senders')
+ this.plugin.register()
this.plugin.init_redis_plugin(done)
})
after(function () {
- this.plugin.shutdown();
+ this.plugin.shutdown()
})
it('finds dkim results', function (done) {
- const plugin = this.plugin;
- const connection = this.connection;
- const txn = this.connection.transaction;
+ const plugin = this.plugin
+ const connection = this.connection
+ const txn = this.connection.transaction
- txn.results.add(plugin, { sender: 'sender.com' });
- txn.results.push(plugin, { rcpt_ods: 'rcpt.com' });
- txn.results.add({ name: 'dkim_verify'}, { pass: 'sender.com'});
+ txn.results.add(plugin, { sender: 'sender.com' })
+ txn.results.push(plugin, { rcpt_ods: 'rcpt.com' })
+ txn.results.add({ name: 'dkim_verify' }, { pass: 'sender.com' })
plugin.is_dkim_authenticated(() => {
- const res = txn.results.get(plugin.name);
- assert.equal('dkim', res.auth);
- done();
- },
- connection);
+ const res = txn.results.get(plugin.name)
+ assert.equal('dkim', res.auth)
+ done()
+ }, connection)
})
})