c
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
#include "ban.h"
|
||||
#if defined(__unix__) || defined(__linux__)
|
||||
#include <fcntl.h> // O_CREAT, O_RDWR
|
||||
#include <sys/file.h> // flock, LOCK_EX, LOCK_UN
|
||||
#include <signal.h> // SIGCHLD, SIG_IGN
|
||||
#else
|
||||
#define O_CREAT 0x0100
|
||||
#define O_RDWR 0x0002
|
||||
#define LOCK_EX 2
|
||||
#define LOCK_UN 8
|
||||
#define SIGCHLD 17
|
||||
#define SIG_IGN ((void (*)(int))1)
|
||||
#endif
|
||||
#include "nftables.h"
|
||||
#include "whitelist.h"
|
||||
#include "geo.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
int ban_ip(const char *ip, bool save_to_disk) {
|
||||
if (!ip || !validate_ip_format(ip)) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* 检查白名单 */
|
||||
if (is_in_whitelist(ip)) {
|
||||
log_write("[白名单保护] IP=%s 在白名单中,拒绝封禁", ip);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/* 解析IP信息 */
|
||||
ip_info_t info;
|
||||
if (parse_ip_info(ip, &info) != SUCCESS) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* 立即添加到nftables(关键操作,不能延迟) */
|
||||
if (nft_add_to_blacklist(&info) != SUCCESS) {
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
/* 先保存到磁盘(不查询国家) */
|
||||
if (save_to_disk) {
|
||||
persist_add_ip(ip, "");
|
||||
log_write("[执行封禁] IP=%s 已封禁", ip);
|
||||
}
|
||||
|
||||
/* 异步查询国家信息(耗时操作,放在后台执行) */
|
||||
bool should_query = save_to_disk && !is_ipv6(ip) && !is_cidr(ip);
|
||||
if (should_query) {
|
||||
/* 设置忽略SIGCHLD信号,防止僵尸进程 */
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
/* 子进程:执行耗时的网络查询 */
|
||||
char country_code[MAX_COUNTRY_CODE] = {0};
|
||||
if (query_country_code(ip, country_code, sizeof(country_code)) == SUCCESS) {
|
||||
/* 更新持久化文件中的国家信息 */
|
||||
update_ip_country(ip, country_code);
|
||||
log_write("[地理查询] IP=%s 国家=%s", ip, get_country_name(country_code));
|
||||
}
|
||||
|
||||
/* 补充其他IP的国家信息 */
|
||||
supplement_country_info(ip);
|
||||
_exit(0);
|
||||
}
|
||||
/* 父进程:立即返回 */
|
||||
} else if (!save_to_disk) {
|
||||
log_write("[执行封禁] IP=%s 已封禁", ip);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int unban_ip(const char *ip) {
|
||||
if (!ip) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* 从nftables移除 */
|
||||
nft_remove_from_blacklist(ip);
|
||||
|
||||
/* 从持久化文件移除 */
|
||||
persist_remove_ip(ip);
|
||||
|
||||
log_write("[手动解封] IP=%s", ip);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int persist_add_ip(const char *ip, const char *country_code) {
|
||||
if (!ip) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/* 加锁 */
|
||||
char lock_file[MAX_PATH_LEN];
|
||||
snprintf(lock_file, sizeof(lock_file), "%s.lock", PERSIST_FILE);
|
||||
|
||||
int lock_fd = open(lock_file, O_CREAT | O_RDWR, 0600);
|
||||
if (lock_fd < 0) {
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
flock(lock_fd, LOCK_EX);
|
||||
|
||||
/* 检查是否已存在 */
|
||||
FILE *fp = fopen(PERSIST_FILE, "r");
|
||||
bool exists = false;
|
||||
|
||||
if (fp) {
|
||||
char line[MAX_LINE_LEN];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
|
||||
/* 提取IP部分 */
|
||||
char *pipe = strchr(line, '|');
|
||||
if (pipe) *pipe = '\0';
|
||||
|
||||
if (strcmp(line, ip) == 0) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* 添加到文件 */
|
||||
if (!exists) {
|
||||
fp = fopen(PERSIST_FILE, "a");
|
||||
if (fp) {
|
||||
if (country_code && strlen(country_code) > 0) {
|
||||
fprintf(fp, "%s|%s\n", ip, country_code);
|
||||
} else {
|
||||
fprintf(fp, "%s\n", ip);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
flock(lock_fd, LOCK_UN);
|
||||
close(lock_fd);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int persist_remove_ip(const char *ip) {
|
||||
if (!ip) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(PERSIST_FILE, "r");
|
||||
if (!fp) {
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
char temp_file[MAX_PATH_LEN];
|
||||
snprintf(temp_file, sizeof(temp_file), "%s.tmp", PERSIST_FILE);
|
||||
FILE *temp_fp = fopen(temp_file, "w");
|
||||
if (!temp_fp) {
|
||||
fclose(fp);
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
char line_copy[MAX_LINE_LEN];
|
||||
snprintf(line_copy, sizeof(line_copy), "%s", line);
|
||||
line_copy[strcspn(line_copy, "\n")] = 0;
|
||||
|
||||
/* 提取IP部分 */
|
||||
char *pipe = strchr(line_copy, '|');
|
||||
if (pipe) *pipe = '\0';
|
||||
|
||||
if (strcmp(line_copy, ip) != 0) {
|
||||
fputs(line, temp_fp);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
fclose(temp_fp);
|
||||
|
||||
rename(temp_file, PERSIST_FILE);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int update_ip_country(const char *ip, const char *country_code) {
|
||||
if (!ip || !country_code) {
|
||||
return ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(PERSIST_FILE, "r");
|
||||
if (!fp) {
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
char temp_file[MAX_PATH_LEN];
|
||||
snprintf(temp_file, sizeof(temp_file), "%s.tmp", PERSIST_FILE);
|
||||
FILE *temp_fp = fopen(temp_file, "w");
|
||||
if (!temp_fp) {
|
||||
fclose(fp);
|
||||
return ERROR_FILE;
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
bool found = false;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
char line_copy[MAX_LINE_LEN];
|
||||
snprintf(line_copy, sizeof(line_copy), "%s", line);
|
||||
line_copy[strcspn(line_copy, "\n")] = 0;
|
||||
|
||||
/* 提取IP部分 */
|
||||
char *pipe = strchr(line_copy, '|');
|
||||
if (pipe) *pipe = '\0';
|
||||
|
||||
if (strcmp(line_copy, ip) == 0 && !found) {
|
||||
/* 找到目标IP,更新国家信息 */
|
||||
fprintf(temp_fp, "%s|%s\n", ip, country_code);
|
||||
found = true;
|
||||
} else {
|
||||
/* 保持原样 */
|
||||
fputs(line, temp_fp);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
fclose(temp_fp);
|
||||
|
||||
rename(temp_file, PERSIST_FILE);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int restore_from_persist(void) {
|
||||
FILE *fp = fopen(PERSIST_FILE, "r");
|
||||
if (!fp) {
|
||||
return SUCCESS; /* 文件不存在 */
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LEN];
|
||||
int count = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
|
||||
if (strlen(line) == 0) continue;
|
||||
|
||||
/* 提取IP部分 */
|
||||
char ip[MAX_IP_LEN];
|
||||
char *pipe = strchr(line, '|');
|
||||
if (pipe) {
|
||||
*pipe = '\0';
|
||||
strncpy(ip, line, sizeof(ip) - 1);
|
||||
} else {
|
||||
strncpy(ip, line, sizeof(ip) - 1);
|
||||
}
|
||||
ip[sizeof(ip) - 1] = '\0';
|
||||
|
||||
/* 恢复到nftables(不保存到磁盘) */
|
||||
ip_info_t info;
|
||||
if (parse_ip_info(ip, &info) == SUCCESS) {
|
||||
if (nft_add_to_blacklist(&info) == SUCCESS) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
log_write("[系统恢复] 已从磁盘恢复 %d 个黑名单 IP", count);
|
||||
|
||||
char message[MAX_LINE_LEN];
|
||||
snprintf(message, sizeof(message), "✅ 已从磁盘恢复 %d 个黑名单 IP", count);
|
||||
msg(C_GREEN, message);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void show_persist_list(void) {
|
||||
msg(C_CYAN, "=== 📋 本地持久化封禁列表 ===");
|
||||
|
||||
FILE *fp = fopen(PERSIST_FILE, "r");
|
||||
if (!fp || fseek(fp, 0, SEEK_END) == 0) {
|
||||
if (fp) fclose(fp);
|
||||
printf("(暂无持久化记录)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rewind(fp);
|
||||
|
||||
/* 统计 */
|
||||
int total = 0, ipv4_count = 0, ipv6_count = 0;
|
||||
char line[MAX_LINE_LEN];
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
if (strlen(line) == 0) continue;
|
||||
|
||||
total++;
|
||||
if (strchr(line, ':')) {
|
||||
ipv6_count++;
|
||||
} else {
|
||||
ipv4_count++;
|
||||
}
|
||||
}
|
||||
|
||||
printf("总计: %s%d%s 条 | IPv4: %s%d%s 条 | IPv6: %s%d%s 条\n\n",
|
||||
C_GREEN, total, C_RESET,
|
||||
C_CYAN, ipv4_count, C_RESET,
|
||||
C_YELLOW, ipv6_count, C_RESET);
|
||||
|
||||
printf("%s%-45s%s\n", C_YELLOW, "IP 地址", C_RESET);
|
||||
printf("---------------------------------------------\n");
|
||||
|
||||
rewind(fp);
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
if (strlen(line) > 0) {
|
||||
/* 只显示IP部分 */
|
||||
char *pipe = strchr(line, '|');
|
||||
if (pipe) *pipe = '\0';
|
||||
printf("%-45s\n", line);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
printf("\n");
|
||||
printf("%s📌 文件位置: %s%s\n", C_CYAN, PERSIST_FILE, C_RESET);
|
||||
}
|
||||
Reference in New Issue
Block a user