BastionGuard/src/phishing/dnsmasq_backend.cpp

218 lines
5.4 KiB
C++

/*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 <fstream>
#include <filesystem>
#include <algorithm>
#include <thread>
#include <mutex>
#include <atomic>
#include <signal.h>
#include <sys/wait.h>
namespace fs = std::filesystem;
namespace BastionGuard {
static std::mutex g_mutex;
static std::atomic<bool> g_loaded{false};
static std::vector<std::string> g_blacklist;
static std::vector<std::string> 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<std::string>& 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<std::mutex> 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<std::string>& 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::string>& domains)
{
return atomic_write(blacklist_path_, make_dnsmasq_entries(domains));
}
bool DnsmasqBackend::write_whitelist(
const std::vector<std::string>& 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