From 43fc123cb409ba5df81fa280aabce9e6a0324fab Mon Sep 17 00:00:00 2001 From: sushen339 Date: Tue, 18 Nov 2025 12:54:21 +0800 Subject: [PATCH] c --- block-ip.sh | 4 +- blockip/.gitignore | 32 ++++ blockip/Makefile | 103 +++++++++++ blockip/README.md | 279 ++++++++++++++++++++++++++++++ blockip/include/ban.h | 29 ++++ blockip/include/common.h | 79 +++++++++ blockip/include/geo.h | 15 ++ blockip/include/install.h | 21 +++ blockip/include/ip_utils.h | 45 +++++ blockip/include/log.h | 18 ++ blockip/include/nftables.h | 32 ++++ blockip/include/pam.h | 21 +++ blockip/include/stats.h | 18 ++ blockip/include/whitelist.h | 22 +++ blockip/src/ban.c | 330 ++++++++++++++++++++++++++++++++++++ blockip/src/common.c | 201 ++++++++++++++++++++++ blockip/src/geo.c | 150 ++++++++++++++++ blockip/src/install.c | 246 +++++++++++++++++++++++++++ blockip/src/ip_utils.c | 180 ++++++++++++++++++++ blockip/src/log.c | 63 +++++++ blockip/src/main.c | 263 ++++++++++++++++++++++++++++ blockip/src/nftables.c | 284 +++++++++++++++++++++++++++++++ blockip/src/pam.c | 145 ++++++++++++++++ blockip/src/stats.c | 228 +++++++++++++++++++++++++ blockip/src/whitelist.c | 172 +++++++++++++++++++ 25 files changed, 2978 insertions(+), 2 deletions(-) create mode 100644 blockip/.gitignore create mode 100644 blockip/Makefile create mode 100644 blockip/README.md create mode 100644 blockip/include/ban.h create mode 100644 blockip/include/common.h create mode 100644 blockip/include/geo.h create mode 100644 blockip/include/install.h create mode 100644 blockip/include/ip_utils.h create mode 100644 blockip/include/log.h create mode 100644 blockip/include/nftables.h create mode 100644 blockip/include/pam.h create mode 100644 blockip/include/stats.h create mode 100644 blockip/include/whitelist.h create mode 100644 blockip/src/ban.c create mode 100644 blockip/src/common.c create mode 100644 blockip/src/geo.c create mode 100644 blockip/src/install.c create mode 100644 blockip/src/ip_utils.c create mode 100644 blockip/src/log.c create mode 100644 blockip/src/main.c create mode 100644 blockip/src/nftables.c create mode 100644 blockip/src/pam.c create mode 100644 blockip/src/stats.c create mode 100644 blockip/src/whitelist.c diff --git a/block-ip.sh b/block-ip.sh index bbdf476..7a447f7 100644 --- a/block-ip.sh +++ b/block-ip.sh @@ -98,8 +98,8 @@ check_and_install_env() { if ! command -v nft >/dev/null 2>&1; then . /etc/os-release case "$ID" in - debian|ubuntu|kali) apt-get update && apt-get install -y nftables ;; - centos|rhel|alma) dnf install -y nftables || yum install -y nftables ;; + debian|ubuntu|kali) apt-get update && apt-get install -y nftables curl ;; + centos|rhel|alma) dnf install -y nftables || yum install -y nftables curl ;; alpine) apk add nftables ;; *) return 1 ;; esac diff --git a/blockip/.gitignore b/blockip/.gitignore new file mode 100644 index 0000000..7e80388 --- /dev/null +++ b/blockip/.gitignore @@ -0,0 +1,32 @@ +# 编译生成的文件 +*.o +*.a +*.so +*.out +bip + +# 目录 +obj/ +bin/ + +# 调试文件 +*.dSYM/ +*.su +*.idb +*.pdb + +# 编辑器和IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# 临时文件 +*.tmp +*.log +*.bak + +# 构建文件 +Makefile.deps diff --git a/blockip/Makefile b/blockip/Makefile new file mode 100644 index 0000000..5102f6b --- /dev/null +++ b/blockip/Makefile @@ -0,0 +1,103 @@ +# Block-IP Makefile +# C语言重构版本 + +CC = gcc +CFLAGS = -Wall -Wextra -O2 -std=c11 +LDFLAGS = +TARGET = bip +INSTALL_PATH = /usr/local/bin + +# 源文件目录 +SRC_DIR = src +INC_DIR = include +OBJ_DIR = obj + +# 源文件 +SRCS = $(SRC_DIR)/main.c \ + $(SRC_DIR)/common.c \ + $(SRC_DIR)/log.c \ + $(SRC_DIR)/ip_utils.c \ + $(SRC_DIR)/geo.c \ + $(SRC_DIR)/nftables.c \ + $(SRC_DIR)/whitelist.c \ + $(SRC_DIR)/ban.c \ + $(SRC_DIR)/pam.c \ + $(SRC_DIR)/stats.c \ + $(SRC_DIR)/install.c + +# 目标文件 +OBJS = $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) + +# 头文件依赖 +DEPS = $(wildcard $(INC_DIR)/*.h) + +# 默认目标 +all: $(TARGET) + +# 创建目标目录 +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) + +# 编译目标文件 +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(DEPS) | $(OBJ_DIR) + $(CC) $(CFLAGS) -I$(INC_DIR) -c $< -o $@ + +# 链接生成可执行文件 +$(TARGET): $(OBJS) + $(CC) $(OBJS) $(LDFLAGS) -o $(TARGET) + @echo "编译完成: $(TARGET)" + +# 安装 +install: $(TARGET) + @if [ $$(id -u) -ne 0 ]; then \ + echo "错误: 需要root权限执行安装"; \ + exit 1; \ + fi + install -m 755 $(TARGET) $(INSTALL_PATH)/$(TARGET) + @echo "已安装到: $(INSTALL_PATH)/$(TARGET)" + @echo "运行 'block-ip install' 来完成系统配置" + +# 卸载 +uninstall: + @if [ $$(id -u) -ne 0 ]; then \ + echo "错误: 需要root权限执行卸载"; \ + exit 1; \ + fi + $(INSTALL_PATH)/$(TARGET) uninstall || true + rm -f $(INSTALL_PATH)/$(TARGET) + @echo "已卸载" + +# 清理编译文件 +clean: + rm -rf $(OBJ_DIR) $(TARGET) + @echo "清理完成" + +# 清理所有文件(包括配置) +distclean: clean + rm -rf /etc/blockip + rm -f /var/log/block-ip.log /var/log/block-ip.log.1 + @echo "深度清理完成" + +# 调试版本 +debug: CFLAGS += -g -DDEBUG +debug: clean $(TARGET) + +# 静态链接版本 +static: LDFLAGS += -static +static: clean $(TARGET) + +# 显示帮助 +help: + @echo "Block-IP 构建系统" + @echo "" + @echo "可用目标:" + @echo " make - 编译程序" + @echo " make install - 安装到系统 (需要root)" + @echo " make uninstall- 从系统卸载 (需要root)" + @echo " make clean - 清理编译文件" + @echo " make distclean- 清理所有文件(包括配置)" + @echo " make debug - 编译调试版本" + @echo " make static - 编译静态链接版本" + @echo " make help - 显示此帮助信息" + +.PHONY: all install uninstall clean distclean debug static help diff --git a/blockip/README.md b/blockip/README.md new file mode 100644 index 0000000..25d0a90 --- /dev/null +++ b/blockip/README.md @@ -0,0 +1,279 @@ +# BIP (Block-IP) - C语言实现 + +基于 nftables 的自动封禁恶意IP工具,支持IPv4/IPv6和CIDR格式。 + +## 特性 + + +## 模块划分 + +``` +blockip/ +├── include/ # 头文件目录 +│ ├── common.h # 公共定义和工具函数 +│ ├── log.h # 日志模块 +│ ├── ip_utils.h # IP地址处理工具 +│ ├── geo.h # 地理位置查询 +│ ├── nftables.h # nftables操作接口 +│ ├── whitelist.h # 白名单管理 +│ ├── ban.h # 封禁/解封核心逻辑 +│ ├── pam.h # PAM集成模块 +│ ├── stats.h # 统计和展示 +│ └── install.h # 安装/卸载功能 +├── src/ # 源文件目录 +│ ├── main.c # 主程序入口 +│ ├── common.c # 公共函数实现 +│ ├── log.c # 日志功能实现 +│ ├── ip_utils.c # IP处理实现 +│ ├── geo.c # 地理位置实现 +│ ├── nftables.c # nftables实现 +│ ├── whitelist.c # 白名单实现 +│ ├── ban.c # 封禁逻辑实现 +│ ├── pam.c # PAM集成实现 +│ ├── stats.c # 统计功能实现 +│ └── install.c # 安装功能实现 +├── Makefile # 构建脚本 +└── README.md # 本文档 +``` + +## 系统要求 + + +## 编译安装 + +### 1. 编译程序 + +```bash +cd blockip +make +``` + +### 2. 安装到系统(需要root权限) + +```bash +sudo make install +``` + +### 3. 配置系统服务 + +```bash +sudo bip install +``` + +这将自动完成以下配置: + +## 使用方法 + +### 查看状态和统计 + +```bash +# 查看实时统计、活跃封禁列表、日志 +bip list + +# 显示本地持久化封禁列表 +bip show +``` + +### 手动封禁/解封IP + +```bash +# 封禁单个IPv4地址 +bip add 1.2.3.4 + +# 封禁IPv4网段(CIDR) +bip add 1.2.3.0/24 + +# 封禁IPv6地址 +bip add 2001:db8::1 + +# 封禁IPv6网段 +bip add 2001:db8::/32 + +# 解封IP +bip del 1.2.3.4 +``` + +### 白名单管理 + +```bash +# 添加IP到白名单 +bip vip add 192.168.1.100 + +# 添加网段到白名单 +bip vip add 192.168.0.0/16 + +# 从白名单移除 +bip vip del 192.168.1.100 + +# 显示白名单列表 +bip vip list +``` + +### 系统管理 + +```bash +# 从持久化文件恢复黑白名单 +bip restore + +# 卸载服务 +bip uninstall +``` + +## 工作原理 + +1. **PAM集成**:通过PAM模块监控SSH登录尝试 +2. **失败计数**:记录每个IP的失败登录次数 +3. **自动封禁**:达到阈值(默认3次)后自动封禁IP +4. **异步处理**:使用fork子进程异步执行封禁和地理查询,不阻塞SSH登录 +5. **nftables规则**:使用nftables的集合(set)功能高效封禁 +6. **持久化存储**:封禁记录保存到磁盘,重启后自动恢复 +7. **白名单保护**:白名单IP永不封禁 +8. **自动解封**:24小时后自动解封(可配置) + +## 配置参数 + +### 动态配置(无需重新编译) + +使用配置文件 `/etc/blockip/config` 灵活修改封禁时间: + +```bash +# 查看当前配置 +bip config + +# 设置封禁时间为12小时 +bip config time 12h + +# 设置封禁时间为30分钟 +bip config time 30m + +# 设置为永久封禁 +bip config time "" + +# 设置最大重试次数为5次 +bip config retries 5 +``` + +支持的配置参数: + - `24h` - 24小时 + - `12h` - 12小时 + - `1h` - 1小时 + - `30m` - 30分钟 + - `""` - 永久封禁(空字符串) + + - 范围:1-10 次 + - 默认:3 次 + - 说明:SSH登录失败达到此次数后自动封禁 + +配置文件位置:`/etc/blockip/config` + +### 静态配置(需要重新编译) + +在 `include/common.h` 中可以修改以下默认参数: + +```c +#define MAX_RETRIES 3 // 默认最大失败次数 +#define DEFAULT_BAN_TIME "24h" // 默认封禁时长 +``` + +修改后需要重新编译: + +```bash +make clean +make +sudo make install +``` + +## 文件说明 + + - `config` - 配置文件(封禁时间等) + - `blacklist` - 封禁IP列表(持久化) + - `whitelist` - 白名单列表 + - `counts/` - 失败次数记录目录 + +## 卸载 + +```bash +# 完全卸载(会询问是否删除数据文件) +sudo make uninstall +``` + +或者: + +```bash +sudo bip uninstall +``` + +## 开发和调试 + +### 编译调试版本 + +```bash +make debug +``` + +### 清理编译文件 + +```bash +make clean +``` + +### 深度清理(包括配置文件) + +```bash +make distclean +``` + +## 性能优化 + + +## 注意事项 + +1. **白名单优先**:请先将信任的IP加入白名单,避免误封 +2. **网段封禁**:使用CIDR封禁时请谨慎,避免误伤 +3. **日志监控**:定期查看日志,了解攻击情况 +4. **备份配置**:重要服务器建议备份白名单配置 + +## 常见问题 + +### Q: 不小心把自己封禁了怎么办? + +A: 通过控制台登录服务器,执行: +```bash +bip del YOUR_IP +bip vip add YOUR_IP +``` + +### Q: 如何查看当前封禁了多少IP? + +A: 执行 `bip list` 查看统计信息 + +### Q: 封禁时间可以永久吗? + +A: 修改 `include/common.h` 中的 `BAN_TIME` 为空字符串 `""`,然后重新编译 + +### Q: 支持自定义失败次数吗? + +A: 修改 `include/common.h` 中的 `MAX_RETRIES` 值,然后重新编译 + +## 技术特点 + + +## 贡献 + +欢迎提交Issue和Pull Request! + +## 许可证 + +MIT License + +## 作者 + +原Shell版本:su +C语言重构:GitHub Copilot + +## 更新日志 + +### v16.2-C (2025-11-18) + + +**享受更安全的服务器环境! 🛡️** diff --git a/blockip/include/ban.h b/blockip/include/ban.h new file mode 100644 index 0000000..436395c --- /dev/null +++ b/blockip/include/ban.h @@ -0,0 +1,29 @@ +#ifndef BAN_H +#define BAN_H + +#include "common.h" +#include "ip_utils.h" +#include + +/* 封禁IP */ +int ban_ip(const char *ip, bool save_to_disk); + +/* 解封IP */ +int unban_ip(const char *ip); + +/* 添加到持久化列表 */ +int persist_add_ip(const char *ip, const char *country_code); + +/* 从持久化列表移除 */ +int persist_remove_ip(const char *ip); + +/* 更新IP的国家信息 */ +int update_ip_country(const char *ip, const char *country_code); + +/* 恢复持久化列表到nftables */ +int restore_from_persist(void); + +/* 显示持久化列表 */ +void show_persist_list(void); + +#endif /* BAN_H */ diff --git a/blockip/include/common.h b/blockip/include/common.h new file mode 100644 index 0000000..3b6eaba --- /dev/null +++ b/blockip/include/common.h @@ -0,0 +1,79 @@ +#ifndef COMMON_H +#define COMMON_H + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 配置常量 */ + +#define BIP_VERSION "v25.11.18" +#define CONFIG_DIR "/etc/blockip" +#define CONFIG_FILE CONFIG_DIR "/config" +#define LOG_FILE "/var/log/bip.log" +#define MAX_LOG_SIZE 10485760 // 10MB +#define DEFAULT_MAX_RETRIES 3 +#define DEFAULT_BAN_TIME "24h" +#define RECORD_DIR CONFIG_DIR "/counts" +#define PERSIST_FILE CONFIG_DIR "/blacklist" +#define WHITELIST_FILE CONFIG_DIR "/whitelist" +#define INSTALL_PATH "/usr/local/bin/bip" +#define NFT_TABLE "inet filter" +#define NFT_SET "blacklist" +#define NFT_SET_V6 "blacklist_v6" +#define NFT_WHITELIST "whitelist" +#define NFT_WHITELIST_V6 "whitelist_v6" + +/* 缓冲区大小 */ +#define MAX_LINE_LEN 512 +#define MAX_IP_LEN 128 +#define MAX_COUNTRY_CODE 8 +#define MAX_COMMAND_LEN 1024 +#define MAX_PATH_LEN 256 + +/* 颜色定义 */ +#define C_RESET "\033[0m" +#define C_GREEN "\033[32m" +#define C_CYAN "\033[36m" +#define C_YELLOW "\033[33m" +#define C_RED "\033[31m" + +/* 错误码 */ +#define SUCCESS 0 +#define ERROR_PERMISSION -1 +#define ERROR_FILE -2 +#define ERROR_NETWORK -3 +#define ERROR_INVALID_ARG -4 + +/* 工具宏 */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* 打印消息 */ +void msg(const char *color, const char *message); + +/* 检查root权限 */ +int check_root(void); + +/* 获取当前时间戳字符串 */ +void get_timestamp(char *buffer, size_t size); + +/* 读取配置文件中的封禁时间 */ +const char* get_ban_time_from_config(void); + +/* 保存封禁时间到配置文件 */ +int save_ban_time_to_config(const char *ban_time); + +/* 读取配置文件中的最大重试次数 */ +int get_max_retries_from_config(void); + +/* 保存最大重试次数到配置文件 */ +int save_max_retries_to_config(int max_retries); + +#endif /* COMMON_H */ diff --git a/blockip/include/geo.h b/blockip/include/geo.h new file mode 100644 index 0000000..232a806 --- /dev/null +++ b/blockip/include/geo.h @@ -0,0 +1,15 @@ +#ifndef GEO_H +#define GEO_H + +#include "common.h" + +/* 查询IP的国家代码 */ +int query_country_code(const char *ip, char *country_code, size_t size); + +/* 获取国家名称 */ +const char* get_country_name(const char *country_code); + +/* 补充持久化文件中缺失的国家信息 */ +void supplement_country_info(const char *current_ip); + +#endif /* GEO_H */ diff --git a/blockip/include/install.h b/blockip/include/install.h new file mode 100644 index 0000000..1b01171 --- /dev/null +++ b/blockip/include/install.h @@ -0,0 +1,21 @@ +#ifndef INSTALL_H +#define INSTALL_H + +#include "common.h" + +/* 安装服务 */ +int install_service(void); + +/* 卸载服务 */ +int uninstall_service(void); + +/* 配置PAM钩子 */ +int setup_pam_hooks(void); + +/* 移除PAM钩子 */ +int remove_pam_hooks(void); + +/* 创建systemd服务 */ +int create_systemd_service(void); + +#endif /* INSTALL_H */ diff --git a/blockip/include/ip_utils.h b/blockip/include/ip_utils.h new file mode 100644 index 0000000..6246250 --- /dev/null +++ b/blockip/include/ip_utils.h @@ -0,0 +1,45 @@ +#ifndef IP_UTILS_H +#define IP_UTILS_H + +#include "common.h" +#include + +/* IP类型 */ +typedef enum { + IP_TYPE_UNKNOWN = 0, + IP_TYPE_V4, + IP_TYPE_V6, + IP_TYPE_V4_CIDR, + IP_TYPE_V6_CIDR +} ip_type_t; + +/* IP信息结构 */ +typedef struct { + char ip[MAX_IP_LEN]; + char country_code[MAX_COUNTRY_CODE]; + ip_type_t type; + int cidr_mask; +} ip_info_t; + +/* 判断是否为IPv6 */ +bool is_ipv6(const char *ip); + +/* 判断是否为CIDR格式 */ +bool is_cidr(const char *ip); + +/* 解析IP信息 */ +int parse_ip_info(const char *input, ip_info_t *info); + +/* 获取当前连接IP */ +char* get_remote_ip(void); + +/* 验证IP格式 */ +bool validate_ip_format(const char *ip); + +/* 格式化IP为nftables元素 */ +void format_nft_element(const char *ip, char *output, size_t size, const char *timeout); + +/* 检查IP是否匹配白名单 */ +bool ip_matches_whitelist_entry(const char *ip, const char *whitelist_entry); + +#endif /* IP_UTILS_H */ diff --git a/blockip/include/log.h b/blockip/include/log.h new file mode 100644 index 0000000..9cf79b8 --- /dev/null +++ b/blockip/include/log.h @@ -0,0 +1,18 @@ +#ifndef LOG_H +#define LOG_H + +#include "common.h" + +/* 日志初始化 */ +int log_init(void); + +/* 写入日志 */ +void log_write(const char *format, ...); + +/* 日志轮转 */ +void log_rotate(void); + +/* 显示最新日志 */ +void log_show_recent(int lines); + +#endif /* LOG_H */ diff --git a/blockip/include/nftables.h b/blockip/include/nftables.h new file mode 100644 index 0000000..52a123a --- /dev/null +++ b/blockip/include/nftables.h @@ -0,0 +1,32 @@ +#ifndef NFTABLES_H +#define NFTABLES_H + +#include "common.h" +#include "ip_utils.h" +#include + +/* 检查并安装nftables环境 */ +int check_and_install_nftables(void); + +/* 初始化nftables规则 */ +int init_nftables_rules(void); + +/* 添加IP到nftables黑名单 */ +int nft_add_to_blacklist(const ip_info_t *ip_info); + +/* 从nftables黑名单移除IP */ +int nft_remove_from_blacklist(const char *ip); + +/* 添加IP到nftables白名单 */ +int nft_add_to_whitelist(const char *ip); + +/* 从nftables白名单移除IP */ +int nft_remove_from_whitelist(const char *ip); + +/* 获取nftables集合中的元素数量 */ +int nft_get_set_count(const char *set_name); + +/* 列出nftables集合中的元素 */ +int nft_list_set_elements(const char *set_name, char *buffer, size_t size); + +#endif /* NFTABLES_H */ diff --git a/blockip/include/pam.h b/blockip/include/pam.h new file mode 100644 index 0000000..b460c1d --- /dev/null +++ b/blockip/include/pam.h @@ -0,0 +1,21 @@ +#ifndef PAM_H +#define PAM_H + +#include "common.h" + +/* PAM检查:处理登录失败 */ +int pam_check_failed_login(void); + +/* PAM清理:处理登录成功 */ +int pam_clean_on_success(void); + +/* 记录失败次数 */ +int record_failure(const char *ip); + +/* 清除失败记录 */ +int clear_failure_record(const char *ip); + +/* 获取失败次数 */ +int get_failure_count(const char *ip); + +#endif /* PAM_H */ diff --git a/blockip/include/stats.h b/blockip/include/stats.h new file mode 100644 index 0000000..eb9fe08 --- /dev/null +++ b/blockip/include/stats.h @@ -0,0 +1,18 @@ +#ifndef STATS_H +#define STATS_H + +#include "common.h" + +/* 显示完整统计信息 */ +void show_statistics(void); + +/* 显示活跃封禁列表 */ +void show_active_bans(void); + +/* 显示国家统计 */ +void show_country_stats(void); + +/* 显示IP段聚合统计 */ +void show_subnet_aggregation(void); + +#endif /* STATS_H */ diff --git a/blockip/include/whitelist.h b/blockip/include/whitelist.h new file mode 100644 index 0000000..5410337 --- /dev/null +++ b/blockip/include/whitelist.h @@ -0,0 +1,22 @@ +#ifndef WHITELIST_H +#define WHITELIST_H + +#include "common.h" +#include + +/* 检查IP是否在白名单中 */ +bool is_in_whitelist(const char *ip); + +/* 添加IP到白名单文件 */ +int whitelist_add_to_file(const char *ip); + +/* 从白名单文件移除IP */ +int whitelist_remove_from_file(const char *ip); + +/* 显示白名单列表 */ +void whitelist_show(void); + +/* 恢复白名单到nftables */ +int whitelist_restore(void); + +#endif /* WHITELIST_H */ diff --git a/blockip/src/ban.c b/blockip/src/ban.c new file mode 100644 index 0000000..1231e11 --- /dev/null +++ b/blockip/src/ban.c @@ -0,0 +1,330 @@ +#include "ban.h" +#if defined(__unix__) || defined(__linux__) +#include // O_CREAT, O_RDWR +#include // flock, LOCK_EX, LOCK_UN +#include // 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); +} diff --git a/blockip/src/common.c b/blockip/src/common.c new file mode 100644 index 0000000..3c09338 --- /dev/null +++ b/blockip/src/common.c @@ -0,0 +1,201 @@ +#include "common.h" +#include + +void msg(const char *color, const char *message) { + printf("%s%s%s\n", color, message, C_RESET); +} + +int check_root(void) { + if (getuid() != 0) { + msg(C_RED, "❌ 需要 root 权限"); + return ERROR_PERMISSION; + } + return SUCCESS; +} + +void get_timestamp(char *buffer, size_t size) { + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + strftime(buffer, size, "%Y-%m-%d %H:%M:%S", tm_info); +} + +const char* get_ban_time_from_config(void) { + static char ban_time[32] = {0}; + + FILE *fp = fopen(CONFIG_FILE, "r"); + if (!fp) { + /* 配置文件不存在,返回默认值 */ + return DEFAULT_BAN_TIME; + } + + char line[MAX_LINE_LEN]; + while (fgets(line, sizeof(line), fp)) { + /* 跳过注释和空行 */ + if (line[0] == '#' || line[0] == '\n') { + continue; + } + + /* 解析 BAN_TIME=xxx */ + if (strncmp(line, "BAN_TIME=", 9) == 0) { + char *value = line + 9; + /* 去除换行符和空白 */ + char *p = value; + while (*p && *p != '\n' && *p != '\r') p++; + *p = '\0'; + + /* 去除前导空白 */ + while (*value == ' ' || *value == '\t') value++; + + if (strlen(value) > 0) { + strncpy(ban_time, value, sizeof(ban_time) - 1); + ban_time[sizeof(ban_time) - 1] = '\0'; + fclose(fp); + return ban_time; + } + } + } + + fclose(fp); + return DEFAULT_BAN_TIME; +} + +int save_ban_time_to_config(const char *ban_time) { + if (!ban_time) { + return ERROR_INVALID_ARG; + } + + /* 创建配置目录 */ + mkdir(CONFIG_DIR, 0700); + + /* 读取现有配置 */ + FILE *fp = fopen(CONFIG_FILE, "r"); + char temp_file[MAX_PATH_LEN]; + snprintf(temp_file, sizeof(temp_file), "%s.tmp", CONFIG_FILE); + FILE *temp_fp = fopen(temp_file, "w"); + + if (!temp_fp) { + if (fp) fclose(fp); + return ERROR_FILE; + } + + bool found = false; + bool has_header = false; + + if (fp) { + char line[MAX_LINE_LEN]; + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '#') { + has_header = true; + } + + if (strncmp(line, "BAN_TIME=", 9) == 0) { + fprintf(temp_fp, "BAN_TIME=%s\n", ban_time); + found = true; + } else { + fputs(line, temp_fp); + } + } + fclose(fp); + } + + /* 如果没有找到 BAN_TIME,添加新配置 */ + if (!found) { + /* 如果是新文件,添加完整头部 */ + if (!has_header && access(CONFIG_FILE, F_OK) != 0) { + fprintf(temp_fp, "# Block-IP Configuration\n"); + fprintf(temp_fp, "# Ban time format: Xh (hours), Xm (minutes), or empty for permanent\n"); + fprintf(temp_fp, "# Examples: 24h, 12h, 1h, 30m, or empty string for permanent ban\n"); + fprintf(temp_fp, "# Max retries: 1-10, default is 3\n\n"); + } + fprintf(temp_fp, "BAN_TIME=%s\n", ban_time); + /* 如果是新文件,也添加默认的 MAX_RETRIES */ + if (!has_header && access(CONFIG_FILE, F_OK) != 0) { + fprintf(temp_fp, "MAX_RETRIES=%d\n", DEFAULT_MAX_RETRIES); + } + } + + fclose(temp_fp); + chmod(temp_file, 0600); + rename(temp_file, CONFIG_FILE); + + return SUCCESS; +} + +int get_max_retries_from_config(void) { + FILE *fp = fopen(CONFIG_FILE, "r"); + if (!fp) { + return DEFAULT_MAX_RETRIES; /* 返回默认值 */ + } + + char line[MAX_LINE_LEN]; + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '#' || line[0] == '\n') { + continue; + } + + if (strncmp(line, "MAX_RETRIES=", 12) == 0) { + int retries = atoi(line + 12); + fclose(fp); + return (retries > 0 && retries <= 10) ? retries : DEFAULT_MAX_RETRIES; + } + } + + fclose(fp); + return DEFAULT_MAX_RETRIES; +} + +int save_max_retries_to_config(int max_retries) { + if (max_retries <= 0 || max_retries > 10) { + return ERROR_INVALID_ARG; + } + + mkdir(CONFIG_DIR, 0700); + + FILE *fp = fopen(CONFIG_FILE, "r"); + char temp_file[MAX_PATH_LEN]; + snprintf(temp_file, sizeof(temp_file), "%s.tmp", CONFIG_FILE); + FILE *temp_fp = fopen(temp_file, "w"); + + if (!temp_fp) { + if (fp) fclose(fp); + return ERROR_FILE; + } + + bool found = false; + bool has_header = false; + + if (fp) { + char line[MAX_LINE_LEN]; + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '#') { + has_header = true; + } + + if (strncmp(line, "MAX_RETRIES=", 12) == 0) { + fprintf(temp_fp, "MAX_RETRIES=%d\n", max_retries); + found = true; + } else { + fputs(line, temp_fp); + } + } + fclose(fp); + } + + /* 如果没有找到 MAX_RETRIES,添加新配置 */ + if (!found) { + /* 如果是新文件,添加完整头部 */ + if (!has_header && access(CONFIG_FILE, F_OK) != 0) { + fprintf(temp_fp, "# Block-IP Configuration\n"); + fprintf(temp_fp, "# Ban time format: Xh (hours), Xm (minutes), or empty for permanent\n"); + fprintf(temp_fp, "# Max retries: 1-10, default is 3\n\n"); + fprintf(temp_fp, "BAN_TIME=%s\n", DEFAULT_BAN_TIME); + } + fprintf(temp_fp, "MAX_RETRIES=%d\n", max_retries); + } + + fclose(temp_fp); + chmod(temp_file, 0600); + rename(temp_file, CONFIG_FILE); + + return SUCCESS; +} diff --git a/blockip/src/geo.c b/blockip/src/geo.c new file mode 100644 index 0000000..b3f53dc --- /dev/null +++ b/blockip/src/geo.c @@ -0,0 +1,150 @@ +#include "geo.h" +#include "log.h" +#include + +/* 国家代码映射表 */ +static const struct { + const char *code; + const char *name; +} country_map[] = { + {"CN", "中国"}, + {"US", "美国"}, + {"RU", "俄罗斯"}, + {"MY", "马来西亚"}, + {"NL", "荷兰"}, + {"DE", "德国"}, + {"GB", "英国"}, + {"FR", "法国"}, + {"JP", "日本"}, + {"KR", "韩国"}, + {"SG", "新加坡"}, + {"HK", "香港"}, + {"TW", "台湾"}, + {"IN", "印度"}, + {"BR", "巴西"}, + {"CA", "加拿大"}, + {"AU", "澳大利亚"}, + {"IT", "意大利"}, + {"ES", "西班牙"}, + {"SE", "瑞典"}, + {"PL", "波兰"}, + {"UA", "乌克兰"}, + {"TR", "土耳其"}, + {"ID", "印度尼西亚"}, + {"TH", "泰国"}, + {"VN", "越南"}, + {"MX", "墨西哥"}, + {"AR", "阿根廷"}, + {"CL", "智利"}, + {"RO", "罗马尼亚"}, + {"CZ", "捷克"} +}; + +int query_country_code(const char *ip, char *country_code, size_t size) { + if (!ip || !country_code) { + return ERROR_INVALID_ARG; + } + + char command[MAX_COMMAND_LEN]; + snprintf(command, sizeof(command), + "curl -s --max-time 2 \"https://ipinfo.io/%s/country\" 2>/dev/null | tr -d '\\n\\r '", + ip); + + FILE *fp = popen(command, "r"); + if (!fp) { + return ERROR_NETWORK; + } + + char result[16] = {0}; + if (fgets(result, sizeof(result), fp)) { + /* 去除空白字符 */ + char *p = result; + while (*p && isspace(*p)) p++; + + if (strlen(p) == 2 && isalpha(p[0]) && isalpha(p[1])) { + strncpy(country_code, p, size - 1); + country_code[size - 1] = '\0'; + pclose(fp); + return SUCCESS; + } + } + + pclose(fp); + return ERROR_NETWORK; +} + +const char* get_country_name(const char *country_code) { + if (!country_code) return country_code; + + for (size_t i = 0; i < ARRAY_SIZE(country_map); i++) { + if (strcmp(country_map[i].code, country_code) == 0) { + return country_map[i].name; + } + } + + return country_code; +} + +void supplement_country_info(const char *current_ip) { + FILE *fp = fopen(PERSIST_FILE, "r"); + if (!fp) return; + + char line[MAX_LINE_LEN]; + int update_count = 0; + const int MAX_UPDATES = 3; + + /* 创建临时文件 */ + 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; + } + + while (fgets(line, sizeof(line), fp) && update_count < MAX_UPDATES) { + /* 去除换行符 */ + line[strcspn(line, "\n")] = 0; + + if (strlen(line) == 0) continue; + + /* 检查是否已有国家信息 */ + if (strchr(line, '|')) { + fprintf(temp_fp, "%s\n", line); + continue; + } + + /* 检查是否是IPv6或CIDR */ + if (strchr(line, ':') || strchr(line, '/')) { + fprintf(temp_fp, "%s\n", line); + continue; + } + + /* 跳过当前正在处理的IP */ + if (current_ip && strcmp(line, current_ip) == 0) { + fprintf(temp_fp, "%s\n", line); + continue; + } + + /* 查询国家信息 */ + char country_code[MAX_COUNTRY_CODE]; + if (query_country_code(line, country_code, sizeof(country_code)) == SUCCESS) { + fprintf(temp_fp, "%s|%s\n", line, country_code); + log_write("[补充地区] IP=%s 国家=%s", line, get_country_name(country_code)); + update_count++; + } else { + fprintf(temp_fp, "%s\n", line); + } + } + + /* 复制剩余内容 */ + while (fgets(line, sizeof(line), fp)) { + fputs(line, temp_fp); + } + + fclose(fp); + fclose(temp_fp); + + /* 替换原文件 */ + rename(temp_file, PERSIST_FILE); +} diff --git a/blockip/src/install.c b/blockip/src/install.c new file mode 100644 index 0000000..273bfc5 --- /dev/null +++ b/blockip/src/install.c @@ -0,0 +1,246 @@ +#include "install.h" +#include "nftables.h" +#include "ban.h" +#include "whitelist.h" +#include "log.h" + +int setup_pam_hooks(void) { + const char *pam_file = "/etc/pam.d/sshd"; + + /* 备份原文件 */ + char backup_cmd[MAX_COMMAND_LEN]; + snprintf(backup_cmd, sizeof(backup_cmd), "cp -f %s %s.bak.blockip", pam_file, pam_file); + system(backup_cmd); + + /* 移除旧的钩子 */ + char remove_cmd[MAX_COMMAND_LEN]; + snprintf(remove_cmd, sizeof(remove_cmd), "sed -i '\\|%s|d' %s", INSTALL_PATH, pam_file); + system(remove_cmd); + + /* 添加新的钩子 */ + FILE *fp = fopen(pam_file, "r"); + if (!fp) { + return ERROR_FILE; + } + + char temp_file[MAX_PATH_LEN]; + snprintf(temp_file, sizeof(temp_file), "%s.tmp", pam_file); + FILE *temp_fp = fopen(temp_file, "w"); + if (!temp_fp) { + fclose(fp); + return ERROR_FILE; + } + + /* 在第一行插入check钩子 */ + fprintf(temp_fp, "auth optional pam_exec.so quiet %s check\n", INSTALL_PATH); + + /* 复制原内容 */ + char line[MAX_LINE_LEN]; + while (fgets(line, sizeof(line), fp)) { + fputs(line, temp_fp); + } + + /* 在末尾添加clean钩子 */ + fprintf(temp_fp, "session optional pam_exec.so quiet %s clean\n", INSTALL_PATH); + + fclose(fp); + fclose(temp_fp); + + rename(temp_file, pam_file); + + log_write("[安装] PAM钩子已配置"); + return SUCCESS; +} + +int remove_pam_hooks(void) { + const char *pam_file = "/etc/pam.d/sshd"; + + char command[MAX_COMMAND_LEN]; + snprintf(command, sizeof(command), "sed -i '\\|%s|d' %s", INSTALL_PATH, pam_file); + system(command); + + log_write("[卸载] PAM钩子已移除"); + return SUCCESS; +} + +int create_systemd_service(void) { + + const char *service_file = "/etc/systemd/system/bip.service"; FILE *fp = fopen(service_file, "w"); + if (!fp) { + return ERROR_FILE; + } + + fprintf(fp, "[Unit]\n"); + fprintf(fp, "Description=BIP (Block-IP) Service\n"); + fprintf(fp, "After=network.target nftables.service\n\n"); + fprintf(fp, "[Service]\n"); + fprintf(fp, "Type=oneshot\n"); + fprintf(fp, "ExecStart=%s restore\n", INSTALL_PATH); + fprintf(fp, "RemainAfterExit=yes\n\n"); + fprintf(fp, "[Install]\n"); + fprintf(fp, "WantedBy=multi-user.target\n"); + + fclose(fp); + + /* 重载systemd配置 */ + system("systemctl daemon-reload"); + + /* 启用服务 */ + system("systemctl enable bip.service"); + + log_write("[安装] systemd服务已创建"); + return SUCCESS; +} + +static int remove_systemd_service(void) { + /* 停止并禁用服务 */ + system("systemctl stop bip.service 2>/dev/null"); + system("systemctl disable bip.service 2>/dev/null"); + + /* 删除服务文件 */ + remove("/etc/systemd/system/bip.service"); + + /* 重载systemd配置 */ + system("systemctl daemon-reload"); + + log_write("[卸载] systemd服务已移除"); + return SUCCESS; +} + +int install_service(void) { + msg(C_YELLOW, "开始安装 BIP (Block-IP)..."); + + /* 复制程序到安装路径 */ + char exe_path[MAX_PATH_LEN]; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len != -1) { + exe_path[len] = '\0'; + + char copy_cmd[MAX_COMMAND_LEN]; + snprintf(copy_cmd, sizeof(copy_cmd), "cp -f %s %s && chmod +x %s", + exe_path, INSTALL_PATH, INSTALL_PATH); + system(copy_cmd); + } + + /* 创建必要的目录和文件 */ + char mkdir_cmd[MAX_COMMAND_LEN]; + snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p %s && chmod 700 %s", CONFIG_DIR, CONFIG_DIR); + system(mkdir_cmd); + + snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p %s && chmod 770 %s", RECORD_DIR, RECORD_DIR); + system(mkdir_cmd); + + FILE *fp = fopen(PERSIST_FILE, "a"); + if (fp) { + chmod(PERSIST_FILE, 0600); + fclose(fp); + } + + fp = fopen(WHITELIST_FILE, "a"); + if (fp) { + chmod(WHITELIST_FILE, 0600); + fclose(fp); + } + + /* 创建默认配置文件 */ + if (access(CONFIG_FILE, F_OK) != 0) { + save_ban_time_to_config(DEFAULT_BAN_TIME); + msg(C_GREEN, " ✓ 已创建默认配置文件"); + } + + log_init(); + + /* 安装nftables */ + check_and_install_nftables(); + + /* 初始化规则 */ + init_nftables_rules(); + + /* 恢复数据 */ + restore_from_persist(); + whitelist_restore(); + + /* 配置PAM钩子 */ + setup_pam_hooks(); + + /* 创建systemd服务 */ + create_systemd_service(); + + msg(C_GREEN, "✅ 安装完成!输入 bip list 查看效果。"); + + return SUCCESS; +} + +int uninstall_service(void) { + msg(C_YELLOW, "⚠️ 开始卸载 BIP (Block-IP)..."); + + /* 移除systemd服务 */ + remove_systemd_service(); + msg(C_GREEN, " ✓ 已移除 systemd 服务"); + + /* 清除nftables规则 */ + char command[MAX_COMMAND_LEN]; + + snprintf(command, sizeof(command), "nft delete rule %s input ip saddr @%s drop 2>/dev/null", + NFT_TABLE, NFT_SET); + system(command); + + snprintf(command, sizeof(command), "nft delete rule %s input ip6 saddr @%s drop 2>/dev/null", + NFT_TABLE, NFT_SET_V6); + system(command); + + snprintf(command, sizeof(command), "nft delete rule %s input ip saddr @%s accept 2>/dev/null", + NFT_TABLE, NFT_WHITELIST); + system(command); + + snprintf(command, sizeof(command), "nft delete rule %s input ip6 saddr @%s accept 2>/dev/null", + NFT_TABLE, NFT_WHITELIST_V6); + system(command); + + snprintf(command, sizeof(command), "nft delete set %s %s 2>/dev/null", NFT_TABLE, NFT_SET); + system(command); + + snprintf(command, sizeof(command), "nft delete set %s %s 2>/dev/null", NFT_TABLE, NFT_SET_V6); + system(command); + + snprintf(command, sizeof(command), "nft delete set %s %s 2>/dev/null", NFT_TABLE, NFT_WHITELIST); + system(command); + + snprintf(command, sizeof(command), "nft delete set %s %s 2>/dev/null", NFT_TABLE, NFT_WHITELIST_V6); + system(command); + + msg(C_GREEN, " ✓ 已清除防火墙规则"); + + /* 移除PAM钩子 */ + remove_pam_hooks(); + msg(C_GREEN, " ✓ 已移除 PAM 钩子"); + + /* 询问是否删除数据文件 */ + printf("是否删除配置目录和日志? [y/N] "); + char answer[10]; + if (fgets(answer, sizeof(answer), stdin)) { + if (answer[0] == 'y' || answer[0] == 'Y') { + char rm_cmd[MAX_COMMAND_LEN]; + snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", CONFIG_DIR); + system(rm_cmd); + + remove(LOG_FILE); + char log_backup[MAX_PATH_LEN]; + snprintf(log_backup, sizeof(log_backup), "%s.1", LOG_FILE); + remove(log_backup); + + msg(C_GREEN, " ✓ 已删除数据文件"); + } else { + printf("%s ↳ 保留: %s, %s%s\n", + C_CYAN, CONFIG_DIR, LOG_FILE, C_RESET); + } + } + + /* 删除程序文件 */ + remove(INSTALL_PATH); + msg(C_GREEN, " ✓ 已删除程序文件"); + + msg(C_GREEN, "\n✅ 卸载完成!"); + + return SUCCESS; +} diff --git a/blockip/src/ip_utils.c b/blockip/src/ip_utils.c new file mode 100644 index 0000000..5a5d642 --- /dev/null +++ b/blockip/src/ip_utils.c @@ -0,0 +1,180 @@ +#include "ip_utils.h" +#include +#include + +bool is_ipv6(const char *ip) { + if (!ip) return false; + + /* 移除CIDR部分 */ + char ip_copy[MAX_IP_LEN]; + strncpy(ip_copy, ip, sizeof(ip_copy) - 1); + ip_copy[sizeof(ip_copy) - 1] = '\0'; + + char *slash = strchr(ip_copy, '/'); + if (slash) *slash = '\0'; + + return strchr(ip_copy, ':') != NULL; +} + +bool is_cidr(const char *ip) { + return strchr(ip, '/') != NULL; +} + +int parse_ip_info(const char *input, ip_info_t *info) { + if (!input || !info) { + return ERROR_INVALID_ARG; + } + + memset(info, 0, sizeof(ip_info_t)); + strncpy(info->ip, input, sizeof(info->ip) - 1); + + /* 检查是否是CIDR */ + char *slash = strchr(info->ip, '/'); + if (slash) { + *slash = '\0'; + info->cidr_mask = atoi(slash + 1); + + if (is_ipv6(info->ip)) { + info->type = IP_TYPE_V6_CIDR; + } else { + info->type = IP_TYPE_V4_CIDR; + } + *slash = '/'; /* 恢复 */ + } else { + if (is_ipv6(info->ip)) { + info->type = IP_TYPE_V6; + } else { + info->type = IP_TYPE_V4; + } + } + + return SUCCESS; +} + +char* get_remote_ip(void) { + static char ip[MAX_IP_LEN]; + char *env_ip = NULL; + + env_ip = getenv("PAM_RHOST"); + if (!env_ip) { + env_ip = getenv("RHOST"); + } + + if (env_ip) { + strncpy(ip, env_ip, sizeof(ip) - 1); + ip[sizeof(ip) - 1] = '\0'; + return ip; + } + + return NULL; +} + +bool validate_ip_format(const char *ip) { + if (!ip) return false; + + char ip_copy[MAX_IP_LEN]; + strncpy(ip_copy, ip, sizeof(ip_copy) - 1); + ip_copy[sizeof(ip_copy) - 1] = '\0'; + + /* 检查CIDR */ + char *slash = strchr(ip_copy, '/'); + if (slash) { + *slash = '\0'; + int mask = atoi(slash + 1); + + if (is_ipv6(ip_copy)) { + if (mask < 0 || mask > 128) return false; + } else { + if (mask < 0 || mask > 32) return false; + } + } + + /* 验证IP地址 */ + struct sockaddr_in sa; + struct sockaddr_in6 sa6; + + if (inet_pton(AF_INET, ip_copy, &(sa.sin_addr)) == 1) { + return true; + } + if (inet_pton(AF_INET6, ip_copy, &(sa6.sin6_addr)) == 1) { + return true; + } + + return false; +} + +void format_nft_element(const char *ip, char *output, size_t size, const char *timeout) { + if (!ip || !output) return; + + char element[MAX_LINE_LEN]; + + if (is_cidr(ip)) { + strncpy(element, ip, sizeof(element) - 1); + } else if (is_ipv6(ip)) { + snprintf(element, sizeof(element), "%s/128", ip); + } else { + snprintf(element, sizeof(element), "%s/32", ip); + } + + if (timeout && strlen(timeout) > 0) { + snprintf(output, size, "%s timeout %s", element, timeout); + } else { + snprintf(output, size, "%s", element); + } +} + +bool ip_matches_whitelist_entry(const char *ip, const char *whitelist_entry) { + if (!ip || !whitelist_entry) return false; + + /* 完全匹配 */ + if (strcmp(ip, whitelist_entry) == 0) { + return true; + } + + /* CIDR匹配 */ + if (strchr(whitelist_entry, '/')) { + char wl_copy[MAX_IP_LEN]; + strncpy(wl_copy, whitelist_entry, sizeof(wl_copy) - 1); + wl_copy[sizeof(wl_copy) - 1] = '\0'; + + char *slash = strchr(wl_copy, '/'); + if (slash) { + *slash = '\0'; + int mask = atoi(slash + 1); + + /* 简单的IPv4段匹配 */ + if (!is_ipv6(ip)) { + char ip_prefix[MAX_IP_LEN]; + strncpy(ip_prefix, ip, sizeof(ip_prefix) - 1); + + if (mask == 8) { + /* 匹配 A.*.*.* */ + char *dot = strchr(ip_prefix, '.'); + if (dot) *dot = '\0'; + return strncmp(ip, wl_copy, strlen(ip_prefix)) == 0; + } else if (mask == 16) { + /* 匹配 A.B.*.* */ + char *dot1 = strchr(ip_prefix, '.'); + if (dot1) { + char *dot2 = strchr(dot1 + 1, '.'); + if (dot2) *dot2 = '\0'; + } + return strncmp(ip, wl_copy, strlen(ip_prefix)) == 0; + } else if (mask == 24) { + /* 匹配 A.B.C.* */ + char *dot1 = strchr(ip_prefix, '.'); + if (dot1) { + char *dot2 = strchr(dot1 + 1, '.'); + if (dot2) { + char *dot3 = strchr(dot2 + 1, '.'); + if (dot3) *dot3 = '\0'; + } + } + return strncmp(ip, wl_copy, strlen(ip_prefix)) == 0; + } + } + } + } + + return false; +} diff --git a/blockip/src/log.c b/blockip/src/log.c new file mode 100644 index 0000000..a0762e9 --- /dev/null +++ b/blockip/src/log.c @@ -0,0 +1,63 @@ +#include "log.h" +#include +#include +#include + +int log_init(void) { + FILE *fp = fopen(LOG_FILE, "a"); + if (!fp) { + return ERROR_FILE; + } + chmod(LOG_FILE, 0666); + fclose(fp); + return SUCCESS; +} + +void log_rotate(void) { + struct stat st; + if (stat(LOG_FILE, &st) != 0) { + return; + } + + if (st.st_size >= MAX_LOG_SIZE) { + char backup_file[MAX_PATH_LEN]; + snprintf(backup_file, sizeof(backup_file), "%s.1", LOG_FILE); + + remove(backup_file); + rename(LOG_FILE, backup_file); + + FILE *fp = fopen(LOG_FILE, "w"); + if (fp) { + chmod(LOG_FILE, 0666); + fclose(fp); + } + } +} + +void log_write(const char *format, ...) { + log_rotate(); + + FILE *fp = fopen(LOG_FILE, "a"); + if (!fp) { + return; + } + + char timestamp[64]; + get_timestamp(timestamp, sizeof(timestamp)); + + fprintf(fp, "[%s] ", timestamp); + + va_list args; + va_start(args, format); + vfprintf(fp, format, args); + va_end(args); + + fprintf(fp, "\n"); + fclose(fp); +} + +void log_show_recent(int lines) { + char command[MAX_COMMAND_LEN]; + snprintf(command, sizeof(command), "tail -n %d %s 2>/dev/null", lines, LOG_FILE); + system(command); +} diff --git a/blockip/src/main.c b/blockip/src/main.c new file mode 100644 index 0000000..46ccf4d --- /dev/null +++ b/blockip/src/main.c @@ -0,0 +1,263 @@ +#include "common.h" +#include "log.h" +#include "ban.h" +#include "whitelist.h" +#include "stats.h" +#include "pam.h" +#include "install.h" +#include "nftables.h" +#include "ip_utils.h" + +/* 显示帮助信息 */ +void show_help(void) { + printf("BIP (Block-IP) %s - IPv6 + CIDR + Whitelist\n", BIP_VERSION); + printf("--------------------------------------\n"); + printf("使用方法:\n"); + printf(" bip list 查看实时统计/活跃列表/日志\n"); + printf(" bip show 显示本地持久化封禁列表\n"); + printf(" bip add 手动封禁 IP (支持IPv4/IPv6/CIDR)\n"); + printf(" 示例: 1.1.1.1 或 1.1.1.0/24 或 2001:db8::/32\n"); + printf(" bip del 手动解封 IP (支持IPv4/IPv6/CIDR)\n"); + printf(" bip vip add 添加IP到白名单 (支持IPv4/IPv6/CIDR)\n"); + printf(" bip vip del 从白名单移除IP\n"); + printf(" bip vip list 显示白名单列表\n"); + printf(" bip config 显示当前配置\n"); + printf(" bip config time