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.
This commit is contained in:
2025-10-05 17:35:00 +00:00
parent 46b653efe0
commit 72df6a73fc
13 changed files with 1420 additions and 1000 deletions

View File

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