|
|
|
'use strict';
|
|
|
|
|
|
|
|
var ip = require('ip');
|
|
|
|
var winston = require('winston');
|
|
|
|
var async = require('async');
|
|
|
|
var db = require('../database');
|
|
|
|
|
|
|
|
var Blacklist = {
|
|
|
|
_rules: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
Blacklist.load = function (callback) {
|
|
|
|
async.waterfall([
|
|
|
|
async.apply(db.get, 'ip-blacklist-rules'),
|
|
|
|
async.apply(Blacklist.validate),
|
|
|
|
], function (err, rules) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Blacklist.save = function (rules, callback) {
|
|
|
|
db.set('ip-blacklist-rules', rules, function (err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
Blacklist.load(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') {
|
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var err = new Error('[[error:blacklisted-ip]]');
|
|
|
|
err.code = 'blacklisted-ip';
|
|
|
|
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
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;
|