Skip to content
API Documentation
BastionGuard™
Backend.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 "Backend.hpp"
23#include "SafeProcess.hpp"
24#include "DistroUtils.hpp"
25#include "Utils.hpp"
26#include "Quarantine.hpp"
30#include <gtkmm.h>
31#include <thread>
32#include <chrono>
33#include <cstdlib>
34#include <cstdio>
35#include <array>
36#include <vector>
37#include <memory>
38#include <stdexcept>
39#include <fstream>
40#include <unistd.h>
41#include <cerrno>
42#include <sys/stat.h>
43#include <sys/wait.h>
44#include <algorithm>
45#include <unordered_set>
46#include <regex>
47#include <filesystem>
48#include <iostream>
49#include <glib/gi18n.h>
50#include <cstring>
51#include <sstream>
52#include <pwd.h>
53#include <random>
54#include <curl/curl.h>
55#include <nlohmann/json.hpp>
56#include <giomm/dbusconnection.h>
57#include <giomm/dbusinterface.h>
58#include <giomm/dbusownname.h>
59#include <giomm/file.h>
60#include <giomm/fileenumerator.h>
61#include <giomm/fileinfo.h>
62#include <glibmm/i18n.h>
63#include <iomanip>
64#include <ctime>
65#include <glibmm/miscutils.h>
66#include <libsmbclient.h>
67
68namespace fs = std::filesystem;
69
71#ifdef BASTIONGUARD_VERSION
72 return BASTIONGUARD_VERSION;
73#else
74 return "0.0.0";
75#endif
76}
77
79#ifdef BASTIONGUARD_BUILD
80 return BASTIONGUARD_BUILD;
81#else
82 return 0;
83#endif
84}
85
86static std::string b64_encode(const std::string& s) {
87 return Glib::Base64::encode(s);
88}
89
90static std::string b64_decode(const std::string& s) {
91 return Glib::Base64::decode(s);
92}
93
94static const std::string SMB_KEY = "BastionGuard0101SecretKey";
95
96static std::string xor_crypt(const std::string& input) {
97 std::string out = input;
98 for (size_t i = 0; i < input.size(); ++i)
99 out[i] = input[i] ^ SMB_KEY[i % SMB_KEY.size()];
100 return out;
101}
102
103static std::string encrypt_pass(const std::string& pass) {
104 return b64_encode(xor_crypt(pass));
105}
106
107static std::string decrypt_pass(const std::string& enc) {
108 return xor_crypt(b64_decode(enc));
109}
110
111
112static inline std::string trim(const std::string& s)
113{
114 size_t start = s.find_first_not_of(" \t\r\n");
115 if (start == std::string::npos) return "";
116
117 size_t end = s.find_last_not_of(" \t\r\n");
118 return s.substr(start, end - start + 1);
119}
120
121
122static std::string extract_path_from_clamav_line(const std::string& line)
123{
124 auto pos = line.find(':');
125 if (pos == std::string::npos)
126 return line;
127
128 return line.substr(0, pos);
129}
130
131
132static std::vector<std::string> clamDbDirs()
133{
134 std::vector<std::string> dirs = {
135 "/var/lib/clamav",
136 Glib::get_home_dir() + "/.local/share/clamav",
137 "/usr/local/share/clamav"
138 };
139 return dirs;
140}
141
142struct DbInfo {
143 std::string file;
144 long long version = -1;
145 long long epoch = -1;
146};
147
148
149static std::time_t filetime_to_time_t(fs::file_time_type ft)
150{
151 using namespace std::chrono;
152 auto sctp = time_point_cast<system_clock::duration>(
153 ft - fs::file_time_type::clock::now() + system_clock::now()
154 );
155 return system_clock::to_time_t(sctp);
156}
157
158
159static bool read_cvd_header(const std::string& path, DbInfo& out)
160{
161 std::ifstream f(path, std::ios::binary);
162 if (!f.is_open()) return false;
163
164 std::string line;
165 if (!std::getline(f, line)) return false;
166
167
168 std::vector<std::string> toks;
169 {
170 std::stringstream ss(line);
171 std::string seg;
172 while (std::getline(ss, seg, ':')) {
173 auto l = seg.find_first_not_of(" \t\r\n");
174 auto r = seg.find_last_not_of(" \t\r\n");
175 if (l == std::string::npos) toks.push_back("");
176 else toks.push_back(seg.substr(l, r - l + 1));
177 }
178 }
179
180 int nameIdx = -1;
181 for (int i = 0; i < (int)toks.size(); ++i) {
182 if (toks[i].find(".cvd") != std::string::npos ||
183 toks[i].find(".cld") != std::string::npos) {
184 nameIdx = i;
185 break;
186 }
187 }
188 if (nameIdx < 0) return false;
189
190 out.file = fs::path(path).filename().string();
191
192 // Versione = primo intero dopo nome file
193 for (int i = nameIdx + 1; i < (int)toks.size(); ++i) {
194 try {
195 long long v = std::stoll(toks[i]);
196 if (v > 0) { out.version = v; break; }
197 } catch (...) {}
198 }
199
200 for (int i = nameIdx + 1; i < (int)toks.size(); ++i) {
201 const std::string& t = toks[i];
202 if (std::all_of(t.begin(), t.end(), ::isdigit)) {
203 if (t.size() >= 10 && t.size() <= 13) {
204 try {
205 long long e = std::stoll(t);
206 if (t.size() > 10) e /= 1000;
207 if (e > 631152000LL && e < 4102444800LL) {
208 out.epoch = e;
209 break;
210 }
211 } catch (...) {}
212 }
213 }
214 }
215
216
217 if (out.epoch < 0) {
218 try {
219 auto ft = fs::last_write_time(path);
220 out.epoch = (long long)filetime_to_time_t(ft);
221 } catch (...) {}
222 }
223
224 return (out.version > 0);
225}
226
227static std::string first_existing(const std::vector<std::string>& names)
228{
229 for (const auto& dir : clamDbDirs()) {
230 for (const auto& n : names) {
231 std::string p = dir + "/" + n;
232 if (fs::exists(p)) return p;
233 }
234 }
235 return {};
236}
237
238
239
240
242{
243
244 int ret = (bg_process::run({"pgrep", "-f", "clamdscan"}).exit_code);
245 return (ret == 0);
246}
247
248std::string Backend::getSambaCredsPath() const {
249 const char* home = getenv("HOME");
250 if (!home)
251 return "/tmp/BastionGuard/cred.conf";
252
253 std::string dir = std::string(home) + "/.config/BastionGuard";
254 std::filesystem::create_directories(dir);
255
256 return dir + "/cred.conf";
257}
258
259
261 std::ifstream f(getSambaConfigPath());
262 if (!f.is_open()) return false;
263
264 std::string line;
265 while (std::getline(f, line)) {
266 if (line.find("enabled=true") != std::string::npos)
267 return true;
268 }
269 return false;
270}
271
272void Backend::setSambaEnabled(bool enabled) {
273 std::ofstream f(getSambaConfigPath(), std::ios::trunc);
274 if (!f.is_open()) {
275 std::cerr << "⚠️ " << _("Impossibile scrivere la configurazione Samba") << "\n";
276 return;
277 }
278
279 f << "enabled=" << (enabled ? "true" : "false") << "\n";
280 f.close();
281
282 std::cout << "ℹ️ " << _("Stato Samba salvato: ")
283 << (enabled ? _("abilitato") : _("disabilitato")) << "\n";
284}
285
286
287
288std::pair<int,int> read_ports_from_conf()
289{
290 constexpr int default_http_port = 81;
291 constexpr int default_https_port = 444;
292
293 std::ifstream f("/etc/BastionGuard/webports.conf");
294 if (!f.is_open())
295 return {default_http_port, default_https_port};
296
297 int http_port = default_http_port;
298 int https_port = default_https_port;
299 if ((f >> http_port >> https_port) &&
300 http_port > 0 && http_port < 65536 &&
301 https_port > 0 && https_port < 65536) {
302 return {http_port, https_port};
303 }
304
305 return {default_http_port, default_https_port};
306}
307
308static void restart_clamd() {
309 if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "clamav-daemon.service"}).exit_code) == 0) {
310 (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "clamav-daemon.service"}).exit_code);
311 std::cout << _("✔ Riavviato clamav-daemon\n");
312 } else if ((bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "clamd.service"}).exit_code) == 0) {
313 (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "restart", "clamd.service"}).exit_code);
314 std::cout << _("✔ Riavviato clamd\n");
315 } else {
316 std::cerr << _("⚠ Nessun servizio clamd/clamav-daemon trovato\n");
317 }
318}
319
320
321static bool install_to_system_data(const std::string& src, const std::string& dest) {
322 try {
323 // One fixed privileged executable, with src/dest as literal argv entries.
324 const auto installation = bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644", src, dest});
325 if (!installation.ok()) {
326 std::cerr << _("⚠ install_to_system_data: pkexec failed (code ")
327 << installation.exit_code << ")\n";
328 return false;
329 }
331 return true;
332 } catch (const std::exception& e) {
333 std::cerr << _("❌ install_to_system_data exception: ") << e.what() << "\n";
334 return false;
335 }
336}
337
338
339static std::string quarantinePath = Glib::get_home_dir() + "/.local/share/BastionGuard/quarantine";
340
341static std::string getUserConfigDir() {
342 const char* xdg = std::getenv("XDG_CONFIG_HOME");
343 if (xdg && std::strlen(xdg) > 0) {
344 return std::string(xdg);
345 }
346 const char* home = std::getenv("HOME");
347 if (home) return std::string(home) + "/.config";
348 return std::string("/tmp");
349}
350
351static std::string getConfigFilePath() {
352 return getUserConfigDir() + "/BastionGuard/config.ini";
353}
354
355static void persistQuarantinePath(const std::string& path) {
356 try {
357 std::string cfgDir = getUserConfigDir() + "/BastionGuard";
358 std::filesystem::create_directories(cfgDir);
359
360 std::string cfgPath = cfgDir + "/config.ini";
361 std::ofstream f(cfgPath, std::ios::trunc);
362 if (!f.is_open()) {
363 std::cerr << "⚠️ " << _("Impossibile aprire il file di configurazione per scrittura: ")
364 << cfgPath << std::endl;
365 return;
366 }
367
368 f << "quarantinePath=" << path << "\n";
369 f.close();
370
371 std::cout << "💾 " << _("Percorso quarantena salvato in configurazione: ") << path << std::endl;
372 } catch (const std::exception& e) {
373 std::cerr << "❌ " << _("Errore durante la scrittura della configurazione: ") << e.what() << std::endl;
374 }
375}
376
377
379 std::string cfg = getConfigFilePath();
380 if (!fs::exists(cfg)) return;
381
382 std::ifstream f(cfg);
383 if (!f.is_open()) return;
384
385 std::string line;
386 while (std::getline(f, line)) {
387 auto pos = line.find('=');
388 if (pos == std::string::npos) continue;
389 std::string key = line.substr(0, pos);
390 std::string val = line.substr(pos + 1);
391 if (key == "quarantinePath" && !val.empty()) {
392 quarantinePath = val;
393 break;
394 }
395 }
396 f.close();
397}
398
399static void ensureQuarantinePath() {
400 try {
401 fs::create_directories(quarantinePath);
402 } catch (const std::exception& e) {
403 std::cerr << _("❌ Errore creazione cartella quarantena: ") << e.what() << std::endl;
404 }
405}
406
407static const char* CLAMONACC_SERVICES[] = {
408 "clamav-clamonacc.service",
409 "clamonacc.service"
410};
411
412
413static std::string getRuntimeDataDir() {
414 const char* xdg_data = std::getenv("XDG_DATA_HOME");
415 if (xdg_data && std::strlen(xdg_data) > 0) {
416 return std::string(xdg_data) + "/BastionGuard";
417 }
418
419 const char* home = std::getenv("HOME");
420 if (home)
421 return std::string(home) + "/.local/share/BastionGuard";
422
423 return "/tmp/BastionGuard";
424}
425
426
427
428
429
430
431static bool copy_to_final(const std::string& src, const std::string& dest) {
432 try {
433 fs::create_directories(fs::path(dest).parent_path());
434 fs::copy_file(src, dest, fs::copy_options::overwrite_existing);
435 return true;
436 } catch (const std::exception& e) {
437 std::cerr << _("❌ Errore copia ") << src << " -> " << dest << ": " << e.what() << std::endl;
438 return false;
439 }
440}
441
442
443static std::string findClamonaccService() {
444 for (auto service : CLAMONACC_SERVICES) {
445 if (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
446 "--system", "is-active", service}).ok()) {
447 return service;
448 }
449 }
450 return CLAMONACC_SERVICES[0];
451}
452
453
454
456 auto service = findClamonaccService();
457 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
458 "--system", "enable", "--now", service}).ok();
459}
460
462 auto service = findClamonaccService();
463 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
464 "--system", "disable", "--now", service}).ok();
465}
466
468 for (auto service : CLAMONACC_SERVICES) {
469 const auto check = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
470 "--system", "is-active", service});
471 if (!check.started) continue;
472 std::string result = check.output;
473
474 // Normalizza
475 result.erase(std::remove(result.begin(), result.end(), '\n'), result.end());
476 result.erase(std::remove(result.begin(), result.end(), '\r'), result.end());
477
478 if (result == "active")
479 return true;
480 }
481 return false;
482}
483
485 return isClamonaccActive() ? _("Abilitata") : _("Disabilitata");
486}
487
488
489
490void Backend::setQuarantinePath(const std::string& path) {
491 quarantinePath = path;
494}
495
501
502
503
504
505std::vector<std::string> Backend::getClamonaccLogs() {
506 std::vector<std::string> lines;
507 for (auto service : CLAMONACC_SERVICES) {
508 const auto logs = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
509 "--system", "logs", service, "100"});
510 if (!logs.started) continue;
511 std::istringstream stream(logs.output);
512 std::string line;
513 while (std::getline(stream, line)) lines.push_back(line + "\n");
514
515 if (!lines.empty()) break;
516 }
517 return lines;
518}
519
520
521
523 namespace fs = std::filesystem;
524 std::vector<std::string> db_files = {
525 "/var/lib/clamav/daily.cvd",
526 "/var/lib/clamav/main.cvd",
527 "/var/lib/clamav/bytecode.cvd",
528 "/var/lib/clamav/daily.cld",
529 "/var/lib/clamav/main.cld",
530 "/var/lib/clamav/bytecode.cld"
531 };
532
533 std::time_t newest = 0;
534 for (const auto& file : db_files) {
535 if (fs::exists(file)) {
536 auto ftime = fs::last_write_time(file);
537 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
538 ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
539 std::time_t t = std::chrono::system_clock::to_time_t(sctp);
540 if (t > newest) newest = t;
541 }
542 }
543
544 if (newest == 0)
545 return _("sconosciuta");
546
547 char buf[64];
548 std::tm tmbuf{};
549 localtime_r(&newest, &tmbuf); // thread-safe
550 std::strftime(buf, sizeof(buf), "%d %B %Y %H:%M", &tmbuf);
551 return buf;
552}
553
554
555
557 namespace fs = std::filesystem;
558
559
560 std::vector<std::string> db_files = {
561 "/var/lib/clamav/daily.cld",
562 "/var/lib/clamav/daily.cvd",
563 "/var/lib/clamav/main.cld",
564 "/var/lib/clamav/main.cvd"
565 };
566
567 for (const auto &file : db_files) {
568 if (!fs::exists(file))
569 continue;
570
571 std::ifstream f(file, std::ios::binary);
572 if (!f.is_open())
573 continue;
574
575 std::string line;
576 std::getline(f, line);
577
578
579 size_t first_colon = line.find(':');
580 size_t second_colon = line.find(':', first_colon + 1);
581 size_t third_colon = line.find(':', second_colon + 1);
582
583 if (first_colon == std::string::npos || second_colon == std::string::npos)
584 continue;
585
586 std::string date_part = line.substr(first_colon + 1, second_colon - first_colon - 1);
587 std::string version = line.substr(second_colon + 1, third_colon - second_colon - 1);
588
589 std::ostringstream out;
590 out << "daily.cvd v" << version << " (" << date_part << ")";
591 return out.str();
592 }
593
594 return _("sconosciuta");
595}
596
597
598
599
600
601
602
603std::vector<std::string> Backend::getOnAccessPaths() {
604 std::vector<std::string> paths;
605 std::ifstream f("/etc/clamav/clamd.conf");
606 if (!f.is_open()) return paths;
607
608 std::string line;
609 while (std::getline(f, line)) {
610 if (line.find("OnAccessIncludePath") == 0) {
611 auto pos = line.find(' ');
612 if (pos != std::string::npos) {
613 paths.push_back(line.substr(pos + 1));
614 }
615 }
616 }
617 return paths;
618}
619
620std::vector<std::string> Backend::getOnAccessEvents() {
621 std::vector<std::string> events;
622 for (auto service : CLAMONACC_SERVICES) {
623 const auto logs = bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
624 "--system", "logs", service, "50"});
625 if (!logs.started) continue;
626 std::istringstream stream(logs.output);
627 std::string line;
628 while (std::getline(stream, line)) {
629 if (line.find("Scanned file") != std::string::npos ||
630 line.find("FOUND") != std::string::npos ||
631 line.find("Access denied") != std::string::npos ||
632 line.find("ERROR") != std::string::npos) {
633 events.push_back(line);
634 }
635 }
636
637 if (!events.empty()) break;
638 }
639 return events;
640}
641
642
643
644// Install packages via an explicit allowlisted command; never pass an arbitrary
645// command string to pexec or a privileged shell.
646static int installPackages(const std::vector<std::string>& args) {
647 if (args.empty()) return 127;
648 std::vector<std::string> argv = {"pkexec"};
649 argv.insert(argv.end(), args.begin(), args.end());
650 return bg_process::run(argv, true).exit_code;
651}
652
653static std::string detectDistro() {
654 const std::string id = BastionGuard::Platform::distribution_id();
655 return id.empty() ? "unknown" : id;
656}
657
658static bool isClamdInstalled() {
659 return ((bg_process::exists_on_path("clamd") ? 0 : 1) == 0) ||
660 (std::filesystem::exists("/usr/sbin/clamd") ||
661 std::filesystem::exists("/usr/bin/clamd"));
662}
663
665 if (isClamdInstalled()) {
666 std::cout << _("✔ clamd già installato\n");
667 return true;
668 }
669
670 std::string distro = detectDistro();
671 std::cout << _("⚠ clamd non trovato, tentativo installazione per ") << distro << "...\n";
672
673 int ret = -1;
674 if (distro == "debian" || distro == "ubuntu" || distro == "linuxmint") {
675 ret = installPackages({"apt-get", "update"});
676 if (ret == 0) ret = installPackages({"apt-get", "install", "-y", "clamav", "clamav-daemon"});
677 } else if (distro == "fedora") {
678 ret = installPackages({"dnf", "install", "-y", "clamav", "clamav-update"});
679 } else if (distro == "centos" || distro == "rhel") {
680 ret = installPackages({"yum", "install", "-y", "clamav", "clamav-update"});
681 } else if (distro == "opensuse" || distro == "suse") {
682 ret = installPackages({"zypper", "install", "-y", "clamav"});
683 } else if (distro == "arch" || distro == "manjaro" || distro == "endeavouros") {
684 ret = installPackages({"pacman", "-S", "--needed", "--noconfirm", "clamav"});
685 } else if (distro == "gentoo" || distro == "funtoo") {
686 ret = installPackages({"emerge", "app-antivirus/clamav"});
687 } else if (distro == "slackware") {
688 ret = installPackages({"slackpkg", "install", "clamav"});
689 } else {
690 std::cerr << _("❌ Distribuzione non riconosciuta: ") << distro << "\n";
691 return false;
692 }
693
694 if (ret == 0) {
695 std::cout << _("✔ clamd installato correttamente\n");
696 return true;
697 } else {
698 std::cerr << _("❌ Errore durante l’installazione di clamd\n");
699 return false;
700 }
701}
702
704 : antiransom_()
705{
711
712
713
715
716
717 try {
718 std::string fwCfg =
719 Glib::get_home_dir() + "/.config/BastionGuard/firewall.conf";
720
721 if (std::filesystem::exists(fwCfg)) {
722 std::ifstream fin(fwCfg);
723 std::string line;
724 std::getline(fin, line);
725
726 if (line.find("firewalld") != std::string::npos)
728 else if (line.find("ufw") != std::string::npos)
730 else
732
733 std::cout << "[Backend] Firewall caricato da config: "
734 << line << std::endl;
735 } else {
736 std::cout << "[Backend] Firewall autodetect: "
737 << static_cast<int>(firewallType_) << std::endl;
738 }
739 } catch (...) {
740 std::cerr << _("[Backend] ⚠ Errore lettura configurazione firewall\n");
741 }
742
743
744 try {
746 std::cout << _("[Backend] 🛰️ Listener D-Bus ransomware attivo\n");
747 } catch (const std::exception& e) {
748 std::cerr << _("[Backend] ⚠ Impossibile avviare listener D-Bus ransomware: ")
749 << e.what() << std::endl;
750 }
751
752
753 googleSafeEnabled_ = false;
754 googleSafeApiKey_.clear();
755
756
757 try {
758 const char* home = getenv("HOME");
759 std::string cfgPath = std::string(home) + "/.config/BastionGuard/googlesafe.conf";
760 if (std::filesystem::exists(cfgPath)) {
761 std::ifstream fin(cfgPath);
762 std::string line;
763 while (std::getline(fin, line)) {
764 if (line.find("google_safe_enabled=true") != std::string::npos)
765 googleSafeEnabled_ = true;
766 if (line.find("google_safe_key=") != std::string::npos) {
767 auto pos = line.find('=');
768 if (pos != std::string::npos)
769 googleSafeApiKey_ = line.substr(pos + 1);
770 }
771 }
772 std::cout << _("[Backend] Configurazione Google Safe caricata da ") << cfgPath << "\n";
773 }
774 } catch (...) {
775 std::cerr << _("[Backend] ⚠ Impossibile leggere config Google Safe\n");
776 }
777
779 std::cout << Glib::ustring::compose(
780 _("[Backend] Google Safe Browsing attivo (API Key: %1)\n"),
781 googleSafeApiKey_.empty() ? _("vuota") : _("presente")
782 );
783
784 else
785 std::cout << _("[Backend] Google Safe Browsing disattivato.\n");
786
787
788 std::string blacklist = resource("phishing/blacklist.txt");
789
790 if (!std::filesystem::exists(blacklist)) {
791 std::cerr << _("[Backend] Blacklist non trovata in ") << blacklist
792 << _(" — provo a scaricarla con updatePhishLists()\n");
793 if (updatePhishLists()) {
794 blacklist = resource("phishing/blacklist.txt");
795 std::cout << _("[Backend] Blacklist aggiornata e trovata in ") << blacklist << std::endl;
796 } else {
797 std::cerr << _("[Backend] updatePhishLists() fallita, continuerò senza WebProbe\n");
798 }
799 }
800
801 const auto [warning_port, warning_https_port] = read_ports_from_conf();
802 (void)warning_https_port;
803
804 if (!blacklist.empty() && std::filesystem::exists(blacklist)) {
805 try {
806 webprobe_ = std::make_unique<WebProbe>(blacklist, warning_port);
807 std::cout << _("[Backend] WebProbe avviato su porta ") << warning_port
808 << _(" con blacklist: ") << blacklist << std::endl;
809 } catch (const std::exception& ex) {
810 std::cerr << _("[Backend] Errore inizializzazione WebProbe: ") << ex.what() << std::endl;
811 webprobe_.reset();
812 }
813 } else {
814 std::cerr << _("[Backend] Nessuna blacklist disponibile, WebProbe non inizializzato\n");
815 }
816
817
818 try {
820 std::cout << _("[Backend] Whitelist caricata con ")
821 << whitelist_.size() << " voci.\n";
822 } catch (const std::exception& e) {
823 std::cerr << _("[Backend] ⚠ Errore caricamento whitelist: ")
824 << e.what() << std::endl;
825 }
826
827 }
828
829
831 static Backend inst;
832 return inst;
833}
834
835
836
838 const int ret = bg_process::run({"runuser", "-u", Glib::get_user_name(), "--",
839 "/usr/libexec/bastionguard/bastionguard-service",
840 "--user", "start", "BastionGuard-ransomware-scanner.service"}).exit_code;
841 if (ret == 0)
842 std::cout << _("✔ Servizio Anti-Ransomware avviato\n");
843 else
844 std::cerr << _("❌ Errore avvio servizio Anti-Ransomware\n");
845}
846
848 const int ret = bg_process::run({"runuser", "-u", Glib::get_user_name(), "--",
849 "/usr/libexec/bastionguard/bastionguard-service",
850 "--user", "stop", "BastionGuard-ransomware-scanner.service"}).exit_code;
851 if (ret == 0)
852 std::cout << _("✔ Servizio Anti-Ransomware fermato\n");
853 else
854 std::cerr << _("⚠ Impossibile fermare il servizio Anti-Ransomware\n");
855}
856
857bool Backend::updateYaraRules(std::function<void(int, int, const std::string&)> progressCallback)
858{
859 namespace fs = std::filesystem;
860
861 std::string userYaraDir = getRuntimeDataDir() + "/data/yara";
862 std::string systemYaraDir = "/usr/share/BastionGuard/data/yara";
863
864 fs::create_directories(userYaraDir);
865
866 std::vector<std::string> files = {
867 "ByteCode.MSIL.Ransomware.Apis.yara",
868 "ByteCode.MSIL.Ransomware.Thanos.yara",
869 "Linux.Ransomware.KillDisk.yara",
870 "Win32.Ransomware.WannaCry.yara",
871 "Win32.Ransomware.LockBit.yara",
872 "Win64.Ransomware.BlackBasta.yara",
873 "ByteCode.MSIL.Ransomware.ChupaCabra.yara",
874 "ByteCode.MSIL.Ransomware.Cring.yara",
875 "ByteCode.MSIL.Ransomware.Dusk.yara",
876 "ByteCode.MSIL.Ransomware.EAF.yara",
877 "ByteCode.MSIL.Ransomware.Eternity.yara",
878 "ByteCode.MSIL.Ransomware.Fantom.yara",
879 "ByteCode.MSIL.Ransomware.GhosTEncryptor.yara",
880 "ByteCode.MSIL.Ransomware.Ghostbin.yara",
881 "ByteCode.MSIL.Ransomware.GoodWill.yara",
882 "ByteCode.MSIL.Ransomware.HarpoonLocker.yara",
883 "ByteCode.MSIL.Ransomware.Hog.yara",
884 "ByteCode.MSIL.Ransomware.Invert.yara",
885 "ByteCode.MSIL.Ransomware.Janelle.yara",
886 "ByteCode.MSIL.Ransomware.Khonsari.yara",
887 "ByteCode.MSIL.Ransomware.McBurglar.yara",
888 "ByteCode.MSIL.Ransomware.Moisha.yara",
889 "ByteCode.MSIL.Ransomware.Namaste.yara",
890 "ByteCode.MSIL.Ransomware.Oct.yara",
891 "ByteCode.MSIL.Ransomware.Pacman.yara",
892 "ByteCode.MSIL.Ransomware.PoliceRecords.yara",
893 "ByteCode.MSIL.Ransomware.Povlsomware.yara",
894 "ByteCode.MSIL.Ransomware.Retis.yara",
895 "ByteCode.MSIL.Ransomware.TaRRaK.yara",
896 "ByteCode.MSIL.Ransomware.TimeCrypt.yara",
897 "ByteCode.MSIL.Ransomware.TimeTime.yara",
898 "ByteCode.MSIL.Ransomware.Venom.yara",
899 "ByteCode.MSIL.Ransomware.WildFire.yara",
900 "ByteCode.MSIL.Ransomware.WormLocker.yara",
901 "ByteCode.MSIL.Ransomware.ZeroLocker.yara",
902 "Bytecode.MSIL.Ransomware.CobraLocker.yara",
903 "Linux.Ransomware.GwisinLocker.yara",
904 "Linux.Ransomware.Helldown.yara",
905 "Linux.Ransomware.LuckyJoe.yara",
906 "Linux.Ransomware.RedAlert.yara",
907 "Win32.Ransomware.Conti.yara",
908 "Win32.Ransomware.BlackCat.yara",
909 "Win32.Ransomware.Clop.yara",
910 "Win32.Ransomware.Ryuk.yara",
911 "Win64.Ransomware.Cactus.yara",
912 "Win64.Ransomware.Pandora.yara",
913 "Win64.Ransomware.HermeticRansom.yara"
914 };
915
916 std::string baseUrl =
917 "https://raw.githubusercontent.com/reversinglabs/reversinglabs-yara-rules/develop/yara/ransomware";
918
919 int total = files.size();
920 int index = 0;
921 int successCount = 0;
922 int failCount = 0;
923 bool blockedDetected = false;
924
925 std::cout << _("⬇ Inizio aggiornamento delle regole YARA...") << "\n";
926
927 for (const auto& file : files) {
928 ++index;
929
930 if (progressCallback)
931 progressCallback(index, total, file);
932
933 std::string url = baseUrl + "/" + file;
934 std::string dest = userYaraDir + "/" + file;
935
936 const auto download = bg_process::run({"curl", "--fail", "--silent", "--show-error",
937 "--location", "--max-time", "10", "-o", dest, "--", url}, true);
938 const std::string& output = download.output;
939 if (!download.ok()) {
940 std::error_code ec;
941 fs::remove(dest, ec);
942 failCount++;
943 continue;
944 }
945
946 if (fs::exists(dest)) {
947 auto size = fs::file_size(dest);
948
949 if (size < 100 ||
950 output.find("redirect_to_local_warning_server") != std::string::npos) {
951 blockedDetected = true;
952 fs::remove(dest);
953 failCount++;
954 continue;
955 }
956
957 std::cout << _("✔ Aggiornato: ") << file << "\n";
958 successCount++;
959
960 } else {
961 failCount++;
962 }
963 }
964
965 std::cout << "\n📊 " << _("Totale riusciti: ") << successCount
966 << " | " << _("Falliti: ") << failCount << "\n";
967
968 if (blockedDetected) {
969 std::cerr << _("⚠ Alcuni download sono stati bloccati dall'AntiPhishing.") << "\n";
970 }
971
972 std::cout << _("📁 Regole salvate in: ") << userYaraDir << "\n";
973
974
975 if (successCount > 0) {
976
977 // Install each downloaded rule by argv, never interpolate file paths
978 // into a root shell script.
979 bool copied_all = true;
980 for (const auto& file : files) {
981 const fs::path src_path = fs::path(userYaraDir) / file;
982 if (!fs::exists(src_path)) continue;
983 const fs::path dst_path = fs::path(systemYaraDir) / file;
984 if (!bg_process::run({"pkexec", "/usr/bin/install", "-D", "-m", "0644",
985 src_path.string(), dst_path.string()}).ok())
986 copied_all = false;
987 }
988 if (copied_all)
989 std::cout << _("✔ Copiate anche in /usr/share/BastionGuard/data/yara") << "\n";
990 else
991 std::cerr << _("⚠ Alcune regole YARA non sono state installate nel sistema") << "\n";
992 }
993
994 return successCount > 0;
995}
996
997
998
999
1000
1001// ---------------------------------------------------------------------------
1002// Helper: esegue lo script privilegiato dopo autorizzazione esplicita via pkexec
1003// Lo script si trova in /usr/share/BastionGuard/data/scripts/
1004// ---------------------------------------------------------------------------
1005static bool run_phishing_updater_script(const std::string& staging_dir) {
1006 const std::string helper =
1007 "/usr/share/BastionGuard/data/scripts/BastionGuard-phishing-updater.sh";
1008
1009 if (!std::filesystem::exists(helper)) {
1010 std::cerr << _("[Phishing] Helper non trovato: ") << helper << "\n";
1011 return false;
1012 }
1013
1014 // No shell and no passwordless sudo rule: pkexec performs the explicit
1015 // administrator authorization, while the staging directory is passed as
1016 // one opaque argv element.
1017 const pid_t pid = ::fork();
1018 if (pid < 0) {
1019 std::cerr << _("[Phishing] fork() fallita per helper privilegiato.\n");
1020 return false;
1021 }
1022
1023 if (pid == 0) {
1024 const char* argv[] = {
1025 "pkexec", helper.c_str(), staging_dir.c_str(), nullptr
1026 };
1027 ::execvp("pkexec", const_cast<char* const*>(argv));
1028 _exit(127);
1029 }
1030
1031 int status = 0;
1032 if (::waitpid(pid, &status, 0) < 0) return false;
1033 return WIFEXITED(status) && WEXITSTATUS(status) == 0;
1034}
1035
1036// ---------------------------------------------------------------------------
1037// mergeWhitelistSources: unifica whitelist.json e whitelist.txt evitando dup
1038// ---------------------------------------------------------------------------
1040 // Ricarica whitelist.json in whitelist_
1041 loadWhitelist();
1042
1043 const std::string txtPath =
1044 Glib::get_home_dir() +
1045 "/.local/share/BastionGuard/phishing/whitelist.txt";
1046
1047 if (!std::filesystem::exists(txtPath)) return;
1048
1049 std::ifstream f(txtPath);
1050 if (!f.is_open()) return;
1051
1052 bool changed = false;
1053 std::string line;
1054 while (std::getline(f, line)) {
1055 // trim
1056 const char* ws = " \t\r\n\f\v";
1057 size_t s = line.find_first_not_of(ws);
1058 if (s == std::string::npos) continue;
1059 size_t e = line.find_last_not_of(ws);
1060 line = line.substr(s, e - s + 1);
1061 if (line.empty() || line[0] == '#') continue;
1062
1063 if (std::find(whitelist_.begin(), whitelist_.end(), line)
1064 == whitelist_.end()) {
1065 whitelist_.push_back(line);
1066 changed = true;
1067 }
1068 }
1069 f.close();
1070
1071 if (changed) {
1072 // Risalva JSON unificato
1073 saveWhitelist();
1074
1075 // Riscrive anche il .txt con il contenuto unificato
1076 std::ofstream out(txtPath, std::ios::trunc);
1077 if (out.is_open())
1078 for (const auto& d : whitelist_)
1079 out << d << "\n";
1080
1081 std::cout << _("[Backend] ✅ Whitelist merged: ")
1082 << whitelist_.size() << " voci totali\n";
1083 }
1084}
1085
1087 // Unifica whitelist.json + whitelist.txt prima di procedere
1089
1090 namespace fs = std::filesystem;
1091
1092 std::string userBase = getRuntimeDataDir();
1093 std::string baseDir = userBase + "/data/phishing";
1094 std::string finalPath = baseDir + "/blacklist.txt";
1095 std::string finalReduce = baseDir + "/blacklist-reduce.txt";
1096
1097 // Use a private per-update directory instead of predictable /tmp names.
1098 // The privileged helper verifies owner/mode before consuming it.
1099 std::string stage_template = "/tmp/bastionguard-phish.XXXXXX";
1100 std::vector<char> stage_buf(stage_template.begin(), stage_template.end());
1101 stage_buf.push_back('\0');
1102 char* stage_ptr = ::mkdtemp(stage_buf.data());
1103 if (!stage_ptr) {
1104 std::cerr << _("[Phishing] Impossibile creare staging directory sicura.\n");
1105 return false;
1106 }
1107 std::string stagingDir(stage_ptr);
1108 ::chmod(stagingDir.c_str(), 0700);
1109
1110 std::string tmpPath = stagingDir + "/blacklist.txt";
1111 std::string tmpReduce = stagingDir + "/blacklist-reduce.txt";
1112 std::string tmpDnsmasq = stagingDir + "/dnsmasq-blacklist.conf";
1113
1114 fs::create_directories(baseDir);
1115
1116 struct Cleanup {
1117 std::vector<std::string> paths;
1118 ~Cleanup() {
1119 for (auto &p : paths) {
1120 try {
1121 if (!p.empty() && std::filesystem::exists(p))
1122 std::filesystem::remove(p);
1123 } catch(...) {}
1124 }
1125 }
1126 };
1127 Cleanup cleanup;
1128 cleanup.paths.push_back(tmpPath);
1129 cleanup.paths.push_back(tmpReduce);
1130 cleanup.paths.push_back(tmpDnsmasq);
1131 cleanup.paths.push_back(stagingDir);
1132
1133 std::vector<std::string> sources = {
1134 "https://phishing.army/download/phishing_army_blocklist.txt",
1135 "https://openphish.com/feed.txt",
1136 "https://urlhaus.abuse.ch/downloads/text/",
1137 };
1138
1139 auto trim = [](std::string &s) {
1140 const char *ws = " \t\n\r\f\v";
1141 size_t start = s.find_first_not_of(ws);
1142 if (start == std::string::npos) { s.clear(); return; }
1143 size_t end = s.find_last_not_of(ws);
1144 s = s.substr(start, end - start + 1);
1145 };
1146
1147 const std::regex re_ipv4(R"((?:(?:25[0-5]|2[0-4]\d|1?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|1?\d{1,2})){3}))");
1148 const std::regex re_ipv6(R"((?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){2,7}))");
1149
1150 auto looks_like_ip = [&](const std::string &s)->bool{
1151 return std::regex_search(s, re_ipv4) || std::regex_search(s, re_ipv6);
1152 };
1153
1154 auto looks_like_url_or_domain = [](const std::string &s) -> bool {
1155 if (s.empty()) return false;
1156 if (s.find("http://") != std::string::npos || s.find("https://") != std::string::npos) return true;
1157 if (s.find('.') != std::string::npos) {
1158 bool all_digits = true;
1159 for (char c : s) {
1160 if (!std::isdigit(static_cast<unsigned char>(c)) && c != '.') { all_digits = false; break; }
1161 }
1162 return !all_digits;
1163 }
1164 return false;
1165 };
1166
1167
1168 {
1169 std::ofstream out(tmpPath, std::ios::trunc);
1170 if (!out.is_open()) {
1171 std::cerr << _("❌ Impossibile scrivere ") << tmpPath << "\n";
1172 return false;
1173 }
1174
1175 for (const auto& url : sources) {
1176 const auto download = bg_process::run({"curl", "--fail", "--silent", "--max-time", "30",
1177 "--user-agent", "BastionGuard-PhishUpdater/1.0",
1178 "--", url}, false, 16U * 1024U * 1024U);
1179 if (!download.ok()) { std::cerr << _("⚠ Impossibile aprire ") << url << "\n"; continue; }
1180
1181 bool any = false;
1182 std::string acc;
1183 std::istringstream fetched(download.output);
1184 std::string fragment;
1185 while (std::getline(fetched, fragment)) {
1186 acc.append(fragment).append("\n");
1187 while (true) {
1188 size_t nl = acc.find('\n');
1189 if (nl == std::string::npos) break;
1190 std::string line = acc.substr(0, nl);
1191 acc.erase(0, nl + 1);
1192 if (!line.empty() && line.back() == '\r') line.pop_back();
1193 trim(line);
1194 if (line.empty() || line[0] == '#') continue;
1195
1196 if (looks_like_ip(line) || looks_like_url_or_domain(line)) {
1197 out << line << "\n";
1198 any = true;
1199 }
1200 }
1201 }
1202 if (!any) std::cerr << _("⚠ Nessun dato da ") << url << "\n";
1203 else out << "\n";
1204 }
1205 out.close();
1206 ::chmod(tmpPath.c_str(), 0600);
1207 }
1208
1209
1210 {
1211 std::ifstream fin(tmpPath);
1212 if (!fin.is_open()) return false;
1213
1214 std::unordered_set<std::string> seen;
1215 std::vector<std::string> lines;
1216 std::string line;
1217 while (std::getline(fin, line)) {
1218 if (!line.empty() && line.back() == '\r') line.pop_back();
1219 trim(line);
1220 if (line.empty() || line[0] == '#') continue;
1221 if (seen.insert(line).second) lines.push_back(line);
1222 }
1223 fin.close();
1224
1225 const size_t maxLines = 30;
1226 std::ofstream fout(tmpReduce, std::ios::trunc);
1227 for (size_t i = 0; i < std::min(lines.size(), maxLines); ++i)
1228 fout << lines[i] << "\n";
1229 fout << _("# ... lista completa salvata in blacklist.txt\n");
1230 fout.close();
1231 ::chmod(tmpReduce.c_str(), 0600);
1232 }
1233
1234 copy_to_final(tmpPath, finalPath);
1235 copy_to_final(tmpReduce, finalReduce);
1236
1237 try {
1238 std::ifstream fin(finalPath);
1239 if (!fin.is_open()) {
1240 std::cerr << _("❌ Impossibile aprire la blacklist per generare dnsmasq.") << std::endl;
1241 } else {
1242 std::ofstream fout(tmpDnsmasq, std::ios::trunc);
1243 if (!fout.is_open()) {
1244 std::cerr << _("❌ Impossibile creare file temporaneo dnsmasq.") << std::endl;
1245 } else {
1246
1247
1248 static const std::unordered_set<std::string> infra_whitelist = {
1249 "github.com",
1250 "githubusercontent.com",
1251 "gitlab.com",
1252 "bitbucket.org",
1253 "sourceforge.net",
1254
1255
1256 "archive.org",
1257
1258
1259 "npmjs.com",
1260 "jsdelivr.net",
1261 "unpkg.com",
1262
1263
1264 "cloudflare.com",
1265 "cloudfront.net",
1266 "amazonaws.com",
1267 "azurewebsites.net",
1268 "windows.net",
1269 "vercel.app",
1270 "netlify.app",
1271 "herokuapp.com",
1272
1273
1274 "googleapis.com",
1275 "googleusercontent.com",
1276 "gstatic.com",
1277 "firebaseapp.com",
1278 "firebasestorage.googleapis.com",
1279
1280 "microsoft.com",
1281 "office.com",
1282 "live.com"
1283 };
1284
1285
1286 std::unordered_set<std::string> trusted;
1287
1288 auto load_json_list = [&](const std::string& path) {
1289 if (!fs::exists(path)) return;
1290 try {
1291 std::ifstream f(path);
1292 nlohmann::json j;
1293 f >> j;
1294
1295 auto push_str = [&](const nlohmann::json& arr) {
1296 for (auto& v : arr) {
1297 if (v.is_string()) trusted.insert(v.get<std::string>());
1298 }
1299 };
1300
1301 if (j.is_array()) {
1302 push_str(j);
1303 } else if (j.contains("list") && j["list"].is_array()) {
1304 push_str(j["list"]);
1305 } else if (j.contains("whitelist") && j["whitelist"].is_array()) {
1306 push_str(j["whitelist"]);
1307 }
1308 } catch (...) {}
1309 };
1310
1311 auto load_text_list = [&](const std::string& path) {
1312 if (!fs::exists(path)) return;
1313
1314 try {
1315 std::ifstream f(path);
1316 std::string line;
1317 while (std::getline(f, line)) {
1318 line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
1319 line.erase(std::remove(line.begin(), line.end(), '\n'), line.end());
1320
1321 const char* ws = " \t\f\v";
1322 size_t start = line.find_first_not_of(ws);
1323 if (start == std::string::npos) continue;
1324 size_t end = line.find_last_not_of(ws);
1325 line = line.substr(start, end - start + 1);
1326
1327 if (line.empty() || line[0] == '#')
1328 continue;
1329
1330 trusted.insert(line);
1331 }
1332 } catch (...) {}
1333 };
1334
1335 load_json_list("/usr/share/BastionGuard/data/bank/banks.json");
1336
1337 if (const char* home = std::getenv("HOME")) {
1338 load_json_list(std::string(home) + "/.config/BastionGuard/whitelist.json");
1339 load_text_list(std::string(home) + "/.local/share/BastionGuard/phishing/whitelist.txt");
1340 }
1341
1342 auto clean_domain = [](std::string s) {
1343 std::transform(s.begin(), s.end(), s.begin(),
1344 [](unsigned char c){ return (char)std::tolower(c); });
1345
1346 const char* ws = " \t\r\n\f\v";
1347 size_t start = s.find_first_not_of(ws);
1348 if (start == std::string::npos) return std::string{};
1349 size_t end = s.find_last_not_of(ws);
1350 s = s.substr(start, end - start + 1);
1351
1352 if (auto pos = s.find("://"); pos != std::string::npos)
1353 s = s.substr(pos + 3);
1354
1355 for (char c : {'/', ' ', ';', '"', '\'', '%', '&', '(', ')', '=', '?', '*', '#'})
1356 if (auto p = s.find(c); p != std::string::npos)
1357 s = s.substr(0, p);
1358
1359 if (auto at = s.rfind('@'); at != std::string::npos)
1360 s = s.substr(at + 1);
1361
1362 if (auto colon = s.find(':'); colon != std::string::npos)
1363 s = s.substr(0, colon);
1364
1365 if (s.rfind("www.", 0) == 0)
1366 s.erase(0, 4);
1367
1368 while (!s.empty() && s.front() == '.')
1369 s.erase(0, 1);
1370 while (!s.empty() && s.back() == '.')
1371 s.pop_back();
1372
1373 return s;
1374 };
1375
1376
1377 auto is_whitelisted_domain = [&](const std::string& domain,
1378 const std::unordered_set<std::string>& wl) -> bool {
1379 if (domain.empty()) return false;
1380 if (wl.count(domain)) return true;
1381
1382 size_t dot = domain.find('.');
1383 while (dot != std::string::npos) {
1384 std::string parent = domain.substr(dot + 1);
1385 if (wl.count(parent)) return true;
1386 dot = domain.find('.', dot + 1);
1387 }
1388 return false;
1389 };
1390
1391
1392 if (!trusted.empty()) {
1393 std::unordered_set<std::string> normalized;
1394 normalized.reserve(trusted.size());
1395 for (const auto& d : trusted) normalized.insert(clean_domain(d));
1396 trusted.swap(normalized);
1397 }
1398
1399 std::cerr << _("[Backend] ✅ Whitelist utente/banca caricata: ")
1400 << trusted.size() << " domini.\n";
1401 std::cerr << _("[Backend] ✅ Whitelist infrastrutturale: ")
1402 << infra_whitelist.size() << _(" domini.\n");
1403
1404 size_t written = 0, skipped = 0, skipped_ip = 0;
1405 std::string line;
1406
1407 while (std::getline(fin, line)) {
1408 line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
1409 line.erase(std::remove(line.begin(), line.end(), '\n'), line.end());
1410 if (line.empty() || line[0] == '#') continue;
1411
1412
1413 std::string host = line;
1414
1415 if (auto pos = host.find("://"); pos != std::string::npos)
1416 host = host.substr(pos + 3);
1417
1418
1419 for (char c : {'/', ' ', ';', '"', '\'', '%', '&', '(', ')', '=', '?', '*', '#'})
1420 if (auto p = host.find(c); p != std::string::npos)
1421 host = host.substr(0, p);
1422
1423
1424 if (auto at = host.rfind('@'); at != std::string::npos)
1425 host = host.substr(at + 1);
1426
1427
1428 if (auto colon = host.find(':'); colon != std::string::npos)
1429 host = host.substr(0, colon);
1430
1431 std::string clean_host = clean_domain(host);
1432 if (clean_host.empty()) continue;
1433
1434 if (looks_like_ip(clean_host)) {
1435 skipped_ip++;
1436 continue;
1437 }
1438
1439
1440 if (is_whitelisted_domain(clean_host, infra_whitelist)) {
1441 skipped++;
1442 continue;
1443 }
1444 if (is_whitelisted_domain(clean_host, trusted)) {
1445 skipped++;
1446 continue;
1447 }
1448
1449 fout << "address=/" << clean_host << "/127.0.0.2\n";
1450 written++;
1451
1452 if (written % 1000 == 0) fout.flush();
1453 if (written > 1000000) break;
1454 }
1455
1456 fout.close();
1457 ::chmod(tmpDnsmasq.c_str(), 0600);
1458 fin.close();
1459
1460 std::cout << _("✔ File dnsmasq generato: ") << written
1461 << _(" voci. Skipped whitelist: ") << skipped
1462 << _(" | Skipped IP: ") << skipped_ip << "\n";
1463
1464 bool script_ok = run_phishing_updater_script(stagingDir);
1465 if (!script_ok) {
1466 std::cerr << _("[Phishing] Installazione privilegiata annullata o fallita; ")
1467 << _("nessun fallback insicuro viene eseguito.\n");
1468 return false;
1469 }
1470 fs::remove(tmpDnsmasq);
1471 }
1472 }
1473 } catch (const std::exception& e) {
1474 std::cerr << _("⚠ Errore generazione dnsmasq blocklist: ") << e.what() << std::endl;
1475 }
1476
1477
1478
1479
1480 try {
1481 if (!isFirewallEnabled()) {
1482 std::cout << _("[Firewall] Blocco IP disabilitato — salto applicazione regole.\n");
1483 } else if (firewallType_ == FirewallType::NONE) {
1484 std::cout << _("[Firewall] Nessun firewall supportato rilevato.\n");
1485 } else {
1486 std::ifstream fin(finalPath);
1487 std::vector<std::string> lines;
1488 std::string line;
1489
1490 while (std::getline(fin, line))
1491 lines.push_back(line);
1492
1493 const auto before = lines.size();
1494
1496
1497 std::cout << _("[Firewall] Tentata applicazione regole da ")
1498 << before << _(" righe blacklist ")
1499 << "(firewall=" << static_cast<int>(firewallType_) << ")\n";
1500 }
1501 }
1502 catch (const std::exception& e) {
1503 std::cerr << _("[Firewall] Errore applicazione regole firewall: ")
1504 << e.what() << std::endl;
1505 }
1506 catch (...) {
1507 std::cerr << _("[Firewall] Errore sconosciuto applicazione regole firewall.\n");
1508 }
1509
1510
1511 std::cout << _("✔ Blacklist phishing aggiornata in ") << finalPath
1512 << _(" e ridotta in ") << finalReduce << "\n";
1513 return true;
1514}
1515
1516
1517
1518
1519
1521 std::cout << _("▶ Avvio protezione Anti-Phishing...\n");
1522 int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "BastionGuard-phishing-scanner.service"}).exit_code);
1523 if (ret == 0) {
1524 std::cout << _("✔ Servizio Anti-Phishing avviato\n");
1525 } else {
1526 std::cerr << _("❌ Errore avvio servizio Anti-Phishing\n");
1527 }
1528}
1529
1531 std::cout << _("⏹ Arresto protezione Anti-Phishing...\n");
1532 int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "BastionGuard-phishing-scanner.service"}).exit_code);
1533 if (ret == 0) {
1534 std::cout << _("✔ Servizio Anti-Phishing fermato\n");
1535 } else {
1536 std::cerr << _("⚠ Errore arresto BastionGuard-phishing-scanner.service\n");
1537 }
1538}
1539
1540
1541
1542bool Backend::updateSanesecurityDB(std::function<void(int, int, const std::string&)> progressCallback)
1543{
1544 namespace fs = std::filesystem;
1545
1546 std::string userBase = getRuntimeDataDir();
1547 std::string baseDir = userBase + "/data/sanesecurity";
1548 std::string tmpDir = "/tmp/sanesecurity";
1549
1550 fs::create_directories(tmpDir);
1551 fs::create_directories(baseDir);
1552
1553 std::vector<std::string> dbs = {
1554 "scamsigs.hdb",
1555 "scam.ndb",
1556 "phish.ndb",
1557 "rogue.hdb",
1558 "ransomware.ndb"
1559 };
1560
1561 int total = static_cast<int>(dbs.size());
1562 int current = 0;
1563
1564 std::cout << _("⬇ Inizio aggiornamento Sanesecurity DB...") << "\n";
1565
1566 for (const auto& db : dbs) {
1567 ++current;
1568 if (progressCallback)
1569 progressCallback(current - 1, total, db);
1570
1571 std::string url = "https://sanesecurity.com/clamav/" + db;
1572 std::string dest = tmpDir + "/" + db;
1573 std::string finalPath = baseDir + "/" + db;
1574 std::string systemDest = "/usr/share/BastionGuard/data/sanesecurity/" + db;
1575
1576 std::cout << _("⬇ Scarico ") << db << " ...\n";
1577
1578 const auto download = bg_process::run({"curl", "--fail", "--silent", "--location",
1579 "--max-time", "30", "-o", dest, "--", url});
1580 const int ret = download.exit_code;
1581
1582 if (ret != 0 || !fs::exists(dest) || fs::file_size(dest) < 100) {
1583 std::cerr << _("⚠ Errore download o file vuoto: ") << db << "\n";
1584 if (progressCallback)
1585 progressCallback(current, total, db);
1586 continue;
1587 }
1588
1589
1590 if (!copy_to_final(dest, finalPath)) {
1591 std::cerr << _("❌ Errore copia locale ") << finalPath << "\n";
1592 return false;
1593 }
1594
1595
1596 if (install_to_system_data(dest, systemDest)) {
1597 std::cout << _("✔ ") << db << _(" installato system-wide in ") << systemDest << "\n";
1598 } else {
1599 std::cout << _("ℹ ") << db << _(" disponibile solo in ") << finalPath << "\n";
1600 }
1601
1602 if (progressCallback)
1603 progressCallback(current, total, db);
1604 }
1605
1606 std::cout << _("✔ Database Sanesecurity aggiornato in ") << baseDir << "\n";
1607 return true;
1608}
1609
1610
1611
1612
1614 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
1615 "--user", "is-active", "BastionGuard-ransomware-scanner.service"}).ok();
1616}
1617
1619 int ret = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-active", "BastionGuard-phishing-scanner.service"}).exit_code);
1620 return (ret == 0);
1621}
1622
1623sigc::signal<void(const std::string&)>& Backend::signal_antiransom_event() {
1624 return antiransom_signal_;
1625}
1626
1627sigc::signal<void(const std::string&)> antiphish_signal_;
1628
1629std::string Backend::checkUrl(const std::string& url) {
1630 if (!webprobe_) return url;
1631 std::string result = webprobe_->check_url(url);
1632
1633 if (result != url) {
1634
1635 antiphish_signal_.emit(url);
1636 }
1637
1638 return result;
1639}
1640
1641sigc::signal<void(const std::string&)>& Backend::signal_antiphish_event() {
1642 return antiphish_signal_;
1643}
1644
1645
1646
1647
1649 try {
1650 const char* home = getenv("HOME");
1651 if (!home) {
1652 std::cerr << "[Backend] ❌ " << _("Variabile HOME non trovata") << "\n";
1653 return;
1654 }
1655
1656 std::string cfgDir = std::string(home) + "/.config/BastionGuard";
1657 std::string cfgPath = cfgDir + "/googlesafe.conf";
1658
1659 std::filesystem::create_directories(cfgDir);
1660
1661 std::ofstream fout(cfgPath, std::ios::trunc);
1662 if (!fout.is_open()) {
1663 std::cerr << "[Backend] ❌ " << _("Impossibile aprire il file di configurazione per scrittura: ")
1664 << cfgPath << "\n";
1665 return;
1666 }
1667
1668 fout << "# " << _("Configurazione Google Safe Browsing") << "\n";
1669 fout << "google_safe_enabled=" << (googleSafeEnabled_ ? "true" : "false") << "\n";
1670 fout << "google_safe_key=" << googleSafeApiKey_ << "\n";
1671 fout.close();
1672
1673 std::cout << "[Backend] ✔ " << _("Configurazione Google Safe salvata in ") << cfgPath << "\n";
1674 } catch (const std::exception& ex) {
1675 std::cerr << "[Backend] ⚠ " << _("Errore durante il salvataggio della configurazione Google Safe: ")
1676 << ex.what() << "\n";
1677 }
1678}
1679
1681 if (googleSafeApiKey_.empty()) {
1682 std::cerr << "[Backend] ❌ " << _("Nessuna chiave API Google Safe impostata") << "\n";
1683 return false;
1684 }
1685
1686 CURL* curl = curl_easy_init();
1687 if (!curl) {
1688 std::cerr << "[Backend] ⚠ " << _("Impossibile inizializzare la libreria CURL") << "\n";
1689 return false;
1690 }
1691
1692 std::string url = "https://safebrowsing.googleapis.com/v4/threatLists?key=" + googleSafeApiKey_;
1693 std::string response;
1694
1695 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
1696 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
1697 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1698 curl_easy_setopt(curl, CURLOPT_USERAGENT, "BastionGuard/1.0");
1699
1700 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
1701 +[](void* contents, size_t size, size_t nmemb, void* userp) -> size_t {
1702 ((std::string*)userp)->append((char*)contents, size * nmemb);
1703 return size * nmemb;
1704 });
1705 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
1706
1707 CURLcode res = curl_easy_perform(curl);
1708
1709 if (res != CURLE_OK) {
1710 std::cerr << "[Backend] ⚠ " << _("Errore nella richiesta HTTP a Google Safe: ")
1711 << curl_easy_strerror(res) << "\n";
1712 curl_easy_cleanup(curl);
1713 return false;
1714 }
1715
1716 curl_easy_cleanup(curl);
1717
1718 if (response.find("threatType") != std::string::npos) {
1719 std::cout << "[Backend] ✔ " << _("Chiave Google Safe valida e attiva") << "\n";
1720 return true;
1721 } else {
1722 std::cerr << "[Backend] ❌ " << _("Chiave API non valida o risposta inattesa dal server") << "\n";
1723 return false;
1724 }
1725}
1726
1728 int ret = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "is-enabled", "BastionGuard-phishing-updater.timer"}).exit_code);
1729 return (ret == 0);
1730}
1731
1733 int ret = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
1734 "--system", enable ? "enable" : "disable", "--now", "BastionGuard-phishing-updater.timer"}).exit_code;
1735
1736 if (ret == 0) {
1737 if (enable) {
1738 std::cout << "✔ " << _("Timer aggiornamento phishing attivato correttamente.") << std::endl;
1739 } else {
1740 std::cout << "ℹ️ " << _("Timer aggiornamento phishing disattivato.") << std::endl;
1741 }
1742 return true;
1743 } else {
1744 if (enable) {
1745 std::cerr << "❌ " << _("Errore durante l’attivazione del timer di aggiornamento phishing!") << std::endl;
1746 } else {
1747 std::cerr << "⚠️ " << _("Errore durante la disattivazione del timer di aggiornamento phishing!") << std::endl;
1748 }
1749 return false;
1750 }
1751}
1752
1753
1754std::string Backend::getSambaConfigPath() const {
1755 const char* home = getenv("HOME");
1756 if (!home) return "/tmp/BastionGuard/samba.conf";
1757
1758 std::string cfgDir = std::string(home) + "/.config/BastionGuard";
1759 std::filesystem::create_directories(cfgDir);
1760
1761 return cfgDir + "/samba.conf";
1762}
1763
1764std::vector<std::string> Backend::getSambaDirs()
1765{
1766 std::vector<std::string> out;
1767 std::ifstream f(getSambaConfigPath());
1768 if (!f.is_open()) return out;
1769
1770 std::string line;
1771 while (std::getline(f, line))
1772 {
1773 line = trim(line);
1774 if (line.empty()) continue;
1775 if (line == "enabled=true" || line == "enabled=false") continue;
1776
1777 out.push_back(line);
1778 }
1779 return out;
1780}
1781
1782
1783static bool is_writable_dir(const std::filesystem::path& dir)
1784{
1785 namespace fs = std::filesystem;
1786 std::error_code ec;
1787
1788 if (!fs::exists(dir, ec) || !fs::is_directory(dir, ec))
1789 return false;
1790
1791 // Prova a creare un file temporaneo (probe)
1792 auto probe = dir / ".bastionguard_write_test";
1793 {
1794 std::ofstream out(probe, std::ios::out | std::ios::trunc);
1795 if (!out.is_open())
1796 return false;
1797 out << "ok\n";
1798 }
1799 fs::remove(probe, ec);
1800 return true;
1801}
1802
1803static std::filesystem::path make_temp_scan_dir()
1804{
1805 namespace fs = std::filesystem;
1806 std::error_code ec;
1807
1808
1809 fs::path candidates[] = {
1810 fs::temp_directory_path(),
1811 fs::path("/var/tmp")
1812 };
1813
1814 auto ensure_dir_ok = [&](const fs::path& p) -> bool {
1815 ec.clear();
1816 if (!fs::exists(p, ec))
1817 return false;
1818 if (!fs::is_directory(p, ec))
1819 return false;
1820
1821
1822 return is_writable_dir(p);
1823 };
1824
1825 fs::path base;
1826 for (const auto& c : candidates) {
1827 if (ensure_dir_ok(c)) {
1828 base = c;
1829 break;
1830 }
1831 }
1832 if (base.empty())
1833 return {};
1834
1835 std::random_device rd;
1836 std::mt19937_64 gen(rd());
1837 std::uniform_int_distribution<unsigned long long> dis(1, ULLONG_MAX);
1838
1839 for (int tries = 0; tries < 60; ++tries) {
1840 fs::path tmp = base / ("BastionGuard_samba_" + std::to_string(dis(gen)));
1841
1842 ec.clear();
1843 if (!fs::create_directories(tmp, ec) || ec)
1844 continue;
1845
1846
1847 fs::permissions(tmp,
1848 fs::perms::owner_all |
1849 fs::perms::group_read | fs::perms::group_exec |
1850 fs::perms::others_read | fs::perms::others_exec,
1851 fs::perm_options::replace,
1852 ec);
1853
1854 if (ec) {
1855 fs::remove_all(tmp, ec);
1856 continue;
1857 }
1858
1859 if (is_writable_dir(tmp))
1860 return tmp;
1861
1862 fs::remove_all(tmp, ec);
1863 }
1864
1865 return {};
1866}
1867
1868
1869
1870
1872{
1873 SMBCCTX* ctx = smbc_new_context();
1874 if (!ctx) return nullptr;
1875
1876 smbc_setOptionUseKerberos(ctx, false);
1877 smbc_setFunctionAuthDataWithContext(ctx, Backend::smb_auth_fn);
1878 smbc_setOptionUserData(ctx, this);
1879
1880 if (!smbc_init_context(ctx)) {
1881 smbc_free_context(ctx, 1);
1882 return nullptr;
1883 }
1884
1885 return ctx;
1886}
1887
1888
1889
1890
1891void Backend::smb_auth_fn(SMBCCTX* ctx,
1892 const char* srv,
1893 const char* shr,
1894 char* wg, int wglen,
1895 char* un, int unlen,
1896 char* pw, int pwlen)
1897{
1898 std::string key = "smb://" + std::string(srv) + "/" + std::string(shr);
1899
1900 auto creds = Backend::instance().getCreds(key);
1901
1902 std::string user = creds.first;
1903 std::string pass = creds.second;
1904
1905 if (user.empty()) user = "guest";
1906 if (pass.empty()) pass = "";
1907
1908 strncpy(un, user.c_str(), unlen - 1);
1909 un[unlen - 1] = '\0';
1910
1911 strncpy(pw, pass.c_str(), pwlen - 1);
1912 pw[pwlen - 1] = '\0';
1913
1914 strncpy(wg, "WORKGROUP", wglen - 1);
1915 wg[wglen - 1] = '\0';
1916}
1917
1918
1919bool Backend::isValidSMB(const std::string& url)
1920{
1921
1922 if (url.rfind("smb://", 0) != 0)
1923 return false;
1924
1925
1926 std::regex re(R"(smb:\/\/[^\/]+\/[^\/]+)");
1927 return std::regex_search(url, re);
1928}
1929
1930void Backend::saveSMBCredentials(const std::string& url,
1931 const std::string& user,
1932 const std::string& pass)
1933{
1934
1935 std::regex re(R"(smb:\/\/([^\/]+)\/([^\/]+))");
1936 std::smatch m;
1937
1938 std::string key = url;
1939 if (std::regex_search(url, m, re)) {
1940 key = "smb://" + m[1].str() + "/" + m[2].str();
1941 }
1942
1943 smb_credentials[key] = { user, pass };
1944
1945
1946 std::string path = getSambaCredsPath();
1947 std::ifstream fin(path);
1948
1949 std::map<std::string,std::pair<std::string,std::string>> all;
1950
1951 // Carica file esistente
1952 if (fin.is_open()) {
1953 std::string line;
1954 std::string cur;
1955
1956 while (std::getline(fin, line)) {
1957 if (line.empty()) continue;
1958
1959 if (line[0] == '[' && line.back() == ']') {
1960 cur = line.substr(1, line.size()-2);
1961 all[cur] = {"",""};
1962 }
1963 else if (line.find("user=") == 0) {
1964 all[cur].first = b64_decode(line.substr(5));
1965 }
1966 else if (line.find("pass=") == 0) {
1967 all[cur].second = decrypt_pass(line.substr(5));
1968 }
1969 }
1970 }
1971 fin.close();
1972
1973
1974 all[key] = { user, pass };
1975
1976
1977 std::ofstream fout(path, std::ios::trunc);
1978 if (!fout.is_open()) {
1979 std::cerr << _("❌ Impossibile scrivere credenziali SMB\n");
1980 return;
1981 }
1982
1983 for (auto &kv : all) {
1984 fout << "[" << kv.first << "]\n";
1985 fout << "user=" << b64_encode(kv.second.first) << "\n";
1986 fout << "pass=" << encrypt_pass(kv.second.second) << "\n\n";
1987 }
1988
1989 fout.close();
1990 std::cout << _("✔ Credenziali SMB salvate in modo sicuro (XOR + Base64)\n");
1991}
1992
1994{
1995 smb_credentials.clear();
1996
1997 std::string path = getSambaCredsPath();
1998 std::ifstream fin(path);
1999 if (!fin.is_open())
2000 return;
2001
2002 std::string line, cur;
2003
2004 while (std::getline(fin, line)) {
2005 if (line.empty()) continue;
2006
2007 if (line[0] == '[' && line.back() == ']') {
2008 cur = line.substr(1, line.size()-2);
2009 smb_credentials[cur] = {"",""};
2010 }
2011 else if (line.find("user=") == 0) {
2012 smb_credentials[cur].first = b64_decode(line.substr(5));
2013 }
2014 else if (line.find("pass=") == 0) {
2015 smb_credentials[cur].second = b64_decode(line.substr(5));
2016 }
2017 }
2018
2019 fin.close();
2020
2021 std::cout << _("✔ Credenziali SMB caricate: ")
2022 << smb_credentials.size() << " share\n";
2023}
2024
2025
2026
2027std::pair<std::string,std::string>
2028Backend::getCreds(const std::string& url)
2029{
2030 std::string path = getSambaCredsPath();
2031 std::ifstream f(path);
2032
2033 if (!f.is_open())
2034 return {"",""};
2035
2036 std::string line;
2037 std::string cur;
2038 bool found = false;
2039 std::string user, pass;
2040
2041 while (std::getline(f, line)) {
2042 if (line.empty()) continue;
2043
2044 if (line == "[" + url + "]") {
2045 found = true;
2046 continue;
2047 }
2048
2049 if (found) {
2050 if (line.rfind("user=", 0) == 0)
2051 user = b64_decode(line.substr(5));
2052
2053 else if (line.rfind("pass=", 0) == 0)
2054 pass = decrypt_pass(line.substr(5));
2055
2056 if (!user.empty() && !pass.empty())
2057 return { user, pass };
2058 }
2059 }
2060
2061 return {"",""};
2062}
2063
2064
2065std::string Backend::resolveSambaDir(const std::string& path)
2066{
2067
2068 if (path.rfind("smb://", 0) == 0)
2069 return path;
2070
2071
2072 return path;
2073}
2074
2075
2077 const std::string& url,
2078 std::vector<std::string>& out)
2079{
2080 if (!ctx) {
2081 std::cerr << _("SMB ERROR: contesto SMB nullo\n");
2082 return;
2083 }
2084
2085 smb_collect_files_impl(ctx, url, out);
2086}
2087
2088
2089
2090
2092 SMBCCTX* ctx,
2093 const std::string& url,
2094 std::vector<std::string>& out)
2095{
2096 if (!ctx) {
2097 std::cerr << "SMB ERROR: ctx NULL in smb_collect_files_impl\n";
2098 return;
2099 }
2100
2101
2102 auto fn_opendir = smbc_getFunctionOpendir(ctx);
2103 auto fn_readdir = smbc_getFunctionReaddir(ctx);
2104 auto fn_closedir = smbc_getFunctionClosedir(ctx);
2105
2106 if (!fn_opendir || !fn_readdir || !fn_closedir) {
2107 std::cerr << _("SMB ERROR: funzioni SMB mancanti (contesto non valido)\n");
2108 return;
2109 }
2110
2111
2112 SMBCFILE* dir = fn_opendir(ctx, url.c_str());
2113 if (!dir) {
2114 int err = errno;
2115 std::cerr << _("SMB ERROR: impossibile aprire ") << url
2116 << " (errno=" << err << ")\n";
2117 return;
2118 }
2119
2120 struct smbc_dirent* ent = nullptr;
2121
2122
2123 while ((ent = fn_readdir(ctx, dir)) != nullptr)
2124 {
2125 if (!ent) continue;
2126
2127 const char* name = ent->name;
2128 if (!name || name[0] == '.') continue; // ignora . e ..
2129
2130 std::string full = url;
2131 if (full.back() != '/')
2132 full += "/";
2133 full += name;
2134
2135 switch (ent->smbc_type)
2136 {
2137 case SMBC_FILE:
2138 out.push_back(full);
2139 break;
2140
2141 case SMBC_DIR:
2142 smb_collect_files_impl(ctx, full, out);
2143 break;
2144
2145 default:
2146 break;
2147 }
2148 }
2149
2150 fn_closedir(ctx, dir);
2151}
2152
2154 SMBCCTX* ctx,
2155 const std::string& smbPath,
2156 std::function<bool(const char*, size_t)> onChunk)
2157{
2158 if (!ctx) {
2159 std::cerr << _("SMB ERROR: ctx nullo\n");
2160 return false;
2161 }
2162
2163
2164 auto fn_open = smbc_getFunctionOpen (ctx);
2165 auto fn_read = smbc_getFunctionRead (ctx);
2166 auto fn_close = smbc_getFunctionClose (ctx);
2167
2168 if (!fn_open || !fn_read || !fn_close)
2169 {
2170 std::cerr << _("SMB ERROR: funzioni SMB non disponibili\n");
2171 return false;
2172 }
2173
2174
2175 SMBCFILE* fd = fn_open(ctx, smbPath.c_str(), O_RDONLY, 0);
2176 if (!fd) {
2177 std::cerr << _("SMB ERROR: impossibile aprire file: ") << smbPath << "\n";
2178 return false;
2179 }
2180
2181 char buffer[8192];
2182 ssize_t n = 0;
2183
2184 while ((n = fn_read(ctx, fd, buffer, sizeof(buffer))) > 0)
2185 {
2186 if (!onChunk(buffer, (size_t)n))
2187 {
2188 std::cerr << _("SMB ERROR: callback fallita\n");
2189 fn_close(ctx, fd);
2190 return false;
2191 }
2192 }
2193
2194 fn_close(ctx, fd);
2195 return true;
2196}
2197
2198
2199
2200bool Backend::smb_copy_file(const std::string& smbPath,
2201 const std::filesystem::path& dest)
2202{
2203 namespace fs = std::filesystem;
2204
2205 SMBCCTX* ctx = createThreadSmbContext();
2206 if (!ctx) {
2207 std::cerr << _("SMB ERROR: impossibile creare contesto SMB\n");
2208 return false;
2209 }
2210
2211 // Crea directory destinazione
2212 std::error_code ec;
2213 fs::create_directories(dest.parent_path(), ec);
2214 if (ec) {
2215 std::cerr << _("SMB ERROR: impossibile creare directory ")
2216 << dest.parent_path().string()
2217 << " (" << ec.message() << ")\n";
2218 smbc_free_context(ctx, 1);
2219 return false;
2220 }
2221
2222 fs::permissions(dest.parent_path(),
2223 fs::perms::owner_all |
2224 fs::perms::group_read | fs::perms::group_exec |
2225 fs::perms::others_read | fs::perms::others_exec,
2226 fs::perm_options::add,
2227 ec);
2228
2229
2230 std::ofstream out(dest, std::ios::binary | std::ios::trunc);
2231 if (!out.is_open()) {
2232 std::cerr << _("SMB ERROR: impossibile aprire file locale ")
2233 << dest.string() << _(" per scrittura\n");
2234 smbc_free_context(ctx, 1);
2235 return false;
2236 }
2237
2238 bool ok = smb_read_file_with_ctx(
2239 ctx,
2240 smbPath,
2241 [&](const char* data, size_t n) -> bool
2242 {
2243 out.write(data, static_cast<std::streamsize>(n));
2244 return out.good();
2245 }
2246 );
2247
2248 out.flush();
2249 out.close();
2250
2251 smbc_free_context(ctx, 1);
2252
2253 if (!ok) {
2254 std::cerr << _("SMB ERROR: copia fallita per: ") << smbPath << "\n";
2255 std::error_code ec2;
2256 fs::remove(dest, ec2);
2257 return false;
2258 }
2259
2260
2261 fs::permissions(dest,
2262 fs::perms::owner_read | fs::perms::owner_write |
2263 fs::perms::group_read |
2264 fs::perms::others_read,
2265 fs::perm_options::replace,
2266 ec);
2267
2268 return true;
2269}
2270
2271
2272
2274{
2275
2276}
2277
2278
2279
2280void Backend::scanSambaDir(const std::string& dirInput)
2281{
2282 namespace fs = std::filesystem;
2283
2284 samba_stop_requested = false;
2285 samba_scan_state = {};
2287 samba_last_progress = 0.0f;
2288
2289
2290 std::string scanPath = trim(dirInput);
2291
2292 // Mantieni copia originale SENZA "/" finale (serve per FOUND)
2293 std::string originalSmbRoot = scanPath;
2294 if (!originalSmbRoot.empty() && originalSmbRoot.back() == '/')
2295 originalSmbRoot.pop_back();
2296
2297 // Aggiungi "/" finale per scansione interna
2298 if (scanPath.rfind("smb://", 0) == 0)
2299 {
2300 if (scanPath.back() != '/')
2301 scanPath += '/';
2302 }
2303
2304 auto push_log = [&](const std::string& s)
2305 {
2306 samba_scan_state.log.push_back(s);
2309 };
2310
2311 push_log(_("=== Scansione Samba: ") + scanPath + " ===");
2312
2313 bool isSmb = scanPath.rfind("smb://", 0) == 0;
2314
2315 std::vector<fs::path> files;
2316 std::vector<std::string> smbFiles;
2317
2318 SMBCCTX* ctx = nullptr;
2319
2320
2321 if (isSmb)
2322 {
2323 push_log(_("Percorso SMB rilevato. Creo contesto SMB..."));
2324
2325 ctx = createThreadSmbContext();
2326 if (!ctx)
2327 {
2328 push_log(_("ERRORE: impossibile creare contesto SMB."));
2330 return;
2331 }
2332
2333 smb_collect_files(ctx, scanPath, smbFiles);
2334
2335 if (smbFiles.empty())
2336 {
2337 push_log(_("ERRORE: impossibile elencare la share SMB."));
2339 return;
2340 }
2341
2342 for (auto& f : smbFiles)
2343 files.push_back(f);
2344
2345 push_log(_("File trovati via SMB: ") + std::to_string(files.size()));
2346 }
2347
2348 else
2349 {
2350 if (!fs::exists(scanPath))
2351 {
2352 push_log(_("ERRORE: directory locale non trovata."));
2354 return;
2355 }
2356
2357 try
2358 {
2359 for (auto& e :
2360 fs::recursive_directory_iterator(scanPath, fs::directory_options::skip_permission_denied))
2361 {
2362 if (e.is_regular_file())
2363 files.push_back(e.path());
2364 }
2365 }
2366 catch (...)
2367 {
2368 push_log(_("ERRORE: impossibile scansionare la directory locale."));
2369 }
2370
2371 if (files.empty())
2372 {
2373 push_log(_("Nessun file da scansionare."));
2375 return;
2376 }
2377
2378 push_log(_("File trovati localmente: ") + std::to_string(files.size()));
2379 }
2380
2381
2382
2383 std::string clamdscanPath;
2384 const char* paths[] = {
2385 "/usr/bin/clamdscan",
2386 "/usr/local/bin/clamdscan",
2387 "/bin/clamdscan"
2388 };
2389 for (auto p : paths)
2390 if (fs::exists(p)) { clamdscanPath = p; break; }
2391
2392 if (clamdscanPath.empty()) {
2393 push_log(_("ERRORE: clamdscan non trovato."));
2395 return;
2396 }
2397
2398
2399 if ((bg_process::run({"pgrep", "-f", "clamd"}).exit_code) != 0 &&
2400 (bg_process::run({"pgrep", "-f", "clamav-daemon"}).exit_code) != 0)
2401 {
2402 push_log(_("ERRORE: il demone clamd non è attivo."));
2404 return;
2405 }
2406
2407 push_log(_("Demone ClamAV rilevato."));
2408
2409
2410 //-----------------------------------------------------------
2411 // Temp directory
2412 //-----------------------------------------------------------
2413 fs::path tempDir = make_temp_scan_dir();
2414
2415 if (tempDir.empty()) {
2416 push_log(_("ERRORE: impossibile determinare una directory temporanea valida (path vuoto)."));
2418 return;
2419 }
2420
2421 std::error_code ec;
2422 if (!fs::exists(tempDir, ec) || ec) {
2423 push_log(_("ERRORE: directory temporanea non esistente o non accessibile: ") + tempDir.string());
2424 if (ec) push_log(std::string("std::filesystem error: ") + ec.message());
2426 return;
2427 }
2428
2429 if (!fs::is_directory(tempDir, ec) || ec) {
2430 push_log(_("ERRORE: path temporaneo non è una directory valida: ") + tempDir.string());
2431 if (ec) push_log(std::string("std::filesystem error: ") + ec.message());
2433 return;
2434 }
2435
2436 samba_last_temp_dir = tempDir.string();
2437 push_log("Temp dir: " + samba_last_temp_dir);
2438
2439
2440
2441 //-----------------------------------------------------------
2442 // Copia file
2443 //-----------------------------------------------------------
2444 size_t total = files.size();
2445 size_t copied = 0;
2446
2447 push_log(_("Trovati ") + std::to_string(total) + _(" file da copiare."));
2448
2449 for (const auto& src : files)
2450 {
2451 if (samba_stop_requested) goto end_scan;
2452
2453 fs::path dest;
2454
2455 if (!isSmb)
2456 {
2457 fs::path rel = fs::relative(src, scanPath);
2458 dest = tempDir / rel;
2459
2460 fs::create_directories(dest.parent_path());
2461
2462 std::error_code ec;
2463 fs::copy_file(src, dest, fs::copy_options::overwrite_existing, ec);
2464 if (ec)
2465 {
2466 push_log(_("ERRORE copia locale: ") + rel.string());
2467 continue;
2468 }
2469 }
2470 else
2471 {
2472 std::string smbFile = src.string();
2473
2474 std::string relative = smbFile.substr(scanPath.size());
2475 if (!relative.empty() && relative[0] == '/')
2476 relative.erase(0,1);
2477
2478 dest = tempDir / relative;
2479 fs::create_directories(dest.parent_path());
2480
2481 bool ok = smb_read_file_with_ctx(
2482 ctx,
2483 smbFile,
2484 [&](const char* data, size_t n)
2485 {
2486 std::ofstream out(dest, std::ios::binary | std::ios::app);
2487 if (!out.is_open()) return false;
2488 out.write(data, n);
2489 return true;
2490 }
2491 );
2492
2493 if (!ok)
2494 {
2495 push_log(_("ERRORE lettura SMB: ") + smbFile);
2496 continue;
2497 }
2498 }
2499
2500 samba_scan_state.current_file = src.string();
2501 copied++;
2502
2503 samba_last_progress = float(copied) / float(total) * 0.30f;
2504 push_log(_("Copiato: ") + src.string());
2505 }
2506
2508 goto end_scan;
2509
2510
2511
2512 {
2513 // Paths are supplied as distinct argv entries and cannot alter command syntax.
2514 push_log(_("Eseguo scansione ClamAV: ") + tempDir.string());
2515 size_t scanned = 0;
2516 auto scan = bg_process::run({clamdscanPath, "--fdpass", "--no-summary", "--multiscan",
2517 "--", tempDir.string()}, true, 32U * 1024U * 1024U, {},
2518 [&](const std::string& line) {
2519 if (samba_stop_requested) return false;
2520
2521 push_log("ClamAV: " + line);
2522
2523 std::string filename = line.substr(0, line.find(':'));
2524 samba_scan_state.current_file = filename;
2525
2526 if (line.find("FOUND") != std::string::npos)
2527 {
2528 fs::path tmpFile = filename;
2529 fs::path rel = fs::relative(tmpFile, tempDir);
2530
2531
2532 std::string orig = originalSmbRoot + "/" + rel.string();
2533
2535 push_log("INFETTO: " + orig);
2536
2537 sendRansomwareAlert(orig, "Virus");
2538 }
2539
2540 scanned++;
2541 samba_last_progress = 0.30f + 0.70f * (float(scanned) / float(total));
2542 return true;
2543 });
2544 if (!scan.started || scan.truncated) push_log(_("ERRORE: scansione ClamAV non completata."));
2545 std::this_thread::sleep_for(std::chrono::milliseconds(300));
2546 }
2547
2548
2549 end_scan:
2550
2552
2553 push_log(_("=== Scansione completata ==="));
2554 push_log(_("File analizzati: ") + std::to_string(files.size()));
2555 push_log(_("File infetti: ") + std::to_string(samba_scan_state.infected));
2556
2558 samba_progress_callback(_("Scansione completata."), 1.0f);
2559
2560 try { fs::remove_all(tempDir); } catch (...) {}
2561
2562 samba_last_temp_dir.clear();
2563}
2564
2565
2566
2567
2571
2575
2576
2577bool Backend::removeSambaDir(const std::string& dir) {
2578 auto dirs = getSambaDirs();
2579
2580 auto it = std::remove(dirs.begin(), dirs.end(), dir);
2581 if (it == dirs.end()) {
2582 std::cerr << "⚠️ " << _("Directory Samba non trovata: ") << dir << "\n";
2583 return false;
2584 }
2585
2586 dirs.erase(it, dirs.end());
2587
2588
2589 bool enabled = isSambaEnabled();
2590
2591
2592 std::ofstream f(getSambaConfigPath(), std::ios::trunc);
2593 if (!f.is_open()) {
2594 std::cerr << _("⚠️ Impossibile riscrivere la configurazione Samba\n");
2595 return false;
2596 }
2597
2598 f << "enabled=" << (enabled ? "true" : "false") << "\n";
2599
2600
2601 for (const auto& d : dirs)
2602 f << d << "\n";
2603
2604 std::cout << "✔ " << _("Rimossa directory Samba: ") << dir << "\n";
2605 return true;
2606}
2607
2608void Backend::saveSambaConfig(bool enabled, const std::vector<std::string>& dirs)
2609{
2610 std::ofstream f(getSambaConfigPath(), std::ios::trunc);
2611 f << "enabled=" << (enabled ? "true" : "false") << "\n";
2612
2613 for (auto& d : dirs)
2614 f << trim(d) << "\n";
2615}
2616
2617
2618
2619
2621{
2622 std::cout << _("🔧 Inizializzazione canale D-Bus per invio alert...") << std::endl;
2623
2624 try {
2625 auto connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
2626
2627
2628 auto result = connection->call_sync(
2629 "/org/freedesktop/DBus",
2630 "org.freedesktop.DBus",
2631 "NameHasOwner",
2632 Glib::VariantContainerBase::create_tuple({
2633 Glib::Variant<Glib::ustring>::create("org.BastionGuard.Ransomware.Alert")
2634 }),
2635 "org.freedesktop.DBus",
2636 -1,
2637 Gio::DBus::CallFlags::NONE,
2638 Glib::VariantType()
2639 );
2640
2641 bool listener_attivo = false;
2642 if (result.gobj() != nullptr) {
2643 Glib::Variant<bool> active;
2644 result.get_child(active, 0);
2645 listener_attivo = active.get();
2646 }
2647
2648 if (listener_attivo)
2649 std::cout << _("🟢 Servizio BastionGuard-ransomware-alert trovato su D-Bus.") << std::endl;
2650 else
2651 std::cerr << _("⚠️ Servizio BastionGuard-ransomware-alert non attivo. Gli avvisi non verranno mostrati.") << std::endl;
2652
2653 } catch (const Glib::Error& ex) {
2654 std::cerr << _("❌ Errore inizializzazione D-Bus: ") << ex.what() << std::endl;
2655 }
2656}
2657
2658void Backend::sendRansomwareAlert(const std::string& path, const std::string& family)
2659{
2660 try {
2661 auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
2662
2663
2664 auto args = Glib::VariantContainerBase::create_tuple({
2665 Glib::Variant<Glib::ustring>::create("org.BastionGuard.Ransomware.Alert")
2666 });
2667
2668 bool nameHasOwner = false;
2669 try {
2670 auto reply = conn->call_sync(
2671 "/org/freedesktop/DBus",
2672 "org.freedesktop.DBus",
2673 "NameHasOwner",
2674 args,
2675 "org.freedesktop.DBus"
2676 );
2677
2678 Glib::Variant<bool> result;
2679 reply.get_child(result, 0);
2680 nameHasOwner = result.get();
2681 } catch (const Glib::Error& ex) {
2682 std::cerr << _("⚠️ Impossibile verificare il listener D-Bus: ")
2683 << ex.what() << std::endl;
2684 }
2685
2686 if (!nameHasOwner) {
2687 std::cerr << _("⚠️ Listener org.BastionGuard.Ransomware.Alert non attivo — nessun popup verrà mostrato.") << std::endl;
2688 return;
2689 }
2690
2691
2692 auto parameters = Glib::VariantContainerBase::create_tuple({
2693 Glib::Variant<Glib::ustring>::create(path),
2694 Glib::Variant<Glib::ustring>::create(family)
2695 });
2696
2697
2698 conn->call_sync(
2699 "/org/BastionGuard/ransomware/alert",
2700 "org.BastionGuard.Ransomware.Alert",
2701 "ShowAlert",
2702 parameters,
2703 "org.BastionGuard.Ransomware.Alert",
2704 -1,
2705 Gio::DBus::CallFlags::NONE,
2706 Glib::VariantType()
2707 );
2708
2709 std::cout << Glib::ustring::compose(
2710 _("📢 Alert ransomware inviato via D-Bus:\n - File: %1\n - Famiglia: %2"),
2711 path, family
2712 ) << std::endl;
2713 }
2714 catch (const Glib::Error& ex) {
2715 std::cerr << _("❌ Errore nell'invio alert D-Bus: ") << ex.what() << std::endl;
2716 }
2717}
2718
2719
2720
2721
2722void Backend::moveToQuarantine(const std::string& path) {
2723 // Tutte le richieste passano dalla stessa implementazione validata.
2724 // Quarantine::move() accetta esclusivamente file regolari e rifiuta
2725 // directory, symlink e altri oggetti del filesystem.
2726 const bool moved = Quarantine::move(path);
2727
2728 Glib::signal_idle().connect_once([path, moved]() {
2729 if (moved) {
2731 nullptr,
2732 _("File spostato in quarantena con successo:\n") + path,
2733 Gtk::MessageType::INFO);
2734 } else {
2736 nullptr,
2737 _("Impossibile spostare l'elemento in quarantena. "
2738 "Sono accettati esclusivamente file regolari:\n") + path,
2739 Gtk::MessageType::ERROR);
2740 }
2741 });
2742}
2743
2744
2746 try {
2747 std::string quarantineDir = Backend::getQuarantinePath();
2748
2749 if (quarantineDir.empty()) {
2750 quarantineDir = "/var/lib/clamav/quarantine";
2751 }
2752
2753 std::filesystem::path qPath(quarantineDir);
2754
2755 if (!std::filesystem::exists(qPath)) {
2756 std::filesystem::create_directories(qPath);
2757 std::cout << _("📁 Creata directory di quarantena: ") << qPath << "\n";
2758 }
2759
2760
2761 std::filesystem::permissions(qPath,
2762 std::filesystem::perms::owner_all |
2763 std::filesystem::perms::group_read |
2764 std::filesystem::perms::group_write,
2765 std::filesystem::perm_options::replace
2766 );
2767 } catch (const std::exception& ex) {
2768 std::cerr << _("❌ Errore in ensureQuarantinePath(): ") << ex.what() << "\n";
2769 }
2770}
2771
2772
2774{
2775 samba_stop_requested = false;
2776
2777 auto dirs = getSambaDirs();
2778 if (dirs.empty()) {
2780 samba_progress_callback("Nessuna directory Samba configurata.", 1.0f);
2781 return;
2782 }
2783
2784 for (const auto& dir : dirs)
2785 {
2787 break;
2788
2789 scanSambaDir(dir);
2790 }
2791
2793 samba_progress_callback("Tutte le scansioni completate.", 1.0f);
2794}
2795
2796
2797
2798
2799static size_t write_cb(void* c, size_t s, size_t n, std::string* o) {
2800 o->append((char*)c, s * n);
2801 return s * n;
2802}
2803
2804
2805bool Backend::updateBankListFromURL(const std::string& url) {
2806 if (url.rfind("https://", 0) != 0) {
2807 std::cerr << _("[Backend] ❌ La lista banche deve essere scaricata via HTTPS.\n");
2808 return false;
2809 }
2810
2811 std::string dir = Glib::get_home_dir() + "/.local/share/BastionGuard";
2812 std::string path = dir + "/banks.json";
2813 std::string tmp_path = path + ".download";
2814 std::filesystem::create_directories(dir);
2815 std::error_code cleanup_ec;
2816 std::filesystem::remove(tmp_path, cleanup_ec);
2817
2818 CURL* curl = curl_easy_init();
2819 if (!curl) {
2820 std::cerr << _("[Backend] ❌ Errore: init cURL\n");
2821 return false;
2822 }
2823
2824 std::ofstream outfile(tmp_path, std::ios::binary | std::ios::trunc);
2825 if (!outfile.is_open()) {
2826 std::cerr << _("[Backend] ❌ Errore apertura file temporaneo ") << tmp_path << "\n";
2827 curl_easy_cleanup(curl);
2828 return false;
2829 }
2830
2831 std::cout << _("[Backend] 🌍 Download da URL: ") << url << std::endl;
2832
2833 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
2834 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
2835 curl_easy_setopt(curl, CURLOPT_USERAGENT, "BastionGuard/1.0");
2836 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
2837 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 25L);
2838 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
2839 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
2840 curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
2841 curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
2842
2843 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outfile);
2844 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
2845 +[](void* ptr, size_t size, size_t nmemb, void* stream) -> size_t {
2846 std::ofstream* out = static_cast<std::ofstream*>(stream);
2847 out->write(static_cast<const char*>(ptr), size * nmemb);
2848 return size * nmemb;
2849 });
2850
2851 CURLcode res = curl_easy_perform(curl);
2852 long response_code = 0;
2853 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
2854 outfile.flush();
2855 outfile.close();
2856 curl_easy_cleanup(curl);
2857
2858 std::cout << _("[Backend] Risultato cURL: ") << res << " (" << curl_easy_strerror(res) << "), HTTP " << response_code << "\n";
2859
2860 if (res != CURLE_OK || response_code != 200) {
2861 std::cerr << _("[Backend] ❌ Errore rete o HTTP (") << curl_easy_strerror(res) << ")\n";
2862 std::error_code ec; std::filesystem::remove(tmp_path, ec);
2863 return false;
2864 }
2865
2866 if (!std::filesystem::exists(tmp_path) || std::filesystem::file_size(tmp_path) < 50) {
2867 std::cerr << _("[Backend] ⚠ banks.json scaricato ma vuoto.\n");
2868 std::error_code ec; std::filesystem::remove(tmp_path, ec);
2869 return false;
2870 }
2871
2872 // Validate the trust-list structure before replacing the last known-good copy.
2873 try {
2874 std::ifstream check(tmp_path);
2875 nlohmann::json j;
2876 check >> j;
2877 if (!j.is_object() || !j.contains("list") || !j["list"].is_array() || j["list"].empty()) {
2878 std::cerr << _("[Backend] ❌ banks.json non ha una struttura valida.\n");
2879 std::error_code ec; std::filesystem::remove(tmp_path, ec);
2880 return false;
2881 }
2882 for (const auto& item : j["list"]) {
2883 if (item.is_string()) continue;
2884 if (item.is_object() && item.contains("value") && item["value"].is_string()) continue;
2885 std::cerr << _("[Backend] ❌ banks.json contiene una voce non valida.\n");
2886 std::error_code ec; std::filesystem::remove(tmp_path, ec);
2887 return false;
2888 }
2889 } catch (...) {
2890 std::cerr << _("[Backend] ❌ banks.json non è JSON valido.\n");
2891 std::error_code ec; std::filesystem::remove(tmp_path, ec);
2892 return false;
2893 }
2894
2895 std::error_code replace_ec;
2896 std::filesystem::rename(tmp_path, path, replace_ec);
2897 if (replace_ec) {
2898 std::cerr << _("[Backend] ❌ Impossibile sostituire banks.json: ") << replace_ec.message() << "\n";
2899 std::filesystem::remove(tmp_path, cleanup_ec);
2900 return false;
2901 }
2902
2903 std::cout << _("[Backend] ✅ Lista banche aggiornata (") << std::filesystem::file_size(path) << " bytes)\n";
2904 return true;
2905}
2906
2907
2908using json = nlohmann::json;
2909std::vector<std::string> Backend::getWhitelist() const {
2910 return whitelist_;
2911}
2912
2913void Backend::addToWhitelist(const std::string& domain) {
2914 if (domain.empty()) return;
2915 // evita duplicati
2916 if (std::find(whitelist_.begin(), whitelist_.end(), domain) == whitelist_.end()) {
2917 whitelist_.push_back(domain);
2918 saveWhitelist();
2919 }
2920}
2921
2922void Backend::removeFromWhitelist(const std::string& domain) {
2923 whitelist_.erase(
2924 std::remove(whitelist_.begin(), whitelist_.end(), domain),
2925 whitelist_.end()
2926 );
2927 saveWhitelist();
2928}
2929
2931 try {
2932 std::filesystem::create_directories(Glib::get_home_dir() + "/.config/BastionGuard");
2933 std::ofstream f(Glib::get_home_dir() + "/.config/BastionGuard/whitelist.json", std::ios::trunc);
2934 json j(whitelist_);
2935 f << j.dump(2);
2936 f.close();
2937 std::cout << _("✔ Whitelist salvata (") << whitelist_.size() << " voci)\n";
2938 } catch (const std::exception& e) {
2939 std::cerr << _("❌ Errore salvataggio whitelist: ") << e.what() << std::endl;
2940 }
2941}
2942
2944 whitelist_.clear();
2945 std::string path = Glib::get_home_dir() + "/.config/BastionGuard/whitelist.json";
2946 if (!std::filesystem::exists(path)) return;
2947
2948 try {
2949 std::ifstream f(path);
2950 json j;
2951 f >> j;
2952 if (j.is_array()) {
2953 for (auto& item : j)
2954 whitelist_.push_back(item.get<std::string>());
2955 }
2956 std::cout << _("✔ Whitelist caricata (") << whitelist_.size() << " voci)\n";
2957 } catch (const std::exception& e) {
2958 std::cerr << _("⚠ Errore caricamento whitelist: ") << e.what() << std::endl;
2959 }
2960}
2961
2962bool Backend::isBankDomain(const std::string& host) const {
2963 return std::find(whitelist_.begin(), whitelist_.end(), host) != whitelist_.end();
2964}
2965
2967 int ret = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "reload", "dnsmasq.service"}).exit_code);
2968 if (ret == 0) {
2969 std::cout << _("✔ dnsmasq ricaricato correttamente.\n");
2970 return true;
2971 } else {
2972 std::cerr << _("⚠️ Errore nel reload di dnsmasq.\n");
2973 return false;
2974 }
2975}
2976
2978 std::cout << _("[Backend] setPhishingPort(") << port << _(") — funzione non più usata.") << std::endl;
2979}
2980
2982 return 5353;
2983}
2984
2997
2998
2999
3000
3001namespace {
3002
3004 const std::vector<std::string>& blacklistLines,
3005 std::string& outPath
3006) {
3007 std::string pathTemplate = "/tmp/bastionguard-firewall-ips-XXXXXX";
3008 std::vector<char> pathBuffer(pathTemplate.begin(), pathTemplate.end());
3009 pathBuffer.push_back('\0');
3010
3011 const int fd = ::mkstemp(pathBuffer.data());
3012 if (fd < 0) {
3013 std::cerr << _("[Firewall] Impossibile creare il file temporaneo sicuro\n");
3014 return false;
3015 }
3016
3017 outPath.assign(pathBuffer.data());
3018
3019 if (::fchmod(fd, S_IRUSR | S_IWUSR) != 0) {
3020 ::close(fd);
3021 ::unlink(outPath.c_str());
3022 outPath.clear();
3023 std::cerr << _("[Firewall] Impossibile proteggere il file temporaneo\n");
3024 return false;
3025 }
3026
3027 ::close(fd);
3028
3029 if (!BlacklistIpExtractor::extractAndWrite(blacklistLines, outPath)) {
3030 ::unlink(outPath.c_str());
3031 outPath.clear();
3032 return false;
3033 }
3034
3035 // extractAndWrite() riapre il file: imponiamo nuovamente 0600 e
3036 // verifichiamo che il pathname indichi ancora un singolo file regolare
3037 // appartenente all'utente che ha avviato BastionGuard.
3038 if (::chmod(outPath.c_str(), S_IRUSR | S_IWUSR) != 0) {
3039 ::unlink(outPath.c_str());
3040 outPath.clear();
3041 std::cerr << _("[Firewall] Impossibile impostare i permessi del file temporaneo\n");
3042 return false;
3043 }
3044
3045 struct stat st {};
3046 if (::lstat(outPath.c_str(), &st) != 0 ||
3047 !S_ISREG(st.st_mode) ||
3048 st.st_uid != ::getuid() ||
3049 st.st_nlink != 1 ||
3050 (st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
3051 ::unlink(outPath.c_str());
3052 outPath.clear();
3053 std::cerr << _("[Firewall] File temporaneo non sicuro\n");
3054 return false;
3055 }
3056
3057 return true;
3058}
3059
3060int run_firewall_helper(const std::string& inputPath) {
3061 const pid_t pid = ::fork();
3062 if (pid < 0)
3063 return -1;
3064
3065 if (pid == 0) {
3066 ::execl(
3067 "/usr/bin/pkexec",
3068 "pkexec",
3069 "/usr/bin/bastionguard-firewall",
3070 "apply",
3071 inputPath.c_str(),
3072 static_cast<char*>(nullptr)
3073 );
3074 _exit(127);
3075 }
3076
3077 int status = 0;
3078 while (::waitpid(pid, &status, 0) < 0) {
3079 if (errno == EINTR)
3080 continue;
3081 return -1;
3082 }
3083
3084 if (!WIFEXITED(status))
3085 return -1;
3086
3087 return WEXITSTATUS(status);
3088}
3089
3090} // namespace
3091
3093 const std::vector<std::string>& blacklistLines
3094) {
3096 return;
3097
3098 std::string outPath;
3099 if (!create_secure_firewall_input(blacklistLines, outPath)) {
3100 std::cout << _("[Firewall] Nessun IP valido estratto\n");
3101 return;
3102 }
3103
3104 scheduleFirewallApply(outPath);
3105}
3106
3107
3108
3109
3110
3111
3113 firewallType_ = fw;
3114
3115 if (fw != FirewallType::NONE)
3116 firewallEnabled_ = true;
3117
3119}
3120
3121
3125
3127 firewallEnabled_ = enabled;
3129}
3130
3132 return firewallEnabled_;
3133}
3134
3136 const char* home = std::getenv("HOME");
3137 if (!home) return;
3138
3139 std::ifstream fin(std::string(home) + "/.config/BastionGuard/firewall.conf");
3140 if (!fin) return;
3141
3142 std::string line;
3143 while (std::getline(fin, line)) {
3144 if (line == "firewall_enabled=true")
3145 firewallEnabled_ = true;
3146 else if (line == "firewall_enabled=false")
3147 firewallEnabled_ = false;
3148 else if (line == "firewall_type=firewalld")
3150 else if (line == "firewall_type=ufw")
3152 }
3153}
3154
3156 const char* home = std::getenv("HOME");
3157 if (!home) return;
3158
3159 std::ofstream fout;
3160 fout.open(std::string(home) + "/.config/BastionGuard/firewall.conf");
3161
3162 fout << "firewall_enabled=" << (firewallEnabled_ ? "true" : "false") << "\n";
3163
3164 switch (firewallType_) {
3166 fout << "firewall_type=firewalld\n"; break;
3167 case FirewallType::UFW:
3168 fout << "firewall_type=ufw\n"; break;
3169 default:
3170 break;
3171 }
3172}
3173
3175{
3177 return;
3178
3179 const std::string blacklistPath =
3180 "/usr/share/BastionGuard/data/phishing/blacklist.txt";
3181
3182 std::ifstream fin(blacklistPath);
3183 if (!fin) {
3184 std::cerr << _("[Firewall] Impossibile aprire blacklist\n");
3185 return;
3186 }
3187
3188 std::vector<std::string> lines;
3189 std::string line;
3190 while (std::getline(fin, line))
3191 lines.push_back(line);
3192
3193 fin.close();
3194
3195
3196 std::string outPath;
3197 if (!create_secure_firewall_input(lines, outPath)) {
3198 std::cout << _("[Firewall] Nessun IP valido trovato\n");
3199 return;
3200 }
3201
3202 scheduleFirewallApply(outPath);
3203}
3204
3205
3206
3207void Backend::scheduleFirewallApply(const std::string& inputPath) {
3208 static std::atomic<bool> pending{false};
3209
3210 if (pending.exchange(true)) {
3211 ::unlink(inputPath.c_str());
3212 return;
3213 }
3214
3215 std::thread([inputPath] {
3216 std::this_thread::sleep_for(std::chrono::seconds(2));
3217
3218 const int exitCode = run_firewall_helper(inputPath);
3219 if (exitCode != 0) {
3220 std::cerr << _("[Firewall] Applicazione helper fallita, codice: ")
3221 << exitCode << "\n";
3222 ::unlink(inputPath.c_str());
3223 }
3224
3225 pending.store(false);
3226 }).detach();
3227}
3228
3229
3230
3231
3232
3233Backend::~Backend() = default;
static std::string extract_path_from_clamav_line(const std::string &line)
Definition Backend.cpp:122
static std::string decrypt_pass(const std::string &enc)
Definition Backend.cpp:107
static int installPackages(const std::vector< std::string > &args)
Definition Backend.cpp:646
static bool is_writable_dir(const std::filesystem::path &dir)
Definition Backend.cpp:1783
static std::string encrypt_pass(const std::string &pass)
Definition Backend.cpp:103
static const std::string SMB_KEY
Definition Backend.cpp:94
static std::string first_existing(const std::vector< std::string > &names)
Definition Backend.cpp:227
static std::string getConfigFilePath()
Definition Backend.cpp:351
static size_t write_cb(void *c, size_t s, size_t n, std::string *o)
Definition Backend.cpp:2799
static std::string quarantinePath
Definition Backend.cpp:339
sigc::signal< void(const std::string &)> antiphish_signal_
Definition Backend.cpp:1627
static bool copy_to_final(const std::string &src, const std::string &dest)
Definition Backend.cpp:431
static std::vector< std::string > clamDbDirs()
Definition Backend.cpp:132
static void ensureQuarantinePath()
Definition Backend.cpp:399
static std::string detectDistro()
Definition Backend.cpp:653
static bool isClamdInstalled()
Definition Backend.cpp:658
static std::filesystem::path make_temp_scan_dir()
Definition Backend.cpp:1803
static std::string xor_crypt(const std::string &input)
Definition Backend.cpp:96
static std::string trim(const std::string &s)
Definition Backend.cpp:112
static std::string findClamonaccService()
Definition Backend.cpp:443
std::pair< int, int > read_ports_from_conf()
Definition Backend.cpp:288
static bool run_phishing_updater_script(const std::string &staging_dir)
Definition Backend.cpp:1005
static std::string b64_encode(const std::string &s)
Definition Backend.cpp:86
static std::string b64_decode(const std::string &s)
Definition Backend.cpp:90
nlohmann::json json
Definition Backend.cpp:2908
static void restart_clamd()
Definition Backend.cpp:308
static bool install_to_system_data(const std::string &src, const std::string &dest)
Definition Backend.cpp:321
static std::string getUserConfigDir()
Definition Backend.cpp:341
static const char * CLAMONACC_SERVICES[]
Definition Backend.cpp:407
static void persistQuarantinePath(const std::string &path)
Definition Backend.cpp:355
static bool read_cvd_header(const std::string &path, DbInfo &out)
Definition Backend.cpp:159
static void loadQuarantinePathFromConfig()
Definition Backend.cpp:378
static std::string getRuntimeDataDir()
Definition Backend.cpp:413
static std::time_t filetime_to_time_t(fs::file_time_type ft)
Definition Backend.cpp:149
#define _(s)
FirewallType
std::function< void(const std::string &, float)> samba_progress_callback
Definition Backend.hpp:150
static std::string resolveSambaDir(const std::string &path)
Definition Backend.cpp:2065
AntiRansomEngine antiransom_
Definition Backend.hpp:198
bool isFirewallEnabled() const
Definition Backend.cpp:3131
std::vector< std::string > whitelist_
Definition Backend.hpp:203
bool isSambaScanRunning()
Definition Backend.cpp:241
void initSambaClient()
Definition Backend.cpp:2273
bool isBankDomain(const std::string &host) const
Definition Backend.cpp:2962
bool removeSambaDir(const std::string &dir)
Definition Backend.cpp:2577
static std::vector< std::string > getOnAccessEvents()
Definition Backend.cpp:620
bool testGoogleSafeKey()
Definition Backend.cpp:1680
static std::vector< std::string > getOnAccessPaths()
Definition Backend.cpp:603
bool updatePhishLists()
Definition Backend.cpp:1086
void scanAllSambaDirs()
Definition Backend.cpp:2773
static std::vector< std::string > getClamonaccLogs()
Definition Backend.cpp:505
std::string samba_last_temp_dir
Definition Backend.hpp:213
bool smb_copy_file(const std::string &smbPath, const std::filesystem::path &dest)
Definition Backend.cpp:2200
void stop_antiphish()
Definition Backend.cpp:1530
void addToWhitelist(const std::string &domain)
Definition Backend.cpp:2913
void loadSMBCredentials()
Definition Backend.cpp:1993
void requestStopSambaScan()
Definition Backend.cpp:2572
bool enablePhishAutoUpdate(bool enable)
Definition Backend.cpp:1732
std::atomic< bool > samba_stop_requested
Definition Backend.hpp:113
static bool isAntiransomActive()
Definition Backend.cpp:1613
static int getAppBuild()
Definition Backend.cpp:78
void stop_antiransom()
Definition Backend.cpp:847
void scheduleFirewallApply(const std::string &inputPath)
Definition Backend.cpp:3207
void start_antiransom()
Definition Backend.cpp:837
std::string getSambaCredsPath() const
Definition Backend.cpp:248
static std::string getAppVersion()
Definition Backend.cpp:70
void saveSambaConfig(bool enabled, const std::vector< std::string > &dirs)
Definition Backend.cpp:2608
void setFirewallEnabled(bool enabled)
Definition Backend.cpp:3126
float samba_last_progress
Definition Backend.hpp:214
sigc::signal< void(const std::string &)> antiransom_signal_
Definition Backend.hpp:199
void setSambaEnabled(bool enabled)
Definition Backend.cpp:272
static std::string getQuarantinePath()
Definition Backend.cpp:496
std::string checkUrl(const std::string &url)
Definition Backend.cpp:1629
static void smb_auth_fn(SMBCCTX *context, const char *srv, const char *shr, char *wg, int wglen, char *un, int unlen, char *pw, int pwlen)
Definition Backend.cpp:1891
void smb_collect_files(SMBCCTX *ctx, const std::string &url, std::vector< std::string > &out)
Definition Backend.cpp:2076
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
void ensureQuarantinePath()
Definition Backend.cpp:2745
static Backend & instance()
Definition Backend.cpp:830
int getPhishingPort() const
Definition Backend.cpp:2981
bool isSambaEnabled()
Definition Backend.cpp:260
bool updateBankListFromURL(const std::string &source_url)
Definition Backend.cpp:2805
void loadFirewallConfig()
Definition Backend.cpp:3135
std::unique_ptr< WebProbe > webprobe_
Definition Backend.hpp:202
void setupAlertListener()
Definition Backend.cpp:2620
static bool isValidSMB(const std::string &url)
Definition Backend.cpp:1919
sigc::signal< void(const std::string &)> & signal_antiphish_event()
Definition Backend.cpp:1641
void removeFromWhitelist(const std::string &domain)
Definition Backend.cpp:2922
std::string googleSafeApiKey_
Definition Backend.hpp:206
void setPhishingPort(int port)
Definition Backend.cpp:2977
void resetSambaStop()
Definition Backend.cpp:2568
std::pair< std::string, std::string > getCreds(const std::string &url)
Definition Backend.cpp:2028
ScanStatus get_current_scan_files()
Definition Backend.cpp:2985
void sendRansomwareAlert(const std::string &path, const std::string &family)
Definition Backend.cpp:2658
void loadWhitelist()
Definition Backend.cpp:2943
SMBCCTX * createThreadSmbContext()
Definition Backend.cpp:1871
bool firewallEnabled_
Definition Backend.hpp:207
void applyFirewallFromBlacklist(const std::vector< std::string > &blacklistLines)
Definition Backend.cpp:3092
static std::string getClamonaccStatus()
Definition Backend.cpp:484
static std::string getLastUpdateDate()
Definition Backend.cpp:522
FirewallType getFirewallType() const
Definition Backend.cpp:3122
void moveToQuarantine(const std::string &)
Definition Backend.cpp:2722
bool updateSanesecurityDB(std::function< void(int, int, const std::string &)> progressCallback=nullptr)
Definition Backend.cpp:1542
void start_antiphish()
Definition Backend.cpp:1520
void mergeWhitelistSources()
Definition Backend.cpp:1039
std::vector< std::string > getSambaDirs()
Definition Backend.cpp:1764
std::string getSambaConfigPath() const
Definition Backend.cpp:1754
void setFirewallType(FirewallType fw)
Definition Backend.cpp:3112
void saveSMBCredentials(const std::string &url, const std::string &user, const std::string &pass)
Definition Backend.cpp:1930
static bool isClamonaccActive()
Definition Backend.cpp:467
bool isAntiphishActive()
Definition Backend.cpp:1618
std::map< std::string, std::pair< std::string, std::string > > smb_credentials
Definition Backend.hpp:123
bool reload_dnsmasq()
Definition Backend.cpp:2966
static bool disableClamonacc()
Definition Backend.cpp:461
void scanSambaDir(const std::string &dir)
Definition Backend.cpp:2280
void saveWhitelist() const
Definition Backend.cpp:2930
void saveFirewallConfig() const
Definition Backend.cpp:3155
FirewallType firewallType_
Definition Backend.hpp:208
sigc::signal< void(const std::string &)> & signal_antiransom_event()
Definition Backend.cpp:1623
bool isPhishAutoUpdateEnabled()
Definition Backend.cpp:1727
void applyFirewallFromPhishingBlacklist()
Definition Backend.cpp:3174
void saveGoogleSafeConfig()
Definition Backend.cpp:1648
bool smb_read_file_with_ctx(SMBCCTX *ctx, const std::string &smbPath, std::function< bool(const char *, size_t)>)
Definition Backend.cpp:2153
SambaScanState samba_scan_state
Definition Backend.hpp:172
void smb_collect_files_impl(SMBCCTX *ctx, const std::string &url, std::vector< std::string > &out)
Definition Backend.cpp:2091
std::vector< std::string > getWhitelist() const
Definition Backend.cpp:2909
static bool enableClamonacc()
Definition Backend.cpp:455
bool googleSafeEnabled_
Definition Backend.hpp:205
static bool installClamdIfMissing()
Definition Backend.cpp:664
static std::string getDbVersion()
Definition Backend.cpp:556
static bool extractAndWrite(const std::vector< std::string > &lines, const std::string &outPath="/tmp/bastionguard-firewall-ips.txt")
static FirewallType detectFirewall()
static bool move(const std::string &file)
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
std::string distribution_id()
bool create_secure_firewall_input(const std::vector< std::string > &blacklistLines, std::string &outPath)
Definition Backend.cpp:3003
int run_firewall_helper(const std::string &inputPath)
Definition Backend.cpp:3060
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={})
bool exists_on_path(const std::string &name)
#define S_IWOTH
#define S_IRUSR
#define S_IWUSR
#define S_IWGRP
std::string resource(const std::string &rel)
Definition Resource.hpp:31
std::string current_file
Definition Backend.hpp:167
std::vector< std::string > log
Definition Backend.hpp:168
std::vector< std::string > files
Definition Backend.hpp:159
std::string temp_dir
Definition Backend.hpp:158
std::string current_scanning
Definition Backend.hpp:157
long long version
Definition Backend.cpp:144
long long epoch
Definition Backend.cpp:145
std::string file
Definition Backend.cpp:143