Skip to content
API Documentation
BastionGuard™
BastionGuard-secure.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 "SecureBrowser.hpp"
23#include "SettingsStore.hpp"
24#include <glib/gi18n.h>
25#include <glibmm.h>
26#include <nlohmann/json.hpp>
27
28#include <algorithm>
29#include <cctype>
30#include <cstdlib>
31#include <filesystem>
32#include <fstream>
33#include <iostream>
34#include <regex>
35#include <string>
36#include <vector>
37#include <sys/wait.h>
38#include <unistd.h>
39
40namespace fs = std::filesystem;
41using json = nlohmann::json;
42
43
44static std::string to_lower(std::string s) {
45 std::transform(s.begin(), s.end(), s.begin(),
46 [](unsigned char c){ return std::tolower(c); });
47 return s;
48}
49
51 const auto base = Glib::get_user_config_dir();
52 return base + "/BastionGuard/payments.json";
53}
54
55void SecureList::open_system_browser(const std::string& url) {
56 // ANTI-LOOP CRITICO.
57 // Questo fallback apre l'URL nel browser di SISTEMA. Ma il browser di
58 // sistema è configurato per usare il proxy MITM di BastionGuard, quindi
59 // la richiesta verso un dominio bank/secure torna al proxy, che la
60 // re-intercetta e rilancia di nuovo BastionGuard-secure → che richiama
61 // open_system_browser → xdg-open → ... loop infinito = migliaia di
62 // finestre/processi.
63 //
64 // Due protezioni:
65 // 1) BG_OPENER_DEPTH: se siamo già stati invocati dalla catena opener,
66 // NON riapriamo il browser di sistema (interrompe la ricorsione).
67 // 2) no_proxy="*"/NO_PROXY="*" nell'ambiente del comando lanciato: il
68 // browser di sistema NON deve instradare QUESTA apertura attraverso
69 // il proxy di BastionGuard, così non viene re-intercettata.
70 if (std::getenv("BG_OPENER_DEPTH")) {
71 std::cerr << _("[BastionGuard-secure] ⛔ Anti-loop: apertura browser di "
72 "sistema già in corso, non rilancio (") << url << ")\n";
73 return;
74 }
75
76 const char* wayland = std::getenv("WAYLAND_DISPLAY");
77 const pid_t pid = ::fork();
78 if (pid < 0) {
79 std::cerr << _("[BastionGuard-secure] ❌ Impossibile avviare il browser di sistema.\n");
80 return;
81 }
82
83 if (pid == 0) {
84 // Neutralizza il proxy soltanto nel processo figlio e passa l'URL come
85 // singolo argv: nessuna interpretazione di metacaratteri da una shell.
86 ::setenv("BG_OPENER_DEPTH", "1", 1);
87 ::setenv("no_proxy", "*", 1);
88 ::setenv("NO_PROXY", "*", 1);
89 static const char* proxy_vars[] = {
90 "http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"
91 };
92 for (const char* name : proxy_vars)
93 ::unsetenv(name);
94
95 if (wayland && *wayland) {
96 const char* gio_argv[] = {"gio", "open", url.c_str(), nullptr};
97 ::execvp("gio", const_cast<char* const*>(gio_argv));
98 }
99
100 const char* xdg_argv[] = {"xdg-open", url.c_str(), nullptr};
101 ::execvp("xdg-open", const_cast<char* const*>(xdg_argv));
102 _exit(127);
103 }
104
105 int status = 0;
106 if (::waitpid(pid, &status, 0) < 0)
107 std::cerr << _("[BastionGuard-secure] ⚠️ waitpid fallita per browser di sistema.\n");
108}
109
110std::string SecureList::clean_domain(const std::string& d) {
111 std::string s = d;
112
113 // trim minimo
114 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front())))
115 s.erase(s.begin());
116 while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back())))
117 s.pop_back();
118
119 s = to_lower(s);
120
121 if (!s.empty() && s[0] == '.')
122 s.erase(0, 1);
123
124 if (s.rfind("www.", 0) == 0)
125 s.erase(0, 4);
126
127 // rimuovi porta
128 auto p = s.find(':');
129 if (p != std::string::npos)
130 s.erase(p);
131
132 return s;
133}
134
135std::string SecureList::extract_domain(const std::string& url) {
136 std::regex re(R"(^(?:[a-zA-Z][a-zA-Z0-9+\-.]*:\/\/)?(?:[^\/@]+@)?([^\/:?#]+))",
137 std::regex::icase);
138
139 std::smatch match;
140 if (std::regex_search(url, match, re) && match.size() >= 2) {
141 return clean_domain(match[1].str());
142 }
143 return "";
144}
145
146bool SecureList::ends_with(const std::string& s, const std::string& suffix) {
147 if (suffix.size() > s.size()) return false;
148 return std::equal(
149 suffix.rbegin(), suffix.rend(),
150 s.rbegin(),
151 [](char a, char b) {
152 return std::tolower(static_cast<unsigned char>(a)) ==
153 std::tolower(static_cast<unsigned char>(b));
154 }
155 );
156}
157
158
159static void load_payments_list_only(const std::string& path, std::vector<std::string>& out) {
160 if (!fs::exists(path)) {
161 std::cerr << _("[BastionGuard-secure] ⚠ payments.json non trovato: ") << path << "\n";
162 return;
163 }
164
165 std::ifstream f(path, std::ios::binary);
166 if (!f.is_open()) {
167 std::cerr << _("[BastionGuard-secure] ⚠ Impossibile aprire: ") << path << "\n";
168 return;
169 }
170
171 json j;
172 try {
173 f >> j;
174 } catch (const std::exception& e) {
175 std::cerr << Glib::ustring::compose(
176 _("[BastionGuard-secure] ❌ Errore parsing JSON (%1): %2"),
177 path,
178 e.what()
179 ) << "\n";
180 return;
181 }
182
183 if (!j.is_object()) {
184 std::cerr << _(
185 "[BastionGuard-secure] ⚠ payments.json deve essere un oggetto con chiave \"list\"."
186 ) << "\n";
187
188 return;
189 }
190
191 if (!j.contains("list") || !j["list"].is_array()) {
192 std::cerr << _("[BastionGuard-secure] ⚠ payments.json: chiave \"list\" mancante o non array.\n");
193 return;
194 }
195
196 for (const auto& v : j["list"]) {
197 if (!v.is_string()) continue;
198 auto cd = SecureList::clean_domain(v.get<std::string>());
199 if (!cd.empty()) out.push_back(cd);
200 }
201
202 std::cerr << _("[BastionGuard-secure] ✅ Caricati ") << out.size()
203 << _(" domini da payments.json (list)\n");
204}
205
206const std::vector<std::string>& SecureList::payments_cache_only() {
207 static bool init = false;
208 static std::vector<std::string> cached;
209
210 if (!init) {
211 const std::string path_user = config_path_payments();
212 load_payments_list_only(path_user, cached);
213 init = true;
214 }
215
216 return cached;
217}
218
219static bool matches_payment_domain(const std::string& host_norm,
220 const std::vector<std::string>& list_norm) {
221 for (const auto& d : list_norm) {
222 if (d.empty()) continue;
223 if (host_norm == d) return true;
224 if (SecureList::ends_with(host_norm, "." + d)) return true;
225 }
226 return false;
227}
228
229
230bool SecureList::is_payment_domain(const std::string& domain) {
231 // Se vuoi che sia sempre attivo a prescindere dalle impostazioni,
232 // rimuovi questo controllo.
234 return false;
235
236 const auto& list = payments_cache_only();
237 if (list.empty()) {
238 std::cerr << _("[BastionGuard-secure] ⚠ Nessun dominio presente in payments.json.\n");
239 return false;
240 }
241
242 const std::string dnorm = clean_domain(domain);
243 return matches_payment_domain(dnorm, list);
244}
245
246bool SecureList::is_builtin_payment_provider(const std::string& domain) {
247 // Domini "navigabili" dei principali provider di pagamento: pagine dove
248 // l'utente effettua davvero login/checkout/inserimento carta. Devono
249 // SEMPRE aprirsi nella sandbox CEF, anche se l'utente non li ha messi in
250 // payments.json.
251 //
252 // ATTENZIONE: qui NON vanno gli endpoint tecnici (js.stripe.com,
253 // api.stripe.com, hooks.*, cdn.*, api.paypal.com, ...). Quelli sono
254 // iframe/API caricati DENTRO la pagina di un merchant e restano nelle
255 // esclusioni del proxy: intercettarli romperebbe il checkout. Qui solo
256 // i domini che l'utente naviga direttamente.
257 static const std::vector<std::string> providers = {
258 "stripe.com", // checkout.stripe.com, dashboard.stripe.com, billing.stripe.com
259 "paypal.com", // www.paypal.com, checkout.paypal.com
260 "paypalobjects.com",
261 "adyen.com", // ca-live/checkout pages (NB: checkoutshopper-* resta escluso)
262 "checkout.com",
263 "klarna.com",
264 "worldpay.com",
265 "braintreegateway.com",
266 "nexi.it",
267 "nexigroup.com",
268 "sisalpay.it",
269 "satispay.com",
270 "sumup.com",
271 "square.com",
272 "squareup.com",
273 "mollie.com",
274 "gocardless.com",
275 "wise.com",
276 "revolut.com",
277 "skrill.com",
278 "neteller.com",
279 "paysafecard.com",
280 "amazonpay.com",
281 "wallet.google.com",
282 "pay.google.com",
283 };
284
285 const std::string dnorm = clean_domain(domain);
286 for (const auto& d : providers) {
287 if (dnorm == d) return true;
288 if (ends_with(dnorm, "." + d)) return true;
289 }
290 return false;
291}
292
293bool SecureList::open_if_allowed(const std::string& url, int argc, char** argv) {
294 if (url.empty()) {
295 std::cerr << _("[BastionGuard-secure] ⚠ URL vuoto.\n");
296 return false;
297 }
298
299 const std::string domain = extract_domain(url);
300 if (domain.empty()) {
301 std::cerr << _("[BastionGuard-secure] ❌ Impossibile estrarre dominio da: ") << url << "\n";
303 return false;
304 }
305
306 if (is_payment_domain(domain) || is_builtin_payment_provider(domain)) {
307 std::cout << _("[BastionGuard-secure] 🟢 Dominio consentito (payments.json / provider noto): ") << domain << "\n";
308 std::cout << _("[BastionGuard-secure] 🔐 Avvio SecureBrowser (CEF)...\n");
309 SecureBrowser::open(url, argc, argv);
310 return true;
311 }
312
313 std::cout << _("[BastionGuard-secure] 🔴 Dominio NON presente in payments.json: ") << domain << "\n";
314 std::cout << _("[BastionGuard-secure] → Apertura nel browser di sistema.\n");
316 return false;
317}
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
static std::string to_lower(std::string s)
static std::string to_lower(std::string s)
static bool matches_payment_domain(const std::string &host_norm, const std::vector< std::string > &list_norm)
static void load_payments_list_only(const std::string &path, std::vector< std::string > &out)
static bool open(const std::string &url, int argc, char **argv)
static std::string clean_domain(const std::string &d)
static bool open_if_allowed(const std::string &url, int argc, char **argv)
static void open_system_browser(const std::string &url)
static std::string extract_domain(const std::string &url)
static const std::vector< std::string > & payments_cache_only()
static bool ends_with(const std::string &s, const std::string &suffix)
static std::string config_path_payments()
static bool is_payment_domain(const std::string &domain)
static bool is_builtin_payment_provider(const std::string &domain)
static bool get_secure_payments_enabled()