From d5720eef1156de7c27382ede8b7c7063c12e881c Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 17:13:53 -0700 Subject: [PATCH] Release v1.0.2 (#4) - dep: eslint-plugin-haraka -> @haraka/eslint-config - populate [files] in package.json. Delete .npmignore. - doc(CHANGE): renamed Changes.md -> CHANGELOG.md - doc(CONTRIBUTORS): added - prettier --- .codeclimate.yml | 8 +- .eslintrc.yaml | 22 +-- .github/PULL_REQUEST_TEMPLATE.md | 5 +- .github/dependabot.yml | 6 +- .github/workflows/ci.yml | 6 +- .github/workflows/codeql.yml | 6 +- .github/workflows/publish.yml | 1 - .npmignore | 59 ------- .prettierrc | 2 + .release | 2 +- CHANGELOG.md | 21 +++ CONTRIBUTORS.md | 8 + Changes.md | 3 - README.md | 37 ++--- config/dns-list.ini | 2 +- index.js | 258 ++++++++++++++++--------------- package.json | 26 ++-- test/dns-list.js | 114 +++++++------- 18 files changed, 271 insertions(+), 315 deletions(-) delete mode 100644 .npmignore create mode 100644 .prettierrc create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTORS.md delete mode 100644 Changes.md diff --git a/.codeclimate.yml b/.codeclimate.yml index d7bc7e9..244f9b9 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,13 +1,13 @@ engines: eslint: enabled: true - channel: "eslint-8" + channel: 'eslint-8' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' ratings: - paths: - - "**.js" + paths: + - '**.js' checks: return-statements: diff --git a/.eslintrc.yaml b/.eslintrc.yaml index fe947ea..035a400 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -2,24 +2,6 @@ env: node: true es6: true mocha: true - es2020: true + es2022: true -plugins: - - haraka - -extends: - - eslint:recommended - - plugin:haraka/recommended - -rules: - indent: [2, 2, {"SwitchCase": 1}] - -root: true - -globals: - OK: true - CONT: true - DENY: true - DENYSOFT: true - DENYDISCONNECT: true - DENYSOFTDISCONNECT: true +extends: ['@haraka'] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ba28fbb..5ccd7ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,12 @@ Changes proposed in this pull request: -- -- +- +- Fixes # Checklist: + - [ ] docs updated - [ ] tests updated - [ ] Changes.md updated diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8c3ac4a..df04b68 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,9 +2,9 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" + - package-ecosystem: 'npm' + directory: '/' schedule: - interval: "monthly" + interval: 'monthly' allow: - dependency-type: production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 325fb6d..3d01042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [ push, pull_request ] +on: [push, pull_request] env: CI: true @@ -14,9 +14,9 @@ jobs: # secrets: inherit 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 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/.npmignore b/.npmignore deleted file mode 100644 index a1802f9..0000000 --- a/.npmignore +++ /dev/null @@ -1,59 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history - -package-lock.json -bower_components -# Optional npm cache directory -.npmrc - -.idea -.DS_Store -haraka-update.sh - -.github -.release -.codeclimate.yml -.editorconfig -.gitignore -.gitmodules -.lgtm.yml -appveyor.yml -codecov.yml -.travis.yml -.eslintrc.yaml -.eslintrc.json 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..f18c599 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 0890e945e4e061c96c7b2ab45017525904c17728 +Subproject commit f18c599497f7cb1c3b68e9555fb14e8075c4866b diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ef9822 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +### Unreleased + +### [1.0.2] - 2024-04-09 + +- dep: eslint-plugin-haraka -> @haraka/eslint-config +- populate [files] in package.json. Delete .npmignore. +- doc(CHANGE): renamed Changes.md -> CHANGELOG.md +- doc(CONTRIBUTORS): added +- prettier +- feat: add timeout to DNS Resolver #2 + +## 1.0.0 - 2023-12-15 + +- Initial release + +[1.0.1]: https://github.com/haraka/haraka-plugin-dns-list/releases/tag/1.0.1 +[1.0.2]: https://github.com/haraka/haraka-plugin-dns-list/releases/tag/1.0.2 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..9f1c3fa --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +# Contributors + +This handcrafted artisinal software is brought to you by: + +|
msimerson (4) |
lnedry (1) | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | + +this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/Changes.md b/Changes.md deleted file mode 100644 index 5697bc1..0000000 --- a/Changes.md +++ /dev/null @@ -1,3 +0,0 @@ -## 1.0.0 - 2023-12-15 - -- Initial release diff --git a/README.md b/README.md index 3e52802..305aaa8 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,16 @@ Looks up the IP address of the remote host in DNS lists. There are several types Block lists (aka: DNSBL) are designed to be used for blocking mail from any host listed in them. Block lists are the most common DNS list type and lists without a type specified are considered block lists. The default action for block lists is to reject the connection. This can be changed by setting `reject=false` in the zone's settings block. - ### allow When the remote IP is found in an allow list, this plugin returns OK for the ehlo, helo, and mail hooks. -IMPORTANT! The order of plugins in config/plugins is important when this feature is used. It should be listed *before* any plugins that you wish to skip, but after any plugins that accept recipients. +IMPORTANT! The order of plugins in config/plugins is important when this feature is used. It should be listed _before_ any plugins that you wish to skip, but after any plugins that accept recipients. ### karma Karma lists can have different results for IPs beyond a simple block or allow. See [hostkarma.junkemailfilter.com](https://hostkarma.junkemailfilter.com) for details. - ## INSTALL ```sh @@ -34,7 +32,7 @@ echo "dns-list" >> config/plugins service haraka restart ``` -### Configuration +## Configure If the default configuration is insufficient, copy the config file from the distribution into your haraka config dir and modify it: @@ -51,31 +49,28 @@ Check every DNS zone every `N` minutes. When the value is less than 5, checks wi The checks confirm that lists are responding correctly. When errors are detected, the zone is disabled and will be checked at the next interval. When a zone resumes working correctly it will be enabled. - #### [main] zones An array or comma separated list of zones to query. - #### [main] search: (default: all) - first: consider first DNS list response conclusive. End processing. -- all: process all DNS list results - +- all: process all DNS list results #### [stats] enable=true -This feature requires the 'redis' plugin. When enabled, this will record several list statistics to redis: - +This feature requires the [redis](https://github.com/haraka/haraka-plugin-redis) plugin. When enabled, this will record several list statistics to redis: + - the total number of queries (TOTAL) -- the average response time (AVG\_RT) +- the average response time (AVG_RT) - the return type (e.g. LISTED or ERROR) -to a redis hash where the key is 'dns-list-stat:zone' and the hash field is the response type. +to a redis hash where the key is `dns-list-stat:zone` and the hash field is the response type. -It will also track the positive response overlap between the lists in another redis hash where the key is 'dns-list-overlap:zone' and the hash field is the other list names. Example: +It will also track the positive response overlap between the lists in another redis hash where the key is `dns-list-overlap:zone` and the hash field is the other list names. Example: -```` +``` redis 127.0.0.1:6379> hgetall dns-list-stat:zen.spamhaus.org 1) "TOTAL" 2) "23" @@ -93,21 +88,19 @@ redis 127.0.0.1:6379> hgetall dns-list-overlap:zen.spamhaus.org 4) "1" 5) "TOTAL" 6) "1" -```` +``` -#### [stats] redis\_host +#### [stats] redis_host In the form of `host:port` this option allows you to specify a different host on which redis runs. - ### Per-Zone DNS list settings -The exact name of the DNS zone (as specified above in main.zones) may contain settings about that DNS list. - -* type=[ block, allow, karma ] -* reject=true (default: true) Reject connections from IPs on block lists. Setting this to false makes dnsbl informational. reject=false is best used in conjunction with plugins like [karma](/manual/plugins/karma.html) that employ a scoring engine to make choices about message delivery. -* ipv6=true | false +The exact name of the DNS zone (as specified above in main.zones) may contain settings about that DNS list. +- type=[ block, allow, karma ] +- reject=true (default: true) Reject connections from IPs on block lists. Setting this to false makes dnsbl informational. reject=false is best used in conjunction with plugins like [karma](https://github.com/haraka/haraka-plugin-karma) that employ a scoring engine to make choices about message delivery. +- ipv6=true | false [ci-img]: https://github.com/haraka/haraka-plugin-dns-list/actions/workflows/ci.yml/badge.svg [ci-url]: https://github.com/haraka/haraka-plugin-dns-list/actions/workflows/ci.yml diff --git a/config/dns-list.ini b/config/dns-list.ini index b6b8eba..3660d0f 100644 --- a/config/dns-list.ini +++ b/config/dns-list.ini @@ -2,7 +2,7 @@ [main] ; periodically check each DNS list, disabling ones that fail checks -periodic_checks = 30 +periodic_checks=30 ; zones: an array or a comma separated list of DNS zones ; diff --git a/index.js b/index.js index 68f6e6d..d6aabce 100644 --- a/index.js +++ b/index.js @@ -1,47 +1,47 @@ // dns-lists plugin -const net = require('net'); -const net_utils = require('haraka-net-utils'); const dnsPromises = require('dns').promises; -const dns = new dnsPromises.Resolver({timeout: 25000, tries: 1}); +const dns = new dnsPromises.Resolver({timeout: 25000, tries: 1}); +const net = require('net') +const net_utils = require('haraka-net-utils') -exports.disable_allowed = false; -let redis_client; +let redis_client exports.register = function () { - - this.load_config(); + this.load_config() if (this.cfg.main.periodic_checks) { - this.check_zones(this.cfg.main.periodic_checks); + this.check_zones(this.cfg.main.periodic_checks) } - this.register_hook('connect', 'onConnect'); + this.register_hook('connect', 'onConnect') // IMPORTANT: don't run this on hook_rcpt otherwise we're an open relay... - ['ehlo','helo','mail'].forEach(hook => { - this.register_hook(hook, 'check_dnswl'); - }); + ;['ehlo', 'helo', 'mail'].forEach((hook) => { + this.register_hook(hook, 'check_dnswl') + }) } exports.load_config = function () { - - this.cfg = this.config.get('dns-list.ini', { - booleans: [ - '-stats.enable', - '*.reject', - '*.ipv6', - '*.loopback_is_rejected', - ], - }, () => { - this.load_config(); - }); + this.cfg = this.config.get( + 'dns-list.ini', + { + booleans: [ + '-stats.enable', + '*.reject', + '*.ipv6', + '*.loopback_is_rejected', + ], + }, + () => { + this.load_config() + }, + ) if (Array.isArray(this.cfg.main.zones)) { - this.cfg.main.zones = new Set(this.cfg.main.zones); - } - else { - this.cfg.main.zones = new Set(this.cfg.main.zones.split(/[\s,;]+/)); + this.cfg.main.zones = new Set(this.cfg.main.zones) + } else { + this.cfg.main.zones = new Set(this.cfg.main.zones.split(/[\s,;]+/)) } // Compatibility with old-plugin @@ -51,28 +51,32 @@ exports.load_config = function () { for (const z in this.config.get('dnswl.zones', 'list')) { this.cfg.main.zones.add(z) if (this.cfg[z] === undefined) this.cfg[z] = {} - this.cfg[z].type='allow' + this.cfg[z].type = 'allow' } // active zones - this.zones = new Set() + if (this.cfg.main.periodic_checks < 5) { + // all configured are enabled + this.zones = new Set(...this.cfg.main.zones) + } else { + this.zones = new Set() // populated by check_zones() + } } exports.should_skip = function (connection) { - - if (!connection) return true; + if (!connection) return true if (connection.remote.is_private) { - connection.logdebug(this, `skip private: ${connection.remote.ip}`); - return true; + connection.logdebug(this, `skip private: ${connection.remote.ip}`) + return true } if (this.zones.length === 0) { - connection.logerror(this, "no zones"); - return true; + connection.logerror(this, 'no zones') + return true } - return false; + return false } exports.eachActiveDnsList = async function (connection, zone, nextOnce) { @@ -102,12 +106,10 @@ exports.eachActiveDnsList = async function (connection, zone, nextOnce) { if (type === 'karma') { if (ips.includes('127.0.0.1')) { connection.results.add(this, { pass: zone }) - } - else if (ips.includes('127.0.0.2')) { + } else if (ips.includes('127.0.0.2')) { connection.results.add(this, { fail: zone }) if (this.cfg.main.search === 'first') nextOnce(DENY, [zone]) - } - else { + } else { connection.results.add(this, { msg: zone }) } return @@ -122,15 +124,18 @@ exports.eachActiveDnsList = async function (connection, zone, nextOnce) { exports.onConnect = function (next, connection) { // console.log(`onConnect`) - if (this.should_skip(connection)) return next(); + if (this.should_skip(connection)) return next() let calledNext = false - function nextOnce (code, zones) { + function nextOnce(code, zones) { // console.log(`nextOnce: ${code} : ${zones}`) if (calledNext) return calledNext = true if (code === undefined || zones === undefined) return next() - next(code, `host [${connection.remote.ip}] is listed on ${zones.join(', ')}`) + next( + code, + `host [${connection.remote.ip}] is listed on ${zones.join(', ')}`, + ) } const promises = [] @@ -141,7 +146,7 @@ exports.onConnect = function (next, connection) { Promise.all(promises).then(() => { // console.log(`Promise.all`) - if (connection.results.get(this).fail?.length) { + if (connection.results.get(this)?.fail?.length) { nextOnce(DENY, connection.results.get(this).fail) return } @@ -149,26 +154,27 @@ exports.onConnect = function (next, connection) { }) } -exports.check_dnswl = (next, connection) => connection.notes.dnswl ? next(OK) : next() +exports.check_dnswl = (next, connection) => + connection.notes.dnswl ? next(OK) : next() -function ipQuery (ip, zone) { +function ipQuery(ip, zone) { // 1.2.3.4 -> 4.3.2.1.$zone. - if (net.isIPv6(ip)) return [ net_utils.ipv6_reverse(ip), zone, '' ].join('.') + if (net.isIPv6(ip)) return [net_utils.ipv6_reverse(ip), zone, ''].join('.') // ::FFFF:7F00:2 -> 2.0.0.0.0.0.f.7.f.f.f.f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.$zone. - if (net.isIPv4(ip)) return [ ip.split('.').reverse().join('.'), zone, '' ].join('.') + if (net.isIPv4(ip)) + return [ip.split('.').reverse().join('.'), zone, ''].join('.') throw new Error('invalid IP: ${ip}') } exports.lookup = async function (ip, zone) { + if (!ip || !zone) throw new Error(`lookup: invalid request`) - if (!ip || !zone) throw new Error(`lookup: invalid request`); - - let start; + let start if (this.cfg.stats.enable) { - this.init_redis(); - start = new Date().getTime(); + this.init_redis() + start = new Date().getTime() } try { @@ -176,19 +182,19 @@ exports.lookup = async function (ip, zone) { const a = await dns.resolve4(query, 'A') // console.log(`lookup ${query} -> a: ${a}`) - this.stats_incr_zone(null, zone, start); // Statistics + this.stats_incr_zone(null, zone, start) // Statistics if (this.hasSpecialResults(zone, a)) return return a - } - catch (err) { - this.stats_incr_zone(err, zone, start); // Statistics + } catch (err) { + this.stats_incr_zone(err, zone, start) // Statistics - if (err.code === dnsPromises.NOTFOUND) return; // unlisted, not an error + if (err.code === dnsPromises.NOTFOUND) return // unlisted, not an error - if (err.code === dnsPromises.TIMEOUT) { // list timed out - this.disable_zone(zone, err.code); // disable it + if (err.code === dnsPromises.TIMEOUT) { + // list timed out + this.disable_zone(zone, err.code) // disable it return } @@ -200,56 +206,67 @@ exports.lookup = async function (ip, zone) { exports.hasSpecialResults = function (zone, a) { // Check for a result outside 127/8 // This should *never* happen on a proper DNS list - if (a && a.find((rec) => { return rec.split('.')[0] !== '127' })) { - this.disable_zone(zone, a); - return true; + if ( + a && + a.find((rec) => { + return rec.split('.')[0] !== '127' + }) + ) { + this.disable_zone(zone, a) + return true } if (/spamhaus/.test(zone)) { // https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now - if (a?.includes('127.255.255.252') || a?.includes('127.255.255.254') || a?.includes('127.255.255.255')) { - this.disable_zone(zone, a); - return true; + if ( + a?.includes('127.255.255.252') || + a?.includes('127.255.255.254') || + a?.includes('127.255.255.255') + ) { + this.disable_zone(zone, a) + return true } } // https://www.dnswl.org/?page_id=15 if ('list.dnswl.org' === zone && a?.includes('127.0.0.255')) { - this.disable_zone(zone, a); - return true; + this.disable_zone(zone, a) + return true } return false } exports.stats_incr_zone = function (err, zone, start) { - if (!this.cfg.stats.enable) return; - - const rkey = `dns-list-stat:${zone}`; - const elapsed = new Date().getTime() - start; - redis_client.hIncrBy(rkey, 'TOTAL', 1); - const foo = (err) ? err.code : 'LISTED'; - redis_client.hIncrBy(rkey, foo, 1); - redis_client.hGet(rkey, 'AVG_RT').then(rt => { - const avg = parseInt(rt) ? (parseInt(elapsed) + parseInt(rt))/2 : parseInt(elapsed); - redis_client.hSet(rkey, 'AVG_RT', avg); - }); + if (!this.cfg.stats.enable) return + + const rkey = `dns-list-stat:${zone}` + const elapsed = new Date().getTime() - start + redis_client.hIncrBy(rkey, 'TOTAL', 1) + const foo = err ? err.code : 'LISTED' + redis_client.hIncrBy(rkey, foo, 1) + redis_client.hGet(rkey, 'AVG_RT').then((rt) => { + const avg = parseInt(rt) + ? (parseInt(elapsed) + parseInt(rt)) / 2 + : parseInt(elapsed) + redis_client.hSet(rkey, 'AVG_RT', avg) + }) } exports.init_redis = function () { - if (redis_client) return; + if (redis_client) return - const redis = require('redis'); - const host_port = this.cfg.stats.redis_host.split(':'); - const host = host_port[0] || '127.0.0.1'; - const port = parseInt(host_port[1], 10) || 6379; + const redis = require('redis') + const host_port = this.cfg.stats.redis_host.split(':') + const host = host_port[0] || '127.0.0.1' + const port = parseInt(host_port[1], 10) || 6379 - redis_client = redis.createClient(port, host); + redis_client = redis.createClient(port, host) redis_client.connect().then(() => { - redis_client.on('error', err => { - this.logerror(`Redis error: ${err}`); - redis_client.quit(); - redis_client = null; // should force a reconnect + redis_client.on('error', (err) => { + this.logerror(`Redis error: ${err}`) + redis_client.quit() + redis_client = null // should force a reconnect // not sure if that's the right thing but better than nothing... }) }) @@ -266,7 +283,6 @@ exports.getListReject = function (zone) { } exports.checkZonePositive = async function (zone, ip) { - // RFC 5782 § 5 // IPv4-based DNSxLs MUST contain an entry for 127.0.0.2 for testing purposes. @@ -282,20 +298,17 @@ exports.checkZonePositive = async function (zone, ip) { } } return true + } else { + this.logwarn(`${query}\tno response`) + this.disable_zone(zone, a) } - else { - this.logwarn(`${query}\tno response`); - this.disable_zone(zone, a); - } - } - catch (err) { + } catch (err) { console.error(`${query} -> ${err}`) } return false } exports.checkZoneNegative = async function (zone, ip) { - // RFC 5782 § 5 // IPv4-based DNSxLs MUST NOT contain an entry for 127.0.0.1. @@ -309,14 +322,13 @@ exports.checkZoneNegative = async function (zone, ip) { // results here are invalid // const txt = await dns.resolve4(query, 'TXT') // if (txt && txt !== a) console.warn(`${query} -> ${a}\t${txt}`) - this.disable_zone(zone, a); + this.disable_zone(zone, a) } - } - catch (err) { + } catch (err) { switch (err.code) { - case dnsPromises.NOTFOUND: // IP not listed + case dnsPromises.NOTFOUND: // IP not listed return true - case dnsPromises.TIMEOUT: // list timed out + case dnsPromises.TIMEOUT: // list timed out this.disable_zone(zone, err.code) } console.error(`${query} -> got err ${err}`) @@ -325,11 +337,10 @@ exports.checkZoneNegative = async function (zone, ip) { } exports.check_zone = async function (zone) { + if (!(await this.checkZonePositive(zone, '127.0.0.2'))) return false + if (!(await this.checkZoneNegative(zone, '127.0.0.1'))) return false - if (!await this.checkZonePositive(zone, '127.0.0.2')) return false - if (!await this.checkZoneNegative(zone, '127.0.0.1')) return false - - this.enable_zone(zone) // both tests passed + this.enable_zone(zone) // both tests passed if (this.cfg[zone].ipv6 === true) { await this.checkZonePositive(zone, '::FFFF:7F00:2') @@ -339,52 +350,53 @@ exports.check_zone = async function (zone) { return true } -exports.check_zones = async function (interval, done) { - - if (interval) interval = parseInt(interval); +exports.check_zones = async function (interval) { + if (interval) interval = parseInt(interval) for (const zone of this.cfg.main.zones) { try { - await this.check_zone(zone); - } - catch (err) { + await this.check_zone(zone) + } catch (err) { console.error(`zone ${zone} err: ${err}`) } } // Set a timer to re-test if (interval && interval >= 5 && !this._interval) { - this.logdebug(`will re-test list zones every ${interval} minutes`); - this._interval = setInterval(() => { - this.check_zones(); - }, (interval * 60) * 1000); + this.loginfo(`will re-test list zones every ${interval} minutes`) + this._interval = setInterval( + () => { + this.check_zones() + }, + interval * 60 * 1000, + ) this._interval.unref() // don't block node process from exiting } } exports.shutdown = function () { - clearInterval(this._interval); - if (redis_client) redis_client.quit(); + clearInterval(this._interval) + if (redis_client) redis_client.quit() } -exports.enable_zone = function (zone, result) { - if (!zone) return false; +exports.enable_zone = function (zone) { + if (!zone) return false const type = this.getListType(zone) if (!this.zones.has(zone)) { - this.loginfo(`enabling ${type} zone ${zone}`); + this.loginfo(`enabling ${type} zone ${zone}`) this.zones.add(zone, true) } } exports.disable_zone = function (zone, result) { - if (!zone) return false; + if (!zone) return false const type = this.getListType(zone) if (!this.zones.has(zone)) return false - this.logwarn(`disabling ${type} zone '${zone}' ${result ? result : ''}`); + this.logwarn(`disabling ${type} zone '${zone}' ${result ? result : ''}`) this.zones.delete(zone) return true } diff --git a/package.json b/package.json index b169ea3..2c262d4 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,21 @@ { "name": "haraka-plugin-dns-list", - "version": "1.0.1", + "version": "1.0.2", "description": "Haraka plugin for DNS lists (DNSBL, DNSWL)", "main": "index.js", + "files": [ + "config", + "CHANGELOG.md" + ], "scripts": { - "lint": "npx eslint *.js test", - "lintfix": "npx eslint --fix *.js test", - "versions": "npx dependency-version-checker check", - "test": "npx mocha" + "format": "npm run prettier:fix && npm run lint:fix", + "lint": "npx eslint@^8 *.js test", + "lint:fix": "npx eslint@^8 *.js test --fix", + "prettier": "npx prettier . --check", + "prettier:fix": "npx prettier . --write --log-level=warn", + "test": "npx mocha@^10", + "versions": "npx @msimerson/dependency-version-checker check", + "versions:fix": "npx @msimerson/dependency-version-checker update && npx prettier package.json --write --log-level=warn" }, "repository": { "type": "git", @@ -27,12 +35,10 @@ }, "homepage": "https://github.com/haraka/haraka-plugin-dns-list#readme", "devDependencies": { - "eslint": "^8.55.0", - "eslint-plugin-haraka": "*", - "haraka-test-fixtures": "*", - "mocha": "^10.2.0" + "@haraka/eslint-config": "^1.1.3", + "haraka-test-fixtures": "^1.3.6" }, "dependencies": { - "haraka-net-utils": "^1.5.3" + "haraka-net-utils": "^1.5.4" } } diff --git a/test/dns-list.js b/test/dns-list.js index 829fd4b..8147fb1 100644 --- a/test/dns-list.js +++ b/test/dns-list.js @@ -1,6 +1,5 @@ - // node.js built-in modules -const assert = require('assert') +const assert = require('assert') // npm modules const fixtures = require('haraka-test-fixtures') @@ -12,7 +11,6 @@ beforeEach(function () { }) describe('dns-list', function () { - it('plugin loads', function () { assert.ok(this.plugin) }) @@ -41,46 +39,45 @@ describe('dns-list', function () { }) describe('lookup', function () { - it('Spamcop, test IPv4', async function () { const a = await this.plugin.lookup('127.0.0.2', 'bl.spamcop.net') - assert.deepStrictEqual(['127.0.0.2'], a); + assert.deepStrictEqual(['127.0.0.2'], a) }) it('Spamcop, unlisted IPv6', async function () { const r = await this.plugin.lookup('::1', 'bl.spamcop.net') - assert.deepStrictEqual(undefined, r); + assert.deepStrictEqual(undefined, r) }) it('b.barracudacentral.org, unlisted IPv6', async function () { const r = await this.plugin.lookup('::1', 'b.barracudacentral.org') - assert.deepStrictEqual(undefined, r); + assert.deepStrictEqual(undefined, r) }) it('Spamcop, unlisted IPv4', async function () { const a = await this.plugin.lookup('127.0.0.1', 'bl.spamcop.net') - assert.deepStrictEqual(undefined, a); + assert.deepStrictEqual(undefined, a) }) it('CBL', async function () { const a = await this.plugin.lookup('127.0.0.2', 'xbl.spamhaus.org') - assert.deepStrictEqual(a, [ '127.0.0.4' ]); + assert.deepStrictEqual(a, ['127.0.0.4']) }) }) describe('check_zone', function () { it('tests DNS list bl.spamcop.net', async function () { - const r = await this.plugin.check_zone('bl.spamcop.net'); + const r = await this.plugin.check_zone('bl.spamcop.net') assert.deepStrictEqual(r, true) }) it('tests DNS list zen.spamhaus.org', async function () { - const r = await this.plugin.check_zone('zen.spamhaus.org'); + const r = await this.plugin.check_zone('zen.spamhaus.org') assert.deepStrictEqual(r, true) }) it('tests DNS list hostkarma.junkemailfilter.com', async function () { - const r = await this.plugin.check_zone('hostkarma.junkemailfilter.com'); + const r = await this.plugin.check_zone('hostkarma.junkemailfilter.com') assert.deepStrictEqual(r, true) }) }) @@ -89,84 +86,78 @@ describe('check_zones', function () { this.timeout(22000) it('tests each block list', async function () { - await this.plugin.check_zones(6000); + await this.plugin.check_zones(6000) }) }) describe('onConnect', function () { - beforeEach(function () { this.connection = fixtures.connection.createConnection() }) it('onConnect 127.0.0.1', function (done) { this.connection.set('remote.ip', '127.0.0.1') - this.plugin.zones = new Set([ 'bl.spamcop.net', 'list.dnswl.org' ]) + this.plugin.zones = new Set(['bl.spamcop.net', 'list.dnswl.org']) this.plugin.onConnect((code, msg) => { - assert.strictEqual(code, undefined); - assert.strictEqual(msg, undefined); + assert.strictEqual(code, undefined) + assert.strictEqual(msg, undefined) done() - }, - this.connection) + }, this.connection) }) it('onConnect 127.0.0.2', function (done) { this.connection.set('remote.ip', '127.0.0.2') - this.plugin.zones = new Set([ 'bl.spamcop.net', 'list.dnswl.org' ]) + this.plugin.zones = new Set(['bl.spamcop.net', 'list.dnswl.org']) this.plugin.onConnect((code, msg) => { // console.log(`code: ${code}, ${msg}`) if (code === OK) { - assert.strictEqual(code, OK); - assert.strictEqual(msg, 'host [127.0.0.2] is listed on list.dnswl.org'); - } - else { - assert.strictEqual(code, DENY); - assert.strictEqual(msg, 'host [127.0.0.2] is listed on bl.spamcop.net'); + assert.strictEqual(code, OK) + assert.strictEqual(msg, 'host [127.0.0.2] is listed on list.dnswl.org') + } else { + assert.strictEqual(code, DENY) + assert.strictEqual(msg, 'host [127.0.0.2] is listed on bl.spamcop.net') } done() - }, - this.connection) + }, this.connection) }) it('Spamcop + CBL', function (done) { this.connection.set('remote.ip', '127.0.0.2') - this.plugin.zones = new Set([ 'bl.spamcop.net', 'xbl.spamhaus.org' ]) + this.plugin.zones = new Set(['bl.spamcop.net', 'xbl.spamhaus.org']) this.plugin.onConnect((code, msg) => { // console.log(`code: ${code}, ${msg}`) - assert.strictEqual(code, DENY); - assert.ok(/is listed on/.test(msg)); + assert.strictEqual(code, DENY) + assert.ok(/is listed on/.test(msg)) done() - }, - this.connection) + }, this.connection) }) it('Spamcop + CBL + negative result', function (done) { this.connection.set('remote.ip', '127.0.0.1') - this.plugin.zones = new Set([ 'bl.spamcop.net', 'xbl.spamhaus.org' ]) + this.plugin.zones = new Set(['bl.spamcop.net', 'xbl.spamhaus.org']) this.plugin.onConnect((code, msg) => { // console.log(`test return ${code} ${msg}`) - assert.strictEqual(code, undefined); - assert.strictEqual(msg, undefined); + assert.strictEqual(code, undefined) + assert.strictEqual(msg, undefined) done() }, this.connection) }) it('IPv6 addresses supported', function (done) { this.connection.set('remote.ip', '::1') - this.plugin.zones = new Set(['bl.spamcop.net','xbl.spamhaus.org']) + this.plugin.zones = new Set(['bl.spamcop.net', 'xbl.spamhaus.org']) this.plugin.onConnect((code, msg) => { - assert.strictEqual(code, undefined); - assert.strictEqual(msg, undefined); - done(); + assert.strictEqual(code, undefined) + assert.strictEqual(msg, undefined) + done() }, this.connection) }) }) describe('first', function () { - beforeEach(function () { - this.plugin.cfg.main.search='first' - this.plugin.zones = new Set([ 'xbl.spamhaus.org', 'bl.spamcop.net' ]); + this.plugin.cfg.main.search = 'first' + this.plugin.zones = new Set(['xbl.spamhaus.org', 'bl.spamcop.net']) this.connection = fixtures.connection.createConnection() }) @@ -174,11 +165,10 @@ describe('first', function () { this.connection.set('remote.ip', '127.0.0.2') this.plugin.onConnect((code, msg) => { // console.log(`onConnect return ${code} ${msg}`) - assert.strictEqual(code, DENY); - assert.ok(/is listed on/.test(msg)); - done(); - }, - this.connection) + assert.strictEqual(code, DENY) + assert.ok(/is listed on/.test(msg)) + done() + }, this.connection) }) it('negative result', function (done) { @@ -188,31 +178,35 @@ describe('first', function () { assert.strictEqual(code, undefined) assert.strictEqual(msg, undefined) done() - }, - this.connection) + }, this.connection) }) }) describe('disable_zone', function () { - it('empty request', function () { - assert.strictEqual(this.plugin.disable_zone(), false); + assert.strictEqual(this.plugin.disable_zone(), false) }) it('testbl1, no zones', function () { - this.plugin.zones=new Set() - assert.strictEqual(this.plugin.disable_zone('testbl1', 'test result'), false); + this.plugin.zones = new Set() + assert.strictEqual( + this.plugin.disable_zone('testbl1', 'test result'), + false, + ) }) it('testbl1, zones miss', function () { - this.plugin.zones = new Set([ 'testbl2' ]) - assert.strictEqual(this.plugin.disable_zone('testbl1', 'test result'), false); - assert.strictEqual(this.plugin.zones.size, 1); + this.plugin.zones = new Set(['testbl2']) + assert.strictEqual( + this.plugin.disable_zone('testbl1', 'test result'), + false, + ) + assert.strictEqual(this.plugin.zones.size, 1) }) it('testbl1, zones hit', function () { - this.plugin.zones = new Set([ 'testbl1' ]); - assert.strictEqual(this.plugin.disable_zone('testbl1', 'test result'), true); - assert.strictEqual(this.plugin.zones.size, 0); + this.plugin.zones = new Set(['testbl1']) + assert.strictEqual(this.plugin.disable_zone('testbl1', 'test result'), true) + assert.strictEqual(this.plugin.zones.size, 0) }) })