Fix concurrent connection issues in wsssht daemon mode

- Implement thread-based concurrent connection handling
- Fix segmentation fault caused by shared static variables
- Each connection now gets its own WebSocket tunnel and resources
- Replace global tunnel management with per-thread instances
- Add proper cleanup for individual connection threads
- Allow multiple SSH sessions to connect simultaneously to the same port
parent 91fe8573
...@@ -607,292 +607,319 @@ int run_script_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -607,292 +607,319 @@ int run_script_mode(wsssh_config_t *config, const char *client_id, const char *w
return 0; return 0;
} }
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) { typedef struct {
// Daemon mode: Lazy tunnel establishment - bind port immediately, establish tunnel on connection wsssh_config_t *config;
const char *client_id;
const char *wssshd_host;
int wssshd_port;
int accepted_sock;
int local_port;
} connection_handler_args_t;
// Connection handler function for each accepted connection
void *handle_connection(void *arg) {
connection_handler_args_t *args = (connection_handler_args_t *)arg;
wsssh_config_t *config = args->config;
const char *client_id = args->client_id;
const char *wssshd_host = args->wssshd_host;
int wssshd_port = args->wssshd_port;
int accepted_sock = args->accepted_sock;
int local_port = args->local_port;
// Free the args structure
free(args);
if (config->debug) { if (config->debug) {
printf("[DEBUG] Starting daemon mode with lazy tunnel establishment\n"); printf("[DEBUG] [Thread %ld] Handling connection on port %d\n", pthread_self(), local_port);
fflush(stdout); fflush(stdout);
} }
// Find available local port // Create a new tunnel instance for this connection
int local_port = config->local_port ? atoi(config->local_port) : find_available_port(); tunnel_t *new_tunnel = malloc(sizeof(tunnel_t));
if (local_port == 0) { if (!new_tunnel) {
fprintf(stderr, "Error: Could not find available local port\n"); perror("Memory allocation failed for tunnel");
return 1; close(accepted_sock);
return NULL;
} }
if (config->debug) { // Initialize tunnel structure
printf("[DEBUG] Using local port: %d\n", local_port); generate_request_id(new_tunnel->request_id, sizeof(new_tunnel->request_id));
fflush(stdout); new_tunnel->sock = -1; // wsssh doesn't connect to remote server
new_tunnel->local_sock = accepted_sock;
new_tunnel->active = 1;
new_tunnel->broken = 0;
new_tunnel->ssl = NULL;
new_tunnel->outgoing_buffer = NULL;
new_tunnel->incoming_buffer = frame_buffer_init();
new_tunnel->server_version_sent = 0;
if (!new_tunnel->incoming_buffer) {
perror("Failed to initialize incoming buffer");
free(new_tunnel);
close(accepted_sock);
return NULL;
} }
// Create listening socket // Establish WebSocket connection for this tunnel
int listen_sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr;
if (listen_sock < 0) { struct hostent *he;
perror("Local socket creation failed"); int ws_sock;
return 1; SSL_CTX *ssl_ctx;
SSL *ws_ssl;
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
return NULL;
} }
struct sockaddr_in local_addr; // Create socket
memset(&local_addr, 0, sizeof(local_addr)); if ((ws_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
local_addr.sin_family = AF_INET; perror("WebSocket socket creation failed");
local_addr.sin_port = htons(local_port); frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
// Use specified tunnel_host or default to 127.0.0.1 close(accepted_sock);
if (config->tunnel_host && strcmp(config->tunnel_host, "127.0.0.1") != 0) { return NULL;
struct hostent *tunnel_he;
if ((tunnel_he = gethostbyname(config->tunnel_host)) == NULL) {
if (config->debug) {
fprintf(stderr, "[DEBUG] Failed to resolve tunnel_host '%s', using 127.0.0.1\n", config->tunnel_host);
}
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
} else {
local_addr.sin_addr = *((struct in_addr *)tunnel_he->h_addr);
if (config->debug) {
printf("[DEBUG] Binding tunnel to %s:%d\n", config->tunnel_host, local_port);
fflush(stdout);
}
}
} else {
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (config->debug) {
printf("[DEBUG] Binding tunnel to 127.0.0.1:%d\n", local_port);
fflush(stdout);
}
} }
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) { memset(&server_addr, 0, sizeof(server_addr));
perror("Local bind failed"); server_addr.sin_family = AF_INET;
close(listen_sock); server_addr.sin_port = htons(wssshd_port);
return 1; server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(ws_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("WebSocket connection failed");
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
} }
if (listen(listen_sock, 1) < 0) { // Create SSL context and connection
perror("Local listen failed"); ssl_ctx = create_ssl_context();
close(listen_sock); if (!ssl_ctx) {
return 1; frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
} }
if (config->mode != MODE_SILENT) { ws_ssl = create_ssl_connection(ssl_ctx, ws_sock, config->debug);
printf("Daemon mode: Listening on port %d, waiting for connection...\n", local_port); if (!ws_ssl) {
printf("Tunnel will be established on first connection attempt.\n"); SSL_CTX_free(ssl_ctx);
fflush(stdout); frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
} }
// Daemon mode main loop - accept connections and establish tunnels // Perform WebSocket handshake
while (1) { if (!websocket_handshake(ws_ssl, wssshd_host, wssshd_port, "/", config->debug)) {
// Check for SIGINT fprintf(stderr, "WebSocket handshake failed\n");
if (sigint_received) { SSL_free(ws_ssl);
if (config->debug) { SSL_CTX_free(ssl_ctx);
printf("[DEBUG] SIGINT received in daemon mode, exiting\n"); frame_buffer_free(new_tunnel->incoming_buffer);
fflush(stdout); free(new_tunnel);
} close(accepted_sock);
break; close(ws_sock);
} return NULL;
}
// Wait for connection with timeout
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(listen_sock, &readfds);
tv.tv_sec = 1; // 1 second timeout to check for signals
tv.tv_usec = 0;
int retval = select(listen_sock + 1, &readfds, NULL, NULL, &tv);
if (retval < 0) {
if (errno == EINTR) {
// Interrupted by signal, continue to check sigint_received
continue;
}
perror("Select failed in daemon mode");
// In daemon mode, don't exit on select errors, wait a bit and retry
sleep(1);
continue;
} else if (retval == 0) {
// Timeout, continue loop to check signals
continue;
}
// Accept connection // Send tunnel request
struct sockaddr_in client_addr; char tunnel_request_msg[1024];
socklen_t client_len = sizeof(client_addr); char *expanded_tunnel = expand_transport_list("any", 0);
int accepted_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len); char *expanded_tunnel_control = expand_transport_list("any", 1);
if (accepted_sock < 0) { char *best_tunnel = select_best_transport(expanded_tunnel);
if (errno == EINTR) { char *best_tunnel_control = select_best_transport(expanded_tunnel_control);
continue;
} snprintf(tunnel_request_msg, sizeof(tunnel_request_msg),
perror("Local accept failed"); "{\"type\":\"tunnel_request\",\"client_id\":\"%s\",\"request_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"service\":\"ssh\"}",
continue; // Don't exit on accept failure, keep listening client_id, new_tunnel->request_id,
} best_tunnel ? best_tunnel : expanded_tunnel,
best_tunnel_control ? best_tunnel_control : expanded_tunnel_control);
if (!send_websocket_frame(ws_ssl, tunnel_request_msg)) {
free(expanded_tunnel);
free(expanded_tunnel_control);
if (best_tunnel) free(best_tunnel);
if (best_tunnel_control) free(best_tunnel_control);
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
if (config->debug) { free(expanded_tunnel);
printf("[DEBUG] Connection received on port %d, establishing tunnel...\n", local_port); free(expanded_tunnel_control);
fflush(stdout); if (best_tunnel) free(best_tunnel);
} if (best_tunnel_control) free(best_tunnel_control);
// Read acknowledgment
char ack_buffer[BUFFER_SIZE];
int ack_bytes_read = SSL_read(ws_ssl, ack_buffer, sizeof(ack_buffer));
if (ack_bytes_read <= 0) {
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Now establish the tunnel for this connection // Parse WebSocket frame
// In daemon mode, use port 0 to let setup_tunnel find an available port for WebSocket setup, char *payload;
// but we'll use our already accepted socket for the local connection int payload_len;
int tunnel_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, 0, config->debug, 0, config->tunnel_host); if (!parse_websocket_frame(ack_buffer, ack_bytes_read, &payload, &payload_len)) {
if (tunnel_sock < 0) { SSL_free(ws_ssl);
fprintf(stderr, "Failed to establish tunnel for connection\n"); SSL_CTX_free(ssl_ctx);
close(accepted_sock); frame_buffer_free(new_tunnel->incoming_buffer);
continue; // Don't exit, keep listening for new connections free(new_tunnel);
} close(accepted_sock);
// Close the dummy listening socket created by setup_tunnel close(ws_sock);
close(tunnel_sock); return NULL;
}
// Print tunnel information (unless silent mode) // Check for tunnel acknowledgment
if (config->mode != MODE_SILENT) { payload[payload_len] = '\0';
printf("\n"); if (strstr(payload, "tunnel_ack") == NULL) {
printf("========================================\n"); fprintf(stderr, "Tunnel request denied: %s\n", payload);
printf(" WEBSSH TUNNEL READY\n"); SSL_free(ws_ssl);
printf("========================================\n"); SSL_CTX_free(ssl_ctx);
printf("Tunnel established successfully!\n"); frame_buffer_free(new_tunnel->incoming_buffer);
printf("Local port: %d\n", local_port); free(new_tunnel);
printf("Target: %s@%s\n", client_id, wssshd_host); close(accepted_sock);
printf("\n"); close(ws_sock);
printf("Connect manually using one of these commands:\n"); return NULL;
printf("\n");
printf(" Telnet:\n");
printf(" telnet localhost %d\n", local_port);
printf("\n");
printf(" Netcat:\n");
printf(" nc localhost %d\n", local_port);
printf("\n");
printf(" SSH (if connecting to SSH server):\n");
printf(" ssh -p %d user@localhost\n", local_port);
printf("\n");
printf(" SCP (if connecting to SSH server):\n");
printf(" scp -P %d user@localhost:/remote/path ./local/path\n", local_port);
printf("\n");
printf(" Any TCP client:\n");
printf(" Connect to localhost:%d\n", local_port);
printf("\n");
printf("Press Ctrl+C to close the tunnel and exit.\n");
printf("========================================\n");
printf("\n");
} }
// Continue with normal tunnel operation // Success - set up the tunnel
// Set the accepted socket with mutex protection new_tunnel->ssl = ws_ssl;
SSL_CTX_free(ssl_ctx); // SSL context no longer needed
// Add tunnel to the global array
pthread_mutex_lock(&tunnel_mutex); pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock; if (!add_tunnel(new_tunnel)) {
SSL_free(ws_ssl);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
pthread_mutex_unlock(&tunnel_mutex);
close(accepted_sock);
close(ws_sock);
return NULL;
}
pthread_mutex_unlock(&tunnel_mutex);
// Send any buffered data to the client immediately // Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) { if (new_tunnel->incoming_buffer && new_tunnel->incoming_buffer->used > 0) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used); printf("[DEBUG - Tunnel] [Thread %ld] Sending %zu bytes of buffered server response to client\n", pthread_self(), new_tunnel->incoming_buffer->used);
fflush(stdout); fflush(stdout);
} }
ssize_t sent = send(accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0); ssize_t sent = send(accepted_sock, new_tunnel->incoming_buffer->buffer, new_tunnel->incoming_buffer->used, 0);
if (sent > 0) { if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent); frame_buffer_consume(new_tunnel->incoming_buffer, sent);
if (config->debug) { if (config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent); printf("[DEBUG] [Thread %ld] Sent %zd bytes of buffered server response to client\n", pthread_self(), sent);
fflush(stdout); fflush(stdout);
} }
} }
} }
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n"); printf("[DEBUG - Tunnel] [Thread %ld] Local connection accepted! Starting data forwarding...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Get initial SSL connection for thread // Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex); SSL *current_ssl = new_tunnel->ssl;
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
// Start forwarding thread // Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t)); thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) { if (!thread_args) {
perror("Memory allocation failed for thread args"); perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock); close(new_tunnel->local_sock);
free(active_tunnel); frame_buffer_free(new_tunnel->incoming_buffer);
active_tunnel = NULL; free(new_tunnel);
pthread_mutex_destroy(&tunnel_mutex); return NULL;
return 1;
} }
thread_args->ssl = current_ssl; thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel; thread_args->tunnel = new_tunnel;
thread_args->debug = config->debug; thread_args->debug = config->debug;
pthread_t thread; pthread_t forward_thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args); pthread_create(&forward_thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread); pthread_detach(forward_thread);
// Main tunnel loop - handle WebSocket messages // Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE]; char ws_buffer[BUFFER_SIZE];
int bytes_read; int ws_bytes_read;
fd_set tunnel_readfds; fd_set tunnel_readfds;
struct timeval tunnel_tv; struct timeval tunnel_tv;
int tunnel_broken = 0;
// Frame accumulation buffer for handling partial WebSocket frames // Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4]; char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0; int frame_buffer_used = 0;
while (1) { while (1) {
// Get SSL fd with mutex protection // Check if tunnel is still active (no mutex needed since this is our private tunnel)
pthread_mutex_lock(&tunnel_mutex); if (!new_tunnel || !new_tunnel->active) {
if (!active_tunnel || !active_tunnel->active) { if (new_tunnel && new_tunnel->broken) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit; goto cleanup_and_exit;
} else { } else {
// normal closure // normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n"); printf("[DEBUG - Tunnel] [Thread %ld] Tunnel is no longer active, exiting main loop\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
break; break;
} }
} }
int ssl_fd = SSL_get_fd(active_tunnel->ssl); int ssl_fd = SSL_get_fd(new_tunnel->ssl);
current_ssl = active_tunnel->ssl; current_ssl = new_tunnel->ssl;
// Check if local socket is still valid // Check if local socket is still valid
if (active_tunnel->local_sock < 0) { if (new_tunnel->local_sock < 0) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n"); printf("[DEBUG - Tunnel] [Thread %ld] Local socket is invalid, tunnel broken\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
active_tunnel->broken = 1; new_tunnel->broken = 1;
// Send tunnel_close notification // Send tunnel_close notification
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n"); printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to invalid local socket...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug); send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit; goto cleanup_and_exit;
} }
// Check if the local socket connection is broken // Check if the local socket connection is broken
char test_buf[1]; char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT); int result = recv(new_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) { if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno); printf("[DEBUG - Tunnel] [Thread %ld] Local socket connection is broken (errno=%d), sending tunnel_close\n", pthread_self(), errno);
fflush(stdout); fflush(stdout);
} }
active_tunnel->broken = 1; new_tunnel->broken = 1;
// Send tunnel_close notification // Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug); send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
goto cleanup_and_exit; goto cleanup_and_exit;
} }
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout // Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds); FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds); FD_SET(ssl_fd, &tunnel_readfds);
...@@ -908,24 +935,21 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -908,24 +935,21 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
// Send tunnel_close notification // Send tunnel_close notification
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n"); printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to select failure...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug); send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit; goto cleanup_and_exit;
} else if (retval == 0) { } else if (retval == 0) {
// Timeout, check if tunnel became inactive // Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex); if (!new_tunnel || !new_tunnel->active) {
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n"); printf("[DEBUG - Tunnel] [Thread %ld] Tunnel became inactive during timeout, exiting\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
goto cleanup_and_exit; goto cleanup_and_exit;
} }
pthread_mutex_unlock(&tunnel_mutex);
continue; continue;
} }
...@@ -935,10 +959,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -935,10 +959,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
// Validate SSL connection state // Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) { if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n"); printf("[DEBUG - WebSockets] [Thread %ld] SSL connection has received shutdown\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
cleanup_tunnel(config->debug); new_tunnel->active = 0;
break; break;
} }
...@@ -958,29 +982,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -958,29 +982,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
perror("[DEBUG - WebSockets] select failed"); perror("[DEBUG - WebSockets] select failed");
fflush(stdout); fflush(stdout);
} }
cleanup_tunnel(config->debug); new_tunnel->active = 0;
break; break;
} else if (select_result == 0) { } else if (select_result == 0) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n"); printf("[DEBUG - WebSockets] [Thread %ld] SSL read timeout\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
continue; continue;
} }
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used); ws_bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) { if (ws_bytes_read <= 0) {
if (bytes_read < 0) { if (ws_bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read); int ssl_error = SSL_get_error(current_ssl, ws_bytes_read);
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error); printf("[DEBUG - WebSockets] [Thread %ld] SSL read error: %d\n", pthread_self(), ssl_error);
fflush(stdout); fflush(stdout);
} }
// Handle transient SSL errors // Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) { if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n"); printf("[DEBUG - WebSockets] [Thread %ld] Transient SSL error, retrying...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
usleep(10000); usleep(10000);
...@@ -991,19 +1015,19 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -991,19 +1015,19 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
char error_buf[256]; char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf)); ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf); printf("[DEBUG - WebSockets] [Thread %ld] SSL error details: %s\n", pthread_self(), error_buf);
fflush(stdout); fflush(stdout);
} }
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf); fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else { } else {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n"); printf("[DEBUG - WebSockets] [Thread %ld] Connection closed by server (EOF)\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
} }
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n"); printf("[DEBUG - WebSockets] [Thread %ld] WebSocket connection lost, attempting reconnection...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
...@@ -1014,32 +1038,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1014,32 +1038,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
while (reconnect_attempts < max_reconnect_attempts && !reconnected) { while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts); printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection attempt %d/%d\n", pthread_self(), reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout); fflush(stdout);
} }
pthread_mutex_lock(&tunnel_mutex); if (!new_tunnel) {
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break; break;
} }
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config->debug) == 0) { if (reconnect_websocket(new_tunnel, wssshd_host, wssshd_port, client_id, new_tunnel->request_id, config->debug) == 0) {
reconnected = 1; reconnected = 1;
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n"); printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection successful, continuing tunnel\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Update ssl_fd for select // Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl); ssl_fd = SSL_get_fd(new_tunnel->ssl);
current_ssl = active_tunnel->ssl; current_ssl = new_tunnel->ssl;
} }
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) { if (!reconnected) {
reconnect_attempts++; reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) { if (reconnect_attempts < max_reconnect_attempts) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n"); printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection failed, waiting 1 second...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
sleep(1); sleep(1);
...@@ -1049,16 +1070,16 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1049,16 +1070,16 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
if (!reconnected) { if (!reconnected) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n"); printf("[DEBUG - WebSockets] [Thread %ld] All reconnection attempts failed, exiting\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Send tunnel_close notification // Send tunnel_close notification
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n"); printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to connection failure...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug); send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit; goto cleanup_and_exit;
} }
...@@ -1066,10 +1087,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1066,10 +1087,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
continue; continue;
} }
frame_buffer_used += bytes_read; frame_buffer_used += ws_bytes_read;
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Accumulated %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", frame_buffer_used, frame_buffer[0], frame_buffer[1], frame_buffer[2], frame_buffer[3]); printf("[DEBUG - WebSockets] [Thread %ld] Accumulated %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", pthread_self(), frame_buffer_used, frame_buffer[0], frame_buffer[1], frame_buffer[2], frame_buffer[3]);
fflush(stdout); fflush(stdout);
} }
} }
...@@ -1083,39 +1104,39 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1083,39 +1104,39 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
if (frame_type == 0x88) { // Close frame if (frame_type == 0x88) { // Close frame
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n"); printf("[DEBUG - WebSockets] [Thread %ld] Received close frame from server\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Send tunnel_close notification // Send tunnel_close notification
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n"); printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to server close frame...\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug); send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit; goto cleanup_and_exit;
} else if (frame_type == 0x89) { // Ping frame } else if (frame_type == 0x89) { // Ping frame
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n"); printf("[DEBUG - WebSockets] [Thread %ld] Received ping frame, sending pong\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Send pong // Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) { if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n"); printf("[DEBUG - WebSockets] [Thread %ld] Failed to send pong frame\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
} }
} else if (frame_type == 0x8A) { // Pong frame } else if (frame_type == 0x8A) { // Pong frame
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n"); printf("[DEBUG - WebSockets] [Thread %ld] Received pong frame\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame } else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer // Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) { if ((size_t)payload_len < sizeof(ws_buffer)) {
memcpy(buffer, payload, payload_len); memcpy(ws_buffer, payload, payload_len);
buffer[payload_len] = '\0'; ws_buffer[payload_len] = '\0';
} else { } else {
fprintf(stderr, "Payload too large for processing buffer\n"); fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0; frame_buffer_used = 0;
...@@ -1123,32 +1144,32 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1123,32 +1144,32 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
} }
// Check if this is a data message to suppress verbose logging // Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\": \"tunnel_data\"") != NULL || int is_data_message = (strstr(ws_buffer, "\"type\": \"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\": \"tunnel_response\"") != NULL); strstr(ws_buffer, "\"type\": \"tunnel_response\"") != NULL);
if (config->debug && !is_data_message) { if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload); printf("[DEBUG - WebSockets] [Thread %ld] Received message: %.*s\n", pthread_self(), payload_len, payload);
fflush(stdout); fflush(stdout);
} }
// Handle message // Handle message
if (config->debug && !is_data_message) { if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer); printf("[DEBUG - WebSockets] [Thread %ld] Processing message: %s\n", pthread_self(), ws_buffer);
fflush(stdout); fflush(stdout);
} }
// Handle tunnel messages // Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) { if (strstr(ws_buffer, "tunnel_data") || strstr(ws_buffer, "tunnel_response")) {
if (config->debug) { if (config->debug) {
// Suppress tunnel_data debug messages in debug mode // Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) { if (!strstr(ws_buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n"); printf("[DEBUG - Tunnel] [Thread %ld] Received tunnel_response message\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
} }
// Extract request_id and data // Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\""); char *id_start = strstr(ws_buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\""); char *data_start = strstr(ws_buffer, "\"data\"");
if (id_start && data_start) { if (id_start && data_start) {
char *colon = strchr(id_start, ':'); char *colon = strchr(id_start, ':');
if (colon) { if (colon) {
...@@ -1174,12 +1195,12 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1174,12 +1195,12 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
} }
} }
} }
} else if (strstr(buffer, "tunnel_close")) { } else if (strstr(ws_buffer, "tunnel_close")) {
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n"); printf("[DEBUG - Tunnel] [Thread %ld] Received tunnel_close message\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
char *id_start = strstr(buffer, "\"request_id\""); char *id_start = strstr(ws_buffer, "\"request_id\"");
if (id_start) { if (id_start) {
char *colon = strchr(id_start, ':'); char *colon = strchr(id_start, ':');
if (colon) { if (colon) {
...@@ -1196,7 +1217,7 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1196,7 +1217,7 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
} }
} else { } else {
if (config->debug) { if (config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer); printf("[DEBUG - WebSockets] [Thread %ld] Received unknown message type: %s\n", pthread_self(), ws_buffer);
fflush(stdout); fflush(stdout);
} }
} }
...@@ -1220,42 +1241,189 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w ...@@ -1220,42 +1241,189 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
cleanup_and_exit: cleanup_and_exit:
// Cleanup section // Cleanup section
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n"); printf("[DEBUG - Tunnel] [Thread %ld] Performing cleanup and exiting\n", pthread_self());
fflush(stdout); fflush(stdout);
} }
// Cleanup // Cleanup
if (active_tunnel) { if (new_tunnel) {
if (active_tunnel->local_sock >= 0) { if (new_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock); close(new_tunnel->local_sock);
} }
if (active_tunnel->ssl) { if (new_tunnel->ssl) {
SSL_free(active_tunnel->ssl); SSL_free(new_tunnel->ssl);
} }
free(active_tunnel); // Remove tunnel from global array
active_tunnel = NULL; pthread_mutex_lock(&tunnel_mutex);
remove_tunnel(new_tunnel->request_id);
pthread_mutex_unlock(&tunnel_mutex);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
} }
free(config->local_port); if (config->debug) {
free(config->tunnel); printf("[DEBUG - Tunnel] [Thread %ld] Cleanup complete, thread exiting\n", pthread_self());
free(config->tunnel_control); fflush(stdout);
free(config->service); }
// Set to NULL to prevent double-free in main()
config->local_port = NULL; return NULL;
config->tunnel = NULL; }
config->tunnel_control = NULL;
config->service = NULL;
// Note: config_domain, config_clientid, etc. are freed in main()
pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex);
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Daemon mode: Lazy tunnel establishment - bind port immediately, establish tunnel on connection
if (config->debug) { if (config->debug) {
printf("[DEBUG - Tunnel] Cleanup complete, exiting with code %d\n", tunnel_broken ? 1 : 0); printf("[DEBUG] Starting daemon mode with lazy tunnel establishment\n");
fflush(stdout);
}
// Find available local port
int local_port = config->local_port ? atoi(config->local_port) : find_available_port();
if (local_port == 0) {
fprintf(stderr, "Error: Could not find available local port\n");
return 1;
}
if (config->debug) {
printf("[DEBUG] Using local port: %d\n", local_port);
fflush(stdout);
}
// Create listening socket
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
return 1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
// Use specified tunnel_host or default to 127.0.0.1
if (config->tunnel_host && strcmp(config->tunnel_host, "127.0.0.1") != 0) {
struct hostent *tunnel_he;
if ((tunnel_he = gethostbyname(config->tunnel_host)) == NULL) {
if (config->debug) {
fprintf(stderr, "[DEBUG] Failed to resolve tunnel_host '%s', using 127.0.0.1\n", config->tunnel_host);
}
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
} else {
local_addr.sin_addr = *((struct in_addr *)tunnel_he->h_addr);
if (config->debug) {
printf("[DEBUG] Binding tunnel to %s:%d\n", config->tunnel_host, local_port);
fflush(stdout);
}
}
} else {
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (config->debug) {
printf("[DEBUG] Binding tunnel to 127.0.0.1:%d\n", local_port);
fflush(stdout);
}
}
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
return 1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
return 1;
}
if (config->mode != MODE_SILENT) {
printf("Daemon mode: Listening on port %d, waiting for connection...\n", local_port);
printf("Tunnel will be established on first connection attempt.\n");
fflush(stdout); fflush(stdout);
} }
// In daemon mode, continue to accept new connections // Daemon mode main loop - accept connections and establish tunnels
// Continue the while(1) loop for next connection while (1) {
// Check for SIGINT
if (sigint_received) {
if (config->debug) {
printf("[DEBUG] SIGINT received in daemon mode, exiting\n");
fflush(stdout);
}
break;
}
// Wait for connection with timeout
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(listen_sock, &readfds);
tv.tv_sec = 1; // 1 second timeout to check for signals
tv.tv_usec = 0;
int retval = select(listen_sock + 1, &readfds, NULL, NULL, &tv);
if (retval < 0) {
if (errno == EINTR) {
// Interrupted by signal, continue to check sigint_received
continue;
}
perror("Select failed in daemon mode");
// In daemon mode, don't exit on select errors, wait a bit and retry
sleep(1);
continue;
} else if (retval == 0) {
// Timeout, continue loop to check signals
continue;
}
// Accept connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int accepted_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
if (accepted_sock < 0) {
if (errno == EINTR) {
continue;
}
perror("Local accept failed");
continue; // Don't exit on accept failure, keep listening
}
if (config->debug) {
printf("[DEBUG] Connection received on port %d, spawning handler thread...\n", local_port);
fflush(stdout);
}
// Create thread arguments
connection_handler_args_t *thread_args = malloc(sizeof(connection_handler_args_t));
if (!thread_args) {
perror("Memory allocation failed for connection handler args");
close(accepted_sock);
continue;
}
thread_args->config = config;
thread_args->client_id = client_id;
thread_args->wssshd_host = wssshd_host;
thread_args->wssshd_port = wssshd_port;
thread_args->accepted_sock = accepted_sock;
thread_args->local_port = local_port;
// Spawn a new thread to handle this connection
pthread_t connection_thread;
if (pthread_create(&connection_thread, NULL, handle_connection, thread_args) != 0) {
perror("Failed to create connection handler thread");
free(thread_args);
close(accepted_sock);
continue;
}
// Detach the thread so it can clean up automatically when done
pthread_detach(connection_thread);
if (config->debug) {
printf("[DEBUG] Connection handler thread spawned (ID: %ld)\n", connection_thread);
fflush(stdout);
}
} }
// This should never be reached, but just in case // This should never be reached, but just in case
......
...@@ -66,6 +66,11 @@ int frame_buffer_resize(frame_buffer_t *fb, size_t new_size); ...@@ -66,6 +66,11 @@ int frame_buffer_resize(frame_buffer_t *fb, size_t new_size);
int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len); int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len);
int frame_buffer_consume(frame_buffer_t *fb, size_t len); int frame_buffer_consume(frame_buffer_t *fb, size_t len);
// Helper functions for managing multiple tunnels
tunnel_t *find_tunnel_by_request_id(const char *request_id);
int add_tunnel(tunnel_t *tunnel);
void remove_tunnel(const char *request_id);
void *forward_tcp_to_ws(void *arg); void *forward_tcp_to_ws(void *arg);
void *forward_ws_to_local(void *arg); void *forward_ws_to_local(void *arg);
void *forward_ws_to_ssh_server(void *arg); void *forward_ws_to_ssh_server(void *arg);
......
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