Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time.
Cannot retrieve contributors at this time
| 'use strict'; | |
| // Check MAIL FROM domain is resolvable to an MX | |
| var dns = require('dns'); | |
| var net = require('net'); | |
| var net_utils = require('./net_utils'); | |
| exports.register = function () { | |
| this.load_ini(); | |
| }; | |
| exports.load_ini = function () { | |
| var plugin = this; | |
| plugin.cfg = plugin.config.get('mail_from.is_resolvable.ini', { | |
| booleans: [ | |
| '-main.allow_mx_ip', | |
| '+main.reject_no_mx', | |
| ], | |
| }, function () { | |
| plugin.load_ini(); | |
| }); | |
| plugin.re_bogus_ip = new RegExp(plugin.cfg.main.re_bogus_ip || | |
| '^(?:0\\.0\\.0\\.0|255\\.255\\.255\\.255|127\\.)' ); | |
| }; | |
| exports.hook_mail = function(next, connection, params) { | |
| var plugin = this; | |
| var mail_from = params[0]; | |
| var txn = connection.transaction; | |
| var results = txn.results; | |
| // Check for MAIL FROM without an @ first - ignore those here | |
| if (!mail_from.host) { | |
| results.add(plugin, {skip: 'null host'}); | |
| return next(); | |
| } | |
| var called_next = 0; | |
| var domain = mail_from.host; | |
| var c = plugin.cfg.main; | |
| var timeout_id = setTimeout(function () { | |
| // DNS answer didn't return (UDP) | |
| connection.loginfo(plugin, 'timed out resolving MX for ' + domain); | |
| called_next++; | |
| if (txn) results.add(plugin, {err: 'timeout(' + domain + ')'}); | |
| return next(DENYSOFT, 'Temporary resolver error (timeout)'); | |
| }, ((c.timeout) ? c.timeout : 30) * 1000); | |
| var mxDone = function (code, reply) { | |
| if (called_next) return; | |
| clearTimeout(timeout_id); | |
| called_next++; | |
| next(code, reply); | |
| }; | |
| // IS: IPv6 compatible | |
| dns.resolveMx(domain, function(err, addresses) { | |
| if (!txn) return; | |
| if (err && plugin.mxErr(connection, domain, 'MX', err, mxDone)) return; | |
| if (!addresses || !addresses.length) { | |
| // Check for implicit MX 0 record | |
| return plugin.implicit_mx(connection, domain, mxDone); | |
| } | |
| // Verify that the MX records resolve to valid addresses | |
| var records = {}; | |
| var pending_queries = 0; | |
| function check_results () { | |
| if (pending_queries !== 0) return; | |
| records = Object.keys(records); | |
| if (records && records.length) { | |
| connection.logdebug(plugin, domain + ': ' + records); | |
| results.add(plugin, {pass: 'has_fwd_dns'}); | |
| return mxDone(); | |
| } | |
| results.add(plugin, {fail: 'has_fwd_dns'}); | |
| return mxDone(((c.reject_no_mx) ? DENY : DENYSOFT), | |
| 'MX without A/AAAA records'); | |
| } | |
| addresses.forEach(function (addr) { | |
| // Handle MX records that are IP addresses | |
| // This is invalid - but a lot of MTAs allow it. | |
| if (net_utils.get_ipany_re('^\\[','\\]$','').test(addr.exchange)) { | |
| connection.logwarn(plugin, domain + ': invalid MX ' + | |
| addr.exchange); | |
| if (c.allow_mx_ip) { | |
| records[addr.exchange] = 1; | |
| } | |
| return; | |
| } | |
| pending_queries++; | |
| net_utils.get_ips_by_host(addr.exchange, function(err2, addresses2) { | |
| pending_queries--; | |
| if (!txn) return; | |
| if (err2 && err2.length === 2) { | |
| results.add(plugin, {msg: err2[0]}); | |
| connection.logdebug(plugin, domain + ': MX ' + | |
| addr.priority + ' ' + addr.exchange + | |
| ' => ' + err2[0]); | |
| check_results(); | |
| return; | |
| } | |
| connection.logdebug(plugin, domain + ': MX ' + addr.priority + | |
| ' ' + addr.exchange + ' => ' + addresses2); | |
| for (var i=0; i < addresses2.length; i++) { | |
| // Ignore anything obviously bogus | |
| if (net.isIPv4(addresses2[i])){ | |
| if (plugin.re_bogus_ip.test(addresses2[i])) { | |
| connection.logdebug(plugin, addr.exchange + | |
| ': discarding ' + addresses2[i]); | |
| continue; | |
| } | |
| } | |
| if (net.isIPv6(addresses2[i])){ | |
| if (net_utils.ipv6_bogus(addresses2[i])) { | |
| connection.logdebug(plugin, addr.exchange + | |
| ': discarding ' + addresses2[i]); | |
| continue; | |
| } | |
| } | |
| records[addresses2[i]] = 1; | |
| } | |
| check_results(); | |
| }); | |
| }); | |
| // In case we don't run any queries | |
| check_results(); | |
| }); | |
| }; | |
| exports.mxErr = function (connection, domain, type, err, mxDone) { | |
| var plugin = this; | |
| var txn = connection.transaction; | |
| if (!txn) return; | |
| txn.results.add(plugin, {msg: domain + ':' + type + ':' + err.message}); | |
| connection.logdebug(plugin, domain + ':' + type + ' => ' + err.message); | |
| switch (err.code) { | |
| case 'NXDOMAIN': | |
| case 'ENOTFOUND': | |
| case 'ENODATA': | |
| // Ignore | |
| break; | |
| default: | |
| mxDone(DENYSOFT, 'Temp. resolver error (' + err.code + ')'); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| // IS: IPv6 compatible | |
| exports.implicit_mx = function (connection, domain, mxDone) { | |
| var plugin = this; | |
| var txn = connection.transaction; | |
| net_utils.get_ips_by_host(domain, function(err, addresses) { | |
| if (!txn) return; | |
| if (!addresses || !addresses.length) { | |
| txn.results.add(plugin, {fail: 'has_fwd_dns'}); | |
| return mxDone(((plugin.cfg.main.reject_no_mx) ? DENY : DENYSOFT), | |
| 'No MX for your FROM address'); | |
| } | |
| connection.logdebug(plugin, domain + ': A/AAAA => ' + addresses); | |
| var records = {}; | |
| for (var i=0; i < addresses.length; i++) { | |
| var addr = addresses[i]; | |
| // Ignore anything obviously bogus | |
| if (net.isIPv4(addr)){ | |
| if (plugin.re_bogus_ip.test(addr)) { | |
| connection.logdebug(plugin, domain + ': discarding ' + addr); | |
| continue; | |
| } | |
| } | |
| if (net.isIPv6(addr)){ | |
| if (net_utils.ipv6_bogus(addr)) { | |
| connection.logdebug(plugin, domain + ': discarding ' + addr); | |
| continue; | |
| } | |
| } | |
| records[addr] = true; | |
| } | |
| records = Object.keys(records); | |
| if (records && records.length) { | |
| txn.results.add(plugin, {pass: 'implicit_mx'}); | |
| return mxDone(); | |
| } | |
| txn.results.add(plugin, {fail: 'implicit_mx('+domain+')'}); | |
| return mxDone(); | |
| }); | |
| }; |