Skip to content
API Documentation
BastionGuard™
BastionGuard-secure-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 <gtkmm.h>
22#include <giomm.h>
23#include <glib/gi18n.h>
24
25#include <csignal>
26#include <iostream>
27#include <memory>
28#include <unistd.h>
29
30#include <fstream>
31#include <filesystem>
32#include <nlohmann/json.hpp>
33
34#include <glibmm.h>
35#include <clocale>
36#include <cstring>
37#include "StyleProvider.hpp"
38#include "Resource.hpp"
39#include "dns_secure.hpp"
40#ifndef LOCALEDIR
41#error "LOCALEDIR non definita per questo target"
42#endif
43
44namespace fs = std::filesystem;
45using json = nlohmann::json;
46
47class SecureGuiWindow : public Gtk::Window {
48public:
50 set_title("");
51 set_resizable(false);
52 set_default_size(640, 300);
54
55 auto main_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
56 set_child(*main_box);
57
58 auto sidebar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
59 sidebar->set_size_request(240, -1);
60 sidebar->get_style_context()->add_class("sidebar");
61
62 {
63 auto header = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
64 header->get_style_context()->add_class("sidebar-header");
65
66 try {
67 auto pixbuf = Gdk::Pixbuf::create_from_file(resource("logo-sidebar.png"), 96, 96, true);
68 auto logo = Gtk::make_managed<Gtk::Image>(pixbuf);
69 header->append(*logo);
70 } catch (...) {
71 std::cerr << _("⚠ Impossibile caricare data/logo-sidebar.png") << "\n";
72 }
73
74 auto lbl = Gtk::make_managed<Gtk::Label>("BastionGuard");
75 lbl->set_halign(Gtk::Align::START);
76 lbl->get_style_context()->add_class("sidebar-title");
77 header->append(*lbl);
78
79 sidebar->append(*header);
80 }
81
82 auto spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
83 spacer->set_vexpand(true);
84 sidebar->append(*spacer);
85
86 main_box->append(*sidebar);
87 auto content_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
88 content_box->set_margin(24);
89 content_box->set_hexpand(true);
90 content_box->get_style_context()->add_class("content");
91 main_box->append(*content_box);
92
93 auto label = Gtk::make_managed<Gtk::Label>(
94 _("Seleziona un sito sicuro per i pagamenti/e-commerce:")
95 );
96 label->set_wrap(true);
97 label->set_halign(Gtk::Align::START);
98 content_box->append(*label);
99
101 if (domains_.empty())
102 domains_.push_back("paypal.com");
103
104 auto string_list = Gtk::StringList::create(domains_);
105 dropdown_.set_model(string_list);
106 dropdown_.set_selected(0);
107 content_box->append(dropdown_);
108
109 auto hint = Gtk::make_managed<Gtk::Label>(
110 _("La lista proviene da ~/.config/BastionGuard/payments.json (chiave: \"list\").")
111 );
112 hint->set_halign(Gtk::Align::START);
113 hint->get_style_context()->add_class("hint");
114 content_box->append(*hint);
115
116 auto buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
117 buttons->set_halign(Gtk::Align::END);
118
119 btn_cancel_.set_label(_("Annulla"));
120 btn_cancel_.signal_clicked().connect(sigc::mem_fun(*this, &SecureGuiWindow::on_cancel_clicked));
121 buttons->append(btn_cancel_);
122
123 btn_open_.set_label(_("Apri in Secure Browser"));
124 btn_open_.signal_clicked().connect(sigc::mem_fun(*this, &SecureGuiWindow::on_open_clicked));
125 buttons->append(btn_open_);
126
127 content_box->append(*buttons);
128 }
129
130protected:
131 bool on_close_request() override {
133 return false;
134 }
135
136private:
137 Gtk::DropDown dropdown_;
138 std::vector<Glib::ustring> domains_;
139
140 Gtk::Button btn_open_;
141 Gtk::Button btn_cancel_;
142 Glib::RefPtr<Gio::Subprocess> sandbox_proc_;
143
145 auto header = Gtk::make_managed<Gtk::HeaderBar>();
146 header->set_show_title_buttons(false);
147 header->set_decoration_layout("");
148 header->get_style_context()->add_class("custom-headerbar");
149
150 auto title_label = Gtk::make_managed<Gtk::Label>("BastionGuard Secure Browser");
151 title_label->get_style_context()->add_class("header-title");
152 title_label->set_halign(Gtk::Align::CENTER);
153 title_label->set_valign(Gtk::Align::CENTER);
154 header->set_title_widget(*title_label);
155
156 auto header_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
157 header_right->get_style_context()->add_class("header-right");
158
159 try {
160 auto pix_min = Gdk::Pixbuf::create_from_file(resource("icon-minimize.png"), 32, 32, true);
161 auto btn_min = Gtk::make_managed<Gtk::Button>();
162 btn_min->set_child(*Gtk::make_managed<Gtk::Image>(pix_min));
163 btn_min->get_style_context()->add_class("header-button");
164 header_right->append(*btn_min);
165 btn_min->signal_clicked().connect([this]() { minimize(); });
166 } catch (...) {
167 std::cerr << _("⚠ Errore caricamento icona minimize") << "\n";
168 }
169
170 try {
171 auto pix_close = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 32, 32, true);
172 auto btn_close = Gtk::make_managed<Gtk::Button>();
173 btn_close->set_child(*Gtk::make_managed<Gtk::Image>(pix_close));
174 btn_close->get_style_context()->add_class("header-button");
175 header_right->append(*btn_close);
176 btn_close->signal_clicked().connect([this]() {
178 this->hide();
179 });
180 } catch (...) {
181 std::cerr << _("⚠ Errore caricamento icona close") << "\n";
182 }
183
184 header->pack_end(*header_right);
185 set_titlebar(*header);
186 }
187
188 void minimize() {
189#if GTKMM_CHECK_VERSION(4,10,0)
190 this->hide();
191#endif
192 }
193
194 static std::string config_path_payments() {
195 const auto base = Glib::get_user_config_dir();
196 return base + "/BastionGuard/payments.json";
197 }
198
199 static std::vector<Glib::ustring> load_domains_from_config() {
200 std::vector<Glib::ustring> out;
201 const auto path = config_path_payments();
202
203 if (!fs::exists(path)) {
204 std::cerr << "⚠ Config non trovata: " << path << "\n";
205 return out;
206 }
207
208 std::ifstream f(path, std::ios::binary);
209 if (!f.is_open()) {
210 std::cerr << "⚠ Impossibile aprire: " << path << "\n";
211 return out;
212 }
213
214 json j;
215 try {
216 f >> j;
217 } catch (const std::exception& e) {
218 std::cerr << "⚠ Errore parsing JSON: " << e.what() << "\n";
219 return out;
220 }
221
222 if (!j.is_object() || !j.contains("list") || !j["list"].is_array()) {
223 std::cerr << "⚠ payments.json invalido: manca \"list\" (array).\n";
224 return out;
225 }
226
227 for (const auto& v : j["list"]) {
228 if (!v.is_string()) continue;
229 const auto s = v.get<std::string>();
230 if (!s.empty()) out.emplace_back(s);
231 }
232
233 return out;
234 }
235
236 void show_warning(const Glib::ustring& msg, const Glib::ustring& secondary = {}) {
237 auto* dialog = new Gtk::MessageDialog(
238 *this, msg, false, Gtk::MessageType::WARNING, Gtk::ButtonsType::OK, true);
239 if (!secondary.empty()) dialog->set_secondary_text(secondary);
240 dialog->set_modal(true);
241 dialog->signal_response().connect([dialog](int){ dialog->hide(); delete dialog; });
242 dialog->present();
243 }
244
247 hide();
248 }
249
251 const auto idx = dropdown_.get_selected();
252 if (idx < 0 || static_cast<size_t>(idx) >= domains_.size()) {
253 show_warning(_("Seleziona un elemento valido."));
254 return;
255 }
256
257 const auto domain = domains_[static_cast<size_t>(idx)];
258 if (domain.empty()) {
259 show_warning(_("Dominio non valido."));
260 return;
261 }
262
263 const std::string url = "https://" + domain.raw();
264
265 set_sensitive(false);
266 try {
267 sandbox_proc_ = Gio::Subprocess::create(
268 {"BastionGuard-secure", url},
269 Gio::Subprocess::Flags::NONE
270 );
271 sandbox_proc_->wait_check_async(
272 sigc::mem_fun(*this, &SecureGuiWindow::on_sandbox_finished)
273 );
274 } catch (const Glib::Error& ex) {
275 show_warning(_("Errore avvio subprocess."), ex.what());
276 set_sensitive(true);
277 }
278 }
279
280 void on_sandbox_finished(const Glib::RefPtr<Gio::AsyncResult>& ) {
281 hide();
282 }
283
285 if (!sandbox_proc_) return;
286
287 try {
288 sandbox_proc_->send_signal(SIGTERM);
289 } catch (...) {
290 try { sandbox_proc_->force_exit(); } catch (...) {}
291 }
292 }
293};
294static bool load_lang_conf()
295{
296 const std::string lang_conf = Glib::get_home_dir() + "/.config/BastionGuard/lang.conf";
297 std::ifstream f(lang_conf);
298 if (!f.is_open())
299 return false;
300
301 std::string line;
302 bool any = false;
303
304 while (std::getline(f, line)) {
305 if (line.empty() || line[0] == '#')
306 continue;
307
308 auto pos = line.find('=');
309 if (pos == std::string::npos)
310 continue;
311
312 std::string key = line.substr(0, pos);
313 std::string val = line.substr(pos + 1);
314
315 auto ltrim = [](std::string& s){
316 s.erase(0, s.find_first_not_of(" \t\r\n"));
317 };
318 auto rtrim = [](std::string& s){
319 s.erase(s.find_last_not_of(" \t\r\n") + 1);
320 };
321 ltrim(key); rtrim(key);
322 ltrim(val); rtrim(val);
323
324 if (!key.empty() && !val.empty()) {
325 setenv(key.c_str(), val.c_str(), 1);
326 any = true;
327 }
328 }
329
330 return any;
331}
332int main(int argc, char* argv[]) {
333 bool loaded = load_lang_conf();
334
335 std::setlocale(LC_ALL, "");
336 bindtextdomain("BastionGuard", LOCALEDIR);
337 bind_textdomain_codeset("BastionGuard", "UTF-8");
338 textdomain("BastionGuard");
339
340 if (loaded) {
341 std::cerr << _("[Lang] Config caricata da ~/.config/BastionGuard/lang.conf") << "\n";
342 } else {
343 std::cerr << _("[Lang] Nessuna config trovata, uso locale di sistema") << "\n";
344 }
345
346 // Isola anche la GUI dei pagamenti dentro bubblewrap (Wayland o X11).
347 // Stesso meccanismo del CLI: il flag BASTIONGUARD_NO_BWRAP=1 disattiva.
348 // Importante: niente bwrap se siamo già dentro (BASTIONGUARD_IN_BWRAP=1)
349 // e niente bwrap se siamo root (Gtk e dbus user-session non lo gradiscono).
350 {
351 const char* no_bwrap = std::getenv("BASTIONGUARD_NO_BWRAP");
352 const bool bypass = (no_bwrap && std::strcmp(no_bwrap, "1") == 0);
353 if (!bypass && geteuid() != 0) {
355 // Se siamo qui, bwrap non c'era oppure ha fallito: continuiamo
356 // comunque ma loggiamo (la GUI deve sempre aprirsi).
357 }
358 }
359
360 auto app = Gtk::Application::create("org.BastionGuard.secure.gui");
361 load_style();
362 return app->make_window_and_run<SecureGuiWindow>(argc, argv);
363}
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
int main(int argc, char *argv[])
static bool load_lang_conf()
#define LOCALEDIR
void on_sandbox_finished(const Glib::RefPtr< Gio::AsyncResult > &)
void show_warning(const Glib::ustring &msg, const Glib::ustring &secondary={})
static std::vector< Glib::ustring > load_domains_from_config()
static std::string config_path_payments()
std::vector< Glib::ustring > domains_
Glib::RefPtr< Gio::Subprocess > sandbox_proc_
void reexec_inside_bwrap_with_custom_dns(int argc, char **argv)
std::string resource(const std::string &rel)
Definition Resource.hpp:31
void load_style()