From 963a137224ceb09a1d580013df9a3be2db6dfcf7 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:04:50 -0700 Subject: [PATCH 01/10] chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file --- Changes.md | 3 + README.md | 4 +- index.js | 381 ++++++++++++++++++++++--------------------------- test/access.js | 39 +---- 4 files changed, 179 insertions(+), 248 deletions(-) diff --git a/Changes.md b/Changes.md index f9393c3..5113918 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,8 @@ ### Unreleased +- +- chore: remove `const plugin = this` pattern (deprecated) +- chore: remove unused in_file and in_re_file ### [1.1.5] - 2022-06-06 diff --git a/README.md b/README.md index aae23af..131f2e5 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ When upgrading from the rdns\_access, mail\_from.access, and rcpt\_to.access plugins, be sure to remove the plugins from config/plugins, upon pain of wasted CPU cycles. -There is no need to modify your black/white lists in any way. +There is no need to modify your black/white lists. If you just want the new plugin to work exactly like the old trio it replaces, add this section to _config/access.ini_: @@ -169,7 +169,7 @@ By default this plugin only rejects recipients on the blacklists, and ignores th ### Organizational Domain -The OD is a term that describes the highest level portion of domain name that is under the control of a private organization. I'll explain, but first, lets clarify a few terms: +The OD is a term that describes the highest level portion of domain name that is under the control of a private organization. Let's clarify a few terms: #### TLD diff --git a/index.js b/index.js index fc3d1e4..3913f37 100644 --- a/index.js +++ b/index.js @@ -1,49 +1,48 @@ // access plugin + const tlds = require('haraka-tld'); const haddr = require('address-rfc2822'); const net_utils = require('haraka-net-utils'); const utils = require('haraka-utils'); exports.register = function () { - const plugin = this; - plugin.init_config(); // init plugin.cfg - plugin.init_lists(); - plugin.load_access_ini(); // update with *.ini settings + this.init_config(); // init this.cfg + this.init_lists(); + this.load_access_ini(); // update with *.ini settings let p; - for (p in plugin.cfg.white) { plugin.load_file('white', p); } - for (p in plugin.cfg.black) { plugin.load_file('black', p); } - for (p in plugin.cfg.re.white) { plugin.load_re_file('white', p); } - for (p in plugin.cfg.re.black) { plugin.load_re_file('black', p); } + for (p in this.cfg.white) { this.load_file('white', p); } + for (p in this.cfg.black) { this.load_file('black', p); } + for (p in this.cfg.re.white) { this.load_re_file('white', p); } + for (p in this.cfg.re.black) { this.load_re_file('black', p); } - if (plugin.cfg.check.conn) { - plugin.register_hook('connect_init', 'rdns_access'); + if (this.cfg.check.conn) { + this.register_hook('connect', 'rdns_access'); } - if (plugin.cfg.check.helo) { - plugin.register_hook('helo', 'helo_access'); - plugin.register_hook('ehlo', 'helo_access'); + if (this.cfg.check.helo) { + this.register_hook('helo', 'helo_access'); + this.register_hook('ehlo', 'helo_access'); } - if (plugin.cfg.check.mail) { - plugin.register_hook('mail', 'mail_from_access'); + if (this.cfg.check.mail) { + this.register_hook('mail', 'mail_from_access'); } - if (plugin.cfg.check.rcpt) { - plugin.register_hook('rcpt', 'rcpt_to_access'); + if (this.cfg.check.rcpt) { + this.register_hook('rcpt', 'rcpt_to_access'); } - if (plugin.cfg.check.any) { - plugin.load_domain_file('domain', 'any'); - ['connect','helo','ehlo','mail','rcpt'].forEach(function (hook) { - plugin.register_hook(hook, 'any'); - }); - plugin.register_hook('data_post', 'data_any'); + if (this.cfg.check.any) { + this.load_domain_file('domain', 'any'); + for (const hook in ['connect','helo','ehlo','mail','rcpt']) { + this.register_hook(hook, 'any'); + } + this.register_hook('data_post', 'data_any'); } } exports.init_config = function () { - const plugin = this; - plugin.cfg = { + this.cfg = { deny_msg: { conn: 'You are not allowed to connect', helo: 'That HELO is not allowed to connect', @@ -80,8 +79,7 @@ exports.init_config = function () { } exports.load_access_ini = function () { - const plugin = this; - const cfg = plugin.config.get('access.ini', { + const cfg = this.config.get('access.ini', { booleans: [ '+check.any', '+check.conn', @@ -90,45 +88,44 @@ exports.load_access_ini = function () { '+check.rcpt', '-rcpt.accept', ], - }, function () { - plugin.load_access_ini(); - }); + }, () => { + this.load_access_ini(); + }) - plugin.cfg.check = cfg.check; + this.cfg.check = cfg.check; if (cfg.deny_msg) { let p; - for (p in plugin.cfg.deny_msg) { + for (p in this.cfg.deny_msg) { if (cfg.deny_msg[p]) { - plugin.cfg.deny_msg[p] = cfg.deny_msg[p]; + this.cfg.deny_msg[p] = cfg.deny_msg[p]; } } } - plugin.cfg.rcpt = cfg.rcpt; + this.cfg.rcpt = cfg.rcpt; // backwards compatibility - const mf_cfg = plugin.config.get('mail_from.access.ini'); + const mf_cfg = this.config.get('mail_from.access.ini'); if (mf_cfg && mf_cfg.general && mf_cfg.general.deny_msg) { - plugin.cfg.deny_msg.mail = mf_cfg.general.deny_msg; + this.cfg.deny_msg.mail = mf_cfg.general.deny_msg; } - const rcpt_cfg = plugin.config.get('rcpt_to.access.ini'); + const rcpt_cfg = this.config.get('rcpt_to.access.ini'); if (rcpt_cfg && rcpt_cfg.general && rcpt_cfg.general.deny_msg) { - plugin.cfg.deny_msg.rcpt = rcpt_cfg.general.deny_msg; + this.cfg.deny_msg.rcpt = rcpt_cfg.general.deny_msg; } - const rdns_cfg = plugin.config.get('connect.rdns_access.ini'); + const rdns_cfg = this.config.get('connect.rdns_access.ini'); if (rdns_cfg && rdns_cfg.general && rdns_cfg.general.deny_msg) { - plugin.cfg.deny_msg.conn = rdns_cfg.general.deny_msg; + this.cfg.deny_msg.conn = rdns_cfg.general.deny_msg; } } exports.init_lists = function () { - const plugin = this; - plugin.list = { + this.list = { black: { conn: {}, helo: {}, mail: {}, rcpt: {} }, white: { conn: {}, helo: {}, mail: {}, rcpt: {} }, domain: { any: {} }, }; - plugin.list_re = { + this.list_re = { black: {}, white: {}, }; @@ -154,81 +151,77 @@ exports.get_domain = function (hook, connection, params) { } exports.any_whitelist = function (connection, hook, params, domain, org_domain) { - const plugin = this; if (hook === 'mail' || hook === 'rcpt') { const email = params[0].address(); - if (email && plugin.in_list('domain', 'any', `!${email}`)) return true; + if (email && this.in_list('domain', 'any', `!${email}`)) return true; } - if (plugin.in_list('domain', 'any', `!${org_domain}`)) return true; - if (plugin.in_list('domain', 'any', `!${domain}`)) return true; + if (this.in_list('domain', 'any', `!${org_domain}`)) return true; + if (this.in_list('domain', 'any', `!${domain}`)) return true; return false; } exports.any = function (next, connection, params) { - const plugin = this; - if (!plugin.cfg.check.any) return next(); + if (!this.cfg.check.any) return next(); const hook = connection.hook; if (!hook) { - connection.logerror(plugin, "hook detection failed"); + connection.logerror(this, "hook detection failed"); return next(); } // step 1: get a domain name from whatever info is available - const domain = plugin.get_domain(hook, connection, params); + const domain = this.get_domain(hook, connection, params); if (!domain) { - connection.logdebug(plugin, `domain detect failed on hook: ${hook}`); + connection.logdebug(this, `domain detect failed on hook: ${hook}`); return next(); } if (!/\./.test(domain)) { - connection.results.add(plugin, {fail: `invalid domain: ${domain}`, emit: true}); + connection.results.add(this, {fail: `invalid domain: ${domain}`, emit: true}); return next(); } const org_domain = tlds.get_organizational_domain(domain); if (!org_domain) { - connection.loginfo(plugin, `no org domain from ${domain}`); + connection.loginfo(this, `no org domain from ${domain}`); return next(); } - const file = plugin.cfg.domain.any; + const file = this.cfg.domain.any; // step 2: check for whitelist - if (plugin.any_whitelist(connection, hook, params, domain, org_domain)) { + if (this.any_whitelist(connection, hook, params, domain, org_domain)) { const whiteResults = {pass: `${hook}:${file}`, whitelist: true, emit: true} - connection.results.add(plugin, whiteResults); + connection.results.add(this, whiteResults); return next(); } // step 3: check for blacklist - if (plugin.in_list('domain', 'any', org_domain)) { - connection.results.add(plugin, {fail: `${file}(${org_domain})`, blacklist: true, emit: true}); + if (this.in_list('domain', 'any', org_domain)) { + connection.results.add(this, {fail: `${file}(${org_domain})`, blacklist: true, emit: true}); return next(DENY, "You are not welcome here."); } const umsg = hook ? `${hook}:any` : 'any'; - connection.results.add(plugin, {msg: `unlisted(${umsg})` }); - return next(); + connection.results.add(this, {msg: `unlisted(${umsg})` }); + next(); } exports.rdns_store_results = function (connection, color, file) { - const plugin = this; switch (color) { case 'white': - connection.results.add(plugin, { whitelist: true, pass: file, emit: true }) + connection.results.add(this, { whitelist: true, pass: file, emit: true }) break; case 'black': - connection.results.add(plugin, { fail: file, emit: true }) + connection.results.add(this, { fail: file, emit: true }) break; } } exports.rdns_is_listed = function (connection, color) { - const plugin = this; const addrs = [ connection.remote.ip, connection.remote.host ]; @@ -236,18 +229,18 @@ exports.rdns_is_listed = function (connection, color) { if (!addr) continue; // empty rDNS host if (/[\w]/.test(addr)) addr = addr.toLowerCase(); - let file = plugin.cfg[color].conn; - connection.logdebug(plugin, `checking ${addr} against ${file}`); + let file = this.cfg[color].conn; + connection.logdebug(this, `checking ${addr} against ${file}`); - if (plugin.in_list(color, 'conn', addr)) { - plugin.rdns_store_results(connection, color, file) + if (this.in_list(color, 'conn', addr)) { + this.rdns_store_results(connection, color, file) return true; } - file = plugin.cfg.re[color].conn; - connection.logdebug(plugin, `checking ${addr} against ${file}`); - if (plugin.in_re_list(color, 'conn', addr)) { - plugin.rdns_store_results(connection, color, file) + file = this.cfg.re[color].conn; + connection.logdebug(this, `checking ${addr} against ${file}`); + if (this.in_re_list(color, 'conn', addr)) { + this.rdns_store_results(connection, color, file) return true; } } @@ -256,82 +249,79 @@ exports.rdns_is_listed = function (connection, color) { } exports.rdns_access = function (next, connection) { - const plugin = this; - if (!plugin.cfg.check.conn) return next(); + if (!this.cfg.check.conn) return next(); - if (plugin.rdns_is_listed(connection, 'white')) return next(); + if (this.rdns_is_listed(connection, 'white')) return next(); - const deny_msg = `${connection.remote.host} [${connection.remote.ip}] ${plugin.cfg.deny_msg.conn}` - if (plugin.rdns_is_listed(connection, 'black')) return next(DENYDISCONNECT, deny_msg); + const deny_msg = `${connection.remote.host} [${connection.remote.ip}] ${this.cfg.deny_msg.conn}` + if (this.rdns_is_listed(connection, 'black')) return next(DENYDISCONNECT, deny_msg); - connection.results.add(plugin, { msg: 'unlisted(conn)' }); + connection.results.add(this, { msg: 'unlisted(conn)' }); next(); } exports.helo_access = function (next, connection, helo) { - const plugin = this; - if (!plugin.cfg.check.helo) { return next(); } + if (!this.cfg.check.helo) return next(); - const file = plugin.cfg.re.black.helo; - if (plugin.in_re_list('black', 'helo', helo)) { - connection.results.add(plugin, {fail: file, emit: true}); - return next(DENY, `${helo} ${plugin.cfg.deny_msg.helo}`); + const file = this.cfg.re.black.helo; + if (this.in_re_list('black', 'helo', helo)) { + connection.results.add(this, {fail: file, emit: true}); + return next(DENY, `${helo} ${this.cfg.deny_msg.helo}`); } - connection.results.add(plugin, {msg: 'unlisted(helo)' }); - return next(); + connection.results.add(this, {msg: 'unlisted(helo)' }); + next(); } exports.mail_from_access = function (next, connection, params) { - const plugin = this; - if (!plugin.cfg.check.mail) { return next(); } + + if (!this.cfg.check.mail) return next(); const mail_from = params[0].address(); if (!mail_from) { - connection.transaction.results.add(plugin, { + connection.transaction.results.add(this, { skip: 'null sender', emit: true}); return next(); } // address whitelist checks - let file = plugin.cfg.white.mail; - connection.logdebug(plugin, `checking ${mail_from} against ${file}`); - if (plugin.in_list('white', 'mail', mail_from)) { - connection.transaction.results.add(plugin, {pass: file, emit: true}); + let file = this.cfg.white.mail; + connection.logdebug(this, `checking ${mail_from} against ${file}`); + if (this.in_list('white', 'mail', mail_from)) { + connection.transaction.results.add(this, {pass: file, emit: true}); return next(); } - file = plugin.cfg.re.white.mail; - connection.logdebug(plugin, `checking ${mail_from} against ${file}`); - if (plugin.in_re_list('white', 'mail', mail_from)) { - connection.transaction.results.add(plugin, {pass: file, emit: true}); + file = this.cfg.re.white.mail; + connection.logdebug(this, `checking ${mail_from} against ${file}`); + if (this.in_re_list('white', 'mail', mail_from)) { + connection.transaction.results.add(this, {pass: file, emit: true}); return next(); } // address blacklist checks - file = plugin.cfg.black.mail; - if (plugin.in_list('black', 'mail', mail_from)) { - connection.transaction.results.add(plugin, {fail: file, emit: true}); - return next(DENY, `${mail_from} ${plugin.cfg.deny_msg.mail}`); + file = this.cfg.black.mail; + if (this.in_list('black', 'mail', mail_from)) { + connection.transaction.results.add(this, {fail: file, emit: true}); + return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`); } - file = plugin.cfg.re.black.mail; - connection.logdebug(plugin, `checking ${mail_from} against ${file}`); - if (plugin.in_re_list('black', 'mail', mail_from)) { - connection.transaction.results.add(plugin, {fail: file, emit: true}); - return next(DENY, `${mail_from} ${plugin.cfg.deny_msg.mail}`); + file = this.cfg.re.black.mail; + connection.logdebug(this, `checking ${mail_from} against ${file}`); + if (this.in_re_list('black', 'mail', mail_from)) { + connection.transaction.results.add(this, {fail: file, emit: true}); + return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`); } - connection.transaction.results.add(plugin, {msg: 'unlisted(mail)' }); - return next(); + connection.transaction.results.add(this, {msg: 'unlisted(mail)' }); + next(); } exports.rcpt_to_access = function (next, connection, params) { - const plugin = this; - if (!plugin.cfg.check.rcpt) { return next(); } + if (!this.cfg.check.rcpt) return next(); let pass_status = undefined; - if (plugin.cfg.rcpt.accept) { + if (this.cfg.rcpt.accept) { pass_status = OK; } @@ -339,50 +329,49 @@ exports.rcpt_to_access = function (next, connection, params) { // address whitelist checks if (!rcpt_to) { - connection.transaction.results.add(plugin, { + connection.transaction.results.add(this, { skip: 'null rcpt', emit: true}); return next(); } - let file = plugin.cfg.white.rcpt; - if (plugin.in_list('white', 'rcpt', rcpt_to)) { - connection.transaction.results.add(plugin, {pass: file, emit: true}); + let file = this.cfg.white.rcpt; + if (this.in_list('white', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, {pass: file, emit: true}); return next(pass_status); } - file = plugin.cfg.re.white.rcpt; - if (plugin.in_re_list('white', 'rcpt', rcpt_to)) { - connection.transaction.results.add(plugin, {pass: file, emit: true}); + file = this.cfg.re.white.rcpt; + if (this.in_re_list('white', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, {pass: file, emit: true}); return next(pass_status); } // address blacklist checks - file = plugin.cfg.black.rcpt; - if (plugin.in_list('black', 'rcpt', rcpt_to)) { - connection.transaction.results.add(plugin, {fail: file, emit: true}); - return next(DENY, `${rcpt_to} ${plugin.cfg.deny_msg.rcpt}`); + file = this.cfg.black.rcpt; + if (this.in_list('black', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, {fail: file, emit: true}); + return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`); } - file = plugin.cfg.re.black.rcpt; - if (plugin.in_re_list('black', 'rcpt', rcpt_to)) { - connection.transaction.results.add(plugin, {fail: file, emit: true}); - return next(DENY, `${rcpt_to} ${plugin.cfg.deny_msg.rcpt}`); + file = this.cfg.re.black.rcpt; + if (this.in_re_list('black', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, {fail: file, emit: true}); + return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`); } - connection.transaction.results.add(plugin, {msg: 'unlisted(rcpt)' }); - return next(); + connection.transaction.results.add(this, {msg: 'unlisted(rcpt)' }); + next(); } exports.data_any = function (next, connection) { - const plugin = this; - if (!plugin.cfg.check.data && !plugin.cfg.check.any) { - connection.transaction.results.add(plugin, {skip: 'data(disabled)'}); + if (!this.cfg.check.data && !this.cfg.check.any) { + connection.transaction.results.add(this, {skip: 'data(disabled)'}); return next(); } const hdr_from = connection.transaction.header.get_decoded('From'); if (!hdr_from) { - connection.transaction.results.add(plugin, {fail: 'data(missing_from)'}); + connection.transaction.results.add(this, {fail: 'data(missing_from)'}); return next(); } @@ -391,162 +380,138 @@ exports.data_any = function (next, connection) { hdr_addr = haddr.parse(hdr_from)[0]; } catch (e) { - connection.transaction.results.add(plugin, {fail: `data(unparsable_from:${hdr_from})`}); + connection.transaction.results.add(this, {fail: `data(unparsable_from:${hdr_from})`}); return next(); } if (!hdr_addr) { - connection.transaction.results.add(plugin, {fail: `data(unparsable_from:${hdr_from})`}); + connection.transaction.results.add(this, {fail: `data(unparsable_from:${hdr_from})`}); return next(); } const hdr_dom = tlds.get_organizational_domain(hdr_addr.host()); if (!hdr_dom) { - connection.transaction.results.add(plugin, {fail: `data(no_od_from:${hdr_addr})`}); + connection.transaction.results.add(this, {fail: `data(no_od_from:${hdr_addr})`}); return next(); } - const file = plugin.cfg.domain.any; - if (plugin.in_list('domain', 'any', `!${hdr_dom}`)) { - connection.results.add(plugin, {pass: file, whitelist: true, emit: true}); + const file = this.cfg.domain.any; + if (this.in_list('domain', 'any', `!${hdr_dom}`)) { + connection.results.add(this, {pass: file, whitelist: true, emit: true}); return next(); } - if (plugin.in_list('domain', 'any', hdr_dom)) { - connection.results.add(plugin, {fail: `${file}(${hdr_dom})`, blacklist: true, emit: true}); + if (this.in_list('domain', 'any', hdr_dom)) { + connection.results.add(this, {fail: `${file}(${hdr_dom})`, blacklist: true, emit: true}); return next(DENY, "Email from that domain is not accepted here."); } - connection.results.add(plugin, {msg: 'unlisted(any)' }); - return next(); + connection.results.add(this, {msg: 'unlisted(any)' }); + next(); } exports.in_list = function (type, phase, address) { - const plugin = this; - if (plugin.list[type][phase] === undefined) { + if (this.list[type][phase] === undefined) { console.log(`phase not defined: ${phase}`); return false; } if (!address) return false; - if (plugin.list[type][phase][address.toLowerCase()]) return true; + if (this.list[type][phase][address.toLowerCase()]) return true; return false; } exports.in_re_list = function (type, phase, address) { - const plugin = this; - if (!plugin.list_re[type][phase]) { return false; } - if (!plugin.cfg.re[type][phase].source) { - plugin.logdebug(plugin, `empty file: ${plugin.cfg.re[type][phase]}`); + if (!this.list_re[type][phase]) { return false; } + if (!this.cfg.re[type][phase].source) { + this.logdebug(this, `empty file: ${this.cfg.re[type][phase]}`); } else { - plugin.logdebug(plugin, `checking ${address} against ` + - `${plugin.cfg.re[type][phase].source}`); - } - return plugin.list_re[type][phase].test(address); -} - -exports.in_file = function (file_name, address, connection) { - const plugin = this; - // using indexOf on an array here is about 20x slower than testing against a key in an object - connection.logdebug(plugin, `checking ${address} against ${file_name}`); - return (plugin.config.get(file_name, 'list').indexOf(address) === -1) ? false : true; -} - -exports.in_re_file = function (file_name, address) { - // Since the helo.checks plugin uses this method, I tested to see how - // badly if affected performance. It took 8.5x longer to run than - // in_re_list. - this.logdebug(this, `checking ${address} against ${file_name}`); - const re_list = utils.valid_regexes( - this.config.get(file_name, 'list'), file_name); - for (let i=0; i < re_list.length; i++) { - if (new RegExp('^' + re_list[i] + '$', 'i').test(address)) return true; + this.logdebug(this, `checking ${address} against ` + + `${this.cfg.re[type][phase].source}`); } - return false; + return this.list_re[type][phase].test(address); } exports.load_file = function (type, phase) { - const plugin = this; - if (!plugin.cfg.check[phase]) { - plugin.logdebug(plugin, `skipping ${plugin.cfg[type][phase]}`); + if (!this.cfg.check[phase]) { + this.logdebug(this, `skipping ${this.cfg[type][phase]}`); return; } - const file_name = plugin.cfg[type][phase]; + const file_name = this.cfg[type][phase]; // load config with a self-referential callback - const list = plugin.config.get(file_name, 'list', () => { - plugin.load_file(type, phase); + const list = this.config.get(file_name, 'list', () => { + this.load_file(type, phase); }); // init the list store, type is white or black - if (!plugin.list) { plugin.list = { type: {} }; } - if (!plugin.list[type]) { plugin.list[type] = {}; } + if (!this.list) this.list = { type: {} }; + if (!this.list[type]) this.list[type] = {}; // toLower when loading spends a fraction of a second at load time // to save millions of seconds during run time. const listAsHash = {}; // store as hash for speedy lookups - for (let i=0; i { plugin.load_re_file(type, phase); }) ); // initialize the list store - if (!plugin.list_re) plugin.list_re = { type: {} }; - if (!plugin.list_re[type]) plugin.list_re[type] = {}; + if (!this.list_re) this.list_re = { type: {} }; + if (!this.list_re[type]) this.list_re[type] = {}; // compile the regexes at the designated location - plugin.list_re[type][phase] = - new RegExp(`^(${regex_list.join('|')})$`, 'i'); + this.list_re[type][phase] = new RegExp(`^(${regex_list.join('|')})$`, 'i'); } exports.load_domain_file = function (type, phase) { - const plugin = this; - if (!plugin.cfg.check[phase]) { - plugin.logdebug(plugin, `skipping ${plugin.cfg[type][phase]}`); + if (!this.cfg.check[phase]) { + this.logdebug(this, `skipping ${this.cfg[type][phase]}`); return; } - const file_name = plugin.cfg[type][phase]; - const list = plugin.config.get(file_name, 'list', function () { - plugin.load_domain_file(type, phase); + const file_name = this.cfg[type][phase]; + const list = this.config.get(file_name, 'list', () => { + this.load_domain_file(type, phase); }); // init the list store, if needed - if (!plugin.list) plugin.list = { type: {} }; - if (!plugin.list[type]) plugin.list[type] = {}; + if (!this.list) this.list = { type: {} }; + if (!this.list[type]) this.list[type] = {}; + + // lowercase list items at load (much faster than at run time) + for (const entry of list) { - // convert list items to LC at load (much faster than at run time) - for (let i=0; i < list.length; i++) { - if (list[i][0] === '!') { // whitelist entry - plugin.list[type][phase][list[i].toLowerCase()] = true; + if (entry[0] === '!') { // whitelist entry + this.list[type][phase][entry.toLowerCase()] = true; continue; } - if (/@/.test(list[i][0])) { // email address - plugin.list[type][phase][list[i].toLowerCase()] = true; + if (/@/.test(entry[0])) { // email address + this.list[type][phase][entry.toLowerCase()] = true; continue; } - const d = tlds.get_organizational_domain(list[i]); + const d = tlds.get_organizational_domain(entry); if (!d) continue; - plugin.list[type][phase][d.toLowerCase()] = true; + this.list[type][phase][d.toLowerCase()] = true; } } diff --git a/test/access.js b/test/access.js index 03ce482..f34a609 100644 --- a/test/access.js +++ b/test/access.js @@ -157,7 +157,7 @@ describe('load_file', function () { }) it('case normalizing', function (done) { - console.log(this.plugin.config.root_path); + // console.log(this.plugin.config.root_path); this.plugin.load_file('white', 'rcpt'); assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); // was ADMIN2@EXAMPLE.com @@ -185,43 +185,6 @@ describe('load_re_file', function () { }) }) -describe('in_file', function () { - beforeEach(function (done) { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - this.connection.init_transaction(); - done() - }) - - it('in_file', function (done) { - const file = 'mail_from.access.whitelist'; - assert.equal(true, this.plugin.in_file(file, 'haraka@harakamail.com', this.connection)); - assert.equal(false, this.plugin.in_file(file, 'matt@harakamail.com', this.connection)); - done(); - }) -}) - -describe('in_re_file', function () { - beforeEach(function (done) { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - this.connection.init_transaction(); - done() - }) - - it('in_re_file', function (done) { - const file = 'mail_from.access.whitelist_regex'; - // console.log(this.plugin.cfg); - assert.equal(true, this.plugin.in_re_file(file, 'list@harakamail.com')); - assert.equal(false, this.plugin.in_re_file(file, 'matt@harkatamale.com')); - done(); - }) -}) - describe('rdns_access', function () { beforeEach(function (done) { this.plugin = new fixtures.plugin('access'); From 76dd21f813d060cbac0c21891862ac382ae9cd3c Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:09:33 -0700 Subject: [PATCH 02/10] update changes --- Changes.md | 10 ++++------ package.json | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Changes.md b/Changes.md index 5113918..13f9f6b 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,8 @@ ### Unreleased + +### [1.1.6] - 2024-04-09 + - - chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file @@ -10,34 +13,29 @@ - ci: add submodule .release - ci: expand codeclimate config - ### 1.1.4 - 2020-04-09 - wrap from parsing in a try #20 - ### 1.1.3 - 2018-11-16 - check if OD was found before attemping to use it - ### 1.1.2 - 2018-11-10 - use header.get_decoded('from'), was get('from') - ### 1.1.1 - 2018-06-09 - #9: make all mail address comparisons case insensitive, instead of the previously mixed behavior - ### 1.1.0 - 2018-04-23 - #6: add rcpt.accept setting to enable recipient validation for users in whitelists (like an rcpt_to.* plugin) - ### 1.0.0 - 2017-06-29 - initial release [1.1.5]: https://github.com/haraka/haraka-plugin-access/releases/tag/1.1.5 +[1.1.6]: https://github.com/haraka/haraka-plugin-access/releases/tag/1.1.6 diff --git a/package.json b/package.json index 654560b..76ba238 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haraka-plugin-access", - "version": "1.1.5", + "version": "1.1.6", "description": "Haraka plugin for ACLs for email connections", "main": "index.js", "scripts": { From 9e58fbb43355ff95d7dcbe7b1e3a14607f0a8883 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:33:37 -0700 Subject: [PATCH 03/10] populate [files] in package.json --- Changes.md | 2 +- package.json | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Changes.md b/Changes.md index 13f9f6b..77a4167 100644 --- a/Changes.md +++ b/Changes.md @@ -3,7 +3,7 @@ ### [1.1.6] - 2024-04-09 -- +- chore: populate [files] in package.json. - chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file diff --git a/package.json b/package.json index 76ba238..a803ad5 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "version": "1.1.6", "description": "Haraka plugin for ACLs for email connections", "main": "index.js", + "files": ["CHANGELOG", "config"], "scripts": { - "lint": "npx eslint *.js test", - "lintfix": "npx eslint --fix *.js test", - "test": "npx mocha" + "lint": "npx eslint@^8 *.js test", + "lintfix": "npx eslint@^8 --fix *.js test", + "test": "npx mocha@10" }, "repository": { "type": "git", @@ -24,15 +25,13 @@ }, "homepage": "https://github.com/haraka/haraka-plugin-access#readme", "devDependencies": { - "eslint": ">=8", - "eslint-plugin-haraka": "*", - "haraka-test-fixtures": "*", - "mocha": ">=9" + "@haraka/eslint-config": "^1.0.0", + "haraka-test-fixtures": "1.0.0" }, "dependencies": { - "address-rfc2822": "*", - "haraka-net-utils": "*", - "haraka-tld": "*", - "haraka-utils": "*" + "address-rfc2822": "^1.0.0", + "haraka-net-utils": "^1.0.0", + "haraka-tld": "^1.0.0", + "haraka-utils": "^1.0.0" } } From 06f68f01fb5ba83ecfea172b546caf0a102d4153 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:36:02 -0700 Subject: [PATCH 04/10] lint: remove duplicate / stale rules from .eslintrc --- .eslintrc.yaml | 19 ++----------------- .github/workflows/ci.yml | 37 +++++++------------------------------ Changes.md | 1 + 3 files changed, 10 insertions(+), 47 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 6909947..ddf5f86 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -2,21 +2,6 @@ env: node: true es6: true mocha: true + es2022: true -plugins: - - haraka - -extends: [ "eslint:recommended", "plugin:haraka/recommended" ] - -root: true - -rules: - indent: [2, 4, {"SwitchCase": 1}] - -globals: - OK: true - CONT: true - DENY: true - DENYSOFT: true - DENYDISCONNECT: true - DENYSOFTDISCONNECT: true +extends: ['@haraka'] \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d768329..b283bcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,41 +1,18 @@ name: CI -on: [ push ] +on: [push, pull_request] env: CI: true jobs: - lint: uses: haraka/.github/.github/workflows/lint.yml@master - # coverage: - # uses: haraka/.github/.github/workflows/coverage.yml@master - # secrets: inherit - - test: - needs: [ lint, get-lts ] - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ ubuntu-latest, windows-latest ] - node-version: ${{ fromJson(needs.get-lts.outputs.active) }} - fail-fast: false - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - name: Node ${{ matrix.node-version }} on ${{ matrix.os }} - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm test + ubuntu: + needs: [lint] + uses: haraka/.github/.github/workflows/ubuntu.yml@master - get-lts: - runs-on: ubuntu-latest - steps: - - id: get - uses: msimerson/node-lts-versions@v1 - outputs: - active: ${{ steps.get.outputs.active }} - lts: ${{ steps.get.outputs.lts }} + windows: + needs: [lint] + uses: haraka/.github/.github/workflows/windows.yml@master diff --git a/Changes.md b/Changes.md index 77a4167..e37d0a3 100644 --- a/Changes.md +++ b/Changes.md @@ -3,6 +3,7 @@ ### [1.1.6] - 2024-04-09 +- dep: eslint-plugin-haraka -> @haraka/eslint-config - chore: populate [files] in package.json. - chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file From 91f9bb4dd09438b3cb3a96859fb8a6f3a3fc48bc Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:44:47 -0700 Subject: [PATCH 05/10] deps: pin versions to latest --- Changes.md | 1 + package.json | 26 +++++++++++++++++--------- test/access.js | 5 ++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Changes.md b/Changes.md index e37d0a3..5d84cf8 100644 --- a/Changes.md +++ b/Changes.md @@ -4,6 +4,7 @@ ### [1.1.6] - 2024-04-09 - dep: eslint-plugin-haraka -> @haraka/eslint-config +- lint: remove duplicate / stale rules from .eslintrc - chore: populate [files] in package.json. - chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file diff --git a/package.json b/package.json index a803ad5..24cc56a 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,19 @@ "version": "1.1.6", "description": "Haraka plugin for ACLs for email connections", "main": "index.js", - "files": ["CHANGELOG", "config"], + "files": [ + "CHANGELOG", + "config" + ], "scripts": { + "format": "npm run prettier:fix && npm run lint:fix", "lint": "npx eslint@^8 *.js test", - "lintfix": "npx eslint@^8 --fix *.js test", - "test": "npx mocha@10" + "lint:fix": "npx eslint@^8 *.js test --fix", + "prettier": "npx prettier . --check", + "prettier:fix": "npx prettier . --write --log-level=warn", + "test": "npx mocha@^10", + "versions": "npx @msimerson/dependency-version-checker check", + "versions:fix": "npx @msimerson/dependency-version-checker update && npx prettier package.json --write --log-level=warn" }, "repository": { "type": "git", @@ -25,13 +33,13 @@ }, "homepage": "https://github.com/haraka/haraka-plugin-access#readme", "devDependencies": { - "@haraka/eslint-config": "^1.0.0", - "haraka-test-fixtures": "1.0.0" + "@haraka/eslint-config": "^1.1.3", + "haraka-test-fixtures": "1.3.5" }, "dependencies": { - "address-rfc2822": "^1.0.0", - "haraka-net-utils": "^1.0.0", - "haraka-tld": "^1.0.0", - "haraka-utils": "^1.0.0" + "address-rfc2822": "^2.2.1", + "haraka-net-utils": "^1.5.4", + "haraka-tld": "^1.2.1", + "haraka-utils": "^1.1.2" } } diff --git a/test/access.js b/test/access.js index f34a609..b45cc7b 100644 --- a/test/access.js +++ b/test/access.js @@ -7,9 +7,8 @@ const Address = require('address-rfc2821').Address; const fixtures = require('haraka-test-fixtures'); describe('in_list', function () { - beforeEach(function (done) { - this.plugin = new fixtures.plugin('access'); - done() + beforeEach(function () { + this.plugin = new fixtures.plugin('../index'); }) it('white, mail', function (done) { From 38f002d03ed44aa791ecd7ca7021ffb08972a2b0 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:49:03 -0700 Subject: [PATCH 06/10] test: remove `done` from sync tests --- Changes.md | 2 ++ package.json | 2 +- test/access.js | 66 +++++++++++++++++--------------------------------- 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/Changes.md b/Changes.md index 5d84cf8..68e7deb 100644 --- a/Changes.md +++ b/Changes.md @@ -3,11 +3,13 @@ ### [1.1.6] - 2024-04-09 +- dep: update all versions and pin to latest - dep: eslint-plugin-haraka -> @haraka/eslint-config - lint: remove duplicate / stale rules from .eslintrc - chore: populate [files] in package.json. - chore: remove `const plugin = this` pattern (deprecated) - chore: remove unused in_file and in_re_file +- test: remove `done` from sync tests ### [1.1.5] - 2022-06-06 diff --git a/package.json b/package.json index 24cc56a..b3342ab 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "homepage": "https://github.com/haraka/haraka-plugin-access#readme", "devDependencies": { "@haraka/eslint-config": "^1.1.3", - "haraka-test-fixtures": "1.3.5" + "haraka-test-fixtures": "^1.3.5" }, "dependencies": { "address-rfc2822": "^2.2.1", diff --git a/test/access.js b/test/access.js index b45cc7b..0959992 100644 --- a/test/access.js +++ b/test/access.js @@ -11,187 +11,168 @@ describe('in_list', function () { this.plugin = new fixtures.plugin('../index'); }) - it('white, mail', function (done) { + it('white, mail', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { white: { mail: 'test no file' }}; this.plugin.list = { white: { mail: list }}; assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@example.com')); assert.equal(false, this.plugin.in_list('white', 'mail', 'matt@non-exist')); - done(); }) - it('white, mail, case', function (done) { + it('white, mail, case', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { white: { mail: 'test no file' }}; this.plugin.list = { white: { mail: list }}; assert.equal(true, this.plugin.in_list('white', 'mail', 'MATT@exam.ple')); - done(); }) - it('white, rcpt', function (done) { + it('white, rcpt', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { re: { white: { rcpt: 'test file name' }}}; this.plugin.list = { white: { rcpt: list }}; assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@example.com')); assert.equal(false, this.plugin.in_list('white', 'rcpt', 'matt@non-exist')); - done(); }) - it('white, helo', function (done) { + it('white, helo', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { re: { white: { helo: 'test file name' }}}; this.plugin.list = { white: { helo: list }}; assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@example.com')); assert.equal(false, this.plugin.in_list('white', 'helo', 'matt@non-exist')); - done(); }) - it('black, mail', function (done) { + it('black, mail', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { re: { black: { mail: 'test file name' }}}; this.plugin.list = { black: { mail: list }}; assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@example.com')); assert.equal(false, this.plugin.in_list('black', 'mail', 'matt@non-exist')); - done(); }) - it('black, rcpt', function (done) { + it('black, rcpt', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { re: { black: { rcpt: 'test file name' }}}; this.plugin.list = { black: { rcpt: list }}; assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@example.com')); assert.equal(false, this.plugin.in_list('black', 'rcpt', 'matt@non-exist')); - done(); }) - it('black, helo', function (done) { + it('black, helo', function () { const list = {'matt@exam.ple':true,'matt@example.com':true}; this.plugin.cfg = { re: { black: { helo: 'test file name' }}}; this.plugin.list = { black: { helo: list }}; assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@exam.ple')); assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@example.com')); assert.equal(false, this.plugin.in_list('black', 'helo', 'matt@non-exist')); - done(); }) }) describe('in_re_list', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); - done() }) - it('white, mail', function (done) { + it('white, mail', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { white: { mail: 'test file name' }}}; this.plugin.list_re = { white: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('white', 'mail', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('white', 'mail', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('white', 'mail', 'matt@non-exist')); - done(); }) - it('white, rcpt', function (done) { + it('white, rcpt', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { white: { rcpt: 'test file name' }}}; this.plugin.list_re = { white: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('white', 'rcpt', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('white', 'rcpt', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('white', 'rcpt', 'matt@non-exist')); - done(); }) - it('white, helo', function (done) { + it('white, helo', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { white: { helo: 'test file name' }}}; this.plugin.list_re = { white: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('white', 'helo', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('white', 'helo', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('white', 'helo', 'matt@non-exist')); - done(); }) - it('black, mail', function (done) { + it('black, mail', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { black: { mail: 'test file name' }}}; this.plugin.list_re = { black: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('black', 'mail', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('black', 'mail', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('black', 'mail', 'matt@non-exist')); - done(); }) - it('black, rcpt', function (done) { + it('black, rcpt', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { black: { rcpt: 'test file name' }}}; this.plugin.list_re = { black: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('black', 'rcpt', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('black', 'rcpt', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('black', 'rcpt', 'matt@non-exist')); - done(); }) - it('black, helo', function (done) { + it('black, helo', function () { const list = ['.*exam.ple','.*example.com']; this.plugin.cfg = { re: { black: { helo: 'test file name' }}}; this.plugin.list_re = { black: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }}; assert.equal(true, this.plugin.in_re_list('black', 'helo', 'matt@exam.ple')); assert.equal(true, this.plugin.in_re_list('black', 'helo', 'matt@example.com')); assert.equal(false, this.plugin.in_re_list('black', 'helo', 'matt@non-exist')); - done(); }) }) describe('load_file', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); - done() }) - it('case normalizing', function (done) { + it('case normalizing', function () { // console.log(this.plugin.config.root_path); this.plugin.load_file('white', 'rcpt'); assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); // was ADMIN2@EXAMPLE.com assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin1@example.com')); // was admin3@EXAMPLE.com - done(); }) }) describe('load_re_file', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); - done() }) - it('whitelist', function (done) { + it('whitelist', function () { this.plugin.load_re_file('white', 'mail'); assert.ok(this.plugin.list_re); // console.log(this.plugin.temp); assert.equal(true, this.plugin.in_re_list('white', 'mail', 'list@harakamail.com')); assert.equal(false, this.plugin.in_re_list('white', 'mail', 'list@harail.com')); assert.equal(false, this.plugin.in_re_list('white', 'mail', 'LIST@harail.com')); - done(); }) }) describe('rdns_access', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); this.connection = fixtures.connection.createConnection(); this.connection.init_transaction(); - done() }) it('no list', function (done) { @@ -244,12 +225,11 @@ describe('rdns_access', function () { }) describe('helo_access', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); this.connection = fixtures.connection.createConnection(); - done() }) it('no list', function (done) { @@ -277,13 +257,12 @@ describe('helo_access', function () { }) describe('mail_from_access', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); this.connection = fixtures.connection.createConnection(); this.connection.init_transaction(); - done() }) it('no lists populated', function (done) { @@ -335,13 +314,12 @@ describe('mail_from_access', function () { }) describe('rcpt_to_access', function () { - beforeEach(function (done) { + beforeEach(function () { this.plugin = new fixtures.plugin('access'); this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); this.plugin.register(); this.connection = fixtures.connection.createConnection(); this.connection.init_transaction(); - done() }) it('no lists populated', function (done) { From adcc909f75d820fef34cf2c9e0b935abd3e3bcbd Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 09:49:45 -0700 Subject: [PATCH 07/10] renamed: Changes.md -> CHANGELOG.md --- Changes.md => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Changes.md => CHANGELOG.md (100%) diff --git a/Changes.md b/CHANGELOG.md similarity index 100% rename from Changes.md rename to CHANGELOG.md From 77d48a80a2c2c711b9796dad933cae4eef27d64d Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 10:03:42 -0700 Subject: [PATCH 08/10] prettier --- .codeclimate.yml | 8 +- .eslintrc.yaml | 2 +- .github/dependabot.yml | 6 +- .github/workflows/codeql.yml | 6 +- .github/workflows/publish.yml | 2 +- .prettierrc | 2 + .release | 2 +- CHANGELOG.md | 3 +- README.md | 89 ++-- index.js | 917 ++++++++++++++++++---------------- test/access.js | 879 ++++++++++++++++++-------------- 11 files changed, 1043 insertions(+), 873 deletions(-) create mode 100644 .prettierrc diff --git a/.codeclimate.yml b/.codeclimate.yml index 5ca8be1..0e0a85a 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,16 +1,16 @@ engines: eslint: enabled: true - channel: "eslint-8" + channel: 'eslint-8' config: - config: ".eslintrc.yaml" + config: '.eslintrc.yaml' checks: complexity: enabled: false ratings: - paths: - - "**.js" + paths: + - '**.js' checks: file-lines: 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/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/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 42a9bb9..d97a994 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,4 +11,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/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8ded5e0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,2 @@ +singleQuote: true +semi: false diff --git a/.release b/.release index 9be2b27..7cd5707 160000 --- a/.release +++ b/.release @@ -1 +1 @@ -Subproject commit 9be2b270ef836bcfefda085674bf62e2a91defe8 +Subproject commit 7cd5707f7d69f8d4dca1ec407ada911890e59d0a diff --git a/CHANGELOG.md b/CHANGELOG.md index 68e7deb..cf1c6b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,5 @@ ### Unreleased - ### [1.1.6] - 2024-04-09 - dep: update all versions and pin to latest @@ -35,7 +34,7 @@ ### 1.1.0 - 2018-04-23 -- #6: add rcpt.accept setting to enable recipient validation for users in whitelists (like an rcpt_to.* plugin) +- #6: add rcpt.accept setting to enable recipient validation for users in whitelists (like an rcpt_to.\* plugin) ### 1.0.0 - 2017-06-29 diff --git a/README.md b/README.md index 131f2e5..83abaed 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,12 @@ the message headers as well. Settings 'data=true' in the [checks] section of ## PRECISE The precise ACLs share a common file format with each phase having a set of -4 files (whitelist, whitelist\_regex, blacklist, and blacklist\_regex) which +4 files (whitelist, whitelist_regex, blacklist, and blacklist_regex) which are simple lists. The ACLs for each phase apply their tests in the order listed. The whitelist is primarily to counter blacklist entries that match too much, so the the flow -of control is: if whitelisted, stop processing. Then apply the blacklist. +of control is: if whitelisted, stop processing. Then apply the blacklist. Entries in ACL files are one per line. @@ -63,7 +63,7 @@ add entries to the config files for the addresses or patterns to block. ## Upgrading -When upgrading from the rdns\_access, mail\_from.access, and rcpt\_to.access +When upgrading from the rdns_access, mail_from.access, and rcpt_to.access plugins, be sure to remove the plugins from config/plugins, upon pain of wasted CPU cycles. @@ -81,18 +81,20 @@ add this section to _config/access.ini_: ### Checking ACL results -To check access results from other plugins, use the standard *results* +To check access results from other plugins, use the standard _results_ methods. - var ar = connection.results.get('access'); - if (ar.pass.length > 2) { - // they passed the connection and helo checks - } - - var ar = connection.transaction.results.get('access'); - if (ar.pass.length > 2) { - // they passed the mail and rcpt checks - } +```js +const ar = connection.results.get('access'); +if (ar.pass.length > 2) { + // they passed the connection and helo checks +} + +const ar = connection.transaction.results.get('access'); +if (ar.pass.length > 2) { + // they passed the mail and rcpt checks +} +``` To determine which file(s) had matching entries, inspect the contents of the pass/fail elements in the result object. @@ -103,24 +105,27 @@ of the pass/fail elements in the result object. Each check can be enabled or disabled in the [check] section of access.ini: - [check] - any=true (see below) - conn=false - helo=false - mail=false - rcpt=false +```ini +[check] +any=true (see below) +conn=false +helo=false +mail=false +rcpt=false - [rcpt] - accept=false (see below) +[rcpt] +accept=false (see below) +``` A custom deny message can be configured for each SMTP phase: - [deny_msg] - conn=You are not allowed to connect - helo=That HELO is not allowed to connect - mail=That sender cannot send mail here - rcpt=That recipient is not allowed - +```ini +[deny_msg] +conn=You are not allowed to connect +helo=That HELO is not allowed to connect +mail=That sender cannot send mail here +rcpt=That recipient is not allowed +``` ## PRECISE ACLs @@ -129,24 +134,24 @@ A custom deny message can be configured for each SMTP phase: The connect ACLs are evaluated against the IP address **and** the rDNS hostname (if any) of the remote. -* connect.rdns\_access.whitelist (pass) -* connect.rdns\_access.whitelist\_regex (pass) -* connect.rdns\_access.blacklist (block) -* connect.rdns\_access.blacklist\_regex (block) +* connect.rdns_access.whitelist (pass) +* connect.rdns_access.whitelist_regex (pass) +* connect.rdns_access.blacklist (block) +* connect.rdns_access.blacklist_regex (block) ### MAIL FROM -* mail\_from.access.whitelist (pass) -* mail\_from.access.whitelist\_regex (pass) -* mail\_from.access.blacklist (block) -* mail\_from.access.blacklist\_regex (block) +* mail_from.access.whitelist (pass) +* mail_from.access.whitelist_regex (pass) +* mail_from.access.blacklist (block) +* mail_from.access.blacklist_regex (block) ### RCPT TO -* rcpt\_to.access.whitelist (pass) -* rcpt\_to.access.whitelist\_regex (pass) -* rcpt\_to.access.blacklist (block) -* rcpt\_to.access.blacklist\_regex (block) +* rcpt_to.access.whitelist (pass) +* rcpt_to.access.whitelist_regex (pass) +* rcpt_to.access.blacklist (block) +* rcpt_to.access.blacklist_regex (block) ## NOTES @@ -160,7 +165,7 @@ matches are 3x times as slow. When the matches are moved to the end of the 30 member list, the regex searches are over 100x slower than indexOf. Based on this observation, reducing the domain name and doing an indexOf -search of an (even much longer) blacklist is *much* faster than adding lists +search of an (even much longer) blacklist is _much_ faster than adding lists of .\*domain.com entries to the \*\_regex files. ### rcpt accept mode @@ -189,10 +194,10 @@ The portion of a domain name that is operated by a registry. These are often syn com co.uk -The Organizational Domain is the next level higher than the Public Suffix. So if a hostname is *mail.example.com*, and *com* is the Public Suffix, the OD is *example.com*. If the hostname is *www.bbc.co.uk*, the PS is *co.uk* and the OD is *bbc.co.uk*. - +The Organizational Domain is the next level higher than the Public Suffix. So if a hostname is _mail.example.com_, and _com_ is the Public Suffix, the OD is _example.com_. If the hostname is *www.bbc.co.uk*, the PS is _co.uk_ and the OD is _bbc.co.uk_. + [ci-img]: https://github.com/haraka/haraka-plugin-access/actions/workflows/ci.yml/badge.svg [ci-url]: https://github.com/haraka/haraka-plugin-access/actions/workflows/ci.yml [clim-img]: https://codeclimate.com/github/haraka/haraka-plugin-access/badges/gpa.svg diff --git a/index.js b/index.js index 3913f37..b080511 100644 --- a/index.js +++ b/index.js @@ -1,517 +1,554 @@ // access plugin -const tlds = require('haraka-tld'); -const haddr = require('address-rfc2822'); -const net_utils = require('haraka-net-utils'); -const utils = require('haraka-utils'); +const tlds = require('haraka-tld') +const haddr = require('address-rfc2822') +const net_utils = require('haraka-net-utils') +const utils = require('haraka-utils') exports.register = function () { - - this.init_config(); // init this.cfg - this.init_lists(); - this.load_access_ini(); // update with *.ini settings - - let p; - for (p in this.cfg.white) { this.load_file('white', p); } - for (p in this.cfg.black) { this.load_file('black', p); } - for (p in this.cfg.re.white) { this.load_re_file('white', p); } - for (p in this.cfg.re.black) { this.load_re_file('black', p); } - - if (this.cfg.check.conn) { - this.register_hook('connect', 'rdns_access'); - } - if (this.cfg.check.helo) { - this.register_hook('helo', 'helo_access'); - this.register_hook('ehlo', 'helo_access'); - } - if (this.cfg.check.mail) { - this.register_hook('mail', 'mail_from_access'); - } - if (this.cfg.check.rcpt) { - this.register_hook('rcpt', 'rcpt_to_access'); - } - - if (this.cfg.check.any) { - this.load_domain_file('domain', 'any'); - for (const hook in ['connect','helo','ehlo','mail','rcpt']) { - this.register_hook(hook, 'any'); - } - this.register_hook('data_post', 'data_any'); - } + this.init_config() // init this.cfg + this.init_lists() + this.load_access_ini() // update with *.ini settings + + let p + for (p in this.cfg.white) { + this.load_file('white', p) + } + for (p in this.cfg.black) { + this.load_file('black', p) + } + for (p in this.cfg.re.white) { + this.load_re_file('white', p) + } + for (p in this.cfg.re.black) { + this.load_re_file('black', p) + } + + if (this.cfg.check.conn) { + this.register_hook('connect', 'rdns_access') + } + if (this.cfg.check.helo) { + this.register_hook('helo', 'helo_access') + this.register_hook('ehlo', 'helo_access') + } + if (this.cfg.check.mail) { + this.register_hook('mail', 'mail_from_access') + } + if (this.cfg.check.rcpt) { + this.register_hook('rcpt', 'rcpt_to_access') + } + + if (this.cfg.check.any) { + this.load_domain_file('domain', 'any') + for (const hook in ['connect', 'helo', 'ehlo', 'mail', 'rcpt']) { + this.register_hook(hook, 'any') + } + this.register_hook('data_post', 'data_any') + } } exports.init_config = function () { - - this.cfg = { - deny_msg: { - conn: 'You are not allowed to connect', - helo: 'That HELO is not allowed to connect', - mail: 'That sender cannot send mail here', - rcpt: 'That recipient is not allowed', - }, - domain: { - any: 'access.domains', - }, - white: { - conn: 'connect.rdns_access.whitelist', - mail: 'mail_from.access.whitelist', - rcpt: 'rcpt_to.access.whitelist', - }, - black: { - conn: 'connect.rdns_access.blacklist', - mail: 'mail_from.access.blacklist', - rcpt: 'rcpt_to.access.blacklist', - }, - re: { - black: { - conn: 'connect.rdns_access.blacklist_regex', - mail: 'mail_from.access.blacklist_regex', - rcpt: 'rcpt_to.access.blacklist_regex', - helo: 'helo.checks.regexps', - }, - white: { - conn: 'connect.rdns_access.whitelist_regex', - mail: 'mail_from.access.whitelist_regex', - rcpt: 'rcpt_to.access.whitelist_regex', - }, - }, - }; + this.cfg = { + deny_msg: { + conn: 'You are not allowed to connect', + helo: 'That HELO is not allowed to connect', + mail: 'That sender cannot send mail here', + rcpt: 'That recipient is not allowed', + }, + domain: { + any: 'access.domains', + }, + white: { + conn: 'connect.rdns_access.whitelist', + mail: 'mail_from.access.whitelist', + rcpt: 'rcpt_to.access.whitelist', + }, + black: { + conn: 'connect.rdns_access.blacklist', + mail: 'mail_from.access.blacklist', + rcpt: 'rcpt_to.access.blacklist', + }, + re: { + black: { + conn: 'connect.rdns_access.blacklist_regex', + mail: 'mail_from.access.blacklist_regex', + rcpt: 'rcpt_to.access.blacklist_regex', + helo: 'helo.checks.regexps', + }, + white: { + conn: 'connect.rdns_access.whitelist_regex', + mail: 'mail_from.access.whitelist_regex', + rcpt: 'rcpt_to.access.whitelist_regex', + }, + }, + } } exports.load_access_ini = function () { - const cfg = this.config.get('access.ini', { - booleans: [ - '+check.any', - '+check.conn', - '-check.helo', - '+check.mail', - '+check.rcpt', - '-rcpt.accept', - ], - }, () => { - this.load_access_ini(); - }) - - this.cfg.check = cfg.check; - if (cfg.deny_msg) { - let p; - for (p in this.cfg.deny_msg) { - if (cfg.deny_msg[p]) { - this.cfg.deny_msg[p] = cfg.deny_msg[p]; - } - } - } - - this.cfg.rcpt = cfg.rcpt; - - // backwards compatibility - const mf_cfg = this.config.get('mail_from.access.ini'); - if (mf_cfg && mf_cfg.general && mf_cfg.general.deny_msg) { - this.cfg.deny_msg.mail = mf_cfg.general.deny_msg; - } - const rcpt_cfg = this.config.get('rcpt_to.access.ini'); - if (rcpt_cfg && rcpt_cfg.general && rcpt_cfg.general.deny_msg) { - this.cfg.deny_msg.rcpt = rcpt_cfg.general.deny_msg; - } - const rdns_cfg = this.config.get('connect.rdns_access.ini'); - if (rdns_cfg && rdns_cfg.general && rdns_cfg.general.deny_msg) { - this.cfg.deny_msg.conn = rdns_cfg.general.deny_msg; - } + const cfg = this.config.get( + 'access.ini', + { + booleans: [ + '+check.any', + '+check.conn', + '-check.helo', + '+check.mail', + '+check.rcpt', + '-rcpt.accept', + ], + }, + () => { + this.load_access_ini() + }, + ) + + this.cfg.check = cfg.check + if (cfg.deny_msg) { + let p + for (p in this.cfg.deny_msg) { + if (cfg.deny_msg[p]) { + this.cfg.deny_msg[p] = cfg.deny_msg[p] + } + } + } + + this.cfg.rcpt = cfg.rcpt + + // backwards compatibility + const mf_cfg = this.config.get('mail_from.access.ini') + if (mf_cfg && mf_cfg.general && mf_cfg.general.deny_msg) { + this.cfg.deny_msg.mail = mf_cfg.general.deny_msg + } + const rcpt_cfg = this.config.get('rcpt_to.access.ini') + if (rcpt_cfg && rcpt_cfg.general && rcpt_cfg.general.deny_msg) { + this.cfg.deny_msg.rcpt = rcpt_cfg.general.deny_msg + } + const rdns_cfg = this.config.get('connect.rdns_access.ini') + if (rdns_cfg && rdns_cfg.general && rdns_cfg.general.deny_msg) { + this.cfg.deny_msg.conn = rdns_cfg.general.deny_msg + } } exports.init_lists = function () { - this.list = { - black: { conn: {}, helo: {}, mail: {}, rcpt: {} }, - white: { conn: {}, helo: {}, mail: {}, rcpt: {} }, - domain: { any: {} }, - }; - this.list_re = { - black: {}, - white: {}, - }; + this.list = { + black: { conn: {}, helo: {}, mail: {}, rcpt: {} }, + white: { conn: {}, helo: {}, mail: {}, rcpt: {} }, + domain: { any: {} }, + } + this.list_re = { + black: {}, + white: {}, + } } exports.get_domain = function (hook, connection, params) { - - switch (hook) { - case 'connect': - if (!connection.remote.host) return; - if (connection.remote.host === 'DNSERROR') return; - if (connection.remote.host === 'Unknown') return; - return connection.remote.host; - case 'helo': - case 'ehlo': - if (net_utils.is_ip_literal(params)) return; - return params; - case 'mail': - case 'rcpt': - if (params && params[0]) return params[0].host; - } - return; + switch (hook) { + case 'connect': + if (!connection.remote.host) return + if (connection.remote.host === 'DNSERROR') return + if (connection.remote.host === 'Unknown') return + return connection.remote.host + case 'helo': + case 'ehlo': + if (net_utils.is_ip_literal(params)) return + return params + case 'mail': + case 'rcpt': + if (params && params[0]) return params[0].host + } + return } -exports.any_whitelist = function (connection, hook, params, domain, org_domain) { - - if (hook === 'mail' || hook === 'rcpt') { - const email = params[0].address(); - if (email && this.in_list('domain', 'any', `!${email}`)) return true; - } - - if (this.in_list('domain', 'any', `!${org_domain}`)) return true; - if (this.in_list('domain', 'any', `!${domain}`)) return true; - - return false; +exports.any_whitelist = function ( + connection, + hook, + params, + domain, + org_domain, +) { + if (hook === 'mail' || hook === 'rcpt') { + const email = params[0].address() + if (email && this.in_list('domain', 'any', `!${email}`)) return true + } + + if (this.in_list('domain', 'any', `!${org_domain}`)) return true + if (this.in_list('domain', 'any', `!${domain}`)) return true + + return false } exports.any = function (next, connection, params) { - if (!this.cfg.check.any) return next(); - - const hook = connection.hook; - if (!hook) { - connection.logerror(this, "hook detection failed"); - return next(); - } - - // step 1: get a domain name from whatever info is available - const domain = this.get_domain(hook, connection, params); - if (!domain) { - connection.logdebug(this, `domain detect failed on hook: ${hook}`); - return next(); - } - if (!/\./.test(domain)) { - connection.results.add(this, {fail: `invalid domain: ${domain}`, emit: true}); - return next(); - } - - const org_domain = tlds.get_organizational_domain(domain); - if (!org_domain) { - connection.loginfo(this, `no org domain from ${domain}`); - return next(); - } - - const file = this.cfg.domain.any; - - // step 2: check for whitelist - if (this.any_whitelist(connection, hook, params, domain, org_domain)) { - const whiteResults = {pass: `${hook}:${file}`, whitelist: true, emit: true} - connection.results.add(this, whiteResults); - return next(); - } - - // step 3: check for blacklist - if (this.in_list('domain', 'any', org_domain)) { - connection.results.add(this, {fail: `${file}(${org_domain})`, blacklist: true, emit: true}); - return next(DENY, "You are not welcome here."); - } + if (!this.cfg.check.any) return next() + + const hook = connection.hook + if (!hook) { + connection.logerror(this, 'hook detection failed') + return next() + } + + // step 1: get a domain name from whatever info is available + const domain = this.get_domain(hook, connection, params) + if (!domain) { + connection.logdebug(this, `domain detect failed on hook: ${hook}`) + return next() + } + if (!/\./.test(domain)) { + connection.results.add(this, { + fail: `invalid domain: ${domain}`, + emit: true, + }) + return next() + } + + const org_domain = tlds.get_organizational_domain(domain) + if (!org_domain) { + connection.loginfo(this, `no org domain from ${domain}`) + return next() + } + + const file = this.cfg.domain.any + + // step 2: check for whitelist + if (this.any_whitelist(connection, hook, params, domain, org_domain)) { + const whiteResults = { + pass: `${hook}:${file}`, + whitelist: true, + emit: true, + } + connection.results.add(this, whiteResults) + return next() + } + + // step 3: check for blacklist + if (this.in_list('domain', 'any', org_domain)) { + connection.results.add(this, { + fail: `${file}(${org_domain})`, + blacklist: true, + emit: true, + }) + return next(DENY, 'You are not welcome here.') + } - const umsg = hook ? `${hook}:any` : 'any'; - connection.results.add(this, {msg: `unlisted(${umsg})` }); - next(); + const umsg = hook ? `${hook}:any` : 'any' + connection.results.add(this, { msg: `unlisted(${umsg})` }) + next() } exports.rdns_store_results = function (connection, color, file) { - - switch (color) { - case 'white': - connection.results.add(this, { whitelist: true, pass: file, emit: true }) - break; - case 'black': - connection.results.add(this, { fail: file, emit: true }) - break; - } + switch (color) { + case 'white': + connection.results.add(this, { whitelist: true, pass: file, emit: true }) + break + case 'black': + connection.results.add(this, { fail: file, emit: true }) + break + } } exports.rdns_is_listed = function (connection, color) { + const addrs = [connection.remote.ip, connection.remote.host] - const addrs = [ connection.remote.ip, connection.remote.host ]; - - for (let addr of addrs) { - if (!addr) continue; // empty rDNS host - if (/[\w]/.test(addr)) addr = addr.toLowerCase(); + for (let addr of addrs) { + if (!addr) continue // empty rDNS host + if (/[\w]/.test(addr)) addr = addr.toLowerCase() - let file = this.cfg[color].conn; - connection.logdebug(this, `checking ${addr} against ${file}`); + let file = this.cfg[color].conn + connection.logdebug(this, `checking ${addr} against ${file}`) - if (this.in_list(color, 'conn', addr)) { - this.rdns_store_results(connection, color, file) - return true; - } + if (this.in_list(color, 'conn', addr)) { + this.rdns_store_results(connection, color, file) + return true + } - file = this.cfg.re[color].conn; - connection.logdebug(this, `checking ${addr} against ${file}`); - if (this.in_re_list(color, 'conn', addr)) { - this.rdns_store_results(connection, color, file) - return true; - } + file = this.cfg.re[color].conn + connection.logdebug(this, `checking ${addr} against ${file}`) + if (this.in_re_list(color, 'conn', addr)) { + this.rdns_store_results(connection, color, file) + return true } + } - return false; + return false } exports.rdns_access = function (next, connection) { - if (!this.cfg.check.conn) return next(); + if (!this.cfg.check.conn) return next() - if (this.rdns_is_listed(connection, 'white')) return next(); + if (this.rdns_is_listed(connection, 'white')) return next() - const deny_msg = `${connection.remote.host} [${connection.remote.ip}] ${this.cfg.deny_msg.conn}` - if (this.rdns_is_listed(connection, 'black')) return next(DENYDISCONNECT, deny_msg); + const deny_msg = `${connection.remote.host} [${connection.remote.ip}] ${this.cfg.deny_msg.conn}` + if (this.rdns_is_listed(connection, 'black')) + return next(DENYDISCONNECT, deny_msg) - connection.results.add(this, { msg: 'unlisted(conn)' }); - next(); + connection.results.add(this, { msg: 'unlisted(conn)' }) + next() } exports.helo_access = function (next, connection, helo) { - if (!this.cfg.check.helo) return next(); + if (!this.cfg.check.helo) return next() - const file = this.cfg.re.black.helo; - if (this.in_re_list('black', 'helo', helo)) { - connection.results.add(this, {fail: file, emit: true}); - return next(DENY, `${helo} ${this.cfg.deny_msg.helo}`); - } + const file = this.cfg.re.black.helo + if (this.in_re_list('black', 'helo', helo)) { + connection.results.add(this, { fail: file, emit: true }) + return next(DENY, `${helo} ${this.cfg.deny_msg.helo}`) + } - connection.results.add(this, {msg: 'unlisted(helo)' }); - next(); + connection.results.add(this, { msg: 'unlisted(helo)' }) + next() } exports.mail_from_access = function (next, connection, params) { + if (!this.cfg.check.mail) return next() - if (!this.cfg.check.mail) return next(); - - const mail_from = params[0].address(); - if (!mail_from) { - connection.transaction.results.add(this, { - skip: 'null sender', emit: true}); - return next(); - } - - // address whitelist checks - let file = this.cfg.white.mail; - connection.logdebug(this, `checking ${mail_from} against ${file}`); - if (this.in_list('white', 'mail', mail_from)) { - connection.transaction.results.add(this, {pass: file, emit: true}); - return next(); - } - - file = this.cfg.re.white.mail; - connection.logdebug(this, `checking ${mail_from} against ${file}`); - if (this.in_re_list('white', 'mail', mail_from)) { - connection.transaction.results.add(this, {pass: file, emit: true}); - return next(); - } - - // address blacklist checks - file = this.cfg.black.mail; - if (this.in_list('black', 'mail', mail_from)) { - connection.transaction.results.add(this, {fail: file, emit: true}); - return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`); - } - - file = this.cfg.re.black.mail; - connection.logdebug(this, `checking ${mail_from} against ${file}`); - if (this.in_re_list('black', 'mail', mail_from)) { - connection.transaction.results.add(this, {fail: file, emit: true}); - return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`); - } - - connection.transaction.results.add(this, {msg: 'unlisted(mail)' }); - next(); + const mail_from = params[0].address() + if (!mail_from) { + connection.transaction.results.add(this, { + skip: 'null sender', + emit: true, + }) + return next() + } + + // address whitelist checks + let file = this.cfg.white.mail + connection.logdebug(this, `checking ${mail_from} against ${file}`) + if (this.in_list('white', 'mail', mail_from)) { + connection.transaction.results.add(this, { pass: file, emit: true }) + return next() + } + + file = this.cfg.re.white.mail + connection.logdebug(this, `checking ${mail_from} against ${file}`) + if (this.in_re_list('white', 'mail', mail_from)) { + connection.transaction.results.add(this, { pass: file, emit: true }) + return next() + } + + // address blacklist checks + file = this.cfg.black.mail + if (this.in_list('black', 'mail', mail_from)) { + connection.transaction.results.add(this, { fail: file, emit: true }) + return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`) + } + + file = this.cfg.re.black.mail + connection.logdebug(this, `checking ${mail_from} against ${file}`) + if (this.in_re_list('black', 'mail', mail_from)) { + connection.transaction.results.add(this, { fail: file, emit: true }) + return next(DENY, `${mail_from} ${this.cfg.deny_msg.mail}`) + } + + connection.transaction.results.add(this, { msg: 'unlisted(mail)' }) + next() } exports.rcpt_to_access = function (next, connection, params) { - if (!this.cfg.check.rcpt) return next(); - - let pass_status = undefined; - if (this.cfg.rcpt.accept) { - pass_status = OK; - } - - const rcpt_to = params[0].address(); - - // address whitelist checks - if (!rcpt_to) { - connection.transaction.results.add(this, { - skip: 'null rcpt', emit: true}); - return next(); - } - - let file = this.cfg.white.rcpt; - if (this.in_list('white', 'rcpt', rcpt_to)) { - connection.transaction.results.add(this, {pass: file, emit: true}); - return next(pass_status); - } - - file = this.cfg.re.white.rcpt; - if (this.in_re_list('white', 'rcpt', rcpt_to)) { - connection.transaction.results.add(this, {pass: file, emit: true}); - return next(pass_status); - } + if (!this.cfg.check.rcpt) return next() - // address blacklist checks - file = this.cfg.black.rcpt; - if (this.in_list('black', 'rcpt', rcpt_to)) { - connection.transaction.results.add(this, {fail: file, emit: true}); - return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`); - } + let pass_status = undefined + if (this.cfg.rcpt.accept) { + pass_status = OK + } - file = this.cfg.re.black.rcpt; - if (this.in_re_list('black', 'rcpt', rcpt_to)) { - connection.transaction.results.add(this, {fail: file, emit: true}); - return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`); - } + const rcpt_to = params[0].address() - connection.transaction.results.add(this, {msg: 'unlisted(rcpt)' }); - next(); + // address whitelist checks + if (!rcpt_to) { + connection.transaction.results.add(this, { + skip: 'null rcpt', + emit: true, + }) + return next() + } + + let file = this.cfg.white.rcpt + if (this.in_list('white', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, { pass: file, emit: true }) + return next(pass_status) + } + + file = this.cfg.re.white.rcpt + if (this.in_re_list('white', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, { pass: file, emit: true }) + return next(pass_status) + } + + // address blacklist checks + file = this.cfg.black.rcpt + if (this.in_list('black', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, { fail: file, emit: true }) + return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`) + } + + file = this.cfg.re.black.rcpt + if (this.in_re_list('black', 'rcpt', rcpt_to)) { + connection.transaction.results.add(this, { fail: file, emit: true }) + return next(DENY, `${rcpt_to} ${this.cfg.deny_msg.rcpt}`) + } + + connection.transaction.results.add(this, { msg: 'unlisted(rcpt)' }) + next() } exports.data_any = function (next, connection) { - if (!this.cfg.check.data && !this.cfg.check.any) { - connection.transaction.results.add(this, {skip: 'data(disabled)'}); - return next(); - } - - const hdr_from = connection.transaction.header.get_decoded('From'); - if (!hdr_from) { - connection.transaction.results.add(this, {fail: 'data(missing_from)'}); - return next(); - } - - let hdr_addr; - try { - hdr_addr = haddr.parse(hdr_from)[0]; - } - catch (e) { - connection.transaction.results.add(this, {fail: `data(unparsable_from:${hdr_from})`}); - return next(); - } - - if (!hdr_addr) { - connection.transaction.results.add(this, {fail: `data(unparsable_from:${hdr_from})`}); - return next(); - } - - const hdr_dom = tlds.get_organizational_domain(hdr_addr.host()); - if (!hdr_dom) { - connection.transaction.results.add(this, {fail: `data(no_od_from:${hdr_addr})`}); - return next(); - } + if (!this.cfg.check.data && !this.cfg.check.any) { + connection.transaction.results.add(this, { skip: 'data(disabled)' }) + return next() + } + + const hdr_from = connection.transaction.header.get_decoded('From') + if (!hdr_from) { + connection.transaction.results.add(this, { fail: 'data(missing_from)' }) + return next() + } + + let hdr_addr + try { + hdr_addr = haddr.parse(hdr_from)[0] + } catch (e) { + connection.transaction.results.add(this, { + fail: `data(unparsable_from:${hdr_from})`, + }) + return next() + } - const file = this.cfg.domain.any; - if (this.in_list('domain', 'any', `!${hdr_dom}`)) { - connection.results.add(this, {pass: file, whitelist: true, emit: true}); - return next(); - } + if (!hdr_addr) { + connection.transaction.results.add(this, { + fail: `data(unparsable_from:${hdr_from})`, + }) + return next() + } - if (this.in_list('domain', 'any', hdr_dom)) { - connection.results.add(this, {fail: `${file}(${hdr_dom})`, blacklist: true, emit: true}); - return next(DENY, "Email from that domain is not accepted here."); - } + const hdr_dom = tlds.get_organizational_domain(hdr_addr.host()) + if (!hdr_dom) { + connection.transaction.results.add(this, { + fail: `data(no_od_from:${hdr_addr})`, + }) + return next() + } + + const file = this.cfg.domain.any + if (this.in_list('domain', 'any', `!${hdr_dom}`)) { + connection.results.add(this, { pass: file, whitelist: true, emit: true }) + return next() + } + + if (this.in_list('domain', 'any', hdr_dom)) { + connection.results.add(this, { + fail: `${file}(${hdr_dom})`, + blacklist: true, + emit: true, + }) + return next(DENY, 'Email from that domain is not accepted here.') + } - connection.results.add(this, {msg: 'unlisted(any)' }); - next(); + connection.results.add(this, { msg: 'unlisted(any)' }) + next() } exports.in_list = function (type, phase, address) { - if (this.list[type][phase] === undefined) { - console.log(`phase not defined: ${phase}`); - return false; - } - if (!address) return false; - if (this.list[type][phase][address.toLowerCase()]) return true; - return false; + if (this.list[type][phase] === undefined) { + console.log(`phase not defined: ${phase}`) + return false + } + if (!address) return false + if (this.list[type][phase][address.toLowerCase()]) return true + return false } exports.in_re_list = function (type, phase, address) { - if (!this.list_re[type][phase]) { return false; } - if (!this.cfg.re[type][phase].source) { - this.logdebug(this, `empty file: ${this.cfg.re[type][phase]}`); - } - else { - this.logdebug(this, `checking ${address} against ` + - `${this.cfg.re[type][phase].source}`); - } - return this.list_re[type][phase].test(address); + if (!this.list_re[type][phase]) { + return false + } + if (!this.cfg.re[type][phase].source) { + this.logdebug(this, `empty file: ${this.cfg.re[type][phase]}`) + } else { + this.logdebug( + this, + `checking ${address} against ` + `${this.cfg.re[type][phase].source}`, + ) + } + return this.list_re[type][phase].test(address) } exports.load_file = function (type, phase) { - if (!this.cfg.check[phase]) { - this.logdebug(this, `skipping ${this.cfg[type][phase]}`); - return; - } - - const file_name = this.cfg[type][phase]; - - // load config with a self-referential callback - const list = this.config.get(file_name, 'list', () => { - this.load_file(type, phase); - }); - - // init the list store, type is white or black - if (!this.list) this.list = { type: {} }; - if (!this.list[type]) this.list[type] = {}; - - // toLower when loading spends a fraction of a second at load time - // to save millions of seconds during run time. - const listAsHash = {}; // store as hash for speedy lookups - for (const entry of list) { - listAsHash[entry.toLowerCase()] = true; - } - this.list[type][phase] = listAsHash; + if (!this.cfg.check[phase]) { + this.logdebug(this, `skipping ${this.cfg[type][phase]}`) + return + } + + const file_name = this.cfg[type][phase] + + // load config with a self-referential callback + const list = this.config.get(file_name, 'list', () => { + this.load_file(type, phase) + }) + + // init the list store, type is white or black + if (!this.list) this.list = { type: {} } + if (!this.list[type]) this.list[type] = {} + + // toLower when loading spends a fraction of a second at load time + // to save millions of seconds during run time. + const listAsHash = {} // store as hash for speedy lookups + for (const entry of list) { + listAsHash[entry.toLowerCase()] = true + } + this.list[type][phase] = listAsHash } exports.load_re_file = function (type, phase) { - if (!this.cfg.check[phase]) { - this.logdebug(this, `skipping ${this.cfg.re[type][phase]}`); - return; - } - - const plugin = this; - const regex_list = utils.valid_regexes( - plugin.config.get( - plugin.cfg.re[type][phase], - 'list', - () => { - plugin.load_re_file(type, phase); - }) - ); - - // initialize the list store - if (!this.list_re) this.list_re = { type: {} }; - if (!this.list_re[type]) this.list_re[type] = {}; - - // compile the regexes at the designated location - this.list_re[type][phase] = new RegExp(`^(${regex_list.join('|')})$`, 'i'); + if (!this.cfg.check[phase]) { + this.logdebug(this, `skipping ${this.cfg.re[type][phase]}`) + return + } + + const plugin = this + const regex_list = utils.valid_regexes( + plugin.config.get(plugin.cfg.re[type][phase], 'list', () => { + plugin.load_re_file(type, phase) + }), + ) + + // initialize the list store + if (!this.list_re) this.list_re = { type: {} } + if (!this.list_re[type]) this.list_re[type] = {} + + // compile the regexes at the designated location + this.list_re[type][phase] = new RegExp(`^(${regex_list.join('|')})$`, 'i') } exports.load_domain_file = function (type, phase) { - if (!this.cfg.check[phase]) { - this.logdebug(this, `skipping ${this.cfg[type][phase]}`); - return; - } - - const file_name = this.cfg[type][phase]; - const list = this.config.get(file_name, 'list', () => { - this.load_domain_file(type, phase); - }); - - // init the list store, if needed - if (!this.list) this.list = { type: {} }; - if (!this.list[type]) this.list[type] = {}; - - // lowercase list items at load (much faster than at run time) - for (const entry of list) { - - if (entry[0] === '!') { // whitelist entry - this.list[type][phase][entry.toLowerCase()] = true; - continue; - } - - if (/@/.test(entry[0])) { // email address - this.list[type][phase][entry.toLowerCase()] = true; - continue; - } - - const d = tlds.get_organizational_domain(entry); - if (!d) continue; - this.list[type][phase][d.toLowerCase()] = true; - } + if (!this.cfg.check[phase]) { + this.logdebug(this, `skipping ${this.cfg[type][phase]}`) + return + } + + const file_name = this.cfg[type][phase] + const list = this.config.get(file_name, 'list', () => { + this.load_domain_file(type, phase) + }) + + // init the list store, if needed + if (!this.list) this.list = { type: {} } + if (!this.list[type]) this.list[type] = {} + + // lowercase list items at load (much faster than at run time) + for (const entry of list) { + if (entry[0] === '!') { + // whitelist entry + this.list[type][phase][entry.toLowerCase()] = true + continue + } + + if (/@/.test(entry[0])) { + // email address + this.list[type][phase][entry.toLowerCase()] = true + continue + } + + const d = tlds.get_organizational_domain(entry) + if (!d) continue + this.list[type][phase][d.toLowerCase()] = true + } } diff --git a/test/access.js b/test/access.js index 0959992..0aca53b 100644 --- a/test/access.js +++ b/test/access.js @@ -1,402 +1,529 @@ -'use strict'; +'use strict' -const assert = require('assert') -const path = require('path') +const assert = require('assert') +const path = require('path') -const Address = require('address-rfc2821').Address; -const fixtures = require('haraka-test-fixtures'); +const Address = require('address-rfc2821').Address +const fixtures = require('haraka-test-fixtures') describe('in_list', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('../index'); - }) - - it('white, mail', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { white: { mail: 'test no file' }}; - this.plugin.list = { white: { mail: list }}; - assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('white', 'mail', 'matt@non-exist')); - }) - - it('white, mail, case', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { white: { mail: 'test no file' }}; - this.plugin.list = { white: { mail: list }}; - assert.equal(true, this.plugin.in_list('white', 'mail', 'MATT@exam.ple')); - }) - - it('white, rcpt', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { re: { white: { rcpt: 'test file name' }}}; - this.plugin.list = { white: { rcpt: list }}; - assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('white', 'rcpt', 'matt@non-exist')); - }) - - it('white, helo', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { re: { white: { helo: 'test file name' }}}; - this.plugin.list = { white: { helo: list }}; - assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('white', 'helo', 'matt@non-exist')); - }) - - it('black, mail', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { re: { black: { mail: 'test file name' }}}; - this.plugin.list = { black: { mail: list }}; - assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('black', 'mail', 'matt@non-exist')); - }) - - it('black, rcpt', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { re: { black: { rcpt: 'test file name' }}}; - this.plugin.list = { black: { rcpt: list }}; - assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('black', 'rcpt', 'matt@non-exist')); - }) - - it('black, helo', function () { - const list = {'matt@exam.ple':true,'matt@example.com':true}; - this.plugin.cfg = { re: { black: { helo: 'test file name' }}}; - this.plugin.list = { black: { helo: list }}; - assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@example.com')); - assert.equal(false, this.plugin.in_list('black', 'helo', 'matt@non-exist')); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('../index') + }) + + it('white, mail', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { white: { mail: 'test no file' } } + this.plugin.list = { white: { mail: list } } + assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('white', 'mail', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('white', 'mail', 'matt@non-exist')) + }) + + it('white, mail, case', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { white: { mail: 'test no file' } } + this.plugin.list = { white: { mail: list } } + assert.equal(true, this.plugin.in_list('white', 'mail', 'MATT@exam.ple')) + }) + + it('white, rcpt', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { re: { white: { rcpt: 'test file name' } } } + this.plugin.list = { white: { rcpt: list } } + assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('white', 'rcpt', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('white', 'rcpt', 'matt@non-exist')) + }) + + it('white, helo', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { re: { white: { helo: 'test file name' } } } + this.plugin.list = { white: { helo: list } } + assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('white', 'helo', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('white', 'helo', 'matt@non-exist')) + }) + + it('black, mail', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { re: { black: { mail: 'test file name' } } } + this.plugin.list = { black: { mail: list } } + assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('black', 'mail', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('black', 'mail', 'matt@non-exist')) + }) + + it('black, rcpt', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { re: { black: { rcpt: 'test file name' } } } + this.plugin.list = { black: { rcpt: list } } + assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('black', 'rcpt', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('black', 'rcpt', 'matt@non-exist')) + }) + + it('black, helo', function () { + const list = { 'matt@exam.ple': true, 'matt@example.com': true } + this.plugin.cfg = { re: { black: { helo: 'test file name' } } } + this.plugin.list = { black: { helo: list } } + assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@exam.ple')) + assert.equal(true, this.plugin.in_list('black', 'helo', 'matt@example.com')) + assert.equal(false, this.plugin.in_list('black', 'helo', 'matt@non-exist')) + }) }) describe('in_re_list', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - }) - - it('white, mail', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { white: { mail: 'test file name' }}}; - this.plugin.list_re = { white: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('white', 'mail', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('white', 'mail', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('white', 'mail', 'matt@non-exist')); - }) - - it('white, rcpt', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { white: { rcpt: 'test file name' }}}; - this.plugin.list_re = { white: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('white', 'rcpt', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('white', 'rcpt', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('white', 'rcpt', 'matt@non-exist')); - }) - - it('white, helo', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { white: { helo: 'test file name' }}}; - this.plugin.list_re = { white: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('white', 'helo', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('white', 'helo', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('white', 'helo', 'matt@non-exist')); - }) - - it('black, mail', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { black: { mail: 'test file name' }}}; - this.plugin.list_re = { black: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('black', 'mail', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('black', 'mail', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('black', 'mail', 'matt@non-exist')); - }) - - it('black, rcpt', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { black: { rcpt: 'test file name' }}}; - this.plugin.list_re = { black: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('black', 'rcpt', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('black', 'rcpt', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('black', 'rcpt', 'matt@non-exist')); - }) - - it('black, helo', function () { - const list = ['.*exam.ple','.*example.com']; - this.plugin.cfg = { re: { black: { helo: 'test file name' }}}; - this.plugin.list_re = { black: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }}; - assert.equal(true, this.plugin.in_re_list('black', 'helo', 'matt@exam.ple')); - assert.equal(true, this.plugin.in_re_list('black', 'helo', 'matt@example.com')); - assert.equal(false, this.plugin.in_re_list('black', 'helo', 'matt@non-exist')); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + }) + + it('white, mail', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { white: { mail: 'test file name' } } } + this.plugin.list_re = { + white: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('white', 'mail', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('white', 'mail', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('white', 'mail', 'matt@non-exist'), + ) + }) + + it('white, rcpt', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { white: { rcpt: 'test file name' } } } + this.plugin.list_re = { + white: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('white', 'rcpt', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('white', 'rcpt', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('white', 'rcpt', 'matt@non-exist'), + ) + }) + + it('white, helo', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { white: { helo: 'test file name' } } } + this.plugin.list_re = { + white: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('white', 'helo', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('white', 'helo', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('white', 'helo', 'matt@non-exist'), + ) + }) + + it('black, mail', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { black: { mail: 'test file name' } } } + this.plugin.list_re = { + black: { mail: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('black', 'mail', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('black', 'mail', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('black', 'mail', 'matt@non-exist'), + ) + }) + + it('black, rcpt', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { black: { rcpt: 'test file name' } } } + this.plugin.list_re = { + black: { rcpt: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('black', 'rcpt', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('black', 'rcpt', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('black', 'rcpt', 'matt@non-exist'), + ) + }) + + it('black, helo', function () { + const list = ['.*exam.ple', '.*example.com'] + this.plugin.cfg = { re: { black: { helo: 'test file name' } } } + this.plugin.list_re = { + black: { helo: new RegExp(`^(${list.join('|')})$`, 'i') }, + } + assert.equal(true, this.plugin.in_re_list('black', 'helo', 'matt@exam.ple')) + assert.equal( + true, + this.plugin.in_re_list('black', 'helo', 'matt@example.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('black', 'helo', 'matt@non-exist'), + ) + }) }) describe('load_file', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - }) - - it('case normalizing', function () { - // console.log(this.plugin.config.root_path); - this.plugin.load_file('white', 'rcpt'); - assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); - assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin2@example.com')); // was ADMIN2@EXAMPLE.com - assert.equal(true, this.plugin.in_list('white', 'rcpt', 'admin1@example.com')); // was admin3@EXAMPLE.com - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + }) + + it('case normalizing', function () { + // console.log(this.plugin.config.root_path); + this.plugin.load_file('white', 'rcpt') + assert.equal( + true, + this.plugin.in_list('white', 'rcpt', 'admin2@example.com'), + ) + assert.equal( + true, + this.plugin.in_list('white', 'rcpt', 'admin2@example.com'), + ) // was ADMIN2@EXAMPLE.com + assert.equal( + true, + this.plugin.in_list('white', 'rcpt', 'admin1@example.com'), + ) // was admin3@EXAMPLE.com + }) }) describe('load_re_file', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - }) - - it('whitelist', function () { - this.plugin.load_re_file('white', 'mail'); - assert.ok(this.plugin.list_re); - // console.log(this.plugin.temp); - assert.equal(true, this.plugin.in_re_list('white', 'mail', 'list@harakamail.com')); - assert.equal(false, this.plugin.in_re_list('white', 'mail', 'list@harail.com')); - assert.equal(false, this.plugin.in_re_list('white', 'mail', 'LIST@harail.com')); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + }) + + it('whitelist', function () { + this.plugin.load_re_file('white', 'mail') + assert.ok(this.plugin.list_re) + // console.log(this.plugin.temp); + assert.equal( + true, + this.plugin.in_re_list('white', 'mail', 'list@harakamail.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('white', 'mail', 'list@harail.com'), + ) + assert.equal( + false, + this.plugin.in_re_list('white', 'mail', 'LIST@harail.com'), + ) + }) }) describe('rdns_access', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - this.connection.init_transaction(); - }) - - it('no list', function (done) { - this.connection.remote.ip='1.1.1.1'; - this.connection.remote.host='host.example.com'; - this.plugin.rdns_access((rc) => { - // console.log(this.connection.results.get('access')); - assert.equal(undefined, rc); - assert.ok(this.connection.results.get('access').msg.length); - done(); - }, this.connection); - }) - - it('whitelist', function (done) { - this.connection.remote.ip='1.1.1.1'; - this.connection.remote.host='host.example.com'; - this.plugin.list.white.conn['host.example.com']=true; - this.plugin.rdns_access((rc) => { - assert.equal(undefined, rc); - assert.ok(this.connection.results.get('access').pass.length); - // assert.ok(this.connection.results.has('access', 'pass', /white/)); - done(); - }, this.connection); - }) - - it('blacklist', function (done) { - this.connection.remote.ip='1.1.1.1'; - this.connection.remote.host='host.example.com'; - this.plugin.list.black.conn['host.example.com']=true; - this.plugin.rdns_access((rc, msg) => { - assert.equal(DENYDISCONNECT, rc); - assert.equal("host.example.com [1.1.1.1] You are not allowed to connect", msg); - assert.ok(this.connection.results.get('access').fail.length); - done(); - }, this.connection); - }) - - it('blacklist regex', function (done) { - this.connection.remote.ip='1.1.1.1'; - this.connection.remote.host='host.antispam.com'; - const black = [ '.*spam.com' ]; - this.plugin.list_re.black.conn = new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.rdns_access( (rc, msg) => { - assert.equal(DENYDISCONNECT, rc); - assert.equal("host.antispam.com [1.1.1.1] You are not allowed to connect", msg); - assert.ok(this.connection.results.get('access').fail.length); - done(); - }, this.connection); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + this.connection = fixtures.connection.createConnection() + this.connection.init_transaction() + }) + + it('no list', function (done) { + this.connection.remote.ip = '1.1.1.1' + this.connection.remote.host = 'host.example.com' + this.plugin.rdns_access((rc) => { + // console.log(this.connection.results.get('access')); + assert.equal(undefined, rc) + assert.ok(this.connection.results.get('access').msg.length) + done() + }, this.connection) + }) + + it('whitelist', function (done) { + this.connection.remote.ip = '1.1.1.1' + this.connection.remote.host = 'host.example.com' + this.plugin.list.white.conn['host.example.com'] = true + this.plugin.rdns_access((rc) => { + assert.equal(undefined, rc) + assert.ok(this.connection.results.get('access').pass.length) + // assert.ok(this.connection.results.has('access', 'pass', /white/)); + done() + }, this.connection) + }) + + it('blacklist', function (done) { + this.connection.remote.ip = '1.1.1.1' + this.connection.remote.host = 'host.example.com' + this.plugin.list.black.conn['host.example.com'] = true + this.plugin.rdns_access((rc, msg) => { + assert.equal(DENYDISCONNECT, rc) + assert.equal( + 'host.example.com [1.1.1.1] You are not allowed to connect', + msg, + ) + assert.ok(this.connection.results.get('access').fail.length) + done() + }, this.connection) + }) + + it('blacklist regex', function (done) { + this.connection.remote.ip = '1.1.1.1' + this.connection.remote.host = 'host.antispam.com' + const black = ['.*spam.com'] + this.plugin.list_re.black.conn = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.rdns_access((rc, msg) => { + assert.equal(DENYDISCONNECT, rc) + assert.equal( + 'host.antispam.com [1.1.1.1] You are not allowed to connect', + msg, + ) + assert.ok(this.connection.results.get('access').fail.length) + done() + }, this.connection) + }) }) describe('helo_access', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - }) - - it('no list', function (done) { - this.plugin.cfg.check.helo=true; - this.plugin.helo_access((rc) => { - const r = this.connection.results.get('access'); - assert.equal(undefined, rc); - assert.ok(r && r.msg && r.msg.length); - done(); - }, this.connection, 'host.example.com'); - }) - - it('blacklisted regex', function (done) { - const black = [ '.*spam.com' ]; - this.plugin.list_re.black.helo = - new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.cfg.check.helo=true; - this.plugin.helo_access((rc) => { - assert.equal(DENY, rc); - const r = this.connection.results.get('access'); - assert.ok(r && r.fail && r.fail.length); - done(); - }, this.connection, 'bad.spam.com'); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + this.connection = fixtures.connection.createConnection() + }) + + it('no list', function (done) { + this.plugin.cfg.check.helo = true + this.plugin.helo_access( + (rc) => { + const r = this.connection.results.get('access') + assert.equal(undefined, rc) + assert.ok(r && r.msg && r.msg.length) + done() + }, + this.connection, + 'host.example.com', + ) + }) + + it('blacklisted regex', function (done) { + const black = ['.*spam.com'] + this.plugin.list_re.black.helo = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.cfg.check.helo = true + this.plugin.helo_access( + (rc) => { + assert.equal(DENY, rc) + const r = this.connection.results.get('access') + assert.ok(r && r.fail && r.fail.length) + done() + }, + this.connection, + 'bad.spam.com', + ) + }) }) describe('mail_from_access', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - this.connection.init_transaction(); - }) - - it('no lists populated', function (done) { - this.plugin.mail_from_access( (rc) => { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').msg.length); - done(); - }, this.connection, [new Address('')]); - }) - - it('whitelisted addr', function (done) { - this.plugin.list.white.mail['list@harakamail.com']=true; - this.plugin.mail_from_access( (rc) => { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - done(); - }, this.connection, [new Address('')]); - }) - - it('blacklisted addr', function (done) { - this.plugin.list.black.mail['list@badmail.com']=true; - this.plugin.mail_from_access( (rc) => { - assert.equal(DENY, rc); - assert.ok(this.connection.transaction.results.get('access').fail.length); - done(); - }, this.connection, [new Address('')]); - }) - - it('blacklisted domain', function (done) { - const black = [ '.*@spam.com' ]; - this.plugin.list_re.black.mail = new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.mail_from_access( (rc) => { - assert.equal(DENY, rc); - assert.ok(this.connection.transaction.results.get('access').fail.length); - done(); - }, this.connection, [new Address('')]); - }) - - it('blacklisted domain, white addr', function (done) { - this.plugin.list.white.mail['special@spam.com']=true; - const black = [ '.*@spam.com' ]; - this.plugin.list_re.black.mail = new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.mail_from_access( (rc) => { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - done(); - }, this.connection, [new Address('')]); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + this.connection = fixtures.connection.createConnection() + this.connection.init_transaction() + }) + + it('no lists populated', function (done) { + this.plugin.mail_from_access( + (rc) => { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').msg.length) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('whitelisted addr', function (done) { + this.plugin.list.white.mail['list@harakamail.com'] = true + this.plugin.mail_from_access( + (rc) => { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('blacklisted addr', function (done) { + this.plugin.list.black.mail['list@badmail.com'] = true + this.plugin.mail_from_access( + (rc) => { + assert.equal(DENY, rc) + assert.ok(this.connection.transaction.results.get('access').fail.length) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('blacklisted domain', function (done) { + const black = ['.*@spam.com'] + this.plugin.list_re.black.mail = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.mail_from_access( + (rc) => { + assert.equal(DENY, rc) + assert.ok(this.connection.transaction.results.get('access').fail.length) + done() + }, + this.connection, + [new Address('')], + ) + }) + + it('blacklisted domain, white addr', function (done) { + this.plugin.list.white.mail['special@spam.com'] = true + const black = ['.*@spam.com'] + this.plugin.list_re.black.mail = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.mail_from_access( + (rc) => { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + done() + }, + this.connection, + [new Address('')], + ) + }) }) describe('rcpt_to_access', function () { - beforeEach(function () { - this.plugin = new fixtures.plugin('access'); - this.plugin.config = this.plugin.config.module_config(path.resolve(__dirname)); - this.plugin.register(); - this.connection = fixtures.connection.createConnection(); - this.connection.init_transaction(); - }) - - it('no lists populated', function (done) { - const cb = function (rc) { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').msg.length); - done(); - }.bind(this); - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('whitelisted addr', function (done) { - let calls = 0; - const cb = function (rc) { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - if (++calls == 2) { - done(); - } - }.bind(this); - this.plugin.list.white.rcpt['user@example.com']=true; - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('whitelisted addr, accept enabled', function (done) { - const cb = function (rc) { - assert.equal(OK, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - done(); - }.bind(this); - this.plugin.cfg.rcpt.accept=true; - this.plugin.list.white.rcpt['user@example.com']=true; - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('regex whitelisted addr, accept enabled', function (done) { - const cb = function (rc) { - assert.equal(OK, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - done(); - }.bind(this); - this.plugin.cfg.rcpt.accept=true; - this.plugin.list_re.white.rcpt = new RegExp(`^user@example.com$`, 'i'); - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('blacklisted addr', function (done) { - const cb = function (rc) { - assert.equal(DENY, rc); - assert.ok(this.connection.transaction.results.get('access').fail.length); - done(); - }.bind(this); - this.plugin.list.black.rcpt['user@badmail.com']=true; - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('blacklisted domain', function (done) { - const cb = function (rc) { - assert.equal(DENY, rc); - assert.ok(this.connection.transaction.results.get('access').fail.length); - done(); - }.bind(this); - const black = [ '.*@spam.com' ]; - this.plugin.list_re.black.rcpt = new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) - - it('blacklisted domain, white addr', function (done) { - const cb = function (rc) { - assert.equal(undefined, rc); - assert.ok(this.connection.transaction.results.get('access').pass.length); - done(); - }.bind(this); - this.plugin.list.white.rcpt['special@spam.com'] = true; - const black = [ '.*@spam.com' ]; - this.plugin.list_re.black.rcpt = new RegExp(`^(${black.join('|')})$`, 'i'); - this.plugin.rcpt_to_access(cb, this.connection, [new Address('')]); - }) + beforeEach(function () { + this.plugin = new fixtures.plugin('access') + this.plugin.config = this.plugin.config.module_config( + path.resolve(__dirname), + ) + this.plugin.register() + this.connection = fixtures.connection.createConnection() + this.connection.init_transaction() + }) + + it('no lists populated', function (done) { + const cb = function (rc) { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').msg.length) + done() + }.bind(this) + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('whitelisted addr', function (done) { + let calls = 0 + const cb = function (rc) { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + if (++calls == 2) { + done() + } + }.bind(this) + this.plugin.list.white.rcpt['user@example.com'] = true + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('whitelisted addr, accept enabled', function (done) { + const cb = function (rc) { + assert.equal(OK, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + done() + }.bind(this) + this.plugin.cfg.rcpt.accept = true + this.plugin.list.white.rcpt['user@example.com'] = true + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('regex whitelisted addr, accept enabled', function (done) { + const cb = function (rc) { + assert.equal(OK, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + done() + }.bind(this) + this.plugin.cfg.rcpt.accept = true + this.plugin.list_re.white.rcpt = new RegExp(`^user@example.com$`, 'i') + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('blacklisted addr', function (done) { + const cb = function (rc) { + assert.equal(DENY, rc) + assert.ok(this.connection.transaction.results.get('access').fail.length) + done() + }.bind(this) + this.plugin.list.black.rcpt['user@badmail.com'] = true + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('blacklisted domain', function (done) { + const cb = function (rc) { + assert.equal(DENY, rc) + assert.ok(this.connection.transaction.results.get('access').fail.length) + done() + }.bind(this) + const black = ['.*@spam.com'] + this.plugin.list_re.black.rcpt = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) + + it('blacklisted domain, white addr', function (done) { + const cb = function (rc) { + assert.equal(undefined, rc) + assert.ok(this.connection.transaction.results.get('access').pass.length) + done() + }.bind(this) + this.plugin.list.white.rcpt['special@spam.com'] = true + const black = ['.*@spam.com'] + this.plugin.list_re.black.rcpt = new RegExp(`^(${black.join('|')})$`, 'i') + this.plugin.rcpt_to_access(cb, this.connection, [ + new Address(''), + ]) + }) }) From 2cefd8681a42cdcd6d03bdfc86796bfcb3ff3be2 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 11:55:11 -0700 Subject: [PATCH 09/10] cc: adjust config --- .codeclimate.yml | 4 ++-- index.js | 19 +++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 0e0a85a..8b9c8d1 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -18,7 +18,7 @@ checks: threshold: 500 method-lines: config: - threshold: 45 + threshold: 50 method-complexity: config: - threshold: 10 + threshold: 11 diff --git a/index.js b/index.js index b080511..67ae753 100644 --- a/index.js +++ b/index.js @@ -10,18 +10,13 @@ exports.register = function () { this.init_lists() this.load_access_ini() // update with *.ini settings - let p - for (p in this.cfg.white) { - this.load_file('white', p) - } - for (p in this.cfg.black) { - this.load_file('black', p) - } - for (p in this.cfg.re.white) { - this.load_re_file('white', p) - } - for (p in this.cfg.re.black) { - this.load_re_file('black', p) + for (const c of ['black', 'white']) { + for (const p in this.cfg[c]) { + this.load_file(c, p) + } + for (const p in this.cfg.re[c]) { + this.load_re_file(c, p) + } } if (this.cfg.check.conn) { From 6023ff385091f5722f1e72ca846b653b69726033 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Tue, 9 Apr 2024 13:22:39 -0700 Subject: [PATCH 10/10] add CONTRIBUTORS.md --- CONTRIBUTORS.md | 9 +++++++++ package.json | 1 + 2 files changed, 10 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..310ffcf --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,9 @@ + +# Contributors + +This handcrafted artisinal software is brought to you by: + +|
msimerson (37)|
luto (8)|
Dexus (2)|
polarismail (1)| +| :---: | :---: | :---: | :---: | + +created and maintained with [.release](https://github.com/msimerson/.release) diff --git a/package.json b/package.json index b3342ab..d32c8be 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "files": [ "CHANGELOG", + "CONTRIBUTORS", "config" ], "scripts": {