aboutsummaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c130
1 files changed, 105 insertions, 25 deletions
diff --git a/src/server.c b/src/server.c
index 7d5eb8f..33fa54a 100644
--- a/src/server.c
+++ b/src/server.c
@@ -21,6 +21,8 @@
static volatile sig_atomic_t g_shutdown_requested = 0;
+static bool validate_request_headers(const char *request_buffer);
+
static void signal_handler(int sig) {
(void)sig;
g_shutdown_requested = 1;
@@ -280,6 +282,14 @@ void handle_client_connection(int new_socket) {
&started_at);
return;
}
+ if (!validate_request_headers(buffer)) {
+ beaker_log("SECURITY",
+ "handle_client_connection: Rejected malformed headers.");
+ send_status("400 Bad Request");
+ finish_client_connection(new_socket, requested_path, method, log_path,
+ &started_at);
+ return;
+ }
size_t request_line_len = first_line_end - buffer;
if (request_line_len >= sizeof(request_line)) {
beaker_log("ERROR", "handle_client_connection: Request line too long.\n");
@@ -454,42 +464,112 @@ const char *beaker_get_remote_addr(void) {
static __thread char g_header_value[MAX_VALUE_LEN];
+static bool is_valid_request_header_value(const char *start, const char *end) {
+ for (const char *cursor = start; cursor < end; cursor++) {
+ unsigned char c = (unsigned char)*cursor;
+ if ((c < 0x20 && c != '\t') || c == 0x7f)
+ return false;
+ }
+ return true;
+}
+
+static bool validate_request_headers(const char *request_buffer) {
+ if (request_buffer == NULL)
+ return false;
+
+ const char *request_line_end = strstr(request_buffer, "\r\n");
+ if (request_line_end == NULL)
+ return false;
+
+ const char *cursor = request_line_end + 2;
+ int host_count = 0;
+ while (true) {
+ const char *line_end = strstr(cursor, "\r\n");
+ if (line_end == NULL)
+ return false;
+ if (line_end == cursor)
+ return host_count <= 1;
+ if (*cursor == ' ' || *cursor == '\t')
+ return false;
+
+ const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor));
+ if (colon == NULL || colon == cursor)
+ return false;
+
+ size_t name_len = (size_t)(colon - cursor);
+ if (!beaker_is_valid_http_token_span(cursor, name_len) ||
+ !is_valid_request_header_value(colon + 1, line_end)) {
+ return false;
+ }
+
+ if (name_len == strlen("Host") &&
+ strncasecmp(cursor, "Host", name_len) == 0 && ++host_count > 1) {
+ return false;
+ }
+ cursor = line_end + 2;
+ }
+}
+
const char *beaker_get_header(const char *name) {
- if (name == NULL)
- return "";
+ g_header_value[0] = '\0';
+ if (!beaker_is_valid_http_token(name))
+ return g_header_value;
+
+ size_t requested_name_len = strlen(name);
+ const char *request_line_end = strstr(current_request_buffer, "\r\n");
+ if (request_line_end == NULL)
+ return g_header_value;
+
+ const char *cursor = request_line_end + 2;
+ bool found = false;
+ while (true) {
+ const char *line_end = strstr(cursor, "\r\n");
+ if (line_end == NULL || line_end == cursor)
+ break;
+ if (*cursor == ' ' || *cursor == '\t')
+ return g_header_value;
- size_t name_len = strlen(name);
- if (name_len == 0)
- return "";
+ const char *colon = memchr(cursor, ':', (size_t)(line_end - cursor));
+ if (colon == NULL || colon == cursor)
+ return g_header_value;
- if (strstr(name, "\r\n") != NULL)
- return "";
+ size_t header_name_len = (size_t)(colon - cursor);
+ if (header_name_len == requested_name_len &&
+ strncasecmp(cursor, name, requested_name_len) == 0) {
+ if (found) {
+ beaker_log("SECURITY",
+ "beaker_get_header: Rejected duplicate header value.");
+ g_header_value[0] = '\0';
+ return g_header_value;
+ }
- char *buffer = current_request_buffer;
- char *search_end = strstr(buffer, "\r\n\r\n");
- if (search_end == NULL)
- search_end = buffer + strlen(buffer);
- while (buffer < search_end) {
- if (strncasecmp(buffer, name, name_len) == 0 && buffer[name_len] == ':') {
- char *value_start = buffer + name_len + 1;
- while (*value_start == ' ')
+ const char *value_start = colon + 1;
+ while (value_start < line_end &&
+ (*value_start == ' ' || *value_start == '\t')) {
value_start++;
+ }
+ const char *value_end = line_end;
+ while (value_end > value_start &&
+ (value_end[-1] == ' ' || value_end[-1] == '\t')) {
+ value_end--;
+ }
- char *value_end = strstr(value_start, "\r\n");
- if (value_end == NULL)
- value_end = search_end;
- size_t value_len = value_end - value_start;
- if (value_len >= sizeof(g_header_value)) {
- value_len = sizeof(g_header_value) - 1;
+ size_t value_len = (size_t)(value_end - value_start);
+ if (value_len >= sizeof(g_header_value) ||
+ !is_valid_request_header_value(value_start, value_end)) {
+ beaker_log("SECURITY",
+ "beaker_get_header: Rejected invalid header value.");
+ g_header_value[0] = '\0';
+ return g_header_value;
}
- strncpy(g_header_value, value_start, value_len);
+ memcpy(g_header_value, value_start, value_len);
g_header_value[value_len] = '\0';
- return g_header_value;
+ found = true;
}
- buffer++;
+ cursor = line_end + 2;
}
- return "";
+ return g_header_value;
}
void beaker_set_request_buffer(const char *buffer) {