BastionGuard/src/firewall/FirewallRuleManager.cpp

122 lines
4.6 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 "FirewallRuleManager.hpp"
#include <cerrno>
#include <iostream>
#include <unordered_set>
#include <vector>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <glib/gi18n.h>
namespace {
int ip_family(const std::string& ip) {
struct in_addr ipv4 {};
if (::inet_pton(AF_INET, ip.c_str(), &ipv4) == 1) return AF_INET;
struct in6_addr ipv6 {};
if (::inet_pton(AF_INET6, ip.c_str(), &ipv6) == 1) return AF_INET6;
return AF_UNSPEC;
}
}
bool FirewallRuleManager::run(const std::vector<std::string>& command) {
if (command.empty()) return false;
const pid_t pid = ::fork();
if (pid < 0) return false;
if (pid == 0) {
std::vector<char*> argv;
for (const auto& arg : command) argv.push_back(const_cast<char*>(arg.c_str()));
argv.push_back(nullptr);
::execvp(argv.front(), argv.data());
_exit(127);
}
int status = 0;
pid_t result;
do { result = ::waitpid(pid, &status, 0); } while (result < 0 && errno == EINTR);
const bool ok = result == pid && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (!ok) std::cerr << _("[Firewall] Command failed: ") << command[0] << "\n";
return ok;
}
void FirewallRuleManager::applyWithFirewalld(const std::vector<std::string>& ips) {
std::unordered_set<std::string> ipv4;
std::unordered_set<std::string> ipv6;
for (const auto& ip : ips) {
switch (ip_family(ip)) {
case AF_INET: ipv4.insert(ip); break;
case AF_INET6: ipv6.insert(ip); break;
default: std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n"; break;
}
}
if (ipv4.empty() && ipv6.empty()) {
std::cerr << _("[Firewall] Nessun IP valido da applicare.\n");
return;
}
const auto add_family = [&](const std::unordered_set<std::string>& entries,
const std::string& name, const std::string& family,
const std::string& ipset_family) {
if (entries.empty()) return;
// Already-existing sets may return nonzero; adding entries is idempotent.
run({"pkexec", "firewall-cmd", "--permanent", "--new-ipset=" + name,
"--type=hash:ip", "--option=family=" + ipset_family});
for (const auto& ip : entries) {
run({"pkexec", "firewall-cmd", "--permanent", "--ipset=" + name,
"--add-entry=" + ip});
}
run({"pkexec", "firewall-cmd", "--permanent",
"--add-rich-rule=rule family=\"" + family + "\" source ipset=\"" + name + "\" reject"});
};
add_family(ipv4, "bastionguard-blocklist", "ipv4", "inet");
add_family(ipv6, "bastionguard-blocklist-v6", "ipv6", "inet6");
run({"pkexec", "firewall-cmd", "--reload"});
}
void FirewallRuleManager::applyWithUfw(const std::vector<std::string>& ips) {
std::unordered_set<std::string> filtered;
for (const auto& ip : ips) {
if (ip_family(ip) != AF_UNSPEC) filtered.insert(ip);
else std::cerr << _("[Firewall] SKIP non-IP: ") << ip << "\n";
}
if (filtered.empty()) {
std::cerr << _("[Firewall] Nessun IP valido da applicare.\n");
return;
}
for (const auto& ip : filtered) run({"pkexec", "ufw", "deny", "from", ip});
run({"pkexec", "ufw", "reload"});
}
void FirewallRuleManager::apply(FirewallType fw, const std::vector<std::string>& ips) {
switch (fw) {
case FirewallType::FIREWALLD:
std::cout << _("[Firewall] Applying rules with firewalld (ipset)\n");
applyWithFirewalld(ips);
break;
case FirewallType::UFW:
std::cout << _("[Firewall] Applying rules with UFW\n");
applyWithUfw(ips);
break;
default:
std::cerr << _("[Firewall] Nessun firewall selezionato.\n");
break;
}
}