/* * 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 "FirewallRuleManager.hpp" #include #include #include #include #include #include #include #include #include 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& command) { if (command.empty()) return false; const pid_t pid = ::fork(); if (pid < 0) return false; if (pid == 0) { std::vector argv; for (const auto& arg : command) argv.push_back(const_cast(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& ips) { std::unordered_set ipv4; std::unordered_set 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& 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& ips) { std::unordered_set 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& 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; } }