2020-08-31 18:22:33 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2023-08-03 13:38:42 +05:30
|
|
|
testIPv4Config,
|
|
|
|
|
testIPv6Config,
|
|
|
|
|
|
2020-08-31 18:22:33 -07:00
|
|
|
getBlocklist,
|
2023-05-13 14:59:57 +02:00
|
|
|
setBlocklist,
|
2023-08-02 22:53:29 +05:30
|
|
|
|
|
|
|
|
getDynamicDns,
|
|
|
|
|
setDynamicDns,
|
2023-08-03 06:05:29 +05:30
|
|
|
|
|
|
|
|
getIPv4Config,
|
|
|
|
|
setIPv4Config,
|
|
|
|
|
|
|
|
|
|
getIPv6Config,
|
|
|
|
|
setIPv6Config,
|
2023-08-03 13:38:42 +05:30
|
|
|
|
|
|
|
|
getIPv4,
|
|
|
|
|
hasIPv6,
|
2024-04-26 20:36:23 +02:00
|
|
|
getIPv6,
|
|
|
|
|
detectIP
|
2020-08-31 18:22:33 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const assert = require('assert'),
|
|
|
|
|
BoxError = require('./boxerror.js'),
|
2023-08-04 14:13:30 +05:30
|
|
|
constants = require('./constants.js'),
|
2023-08-02 22:53:29 +05:30
|
|
|
cron = require('./cron.js'),
|
2023-08-03 13:38:42 +05:30
|
|
|
fs = require('fs'),
|
2025-05-06 16:16:33 +02:00
|
|
|
ipaddr = require('./ipaddr.js'),
|
2020-08-31 18:22:33 -07:00
|
|
|
path = require('path'),
|
|
|
|
|
paths = require('./paths.js'),
|
|
|
|
|
safe = require('safetydance'),
|
2020-09-02 23:04:42 -07:00
|
|
|
settings = require('./settings.js'),
|
2025-03-07 11:56:47 +01:00
|
|
|
shell = require('./shell.js')('network');
|
2020-08-31 18:22:33 -07:00
|
|
|
|
|
|
|
|
const SET_BLOCKLIST_CMD = path.join(__dirname, 'scripts/setblocklist.sh');
|
|
|
|
|
|
2023-08-03 13:38:42 +05:30
|
|
|
function api(provider) {
|
|
|
|
|
assert.strictEqual(typeof provider, 'string');
|
|
|
|
|
|
|
|
|
|
switch (provider) {
|
|
|
|
|
case 'noop': return require('./network/noop.js');
|
|
|
|
|
case 'fixed': return require('./network/fixed.js');
|
|
|
|
|
case 'network-interface': return require('./network/network-interface.js');
|
|
|
|
|
default: return require('./network/generic.js');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasIPv6() {
|
|
|
|
|
const IPV6_PROC_FILE = '/proc/net/if_inet6';
|
|
|
|
|
// on contabo, /proc/net/if_inet6 is an empty file. so just exists is not enough
|
|
|
|
|
return fs.existsSync(IPV6_PROC_FILE) && fs.readFileSync(IPV6_PROC_FILE, 'utf8').trim().length !== 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function testIPv4Config(config) {
|
|
|
|
|
assert.strictEqual(typeof config, 'object');
|
|
|
|
|
|
|
|
|
|
return await api(config.provider).testIPv4Config(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function testIPv6Config(config) {
|
|
|
|
|
assert.strictEqual(typeof config, 'object');
|
|
|
|
|
|
|
|
|
|
return await api(config.provider).testIPv6Config(config);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-18 15:31:07 -07:00
|
|
|
async function getBlocklist() {
|
2023-08-02 19:17:22 +05:30
|
|
|
const value = await settings.getBlob(settings.FIREWALL_BLOCKLIST_KEY);
|
|
|
|
|
return value ? value.toString('utf8') : '';
|
2020-08-31 18:22:33 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-18 15:31:07 -07:00
|
|
|
async function setBlocklist(blocklist, auditSource) {
|
2020-09-14 10:29:48 -07:00
|
|
|
assert.strictEqual(typeof blocklist, 'string');
|
|
|
|
|
assert.strictEqual(typeof auditSource, 'object');
|
2020-08-31 18:22:33 -07:00
|
|
|
|
2023-12-07 21:52:51 +01:00
|
|
|
let count = 0;
|
2020-09-14 10:29:48 -07:00
|
|
|
for (const line of blocklist.split('\n')) {
|
|
|
|
|
if (!line || line.startsWith('#')) continue;
|
|
|
|
|
const rangeOrIP = line.trim();
|
2025-03-07 11:56:47 +01:00
|
|
|
if (!ipaddr.isValid(rangeOrIP) && !ipaddr.isValidCIDR(rangeOrIP)) throw new BoxError(BoxError.BAD_FIELD, `${rangeOrIP} is not a valid IP or range`);
|
2020-09-02 23:04:42 -07:00
|
|
|
|
2020-09-14 10:29:48 -07:00
|
|
|
if (rangeOrIP.indexOf('/') === -1) {
|
2025-05-06 16:16:33 +02:00
|
|
|
if (ipaddr.isEqual(rangeOrIP, auditSource.ip)) throw new BoxError(BoxError.BAD_FIELD, `IP ${rangeOrIP} is the client IP. Cannot block yourself`);
|
2020-09-14 10:29:48 -07:00
|
|
|
} else {
|
2025-05-06 16:16:33 +02:00
|
|
|
if (ipaddr.includes(rangeOrIP, auditSource.ip)) throw new BoxError(BoxError.BAD_FIELD, `range ${rangeOrIP} includes client IP. Cannot block yourself`);
|
2020-09-14 10:29:48 -07:00
|
|
|
}
|
2023-12-07 21:52:51 +01:00
|
|
|
++count;
|
2020-09-14 10:29:48 -07:00
|
|
|
}
|
2020-08-31 18:22:33 -07:00
|
|
|
|
2023-12-07 22:39:36 +01:00
|
|
|
if (count >= 262144) throw new BoxError(BoxError.CONFLICT, 'Blocklist is too large. Max 262144 entries are allowed'); // see the cloudron-firewall.sh
|
2024-01-13 21:15:41 +01:00
|
|
|
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
2021-05-04 15:21:38 -07:00
|
|
|
|
2023-08-02 19:17:22 +05:30
|
|
|
// store in blob since the value field is TEXT and has 16kb size limit
|
|
|
|
|
await settings.setBlob(settings.FIREWALL_BLOCKLIST_KEY, Buffer.from(blocklist));
|
2020-08-31 18:22:33 -07:00
|
|
|
|
2021-08-18 15:31:07 -07:00
|
|
|
// this is done only because it's easier for the shell script and the firewall service to get the value
|
|
|
|
|
if (!safe.fs.writeFileSync(paths.FIREWALL_BLOCKLIST_FILE, blocklist + '\n', 'utf8')) throw new BoxError(BoxError.FS_ERROR, safe.error.message);
|
2020-08-31 18:22:33 -07:00
|
|
|
|
2024-10-14 19:10:31 +02:00
|
|
|
const [error] = await safe(shell.promises.sudo([ SET_BLOCKLIST_CMD ], {}));
|
2021-08-18 15:31:07 -07:00
|
|
|
if (error) throw new BoxError(BoxError.IPTABLES_ERROR, `Error setting blocklist: ${error.message}`);
|
2020-08-31 18:22:33 -07:00
|
|
|
}
|
2023-08-02 22:53:29 +05:30
|
|
|
|
|
|
|
|
async function getDynamicDns() {
|
|
|
|
|
const enabled = await settings.get(settings.DYNAMIC_DNS_KEY);
|
|
|
|
|
return enabled ? !!enabled : false; // db holds string values only
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function setDynamicDns(enabled) {
|
|
|
|
|
assert.strictEqual(typeof enabled, 'boolean');
|
|
|
|
|
|
|
|
|
|
await settings.set(settings.DYNAMIC_DNS_KEY, enabled ? 'enabled' : ''); // db holds string values only
|
2023-08-04 11:43:39 +05:30
|
|
|
await cron.handleDynamicDnsChanged(enabled);
|
2023-08-02 22:53:29 +05:30
|
|
|
}
|
2023-08-03 06:05:29 +05:30
|
|
|
|
|
|
|
|
async function getIPv4Config() {
|
2023-08-03 11:34:33 +05:30
|
|
|
const value = await settings.getJson(settings.IPV4_CONFIG_KEY);
|
|
|
|
|
return value || { provider: 'generic' };
|
2023-08-03 06:05:29 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function setIPv4Config(ipv4Config) {
|
|
|
|
|
assert.strictEqual(typeof ipv4Config, 'object');
|
|
|
|
|
|
2024-01-13 21:15:41 +01:00
|
|
|
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
2023-08-03 06:05:29 +05:30
|
|
|
|
2023-08-03 13:38:42 +05:30
|
|
|
const error = await testIPv4Config(ipv4Config);
|
2023-08-03 06:05:29 +05:30
|
|
|
if (error) throw error;
|
|
|
|
|
|
2023-08-03 11:34:33 +05:30
|
|
|
await settings.setJson(settings.IPV4_CONFIG_KEY, ipv4Config);
|
2023-08-03 06:05:29 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getIPv6Config() {
|
2023-08-03 11:34:33 +05:30
|
|
|
const value = await settings.getJson(settings.IPV6_CONFIG_KEY);
|
|
|
|
|
return value || { provider: 'noop' };
|
2023-08-03 06:05:29 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function setIPv6Config(ipv6Config) {
|
|
|
|
|
assert.strictEqual(typeof ipv6Config, 'object');
|
|
|
|
|
|
2024-01-13 21:15:41 +01:00
|
|
|
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
2023-08-03 06:05:29 +05:30
|
|
|
|
2023-08-03 13:38:42 +05:30
|
|
|
const error = await testIPv6Config(ipv6Config);
|
2023-08-03 06:05:29 +05:30
|
|
|
if (error) throw error;
|
|
|
|
|
|
2023-08-03 11:34:33 +05:30
|
|
|
await settings.setJson(settings.IPV6_CONFIG_KEY, ipv6Config);
|
2023-08-03 06:05:29 +05:30
|
|
|
}
|
2023-08-03 13:38:42 +05:30
|
|
|
|
|
|
|
|
async function getIPv4() {
|
|
|
|
|
const config = await getIPv4Config();
|
|
|
|
|
|
|
|
|
|
return await api(config.provider).getIPv4(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// returns RFC 5952 formatted address (https://datatracker.ietf.org/doc/html/rfc5952)
|
|
|
|
|
async function getIPv6() {
|
|
|
|
|
const config = await getIPv6Config();
|
|
|
|
|
const result = await api(config.provider).getIPv6(config);
|
|
|
|
|
if (!result) return null;
|
|
|
|
|
return ipaddr.parse(result).toRFC5952String();
|
|
|
|
|
}
|
2024-04-26 20:36:23 +02:00
|
|
|
|
|
|
|
|
async function detectIP() {
|
|
|
|
|
const genericProvider = require('./network/generic.js');
|
|
|
|
|
|
|
|
|
|
const [error4, ipv4] = await safe(genericProvider.getIPv4({}));
|
|
|
|
|
const [error6, ipv6] = await safe(genericProvider.getIPv6({}));
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ipv4: error4 ? null : ipv4,
|
|
|
|
|
ipv6: error6 ? null : ipv6
|
|
|
|
|
};
|
|
|
|
|
}
|