'use strict'; var ip = require('ip'); var winston = require('winston'); var async = require('async'); var db = require('../database'); var pubsub = require('../pubsub'); var Blacklist = { _rules: [], }; Blacklist.load = function (callback) { callback = callback || function () {}; async.waterfall([ Blacklist.get, Blacklist.validate, function (rules, next) { winston.verbose('[meta/blacklist] Loading ' + rules.valid.length + ' blacklist rules'); if (rules.invalid.length) { winston.warn('[meta/blacklist] ' + rules.invalid.length + ' invalid blacklist rule(s) were ignored.'); } Blacklist._rules = { ipv4: rules.ipv4, ipv6: rules.ipv6, cidr: rules.cidr, }; next(); }, ], callback); }; pubsub.on('blacklist:reload', Blacklist.load); Blacklist.save = function (rules, callback) { async.waterfall([ function (next) { db.set('ip-blacklist-rules', rules, next); }, function (next) { Blacklist.load(next); pubsub.publish('blacklist:reload'); }, ], callback); }; Blacklist.get = function (callback) { db.get('ip-blacklist-rules', callback); }; Blacklist.test = function (clientIp, callback) { if ( Blacklist._rules.ipv4.indexOf(clientIp) === -1 &&// not explicitly specified in ipv4 list Blacklist._rules.ipv6.indexOf(clientIp) === -1 &&// not explicitly specified in ipv6 list !Blacklist._rules.cidr.some(function (subnet) { return ip.cidrSubnet(subnet).contains(clientIp); }) // not in a blacklisted cidr range ) { if (typeof callback === 'function') { setImmediate(callback); } else { return false; } } else { var err = new Error('[[error:blacklisted-ip]]'); err.code = 'blacklisted-ip'; if (typeof callback === 'function') { setImmediate(callback, err); } else { return true; } } }; Blacklist.validate = function (rules, callback) { rules = (rules || '').split('\n'); var ipv4 = []; var ipv6 = []; var cidr = []; var invalid = []; var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/; var inlineCommentMatch = /#.*$/; var whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1']; // Filter out blank lines and lines starting with the hash character (comments) // Also trim inputs and remove inline comments rules = rules.map(function (rule) { rule = rule.replace(inlineCommentMatch, '').trim(); return rule.length && !rule.startsWith('#') ? rule : null; }).filter(Boolean); // Filter out invalid rules rules = rules.filter(function (rule) { if (whitelist.indexOf(rule) !== -1) { invalid.push(rule); return false; } if (ip.isV4Format(rule)) { ipv4.push(rule); return true; } if (ip.isV6Format(rule)) { ipv6.push(rule); return true; } if (isCidrSubnet.test(rule)) { cidr.push(rule); return true; } invalid.push(rule); return false; }); callback(null, { numRules: rules.length + invalid.length, ipv4: ipv4, ipv6: ipv6, cidr: cidr, valid: rules, invalid: invalid, }); }; module.exports = Blacklist;