all files / src/meta/ blacklist.js

18.33% Statements 11/60
0% Branches 0/29
0% Functions 0/10
18.33% Lines 11/60
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                                                                                                                                                                                     
'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]))$/,
		inlineCommentMatch = /#.*$/,
		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;
		} else if (ip.isV6Format(rule)) {
			ipv6.push(rule);
			return true;
		} else if (isCidrSubnet.test(rule)) {
			cidr.push(rule);
			return true;
		} else {
			invalid.push(rule);
			return false;
		}
 
		return true;
	});
 
	callback(null, {
		numRules: rules.length + invalid.length,
		ipv4: ipv4,
		ipv6: ipv6,
		cidr: cidr,
		valid: rules,
		invalid: invalid
	});
};
 
module.exports = Blacklist;