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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -59,3 +59,4 @@ ssl/*
|
|||||||
src/bin
|
src/bin
|
||||||
docker-push.sh
|
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 \
|
RUN apk add --no-cache \
|
||||||
gcc \
|
gcc \
|
||||||
g++ \
|
g++ \
|
||||||
@@ -12,15 +13,20 @@ RUN apk add --no-cache \
|
|||||||
zlib-dev \
|
zlib-dev \
|
||||||
git \
|
git \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& apk update \
|
&& rm -rf /var/cache/apk/*
|
||||||
&& apk upgrade --available
|
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
RUN git clone --depth 1 --branch main https://github.com/Azreyo/Carbon.git . && \
|
COPY . .
|
||||||
make clean && make release
|
|
||||||
|
|
||||||
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 \
|
RUN apk add --no-cache \
|
||||||
libssl3 \
|
libssl3 \
|
||||||
@@ -29,17 +35,17 @@ RUN apk add --no-cache \
|
|||||||
zlib \
|
zlib \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
&& apk update \
|
&& rm -rf /var/cache/apk/* /tmp/*
|
||||||
&& apk upgrade --available \
|
|
||||||
&& rm -rf /tmp/* /var/cache/apk/*
|
|
||||||
|
|
||||||
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
|
WORKDIR /app
|
||||||
|
|
||||||
RUN mkdir -p /app/www /app/log /app/ssl/cert /app/ssl/key && \
|
RUN mkdir -p /app/www /app/log /app/ssl/cert /app/ssl/key && \
|
||||||
chown -R carbon:carbon /app && \
|
chown -R carbon:carbon /app && \
|
||||||
chmod 755 /app && \
|
chmod 755 /app /app/www /app/log && \
|
||||||
chmod 750 /app/ssl
|
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/server /app/
|
||||||
COPY --from=builder --chown=carbon:carbon /build/www/ /app/www/
|
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 --from=builder --chown=carbon:carbon /build/LICENSE /app/
|
||||||
COPY --chown=carbon:carbon entrypoint.sh /app/entrypoint.sh
|
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
|
USER carbon
|
||||||
|
|
||||||
@@ -58,10 +65,13 @@ ENV SERVER_NAME=0.0.0.0 \
|
|||||||
ENABLE_HTTP2=false \
|
ENABLE_HTTP2=false \
|
||||||
ENABLE_WEBSOCKET=false \
|
ENABLE_WEBSOCKET=false \
|
||||||
MAX_THREADS=4 \
|
MAX_THREADS=4 \
|
||||||
VERBOSE=true
|
MAX_CONNECTIONS=1024 \
|
||||||
|
LOG_MODE=classic
|
||||||
|
|
||||||
EXPOSE 8080 8443
|
EXPOSE 8080 8443
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
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
|
LIBS = -lssl -lcrypto -lmagic -lnghttp2 -lz
|
||||||
|
|
||||||
# Source files and object files
|
# 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/
|
DEST = src/bin/
|
||||||
OBJS = $(patsubst src/%.c,$(DEST)%.o,$(SRCS))
|
OBJS = $(patsubst src/%.c,$(DEST)%.o,$(SRCS))
|
||||||
TARGET = server
|
TARGET = server
|
||||||
|
|
||||||
# Header files
|
# 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
|
# Include directories
|
||||||
INCLUDES =
|
INCLUDES =
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
carbon-server:
|
carbon-server:
|
||||||
image: azreyo/carbon:latest
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: carbon:latest
|
||||||
container_name: carbon-http-server
|
container_name: carbon-http-server
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
@@ -14,9 +17,34 @@ services:
|
|||||||
- ENABLE_HTTP2=false
|
- ENABLE_HTTP2=false
|
||||||
- ENABLE_WEBSOCKET=false
|
- ENABLE_WEBSOCKET=false
|
||||||
- MAX_THREADS=4
|
- 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
|
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:
|
networks:
|
||||||
carbon-net:
|
carbon-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
carbon-logs:
|
||||||
|
|||||||
@@ -24,8 +24,12 @@ max_connections = 1024
|
|||||||
# ---Path configuration---
|
# ---Path configuration---
|
||||||
# Log file location
|
# Log file location
|
||||||
log_file = log/server.log
|
log_file = log/server.log
|
||||||
# Enable verbose logging
|
# Log mode: off, classic, debug, advanced
|
||||||
verbose = true
|
# - 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
|
# Path to www
|
||||||
www_path = www
|
www_path = www
|
||||||
# path to public ssl certification
|
# path to public ssl certification
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include "server_config.h"
|
#include "server_config.h"
|
||||||
@@ -13,7 +14,8 @@ typedef enum
|
|||||||
CONFIG_MAX_THREADS,
|
CONFIG_MAX_THREADS,
|
||||||
CONFIG_RUNNING,
|
CONFIG_RUNNING,
|
||||||
CONFIG_SERVER_NAME,
|
CONFIG_SERVER_NAME,
|
||||||
CONFIG_VERBOSE,
|
CONFIG_LOG_MODE,
|
||||||
|
CONFIG_VERBOSE, // Keep for backwards compatibility
|
||||||
CONFIG_ENABLE_HTTP2,
|
CONFIG_ENABLE_HTTP2,
|
||||||
CONFIG_ENABLE_WEBSOCKET,
|
CONFIG_ENABLE_WEBSOCKET,
|
||||||
CONFIG_WWW_PATH,
|
CONFIG_WWW_PATH,
|
||||||
@@ -57,6 +59,21 @@ static bool parse_bool(const char *value)
|
|||||||
}
|
}
|
||||||
return false;
|
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
|
// Map string to enum
|
||||||
static ConfigKey get_config_key(const char *key)
|
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},
|
{"max_threads", CONFIG_MAX_THREADS},
|
||||||
{"running", CONFIG_RUNNING},
|
{"running", CONFIG_RUNNING},
|
||||||
{"server_name", CONFIG_SERVER_NAME},
|
{"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_http2", CONFIG_ENABLE_HTTP2},
|
||||||
{"enable_websocket", CONFIG_ENABLE_WEBSOCKET},
|
{"enable_websocket", CONFIG_ENABLE_WEBSOCKET},
|
||||||
{"www_path", CONFIG_WWW_PATH},
|
{"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");
|
"Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n");
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case CONFIG_VERBOSE:
|
||||||
config->verbose = parse_bool(value);
|
// Backwards compatibility: map verbose boolean to log_mode
|
||||||
printf("load_config: verbose = %d\n", config->verbose);
|
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;
|
break;
|
||||||
|
|
||||||
case CONFIG_ENABLE_HTTP2:
|
case CONFIG_ENABLE_HTTP2:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "server_config.h"
|
#include "server_config.h"
|
||||||
|
#include "logging.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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 "performance.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.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 MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB
|
||||||
#define BUFFER_POOL_SIZE 32
|
#define BUFFER_POOL_SIZE 64
|
||||||
#define DEFAULT_BUFFER_SIZE 16384
|
#define DEFAULT_BUFFER_SIZE 32768
|
||||||
|
|
||||||
// Global cache structures
|
// Global cache structures
|
||||||
static mmap_cache_entry_t *mmap_cache = NULL;
|
static mmap_cache_entry_t *mmap_cache = NULL;
|
||||||
|
|||||||
177
src/server.c
177
src/server.c
@@ -29,6 +29,7 @@
|
|||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "performance.h"
|
#include "performance.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
#define MAX_REQUEST_SIZE 16384
|
#define MAX_REQUEST_SIZE 16384
|
||||||
#define MAX_LOG_SIZE 2048
|
#define MAX_LOG_SIZE 2048
|
||||||
@@ -54,10 +55,14 @@
|
|||||||
|
|
||||||
#define SECURITY_HEADERS \
|
#define SECURITY_HEADERS \
|
||||||
"X-Content-Type-Options: nosniff\r\n" \
|
"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" \
|
"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; " \
|
"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
|
#define RATE_LIMIT_WINDOW 60 // 60 seconds
|
||||||
static int MAX_REQUESTS_DYNAMIC = 500; // Will be calculated dynamically
|
static int MAX_REQUESTS_DYNAMIC = 500; // Will be calculated dynamically
|
||||||
@@ -203,18 +208,44 @@ void configure_ssl_context(SSL_CTX *ctx)
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Security hardening
|
// Verify private key matches certificate
|
||||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
if (SSL_CTX_check_private_key(ctx) != 1)
|
||||||
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)
|
LOG_ERROR(LOG_CAT_SSL, "Private key does not match certificate");
|
||||||
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
ERR_print_errors_fp(stderr);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
// Use secure ciphers only - TLS 1.3 and strong TLS 1.2 ciphers
|
// Security hardening - enforce TLS 1.2 minimum (TLS 1.3 preferred)
|
||||||
const char *cipher_list = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:"
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||||
"TLS_AES_128_GCM_SHA256:" // TLS 1.3
|
|
||||||
"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:"
|
// Disable all insecure protocols and options
|
||||||
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:"
|
SSL_CTX_set_options(ctx,
|
||||||
"!aNULL:!eNULL:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK:!CBC";
|
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)
|
if (SSL_CTX_set_cipher_list(ctx, cipher_list) != 1)
|
||||||
{
|
{
|
||||||
@@ -222,12 +253,27 @@ void configure_ssl_context(SSL_CTX *ctx)
|
|||||||
exit(EXIT_FAILURE);
|
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
|
// Enable HTTP/2 ALPN if configured
|
||||||
if (config.enable_http2)
|
if (config.enable_http2)
|
||||||
{
|
{
|
||||||
SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL);
|
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)
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
|
// Initialize default config first
|
||||||
|
init_config(&config);
|
||||||
|
|
||||||
if (load_config("server.conf", &config) != 0)
|
if (load_config("server.conf", &config) != 0)
|
||||||
{
|
{
|
||||||
printf("Using default configuration.\n");
|
printf("Using default configuration.\n");
|
||||||
@@ -1854,13 +1903,33 @@ int main()
|
|||||||
|
|
||||||
config.running = 1;
|
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
|
// Calculate dynamic rate limit based on system resources
|
||||||
MAX_REQUESTS_DYNAMIC = calculate_dynamic_rate_limit();
|
MAX_REQUESTS_DYNAMIC = calculate_dynamic_rate_limit();
|
||||||
|
|
||||||
char rate_limit_msg[256];
|
char rate_limit_msg[256];
|
||||||
snprintf(rate_limit_msg, sizeof(rate_limit_msg),
|
snprintf(rate_limit_msg, sizeof(rate_limit_msg),
|
||||||
"Dynamic rate limit set to %d requests per IP per minute", MAX_REQUESTS_DYNAMIC);
|
"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
|
// Allocate client threads array
|
||||||
client_threads = calloc(config.max_connections, sizeof(pthread_t));
|
client_threads = calloc(config.max_connections, sizeof(pthread_t));
|
||||||
@@ -1929,84 +1998,6 @@ int main()
|
|||||||
return 0;
|
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)
|
char *get_mime_type(const char *filepath)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ void init_config(ServerConfig *config)
|
|||||||
{
|
{
|
||||||
config->port = 8080;
|
config->port = 8080;
|
||||||
config->use_https = false;
|
config->use_https = false;
|
||||||
strcpy(config->log_file, "server.log");
|
strcpy(config->log_file, "log/server.log");
|
||||||
config->max_threads = 4;
|
config->max_threads = 4;
|
||||||
config->running = true;
|
config->running = true;
|
||||||
config->automatic_startup = false;
|
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");
|
strcpy(config->server_name, "127.0.0.1");
|
||||||
config->enable_http2 = false;
|
config->enable_http2 = false;
|
||||||
config->enable_websocket = false;
|
config->enable_websocket = false;
|
||||||
strcpy(config->www_path, "www");
|
strcpy(config->www_path, "www");
|
||||||
config->max_connections = 1024;
|
config->max_connections = 1024;
|
||||||
strcpy(config->ssl_cert_path, "ssl/cert/");
|
strcpy(config->ssl_cert_path, "ssl/cert/cert.pem");
|
||||||
strcpy(config->ssl_key_path, "ssl");
|
strcpy(config->ssl_key_path, "ssl/key/key.key");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#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
|
typedef struct
|
||||||
{
|
{
|
||||||
int port;
|
int port;
|
||||||
@@ -12,7 +20,7 @@ typedef struct
|
|||||||
bool running;
|
bool running;
|
||||||
bool automatic_startup;
|
bool automatic_startup;
|
||||||
char server_name[256];
|
char server_name[256];
|
||||||
int verbose;
|
LogMode log_mode; // Replaces verbose - supports off/classic/debug/advanced
|
||||||
bool enable_http2;
|
bool enable_http2;
|
||||||
bool enable_websocket;
|
bool enable_websocket;
|
||||||
char www_path[256];
|
char www_path[256];
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <openssl/sha.h>
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/evp.h>
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/bio.h>
|
||||||
#include <openssl/buffer.h>
|
#include <openssl/buffer.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
#define SHA1_DIGEST_LENGTH 20
|
||||||
|
|
||||||
// Base64 encode function
|
// Base64 encode function
|
||||||
static char *base64_encode(const unsigned char *input, int length)
|
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);
|
BIO_get_mem_ptr(b64, &bptr);
|
||||||
|
|
||||||
char *buff = (char *)malloc(bptr->length + 1);
|
char *buff = (char *)malloc(bptr->length + 1);
|
||||||
|
if (!buff) {
|
||||||
|
BIO_free_all(b64);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
memcpy(buff, bptr->data, bptr->length);
|
memcpy(buff, bptr->data, bptr->length);
|
||||||
buff[bptr->length] = '\0';
|
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
|
// Generate WebSocket accept key from client key
|
||||||
char *ws_generate_accept_key(const char *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];
|
char combined[256];
|
||||||
int written = snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID);
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
unsigned char hash[SHA1_DIGEST_LENGTH];
|
||||||
SHA1((unsigned char *)combined, strlen(combined), hash);
|
unsigned int hash_len = 0;
|
||||||
|
|
||||||
return base64_encode(hash, SHA_DIGEST_LENGTH);
|
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, (int)hash_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle WebSocket handshake
|
// Handle WebSocket handshake
|
||||||
|
|||||||
Reference in New Issue
Block a user