155 lines
4.6 KiB
JavaScript
155 lines
4.6 KiB
JavaScript
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const logger = require('../logger.js');
|
|
const isSpecialPurposeIP = require('../isSpecialPurposeIP.js');
|
|
|
|
const filePath = path.resolve(__dirname, '..', '..', 'whitelist.txt');
|
|
let whitelistedIPs = new Set();
|
|
let whitelistedPorts = new Set(); // @chillcog Port Whitelist
|
|
let reloadTimer = null;
|
|
|
|
const parse = content => {
|
|
// @chillcog Port Whitelist
|
|
const ipSet = new Set();
|
|
const portSet = new Set();
|
|
|
|
const invalidIPs = [], skippedIPs = [];
|
|
const invalidPorts = [], skippedPorts = [];
|
|
|
|
let section = 'ip';
|
|
const lines = content.split('\n');
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const raw = lines[i];
|
|
|
|
const header = raw.trim().toLowerCase();
|
|
if (header.includes('[port whitelist]') || (header.includes('port whitelist') && header.includes('['))) {
|
|
section = 'port';
|
|
continue;
|
|
}
|
|
if (header.includes('[ip whitelist]') || (header.includes('ip whitelist') && header.includes('['))) {
|
|
section = 'ip';
|
|
continue;
|
|
}
|
|
|
|
const line = raw.split('#')[0].trim();
|
|
if (!line) continue;
|
|
|
|
if (section === 'port') {
|
|
const portStr = String(line).split(':').pop().split('/')[0].trim();
|
|
const port = Number(portStr);
|
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
invalidPorts.push(`${line} (line ${i + 1})`);
|
|
continue;
|
|
}
|
|
|
|
if (portSet.has(port)) {
|
|
skippedPorts.push(`${line} (line ${i + 1})`);
|
|
continue;
|
|
}
|
|
|
|
portSet.add(port);
|
|
continue;
|
|
}
|
|
|
|
const { is, range } = isSpecialPurposeIP(line);
|
|
if (range === null) {
|
|
invalidIPs.push(`${line} (line ${i + 1})`);
|
|
continue;
|
|
}
|
|
|
|
if (is) {
|
|
skippedIPs.push(`${line} (${range})`);
|
|
continue;
|
|
}
|
|
|
|
ipSet.add(line);
|
|
}
|
|
|
|
if (invalidIPs.length > 0) {
|
|
logger.warn(`Invalid IP address${invalidIPs.length > 1 ? 'es' : ''} in whitelist, skipping: ${invalidIPs.join(', ')}`);
|
|
}
|
|
|
|
if (skippedIPs.length > 0) {
|
|
logger.warn(`Special-purpose IP${skippedIPs.length > 1 ? 's' : ''} detected in whitelist.txt, was this intentional? ${skippedIPs.join(', ')}`);
|
|
}
|
|
|
|
if (invalidPorts.length > 0) {
|
|
logger.warn(`Invalid port${invalidPorts.length > 1 ? 's' : ''} in whitelist, skipping: ${invalidPorts.join(', ')}`);
|
|
}
|
|
|
|
if (skippedPorts.length > 0) {
|
|
logger.warn(`Skipped duplicate port${skippedPorts.length > 1 ? 's' : ''} in whitelist.txt: ${skippedPorts.join(', ')}`);
|
|
}
|
|
|
|
return { ipSet, portSet };
|
|
};
|
|
|
|
const load = () => {
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const { ipSet, portSet } = parse(content);
|
|
whitelistedIPs = ipSet;
|
|
whitelistedPorts = portSet;
|
|
|
|
if (whitelistedIPs.size > 0) logger.info(`Loaded ${whitelistedIPs.size} whitelisted IP${whitelistedIPs.size > 1 ? 's' : ''} from ${filePath}`);
|
|
if (whitelistedPorts.size > 0) logger.info(`Loaded ${whitelistedPorts.size} whitelisted port${whitelistedPorts.size > 1 ? 's' : ''} from ${filePath}`);
|
|
} catch (err) {
|
|
logger.error(`Failed to load whitelist file: ${err.message}`);
|
|
}
|
|
};
|
|
|
|
const initWhitelist = () => {
|
|
if (!fs.existsSync(filePath)) {
|
|
fs.writeFileSync(
|
|
filePath,
|
|
[
|
|
'# UFW AbuseIPDB Reporter - whitelist.txt',
|
|
'# Changes to this file are detected and applied automatically (no restart required)',
|
|
'',
|
|
'# ====== [IP WHITELIST] ======',
|
|
'# One IP address per line (IPv4 or IPv6)',
|
|
'# Lines starting with # are comments',
|
|
'#',
|
|
'# Examples:',
|
|
'# 79.186.0.0',
|
|
'# 2a01:11bf:4504:b10c:8a32:ffe7:510a:6d4e',
|
|
'',
|
|
'# ====== [PORT WHITELIST] ======',
|
|
'# One destination port per line. Applies to ALL IPs.',
|
|
'# Optional: you may write entries like "22/tcp"; the numeric port will be extracted.',
|
|
'# Added if you have certain services that trigger false positives and you want to exclude',
|
|
'# them from triggering reports regardless of source IP. Use with caution, as this will ignore',
|
|
'# all traffic to the specified port(s) and may cause you to miss legitimate reports if that port is attacked.',
|
|
'# Examples:',
|
|
'# 22',
|
|
'# 25565',
|
|
'# 1194/tcp',
|
|
].join('\n') + '\n'
|
|
);
|
|
logger.info(`Created default whitelist file: ${filePath}`);
|
|
}
|
|
// @chillcog end;
|
|
load();
|
|
|
|
const dir = path.dirname(filePath);
|
|
const filename = path.basename(filePath);
|
|
|
|
try {
|
|
const watcher = fs.watch(dir, (event, file) => {
|
|
if (file !== filename) return;
|
|
clearTimeout(reloadTimer);
|
|
reloadTimer = setTimeout(load, 200);
|
|
});
|
|
watcher.on('error', err => logger.error(`Whitelist watcher error: ${err.message}`));
|
|
} catch (err) {
|
|
logger.error(`Failed to watch whitelist file: ${err.message}`);
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
initWhitelist,
|
|
isWhitelisted: ip => whitelistedIPs.has(ip),
|
|
isPortWhitelisted: port => whitelistedPorts.has(port),
|
|
};
|