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.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -58,4 +58,5 @@ ssl/*
|
||||
!.github/workflows/
|
||||
src/bin
|
||||
docker-push.sh
|
||||
entrypoint.sh
|
||||
entrypoint.sh
|
||||
.idea
|
||||
38
Dockerfile
38
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 \
|
||||
|
||||
4
Makefile
4
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 =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#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:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "http2.h"
|
||||
#include "server_config.h"
|
||||
#include "logging.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
476
src/logging.c
Normal file
476
src/logging.c
Normal file
@@ -0,0 +1,476 @@
|
||||
#include "logging.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
109
src/logging.h
Normal file
109
src/logging.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
// 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
|
||||
@@ -1,15 +1,16 @@
|
||||
#include "performance.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
|
||||
177
src/server.c
177
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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// 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];
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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
|
||||
|
||||
Reference in New Issue
Block a user