mail: add rbl6
abuse.ch is using spamhaus sorbs is dead
This commit is contained in:
134
src/mail.js
134
src/mail.js
@@ -373,6 +373,18 @@ async function checkDmarc(mailDomain) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function reverseIPv6(ipv6) {
|
||||
const parts = ipv6.split('::');
|
||||
const left = parts[0].split(':');
|
||||
const right = parts[1] ? parts[1].split(':') : [];
|
||||
const fill = new Array(8 - left.length - right.length).fill('0');
|
||||
const full = [...left, ...fill, ...right];
|
||||
const expanded = full.map(part => part.padStart(4, '0')).join('');
|
||||
const reversed = expanded.split('').reverse().join('');
|
||||
const reversedWithDots = reversed.split('').join('.');
|
||||
return reversedWithDots;
|
||||
}
|
||||
|
||||
async function checkPtr6(mailDomain, mailFqdn) {
|
||||
assert.strictEqual(typeof mailDomain, 'object');
|
||||
assert.strictEqual(typeof mailFqdn, 'string');
|
||||
@@ -394,20 +406,8 @@ async function checkPtr6(mailDomain, mailFqdn) {
|
||||
if (error) return Object.assign(result, { status: 'failed', message: error.message });
|
||||
if (ip === null) return Object.assign(result, { status: 'skipped', message: 'PTR6 check was skipped, server has no IPv6' });
|
||||
|
||||
function expandIPv6(ipv6) {
|
||||
const parts = ipv6.split('::');
|
||||
const left = parts[0].split(':');
|
||||
const right = parts[1] ? parts[1].split(':') : [];
|
||||
const fill = new Array(8 - left.length - right.length).fill('0');
|
||||
const full = [...left, ...fill, ...right];
|
||||
return full.map(part => part.padStart(4, '0')).join('');
|
||||
}
|
||||
|
||||
const expanded = expandIPv6(ip);
|
||||
const reversed = expanded.split('').reverse().join('');
|
||||
const reversedWithDots = reversed.split('').join('.');
|
||||
|
||||
result.domain = `${reversedWithDots}.ip6.arpa`;
|
||||
const reversed = reverseIPv6(ip);
|
||||
result.domain = `${reversed}.ip6.arpa`;
|
||||
result.name = ip;
|
||||
|
||||
const [error2, ptrRecords] = await safe(dig.resolve(result.domain, 'PTR', DNS_OPTIONS));
|
||||
@@ -457,60 +457,52 @@ async function checkPtr4(mailDomain, mailFqdn) {
|
||||
// https://raw.githubusercontent.com/jawsome/node-dnsbl/master/list.json https://multirbl.valli.org/list/
|
||||
const RBL_LIST = [
|
||||
{
|
||||
'name': 'Abuse.ch',
|
||||
'dns': 'spam.abuse.ch',
|
||||
'site': 'http://abuse.ch/'
|
||||
},
|
||||
|
||||
{
|
||||
'name': 'Barracuda',
|
||||
'dns': 'b.barracudacentral.org',
|
||||
'site': 'http://www.barracudacentral.org/rbl/removal-request'
|
||||
name: 'Barracuda',
|
||||
dns: 'b.barracudacentral.org',
|
||||
site: 'https://barracudacentral.org/',
|
||||
removal: 'http://www.barracudacentral.org/rbl/removal-request',
|
||||
},
|
||||
{
|
||||
'name': 'Multi SURBL',
|
||||
'dns': 'multi.surbl.org',
|
||||
'site': 'http://www.surbl.org'
|
||||
name: 'Multi SURBL',
|
||||
dns: 'multi.surbl.org',
|
||||
site: 'http://www.surbl.org',
|
||||
removal: 'https://surbl.org/surbl-analysis',
|
||||
},
|
||||
{
|
||||
'name': 'Passive Spam Block List',
|
||||
'dns': 'psbl.surriel.com',
|
||||
'site': 'https://psbl.org'
|
||||
name: 'Passive Spam Block List',
|
||||
dns: 'psbl.surriel.com',
|
||||
site: 'https://psbl.org',
|
||||
removal: 'https://psbl.org',
|
||||
},
|
||||
{
|
||||
'name': 'Sorbs Aggregate Zone',
|
||||
'dns': 'dnsbl.sorbs.net',
|
||||
'site': 'http://dnsbl.sorbs.net/'
|
||||
name: 'SpamCop',
|
||||
dns: 'bl.spamcop.net',
|
||||
site: 'http://spamcop.net',
|
||||
removal: 'https://www.spamcop.net/bl.shtml',
|
||||
},
|
||||
{
|
||||
'name': 'Sorbs spam.dnsbl Zone',
|
||||
'dns': 'spam.dnsbl.sorbs.net',
|
||||
'site': 'http://sorbs.net'
|
||||
name: 'SpamHaus Zen',
|
||||
dns: 'zen.spamhaus.org',
|
||||
site: 'https://www.spamhaus.org/blocklists/zen-blocklist/',
|
||||
removal: 'https://check.spamhaus.org/',
|
||||
ipv6: true
|
||||
},
|
||||
{
|
||||
'name': 'SpamCop',
|
||||
'dns': 'bl.spamcop.net',
|
||||
'site': 'http://spamcop.net'
|
||||
name: 'The Unsubscribe Blacklist(UBL)',
|
||||
dns: 'ubl.unsubscore.com ',
|
||||
site: 'https://blacklist.lashback.com/',
|
||||
removal: 'https://blacklist.lashback.com/',
|
||||
},
|
||||
{
|
||||
'name': 'SpamHaus Zen',
|
||||
'dns': 'zen.spamhaus.org',
|
||||
'site': 'http://spamhaus.org'
|
||||
},
|
||||
{
|
||||
'name': 'The Unsubscribe Blacklist(UBL)',
|
||||
'dns': 'ubl.unsubscore.com ',
|
||||
'site': 'http://www.lashback.com/blacklist/'
|
||||
},
|
||||
{
|
||||
'name': 'UCEPROTECT Network',
|
||||
'dns': 'dnsbl-1.uceprotect.net',
|
||||
'site': 'http://www.uceprotect.net/en'
|
||||
name: 'UCEPROTECT Network',
|
||||
dns: 'dnsbl-1.uceprotect.net', // it has 3 "zones"
|
||||
site: 'http://www.uceprotect.net/en',
|
||||
removal: 'https://www.uceprotect.net/en/index.php?m=7&s=0',
|
||||
}
|
||||
];
|
||||
|
||||
// this function currently only looks for black lists based on IP. TODO: also look up by domain
|
||||
async function checkRbl4(mailDomain) {
|
||||
async function checkRbl(type, mailDomain) {
|
||||
assert.strictEqual(typeof type, 'string');
|
||||
assert.strictEqual(typeof mailDomain, 'object');
|
||||
|
||||
if (mailDomain.relay.provider === 'noop') return { status: 'skipped', message: 'Outbound disabled' };
|
||||
@@ -518,33 +510,38 @@ async function checkRbl4(mailDomain) {
|
||||
|
||||
const { domain } = mailDomain;
|
||||
|
||||
const [error, ip] = await safe(network.getIPv4());
|
||||
if (error) return { status: 'failed', ip: null, servers: [], message: `Unable to determine server IPv4: ${error.message}` };
|
||||
if (ip === null) return { status: 'skipped', ip: null, servers: [], message: 'RBL check was skipped, server has no IPv4' };
|
||||
const [error, ip] = await safe(type === 'ipv4' ? network.getIPv4() : network.getIPv6());
|
||||
if (error) return { status: 'failed', ip: null, servers: [], message: `Unable to determine server ${type}: ${error.message}` };
|
||||
if (ip === null) return { status: 'skipped', ip: null, servers: [], message: `RBL check was skipped, server has no ${type}` };
|
||||
|
||||
const flippedIp = ip.split('.').reverse().join('.');
|
||||
const flippedIp = type === 'ipv4' ? ip.split('.').reverse().join('.') : reverseIPv6(ip);
|
||||
|
||||
// https://tools.ietf.org/html/rfc5782
|
||||
const blockedServers = [];
|
||||
for (const rblServer of RBL_LIST) {
|
||||
const [error, records] = await safe(dig.resolve(flippedIp + '.' + rblServer.dns, 'A', DNS_OPTIONS));
|
||||
if (error || !records) continue; // not listed
|
||||
if (type === 'ipv6' && rblServer[type] !== true) continue; // all support ipv4
|
||||
|
||||
debug(`checkRblStatus: ${domain} (flippedIp: ${flippedIp}) is in the blocklist of ${JSON.stringify(rblServer)}`);
|
||||
const [error, records] = await safe(dig.resolve(`${flippedIp}.${rblServer.dns}`, 'A', DNS_OPTIONS));
|
||||
if (error || !records) continue; // not listed
|
||||
|
||||
debug(`checkRbl: ${domain} flippedIp: ${flippedIp} is in the blocklist of ${rblServer.dns}`);
|
||||
|
||||
const result = Object.assign({}, rblServer);
|
||||
|
||||
const [error2, txtRecords] = await safe(dig.resolve(flippedIp + '.' + rblServer.dns, 'TXT', DNS_OPTIONS));
|
||||
result.txtRecords = error2 || !txtRecords ? 'No txt record' : txtRecords.map(x => x.join(''));
|
||||
const [error2, txtRecords] = await safe(dig.resolve(`${flippedIp}.${rblServer.dns}`, 'TXT', DNS_OPTIONS));
|
||||
result.txtRecords = error2 || !txtRecords ? 'No TXT record' : txtRecords.map(x => x.join(''));
|
||||
|
||||
debug(`checkRblStatus: ${domain} (error: ${error2?.message || null}) (txtRecords: ${JSON.stringify(txtRecords)})`);
|
||||
debug(`checkRbl: ${domain} error: ${error2?.message || null} txtRecords: ${JSON.stringify(txtRecords)}`);
|
||||
|
||||
blockedServers.push(result);
|
||||
}
|
||||
|
||||
debug(`checkRblStatus: ${domain} (ip: ${ip}) blockedServers: ${JSON.stringify(blockedServers)})`);
|
||||
|
||||
return { status: blockedServers.length === 0 ? 'passed' : 'failed', ip, servers: blockedServers };
|
||||
return {
|
||||
status: blockedServers.length === 0 ? 'passed' : 'failed',
|
||||
ip,
|
||||
servers: blockedServers,
|
||||
message: `Check using "host ${flippedIp} 127.0.0.150"`
|
||||
};
|
||||
}
|
||||
|
||||
async function getStatus(domain) {
|
||||
@@ -567,7 +564,8 @@ async function getStatus(domain) {
|
||||
{ what: 'dkim', promise: checkDkim(mailDomain) },
|
||||
{ what: 'ptr4', promise: checkPtr4(mailDomain, fqdn) },
|
||||
{ what: 'ptr6', promise: checkPtr6(mailDomain, fqdn) },
|
||||
{ what: 'rbl4', promise: checkRbl4(mailDomain) },
|
||||
{ what: 'rbl4', promise: checkRbl('ipv4', mailDomain) },
|
||||
{ what: 'rbl6', promise: checkRbl('ipv6', mailDomain) },
|
||||
{ what: 'relay', promise: checkSmtpRelay(mailDomain.relay) }
|
||||
];
|
||||
|
||||
@@ -579,7 +577,7 @@ async function getStatus(domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (response.value.message) debug(`${check.what} : ${response.value.message}`);
|
||||
if (response.value.message) debug(`${check.what} (${domain}): ${response.value.message}`);
|
||||
safe.set(results, checks[i].what, response.value || {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user