From 1b83097f0077e7f2ec790c11448408aca6e8d2e8 Mon Sep 17 00:00:00 2001 From: Azreyo <58790873+Azreyo@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:04:54 +0100 Subject: [PATCH] Refactor logging system and enhance Dockerfile configuration - Introduced a new logging system with configurable log levels and categories. - Added support for different log formats: plain text, JSON, and syslog. - Updated Dockerfile to use Alpine 3.19 and improved build process. - Enhanced server configuration to replace verbose logging with log modes (off, classic, debug, advanced). - Improved security measures in SSL context configuration. - Added health checks and resource limits in docker-compose.yml. - Refactored Makefile to include new logging source files. - Updated server configuration to set default log file path and SSL certificate paths. - Enhanced performance tracking and logging capabilities. - Added hex dump utility for debugging binary data. --- .gitignore | 3 +- Dockerfile | 38 ++-- Makefile | 4 +- docker-compose.yml | 32 ++- server.conf | 8 +- src/config_parser.c | 39 +++- src/http2.c | 1 + src/logging.c | 476 ++++++++++++++++++++++++++++++++++++++++++++ src/logging.h | 109 ++++++++++ src/performance.c | 7 +- src/server.c | 177 ++++++++-------- src/server_config.c | 8 +- src/server_config.h | 10 +- src/websocket.c | 31 ++- 14 files changed, 812 insertions(+), 131 deletions(-) create mode 100644 src/logging.c create mode 100644 src/logging.h diff --git a/.gitignore b/.gitignore index f0e2fe6..8da261c 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,5 @@ ssl/* !.github/workflows/ src/bin docker-push.sh -entrypoint.sh \ No newline at end of file +entrypoint.sh +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 961a87f..6a08b73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ -FROM alpine:edge AS builder +FROM alpine:3.19 AS builder +# Install build dependencies RUN apk add --no-cache \ gcc \ g++ \ @@ -12,15 +13,20 @@ RUN apk add --no-cache \ zlib-dev \ git \ ca-certificates \ - && apk update \ - && apk upgrade --available + && rm -rf /var/cache/apk/* WORKDIR /build -RUN git clone --depth 1 --branch main https://github.com/Azreyo/Carbon.git . && \ - make clean && make release +COPY . . -FROM alpine:edge +RUN make clean && make release + + +FROM alpine:3.19 + +LABEL maintainer="Carbon Team" \ + version="1.0" \ + description="Carbon Web Server - High Performance HTTP Server" RUN apk add --no-cache \ libssl3 \ @@ -29,17 +35,17 @@ RUN apk add --no-cache \ zlib \ ca-certificates \ curl \ - && apk update \ - && apk upgrade --available \ - && rm -rf /tmp/* /var/cache/apk/* + && rm -rf /var/cache/apk/* /tmp/* -RUN adduser -D -u 1000 -s /bin/sh carbon +RUN addgroup -g 1000 carbon && \ + adduser -D -u 1000 -G carbon -s /sbin/nologin carbon WORKDIR /app + RUN mkdir -p /app/www /app/log /app/ssl/cert /app/ssl/key && \ chown -R carbon:carbon /app && \ - chmod 755 /app && \ - chmod 750 /app/ssl + chmod 755 /app /app/www /app/log && \ + chmod 700 /app/ssl /app/ssl/cert /app/ssl/key COPY --from=builder --chown=carbon:carbon /build/server /app/ COPY --from=builder --chown=carbon:carbon /build/www/ /app/www/ @@ -48,7 +54,8 @@ COPY --from=builder --chown=carbon:carbon /build/DOCUMENTATION.md /app/ COPY --from=builder --chown=carbon:carbon /build/LICENSE /app/ COPY --chown=carbon:carbon entrypoint.sh /app/entrypoint.sh -RUN chmod 500 /app/server /app/entrypoint.sh +RUN chmod 500 /app/server /app/entrypoint.sh && \ + chmod 644 /app/README.md /app/DOCUMENTATION.md /app/LICENSE 2>/dev/null || true USER carbon @@ -58,10 +65,13 @@ ENV SERVER_NAME=0.0.0.0 \ ENABLE_HTTP2=false \ ENABLE_WEBSOCKET=false \ MAX_THREADS=4 \ - VERBOSE=true + MAX_CONNECTIONS=1024 \ + LOG_MODE=classic EXPOSE 8080 8443 + + ENTRYPOINT ["/app/entrypoint.sh"] HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ diff --git a/Makefile b/Makefile index eab76a7..c3b0311 100644 --- a/Makefile +++ b/Makefile @@ -16,13 +16,13 @@ LDFLAGS = -pthread -Wl,-z,relro,-z,now -pie LIBS = -lssl -lcrypto -lmagic -lnghttp2 -lz # Source files and object files -SRCS = src/server.c src/config_parser.c src/server_config.c src/websocket.c src/http2.c src/performance.c +SRCS = src/server.c src/config_parser.c src/server_config.c src/websocket.c src/http2.c src/performance.c src/logging.c DEST = src/bin/ OBJS = $(patsubst src/%.c,$(DEST)%.o,$(SRCS)) TARGET = server # Header files -HEADERS = src/server_config.h src/websocket.h src/http2.h src/performance.h +HEADERS = src/server_config.h src/websocket.h src/http2.h src/performance.h src/logging.h # Include directories INCLUDES = diff --git a/docker-compose.yml b/docker-compose.yml index 4d753d9..e44a9ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,10 @@ version: '3.8' services: carbon-server: - image: azreyo/carbon:latest + build: + context: . + dockerfile: Dockerfile + image: carbon:latest container_name: carbon-http-server ports: - "8080:8080" @@ -14,9 +17,34 @@ services: - ENABLE_HTTP2=false - ENABLE_WEBSOCKET=false - MAX_THREADS=4 - - VERBOSE=true + - MAX_CONNECTIONS=1024 + - LOG_MODE=classic + volumes: + - ./www:/app/www:ro + - carbon-logs:/app/log + # For HTTPS, mount your certificates: + # - ./ssl/cert:/app/ssl/cert:ro + # - ./ssl/key:/app/ssl/key:ro restart: unless-stopped + read_only: true + tmpfs: + - /tmp:size=64M + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + networks: + - carbon-net + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s networks: carbon-net: driver: bridge + +volumes: + carbon-logs: diff --git a/server.conf b/server.conf index 8e38401..45e2642 100644 --- a/server.conf +++ b/server.conf @@ -24,8 +24,12 @@ max_connections = 1024 # ---Path configuration--- # Log file location log_file = log/server.log -# Enable verbose logging -verbose = true +# Log mode: off, classic, debug, advanced +# - off: No logging +# - classic: Standard logging (info, warnings, errors) +# - debug: Detailed logging including debug messages +# - advanced: Full trace logging with performance metrics +log_mode = classic # Path to www www_path = www # path to public ssl certification diff --git a/src/config_parser.c b/src/config_parser.c index e7f5449..009d6ee 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include "server_config.h" @@ -13,7 +14,8 @@ typedef enum CONFIG_MAX_THREADS, CONFIG_RUNNING, CONFIG_SERVER_NAME, - CONFIG_VERBOSE, + CONFIG_LOG_MODE, + CONFIG_VERBOSE, // Keep for backwards compatibility CONFIG_ENABLE_HTTP2, CONFIG_ENABLE_WEBSOCKET, CONFIG_WWW_PATH, @@ -57,6 +59,21 @@ static bool parse_bool(const char *value) } return false; } +// Parse log mode value (off/classic/debug/advanced) +static LogMode parse_log_mode(const char *value) +{ + if (strcasecmp(value, "off") == 0 || strcmp(value, "0") == 0) + return LOG_MODE_OFF; + if (strcasecmp(value, "classic") == 0 || strcasecmp(value, "true") == 0 || + strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0) + return LOG_MODE_CLASSIC; + if (strcasecmp(value, "debug") == 0) + return LOG_MODE_DEBUG; + if (strcasecmp(value, "advanced") == 0) + return LOG_MODE_ADVANCED; + return LOG_MODE_CLASSIC; // Default +} + // Map string to enum static ConfigKey get_config_key(const char *key) { @@ -71,7 +88,8 @@ static ConfigKey get_config_key(const char *key) {"max_threads", CONFIG_MAX_THREADS}, {"running", CONFIG_RUNNING}, {"server_name", CONFIG_SERVER_NAME}, - {"verbose", CONFIG_VERBOSE}, + {"log_mode", CONFIG_LOG_MODE}, + {"verbose", CONFIG_VERBOSE}, // Keep for backwards compatibility {"enable_http2", CONFIG_ENABLE_HTTP2}, {"enable_websocket", CONFIG_ENABLE_WEBSOCKET}, {"www_path", CONFIG_WWW_PATH}, @@ -195,9 +213,22 @@ int load_config(const char *filename, ServerConfig *config) "Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n"); } break; + case CONFIG_LOG_MODE: + config->log_mode = parse_log_mode(value); + printf("load_config: log_mode = %s\n", + config->log_mode == LOG_MODE_OFF ? "off" : + config->log_mode == LOG_MODE_DEBUG ? "debug" : + config->log_mode == LOG_MODE_ADVANCED ? "advanced" : "classic"); + break; case CONFIG_VERBOSE: - config->verbose = parse_bool(value); - printf("load_config: verbose = %d\n", config->verbose); + // Backwards compatibility: map verbose boolean to log_mode + if (parse_bool(value)) { + config->log_mode = LOG_MODE_CLASSIC; + } else { + config->log_mode = LOG_MODE_OFF; + } + printf("load_config: verbose (legacy) -> log_mode = %s\n", + config->log_mode == LOG_MODE_OFF ? "off" : "classic"); break; case CONFIG_ENABLE_HTTP2: diff --git a/src/http2.c b/src/http2.c index c6226a4..37a8526 100644 --- a/src/http2.c +++ b/src/http2.c @@ -1,5 +1,6 @@ #include "http2.h" #include "server_config.h" +#include "logging.h" #include #include #include diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..4bd37f0 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,476 @@ +#include "logging.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes +#define COLOR_RESET "\x1b[0m" +#define COLOR_RED "\x1b[31m" +#define COLOR_GREEN "\x1b[32m" +#define COLOR_YELLOW "\x1b[33m" +#define COLOR_BLUE "\x1b[34m" +#define COLOR_MAGENTA "\x1b[35m" +#define COLOR_CYAN "\x1b[36m" +#define COLOR_WHITE "\x1b[37m" +#define COLOR_BOLD "\x1b[1m" + +// Default configuration +static LogConfig g_log_config = { + .level = LOG_LEVEL_INFO, + .categories = LOG_CAT_ALL, + .format = LOG_FORMAT_PLAIN, + .console_output = true, + .file_output = true, + .include_timestamp = true, + .include_thread_id = true, + .include_source_location = false, + .colorize_console = true, + .log_file = "log/server.log", + .max_file_size = 100 * 1024 * 1024, // 100MB + .max_backup_files = 5 +}; + +static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER; +static bool g_log_initialized = false; + +// Performance tracking +typedef struct { + char operation[64]; + struct timeval start_time; + bool active; +} PerfTracker; + +#define MAX_PERF_TRACKERS 32 +static PerfTracker g_perf_trackers[MAX_PERF_TRACKERS]; +static pthread_mutex_t g_perf_mutex = PTHREAD_MUTEX_INITIALIZER; + +// Get color for log level +static const char *get_level_color(LogLevel level) +{ + switch (level) { + case LOG_LEVEL_ERROR: return COLOR_RED; + case LOG_LEVEL_WARN: return COLOR_YELLOW; + case LOG_LEVEL_INFO: return COLOR_GREEN; + case LOG_LEVEL_DEBUG: return COLOR_CYAN; + case LOG_LEVEL_TRACE: return COLOR_MAGENTA; + default: return COLOR_WHITE; + } +} + +// Get level prefix +static const char *get_level_prefix(LogLevel level) +{ + switch (level) { + case LOG_LEVEL_ERROR: return "ERROR"; + case LOG_LEVEL_WARN: return "WARN "; + case LOG_LEVEL_INFO: return "INFO "; + case LOG_LEVEL_DEBUG: return "DEBUG"; + case LOG_LEVEL_TRACE: return "TRACE"; + default: return "?????"; + } +} + +// Get category name +static const char *get_category_name(LogCategory cat) +{ + switch (cat) { + case LOG_CAT_GENERAL: return "GENERAL"; + case LOG_CAT_SECURITY: return "SECURITY"; + case LOG_CAT_NETWORK: return "NETWORK"; + case LOG_CAT_HTTP: return "HTTP"; + case LOG_CAT_SSL: return "SSL"; + case LOG_CAT_WEBSOCKET: return "WEBSOCKET"; + case LOG_CAT_CACHE: return "CACHE"; + case LOG_CAT_PERFORMANCE: return "PERF"; + default: return "UNKNOWN"; + } +} + +const char *log_level_to_string(LogLevel level) +{ + switch (level) { + case LOG_LEVEL_OFF: return "off"; + case LOG_LEVEL_ERROR: return "error"; + case LOG_LEVEL_WARN: return "warn"; + case LOG_LEVEL_INFO: return "info"; + case LOG_LEVEL_DEBUG: return "debug"; + case LOG_LEVEL_TRACE: return "trace"; + default: return "unknown"; + } +} + +const char *log_mode_to_string(LogLevel level) +{ + switch (level) { + case LOG_LEVEL_OFF: return "off"; + case LOG_LEVEL_ERROR: + case LOG_LEVEL_WARN: + case LOG_LEVEL_INFO: return "classic"; + case LOG_LEVEL_DEBUG: return "debug"; + case LOG_LEVEL_TRACE: return "advanced"; + default: return "classic"; + } +} + +LogLevel log_level_from_string(const char *str) +{ + if (!str) return LOG_LEVEL_INFO; + + // Handle mode names + if (strcasecmp(str, "off") == 0) return LOG_LEVEL_OFF; + if (strcasecmp(str, "classic") == 0) return LOG_LEVEL_INFO; + if (strcasecmp(str, "debug") == 0) return LOG_LEVEL_DEBUG; + if (strcasecmp(str, "advanced") == 0) return LOG_LEVEL_TRACE; + + // Handle level names + if (strcasecmp(str, "error") == 0) return LOG_LEVEL_ERROR; + if (strcasecmp(str, "warn") == 0) return LOG_LEVEL_WARN; + if (strcasecmp(str, "warning") == 0) return LOG_LEVEL_WARN; + if (strcasecmp(str, "info") == 0) return LOG_LEVEL_INFO; + if (strcasecmp(str, "trace") == 0) return LOG_LEVEL_TRACE; + + // Handle boolean-like values for backwards compatibility + if (strcasecmp(str, "true") == 0 || strcmp(str, "1") == 0) + return LOG_LEVEL_INFO; + if (strcasecmp(str, "false") == 0 || strcmp(str, "0") == 0) + return LOG_LEVEL_OFF; + + return LOG_LEVEL_INFO; +} + +// Rotate log files +static void rotate_logs(void) +{ + struct stat st; + if (stat(g_log_config.log_file, &st) != 0) + return; + + if (st.st_size < (off_t)g_log_config.max_file_size) + return; + + // Rotate existing backup files + char old_path[512], new_path[512]; + for (int i = g_log_config.max_backup_files - 1; i >= 0; i--) { + if (i == 0) { + snprintf(old_path, sizeof(old_path), "%s", g_log_config.log_file); + } else { + snprintf(old_path, sizeof(old_path), "%s.%d", g_log_config.log_file, i); + } + snprintf(new_path, sizeof(new_path), "%s.%d", g_log_config.log_file, i + 1); + + if (i + 1 >= g_log_config.max_backup_files) { + unlink(old_path); + } else { + rename(old_path, new_path); + } + } +} + +// Create log directory if needed +static void ensure_log_directory(void) +{ + char log_dir[512]; + strncpy(log_dir, g_log_config.log_file, sizeof(log_dir) - 1); + log_dir[sizeof(log_dir) - 1] = '\0'; + + char *dir_path = dirname(log_dir); + if (!dir_path || strcmp(dir_path, ".") == 0) + return; + + struct stat st; + if (stat(dir_path, &st) != 0) { + if (mkdir(dir_path, 0755) != 0 && errno != EEXIST) { + fprintf(stderr, "Failed to create log directory: %s\n", strerror(errno)); + } + } +} + +void log_init(LogConfig *config) +{ + pthread_mutex_lock(&g_log_mutex); + + if (config) { + memcpy(&g_log_config, config, sizeof(LogConfig)); + } + + ensure_log_directory(); + g_log_initialized = true; + + pthread_mutex_unlock(&g_log_mutex); + + LOG_INFO(LOG_CAT_GENERAL, "Logging system initialized [mode=%s, level=%s]", + log_mode_to_string(g_log_config.level), + log_level_to_string(g_log_config.level)); +} + +void log_cleanup(void) +{ + pthread_mutex_lock(&g_log_mutex); + g_log_initialized = false; + pthread_mutex_unlock(&g_log_mutex); +} + +void log_set_level(LogLevel level) +{ + pthread_mutex_lock(&g_log_mutex); + g_log_config.level = level; + pthread_mutex_unlock(&g_log_mutex); +} + +void log_set_categories(LogCategory categories) +{ + pthread_mutex_lock(&g_log_mutex); + g_log_config.categories = categories; + pthread_mutex_unlock(&g_log_mutex); +} + +void log_write(LogLevel level, LogCategory category, const char *file, + int line, const char *func, const char *fmt, ...) +{ + // Quick check without lock + if (level == LOG_LEVEL_OFF || g_log_config.level == LOG_LEVEL_OFF) + return; + + if (level > g_log_config.level) + return; + + if (!(category & g_log_config.categories)) + return; + + pthread_mutex_lock(&g_log_mutex); + + // Get timestamp + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm tm; + localtime_r(&tv.tv_sec, &tm); + + char timestamp[64] = ""; + if (g_log_config.include_timestamp) { + snprintf(timestamp, sizeof(timestamp), "%04d-%02d-%02d %02d:%02d:%02d.%03ld", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec / 1000); + } + + // Get thread ID + char thread_id[32] = ""; + if (g_log_config.include_thread_id) { + snprintf(thread_id, sizeof(thread_id), "%lu", (unsigned long)pthread_self()); + } + + // Get source location + char source_loc[256] = ""; + if (g_log_config.include_source_location && file && func) { + const char *filename = strrchr(file, '/'); + filename = filename ? filename + 1 : file; + snprintf(source_loc, sizeof(source_loc), "%s:%d:%s", filename, line, func); + } + + // Format the message + char message[4096]; + va_list args; + va_start(args, fmt); + vsnprintf(message, sizeof(message), fmt, args); + va_end(args); + + // Build log entry based on format + char log_entry[8192]; + + if (g_log_config.format == LOG_FORMAT_JSON) { + snprintf(log_entry, sizeof(log_entry), + "{\"timestamp\":\"%s\",\"level\":\"%s\",\"category\":\"%s\"," + "\"pid\":%d,\"tid\":\"%s\",\"source\":\"%s\",\"message\":\"%s\"}\n", + timestamp, get_level_prefix(level), get_category_name(category), + getpid(), thread_id, source_loc, message); + } else if (g_log_config.format == LOG_FORMAT_SYSLOG) { + // Syslog-compatible format + snprintf(log_entry, sizeof(log_entry), + "<%d>%s %s[%d]: [%s] %s\n", + level, timestamp, "carbon", getpid(), + get_category_name(category), message); + } else { + // Plain text format + if (g_log_config.include_source_location && source_loc[0]) { + snprintf(log_entry, sizeof(log_entry), + "[%s] [%s] [PID:%d] [TID:%s] [%s] [%s] %s\n", + timestamp, get_level_prefix(level), getpid(), thread_id, + get_category_name(category), source_loc, message); + } else { + snprintf(log_entry, sizeof(log_entry), + "[%s] [%s] [PID:%d] [TID:%s] [%s] %s\n", + timestamp, get_level_prefix(level), getpid(), thread_id, + get_category_name(category), message); + } + } + + // Write to console + if (g_log_config.console_output) { + if (g_log_config.colorize_console && isatty(STDOUT_FILENO)) { + fprintf(stdout, "%s%s%s", get_level_color(level), log_entry, COLOR_RESET); + } else { + fputs(log_entry, stdout); + } + fflush(stdout); + } + + // Write to file + if (g_log_config.file_output && g_log_config.log_file[0]) { + rotate_logs(); + + FILE *fp = fopen(g_log_config.log_file, "a"); + if (fp) { + fputs(log_entry, fp); + fflush(fp); + fclose(fp); + } + } + + pthread_mutex_unlock(&g_log_mutex); +} + +// Backwards compatible log_event function +void log_event(const char *message) +{ + if (!message) return; + log_write(LOG_LEVEL_INFO, LOG_CAT_GENERAL, NULL, 0, NULL, "%s", message); +} + +// Secure logging - sanitizes potentially sensitive data +void log_secure(LogLevel level, LogCategory category, const char *fmt, ...) +{ + char message[4096]; + va_list args; + va_start(args, fmt); + vsnprintf(message, sizeof(message), fmt, args); + va_end(args); + + // Sanitize common sensitive patterns + char *patterns[] = { + "password", "passwd", "pwd", "secret", "token", "key", "auth", + "credential", "credit", "ssn", "api_key", "apikey", NULL + }; + + char sanitized[4096]; + strncpy(sanitized, message, sizeof(sanitized) - 1); + sanitized[sizeof(sanitized) - 1] = '\0'; + + // Convert to lowercase for pattern matching + char lower[4096]; + for (size_t i = 0; i < strlen(sanitized) && i < sizeof(lower) - 1; i++) { + lower[i] = tolower((unsigned char)sanitized[i]); + } + lower[strlen(sanitized)] = '\0'; + + // Check for sensitive patterns + bool has_sensitive = false; + for (int i = 0; patterns[i]; i++) { + if (strstr(lower, patterns[i])) { + has_sensitive = true; + break; + } + } + + if (has_sensitive) { + log_write(level, category, NULL, 0, NULL, "[REDACTED] Message contained sensitive data"); + } else { + log_write(level, category, NULL, 0, NULL, "%s", sanitized); + } +} + +void log_perf_start(const char *operation) +{ + if (g_log_config.level < LOG_LEVEL_DEBUG) + return; + + pthread_mutex_lock(&g_perf_mutex); + + for (int i = 0; i < MAX_PERF_TRACKERS; i++) { + if (!g_perf_trackers[i].active) { + strncpy(g_perf_trackers[i].operation, operation, sizeof(g_perf_trackers[i].operation) - 1); + g_perf_trackers[i].operation[sizeof(g_perf_trackers[i].operation) - 1] = '\0'; + gettimeofday(&g_perf_trackers[i].start_time, NULL); + g_perf_trackers[i].active = true; + break; + } + } + + pthread_mutex_unlock(&g_perf_mutex); +} + +void log_perf_end(const char *operation) +{ + if (g_log_config.level < LOG_LEVEL_DEBUG) + return; + + struct timeval end_time; + gettimeofday(&end_time, NULL); + + pthread_mutex_lock(&g_perf_mutex); + + for (int i = 0; i < MAX_PERF_TRACKERS; i++) { + if (g_perf_trackers[i].active && + strcmp(g_perf_trackers[i].operation, operation) == 0) { + + long elapsed_us = (end_time.tv_sec - g_perf_trackers[i].start_time.tv_sec) * 1000000 + + (end_time.tv_usec - g_perf_trackers[i].start_time.tv_usec); + + g_perf_trackers[i].active = false; + + pthread_mutex_unlock(&g_perf_mutex); + + if (elapsed_us > 1000000) { + LOG_DEBUG(LOG_CAT_PERFORMANCE, "%s completed in %.2f s", operation, elapsed_us / 1000000.0); + } else if (elapsed_us > 1000) { + LOG_DEBUG(LOG_CAT_PERFORMANCE, "%s completed in %.2f ms", operation, elapsed_us / 1000.0); + } else { + LOG_DEBUG(LOG_CAT_PERFORMANCE, "%s completed in %ld µs", operation, elapsed_us); + } + return; + } + } + + pthread_mutex_unlock(&g_perf_mutex); +} + +void log_hexdump(const char *label, const void *data, size_t len) +{ + if (g_log_config.level < LOG_LEVEL_TRACE) + return; + + if (!data || len == 0) + return; + + // Limit output size + if (len > 256) { + LOG_TRACE(LOG_CAT_GENERAL, "%s: [%zu bytes, showing first 256]", label, len); + len = 256; + } + + const unsigned char *bytes = (const unsigned char *)data; + char line[80]; + char ascii[17]; + + for (size_t i = 0; i < len; i += 16) { + int pos = snprintf(line, sizeof(line), "%04zx: ", i); + + for (size_t j = 0; j < 16; j++) { + if (i + j < len) { + pos += snprintf(line + pos, sizeof(line) - pos, "%02x ", bytes[i + j]); + ascii[j] = isprint(bytes[i + j]) ? bytes[i + j] : '.'; + } else { + pos += snprintf(line + pos, sizeof(line) - pos, " "); + ascii[j] = ' '; + } + } + ascii[16] = '\0'; + + LOG_TRACE(LOG_CAT_GENERAL, "%s: %s |%s|", label, line, ascii); + } +} diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 0000000..52a8016 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,109 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include +#include +#include +#include + +// Log levels +typedef enum { + LOG_LEVEL_OFF = 0, // No logging + LOG_LEVEL_ERROR = 1, // Only errors + LOG_LEVEL_WARN = 2, // Errors + warnings + LOG_LEVEL_INFO = 3, // Classic mode: errors + warnings + info + LOG_LEVEL_DEBUG = 4, // Debug mode: all above + debug messages + LOG_LEVEL_TRACE = 5 // Advanced mode: everything including traces +} LogLevel; + +// Log categories for filtering +typedef enum { + LOG_CAT_GENERAL = 0x01, + LOG_CAT_SECURITY = 0x02, + LOG_CAT_NETWORK = 0x04, + LOG_CAT_HTTP = 0x08, + LOG_CAT_SSL = 0x10, + LOG_CAT_WEBSOCKET = 0x20, + LOG_CAT_CACHE = 0x40, + LOG_CAT_PERFORMANCE = 0x80, + LOG_CAT_ALL = 0xFF +} LogCategory; + +// Log output formats +typedef enum { + LOG_FORMAT_PLAIN = 0, // Simple text format + LOG_FORMAT_JSON = 1, // JSON structured format + LOG_FORMAT_SYSLOG = 2 // Syslog compatible format +} LogFormat; + +// Logger configuration +typedef struct { + LogLevel level; + LogCategory categories; + LogFormat format; + bool console_output; + bool file_output; + bool include_timestamp; + bool include_thread_id; + bool include_source_location; + bool colorize_console; + char log_file[256]; + size_t max_file_size; + int max_backup_files; +} LogConfig; + +// Initialize the logging system +void log_init(LogConfig *config); + +// Cleanup logging system +void log_cleanup(void); + +// Set log level at runtime +void log_set_level(LogLevel level); + +// Set log categories at runtime +void log_set_categories(LogCategory categories); + +// Core logging functions +void log_write(LogLevel level, LogCategory category, const char *file, + int line, const char *func, const char *fmt, ...); + +// Convenience macros with source location +#define LOG_ERROR(cat, ...) \ + log_write(LOG_LEVEL_ERROR, cat, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define LOG_WARN(cat, ...) \ + log_write(LOG_LEVEL_WARN, cat, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define LOG_INFO(cat, ...) \ + log_write(LOG_LEVEL_INFO, cat, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define LOG_DEBUG(cat, ...) \ + log_write(LOG_LEVEL_DEBUG, cat, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#define LOG_TRACE(cat, ...) \ + log_write(LOG_LEVEL_TRACE, cat, __FILE__, __LINE__, __func__, __VA_ARGS__) + +// Security-specific logging (always logs regardless of level if security category enabled) +#define LOG_SECURITY(level, ...) \ + log_write(level, LOG_CAT_SECURITY, __FILE__, __LINE__, __func__, __VA_ARGS__) + +// Simple backwards-compatible log function +void log_event(const char *message); + +// Log mode string conversion +const char *log_level_to_string(LogLevel level); +LogLevel log_level_from_string(const char *str); +const char *log_mode_to_string(LogLevel level); + +// Secure logging (sanitizes sensitive data) +void log_secure(LogLevel level, LogCategory category, const char *fmt, ...); + +// Performance logging with timing +void log_perf_start(const char *operation); +void log_perf_end(const char *operation); + +// Hex dump for debugging binary data (only in TRACE level) +void log_hexdump(const char *label, const void *data, size_t len); + +#endif \ No newline at end of file diff --git a/src/performance.c b/src/performance.c index 46bf3e6..f7c082c 100644 --- a/src/performance.c +++ b/src/performance.c @@ -1,15 +1,16 @@ #include "performance.h" #include #include +#include #include #include #include #include -#define MAX_MMAP_CACHE_SIZE 50 +#define MAX_MMAP_CACHE_SIZE 100 #define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB -#define BUFFER_POOL_SIZE 32 -#define DEFAULT_BUFFER_SIZE 16384 +#define BUFFER_POOL_SIZE 64 +#define DEFAULT_BUFFER_SIZE 32768 // Global cache structures static mmap_cache_entry_t *mmap_cache = NULL; diff --git a/src/server.c b/src/server.c index 3b9641b..fd7316b 100644 --- a/src/server.c +++ b/src/server.c @@ -29,6 +29,7 @@ #include "websocket.h" #include "http2.h" #include "performance.h" +#include "logging.h" #define MAX_REQUEST_SIZE 16384 #define MAX_LOG_SIZE 2048 @@ -54,10 +55,14 @@ #define SECURITY_HEADERS \ "X-Content-Type-Options: nosniff\r\n" \ - "X-Frame-Options: SAMEORIGIN\r\n" \ + "X-Frame-Options: DENY\r\n" \ "X-XSS-Protection: 1; mode=block\r\n" \ + "Referrer-Policy: strict-origin-when-cross-origin\r\n" \ + "Permissions-Policy: geolocation=(), microphone=(), camera=()\r\n" \ "Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " \ - "font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline';\r\n" + "font-src 'self' https://fonts.gstatic.com; script-src 'self'; img-src 'self' data:; " \ + "frame-ancestors 'none'; base-uri 'self'; form-action 'self';\r\n" \ + "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" #define RATE_LIMIT_WINDOW 60 // 60 seconds static int MAX_REQUESTS_DYNAMIC = 500; // Will be calculated dynamically @@ -203,31 +208,72 @@ void configure_ssl_context(SSL_CTX *ctx) exit(EXIT_FAILURE); } - // Security hardening - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); - SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); // Disable compression (CRIME attack) - SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + // Verify private key matches certificate + if (SSL_CTX_check_private_key(ctx) != 1) + { + LOG_ERROR(LOG_CAT_SSL, "Private key does not match certificate"); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } - // Use secure ciphers only - TLS 1.3 and strong TLS 1.2 ciphers - const char *cipher_list = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:" - "TLS_AES_128_GCM_SHA256:" // TLS 1.3 - "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:" - "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:" - "!aNULL:!eNULL:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK:!CBC"; + // Security hardening - enforce TLS 1.2 minimum (TLS 1.3 preferred) + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + + // Disable all insecure protocols and options + SSL_CTX_set_options(ctx, + SSL_OP_NO_SSLv2 | // Disable SSLv2 (CVE-2016-0800) + SSL_OP_NO_SSLv3 | // Disable SSLv3 (POODLE - CVE-2014-3566) + SSL_OP_NO_TLSv1 | // Disable TLS 1.0 (BEAST - CVE-2011-3389) + SSL_OP_NO_TLSv1_1 | // Disable TLS 1.1 (deprecated) + SSL_OP_NO_COMPRESSION | // Disable compression (CRIME - CVE-2012-4929) + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_NO_TICKET | // Disable session tickets for forward secrecy + SSL_OP_CIPHER_SERVER_PREFERENCE | // Server chooses cipher order + SSL_OP_SINGLE_DH_USE | // Generate new DH key for each handshake + SSL_OP_SINGLE_ECDH_USE // Generate new ECDH key for each handshake + ); + + // Use secure ciphers only - TLS 1.3 and strong TLS 1.2 AEAD ciphers + // Prioritize ChaCha20 for mobile devices, AES-GCM for servers with AES-NI + const char *cipher_list = + "TLS_AES_256_GCM_SHA384:" + "TLS_CHACHA20_POLY1305_SHA256:" + "TLS_AES_128_GCM_SHA256:" // TLS 1.3 ciphers + "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-GCM-SHA384:" + "ECDHE-ECDSA-CHACHA20-POLY1305:" + "ECDHE-RSA-CHACHA20-POLY1305:" + "ECDHE-ECDSA-AES128-GCM-SHA256:" + "ECDHE-RSA-AES128-GCM-SHA256:" // TLS 1.2 AEAD ciphers + "!aNULL:!eNULL:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK:!SRP:!DSS:!CBC"; if (SSL_CTX_set_cipher_list(ctx, cipher_list) != 1) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } + + // Set ECDH curve for key exchange (prefer X25519, fall back to P-256) +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + if (SSL_CTX_set1_groups_list(ctx, "X25519:P-256:P-384") != 1) + { + LOG_WARN(LOG_CAT_SSL, "Failed to set ECDH groups, using defaults"); + } +#endif + + // Enable OCSP stapling if available +#ifdef SSL_CTX_set_tlsext_status_type + SSL_CTX_set_tlsext_status_type(ctx, TLSEXT_STATUSTYPE_ocsp); +#endif // Enable HTTP/2 ALPN if configured if (config.enable_http2) { SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); - log_event("HTTP/2 ALPN enabled"); + LOG_INFO(LOG_CAT_SSL, "HTTP/2 ALPN enabled"); } + + LOG_INFO(LOG_CAT_SSL, "SSL/TLS context configured with secure settings"); } void optimize_socket_for_send(int socket_fd) @@ -1847,6 +1893,9 @@ unsigned char *gzip_compress(const unsigned char *data, size_t size, size_t *com int main() { + // Initialize default config first + init_config(&config); + if (load_config("server.conf", &config) != 0) { printf("Using default configuration.\n"); @@ -1854,13 +1903,33 @@ int main() config.running = 1; + // Initialize logging system based on config + LogConfig log_cfg = { + .level = (config.log_mode == LOG_MODE_OFF) ? LOG_LEVEL_OFF : + (config.log_mode == LOG_MODE_DEBUG) ? LOG_LEVEL_DEBUG : + (config.log_mode == LOG_MODE_ADVANCED) ? LOG_LEVEL_TRACE : + LOG_LEVEL_INFO, + .categories = LOG_CAT_ALL, + .format = LOG_FORMAT_PLAIN, + .console_output = true, + .file_output = true, + .include_timestamp = true, + .include_thread_id = true, + .include_source_location = (config.log_mode == LOG_MODE_ADVANCED), + .colorize_console = true, + .max_file_size = 100 * 1024 * 1024, + .max_backup_files = 5 + }; + strncpy(log_cfg.log_file, config.log_file, sizeof(log_cfg.log_file) - 1); + log_init(&log_cfg); + // Calculate dynamic rate limit based on system resources MAX_REQUESTS_DYNAMIC = calculate_dynamic_rate_limit(); char rate_limit_msg[256]; snprintf(rate_limit_msg, sizeof(rate_limit_msg), "Dynamic rate limit set to %d requests per IP per minute", MAX_REQUESTS_DYNAMIC); - log_event(rate_limit_msg); + LOG_INFO(LOG_CAT_GENERAL, "%s", rate_limit_msg); // Allocate client threads array client_threads = calloc(config.max_connections, sizeof(pthread_t)); @@ -1929,84 +1998,6 @@ int main() return 0; } -void log_event(const char *message) -{ - pthread_mutex_lock(&log_mutex); - - time_t t = time(NULL); - struct tm tm = *localtime(&t); - char timestamp[64]; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm); - - // Create log directory if it doesn't exist - char log_dir[512]; - strncpy(log_dir, config.log_file, sizeof(log_dir) - 1); - log_dir[sizeof(log_dir) - 1] = '\0'; - char *dir_path = dirname(log_dir); - - struct stat st; - if (stat(dir_path, &st) != 0) - { - if (mkdir(dir_path, 0755) != 0) - { - fprintf(stderr, "Error creating log directory (%s): %s\n", dir_path, strerror(errno)); - pthread_mutex_unlock(&log_mutex); - return; - } - } - else if (!S_ISDIR(st.st_mode)) - { - fprintf(stderr, "Log path (%s) exists but is not a directory\n", dir_path); - pthread_mutex_unlock(&log_mutex); - return; - } - - // Check log file size and rotate if necessary - if (stat(config.log_file, &st) == 0) - { - if (st.st_size > MAX_LOG_FILE_SIZE) - { - char backup_log[512]; - snprintf(backup_log, sizeof(backup_log), "%s.old", config.log_file); - rename(config.log_file, backup_log); - } - } - - FILE *logfile = fopen(config.log_file, "a"); - if (!logfile) - { - fprintf(stderr, "Error opening log file (%s): %s\n", config.log_file, strerror(errno)); - pthread_mutex_unlock(&log_mutex); - return; - } - - // Format log entry with timestamp, process ID, and thread ID - char log_entry[LOG_BUFFER_SIZE]; - snprintf(log_entry, sizeof(log_entry), "[%s] [PID:%d] [TID:%lu] %s\n", - timestamp, - getpid(), - pthread_self(), - message); - - // Write to log file - if (fputs(log_entry, logfile) == EOF) - { - fprintf(stderr, "Error writing to log file: %s\n", strerror(errno)); - } - - // Ensure log is written immediately - fflush(logfile); - fclose(logfile); - - // Also print to stdout for debugging if verbose mode is enabled - if (config.verbose) - { - printf("%s", log_entry); - fflush(stdout); - } - - pthread_mutex_unlock(&log_mutex); -} char *get_mime_type(const char *filepath) { diff --git a/src/server_config.c b/src/server_config.c index e1a3274..730dfb3 100644 --- a/src/server_config.c +++ b/src/server_config.c @@ -6,16 +6,16 @@ void init_config(ServerConfig *config) { config->port = 8080; config->use_https = false; - strcpy(config->log_file, "server.log"); + strcpy(config->log_file, "log/server.log"); config->max_threads = 4; config->running = true; config->automatic_startup = false; - config->verbose = 0; + config->log_mode = LOG_MODE_CLASSIC; // Default to classic logging strcpy(config->server_name, "127.0.0.1"); config->enable_http2 = false; config->enable_websocket = false; strcpy(config->www_path, "www"); config->max_connections = 1024; - strcpy(config->ssl_cert_path, "ssl/cert/"); - strcpy(config->ssl_key_path, "ssl"); + strcpy(config->ssl_cert_path, "ssl/cert/cert.pem"); + strcpy(config->ssl_key_path, "ssl/key/key.key"); } diff --git a/src/server_config.h b/src/server_config.h index 2443dab..bf14b2f 100644 --- a/src/server_config.h +++ b/src/server_config.h @@ -3,6 +3,14 @@ #include +// Log modes +typedef enum { + LOG_MODE_OFF = 0, + LOG_MODE_CLASSIC = 1, + LOG_MODE_DEBUG = 2, + LOG_MODE_ADVANCED = 3 +} LogMode; + typedef struct { int port; @@ -12,7 +20,7 @@ typedef struct bool running; bool automatic_startup; char server_name[256]; - int verbose; + LogMode log_mode; // Replaces verbose - supports off/classic/debug/advanced bool enable_http2; bool enable_websocket; char www_path[256]; diff --git a/src/websocket.c b/src/websocket.c index cd8c0ae..8e3ea30 100644 --- a/src/websocket.c +++ b/src/websocket.c @@ -4,13 +4,13 @@ #include #include #include -#include -#include #include +#include #include #include #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" +#define SHA1_DIGEST_LENGTH 20 // Base64 encode function static char *base64_encode(const unsigned char *input, int length) @@ -27,6 +27,10 @@ static char *base64_encode(const unsigned char *input, int length) BIO_get_mem_ptr(b64, &bptr); char *buff = (char *)malloc(bptr->length + 1); + if (!buff) { + BIO_free_all(b64); + return NULL; + } memcpy(buff, bptr->data, bptr->length); buff[bptr->length] = '\0'; @@ -38,6 +42,10 @@ static char *base64_encode(const unsigned char *input, int length) // Generate WebSocket accept key from client key char *ws_generate_accept_key(const char *client_key) { + if (!client_key || strlen(client_key) > 128) { + return NULL; // Security: validate input length + } + char combined[256]; int written = snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID); @@ -46,10 +54,23 @@ char *ws_generate_accept_key(const char *client_key) return NULL; } - unsigned char hash[SHA_DIGEST_LENGTH]; - SHA1((unsigned char *)combined, strlen(combined), hash); + unsigned char hash[SHA1_DIGEST_LENGTH]; + unsigned int hash_len = 0; + + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) { + return NULL; + } + + if (EVP_DigestInit_ex(ctx, EVP_sha1(), NULL) != 1 || + EVP_DigestUpdate(ctx, combined, strlen(combined)) != 1 || + EVP_DigestFinal_ex(ctx, hash, &hash_len) != 1) { + EVP_MD_CTX_free(ctx); + return NULL; + } + EVP_MD_CTX_free(ctx); - return base64_encode(hash, SHA_DIGEST_LENGTH); + return base64_encode(hash, (int)hash_len); } // Handle WebSocket handshake