Skip to content
API Documentation
BastionGuard™
WebProbe.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
22#include "WebProbe.hpp"
23#include "network_quality.hpp"
24#include "dns_util.hpp"
25#include "dns_cache.hpp"
26#include "Backend.hpp"
27#include <glibmm/i18n.h>
28#include <glibmm/miscutils.h>
29
30#include <iostream>
31#include <regex>
32#include <thread>
33#include <future>
34#include <chrono>
35#include <cstring>
36#include <arpa/inet.h>
37#include <unordered_map>
38#include <unordered_set>
39#include <mutex>
40#include <shared_mutex>
41#include <filesystem>
42#include <fstream>
43#include <sstream>
44#include <set>
45#include <idn2.h>
46
47using namespace std::chrono_literals;
48
49
52 uint64_t expires_ms;
53};
54
55static std::mutex g_verdict_mutex;
56static std::unordered_map<std::string, VerdictEntry> g_verdicts;
57static std::unordered_set<std::string> g_blocklist;
58static std::shared_mutex g_hot_mtx;
59static std::unordered_map<std::string, std::pair<bool,uint64_t>> g_hot;
60static constexpr uint64_t TTL_MS = 15000;
61
62static inline uint64_t now_ms() {
63 return std::chrono::duration_cast<std::chrono::milliseconds>(
64 std::chrono::steady_clock::now().time_since_epoch()).count();
65}
66
67static std::string expand_user_path(const std::string& rel) {
68 std::string home = Glib::get_home_dir();
69 if (rel.empty()) return home;
70 return home + rel;
71}
72
73static std::string to_ascii_idna(const std::string& host) {
74 if (host.empty()) return host;
75 char *out = nullptr;
76 int rc = idn2_lookup_ul(host.c_str(), &out, 0);
77 if (rc == IDN2_OK && out) {
78 std::string r(out);
79 idn2_free(out);
80 return r;
81 }
82 return host;
83}
84
85
86
87static const std::string kBlocklistFile = "/.local/share/BastionGuard/blocklist_hosts.txt";
88
89static void load_blocklist(const std::string& hint) {
90 std::lock_guard<std::mutex> lg(g_verdict_mutex);
91 g_blocklist.clear();
92
93 std::vector<std::string> tries;
94 if (!hint.empty()) tries.push_back(hint);
95 tries.push_back(expand_user_path(kBlocklistFile));
96 tries.push_back("/etc/BastionGuard/blocklist_hosts.txt");
97
98 for (const auto& p : tries) {
99 if (std::filesystem::exists(p)) {
100 std::ifstream ifs(p);
101 if (!ifs) continue;
102 std::string line;
103 while (std::getline(ifs, line)) {
104 line.erase(std::remove_if(line.begin(), line.end(), ::isspace), line.end());
105 if (!line.empty()) {
106 std::string lower = to_ascii_idna(line);
107 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
108 g_blocklist.insert(lower);
109 }
110 }
111 std::cout << _("[WebProbe] ✅ Caricata blocklist da: ") << p
112 << " (" << g_blocklist.size() << _(" domini)\n");
113 return;
114 }
115 }
116 std::cout << _("[WebProbe] ⚠️ Nessuna blocklist trovata.\n");
117}
118
119static bool match_blocklist_pattern(const std::string& pattern, const std::string& host) {
120 if (pattern.size() > 2 && pattern[0]=='*' && pattern[1]=='.') {
121 const std::string tail = pattern.substr(2);
122 if (host == tail) return true;
123 if (host.size() > tail.size()+1 &&
124 host.compare(host.size()-tail.size(), tail.size(), tail) == 0 &&
125 host[host.size()-tail.size()-1] == '.') return true;
126 }
127 return host == pattern;
128}
129
130static bool is_blocked_host(const std::string& host) {
131 std::lock_guard<std::mutex> lg(g_verdict_mutex);
132 for (const auto& pat : g_blocklist)
133 if (match_blocklist_pattern(pat, host))
134 return true;
135 return false;
136}
137
138
139WebProbe::WebProbe(const std::string& blocklist_path_hint, int warning_port)
140 : warning_port_(warning_port)
141{
142 load_blocklist(blocklist_path_hint);
143}
144
145WebProbe::~WebProbe() = default;
146
147std::string WebProbe::extract_host(const std::string& raw) {
148 if (raw.empty()) return {};
149 std::string s = raw;
150 auto notspace = [](int ch){ return !std::isspace(ch); };
151 s.erase(s.begin(), std::find_if(s.begin(), s.end(), notspace));
152 s.erase(std::find_if(s.rbegin(), s.rend(), notspace).base(), s.end());
153 if (s.empty()) return {};
154
155 const char* schemes[] = {"http://","https://","ftp://"};
156 for (auto sch : schemes) {
157 size_t n = std::strlen(sch);
158 if (s.rfind(sch, 0) == 0) { s.erase(0, n); break; }
159 }
160 auto at = s.find('@');
161 if (at != std::string::npos) s.erase(0, at + 1);
162 size_t slash = s.find_first_of("/?#");
163 if (slash != std::string::npos) s = s.substr(0, slash);
164 size_t colon = s.rfind(':');
165 if (colon != std::string::npos) {
166 std::string after = s.substr(colon + 1);
167 if (std::all_of(after.begin(), after.end(), ::isdigit))
168 s = s.substr(0, colon);
169 }
170 if (!s.empty() && s.back() == '.') s.pop_back();
171
172 std::string ascii = to_ascii_idna(s);
173 std::transform(ascii.begin(), ascii.end(), ascii.begin(), ::tolower);
174 return ascii;
175}
176
177
178std::string WebProbe::build_local_warning_redirect(const std::string& url) const {
179 std::string enc;
180 for (unsigned char c : url)
181 enc += (c == ' ') ? "%20" : std::string(1, c);
182 return "http://127.0.0.2:" + std::to_string(warning_port_) + "/?target=" + enc;
183}
184
185std::string WebProbe::check_url(const std::string& url) {
186 if (url.empty()) return url;
187 std::string host = extract_host(url);
188 if (host.empty()) return url;
189
190 auto now = now_ms();
191 {
192 std::shared_lock<std::shared_mutex> lk(g_hot_mtx);
193 auto it = g_hot.find(host);
194 if (it != g_hot.end() && now < it->second.second)
195 return it->second.first ? build_local_warning_redirect(url) : url;
196 }
197
198
199 if (Backend::instance().isBankDomain(host)) {
200 std::unique_lock<std::shared_mutex> lk(g_hot_mtx);
201 g_hot[host] = {false, now + TTL_MS};
202 return url;
203 }
204
205 if (is_blocked_host(host)) {
206 std::cout << "[WebProbe] 🔴 Bloccato: " << host << "\n";
207 std::unique_lock<std::shared_mutex> lk(g_hot_mtx);
208 g_hot[host] = {true, now + TTL_MS};
210 }
211
212 std::string cached_ip = AsyncDNSResolver::instance().resolve_cached(host);
213 if (cached_ip == "127.0.0.2") {
214 std::cout << _("[WebProbe] ⚠️ DNS ha restituito 127.0.0.2 per ") << host << "\n";
215 std::unique_lock<std::shared_mutex> lk(g_hot_mtx);
216 g_hot[host] = {true, now + TTL_MS};
218 }
219
220
221 std::unique_lock<std::shared_mutex> lk(g_hot_mtx);
222 g_hot[host] = {false, now + TTL_MS};
223 return url;
224}
225
226
228 std::unique_lock<std::shared_mutex> lock(mutex_);
229 notify_cb_ = std::move(cb);
230}
231
232void WebProbe::reload_blocklist(const std::string& path) {
233 load_blocklist(path);
234}
#define _(s)
static const std::string kBlocklistFile
Definition WebProbe.cpp:87
static std::string expand_user_path(const std::string &rel)
Definition WebProbe.cpp:67
static bool is_blocked_host(const std::string &host)
Definition WebProbe.cpp:130
static void load_blocklist(const std::string &hint)
Definition WebProbe.cpp:89
static std::string to_ascii_idna(const std::string &host)
Definition WebProbe.cpp:73
static bool match_blocklist_pattern(const std::string &pattern, const std::string &host)
Definition WebProbe.cpp:119
static uint64_t now_ms()
Definition WebProbe.cpp:62
static std::shared_mutex g_hot_mtx
Definition WebProbe.cpp:58
static std::mutex g_verdict_mutex
Definition WebProbe.cpp:55
static std::unordered_map< std::string, VerdictEntry > g_verdicts
Definition WebProbe.cpp:56
static std::unordered_map< std::string, std::pair< bool, uint64_t > > g_hot
Definition WebProbe.cpp:59
static constexpr uint64_t TTL_MS
Definition WebProbe.cpp:60
static std::unordered_set< std::string > g_blocklist
Definition WebProbe.cpp:57
std::string resolve_cached(const std::string &host)
static AsyncDNSResolver & instance()
static Backend & instance()
Definition Backend.cpp:830
NotifyCb notify_cb_
Definition WebProbe.hpp:43
void register_callback(NotifyCb cb)
Definition WebProbe.cpp:227
std::string build_local_warning_redirect(const std::string &url) const
Definition WebProbe.cpp:178
std::string check_url(const std::string &url)
Definition WebProbe.cpp:185
void reload_blocklist(const std::string &path_hint)
Definition WebProbe.cpp:232
int warning_port_
Definition WebProbe.hpp:41
WebProbe(const std::string &blocklist_path_hint="", int warning_port=81)
Definition WebProbe.cpp:139
std::shared_mutex mutex_
Definition WebProbe.hpp:42
std::function< void(const std::string &, const std::string &, bool)> NotifyCb
Definition WebProbe.hpp:28
std::string extract_host(const std::string &raw)
Definition WebProbe.cpp:147
uint64_t expires_ms
Definition WebProbe.cpp:52
bool suspicious
Definition WebProbe.cpp:51