diff --git a/.codeclimate.yml b/.codeclimate.yml
index 0e443ca..c889eb8 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,10 +1,10 @@
engines:
eslint:
enabled: true
- channel: "eslint-8"
+ channel: 'eslint-8'
config:
- config: ".eslintrc.yaml"
+ config: '.eslintrc.yaml'
ratings:
- paths:
- - "**.js"
+ paths:
+ - '**.js'
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index ddf5f86..035a400 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -4,4 +4,4 @@ env:
mocha: true
es2022: true
-extends: ['@haraka']
\ No newline at end of file
+extends: ['@haraka']
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 957ccf3..4221f1c 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,10 +1,12 @@
Fixes #
Changes proposed in this pull request:
--
--
+
+-
+-
Checklist:
+
- [ ] docs updated
- [ ] tests updated
- [ ] Changes.md updated
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0449e4a..d450132 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: "weekly"
+ interval: 'weekly'
allow:
- dependency-type: production
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9a519f2..3d01042 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,11 @@
name: CI
-on: [ push, pull_request ]
+on: [push, pull_request]
env:
CI: true
jobs:
-
lint:
uses: haraka/.github/.github/workflows/lint.yml@master
@@ -15,9 +14,9 @@ jobs:
# secrets: inherit
ubuntu:
- needs: [ lint ]
+ needs: [lint]
uses: haraka/.github/.github/workflows/ubuntu.yml@master
windows:
- needs: [ lint ]
- uses: haraka/.github/.github/workflows/windows.yml@master
\ No newline at end of file
+ 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 d489fbd..e81c15f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -13,4 +13,4 @@ env:
jobs:
publish:
uses: haraka/.github/.github/workflows/publish.yml@master
- secrets: inherit
\ No newline at end of file
+ secrets: inherit
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 28f1e4e..037f735 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,7 +2,7 @@
This handcrafted artisinal software is brought to you by:
-|
msimerson (13)|
gramakri (1)|
DoobleD (1)|
smfreegard (1)|
ne4t0 (1)|
-| :---: | :---: | :---: | :---: | :---: |
+|
msimerson (13) |
gramakri (1) |
DoobleD (1) |
smfreegard (1) |
ne4t0 (1) |
+| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
this file is maintained by [.release](https://github.com/msimerson/.release)
diff --git a/README.md b/README.md
index a894e41..78b8489 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,11 @@ domain whilst preserving the original return-path.
## Configuration
--------------
+---
This plugin uses spf.ini for configuration and the following options are
available:
-
```ini
[relay]
context=sender (default: sender)
@@ -64,27 +63,27 @@ openspf_text = true
### Things to Know
-* Most senders do not publish SPF records for their mail server *hostname*,
+- Most senders do not publish SPF records for their mail server _hostname_,
which means that the SPF HELO test rarely passes. During observation in 2014,
more spam senders have valid SPF HELO than ham senders. If you expect very
little from SPF HELO validation, you might still be disappointed.
-* Enabling error deferrals will cause excessive delays and perhaps bounced
+- Enabling error deferrals will cause excessive delays and perhaps bounced
mail for senders with broken DNS. Enable this only if you are willing to
delay and sometimes lose valid mail.
-* Broken SPF records by valid senders are common. Keep that in mind when
+- Broken SPF records by valid senders are common. Keep that in mind when
considering denial of SPF error results. If you deny on error, budget
time for instructing senders on how to correct their SPF records so they
can email you.
-* The only deny option most sites should consider is `mfrom_fail`. That will
+- The only deny option most sites should consider is `mfrom_fail`. That will
reject messages that explicitely fail SPF tests. SPF failures have a high
correlation with spam. However, up to 10% of ham transits forwarders and/or
email lists which frequently break SPF. SPF results are best used as inputs
to other plugins such as DMARC, [spamassassin](http://haraka.github.io/manual/plugins/spamassassin.html), and [karma](http://haraka.github.io/manual/plugins/karma.html).
-* Heed well the implications of SPF, as described in [RFC 4408](http://tools.ietf.org/html/rfc4408#section-9.3)
+- Heed well the implications of SPF, as described in [RFC 4408](http://tools.ietf.org/html/rfc4408#section-9.3)
### spf.ini default settings
@@ -140,9 +139,7 @@ mfrom_permerror=false
openspf_text=false
```
-
-Testing
--------
+## Testing
This plugin also provides a command-line test tool that can be used to debug SPF issues or to check results.
@@ -167,8 +164,8 @@ You can add `--debug` to the option arguments to see a full trace of the SPF pro
Node does not support the SPF DNS Resource Record type. Only TXT records are
checked. This is a non-issue as < 1% (as of 2014) of SPF records use the SPF RR type. Due to lack of adoption, SPF has deprecated the SPF RR type.
-
+
[ci-img]: https://github.com/haraka/haraka-plugin-spf/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/haraka/haraka-plugin-spf/actions/workflows/ci.yml
[clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-spf/badges/gpa.svg
diff --git a/bin/spf b/bin/spf
index 109429f..7a3e24b 100755
--- a/bin/spf
+++ b/bin/spf
@@ -2,46 +2,55 @@
// SPF test tool
-const nopt = require('nopt');
-const path = require('path');
-const base_path = path.join(__dirname, '..');
-const SPF = require(`${base_path}/lib/spf`).SPF;
-const spf = new SPF();
-
-const parsed = nopt({ 'debug': Boolean, 'ip': String, 'helo': String, 'domain': String });
-
-function print_usage () {
- console.log('Usage: spf [--debug] --ip --helo --domain ');
- process.exit(1);
+const nopt = require('nopt')
+const path = require('path')
+const base_path = path.join(__dirname, '..')
+const SPF = require(`${base_path}/lib/spf`).SPF
+const spf = new SPF()
+
+const parsed = nopt({
+ debug: Boolean,
+ ip: String,
+ helo: String,
+ domain: String,
+})
+
+function print_usage() {
+ console.log('Usage: spf [--debug] --ip --helo --domain ')
+ process.exit(1)
}
-if (!parsed.ip || (parsed.ip && (!parsed.domain && !parsed.helo))) {
- print_usage();
+if (!parsed.ip || (parsed.ip && !parsed.domain && !parsed.helo)) {
+ print_usage()
}
if (!parsed.debug) {
- SPF.prototype.log_debug = function (str) {}
+ SPF.prototype.log_debug = function () {}
}
-let domain;
+let domain
if (parsed.domain) {
- domain = /@(.+)$/.exec(parsed.domain);
+ domain = /@(.+)$/.exec(parsed.domain)
if (domain) {
- domain = domain[1];
- }
- else {
- domain = parsed.domain;
+ domain = domain[1]
+ } else {
+ domain = parsed.domain
}
}
-spf.check_host(parsed.ip, (domain ? domain : parsed.helo)).then((result) => {
- console.log([
- `ip=${parsed.ip}`,
- `helo="${(parsed.helo ? parsed.helo : '')}"`,
- `domain="${(domain ? domain : '')}"`,
- `result=${spf.result(result)}`
- ].join(' '));
-}).catch((err) => {
- console.error(`Error: ${err.message}`);
- process.exit(1);
-});
+spf
+ .check_host(parsed.ip, domain ? domain : parsed.helo)
+ .then((result) => {
+ console.log(
+ [
+ `ip=${parsed.ip}`,
+ `helo="${parsed.helo ? parsed.helo : ''}"`,
+ `domain="${domain ? domain : ''}"`,
+ `result=${spf.result(result)}`,
+ ].join(' '),
+ )
+ })
+ .catch((err) => {
+ console.error(`Error: ${err.message}`)
+ process.exit(1)
+ })
diff --git a/index.js b/index.js
index b72d136..0edf96d 100644
--- a/index.js
+++ b/index.js
@@ -1,217 +1,250 @@
// spf
-const SPF = require('./lib/spf').SPF;
-const net_utils = require('haraka-net-utils');
-const DSN = require('haraka-dsn');
+const SPF = require('./lib/spf').SPF
+const net_utils = require('haraka-net-utils')
+const DSN = require('haraka-dsn')
-exports.SPF = SPF;
+exports.SPF = SPF
exports.register = function () {
-
// Override logging in SPF module
- SPF.prototype.log_debug = str => this.logdebug(str);
+ SPF.prototype.log_debug = (str) => this.logdebug(str)
- this.load_spf_ini();
+ this.load_spf_ini()
- this.register_hook('helo', 'helo_spf');
- this.register_hook('ehlo', 'helo_spf');
+ this.register_hook('helo', 'helo_spf')
+ this.register_hook('ehlo', 'helo_spf')
}
exports.load_spf_ini = function () {
- this.nu = net_utils; // so tests can set public_ip
- this.SPF = SPF;
-
- this.cfg = this.config.get('spf.ini', {
- booleans: [
- '-defer.helo_temperror',
- '-defer.mfrom_temperror',
-
- '-defer_relay.helo_temperror',
- '-defer_relay.mfrom_temperror',
-
- '-deny.helo_none',
- '-deny.helo_softfail',
- '-deny.helo_fail',
- '-deny.helo_permerror',
- '-deny.openspf_text',
-
- '-deny.mfrom_none',
- '-deny.mfrom_softfail',
- '-deny.mfrom_fail',
- '-deny.mfrom_permerror',
-
- '-deny_relay.helo_none',
- '-deny_relay.helo_softfail',
- '-deny_relay.helo_fail',
- '-deny_relay.helo_permerror',
-
- '-deny_relay.mfrom_none',
- '-deny_relay.mfrom_softfail',
- '-deny_relay.mfrom_fail',
- '-deny_relay.mfrom_permerror',
- '-deny_relay.openspf_text',
-
- '-skip.relaying',
- '-skip.auth',
- ]
- },
- () => { this.load_spf_ini(); }
- );
+ this.nu = net_utils // so tests can set public_ip
+ this.SPF = SPF
+
+ this.cfg = this.config.get(
+ 'spf.ini',
+ {
+ booleans: [
+ '-defer.helo_temperror',
+ '-defer.mfrom_temperror',
+
+ '-defer_relay.helo_temperror',
+ '-defer_relay.mfrom_temperror',
+
+ '-deny.helo_none',
+ '-deny.helo_softfail',
+ '-deny.helo_fail',
+ '-deny.helo_permerror',
+ '-deny.openspf_text',
+
+ '-deny.mfrom_none',
+ '-deny.mfrom_softfail',
+ '-deny.mfrom_fail',
+ '-deny.mfrom_permerror',
+
+ '-deny_relay.helo_none',
+ '-deny_relay.helo_softfail',
+ '-deny_relay.helo_fail',
+ '-deny_relay.helo_permerror',
+
+ '-deny_relay.mfrom_none',
+ '-deny_relay.mfrom_softfail',
+ '-deny_relay.mfrom_fail',
+ '-deny_relay.mfrom_permerror',
+ '-deny_relay.openspf_text',
+
+ '-skip.relaying',
+ '-skip.auth',
+ ],
+ },
+ () => {
+ this.load_spf_ini()
+ },
+ )
// when set, preserve legacy config settings
- for (const phase of ['helo','mail']) {
+ for (const phase of ['helo', 'mail']) {
if (this.cfg.main[`${phase}_softfail_reject`]) {
- this.cfg.deny[`${phase}_softfail`] = true;
+ this.cfg.deny[`${phase}_softfail`] = true
}
if (this.cfg.main[`${phase}_fail_reject`]) {
- this.cfg.deny[`${phase}_fail`] = true;
+ this.cfg.deny[`${phase}_fail`] = true
}
if (this.cfg.main[`${phase}_temperror_defer`]) {
- this.cfg.defer[`${phase}_temperror`] = true;
+ this.cfg.defer[`${phase}_temperror`] = true
}
if (this.cfg.main[`${phase}_permerror_reject`]) {
- this.cfg.deny[`${phase}_permerror`] = true;
+ this.cfg.deny[`${phase}_permerror`] = true
}
}
if (!this.cfg.relay) {
- this.cfg.relay = { context: 'sender' }; // default/legacy
+ this.cfg.relay = { context: 'sender' } // default/legacy
}
- this.cfg.lookup_timeout = this.cfg.main.lookup_timeout || this.timeout - 1;
+ this.cfg.lookup_timeout = this.cfg.main.lookup_timeout || this.timeout - 1
}
exports.helo_spf = async function (next, connection, helo) {
- const plugin = this;
+ const plugin = this
// bypass auth'ed or relay'ing hosts if told to
- const skip_reason = this.skip_hosts(connection);
+ const skip_reason = this.skip_hosts(connection)
if (skip_reason) {
- connection.results.add(plugin, {skip: `helo(${skip_reason})`});
- return next();
+ connection.results.add(plugin, { skip: `helo(${skip_reason})` })
+ return next()
}
// Bypass private IPs
if (connection.remote.is_private) {
- connection.results.add(plugin, {skip: 'helo(private_ip)'});
- return next();
+ connection.results.add(plugin, { skip: 'helo(private_ip)' })
+ return next()
}
// RFC 4408, 2.1: "SPF clients must be prepared for the "HELO"
// identity to be malformed or an IP address literal.
if (net_utils.is_ip_literal(helo)) {
- connection.results.add(plugin, {skip: 'helo(ip_literal)'});
- return next();
+ connection.results.add(plugin, { skip: 'helo(ip_literal)' })
+ return next()
}
// avoid 2nd EHLO evaluation if EHLO host is identical
- const results = connection.results.get(plugin);
- if (results && results.domain === helo) return next();
+ const results = connection.results.get(plugin)
+ if (results && results.domain === helo) return next()
- let timeout = false;
- const spf = new SPF();
+ let timeout = false
+ const spf = new SPF()
const timer = setTimeout(() => {
- timeout = true;
- connection.loginfo(plugin, 'timeout');
- next();
- }, plugin.cfg.lookup_timeout * 1000);
+ timeout = true
+ connection.loginfo(plugin, 'timeout')
+ next()
+ }, plugin.cfg.lookup_timeout * 1000)
try {
const result = await spf.check_host(connection.remote.ip, helo, null)
- if (timer) clearTimeout(timer);
- if (timeout) return;
- const host = connection.hello.host;
- plugin.log_result(connection, 'helo', host, `postmaster@${host}`, spf.result(result));
-
- connection.notes.spf_helo = result; // used between hooks
+ if (timer) clearTimeout(timer)
+ if (timeout) return
+ const host = connection.hello.host
+ plugin.log_result(
+ connection,
+ 'helo',
+ host,
+ `postmaster@${host}`,
+ spf.result(result),
+ )
+
+ connection.notes.spf_helo = result // used between hooks
connection.results.add(plugin, {
scope: 'helo',
result: spf.result(result),
domain: host,
emit: true,
- });
- if (spf.result(result) === 'Pass') connection.results.add(plugin, { pass: host });
+ })
+ if (spf.result(result) === 'Pass')
+ connection.results.add(plugin, { pass: host })
+ } catch (err) {
+ connection.logerror(plugin, err)
}
- catch (err) {
- connection.logerror(plugin, err);
- }
- next();
+ next()
}
exports.hook_mail = async function (next, connection, params) {
- const plugin = this;
+ const plugin = this
- const txn = connection?.transaction;
- if (!txn) return next();
+ const txn = connection?.transaction
+ if (!txn) return next()
// bypass auth'ed or relay'ing hosts if told to
- const skip_reason = this.skip_hosts(connection);
+ const skip_reason = this.skip_hosts(connection)
if (skip_reason) {
- txn.results.add(plugin, {skip: `host(${skip_reason})`});
- return next(CONT, `skipped because host(${skip_reason})`);
+ txn.results.add(plugin, { skip: `host(${skip_reason})` })
+ return next(CONT, `skipped because host(${skip_reason})`)
}
// For messages from private IP space...
if (connection.remote?.is_private) {
- if (!connection.relaying) return next();
+ if (!connection.relaying) return next()
if (plugin.cfg.relay?.context !== 'myself') {
- txn.results.add(plugin, {skip: 'host(private_ip)'});
- return next(CONT, 'envelope from private IP space');
+ txn.results.add(plugin, { skip: 'host(private_ip)' })
+ return next(CONT, 'envelope from private IP space')
}
}
- const mfrom = params[0].address();
- const host = params[0].host;
- let spf = new SPF();
- let auth_result;
+ const mfrom = params[0].address()
+ const host = params[0].host
+ let spf = new SPF()
+ let auth_result
if (connection.notes?.spf_helo) {
- const h_result = connection.notes.spf_helo;
- const h_host = connection.hello?.host;
- plugin.save_to_header(connection, spf, h_result, mfrom, h_host, 'helo');
- if (!host) { // Use results from HELO if the return-path is null
- auth_result = spf.result(h_result).toLowerCase();
- connection.auth_results(`spf=${auth_result} smtp.helo=${h_host}`);
-
- const sender = `<> via ${h_host}`;
- return plugin.return_results(next, connection, spf, 'helo', h_result, sender);
+ const h_result = connection.notes.spf_helo
+ const h_host = connection.hello?.host
+ plugin.save_to_header(connection, spf, h_result, mfrom, h_host, 'helo')
+ if (!host) {
+ // Use results from HELO if the return-path is null
+ auth_result = spf.result(h_result).toLowerCase()
+ connection.auth_results(`spf=${auth_result} smtp.helo=${h_host}`)
+
+ const sender = `<> via ${h_host}`
+ return plugin.return_results(
+ next,
+ connection,
+ spf,
+ 'helo',
+ h_result,
+ sender,
+ )
}
}
- if (!host) return next(); // null-sender
+ if (!host) return next() // null-sender
- let timeout = false;
+ let timeout = false
const timer = setTimeout(() => {
- timeout = true;
- connection.loginfo(plugin, 'timeout');
- next();
- }, plugin.cfg.lookup_timeout * 1000);
+ timeout = true
+ connection.loginfo(plugin, 'timeout')
+ next()
+ }, plugin.cfg.lookup_timeout * 1000)
- spf.helo = connection.hello?.host;
+ spf.helo = connection.hello?.host
- function ch_cb (err, result, ip) {
- if (timer) clearTimeout(timer);
- if (timeout) return;
+ function ch_cb(err, result, ip) {
+ if (timer) clearTimeout(timer)
+ if (timeout) return
if (err) {
- connection.logerror(plugin, err);
- return next();
+ connection.logerror(plugin, err)
+ return next()
}
- plugin.log_result(connection, 'mfrom', host, mfrom, spf.result(result), (ip ? ip : connection.remote.ip));
- plugin.save_to_header(connection, spf, result, mfrom, host, 'mailfrom', (ip ? ip : connection.remote.ip));
-
- auth_result = spf.result(result).toLowerCase();
- connection.auth_results(`spf=${auth_result} smtp.mailfrom=${host}`);
-
- txn.notes.spf_mail_result = spf.result(result);
- txn.notes.spf_mail_record = spf.spf_record;
+ plugin.log_result(
+ connection,
+ 'mfrom',
+ host,
+ mfrom,
+ spf.result(result),
+ ip ? ip : connection.remote.ip,
+ )
+ plugin.save_to_header(
+ connection,
+ spf,
+ result,
+ mfrom,
+ host,
+ 'mailfrom',
+ ip ? ip : connection.remote.ip,
+ )
+
+ auth_result = spf.result(result).toLowerCase()
+ connection.auth_results(`spf=${auth_result} smtp.mailfrom=${host}`)
+
+ txn.notes.spf_mail_result = spf.result(result)
+ txn.notes.spf_mail_record = spf.spf_record
txn.results.add(plugin, {
scope: 'mfrom',
result: spf.result(result),
domain: host,
emit: true,
- });
- if (spf.result(result) === 'Pass') connection.results.add(plugin, { pass: host });
- plugin.return_results(next, connection, spf, 'mfrom', result, mfrom);
+ })
+ if (spf.result(result) === 'Pass')
+ connection.results.add(plugin, { pass: host })
+ plugin.return_results(next, connection, spf, 'mfrom', result, mfrom)
}
try {
@@ -228,91 +261,104 @@ exports.hook_mail = async function (next, connection, params) {
// outbound (relaying), context=myself
const my_public_ip = await net_utils.get_public_ip()
- let spf_result;
- if (result) spf_result = spf.result(result).toLowerCase();
+ let spf_result
+ if (result) spf_result = spf.result(result).toLowerCase()
if (spf_result && spf_result !== 'pass') {
if (!my_public_ip) {
- return ch_cb(new Error(`failed to discover public IP`));
+ return ch_cb(new Error(`failed to discover public IP`))
}
- spf = new SPF();
+ spf = new SPF()
const r = await spf.check_host(my_public_ip, host, mfrom)
- return ch_cb(null, r, my_public_ip);
+ return ch_cb(null, r, my_public_ip)
}
- ch_cb(null, result, connection.remote.ip);
- }
- catch (err) {
+ ch_cb(null, result, connection.remote.ip)
+ } catch (err) {
ch_cb(err)
}
}
exports.log_result = function (connection, scope, host, mfrom, result, ip) {
- const show_ip=ip ? ip : connection.remote.ip;
- connection.loginfo(this, `identity=${scope} ip=${show_ip} domain="${host}" mfrom=<${mfrom}> result=${result}`);
+ const show_ip = ip ? ip : connection.remote.ip
+ connection.loginfo(
+ this,
+ `identity=${scope} ip=${show_ip} domain="${host}" mfrom=<${mfrom}> result=${result}`,
+ )
}
-exports.return_results = function (next, connection, spf, scope, result, sender) {
- const msgpre = (scope === 'helo') ? `sender ${sender}` : `sender <${sender}>`;
- const deny = connection.relaying ? 'deny_relay' : 'deny';
- const defer = connection.relaying ? 'defer_relay' : 'defer';
- const sender_id = (scope === 'helo') ? connection.hello_host : sender;
- let text = DSN.sec_unauthorized(`http://www.openspf.org/Why?s=${scope}&id=${sender_id}&ip=${connection.remote.ip}`);
+exports.return_results = function (
+ next,
+ connection,
+ spf,
+ scope,
+ result,
+ sender,
+) {
+ const msgpre = scope === 'helo' ? `sender ${sender}` : `sender <${sender}>`
+ const deny = connection.relaying ? 'deny_relay' : 'deny'
+ const defer = connection.relaying ? 'defer_relay' : 'defer'
+ const sender_id = scope === 'helo' ? connection.hello_host : sender
+ let text = DSN.sec_unauthorized(
+ `http://www.openspf.org/Why?s=${scope}&id=${sender_id}&ip=${connection.remote.ip}`,
+ )
switch (result) {
case spf.SPF_NONE:
if (this.cfg[deny][`${scope}_none`]) {
- text = this.cfg[deny].openspf_text ? text : `${msgpre} SPF record not found`;
- return next(DENY, text);
+ text = this.cfg[deny].openspf_text
+ ? text
+ : `${msgpre} SPF record not found`
+ return next(DENY, text)
}
- return next();
+ return next()
case spf.SPF_NEUTRAL:
case spf.SPF_PASS:
- return next();
+ return next()
case spf.SPF_SOFTFAIL:
if (this.cfg[deny][`${scope}_softfail`]) {
- text = this.cfg[deny].openspf_text ? text : `${msgpre} SPF SoftFail`;
- return next(DENY, text);
+ text = this.cfg[deny].openspf_text ? text : `${msgpre} SPF SoftFail`
+ return next(DENY, text)
}
- return next();
+ return next()
case spf.SPF_FAIL:
if (this.cfg[deny][`${scope}_fail`]) {
- text = this.cfg[deny].openspf_text ? text : `${msgpre} SPF Fail`;
- return next(DENY, text);
+ text = this.cfg[deny].openspf_text ? text : `${msgpre} SPF Fail`
+ return next(DENY, text)
}
- return next();
+ return next()
case spf.SPF_TEMPERROR:
if (this.cfg[defer][`${scope}_temperror`]) {
- return next(DENYSOFT, `${msgpre} SPF Temporary Error`);
+ return next(DENYSOFT, `${msgpre} SPF Temporary Error`)
}
- return next();
+ return next()
case spf.SPF_PERMERROR:
if (this.cfg[deny][`${scope}_permerror`]) {
- return next(DENY, `${msgpre} SPF Permanent Error`);
+ return next(DENY, `${msgpre} SPF Permanent Error`)
}
- return next();
+ return next()
default:
// Unknown result
- connection.logerror(this, `unknown result code=${result}`);
- return next();
+ connection.logerror(this, `unknown result code=${result}`)
+ return next()
}
}
exports.save_to_header = (connection, spf, result, mfrom, host, id, ip) => {
// Add a trace header
- if (!connection?.transaction) return;
-
- const des = result === spf.SPF_PASS ? 'designates' : 'does not designate';
- const identity = `identity=${id}; client-ip=${ip ? ip : connection.remote.ip}`;
- connection.transaction.add_leading_header('Received-SPF',
- `${spf.result(result)} (${connection.local.host}: domain of ${host} ${des} ${connection.remote.ip} as permitted sender) receiver=${connection.local.host}; ${identity} helo=${connection.hello.host}; envelope-from=<${mfrom}>`
- );
+ if (!connection?.transaction) return
+
+ const des = result === spf.SPF_PASS ? 'designates' : 'does not designate'
+ const identity = `identity=${id}; client-ip=${ip ? ip : connection.remote.ip}`
+ connection.transaction.add_leading_header(
+ 'Received-SPF',
+ `${spf.result(result)} (${connection.local.host}: domain of ${host} ${des} ${connection.remote.ip} as permitted sender) receiver=${connection.local.host}; ${identity} helo=${connection.hello.host}; envelope-from=<${mfrom}>`,
+ )
}
exports.skip_hosts = function (connection) {
-
- const skip = this?.cfg?.skip;
+ const skip = this?.cfg?.skip
if (skip) {
- if (skip.relaying && connection.relaying) return 'relay';
- if (skip.auth && connection.notes.auth_user) return 'auth';
+ if (skip.relaying && connection.relaying) return 'relay'
+ if (skip.auth && connection.notes.auth_user) return 'auth'
}
}
diff --git a/lib/spf.js b/lib/spf.js
index 718fb8a..508dcde 100644
--- a/lib/spf.js
+++ b/lib/spf.js
@@ -1,216 +1,227 @@
-'use strict';
+'use strict'
// spf
-const dns = require('dns').promises;
-const ipaddr = require('ipaddr.js');
+const dns = require('node:dns/promises')
+const net = require('node:net')
+const ipaddr = require('ipaddr.js')
const net_utils = require('haraka-net-utils')
class SPF {
- constructor (count, been_there) {
+ constructor(count, been_there) {
// For macro expansion
// This should be set before check_host() is called
- this.helo = 'unknown';
- this.spf_record = '';
+ this.helo = 'unknown'
+ this.spf_record = ''
// RFC 4408 Section 10.1
// Limit the number of mechanisms/modifiers that require DNS lookups to complete.
- this.count = 0;
+ this.count = 0
// If we have recursed we are supplied the count
- if (count) this.count = count;
+ if (count) this.count = count
// Prevent circular references, this isn't covered in the RFC
- this.been_there = {};
- if (been_there) this.been_there = been_there;
+ this.been_there = {}
+ if (been_there) this.been_there = been_there
// RFC 4408 Section 10.1
- this.LIMIT = 10;
+ this.LIMIT = 10
// Constants
- this.SPF_NONE = 1;
- this.SPF_PASS = 2;
- this.SPF_FAIL = 3;
- this.SPF_SOFTFAIL = 4;
- this.SPF_NEUTRAL = 5;
- this.SPF_TEMPERROR = 6;
- this.SPF_PERMERROR = 7;
-
- this.mech_ip4 = this.mech_ip;
- this.mech_ip6 = this.mech_ip;
+ this.SPF_NONE = 1
+ this.SPF_PASS = 2
+ this.SPF_FAIL = 3
+ this.SPF_SOFTFAIL = 4
+ this.SPF_NEUTRAL = 5
+ this.SPF_TEMPERROR = 6
+ this.SPF_PERMERROR = 7
+
+ this.mech_ip4 = this.mech_ip
+ this.mech_ip6 = this.mech_ip
}
- const_translate (value) {
- const t = {};
+ const_translate(value) {
+ const t = {}
for (const k in this) {
if (typeof this[k] === 'number') {
- t[this[k]] = k.toUpperCase();
+ t[this[k]] = k.toUpperCase()
}
}
- if (t[value]) return t[value];
- return 'UNKNOWN';
+ if (t[value]) return t[value]
+ return 'UNKNOWN'
}
- result (value) {
+ result(value) {
switch (value) {
- case this.SPF_NONE: return 'None'
- case this.SPF_PASS: return 'Pass'
- case this.SPF_FAIL: return 'Fail'
- case this.SPF_SOFTFAIL: return 'SoftFail'
- case this.SPF_NEUTRAL: return 'Neutral'
- case this.SPF_TEMPERROR: return 'TempError'
- case this.SPF_PERMERROR: return 'PermError'
- default: return `Unknown (${value})`;
+ case this.SPF_NONE:
+ return 'None'
+ case this.SPF_PASS:
+ return 'Pass'
+ case this.SPF_FAIL:
+ return 'Fail'
+ case this.SPF_SOFTFAIL:
+ return 'SoftFail'
+ case this.SPF_NEUTRAL:
+ return 'Neutral'
+ case this.SPF_TEMPERROR:
+ return 'TempError'
+ case this.SPF_PERMERROR:
+ return 'PermError'
+ default:
+ return `Unknown (${value})`
}
}
- return_const (qualifier) {
+ return_const(qualifier) {
switch (qualifier) {
- case '+': return this.SPF_PASS
- case '-': return this.SPF_FAIL
- case '~': return this.SPF_SOFTFAIL
- case '?': return this.SPF_NEUTRAL
- default : return this.SPF_PERMERROR
+ case '+':
+ return this.SPF_PASS
+ case '-':
+ return this.SPF_FAIL
+ case '~':
+ return this.SPF_SOFTFAIL
+ case '?':
+ return this.SPF_NEUTRAL
+ default:
+ return this.SPF_PERMERROR
}
}
- expand_macros (str) {
- const macro = /%{([slodipvh])((?:(?:\d+)?r?)?)?([-.+,/_=])?}/ig;
- let match;
+ expand_macros(str) {
+ const macro = /%{([slodipvh])((?:(?:\d+)?r?)?)?([-.+,/_=])?}/gi
+ let match
while ((match = macro.exec(str))) {
// match[1] = macro-letter
// match[2] = transformers
// match[3] = delimiter
if (!match[3]) match[3] = '.'
- let strip = /(\d+)/.exec(match[2]);
- if (strip) strip = strip[1];
+ let strip = /(\d+)/.exec(match[2])
+ if (strip) strip = strip[1]
- const reverse = (((`${match[2]}`).indexOf('r')) !== -1);
- let replace;
- let kind;
+ const reverse = `${match[2]}`.indexOf('r') !== -1
+ let replace
+ let kind
switch (match[1]) {
- case 's': // sender
- replace = this.mail_from;
- break;
- case 'l': // local-part of sender
- replace = (this.mail_from.split('@'))[0];
- break;
- case 'o': // domain of sender
- replace = (this.mail_from.split('@'))[1];
- break;
- case 'd': // domain
- replace = this.domain;
- break;
- case 'i': // IP
- replace = this.ip;
- break;
- case 'p': // validated domain name of IP
+ case 's': // sender
+ replace = this.mail_from
+ break
+ case 'l': // local-part of sender
+ replace = this.mail_from.split('@')[0]
+ break
+ case 'o': // domain of sender
+ replace = this.mail_from.split('@')[1]
+ break
+ case 'd': // domain
+ replace = this.domain
+ break
+ case 'i': // IP
+ replace = this.ip
+ break
+ case 'p': // validated domain name of IP
// NOT IMPLEMENTED
- replace = 'unknown';
- break;
- case 'v': // IP version
+ replace = 'unknown'
+ break
+ case 'v': // IP version
try {
- if (this.ip_ver === 'ipv4') kind = 'in-addr';
- if (this.ip_ver === 'ipv6') kind = 'ip6';
- replace = kind;
- }
- catch (e) {}
- break;
- case 'h': // EHLO/HELO domain
- replace = this.helo;
- break;
+ if (this.ip_ver === 'ipv4') kind = 'in-addr'
+ if (this.ip_ver === 'ipv6') kind = 'ip6'
+ replace = kind
+ } catch (e) {}
+ break
+ case 'h': // EHLO/HELO domain
+ replace = this.helo
+ break
}
// Process any transformers
if (replace) {
if (reverse || strip) {
- replace = replace.split(match[3]);
+ replace = replace.split(match[3])
if (strip) {
- strip = ((strip > replace.length) ? replace.length : strip);
- replace = replace.slice(0,strip);
+ strip = strip > replace.length ? replace.length : strip
+ replace = replace.slice(0, strip)
}
- if (reverse) replace = replace.reverse();
- replace = replace.join('.');
+ if (reverse) replace = replace.reverse()
+ replace = replace.join('.')
}
- str = str.replace(match[0], replace);
+ str = str.replace(match[0], replace)
}
}
// Process any other expansions
- return str
- .replace(/%%/g, '%')
- .replace(/%_/g, ' ')
- .replace(/%-/g, '%20');
+ return str.replace(/%%/g, '%').replace(/%_/g, ' ').replace(/%-/g, '%20')
}
- log_debug (str) {
- console.error(str);
+ log_debug(str) {
+ console.error(str)
}
- valid_ip (ip) {
- const ip_split = /^:([^/ ]+)(?:\/([^ ]+))?$/.exec(ip);
+ valid_ip(ip) {
+ const ip_split = /^:([^/ ]+)(?:\/([^ ]+))?$/.exec(ip)
if (!ip_split) {
- this.log_debug(`invalid IP address: ${ip}`);
- return false;
+ this.log_debug(`invalid IP address: ${ip}`)
+ return false
}
if (!ipaddr.isValid(ip_split[1])) {
- this.log_debug(`invalid IP address: ${ip_split[1]}`);
- return false;
+ this.log_debug(`invalid IP address: ${ip_split[1]}`)
+ return false
}
- return true;
+ return true
}
- async check_host (ip, domain, mail_from) {
- domain = domain.toLowerCase();
+ async check_host(ip, domain, mail_from) {
+ domain = domain.toLowerCase()
mail_from = mail_from ? mail_from.toLowerCase() : `postmaster@${domain}`
- this.ipaddr = ipaddr.parse(ip);
- this.ip_ver = this.ipaddr.kind();
+ this.ipaddr = ipaddr.parse(ip)
+ this.ip_ver = this.ipaddr.kind()
if (this.ip_ver === 'ipv6') {
- this.ip = this.ipaddr.toString();
- }
- else {
- this.ip = ip;
+ this.ip = this.ipaddr.toString()
+ } else {
+ this.ip = ip
}
- this.domain = domain;
- this.mail_from = mail_from;
+ this.domain = domain
+ this.mail_from = mail_from
- this.log_debug(`ip=${ip} domain=${domain} mail_from=${mail_from}`);
+ this.log_debug(`ip=${ip} domain=${domain} mail_from=${mail_from}`)
- const mech_array = [];
- const mod_array = [];
+ const mech_array = []
+ const mod_array = []
// Get the SPF record for domain
let txt_rrs
try {
txt_rrs = await dns.resolveTxt(domain)
- }
- catch (err) {
- this.log_debug(`error looking up TXT record: ${err.message}`);
+ } catch (err) {
+ this.log_debug(`error looking up TXT record: ${err.message}`)
switch (err.code) {
case dns.NOTFOUND:
case dns.NODATA:
- case dns.NXDOMAIN: return this.SPF_NONE
- default: return this.SPF_TEMPERROR
+ case dns.NXDOMAIN:
+ return this.SPF_NONE
+ default:
+ return this.SPF_TEMPERROR
}
}
- let spf_record;
- let match;
+ let spf_record
+ let match
for (let txt_rr of txt_rrs) {
// txt_rr might be an array, so handle that case
if (Array.isArray(txt_rr)) {
- txt_rr = txt_rr.join('');
+ txt_rr = txt_rr.join('')
}
- match = /^(v=spf1(?:$|\s.+$))/i.exec(txt_rr);
+ match = /^(v=spf1(?:$|\s.+$))/i.exec(txt_rr)
if (!match) {
- this.log_debug(`discarding TXT record: ${txt_rr}`);
- continue;
+ this.log_debug(`discarding TXT record: ${txt_rr}`)
+ continue
}
if (!spf_record) {
- this.log_debug(`found SPF record for domain ${domain}: ${match[1]}`);
- spf_record = match[1].replace(/\s+/, ' ').toLowerCase();
- }
- else {
- this.log_debug(`found additional SPF record for domain ${domain}: ${match[1]}`);
+ this.log_debug(`found SPF record for domain ${domain}: ${match[1]}`)
+ spf_record = match[1].replace(/\s+/, ' ').toLowerCase()
+ } else {
+ this.log_debug(
+ `found additional SPF record for domain ${domain}: ${match[1]}`,
+ )
return this.SPF_PERMERROR
}
}
@@ -218,79 +229,82 @@ class SPF {
if (!spf_record) return this.SPF_NONE // No SPF record?
// Store the SPF record used in the object
- this.spf_record = spf_record;
+ this.spf_record = spf_record
// Validate SPF record and build call chain
- const mech_regexp1 = /^([-+~?])?(all|a|mx|ptr)$/;
- const mech_regexp2 = /^([-+~?])?(a|mx|ptr|ip4|ip6|include|exists)((?::[^/ ]+(?:\/\d+(?:\/\/\d+)?)?)|\/\d+(?:\/\/\d+)?)$/;
- const mod_regexp = /^([^ =]+)=([a-z0-9:/._-]+)$/;
- const split = spf_record.split(' ');
+ const mech_regexp1 = /^([-+~?])?(all|a|mx|ptr)$/
+ const mech_regexp2 =
+ /^([-+~?])?(a|mx|ptr|ip4|ip6|include|exists)((?::[^/ ]+(?:\/\d+(?:\/\/\d+)?)?)|\/\d+(?:\/\/\d+)?)$/
+ const mod_regexp = /^([^ =]+)=([a-z0-9:/._-]+)$/
+ const split = spf_record.split(' ')
for (const mechanism of split) {
- if (!mechanism) continue; // Skip blanks
+ if (!mechanism) continue // Skip blanks
const obj = {}
- if ((match = (mech_regexp1.exec(mechanism) || mech_regexp2.exec(mechanism)))) {
+ if (
+ (match = mech_regexp1.exec(mechanism) || mech_regexp2.exec(mechanism))
+ ) {
// match: 1=qualifier, 2=mechanism, 3=optional args
- if (!match[1]) match[1] = '+';
- this.log_debug(`found mechanism: ${match}`);
+ if (!match[1]) match[1] = '+'
+ this.log_debug(`found mechanism: ${match}`)
if (match[2] === 'ip4' || match[2] === 'ip6') {
if (!this.valid_ip(match[3])) return this.SPF_PERMERROR
- }
- else {
+ } else {
// Validate macro strings
if (match[3] && /%[^{%+-]/.exec(match[3])) {
- this.log_debug('invalid macro string');
+ this.log_debug('invalid macro string')
return this.SPF_PERMERROR
}
if (match[3]) {
// Expand macros
- match[3] = this.expand_macros(match[3]);
+ match[3] = this.expand_macros(match[3])
}
}
- obj[match[2]] = [ match[1], match[3] ];
- mech_array.push(obj);
+ obj[match[2]] = [match[1], match[3]]
+ mech_array.push(obj)
// console.log(mech_array)
- }
- else if ((match = mod_regexp.exec(mechanism))) {
- this.log_debug(`found modifier: ${match}`);
+ } else if ((match = mod_regexp.exec(mechanism))) {
+ this.log_debug(`found modifier: ${match}`)
// match[1] = modifier
// match[2] = name
// Make sure we have a method
if (!this[`mod_${match[1]}`]) {
- this.log_debug(`skipping unknown modifier: ${match[1]}`);
- }
- else {
- obj[match[1]] = match[2];
- mod_array.push(obj);
+ this.log_debug(`skipping unknown modifier: ${match[1]}`)
+ } else {
+ obj[match[1]] = match[2]
+ mod_array.push(obj)
// console.log(mod_array)
}
- }
- else {
+ } else {
// Syntax error
- this.log_debug(`syntax error: ${mechanism}`);
+ this.log_debug(`syntax error: ${mechanism}`)
return this.SPF_PERMERROR
}
}
- this.log_debug(`SPF record for '${this.domain}' validated OK`);
+ this.log_debug(`SPF record for '${this.domain}' validated OK`)
// Run all the mechanisms first
for (const mech of mech_array) {
-
- const func = Object.keys(mech);
- const args = mech[func];
+ const func = Object.keys(mech)
+ const args = mech[func]
// console.log(`running mechanism: ${func} args=${args} domain=${this.domain}`);
- this.log_debug(`running mechanism: ${func} args=${args} domain=${this.domain}`);
+ this.log_debug(
+ `running mechanism: ${func} args=${args} domain=${this.domain}`,
+ )
if (this.count > this.LIMIT) {
- this.log_debug('lookup limit reached');
+ this.log_debug('lookup limit reached')
return this.SPF_PERMERROR
}
- const result = await this[`mech_${func}`](((args && args.length) ? args[0] : null), ((args && args.length) ? args[1] : null));
+ const result = await this[`mech_${func}`](
+ args && args.length ? args[0] : null,
+ args && args.length ? args[1] : null,
+ )
// console.log(result)
// If we have a result other than SPF_NONE
@@ -299,15 +313,16 @@ class SPF {
// run any modifiers
for (const mod of mod_array) {
-
- const func = Object.keys(mod);
- const args = mod[func];
- this.log_debug(`running modifier: ${func} args=${args} domain=${this.domain}`);
- const result = await this[`mod_${func}`](args);
+ const func = Object.keys(mod)
+ const args = mod[func]
+ this.log_debug(
+ `running modifier: ${func} args=${args} domain=${this.domain}`,
+ )
+ const result = await this[`mod_${func}`](args)
// Check limits
if (this.count > this.LIMIT) {
- this.log_debug('lookup limit reached');
+ this.log_debug('lookup limit reached')
return this.SPF_PERMERROR
}
@@ -318,49 +333,53 @@ class SPF {
return this.SPF_NEUTRAL // default if no more mechanisms
}
- async mech_all (qualifier, args) {
+ async mech_all(qualifier) {
return this.return_const(qualifier)
}
- async mech_include (qualifier, args) {
- const domain = args.substr(1);
+ async mech_include(qualifier, args) {
+ const domain = args.substr(1)
// Avoid circular references
if (this.been_there[domain]) {
- this.log_debug(`circular reference detected: ${domain}`);
+ this.log_debug(`circular reference detected: ${domain}`)
return this.SPF_NONE
}
- this.count++;
- this.been_there[domain] = true;
+ this.count++
+ this.been_there[domain] = true
// Recurse
- const recurse = new SPF(this.count, this.been_there);
+ const recurse = new SPF(this.count, this.been_there)
try {
const result = await recurse.check_host(this.ip, domain, this.mail_from)
- this.log_debug(`mech_include: domain=${domain} returned=${this.const_translate(result)}`);
+ this.log_debug(
+ `mech_include: domain=${domain} returned=${this.const_translate(result)}`,
+ )
switch (result) {
- case this.SPF_PASS: return this.SPF_PASS
+ case this.SPF_PASS:
+ return this.SPF_PASS
case this.SPF_FAIL:
case this.SPF_SOFTFAIL:
- case this.SPF_NEUTRAL: return this.SPF_NONE
- case this.SPF_TEMPERROR: return this.SPF_TEMPERROR
- default: return this.SPF_PERMERROR
+ case this.SPF_NEUTRAL:
+ return this.SPF_NONE
+ case this.SPF_TEMPERROR:
+ return this.SPF_TEMPERROR
+ default:
+ return this.SPF_PERMERROR
}
- }
- catch (err) {
+ } catch (err) {
// ignore
}
}
- async mech_exists (qualifier, args) {
- this.count++;
- const exists = args.substr(1);
+ async mech_exists(qualifier, args) {
+ this.count++
+ const exists = args.substr(1)
try {
const addrs = await dns.resolve(exists)
- this.log_debug(`mech_exists: ${exists} result=${addrs.join(',')}`);
+ this.log_debug(`mech_exists: ${exists} result=${addrs.join(',')}`)
return this.return_const(qualifier)
- }
- catch (err) {
- this.log_debug(`mech_exists: ${err}`);
+ } catch (err) {
+ this.log_debug(`mech_exists: ${err}`)
switch (err.code) {
case dns.NOTFOUND:
case dns.NODATA:
@@ -372,44 +391,44 @@ class SPF {
}
}
- async mech_a (qualifier, args) {
- this.count++;
+ async mech_a(qualifier, args) {
+ this.count++
// Parse any arguments
- let cm;
- let cidr4;
- let cidr6;
+ let cm
+ let cidr4
+ let cidr6
if (args && (cm = /\/(\d+)(?:\/\/(\d+))?$/.exec(args))) {
- cidr4 = cm[1];
- cidr6 = cm[2];
+ cidr4 = cm[1]
+ cidr6 = cm[2]
}
- let dm;
- let domain = this.domain;
+ let dm
+ let domain = this.domain
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
- domain = dm[1];
+ domain = dm[1]
}
// Calculate with IP method to use
- let resolve_method;
- let cidr;
+ let resolve_method
+ let cidr
if (this.ip_ver === 'ipv4') {
- cidr = cidr4;
- resolve_method = 'resolve4';
- }
- else if (this.ip_ver === 'ipv6') {
- cidr = cidr6;
- resolve_method = 'resolve6';
+ cidr = cidr4
+ resolve_method = 'resolve4'
+ } else if (this.ip_ver === 'ipv6') {
+ cidr = cidr6
+ resolve_method = 'resolve6'
}
// Use current domain
let addrs
try {
addrs = await dns[resolve_method](domain)
- }
- catch (err) {
- this.log_debug(`mech_a: ${err}`);
+ } catch (err) {
+ this.log_debug(`mech_a: ${err}`)
switch (err.code) {
case dns.NOTFOUND:
case dns.NODATA:
- case dns.NXDOMAIN: return this.SPF_NONE
- default: return this.SPF_TEMPERROR
+ case dns.NXDOMAIN:
+ return this.SPF_NONE
+ default:
+ return this.SPF_TEMPERROR
}
}
@@ -418,94 +437,92 @@ class SPF {
for (const addr of addrs) {
if (cidr) {
// CIDR
- const range = ipaddr.parse(addr);
+ const range = ipaddr.parse(addr)
if (this.ipaddr.match(range, cidr)) {
- this.log_debug(`mech_a: ${this.ip} => ${addr}/${cidr}: MATCH!`);
+ this.log_debug(`mech_a: ${this.ip} => ${addr}/${cidr}: MATCH!`)
return this.return_const(qualifier)
+ } else {
+ this.log_debug(`mech_a: ${this.ip} => ${addr}/${cidr}: NO MATCH`)
}
- else {
- this.log_debug(`mech_a: ${this.ip} => ${addr}/${cidr}: NO MATCH`);
- }
- }
- else {
+ } else {
if (addr === this.ip) {
return this.return_const(qualifier)
- }
- else {
- this.log_debug(`mech_a: ${this.ip} => ${addr}: NO MATCH`);
+ } else {
+ this.log_debug(`mech_a: ${this.ip} => ${addr}: NO MATCH`)
}
}
}
return this.SPF_NONE
}
- async mech_mx (qualifier, args) {
- this.count++;
+ async mech_mx(qualifier, args) {
+ this.count++
// Parse any arguments
- let cm;
- let cidr4;
- let cidr6;
+ let cm
+ let cidr4
+ let cidr6
if (args && (cm = /\/(\d+)((?:\/\/(\d+))?)$/.exec(args))) {
- cidr4 = cm[1];
- cidr6 = cm[2];
+ cidr4 = cm[1]
+ cidr6 = cm[2]
}
- let dm;
- let domain = this.domain;
+ let dm
+ let domain = this.domain
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
- domain = dm[1];
+ domain = dm[1]
}
// Fetch the MX records for the specified domain
let mxes
try {
mxes = await net_utils.get_mx(domain)
- mxes = mxes.filter(mx => !net.isIP(mx.exchange)) // remove implicit MX
- }
- catch (err) {
+ mxes = mxes.filter((mx) => !net.isIP(mx.exchange)) // remove implicit MX
+ } catch (err) {
switch (err.code) {
case dns.NOTFOUND:
case dns.NODATA:
- case dns.NXDOMAIN: return this.SPF_NONE
- default: return this.SPF_TEMPERROR
+ case dns.NXDOMAIN:
+ return this.SPF_NONE
+ default:
+ return this.SPF_TEMPERROR
}
}
- let pending = 0;
- let addresses = [];
+ let pending = 0
+ let addresses = []
// RFC 4408 Section 10.1
if (mxes.length > this.LIMIT) return this.SPF_PERMERROR
for (const element of mxes) {
- pending++;
- const mx = element.exchange;
+ pending++
+ const mx = element.exchange
// Calculate which IP method to use
- let resolve_method;
- let cidr;
+ let resolve_method
+ let cidr
if (this.ip_ver === 'ipv4') {
- cidr = cidr4;
- resolve_method = 'resolve4';
- }
- else if (this.ip_ver === 'ipv6') {
- cidr = cidr6;
- resolve_method = 'resolve6';
+ cidr = cidr4
+ resolve_method = 'resolve4'
+ } else if (this.ip_ver === 'ipv6') {
+ cidr = cidr6
+ resolve_method = 'resolve6'
}
let addrs
try {
addrs = await dns[resolve_method](mx)
- }
- catch (err) {
+ } catch (err) {
switch (err.code) {
case dns.NOTFOUND:
case dns.NODATA:
- case dns.NXDOMAIN: break;
- default: return this.SPF_TEMPERROR
+ case dns.NXDOMAIN:
+ break
+ default:
+ return this.SPF_TEMPERROR
}
}
- pending--;
+ pending--
if (addrs) {
- this.log_debug(`mech_mx: mx=${mx} addresses=${addrs.join(',')}`);
- addresses = addrs.concat(addresses);
+ this.log_debug(`mech_mx: mx=${mx} addresses=${addrs.join(',')}`)
+ addresses = addrs.concat(addresses)
}
if (pending === 0) {
if (!addresses.length) return this.SPF_NONE
@@ -513,25 +530,30 @@ class SPF {
if (cidr) {
// CIDR match type
for (const address of addresses) {
- const range = ipaddr.parse(address);
+ const range = ipaddr.parse(address)
if (this.ipaddr.match(range, cidr)) {
- this.log_debug(`mech_mx: ${this.ip} => ${address}/${cidr}: MATCH!`);
+ this.log_debug(
+ `mech_mx: ${this.ip} => ${address}/${cidr}: MATCH!`,
+ )
return this.return_const(qualifier)
- }
- else {
- this.log_debug(`mech_mx: ${this.ip} => ${address}/${cidr}: NO MATCH`);
+ } else {
+ this.log_debug(
+ `mech_mx: ${this.ip} => ${address}/${cidr}: NO MATCH`,
+ )
}
}
// No matches
return this.SPF_NONE
- }
- else {
+ } else {
if (addresses.includes(this.ip)) {
- this.log_debug(`mech_mx: ${this.ip} => ${addresses.join(',')}: MATCH!`);
+ this.log_debug(
+ `mech_mx: ${this.ip} => ${addresses.join(',')}: MATCH!`,
+ )
return this.return_const(qualifier)
- }
- else {
- this.log_debug(`mech_mx: ${this.ip} => ${addresses.join(',')}: NO MATCH`);
+ } else {
+ this.log_debug(
+ `mech_mx: ${this.ip} => ${addresses.join(',')}: NO MATCH`,
+ )
return this.SPF_NONE
}
}
@@ -542,47 +564,45 @@ class SPF {
if (pending === 0) this.SPF_NONE
}
- async mech_ptr (qualifier, args) {
- this.count++;
- let dm;
- let domain = this.domain;
+ async mech_ptr(qualifier, args) {
+ this.count++
+ let dm
+ let domain = this.domain
if (args && (dm = /^:([^/ ]+)/.exec(args))) {
- domain = dm[1];
+ domain = dm[1]
}
// First do a PTR lookup for the connecting IP
let ptrs
try {
ptrs = await dns.reverse(this.ip)
- }
- catch (err) {
- this.log_debug(`mech_ptr: lookup=${this.ip} => ${err}`);
+ } catch (err) {
+ this.log_debug(`mech_ptr: lookup=${this.ip} => ${err}`)
return this.SPF_NONE
}
- let resolve_method;
- if (this.ip_ver === 'ipv4') resolve_method = 'resolve4';
- if (this.ip_ver === 'ipv6') resolve_method = 'resolve6';
- const names = [];
+ let resolve_method
+ if (this.ip_ver === 'ipv4') resolve_method = 'resolve4'
+ if (this.ip_ver === 'ipv6') resolve_method = 'resolve6'
+ const names = []
// RFC 4408 Section 10.1
if (ptrs.length > this.LIMIT) return this.SPF_PERMERROR
for (const ptr of ptrs) {
-
try {
const addrs = await dns[resolve_method](ptr)
for (const addr of addrs) {
if (addr === this.ip) {
- this.log_debug(`mech_ptr: ${this.ip} => ${ptr} => ${addr}: MATCH!`);
- names.push(ptr.toLowerCase());
- }
- else {
- this.log_debug(`mech_ptr: ${this.ip} => ${ptr} => ${addr}: NO MATCH`);
+ this.log_debug(`mech_ptr: ${this.ip} => ${ptr} => ${addr}: MATCH!`)
+ names.push(ptr.toLowerCase())
+ } else {
+ this.log_debug(
+ `mech_ptr: ${this.ip} => ${ptr} => ${addr}: NO MATCH`,
+ )
}
}
- }
- catch (err) {
+ } catch (err) {
// Skip on error
- this.log_debug(`mech_ptr: lookup=${ptr} => ${err}`);
+ this.log_debug(`mech_ptr: lookup=${ptr} => ${err}`)
continue
}
}
@@ -591,27 +611,25 @@ class SPF {
// Catch bogus PTR matches e.g. ptr:*.bahnhof.se (should be ptr:bahnhof.se)
// These will cause a regexp error, so we can catch them.
try {
- const re = new RegExp(`${domain.replace('.','\\.')}$`, 'i');
+ const re = new RegExp(`${domain.replace('.', '\\.')}$`, 'i')
for (const name of names) {
if (re.test(name)) {
- this.log_debug(`mech_ptr: ${name} => ${domain}: MATCH!`);
+ this.log_debug(`mech_ptr: ${name} => ${domain}: MATCH!`)
return this.return_const(qualifier)
- }
- else {
- this.log_debug(`mech_ptr: ${name} => ${domain}: NO MATCH`);
+ } else {
+ this.log_debug(`mech_ptr: ${name} => ${domain}: NO MATCH`)
}
}
return this.SPF_NONE
- }
- catch (e) {
- this.log_debug('mech_ptr', { domain: this.domain, err: e.message });
+ } catch (e) {
+ this.log_debug('mech_ptr', { domain: this.domain, err: e.message })
return this.SPF_PERMERROR
}
}
- async mech_ip (qualifier, args) {
- const cidr = args.substr(1);
- const match = /^([^/ ]+)(?:\/(\d+))?$/.exec(cidr);
+ async mech_ip(qualifier, args) {
+ const cidr = args.substr(1)
+ const match = /^([^/ ]+)(?:\/(\d+))?$/.exec(cidr)
if (!match) return this.SPF_NONE
// match[1] == ip
@@ -619,49 +637,47 @@ class SPF {
try {
if (!match[2]) {
// Default masks for each IP version
- if (this.ip_ver === 'ipv4') match[2] = '32';
- if (this.ip_ver === 'ipv6') match[2] = '128';
+ if (this.ip_ver === 'ipv4') match[2] = '32'
+ if (this.ip_ver === 'ipv6') match[2] = '128'
}
- const range = ipaddr.parse(match[1]);
- const rtype = range.kind();
+ const range = ipaddr.parse(match[1])
+ const rtype = range.kind()
if (this.ip_ver !== rtype) {
- this.log_debug(`mech_ip: ${this.ip} => ${cidr}: SKIP`);
+ this.log_debug(`mech_ip: ${this.ip} => ${cidr}: SKIP`)
return this.SPF_NONE
}
if (this.ipaddr.match(range, match[2])) {
- this.log_debug(`mech_ip: ${this.ip} => ${cidr}: MATCH!`);
+ this.log_debug(`mech_ip: ${this.ip} => ${cidr}: MATCH!`)
return this.return_const(qualifier)
+ } else {
+ this.log_debug(`mech_ip: ${this.ip} => ${cidr}: NO MATCH`)
}
- else {
- this.log_debug(`mech_ip: ${this.ip} => ${cidr}: NO MATCH`);
- }
- }
- catch (e) {
- this.log_debug(e.message);
+ } catch (e) {
+ this.log_debug(e.message)
return this.SPF_PERMERROR
}
return this.SPF_NONE
}
- async mod_redirect (domain) {
+ async mod_redirect(domain) {
// Avoid circular references
if (this.been_there[domain]) {
- this.log_debug(`circular reference detected: ${domain}`);
+ this.log_debug(`circular reference detected: ${domain}`)
return this.SPF_NONE
}
- this.count++;
- this.been_there[domain] = 1;
- return await this.check_host(this.ip, domain, this.mail_from);
+ this.count++
+ this.been_there[domain] = 1
+ return await this.check_host(this.ip, domain, this.mail_from)
}
- async mod_exp (str) {
+ async mod_exp() {
// NOT IMPLEMENTED
return this.SPF_NONE
}
- async mod_v (str) {
+ async mod_v() {
return this.SPF_NONE
}
}
-exports.SPF = SPF;
+exports.SPF = SPF
diff --git a/package.json b/package.json
index edc75cf..f40f089 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,12 @@
"version": "1.2.5",
"description": "Sender Policy Framework (SPF) plugin for Haraka",
"main": "index.js",
- "files": ["CHANGELOG.md","bin","config","lib"],
+ "files": [
+ "CHANGELOG.md",
+ "bin",
+ "config",
+ "lib"
+ ],
"scripts": {
"format": "npm run prettier:fix && npm run lint:fix",
"lint": "npx eslint@^8 *.js bin/spf lib test",
@@ -19,7 +24,8 @@
"url": "git+https://github.com/haraka/haraka-plugin-spf.git"
},
"keywords": [
- "haraka", "haraka-plugin",
+ "haraka",
+ "haraka-plugin",
"plugin",
"spf"
],
diff --git a/test/index.js b/test/index.js
index bf16cf3..f413b90 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,25 +1,24 @@
-
// node.js built-in modules
-const assert = require('assert')
+const assert = require('assert')
// npm modules
-const Address = require('address-rfc2821').Address;
-const constants = require('haraka-constants');
-const fixtures = require('haraka-test-fixtures')
+const Address = require('address-rfc2821').Address
+const constants = require('haraka-constants')
+const fixtures = require('haraka-test-fixtures')
-const SPF = require('../lib/spf').SPF;
-const spf = new SPF();
+const SPF = require('../lib/spf').SPF
+const spf = new SPF()
beforeEach(function () {
this.plugin = new fixtures.plugin('spf')
- this.plugin.timeout = 8000;
- this.plugin.load_spf_ini();
+ this.plugin.timeout = 8000
+ this.plugin.load_spf_ini()
// comment this line to see detailed SPF evaluation
- this.plugin.SPF.prototype.log_debug = () => {};
+ this.plugin.SPF.prototype.log_debug = () => {}
- this.connection = fixtures.connection.createConnection();
+ this.connection = fixtures.connection.createConnection()
this.connection.init_transaction()
})
@@ -38,216 +37,337 @@ describe('load_spf_ini', function () {
describe('return_results', function () {
it('result, none, reject=false', function (done) {
- this.plugin.cfg.deny.mfrom_none=false;
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_NONE, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_none = false
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_NONE,
+ 'test@example.com',
+ )
})
it('result, none, reject=true', function (done) {
-
- this.plugin.cfg.deny.mfrom_none=true;
- this.plugin.return_results(function next () {
- assert.equal(DENY, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_NONE, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_none = true
+ this.plugin.return_results(
+ function next() {
+ assert.equal(DENY, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_NONE,
+ 'test@example.com',
+ )
})
it('result, neutral', function (done) {
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_NEUTRAL, 'test@example.com');
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_NEUTRAL,
+ 'test@example.com',
+ )
})
it('result, pass', function (done) {
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_PASS, 'test@example.com');
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_PASS,
+ 'test@example.com',
+ )
})
it('result, softfail, reject=false', function (done) {
- this.plugin.cfg.deny.mfrom_softfail=false;
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_SOFTFAIL, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_softfail = false
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_SOFTFAIL,
+ 'test@example.com',
+ )
})
it('result, softfail, reject=true', function (done) {
- this.plugin.cfg.deny.mfrom_softfail=true;
- this.plugin.return_results(function next () {
- assert.equal(DENY, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_SOFTFAIL, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_softfail = true
+ this.plugin.return_results(
+ function next() {
+ assert.equal(DENY, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_SOFTFAIL,
+ 'test@example.com',
+ )
})
it('result, fail, reject=false', function (done) {
- this.plugin.cfg.deny.mfrom_fail=false;
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_FAIL, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_fail = false
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_FAIL,
+ 'test@example.com',
+ )
})
it('result, fail, reject=true', function (done) {
- this.plugin.cfg.deny.mfrom_fail=true;
- this.plugin.return_results(function next () {
- assert.equal(DENY, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_FAIL, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_fail = true
+ this.plugin.return_results(
+ function next() {
+ assert.equal(DENY, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_FAIL,
+ 'test@example.com',
+ )
})
it('result, temperror, reject=false', function (done) {
- this.plugin.cfg.defer.mfrom_temperror=false;
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_TEMPERROR, 'test@example.com');
+ this.plugin.cfg.defer.mfrom_temperror = false
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_TEMPERROR,
+ 'test@example.com',
+ )
})
it('result, temperror, reject=true', function (done) {
- this.plugin.cfg.defer.mfrom_temperror=true;
- this.plugin.return_results(function next () {
- assert.equal(DENYSOFT, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_TEMPERROR, 'test@example.com');
+ this.plugin.cfg.defer.mfrom_temperror = true
+ this.plugin.return_results(
+ function next() {
+ assert.equal(DENYSOFT, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_TEMPERROR,
+ 'test@example.com',
+ )
})
it('result, permerror, reject=false', function (done) {
- this.plugin.cfg.deny.mfrom_permerror=false;
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_PERMERROR, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_permerror = false
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_PERMERROR,
+ 'test@example.com',
+ )
})
it('result, permerror, reject=true', function (done) {
- this.plugin.cfg.deny.mfrom_permerror=true;
- this.plugin.return_results( function next () {
- assert.equal(DENY, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', spf.SPF_PERMERROR, 'test@example.com');
+ this.plugin.cfg.deny.mfrom_permerror = true
+ this.plugin.return_results(
+ function next() {
+ assert.equal(DENY, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ spf.SPF_PERMERROR,
+ 'test@example.com',
+ )
})
it('result, unknown', function (done) {
- this.plugin.return_results(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, spf, 'mfrom', 'unknown', 'test@example.com');
+ this.plugin.return_results(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ spf,
+ 'mfrom',
+ 'unknown',
+ 'test@example.com',
+ )
})
})
describe('hook_helo', function () {
it('rfc1918', function (done) {
- let completed = 0;
- function next (rc) {
- completed++;
- assert.equal(undefined, rc);
+ let completed = 0
+ function next(rc) {
+ completed++
+ assert.equal(undefined, rc)
if (completed >= 2) done()
}
- this.connection.remote.is_private=true;
- this.plugin.helo_spf(next, this.connection);
- this.plugin.helo_spf(next, this.connection, 'helo.sender.com');
+ this.connection.remote.is_private = true
+ this.plugin.helo_spf(next, this.connection)
+ this.plugin.helo_spf(next, this.connection, 'helo.sender.com')
})
it('IPv4 literal', function (done) {
- this.connection.remote.ip='190.168.1.1';
- this.plugin.helo_spf(function next (rc) {
- assert.equal(undefined, rc);
- done()
- }, this.connection, '[190.168.1.1]' );
+ this.connection.remote.ip = '190.168.1.1'
+ this.plugin.helo_spf(
+ function next(rc) {
+ assert.equal(undefined, rc)
+ done()
+ },
+ this.connection,
+ '[190.168.1.1]',
+ )
})
it('MX with no A record', function (done) {
this.timeout(5000)
- this.connection.set('remote.ip', '192.0.2.0');
- this.plugin.helo_spf(function next (rc) {
- assert.equal(undefined, rc);
- done()
- }, this.connection, 'test.haraka.tnpi.net' );
+ this.connection.set('remote.ip', '192.0.2.0')
+ this.plugin.helo_spf(
+ function next(rc) {
+ assert.equal(undefined, rc)
+ done()
+ },
+ this.connection,
+ 'test.haraka.tnpi.net',
+ )
})
})
-const test_addr = new Address('');
+const test_addr = new Address('')
describe('hook_mail', function () {
-
it('rfc1918', function (done) {
- this.connection.set('remote.is_private', true);
- this.connection.set('remote.ip', '192.168.1.1');
- this.plugin.hook_mail(function next () {
- assert.equal(undefined, arguments[0]);
- done()
- }, this.connection, [test_addr]);
+ this.connection.set('remote.is_private', true)
+ this.connection.set('remote.ip', '192.168.1.1')
+ this.plugin.hook_mail(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ done()
+ },
+ this.connection,
+ [test_addr],
+ )
})
it('rfc1918 relaying', function (done) {
- this.connection.set('remote.is_private', true);
- this.connection.set('remote.ip','192.168.1.1');
- this.connection.relaying=true;
- this.plugin.hook_mail(function next () {
- assert.ok([undefined, constants.CONT].includes(arguments[0]));
- done()
- }, this.connection, [test_addr]);
+ this.connection.set('remote.is_private', true)
+ this.connection.set('remote.ip', '192.168.1.1')
+ this.connection.relaying = true
+ this.plugin.hook_mail(
+ function next() {
+ assert.ok([undefined, constants.CONT].includes(arguments[0]))
+ done()
+ },
+ this.connection,
+ [test_addr],
+ )
})
it('no txn', function (done) {
- this.connection.remote.ip='207.85.1.1';
- delete this.connection.transaction;
- this.plugin.hook_mail(function next () {
- assert.equal(undefined, arguments[0]);
- assert.equal(undefined, arguments[1]);
+ this.connection.remote.ip = '207.85.1.1'
+ delete this.connection.transaction
+ this.plugin.hook_mail(function next() {
+ assert.equal(undefined, arguments[0])
+ assert.equal(undefined, arguments[1])
done()
- }, this.connection);
+ }, this.connection)
})
it('txn, no helo', function (done) {
this.timeout(3000)
- this.plugin.cfg.deny.mfrom_fail = false;
- this.connection.set('remote.ip', '207.85.1.1');
- this.plugin.hook_mail(function next () {
- assert.equal(undefined, arguments[0]);
- assert.equal(undefined, arguments[1]);
- done()
- }, this.connection, [test_addr]);
+ this.plugin.cfg.deny.mfrom_fail = false
+ this.connection.set('remote.ip', '207.85.1.1')
+ this.plugin.hook_mail(
+ function next() {
+ assert.equal(undefined, arguments[0])
+ assert.equal(undefined, arguments[1])
+ done()
+ },
+ this.connection,
+ [test_addr],
+ )
})
it('txn', function (done) {
this.timeout(3000)
- this.connection.set('remote.ip', '207.85.1.1');
- this.connection.set('hello.host', 'mail.example.com');
- this.plugin.hook_mail(function next (rc) {
- assert.equal(undefined, rc);
- done()
- }, this.connection, [test_addr]);
+ this.connection.set('remote.ip', '207.85.1.1')
+ this.connection.set('hello.host', 'mail.example.com')
+ this.plugin.hook_mail(
+ function next(rc) {
+ assert.equal(undefined, rc)
+ done()
+ },
+ this.connection,
+ [test_addr],
+ )
})
it('txn, relaying', function (done) {
this.timeout(3000)
- this.connection.set('remote.ip', '207.85.1.1');
- this.connection.set('relaying', true);
- this.connection.set('hello.host', 'mail.example.com');
- this.plugin.hook_mail(function next (rc) {
- assert.equal(undefined, rc);
- done()
- }, this.connection, [test_addr]);
+ this.connection.set('remote.ip', '207.85.1.1')
+ this.connection.set('relaying', true)
+ this.connection.set('hello.host', 'mail.example.com')
+ this.plugin.hook_mail(
+ function next(rc) {
+ assert.equal(undefined, rc)
+ done()
+ },
+ this.connection,
+ [test_addr],
+ )
})
it('txn, relaying, is_private', function (done) {
this.timeout(8000)
- this.plugin.cfg.relay.context='myself';
- this.plugin.cfg.deny_relay.mfrom_fail = true;
- this.connection.set('remote.ip', '127.0.1.1');
- this.connection.set('remote.is_private', true);
- this.connection.relaying = true;
- this.connection.set('hello.host', 'www.tnpi.net');
- this.plugin.nu.public_ip = '66.128.51.165';
- this.plugin.hook_mail(function next (rc) {
- assert.equal(undefined, rc);
- done()
- }, this.connection, [new Address('')]);
+ this.plugin.cfg.relay.context = 'myself'
+ this.plugin.cfg.deny_relay.mfrom_fail = true
+ this.connection.set('remote.ip', '127.0.1.1')
+ this.connection.set('remote.is_private', true)
+ this.connection.relaying = true
+ this.connection.set('hello.host', 'www.tnpi.net')
+ this.plugin.nu.public_ip = '66.128.51.165'
+ this.plugin.hook_mail(
+ function next(rc) {
+ assert.equal(undefined, rc)
+ done()
+ },
+ this.connection,
+ [new Address('')],
+ )
})
})
diff --git a/test/spf.js b/test/spf.js
index e64a2c8..a260fa3 100644
--- a/test/spf.js
+++ b/test/spf.js
@@ -1,10 +1,8 @@
const assert = require('assert')
-const SPF = require('../lib/spf').SPF;
-
-SPF.prototype.log_debug = () => {}; // noop, hush debug output
-
+const SPF = require('../lib/spf').SPF
+SPF.prototype.log_debug = () => {} // noop, hush debug output
beforeEach(function () {
this.SPF = new SPF()
@@ -12,32 +10,32 @@ beforeEach(function () {
describe('SPF', function () {
it('new SPF', function () {
- assert.ok(this.SPF);
+ assert.ok(this.SPF)
})
it('constants', function () {
- assert.equal(1, this.SPF.SPF_NONE);
- assert.equal(2, this.SPF.SPF_PASS);
- assert.equal(3, this.SPF.SPF_FAIL);
- assert.equal(4, this.SPF.SPF_SOFTFAIL);
- assert.equal(5, this.SPF.SPF_NEUTRAL);
- assert.equal(6, this.SPF.SPF_TEMPERROR);
- assert.equal(7, this.SPF.SPF_PERMERROR);
- assert.equal(10, this.SPF.LIMIT);
+ assert.equal(1, this.SPF.SPF_NONE)
+ assert.equal(2, this.SPF.SPF_PASS)
+ assert.equal(3, this.SPF.SPF_FAIL)
+ assert.equal(4, this.SPF.SPF_SOFTFAIL)
+ assert.equal(5, this.SPF.SPF_NEUTRAL)
+ assert.equal(6, this.SPF.SPF_TEMPERROR)
+ assert.equal(7, this.SPF.SPF_PERMERROR)
+ assert.equal(10, this.SPF.LIMIT)
})
it('mod_redirect, true', async function () {
- this.SPF.been_there['example.com'] = true;
+ this.SPF.been_there['example.com'] = true
const rc = await this.SPF.mod_redirect('example.com')
// assert.equal(null, err);
- assert.equal(1, rc);
+ assert.equal(1, rc)
})
it('mod_redirect, false', async function () {
- this.timeout=4000
- this.SPF.count=0;
- this.SPF.ip='212.70.129.94';
- this.SPF.mail_from='fraud@aexp.com';
+ this.timeout = 4000
+ this.SPF.count = 0
+ this.SPF.ip = '212.70.129.94'
+ this.SPF.mail_from = 'fraud@aexp.com'
const rc = await this.SPF.mod_redirect('aexp.com')
switch (rc) {
@@ -45,53 +43,59 @@ describe('SPF', function () {
// from time to time (this is the third time we've seen it,
// American Express publishes an invalid SPF record which results
// in a PERMERROR. Ignore it.
- assert.equal(rc, 7, "aexp SPF record is broken again");
- break;
+ assert.equal(rc, 7, 'aexp SPF record is broken again')
+ break
case 6:
- assert.equal(rc, 6, "temporary (likely DNS timeout) error");
- break;
+ assert.equal(rc, 6, 'temporary (likely DNS timeout) error')
+ break
default:
- assert.equal(rc, 3);
+ assert.equal(rc, 3)
}
})
it('check_host, gmail.com, fail', async function () {
- this.timeout = 3000;
- this.SPF.count=0;
- const rc = await this.SPF.check_host('212.70.129.94', 'gmail.com', 'haraka.mail@gmail.com')
+ this.timeout = 3000
+ this.SPF.count = 0
+ const rc = await this.SPF.check_host(
+ '212.70.129.94',
+ 'gmail.com',
+ 'haraka.mail@gmail.com',
+ )
switch (rc) {
case 1:
- assert.equal(rc, 1, "none");
- console.log('Why do DNS lookup fail to find gmail SPF record on GitHub Actions?');
- break;
+ assert.equal(rc, 1, 'none')
+ console.log(
+ 'Why do DNS lookup fail to find gmail SPF record on GitHub Actions?',
+ )
+ break
case 3:
- assert.equal(rc, 3, "fail");
- break;
+ assert.equal(rc, 3, 'fail')
+ break
case 4:
- assert.equal(rc, 4, "soft fail");
- break;
+ assert.equal(rc, 4, 'soft fail')
+ break
case 7:
- assert.equal(rc, 7, "perm error");
- break;
+ assert.equal(rc, 7, 'perm error')
+ break
default:
assert.equal(rc, 4)
}
})
it('check_host, facebook.com, pass', async function () {
- this.timeout = 3000;
- this.SPF.count = 0;
- const rc = await this.SPF.check_host('69.171.232.145', 'facebookmail.com');
- assert.equal(rc, this.SPF.SPF_PASS, "pass");
+ this.timeout = 3000
+ this.SPF.count = 0
+ const rc = await this.SPF.check_host('69.171.232.145', 'facebookmail.com')
+ assert.equal(rc, this.SPF.SPF_PASS, 'pass')
})
it('valid_ip, true', function (done) {
- assert.equal(this.SPF.valid_ip(':212.70.129.94'), true);
+ assert.equal(this.SPF.valid_ip(':212.70.129.94'), true)
done()
})
it('valid_ip, false', function (done) {
- assert.equal(this.SPF.valid_ip(':212.70.d.94'), false);
+ assert.equal(this.SPF.valid_ip(':212.70.d.94'), false)
done()
})
})