Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
return next();
}
// step 1: get a domain name from whatever info is available
var domain = plugin.get_domain(hook, connection, params);
if (!domain) {
connection.logdebug(plugin, "domain detect failed on hook: " + hook);
return next();
}
if (!/\./.test(domain)) {
connection.results.add(plugin, {
fail: 'invalid domain: ' + domain, emit: true});
return next();
}
var org_domain = tlds.get_organizational_domain(domain);
if (!org_domain) {
connection.logerror(plugin, "no org domain from " + domain);
return next();
}
// step 2: check for whitelist
var file = plugin.cfg.domain.any;
var cr = connection.results;
if (plugin.in_list('domain', 'any', '!'+org_domain)) {
cr.add(plugin, {pass: hook +':' + file, whitelist: true, emit: true});
return next();
}
var email;
if (hook === 'mail' || hook === 'rcpt') { email = params[0].address(); }
if (email && plugin.in_list('domain', 'any', '!'+email)) {
if (!plugin.list) { plugin.list = { type: {} }; }
if (!plugin.list[type]) { plugin.list[type] = {}; }
// convert list items to LC at load (much faster than at run time)
for (var i=0; i < list.length; i++) {
if (list[i][0] === '!') { // whitelist entry
plugin.list[type][phase][list[i].toLowerCase()] = true;
continue;
}
if (/@/.test(list[i][0])) { // email address
plugin.list[type][phase][list[i].toLowerCase()] = true;
continue;
}
var d = tlds.get_organizational_domain(list[i]);
if (!d) { continue; }
plugin.list[type][phase][d.toLowerCase()] = true;
}
};
if (net_utils.is_ip_literal(helo)) {
connection.results.add(plugin, {skip: 'valid_hostname(literal)'});
return next();
}
if (!/\./.test(helo)) {
connection.results.add(plugin, {fail: 'valid_hostname(no_dot)'});
if (plugin.cfg.reject.valid_hostname) {
return next(DENY, 'Host names have more than one DNS label');
}
return next();
}
// this will fail if TLD is invalid or hostname is a public suffix
if (!tlds.get_organizational_domain(helo)) {
// Check for any excluded TLDs
const excludes = this.config.get('helo.checks.allow', 'list');
const tld = (helo.split(/\./).reverse())[0].toLowerCase();
// Exclude .local, .lan and .corp
if (tld === 'local' || tld === 'lan' || tld === 'corp' || excludes.includes('.' + tld)) {
return next();
}
connection.results.add(plugin, {fail: 'valid_hostname'});
if (plugin.cfg.reject.valid_hostname) {
return next(DENY, "HELO host name invalid");
}
return next();
}
connection.results.add(plugin, {pass: 'valid_hostname'});
return next();
}
if (!connection.results.has('fcrdns', 'pass', 'fcrdns')) // FcrDNS failed
return chsit(null, 'FcrDNS failed');
if (connection.results.get('fcrdns').ptr_names.length > 1) // multiple PTR returned
return chsit(null, 'multiple PTR returned');
if (connection.results.has('fcrdns', 'fail', /^is_generic/)) // generic/dynamic rDNS record
return chsit(null, 'rDNS is a generic record');
if (connection.results.has('fcrdns', 'fail', /^valid_tld/)) // invalid org domain in rDNS
return chsit(null, 'invalid org domain in rDNS');
// strip first label up until the tld boundary.
const decoupled = tlds.split_hostname(rdns, 3);
const vardom = decoupled[0]; // "variable" portion of domain
const dom = decoupled[1]; // "static" portion of domain
// we check for special cases where rdns looks custom/static, but really is dynamic
const special_case_info = plugin.check_rdns_for_special_cases(rdns, vardom);
if (special_case_info) {
return chsit(null, special_case_info.why);
}
let stripped_dom = dom;
if (vardom) {
// check for decimal IP in rDNS
if (vardom.match(String(net_utils.ip_to_long(ip))))
return chsit(null, 'decimal IP');
exports.is_ip_in_str = function (ip, str) {
if (!str) { return false; }
if (!ip) { return false; }
if (!net.isIPv4(ip)) {
return false; // IPv4 only, for now
}
var host_part = (tlds.split_hostname(str,1))[0].toString();
var octets = ip.split('.');
// See if the 3rd and 4th octets appear in the string
if (this.octets_in_string(host_part, octets[2], octets[3])) {
return true;
}
// then the 1st and 2nd octets
if (this.octets_in_string(host_part, octets[0], octets[1])) {
return true;
}
// Whole IP in hex
var host_part_copy = host_part;
var ip_hex = this.dec_to_hex(this.ip_to_long(ip));
for (var i=0; i<4; i++) {
var part = host_part_copy.indexOf(ip_hex.substring(i*2, (i*2)+2));
if (part === -1) break;
if (net.isIPv6(host)) {
if (/^(?:1|true|yes|enabled|on)$/i.test(lists[zone].not_ipv6_compatible) || /^(?:1|true|yes|enabled|on)$/i.test(lists[zone].no_ip_lookups)) {
results.add(plugin, {skip: `IP (${host}) not supported for ${zone}` });
continue;
}
// Skip any private IPs
if (net_utils.is_private_ip(host)) {
results.add(plugin, {skip: 'private IP' });
continue;
}
// Reverse IP for lookup
lookup = net_utils.ipv6_reverse(host);
}
// Handle zones that require host to be stripped to a domain boundary
else if (/^(?:1|true|yes|enabled|on)$/i.test(lists[zone].strip_to_domain)) {
lookup = (tlds.split_hostname(host, 3))[1];
}
// Anything else..
else {
lookup = host;
}
if (!lookup) continue;
if (!queries[zone]) queries[zone] = {};
if (Object.keys(queries[zone]).length > lists.main.max_uris_per_list) {
connection.logwarn(plugin, `discarding lookup ${lookup} for zone ${zone} maximum query limit reached`);
results.add(plugin, {skip: `max query limit for ${zone}` });
continue;
}
queries[zone][lookup] = 1;
}
}
}
if (!hosts || !hosts.length) {
connection.logdebug(plugin, `(${type}) no items found for lookup`);
results.add(plugin, {skip: type});
return next();
}
connection.logdebug(plugin, `(${type}) found ${hosts.length} items for lookup` );
utils.shuffle(hosts);
let j;
const queries = {};
for (let i=0; i < hosts.length; i++) {
let host = hosts[i].toLowerCase();
connection.logdebug(plugin, `(${type}) checking: ${host}`);
// Make sure we have a valid TLD
if (!net.isIPv4(host) && !net.isIPv6(host) && !tlds.top_level_tlds[(host.split('.').reverse())[0]]) {
continue;
}
// Check the exclusion list
if (check_excludes_list(host)) {
results.add(plugin, {skip: `excluded domain:${host}`});
continue;
}
// Loop through the zones
for (j=0; j < zones.length; j++) {
const zone = zones[j];
if (zone === 'main') continue; // skip config
if (!lists[zone] || (lists[zone] && !/^(?:1|true|yes|enabled|on)$/i.test(lists[zone][type]))) {
results.add(plugin, {skip: `${type} unsupported for ${zone}` });
continue;
}
// Convert in-addr.arpa into bare IPv4/v6 lookup
domains.push(res[1]);
}
if (domains.length === 0) {
connection.loginfo(plugin,
'no domain(s) parsed from Message-ID headers');
transaction.results.add(plugin, { fail: 'Message-ID parseable' });
if (!plugin.cfg.reject.non_local_msgid) return next();
return next(DENY, `bounce with invalid Message-ID, I didn't send it.`);
}
connection.logdebug(plugin, domains);
const valid_domains=[];
for (let j=0; j < domains.length; j++) {
const org_dom = tlds.get_organizational_domain(domains[j]);
if (!org_dom) { continue; }
valid_domains.push(org_dom);
}
if (valid_domains.length === 0) {
transaction.results.add(plugin, { fail: 'Message-ID valid domain' });
if (!plugin.cfg.reject.non_local_msgid) return next();
return next(DENY, `bounce Message-ID without valid domain, I didn't send it.`);
}
return next();
/* The code below needs some kind of test to say the domain isn't local.
this would be hard to do without knowing how you have Haraka configured.
e.g. it could be config/host_list, or it could be some other way.
- hence I added the return next() above or this test can never be correct.
return next();
}
connection.results.add(plugin, {ips});
if (ips.includes(connection.remote.ip)) {
connection.results.add(plugin, {pass: 'forward_dns'});
return next();
}
// some valid hosts (facebook.com, hotmail.com, ) use a generic HELO
// hostname that resolves but doesn't contain the IP that is
// connecting. If their rDNS passed, and their HELO hostname is in
// the same domain, consider it close enough.
if (connection.results.has('helo.checks', 'pass', /^rdns_match/)) {
const helo_od = tlds.get_organizational_domain(helo);
const rdns_od = tlds.get_organizational_domain(connection.remote.host);
if (helo_od && helo_od === rdns_od) {
connection.results.add(plugin, {pass: 'forward_dns(domain)'});
return next();
}
connection.results.add(plugin, {msg: "od miss: " + helo_od + ', ' + rdns_od});
}
connection.results.add(plugin, {fail: 'forward_dns(no IP match)'});
if (plugin.cfg.reject.forward_dns) {
return next(DENY, "HELO host has no forward DNS match");
}
return next();
};
connection.results.add(plugin, {err: 'forward_dns, no ips!'});
return next();
}
connection.results.add(plugin, {ips});
if (ips.includes(connection.remote.ip)) {
connection.results.add(plugin, {pass: 'forward_dns'});
return next();
}
// some valid hosts (facebook.com, hotmail.com, ) use a generic HELO
// hostname that resolves but doesn't contain the IP that is
// connecting. If their rDNS passed, and their HELO hostname is in
// the same domain, consider it close enough.
if (connection.results.has('helo.checks', 'pass', /^rdns_match/)) {
const helo_od = tlds.get_organizational_domain(helo);
const rdns_od = tlds.get_organizational_domain(connection.remote.host);
if (helo_od && helo_od === rdns_od) {
connection.results.add(plugin, {pass: 'forward_dns(domain)'});
return next();
}
connection.results.add(plugin, {msg: "od miss: " + helo_od + ', ' + rdns_od});
}
connection.results.add(plugin, {fail: 'forward_dns(no IP match)'});
if (plugin.cfg.reject.forward_dns) {
return next(DENY, "HELO host has no forward DNS match");
}
return next();
};