Fix wssshc tunnel timeout and debug output issues

- Add periodic ping frames to keep WebSocket connections alive (15s interval)
- Implement send_ping_frame() function in websocket.c
- Fix tunnel request_id generation to ensure unique IDs for recreations
- Suppress raw data in tunnel_data debug messages, show size instead
- Sanitize both 'Received message' and 'Processing message' debug outputs
- Improve tunnel cleanup and state management
parent 293be902
/*
* WebSocket SSH Library - Tunnel management implementation
* WSSSH Library - Tunnel management implementation
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
......@@ -192,16 +192,30 @@ void remove_tunnel(const char *request_id) {
}
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug, const char *ssh_host, int ssh_port) {
char actual_request_id[37];
strcpy(actual_request_id, request_id);
pthread_mutex_lock(&tunnel_mutex);
// Check if tunnel with this request_id already exists
tunnel_t *existing_tunnel = find_tunnel_by_request_id(request_id);
tunnel_t *existing_tunnel = find_tunnel_by_request_id(actual_request_id);
if (existing_tunnel) {
if (existing_tunnel->active) {
if (debug) {
printf("[DEBUG - Tunnel] Tunnel with request_id %s already exists, ignoring duplicate request\n", request_id);
printf("[DEBUG - Tunnel] Tunnel with request_id %s already exists and is active, ignoring duplicate request\n", actual_request_id);
}
pthread_mutex_unlock(&tunnel_mutex);
return;
} else {
// Tunnel exists but is inactive - generate a new request_id for the new tunnel
if (debug) {
printf("[DEBUG - Tunnel] Tunnel with request_id %s exists but is inactive, will create new tunnel with new request_id\n", actual_request_id);
}
// Generate a new unique request_id for this new tunnel request
generate_request_id(actual_request_id, sizeof(actual_request_id));
if (debug) {
printf("[DEBUG - Tunnel] Generated new request_id %s for tunnel recreation\n", actual_request_id);
}
}
}
tunnel_t *new_tunnel = malloc(sizeof(tunnel_t));
......@@ -243,7 +257,7 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug, const ch
new_tunnel->sock = target_sock; // TCP connection to target
new_tunnel->local_sock = -1; // Not used in wssshc
strcpy(new_tunnel->request_id, request_id);
strcpy(new_tunnel->request_id, actual_request_id);
new_tunnel->active = 1;
new_tunnel->broken = 0;
new_tunnel->ssl = ssl;
......@@ -268,7 +282,7 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug, const ch
// Send tunnel_ack back to server
char ack_msg[256];
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", request_id);
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", actual_request_id);
if (debug) {
printf("[DEBUG - WebSockets] Sending tunnel_ack: %s\n", ack_msg);
......@@ -324,16 +338,30 @@ static int execute_service_command(const char *command, int debug) {
}
void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, service_config_t *service, int debug) {
char actual_request_id[37];
strcpy(actual_request_id, request_id);
pthread_mutex_lock(&tunnel_mutex);
// Check if tunnel with this request_id already exists
tunnel_t *existing_tunnel = find_tunnel_by_request_id(request_id);
tunnel_t *existing_tunnel = find_tunnel_by_request_id(actual_request_id);
if (existing_tunnel) {
if (existing_tunnel->active) {
if (debug) {
printf("[DEBUG - Tunnel] Tunnel with request_id %s already exists, ignoring duplicate request\n", request_id);
printf("[DEBUG - Tunnel] Tunnel with request_id %s already exists and is active, ignoring duplicate request\n", actual_request_id);
}
pthread_mutex_unlock(&tunnel_mutex);
return;
} else {
// Tunnel exists but is inactive - generate a new request_id for the new tunnel
if (debug) {
printf("[DEBUG - Tunnel] Tunnel with request_id %s exists but is inactive, will create new tunnel with new request_id\n", actual_request_id);
}
// Generate a new unique request_id for this new tunnel request
generate_request_id(actual_request_id, sizeof(actual_request_id));
if (debug) {
printf("[DEBUG - Tunnel] Generated new request_id %s for tunnel recreation\n", actual_request_id);
}
}
}
// Execute service command if specified
......@@ -413,7 +441,7 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic
new_tunnel->sock = target_sock; // TCP/UDP connection to target
new_tunnel->local_sock = -1; // Not used in wssshc
strcpy(new_tunnel->request_id, request_id);
strcpy(new_tunnel->request_id, actual_request_id);
new_tunnel->active = 1;
new_tunnel->broken = 0;
new_tunnel->ssl = ssl;
......@@ -438,7 +466,7 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic
// Send tunnel_ack back to server
char ack_msg[256];
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", request_id);
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", actual_request_id);
if (debug) {
printf("[DEBUG - WebSockets] Sending tunnel_ack: %s\n", ack_msg);
......@@ -489,9 +517,8 @@ void cleanup_tunnel(int debug) {
usleep(1000000); // 1 second - increased timeout for better thread cleanup
pthread_mutex_lock(&tunnel_mutex);
// Now safely clean up - don't free tunnel structures to avoid use-after-free in threads
// Just close sockets and clean up resources, tunnels will be freed when process exits
for (int i = 0; i < active_tunnels_count; i++) {
// Now safely clean up - free tunnel structures since threads should have stopped
for (int i = active_tunnels_count - 1; i >= 0; i--) {
if (active_tunnels[i]) {
if (active_tunnels[i]->sock >= 0) {
// Close the socket directly without validity checks
......@@ -523,12 +550,22 @@ void cleanup_tunnel(int debug) {
active_tunnel = NULL;
}
// Mark tunnel as cleaned up but don't free the structure
active_tunnels[i]->active = 0;
// Free the tunnel structure
free(active_tunnels[i]);
active_tunnels[i] = NULL;
// Shift remaining tunnels down
for (int j = i; j < active_tunnels_count - 1; j++) {
active_tunnels[j] = active_tunnels[j + 1];
}
active_tunnels_count--;
i++; // Adjust index since we shifted elements
}
}
// Don't reset count, just mark tunnels inactive
// Don't free the array itself, just reset count
// Reset global shutdown flag for potential restart
global_shutdown = 0;
pthread_mutex_unlock(&tunnel_mutex);
}
......@@ -857,13 +894,27 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
size_t hex_len = strlen(data_hex);
if (hex_len % 2 != 0) {
if (debug) {
printf("[DEBUG] Invalid hex data length: %zu (must be even)\n", hex_len);
printf("[DEBUG] Invalid hex data length: %zu (must be even), data_hex: %.50s...\n", hex_len, data_hex);
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
return;
}
// Additional validation: check for reasonable data size
size_t expected_data_len = hex_len / 2;
if (expected_data_len > 64 * 1024) { // 64KB limit for SSH packets
if (debug) {
printf("[DEBUG] Hex data too large: %zu bytes, truncating to 64KB\n", expected_data_len);
fflush(stdout);
}
// Truncate to reasonable size for SSH
hex_len = 2 * 64 * 1024; // 128KB hex = 64KB data
// Make sure we don't truncate in the middle of a hex byte
if (hex_len % 2 != 0) hex_len--;
expected_data_len = hex_len / 2;
}
// Decode hex data
size_t data_len = hex_len / 2;
char *data = malloc(data_len);
......@@ -879,7 +930,7 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
// Use more efficient hex decoding
for (size_t i = 0; i < data_len; i++) {
unsigned int byte_val;
if (sscanf(data_hex + i * 2, "%2x", &byte_val) != 1) {
if (i * 2 >= hex_len || sscanf(data_hex + i * 2, "%2x", &byte_val) != 1) {
if (debug) {
printf("[DEBUG] Failed to decode hex byte at position %zu\n", i * 2);
fflush(stdout);
......
/*
* WebSocket SSH Library - WebSocket functions implementation
* WSSSH Library - WebSocket functions implementation
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
......@@ -506,6 +506,92 @@ int send_pong_frame_ws(int sock, const char *ping_payload, int payload_len) {
return 1;
}
int send_ping_frame(SSL *ssl, const char *ping_payload, int payload_len) {
// Lock SSL mutex to prevent concurrent SSL operations
pthread_mutex_lock(&ssl_mutex);
char frame[BUFFER_SIZE];
frame[0] = 0x89; // FIN + ping opcode
int header_len = 2;
if (payload_len <= 125) {
frame[1] = 0x80 | payload_len; // MASK + length
} else if (payload_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (payload_len >> 8) & 0xFF;
frame[3] = payload_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (payload_len >> 24) & 0xFF;
frame[7] = (payload_len >> 16) & 0xFF;
frame[8] = (payload_len >> 8) & 0xFF;
frame[9] = payload_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < payload_len; i++) {
frame[header_len + i] = ping_payload[i] ^ mask_key[i % 4];
}
int frame_len = header_len + payload_len;
// Handle partial writes for large frames
int total_written = 0;
int retry_count = 0;
const int max_retries = 3;
while (total_written < frame_len && retry_count < max_retries) {
int to_write = frame_len - total_written;
// Limit to BUFFER_SIZE to avoid issues with very large frames
if (to_write > BUFFER_SIZE) {
to_write = BUFFER_SIZE;
}
int written = SSL_write(ssl, frame + total_written, to_write);
if (written <= 0) {
int ssl_error = SSL_get_error(ssl, written);
// Handle transient SSL errors with retry
if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) && retry_count < max_retries - 1) {
retry_count++;
usleep(10000); // Wait 10ms before retry
continue; // Retry the write operation
}
fprintf(stderr, "Ping frame SSL_write failed: %d (after %d retries)\n", ssl_error, retry_count);
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
fprintf(stderr, "SSL write error details: %s\n", error_buf);
pthread_mutex_unlock(&ssl_mutex);
return 0; // Write failed
}
total_written += written;
retry_count = 0; // Reset retry count on successful write
}
if (total_written < frame_len) {
fprintf(stderr, "Ping frame write incomplete: %d/%d bytes written\n", total_written, frame_len);
pthread_mutex_unlock(&ssl_mutex);
return 0;
}
pthread_mutex_unlock(&ssl_mutex);
return 1;
}
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len) {
// Lock SSL mutex to prevent concurrent SSL operations
pthread_mutex_lock(&ssl_mutex);
......
/*
* WebSocket SSH Client (wssshc) - C Implementation
* WSSSH Client (wssshc) - C Implementation
* WebSocket SSH client that registers with wssshd server.
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
......@@ -359,7 +359,7 @@ int execute_service_command(const char *command, int debug) {
}
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options]\n", program_name);
fprintf(stderr, "WebSocket SSH Client - Register with wssshd server\n\n");
fprintf(stderr, "WSSSH Client - Register with wssshd server\n\n");
fprintf(stderr, "Protect the dolls!\n\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " --config FILE Configuration file path (overrides default hierarchy)\n");
......@@ -764,8 +764,8 @@ int connect_to_server(const wssshc_config_t *config) {
if (bytes_read > 0) {
if (config->debug) {
printf("[DEBUG] SSL_read returned %d bytes\n", bytes_read);
printf("[DEBUG] Received data: %.100s%s\n", buffer, bytes_read > 100 ? "..." : "");
printf("[DEBUG] SSL_read returned %d bytes (raw WebSocket frame)\n", bytes_read);
// Don't print raw frame data as it contains binary headers
fflush(stdout);
}
break; // Success
......@@ -899,6 +899,10 @@ int connect_to_server(const wssshc_config_t *config) {
static char frame_buffer[BUFFER_SIZE * 4]; // Quadruple buffer size for accumulation
static int frame_buffer_used = 0;
// Keep-alive ping variables
time_t last_ping_time = time(NULL);
const int ping_interval = 15; // Send ping every 15 seconds
while (1) {
// Check for SIGINT
if (sigint_received) {
......@@ -908,6 +912,24 @@ int connect_to_server(const wssshc_config_t *config) {
break;
}
// Send keep-alive ping if needed
time_t current_time = time(NULL);
if (current_time - last_ping_time >= ping_interval) {
if (config->debug) {
printf("[DEBUG - WebSockets] Sending keep-alive ping\n");
fflush(stdout);
}
// Send ping with empty payload
if (!send_ping_frame(ssl, "", 0)) {
if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send keep-alive ping\n");
fflush(stdout);
}
} else {
last_ping_time = current_time;
}
}
// Always try to read more data if there's space, even if we have a complete frame
// This ensures we can accumulate data for very large frames
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
......@@ -1056,11 +1078,6 @@ int connect_to_server(const wssshc_config_t *config) {
}
// Just acknowledge, no response needed
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
fflush(stdout);
}
// Copy payload to buffer for processing (null-terminate for string operations)
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
......@@ -1072,9 +1089,57 @@ int connect_to_server(const wssshc_config_t *config) {
continue;
}
// Check if this is a tunnel_data message to suppress verbose logging
int is_tunnel_data = (strstr(buffer, "\"type\":\"tunnel_data\"") != NULL);
if (config->debug) {
if (is_tunnel_data) {
// Extract data size for tunnel_data messages
char *data_start = strstr(buffer, "\"data\"");
size_t data_size = 0;
if (data_start) {
char *data_colon = strchr(data_start, ':');
if (data_colon) {
char *data_quote = strchr(data_colon, '"');
if (data_quote) {
data_start = data_quote + 1;
char *data_end = strchr(data_start, '"');
if (data_end) {
data_size = data_end - data_start;
}
}
}
}
printf("[DEBUG - WebSockets] Received message: {\"type\":\"tunnel_data\", \"request_id\":\"...\", \"data\":\"<size: %zu bytes>\"}\n", data_size);
} else {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
}
fflush(stdout);
}
// Handle message
if (config->debug) {
if (is_tunnel_data) {
// Extract data size for tunnel_data messages
char *data_start = strstr(buffer, "\"data\"");
size_t data_size = 0;
if (data_start) {
char *data_colon = strchr(data_start, ':');
if (data_colon) {
char *data_quote = strchr(data_colon, '"');
if (data_quote) {
data_start = data_quote + 1;
char *data_end = strchr(data_start, '"');
if (data_end) {
data_size = data_end - data_start;
}
}
}
}
printf("[DEBUG - WebSockets] Processing message: {\"type\":\"tunnel_data\", \"request_id\":\"...\", \"data\":\"<size: %zu bytes>\"}\n", data_size);
} else {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
}
fflush(stdout);
}
......@@ -1327,7 +1392,7 @@ int main(int argc, char *argv[]) {
start_time = time(NULL);
// Print configured options
printf("WebSocket SSH Client starting...\n");
printf("WSSSH Client starting...\n");
printf("Configuration:\n");
printf(" WSSSHD Server: %s\n", config.wssshd_server ? config.wssshd_server : "(null)");
printf(" WSSSHD Port: %d\n", config.wssshd_port);
......
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