21#include <boost/asio.hpp>
22#include <boost/asio/signal_set.hpp>
23#include <boost/asio/thread_pool.hpp>
24#include <boost/asio/ssl.hpp>
26#include <include/cef_app.h>
27#include <include/cef_command_line.h>
28#include <glib/gi18n.h>
29#include <libsoup/soup.h>
47#include <unordered_map>
48#include <unordered_set>
59#include "Resource.hpp"
67# define LOCALEDIR "/usr/share/BastionGuard/locale"
69#ifndef BG_WARNING_HTTP_PORT
70# define BG_WARNING_HTTP_PORT 81
72#ifndef BG_WARNING_HTTPS_PORT
73# define BG_WARNING_HTTPS_PORT 444
75#ifndef BG_WARNING_ADDR
76# define BG_WARNING_ADDR "127.0.0.2"
82 std::max(2u, std::thread::hardware_concurrency());
84namespace asio = boost::asio;
85using tcp = asio::ip::tcp;
86using Clock = std::chrono::steady_clock;
91 const auto dots = std::count(host.begin(), host.end(),
'.');
92 if (dots <= 1)
return host;
93 const auto p1 = host.rfind(
'.');
94 const auto p2 = host.rfind(
'.', p1 - 1);
95 return host.substr(p2 + 1);
105static std::unordered_map<std::string, SessionEntry>
g_sessions;
116 bool expired =
false;
121 if (Clock::now() - it->second.opened_at >
SESSION_TTL) {
125 out_type = it->second.type;
148 it->second.child_pid = pid;
152 bool removed =
false;
159 std::snprintf(msg,
sizeof(msg),
160 _(
"[bastionguard] session closed for root: %s — browser proxy restored\n"),
167 bool removed =
false;
171 if (it !=
g_sessions.end() && it->second.child_pid == pid) {
178 std::snprintf(msg,
sizeof(msg),
179 _(
"[bastionguard] secure browser pid %ld closed for root: %s — browser proxy restored\n"),
180 static_cast<long>(pid), root.c_str());
188 std::array<char, 64 * 1024>
cbuf_{};
189 std::array<char, 64 * 1024>
rbuf_{};
198 auto self = shared_from_this();
200 [
this, self](boost::system::error_code ec, std::size_t n) {
201 if (ec || n == 0) {
shutdown();
return; }
202 asio::async_write(
remote_, asio::buffer(
cbuf_.data(), n),
203 [
this, self](boost::system::error_code wec, std::size_t) {
204 if (wec) { shutdown(); return; }
211 auto self = shared_from_this();
213 [
this, self](boost::system::error_code ec, std::size_t n) {
214 if (ec || n == 0) {
shutdown();
return; }
215 asio::async_write(
client_, asio::buffer(
rbuf_.data(), n),
216 [
this, self](boost::system::error_code wec, std::size_t) {
217 if (wec) { shutdown(); return; }
224 if (
closed_.exchange(
true))
return;
225 boost::system::error_code ignore;
235 const std::string& root) {
236 const std::string binary = is_bank ?
"/usr/bin/BastionGuard-bankopener"
237 :
"/usr/bin/BastionGuard-secure";
245 std::cerr <<
_(
"[bastionguard] fork() fallita per launch_sandbox\n");
252 setenv(
"BG_SANDBOX",
"1", 1);
253 execl(binary.c_str(), binary.c_str(), url.c_str(), (
char*)
nullptr);
265 std::snprintf(msg,
sizeof(msg),
266 _(
"[bastionguard] secure browser started: pid=%ld root=%s binary=%s\n"),
267 static_cast<long>(pid), root.c_str(), binary.c_str());
271 std::thread([pid, root]() {
275 rc = waitpid(pid, &status, 0);
276 }
while (rc < 0 && errno == EINTR);
279 const int wait_err = errno;
281 std::snprintf(msg,
sizeof(msg),
282 _(
"[bastionguard] waitpid(%ld) failed for root %s: %s; uso TTL fallback\n"),
283 static_cast<long>(pid), root.c_str(), std::strerror(wait_err));
310 std::istringstream ss(first_line);
311 std::string method, target, ver;
312 ss >> method >> target >> ver;
313 if (method.empty() || target.empty())
return false;
317 if (method ==
"CONNECT") {
318 const auto p = target.find(
':');
319 if (p == std::string::npos)
return false;
320 out.
host = target.substr(0, p);
321 out.
port = target.substr(p + 1);
323 return !out.
host.empty() && !out.
port.empty();
326 if (target.size() > 7 && target.substr(0, 7) ==
"http://") {
327 std::string rest = target.substr(7);
328 const auto slash = rest.find(
'/');
329 const std::string authority = slash != std::string::npos ? rest.substr(0, slash) : rest;
330 out.
path = slash != std::string::npos ? rest.substr(slash) :
"/";
332 const auto colon = authority.rfind(
':');
333 if (colon != std::string::npos) {
334 out.
host = authority.substr(0, colon);
335 out.
port = authority.substr(colon + 1);
337 out.
host = authority;
342 return !out.
host.empty();
348static void async_tunnel(tcp::socket sock,
const std::string& host,
const std::string& port) {
349 auto resolver = std::make_shared<tcp::resolver>(
g_io);
350 auto psock = std::make_shared<tcp::socket>(std::move(sock));
352 resolver->async_resolve(host, port,
353 [resolver, psock](boost::system::error_code ec, tcp::resolver::results_type eps)
mutable {
355 static constexpr char bad[] =
356 "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n";
357 asio::async_write(*psock, asio::buffer(bad,
sizeof(bad) - 1), [psock](
auto,
auto) {});
361 auto remote = std::make_shared<tcp::socket>(
g_io);
362 asio::async_connect(*remote, eps,
363 [remote, psock](boost::system::error_code ec,
const tcp::endpoint&)
mutable {
365 static constexpr char bad[] =
366 "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n";
367 asio::async_write(*psock, asio::buffer(bad,
sizeof(bad) - 1), [psock](
auto,
auto) {});
371 static constexpr char ok[] =
372 "HTTP/1.1 200 Connection Established\r\n\r\n";
374 auto ok_buf = std::make_shared<std::string>(ok);
375 asio::async_write(*psock, asio::buffer(*ok_buf),
376 [ok_buf, psock, remote](boost::system::error_code ec, std::size_t)
mutable {
378 auto session = std::make_shared<TunnelSession>(std::move(*psock), std::move(*remote));
387 const std::string& full_request)
389 std::string rewritten = req.
method +
" " + req.
path +
" HTTP/1.1\r\n";
391 static const std::vector<std::string> strip_headers = {
395 "x-forwarded-proto:",
399 "proxy-authorization:",
400 "proxy-authenticate:",
403 auto header_should_strip = [](
const std::string& line) ->
bool {
404 std::string lower = line;
405 for (
auto& c : lower) c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
406 for (
const auto& h : strip_headers) {
407 if (lower.rfind(h, 0) == 0)
return true;
412 const auto first_crlf = full_request.find(
"\r\n");
413 if (first_crlf != std::string::npos) {
414 std::istringstream ss(full_request.substr(first_crlf + 2));
415 std::string hdr_line;
416 while (std::getline(ss, hdr_line)) {
417 if (!hdr_line.empty() && hdr_line.back() ==
'\r')
419 if (hdr_line.empty()) {
423 if (!header_should_strip(hdr_line))
424 rewritten += hdr_line +
"\r\n";
430 auto resolver = std::make_shared<tcp::resolver>(
g_io);
431 auto psock = std::make_shared<tcp::socket>(std::move(sock));
432 auto req_buf = std::make_shared<std::string>(std::move(rewritten));
434 resolver->async_resolve(req.
host, req.
port,
435 [resolver, psock, req_buf](boost::system::error_code ec, tcp::resolver::results_type eps)
mutable {
437 static constexpr char bad[] =
438 "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n";
439 asio::async_write(*psock, asio::buffer(bad, sizeof(bad) - 1), [psock](auto, auto) {});
443 auto remote = std::make_shared<tcp::socket>(
g_io);
444 asio::async_connect(*remote, eps,
445 [remote, psock, req_buf](boost::system::error_code ec,
const tcp::endpoint&)
mutable {
447 static constexpr char bad[] =
448 "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n";
449 asio::async_write(*psock, asio::buffer(bad, sizeof(bad) - 1), [psock](auto, auto) {});
453 asio::async_write(*remote, asio::buffer(*req_buf),
454 [remote, psock, req_buf](boost::system::error_code ec, std::size_t)
mutable {
456 auto session = std::make_shared<TunnelSession>(std::move(*psock), std::move(*remote));
464 return "https://bastionguard.eu/secure.html?host=" + host +
"&type=" + type;
470 +
"/" + host +
"?type=" + type;
478 const std::string& host,
479 const std::string& type,
491 std::thread([root, url, argc, argv]() {
494 }
catch (
const std::exception& e) {
496 std::snprintf(buf,
sizeof(buf),
497 _(
"[bastionguard] open_notify exception: %s\n"), e.what());
500 std::cerr <<
_(
"[bastionguard] open_notify unknown exception\n");
543 const std::string& type)
546 static constexpr char ok200[] =
547 "HTTP/1.1 200 Connection Established\r\n\r\n";
549 const std::string intercept_host = req.
host;
550 const std::string intercept_type = type;
552 auto ok_buf = std::make_shared<std::string>(ok200);
553 asio::async_write(*sock, asio::buffer(*ok_buf),
554 [sock, ok_buf, intercept_host, intercept_type](
555 boost::system::error_code ec, std::size_t)
mutable {
557 std::thread([s = std::move(*sock),
558 host = intercept_host,
559 t = intercept_type]()
mutable {
561 TlsIntercept::do_intercept(std::move(s), host, t);
566 auto redir = std::make_shared<std::string>(
567 "HTTP/1.1 302 Found\r\n"
568 "Cache-Control: no-store\r\n"
569 "Pragma: no-cache\r\n"
570 "Location: " + loc +
"\r\n"
571 "Connection: close\r\n\r\n");
572 asio::async_write(*sock, asio::buffer(*redir), [sock, redir](
auto,
auto) {});
578 const std::string& type,
579 const std::string& root,
580 int argc,
char** argv)
583 static constexpr char ok200[] =
584 "HTTP/1.1 200 Connection Established\r\n\r\n";
586 const std::string intercept_host = req.
host;
587 const std::string intercept_type = type;
588 const bool is_bank_type = (type ==
"bank");
590 auto ok_buf = std::make_shared<std::string>(ok200);
591 asio::async_write(*sock, asio::buffer(*ok_buf),
592 [sock, ok_buf, intercept_host, intercept_type, is_bank_type, root, argc, argv](
593 boost::system::error_code ec, std::size_t)
mutable {
596 std::thread([s = std::move(*sock),
597 host = intercept_host,
601 argc, argv]()
mutable {
602 auto on_url_ready = [bank, r, t, host, argc, argv](
const std::string& full_url) {
619 TlsIntercept::do_intercept(std::move(s), host, t,
620 std::move(on_url_ready));
624 const std::string full_url =
"https://" + req.
host + req.
path;
628 std::thread([full_url, type, root]() {
637 auto redir = std::make_shared<std::string>(
638 "HTTP/1.1 302 Found\r\n"
639 "Cache-Control: no-store\r\n"
640 "Pragma: no-cache\r\n"
641 "Location: " + loc +
"\r\n"
642 "Connection: close\r\n\r\n");
643 asio::async_write(*sock, asio::buffer(*redir), [sock, redir](
auto,
auto) {});
648 auto sbuf = std::make_shared<asio::streambuf>();
649 auto sock = std::make_shared<tcp::socket>(std::move(client));
651 asio::async_read_until(*sock, *sbuf,
"\r\n\r\n",
652 [sock, sbuf, argc, argv](boost::system::error_code ec, std::size_t)
mutable {
655 std::string full_request{
656 asio::buffers_begin(sbuf->data()),
657 asio::buffers_end(sbuf->data())
660 std::istringstream is(full_request);
661 std::string first_line;
662 std::getline(is, first_line);
663 if (!first_line.empty() && first_line.back() ==
'\r')
664 first_line.pop_back();
668 static constexpr char resp[] =
669 "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n";
670 asio::async_write(*sock, asio::buffer(resp,
sizeof(resp) - 1), [sock](
auto,
auto) {});
689 std::snprintf(dbg,
sizeof(dbg),
690 "[bastionguard] LAN/loopback bypass: %s\n",
708 std::snprintf(dbg,
sizeof(dbg),
709 "[bastionguard] request host=%s bypass=%d intercept=%d bank=%d method=%s\n",
711 static_cast<int>(bypass),
712 static_cast<int>(secure),
713 static_cast<int>(bank),
721 std::snprintf(dbg,
sizeof(dbg),
722 "[bastionguard] PROXY-BYPASS (user configured): %s\n",
733 std::string active_type;
734 const bool has_active_session =
session_active(root, active_type);
738 std::snprintf(dbg,
sizeof(dbg),
739 "[bastionguard] TUNNEL-ONLY (no MITM): %s\n",
749 if (has_active_session) {
751 std::snprintf(msg,
sizeof(msg),
752 _(
"[bastionguard] secure domain already active, blocking again: %s (session: %s)\n"),
753 req.
host.c_str(), root.c_str());
758 const std::string type = bank ?
"bank" :
"payment";
762 std::snprintf(msg,
sizeof(msg),
763 _(
"[bastionguard] %s intercepted: %s (root: %s)\n"),
764 type.c_str(), req.
host.c_str(), root.c_str());
776static void do_accept(tcp::acceptor& acceptor,
int argc,
char** argv) {
777 acceptor.async_accept(
g_io,
778 [&acceptor, argc, argv](boost::system::error_code ec, tcp::socket client) {
779 if (ec == asio::error::operation_aborted)
return;
785 std::snprintf(buf,
sizeof(buf),
_(
"[cef] accept error: %s\n"), ec.message().c_str());
795class BastionApp :
public CefApp,
public CefBrowserProcessHandler {
804 cmd->AppendSwitch(
"incognito");
805 cmd->AppendSwitch(
"disable-extensions");
806 cmd->AppendSwitch(
"no-first-run");
807 cmd->AppendSwitch(
"no-default-browser-check");
808 cmd->AppendSwitch(
"disable-gpu");
809 cmd->AppendSwitch(
"disable-gpu-compositing");
810 cmd->AppendSwitch(
"disable-software-rasterizer");
811 cmd->AppendSwitch(
"disable-gpu-process-crash-limit");
812 cmd->AppendSwitch(
"in-process-gpu");
813 cmd->AppendSwitch(
"disable-accelerated-2d-canvas");
814 cmd->AppendSwitch(
"disable-accelerated-video-decode");
815 cmd->AppendSwitch(
"disable-webgl");
823 if (!cmd->HasSwitch(
"no-sandbox"))
824 cmd->AppendSwitch(
"no-sandbox");
825 if (!cmd->HasSwitch(
"disable-setuid-sandbox"))
826 cmd->AppendSwitch(
"disable-setuid-sandbox");
827 if (!cmd->HasSwitch(
"disable-namespace-sandbox"))
828 cmd->AppendSwitch(
"disable-namespace-sandbox");
843static bool parse_args(
int argc,
char** argv, std::string& listen_host,
int& listen_port) {
844 for (
int i = 1; i < argc; ++i) {
845 const std::string_view a = argv[i];
847 if (a.starts_with(
"--type=") || a.starts_with(
"--lang=") ||
848 a.starts_with(
"--user-data-dir=") || a.starts_with(
"--enable-") ||
849 a.starts_with(
"--disable-") || a.starts_with(
"--service-") ||
850 a.starts_with(
"--utility-") || a.starts_with(
"--ozone-") ||
851 a.starts_with(
"--render-node-") || a.starts_with(
"--change-stack-") ||
852 a.starts_with(
"--no-zygote"))
855 if (a ==
"--listen" && i + 1 < argc) {
856 listen_host = argv[++i];
857 }
else if (a ==
"--port" && i + 1 < argc) {
859 const int p = std::stoi(argv[++i]);
860 if (p < 1 || p > 65535)
throw std::out_of_range(
"port");
863 std::cerr <<
_(
"[args] invalid --port value\n");
889 if (std::getenv(
"BG_SANDBOX"))
return true;
890 if (std::getenv(
"BG_NO_CEF_SANDBOX"))
return true;
893 if (std::getenv(
"FLATPAK_ID"))
return true;
894 if (std::getenv(
"SNAP"))
return true;
895 if (access(
"/.flatpak-info", F_OK) == 0)
return true;
898 if (access(
"/run/.containerenv", F_OK) == 0)
return true;
900 std::ifstream cg(
"/proc/1/cgroup");
902 while (std::getline(cg, line)) {
903 if (line.find(
"docker") != std::string::npos ||
904 line.find(
"libpod") != std::string::npos ||
905 line.find(
"/lxc") != std::string::npos) {
915 std::ifstream f(
"/proc/sys/kernel/unprivileged_userns_clone");
919 if (v == 0)
return true;
927 static const char* helpers[] = {
928 "/usr/share/BastionGuard/cef/chrome-sandbox",
929 "/usr/lib/BastionGuard/chrome-sandbox",
931 bool any_helper =
false;
932 for (
const char* h : helpers) {
934 if (stat(h, &st) == 0) {
937 if ((st.st_mode &
S_ISUID) && st.st_uid == 0) {
944 std::ifstream f(
"/proc/sys/kernel/unprivileged_userns_clone");
946 if (f.good()) f >> v;
947 if (v == 0)
return true;
955int main(
int argc,
char** argv) {
957 std::cerr <<
"[bastionguard-cef] CEF disabilitato su questa build o distribuzione.\n";
963 bindtextdomain(
"bastionguard",
LOCALEDIR);
964 bind_textdomain_codeset(
"bastionguard",
"UTF-8");
965 textdomain(
"bastionguard");
967 CefMainArgs main_args(argc, argv);
970 const int exit_code = CefExecuteProcess(main_args, app,
nullptr);
971 if (exit_code >= 0)
return exit_code;
973 std::string listen_host =
"127.0.0.1";
974 int listen_port = 3130;
975 if (!
parse_args(argc, argv, listen_host, listen_port))
return 2;
977 CefSettings settings;
986 settings.no_sandbox = confined;
987 settings.multi_threaded_message_loop =
true;
988 settings.windowless_rendering_enabled =
false;
991 std::cerr <<
_(
"[cef] ambiente confinato rilevato → sandbox CEF disabilitato\n");
994 if (!CefInitialize(main_args, settings, app,
nullptr)) {
995 std::cerr <<
_(
"[cef] CefInitialize failed\n");
999 std::cerr << (settings.no_sandbox
1000 ?
_(
"[cef] initialized (sandbox OFF)\n")
1001 :
_(
"[cef] initialized (sandbox ON)\n"));
1002 CaInstaller::ensure_trusted();
1009 warning_server.
start();
1011 ProxyConfig::apply({ listen_host, listen_port });
1016 ProxyConfig::restore();
1019 } guard{warning_server};
1021 std::vector<std::thread> io_threads;
1026 std::snprintf(buf,
sizeof(buf),
_(
"[cef] starting %u I/O threads\n"),
IO_THREADS);
1030 asio::signal_set sig(
g_io, SIGINT, SIGTERM);
1031 sig.async_wait([&](
const boost::system::error_code&,
int) {
1032 std::cerr <<
_(
"[cef] signal received, exiting\n");
1033 ProxyConfig::restore();
1038 asio::signal_set sighup(
g_io, SIGHUP);
1039 std::function<void()> arm_sighup = [&]() {
1040 sighup.async_wait([&](
const boost::system::error_code& ec,
int) {
1042 std::cerr <<
_(
"[filter] SIGHUP: reloading domain lists\n");
1049 tcp::acceptor acceptor(
g_io);
1051 boost::system::error_code ec;
1052 tcp::endpoint ep(asio::ip::make_address(listen_host),
1053 static_cast<unsigned short>(listen_port));
1055 acceptor.open(ep.protocol(), ec);
1056 if (!ec) acceptor.set_option(asio::socket_base::reuse_address(
true), ec);
1057 if (!ec) acceptor.bind(ep, ec);
1058 if (!ec) acceptor.listen(asio::socket_base::max_listen_connections, ec);
1062 std::snprintf(buf,
sizeof(buf),
_(
"[cef] acceptor error: %s\n"), ec.message().c_str());
1064 ProxyConfig::restore();
1065 warning_server.
stop();
1073 std::snprintf(buf,
sizeof(buf),
_(
"[cef] backend proxy listening on %s:%d\n"),
1074 listen_host.c_str(), listen_port);
1080 for (
unsigned int i = 1; i <
IO_THREADS; ++i)
1081 io_threads.emplace_back([]() { g_io.run(); });
1085 for (
auto& t : io_threads)
1086 if (t.joinable()) t.join();
int main(int argc, char *argv[])
DomainFilter & domain_filter()
static bool running_confined()
static bool parse_args(int argc, char **argv, std::string &listen_host, int &listen_port)
static bool session_try_open(const std::string &root, const std::string &type)
static bool sandbox_try_open(const std::string &root)
static void block_active_session(std::shared_ptr< tcp::socket > sock, const ParsedRequest &req, const std::string &type)
std::chrono::steady_clock Clock
static std::unordered_map< std::string, SessionEntry > g_sessions
static void launch_sandbox(const std::string &url, bool is_bank, const std::string &root)
static void async_tunnel(tcp::socket sock, const std::string &host, const std::string &port)
static std::mutex g_session_mutex
static std::unordered_set< std::string > g_notify_open
static void do_accept(tcp::acceptor &acceptor, int argc, char **argv)
#define BG_WARNING_HTTP_PORT
static void session_close_if_pid(const std::string &root, pid_t pid)
static void async_forward_http(tcp::socket sock, const ParsedRequest &req, const std::string &full_request)
static std::string local_warning_url_for(const std::string &host, const std::string &type)
static bool session_active(const std::string &root, std::string &out_type)
#define BG_WARNING_HTTPS_PORT
static void open_notify_window(const std::string &root, const std::string &host, const std::string &type, int argc, char **argv)
static void session_close(const std::string &root)
static void sandbox_bind_pid(const std::string &root, pid_t pid)
static std::string root_domain(const std::string &host)
static void sandbox_close(const std::string &root)
static void block_secure_request(std::shared_ptr< tcp::socket > sock, const ParsedRequest &req, const std::string &type, const std::string &root, int argc, char **argv)
static const unsigned int IO_THREADS
static std::mutex g_sandbox_mutex
static void handle_client(tcp::socket client, int argc, char **argv)
static std::string warning_url_for(const std::string &host, const std::string &type)
static bool parse_request(const std::string &first_line, ParsedRequest &out)
static std::unordered_map< std::string, pid_t > g_sandbox_pid
static std::mutex g_notify_mutex
static std::unordered_set< std::string > g_sandbox_open
static void session_bind_pid(const std::string &root, pid_t pid)
static void sandbox_close_if_pid(const std::string &root, pid_t pid)
static asio::io_context g_io
static constexpr auto SESSION_TTL
IMPLEMENT_REFCOUNTING(BastionApp)
CefRefPtr< CefBrowserProcessHandler > GetBrowserProcessHandler() override
void OnBeforeCommandLineProcessing(const CefString &, CefRefPtr< CefCommandLine > cmd) override
void set_no_sandbox(bool v)
bool is_secure(const std::string &host) const
bool is_bank(const std::string &host) const
static bool is_local_network(const std::string &host_raw)
bool is_proxy_bypass(const std::string &host) const
static void open_notify(const std::string &url, int argc, char **argv)
static void apply_language()
Clock::time_point opened_at
std::atomic< bool > closed_
std::array< char, 64 *1024 > rbuf_
std::array< char, 64 *1024 > cbuf_
TunnelSession(tcp::socket c, tcp::socket r)