553 lines
15 KiB
C++
553 lines
15 KiB
C++
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <gtkmm.h>
|
|
#include <giomm.h>
|
|
#include <glibmm/main.h>
|
|
#include <glibmm/i18n.h>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <regex>
|
|
|
|
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::Box>(Gtk::Orientation::HORIZONTAL, 6);
|
|
|
|
auto lbl = Gtk::make_managed<Gtk::Label>(_("<b>Abilita scansione Samba</b>"));
|
|
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<Gtk::TreeViewColumn>(_("Percorso"));
|
|
auto cell = Gtk::make_managed<Gtk::CellRendererText>();
|
|
|
|
col->pack_start(*cell, true);
|
|
col->add_attribute(cell->property_text(), sambaCols.col_path);
|
|
sambaView.append_column(*col);
|
|
|
|
auto scroll = Gtk::make_managed<Gtk::ScrolledWindow>();
|
|
scroll->set_child(sambaView);
|
|
scroll->set_vexpand(true);
|
|
scroll->set_min_content_height(180);
|
|
append(*scroll);
|
|
|
|
|
|
auto btnBox = Gtk::make_managed<Gtk::Box>(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<std::string> 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<Gtk::Window*>(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>();
|
|
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<Gtk::HeaderBar>();
|
|
header->set_show_title_buttons(false);
|
|
header->set_decoration_layout("");
|
|
header->add_css_class("custom-headerbar");
|
|
|
|
auto close_box = Gtk::make_managed<Gtk::Box>(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<Gtk::Button>();
|
|
btnCloseIcon->set_child(*Gtk::make_managed<Gtk::Image>(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::Box>(Gtk::Orientation::VERTICAL);
|
|
header_wrapper->append(*header);
|
|
dialog->set_titlebar(*header_wrapper);
|
|
|
|
|
|
auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 14);
|
|
box->set_margin(20);
|
|
dialog->get_content_area()->append(*box);
|
|
|
|
auto lblTitle = Gtk::make_managed<Gtk::Label>(_("<b>Aggiungi percorso SMB</b>"));
|
|
lblTitle->set_use_markup(true);
|
|
lblTitle->set_halign(Gtk::Align::CENTER);
|
|
lblTitle->add_css_class("title");
|
|
box->append(*lblTitle);
|
|
|
|
auto entryUrl = Gtk::make_managed<Gtk::Entry>();
|
|
entryUrl->set_placeholder_text("smb://server/share/percorso");
|
|
|
|
auto entryUser = Gtk::make_managed<Gtk::Entry>();
|
|
entryUser->set_placeholder_text(_("Username (opzionale)"));
|
|
|
|
auto entryPass = Gtk::make_managed<Gtk::Entry>();
|
|
entryPass->set_placeholder_text(_("Password (opzionale)"));
|
|
entryPass->set_visibility(false);
|
|
|
|
box->append(*entryUrl);
|
|
box->append(*entryUser);
|
|
box->append(*entryPass);
|
|
|
|
auto infoAlert = Gtk::make_managed<Gtk::Label>(_(
|
|
"<i>Nota: in caso di rilevamento malware su file remoti,\n"
|
|
"sarà necessario intervenire manualmente per la rimozione.</i>")
|
|
);
|
|
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::Box>(Gtk::Orientation::HORIZONTAL, 12);
|
|
btnRow->set_halign(Gtk::Align::CENTER);
|
|
box->append(*btnRow);
|
|
|
|
auto btnOk = Gtk::make_managed<Gtk::Button>(_("Aggiungi"));
|
|
auto btnCancel = Gtk::make_managed<Gtk::Button>(_("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<Gtk::Window*>(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<std::atomic<bool>>(true);
|
|
auto dialog_open = std::make_shared<bool>(true);
|
|
|
|
|
|
auto dialog = Gtk::make_managed<Gtk::Dialog>();
|
|
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<Gtk::HeaderBar>();
|
|
header->set_show_title_buttons(false);
|
|
header->set_decoration_layout("");
|
|
header->add_css_class("custom-headerbar");
|
|
|
|
auto close_box = Gtk::make_managed<Gtk::Box>(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<Gtk::Button>();
|
|
btnCloseIcon->set_child(*Gtk::make_managed<Gtk::Image>(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::Box>(Gtk::Orientation::VERTICAL);
|
|
header_wrapper->append(*header);
|
|
dlg->set_titlebar(*header_wrapper);
|
|
|
|
|
|
auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 20);
|
|
box->set_margin(20);
|
|
dlg->get_content_area()->append(*box);
|
|
|
|
auto lbl = Gtk::make_managed<Gtk::Label>(_("Scansione Samba avviata…"));
|
|
lbl->set_halign(Gtk::Align::CENTER);
|
|
lbl->add_css_class("title");
|
|
box->append(*lbl);
|
|
|
|
auto lblCurrent = Gtk::make_managed<Gtk::Label>(_("File corrente: --"));
|
|
lblCurrent->set_halign(Gtk::Align::CENTER);
|
|
lblCurrent->add_css_class("subtitle");
|
|
box->append(*lblCurrent);
|
|
|
|
|
|
auto prog = Gtk::make_managed<Gtk::ProgressBar>();
|
|
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<Gtk::ListBox>();
|
|
fileList->set_vexpand(true);
|
|
fileList->add_css_class("scan-file-list");
|
|
|
|
auto scroll = Gtk::make_managed<Gtk::ScrolledWindow>();
|
|
scroll->set_child(*fileList);
|
|
scroll->set_vexpand(true);
|
|
scroll->set_min_content_height(140);
|
|
box->append(*scroll);
|
|
|
|
|
|
|
|
auto buttonRow = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 20);
|
|
buttonRow->set_halign(Gtk::Align::CENTER);
|
|
box->append(*buttonRow);
|
|
|
|
auto btnStop = Gtk::make_managed<Gtk::Button>(_("Interrompi scansione"));
|
|
btnStop->add_css_class("btn-danger");
|
|
|
|
auto btnClose = Gtk::make_managed<Gtk::Button>(_("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<sigc::connection>();
|
|
|
|
*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<Gtk::Label>(f);
|
|
lblLine->set_halign(Gtk::Align::START);
|
|
|
|
auto row = Gtk::make_managed<Gtk::ListBoxRow>();
|
|
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<Gtk::Label>(f);
|
|
lblLine->set_halign(Gtk::Align::START);
|
|
|
|
auto row = Gtk::make_managed<Gtk::ListBoxRow>();
|
|
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();
|
|
}
|