Skip to content
API Documentation
BastionGuard™
BlacklistIpExtractor.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
22#include <unordered_set>
23#include <algorithm>
24#include <filesystem>
25#include <cstdio>
26#include <unistd.h>
27#include <regex>
28
29namespace fs = std::filesystem;
30
31static bool is_valid_ipv4(const std::string& s) {
32 int parts = 0, acc = 0;
33 bool has_digit = false;
34
35 for (char c : s) {
36 if (c >= '0' && c <= '9') {
37 has_digit = true;
38 acc = acc * 10 + (c - '0');
39 if (acc > 255) return false;
40 } else if (c == '.') {
41 if (!has_digit) return false;
42 parts++;
43 acc = 0;
44 has_digit = false;
45 } else return false;
46 }
47
48 return has_digit && parts == 3;
49}
50
51static bool is_valid_ipv4_cidr(const std::string& s) {
52 auto pos = s.find('/');
53 if (pos == std::string::npos) return false;
54 if (!is_valid_ipv4(s.substr(0, pos))) return false;
55
56 try {
57 int m = std::stoi(s.substr(pos + 1));
58 return m >= 0 && m <= 32;
59 } catch (...) {
60 return false;
61 }
62}
63
64static bool is_valid_ipv6(const std::string& s) {
65 static const std::regex ipv6(
66 R"(^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$)"
67 );
68 return std::regex_match(s, ipv6);
69}
70
71static bool extract_host_from_url(const std::string& line, std::string& host) {
72 static const std::regex url_re(
73 R"(^(?:https?|ftp)://([^/:?#]+))",
74 std::regex::icase
75 );
76
77 std::smatch m;
78 if (!std::regex_search(line, m, url_re))
79 return false;
80
81 host = m[1].str();
82 return true;
83}
84
86 const std::vector<std::string>& lines,
87 const std::string& outPath
88) {
89 std::unordered_set<std::string> unique;
90
91 for (std::string line : lines) {
92 // trim
93 auto start = line.find_first_not_of(" \t\r\n");
94 if (start == std::string::npos) continue;
95 line.erase(0, start);
96
97 if (auto pos = line.find('#'); pos != std::string::npos)
98 line = line.substr(0, pos);
99
100 auto end = line.find_last_not_of(" \t\r\n");
101 if (end == std::string::npos) continue;
102 line.erase(end + 1);
103 if (is_valid_ipv4(line) ||
104 is_valid_ipv4_cidr(line) ||
105 is_valid_ipv6(line)) {
106 unique.insert(line);
107 continue;
108 }
109
110 std::string host;
111 if (extract_host_from_url(line, host)) {
112 if (is_valid_ipv4(host) || is_valid_ipv6(host)) {
113 unique.insert(host);
114 }
115 }
116 }
117
118 if (unique.empty())
119 return false;
120
121 std::vector<std::string> ips(unique.begin(), unique.end());
122 std::sort(ips.begin(), ips.end());
123
124 std::string tmpl = outPath + ".XXXXXX";
125 std::vector<char> path(tmpl.begin(), tmpl.end());
126 path.push_back('\0');
127
128 int fd = mkstemp(path.data());
129 if (fd < 0) return false;
130
131 FILE* f = fdopen(fd, "w");
132 if (!f) { close(fd); return false; }
133
134 for (const auto& ip : ips)
135 fprintf(f, "%s\n", ip.c_str());
136
137 fflush(f);
138 fsync(fd);
139 fclose(f);
140
141 fs::rename(path.data(), outPath);
142 fs::permissions(
143 outPath,
144 fs::perms::owner_read | fs::perms::owner_write,
145 fs::perm_options::replace
146 );
147
148 return true;
149}
static bool is_valid_ipv6(const std::string &s)
static bool is_valid_ipv4(const std::string &s)
static bool extract_host_from_url(const std::string &line, std::string &host)
static bool is_valid_ipv4_cidr(const std::string &s)
static bool extractAndWrite(const std::vector< std::string > &lines, const std::string &outPath="/tmp/bastionguard-firewall-ips.txt")