Skip to content
API Documentation
BastionGuard™
UpdatePage.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 "UpdatePage.hpp"
22#include "SafeProcess.hpp"
23#include <filesystem>
24#include <unistd.h>
25#include <system_error>
26#include <cstring>
27#include "Resource.hpp"
28#include <glibmm/spawn.h>
29#include <glibmm/i18n.h>
30#include <giomm.h>
31#include <fstream>
32#include <thread>
33#include <utility>
34#include <random>
35#include <sstream>
36#include <vector>
37#include <thread>
38
39extern "C" {
40 #include <libsecret/secret.h>
41}
42
43static const SecretSchema BastionGuard_SCHEMA = {
44 "org.BastionGuard.source",
45 SECRET_SCHEMA_NONE,
46 {
47 { "secret_key", SECRET_SCHEMA_ATTRIBUTE_STRING },
48 { NULL, SECRET_SCHEMA_ATTRIBUTE_STRING }
49 }
50};
51
53: Gtk::Box(Gtk::Orientation::VERTICAL, 12),
54 updateButton(_("Aggiorna firme (freshclam)")),
56 addSourceButton(_("Aggiungi sorgente")),
57 saveSourcesButton(_("Salva sorgenti")),
58 sourcesLabel(_("Sorgenti aggiuntive"))
59{
60 buffer = Gtk::TextBuffer::create();
61 outputView.set_buffer(buffer);
62 outputView.set_editable(false);
63 outputView.set_monospace(true);
64
65 updateButton.signal_clicked().connect(sigc::mem_fun(*this, &UpdatePage::onUpdateClicked));
66
68 serviceSwitch.property_active().signal_changed().connect(sigc::mem_fun(*this, &UpdatePage::onServiceToggled));
69
70 auto serviceBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
71 auto serviceLabel = Gtk::make_managed<Gtk::Label>(_("Servizio automatico Freshclam:"));
72 serviceBox->append(*serviceLabel);
73 serviceBox->append(serviceSwitch);
74 auto saneSwitchLabel = Gtk::make_managed<Gtk::Label>(
75 _("Aggiorna automaticamente le firme SaneSecurity (ogni 2 ore):")
76 );
77 saneAutoSwitch.set_active(checkSaneTimer());
78 saneAutoSwitch.property_active().signal_changed().connect(
79 sigc::mem_fun(*this, &UpdatePage::onSaneAutoToggled)
80 );
81
82 auto saneBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
83 saneBox->append(*saneSwitchLabel);
84 saneBox->append(saneAutoSwitch);
85 append(*saneBox);
86 addSourceButton.signal_clicked().connect(sigc::mem_fun(*this, &UpdatePage::onAddSourceClicked));
87 saveSourcesButton.signal_clicked().connect(sigc::mem_fun(*this, &UpdatePage::onSaveSourcesClicked));
88
89 sourcesBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
91
92 auto sourcesFrame = Gtk::make_managed<Gtk::Frame>();
93 sourcesFrame->set_label(_("Sorgenti di aggiornamento aggiuntive"));
94 sourcesFrame->set_child(*sourcesBox);
95
96 auto controlBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
97 controlBox->append(addSourceButton);
98 controlBox->append(saveSourcesButton);
99 auto scrolled = Gtk::make_managed<Gtk::ScrolledWindow>();
100 scrolled->set_child(outputView);
101 scrolled->set_expand(true);
102
103 append(updateButton);
104 append(*serviceBox);
105 append(sourcesLabel);
106 append(*sourcesFrame);
107 append(*controlBox);
108 append(*scrolled);
109}
110
112 return bg_process::run({"/usr/libexec/bastionguard/bastionguard-service",
113 "--system", "is-active", "clamav-freshclam.service"}).ok();
114}
115
117 bool enable = serviceSwitch.get_active();
118 buffer->insert(buffer->end(), (enable ? _("Abilito") : _("Disabilito")) + std::string(" il servizio freshclam...\n"));
119 const auto result = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
120 "--system", enable ? "enable" : "disable", "--now", "clamav-freshclam.service"}, true);
121 buffer->insert(buffer->end(), result.output + (result.ok() ? "\n" : _("Errore modifica servizio.\n")));
122}
123
124static const std::vector<std::string> sane_files = {
125 "junk.ndb",
126 "jurlbl.ndb",
127 "jurlbla.ndb",
128 "jurlwr.ndb",
129 "jurlsb.ndb",
130 "lott.ndb",
131 "phishtank.ndb",
132 "scam.ndb",
133 "rogue.hdb",
134 "spamattach.hdb",
135 "winnow_extended.hdb",
136 "winnow_malware.hdb",
137 "winnow_phish_complete.ndb",
138 "bytecode.cdb"
139};
140
141
143 buffer->set_text(_("🔄 Avvio aggiornamento delle firme...\n"));
144 updateButton.set_sensitive(false);
145 std::thread([this, alive = alive_]() {
146 std::string out, err;
147 int exit_status = -1;
148
149 try {
150 logSafe(_("⏸️ Arresto servizio clamav-freshclam...\n"));
151 (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "stop", "clamav-freshclam.service"}).exit_code);
152 logSafe(_("🚀 Avvio aggiornamento con pkexec freshclam...\n"));
153 try {
154 Glib::spawn_command_line_sync("pkexec freshclam", &out, &err, &exit_status);
155 } catch (const Glib::Error& ex) {
156 logSafe(std::string(_("❌ Errore freshclam: ")) + ex.what() + "\n");
157 }
158 if (!out.empty())
159 logSafe("\n" + out + "\n");
160 if (!err.empty()) {
161 logSafe("\n--- STDERR ---\n");
162 logSafe(err + "\n");
163 }
164 logSafe(Glib::ustring::compose(_("\n🧾 Codice di uscita: %1\n"), exit_status));
165 logSafe(_("▶️ Riavvio del servizio clamav-freshclam...\n"));
166 (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "start", "clamav-freshclam.service"}).exit_code);
167
168 logSafe(_("\n🔽 Scarico firme aggiuntive dalle sorgenti configurate...\n"));
169 if (!alive->load()) return;
171
172 logSafe(_("🔐 Aggiornamento firme SaneSecurity...\n"));
173 if (!alive->load()) return;
175
176 logSafe(_("🔁 Ricarico clamd (clamav-daemon)...\n"));
177 int reload_status = (bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service", "--system", "try-reload-or-restart", "clamav-daemon.service"}).exit_code);
178 if (reload_status != 0) {
179 logSafe(_("⚠ Reload clamav-daemon fallito (codice non zero).\n"));
180 }
181 Glib::signal_idle().connect_once([this, alive]() {
182 BastionGuard::if_alive(alive, [this]() {
183 buffer->insert(buffer->end(), _("✅ Aggiornamento completato con successo.\n"));
184 updateButton.set_sensitive(true);
185 });
186 });
187
188 } catch (const Glib::Error& ex) {
189 Glib::signal_idle().connect_once([this, alive, ex]() {
190 BastionGuard::if_alive(alive, [this, &ex]() {
191 buffer->insert(buffer->end(),
192 Glib::ustring::compose(_("❌ Errore durante l'aggiornamento: %1\n"), ex.what()));
193 updateButton.set_sensitive(true);
194 });
195 });
196 }
197
198 }).detach();
199}
200
202 auto alive = alive_;
203 buffer->insert(buffer->end(), _("\n🔽 Scarico firme aggiuntive dalle sorgenti configurate...\n"));
204
205 // Snapshot the GTK widget values on the UI thread before starting a worker.
206 // A deferred idle callback here would capture a dead stack reference and
207 // race with the worker's copy of the source list.
208 std::vector<SourceItem> sources;
209 for (auto child : sourcesBox->get_children()) {
210 auto frame = dynamic_cast<Gtk::Frame*>(child);
211 if (!frame) continue;
212 auto section = dynamic_cast<Gtk::Box*>(frame->get_child());
213 if (!section) continue;
214 auto section_children = section->get_children();
215 if (section_children.size() < 2) continue;
216 auto revealer = dynamic_cast<Gtk::Revealer*>(section_children[1]);
217 if (!revealer) continue;
218 auto inner = dynamic_cast<Gtk::Box*>(revealer->get_child());
219 if (!inner) continue;
220 auto entries = inner->get_children();
221 if (entries.size() < 4) continue;
222 auto name = dynamic_cast<Gtk::Entry*>(entries[0]);
223 auto url = dynamic_cast<Gtk::Entry*>(entries[1]);
224 auto auth = dynamic_cast<Gtk::CheckButton*>(entries[2]);
225 auto username = dynamic_cast<Gtk::Entry*>(entries[3]);
226 if (!name || !url || !auth || !username) continue;
227
228 SourceItem source;
229 source.name = name->get_text();
230 source.url = url->get_text();
231 source.requires_auth = auth->get_active();
232 source.username = username->get_text();
233 if (!source.url.empty()) sources.push_back(std::move(source));
234 }
235
236 if (sources.empty()) {
237 buffer->insert(buffer->end(), _("Nessuna sorgente aggiuntiva configurata.\n"));
238 return;
239 }
240
241 std::thread([this, alive, sources = std::move(sources)]() {
242 bool all_ok = true;
243 for (const auto& src : sources) {
244 // Never allow an untrusted URL to select a local-file or other
245 // potentially unsafe curl protocol. No URL reaches a shell.
246 if (src.url.rfind("https://", 0) != 0) {
247 all_ok = false;
248 Glib::signal_idle().connect_once([this, alive, url = src.url]() {
249 BastionGuard::if_alive(alive, [this, &url]() {
250 buffer->insert(buffer->end(), _("⚠ Sorgente non HTTPS ignorata: ") + url + "\n");
251 });
252 });
253 continue;
254 }
255
256 std::string pw;
257 if (src.requires_auth)
258 pw = lookupPassword("BastionGuard:" + src.name);
259
260 std::vector<std::string> argv = {
261 "curl", "-f", "--proto", "=https", "--proto-redir", "=https"
262 };
263 if (src.requires_auth) {
264 argv.push_back("-u");
265 argv.push_back(src.username + ":" + pw);
266 }
267 argv.insert(argv.end(), {
268 "-O", "--output-dir", "/var/lib/clamav",
269 "--url", src.url
270 });
271
272 std::string out, err;
273 int status = -1;
274 try {
275 Glib::spawn_sync("", argv, Glib::SpawnFlags::SEARCH_PATH,
276 {}, &out, &err, &status);
277 } catch (const Glib::Error& ex) {
278 err = ex.what();
279 }
280 const bool ok = status == 0;
281 if (!ok) all_ok = false;
282 if (!alive->load()) return;
283
284 Glib::signal_idle().connect_once([this, alive, src, out, err, ok]() {
285 BastionGuard::if_alive(alive, [this, &src, &out, &err, ok]() {
286 buffer->insert(buffer->end(),
287 (ok ? _("⬇️ Scarico da: ") : _("❌ Download fallito: ")) + src.url + "\n");
288 if (!out.empty()) buffer->insert(buffer->end(), out + "\n");
289 if (!err.empty()) buffer->insert(buffer->end(), err + "\n");
290 });
291 });
292 }
293
294 Glib::signal_idle().connect_once([this, alive, all_ok]() {
295 BastionGuard::if_alive(alive, [this, all_ok]() {
296 buffer->insert(buffer->end(), all_ok
297 ? _("\n✅ Aggiornamento firme extra completato.\n")
298 : _("\n⚠ Aggiornamento firme extra incompleto: controllare gli errori.\n"));
299 });
300 });
301 }).detach();
302}
303
304
308
310 auto frame = Gtk::make_managed<Gtk::Frame>();
311 frame->set_hexpand(true);
312 frame->set_margin(4);
313 frame->set_valign(Gtk::Align::START);
314
315 auto headerBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
316 auto titleLabel = Gtk::make_managed<Gtk::Label>(
317 src.name.empty() ? _("➕ Nuova sorgente") : ("📦 " + src.name));
318 titleLabel->set_xalign(0);
319 titleLabel->add_css_class(src.name.empty() ? "suggested-action" : "highlight");
320
321 auto expandIcon = Gtk::make_managed<Gtk::Label>("▼");
322 expandIcon->set_margin_start(6);
323 expandIcon->set_css_classes({ "expand-icon" });
324
325 headerBox->append(*titleLabel);
326 headerBox->append(*expandIcon);
327 headerBox->set_margin(6);
328 headerBox->set_css_classes({ "source-header" });
329
330 auto innerBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
331 innerBox->set_css_classes({ "revealer-content" });
332
333
334 auto nameEntry = Gtk::make_managed<Gtk::Entry>();
335 nameEntry->set_placeholder_text(_("Nome sorgente (es. Sanesecurity)"));
336 nameEntry->set_text(src.name);
337
338 auto urlEntry = Gtk::make_managed<Gtk::Entry>();
339 urlEntry->set_placeholder_text(_("https://example.com/firme.cvd"));
340 urlEntry->set_text(src.url);
341
342 auto authSwitch = Gtk::make_managed<Gtk::CheckButton>(_("_Richiede autenticazione"), true);
343 authSwitch->set_active(src.requires_auth);
344
345 auto usernameEntry = Gtk::make_managed<Gtk::Entry>();
346 usernameEntry->set_placeholder_text(_("Username"));
347 usernameEntry->set_text(src.username);
348
349 auto passwordEntry = Gtk::make_managed<Gtk::PasswordEntry>();
350 passwordEntry->property_placeholder_text() = _("Password");
351 passwordEntry->set_text(lookupPassword("BastionGuard:" + src.name));
352
353 auto buttonBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
354 auto testButton = Gtk::make_managed<Gtk::Button>(_("_Test connessione"), true);
355 auto saveButton = Gtk::make_managed<Gtk::Button>(_("_Salva sorgente"), true);
356 auto deleteButton = Gtk::make_managed<Gtk::Button>(_("_Elimina sorgente"), true);
357 deleteButton->add_css_class("btn-danger");
358
359 buttonBox->append(*testButton);
360 buttonBox->append(*saveButton);
361 buttonBox->append(*deleteButton);
362
363 innerBox->append(*nameEntry);
364 innerBox->append(*urlEntry);
365 innerBox->append(*authSwitch);
366 innerBox->append(*usernameEntry);
367 innerBox->append(*passwordEntry);
368 innerBox->append(*buttonBox);
369
370 auto revealer = Gtk::make_managed<Gtk::Revealer>();
371 revealer->set_transition_type(Gtk::RevealerTransitionType::SLIDE_DOWN);
372 revealer->set_transition_duration(200);
373 revealer->set_reveal_child(false);
374 revealer->set_child(*innerBox);
375
376 auto sectionBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
377 sectionBox->append(*headerBox);
378 sectionBox->append(*revealer);
379 frame->set_child(*sectionBox);
380 sourcesBox->append(*frame);
381
382 auto click = Gtk::GestureClick::create();
383 click->signal_pressed().connect([this, revealer, expandIcon](int n_press, double, double) {
384 if (n_press != 1) return;
385
386 bool currentlyOpen = revealer->get_reveal_child();
387
388 for (auto child : sourcesBox->get_children()) {
389 if (auto f = dynamic_cast<Gtk::Frame*>(child)) {
390 if (auto box = dynamic_cast<Gtk::Box*>(f->get_child())) {
391 auto children = box->get_children();
392 if (children.size() >= 2) {
393 if (auto r = dynamic_cast<Gtk::Revealer*>(children[1])) {
394 r->set_reveal_child(false);
395 if (auto hBox = dynamic_cast<Gtk::Box*>(children[0])) {
396 for (auto& c : hBox->get_children()) {
397 if (auto lbl = dynamic_cast<Gtk::Label*>(c)) {
398 if (lbl->get_text() == "▲" || lbl->get_text() == "▼")
399 lbl->set_text("▼");
400 }
401 }
402 }
403 }
404 }
405 }
406 }
407 }
408
409 revealer->set_reveal_child(!currentlyOpen);
410 expandIcon->set_text(!currentlyOpen ? "▲" : "▼");
411 if (auto header = dynamic_cast<Gtk::Widget*>(expandIcon->get_parent())) {
412 if (!currentlyOpen)
413 header->add_css_class("open");
414 else
415 header->remove_css_class("open");
416 }
417
418 });
419 headerBox->add_controller(click);
420
421 // --- Aggiorna titolo dinamico ---
422 nameEntry->signal_changed().connect([titleLabel, nameEntry]() {
423 std::string newName = nameEntry->get_text();
424 if (newName.empty()) {
425 titleLabel->set_text(_("➕ Nuova sorgente"));
426 titleLabel->remove_css_class("highlight");
427 titleLabel->add_css_class("suggested-action");
428 } else {
429 titleLabel->set_text("📦 " + newName);
430 titleLabel->remove_css_class("suggested-action");
431 titleLabel->add_css_class("highlight");
432 }
433 });
434
435 testButton->signal_clicked().connect([this, nameEntry, urlEntry, authSwitch, usernameEntry, passwordEntry]() {
436 std::string pw = passwordEntry->get_text();
437 if (!pw.empty())
438 storePassword("BastionGuard:" + nameEntry->get_text(), pw);
439 testConnection(nameEntry->get_text(), urlEntry->get_text(),
440 authSwitch->get_active(), usernameEntry->get_text());
441 });
442
443 saveButton->signal_clicked().connect([this, nameEntry, passwordEntry]() {
444 std::string key = "BastionGuard:" + nameEntry->get_text();
445 storePassword(key, passwordEntry->get_text());
446 buffer->insert(buffer->end(), _("💾 Sorgente salvata.\n"));
447 });
448
449 deleteButton->signal_clicked().connect([this, frame]() {
450 sourcesBox->remove(*frame);
451 buffer->insert(buffer->end(), _("❌ Sorgente rimossa.\n"));
452 });
453 for (auto child : sourcesBox->get_children()) {
454 if (auto f = dynamic_cast<Gtk::Frame*>(child)) {
455 if (auto box = dynamic_cast<Gtk::Box*>(f->get_child())) {
456 auto children = box->get_children();
457 if (children.size() >= 2) {
458 if (auto r = dynamic_cast<Gtk::Revealer*>(children[1])) {
459 r->set_reveal_child(false);
460 }
461 }
462 }
463 }
464 }
465
466 revealer->set_reveal_child(true);
467 expandIcon->set_text("▲");
468}
469
470
471
473 saveSources();
474 buffer->insert(buffer->end(), _("Sorgenti salvate in ~/.config/BastionGuard/sources.conf\n"));
475}
476
478 std::string cfgPath = Glib::get_home_dir() + "/.config/BastionGuard";
479 auto dirFile = Gio::File::create_for_path(cfgPath);
480 if (!dirFile->query_exists()) {
481 dirFile->make_directory_with_parents();
482 }
483
484 std::ofstream file(cfgPath + "/sources.conf");
485 if (!file) return;
486
487 for (auto child : sourcesBox->get_children()) {
488 if (auto frame = dynamic_cast<Gtk::Frame*>(child)) {
489 auto inner = dynamic_cast<Gtk::Box*>(frame->get_child());
490 if (!inner) continue;
491
492 auto entries = inner->get_children();
493 if (entries.size() < 5) continue;
494
495 auto nameEntry = dynamic_cast<Gtk::Entry*>(entries[0]);
496 auto urlEntry = dynamic_cast<Gtk::Entry*>(entries[1]);
497 auto authSwitch= dynamic_cast<Gtk::CheckButton*>(entries[2]);
498 auto userEntry = dynamic_cast<Gtk::Entry*>(entries[3]);
499
500 file << nameEntry->get_text() << "|"
501 << urlEntry->get_text() << "|"
502 << (authSwitch->get_active() ? "1" : "0") << "|"
503 << userEntry->get_text() << "\n";
504 }
505 }
506}
507
509 std::ifstream file(Glib::get_home_dir() + "/.config/BastionGuard/sources.conf");
510 std::string line;
511 while (std::getline(file, line)) {
512 SourceItem src;
513 std::stringstream ss(line);
514 std::getline(ss, src.name, '|');
515 std::getline(ss, src.url, '|');
516 std::string auth;
517 std::getline(ss, auth, '|');
518 src.requires_auth = (auth == "1");
519 std::getline(ss, src.username, '|');
520 createSourceRow(src);
521 }
522}
523
524void UpdatePage::testConnection(const std::string& name, const std::string& url, bool auth, const std::string& user) {
525 if (url.empty()) {
526 buffer->insert(buffer->end(), _("⚠ Nessun URL specificato per ") + name + "\n");
527 return;
528 }
529
530 buffer->insert(buffer->end(), _("🔍 Test connessione a ") + url + "...\n");
531 std::string out, err;
532
533 std::vector<std::string> argv = {
534 "curl", "-I", "-s", "-o", "/dev/null", "-w", "%{http_code}"
535 };
536 if (auth) {
537 std::string pw = lookupPassword("BastionGuard:" + name);
538 if (pw.empty()) {
539 buffer->insert(buffer->end(), _("⚠ Nessuna password trovata per ") + name + "\n");
540 return;
541 }
542 argv.push_back("-u");
543 argv.push_back(user + ":" + pw);
544 }
545 argv.push_back("--url");
546 argv.push_back(url);
547
548 int status = -1;
549 Glib::spawn_sync("", argv, Glib::SpawnFlags::SEARCH_PATH,
550 {}, &out, &err, &status);
551 if (!err.empty())
552 buffer->insert(buffer->end(), _("Errore: ") + err + "\n");
553
554 if (!out.empty()) {
555 buffer->insert(buffer->end(), _("Risposta HTTP: ") + out + "\n");
556 if (out.find("200") != std::string::npos)
557 buffer->insert(buffer->end(), _("✅ Connessione riuscita.\n"));
558 else
559 buffer->insert(buffer->end(), _("❌ Connessione fallita o accesso negato.\n"));
560 }
561}
562
563bool UpdatePage::storePassword(const std::string& key, const std::string& password) {
564 GError *error = nullptr;
565 gboolean res = secret_password_store_sync(
566 &BastionGuard_SCHEMA, SECRET_COLLECTION_DEFAULT,
567 ("BastionGuard source " + key).c_str(),
568 password.c_str(),
569 nullptr, &error,
570 "secret_key", key.c_str(),
571 nullptr
572 );
573 if (error) g_error_free(error);
574 return res == TRUE;
575}
576
577std::string UpdatePage::lookupPassword(const std::string& key) {
578 GError *error = nullptr;
579 gchar *pw = secret_password_lookup_sync(
580 &BastionGuard_SCHEMA, nullptr, &error,
581 "secret_key", key.c_str(),
582 nullptr
583 );
584 std::string result;
585 if (pw) {
586 result = pw;
587 secret_password_free(pw);
588 }
589 if (error) g_error_free(error);
590 return result;
591}
592
594 std::random_device rd;
595 std::mt19937 gen(rd());
596 std::uniform_int_distribution<> dis(0, 15);
597 std::ostringstream oss;
598 for (int i = 0; i < 12; ++i) oss << std::hex << dis(gen);
599 return "BastionGuard:" + oss.str();
600}
601
603 // Invalida i lambda async in volo.
604 if (alive_) alive_->store(false, std::memory_order_release);
605
606 if (sourcesNotebook) {
607 sourcesNotebook->set_visible(false);
608 sourcesNotebook = nullptr;
609 }
610}
611
613 auto push = [this, alive = alive_](const std::string& msg) {
614 Glib::signal_idle().connect_once([this, alive, msg]() {
615 BastionGuard::if_alive(alive, [this, &msg]() {
616 buffer->insert(buffer->end(), msg);
617 });
618 });
619 };
620
621 push(_("🌐 Aggiornamento database SaneSecurity (mirror sicuro)...\n"));
622
623 namespace fs = std::filesystem;
624 // Keep the sync unprivileged. mkdtemp ensures an exclusive 0700 directory.
625 char tmpl[] = "/tmp/bastionguard-sanesecurity-XXXXXX";
626 char* created = ::mkdtemp(tmpl);
627 if (!created) {
628 push(_("❌ Impossibile creare la directory temporanea di staging.\n"));
629 return;
630 }
631 const fs::path staging(created);
632 struct StagingGuard {
633 fs::path path;
634 ~StagingGuard() { std::error_code ec; fs::remove_all(path, ec); }
635 } cleanup{staging};
636
637 const auto sync = bg_process::run({"rsync", "-av", "--",
638 "rsync://rsync.sanesecurity.net/sanesecurity/", staging.string() + "/"},
639 true, 4U * 1024U * 1024U);
640 if (!sync.ok()) {
641 push(_("❌ Download SaneSecurity fallito. Nessun database modificato.\n"));
642 return;
643 }
644
645 const std::vector<std::string> extensions = {
646 ".ndb", ".hdb", ".ldb", ".yar", ".yara", ".ign2", ".txt"
647 };
648 std::size_t installed = 0;
649 std::error_code ec;
650 for (fs::directory_iterator it(staging, fs::directory_options::skip_permission_denied, ec),
651 end; !ec && it != end; it.increment(ec)) {
652 const fs::path path = it->path();
653 // Do not follow symlinks, directories, or special devices from a mirror.
654 if (!it->is_regular_file(ec) || it->is_symlink(ec)) { ec.clear(); continue; }
655 const std::string extension = path.extension().string();
656 if (std::find(extensions.begin(), extensions.end(), extension) == extensions.end())
657 continue;
658 const auto install = bg_process::run({"pkexec", "/usr/bin/install", "-m", "0644", "--",
659 path.string(), (fs::path("/var/lib/clamav") / path.filename()).string()}, true);
660 if (install.ok()) ++installed;
661 else push(_("⚠ Installazione firma fallita: ") + path.filename().string() + "\n");
662 }
663 if (installed == 0) push(_("⚠ Nessuna firma SaneSecurity installata.\n"));
664 else push(_("📦 Firme SaneSecurity installate: ") + std::to_string(installed) + "\n");
665}
666
667
668
669
670void UpdatePage::logSafe(const std::string& msg) {
671 auto alive = alive_;
672 Glib::signal_idle().connect_once([this, alive, msg]() {
673 BastionGuard::if_alive(alive, [this, &msg]() {
674 buffer->insert(buffer->end(), msg);
675 });
676 });
677}
678
680 std::string out;
681 int status = -1;
682
683 try {
684 Glib::spawn_command_line_sync(
685 "/usr/libexec/bastionguard/bastionguard-service --system is-enabled bastionguard-sanesecurity.timer",
686 &out, nullptr, &status
687 );
688 return out.find("enabled") != std::string::npos;
689 }
690 catch (...) {
691 return false;
692 }
693}
694
696 bool enable = saneAutoSwitch.get_active();
697
698 buffer->insert(buffer->end(),
699 enable ?
700 _("⏱️ Attivo aggiornamento automatico SaneSecurity ogni 2 ore...\n" ):
701 _("⏸️ Disattivo aggiornamento automatico SaneSecurity...\n")
702 );
703
704 const auto result = bg_process::run({"pkexec", "/usr/libexec/bastionguard/bastionguard-service",
705 "--system", enable ? "enable" : "disable", "--now", "bastionguard-sanesecurity.timer"}, true);
706 buffer->insert(buffer->end(), result.output + (result.ok() ? "\n" : _("Errore modifica timer.\n")));
707}
#define _(s)
static const std::vector< std::string > sane_files
static const SecretSchema BastionGuard_SCHEMA
std::string lookupPassword(const std::string &key)
void updateFromExtraSources()
Gtk::Button addSourceButton
BastionGuard::AliveFlag alive_
bool checkServiceStatus()
void onSaneAutoToggled()
void onSaveSourcesClicked()
Gtk::Box * sourcesBox
void createSourceRow(const SourceItem &src={})
Gtk::Button saveSourcesButton
Glib::RefPtr< Gtk::TextBuffer > buffer
void onAddSourceClicked()
void logSafe(const std::string &msg)
void updateSaneSecurity()
bool checkSaneTimer()
Gtk::Label sourcesLabel
void saveSources()
std::string generateSecretKey()
void onServiceToggled()
bool storePassword(const std::string &key, const std::string &password)
Gtk::TextView outputView
Gtk::Switch serviceSwitch
Gtk::Notebook * sourcesNotebook
Gtk::Switch saneAutoSwitch
void loadSources()
void onUpdateClicked()
Gtk::Button updateButton
void testConnection(const std::string &name, const std::string &url, bool auth, const std::string &user)
void if_alive(const AliveFlag &alive, Fn &&fn)
Result run(const std::vector< std::string > &args, bool merge_stderr=false, std::size_t limit=8U *1024U *1024U, const std::map< std::string, std::string > &environment={}, const std::function< bool(const std::string &)> &on_line={})
std::string url
std::string username
bool requires_auth
std::string name