320 lines
9.6 KiB
C
320 lines
9.6 KiB
C
#define _GNU_SOURCE
|
||
#include <dlfcn.h>
|
||
#include <fcntl.h>
|
||
#include <json-c/json.h>
|
||
#include <stdbool.h> // 引入 bool 类型
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <time.h>
|
||
#include <unistd.h>
|
||
|
||
#define CONFIG_FILE "./config/execve_rules.json"
|
||
#define LOG_FILE "./logs/execve.log"
|
||
#define LOG_OUT_FILE "./logs/execve_out.log"
|
||
|
||
#define COMMAND_NOT_FOUND "/usr/lib/command-not-found"
|
||
|
||
#define ANSI_COLOR_RED "\033[31m"
|
||
#define ANSI_COLOR_YELLOW "\033[33m"
|
||
#define ANSI_COLOR_RESET "\033[0m"
|
||
|
||
typedef struct {
|
||
char cmd[256];
|
||
char type[32];
|
||
char msg[1024];
|
||
char args[10][256]; // 支持最多 10 个参数
|
||
int arg_count;
|
||
} Rule;
|
||
|
||
typedef struct {
|
||
bool enabled;
|
||
Rule *rules;
|
||
int rule_count;
|
||
} Config;
|
||
|
||
// 加载配置
|
||
Config load_config() {
|
||
Config config = {false, NULL, 0};
|
||
json_object *root = json_object_from_file(CONFIG_FILE);
|
||
if (!root) {
|
||
fprintf(stderr, "Failed to parse JSON from %s\n", CONFIG_FILE);
|
||
return config;
|
||
}
|
||
|
||
json_object *enabled_obj;
|
||
if (json_object_object_get_ex(root, "enabled", &enabled_obj)) {
|
||
config.enabled = json_object_get_boolean(enabled_obj);
|
||
}
|
||
|
||
if (!config.enabled) {
|
||
json_object_put(root);
|
||
return config;
|
||
}
|
||
|
||
json_object *rules_array_obj;
|
||
if (json_object_object_get_ex(root, "rules", &rules_array_obj) &&
|
||
json_object_get_type(rules_array_obj) == json_type_array) {
|
||
config.rule_count = json_object_array_length(rules_array_obj);
|
||
config.rules = malloc(sizeof(Rule) * config.rule_count);
|
||
if (!config.rules) {
|
||
fprintf(stderr, "Failed to allocate memory for rules\n");
|
||
json_object_put(root);
|
||
config.rule_count = 0;
|
||
return config;
|
||
}
|
||
|
||
for (int i = 0; i < config.rule_count; i++) {
|
||
json_object *rule_obj =
|
||
json_object_array_get_idx(rules_array_obj, i);
|
||
json_object *cmd, *type, *msg, *args;
|
||
|
||
json_object_object_get_ex(rule_obj, "cmd", &cmd);
|
||
json_object_object_get_ex(rule_obj, "type", &type);
|
||
json_object_object_get_ex(rule_obj, "msg", &msg);
|
||
|
||
if (cmd)
|
||
strncpy(config.rules[i].cmd, json_object_get_string(cmd), 255);
|
||
if (type)
|
||
strncpy(config.rules[i].type, json_object_get_string(type), 31);
|
||
if (msg)
|
||
strncpy(config.rules[i].msg, json_object_get_string(msg), 1023);
|
||
|
||
// 解析 args 参数
|
||
config.rules[i].arg_count = 0;
|
||
if (json_object_object_get_ex(rule_obj, "args", &args) &&
|
||
json_object_get_type(args) == json_type_array) {
|
||
int args_len = json_object_array_length(args);
|
||
config.rules[i].arg_count =
|
||
args_len < 10 ? args_len : 10; // 限制最多 10 个参数
|
||
|
||
for (int j = 0; j < config.rules[i].arg_count; j++) {
|
||
json_object *arg_item = json_object_array_get_idx(args, j);
|
||
if (arg_item) {
|
||
strncpy(config.rules[i].args[j],
|
||
json_object_get_string(arg_item), 255);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
json_object_put(root);
|
||
return config;
|
||
}
|
||
|
||
// 检查 args 是否匹配
|
||
int args_match(char *const argv[], Rule *rule) {
|
||
if (rule->arg_count == 0) {
|
||
return 1; // 没有 args 约束,则直接匹配
|
||
}
|
||
|
||
for (int i = 0; i < rule->arg_count; i++) {
|
||
int found = 0;
|
||
for (int j = 1; argv[j] != NULL; j++) { // 跳过 argv[0] (命令本身)
|
||
if (strcmp(argv[j], rule->args[i]) == 0) {
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) return 0; // 只要有一个参数没有匹配,则不符合规则
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
// 写入日志
|
||
void write_log(const char *filename, char *const argv[]) {
|
||
time_t now;
|
||
time(&now);
|
||
|
||
FILE *log = fopen(LOG_FILE, "a");
|
||
if (!log) return;
|
||
|
||
fprintf(log, "[%s] Command: %s\n", ctime(&now), filename);
|
||
|
||
for (int i = 0; argv[i]; i++) {
|
||
fprintf(log, "arg[%d]: %s\n", i, argv[i]);
|
||
}
|
||
|
||
fclose(log);
|
||
}
|
||
|
||
// 判断字符是否为 ANSI 转义序列
|
||
int is_ansi_escape_sequence(const char *str) {
|
||
// ANSI 转义序列的常见格式是以 ESC 开头,后跟 '[', 'm' 等
|
||
return str[0] == '\033' && str[1] == '[';
|
||
}
|
||
|
||
// 复制标准输出和错误输出到日志文件
|
||
void duplicate_output_to_log() {
|
||
int log_fd = open(LOG_OUT_FILE, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||
if (log_fd == -1) {
|
||
perror("Failed to open log file");
|
||
return;
|
||
}
|
||
|
||
int pipe_fds[2];
|
||
if (pipe(pipe_fds) == -1) {
|
||
perror("Failed to create pipe");
|
||
close(log_fd);
|
||
return;
|
||
}
|
||
|
||
pid_t pid = fork();
|
||
if (pid == -1) {
|
||
perror("Failed to fork");
|
||
close(log_fd);
|
||
close(pipe_fds[0]);
|
||
close(pipe_fds[1]);
|
||
return;
|
||
}
|
||
|
||
if (pid == 0) { // 子进程:读取 pipe,并写入日志
|
||
close(pipe_fds[1]); // 关闭写端
|
||
char buffer[1024];
|
||
ssize_t n;
|
||
int has_error = 0;
|
||
|
||
while ((n = read(pipe_fds[0], buffer, sizeof(buffer))) > 0) {
|
||
// 检查buffer中是否包含错误信息
|
||
if (strstr(buffer, "error") || strstr(buffer, "Error") ||
|
||
strstr(buffer, "ERROR")) {
|
||
has_error = 1;
|
||
}
|
||
|
||
// 输出到终端时保留颜色
|
||
if (isatty(STDOUT_FILENO)) {
|
||
if (write(STDOUT_FILENO, buffer, n) == -1) {
|
||
perror("Failed to write to stdout");
|
||
}
|
||
}
|
||
if (write(log_fd, buffer, n) == -1) {
|
||
perror("Failed to write to log file");
|
||
}
|
||
}
|
||
|
||
if (has_error) {
|
||
printf("\n检测到命令执行出错,已经上报北冥论坛~ \n");
|
||
fflush(stdout); // 确保提示文字被输出
|
||
}
|
||
|
||
close(pipe_fds[0]);
|
||
close(log_fd);
|
||
_exit(0);
|
||
} else { // 父进程:写入 pipe
|
||
close(pipe_fds[0]); // 关闭读端
|
||
dup2(pipe_fds[1], STDOUT_FILENO);
|
||
dup2(pipe_fds[1], STDERR_FILENO);
|
||
close(pipe_fds[1]);
|
||
close(log_fd);
|
||
}
|
||
}
|
||
|
||
typedef int (*orig_execve_type)(const char *filename, char *const argv[],
|
||
char *const envp[]);
|
||
|
||
static Config config;
|
||
static time_t last_modified_time = 0;
|
||
|
||
// 判断父进程是否为终端 shell (bash, zsh, fish 等)
|
||
int is_terminal_shell() {
|
||
pid_t ppid = getppid();
|
||
char path[64], proc_name[256];
|
||
FILE *file;
|
||
|
||
snprintf(path, sizeof(path), "/proc/%d/comm", ppid);
|
||
file = fopen(path, "r");
|
||
if (!file) return 0;
|
||
|
||
if (fgets(proc_name, sizeof(proc_name), file)) {
|
||
proc_name[strcspn(proc_name, "\n")] = 0; // 去除换行符
|
||
if (strcmp(proc_name, "bash") == 0 || strcmp(proc_name, "zsh") == 0 ||
|
||
strcmp(proc_name, "fish") == 0 || strcmp(proc_name, "sh") == 0) {
|
||
fclose(file);
|
||
return 1;
|
||
}
|
||
}
|
||
fclose(file);
|
||
return 0;
|
||
}
|
||
|
||
// 检查配置文件是否已修改
|
||
int config_file_modified() {
|
||
struct stat file_stat;
|
||
if (stat(CONFIG_FILE, &file_stat) != 0) {
|
||
return 0;
|
||
}
|
||
return file_stat.st_mtime != last_modified_time;
|
||
}
|
||
|
||
// 加载或重新加载配置
|
||
void load_config_if_needed() {
|
||
if (config.rules == NULL || config_file_modified()) {
|
||
// 释放旧的规则
|
||
if (config.rules) {
|
||
free(config.rules);
|
||
config.rules = NULL;
|
||
config.rule_count = 0;
|
||
}
|
||
config = load_config();
|
||
struct stat file_stat;
|
||
if (stat(CONFIG_FILE, &file_stat) == 0) {
|
||
last_modified_time = file_stat.st_mtime;
|
||
}
|
||
}
|
||
}
|
||
|
||
int execve(const char *filename, char *const argv[], char *const envp[]) {
|
||
// 仅在 shell 终端调用 execve 时拦截
|
||
if (!is_terminal_shell()) {
|
||
orig_execve_type orig_execve =
|
||
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
|
||
return orig_execve(filename, argv, envp);
|
||
}
|
||
|
||
// 加载配置(仅在需要时)
|
||
load_config_if_needed();
|
||
|
||
// 如果功能被禁用,则直接执行
|
||
if (!config.enabled) {
|
||
orig_execve_type orig_execve =
|
||
(orig_execve_type)dlsym(RTLD_NEXT, "execve");
|
||
return orig_execve(filename, argv, envp);
|
||
}
|
||
|
||
write_log(filename, argv);
|
||
|
||
const char *basename = argv[0];
|
||
if (strcmp(filename, COMMAND_NOT_FOUND) == 0 && argv[2]) {
|
||
basename = argv[2];
|
||
}
|
||
|
||
for (int i = 0; i < config.rule_count; i++) {
|
||
if (strcmp(basename, config.rules[i].cmd) == 0 &&
|
||
args_match(argv, &config.rules[i])) {
|
||
if (strcmp(config.rules[i].type, "warn") == 0) {
|
||
printf(ANSI_COLOR_YELLOW "[Warning] %s\n" ANSI_COLOR_RESET,
|
||
config.rules[i].msg);
|
||
printf("按下 'Y' 继续执行, 或按任意键取消: ");
|
||
char input = getchar();
|
||
if (input != 'Y' && input != 'y') {
|
||
printf("\nExecution cancelled.\n");
|
||
return -1;
|
||
}
|
||
printf("\nContinuing execution...\n");
|
||
} else if (strcmp(config.rules[i].type, "error") == 0) {
|
||
printf(ANSI_COLOR_RED "[Error] %s" ANSI_COLOR_RESET "\n",
|
||
config.rules[i].msg);
|
||
return -1;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 复制 stdout 和 stderr 到日志文件
|
||
duplicate_output_to_log();
|
||
|
||
orig_execve_type orig_execve = (orig_execve_type)dlsym(RTLD_NEXT, "execve");
|
||
return orig_execve(filename, argv, envp);
|
||
} |