From 72df6a73fcee6d5b7dff98ace6554af3b0b36492 Mon Sep 17 00:00:00 2001 From: Azreyo <58790873+Azreyo@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:35:00 +0000 Subject: [PATCH] Enhance server ssl configuration - Added SSL certificate and key paths to ServerConfig structure. - Updated init_config function to initialize new SSL paths. - Formated code for better readability. --- .gitignore | 2 +- README.md | 26 +- server.conf | 12 +- src/config_parser.c | 265 ++++++----- src/http2.c | 560 +++++++++++++---------- src/http2.h | 16 +- src/performance.c | 201 ++++---- src/performance.h | 18 +- src/server.c | 1065 ++++++++++++++++++++++++++----------------- src/server_config.c | 7 +- src/server_config.h | 8 +- src/websocket.c | 222 +++++---- src/websocket.h | 18 +- 13 files changed, 1420 insertions(+), 1000 deletions(-) diff --git a/.gitignore b/.gitignore index e307778..08afbbe 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,4 @@ Mkfile.old dkms.conf log/* server -certs/*.pem +ssl/* diff --git a/README.md b/README.md index 342f533..8771c5c 100644 --- a/README.md +++ b/README.md @@ -118,24 +118,6 @@ make # Run the server sudo ./server ``` -### Using SSL certificate or HTTPS, WebSocket, HTTP/2 -```bash -# Generate SSL certificates (optional) -mkdir -p certs -openssl req -x509 -nodes -days 365 -newkey rsa:4096 \ - -keyout certs/key.pem -out certs/cert.pem \ - -subj "/C=US/ST=State/L=City/O=Carbon/CN=localhost" - - -# Test HTTP/2 -curl --http2 -k https://localhost:443/ - -# to use WebSocket (edit server.conf) -# Set: use_https = true, enable_http2 = true, enable_websocket = true - -# Test WebSocket -# Visit https://localhost:443/websocket-test.html in your browser -``` ### Build Options @@ -167,12 +149,12 @@ gcc src/server.c src/config_parser.c src/server_config.c src/websocket.c src/htt ```bash # Create certificates directory -mkdir -p certs +mkdir -p ssl ssl/certs ssl/key # Generate self-signed certificate (for testing only) openssl req -x509 -newkey rsa:2048 \ - -keyout certs/key.pem \ - -out certs/cert.pem \ + -keyout ssl/key/key.key \ + -out ssl/cert/cert.pem \ -days 365 -nodes \ -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" ``` @@ -223,6 +205,8 @@ enable_websocket = false - `max_threads`: Number of worker threads - `server_name`: Your domain or IP address - `verbose`: Enable detailed logging - accepts: true/false, yes/no, on/off, 1/0 +- `ssl_cert_path`: Path to ssl certificate +- `ssl_key_path`: Path to ssl key **Note:** Boolean values are flexible and accept multiple formats: - True: `true`, `yes`, `on`, `1` diff --git a/server.conf b/server.conf index 96dfb68..8e38401 100644 --- a/server.conf +++ b/server.conf @@ -7,9 +7,9 @@ running = true # Server listening port port = 8080 # Enable HTTPS (requires valid certificates in certs/ directory) -use_https = false +use_https = false # Enable HTTP/2 support (requires HTTPS) -enable_http2 = false +enable_http2 = false # Enable WebSocket support enable_websocket = false # Server name or IP address (used for logging and response headers) @@ -21,12 +21,14 @@ server_name = Your_domain/IP max_threads = 4 max_connections = 1024 - - # ---Path configuration--- # Log file location log_file = log/server.log # Enable verbose logging verbose = true # Path to www -www_path = www \ No newline at end of file +www_path = www +# path to public ssl certification +ssl_cert_path = ssl/cert/cert.pem +# path to private ssl key +ssl_key_path = ssl/key/key.key \ No newline at end of file diff --git a/src/config_parser.c b/src/config_parser.c index 3711e09..d68be2f 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -5,7 +5,8 @@ #include #include "server_config.h" -typedef enum { +typedef enum +{ CONFIG_PORT, CONFIG_USE_HTTPS, CONFIG_LOG_FILE, @@ -17,40 +18,49 @@ typedef enum { CONFIG_ENABLE_WEBSOCKET, CONFIG_WWW_PATH, CONFIG_MAX_CONNECTIONS, + CONFIG_SSL_CERT_PATH, + CONFIG_SSL_KEY_PATH, CONFIG_UNKNOWN } ConfigKey; // Trim whitespace from both ends of a string -static char* trim_whitespace(char *str) { +static char *trim_whitespace(char *str) +{ char *end; - + // Trim leading space - while(isspace((unsigned char)*str)) str++; - - if(*str == 0) return str; - + while (isspace((unsigned char)*str)) + str++; + + if (*str == 0) + return str; + // Trim trailing space end = str + strlen(str) - 1; - while(end > str && isspace((unsigned char)*end)) end--; - + while (end > str && isspace((unsigned char)*end)) + end--; + end[1] = '\0'; return str; } // Parse a boolean value (true/false, yes/no, on/off, 1/0) -static bool parse_bool(const char *value) { - if (strcasecmp(value, "true") == 0 || - strcasecmp(value, "yes") == 0 || +static bool parse_bool(const char *value) +{ + if (strcasecmp(value, "true") == 0 || + strcasecmp(value, "yes") == 0 || strcasecmp(value, "on") == 0 || - strcmp(value, "1") == 0) { + strcmp(value, "1") == 0) + { return true; } return false; } // Map string to enum -static ConfigKey get_config_key(const char *key) { - static const struct +static ConfigKey get_config_key(const char *key) +{ + static const struct { const char *name; ConfigKey key; @@ -63,23 +73,29 @@ static ConfigKey get_config_key(const char *key) { {"server_name", CONFIG_SERVER_NAME}, {"verbose", CONFIG_VERBOSE}, {"enable_http2", CONFIG_ENABLE_HTTP2}, - {"enable_websocket",CONFIG_ENABLE_WEBSOCKET}, + {"enable_websocket", CONFIG_ENABLE_WEBSOCKET}, {"www_path", CONFIG_WWW_PATH}, {"max_connections", CONFIG_MAX_CONNECTIONS}, + {"ssl_cert_path", CONFIG_SSL_CERT_PATH}, + {"ssl_key_path", CONFIG_SSL_KEY_PATH}, {NULL, CONFIG_UNKNOWN} - + }; - for (int i = 0;key_map[i].name != NULL; i++) { - if (strcasecmp(key, key_map[i].name) == 0) { + for (int i = 0; key_map[i].name != NULL; i++) + { + if (strcasecmp(key, key_map[i].name) == 0) + { return key_map[i].key; } } return CONFIG_UNKNOWN; } -int load_config(const char *filename, ServerConfig *config) { +int load_config(const char *filename, ServerConfig *config) +{ FILE *fp = fopen(filename, "r"); - if (!fp) { + if (!fp) + { perror("Error opening config file"); return 1; } @@ -87,115 +103,146 @@ int load_config(const char *filename, ServerConfig *config) { char line[512]; int line_number = 0; - while (fgets(line, sizeof(line), fp)) { - line_number++; - - // Remove newline - line[strcspn(line, "\r\n")] = 0; - - // Trim whitespace - char *trimmed = trim_whitespace(line); - - // Skip empty lines and comments - if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') { - continue; - } - - // Find the delimiter (= or space) - char *delim = strchr(trimmed, '='); - if (!delim) { - // Try space as delimiter - delim = strchr(trimmed, ' '); - } - - if (!delim) { - fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed); - continue; - } - - // Split into key and value - *delim = '\0'; - char *key = trim_whitespace(trimmed); - char *value = trim_whitespace(delim + 1); - - // Remove quotes from value if present - if ((value[0] == '"' || value[0] == '\'') && - value[strlen(value) - 1] == value[0]) { - value[strlen(value) - 1] = '\0'; - value++; - } - // Parse configuration options - switch (get_config_key(key)) { - case CONFIG_PORT: - config->port = atoi(value); - printf("load_config: port = %d\n", config->port); + while (fgets(line, sizeof(line), fp)) + { + line_number++; + + // Remove newline + line[strcspn(line, "\r\n")] = 0; + + // Trim whitespace + char *trimmed = trim_whitespace(line); + + // Skip empty lines and comments + if (trimmed[0] == '\0' || trimmed[0] == '#' || trimmed[0] == ';') + { + continue; + } + + // Find the delimiter (= or space) + char *delim = strchr(trimmed, '='); + if (!delim) + { + // Try space as delimiter + delim = strchr(trimmed, ' '); + } + + if (!delim) + { + fprintf(stderr, "Warning: Invalid config line %d: %s\n", line_number, trimmed); + continue; + } + + // Split into key and value + *delim = '\0'; + char *key = trim_whitespace(trimmed); + char *value = trim_whitespace(delim + 1); + + // Remove quotes from value if present + if ((value[0] == '"' || value[0] == '\'') && + value[strlen(value) - 1] == value[0]) + { + value[strlen(value) - 1] = '\0'; + value++; + } + // Parse configuration options + switch (get_config_key(key)) + { + case CONFIG_PORT: + config->port = atoi(value); + printf("load_config: port = %d\n", config->port); break; - case CONFIG_USE_HTTPS: - config->use_https = parse_bool(value); - printf("load_config: use_https = %d\n", config->use_https); + case CONFIG_USE_HTTPS: + config->use_https = parse_bool(value); + printf("load_config: use_https = %d\n", config->use_https); break; - case CONFIG_LOG_FILE: + case CONFIG_LOG_FILE: strncpy(config->log_file, value, sizeof(config->log_file) - 1); - config->log_file[sizeof(config->log_file) - 1] = '\0'; - printf("load_config: log_file = %s\n", config->log_file); + config->log_file[sizeof(config->log_file) - 1] = '\0'; + printf("load_config: log_file = %s\n", config->log_file); break; - case CONFIG_MAX_THREADS: - config->max_threads = atoi(value); - printf("load_config: max_threads = %d\n", config->max_threads); + case CONFIG_MAX_THREADS: + config->max_threads = atoi(value); + printf("load_config: max_threads = %d\n", config->max_threads); break; - case CONFIG_RUNNING: - config->running = parse_bool(value); - if (config->running == 0 || false) { - fprintf(stderr, "ERROR: current run state is false cannot run the server!\n"); - exit(EXIT_FAILURE); - } - printf("load_config: running = %d\n", config->running); + case CONFIG_RUNNING: + config->running = parse_bool(value); + if (!config->running) + { + fprintf(stderr, "ERROR: current run state is false cannot run the server!\n"); + exit(EXIT_FAILURE); + } + printf("load_config: running = %d\n", config->running); break; - case CONFIG_SERVER_NAME: - strncpy(config->server_name, value, sizeof(config->server_name) - 1); - config->server_name[sizeof(config->server_name) - 1] = '\0'; - printf("load_config: server_name = %s\n", config->server_name); - if (strcmp(config->server_name, "Your_domain/IP") == 0) { - fprintf(stderr, "WARNING: server_name is set to default\n" - "Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n"); - } + case CONFIG_SERVER_NAME: + strncpy(config->server_name, value, sizeof(config->server_name) - 1); + config->server_name[sizeof(config->server_name) - 1] = '\0'; + printf("load_config: server_name = %s\n", config->server_name); + if (strcmp(config->server_name, "Your_domain/IP") == 0) + { + fprintf(stderr, "WARNING: server_name is set to default\n" + "Please set server_name in server.conf to the server's IP address or domain name for proper operation.\n"); + } + break; + case CONFIG_VERBOSE: + config->verbose = parse_bool(value); + printf("load_config: verbose = %d\n", config->verbose); break; - case CONFIG_VERBOSE: - config->verbose = parse_bool(value); - printf("load_config: verbose = %d\n", config->verbose); - break; - - case CONFIG_ENABLE_HTTP2: - config->enable_http2 = parse_bool(value); + case CONFIG_ENABLE_HTTP2: + config->enable_http2 = parse_bool(value); + if (!config->use_https && config->enable_http2) + { + printf("Error: Cannot load HTTP/2 while HTTPS is not enabled!\n"); + exit(EXIT_FAILURE); + } + else + { printf("load_config: enable_http2 = %d\n", config->enable_http2); + } + + break; + case CONFIG_ENABLE_WEBSOCKET: + config->enable_websocket = parse_bool(value); + printf("load_config: enable_websocket = %d\n", config->enable_websocket); break; - case CONFIG_ENABLE_WEBSOCKET: - config->enable_websocket = parse_bool(value); - printf("load_config: enable_websocket = %d\n", config->enable_websocket); + case CONFIG_WWW_PATH: + strncpy(config->www_path, value, sizeof(config->www_path) - 1); + config->www_path[sizeof(config->www_path) - 1] = '\0'; + printf("load_config: www_path = %s\n", config->www_path); break; - case CONFIG_WWW_PATH: - strncpy(config->www_path, value, sizeof(config->www_path) - 1); - config->www_path[sizeof(config->www_path) - 1] = '\0'; - printf("load_config: www_path = %s\n", config->www_path); - break; - - case CONFIG_MAX_CONNECTIONS: - config->max_connections = atoi(value); - printf("load_config: max_connections: = %d\n", config->max_connections); + case CONFIG_MAX_CONNECTIONS: + config->max_connections = atoi(value); + printf("load_config: max_connections: = %d\n", config->max_connections); break; - case CONFIG_UNKNOWN: - default: - fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number); + case CONFIG_SSL_CERT_PATH: + if (config->use_https) + { + strncpy(config->ssl_cert_path, value, sizeof(config->ssl_cert_path) - 1); + config->ssl_cert_path[sizeof(config->ssl_cert_path) - 1] = '\0'; + printf("load_config: ssl_cert_path = %s\n", config->ssl_cert_path); + } + break; + case CONFIG_SSL_KEY_PATH: + if (config->use_https) + { + strncpy(config->ssl_key_path, value, sizeof(config->ssl_key_path) - 1); + config->ssl_key_path[sizeof(config->ssl_key_path) - 1] = '\0'; + printf("load_config: ssl_key_path = %s\n", config->ssl_key_path); + } + break; + case CONFIG_UNKNOWN: + default: + fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number); break; } } diff --git a/src/http2.c b/src/http2.c index aa6ce9f..5aca634 100644 --- a/src/http2.c +++ b/src/http2.c @@ -10,27 +10,31 @@ extern ServerConfig config; extern void log_event(const char *message); -extern char* get_mime_type(const char *filepath); -extern char* sanitize_url(const char *url); +extern char *get_mime_type(const char *filepath); +extern char *sanitize_url(const char *url); // ALPN callback - select HTTP/2 protocol int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg) { + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ (void)ssl; (void)arg; - + int ret = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); - - if (ret == 1) { + + if (ret == 1) + { // HTTP/2 selected return SSL_TLSEXT_ERR_OK; - } else if (ret == 0) { + } + else if (ret == 0) + { // HTTP/1.1 selected return SSL_TLSEXT_ERR_OK; } - + // No match, use HTTP/1.1 as fallback *out = (const unsigned char *)"http/1.1"; *outlen = 8; @@ -39,252 +43,285 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, // Data read callback for nghttp2 static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, - uint8_t *buf, size_t length, - uint32_t *data_flags, - nghttp2_data_source *source, - void *user_data) { + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) +{ (void)session; (void)stream_id; (void)user_data; - + int fd = source->fd; ssize_t nread; - - while ((nread = read(fd, buf, length)) == -1 && errno == EINTR); - - if (nread == -1) { + + while ((nread = read(fd, buf, length)) == -1 && errno == EINTR) + ; + + if (nread == -1) + { return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } - - if (nread == 0) { + + if (nread == 0) + { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } - + return nread; } // Send callback for nghttp2 static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, - size_t length, int flags, void *user_data) { + size_t length, int flags, void *user_data) +{ (void)session; (void)flags; - + http2_session_t *h2_session = (http2_session_t *)user_data; ssize_t rv; - - if (h2_session->ssl) { + + if (h2_session->ssl) + { rv = SSL_write(h2_session->ssl, data, (int)length); - if (rv < 0) { + if (rv < 0) + { int ssl_error = SSL_get_error(h2_session->ssl, rv); - if (ssl_error == SSL_ERROR_WANT_WRITE) { - return NGHTTP2_ERR_WOULDBLOCK; - } - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } else { - rv = write(h2_session->client_socket, data, length); - if (rv < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (ssl_error == SSL_ERROR_WANT_WRITE) + { return NGHTTP2_ERR_WOULDBLOCK; } return NGHTTP2_ERR_CALLBACK_FAILURE; } } - + else + { + rv = write(h2_session->client_socket, data, length); + if (rv < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + return NGHTTP2_ERR_WOULDBLOCK; + } + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return rv; } // Receive callback for nghttp2 static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, - size_t length, int flags, void *user_data) { + size_t length, int flags, void *user_data) +{ (void)session; (void)flags; - + http2_session_t *h2_session = (http2_session_t *)user_data; ssize_t rv; - - if (h2_session->ssl) { + + if (h2_session->ssl) + { rv = SSL_read(h2_session->ssl, buf, (int)length); - if (rv < 0) { + if (rv < 0) + { int ssl_error = SSL_get_error(h2_session->ssl, rv); - if (ssl_error == SSL_ERROR_WANT_READ) { + if (ssl_error == SSL_ERROR_WANT_READ) + { return NGHTTP2_ERR_WOULDBLOCK; } return NGHTTP2_ERR_CALLBACK_FAILURE; } - if (rv == 0) { - return NGHTTP2_ERR_EOF; - } - } else { - rv = read(h2_session->client_socket, buf, length); - if (rv < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - return NGHTTP2_ERR_WOULDBLOCK; - } - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - if (rv == 0) { + if (rv == 0) + { return NGHTTP2_ERR_EOF; } } - + else + { + rv = read(h2_session->client_socket, buf, length); + if (rv < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + return NGHTTP2_ERR_WOULDBLOCK; + } + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + if (rv == 0) + { + return NGHTTP2_ERR_EOF; + } + } + return rv; } // Frame receive callback static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, - void *user_data) { + void *user_data) +{ (void)user_data; - - switch (frame->hd.type) { - case NGHTTP2_HEADERS: - if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - log_event("HTTP/2: Received HEADERS frame (request)"); - - // Get stream data - http2_stream_data_t *stream_data = - (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - - if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { - // Request is complete, send response - char *path = stream_data->request_path; - if (strlen(path) == 0) { - strcpy(path, "/"); - } - - // Sanitize URL - char *sanitized = sanitize_url(path); - if (!sanitized) { - log_event("HTTP/2: Blocked malicious URL"); - - // Send 403 error - nghttp2_nv hdrs[] = { - {(uint8_t *)":status", (uint8_t *)"403", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE} - }; - nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); - break; - } - - // Build file path - char filepath[512]; - snprintf(filepath, sizeof(filepath), "www%s", - (strcmp(sanitized, "/") == 0) ? "/index.html" : sanitized); - free(sanitized); - - // Open file - int fd = open(filepath, O_RDONLY); - if (fd == -1) { - log_event("HTTP/2: File not found"); - - // Send 404 error - nghttp2_nv hdrs[] = { - {(uint8_t *)":status", (uint8_t *)"404", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE} - }; - nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); - break; - } - - // Get file size - struct stat st; - if (fstat(fd, &st) == -1) { - close(fd); - log_event("HTTP/2: Error getting file size"); - - // Send 500 error - nghttp2_nv hdrs[] = { - {(uint8_t *)":status", (uint8_t *)"500", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE} - }; - nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); - break; - } - - // Get MIME type - char *mime_type = get_mime_type(filepath); - if (!mime_type) { - mime_type = strdup("application/octet-stream"); - } - - // Store file info in stream data - stream_data->fd = fd; - stream_data->file_size = st.st_size; - stream_data->mime_type = mime_type; - - // Build response headers - allocate content length string - char *content_length = malloc(32); - if (!content_length) { - close(fd); - free(mime_type); - log_event("HTTP/2: Memory allocation failed"); - break; - } - snprintf(content_length, 32, "%ld", (long)st.st_size); - - // Store content_length in stream_data for cleanup - stream_data->content_length = content_length; - - nghttp2_nv hdrs[] = { - {(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-type", (uint8_t *)mime_type, 12, strlen(mime_type), NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-length", (uint8_t *)content_length, 14, strlen(content_length), NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE} - }; - - // Submit response with file data provider - nghttp2_data_provider data_prd; - data_prd.source.fd = fd; - data_prd.read_callback = file_read_callback; - - nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 4, &data_prd); - - char log_msg[1024]; - snprintf(log_msg, sizeof(log_msg), "HTTP/2: Response submitted for %s (%ld bytes)", - filepath, (long)st.st_size); - log_event(log_msg); + + switch (frame->hd.type) + { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) + { + log_event("HTTP/2: Received HEADERS frame (request)"); + + // Get stream data + http2_stream_data_t *stream_data = + (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + + if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) + { + // Request is complete, send response + char *path = stream_data->request_path; + if (strlen(path) == 0) + { + strcpy(path, "/"); } + + // Sanitize URL + char *sanitized = sanitize_url(path); + if (!sanitized) + { + log_event("HTTP/2: Blocked malicious URL"); + + // Send 403 error + nghttp2_nv hdrs[] = { + {(uint8_t *)":status", (uint8_t *)"403", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}}; + nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); + break; + } + + // Build file path + char filepath[512]; + snprintf(filepath, sizeof(filepath), "www%s", + (strcmp(sanitized, "/") == 0) ? "/index.html" : sanitized); + free(sanitized); + + // Open file + int fd = open(filepath, O_RDONLY); + if (fd == -1) + { + log_event("HTTP/2: File not found"); + + // Send 404 error + nghttp2_nv hdrs[] = { + {(uint8_t *)":status", (uint8_t *)"404", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}}; + nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); + break; + } + + // Get file size + struct stat st; + if (fstat(fd, &st) == -1) + { + close(fd); + log_event("HTTP/2: Error getting file size"); + + // Send 500 error + nghttp2_nv hdrs[] = { + {(uint8_t *)":status", (uint8_t *)"500", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}}; + nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL); + break; + } + + // Get MIME type + char *mime_type = get_mime_type(filepath); + if (!mime_type) + { + mime_type = strdup("application/octet-stream"); + } + + // Store file info in stream data + stream_data->fd = fd; + stream_data->file_size = st.st_size; + stream_data->mime_type = mime_type; + + // Build response headers - allocate content length string + char *content_length = malloc(32); + if (!content_length) + { + close(fd); + free(mime_type); + log_event("HTTP/2: Memory allocation failed"); + break; + } + snprintf(content_length, 32, "%ld", (long)st.st_size); + + // Store content_length in stream_data for cleanup + stream_data->content_length = content_length; + + nghttp2_nv hdrs[] = { + {(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"content-type", (uint8_t *)mime_type, 12, strlen(mime_type), NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"content-length", (uint8_t *)content_length, 14, strlen(content_length), NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}}; + + // Submit response with file data provider + nghttp2_data_provider data_prd; + data_prd.source.fd = fd; + data_prd.read_callback = file_read_callback; + + nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 4, &data_prd); + + char log_msg[1024]; + snprintf(log_msg, sizeof(log_msg), "HTTP/2: Response submitted for %s (%ld bytes)", + filepath, (long)st.st_size); + log_event(log_msg); } - break; - case NGHTTP2_DATA: - log_event("HTTP/2: Received DATA frame"); - break; - case NGHTTP2_SETTINGS: - log_event("HTTP/2: Received SETTINGS frame"); - break; - default: - break; + } + break; + case NGHTTP2_DATA: + log_event("HTTP/2: Received DATA frame"); + break; + case NGHTTP2_SETTINGS: + log_event("HTTP/2: Received SETTINGS frame"); + break; + default: + break; } - + return 0; } // Stream close callback static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *user_data) { + uint32_t error_code, void *user_data) +{ (void)error_code; (void)user_data; - + // Get stream data and clean up - http2_stream_data_t *stream_data = + http2_stream_data_t *stream_data = (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id); - - if (stream_data) { - if (stream_data->fd != -1) { + + if (stream_data) + { + if (stream_data->fd != -1) + { close(stream_data->fd); } - if (stream_data->mime_type) { + if (stream_data->mime_type) + { free(stream_data->mime_type); } - if (stream_data->content_length) { + if (stream_data->content_length) + { free(stream_data->content_length); } free(stream_data); } - + log_event("HTTP/2: Stream closed"); - + return 0; } @@ -293,89 +330,97 @@ static int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, - uint8_t flags, void *user_data) { + uint8_t flags, void *user_data) +{ (void)flags; (void)user_data; - + if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + frame->headers.cat != NGHTTP2_HCAT_REQUEST) + { return 0; } - + // Get stream data - http2_stream_data_t *stream_data = + http2_stream_data_t *stream_data = (http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - - if (!stream_data) { + + if (!stream_data) + { return 0; } - + // Process request headers - if (namelen == 5 && memcmp(name, ":path", 5) == 0) { - size_t copy_len = valuelen < sizeof(stream_data->request_path) - 1 ? - valuelen : sizeof(stream_data->request_path) - 1; + if (namelen == 5 && memcmp(name, ":path", 5) == 0) + { + size_t copy_len = valuelen < sizeof(stream_data->request_path) - 1 ? valuelen : sizeof(stream_data->request_path) - 1; memcpy(stream_data->request_path, value, copy_len); stream_data->request_path[copy_len] = '\0'; - + char log_msg[512]; snprintf(log_msg, sizeof(log_msg), "HTTP/2: Request path: %s", stream_data->request_path); log_event(log_msg); } - + return 0; } // Begin headers callback static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, - void *user_data) { + void *user_data) +{ (void)session; (void)user_data; - + if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + frame->headers.cat != NGHTTP2_HCAT_REQUEST) + { return 0; } - + // Allocate stream data http2_stream_data_t *stream_data = calloc(1, sizeof(http2_stream_data_t)); - if (!stream_data) { + if (!stream_data) + { return NGHTTP2_ERR_CALLBACK_FAILURE; } - + stream_data->stream_id = frame->hd.stream_id; stream_data->fd = -1; - + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); - + return 0; } // Data chunk receive callback static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, - size_t len, void *user_data) { + size_t len, void *user_data) +{ (void)session; (void)flags; (void)stream_id; (void)data; (void)len; (void)user_data; - + // Handle POST data if needed return 0; } // Initialize HTTP/2 session -int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) { +int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) +{ h2_session->client_socket = client_socket; h2_session->ssl = ssl; h2_session->handshake_complete = false; - + // Setup callbacks nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks_new(&callbacks); - + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); @@ -383,39 +428,42 @@ int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); - + // Create server session int rv = nghttp2_session_server_new(&h2_session->session, callbacks, h2_session); nghttp2_session_callbacks_del(callbacks); - - if (rv != 0) { + + if (rv != 0) + { log_event("HTTP/2: Failed to create session"); return -1; } - + // Send initial SETTINGS frame nghttp2_settings_entry settings[] = { {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, - {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535} - }; - + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535}}; + rv = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE, - settings, sizeof(settings) / sizeof(settings[0])); - if (rv != 0) { + settings, sizeof(settings) / sizeof(settings[0])); + if (rv != 0) + { log_event("HTTP/2: Failed to submit settings"); nghttp2_session_del(h2_session->session); return -1; } - + h2_session->handshake_complete = true; log_event("HTTP/2: Session initialized"); - + return 0; } // Cleanup HTTP/2 session -void http2_session_cleanup(http2_session_t *h2_session) { - if (h2_session->session) { +void http2_session_cleanup(http2_session_t *h2_session) +{ + if (h2_session->session) + { nghttp2_session_del(h2_session->session); h2_session->session = NULL; } @@ -423,60 +471,66 @@ void http2_session_cleanup(http2_session_t *h2_session) { // Send HTTP/2 response int http2_send_response(http2_session_t *h2_session, int32_t stream_id, - const char *data, size_t len, bool end_stream) { - (void)data; // Unused in current implementation - (void)len; // Unused in current implementation - (void)end_stream; // Unused in current implementation - + const char *data, size_t len, bool end_stream) +{ + (void)data; // Unused in current implementation + (void)len; // Unused in current implementation + (void)end_stream; // Unused in current implementation + // Send response headers nghttp2_nv hdrs[] = { {(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE}, {(uint8_t *)"content-type", (uint8_t *)"text/html", 12, 9, NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE} - }; - + {(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}}; + int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 3, NULL); - if (rv != 0) { + if (rv != 0) + { return -1; } - + return nghttp2_session_send(h2_session->session); } // Send HTTP/2 error response int http2_send_error(http2_session_t *h2_session, int32_t stream_id, - int status_code, const char *message) { + int status_code, const char *message) +{ char status_str[4]; snprintf(status_str, sizeof(status_str), "%d", status_code); - + nghttp2_nv hdrs[] = { {(uint8_t *)":status", (uint8_t *)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE}, - {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE} - }; - + {(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}}; + int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 2, NULL); - if (rv != 0) { + if (rv != 0) + { return -1; } - - if (message) { + + if (message) + { nghttp2_data_provider prd; prd.source.ptr = (void *)message; prd.read_callback = NULL; - + nghttp2_submit_data(h2_session->session, NGHTTP2_FLAG_END_STREAM, - stream_id, &prd); + stream_id, &prd); } - + return nghttp2_session_send(h2_session->session); } // Handle HTTP/2 connection -int http2_handle_connection(http2_session_t *h2_session) { +int http2_handle_connection(http2_session_t *h2_session) +{ // Receive and process frames first int rv = nghttp2_session_recv(h2_session->session); - if (rv != 0) { - if (rv == NGHTTP2_ERR_EOF) { + if (rv != 0) + { + if (rv == NGHTTP2_ERR_EOF) + { log_event("HTTP/2: Connection closed"); return 0; } @@ -485,22 +539,24 @@ int http2_handle_connection(http2_session_t *h2_session) { log_event(err_msg); return -1; } - + // Send all pending data rv = nghttp2_session_send(h2_session->session); - if (rv != 0) { + if (rv != 0) + { char err_msg[128]; snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session send failed: %s", nghttp2_strerror(rv)); log_event(err_msg); return -1; } - + // Check if session wants to terminate if (nghttp2_session_want_read(h2_session->session) == 0 && - nghttp2_session_want_write(h2_session->session) == 0) { + nghttp2_session_want_write(h2_session->session) == 0) + { log_event("HTTP/2: Session terminated normally"); return 0; } - + return 1; } diff --git a/src/http2.h b/src/http2.h index 31e5bbf..56753f4 100644 --- a/src/http2.h +++ b/src/http2.h @@ -6,7 +6,8 @@ #include // HTTP/2 session context -typedef struct { +typedef struct +{ nghttp2_session *session; SSL *ssl; int client_socket; @@ -14,11 +15,12 @@ typedef struct { } http2_session_t; // HTTP/2 stream data -typedef struct { +typedef struct +{ int32_t stream_id; char request_path[256]; char *request_method; - int fd; // File descriptor for response + int fd; // File descriptor for response size_t file_size; char *mime_type; char *content_length; @@ -28,14 +30,14 @@ typedef struct { int http2_session_init(http2_session_t *session, int client_socket, SSL *ssl); void http2_session_cleanup(http2_session_t *session); int http2_handle_connection(http2_session_t *session); -int http2_send_response(http2_session_t *session, int32_t stream_id, +int http2_send_response(http2_session_t *session, int32_t stream_id, const char *data, size_t len, bool end_stream); -int http2_send_error(http2_session_t *session, int32_t stream_id, +int http2_send_error(http2_session_t *session, int32_t stream_id, int status_code, const char *message); // ALPN callback for protocol selection int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, - unsigned char *outlen, const unsigned char *in, - unsigned int inlen, void *arg); + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg); #endif diff --git a/src/performance.c b/src/performance.c index d179afb..ac4834a 100644 --- a/src/performance.c +++ b/src/performance.c @@ -7,7 +7,7 @@ #include #define MAX_MMAP_CACHE_SIZE 50 -#define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB +#define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB #define BUFFER_POOL_SIZE 32 #define DEFAULT_BUFFER_SIZE 16384 @@ -27,7 +27,8 @@ const char *response_429_header = "HTTP/1.1 429 Too Many Requests\r\n\r\nRate li const char *response_500_header = "HTTP/1.1 500 Internal Server Error\r\n\r\nInternal Server Error"; // Task queue implementation -void init_task_queue(task_queue_t *queue) { +void init_task_queue(task_queue_t *queue) +{ queue->head = NULL; queue->tail = NULL; queue->count = 0; @@ -35,182 +36,216 @@ void init_task_queue(task_queue_t *queue) { pthread_cond_init(&queue->cond, NULL); } -void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https) { +void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https) +{ connection_task_t *task = malloc(sizeof(connection_task_t)); - if (!task) return; - + if (!task) + return; + task->socket_fd = socket_fd; task->ssl = ssl; task->is_https = is_https; task->next = NULL; - + pthread_mutex_lock(&queue->mutex); - - if (queue->tail) { + + if (queue->tail) + { queue->tail->next = task; - } else { + } + else + { queue->head = task; } queue->tail = task; queue->count++; - + pthread_cond_signal(&queue->cond); pthread_mutex_unlock(&queue->mutex); } -connection_task_t* dequeue_task(task_queue_t *queue) { +connection_task_t *dequeue_task(task_queue_t *queue) +{ pthread_mutex_lock(&queue->mutex); - - while (queue->head == NULL) { + + while (queue->head == NULL) + { pthread_cond_wait(&queue->cond, &queue->mutex); } - + connection_task_t *task = queue->head; queue->head = task->next; - - if (queue->head == NULL) { + + if (queue->head == NULL) + { queue->tail = NULL; } queue->count--; - + pthread_mutex_unlock(&queue->mutex); return task; } -void destroy_task_queue(task_queue_t *queue) { +void destroy_task_queue(task_queue_t *queue) +{ pthread_mutex_lock(&queue->mutex); - + connection_task_t *current = queue->head; - while (current) { + while (current) + { connection_task_t *next = current->next; free(current); current = next; } - + pthread_mutex_unlock(&queue->mutex); pthread_mutex_destroy(&queue->mutex); pthread_cond_destroy(&queue->cond); } // Memory-mapped file cache implementation -void init_mmap_cache(void) { +void init_mmap_cache(void) +{ mmap_cache = calloc(MAX_MMAP_CACHE_SIZE, sizeof(mmap_cache_entry_t)); } -mmap_cache_entry_t* get_cached_file(const char *path) { +mmap_cache_entry_t *get_cached_file(const char *path) +{ pthread_mutex_lock(&mmap_cache_mutex); - - for (int i = 0; i < mmap_cache_size; i++) { - if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) { + + for (int i = 0; i < mmap_cache_size; i++) + { + if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) + { mmap_cache[i].last_access = time(NULL); mmap_cache[i].ref_count++; pthread_mutex_unlock(&mmap_cache_mutex); return &mmap_cache[i]; } } - + pthread_mutex_unlock(&mmap_cache_mutex); return NULL; } -void cache_file_mmap(const char *path, size_t size, const char *mime_type) { - if (size > MAX_MMAP_FILE_SIZE) return; - +void cache_file_mmap(const char *path, size_t size, const char *mime_type) +{ + if (size > MAX_MMAP_FILE_SIZE) + return; + pthread_mutex_lock(&mmap_cache_mutex); - + // Check if already cached - for (int i = 0; i < mmap_cache_size; i++) { - if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) { + for (int i = 0; i < mmap_cache_size; i++) + { + if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0) + { pthread_mutex_unlock(&mmap_cache_mutex); return; } } - + // Find slot (evict LRU if full) int slot = mmap_cache_size; - if (mmap_cache_size >= MAX_MMAP_CACHE_SIZE) { + if (mmap_cache_size >= MAX_MMAP_CACHE_SIZE) + { time_t oldest = time(NULL); - for (int i = 0; i < mmap_cache_size; i++) { - if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest) { + for (int i = 0; i < mmap_cache_size; i++) + { + if (mmap_cache[i].ref_count == 0 && mmap_cache[i].last_access < oldest) + { oldest = mmap_cache[i].last_access; slot = i; } } - - if (slot == mmap_cache_size) { + + if (slot == mmap_cache_size) + { pthread_mutex_unlock(&mmap_cache_mutex); - return; // All entries in use + return; // All entries in use } - + // Evict old entry - if (mmap_cache[slot].mmap_data) { + if (mmap_cache[slot].mmap_data) + { munmap(mmap_cache[slot].mmap_data, mmap_cache[slot].size); } free(mmap_cache[slot].path); free(mmap_cache[slot].mime_type); - } else { + } + else + { mmap_cache_size++; } - + // Map file int fd = open(path, O_RDONLY); - if (fd < 0) { + if (fd < 0) + { pthread_mutex_unlock(&mmap_cache_mutex); return; } - + void *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - - if (mapped == MAP_FAILED) { + + if (mapped == MAP_FAILED) + { pthread_mutex_unlock(&mmap_cache_mutex); return; } - + // Advise kernel about access pattern madvise(mapped, size, MADV_WILLNEED | MADV_SEQUENTIAL); - + mmap_cache[slot].path = strdup(path); mmap_cache[slot].mmap_data = mapped; mmap_cache[slot].size = size; mmap_cache[slot].last_access = time(NULL); mmap_cache[slot].mime_type = strdup(mime_type); mmap_cache[slot].ref_count = 0; - + pthread_mutex_unlock(&mmap_cache_mutex); } -void release_cached_file(mmap_cache_entry_t *entry) { +void release_cached_file(mmap_cache_entry_t *entry) +{ pthread_mutex_lock(&mmap_cache_mutex); entry->ref_count--; pthread_mutex_unlock(&mmap_cache_mutex); } -void cleanup_mmap_cache(void) { +void cleanup_mmap_cache(void) +{ pthread_mutex_lock(&mmap_cache_mutex); - - for (int i = 0; i < mmap_cache_size; i++) { - if (mmap_cache[i].mmap_data) { + + for (int i = 0; i < mmap_cache_size; i++) + { + if (mmap_cache[i].mmap_data) + { munmap(mmap_cache[i].mmap_data, mmap_cache[i].size); } free(mmap_cache[i].path); free(mmap_cache[i].mime_type); } - + free(mmap_cache); mmap_cache = NULL; mmap_cache_size = 0; - + pthread_mutex_unlock(&mmap_cache_mutex); } // Buffer pool implementation -void init_buffer_pool(void) { +void init_buffer_pool(void) +{ pthread_mutex_lock(&buffer_pool_mutex); - - for (int i = 0; i < BUFFER_POOL_SIZE; i++) { + + for (int i = 0; i < BUFFER_POOL_SIZE; i++) + { buffer_pool_t *buf = malloc(sizeof(buffer_pool_t)); - if (buf) { + if (buf) + { buf->buffer = malloc(DEFAULT_BUFFER_SIZE); buf->size = DEFAULT_BUFFER_SIZE; buf->in_use = false; @@ -218,58 +253,66 @@ void init_buffer_pool(void) { buffer_pool = buf; } } - + pthread_mutex_unlock(&buffer_pool_mutex); } -char* get_buffer_from_pool(size_t min_size) { +char *get_buffer_from_pool(size_t min_size) +{ pthread_mutex_lock(&buffer_pool_mutex); - + buffer_pool_t *current = buffer_pool; - while (current) { - if (!current->in_use && current->size >= min_size) { + while (current) + { + if (!current->in_use && current->size >= min_size) + { current->in_use = true; pthread_mutex_unlock(&buffer_pool_mutex); return current->buffer; } current = current->next; } - + pthread_mutex_unlock(&buffer_pool_mutex); - + return malloc(min_size); } -void return_buffer_to_pool(char *buffer) { +void return_buffer_to_pool(char *buffer) +{ pthread_mutex_lock(&buffer_pool_mutex); - + buffer_pool_t *current = buffer_pool; - while (current) { - if (current->buffer == buffer) { + while (current) + { + if (current->buffer == buffer) + { current->in_use = false; pthread_mutex_unlock(&buffer_pool_mutex); return; } current = current->next; } - + pthread_mutex_unlock(&buffer_pool_mutex); - + // Not from pool, free it free(buffer); } -void cleanup_buffer_pool(void) { +void cleanup_buffer_pool(void) +{ pthread_mutex_lock(&buffer_pool_mutex); - + buffer_pool_t *current = buffer_pool; - while (current) { + while (current) + { buffer_pool_t *next = current->next; free(current->buffer); free(current); current = next; } - + buffer_pool = NULL; pthread_mutex_unlock(&buffer_pool_mutex); } diff --git a/src/performance.h b/src/performance.h index e62a854..0bbdbc2 100644 --- a/src/performance.h +++ b/src/performance.h @@ -8,14 +8,16 @@ #include // Connection pool structures -typedef struct connection_task_t { +typedef struct connection_task_t +{ int socket_fd; SSL *ssl; bool is_https; struct connection_task_t *next; } connection_task_t; -typedef struct { +typedef struct +{ connection_task_t *head; connection_task_t *tail; pthread_mutex_t mutex; @@ -24,7 +26,8 @@ typedef struct { } task_queue_t; // Memory-mapped file cache -typedef struct { +typedef struct +{ char *path; void *mmap_data; size_t size; @@ -34,7 +37,8 @@ typedef struct { } mmap_cache_entry_t; // Response buffer pool -typedef struct buffer_pool_t { +typedef struct buffer_pool_t +{ char *buffer; size_t size; bool in_use; @@ -44,17 +48,17 @@ typedef struct buffer_pool_t { // Function declarations void init_task_queue(task_queue_t *queue); void enqueue_task(task_queue_t *queue, int socket_fd, SSL *ssl, bool is_https); -connection_task_t* dequeue_task(task_queue_t *queue); +connection_task_t *dequeue_task(task_queue_t *queue); void destroy_task_queue(task_queue_t *queue); void init_mmap_cache(void); -mmap_cache_entry_t* get_cached_file(const char *path); +mmap_cache_entry_t *get_cached_file(const char *path); void cache_file_mmap(const char *path, size_t size, const char *mime_type); void release_cached_file(mmap_cache_entry_t *entry); void cleanup_mmap_cache(void); void init_buffer_pool(void); -char* get_buffer_from_pool(size_t min_size); +char *get_buffer_from_pool(size_t min_size); void return_buffer_to_pool(char *buffer); void cleanup_buffer_pool(void); diff --git a/src/server.c b/src/server.c index 772be26..75d254b 100644 --- a/src/server.c +++ b/src/server.c @@ -30,47 +30,50 @@ #define MAX_LOG_SIZE 2048 #define MAX_EVENTS 1024 -#define BOLD "\x1b[1m" -#define RED "\x1b[31m" -#define GREEN "\x1b[32m" -#define YELLOW "\x1b[33m" -#define BLUE "\x1b[34m" -#define RESET "\x1b[0m" +#define BOLD "\x1b[1m" +#define RED "\x1b[31m" +#define GREEN "\x1b[32m" +#define YELLOW "\x1b[33m" +#define BLUE "\x1b[34m" +#define RESET "\x1b[0m" -#define HANDLE_ERROR(msg) do { \ - log_event(msg ": " BOLD RED); \ - log_event(strerror(errno)); \ - goto cleanup; \ -} while(0) +#define HANDLE_ERROR(msg) \ + do \ + { \ + log_event(msg ": " BOLD RED); \ + log_event(strerror(errno)); \ + goto cleanup; \ + } while (0) // Use larger buffer for file operations #define FILE_BUFFER_SIZE 65536 -#define SECURITY_HEADERS \ - "X-Content-Type-Options: nosniff\r\n" \ - "X-Frame-Options: SAMEORIGIN\r\n" \ - "X-XSS-Protection: 1; mode=block\r\n" \ +#define SECURITY_HEADERS \ + "X-Content-Type-Options: nosniff\r\n" \ + "X-Frame-Options: SAMEORIGIN\r\n" \ + "X-XSS-Protection: 1; mode=block\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" -#define RATE_LIMIT_WINDOW 60 // 60 seconds -#define MAX_REQUESTS 100 // max requests per window +#define RATE_LIMIT_WINDOW 60 // 60 seconds +#define MAX_REQUESTS 100 // max requests per window #define LOG_BUFFER_SIZE 4096 -#define MAX_LOG_FILE_SIZE (100 * 1024 * 1024) // 100MB max log file size +#define MAX_LOG_FILE_SIZE (100 * 1024 * 1024) // 100MB max log file size -#define SOCKET_SEND_BUFFER_SIZE (256 * 1024) // 256KB -#define SOCKET_RECV_BUFFER_SIZE (256 * 1024) // 256KB -#define SOCKET_BACKLOG 128 // Increased from 50 -#define EPOLL_TIMEOUT 100 // 100ms timeout +#define SOCKET_SEND_BUFFER_SIZE (256 * 1024) // 256KB +#define SOCKET_RECV_BUFFER_SIZE (256 * 1024) // 256KB +#define SOCKET_BACKLOG 128 // Increased from 50 +#define EPOLL_TIMEOUT 100 // 100ms timeout #define MAX_THREAD_POOL_SIZE 32 #define MAX_CACHE_SIZE 100 -#define MAX_CACHE_FILE_SIZE (1024 * 1024) // 1MB -#define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB +#define MAX_CACHE_FILE_SIZE (1024 * 1024) // 1MB +#define MAX_MMAP_FILE_SIZE (10 * 1024 * 1024) // 10MB -typedef struct { +typedef struct +{ pthread_t thread; int busy; } ThreadInfo; @@ -80,13 +83,15 @@ int thread_pool_size = 0; pthread_mutex_t thread_pool_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t thread_pool_cond = PTHREAD_COND_INITIALIZER; -typedef struct { +typedef struct +{ char ip[INET_ADDRSTRLEN]; time_t window_start; int request_count; } RateLimit; -typedef struct { +typedef struct +{ char *path; char *data; size_t size; @@ -126,12 +131,14 @@ void *start_http_server(void *arg); void *start_https_server(void *arg); void shutdown_server(); int parse_request_line(char *request_buffer, char *method, char *url, char *protocol); -char* get_mime_type(const char *filepath); -char* sanitize_url(const char *url); +char *get_mime_type(const char *filepath); +char *sanitize_url(const char *url); int check_rate_limit(const char *ip); -void initialize_openssl() { - if (!SSL_library_init()) { +void initialize_openssl() +{ + if (!SSL_library_init()) + { perror(BOLD RED "Error initializing OpenSSL library" RESET); exit(EXIT_FAILURE); } @@ -139,50 +146,58 @@ void initialize_openssl() { OpenSSL_add_all_algorithms(); } - -void cleanup_openssl() { - if (ssl_ctx) { +void cleanup_openssl() +{ + if (ssl_ctx) + { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } EVP_cleanup(); } - -SSL_CTX *create_ssl_context() { +SSL_CTX *create_ssl_context() +{ const SSL_METHOD *method = TLS_server_method(); SSL_CTX *ctx = SSL_CTX_new(method); - if (!ctx) { + if (!ctx) + { perror(BOLD RED "Unable to create SSL context" RESET); exit(EXIT_FAILURE); } return ctx; } -void configure_ssl_context(SSL_CTX *ctx) { - if (SSL_CTX_use_certificate_file(ctx, "certs/cert.pem", SSL_FILETYPE_PEM) <= 0) { +void configure_ssl_context(SSL_CTX *ctx) +{ + if (SSL_CTX_use_certificate_file(ctx, config.ssl_cert_path, SSL_FILETYPE_PEM) <= 0) + { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } - if (SSL_CTX_use_PrivateKey_file(ctx, "certs/key.pem", SSL_FILETYPE_PEM) <= 0) { + if (SSL_CTX_use_PrivateKey_file(ctx, config.ssl_key_path, SSL_FILETYPE_PEM) <= 0) + { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } - if (SSL_CTX_set_cipher_list(ctx, "HIGH: !aNULL: !MD5") != 1) { - ERR_print_errors_fp(stderr); - exit(EXIT_FAILURE); - } - - // 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"); - } + if (SSL_CTX_set_cipher_list(ctx, "HIGH: !aNULL: !MD5") != 1) + { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + // 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"); + } } -void set_socket_options(int socket_fd) { +void set_socket_options(int socket_fd) +{ int flags = fcntl(socket_fd, F_GETFL, 0); - fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); // Make socket non-blocking + fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK); // Make socket non-blocking int reuse = 1; int keepalive = 1; @@ -192,9 +207,9 @@ void set_socket_options(int socket_fd) { int nodelay = 1; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); - #ifdef SO_REUSEPORT +#ifdef SO_REUSEPORT setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)); - #endif +#endif setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)); setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); setsockopt(socket_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)); @@ -207,10 +222,12 @@ void set_socket_options(int socket_fd) { setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf)); } -void *start_http_server(void *arg) { - (void)arg; +void *start_http_server(void *arg) +{ + (void)arg; http_socket = socket(AF_INET, SOCK_STREAM, 0); - if (http_socket < 0) { + if (http_socket < 0) + { perror(BOLD RED "Error creating HTTP socket" RESET); pthread_exit(NULL); } @@ -222,20 +239,23 @@ void *start_http_server(void *arg) { http_address.sin_addr.s_addr = INADDR_ANY; http_address.sin_port = htons(config.port); - if (bind(http_socket, (struct sockaddr *)&http_address, sizeof(http_address)) < 0) { + if (bind(http_socket, (struct sockaddr *)&http_address, sizeof(http_address)) < 0) + { perror(BOLD RED "Error binding HTTP socket" RESET); close(http_socket); pthread_exit(NULL); } - if (listen(http_socket, SOCKET_BACKLOG) < 0) { + if (listen(http_socket, SOCKET_BACKLOG) < 0) + { perror(BOLD RED "Error listening on HTTP socket" RESET); close(http_socket); pthread_exit(NULL); } - epoll_fd = epoll_create1(0); // Create epoll instance - if (epoll_fd == -1) { + epoll_fd = epoll_create1(0); // Create epoll instance + if (epoll_fd == -1) + { perror("epoll_create1"); close(http_socket); // Close the socket before exiting pthread_exit(NULL); @@ -244,7 +264,8 @@ void *start_http_server(void *arg) { struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = http_socket; - if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, http_socket, &ev) == -1) { + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, http_socket, &ev) == -1) + { perror("epoll_ctl: http_socket"); close(http_socket); close(epoll_fd); // Close epoll fd @@ -254,41 +275,53 @@ void *start_http_server(void *arg) { log_event("HTTP server started."); struct epoll_event events[MAX_EVENTS]; - while (config.running && server_running) { + while (config.running && server_running) + { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, EPOLL_TIMEOUT); // 100ms timeout - if (nfds == -1) { - if (errno != EINTR) { // Ignore interrupts for shutdown + if (nfds == -1) + { + if (errno != EINTR) + { // Ignore interrupts for shutdown perror("epoll_wait"); break; // Exit loop on error } continue; // Continue if it was an interrupt } - for (int i = 0; i < nfds; ++i) { - if (events[i].data.fd == http_socket) { + for (int i = 0; i < nfds; ++i) + { + if (events[i].data.fd == http_socket) + { // New connection struct sockaddr_in client_addr; socklen_t addr_size = sizeof(client_addr); int client_socket = accept(http_socket, (struct sockaddr *)&client_addr, &addr_size); - if (client_socket < 0) { + if (client_socket < 0) + { perror("accept"); continue; } pthread_mutex_lock(&thread_count_mutex); - if (num_client_threads < config.max_connections) { + if (num_client_threads < config.max_connections) + { pthread_t client_thread; int *client_socket_ptr = malloc(sizeof(int)); *client_socket_ptr = client_socket; - if (pthread_create(&client_thread, NULL, handle_http_client, client_socket_ptr) == 0) { + if (pthread_create(&client_thread, NULL, handle_http_client, client_socket_ptr) == 0) + { client_threads[num_client_threads++] = client_thread; - } else { + } + else + { perror("Error creating HTTP client thread"); close(client_socket); free(client_socket_ptr); } - } else { + } + else + { log_event("Max client threads reached, rejecting connection."); close(client_socket); } @@ -304,12 +337,12 @@ void *start_http_server(void *arg) { pthread_exit(NULL); } - - -void *start_https_server(void *arg) { - (void)arg; +void *start_https_server(void *arg) +{ + (void)arg; https_socket = socket(AF_INET, SOCK_STREAM, 0); - if (https_socket < 0) { + if (https_socket < 0) + { perror(BOLD RED "Error creating HTTPS socket" RESET); pthread_exit(NULL); } @@ -322,13 +355,15 @@ void *start_https_server(void *arg) { https_address.sin_addr.s_addr = INADDR_ANY; https_address.sin_port = htons(443); - if (bind(https_socket, (struct sockaddr *)&https_address, sizeof(https_address)) < 0) { + if (bind(https_socket, (struct sockaddr *)&https_address, sizeof(https_address)) < 0) + { perror(BOLD RED "Error binding HTTPS socket" RESET); close(https_socket); pthread_exit(NULL); } - if (listen(https_socket, SOCKET_BACKLOG) < 0) { + if (listen(https_socket, SOCKET_BACKLOG) < 0) + { perror(BOLD RED "Error listening on HTTPS socket" RESET); close(https_socket); pthread_exit(NULL); @@ -336,10 +371,13 @@ void *start_https_server(void *arg) { log_event("HTTPS server started."); - while (config.running && server_running) { + while (config.running && server_running) + { int client_socket = accept(https_socket, NULL, NULL); - if (client_socket < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (client_socket < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { usleep(10000); continue; } @@ -348,19 +386,25 @@ void *start_https_server(void *arg) { } pthread_mutex_lock(&thread_count_mutex); - if (num_client_threads < config.max_connections) { + if (num_client_threads < config.max_connections) + { pthread_t client_thread; int *client_socket_ptr = malloc(sizeof(int)); *client_socket_ptr = client_socket; - if (pthread_create(&client_thread, NULL, handle_https_client, client_socket_ptr) == 0) { + if (pthread_create(&client_thread, NULL, handle_https_client, client_socket_ptr) == 0) + { client_threads[num_client_threads++] = client_thread; - } else { + } + else + { perror("Error creating HTTPS client thread"); close(client_socket); free(client_socket_ptr); } - } else { + } + else + { log_event("Max client threads reached, rejecting connection."); close(client_socket); } @@ -372,100 +416,116 @@ void *start_https_server(void *arg) { } // Check if request is a WebSocket upgrade request -static int is_websocket_upgrade(const char *request) { +static int is_websocket_upgrade(const char *request) +{ // Make a lowercase copy for case-insensitive comparison char *request_lower = strdup(request); - if (!request_lower) return 0; - - for (char *p = request_lower; *p; p++) { + if (!request_lower) + return 0; + + for (char *p = request_lower; *p; p++) + { *p = tolower((unsigned char)*p); } - + // Check for "upgrade: websocket" and "connection:" containing "upgrade" int has_upgrade = strstr(request_lower, "upgrade: websocket") != NULL; int has_connection = strstr(request_lower, "connection:") != NULL && strstr(request_lower, "upgrade") != NULL; - + free(request_lower); return has_upgrade && has_connection; } // Handle WebSocket connection -static void *handle_websocket(void *arg) { +static void *handle_websocket(void *arg) +{ ws_connection_t *conn = (ws_connection_t *)arg; - + log_event("WebSocket connection established"); - + uint8_t buffer[65536]; - while (server_running && config.running) { + while (server_running && config.running) + { ssize_t bytes_received; - - if (conn->is_ssl) { + + if (conn->is_ssl) + { bytes_received = SSL_read(conn->ssl, buffer, sizeof(buffer)); - } else { + } + else + { bytes_received = recv(conn->socket_fd, buffer, sizeof(buffer), 0); } - - if (bytes_received <= 0) { + + if (bytes_received <= 0) + { break; } - + ws_frame_header_t header; uint8_t *payload = NULL; int parsed = ws_parse_frame(buffer, bytes_received, &header, &payload); - - if (parsed < 0) { + + if (parsed < 0) + { log_event("Failed to parse WebSocket frame"); free(payload); break; } - - switch (header.opcode) { - case WS_OPCODE_TEXT: - if (ws_is_valid_utf8(payload, header.payload_length)) { - // Echo back the text message - ws_send_text(conn, (const char *)payload); - log_event("WebSocket text frame received and echoed"); - } else { - log_event("Invalid UTF-8 in text frame"); - } - break; - - case WS_OPCODE_BINARY: - // Echo back binary data - ws_send_frame(conn, WS_OPCODE_BINARY, payload, header.payload_length); - log_event("WebSocket binary frame received and echoed"); - break; - - case WS_OPCODE_PING: - ws_send_pong(conn, payload, header.payload_length); - log_event("WebSocket ping received, pong sent"); - break; - - case WS_OPCODE_CLOSE: - log_event("WebSocket close frame received"); - free(payload); - ws_close_connection(conn, 1000); - free(conn); - pthread_exit(NULL); - - default: - break; + + switch (header.opcode) + { + case WS_OPCODE_TEXT: + if (ws_is_valid_utf8(payload, header.payload_length)) + { + // Echo back the text message + ws_send_text(conn, (const char *)payload); + log_event("WebSocket text frame received and echoed"); + } + else + { + log_event("Invalid UTF-8 in text frame"); + } + break; + + case WS_OPCODE_BINARY: + // Echo back binary data + ws_send_frame(conn, WS_OPCODE_BINARY, payload, header.payload_length); + log_event("WebSocket binary frame received and echoed"); + break; + + case WS_OPCODE_PING: + ws_send_pong(conn, payload, header.payload_length); + log_event("WebSocket ping received, pong sent"); + break; + + case WS_OPCODE_CLOSE: + log_event("WebSocket close frame received"); + free(payload); + ws_close_connection(conn, 1000); + free(conn); + pthread_exit(NULL); + + default: + break; } - + free(payload); } - + ws_close_connection(conn, 1000); free(conn); pthread_exit(NULL); } -void *handle_http_client(void *arg) { +void *handle_http_client(void *arg) +{ int client_socket = *((int *)arg); free(arg); - if (!server_running) { + if (!server_running) + { close(client_socket); pthread_exit(NULL); } @@ -474,32 +534,40 @@ void *handle_http_client(void *arg) { memset(request_buffer, 0, MAX_REQUEST_SIZE); ssize_t bytes_received = recv(client_socket, request_buffer, MAX_REQUEST_SIZE - 1, 0); - if (bytes_received > 0) { + if (bytes_received > 0) + { request_buffer[bytes_received] = '\0'; log_event("Received HTTP request"); // Check for WebSocket upgrade request - if (config.enable_websocket && is_websocket_upgrade(request_buffer)) { + if (config.enable_websocket && is_websocket_upgrade(request_buffer)) + { log_event("WebSocket upgrade request detected"); - + char response[512]; - if (ws_handle_handshake(client_socket, request_buffer, response, sizeof(response)) == 0) { + if (ws_handle_handshake(client_socket, request_buffer, response, sizeof(response)) == 0) + { send(client_socket, response, strlen(response), 0); - + // Create WebSocket connection context ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t)); - if (ws_conn) { + if (ws_conn) + { ws_conn->socket_fd = client_socket; ws_conn->ssl = NULL; ws_conn->is_ssl = false; ws_conn->handshake_complete = true; - + // Handle WebSocket connection in this thread handle_websocket(ws_conn); - } else { + } + else + { close(client_socket); } - } else { + } + else + { log_event("WebSocket handshake failed"); close(client_socket); } @@ -507,7 +575,8 @@ void *handle_http_client(void *arg) { } char method[8], url[256], protocol[16]; - if (parse_request_line(request_buffer, method, url, protocol) != 0) { + if (parse_request_line(request_buffer, method, url, protocol) != 0) + { log_event("Invalid request line."); const char *bad_request_response = "HTTP/1.1 400 Bad Request\r\n\r\nInvalid Request"; send(client_socket, bad_request_response, strlen(bad_request_response), 0); @@ -515,29 +584,32 @@ void *handle_http_client(void *arg) { pthread_exit(NULL); } - if (config.use_https) { // Check if HTTPS is enabled + if (config.use_https) + { // Check if HTTPS is enabled size_t needed = snprintf(NULL, 0, - "HTTP/1.1 301 Moved Permanently\r\n" - "Location: https://%s%s\r\n\r\n", - config.server_name, url) + 1; - + "HTTP/1.1 301 Moved Permanently\r\n" + "Location: https://%s%s\r\n\r\n", + config.server_name, url) + + 1; + char *redirect_response = malloc(needed); - if (redirect_response) { + if (redirect_response) + { snprintf(redirect_response, needed, - "HTTP/1.1 301 Moved Permanently\r\n" - "Location: https://%s%s\r\n\r\n", - config.server_name, url); + "HTTP/1.1 301 Moved Permanently\r\n" + "Location: https://%s%s\r\n\r\n", + config.server_name, url); send(client_socket, redirect_response, strlen(redirect_response), 0); free(redirect_response); } log_event("Redirecting to HTTPS"); close(client_socket); return NULL; - } char *sanitized_url = sanitize_url(url); - if (!sanitized_url) { + if (!sanitized_url) + { log_event("Blocked malicious URL"); const char *forbidden_response = "HTTP/1.1 403 Forbidden\r\n\r\nAccess Denied"; send(client_socket, forbidden_response, strlen(forbidden_response), 0); @@ -551,7 +623,8 @@ void *handle_http_client(void *arg) { getpeername(client_socket, (struct sockaddr *)&addr, &addr_len); inet_ntop(AF_INET, &addr.sin_addr, client_ip, sizeof(client_ip)); - if (!check_rate_limit(client_ip)) { + if (!check_rate_limit(client_ip)) + { log_event("Rate limit exceeded for IP:"); log_event(client_ip); const char *rate_limit_response = "HTTP/1.1 429 Too Many Requests\r\n\r\nRate limit exceeded"; @@ -567,34 +640,37 @@ void *handle_http_client(void *arg) { // Get MIME type char *mime_type = get_mime_type(filepath); - + // Try cache first mmap_cache_entry_t *cached = get_cached_file(filepath); - - if (cached) { + + if (cached) + { // Serve from cache char response_header[1024]; int header_len = snprintf(response_header, sizeof(response_header), - "HTTP/1.1 200 OK\r\n" - "Content-Length: %zu\r\n" - "Content-Type: %s\r\n" - "%s" - "\r\n", - cached->size, - cached->mime_type, - SECURITY_HEADERS); + "HTTP/1.1 200 OK\r\n" + "Content-Length: %zu\r\n" + "Content-Type: %s\r\n" + "%s" + "\r\n", + cached->size, + cached->mime_type, + SECURITY_HEADERS); send(client_socket, response_header, header_len, 0); - + // Send cached data size_t total_sent = 0; - while (total_sent < cached->size) { - ssize_t sent = send(client_socket, (char*)cached->mmap_data + total_sent, - cached->size - total_sent, 0); - if (sent <= 0) break; + while (total_sent < cached->size) + { + ssize_t sent = send(client_socket, (char *)cached->mmap_data + total_sent, + cached->size - total_sent, 0); + if (sent <= 0) + break; total_sent += sent; } - + release_cached_file(cached); free(mime_type); log_event("Served file from cache"); @@ -602,38 +678,43 @@ void *handle_http_client(void *arg) { } int fd = open(filepath, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { const char *not_found_response = "HTTP/1.1 404 Not Found\r\n\r\nFile Not Found"; send(client_socket, not_found_response, strlen(not_found_response), 0); free(mime_type); log_event("File not found, sent 404."); - } else { + } + else + { struct stat st; - if (fstat(fd, &st) == -1) { + if (fstat(fd, &st) == -1) + { log_event("Error getting file size."); - const char *internal_server_error = + const char *internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n\r\nInternal Server Error"; send(client_socket, internal_server_error, strlen(internal_server_error), 0); close(fd); free(mime_type); goto cleanup; } - + // Cache if eligible - if (st.st_size > 0 && st.st_size < MAX_MMAP_FILE_SIZE) { + if (st.st_size > 0 && st.st_size < MAX_MMAP_FILE_SIZE) + { cache_file_mmap(filepath, st.st_size, mime_type); } char response_header[1024]; int header_len = snprintf(response_header, sizeof(response_header), - "HTTP/1.1 200 OK\r\n" - "Content-Length: %ld\r\n" - "Content-Type: %s\r\n" - "%s" - "\r\n", - (long)st.st_size, - mime_type, - SECURITY_HEADERS); + "HTTP/1.1 200 OK\r\n" + "Content-Length: %ld\r\n" + "Content-Type: %s\r\n" + "%s" + "\r\n", + (long)st.st_size, + mime_type, + SECURITY_HEADERS); free(mime_type); @@ -642,20 +723,23 @@ void *handle_http_client(void *arg) { // Use sendfile for zero-copy transfer off_t offset = 0; ssize_t sent = sendfile(client_socket, fd, &offset, st.st_size); - if (sent != st.st_size) { + if (sent != st.st_size) + { log_event("Error sending file with sendfile()"); } close(fd); log_event("Served requested file successfully."); } - -done_serving: - } else if (bytes_received < 0) { + + done_serving: + } + else if (bytes_received < 0) + { HANDLE_ERROR("Error receiving request"); } - close(client_socket); + close(client_socket); pthread_exit(NULL); cleanup: @@ -663,31 +747,33 @@ cleanup: pthread_exit(NULL); } - - -void *handle_https_client(void *arg) { +void *handle_https_client(void *arg) +{ int client_socket = *((int *)arg); free(arg); SSL *ssl = SSL_new(ssl_ctx); - if (!ssl) { + if (!ssl) + { log_event("SSL_new failed"); close(client_socket); pthread_exit(NULL); } SSL_set_fd(ssl, client_socket); - if (!server_running) { + if (!server_running) + { SSL_free(ssl); // Free SSL context if server is not running close(client_socket); pthread_exit(NULL); } - if (SSL_accept(ssl) <= 0) { + if (SSL_accept(ssl) <= 0) + { int ssl_error = SSL_get_error(ssl, -1); char error_msg[256]; snprintf(error_msg, sizeof(error_msg), - "SSL handshake failed. SSL error code: %d", ssl_error); + "SSL handshake failed. SSL error code: %d", ssl_error); log_event(error_msg); ERR_print_errors_fp(stderr); SSL_free(ssl); @@ -698,38 +784,45 @@ void *handle_https_client(void *arg) { log_event("SSL handshake successful!"); // Check if HTTP/2 was negotiated via ALPN - if (config.enable_http2) { + if (config.enable_http2) + { const unsigned char *alpn_data = NULL; unsigned int alpn_len = 0; - + SSL_get0_alpn_selected(ssl, &alpn_data, &alpn_len); - - if (alpn_data && alpn_len == 2 && memcmp(alpn_data, "h2", 2) == 0) { + + if (alpn_data && alpn_len == 2 && memcmp(alpn_data, "h2", 2) == 0) + { log_event("HTTP/2 protocol negotiated via ALPN"); - + // Set socket to non-blocking mode for HTTP/2 int flags = fcntl(client_socket, F_GETFL, 0); fcntl(client_socket, F_SETFL, flags | O_NONBLOCK); - + // Initialize HTTP/2 session http2_session_t h2_session; - if (http2_session_init(&h2_session, client_socket, ssl) == 0) { + if (http2_session_init(&h2_session, client_socket, ssl) == 0) + { // Handle HTTP/2 connection - while (server_running) { + while (server_running) + { int result = http2_handle_connection(&h2_session); - if (result <= 0) { - break; // Connection closed or error + if (result <= 0) + { + break; // Connection closed or error } - + // Small delay to avoid busy loop - usleep(1000); // 1ms + usleep(1000); // 1ms } - + http2_session_cleanup(&h2_session); - } else { + } + else + { log_event("Failed to initialize HTTP/2 session"); } - + SSL_shutdown(ssl); SSL_free(ssl); close(client_socket); @@ -741,58 +834,73 @@ void *handle_https_client(void *arg) { memset(buffer, 0, MAX_REQUEST_SIZE); ssize_t bytes_received = SSL_read(ssl, buffer, MAX_REQUEST_SIZE - 1); - if (bytes_received < 0) { + if (bytes_received < 0) + { perror("SSL_read error"); ERR_print_errors_fp(stderr); log_event("SSL_read failed"); goto cleanup; - } else if (bytes_received == 0) { + } + else if (bytes_received == 0) + { log_event("Client closed connection"); goto cleanup; - } else { + } + else + { buffer[bytes_received] = '\0'; log_event("Received HTTPS request:"); log_event(buffer); } // Check for WebSocket upgrade request on HTTPS - if (config.enable_websocket && is_websocket_upgrade(buffer)) { + if (config.enable_websocket && is_websocket_upgrade(buffer)) + { log_event("Secure WebSocket upgrade request detected"); - + char response[512]; - if (ws_handle_handshake_ssl(ssl, buffer, response, sizeof(response)) == 0) { + if (ws_handle_handshake_ssl(ssl, buffer, response, sizeof(response)) == 0) + { SSL_write(ssl, response, strlen(response)); - + // Create WebSocket connection context ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t)); - if (ws_conn) { + if (ws_conn) + { ws_conn->socket_fd = client_socket; ws_conn->ssl = ssl; ws_conn->is_ssl = true; ws_conn->handshake_complete = true; - + // Handle WebSocket connection in this thread handle_websocket(ws_conn); pthread_exit(NULL); - } else { + } + else + { SSL_shutdown(ssl); SSL_free(ssl); close(client_socket); pthread_exit(NULL); } - } else { + } + else + { log_event("Secure WebSocket handshake failed"); goto cleanup; } } char method[8], url[256], protocol[16]; - if (parse_request_line(buffer, method, url, protocol) != 0) { + if (parse_request_line(buffer, method, url, protocol) != 0) + { log_event("Invalid request line."); const char *bad_request_response = "HTTP/1.1 400 Bad Request\r\n\r\nInvalid Request"; SSL_write(ssl, bad_request_response, strlen(bad_request_response)); goto cleanup; - } else { + } + else + { log_event("Method:"); log_event(method); log_event("URL:"); @@ -802,7 +910,8 @@ void *handle_https_client(void *arg) { } char *sanitized_url = sanitize_url(url); - if (!sanitized_url) { + if (!sanitized_url) + { log_event("Blocked malicious URL"); const char *forbidden_response = "HTTP/1.1 403 Forbidden\r\n\r\nAccess Denied"; SSL_write(ssl, forbidden_response, strlen(forbidden_response)); @@ -815,7 +924,8 @@ void *handle_https_client(void *arg) { getpeername(client_socket, (struct sockaddr *)&addr, &addr_len); inet_ntop(AF_INET, &addr.sin_addr, client_ip, sizeof(client_ip)); - if (!check_rate_limit(client_ip)) { + if (!check_rate_limit(client_ip)) + { log_event("Rate limit exceeded for IP:"); log_event(client_ip); const char *rate_limit_response = "HTTP/1.1 429 Too Many Requests\r\n\r\nRate limit exceeded"; @@ -832,34 +942,37 @@ void *handle_https_client(void *arg) { // Get MIME type char *mime_type = get_mime_type(filepath); - + // Try to get file from cache first mmap_cache_entry_t *cached = get_cached_file(filepath); - - if (cached) { + + if (cached) + { // Serve from cache (fast path) char response_header[1024]; int header_len = snprintf(response_header, sizeof(response_header), - "HTTP/1.1 200 OK\r\n" - "Content-Length: %zu\r\n" - "Content-Type: %s\r\n" - "%s" - "\r\n", - cached->size, - cached->mime_type, - SECURITY_HEADERS); + "HTTP/1.1 200 OK\r\n" + "Content-Length: %zu\r\n" + "Content-Type: %s\r\n" + "%s" + "\r\n", + cached->size, + cached->mime_type, + SECURITY_HEADERS); SSL_write(ssl, response_header, header_len); - + // Send cached data directly from memory size_t total_sent = 0; - while (total_sent < cached->size) { + while (total_sent < cached->size) + { int to_send = (cached->size - total_sent > 16384) ? 16384 : (cached->size - total_sent); - int sent = SSL_write(ssl, (char*)cached->mmap_data + total_sent, to_send); - if (sent <= 0) break; + int sent = SSL_write(ssl, (char *)cached->mmap_data + total_sent, to_send); + if (sent <= 0) + break; total_sent += sent; } - + release_cached_file(cached); free(mime_type); log_event("Served file from cache (mmap)"); @@ -868,18 +981,20 @@ void *handle_https_client(void *arg) { // Not in cache - load from disk int fd = open(filepath, O_RDONLY); - if (fd == -1) { + if (fd == -1) + { log_event("File open failed"); const char *not_found_response = "HTTP/1.1 404 Not Found\r\n\r\nFile Not Found"; SSL_write(ssl, not_found_response, strlen(not_found_response)); free(mime_type); goto cleanup; } - + struct stat st; - if (fstat(fd, &st) == -1) { + if (fstat(fd, &st) == -1) + { log_event("Error getting file size."); - const char *internal_server_error = + const char *internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n\r\nInternal Server Error"; SSL_write(ssl, internal_server_error, strlen(internal_server_error)); close(fd); @@ -888,20 +1003,21 @@ void *handle_https_client(void *arg) { } // Cache file if it's small enough - if (st.st_size > 0 && st.st_size < MAX_MMAP_FILE_SIZE) { + if (st.st_size > 0 && st.st_size < MAX_MMAP_FILE_SIZE) + { cache_file_mmap(filepath, st.st_size, mime_type); } char response_header[1024]; int header_len = snprintf(response_header, sizeof(response_header), - "HTTP/1.1 200 OK\r\n" - "Content-Length: %ld\r\n" - "Content-Type: %s\r\n" - "%s" - "\r\n", - (long)st.st_size, - mime_type, - SECURITY_HEADERS); + "HTTP/1.1 200 OK\r\n" + "Content-Length: %ld\r\n" + "Content-Type: %s\r\n" + "%s" + "\r\n", + (long)st.st_size, + mime_type, + SECURITY_HEADERS); free(mime_type); @@ -910,8 +1026,10 @@ void *handle_https_client(void *arg) { // Use larger buffer for better performance char *file_buffer = get_buffer_from_pool(16384); ssize_t bytes_read; - while ((bytes_read = read(fd, file_buffer, 16384)) > 0) { - if (SSL_write(ssl, file_buffer, bytes_read) <= 0) { + while ((bytes_read = read(fd, file_buffer, 16384)) > 0) + { + if (SSL_write(ssl, file_buffer, bytes_read) <= 0) + { log_event("Error sending file content."); break; } @@ -920,9 +1038,9 @@ void *handle_https_client(void *arg) { close(fd); log_event("Served requested file successfully."); - cleanup: - if (ssl) { + if (ssl) + { SSL_shutdown(ssl); SSL_free(ssl); } @@ -930,29 +1048,31 @@ cleanup: pthread_exit(NULL); } - -void shutdown_server() { +void shutdown_server() +{ log_event("Initiating server shutdown..."); - // Set shutdown flags atomically __atomic_store_n(&server_running, 0, __ATOMIC_SEQ_CST); __atomic_store_n(&config.running, 0, __ATOMIC_SEQ_CST); - + // Close all sockets - if (http_socket != -1) { + if (http_socket != -1) + { shutdown(http_socket, SHUT_RDWR); close(http_socket); http_socket = -1; } - - if (https_socket != -1) { + + if (https_socket != -1) + { shutdown(https_socket, SHUT_RDWR); close(https_socket); https_socket = -1; } - - if (epoll_fd != -1) { + + if (epoll_fd != -1) + { close(epoll_fd); epoll_fd = -1; } @@ -961,16 +1081,19 @@ void shutdown_server() { time_t start_time = time(NULL); pthread_mutex_lock(&thread_count_mutex); - while (num_client_threads > 0 && (time(NULL) - start_time) < 5) { + while (num_client_threads > 0 && (time(NULL) - start_time) < 5) + { struct timespec ts; ts.tv_sec = 0; - ts.tv_nsec = 100000000; // 100ms + ts.tv_nsec = 100000000; // 100ms pthread_cond_timedwait(&thread_pool_cond, &thread_count_mutex, &ts); } - + // Force kill remaining threads - for (int i = 0; i < num_client_threads; i++) { - if (client_threads[i] != 0) { + for (int i = 0; i < num_client_threads; i++) + { + if (client_threads[i] != 0) + { pthread_cancel(client_threads[i]); pthread_join(client_threads[i], NULL); client_threads[i] = 0; @@ -983,14 +1106,17 @@ void shutdown_server() { cleanup_thread_pool(); cleanup_mmap_cache(); cleanup_buffer_pool(); - - if (rate_limits) { + + if (rate_limits) + { free(rate_limits); rate_limits = NULL; } - - if (file_cache) { - for (int i = 0; i < cache_size; i++) { + + if (file_cache) + { + for (int i = 0; i < cache_size; i++) + { free(file_cache[i].path); free(file_cache[i].data); free(file_cache[i].mime_type); @@ -998,150 +1124,179 @@ void shutdown_server() { free(file_cache); file_cache = NULL; } - - if (client_threads) { + + if (client_threads) + { free(client_threads); client_threads = NULL; } - + log_event("Server shutdown completed."); } +int parse_request_line(char *request_buffer, char *method, char *url, char *protocol) +{ + if (!request_buffer || !method || !url || !protocol) + return -1; -int parse_request_line(char *request_buffer, char *method, char *url, char *protocol) { - if (!request_buffer || !method || !url || !protocol) return -1; - method[0] = '\0'; url[0] = '\0'; protocol[0] = '\0'; - + char *saveptr1, *saveptr2; char *line = strtok_r(request_buffer, "\r\n", &saveptr1); - if (line == NULL || strlen(line) == 0) return -1; + if (line == NULL || strlen(line) == 0) + return -1; char *token = strtok_r(line, " ", &saveptr2); - if (token == NULL || strlen(token) > 7) return -1; - strncpy(method, token, 7); + if (token == NULL || strlen(token) > 7) + return -1; + strncpy(method, token, 7); method[7] = '\0'; token = strtok_r(NULL, " ", &saveptr2); - if (token == NULL || strlen(token) > 255) return -1; - strncpy(url, token, 255); + if (token == NULL || strlen(token) > 255) + return -1; + strncpy(url, token, 255); url[255] = '\0'; token = strtok_r(NULL, " ", &saveptr2); - if (token == NULL || strlen(token) > 15) return -1; - strncpy(protocol, token, 15); + if (token == NULL || strlen(token) > 15) + return -1; + strncpy(protocol, token, 15); protocol[15] = '\0'; return 0; } -void signal_handler(int sig) { - if (sig == SIGINT || sig == SIGTERM) { +void signal_handler(int sig) +{ + if (sig == SIGINT || sig == SIGTERM) + { server_running = 0; config.running = 0; - if(config.use_https && config.running == 0 && server_running == 0){ - if (https_socket != -1) { + if (config.use_https && config.running == 0 && server_running == 0) + { + if (https_socket != -1) + { shutdown(https_socket, SHUT_RDWR); close(https_socket); https_socket = -1; - exit(EXIT_SUCCESS); } - } else { - if (http_socket != -1) { + exit(EXIT_SUCCESS); + } + } + else + { + if (http_socket != -1) + { shutdown(http_socket, SHUT_RDWR); close(http_socket); http_socket = -1; - exit(EXIT_SUCCESS); } + exit(EXIT_SUCCESS); + } } printf("\nReceived signal %d, initiating shutdown...\n", sig); - if (epoll_fd != -1) { + if (epoll_fd != -1) + { close(epoll_fd); epoll_fd = -1; } - + log_event("Signal received, initiating shutdown..."); } } -void initialize_thread_pool() { +void initialize_thread_pool() +{ thread_pool = calloc(MAX_THREAD_POOL_SIZE, sizeof(ThreadInfo)); - if (!thread_pool) { + if (!thread_pool) + { perror("Failed to allocate thread pool"); exit(EXIT_FAILURE); } } -int main() { - if (load_config("server.conf", &config) != 0) { +int main() +{ + if (load_config("server.conf", &config) != 0) + { printf("Using default configuration.\n"); } config.running = 1; - + // Allocate client threads array client_threads = calloc(config.max_connections, sizeof(pthread_t)); - if (!client_threads) { + if (!client_threads) + { perror("Failed to allocate client threads array"); exit(EXIT_FAILURE); } - + // Initialize thread pool initialize_thread_pool(); - + // Initialize performance optimizations init_mmap_cache(); init_buffer_pool(); log_event("Performance optimizations initialized"); - if (config.use_https) { + if (config.use_https) + { initialize_openssl(); ssl_ctx = create_ssl_context(); configure_ssl_context(ssl_ctx); } - struct sigaction sa; + struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; // Restart interrupted system calls + sa.sa_flags = SA_RESTART; // Restart interrupted system calls - if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) { + if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) + { perror("Failed to set up signal handlers"); exit(EXIT_FAILURE); } - pthread_t http_thread; - if (pthread_create(&http_thread, NULL, start_http_server, NULL) != 0) { + pthread_t http_thread; + if (pthread_create(&http_thread, NULL, start_http_server, NULL) != 0) + { perror("Failed to create HTTP server thread"); exit(EXIT_FAILURE); } pthread_t https_thread; - if (config.use_https) { - if (pthread_create(&https_thread, NULL, start_https_server, NULL) != 0) { + if (config.use_https) + { + if (pthread_create(&https_thread, NULL, start_https_server, NULL) != 0) + { perror("Failed to create HTTPS server thread"); exit(EXIT_FAILURE); } } - while (config.running) { + while (config.running) + { sleep(1); } shutdown_server(); pthread_join(http_thread, NULL); - if (config.use_https) { + if (config.use_https) + { pthread_join(https_thread, NULL); } return 0; } -void log_event(const char *message) { +void log_event(const char *message) +{ pthread_mutex_lock(&log_mutex); time_t t = time(NULL); @@ -1156,21 +1311,27 @@ void log_event(const char *message) { char *dir_path = dirname(log_dir); struct stat st; - if (stat(dir_path, &st) != 0) { - if (mkdir(dir_path, 0755) != 0) { + 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)) { + } + 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) { + 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); @@ -1178,7 +1339,8 @@ void log_event(const char *message) { } FILE *logfile = fopen(config.log_file, "a"); - if (!logfile) { + if (!logfile) + { fprintf(stderr, "Error opening log file (%s): %s\n", config.log_file, strerror(errno)); pthread_mutex_unlock(&log_mutex); return; @@ -1186,14 +1348,15 @@ void log_event(const char *message) { // 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(), + 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) { + if (fputs(log_entry, logfile) == EOF) + { fprintf(stderr, "Error writing to log file: %s\n", strerror(errno)); } @@ -1202,7 +1365,8 @@ void log_event(const char *message) { fclose(logfile); // Also print to stdout for debugging if verbose mode is enabled - if (config.verbose) { + if (config.verbose) + { printf("%s", log_entry); fflush(stdout); } @@ -1210,12 +1374,14 @@ void log_event(const char *message) { pthread_mutex_unlock(&log_mutex); } -char* get_mime_type(const char *filepath) { +char *get_mime_type(const char *filepath) +{ const char *ext = strrchr(filepath, '.'); - if (!ext) return strdup("application/octet-stream"); - + if (!ext) + return strdup("application/octet-stream"); + ext++; // Skip the dot - + if (strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0) return strdup("text/html"); if (strcasecmp(ext, "css") == 0) @@ -1240,166 +1406,204 @@ char* get_mime_type(const char *filepath) { return strdup("font/ttf"); if (strcasecmp(ext, "otf") == 0) return strdup("font/otf"); - + // Fallback to using libmagic for unknown types magic_t magic = magic_open(MAGIC_MIME_TYPE); - if (magic == NULL) { + if (magic == NULL) + { return strdup("application/octet-stream"); } - - if (magic_load(magic, NULL) != 0) { + + if (magic_load(magic, NULL) != 0) + { magic_close(magic); return strdup("application/octet-stream"); } - + const char *mime = magic_file(magic, filepath); char *result = mime ? strdup(mime) : strdup("application/octet-stream"); - + magic_close(magic); return result; } -char* sanitize_url(const char *url) { - if (!url) return NULL; - +char *sanitize_url(const char *url) +{ + if (!url) + return NULL; + size_t url_len = strlen(url); - if (url_len == 0 || url_len > 2048) return NULL; - + if (url_len == 0 || url_len > 2048) + return NULL; + char *sanitized = calloc(1, url_len + 2); - if (!sanitized) { + if (!sanitized) + { log_event("Memory allocation failed in sanitize_url"); return NULL; } - + int j = 0; int slash_count = 0; int consecutive_dots = 0; bool last_was_slash = false; - + // Must start with '/' - if (url[0] != '/') { + if (url[0] != '/') + { sanitized[j++] = '/'; } - - for (size_t i = 0; i < url_len && j < (int)url_len; i++) { + + for (size_t i = 0; i < url_len && j < (int)url_len; i++) + { char c = url[i]; - + // Check for null bytes (security) - if (c == '\0') break; - + if (c == '\0') + break; + // Handle slashes - if (c == '/') { - if (last_was_slash) continue; + if (c == '/') + { + if (last_was_slash) + continue; last_was_slash = true; consecutive_dots = 0; slash_count++; - - if (slash_count > 20) { + + if (slash_count > 20) + { free(sanitized); return NULL; } - + sanitized[j++] = c; continue; } - + last_was_slash = false; - + // Handle dots (prevent traversal) - if (c == '.') { + if (c == '.') + { consecutive_dots++; - if (consecutive_dots > 2) { // Too many dots + if (consecutive_dots > 2) + { // Too many dots free(sanitized); return NULL; } // Check for path traversal patterns - if (consecutive_dots == 2 && (i == 0 || url[i-1] == '/')) { + if (consecutive_dots == 2 && (i == 0 || url[i - 1] == '/')) + { free(sanitized); return NULL; } - } else { + } + else + { consecutive_dots = 0; } - + // Only allow safe characters (alphanumeric, dash, underscore, dot) - if (isalnum((unsigned char)c) || c == '-' || c == '_' || c == '.') { + if (isalnum((unsigned char)c) || c == '-' || c == '_' || c == '.') + { sanitized[j++] = c; - } else if (c == '%') { + } + else if (c == '%') + { // URL encoding - only allow safe encoded characters - if (i + 2 < url_len && isxdigit(url[i+1]) && isxdigit(url[i+2])) { + if (i + 2 < url_len && isxdigit(url[i + 1]) && isxdigit(url[i + 2])) + { sanitized[j++] = c; - } else { + } + else + { free(sanitized); return NULL; } } // Skip other characters silently } - + sanitized[j] = '\0'; - + // Final security checks - if (j == 0 || j > 2048) { + if (j == 0 || j > 2048) + { free(sanitized); return NULL; } - + // Check for dangerous patterns - if (strstr(sanitized, "/../") || + if (strstr(sanitized, "/../") || strstr(sanitized, "/./") || strstr(sanitized, "//") || - (strlen(sanitized) >= 3 && strcmp(sanitized + strlen(sanitized) - 3, "/..") == 0)) { + (strlen(sanitized) >= 3 && strcmp(sanitized + strlen(sanitized) - 3, "/..") == 0)) + { free(sanitized); return NULL; } - + return sanitized; } -int check_rate_limit(const char *ip) { +int check_rate_limit(const char *ip) +{ pthread_mutex_lock(&rate_limit_mutex); - + time_t now = time(NULL); int i; - + // Clean up expired entries - for (i = 0; i < rate_limit_count; i++) { - if (now - rate_limits[i].window_start >= RATE_LIMIT_WINDOW) { - if (i < rate_limit_count - 1) { - memcpy(&rate_limits[i], &rate_limits[rate_limit_count-1], sizeof(RateLimit)); + for (i = 0; i < rate_limit_count; i++) + { + if (now - rate_limits[i].window_start >= RATE_LIMIT_WINDOW) + { + if (i < rate_limit_count - 1) + { + memcpy(&rate_limits[i], &rate_limits[rate_limit_count - 1], sizeof(RateLimit)); } rate_limit_count--; i--; } } - + // Find or create entry for this IP - for (i = 0; i < rate_limit_count; i++) { - if (strcmp(rate_limits[i].ip, ip) == 0) { - if (now - rate_limits[i].window_start >= RATE_LIMIT_WINDOW) { + for (i = 0; i < rate_limit_count; i++) + { + if (strcmp(rate_limits[i].ip, ip) == 0) + { + if (now - rate_limits[i].window_start >= RATE_LIMIT_WINDOW) + { rate_limits[i].window_start = now; rate_limits[i].request_count = 1; - } else if (rate_limits[i].request_count >= MAX_REQUESTS) { + } + else if (rate_limits[i].request_count >= MAX_REQUESTS) + { pthread_mutex_unlock(&rate_limit_mutex); - return 0; // Rate limit exceeded - } else { + return 0; // Rate limit exceeded + } + else + { rate_limits[i].request_count++; } pthread_mutex_unlock(&rate_limit_mutex); - return 1; // Request allowed + return 1; // Request allowed } } - + // Add new entry RateLimit *new_limits = realloc(rate_limits, (rate_limit_count + 1) * sizeof(RateLimit)); - if (!new_limits) { + if (!new_limits) + { pthread_mutex_unlock(&rate_limit_mutex); - return 0; // Memory allocation failed, deny request + return 0; // Memory allocation failed, deny request } rate_limits = new_limits; - + size_t ip_len = strlen(ip); - if (ip_len >= INET_ADDRSTRLEN) { + if (ip_len >= INET_ADDRSTRLEN) + { ip_len = INET_ADDRSTRLEN - 1; } memcpy(rate_limits[rate_limit_count].ip, ip, ip_len); @@ -1407,18 +1611,22 @@ int check_rate_limit(const char *ip) { rate_limits[rate_limit_count].window_start = now; rate_limits[rate_limit_count].request_count = 1; rate_limit_count++; - + pthread_mutex_unlock(&rate_limit_mutex); - return 1; // Request allowed + return 1; // Request allowed } -void cleanup_thread_pool() { - if (!thread_pool) { +void cleanup_thread_pool() +{ + if (!thread_pool) + { return; } - - for (int i = 0; i < thread_pool_size; i++) { - if (thread_pool[i].busy) { + + for (int i = 0; i < thread_pool_size; i++) + { + if (thread_pool[i].busy) + { pthread_cancel(thread_pool[i].thread); pthread_join(thread_pool[i].thread, NULL); } @@ -1428,32 +1636,37 @@ void cleanup_thread_pool() { thread_pool_size = 0; } -void cache_file(const char *path, const char *data, size_t size, const char *mime_type) { +void cache_file(const char *path, const char *data, size_t size, const char *mime_type) +{ pthread_mutex_lock(&cache_mutex); - - if (cache_size >= MAX_CACHE_SIZE) { + + if (cache_size >= MAX_CACHE_SIZE) + { // Remove least recently used entry int lru_index = 0; time_t oldest = file_cache[0].last_access; - - for (int i = 1; i < cache_size; i++) { - if (file_cache[i].last_access < oldest) { + + for (int i = 1; i < cache_size; i++) + { + if (file_cache[i].last_access < oldest) + { oldest = file_cache[i].last_access; lru_index = i; } } - + free(file_cache[lru_index].path); free(file_cache[lru_index].data); free(file_cache[lru_index].mime_type); - + // Move last entry to this position - if (lru_index < cache_size - 1) { + if (lru_index < cache_size - 1) + { memmove(&file_cache[lru_index], &file_cache[cache_size - 1], sizeof(CacheEntry)); } cache_size--; } - + file_cache = realloc(file_cache, (cache_size + 1) * sizeof(CacheEntry)); file_cache[cache_size].path = strdup(path); file_cache[cache_size].data = malloc(size); @@ -1462,6 +1675,6 @@ void cache_file(const char *path, const char *data, size_t size, const char *mim file_cache[cache_size].last_access = time(NULL); file_cache[cache_size].mime_type = strdup(mime_type); cache_size++; - + pthread_mutex_unlock(&cache_mutex); } diff --git a/src/server_config.c b/src/server_config.c index 8ede6f6..e1a3274 100644 --- a/src/server_config.c +++ b/src/server_config.c @@ -2,17 +2,20 @@ #include #include "server_config.h" -void init_config(ServerConfig *config) { +void init_config(ServerConfig *config) +{ config->port = 8080; config->use_https = false; strcpy(config->log_file, "server.log"); config->max_threads = 4; config->running = true; - config->automatic_startup = false; + config->automatic_startup = false; config->verbose = 0; 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"); } diff --git a/src/server_config.h b/src/server_config.h index 9e9b265..2443dab 100644 --- a/src/server_config.h +++ b/src/server_config.h @@ -3,24 +3,26 @@ #include -typedef struct { +typedef struct +{ int port; bool use_https; char log_file[256]; int max_threads; bool running; bool automatic_startup; - char server_name[256]; + char server_name[256]; int verbose; bool enable_http2; bool enable_websocket; char www_path[256]; int max_connections; + char ssl_cert_path[256]; + char ssl_key_path[256]; } ServerConfig; int load_config(const char *filename, ServerConfig *config); void init_config(ServerConfig *config); void log_event(const char *message); - #endif diff --git a/src/websocket.c b/src/websocket.c index 5059abd..4a43851 100644 --- a/src/websocket.c +++ b/src/websocket.c @@ -12,7 +12,8 @@ #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // Base64 encode function -static char* base64_encode(const unsigned char *input, int length) { +static char *base64_encode(const unsigned char *input, int length) +{ BIO *bmem, *b64; BUF_MEM *bptr; @@ -34,47 +35,53 @@ 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) { +char *ws_generate_accept_key(const char *client_key) +{ char combined[256]; snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID); unsigned char hash[SHA_DIGEST_LENGTH]; - SHA1((unsigned char*)combined, strlen(combined), hash); + SHA1((unsigned char *)combined, strlen(combined), hash); return base64_encode(hash, SHA_DIGEST_LENGTH); } // Handle WebSocket handshake -int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size) { +int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size) +{ (void)client_socket; // Unused in this implementation - + // Extract Sec-WebSocket-Key from request const char *key_header = "Sec-WebSocket-Key: "; char *key_start = strstr(request, key_header); - if (!key_start) { + if (!key_start) + { return -1; } key_start += strlen(key_header); - + char *key_end = strstr(key_start, "\r\n"); - if (!key_end) { + if (!key_end) + { return -1; } - + char client_key[256]; size_t key_len = key_end - key_start; - if (key_len >= sizeof(client_key)) { + if (key_len >= sizeof(client_key)) + { return -1; } memcpy(client_key, key_start, key_len); client_key[key_len] = '\0'; - + // Generate accept key char *accept_key = ws_generate_accept_key(client_key); - if (!accept_key) { + if (!accept_key) + { return -1; } - + // Create handshake response snprintf(response, response_size, "HTTP/1.1 101 Switching Protocols\r\n" @@ -83,153 +90,194 @@ int ws_handle_handshake(int client_socket, const char *request, char *response, "Sec-WebSocket-Accept: %s\r\n" "\r\n", accept_key); - + free(accept_key); return 0; } // Handle WebSocket handshake for SSL connections -int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size) { +int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size) +{ (void)ssl; // Use the same logic, just different transport return ws_handle_handshake(0, request, response, response_size); } // Parse WebSocket frame -int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload) { - if (len < 2) { +int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload) +{ + if (len < 2) + { return -1; } - + header->fin = (data[0] & 0x80) >> 7; header->opcode = data[0] & 0x0F; header->mask = (data[1] & 0x80) >> 7; - + size_t offset = 2; uint8_t payload_len = data[1] & 0x7F; - - if (payload_len == 126) { - if (len < 4) return -1; + + if (payload_len == 126) + { + if (len < 4) + return -1; header->payload_length = (data[2] << 8) | data[3]; offset = 4; - } else if (payload_len == 127) { - if (len < 10) return -1; + } + else if (payload_len == 127) + { + if (len < 10) + return -1; header->payload_length = 0; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) + { header->payload_length = (header->payload_length << 8) | data[2 + i]; } offset = 10; - } else { + } + else + { header->payload_length = payload_len; } - - if (header->mask) { - if (len < offset + 4) return -1; + + if (header->mask) + { + if (len < offset + 4) + return -1; memcpy(header->masking_key, data + offset, 4); offset += 4; } - - if (len < offset + header->payload_length) { + + if (len < offset + header->payload_length) + { return -1; } - + // Unmask payload if masked - *payload = (uint8_t*)malloc(header->payload_length); - if (!*payload) { + *payload = (uint8_t *)malloc(header->payload_length); + if (!*payload) + { return -1; } - - if (header->mask) { - for (uint64_t i = 0; i < header->payload_length; i++) { + + if (header->mask) + { + for (uint64_t i = 0; i < header->payload_length; i++) + { (*payload)[i] = data[offset + i] ^ header->masking_key[i % 4]; } - } else { + } + else + { memcpy(*payload, data + offset, header->payload_length); } - + return offset + header->payload_length; } // Create WebSocket frame -int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len) { +int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len) +{ size_t offset = 0; - + // First byte: FIN + opcode buffer[offset++] = 0x80 | (opcode & 0x0F); - + // Second byte: MASK + payload length - if (payload_len < 126) { - if (buffer_size < offset + 1 + payload_len) return -1; + if (payload_len < 126) + { + if (buffer_size < offset + 1 + payload_len) + return -1; buffer[offset++] = payload_len; - } else if (payload_len < 65536) { - if (buffer_size < offset + 3 + payload_len) return -1; + } + else if (payload_len < 65536) + { + if (buffer_size < offset + 3 + payload_len) + return -1; buffer[offset++] = 126; buffer[offset++] = (payload_len >> 8) & 0xFF; buffer[offset++] = payload_len & 0xFF; - } else { - if (buffer_size < offset + 9 + payload_len) return -1; + } + else + { + if (buffer_size < offset + 9 + payload_len) + return -1; buffer[offset++] = 127; - for (int i = 7; i >= 0; i--) { + for (int i = 7; i >= 0; i--) + { buffer[offset++] = (payload_len >> (i * 8)) & 0xFF; } } - + // Copy payload - if (payload && payload_len > 0) { + if (payload && payload_len > 0) + { memcpy(buffer + offset, payload, payload_len); offset += payload_len; } - + return offset; } // Send WebSocket frame -int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len) { +int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len) +{ // Allocate buffer with enough space for header (max 10 bytes) + payload size_t max_frame_size = 10 + payload_len; - if (max_frame_size > 65536) { + if (max_frame_size > 65536) + { max_frame_size = 65536; } - + uint8_t buffer[65536]; - + // Limit payload to avoid overflow (65536 - 10 bytes for max header) size_t safe_payload_len = payload_len; - if (safe_payload_len > 65526) { + if (safe_payload_len > 65526) + { safe_payload_len = 65526; } - + int frame_len = ws_create_frame(buffer, sizeof(buffer), opcode, payload, safe_payload_len); - - if (frame_len < 0) { + + if (frame_len < 0) + { return -1; } - - if (conn->is_ssl && conn->ssl) { + + if (conn->is_ssl && conn->ssl) + { return SSL_write(conn->ssl, buffer, frame_len); - } else { + } + else + { return write(conn->socket_fd, buffer, frame_len); } } // Send text message -int ws_send_text(ws_connection_t *conn, const char *text) { - return ws_send_frame(conn, WS_OPCODE_TEXT, (const uint8_t*)text, strlen(text)); +int ws_send_text(ws_connection_t *conn, const char *text) +{ + return ws_send_frame(conn, WS_OPCODE_TEXT, (const uint8_t *)text, strlen(text)); } // Send pong response -int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len) { +int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len) +{ return ws_send_frame(conn, WS_OPCODE_PONG, payload, payload_len); } // Close WebSocket connection -void ws_close_connection(ws_connection_t *conn, uint16_t status_code) { +void ws_close_connection(ws_connection_t *conn, uint16_t status_code) +{ uint8_t close_payload[2]; close_payload[0] = (status_code >> 8) & 0xFF; close_payload[1] = status_code & 0xFF; - + ws_send_frame(conn, WS_OPCODE_CLOSE, close_payload, 2); - - if (conn->is_ssl && conn->ssl) { + + if (conn->is_ssl && conn->ssl) + { SSL_shutdown(conn->ssl); SSL_free(conn->ssl); } @@ -237,21 +285,35 @@ void ws_close_connection(ws_connection_t *conn, uint16_t status_code) { } // Validate UTF-8 encoding -bool ws_is_valid_utf8(const uint8_t *data, size_t len) { +bool ws_is_valid_utf8(const uint8_t *data, size_t len) +{ size_t i = 0; - while (i < len) { - if (data[i] < 0x80) { + while (i < len) + { + if (data[i] < 0x80) + { i++; - } else if ((data[i] & 0xE0) == 0xC0) { - if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) return false; + } + else if ((data[i] & 0xE0) == 0xC0) + { + if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) + return false; i += 2; - } else if ((data[i] & 0xF0) == 0xE0) { - if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false; + } + else if ((data[i] & 0xF0) == 0xE0) + { + if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) + return false; i += 3; - } else if ((data[i] & 0xF8) == 0xF0) { - if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false; + } + else if ((data[i] & 0xF8) == 0xF0) + { + if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) + return false; i += 4; - } else { + } + else + { return false; } } diff --git a/src/websocket.h b/src/websocket.h index ce4ba74..ae2521d 100644 --- a/src/websocket.h +++ b/src/websocket.h @@ -7,14 +7,15 @@ // WebSocket opcodes #define WS_OPCODE_CONTINUATION 0x0 -#define WS_OPCODE_TEXT 0x1 -#define WS_OPCODE_BINARY 0x2 -#define WS_OPCODE_CLOSE 0x8 -#define WS_OPCODE_PING 0x9 -#define WS_OPCODE_PONG 0xA +#define WS_OPCODE_TEXT 0x1 +#define WS_OPCODE_BINARY 0x2 +#define WS_OPCODE_CLOSE 0x8 +#define WS_OPCODE_PING 0x9 +#define WS_OPCODE_PONG 0xA // WebSocket frame header structure -typedef struct { +typedef struct +{ uint8_t fin; uint8_t opcode; uint8_t mask; @@ -23,7 +24,8 @@ typedef struct { } ws_frame_header_t; // WebSocket connection context -typedef struct { +typedef struct +{ int socket_fd; SSL *ssl; bool is_ssl; @@ -41,7 +43,7 @@ int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_l void ws_close_connection(ws_connection_t *conn, uint16_t status_code); // Helper functions -char* ws_generate_accept_key(const char *client_key); +char *ws_generate_accept_key(const char *client_key); bool ws_is_valid_utf8(const uint8_t *data, size_t len); #endif