Skip to content
API Documentation
BastionGuard™
main.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 "MainWindow.hpp"
23#include "Backend.hpp"
24#include "StyleProvider.hpp"
25#include "TrayIcon.hpp"
28#include "SettingsWindow.hpp"
29#include "SettingsStore.hpp"
30
32
33#include <gtkmm/application.h>
34#include <glib/gi18n.h>
35#include <fstream>
36#include <iostream>
37#include <filesystem>
38#include <memory>
39#include <nlohmann/json.hpp>
40#include <cstdlib>
41#include <map>
42#include <vector>
43#include <system_error>
44#include <unistd.h>
45#include <pwd.h>
46#include <grp.h>
47
48namespace fs = std::filesystem;
49
50static void ensure_dir_exists(const std::string& dir) {
51 std::error_code ec;
52 fs::create_directories(dir, ec);
53 if (ec) {
54 std::cerr << Glib::ustring::compose(
55 _("[Lang] ⚠️ Errore creando %1: %2"), dir, ec.message()) << std::endl;
56 }
57}
58
59static std::string getenv_str(const char* k) {
60 const char* v = std::getenv(k);
61 return (v && *v) ? std::string(v) : std::string();
62}
63
64static bool icontains(std::string hay, std::string needle) {
65 auto tolower_ascii = [](unsigned char c){ return (c >= 'A' && c <= 'Z') ? char(c + 32) : char(c); };
66 for (auto& c : hay) c = tolower_ascii((unsigned char)c);
67 for (auto& c : needle) c = tolower_ascii((unsigned char)c);
68 return hay.find(needle) != std::string::npos;
69}
70
71
73 const std::string xdg_current = getenv_str("XDG_CURRENT_DESKTOP");
74 const std::string xdg_session = getenv_str("XDG_SESSION_DESKTOP");
75 const std::string desktop_sess = getenv_str("DESKTOP_SESSION");
76
77 auto is = [&](const char* s) {
78 return icontains(xdg_current, s) || icontains(xdg_session, s) || icontains(desktop_sess, s);
79 };
80
81 if (is("gnome") || is("xfce")) return true;
82
83 if (is("kde") || is("plasma")) return true;
84
85
86 if (is("cinnamon") || is("mate") || is("lxqt") || is("lxde")) return true;
87
88 return false;
89}
90
91
92static void ensure_lang_conf_exists(const std::string& lang_conf_path) {
93 namespace fs = std::filesystem;
94
95 std::error_code ec;
96 if (fs::exists(lang_conf_path, ec) && !ec) {
97 return;
98 }
99
100 std::string cfgdir = Glib::get_home_dir() + "/.config/BastionGuard";
101 ensure_dir_exists(cfgdir);
102
103 std::ofstream out(lang_conf_path, std::ios::out | std::ios::trunc);
104 if (!out.is_open()) {
105 std::cerr << "[Lang] ⚠️ Impossibile creare " << lang_conf_path << std::endl;
106 return;
107 }
108
109 out << "LANG=en_US.UTF-8\n";
110 out << "LC_ALL=en_US.UTF-8\n";
111 out << "LANGUAGE=en_US\n";
112 out.close();
113
114 std::cout << "[Lang] ✅ Creato lang.conf di default: " << lang_conf_path << std::endl;
115}
116
117
118static std::string resolve_path(const std::string& local, const std::string& installed) {
119 return std::filesystem::exists(local) ? local : installed;
120}
121
122
123static bool wizard_needed() {
124 namespace fs = std::filesystem;
125 std::string cfgdir = Glib::build_filename(Glib::get_user_config_dir(), "BastionGuard");
126 std::string cfgfile = cfgdir + "/config.json";
127 if (!fs::exists(cfgfile)) return true;
128
129 try {
130 std::ifstream in(cfgfile);
131 nlohmann::json j;
132 in >> j;
133 return !(j.contains("wizard_completed") && j["wizard_completed"].get<bool>() == true);
134 } catch (...) {
135 return true;
136 }
137}
138
139
141 const char* homeC = std::getenv("HOME");
142 if (!homeC) {
143 std::cerr << _("[Main] ⚠️ HOME non trovato, salto install wrapper") << "\n";
144 return;
145 }
146
147 std::string home = homeC;
148 std::string localBin = home + "/.local/bin";
149 std::string targetLink = localBin + "/xdg-open";
150 std::string realApp = "/usr/bin/BastionGuard-bankopener";
151
152 try {
153
154 std::error_code ec;
155 fs::create_directories(localBin, ec);
156 if (ec) {
157 std::cerr << Glib::ustring::compose(
158 _("[Main] ⚠️ Errore creando %1: %2"), localBin, ec.message()) << "\n";
159 return;
160 }
161
162
163 bool must_create = true;
164 if (fs::exists(targetLink)) {
165 std::error_code ec2;
166 fs::file_status st = fs::symlink_status(targetLink, ec2);
167 if (!ec2 && fs::is_symlink(st)) {
168 try {
169 std::string dest = fs::read_symlink(targetLink).string();
170 if (dest == realApp) must_create = false;
171 } catch (...) {
172 must_create = true;
173 }
174 } else {
175 try { fs::remove(targetLink); } catch (...) {}
176 must_create = true;
177 }
178 }
179
180 if (must_create) {
181 try {
182 if (fs::exists(targetLink)) fs::remove(targetLink);
183 fs::create_symlink(realApp, targetLink);
184 std::cout << Glib::ustring::compose(
185 _("[Main] 🔗 Creato symlink: %1 → %2"), targetLink, realApp) << "\n";
186 } catch (const std::exception& e) {
187 std::cerr << Glib::ustring::compose(
188 _("[Main] ⚠️ Impossibile creare symlink %1: %2"), targetLink, e.what()) << "\n";
189 }
190 } else {
191 std::cout << Glib::ustring::compose(
192 _("[Main] 🔁 Symlink già presente: %1 → %2"), targetLink, realApp) << "\n";
193 }
194
195
196 std::map<std::string, std::string> rc_to_line;
197 rc_to_line[home + "/.bashrc"] = "export PATH=\"$HOME/.local/bin:$PATH\"";
198 rc_to_line[home + "/.zshrc"] = "export PATH=\"$HOME/.local/bin:$PATH\"";
199 rc_to_line[home + "/.config/fish/config.fish"] = "set -gx PATH $HOME/.local/bin $PATH";
200 rc_to_line[home + "/.cshrc"] = "setenv PATH $HOME/.local/bin:$PATH";
201 rc_to_line[home + "/.tcshrc"] = "setenv PATH $HOME/.local/bin:$PATH";
202
203 for (const auto& [rcfile, line] : rc_to_line) {
204 bool present = false;
205 std::ifstream in(rcfile);
206 if (in.is_open()) {
207 std::string l;
208 while (std::getline(in, l)) {
209 if (l.find(line) != std::string::npos) { present = true; break; }
210 }
211 }
212 in.close();
213
214 if (!present) {
215 std::ofstream out(rcfile, std::ios::app);
216 if (out.is_open()) {
217 out << "\n# Added by BastionGuard Secure Browser\n" << line << "\n";
218 std::cout << Glib::ustring::compose(
219 _("[Main] ⚙️ Aggiornato %1"), rcfile) << "\n";
220 }
221 }
222 }
223
224 const char* oldPathC = std::getenv("PATH");
225 std::string oldPath = oldPathC ? oldPathC : "";
226 std::string newPath = localBin + ":" + oldPath;
227 setenv("PATH", newPath.c_str(), 1);
228
229 std::cout << _("[Main] ✅ PATH aggiornato per sessione corrente") << "\n";
230
231 } catch (const std::exception& ex) {
232 std::cerr << Glib::ustring::compose(
233 _("[Main] ⚠️ Errore ensureUserWrapperInstalled(): %1"), ex.what()) << "\n";
234 }
235}
236
237
238
239
240class ClamApp : public Gtk::Application {
241public:
242 ClamApp(bool start_minimized)
243 : Gtk::Application("org.BastionGuard.gui"),
244 start_minimized_(start_minimized)
245 {}
246
247protected:
248 void on_activate() override {
249 if (win_) {
250 win_->present();
251 return;
252 }
253
254 if (wizard_needed()) {
255 auto wiz = new BastionGuard::wizard::WizardWindow();
256 add_window(*wiz);
257
258 load_style();
259
260 wiz->signal_finished().connect([this, wiz]() {
261 wiz->hide();
262 delete wiz;
264 });
265
266 wiz->present();
267 return;
268 }
269
271
273
274 if (!start_minimized_ && win_) {
275 win_->present();
276 }
277 }
278
279private:
281 win_ = new MainWindow();
282 add_window(*win_);
283
284 load_style();
285
286 win_->set_resizable(false);
287 win_->set_default_size(600, 400);
288
289 bool tray_ok = should_enable_trayicon();
290
291
292 const std::string xdg_current = getenv_str("XDG_CURRENT_DESKTOP");
293 const std::string xdg_session = getenv_str("XDG_SESSION_DESKTOP");
294 const std::string desktop_sess = getenv_str("DESKTOP_SESSION");
295
296 auto is = [&](const char* s) {
297 return icontains(xdg_current, s) || icontains(xdg_session, s) || icontains(desktop_sess, s);
298 };
299
300 const bool is_gnome = is("gnome");
301
302#if BASTIONGUARD_HAS_SDBUS
303 if (is_gnome) {
304 tray_ok = true;
305 }
306#else
307 tray_ok = false;
308#endif
309
310 const bool hide_on_close = tray_ok;
311
312
313 if (hide_on_close && !holding_) {
314 hold();
315 holding_ = true;
316 }
317
318 win_->signal_close_request().connect(
319 [this, hide_on_close]() -> bool {
320 if (!hide_on_close)
321 return false;
322
323 if (win_)
324 win_->hide();
325
326 return true;
327 },
328 false
329 );
330
331
332 if (start_minimized_ && !tray_ok) {
333 start_minimized_ = false;
334 }
335
336 if (!start_minimized_) {
337 win_->present();
338 } else {
339 win_->hide();
340 }
341
342 if (tray_ok) {
343 tray_ = std::make_unique<TrayIcon>();
344
345 tray_->bind_ui_context();
346
347 const std::string icon_path =
348 resolve_path("data/logo.png", std::string(DATA_DIR) + "/logo.png");
349
350 tray_->set_icon_file(icon_path, 32);
351 tray_->set_tooltip(_("BastionGuard in esecuzione"));
352
353 tray_->set_on_activate([this]() {
354 // NB: questa lambda è già dispatchata sul main loop GTK da TrayIcon
355 // via g_main_context_invoke(ui_ctx_, ...). Tuttavia, sd_bus può
356 // invocarla all'interno di sd_bus_process: per evitare che gtk_widget_show
357 // riesca a generare un GMainContext annidato dentro lo stack di un
358 // callback sd_bus (che è la causa del crash SIGTRAP visto in
359 // g_main_context_new_with_flags -> g_log fatale), differiamo
360 // ulteriormente di un giro idle del main loop.
361 Glib::signal_idle().connect_once([this]() {
362 if (!win_) return;
363 try {
364 // IMPORTANTE: present() PRIMA di showDashboard().
365 // Se l'app è stata avviata con --minimized, alla prima Activate
366 // dalla tray la finestra non è ancora "realized": chiamare
367 // showDashboard() (che fa grab_focus su widget non realizzati)
368 // emette un g_critical che, con G_DEBUG=fatal-criticals,
369 // termina il processo. Realizziamo la finestra prima.
370 win_->present();
372 } catch (const std::exception& ex) {
373 std::cerr << "[ClamApp] tray activate: " << ex.what() << std::endl;
374 } catch (...) {
375 std::cerr << "[ClamApp] tray activate: errore sconosciuto" << std::endl;
376 }
377 });
378 });
379
380 tray_->set_on_settings([this]() {
381 Glib::signal_idle().connect_once([this]() {
382 if (!win_) return;
383 try {
385 } catch (const std::exception& ex) {
386 std::cerr << "[ClamApp] tray settings: " << ex.what() << std::endl;
387 } catch (...) {
388 std::cerr << "[ClamApp] tray settings: errore sconosciuto" << std::endl;
389 }
390 });
391 });
392
393 tray_->set_on_quit([this]() {
394 Glib::signal_idle().connect_once([this]() {
395 if (tray_) tray_->stop();
396 quit();
397 });
398 });
399
400 tray_->start();
401 }
402}
403
404private:
405 MainWindow* win_ = nullptr;
406 std::unique_ptr<TrayIcon> tray_;
408 bool holding_ = false;
409};
410
411
412
413int main(int argc, char *argv[]) {
414
415 std::string lang_conf = Glib::get_home_dir() + "/.config/BastionGuard/lang.conf";
416 ensure_lang_conf_exists(lang_conf);
417
418 std::ifstream f(lang_conf);
419 if (f.is_open()) {
420 std::string line;
421 while (std::getline(f, line)) {
422 auto pos = line.find('=');
423 if (pos != std::string::npos) {
424 std::string key = line.substr(0, pos);
425 std::string val = line.substr(pos + 1);
426 if (!key.empty() && !val.empty()) {
427 setenv(key.c_str(), val.c_str(), 1);
428 }
429 }
430 }
431 f.close();
432 std::cout << "[Lang] Config caricata da " << lang_conf << std::endl;
433 } else {
434 std::cerr << "[Lang] ⚠️ Impossibile aprire lang.conf, forzo inglese di default" << std::endl;
435 }
436
437 // Forza inglese se il file non ha impostato correttamente le variabili
438 const char* lang_env = std::getenv("LANG");
439 if (!lang_env || std::string(lang_env).empty()) {
440 setenv("LANG", "en_US.UTF-8", 1);
441 }
442
443 const char* lc_all_env = std::getenv("LC_ALL");
444 if (!lc_all_env || std::string(lc_all_env).empty()) {
445 setenv("LC_ALL", "en_US.UTF-8", 1);
446 }
447
448 const char* language_env = std::getenv("LANGUAGE");
449 if (!language_env || std::string(language_env).empty()) {
450 setenv("LANGUAGE", "en_US", 1);
451 }
452
453 const char* applied = setlocale(LC_ALL, "");
454 if (!applied) {
455 applied = setlocale(LC_ALL, "en_US.UTF-8");
456 }
457 if (!applied) {
458 applied = setlocale(LC_ALL, "en_US");
459 }
460 if (!applied) {
461 applied = setlocale(LC_ALL, "C.UTF-8");
462 }
463 if (!applied) {
464 applied = setlocale(LC_ALL, "C");
465 }
466
467 bindtextdomain("BastionGuard", LOCALEDIR);
468 bind_textdomain_codeset("BastionGuard", "UTF-8");
469 textdomain("BastionGuard");
470
471 bool do_update_phish = false;
472 bool do_update_sanesecurity = false;
473 bool start_minimized = false;
474
475 std::vector<char*> gtk_argv;
476 gtk_argv.push_back(argv[0]);
477
478 for (int i = 1; i < argc; ++i) {
479 std::string arg = argv[i];
480 if (arg == "--update-phish") {
481 do_update_phish = true;
482 } else if (arg == "--update-sanesecurity") {
483 do_update_sanesecurity = true;
484 } else if (arg == "--minimized") {
485 start_minimized = true;
486 } else {
487 gtk_argv.push_back(argv[i]);
488 }
489 }
490
492
493 if (do_update_phish) {
494 std::cout << "[BastionGuard] " << _("Avvio aggiornamento blacklist (modalità headless)...") << std::endl;
495 try {
497 if (ok) {
498 std::cout << "[BastionGuard] " << _("✔ Blacklist aggiornata con successo.") << std::endl;
499 return 0;
500 } else {
501 std::cerr << "[BastionGuard] " << _("❌ Errore durante l'aggiornamento blacklist.") << std::endl;
502 return 2;
503 }
504 } catch (const std::exception& ex) {
505 std::cerr << "[BastionGuard] " << _("Eccezione durante updatePhishLists(): ") << ex.what() << std::endl;
506 return 3;
507 }
508 }
509
510 if (do_update_sanesecurity) {
511 std::cout << _("[BastionGuard] Avvio aggiornamento SaneSecurity (modalità headless)...") << std::endl;
512 try {
514 if (ok) {
515 std::cout << _("[BastionGuard] ✔ Aggiornamento SaneSecurity completato.") << std::endl;
516 return 0;
517 } else {
518 std::cerr << _("[BastionGuard] ❌ Errore durante l'aggiornamento SaneSecurity.") << std::endl;
519 return 5;
520 }
521 } catch (const std::exception& ex) {
522 std::cerr << _("[BastionGuard] ❌ Eccezione durante updateSaneSecurity(): ")
523 << ex.what() << std::endl;
524 return 6;
525 }
526 }
527
528 {
529 struct passwd* pw = getpwnam("clamav");
530 if (!pw) {
531 std::cerr << _("[Main] ❌ Utente 'clamav' non trovato. Lo scanner userà l'utente normale.") << std::endl;
532 } else {
533 uid_t original_euid = geteuid();
534 gid_t original_egid = getegid();
535
536 setenv("BastionGuard_ORIG_EUID", std::to_string(original_euid).c_str(), 1);
537 setenv("BastionGuard_ORIG_EGID", std::to_string(original_egid).c_str(), 1);
538
539 initgroups("clamav", pw->pw_gid);
540
541 if (setegid(pw->pw_gid) != 0)
542 perror("[Main] setegid clamav");
543
544 if (seteuid(pw->pw_uid) != 0)
545 perror("[Main] seteuid clamav");
546
547 std::cout << _("[Main] 🔐 Processo configurato per lanciare i sottoprocessi come 'clamav'") << std::endl;
548
549 setegid(original_egid);
550 seteuid(original_euid);
551 }
552 }
553
554 int gtk_argc = static_cast<int>(gtk_argv.size());
555 char** gtk_argv_data = gtk_argv.data();
556
557 auto app = Glib::make_refptr_for_instance<ClamApp>(new ClamApp(start_minimized));
558 return app->run(gtk_argc, gtk_argv_data);
559}
#define _(s)
int main(int argc, char *argv[])
#define LOCALEDIR
bool updatePhishLists()
Definition Backend.cpp:1086
static Backend & instance()
Definition Backend.cpp:830
void on_activate() override
Definition main.cpp:248
void launch_main_window()
Definition main.cpp:280
std::unique_ptr< TrayIcon > tray_
Definition main.cpp:406
bool start_minimized_
Definition main.cpp:407
bool holding_
Definition main.cpp:408
MainWindow * win_
Definition main.cpp:405
ClamApp(bool start_minimized)
Definition main.cpp:242
void open_settings_window()
void showDashboard()
#define DATA_DIR
Definition Resource.hpp:28
void load_style()
static void ensure_dir_exists(const std::string &dir)
Definition main.cpp:50
static void ensure_lang_conf_exists(const std::string &lang_conf_path)
Definition main.cpp:92
static std::string getenv_str(const char *k)
Definition main.cpp:59
static void ensureUserWrapperInstalled()
Definition main.cpp:140
static bool icontains(std::string hay, std::string needle)
Definition main.cpp:64
static bool wizard_needed()
Definition main.cpp:123
static std::string resolve_path(const std::string &local, const std::string &installed)
Definition main.cpp:118
static bool should_enable_trayicon()
Definition main.cpp:72