Skip to content
API Documentation
BastionGuard™
USBScanPage.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 "USBScanPage.hpp"
22#include "../Resource.hpp"
23#include "../Quarantine.hpp"
24#include "ScanPromptWindow.hpp"
25#include "LiveScanDialog.hpp"
26#include <giomm.h>
27#include <giomm/subprocess.h>
28#include <giomm/datainputstream.h>
29#include <filesystem>
30#include <fstream>
31#include <iostream>
32#include <thread>
33#include <chrono>
34#include <regex>
35#include <glibmm/i18n.h>
36#include <glibmm/miscutils.h>
37#include <libudev.h>
38
39namespace fs = std::filesystem;
40
41
42
43std::string usb_conf_path() {
44 return Glib::get_home_dir() + "/.config/BastionGuard/usb.conf";
45}
46
47void save_usb_autoscan(bool enabled) {
48 std::string dir = Glib::get_home_dir() + "/.config/BastionGuard";
49 std::filesystem::create_directories(dir);
50
51 std::ofstream f(usb_conf_path());
52 if (!f) return;
53 f << "auto_scan=" << (enabled ? "1" : "0") << "\n";
54}
55
57 std::ifstream f(usb_conf_path());
58 if (!f) return false;
59
60 std::string line;
61 while (std::getline(f, line)) {
62 if (line.rfind("auto_scan=", 0) == 0)
63 return line.substr(10) == "1";
64 }
65 return false;
66}
67
68bool has_virus(const std::string& text)
69{
70 std::regex re(R"(FOUND\s*$)");
71 std::istringstream ss(text);
72 std::string line;
73
74 while (std::getline(ss, line)) {
75 if (line.find("FOUND") != std::string::npos)
76 return true;
77 }
78 return false;
79}
80
81
82std::string USBScanPage::human_readable_size(uint64_t bytes) {
83 const char* units[] = {"B", "KB", "MB", "GB", "TB"};
84 int idx = 0;
85 double sz = bytes;
86
87 while (sz > 1024 && idx < 4) {
88 sz /= 1024;
89 idx++;
90 }
91
92 char buf[32];
93 std::snprintf(buf, sizeof(buf), "%.1f %s", sz, units[idx]);
94 return buf;
95}
96
97static std::string trim(const std::string& s) {
98 size_t a = s.find_first_not_of(" \t\n\r");
99 size_t b = s.find_last_not_of(" \t\n\r");
100 if (a == std::string::npos) return "";
101 return s.substr(a, b - a + 1);
102}
103
105{
106 if (alive_) alive_->store(false, std::memory_order_release);
107 monitor_running_ = false;
108 if (monitor_thread_.joinable()) monitor_thread_.join();
109 if (log_idle_connection_.connected()) log_idle_connection_.disconnect();
110 if (auto_refresh_conn_.connected()) auto_refresh_conn_.disconnect();
111}
112
113
115: Gtk::Box(Gtk::Orientation::VERTICAL, 12),
116 initial_scan_done_(false)
117{
118 set_margin(16);
119 set_vexpand(true);
120
121
122 auto title_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
123 title_box->add_css_class("secure-headerbar");
124 title_box->set_margin_bottom(8);
125
126 auto spacer_left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
127 spacer_left->set_hexpand(true);
128
129 auto title_label = Gtk::make_managed<Gtk::Label>(_("🖴 Periferiche USB"));
130 title_label->set_margin(8);
131 title_label->set_halign(Gtk::Align::CENTER);
132 title_label->add_css_class("secure-header-title");
133
134 auto spacer_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
135 spacer_right->set_hexpand(true);
136
137 title_box->append(*spacer_left);
138 title_box->append(*title_label);
139 title_box->append(*spacer_right);
140
141 append(*title_box);
142
143 auto ctrl = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
144 ctrl->set_halign(Gtk::Align::CENTER);
145
146
147 btn_refresh_.set_label(_("🔄 Aggiorna"));
148 btn_refresh_.signal_clicked().connect([this]() {
149 update_cards();
151 write_log(_("Aggiornamento manuale lista USB."));
152 });
153 ctrl->append(btn_refresh_);
154
155 chk_auto_refresh_.set_label(_("Auto scan ogni 3 min"));
156 bool autoscan_enabled = load_usb_autoscan();
157 chk_auto_refresh_.set_active(autoscan_enabled);
158
159 if (autoscan_enabled) {
161 Glib::signal_timeout().connect_seconds(
162 [this]() { update_cards(); return chk_auto_refresh_.get_active(); },
163 180
164 );
165 }
166
167 chk_auto_refresh_.signal_toggled().connect([this]() {
168
169 bool enabled = chk_auto_refresh_.get_active();
170 save_usb_autoscan(enabled);
171
172 if (enabled) {
174 Glib::signal_timeout().connect_seconds(
175 [this]() { update_cards(); return chk_auto_refresh_.get_active(); },
176 180
177 );
178 write_log(_("Autoscan USB attivato (ogni 3 minuti)"));
179 } else {
180 auto_refresh_conn_.disconnect();
181 write_log(_("Autoscan USB disattivato"));
182 }
183 });
184
185 ctrl->append(chk_auto_refresh_);
186
187
188 append(*ctrl);
189
190 flowbox_devices_.set_valign(Gtk::Align::START);
191 flowbox_devices_.set_max_children_per_line(3);
192 flowbox_devices_.set_selection_mode(Gtk::SelectionMode::NONE);
193 flowbox_devices_.set_row_spacing(8);
194 flowbox_devices_.set_column_spacing(8);
195
196 auto sc_cards = Gtk::make_managed<Gtk::ScrolledWindow>();
197 sc_cards->set_child(flowbox_devices_);
198 sc_cards->set_vexpand(true);
199 append(*sc_cards);
200
201 auto lbl_log = Gtk::make_managed<Gtk::Label>(_("📜 Log giornaliero USB"));
202 lbl_log->set_halign(Gtk::Align::CENTER);
203 append(*lbl_log);
204
205 scroller_log_.set_vexpand(true);
206 scroller_log_.set_hexpand(true);
207
208 txt_log_.set_editable(false);
209 txt_log_.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
210
211 scroller_log_.set_child(txt_log_);
212 append(scroller_log_);
213
214 monitor_running_ = true;
215
217 Glib::signal_timeout().connect_seconds_once([this]() {
219 }, 3);
220
222
223 update_cards();
225
226}
227
228
229std::vector<USBDeviceInfo> USBScanPage::detect_usb_devices()
230{
231 std::vector<USBDeviceInfo> list;
232
233 udev* udev = udev_new();
234 if (!udev) return list;
235
236 udev_enumerate* en = udev_enumerate_new(udev);
237 udev_enumerate_add_match_subsystem(en, "block");
238 udev_enumerate_add_match_property(en, "DEVTYPE", "disk");
239 udev_enumerate_scan_devices(en);
240
241 udev_list_entry* devices = udev_enumerate_get_list_entry(en);
242 udev_list_entry* entry;
243
244 udev_list_entry_foreach(entry, devices)
245 {
246 const char* path = udev_list_entry_get_name(entry);
247 udev_device* dev = udev_device_new_from_syspath(udev, path);
248 if (!dev) continue;
249
250 const char* devnode = udev_device_get_devnode(dev);
251 if (!devnode) { udev_device_unref(dev); continue; }
252
253 std::string dn = devnode;
254 if (dn.starts_with("/dev/sr")) { udev_device_unref(dev); continue; }
255
256 udev_device* parent =
257 udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
258 if (!parent) { udev_device_unref(dev); continue; }
259
260 const char* rem = udev_device_get_sysattr_value(dev, "removable");
261 if (!rem || std::string(rem) != "1") {
262 udev_device_unref(dev);
263 continue;
264 }
265
266 const char* size = udev_device_get_sysattr_value(dev, "size");
267 uint64_t sectors = size ? std::strtoull(size, nullptr, 10) : 0;
268 if (sectors == 0) { udev_device_unref(dev); continue; }
269
270 USBDeviceInfo info;
271 info.devnode = devnode;
272 info.size_bytes = sectors * 512ULL;
273
274 const char* vendor = udev_device_get_sysattr_value(parent, "manufacturer");
275 const char* product = udev_device_get_sysattr_value(parent, "product");
276
277 if (vendor) info.name += vendor;
278 if (product) {
279 if (!info.name.empty()) info.name += " ";
280 info.name += product;
281 }
282 if (info.name.empty())
283 info.name = info.devnode;
284
285 const char* serial =
286 udev_device_get_property_value(dev, "ID_SERIAL_SHORT");
287
288 if (!serial)
289 serial = udev_device_get_property_value(dev, "ID_SERIAL");
290
291 info.uid = serial ? serial : udev_device_get_syspath(dev);
292
293 list.push_back(info);
294 udev_device_unref(dev);
295 }
296
297 udev_enumerate_unref(en);
298 udev_unref(udev);
299 return list;
300}
301
302bool USBScanPage::try_start_scan(const std::string& uid)
303{
304 std::lock_guard<std::mutex> lk(scan_mtx_);
305
306 auto& st = device_state_[uid];
307 if (st == ScanState::Mounting || st == ScanState::Scanning)
308 return false;
309
311 return true;
312}
313
314
316
317 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
318 box->add_css_class("usb-card");
319
320 try {
321 auto icon = Gtk::make_managed<Gtk::Image>(
322 Gdk::Pixbuf::create_from_file(resource("icons/usb.png"), 24, 24, true)
323 );
324 icon->set_halign(Gtk::Align::START);
325 box->append(*icon);
326 } catch (...) {}
327
328 auto lbl_title = Gtk::make_managed<Gtk::Label>(dev.name);
329 lbl_title->add_css_class("usb-card-title");
330 lbl_title->set_halign(Gtk::Align::START);
331 lbl_title->set_wrap(true);
332 box->append(*lbl_title);
333
334 auto lbl_info = Gtk::make_managed<Gtk::Label>(
335 dev.devnode + "\n" +
336 (dev.serial.empty() ? "" : "SN: " + dev.serial + "\n") +
338 );
339 lbl_info->add_css_class("usb-card-info");
340 lbl_info->set_halign(Gtk::Align::START);
341 lbl_info->set_wrap(true);
342 box->append(*lbl_info);
343
344 auto btn_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
345 btn_box->add_css_class("usb-card-buttons");
346
347 auto btn_action = Gtk::make_managed<Gtk::Button>(_("Monta e Scansiona"));
348 btn_action->add_css_class("scan-btn");
349 btn_box->append(*btn_action);
350 box->append(*btn_box);
351
352 btn_action->signal_clicked().connect([this, dev]() {
353 write_log(_("Richiesta scansione completa per: ") + dev.devnode);
355 });
356
357 return box;
358}
359
360
362 auto list = detect_usb_devices();
363
364 auto children = flowbox_devices_.get_children();
365 for (auto* child : children)
366 flowbox_devices_.remove(*child);
367
368 for (auto& dev : list)
369 flowbox_devices_.insert(*build_usb_card(dev), -1);
370
371 flowbox_devices_.show();
372}
373
374std::string USBScanPage::find_mountpoint(const std::string& devnode) {
375 std::ifstream f("/proc/mounts");
376 std::string line;
377
378 while (std::getline(f, line)) {
379 if (line.starts_with(devnode + " ")) {
380 auto pos = line.find(' ');
381 if (pos != std::string::npos)
382 return line.substr(pos + 1, line.find(' ', pos + 1) - (pos + 1));
383 }
384 }
385 return "";
386}
387
388std::string USBScanPage::mount_partition(const std::string& devnode, bool& auto_mounted)
389{
390 auto_mounted = false;
391 std::string partition = devnode;
392
393 if (devnode.rfind("/dev/sd", 0) == 0 && devnode.size() == 8) {
394 std::string p1 = devnode + "1";
395 if (fs::exists(p1)) {
396 write_log(_("Rilevato disco senza partizione montabile, uso: ") + p1);
397 partition = p1;
398 } else {
399 write_log(_("Nessuna partizione valida trovata su ") + devnode);
400 return "";
401 }
402 }
403
404 std::string already = find_mountpoint(partition);
405 if (!already.empty()) {
406 write_log(_("Device già montato: ") + already);
407 return already;
408 }
409
410 try {
411 auto proc = Gio::Subprocess::create(
412 { "udisksctl", "mount", "-b", partition, "--options", "umask=022" },
413 Gio::Subprocess::Flags::STDOUT_PIPE |
414 Gio::Subprocess::Flags::STDERR_MERGE
415 );
416
417 proc->wait();
418
419 int exit_status = proc->get_exit_status();
420 bool ok = (exit_status == 0);
421
422 auto stream = proc->get_stdout_pipe();
423 auto data = Gio::DataInputStream::create(stream);
424
425 std::string output;
426 data->read_line(output);
427 write_log(_("Mount output: ") + output);
428
429 if (!ok || output.find("Error") != std::string::npos) {
430 write_log(_("Errore udisksctl: ") + output);
431 return "";
432 }
433
434 if (output.find("not a mountable filesystem") != std::string::npos) {
435 write_log(_("Non è un filesystem montabile: ") + partition);
436 return "";
437 }
438
439 const std::string key = " at ";
440 size_t pos = output.find(key);
441 if (pos == std::string::npos) {
442 write_log(_("Output udisksctl non interpretabile."));
443 return "";
444 }
445
446 std::string mp = output.substr(pos + key.size());
447
448 while (!mp.empty() &&
449 (mp.back() == '\n' ||
450 mp.back() == '.' ||
451 mp.back() == ' '))
452 {
453 mp.pop_back();
454 }
455
456 if (!fs::exists(mp) || !fs::is_directory(mp)) {
457 write_log(_("Mountpoint non valido o inesistente: ") + mp);
458 return "";
459 }
460
461 auto_mounted = true;
462 return mp;
463 }
464 catch (const Glib::Error& ex) {
465 write_log(_("Eccezione montaggio: ") + std::string(ex.what()));
466 return "";
467 }
468}
469
470void USBScanPage::run_clamdscan_async(const std::string& devnode)
471{
472 USBDeviceInfo dev;
473 for (auto& d : detect_usb_devices())
474 if (d.devnode == devnode)
475 dev = d;
476
477 if (dev.uid.empty()) {
478 write_log(_("Device non trovato: ") + devnode);
479 return;
480 }
481
482 if (!try_start_scan(dev.uid)) {
483 write_log(_("Scansione già in corso per device: ") + dev.uid);
484 return;
485 }
486
487 write_log(_("Avvio scansione USB: ") + dev.devnode);
488
489 bool autom = false;
490 std::string mp = mount_partition(dev.devnode, autom);
491 if (mp.empty()) {
492 {
493 std::lock_guard<std::mutex> lk(scan_mtx_);
494 device_state_.erase(dev.uid);
495 }
496 show_scan_result(_("Errore: impossibile montare il dispositivo USB."));
497 return;
498 }
499
500 {
501 std::lock_guard<std::mutex> lk(scan_mtx_);
503 }
504
505 Gtk::Window* parent = dynamic_cast<Gtk::Window*>(get_root());
506 auto dialog = std::make_shared<LiveScanDialog>(parent, mp);
507
508 std::vector<std::string> args = {
509 "clamdscan",
510 "--verbose",
511 "--fdpass",
512 "--remove=no"
513 };
514
515
516 args.push_back(mp);
517
518 auto proc = Gio::Subprocess::create(
519 args,
520 Gio::Subprocess::Flags::STDOUT_PIPE |
521 Gio::Subprocess::Flags::STDERR_MERGE
522 );
523
524 auto stream = proc->get_stdout_pipe();
525 auto data = Gio::DataInputStream::create(stream);
526
527 auto pproc = std::make_shared<Glib::RefPtr<Gio::Subprocess>>(proc);
528 auto pdata = std::make_shared<Glib::RefPtr<Gio::DataInputStream>>(data);
529
530 auto loop = std::make_shared<std::function<void()>>();
531
532 *loop = [this, dialog, loop, pproc, pdata, uid = dev.uid]()
533 {
534 if (dialog->cancelled()) {
535 (*pproc)->send_signal(SIGKILL);
536 return;
537 }
538
539 (*pdata)->read_line_async(
540 [this, dialog, loop, pproc, pdata, uid]
541 (const Glib::RefPtr<Gio::AsyncResult>& r)
542 {
543 std::string line;
544 bool ok = false;
545
546 try {
547 ok = (*pdata)->read_line_finish(r, line);
548 } catch (...) {
549 ok = false;
550 }
551
552 if (ok) {
553 line += "\n";
554 Glib::signal_idle().connect_once([dialog, line]() {
555 dialog->append_output(line);
556 });
557 (*loop)();
558 return;
559 }
560
561 (*pproc)->wait_async(
562 [this, alive = alive_, dialog, uid](const Glib::RefPtr<Gio::AsyncResult>&)
563 {
564 {
565 std::lock_guard<std::mutex> lk(scan_mtx_);
566 if (!alive->load()) return;
567 device_state_.erase(uid);
568 }
569
570 dialog->mark_finished();
571
572 Glib::signal_idle().connect_once([this, alive, dialog]() {
573 BastionGuard::if_alive(alive, [this, dialog]() {
574 std::string out = dialog->get_full_output();
575 write_log("SCAN OUTPUT:\n" + out);
576
577 dialog->close();
578
579 show_scan_result(out);
580
581
582 if (out.find("FOUND") != std::string::npos) {
584 }
585 });
586 });
587 }
588 );
589 },
590 Glib::RefPtr<Gio::Cancellable>(),
591 Glib::PRIORITY_DEFAULT
592 );
593 };
594
595 (*loop)();
596}
597
598
599void USBScanPage::send_dbus_alert(const std::string& file, const std::string& family)
600{
601 try {
602 auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
603
604 auto msg = Gio::DBus::Message::create_method_call(
605 "org.BastionGuard.Ransomware.Alert",
606 "/org/BastionGuard/ransomware/alert",
607 "org.BastionGuard.Ransomware.Alert",
608 "ShowAlert"
609 );
610
611 auto body = Glib::VariantContainerBase::create_tuple({
612 Glib::Variant<Glib::ustring>::create(file),
613 Glib::Variant<Glib::ustring>::create(family)
614 });
615
616 msg->set_body(body);
617
618 conn->send_message(msg);
619 }
620 catch (const Glib::Error& ex) {
621 write_log(_("Errore invio D-Bus alert: ") + std::string(ex.what()));
622 }
623}
624
625
626
627std::string USBScanPage::build_scan_report(const std::string& raw)
628{
629 std::istringstream ss(raw);
630 std::string line;
631
632 int scanned_files = 0;
633 int infected_files = 0;
634 std::vector<std::string> infected_list;
635
636 // Parsing
637 while (std::getline(ss, line)) {
638
639 if (line.find(": OK") != std::string::npos)
640 scanned_files++;
641
642 else if (line.find("FOUND") != std::string::npos) {
643 infected_files++;
644 scanned_files++;
645 infected_list.push_back(line);
646 }
647 }
648
649 std::ostringstream out;
650
651 out << _("=========== RISULTATO SCANSIONE ===========\n");
652 out << _("File analizzati: ") << scanned_files << "\n";
653 out << _("File infetti: ") << infected_files << "\n";
654 out << "===========================================\n\n";
655
656 if (infected_files > 0) {
657 out << _("---- FILE INFETTI ----\n");
658 for (auto& f : infected_list)
659 out << f << "\n";
660 out << "\n";
661 } else {
662 out << _("Nessuna minaccia rilevata.\n\n");
663 }
664
665 out << _("---- OUTPUT COMPLETO ----\n");
666 out << raw << "\n";
667
668 return out.str();
669}
670
671
672void USBScanPage::show_scan_result(const std::string& text)
673{
674 bool infected = (text.find("FOUND") != std::string::npos);
675
676 auto dialog = new Gtk::Window();
677 dialog->set_title("");
678 dialog->set_default_size(620, 240);
679 dialog->set_resizable(false);
680 dialog->set_modal(true);
681 dialog->add_css_class("app-dialog");
682 dialog->add_css_class("main-window");
683 dialog->add_css_class("app-window");
684
685 try {
686 auto css = Gtk::CssProvider::create();
687 css->load_from_path(resource("BastionGuard.css"));
688 auto display = Gdk::Display::get_default();
689 Gtk::StyleContext::add_provider_for_display(
690 display, css, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
691 );
692 } catch (const Glib::Error& e) {
693 std::cerr << _("[ResultDialog] CSS error: ") << e.what() << "\n";
694 }
695
696
697 auto *header = Gtk::make_managed<Gtk::HeaderBar>();
698 header->set_show_title_buttons(false);
699 header->set_decoration_layout("");
700 header->add_css_class("custom-headerbar");
701
702 auto *close_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
703 close_box->set_margin_end(6);
704
705 try {
706 auto icon = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 28, 28, true);
707 auto *btn = Gtk::make_managed<Gtk::Button>();
708 btn->set_child(*Gtk::make_managed<Gtk::Image>(icon));
709 btn->add_css_class("header-button");
710 btn->signal_clicked().connect([dialog]() { dialog->hide(); delete dialog; });
711 close_box->append(*btn);
712 } catch (...) {}
713
714 header->pack_end(*close_box);
715
716 auto *header_wrapper = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
717 header_wrapper->append(*header);
718 dialog->set_titlebar(*header_wrapper);
719
720
721 auto *vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 16);
722 vbox->set_margin(25);
723
724 auto titleLabel = Gtk::make_managed<Gtk::Label>(_("Scansione USB completata"));
725 titleLabel->add_css_class("title");
726 titleLabel->set_halign(Gtk::Align::CENTER);
727
728 // ICONA: OK oppure threat
729 auto *picture = Gtk::make_managed<Gtk::Picture>();
730 picture->set_content_fit(Gtk::ContentFit::CONTAIN);
731 picture->set_halign(Gtk::Align::CENTER);
732 picture->set_margin_bottom(10);
733 picture->set_size_request(68, 68);
734 std::string iconPath = infected
735 ? "/usr/share/BastionGuard/data/icons/false.png"
736 : "/usr/share/BastionGuard/data/icons/true.png";
737
738 try {
739 if (std::filesystem::exists(iconPath))
740 {
741 auto pb = Gdk::Pixbuf::create_from_file(iconPath);
742 auto scaled = pb->scale_simple(68, 68, Gdk::InterpType::BILINEAR);
743 auto tex = Gdk::Texture::create_for_pixbuf(scaled);
744 picture->set_paintable(tex);
745 }
746 } catch (...) {}
747
748 std::string msg = infected
749 ? _("Minacce rilevate durante la scansione.\nVerifica la quarantena.")
750 : _("La scansione si è conclusa senza rilevare minacce.");
751
752 auto messageLabel = Gtk::make_managed<Gtk::Label>(msg);
753 messageLabel->set_wrap(true);
754 messageLabel->set_justify(Gtk::Justification::CENTER);
755 messageLabel->set_halign(Gtk::Align::CENTER);
756
757 // Pulsante chiudi
758 auto closeBtn = Gtk::make_managed<Gtk::Button>(_("Chiudi"));
759 closeBtn->add_css_class("btn-success");
760 closeBtn->set_halign(Gtk::Align::CENTER);
761 closeBtn->signal_clicked().connect([dialog]() {
762 dialog->close();
763 delete dialog;
764 });
765
766 vbox->append(*titleLabel);
767 vbox->append(*picture);
768 vbox->append(*messageLabel);
769 vbox->append(*closeBtn);
770
771 dialog->set_child(*vbox);
772 dialog->present();
773}
774
775
776
777
779 monitor_thread_ = std::thread([this]() {
780 std::vector<std::string> previous;
781
782 while (monitor_running_) {
783
784 auto list = detect_usb_devices();
785 std::vector<std::string> current;
786
787 for (auto& i : list)
788 current.push_back(i.devnode);
789
790 if (!initial_scan_done_) {
791 previous = current;
792 initial_scan_done_ = true;
793 }
794 else {
795
796 for (auto& dev : current)
797 if (std::find(previous.begin(), previous.end(), dev) == previous.end()) {
798
799 write_log(_("Collegata periferica: ") + dev);
800
801 auto alive = alive_;
802 Glib::signal_idle().connect([this, alive, dev]() {
803 if (!alive || !alive->load(std::memory_order_acquire))
804 return false;
805
806 auto *popup = new ScanPromptWindow(dev);
807
808 popup->set_modal(false);
809 popup->set_resizable(false);
810
811 popup->signal_scan_requested().connect([this, alive, dev]() {
812 if (!alive || !alive->load(std::memory_order_acquire)) return;
813 write_log(_("Avvio scansione richiesta dall’utente per: ") + dev);
815 });
816
817 popup->signal_cancel_requested().connect([this, alive, dev]() {
818 if (!alive || !alive->load(std::memory_order_acquire)) return;
819 write_log(_("Scansione USB annullata per: ") + dev);
820 });
821
822 popup->signal_hide().connect([popup]() {
823 delete popup;
824 });
825
826 popup->present();
827 return false;
828 });
829
830 Glib::signal_idle().connect([this, alive]() {
831 if (!alive || !alive->load(std::memory_order_acquire))
832 return false;
833 update_cards();
835 return false;
836 });
837 }
838
839
840
841 for (auto& dev : previous)
842 if (std::find(current.begin(), current.end(), dev) == current.end()) {
843
844 write_log(_("Rimossa periferica: ") + dev);
845
846 auto alive = alive_;
847 Glib::signal_idle().connect([this, alive]() {
848 if (!alive || !alive->load(std::memory_order_acquire))
849 return false;
850 update_cards();
852 return false;
853 });
854 }
855 }
856
857 previous = current;
858 std::this_thread::sleep_for(std::chrono::seconds(2));
859 }
860 });
861}
862
863
864
865void USBScanPage::append_live_output(const std::string& line) {
866 auto alive = alive_;
867 Glib::signal_idle().connect([this, alive, line]() {
868 if (!alive || !alive->load(std::memory_order_acquire)) return false;
869
870 if (!live_scan_dialog_) return false;
871 if (!live_scan_textview_) return false;
872
873 auto buf = live_scan_textview_->get_buffer();
874 if (!buf) return false;
875
876 buf->insert(buf->end(), line);
877
878 Gtk::TextBuffer::iterator iter = buf->end();
879 live_scan_textview_->scroll_to(iter);
880
881 return false;
882 });
883}
884
886 std::string home = Glib::get_home_dir();
887 return home + "/.bastionguard_usb.log";
888}
889
890void USBScanPage::write_log(const std::string& msg) {
891 {
892 std::lock_guard<std::mutex> lock(log_mtx_);
893
894 std::ofstream f(get_log_path(), std::ios::app);
895 if (!f) return;
896
897 auto t = std::time(nullptr);
898 std::tm tmbuf{};
899 localtime_r(&t, &tmbuf); // thread-safe
900 char buf[64];
901 std::strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S] ", &tmbuf);
902
903 f << buf << msg << "\n";
904 }
905
906 {
907 std::lock_guard<std::mutex> lk(pending_log_mtx_);
908 pending_log_lines_.push_back(msg);
909 }
910
912 auto alive = alive_;
914 Glib::signal_idle().connect([this, alive]() {
915 if (!alive || !alive->load(std::memory_order_acquire))
916 return false;
917
918 std::vector<std::string> lines;
919 {
920 std::lock_guard<std::mutex> lk(pending_log_mtx_);
921 lines.swap(pending_log_lines_);
922 }
923
924 auto buf = txt_log_.get_buffer();
925 Gtk::TextIter it = buf->end();
926
927 for (auto& l : lines) {
928 auto t = std::time(nullptr);
929 std::tm tmbuf{};
930 localtime_r(&t, &tmbuf); // thread-safe
931 char ts[64];
932 std::strftime(ts, sizeof(ts), "[%Y-%m-%d %H:%M:%S] ", &tmbuf);
933
934 buf->insert(it, std::string(ts) + l + "\n");
935 it = buf->end();
936 }
937
938 auto iter = buf->end();
939 txt_log_.scroll_to(iter);
940
941
942 log_idle_connection_.disconnect();
943 return false;
944 });
945 }
946}
947
948
950 std::ifstream f(get_log_path());
951 if (!f) {
952 txt_log_.get_buffer()->set_text(_("(Nessun log disponibile)"));
953 return;
954 }
955
956 std::string all((std::istreambuf_iterator<char>(f)),
957 std::istreambuf_iterator<char>());
958
959 auto buf = txt_log_.get_buffer();
960 buf->set_text(all);
961
962 auto iter = buf->end();
963 txt_log_.scroll_to(iter);
964
965}
966
967
968
969
970void USBScanPage::show_infected_dialog(const std::string& text)
971{
972 std::istringstream ss(text);
973 std::string line;
974
975 while (std::getline(ss, line))
976 {
977 if (line.find("FOUND") == std::string::npos)
978 continue;
979
980 std::string file, family;
981
982 size_t col = line.find(':');
983 if (col != std::string::npos)
984 file = line.substr(0, col);
985
986 size_t fpos = line.find("FOUND");
987 if (fpos != std::string::npos) {
988 std::string tmp = line.substr(col + 1, fpos - col - 1);
989
990 auto trim = [](std::string& s) {
991 s.erase(s.begin(), std::find_if(s.begin(), s.end(),
992 [](unsigned char c){ return !std::isspace(c); }));
993 s.erase(std::find_if(s.rbegin(), s.rend(),
994 [](unsigned char c){ return !std::isspace(c); }).base(), s.end());
995 };
996
997 trim(tmp);
998 family = tmp;
999 }
1000
1001 if (file.empty() || family.empty())
1002 continue;
1003
1004 send_dbus_alert(file, family);
1005 }
1006}
static std::string trim(const std::string &s)
Definition Backend.cpp:112
#define _(s)
void save_usb_autoscan(bool enabled)
bool load_usb_autoscan()
static std::string trim(const std::string &s)
bool has_virus(const std::string &text)
std::string usb_conf_path()
ScanPromptWindow(const std::string &devnode)
std::string find_mountpoint(const std::string &devnode)
virtual ~USBScanPage()
void update_cards()
Gtk::TextView * live_scan_textview_
std::atomic< bool > monitor_running_
Gtk::ScrolledWindow scroller_log_
void append_live_output(const std::string &line)
void send_dbus_alert(const std::string &file, const std::string &family)
std::mutex scan_mtx_
void show_scan_result(const std::string &text)
Gtk::Widget * build_usb_card(const USBDeviceInfo &dev)
Gtk::FlowBox flowbox_devices_
bool try_start_scan(const std::string &uid)
void update_log_view()
Gtk::TextView txt_log_
std::string get_log_path()
void start_monitor_thread()
std::mutex log_mtx_
sigc::connection auto_refresh_conn_
Gtk::CheckButton chk_auto_refresh_
std::mutex pending_log_mtx_
std::vector< std::string > pending_log_lines_
std::string mount_partition(const std::string &devnode, bool &auto_mounted)
bool initial_scan_done_
class LiveScanDialog * live_scan_dialog_
void run_clamdscan_async(const std::string &devnode)
void show_infected_dialog(const std::string &text)
std::string build_scan_report(const std::string &raw)
BastionGuard::AliveFlag alive_
std::unordered_map< std::string, ScanState > device_state_
std::thread monitor_thread_
sigc::connection log_idle_connection_
std::vector< USBDeviceInfo > detect_usb_devices()
bool ignore_existing_devices_
static std::string human_readable_size(uint64_t bytes)
void write_log(const std::string &msg)
Gtk::Button btn_refresh_
void if_alive(const AliveFlag &alive, Fn &&fn)
std::string resource(const std::string &rel)
Definition Resource.hpp:31
uint64_t size_bytes
std::string uid
std::string name
std::string devnode
std::string serial