122 lines
4.6 KiB
C++
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;
|
|
}
|
|
}
|