Skip to content
API Documentation
BastionGuard™
BastionGuard-privacyd.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 <filesystem>
22#include <fstream>
23#include <iostream>
24#include <thread>
25#include <set>
26#include <atomic>
27#include <mutex>
28#include <csignal>
29#include <algorithm>
30#include <chrono>
31#include <vector>
32#include <clocale>
33#include <glibmm/miscutils.h>
34#include <glibmm/i18n.h>
35
36namespace fs = std::filesystem;
37static std::atomic<bool> running{true};
38static std::mutex log_mutex;
39
40void write_log(const std::string& msg) {
41 std::thread([msg]() {
42 try {
43 std::lock_guard<std::mutex> lock(log_mutex);
44 std::string base_dir = Glib::get_home_dir() + "/.local/share/BastionGuard/logs";
45 if (!fs::exists(base_dir))
46 fs::create_directories(base_dir);
47
48
49 auto t = std::time(nullptr);
50 std::tm tm{};
51 localtime_r(&t, &tm); // thread-safe
52 char datebuf[16];
53 std::strftime(datebuf, sizeof(datebuf), "%Y-%m-%d", &tm);
54 std::string log_path = base_dir + "/privacyd_" + std::string(datebuf) + ".log";
55
56
57 std::ofstream f(log_path, std::ios::app);
58 if (!f.is_open()) return;
59
60 char timebuf[32];
61 std::strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
62 f << "[" << timebuf << "] " << msg << std::endl;
63
64
65 const auto now = std::chrono::system_clock::now();
66 const auto seven_days = std::chrono::hours(24 * 7);
67
68 for (const auto& entry : fs::directory_iterator(base_dir)) {
69 if (entry.is_regular_file()) {
70 auto ftime = fs::last_write_time(entry);
71 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
72 ftime - fs::file_time_type::clock::now()
73 + std::chrono::system_clock::now());
74 auto age = now - sctp;
75
76 if (age > seven_days) {
77 try {
78 fs::remove(entry.path());
79 } catch (...) {
80
81 }
82 }
83 }
84 }
85 } catch (...) {
86 std::cerr << _("[privacyd] Errore scrittura log.") << std::endl;
87 }
88 }).detach();
89}
90
91
92std::vector<std::string> detect_active_devices() {
93 std::vector<std::string> result;
94 const std::vector<std::string> prefixes = {"/dev/video", "/dev/snd/pcm"};
95
96 // /proc è race-y: i PID possono sparire in qualsiasi momento tra open e iter.
97 // Usiamo l'overload con std::error_code per non lanciare eccezioni
98 // (altrimenti std::terminate nel thread monitor_loop -> SIGABRT).
99 std::error_code ec;
100 fs::directory_iterator proc_it("/proc", ec);
101 if (ec) return result;
102
103 for (; proc_it != fs::directory_iterator(); proc_it.increment(ec)) {
104 if (ec) break; // PID scomparso mentre iteravamo
105
106 const auto& pid_entry = *proc_it;
107 std::error_code ec_dir;
108 if (!pid_entry.is_directory(ec_dir) || ec_dir) continue;
109
110 const std::string pid_str = pid_entry.path().filename();
111 if (!std::all_of(pid_str.begin(), pid_str.end(), ::isdigit)) continue;
112
113 const std::string fd_path = "/proc/" + pid_str + "/fd";
114 std::error_code ec_exists;
115 if (!fs::exists(fd_path, ec_exists) || ec_exists) continue;
116
117 std::error_code ec_fd;
118 fs::directory_iterator fd_it(
119 fd_path,
120 fs::directory_options::skip_permission_denied,
121 ec_fd);
122 if (ec_fd) continue;
123
124 for (; fd_it != fs::directory_iterator(); fd_it.increment(ec_fd)) {
125 if (ec_fd) break; // processo terminato mentre iteravamo gli fd
126
127 const auto& fd_entry = *fd_it;
128 std::error_code ec_link;
129 auto link = fs::read_symlink(fd_entry.path(), ec_link);
130 if (ec_link) continue;
131 const std::string target = link.string();
132
133 for (const auto& prefix : prefixes) {
134 if (target.find(prefix) == 0) {
135 std::ifstream comm("/proc/" + pid_str + "/comm");
136 std::string name;
137 std::getline(comm, name);
138 if (name.empty()) name = "(sconosciuto)";
139 result.push_back(name + " (PID=" + pid_str + ")");
140 break;
141 }
142 }
143 }
144 }
145
146 std::sort(result.begin(), result.end());
147 result.erase(std::unique(result.begin(), result.end()), result.end());
148 return result;
149}
150
151
153 std::set<std::string> known;
154 write_log(_("[privacyd] Avviato monitoraggio webcam/microfono."));
155
156 while (running) {
157 try {
158 auto active = detect_active_devices();
159
160 for (const auto& proc : active) {
161 if (!known.count(proc)) {
162 known.insert(proc);
163 write_log(_("Nuovo accesso: ") + proc);
164 }
165 }
166
167 for (auto it = known.begin(); it != known.end();) {
168 if (std::find(active.begin(), active.end(), *it) == active.end()) {
169 write_log(
170 Glib::ustring::compose(
171 _("Accesso terminato: %1"),
172 *it
173 )
174 );
175 it = known.erase(it);
176 } else ++it;
177 }
178 } catch (const std::exception& e) {
179 // Difesa in profondità: qualsiasi eccezione non catturata qui
180 // ucciderebbe il daemon.
181 try { write_log(std::string("[privacyd] Errore monitor_loop: ") + e.what()); }
182 catch (...) {}
183 } catch (...) {
184 try { write_log("[privacyd] Errore sconosciuto in monitor_loop"); }
185 catch (...) {}
186 }
187
188 std::this_thread::sleep_for(std::chrono::seconds(3));
189 }
190
191 write_log(_("[privacyd] Monitoraggio terminato."));
192}
193
194
195void handle_signal(int) { running = false; }
196
197int main(int argc, char** argv) {
198 setlocale(LC_ALL, "");
199 bindtextdomain("BastionGuard", LOCALEDIR);
200 bind_textdomain_codeset("BastionGuard", "UTF-8");
201 textdomain("BastionGuard");
202
203 std::signal(SIGINT, handle_signal);
204 std::signal(SIGTERM, handle_signal);
205
206 write_log(_("=== Avvio BastionGuard-privacyd (headless) ==="));
207
208 std::thread monitor(monitor_loop);
209 while (running)
210 std::this_thread::sleep_for(std::chrono::seconds(1));
211
212 if (monitor.joinable()) monitor.join();
213
214 write_log(_("=== Arresto BastionGuard-privacyd ==="));
215 return 0;
216}
#define _(s)
void handle_signal(int)
std::vector< std::string > detect_active_devices()
static std::mutex log_mutex
void monitor_loop()
void write_log(const std::string &msg)
static std::atomic< bool > running
int main(int argc, char *argv[])
#define LOCALEDIR