Skip to content
API Documentation
BastionGuard™
BastionGuard-ransomware-alert.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 "Quarantine.hpp"
24#include <gtkmm.h>
25#include <giomm.h>
26#include <glibmm/i18n.h>
27
28#include <iostream>
29#include <queue>
30#include <filesystem>
31#include <fstream>
32#include <clocale>
33#ifndef LOCALEDIR
34#error "LOCALEDIR non definita per questo target"
35#endif
36
37
38
39static std::string resolve_theme_css() {
40 const std::string conf =
41 Glib::get_home_dir() + "/.config/BastionGuard/theme.conf";
42 std::ifstream tf(conf);
43 std::string theme;
44 if (tf >> theme && theme == "dark")
45 return "BastionGuard-dark.css";
46 return "BastionGuard.css";
47}
48namespace fs = std::filesystem;
49
50class AlertWindow : public Gtk::Window {
51public:
52 AlertWindow(const std::string& file,
53 const std::string& family,
54 size_t pending = 0)
55 : filePath(file),
56 vbox(Gtk::Orientation::VERTICAL, 15),
57 titleLabel(_("⚠️ Attenzione: BastionGuard ha rilevato un'infezione")),
58 messageLabel(_("Famiglia: ") + family + "\n" + _("File: ") + file),
59 quarantineButton(_("Metti in quarantena")),
60 ignoreButton(_("Ignora")),
61 ignoreAllButton(_("Ignora tutti"))
62 {
63 set_title("");
64 set_default_size(620, 240);
65 set_resizable(false);
66 set_modal(true);
67
68 auto ctx = get_style_context();
69 ctx->add_class("app-dialog");
70 ctx->add_class("main-window");
71 ctx->add_class("app-window");
72
73 // CSS
74 try {
75 auto css = Gtk::CssProvider::create();
76 css->load_from_path(resource(resolve_theme_css()));
77 Gtk::StyleContext::add_provider_for_display(
78 Gdk::Display::get_default(),
79 css,
80 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
81 );
82 } catch (...) {}
83 auto header = Gtk::make_managed<Gtk::HeaderBar>();
84 header->set_show_title_buttons(false);
85 header->set_decoration_layout("");
86 header->add_css_class("custom-headerbar");
87
88 auto close_btn = Gtk::make_managed<Gtk::Button>();
89 try {
90 auto px = Gdk::Pixbuf::create_from_file(
91 resource("icon-close.png"), 28, 28, true
92 );
93 close_btn->set_child(*Gtk::make_managed<Gtk::Image>(px));
94 } catch (...) {}
95
96 close_btn->add_css_class("header-button");
97 close_btn->signal_clicked().connect([this]() {
98 close();
99 });
100
101 header->pack_end(*close_btn);
102 set_titlebar(*header);
103 titleLabel.set_halign(Gtk::Align::CENTER);
104 titleLabel.get_style_context()->add_class("title");
105
106 std::string iconPath =
107 (family == "OK" || family == "Clean" || family == "None")
108 ? "/usr/share/BastionGuard/data/icons/true.png"
109 : "/usr/share/BastionGuard/data/icons/false.png";
110
111 auto icon = Gtk::make_managed<Gtk::Picture>();
112 icon->set_halign(Gtk::Align::CENTER);
113
114 if (fs::exists(iconPath)) {
115 try {
116 auto pb = Gdk::Pixbuf::create_from_file(iconPath);
117 auto scaled = pb->scale_simple(68, 68, Gdk::InterpType::BILINEAR);
118 icon->set_paintable(Gdk::Texture::create_for_pixbuf(scaled));
119 } catch (...) {}
120 }
121
122 messageLabel.set_wrap(true);
123 messageLabel.set_justify(Gtk::Justification::CENTER);
124 messageLabel.set_halign(Gtk::Align::CENTER);
125
126 if (pending > 0) {
127 pendingLabel.set_text(
128 Glib::ustring::compose(_("🕒 %1 altri in attesa..."), pending)
129 );
130 pendingLabel.set_halign(Gtk::Align::CENTER);
131 }
132
133 quarantineButton.add_css_class("btn-danger");
134 ignoreButton.add_css_class("btn-success");
135 ignoreAllButton.add_css_class("btn-warning");
136
137 auto buttons = Gtk::make_managed<Gtk::Box>(
138 Gtk::Orientation::HORIZONTAL, 20
139 );
140 buttons->set_halign(Gtk::Align::CENTER);
141 buttons->append(quarantineButton);
142 buttons->append(ignoreButton);
143 buttons->append(ignoreAllButton);
144
145 set_child(vbox);
146 vbox.set_margin(25);
147 vbox.set_halign(Gtk::Align::CENTER);
148 vbox.append(titleLabel);
149 vbox.append(*icon);
150 vbox.append(messageLabel);
151 if (pending > 0) vbox.append(pendingLabel);
152 vbox.append(*buttons);
153
154 quarantineButton.signal_clicked().connect([this]() {
156 close();
157 });
158
159 ignoreButton.signal_clicked().connect([this]() {
160 close();
161 });
162
163 ignoreAllButton.signal_clicked().connect([this]() {
164 signal_ignore_all.emit();
165 close();
166 });
167 }
168
169 sigc::signal<void()> signal_ignore_all;
170
171private:
172 std::string filePath;
173
174 Gtk::Box vbox;
175 Gtk::Label titleLabel;
176 Gtk::Label messageLabel;
177 Gtk::Label pendingLabel;
178
179 Gtk::Button quarantineButton;
180 Gtk::Button ignoreButton;
181 Gtk::Button ignoreAllButton;
182
185 std::cout << _("[✔] File in quarantena: ") << filePath << "\n";
186 else
187 std::cerr << _("❌ Errore quarantena: ") << filePath << "\n";
188 }
189};
190
191const char introspection_xml[] =
192"<node>"
193" <interface name='org.BastionGuard.Ransomware.Alert'>"
194" <method name='ShowAlert'>"
195" <arg type='s' direction='in'/>"
196" <arg type='s' direction='in'/>"
197" </method>"
198" </interface>"
199"</node>";
200
201class AlertApp {
202public:
204 : app(Gtk::Application::create(
205 "org.BastionGuard.ransomware.alert"))
206 {
207 app->hold();
208 }
209
210 int run(int argc, char* argv[]) {
211 auto connection = Gio::DBus::Connection::get_sync(
212 Gio::DBus::BusType::SESSION
213 );
214
215 auto node_info =
216 Gio::DBus::NodeInfo::create_for_xml(introspection_xml);
217 auto iface_info =
218 node_info->lookup_interface(
219 "org.BastionGuard.Ransomware.Alert");
220
221 Gio::DBus::InterfaceVTable vtable(
222 sigc::mem_fun(*this, &AlertApp::on_method_call)
223 );
224
225 connection->register_object(
226 "/org/BastionGuard/ransomware/alert",
227 iface_info,
228 vtable
229 );
230 auto msg = Glib::VariantContainerBase::create_tuple({
231 Glib::Variant<Glib::ustring>::create(
232 "org.BastionGuard.Ransomware.Alert"),
233 Glib::Variant<guint32>::create(0)
234 });
235
236 connection->call_sync(
237 "/org/freedesktop/DBus",
238 "org.freedesktop.DBus",
239 "RequestName",
240 msg,
241 "org.freedesktop.DBus"
242 );
243
244 return app->run(argc, argv);
245 }
246
247private:
248 Glib::RefPtr<Gtk::Application> app;
249 std::queue<std::pair<std::string, std::string>> queue;
251
253 const Glib::RefPtr<Gio::DBus::Connection>&,
254 const Glib::ustring&,
255 const Glib::ustring&,
256 const Glib::ustring&,
257 const Glib::ustring& method,
258 const Glib::VariantContainerBase& params,
259 const Glib::RefPtr<Gio::DBus::MethodInvocation>& inv)
260 {
261 if (method == "ShowAlert") {
262 Glib::Variant<Glib::ustring> file, family;
263 params.get_child(file, 0);
264 params.get_child(family, 1);
265
266 queue.push({file.get(), family.get()});
267 inv->return_value({});
268
269 if (!current_window)
270 show_next();
271 }
272 }
273
274 void show_next() {
275 if (current_window || queue.empty())
276 return;
277
278 auto [file, family] = queue.front();
279 queue.pop();
280
282 file, family, queue.size());
283
284 app->add_window(*current_window);
285
286 current_window->signal_ignore_all.connect([this]() {
287 std::queue<std::pair<std::string, std::string>> empty;
288 std::swap(queue, empty);
289 });
290
291 current_window->signal_close_request().connect(
292 [this]() -> bool {
293 current_window = nullptr;
294 show_next();
295 return false;
296 },
297 false
298 );
299
300 current_window->present();
301 }
302};
303
304
305static bool load_lang_conf()
306{
307 const std::string lang_conf = Glib::get_home_dir() + "/.config/BastionGuard/lang.conf";
308 std::ifstream f(lang_conf);
309 if (!f.is_open())
310 return false;
311
312 std::string line;
313 bool any = false;
314
315 while (std::getline(f, line)) {
316 if (line.empty() || line[0] == '#')
317 continue;
318
319 auto pos = line.find('=');
320 if (pos == std::string::npos)
321 continue;
322
323 std::string key = line.substr(0, pos);
324 std::string val = line.substr(pos + 1);
325
326 // trim minimo (spazi attorno)
327 auto ltrim = [](std::string& s){
328 s.erase(0, s.find_first_not_of(" \t\r\n"));
329 };
330 auto rtrim = [](std::string& s){
331 s.erase(s.find_last_not_of(" \t\r\n") + 1);
332 };
333 ltrim(key); rtrim(key);
334 ltrim(val); rtrim(val);
335
336 if (!key.empty() && !val.empty()) {
337 setenv(key.c_str(), val.c_str(), 1);
338 any = true;
339 }
340 }
341
342 return any;
343}
344
345int main(int argc, char* argv[]) {
346 bool loaded = load_lang_conf();
347 std::setlocale(LC_ALL, "");
348 bindtextdomain("BastionGuard", LOCALEDIR);
349 bind_textdomain_codeset("BastionGuard", "UTF-8");
350 textdomain("BastionGuard");
351 if (loaded) {
352 std::cerr << _("[Lang] Config caricata da ~/.config/BastionGuard/lang.conf") << "\n";
353 } else {
354 std::cerr << _("[Lang] Nessuna config trovata, uso locale di sistema") << "\n";
355 }
356 AlertApp app;
357 return app.run(argc, argv);
358}
static std::string resolve_theme_css()
#define _(s)
int main(int argc, char *argv[])
static std::string resolve_theme_css()
static bool load_lang_conf()
const char introspection_xml[]
#define LOCALEDIR
void on_method_call(const Glib::RefPtr< Gio::DBus::Connection > &, const Glib::ustring &, const Glib::ustring &, const Glib::ustring &, const Glib::ustring &method, const Glib::VariantContainerBase &params, const Glib::RefPtr< Gio::DBus::MethodInvocation > &inv)
Glib::RefPtr< Gtk::Application > app
int run(int argc, char *argv[])
std::queue< std::pair< std::string, std::string > > queue
sigc::signal< void()> signal_ignore_all
AlertWindow(const std::string &file, const std::string &family, size_t pending=0)
static bool move(const std::string &file)
std::string resource(const std::string &rel)
Definition Resource.hpp:31