115: Gtk::Box(Gtk::Orientation::VERTICAL, 12),
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);
126 auto spacer_left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
127 spacer_left->set_hexpand(
true);
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");
134 auto spacer_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
135 spacer_right->set_hexpand(
true);
137 title_box->append(*spacer_left);
138 title_box->append(*title_label);
139 title_box->append(*spacer_right);
143 auto ctrl = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
144 ctrl->set_halign(Gtk::Align::CENTER);
151 write_log(
_(
"Aggiornamento manuale lista USB."));
159 if (autoscan_enabled) {
161 Glib::signal_timeout().connect_seconds(
174 Glib::signal_timeout().connect_seconds(
178 write_log(
_(
"Autoscan USB attivato (ogni 3 minuti)"));
196 auto sc_cards = Gtk::make_managed<Gtk::ScrolledWindow>();
198 sc_cards->set_vexpand(
true);
201 auto lbl_log = Gtk::make_managed<Gtk::Label>(
_(
"📜 Log giornaliero USB"));
202 lbl_log->set_halign(Gtk::Align::CENTER);
209 txt_log_.set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
217 Glib::signal_timeout().connect_seconds_once([
this]() {
231 std::vector<USBDeviceInfo> list;
233 udev* udev = udev_new();
234 if (!udev)
return list;
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);
241 udev_list_entry* devices = udev_enumerate_get_list_entry(en);
242 udev_list_entry* entry;
244 udev_list_entry_foreach(entry, devices)
246 const char* path = udev_list_entry_get_name(entry);
247 udev_device* dev = udev_device_new_from_syspath(udev, path);
250 const char* devnode = udev_device_get_devnode(dev);
251 if (!devnode) { udev_device_unref(dev);
continue; }
253 std::string dn = devnode;
254 if (dn.starts_with(
"/dev/sr")) { udev_device_unref(dev);
continue; }
256 udev_device* parent =
257 udev_device_get_parent_with_subsystem_devtype(dev,
"usb",
"usb_device");
258 if (!parent) { udev_device_unref(dev);
continue; }
260 const char* rem = udev_device_get_sysattr_value(dev,
"removable");
261 if (!rem || std::string(rem) !=
"1") {
262 udev_device_unref(dev);
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; }
274 const char* vendor = udev_device_get_sysattr_value(parent,
"manufacturer");
275 const char* product = udev_device_get_sysattr_value(parent,
"product");
277 if (vendor) info.
name += vendor;
279 if (!info.
name.empty()) info.
name +=
" ";
280 info.
name += product;
282 if (info.
name.empty())
286 udev_device_get_property_value(dev,
"ID_SERIAL_SHORT");
289 serial = udev_device_get_property_value(dev,
"ID_SERIAL");
291 info.
uid = serial ? serial : udev_device_get_syspath(dev);
293 list.push_back(info);
294 udev_device_unref(dev);
297 udev_enumerate_unref(en);
317 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
318 box->add_css_class(
"usb-card");
321 auto icon = Gtk::make_managed<Gtk::Image>(
322 Gdk::Pixbuf::create_from_file(
resource(
"icons/usb.png"), 24, 24,
true)
324 icon->set_halign(Gtk::Align::START);
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);
334 auto lbl_info = Gtk::make_managed<Gtk::Label>(
336 (dev.
serial.empty() ?
"" :
"SN: " + dev.
serial +
"\n") +
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);
344 auto btn_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
345 btn_box->add_css_class(
"usb-card-buttons");
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);
352 btn_action->signal_clicked().connect([
this, dev]() {
390 auto_mounted =
false;
391 std::string partition = devnode;
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);
399 write_log(
_(
"Nessuna partizione valida trovata su ") + devnode);
405 if (!already.empty()) {
406 write_log(
_(
"Device già montato: ") + already);
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
419 int exit_status = proc->get_exit_status();
420 bool ok = (exit_status == 0);
422 auto stream = proc->get_stdout_pipe();
423 auto data = Gio::DataInputStream::create(stream);
426 data->read_line(output);
429 if (!ok || output.find(
"Error") != std::string::npos) {
434 if (output.find(
"not a mountable filesystem") != std::string::npos) {
435 write_log(
_(
"Non è un filesystem montabile: ") + partition);
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."));
446 std::string mp = output.substr(pos + key.size());
448 while (!mp.empty() &&
449 (mp.back() ==
'\n' ||
456 if (!fs::exists(mp) || !fs::is_directory(mp)) {
457 write_log(
_(
"Mountpoint non valido o inesistente: ") + mp);
464 catch (
const Glib::Error& ex) {
465 write_log(
_(
"Eccezione montaggio: ") + std::string(ex.what()));
474 if (d.devnode == devnode)
477 if (dev.
uid.empty()) {
478 write_log(
_(
"Device non trovato: ") + devnode);
483 write_log(
_(
"Scansione già in corso per device: ") + dev.
uid);
493 std::lock_guard<std::mutex> lk(
scan_mtx_);
501 std::lock_guard<std::mutex> lk(
scan_mtx_);
505 Gtk::Window* parent =
dynamic_cast<Gtk::Window*
>(get_root());
506 auto dialog = std::make_shared<LiveScanDialog>(parent, mp);
508 std::vector<std::string> args = {
518 auto proc = Gio::Subprocess::create(
520 Gio::Subprocess::Flags::STDOUT_PIPE |
521 Gio::Subprocess::Flags::STDERR_MERGE
524 auto stream = proc->get_stdout_pipe();
525 auto data = Gio::DataInputStream::create(stream);
527 auto pproc = std::make_shared<Glib::RefPtr<Gio::Subprocess>>(proc);
528 auto pdata = std::make_shared<Glib::RefPtr<Gio::DataInputStream>>(data);
530 auto loop = std::make_shared<std::function<void()>>();
532 *loop = [
this, dialog, loop, pproc, pdata, uid = dev.
uid]()
534 if (dialog->cancelled()) {
535 (*pproc)->send_signal(SIGKILL);
539 (*pdata)->read_line_async(
540 [
this, dialog, loop, pproc, pdata, uid]
541 (
const Glib::RefPtr<Gio::AsyncResult>& r)
547 ok = (*pdata)->read_line_finish(r, line);
554 Glib::signal_idle().connect_once([dialog, line]() {
555 dialog->append_output(line);
561 (*pproc)->wait_async(
562 [
this, alive =
alive_, dialog, uid](
const Glib::RefPtr<Gio::AsyncResult>&)
565 std::lock_guard<std::mutex> lk(
scan_mtx_);
566 if (!alive->load())
return;
570 dialog->mark_finished();
572 Glib::signal_idle().connect_once([
this, alive, dialog]() {
574 std::string out = dialog->get_full_output();
582 if (out.find(
"FOUND") != std::string::npos) {
590 Glib::RefPtr<Gio::Cancellable>(),
591 Glib::PRIORITY_DEFAULT
602 auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SESSION);
604 auto msg = Gio::DBus::Message::create_method_call(
605 "org.BastionGuard.Ransomware.Alert",
606 "/org/BastionGuard/ransomware/alert",
607 "org.BastionGuard.Ransomware.Alert",
611 auto body = Glib::VariantContainerBase::create_tuple({
612 Glib::Variant<Glib::ustring>::create(file),
613 Glib::Variant<Glib::ustring>::create(family)
618 conn->send_message(msg);
620 catch (
const Glib::Error& ex) {
621 write_log(
_(
"Errore invio D-Bus alert: ") + std::string(ex.what()));
674 bool infected = (text.find(
"FOUND") != std::string::npos);
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");
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
692 }
catch (
const Glib::Error& e) {
693 std::cerr <<
_(
"[ResultDialog] CSS error: ") << e.what() <<
"\n";
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");
702 auto *close_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
703 close_box->set_margin_end(6);
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);
714 header->pack_end(*close_box);
716 auto *header_wrapper = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
717 header_wrapper->append(*header);
718 dialog->set_titlebar(*header_wrapper);
721 auto *vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 16);
722 vbox->set_margin(25);
724 auto titleLabel = Gtk::make_managed<Gtk::Label>(
_(
"Scansione USB completata"));
725 titleLabel->add_css_class(
"title");
726 titleLabel->set_halign(Gtk::Align::CENTER);
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";
739 if (std::filesystem::exists(iconPath))
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);
748 std::string msg = infected
749 ?
_(
"Minacce rilevate durante la scansione.\nVerifica la quarantena.")
750 :
_(
"La scansione si è conclusa senza rilevare minacce.");
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);
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]() {
766 vbox->append(*titleLabel);
767 vbox->append(*picture);
768 vbox->append(*messageLabel);
769 vbox->append(*closeBtn);
771 dialog->set_child(*vbox);
780 std::vector<std::string> previous;
785 std::vector<std::string> current;
788 current.push_back(i.devnode);
796 for (
auto& dev : current)
797 if (std::find(previous.begin(), previous.end(), dev) == previous.end()) {
802 Glib::signal_idle().connect([
this, alive, dev]() {
803 if (!alive || !alive->load(std::memory_order_acquire))
808 popup->set_modal(
false);
809 popup->set_resizable(
false);
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);
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);
822 popup->signal_hide().connect([popup]() {
830 Glib::signal_idle().connect([
this, alive]() {
831 if (!alive || !alive->load(std::memory_order_acquire))
841 for (
auto& dev : previous)
842 if (std::find(current.begin(), current.end(), dev) == current.end()) {
847 Glib::signal_idle().connect([
this, alive]() {
848 if (!alive || !alive->load(std::memory_order_acquire))
858 std::this_thread::sleep_for(std::chrono::seconds(2));
892 std::lock_guard<std::mutex> lock(
log_mtx_);
897 auto t = std::time(
nullptr);
899 localtime_r(&t, &tmbuf);
901 std::strftime(buf,
sizeof(buf),
"[%Y-%m-%d %H:%M:%S] ", &tmbuf);
903 f << buf << msg <<
"\n";
914 Glib::signal_idle().connect([
this, alive]() {
915 if (!alive || !alive->load(std::memory_order_acquire))
918 std::vector<std::string> lines;
925 Gtk::TextIter it = buf->end();
927 for (
auto& l : lines) {
928 auto t = std::time(
nullptr);
930 localtime_r(&t, &tmbuf);
932 std::strftime(ts,
sizeof(ts),
"[%Y-%m-%d %H:%M:%S] ", &tmbuf);
934 buf->insert(it, std::string(ts) + l +
"\n");
938 auto iter = buf->end();
972 std::istringstream ss(text);
975 while (std::getline(ss, line))
977 if (line.find(
"FOUND") == std::string::npos)
980 std::string file, family;
982 size_t col = line.find(
':');
983 if (col != std::string::npos)
984 file = line.substr(0, col);
986 size_t fpos = line.find(
"FOUND");
987 if (fpos != std::string::npos) {
988 std::string tmp = line.substr(col + 1, fpos - col - 1);
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());
1001 if (file.empty() || family.empty())
void save_usb_autoscan(bool enabled)
static std::string trim(const std::string &s)
bool has_virus(const std::string &text)
std::string usb_conf_path()
std::string find_mountpoint(const std::string &devnode)
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)
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)
std::string get_log_path()
void start_monitor_thread()
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)
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)