33#include <glib/gi18n.h>
37namespace fs = std::filesystem;
75 "*.local,*.lan,*.intranet,*.internal,*.home.arpa,*.corp,*.private";
85 "172.16.,172.17.,172.18.,172.19.,"
86 "172.20.,172.21.,172.22.,172.23.,"
87 "172.24.,172.25.,172.26.,172.27.,"
88 "172.28.,172.29.,172.30.,172.31.,"
90 ".local,.lan,.intranet,.internal,.home.arpa,.corp,.private";
101 "'::1','fe80::/10','fc00::/7',"
102 "'*.local','*.lan','*.intranet','*.internal','*.home.arpa']";
105 const char* h = std::getenv(
"HOME");
109static void write_file(
const fs::path& p,
const std::string& content) {
115 if (!fs::exists(p))
return;
117 std::ostringstream out;
119 while (std::getline(in, line))
120 if (line.find(needle) == std::string::npos)
126static constexpr const char*
FF_MARKER =
"// BastionGuard-proxy";
130 const fs::path ini = profiles_dir /
"profiles.ini";
131 if (!fs::exists(ini))
return {};
135 bool is_relative =
true;
136 bool is_default =
false;
139 std::vector<Profile> profiles;
140 std::string install_default;
141 bool in_install =
false;
142 bool in_profile =
false;
144 std::ifstream f(ini);
147 while (std::getline(f, line)) {
148 line.erase(0, line.find_first_not_of(
" \t\r\n"));
149 if (!line.empty()) line.erase(line.find_last_not_of(
" \t\r\n") + 1);
150 if (line.empty() || line[0] ==
';')
continue;
152 if (line[0] ==
'[') {
153 in_install = (line.rfind(
"[Install", 0) == 0);
154 in_profile = (line.rfind(
"[Profile", 0) == 0);
155 if (in_profile) profiles.push_back({});
156 }
else if (in_install) {
157 if (line.rfind(
"Default=", 0) == 0)
158 install_default = line.substr(8);
159 }
else if (in_profile && !profiles.empty()) {
160 auto& p = profiles.back();
161 if (line.rfind(
"Default=1", 0) == 0) p.is_default =
true;
162 else if (line.rfind(
"Path=", 0) == 0) p.path = line.substr(5);
163 else if (line.rfind(
"IsRelative=0", 0) == 0) p.is_relative =
false;
166 if (!install_default.empty()) {
167 for (
const auto& p : profiles) {
168 if (p.path == install_default) {
169 fs::path full = p.is_relative
170 ? profiles_dir / p.path
172 if (fs::exists(full))
return full;
176 fs::path full = profiles_dir / install_default;
177 if (fs::exists(full))
return full;
179 for (
const auto& p : profiles) {
180 if (!p.path.empty() && p.path.find(
"default-release") != std::string::npos) {
181 fs::path full = p.is_relative ? profiles_dir / p.path : fs::path(p.path);
182 if (fs::exists(full))
return full;
186 for (
const auto& p : profiles) {
187 if (p.is_default && !p.path.empty()) {
188 fs::path full = p.is_relative ? profiles_dir / p.path : fs::path(p.path);
189 if (fs::exists(full))
return full;
194 for (
auto& e : fs::directory_iterator(profiles_dir, ec))
195 if (e.is_directory() && e.path().filename().string().find(
'.') != std::string::npos)
202 if (profile.empty() || !fs::exists(profile))
return;
204 const fs::path user_js = profile /
"user.js";
207 std::ofstream f(user_js, std::ios::app);
211 <<
"user_pref(\"network.proxy.type\", 1); " <<
FF_MARKER <<
"\n"
212 <<
"user_pref(\"network.proxy.http\", \"" << s.
host <<
"\"); " <<
FF_MARKER <<
"\n"
213 <<
"user_pref(\"network.proxy.http_port\", " << s.
port <<
"); " <<
FF_MARKER <<
"\n"
214 <<
"user_pref(\"network.proxy.ssl\", \"" << s.
host <<
"\"); " <<
FF_MARKER <<
"\n"
215 <<
"user_pref(\"network.proxy.ssl_port\", " << s.
port <<
"); " <<
FF_MARKER <<
"\n"
219 <<
"user_pref(\"network.proxy.allow_hijacking_localhost\", false); " <<
FF_MARKER <<
"\n";
222 std::snprintf(buf,
sizeof(buf),
223 _(
"[browser-proxy] Firefox profile configured: %s\n"), profile.c_str());
228 if (profile.empty())
return;
233 const std::string home =
home_dir();
234 if (home.empty())
return;
236 const std::vector<fs::path> roots = {
237 fs::path(home) /
".mozilla/firefox",
238 fs::path(home) /
".librewolf",
239 fs::path(home) /
".waterfox",
240 fs::path(home) /
"snap/firefox/common/.mozilla/firefox",
241 fs::path(home) /
".var/app/org.mozilla.firefox/.mozilla/firefox",
242 fs::path(home) /
".var/app/io.gitlab.librewolf-community/.librewolf",
245 for (
const auto& root : roots) {
246 if (!fs::exists(root))
continue;
248 if (profile.empty())
continue;
256 " \"ProxyMode\": \"fixed_servers\",\n"
257 " \"ProxyServer\": \"http://" + s.
host +
":" + std::to_string(s.
port) +
"\",\n"
258 " \"ProxyBypassList\": \"<-loopback>," + std::string(
LAN_BYPASS_CIDR) +
"\"\n"
264 fs::create_directories(dir, ec);
270 std::snprintf(buf,
sizeof(buf),
271 _(
"[browser-proxy] Chromium policy written: %s\n"), dir.c_str());
277 fs::remove(dir /
"bastionguard_proxy.json", ec);
281 const std::string home =
home_dir();
282 if (home.empty())
return;
284 const std::vector<fs::path> dirs = {
285 fs::path(home) /
".config/chromium/policies/managed",
286 fs::path(home) /
".config/google-chrome/policies/managed",
287 fs::path(home) /
".config/BraveSoftware/Brave-Browser/policies/managed",
288 fs::path(home) /
".config/microsoft-edge/policies/managed",
289 fs::path(home) /
".config/vivaldi/policies/managed",
290 fs::path(home) /
".config/opera/policies/managed",
291 fs::path(home) /
".var/app/org.chromium.Chromium/config/chromium/policies/managed",
292 fs::path(home) /
".var/app/com.google.Chrome/config/google-chrome/policies/managed",
293 fs::path(home) /
".var/app/com.brave.Browser/config/BraveSoftware/Brave-Browser/policies/managed",
294 fs::path(home) /
"snap/chromium/current/.config/chromium/policies/managed",
297 for (
const auto& dir : dirs) {
301 if (fs::exists(dir.parent_path().parent_path()))
307static constexpr const char*
BG_CA_CERT =
"/etc/BastionGuard/certs/BastionGuard-ca.crt.pem";
313 return list.
ok() && list.output.find(
BG_CA_NICK) != std::string::npos;
317 if (profile.empty() || !fs::exists(profile) || !fs::exists(
BG_CA_CERT))
return;
324 const std::string home =
home_dir();
325 if (home.empty())
return;
327 const std::vector<fs::path> roots = {
328 fs::path(home) /
".mozilla/firefox",
329 fs::path(home) /
".librewolf",
330 fs::path(home) /
".waterfox",
331 fs::path(home) /
"snap/firefox/common/.mozilla/firefox",
332 fs::path(home) /
".var/app/org.mozilla.firefox/.mozilla/firefox",
333 fs::path(home) /
".var/app/io.gitlab.librewolf-community/.librewolf",
336 for (
const auto& root : roots) {
337 if (!fs::exists(root))
continue;
339 if (!profile.empty())
346 const std::string home =
home_dir();
347 if (home.empty())
return;
349 const fs::path nssdb = fs::path(home) /
".pki/nssdb";
350 if (!fs::exists(nssdb)) {
352 fs::create_directories(nssdb, ec);
353 if (!ec) (void)
bg_process::run({
"certutil",
"-N",
"--empty-password",
"-d", nssdb.string()});
355 for (
const fs::path& database : {nssdb,
356 fs::path(home) /
"snap/chromium/current/.pki/nssdb"}) {
366 std::cerr <<
_(
"[ca-install] CA cert not found, skipping\n");
370 std::cerr <<
_(
"[ca-install] installing BastionGuard CA in browsers\n");
376 std::cerr <<
_(
"[browser-proxy] configuring browsers\n");
382 std::cerr <<
_(
"[browser-proxy] restoring browser proxy settings\n");
397static bool run(
const std::vector<std::string>& args,
bool silent =
false) {
399 if (!result.ok() && !silent)
400 std::cerr <<
"[proxy-config] command failed: " << args.front() <<
" ("
401 << result.exit_code <<
")\n";
408 const char* xdg = std::getenv(
"XDG_CURRENT_DESKTOP");
411 for (
auto& c : d) c =
static_cast<char>(std::toupper(
static_cast<unsigned char>(c)));
412 if (d.find(
"GNOME") != std::string::npos)
return DE::GNOME;
413 if (d.find(
"KDE") != std::string::npos)
return DE::KDE;
414 if (d.find(
"XFCE") != std::string::npos)
return DE::XFCE;
415 if (d.find(
"MATE") != std::string::npos)
return DE::MATE;
416 if (d.find(
"CINNAMON") != std::string::npos)
return DE::CINNAMON;
417 if (d.find(
"LXQT") != std::string::npos)
return DE::LXQT;
419 if (std::getenv(
"KDE_FULL_SESSION"))
return DE::KDE;
420 if (std::getenv(
"GNOME_DESKTOP_SESSION_ID"))
return DE::GNOME;
425 const std::string port = std::to_string(s.
port);
426 run({
"gsettings",
"set",
"org.gnome.system.proxy",
"mode",
"manual"});
427 for (
const char* scheme : {
"http",
"https"}) {
428 const std::string schema = std::string(
"org.gnome.system.proxy.") + scheme;
429 run({
"gsettings",
"set", schema,
"host", s.
host});
430 run({
"gsettings",
"set", schema,
"port", port});
432 run({
"gsettings",
"set",
"org.gnome.system.proxy.socks",
"host",
""});
433 run({
"gsettings",
"set",
"org.gnome.system.proxy.socks",
"port",
"0"});
434 run({
"gsettings",
"set",
"org.gnome.system.proxy",
"ignore-hosts",
439 run({
"gsettings",
"set",
"org.gnome.system.proxy",
"mode",
"none"});
444 if (!
run({
"kioclient6",
"clear"},
true))
445 (
void)
run({
"kioclient5",
"clear"},
true);
449 const std::string hp =
"http://" + s.
host +
":" + std::to_string(s.
port);
450 for (
const char* bin : {
"kwriteconfig6",
"kwriteconfig5"}) {
451 const std::vector<std::string> common = {bin,
"--file",
"kioslaverc",
"--group",
"Proxy Settings"};
452 auto set = [&](
const std::string& key,
const std::string& val) {
453 std::vector<std::string> args = common;
454 args.insert(args.end(), {
"--key", key, val});
455 (void)
run(args,
true);
457 set(
"ProxyType",
"1");
458 set(
"httpProxy", hp);
459 set(
"httpsProxy", hp);
461 set(
"ReversedException",
"false");
467 for (
const char* bin : {
"kwriteconfig6",
"kwriteconfig5"})
468 (
void)
run({bin,
"--file",
"kioslaverc",
"--group",
"Proxy Settings",
469 "--key",
"ProxyType",
"0"},
true);
474 const std::string port = std::to_string(s.
port);
475 (void)
run({
"xfconf-query",
"-c",
"xfce4-settings-editor",
"-p",
"/Net/ProxyMode",
"-s",
"manual"},
true);
476 for (
const char* scheme : {
"http",
"https"}) {
477 const std::string key = std::string(
"/Net/") + scheme +
"-proxy/";
478 (void)
run({
"xfconf-query",
"-c",
"xfce4-settings-editor",
"-p", key +
"host",
"-s", s.
host},
true);
479 (void)
run({
"xfconf-query",
"-c",
"xfce4-settings-editor",
"-p", key +
"port",
"-s", port},
true);
481 (void)
run({
"gsettings",
"set",
"org.gnome.system.proxy",
"ignore-hosts",
486 (void)
run({
"xfconf-query",
"-c",
"xfce4-settings-editor",
"-p",
"/Net/ProxyMode",
"-s",
"none"},
true);
490 const std::string port = std::to_string(s.
port);
491 for (
const auto& [key, value] : std::vector<std::pair<std::string, std::string>>{
492 {
"proxy-mode",
"manual"}, {
"http-host", s.
host}, {
"http-port", port},
493 {
"https-host", s.
host}, {
"https-port", port},
495 (void)
run({
"gsettings",
"set",
"org.mate.network-preferences", key, value},
true);
500 (void)
run({
"gsettings",
"set",
"org.mate.network-preferences",
"proxy-mode",
"none"},
true);
504 const char* home = std::getenv(
"HOME");
507 namespace fs = std::filesystem;
508 std::ofstream f(fs::path(home) /
".config/lxqt/session.conf", std::ios::app);
510 const std::string val =
"http://" + s.
host +
":" + std::to_string(s.
port);
511 f <<
"\n[Environment]\n"
512 <<
"http_proxy=" << val <<
"\n"
513 <<
"https_proxy=" << val <<
"\n"
519 const std::string val =
"http://" + s.
host +
":" + std::to_string(s.
port);
522 setenv(
"http_proxy", val.c_str(), 1);
523 setenv(
"HTTP_PROXY", val.c_str(), 1);
524 setenv(
"https_proxy", val.c_str(), 1);
525 setenv(
"HTTPS_PROXY", val.c_str(), 1);
526 setenv(
"all_proxy", val.c_str(), 1);
527 setenv(
"ALL_PROXY", val.c_str(), 1);
528 setenv(
"no_proxy", nopx, 1);
529 setenv(
"NO_PROXY", nopx, 1);
531 const char* home = std::getenv(
"HOME");
533 namespace fs = std::filesystem;
534 const fs::path conf_dir = fs::path(home) /
".config/environment.d";
536 fs::create_directories(conf_dir, ec);
538 std::ofstream f(conf_dir /
"bastionguard-proxy.conf");
540 f <<
"http_proxy=" << val <<
"\n"
541 <<
"HTTP_PROXY=" << val <<
"\n"
542 <<
"https_proxy=" << val <<
"\n"
543 <<
"HTTPS_PROXY=" << val <<
"\n"
544 <<
"all_proxy=" << val <<
"\n"
545 <<
"ALL_PROXY=" << val <<
"\n"
546 <<
"no_proxy=" << nopx <<
"\n"
547 <<
"NO_PROXY=" << nopx <<
"\n";
554 unsetenv(
"http_proxy");
555 unsetenv(
"HTTP_PROXY");
556 unsetenv(
"https_proxy");
557 unsetenv(
"HTTPS_PROXY");
558 unsetenv(
"all_proxy");
559 unsetenv(
"ALL_PROXY");
560 unsetenv(
"no_proxy");
561 unsetenv(
"NO_PROXY");
563 const char* home = std::getenv(
"HOME");
566 std::filesystem::remove(
567 std::filesystem::path(home) /
".config/environment.d/bastionguard-proxy.conf", ec);
573 const bool valid_host = !s.
host.empty() && s.
host.size() <= 253 &&
574 std::all_of(s.
host.begin(), s.
host.end(), [](
unsigned char c) {
575 return std::isalnum(c) || c ==
'.' || c ==
'-' || c ==
':' || c ==
'[' || c ==
']';
577 if (!valid_host || s.
port < 1 || s.
port > 65535) {
578 std::cerr <<
"[proxy-config] refusing unsafe host or port\n";
582 std::snprintf(msg,
sizeof(msg),
_(
"[proxy-config] setting system proxy → %s:%d\n"),
603 std::cerr <<
_(
"[proxy-config] restoring system proxy\n");
static void configure_firefox_family(const Settings &s, bool clear)
static bool nss_contains_ca(const std::string &database)
static std::string home_dir()
static constexpr const char * BG_CA_CERT
static void chromium_clear_policy(const fs::path &dir)
static void install_ca_firefox_family()
static void install_ca_firefox_profile(const fs::path &profile)
static void install_ca_all_browsers()
static constexpr const char * LAN_BYPASS_NOCIDR
static constexpr const char * LAN_BYPASS_CIDR
static void install_ca_nssdb()
static void configure_chromium_family(const Settings &s, bool clear)
static void remove_lines_containing(const fs::path &p, const std::string &needle)
static void chromium_set_policy(const fs::path &dir, const Settings &s)
static std::string make_chromium_policy(const Settings &s)
static constexpr const char * LAN_BYPASS_GVARIANT
static fs::path firefox_active_profile(const fs::path &profiles_dir)
static constexpr const char * BG_CA_NICK
static constexpr const char * FF_MARKER
static void apply(const Settings &s)
static void firefox_clear_profile(const fs::path &profile)
static void firefox_set_profile(const fs::path &profile, const Settings &s)
static void write_file(const fs::path &p, const std::string &content)
static void lxqt_set(const Settings &s)
static void mate_set(const Settings &s)
static void gnome_clear()
static void env_set(const Settings &s)
static bool run(const std::vector< std::string > &args, bool silent=false)
static void xfce_set(const Settings &s)
static void kde_refresh()
static void gnome_set(const Settings &s)
static void apply(const Settings &s)
static void kde_set(const Settings &s)
Result run(const std::vector< std::string > &args, bool merge_stderr=false, std::size_t limit=8U *1024U *1024U, const std::map< std::string, std::string > &environment={}, const std::function< bool(const std::string &)> &on_line={})