Skip to content
API Documentation
BastionGuard™
dnsmasq_backend.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#include "../SafeProcess.hpp"
22#include "dnsmasq_backend.hpp"
23#include <fstream>
24#include <filesystem>
25#include <algorithm>
26#include <thread>
27#include <mutex>
28#include <atomic>
29#include <signal.h>
30#include <sys/wait.h>
31
32namespace fs = std::filesystem;
33
34namespace BastionGuard {
35
36static std::mutex g_mutex;
37static std::atomic<bool> g_loaded{false};
38static std::vector<std::string> g_blacklist;
39static std::vector<std::string> g_whitelist;
40
41DnsmasqBackend::DnsmasqBackend(const std::string& bl,
42 const std::string& wl,
43 const std::string& user)
44 : blacklist_path_(bl),
46 dnsmasq_user_(user)
47{
48}
49
50
51
52static void load_list_file(const std::string& path,
53 std::vector<std::string>& out,
54 bool is_blacklist)
55{
56 out.clear();
57
58 if (!fs::exists(path)) return;
59
60 std::ifstream ifs(path);
61 std::string line;
62
63 while (std::getline(ifs, line)) {
64 if (line.empty()) continue;
65
66 if (is_blacklist) {
67 auto a = line.find("address=/");
68 if (a != std::string::npos) {
69 auto b = line.find('/', a + 9);
70 if (b != std::string::npos)
71 out.push_back(line.substr(a + 9, b - (a + 9)));
72 }
73 } else {
74 out.push_back(line);
75 }
76 }
77}
78
79static void ensure_loaded(const std::string& bl, const std::string& wl)
80{
81 if (g_loaded.load()) return;
82
83 std::lock_guard<std::mutex> lock(g_mutex);
84 if (g_loaded) return;
85
86 load_list_file(bl, g_blacklist, true);
87 load_list_file(wl, g_whitelist, false);
88
89 g_loaded = true;
90}
91
92
93bool DnsmasqBackend::atomic_write(const std::string& path,
94 const std::string& content)
95{
96 try {
97 fs::path p(path);
98 fs::path tmp = p.string() + ".tmp";
99
100 {
101 std::ofstream o(tmp);
102 if (!o) return false;
103 o << content;
104 }
105 fs::rename(tmp, p);
106 return true;
107 } catch (...) {
108 return false;
109 }
110}
111
112
114 const std::vector<std::string>& domains)
115{
116 std::ostringstream oss;
117 for (auto& d : domains)
118 oss << "address=/" << d << "/127.0.0.1\n";
119 return oss.str();
120}
121
122
123bool DnsmasqBackend::add_to_blacklist(const std::string& domain)
124{
125 std::thread([this, domain] {
127
128 std::lock_guard<std::mutex> lock(g_mutex);
129 if (std::find(g_blacklist.begin(), g_blacklist.end(), domain)
130 != g_blacklist.end())
131 return;
132
133 g_blacklist.push_back(domain);
134
137 }).detach();
138
139 return true;
140}
141
142bool DnsmasqBackend::remove_from_blacklist(const std::string& domain)
143{
144 std::thread([this, domain] {
146
147 std::lock_guard<std::mutex> lock(g_mutex);
148
149 g_blacklist.erase(
150 std::remove(g_blacklist.begin(), g_blacklist.end(), domain),
151 g_blacklist.end());
152
155 }).detach();
156
157 return true;
158}
159
160bool DnsmasqBackend::add_to_whitelist(const std::string& domain)
161{
162 std::thread([this, domain] {
164
165 std::lock_guard<std::mutex> lock(g_mutex);
166 if (std::find(g_whitelist.begin(), g_whitelist.end(), domain)
167 != g_whitelist.end())
168 return;
169
170 g_whitelist.push_back(domain);
171
173 }).detach();
174
175 return true;
176}
177
178bool DnsmasqBackend::remove_from_whitelist(const std::string& domain)
179{
180 std::thread([this, domain] {
182
183 std::lock_guard<std::mutex> lock(g_mutex);
184
185 g_whitelist.erase(
186 std::remove(g_whitelist.begin(), g_whitelist.end(), domain),
187 g_whitelist.end());
188
190 }).detach();
191
192 return true;
193}
194
195
197 const std::vector<std::string>& domains)
198{
200}
201
203 const std::vector<std::string>& domains)
204{
205 std::ostringstream oss;
206 for (auto& d : domains)
207 oss << d << "\n";
208 return atomic_write(whitelist_path_, oss.str());
209}
210
211
213{
214 int st = (bg_process::run({"/usr/libexec/bastionguard/bastionguard-service", "--system", "try-reload-or-restart", "dnsmasq.service"}).exit_code);
215 return WIFEXITED(st) && WEXITSTATUS(st) == 0;
216}
217
218} // namespace BastionGuard
bool add_to_blacklist(const std::string &domain)
bool remove_from_blacklist(const std::string &domain)
std::string make_dnsmasq_entries(const std::vector< std::string > &domains)
bool remove_from_whitelist(const std::string &domain)
DnsmasqBackend(const std::string &blacklist_path="/etc/dnsmasq.d/BastionGuard-blacklist.conf", const std::string &whitelist_path="/etc/dnsmasq.d/BastionGuard-whitelist.conf", const std::string &dnsmasq_user="dnsmasq")
bool atomic_write(const std::string &path, const std::string &content)
bool write_blacklist(const std::vector< std::string > &domains)
bool write_whitelist(const std::vector< std::string > &domains)
bool add_to_whitelist(const std::string &domain)
static void load_list_file(const std::string &path, std::vector< std::string > &out, bool is_blacklist)
static std::mutex g_mutex
static std::vector< std::string > g_whitelist
static void ensure_loaded(const std::string &bl, const std::string &wl)
static std::vector< std::string > g_blacklist
static std::atomic< bool > g_loaded
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={})