/* * BastionGuard™ * Copyright (C) 2025–2026 Calogero Scarnà * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * BastionGuard™ is a trademark of Calogero Scarnà. * The BastionGuard™ name and branding are not licensed under the GPL. */ #include "../SafeProcess.hpp" #include "dnsmasq_backend.hpp" #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace BastionGuard { static std::mutex g_mutex; static std::atomic g_loaded{false}; static std::vector g_blacklist; static std::vector g_whitelist; DnsmasqBackend::DnsmasqBackend(const std::string& bl, const std::string& wl, const std::string& user) : blacklist_path_(bl), whitelist_path_(wl), dnsmasq_user_(user) { } static void load_list_file(const std::string& path, std::vector& out, bool is_blacklist) { out.clear(); if (!fs::exists(path)) return; std::ifstream ifs(path); std::string line; while (std::getline(ifs, line)) { if (line.empty()) continue; if (is_blacklist) { auto a = line.find("address=/"); if (a != std::string::npos) { auto b = line.find('/', a + 9); if (b != std::string::npos) out.push_back(line.substr(a + 9, b - (a + 9))); } } else { out.push_back(line); } } } static void ensure_loaded(const std::string& bl, const std::string& wl) { if (g_loaded.load()) return; std::lock_guard lock(g_mutex); if (g_loaded) return; load_list_file(bl, g_blacklist, true); load_list_file(wl, g_whitelist, false); g_loaded = true; } bool DnsmasqBackend::atomic_write(const std::string& path, const std::string& content) { try { fs::path p(path); fs::path tmp = p.string() + ".tmp"; { std::ofstream o(tmp); if (!o) return false; o << content; } fs::rename(tmp, p); return true; } catch (...) { return false; } } std::string DnsmasqBackend::make_dnsmasq_entries( const std::vector& domains) { std::ostringstream oss; for (auto& d : domains) oss << "address=/" << d << "/127.0.0.1\n"; return oss.str(); } bool DnsmasqBackend::add_to_blacklist(const std::string& domain) { std::thread([this, domain] { ensure_loaded(blacklist_path_, whitelist_path_); std::lock_guard lock(g_mutex); if (std::find(g_blacklist.begin(), g_blacklist.end(), domain) != g_blacklist.end()) return; g_blacklist.push_back(domain); write_blacklist(g_blacklist); reload_dnsmasq(); }).detach(); return true; } bool DnsmasqBackend::remove_from_blacklist(const std::string& domain) { std::thread([this, domain] { ensure_loaded(blacklist_path_, whitelist_path_); std::lock_guard lock(g_mutex); g_blacklist.erase( std::remove(g_blacklist.begin(), g_blacklist.end(), domain), g_blacklist.end()); write_blacklist(g_blacklist); reload_dnsmasq(); }).detach(); return true; } bool DnsmasqBackend::add_to_whitelist(const std::string& domain) { std::thread([this, domain] { ensure_loaded(blacklist_path_, whitelist_path_); std::lock_guard lock(g_mutex); if (std::find(g_whitelist.begin(), g_whitelist.end(), domain) != g_whitelist.end()) return; g_whitelist.push_back(domain); write_whitelist(g_whitelist); }).detach(); return true; } bool DnsmasqBackend::remove_from_whitelist(const std::string& domain) { std::thread([this, domain] { ensure_loaded(blacklist_path_, whitelist_path_); std::lock_guard lock(g_mutex); g_whitelist.erase( std::remove(g_whitelist.begin(), g_whitelist.end(), domain), g_whitelist.end()); write_whitelist(g_whitelist); }).detach(); return true; } bool DnsmasqBackend::write_blacklist( const std::vector& domains) { return atomic_write(blacklist_path_, make_dnsmasq_entries(domains)); } bool DnsmasqBackend::write_whitelist( const std::vector& domains) { std::ostringstream oss; for (auto& d : domains) oss << d << "\n"; return atomic_write(whitelist_path_, oss.str()); } bool DnsmasqBackend::reload_dnsmasq() { int st = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "try-reload-or-restart", "dnsmasq.service"}).exit_code); return WIFEXITED(st) && WEXITSTATUS(st) == 0; } } // namespace BastionGuard