Skip to content
API Documentation
BastionGuard™
SettingsPage.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#include "Resource.hpp"
22#include "SettingsPage.hpp"
23#include "SafeProcess.hpp"
24#include "DistroUtils.hpp"
25#include "Utils.hpp"
26#include "pac/PacManager.hpp"
27#include "SettingsStore.hpp"
29#include "Backend.hpp"
30#include <iostream>
31#include <filesystem>
32#include <cstdlib>
33#include <chrono>
34#include <sys/wait.h>
35#include <ctime>
36#include <unistd.h>
37#include <gtkmm/listview.h>
38#include <gtkmm/entry.h>
39#include <gtkmm/alertdialog.h>
40#include <gtkmm/scrolledwindow.h>
41#include <gtkmm/searchentry.h>
42#include <gtkmm/singleselection.h>
43#include <gtkmm/signallistitemfactory.h>
44#include <giomm/liststore.h>
45#include <glibmm/miscutils.h>
46#include <glibmm/shell.h>
47#include <gtkmm/filechooserdialog.h>
48#include <gtkmm/messagedialog.h>
49#include <glibmm/miscutils.h>
50#include <gtkmm/filterlistmodel.h>
51#include <fstream>
52#include <regex>
53#include <pwd.h>
54#include <arpa/inet.h> // inet_pton per validazione IP/CIDR nel bypass proxy
55#include <unistd.h>
56#include <sys/stat.h>
57#include <glibmm/i18n.h>
58#include <array>
59#include <memory>
60#include <fcntl.h>
61#include <sys/stat.h>
62#include <gtkmm/grid.h>
63#include <gtkmm/spinbutton.h>
64#include <gtkmm/window.h>
65#include <sstream>
66#include <iomanip>
67
68namespace fs = std::filesystem;
69using json = nlohmann::json;
70
71namespace {
72 static bool run_cmd(const std::vector<std::string>& argv,
73 std::string* out_stdout = nullptr,
74 bool silence_stderr = true)
75 {
76 const auto result = bg_process::run(argv, !silence_stderr);
77 if (out_stdout) *out_stdout = result.output;
78 return result.ok();
79 }
80
81 static bool pkexec_servicectl(const std::vector<std::string>& args,
82 std::string* out_stdout = nullptr)
83 {
84 std::vector<std::string> argv;
85 argv.reserve(args.size() + 3);
86 argv.push_back("pkexec");
87 argv.push_back("/usr/libexec/bastionguard/bastionguard-service");
88 argv.push_back("--system");
89 argv.insert(argv.end(), args.begin(), args.end());
90
91 return run_cmd(argv, out_stdout, false);
92 }
93}
94
95
97 std::string display_name;
98 std::string job_title;
99 std::string company;
100 std::string phone;
101 std::string website;
102 std::string logo_path;
103};
104
106 std::string id;
107 std::string label;
108
109 std::vector<std::string> match_from;
110 std::vector<std::string> match_from_domain;
111
112 std::string smtp_host;
113 int smtp_port = 587;
114 bool starttls = true;
115 bool implicit_tls = false;
116
117 std::string username;
118 std::string password;
119
121};
122
124 int version = 2;
125 bool enabled = false;
126 bool scan_outgoing = true;
127 bool inject_signature = false;
128
129 std::string local_host = "127.0.0.1";
130 int local_port = 2525;
131 int local_tls_port = 2465;
133
136
138 std::vector<MailRelayProfile> profiles;
139};
140
141static std::string mail_json_path_user();
142static std::string iso_today_yyyy_mm_dd();
143
144static std::string mail_json_path_user() {
145 return Glib::get_home_dir() + "/.config/BastionGuard/mail.json";
146}
147
148static std::vector<std::string> split_semicolon_list(const std::string& input) {
149 std::vector<std::string> out;
150 std::string cur;
151
152 auto push_trimmed = [&](std::string s) {
153 auto l = s.find_first_not_of(" \t\r\n");
154 auto r = s.find_last_not_of(" \t\r\n");
155 if (l == std::string::npos) return;
156 s = s.substr(l, r - l + 1);
157 if (!s.empty()) out.push_back(s);
158 };
159
160 for (char c : input) {
161 if (c == ';') {
162 push_trimmed(cur);
163 cur.clear();
164 } else {
165 cur += c;
166 }
167 }
168 push_trimmed(cur);
169 return out;
170}
171
172static std::string join_semicolon_list(const std::vector<std::string>& items) {
173 std::ostringstream oss;
174 for (size_t i = 0; i < items.size(); ++i) {
175 if (i) oss << "; ";
176 oss << items[i];
177 }
178 return oss.str();
179}
180
181static std::string make_profile_id(const std::string& label) {
182 std::string out;
183 for (unsigned char c : label) {
184 if (std::isalnum(c)) out += (char)std::tolower(c);
185 else if (c == ' ' || c == '-' || c == '_') out += '-';
186 }
187
188 while (out.find("--") != std::string::npos)
189 out = std::regex_replace(out, std::regex("--"), "-");
190
191 if (!out.empty() && out.front() == '-') out.erase(out.begin());
192 if (!out.empty() && out.back() == '-') out.pop_back();
193 if (out.empty()) out = "profile";
194 return out;
195}
196
198 const std::string path = mail_json_path_user();
199
200 try {
201 std::filesystem::create_directories(std::filesystem::path(path).parent_path());
202 } catch (...) {}
203
204 if (std::filesystem::exists(path)) return;
205
206 json j;
207 j["version"] = 2;
208 j["enabled"] = false;
209 j["scan_outgoing"] = true;
210 j["inject_signature"] = false;
211 j["local_smtp_host"] = "127.0.0.1";
212 j["local_smtp_port"] = 2525;
213 j["default_profile_id"] = "default";
214
215 j["profiles"] = json::array({
216 {
217 {"id", "default"},
218 {"label", "Default"},
219 {"match_from", json::array()},
220 {"match_from_domain", json::array()},
221 {"smtp_host", ""},
222 {"smtp_port", 587},
223 {"starttls", true},
224 {"implicit_tls", false},
225 {"username", ""},
226 {"password", ""},
227 {"signature", {
228 {"display_name", ""},
229 {"job_title", ""},
230 {"company", ""},
231 {"phone", ""},
232 {"website", ""},
233 {"logo_path", ""}
234 }}
235 }
236 });
237
238 std::ofstream out(path, std::ios::binary | std::ios::trunc);
239 if (out.good()) out << j.dump(2);
240}
241
242
243static bool run_privhelper_and_capture(const std::vector<std::string>& args,
244 std::string& stdout_out,
245 std::string& stderr_out,
246 int& exit_status)
247{
248 try {
249 std::vector<std::string> argv;
250 argv.push_back("pkexec");
251 argv.push_back("/usr/bin/bastionguard-emailproxy");
252 argv.insert(argv.end(), args.begin(), args.end());
253
254 auto proc = Gio::Subprocess::create(
255 argv,
256 Gio::Subprocess::Flags::STDOUT_PIPE |
257 Gio::Subprocess::Flags::STDERR_PIPE
258 );
259
260 auto result = proc->communicate_utf8("");
261 stdout_out = result.first;
262 stderr_out = result.second;
263
264 if (proc->get_if_exited())
265 exit_status = proc->get_exit_status();
266 else
267 exit_status = -1;
268
269 return proc->get_if_exited() && exit_status == 0;
270 } catch (const std::exception& e) {
271 stdout_out.clear();
272 stderr_out = e.what();
273 exit_status = -1;
274 return false;
275 }
276}
277
278static bool write_mail_config_temp_json(const MailSecurityConfig& cfg, std::string& out_path) {
279 try {
280 auto tmpdir = Glib::get_tmp_dir();
281 out_path = Glib::build_filename(tmpdir, "bastionguard-mail-config.json");
282
283 json j;
284 j["version"] = cfg.version;
285 j["enabled"] = cfg.enabled;
286 j["scan_outgoing"] = cfg.scan_outgoing;
287 j["inject_signature"] = cfg.inject_signature;
288 j["local_smtp_host"] = cfg.local_host;
289 j["local_smtp_port"] = cfg.local_port;
290 j["local_smtp_tls_port"] = cfg.local_tls_port;
291 j["advertise_starttls"] = cfg.advertise_starttls;
292 j["enable_implicit_tls_listener"] = cfg.enable_implicit_tls_listener;
293 j["default_profile_id"] = cfg.default_profile_id;
294
295 j["profiles"] = json::array();
296 for (const auto& p : cfg.profiles) {
297 json jp;
298 jp["id"] = p.id;
299 jp["label"] = p.label;
300 jp["match_from"] = p.match_from;
301 jp["match_from_domain"] = p.match_from_domain;
302 jp["smtp_host"] = p.smtp_host;
303 jp["smtp_port"] = p.smtp_port;
304 jp["starttls"] = p.starttls;
305 jp["implicit_tls"] = p.implicit_tls;
306 jp["username"] = p.username;
307 jp["password"] = p.password;
308
309 jp["signature"] = {
310 {"display_name", p.signature.display_name},
311 {"job_title", p.signature.job_title},
312 {"company", p.signature.company},
313 {"phone", p.signature.phone},
314 {"website", p.signature.website},
315 {"logo_path", "/usr/share/BastionGuard/data/logo.png"}
316 };
317
318 j["profiles"].push_back(jp);
319 }
320
321 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
322 if (!out.good())
323 return false;
324
325 out << j.dump(2);
326 return out.good();
327 } catch (...) {
328 return false;
329 }
330}
331
332static void append_to_textbuffer(const Glib::RefPtr<Gtk::TextBuffer>& buffer,
333 const Glib::ustring& text)
334{
335 buffer->insert(buffer->end(), text);
336 auto mark = buffer->create_mark(buffer->end());
337}
338
341
343 const std::string path = mail_json_path_user();
344
345 try {
346 std::ifstream in(path, std::ios::binary);
347 if (!in.good()) return cfg;
348
349 json j;
350 in >> j;
351
352 cfg.version = j.value("version", 2);
353 cfg.enabled = j.value("enabled", false);
354 cfg.scan_outgoing = j.value("scan_outgoing", true);
355 cfg.inject_signature = j.value("inject_signature", false);
356 cfg.local_host = j.value("local_smtp_host", std::string("127.0.0.1"));
357 cfg.local_port = j.value("local_smtp_port", 2525);
358 cfg.local_tls_port = j.value("local_smtp_tls_port", 2465);
359 cfg.local_submission_port = j.value("local_submission_port", 2587);
360 cfg.advertise_starttls = j.value("advertise_starttls", true);
361 cfg.enable_implicit_tls_listener = j.value("enable_implicit_tls_listener", true);
362 cfg.default_profile_id = j.value("default_profile_id", std::string("default"));
363
364 if (j.contains("profiles") && j["profiles"].is_array()) {
365 for (const auto& p : j["profiles"]) {
366 if (!p.is_object()) continue;
367
368 MailRelayProfile prof;
369 prof.id = p.value("id", std::string());
370 prof.label = p.value("label", std::string("Profile"));
371 prof.smtp_host = p.value("smtp_host", std::string());
372 prof.smtp_port = p.value("smtp_port", 587);
373 prof.starttls = p.value("starttls", true);
374 prof.implicit_tls = p.value("implicit_tls", false);
375 prof.username = p.value("username", std::string());
376 prof.password = p.value("password", std::string());
377
378 if (p.contains("match_from") && p["match_from"].is_array()) {
379 for (const auto& v : p["match_from"])
380 if (v.is_string()) prof.match_from.push_back(v.get<std::string>());
381 }
382
383 if (p.contains("match_from_domain") && p["match_from_domain"].is_array()) {
384 for (const auto& v : p["match_from_domain"])
385 if (v.is_string()) prof.match_from_domain.push_back(v.get<std::string>());
386 }
387
388 if (p.contains("signature") && p["signature"].is_object()) {
389 const auto& s = p["signature"];
390 prof.signature.display_name = s.value("display_name", std::string());
391 prof.signature.job_title = s.value("job_title", std::string());
392 prof.signature.company = s.value("company", std::string());
393 prof.signature.phone = s.value("phone", std::string());
394 prof.signature.website = s.value("website", std::string());
395 prof.signature.logo_path = s.value("logo_path", std::string());
396 }
397
398 if (prof.id.empty())
399 prof.id = make_profile_id(prof.label);
400
401 cfg.profiles.push_back(std::move(prof));
402 }
403 }
404
405 if (cfg.profiles.empty()) {
407 p.id = "default";
408 p.label = "Default";
409 cfg.profiles.push_back(std::move(p));
410 cfg.default_profile_id = "default";
411 }
412 } catch (...) {
413 }
414
415 return cfg;
416}
417
420 const std::string path = mail_json_path_user();
421
422 try {
423 json j;
424 j["version"] = 2;
425 j["updated_at"] = iso_today_yyyy_mm_dd();
426 j["enabled"] = cfg.enabled;
427 j["scan_outgoing"] = cfg.scan_outgoing;
428 j["inject_signature"] = cfg.inject_signature;
429 j["local_smtp_host"] = cfg.local_host;
430 j["local_smtp_port"] = cfg.local_port;
431 j["local_smtp_tls_port"] = cfg.local_tls_port;
432 j["local_submission_port"] = cfg.local_submission_port;
433 j["advertise_starttls"] = cfg.advertise_starttls;
434 j["enable_implicit_tls_listener"] = cfg.enable_implicit_tls_listener;
435 j["default_profile_id"] = cfg.default_profile_id;
436 j["profiles"] = json::array();
437
438 for (const auto& p : cfg.profiles) {
439 json jp;
440 jp["id"] = p.id;
441 jp["label"] = p.label;
442 jp["match_from"] = p.match_from;
443 jp["match_from_domain"] = p.match_from_domain;
444 jp["smtp_host"] = p.smtp_host;
445 jp["smtp_port"] = p.smtp_port;
446 jp["starttls"] = p.starttls;
447 jp["implicit_tls"] = p.implicit_tls;
448 jp["username"] = p.username;
449 jp["password"] = p.password;
450 jp["signature"] = {
451 {"display_name", p.signature.display_name},
452 {"job_title", p.signature.job_title},
453 {"company", p.signature.company},
454 {"phone", p.signature.phone},
455 {"website", p.signature.website},
456 {"logo_path", p.signature.logo_path}
457 };
458 j["profiles"].push_back(std::move(jp));
459 }
460
461 const std::string tmp = path + ".tmp";
462 {
463 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
464 if (!out.good()) return false;
465 out << j.dump(2);
466 out.flush();
467 if (!out.good()) return false;
468 }
469
470 std::error_code ec;
471 std::filesystem::rename(tmp, path, ec);
472 if (ec) {
473 std::filesystem::remove(path, ec);
474 ec.clear();
475 std::filesystem::rename(tmp, path, ec);
476 if (ec) {
477 std::filesystem::remove(tmp, ec);
478 return false;
479 }
480 }
481
482 return true;
483 } catch (...) {
484 return false;
485 }
486}
487
489 std::ostringstream html;
490
491 html << "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" "
492 << "style=\"font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#212529;\">"
493 << "<tr>";
494
495 if (!sig.logo_path.empty()) {
496 html << "<td style=\"vertical-align:top;padding-right:12px;\">"
497 << "<img src=\"file://" << sig.logo_path << "\" alt=\"logo\" width=\"72\" "
498 << "style=\"display:block;border:0;\">"
499 << "</td>";
500 }
501
502 html << "<td style=\"vertical-align:top;\">";
503
504 if (!sig.display_name.empty()) {
505 html << "<div style=\"font-size:16px;font-weight:700;color:#0d6efd;\">"
506 << sig.display_name << "</div>";
507 }
508
509 {
510 std::string roleLine;
511 if (!sig.job_title.empty()) roleLine += sig.job_title;
512 if (!sig.job_title.empty() && !sig.company.empty()) roleLine += " · ";
513 if (!sig.company.empty()) roleLine += sig.company;
514
515 if (!roleLine.empty()) {
516 html << "<div style=\"font-size:13px;color:#6c757d;\">"
517 << roleLine << "</div>";
518 }
519 }
520
521 html << "<div style=\"margin-top:8px;\">";
522
523 if (!sig.website.empty()) {
524 html << "<a href=\"" << sig.website << "\" "
525 << "style=\"color:#0d6efd;text-decoration:none;\">"
526 << sig.website << "</a><br>";
527 }
528
529 if (!sig.phone.empty()) {
530 html << "<span style=\"color:#212529;\">" << sig.phone << "</span><br>";
531 }
532
533 html << "<span style=\"color:#6c757d;\">Protected by BastionGuard</span>";
534 html << "</div>";
535 html << "</td></tr></table>";
536
537 return html.str();
538}
539
540static bool is_clamav_signature_file(const std::filesystem::path& p)
541{
542 if (!p.has_extension()) return false;
543
544 std::string ext = p.extension().string();
545 std::transform(ext.begin(), ext.end(), ext.begin(),
546 [](unsigned char c){ return std::tolower(c); });
547
548 // Estensioni comuni DB/firme ClamAV
549 static const std::set<std::string> exts = {
550 ".ndb", ".hdb", ".hsb", ".ldb", ".sdb", ".fp", ".mdb", ".pdb",
551 ".wdb", ".cbc", ".cdb", ".cat"
552 };
553
554 if (exts.find(ext) == exts.end())
555 return false;
556
557 // scarta file nascosti
558 const auto name = p.filename().string();
559 if (!name.empty() && name[0] == '.') return false;
560
561 return true;
562}
563
564static Gtk::Window* create_log_popup(Gtk::Widget* owner, const Glib::RefPtr<Gtk::TextBuffer>& buffer)
565{
566 auto* win = new Gtk::Window();
567 win->set_title(_("Installazione navigazione sicura"));
568 win->set_default_size(760, 420);
569 win->set_modal(true);
570 win->set_resizable(true);
571
572 if (owner) {
573 if (auto* root = owner->get_root()) {
574 if (auto* parent = dynamic_cast<Gtk::Window*>(root)) {
575 win->set_transient_for(*parent);
576 }
577 }
578 }
579
580 auto* outer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
581 outer->set_margin_top(12);
582 outer->set_margin_bottom(12);
583 outer->set_margin_start(12);
584 outer->set_margin_end(12);
585
586 auto* title = Gtk::make_managed<Gtk::Label>(_("Log installazione"));
587 title->set_halign(Gtk::Align::START);
588 title->add_css_class("title-4");
589 outer->append(*title);
590
591 auto* text = Gtk::make_managed<Gtk::TextView>();
592 text->set_buffer(buffer);
593 text->set_editable(false);
594 text->set_cursor_visible(false);
595 text->set_monospace(true);
596 text->set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
597 text->set_vexpand(true);
598 text->set_hexpand(true);
599
600 auto* scroll = Gtk::make_managed<Gtk::ScrolledWindow>();
601 scroll->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
602 scroll->set_min_content_height(320);
603 scroll->set_child(*text);
604 outer->append(*scroll);
605
606 auto* btnClose = Gtk::make_managed<Gtk::Button>(_("Chiudi"));
607 btnClose->set_halign(Gtk::Align::END);
608 btnClose->signal_clicked().connect([win]() {
609 win->hide();
610 delete win;
611 });
612 outer->append(*btnClose);
613
614 win->set_child(*outer);
615 win->present();
616 return win;
617}
618
619static std::vector<std::filesystem::path> list_clamav_signatures(const std::filesystem::path& dir)
620{
621 std::vector<std::filesystem::path> out;
622 std::error_code ec;
623
624 if (!std::filesystem::exists(dir, ec) || !std::filesystem::is_directory(dir, ec))
625 return out;
626
627 for (const auto& entry : std::filesystem::directory_iterator(dir, ec))
628 {
629 if (ec) break;
630 if (!entry.is_regular_file(ec)) continue;
631
632 const auto& p = entry.path();
634 out.push_back(p);
635 }
636
637 std::sort(out.begin(), out.end(),
638 [](const auto& a, const auto& b){
639 return a.filename().string() < b.filename().string();
640 });
641
642 return out;
643}
644
645static std::string payments_json_path_user() {
646 return Glib::get_home_dir() + "/.config/BastionGuard/payments.json";
647}
648
649
651 std::string id;
652 std::string hostname;
653 int port = 587;
654 std::string username;
655 int try_ssl = 0;
656 std::string description;
657};
658
660 std::string email;
661 std::string smtp_server;
662};
663
664static bool tb_parse_pref_line(const std::string& line,
665 std::string& key, std::string& value) {
666 const std::string prefix = "user_pref(\"";
667 if (line.rfind(prefix, 0) != 0) return false;
668
669 auto ks = prefix.size();
670 auto ke = line.find('"', ks);
671 if (ke == std::string::npos) return false;
672 key = line.substr(ks, ke - ks);
673
674 auto vs = line.find(',', ke);
675 if (vs == std::string::npos) return false;
676 ++vs;
677 while (vs < line.size() && line[vs] == ' ') ++vs;
678
679 auto ve = line.rfind(')');
680 if (ve == std::string::npos || ve <= vs) return false;
681 value = line.substr(vs, ve - vs);
682
683
684 if (value.size() >= 2 && value.front() == '"' && value.back() == '"')
685 value = value.substr(1, value.size() - 2);
686
687 return true;
688}
689
690static std::map<std::string, std::map<std::string,std::string>>
691tb_parse_ini_file(const std::string& path) {
692 std::map<std::string, std::map<std::string,std::string>> sections;
693 std::ifstream f(path);
694 if (!f.is_open()) return sections;
695
696 std::string line, sec;
697 while (std::getline(f, line)) {
698 // trim
699 auto s = line.find_first_not_of(" \t\r\n");
700 if (s == std::string::npos || line[s] == ';' || line[s] == '#') continue;
701 line = line.substr(s);
702 auto e = line.find_last_not_of(" \t\r\n");
703 if (e != std::string::npos) line = line.substr(0, e + 1);
704
705 if (line.front() == '[' && line.back() == ']') {
706 sec = line.substr(1, line.size() - 2);
707 continue;
708 }
709 auto eq = line.find('=');
710 if (eq == std::string::npos || sec.empty()) continue;
711 sections[sec][line.substr(0, eq)] = line.substr(eq + 1);
712 }
713 return sections;
714}
715
716static std::string tb_find_default_profile() {
717 const std::string base = Glib::get_home_dir() + "/.thunderbird";
718
719 {
720 auto installs = tb_parse_ini_file(base + "/installs.ini");
721 for (const auto& [sec, kv] : installs) {
722 auto it = kv.find("Default");
723 if (it == kv.end()) continue;
724 std::string path = it->second;
725 if (!path.empty() && path.front() != '/')
726 path = base + "/" + path;
727 if (std::filesystem::exists(path + "/prefs.js"))
728 return path;
729 }
730 }
731
732 auto sections = tb_parse_ini_file(base + "/profiles.ini");
733
734 std::string best_default_release;
735 std::string best_default_flag;
736 std::string best_any;
737
738 for (const auto& [sec, kv] : sections) {
739 if (sec.rfind("Profile", 0) != 0) continue;
740 auto it_path = kv.find("Path");
741 if (it_path == kv.end()) continue;
742
743 std::string path = it_path->second;
744 auto it_rel = kv.find("IsRelative");
745 bool rel = (it_rel == kv.end() || it_rel->second != "0");
746 if (rel) path = base + "/" + path;
747
748 if (!std::filesystem::exists(path + "/prefs.js")) continue;
749
750 // Default=1
751 auto it_def = kv.find("Default");
752 if (it_def != kv.end() && it_def->second == "1" && best_default_flag.empty())
753 best_default_flag = path;
754
755 // suffisso .default-release
756 const std::string suffix = ".default-release";
757 if (path.size() >= suffix.size() &&
758 path.compare(path.size() - suffix.size(), suffix.size(), suffix) == 0 &&
759 best_default_release.empty())
760 best_default_release = path;
761
762 if (best_any.empty()) best_any = path;
763 }
764
765 if (!best_default_flag.empty()) return best_default_flag;
766 if (!best_default_release.empty()) return best_default_release;
767 return best_any;
768}
769
770static std::vector<MailRelayProfile> tb_import_profiles(std::string& err_out) {
771 std::vector<MailRelayProfile> result;
772 err_out.clear();
773
774 const std::string profile_dir = tb_find_default_profile();
775 if (profile_dir.empty()) {
776 err_out = _("Profilo Thunderbird non trovato");
777 return result;
778 }
779
780 const std::string prefs_path = profile_dir + "/prefs.js";
781 std::ifstream in(prefs_path);
782 if (!in.is_open()) {
783 err_out = _("prefs.js non trovato: ") + prefs_path;
784 return result;
785 }
786
787 std::map<std::string, TbSmtpEntry> smtp_map;
788 std::map<std::string, TbIdentityEntry> identity_map;
789
790 std::string line;
791 while (std::getline(in, line)) {
792 std::string key, value;
793 if (!tb_parse_pref_line(line, key, value)) continue;
794
795 if (key.rfind("mail.smtpserver.", 0) == 0) {
796 auto rest = key.substr(16);
797 auto dot = rest.find('.');
798 if (dot == std::string::npos) continue;
799 auto sid = rest.substr(0, dot);
800 auto field = rest.substr(dot + 1);
801 auto& s = smtp_map[sid];
802 s.id = sid;
803 if (field == "hostname") s.hostname = value;
804 else if (field == "port") s.port = std::atoi(value.c_str());
805 else if (field == "username") s.username = value;
806 else if (field == "try_ssl") s.try_ssl = std::atoi(value.c_str());
807 else if (field == "description") s.description = value;
808 }
809 else if (key.rfind("mail.identity.", 0) == 0) {
810 auto rest = key.substr(14);
811 auto dot = rest.find('.');
812 if (dot == std::string::npos) continue;
813 auto iid = rest.substr(0, dot);
814 auto field = rest.substr(dot + 1);
815 auto& id = identity_map[iid];
816 if (field == "useremail") id.email = value;
817 else if (field == "smtpServer") id.smtp_server = value;
818 }
819 }
820
821 std::map<std::string, MailRelayProfile> built;
822
823 for (const auto& [iid, ident] : identity_map) {
824 if (ident.email.empty() || ident.smtp_server.empty()) continue;
825 auto it = smtp_map.find(ident.smtp_server);
826 if (it == smtp_map.end()) continue;
827
828 const auto& s = it->second;
829 auto& p = built[s.id];
830
831 if (p.id.empty()) {
832 p.id = s.id;
833 p.label = s.description.empty() ? s.hostname : s.description;
834 p.smtp_host = s.hostname;
835 p.smtp_port = (s.port > 0 ? s.port : 587);
836 p.username = s.username;
837 p.password = "";
838 p.starttls = (s.try_ssl == 2);
839 p.implicit_tls = (s.try_ssl == 3);
840 }
841
842 std::string email_lc = ident.email;
843 std::transform(email_lc.begin(), email_lc.end(), email_lc.begin(), ::tolower);
844 if (std::find(p.match_from.begin(), p.match_from.end(), email_lc) == p.match_from.end())
845 p.match_from.push_back(email_lc);
846
847 auto at = email_lc.find('@');
848 if (at != std::string::npos) {
849 std::string dom = email_lc.substr(at + 1);
850 if (std::find(p.match_from_domain.begin(), p.match_from_domain.end(), dom)
851 == p.match_from_domain.end())
852 p.match_from_domain.push_back(dom);
853 }
854 }
855
856 for (auto& [id, p] : built)
857 result.push_back(std::move(p));
858
859 if (result.empty())
860 err_out = _("Nessun server SMTP trovato in Thunderbird");
861
862 return result;
863}
864
865static std::string iso_today_yyyy_mm_dd() {
866 std::time_t t = std::time(nullptr);
867 std::tm tm{};
868 localtime_r(&t, &tm);
869 std::ostringstream oss;
870 oss << std::put_time(&tm, "%Y-%m-%d");
871 return oss.str();
872}
873
874static std::string normalize_domain_or_url(const std::string& in_raw) {
875 std::string in = in_raw;
876 auto l = in.find_first_not_of(" \t\r\n");
877 auto r = in.find_last_not_of(" \t\r\n");
878 if (l == std::string::npos) return {};
879 in = in.substr(l, r - l + 1);
880 std::string s = in;
881 auto pos = s.find("://");
882 if (pos != std::string::npos) s.erase(0, pos + 3);
883 pos = s.find('/');
884 if (pos != std::string::npos) s.erase(pos);
885
886 pos = s.find('@');
887 if (pos != std::string::npos) s = s.substr(pos + 1);
888
889 pos = s.find(':');
890 if (pos != std::string::npos) s.erase(pos);
891
892 if (s.rfind("www.", 0) == 0) s.erase(0, 4);
893
894 std::transform(s.begin(), s.end(), s.begin(),
895 [](unsigned char c){ return std::tolower(c); });
896
897 static const std::regex re(R"(^[a-z0-9][a-z0-9.-]*[a-z0-9]$)");
898 if (!std::regex_match(s, re)) return {};
899
900 if (s.find('.') == std::string::npos) return {};
901
902 return s;
903}
904
906 const std::string path = payments_json_path_user();
907 try {
908 fs::create_directories(fs::path(path).parent_path());
909 } catch (...) {}
910
911 if (fs::exists(path)) return;
912
913 json j;
914 j["version"] = 1;
915 j["updated_at"] = iso_today_yyyy_mm_dd();
916 j["enabled"] = false;
917 j["list"] = json::array({
918 "paypal.com",
919 "stripe.com",
920 "adyen.com",
921 "klarna.com",
922 "checkout.com",
923 "worldpay.com",
924 "braintreepayments.com",
925 "braintreegateway.com",
926 "amazonpay.com",
927 "pay.google.com",
928 "pay.apple.com",
929 "amazon.com"
930 });
931
932 std::ofstream out(path, std::ios::binary | std::ios::trunc);
933 if (out.good()) out << j.dump(2);
934}
935
936static std::vector<std::string> load_payments_domains() {
938
939 const std::string path = payments_json_path_user();
940 std::vector<std::string> out;
941
942 try {
943 std::ifstream in(path, std::ios::binary);
944 if (!in.good()) return out;
945
946 json j;
947 in >> j;
948
949 if (j.contains("list") && j["list"].is_array()) {
950 for (const auto& v : j["list"]) {
951 if (!v.is_string()) continue;
952 std::string d = normalize_domain_or_url(v.get<std::string>());
953 if (!d.empty()) out.push_back(d);
954 }
955 }
956
957 std::sort(out.begin(), out.end());
958 out.erase(std::unique(out.begin(), out.end()), out.end());
959
960 } catch (...) {
961 }
962
963 return out;
964}
965
966static bool save_payments_domains(const std::vector<std::string>& domains_in, bool enabled_flag) {
968
969 const std::string path = payments_json_path_user();
970
971 std::vector<std::string> domains;
972 domains.reserve(domains_in.size());
973 for (const auto& s : domains_in) {
974 auto d = normalize_domain_or_url(s);
975 if (!d.empty()) domains.push_back(d);
976 }
977 std::sort(domains.begin(), domains.end());
978 domains.erase(std::unique(domains.begin(), domains.end()), domains.end());
979
980 try {
981 json j;
982
983 {
984 std::ifstream in(path, std::ios::binary);
985 if (in.good()) {
986 try { in >> j; } catch (...) { j = json::object(); }
987 }
988 }
989
990 if (!j.is_object()) j = json::object();
991
992 j["version"] = 1;
993 j["updated_at"] = iso_today_yyyy_mm_dd();
994 j["enabled"] = enabled_flag;
995 j["list"] = json::array();
996 for (const auto& d : domains) j["list"].push_back(d);
997
998 const std::string tmp = path + ".tmp";
999 {
1000 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
1001 if (!out.good()) return false;
1002 out << j.dump(2);
1003 out.flush();
1004 if (!out.good()) return false;
1005 }
1006
1007 std::error_code ec;
1008 fs::rename(tmp, path, ec);
1009 if (ec) {
1010 fs::remove(path, ec);
1011 ec.clear();
1012 fs::rename(tmp, path, ec);
1013 if (ec) {
1014 fs::remove(tmp, ec);
1015 return false;
1016 }
1017 }
1018
1019 return true;
1020 } catch (...) {
1021 return false;
1022 }
1023}
1024
1025static bool update_payments_enabled_only(bool enabled_flag) {
1027
1028 const std::string path = payments_json_path_user();
1029 try {
1030 json j;
1031 {
1032 std::ifstream in(path, std::ios::binary);
1033 if (!in.good()) return false;
1034 in >> j;
1035 }
1036 if (!j.is_object()) j = json::object();
1037
1038 j["version"] = 1;
1039 j["updated_at"] = iso_today_yyyy_mm_dd();
1040 j["enabled"] = enabled_flag;
1041
1042 if (!j.contains("list") || !j["list"].is_array())
1043 j["list"] = json::array();
1044
1045 const std::string tmp = path + ".tmp";
1046 {
1047 std::ofstream out(tmp, std::ios::binary | std::ios::trunc);
1048 if (!out.good()) return false;
1049 out << j.dump(2);
1050 out.flush();
1051 if (!out.good()) return false;
1052 }
1053
1054 std::error_code ec;
1055 fs::rename(tmp, path, ec);
1056 if (ec) {
1057 fs::remove(path, ec);
1058 ec.clear();
1059 fs::rename(tmp, path, ec);
1060 if (ec) {
1061 fs::remove(tmp, ec);
1062 return false;
1063 }
1064 }
1065
1066 return true;
1067 } catch (...) {
1068 return false;
1069 }
1070}
1071
1072
1073
1074static std::string elev_cmd(const std::string& inner) {
1075 if (std::filesystem::exists("/usr/bin/pkexec")) return std::string("pkexec ") + inner;
1076 return std::string("sudo ") + inner;
1077}
1078
1079static bool write_temp_script_and_elevate(const std::string& script_contents, std::string* out_log = nullptr, int* exit_code = nullptr) {
1080 char tmpl[] = "/tmp/BastionGuard_tmp_XXXXXX";
1081 int fd = mkstemp(tmpl);
1082 if (fd < 0) {
1083 if (out_log) *out_log = _("Impossibile creare file temporaneo");
1084 return false;
1085 }
1086 std::string tmpname = tmpl;
1087 // write
1088 ssize_t written = write(fd, script_contents.c_str(), script_contents.size());
1089 close(fd);
1090 if (written < 0) {
1091 if (out_log) *out_log = _("Errore scrittura temporanea");
1092 std::filesystem::remove(tmpname);
1093 return false;
1094 }
1095 ::chmod(tmpname.c_str(), 0755);
1096
1097 const std::string runner = std::filesystem::exists("/usr/bin/pkexec") ? "pkexec" : "sudo";
1098 const auto result = bg_process::run({runner, tmpname}, true);
1099 std::string out = result.output;
1100 const int rc = result.exit_code;
1101 const bool ok = result.ok();
1102
1103 // pulizia
1104 std::filesystem::remove(tmpname);
1105
1106 if (out_log) *out_log = out;
1107 if (exit_code) *exit_code = rc;
1108 return ok;
1109}
1110
1111std::pair<int,int> read_webports_conf()
1112{
1113 constexpr int default_http_port = 81;
1114 constexpr int default_https_port = 444;
1115
1116 auto valid_port = [](int port) {
1117 return port > 0 && port < 65536;
1118 };
1119
1120 std::vector<std::string> paths = {
1121 "/etc/BastionGuard/webports.conf",
1122 "/tmp/BastionGuard_webports.conf"
1123 };
1124
1125 for (const auto& p : paths) {
1126 std::ifstream f(p);
1127 if (!f.is_open())
1128 continue;
1129
1130 std::stringstream buffer;
1131 buffer << f.rdbuf();
1132 const std::string content = buffer.str();
1133
1134 int http_port = default_http_port;
1135 int https_port = default_https_port;
1136 std::istringstream iss(content);
1137
1138 if ((iss >> http_port >> https_port) &&
1139 valid_port(http_port) && valid_port(https_port)) {
1140 std::cout << _("[settings] 📖 Letto webports.conf: ")
1141 << http_port << " " << https_port << std::endl;
1142 return {http_port, https_port};
1143 }
1144
1145 // Evita che un file scritto male venga interpretato come una singola porta HTTP.
1146 // La coppia supportata resta 81/444.
1147 std::cerr << _("[settings] ⚠️ webports.conf malformato, uso default 81/444: ")
1148 << content << std::endl;
1149 return {default_http_port, default_https_port};
1150 }
1151
1152 std::cout << _("[settings] ⚠️ Nessun webports.conf trovato, uso default 81/444\n");
1153 return {default_http_port, default_https_port};
1154}
1155
1156namespace fs = std::filesystem;
1157
1158bool copy_with_pkexec(const std::string &src, const std::string &dst)
1159{
1160 if (!fs::exists(src)) {
1161 std::cerr << _("[copy_with_pkexec] File sorgente non trovato: ") << src << std::endl;
1162 return false;
1163 }
1164
1165 return bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", src, dst}).ok();
1166}
1167
1168bool restart_service(const std::string &svc)
1169{
1170 return bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
1171 "--system", "restart", svc}).ok();
1172}
1173
1174bool update_ports_in_file(const std::string &file, int http_port, int https_port)
1175{
1176 if (!fs::exists(file)) {
1177 std::cerr << _("[update_ports_in_file] File non trovato: ") << file << std::endl;
1178 return false;
1179 }
1180
1181 std::ifstream fin(file);
1182 if (!fin.is_open()) {
1183 std::cerr << _("[update_ports_in_file] Impossibile aprire il file: ") << file << std::endl;
1184 return false;
1185 }
1186
1187 std::stringstream buffer;
1188 buffer << fin.rdbuf();
1189 fin.close();
1190 std::string content = buffer.str();
1191
1192 std::regex http_regex(R"((Listen\s+)80\b)");
1193 std::regex https_regex(R"((Listen\s+)443\b)");
1194 std::regex star_http(R"((\*|127\.0\.0\.1|\‍[::1\‍])?:80\b)");
1195 std::regex star_https(R"((\*|127\.0\.0\.1|\‍[::1\‍])?:443\b)");
1196
1197 content = std::regex_replace(content, http_regex, "Listen " + std::to_string(http_port));
1198 content = std::regex_replace(content, https_regex, "Listen " + std::to_string(https_port));
1199 content = std::regex_replace(content, star_http, "$1:" + std::to_string(http_port));
1200 content = std::regex_replace(content, star_https, "$1:" + std::to_string(https_port));
1201
1202 std::string tmpfile = "/tmp/BastionGuard-vhost.conf";
1203 std::ofstream fout(tmpfile);
1204 if (!fout.is_open()) {
1205 std::cerr << _("[update_ports_in_file] Impossibile scrivere il file temporaneo") << std::endl;
1206 return false;
1207 }
1208 fout << content;
1209 fout.close();
1210
1211 return bg_process::run({"pkexec", "/usr/bin/install", "-m", "0644", tmpfile, file}).ok();
1212}
1213
1214
1215
1216std::string detect_distro()
1217{
1218 std::string distro = "auto";
1219 std::string id;
1220
1221
1222 {
1223 const auto lsb = bg_process::run({"lsb_release", "-si"});
1224 std::string result = lsb.ok() ? lsb.output : std::string{};
1225
1226 result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
1227 result.erase(std::remove(result.begin(), result.end(), '\r'), result.end());
1228
1229 if (!result.empty() && result.find("n/a") == std::string::npos) {
1230 id = result;
1231 } else {
1232 std::cout << _("[detect_distro] lsb_release non fornisce info, fallback su /etc/os-release\n");
1233 }
1234 }
1235
1236
1237 if (id.empty() && std::filesystem::exists("/etc/os-release")) {
1238 std::ifstream file("/etc/os-release");
1239 std::string line;
1240 while (std::getline(file, line)) {
1241 if (line.rfind("ID=", 0) == 0)
1242 id = line.substr(3);
1243 }
1244 if (!id.empty() && id.front() == '"' && id.back() == '"')
1245 id = id.substr(1, id.size() - 2);
1246 }
1247
1248
1249 if (id.empty()) {
1250 if (std::filesystem::exists("/etc/arch-release")) id = "arch";
1251 else if (std::filesystem::exists("/etc/debian_version")) id = "debian";
1252 else if (std::filesystem::exists("/etc/redhat-release")) id = "fedora";
1253 else if (std::filesystem::exists("/etc/gentoo-release")) id = "gentoo";
1254 else if (std::filesystem::exists("/etc/slackware-version")) id = "slackware";
1255 else if (std::filesystem::exists("/usr/local/etc/rc.conf")) id = "bsd";
1256 }
1257
1258 // Normalizza minuscolo
1259 std::transform(id.begin(), id.end(), id.begin(), ::tolower);
1260
1261
1262 if (id.find("debian") != std::string::npos || id.find("ubuntu") != std::string::npos || id.find("mint") != std::string::npos)
1263 distro = "debian";
1264 else if (id.find("fedora") != std::string::npos || id.find("rhel") != std::string::npos ||
1265 id.find("centos") != std::string::npos || id.find("alma") != std::string::npos || id.find("rocky") != std::string::npos)
1266 distro = "fedora";
1267 else if (id.find("arch") != std::string::npos || id.find("manjaro") != std::string::npos || id.find("endeavour") != std::string::npos)
1268 distro = "arch";
1269 else if (id.find("suse") != std::string::npos)
1270 distro = "opensuse";
1271 else if (id.find("gentoo") != std::string::npos)
1272 distro = "gentoo";
1273 else if (id.find("slackware") != std::string::npos)
1274 distro = "slackware";
1275 else if (id.find("bsd") != std::string::npos)
1276 distro = "bsd";
1277
1278 std::cout << _("[detect_distro] Distribuzione rilevata: ") << distro << std::endl;
1279 return distro;
1280}
1281
1282
1283static std::string detect_webserver() {
1284 using namespace std;
1285
1286 auto is_active = [](const std::string& svc) -> bool {
1287 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
1288 "--system", "is-active", svc}).ok();
1289 };
1290
1291 auto cmd_exists = [](const std::string& cmd) -> bool {
1292 return bg_process::run({"which", "--", cmd}).ok();
1293 };
1294
1295 if (is_active("apache2") || is_active("httpd") || cmd_exists("apache2") || cmd_exists("httpd")) {
1296 std::cout << _("[detect_webserver] Apache rilevato") << std::endl;
1297 return "apache";
1298 }
1299
1300 if (is_active("nginx") || cmd_exists("nginx")) {
1301 std::cout << _("[detect_webserver] Nginx rilevato") << std::endl;
1302 return "nginx";
1303 }
1304
1305 if (is_active("lshttpd") || is_active("lsws") || cmd_exists("lsws") || cmd_exists("lshttpd")) {
1306 std::cout << _("[detect_webserver] LiteSpeed rilevato") << std::endl;
1307 return "litespeed";
1308 }
1309
1310 {
1311 const auto processes = bg_process::run({"ps", "-eo", "comm"});
1312 const std::string& out = processes.output;
1313 if (out.find("apache2") != std::string::npos || out.find("httpd") != std::string::npos) return "apache";
1314 if (out.find("nginx") != std::string::npos) return "nginx";
1315 if (out.find("lshttpd") != std::string::npos || out.find("lsws") != std::string::npos) return "litespeed";
1316 }
1317
1318 std::cout << _("[detect_webserver] Nessun server rilevato") << std::endl;
1319 return "unknown";
1320}
1321
1322
1323std::pair<int, int> read_current_ports(const std::string& )
1324{
1325 int http_port = 81;
1326 int https_port = 444;
1327 const std::string portfile = "/etc/BastionGuard/webports.conf";
1328
1329 std::ifstream f(portfile);
1330 if (f.is_open()) {
1331 int p1 = 0, p2 = 0;
1332 f >> p1 >> p2;
1333
1334 if (p1 > 0 && p1 < 65536) http_port = p1;
1335 if (p2 > 0 && p2 < 65536) https_port = p2;
1336
1337 std::cout << _("[settings] Porte lette da ") << portfile
1338 << " → HTTP=" << http_port
1339 << " HTTPS=" << https_port << std::endl;
1340 } else {
1341 std::cerr << _("[settings] ⚠ Impossibile aprire ") << portfile
1342 << _(" — uso valori di default (80/443)\n");
1343 }
1344
1345 return {http_port, https_port};
1346}
1347
1348
1349
1350bool update_webserver_ports(const std::string& websrv, int http_port, int https_port)
1351{
1352 (void)websrv;
1353 namespace fs = std::filesystem;
1354
1355 const std::string tmpfile = "/tmp/BastionGuard_webports.conf";
1356 const std::string targetfile = "/etc/BastionGuard/webports.conf";
1357
1358 try {
1359
1360 std::ofstream fout(tmpfile);
1361 if (!fout.is_open()) {
1362 std::cerr << _("[settings] ❌ Impossibile creare file temporaneo: ") << tmpfile << std::endl;
1363 return false;
1364 }
1365
1366 fout << http_port << " " << https_port << "\n";
1367 fout.close();
1368
1369 const int ret = bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
1370 tmpfile, targetfile}).exit_code;
1371
1372 if (ret == 0) {
1373 std::cout << _("[settings] ✅ Porte BastionGuard salvate in ") << targetfile
1374 << " → HTTP interno=" << http_port
1375 << ", HTTPS interno=" << https_port << std::endl;
1376 return true;
1377 } else {
1378 std::cerr << _("[settings] ❌ Errore durante l'installazione via pkexec.\n");
1379 return false;
1380 }
1381 } catch (const std::exception& e) {
1382 std::cerr << _("[settings] ⚠ Eccezione durante update_webserver_ports: ") << e.what() << std::endl;
1383 return false;
1384 }
1385}
1386
1387
1388
1389static bool updatingToggle = false;
1390
1391static std::map<std::string, bool> toggleControlled;
1392
1393namespace {
1394
1396 const std::vector<std::string> candidates = {
1397 "/etc/clamd/clamd.conf",
1398 "/etc/clamav/clamd.conf",
1399 "/etc/clamd.d/clamd.conf",
1400 "/etc/clamd.conf"
1401 };
1402
1403 for (const auto& p : candidates) {
1404 if (std::filesystem::exists(p)) return p;
1405 }
1406
1407 for (const auto& p : candidates) {
1408 std::filesystem::path dir = std::filesystem::path(p).parent_path();
1409 if (dir.empty() || std::filesystem::exists(dir)) return p;
1410 }
1411
1412 return "/etc/clamav/clamd.conf";
1413}
1414
1415bool atomic_install_with_pkexec(const std::string& tmp_src, const std::string& target) {
1416 if (fs::exists(target)) {
1417 const std::string backup = target + ".bak." + std::to_string(std::time(nullptr));
1418 if (!bg_process::run({"pkexec", "/usr/bin/cp", "-a", "--", target, backup}).ok())
1419 return false;
1420 }
1421 return bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
1422 tmp_src, target}).ok();
1423}
1424
1425} // namespace
1426
1427
1428namespace {
1429 bool isServiceActive(const std::string& service, bool user = false) {
1430 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
1431 user ? "--user" : "--system", "is-active", service}).ok();
1432 }
1433}
1434
1435
1436
1438 return isServiceActive("clamonacc.service") ||
1439 isServiceActive("clamav-clamonacc.service");
1440 }
1441
1442
1444 // Invalida i lambda asincroni in volo: faranno early-return
1445 // invece di dereferenziare `this` dopo la distruzione.
1446 if (alive_) alive_->store(false, std::memory_order_release);
1447}
1448
1449
1451
1452: Gtk::Box(Gtk::Orientation::VERTICAL, 12),
1453 clamdConfig(config),
1455 quarantineLabel(_("Percorso quarantena: ") + Backend::getQuarantinePath()),
1456 quarantineButton(_("Seleziona cartella quarantena")),
1457 realtimeLabel(_("Antivirus real time: Disabilitato")),
1459 toggleAllLabel(_("Abilita tutte le opzioni")),
1461 antiransomStatus(_("Protezione disattivata"))
1462{
1463 sambaStore = Gtk::ListStore::create(sambaCols);
1464 sambaView.set_model(sambaStore);
1465 sambaView.append_column(_("Directory Samba"), sambaCols.col_path);
1466
1467 set_margin(12);
1468 stack.set_hexpand(true);
1469 stack.set_vexpand(true);
1470 stack.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
1471 stack.set_transition_duration(300);
1472
1473 stack_switcher.set_stack(stack);
1474 stack_switcher.set_hexpand(true);
1475 stack_switcher.set_halign(Gtk::Align::FILL);
1476
1477 append(stack_switcher);
1478 append(stack);
1479
1480 auto page_base = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
1481
1482 page_base->append(realtimeLabel);
1483 auto realtimeBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1484 realtimeBox->set_hexpand(true);
1485 realtimeBox->set_halign(Gtk::Align::CENTER);
1486 realtimeBox->append(clamonaccSwitch);
1487 page_base->append(*realtimeBox);
1488
1489 bool clamonacc_enabled = isClamonaccRunning();
1490 clamonaccSwitch.set_active(clamonacc_enabled);
1491 realtimeLabel.set_text(clamonacc_enabled ?
1492 _("Antivirus real time: Abilitato") :
1493 _("Antivirus real time: Disabilitato"));
1494
1495 clamonaccSwitch.property_active().signal_changed().connect(
1496 sigc::mem_fun(*this, &SettingsPage::onRealtimeToggled)
1497 );
1498
1499 page_base->append(quarantineLabel);
1500 page_base->append(quarantineButton);
1501 quarantineButton.signal_clicked().connect(
1502 sigc::mem_fun(*this, &SettingsPage::onQuarantineClicked)
1503 );
1504
1505 auto framePaths = Gtk::make_managed<Gtk::Frame>(_("Cartelle monitorate (clamonacc)"));
1506 auto vboxPaths = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 5);
1507 vboxPaths->append(listbox_paths);
1508
1509 auto hboxButtons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1510 hboxButtons->append(btn_addPath);
1511 hboxButtons->append(btn_removePath);
1512
1513 vboxPaths->append(*hboxButtons);
1514 framePaths->set_child(*vboxPaths);
1515 page_base->append(*framePaths);
1516
1517 for (auto& f : clamdConfig.getOnAccessPaths()) {
1518 auto row = Gtk::make_managed<Gtk::Label>(f);
1519 listbox_paths.append(*row);
1520 row->show();
1521 }
1522
1523 btn_addPath.signal_clicked().connect(sigc::mem_fun(*this, &SettingsPage::onAddPath));
1524 btn_removePath.signal_clicked().connect(sigc::mem_fun(*this, &SettingsPage::onRemovePath));
1525 btn_addPath.set_child(*Gtk::make_managed<Gtk::Label>(_("Aggiungi cartella")));
1526 btn_removePath.set_child(*Gtk::make_managed<Gtk::Label>(_("Rimuovi cartella")));
1527
1528
1529 page_order_.push_back(page_base);
1530 stack.add(*page_base, "base", _("Base"));
1531
1532
1533 auto page_adv = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
1534 auto frameScan = Gtk::make_managed<Gtk::Frame>(_("Opzioni di scansione"));
1535
1536 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
1537 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
1538 scrolled->set_expand(true);
1539
1540 auto flowScan = Gtk::make_managed<Gtk::FlowBox>();
1541 flowScan->set_valign(Gtk::Align::START);
1542 flowScan->set_max_children_per_line(4);
1543 flowScan->set_selection_mode(Gtk::SelectionMode::NONE);
1544
1545 auto scanOptions = clamdConfig.getScanOptions();
1546 for (auto& [opt, enabled] : scanOptions) {
1547 auto cb = Gtk::make_managed<Gtk::CheckButton>(opt);
1548 cb->set_active(enabled);
1549 checkOptions[opt] = cb;
1550 toggleControlled[opt] = false;
1551
1552 flowScan->append(*cb);
1553 cb->show();
1554
1555 cb->signal_toggled().connect([this, opt]() {
1556 if (updatingToggle) return;
1557 toggleControlled[opt] = false;
1558 });
1559 }
1560
1561 scrolled->set_child(*flowScan);
1562 frameScan->set_child(*scrolled);
1563
1564 auto lblWarning = Gtk::make_managed<Gtk::Label>(
1565 _("⚠️ Attenzione: modificare queste opzioni avanzate solo se si è sicuri.\n"
1566 "Un uso improprio può compromettere la protezione.")
1567 );
1568 lblWarning->set_wrap(true);
1569 lblWarning->set_margin_bottom(10);
1570 lblWarning->set_halign(Gtk::Align::FILL);
1571 lblWarning->set_justify(Gtk::Justification::CENTER);
1572
1573 lblWarning->get_style_context()->add_class("alert-warning");
1574
1575 page_adv->append(*lblWarning);
1576
1577 page_adv->append(*frameScan);
1578
1579 auto hboxToggleAll = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1580 hboxToggleAll->append(toggleAllLabel);
1581 hboxToggleAll->append(toggleAllSwitch);
1582 page_adv->append(*hboxToggleAll);
1583
1584 toggleAllSwitch.property_active().signal_changed().connect([this]() {
1585 if (updatingToggle) return;
1586 updatingToggle = true;
1587
1588 bool enabled = toggleAllSwitch.get_active();
1589 for (auto& [opt, cb] : checkOptions) {
1590 if (enabled && !cb->get_active()) {
1591 cb->set_active(true);
1592 toggleControlled[opt] = true;
1593 } else if (!enabled && toggleControlled[opt]) {
1594 cb->set_active(false);
1595 toggleControlled[opt] = false;
1596 }
1597 }
1598
1599 updatingToggle = false;
1600 toggleAllLabel.set_text(enabled ?
1601 _("Tutte le opzioni avanzate abilitate (toggle)") :
1602 _("Abilita tutte le opzioni avanzate"));
1603 });
1604
1605 page_adv->append(btn_save);
1606 btn_save.signal_clicked().connect(sigc::mem_fun(*this, &SettingsPage::onSave));
1607 btn_save.set_child(*Gtk::make_managed<Gtk::Label>(_("Salva configurazione")));
1608 auto frameCustomDB = Gtk::make_managed<Gtk::Frame>(_("Database firme personalizzato"));
1609
1610 auto boxCustomDB = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
1611 boxCustomDB->set_margin(10);
1612
1613
1614 page_order_.push_back(page_adv);
1615 stack.add(*page_adv, "advanced", _("Avanzate"));
1616
1617
1630
1631
1632}
1633
1634
1636 auto dialog = new Gtk::FileChooserDialog(_("Seleziona una cartella quarantena"),
1637 Gtk::FileChooser::Action::SELECT_FOLDER);
1638 dialog->add_button(_("_Annulla"), Gtk::ResponseType::CANCEL);
1639 dialog->add_button(_("_Seleziona"), Gtk::ResponseType::OK);
1640 dialog->set_modal(true);
1641 dialog->get_style_context()->add_class("app-dialog");
1642
1643 dialog->signal_response().connect([this, dialog](int response) {
1644 if (response == Gtk::ResponseType::OK) {
1645 std::string folder = dialog->get_file()->get_path();
1647 quarantineLabel.set_text(_("Percorso quarantena: ") + folder);
1648 }
1649 dialog->hide();
1650 delete dialog;
1651 });
1652 dialog->show();
1653}
1654
1656 auto dialog = new Gtk::FileChooserDialog(_("Seleziona una cartella"),
1657 Gtk::FileChooser::Action::SELECT_FOLDER);
1658 dialog->add_button(_("_Annulla"), Gtk::ResponseType::CANCEL);
1659 dialog->add_button(_("_Seleziona"), Gtk::ResponseType::OK);
1660 dialog->set_modal(true);
1661
1662 dialog->signal_response().connect([this, dialog](int response) {
1663 if (response == Gtk::ResponseType::OK) {
1664 auto folder = dialog->get_file()->get_path();
1665 auto row = Gtk::make_managed<Gtk::Label>(folder);
1666 listbox_paths.append(*row);
1667 row->show();
1669 }
1670 dialog->hide();
1671 delete dialog;
1672 });
1673 dialog->show();
1674}
1675
1677 if (auto row = listbox_paths.get_selected_row()) {
1678 if (auto child = row->get_child()) {
1679 if (auto label = dynamic_cast<Gtk::Label*>(child)) {
1680 clamdConfig.removeOnAccessPath(label->get_text());
1681 }
1682 }
1683 listbox_paths.remove(*row);
1684 }
1685}
1686
1688 for (auto& [opt, cb] : checkOptions) {
1689 clamdConfig.setScanOption(opt, cb->get_active());
1690 }
1691 const std::string target = choose_clamd_conf_target();
1692 const std::string tmp_path = "/tmp/BastionGuard-clamd.conf.tmp";
1693 if (!clamdConfig.save(tmp_path)) {
1694 std::cerr << "❌ " << _("Errore: impossibile scrivere il file temporaneo di configurazione")
1695 << " " << tmp_path << "\n";
1696 return;
1697 }
1698
1699 if (atomic_install_with_pkexec(tmp_path, target)) {
1700 std::cout << "✔ " << _("Configurazione salvata su") << " " << target << "\n";
1701 } else {
1702 std::cerr << "❌ " << _("Errore: impossibile salvare configurazione!")
1703 << " (" << target << ")\n";
1704 }
1705
1707 std::cerr << "⚠ " << _("Errore: impossibile installare clamd automaticamente.") << "\n";
1708 }
1709}
1710
1711
1713 realtimeLabel.set_text(clamonaccSwitch.get_active() ?
1714 _("Antivirus real time: Abilitato") :
1715 _("Antivirus real time: Disabilitato"));
1716}
1717
1718
1720 auto page_ransom = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
1721
1722 auto hboxRan = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
1723 auto lblRan = Gtk::make_managed<Gtk::Label>(_("Protezione Anti-Ransomware"));
1724 hboxRan->append(*lblRan);
1725 hboxRan->append(antiransomSwitch);
1726 page_ransom->append(*hboxRan);
1727
1728 bool running = isServiceActive("BastionGuard-ransomware-scanner.service", true);
1729 antiransomSwitch.set_active(running);
1730 antiransomStatus.set_text(running
1731 ? _("Protezione Anti-Ransomware: Attiva")
1732 : _("Protezione Anti-Ransomware: Disattivata (servizio non attivo)"));
1733 page_ransom->append(antiransomStatus);
1734
1735 antiransomSwitch.property_active().signal_changed().connect(
1736 sigc::mem_fun(*this, &SettingsPage::onAntiransomToggled)
1737 );
1738
1739 rulesStore = Gtk::ListStore::create(ruleCols);
1740 rulesView.set_model(rulesStore);
1741 rulesView.append_column(_("Regole YARA in uso"), ruleCols.name);
1742
1743 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
1744 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
1745 scrolled->set_expand(true);
1746 scrolled->set_child(rulesView);
1747 scrolled->set_min_content_height(100);
1748 page_ransom->append(*scrolled);
1749
1750 loadYaraRules();
1751
1752 auto btn_updateYara = Gtk::make_managed<Gtk::Button>(_("Aggiorna regole YARA"));
1753 page_ransom->append(*btn_updateYara);
1754
1755 btn_updateYara->signal_clicked().connect([this]() {
1756 antiransomStatus.set_text(_("⏳ Aggiornamento regole YARA in corso..."));
1757
1758 auto alive = alive_;
1759 std::thread([this, alive]() {
1760 bool ok = Backend::instance().updateYaraRules();
1761 if (!alive->load()) return;
1762
1763 Glib::signal_idle().connect_once([this, alive, ok]() {
1764 BastionGuard::if_alive(alive, [this, ok]() {
1765 if (ok) {
1766 antiransomStatus.set_text(_("✔ Regole YARA aggiornate con successo"));
1768 } else {
1769 antiransomStatus.set_text(_("❌ Errore durante l'aggiornamento delle regole YARA"));
1770 }
1771 });
1772 });
1773 }).detach();
1774 });
1775
1776
1777 auto btn_updateSanesec = Gtk::make_managed<Gtk::Button>(_("Aggiorna Sanesecurity DB"));
1778 page_ransom->append(*btn_updateSanesec);
1779
1780 btn_updateSanesec->signal_clicked().connect([this]() {
1781 antiransomStatus.set_text(_("⏳ Aggiornamento Sanesecurity DB in corso..."));
1782
1783 auto alive = alive_;
1784 std::thread([this, alive]() {
1786 if (!alive->load()) return;
1787
1788 Glib::signal_idle().connect_once([this, alive, ok]() {
1789 BastionGuard::if_alive(alive, [this, ok]() {
1790 if (ok) {
1791 antiransomStatus.set_text(_("✔ Sanesecurity DB aggiornato con successo"));
1792 } else {
1793 antiransomStatus.set_text(_("❌ Errore durante l'aggiornamento del Sanesecurity DB"));
1794 }
1795 });
1796 });
1797 }).detach();
1798 });
1799
1800
1801 bool use_rules, use_db;
1802 int interval;
1803 std::vector<std::string> scan_dirs;
1804 loadScannerConfig(use_rules, use_db, interval, scan_dirs);
1805
1806 auto chk_rules = Gtk::make_managed<Gtk::CheckButton>(_("Usa regole YARA"));
1807 chk_rules->set_active(use_rules);
1808 page_ransom->append(*chk_rules);
1809
1810 auto chk_db = Gtk::make_managed<Gtk::CheckButton>(_("Usa DB Sanesecurity"));
1811 chk_db->set_active(use_db);
1812 page_ransom->append(*chk_db);
1813
1814 auto lblDirs = Gtk::make_managed<Gtk::Label>(_("Cartelle da scansionare:"));
1815 page_ransom->append(*lblDirs);
1816
1817 auto list_dirs = Gtk::make_managed<Gtk::ListBox>();
1818 for (auto& d : scan_dirs) {
1819 auto row = Gtk::make_managed<Gtk::Label>(d);
1820 row->set_visible(true);
1821 list_dirs->append(*row);
1822 }
1823 page_ransom->append(*list_dirs);
1824
1825 auto btn_addDir = Gtk::make_managed<Gtk::Button>(_("Aggiungi cartella"));
1826 auto btn_rmDir = Gtk::make_managed<Gtk::Button>(_("Rimuovi cartella"));
1827 auto hboxDirs = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1828 hboxDirs->append(*btn_addDir);
1829 hboxDirs->append(*btn_rmDir);
1830 page_ransom->append(*hboxDirs);
1831
1832 btn_addDir->signal_clicked().connect([list_dirs]() {
1833 auto dialog = new Gtk::FileChooserDialog(_("Seleziona cartella da scansionare"),
1834 Gtk::FileChooser::Action::SELECT_FOLDER);
1835 dialog->add_button(_("_Annulla"), Gtk::ResponseType::CANCEL);
1836 dialog->add_button(_("_Seleziona"), Gtk::ResponseType::OK);
1837 dialog->signal_response().connect([dialog, list_dirs](int response) {
1838 if (response == Gtk::ResponseType::OK) {
1839 auto folder = dialog->get_file()->get_path();
1840 auto row = Gtk::make_managed<Gtk::Label>(folder);
1841 row->set_visible(true);
1842 list_dirs->append(*row);
1843 }
1844 dialog->hide();
1845 delete dialog;
1846 });
1847 dialog->show();
1848 });
1849
1850 btn_rmDir->signal_clicked().connect([list_dirs]() {
1851 if (auto row = list_dirs->get_selected_row()) {
1852 list_dirs->remove(*row);
1853 }
1854 });
1855
1856 auto spin_interval = Gtk::make_managed<Gtk::SpinButton>();
1857 spin_interval->set_range(1, 120);
1858 spin_interval->set_value(interval);
1859 spin_interval->set_increments(1, 5);
1860 auto hboxInt = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1861 hboxInt->append(*Gtk::make_managed<Gtk::Label>(_("Intervallo scansione (min):")));
1862 hboxInt->append(*spin_interval);
1863 page_ransom->append(*hboxInt);
1864
1865 auto btn_saveConf = Gtk::make_managed<Gtk::Button>(_("Salva configurazione scanner"));
1866 page_ransom->append(*btn_saveConf);
1867 btn_saveConf->signal_clicked().connect([this, chk_rules, chk_db, spin_interval, list_dirs]() {
1868 std::vector<std::string> dirs;
1869 // Recupera le directory di scansione principali
1870 for (auto row : list_dirs->get_children()) {
1871 if (auto lbl = dynamic_cast<Gtk::Label*>(row))
1872 dirs.push_back(lbl->get_text());
1873 }
1874
1876 chk_rules->get_active(),
1877 chk_db->get_active(),
1878 spin_interval->get_value_as_int(),
1879 dirs
1880 );
1881
1882 });
1883
1884 page_order_.push_back(page_ransom);
1885 stack.add(*page_ransom, "anti-ransomware", _("Anti-Ransomware"));
1886}
1887
1888
1889
1890
1892 if (antiransomSwitch.get_active()) {
1893 if (!isServiceActive("BastionGuard-ransomware-scanner.service", true)) {
1894 antiransomStatus.set_text(_("⏳ Avvio servizio Anti-Ransomware..."));
1895 if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "start", "BastionGuard-ransomware-scanner.service"}).exit_code) != 0) {
1896 antiransomStatus.set_text(_("❌ Impossibile avviare il servizio Anti-Ransomware"));
1897 antiransomSwitch.set_active(false);
1898 return;
1899 }
1900 }
1902 antiransomStatus.set_text(_("Protezione Anti-Ransomware: Attiva"));
1903 } else {
1904 if (isServiceActive("BastionGuard-ransomware-scanner.service", true)) {
1905 antiransomStatus.set_text(_("⏳ Arresto servizio Anti-Ransomware..."));
1906 if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "stop", "BastionGuard-ransomware-scanner.service"}).exit_code) != 0) {
1907 antiransomStatus.set_text(_("❌ Impossibile fermare il servizio Anti-Ransomware"));
1908 antiransomSwitch.set_active(true);
1909 return;
1910 }
1911 }
1913 antiransomStatus.set_text(_("Protezione Anti-Ransomware: Disattivata"));
1914 }
1915}
1916
1917
1918
1919
1921 if (!rulesStore) {
1922 std::cerr << _("⚠ refreshAntiransomRules chiamata ma rulesStore non è inizializzato\n");
1923 return;
1924 }
1925 loadYaraRules();
1926}
1927
1928
1930 if (!rulesStore) {
1931 std::cerr << _("⚠ rulesStore non inizializzato\n");
1932 return;
1933 }
1934
1935 rulesStore->clear();
1936
1937 namespace fs = std::filesystem;
1938 std::string dir = "/usr/share/BastionGuard/data/yara";
1939
1940 if (!fs::exists(dir)) {
1941 std::cerr << _("⚠ Nessuna cartella YARA trovata in ") << dir << "\n";
1942 return;
1943 }
1944
1945 std::regex ruleRegex(R"(^\s*rule\s+([A-Za-z0-9_]+))");
1946 std::smatch match;
1947 int total_rules = 0;
1948
1949 std::error_code ec_it;
1950 for (auto& entry : fs::directory_iterator(dir, ec_it)) {
1951 if (ec_it) {
1952 std::cerr << _("⚠ Impossibile leggere la cartella YARA: ") << dir << "\n";
1953 break;
1954 }
1955 if (!entry.is_regular_file())
1956 continue;
1957
1958 std::string ext = entry.path().extension().string();
1959 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
1960 if (ext != ".yar" && ext != ".yara")
1961 continue;
1962
1963 std::ifstream f(entry.path());
1964 if (!f.is_open()) {
1965 std::cerr << _("⚠ Impossibile aprire ") << entry.path() << "\n";
1966 continue;
1967 }
1968
1969 std::string line;
1970 while (std::getline(f, line)) {
1971 if (std::regex_search(line, match, ruleRegex)) {
1972 auto row = *(rulesStore->append());
1973 row[ruleCols.name] = match[1].str();
1974 total_rules++;
1975 }
1976 }
1977 }
1978
1979 if (total_rules > 0) {
1980 std::cout << "✔ " << total_rules << _(" regole YARA caricate da ") << dir << "\n";
1981 } else {
1982 std::cout << _("⚠ Nessuna regola YARA trovata in ") << dir << "\n";
1983 }
1984}
1985
1986
1987void SettingsPage::loadScannerConfig(bool& use_rules, bool& use_db,
1988 int& interval, std::vector<std::string>& dirs)
1989{
1990 use_rules = true;
1991 use_db = false;
1992 interval = 60;
1993 dirs.clear();
1994
1995 std::string conf_path = resource("config/scanner.conf");
1996
1997 std::ifstream f(conf_path);
1998 if (!f.is_open()) {
1999 std::cerr << _("⚠️ Config non trovata → uso default (/home)\n");
2000 dirs.push_back("/home");
2001 return;
2002 }
2003
2004 std::string line;
2005
2006 while (std::getline(f, line)) {
2007 if (line.empty() || line[0] == '#')
2008 continue;
2009
2010 std::string lower = line;
2011 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
2012
2013 if (lower.find("enable_yara=") == 0) {
2014 std::string val = lower.substr(12);
2015 use_rules = (val == "1" || val == "true" || val == "yes");
2016 }
2017 else if (lower.find("enable_sanesecurity=") == 0) {
2018 std::string val = lower.substr(20);
2019 use_db = (val == "1" || val == "true" || val == "yes");
2020 }
2021 else if (lower.find("scan_interval=") == 0) {
2022 std::string val = lower.substr(14);
2023 try { interval = std::stoi(val); }
2024 catch (...) { interval = 60; }
2025 }
2026 else if (lower.find("scan_path=") == 0) {
2027 std::string paths = line.substr(10);
2028 size_t start = 0, end;
2029 while ((end = paths.find(';', start)) != std::string::npos) {
2030 dirs.push_back(paths.substr(start, end - start));
2031 start = end + 1;
2032 }
2033 if (start < paths.size())
2034 dirs.push_back(paths.substr(start));
2035 }
2036 }
2037
2038 if (dirs.empty())
2039 dirs.push_back("/home");
2040
2041 std::cout << _("✔ Config caricata da ") << conf_path
2042 << " (interval=" << interval << ")\n";
2043}
2044
2045
2046
2047void SettingsPage::saveScannerConfig(bool use_rules, bool use_db,
2048 int interval, const std::vector<std::string>& dirs)
2049{
2050 namespace fs = std::filesystem;
2051 std::string save_path;
2052 bool use_local = false;
2053
2054 if (fs::exists("config"))
2055 save_path = "config/scanner.conf";
2056 else
2057 save_path = std::string(DATA_DIR) + "/config/scanner.conf";
2058
2059 std::string tmp_path = use_local ? save_path : "/tmp/BastionGuard-scanner.conf.tmp";
2060
2061 std::ofstream f(tmp_path, std::ios::trunc);
2062 if (!f.is_open()) {
2063 std::cerr << "❌ Impossibile scrivere config in " << tmp_path << "\n";
2064 return;
2065 }
2066
2067 f << "# ===============================\n";
2068 f << _("# Configurazione BastionGuard Ransomware Scanner\n");
2069 f << "# ===============================\n\n";
2070 f << "enable_yara=" << (use_rules ? "1" : "0") << "\n";
2071 f << "enable_sanesecurity=" << (use_db ? "1" : "0") << "\n";
2072 f << "scan_interval=" << interval << "\n";
2073 f << "scan_path=";
2074 for (size_t i = 0; i < dirs.size(); ++i) {
2075 f << dirs[i];
2076 if (i + 1 < dirs.size()) f << ";";
2077 }
2078 f << "\n";
2079
2080 f.close();
2081
2082 if (!use_local) {
2083 if (bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
2084 tmp_path, save_path}).ok())
2085 std::cout << _("✔ Config aggiornata in: ") << save_path << "\n";
2086 else
2087 std::cerr << _("❌ Errore: impossibile aggiornare config in ") << save_path << "\n";
2088 } else {
2089 std::cout << _("✔ Config aggiornata in: ") << save_path << "\n";
2090 }
2091}
2092
2093
2094
2095
2096
2098 auto page_phish = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2099
2100 auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2101 auto lbl = Gtk::make_managed<Gtk::Label>(_("Protezione Anti-Phishing"));
2102 hbox->append(*lbl);
2103 hbox->append(antiphishSwitch);
2104 page_phish->append(*hbox);
2105
2106 bool running = isServiceActive("BastionGuard-phishing-scanner.service");
2107 antiphishSwitch.set_active(running);
2108 antiphishStatus.set_text(running
2109 ? _("Protezione Anti-Phishing: Attiva (servizio di sistema)")
2110 : _("Protezione Anti-Phishing: Disattivata (servizio non attivo)"));
2111 page_phish->append(antiphishStatus);
2112
2113 antiphishSwitch.property_active().signal_changed().connect(
2114 sigc::mem_fun(*this, &SettingsPage::onAntiphishToggled)
2115 );
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);
2120
2121 auto chk_auto_update = Gtk::make_managed<Gtk::CheckButton>();
2122 chk_auto_update->set_active(Backend::instance().isPhishAutoUpdateEnabled());
2123 chk_auto_update->set_halign(Gtk::Align::END);
2124 hbox_auto->append(*chk_auto_update);
2125
2126 chk_auto_update->signal_toggled().connect([this, chk_auto_update]() {
2127 bool enable = chk_auto_update->get_active();
2128 antiphishStatus.set_text(enable
2129 ? _("⏳ Attivazione aggiornamento automatico...")
2130 : _("⏳ Disattivazione aggiornamento automatico..."));
2131
2132 auto alive = alive_;
2133 std::thread([this, alive, enable]() {
2134 bool ok = Backend::instance().enablePhishAutoUpdate(enable);
2135 if (!alive->load()) return;
2136 Glib::signal_idle().connect_once([this, alive, enable, ok]() {
2137 BastionGuard::if_alive(alive, [this, enable, ok]() {
2138 if (ok) {
2139 antiphishStatus.set_text(enable
2140 ? _("✔ Aggiornamento automatico abilitato (ogni 2 ore)")
2141 : _("ℹ️ Aggiornamento automatico disabilitato"));
2142 } else {
2143 antiphishStatus.set_text(_("❌ Errore nella modifica del timer automatico"));
2144 }
2145 });
2146 });
2147 }).detach();
2148 });
2149
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);
2156
2157 googleSafeKeyEntry.set_placeholder_text(_("Inserisci la tua API key..."));
2158 googleSafeKeyEntry.set_hexpand(true);
2159 googleSafeKeyEntry.set_visibility(false);
2160 googleSafeKeyEntry.set_text(Backend::instance().getGoogleSafeApiKey());
2161 auto btn_save_key = Gtk::make_managed<Gtk::Button>(_("Salva"));
2162 btn_save_key->signal_clicked().connect([this]() {
2163 std::string key = googleSafeKeyEntry.get_text();
2166 antiphishStatus.set_text("✔ " + std::string(_("Chiave Google Safe salvata")));
2167 });
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;
2174
2175 googleSafeKeyEntry.set_visibility(*visible);
2176 btn_toggle_key->set_label(*visible ? _("Nascondi") : _("Mostra"));
2177 }, *btn_toggle_key)
2178 );
2179
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_]() {
2184 bool valid = Backend::instance().testGoogleSafeKey();
2185 if (!alive->load()) return;
2186 Glib::signal_idle().connect_once([this, alive, valid]() {
2187 BastionGuard::if_alive(alive, [this, valid]() {
2188 if (valid)
2189 antiphishStatus.set_text("✔ " + std::string(_("Chiave Google Safe valida")));
2190 else
2191 antiphishStatus.set_text("❌ " + std::string(_("Chiave Google Safe non valida")));
2192 });
2193 });
2194 }).detach();
2195 });
2196
2197
2198
2199 hbox_key->append(*lbl_key);
2200 hbox_key->append(googleSafeKeyEntry);
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);
2205
2206 googleSafeSwitch.set_active(Backend::instance().isGoogleSafeEnabled());
2207
2208 hbox_safe->append(*lbl_safe);
2209 hbox_safe->append(googleSafeSwitch);
2210 page_phish->append(*hbox_safe);
2211
2212 googleSafeSwitch.property_active().signal_changed().connect([this]() {
2213 bool enabled = googleSafeSwitch.get_active();
2215
2216 antiphishStatus.set_text(enabled
2217 ? _("✔ Google Safe Browsing abilitato")
2218 : _("ℹ️ Google Safe Browsing disabilitato"));
2219 });
2220
2221 phishStore = Gtk::ListStore::create(phishCols);
2222 phishView.set_model(phishStore);
2223 phishView.append_column(_("Blacklist caricate"), phishCols.source);
2224
2225 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
2226 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
2227 scrolled->set_expand(true);
2228 scrolled->set_child(phishView);
2229 page_phish->append(*scrolled);
2231
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...")));
2236
2237 std::thread([this, alive = alive_]() {
2238 bool ok = Backend::instance().updatePhishLists();
2239 if (!alive->load()) return;
2240
2241 Glib::signal_idle().connect_once([this, alive, ok]() {
2242 BastionGuard::if_alive(alive, [this, ok]() {
2243 if (ok) {
2244 antiphishStatus.set_text("✔ " + std::string(_("Blacklist aggiornata")));
2245
2247 if (isServiceActive("BastionGuard-phishing-scanner.service")) {
2248 antiphishStatus.set_text(_("♻ Riavvio servizio Anti-Phishing..."));
2249 int r1 = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "BastionGuard-phishing-scanner.service"}).exit_code);
2250 if (r1 == 0) {
2251 antiphishStatus.set_text(_("✔ Servizio Anti-Phishing riavviato con nuova blacklist"));
2252 } else {
2253 antiphishStatus.set_text(_("⚠ Errore durante il riavvio del servizio Anti-Phishing"));
2254 }
2255 }
2256
2257 } else {
2258 antiphishStatus.set_text("❌ " + std::string(_("Errore aggiornamento blacklist")));
2259 }
2260 });
2261 });
2262 }).detach();
2263});
2264
2265
2266 auto lblInfo = Gtk::make_managed<Gtk::Label>(
2267 "ℹ️ " + std::string(_("La lista completa aggiornata si trova in: phishing/blacklist.txt"))
2268 );
2269 lblInfo->set_wrap(true);
2270 page_phish->append(*lblInfo);
2271 auto frameFirewall = Gtk::make_managed<Gtk::Frame>();
2272 frameFirewall->set_hexpand(true);
2273
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)")
2279 );
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")
2284 );
2285 lblFwToggle->set_halign(Gtk::Align::START);
2286
2287 auto swFirewall = Gtk::make_managed<Gtk::Switch>();
2288 swFirewall->set_active(Backend::instance().isFirewallEnabled());
2289
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).")
2296 );
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]() {
2303 auto& backend = Backend::instance();
2304
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");
2309 return;
2310 }
2311
2312 switch (backend.getFirewallType()) {
2313 case FirewallType::UFW:
2314 lblFwStatus->set_text(_("Firewall IP attivo: UFW"));
2315 break;
2317 lblFwStatus->set_text(_("Firewall IP attivo: firewalld"));
2318 break;
2319 default:
2320 lblFwStatus->set_text(_("Firewall IP attivo"));
2321 break;
2322 }
2323
2324 lblFwStatus->remove_css_class("status-muted");
2325 lblFwStatus->add_css_class("status-ok");
2326 };
2327
2328 updateFwStatus();
2329
2330 auto btnFirewall = Gtk::make_managed<Gtk::Button>(
2331 _("Configura blocco IP firewall")
2332 );
2333 btnFirewall->set_halign(Gtk::Align::END);
2334 btnFirewall->add_css_class("suggested-action");
2335
2336 btnFirewall->signal_clicked().connect(
2337 [this, updateFwStatus]() {
2338
2339 auto dialog = std::make_unique<FirewallChoiceDialog>();
2340
2341 if (auto *win = dynamic_cast<Gtk::Window*>(get_root()))
2342 dialog->set_transient_for(*win);
2343
2344 dialog->set_modal(true);
2345
2346 dialog->signal_firewall_selected.connect(
2347 [updateFwStatus](FirewallType type) {
2348
2349 auto& backend = Backend::instance();
2350
2351 backend.setFirewallType(type);
2352 backend.setFirewallEnabled(true);
2353
2354 backend.applyFirewallFromPhishingBlacklist();
2355
2356 updateFwStatus();
2357 }
2358 );
2359
2360 dialog->present();
2361 dialog.release();
2362 });
2363
2364 swFirewall->property_active().signal_changed().connect(
2365 [this, swFirewall, updateFwStatus]() {
2366 bool enabled = swFirewall->get_active();
2368
2369 antiphishStatus.set_text(
2370 enabled
2371 ? _("✔ Blocco IP firewall abilitato")
2372 : _("ℹ️ Blocco IP firewall disabilitato")
2373 );
2374
2375 updateFwStatus();
2376 }
2377 );
2378
2379 boxFirewall->append(*lblFwTitle);
2380 boxFirewall->append(*hboxFwToggle);
2381 boxFirewall->append(*lblFwDesc);
2382 boxFirewall->append(*lblFwStatus);
2383 boxFirewall->append(*btnFirewall);
2384
2385 frameFirewall->set_child(*boxFirewall);
2386 page_phish->append(*frameFirewall);
2387
2388
2389 page_order_.push_back(page_phish);
2390 stack.add(*page_phish, "anti-phishing", _("Anti-Phishing"));
2391}
2392
2393
2394
2396 if (antiphishSwitch.get_active()) {
2397 if (!isServiceActive("BastionGuard-phishing-scanner.service")) {
2398 antiphishStatus.set_text(_("⏳ Avvio servizio Anti-Phishing..."));
2399 if ((bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "BastionGuard-phishing-scanner.service"}).exit_code) != 0) {
2400 antiphishStatus.set_text(_("❌ Impossibile avviare il servizio Anti-Phishing"));
2401 antiphishSwitch.set_active(false);
2402 return;
2403 }
2404 }
2406 antiphishStatus.set_text(_("Protezione Anti-Phishing: Attiva"));
2407 } else {
2408 if (isServiceActive("BastionGuard-phishing-scanner.service")) {
2409 antiphishStatus.set_text(_("⏳ Arresto servizio Anti-Phishing..."));
2410 if ((bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "BastionGuard-phishing-scanner.service"}).exit_code) != 0) {
2411 antiphishStatus.set_text(_("❌ Impossibile fermare il servizio Anti-Phishing"));
2412 antiphishSwitch.set_active(true);
2413 return;
2414 }
2415 }
2417 antiphishStatus.set_text(_("Protezione Anti-Phishing: Disattivata"));
2418 }
2419}
2420
2422 if (!phishStore) {
2423 std::cerr << _("⚠ refreshPhishLists chiamata ma phishStore non è inizializzato\n");
2424 return;
2425 }
2427}
2428
2430 if (!phishStore) {
2431 std::cerr << _("⚠ phishStore non inizializzato\n");
2432 return;
2433 }
2434
2435 phishStore->clear();
2436 std::string reducePath = resource("phishing/blacklist-reduce.txt");
2437 std::ifstream freduce(reducePath);
2438 if (freduce.is_open()) {
2439 std::string line;
2440 int count = 0;
2441 while (std::getline(freduce, line)) {
2442 if (!line.empty()) {
2443 auto row = *(phishStore->append());
2444 row[phishCols.source] = line;
2445 count++;
2446 }
2447 }
2448 freduce.close();
2449
2450 if (count == 0) {
2451 auto row = *(phishStore->append());
2452 row[phishCols.source] = "⚠ " + std::string(_("Blacklist ridotta vuota in ")) + reducePath;
2453 } else {
2454 auto row = *(phishStore->append());
2455 row[phishCols.source] = "✔ " + std::string(_("Caricate ")) + std::to_string(count) +
2456 " " + std::string(_("righe da blacklist ridotta"));
2457 }
2458 } else {
2459 auto row = *(phishStore->append());
2460 row[phishCols.source] = "⚠ " + std::string(_("Nessuna blacklist ridotta trovata in ")) + reducePath;
2461 }
2462 std::string fullPath = resource("phishing/blacklist.txt");
2463 if (std::filesystem::exists(fullPath)) {
2464 auto row = *(phishStore->append());
2465 row[phishCols.source] = "✔ " + std::string(_("Lista completa in ")) + fullPath;
2466 } else {
2467 auto row = *(phishStore->append());
2468 row[phishCols.source] = "⚠ " + std::string(_("Lista completa non trovata in ")) + fullPath;
2469 }
2470}
2471
2472
2473
2474
2475
2476static std::string normalize_locale(std::string s) {
2477 auto p = s.find('.'); if (p != std::string::npos) s = s.substr(0, p);
2478 p = s.find('@'); if (p != std::string::npos) s = s.substr(0, p);
2479 for (auto& c : s) if (c == '-') c = '_';
2480 return s;
2481}
2482
2484 auto page_opts = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2485
2486 auto frame = Gtk::make_managed<Gtk::Frame>();
2487 frame->get_style_context()->add_class("card");
2488
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);
2500
2501 combo_language_.set_hexpand(true);
2502 rowLang->append(*lblSel);
2503 rowLang->append(combo_language_);
2504 inner->append(*rowLang);
2505 combo_language_.append("it_IT", "Italiano");
2506 combo_language_.append("en_US", "English");
2507 combo_language_.append("ar_SA", "العربية");
2508 combo_language_.append("de_DE", "Deutsch");
2509 combo_language_.append("es_ES", "Español");
2510 combo_language_.append("fr_FR", "Français");
2511 combo_language_.append("ja_JP", "日本語");
2512 combo_language_.append("nl_NL", "Nederlands");
2513 combo_language_.append("pl_PL", "Polski");
2514 combo_language_.append("pt_PT", "Português");
2515 combo_language_.append("ru_RU", "Русский");
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";
2521
2522 combo_language_.set_active_id(currentLang);
2523 if (combo_language_.get_active_id() != currentLang) {
2524 combo_language_.set_active_id("en_US");
2525 if (combo_language_.get_active_id() != "en_US")
2526 combo_language_.set_active(0);
2527 }
2528
2529 btn_apply_language_.set_label(_("Applica"));
2530 btn_apply_language_.set_hexpand(false);
2531 inner->append(btn_apply_language_);
2532
2533 btn_apply_language_.signal_clicked().connect([this]() {
2534 std::string lang = combo_language_.get_active_id();
2535 if (lang.empty()) lang = "en_US";
2536
2537 std::string lang_full = lang + ".UTF-8";
2538 std::string lang_short = lang.substr(0, 2);
2539
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);
2543 if (f.is_open()) {
2544 f << "LANG=" << lang_full << "\n";
2545 f << "LC_ALL=" << lang_full << "\n";
2546 f << "LANGUAGE=" << lang << ":" << lang_short << "\n";
2547 f.close();
2548 std::cout << _("✔ Lingua salvata in ") << config_dir << "/lang.conf\n";
2549 } else {
2550 std::cerr << _("❌ Impossibile salvare ") << config_dir << "/lang.conf\n";
2551 }
2552
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..."));
2557
2558 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2559 box->set_margin(20);
2560
2561 auto spinner = Gtk::make_managed<Gtk::Spinner>();
2562 spinner->set_hexpand(true);
2563 spinner->set_vexpand(true);
2564 spinner->start();
2565
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);
2570
2571 box->append(*spinner);
2572 box->append(*label);
2573 dialog->set_child(*box);
2574 dialog->show();
2575 auto alive = alive_;
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";
2581
2582 const auto restart_user = [](const char* service) {
2583 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
2584 "--user", "restart", service}).ok();
2585 };
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;
2589 }
2590 // Do not fall back to pkill -f or nohup with service-derived patterns.
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;
2594
2595 std::this_thread::sleep_for(std::chrono::milliseconds(800));
2596
2597 Glib::signal_idle().connect_once([alive, dialog]() {
2598 if (alive && alive->load(std::memory_order_acquire))
2599 dialog->close();
2600 });
2601
2602 char self_path[1024];
2603 ssize_t n = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
2604 if (n > 0) {
2605 self_path[n] = '\0';
2606 execl(self_path, self_path, nullptr);
2607 std::cerr << _("❌ Impossibile rilanciare l’app (execl fallito)") << "\n";
2608 } else {
2609 std::cerr << _("❌ Impossibile determinare il percorso dell’eseguibile") << "\n";
2610 }
2611 }).detach();
2612
2613 });
2614
2615
2616 frame->set_child(*inner);
2617 page_opts->append(*frame);
2618
2619
2620 {
2621 auto frameTheme = Gtk::make_managed<Gtk::Frame>();
2622 frameTheme->get_style_context()->add_class("card");
2623
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);
2629
2630 auto lblTheme = Gtk::make_managed<Gtk::Label>(_("Tema dell'interfaccia"));
2631 lblTheme->set_halign(Gtk::Align::START);
2632 innerTheme->append(*lblTheme);
2633
2634 auto rowTheme = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
2635
2636 auto lblThemeSel = Gtk::make_managed<Gtk::Label>(_("Seleziona tema"));
2637 lblThemeSel->set_halign(Gtk::Align::START);
2638
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)"));
2643
2644 {
2645 std::string themeCfg = Glib::get_home_dir() + "/.config/BastionGuard/theme.conf";
2646 std::ifstream tf(themeCfg);
2647 std::string saved;
2648 if (tf >> saved && (saved == "light" || saved == "dark"))
2649 combo_theme->set_active_id(saved);
2650 else
2651 combo_theme->set_active_id("light");
2652 }
2653
2654 rowTheme->append(*lblThemeSel);
2655 rowTheme->append(*combo_theme);
2656 innerTheme->append(*rowTheme);
2657
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);
2662
2663 auto updatePreview = [lblPreview](const std::string& id) {
2664 if (id == "dark")
2665 lblPreview->set_text(_("Tema scuro: sfondo #1a1d23, testo chiaro."));
2666 else
2667 lblPreview->set_text(_("Tema chiaro: sfondo #f8f9fa, testo scuro."));
2668 };
2669 updatePreview(combo_theme->get_active_id());
2670 combo_theme->signal_changed().connect([combo_theme, updatePreview]() {
2671 updatePreview(combo_theme->get_active_id());
2672 });
2673
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);
2677
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;
2681
2682
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);
2686 tf << themeId;
2687 tf.close();
2688
2689
2690 std::string cssFile;
2691 if (themeId == "dark")
2692 cssFile = Glib::build_filename(DATA_DIR, "BastionGuard-dark.css");
2693 else
2694 cssFile = Glib::build_filename(DATA_DIR, "BastionGuard.css");
2695
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);
2699 if (n > 0) {
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;
2705 }
2706 }
2707
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);
2713 return;
2714 }
2715
2716 try {
2717 auto provider = Gtk::CssProvider::create();
2718 provider->load_from_path(cssFile);
2719 Gtk::StyleContext::add_provider_for_display(
2720 Gdk::Display::get_default(),
2721 provider,
2722 GTK_STYLE_PROVIDER_PRIORITY_USER);
2723
2724 std::cout << "[BastionGuard] Tema applicato: " << cssFile << std::endl;
2725
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);
2731
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);
2738 }
2739 });
2740
2741 frameTheme->set_child(*innerTheme);
2742 page_opts->append(*frameTheme);
2743 }
2744
2745 auto frameWeb = Gtk::make_managed<Gtk::Frame>();
2746 frameWeb->get_style_context()->add_class("card");
2747
2748 auto innerWeb = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
2749 innerWeb->set_margin(10);
2750
2751 auto lblSystem = Gtk::make_managed<Gtk::Label>(_("Sistema operativo / Distro"));
2752 lblSystem->set_halign(Gtk::Align::START);
2753 innerWeb->append(*lblSystem);
2754
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");
2760 combo_os_.append("gentoo", "Gentoo");
2761 combo_os_.append("slackware", "Slackware");
2762 combo_os_.append("bsd", "FreeBSD / OpenBSD / NetBSD");
2763 combo_os_.set_active_id(detect_distro());
2764 innerWeb->append(combo_os_);
2765
2766 auto gridPorts = Gtk::make_managed<Gtk::Grid>();
2767 gridPorts->set_row_spacing(6);
2768 gridPorts->set_column_spacing(6);
2769
2770 auto lblHttp = Gtk::make_managed<Gtk::Label>("Porta HTTP:");
2771 lblHttp->set_halign(Gtk::Align::START);
2772 auto [saved_http, saved_https] = read_webports_conf();
2773 entry_http_port_.set_placeholder_text("81");
2774 entry_http_port_.set_width_chars(6);
2775 entry_http_port_.set_max_width_chars(6);
2776 entry_http_port_.set_text(std::to_string(saved_http));
2777
2778 auto lblHttps = Gtk::make_managed<Gtk::Label>("Porta HTTPS:");
2779 lblHttps->set_halign(Gtk::Align::START);
2780 entry_https_port_.set_placeholder_text("444");
2781 entry_https_port_.set_width_chars(6);
2782 entry_https_port_.set_max_width_chars(6);
2783 entry_https_port_.set_text(std::to_string(saved_https));
2784
2785 gridPorts->attach(*lblHttp, 0, 0, 1, 1);
2786 gridPorts->attach(entry_http_port_, 1, 0, 1, 1);
2787 gridPorts->attach(*lblHttps, 0, 1, 1, 1);
2788 gridPorts->attach(entry_https_port_, 1, 1, 1, 1);
2789 innerWeb->append(*gridPorts);
2790
2791 btn_apply_webconfig_.set_label(_("Applica configurazione web"));
2792 innerWeb->append(btn_apply_webconfig_);
2793
2794 auto write_temp_script_and_elevate = [](const std::string& script_contents, std::string* out_log = nullptr) -> bool {
2795 char tmpl[] = "/tmp/BastionGuard_tmp_XXXXXX";
2796 int fd = mkstemp(tmpl);
2797 if (fd < 0) return false;
2798
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);
2803 fclose(f);
2804 chmod(tmpname.c_str(), 0755);
2805
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);
2810 }
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;
2815 return result.ok();
2816 };
2817
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;
2823 std::string line;
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";
2828 }
2829 return true;
2830 };
2831
2832 btn_apply_webconfig_.signal_clicked().connect([this, replace_placeholders, write_temp_script_and_elevate]() {
2833 namespace fs = std::filesystem;
2834
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& /*title*/) {
2837 Utils::show_message(parent, msg.raw(), type);
2838 };
2839
2840 std::string distro = combo_os_.get_active_id();
2841 if (distro.empty() || distro == "auto")
2842 distro = detect_distro();
2843
2844 std::cout << _("[BastionGuard] Distro rilevata: ") << distro << std::endl;
2845
2846 int http_port = 81, https_port = 444;
2847 try {
2848 http_port = std::stoi(entry_http_port_.get_text());
2849 https_port = std::stoi(entry_https_port_.get_text());
2850 } catch (...) {
2851 show_message(_("Le porte devono essere numeriche valide."),
2852 Gtk::MessageType::ERROR, _("BastionGuard - Errore"));
2853 return;
2854 }
2855
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"));
2860 return;
2861 }
2862
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"));
2868 return;
2869 }
2870
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");
2874 } else {
2875 nginx_default_src = Glib::build_filename(DATA_DIR, "vhosts/nginx-default.conf");
2876 }
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;
2882 } else {
2883 target_conf = fs::exists("/etc/nginx/conf.d")
2884 ? "/etc/nginx/conf.d/BastionGuard.conf"
2885 : "/etc/nginx/sites-enabled/BastionGuard.conf";
2886 }
2887
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";
2893
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";
2899 } else {
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";
2905 script << "else\n";
2906 script << _(" echo '[BastionGuard] Configurazione già presente, aggiornamento non necessario.'\n");
2907 script << "fi\n";
2908 script << "install -m 0644 " << tmpfile << " " << target_conf << "\n";
2909 }
2910
2911 script << "nginx -t && /usr/libexec/bastionguard/bastionguard-service --system restart nginx.service\n";
2912
2913 std::string out;
2914 if (!write_temp_script_and_elevate(script.str(), &out)) {
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"));
2918 return;
2919 }
2920
2921 show_message(_("Configurazione NGINX aggiornata e servizio riavviato."),
2922 Gtk::MessageType::INFO, _("BastionGuard - Esito"));
2923 });
2924
2925 frameWeb->set_child(*innerWeb);
2926 page_opts->append(*frameWeb);
2927
2928 page_order_.push_back(page_opts);
2929 stack.add(*page_opts, "options", _("Opzioni"));
2930}
2931
2933 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
2934
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);
2939
2940
2941 const std::string baseDir = Glib::get_home_dir() + "/.local/share/BastionGuard/phishing";
2942 std::filesystem::create_directories(baseDir);
2943
2944 const std::string wlUserPath = baseDir + "/whitelist.txt";
2945 const std::string wlSysPath = "/usr/share/BastionGuard/data/phishing/whitelist.txt";
2946
2947 const std::string blUserPath = baseDir + "/blacklist_custom.txt";
2948 const std::string blSysPath = "/usr/share/BastionGuard/data/phishing/blacklist_custom.txt";
2949
2950 const std::string dnsmasqConfPath = "/etc/dnsmasq.d/bastionguard-blacklist.conf";
2951 const std::string tmpDnsmasqConf = "/tmp/bastionguard-blacklist.conf";
2952
2953
2954 auto searchEntry = Gtk::make_managed<Gtk::SearchEntry>();
2955 searchEntry->set_placeholder_text(_("Cerca in whitelist/blacklist..."));
2956 page->append(*searchEntry);
2957
2958
2959 auto wlStoreView = Gio::ListStore<Gtk::StringObject>::create();
2960 auto blStoreView = Gio::ListStore<Gtk::StringObject>::create();
2961
2962
2963 auto wlSelection = Gtk::SingleSelection::create(wlStoreView);
2964 auto blSelection = Gtk::SingleSelection::create(blStoreView);
2965
2966
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>();
2970 lbl->set_xalign(0);
2971 lbl->set_margin(4);
2972 item->set_child(*lbl);
2973 });
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() : "");
2978 });
2979
2980
2981 auto wlLabel = Gtk::make_managed<Gtk::Label>(_("Whitelist (consenti)"));
2982 wlLabel->set_halign(Gtk::Align::START);
2983 page->append(*wlLabel);
2984
2985 Gtk::ListView wlListView(wlSelection, factory);
2986 wlListView.set_vexpand(true);
2987 wlListView.add_css_class("whitelist-view");
2988
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);
2994
2995 auto wlEntry = Gtk::make_managed<Gtk::Entry>();
2996 wlEntry->set_placeholder_text(_("Aggiungi a whitelist (dominio o IPv4)"));
2997 wlEntry->set_hexpand(true);
2998
2999 auto wlAdd = Gtk::make_managed<Gtk::Button>(_("Aggiungi"));
3000 auto wlRemove = Gtk::make_managed<Gtk::Button>(_("Rimuovi selezionato"));
3001
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);
3007
3008
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);
3013
3014 Gtk::ListView blListView(blSelection, factory);
3015 blListView.set_vexpand(true);
3016 blListView.add_css_class("whitelist-view");
3017
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);
3023
3024 auto blEntry = Gtk::make_managed<Gtk::Entry>();
3025 blEntry->set_placeholder_text(_("Aggiungi a blacklist (dominio o IPv4)"));
3026 blEntry->set_hexpand(true);
3027
3028 auto blAdd = Gtk::make_managed<Gtk::Button>(_("Aggiungi"));
3029 auto blRemove = Gtk::make_managed<Gtk::Button>(_("Rimuovi selezionato"));
3030
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);
3036
3037
3038 auto btnReload = Gtk::make_managed<Gtk::Button>(_("Ricarica"));
3039 auto btnSave = Gtk::make_managed<Gtk::Button>(_("Salva modifiche"));
3040
3041 auto bottom = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
3042 bottom->append(*btnReload);
3043 bottom->append(*btnSave);
3044 page->append(*bottom);
3045
3046
3047 auto wlAll = std::make_shared<std::vector<std::string>>();
3048 auto blAll = std::make_shared<std::vector<std::string>>();
3049
3050 // Utils
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());
3055 return s;
3056 };
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); });
3060 return s;
3061 };
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}$)"
3065 );
3066 return std::regex_match(s, re);
3067 };
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])?)*$)",
3071 std::regex::icase
3072 );
3073 if (!std::regex_match(s, re)) return false;
3074 if (s.find("..") != std::string::npos) return false;
3075 return true;
3076 };
3077
3078 auto loadFileToVector = [](const std::string& userPath,
3079 const std::string& sysPath,
3080 std::vector<std::string>& out) {
3081 out.clear();
3082
3083 auto load = [&](const std::string& path) -> bool {
3084 std::ifstream f(path);
3085 if (!f.is_open()) return false;
3086 std::string line;
3087 while (std::getline(f, line)) {
3088 if (!line.empty()) out.push_back(line);
3089 }
3090 return true;
3091 };
3092
3093 if (!load(userPath)) load(sysPath);
3094
3095
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())
3100 dedup.push_back(s);
3101 }
3102 out.swap(dedup);
3103 };
3104
3105 auto applyFilter = [=]() {
3106 const std::string qraw = Glib::ustring(searchEntry->get_text()).raw();
3107 const Glib::ustring q = Glib::ustring(qraw).lowercase();
3108
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));
3113 }
3114
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));
3119 }
3120 };
3121
3122 auto loadAll = [=]() {
3123 loadFileToVector(wlUserPath, wlSysPath, *wlAll);
3124 loadFileToVector(blUserPath, blSysPath, *blAll);
3125 applyFilter();
3126 };
3127
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();
3130 };
3131
3132 auto removeFromVec = [](std::vector<std::string>& v, const std::string& x) {
3133 v.erase(std::remove(v.begin(), v.end(), x), v.end());
3134 };
3135
3136
3137 loadAll();
3138
3139
3140 searchEntry->signal_search_changed().connect([applyFilter]() {
3141 applyFilter();
3142 });
3143
3144
3145 wlAdd->signal_clicked().connect([=]() {
3146 std::string v = trim(wlEntry->get_text());
3147 if (v.empty()) return;
3148
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."));
3153 dlg->show();
3154 return;
3155 }
3156 if (existsInVec(*wlAll, norm)) {
3157 auto dlg = Gtk::AlertDialog::create(_("Già presente"));
3158 dlg->set_detail(_("Questo elemento è già nella whitelist."));
3159 dlg->show();
3160 wlEntry->set_text("");
3161 return;
3162 }
3163
3164 wlAll->push_back(norm);
3165 wlEntry->set_text("");
3166 applyFilter();
3167 });
3168
3169
3170 wlRemove->signal_clicked().connect([=]() {
3171 guint pos = wlSelection->get_selected();
3172 if (pos == GTK_INVALID_LIST_POSITION) return;
3173
3174 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(wlStoreView->get_object(pos));
3175 if (!obj) return;
3176
3177 removeFromVec(*wlAll, obj->get_string());
3178 applyFilter();
3179 });
3180
3181
3182 blAdd->signal_clicked().connect([=]() {
3183 std::string v = trim(blEntry->get_text());
3184 if (v.empty()) return;
3185
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."));
3190 dlg->show();
3191 return;
3192 }
3193 if (existsInVec(*blAll, norm)) {
3194 auto dlg = Gtk::AlertDialog::create(_("Già presente"));
3195 dlg->set_detail(_("Questo elemento è già nella blacklist."));
3196 dlg->show();
3197 blEntry->set_text("");
3198 return;
3199 }
3200
3201 blAll->push_back(norm);
3202 blEntry->set_text("");
3203 applyFilter();
3204 });
3205
3206
3207 blRemove->signal_clicked().connect([=]() {
3208 guint pos = blSelection->get_selected();
3209 if (pos == GTK_INVALID_LIST_POSITION) return;
3210
3211 auto obj = std::dynamic_pointer_cast<Gtk::StringObject>(blStoreView->get_object(pos));
3212 if (!obj) return;
3213
3214 removeFromVec(*blAll, obj->get_string());
3215 applyFilter();
3216 });
3217
3218
3219 btnReload->signal_clicked().connect([=]() { loadAll(); });
3220
3221
3222 btnSave->signal_clicked().connect([=]() {
3223
3224 {
3225 std::ofstream f(wlUserPath, std::ios::trunc);
3226 if (!f.is_open()) {
3227 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3228 dlg->set_detail(_("Impossibile salvare la whitelist utente."));
3229 dlg->show();
3230 return;
3231 }
3232 for (const auto& s : *wlAll) f << s << "\n";
3233 }
3234
3235 {
3236 std::ofstream f(blUserPath, std::ios::trunc);
3237 if (!f.is_open()) {
3238 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3239 dlg->set_detail(_("Impossibile salvare la blacklist utente."));
3240 dlg->show();
3241 return;
3242 }
3243 for (const auto& s : *blAll) f << s << "\n";
3244 }
3245
3246 {
3247 std::ofstream f(tmpDnsmasqConf, std::ios::trunc);
3248 if (!f.is_open()) {
3249 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3250 dlg->set_detail(_("Impossibile creare file temporaneo per dnsmasq."));
3251 dlg->show();
3252 return;
3253 }
3254
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";
3259 } else {
3260 f << "# IP entry (not used by dnsmasq address=): " << item << "\n";
3261 }
3262 }
3263 }
3264
3265 const bool saved =
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();
3271 if (!saved) {
3272 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3273 dlg->set_detail(_("Impossibile installare i file o ricaricare dnsmasq."));
3274 dlg->show();
3275 return;
3276 }
3277
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."));
3280 dlg->show();
3281
3282 std::thread([=]() {
3285 (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef"}).exit_code);
3286 }
3287 }).detach();
3288 });
3289
3290 page_order_.push_back(page);
3291 stack.add(*page, "advanced-anti-phishing", _("Anti-Phishing avanzate"));
3292}
3293
3294
3295
3297 auto page_adv = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
3298 page_adv->set_margin(12);
3299
3300 // ===== Allowlist SHA256 =====
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);
3304
3305 allowStore = Gtk::ListStore::create(allowCols);
3306 allowView.set_model(allowStore);
3307 allowView.append_column(_("SHA256"), allowCols.col_hash);
3308 allowView.append_column(_("Regola (opzionale)"), allowCols.col_rule);
3309
3310 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
3311 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
3312 scrolled->set_expand(true);
3313 scrolled->set_child(allowView);
3314 vboxAllow->append(*scrolled);
3315
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);
3324
3326
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");
3333
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)");
3338
3339 shaEntry->set_margin_bottom(6);
3340 ruleEntry->set_margin_bottom(6);
3341
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);
3347
3348 dialog->add_button(_("_Annulla"), Gtk::ResponseType::CANCEL);
3349 dialog->add_button(_("_Aggiungi"), Gtk::ResponseType::OK);
3350
3351 dialog->signal_show().connect([shaEntry]() {
3352 shaEntry->grab_focus();
3353 });
3354
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);
3360
3361 if (!sha.empty()) {
3362 auto row = *(allowStore->append());
3363 row[allowCols.col_hash] = sha;
3364 row[allowCols.col_rule] = ruleEntry->get_text();
3365 }
3366 }
3367 dialog->close();
3368 });
3369
3370 dialog->set_visible(true);
3371 });
3372
3373 btnDel->signal_clicked().connect([this]() {
3374 if (auto sel = allowView.get_selection()) {
3375 if (auto iter = sel->get_selected())
3376 allowStore->erase(iter);
3377 }
3378 });
3379
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);
3385
3386 chkSuspOnly = Gtk::make_managed<Gtk::CheckButton>(_("Analizza solo file sospetti"));
3387 auto lblIgnPaths = Gtk::make_managed<Gtk::Label>(_("Percorsi da ignorare"));
3388 txtIgnPaths = Gtk::make_managed<Gtk::Entry>();
3389 txtIgnPaths->set_placeholder_text("/home/*/.cache;/var/cache;/tmp;...");
3390 txtIgnPaths->add_css_class("home-entry");
3391
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;...");
3395 txtCompany->add_css_class("company-entry");
3396
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");
3401
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;...");
3405 txtSuspExt->add_css_class("danger-entry");
3406
3407 grid->attach(*chkSuspOnly, 0, 0, 2, 1);
3408 grid->attach(*lblIgnPaths, 0, 1, 1, 1);
3409 grid->attach(*txtIgnPaths, 1, 1, 1, 1);
3410 grid->attach(*lblCompany, 0, 2, 1, 1);
3411 grid->attach(*txtCompany, 1, 2, 1, 1);
3412 grid->attach(*lblIgnExt, 0, 3, 1, 1);
3413 grid->attach(*txtIgnExt, 1, 3, 1, 1);
3414 grid->attach(*lblSuspExt, 0, 4, 1, 1);
3415 grid->attach(*txtSuspExt, 1, 4, 1, 1);
3416
3417 frameFilters->set_child(*grid);
3418 page_adv->append(*frameFilters);
3419
3420 // === Pulsante Salva impostazioni ===
3421 auto btnSave = Gtk::make_managed<Gtk::Button>(_("Salva impostazioni avanzate"));
3422 page_adv->append(*btnSave);
3423
3424 btnSave->signal_clicked().connect([this]() {
3427 chkSuspOnly->get_active(),
3428 txtIgnPaths->get_text(),
3429 txtIgnExt->get_text(),
3430 txtSuspExt->get_text()
3431 );
3432
3433
3434 antiransomStatus.set_text(_("✔ Impostazioni avanzate salvate"));
3435 });
3436
3438 page_order_.push_back(page_adv);
3439 stack.add(*page_adv, "advanced-anti-ransomware", _("Anti-Ransomware avanzate"));
3440}
3441
3444
3445 auto page_pay = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
3446 page_pay->set_margin(12);
3447
3448 auto framePay = Gtk::make_managed<Gtk::Frame>();
3449
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);
3455
3456 auto run_cmd_capture = [](const std::vector<std::string>& argv,
3457 std::string* stdout_out = nullptr,
3458 std::string* stderr_out = nullptr) -> bool
3459 {
3460 try {
3461 auto proc = Gio::Subprocess::create(
3462 argv,
3463 Gio::Subprocess::Flags::STDOUT_PIPE |
3464 Gio::Subprocess::Flags::STDERR_PIPE
3465 );
3466
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();
3470
3471 return proc->get_successful();
3472 } catch (const std::exception& ex) {
3473 if (stderr_out) *stderr_out = ex.what();
3474 return false;
3475 } catch (...) {
3476 if (stderr_out) *stderr_out = "Eccezione sconosciuta";
3477 return false;
3478 }
3479 };
3480
3481 auto resolve_ca_path = []() -> std::string
3482 {
3483 const auto user_ca = Glib::build_filename(
3484 Glib::get_home_dir(),
3485 ".local", "share", "BastionGuard", "certs", "intercept-ca.crt.pem");
3486
3487 if (std::filesystem::exists(user_ca))
3488 return user_ca;
3489
3490 const std::string system_ca = "/etc/BastionGuard/certs/intercept-ca.crt.pem";
3491 if (std::filesystem::exists(system_ca))
3492 return system_ca;
3493
3494 return {};
3495 };
3496
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)) {
3500 dlg->show(*win);
3501 return;
3502 }
3503 }
3504 dlg->show();
3505 };
3506
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);
3511
3512
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.")
3517 );
3518 lblPayDesc->set_wrap(true);
3519 lblPayDesc->set_halign(Gtk::Align::START);
3520 lblPayDesc->add_css_class("card-description");
3521 innerPay->append(*lblPayDesc);
3522
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.")
3528 );
3529 lblPayInfo->set_wrap(true);
3530 lblPayInfo->set_halign(Gtk::Align::START);
3531 lblPayInfo->add_css_class("alert-info");
3532 innerPay->append(*lblPayInfo);
3533
3534 auto lblPayToggle = Gtk::make_managed<Gtk::Label>(_("Abilita pagamenti sicuri"));
3535 lblPayToggle->set_halign(Gtk::Align::START);
3536
3537 auto swPay = Gtk::make_managed<Gtk::Switch>();
3538 swPay->set_halign(Gtk::Align::END);
3539 swPay->set_active(SettingsStore::get_secure_payments_enabled());
3540
3541 auto rowPay = Gtk::make_managed<Gtk::CenterBox>();
3542 rowPay->set_start_widget(*lblPayToggle);
3543 rowPay->set_end_widget(*swPay);
3544 innerPay->append(*rowPay);
3545
3546 auto lblPayStatus = Gtk::make_managed<Gtk::Label>();
3547 lblPayStatus->set_halign(Gtk::Align::START);
3548 innerPay->append(*lblPayStatus);
3549
3550 auto refreshPayStatus = [lblPayStatus, swPay]() {
3551 lblPayStatus->set_text(
3552 swPay->get_active()
3553 ? _("✔ Pagamenti sicuri: abilitati")
3554 : _("ℹ️ Pagamenti sicuri: disabilitati")
3555 );
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");
3559 };
3560 refreshPayStatus();
3561
3562 auto lblList = Gtk::make_managed<Gtk::Label>(_("Domini/URL per Pagamenti Sicuri"));
3563 lblList->set_halign(Gtk::Align::START);
3564 innerPay->append(*lblList);
3565
3566 auto store = Gio::ListStore<Gtk::StringObject>::create();
3567 auto selection = Gtk::SingleSelection::create(store);
3568
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);
3573 lbl->set_margin(6);
3574 item->set_child(*lbl);
3575 });
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() : "");
3580 });
3581
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");
3586
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);
3593
3594 auto reload_from_file = [store]() {
3595 store->remove_all();
3596 auto domains = load_payments_domains();
3597 for (const auto& d : domains)
3598 store->append(Gtk::StringObject::create(d));
3599 };
3600 reload_from_file();
3601
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");
3606
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"));
3611
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);
3619
3620 btnAdd->signal_clicked().connect([store, entry, show_alert]() {
3621 std::string in = entry->get_text();
3622 std::string dom = normalize_domain_or_url(in);
3623 if (dom.empty()) {
3624 auto dlg = Gtk::AlertDialog::create(_("Valore non valido"));
3625 dlg->set_detail(_("Inserisci un dominio o URL valido."));
3626 show_alert(dlg);
3627 return;
3628 }
3629
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("");
3634 return;
3635 }
3636 }
3637
3638 store->append(Gtk::StringObject::create(dom));
3639 entry->set_text("");
3640 });
3641
3642 btnRemove->signal_clicked().connect([selection, store]() {
3643 guint pos = selection->get_selected();
3644 if (pos != GTK_INVALID_LIST_POSITION)
3645 store->remove(pos);
3646 });
3647
3648 btnReload->signal_clicked().connect([reload_from_file]() {
3649 reload_from_file();
3650 });
3651
3652 btnSave->signal_clicked().connect([store, swPay, lblPayStatus, show_alert]() {
3653 std::vector<std::string> domains;
3654 domains.reserve(store->get_n_items());
3655
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());
3659 }
3660
3661 const bool enabled_flag = swPay->get_active();
3662
3663 if (!save_payments_domains(domains, enabled_flag)) {
3664 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3665 dlg->set_detail(_("Impossibile salvare payments.json."));
3666 show_alert(dlg);
3667 return;
3668 }
3669
3670 lblPayStatus->set_text(_("✔ Lista pagamenti salvata"));
3671 lblPayStatus->remove_css_class("status-muted");
3672 lblPayStatus->add_css_class("status-ok");
3673
3675 std::thread([]() {
3676 (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef.service"}).exit_code);
3677 }).detach();
3678 }
3679 });
3680
3681 swPay->property_active().signal_changed().connect([swPay, refreshPayStatus]() {
3682 const bool enabled = swPay->get_active();
3685 refreshPayStatus();
3686 });
3687
3688 auto btnInstallSafeNav = Gtk::make_managed<Gtk::Button>(_("Installazione navigazione sicura"));
3689 btnInstallSafeNav->add_css_class("btn-warning");
3690 innerPay->append(*btnInstallSafeNav);
3691
3692 auto installLogBuffer = Gtk::TextBuffer::create();
3693
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."));
3698 return;
3699 }
3700 btnInstallSafeNav->set_sensitive(false);
3701 installLogBuffer->set_text(_("Avvio installazione navigazione sicura...\n"));
3702
3703 Gtk::Window* log_popup = create_log_popup(this, installLogBuffer);
3704
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;
3709
3710 const std::string ca_src = resolve_ca_path();
3711 const std::string helper = "/usr/share/BastionGuard/data/scripts/install-ca-system.sh";
3712
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";
3717 } else {
3718 final_log += "[BastionGuard] Uso certificato: " + ca_src + "\n";
3719 final_log += "[BastionGuard] Eseguo install-ca-system.sh con pkexec...\n\n";
3720
3721 std::string out1, err1;
3722 install_ok = run_cmd_capture({"pkexec", helper, ca_src}, &out1, &err1);
3723
3724 if (!out1.empty()) final_log += out1;
3725 if (!err1.empty()) {
3726 if (!final_log.empty() && final_log.back() != '\n') final_log += "\n";
3727 final_log += err1;
3728 }
3729
3730 if (!install_ok) {
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);
3734
3735 if (!out2.empty()) final_log += out2;
3736 if (!err2.empty()) {
3737 if (!final_log.empty() && final_log.back() != '\n') final_log += "\n";
3738 final_log += err2;
3739 }
3740 }
3741
3742 if (install_ok) {
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"},
3747 &out3, &err3
3748 );
3749
3750 if (!out3.empty()) final_log += out3;
3751 if (!err3.empty()) {
3752 if (!final_log.empty() && final_log.back() != '\n') final_log += "\n";
3753 final_log += err3;
3754 }
3755
3756 if (restart_ok)
3757 final_log += "\n[BastionGuard] ✅ BastionGuard-cef.service riavviato correttamente.\n";
3758 else
3759 final_log += "\n[BastionGuard] ⚠️ Impossibile riavviare BastionGuard-cef.service in modalità user.\n";
3760 } else {
3761 final_log += "\n[BastionGuard] ❌ Installazione navigazione sicura fallita.\n";
3762 }
3763 }
3764
3765 // Se la pagina è già stata distrutta, non schedulare la UI callback.
3766 if (!alive->load()) return;
3767
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; // pagina morta
3771 installLogBuffer->set_text(final_log);
3772 btnInstallSafeNav->set_sensitive(true);
3773
3774 if (log_popup)
3775 log_popup->present();
3776
3777 if (install_ok) {
3778 lblPayStatus->set_text(_("✔ Navigazione sicura installata"));
3779 lblPayStatus->remove_css_class("status-muted");
3780 lblPayStatus->add_css_class("status-ok");
3781
3782 auto dlg = Gtk::AlertDialog::create(_("Installazione completata"));
3783 if (restart_ok) {
3784 dlg->set_detail(_("La navigazione sicura è stata installata correttamente.\n\nChiudi il browser e riaprilo."));
3785 } else {
3786 dlg->set_detail(_("La navigazione sicura è stata installata, ma il riavvio del servizio BastionGuard-cef non è riuscito.\n\nChiudi il browser e riaprilo."));
3787 }
3788 show_alert(dlg);
3789 } else {
3790 lblPayStatus->set_text(_("❌ Installazione navigazione sicura fallita"));
3791 lblPayStatus->remove_css_class("status-ok");
3792 lblPayStatus->add_css_class("status-muted");
3793
3794 auto dlg = Gtk::AlertDialog::create(_("Errore"));
3795 dlg->set_detail(_("Impossibile completare l'installazione della navigazione sicura. Controlla il log nella finestra aperta."));
3796 show_alert(dlg);
3797 }
3798
3799 return false;
3800 }
3801 );
3802 }).detach();
3803 }
3804 );
3805
3806 const bool cef_available = BastionGuard::Platform::cef_available();
3807
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");
3822 }
3823
3824 framePay->set_child(*innerPay);
3825 page_pay->append(*framePay);
3826
3827 page_order_.push_back(page_pay);
3828 stack.add(*page_pay, "secure-payments", _("Pagamenti Sicuri"));
3829}
3830
3831
3832static std::vector<std::string> load_proxy_bypass_domains() {
3833 const std::string path = Glib::build_filename(
3834 Glib::get_home_dir(), ".config", "BastionGuard", "proxy_bypass.json");
3835 std::vector<std::string> result;
3836 if (!std::filesystem::exists(path)) return result;
3837 try {
3838 std::ifstream f(path);
3839 nlohmann::json j; f >> j;
3840 if (j.contains("list") && j["list"].is_array())
3841 for (const auto& item : j["list"])
3842 if (item.is_string()) {
3843 std::string d = item.get<std::string>();
3844 if (!d.empty()) result.push_back(d);
3845 }
3846 } catch (...) {}
3847 return result;
3848}
3849
3850static bool save_proxy_bypass_domains(const std::vector<std::string>& domains) {
3851 const std::string dir = Glib::build_filename(
3852 Glib::get_home_dir(), ".config", "BastionGuard");
3853 const std::string path = Glib::build_filename(dir, "proxy_bypass.json");
3854 try {
3855 std::filesystem::create_directories(dir);
3856 nlohmann::json j;
3857 j["version"] = 1;
3858 j["description"] = "Domini che bypassano completamente il proxy BastionGuard. "
3859 "Tunnel TCP puro trasparente — nessuna intercettazione TLS. "
3860 "Usare per siti che attivano 2FA o CAPTCHA in presenza del proxy.";
3861 j["list"] = nlohmann::json::array();
3862 for (const auto& d : domains) j["list"].push_back(d);
3863 const std::string tmp = path + ".tmp";
3864 std::ofstream out(tmp, std::ios::trunc);
3865 if (!out.is_open()) return false;
3866 out << j.dump(2); out.flush(); out.close();
3867 std::error_code ec;
3868 std::filesystem::rename(tmp, path, ec);
3869 return !ec;
3870 } catch (...) { return false; }
3871}
3872
3873// =============================================================================
3874// classify_bypass_entry
3875//
3876// Classifica una entry inserita dall'utente nel campo "Bypass Proxy".
3877// Sono accettati 4 formati:
3878// - dominio DNS: "banca.it", "checkout.esempio.com"
3879// - IPv4 letterale: "93.55.84.43"
3880// - IPv6 letterale: "2001:4860:4860::8888"
3881// - CIDR: "93.55.84.0/24", "2a01:4f8::/29"
3882//
3883// Ritorna: "domain", "ipv4", "ipv6", "cidr", oppure "" se invalido.
3884// =============================================================================
3886
3887static BypassKind classify_bypass_entry(const std::string& s_in) {
3888 if (s_in.empty()) return BypassKind::Invalid;
3889
3890 // Strip eventuali bracket IPv6: [::1] → ::1
3891 std::string s = s_in;
3892 if (s.size() >= 2 && s.front() == '[' && s.back() == ']')
3893 s = s.substr(1, s.size() - 2);
3894
3895 // Caso CIDR: c'è uno slash, tipo "1.2.3.0/24" o "2a01::/32"
3896 auto slash = s.find('/');
3897 if (slash != std::string::npos) {
3898 const std::string addr = s.substr(0, slash);
3899 const std::string mask = s.substr(slash + 1);
3900 if (addr.empty() || mask.empty()) return BypassKind::Invalid;
3901
3902 // mask numerica
3903 for (char c : mask) if (!std::isdigit(static_cast<unsigned char>(c)))
3904 return BypassKind::Invalid;
3905 const int m = std::atoi(mask.c_str());
3906
3907 in_addr v4{};
3908 in6_addr v6{};
3909 if (::inet_pton(AF_INET, addr.c_str(), &v4) == 1) {
3910 if (m < 0 || m > 32) return BypassKind::Invalid;
3911 return BypassKind::CIDR;
3912 }
3913 if (::inet_pton(AF_INET6, addr.c_str(), &v6) == 1) {
3914 if (m < 0 || m > 128) return BypassKind::Invalid;
3915 return BypassKind::CIDR;
3916 }
3917 return BypassKind::Invalid;
3918 }
3919
3920 // Caso IPv4/IPv6 letterale
3921 {
3922 in_addr v4{};
3923 in6_addr v6{};
3924 if (::inet_pton(AF_INET, s.c_str(), &v4) == 1) return BypassKind::IPv4;
3925 if (::inet_pton(AF_INET6, s.c_str(), &v6) == 1) return BypassKind::IPv6;
3926 }
3927
3928 // Caso dominio DNS: deve avere almeno un punto, solo caratteri DNS validi.
3929 if (s.find('.') == std::string::npos) return BypassKind::Invalid;
3930 for (char c : s) {
3931 const unsigned char uc = static_cast<unsigned char>(c);
3932 if (!(std::isalnum(uc) || c == '.' || c == '-' || c == '_'))
3933 return BypassKind::Invalid;
3934 }
3935 if (s.front() == '.' || s.back() == '.' || s.front() == '-')
3936 return BypassKind::Invalid;
3937
3938 return BypassKind::Domain;
3939}
3940
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);
3947
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);
3952
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.")
3963 );
3964 lblDesc->set_wrap(true);
3965 lblDesc->set_halign(Gtk::Align::START);
3966 lblDesc->add_css_class("card-description");
3967 inner->append(*lblDesc);
3968
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.")
3972 );
3973 lblInfo->set_wrap(true);
3974 lblInfo->set_halign(Gtk::Align::START);
3975 lblInfo->add_css_class("alert-info");
3976 inner->append(*lblInfo);
3977
3978 auto lblList = Gtk::make_managed<Gtk::Label>(_("Domini in bypass:"));
3979 lblList->set_halign(Gtk::Align::START);
3980 inner->append(*lblList);
3981
3982 auto store = Gio::ListStore<Gtk::StringObject>::create();
3983 auto selection = Gtk::SingleSelection::create(store);
3984
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);
3989 lbl->set_margin(6);
3990 item->set_child(*lbl);
3991 });
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() : "");
3996 });
3997
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");
4002
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);
4009
4010 auto reload_from_file = [store]() {
4011 store->remove_all();
4012 for (const auto& d : load_proxy_bypass_domains())
4013 store->append(Gtk::StringObject::create(d));
4014 };
4015 reload_from_file();
4016
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");
4021
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");
4027
4028 auto lblStatus = Gtk::make_managed<Gtk::Label>();
4029 lblStatus->set_halign(Gtk::Align::START);
4030
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);
4039
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;
4044 }
4045 dlg->show();
4046 };
4047
4048 btnAdd->signal_clicked().connect([store, entry, lblStatus, show_alert_bypass]() {
4049 std::string s = entry->get_text();
4050
4051 // Strip schema https:// http:// se presente
4052 auto p = s.find("://");
4053 if (p != std::string::npos) s = s.substr(p + 3);
4054
4055 // Per IPv6 con bracket eventuali "[::1]:443" → strip della porta DOPO ]
4056 // Per dominio/IPv4 strip del path "/foo" e della porta ":443"
4057 if (!s.empty() && s.front() == '[') {
4058 auto rb = s.find(']');
4059 if (rb != std::string::npos) {
4060 // tieni "[...]" pulito, scarta tutto dopo ]
4061 s = s.substr(0, rb + 1);
4062 }
4063 } else {
4064 // Path
4065 auto sl = s.find('/');
4066 // Una "/" può essere parte di un CIDR (es. "1.2.3.0/24"). Per
4067 // capire se è path o CIDR: dopo lo / ci sono solo digit?
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);
4074 }
4075 // Porta ":443" — solo se NON è un IPv6 (che ha tanti ":")
4076 if (std::count(s.begin(), s.end(), ':') == 1) {
4077 auto col = s.find(':');
4078 s = s.substr(0, col);
4079 }
4080 }
4081
4082 // Trim whitespace
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();
4085
4086 // I domini DNS sono case-insensitive, gli IPv6 letterali sì (per-RFC),
4087 // gli IPv4 e i CIDR contengono solo digit/. quindi il tolower non li tocca.
4088 std::transform(s.begin(), s.end(), s.begin(),
4089 [](unsigned char c){ return static_cast<char>(std::tolower(c)); });
4090
4091 const BypassKind kind = classify_bypass_entry(s);
4092 if (kind == BypassKind::Invalid) {
4093 auto dlg = Gtk::AlertDialog::create(_("Valore non valido"));
4094 dlg->set_detail(_("Formato non riconosciuto.\n\n"
4095 "Esempi accettati:\n"
4096 " • banca.it\n"
4097 " • checkout.esempio.com\n"
4098 " • 93.55.84.43\n"
4099 " • 2001:db8::1\n"
4100 " • 93.55.84.0/24"));
4101 show_alert_bypass(dlg);
4102 return;
4103 }
4104
4105 // Anti-duplicati
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; }
4109 }
4110 store->append(Gtk::StringObject::create(s));
4111 entry->set_text("");
4112
4113 const char* kind_name = "?";
4114 switch (kind) {
4115 case BypassKind::Domain: kind_name = "dominio"; break;
4116 case BypassKind::IPv4: kind_name = "IPv4"; break;
4117 case BypassKind::IPv6: kind_name = "IPv6"; break;
4118 case BypassKind::CIDR: kind_name = "CIDR"; break;
4119 case BypassKind::Invalid: break;
4120 }
4121 char buf[256];
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");
4127 });
4128
4129 btnRemove->signal_clicked().connect([selection, store, lblStatus]() {
4130 guint pos = selection->get_selected();
4131 if (pos != GTK_INVALID_LIST_POSITION) {
4132 store->remove(pos);
4133 lblStatus->set_text(_("🗑 Dominio rimosso — clicca Salva per applicare."));
4134 lblStatus->remove_css_class("status-ok");
4135 lblStatus->add_css_class("status-muted");
4136 }
4137 });
4138
4139 btnReload->signal_clicked().connect([reload_from_file, lblStatus]() {
4140 reload_from_file();
4141 lblStatus->set_text(_("↩️ Lista ripristinata dal file."));
4142 lblStatus->remove_css_class("status-ok");
4143 lblStatus->add_css_class("status-muted");
4144 });
4145
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());
4152 }
4153 if (!save_proxy_bypass_domains(domains)) {
4154 auto dlg = Gtk::AlertDialog::create(_("Errore"));
4155 dlg->set_detail(_("Impossibile salvare proxy_bypass.json."));
4156 show_alert_bypass(dlg);
4157 return;
4158 }
4159 lblStatus->set_text(_("✔ proxy_bypass.json salvato"));
4160 lblStatus->remove_css_class("status-muted");
4161 lblStatus->add_css_class("status-ok");
4162 // Riavvia il servizio CEF solo nelle build/distribuzioni che lo supportano.
4164 std::thread([]() {
4165 (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-cef.service"}).exit_code);
4166 }).detach();
4167 }
4168 });
4169
4170 frame->set_child(*inner);
4171 page->append(*frame);
4172 page_order_.push_back(page);
4173 stack.add(*page, "proxy-bypass", _("Bypass Proxy"));
4174}
4175
4176
4177
4179{
4180 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4181 page->set_margin(12);
4182
4183 auto frame = Gtk::make_managed<Gtk::Frame>();
4184 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
4185 inner->set_margin(10);
4186
4187 auto title = Gtk::make_managed<Gtk::Label>(_("Servizi utente"));
4188 title->set_halign(Gtk::Align::START);
4189 title->add_css_class("card-title");
4190 inner->append(*title);
4191
4192 auto desc = Gtk::make_managed<Gtk::Label>(
4193 _("Questi servizi vengono gestiti dal backend init rilevato.\n"
4194 "Abilitare = avvio automatico al login. Disabilitare = non parte automaticamente.")
4195 );
4196 desc->set_wrap(true);
4197 desc->set_halign(Gtk::Align::START);
4198 desc->add_css_class("card-description");
4199 inner->append(*desc);
4200
4201 auto info = Gtk::make_managed<Gtk::Label>(
4202 _("Nota: puoi avviarli manualmente con: /usr/libexec/bastionguard/bastionguard-service --user start <unit>")
4203 );
4204 info->set_wrap(true);
4205 info->add_css_class("alert-info");
4206 inner->append(*info);
4207
4208 std::vector<std::tuple<std::string, Glib::ustring, Glib::ustring>> user_services = {
4209 {"BastionGuard-privacyd.service", _("Privacy Guard"), _("Blocco microfono e webcam quando non in uso")},
4210 {"BastionGuard-ransomware-alert.service", _("Ransomware Alert"), _("Notifiche su possibili attività ransomware")},
4211 {"BastionGuard-ransomware-realtime-alert.service", _("Ransomware Realtime Alert"), _("Avvisi in tempo reale su comportamenti sospetti")},
4212 {"BastionGuard-ransomware-scanner.service", _("Ransomware Scanner"), _("Scansione periodica alla ricerca di comportamenti malevoli")},
4213 {"BastionGuard-useragent.service", _("User-Agent Randomizer"), _("Modifica user-agent per aumentare anonimato")},
4214 {"BastionGuard-pacd.service", _("PAC Daemon"), _("Gestione automatica di proxy PAC sicuri")},
4215 {"BastionGuard-mailproxy.service", _("Mail Proxy"), _("Proxy SMTP locale per protezione email in uscita")}
4216 };
4217
4219 user_services.emplace_back(
4220 "BastionGuard-cef.service",
4221 _("CEF Sandbox Service"),
4222 _("Supporto in background per pagamenti sicuri con CEF")
4223 );
4224 }
4225
4226 build_services_section(*inner, user_services, false);
4227
4228 frame->set_child(*inner);
4229 page->append(*frame);
4230
4231 page_order_.push_back(page);
4232 stack.add(*page, "user-services", _("Servizi Utente"));
4233}
4234
4236{
4237 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4238 page->set_margin(12);
4239
4240 auto frame = Gtk::make_managed<Gtk::Frame>();
4241 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
4242 inner->set_margin(10);
4243
4244 auto title = Gtk::make_managed<Gtk::Label>(_("Servizi di Sistema (pkexec)"));
4245 title->set_halign(Gtk::Align::START);
4246 title->add_css_class("card-title");
4247 inner->append(*title);
4248
4249 auto desc = Gtk::make_managed<Gtk::Label>(
4250 _("Questi servizi richiedono privilegi amministrativi.\n"
4251 "Vengono gestiti con: pkexec /usr/libexec/bastionguard/bastionguard-service --system ...")
4252 );
4253 desc->set_wrap(true);
4254 desc->set_halign(Gtk::Align::START);
4255 desc->add_css_class("card-description");
4256 inner->append(*desc);
4257
4258 auto warn = Gtk::make_managed<Gtk::Label>(
4259 _("Durante l’attivazione/disattivazione potrebbe essere richiesta la password.")
4260 );
4261 warn->set_wrap(true);
4262 warn->add_css_class("alert-warning");
4263 inner->append(*warn);
4264
4265 const std::vector<std::tuple<std::string, Glib::ustring, Glib::ustring>> system_services = {
4266 {"BastionGuard-phishing-scanner.service", _("Phishing Scanner (System)"), _("Scansione anti-phishing a livello di sistema")},
4267 {"BastionGuard-ransomware-realtime.service", _("Ransomware Realtime (System)"), _("Monitor realtime ransomware a livello di sistema")},
4268 {"BastionGuard-usbd.service", _("USB Protection (System)"), _("Monitoraggio e blocco dispositivi USB sospetti")},
4269 };
4270
4271 build_services_section(*inner, system_services, true);
4272
4273 frame->set_child(*inner);
4274 page->append(*frame);
4275
4276 page_order_.push_back(page);
4277 stack.add(*page, "system-services", _("Servizi Sistema"));
4278}
4279
4280
4282 Gtk::Box& parent,
4283 const std::vector<std::tuple<std::string, Glib::ustring, Glib::ustring>>& services,
4284 bool system_units)
4285{
4286 auto grid = Gtk::make_managed<Gtk::Grid>();
4287 grid->set_column_spacing(28);
4288 grid->set_row_spacing(18);
4289 grid->set_margin_top(12);
4290 grid->set_margin_start(8);
4291 grid->set_margin_end(8);
4292
4293 struct RowBinding {
4294 Gtk::Switch* sw;
4295 std::string name;
4296 };
4297 auto bindings = std::make_shared<std::vector<RowBinding>>();
4298 bindings->reserve(services.size());
4299
4300 int col = 0;
4301 int row = 0;
4302
4303 for (const auto& svc : services)
4304 {
4305 const std::string name = std::get<0>(svc);
4306 const Glib::ustring label = std::get<1>(svc);
4307 const Glib::ustring description = std::get<2>(svc);
4308
4309 auto service_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
4310 service_box->set_hexpand(true);
4311
4312 auto lbl_name = Gtk::make_managed<Gtk::Label>(label);
4313 lbl_name->set_halign(Gtk::Align::START);
4314 lbl_name->add_css_class("service-name");
4315
4316 auto lbl_desc = Gtk::make_managed<Gtk::Label>(description);
4317 lbl_desc->set_halign(Gtk::Align::START);
4318 lbl_desc->set_wrap(true);
4319 lbl_desc->set_wrap_mode(Pango::WrapMode::WORD_CHAR);
4320 lbl_desc->set_ellipsize(Pango::EllipsizeMode::END);
4321 lbl_desc->set_max_width_chars(42);
4322 lbl_desc->set_hexpand(true);
4323 lbl_desc->add_css_class("service-desc");
4324
4325 service_box->append(*lbl_name);
4326 service_box->append(*lbl_desc);
4327
4328 auto sw = Gtk::make_managed<Gtk::Switch>();
4329 sw->set_halign(Gtk::Align::END);
4330 sw->set_valign(Gtk::Align::CENTER);
4331 sw->set_hexpand(false);
4332
4333 bool enabled = system_units ? is_system_service_enabled(name)
4335 sw->set_active(enabled);
4336
4337 auto row_container = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 16);
4338 row_container->set_hexpand(true);
4339 row_container->add_css_class("service-row");
4340 row_container->append(*service_box);
4341 row_container->append(*sw);
4342
4343 grid->attach(*row_container, col, row, 1, 1);
4344
4345 bindings->push_back({sw, name});
4346
4347 // Cambio stato
4348 sw->property_active().signal_changed().connect(
4349 [this, name, sw, system_units]()
4350 {
4351 bool want_enabled = sw->get_active();
4352
4353 bool success = false;
4354 if (system_units)
4355 success = want_enabled ? enable_system_service(name) : disable_system_service(name);
4356 else
4357 success = want_enabled ? enable_user_service(name) : disable_user_service(name);
4358
4359 if (!success)
4360 {
4361 sw->set_active(!want_enabled);
4362
4364 want_enabled ? _("Impossibile abilitare il servizio")
4365 : _("Impossibile disabilitare il servizio"),
4366 name
4367 );
4368 }
4369 });
4370
4371 col++;
4372 if (col >= 2)
4373 {
4374 col = 0;
4375 row++;
4376 }
4377 }
4378
4379 parent.append(*grid);
4380
4381 auto btn_refresh = Gtk::make_managed<Gtk::Button>(_("Aggiorna stato servizi"));
4382 btn_refresh->set_halign(Gtk::Align::END);
4383 btn_refresh->signal_clicked().connect(
4384 [this, bindings, system_units]()
4385 {
4386 for (auto& b : *bindings)
4387 {
4388 bool enabled = system_units ? is_system_service_enabled(b.name)
4389 : is_user_service_enabled(b.name);
4390 b.sw->set_active(enabled);
4391 }
4392 });
4393
4394 parent.append(*btn_refresh);
4395}
4396
4397
4398
4399bool SettingsPage::pkexec_servicectl(const std::vector<std::string>& args,
4400 std::string* out_stdout)
4401{
4402 std::vector<std::string> argv;
4403 argv.reserve(args.size() + 3);
4404 argv.push_back("pkexec");
4405 argv.push_back("/usr/libexec/bastionguard/bastionguard-service");
4406 argv.push_back("--system");
4407 argv.insert(argv.end(), args.begin(), args.end());
4408
4409 return run_cmd(argv, out_stdout, false);
4410}
4411
4412
4413bool SettingsPage::is_user_service_enabled(const std::string& service_name)
4414{
4415 try
4416 {
4417 auto proc = Gio::Subprocess::create(
4418 {"/usr/libexec/bastionguard/bastionguard-service", "--user", "is-enabled", service_name},
4419 Gio::Subprocess::Flags::STDOUT_PIPE |
4420 Gio::Subprocess::Flags::STDERR_PIPE
4421 );
4422
4423 proc->wait();
4424 auto [stdout_str, stderr_str] = proc->communicate_utf8("");
4425
4426 if (!proc->get_successful())
4427 return false;
4428
4429 std::string result = stdout_str.raw();
4430 result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
4431 result.erase(std::remove(result.begin(), result.end(), '\r'), result.end());
4432
4433 return result == "enabled";
4434 }
4435 catch (...)
4436 {
4437 return false;
4438 }
4439}
4440
4441bool SettingsPage::enable_user_service(const std::string& service_name)
4442{
4443 if (!run_cmd({"/usr/libexec/bastionguard/bastionguard-service", "--user", "enable", "--now", service_name}))
4444 return false;
4445
4446 run_cmd({"/usr/libexec/bastionguard/bastionguard-service", "--user", "start", service_name});
4447 return true;
4448}
4449
4450bool SettingsPage::disable_user_service(const std::string& service_name)
4451{
4452 run_cmd({"/usr/libexec/bastionguard/bastionguard-service", "--user", "stop", service_name});
4453 return run_cmd({"/usr/libexec/bastionguard/bastionguard-service", "--user", "disable", "--now", service_name});
4454}
4455
4456
4457bool SettingsPage::is_system_service_enabled(const std::string& unit_name)
4458{
4459 try
4460 {
4461 auto proc = Gio::Subprocess::create(
4462 {"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-enabled", unit_name},
4463 Gio::Subprocess::Flags::STDOUT_PIPE |
4464 Gio::Subprocess::Flags::STDERR_PIPE
4465 );
4466
4467 proc->wait();
4468 auto [stdout_str, stderr_str] = proc->communicate_utf8("");
4469
4470 if (!proc->get_successful())
4471 return false;
4472
4473 std::string result = stdout_str.raw();
4474 result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
4475 result.erase(std::remove(result.begin(), result.end(), '\r'), result.end());
4476
4477 return result == "enabled";
4478 }
4479 catch (...)
4480 {
4481 return false;
4482 }
4483}
4484
4485bool SettingsPage::enable_system_service(const std::string& unit_name)
4486{
4487 if (!pkexec_servicectl({"enable", "--now", unit_name}, nullptr))
4488 return false;
4489
4490 pkexec_servicectl({"start", unit_name}, nullptr);
4491 return true;
4492}
4493
4494bool SettingsPage::disable_system_service(const std::string& unit_name)
4495{
4496 pkexec_servicectl({"stop", unit_name}, nullptr);
4497 return pkexec_servicectl({"disable", "--now", unit_name}, nullptr);
4498}
4499
4500
4501void show_service_error_dialog(const Glib::ustring& msg, const std::string& service)
4502{
4503 auto dlg = Gtk::AlertDialog::create(msg);
4504 dlg->set_detail(Glib::ustring::compose(_("Servizio: %1\n\n"
4505 "Verifica i permessi o prova ad eseguire il comando da terminale."), service));
4506 dlg->show();
4507}
4508
4510 allowStore->clear();
4511 std::ifstream f("config/allowlist.txt");
4512 if (!f.is_open()) return;
4513 std::string line;
4514 while (std::getline(f, line)) {
4515 if (line.empty() || line[0] == '#') continue;
4516 std::string sha, rule;
4517 auto semi = line.find(';');
4518 if (semi == std::string::npos) sha = line;
4519 else { sha = line.substr(0, semi); rule = line.substr(semi + 1); }
4520 auto row = *(allowStore->append());
4521 row[allowCols.col_hash] = Glib::ustring(sha);
4522 row[allowCols.col_rule] = Glib::ustring(rule);
4523 }
4524}
4525
4526
4527
4529 std::string confPath = Glib::get_home_dir() + "/.config/BastionGuard/allowlist.txt";
4530
4531 std::filesystem::create_directories(Glib::get_home_dir() + "/.config/BastionGuard");
4532
4533 std::ofstream out(confPath, std::ios::trunc);
4534 if (!out.is_open()) {
4535 std::cerr << _("[SettingsPage] ❌ Impossibile scrivere ") << confPath << std::endl;
4536 return;
4537 }
4538
4539 for (const auto& row : allowStore->children()) {
4540 Glib::ustring sha = row[allowCols.col_hash];
4541 Glib::ustring rule = row[allowCols.col_rule];
4542
4543 if (sha.raw().empty())
4544 continue;
4545
4546 out << sha.raw();
4547 if (!rule.raw().empty())
4548 out << ";" << rule.raw();
4549 out << "\n";
4550 }
4551
4552 out.flush();
4553 out.close();
4554
4555 std::cout << _("[SettingsPage] ✅ Allowlist salvata in ") << confPath << std::endl;
4556}
4557
4558
4559
4560void SettingsPage::saveAdvancedConfig(bool suspicious_only,
4561 const Glib::ustring& ignore_paths,
4562 const Glib::ustring& ignore_ext,
4563 const Glib::ustring& suspicious_ext)
4564{
4565 namespace fs = std::filesystem;
4566 try {
4567 const char* real_user = getenv("SUDO_USER");
4568 if (!real_user || !*real_user)
4569 real_user = getenv("USER");
4570
4571 struct passwd* pw = getpwnam(real_user ? real_user : "root");
4572 std::string home_dir = pw ? pw->pw_dir : Glib::get_home_dir();
4573
4574 std::string conf_dir = home_dir + "/.config/BastionGuard";
4575 std::string conf_file = conf_dir + "/scanner.conf";
4576 fs::create_directories(conf_dir);
4577
4578 std::map<std::string, std::string> kv;
4579 std::vector<std::string> comments;
4580 if (fs::exists(conf_file)) {
4581 std::ifstream in(conf_file);
4582 std::string line;
4583 std::regex re("^\\s*([^#=]+?)\\s*=\\s*(.*?)\\s*$");
4584 std::smatch m;
4585 while (std::getline(in, line)) {
4586 if (line.empty() || line[0] == '#') {
4587 comments.push_back(line);
4588 continue;
4589 }
4590 if (std::regex_match(line, m, re))
4591 kv[m[1]] = m[2];
4592 }
4593 }
4594
4595 kv["suspicious_only"] = suspicious_only ? "1" : "0";
4596 kv["ignore_paths"] = ignore_paths.raw();
4597 kv["ignore_ext"] = ignore_ext.raw();
4598 kv["suspicious_ext"] = suspicious_ext.raw();
4599
4600 std::ofstream out(conf_file, std::ios::trunc);
4601 if (!out.is_open()) {
4602 std::cerr << _("[SettingsPage] ❌ Impossibile scrivere ") << conf_file << std::endl;
4603 return;
4604 }
4605
4606 out << _("# --- BastionGuard Anti-Ransomware Config ---\n");
4607 for (const auto& c : comments)
4608 if (!c.empty()) out << c << "\n";
4609
4610 for (const auto& [k, v] : kv)
4611 out << k << "=" << v << "\n";
4612
4613 out.close();
4614
4615 if (pw) chown(conf_file.c_str(), pw->pw_uid, pw->pw_gid);
4616 chmod(conf_file.c_str(), 0644);
4617
4618 std::cout << _("[SettingsPage] ✅ Config salvata in ") << conf_file << std::endl;
4619
4621
4622 Gtk::Window* parent = dynamic_cast<Gtk::Window*>(get_root());
4623 Utils::show_message(parent,
4624 _("Configurazione salvata correttamente."),
4625 Gtk::MessageType::INFO,
4626 _("Le impostazioni aggiornate sono state caricate e applicate.\n"
4627 "Non serve riavviare il programma."));
4628
4629 } catch (const std::exception& ex) {
4630 std::cerr << _("[SettingsPage] ⚠ Errore in saveAdvancedConfig: ") << ex.what() << std::endl;
4631 }
4632}
4633
4635 try {
4636 std::string conf_file = Glib::get_home_dir() + "/.config/BastionGuard/scanner.conf";
4637 if (!std::filesystem::exists(conf_file))
4638 return;
4639
4640 std::ifstream in(conf_file);
4641 if (!in.is_open()) return;
4642
4643 std::string line;
4644 std::regex re("^\\s*([^#=]+?)\\s*=\\s*(.*?)\\s*$");
4645 std::smatch m;
4646
4647 bool susp = false;
4648 Glib::ustring ign_paths, ign_ext, susp_ext;
4649
4650 while (std::getline(in, line)) {
4651 if (line.empty() || line[0] == '#') continue;
4652 if (std::regex_match(line, m, re)) {
4653 std::string key = m[1], val = m[2];
4654 if (key == "suspicious_only") susp = (val == "1" || val == "true");
4655 else if (key == "ignore_paths") ign_paths = val;
4656 else if (key == "ignore_ext") ign_ext = val;
4657 else if (key == "suspicious_ext") susp_ext = val;
4658 }
4659 }
4660
4661 if (chkSuspOnly) chkSuspOnly->set_active(susp);
4662 if (txtIgnPaths) txtIgnPaths->set_text(ign_paths);
4663 if (txtIgnExt) txtIgnExt->set_text(ign_ext);
4664 if (txtSuspExt) txtSuspExt->set_text(susp_ext);
4665
4666 std::cout << _("[SettingsPage] 🔄 Configurazione caricata da ") << conf_file << std::endl;
4667
4668 } catch (const std::exception& ex) {
4669 std::cerr << _("[SettingsPage] ⚠ Errore caricando configurazione: ") << ex.what() << std::endl;
4670 }
4671}
4672
4673bool SettingsPage::run_cmd(const std::vector<std::string>& argv,
4674 std::string* out_stdout,
4675 bool silence_stderr)
4676{
4677 try
4678 {
4679 Gio::Subprocess::Flags flags = Gio::Subprocess::Flags::NONE;
4680
4681 if (out_stdout)
4682 flags |= Gio::Subprocess::Flags::STDOUT_PIPE;
4683
4684 if (silence_stderr)
4685 flags |= Gio::Subprocess::Flags::STDERR_SILENCE;
4686 else
4687 flags |= Gio::Subprocess::Flags::STDERR_PIPE;
4688
4689 auto proc = Gio::Subprocess::create(argv, flags);
4690 proc->wait();
4691
4692 if (out_stdout)
4693 {
4694 auto [stdout_str, stderr_str] = proc->communicate_utf8("");
4695 *out_stdout = stdout_str.raw();
4696 }
4697
4698 return proc->get_successful();
4699 }
4700 catch (...)
4701 {
4702 return false;
4703 }
4704}
4705
4706
4708 const Glib::ustring& message,
4709 const std::string& service_name)
4710{
4711 auto dlg = Gtk::AlertDialog::create(message);
4712 dlg->set_detail(
4713 Glib::ustring::compose(
4714 _("Servizio: %1\n\n"
4715 "Possibili cause:\n"
4716 "• Il file .service non esiste\n"
4717 "• Il gestore servizi utente non è disponibile\n"
4718 "• Problemi di permessi o path\n"
4719 "• Errore di sintassi nel file di servizio"),
4720 service_name
4721 )
4722 );
4723 dlg->show();
4724}
4725
4727{
4728 if (!listBox) return;
4729
4730 auto children = listBox->get_children();
4731 for (auto* w : children)
4732 listBox->remove(*w);
4733
4734 const std::filesystem::path clamdb = "/var/lib/clamav";
4735 auto sigs = list_clamav_signatures(clamdb);
4736
4737 if (clamdb_count_label_) {
4738 clamdb_count_label_->set_text(
4739 Glib::ustring::compose(_("Firme trovate: %1"), sigs.size())
4740 );
4741 }
4742
4743 if (sigs.empty())
4744 {
4745 auto lbl = Gtk::make_managed<Gtk::Label>(
4746 _("Nessuna firma trovata in /var/lib/clamav")
4747 );
4748 lbl->set_xalign(0.0);
4749 lbl->add_css_class("alert-warning");
4750 listBox->append(*lbl);
4751 return;
4752 }
4753
4754 for (const auto& p : sigs)
4755 {
4756 auto lbl = Gtk::make_managed<Gtk::Label>(p.filename().string());
4757 lbl->set_xalign(0.0);
4758 lbl->add_css_class("readonly-path");
4759 listBox->append(*lbl);
4760 }
4761}
4762
4764{
4765 auto page_db = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 12);
4766 page_db->set_margin(12);
4767
4768 auto frame = Gtk::make_managed<Gtk::Frame>(_("Database firme ClamAV"));
4769 auto inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
4770 inner->set_margin(10);
4771
4772 auto lblInfo = Gtk::make_managed<Gtk::Label>(
4773 _("Questa sezione mostra le firme presenti nel database ClamAV.\n"
4774 "Le firme personalizzate installate in /var/lib/clamav verranno caricate automaticamente da clamd.")
4775 );
4776 lblInfo->set_wrap(true);
4777 lblInfo->set_halign(Gtk::Align::FILL);
4778 lblInfo->add_css_class("alert-info");
4779 inner->append(*lblInfo);
4780
4781 auto lblPathTitle = Gtk::make_managed<Gtk::Label>(_("Percorso database:"));
4782 lblPathTitle->set_halign(Gtk::Align::START);
4783 inner->append(*lblPathTitle);
4784
4785 auto lblPath = Gtk::make_managed<Gtk::Label>("/var/lib/clamav");
4786 lblPath->set_xalign(0.0);
4787 lblPath->add_css_class("readonly-path");
4788 inner->append(*lblPath);
4789
4790 auto rowTop = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
4791
4792 auto lblCount = Gtk::make_managed<Gtk::Label>();
4793 lblCount->set_halign(Gtk::Align::START);
4794 lblCount->add_css_class("status-muted");
4795 clamdb_count_label_ = lblCount;
4796
4797 auto btnRefresh = Gtk::make_managed<Gtk::Button>(_("Aggiorna"));
4798 btnRefresh->set_halign(Gtk::Align::END);
4799 btnRefresh->add_css_class("suggested-action");
4800
4801 rowTop->append(*lblCount);
4802 rowTop->append(*btnRefresh);
4803 inner->append(*rowTop);
4804
4805 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
4806 scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
4807 scrolled->set_expand(true);
4808 scrolled->set_min_content_height(220);
4809
4810 auto listBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
4811 listBox->set_margin_start(6);
4812 scrolled->set_child(*listBox);
4813
4814
4815 inner->append(*scrolled);
4816
4817 refresh_clamav_db_list(listBox);
4818 btnRefresh->signal_clicked().connect([this, listBox]() {
4819 refresh_clamav_db_list(listBox);
4820 });
4821
4822 frame->set_child(*inner);
4823 page_db->append(*frame);
4824
4825 page_order_.push_back(page_db);
4826 stack.add(*page_db, "clamav-db", _("DB ClamAV"));
4827}
4828
4830 auto cfg = load_mail_security_config();
4831
4832 auto page = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
4833 page->set_hexpand(true);
4834 page->set_vexpand(true);
4835
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);
4840
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);
4848
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);
4854
4855 auto frame = Gtk::make_managed<Gtk::Frame>();
4856 frame->set_hexpand(true);
4857
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);
4863
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);
4869
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);
4879
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);
4883
4884 if (profiles->empty()) {
4886 p.id = "";
4887 p.label = "";
4888 p.signature.logo_path = "/usr/share/BastionGuard/data/logo.png";
4889 profiles->push_back(p);
4890 }
4891
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);
4896 return lbl;
4897 };
4898
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);
4906 return row;
4907 };
4908
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);
4914
4915 auto swEnable = Gtk::make_managed<Gtk::Switch>();
4916 swEnable->set_active(cfg.enabled);
4917
4918 auto swScan = Gtk::make_managed<Gtk::Switch>();
4919 swScan->set_active(cfg.scan_outgoing);
4920
4921 auto swSig = Gtk::make_managed<Gtk::Switch>();
4922 swSig->set_active(cfg.inject_signature);
4923
4924 auto swAdvertiseStartTls = Gtk::make_managed<Gtk::Switch>();
4925 swAdvertiseStartTls->set_active(cfg.advertise_starttls);
4926
4927 auto swImplicitTlsListener = Gtk::make_managed<Gtk::Switch>();
4928 swImplicitTlsListener->set_active(cfg.enable_implicit_tls_listener);
4929
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));
4935
4936
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);
4942
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>();
4948
4949 auto sig_grid = Gtk::make_managed<Gtk::Grid>();
4950 sig_grid->set_row_spacing(10);
4951 sig_grid->set_column_spacing(12);
4952
4953 int sr = 0;
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);
4957 sr++;
4958 };
4959
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);
4965
4966 auto fixedLogoInfo = Gtk::make_managed<Gtk::Label>(
4967 _("Logo fisso utilizzato: /usr/share/BastionGuard/data/logo.png")
4968 );
4969 fixedLogoInfo->set_halign(Gtk::Align::START);
4970 fixedLogoInfo->set_xalign(0.0f);
4971
4972 tab_signature->append(*sig_grid);
4973 tab_signature->append(*fixedLogoInfo);
4974
4975 auto load_signature_fields = [=]() {
4976 if (profiles->empty()) return;
4977 *ui_updating = true;
4978
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);
4985
4986 *ui_updating = false;
4987 };
4988
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";
4998 };
4999
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);
5005
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.")
5009 );
5010 tbInstallInfo->set_wrap(true);
5011 tbInstallInfo->set_halign(Gtk::Align::START);
5012 tbInstallInfo->set_xalign(0.0f);
5013 tab_thunderbird->append(*tbInstallInfo);
5014
5015 auto tbInstallGrid = Gtk::make_managed<Gtk::Grid>();
5016 tbInstallGrid->set_row_spacing(10);
5017 tbInstallGrid->set_column_spacing(12);
5018
5019 int tbr = 0;
5020
5021 auto lblAdminPw = Gtk::make_managed<Gtk::Label>(_("Password amministratore"));
5022 lblAdminPw->set_halign(Gtk::Align::START);
5023 lblAdminPw->set_xalign(0.0f);
5024
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"));
5029
5030 tbInstallGrid->attach(*lblAdminPw, 0, tbr, 1, 1);
5031 tbInstallGrid->attach(*eAdminPw, 1, tbr, 1, 1);
5032 tbr++;
5033
5034 tab_thunderbird->append(*tbInstallGrid);
5035
5036 auto tbInstallButtons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
5037
5038 auto btnInstallTbExt = Gtk::make_managed<Gtk::Button>(_("Installa estensione Thunderbird"));
5039 btnInstallTbExt->add_css_class("suggested-action");
5040
5041 auto btnClearTbLog = Gtk::make_managed<Gtk::Button>(_("Pulisci log"));
5042
5043 tbInstallButtons->append(*btnInstallTbExt);
5044 tbInstallButtons->append(*btnClearTbLog);
5045 tab_thunderbird->append(*tbInstallButtons);
5046
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);
5053
5054 auto tbInstallBuffer = Gtk::TextBuffer::create();
5055 tbInstallOutput->set_buffer(tbInstallBuffer);
5056
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);
5062
5063 btnClearTbLog->signal_clicked().connect([=]() {
5064 tbInstallBuffer->set_text("");
5065 });
5066
5067 auto run_tb_installer = [=]() {
5068 const std::string script_path =
5069 "/usr/share/BastionGuard/data/extension/bastionguard-tb-extension/install-tb-extension.sh";
5070
5071 tbInstallBuffer->set_text("");
5072
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"
5076 );
5077 return;
5078 }
5079
5080 const std::string password = std::string(eAdminPw->get_text());
5081
5082 // Password goes only to the child's environment, never to a shell or argv.
5083 // The installer consumes it via sudo -S, and the parent does not log it.
5084 const auto install = bg_process::run({script_path}, true,
5085 8U * 1024U * 1024U, {{"BASTIONGUARD_SUDO_PASSWORD", password}});
5086 if (!install.started) {
5087 tbInstallBuffer->set_text(_("Impossibile avviare lo script di installazione.\n"));
5088 return;
5089 }
5090 const std::string& output = install.output;
5091 const int exit_code = install.exit_code;
5092
5093 if (exit_code == 0) {
5094 tbInstallBuffer->insert(
5095 tbInstallBuffer->end(),
5096 _("\n✔ Installazione Thunderbird completata.\n")
5097 );
5098 } else {
5099 tbInstallBuffer->insert(
5100 tbInstallBuffer->end(),
5101 Glib::ustring::compose(_("\n❌ Installazione fallita (exit=%1)\n"), exit_code)
5102 );
5103 }
5104 };
5105
5106 btnInstallTbExt->signal_clicked().connect([=]() {
5107 run_tb_installer();
5108 });
5109
5110
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);
5116
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.")
5120 );
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);
5125
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);
5130
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);
5136
5137 auto tb_found = std::make_shared<std::vector<MailRelayProfile>>();
5138 auto tb_selected = std::make_shared<std::vector<Gtk::CheckButton*>>();
5139
5140 using PwEntriesPtr = std::shared_ptr<std::vector<Gtk::Entry*>>;
5141 auto tb_pw_entries_ref = std::make_shared<PwEntriesPtr>();
5142
5143 using HostEntriesPtr = std::shared_ptr<std::vector<Gtk::Entry*>>;
5144 auto tb_host_entries_ref = std::make_shared<HostEntriesPtr>();
5145
5146 using PortSpinsPtr = std::shared_ptr<std::vector<Gtk::SpinButton*>>;
5147 auto tb_port_spins_ref = std::make_shared<PortSpinsPtr>();
5148
5149 auto populate_profile_list = [=](const std::vector<MailRelayProfile>& source_profiles,
5150 bool from_json) {
5151 for (auto* w : tb_list_box->get_children())
5152 tb_list_box->remove(*w);
5153
5154 tb_found->clear();
5155 tb_selected->clear();
5156
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*>>();
5160
5161 for (size_t i = 0; i < source_profiles.size(); ++i) {
5162 const auto& p = source_profiles[i];
5163 tb_found->push_back(p);
5164
5165 auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
5166 row->set_margin_bottom(10);
5167
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);
5174 }
5175
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);
5179 if (from_json)
5180 cb->set_sensitive(false);
5181 row->append(*cb);
5182
5183 std::string emails;
5184 for (size_t j = 0; j < p.match_from.size(); ++j) {
5185 if (j) emails += ", ";
5186 emails += p.match_from[j];
5187 }
5188
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);
5196 }
5197
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" ||
5203 p.smtp_host.empty()
5204 );
5205
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);
5213 }
5214
5215 auto host_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
5216 host_row->set_margin_start(24);
5217
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);
5221
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);
5226
5227 if (host_is_local && !from_json)
5228 host_entry->add_css_class("entry-error");
5229
5230 if (from_json)
5231 host_entry->set_sensitive(!p.smtp_host.empty());
5232
5233 host_row->append(*lbl_host);
5234 host_row->append(*host_entry);
5235
5236 auto lbl_port = Gtk::make_managed<Gtk::Label>(_("Porta:"));
5237 lbl_port->set_halign(Gtk::Align::START);
5238
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);
5244
5245 if (from_json)
5246 port_spin->set_sensitive(false);
5247
5248 host_row->append(*lbl_port);
5249 host_row->append(*port_spin);
5250 row->append(*host_row);
5251
5252 auto pw_row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
5253 pw_row->set_margin_start(24);
5254
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);
5258
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")
5263 );
5264 pw_entry->set_hexpand(true);
5265
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);
5271 break;
5272 }
5273 }
5274
5275 pw_row->append(*lbl_pw_label);
5276 pw_row->append(*pw_entry);
5277 row->append(*pw_row);
5278
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);
5284 }
5285
5286 *tb_pw_entries_ref = tb_pw_entries;
5287 *tb_host_entries_ref = tb_host_entries;
5288 *tb_port_spins_ref = tb_port_spins;
5289 };
5290
5291 {
5292 auto existing_cfg = load_mail_security_config();
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);
5297 }
5298 }
5299
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);
5303
5304 btn_scan_tb->signal_clicked().connect([=]() {
5305 std::string err;
5306 auto scanned = tb_import_profiles(err);
5307
5308 if (scanned.empty()) {
5309 tb_status->set_text("❌ " + (err.empty()
5310 ? std::string(_("Nessun profilo trovato"))
5311 : err));
5312 return;
5313 }
5314
5315 tb_status->set_text(Glib::ustring::compose(
5316 _("✔ Trovati %1 profili SMTP da Thunderbird"), (int)scanned.size()));
5317
5318 populate_profile_list(scanned, false);
5319 });
5320
5321 auto btn_import_tb = Gtk::make_managed<Gtk::Button>(_("⬇ Importa profili selezionati"));
5322 tab_profiles->append(*btn_import_tb);
5323
5324 btn_import_tb->signal_clicked().connect([=]() {
5325 if (tb_found->empty()) {
5326 tb_status->set_text(_("❌ Nessun profilo da importare. Scansiona prima."));
5327 return;
5328 }
5329
5330 int imported = 0;
5331 auto& pw_entries = *tb_pw_entries_ref;
5332
5333 for (size_t i = 0; i < tb_found->size() && i < tb_selected->size(); ++i) {
5334 if (!(*tb_selected)[i]->get_active())
5335 continue;
5336
5337 MailRelayProfile src = (*tb_found)[i];
5338
5339 if (pw_entries && i < pw_entries->size()) {
5340 std::string pw = std::string((*pw_entries)[i]->get_text());
5341 if (!pw.empty())
5342 src.password = pw;
5343 }
5344
5345 auto& host_entries = *tb_host_entries_ref;
5346 auto& port_spins = *tb_port_spins_ref;
5347
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) {
5352 h.clear();
5353 } else {
5354 auto end = h.find_last_not_of(" \t");
5355 h = h.substr(start, end - start + 1);
5356 }
5357 if (!h.empty())
5358 src.smtp_host = h;
5359 }
5360
5361 if (port_spins && i < port_spins->size()) {
5362 int pt = (*port_spins)[i]->get_value_as_int();
5363 if (pt > 0 && pt < 65536)
5364 src.smtp_port = pt;
5365 }
5366
5367 bool found = false;
5368 for (auto& existing : *profiles) {
5369 if (existing.id == src.id || existing.smtp_host == src.smtp_host) {
5370 existing = src;
5371 found = true;
5372 break;
5373 }
5374 }
5375
5376 if (!found) {
5377 profiles->push_back(src);
5378 if (profiles->size() == 1)
5379 *selected_profile = 0;
5380 }
5381
5382 ++imported;
5383 }
5384
5385 if (imported > 0) {
5387 auto_save.profiles = *profiles;
5388
5389 if (!auto_save.default_profile_id.empty()) {
5390 bool found = false;
5391 for (const auto& p : auto_save.profiles) {
5392 if (p.id == auto_save.default_profile_id) {
5393 found = true;
5394 break;
5395 }
5396 }
5397 if (!found && !auto_save.profiles.empty())
5398 auto_save.default_profile_id = auto_save.profiles[0].id;
5399 } else if (!auto_save.profiles.empty()) {
5400 auto_save.default_profile_id = auto_save.profiles[0].id;
5401 }
5402
5403 if (save_mail_security_config(auto_save)) {
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."),
5407 (int)auto_save.profiles.size()));
5408 } else {
5409 tb_status->set_text(Glib::ustring::compose(
5410 _("✔ Importati %1 profili — salvataggio fallito, usa 'Salva' manualmente."),
5411 imported));
5412 }
5413
5414 load_signature_fields();
5415
5416 auto refreshed = load_mail_security_config();
5417 populate_profile_list(
5418 refreshed.profiles.empty() ? *profiles : refreshed.profiles,
5419 true);
5420 } else {
5421 tb_status->set_text(_("Nessun profilo selezionato."));
5422 }
5423 });
5424
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"));
5429
5430 inner->append(*tabs_switcher);
5431 inner->append(*tabs);
5432
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);
5438
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);
5443
5444 btnSave->signal_clicked().connect([=]() {
5445 if (*ui_updating)
5446 return;
5447
5448 save_signature_fields();
5449
5451 out.version = 2;
5452 out.enabled = swEnable->get_active();
5453 out.scan_outgoing = swScan->get_active();
5454 out.inject_signature = swSig->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;
5458 out.local_submission_port = cfg.local_submission_port > 0 ? cfg.local_submission_port : 2587;
5459 out.advertise_starttls = swAdvertiseStartTls->get_active();
5460 out.enable_implicit_tls_listener = swImplicitTlsListener->get_active();
5461
5462 MailSignatureConfig sig_ui;
5463 sig_ui.display_name = sName->get_text();
5464 sig_ui.job_title = sRole->get_text();
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";
5469
5470 if (out.inject_signature) {
5471 if (sig_ui.display_name.empty() &&
5472 sig_ui.job_title.empty() &&
5473 sig_ui.company.empty() &&
5474 sig_ui.phone.empty() &&
5475 sig_ui.website.empty()) {
5476 auto dlg = Gtk::AlertDialog::create(_("Firma vuota"));
5477 dlg->set_detail(_("La firma automatica è abilitata ma tutti i campi firma sono vuoti."));
5478 dlg->show();
5479 return;
5480 }
5481 }
5482
5483 out.profiles = *profiles;
5484
5485 if (out.profiles.empty()) {
5487 p.id = "bastionguard";
5488 p.label = "BastionGuard";
5489 p.signature = sig_ui;
5490 out.profiles.push_back(p);
5491 out.default_profile_id = "bastionguard";
5492 } else {
5493 for (auto& p : out.profiles)
5494 p.signature = sig_ui;
5495
5496 std::vector<std::string> loop_profiles;
5497 for (auto& p : out.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);
5501 p.smtp_host = "";
5502 }
5503 }
5504
5505 if (!loop_profiles.empty()) {
5506 std::string names;
5507 for (const auto& n : loop_profiles)
5508 names += "• " + n + "\n";
5509
5510 auto dlg = Gtk::AlertDialog::create(_("Profili SMTP non configurati"));
5511 dlg->set_detail(
5512 _("I seguenti profili avevano smtp_host = 127.0.0.1 e non possono essere usati come relay:\n") +
5513 names +
5514 _("\nSono stati salvati senza server SMTP. Aggiorna i campi 'Server SMTP' nel tab Profili SMTP e reimporta.")
5515 );
5516 dlg->show();
5517 }
5518
5519 bool def_found = false;
5520 for (const auto& p : out.profiles) {
5521 if (p.id == out.default_profile_id) {
5522 def_found = true;
5523 break;
5524 }
5525 }
5526 if (!def_found)
5527 out.default_profile_id = out.profiles[0].id;
5528 }
5529
5530 if (!save_mail_security_config(out)) {
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"));
5534 dlg->show();
5535 return;
5536 }
5537
5538 (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--user", "restart", "BastionGuard-mailproxy.service"}).exit_code);
5539
5540 status->set_text(_("✔ Configurazione salvata in ~/.config/BastionGuard/mail.json"));
5541
5542 populate_profile_list(out.profiles, true);
5543 tb_status->set_text(Glib::ustring::compose(
5544 _("✔ %1 profili attivi"), (int)out.profiles.size()));
5545 });
5546
5547 load_signature_fields();
5548
5549 frame->set_child(*inner);
5550 center->append(*frame);
5551 outer->append(*center);
5552 scrolled->set_child(*outer);
5553 page->append(*scrolled);
5554
5555 page_order_.push_back(page);
5556 stack.add(*page, "email", _("Email"));
5557}
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
static std::string normalize_domain_or_url(std::string in)
static void split_semicolon_list(const std::string &val, std::vector< std::string > &out)
FirewallType
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57
void setGoogleSafeEnabled(bool enabled)
Definition Backend.hpp:86
bool testGoogleSafeKey()
Definition Backend.cpp:1680
bool updatePhishLists()
Definition Backend.cpp:1086
void stop_antiphish()
Definition Backend.cpp:1530
bool enablePhishAutoUpdate(bool enable)
Definition Backend.cpp:1732
void stop_antiransom()
Definition Backend.cpp:847
void start_antiransom()
Definition Backend.cpp:837
void setFirewallEnabled(bool enabled)
Definition Backend.cpp:3126
static void setQuarantinePath(const std::string &path)
Definition Backend.cpp:490
bool updateYaraRules(std::function< void(int, int, const std::string &)> progressCallback=nullptr)
Definition Backend.cpp:857
static Backend & instance()
Definition Backend.cpp:830
bool updateSanesecurityDB(std::function< void(int, int, const std::string &)> progressCallback=nullptr)
Definition Backend.cpp:1542
void start_antiphish()
Definition Backend.cpp:1520
void setGoogleSafeApiKey(const std::string &key)
Definition Backend.hpp:87
void saveGoogleSafeConfig()
Definition Backend.cpp:1648
static bool installClamdIfMissing()
Definition Backend.cpp:664
std::vector< std::string > getOnAccessPaths() const
void setScanOption(const std::string &key, bool enabled)
std::map< std::string, bool > getScanOptions() const
void removeOnAccessPath(const std::string &path)
void addOnAccessPath(const std::string &path)
bool save(const std::string &path)
Gtk::TreeModelColumn< Glib::ustring > source
Gtk::TreeModelColumn< Glib::ustring > name
Gtk::ListBox listbox_paths
Gtk::Label antiransomStatus
Glib::RefPtr< Gtk::ListStore > allowStore
Gtk::Entry * txtSuspExt
void saveScannerConfig(bool use_rules, bool use_db, int interval, const std::vector< std::string > &dirs)
void onQuarantineClicked()
void saveAdvancedConfig(bool suspicious_only, const Glib::ustring &ignore_paths, const Glib::ustring &ignore_ext, const Glib::ustring &suspicious_ext)
void refreshAntiransomRules()
Gtk::StackSwitcher stack_switcher
Gtk::Button btn_apply_language_
Gtk::Switch antiphishSwitch
Gtk::Switch clamonaccSwitch
Gtk::Button btn_removePath
Gtk::Label toggleAllLabel
bool enable_system_service(const std::string &unit_name)
void refresh_clamav_db_list(Gtk::Box *listBox)
BastionGuard::AliveFlag alive_
Gtk::Switch antiransomSwitch
Gtk::Button quarantineButton
void loadAdvancedConfig()
AllowlistCols allowCols
Gtk::Switch googleSafeSwitch
RuleColumns ruleCols
Gtk::ComboBoxText combo_os_
static void show_service_error_dialog(const Glib::ustring &message, const std::string &service_name)
void build_services_section(Gtk::Box &parent, const std::vector< std::tuple< std::string, Glib::ustring, Glib::ustring > > &services, bool system_units)
~SettingsPage() override
Gtk::TreeView rulesView
Gtk::Entry entry_http_port_
void build_clamav_db_tab()
Gtk::TreeView sambaView
void onRealtimeToggled()
Gtk::ComboBoxText combo_language_
void loadScannerConfig(bool &use_rules, bool &use_db, int &interval, std::vector< std::string > &dirs)
void build_secure_payments_tab()
void build_system_services_tab()
Gtk::TreeView phishView
Gtk::Button btn_addPath
void onAntiphishToggled()
Gtk::Entry googleSafeKeyEntry
void refreshPhishLists()
Gtk::Label * clamdb_count_label_
bool run_cmd(const std::vector< std::string > &argv, std::string *out_stdout=nullptr, bool silence_stderr=true)
void onAntiransomToggled()
std::map< std::string, bool > toggleControlled
bool enable_user_service(const std::string &service_name)
bool is_user_service_enabled(const std::string &service_name)
void build_email_security_tab()
Gtk::Stack stack
Gtk::Button btn_save
Gtk::Label realtimeLabel
void build_antiransom_tab()
bool is_system_service_enabled(const std::string &unit_name)
Gtk::Switch toggleAllSwitch
Gtk::Entry * txtCompany
Gtk::Label antiphishStatus
ClamdConfig & clamdConfig
PhishColumns phishCols
Glib::RefPtr< Gtk::ListStore > phishStore
void build_proxy_bypass_tab()
Gtk::Entry entry_https_port_
Gtk::SpinButton spin_interval
SambaColumns sambaCols
std::map< std::string, Gtk::CheckButton * > checkOptions
void build_antiphish_tab()
Gtk::Entry * txtIgnExt
Gtk::TreeView allowView
void build_whitelist_tab()
void build_options_tab()
std::vector< Gtk::Widget * > page_order_
void build_antiransom_advanced_tab()
Gtk::Entry * txtIgnPaths
void build_user_services_tab()
bool disable_user_service(const std::string &service_name)
bool pkexec_servicectl(const std::vector< std::string > &args, std::string *out_stdout=nullptr)
Glib::RefPtr< Gtk::ListStore > sambaStore
bool disable_system_service(const std::string &unit_name)
Gtk::Button btn_apply_webconfig_
Gtk::CheckButton * chkSuspOnly
Gtk::Label quarantineLabel
Glib::RefPtr< Gtk::ListStore > rulesStore
static void set_secure_payments_enabled(bool enabled)
static bool get_secure_payments_enabled()
static void show_message(Gtk::Window *parent, const std::string &message, Gtk::MessageType type=Gtk::MessageType::INFO, const std::string &secondary=std::string())
Definition Utils.cpp:33
void if_alive(const AliveFlag &alive, Fn &&fn)
static bool run_cmd(const std::vector< std::string > &argv, std::string *out_stdout=nullptr, bool silence_stderr=true)
static bool pkexec_servicectl(const std::vector< std::string > &args, std::string *out_stdout=nullptr)
bool isServiceActive(const std::string &service, bool user=false)
bool atomic_install_with_pkexec(const std::string &tmp_src, const std::string &target)
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={})
std::string resource(const std::string &rel)
Definition Resource.hpp:31
#define DATA_DIR
Definition Resource.hpp:28
static bool update_payments_enabled_only(bool enabled_flag)
static std::string detect_webserver()
static bool save_mail_security_config(const MailSecurityConfig &cfg)
static std::string payments_json_path_user()
static std::map< std::string, bool > toggleControlled
static MailSecurityConfig load_mail_security_config()
static std::vector< std::string > load_payments_domains()
std::pair< int, int > read_webports_conf()
static void ensure_payments_json_exists()
BypassKind
static bool write_temp_script_and_elevate(const std::string &script_contents, std::string *out_log=nullptr, int *exit_code=nullptr)
static std::string normalize_locale(std::string s)
bool update_webserver_ports(const std::string &websrv, int http_port, int https_port)
std::pair< int, int > read_current_ports(const std::string &)
static BypassKind classify_bypass_entry(const std::string &s_in)
static bool is_clamav_signature_file(const std::filesystem::path &p)
static bool run_privhelper_and_capture(const std::vector< std::string > &args, std::string &stdout_out, std::string &stderr_out, int &exit_status)
static std::map< std::string, std::map< std::string, std::string > > tb_parse_ini_file(const std::string &path)
static bool save_proxy_bypass_domains(const std::vector< std::string > &domains)
static void append_to_textbuffer(const Glib::RefPtr< Gtk::TextBuffer > &buffer, const Glib::ustring &text)
static std::string join_semicolon_list(const std::vector< std::string > &items)
static std::string build_email_signature_preview_html(const MailSignatureConfig &sig)
std::string detect_distro()
static std::string iso_today_yyyy_mm_dd()
static bool updatingToggle
bool isClamonaccRunning()
static void ensure_mail_json_exists()
bool update_ports_in_file(const std::string &file, int http_port, int https_port)
void show_service_error_dialog(const Glib::ustring &msg, const std::string &service)
static std::string make_profile_id(const std::string &label)
static std::string normalize_domain_or_url(const std::string &in_raw)
static Gtk::Window * create_log_popup(Gtk::Widget *owner, const Glib::RefPtr< Gtk::TextBuffer > &buffer)
static std::vector< std::string > load_proxy_bypass_domains()
static bool save_payments_domains(const std::vector< std::string > &domains_in, bool enabled_flag)
static std::string mail_json_path_user()
static std::vector< MailRelayProfile > tb_import_profiles(std::string &err_out)
static bool write_mail_config_temp_json(const MailSecurityConfig &cfg, std::string &out_path)
static bool tb_parse_pref_line(const std::string &line, std::string &key, std::string &value)
static std::string elev_cmd(const std::string &inner)
static std::vector< std::filesystem::path > list_clamav_signatures(const std::filesystem::path &dir)
bool restart_service(const std::string &svc)
bool copy_with_pkexec(const std::string &src, const std::string &dst)
static std::string tb_find_default_profile()
std::vector< std::string > match_from_domain
MailSignatureConfig signature
std::string username
std::vector< std::string > match_from
std::string smtp_host
std::string password
std::string default_profile_id
std::vector< MailRelayProfile > profiles
std::string display_name
Gtk::TreeModelColumn< Glib::ustring > col_hash
Gtk::TreeModelColumn< Glib::ustring > col_rule
Gtk::TreeModelColumn< Glib::ustring > col_path
std::string smtp_server
std::string email
std::string username
std::string id
std::string description
std::string hostname