Skip to content
API Documentation
BastionGuard™
wizard_setup.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
21
22#include "wizard_setup.hpp"
23#include "shell_quote.hpp"
24#include "../SafeProcess.hpp"
25#include "resource.hpp"
26#include "../DistroUtils.hpp"
27#include <glibmm/i18n.h>
28#include <filesystem>
29#include <fstream>
30#include <iostream>
31#include <sstream>
32#include <array>
33#include <cstdio>
34#include <cstdlib>
35#include <sys/stat.h>
36#include <unistd.h>
37#include <vector>
38#include <algorithm>
39#include <pwd.h>
40#include <sys/types.h>
41#include <sys/wait.h>
42
43#include <gtkmm/messagedialog.h>
44
45
46
47namespace fs = std::filesystem;
48
49namespace {
50
51
52static bool run_cmd_capture(const std::string& cmd, std::string* out = nullptr, int* exit_code = nullptr) {
53 // The legacy setup command plans contain fixed firewall shell operators.
54 // Do not add an extra /bin/sh layer or interpolate unquoted user data.
55 const auto result = bg_process::run({"/bin/sh", "-c", cmd}, true);
56 if (out) *out = result.output;
57 if (exit_code) *exit_code = result.exit_code;
58 return result.ok();
59}
60
61static std::string elev_cmd(const std::string& inner,
62 const std::string& action_id = "org.BastionGuard.maintenance") {
63 std::ostringstream cmd;
64
65 bool has_action_id = run_cmd_capture("pkexec --help | grep -q action-id");
66
67 if (fs::exists("/usr/bin/pkexec")) {
68 if (has_action_id) {
69 cmd << "pkexec --disable-internal-agent --action-id " << action_id
70 << " /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
71 } else {
72
73 cmd << "pkexec /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
74 }
75 } else {
76 cmd << "sudo /bin/bash -c " << BastionGuard::wizard::shell_quote(inner);
77 }
78
79 return cmd.str();
80}
81
82
83
84
85
86static std::string user_data_dir() {
87 const char* xdg = getenv("XDG_DATA_HOME");
88 if (xdg && *xdg) return std::string(xdg) + "/BastionGuard";
89 const char* home = getenv("HOME");
90 if (!home) home = "";
91 return std::string(home) + "/.local/share/BastionGuard";
92}
93
94static bool write_temp_script_and_elevate(const std::string& script_contents, std::string* out_log = nullptr) {
95 char tmpl[] = "/tmp/BastionGuard_tmp_XXXXXX";
96 int fd = mkstemp(tmpl);
97 if (fd < 0) {
98 if (out_log) *out_log = _("Impossibile creare file temporaneo");
99 return false;
100 }
101
102 std::string tmpname = tmpl;
103 FILE* f = fdopen(fd, "w");
104 if (!f) {
105 close(fd);
106 if (out_log) *out_log = _("fdopen fallita");
107 return false;
108 }
109
110 fputs(script_contents.c_str(), f);
111 fclose(f);
112 chmod(tmpname.c_str(), 0755);
113
114 bool has_action_id = run_cmd_capture("pkexec --help | grep -q action-id");
115
116 std::ostringstream elev;
117 if (fs::exists("/usr/bin/pkexec")) {
118 if (has_action_id) {
119 elev << "pkexec --disable-internal-agent --action-id org.BastionGuard.maintenance " << tmpname;
120 } else {
121 elev << "pkexec " << tmpname;
122 }
123 } else {
124 elev << "sudo " << tmpname;
125 }
126
127 std::string out;
128 int rc = 0;
129 bool ok = run_cmd_capture(elev.str(), &out, &rc);
130 std::cerr << _("\n===== [BastionGuard DEBUG: write_temp_script_and_elevate] =====\n");
131 std::cerr << _("Comando elevato eseguito:\n") << elev.str() << "\n";
132 std::cerr << "Exit code: " << rc << (ok ? " (OK)" : " (FAIL)") << "\n";
133 if (!out.empty())
134 std::cerr << "Output:\n" << out << "\n";
135 else
136 std::cerr << _("(nessun output catturato)\n");
137 std::cerr << "===========================================================\n";
138
139 std::error_code ec;
140 fs::remove(tmpname, ec);
141
142 if (out_log) *out_log = out;
143 return ok && rc == 0;
144}
145
146
147
148
149
150
151static bool file_exists(const std::string& path) {
152 struct stat st;
153 return (stat(path.c_str(), &st) == 0);
154}
155
156static std::string run_capture(const std::string& cmd) {
157 const auto capture = bg_process::run({"/bin/sh", "-c", cmd});
158 if (!capture.ok()) return {};
159 std::string out = capture.output;
160 while (!out.empty() && (out.back() == '\n' || out.back() == '\r')) out.pop_back();
161 return out;
162}
163
164static bool detect_resolvectl() {
165 if (run_cmd_capture("command -v resolvectl >/dev/null 2>&1")) return true;
166 return false;
167}
168
169static bool detect_nmcli() {
170 return run_cmd_capture("command -v nmcli >/dev/null 2>&1");
171}
172
173static std::string find_active_interface_nmcli() {
174 std::string cmd = "nmcli -t -f DEVICE,STATE device status 2>/dev/null | awk -F: '$2==\"connected\"{print $1; exit}'";
175 return run_capture(cmd);
176}
177
178static std::string find_active_link_resolvectl() {
179 std::string out = run_capture("ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++){ if($i==\"dev\") {print $(i+1); exit}}}'");
180 if (!out.empty()) return out;
181 if (detect_nmcli()) {
183 }
184 return {};
185}
186
187} // anonymous
188
189namespace BastionGuard::wizard {
190bool run_all_elevated_steps(const std::vector<std::string>& commands, std::string* out_log) {
191 if (commands.empty()) {
192 if (out_log) *out_log = "No commands";
193 return true;
194 }
195
196 char tmpl[] = "/tmp/BastionGuard_all_XXXXXX";
197 int fd = mkstemp(tmpl);
198 if (fd < 0) {
199 if (out_log) *out_log = _("Impossibile creare file temporaneo");
200 return false;
201 }
202 std::string tmpname = tmpl;
203
204 FILE* f = fdopen(fd, "w");
205 if (!f) {
206 close(fd);
207 if (out_log) *out_log = _("fdopen fallita");
208 return false;
209 }
210
211 fputs("#!/bin/bash\nset -euo pipefail\n\n", f);
212 fputs("echo \"[BastionGuard] start elevated script\" >&2\n", f);
213 for (const auto &c : commands) {
214 fputs(c.c_str(), f);
215 fputc('\n', f);
216 }
217
218 fputs("echo \"[BastionGuard] end elevated script\" >&2\n", f);
219 fclose(f);
220 chmod(tmpname.c_str(), 0750);
221
222 bool use_pkexec = fs::exists("/usr/bin/pkexec");
223 bool has_action_id = run_cmd_capture("pkexec --help | grep -q action-id");
224 std::ostringstream elev;
225 if (use_pkexec) {
226 if (has_action_id) {
227 elev << "pkexec --disable-internal-agent --action-id org.BastionGuard.maintenance " << tmpname;
228 } else {
229 elev << "pkexec " << tmpname;
230 }
231 } else {
232 elev << "sudo " << tmpname;
233 }
234
235 std::string out;
236 int rc = 0;
237 bool ok = run_cmd_capture(elev.str(), &out, &rc);
238
239 // Pulizia
240 std::error_code ec;
241 fs::remove(tmpname, ec);
242
243 if (out_log) *out_log = out;
244 return ok && rc == 0;
245}
246
247
248
249void gen_cert_commands(std::vector<std::string>& out) {
251 std::cerr << _("[wizard] Certificati e CA disabilitati su Fedora/RHEL/Alma/Rocky.\n");
252 return;
253 }
254 out.push_back(_("echo '[BastionGuard] Generazione certificati...' >&2"));
255 out.push_back("mkdir -p /etc/BastionGuard/certs && chmod 755 /etc/BastionGuard/certs");
256 out.push_back("[ -f /etc/BastionGuard/certs/BastionGuard-ca.key.pem ] || openssl genrsa -out /etc/BastionGuard/certs/BastionGuard-ca.key.pem 4096");
257 out.push_back("[ -f /etc/BastionGuard/certs/BastionGuard-ca.crt.pem ] || openssl req -x509 -new -nodes -key /etc/BastionGuard/certs/BastionGuard-ca.key.pem -sha256 -days 3650 -subj '/CN=BastionGuard Local CA' -out /etc/BastionGuard/certs/BastionGuard-ca.crt.pem");
258 out.push_back("[ -f /etc/BastionGuard/certs/local-warning.key.pem ] || openssl genrsa -out /etc/BastionGuard/certs/local-warning.key.pem 2048");
259 out.push_back("openssl req -new -key /etc/BastionGuard/certs/local-warning.key.pem -subj '/CN=localhost' -out /etc/BastionGuard/certs/local-warning.csr.pem");
260 out.push_back("echo 'subjectAltName = DNS:localhost,IP:127.0.0.2' > /etc/BastionGuard/certs/v3ext.cnf");
261 out.push_back("openssl x509 -req -in /etc/BastionGuard/certs/local-warning.csr.pem -CA /etc/BastionGuard/certs/BastionGuard-ca.crt.pem -CAkey /etc/BastionGuard/certs/BastionGuard-ca.key.pem -CAcreateserial -out /etc/BastionGuard/certs/local-warning.crt.pem -days 825 -sha256 -extfile /etc/BastionGuard/certs/v3ext.cnf");
262 out.push_back("rm -f /etc/BastionGuard/certs/local-warning.csr.pem /etc/BastionGuard/certs/BastionGuard-ca.srl /etc/BastionGuard/certs/v3ext.cnf || true");
263 out.push_back("chmod 600 /etc/BastionGuard/certs/*key.pem || true");
264 out.push_back("chmod 644 /etc/BastionGuard/certs/*.crt.pem || true");
265 out.push_back("if command -v update-ca-certificates >/dev/null 2>&1; then install -m 644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /usr/local/share/ca-certificates/BastionGuard-ca.crt && update-ca-certificates; "
266 "elif command -v trust >/dev/null 2>&1; then trust anchor --store /etc/BastionGuard/certs/BastionGuard-ca.crt.pem; "
267 "elif command -v update-ca-trust >/dev/null 2>&1; then install -m 644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt && update-ca-trust extract; fi");
268}
269
270void gen_web_commands(std::vector<std::string>& out, int http_port, int https_port) {
271 out.push_back(_("echo '[BastionGuard] Configuro webserver (nginx) porte...' >&2"));
272 out.push_back("mkdir -p /etc/BastionGuard");
273 out.push_back("echo \"" + std::to_string(http_port) + " " + std::to_string(https_port) + "\" > /etc/BastionGuard/webports.conf");
274 out.push_back("sed -i 's/listen [0-9]\\+/listen " + std::to_string(http_port) + "/g' /etc/nginx/conf.d/*.conf 2>/dev/null || true");
275 out.push_back("sed -i 's/listen [0-9]\\+ ssl/listen " + std::to_string(https_port) + " ssl/g' /etc/nginx/conf.d/*.conf 2>/dev/null || true");
276 out.push_back("nginx -t && /usr/libexec/bastionguard/bastionguard-service --system restart nginx.service || true");
277}
278
279void gen_blacklist_commands(std::vector<std::string>& out, const std::string& packaged_blacklist) {
280 if (fs::exists(packaged_blacklist)) {
281 std::string dst = (fs::path(user_data_dir()) / "phishing/blacklist.txt").string();
282 out.push_back("mkdir -p " + (fs::path(user_data_dir()) / "phishing").string());
283 out.push_back("install -m 0644 " + packaged_blacklist + " " + dst + " || true");
284 }
285}
286
287
288
290 bool group_modified = false;
291
292 try {
293 std::cout << "[wizard] " << _("Apro le porte firewall necessarie (53/udp, 81/tcp, 444/tcp, 4443/tcp)...") << std::endl;
294
295 auto detect_distro_family = []() -> std::string {
296 std::ifstream f("/etc/os-release");
297 if (!f.is_open()) return "unknown";
298 std::string line, content;
299 while (std::getline(f, line)) content += line + "\n";
300 f.close();
301
302 if (content.find("ID=fedora") != std::string::npos ||
303 content.find("ID_LIKE=fedora") != std::string::npos ||
304 content.find("ID_LIKE=rhel") != std::string::npos)
305 return "fedora";
306 if (content.find("ID=arch") != std::string::npos ||
307 content.find("ID_LIKE=arch") != std::string::npos)
308 return "arch";
309 if (content.find("ID=opensuse") != std::string::npos)
310 return "suse";
311 if (content.find("ID=debian") != std::string::npos ||
312 content.find("ID_LIKE=debian") != std::string::npos ||
313 content.find("ID=ubuntu") != std::string::npos)
314 return "debian";
315 return "generic";
316 };
317
318 std::string distro = detect_distro_family();
319 std::cout << "[wizard] " << _("Rilevata distribuzione: ") << distro << std::endl;
320
321 auto try_firewalld = [&]() -> bool {
322 if (!fs::exists("/usr/bin/firewall-cmd")) return false;
323 std::string cmd = elev_cmd(
324 "firewall-cmd --permanent --add-port=53/udp && "
325 "firewall-cmd --permanent --add-port=81/tcp && "
326 "firewall-cmd --permanent --add-port=444/tcp && "
327 "firewall-cmd --permanent --add-port=4443/tcp && "
328 "firewall-cmd --reload"
329 );
330 std::string out; int rc = 0;
331 bool ok = run_cmd_capture(cmd, &out, &rc);
332 if (ok && rc == 0)
333 std::cout << "[wizard] ✅ " << _("Porte aperte tramite firewalld.") << std::endl;
334 return ok;
335 };
336
337 auto try_ufw = [&]() -> bool {
338 if (!fs::exists("/usr/sbin/ufw") && !fs::exists("/usr/bin/ufw")) return false;
339 std::ostringstream cmd;
340 cmd << "ufw allow 53/udp && ufw allow 81/tcp && ufw allow 444/tcp && ufw allow 4443/tcp";
341 std::string out; int rc = 0;
342 bool ok = run_cmd_capture(elev_cmd(cmd.str(), "org.BastionGuard.nftables"), &out, &rc);
343
344 if (ok && rc == 0)
345 std::cout << "[wizard] ✅ " << _("Porte aperte tramite ufw.") << std::endl;
346 return ok;
347 };
348
349 auto try_nft = [&]() -> bool {
350 if (!fs::exists("/usr/sbin/nft") && !fs::exists("/usr/bin/nft")) return false;
351
352 run_cmd_capture(elev_cmd("modprobe nf_nat 2>/dev/null || true"), nullptr, nullptr);
353 run_cmd_capture(elev_cmd("sh -c 'echo nf_nat > /etc/modules-load.d/BastionGuard.conf 2>/dev/null || true'"), nullptr, nullptr);
354
355 std::string cmd = elev_cmd(
356 "nft add table inet BastionGuard_filter 2>/dev/null || true; "
357 "nft add chain inet BastionGuard_filter input { type filter hook input priority 0; policy accept; } 2>/dev/null || true; "
358 "nft add rule inet BastionGuard_filter input udp dport 53 accept || true; "
359 "nft add rule inet BastionGuard_filter input tcp dport 81 accept || true; "
360 "nft add rule inet BastionGuard_filter input tcp dport 444 accept || true; "
361 "nft add rule inet BastionGuard_filter input tcp dport 4443 accept || true"
362 );
363 std::string out; int rc = 0;
364 run_cmd_capture(cmd, &out, &rc);
365 std::cout << "[wizard] ✅ " << _("Regole nftables applicate (best-effort).") << std::endl;
366 return true;
367 };
368
369 auto try_iptables = [&]() -> bool {
370 if (!fs::exists("/sbin/iptables") && !fs::exists("/usr/sbin/iptables")) return false;
371 std::string cmd = elev_cmd(
372 "iptables -I INPUT -p udp --dport 53 -j ACCEPT || true; "
373 "iptables -I INPUT -p tcp --dport 81 -j ACCEPT || true; "
374 "iptables -I INPUT -p tcp --dport 444 -j ACCEPT || true; "
375 "iptables -I INPUT -p tcp --dport 4443 -j ACCEPT || true"
376 );
377 std::string out; int rc = 0;
378 run_cmd_capture(cmd, &out, &rc);
379 std::cout << "[wizard] ✅ " << _("Regole iptables applicate (best-effort).") << std::endl;
380 return true;
381 };
382
383 bool ok = false;
384 if (try_firewalld()) ok = true;
385 else if (try_ufw()) ok = true;
386 else if (try_nft()) ok = true;
387 else if (try_iptables()) ok = true;
388 else
389 std::cerr << "[wizard] ⚠ " << _("Nessuno strumento firewall rilevato; apri manualmente le porte se necessario.") << std::endl;
390
391 const char* user = getenv("SUDO_USER");
392 if (!user || !*user) {
393 const char* pk_uid = getenv("PKEXEC_UID");
394 if (pk_uid && *pk_uid) {
395 struct passwd* pwuid_entry = getpwuid(static_cast<uid_t>(std::stoi(pk_uid)));
396 if (pwuid_entry)
397 user = pwuid_entry->pw_name;
398 }
399 }
400 if (!user || !*user)
401 user = getenv("USER");
402
403 if (user && *user) {
404 std::cout << "[wizard] " << _("Utente effettivo: ") << user << std::endl;
405
406 const auto groups = bg_process::run({"id", "-nG", user});
407 std::istringstream words(groups.output);
408 std::string group;
409 bool member = false;
410 while (words >> group) {
411 if (group == "adm" || group == "systemd-journal") { member = true; break; }
412 }
413 const int rc_check = groups.ok() && member ? 0 : 1;
414
415 if (rc_check != 0) {
416 std::ostringstream cmd;
417 cmd << "gpasswd -a " << BastionGuard::wizard::shell_quote(user) << " adm; "
418 << "if getent group systemd-journal >/dev/null 2>&1; then "
419 << "gpasswd -a " << BastionGuard::wizard::shell_quote(user) << " systemd-journal; fi";
420 std::string out;
421 int rc = 0;
422 std::cout << "[wizard] " << _("Aggiungo l'utente ai gruppi di log disponibili...") << std::endl;
423 bool okgrp = run_cmd_capture(elev_cmd(cmd.str()), &out, &rc);
424 if (okgrp && rc == 0) {
425 std::cout << "[wizard] ✅ " << _("Utente aggiunto ai gruppi richiesti.") << std::endl;
426 group_modified = true;
427 } else {
428 std::cerr << "[wizard] ⚠ " << _("Errore aggiungendo l'utente ai gruppi: ") << out << std::endl;
429 }
430 } else {
431 std::cout << "[wizard] " << _("Utente già presente in un gruppo di accesso ai log.") << std::endl;
432 }
433 } else {
434 std::cerr << "[wizard] ⚠ " << _("Impossibile determinare l'utente effettivo; salto l'aggiunta ai gruppi.") << std::endl;
435 }
436
437 if (group_modified) {
438 auto* dlg = new Gtk::MessageDialog(
439 _("Per completare la configurazione,\ntermina e riavvia la sessione utente."),
440 false,
441 Gtk::MessageType::INFO,
442 Gtk::ButtonsType::OK,
443 true
444 );
445 dlg->set_title(_("BastionGuard - Permessi aggiornati"));
446 dlg->set_secondary_text(
447 _("I nuovi permessi di accesso ai log diventeranno attivi dopo il logout.\n"
448 "Chiudi e riapri la sessione per visualizzare correttamente l'avvio dell'applicazione BastionGuard.")
449 );
450 dlg->signal_response().connect([dlg](int) { dlg->hide(); delete dlg; });
451 dlg->present();
452 }
453
454 if (ok) {
455 std::cout << "[wizard] " << _("Riavvio del servizio BastionGuard-phishing-scanner...") << std::endl;
456
457 {
458 std::string out; int rc = 0;
459 run_cmd_capture("test -x /usr/libexec/bastionguard/bastionguard-service", &out, &rc);
460 if (rc != 0) {
461 std::cout << "[wizard] " << _("Gestore servizi non presente: salto il riavvio del servizio.") << std::endl;
462 } else {
463
464 run_cmd_capture(elev_cmd("/usr/libexec/bastionguard/bastionguard-service --system exists BastionGuard-phishing-scanner.service >/dev/null 2>&1"), &out, &rc);
465 if (rc == 0) {
466 run_cmd_capture(elev_cmd("/usr/libexec/bastionguard/bastionguard-service --system restart BastionGuard-phishing-scanner.service >/dev/null 2>&1 || true", "org.BastionGuard.service.manage"), &out, &rc);
467
468 if (rc == 0) {
469 std::cout << "[wizard] ✅ " << _("Servizio BastionGuard-phishing-scanner riavviato.") << std::endl;
470 } else {
471 std::cerr << "[wizard] ⚠ " << _("Tentativo di riavvio eseguito (codice ") << rc << _("). Verifica lo stato del servizio.") << std::endl;
472 }
473 } else {
474 std::cout << "[wizard] " << _("Servizio BastionGuard-phishing-scanner non trovato: salto il riavvio.") << std::endl;
475 }
476 }
477 }
478 }
479 return ok;
480
481 } catch (const std::exception& ex) {
482 std::cerr << "[wizard] ⚠ " << _("Eccezione in setup_firewall_ports: ") << ex.what() << std::endl;
483 return false;
484 }
485}
486
488 try {
489 std::cout << _("[wizard] 🔧 Configurazione rete e nftables (modalità agent)...\n");
490
491 const std::string src_conf = "/usr/share/BastionGuard/data/config/nftables.conf";
492 const std::string dst_conf = "/etc/nftables.conf";
493 const std::string net_script = "/usr/share/BastionGuard/data/scripts/BastionGuard-net-setup.sh";
494
495 if (!fs::exists(src_conf)) {
496 std::cerr << _("[wizard] ⚠ File di configurazione nftables non trovato: ") << src_conf << "\n";
497 return false;
498 }
499
500 std::ostringstream script;
501 script << "#!/bin/bash\n";
502 script << "set -euo pipefail\n";
503 script << "export PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n";
504 script << "export XDG_RUNTIME_DIR=\"/run/user/0\"\n";
505 script << "export DBUS_SESSION_BUS_ADDRESS=\"unix:path=/run/dbus/system_bus_socket\"\n";
506 script << "export DISPLAY=${DISPLAY:-:0}\n";
507 script << _("echo '[BastionGuard] ▶ Configurazione nftables e rete...'\n");
508
509 script << "install -m 0644 \"" << src_conf << "\" \"" << dst_conf << "\"\n";
510 script << "nft -f \"" << dst_conf << "\" || true\n";
511 script << "if ! /usr/libexec/bastionguard/bastionguard-service --system is-active ufw.service 2>/dev/null && "
512 "! /usr/libexec/bastionguard/bastionguard-service --system is-active firewalld.service 2>/dev/null; then\n";
513 script << _(" echo '[BastionGuard] Abilito e avvio nftables.service...'\n");
514 script << " /usr/libexec/bastionguard/bastionguard-service --system enable --now nftables.service || true\n";
515 script << "else\n";
516 script << _(" echo '[BastionGuard] UFW o Firewalld rilevati: mantengo configurazione esistente.'\n");
517 script << "fi\n";
518
519 if (fs::exists(net_script)) {
520 script << _("echo '[BastionGuard] ▶ Eseguo BastionGuard-net-setup.sh (modalità agent)...'\n");
521 script << "/usr/bin/env bash \"" << net_script << "\" || true\n";
522 } else {
523 script << _("echo '[BastionGuard] Nessuno script di rete trovato.'\n");
524 }
525
526 script << _("echo '[BastionGuard] ✅ Configurazione nftables + rete completata.'\n");
527
528 std::string out;
529 bool ok = write_temp_script_and_elevate(script.str(), &out);
530
531 // 🔍 Diagnostica
532 std::cerr << _("[wizard][DEBUG] Output BastionGuard-net-setup.sh:\n") << out << "\n";
533
534 if (ok) {
535 std::cout << _("[wizard] ✅ nftables e rete configurati con successo (agent mode)\n");
536 } else {
537 std::cerr << _("[wizard] ⚠ Errore configurando nftables o rete:\n") << out << "\n";
538 }
539
540 return ok;
541 } catch (const std::exception& ex) {
542 std::cerr << _("[wizard] ⚠ Eccezione in install_nftables_conf: ") << ex.what() << "\n";
543 return false;
544 }
545}
546
547
548
549bool install_system_ca(const std::string& ca_crt_path) {
551 std::cerr << _("[wizard] Installazione CA di sistema bloccata su Fedora/RHEL/Alma/Rocky.\n");
552 return false;
553 }
554 try {
555 if (!fs::exists(ca_crt_path)) {
556 std::cerr << "[wizard] " << _("File CA non trovato:") << " " << ca_crt_path << std::endl;
557 return false;
558 }
559
560 std::ostringstream script;
561 script << "#!/bin/sh\nset -e\n\n";
562 script << "CA=\"" << ca_crt_path << "\"\n";
563 script << "OK=0\n\n";
564
565 script << "if command -v update-ca-certificates >/dev/null 2>&1; then\n";
566 script << " install -m 644 \"$CA\" /usr/local/share/ca-certificates/BastionGuard-ca.crt && update-ca-certificates && OK=1 || true\n";
567 script << "fi\n\n";
568
569
570 script << "if command -v trust >/dev/null 2>&1; then\n";
571 script << " trust anchor --store \"$CA\" && OK=1 || true\n";
572 script << "fi\n\n";
573
574 script << "if command -v update-ca-trust >/dev/null 2>&1; then\n";
575 script << " install -m 644 \"$CA\" /etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt && update-ca-trust extract && OK=1 || true\n";
576 script << "fi\n\n";
577
578 script << "if [ -d /etc/ca-certificates/trust-source/anchors ]; then\n";
579 script << " install -m 644 \"$CA\" /etc/ca-certificates/trust-source/anchors/BastionGuard-ca.crt && \\\n";
580 script << " ( (command -v trust >/dev/null 2>&1 && trust extract-compat) || (trust anchor --store \"$CA\") ) && OK=1 || true\n";
581 script << "fi\n\n";
582 script << "install -m 644 \"$CA\" /usr/local/share/ca-certificates/BastionGuard-ca.crt || true\n";
583 script << "install -m 644 \"$CA\" /etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt || true\n";
584 script << "if command -v update-ca-certificates >/dev/null 2>&1; then update-ca-certificates || true; fi\n";
585 script << "if command -v update-ca-trust >/dev/null 2>&1; then update-ca-trust extract || true; fi\n";
586 script << "if command -v trust >/dev/null 2>&1; then trust anchor --store \"$CA\" || true; fi\n\n";
587
588 script << "if [ \"$OK\" -eq 1 ]; then\n";
589 script << " echo \"OK: CA installed into system trust\"\n";
590 script << " exit 0\n";
591 script << "else\n";
592 script << " echo \"WARN: none of the system trust installers succeeded; CA copied to common paths\"\n";
593 script << " exit 2\n";
594 script << "fi\n";
595
596 std::string out;
597 bool ok = write_temp_script_and_elevate(script.str(), &out);
598
599 if (!ok) {
600 std::cerr << "[wizard] " << _("Installazione CA: script restituisce warning o errore") << "\n" << out << std::endl;
601 if (out.find("OK: CA installed") != std::string::npos) {
602 std::cout << "[wizard] " << _("CA installata nel trust store di sistema") << std::endl;
603 return true;
604 }
605 if (out.find("WARN:") != std::string::npos) {
606 std::cout << "[wizard] " << _("CA copiata in directory comuni, ma potresti doverla abilitare manualmente") << std::endl;
607 return true;
608 }
609 return false;
610 }
611
612 std::cout << "[wizard] " << _("CA installata nel trust store di sistema") << std::endl;
613 return true;
614
615 } catch (const std::exception& ex) {
616 std::cerr << "[wizard] " << _("Eccezione in install_system_ca:") << " " << ex.what() << std::endl;
617 return false;
618 }
619}
620
621
624 std::cerr << _("[wizard] Generazione/installazione certificati disabilitata su Fedora/RHEL/Alma/Rocky.\n");
625 return true;
626 }
627 try {
628 std::cout << "[wizard] " << _("Generazione CA locale e certificato per il warning server...") << std::endl;
629
630 std::ostringstream script;
631 script << "#!/bin/sh\nset -e\n";
632 script << "OUT=/etc/BastionGuard/certs\n";
633 script << "mkdir -p \"$OUT\"\n";
634 script << "chmod 755 \"$OUT\"\n\n";
635
636 script << "if [ ! -f \"$OUT/BastionGuard-ca.key.pem\" ] || [ ! -f \"$OUT/BastionGuard-ca.crt.pem\" ]; then\n";
637 script << " openssl genrsa -out \"$OUT/BastionGuard-ca.key.pem\" 4096\n";
638 script << " openssl req -x509 -new -nodes -key \"$OUT/BastionGuard-ca.key.pem\" "
639 "-sha256 -days 3650 -subj \"/CN=BastionGuard Local CA\" -out \"$OUT/BastionGuard-ca.crt.pem\"\n";
640 script << "fi\n\n";
641
642 script << "if [ ! -f \"$OUT/local-warning.key.pem\" ] || [ ! -f \"$OUT/local-warning.crt.pem\" ]; then\n";
643 script << " openssl genrsa -out \"$OUT/local-warning.key.pem\" 2048\n";
644 script << " openssl req -new -key \"$OUT/local-warning.key.pem\" -subj \"/CN=localhost\" -out \"$OUT/local-warning.csr.pem\"\n";
645 script << " echo \"subjectAltName = DNS:localhost,IP:127.0.0.2\" > \"$OUT/v3ext.cnf\"\n";
646 script << " openssl x509 -req -in \"$OUT/local-warning.csr.pem\" "
647 "-CA \"$OUT/BastionGuard-ca.crt.pem\" -CAkey \"$OUT/BastionGuard-ca.key.pem\" -CAcreateserial "
648 "-out \"$OUT/local-warning.crt.pem\" -days 825 -sha256 -extfile \"$OUT/v3ext.cnf\"\n";
649 script << " rm -f \"$OUT/local-warning.csr.pem\" \"$OUT/BastionGuard-ca.srl\" \"$OUT/v3ext.cnf\"\n";
650 script << " chmod 600 \"$OUT/BastionGuard-ca.key.pem\" \"$OUT/local-warning.key.pem\"\n";
651 script << " chmod 644 \"$OUT/BastionGuard-ca.crt.pem\" \"$OUT/local-warning.crt.pem\"\n";
652 script << "fi\n";
653
654 std::string out;
655 if (!write_temp_script_and_elevate(script.str(), &out)) {
656 std::cerr << _("[wizard] Certificati: errore nello script\n") << out << std::endl;
657 return false;
658 }
659
660 if (!install_system_ca("/etc/BastionGuard/certs/BastionGuard-ca.crt.pem")) {
661 std::cerr << _("[wizard] ⚠️ Attenzione: installazione CA di sistema non riuscita.") << std::endl;
662 std::cerr << _("[wizard] L'utente può installare manualmente il file: /etc/BastionGuard/certs/BastionGuard-ca.crt.pem") << std::endl;
663 return false;
664 }
665
666 std::cout << _("[wizard] ✅ Certificati generati in /etc/BastionGuard/certs e CA installata nel sistema") << std::endl;
667 return true;
668
669 } catch (const std::exception& ex) {
670 std::cerr << _("[wizard] Eccezione in setup_certificates: ") << ex.what() << std::endl;
671 return false;
672 }
673}
674
675
676
677
678bool install_phishing_blacklist(const std::string& src, const std::string& dst) {
679 try {
680 fs::create_directories(fs::path(dst).parent_path());
681 fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
682 std::cout << "[wizard] " << _("Blacklist phishing copiata in:") << " " << dst << std::endl;
683 return true;
684 } catch (const std::exception& ex) {
685 std::cerr << "[wizard] " << _("Errore copiando blacklist:") << " " << ex.what() << std::endl;
686 return false;
687 }
688}
689
690
691bool install_native_host(const std::string& /*src_host*/, const std::string& extension_id) {
692 try {
693 namespace fs = std::filesystem;
694
695 std::cout << "[wizard] " << _("Installazione native messaging host...") << std::endl;
696 std::string bin_path = "/usr/bin/BastionGuard-native-host";
697
698 if (fs::exists(bin_path)) {
699 std::error_code ec;
700 auto perms = fs::status(bin_path, ec).permissions();
701 if (!ec && ((perms & fs::perms::owner_exec) == fs::perms::none)) {
702 fs::permissions(
703 bin_path,
704 fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec,
705 fs::perm_options::add,
706 ec
707 );
708 if (ec) {
709 std::cerr << "[wizard] " << _("Impossibile impostare i permessi eseguibili: ")
710 << ec.message() << std::endl;
711 }
712 }
713 } else {
714 std::cerr << "[wizard] " << _("Avviso: binario native host non trovato: ") << bin_path << std::endl;
715 }
716
717 auto json_escape = [](const std::string& s) {
718 std::string out;
719 for (char c : s) {
720 if (c == '\\') out += "\\\\";
721 else if (c == '"') out += "\\\"";
722 else out += c;
723 }
724 return out;
725 };
726
727 auto make_chrome_manifest = [&](const std::string& ext_id) {
728 std::ostringstream o;
729 o << "{\n"
730 << " \"name\": \"com.BastionGuard.native_host\",\n"
731 << " \"description\": \"BastionGuard native messaging host\",\n"
732 << " \"path\": \"" << json_escape(bin_path) << "\",\n"
733 << " \"type\": \"stdio\",\n"
734 << " \"allowed_origins\": [\n";
735 if (!ext_id.empty())
736 o << " \"chrome-extension://" << ext_id << "/\"\n";
737 o << " ]\n"
738 << "}\n";
739 return o.str();
740 };
741
742 auto make_firefox_manifest = [&](const std::string& ext_id) {
743 std::ostringstream o;
744 o << "{\n"
745 << " \"name\": \"com.BastionGuard.native_host\",\n"
746 << " \"description\": \"BastionGuard native messaging host\",\n"
747 << " \"path\": \"" << json_escape(bin_path) << "\",\n"
748 << " \"type\": \"stdio\",\n"
749 << " \"allowed_extensions\": [\n";
750 if (!ext_id.empty())
751 o << " \"" << ext_id << "\"\n";
752 o << " ]\n"
753 << "}\n";
754 return o.str();
755 };
756
757 const char* xdg = getenv("XDG_CONFIG_HOME");
758 std::string config_base = (xdg && *xdg)
759 ? std::string(xdg)
760 : (std::string(getenv("HOME")) + "/.config");
761
762 std::vector<std::pair<std::string,std::string>> manifest_targets;
763
764 manifest_targets.emplace_back(config_base + "/google-chrome/NativeMessagingHosts", make_chrome_manifest(extension_id));
765 manifest_targets.emplace_back(config_base + "/chromium/NativeMessagingHosts", make_chrome_manifest(extension_id));
766 manifest_targets.emplace_back(config_base + "/BraveSoftware/Brave-Browser/NativeMessagingHosts", make_chrome_manifest(extension_id));
767 manifest_targets.emplace_back(config_base + "/microsoft-edge/NativeMessagingHosts", make_chrome_manifest(extension_id));
768
769 manifest_targets.emplace_back(std::string(getenv("HOME")) + "/.mozilla/native-messaging-hosts", make_firefox_manifest("BastionGuard@local"));
770 manifest_targets.emplace_back(std::string(getenv("HOME")) + "/.librewolf/native-messaging-hosts", make_firefox_manifest("BastionGuard@local"));
771
772 manifest_targets.emplace_back("/etc/opt/chrome/native-messaging-hosts", make_chrome_manifest(extension_id));
773 manifest_targets.emplace_back("/etc/chromium/native-messaging-hosts", make_chrome_manifest(extension_id));
774 manifest_targets.emplace_back("/usr/lib/mozilla/native-messaging-hosts", make_firefox_manifest("BastionGuard@local"));
775
776 bool any_written = false;
777 for (auto& t : manifest_targets) {
778 const std::string& dir = t.first;
779 const std::string& content = t.second;
780 std::error_code ec;
781 fs::create_directories(dir, ec);
782 if (ec) {
783 std::cerr << "[wizard] " << _("Impossibile creare la directory: ")
784 << dir << " (" << ec.message() << ")" << std::endl;
785 continue;
786 }
787 std::string manifest_name = dir + "/com.BastionGuard.native_host.json";
788
789 if (fs::exists(manifest_name)) {
790 std::cout << "[wizard] " << _("Manifest già presente, salto: ") << manifest_name << std::endl;
791 continue;
792 }
793
794 std::ofstream man(manifest_name, std::ios::trunc);
795 if (man.is_open()) {
796 man << content;
797 man.close();
798 std::cout << "[wizard] " << _("Manifest scritto in: ") << manifest_name << std::endl;
799 any_written = true;
800 } else {
801 std::cerr << "[wizard] " << _("Impossibile scrivere manifest in: ") << manifest_name << std::endl;
802 }
803 }
804
805 if (any_written) {
806 std::cout << "[wizard] " << _("Native host configurato, manifest creati") << std::endl;
807 return true;
808 }
809
810 std::cerr << "[wizard] " << _("Nessun manifest creato (tutti già presenti?)") << std::endl;
811 return false;
812
813 } catch (const std::exception& ex) {
814 std::cerr << "[wizard] " << _("Eccezione in install_native_host: ") << ex.what() << std::endl;
815 return false;
816 }
817}
818
819
820bool run_wizard_setup(const std::vector<std::string>& options) {
821 bool run_all = options.empty();
822 bool ok = true;
823
824 auto has_opt = [&](const std::string& key) {
825 return run_all || std::find(options.begin(), options.end(), key) != options.end();
826 };
827
828 std::vector<std::string> cmds;
829
830 std::cout << _("[wizard] 🔁 Esecuzione privilegiata combinata (una sola autenticazione)...\n");
831
832
833
834
835 if (has_opt("certs") && !BastionGuard::Platform::is_fedora_rhel_family()) {
836 cmds.push_back(_("echo '[BastionGuard] Genero certificati locali...' >&2"));
837 cmds.push_back("mkdir -p /etc/BastionGuard/certs && chmod 755 /etc/BastionGuard/certs");
838 cmds.push_back("[ -f /etc/BastionGuard/certs/BastionGuard-ca.key.pem ] || openssl genrsa -out /etc/BastionGuard/certs/BastionGuard-ca.key.pem 4096");
839 cmds.push_back("[ -f /etc/BastionGuard/certs/BastionGuard-ca.crt.pem ] || openssl req -x509 -new -nodes "
840 "-key /etc/BastionGuard/certs/BastionGuard-ca.key.pem -sha256 -days 3650 "
841 "-subj '/CN=BastionGuard Local CA' -out /etc/BastionGuard/certs/BastionGuard-ca.crt.pem");
842 cmds.push_back("[ -f /etc/BastionGuard/certs/local-warning.key.pem ] || openssl genrsa -out /etc/BastionGuard/certs/local-warning.key.pem 2048");
843 cmds.push_back("openssl req -new -key /etc/BastionGuard/certs/local-warning.key.pem "
844 "-subj '/CN=localhost' -out /etc/BastionGuard/certs/local-warning.csr.pem");
845 cmds.push_back("echo 'subjectAltName = DNS:localhost,IP:127.0.0.2' > /etc/BastionGuard/certs/v3ext.cnf");
846 cmds.push_back("openssl x509 -req -in /etc/BastionGuard/certs/local-warning.csr.pem "
847 "-CA /etc/BastionGuard/certs/BastionGuard-ca.crt.pem -CAkey /etc/BastionGuard/certs/BastionGuard-ca.key.pem "
848 "-CAcreateserial -out /etc/BastionGuard/certs/local-warning.crt.pem -days 825 "
849 "-sha256 -extfile /etc/BastionGuard/certs/v3ext.cnf");
850 cmds.push_back("rm -f /etc/BastionGuard/certs/local-warning.csr.pem /etc/BastionGuard/certs/BastionGuard-ca.srl /etc/BastionGuard/certs/v3ext.cnf");
851 cmds.push_back("chmod 600 /etc/BastionGuard/certs/*key.pem || true");
852 cmds.push_back("chmod 644 /etc/BastionGuard/certs/*.crt.pem || true");
853
854 cmds.push_back("if command -v update-ca-certificates >/dev/null 2>&1; then "
855 "install -m 644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /usr/local/share/ca-certificates/BastionGuard-ca.crt && "
856 "update-ca-certificates; "
857 "elif command -v trust >/dev/null 2>&1; then "
858 "trust anchor --store /etc/BastionGuard/certs/BastionGuard-ca.crt.pem; "
859 "elif command -v update-ca-trust >/dev/null 2>&1; then "
860 "install -m 644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt && "
861 "update-ca-trust extract; fi");
862 }
863
864 if (has_opt("blacklist")) {
865 std::string dst = user_data_dir() + "/blacklist/phishing-blacklist.txt";
866 cmds.push_back(_("echo '[BastionGuard] Copio blacklist phishing...' >&2"));
867 cmds.push_back("mkdir -p " + user_data_dir() + "/blacklist || true");
868 cmds.push_back("install -m 0644 phishing-blacklist.txt " + dst + " || true");
869 }
870
871 cmds.push_back(_("echo '[BastionGuard] Riavvio BastionGuard-phishing-scanner...' >&2"));
872 cmds.push_back("if [ -x /usr/libexec/bastionguard/bastionguard-service ] && "
873 "/usr/libexec/bastionguard/bastionguard-service --system exists BastionGuard-phishing-scanner.service >/dev/null 2>&1; then "
874 "/usr/libexec/bastionguard/bastionguard-service --system restart BastionGuard-phishing-scanner.service || true; "
875 "else echo '[BastionGuard] Nessun servizio BastionGuard-phishing-scanner trovato.' >&2; fi");
876
877 if (!cmds.empty()) {
878 std::string log;
879 bool ok_elev = run_all_elevated_steps(cmds, &log);
880 if (!ok_elev) {
881 std::cerr << _("[wizard] ⚠ Errore nello script privilegiato:\n") << log << std::endl;
882 return false;
883 }
884 std::cout << _("[wizard] ✅ Tutte le operazioni privilegiate completate.") << std::endl;
885 }
886
887 if (has_opt("native-host")) {
888 std::string ext_id;
889 ok &= install_native_host("/usr/bin/BastionGuard-native-host", ext_id);
890 }
891
892 return ok;
893}
894
895
896
897} // namespace BastionGuard::wizard
#define _(s)
bool run_all_elevated_steps(const std::vector< std::string > &commands, std::string *out_log)
bool install_system_ca(const std::string &ca_crt_path)
bool run_wizard_setup(const std::vector< std::string > &options)
std::string shell_quote(const std::string &argument)
void gen_web_commands(std::vector< std::string > &out, int http_port, int https_port)
void gen_cert_commands(std::vector< std::string > &out)
bool install_native_host(const std::string &, const std::string &extension_id)
bool install_phishing_blacklist(const std::string &src, const std::string &dst)
void gen_blacklist_commands(std::vector< std::string > &out, const std::string &packaged_blacklist)
static bool file_exists(const std::string &path)
static std::string elev_cmd(const std::string &inner, const std::string &action_id="org.BastionGuard.maintenance")
static bool run_cmd_capture(const std::string &cmd, std::string *out=nullptr, int *exit_code=nullptr)
static std::string run_capture(const std::string &cmd)
static std::string find_active_interface_nmcli()
static std::string find_active_link_resolvectl()
static bool write_temp_script_and_elevate(const std::string &script_contents, std::string *out_log=nullptr)
Result run(const std::vector< std::string > &args, bool merge_stderr=false, std::size_t limit=8U *1024U *1024U, const std::map< std::string, std::string > &environment={}, const std::function< bool(const std::string &)> &on_line={})