2098 auto page_phish = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2100 auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2101 auto lbl = Gtk::make_managed<Gtk::Label>(
_(
"Protezione Anti-Phishing"));
2104 page_phish->append(*hbox);
2106 bool running = isServiceActive(
"BastionGuard-phishing-scanner.service");
2109 ?
_(
"Protezione Anti-Phishing: Attiva (servizio di sistema)")
2110 :
_(
"Protezione Anti-Phishing: Disattivata (servizio non attivo)"));
2116 auto hbox_auto = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2117 auto lbl_auto = Gtk::make_managed<Gtk::Label>(
2118 _(
"Aggiorna automaticamente la blacklist ogni 2 ore"));
2119 hbox_auto->append(*lbl_auto);
2121 auto chk_auto_update = Gtk::make_managed<Gtk::CheckButton>();
2123 chk_auto_update->set_halign(Gtk::Align::END);
2124 hbox_auto->append(*chk_auto_update);
2126 chk_auto_update->signal_toggled().connect([
this, chk_auto_update]() {
2127 bool enable = chk_auto_update->get_active();
2129 ?
_(
"⏳ Attivazione aggiornamento automatico...")
2130 :
_(
"⏳ Disattivazione aggiornamento automatico..."));
2133 std::thread([
this, alive, enable]() {
2135 if (!alive->load())
return;
2136 Glib::signal_idle().connect_once([
this, alive, enable, ok]() {
2140 ?
_(
"✔ Aggiornamento automatico abilitato (ogni 2 ore)")
2141 :
_(
"ℹ️ Aggiornamento automatico disabilitato"));
2143 antiphishStatus.set_text(
_(
"❌ Errore nella modifica del timer automatico"));
2150 page_phish->append(*hbox_auto);
2151 auto hbox_safe = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2152 auto lbl_safe = Gtk::make_managed<Gtk::Label>(
_(
"Usa Google Safe Browsing (verifica aggiuntiva)"));
2153 auto hbox_key = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2154 auto lbl_key = Gtk::make_managed<Gtk::Label>(
_(
"Chiave API Google Safe Browsing:"));
2155 lbl_key->set_halign(Gtk::Align::START);
2161 auto btn_save_key = Gtk::make_managed<Gtk::Button>(
_(
"Salva"));
2162 btn_save_key->signal_clicked().connect([
this]() {
2166 antiphishStatus.set_text(
"✔ " + std::string(
_(
"Chiave Google Safe salvata")));
2168 auto btn_toggle_key = Gtk::make_managed<Gtk::Button>(
_(
"Mostra"));
2169 btn_toggle_key->set_data(
"visible",
new bool(
false), [](
void* p){
delete static_cast<bool*
>(p); });
2170 btn_toggle_key->signal_clicked().connect(
2171 sigc::track_obj([
this, btn_toggle_key]() {
2172 bool* visible =
static_cast<bool*
>(btn_toggle_key->get_data(
"visible"));
2173 *visible = !*visible;
2176 btn_toggle_key->set_label(*visible ?
_(
"Nascondi") :
_(
"Mostra"));
2180 auto btn_test_key = Gtk::make_managed<Gtk::Button>(
_(
"Verifica"));
2181 btn_test_key->signal_clicked().connect([
this]() {
2182 antiphishStatus.set_text(
"⏳ " + std::string(
_(
"Verifica chiave Google Safe...")));
2183 std::thread([
this, alive =
alive_]() {
2185 if (!alive->load())
return;
2186 Glib::signal_idle().connect_once([
this, alive, valid]() {
2189 antiphishStatus.set_text(
"✔ " + std::string(
_(
"Chiave Google Safe valida")));
2191 antiphishStatus.set_text(
"❌ " + std::string(
_(
"Chiave Google Safe non valida")));
2199 hbox_key->append(*lbl_key);
2201 hbox_key->append(*btn_toggle_key);
2202 hbox_key->append(*btn_save_key);
2203 hbox_key->append(*btn_test_key);
2204 page_phish->append(*hbox_key);
2208 hbox_safe->append(*lbl_safe);
2210 page_phish->append(*hbox_safe);
2217 ?
_(
"✔ Google Safe Browsing abilitato")
2218 :
_(
"ℹ️ Google Safe Browsing disabilitato"));
2225 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
2226 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
2227 scrolled->set_expand(
true);
2229 page_phish->append(*scrolled);
2232 auto btn_update = Gtk::make_managed<Gtk::Button>(
_(
"Aggiorna blacklist"));
2233 page_phish->append(*btn_update);
2234 btn_update->signal_clicked().connect([
this]() {
2235 antiphishStatus.set_text(
"⏳ " + std::string(
_(
"Aggiornamento blacklist...")));
2237 std::thread([
this, alive =
alive_]() {
2239 if (!alive->load())
return;
2241 Glib::signal_idle().connect_once([
this, alive, ok]() {
2247 if (isServiceActive(
"BastionGuard-phishing-scanner.service")) {
2249 int r1 = (
bg_process::run({
"pkexec",
"/usr/libexec/bastionguard/bastionguard-service",
"--system",
"restart",
"BastionGuard-phishing-scanner.service"}).exit_code);
2251 antiphishStatus.set_text(
_(
"✔ Servizio Anti-Phishing riavviato con nuova blacklist"));
2253 antiphishStatus.set_text(
_(
"⚠ Errore durante il riavvio del servizio Anti-Phishing"));
2258 antiphishStatus.set_text(
"❌ " + std::string(
_(
"Errore aggiornamento blacklist")));
2266 auto lblInfo = Gtk::make_managed<Gtk::Label>(
2267 "ℹ️ " + std::string(
_(
"La lista completa aggiornata si trova in: phishing/blacklist.txt"))
2269 lblInfo->set_wrap(
true);
2270 page_phish->append(*lblInfo);
2271 auto frameFirewall = Gtk::make_managed<Gtk::Frame>();
2272 frameFirewall->set_hexpand(
true);
2274 auto boxFirewall = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
2275 boxFirewall->set_margin(12);
2276 boxFirewall->set_hexpand(
true);
2277 auto lblFwTitle = Gtk::make_managed<Gtk::Label>(
2278 _(
"Blocco IP automatico (Firewall)")
2280 lblFwTitle->set_halign(Gtk::Align::START);
2281 lblFwTitle->add_css_class(
"card-title");
2282 auto lblFwToggle = Gtk::make_managed<Gtk::Label>(
2283 _(
"Abilita blocco IP automatico")
2285 lblFwToggle->set_halign(Gtk::Align::START);
2287 auto swFirewall = Gtk::make_managed<Gtk::Switch>();
2290 auto hboxFwToggle = Gtk::make_managed<Gtk::CenterBox>();
2291 hboxFwToggle->set_start_widget(*lblFwToggle);
2292 hboxFwToggle->set_end_widget(*swFirewall);
2293 auto lblFwDesc = Gtk::make_managed<Gtk::Label>(
2294 _(
"Blocca automaticamente gli IP malevoli rilevati dal modulo Anti-Phishing\n"
2295 "utilizzando il firewall di sistema (UFW / firewalld).")
2297 lblFwDesc->set_wrap(
true);
2298 lblFwDesc->set_halign(Gtk::Align::START);
2299 lblFwDesc->add_css_class(
"card-description");
2300 auto lblFwStatus = Gtk::make_managed<Gtk::Label>();
2301 lblFwStatus->set_halign(Gtk::Align::START);
2302 auto updateFwStatus = [lblFwStatus]() {
2305 if (!backend.isFirewallEnabled()) {
2306 lblFwStatus->set_text(
_(
"Firewall IP: Disattivato"));
2307 lblFwStatus->remove_css_class(
"status-ok");
2308 lblFwStatus->add_css_class(
"status-muted");
2312 switch (backend.getFirewallType()) {
2314 lblFwStatus->set_text(
_(
"Firewall IP attivo: UFW"));
2317 lblFwStatus->set_text(
_(
"Firewall IP attivo: firewalld"));
2320 lblFwStatus->set_text(
_(
"Firewall IP attivo"));
2324 lblFwStatus->remove_css_class(
"status-muted");
2325 lblFwStatus->add_css_class(
"status-ok");
2330 auto btnFirewall = Gtk::make_managed<Gtk::Button>(
2331 _(
"Configura blocco IP firewall")
2333 btnFirewall->set_halign(Gtk::Align::END);
2334 btnFirewall->add_css_class(
"suggested-action");
2336 btnFirewall->signal_clicked().connect(
2337 [
this, updateFwStatus]() {
2339 auto dialog = std::make_unique<FirewallChoiceDialog>();
2341 if (
auto *win =
dynamic_cast<Gtk::Window*
>(get_root()))
2342 dialog->set_transient_for(*win);
2344 dialog->set_modal(
true);
2346 dialog->signal_firewall_selected.connect(
2351 backend.setFirewallType(type);
2352 backend.setFirewallEnabled(
true);
2354 backend.applyFirewallFromPhishingBlacklist();
2364 swFirewall->property_active().signal_changed().connect(
2365 [
this, swFirewall, updateFwStatus]() {
2366 bool enabled = swFirewall->get_active();
2371 ?
_(
"✔ Blocco IP firewall abilitato")
2372 :
_(
"ℹ️ Blocco IP firewall disabilitato")
2379 boxFirewall->append(*lblFwTitle);
2380 boxFirewall->append(*hboxFwToggle);
2381 boxFirewall->append(*lblFwDesc);
2382 boxFirewall->append(*lblFwStatus);
2383 boxFirewall->append(*btnFirewall);
2385 frameFirewall->set_child(*boxFirewall);
2386 page_phish->append(*frameFirewall);
2390 stack.add(*page_phish,
"anti-phishing",
_(
"Anti-Phishing"));
2484 auto page_opts = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2486 auto frame = Gtk::make_managed<Gtk::Frame>();
2487 frame->get_style_context()->add_class(
"card");
2489 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
2490 inner->set_margin_top(8);
2491 inner->set_margin_bottom(8);
2492 inner->set_margin_start(8);
2493 inner->set_margin_end(8);
2494 auto lblLang = Gtk::make_managed<Gtk::Label>(
_(
"Lingua dell'applicazione"));
2495 lblLang->set_halign(Gtk::Align::START);
2496 inner->append(*lblLang);
2497 auto rowLang = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2498 auto lblSel = Gtk::make_managed<Gtk::Label>(
_(
"Seleziona lingua"));
2499 lblSel->set_halign(Gtk::Align::START);
2502 rowLang->append(*lblSel);
2504 inner->append(*rowLang);
2516 std::string currentLang;
2517 if (
const char* v = std::getenv(
"LC_ALL"); v && *v) currentLang =
normalize_locale(v);
2518 else if (
const char* v = std::getenv(
"LC_MESSAGES"); v && *v) currentLang =
normalize_locale(v);
2519 else if (
const char* v = std::getenv(
"LANG"); v && *v) currentLang =
normalize_locale(v);
2520 if (currentLang.empty()) currentLang =
"it_IT";
2535 if (lang.empty()) lang =
"en_US";
2537 std::string lang_full = lang +
".UTF-8";
2538 std::string lang_short = lang.substr(0, 2);
2540 std::string config_dir = Glib::get_home_dir() +
"/.config/BastionGuard";
2541 std::filesystem::create_directories(config_dir);
2542 std::ofstream f(config_dir +
"/lang.conf", std::ios::trunc);
2544 f <<
"LANG=" << lang_full <<
"\n";
2545 f <<
"LC_ALL=" << lang_full <<
"\n";
2546 f <<
"LANGUAGE=" << lang <<
":" << lang_short <<
"\n";
2548 std::cout <<
_(
"✔ Lingua salvata in ") << config_dir <<
"/lang.conf\n";
2550 std::cerr <<
_(
"❌ Impossibile salvare ") << config_dir <<
"/lang.conf\n";
2553 auto dialog = Gtk::make_managed<Gtk::Window>();
2554 dialog->set_modal(
true);
2555 dialog->set_default_size(280, 120);
2556 dialog->set_title(
_(
"Riavvio dei servizi in corso..."));
2558 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2559 box->set_margin(20);
2561 auto spinner = Gtk::make_managed<Gtk::Spinner>();
2562 spinner->set_hexpand(
true);
2563 spinner->set_vexpand(
true);
2566 auto label = Gtk::make_managed<Gtk::Label>(
2567 _(
"Attendere qualche secondo, l'app verrà riavviata automaticamente."));
2568 label->set_wrap(
true);
2569 label->set_justify(Gtk::Justification::CENTER);
2571 box->append(*spinner);
2572 box->append(*label);
2573 dialog->set_child(*box);
2576 std::thread([alive, dialog]() {
2577 const char* alert_service =
"BastionGuard-ransomware-alert.service";
2578 const char* scanner_service =
"BastionGuard-phishing-scanner.service";
2579 const char* ransomware_service =
"BastionGuard-ransomware-scanner.service";
2580 const char* alert_realtime_service =
"BastionGuard-ransomware-realtime-alert.service";
2582 const auto restart_user = [](
const char* service) {
2583 return bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
2584 "--user",
"restart", service}).ok();
2586 for (
const auto* unit : {alert_service, alert_realtime_service, ransomware_service}) {
2587 if (!restart_user(unit))
2588 std::cerr <<
_(
"[BastionGuard] Impossibile riavviare ") << unit << std::endl;
2591 if (!
bg_process::run({
"pkexec",
"/usr/libexec/bastionguard/bastionguard-service",
2592 "--system",
"restart", scanner_service}).
ok())
2593 std::cerr <<
_(
"[BastionGuard] Riavvio servizio di sistema fallito") << std::endl;
2595 std::this_thread::sleep_for(std::chrono::milliseconds(800));
2597 Glib::signal_idle().connect_once([alive, dialog]() {
2598 if (alive && alive->load(std::memory_order_acquire))
2602 char self_path[1024];
2603 ssize_t n = readlink(
"/proc/self/exe", self_path,
sizeof(self_path) - 1);
2605 self_path[n] =
'\0';
2606 execl(self_path, self_path,
nullptr);
2607 std::cerr <<
_(
"❌ Impossibile rilanciare l’app (execl fallito)") <<
"\n";
2609 std::cerr <<
_(
"❌ Impossibile determinare il percorso dell’eseguibile") <<
"\n";
2616 frame->set_child(*inner);
2617 page_opts->append(*frame);
2621 auto frameTheme = Gtk::make_managed<Gtk::Frame>();
2622 frameTheme->get_style_context()->add_class(
"card");
2624 auto innerTheme = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
2625 innerTheme->set_margin_top(8);
2626 innerTheme->set_margin_bottom(8);
2627 innerTheme->set_margin_start(8);
2628 innerTheme->set_margin_end(8);
2630 auto lblTheme = Gtk::make_managed<Gtk::Label>(
_(
"Tema dell'interfaccia"));
2631 lblTheme->set_halign(Gtk::Align::START);
2632 innerTheme->append(*lblTheme);
2634 auto rowTheme = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2636 auto lblThemeSel = Gtk::make_managed<Gtk::Label>(
_(
"Seleziona tema"));
2637 lblThemeSel->set_halign(Gtk::Align::START);
2639 auto* combo_theme = Gtk::make_managed<Gtk::ComboBoxText>();
2640 combo_theme->set_hexpand(
true);
2641 combo_theme->append(
"light",
_(
"☀ Chiaro (Light)"));
2642 combo_theme->append(
"dark",
_(
"🌙 Scuro (Dark)"));
2645 std::string themeCfg = Glib::get_home_dir() +
"/.config/BastionGuard/theme.conf";
2646 std::ifstream tf(themeCfg);
2648 if (tf >> saved && (saved ==
"light" || saved ==
"dark"))
2649 combo_theme->set_active_id(saved);
2651 combo_theme->set_active_id(
"light");
2654 rowTheme->append(*lblThemeSel);
2655 rowTheme->append(*combo_theme);
2656 innerTheme->append(*rowTheme);
2658 auto lblPreview = Gtk::make_managed<Gtk::Label>(
"");
2659 lblPreview->set_halign(Gtk::Align::START);
2660 lblPreview->get_style_context()->add_class(
"dim-label");
2661 innerTheme->append(*lblPreview);
2663 auto updatePreview = [lblPreview](
const std::string& id) {
2665 lblPreview->set_text(
_(
"Tema scuro: sfondo #1a1d23, testo chiaro."));
2667 lblPreview->set_text(
_(
"Tema chiaro: sfondo #f8f9fa, testo scuro."));
2669 updatePreview(combo_theme->get_active_id());
2670 combo_theme->signal_changed().connect([combo_theme, updatePreview]() {
2671 updatePreview(combo_theme->get_active_id());
2674 auto* btn_apply_theme = Gtk::make_managed<Gtk::Button>(
_(
"Applica tema"));
2675 btn_apply_theme->set_hexpand(
false);
2676 innerTheme->append(*btn_apply_theme);
2678 btn_apply_theme->signal_clicked().connect([
this, combo_theme]() {
2679 const std::string themeId = combo_theme->get_active_id();
2680 if (themeId.empty())
return;
2683 std::string cfgDir = Glib::get_home_dir() +
"/.config/BastionGuard";
2684 std::filesystem::create_directories(cfgDir);
2685 std::ofstream tf(cfgDir +
"/theme.conf", std::ios::trunc);
2690 std::string cssFile;
2691 if (themeId ==
"dark")
2692 cssFile = Glib::build_filename(
DATA_DIR,
"BastionGuard-dark.css");
2694 cssFile = Glib::build_filename(
DATA_DIR,
"BastionGuard.css");
2696 if (!std::filesystem::exists(cssFile)) {
2697 char self_path[1024] = {};
2698 ssize_t n = readlink(
"/proc/self/exe", self_path,
sizeof(self_path) - 1);
2700 std::string exeDir = std::filesystem::path(self_path).parent_path().string();
2701 std::string candidate = exeDir +
"/" +
2702 (themeId ==
"dark" ?
"BastionGuard-dark.css" :
"BastionGuard.css");
2703 if (std::filesystem::exists(candidate))
2704 cssFile = candidate;
2708 if (!std::filesystem::exists(cssFile)) {
2709 Gtk::Window* pw =
dynamic_cast<Gtk::Window*
>(get_root());
2711 _(
"File CSS non trovato:\n") + cssFile,
2712 Gtk::MessageType::ERROR);
2717 auto provider = Gtk::CssProvider::create();
2718 provider->load_from_path(cssFile);
2719 Gtk::StyleContext::add_provider_for_display(
2720 Gdk::Display::get_default(),
2722 GTK_STYLE_PROVIDER_PRIORITY_USER);
2724 std::cout <<
"[BastionGuard] Tema applicato: " << cssFile << std::endl;
2726 Gtk::Window* pw =
dynamic_cast<Gtk::Window*
>(get_root());
2728 _(
"Tema applicato con successo.\n"
2729 "Per un'applicazione completa riavvia BastionGuard."),
2730 Gtk::MessageType::INFO);
2732 }
catch (
const Glib::Error& e) {
2733 std::cerr <<
"[BastionGuard] Errore CSS: " << e.what() << std::endl;
2734 Gtk::Window* pw =
dynamic_cast<Gtk::Window*
>(get_root());
2736 (Glib::ustring(
_(
"Errore nel caricamento del tema:\n")) + e.what()).raw(),
2737 Gtk::MessageType::ERROR);
2741 frameTheme->set_child(*innerTheme);
2742 page_opts->append(*frameTheme);
2745 auto frameWeb = Gtk::make_managed<Gtk::Frame>();
2746 frameWeb->get_style_context()->add_class(
"card");
2748 auto innerWeb = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
2749 innerWeb->set_margin(10);
2751 auto lblSystem = Gtk::make_managed<Gtk::Label>(
_(
"Sistema operativo / Distro"));
2752 lblSystem->set_halign(Gtk::Align::START);
2753 innerWeb->append(*lblSystem);
2755 combo_os_.append(
"auto",
_(
"Rilevamento automatico"));
2756 combo_os_.append(
"debian",
"Debian / Ubuntu / Mint");
2757 combo_os_.append(
"fedora",
"Fedora / RHEL / CentOS / Alma / Rocky");
2758 combo_os_.append(
"arch",
"Arch / Manjaro / Endeavour");
2759 combo_os_.append(
"opensuse",
"openSUSE / SLE");
2761 combo_os_.append(
"slackware",
"Slackware");
2762 combo_os_.append(
"bsd",
"FreeBSD / OpenBSD / NetBSD");
2766 auto gridPorts = Gtk::make_managed<Gtk::Grid>();
2767 gridPorts->set_row_spacing(6);
2768 gridPorts->set_column_spacing(6);
2770 auto lblHttp = Gtk::make_managed<Gtk::Label>(
"Porta HTTP:");
2771 lblHttp->set_halign(Gtk::Align::START);
2778 auto lblHttps = Gtk::make_managed<Gtk::Label>(
"Porta HTTPS:");
2779 lblHttps->set_halign(Gtk::Align::START);
2785 gridPorts->attach(*lblHttp, 0, 0, 1, 1);
2787 gridPorts->attach(*lblHttps, 0, 1, 1, 1);
2789 innerWeb->append(*gridPorts);
2795 char tmpl[] =
"/tmp/BastionGuard_tmp_XXXXXX";
2796 int fd = mkstemp(tmpl);
2797 if (fd < 0)
return false;
2799 std::string tmpname = tmpl;
2800 FILE* f = fdopen(fd,
"w");
2801 if (!f) { close(fd);
return false; }
2802 fputs(script_contents.c_str(), f);
2804 chmod(tmpname.c_str(), 0755);
2806 const std::string runner = std::filesystem::exists(
"/usr/bin/pkexec") ?
"pkexec" :
"sudo";
2807 std::map<std::string, std::string> env;
2808 for (
const char* key : {
"DISPLAY",
"XAUTHORITY",
"DBUS_SESSION_BUS_ADDRESS"}) {
2809 if (
const char* val = std::getenv(key)) env.emplace(key, val);
2811 const auto result =
bg_process::run({runner, tmpname},
true, 8U * 1024U * 1024U, env);
2812 const std::string& output = result.
output;
2813 std::filesystem::remove(tmpname);
2814 if (out_log) *out_log = output;
2818 auto replace_placeholders = [](
const std::string& src,
const std::string& dest,
2819 int http_port,
int https_port) ->
bool {
2820 std::ifstream in(src);
2821 std::ofstream out(dest);
2822 if (!in.is_open() || !out.is_open())
return false;
2824 while (std::getline(in, line)) {
2825 line = std::regex_replace(line, std::regex(
"\\{\\{HTTP_PORT\\}\\}"), std::to_string(http_port));
2826 line = std::regex_replace(line, std::regex(
"\\{\\{HTTPS_PORT\\}\\}"), std::to_string(https_port));
2827 out << line <<
"\n";
2833 namespace fs = std::filesystem;
2835 Gtk::Window* parent =
dynamic_cast<Gtk::Window*
>(get_root());
2836 auto show_message = [parent](
const Glib::ustring& msg, Gtk::MessageType type,
const Glib::ustring& ) {
2840 std::string distro =
combo_os_.get_active_id();
2841 if (distro.empty() || distro ==
"auto")
2844 std::cout <<
_(
"[BastionGuard] Distro rilevata: ") << distro << std::endl;
2846 int http_port = 81, https_port = 444;
2851 show_message(
_(
"Le porte devono essere numeriche valide."),
2852 Gtk::MessageType::ERROR,
_(
"BastionGuard - Errore"));
2856 if (http_port <= 0 || http_port >= 65536 ||
2857 https_port <= 0 || https_port >= 65536) {
2858 show_message(
_(
"Le porte devono essere comprese tra 1 e 65535."),
2859 Gtk::MessageType::ERROR,
_(
"BastionGuard - Errore"));
2863 std::string template_path = Glib::build_filename(
DATA_DIR,
"vhosts/nginx.conf");
2864 std::string tmpfile =
"/tmp/BastionGuard_nginx.conf";
2865 if (!replace_placeholders(template_path, tmpfile, http_port, https_port)) {
2866 show_message(
_(
"Errore durante la sostituzione delle porte nel template."),
2867 Gtk::MessageType::ERROR,
_(
"BastionGuard - Errore"));
2871 std::string nginx_default_src;
2872 if (distro ==
"arch" || distro ==
"manjaro" || distro ==
"endeavour") {
2873 nginx_default_src = Glib::build_filename(
DATA_DIR,
"vhosts/nginx-config-arch.conf");
2875 nginx_default_src = Glib::build_filename(
DATA_DIR,
"vhosts/nginx-default.conf");
2877 std::string target_conf, site_avail, site_enable;
2878 if (distro ==
"arch" || distro ==
"manjaro" || distro ==
"endeavour") {
2879 site_avail =
"/etc/nginx/sites-available/default";
2880 site_enable =
"/etc/nginx/sites-enabled/default";
2881 target_conf = site_avail;
2883 target_conf = fs::exists(
"/etc/nginx/conf.d")
2884 ?
"/etc/nginx/conf.d/BastionGuard.conf"
2885 :
"/etc/nginx/sites-enabled/BastionGuard.conf";
2888 std::ostringstream script;
2889 script <<
"#!/bin/sh\nset -e\n";
2890 script <<
_(
"echo '[BastionGuard] Applicazione configurazione NGINX...'\n");
2891 script <<
"mkdir -p /etc/BastionGuard\n";
2892 script <<
"echo \"" << http_port <<
" " << https_port <<
"\" > /etc/BastionGuard/webports.conf\n";
2894 if (distro ==
"arch" || distro ==
"manjaro" || distro ==
"endeavour") {
2895 script <<
"mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled\n";
2896 script <<
"echo '[BastionGuard] Installo configurazione Arch...'\n";
2897 script <<
"install -m 0644 " << nginx_default_src <<
" " << site_avail <<
"\n";
2898 script <<
"ln -sf " << site_avail <<
" " << site_enable <<
"\n";
2900 script <<
_(
"echo '[BastionGuard] Verifica configurazione esistente...'\n");
2901 script <<
"if ! grep -q 'BastionGuard Local Web Server' /etc/nginx/nginx.conf 2>/dev/null; then\n";
2902 script <<
" echo '[BastionGuard] Inserisco blocco mancante in nginx.conf...'\n";
2903 script <<
" cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak\n";
2904 script <<
" cat " << nginx_default_src <<
" >> /etc/nginx/nginx.conf\n";
2906 script <<
_(
" echo '[BastionGuard] Configurazione già presente, aggiornamento non necessario.'\n");
2908 script <<
"install -m 0644 " << tmpfile <<
" " << target_conf <<
"\n";
2911 script <<
"nginx -t && /usr/libexec/bastionguard/bastionguard-service --system restart nginx.service\n";
2915 std::cerr <<
_(
"[BastionGuard] Fallito:\n") << out << std::endl;
2916 show_message(
_(
"Errore durante l’applicazione della configurazione:\n") + out,
2917 Gtk::MessageType::ERROR,
_(
"BastionGuard - Errore"));
2921 show_message(
_(
"Configurazione NGINX aggiornata e servizio riavviato."),
2922 Gtk::MessageType::INFO,
_(
"BastionGuard - Esito"));
2925 frameWeb->set_child(*innerWeb);
2926 page_opts->append(*frameWeb);
2929 stack.add(*page_opts,
"options",
_(
"Opzioni"));
2933 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2935 auto lblTitle = Gtk::make_managed<Gtk::Label>(
_(
"Gestione Whitelist / Blacklist Anti-Phishing"));
2936 lblTitle->set_halign(Gtk::Align::START);
2937 lblTitle->set_margin_bottom(6);
2938 page->append(*lblTitle);
2941 const std::string baseDir = Glib::get_home_dir() +
"/.local/share/BastionGuard/phishing";
2942 std::filesystem::create_directories(baseDir);
2944 const std::string wlUserPath = baseDir +
"/whitelist.txt";
2945 const std::string wlSysPath =
"/usr/share/BastionGuard/data/phishing/whitelist.txt";
2947 const std::string blUserPath = baseDir +
"/blacklist_custom.txt";
2948 const std::string blSysPath =
"/usr/share/BastionGuard/data/phishing/blacklist_custom.txt";
2950 const std::string dnsmasqConfPath =
"/etc/dnsmasq.d/bastionguard-blacklist.conf";
2951 const std::string tmpDnsmasqConf =
"/tmp/bastionguard-blacklist.conf";
2954 auto searchEntry = Gtk::make_managed<Gtk::SearchEntry>();
2955 searchEntry->set_placeholder_text(
_(
"Cerca in whitelist/blacklist..."));
2956 page->append(*searchEntry);
2959 auto wlStoreView = Gio::ListStore<Gtk::StringObject>::create();
2960 auto blStoreView = Gio::ListStore<Gtk::StringObject>::create();
2963 auto wlSelection = Gtk::SingleSelection::create(wlStoreView);
2964 auto blSelection = Gtk::SingleSelection::create(blStoreView);
2967 auto factory = Gtk::SignalListItemFactory::create();
2968 factory->signal_setup().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
2969 auto lbl = Gtk::make_managed<Gtk::Label>();
2972 item->set_child(*lbl);
2974 factory->signal_bind().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
2975 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(item->get_item());
2976 if (
auto lbl =
dynamic_cast<Gtk::Label*
>(item->get_child()))
2977 lbl->set_text(obj ? obj->get_string() :
"");
2981 auto wlLabel = Gtk::make_managed<Gtk::Label>(
_(
"Whitelist (consenti)"));
2982 wlLabel->set_halign(Gtk::Align::START);
2983 page->append(*wlLabel);
2985 Gtk::ListView wlListView(wlSelection, factory);
2986 wlListView.set_vexpand(
true);
2987 wlListView.add_css_class(
"whitelist-view");
2989 auto wlScrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
2990 wlScrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
2991 wlScrolled->set_expand(
true);
2992 wlScrolled->set_child(wlListView);
2993 page->append(*wlScrolled);
2995 auto wlEntry = Gtk::make_managed<Gtk::Entry>();
2996 wlEntry->set_placeholder_text(
_(
"Aggiungi a whitelist (dominio o IPv4)"));
2997 wlEntry->set_hexpand(
true);
2999 auto wlAdd = Gtk::make_managed<Gtk::Button>(
_(
"Aggiungi"));
3000 auto wlRemove = Gtk::make_managed<Gtk::Button>(
_(
"Rimuovi selezionato"));
3002 auto wlRow = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3003 wlRow->append(*wlEntry);
3004 wlRow->append(*wlAdd);
3005 wlRow->append(*wlRemove);
3006 page->append(*wlRow);
3009 auto blLabel = Gtk::make_managed<Gtk::Label>(
_(
"Blacklist custom (blocca via dnsmasq)"));
3010 blLabel->set_halign(Gtk::Align::START);
3011 blLabel->set_margin_top(8);
3012 page->append(*blLabel);
3014 Gtk::ListView blListView(blSelection, factory);
3015 blListView.set_vexpand(
true);
3016 blListView.add_css_class(
"whitelist-view");
3018 auto blScrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
3019 blScrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
3020 blScrolled->set_expand(
true);
3021 blScrolled->set_child(blListView);
3022 page->append(*blScrolled);
3024 auto blEntry = Gtk::make_managed<Gtk::Entry>();
3025 blEntry->set_placeholder_text(
_(
"Aggiungi a blacklist (dominio o IPv4)"));
3026 blEntry->set_hexpand(
true);
3028 auto blAdd = Gtk::make_managed<Gtk::Button>(
_(
"Aggiungi"));
3029 auto blRemove = Gtk::make_managed<Gtk::Button>(
_(
"Rimuovi selezionato"));
3031 auto blRow = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3032 blRow->append(*blEntry);
3033 blRow->append(*blAdd);
3034 blRow->append(*blRemove);
3035 page->append(*blRow);
3038 auto btnReload = Gtk::make_managed<Gtk::Button>(
_(
"Ricarica"));
3039 auto btnSave = Gtk::make_managed<Gtk::Button>(
_(
"Salva modifiche"));
3041 auto bottom = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3042 bottom->append(*btnReload);
3043 bottom->append(*btnSave);
3044 page->append(*bottom);
3047 auto wlAll = std::make_shared<std::vector<std::string>>();
3048 auto blAll = std::make_shared<std::vector<std::string>>();
3051 auto trim = [](std::string s) {
3052 auto notSpace = [](
unsigned char c){
return !std::isspace(c); };
3053 s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace));
3054 s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end());
3057 auto toLower = [](std::string s) {
3058 std::transform(s.begin(), s.end(), s.begin(),
3059 [](
unsigned char c){ return (unsigned char)std::tolower(c); });
3062 auto isIPv4 = [](
const std::string& s) ->
bool {
3063 static const std::regex re(
3064 R
"(^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$)"
3066 return std::regex_match(s, re);
3068 auto isDomain = [](
const std::string& s) ->
bool {
3069 static const std::regex re(
3070 R
"(^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$)",
3073 if (!std::regex_match(s, re))
return false;
3074 if (s.find(
"..") != std::string::npos)
return false;
3078 auto loadFileToVector = [](
const std::string& userPath,
3079 const std::string& sysPath,
3080 std::vector<std::string>& out) {
3083 auto load = [&](
const std::string& path) ->
bool {
3084 std::ifstream f(path);
3085 if (!f.is_open())
return false;
3087 while (std::getline(f, line)) {
3088 if (!line.empty()) out.push_back(line);
3093 if (!load(userPath)) load(sysPath);
3096 std::vector<std::string> dedup;
3097 dedup.reserve(out.size());
3098 for (
auto& s : out) {
3099 if (std::find(dedup.begin(), dedup.end(), s) == dedup.end())
3105 auto applyFilter = [=]() {
3106 const std::string qraw = Glib::ustring(searchEntry->get_text()).raw();
3107 const Glib::ustring q = Glib::ustring(qraw).lowercase();
3109 wlStoreView->remove_all();
3110 for (
const auto& s : *wlAll) {
3111 if (q.empty() || Glib::ustring(s).lowercase().find(q) != Glib::ustring::npos)
3112 wlStoreView->append(Gtk::StringObject::create(s));
3115 blStoreView->remove_all();
3116 for (
const auto& s : *blAll) {
3117 if (q.empty() || Glib::ustring(s).lowercase().find(q) != Glib::ustring::npos)
3118 blStoreView->append(Gtk::StringObject::create(s));
3122 auto loadAll = [=]() {
3123 loadFileToVector(wlUserPath, wlSysPath, *wlAll);
3124 loadFileToVector(blUserPath, blSysPath, *blAll);
3128 auto existsInVec = [](
const std::vector<std::string>& v,
const std::string& x) ->
bool {
3129 return std::find(v.begin(), v.end(), x) != v.end();
3132 auto removeFromVec = [](std::vector<std::string>& v,
const std::string& x) {
3133 v.erase(std::remove(v.begin(), v.end(), x), v.end());
3140 searchEntry->signal_search_changed().connect([applyFilter]() {
3145 wlAdd->signal_clicked().connect([=]() {
3146 std::string v = trim(wlEntry->get_text());
3147 if (v.empty())
return;
3149 std::string norm = isIPv4(v) ? v : toLower(v);
3150 if (!isIPv4(norm) && !isDomain(norm)) {
3151 auto dlg = Gtk::AlertDialog::create(
_(
"Valore non valido"));
3152 dlg->set_detail(
_(
"Inserisci un dominio valido o un IPv4 valido."));
3156 if (existsInVec(*wlAll, norm)) {
3157 auto dlg = Gtk::AlertDialog::create(
_(
"Già presente"));
3158 dlg->set_detail(
_(
"Questo elemento è già nella whitelist."));
3160 wlEntry->set_text(
"");
3164 wlAll->push_back(norm);
3165 wlEntry->set_text(
"");
3170 wlRemove->signal_clicked().connect([=]() {
3171 guint pos = wlSelection->get_selected();
3172 if (pos == GTK_INVALID_LIST_POSITION)
return;
3174 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(wlStoreView->get_object(pos));
3177 removeFromVec(*wlAll, obj->get_string());
3182 blAdd->signal_clicked().connect([=]() {
3183 std::string v = trim(blEntry->get_text());
3184 if (v.empty())
return;
3186 std::string norm = isIPv4(v) ? v : toLower(v);
3187 if (!isIPv4(norm) && !isDomain(norm)) {
3188 auto dlg = Gtk::AlertDialog::create(
_(
"Valore non valido"));
3189 dlg->set_detail(
_(
"Inserisci un dominio valido o un IPv4 valido."));
3193 if (existsInVec(*blAll, norm)) {
3194 auto dlg = Gtk::AlertDialog::create(
_(
"Già presente"));
3195 dlg->set_detail(
_(
"Questo elemento è già nella blacklist."));
3197 blEntry->set_text(
"");
3201 blAll->push_back(norm);
3202 blEntry->set_text(
"");
3207 blRemove->signal_clicked().connect([=]() {
3208 guint pos = blSelection->get_selected();
3209 if (pos == GTK_INVALID_LIST_POSITION)
return;
3211 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(blStoreView->get_object(pos));
3214 removeFromVec(*blAll, obj->get_string());
3219 btnReload->signal_clicked().connect([=]() { loadAll(); });
3222 btnSave->signal_clicked().connect([=]() {
3225 std::ofstream f(wlUserPath, std::ios::trunc);
3227 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3228 dlg->set_detail(
_(
"Impossibile salvare la whitelist utente."));
3232 for (
const auto& s : *wlAll) f << s <<
"\n";
3236 std::ofstream f(blUserPath, std::ios::trunc);
3238 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3239 dlg->set_detail(
_(
"Impossibile salvare la blacklist utente."));
3243 for (
const auto& s : *blAll) f << s <<
"\n";
3247 std::ofstream f(tmpDnsmasqConf, std::ios::trunc);
3249 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3250 dlg->set_detail(
_(
"Impossibile creare file temporaneo per dnsmasq."));
3255 f <<
"# Generated by BastionGuard\n";
3256 for (
const auto& item : *blAll) {
3257 if (!isIPv4(item)) {
3258 f <<
"address=/" << item <<
"/127.0.0.2\n";
3260 f <<
"# IP entry (not used by dnsmasq address=): " << item <<
"\n";
3266 bg_process::run({
"pkexec",
"/usr/bin/install",
"-D",
"-m",
"0644", wlUserPath, wlSysPath}).ok() &&
3267 bg_process::run({
"pkexec",
"/usr/bin/install",
"-D",
"-m",
"0644", blUserPath, blSysPath}).ok() &&
3268 bg_process::run({
"pkexec",
"/usr/bin/install",
"-D",
"-m",
"0644", tmpDnsmasqConf, dnsmasqConfPath}).ok() &&
3269 bg_process::run({
"pkexec",
"/usr/libexec/bastionguard/bastionguard-service",
3270 "--system",
"reload",
"dnsmasq.service"}).ok();
3272 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3273 dlg->set_detail(
_(
"Impossibile installare i file o ricaricare dnsmasq."));
3278 auto dlg = Gtk::AlertDialog::create(
_(
"Salvataggio completato"));
3279 dlg->set_detail(
_(
"Whitelist/Blacklist salvate. Config dnsmasq aggiornata (127.0.0.2) e servizio ricaricato."));
3285 (
bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-cef"}).exit_code);
3291 stack.add(*page,
"advanced-anti-phishing",
_(
"Anti-Phishing avanzate"));
3297 auto page_adv = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
3298 page_adv->set_margin(12);
3301 auto frameAllow = Gtk::make_managed<Gtk::Frame>(
_(
"Allowlist SHA256 Anti-ransomware"));
3302 auto vboxAllow = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
3303 vboxAllow->set_margin(6);
3310 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
3311 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
3312 scrolled->set_expand(
true);
3314 vboxAllow->append(*scrolled);
3316 auto btnAdd = Gtk::make_managed<Gtk::Button>(
_(
"Aggiungi"));
3317 auto btnDel = Gtk::make_managed<Gtk::Button>(
_(
"Rimuovi"));
3318 auto btnBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3319 btnBox->append(*btnAdd);
3320 btnBox->append(*btnDel);
3321 vboxAllow->append(*btnBox);
3322 frameAllow->set_child(*vboxAllow);
3323 page_adv->append(*frameAllow);
3327 btnAdd->signal_clicked().connect([
this]() {
3328 auto dialog = std::make_shared<Gtk::Dialog>();
3329 dialog->set_title(
_(
"Aggiungi SHA256"));
3330 dialog->set_modal(
true);
3331 dialog->set_default_size(360, 180);
3332 dialog->add_css_class(
"sha-dialog");
3334 auto shaEntry = Gtk::make_managed<Gtk::Entry>();
3335 auto ruleEntry = Gtk::make_managed<Gtk::Entry>();
3336 shaEntry->set_placeholder_text(
"SHA256...");
3337 ruleEntry->set_placeholder_text(
"Regola (facoltativa)");
3339 shaEntry->set_margin_bottom(6);
3340 ruleEntry->set_margin_bottom(6);
3342 auto content = dialog->get_content_area();
3343 content->set_margin(12);
3344 content->set_spacing(8);
3345 content->append(*shaEntry);
3346 content->append(*ruleEntry);
3348 dialog->add_button(
_(
"_Annulla"), Gtk::ResponseType::CANCEL);
3349 dialog->add_button(
_(
"_Aggiungi"), Gtk::ResponseType::OK);
3351 dialog->signal_show().connect([shaEntry]() {
3352 shaEntry->grab_focus();
3355 dialog->signal_response().connect([
this, dialog, shaEntry, ruleEntry](
int response) {
3356 if (response == Gtk::ResponseType::OK) {
3357 auto sha = shaEntry->get_text();
3358 sha.erase(0, sha.find_first_not_of(
" \t\n\r"));
3359 sha.erase(sha.find_last_not_of(
" \t\n\r") + 1);
3370 dialog->set_visible(
true);
3373 btnDel->signal_clicked().connect([
this]() {
3374 if (
auto sel =
allowView.get_selection()) {
3375 if (
auto iter = sel->get_selected())
3380 auto frameFilters = Gtk::make_managed<Gtk::Frame>(
_(
"Filtri Anti-Falsi Positivi"));
3381 auto grid = Gtk::make_managed<Gtk::Grid>();
3382 grid->set_row_spacing(6);
3383 grid->set_column_spacing(6);
3384 grid->set_margin(6);
3386 chkSuspOnly = Gtk::make_managed<Gtk::CheckButton>(
_(
"Analizza solo file sospetti"));
3387 auto lblIgnPaths = Gtk::make_managed<Gtk::Label>(
_(
"Percorsi da ignorare"));
3389 txtIgnPaths->set_placeholder_text(
"/home/*/.cache;/var/cache;/tmp;...");
3392 auto lblCompany = Gtk::make_managed<Gtk::Label>(
_(
"Percorsi aziendali sicuri"));
3393 txtCompany = Gtk::make_managed<Gtk::Entry>();
3394 txtCompany->set_placeholder_text(
"/srv/company;/mnt/share;/opt/apps;...");
3397 auto lblIgnExt = Gtk::make_managed<Gtk::Label>(
_(
"Estensioni da ignorare"));
3398 txtIgnExt = Gtk::make_managed<Gtk::Entry>();
3399 txtIgnExt->set_placeholder_text(
".jpg;.png;.pdf;...");
3400 txtIgnExt->add_css_class(
"whitelist-entry");
3402 auto lblSuspExt = Gtk::make_managed<Gtk::Label>(
_(
"Estensioni sospette"));
3403 txtSuspExt = Gtk::make_managed<Gtk::Entry>();
3404 txtSuspExt->set_placeholder_text(
".exe;.dll;.js;.sh;...");
3408 grid->attach(*lblIgnPaths, 0, 1, 1, 1);
3410 grid->attach(*lblCompany, 0, 2, 1, 1);
3412 grid->attach(*lblIgnExt, 0, 3, 1, 1);
3414 grid->attach(*lblSuspExt, 0, 4, 1, 1);
3417 frameFilters->set_child(*grid);
3418 page_adv->append(*frameFilters);
3421 auto btnSave = Gtk::make_managed<Gtk::Button>(
_(
"Salva impostazioni avanzate"));
3422 page_adv->append(*btnSave);
3424 btnSave->signal_clicked().connect([
this]() {
3439 stack.add(*page_adv,
"advanced-anti-ransomware",
_(
"Anti-Ransomware avanzate"));
3445 auto page_pay = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
3446 page_pay->set_margin(12);
3448 auto framePay = Gtk::make_managed<Gtk::Frame>();
3450 auto innerPay = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
3451 innerPay->set_margin_top(10);
3452 innerPay->set_margin_bottom(10);
3453 innerPay->set_margin_start(10);
3454 innerPay->set_margin_end(10);
3456 auto run_cmd_capture = [](
const std::vector<std::string>& argv,
3457 std::string* stdout_out =
nullptr,
3458 std::string* stderr_out =
nullptr) ->
bool
3461 auto proc = Gio::Subprocess::create(
3463 Gio::Subprocess::Flags::STDOUT_PIPE |
3464 Gio::Subprocess::Flags::STDERR_PIPE
3467 auto [stdout_str, stderr_str] = proc->communicate_utf8(
"");
3468 if (stdout_out) *stdout_out = stdout_str.raw();
3469 if (stderr_out) *stderr_out = stderr_str.raw();
3471 return proc->get_successful();
3472 }
catch (
const std::exception& ex) {
3473 if (stderr_out) *stderr_out = ex.what();
3476 if (stderr_out) *stderr_out =
"Eccezione sconosciuta";
3481 auto resolve_ca_path = []() -> std::string
3483 const auto user_ca = Glib::build_filename(
3484 Glib::get_home_dir(),
3485 ".local",
"share",
"BastionGuard",
"certs",
"intercept-ca.crt.pem");
3487 if (std::filesystem::exists(user_ca))
3490 const std::string system_ca =
"/etc/BastionGuard/certs/intercept-ca.crt.pem";
3491 if (std::filesystem::exists(system_ca))
3497 auto show_alert = [
this](
const Glib::RefPtr<Gtk::AlertDialog>& dlg) {
3498 if (
auto* root = this->get_root()) {
3499 if (
auto* win =
dynamic_cast<Gtk::Window*
>(root)) {
3507 auto lblPayTitle = Gtk::make_managed<Gtk::Label>(
_(
"Pagamenti Sicuri (CEF)"));
3508 lblPayTitle->set_halign(Gtk::Align::START);
3509 lblPayTitle->add_css_class(
"card-title");
3510 innerPay->append(*lblPayTitle);
3513 auto lblPayDesc = Gtk::make_managed<Gtk::Label>(
3514 _(
"Questa opzione apre in sandbox CEF delle pagine tipiche di pagamento "
3515 "(checkout, gateway, 3D Secure)\n\n"
3516 "Consigliato per acquisti online e inserimento dati carta.")
3518 lblPayDesc->set_wrap(
true);
3519 lblPayDesc->set_halign(Gtk::Align::START);
3520 lblPayDesc->add_css_class(
"card-description");
3521 innerPay->append(*lblPayDesc);
3523 auto lblPayInfo = Gtk::make_managed<Gtk::Label>(
3524 _(
"ℹ️ I pagamenti sicuri utilizzano un browser isolato (CEF sandbox) "
3525 "per ridurre il rischio di furto di credenziali e dati di pagamento.\n"
3526 "Questa modalità non modifica il funzionamento del sito e non salva "
3527 "alcuna informazione sensibile.")
3529 lblPayInfo->set_wrap(
true);
3530 lblPayInfo->set_halign(Gtk::Align::START);
3531 lblPayInfo->add_css_class(
"alert-info");
3532 innerPay->append(*lblPayInfo);
3534 auto lblPayToggle = Gtk::make_managed<Gtk::Label>(
_(
"Abilita pagamenti sicuri"));
3535 lblPayToggle->set_halign(Gtk::Align::START);
3537 auto swPay = Gtk::make_managed<Gtk::Switch>();
3538 swPay->set_halign(Gtk::Align::END);
3541 auto rowPay = Gtk::make_managed<Gtk::CenterBox>();
3542 rowPay->set_start_widget(*lblPayToggle);
3543 rowPay->set_end_widget(*swPay);
3544 innerPay->append(*rowPay);
3546 auto lblPayStatus = Gtk::make_managed<Gtk::Label>();
3547 lblPayStatus->set_halign(Gtk::Align::START);
3548 innerPay->append(*lblPayStatus);
3550 auto refreshPayStatus = [lblPayStatus, swPay]() {
3551 lblPayStatus->set_text(
3553 ?
_(
"✔ Pagamenti sicuri: abilitati")
3554 :
_(
"ℹ️ Pagamenti sicuri: disabilitati")
3556 lblPayStatus->remove_css_class(
"status-ok");
3557 lblPayStatus->remove_css_class(
"status-muted");
3558 lblPayStatus->add_css_class(swPay->get_active() ?
"status-ok" :
"status-muted");
3562 auto lblList = Gtk::make_managed<Gtk::Label>(
_(
"Domini/URL per Pagamenti Sicuri"));
3563 lblList->set_halign(Gtk::Align::START);
3564 innerPay->append(*lblList);
3566 auto store = Gio::ListStore<Gtk::StringObject>::create();
3567 auto selection = Gtk::SingleSelection::create(store);
3569 auto factory = Gtk::SignalListItemFactory::create();
3570 factory->signal_setup().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
3571 auto lbl = Gtk::make_managed<Gtk::Label>();
3572 lbl->set_xalign(0.0);
3574 item->set_child(*lbl);
3576 factory->signal_bind().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
3577 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(item->get_item());
3578 if (
auto lbl =
dynamic_cast<Gtk::Label*
>(item->get_child()))
3579 lbl->set_text(obj ? obj->get_string() :
"");
3582 auto listView = Gtk::make_managed<Gtk::ListView>(selection, factory);
3583 listView->set_vexpand(
true);
3584 listView->set_can_focus(
true);
3585 listView->add_css_class(
"whitelist-view");
3587 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
3588 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
3589 scrolled->set_expand(
true);
3590 scrolled->set_min_content_height(160);
3591 scrolled->set_child(*listView);
3592 innerPay->append(*scrolled);
3594 auto reload_from_file = [store]() {
3595 store->remove_all();
3597 for (
const auto& d : domains)
3598 store->append(Gtk::StringObject::create(d));
3602 auto entry = Gtk::make_managed<Gtk::Entry>();
3603 entry->set_placeholder_text(
_(
"Inserisci dominio o URL (es. paypal.com oppure https://checkout.stripe.com)"));
3604 entry->set_hexpand(
true);
3605 entry->add_css_class(
"whitelist-entry");
3607 auto btnAdd = Gtk::make_managed<Gtk::Button>(
_(
"Aggiungi"));
3608 auto btnRemove = Gtk::make_managed<Gtk::Button>(
_(
"Rimuovi selezionato"));
3609 auto btnSave = Gtk::make_managed<Gtk::Button>(
_(
"Salva"));
3610 auto btnReload = Gtk::make_managed<Gtk::Button>(
_(
"Ripristina"));
3612 auto rowBtns = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3613 rowBtns->append(*entry);
3614 rowBtns->append(*btnAdd);
3615 rowBtns->append(*btnRemove);
3616 rowBtns->append(*btnReload);
3617 rowBtns->append(*btnSave);
3618 innerPay->append(*rowBtns);
3620 btnAdd->signal_clicked().connect([store, entry, show_alert]() {
3621 std::string in = entry->get_text();
3624 auto dlg = Gtk::AlertDialog::create(
_(
"Valore non valido"));
3625 dlg->set_detail(
_(
"Inserisci un dominio o URL valido."));
3630 for (guint i = 0; i < store->get_n_items(); ++i) {
3631 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(store->get_object(i));
3632 if (obj && obj->get_string() == dom) {
3633 entry->set_text(
"");
3638 store->append(Gtk::StringObject::create(dom));
3639 entry->set_text(
"");
3642 btnRemove->signal_clicked().connect([selection, store]() {
3643 guint pos = selection->get_selected();
3644 if (pos != GTK_INVALID_LIST_POSITION)
3648 btnReload->signal_clicked().connect([reload_from_file]() {
3652 btnSave->signal_clicked().connect([store, swPay, lblPayStatus, show_alert]() {
3653 std::vector<std::string> domains;
3654 domains.reserve(store->get_n_items());
3656 for (guint i = 0; i < store->get_n_items(); ++i) {
3657 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(store->get_object(i));
3658 if (obj) domains.push_back(obj->get_string());
3661 const bool enabled_flag = swPay->get_active();
3664 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3665 dlg->set_detail(
_(
"Impossibile salvare payments.json."));
3670 lblPayStatus->set_text(
_(
"✔ Lista pagamenti salvata"));
3671 lblPayStatus->remove_css_class(
"status-muted");
3672 lblPayStatus->add_css_class(
"status-ok");
3676 (
bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-cef.service"}).exit_code);
3681 swPay->property_active().signal_changed().connect([swPay, refreshPayStatus]() {
3682 const bool enabled = swPay->get_active();
3688 auto btnInstallSafeNav = Gtk::make_managed<Gtk::Button>(
_(
"Installazione navigazione sicura"));
3689 btnInstallSafeNav->add_css_class(
"btn-warning");
3690 innerPay->append(*btnInstallSafeNav);
3692 auto installLogBuffer = Gtk::TextBuffer::create();
3694 btnInstallSafeNav->signal_clicked().connect(
3695 [
this, btnInstallSafeNav, installLogBuffer, lblPayStatus, run_cmd_capture, resolve_ca_path, show_alert]() {
3697 lblPayStatus->set_text(
_(
"CEF e installazione CA non disponibili su questa build o distribuzione."));
3700 btnInstallSafeNav->set_sensitive(
false);
3701 installLogBuffer->set_text(
_(
"Avvio installazione navigazione sicura...\n"));
3705 std::thread([
this, alive =
alive_, btnInstallSafeNav, installLogBuffer, lblPayStatus, run_cmd_capture, resolve_ca_path, show_alert, log_popup]() {
3706 std::string final_log;
3707 bool install_ok =
false;
3708 bool restart_ok =
false;
3710 const std::string ca_src = resolve_ca_path();
3711 const std::string helper =
"/usr/share/BastionGuard/data/scripts/install-ca-system.sh";
3713 if (ca_src.empty()) {
3714 final_log +=
"[BastionGuard] CA non trovata nella home utente né in /etc.\n";
3715 }
else if (!std::filesystem::exists(helper)) {
3716 final_log += std::string(
"[BastionGuard] Helper non trovato: ") + helper +
"\n";
3718 final_log +=
"[BastionGuard] Uso certificato: " + ca_src +
"\n";
3719 final_log +=
"[BastionGuard] Eseguo install-ca-system.sh con pkexec...\n\n";
3721 std::string out1, err1;
3722 install_ok = run_cmd_capture({
"pkexec", helper, ca_src}, &out1, &err1);
3724 if (!out1.empty()) final_log += out1;
3725 if (!err1.empty()) {
3726 if (!final_log.empty() && final_log.back() !=
'\n') final_log +=
"\n";
3731 final_log +=
"\n[BastionGuard] pkexec fallito, tentativo fallback con sudo --non-interactive...\n";
3732 std::string out2, err2;
3733 install_ok = run_cmd_capture({
"sudo",
"--non-interactive", helper, ca_src}, &out2, &err2);
3735 if (!out2.empty()) final_log += out2;
3736 if (!err2.empty()) {
3737 if (!final_log.empty() && final_log.back() !=
'\n') final_log +=
"\n";
3743 final_log +=
"\n[BastionGuard] Installazione completata. Riavvio BastionGuard-cef.service (user)...\n";
3744 std::string out3, err3;
3745 restart_ok = run_cmd_capture(
3746 {
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-cef.service"},
3750 if (!out3.empty()) final_log += out3;
3751 if (!err3.empty()) {
3752 if (!final_log.empty() && final_log.back() !=
'\n') final_log +=
"\n";
3757 final_log +=
"\n[BastionGuard] ✅ BastionGuard-cef.service riavviato correttamente.\n";
3759 final_log +=
"\n[BastionGuard] ⚠️ Impossibile riavviare BastionGuard-cef.service in modalità user.\n";
3761 final_log +=
"\n[BastionGuard] ❌ Installazione navigazione sicura fallita.\n";
3766 if (!alive->load())
return;
3768 Glib::MainContext::get_default()->invoke(
3769 [
this, alive, btnInstallSafeNav, installLogBuffer, lblPayStatus, final_log, install_ok, restart_ok, show_alert, log_popup]() ->
bool {
3770 if (!alive->load())
return false;
3771 installLogBuffer->set_text(final_log);
3772 btnInstallSafeNav->set_sensitive(
true);
3775 log_popup->present();
3778 lblPayStatus->set_text(
_(
"✔ Navigazione sicura installata"));
3779 lblPayStatus->remove_css_class(
"status-muted");
3780 lblPayStatus->add_css_class(
"status-ok");
3782 auto dlg = Gtk::AlertDialog::create(
_(
"Installazione completata"));
3784 dlg->set_detail(
_(
"La navigazione sicura è stata installata correttamente.\n\nChiudi il browser e riaprilo."));
3786 dlg->set_detail(
_(
"La navigazione sicura è stata installata, ma il riavvio del servizio BastionGuard-cef non è riuscito.\n\nChiudi il browser e riaprilo."));
3790 lblPayStatus->set_text(
_(
"❌ Installazione navigazione sicura fallita"));
3791 lblPayStatus->remove_css_class(
"status-ok");
3792 lblPayStatus->add_css_class(
"status-muted");
3794 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
3795 dlg->set_detail(
_(
"Impossibile completare l'installazione della navigazione sicura. Controlla il log nella finestra aperta."));
3808 if (!cef_available) {
3809 swPay->set_active(
false);
3810 swPay->set_sensitive(
false);
3811 entry->set_sensitive(
false);
3812 btnAdd->set_sensitive(
false);
3813 btnRemove->set_sensitive(
false);
3814 btnReload->set_sensitive(
false);
3815 btnSave->set_sensitive(
false);
3816 btnInstallSafeNav->set_sensitive(
false);
3819 lblPayStatus->set_text(
_(
"⊘ CEF disabilitato su Fedora/RHEL/Alma/Rocky o escluso dalla build."));
3820 lblPayStatus->remove_css_class(
"status-ok");
3821 lblPayStatus->add_css_class(
"status-muted");
3824 framePay->set_child(*innerPay);
3825 page_pay->append(*framePay);
3828 stack.add(*page_pay,
"secure-payments",
_(
"Pagamenti Sicuri"));
3942 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
3943 page->set_margin(12);
3944 auto frame = Gtk::make_managed<Gtk::Frame>();
3945 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
3946 inner->set_margin(10);
3948 auto lblTitle = Gtk::make_managed<Gtk::Label>(
_(
"🔓 Bypass Proxy"));
3949 lblTitle->set_halign(Gtk::Align::START);
3950 lblTitle->add_css_class(
"card-title");
3951 inner->append(*lblTitle);
3953 auto lblDesc = Gtk::make_managed<Gtk::Label>(
3954 _(
"Domini, indirizzi IP e range CIDR in questa lista bypassano completamente il proxy BastionGuard.\n"
3955 "Il traffico viene inoltrato in tunnel TCP puro trasparente: nessuna\n"
3956 "intercettazione TLS, nessuna sostituzione certificati.\n\n"
3957 "Formati accettati:\n"
3958 " • dominio: banca.it, checkout.esempio.com\n"
3959 " • IPv4: 1.1.1.1\n"
3960 " • IPv6: 2001:db8::1\n"
3961 " • CIDR: 1.1.1.0/24, 2a01:4f8::/29\n\n"
3962 "Usare per siti che attivano 2FA o CAPTCHA quando rilevano la presenza del proxy.")
3964 lblDesc->set_wrap(
true);
3965 lblDesc->set_halign(Gtk::Align::START);
3966 lblDesc->add_css_class(
"card-description");
3967 inner->append(*lblDesc);
3969 auto lblInfo = Gtk::make_managed<Gtk::Label>(
3970 _(
"ℹ️ Anche le banche presenti in banks.json vengono bypassate se inserite qui.\n"
3971 "Rimuoverle dal bypass per ripristinare la protezione sandbox.")
3973 lblInfo->set_wrap(
true);
3974 lblInfo->set_halign(Gtk::Align::START);
3975 lblInfo->add_css_class(
"alert-info");
3976 inner->append(*lblInfo);
3978 auto lblList = Gtk::make_managed<Gtk::Label>(
_(
"Domini in bypass:"));
3979 lblList->set_halign(Gtk::Align::START);
3980 inner->append(*lblList);
3982 auto store = Gio::ListStore<Gtk::StringObject>::create();
3983 auto selection = Gtk::SingleSelection::create(store);
3985 auto factory = Gtk::SignalListItemFactory::create();
3986 factory->signal_setup().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
3987 auto lbl = Gtk::make_managed<Gtk::Label>();
3988 lbl->set_xalign(0.0);
3990 item->set_child(*lbl);
3992 factory->signal_bind().connect([](
const Glib::RefPtr<Gtk::ListItem>& item) {
3993 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(item->get_item());
3994 if (
auto lbl =
dynamic_cast<Gtk::Label*
>(item->get_child()))
3995 lbl->set_text(obj ? obj->get_string() :
"");
3998 auto listView = Gtk::make_managed<Gtk::ListView>(selection, factory);
3999 listView->set_vexpand(
true);
4000 listView->set_can_focus(
true);
4001 listView->add_css_class(
"whitelist-view");
4003 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
4004 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
4005 scrolled->set_expand(
true);
4006 scrolled->set_min_content_height(160);
4007 scrolled->set_child(*listView);
4008 inner->append(*scrolled);
4010 auto reload_from_file = [store]() {
4011 store->remove_all();
4013 store->append(Gtk::StringObject::create(d));
4017 auto entry = Gtk::make_managed<Gtk::Entry>();
4018 entry->set_placeholder_text(
_(
"dominio (banca.it), IP (1.1.1.1) o CIDR (1.1.1.0/24)"));
4019 entry->set_hexpand(
true);
4020 entry->add_css_class(
"whitelist-entry");
4022 auto btnAdd = Gtk::make_managed<Gtk::Button>(
_(
"Aggiungi"));
4023 auto btnRemove = Gtk::make_managed<Gtk::Button>(
_(
"Rimuovi"));
4024 auto btnReload = Gtk::make_managed<Gtk::Button>(
_(
"Ripristina"));
4025 auto btnSave = Gtk::make_managed<Gtk::Button>(
_(
"Salva"));
4026 btnSave->add_css_class(
"btn-primary");
4028 auto lblStatus = Gtk::make_managed<Gtk::Label>();
4029 lblStatus->set_halign(Gtk::Align::START);
4031 auto rowBtns = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
4032 rowBtns->append(*entry);
4033 rowBtns->append(*btnAdd);
4034 rowBtns->append(*btnRemove);
4035 rowBtns->append(*btnReload);
4036 rowBtns->append(*btnSave);
4037 inner->append(*rowBtns);
4038 inner->append(*lblStatus);
4040 auto show_alert_bypass = [
this](
const Glib::RefPtr<Gtk::AlertDialog>& dlg) {
4041 if (
auto* root = this->get_root())
4042 if (
auto* win =
dynamic_cast<Gtk::Window*
>(root)) {
4043 dlg->show(*win);
return;
4048 btnAdd->signal_clicked().connect([store, entry, lblStatus, show_alert_bypass]() {
4049 std::string s = entry->get_text();
4052 auto p = s.find(
"://");
4053 if (p != std::string::npos) s = s.substr(p + 3);
4057 if (!s.empty() && s.front() ==
'[') {
4058 auto rb = s.find(
']');
4059 if (rb != std::string::npos) {
4061 s = s.substr(0, rb + 1);
4065 auto sl = s.find(
'/');
4068 if (sl != std::string::npos) {
4069 std::string after = s.substr(sl + 1);
4070 bool only_digits = !after.empty();
4071 for (
char c : after)
4072 if (!std::isdigit(
static_cast<unsigned char>(c))) { only_digits =
false;
break; }
4073 if (!only_digits) s = s.substr(0, sl);
4076 if (std::count(s.begin(), s.end(),
':') == 1) {
4077 auto col = s.find(
':');
4078 s = s.substr(0, col);
4083 while (!s.empty() && std::isspace(
static_cast<unsigned char>(s.front()))) s.erase(s.begin());
4084 while (!s.empty() && std::isspace(
static_cast<unsigned char>(s.back()))) s.pop_back();
4088 std::transform(s.begin(), s.end(), s.begin(),
4089 [](
unsigned char c){ return static_cast<char>(std::tolower(c)); });
4093 auto dlg = Gtk::AlertDialog::create(
_(
"Valore non valido"));
4094 dlg->set_detail(
_(
"Formato non riconosciuto.\n\n"
4095 "Esempi accettati:\n"
4097 " • checkout.esempio.com\n"
4100 " • 93.55.84.0/24"));
4101 show_alert_bypass(dlg);
4106 for (guint i = 0; i < store->get_n_items(); ++i) {
4107 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(store->get_object(i));
4108 if (obj && obj->get_string() == s) { entry->set_text(
"");
return; }
4110 store->append(Gtk::StringObject::create(s));
4111 entry->set_text(
"");
4113 const char* kind_name =
"?";
4122 std::snprintf(buf,
sizeof(buf),
4123 _(
"➕ %s aggiunto — clicca Salva per applicare."), kind_name);
4124 lblStatus->set_text(buf);
4125 lblStatus->remove_css_class(
"status-ok");
4126 lblStatus->add_css_class(
"status-muted");
4129 btnRemove->signal_clicked().connect([selection, store, lblStatus]() {
4130 guint pos = selection->get_selected();
4131 if (pos != GTK_INVALID_LIST_POSITION) {
4133 lblStatus->set_text(
_(
"🗑 Dominio rimosso — clicca Salva per applicare."));
4134 lblStatus->remove_css_class(
"status-ok");
4135 lblStatus->add_css_class(
"status-muted");
4139 btnReload->signal_clicked().connect([reload_from_file, lblStatus]() {
4141 lblStatus->set_text(
_(
"↩️ Lista ripristinata dal file."));
4142 lblStatus->remove_css_class(
"status-ok");
4143 lblStatus->add_css_class(
"status-muted");
4146 btnSave->signal_clicked().connect([store, lblStatus, show_alert_bypass]() {
4147 std::vector<std::string> domains;
4148 domains.reserve(store->get_n_items());
4149 for (guint i = 0; i < store->get_n_items(); ++i) {
4150 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(store->get_object(i));
4151 if (obj) domains.push_back(obj->get_string());
4154 auto dlg = Gtk::AlertDialog::create(
_(
"Errore"));
4155 dlg->set_detail(
_(
"Impossibile salvare proxy_bypass.json."));
4156 show_alert_bypass(dlg);
4159 lblStatus->set_text(
_(
"✔ proxy_bypass.json salvato"));
4160 lblStatus->remove_css_class(
"status-muted");
4161 lblStatus->add_css_class(
"status-ok");
4165 (
bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-cef.service"}).exit_code);
4170 frame->set_child(*inner);
4171 page->append(*frame);
4173 stack.add(*page,
"proxy-bypass",
_(
"Bypass Proxy"));
4832 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
4833 page->set_hexpand(
true);
4834 page->set_vexpand(
true);
4836 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
4837 scrolled->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
4838 scrolled->set_hexpand(
true);
4839 scrolled->set_vexpand(
true);
4841 auto outer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
4842 outer->set_margin_top(28);
4843 outer->set_margin_bottom(28);
4844 outer->set_margin_start(24);
4845 outer->set_margin_end(24);
4846 outer->set_hexpand(
true);
4847 outer->set_vexpand(
true);
4849 auto center = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4850 center->set_halign(Gtk::Align::CENTER);
4851 center->set_valign(Gtk::Align::START);
4852 center->set_hexpand(
true);
4853 center->set_size_request(820, -1);
4855 auto frame = Gtk::make_managed<Gtk::Frame>();
4856 frame->set_hexpand(
true);
4858 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4859 inner->set_margin_top(20);
4860 inner->set_margin_bottom(20);
4861 inner->set_margin_start(20);
4862 inner->set_margin_end(20);
4864 auto title = Gtk::make_managed<Gtk::Label>(
_(
"Protezione Email"));
4865 title->set_halign(Gtk::Align::START);
4866 title->set_xalign(0.0f);
4867 title->add_css_class(
"card-title");
4868 inner->append(*title);
4870 auto tabs_switcher = Gtk::make_managed<Gtk::StackSwitcher>();
4871 auto tabs = Gtk::make_managed<Gtk::Stack>();
4872 tabs->set_hexpand(
true);
4873 tabs->set_vexpand(
true);
4874 tabs->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
4875 tabs->set_transition_duration(250);
4876 tabs_switcher->set_stack(*tabs);
4877 tabs_switcher->set_halign(Gtk::Align::FILL);
4878 tabs_switcher->set_hexpand(
true);
4880 auto profiles = std::make_shared<std::vector<MailRelayProfile>>(cfg.profiles);
4881 auto selected_profile = std::make_shared<int>(0);
4882 auto ui_updating = std::make_shared<bool>(
false);
4884 if (profiles->empty()) {
4889 profiles->push_back(p);
4892 auto mkLabel = [](
const Glib::ustring& text) {
4893 auto lbl = Gtk::make_managed<Gtk::Label>(text);
4894 lbl->set_halign(Gtk::Align::START);
4895 lbl->set_xalign(0.0f);
4899 auto make_switch_row = [](
const Glib::ustring& txt, Gtk::Switch& sw) {
4900 auto row = Gtk::make_managed<Gtk::CenterBox>();
4901 auto lbl = Gtk::make_managed<Gtk::Label>(txt);
4902 lbl->set_halign(Gtk::Align::START);
4903 lbl->set_xalign(0.0f);
4904 row->set_start_widget(*lbl);
4905 row->set_end_widget(sw);
4909 auto tab_general = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4910 tab_general->set_margin_top(12);
4911 tab_general->set_margin_bottom(12);
4912 tab_general->set_margin_start(12);
4913 tab_general->set_margin_end(12);
4915 auto swEnable = Gtk::make_managed<Gtk::Switch>();
4916 swEnable->set_active(cfg.enabled);
4918 auto swScan = Gtk::make_managed<Gtk::Switch>();
4919 swScan->set_active(cfg.scan_outgoing);
4921 auto swSig = Gtk::make_managed<Gtk::Switch>();
4922 swSig->set_active(cfg.inject_signature);
4924 auto swAdvertiseStartTls = Gtk::make_managed<Gtk::Switch>();
4925 swAdvertiseStartTls->set_active(cfg.advertise_starttls);
4927 auto swImplicitTlsListener = Gtk::make_managed<Gtk::Switch>();
4928 swImplicitTlsListener->set_active(cfg.enable_implicit_tls_listener);
4930 tab_general->append(*make_switch_row(
_(
"Abilita protezione email"), *swEnable));
4931 tab_general->append(*make_switch_row(
_(
"Scansiona email in uscita"), *swScan));
4932 tab_general->append(*make_switch_row(
_(
"Inserisci firma automatica"), *swSig));
4933 tab_general->append(*make_switch_row(
_(
"Pubblica STARTTLS"), *swAdvertiseStartTls));
4934 tab_general->append(*make_switch_row(
_(
"Abilita listener TLS implicito"), *swImplicitTlsListener));
4937 auto tab_signature = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4938 tab_signature->set_margin_top(12);
4939 tab_signature->set_margin_bottom(12);
4940 tab_signature->set_margin_start(12);
4941 tab_signature->set_margin_end(12);
4943 auto sName = Gtk::make_managed<Gtk::Entry>();
4944 auto sRole = Gtk::make_managed<Gtk::Entry>();
4945 auto sCompany = Gtk::make_managed<Gtk::Entry>();
4946 auto sPhone = Gtk::make_managed<Gtk::Entry>();
4947 auto sWeb = Gtk::make_managed<Gtk::Entry>();
4949 auto sig_grid = Gtk::make_managed<Gtk::Grid>();
4950 sig_grid->set_row_spacing(10);
4951 sig_grid->set_column_spacing(12);
4954 auto attach_sig_row = [&](
const Glib::ustring& lbl, Gtk::Widget& w) {
4955 sig_grid->attach(*mkLabel(lbl), 0, sr, 1, 1);
4956 sig_grid->attach(w, 1, sr, 1, 1);
4960 attach_sig_row(
_(
"Nome visualizzato"), *sName);
4961 attach_sig_row(
_(
"Ruolo"), *sRole);
4962 attach_sig_row(
_(
"Azienda"), *sCompany);
4963 attach_sig_row(
_(
"Telefono"), *sPhone);
4964 attach_sig_row(
_(
"Sito web"), *sWeb);
4966 auto fixedLogoInfo = Gtk::make_managed<Gtk::Label>(
4967 _(
"Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png")
4969 fixedLogoInfo->set_halign(Gtk::Align::START);
4970 fixedLogoInfo->set_xalign(0.0f);
4972 tab_signature->append(*sig_grid);
4973 tab_signature->append(*fixedLogoInfo);
4975 auto load_signature_fields = [=]() {
4976 if (profiles->empty())
return;
4977 *ui_updating =
true;
4979 auto& s = (*profiles)[*selected_profile].signature;
4980 sName->set_text(s.display_name);
4981 sRole->set_text(s.job_title);
4982 sCompany->set_text(s.company);
4983 sPhone->set_text(s.phone);
4984 sWeb->set_text(s.website);
4986 *ui_updating =
false;
4989 auto save_signature_fields = [=]() {
4990 if (profiles->empty())
return;
4991 auto& s = (*profiles)[*selected_profile].signature;
4992 s.display_name = sName->get_text();
4993 s.job_title = sRole->get_text();
4994 s.company = sCompany->get_text();
4995 s.phone = sPhone->get_text();
4996 s.website = sWeb->get_text();
4997 s.logo_path =
"/usr/share/BastionGuard/data/logo.png";
5000 auto tab_thunderbird = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
5001 tab_thunderbird->set_margin_top(12);
5002 tab_thunderbird->set_margin_bottom(12);
5003 tab_thunderbird->set_margin_start(12);
5004 tab_thunderbird->set_margin_end(12);
5006 auto tbInstallInfo = Gtk::make_managed<Gtk::Label>(
5007 _(
"Installa l'estensione BastionGuard per Thunderbird e il native host.\n"
5008 "Se necessario, inserisci la password amministratore per completare l'installazione.")
5010 tbInstallInfo->set_wrap(
true);
5011 tbInstallInfo->set_halign(Gtk::Align::START);
5012 tbInstallInfo->set_xalign(0.0f);
5013 tab_thunderbird->append(*tbInstallInfo);
5015 auto tbInstallGrid = Gtk::make_managed<Gtk::Grid>();
5016 tbInstallGrid->set_row_spacing(10);
5017 tbInstallGrid->set_column_spacing(12);
5021 auto lblAdminPw = Gtk::make_managed<Gtk::Label>(
_(
"Password amministratore"));
5022 lblAdminPw->set_halign(Gtk::Align::START);
5023 lblAdminPw->set_xalign(0.0f);
5025 auto eAdminPw = Gtk::make_managed<Gtk::Entry>();
5026 eAdminPw->set_visibility(
false);
5027 eAdminPw->set_hexpand(
true);
5028 eAdminPw->set_placeholder_text(
_(
"Inserisci la password sudo"));
5030 tbInstallGrid->attach(*lblAdminPw, 0, tbr, 1, 1);
5031 tbInstallGrid->attach(*eAdminPw, 1, tbr, 1, 1);
5034 tab_thunderbird->append(*tbInstallGrid);
5036 auto tbInstallButtons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
5038 auto btnInstallTbExt = Gtk::make_managed<Gtk::Button>(
_(
"Installa estensione Thunderbird"));
5039 btnInstallTbExt->add_css_class(
"suggested-action");
5041 auto btnClearTbLog = Gtk::make_managed<Gtk::Button>(
_(
"Pulisci log"));
5043 tbInstallButtons->append(*btnInstallTbExt);
5044 tbInstallButtons->append(*btnClearTbLog);
5045 tab_thunderbird->append(*tbInstallButtons);
5047 auto tbInstallOutput = Gtk::make_managed<Gtk::TextView>();
5048 tbInstallOutput->set_editable(
false);
5049 tbInstallOutput->set_cursor_visible(
false);
5050 tbInstallOutput->set_monospace(
true);
5051 tbInstallOutput->set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
5052 tbInstallOutput->set_vexpand(
true);
5054 auto tbInstallBuffer = Gtk::TextBuffer::create();
5055 tbInstallOutput->set_buffer(tbInstallBuffer);
5057 auto tbInstallScroll = Gtk::make_managed<Gtk::ScrolledWindow>();
5058 tbInstallScroll->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
5059 tbInstallScroll->set_min_content_height(260);
5060 tbInstallScroll->set_child(*tbInstallOutput);
5061 tab_thunderbird->append(*tbInstallScroll);
5063 btnClearTbLog->signal_clicked().connect([=]() {
5064 tbInstallBuffer->set_text(
"");
5067 auto run_tb_installer = [=]() {
5068 const std::string script_path =
5069 "/usr/share/BastionGuard/data/extension/bastionguard-tb-extension/install-tb-extension.sh";
5071 tbInstallBuffer->set_text(
"");
5073 if (!Glib::file_test(script_path, Glib::FileTest::IS_REGULAR)) {
5074 tbInstallBuffer->set_text(
5075 _(
"Script non trovato:\n") + Glib::ustring(script_path) +
"\n"
5080 const std::string password = std::string(eAdminPw->get_text());
5085 8U * 1024U * 1024U, {{
"BASTIONGUARD_SUDO_PASSWORD", password}});
5086 if (!install.started) {
5087 tbInstallBuffer->set_text(
_(
"Impossibile avviare lo script di installazione.\n"));
5090 const std::string& output = install.
output;
5091 const int exit_code = install.exit_code;
5093 if (exit_code == 0) {
5094 tbInstallBuffer->insert(
5095 tbInstallBuffer->end(),
5096 _(
"\n✔ Installazione Thunderbird completata.\n")
5099 tbInstallBuffer->insert(
5100 tbInstallBuffer->end(),
5101 Glib::ustring::compose(
_(
"\n❌ Installazione fallita (exit=%1)\n"), exit_code)
5106 btnInstallTbExt->signal_clicked().connect([=]() {
5111 auto tab_profiles = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
5112 tab_profiles->set_margin_top(12);
5113 tab_profiles->set_margin_bottom(12);
5114 tab_profiles->set_margin_start(12);
5115 tab_profiles->set_margin_end(12);
5117 auto lbl_profiles_info = Gtk::make_managed<Gtk::Label>(
5118 _(
"Seleziona i profili SMTP da importare da Thunderbird.\n"
5119 "Le password non sono esportabili: dovrai inserirle manualmente dopo l'importazione.")
5121 lbl_profiles_info->set_wrap(
true);
5122 lbl_profiles_info->set_halign(Gtk::Align::START);
5123 lbl_profiles_info->add_css_class(
"alert-info");
5124 tab_profiles->append(*lbl_profiles_info);
5126 auto tb_status = Gtk::make_managed<Gtk::Label>();
5127 tb_status->set_halign(Gtk::Align::START);
5128 tb_status->add_css_class(
"status-muted");
5129 tab_profiles->append(*tb_status);
5131 auto tb_list_frame = Gtk::make_managed<Gtk::Frame>(
_(
"Profili trovati in Thunderbird"));
5132 auto tb_list_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
5133 tb_list_box->set_margin(8);
5134 tb_list_frame->set_child(*tb_list_box);
5135 tab_profiles->append(*tb_list_frame);
5137 auto tb_found = std::make_shared<std::vector<MailRelayProfile>>();
5138 auto tb_selected = std::make_shared<std::vector<Gtk::CheckButton*>>();
5140 using PwEntriesPtr = std::shared_ptr<std::vector<Gtk::Entry*>>;
5141 auto tb_pw_entries_ref = std::make_shared<PwEntriesPtr>();
5143 using HostEntriesPtr = std::shared_ptr<std::vector<Gtk::Entry*>>;
5144 auto tb_host_entries_ref = std::make_shared<HostEntriesPtr>();
5146 using PortSpinsPtr = std::shared_ptr<std::vector<Gtk::SpinButton*>>;
5147 auto tb_port_spins_ref = std::make_shared<PortSpinsPtr>();
5149 auto populate_profile_list = [=](
const std::vector<MailRelayProfile>& source_profiles,
5151 for (
auto* w : tb_list_box->get_children())
5152 tb_list_box->remove(*w);
5155 tb_selected->clear();
5157 auto tb_pw_entries = std::make_shared<std::vector<Gtk::Entry*>>();
5158 auto tb_host_entries = std::make_shared<std::vector<Gtk::Entry*>>();
5159 auto tb_port_spins = std::make_shared<std::vector<Gtk::SpinButton*>>();
5161 for (
size_t i = 0; i < source_profiles.size(); ++i) {
5162 const auto& p = source_profiles[i];
5163 tb_found->push_back(p);
5165 auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
5166 row->set_margin_bottom(10);
5168 if (i == 0 && from_json) {
5169 auto sep_lbl = Gtk::make_managed<Gtk::Label>(
_(
"Profili attivi (da mail.json)"));
5170 sep_lbl->set_halign(Gtk::Align::START);
5171 sep_lbl->add_css_class(
"caption");
5172 sep_lbl->add_css_class(
"status-muted");
5173 tb_list_box->append(*sep_lbl);
5176 std::string title = p.label +
" — " + p.smtp_host +
":" + std::to_string(p.smtp_port);
5177 auto cb = Gtk::make_managed<Gtk::CheckButton>(title);
5178 cb->set_active(
true);
5180 cb->set_sensitive(
false);
5184 for (
size_t j = 0; j < p.match_from.size(); ++j) {
5185 if (j) emails +=
", ";
5186 emails += p.match_from[j];
5189 if (!emails.empty()) {
5190 auto lbl_emails = Gtk::make_managed<Gtk::Label>(
5191 " " + std::string(
_(
"Email: ")) + emails);
5192 lbl_emails->set_halign(Gtk::Align::START);
5193 lbl_emails->set_xalign(0.0f);
5194 lbl_emails->add_css_class(
"status-muted");
5195 row->append(*lbl_emails);
5198 const bool host_is_local = (
5199 p.smtp_host ==
"127.0.0.1" ||
5200 p.smtp_host ==
"127.0.0.4" ||
5201 p.smtp_host ==
"::1" ||
5202 p.smtp_host ==
"localhost" ||
5206 if (host_is_local && !from_json) {
5207 auto lbl_warn = Gtk::make_managed<Gtk::Label>(
5208 "⚠ " + std::string(
_(
"smtp_host non valido — inserisci il server SMTP reale")));
5209 lbl_warn->set_halign(Gtk::Align::START);
5210 lbl_warn->set_margin_start(24);
5211 lbl_warn->add_css_class(
"alert-warning");
5212 row->append(*lbl_warn);
5215 auto host_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
5216 host_row->set_margin_start(24);
5218 auto lbl_host = Gtk::make_managed<Gtk::Label>(
_(
"Server SMTP:"));
5219 lbl_host->set_halign(Gtk::Align::START);
5220 lbl_host->set_size_request(100, -1);
5222 auto host_entry = Gtk::make_managed<Gtk::Entry>();
5223 host_entry->set_text(host_is_local ?
"" : p.smtp_host);
5224 host_entry->set_placeholder_text(
_(
"es. mail.esempio.it"));
5225 host_entry->set_hexpand(
true);
5227 if (host_is_local && !from_json)
5228 host_entry->add_css_class(
"entry-error");
5231 host_entry->set_sensitive(!p.smtp_host.empty());
5233 host_row->append(*lbl_host);
5234 host_row->append(*host_entry);
5236 auto lbl_port = Gtk::make_managed<Gtk::Label>(
_(
"Porta:"));
5237 lbl_port->set_halign(Gtk::Align::START);
5239 auto port_spin = Gtk::make_managed<Gtk::SpinButton>();
5240 port_spin->set_range(1, 65535);
5241 port_spin->set_increments(1, 10);
5242 port_spin->set_value(host_is_local ? 587 : (p.smtp_port > 0 ? p.smtp_port : 587));
5243 port_spin->set_size_request(90, -1);
5246 port_spin->set_sensitive(
false);
5248 host_row->append(*lbl_port);
5249 host_row->append(*port_spin);
5250 row->append(*host_row);
5252 auto pw_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
5253 pw_row->set_margin_start(24);
5255 auto lbl_pw_label = Gtk::make_managed<Gtk::Label>(
_(
"Password:"));
5256 lbl_pw_label->set_halign(Gtk::Align::START);
5257 lbl_pw_label->set_size_request(100, -1);
5259 auto pw_entry = Gtk::make_managed<Gtk::Entry>();
5260 pw_entry->set_visibility(
false);
5261 pw_entry->set_placeholder_text(
5262 from_json ?
_(
"(salvata)") :
_(
"Inserisci la password SMTP")
5264 pw_entry->set_hexpand(
true);
5266 for (
const auto& existing : *profiles) {
5267 if ((existing.id == p.id || existing.smtp_host == p.smtp_host) &&
5268 !existing.password.empty() &&
5269 existing.password !=
"placeholder") {
5270 pw_entry->set_text(existing.password);
5275 pw_row->append(*lbl_pw_label);
5276 pw_row->append(*pw_entry);
5277 row->append(*pw_row);
5279 tb_list_box->append(*row);
5280 tb_selected->push_back(cb);
5281 tb_pw_entries->push_back(pw_entry);
5282 tb_host_entries->push_back(host_entry);
5283 tb_port_spins->push_back(port_spin);
5286 *tb_pw_entries_ref = tb_pw_entries;
5287 *tb_host_entries_ref = tb_host_entries;
5288 *tb_port_spins_ref = tb_port_spins;
5293 if (!existing_cfg.profiles.empty()) {
5294 tb_status->set_text(Glib::ustring::compose(
5295 _(
"✔ %1 profili attivi"), (
int)existing_cfg.profiles.size()));
5296 populate_profile_list(existing_cfg.profiles,
true);
5300 auto btn_scan_tb = Gtk::make_managed<Gtk::Button>(
_(
"🔍 Scansiona Thunderbird"));
5301 btn_scan_tb->add_css_class(
"suggested-action");
5302 tab_profiles->append(*btn_scan_tb);
5304 btn_scan_tb->signal_clicked().connect([=]() {
5308 if (scanned.empty()) {
5309 tb_status->set_text(
"❌ " + (err.empty()
5310 ? std::string(
_(
"Nessun profilo trovato"))
5315 tb_status->set_text(Glib::ustring::compose(
5316 _(
"✔ Trovati %1 profili SMTP da Thunderbird"), (
int)scanned.size()));
5318 populate_profile_list(scanned,
false);
5321 auto btn_import_tb = Gtk::make_managed<Gtk::Button>(
_(
"⬇ Importa profili selezionati"));
5322 tab_profiles->append(*btn_import_tb);
5324 btn_import_tb->signal_clicked().connect([=]() {
5325 if (tb_found->empty()) {
5326 tb_status->set_text(
_(
"❌ Nessun profilo da importare. Scansiona prima."));
5331 auto& pw_entries = *tb_pw_entries_ref;
5333 for (
size_t i = 0; i < tb_found->size() && i < tb_selected->size(); ++i) {
5334 if (!(*tb_selected)[i]->get_active())
5339 if (pw_entries && i < pw_entries->size()) {
5340 std::string pw = std::string((*pw_entries)[i]->get_text());
5345 auto& host_entries = *tb_host_entries_ref;
5346 auto& port_spins = *tb_port_spins_ref;
5348 if (host_entries && i < host_entries->size()) {
5349 std::string h = std::string((*host_entries)[i]->get_text());
5350 auto start = h.find_first_not_of(
" \t");
5351 if (start == std::string::npos) {
5354 auto end = h.find_last_not_of(
" \t");
5355 h = h.substr(start, end - start + 1);
5361 if (port_spins && i < port_spins->size()) {
5362 int pt = (*port_spins)[i]->get_value_as_int();
5363 if (pt > 0 && pt < 65536)
5368 for (
auto& existing : *profiles) {
5369 if (existing.id == src.
id || existing.smtp_host == src.
smtp_host) {
5377 profiles->push_back(src);
5378 if (profiles->size() == 1)
5379 *selected_profile = 0;
5391 for (
const auto& p : auto_save.
profiles) {
5397 if (!found && !auto_save.
profiles.empty())
5399 }
else if (!auto_save.
profiles.empty()) {
5404 (
bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-mailproxy.service"}).exit_code);
5405 tb_status->set_text(Glib::ustring::compose(
5406 _(
"✔ %1 profili salvati e proxy riavviato."),
5409 tb_status->set_text(Glib::ustring::compose(
5410 _(
"✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."),
5414 load_signature_fields();
5417 populate_profile_list(
5418 refreshed.profiles.empty() ? *profiles : refreshed.profiles,
5421 tb_status->set_text(
_(
"Nessun profilo selezionato."));
5425 tabs->add(*tab_general,
"general",
_(
"Generale"));
5426 tabs->add(*tab_signature,
"signature",
_(
"Firma"));
5427 tabs->add(*tab_profiles,
"smtp-profiles",
_(
"Profili SMTP"));
5428 tabs->add(*tab_thunderbird,
"thunderbird",
_(
"Thunderbird"));
5430 inner->append(*tabs_switcher);
5431 inner->append(*tabs);
5433 auto status = Gtk::make_managed<Gtk::Label>();
5434 status->set_halign(Gtk::Align::START);
5435 status->set_xalign(0.0f);
5436 status->add_css_class(
"status-muted");
5437 inner->append(*status);
5439 auto btnSave = Gtk::make_managed<Gtk::Button>(
_(
"Salva configurazione email"));
5440 btnSave->add_css_class(
"suggested-action");
5441 btnSave->set_halign(Gtk::Align::START);
5442 inner->append(*btnSave);
5444 btnSave->signal_clicked().connect([=]() {
5448 save_signature_fields();
5452 out.
enabled = swEnable->get_active();
5455 out.
local_host = cfg.local_host.empty() ?
"127.0.0.1" : cfg.local_host;
5456 out.
local_port = cfg.local_port > 0 ? cfg.local_port : 2525;
5457 out.
local_tls_port = cfg.local_tls_port > 0 ? cfg.local_tls_port : 2465;
5465 sig_ui.
company = sCompany->get_text();
5466 sig_ui.
phone = sPhone->get_text();
5467 sig_ui.
website = sWeb->get_text();
5468 sig_ui.
logo_path =
"/usr/share/BastionGuard/data/logo.png";
5474 sig_ui.
phone.empty() &&
5476 auto dlg = Gtk::AlertDialog::create(
_(
"Firma vuota"));
5477 dlg->set_detail(
_(
"La firma automatica è abilitata ma tutti i campi firma sono vuoti."));
5487 p.
id =
"bastionguard";
5488 p.
label =
"BastionGuard";
5494 p.signature = sig_ui;
5496 std::vector<std::string> loop_profiles;
5498 const std::string h = p.smtp_host;
5499 if (h ==
"127.0.0.1" || h ==
"127.0.0.4" || h ==
"::1" || h ==
"localhost") {
5500 loop_profiles.push_back(p.label.empty() ? p.id : p.label);
5505 if (!loop_profiles.empty()) {
5507 for (
const auto& n : loop_profiles)
5508 names +=
"• " + n +
"\n";
5510 auto dlg = Gtk::AlertDialog::create(
_(
"Profili SMTP non configurati"));
5512 _(
"I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n") +
5514 _(
"\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta.")
5519 bool def_found =
false;
5520 for (
const auto& p : out.
profiles) {
5531 status->set_text(
_(
"❌ Impossibile salvare la configurazione"));
5532 auto dlg = Gtk::AlertDialog::create(
_(
"Errore salvataggio"));
5533 dlg->set_detail(
_(
"Impossibile scrivere ~/.config/BastionGuard/mail.json"));
5538 (
bg_process::run({
"/usr/libexec/bastionguard/bastionguard-service",
"--user",
"restart",
"BastionGuard-mailproxy.service"}).exit_code);
5540 status->set_text(
_(
"✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"));
5542 populate_profile_list(out.
profiles,
true);
5543 tb_status->set_text(Glib::ustring::compose(
5544 _(
"✔ %1 profili attivi"), (
int)out.
profiles.size()));
5547 load_signature_fields();
5549 frame->set_child(*inner);
5550 center->append(*frame);
5551 outer->append(*center);
5552 scrolled->set_child(*outer);
5553 page->append(*scrolled);
5556 stack.add(*page,
"email",
_(
"Email"));