Skip to content
API Documentation
BastionGuard™
AppResolver.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ Secure Connection
3 * GPL-3.0
4 */
5
6#include "AppResolver.hpp"
7
8#include <arpa/inet.h>
9
10#include <giomm/appinfo.h>
11#include <giomm/desktopappinfo.h>
12#include <giomm/themedicon.h>
13
14#include <algorithm>
15#include <cctype>
16#include <filesystem>
17#include <mutex>
18#include <optional>
19#include <unordered_map>
20#include <unordered_set>
21#include <libintl.h>
22
23#define _(text) gettext(text)
24#define N_(text) text
25
26
27namespace bsc::ui_util {
28namespace fs = std::filesystem;
29namespace {
30
31constexpr const char* kGenericIcon = "application-x-executable";
32constexpr const char* kUnknownProcessLabel =
33N_("(processo sconosciuto)");
34
35constexpr const char* kSafeLocalProcessLabel =
36N_("Processo locale sicuro");
37constexpr const char* kSafeLocalProcessIcon = "network-workgroup";
38
40 std::unordered_map<std::string, AppMetadata> by_key;
41 bool built{false};
42};
43
45std::mutex g_index_mutex;
46
47std::string lower(std::string s) {
48 std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
49 return static_cast<char>(std::tolower(c));
50 });
51 return s;
52}
53
54std::string trim_copy(std::string s) {
55 auto is_ws = [](unsigned char c) { return std::isspace(c) != 0; };
56 while (!s.empty() && is_ws(static_cast<unsigned char>(s.front()))) s.erase(s.begin());
57 while (!s.empty() && is_ws(static_cast<unsigned char>(s.back()))) s.pop_back();
58 return s;
59}
60
61std::string normalized_address(std::string s) {
62 s = lower(trim_copy(std::move(s)));
63
64 // Rimuove eventuali parentesi di IPv6: [::1]
65 if (s.size() >= 2 && s.front() == '[' && s.back() == ']') {
66 s = s.substr(1, s.size() - 2);
67 }
68
69 // Rimuove eventuale zone-id IPv6: fe80::1%lo
70 if (const auto percent = s.find('%'); percent != std::string::npos) {
71 s = s.substr(0, percent);
72 }
73
74 // Alcuni log salvano un endpoint come host:porta. Per IPv4/localhost lo
75 // normalizziamo, per IPv6 puro lasciamo i ':' intatti.
76 const auto colon = s.rfind(':');
77 if (colon != std::string::npos && s.find(':') == colon) {
78 const auto after = s.substr(colon + 1);
79 if (!after.empty() && std::all_of(after.begin(), after.end(), [](unsigned char c) {
80 return std::isdigit(c) != 0;
81 })) {
82 s = s.substr(0, colon);
83 }
84 }
85
86 return s;
87}
88
89bool is_unknown_identity_value(const std::string& value) {
90 const auto d = lower(trim_copy(value));
91 return d.empty()
93 || d == "processo sconosciuto"
94 || d == "unknown"
95 || d == "unknown process";
96}
97
98bool has_reliable_process_identity(const std::string& app_comm,
99 const std::string& app_path,
100 const std::vector<std::string>& app_args) {
101 if (!is_unknown_identity_value(app_comm)) return true;
102 if (!is_unknown_identity_value(app_path)) return true;
103 if (!app_args.empty() && !is_unknown_identity_value(app_args.front())) return true;
104 return false;
105}
106
107bool is_unknown_display_name(const std::string& display_name) {
108 return is_unknown_identity_value(display_name);
109}
110
111std::string basename_of(std::string s) {
112 if (s.empty()) return {};
113 try {
114 return fs::path(s).filename().string();
115 } catch (...) {
116 const auto pos = s.find_last_of('/');
117 return pos == std::string::npos ? s : s.substr(pos + 1);
118 }
119}
120
121std::string strip_wrappers(std::string s) {
122 if (s.empty()) return s;
123 if ((s.front() == '"' && s.back() == '"') ||
124 (s.front() == '\'' && s.back() == '\'')) {
125 s = s.substr(1, s.size() - 2);
126 }
127 if (s.rfind("--", 0) == 0) return {};
128 if (!s.empty() && s[0] == '%') return {};
129 return s;
130}
131
132std::vector<std::string> split_exec_tokens(const std::string& exec) {
133 std::vector<std::string> out;
134 std::string cur;
135 char quote = 0;
136 bool esc = false;
137 for (char ch : exec) {
138 if (esc) { cur.push_back(ch); esc = false; continue; }
139 if (ch == '\\') { esc = true; continue; }
140 if (quote) {
141 if (ch == quote) quote = 0;
142 else cur.push_back(ch);
143 continue;
144 }
145 if (ch == '\'' || ch == '"') { quote = ch; continue; }
146 if (std::isspace(static_cast<unsigned char>(ch))) {
147 if (!cur.empty()) { out.push_back(cur); cur.clear(); }
148 continue;
149 }
150 cur.push_back(ch);
151 }
152 if (!cur.empty()) out.push_back(cur);
153 return out;
154}
155
156std::string key_for(std::string s) {
157 s = strip_wrappers(std::move(s));
158 if (s.empty()) return {};
159 return lower(std::move(s));
160}
161
162
163
164void add_key(std::unordered_map<std::string, AppMetadata>& map,
165 const std::string& raw,
166 const AppMetadata& meta) {
167 const auto k = key_for(raw);
168 if (k.empty()) return;
169 map.emplace(k, meta);
170 const auto base = key_for(basename_of(raw));
171 if (!base.empty()) map.emplace(base, meta);
172}
173
174bool token_is_env_assignment(const std::string& t) {
175 const auto eq = t.find('=');
176 return eq != std::string::npos && eq > 0 && t.find('/') == std::string::npos;
177}
178
179void add_exec_aliases(std::unordered_map<std::string, AppMetadata>& map,
180 const std::string& commandline,
181 const AppMetadata& meta) {
182 const auto tokens = split_exec_tokens(commandline);
183 if (tokens.empty()) return;
184
185 for (std::size_t i = 0; i < tokens.size(); ++i) {
186 std::string t = strip_wrappers(tokens[i]);
187 if (t.empty()) continue;
188 if (t == "env") continue;
189 if (token_is_env_assignment(t)) continue;
190 if (t == "sh" || t == "/bin/sh" || t == "bash" || t == "/bin/bash") continue;
191 if (t == "-c") continue;
192 if (t == "flatpak") {
193 for (std::size_t j = i + 1; j < tokens.size(); ++j) {
194 if (tokens[j] == "run" || tokens[j].rfind("--", 0) == 0) continue;
195 add_key(map, tokens[j], meta);
196 const auto dot = tokens[j].find_last_of('.');
197 if (dot != std::string::npos) add_key(map, tokens[j].substr(dot + 1), meta);
198 return;
199 }
200 }
201 add_key(map, t, meta);
202 return;
203 }
204}
205
206bool looks_like_thread_name(const std::string& comm) {
207 const auto s = lower(comm);
208 return s.find("thread") != std::string::npos ||
209 s.find("web content") != std::string::npos ||
210 s.find("socket") != std::string::npos ||
211 s.find("renderer") != std::string::npos ||
212 s.find("utility") != std::string::npos ||
213 s.find("chrome_") != std::string::npos ||
214 s.find("isolated web") != std::string::npos ||
215 s == "gpu process" ||
216 s == "rdd process" ||
217 s == "privileged cont";
218}
219
220std::string icon_name_from_appinfo(const Glib::RefPtr<Gio::AppInfo>& ai) {
221 if (!ai) return {};
222 auto icon = ai->get_icon();
223 if (!icon) return {};
224 auto themed = std::dynamic_pointer_cast<Gio::ThemedIcon>(icon);
225 if (!themed) return {};
226 const auto names = themed->get_names();
227 for (const auto& n : names) {
228 if (!n.empty()) return n.raw();
229 }
230 return {};
231}
232
234 if (g_index.built) return;
235 g_index.built = true;
236
237 auto apps = Gio::AppInfo::get_all();
238 for (const auto& ai : apps) {
239 if (!ai) continue;
240
241 AppMetadata meta;
242 meta.display_name = ai->get_name();
244 if (meta.icon_name.empty()) meta.icon_name = kGenericIcon;
245
246 add_key(g_index.by_key, meta.display_name, meta);
247
248 auto dai = std::dynamic_pointer_cast<Gio::DesktopAppInfo>(ai);
249 if (!dai) continue;
250
251 const std::string cmd = dai->get_commandline();
252 add_exec_aliases(g_index.by_key, cmd, meta);
253
254 const std::string wm = dai->get_startup_wm_class();
255 if (!wm.empty()) add_key(g_index.by_key, wm, meta);
256 }
257}
258
259void add_candidate(std::vector<std::string>& out, const std::string& s) {
260 const auto cleaned = strip_wrappers(s);
261 if (cleaned.empty()) return;
262 out.push_back(cleaned);
263 const auto base = basename_of(cleaned);
264 if (!base.empty() && base != cleaned) out.push_back(base);
265}
266
267std::vector<std::string> browser_aliases_for(const std::string& raw) {
268 const auto s = lower(raw);
269 if (s.find("firefox") != std::string::npos || s.find("mozilla") != std::string::npos) {
270 return {"firefox", "firefox-esr", "org.mozilla.firefox"};
271 }
272 if (s.find("chromium") != std::string::npos) {
273 return {"chromium", "chromium-browser", "org.chromium.chromium"};
274 }
275 if (s.find("google-chrome") != std::string::npos || s == "chrome" || s.find("chrome_") != std::string::npos) {
276 return {"google-chrome", "google-chrome-stable", "chrome", "chromium", "chromium-browser"};
277 }
278 if (s.find("brave") != std::string::npos) {
279 return {"brave-browser", "brave", "com.brave.browser"};
280 }
281 if (s.find("vivaldi") != std::string::npos) {
282 return {"vivaldi", "vivaldi-stable", "com.vivaldi.vivaldi"};
283 }
284 if (s.find("librewolf") != std::string::npos) {
285 return {"librewolf", "io.gitlab.librewolf-community"};
286 }
287 if (s.find("waterfox") != std::string::npos) {
288 return {"waterfox"};
289 }
290 if (s.find("opera") != std::string::npos) {
291 return {"opera", "opera-browser", "com.opera.Opera"};
292 }
293 if (s.find("msedge") != std::string::npos || s.find("edge") != std::string::npos) {
294 return {"microsoft-edge", "microsoft-edge-stable", "msedge"};
295 }
296 return {};
297}
298
299std::optional<AppMetadata> lookup_exact_or_alias(const std::vector<std::string>& candidates) {
300 for (const auto& c : candidates) {
301 const auto k = key_for(c);
302 if (k.empty()) continue;
303 if (auto it = g_index.by_key.find(k); it != g_index.by_key.end()) return it->second;
304 for (const auto& alias : browser_aliases_for(k)) {
305 if (auto ai = g_index.by_key.find(key_for(alias)); ai != g_index.by_key.end()) return ai->second;
306 }
307 }
308 return std::nullopt;
309}
310
311std::optional<AppMetadata> lookup_prefix(const std::vector<std::string>& candidates) {
312 for (const auto& c : candidates) {
313 const auto needle = key_for(c);
314 if (needle.size() < 4) continue;
315 for (const auto& [key, meta] : g_index.by_key) {
316 if (key.size() < 4) continue;
317 if ((key.size() > needle.size() && key.compare(0, needle.size(), needle) == 0) ||
318 (needle.size() > key.size() && needle.compare(0, key.size(), key) == 0)) {
319 return meta;
320 }
321 }
322 }
323 return std::nullopt;
324}
325
326AppMetadata fallback_metadata(const std::string& app_comm,
327 const std::string& app_path,
328 const std::vector<std::string>& app_args) {
329 AppMetadata meta;
330 if (!app_comm.empty() && !looks_like_thread_name(app_comm)) {
331 meta.display_name = app_comm;
332 } else if (!app_path.empty()) {
333 meta.display_name = basename_of(app_path);
334 } else if (!app_args.empty() && !app_args.front().empty()) {
335 meta.display_name = basename_of(app_args.front());
336 } else if (!app_comm.empty()) {
337 meta.display_name = app_comm;
338 } else {
340 }
341
342 std::string icon_guess = basename_of(app_path.empty() ? app_comm : app_path);
343 icon_guess = key_for(icon_guess);
344 if (!icon_guess.empty() && !looks_like_thread_name(icon_guess)) meta.icon_name = icon_guess;
345 else meta.icon_name = kGenericIcon;
346 return meta;
347}
348
349} // namespace
350
351AppMetadata resolve_app_metadata(const std::string& app_comm,
352 const std::string& app_path,
353 const std::vector<std::string>& app_args) {
354 std::lock_guard lock(g_index_mutex);
355 build_index_locked();
356
357 std::vector<std::string> candidates;
358 add_candidate(candidates, app_path);
359 if (!app_args.empty()) add_candidate(candidates, app_args.front());
360 add_candidate(candidates, app_comm);
361
362 if (auto found = lookup_exact_or_alias(candidates)) return *found;
363 if (auto found = lookup_prefix(candidates)) return *found;
364 return fallback_metadata(app_comm, app_path, app_args);
365}
366
367std::string best_app_display_name(const std::string& app_comm,
368 const std::string& app_path,
369 const std::vector<std::string>& app_args) {
370 return resolve_app_metadata(app_comm, app_path, app_args).display_name;
371}
372
373std::string best_app_icon_name(const std::string& app_comm,
374 const std::string& app_path,
375 const std::vector<std::string>& app_args) {
376 auto icon = resolve_app_metadata(app_comm, app_path, app_args).icon_name;
377 return icon.empty() ? kGenericIcon : icon;
378}
379
380bool is_safe_local_address(const std::string& address) {
381 const auto s = normalized_address(address);
382 if (s.empty()) return false;
383
384 if (s == "localhost" || s == "localhost.localdomain" || s == "ip6-localhost") return true;
385 if (s == "::1" || s == "0:0:0:0:0:0:0:1") return true;
386
387 // Endpoint unspecified che indicano binding/traffico locale, non Internet.
388 if (s == "0.0.0.0" || s == "::" || s == "0:0:0:0:0:0:0:0") return true;
389
390 // IPv4 loopback 127.0.0.0/8 e IPv4-mapped IPv6 loopback.
391 if (s.rfind("127.", 0) == 0) return true;
392 if (s.rfind("::ffff:127.", 0) == 0) return true;
393 if (s.rfind("0:0:0:0:0:ffff:127.", 0) == 0) return true;
394
395 return false;
396}
397
398bool is_safe_local_connection(const std::string& dst_ip, const std::string& src_ip) {
399 return is_safe_local_address(dst_ip) || is_safe_local_address(src_ip);
400}
401
403 return kSafeLocalProcessLabel;
404}
405
407 return kSafeLocalProcessIcon;
408}
409
410std::string best_connection_display_name(const std::string& app_comm,
411 const std::string& app_path,
412 const std::vector<std::string>& app_args,
413 const std::string& dst_ip,
414 const std::string& src_ip) {
415 const auto display = best_app_display_name(app_comm, app_path, app_args);
416 if (is_unknown_display_name(display)
417 && !has_reliable_process_identity(app_comm, app_path, app_args)
418 && is_safe_local_connection(dst_ip, src_ip)) {
419 return kSafeLocalProcessLabel;
420 }
421 return display;
422}
423
424std::string best_connection_icon_name(const std::string& app_comm,
425 const std::string& app_path,
426 const std::vector<std::string>& app_args,
427 const std::string& dst_ip,
428 const std::string& src_ip) {
429 if (!has_reliable_process_identity(app_comm, app_path, app_args)
430 && is_safe_local_connection(dst_ip, src_ip)) {
431 return kSafeLocalProcessIcon;
432 }
433 return best_app_icon_name(app_comm, app_path, app_args);
434}
435
436
437} // namespace bsc::ui_util
#define N_(text)
void add_exec_aliases(std::unordered_map< std::string, AppMetadata > &map, const std::string &commandline, const AppMetadata &meta)
void add_candidate(std::vector< std::string > &out, const std::string &s)
bool is_unknown_identity_value(const std::string &value)
std::optional< AppMetadata > lookup_prefix(const std::vector< std::string > &candidates)
bool looks_like_thread_name(const std::string &comm)
bool has_reliable_process_identity(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
std::vector< std::string > browser_aliases_for(const std::string &raw)
bool is_unknown_display_name(const std::string &display_name)
std::string icon_name_from_appinfo(const Glib::RefPtr< Gio::AppInfo > &ai)
void add_key(std::unordered_map< std::string, AppMetadata > &map, const std::string &raw, const AppMetadata &meta)
std::optional< AppMetadata > lookup_exact_or_alias(const std::vector< std::string > &candidates)
AppMetadata fallback_metadata(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
std::vector< std::string > split_exec_tokens(const std::string &exec)
std::string safe_local_process_icon_name()
std::string best_connection_icon_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)
std::string safe_local_process_label()
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)
std::string best_app_display_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
bool is_safe_local_connection(const std::string &dst_ip, const std::string &src_ip)
AppMetadata resolve_app_metadata(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
std::string best_app_icon_name(const std::string &app_comm, const std::string &app_path, const std::vector< std::string > &app_args)
bool is_safe_local_address(const std::string &address)
std::unordered_map< std::string, AppMetadata > by_key