Skip to content
API Documentation
BastionGuard™
BastionGuard-daemon.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
23#include "../SafeProcess.hpp"
24#include <iostream>
25#include <csignal>
26#include <thread>
27#include <chrono>
28#include <memory>
29#include <string>
30#include <unordered_map>
31#include <filesystem>
32#include <fstream>
33#include <pwd.h>
34#include <grp.h>
35#include <sys/types.h>
36#include <unistd.h>
37#include <cstring>
38#include <sys/socket.h>
39#include <netinet/in.h>
40#include <nlohmann/json.hpp>
41#include <unordered_set>
42#include <glib/gi18n.h>
43
44namespace fs = std::filesystem;
45
46
47static bool running = true;
48static void handle_signal(int) { running = false; }
49
50static std::unordered_map<std::string, std::string> parse_args(int argc, char* argv[]) {
51 std::unordered_map<std::string, std::string> opts;
52 for (int i = 1; i < argc; ++i) {
53 std::string arg = argv[i];
54 if (arg.rfind("--", 0) == 0) {
55 size_t eq = arg.find('=');
56 if (eq != std::string::npos) {
57 std::string key = arg.substr(2, eq - 2);
58 std::string value = arg.substr(eq + 1);
59 opts[key] = value;
60 }
61 }
62 }
63 return opts;
64}
65
66
67static std::unordered_set<std::string> g_trusted;
68static bool g_trusted_loaded = false;
69
70static std::string clean_domain(std::string d) {
71 std::transform(d.begin(), d.end(), d.begin(), ::tolower);
72 if (!d.empty() && d.front() == '.') d.erase(0, 1);
73 if (d.rfind("www.", 0) == 0) d.erase(0, 4);
74 auto p = d.find(':'); if (p != std::string::npos) d.erase(p);
75 if (!d.empty() && d.back() == '.') d.pop_back();
76 return d;
77}
78
79static void load_list_file(const std::string& path, std::vector<std::string>& out) {
80 if (!fs::exists(path)) return;
81 try {
82 std::ifstream f(path);
83 if (!f.good()) return;
84 nlohmann::json j;
85 f >> j;
86
87 auto push = [&](const std::string& s){
88 auto d = clean_domain(s);
89 if(!d.empty()) out.push_back(d);
90 };
91
92 if (j.is_array()) {
93 for (auto& v : j) if (v.is_string()) push(v.get<std::string>());
94 } else {
95 if (j.contains("list") && j["list"].is_array())
96 for (auto& v : j["list"]) if (v.is_string()) push(v.get<std::string>());
97 if (j.contains("domains") && j["domains"].is_array())
98 for (auto& v : j["domains"]) if (v.is_string()) push(v.get<std::string>());
99 if (j.contains("whitelist") && j["whitelist"].is_array())
100 for (auto& v : j["whitelist"]) if (v.is_string()) push(v.get<std::string>());
101 }
102 } catch (...) {}
103}
104
106 if (g_trusted_loaded) return;
107 std::vector<std::string> tmp;
108 load_list_file("/usr/share/BastionGuard/data/bank/banks.json", tmp);
109 if (const char* home = std::getenv("HOME"))
110 load_list_file(std::string(home) + "/.config/BastionGuard/whitelist.json", tmp);
111 for (auto& d : tmp) g_trusted.insert(d);
112 g_trusted_loaded = true;
113 std::cerr << _("[BastionGuard-daemon] ✅ Whitelist caricata: ") << g_trusted.size() << _(" domini trusted.\n");
114}
115
116static bool is_trusted_domain(const std::string& host) {
118 std::string h = clean_domain(host);
119 if (h.empty()) return false;
120 if (g_trusted.count(h)) return true;
121 auto pos = h.find('.');
122 while (pos != std::string::npos) {
123 std::string parent = h.substr(pos + 1);
124 if (g_trusted.count(parent)) return true;
125 pos = h.find('.', pos + 1);
126 }
127 return false;
128}
129
130
131static void ensure_log_file(const std::string& log_dir, const std::string& log_file, const char* user_name = "BastionGuard", mode_t file_mode = 0640) {
132 try {
133 if (!fs::exists(log_dir)) {
134 fs::create_directories(log_dir);
135 std::cout << _("[BastionGuard-daemon] 📁 Creata directory log: ") << log_dir << "\n";
136 }
137
138 if (!fs::exists(log_file)) {
139 std::ofstream ofs(log_file, std::ios::trunc);
140 ofs << _("[BastionGuard-daemon] Log creato automaticamente.\n");
141 ofs.close();
142 std::cout << _("[BastionGuard-daemon] 🧾 Creato file log: ") << log_file << "\n";
143 }
144
145 std::filesystem::permissions(
146 log_file,
147 std::filesystem::perms::owner_read | std::filesystem::perms::owner_write |
148 std::filesystem::perms::group_read,
149 std::filesystem::perm_options::replace
150 );
151
152 if (geteuid() == 0 && user_name != nullptr) {
153 struct passwd *pw = getpwnam(user_name);
154 if (pw) {
155 gid_t gid = pw->pw_gid;
156 uid_t uid = pw->pw_uid;
157 struct group *gr = getgrnam(user_name);
158 if (gr) gid = gr->gr_gid;
159 if (chown(log_file.c_str(), uid, gid) == 0) {
160 std::cout << _("[BastionGuard-daemon] 🔐 Proprietario impostato su ")
161 << user_name << ":" << user_name << "\n";
162 } else {
163 std::cerr << _("[BastionGuard-daemon] ⚠️ Impossibile cambiare owner del log (")
164 << strerror(errno) << ")\n";
165 }
166 } else {
167 std::cerr << _("[BastionGuard-daemon] ⚠️ Utente '") << user_name
168 << _("' non trovato: skip chown\n");
169 }
170 }
171 } catch (const std::exception& e) {
172 std::cerr << _("[BastionGuard-daemon] ⚠️ Errore gestione log: ") << e.what() << "\n";
173 }
174}
175
177 if (geteuid() == 0) return true;
178 const auto caps = bg_process::run({"getcap", "/proc/self/exe"});
179 const bool ok = caps.ok() && caps.output.find("cap_net_bind_service") != std::string::npos;
180 return ok;
181}
182
183int main(int argc, char* argv[]) {
184 std::signal(SIGINT, handle_signal);
185 std::signal(SIGTERM, handle_signal);
186
187 std::cout << _("[BastionGuard-daemon] 🚀 Avvio del servizio LocalWarningServer...") << std::endl;
188
189 auto args = parse_args(argc, argv);
190
191 auto parse_int_arg = [&](const std::string& key, int fallback) {
192 if (!args.count(key)) return fallback;
193 try {
194 return std::stoi(args[key]);
195 } catch (...) {
196 std::cerr << "[BastionGuard-daemon] ⚠️ "
197 << "--" << key << " non è un intero valido, uso fallback "
198 << fallback << "\n";
199 return fallback;
200 }
201 };
202
203 int http_port = parse_int_arg("http-port", 81);
204 int https_port = parse_int_arg("https-port", 444);
205 std::string bind_address = args.count("bind-address") ? args["bind-address"] : "127.0.0.2";
206 std::string page_warning = args.count("page-warning") ? args["page-warning"] : "/usr/share/BastionGuard/data/blocking/block.html";
207
208 std::cout << "[BastionGuard-daemon] Configurazione:\n"
209 << " ├─ HTTP port: " << http_port << "\n"
210 << " ├─ HTTPS port: " << https_port << "\n"
211 << " ├─ Bind address: " << bind_address << "\n"
212 << " ├─ Warning page: " << page_warning << "\n";
213
215 std::cerr << _("[BastionGuard-daemon] ⚠️ Permessi insufficienti per aprire porte 81/444.\n")
216 << _( " Esegui: sudo setcap 'cap_net_bind_service=+ep' /usr/bin/BastionGuard-daemon\n");
217 }
218
219 const std::string log_dir = "/var/log/BastionGuard";
220 const std::string log_file = log_dir + "/phishing_scanner";
221 ensure_log_file(log_dir, log_file, "BastionGuard", 0640);
222
223 try {
225 auto warning_server = std::make_unique<LocalWarningServer>(
226 http_port, https_port, bind_address, "", "", page_warning
227 );
228
229 warning_server->start();
230 std::cout << _("[BastionGuard-daemon] ✅ Server attivo su ") << bind_address
231 << ":" << http_port << _(" e ") << https_port << "." << std::endl;
232
233 while (running) {
234 std::ifstream dnslog("/var/log/BastionGuard/dns_query.log");
235 std::string line;
236 while (std::getline(dnslog, line)) {
237 std::string host = clean_domain(line);
238 if (is_trusted_domain(host)) {
239 std::cout << _("[BastionGuard-daemon] 🟢 Trusted domain rilevato: ") << host << _(" — bypass redirect.\n");
240 } else {
241 std::cout << _("[BastionGuard-daemon] 🔴 Sospetto: ") << host << _(" — instradato verso 127.0.0.2.\n");
242 }
243 }
244 dnslog.close();
245
246 std::this_thread::sleep_for(std::chrono::seconds(2));
247}
248
249
250 std::cout << _("[BastionGuard-daemon] 🛑 Arresto in corso...") << std::endl;
251 warning_server->stop();
252 std::cout << _("[BastionGuard-daemon] ✅ Terminato correttamente.") << std::endl;
253 }
254 catch (const std::exception& e) {
255 std::cerr << _("[BastionGuard-daemon] ❌ Errore fatale: ") << e.what() << std::endl;
256 return 1;
257 }
258 catch (...) {
259 std::cerr << _("[BastionGuard-daemon] ❌ Errore sconosciuto.") << std::endl;
260 return 1;
261 }
262
263 return 0;
264}
static void load_list_file(const std::string &path, std::vector< std::string > &out)
static bool is_trusted_domain(const std::string &host)
static std::unordered_set< std::string > g_trusted
static std::string clean_domain(std::string d)
static bool g_trusted_loaded
static void ensure_trusted_loaded()
static void ensure_log_file(const std::string &log_dir, const std::string &log_file, const char *user_name="BastionGuard", mode_t file_mode=0640)
static bool can_bind_privileged_ports()
#define _(s)
void handle_signal(int)
static std::atomic< bool > running
int main(int argc, char *argv[])
static bool parse_args(int argc, char **argv, std::string &listen_host, int &listen_port)
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={})