From b86dfb714602b3fc6d4b88bd1bb80e11dd5dd35b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 10 Jun 2025 11:03:42 +0800 Subject: [PATCH 01/10] test code for audit log --- src/audit.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ src/audit.h | 37 ++++++++++ src/protocol.c | 123 +++++++++++++++++++++++++++++++-- src/server.c | 42 ++++++------ src/server.h | 9 ++- 5 files changed, 362 insertions(+), 28 deletions(-) create mode 100644 src/audit.c create mode 100644 src/audit.h diff --git a/src/audit.c b/src/audit.c new file mode 100644 index 00000000..dd881a0b --- /dev/null +++ b/src/audit.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "audit.h" +#include "utils.h" + +static audit_config_t config = {0}; +static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; + +// 确保日志目录存在 +static int ensure_log_dir(const char *log_file) { + char *log_dir = strdup(log_file); + char *last_slash = strrchr(log_dir, '/'); + if (last_slash) { + *last_slash = '\0'; + lwsl_notice("Creating log directory: %s\n", log_dir); + if (mkdir(log_dir, 0755) != 0 && errno != EEXIST) { + lwsl_err("Failed to create log directory: %s, error: %s\n", log_dir, strerror(errno)); + free(log_dir); + return -1; + } + lwsl_notice("Log directory created or already exists: %s\n", log_dir); + } + free(log_dir); + return 0; +} + +int audit_init(const char *log_file, bool log_commands, bool log_output) { + lwsl_notice("Initializing audit system with log file: %s\n", log_file); + + if (config.enabled) { + lwsl_notice("Audit system already initialized\n"); + return 0; + } + + // 确保日志目录存在 + if (ensure_log_dir(log_file) != 0) { + lwsl_err("Failed to ensure log directory exists\n"); + return -1; + } + + config.log_file = strdup(log_file); + config.enabled = true; + config.log_commands = log_commands; + config.log_output = log_output; + + lwsl_notice("Opening log file: %s\n", log_file); + // 创建日志文件 + FILE *fp = fopen(log_file, "a"); + if (fp == NULL) { + lwsl_err("Failed to open audit log file: %s, error: %s\n", log_file, strerror(errno)); + return -1; + } + fclose(fp); + lwsl_notice("Log file opened successfully\n"); + + lwsl_notice("Audit system initialized successfully\n"); + return 0; +} + +static void write_log_entry(const audit_entry_t *entry) { + if (!config.enabled) { + lwsl_notice("Audit system is not enabled, skipping log entry\n"); + return; + } + + lwsl_notice("Writing log entry to file: %s\n", config.log_file); + pthread_mutex_lock(&log_mutex); + + FILE *fp = fopen(config.log_file, "a"); + if (fp == NULL) { + lwsl_err("Failed to open audit log file for writing: %s\n", strerror(errno)); + pthread_mutex_unlock(&log_mutex); + return; + } + + char timestamp[32]; + struct tm *tm_info = localtime(&entry->timestamp); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); + + // 写入日志文件,所有信息都在一行 + if (entry->command) { + fprintf(fp, "[%s] User: %s, Address: %s, Command: %s, Status: %d\n", + timestamp, + entry->user ? entry->user : "unknown", + entry->address ? entry->address : "unknown", + entry->command, + entry->status); + } else { + fprintf(fp, "[%s] User: %s, Address: %s\n", + timestamp, + entry->user ? entry->user : "unknown", + entry->address ? entry->address : "unknown"); + } + + if (entry->output) { + fprintf(fp, "Output: %s\n", entry->output); + } + fprintf(fp, "---\n"); + fflush(fp); // 确保立即写入文件 + + fclose(fp); + pthread_mutex_unlock(&log_mutex); + lwsl_notice("Log entry written successfully with user: %s\n", entry->user ? entry->user : "unknown"); +} + +void audit_log_command(const char *user, const char *address, const char *command, int status) { + lwsl_notice("Logging command: user='%s', address='%s', command='%s', status=%d\n", + user ? user : "unknown", address, command, status); + + if (!config.enabled || !config.log_commands) { + lwsl_notice("Command logging is disabled, skipping\n"); + return; + } + + if (!user || strlen(user) == 0) { + lwsl_warn("Empty username detected, using 'unknown'\n"); + } + + audit_entry_t entry = { + .timestamp = time(NULL), + .user = strdup(user && strlen(user) > 0 ? user : "unknown"), + .address = strdup(address), + .command = strdup(command), + .output = NULL, + .status = status + }; + + lwsl_notice("Created audit entry with user: '%s'\n", entry.user); + write_log_entry(&entry); + + free(entry.user); + free(entry.address); + free(entry.command); + lwsl_notice("Command logged successfully\n"); +} + +void audit_log_output(const char *user, const char *address, const char *output) { + lwsl_notice("Logging output: user=%s, address=%s\n", user, address); + + if (!config.enabled || !config.log_output) { + lwsl_notice("Output logging is disabled, skipping\n"); + return; + } + + audit_entry_t entry = { + .timestamp = time(NULL), + .user = strdup(user), + .address = strdup(address), + .command = NULL, + .output = strdup(output), + .status = 0 + }; + + write_log_entry(&entry); + + free(entry.user); + free(entry.address); + free(entry.output); + lwsl_notice("Output logged successfully\n"); +} + +void audit_cleanup(void) { + lwsl_notice("Cleaning up audit system\n"); + if (!config.enabled) { + lwsl_notice("Audit system is not enabled, nothing to clean up\n"); + return; + } + + free(config.log_file); + config.enabled = false; + pthread_mutex_destroy(&log_mutex); + lwsl_notice("Audit system cleaned up successfully\n"); +} \ No newline at end of file diff --git a/src/audit.h b/src/audit.h new file mode 100644 index 00000000..05ba9a7c --- /dev/null +++ b/src/audit.h @@ -0,0 +1,37 @@ +#ifndef TTYD_AUDIT_H +#define TTYD_AUDIT_H + +#include +#include + +// 审计日志结构体 +typedef struct { + char *log_file; // 日志文件路径 + bool enabled; // 是否启用审计 + bool log_commands; // 是否记录命令 + bool log_output; // 是否记录输出 +} audit_config_t; + +// 审计日志条目结构体 +typedef struct { + time_t timestamp; // 时间戳 + char *user; // 用户名 + char *address; // 客户端地址 + char *command; // 执行的命令 + char *output; // 命令输出 + int status; // 命令执行状态 +} audit_entry_t; + +// 初始化审计系统 +int audit_init(const char *log_file, bool log_commands, bool log_output); + +// 记录命令 +void audit_log_command(const char *user, const char *address, const char *command, int status); + +// 记录输出 +void audit_log_output(const char *user, const char *address, const char *output); + +// 关闭审计系统 +void audit_cleanup(void); + +#endif // TTYD_AUDIT_H \ No newline at end of file diff --git a/src/protocol.c b/src/protocol.c index 53e65d4d..5a25b420 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -5,14 +5,64 @@ #include #include #include +#include +#include #include "pty.h" #include "server.h" #include "utils.h" +#include "audit.h" // initial message list static char initial_cmds[] = {SET_WINDOW_TITLE, SET_PREFERENCES}; +// 日志文件路径 +#define AUDIT_LOG_FILE "/var/log/ttyd/audit.log" + +// 确保日志目录存在 +static void ensure_log_dir() { + char *log_dir = strdup(AUDIT_LOG_FILE); + char *last_slash = strrchr(log_dir, '/'); + if (last_slash) { + *last_slash = '\0'; + mkdir(log_dir, 0755); + } + free(log_dir); +} + +// 写入审计日志 +static void write_audit_log(const char *user, const char *address, const char *command, int status) { + static FILE *log_file = NULL; + static bool initialized = false; + + if (!initialized) { + ensure_log_dir(); + log_file = fopen(AUDIT_LOG_FILE, "a"); + if (log_file == NULL) { + lwsl_err("Failed to open audit log file: %s\n", strerror(errno)); + return; + } + initialized = true; + } + + time_t now; + struct tm *tm_info; + char timestamp[26]; + + time(&now); + tm_info = localtime(&now); + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); + + // 写入日志文件 + fprintf(log_file, "[%s] User: %s, Address: %s, Command: %s, Status: %d\n", + timestamp, user, address, command, status); + fflush(log_file); + + // 同时输出到标准输出 + printf("[%s] User: %s, Address: %s, Command: %s, Status: %d\n", + timestamp, user, address, command, status); +} + static int send_initial_message(struct lws *wsi, int index) { unsigned char message[LWS_PRE + 1 + 4096]; unsigned char *p = &message[LWS_PRE]; @@ -85,6 +135,15 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { return; } + // 记录命令输出 + if (server->audit_output && buf != NULL) { + char *output = xmalloc(buf->len + 1); + memcpy(output, buf->base, buf->len); + output[buf->len] = '\0'; + audit_log_output(ctx->pss->user, ctx->pss->address, output); + free(output); + } + if (eof && !process_running(process)) ctx->pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006; else @@ -197,7 +256,9 @@ static bool check_auth(struct lws *wsi, struct pss_tty *pss) { int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss_tty *pss = (struct pss_tty *)user; char buf[256]; - size_t n = 0; + static char current_cmd[1024] = {0}; // 用于存储当前命令 + static size_t cmd_len = 0; // 当前命令长度 + int n = 0; // 用于存储 lws_hdr_copy 的返回值 switch (reason) { case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: @@ -233,6 +294,27 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, pss->authenticated = false; pss->wsi = wsi; pss->lws_close_status = LWS_CLOSE_STATUS_NOSTATUS; + memset(pss->user, 0, sizeof(pss->user)); // 初始化 user 字段 + + // 设置用户信息 + if (server->username != NULL) { + // 如果设置了 username 参数,使用它作为审计日志的用户名 + strncpy(pss->user, server->username, sizeof(pss->user) - 1); + pss->user[sizeof(pss->user) - 1] = '\0'; // 确保字符串结束 + lwsl_notice("Using username from command line: %s\n", pss->user); + } else if (server->auth_header != NULL) { + // 否则尝试从 HTTP 头部获取 + if (lws_hdr_custom_copy(wsi, pss->user, sizeof(pss->user), server->auth_header, strlen(server->auth_header)) <= 0) { + lwsl_warn("Failed to get user from auth header\n"); + strcpy(pss->user, "unknown"); + } + } else { + strcpy(pss->user, "anonymous"); + } + + // 确保用户名字符串正确结束 + pss->user[sizeof(pss->user) - 1] = '\0'; + lwsl_notice("Final username set to: %s\n", pss->user); if (server->url_arg) { while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) { @@ -247,7 +329,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, server->client_count++; lws_get_peer_simple(lws_get_network_wsi(wsi), pss->address, sizeof(pss->address)); - lwsl_notice("WS %s - %s, clients: %d\n", pss->path, pss->address, server->client_count); + lwsl_notice("WS %s - %s, clients: %d, user: %s\n", pss->path, pss->address, server->client_count, pss->user); break; case LWS_CALLBACK_SERVER_WRITEABLE: @@ -307,11 +389,38 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, switch (command) { case INPUT: if (!server->writable) break; + + // 获取输入内容 + char *input = xmalloc(pss->len); + memcpy(input, pss->buffer + 1, pss->len - 1); + input[pss->len - 1] = '\0'; + + // 处理命令输入 + if (input[0] == '\r' || input[0] == '\n') { + // 命令结束,打印完整命令 + if (cmd_len > 0) { + current_cmd[cmd_len] = '\0'; + // 记录命令,初始状态为0 + lwsl_notice("Logging command for user: %s\n", pss->user); // 添加调试日志 + audit_log_command(pss->user, pss->address, current_cmd, 0); + cmd_len = 0; // 重置命令长度 + } + } else { + // 将输入添加到当前命令 + if (cmd_len + pss->len - 1 < sizeof(current_cmd)) { + memcpy(current_cmd + cmd_len, input, pss->len - 1); + cmd_len += pss->len - 1; + } + } + + // 继续处理命令 int err = pty_write(pss->process, pty_buf_init(pss->buffer + 1, pss->len - 1)); if (err) { lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err)); + free(input); return -1; } + free(input); break; case RESIZE_TERMINAL: if (pss->process == NULL) break; @@ -371,6 +480,12 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, } if (pss->process != NULL) { + // 记录命令执行状态 + if (cmd_len > 0) { + current_cmd[cmd_len] = '\0'; + audit_log_command(pss->user, pss->address, current_cmd, pss->process->exit_code); + } + ((pty_ctx_t *)pss->process->ctx)->ws_closed = true; if (process_running(pss->process)) { pty_pause(pss->process); @@ -379,8 +494,8 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, } } - if ((server->once || server->exit_no_conn) && server->client_count == 0) { - lwsl_notice("exiting due to the --once/--exit-no-conn option.\n"); + if (server->once && server->client_count == 0) { + lwsl_notice("exiting due to the --once option.\n"); force_exit = true; lws_cancel_service(context); exit(0); diff --git a/src/server.c b/src/server.c index c9e2fa96..00da21fe 100644 --- a/src/server.c +++ b/src/server.c @@ -12,6 +12,7 @@ #include #include "utils.h" +#include "audit.h" #ifndef TTYD_VERSION #define TTYD_VERSION "unknown" @@ -56,6 +57,7 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'}, {"socket-owner", required_argument, NULL, 'U'}, {"credential", required_argument, NULL, 'c'}, {"auth-header", required_argument, NULL, 'H'}, + {"username", required_argument, NULL, 'n'}, {"uid", required_argument, NULL, 'u'}, {"gid", required_argument, NULL, 'g'}, {"signal", required_argument, NULL, 's'}, @@ -77,14 +79,12 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'}, {"check-origin", no_argument, NULL, 'O'}, {"max-clients", required_argument, NULL, 'm'}, {"once", no_argument, NULL, 'o'}, - {"exit-no-conn", no_argument, NULL, 'q'}, {"browser", no_argument, NULL, 'B'}, {"debug", required_argument, NULL, 'd'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, - {"serv_buffer_size", required_argument, NULL, 'f'}, {NULL, 0, 0, 0}}; -static const char *opt_string = "p:i:U:c:H:u:g:s:w:I:b:f:P:6aSC:K:A:Wt:T:Om:oqBd:vh"; +static const char *opt_string = "p:i:U:c:H:n:u:g:s:w:I:b:P:6aSC:K:A:Wt:T:Om:oBd:vh"; static void print_help() { // clang-format off @@ -99,6 +99,7 @@ static void print_help() { " -U, --socket-owner User owner of the UNIX domain socket file, when enabled (eg: user:group)\n" " -c, --credential Credential for basic authentication (format: username:password)\n" " -H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication\n" + " -n, --username Username for audit logging, will be used in audit logs instead of auth header value\n" " -u, --uid User id to run with\n" " -g, --gid Group id to run with\n" " -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n" @@ -110,11 +111,9 @@ static void print_help() { " -O, --check-origin Do not allow websocket connection from different origin\n" " -m, --max-clients Maximum clients to support (default: 0, no limit)\n" " -o, --once Accept only one client and exit on disconnection\n" - " -q, --exit-no-conn Exit on all clients disconnection\n" " -B, --browser Open terminal with the default system browser\n" " -I, --index Custom index.html path\n" " -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)\n" - " -f, --serv_buffer_size Maximum chunk of file that can be sent at once (eg: --service_buffer_size 4096 indicates 4KB)\n" #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 " -P, --ping-interval Websocket ping interval(sec) (default: 5)\n" #endif @@ -154,10 +153,8 @@ static void print_config() { if (server->url_arg) lwsl_notice(" allow url arg: true\n"); if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients); if (server->once) lwsl_notice(" once: true\n"); - if (server->exit_no_conn) lwsl_notice(" exit_no_conn: true\n"); if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index); if (server->cwd != NULL) lwsl_notice(" working directory: %s\n", server->cwd); - if (server->serv_buffer_size != 0) lwsl_notice(" Service buffer size: %d bytes\n", server->serv_buffer_size); if (!server->writable) lwsl_notice("The --writable option is not set, will start in readonly mode"); } @@ -314,7 +311,17 @@ int main(int argc, char **argv) { #endif int start = calc_command_start(argc, argv); + if (start < 0) return 1; + server = server_new(argc, argv, start); + if (server == NULL) return 1; + + // 初始化审计系统 + const char *log_file = "/var/log/ttyd/audit.log"; + if (audit_init(log_file, true, true) != 0) { + lwsl_err("Failed to initialize audit system\n"); + return 1; + } struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); @@ -323,6 +330,7 @@ int main(int argc, char **argv) { info.protocols = protocols; info.gid = -1; info.uid = -1; + info.pt_serv_buf_size = 262144; info.max_http_header_pool = 16; info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DISABLE_IPV6; #ifndef LWS_WITHOUT_EXTENSIONS @@ -330,8 +338,7 @@ int main(int argc, char **argv) { #endif info.max_http_header_data = 65535; - - int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE; + int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO; char iface[128] = ""; char socket_owner[128] = ""; bool browser = false; @@ -354,7 +361,7 @@ int main(int argc, char **argv) { print_help(); return 0; case 'v': - printf("ttyd version %s\n", TTYD_VERSION); + printf("ttyd version 1004 %s\n", TTYD_VERSION); return 0; case 'd': debug_level = parse_int("debug", optarg); @@ -374,9 +381,6 @@ int main(int argc, char **argv) { case 'o': server->once = true; break; - case 'q': - server->exit_no_conn = true; - break; case 'B': browser = true; break; @@ -387,14 +391,6 @@ int main(int argc, char **argv) { return -1; } break; - case 'f': - info.pt_serv_buf_size = parse_int("serv_buffer_size", optarg); - if (info.pt_serv_buf_size < 0) { - fprintf(stderr, "ttyd: invalid service buffer size: %s\n", optarg); - return -1; - } - server->serv_buffer_size = info.pt_serv_buf_size; - break; case 'i': strncpy(iface, optarg, sizeof(iface) - 1); iface[sizeof(iface) - 1] = '\0'; @@ -415,6 +411,10 @@ int main(int argc, char **argv) { case 'H': server->auth_header = strdup(optarg); break; + case 'n': + server->username = strdup(optarg); + lwsl_notice("Username set to: %s\n", server->username); + break; case 'u': info.uid = parse_int("uid", optarg); break; diff --git a/src/server.h b/src/server.h index fcd82ccb..2c2aa6fe 100644 --- a/src/server.h +++ b/src/server.h @@ -3,6 +3,7 @@ #include #include "pty.h" +#include "audit.h" // client message #define INPUT '0' @@ -59,14 +60,14 @@ struct pss_tty { typedef struct { struct pss_tty *pss; bool ws_closed; -} pty_ctx_t; +} pty_ctx_t ; struct server { int client_count; // client count - int serv_buffer_size; // service buffer size char *prefs_json; // client preferences char *credential; // encoded basic auth credential char *auth_header; // header name used for auth proxy + char *username; // username for audit logging char *index; // custom index.html char *command; // full command line char **argv; // command with arguments @@ -79,9 +80,11 @@ struct server { bool check_origin; // whether allow websocket connection from different origin int max_clients; // maximum clients to support bool once; // whether accept only one client and exit on disconnection - bool exit_no_conn; // whether exit on all clients disconnection char socket_path[255]; // UNIX domain socket path char terminal_type[30]; // terminal type to report + char *audit_log; // audit log file path + bool audit_commands; // whether to log commands + bool audit_output; // whether to log output uv_loop_t *loop; // the libuv event loop }; From 4e6e5703301169664d6302ce62ce8cd417cfaa0e Mon Sep 17 00:00:00 2001 From: bin Date: Thu, 12 Jun 2025 16:05:43 +0800 Subject: [PATCH 02/10] use shell hook to audit log --- src/audit.c | 220 +++++++++++++++++++++++++++++++++++++------------ src/audit.h | 45 ++++++---- src/protocol.c | 172 +++++++++++++++++++------------------- src/server.c | 130 +++++++++++++++++++++++++++-- src/server.h | 12 ++- 5 files changed, 410 insertions(+), 169 deletions(-) diff --git a/src/audit.c b/src/audit.c index dd881a0b..474fa2c4 100644 --- a/src/audit.c +++ b/src/audit.c @@ -30,7 +30,7 @@ static int ensure_log_dir(const char *log_file) { return 0; } -int audit_init(const char *log_file, bool log_commands, bool log_output) { +int audit_init(const char *log_file) { lwsl_notice("Initializing audit system with log file: %s\n", log_file); if (config.enabled) { @@ -46,8 +46,6 @@ int audit_init(const char *log_file, bool log_commands, bool log_output) { config.log_file = strdup(log_file); config.enabled = true; - config.log_commands = log_commands; - config.log_output = log_output; lwsl_notice("Opening log file: %s\n", log_file); // 创建日志文件 @@ -63,12 +61,79 @@ int audit_init(const char *log_file, bool log_commands, bool log_output) { return 0; } -static void write_log_entry(const audit_entry_t *entry) { - if (!config.enabled) { - lwsl_notice("Audit system is not enabled, skipping log entry\n"); - return; +// 添加自定义字段 +int audit_add_custom_field(const char *key, const char *value) { + if (!key || !value) { + lwsl_err("Invalid custom field key or value\n"); + return -1; } + pthread_mutex_lock(&log_mutex); + + // 重新分配内存 + audit_custom_field_t *new_fields = xrealloc(config.custom_fields, + (config.custom_fields_count + 1) * sizeof(audit_custom_field_t)); + + if (!new_fields) { + lwsl_err("Failed to allocate memory for custom field\n"); + pthread_mutex_unlock(&log_mutex); + return -1; + } + + config.custom_fields = new_fields; + + // 添加新字段 + size_t key_len = strlen(key); + size_t value_len = strlen(value); + + config.custom_fields[config.custom_fields_count].key = xmalloc(key_len + 1); + config.custom_fields[config.custom_fields_count].value = xmalloc(value_len + 1); + + if (!config.custom_fields[config.custom_fields_count].key || + !config.custom_fields[config.custom_fields_count].value) { + lwsl_err("Failed to allocate memory for custom field strings\n"); + if (config.custom_fields[config.custom_fields_count].key) { + free(config.custom_fields[config.custom_fields_count].key); + } + if (config.custom_fields[config.custom_fields_count].value) { + free(config.custom_fields[config.custom_fields_count].value); + } + pthread_mutex_unlock(&log_mutex); + return -1; + } + + strncpy(config.custom_fields[config.custom_fields_count].key, key, key_len); + config.custom_fields[config.custom_fields_count].key[key_len] = '\0'; + + strncpy(config.custom_fields[config.custom_fields_count].value, value, value_len); + config.custom_fields[config.custom_fields_count].value[value_len] = '\0'; + + config.custom_fields_count++; + pthread_mutex_unlock(&log_mutex); + + lwsl_notice("Added custom field: %s=%s\n", key, value); + return 0; +} + +// 清除所有自定义字段 +void audit_clear_custom_fields(void) { + pthread_mutex_lock(&log_mutex); + + for (int i = 0; i < config.custom_fields_count; i++) { + free(config.custom_fields[i].key); + free(config.custom_fields[i].value); + } + + free(config.custom_fields); + config.custom_fields = NULL; + config.custom_fields_count = 0; + + pthread_mutex_unlock(&log_mutex); + lwsl_notice("Cleared all custom fields\n"); +} + +// 修改 write_log_entry 函数 +static void write_log_entry(const audit_entry_t *entry) { lwsl_notice("Writing log entry to file: %s\n", config.log_file); pthread_mutex_lock(&log_mutex); @@ -83,88 +148,140 @@ static void write_log_entry(const audit_entry_t *entry) { struct tm *tm_info = localtime(&entry->timestamp); strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); - // 写入日志文件,所有信息都在一行 + // 写入时间戳和地址 + fprintf(fp, "[%s] Address: %s", + timestamp, + entry->address ? entry->address : "unknown"); + + // 写入自定义字段 + for (int i = 0; i < entry->custom_fields_count; i++) { + fprintf(fp, ", %s: %s", + entry->custom_fields[i].key, + entry->custom_fields[i].value); + } + if (entry->command) { - fprintf(fp, "[%s] User: %s, Address: %s, Command: %s, Status: %d\n", - timestamp, - entry->user ? entry->user : "unknown", - entry->address ? entry->address : "unknown", + fprintf(fp, ", Command: %s, Status: %d\n", entry->command, entry->status); } else { - fprintf(fp, "[%s] User: %s, Address: %s\n", - timestamp, - entry->user ? entry->user : "unknown", - entry->address ? entry->address : "unknown"); + fprintf(fp, "\n"); } if (entry->output) { fprintf(fp, "Output: %s\n", entry->output); } fprintf(fp, "---\n"); - fflush(fp); // 确保立即写入文件 + fflush(fp); fclose(fp); pthread_mutex_unlock(&log_mutex); - lwsl_notice("Log entry written successfully with user: %s\n", entry->user ? entry->user : "unknown"); + lwsl_notice("Log entry written successfully\n"); } -void audit_log_command(const char *user, const char *address, const char *command, int status) { - lwsl_notice("Logging command: user='%s', address='%s', command='%s', status=%d\n", - user ? user : "unknown", address, command, status); +// 清理命令字符串,移除控制字符 +static char *clean_command_string(const char *cmd) { + if (!cmd) return NULL; - if (!config.enabled || !config.log_commands) { - lwsl_notice("Command logging is disabled, skipping\n"); - return; + size_t len = strlen(cmd); + char *clean = xmalloc(len + 1); + size_t j = 0; + + for (size_t i = 0; i < len; i++) { + // 跳过控制字符(ASCII 0-31,除了换行符和回车符) + if (cmd[i] >= 32 || cmd[i] == '\n' || cmd[i] == '\r') { + clean[j++] = cmd[i]; + } } - - if (!user || strlen(user) == 0) { - lwsl_warn("Empty username detected, using 'unknown'\n"); + clean[j] = '\0'; + + // 移除末尾的空白字符 + while (j > 0 && (clean[j-1] == ' ' || clean[j-1] == '\t')) { + clean[--j] = '\0'; } + + return clean; +} +// 修改 audit_log_command 函数 +void audit_log_command(const char *address, const char *command, int status) { + if (!config.enabled) { + lwsl_notice("Audit system is not enabled, skipping log entry\n"); + return; + } + + char *clean_cmd = clean_command_string(command); + lwsl_notice("Logging command: address='%s', command='%s', status=%d\n", + address, clean_cmd, status); + audit_entry_t entry = { .timestamp = time(NULL), - .user = strdup(user && strlen(user) > 0 ? user : "unknown"), .address = strdup(address), - .command = strdup(command), + .command = clean_cmd, .output = NULL, - .status = status + .status = status, + .custom_fields = NULL, + .custom_fields_count = 0 }; - lwsl_notice("Created audit entry with user: '%s'\n", entry.user); + // 复制自定义字段 + if (config.custom_fields_count > 0) { + entry.custom_fields = xmalloc((config.custom_fields_count + 1) * sizeof(audit_custom_field_t)); + entry.custom_fields_count = config.custom_fields_count; + + for (int i = 0; i < config.custom_fields_count; i++) { + entry.custom_fields[i].key = strdup(config.custom_fields[i].key); + entry.custom_fields[i].value = strdup(config.custom_fields[i].value); + } + } else { + entry.custom_fields = xmalloc(sizeof(audit_custom_field_t)); + entry.custom_fields_count = 0; + } + write_log_entry(&entry); - free(entry.user); + // 清理内存 free(entry.address); free(entry.command); + for (int i = 0; i < entry.custom_fields_count; i++) { + free(entry.custom_fields[i].key); + free(entry.custom_fields[i].value); + } + free(entry.custom_fields); + lwsl_notice("Command logged successfully\n"); } -void audit_log_output(const char *user, const char *address, const char *output) { - lwsl_notice("Logging output: user=%s, address=%s\n", user, address); - - if (!config.enabled || !config.log_output) { - lwsl_notice("Output logging is disabled, skipping\n"); - return; +// 验证审计字段格式 +int validate_audit_field(const char *field) { + if (!field) { + lwsl_err("Invalid audit field: NULL\n"); + return -1; } - audit_entry_t entry = { - .timestamp = time(NULL), - .user = strdup(user), - .address = strdup(address), - .command = NULL, - .output = strdup(output), - .status = 0 - }; + // 检查是否包含等号 + char *value = strchr(field, '='); + if (!value) { + lwsl_err("Invalid audit field format: %s (should be key=value)\n", field); + return -1; + } - write_log_entry(&entry); + // 验证键名不为空 + if (value == field) { + lwsl_err("Empty key in audit field: %s\n", field); + return -1; + } - free(entry.user); - free(entry.address); - free(entry.output); - lwsl_notice("Output logged successfully\n"); + // 验证值不为空 + if (*(value + 1) == '\0') { + lwsl_err("Empty value in audit field: %s\n", field); + return -1; + } + + return 0; } +// 修改 audit_cleanup 函数以清理自定义字段 void audit_cleanup(void) { lwsl_notice("Cleaning up audit system\n"); if (!config.enabled) { @@ -173,6 +290,7 @@ void audit_cleanup(void) { } free(config.log_file); + audit_clear_custom_fields(); // 清理自定义字段 config.enabled = false; pthread_mutex_destroy(&log_mutex); lwsl_notice("Audit system cleaned up successfully\n"); diff --git a/src/audit.h b/src/audit.h index 05ba9a7c..f77ff004 100644 --- a/src/audit.h +++ b/src/audit.h @@ -4,34 +4,45 @@ #include #include -// 审计日志结构体 +// 自定义字段结构 typedef struct { - char *log_file; // 日志文件路径 - bool enabled; // 是否启用审计 - bool log_commands; // 是否记录命令 - bool log_output; // 是否记录输出 + char *key; + char *value; +} audit_custom_field_t; + +// 审计配置结构 +typedef struct { + bool enabled; + char *log_file; + audit_custom_field_t *custom_fields; // 自定义字段数组 + int custom_fields_count; // 自定义字段数量 } audit_config_t; -// 审计日志条目结构体 +// 审计日志条目结构 typedef struct { - time_t timestamp; // 时间戳 - char *user; // 用户名 - char *address; // 客户端地址 - char *command; // 执行的命令 - char *output; // 命令输出 - int status; // 命令执行状态 + time_t timestamp; + char *address; + char *command; + char *output; + int status; + audit_custom_field_t *custom_fields; // 自定义字段数组 + int custom_fields_count; // 自定义字段数量 } audit_entry_t; // 初始化审计系统 -int audit_init(const char *log_file, bool log_commands, bool log_output); +int audit_init(const char *log_file); // 记录命令 -void audit_log_command(const char *user, const char *address, const char *command, int status); - -// 记录输出 -void audit_log_output(const char *user, const char *address, const char *output); +void audit_log_command(const char *address, const char *command, int status); // 关闭审计系统 void audit_cleanup(void); +// 新增函数声明 +int audit_add_custom_field(const char *key, const char *value); +void audit_clear_custom_fields(void); + +// 验证审计字段格式 +int validate_audit_field(const char *field); + #endif // TTYD_AUDIT_H \ No newline at end of file diff --git a/src/protocol.c b/src/protocol.c index 5a25b420..f1dc4c32 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -16,53 +16,6 @@ // initial message list static char initial_cmds[] = {SET_WINDOW_TITLE, SET_PREFERENCES}; -// 日志文件路径 -#define AUDIT_LOG_FILE "/var/log/ttyd/audit.log" - -// 确保日志目录存在 -static void ensure_log_dir() { - char *log_dir = strdup(AUDIT_LOG_FILE); - char *last_slash = strrchr(log_dir, '/'); - if (last_slash) { - *last_slash = '\0'; - mkdir(log_dir, 0755); - } - free(log_dir); -} - -// 写入审计日志 -static void write_audit_log(const char *user, const char *address, const char *command, int status) { - static FILE *log_file = NULL; - static bool initialized = false; - - if (!initialized) { - ensure_log_dir(); - log_file = fopen(AUDIT_LOG_FILE, "a"); - if (log_file == NULL) { - lwsl_err("Failed to open audit log file: %s\n", strerror(errno)); - return; - } - initialized = true; - } - - time_t now; - struct tm *tm_info; - char timestamp[26]; - - time(&now); - tm_info = localtime(&now); - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); - - // 写入日志文件 - fprintf(log_file, "[%s] User: %s, Address: %s, Command: %s, Status: %d\n", - timestamp, user, address, command, status); - fflush(log_file); - - // 同时输出到标准输出 - printf("[%s] User: %s, Address: %s, Command: %s, Status: %d\n", - timestamp, user, address, command, status); -} - static int send_initial_message(struct lws *wsi, int index) { unsigned char message[LWS_PRE + 1 + 4096]; unsigned char *p = &message[LWS_PRE]; @@ -123,6 +76,7 @@ static pty_ctx_t *pty_ctx_init(struct pss_tty *pss) { pty_ctx_t *ctx = xmalloc(sizeof(pty_ctx_t)); ctx->pss = pss; ctx->ws_closed = false; + ctx->last_audit_line = 0; // 初始化行号为0 return ctx; } @@ -135,15 +89,70 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { return; } - // 记录命令输出 - if (server->audit_output && buf != NULL) { + if (buf->len > 0) { char *output = xmalloc(buf->len + 1); memcpy(output, buf->base, buf->len); output[buf->len] = '\0'; - audit_log_output(ctx->pss->user, ctx->pss->address, output); + + lwsl_notice("Received output: %s\n", output); + + // 检查是否包含 AUDIT_CMD: 字符串 + char *audit_cmd = strstr(output, "AUDIT_CMD:"); + if (audit_cmd) { + // 提取行号和命令内容 + char *content = audit_cmd + strlen("AUDIT_CMD:"); + // 去掉换行 + char *newline = strchr(content, '\n'); + if (newline) *newline = '\0'; + + // 解析行号和命令 + int line_num; + char cmd[1024] = {0}; + if (sscanf(content, "%d %[^\n]", &line_num, cmd) == 2) { + // 检查是否是重复命令 + if (line_num != ctx->last_audit_line) { + audit_log_command(ctx->pss->address, cmd, 0); + ctx->last_audit_line = line_num; + } else { + lwsl_notice("Skipping duplicate command at line %d\n", line_num); + } + } + + // 移除 AUDIT_CMD 这一行 + char *line_start = audit_cmd; + // 找到这一行的开始 + while (line_start > output && *(line_start - 1) != '\n') { + line_start--; + } + // 找到这一行的结束 + char *line_end = strchr(audit_cmd, '\n'); + if (!line_end) line_end = output + buf->len; + + // 计算需要保留的内容长度 + size_t before_len = line_start - output; + size_t after_len = buf->len - (line_end - output); + + // 创建新的缓冲区,包含 AUDIT_CMD 行之前和之后的内容 + pty_buf_t *new_buf = pty_buf_init(output, before_len); + if (after_len > 0) { + pty_buf_t *after_buf = pty_buf_init(line_end + 1, after_len); + // 合并两个缓冲区 + char *combined = xmalloc(before_len + after_len); + memcpy(combined, output, before_len); + memcpy(combined + before_len, line_end + 1, after_len); + pty_buf_free(new_buf); + pty_buf_free(after_buf); + new_buf = pty_buf_init(combined, before_len + after_len); + free(combined); + } + + pty_buf_free(buf); + buf = new_buf; + } free(output); } + // 正常输出 if (eof && !process_running(process)) ctx->pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006; else @@ -184,26 +193,34 @@ static char **build_args(struct pss_tty *pss) { return argv; } +// 添加审计命令函数 +static const char *get_audit_command(void) { + return "echo \"AUDIT_CMD:$(fc -l -1 | cut -f 1-)\""; +} + static char **build_env(struct pss_tty *pss) { - int i = 0, n = 2; - char **envp = xmalloc(n * sizeof(char *)); - - // TERM - envp[i] = xmalloc(36); - snprintf(envp[i], 36, "TERM=%s", server->terminal_type); - i++; - - // TTYD_USER - if (strlen(pss->user) > 0) { - envp = xrealloc(envp, (++n) * sizeof(char *)); - envp[i] = xmalloc(40); - snprintf(envp[i], 40, "TTYD_USER=%s", pss->user); + int i = 0, n = 3; + char **envp = xmalloc(n * sizeof(char *)); + + // TERM + envp[i] = xmalloc(36); + snprintf(envp[i], 36, "TERM=%s", server->terminal_type); i++; - } - envp[i] = NULL; + // TTYD_USER + if (strlen(pss->user) > 0) { + envp[i] = xmalloc(40); + snprintf(envp[i], 40, "TTYD_USER=%s", pss->user); + i++; + } - return envp; + // 添加审计命令 + envp[i] = xmalloc(200); + snprintf(envp[i], 200, "PROMPT_COMMAND=%s", get_audit_command()); + i++; + + envp[i] = NULL; + return envp; } static bool spawn_process(struct pss_tty *pss, uint16_t columns, uint16_t rows) { @@ -395,24 +412,6 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, memcpy(input, pss->buffer + 1, pss->len - 1); input[pss->len - 1] = '\0'; - // 处理命令输入 - if (input[0] == '\r' || input[0] == '\n') { - // 命令结束,打印完整命令 - if (cmd_len > 0) { - current_cmd[cmd_len] = '\0'; - // 记录命令,初始状态为0 - lwsl_notice("Logging command for user: %s\n", pss->user); // 添加调试日志 - audit_log_command(pss->user, pss->address, current_cmd, 0); - cmd_len = 0; // 重置命令长度 - } - } else { - // 将输入添加到当前命令 - if (cmd_len + pss->len - 1 < sizeof(current_cmd)) { - memcpy(current_cmd + cmd_len, input, pss->len - 1); - cmd_len += pss->len - 1; - } - } - // 继续处理命令 int err = pty_write(pss->process, pty_buf_init(pss->buffer + 1, pss->len - 1)); if (err) { @@ -480,11 +479,6 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, } if (pss->process != NULL) { - // 记录命令执行状态 - if (cmd_len > 0) { - current_cmd[cmd_len] = '\0'; - audit_log_command(pss->user, pss->address, current_cmd, pss->process->exit_code); - } ((pty_ctx_t *)pss->process->ctx)->ws_closed = true; if (process_running(pss->process)) { diff --git a/src/server.c b/src/server.c index 00da21fe..fd01007e 100644 --- a/src/server.c +++ b/src/server.c @@ -83,6 +83,9 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'}, {"debug", required_argument, NULL, 'd'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, + {"audit-enable", no_argument, NULL, 0}, + {"audit-log-file", required_argument, NULL, 0}, + {"audit-field", required_argument, NULL, 0}, {NULL, 0, 0, 0}}; static const char *opt_string = "p:i:U:c:H:n:u:g:s:w:I:b:P:6aSC:K:A:Wt:T:Om:oBd:vh"; @@ -128,6 +131,9 @@ static void print_help() { #endif " -d, --debug Set log level (default: 7)\n" " -v, --version Print the version and exit\n" + " --audit-enable Enable command audit logging\n" + " --audit-log-file Audit log file path (default: /var/log/ttyd/audit.log)\n" + " --audit-field Add custom field to audit log (format: key=value), can be used multiple times\n" " -h, --help Print this text and exit\n\n" "Visit https://github.com/tsl0922/ttyd to get more information and report bugs.\n", TTYD_VERSION @@ -298,6 +304,61 @@ static int calc_command_start(int argc, char **argv) { return start; } +// 验证和初始化审计字段 +static int init_audit_fields(void) { + if (!server->audit_enabled) { + return 0; + } + lwsl_notice("Initializing audit system\n"); + // 设置默认日志文件路径 + if (!server->audit_log_file) { + server->audit_log_file = strdup("/var/log/ttyd/audit.log"); + lwsl_notice("Using default audit log file: %s\n", server->audit_log_file); + } + + // 验证自定义字段格式 + for (int i = 0; i < server->audit_fields_count; i++) { + char *field = server->audit_fields[i]; + lwsl_notice("Processing audit field[%d]: %s\n", i, field); + + // 创建字段的副本以避免修改原始字符串 + char *field_copy = strdup(field); + if (!field_copy) { + lwsl_err("Failed to duplicate audit field: %s\n", field); + return -1; + } + + char *value = strchr(field_copy, '='); + if (!value) { + lwsl_err("Invalid audit field format: %s (should be key=value)\n", field); + free(field_copy); + return -1; + } + + // 分割键值对 + *value = '\0'; // 在等号处终止键 + value++; // 移动到值部分 + + lwsl_notice("Adding audit field: key='%s', value='%s'\n", field_copy, value); + if (audit_add_custom_field(field_copy, value) != 0) { + lwsl_err("Failed to add audit field: %s=%s\n", field_copy, value); + free(field_copy); + return -1; + } + + free(field_copy); + lwsl_notice("Audit field[%d] added successfully\n", i); + } + + // 初始化审计系统,只记录命令 + if (audit_init(server->audit_log_file) != 0) { + lwsl_err("Failed to initialize audit system\n"); + return -1; + } + + return 0; +} + int main(int argc, char **argv) { if (argc == 1) { print_help(); @@ -316,13 +377,6 @@ int main(int argc, char **argv) { server = server_new(argc, argv, start); if (server == NULL) return 1; - // 初始化审计系统 - const char *log_file = "/var/log/ttyd/audit.log"; - if (audit_init(log_file, true, true) != 0) { - lwsl_err("Failed to initialize audit system\n"); - return 1; - } - struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); info.port = 7681; @@ -338,7 +392,7 @@ int main(int argc, char **argv) { #endif info.max_http_header_data = 65535; - int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO; + int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE; char iface[128] = ""; char socket_owner[128] = ""; bool browser = false; @@ -355,7 +409,9 @@ int main(int argc, char **argv) { // parse command line options int c; - while ((c = getopt_long(start, argv, opt_string, options, NULL)) != -1) { + int option_index = 0; // 添加 option_index 变量 + while ((c = getopt_long(start, argv, opt_string, options, &option_index)) != -1) { + lwsl_notice("Processing option: c=%d, option_index=%d\n", c, option_index); switch (c) { case 'h': print_help(); @@ -519,11 +575,46 @@ int main(int argc, char **argv) { json_object_object_add(client_prefs, key, obj != NULL ? obj : json_object_new_string(value)); } break; + case 0: + lwsl_notice("Case 0: option name=%s, optarg=%s\n", + options[option_index].name, + optarg ? optarg : "NULL"); + if (strcmp(options[option_index].name, "audit-enable") == 0) { + lwsl_notice("Found audit-enable option\n"); + server->audit_enabled = true; + } else if (strcmp(options[option_index].name, "audit-log-file") == 0) { + lwsl_notice("Found audit-log-file option\n"); + server->audit_log_file = strdup(optarg); + lwsl_notice("Audit log file set to: %s\n", server->audit_log_file); + } else if (strcmp(options[option_index].name, "audit-field") == 0) { + lwsl_notice("Found audit-field option\n"); + lwsl_notice("Raw optarg: %s, length: %zu\n", optarg, strlen(optarg)); + for (size_t i = 0; i < strlen(optarg); i++) { + lwsl_notice("optarg[%zu] = '%c' (0x%02x)\n", i, optarg[i], (unsigned char)optarg[i]); + } + if (validate_audit_field(optarg) != 0) { + lwsl_err("Invalid audit field format: %s\n", optarg); + cleanup(); + return -1; + } + server->audit_fields = xrealloc(server->audit_fields, (server->audit_fields_count + 1) * sizeof(char *)); + server->audit_fields[server->audit_fields_count++] = strdup(optarg); + lwsl_notice("Added audit field: %s\n", optarg); + } + break; default: print_help(); return -1; } } + + // 初始化审计字段 + if (init_audit_fields() != 0) { + lwsl_err("Failed to initialize audit fields\n"); + cleanup(); + return -1; + } + server->prefs_json = strdup(json_object_to_json_string(client_prefs)); json_object_put(client_prefs); @@ -632,5 +723,26 @@ int main(int argc, char **argv) { // cleanup server_free(server); + // 清理审计字段 + cleanup(); return 0; } + +void cleanup(void) { + // ... existing code ... + + // 清理审计系统 + if (server->audit_enabled) { + audit_cleanup(); + } + + // 清理审计字段 + if (server->audit_fields) { + for (char **field = server->audit_fields; *field; field++) { + free(*field); + } + free(server->audit_fields); + } + + // ... existing code ... +} diff --git a/src/server.h b/src/server.h index 2c2aa6fe..c8d55037 100644 --- a/src/server.h +++ b/src/server.h @@ -60,7 +60,8 @@ struct pss_tty { typedef struct { struct pss_tty *pss; bool ws_closed; -} pty_ctx_t ; + int last_audit_line; +} pty_ctx_t; struct server { int client_count; // client count @@ -83,8 +84,13 @@ struct server { char socket_path[255]; // UNIX domain socket path char terminal_type[30]; // terminal type to report char *audit_log; // audit log file path - bool audit_commands; // whether to log commands - bool audit_output; // whether to log output + uv_loop_t *loop; // the libuv event loop + + // 审计日志配置 + bool audit_enabled; + char *audit_log_file; + char **audit_fields; // NULL-terminated array of custom fields + int audit_fields_count; // number of audit fields }; From c303e785d87aa9dcbf455bbf22a5382ac8a48229 Mon Sep 17 00:00:00 2001 From: bin Date: Thu, 12 Jun 2025 18:52:43 +0800 Subject: [PATCH 03/10] fix core dump --- src/protocol.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/protocol.c b/src/protocol.c index f1dc4c32..200fa6dc 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -80,7 +80,9 @@ static pty_ctx_t *pty_ctx_init(struct pss_tty *pss) { return ctx; } -static void pty_ctx_free(pty_ctx_t *ctx) { free(ctx); } +static void pty_ctx_free(pty_ctx_t *ctx) { + free(ctx); +} static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { pty_ctx_t *ctx = (pty_ctx_t *)process->ctx; @@ -209,9 +211,10 @@ static char **build_env(struct pss_tty *pss) { // TTYD_USER if (strlen(pss->user) > 0) { - envp[i] = xmalloc(40); - snprintf(envp[i], 40, "TTYD_USER=%s", pss->user); - i++; + envp = xrealloc(envp, (++n) * sizeof(char *)); + envp[i] = xmalloc(40); + snprintf(envp[i], 40, "TTYD_USER=%s", pss->user); + i++; } // 添加审计命令 From d55bfba05816ac115b069055c62276ab260e850b Mon Sep 17 00:00:00 2001 From: bin Date: Fri, 13 Jun 2025 10:30:59 +0800 Subject: [PATCH 04/10] fix console init audit log err --- src/protocol.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/protocol.c b/src/protocol.c index 200fa6dc..efbc238c 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -111,13 +111,20 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { int line_num; char cmd[1024] = {0}; if (sscanf(content, "%d %[^\n]", &line_num, cmd) == 2) { - // 检查是否是重复命令 - if (line_num != ctx->last_audit_line) { - audit_log_command(ctx->pss->address, cmd, 0); + // 打开控制台,会收到一条AUDIT_CMD输出,需要过滤掉 + if (ctx->last_audit_line == 0) { ctx->last_audit_line = line_num; + lwsl_notice("Skipping init command: %s, at line %d\n", cmd, line_num); } else { - lwsl_notice("Skipping duplicate command at line %d\n", line_num); + // 检查是否是重复的AUDIT_CMD命令, 如果按回车键也会触发 PROMPT_COMMAND, 这种情况需要过滤 + if (line_num != ctx->last_audit_line) { + audit_log_command(ctx->pss->address, cmd, 0); + ctx->last_audit_line = line_num; + } else { + lwsl_notice("Skipping duplicate command at line %d\n", line_num); + } } + } // 移除 AUDIT_CMD 这一行 From 474d8b8b8e15ac0279c69ad9e4b27547145d4ac4 Mon Sep 17 00:00:00 2001 From: bin Date: Fri, 13 Jun 2025 11:40:37 +0800 Subject: [PATCH 05/10] fix log format --- src/audit.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/audit.c b/src/audit.c index 474fa2c4..cb14a517 100644 --- a/src/audit.c +++ b/src/audit.c @@ -161,17 +161,13 @@ static void write_log_entry(const audit_entry_t *entry) { } if (entry->command) { - fprintf(fp, ", Command: %s, Status: %d\n", - entry->command, - entry->status); - } else { - fprintf(fp, "\n"); + fprintf(fp, ", Command: %s", entry->command); } + fprintf(fp, "\n"); if (entry->output) { fprintf(fp, "Output: %s\n", entry->output); } - fprintf(fp, "---\n"); fflush(fp); fclose(fp); From 7293ef028935e0cf2237f74ca308a09c127596fd Mon Sep 17 00:00:00 2001 From: bin Date: Fri, 13 Jun 2025 14:38:23 +0800 Subject: [PATCH 06/10] remove command status in audit log --- src/audit.c | 13 +++---------- src/audit.h | 4 +--- src/protocol.c | 2 +- src/server.c | 9 --------- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/audit.c b/src/audit.c index cb14a517..e489d84c 100644 --- a/src/audit.c +++ b/src/audit.c @@ -164,12 +164,7 @@ static void write_log_entry(const audit_entry_t *entry) { fprintf(fp, ", Command: %s", entry->command); } fprintf(fp, "\n"); - - if (entry->output) { - fprintf(fp, "Output: %s\n", entry->output); - } fflush(fp); - fclose(fp); pthread_mutex_unlock(&log_mutex); lwsl_notice("Log entry written successfully\n"); @@ -200,22 +195,20 @@ static char *clean_command_string(const char *cmd) { } // 修改 audit_log_command 函数 -void audit_log_command(const char *address, const char *command, int status) { +void audit_log_command(const char *address, const char *command) { if (!config.enabled) { lwsl_notice("Audit system is not enabled, skipping log entry\n"); return; } char *clean_cmd = clean_command_string(command); - lwsl_notice("Logging command: address='%s', command='%s', status=%d\n", - address, clean_cmd, status); + lwsl_notice("Logging command: address='%s', command='%s'\n", + address, clean_cmd); audit_entry_t entry = { .timestamp = time(NULL), .address = strdup(address), .command = clean_cmd, - .output = NULL, - .status = status, .custom_fields = NULL, .custom_fields_count = 0 }; diff --git a/src/audit.h b/src/audit.h index f77ff004..87b125aa 100644 --- a/src/audit.h +++ b/src/audit.h @@ -23,8 +23,6 @@ typedef struct { time_t timestamp; char *address; char *command; - char *output; - int status; audit_custom_field_t *custom_fields; // 自定义字段数组 int custom_fields_count; // 自定义字段数量 } audit_entry_t; @@ -33,7 +31,7 @@ typedef struct { int audit_init(const char *log_file); // 记录命令 -void audit_log_command(const char *address, const char *command, int status); +void audit_log_command(const char *address, const char *command); // 关闭审计系统 void audit_cleanup(void); diff --git a/src/protocol.c b/src/protocol.c index efbc238c..3e7c8860 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -118,7 +118,7 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { } else { // 检查是否是重复的AUDIT_CMD命令, 如果按回车键也会触发 PROMPT_COMMAND, 这种情况需要过滤 if (line_num != ctx->last_audit_line) { - audit_log_command(ctx->pss->address, cmd, 0); + audit_log_command(ctx->pss->address, cmd); ctx->last_audit_line = line_num; } else { lwsl_notice("Skipping duplicate command at line %d\n", line_num); diff --git a/src/server.c b/src/server.c index fd01007e..591299fd 100644 --- a/src/server.c +++ b/src/server.c @@ -580,18 +580,10 @@ int main(int argc, char **argv) { options[option_index].name, optarg ? optarg : "NULL"); if (strcmp(options[option_index].name, "audit-enable") == 0) { - lwsl_notice("Found audit-enable option\n"); server->audit_enabled = true; } else if (strcmp(options[option_index].name, "audit-log-file") == 0) { - lwsl_notice("Found audit-log-file option\n"); server->audit_log_file = strdup(optarg); - lwsl_notice("Audit log file set to: %s\n", server->audit_log_file); } else if (strcmp(options[option_index].name, "audit-field") == 0) { - lwsl_notice("Found audit-field option\n"); - lwsl_notice("Raw optarg: %s, length: %zu\n", optarg, strlen(optarg)); - for (size_t i = 0; i < strlen(optarg); i++) { - lwsl_notice("optarg[%zu] = '%c' (0x%02x)\n", i, optarg[i], (unsigned char)optarg[i]); - } if (validate_audit_field(optarg) != 0) { lwsl_err("Invalid audit field format: %s\n", optarg); cleanup(); @@ -599,7 +591,6 @@ int main(int argc, char **argv) { } server->audit_fields = xrealloc(server->audit_fields, (server->audit_fields_count + 1) * sizeof(char *)); server->audit_fields[server->audit_fields_count++] = strdup(optarg); - lwsl_notice("Added audit field: %s\n", optarg); } break; default: From fa074695c8c090972b83521c0e416d09a108c7e7 Mon Sep 17 00:00:00 2001 From: bin Date: Fri, 13 Jun 2025 15:19:50 +0800 Subject: [PATCH 07/10] support rotate audit log by file size --- src/audit.c | 42 ++++++++++++++++++++++++++++++++++++++++-- src/audit.h | 1 + src/protocol.c | 25 +------------------------ 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/audit.c b/src/audit.c index e489d84c..e95d4581 100644 --- a/src/audit.c +++ b/src/audit.c @@ -46,6 +46,7 @@ int audit_init(const char *log_file) { config.log_file = strdup(log_file); config.enabled = true; + config.max_size = 1000; // 默认10MB lwsl_notice("Opening log file: %s\n", log_file); // 创建日志文件 @@ -132,11 +133,50 @@ void audit_clear_custom_fields(void) { lwsl_notice("Cleared all custom fields\n"); } +// 轮转日志文件 +static int rotate_log(void) { + char new_name[256]; + snprintf(new_name, sizeof(new_name), "%s.1", config.log_file); + + // 重命名当前日志文件 + if (rename(config.log_file, new_name) != 0) { + lwsl_err("Failed to rename log file: %s\n", strerror(errno)); + return -1; + } + + // 创建新的日志文件 + FILE *fp = fopen(config.log_file, "a"); + if (fp == NULL) { + lwsl_err("Failed to create new log file: %s\n", strerror(errno)); + // 如果创建新文件失败,尝试恢复原文件 + if (rename(new_name, config.log_file) != 0) { + lwsl_err("Failed to restore original log file: %s\n", strerror(errno)); + } + return -1; + } + fclose(fp); + + lwsl_notice("Log rotated: %s -> %s\n", config.log_file, new_name); + return 0; +} + // 修改 write_log_entry 函数 static void write_log_entry(const audit_entry_t *entry) { lwsl_notice("Writing log entry to file: %s\n", config.log_file); pthread_mutex_lock(&log_mutex); + // 检查文件大小 + struct stat st; + if (stat(config.log_file, &st) == 0 && st.st_size >= config.max_size) { + lwsl_notice("Need to rotate log file, current file size: %d, config.max_size: %d\n",st.st_size,config.max_size); + int ret = rotate_log(); + if (ret != 0) { + lwsl_err("Failed to rotate log file\n"); + pthread_mutex_unlock(&log_mutex); + return; + } + } + FILE *fp = fopen(config.log_file, "a"); if (fp == NULL) { lwsl_err("Failed to open audit log file for writing: %s\n", strerror(errno)); @@ -237,8 +277,6 @@ void audit_log_command(const char *address, const char *command) { free(entry.custom_fields[i].value); } free(entry.custom_fields); - - lwsl_notice("Command logged successfully\n"); } // 验证审计字段格式 diff --git a/src/audit.h b/src/audit.h index 87b125aa..cd84cb4e 100644 --- a/src/audit.h +++ b/src/audit.h @@ -16,6 +16,7 @@ typedef struct { char *log_file; audit_custom_field_t *custom_fields; // 自定义字段数组 int custom_fields_count; // 自定义字段数量 + size_t max_size; // 单个日志文件最大大小(字节) } audit_config_t; // 审计日志条目结构 diff --git a/src/protocol.c b/src/protocol.c index 3e7c8860..d252abd6 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -96,8 +96,6 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { memcpy(output, buf->base, buf->len); output[buf->len] = '\0'; - lwsl_notice("Received output: %s\n", output); - // 检查是否包含 AUDIT_CMD: 字符串 char *audit_cmd = strstr(output, "AUDIT_CMD:"); if (audit_cmd) { @@ -321,28 +319,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, pss->authenticated = false; pss->wsi = wsi; pss->lws_close_status = LWS_CLOSE_STATUS_NOSTATUS; - memset(pss->user, 0, sizeof(pss->user)); // 初始化 user 字段 - - // 设置用户信息 - if (server->username != NULL) { - // 如果设置了 username 参数,使用它作为审计日志的用户名 - strncpy(pss->user, server->username, sizeof(pss->user) - 1); - pss->user[sizeof(pss->user) - 1] = '\0'; // 确保字符串结束 - lwsl_notice("Using username from command line: %s\n", pss->user); - } else if (server->auth_header != NULL) { - // 否则尝试从 HTTP 头部获取 - if (lws_hdr_custom_copy(wsi, pss->user, sizeof(pss->user), server->auth_header, strlen(server->auth_header)) <= 0) { - lwsl_warn("Failed to get user from auth header\n"); - strcpy(pss->user, "unknown"); - } - } else { - strcpy(pss->user, "anonymous"); - } - - // 确保用户名字符串正确结束 - pss->user[sizeof(pss->user) - 1] = '\0'; - lwsl_notice("Final username set to: %s\n", pss->user); - + if (server->url_arg) { while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) { if (strncmp(buf, "arg=", 4) == 0) { From fa6628438f81bc6982f0ee8330c3b46b0d531c00 Mon Sep 17 00:00:00 2001 From: bin Date: Wed, 18 Jun 2025 16:58:11 +0800 Subject: [PATCH 08/10] use history command instead of fc --- src/audit.c | 2 +- src/protocol.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audit.c b/src/audit.c index e95d4581..98568de9 100644 --- a/src/audit.c +++ b/src/audit.c @@ -46,7 +46,7 @@ int audit_init(const char *log_file) { config.log_file = strdup(log_file); config.enabled = true; - config.max_size = 1000; // 默认10MB + config.max_size = 10 * 1024 * 1024; // 默认10MB lwsl_notice("Opening log file: %s\n", log_file); // 创建日志文件 diff --git a/src/protocol.c b/src/protocol.c index d252abd6..f06deac7 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -202,7 +202,7 @@ static char **build_args(struct pss_tty *pss) { // 添加审计命令函数 static const char *get_audit_command(void) { - return "echo \"AUDIT_CMD:$(fc -l -1 | cut -f 1-)\""; + return "echo \"AUDIT_CMD:$(history 1)\""; } static char **build_env(struct pss_tty *pss) { From 0a1c385a5b4ca3ad98d89548721cd67083a0adaa Mon Sep 17 00:00:00 2001 From: bin Date: Wed, 18 Jun 2025 17:21:28 +0800 Subject: [PATCH 09/10] update CMakeLists --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cfb56d3c..812212ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ else() set(CMAKE_C_STANDARD 99) endif() -set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c) +set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c src/audit.c) include(FindPackageHandleStandardArgs) From 75ca581b1a2ab7a5270cc25d6077512c4fadfdc4 Mon Sep 17 00:00:00 2001 From: bin Date: Mon, 30 Jun 2025 11:28:51 +0800 Subject: [PATCH 10/10] remove unused codes --- src/audit.c | 54 ++++++++++++++++++++++++-------------------------- src/audit.h | 28 +++++++++++++------------- src/protocol.c | 42 +++++++++++++++++++-------------------- src/server.c | 28 ++++++++++---------------- src/server.h | 2 +- 5 files changed, 72 insertions(+), 82 deletions(-) diff --git a/src/audit.c b/src/audit.c index 98568de9..5fd6a534 100644 --- a/src/audit.c +++ b/src/audit.c @@ -12,7 +12,6 @@ static audit_config_t config = {0}; static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; -// 确保日志目录存在 static int ensure_log_dir(const char *log_file) { char *log_dir = strdup(log_file); char *last_slash = strrchr(log_dir, '/'); @@ -38,7 +37,7 @@ int audit_init(const char *log_file) { return 0; } - // 确保日志目录存在 + // Ensure log directory exists if (ensure_log_dir(log_file) != 0) { lwsl_err("Failed to ensure log directory exists\n"); return -1; @@ -46,10 +45,10 @@ int audit_init(const char *log_file) { config.log_file = strdup(log_file); config.enabled = true; - config.max_size = 10 * 1024 * 1024; // 默认10MB + config.max_size = 10 * 1024 * 1024; // Default 10MB lwsl_notice("Opening log file: %s\n", log_file); - // 创建日志文件 + // Create log file FILE *fp = fopen(log_file, "a"); if (fp == NULL) { lwsl_err("Failed to open audit log file: %s, error: %s\n", log_file, strerror(errno)); @@ -62,7 +61,7 @@ int audit_init(const char *log_file) { return 0; } -// 添加自定义字段 +// Add custom fields int audit_add_custom_field(const char *key, const char *value) { if (!key || !value) { lwsl_err("Invalid custom field key or value\n"); @@ -71,7 +70,7 @@ int audit_add_custom_field(const char *key, const char *value) { pthread_mutex_lock(&log_mutex); - // 重新分配内存 + // Reallocate memory audit_custom_field_t *new_fields = xrealloc(config.custom_fields, (config.custom_fields_count + 1) * sizeof(audit_custom_field_t)); @@ -83,7 +82,7 @@ int audit_add_custom_field(const char *key, const char *value) { config.custom_fields = new_fields; - // 添加新字段 + // Add new field size_t key_len = strlen(key); size_t value_len = strlen(value); @@ -116,7 +115,7 @@ int audit_add_custom_field(const char *key, const char *value) { return 0; } -// 清除所有自定义字段 +// Clear all custom fields void audit_clear_custom_fields(void) { pthread_mutex_lock(&log_mutex); @@ -133,22 +132,22 @@ void audit_clear_custom_fields(void) { lwsl_notice("Cleared all custom fields\n"); } -// 轮转日志文件 +// Rotate log file static int rotate_log(void) { char new_name[256]; snprintf(new_name, sizeof(new_name), "%s.1", config.log_file); - // 重命名当前日志文件 + // Rename current log file if (rename(config.log_file, new_name) != 0) { lwsl_err("Failed to rename log file: %s\n", strerror(errno)); return -1; } - // 创建新的日志文件 + // Create new log file FILE *fp = fopen(config.log_file, "a"); if (fp == NULL) { lwsl_err("Failed to create new log file: %s\n", strerror(errno)); - // 如果创建新文件失败,尝试恢复原文件 + // If creating new file fails, try to restore original file if (rename(new_name, config.log_file) != 0) { lwsl_err("Failed to restore original log file: %s\n", strerror(errno)); } @@ -160,12 +159,11 @@ static int rotate_log(void) { return 0; } -// 修改 write_log_entry 函数 static void write_log_entry(const audit_entry_t *entry) { lwsl_notice("Writing log entry to file: %s\n", config.log_file); pthread_mutex_lock(&log_mutex); - // 检查文件大小 + // Check file size struct stat st; if (stat(config.log_file, &st) == 0 && st.st_size >= config.max_size) { lwsl_notice("Need to rotate log file, current file size: %d, config.max_size: %d\n",st.st_size,config.max_size); @@ -188,12 +186,12 @@ static void write_log_entry(const audit_entry_t *entry) { struct tm *tm_info = localtime(&entry->timestamp); strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); - // 写入时间戳和地址 + // Write timestamp and address fprintf(fp, "[%s] Address: %s", timestamp, entry->address ? entry->address : "unknown"); - // 写入自定义字段 + // Write custom fields for (int i = 0; i < entry->custom_fields_count; i++) { fprintf(fp, ", %s: %s", entry->custom_fields[i].key, @@ -210,7 +208,7 @@ static void write_log_entry(const audit_entry_t *entry) { lwsl_notice("Log entry written successfully\n"); } -// 清理命令字符串,移除控制字符 +// Clean command string, remove control characters static char *clean_command_string(const char *cmd) { if (!cmd) return NULL; @@ -219,14 +217,14 @@ static char *clean_command_string(const char *cmd) { size_t j = 0; for (size_t i = 0; i < len; i++) { - // 跳过控制字符(ASCII 0-31,除了换行符和回车符) + // Skip control characters (ASCII 0-31, except newline and carriage return) if (cmd[i] >= 32 || cmd[i] == '\n' || cmd[i] == '\r') { clean[j++] = cmd[i]; } } clean[j] = '\0'; - // 移除末尾的空白字符 + // Remove trailing whitespace characters while (j > 0 && (clean[j-1] == ' ' || clean[j-1] == '\t')) { clean[--j] = '\0'; } @@ -234,7 +232,7 @@ static char *clean_command_string(const char *cmd) { return clean; } -// 修改 audit_log_command 函数 +// Modify audit_log_command function void audit_log_command(const char *address, const char *command) { if (!config.enabled) { lwsl_notice("Audit system is not enabled, skipping log entry\n"); @@ -253,7 +251,7 @@ void audit_log_command(const char *address, const char *command) { .custom_fields_count = 0 }; - // 复制自定义字段 + // Copy custom fields if (config.custom_fields_count > 0) { entry.custom_fields = xmalloc((config.custom_fields_count + 1) * sizeof(audit_custom_field_t)); entry.custom_fields_count = config.custom_fields_count; @@ -269,7 +267,7 @@ void audit_log_command(const char *address, const char *command) { write_log_entry(&entry); - // 清理内存 + // Clean memory free(entry.address); free(entry.command); for (int i = 0; i < entry.custom_fields_count; i++) { @@ -279,27 +277,27 @@ void audit_log_command(const char *address, const char *command) { free(entry.custom_fields); } -// 验证审计字段格式 +// Validate audit field format int validate_audit_field(const char *field) { if (!field) { lwsl_err("Invalid audit field: NULL\n"); return -1; } - // 检查是否包含等号 + // Check if it contains an equal sign char *value = strchr(field, '='); if (!value) { lwsl_err("Invalid audit field format: %s (should be key=value)\n", field); return -1; } - // 验证键名不为空 + // Validate key name is not empty if (value == field) { lwsl_err("Empty key in audit field: %s\n", field); return -1; } - // 验证值不为空 + // Validate value is not empty if (*(value + 1) == '\0') { lwsl_err("Empty value in audit field: %s\n", field); return -1; @@ -308,7 +306,7 @@ int validate_audit_field(const char *field) { return 0; } -// 修改 audit_cleanup 函数以清理自定义字段 +// Modify audit_cleanup function to clean up custom fields void audit_cleanup(void) { lwsl_notice("Cleaning up audit system\n"); if (!config.enabled) { @@ -317,7 +315,7 @@ void audit_cleanup(void) { } free(config.log_file); - audit_clear_custom_fields(); // 清理自定义字段 + audit_clear_custom_fields(); // Clean up custom fields config.enabled = false; pthread_mutex_destroy(&log_mutex); lwsl_notice("Audit system cleaned up successfully\n"); diff --git a/src/audit.h b/src/audit.h index cd84cb4e..1d88a903 100644 --- a/src/audit.h +++ b/src/audit.h @@ -1,37 +1,37 @@ -#ifndef TTYD_AUDIT_H -#define TTYD_AUDIT_H +#ifndef AUDIT_H +#define AUDIT_H #include #include -// 自定义字段结构 +// Custom field structure typedef struct { char *key; char *value; } audit_custom_field_t; -// 审计配置结构 +// Audit configuration structure typedef struct { bool enabled; char *log_file; - audit_custom_field_t *custom_fields; // 自定义字段数组 - int custom_fields_count; // 自定义字段数量 - size_t max_size; // 单个日志文件最大大小(字节) + audit_custom_field_t *custom_fields; // Custom fields array + int custom_fields_count; // Custom fields count + size_t max_size; // Maximum size of single log file (bytes) } audit_config_t; -// 审计日志条目结构 +// Audit log entry structure typedef struct { time_t timestamp; char *address; char *command; - audit_custom_field_t *custom_fields; // 自定义字段数组 - int custom_fields_count; // 自定义字段数量 + audit_custom_field_t *custom_fields; // Custom fields array + int custom_fields_count; // Custom fields count } audit_entry_t; -// 初始化审计系统 +// Initialize audit system int audit_init(const char *log_file); -// 记录命令 +// Log command void audit_log_command(const char *address, const char *command); // 关闭审计系统 @@ -41,7 +41,7 @@ void audit_cleanup(void); int audit_add_custom_field(const char *key, const char *value); void audit_clear_custom_fields(void); -// 验证审计字段格式 +// Validate audit field format int validate_audit_field(const char *field); -#endif // TTYD_AUDIT_H \ No newline at end of file +#endif // AUDIT_H \ No newline at end of file diff --git a/src/protocol.c b/src/protocol.c index f06deac7..bea0a6e3 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -76,7 +76,7 @@ static pty_ctx_t *pty_ctx_init(struct pss_tty *pss) { pty_ctx_t *ctx = xmalloc(sizeof(pty_ctx_t)); ctx->pss = pss; ctx->ws_closed = false; - ctx->last_audit_line = 0; // 初始化行号为0 + ctx->last_audit_line = 0; // Initialize line number to 0 return ctx; } @@ -96,25 +96,25 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { memcpy(output, buf->base, buf->len); output[buf->len] = '\0'; - // 检查是否包含 AUDIT_CMD: 字符串 + // Check if it contains AUDIT_CMD: string char *audit_cmd = strstr(output, "AUDIT_CMD:"); if (audit_cmd) { - // 提取行号和命令内容 + // Extract line number and command content char *content = audit_cmd + strlen("AUDIT_CMD:"); - // 去掉换行 + // Remove newline char *newline = strchr(content, '\n'); if (newline) *newline = '\0'; - // 解析行号和命令 + // Parse line number and command int line_num; char cmd[1024] = {0}; if (sscanf(content, "%d %[^\n]", &line_num, cmd) == 2) { - // 打开控制台,会收到一条AUDIT_CMD输出,需要过滤掉 + // When opening console, we receive an AUDIT_CMD output that needs to be filtered out if (ctx->last_audit_line == 0) { ctx->last_audit_line = line_num; lwsl_notice("Skipping init command: %s, at line %d\n", cmd, line_num); } else { - // 检查是否是重复的AUDIT_CMD命令, 如果按回车键也会触发 PROMPT_COMMAND, 这种情况需要过滤 + // Check if it's a duplicate AUDIT_CMD command, pressing Enter will also trigger PROMPT_COMMAND, this case needs to be filtered if (line_num != ctx->last_audit_line) { audit_log_command(ctx->pss->address, cmd); ctx->last_audit_line = line_num; @@ -125,25 +125,25 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { } - // 移除 AUDIT_CMD 这一行 + // Remove the AUDIT_CMD line char *line_start = audit_cmd; - // 找到这一行的开始 + // Find the start of this line while (line_start > output && *(line_start - 1) != '\n') { line_start--; } - // 找到这一行的结束 + // Find the end of this line char *line_end = strchr(audit_cmd, '\n'); if (!line_end) line_end = output + buf->len; - // 计算需要保留的内容长度 + // Calculate the length of content to keep size_t before_len = line_start - output; size_t after_len = buf->len - (line_end - output); - // 创建新的缓冲区,包含 AUDIT_CMD 行之前和之后的内容 + // Create new buffer containing content before and after the AUDIT_CMD line pty_buf_t *new_buf = pty_buf_init(output, before_len); if (after_len > 0) { pty_buf_t *after_buf = pty_buf_init(line_end + 1, after_len); - // 合并两个缓冲区 + // Merge two buffers char *combined = xmalloc(before_len + after_len); memcpy(combined, output, before_len); memcpy(combined + before_len, line_end + 1, after_len); @@ -159,7 +159,7 @@ static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) { free(output); } - // 正常输出 + // Normal output if (eof && !process_running(process)) ctx->pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006; else @@ -200,7 +200,7 @@ static char **build_args(struct pss_tty *pss) { return argv; } -// 添加审计命令函数 +// Add audit command function static const char *get_audit_command(void) { return "echo \"AUDIT_CMD:$(history 1)\""; } @@ -222,7 +222,7 @@ static char **build_env(struct pss_tty *pss) { i++; } - // 添加审计命令 + // Add audit command envp[i] = xmalloc(200); snprintf(envp[i], 200, "PROMPT_COMMAND=%s", get_audit_command()); i++; @@ -281,9 +281,9 @@ static bool check_auth(struct lws *wsi, struct pss_tty *pss) { int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss_tty *pss = (struct pss_tty *)user; char buf[256]; - static char current_cmd[1024] = {0}; // 用于存储当前命令 - static size_t cmd_len = 0; // 当前命令长度 - int n = 0; // 用于存储 lws_hdr_copy 的返回值 + static char current_cmd[1024] = {0}; // Used to store current command + static size_t cmd_len = 0; // Current command length + int n = 0; // Used to store lws_hdr_copy return value switch (reason) { case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: @@ -394,12 +394,12 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, case INPUT: if (!server->writable) break; - // 获取输入内容 + // Get input content char *input = xmalloc(pss->len); memcpy(input, pss->buffer + 1, pss->len - 1); input[pss->len - 1] = '\0'; - // 继续处理命令 + // Continue processing command int err = pty_write(pss->process, pty_buf_init(pss->buffer + 1, pss->len - 1)); if (err) { lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err)); diff --git a/src/server.c b/src/server.c index 591299fd..4525129e 100644 --- a/src/server.c +++ b/src/server.c @@ -304,24 +304,22 @@ static int calc_command_start(int argc, char **argv) { return start; } -// 验证和初始化审计字段 static int init_audit_fields(void) { if (!server->audit_enabled) { return 0; } lwsl_notice("Initializing audit system\n"); - // 设置默认日志文件路径 if (!server->audit_log_file) { server->audit_log_file = strdup("/var/log/ttyd/audit.log"); lwsl_notice("Using default audit log file: %s\n", server->audit_log_file); } - // 验证自定义字段格式 + // verify audit fields. for (int i = 0; i < server->audit_fields_count; i++) { char *field = server->audit_fields[i]; lwsl_notice("Processing audit field[%d]: %s\n", i, field); - // 创建字段的副本以避免修改原始字符串 + // Create a copy of the field to avoid modifying the original string char *field_copy = strdup(field); if (!field_copy) { lwsl_err("Failed to duplicate audit field: %s\n", field); @@ -335,9 +333,9 @@ static int init_audit_fields(void) { return -1; } - // 分割键值对 - *value = '\0'; // 在等号处终止键 - value++; // 移动到值部分 + // split key-values. + *value = '\0'; + value++; lwsl_notice("Adding audit field: key='%s', value='%s'\n", field_copy, value); if (audit_add_custom_field(field_copy, value) != 0) { @@ -350,7 +348,7 @@ static int init_audit_fields(void) { lwsl_notice("Audit field[%d] added successfully\n", i); } - // 初始化审计系统,只记录命令 + // Initialize audit system if (audit_init(server->audit_log_file) != 0) { lwsl_err("Failed to initialize audit system\n"); return -1; @@ -409,7 +407,7 @@ int main(int argc, char **argv) { // parse command line options int c; - int option_index = 0; // 添加 option_index 变量 + int option_index = 0; while ((c = getopt_long(start, argv, opt_string, options, &option_index)) != -1) { lwsl_notice("Processing option: c=%d, option_index=%d\n", c, option_index); switch (c) { @@ -417,7 +415,7 @@ int main(int argc, char **argv) { print_help(); return 0; case 'v': - printf("ttyd version 1004 %s\n", TTYD_VERSION); + printf("ttyd version %s\n", TTYD_VERSION); return 0; case 'd': debug_level = parse_int("debug", optarg); @@ -599,7 +597,7 @@ int main(int argc, char **argv) { } } - // 初始化审计字段 + // Initialize audit fields if (init_audit_fields() != 0) { lwsl_err("Failed to initialize audit fields\n"); cleanup(); @@ -714,20 +712,15 @@ int main(int argc, char **argv) { // cleanup server_free(server); - // 清理审计字段 cleanup(); return 0; } void cleanup(void) { - // ... existing code ... - - // 清理审计系统 if (server->audit_enabled) { audit_cleanup(); } - - // 清理审计字段 + if (server->audit_fields) { for (char **field = server->audit_fields; *field; field++) { free(*field); @@ -735,5 +728,4 @@ void cleanup(void) { free(server->audit_fields); } - // ... existing code ... } diff --git a/src/server.h b/src/server.h index c8d55037..00194b62 100644 --- a/src/server.h +++ b/src/server.h @@ -88,7 +88,7 @@ struct server { uv_loop_t *loop; // the libuv event loop - // 审计日志配置 + // Audit log configuration bool audit_enabled; char *audit_log_file; char **audit_fields; // NULL-terminated array of custom fields