Skip to content
API Documentation
BastionGuard™
native_host.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
22#include <iostream>
23#include <string>
24#include <vector>
25#include <cstdint>
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/wait.h>
29#include <fstream>
30#include <chrono>
31#include <iomanip>
32#include <sstream>
33#include <cstdlib>
34#include <filesystem>
35#include <unordered_set>
36#include <algorithm>
37#include <cctype>
38#include <nlohmann/json.hpp>
39
40using json = nlohmann::json;
41namespace fs = std::filesystem;
42
43static std::string now_iso() {
44 using namespace std::chrono;
45 auto t = system_clock::now();
46 std::time_t tt = system_clock::to_time_t(t);
47 std::tm tm{};
48 gmtime_r(&tt, &tm);
49 std::ostringstream ss;
50 ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
51 return ss.str();
52}
53
54
55static bool read_exact(std::istream &in, void* buf, std::size_t n) {
56 std::size_t off = 0;
57 char* p = static_cast<char*>(buf);
58 while (off < n) {
59 in.read(p + off, n - off);
60 std::streamsize got = in.gcount();
61 if (got <= 0) return false;
62 off += static_cast<std::size_t>(got);
63 }
64 return true;
65}
66
68 uint32_t len = 0;
69 if (!read_exact(std::cin, &len, sizeof(len))) throw std::runtime_error("EOF reading length");
70 uint32_t L = (uint8_t)reinterpret_cast<char*>(&len)[0]
71 | ((uint8_t)reinterpret_cast<char*>(&len)[1] << 8)
72 | ((uint8_t)reinterpret_cast<char*>(&len)[2] << 16)
73 | ((uint8_t)reinterpret_cast<char*>(&len)[3] << 24);
74 std::vector<char> buf(L);
75 if (!read_exact(std::cin, buf.data(), L)) throw std::runtime_error("EOF reading payload");
76 std::string s(buf.begin(), buf.end());
77 return json::parse(s, nullptr, false);
78}
79
80void send_message(const json &j) {
81 std::string s = j.dump();
82 uint32_t L = static_cast<uint32_t>(s.size());
83 unsigned char lenbuf[4];
84 lenbuf[0] = static_cast<unsigned char>(L & 0xff);
85 lenbuf[1] = static_cast<unsigned char>((L >> 8) & 0xff);
86 lenbuf[2] = static_cast<unsigned char>((L >> 16) & 0xff);
87 lenbuf[3] = static_cast<unsigned char>((L >> 24) & 0xff);
88 std::cout.write(reinterpret_cast<char*>(lenbuf), 4);
89 std::cout.write(s.data(), s.size());
90 std::cout.flush();
91}
92
93static std::unordered_set<std::string> cached_bl;
94static std::time_t cached_bl_mtime = 0;
95static fs::path cached_bl_path;
96
97static inline std::string str_tolower(std::string s) {
98 std::transform(s.begin(), s.end(), s.begin(),
99 [](unsigned char c){ return std::tolower(c); });
100 return s;
101}
102static inline std::string str_trim(std::string s) {
103 const char* ws = " \t\r\n";
104 size_t start = s.find_first_not_of(ws);
105 if (start==std::string::npos) return "";
106 size_t end = s.find_last_not_of(ws);
107 return s.substr(start, end-start+1);
108}
109
110
111static std::time_t to_time_t(fs::file_time_type ftime) {
112 using namespace std::chrono;
113 auto sctp = time_point_cast<system_clock::duration>(
114 ftime - fs::file_time_type::clock::now()
115 + system_clock::now()
116 );
117 return system_clock::to_time_t(sctp);
118}
119
121 const char* home = std::getenv("HOME");
122 if (!home) return;
123 fs::path bl = fs::path(home) / ".local/share/BastionGuard/data/phishing/blacklist.txt";
124
125 std::error_code ec;
126 if (!fs::exists(bl, ec)) {
127 std::cerr << "[native-host] blacklist not found: " << bl << std::endl;
128 cached_bl.clear();
129 cached_bl_mtime = 0;
130 return;
131 }
132 auto ftime = fs::last_write_time(bl, ec);
133 if (ec) return;
134 std::time_t mtime = to_time_t(ftime);
135 if (mtime == cached_bl_mtime && !cached_bl.empty()) return;
136
137 std::ifstream f(bl);
138 if (!f.is_open()) {
139 std::cerr << "[native-host] cannot open blacklist file" << std::endl;
140 return;
141 }
142 std::unordered_set<std::string> newset;
143 std::string line;
144 while (std::getline(f, line)) {
145 line = str_trim(line);
146 if (line.empty() || line[0]=='#') continue;
147 if (!line.empty() && line.back()=='\r') line.pop_back();
148 newset.insert(str_tolower(line));
149 }
150 cached_bl = std::move(newset);
151 cached_bl_mtime = mtime;
152 cached_bl_path = bl;
153 std::cerr << "[native-host] loaded blacklist entries=" << cached_bl.size() << std::endl;
154}
155
156static std::string extract_host_from_url(const std::string &url) {
157 size_t p = url.find("://");
158 size_t start = (p==std::string::npos) ? 0 : p+3;
159 size_t at = url.find('@', start);
160 if (at != std::string::npos) start = at+1;
161 size_t end = url.find_first_of("/:?#", start);
162 std::string host = (end==std::string::npos) ? url.substr(start) : url.substr(start, end-start);
163 size_t colon = host.find(':');
164 if (colon != std::string::npos) host = host.substr(0, colon);
165 return str_tolower(str_trim(host));
166}
167
168
169json action_set_lang(const json &req) { return {{"ok", true}}; } // stub
170json action_restart_scanner(const json &) { return {{"ok", true}}; } // stub
171json action_open_page(const json &req) { return {{"ok", true}}; } // stub
172json action_ping(const json &) { return { {"ok", true}, {"pong_at", now_iso()} }; }
173
175 try {
176 if (!req.contains("url") || !req["url"].is_string())
177 return { {"ok", false}, {"error", "missing 'url' string"} };
178
179 std::string url = req["url"].get<std::string>();
181
182 json extra;
183 extra["block"] = false;
184 extra["reason"] = "not_in_blacklist";
185
186 if (!cached_bl.empty()) {
187 std::string host = extract_host_from_url(url);
188 if (!host.empty()) {
189 if (cached_bl.find(host) != cached_bl.end()) {
190 extra["block"] = true;
191 extra["reason"] = "host_exact: " + host;
192 } else {
193 for (const auto &entry : cached_bl) {
194 if (entry.empty()) continue;
195 if (host.size() >= entry.size() &&
196 host.compare(host.size()-entry.size(), entry.size(), entry) == 0) {
197 extra["block"] = true;
198 extra["reason"] = "host_suffix: " + entry;
199 break;
200 }
201 }
202 }
203 } else {
204 for (const auto &entry : cached_bl) {
205 if (!entry.empty() && url.find(entry)!=std::string::npos) {
206 extra["block"] = true;
207 extra["reason"] = "url_substr: " + entry;
208 break;
209 }
210 }
211 }
212 }
213
214 return { {"ok", true}, {"url", url}, {"extra", extra} };
215 } catch (const std::exception &ex) {
216 return { {"ok", false}, {"error", std::string("exception: ")+ex.what()} };
217 }
218}
219
220
222 try {
223 const char* home = std::getenv("HOME");
224 if (!home) return { {"ok", false}, {"error", "HOME non impostata"} };
225
226 fs::path config_path = fs::path(home) / ".config/BastionGuard/mail.json";
227
228 std::error_code ec;
229 if (!fs::exists(config_path, ec))
230 return { {"ok", false}, {"error", "mail.json non trovato: " + config_path.string()} };
231
232 std::ifstream f(config_path);
233 if (!f.is_open())
234 return { {"ok", false}, {"error", "impossibile aprire mail.json"} };
235
236 json config = json::parse(f, nullptr, false);
237 if (config.is_discarded())
238 return { {"ok", false}, {"error", "mail.json non è JSON valido"} };
239
240 return { {"ok", true}, {"config", config} };
241
242 } catch (const std::exception &ex) {
243 return { {"ok", false}, {"error", std::string("eccezione: ") + ex.what()} };
244 }
245}
246
247json dispatch(const json &req) {
248 if (!req.is_object()) return { {"ok", false}, {"error", "request not an object"} };
249 if (!req.contains("action") || !req["action"].is_string()) return { {"ok", false}, {"error", "missing 'action'"} };
250 std::string act = req["action"].get<std::string>();
251
252 if (act == "set_lang") return action_set_lang(req);
253 if (act == "restart_scanner") return action_restart_scanner(req);
254 if (act == "open_page") return action_open_page(req);
255 if (act == "ping") return action_ping(req);
256 if (act == "check_url") return action_check_url(req);
257 if (act == "get_mail_config") return action_get_mail_config(req);
258
259 return { {"ok", false}, {"error", "unknown action: " + act} };
260}
261
262
263int main() {
264 try {
265 while (true) {
266 json req = read_message();
267 if (req.is_discarded()) break;
268 std::cerr << "[native-host] req: " << req.dump() << std::endl;
269 json resp = dispatch(req);
270 resp["ts"] = now_iso();
271 send_message(resp);
272 std::cerr << "[native-host] resp: " << resp.dump() << std::endl;
273 }
274 } catch (const std::exception &ex) {
275 std::cerr << "[native-host] fatal: " << ex.what() << std::endl;
276 try {
277 json e = { {"ok", false}, {"error", std::string("fatal: ") + ex.what()} };
278 send_message(e);
279 } catch(...) {}
280 return 1;
281 }
282 return 0;
283}
nlohmann::json json
Definition Backend.cpp:2908
int main(int argc, char *argv[])
static bool extract_host_from_url(const std::string &line, std::string &host)
static std::string str_trim(std::string s)
json action_set_lang(const json &req)
static std::string now_iso()
static std::time_t to_time_t(fs::file_time_type ftime)
json action_open_page(const json &req)
static std::time_t cached_bl_mtime
json action_check_url(const json &req)
static std::unordered_set< std::string > cached_bl
static bool read_exact(std::istream &in, void *buf, std::size_t n)
json read_message()
static fs::path cached_bl_path
json dispatch(const json &req)
static void reload_blacklist_if_needed()
json action_ping(const json &)
json action_get_mail_config(const json &)
void send_message(const json &j)
static std::string str_tolower(std::string s)
json action_restart_scanner(const json &)