11#include <nlohmann/json.hpp>
15#include <condition_variable>
21#include <sys/socket.h>
25#include <unordered_map>
29using json = nlohmann::json;
49 json tree = json::array();
51 tree.push_back({{
"name", name}, {
"pid", pid}});
53 j[
"process_tree"] = std::move(tree);
70 op.
operand = j.value(
"operand", std::string{});
71 op.
data = j.value(
"data", std::string{});
72 op.
sensitive = j.value(
"sensitive",
false);
73 if (j.contains(
"list") && j[
"list"].is_array()) {
81 r.created = j.value(
"created", std::int64_t{0});
82 r.name = j.value(
"name", std::string{});
83 r.description = j.value(
"description", std::string{});
84 r.enabled = j.value(
"enabled",
true);
85 r.precedence = j.value(
"precedence",
false);
86 r.nolog = j.value(
"nolog",
false);
87 r.action = j.value(
"action", std::string{
"allow"});
88 r.duration = j.value(
"duration", std::string{
"once"});
89 if (j.contains(
"operator") && j[
"operator"].is_object()) {
99 explicit Impl(std::string uri)
121 std::condition_variable
cv;
126 std::unordered_map<std::uint64_t, std::shared_ptr<PendingAsk>>
pending_asks_;
139 if (
running_.exchange(
true))
return;
145 int fd =
fd_.exchange(-1);
147 ::shutdown(fd, SHUT_RDWR);
154 std::lock_guard<std::mutex> l2(p->mu);
156 p->reply = json::object();
172 int fd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
173 if (fd < 0)
return -1;
175 addr.sun_family = AF_UNIX;
176 if (
socket_path_.size() >=
sizeof(addr.sun_path)) { ::close(fd);
return -1; }
178 if (::connect(fd,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr)) < 0) {
187 bool previously_connected =
false;
191 if (previously_connected) {
192 log.warning(
"UI disconnessa, tento la riconnessione...");
193 previously_connected =
false;
195 std::this_thread::sleep_for(std::chrono::seconds(1));
206 std::this_thread::sleep_for(std::chrono::seconds(1));
211 previously_connected =
true;
219 int old =
fd_.exchange(-1);
220 if (old >= 0) ::close(old);
230 if (fd < 0)
return false;
231 std::vector<std::uint8_t> frame;
234 std::size_t total = 0;
235 while (total < frame.size()) {
236 ssize_t n = ::send(fd, frame.data() + total, frame.size() - total, MSG_NOSIGNAL);
238 if (errno == EINTR)
continue;
241 total +=
static_cast<std::size_t
>(n);
250 ssize_t r = ::recv(fd, buf + got, n - got, 0);
251 if (r == 0)
return false;
253 if (errno == EINTR)
continue;
256 got +=
static_cast<std::size_t
>(r);
266 std::uint8_t len_buf[4];
270 log.warning(
"UIClient: frame invalido, chiudo connessione");
273 std::vector<std::uint8_t> payload(
static_cast<std::size_t
>(len));
274 if (!
read_exact(fd, payload.data(), payload.size()))
break;
277 json j = json::parse(payload.begin(), payload.end());
279 }
catch (
const std::exception& e) {
280 log.warning(std::string(
"UIClient: JSON invalido: ") + e.what());
288 const std::string type = j.value(
"type", std::string{});
294 const auto id = j.value(
"id", std::uint64_t{0});
295 std::shared_ptr<PendingAsk> p;
303 std::lock_guard<std::mutex> l2(p->mu);
312 ev.
id = j.value(
"id", std::uint64_t{0});
313 ev.
type = j.value(
"kind", std::string{});
314 ev.
data = j.value(
"data", std::string{});
315 if (j.contains(
"rules") && j[
"rules"].is_array()) {
316 for (
const auto& r : j[
"rules"]) ev.
rules.push_back(rule_from_json(r));
327 "UIClient: messaggio di tipo sconosciuto: " + type);
336 {
"daemon_version",
"bsc-daemon-cpp"}
342 std::deque<std::string> q;
347 for (
const auto& payload : q) {
359 const std::string payload = j.dump();
373 :
impl_(std::make_unique<
Impl>(std::move(socket_uri))) {}
381 return impl_->is_connected();
385 std::lock_guard<std::mutex> lk(
impl_->handler_mutex_);
386 impl_->notif_handler_ = std::move(cb);
390 if (!
impl_->is_connected())
return std::nullopt;
392 const auto id =
impl_->next_id_++;
393 auto pending = std::make_shared<Impl::PendingAsk>();
395 std::lock_guard<std::mutex> lk(
impl_->pending_mutex_);
396 impl_->pending_asks_[id] = pending;
405 connection_to_json(conn, cj);
406 j[
"connection"] = cj;
408 if (!
impl_->write_frame(j.dump())) {
409 std::lock_guard<std::mutex> lk(
impl_->pending_mutex_);
410 impl_->pending_asks_.erase(
id);
416 std::unique_lock<std::mutex> lk(pending->mu);
417 const bool ok = pending->cv.wait_for(lk, std::chrono::seconds(30),
418 [&]() {
return pending->ready; });
421 std::lock_guard<std::mutex> lk2(
impl_->pending_mutex_);
422 impl_->pending_asks_.erase(
id);
425 if (!ok)
return std::nullopt;
429 r.action = pending->reply.value(
"action", std::string{
"deny"});
430 r.duration = pending->reply.value(
"duration", std::string{
"once"});
431 r.name = pending->reply.value(
"rule_name", std::string{});
437 if (!
impl_->is_connected())
return false;
440 {
"id",
impl_->next_id_++},
442 {
"daemon_version",
"bsc-daemon-cpp"},
443 {
"rules",
stats.rules()},
444 {
"connections",
stats.connections()},
445 {
"dns_responses",
stats.dns_responses()},
446 {
"ignored",
stats.ignored()},
447 {
"accepted",
stats.accepted()},
448 {
"dropped",
stats.dropped()},
449 {
"rule_hits",
stats.rule_hits()},
450 {
"rule_misses",
stats.rule_misses()},
453 return impl_->write_frame(j.dump());
459 {
"id",
impl_->next_id_++},
464 return impl_->send_or_queue_alert(j);
468 const std::string& action,
469 const std::string& rule_name) {
472 {
"id",
impl_->next_id_++},
473 {
"level", action ==
"deny" ?
"warning" :
"info"},
474 {
"what",
"connection"},
477 connection_to_json(conn, cj);
478 j[
"connection"] = cj;
479 if (!rule_name.empty()) {
480 j[
"rule"] = {{
"name", rule_name}, {
"action", action}};
482 return impl_->send_or_queue_alert(j);
void warning(const std::string &msg)
static Logger & instance()
std::unordered_map< std::uint64_t, std::shared_ptr< PendingAsk > > pending_asks_
std::atomic_bool running_
std::mutex handler_mutex_
std::function< void(const NotificationEvent &)> notif_handler_
std::mutex pending_mutex_
std::deque< std::string > queued_alerts_
std::thread reader_thread_
bool write_frame(const std::string &payload)
std::atomic_uint64_t next_id_
bool is_connected() const noexcept
void flush_queued_alerts()
std::atomic_bool handshake_done_
void dispatch_incoming(const json &j)
bool read_exact(int fd, std::uint8_t *buf, std::size_t n)
bool send_or_queue_alert(const json &j)
bool post_alert(const std::string &text)
void set_notification_handler(std::function< void(const NotificationEvent &)> cb)
std::unique_ptr< Impl > impl_
bool post_connection_alert(const bsc::model::Connection &conn, const std::string &action, const std::string &rule_name={})
bool ping(const bsc::stats::Statistics &stats)
UIClient(std::string socket_uri)
std::optional< bsc::rule::Rule > ask_rule(const bsc::model::Connection &conn)
bool is_connected() const noexcept
constexpr std::string_view kHello
constexpr std::string_view kAskRuleReply
constexpr std::string_view kPing
constexpr std::string_view kAskRule
constexpr std::string_view kPong
constexpr std::string_view kNotification
constexpr std::string_view kAlert
constexpr std::size_t kMaxFrameSize
std::int64_t frame_peek_length(const std::uint8_t *buf, std::size_t len)
void frame_encode(std::string_view payload, std::vector< std::uint8_t > &out)
bsc::rule::Operator operator_from_json(const json &j)
bsc::rule::OperatorType op_type_from_string(const std::string &s)
void connection_to_json(const bsc::model::Connection &c, json &j)
bsc::rule::Rule rule_from_json(const json &j)
std::vector< std::pair< std::string, std::uint32_t > > process_tree
std::vector< std::string > process_args
std::map< std::string, std::string > process_checksums
std::map< std::string, std::string > process_env
std::vector< Operator > list
std::vector< bsc::rule::Rule > rules
std::condition_variable cv