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
}
2025-05-06 16:32:11 +02:00
// this won't work in cases where it's a bigger subnet
if ( rangeOrIP . startsWith ( '172.18.' ) || rangeOrIP . toLowerCase ( ) . startsWith ( 'fd00:c107:d509:' ) ) throw new BoxError ( BoxError . BAD _FIELD , ` ${ rangeOrIP } includes internal docker network. This cannot be blocked ` ) ;
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
} ;
}