|
// retoor <retoor@molodetz.nl>
|
|
|
|
#include "client_handler.h"
|
|
#include "buffer.h"
|
|
#include "http.h"
|
|
#include "http_response.h"
|
|
#include "config.h"
|
|
#include "rate_limit.h"
|
|
#include "auth.h"
|
|
#include "monitor.h"
|
|
#include "dashboard.h"
|
|
#include "upstream.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
extern connection_t connections[MAX_FDS];
|
|
extern time_t cached_time;
|
|
|
|
static int client_has_complete_request(connection_t *conn) {
|
|
size_t available = conn->read_buf.tail - conn->read_buf.head;
|
|
if (available < 4) return 0;
|
|
|
|
char *headers_end = memmem(conn->read_buf.data + conn->read_buf.head, available, "\r\n\r\n", 4);
|
|
return headers_end != NULL;
|
|
}
|
|
|
|
int client_check_rate_limit(connection_t *conn) {
|
|
if (!rate_limit_check(conn->client_ip)) {
|
|
http_response_send_error(conn, 429, "Too Many Requests",
|
|
"429 Too Many Requests - Rate limit exceeded");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int client_check_route_auth(connection_t *conn, route_config_t *route, const char *data, size_t len) {
|
|
if (!route || !route->use_auth) return 1;
|
|
|
|
char auth_header[512] = "";
|
|
http_find_header_value(data, len, "Authorization", auth_header, sizeof(auth_header));
|
|
|
|
char error_msg[256] = "";
|
|
if (!auth_check_route_basic_auth(route, auth_header[0] ? auth_header : NULL, error_msg, sizeof(error_msg))) {
|
|
http_response_send_auth_required(conn, route->hostname);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int client_handle_internal_route(connection_t *conn, const char *data, size_t len) {
|
|
if (!http_uri_is_internal_route(conn->request.uri)) {
|
|
return 0;
|
|
}
|
|
|
|
conn->state = CLIENT_STATE_SERVING_INTERNAL;
|
|
|
|
if (strcmp(conn->request.uri, "/rproxy/dashboard") == 0) {
|
|
dashboard_serve(conn, data, len);
|
|
} else if (strcmp(conn->request.uri, "/rproxy/api/stats") == 0) {
|
|
dashboard_serve_stats_api(conn, data, len);
|
|
} else {
|
|
http_response_send_error(conn, 404, "Not Found", "404 Not Found");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void client_setup_vhost_stats(connection_t *conn) {
|
|
if (!conn->vhost_stats && conn->request.host[0] != '\0') {
|
|
conn->vhost_stats = monitor_get_or_create_vhost_stats(conn->request.host);
|
|
if (conn->vhost_stats) {
|
|
monitor_record_request_start(conn->vhost_stats, conn->request.is_websocket);
|
|
monitor_record_method(conn->vhost_stats, http_method_from_string(conn->request.method));
|
|
}
|
|
}
|
|
}
|
|
|
|
void client_handle_read(connection_t *conn) {
|
|
if (!conn || conn->state == CLIENT_STATE_CLOSING) return;
|
|
|
|
size_t available = buffer_available_write(&conn->read_buf);
|
|
if (available == 0) {
|
|
if (buffer_ensure_capacity(&conn->read_buf, conn->read_buf.capacity * 2) < 0) {
|
|
http_response_send_error(conn, 413, "Request Entity Too Large",
|
|
"413 Request Entity Too Large");
|
|
return;
|
|
}
|
|
available = buffer_available_write(&conn->read_buf);
|
|
}
|
|
|
|
ssize_t n = read(conn->fd, conn->read_buf.data + conn->read_buf.tail, available);
|
|
if (n > 0) {
|
|
conn->read_buf.tail += (size_t)n;
|
|
conn->last_activity = cached_time;
|
|
} else if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
|
|
conn->state = CLIENT_STATE_CLOSING;
|
|
return;
|
|
}
|
|
|
|
if (conn->state == CLIENT_STATE_READING_HEADERS) {
|
|
if (!client_has_complete_request(conn)) return;
|
|
|
|
size_t data_len = conn->read_buf.tail - conn->read_buf.head;
|
|
char *data = conn->read_buf.data + conn->read_buf.head;
|
|
|
|
char *headers_end = memmem(data, data_len, "\r\n\r\n", 4);
|
|
size_t request_len = headers_end ? (size_t)(headers_end - data) + 4 : data_len;
|
|
|
|
int parse_result = http_parse_request(data, data_len, &conn->request);
|
|
if (parse_result <= 0) {
|
|
http_response_send_error(conn, 400, "Bad Request", "400 Bad Request - Invalid HTTP request");
|
|
return;
|
|
}
|
|
|
|
client_setup_vhost_stats(conn);
|
|
|
|
if (!client_check_rate_limit(conn)) return;
|
|
|
|
if (client_handle_internal_route(conn, data, data_len)) {
|
|
conn->read_buf.head += request_len;
|
|
if (conn->read_buf.head >= conn->read_buf.tail) {
|
|
conn->read_buf.head = 0;
|
|
conn->read_buf.tail = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
route_config_t *route = config_find_route(conn->request.host);
|
|
if (!route) {
|
|
http_response_send_error(conn, 502, "Bad Gateway", "502 Bad Gateway - No route configured");
|
|
return;
|
|
}
|
|
|
|
if (!client_check_route_auth(conn, route, data, data_len)) return;
|
|
|
|
conn->state = CLIENT_STATE_FORWARDING;
|
|
upstream_connect(conn, data, data_len);
|
|
}
|
|
}
|