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:
2025-12-11 20:04:54 +01:00
parent 4e018c428b
commit 1b83097f00
14 changed files with 812 additions and 131 deletions

3
.gitignore vendored
View File

@@ -58,4 +58,5 @@ ssl/*
!.github/workflows/
src/bin
docker-push.sh
entrypoint.sh
entrypoint.sh
.idea

View File

@@ -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 \

View File

@@ -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 =

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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
View 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
View 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

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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");
}

View File

@@ -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];

View File

@@ -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