Skip to content
API Documentation
BastionGuard™
UIServer.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ Secure Connection
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * GPL-3.0
5 */
6
7#include "UIServer.hpp"
9#include "bsc/proto/Wire.hpp"
10
11#include <glibmm/main.h>
12#include <nlohmann/json.hpp>
13
14#include <atomic>
15#include <chrono>
16#include <condition_variable>
17#include <cstring>
18#include <deque>
19#include <errno.h>
20#include <filesystem>
21#include <iostream>
22#include <mutex>
23#include <sys/socket.h>
24#include <sys/stat.h>
25#include <sys/un.h>
26#include <thread>
27#include <unistd.h>
28
29namespace fs = std::filesystem;
30using json = nlohmann::json;
31
32// ---------------------------------------------------------------------------
33// Helper: conversione JSON -> ConnectionInfo
34// ---------------------------------------------------------------------------
36 ConnectionInfo info;
37 info.app_path = j.value("process_path", std::string{});
38 info.app_comm = j.value("process_comm", std::string{});
39 info.app_cwd = j.value("process_cwd", std::string{});
40 info.pid = j.value("process_id", std::uint32_t{0});
41 info.uid = j.value("user_id", std::uint32_t{0});
42 if (j.contains("process_args") && j["process_args"].is_array()) {
43 for (const auto& a : j["process_args"]) info.app_args.push_back(a.get<std::string>());
44 }
45 info.protocol = j.value("protocol", std::string{});
46 info.src_ip = j.value("src_ip", std::string{});
47 info.src_port = j.value("src_port", std::uint32_t{0});
48 info.dest_host = j.value("dst_host", std::string{});
49 info.dest_ip = j.value("dst_ip", std::string{});
50 info.dest_port = j.value("dst_port", std::uint32_t{0});
51 return info;
52}
53
54
55static std::string display_app_name(const ConnectionInfo& info) {
57 info.app_comm, info.app_path, info.app_args, info.dest_ip, info.src_ip);
58}
59
60// ---------------------------------------------------------------------------
61// Impl
62// ---------------------------------------------------------------------------
64public:
65 Impl(UIServer* owner) : owner_(owner) {}
66 ~Impl() { stop(); }
67
69
70 // Socket in ascolto
71 int listen_fd_{-1};
72 std::string socket_file_;
73
74 // Client connesso (al massimo uno alla volta: un solo daemon)
75 std::atomic_int client_fd_{-1};
76
77 // Thread di accept
78 std::thread accept_thread_;
79 std::atomic_bool running_{false};
80
81 // Thread di lettura (per-client)
82 std::thread reader_thread_;
83
84 // Scrittore serializzato
85 std::mutex write_mutex_;
86
87 // ID per le notifiche in uscita
88 std::atomic_uint64_t next_id_{1};
89
90 // Coda notifiche da spedire
91 std::mutex notif_mutex_;
92 std::deque<json> pending_notifs_;
93
94 // Risposta al corrente ask_rule pending
95 std::mutex reply_mutex_;
96 std::condition_variable reply_cv_;
97 bool reply_ready_{false};
99 std::uint64_t current_ask_id_{0};
100
101 // ── Lifecycle ─────────────────────────────────────────────────────
102
103 bool start(const std::string& socketPath) {
104 if (running_.exchange(true)) return false;
105
107 try {
108 fs::path p(socket_file_);
109 if (p.has_parent_path()) fs::create_directories(p.parent_path());
110 if (fs::exists(p)) fs::remove(p);
111 } catch (const std::exception& e) {
112 std::cerr << "[UIServer] prep socket: " << e.what() << "\n";
113 }
114
115 listen_fd_ = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
116 if (listen_fd_ < 0) { running_ = false; return false; }
117
118 sockaddr_un addr{};
119 addr.sun_family = AF_UNIX;
120 if (socket_file_.size() >= sizeof(addr.sun_path)) {
121 ::close(listen_fd_); listen_fd_ = -1; running_ = false; return false;
122 }
123 std::memcpy(addr.sun_path, socket_file_.c_str(), socket_file_.size());
124
125 if (::bind(listen_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
126 std::cerr << "[UIServer] bind fallita: " << std::strerror(errno) << "\n";
127 ::close(listen_fd_); listen_fd_ = -1; running_ = false; return false;
128 }
129
130 // Permessi: il daemon gira come root, la UI come utente. Il socket
131 // deve essere accessibile da entrambi → 0666.
132 ::chmod(socket_file_.c_str(), 0666);
133
134 if (::listen(listen_fd_, 4) < 0) {
135 ::close(listen_fd_); listen_fd_ = -1; running_ = false; return false;
136 }
137
138 std::cout << "[UIServer] in ascolto su " << socket_file_ << "\n";
139
140 accept_thread_ = std::thread([this]() { accept_loop(); });
141 return true;
142 }
143
144 void stop() {
145 if (!running_.exchange(false)) return;
146
147 // Chiudi listen e client
148 int lfd = listen_fd_; listen_fd_ = -1;
149 if (lfd >= 0) { ::shutdown(lfd, SHUT_RDWR); ::close(lfd); }
150 int cfd = client_fd_.exchange(-1);
151 if (cfd >= 0) { ::shutdown(cfd, SHUT_RDWR); ::close(cfd); }
152
153 // Sblocca eventuali sendReply in attesa
154 {
155 std::lock_guard<std::mutex> lk(reply_mutex_);
156 reply_ready_ = true;
157 }
158 reply_cv_.notify_all();
159
160 if (accept_thread_.joinable()) accept_thread_.join();
161 if (reader_thread_.joinable()) reader_thread_.join();
162
163 try { if (!socket_file_.empty()) fs::remove(socket_file_); } catch (...) {}
164 }
165
166 // ── Accept loop ───────────────────────────────────────────────────
167
168 void accept_loop() {
169 while (running_) {
170 int fd = ::accept4(listen_fd_, nullptr, nullptr, SOCK_CLOEXEC);
171 if (fd < 0) {
172 if (errno == EINTR) continue;
173 if (!running_) break;
174 std::cerr << "[UIServer] accept fallita: " << std::strerror(errno) << "\n";
175 std::this_thread::sleep_for(std::chrono::milliseconds(200));
176 continue;
177 }
178
179 // Chiudi client precedente se c'e' (un solo daemon alla volta)
180 int old = client_fd_.exchange(fd);
181 if (old >= 0) { ::shutdown(old, SHUT_RDWR); ::close(old); }
182 if (reader_thread_.joinable()) reader_thread_.join();
183
184 reader_thread_ = std::thread([this, fd]() { client_session(fd); });
185 }
186 }
187
188 // ── Lettura frame → dispatch ──────────────────────────────────────
189
190 bool read_exact(int fd, std::uint8_t* buf, std::size_t n) {
191 std::size_t got = 0;
192 while (got < n) {
193 ssize_t r = ::recv(fd, buf + got, n - got, 0);
194 if (r == 0) return false;
195 if (r < 0) { if (errno == EINTR) continue; return false; }
196 got += static_cast<std::size_t>(r);
197 }
198 return true;
199 }
200
201 void client_session(int fd) {
202 // Emetti "connected" al primo frame ricevuto (hello) — non subito,
203 // cosi' non segnaliamo client che si connettono e rompono il socket
204 // senza inviare niente. La pratica: appena ci arriva un hello
205 // dispatchiamo connected.
206 bool announced = false;
207
208 while (running_ && fd == client_fd_.load()) {
209 std::uint8_t lenbuf[4];
210 if (!read_exact(fd, lenbuf, 4)) break;
211 const std::int64_t len = bsc::proto::frame_peek_length(lenbuf, 4);
212 if (len < 0 || static_cast<std::size_t>(len) > bsc::proto::kMaxFrameSize) break;
213 std::vector<std::uint8_t> payload(static_cast<std::size_t>(len));
214 if (!read_exact(fd, payload.data(), payload.size())) break;
215
216 try {
217 json j = json::parse(payload.begin(), payload.end());
218 const std::string type = j.value("type", std::string{});
219
220 if (type == bsc::proto::msg::kHello) {
221 if (!announced) {
222 announced = true;
224 // Spedisci eventuali notifiche accodate prima della
225 // connessione.
227 }
228 continue;
229 }
230
231 if (!announced) {
232 // Pazienza: il daemon non ci ha mandato hello, ma
233 // accettiamo messaggi comunque.
234 announced = true;
236 }
237
238 if (type == bsc::proto::msg::kPing) {
239 // Rispondi con pong; il payload stats lo ignoriamo per ora
240 json pong = { {"type", bsc::proto::msg::kPong}, {"id", j.value("id", std::uint64_t{0})} };
241 send_frame(pong.dump());
242 continue;
243 }
244
245 if (type == bsc::proto::msg::kAskRule) {
246 const auto id = j.value("id", std::uint64_t{0});
247 ConnectionInfo info;
248 if (j.contains("connection")) info = connection_info_from_json(j["connection"]);
249
250 // Registra l'id come "corrente"; sendReply verra' chiamato
251 // dalla UI (thread GTK) e spedira' il reply.
252 {
253 std::lock_guard<std::mutex> lk(reply_mutex_);
254 reply_ready_ = false;
255 current_ask_id_ = id;
256 }
257
259
260 // Aspetta sendReply (timeout 20s)
261 std::unique_lock<std::mutex> lk(reply_mutex_);
262 const bool got = reply_cv_.wait_for(lk, std::chrono::seconds(20),
263 [this]() { return reply_ready_; });
264 const ReplyAction action = got ? reply_action_ : ReplyAction::DENY_ONCE;
265 reply_ready_ = false;
266 lk.unlock();
267
268 json reply = {
270 {"id", id},
271 };
272 switch (action) {
273 case ReplyAction::ALLOW_ONCE: reply["action"] = "allow"; reply["duration"] = "once"; break;
274 case ReplyAction::ALLOW_ALWAYS: reply["action"] = "allow"; reply["duration"] = "always"; break;
275 case ReplyAction::DENY_ONCE: reply["action"] = "deny"; reply["duration"] = "once"; break;
276 case ReplyAction::DENY_ALWAYS: reply["action"] = "deny"; reply["duration"] = "always"; break;
277 }
278 send_frame(reply.dump());
279 continue;
280 }
281
282 if (type == bsc::proto::msg::kAlert) {
283 AlertInfo ai;
284 ai.id = j.value("id", std::uint64_t{0});
285 const std::string level = j.value("level", std::string{"info"});
286 ai.type = level == "error" ? 0
287 : level == "warning" ? 1
288 : 2;
289 const std::string what = j.value("what", std::string{"generic"});
290 ai.what = what == "firewall" ? 2
291 : what == "connection" ? 3
292 : 0;
293 ai.action = 1; // show_alert
294 ai.priority = 1;
295 ai.text = j.value("text", std::string{});
296 if (j.contains("connection")) {
297 ai.connection = connection_info_from_json(j["connection"]);
298 ai.has_connection = true;
299 }
300 if (j.contains("rule")) {
301 ai.rule_name = j["rule"].value("name", std::string{});
302 }
303 if (ai.text.empty() && ai.has_connection) {
304 const auto host = !ai.connection.dest_host.empty()
306 : ai.connection.dest_ip;
308 + " -> " + host + ":" + std::to_string(ai.connection.dest_port);
309 }
310 dispatch_alert(ai);
311 continue;
312 }
313
314 // tipo sconosciuto: ignora
315 } catch (const std::exception& e) {
316 std::cerr << "[UIServer] JSON parse error: " << e.what() << "\n";
317 }
318 }
319
320 // Disconnessione
321 if (announced) dispatch_disconnected();
322 int expected = fd;
323 client_fd_.compare_exchange_strong(expected, -1);
324 ::close(fd);
325
326 // Sblocca eventuale sendReply pendente
327 {
328 std::lock_guard<std::mutex> lk(reply_mutex_);
329 reply_ready_ = true;
331 }
332 reply_cv_.notify_all();
333 }
334
335 // ── Scrittura ─────────────────────────────────────────────────────
336
337 bool send_frame(const std::string& payload) {
338 int fd = client_fd_.load();
339 if (fd < 0) return false;
340 std::vector<std::uint8_t> frame;
341 bsc::proto::frame_encode(payload, frame);
342 std::lock_guard<std::mutex> lk(write_mutex_);
343 std::size_t total = 0;
344 while (total < frame.size()) {
345 ssize_t n = ::send(fd, frame.data() + total, frame.size() - total, MSG_NOSIGNAL);
346 if (n < 0) { if (errno == EINTR) continue; return false; }
347 total += static_cast<std::size_t>(n);
348 }
349 return true;
350 }
351
353 std::deque<json> q;
354 {
355 std::lock_guard<std::mutex> lk(notif_mutex_);
356 q.swap(pending_notifs_);
357 }
358 for (const auto& j : q) {
359 if (!send_frame(j.dump())) {
360 std::lock_guard<std::mutex> lk(notif_mutex_);
361 pending_notifs_.push_front(j);
362 break;
363 }
364 }
365 }
366
367 // ── sendReply pubblico ────────────────────────────────────────────
368
369 void send_reply(ReplyAction action) {
370 {
371 std::lock_guard<std::mutex> lk(reply_mutex_);
372 reply_action_ = action;
373 reply_ready_ = true;
374 }
375 reply_cv_.notify_one();
376 }
377
378 // ── queueNotification pubblico ────────────────────────────────────
379
380 void queue_notification(int type, const std::string& data) {
381 static const char* kinds[] = {
382 "none", "enable_interception", "disable_interception",
383 "enable_firewall", "disable_firewall", "reload_fw_rules",
384 "change_config", "enable_rule", "disable_rule", "delete_rule",
385 "change_rule", "log_level", "stop", "task_start", "task_stop",
386 "desktop_active", "desktop_idle"
387 };
388 const std::string kind = (type >= 0 && type < 17) ? kinds[type] : "none";
389 json j = {
391 {"id", next_id_++},
392 {"kind", kind},
393 {"data", data}
394 };
395 if (client_fd_.load() >= 0 && send_frame(j.dump())) return;
396 std::lock_guard<std::mutex> lk(notif_mutex_);
397 if (pending_notifs_.size() > 128) pending_notifs_.pop_front();
398 pending_notifs_.push_back(std::move(j));
399 }
400
401 // ── Dispatch verso thread GTK ─────────────────────────────────────
402
403 void dispatch_connected() { Glib::signal_idle().connect_once([this]() { owner_->signal_daemon_connected.emit(); }); }
404 void dispatch_disconnected() { Glib::signal_idle().connect_once([this]() { owner_->signal_daemon_disconnected.emit(); }); }
406 Glib::signal_idle().connect_once([this, info]() { owner_->signal_new_connection.emit(info); });
407 }
408 void dispatch_alert(const AlertInfo& a) {
409 Glib::signal_idle().connect_once([this, a]() { owner_->signal_alert.emit(a); });
410 }
411};
412
413// ---------------------------------------------------------------------------
414// Facade
415// ---------------------------------------------------------------------------
416
417UIServer::UIServer() : impl_(std::make_unique<Impl>(this)) {}
419
420void UIServer::start(const std::string& socketPath) { impl_->start(socketPath); }
421void UIServer::stop() { impl_->stop(); }
422void UIServer::sendReply(ReplyAction action) { impl_->send_reply(action); }
423void UIServer::queueNotification(int type, const std::string& data) {
424 impl_->queue_notification(type, data);
425}
nlohmann::json json
Definition Backend.cpp:2908
static std::string display_app_name(const ConnectionInfo &info)
Definition UIServer.cpp:55
static ConnectionInfo connection_info_from_json(const json &j)
Definition UIServer.cpp:35
ReplyAction
Definition UIServer.hpp:42
std::thread accept_thread_
Definition UIServer.cpp:78
void dispatch_disconnected()
Definition UIServer.cpp:404
void dispatch_new_connection(const ConnectionInfo &info)
Definition UIServer.cpp:405
void send_reply(ReplyAction action)
Definition UIServer.cpp:369
ReplyAction reply_action_
Definition UIServer.cpp:98
void queue_notification(int type, const std::string &data)
Definition UIServer.cpp:380
void dispatch_connected()
Definition UIServer.cpp:403
std::mutex write_mutex_
Definition UIServer.cpp:85
std::atomic_uint64_t next_id_
Definition UIServer.cpp:88
std::mutex notif_mutex_
Definition UIServer.cpp:91
std::deque< json > pending_notifs_
Definition UIServer.cpp:92
void flush_pending_notifs()
Definition UIServer.cpp:352
std::condition_variable reply_cv_
Definition UIServer.cpp:96
void dispatch_alert(const AlertInfo &a)
Definition UIServer.cpp:408
std::atomic_bool running_
Definition UIServer.cpp:79
void client_session(int fd)
Definition UIServer.cpp:201
std::thread reader_thread_
Definition UIServer.cpp:82
Impl(UIServer *owner)
Definition UIServer.cpp:65
std::mutex reply_mutex_
Definition UIServer.cpp:95
std::string socket_file_
Definition UIServer.cpp:72
bool read_exact(int fd, std::uint8_t *buf, std::size_t n)
Definition UIServer.cpp:190
std::atomic_int client_fd_
Definition UIServer.cpp:75
UIServer * owner_
Definition UIServer.cpp:68
std::uint64_t current_ask_id_
Definition UIServer.cpp:99
bool start(const std::string &socketPath)
Definition UIServer.cpp:103
bool send_frame(const std::string &payload)
Definition UIServer.cpp:337
void accept_loop()
Definition UIServer.cpp:168
sigc::signal< void(const ConnectionInfo &)> signal_new_connection
Definition UIServer.hpp:91
sigc::signal< void()> signal_daemon_disconnected
Definition UIServer.hpp:90
void sendReply(ReplyAction action)
Definition UIServer.cpp:422
void stop()
Definition UIServer.cpp:421
void queueNotification(int type, const std::string &data={})
Definition UIServer.cpp:423
sigc::signal< void(const AlertInfo &)> signal_alert
Definition UIServer.hpp:92
sigc::signal< void()> signal_daemon_connected
Definition UIServer.hpp:89
std::unique_ptr< Impl > impl_
Definition UIServer.hpp:96
void start(const std::string &socketPath="unix:///tmp/bsd-daemon.sock")
Definition UIServer.cpp:420
constexpr std::string_view kHello
Definition Wire.hpp:48
constexpr std::string_view kAskRuleReply
Definition Wire.hpp:52
constexpr std::string_view kPing
Definition Wire.hpp:49
constexpr std::string_view kAskRule
Definition Wire.hpp:51
constexpr std::string_view kPong
Definition Wire.hpp:50
constexpr std::string_view kNotification
Definition Wire.hpp:54
constexpr std::string_view kAlert
Definition Wire.hpp:53
std::string resolve_socket_path(std::string uri)
Definition Wire.hpp:58
constexpr std::size_t kMaxFrameSize
Definition Wire.hpp:89
std::int64_t frame_peek_length(const std::uint8_t *buf, std::size_t len)
Definition Wire.hpp:78
void frame_encode(std::string_view payload, std::vector< std::uint8_t > &out)
Definition Wire.hpp:66
std::string best_connection_display_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args, const std::string &dst_ip, const std::string &src_ip)
uint64_t id
Definition UIServer.hpp:59
ConnectionInfo connection
Definition UIServer.hpp:68
bool has_connection
Definition UIServer.hpp:69
int priority
Definition UIServer.hpp:62
std::string rule_name
Definition UIServer.hpp:65
std::string text
Definition UIServer.hpp:64
uint32_t src_port
Definition UIServer.hpp:36
uint32_t pid
Definition UIServer.hpp:31
std::string src_ip
Definition UIServer.hpp:35
std::string app_cwd
Definition UIServer.hpp:29
std::string protocol
Definition UIServer.hpp:34
std::string dest_host
Definition UIServer.hpp:37
uint32_t dest_port
Definition UIServer.hpp:39
std::string app_path
Definition UIServer.hpp:27
uint32_t uid
Definition UIServer.hpp:32
std::vector< std::string > app_args
Definition UIServer.hpp:30
std::string dest_ip
Definition UIServer.hpp:38
std::string app_comm
Definition UIServer.hpp:28