Fix web server startup and HTTP response issues

- Move web_start_server() before websocket_start_server() to ensure web server starts
- Implement full HTTP server with request parsing and response handling
- Fix HTTP redirect to properly send Location header instead of Set-Cookie
- Remove non-blocking socket setting that caused inconsistent responses
- Update shutdown sequence to stop web server before WebSocket server
parent 9e469d87
......@@ -21,6 +21,7 @@
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <time.h>
#include "config.h"
......@@ -53,18 +54,25 @@ static void *cleanup_thread(void *arg) {
websocket_cleanup_expired_clients(state);
websocket_check_keepalive_timeouts(state);
// Print status every 60 seconds
// Print status every 60 seconds (only when not in debug mode)
static time_t last_status_time = 0;
time_t current_time = time(NULL);
if (current_time - last_status_time >= 60) {
if (!state->debug && current_time - last_status_time >= 60) {
size_t active_clients = 0;
for (size_t i = 0; i < state->clients_count; i++) {
if (state->clients[i].active) active_clients++;
}
size_t active_tunnels = 0;
unsigned long long total_transfer_rate = 0;
for (size_t i = 0; i < state->tunnels_count; i++) {
if (state->tunnels[i]->status == TUNNEL_STATUS_ACTIVE) active_tunnels++;
if (state->tunnels[i]->status == TUNNEL_STATUS_ACTIVE) {
active_tunnels++;
total_transfer_rate += state->tunnels[i]->bytes_last_period;
// Reset bytes_last_period for next period
state->tunnels[i]->bytes_last_period = 0;
state->tunnels[i]->last_stats_reset = current_time;
}
}
time_t uptime = current_time - state->start_time;
......@@ -72,9 +80,25 @@ static void *cleanup_thread(void *arg) {
int minutes = (uptime % 3600) / 60;
int seconds = uptime % 60;
printf("[STATUS] Uptime: %02d:%02d:%02d | Clients: %zu/%zu active | Tunnels: %zu/%zu active\n",
hours, minutes, seconds, active_clients, state->clients_count,
active_tunnels, state->tunnels_count);
printf("[STATUS] Server uptime: %02d:%02d:%02d\n", hours, minutes, seconds);
printf("[STATUS] Connected wssshc clients (%zu):\n", active_clients);
for (size_t i = 0; i < state->clients_count; i++) {
if (state->clients[i].active) {
printf("[STATUS] - %s\n", state->clients[i].client_id);
}
}
printf("[STATUS] Active tunnels (%zu/%zu):\n", active_tunnels, state->tunnels_count);
for (size_t i = 0; i < state->tunnels_count; i++) {
if (state->tunnels[i]->status == TUNNEL_STATUS_ACTIVE) {
time_t tunnel_uptime = current_time - state->tunnels[i]->created_at;
int t_hours = tunnel_uptime / 3600;
int t_minutes = (tunnel_uptime % 3600) / 60;
int t_seconds = tunnel_uptime % 60;
printf("[STATUS] - %s: uptime %02d:%02d:%02d\n",
state->tunnels[i]->request_id, t_hours, t_minutes, t_seconds);
}
}
printf("[STATUS] Transfer rate since last statistic: %llu bytes\n", total_transfer_rate);
last_status_time = current_time;
}
......@@ -84,6 +108,9 @@ static void *cleanup_thread(void *arg) {
}
int main(int argc, char *argv[]) {
// Create new process group to avoid receiving SIGINT from terminal
setpgid(0, 0);
// Parse configuration
wssshd_config_t *config = load_config(argc, argv);
if (!config) {
......@@ -106,22 +133,24 @@ int main(int argc, char *argv[]) {
// Set up signal handlers
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGPIPE, SIG_IGN); // Ignore SIGPIPE to prevent crashes on broken connections
printf("WSSSH Daemon starting...\n");
// Start web interface if configured
if (web_start_server(config, state) != 0) {
fprintf(stderr, "Warning: Failed to start web interface\n");
}
// Start WebSocket server
if (websocket_start_server(config, state) != 0) {
fprintf(stderr, "Failed to start WebSocket server\n");
web_stop_server();
websocket_free_state(state);
free_config(config);
return 1;
}
// Start web interface if configured
if (web_start_server(config, state) != 0) {
fprintf(stderr, "Warning: Failed to start web interface\n");
}
printf("WSSSH Daemon running on %s:%d\n", config->host, config->port);
printf("Press Ctrl+C to stop the server\n");
......@@ -142,8 +171,8 @@ int main(int argc, char *argv[]) {
printf("\nShutting down WSSSH Daemon...\n");
// Stop servers
websocket_stop_server();
web_stop_server();
websocket_stop_server();
// Clean up state
websocket_free_state(state);
......
......@@ -22,27 +22,896 @@
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include "web.h"
#include "terminal.h"
#include "assets.h"
#include "websocket.h"
// Embedded web assets are defined in assets.c
static wssshd_state_t *global_state = NULL;
static const wssshd_config_t *global_config = NULL;
static int server_socket = -1;
static volatile int server_running = 0;
// Simple user management
#define MAX_USERS 100
typedef struct {
char username[50];
char password_hash[100];
int is_admin;
} web_user_t;
// HTTP server thread function (stub)
static web_user_t users[MAX_USERS];
static int user_count = 0;
// Session management
#define MAX_SESSIONS 100
typedef struct {
char session_id[33]; // UUID-like
char username[50];
time_t created_at;
time_t last_access;
} web_session_t;
static web_session_t sessions[MAX_SESSIONS];
static int session_count = 0;
static pthread_mutex_t session_mutex = PTHREAD_MUTEX_INITIALIZER;
// Active terminal sessions
#define MAX_ACTIVE_TERMINALS 100
static terminal_session_t *active_terminals[MAX_ACTIVE_TERMINALS];
static int active_terminals_count = 0;
static pthread_mutex_t terminals_mutex = PTHREAD_MUTEX_INITIALIZER;
// Simple hash function for passwords (not secure, but for demo)
static void simple_hash(const char *input, char *output, size_t output_size) {
unsigned long hash = 5381;
int c;
while ((c = *input++)) {
hash = ((hash << 5) + hash) + c;
}
snprintf(output, output_size, "%lx", hash);
}
// Initialize default users
static void init_users(void) {
if (user_count == 0) {
strcpy(users[0].username, "admin");
simple_hash("admin123", users[0].password_hash, sizeof(users[0].password_hash));
users[0].is_admin = 1;
user_count = 1;
}
}
// Generate simple session ID
static void generate_session_id(char *session_id, size_t size) {
static unsigned long counter = 0;
snprintf(session_id, size, "%lx%lx", (unsigned long)time(NULL), counter++);
}
// Find user by username
static web_user_t *find_user(const char *username) {
for (int i = 0; i < user_count; i++) {
if (strcmp(users[i].username, username) == 0) {
return &users[i];
}
}
return NULL;
}
// Create new session
static const char *create_session(const char *username) {
pthread_mutex_lock(&session_mutex);
if (session_count >= MAX_SESSIONS) {
// Remove oldest session
memmove(&sessions[0], &sessions[1], sizeof(web_session_t) * (MAX_SESSIONS - 1));
session_count--;
}
generate_session_id(sessions[session_count].session_id, sizeof(sessions[session_count].session_id));
strcpy(sessions[session_count].username, username);
sessions[session_count].created_at = time(NULL);
sessions[session_count].last_access = time(NULL);
session_count++;
pthread_mutex_unlock(&session_mutex);
return sessions[session_count - 1].session_id;
}
// Validate session
static const char *validate_session(const char *session_id) {
pthread_mutex_lock(&session_mutex);
for (int i = 0; i < session_count; i++) {
if (strcmp(sessions[i].session_id, session_id) == 0) {
// Check if session is not expired (24 hours)
if (time(NULL) - sessions[i].created_at < 86400) {
sessions[i].last_access = time(NULL);
pthread_mutex_unlock(&session_mutex);
return sessions[i].username;
} else {
// Remove expired session
memmove(&sessions[i], &sessions[i + 1], sizeof(web_session_t) * (session_count - i - 1));
session_count--;
break;
}
}
}
pthread_mutex_unlock(&session_mutex);
return NULL;
}
// Parse HTTP request
typedef struct {
char method[10];
char path[1024];
char query[1024];
char headers[4096];
char body[4096];
int content_length;
} http_request_t;
static int parse_http_request(int client_fd, http_request_t *req) {
char buffer[8192];
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (bytes_read <= 0) return -1;
buffer[bytes_read] = '\0';
// Parse request line
char *line = strtok(buffer, "\r\n");
if (!line) return -1;
sscanf(line, "%s %s", req->method, req->path);
// Parse query string
char *query = strchr(req->path, '?');
if (query) {
*query = '\0';
strcpy(req->query, query + 1);
} else {
req->query[0] = '\0';
}
// Parse headers and body
char *body_start = strstr(buffer, "\r\n\r\n");
if (body_start) {
*body_start = '\0';
body_start += 4;
strcpy(req->body, body_start);
req->content_length = strlen(req->body);
} else {
req->body[0] = '\0';
req->content_length = 0;
}
strcpy(req->headers, line + strlen(line) + 2);
return 0;
}
// URL decode
static void url_decode(char *str) {
char *src = str;
char *dst = str;
while (*src) {
if (*src == '+') {
*dst++ = ' ';
src++;
} else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2])) {
char hex[3] = {src[1], src[2], '\0'};
*dst++ = (char)strtol(hex, NULL, 16);
src += 3;
} else {
*dst++ = *src++;
}
}
*dst = '\0';
}
// Parse form data
static void parse_form_data(const char *body, char *username, char *password, size_t max_len) {
char temp[4096];
strcpy(temp, body);
char *pair = strtok(temp, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "username") == 0) {
strncpy(username, value, max_len - 1);
} else if (strcmp(key, "password") == 0) {
strncpy(password, value, max_len - 1);
}
}
pair = strtok(NULL, "&");
}
}
// Get cookie value
static const char *get_cookie(const char *headers, const char *name) {
static char value[256];
char cookie_header[1024];
const char *cookie_start = strstr(headers, "Cookie:");
if (!cookie_start) return NULL;
strcpy(cookie_header, cookie_start + 7);
char *end = strchr(cookie_header, '\r');
if (end) *end = '\0';
char *token = strtok(cookie_header, "; ");
while (token) {
char *eq = strchr(token, '=');
if (eq) {
*eq = '\0';
if (strcmp(token, name) == 0) {
strcpy(value, eq + 1);
return value;
}
}
token = strtok(NULL, "; ");
}
return NULL;
}
// Send HTTP response
static void send_response(int client_fd, int status_code, const char *status_text,
const char *content_type, const char *body, size_t body_len,
const char *set_cookie, const char *extra_header) {
char response[8192];
char date[64];
time_t now = time(NULL);
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
int len = snprintf(response, sizeof(response),
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Server: wssshd/1.0\r\n"
"Content-Type: %s\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n",
status_code, status_text, date, content_type, body_len);
if (extra_header) {
len += snprintf(response + len, sizeof(response) - len, "%s\r\n", extra_header);
}
if (set_cookie) {
len += snprintf(response + len, sizeof(response) - len, "Set-Cookie: %s\r\n", set_cookie);
}
len += snprintf(response + len, sizeof(response) - len, "\r\n");
send(client_fd, response, len, 0);
if (body && body_len > 0) {
send(client_fd, body, body_len, 0);
}
}
// Generate dynamic HTML for index page
static char *generate_index_html(const char *username, int is_admin) {
static char html[16384];
char client_list[8192] = "";
// Get connected clients
for (size_t i = 0; i < global_state->clients_count; i++) {
if (global_state->clients[i].active) {
char client_html[1024];
snprintf(client_html, sizeof(client_html),
"<div class=\"col-md-4 mb-3\">"
"<div class=\"card client-card h-100\">"
"<div class=\"card-body text-center\">"
"<i class=\"fas fa-desktop fa-3x text-success mb-3\"></i>"
"<h5 class=\"card-title\">%s</h5>"
"<p class=\"card-text text-muted\">Connected</p>"
"<a href=\"/terminal/%s\" class=\"btn btn-primary\">"
"<i class=\"fas fa-terminal\"></i> Connect</a>"
"</div></div></div>",
global_state->clients[i].client_id,
global_state->clients[i].client_id);
strcat(client_list, client_html);
}
}
if (strlen(client_list) == 0) {
strcpy(client_list,
"<div class=\"text-center py-5\">"
"<i class=\"fas fa-server fa-4x text-muted mb-3\"></i>"
"<h4 class=\"text-muted\">No clients connected</h4>"
"<p class=\"text-muted\">Clients will appear here when they connect.</p>"
"</div>");
}
char admin_actions[1024] = "";
if (is_admin) {
strcpy(admin_actions,
"<a href=\"/users\" class=\"btn btn-outline-primary btn-sm mb-2 w-100\">"
"<i class=\"fas fa-users\"></i> Manage Users</a>");
}
snprintf(html, sizeof(html),
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>Dashboard - WebSocket SSH Daemon</title>"
"<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"<link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">"
"</head>"
"<body>"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">"
"<div class=\"container\">"
"<a class=\"navbar-brand\" href=\"/\">"
"<i class=\"fas fa-terminal\"></i> WebSocket SSH Daemon</a>"
"<div class=\"navbar-nav ms-auto\">"
"<span class=\"navbar-text me-3\">Welcome, %s</span>"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>"
"</div></div></nav>"
"<div class=\"container mt-4\">"
"<div class=\"row\">"
"<div class=\"col-md-8\">"
"<div class=\"card\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-server\"></i> Connected Clients</h3></div>"
"<div class=\"card-body\"><div class=\"row\">%s</div></div></div></div>"
"<div class=\"col-md-4\">"
"<div class=\"card\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-cogs\"></i> Quick Actions</h3></div>"
"<div class=\"card-body\">%s"
"<button class=\"btn btn-outline-secondary btn-sm w-100\" onclick=\"location.reload()\">"
"<i class=\"fas fa-sync\"></i> Refresh Status</button></div></div>"
"<div class=\"card mt-3\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-info-circle\"></i> System Info</h3></div>"
"<div class=\"card-body\">"
"<p class=\"mb-1\"><strong>WebSocket Port:</strong> <span id=\"websocket-port\">%d</span></p>"
"<p class=\"mb-1\"><strong>Domain:</strong> <span id=\"domain\">%s</span></p>"
"<p class=\"mb-0\"><strong>Connected Clients:</strong> <span id=\"client-count\">%zu</span></p>"
"</div></div></div></div></div>"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>"
"</body></html>",
username, client_list, admin_actions, global_config->port,
global_config->domain ? global_config->domain : "N/A", global_state->clients_count);
return html;
}
// Handle HTTP requests
static void handle_request(int client_fd, const http_request_t *req) {
const char *session_id = get_cookie(req->headers, "session_id");
const char *username = NULL;
int is_admin = 0;
if (session_id) {
username = validate_session(session_id);
if (username) {
web_user_t *user = find_user(username);
if (user) is_admin = user->is_admin;
}
}
// Route handling
if (strcmp(req->method, "GET") == 0) {
if (strcmp(req->path, "/") == 0 || strcmp(req->path, "/index.html") == 0) {
if (!username) {
// Redirect to login
char location_header[256];
snprintf(location_header, sizeof(location_header), "Location: /login");
send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
return;
}
char *html = generate_index_html(username, is_admin);
send_response(client_fd, 200, "OK", "text/html", html, strlen(html), NULL, NULL);
} else if (strcmp(req->path, "/login") == 0) {
const char *asset = get_embedded_asset("/login", NULL);
send_response(client_fd, 200, "OK", "text/html", asset, strlen(asset), NULL, NULL);
} else if (strcmp(req->path, "/logout") == 0) {
send_response(client_fd, 302, "Found", "text/html", NULL, 0, "session_id=; Max-Age=0; Path=/", NULL);
return;
} else if (strcmp(req->path, "/users") == 0) {
if (!username || !is_admin) {
send_response(client_fd, 403, "Forbidden", "text/html", "Access denied", 13, NULL, NULL);
return;
}
const char *asset = get_embedded_asset("/users.html", NULL);
send_response(client_fd, 200, "OK", "text/html", asset, strlen(asset), NULL, NULL);
} else if (strncmp(req->path, "/terminal/", 9) == 0) {
if (!username) {
send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, NULL);
return;
}
const char *client_id = req->path + 9;
// Check if client exists
int client_exists = 0;
for (size_t i = 0; i < global_state->clients_count; i++) {
if (global_state->clients[i].active &&
strcmp(global_state->clients[i].client_id, client_id) == 0) {
client_exists = 1;
break;
}
}
if (!client_exists) {
send_response(client_fd, 404, "Not Found", "text/html", "Client not found", 15, NULL, NULL);
return;
}
// Generate terminal HTML with client_id
char html[16384];
int len = snprintf(html, sizeof(html),
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>Terminal - %s</title>"
"<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"<link rel=\"stylesheet\" href=\"https://unpkg.com/xterm@5.3.0/css/xterm.css\">"
"</head>"
"<body>"
"<div class=\"container mt-4\">"
"<div class=\"row\">"
"<div class=\"col-12\">"
"<div class=\"card\">"
"<div class=\"card-header d-flex justify-content-between align-items-center\">"
"<div class=\"d-flex align-items-center\">"
"<a href=\"/\" class=\"btn btn-outline-secondary btn-sm me-3\">"
"<i class=\"fas fa-arrow-left\"></i> Back to Dashboard</a>"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-terminal\"></i> SSH Terminal - %s</h3></div>"
"<div>"
"<input type=\"text\" id=\"sshUsername\" class=\"form-control form-control-sm d-inline-block w-auto me-2\" placeholder=\"Username\" value=\"root\">"
"<button id=\"connectBtn\" class=\"btn btn-success btn-sm\">"
"<i class=\"fas fa-play\"></i> Connect</button>"
"<button id=\"disconnectBtn\" class=\"btn btn-danger btn-sm\" disabled>"
"<i class=\"fas fa-stop\"></i> Disconnect</button>"
"</div></div>"
"<div class=\"card-body p-2\">"
"<div id=\"terminal\" class=\"terminal-container w-100\"></div>"
"</div></div></div></div></div>"
"<script src=\"https://unpkg.com/xterm@5.3.0/lib/xterm.js\"></script>"
"<script src=\"https://unpkg.com/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js\"></script>"
"<script>"
"let term = null; let fitAddon = null; let connected = false; let requestId = null; let pollInterval = null;"
"document.getElementById('connectBtn').addEventListener('click', connect);"
"document.getElementById('disconnectBtn').addEventListener('click', disconnect);"
"function connect() {"
"const username = document.getElementById('sshUsername').value;"
"if (!username) { alert('Please enter a username'); return; }"
"if (!term) {"
"term = new Terminal({cursorBlink: true, fontSize: 14, theme: {background: '#1e1e1e', foreground: '#f8f8f2'}});"
"term.open(document.getElementById('terminal'));"
"fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); fitAddon.fit();"
"window.addEventListener('resize', () => { if (fitAddon) fitAddon.fit(); });"
"term.onData(data => {"
"if (!connected || !requestId) return;"
"fetch('/terminal/%s/data', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'request_id=' + encodeURIComponent(requestId) + '&data=' + encodeURIComponent(data)"
"});"
"});"
"}"
"term.write('Connecting...\\r\\n');"
"connected = true;"
"document.getElementById('connectBtn').disabled = true;"
"document.getElementById('disconnectBtn').disabled = false;"
"document.getElementById('sshUsername').disabled = true;"
"let cols = 80, rows = 24;"
"if (fitAddon) { const d = fitAddon.proposeDimensions(); cols = d.cols || 80; rows = d.rows || 24; }"
"fetch('/terminal/%s/connect', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'username=' + encodeURIComponent(username) + '&cols=' + cols + '&rows=' + rows"
"}).then(r => r.json()).then(data => {"
"if (data.request_id) {"
"requestId = data.request_id;"
"term.write('Connected!\\r\\n');"
"pollInterval = setInterval(pollData, 100);"
"} else {"
"term.write('Error: ' + (data.error || 'Unknown') + '\\r\\n');"
"disconnect();"
"}"
"}).catch(e => { term.write('Connection failed\\r\\n'); disconnect(); });"
"}"
"function disconnect() {"
"connected = false;"
"document.getElementById('connectBtn').disabled = false;"
"document.getElementById('disconnectBtn').disabled = true;"
"document.getElementById('sshUsername').disabled = false;"
"if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }"
"if (requestId) {"
"fetch('/terminal/%s/disconnect', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'request_id=' + encodeURIComponent(requestId)"
"});"
"requestId = null;"
"}"
"if (term) term.write('\\r\\nDisconnected.\\r\\n');"
"}"
"function pollData() {"
"if (!requestId) return;"
"fetch('/terminal/%s/data?request_id=' + encodeURIComponent(requestId))"
".then(r => r.text()).then(data => {"
"if (data) term.write(data.replace(/\\n/g, '\\r\\n'));"
"});"
"}"
"</script>"
"</body></html>",
client_id, client_id, client_id, client_id, client_id, client_id);
send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
} else if (strcmp(req->path, "/api/clients") == 0) {
if (!username) {
send_response(client_fd, 403, "Forbidden", "application/json", "{\"error\":\"Not authenticated\"}", 29, NULL, NULL);
return;
}
char json[4096];
int len = snprintf(json, sizeof(json), "{\"clients\":{");
for (size_t i = 0; i < global_state->clients_count; i++) {
if (global_state->clients[i].active) {
len += snprintf(json + len, sizeof(json) - len, "\"%s\":{\"status\":\"connected\"}",
global_state->clients[i].client_id);
if (i < global_state->clients_count - 1) len += snprintf(json + len, sizeof(json) - len, ",");
}
}
len += snprintf(json + len, sizeof(json) - len, "},\"count\":%zu}", global_state->clients_count);
send_response(client_fd, 200, "OK", "application/json", json, len, NULL, NULL);
} else if (strncmp(req->path, "/terminal/", 9) == 0) {
// Extract client_id and action from path
char path_copy[1024];
strcpy(path_copy, req->path);
char *client_id = path_copy + 9;
char *action = strchr(client_id, '/');
if (action) {
*action = '\0';
action++;
}
if (!username) {
send_response(client_fd, 403, "Forbidden", "text/plain", "Not authenticated", 16, NULL, NULL);
return;
}
// Check if client exists
int client_exists = 0;
for (size_t i = 0; i < global_state->clients_count; i++) {
if (global_state->clients[i].active &&
strcmp(global_state->clients[i].client_id, client_id) == 0) {
client_exists = 1;
break;
}
}
if (!client_exists) {
send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Client not connected\"}", 31, NULL, NULL);
return;
}
if (strcmp(req->method, "POST") == 0) {
if (strcmp(action, "connect") == 0) {
// Create terminal session
char form_username[50] = "root";
int cols = 80, rows = 24; // Used in terminal_create_session call
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "username") == 0) {
strncpy(form_username, value, sizeof(form_username) - 1);
} else if (strcmp(key, "cols") == 0) {
cols = atoi(value);
} else if (strcmp(key, "rows") == 0) {
rows = atoi(value);
}
}
pair = strtok(NULL, "&");
}
terminal_session_t *session = terminal_create_session(global_config, form_username, client_id);
if (session) {
// Resize terminal to requested dimensions
terminal_resize(session, cols, rows);
pthread_mutex_lock(&terminals_mutex);
if (active_terminals_count < MAX_ACTIVE_TERMINALS) {
active_terminals[active_terminals_count++] = session;
char json[256];
int len = snprintf(json, sizeof(json), "{\"request_id\":\"%s\",\"command\":\"%s\"}",
session->request_id, session->command);
pthread_mutex_unlock(&terminals_mutex);
send_response(client_fd, 200, "OK", "application/json", json, len, NULL, NULL);
} else {
terminal_free_session(session);
pthread_mutex_unlock(&terminals_mutex);
send_response(client_fd, 500, "Internal Server Error", "application/json",
"{\"error\":\"Too many active terminals\"}", 36, NULL, NULL);
}
} else {
send_response(client_fd, 500, "Internal Server Error", "application/json",
"{\"error\":\"Failed to create terminal session\"}", 45, NULL, NULL);
}
} else if (strcmp(action, "data") == 0) {
char request_id[37] = "";
char data[4096] = "";
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "request_id") == 0) {
strncpy(request_id, value, sizeof(request_id) - 1);
} else if (strcmp(key, "data") == 0) {
strcpy(data, value);
}
}
pair = strtok(NULL, "&");
}
pthread_mutex_lock(&terminals_mutex);
int found = 0;
for (int i = 0; i < active_terminals_count; i++) {
if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
if (terminal_send_data(active_terminals[i], data)) {
found = 1;
}
break;
}
}
pthread_mutex_unlock(&terminals_mutex);
if (found) {
send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
} else {
send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Terminal session not found\"}", 37, NULL, NULL);
}
} else if (strcmp(action, "disconnect") == 0) {
char request_id[37] = "";
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "request_id") == 0) {
strncpy(request_id, value, sizeof(request_id) - 1);
}
}
pair = strtok(NULL, "&");
}
pthread_mutex_lock(&terminals_mutex);
for (int i = 0; i < active_terminals_count; i++) {
if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
terminal_disconnect(active_terminals[i]);
terminal_free_session(active_terminals[i]);
memmove(&active_terminals[i], &active_terminals[i + 1],
sizeof(terminal_session_t *) * (active_terminals_count - i - 1));
active_terminals_count--;
break;
}
}
pthread_mutex_unlock(&terminals_mutex);
send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
} else if (strcmp(action, "resize") == 0) {
char request_id[37] = "";
int cols = 80, rows = 24;
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "request_id") == 0) {
strncpy(request_id, value, sizeof(request_id) - 1);
} else if (strcmp(key, "cols") == 0) {
cols = atoi(value);
} else if (strcmp(key, "rows") == 0) {
rows = atoi(value);
}
}
pair = strtok(NULL, "&");
}
pthread_mutex_lock(&terminals_mutex);
int found = 0;
for (int i = 0; i < active_terminals_count; i++) {
if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
terminal_resize(active_terminals[i], cols, rows);
found = 1;
break;
}
}
pthread_mutex_unlock(&terminals_mutex);
if (found) {
send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
} else {
send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Terminal session not found\"}", 37, NULL, NULL);
}
} else {
send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
}
} else if (strcmp(req->method, "GET") == 0) {
if (strcmp(action, "data") == 0) {
char request_id[37] = "";
const char *query = req->query;
if (query) {
char query_copy[1024];
strcpy(query_copy, query);
char *token = strtok(query_copy, "&");
while (token) {
char *eq = strchr(token, '=');
if (eq) {
*eq = '\0';
if (strcmp(token, "request_id") == 0) {
strcpy(request_id, eq + 1);
}
}
token = strtok(NULL, "&");
}
}
pthread_mutex_lock(&terminals_mutex);
char *output = NULL;
for (int i = 0; i < active_terminals_count; i++) {
if (strcmp(active_terminals[i]->request_id, request_id) == 0) {
output = terminal_get_output(active_terminals[i]);
break;
}
}
pthread_mutex_unlock(&terminals_mutex);
if (output) {
send_response(client_fd, 200, "OK", "text/plain", output, strlen(output), NULL, NULL);
free(output);
} else {
send_response(client_fd, 200, "OK", "text/plain", "", 0, NULL, NULL);
}
} else {
send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
}
} else {
send_response(client_fd, 405, "Method Not Allowed", "text/plain", "Method not allowed", 18, NULL, NULL);
}
} else {
// Try to serve embedded asset
size_t size;
const char *asset = get_embedded_asset(req->path, &size);
if (asset) {
const char *content_type = "text/plain";
if (strstr(req->path, ".html")) content_type = "text/html";
else if (strstr(req->path, ".css")) content_type = "text/css";
else if (strstr(req->path, ".js")) content_type = "application/javascript";
else if (strstr(req->path, ".jpg") || strstr(req->path, ".jpeg")) content_type = "image/jpeg";
else if (strstr(req->path, ".png")) content_type = "image/png";
send_response(client_fd, 200, "OK", content_type, asset, size, NULL, NULL);
} else {
send_response(client_fd, 404, "Not Found", "text/html", "Not found", 9, NULL, NULL);
}
}
} else if (strcmp(req->method, "POST") == 0) {
if (strcmp(req->path, "/login") == 0) {
char form_username[50] = "";
char form_password[50] = "";
parse_form_data(req->body, form_username, form_password, sizeof(form_username));
web_user_t *user = find_user(form_username);
if (user) {
char hashed[100];
simple_hash(form_password, hashed, sizeof(hashed));
if (strcmp(user->password_hash, hashed) == 0) {
const char *new_session = create_session(form_username);
char cookie[256];
snprintf(cookie, sizeof(cookie), "session_id=%s; Path=/; HttpOnly", new_session);
send_response(client_fd, 302, "Found", "text/html", NULL, 0, cookie, NULL);
return;
}
}
const char *asset = get_embedded_asset("/login", NULL);
send_response(client_fd, 200, "OK", "text/html", asset, strlen(asset), NULL, NULL);
} else {
send_response(client_fd, 405, "Method Not Allowed", "text/html", "Method not allowed", 18, NULL, NULL);
}
} else {
send_response(client_fd, 405, "Method Not Allowed", "text/html", "Method not allowed", 18, NULL, NULL);
}
}
// HTTP server thread function
static void *http_server_thread(void *arg __attribute__((unused))) {
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0) {
perror("Failed to create server socket");
return NULL;
}
// Set socket options
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(global_config->web_host);
server_addr.sin_port = htons(global_config->web_port);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Failed to bind server socket");
close(server_socket);
return NULL;
}
if (listen(server_socket, 10) < 0) {
perror("Failed to listen on server socket");
close(server_socket);
return NULL;
}
printf("Web interface starting on %s:%d\n", global_config->web_host, global_config->web_port);
server_running = 1;
while (server_running) {
int client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
if (server_running) perror("Failed to accept client connection");
continue;
}
// TODO: Implement actual HTTP server using libmongoose, civetweb, or similar
// For now, just sleep
while (1) {
sleep(1);
// In a real implementation, this would handle HTTP requests
http_request_t req;
if (parse_http_request(client_fd, &req) == 0) {
handle_request(client_fd, &req);
}
close(client_fd);
}
close(server_socket);
server_socket = -1;
return NULL;
}
......@@ -54,6 +923,8 @@ int web_start_server(const wssshd_config_t *config, wssshd_state_t *state) {
global_config = config;
global_state = state;
init_users();
// Start HTTP server thread
pthread_t thread;
if (pthread_create(&thread, NULL, http_server_thread, NULL) != 0) {
......@@ -66,6 +937,10 @@ int web_start_server(const wssshd_config_t *config, wssshd_state_t *state) {
}
void web_stop_server(void) {
// TODO: Implement server shutdown
server_running = 0;
if (server_socket >= 0) {
close(server_socket);
server_socket = -1;
}
printf("Web interface stopping\n");
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment