From 00b1c94a46265b107ed73aed2788594706e22f6f Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 10 Dec 2024 14:10:53 -0800 Subject: [PATCH] automated code formatting --- .codeclimate.yml | 8 +- .eslintrc.yaml | 3 - .github/PULL_REQUEST_TEMPLATE.md | 6 +- .github/workflows/ci.yml | 7 +- .github/workflows/codeql.yml | 6 +- .prettierrc | 2 + CHANGELOG.md | 7 +- CONTRIBUTORS.md | 4 +- README.md | 20 +- index.js | 511 +++++++++++++++++-------------- package.json | 5 +- test/index.js | 366 ++++++++++++---------- 12 files changed, 529 insertions(+), 416 deletions(-) create mode 100644 .prettierrc diff --git a/.codeclimate.yml b/.codeclimate.yml index 665ccf2..9d9502f 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,13 +1,13 @@ engines: eslint: enabled: true - channel: "eslint-6" + channel: 'eslint-6' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' ratings: - paths: - - "**.js" + paths: + - '**.js' checks: return-statements: diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 6eee320..068b9ff 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -6,6 +6,3 @@ env: extends: - '@haraka' - -rules: - indent: [2, 2, {"SwitchCase": 1}] 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/workflows/ci.yml b/.github/workflows/ci.yml index 5d1cca8..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 ] + 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/.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/CHANGELOG.md b/CHANGELOG.md index 6a89174..22f571e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ - ### Unreleased - ### [1.0.5] - 2024-12-10 - populate [files] in package.json. @@ -13,8 +11,7 @@ ### [1.0.4] - 2023-12-12 - ci: publish updates, shared test actions -- doc(README): formatting - +- doc(README): formatting ### [1.0.3] - 2022-06-05 @@ -25,14 +22,12 @@ - ci(codeclimate): relax some checks - doc: fixes for config name - ### 1.0.2 - 2020-08-22 - additional test - updated test to newer JS standards - don't call tests that aren't enabled in config (performance) - ### 1.0.0 - 2020-07-28 - repackaged from Haraka diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 16c57ac..7c8f3cb 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,7 +2,7 @@ This handcrafted artisinal software is brought to you by: -|
msimerson (25)| -| :---: | +|
msimerson (25) | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | this file is maintained by [.release](https://github.com/msimerson/.release) diff --git a/README.md b/README.md index 1c07842..515c795 100644 --- a/README.md +++ b/README.md @@ -29,26 +29,26 @@ $EDITOR config/headers.ini The next two tests encompass the RFC 5322 checks: -## duplicate\_singular +## duplicate_singular Assure that all the singular headers are present only once. The list of headers can be adjusted in config/headers.ini: * singular=Date,From,Sender,Reply-To,To,Cc,Bcc,Message-Id,In-Reply-To,References,Subject -## missing\_required +## missing_required Assuring that all the required headers are present. The list of required headers can be altered in config/headers.ini: required=From,Date -## invalid\_return\_path +## invalid_return_path Messages arriving via the internet should not have a Return-Path header set. This checks for that header (unless connection.relaying is set). -## invalid\_date +## invalid_date Checks the date header and makes sure it's somewhat sane. By default, the date cannot be more than 2 days in the future nor 15 days in the past. These can be @@ -59,12 +59,12 @@ date_future_days=2 date_past_days=15 ``` -## user\_agent +## user_agent Attempt to determine the User-Agent that generated the email. A UA is determinable on about 70% of hammy messages. -## direct\_to\_mx +## direct_to_mx Counts the received headers. If there aren't at least two, then the MUA is attempting direct delivery to us instead of via their outbound SMTP server. @@ -72,12 +72,12 @@ This is typical of spam, our own users sending outbound email (which bypasses this test), and machine generated messages like Facebook/Twitter notifications. -## from\_match +## from_match See if the header From domain matches the envelope FROM domain. There are many legit reasons to not match, but matching domains are far more frequent in ham. -## mailing\_list +## mailing_list Attempt to determine if this message was sent via an email list. This is very rudimentary at present and only detects the most common email lists. @@ -87,7 +87,7 @@ of the minority (~10%) of ham which fails SPF and DKIM tests. This MLM detector is a building block in the ability to detect mail from forwarders and assess their reputability. -## from\_phish +## from_phish A common form of phishing is spamming the From display name with the domain name of the popular entity whose accounts they're phishing for. This tests the domains in the [phish_domains] configuration section. If that domains appears in the From header, it must also appear in the envelope sender address. @@ -123,8 +123,8 @@ messages to be rejected: missing_required=false ``` - + [ci-img]: https://github.com/haraka/haraka-plugin-headers/actions/workflows/ci.yml/badge.svg [ci-url]: https://github.com/haraka/haraka-plugin-headers/actions/workflows/ci.yml [clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-headers/badges/gpa.svg diff --git a/index.js b/index.js index 1f6813d..d17fa6d 100644 --- a/index.js +++ b/index.js @@ -1,127 +1,152 @@ // validate message headers and some fields -const tlds = require('haraka-tld'); +const tlds = require('haraka-tld') const phish_targets = [] exports.register = function () { - - this.load_headers_ini(); + this.load_headers_ini() try { this.addrparser = require('address-rfc2822') - } - catch (e) { - this.logerror("unable to load address-rfc2822, try\n\n\t'npm install -g address-rfc2822'\n\n") + } catch (e) { + this.logerror( + "unable to load address-rfc2822, try\n\n\t'npm install -g address-rfc2822'\n\n", + ) } - if (this.cfg.check.duplicate_singular) this.register_hook('data_post', 'duplicate_singular') - if (this.cfg.check.missing_required) this.register_hook('data_post', 'missing_required') - if (this.cfg.check.invalid_return_path) this.register_hook('data_post', 'invalid_return_path') - if (this.cfg.check.invalid_date) this.register_hook('data_post', 'invalid_date') - if (this.cfg.check.user_agent) this.register_hook('data_post', 'user_agent') - if (this.cfg.check.direct_to_mx) this.register_hook('data_post', 'direct_to_mx') + if (this.cfg.check.duplicate_singular) + this.register_hook('data_post', 'duplicate_singular') + if (this.cfg.check.missing_required) + this.register_hook('data_post', 'missing_required') + if (this.cfg.check.invalid_return_path) + this.register_hook('data_post', 'invalid_return_path') + if (this.cfg.check.invalid_date) + this.register_hook('data_post', 'invalid_date') + if (this.cfg.check.user_agent) this.register_hook('data_post', 'user_agent') + if (this.cfg.check.direct_to_mx) + this.register_hook('data_post', 'direct_to_mx') if (this.addrparser) { - if (this.cfg.check.from_match) this.register_hook('data_post', 'from_match') - if (this.cfg.check.delivered_to) this.register_hook('data_post', 'delivered_to') + if (this.cfg.check.from_match) this.register_hook('data_post', 'from_match') + if (this.cfg.check.delivered_to) + this.register_hook('data_post', 'delivered_to') } - if (this.cfg.check.mailing_list) this.register_hook('data_post', 'mailing_list') - if (this.cfg.check.from_phish) this.register_hook('data_post', 'from_phish') + if (this.cfg.check.mailing_list) + this.register_hook('data_post', 'mailing_list') + if (this.cfg.check.from_phish) this.register_hook('data_post', 'from_phish') } exports.load_headers_ini = function () { - const plugin = this; - plugin.cfg = plugin.config.get('headers.ini', { - booleans: [ - '+check.duplicate_singular', - '+check.missing_required', - '+check.invalid_return_path', - '+check.invalid_date', - '+check.user_agent', - '+check.direct_to_mx', - '+check.from_match', - '+check.delivered_to', - '+check.mailing_list', - '+check.from_phish', - - '-reject.duplicate_singular', - '-reject.missing_required', - '-reject.invalid_return_path', - '-reject.invalid_date', - '+reject.delivered_to', - '-reject.from_phish', - ], - }, () => { - plugin.load_headers_ini() - }) + const plugin = this + plugin.cfg = plugin.config.get( + 'headers.ini', + { + booleans: [ + '+check.duplicate_singular', + '+check.missing_required', + '+check.invalid_return_path', + '+check.invalid_date', + '+check.user_agent', + '+check.direct_to_mx', + '+check.from_match', + '+check.delivered_to', + '+check.mailing_list', + '+check.from_phish', + + '-reject.duplicate_singular', + '-reject.missing_required', + '-reject.invalid_return_path', + '-reject.invalid_date', + '+reject.delivered_to', + '-reject.from_phish', + ], + }, + () => { + plugin.load_headers_ini() + }, + ) for (const d in plugin.cfg.phish_domains) { - phish_targets.push(new RegExp(d.replace('.','[.]'), 'i')) + phish_targets.push(new RegExp(d.replace('.', '[.]'), 'i')) } // console.log(phish_targets) } exports.duplicate_singular = function (next, connection) { - const plugin = this; + const plugin = this // RFC 5322 Section 3.6, Headers that MUST be unique if present - const singular = plugin.cfg.main.singular !== undefined ? - plugin.cfg.main.singular.split(',') : [ - 'Date', 'From', 'Sender', 'Reply-To', 'To', 'Cc', - 'Bcc', 'Message-Id', 'In-Reply-To', 'References', - 'Subject' - ]; - - const failures = []; + const singular = + plugin.cfg.main.singular !== undefined + ? plugin.cfg.main.singular.split(',') + : [ + 'Date', + 'From', + 'Sender', + 'Reply-To', + 'To', + 'Cc', + 'Bcc', + 'Message-Id', + 'In-Reply-To', + 'References', + 'Subject', + ] + + const failures = [] for (const name of singular) { if (connection.transaction.header.get_all(name).length <= 1) { - continue; + continue } - connection.transaction.results.add(plugin, {fail: `duplicate:${name}`}); - failures.push(name); + connection.transaction.results.add(plugin, { fail: `duplicate:${name}` }) + failures.push(name) } if (failures.length) { if (plugin.cfg.reject.duplicate_singular) { - return next(DENY, `Only one ${failures[0]} header allowed. See RFC 5322, Section 3.6`); + return next( + DENY, + `Only one ${failures[0]} header allowed. See RFC 5322, Section 3.6`, + ) } - return next(); + return next() } - connection.transaction.results.add(plugin, {pass: 'duplicate'}); - next(); + connection.transaction.results.add(plugin, { pass: 'duplicate' }) + next() } exports.missing_required = function (next, connection) { - const plugin = this; + const plugin = this // Enforce RFC 5322 Section 3.6, Headers that MUST be present - const required = plugin.cfg.main.required !== undefined ? - plugin.cfg.main.required.split(',') : - ['Date', 'From']; + const required = + plugin.cfg.main.required !== undefined + ? plugin.cfg.main.required.split(',') + : ['Date', 'From'] - const failures = []; + const failures = [] for (const h of required) { if (connection.transaction.header.get_all(h).length === 0) { - connection.transaction.results.add(plugin, {fail: `missing:${h}`}); - failures.push(h); + connection.transaction.results.add(plugin, { fail: `missing:${h}` }) + failures.push(h) } } if (failures.length) { if (plugin.cfg.reject.missing_required) { - return next(DENY, `Required header '${failures[0]}' missing`); + return next(DENY, `Required header '${failures[0]}' missing`) } - return next(); + return next() } - connection.transaction.results.add(plugin, {pass: 'missing'}); - next(); + connection.transaction.results.add(plugin, { pass: 'missing' }) + next() } exports.invalid_return_path = function (next, connection) { - const plugin = this; + const plugin = this // Tests for Return-Path headers that shouldn't be present @@ -130,83 +155,94 @@ exports.invalid_return_path = function (next, connection) { // already contains a Return-path header field. // Return-Path, aka Reverse-PATH, Envelope FROM, RFC5321.MailFrom - const rp = connection.transaction.header.get('Return-Path'); + const rp = connection.transaction.header.get('Return-Path') if (rp) { - if (connection.relaying) { // On messages we originate - connection.transaction.results.add(plugin, {fail: 'Return-Path', emit: true}); + if (connection.relaying) { + // On messages we originate + connection.transaction.results.add(plugin, { + fail: 'Return-Path', + emit: true, + }) if (plugin.cfg.reject.invalid_return_path) { - return next(DENY, "outgoing mail must not have a Return-Path header (RFC 5321)"); + return next( + DENY, + 'outgoing mail must not have a Return-Path header (RFC 5321)', + ) } - return next(); + return next() } // generally, messages from the internet shouldn't have a // Return-Path, except for when they can. Read RFC 5321, it's // complicated. In most cases, The Right Thing to do here is to // strip the Return-Path header. - connection.transaction.remove_header('Return-Path'); + connection.transaction.remove_header('Return-Path') // unless it was added by Haraka. Which at present, doesn't. } - connection.transaction.results.add(plugin, {pass: 'Return-Path'}); - next(); + connection.transaction.results.add(plugin, { pass: 'Return-Path' }) + next() } exports.invalid_date = function (next, connection) { - const plugin = this; + const plugin = this // Assure Date header value is [somewhat] sane - let msg_date = connection.transaction.header.get_all('Date'); - if (!msg_date || msg_date.length === 0) return next(); + let msg_date = connection.transaction.header.get_all('Date') + if (!msg_date || msg_date.length === 0) return next() - connection.logdebug(plugin, `message date: ${msg_date}`); - msg_date = Date.parse(msg_date); + connection.logdebug(plugin, `message date: ${msg_date}`) + msg_date = Date.parse(msg_date) - const date_future_days = plugin.cfg.main.date_future_days !== undefined ? - plugin.cfg.main.date_future_days : - 2; + const date_future_days = + plugin.cfg.main.date_future_days !== undefined + ? plugin.cfg.main.date_future_days + : 2 if (date_future_days > 0) { - const too_future = new Date(); - too_future.setHours(too_future.getHours() + 24 * date_future_days); + const too_future = new Date() + too_future.setHours(too_future.getHours() + 24 * date_future_days) // connection.logdebug(plugin, "too future: " + too_future); if (msg_date > too_future) { - connection.transaction.results.add(plugin, {fail: 'invalid_date(future)'}); + connection.transaction.results.add(plugin, { + fail: 'invalid_date(future)', + }) if (plugin.cfg.reject.invalid_date) { - return next(DENY, "The Date header is too far in the future"); + return next(DENY, 'The Date header is too far in the future') } - return next(); + return next() } } - const date_past_days = plugin.cfg.main.date_past_days !== undefined ? - plugin.cfg.main.date_past_days : - 15; + const date_past_days = + plugin.cfg.main.date_past_days !== undefined + ? plugin.cfg.main.date_past_days + : 15 if (date_past_days > 0) { - const too_old = new Date(); - too_old.setHours(too_old.getHours() - 24 * date_past_days); + const too_old = new Date() + too_old.setHours(too_old.getHours() - 24 * date_past_days) // connection.logdebug(plugin, "too old: " + too_old); if (msg_date < too_old) { - connection.loginfo(plugin, `date is older than: ${too_old}`); - connection.transaction.results.add(plugin, {fail: 'invalid_date(past)'}); + connection.loginfo(plugin, `date is older than: ${too_old}`) + connection.transaction.results.add(plugin, { fail: 'invalid_date(past)' }) if (plugin.cfg.reject.invalid_date) { - return next(DENY, "The Date header is too old"); + return next(DENY, 'The Date header is too old') } - return next(); + return next() } } - connection.transaction.results.add(plugin, {pass: 'invalid_date'}); - next(); + connection.transaction.results.add(plugin, { pass: 'invalid_date' }) + next() } exports.user_agent = function (next, connection) { - const plugin = this; + const plugin = this - if (!connection.transaction) return next(); + if (!connection.transaction) return next() - let found_ua = 0; + let found_ua = 0 // User-Agent: Thunderbird, Squirrelmail, Roundcube, Mutt, MacOutlook, // Kmail, IMP @@ -217,240 +253,267 @@ exports.user_agent = function (next, connection) { // Check for User-Agent const headers = [ - 'user-agent','x-mailer','x-mua','x-yahoo-newman-property', - 'x-ms-has-attach' - ]; + 'user-agent', + 'x-mailer', + 'x-mua', + 'x-yahoo-newman-property', + 'x-ms-has-attach', + ] // for (const h in headers) {} for (const name of headers) { - const header = connection.transaction.header.get(name); - if (!header) continue; // header not present - found_ua++; - connection.transaction.results.add(plugin, {pass: `UA(${header.substring(0,12)})`}); + const header = connection.transaction.header.get(name) + if (!header) continue // header not present + found_ua++ + connection.transaction.results.add(plugin, { + pass: `UA(${header.substring(0, 12)})`, + }) } - if (found_ua) return next(); + if (found_ua) return next() - connection.transaction.results.add(plugin, {fail: 'UA'}); - next(); + connection.transaction.results.add(plugin, { fail: 'UA' }) + next() } exports.direct_to_mx = function (next, connection) { - const plugin = this; + const plugin = this - if (!connection.transaction) return next(); + if (!connection.transaction) return next() // Legit messages normally have at least 2 hops (Received headers) // MUA -> sending MTA -> Receiving MTA (Haraka?) if (connection.notes.auth_user) { // User authenticated, so we're likely the first MTA - connection.transaction.results.add(plugin, {skip: 'direct-to-mx(auth)'}); - return next(); + connection.transaction.results.add(plugin, { skip: 'direct-to-mx(auth)' }) + return next() } // what about connection.relaying? - const received = connection.transaction.header.get_all('received'); + const received = connection.transaction.header.get_all('received') if (!received) { - connection.transaction.results.add(plugin, {fail: 'direct-to-mx(none)'}); - return next(); + connection.transaction.results.add(plugin, { fail: 'direct-to-mx(none)' }) + return next() } - const c = received.length; + const c = received.length if (c < 2) { - connection.transaction.results.add(plugin, {fail: `direct-to-mx(too few Received(${c}))`}); - return next(); + connection.transaction.results.add(plugin, { + fail: `direct-to-mx(too few Received(${c}))`, + }) + return next() } - connection.transaction.results.add(plugin, {pass: `direct-to-mx(${c})`}); - next(); + connection.transaction.results.add(plugin, { pass: `direct-to-mx(${c})` }) + next() } exports.from_match = function (next, connection) { - const plugin = this; + const plugin = this // see if the header From matches the envelope FROM. There are valid // cases to not match (~10% of ham) but a non-match is much more // likely to be spam than ham. This test is useful for heuristics. - if (!connection.transaction) return next(); + if (!connection.transaction) return next() - const env_addr = connection.transaction.mail_from; + const env_addr = connection.transaction.mail_from if (!env_addr) { - connection.transaction.results.add(plugin, {fail: 'from_match(null)'}); - return next(); + connection.transaction.results.add(plugin, { fail: 'from_match(null)' }) + return next() } - const hdr_from = connection.transaction.header.get_decoded('From'); + const hdr_from = connection.transaction.header.get_decoded('From') if (!hdr_from) { - connection.transaction.results.add(plugin, {fail: 'from_match(missing)'}); - return next(); + connection.transaction.results.add(plugin, { fail: 'from_match(missing)' }) + return next() } - let hdr_addr; + let hdr_addr try { - hdr_addr = (plugin.addrparser.parse(hdr_from))[0]; - } - catch (e) { - connection.logwarn(plugin, `parsing "${hdr_from.trim()}" with address-rfc2822 plugin returned error: ${e.message}`); - connection.transaction.results.add(plugin, {fail: 'from_match(rfc_violation)'}); - return next(); + hdr_addr = plugin.addrparser.parse(hdr_from)[0] + } catch (e) { + connection.logwarn( + plugin, + `parsing "${hdr_from.trim()}" with address-rfc2822 plugin returned error: ${e.message}`, + ) + connection.transaction.results.add(plugin, { + fail: 'from_match(rfc_violation)', + }) + return next() } if (!hdr_addr) { - connection.loginfo(plugin, `address at fault is: ${hdr_from}`); - connection.transaction.results.add(plugin, {fail: 'from_match(unparsable)'}); - return next(); + connection.loginfo(plugin, `address at fault is: ${hdr_from}`) + connection.transaction.results.add(plugin, { + fail: 'from_match(unparsable)', + }) + return next() } if (env_addr.address().toLowerCase() === hdr_addr.address.toLowerCase()) { - connection.transaction.results.add(plugin, {pass: 'from_match'}); - return next(); + connection.transaction.results.add(plugin, { pass: 'from_match' }) + return next() } - const extra = ['domain']; - const env_dom = tlds.get_organizational_domain(env_addr.host); - const msg_dom = tlds.get_organizational_domain(hdr_addr.host()); + const extra = ['domain'] + const env_dom = tlds.get_organizational_domain(env_addr.host) + const msg_dom = tlds.get_organizational_domain(hdr_addr.host()) if (env_dom && msg_dom && env_dom.toLowerCase() === msg_dom.toLowerCase()) { - const fcrdns = connection.results.get('fcrdns'); - if (fcrdns && fcrdns.fcrdns && new RegExp(`${msg_dom }\\b`, 'i').test(fcrdns.fcrdns)) { - extra.push('fcrdns'); + const fcrdns = connection.results.get('fcrdns') + if ( + fcrdns && + fcrdns.fcrdns && + new RegExp(`${msg_dom}\\b`, 'i').test(fcrdns.fcrdns) + ) { + extra.push('fcrdns') } - const helo = connection.results.get('helo.checks'); + const helo = connection.results.get('helo.checks') if (helo && helo.helo_host && /msg_dom$/.test(helo.helo_host)) { - extra.push('helo'); + extra.push('helo') } - connection.transaction.results.add(plugin, {pass: `from_match(${extra.join(',')})`}); - return next(); + connection.transaction.results.add(plugin, { + pass: `from_match(${extra.join(',')})`, + }) + return next() } - connection.transaction.results.add(plugin, {emit: true, - fail: `from_match(${env_dom} / ${msg_dom})` - }); - next(); + connection.transaction.results.add(plugin, { + emit: true, + fail: `from_match(${env_dom} / ${msg_dom})`, + }) + next() } exports.delivered_to = function (next, connection) { - const plugin = this; + const plugin = this - const txn = connection.transaction; - if (!txn) return next(); - const del_to = txn.header.get('Delivered-To'); - if (!del_to) return next(); + const txn = connection.transaction + if (!txn) return next() + const del_to = txn.header.get('Delivered-To') + if (!del_to) return next() - const rcpts = connection.transaction.rcpt_to; + const rcpts = connection.transaction.rcpt_to for (const rcptElement of rcpts) { - const rcpt = rcptElement.address(); - if (rcpt !== del_to) continue; - connection.transaction.results.add(plugin, {emit: true, fail: 'delivered_to'}); - if (!plugin.cfg.reject.delivered_to) continue; - return next(DENY, "Invalid Delivered-To header content"); + const rcpt = rcptElement.address() + if (rcpt !== del_to) continue + connection.transaction.results.add(plugin, { + emit: true, + fail: 'delivered_to', + }) + if (!plugin.cfg.reject.delivered_to) continue + return next(DENY, 'Invalid Delivered-To header content') } - next(); + next() } exports.mailing_list = function (next, connection) { - const plugin = this; - if (!connection.transaction) return next(); + const plugin = this + if (!connection.transaction) return next() const mlms = { - 'Mailing-List' : [ - { mlm: 'ezmlm', match: 'ezmlm' }, + 'Mailing-List': [ + { mlm: 'ezmlm', match: 'ezmlm' }, { mlm: 'yahoogroups', match: 'yahoogroups' }, { mlm: 'googlegroups', match: 'googlegroups' }, ], - 'Sender' : [ - { mlm: 'majordomo', start: 'owner-' }, - ], - 'X-Mailman-Version' : [ { mlm: 'mailman' } ], - 'X-Majordomo-Version': [ { mlm: 'majordomo' } ], - 'X-Google-Loop' : [ { mlm: 'googlegroups' } ], - }; + Sender: [{ mlm: 'majordomo', start: 'owner-' }], + 'X-Mailman-Version': [{ mlm: 'mailman' }], + 'X-Majordomo-Version': [{ mlm: 'majordomo' }], + 'X-Google-Loop': [{ mlm: 'googlegroups' }], + } - let found_mlm = 0; - const txr = connection.transaction.results; + let found_mlm = 0 + const txr = connection.transaction.results - Object.keys(mlms).forEach(name => { - const header = connection.transaction.header.get(name); - if (!header) { return; } // header not present + Object.keys(mlms).forEach((name) => { + const header = connection.transaction.header.get(name) + if (!header) { + return + } // header not present for (const j of mlms[name]) { if (j.start) { - if (header.substring(0,j.start.length) === j.start) { - txr.add(plugin, {pass: `MLM(${j.mlm})`}); - found_mlm++; - continue; + if (header.substring(0, j.start.length) === j.start) { + txr.add(plugin, { pass: `MLM(${j.mlm})` }) + found_mlm++ + continue } - connection.logdebug(plugin, `mlm start miss: ${name}: ${header}`); + connection.logdebug(plugin, `mlm start miss: ${name}: ${header}`) } if (j.match) { - if (header.match(new RegExp(j.match,'i'))) { - txr.add(plugin, {pass: `MLM(${j.mlm})`}); - found_mlm++; - continue; + if (header.match(new RegExp(j.match, 'i'))) { + txr.add(plugin, { pass: `MLM(${j.mlm})` }) + found_mlm++ + continue } - connection.logdebug(plugin, `mlm match miss: ${name}: ${header}`); + connection.logdebug(plugin, `mlm match miss: ${name}: ${header}`) } if (name === 'X-Mailman-Version') { - txr.add(plugin, {pass: `MLM(${j.mlm})`}); - found_mlm++; - continue; + txr.add(plugin, { pass: `MLM(${j.mlm})` }) + found_mlm++ + continue } if (name === 'X-Majordomo-Version') { - txr.add(plugin, {pass: `MLM(${j.mlm})`}); - found_mlm++; - continue; + txr.add(plugin, { pass: `MLM(${j.mlm})` }) + found_mlm++ + continue } if (name === 'X-Google-Loop') { - txr.add(plugin, {pass: `MLM(${j.mlm})`}); - found_mlm++; - continue; + txr.add(plugin, { pass: `MLM(${j.mlm})` }) + found_mlm++ + continue } } - }); - if (found_mlm) return next(); + }) + if (found_mlm) return next() - connection.transaction.results.add(plugin, {msg: 'not MLM'}); - next(); + connection.transaction.results.add(plugin, { msg: 'not MLM' }) + next() } exports.from_phish = function (next, connection) { - const plugin = this; - if (!connection.transaction) return next(); + const plugin = this + if (!connection.transaction) return next() // check the header From display name for common phish domains const hdr_from = connection.transaction.header.get_decoded('From') if (!hdr_from) { - connection.transaction.results.add(plugin, {skip: 'from_phish(missing)'}) + connection.transaction.results.add(plugin, { skip: 'from_phish(missing)' }) return next() } for (const addr of phish_targets) { - if (!addr.test(hdr_from)) continue; // not a sender match + if (!addr.test(hdr_from)) continue // not a sender match if (!exports.has_auth_match(addr, connection)) { - - connection.transaction.results.add(plugin, {fail: `from_phish(${hdr_from}`}) - if (plugin.cfg.reject.from_phish) return next(DENY, `Phishing message detected`) + connection.transaction.results.add(plugin, { + fail: `from_phish(${hdr_from}`, + }) + if (plugin.cfg.reject.from_phish) + return next(DENY, `Phishing message detected`) return next() } } - connection.transaction.results.add(plugin, {pass: 'from_phish'}) + connection.transaction.results.add(plugin, { pass: 'from_phish' }) next() } exports.has_auth_match = function (re, conn) { // check domain RegEx against spf, dkim, and env sender for a match - const spf = conn.transaction.results.get('spf'); // only check mfrom - if (spf && re.test(spf.pass)) return true; + const spf = conn.transaction.results.get('spf') // only check mfrom + if (spf && re.test(spf.pass)) return true // try DKIM via results - const dkim = conn.transaction.results.get('dkim_verify'); - if (dkim && re.test(dkim.pass)) return true; + const dkim = conn.transaction.results.get('dkim_verify') + if (dkim && re.test(dkim.pass)) return true // fallback DKIM via notes const dkim_note = conn.transaction.notes.dkim_results if (dkim_note) { - const passes = dkim_note.filter( r => r.result === 'pass' && re.test(r.domain)) + const passes = dkim_note.filter( + (r) => r.result === 'pass' && re.test(r.domain), + ) if (passes.length) return true } diff --git a/package.json b/package.json index 20242da..b9bcd01 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,11 @@ "CHANGELOG.md" ], "scripts": { + "format": "npm run prettier:fix && npm run lint:fix", + "prettier": "npx prettier . --check", + "prettier:fix": "npx prettier . --write --log-level=warn", "lint": "npx eslint@^8 *.js test", - "lintfix": "npx eslint@^8 --fix *.js test", + "lint:fix": "npx eslint@^8 --fix *.js test", "test": "npx mocha", "versions": "npx dependency-version-checker check", "versionsup": "npx dependency-version-checker update" diff --git a/test/index.js b/test/index.js index 88e1034..48928a5 100644 --- a/test/index.js +++ b/test/index.js @@ -1,10 +1,9 @@ - // node.js built-in modules -const assert = require('assert'); +const assert = require('assert') // npm modules -const Address = require('address-rfc2821').Address; -const fixtures = require('haraka-test-fixtures'); +const Address = require('address-rfc2821').Address +const fixtures = require('haraka-test-fixtures') // start of tests // assert: https://nodejs.org/api/assert.html @@ -15,14 +14,13 @@ beforeEach(function (done) { this.plugin.register() try { - this.plugin.addrparser = require('address-rfc2822'); - } - catch (ignore) {} + this.plugin.addrparser = require('address-rfc2822') + } catch (ignore) {} - this.connection = fixtures.connection.createConnection(); + this.connection = fixtures.connection.createConnection() this.connection.init_transaction() - done() // if a test hangs, assure you called done() + done() // if a test hangs, assure you called done() }) describe('haraka-plugin-headers', function () { @@ -48,21 +46,20 @@ describe('load_headers_ini', function () { }) describe('user_agent', function () { - it('none', function (done) { - this.plugin.cfg.check.user_agent=true; + this.plugin.cfg.check.user_agent = true this.plugin.user_agent(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(/UA/.test(r.fail), true); - assert.equal(/UA/.test(r.pass), false); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(/UA/.test(r.fail), true) + assert.equal(/UA/.test(r.pass), false) done() - }, this.connection); + }, this.connection) }) it('thunderbird', function (done) { - this.plugin.cfg.check.user_agent=true + this.plugin.cfg.check.user_agent = true this.connection.transaction.header.add_end('User-Agent', 'Thunderbird') - this.plugin.user_agent( () => { + this.plugin.user_agent(() => { const r = this.connection.transaction.results.get('haraka-plugin-headers') // console.log(r) assert.equal(true, /UA/.test(r.pass)) @@ -72,252 +69,295 @@ describe('user_agent', function () { }) it('X-mailer', function (done) { - this.plugin.cfg.check.user_agent=true - this.connection.transaction.header.add_end('X-Mailer', 'Apple Mail'); - this.plugin.user_agent( () => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); + this.plugin.cfg.check.user_agent = true + this.connection.transaction.header.add_end('X-Mailer', 'Apple Mail') + this.plugin.user_agent(() => { + const r = this.connection.transaction.results.get('haraka-plugin-headers') assert.equal(true, /UA/.test(r.pass)) assert.equal(false, /UA/.test(r.fail)) done() - }, this.connection); + }, this.connection) }) }) describe('direct_to_mx', function () { - it('auth user', function (done) { - this.connection.notes.auth_user = 'test@example.com'; - this.plugin.cfg.check.direct_to_mx=true; - this.plugin.direct_to_mx( () => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /^direct-to-mx/.test(r.skip)); - assert.equal(false, /^direct-to-mx/.test(r.pass)); - assert.equal(false, /^direct-to-mx/.test(r.fail)); + this.connection.notes.auth_user = 'test@example.com' + this.plugin.cfg.check.direct_to_mx = true + this.plugin.direct_to_mx(() => { + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /^direct-to-mx/.test(r.skip)) + assert.equal(false, /^direct-to-mx/.test(r.pass)) + assert.equal(false, /^direct-to-mx/.test(r.fail)) done() - }, this.connection); + }, this.connection) }) it('received 0', function (done) { - this.plugin.cfg.check.direct_to_mx=true; + this.plugin.cfg.check.direct_to_mx = true this.plugin.direct_to_mx(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /^direct-to-mx/.test(r.fail)); - assert.equal(false, /^direct-to-mx/.test(r.pass)); - assert.equal(false, /^direct-to-mx/.test(r.skip)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /^direct-to-mx/.test(r.fail)) + assert.equal(false, /^direct-to-mx/.test(r.pass)) + assert.equal(false, /^direct-to-mx/.test(r.skip)) done() - }, this.connection); + }, this.connection) }) it('received 1', function (done) { - this.plugin.cfg.check.direct_to_mx=true; - this.connection.transaction.header.add_end('Received', 'blah'); + this.plugin.cfg.check.direct_to_mx = true + this.connection.transaction.header.add_end('Received', 'blah') this.plugin.direct_to_mx(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /^direct-to-mx/.test(r.fail)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /^direct-to-mx/.test(r.fail)) done() - }, this.connection); + }, this.connection) }) it('received 2', function (done) { - this.plugin.cfg.check.direct_to_mx=true; - this.connection.transaction.header.add_end('Received', 'blah1'); - this.connection.transaction.header.add_end('Received', 'blah2'); + this.plugin.cfg.check.direct_to_mx = true + this.connection.transaction.header.add_end('Received', 'blah1') + this.connection.transaction.header.add_end('Received', 'blah2') this.plugin.direct_to_mx(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /^direct-to-mx/.test(r.pass)); - assert.equal(false, /^direct-to-mx/.test(r.fail)); - assert.equal(false, /^direct-to-mx/.test(r.skip)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /^direct-to-mx/.test(r.pass)) + assert.equal(false, /^direct-to-mx/.test(r.fail)) + assert.equal(false, /^direct-to-mx/.test(r.skip)) done() - }, this.connection); + }, this.connection) }) }) describe('from_match', function () { - it('match bare', function (done) { - this.plugin.cfg.check.from_match=true; - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', 'test@example.com'); + this.plugin.cfg.check.from_match = true + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end('From', 'test@example.com') this.plugin.from_match(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.notEqual(-1, r.pass.indexOf('from_match')); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.notEqual(-1, r.pass.indexOf('from_match')) done() }, this.connection) }) it('match typical', function (done) { - this.plugin.cfg.check.from_match=true; - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', '"Test User" '); + this.plugin.cfg.check.from_match = true + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end( + 'From', + '"Test User" ', + ) this.plugin.from_match(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.notEqual(-1, r.pass.indexOf('from_match')); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.notEqual(-1, r.pass.indexOf('from_match')) done() - }, this.connection); + }, this.connection) }) it('match unquoted', function (done) { - this.plugin.cfg.check.from_match=true; - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', 'Test User '); + this.plugin.cfg.check.from_match = true + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end( + 'From', + 'Test User ', + ) this.plugin.from_match(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.notEqual(-1, r.pass.indexOf('from_match')); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.notEqual(-1, r.pass.indexOf('from_match')) done() - }, this.connection); + }, this.connection) }) it('mismatch', function (done) { - this.plugin.cfg.check.from_match=true; - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', "test@example.net"); + this.plugin.cfg.check.from_match = true + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end('From', 'test@example.net') // console.log(this.connection.transaction.results); this.plugin.from_match(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /^from_match/.test(r.fail)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /^from_match/.test(r.fail)) done() - }, this.connection); + }, this.connection) }) }) describe('mailing_list', function () { - it('ezmlm true', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('Mailing-List', "blah blah: run by ezmlm"); + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'Mailing-List', + 'blah blah: run by ezmlm', + ) this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); + const r = this.connection.transaction.results.get('haraka-plugin-headers') assert.equal(true, /ezmlm/.test(r.pass)) assert.equal(0, r.fail.length) done() - }, this.connection); + }, this.connection) }) it('ezmlm false', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('Mailing-List', "blah blah random header tokens"); + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'Mailing-List', + 'blah blah random header tokens', + ) this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(r.pass.length, 0); - assert.equal(true, /not/.test(r.msg)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(r.pass.length, 0) + assert.equal(true, /not/.test(r.msg)) done() - }, this.connection); + }, this.connection) }) it('yahoogroups', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('Mailing-List', "blah blah such-and-such@yahoogroups.com email list"); + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'Mailing-List', + 'blah blah such-and-such@yahoogroups.com email list', + ) this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /yahoogroups/.test(r.pass)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /yahoogroups/.test(r.pass)) done() - }, this.connection); + }, this.connection) }) it('majordomo', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('Sender', "owner-blah-blah whatcha"); + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'Sender', + 'owner-blah-blah whatcha', + ) this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /majordomo/.test(r.pass)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /majordomo/.test(r.pass)) done() - }, this.connection); + }, this.connection) }) it('mailman', function (done) { - this.connection.transaction.header.add_end('X-Mailman-Version', "owner-blah-blah whatcha"); - this.plugin.cfg.check.mailing_list=true; + this.connection.transaction.header.add_end( + 'X-Mailman-Version', + 'owner-blah-blah whatcha', + ) + this.plugin.cfg.check.mailing_list = true this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /mailman/.test(r.pass)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /mailman/.test(r.pass)) done() - }, this.connection); + }, this.connection) }) it('majordomo v', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('X-Majordomo-Version', "owner-blah-blah whatcha"); + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'X-Majordomo-Version', + 'owner-blah-blah whatcha', + ) this.plugin.mailing_list(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.equal(true, /majordomo/.test(r.pass)); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.equal(true, /majordomo/.test(r.pass)) done() - }, this.connection); + }, this.connection) }) it('google groups', function (done) { - this.plugin.cfg.check.mailing_list=true; - this.connection.transaction.header.add_end('X-Google-Loop', "blah-blah whatcha") + this.plugin.cfg.check.mailing_list = true + this.connection.transaction.header.add_end( + 'X-Google-Loop', + 'blah-blah whatcha', + ) this.plugin.mailing_list(() => { const r = this.connection.transaction.results.get('haraka-plugin-headers') assert.equal(true, /googlegroups/.test(r.pass)) done() - }, this.connection); + }, this.connection) }) }) describe('delivered_to', function () { - it('disabled', function (done) { - this.plugin.cfg.check.delivered_to=false; + this.plugin.cfg.check.delivered_to = false this.plugin.delivered_to(function (res, msg) { - assert.equal(undefined, res); - assert.equal(undefined, msg); + assert.equal(undefined, res) + assert.equal(undefined, msg) done() - }, this.connection); + }, this.connection) }) it('header not present', function (done) { - this.plugin.cfg.check.delivered_to=true; + this.plugin.cfg.check.delivered_to = true this.plugin.delivered_to(function (res, msg) { - assert.equal(undefined, res); - assert.equal(undefined, msg); + assert.equal(undefined, res) + assert.equal(undefined, msg) done() - }, this.connection); + }, this.connection) }) it('no recipient match', function (done) { - this.plugin.cfg.check.delivered_to=true; + this.plugin.cfg.check.delivered_to = true // this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); + this.connection.transaction.header.add_end( + 'Delivered-To', + 'user@example.com', + ) this.plugin.delivered_to(function (res, msg) { - assert.equal(undefined, res); - assert.equal(undefined, msg); + assert.equal(undefined, res) + assert.equal(undefined, msg) done() - }, this.connection); + }, this.connection) }) it('recipient match', function (done) { - this.plugin.cfg.check.delivered_to=true; + this.plugin.cfg.check.delivered_to = true // this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); - this.connection.transaction.rcpt_to.push(new Address('user@example.com')); + this.connection.transaction.header.add_end( + 'Delivered-To', + 'user@example.com', + ) + this.connection.transaction.rcpt_to.push(new Address('user@example.com')) this.plugin.delivered_to(function (res, msg) { - assert.equal(DENY, res); - assert.equal('Invalid Delivered-To header content', msg); + assert.equal(DENY, res) + assert.equal('Invalid Delivered-To header content', msg) done() - }, this.connection); + }, this.connection) }) it('recipient match, reject disabled', function (done) { - this.plugin.cfg.check.delivered_to=true; - this.plugin.cfg.reject.delivered_to=false; + this.plugin.cfg.check.delivered_to = true + this.plugin.cfg.reject.delivered_to = false // this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); - this.connection.transaction.rcpt_to.push(new Address('user@example.com')); + this.connection.transaction.header.add_end( + 'Delivered-To', + 'user@example.com', + ) + this.connection.transaction.rcpt_to.push(new Address('user@example.com')) this.plugin.delivered_to(function (res, msg) { - assert.equal(undefined, res); - assert.equal(undefined, msg); + assert.equal(undefined, res) + assert.equal(undefined, msg) done() - }, this.connection); + }, this.connection) }) }) describe('has_auth_match', function () { it('detects an absense of auth data', function (done) { - assert.equal(this.plugin.has_auth_match(/test\.com/, this.connection), false) + assert.equal( + this.plugin.has_auth_match(/test\.com/, this.connection), + false, + ) done() }) it('detects a passed SPF auth', function (done) { - this.connection.transaction.results.add({name: 'spf'}, { pass: 'test.com' }) + this.connection.transaction.results.add( + { name: 'spf' }, + { pass: 'test.com' }, + ) assert.equal(this.plugin.has_auth_match(/test\.com/, this.connection), true) done() }) it('detects a passed DKIM auth (notes)', function (done) { - this.connection.transaction.notes.dkim_results = [{ result: 'pass', domain: 'test.com' }] + this.connection.transaction.notes.dkim_results = [ + { result: 'pass', domain: 'test.com' }, + ] assert.equal(this.plugin.has_auth_match(/test\.com/, this.connection), true) done() }) it('detects a passed DKIM auth (results)', function (done) { - this.connection.transaction.results.add({ name: 'dkim_verify'}, { pass: 'test.com' }) + this.connection.transaction.results.add( + { name: 'dkim_verify' }, + { pass: 'test.com' }, + ) assert.equal(this.plugin.has_auth_match(/test\.com/, this.connection), true) done() }) @@ -330,40 +370,52 @@ describe('has_auth_match', function () { }) describe('from_phish', function () { - it('passes mfrom match', function (done) { - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', '"Test User" '); + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end( + 'From', + '"Test User" ', + ) this.plugin.from_phish(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); - assert.notEqual(r.pass.indexOf('from_phish'), -1); + const r = this.connection.transaction.results.get('haraka-plugin-headers') + assert.notEqual(r.pass.indexOf('from_phish'), -1) done() - }, this.connection); + }, this.connection) }) it('fails when amazon.com is in the From header and not envelope sender', function (done) { - this.connection.transaction.mail_from = new Address(''); - this.connection.transaction.header.add_end('From', 'Amazon.com '); + this.connection.transaction.mail_from = new Address('') + this.connection.transaction.header.add_end( + 'From', + 'Amazon.com ', + ) this.plugin.from_phish(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); + const r = this.connection.transaction.results.get('haraka-plugin-headers') // console.log(r) - assert.equal(r.fail.length, 1); + assert.equal(r.fail.length, 1) done() - }, this.connection); + }, this.connection) }) it('passes dkim_verify match', function (done) { // this.plugin.cfg.check.from_phish=true; - this.connection.transaction.mail_from = new Address('<01010173e2d51ce9-fda858da-b513-412f-b03b-6db12012417e-000000@us-west-2.amazonses.com>'); - this.connection.transaction.header.add_end('From', 'Amazon Business '); - this.connection.transaction.results.add({name: 'dkim_verify'}, { pass: ['business.amazon.com','amazonses.com'] }) + this.connection.transaction.mail_from = new Address( + '<01010173e2d51ce9-fda858da-b513-412f-b03b-6db12012417e-000000@us-west-2.amazonses.com>', + ) + this.connection.transaction.header.add_end( + 'From', + 'Amazon Business ', + ) + this.connection.transaction.results.add( + { name: 'dkim_verify' }, + { pass: ['business.amazon.com', 'amazonses.com'] }, + ) this.plugin.from_phish(() => { - const r = this.connection.transaction.results.get('haraka-plugin-headers'); + const r = this.connection.transaction.results.get('haraka-plugin-headers') // console.log(r) assert.deepEqual(r.fail, []) - assert.deepEqual(r.pass, ['from_phish']); + assert.deepEqual(r.pass, ['from_phish']) done() - }, this.connection); + }, this.connection) }) - })