core: add IP-range based rules for permissions and fifficulty
This commit is contained in:
11
README.md
11
README.md
@@ -12,13 +12,22 @@ AI scrapers are everywhere. This will stop them. `robots.txt` won't.
|
||||
- Protect your endpoint from AI bots with a cryptographic challenge
|
||||
- Easy configuration in jsonc
|
||||
- Support for cloudflare
|
||||
- Support for IP-Range based rules (both ipv4 and ipv6)
|
||||
- Minimal. The waiting page is tiny and light on network usage.
|
||||
|
||||
### Planned features
|
||||
- Dynamic challenge amount (aka difficulty)
|
||||
- Dynamic challenge amount (aka difficulty) based on traffic
|
||||
- Detection of token overuse
|
||||
- Better wait screen
|
||||
- Better git integration (it's quite rudimentary right now)
|
||||
|
||||
## Caveats
|
||||
If you are using this, it's almost certain search engines will stop indexing your site. Keep this in mind.
|
||||
|
||||
## Setup guide
|
||||
|
||||
1. Clone and build this repo. You will need `openssl`, `g++>=12`, and deps for `pistache`, `fmt` and `tinylates`.
|
||||
2. Create a `config.jsonc`. An example one is in `example/`.
|
||||
3. Adjust the config to your needs. Options are documented with comments in the example config.
|
||||
4. Set up your IP rules if you want. These allow you to set up IPs that are automatically blocked, or allowed to access without a challenge. This is useful for e.g. search engine scrapers. Some IP ranges can be found in `example/index_bots.jsonc`.
|
||||
5. Run checkpoint with your config: `./build/checkpoint -c config.jsonc`. How you run it long-term as a service is up to you.
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
// where the html files are located
|
||||
"html_dir": "./html",
|
||||
|
||||
// where the proxy should store its db (directory)
|
||||
// where the proxy should store its private signing key (directory)
|
||||
"data_dir": "./data",
|
||||
|
||||
// what port the proxy should listen on
|
||||
"port": 3001,
|
||||
|
||||
// what address should the proxy pass after successful verification
|
||||
// what address should the proxy pass after successful verification. DO NOT add a / at the end or http(s)://.
|
||||
// this address should be local (127.0.0.1). Other configurations are not supported.
|
||||
"forward_address": "127.0.0.1:3000",
|
||||
|
||||
// max request size of 10MB
|
||||
@@ -18,5 +19,21 @@
|
||||
"proxy_timeout_sec": 120,
|
||||
|
||||
// enables (a lot) more logging
|
||||
"trace_logging": false
|
||||
"trace_logging": false,
|
||||
|
||||
// the default difficulty for the challenge. 4 takes less than a second on a powerful desktop, and up to 15s on a low-powered phone.
|
||||
// 5 is 16x slower.
|
||||
// NOT recommended to set to anything below 4 or above 5.
|
||||
"default_challenge_difficulty": 4,
|
||||
|
||||
// specific ip range configs.
|
||||
"ip_configs": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"ip_ranges": [
|
||||
"127.0.0.1/24",
|
||||
"::1/128"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
311
example/index_bots.jsonc
Normal file
311
example/index_bots.jsonc
Normal file
@@ -0,0 +1,311 @@
|
||||
// This jsonc file contains some known web crawlers used by search engines.
|
||||
// You may pick and choose whichever you like.
|
||||
[
|
||||
// Google, gathered 15 IV 2025
|
||||
{
|
||||
"action": "Allow",
|
||||
"ip_ranges": [
|
||||
"2001:4860:4801:10::/64",
|
||||
"2001:4860:4801:11::/64",
|
||||
"2001:4860:4801:12::/64",
|
||||
"2001:4860:4801:13::/64",
|
||||
"2001:4860:4801:14::/64",
|
||||
"2001:4860:4801:15::/64",
|
||||
"2001:4860:4801:16::/64",
|
||||
"2001:4860:4801:17::/64",
|
||||
"2001:4860:4801:18::/64",
|
||||
"2001:4860:4801:19::/64",
|
||||
"2001:4860:4801:1a::/64",
|
||||
"2001:4860:4801:1b::/64",
|
||||
"2001:4860:4801:1c::/64",
|
||||
"2001:4860:4801:1d::/64",
|
||||
"2001:4860:4801:1e::/64",
|
||||
"2001:4860:4801:1f::/64",
|
||||
"2001:4860:4801:20::/64",
|
||||
"2001:4860:4801:21::/64",
|
||||
"2001:4860:4801:22::/64",
|
||||
"2001:4860:4801:23::/64",
|
||||
"2001:4860:4801:24::/64",
|
||||
"2001:4860:4801:25::/64",
|
||||
"2001:4860:4801:26::/64",
|
||||
"2001:4860:4801:27::/64",
|
||||
"2001:4860:4801:28::/64",
|
||||
"2001:4860:4801:29::/64",
|
||||
"2001:4860:4801:2::/64",
|
||||
"2001:4860:4801:2a::/64",
|
||||
"2001:4860:4801:2b::/64",
|
||||
"2001:4860:4801:2c::/64",
|
||||
"2001:4860:4801:2d::/64",
|
||||
"2001:4860:4801:2e::/64",
|
||||
"2001:4860:4801:2f::/64",
|
||||
"2001:4860:4801:31::/64",
|
||||
"2001:4860:4801:32::/64",
|
||||
"2001:4860:4801:33::/64",
|
||||
"2001:4860:4801:34::/64",
|
||||
"2001:4860:4801:35::/64",
|
||||
"2001:4860:4801:36::/64",
|
||||
"2001:4860:4801:37::/64",
|
||||
"2001:4860:4801:38::/64",
|
||||
"2001:4860:4801:39::/64",
|
||||
"2001:4860:4801:3a::/64",
|
||||
"2001:4860:4801:3b::/64",
|
||||
"2001:4860:4801:3c::/64",
|
||||
"2001:4860:4801:3d::/64",
|
||||
"2001:4860:4801:3e::/64",
|
||||
"2001:4860:4801:40::/64",
|
||||
"2001:4860:4801:41::/64",
|
||||
"2001:4860:4801:42::/64",
|
||||
"2001:4860:4801:43::/64",
|
||||
"2001:4860:4801:44::/64",
|
||||
"2001:4860:4801:45::/64",
|
||||
"2001:4860:4801:46::/64",
|
||||
"2001:4860:4801:47::/64",
|
||||
"2001:4860:4801:48::/64",
|
||||
"2001:4860:4801:49::/64",
|
||||
"2001:4860:4801:4a::/64",
|
||||
"2001:4860:4801:4b::/64",
|
||||
"2001:4860:4801:4c::/64",
|
||||
"2001:4860:4801:50::/64",
|
||||
"2001:4860:4801:51::/64",
|
||||
"2001:4860:4801:52::/64",
|
||||
"2001:4860:4801:53::/64",
|
||||
"2001:4860:4801:54::/64",
|
||||
"2001:4860:4801:55::/64",
|
||||
"2001:4860:4801:56::/64",
|
||||
"2001:4860:4801:60::/64",
|
||||
"2001:4860:4801:61::/64",
|
||||
"2001:4860:4801:62::/64",
|
||||
"2001:4860:4801:63::/64",
|
||||
"2001:4860:4801:64::/64",
|
||||
"2001:4860:4801:65::/64",
|
||||
"2001:4860:4801:66::/64",
|
||||
"2001:4860:4801:67::/64",
|
||||
"2001:4860:4801:68::/64",
|
||||
"2001:4860:4801:69::/64",
|
||||
"2001:4860:4801:6a::/64",
|
||||
"2001:4860:4801:6b::/64",
|
||||
"2001:4860:4801:6c::/64",
|
||||
"2001:4860:4801:6d::/64",
|
||||
"2001:4860:4801:6e::/64",
|
||||
"2001:4860:4801:6f::/64",
|
||||
"2001:4860:4801:70::/64",
|
||||
"2001:4860:4801:71::/64",
|
||||
"2001:4860:4801:72::/64",
|
||||
"2001:4860:4801:73::/64",
|
||||
"2001:4860:4801:74::/64",
|
||||
"2001:4860:4801:75::/64",
|
||||
"2001:4860:4801:76::/64",
|
||||
"2001:4860:4801:77::/64",
|
||||
"2001:4860:4801:78::/64",
|
||||
"2001:4860:4801:79::/64",
|
||||
"2001:4860:4801:80::/64",
|
||||
"2001:4860:4801:81::/64",
|
||||
"2001:4860:4801:82::/64",
|
||||
"2001:4860:4801:83::/64",
|
||||
"2001:4860:4801:84::/64",
|
||||
"2001:4860:4801:85::/64",
|
||||
"2001:4860:4801:86::/64",
|
||||
"2001:4860:4801:87::/64",
|
||||
"2001:4860:4801:88::/64",
|
||||
"2001:4860:4801:90::/64",
|
||||
"2001:4860:4801:91::/64",
|
||||
"2001:4860:4801:92::/64",
|
||||
"2001:4860:4801:93::/64",
|
||||
"2001:4860:4801:94::/64",
|
||||
"2001:4860:4801:95::/64",
|
||||
"2001:4860:4801:96::/64",
|
||||
"2001:4860:4801:a0::/64",
|
||||
"2001:4860:4801:a1::/64",
|
||||
"2001:4860:4801:a2::/64",
|
||||
"2001:4860:4801:a3::/64",
|
||||
"2001:4860:4801:a4::/64",
|
||||
"2001:4860:4801:a5::/64",
|
||||
"2001:4860:4801:c::/64",
|
||||
"2001:4860:4801:f::/64",
|
||||
"192.178.5.0/27",
|
||||
"192.178.6.0/27",
|
||||
"192.178.6.128/27",
|
||||
"192.178.6.160/27",
|
||||
"192.178.6.192/27",
|
||||
"192.178.6.32/27",
|
||||
"192.178.6.64/27",
|
||||
"192.178.6.96/27",
|
||||
"34.100.182.96/28",
|
||||
"34.101.50.144/28",
|
||||
"34.118.254.0/28",
|
||||
"34.118.66.0/28",
|
||||
"34.126.178.96/28",
|
||||
"34.146.150.144/28",
|
||||
"34.147.110.144/28",
|
||||
"34.151.74.144/28",
|
||||
"34.152.50.64/28",
|
||||
"34.154.114.144/28",
|
||||
"34.155.98.32/28",
|
||||
"34.165.18.176/28",
|
||||
"34.175.160.64/28",
|
||||
"34.176.130.16/28",
|
||||
"34.22.85.0/27",
|
||||
"34.64.82.64/28",
|
||||
"34.65.242.112/28",
|
||||
"34.80.50.80/28",
|
||||
"34.88.194.0/28",
|
||||
"34.89.10.80/28",
|
||||
"34.89.198.80/28",
|
||||
"34.96.162.48/28",
|
||||
"35.247.243.240/28",
|
||||
"66.249.64.0/27",
|
||||
"66.249.64.128/27",
|
||||
"66.249.64.160/27",
|
||||
"66.249.64.224/27",
|
||||
"66.249.64.32/27",
|
||||
"66.249.64.64/27",
|
||||
"66.249.64.96/27",
|
||||
"66.249.65.0/27",
|
||||
"66.249.65.128/27",
|
||||
"66.249.65.160/27",
|
||||
"66.249.65.192/27",
|
||||
"66.249.65.224/27",
|
||||
"66.249.65.32/27",
|
||||
"66.249.65.64/27",
|
||||
"66.249.65.96/27",
|
||||
"66.249.66.0/27",
|
||||
"66.249.66.128/27",
|
||||
"66.249.66.160/27",
|
||||
"66.249.66.192/27",
|
||||
"66.249.66.224/27",
|
||||
"66.249.66.32/27",
|
||||
"66.249.66.64/27",
|
||||
"66.249.66.96/27",
|
||||
"66.249.68.0/27",
|
||||
"66.249.68.128/27",
|
||||
"66.249.68.32/27",
|
||||
"66.249.68.64/27",
|
||||
"66.249.68.96/27",
|
||||
"66.249.69.0/27",
|
||||
"66.249.69.128/27",
|
||||
"66.249.69.160/27",
|
||||
"66.249.69.192/27",
|
||||
"66.249.69.224/27",
|
||||
"66.249.69.32/27",
|
||||
"66.249.69.64/27",
|
||||
"66.249.69.96/27",
|
||||
"66.249.70.0/27",
|
||||
"66.249.70.128/27",
|
||||
"66.249.70.160/27",
|
||||
"66.249.70.192/27",
|
||||
"66.249.70.224/27",
|
||||
"66.249.70.32/27",
|
||||
"66.249.70.64/27",
|
||||
"66.249.70.96/27",
|
||||
"66.249.71.0/27",
|
||||
"66.249.71.128/27",
|
||||
"66.249.71.160/27",
|
||||
"66.249.71.192/27",
|
||||
"66.249.71.224/27",
|
||||
"66.249.71.32/27",
|
||||
"66.249.71.64/27",
|
||||
"66.249.71.96/27",
|
||||
"66.249.72.0/27",
|
||||
"66.249.72.128/27",
|
||||
"66.249.72.160/27",
|
||||
"66.249.72.192/27",
|
||||
"66.249.72.224/27",
|
||||
"66.249.72.32/27",
|
||||
"66.249.72.64/27",
|
||||
"66.249.72.96/27",
|
||||
"66.249.73.0/27",
|
||||
"66.249.73.128/27",
|
||||
"66.249.73.160/27",
|
||||
"66.249.73.192/27",
|
||||
"66.249.73.224/27",
|
||||
"66.249.73.32/27",
|
||||
"66.249.73.64/27",
|
||||
"66.249.73.96/27",
|
||||
"66.249.74.0/27",
|
||||
"66.249.74.128/27",
|
||||
"66.249.74.160/27",
|
||||
"66.249.74.192/27",
|
||||
"66.249.74.32/27",
|
||||
"66.249.74.64/27",
|
||||
"66.249.74.96/27",
|
||||
"66.249.75.0/27",
|
||||
"66.249.75.128/27",
|
||||
"66.249.75.160/27",
|
||||
"66.249.75.192/27",
|
||||
"66.249.75.224/27",
|
||||
"66.249.75.32/27",
|
||||
"66.249.75.64/27",
|
||||
"66.249.75.96/27",
|
||||
"66.249.76.0/27",
|
||||
"66.249.76.128/27",
|
||||
"66.249.76.160/27",
|
||||
"66.249.76.192/27",
|
||||
"66.249.76.224/27",
|
||||
"66.249.76.32/27",
|
||||
"66.249.76.64/27",
|
||||
"66.249.76.96/27",
|
||||
"66.249.77.0/27",
|
||||
"66.249.77.128/27",
|
||||
"66.249.77.160/27",
|
||||
"66.249.77.192/27",
|
||||
"66.249.77.224/27",
|
||||
"66.249.77.32/27",
|
||||
"66.249.77.64/27",
|
||||
"66.249.77.96/27",
|
||||
"66.249.78.0/27",
|
||||
"66.249.78.32/27",
|
||||
"66.249.79.0/27",
|
||||
"66.249.79.128/27",
|
||||
"66.249.79.160/27",
|
||||
"66.249.79.192/27",
|
||||
"66.249.79.224/27",
|
||||
"66.249.79.32/27",
|
||||
"66.249.79.64/27",
|
||||
"66.249.79.96/27"
|
||||
]
|
||||
},
|
||||
// Bing, retrieved 15 IV 2025
|
||||
{
|
||||
"action": "Allow",
|
||||
"ip_ranges": [
|
||||
"157.55.39.0/24",
|
||||
"207.46.13.0/24",
|
||||
"40.77.167.0/24",
|
||||
"13.66.139.0/24",
|
||||
"13.66.144.0/24",
|
||||
"52.167.144.0/24",
|
||||
"13.67.10.16/28",
|
||||
"13.69.66.240/28",
|
||||
"13.71.172.224/28",
|
||||
"139.217.52.0/28",
|
||||
"191.233.204.224/28",
|
||||
"20.36.108.32/28",
|
||||
"20.43.120.16/28",
|
||||
"40.79.131.208/28",
|
||||
"40.79.186.176/28",
|
||||
"52.231.148.0/28",
|
||||
"20.79.107.240/28",
|
||||
"51.105.67.0/28",
|
||||
"20.125.163.80/28",
|
||||
"40.77.188.0/22",
|
||||
"65.55.210.0/24",
|
||||
"199.30.24.0/23",
|
||||
"40.77.202.0/24",
|
||||
"40.77.139.0/25",
|
||||
"20.74.197.0/28",
|
||||
"20.15.133.160/27",
|
||||
"40.77.177.0/24",
|
||||
"40.77.178.0/23"
|
||||
]
|
||||
},
|
||||
{
|
||||
// Kagi, retrieved 15 IV 2025
|
||||
"action": "Allow",
|
||||
"ip_ranges": [
|
||||
"216.18.205.234/32",
|
||||
"35.212.27.76/32",
|
||||
"104.254.65.50/32",
|
||||
"209.151.156.194/32"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -5,6 +5,18 @@
|
||||
#include "../helpers/FsUtils.hpp"
|
||||
#include "../GlobalState.hpp"
|
||||
|
||||
static CConfig::eConfigIPAction strToAction(const std::string& s) {
|
||||
// TODO: allow any case I'm lazy it's 1am
|
||||
if (s == "ALLOW" || s == "allow" || s == "Allow")
|
||||
return CConfig::IP_ACTION_ALLOW;
|
||||
if (s == "Deny" || s == "deny" || s == "Deny")
|
||||
return CConfig::IP_ACTION_DENY;
|
||||
if (s == "CHALLENGE" || s == "challenge" || s == "Challenge")
|
||||
return CConfig::IP_ACTION_CHALLENGE;
|
||||
|
||||
throw std::runtime_error("Invalid ip config action");
|
||||
}
|
||||
|
||||
CConfig::CConfig() {
|
||||
auto json = glz::read_jsonc<SConfig>(
|
||||
NFsUtils::readFileAsString(NFsUtils::isAbsolute(g_pGlobalState->configPath) ? g_pGlobalState->configPath : g_pGlobalState->cwd + "/" + g_pGlobalState->configPath).value());
|
||||
@@ -13,4 +25,17 @@ CConfig::CConfig() {
|
||||
throw std::runtime_error("No config / bad config format");
|
||||
|
||||
m_config = json.value();
|
||||
|
||||
// parse some datas
|
||||
for (const auto& ic : m_config.ip_configs) {
|
||||
SIPRangeConfigParsed parsed;
|
||||
parsed.action = strToAction(ic.action);
|
||||
parsed.difficulty = ic.difficulty;
|
||||
|
||||
for (const auto& ir : ic.ip_ranges) {
|
||||
parsed.ip_ranges.emplace_back(CIPRange(ir));
|
||||
}
|
||||
|
||||
m_parsedConfigDatas.ip_configs.emplace_back(std::move(parsed));
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,46 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "IPRange.hpp"
|
||||
|
||||
class CConfig {
|
||||
public:
|
||||
CConfig();
|
||||
|
||||
enum eConfigIPAction : uint8_t {
|
||||
IP_ACTION_DENY = 0,
|
||||
IP_ACTION_ALLOW,
|
||||
IP_ACTION_CHALLENGE
|
||||
};
|
||||
|
||||
struct SIPRangeConfig {
|
||||
std::string action = "";
|
||||
std::vector<std::string> ip_ranges;
|
||||
int difficulty = -1;
|
||||
};
|
||||
|
||||
struct SIPRangeConfigParsed {
|
||||
eConfigIPAction action = IP_ACTION_DENY;
|
||||
std::vector<CIPRange> ip_ranges;
|
||||
int difficulty = -1;
|
||||
};
|
||||
|
||||
struct SConfig {
|
||||
int port = 3001;
|
||||
std::string forward_address = "127.0.0.1:3000";
|
||||
std::string data_dir = "";
|
||||
std::string html_dir = "";
|
||||
unsigned long int max_request_size = 10000000; // 10MB
|
||||
bool git_host = false;
|
||||
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
||||
bool trace_logging = false;
|
||||
int port = 3001;
|
||||
std::string forward_address = "127.0.0.1:3000";
|
||||
std::string data_dir = "";
|
||||
std::string html_dir = "";
|
||||
unsigned long int max_request_size = 10000000; // 10MB
|
||||
bool git_host = false;
|
||||
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
||||
bool trace_logging = false;
|
||||
std::vector<SIPRangeConfig> ip_configs;
|
||||
int default_challenge_difficulty = 4;
|
||||
} m_config;
|
||||
|
||||
struct {
|
||||
std::vector<SIPRangeConfigParsed> ip_configs;
|
||||
} m_parsedConfigDatas;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CConfig> g_pConfig;
|
||||
129
src/config/IPRange.cpp
Normal file
129
src/config/IPRange.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "IPRange.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
CIP::CIP(const std::string& ip) {
|
||||
if (std::count(ip.begin(), ip.end(), '.') == 3)
|
||||
parseV4(ip);
|
||||
else if (std::count(ip.begin(), ip.end(), ':') >= 2)
|
||||
parseV6(ip);
|
||||
else
|
||||
throw std::runtime_error("IP not valid");
|
||||
}
|
||||
|
||||
void CIP::parseV4(const std::string& ip) {
|
||||
m_v6 = false;
|
||||
|
||||
std::string_view curr;
|
||||
size_t lastPos = 0;
|
||||
auto advance = [&]() {
|
||||
size_t prev = lastPos ? lastPos + 1 : lastPos;
|
||||
lastPos = ip.find('.', prev);
|
||||
|
||||
if (lastPos == std::string::npos)
|
||||
curr = std::string_view{ip}.substr(prev);
|
||||
else
|
||||
curr = std::string_view{ip}.substr(prev, lastPos - prev);
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
advance();
|
||||
m_blocks.push_back(std::stoul(std::string{curr}));
|
||||
|
||||
if (m_blocks.back() > 0xFF)
|
||||
throw std::runtime_error("Invalid IPv4 byte");
|
||||
}
|
||||
}
|
||||
|
||||
void CIP::parseV6(const std::string& ip) {
|
||||
const auto COLONS = std::count(ip.begin(), ip.end(), ':');
|
||||
|
||||
m_v6 = true;
|
||||
|
||||
std::string_view curr;
|
||||
size_t lastPos = 0;
|
||||
bool first = true;
|
||||
auto advance = [&]() {
|
||||
size_t prev = !first ? lastPos + 1 : lastPos;
|
||||
lastPos = ip.find(':', prev);
|
||||
|
||||
if (lastPos == std::string::npos)
|
||||
curr = std::string_view{ip}.substr(prev);
|
||||
else
|
||||
curr = std::string_view{ip}.substr(prev, lastPos - prev);
|
||||
|
||||
first = false;
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
advance();
|
||||
if (curr.empty()) {
|
||||
for (size_t j = 0; j < 8 - COLONS; ++j) {
|
||||
i++;
|
||||
m_blocks.push_back(0);
|
||||
}
|
||||
|
||||
if (ip.starts_with("::") || ip.ends_with("::")) {
|
||||
m_blocks.push_back(0);
|
||||
advance();
|
||||
} else
|
||||
i--;
|
||||
continue;
|
||||
} else
|
||||
m_blocks.push_back(std::stoul(std::string{curr}, nullptr, 16));
|
||||
|
||||
if (m_blocks.back() > 0xFFFF)
|
||||
throw std::runtime_error("Invalid IPv6 byte");
|
||||
}
|
||||
}
|
||||
|
||||
CIPRange::CIPRange(const std::string& range) {
|
||||
if (!range.contains('/'))
|
||||
throw std::runtime_error("Range has no subnet");
|
||||
|
||||
m_subnet = std::stoul(range.substr(range.find('/') + 1));
|
||||
|
||||
m_ip = CIP(range.substr(0, range.find('/')));
|
||||
}
|
||||
|
||||
bool CIPRange::ipMatches(const CIP& ip) const {
|
||||
if (m_ip.m_v6 != ip.m_v6)
|
||||
return false;
|
||||
|
||||
if (m_ip.m_v6)
|
||||
return ipMatchesV6(ip);
|
||||
return ipMatchesV4(ip);
|
||||
}
|
||||
|
||||
bool CIPRange::ipMatchesV4(const CIP& ip) const {
|
||||
uint32_t rangeMask = 0xFFFFFFFF << (32 - m_subnet);
|
||||
uint32_t rangeIP =
|
||||
(((uint32_t)m_ip.m_blocks.at(0)) << 24) | (((uint32_t)m_ip.m_blocks.at(1)) << 16) | (((uint32_t)m_ip.m_blocks.at(2)) << 8) | (((uint32_t)m_ip.m_blocks.at(3)) << 0);
|
||||
uint32_t incomingIP =
|
||||
(((uint32_t)ip.m_blocks.at(0)) << 24) | (((uint32_t)ip.m_blocks.at(1)) << 16) | (((uint32_t)ip.m_blocks.at(2)) << 8) | (((uint32_t)ip.m_blocks.at(3)) << 0);
|
||||
|
||||
return (rangeMask & rangeIP) == (rangeMask & incomingIP);
|
||||
}
|
||||
|
||||
bool CIPRange::ipMatchesV6(const CIP& ip) const {
|
||||
uint64_t rangeMaskLeft = 0xFFFFFFFFFFFFFFFF << (m_subnet > 64 ? 0 : 64 - m_subnet);
|
||||
uint64_t rangeIPLeft =
|
||||
(((uint64_t)m_ip.m_blocks.at(0)) << 48) | (((uint64_t)m_ip.m_blocks.at(1)) << 32) | (((uint64_t)m_ip.m_blocks.at(2)) << 16) | (((uint64_t)m_ip.m_blocks.at(3)) << 0);
|
||||
uint64_t incomingIPLeft =
|
||||
(((uint64_t)ip.m_blocks.at(0)) << 48) | (((uint64_t)ip.m_blocks.at(1)) << 32) | (((uint64_t)ip.m_blocks.at(2)) << 16) | (((uint64_t)ip.m_blocks.at(3)) << 0);
|
||||
|
||||
if ((rangeMaskLeft & rangeIPLeft) != (rangeMaskLeft & incomingIPLeft))
|
||||
return false;
|
||||
|
||||
if (m_subnet <= 64)
|
||||
return true;
|
||||
|
||||
uint64_t rangeMaskRight = 0xFFFFFFFFFFFFFFFF << (/* m_subnet > 64 */ 128 - m_subnet);
|
||||
uint64_t rangeIPRight =
|
||||
(((uint64_t)m_ip.m_blocks.at(4)) << 48) | (((uint64_t)m_ip.m_blocks.at(5)) << 32) | (((uint64_t)m_ip.m_blocks.at(6)) << 16) | (((uint64_t)m_ip.m_blocks.at(7)) << 0);
|
||||
uint64_t incomingIPRight =
|
||||
(((uint64_t)ip.m_blocks.at(4)) << 48) | (((uint64_t)ip.m_blocks.at(5)) << 32) | (((uint64_t)ip.m_blocks.at(6)) << 16) | (((uint64_t)ip.m_blocks.at(7)) << 0);
|
||||
|
||||
return (rangeMaskRight & rangeIPRight) == (rangeMaskRight & incomingIPRight);
|
||||
}
|
||||
33
src/config/IPRange.hpp
Normal file
33
src/config/IPRange.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
class CIP {
|
||||
public:
|
||||
CIP() = default;
|
||||
CIP(const std::string& ip);
|
||||
|
||||
bool m_v6 = false;
|
||||
std::vector<uint16_t> m_blocks;
|
||||
|
||||
private:
|
||||
void parseV4(const std::string& ip);
|
||||
void parseV6(const std::string& ip);
|
||||
};
|
||||
|
||||
// Accepts both ipv4 and ipv6
|
||||
class CIPRange {
|
||||
public:
|
||||
CIPRange(const std::string& range);
|
||||
|
||||
bool ipMatches(const CIP& ip) const;
|
||||
|
||||
private:
|
||||
CIP m_ip;
|
||||
size_t m_subnet = 0;
|
||||
|
||||
bool ipMatchesV6(const CIP& ip) const;
|
||||
bool ipMatchesV4(const CIP& ip) const;
|
||||
};
|
||||
@@ -213,6 +213,40 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
||||
}
|
||||
}
|
||||
|
||||
int challengeDifficulty = g_pConfig->m_config.default_challenge_difficulty;
|
||||
|
||||
if (!g_pConfig->m_parsedConfigDatas.ip_configs.empty()) {
|
||||
const auto IP = CIP(req.address().host());
|
||||
for (const auto& ic : g_pConfig->m_parsedConfigDatas.ip_configs) {
|
||||
bool matched = false;
|
||||
|
||||
for (const auto& ipr : ic.ip_ranges) {
|
||||
if (!ipr.ipMatches(IP))
|
||||
continue;
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
if (ic.action == CConfig::IP_ACTION_ALLOW) {
|
||||
Debug::log(LOG, " | Action: PASS (ip rule matched for {})", req.address().host());
|
||||
proxyPass(req, response);
|
||||
return;
|
||||
} else if (ic.action == CConfig::IP_ACTION_DENY) {
|
||||
Debug::log(LOG, " | Action: DENY (ip rule matched for {})", req.address().host());
|
||||
response.send(Pistache::Http::Code::Forbidden, "Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's challenge then it's default so just set the difficulty if applicable and proceed
|
||||
if (ic.difficulty != -1)
|
||||
challengeDifficulty = ic.difficulty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (req.cookies().has(TOKEN_COOKIE_NAME)) {
|
||||
// check the token
|
||||
const auto TOKEN = CToken(req.cookies().get(TOKEN_COOKIE_NAME).value);
|
||||
@@ -234,7 +268,7 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
||||
} else
|
||||
Debug::log(LOG, " | Action: CHALLENGE (no token)");
|
||||
|
||||
serveStop(req, response);
|
||||
serveStop(req, response, challengeDifficulty);
|
||||
}
|
||||
|
||||
void CServerHandler::onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) {
|
||||
@@ -274,18 +308,16 @@ void CServerHandler::challengeSubmitted(const Pistache::Http::Request& req, Pist
|
||||
response.send(Pistache::Http::Code::Ok, "Ok");
|
||||
}
|
||||
|
||||
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
|
||||
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty) {
|
||||
static const auto PAGE_INDEX = NFsUtils::readFileAsString(NFsUtils::htmlPath("/index.min.html")).value();
|
||||
static const auto PAGE_ROOT = PAGE_INDEX.substr(0, PAGE_INDEX.find_last_of("/") + 1);
|
||||
CTinylates page(PAGE_INDEX);
|
||||
page.setTemplateRoot(PAGE_ROOT);
|
||||
|
||||
const auto NONCE = generateNonce();
|
||||
const auto DIFFICULTY = 4;
|
||||
const auto NONCE = generateNonce();
|
||||
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, difficulty);
|
||||
|
||||
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, DIFFICULTY);
|
||||
|
||||
page.add("challengeDifficulty", CTinylatesProp(std::to_string(DIFFICULTY)));
|
||||
page.add("challengeDifficulty", CTinylatesProp(std::to_string(difficulty)));
|
||||
page.add("challengeNonce", CTinylatesProp(NONCE));
|
||||
page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature()));
|
||||
page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint()));
|
||||
|
||||
@@ -16,7 +16,7 @@ class CServerHandler : public Pistache::Http::Handler {
|
||||
void onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response);
|
||||
|
||||
private:
|
||||
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
||||
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty);
|
||||
void proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
||||
void challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
||||
std::string fingerprintForRequest(const Pistache::Http::Request& req);
|
||||
|
||||
Reference in New Issue
Block a user