/* * BastionGuard™ * Copyright (C) 2025–2026 Calogero Scarnà * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * BastionGuard™ is a trademark of Calogero Scarnà. * The BastionGuard™ name and branding are not licensed under the GPL. */ #include "SafeProcess.hpp" #include "SambaPage.hpp" #include "Utils.hpp" #include "Backend.hpp" #include "Resource.hpp" #include #include #include #include #include #include #include #include #include static bool clamdscan_running() { return ((bg_process::run({"pgrep", "-f", "clamdscan"}).exit_code) == 0); } static bool samba_temp_exists() { namespace fs = std::filesystem; try { fs::path base = fs::temp_directory_path() / "BastionGuard"; if (!fs::exists(base) || !fs::is_directory(base)) return false; for (auto& p : fs::directory_iterator(base)) { if (p.is_directory()) { auto name = p.path().filename().string(); if (name.rfind("samba_scan_", 0) == 0) return true; } } } catch (...) { return false; } return false; } SambaPage::SambaPage() : Gtk::Box(Gtk::Orientation::VERTICAL, 12) { set_margin(12); sambaStore = Gtk::ListStore::create(sambaCols); sambaView.set_model(sambaStore); auto hboxSwitch = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 6); auto lbl = Gtk::make_managed(_("Abilita scansione Samba")); lbl->set_use_markup(true); lbl->set_hexpand(true); lbl->set_halign(Gtk::Align::START); switchEnable.set_valign(Gtk::Align::CENTER); switchEnable.signal_state_set().connect( [this](bool){ if (!loading_) saveConfig(); return false; }, false ); hboxSwitch->append(*lbl); hboxSwitch->append(switchEnable); append(*hboxSwitch); auto col = Gtk::make_managed(_("Percorso")); auto cell = Gtk::make_managed(); col->pack_start(*cell, true); col->add_attribute(cell->property_text(), sambaCols.col_path); sambaView.append_column(*col); auto scroll = Gtk::make_managed(); scroll->set_child(sambaView); scroll->set_vexpand(true); scroll->set_min_content_height(180); append(*scroll); auto btnBox = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 6); btnAdd.set_label(_("Aggiungi")); btnRemove.set_label(_("Rimuovi")); btnScan.set_label(_("Scansiona ora")); btnSave.set_label(_("Salva configurazione")); btnBox->append(btnAdd); btnBox->append(btnRemove); btnBox->append(btnScan); btnBox->append(btnSave); btnBox->set_halign(Gtk::Align::END); append(*btnBox); // Signals btnAdd.signal_clicked().connect(sigc::mem_fun(*this, &SambaPage::onAddDir)); btnRemove.signal_clicked().connect(sigc::mem_fun(*this, &SambaPage::onRemoveDir)); btnSave.signal_clicked().connect(sigc::mem_fun(*this, &SambaPage::saveConfig)); btnScan.signal_clicked().connect(sigc::mem_fun(*this, &SambaPage::onScanNow)); loadConfig(); refreshUI(); } void SambaPage::loadConfig() { loading_ = true; sambaStore->clear(); switchEnable.set_active(Backend::instance().isSambaEnabled()); for (const auto &dir : Backend::instance().getSambaDirs()) { if (dir.empty()) continue; auto row = *(sambaStore->append()); row[sambaCols.col_path] = dir; row[sambaCols.col_color] = "white"; } loading_ = false; } void SambaPage::saveConfig() { std::vector dirs; for (auto &row : sambaStore->children()) { Glib::ustring p = row[sambaCols.col_path]; if (!p.empty()) dirs.push_back(p); } Backend::instance().saveSambaConfig( switchEnable.get_active(), dirs ); refreshUI(); Utils::show_message( dynamic_cast(get_root()), _("Configurazione Samba salvata correttamente."), Gtk::MessageType::INFO); } void SambaPage::refreshUI() { btnScan.set_sensitive( switchEnable.get_active() && sambaStore->children().size() > 0 ); } void SambaPage::onAddDir() { auto dlg = Gtk::make_managed(); Gtk::Dialog* dialog = dlg; dialog->set_title(""); dialog->set_modal(false); dialog->set_destroy_with_parent(true); dialog->set_default_size(480, 260); dialog->set_resizable(false); auto ctx = dialog->get_style_context(); ctx->add_class("app-dialog"); ctx->add_class("main-window"); ctx->add_class("app-window"); auto header = Gtk::make_managed(); header->set_show_title_buttons(false); header->set_decoration_layout(""); header->add_css_class("custom-headerbar"); auto close_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); close_box->set_margin_end(6); try { auto px = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 28, 28, true); auto btnCloseIcon = Gtk::make_managed(); btnCloseIcon->set_child(*Gtk::make_managed(px)); btnCloseIcon->add_css_class("header-button"); btnCloseIcon->signal_clicked().connect([dialog]() { dialog->close(); }); close_box->append(*btnCloseIcon); } catch (...) {} header->pack_end(*close_box); auto header_wrapper = Gtk::make_managed(Gtk::Orientation::VERTICAL); header_wrapper->append(*header); dialog->set_titlebar(*header_wrapper); auto box = Gtk::make_managed(Gtk::Orientation::VERTICAL, 14); box->set_margin(20); dialog->get_content_area()->append(*box); auto lblTitle = Gtk::make_managed(_("Aggiungi percorso SMB")); lblTitle->set_use_markup(true); lblTitle->set_halign(Gtk::Align::CENTER); lblTitle->add_css_class("title"); box->append(*lblTitle); auto entryUrl = Gtk::make_managed(); entryUrl->set_placeholder_text("smb://server/share/percorso"); auto entryUser = Gtk::make_managed(); entryUser->set_placeholder_text(_("Username (opzionale)")); auto entryPass = Gtk::make_managed(); entryPass->set_placeholder_text(_("Password (opzionale)")); entryPass->set_visibility(false); box->append(*entryUrl); box->append(*entryUser); box->append(*entryPass); auto infoAlert = Gtk::make_managed(_( "Nota: in caso di rilevamento malware su file remoti,\n" "sarà necessario intervenire manualmente per la rimozione.") ); infoAlert->set_use_markup(true); infoAlert->add_css_class("alert-info"); infoAlert->set_halign(Gtk::Align::CENTER); box->append(*infoAlert); auto btnRow = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 12); btnRow->set_halign(Gtk::Align::CENTER); box->append(*btnRow); auto btnOk = Gtk::make_managed(_("Aggiungi")); auto btnCancel = Gtk::make_managed(_("Annulla")); btnOk->add_css_class("btn-success"); btnCancel->add_css_class("btn-danger"); btnRow->append(*btnOk); btnRow->append(*btnCancel); btnOk->signal_clicked().connect([=, this]() { std::string url = entryUrl->get_text(); url = Backend::resolveSambaDir(url); std::replace(url.begin(), url.end(), '\\', '/'); if (url.size() > 7 && url.back() == '/') url.pop_back(); if (!Backend::isValidSMB(url)) { Utils::show_message( dynamic_cast(get_root()), _("Percorso SMB non valido:\n") + url, Gtk::MessageType::ERROR); return; } auto row = *(sambaStore->append()); row[sambaCols.col_path] = url; Backend::instance().saveSMBCredentials( url, entryUser->get_text(), entryPass->get_text() ); saveConfig(); dialog->close(); }); btnCancel->signal_clicked().connect([dialog]() { dialog->close(); }); dialog->present(); } void SambaPage::onRemoveDir() { auto sel = sambaView.get_selection(); if (!sel) return; auto it = sel->get_selected(); if (it) { sambaStore->erase(it); saveConfig(); } } void SambaPage::onScanNow() { Backend::instance().resetSambaStop(); auto active = std::make_shared>(true); auto dialog_open = std::make_shared(true); auto dialog = Gtk::make_managed(); Gtk::Dialog* dlg = dialog; dlg->set_title(""); dlg->set_modal(false); dlg->set_default_size(520, 420); dlg->set_resizable(false); auto ctx = dlg->get_style_context(); ctx->add_class("app-dialog"); ctx->add_class("main-window"); ctx->add_class("app-window"); auto header = Gtk::make_managed(); header->set_show_title_buttons(false); header->set_decoration_layout(""); header->add_css_class("custom-headerbar"); auto close_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); close_box->set_margin_end(6); try { auto px = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 28, 28, true); auto btnCloseIcon = Gtk::make_managed(); btnCloseIcon->set_child(*Gtk::make_managed(px)); btnCloseIcon->add_css_class("header-button"); btnCloseIcon->signal_clicked().connect([dlg]() { dlg->close(); }); close_box->append(*btnCloseIcon); } catch (...) {} header->pack_end(*close_box); auto header_wrapper = Gtk::make_managed(Gtk::Orientation::VERTICAL); header_wrapper->append(*header); dlg->set_titlebar(*header_wrapper); auto box = Gtk::make_managed(Gtk::Orientation::VERTICAL, 20); box->set_margin(20); dlg->get_content_area()->append(*box); auto lbl = Gtk::make_managed(_("Scansione Samba avviata…")); lbl->set_halign(Gtk::Align::CENTER); lbl->add_css_class("title"); box->append(*lbl); auto lblCurrent = Gtk::make_managed(_("File corrente: --")); lblCurrent->set_halign(Gtk::Align::CENTER); lblCurrent->add_css_class("subtitle"); box->append(*lblCurrent); auto prog = Gtk::make_managed(); prog->set_show_text(true); prog->set_text("0%"); prog->set_fraction(0.0); prog->set_halign(Gtk::Align::CENTER); prog->set_margin_bottom(10); box->append(*prog); auto fileList = Gtk::make_managed(); fileList->set_vexpand(true); fileList->add_css_class("scan-file-list"); auto scroll = Gtk::make_managed(); scroll->set_child(*fileList); scroll->set_vexpand(true); scroll->set_min_content_height(140); box->append(*scroll); auto buttonRow = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 20); buttonRow->set_halign(Gtk::Align::CENTER); box->append(*buttonRow); auto btnStop = Gtk::make_managed(_("Interrompi scansione")); btnStop->add_css_class("btn-danger"); auto btnClose = Gtk::make_managed(_("Chiudi")); btnClose->add_css_class("btn-success"); buttonRow->append(*btnStop); buttonRow->append(*btnClose); dlg->present(); btnStop->signal_clicked().connect([dlg, active]() { Backend::instance().requestStopSambaScan(); if (active->load()) { active->store(false); dlg->close(); } }); btnClose->signal_clicked().connect([dlg]() { dlg->close(); }); auto timeoutFiles = std::make_shared(); *timeoutFiles = Glib::signal_timeout().connect( [lbl, lblCurrent, prog, fileList, btnStop, dialog_open]() -> bool { if (!*dialog_open) return false; auto st = Backend::instance().get_current_scan_files(); if (!st.scan_active) { const size_t logCount = st.files.size(); const size_t infected = st.infected; std::string summary = Glib::ustring::compose( _("Scansione completata.\n" "File analizzati: %1\n" "File infetti: %2"), logCount, infected ); lbl->set_text(summary); lblCurrent->set_text(_("File corrente: --")); prog->set_fraction(1.0); prog->set_text("100%"); btnStop->set_sensitive(false); btnStop->remove_css_class("btn-danger"); btnStop->add_css_class("btn-grey"); while (auto row = fileList->get_row_at_index(0)) fileList->remove(*row); for (const auto& f : st.files) { auto lblLine = Gtk::make_managed(f); lblLine->set_halign(Gtk::Align::START); auto row = Gtk::make_managed(); row->set_child(*lblLine); fileList->append(*row); } return true; } lblCurrent->set_text(_("File corrente: ") + (st.current_scanning.empty() ? "--" : st.current_scanning)); prog->set_fraction(st.progress); prog->set_text(std::to_string(int(st.progress * 100)) + "%"); while (auto row = fileList->get_row_at_index(0)) fileList->remove(*row); for (const auto& f : st.files) { auto lblLine = Gtk::make_managed(f); lblLine->set_halign(Gtk::Align::START); auto row = Gtk::make_managed(); row->set_child(*lblLine); fileList->append(*row); } return true; }, 250 ); dlg->signal_close_request().connect( [dialog_open, timeoutFiles]() -> bool { *dialog_open = false; if (timeoutFiles->connected()) timeoutFiles->disconnect(); Backend::instance().requestStopSambaScan(); return false; }, false ); Backend::instance().samba_progress_callback = [lbl, dialog_open](const std::string& summary, float pct) { Glib::signal_idle().connect_once( [lbl, dialog_open, summary]() { if (!*dialog_open) return; lbl->set_text(summary); } ); }; std::thread([]() { auto dirs = Backend::instance().getSambaDirs(); for (const auto& d : dirs) Backend::instance().scanSambaDir(d); }).detach(); }